create-headroom 0.1.10 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template/README.md +74 -0
- package/template/package.json +1 -1
- package/template/scripts/setup.sh +181 -9
package/dist/index.js
CHANGED
|
@@ -80,7 +80,11 @@ async function main() {
|
|
|
80
80
|
|
|
81
81
|
Next steps:
|
|
82
82
|
cd ${projectName}
|
|
83
|
-
./scripts/setup.sh <stage> # First-time setup
|
|
83
|
+
./scripts/setup.sh <stage> # First-time setup: sets the three SST secrets
|
|
84
|
+
# (image-signing master + rotation fallback +
|
|
85
|
+
# internal service-to-service) and deploys.
|
|
86
|
+
# Re-running is idempotent — already-set
|
|
87
|
+
# secrets are left alone.
|
|
84
88
|
|
|
85
89
|
For subsequent deploys:
|
|
86
90
|
npx sst deploy --stage <stage>
|
|
@@ -88,6 +92,10 @@ async function main() {
|
|
|
88
92
|
After deploying, create your first admin user:
|
|
89
93
|
./scripts/create-admin.sh admin@example.com 'YourPassword123!'
|
|
90
94
|
|
|
95
|
+
To rotate the image-signing master (preserving the grace window for
|
|
96
|
+
already-signed URLs):
|
|
97
|
+
./scripts/setup.sh <stage> --force-rotate
|
|
98
|
+
|
|
91
99
|
Documentation: https://github.com/cykod/headroom
|
|
92
100
|
`);
|
|
93
101
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Headroom CMS Deployment
|
|
2
|
+
|
|
3
|
+
This project deploys a Headroom CMS instance to AWS via SST.
|
|
4
|
+
|
|
5
|
+
## First-time setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
./scripts/setup.sh <stage>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This generates the three SST secrets required by the stack and then runs
|
|
12
|
+
`npx sst deploy`. Re-running the script is idempotent: already-set secrets
|
|
13
|
+
are left alone.
|
|
14
|
+
|
|
15
|
+
### Required SST secrets
|
|
16
|
+
|
|
17
|
+
The instance name `CMS` (the first argument to `new HeadroomCMS(...)` in
|
|
18
|
+
`sst.config.ts`) is prepended to each secret name. If you change that
|
|
19
|
+
instance name, also update `INSTANCE` at the top of `scripts/setup.sh`.
|
|
20
|
+
|
|
21
|
+
| Secret | Required? | What breaks if missing |
|
|
22
|
+
|--------|-----------|------------------------|
|
|
23
|
+
| `CMSImageSigningMasterSecret` | **Yes** | All `/img/*` image-transform requests return 403. Per-site signing keys are derived from this master via HMAC-SHA256. |
|
|
24
|
+
| `CMSImageSigningMasterSecretOld` | Optional (set to empty by default) | Rotation fallback. The Sharp Lambda accepts URLs signed with either master, so populating this lets you rotate the primary without invalidating already-issued URLs. Empty disables the fallback path. |
|
|
25
|
+
| `CMSInternalSecret` | **Yes** (if collaboration is enabled) | The collaboration Lambda's calls to the Go API (via the `X-Headroom-Internal` header) are rejected, breaking real-time editing. |
|
|
26
|
+
|
|
27
|
+
## Rotating the image signing master
|
|
28
|
+
|
|
29
|
+
To rotate while preserving signed URLs that may still be in CDN / browser
|
|
30
|
+
caches:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
./scripts/setup.sh <stage> --force-rotate
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This moves the current `CMSImageSigningMasterSecret` value to
|
|
37
|
+
`CMSImageSigningMasterSecretOld`, generates a new primary, and redeploys.
|
|
38
|
+
`CMSInternalSecret` is **not** rotated by this flag — it has no OLD-fallback
|
|
39
|
+
pair and rotating it would reject every in-flight collab Lambda → Go API
|
|
40
|
+
request until the new deploy completes.
|
|
41
|
+
|
|
42
|
+
Manual equivalent:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx sst secret set CMSImageSigningMasterSecretOld <current-value> --stage <stage>
|
|
46
|
+
npx sst secret set CMSImageSigningMasterSecret <new-value> --stage <stage>
|
|
47
|
+
npx sst deploy --stage <stage>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
See the root project's `CLAUDE.md` "Rotating the image signing master"
|
|
51
|
+
section for guidance on how long to leave `MasterSecretOld` populated.
|
|
52
|
+
|
|
53
|
+
## Rotating the internal secret
|
|
54
|
+
|
|
55
|
+
`CMSInternalSecret` has no rotation grace window — rotating it causes a
|
|
56
|
+
brief collab outage until the next deploy completes. Plan a deploy window,
|
|
57
|
+
then rotate manually:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx sst secret set CMSInternalSecret "$(openssl rand -hex 32)" --stage <stage>
|
|
61
|
+
npx sst deploy --stage <stage>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Subsequent deploys
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx sst deploy --stage <stage>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Creating an admin user
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
./scripts/create-admin.sh admin@example.com 'YourPassword123!'
|
|
74
|
+
```
|
package/template/package.json
CHANGED
|
@@ -1,37 +1,209 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# First-time setup for a Headroom CMS deployment stage.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# Sets the three SST secrets required by Headroom and then deploys.
|
|
5
|
+
#
|
|
6
|
+
# The instance name (INSTANCE below) MUST match the first argument passed
|
|
7
|
+
# to `new HeadroomCMS(...)` in sst.config.ts — the secret resources are
|
|
8
|
+
# named `${INSTANCE}ImageSigningMasterSecret`, etc.
|
|
9
|
+
#
|
|
10
|
+
# Secrets set:
|
|
11
|
+
# 1. ${INSTANCE}ImageSigningMasterSecret — REQUIRED. Master KDF input
|
|
12
|
+
# used to derive per-site image signing keys. Without it, every
|
|
13
|
+
# /img/* request 403s.
|
|
14
|
+
# 2. ${INSTANCE}ImageSigningMasterSecretOld — OPTIONAL fallback master,
|
|
15
|
+
# initialised empty. The Sharp Lambda accepts URLs signed with either
|
|
16
|
+
# master, so populating this lets you rotate the primary without
|
|
17
|
+
# invalidating already-issued URLs.
|
|
18
|
+
# 3. ${INSTANCE}InternalSecret — REQUIRED for collaboration.
|
|
19
|
+
# Shared secret on the X-Headroom-Internal header between the collab
|
|
20
|
+
# Lambda and the Go API.
|
|
21
|
+
#
|
|
22
|
+
# Idempotency: re-running the script does NOT overwrite already-set
|
|
23
|
+
# secrets. Pass --force-rotate to rotate the image-signing master only
|
|
24
|
+
# (moves the current value to MasterSecretOld before generating a new
|
|
25
|
+
# primary, preserving the rotation grace window for in-the-wild signed
|
|
26
|
+
# URLs). InternalSecret rotation is NOT covered by --force-rotate — it
|
|
27
|
+
# has no OLD-fallback and rotating it breaks in-flight collab connections
|
|
28
|
+
# without grace. Rotate InternalSecret manually if needed:
|
|
29
|
+
# npx sst secret set CMSInternalSecret <new-value> --stage <stage>
|
|
5
30
|
#
|
|
6
31
|
# Usage:
|
|
7
32
|
# ./scripts/setup.sh <stage>
|
|
33
|
+
# ./scripts/setup.sh <stage> --force-rotate
|
|
8
34
|
#
|
|
9
35
|
# Example:
|
|
10
36
|
# ./scripts/setup.sh production
|
|
11
37
|
|
|
12
38
|
set -euo pipefail
|
|
13
39
|
|
|
14
|
-
|
|
40
|
+
INSTANCE="CMS" # Must match the instance name in sst.config.ts
|
|
41
|
+
|
|
42
|
+
FORCE_ROTATE=0
|
|
43
|
+
STAGE=""
|
|
44
|
+
for arg in "$@"; do
|
|
45
|
+
case "$arg" in
|
|
46
|
+
--force-rotate|--rotate)
|
|
47
|
+
FORCE_ROTATE=1
|
|
48
|
+
;;
|
|
49
|
+
-*)
|
|
50
|
+
echo "Unknown flag: $arg" >&2
|
|
51
|
+
echo "Usage: $0 <stage> [--force-rotate]" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
;;
|
|
54
|
+
*)
|
|
55
|
+
if [ -z "$STAGE" ]; then
|
|
56
|
+
STAGE="$arg"
|
|
57
|
+
else
|
|
58
|
+
echo "Unexpected positional argument: $arg" >&2
|
|
59
|
+
echo "Usage: $0 <stage> [--force-rotate]" >&2
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
;;
|
|
63
|
+
esac
|
|
64
|
+
done
|
|
15
65
|
|
|
16
66
|
if [ -z "$STAGE" ]; then
|
|
17
|
-
echo "Usage: $0 <stage>" >&2
|
|
67
|
+
echo "Usage: $0 <stage> [--force-rotate]" >&2
|
|
18
68
|
echo "" >&2
|
|
19
69
|
echo "Run this once per stage before your first deploy." >&2
|
|
20
70
|
echo "Example: $0 production" >&2
|
|
21
71
|
exit 1
|
|
22
72
|
fi
|
|
23
73
|
|
|
74
|
+
# Sanity-check: INSTANCE here must match `new HeadroomCMS("...", ...)` in
|
|
75
|
+
# sst.config.ts. If they disagree, the script would set secrets under the
|
|
76
|
+
# wrong name and the deploy would fail at apply time with an opaque "secret
|
|
77
|
+
# not set" error. Catch the mismatch up front with a helpful message.
|
|
78
|
+
if [ -f sst.config.ts ]; then
|
|
79
|
+
ACTUAL_INSTANCE=$(grep -oE 'new HeadroomCMS\("[^"]+"' sst.config.ts | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || true)
|
|
80
|
+
if [ -n "$ACTUAL_INSTANCE" ] && [ "$ACTUAL_INSTANCE" != "$INSTANCE" ]; then
|
|
81
|
+
echo "ERROR: sst.config.ts uses 'new HeadroomCMS(\"$ACTUAL_INSTANCE\", ...)' but INSTANCE=\"$INSTANCE\" in this script." >&2
|
|
82
|
+
echo " Edit the INSTANCE= line at the top of this script to match \"$ACTUAL_INSTANCE\"." >&2
|
|
83
|
+
echo " (Or rename the instance in sst.config.ts back to \"$INSTANCE\".)" >&2
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
24
88
|
echo "==> Setting up stage: $STAGE"
|
|
89
|
+
if [ "$FORCE_ROTATE" -eq 1 ]; then
|
|
90
|
+
echo " (--force-rotate: image-signing master will be rotated)"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# secret_get <name> — print the current value of an SST secret to stdout, or
|
|
94
|
+
# empty string if the secret has never been set.
|
|
95
|
+
#
|
|
96
|
+
# This must distinguish two failure modes:
|
|
97
|
+
# (A) secret unset → expected on first run; treat as empty result.
|
|
98
|
+
# (B) any other SST/AWS failure (wrong stage, missing creds, transient
|
|
99
|
+
# error, etc.) → hard-fail. We MUST NOT proceed: a silent failure
|
|
100
|
+
# here on a real stage would regenerate the image-signing master
|
|
101
|
+
# and destroy every signed /img/* URL in the wild.
|
|
102
|
+
#
|
|
103
|
+
# SST exits non-zero with a "not set" / "not found" / "does not exist"
|
|
104
|
+
# message on (A); other failures produce different stderr. We match
|
|
105
|
+
# substrings as a defensible-but-imperfect proxy for distinct exit codes
|
|
106
|
+
# (SST does not document distinct codes per failure class).
|
|
107
|
+
secret_get() {
|
|
108
|
+
local name="$1"
|
|
109
|
+
local err_file out
|
|
110
|
+
err_file=$(mktemp 2>/dev/null || echo "/tmp/setup-sst-err.$$")
|
|
111
|
+
if out=$(npx sst secret get "$name" --stage "$STAGE" 2>"$err_file"); then
|
|
112
|
+
rm -f "$err_file"
|
|
113
|
+
printf '%s' "$out"
|
|
114
|
+
return 0
|
|
115
|
+
fi
|
|
116
|
+
# Non-zero exit. Classify.
|
|
117
|
+
local err
|
|
118
|
+
err=$(cat "$err_file" 2>/dev/null || true)
|
|
119
|
+
rm -f "$err_file"
|
|
120
|
+
if echo "$err" | grep -qiE "(not (set|found)|does not exist|no such secret)"; then
|
|
121
|
+
# (A) Secret has never been set — return empty stdout, exit 0.
|
|
122
|
+
return 0
|
|
123
|
+
fi
|
|
124
|
+
# (B) Anything else — surface and abort. Refusing to proceed prevents
|
|
125
|
+
# destroying production secrets when the user typed the wrong --stage
|
|
126
|
+
# or AWS credentials have expired.
|
|
127
|
+
echo "ERROR: querying ${name} (stage=${STAGE}) failed:" >&2
|
|
128
|
+
if [ -n "$err" ]; then
|
|
129
|
+
echo "$err" | sed 's/^/ /' >&2
|
|
130
|
+
fi
|
|
131
|
+
echo "" >&2
|
|
132
|
+
echo " Refusing to proceed — re-run with the correct --stage, or fix" >&2
|
|
133
|
+
echo " AWS credentials. If you believe this is a transient SST error," >&2
|
|
134
|
+
echo " re-run the script." >&2
|
|
135
|
+
exit 1
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# 1. Image signing master (REQUIRED)
|
|
139
|
+
EXISTING_MASTER=$(secret_get "${INSTANCE}ImageSigningMasterSecret")
|
|
140
|
+
if [ "$FORCE_ROTATE" -eq 1 ]; then
|
|
141
|
+
if [ -n "$EXISTING_MASTER" ]; then
|
|
142
|
+
echo "==> Rotating ${INSTANCE}ImageSigningMasterSecret — moving current value to ${INSTANCE}ImageSigningMasterSecretOld"
|
|
143
|
+
npx sst secret set "${INSTANCE}ImageSigningMasterSecretOld" "$EXISTING_MASTER" --stage "$STAGE"
|
|
144
|
+
fi
|
|
145
|
+
SECRET=$(openssl rand -hex 32)
|
|
146
|
+
echo "==> Setting new ${INSTANCE}ImageSigningMasterSecret..."
|
|
147
|
+
npx sst secret set "${INSTANCE}ImageSigningMasterSecret" "$SECRET" --stage "$STAGE"
|
|
148
|
+
elif [ -n "$EXISTING_MASTER" ]; then
|
|
149
|
+
echo "==> ${INSTANCE}ImageSigningMasterSecret already set, leaving alone (pass --force-rotate to rotate)"
|
|
150
|
+
else
|
|
151
|
+
SECRET=$(openssl rand -hex 32)
|
|
152
|
+
echo "==> Setting ${INSTANCE}ImageSigningMasterSecret..."
|
|
153
|
+
npx sst secret set "${INSTANCE}ImageSigningMasterSecret" "$SECRET" --stage "$STAGE"
|
|
154
|
+
fi
|
|
25
155
|
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
156
|
+
# 2. Image signing master OLD (rotation fallback; empty disables it)
|
|
157
|
+
#
|
|
158
|
+
# We always touch this secret on first setup so the resource exists in SST
|
|
159
|
+
# state — that way the eventual rotation step ("move current to OLD before
|
|
160
|
+
# setting a new primary") is a single command rather than a create-or-update
|
|
161
|
+
# branch. Force-rotate above may have populated it with the previous master;
|
|
162
|
+
# only initialize to empty if no prior value (and no rotation) set it.
|
|
163
|
+
if [ "$FORCE_ROTATE" -eq 0 ]; then
|
|
164
|
+
EXISTING_OLD=$(secret_get "${INSTANCE}ImageSigningMasterSecretOld")
|
|
165
|
+
# `sst secret get` returns empty for both "never set" and "set to empty
|
|
166
|
+
# string". That's fine — setting it to empty again is a no-op-ish call
|
|
167
|
+
# that ensures the resource exists. We only call it when nothing else
|
|
168
|
+
# has populated it in this run.
|
|
169
|
+
if [ -z "$EXISTING_OLD" ]; then
|
|
170
|
+
echo "==> Initializing ${INSTANCE}ImageSigningMasterSecretOld (empty)..."
|
|
171
|
+
npx sst secret set "${INSTANCE}ImageSigningMasterSecretOld" "" --stage "$STAGE"
|
|
172
|
+
else
|
|
173
|
+
echo "==> ${INSTANCE}ImageSigningMasterSecretOld already set, leaving alone"
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
# 3. Internal service-to-service secret (REQUIRED if collab is enabled)
|
|
178
|
+
#
|
|
179
|
+
# NOT rotated by --force-rotate: it has no OLD-fallback pair, so rotating
|
|
180
|
+
# it would reject every in-flight collab Lambda → Go API request with 401
|
|
181
|
+
# until the new deploy completes. Rotate manually only when you can tolerate
|
|
182
|
+
# a brief collab outage:
|
|
183
|
+
# npx sst secret set ${INSTANCE}InternalSecret <new> --stage <stage>
|
|
184
|
+
EXISTING_INTERNAL=$(secret_get "${INSTANCE}InternalSecret")
|
|
185
|
+
if [ -z "$EXISTING_INTERNAL" ]; then
|
|
186
|
+
INTERNAL=$(openssl rand -hex 32)
|
|
187
|
+
echo "==> Setting ${INSTANCE}InternalSecret..."
|
|
188
|
+
npx sst secret set "${INSTANCE}InternalSecret" "$INTERNAL" --stage "$STAGE"
|
|
189
|
+
else
|
|
190
|
+
echo "==> ${INSTANCE}InternalSecret already set, leaving alone"
|
|
191
|
+
fi
|
|
30
192
|
|
|
31
193
|
echo ""
|
|
32
194
|
echo "==> Deploying stage: $STAGE"
|
|
33
195
|
npx sst deploy --stage "$STAGE"
|
|
34
196
|
|
|
35
197
|
echo ""
|
|
36
|
-
echo "==> Setup complete!
|
|
37
|
-
echo "
|
|
198
|
+
echo "==> Setup complete!"
|
|
199
|
+
echo ""
|
|
200
|
+
echo " To rotate the image-signing master later, preserving the grace"
|
|
201
|
+
echo " window for already-issued signed URLs:"
|
|
202
|
+
echo " ./scripts/setup.sh $STAGE --force-rotate"
|
|
203
|
+
echo ""
|
|
204
|
+
echo " Or manually:"
|
|
205
|
+
echo " npx sst secret set ${INSTANCE}ImageSigningMasterSecretOld <current> --stage $STAGE"
|
|
206
|
+
echo " npx sst secret set ${INSTANCE}ImageSigningMasterSecret <new> --stage $STAGE"
|
|
207
|
+
echo ""
|
|
208
|
+
echo " Next: create your first admin user"
|
|
209
|
+
echo " ./scripts/create-admin.sh admin@example.com 'YourPassword123!'"
|