gabriel / musehub public
secrets.sh bash
179 lines 7.2 KB
Raw
sha256:20ae2cb8b3425921e96f2131287a153721794504a8d0688f67cc2e3cd158e3ef Merge 'fix/production-backups-r2-bucket' into 'dev' — propo… Human 3 days ago
1 #!/usr/bin/env bash
2 # MuseHub secrets bootstrap — fetch from AWS SSM Parameter Store, write .env
3 #
4 # Runs on the EC2 instance BEFORE deploy.sh. Pulls every secret from SSM
5 # Parameter Store (SecureString, AES-256 at rest via KMS) and writes a fresh
6 # /opt/musehub/.env. The .env on disk is the runtime injection point for
7 # all Docker containers (--env-file).
8 #
9 # Why SSM instead of a static .env:
10 # - Secrets never travel through source control or build artifacts.
11 # - Access is audited via CloudTrail (who fetched what, when).
12 # - Rotation updates SSM; next deploy.sh run picks up the new value.
13 # - IAM role on the EC2 instance grants read access — no AWS keys on disk.
14 #
15 # SSM parameter layout (all SecureString, KMS-encrypted):
16 # /musehub/<env>/DB_PASSWORD
17 # /musehub/<env>/WEBHOOK_SECRET_KEY
18 # /musehub/<env>/RUNNER_TOKEN
19 # /musehub/<env>/BLOB_STORAGE_ACCESS_KEY_ID
20 # /musehub/<env>/BLOB_STORAGE_SECRET_ACCESS_KEY
21 # /musehub/<env>/WORKER_INTERNAL_KEY (shared secret for Cloudflare Worker → MuseHub callbacks)
22 # /musehub/<env>/MPACK_WORKER_URL (public URL of the CF mpack-receiver Worker; optional)
23 # /musehub/<env>/BACKUP_R2_BUCKET (R2 bucket for deploy/backup.sh off-disk backups; optional, plain String not SecureString)
24 #
25 # Prerequisites:
26 # - AWS CLI v2 installed on the EC2 instance
27 # - EC2 instance profile with IAM policy:
28 # ssm:GetParameter, ssm:GetParametersByPath
29 # on arn:aws:ssm:<region>:<account>:parameter/musehub/<env>/*
30 # - KMS decrypt on the CMK used for the SecureString parameters
31 #
32 # Usage:
33 # MUSEHUB_ENV=production bash deploy/secrets.sh
34 # MUSEHUB_ENV=staging bash deploy/secrets.sh
35 #
36 # After this script writes .env, run deploy.sh as usual.
37 #
38 # Fallback (no SSM / local dev):
39 # If AWS CLI is not available or SSM fetch fails, the script exits non-zero
40 # so deploy.sh does not start with stale/missing secrets. For local dev,
41 # manage .env manually — never run this script on a dev laptop.
42
43 set -euo pipefail
44
45 MUSEHUB_ENV="${MUSEHUB_ENV:-production}"
46 APP_DIR="${APP_DIR:-/opt/musehub}"
47 ENV_FILE="$APP_DIR/.env"
48 REGION="${AWS_REGION:-us-east-1}"
49 SSM_PREFIX="/musehub/${MUSEHUB_ENV}"
50
51 log() { echo "[secrets] $*"; }
52 die() { echo "[secrets] ERROR: $*" >&2; exit 1; }
53
54 # ── Preflight ─────────────────────────────────────────────────────────────────
55
56 command -v aws > /dev/null 2>&1 || die "AWS CLI not installed. Install: sudo apt-get install -y awscli"
57
58 # Verify we can reach SSM (IAM role check) — use GetParameter on DB_PASSWORD
59 # (always required) rather than GetParametersByPath (requires broader permission).
60 aws ssm get-parameter \
61 --name "$SSM_PREFIX/DB_PASSWORD" \
62 --region "$REGION" \
63 --with-decryption \
64 --query 'Parameter.Value' \
65 --output text > /dev/null 2>&1 \
66 || die "Cannot read $SSM_PREFIX/DB_PASSWORD from SSM — check the EC2 instance IAM role."
67
68 log "Fetching secrets from SSM: $SSM_PREFIX (region=$REGION)"
69
70 # ── Fetch each parameter ──────────────────────────────────────────────────────
71
72 _get() {
73 local name="$1"
74 local required="${2:-true}"
75 local value
76 value=$(aws ssm get-parameter \
77 --name "$SSM_PREFIX/$name" \
78 --region "$REGION" \
79 --with-decryption \
80 --query 'Parameter.Value' \
81 --output text 2>/dev/null) || {
82 if [ "$required" = "true" ]; then
83 die "Required parameter $SSM_PREFIX/$name not found in SSM"
84 fi
85 echo ""
86 return
87 }
88 echo "$value"
89 }
90
91 DB_PASSWORD=$(_get "DB_PASSWORD")
92 WEBHOOK_SECRET_KEY=$(_get "WEBHOOK_SECRET_KEY")
93 RUNNER_TOKEN=$(_get "RUNNER_TOKEN" false)
94 BLOB_STORAGE_ACCESS_KEY_ID=$(_get "BLOB_STORAGE_ACCESS_KEY_ID" false)
95 BLOB_STORAGE_SECRET_ACCESS_KEY=$(_get "BLOB_STORAGE_SECRET_ACCESS_KEY" false)
96 WORKER_INTERNAL_KEY=$(_get "WORKER_INTERNAL_KEY" false)
97 PACK_WORKER_URL=$(_get "PACK_WORKER_URL" false)
98 BACKUP_R2_BUCKET=$(_get "BACKUP_R2_BUCKET" false)
99
100 # ── Resolve per-environment non-secret config ─────────────────────────────────
101
102 if [ "$MUSEHUB_ENV" = "staging" ]; then
103 PUBLIC_URL="https://staging.musehub.ai"
104 CORS_ORIGINS='["https://staging.musehub.ai"]'
105 BLOB_STORAGE_BUCKET="musehub-staging"
106 BLOB_STORAGE_ENDPOINT="https://bed873d46de5273abf843468a7833f09.r2.cloudflarestorage.com"
107 BLOB_STORAGE_REGION="auto"
108 elif [ "$MUSEHUB_ENV" = "production" ]; then
109 PUBLIC_URL="https://musehub.ai"
110 CORS_ORIGINS='["https://musehub.ai", "https://www.musehub.ai"]'
111 BLOB_STORAGE_BUCKET="musehub-prod"
112 BLOB_STORAGE_ENDPOINT="https://bed873d46de5273abf843468a7833f09.r2.cloudflarestorage.com"
113 BLOB_STORAGE_REGION="auto"
114 else
115 die "Unknown MUSEHUB_ENV='$MUSEHUB_ENV'. Must be 'staging' or 'production'."
116 fi
117
118 # ── Write .env ────────────────────────────────────────────────────────────────
119
120 log "Writing $ENV_FILE (env=$MUSEHUB_ENV, public_url=$PUBLIC_URL)"
121
122 # Back up the existing .env if present
123 if [ -f "$ENV_FILE" ]; then
124 cp "$ENV_FILE" "${ENV_FILE}.bak.$(date +%Y%m%d_%H%M%S)"
125 log "Previous .env backed up"
126 fi
127
128 # Write new .env — mode 600, owner musehub
129 umask 177
130 cat > "$ENV_FILE" << EOF
131 # Generated by deploy/secrets.sh at $(date -u +%Y-%m-%dT%H:%M:%SZ)
132 # Secrets sourced from AWS SSM Parameter Store: $SSM_PREFIX
133 # DO NOT edit manually — re-run secrets.sh to refresh from SSM.
134
135 MUSE_ENV=${MUSEHUB_ENV}
136 DEBUG=false
137 PUBLIC_URL=${PUBLIC_URL}
138 CORS_ORIGINS=${CORS_ORIGINS}
139 DB_PASSWORD=${DB_PASSWORD}
140 BLOB_STORAGE_BUCKET=${BLOB_STORAGE_BUCKET}
141 BLOB_STORAGE_ENDPOINT=${BLOB_STORAGE_ENDPOINT}
142 BLOB_STORAGE_REGION=${BLOB_STORAGE_REGION}
143 WEBHOOK_SECRET_KEY=${WEBHOOK_SECRET_KEY}
144 EOF
145 if [ -n "$RUNNER_TOKEN" ]; then
146 echo "RUNNER_TOKEN=${RUNNER_TOKEN}" >> "$ENV_FILE"
147 fi
148 if [ -n "$BLOB_STORAGE_ACCESS_KEY_ID" ]; then
149 echo "BLOB_STORAGE_ACCESS_KEY_ID=${BLOB_STORAGE_ACCESS_KEY_ID}" >> "$ENV_FILE"
150 echo "BLOB_STORAGE_SECRET_ACCESS_KEY=${BLOB_STORAGE_SECRET_ACCESS_KEY}" >> "$ENV_FILE"
151 fi
152 if [ -n "$WORKER_INTERNAL_KEY" ]; then
153 echo "WORKER_INTERNAL_KEY=${WORKER_INTERNAL_KEY}" >> "$ENV_FILE"
154 fi
155 if [ -n "$PACK_WORKER_URL" ]; then
156 echo "PACK_WORKER_URL=${PACK_WORKER_URL}" >> "$ENV_FILE"
157 fi
158 if [ -n "$BACKUP_R2_BUCKET" ]; then
159 echo "BACKUP_R2_BUCKET=${BACKUP_R2_BUCKET}" >> "$ENV_FILE"
160 fi
161
162 chown musehub:musehub "$ENV_FILE" 2>/dev/null || true
163 log ".env written ($(wc -l < "$ENV_FILE") lines, mode 600)"
164
165 # ── Sanity check — no weak values leaked into env ────────────────────────────
166
167 WEAK_PASSWORDS=("musehub" "changeme123" "password" "postgres" "secret" "")
168 for WEAK in "${WEAK_PASSWORDS[@]}"; do
169 if [ "$DB_PASSWORD" = "$WEAK" ]; then
170 die "DB_PASSWORD from SSM is a known weak value ($WEAK). Rotate it immediately."
171 fi
172 done
173
174 if [ ${#DB_PASSWORD} -lt 16 ]; then
175 die "DB_PASSWORD from SSM is too short (${#DB_PASSWORD} chars). Minimum is 16."
176 fi
177
178 log "Secrets sanity check passed."
179 log "Run 'bash deploy/deploy.sh' to deploy."
File History 15 commits
sha256:20ae2cb8b3425921e96f2131287a153721794504a8d0688f67cc2e3cd158e3ef Merge 'fix/production-backups-r2-bucket' into 'dev' — propo… Human 3 days ago
sha256:cd5c2fcb91a44ac9e38e9c36176c27ce079a074586d9852296da628a29fb01ff Merge 'docs/aws-identity-and-deploy-fixes' into 'dev' — pro… Human 3 days ago
sha256:fc04e4cae9e1774d6a21b65c45daeed0e6787eb581d13aa1b03bfe9384a34226 Merge branch 'fix/two-column-scroll-layout' into dev Human 55 days ago
sha256:408916fc5973ba59c6e4eebaa80ebdcc801c0a63205651e25009d11548f79454 chore: bump version to 0.2.0.dev2 — nightly.2, matching muse Sonnet 4.6 patch 58 days ago
sha256:d035733f21ccff27735fddebfbbe0ed24565a32a22db8de5885402262671ecd2 chore: bump version to 0.2.0rc15 for musehub#113 fix release Sonnet 4.6 patch 61 days ago
sha256:0032d6cfa33bc3c8367436ad768e7dd0e339b4332153160247da8266cb5fa352 Merge branch 'task/version-tags-phase3-server' into dev Human 63 days ago
sha256:4669620efda9ff41c55bdefd1f7bfe1c239d468428744c84ead9957e5a003a53 merge: rescue snapshot-recovery hardening (c00aa21d) into d… Opus 4.8 minor 76 days ago
sha256:a59da49c4611b970fc4b6ae48678ce4943261c213a07ddbd73ce9201df869b4a fix: remove false-positive proposal_comments index drop fro… Sonnet 4.6 patch 80 days ago
sha256:0a240d6dbff234f07d98a28a4a9a68db702f3f9ff9260196f24219bdb1c0b6f3 feat: render markdown mists as HTML with heading anchor links Sonnet 4.6 patch 81 days ago
sha256:24a7d47486ebc4ebd1832830580e177ec6f877b48dced8c000e198cdec4ce9d6 Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 82 days ago
sha256:b9ff931d147e0114a1f17060f415b89ed551c170a91ff226c70437aa5c85f9ee Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 82 days ago
sha256:d1122d21e73471879b460037b22c0b50fded7c423444a176f248428f75dac39c Merge 'task/fix-issue-pagination-cursor' into 'dev' — propo… Human 82 days ago
sha256:01e18975e73d2b3cd5b6db7929c895bef9aa6e0d4391dc5b2adfc548b41318dd Merge 'feat/adding-debug-logs-to-staging' into 'dev' — prop… Human 82 days ago
sha256:6b1949fc2797ca4c1936a637a4cbfec828ef56cf52398a2e74ca3c4f494e728f fix: use wire_bytes not mpack_bytes_raw in compute_object_b… Sonnet 4.6 patch 94 days ago
sha256:b99f2455dc346966d040133f5203297e6e3ef5803a93728a2c30568d0a0f7583 rename: delta_add → delta_upsert across wire format, models… Sonnet 4.6 patch 96 days ago