secrets.sh
bash
sha256:3ff9c9863a9891bdcde71b4a43228f66d0493e38b7cc1d09fe9eb7de774046b2
feat: add repair-commit wire endpoint (API parity with repa…
Opus 4.8
minor
⚠ breaking
1 day 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 | # |
| 24 | # Prerequisites: |
| 25 | # - AWS CLI v2 installed on the EC2 instance |
| 26 | # - EC2 instance profile with IAM policy: |
| 27 | # ssm:GetParameter, ssm:GetParametersByPath |
| 28 | # on arn:aws:ssm:<region>:<account>:parameter/musehub/<env>/* |
| 29 | # - KMS decrypt on the CMK used for the SecureString parameters |
| 30 | # |
| 31 | # Usage: |
| 32 | # MUSEHUB_ENV=production bash deploy/secrets.sh |
| 33 | # MUSEHUB_ENV=staging bash deploy/secrets.sh |
| 34 | # |
| 35 | # After this script writes .env, run deploy.sh as usual. |
| 36 | # |
| 37 | # Fallback (no SSM / local dev): |
| 38 | # If AWS CLI is not available or SSM fetch fails, the script exits non-zero |
| 39 | # so deploy.sh does not start with stale/missing secrets. For local dev, |
| 40 | # manage .env manually — never run this script on a dev laptop. |
| 41 | |
| 42 | set -euo pipefail |
| 43 | |
| 44 | MUSEHUB_ENV="${MUSEHUB_ENV:-production}" |
| 45 | APP_DIR="${APP_DIR:-/opt/musehub}" |
| 46 | ENV_FILE="$APP_DIR/.env" |
| 47 | REGION="${AWS_REGION:-us-east-1}" |
| 48 | SSM_PREFIX="/musehub/${MUSEHUB_ENV}" |
| 49 | |
| 50 | log() { echo "[secrets] $*"; } |
| 51 | die() { echo "[secrets] ERROR: $*" >&2; exit 1; } |
| 52 | |
| 53 | # ── Preflight ───────────────────────────────────────────────────────────────── |
| 54 | |
| 55 | command -v aws > /dev/null 2>&1 || die "AWS CLI not installed. Install: sudo apt-get install -y awscli" |
| 56 | |
| 57 | # Verify we can reach SSM (IAM role check) — use GetParameter on DB_PASSWORD |
| 58 | # (always required) rather than GetParametersByPath (requires broader permission). |
| 59 | aws ssm get-parameter \ |
| 60 | --name "$SSM_PREFIX/DB_PASSWORD" \ |
| 61 | --region "$REGION" \ |
| 62 | --with-decryption \ |
| 63 | --query 'Parameter.Value' \ |
| 64 | --output text > /dev/null 2>&1 \ |
| 65 | || die "Cannot read $SSM_PREFIX/DB_PASSWORD from SSM — check the EC2 instance IAM role." |
| 66 | |
| 67 | log "Fetching secrets from SSM: $SSM_PREFIX (region=$REGION)" |
| 68 | |
| 69 | # ── Fetch each parameter ────────────────────────────────────────────────────── |
| 70 | |
| 71 | _get() { |
| 72 | local name="$1" |
| 73 | local required="${2:-true}" |
| 74 | local value |
| 75 | value=$(aws ssm get-parameter \ |
| 76 | --name "$SSM_PREFIX/$name" \ |
| 77 | --region "$REGION" \ |
| 78 | --with-decryption \ |
| 79 | --query 'Parameter.Value' \ |
| 80 | --output text 2>/dev/null) || { |
| 81 | if [ "$required" = "true" ]; then |
| 82 | die "Required parameter $SSM_PREFIX/$name not found in SSM" |
| 83 | fi |
| 84 | echo "" |
| 85 | return |
| 86 | } |
| 87 | echo "$value" |
| 88 | } |
| 89 | |
| 90 | DB_PASSWORD=$(_get "DB_PASSWORD") |
| 91 | WEBHOOK_SECRET_KEY=$(_get "WEBHOOK_SECRET_KEY") |
| 92 | RUNNER_TOKEN=$(_get "RUNNER_TOKEN" false) |
| 93 | BLOB_STORAGE_ACCESS_KEY_ID=$(_get "BLOB_STORAGE_ACCESS_KEY_ID" false) |
| 94 | BLOB_STORAGE_SECRET_ACCESS_KEY=$(_get "BLOB_STORAGE_SECRET_ACCESS_KEY" false) |
| 95 | WORKER_INTERNAL_KEY=$(_get "WORKER_INTERNAL_KEY" false) |
| 96 | PACK_WORKER_URL=$(_get "PACK_WORKER_URL" false) |
| 97 | |
| 98 | # ── Resolve per-environment non-secret config ───────────────────────────────── |
| 99 | |
| 100 | if [ "$MUSEHUB_ENV" = "staging" ]; then |
| 101 | PUBLIC_URL="https://staging.musehub.ai" |
| 102 | CORS_ORIGINS='["https://staging.musehub.ai"]' |
| 103 | BLOB_STORAGE_BUCKET="musehub-staging" |
| 104 | BLOB_STORAGE_ENDPOINT="https://bed873d46de5273abf843468a7833f09.r2.cloudflarestorage.com" |
| 105 | BLOB_STORAGE_REGION="auto" |
| 106 | elif [ "$MUSEHUB_ENV" = "production" ]; then |
| 107 | PUBLIC_URL="https://musehub.ai" |
| 108 | CORS_ORIGINS='["https://musehub.ai", "https://www.musehub.ai"]' |
| 109 | BLOB_STORAGE_BUCKET="musehub-prod" |
| 110 | BLOB_STORAGE_ENDPOINT="https://bed873d46de5273abf843468a7833f09.r2.cloudflarestorage.com" |
| 111 | BLOB_STORAGE_REGION="auto" |
| 112 | else |
| 113 | die "Unknown MUSEHUB_ENV='$MUSEHUB_ENV'. Must be 'staging' or 'production'." |
| 114 | fi |
| 115 | |
| 116 | # ── Write .env ──────────────────────────────────────────────────────────────── |
| 117 | |
| 118 | log "Writing $ENV_FILE (env=$MUSEHUB_ENV, public_url=$PUBLIC_URL)" |
| 119 | |
| 120 | # Back up the existing .env if present |
| 121 | if [ -f "$ENV_FILE" ]; then |
| 122 | cp "$ENV_FILE" "${ENV_FILE}.bak.$(date +%Y%m%d_%H%M%S)" |
| 123 | log "Previous .env backed up" |
| 124 | fi |
| 125 | |
| 126 | # Write new .env — mode 600, owner musehub |
| 127 | umask 177 |
| 128 | cat > "$ENV_FILE" << EOF |
| 129 | # Generated by deploy/secrets.sh at $(date -u +%Y-%m-%dT%H:%M:%SZ) |
| 130 | # Secrets sourced from AWS SSM Parameter Store: $SSM_PREFIX |
| 131 | # DO NOT edit manually — re-run secrets.sh to refresh from SSM. |
| 132 | |
| 133 | MUSE_ENV=${MUSEHUB_ENV} |
| 134 | DEBUG=false |
| 135 | PUBLIC_URL=${PUBLIC_URL} |
| 136 | CORS_ORIGINS=${CORS_ORIGINS} |
| 137 | DB_PASSWORD=${DB_PASSWORD} |
| 138 | BLOB_STORAGE_BUCKET=${BLOB_STORAGE_BUCKET} |
| 139 | BLOB_STORAGE_ENDPOINT=${BLOB_STORAGE_ENDPOINT} |
| 140 | BLOB_STORAGE_REGION=${BLOB_STORAGE_REGION} |
| 141 | WEBHOOK_SECRET_KEY=${WEBHOOK_SECRET_KEY} |
| 142 | EOF |
| 143 | if [ -n "$RUNNER_TOKEN" ]; then |
| 144 | echo "RUNNER_TOKEN=${RUNNER_TOKEN}" >> "$ENV_FILE" |
| 145 | fi |
| 146 | if [ -n "$BLOB_STORAGE_ACCESS_KEY_ID" ]; then |
| 147 | echo "BLOB_STORAGE_ACCESS_KEY_ID=${BLOB_STORAGE_ACCESS_KEY_ID}" >> "$ENV_FILE" |
| 148 | echo "BLOB_STORAGE_SECRET_ACCESS_KEY=${BLOB_STORAGE_SECRET_ACCESS_KEY}" >> "$ENV_FILE" |
| 149 | fi |
| 150 | if [ -n "$WORKER_INTERNAL_KEY" ]; then |
| 151 | echo "WORKER_INTERNAL_KEY=${WORKER_INTERNAL_KEY}" >> "$ENV_FILE" |
| 152 | fi |
| 153 | if [ -n "$PACK_WORKER_URL" ]; then |
| 154 | echo "PACK_WORKER_URL=${PACK_WORKER_URL}" >> "$ENV_FILE" |
| 155 | fi |
| 156 | |
| 157 | chown musehub:musehub "$ENV_FILE" 2>/dev/null || true |
| 158 | log ".env written ($(wc -l < "$ENV_FILE") lines, mode 600)" |
| 159 | |
| 160 | # ── Sanity check — no weak values leaked into env ──────────────────────────── |
| 161 | |
| 162 | WEAK_PASSWORDS=("musehub" "changeme123" "password" "postgres" "secret" "") |
| 163 | for WEAK in "${WEAK_PASSWORDS[@]}"; do |
| 164 | if [ "$DB_PASSWORD" = "$WEAK" ]; then |
| 165 | die "DB_PASSWORD from SSM is a known weak value ($WEAK). Rotate it immediately." |
| 166 | fi |
| 167 | done |
| 168 | |
| 169 | if [ ${#DB_PASSWORD} -lt 16 ]; then |
| 170 | die "DB_PASSWORD from SSM is too short (${#DB_PASSWORD} chars). Minimum is 16." |
| 171 | fi |
| 172 | |
| 173 | log "Secrets sanity check passed." |
| 174 | log "Run 'bash deploy/deploy.sh' to deploy." |
File History
1 commit
sha256:3ff9c9863a9891bdcde71b4a43228f66d0493e38b7cc1d09fe9eb7de774046b2
feat: add repair-commit wire endpoint (API parity with repa…
Opus 4.8
minor
⚠
1 day ago