diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000..0a11de2852 --- /dev/null +++ b/.envrc @@ -0,0 +1,9 @@ +# direnv: automatically activate flox environment and export helpers +use flake || true +if command -v flox >/dev/null 2>&1; then + eval "$(flox activate --sh --impure)" +fi +# Put repo scripts on PATH when opted-in via .git/safe +if [ -d .git/safe ]; then + PATH_add packages/scripts/bin +fi diff --git a/.flox/env/manifest.toml b/.flox/env/manifest.toml index c0ce1ce55c..2d6c359d1d 100644 --- a/.flox/env/manifest.toml +++ b/.flox/env/manifest.toml @@ -22,7 +22,9 @@ pnpm_10.pkg-path = "pnpm_10" go.pkg-path = "go" go.version = "1.25.0" golangci-lint.pkg-path = "golangci-lint" -dotenvx.pkg-path = "dotenvx" +# sops + age for secrets management +sops.pkg-path = "sops" +age.pkg-path = "age" awscli.pkg-path = "awscli" ripgrep.pkg-path = "ripgrep" uv.pkg-path = "uv" diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index 83d4a91b00..9ea3f74957 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -9,7 +9,8 @@ env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} # You can generate a Fly API token in your account settings GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Required for commenting on pull requests for private repos NODE_ENV: production - DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }} + SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} + SOPS_KMS_KEY: ${{ secrets.SOPS_KMS_KEY }} jobs: deploy-preview: @@ -56,15 +57,16 @@ jobs: - id: deploy uses: "superfly/fly-pr-review-apps@1.5.0" with: - secrets: DATABASE_URL=$DATABASE_URL DOTENV_PRIVATE_KEY_PRODUCTION="$DOTENV_PRIVATE_KEY_PRODUCTION" + secrets: DATABASE_URL=$DATABASE_URL SOPS_AGE_KEY="$SOPS_AGE_KEY" SOPS_KMS_KEY="$SOPS_KMS_KEY" config: apps/nextjs/fly.toml build_args: | ENV=production - DOTENV_PRIVATE_KEY_PRODUCTION=${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }} + SOPS_KMS_KEY=${{ secrets.SOPS_KMS_KEY }} org: acme env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }} + SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} + SOPS_KMS_KEY: ${{ secrets.SOPS_KMS_KEY }} - name: Comment on Pull Request diff --git a/.github/workflows/fly-deploy.yml b/.github/workflows/fly-deploy.yml index 94de2f5554..24281678b4 100644 --- a/.github/workflows/fly-deploy.yml +++ b/.github/workflows/fly-deploy.yml @@ -6,7 +6,8 @@ on: branches: - replace-me # Replace with your production branch, e.g., "main" or "master" env: - DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }} + SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} + SOPS_KMS_KEY: ${{ secrets.SOPS_KMS_KEY }} jobs: deploy: name: Deploy app @@ -17,11 +18,12 @@ jobs: - uses: superfly/flyctl-actions/setup-flyctl@master - run: | echo "validating env vars" - [ -z "$DOTENV_PRIVATE_KEY_PRODUCTION" ] && echo "missing DOTENV_PRIVATE_KEY_PRODUCTION" && exit 1 || echo "✅ Environment variables are valid" - flyctl deploy --config apps/nextjs/fly.toml --dockerfile apps/nextjs/Dockerfile --build-arg DOTENV_PRIVATE_KEY_PRODUCTION="$DOTENV_PRIVATE_KEY_PRODUCTION" -e DOTENV_PRIVATE_KEY_PRODUCTION="$DOTENV_PRIVATE_KEY_PRODUCTION" + echo "Using SOPS for envs (AGE or KMS)" + flyctl deploy --config apps/nextjs/fly.toml --dockerfile apps/nextjs/Dockerfile --build-arg SOPS_KMS_KEY="$SOPS_KMS_KEY" -e SOPS_AGE_KEY="$SOPS_AGE_KEY" -e SOPS_KMS_KEY="$SOPS_KMS_KEY" env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }} + SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} + SOPS_KMS_KEY: ${{ secrets.SOPS_KMS_KEY }} - name: Setup tmate session if: ${{ failure() }} uses: mxschmitt/action-tmate@v3 @@ -34,11 +36,12 @@ jobs: - uses: superfly/flyctl-actions/setup-flyctl@master - run: | echo "validating env vars" - [ -z "$DOTENV_PRIVATE_KEY_PRODUCTION" ] && echo "missing DOTENV_PRIVATE_KEY_PRODUCTION" && exit 1 || echo "✅ Environment variables are valid" - flyctl deploy --config apps/travel-assistant-api/fly.toml --dockerfile apps/travel-assistant-api/Dockerfile --build-arg DOTENV_PRIVATE_KEY_PRODUCTION="$DOTENV_PRIVATE_KEY_PRODUCTION" -e DOTENV_PRIVATE_KEY_PRODUCTION="$DOTENV_PRIVATE_KEY_PRODUCTION" + echo "Using SOPS for envs (AGE or KMS)" + flyctl deploy --config apps/travel-assistant-api/fly.toml --dockerfile apps/travel-assistant-api/Dockerfile --build-arg SOPS_KMS_KEY="$SOPS_KMS_KEY" -e SOPS_AGE_KEY="$SOPS_AGE_KEY" -e SOPS_KMS_KEY="$SOPS_KMS_KEY" env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }} + SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} + SOPS_KMS_KEY: ${{ secrets.SOPS_KMS_KEY }} # Optional: Uncomment the following lines to set up a tmate session for debugging if the deployment fails # - name: Setup tmate session # if: ${{ failure() }} diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000000..e08ba1b593 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,9 @@ +# SOPS config: default to AGE (set recipients when you encrypt) +# For public template we ship an empty rule; pass recipients via CLI: +# sops -e --age "$(age-keygen -y age.key)" +creation_rules: + - path_regex: ".*\\.sops$" + age: [] + kms: [] + gcp_kms: [] + azure_keyvault: [] diff --git a/Makefile b/Makefile index 758c95a937..d722dcc078 100644 --- a/Makefile +++ b/Makefile @@ -65,14 +65,14 @@ prune: ## Remove all unused Docker resources .PHONY: ci-build-nextjs ci-build-nextjs: ## Build Next.js for CI docker build -f apps/nextjs/Dockerfile \ - --build-arg DOTENV_PRIVATE_KEY_PRODUCTION="$(DOTENV_PRIVATE_KEY_PRODUCTION)" \ + --build-arg SOPS_KMS_KEY="$(SOPS_KMS_KEY)" \ --tag voytravel-nextjs:latest . .PHONY: ci-build-api ci-build-api: ## Build Python API for CI docker build -f apps/travel-assistant-api/Dockerfile \ --build-arg ENV=production \ - --build-arg DOTENV_PRIVATE_KEY_PRODUCTION="$(DOTENV_PRIVATE_KEY_PRODUCTION)" \ + --build-arg SOPS_KMS_KEY="$(SOPS_KMS_KEY)" \ --tag voytravel-api:latest . .PHONY: test-build @@ -87,21 +87,17 @@ test-build: ## Test production builds locally .PHONY: env-decrypt-dev env-decrypt-dev: - exec dotenvx decrypt \ - -fk .env.keys \ - -f apps/nextjs/.env.development + @[ -f apps/nextjs/.env.development.sops ] || { echo "No apps/nextjs/.env.development.sops"; exit 1; } + sops -d apps/nextjs/.env.development.sops > apps/nextjs/.env.development .PHONY: env-encrypt-dev env-encrypt-dev: - dotenvx encrypt \ - -fk .env.keys \ - -f apps/nextjs/.env.development + @[ -f apps/nextjs/.env.development ] || { echo "No apps/nextjs/.env.development"; exit 1; } + sops -e -i apps/nextjs/.env.development .PHONY: env-decrypt-prod env-decrypt-prod: - dotenvx decrypt \ - -fk .env.keys \ - -f apps/nextjs/.env.production + @[ -f apps/nextjs/.env.production.sops ] || { echo "No apps/nextjs/.env.production.sops"; exit 1; } + sops -d apps/nextjs/.env.production.sops > apps/nextjs/.env.production .PHONY: env-decrypt-prod env-encrypt-prod: - dotenvx encrypt \ - -fk .env.keys \ - -f apps/nextjs/.env.production + @[ -f apps/nextjs/.env.production ] || { echo "No apps/nextjs/.env.production"; exit 1; } + sops -e -i apps/nextjs/.env.production diff --git a/README.md b/README.md index c0b6074a41..ec288836d9 100644 --- a/README.md +++ b/README.md @@ -88,18 +88,14 @@ To get it running, follow the steps below: # Install dependencies pnpm i -# Configure environment variables -# Environment variables are checked in but encrypted for security. The encryption -# keys are store in .env.keys and should not be checked in. -echo << EOF > .env.keys -#/------------------!DOTENV_PRIVATE_KEYS!-------------------/ -#/ private decryption keys. DO NOT commit to source control / -#/ [how it works](https://dotenvx.com/encryption) / -#/----------------------------------------------------------/ -# .env.development -DOTENV_PRIVATE_KEY_DEVELOPMENT=32481a02ef873fd2386669251ef0d7d5083f76465f92e013cc9948c403fcc20b - -EOF +# Configure environment variables with SOPS (AGE by default) +# Generate an age keypair (store key locally; do not commit): +# age-keygen -o age.key +# Export recipient for this shell (or set in your profile): +# export SOPS_AGE_RECIPIENTS="$(age-keygen -y age.key)" +# Create encrypted envs (edit will create if missing): +# sops apps/nextjs/.env.development.sops +# sops apps/nextjs/.env.production.sops # Push the Drizzle schema to the database pnpm db:push diff --git a/apps/expo/package.json b/apps/expo/package.json index 6e1442a5d3..14dce15fda 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -22,7 +22,6 @@ "@acme/tailwind-config": "workspace:*", "@acme/ui": "workspace:*", "@better-auth/expo": "catalog:", - "@dotenvx/dotenvx": "^1.51.0", "@expo-google-fonts/bebas-neue": "^0.4.1", "@expo-google-fonts/montserrat": "^0.4.2", "@expo-google-fonts/source-code-pro": "^0.4.1", diff --git a/apps/nextjs/.env.development b/apps/nextjs/.env.development index acda4a8e6e..a61431b8c5 100644 --- a/apps/nextjs/.env.development +++ b/apps/nextjs/.env.development @@ -1,8 +1,5 @@ -#/-------------------[DOTENV_PUBLIC_KEY]--------------------/ -#/ public-key encryption for .env files / -#/ [how it works](https://dotenvx.com/encryption) / -#/----------------------------------------------------------/ -DOTENV_PUBLIC_KEY_DEVELOPMENT="022e695fdc17e1866bd101e153c8a5e9fe7eb4331669b0e9c33ed8a9e6423a8d6c" +# Managed with SOPS. Use `.env.development.sops` for secrets. +SOPS_MANAGED=1 # .env.development # Since .env is gitignored, you can use .env.example to build a new `.env` file when you clone the repo. diff --git a/apps/nextjs/.env.production b/apps/nextjs/.env.production index 51f16af367..3d1639fe73 100644 --- a/apps/nextjs/.env.production +++ b/apps/nextjs/.env.production @@ -1,45 +1,42 @@ -#/-------------------[DOTENV_PUBLIC_KEY]--------------------/ -#/ public-key encryption for .env files / -#/ [how it works](https://dotenvx.com/encryption) / -#/----------------------------------------------------------/ -DOTENV_PUBLIC_KEY_PRODUCTION="02ce457bd09c083c2dfd806e80e7728cd479bc74785154d7eb5882fe15de6ade85" +# Managed with SOPS. Use `.env.production.sops` for secrets. +SOPS_MANAGED=1 # .env.production # This file will be committed to version control, so make sure not to have any secrets in it. # If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets. # The database URL is used to connect to your Supabase database. -POSTGRES_URL="encrypted:BB71wsjg6Dcl4pHxqSE7ESd1RAtTx7H0B4njevyuEWUmc8A9DziRwiRDvhHnX4g2x+DXSJrBJwgYNzWSLjOT33OLSf/Viq9yYXpmDKZA/GQFmQktq5d+aGimdYCMJCWLGEZjnsHHTR8xQMsZLdyVIqgQk8fZpJ3nD8BQ1BVMuwrKx9duDu9HdQnzVWm+GA==" +POSTGRES_URL="" # The URL of your Next.js app, used for redirects and links. -NEXTJS_URL='encrypted:BPzL8geQSL0mfhZdJuLsCeOywMcNffUIKBt9DaocdnWVcCwag/MoZ6RuUcob25a7DmRjGAo92WKyGYfzn2ROt9PwjJsUfuhcLt+gJjkXlp7C16MMlvxXOlge0qJ17ii8LfidnBrM4YxlRDrHeXfLPGYak6BBsA==' +NEXTJS_URL='' # Expo web app URL, used for redirects and links. -EXPO_WEB_URL='encrypted:BKK0Vv5n3PLuj3s3M7eepx5PZiOHjSpY3UOupPqn2+O5thi3KjEyudf0X90y2Atonl8PkI7qxbxE4TXdwwZRToFfNpiQaPSa08ipIpKJwOG25nKjUCpH5LctNdXE5DXIGmIPBOTT+cQllUCQNUf+7CB1EV+WXg==' +EXPO_WEB_URL='' # You can generate the secret via 'openssl rand -base64 32' on Unix # @see https://www.better-auth.com/docs/installation -AUTH_SECRET='encrypted:BONPpCzlr0cSmzOhGUJUm9pH9g0DDaDSGk8inZczzGUA9INDpSllJRCor4AQQ2+TUEhtBh2M9bVxzujat4hmVVXe4MxY+y/BhVV+waOz+mUz7cPNX9702EpMTqAr29q/bSPYwPgtjwNqdXr3' +AUTH_SECRET='' # Preconfigured OAuth providers: Add your credentials here to dynamically enable or # disable them in the auth package. # @see https://www.better-auth.com/docs/authentication/oauth-providers # Discord -AUTH_DISCORD_ID='encrypted:BDa5IhK9ihhy/KnFb9vTUNJpGPjX8E3HFqXEWNxPZ7Fnah+1gPFTdc75A3l3+E/cMgz5z7UUUEt/33Z0EdCxQzH1hM/CrNhuSYLd6DQcjdR5nIavRhoFIQjWR2q3J7xwzQ==' -AUTH_DISCORD_SECRET='encrypted:BLf+/Pdhl40socnYD3DMYMjF4hq4GX3NfqOMftOALqOVV+rb4P3vrSswIBL8lLb6w/uQG04V6WL4iTgWUPFbfohvMmXSuqBVBB+9S1O4BsIKAhjzWkCJes1kIuUak639cA==' +AUTH_DISCORD_ID='' +AUTH_DISCORD_SECRET='' # GitHub -AUTH_GITHUB_ID='encrypted:BEn+jRLawZSl+da0+LWwdCKWhAVlxP9W5/jsKOcxvVJeEc+FTzt9/Q7ZVO6FzrnGHXThb1Xb1No76B2uYNNgq4JvjZjZTH4VGkBn4uWhFMhB9l0W1bTH3fgFrMTpt2/KiA==' -AUTH_GITHUB_SECRET='encrypted:BIXVcH18YteMUuxS970qPyjzMhOwMAf9dlg2GwInJKKCMLxEbsKrYv9YNU6Bsvnf1oGk2NMdYbufogAZHYG8ij62ktOGJZGoKUJ/K/VopiKEzHhVjq7V9ZkOx8XknMpqqw==' +AUTH_GITHUB_ID='' +AUTH_GITHUB_SECRET='' # Google -AUTH_GOOGLE_ID='encrypted:BDSlL/6yjHDoeQcHO3eAKXe8g1UsFUyedSya9lnG9cErZYuM0BfMq5/PadWNisoC2Gqh5bur15g1jStvOdL5ydfxRrrIZ44ZXJsUT50fuILy4MrUtmSHX+ivCDTJAQdbrg==' -AUTH_GOOGLE_SECRET='encrypted:BK/QXzE987H+6okt82FGGGhv9n1luT+ndDmK554lAF4puDir+/5yA3S7RwoqkKtB7MBiGPodMr9drUMwu0vEg5YR8UCSx+iCrJOyQVladMVCd+ZUSjky7rnVcyP+vA4A4w==' +AUTH_GOOGLE_ID='' +AUTH_GOOGLE_SECRET='' # In case you're using the Auth Proxy (apps/auth-proxy) # AUTH_REDIRECT_PROXY_URL='https://auth.your-server.com/r' # SendGrid is preconfigured, delete if you want to use another email provider. # @see https://next-auth.js.org/configuration/providers/email -SENDGRID_API_KEY='encrypted:BGp6WnoxkzCGEGAj85g/epiXWPlkJDLEaQF2SbA/2STmef5EF0pMvMYGl6mWej62CSldB+SPr1BtktHEWR6FE426dd0482iJS0yst3XAa3OZQI0f70SAefaCiYh0mWilEA==' +SENDGRID_API_KEY='' # Disable this in production to prevent metro from bundling storybook -STORYBOOK_ENABLED=encrypted:BO51SJiW3UewXmRU/i6aQJCXOACegmPA7j3doI4W/bGSb6vYTNJ9OqGu+57hkRSua5meMo8hcMnA9znXOSmIEdurcDJ/te1ThQ4viJs9Qukuva+0q5bNIzKhArTnTO6vYAzA2MY= +STORYBOOK_ENABLED= diff --git a/apps/nextjs/Dockerfile b/apps/nextjs/Dockerfile index 2337730035..c5637c2621 100644 --- a/apps/nextjs/Dockerfile +++ b/apps/nextjs/Dockerfile @@ -50,7 +50,7 @@ FROM node:${NODE_VERSION}-alpine AS runner WORKDIR /app # Runtime deps -RUN apk add --no-cache libc6-compat curl bash +RUN apk add --no-cache libc6-compat curl bash sops age # Enable pnpm in runtime and set non-root install locations RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate @@ -61,7 +61,7 @@ ENV PATH="/home/nextjs/.npm-global/bin:$PNPM_HOME:$PATH" RUN mkdir -p /home/nextjs/.local/share/pnpm /home/nextjs/.pnpm-store /home/nextjs/.npm-global # Optional: tools used by start.sh -RUN npm install -g @dotenvx/dotenvx@latest +# Using nix/flox or local install for extras; keep minimal RUN npm install -g drizzle-kit # Don't run production as root @@ -80,8 +80,8 @@ COPY --from=installer --chown=nextjs:nodejs /app/apps/nextjs/public ./apps/nextj # Copy database package if needed at runtime (e.g., migrations) COPY --from=installer --chown=nextjs:nodejs /app/packages/db ./packages/db -# Optionally include production env files -COPY --chown=nextjs:nodejs .env.production* ./ +# Optionally include production env files from app workspace +COPY --from=installer --chown=nextjs:nodejs /app/apps/nextjs/.env.production* ./ # Start script COPY --chown=nextjs:nodejs --chmod=0755 apps/nextjs/start.sh ./start.sh diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 437fa7f963..5af9a1b1e4 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -11,7 +11,7 @@ "lint": "biome lint .", "start": "pnpm with-env next start", "typecheck": "tsc --noEmit", - "with-env": "dotenvx run --convention=nextjs -fk ../../.env.keys --", + "with-env": "../../packages/scripts/bin/env.sh", "ui-add": "pnpm dlx shadcn@latest add --overwrite && biome format src --write", "storybook:todo": "storybook dev -p 6006", "storybook": "echo \"TODO put mue back \"", @@ -24,7 +24,6 @@ "@acme/tailwind-config": "workspace:*", "@acme/ui": "workspace:*", "@acme/validators": "workspace:*", - "@dotenvx/dotenvx": "^1.51.0", "@expo/next-adapter": "^6.0.0", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-avatar": "^1.1.10", diff --git a/apps/nextjs/start.sh b/apps/nextjs/start.sh index b7dd38524f..a22266c57f 100755 --- a/apps/nextjs/start.sh +++ b/apps/nextjs/start.sh @@ -33,19 +33,34 @@ fi # Production mode -if [ -z "$DOTENV_PRIVATE_KEY_PRODUCTION" ]; then - echo "Error: DOTENV_PRIVATE_KEY_PRODUCTION is not set." - exit 1 +# Load environment variables +ENV_FILE="/app/.env.production" +ENV_SOPS_FILE="/app/.env.production.sops" + +if [ -f "$ENV_SOPS_FILE" ] && command -v sops >/dev/null 2>&1; then + echo "Decrypting env from $ENV_SOPS_FILE" + tmp_env="$(mktemp)" + trap 'rm -f "$tmp_env"' EXIT + sops -d "$ENV_SOPS_FILE" > "$tmp_env" + set -a + # shellcheck disable=SC1090 + . "$tmp_env" + set +a +elif [ -f "$ENV_FILE" ]; then + echo "Loading env from $ENV_FILE" + set -a + # shellcheck disable=SC1090 + . "$ENV_FILE" + set +a else - echo "DOTENV_PRIVATE_KEY_PRODUCTION is set." + echo "No production env file found; proceeding with existing environment" fi # Run db migration cd /app/packages/db -dotenvx run -f /app/.env.production --ignore=MISSING_ENV_FILE -- \ - npx -p @neondatabase/serverless -p drizzle-orm -p drizzle-kit drizzle-kit migrate +NODE_ENV=production npx -p @neondatabase/serverless -p drizzle-orm -p drizzle-kit drizzle-kit migrate cd /app # Start the Next.js application echo "Starting in production mode..." -dotenvx run -f .env.production --ignore=MISSING_ENV_FILE -- node /app/server.js -H 0.0.0.0 +node /app/server.js -H 0.0.0.0 diff --git a/docker-compose.yml b/docker-compose.yml index e6495f051f..0446c0408b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,13 +41,14 @@ services: - "3000:3000" volumes: - ./apps/nextjs:/app - # dont put it in /app or it will create a copy of the .env.keys file in host - - ./.env.keys:/.env.keys + # Optional: mount SOPS age key for local dev (do not commit) + - ./age.key:/age.key:ro # exclusions # - /app/.venv environment: - NODE_ENV=development - ENV=development + - SOPS_AGE_KEY_FILE=/age.key - POSTGRES_URL=postgresql://pguser:password@postgres:5432/app - REDIS_URL=redis://redis:6379 depends_on: diff --git a/packages/scripts/bin/deploy.sh b/packages/scripts/bin/deploy.sh index 1bd930e041..001aede4cc 100755 --- a/packages/scripts/bin/deploy.sh +++ b/packages/scripts/bin/deploy.sh @@ -49,4 +49,4 @@ fly deploy \ -a "${APPNAME}" \ -c "${APPDIR}/fly-${2}.toml" \ --dockerfile "scripts/docker/${1}.Dockerfile" \ - --build-arg "$KEYNAME=$(dotenvx keypair $KEYNAME -f .env.${2})" \ No newline at end of file + --build-arg "$KEYNAME=$(printenv "$KEYNAME")" \ No newline at end of file diff --git a/packages/scripts/bin/env.sh b/packages/scripts/bin/env.sh index cd2215ee54..2c53f3bad3 100755 --- a/packages/scripts/bin/env.sh +++ b/packages/scripts/bin/env.sh @@ -1,69 +1,70 @@ -#!/usr/bin/env sh -set -e +#!/usr/bin/env bash +set -euo pipefail -# Lightweight entrypoint that sources optional .env.keys without failing -# when the file is missing (useful for container entrypoints). +# Unified env runner using SOPS (AGE by default; optional KMS) or plain .env files +# Requires dotenv-cli for loading plaintext files when present -# Probe to detect and avoid re-running this script -export __ENTRYPROBE=true +: "${ENV:=development}" +: "${NODE_ENV:=${ENV}}" -keyfile=".env.keys" script_path="$(cd "$(dirname "$0")" && pwd -P)" -root_path="$(cd "$script_path/.." && pwd -P)" - -# Candidate locations (in order). Keep this list conservative and explicit. -candidate_repo_root="$(cd "$script_path/../.." 2>/dev/null && pwd -P)" +repo_root="$(cd "$script_path/../.." && pwd -P)" -matched_path="" +info() { printf "%s\n" "$*" >&2; } -# Iterate over explicit candidate paths (quoted to avoid word-splitting issues) -for path in \ - "$PWD/$keyfile" \ - "$script_path/$keyfile" \ - "$root_path/$keyfile" \ - "$script_path/../$keyfile" \ - "$candidate_repo_root/$keyfile" \ - "/.env.keys"; do - [ -z "$path" ] && continue - if [ -r "$path" ]; then - matched_path="$path" - break - fi -done +# Find env file candidates relative to CWD first, then repo root +env_base=".env.${ENV}" +sops_candidates=("$PWD/${env_base}.sops" "$repo_root/${env_base}.sops") +plain_candidates=("$PWD/${env_base}" "$repo_root/${env_base}") -if [ -z "$matched_path" ] && [ -z "$(printenv | grep DOTENV_PRIVATE_KEY)" ]; then - cat >&2 <<'WARN' -************************************************************* -* WARNING: No .env.keys file found! * -* The application may not function correctly without it. * -************************************************************* -WARN - echo "Run the helper script to sync keys from 1Password:" >&2 - echo " ./scripts/bin/get-keys.sh" >&2 +have_sops=false +if command -v sops >/dev/null 2>&1; then + have_sops=true fi -# Ensure ENV / NODE_ENV defaults (do not overwrite if already set) -: "${ENV:=development}" -: "${NODE_ENV:=${ENV}}" +run_with_dotenv() { + local env_file="$1" + shift + if command -v dotenv >/dev/null 2>&1; then + info "Using dotenv file: $env_file" + exec dotenv -e "$env_file" -- "$@" + else + info "dotenv-cli not found; sourcing $env_file directly" + set -a + # shellcheck disable=SC1090 + . "$env_file" + set +a + exec "$@" + fi +} +run_with_sops() { + local sops_file="$1" + shift + info "Decrypting with SOPS: $sops_file" + # Decrypt to a temp file to interop cleanly with dotenv-cli + local tmp + tmp="$(mktemp)" + trap 'rm -f "$tmp"' EXIT + sops -d "$sops_file" > "$tmp" + run_with_dotenv "$tmp" "$@" +} -# If explicit DOTENV_PRIVATE_KEY_* variables are present, prefer them. -if [ -n "$DOTENV_PRIVATE_KEY_STAGING" ]; then - echo "🚀 Running in staging mode using \$DOTENV_PRIVATE_KEY_STAGING..." - export NODE_ENV=production - exec dotenvx run --ignore=MISSING_ENV_FILE -f .env.staging -f .env -- "$@" -elif [ -n "$DOTENV_PRIVATE_KEY_PRODUCTION" ]; then - echo "🚀 Running in production mode using \$DOTENV_PRIVATE_KEY_PRODUCTION..." - exec dotenvx run --ignore=MISSING_ENV_FILE -f .env.production -f .env -- "$@" -elif [ -n "$DOTENV_PRIVATE_KEY_DEVELOPMENT" ]; then - echo "🔐 Running in development mode using \$DOTENV_PRIVATE_KEY_DEVELOPMENT..." - exec dotenvx run --convention=nextjs -- "$@" +# Prefer SOPS files when available +if $have_sops; then + for f in "${sops_candidates[@]}"; do + if [ -f "$f" ]; then + run_with_sops "$f" "$@" + fi + done fi -if [ "${ENV:-development}" = "development" ]; then - echo "🔨 Running in development mode..." - exec dotenvx run --convention=nextjs -fk $matched_path -- "$@" -else - echo "🚀 Running in production mode..." - exec dotenvx run --ignore=MISSING_ENV_FILE -f .env.production -f .env -fk $matched_path -- "$@" -fi +# Fallback to plaintext .env files +for f in "${plain_candidates[@]}"; do + if [ -f "$f" ]; then + run_with_dotenv "$f" "$@" + fi +done + +info "No env file found for ENV=${ENV}. Running without extra env..." +exec "$@" diff --git a/packages/scripts/bin/pre.sh b/packages/scripts/bin/pre.sh index 5170ae9e9f..a548f66833 100755 --- a/packages/scripts/bin/pre.sh +++ b/packages/scripts/bin/pre.sh @@ -34,7 +34,17 @@ keys_file="$root_dir/.env.keys" if [[ -f "$keys_file" ]]; then set -a - export TURBO_TOKEN=$(dotenvx get TURBO_TOKEN -f "$root_dir/.env.development" -f "$keys_file") + if command -v sops >/dev/null 2>&1; then + # Prefer sops if an encrypted env exists + if [[ -f "$root_dir/.env.development.sops" ]]; then + export TURBO_TOKEN=$(sops -d "$root_dir/.env.development.sops" | grep '^TURBO_TOKEN=' | cut -d'=' -f2-) + fi + fi + # Fallback to plaintext .env if present + if [[ -z "${TURBO_TOKEN:-}" && -f "$root_dir/.env.development" ]]; then + # shellcheck disable=SC1090 + . "$root_dir/.env.development" + fi export TURBO_TEAM=team_KXVaispsHwsXlmn4asRmt1iB set +a fi