diff --git a/.bashrc b/.bashrc new file mode 100644 index 0000000..34a7aca --- /dev/null +++ b/.bashrc @@ -0,0 +1,3 @@ +alias k9s='sops exec-file --no-fifo ~/stack/secrets/production.kubeconfig "KUBECONFIG={} /usr/bin/k9s"' +alias kubectl='function _kubectl(){ sops exec-file --no-fifo ~/stack/secrets/production.kubeconfig "KUBECONFIG={} /usr/bin/kubectl $@"; };_kubectl' +alias talosctl='function _talosctl(){ sops exec-file --no-fifo ~/stack/secrets/production.talosconfig "TALOSCONFIG={} /usr/bin/talosctl $@"; };_talosctl' diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +* diff --git a/Makefile b/Makefile index 1407141..a33fe85 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,12 @@ -include $(PWD)/src/toolchain/Makefile -include $(PWD)/src/make/tools.mk +# If using QubesOS, the smart card must be connected directly to the qube, +# rather than using a 'vault' qube. BACKEND_TF := $(wildcard infra/backend/*.tf) MAIN_TF := $(wildcard infra/main/*.tf) ENVIRONMENT := production REGION := sfo3 ROOT_DIR := $(shell pwd) -# TODO: automatically determine -TERRAFORM := $(ROOT_DIR)/out/tofu.linux-x86_64 -SOPS := $(ROOT_DIR)/out/sops.linux-x86_64 +OUT_DIGEST := out/tools-image.digest KEYS := \ 6B61ECD76088748C70590D55E90A401336C8AAA9 \ 88823A75ECAA786B0FF38B148E401478A3FBEF72 \ @@ -16,33 +14,46 @@ KEYS := \ F4BF5C81EC78A5DD341C91EEDC4B7D1F52E0BA4D EXTRA_ARGS := +GPG_TTY ?= $(shell tty) +PLATFORM ?= linux/amd64 +PROGRESS ?= auto +REGISTRY ?= git.distrust.co/public +VERSION := latest + +ifeq ($(NOCACHE), 1) +NOCACHE_FLAG=--no-cache +else +NOCACHE_FLAG= +endif +export NOCACHE_FLAG + +include $(PWD)/src/make/macros.mk .DEFAULT_GOAL := .PHONY: default default: \ - toolchain \ tools \ apply -.PHONY: +.PHONY: clean clean: rm -rf $(CACHE_DIR) -.PHONY: +out: + mkdir out + +.PHONY: update-tools +update-tools: + ./src/make/update.sh + +.PHONY: shell +shell: out/tools-image.digest + $(call run-container, -v $${PWD}:/home/user/stack:rw, $(shell cat $<), /bin/bash) + +.PHONY: credentials credentials: \ $(CACHE_DIR)/secrets/credentials.tfvars -.PHONY: -shell: toolchain tools - $(call toolchain," \ - HOST_OS=linux \ - HOST_ARCH=x86_64 \ - PREFIX=.local \ - XDG_CONFIG_HOME=/home/build/.config \ - make -f src/make/tools.mk tools-install \ - && PS1='build@distrust-stack\\$$ ' bash --norc \ - ",--interactive) - $(KEY_DIR)/%.asc: $(call fetch_pgp_key,$(basename $(notdir $@))) @@ -70,68 +81,93 @@ $(CACHE_DIR)/website/index.html: \ && cp -R _site/* /home/build/out/website/ \ ") -infra/backend/.terraform: \ - $(TERRAFORM) \ - $(BACKEND_TF) - $(SOPS) exec-env secrets/$(ENVIRONMENT).enc.env '\ - env -C infra/backend $(TERRAFORM) init -upgrade \ - ' - $(SOPS) exec-env secrets/$(ENVIRONMENT).enc.env '\ - env -C infra/backend $(TERRAFORM) refresh \ - -var environment=$(ENVIRONMENT) \ - -var namespace=$(ENVIRONMENT) \ - -var region=$(REGION) \ - -state $(ENVIRONMENT).tfstate \ - ' +infra/backend/.terraform: out/tools-image.digest $(BACKEND_TF) + $(call run-container, \ + -v $(PWD)/secrets:/secrets \ + -v $(PWD)/infra:/infra, \ + $(shell cat out/tools-image.digest), \ + sops exec-env /secrets/$(ENVIRONMENT).enc.env -- '\ + tofu -chdir=/infra/backend init -upgrade && \ + tofu -chdir=/infra/backend refresh \ + -var environment=$(ENVIRONMENT) \ + -var namespace=$(ENVIRONMENT) \ + -var region=$(REGION) \ + -state $(ENVIRONMENT).tfstate' \ + ) -infra/main/.terraform: | \ - $(TERRAFORM) \ - config/$(ENVIRONMENT).tfbackend \ - $(MAIN_TF) - $(SOPS) exec-env secrets/$(ENVIRONMENT).enc.env '\ - env -C infra/main $(TERRAFORM) init -upgrade \ - -backend-config="../../config/$(ENVIRONMENT).tfbackend" \ - ' - $(SOPS) exec-env secrets/$(ENVIRONMENT).enc.env '\ - env -C infra/main $(TERRAFORM) refresh \ - -var environment=$(ENVIRONMENT) \ - -var namespace=$(ENVIRONMENT) \ - -var region=$(REGION) \ - -state $(ENVIRONMENT).tfstate \ - ' +infra/main/.terraform: out/tools-image.digest \ + config/$(ENVIRONMENT).tfbackend \ + $(MAIN_TF) + $(call run-container, \ + -v $(PWD)/secrets:/secrets \ + -v $(PWD)/infra:/infra, \ + $(shell cat out/tools-image.digest), \ + sops exec-env /secrets/$(ENVIRONMENT).enc.env -- '\ + tofu -chdir=/infra/main init -upgrade \ + -backend-config="../../config/$(ENVIRONMENT).tfbackend" && \ + tofu -chdir=/infra/main refresh \ + -var environment=$(ENVIRONMENT) \ + -var namespace=$(ENVIRONMENT) \ + -var region=$(REGION) \ + -state $(ENVIRONMENT).tfstate' \ + ) -infra/backend/$(ENVIRONMENT).tfstate: \ - $(TERRAFORM) \ - $(SOPS) \ - infra/backend/.terraform - $(SOPS) exec-env secrets/$(ENVIRONMENT).enc.env '\ - env -C infra/backend \ - $(TERRAFORM) apply \ - -var environment=$(ENVIRONMENT) \ - -var namespace=$(ENVIRONMENT) \ - -var region=$(REGION) \ - -state $@ \ - ' +infra/backend/$(ENVIRONMENT).tfstate: out/tools-image.digest infra/backend/.terraform + $(call run-container, \ + -v $(PWD)/secrets:/secrets \ + -v $(PWD)/infra:/infra, \ + $(shell cat out/tools-image.digest), \ + sops exec-env /secrets/$(ENVIRONMENT).enc.env -- '\ + tofu -chdir=/infra/backend apply \ + -var environment=$(ENVIRONMENT) \ + -var namespace=$(ENVIRONMENT) \ + -var region=$(REGION) \ + -state $(ENVIRONMENT).tfstate' \ + ) -config/$(ENVIRONMENT).tfbackend: | \ - $(TERRAFORM) \ - $(SOPS) \ - # File is not committed and this has no shared state - $(MAKE) infra/backend/$(ENVIRONMENT).tfstate - $(SOPS) exec-env secrets/$(ENVIRONMENT).enc.env '\ - env -C infra/backend \ - $(TERRAFORM) \ - output -state $(ENVIRONMENT).tfstate \ - > $@ \ - ' - $(SOPS) exec-env secrets/$(ENVIRONMENT).enc.env '\ - env -C infra/backend \ - $(TERRAFORM) refresh \ - -var environment=$(ENVIRONMENT) \ - -var namespace=$(ENVIRONMENT) \ - -var region=$(REGION) \ - -state $(ENVIRONMENT).tfstate \ - ' +config/$(ENVIRONMENT).tfbackend: $(OUT_DIGEST) infra/backend/$(ENVIRONMENT).tfstate + $(call run-container, \ + -v $(PWD)/secrets:/secrets \ + -v $(PWD)/infra:/infra, \ + $(shell cat $(OUT_DIGEST)), \ + sops exec-env /secrets/$(ENVIRONMENT).enc.env -- '\ + tofu -chdir=/infra/backend output \ + -state $(ENVIRONMENT).tfstate > $@ && \ + tofu -chdir=/infra/backend refresh \ + -var environment=$(ENVIRONMENT) \ + -var namespace=$(ENVIRONMENT) \ + -var region=$(REGION) \ + -state $(ENVIRONMENT).tfstate' \ + ) + +build-%: REVISION = $(shell git rev-list -1 HEAD -- images/$*) +build-%: SOURCE_DATE_EPOCH = $(shell git log -1 --format=%ct $(REVISION)) +build-%: images/tools/Containerfile | out + export SOURCE_DATE_EPOCH + cd images/tools + $(call build-container,$*,$(VERSION),$<,$(SOURCE_DATE_EPOCH),$(REVISION)) + +out/tools-image.digest: out build-tools + +.PHONY: plan +plan: out/tools-image.digest + $(call run-container, \ + -v $(PWD)/secrets:/secrets -v $(PWD)/infra:/infra, \ + $(shell cat $<), \ + sops exec-env /secrets/$(ENVIRONMENT).enc.env -- \ + 'tofu -chdir=/infra/main plan \ + -var environment=$(ENVIRONMENT) \ + -var namespace=$(ENVIRONMENT) \ + -var region=$(REGION)' \ + ) + +.PHONY: new-apply +new-apply: out/tools-image.digest + $(call run-container,'\ + echo $$GPG_AGENT_INFO; \ + ls -l /S.gpg-agent; \ + gpg --verbose --list-keys \ + ') .PHONY: apply: \ @@ -157,25 +193,3 @@ apply: \ $(CACHE_DIR)/secrets: mkdir -p $@ -# Note: Decryption MUST reset the mod time to avoid encryption/decryption loops -# Encrypt if: -# - Both files exist, local is newer than remote -# - Only local exists -define maybe_encrypt_secret - test \( -f $(1) -a -f $(2) -a $(1) -nt $(2) \) -o \ - \( -f $(1) -a ! -f $(2) \) && \ - $(SOPS) --encrypt $(1) > $(2) || true -endef - -# Only decrypt when local files don't exist -# Unfortunately, this means we can't decrypt if the secrets update. We can't -# do that because otherwise it creates a loop. The secrets update, therefore we -# decrypt secrets, but because the modtime of the decrypted secrets is newer -# than the encrypted secrets, we want to reencrypt encrypted secrets. -define maybe_decrypt_secret - test -f $(1) -a ! -f $(2) && \ - mkdir -p `dirname $(2)` && \ - $(SOPS) --decrypt $(1) > $(2) && \ - touch -d 1970-01-01 $(2) || \ - true -endef diff --git a/images/tools/Containerfile b/images/tools/Containerfile new file mode 100644 index 0000000..2026c52 --- /dev/null +++ b/images/tools/Containerfile @@ -0,0 +1,52 @@ +# Tools used for managing the stagex stack + +FROM stagex/core-busybox@sha256:cac5d773db1c69b832d022c469ccf5f52daf223b91166e6866d42d6983a3b374 AS core-busybox +FROM stagex/core-musl@sha256:d5f86324920cfc7fc34f0163502784b73161543ba0a312030a3ddff3ef8ab2f8 AS core-musl +FROM stagex/core-ca-certificates@sha256:d6fca6c0080e8e5360cd85fc1c4bd3eab71ce626f40602e38488bfd61fd3e89d AS core-ca-certificates +FROM stagex/core-zlib@sha256:b35b643642153b1620093cfe2963f5fa8e4d194fb2344a5786da5717018976c2 AS core-zlib +FROM stagex/user-gpg@sha256:92946bb4143ecbd53999cd520fbcb958aecacbac7a85bd58a758be1b57086a9c AS user-gpg +FROM stagex/user-npth@sha256:6ac9a90ca714ba01911c1f617553a5b23b96e9e37ec4a21e5ba132c4886a70e9 AS user-npth +FROM stagex/user-libksba@sha256:c165fb5b7949473cb00b0fe59add90663346b33c6c682309ca0fcccdcf78d569 AS user-libksba +FROM stagex/user-libgpg-error@sha256:6d7c09e3a7d055a6722910439c533f2babc8eda24b636bf4dfb2b29a3ed6327a AS user-libgpg-error +FROM stagex/user-libassuan@sha256:dea35799659be7b85e523312c55621007b1918ff3590631155ecf2c699ca470f AS user-libassuan +FROM stagex/user-libgcrypt@sha256:384f0e703afad6f8885ec77fb814ef182a08600a2032183d231fee5c048a7d2d AS user-libgcrypt +FROM stagex/user-opentofu@sha256:48fb7bb6504493a95d248c571cc7f3a5cb505edd5007a5f431d3422bf40c19a4 AS user-opentofu +FROM stagex/user-sops@sha256:4a1bd25239d1196bba261303ca657383e634f136f6e5fcb4e368fdb6dcda086c AS user-sops +FROM stagex/user-talosctl@sha256:23ff2d686a0c251db4f8a8f07e9b18c81c64eaa07da97de5a75fccbea3e595c4 AS user-talosctl +FROM stagex/user-kubectl@sha256:878a726130e9c3ea2f41c23725b325a8a4c3c7555971c511fef099daff037753 AS user-kubectl +FROM stagex/user-kustomize@sha256:b5ddc79510731ed6fb9664d2e9ed95e89ec1e58d66a23d2871ec8018c09ac0c9 AS user-kustomize +FROM stagex/user-kustomize-sops@sha256:bcf69eb5e16d280e2989fb028069b8a57b14084d954a3eba3dff3921f1268913 AS user-kustomize-sops +FROM stagex/user-helm@sha256:e7d2e13db8483f5356b96337308edbd5a0e602cc76c4c5ea5ed730ae6d2b2dcc AS user-helm +FROM stagex/user-k9s@sha256:1bd57f44fbf57eeaa3b13baa37293f8294eb392f4493eb8887b9ad62e8f01db3 AS user-k9s +FROM stagex/core-bash@sha256:a4601014df6ed004e0a81f65159b7f9dbdaec73db679ddef338b58ac4b85f0da AS core-bash + +FROM stagex/core-filesystem +COPY --from=core-bash . / +COPY --from=core-busybox . / +COPY --from=core-musl . / +COPY --from=core-ca-certificates . / +COPY --from=core-zlib . / +COPY --from=user-npth . / +COPY --from=user-libksba . / +COPY --from=user-libgpg-error . / +COPY --from=user-libassuan . / +COPY --from=user-libgcrypt . / +COPY --from=user-gpg . / +COPY --from=user-opentofu . / +COPY --from=user-sops . / +COPY --from=user-talosctl . / +COPY --from=user-kubectl . / +COPY --from=user-kustomize . / +COPY --from=user-kustomize-sops . / +COPY --from=user-sops . / +COPY --from=user-helm . / +COPY --from=user-k9s . / +USER 0 +RUN \ + mkdir -p /run/user/1000/ \ + && chown 1000:1000 -R /run/user/1000/ \ + && chown 1000:1000 -R /home/user/ +USER 1000 +ENV PS1="[stack] $ " +ENV KUSTOMIZE_PLUGIN_HOME=/usr/lib/kustomize/plugins/ +WORKDIR /home/user/ diff --git a/infra/main/main.tf b/infra/main/main.tf index 0290012..6f8e3b5 100644 --- a/infra/main/main.tf +++ b/infra/main/main.tf @@ -125,7 +125,10 @@ locals { # `jq .database_users.value.forgejo | sops --encrypt` output "database_users" { value = { - for db_user in concat(module.digitalocean_database_cluster.database_users, module.digitalocean_mysql_database_cluster.database_users): + for db_user in concat( + values(module.digitalocean_database_cluster.database_users), + values(module.digitalocean_mysql_database_cluster.database_users), + ): db_user.name => { apiVersion = "v1", kind = "Secret", diff --git a/secrets/production.kubeconfig b/secrets/production.kubeconfig index 9a4660d..149f873 100644 --- a/secrets/production.kubeconfig +++ b/secrets/production.kubeconfig @@ -1,41 +1,41 @@ { - "data": "ENC[AES256_GCM,data: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,iv:Jh35HfEzpjSOHVATn81jArsnjX1f+CViZZdwngJWp9A=,tag:AuCQNC35S9U3DgyWp1MjfQ==,type:str]", + "data": "ENC[AES256_GCM,data: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,iv:xOVKVjKXjbRMwO8luEu+nmiPlfYL50d8qHCstL9eyKw=,tag:P3AqxLumowreVRHxlRmxvg==,type:str]", "sops": { "kms": null, "gcp_kms": null, "azure_kv": null, "hc_vault": null, "age": null, - "lastmodified": "2023-05-17T02:28:37Z", - "mac": "ENC[AES256_GCM,data:aL/a8y2g5+maOSWg3QnhbNQPMlQnoI1fg0PKb4o5F9mUMWJdXHoNkXDUGfI3w8J+zgH8lm2M2hsYuVwcyX5Tyllbq+NHYWvDrK+34oRxAkdP6JDK2ZOgn+SofjKtN2y6EVfnU66I1UnAZcmzfoANFVfy2qvbZdw8j2+K3cxBS/I=,iv:8U4CvwSmR5rN2yE+l+idJ/sjUTNgoTgxut70iJU6mD0=,tag:7N5VK1T1JXJLEkeBS6tgfw==,type:str]", + "lastmodified": "2025-07-15T05:34:17Z", + "mac": "ENC[AES256_GCM,data:MMDysUs8QMtd9uxROg8/3dH7mJUMK6L1T4CD5kwBP1vNct55IykW/3l6+U1OlUHuuPLNr0bFHPUdZXNPuWvdN+QSyvHL8GMo5rl1O5x6IE+kW7hZruF/lAmf/ncHG3zb2Qt/WQ+1SQMn0XW/a5VsNwd5ONAQ26tPl7K4HGIL0E4=,iv:64XDV3N+77Hh8TCaxfGkvWTlW/oA+pp0CziAm3IqDag=,tag:IM9CvDtX7C7ce6BKsTgWaQ==,type:str]", "pgp": [ { - "created_at": "2024-01-11T20:54:25Z", - "enc": "-----BEGIN PGP MESSAGE-----\n\nwcFMA82rPM2mSf/aAQ//cL5kWpuvvKmUZDxXfpUQ2+uSA3fS4sPaMNRXd9gEuuaa\n183cTt4/FDXedTSbv6idekbF7vc5sHfuUxdrHO5xy8fIZoK3JpO38N4q40VpoI3K\nNu4yYRlRXeVwq+tzHmW9mjnSmV8VJbb6O58gYMdk/2IOeRCz+EB3cCUgF7P5bsgY\nDfW7Y4CyZ3VlUEBNcx44mKh5jQIZFuvxlyoPYVjVm3r0GUZqLE9on3ubMwv/vUmZ\n4WdbiI1Fh7XeRlXYZuL8xKBB/5eGOfFeOIcKR/lhDN7WXi0lvf0RNa9vobkecnql\njVZ+YuL20OI23gfInnYgVEn9IPAS3feD7RBLmRI8l41akZvtj24MRAs750S6W2wl\n92i6/5Ev3tTTunzYKt4wSl7/0bAMqQWNx4zHrMo7KG9pR1W2y0effS9Jvkv+dFru\nrE9mpnDexbCIVsr/Ml3VmuMjADaeqip2a45awzOi83gndFjayNnG40MMu3JMWblA\nOzfZzMGsu9zHe+J2SeQTWDSdl/VfZCN/AGEtYYv91gmHl8yK1YDG2IR8pzyomcCb\nqPBSRoY9NbkLw3NYwwwWYb+bOdjbAXTLPHl5+aBYnf0+HeLG7YrxAWYlQyl6RXmf\nIKtb5550KqpZY8jZC1Qc8iRFVWB4mijILQEPHV1qsSMuh0QdtvdGpx7Z19crTrLS\nUQEg69Thd6IwT+oe5UU8JjfgK7zvMStgneJayXLdRgD54uDpXqAG2jT0MWotepZF\nXPr9veNqoUSNA6VYhS2iNplaauSdRZua/WbELMRAHiu/NA==\n=XHXK\n-----END PGP MESSAGE-----", + "created_at": "2025-07-15T05:34:17Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nhQIMA82rPM2mSf/aAQ//aLysBgnkhnU6Pa6+N15daXCmJeBpION5WVMJ0uSqBKF6\n8Tl7+HnU7xwZOam8/PuFQIkMZsXUq479w63ufPgT/NAZcnhjTpL7D9N9r/Kn5GN1\nRHCnGo6AJwVor/TQzt0ZquUs/r782vvYWpnFqfbRSe0e/6ttRhnHdmLvSPCaFBJi\nwLXTTZjmSAfkRle80rC4BZyzooV2TG2Sn0ZIJIfebd4TQqKq+5umzCRG4533w56R\nz14n5AVdss8qjJklecoqSTUIcckHpU17xFMfqILzd1EqA6HP0x3QUaYggmAXBAMS\nQiz1rHcHi0c7YJlhKSpuCpXzcTVvKq0DLn65S8F7AzMlp99CfZC97uUh+e7vUMke\n/XSZS9IyENJupXEC/qiVR2Z1s7XAF0m5HdDvJkdHMzqbGXI/usmvEjWZokuMPmWT\naiYezNlPHoGCRL/B+08/6FHDrgUnECFmY+MUqlq6ZrmkocFhqBUT7cURbyshwLlE\n4NMndQyxdUtwRcP5/qbiSCdEsR/V3eCnwx7AYAR0cy6bgvNV3su6VR6rJwP/1csc\n7zYP+88lxxNblLWvFA9PlfvC/FdTM9jWj5uSq7+ZvdJRLvBtChbl16ZsqwFkZZ0k\nAFpBaxr38n2bROo/fJg2FilrYRQEQ4xgiFYKC5rylMBqtAD2USs/cjtaMP7zFsLS\nXgHIvdc8LvqEsegt57suEk8AVWNcZMF3IMlsyT9LbWFAQkX2o/Hq39CJLKx/M0IE\nsiUbLuMqbePeWoDHHKiZ3DRdi1R6eNzYcG+cmMPr++Lof8g3qRo2n7M0L98mptY=\n=tDBH\n-----END PGP MESSAGE-----", "fp": "6B61ECD76088748C70590D55E90A401336C8AAA9" }, { - "created_at": "2024-01-11T20:54:25Z", - "enc": "-----BEGIN PGP MESSAGE-----\n\nwcFMAw95Vf08z8oUAQ/9E5SRF/GE7OJ3jYi0j1TAqh0heJVBbqXGPDeOclavInxu\nSRZ083aVg4pbuy5F7SM+rWr3VIIOGZO3RJ0iNK/raCRJKDn97vSls6nZG6je5qFJ\nL3n5Lt8hSySBbvQ2Fh6AjoA5o0sJ+PgNVsKCSx+1LRdmytqqzvM8Ux9NrI643Tuj\niVb3n2X6Qovi1PrDO/0L0XCENFNsauSP5V3QtS8dn2jp3grsY5dj4KDLMk4pfJpb\nhnME2Fslf+HOu8G/sacp9+7AapMPLn7Ih2O/ZWBpsjLyyg3Nuc1+Yxi4D+wH8BGb\npqSE/KV8NLhVKdOvomtDn0GlXhn58MgoHx2SnEdOMpgyS14AF0cchtFc/TdRU+Eg\n2AL1nQYWcorwPp/GSgImLOPet5rxnHemlbzqUVS+RJQrf0pgynASl2N4oEONndED\nOV5h95f3CHzIBvrjO6BlhfAJ8BOn4aBXIe9snigu6gIH6qAoKDWdtPjUPzCiLj9B\nUEji39KYGbeIHKMznzIWKbdDYfbtRcToIehGU9kGvNxBMsK15osBtbxJcMpXRrUn\nsEdf7wX5ORVTmnBIBkZxuAS9161BPbKJCe/k+v2hOZ9odmZkdxifZMpGSEW3bRgr\nZ49XnTS5feXHffu4MUbe8LulDCv9YVuwn9otNJIeT4ws8l+YJ/L88uLOZpKLrh/S\nUQElpCXfgP0GWj77I5EsnItd+7hdFA/dGeQEsFzPwdLNCglhevTmc5rVPwc7ha9G\nHUgSjZO1e83gnEs+VPImgXElD68hs5rJa8WUj3YrUYaVEA==\n=No08\n-----END PGP MESSAGE-----", + "created_at": "2025-07-15T05:34:17Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nhQIMAw95Vf08z8oUAQ/+NL7rhrDCJhneBPNxS2Rlev2E/rzuQwx2rSRk0/YwQW0l\nJdzlJPK2h4gTwKbFOP++y8A1BWvobNQrvSXAHHw+/cvz2zLi9CwAKQ2KAONkb9uy\neA0UwFnYv/WDxxy/Qz0dCdOfawAHGmV/P9h9mR6yZRE8gelfcN7wmXzgv5urf09Y\n4ZRwqotqP4hk8a76PTpjaFtYsW2BcjAL01vDHoBkcQBnVEAI0OkzYMeUapEqJMOm\nJdB6wUUG6yPA5eumVBvDuxnw+GTbG/fMAvShD2Xm/kpN76lefFhWyHYKKbJ2F3/L\ndk3RiWCy/2OKIUucdfOn6uQg3Q0MVcX139LqKIRx8DmI6euvfEm2Q828Jxwb+cdk\nQ9RvLUkHFjS2eIVUzsrZuump88CxW3icbWsXfwWrKFR8royWLn9iqqNAL+17WmrW\nYWeMKDdtXDpODfwvOkULE1hXpxYJOE3pVTMhWMBnGIuANHn+Vo7p85ceyn9rEXac\nMkTUE/nxPrl0qWq886GknVsB/JCk8ISaFIVZh2VUsgsbDWxwx8VBkl6Ok/vpbztd\nJvsqABAco/mSjRm7UxDsnPT1IEUedfG340+Yk9WC/tEUboR+AjcoXMzZR5MYJeGN\nDFpwdjnKd7A9GRzGMEub7owkpbGLC+oCmUTkiEM4/cqvVVhty7c8lKcLg3Y4lTLS\nXgEnLHG+Z4S5klxlru2LjAQ1/du3+/NKOjx41Hh3dMtHAXuZeorGvAB4b1wAGt5s\ntChf2KUHzW7yRevaStZZEyYChQjcmu+phO1vb4O0UekitiCsoZMJKWGeowwWGQ0=\n=bFvC\n-----END PGP MESSAGE-----", "fp": "88823A75ECAA786B0FF38B148E401478A3FBEF72" }, { - "created_at": "2024-01-11T20:54:25Z", - "enc": "-----BEGIN PGP MESSAGE-----\n\nwcFMA0/D4ws+/KPtARAAmRT//kcFnXUhEi/pB2Ie8Dzt0rZrTSe+uiAUdtwa9nIb\nSEWjRW5NVt0Ayg+EA2QdUl5H3J7hz6kIJSnpf1jmJEDc+xUxkfZd+iLJPdcA3r8n\nO9WMFVzpgNiFL86sKLfl+OHQwQbjvNupUtSlP/ZotVaDLCmjjNmxeDHYrBhEJtkU\nKJysynqosqAZDU0bK/gbDwz9CqdYHVLiMBrIpiLnAX1qm3sdHuADQHsYxZTktv6T\naFcRU0V5zVgvgcEEtlG4Rcj6xZkjOs28buihYEcBSip3rrmdInrQyTlOR8prp8Tj\n0+XqT05VxpCEmAbfFyM5v+ntsY0iLQPv2LgNXAU7xJ8Ac5O6sJRdptpKuyYauQm4\nxjNaUTf17uSm4ZmNQzV+8XZcSXqzKw8gcJXv9ZuagQwGCCrwnhrtdo1fJIcl+09l\nzF3llH1qGGN+BaenRaACvNRfDEpl6Sl+xAchN1IOD+84ztvatxd2REomScjtwHRx\n14FUZ8egKLb0M544jBdYcAU18PO2+jawZlbxHTdWDlLj7ZmGRXQxqsnAYVKEqmg7\nU66JmcDXwIqqE9Vn1rc00sH3/O/n3yhpIkbLzTz1KbS2ePvZ1LMKr1SihHpLyFQN\nIPKPJ4Y5bWgsrSs39hRYMAT1/bqIFpJ/EGRBC6kwR1nfkR/QDtgJ+97/cw96ROjS\nUQEJ3rDXixvC3wfGnz3KAiC0Ea9meOxU/2SU7TzXb/MMbpUKfBwSduB9/pl6RgLv\nVZVZB6uz/zhf9omPMCtdLd2RgOFAUe8tyBtr9V54q7Ormg==\n=egrw\n-----END PGP MESSAGE-----", + "created_at": "2025-07-15T05:34:17Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4Dr/MjkOzuuRESAQdA+mGKo1MvpDWw09U78BtR/V45L30BjIJSeMKbeWglXw8w\nnbB9iWVInYnEvOkj07kKeeyUtYke8EtkiuekNFWblCBYOYR9jBA4FjSsh3F7FJtb\n0l4BHQdHnh3/IVhbe4Sr4p4P5IMM7rXC40pVmg+YU2EMdyWjiElfN77vFTtjpS5X\nEsEIlnuA96NmFdDZAIV8a8kuD6yjeghWU4HHBycWscvpDL580MjsgsFQLo9+w2VU\n=4OaW\n-----END PGP MESSAGE-----", "fp": "3D7C8D39E8C4DF771583D3F0A8A091FD346001CA" }, { - "created_at": "2024-01-11T20:54:25Z", - "enc": "-----BEGIN PGP MESSAGE-----\n\nwcFMA5Wf+FyJ+zFJAQ/+MqLW0h1wkXIQaaqYaxD/yUDq/icK4ePF4Gfnh9x+2VLj\nkdZbGal+JMK3ZhlM5F4vA7iIIkg13jfDbft5yxCsAHaUe7IiUjHXqd0vgiQXBJMI\neHaUlPfHmCrDITrs0uSPEjPLQ5YfCPp/eY7YkdVh2Lw9tPJfd4I2BgVCYV8+O1Bi\n9rAR3CaGMwrr8ryJ+Ayyal3lbSo/ptXLNYVSoWyuhKhRExmv7YLrr7PtrlP4vIQ0\nINsEp3dy10oomd4Z2leaH3Nn/TUgbRakTTrqOKzxP3E9sBfEAAgqt78GvADZc8AF\nir5uvFd663ipuHNADH4kk2okgSB9MX8w6TNgU7J+luoZfrs/w7ZbhUnv63RTKcw/\niQZ/vdUjfp9oPYOaYJuU6YTSjU65VXXo7sIembI4JEnypTguziPTWH2afDWMFvDr\nuA+36X2KNyhtkrZp7w+O9YgGnFfnpG6aCek4feDrrrH75m/ba80Yn68Lx74ejHNJ\nz8x9C8I87WMPbEWWxq1/7LHTxJ0CezOR2bcjw0/FbOPWS/jtUFW5Xna3ow7vRogg\ndP2ryQACgKcJqbaxzLIR+5cAMLkm1YsHSUre8bckAS4anfXTAHlpIqu5UH4QdXD9\nMe3C1WsZmiP2nwb1FF27szjAwhia800uHPquGJngPFA7EX72VZeS4pph7xb+aNnS\nUQEldBeGaRAk8L+np7RsaqZYd5NLchGCslHeR7ZnIIpY1clPbsRsOFHFLlm3rLp3\n+usi8XfcF60SYDo61l+pKHK4HsOIdwepDEGPHg6Q+frbMA==\n=rc9f\n-----END PGP MESSAGE-----", + "created_at": "2025-07-15T05:34:17Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nhQIMA5Wf+FyJ+zFJAQ//anxn/CZnetAAuXIpeFxKdEK/HXMJpndX9ZSYyislSByb\n3bu6vFFXS6xDrmRTscjEAssMyLMh/j9mrrwFlpGxQ4bW3oS8cJPMWt7eRcTk21Dd\ngH9t85iDZ/H8ak+/7lNx2lvnqxpmHNZABqMEXH9nu2O04Xj/mrSTRZfEjMUZSjQ0\nDrUuCnLG/7euMewPCAcar2vpRZ/QT0RNO0DncOj48ZJAoMCRCgDvGYG3HrRa75Cn\nju7fMaar9iuoBsD6o4xxUFCLUEuWyyAweZhRKpeIxrujUhvLI7lNLZZ1G5OXufgz\nF0+0UDaXqdLcWxyPxjXOjtSWaLtabyX3Cej2bYTJN+XXUbhyztBeTGrAJZ5VJ0nu\nZf6WXmAefbJyODkpyRzWO9usyVmO6HuozvFwph8yv6APbGHMpCxFRmplr0+o/apE\n3tMGmGaOQjCZVrL+/acRn4YoHkyLXLtUZ+QbAJT3nHlmxB+CPV4sRoKoW4+CHLTr\n5CAyNxo7dHdXZWtavA5p9tg9AMAHYyy5veV8kUKG48ZNCx5YMVUg7K19jxalswbX\n8UBtuyPfMcRVW4VvFZYVNik2tHdP4/mXJOCoIV9wixR47e60xMvy1tL+C3M25bel\n8rBzjZiYBI6GrqPmN7GTv7b7N1HywVmpjLtS6yVAI7kxFbMgjKyEwYrgJakdxQbS\nXgHw7iwNeOp3hBcffjIbQ0tgdPrh90Yy946U/hc7vvdpRRvQG8iWW8Yrbdfellxm\nA54UPtTFYEgkS4MnoOoN6S+eyWHi+j+fdJ+ECbH5zfS0WWKMgH4TtevOgmr7aYY=\n=Ditf\n-----END PGP MESSAGE-----", "fp": "F4BF5C81EC78A5DD341C91EEDC4B7D1F52E0BA4D" }, { - "created_at": "2024-01-11T20:54:25Z", - "enc": "-----BEGIN PGP MESSAGE-----\n\nwcFMA8KRInHl7Vz+AQ//be6WKvWqCUNYgupIyAzLDRc686xkMWSOC73+N017o/GD\nxKciwGcnjB0Ta8kBCo0DnIOacjWf8rEeyycmQqBqO3JKXNzo4fYCczRQ6V6IGLVW\n6UM5CtJOfD3A2UOGS7iq96tKB8eGUQ2KEXJp4qaxV42jTtfNLOSX7+YLcT6Uuu+8\n+w0i2tYLCpY5FdXYRw3YLVnUi6DBcD8NkA08lKfuKycS96tJrtPpGD/EY33+p3k5\nftVFhAi4WreLRJ+3+BYxXkwvSniNIBdY+iGbIzfygEVBbmTQ/4EGVtax3GIWJHDb\nJmL/ERHkLEPXOalej2zinymWOPgBLN1TZlQAEnLcfHfBI4awIIFJIQPGFR2aAsFS\nFSKWGmaCMFb2sSqswJ62T3v3Axak0h0nLCtkm/5p+SuqbpQU+QOMUAU3JLH3UZM2\nqTI88N8jU5Zuba3DYW0dDJcuhq4bEkCWmygt7625tZpZIt/e+BwKM2rmoljag2bC\nuWELOUXlVqJLvxSGNku2GA+uBzsY8xUw6iVy40JlzVuV2RwANg+fXWBdzdTi1Lid\nq28gqiZypshCfl03LZqjUuXwi93z0sc/N+1qQHVHflgAAIyrA5zUD/1tHaH8N2kR\nINBO084n11n8gMs5lMyf48RhC4BkluCKG9upjhfnciCrnjCgotnHnqQp3SZmUdbS\nUQGfJGcklzJOruaIpK8FUEdBm+sgtUTWO2HAe4UPlkf6r3rQWr92X+jv8ziBrHhl\nQ2+W85Yt69mIxpiqdhWZt24WS7KUEAoeb/LwExG4qA4XlA==\n=a/0D\n-----END PGP MESSAGE-----", + "created_at": "2025-07-15T05:34:17Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nhQIMA8KRInHl7Vz+ARAAlRWI9dYAvknki0l3euon0PcCtBV4e/Ww7DFzPxulaxVH\nxkPlYsKg5kSqge/qOq7vYgCjEHfve8xgZ2iIp8tj0lvxhJKf5E7uxHOxnw3uB7KC\npjzKWzxEOiQO2xTBKFWZjM2GckPZmz9/Y/xdAzsK0ZFgU6Bd2qPlBkN0A1BrOKDT\nhgRAHOER8bSlQbNfqsj6aVYjfjqf3one08IVjKNPquziYl+vO96LCyBdF8vFx6i8\nAb/CQVj9PXmZtNfGEJo7GVh6+O9XMqfz0OK5r9i3c1NwKfv4CaBRzX1T5StpRsSs\nTEOw02BurrRk5xhhZLWfDXqVReAY/c7RNqLnVLkeyobTs3jNzbBYS/ULe4WOc86X\nFDhjHOa6q15z/K4aXEGDYs8RLudkboX8s81WKynN6J0JXeHRTksQnPVKW3tpkTNU\nqIS/f4inxCKs/DjOzUfHaD4Qd4UaTq15PPEuMTP/a7vNhWzRgzESzHet+5CAgbj4\nfbB2QQIrAE/nM2mcKJhzJZZUbWzsWbXPFkeo1KMzCNQy8CMVlEx8EIyBQWrl99SQ\n7Dx/icbwvIPJo2Iz5m+Zmwq1lFtWyja9scjKaldA50MZ9SlyFKYQg+XNAMqDUEuw\nH4Z/d1xR/XSfoeZc6F70GJ+lcr64hKMeUYXnE8jg+i+brmN7or0LGEYH5vRQ6irS\nXgE6rZt1FdrnqpYNolbCpgIfYWwXuqUPOQxJwvWqmFhu58nOB/wBfUPfaWssK31Z\nw4WNIZyfpsUSZVwawaKu5CeoIrdJXqw6vqhf8h9x2+96DiaQvmPjcqFJGotLzJM=\n=6HRB\n-----END PGP MESSAGE-----", "fp": "C92FE5A3FBD58DD3EC5AA26BB10116B8193F2DBD" } ], "unencrypted_suffix": "_unencrypted", - "version": "3.7.3" + "version": "3.8.1" } } \ No newline at end of file diff --git a/src/make/macros.mk b/src/make/macros.mk new file mode 100644 index 0000000..68036cf --- /dev/null +++ b/src/make/macros.mk @@ -0,0 +1,50 @@ +# Note: Decryption MUST reset the mod time to avoid encryption/decryption loops +# Encrypt if: +# - Both files exist, local is newer than remote +# - Only local exists +define maybe_encrypt_secret + test \( -f $(1) -a -f $(2) -a $(1) -nt $(2) \) -o \ + \( -f $(1) -a ! -f $(2) \) && \ + $(SOPS) --encrypt $(1) > $(2) || true +endef + +# Only decrypt when local files don't exist +# Unfortunately, this means we can't decrypt if the secrets update. We can't +# do that because otherwise it creates a loop. The secrets update, therefore we +# decrypt secrets, but because the modtime of the decrypted secrets is newer +# than the encrypted secrets, we want to reencrypt encrypted secrets. +define maybe_decrypt_secret + test -f $(1) -a ! -f $(2) && \ + mkdir -p `dirname $(2)` && \ + $(SOPS) --decrypt $(1) > $(2) && \ + touch -d 1970-01-01 $(2) || \ + true +endef + +define run-container + docker run -it $(1) \ + -v $$PWD/.bashrc:/home/user/.bashrc:ro \ + -v $(shell gpgconf --list-dirs socketdir)/:/run/user/1000/gnupg/:ro \ + -v $(shell gpgconf --list-dirs homedir):/home/user/.gnupg:rw \ + -e SSH_AUTH_SOCK=/run/user/1000/gnupg/$(shell basename $(shell gpgconf --list-dirs agent-ssh-socket)) \ + --entrypoint $(3) \ + $(2) +endef + +define build-container + mkdir -p out/image/$(1) + SOURCE_DATE_EPOCH=$(4) docker \ + buildx \ + build \ + --tag $(REGISTRY)/$(1):$(2) \ + --output \ + name=$(1),type=oci,rewrite-timestamp=true,force-compression=true,annotation.org.opencontainers.image.revision=$(5),annotation.org.opencontainers.image.version=$(2),tar=true,dest=- \ + $(EXTRA_ARGS) \ + $(NOCACHE_FLAG) \ + $(CHECK_FLAG) \ + --platform=$(PLATFORM) \ + --progress=$(PROGRESS) \ + -f $(3) \ + $(dir $3) \ + | tar -C out/image/$(1) -mx +endef diff --git a/src/make/update.sh b/src/make/update.sh new file mode 100755 index 0000000..0362522 --- /dev/null +++ b/src/make/update.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +TARGET="Containerfile.tools" +SOURCE="https://codeberg.org/stagex/stagex/raw/branch/main/digests" +STAGES="core user pallet bootstrap" + +TMPFILE="$(mktemp)" +DIGESTS_TMP="$(mktemp)" + +for stage in $STAGES; do + curl -fsSL "$SOURCE/$stage.txt" | while read -r digest name; do + echo "$name $digest" >> "$DIGESTS_TMP" + done +done + +while IFS= read -r line; do + case "$line" in + FROM*stagex/*) + full_image="$(printf '%s' "$line" | awk '{print $2}')" + base="${full_image%@sha256:*}" + prefix="${base%%stagex/*}" + registry="${prefix%/}" + path="stagex/${base#*stagex/}" + + rest="${path#stagex/}" + if echo "$rest" | grep -q ':'; then + name="${rest%%:*}" + tag="${rest#*:}" + else + name="$rest" + tag="" + fi + + digest="$(awk -v n="$name" '$1==n{print $2; exit}' "$DIGESTS_TMP")" + if [ -z "$digest" ]; then + for stage in $STAGES; do + staged_name="$stage-$name" + digest="$(awk -v n="$staged_name" '$1==n{print $2; exit}' "$DIGESTS_TMP")" + if [ -n "$digest" ]; then + name="$staged_name" + break + fi + done + fi + + if [ -n "$digest" ]; then + if [ -n "$registry" ]; then + image_ref="$registry/stagex/$name" + else + image_ref="stagex/$name" + fi + + if [ -n "$tag" ]; then + image_ref="$image_ref:$tag" + fi + + echo "FROM $image_ref@sha256:$digest AS $name" >> "$TMPFILE" + else + echo "$line" >> "$TMPFILE" + fi + ;; + *) + echo "$line" >> "$TMPFILE" + ;; + esac +done < "$TARGET" + +mv "$TMPFILE" "$TARGET" +rm -f "$DIGESTS_TMP"