feat: migrate secrets to Infisical
Validate and deploy production / validate (push) Canceled after 0s
Validate and deploy production / deploy (push) Canceled after 0s
Validate and deploy staging / validate (push) Failing after 22s
Validate and deploy staging / deploy (push) Skipped

This commit is contained in:
jb
2026-08-02 01:19:41 -04:00
parent 9089f4a257
commit 25d9241b51
34 changed files with 2353 additions and 691 deletions
+54 -17
View File
@@ -7,7 +7,7 @@ on:
workflow_dispatch:
concurrency:
group: shd-build-production
group: shd-build-host-maintenance
cancel-in-progress: false
jobs:
@@ -21,7 +21,7 @@ jobs:
run: npm ci
- name: Validate application
run: npm run check
run: npm run check && npm test
- name: Build Node application
run: npm run build:app
@@ -33,30 +33,63 @@ jobs:
- name: Check out repository
uses: actions/checkout@v4
- name: Install environment tooling
run: npm ci
- name: Prepare production configuration
env:
DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }}
SCHEDULER_TOKEN_PRODUCTION: ${{ secrets.SCHEDULER_TOKEN_PRODUCTION }}
INFISICAL_APP_CLIENT_ID:
${{ vars.INFISICAL_PRODUCTION_APP_CLIENT_ID }}
INFISICAL_APP_CLIENT_SECRET:
${{ secrets.INFISICAL_PRODUCTION_APP_CLIENT_SECRET }}
INFISICAL_SCHEDULER_CLIENT_ID:
${{ vars.INFISICAL_PRODUCTION_SCHEDULER_CLIENT_ID }}
INFISICAL_SCHEDULER_CLIENT_SECRET:
${{ secrets.INFISICAL_PRODUCTION_SCHEDULER_CLIENT_SECRET }}
CONFIGURED_DEPLOY_PATH: ${{ vars.PRODUCTION_DEPLOY_PATH }}
CONFIGURED_PRODUCTION_PORT: ${{ vars.PRODUCTION_PORT }}
COMMIT_SHA: ${{ gitea.sha }}
BUILD_NUMBER: ${{ gitea.run_number }}
run: |
set -eu
: "${DOTENV_PRIVATE_KEY_PRODUCTION:?DOTENV_PRIVATE_KEY_PRODUCTION is required}"
: "${SCHEDULER_TOKEN_PRODUCTION:?SCHEDULER_TOKEN_PRODUCTION is required}"
umask 077
: "${INFISICAL_APP_CLIENT_ID:?INFISICAL_PRODUCTION_APP_CLIENT_ID is required}"
: "${INFISICAL_APP_CLIENT_SECRET:?INFISICAL_PRODUCTION_APP_CLIENT_SECRET is required}"
: "${INFISICAL_SCHEDULER_CLIENT_ID:?INFISICAL_PRODUCTION_SCHEDULER_CLIENT_ID is required}"
: "${INFISICAL_SCHEDULER_CLIENT_SECRET:?INFISICAL_PRODUCTION_SCHEDULER_CLIENT_SECRET is required}"
deployment_path="${CONFIGURED_DEPLOY_PATH:-/opt/shd-build-production}"
production_port="${CONFIGURED_PRODUCTION_PORT:-3102}"
case "$deployment_path" in
/opt/shd-build-*) ;;
*) echo "PRODUCTION_DEPLOY_PATH must be beneath /opt/shd-build-." >&2; exit 1 ;;
esac
case "$production_port" in
''|*[!0-9]*) echo "PRODUCTION_PORT must be an integer." >&2; exit 1 ;;
esac
if [ "$production_port" -lt 1 ] || [ "$production_port" -gt 65535 ]; then
echo "PRODUCTION_PORT must be from 1 through 65535." >&2
exit 1
fi
mkdir -p "$deployment_path"
cp deploy/compose.production.yml deploy/deploy-production.sh deploy/bootstrap-production.sh deploy/sync-production-isac-to-staging.sh "$deployment_path/"
DOTENV_PRIVATE_KEY_PRODUCTION="$DOTENV_PRIVATE_KEY_PRODUCTION" npx dotenvx decrypt -f .env.production --stdout > "$deployment_path/.env.production.runtime"
printf '\nSCHEDULER_TOKEN=%s\n' "$SCHEDULER_TOKEN_PRODUCTION" >> "$deployment_path/.env.production.runtime"
printf 'SHD_IMAGE=shd-build-production:%s\nSHD_PRODUCTION_PORT=%s\nSHD_BUILD_NUMBER=%s\n' "$COMMIT_SHA" "$production_port" "$BUILD_NUMBER" > "$deployment_path/.env.deploy"
chmod 600 "$deployment_path/.env.production.runtime" "$deployment_path/.env.deploy"
chmod 755 "$deployment_path/deploy-production.sh" "$deployment_path/bootstrap-production.sh" "$deployment_path/sync-production-isac-to-staging.sh"
release_id="${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT:-1}"
candidate_path="$deployment_path/candidate-$release_id"
rm -rf "$candidate_path"
mkdir -p "$candidate_path"
cp deploy/compose.production.yml deploy/deploy-production.sh deploy/bootstrap-production.sh deploy/sync-production-isac-to-staging.sh "$candidate_path/"
write_credential() {
target="$1"
value="$2"
temporary_file=$(mktemp "${target%/*}/.infisical.XXXXXX")
printf '%s' "$value" > "$temporary_file"
chmod 600 "$temporary_file"
mv "$temporary_file" "$target"
}
write_credential "$candidate_path/.infisical.app.client-id" "$INFISICAL_APP_CLIENT_ID"
write_credential "$candidate_path/.infisical.app.client-secret" "$INFISICAL_APP_CLIENT_SECRET"
write_credential "$candidate_path/.infisical.scheduler.client-id" "$INFISICAL_SCHEDULER_CLIENT_ID"
write_credential "$candidate_path/.infisical.scheduler.client-secret" "$INFISICAL_SCHEDULER_CLIENT_SECRET"
temporary_deploy_environment=$(mktemp "$candidate_path/.env.deploy.XXXXXX")
printf 'SHD_IMAGE=shd-build-production:%s-%s\nSHD_PRODUCTION_PORT=%s\nSHD_BUILD_NUMBER=%s\nSHD_COMMIT_SHA=%s\n' "$COMMIT_SHA" "$release_id" "$production_port" "$BUILD_NUMBER" "$COMMIT_SHA" > "$temporary_deploy_environment"
chmod 600 "$temporary_deploy_environment"
mv "$temporary_deploy_environment" "$candidate_path/.env.deploy"
chmod 755 "$candidate_path/deploy-production.sh" "$candidate_path/bootstrap-production.sh" "$candidate_path/sync-production-isac-to-staging.sh"
- name: Deploy production
env:
@@ -64,8 +97,12 @@ jobs:
COMMIT_SHA: ${{ gitea.sha }}
run: |
deployment_path="${CONFIGURED_DEPLOY_PATH:-/opt/shd-build-production}"
"$deployment_path/deploy-production.sh" "$deployment_path" "$GITHUB_WORKSPACE" "shd-build-production:$COMMIT_SHA"
release_id="${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT:-1}"
candidate_path="$deployment_path/candidate-$release_id"
"$candidate_path/deploy-production.sh" "$deployment_path" "$GITHUB_WORKSPACE" "shd-build-production:$COMMIT_SHA-$release_id" "$candidate_path"
- name: Remove unused application images
if: success()
run: docker image prune --force --filter "label=org.opencontainers.image.title=shd-build"
run:
docker image prune --force --filter
"label=org.opencontainers.image.title=shd-build"
+53 -13
View File
@@ -7,7 +7,7 @@ on:
workflow_dispatch:
concurrency:
group: shd-build-staging
group: shd-build-host-maintenance
cancel-in-progress: false
jobs:
@@ -21,7 +21,7 @@ jobs:
run: npm ci
- name: Validate application
run: npm run check
run: npm run check && npm test
- name: Build Node application
run: npm run build:app
@@ -33,26 +33,62 @@ jobs:
- name: Check out repository
uses: actions/checkout@v4
- name: Install environment tooling
run: npm ci
- name: Prepare deployment configuration
env:
DOTENV_PRIVATE_KEY_STAGING: ${{ secrets.DOTENV_PRIVATE_KEY_STAGING }}
INFISICAL_APP_CLIENT_ID: ${{ vars.INFISICAL_STAGING_APP_CLIENT_ID }}
INFISICAL_APP_CLIENT_SECRET:
${{ secrets.INFISICAL_STAGING_APP_CLIENT_SECRET }}
INFISICAL_SCHEDULER_CLIENT_ID:
${{ vars.INFISICAL_STAGING_SCHEDULER_CLIENT_ID }}
INFISICAL_SCHEDULER_CLIENT_SECRET:
${{ secrets.INFISICAL_STAGING_SCHEDULER_CLIENT_SECRET }}
CONFIGURED_DEPLOY_PATH: ${{ vars.STAGE_DEPLOY_PATH }}
CONFIGURED_STAGE_PORT: ${{ vars.STAGE_PORT }}
COMMIT_SHA: ${{ gitea.sha }}
BUILD_NUMBER: ${{ gitea.run_number }}
run: |
set -eu
umask 077
: "${INFISICAL_APP_CLIENT_ID:?INFISICAL_STAGING_APP_CLIENT_ID is required}"
: "${INFISICAL_APP_CLIENT_SECRET:?INFISICAL_STAGING_APP_CLIENT_SECRET is required}"
: "${INFISICAL_SCHEDULER_CLIENT_ID:?INFISICAL_STAGING_SCHEDULER_CLIENT_ID is required}"
: "${INFISICAL_SCHEDULER_CLIENT_SECRET:?INFISICAL_STAGING_SCHEDULER_CLIENT_SECRET is required}"
deployment_path="${CONFIGURED_DEPLOY_PATH:-/opt/shd-build-stage}"
stage_port="${CONFIGURED_STAGE_PORT:-5569}"
case "$deployment_path" in
/opt/shd-build-*) ;;
*) echo "STAGE_DEPLOY_PATH must be beneath /opt/shd-build-." >&2; exit 1 ;;
esac
case "$stage_port" in
''|*[!0-9]*) echo "STAGE_PORT must be an integer." >&2; exit 1 ;;
esac
if [ "$stage_port" -lt 1 ] || [ "$stage_port" -gt 65535 ]; then
echo "STAGE_PORT must be from 1 through 65535." >&2
exit 1
fi
mkdir -p "$deployment_path"
cp deploy/compose.staging.yml deploy/deploy-staging.sh deploy/bootstrap-staging.sh "$deployment_path/"
DOTENV_PRIVATE_KEY_STAGING="$DOTENV_PRIVATE_KEY_STAGING" npx dotenvx decrypt -f .env.staging --stdout > "$deployment_path/.env.staging.runtime"
printf 'SHD_IMAGE=shd-build-stage:%s\nSHD_STAGE_PORT=%s\nSHD_BUILD_NUMBER=%s\n' "$COMMIT_SHA" "$stage_port" "$BUILD_NUMBER" > "$deployment_path/.env.deploy"
chmod 600 "$deployment_path/.env.staging.runtime" "$deployment_path/.env.deploy"
chmod 755 "$deployment_path/deploy-staging.sh" "$deployment_path/bootstrap-staging.sh"
release_id="${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT:-1}"
candidate_path="$deployment_path/candidate-$release_id"
rm -rf "$candidate_path"
mkdir -p "$candidate_path"
cp deploy/compose.staging.yml deploy/deploy-staging.sh deploy/bootstrap-staging.sh "$candidate_path/"
write_credential() {
target="$1"
value="$2"
temporary_file=$(mktemp "${target%/*}/.infisical.XXXXXX")
printf '%s' "$value" > "$temporary_file"
chmod 600 "$temporary_file"
mv "$temporary_file" "$target"
}
write_credential "$candidate_path/.infisical.app.client-id" "$INFISICAL_APP_CLIENT_ID"
write_credential "$candidate_path/.infisical.app.client-secret" "$INFISICAL_APP_CLIENT_SECRET"
write_credential "$candidate_path/.infisical.scheduler.client-id" "$INFISICAL_SCHEDULER_CLIENT_ID"
write_credential "$candidate_path/.infisical.scheduler.client-secret" "$INFISICAL_SCHEDULER_CLIENT_SECRET"
temporary_deploy_environment=$(mktemp "$candidate_path/.env.deploy.XXXXXX")
printf 'SHD_IMAGE=shd-build-stage:%s-%s\nSHD_STAGE_PORT=%s\nSHD_BUILD_NUMBER=%s\nSHD_COMMIT_SHA=%s\n' "$COMMIT_SHA" "$release_id" "$stage_port" "$BUILD_NUMBER" "$COMMIT_SHA" > "$temporary_deploy_environment"
chmod 600 "$temporary_deploy_environment"
mv "$temporary_deploy_environment" "$candidate_path/.env.deploy"
chmod 755 "$candidate_path/deploy-staging.sh" "$candidate_path/bootstrap-staging.sh"
- name: Deploy staging
env:
@@ -60,8 +96,12 @@ jobs:
COMMIT_SHA: ${{ gitea.sha }}
run: |
deployment_path="${CONFIGURED_DEPLOY_PATH:-/opt/shd-build-stage}"
"$deployment_path/deploy-staging.sh" "$deployment_path" "$GITHUB_WORKSPACE" "shd-build-stage:$COMMIT_SHA"
release_id="${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT:-1}"
candidate_path="$deployment_path/candidate-$release_id"
"$candidate_path/deploy-staging.sh" "$deployment_path" "$GITHUB_WORKSPACE" "shd-build-stage:$COMMIT_SHA-$release_id" "$candidate_path"
- name: Remove unused application images
if: success()
run: docker image prune --force --filter "label=org.opencontainers.image.title=shd-build"
run:
docker image prune --force --filter
"label=org.opencontainers.image.title=shd-build"
+25 -8
View File
@@ -4,7 +4,7 @@ on:
workflow_dispatch:
concurrency:
group: shd-build-staging
group: shd-build-host-maintenance
cancel-in-progress: false
jobs:
@@ -25,15 +25,32 @@ jobs:
compose() {
docker compose --env-file "$deploy_environment" -f "$compose_file" "$@"
}
restart_services() {
compose up -d --no-build app scheduler
}
trap restart_services EXIT
snapshot_name="isac-stage-${GITHUB_RUN_ID}.json"
volume_snapshot="/app/data/exports/$snapshot_name"
remove_volume_snapshot=false
cleanup() {
exit_status=$?
trap - EXIT
if [ "$remove_volume_snapshot" = true ]; then
compose run --rm --no-deps app rm -f "$volume_snapshot" || true
fi
if ! compose up -d --no-build app scheduler; then
if [ "$exit_status" -eq 0 ]; then exit_status=1; fi
fi
exit "$exit_status"
}
trap cleanup EXIT
compose stop scheduler app
compose run --rm --no-deps app node scripts/isac-data-export.mjs "/app/data/exports/$snapshot_name"
compose run --rm --no-deps -v "$GITHUB_WORKSPACE:/export" app \
cp "/app/data/exports/$snapshot_name" /export/isac-stage.json
app_container="$(compose ps --all --quiet app)"
if [ -z "$app_container" ]; then
echo "The stopped staging app container could not be found." >&2
exit 1
fi
remove_volume_snapshot=true
compose run --rm --no-deps app node scripts/isac-data-export.mjs "$volume_snapshot"
docker cp "$app_container:$volume_snapshot" "$GITHUB_WORKSPACE/isac-stage.json"
compose run --rm --no-deps app rm -f "$volume_snapshot"
remove_volume_snapshot=false
- name: Publish ISAC snapshot
uses: actions/upload-artifact@v3
@@ -4,7 +4,7 @@ on:
workflow_dispatch:
concurrency:
group: shd-build-production
group: shd-build-host-maintenance
cancel-in-progress: false
jobs:
@@ -25,15 +25,32 @@ jobs:
compose() {
docker compose --env-file "$deploy_environment" -f "$compose_file" "$@"
}
restart_services() {
compose up -d --no-build app scheduler
}
trap restart_services EXIT
snapshot_name="isac-production-${GITHUB_RUN_ID}.json"
volume_snapshot="/app/data/exports/$snapshot_name"
remove_volume_snapshot=false
cleanup() {
exit_status=$?
trap - EXIT
if [ "$remove_volume_snapshot" = true ]; then
compose run --rm --no-deps app rm -f "$volume_snapshot" || true
fi
if ! compose up -d --no-build app scheduler; then
if [ "$exit_status" -eq 0 ]; then exit_status=1; fi
fi
exit "$exit_status"
}
trap cleanup EXIT
compose stop scheduler app
compose run --rm --no-deps app node scripts/isac-data-export.mjs "/app/data/exports/$snapshot_name"
compose run --rm --no-deps -v "$GITHUB_WORKSPACE:/export" app \
cp "/app/data/exports/$snapshot_name" /export/isac-production.json
app_container="$(compose ps --all --quiet app)"
if [ -z "$app_container" ]; then
echo "The stopped production app container could not be found." >&2
exit 1
fi
remove_volume_snapshot=true
compose run --rm --no-deps app node scripts/isac-data-export.mjs "$volume_snapshot"
docker cp "$app_container:$volume_snapshot" "$GITHUB_WORKSPACE/isac-production.json"
compose run --rm --no-deps app rm -f "$volume_snapshot"
remove_volume_snapshot=false
- name: Publish production ISAC snapshot
uses: actions/upload-artifact@v3
@@ -4,7 +4,7 @@ on:
workflow_dispatch:
concurrency:
group: shd-build-production-to-staging
group: shd-build-host-maintenance
cancel-in-progress: false
jobs: