codex-snapshots 0.1.0 → 0.1.1

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.
Files changed (51) hide show
  1. package/README.md +101 -6
  2. package/bin/codex-snapshot.mjs +1 -6326
  3. package/deploy/aliyun/README.md +311 -0
  4. package/deploy/aliyun/backup-share-data.sh +109 -0
  5. package/deploy/aliyun/check-ecs-status.sh +149 -0
  6. package/deploy/aliyun/codex-snapshot-share.env.example +29 -0
  7. package/deploy/aliyun/codex-snapshot-share.service +26 -0
  8. package/deploy/aliyun/configure-github-pages-api.sh +141 -0
  9. package/deploy/aliyun/configure-local-publisher.sh +197 -0
  10. package/deploy/aliyun/deploy-to-ecs.sh +669 -0
  11. package/deploy/aliyun/deploy.env.example +52 -0
  12. package/deploy/aliyun/doctor.mjs +398 -0
  13. package/deploy/aliyun/install-share-api.sh +252 -0
  14. package/deploy/aliyun/install-system-deps.sh +84 -0
  15. package/deploy/aliyun/nginx-codex-snapshots.bootstrap.conf +34 -0
  16. package/deploy/aliyun/nginx-codex-snapshots.conf +52 -0
  17. package/deploy/aliyun/preflight.mjs +321 -0
  18. package/deploy/aliyun/restore-share-data.sh +141 -0
  19. package/deploy/aliyun/verify-public-share.mjs +404 -0
  20. package/dist/cli/codex-snapshot.mjs +2654 -0
  21. package/dist/core/privacy.js +81 -0
  22. package/dist/core/snapshot.js +1 -0
  23. package/dist/renderers/markdown.mjs +81 -0
  24. package/dist/renderers/transcript.js +195 -0
  25. package/dist/server/http.js +10 -0
  26. package/dist/server/local-security.js +66 -0
  27. package/dist/server/local-viewer-app.mjs +1670 -0
  28. package/dist/server/local-viewer.mjs +210 -0
  29. package/dist/server/share-api.mjs +1149 -0
  30. package/dist/server/share-store.js +136 -0
  31. package/dist/shared/sanitize.js +126 -0
  32. package/dist/shared/transcript.js +1 -0
  33. package/dist/sources/index.mjs +2 -0
  34. package/dist/sources/local-history.mjs +2221 -0
  35. package/package.json +42 -14
  36. package/scripts/build-site.mjs +71 -0
  37. package/scripts/launch-agent.mjs +19 -227
  38. package/scripts/serve-site.mjs +2 -2
  39. package/scripts/test-aliyun-deploy-config.sh +230 -0
  40. package/scripts/test-share-api.mjs +967 -0
  41. package/scripts/test-site-config.mjs +100 -0
  42. package/scripts/test-static-site.mjs +403 -0
  43. package/scripts/write-site-config.mjs +161 -0
  44. package/server/share-api.mjs +1 -771
  45. package/site/assets/config.js +3 -0
  46. package/site/assets/share.js +43 -106
  47. package/site/assets/site.css +3 -605
  48. package/site/assets/site.js +15 -92
  49. package/site/favicon.svg +7 -0
  50. package/site/index.html +3 -83
  51. package/site/share/index.html +3 -8
@@ -0,0 +1,311 @@
1
+ # Deploy the Share API on Alibaba Cloud ECS
2
+
3
+ This deployment runs `codex-snapshot-share` on `127.0.0.1:8787`, exposes it through Nginx + HTTPS, and lets the GitHub Pages site list and render public sessions.
4
+
5
+ Replace these examples before running commands:
6
+
7
+ - `snapshots.example.com`: your public API domain
8
+ - `your-client-id` / `your-client-secret`: your GitHub OAuth App credentials
9
+ - `your-github-login`: your GitHub login; this account can delete any shared session
10
+ - `https://ffffhx.github.io/codex-snapshots/`: the public static site
11
+
12
+ ## 1. Prepare Alibaba Cloud
13
+
14
+ 1. Point a DNS `A` record for `snapshots.example.com` to the ECS public IP.
15
+ 2. In the ECS security group, allow inbound TCP `80` and `443`.
16
+ 3. Do not expose port `8787` publicly; the service binds to `127.0.0.1`.
17
+
18
+ ## 2. Install host dependencies
19
+
20
+ On the ECS host, install Node.js 18 or newer, Nginx, Certbot, Git, OpenSSL, and rsync.
21
+
22
+ Ubuntu/Debian example:
23
+
24
+ ```bash
25
+ sudo apt-get update
26
+ sudo apt-get install -y nginx certbot python3-certbot-nginx git openssl rsync
27
+ node --version
28
+ ```
29
+
30
+ Alibaba Cloud Linux/RHEL-style systems usually use `dnf` or `yum` packages instead of `apt`.
31
+
32
+ You can also let the deploy helper install the common dependencies on Ubuntu/Debian, Alibaba Cloud Linux, or RHEL-style hosts:
33
+
34
+ ```bash
35
+ deploy/aliyun/deploy-to-ecs.sh \
36
+ --ssh root@1.2.3.4 \
37
+ --domain snapshots.example.com \
38
+ --install-deps \
39
+ --configure-local
40
+ ```
41
+
42
+ The helper installs Node.js 20 when the host does not already have Node.js 18+.
43
+
44
+ ## 3. Deploy from your local machine
45
+
46
+ Optionally create a local deployment config so you do not have to type the long command each time:
47
+
48
+ ```bash
49
+ cp deploy/aliyun/deploy.env.example deploy/aliyun/deploy.env
50
+ $EDITOR deploy/aliyun/deploy.env
51
+ deploy/aliyun/deploy-to-ecs.sh --config deploy/aliyun/deploy.env --dry-run
52
+ node deploy/aliyun/doctor.mjs --config deploy/aliyun/deploy.env
53
+ deploy/aliyun/deploy-to-ecs.sh --config deploy/aliyun/deploy.env
54
+ ```
55
+
56
+ `deploy/aliyun/deploy.env` is ignored by git because it contains host details, OAuth secrets, and optional legacy token values.
57
+ The dry run validates that you replaced template values like `root@1.2.3.4`, `snapshots.example.com`, `change-me`, and `you@example.com` before anything connects to ECS.
58
+ The doctor command adds local readiness checks for DNS, local `ssh`/`rsync`/`node`, GitHub CLI when Pages auto-config is enabled, auth config, and the deploy dry run. Pass `--offline` if DNS has not propagated yet.
59
+ The deploy and install scripts also exclude local secret files such as `.env`, `deploy/aliyun/deploy.env`, private-key files, and local `backups/` from the ECS rsync payload.
60
+
61
+ Run the preflight check before deploying:
62
+
63
+ ```bash
64
+ node deploy/aliyun/preflight.mjs \
65
+ --domain snapshots.example.com \
66
+ --ssh root@1.2.3.4
67
+ ```
68
+
69
+ This checks DNS, SSH access, Node.js 18+, Nginx, Certbot, rsync, OpenSSL, systemd, and root/passwordless-sudo privileges on the ECS host.
70
+ When you deploy with `--issue-cert`, the deploy helper also checks that Certbot has the Nginx plugin.
71
+
72
+ After DNS points at the ECS public IP and SSH works, deploy the current checkout from your local machine:
73
+
74
+ ```bash
75
+ SNAPSHOT_GITHUB_CLIENT_ID=your-client-id \
76
+ SNAPSHOT_GITHUB_CLIENT_SECRET=your-client-secret \
77
+ SNAPSHOT_SESSION_SECRET="$(openssl rand -base64 48)" \
78
+ SNAPSHOT_GITHUB_OWNER_LOGIN=your-github-login \
79
+ deploy/aliyun/deploy-to-ecs.sh \
80
+ --ssh root@1.2.3.4 \
81
+ --domain snapshots.example.com \
82
+ --configure-local
83
+ ```
84
+
85
+ This runs the preflight checks, rsyncs the repository to the ECS host, installs the systemd service, and writes the Nginx bootstrap config. If you have already checked the host and want to skip preflight, pass `--no-preflight`.
86
+ GitHub OAuth makes publish/delete use GitHub login instead of a shared token. The site owner in `SNAPSHOT_GITHUB_OWNER_LOGIN` can delete any shared session; other GitHub users can delete only their own sessions.
87
+ Keep the same `SNAPSHOT_SESSION_SECRET` across redeploys so existing login cookies remain valid. If you omit it while OAuth vars are present, the deploy helper generates one for that deploy.
88
+
89
+ After port `80` works, issue HTTPS and switch to the HTTPS proxy template:
90
+
91
+ ```bash
92
+ deploy/aliyun/deploy-to-ecs.sh \
93
+ --ssh root@1.2.3.4 \
94
+ --domain snapshots.example.com \
95
+ --issue-cert \
96
+ --email you@example.com
97
+ ```
98
+
99
+ The deploy script verifies the ECS API at the end. In GitHub OAuth mode it skips command-line token publishing because writes require a browser GitHub login; configure Pages in step 6, then run the public loop verification in step 7.
100
+
101
+ You can also let the deploy command configure GitHub Pages and the local publisher in the same run:
102
+
103
+ ```bash
104
+ deploy/aliyun/deploy-to-ecs.sh \
105
+ --ssh root@1.2.3.4 \
106
+ --domain snapshots.example.com \
107
+ --install-deps \
108
+ --issue-cert \
109
+ --email you@example.com \
110
+ --configure-pages \
111
+ --wait-pages \
112
+ --configure-local \
113
+ --reinstall-daemon
114
+ ```
115
+
116
+ `--configure-pages` requires GitHub CLI auth on your local machine. With `--wait-pages`, the deploy script waits for the Pages workflow and then runs the full public verification.
117
+
118
+ ## 4. Install the service manually
119
+
120
+ Clone this repository on the ECS host, then run the installer from the repository root:
121
+
122
+ ```bash
123
+ git clone https://github.com/ffffhx/codex-snapshots.git
124
+ cd codex-snapshots
125
+
126
+ sudo DOMAIN=snapshots.example.com \
127
+ SNAPSHOT_GITHUB_CLIENT_ID=your-client-id \
128
+ SNAPSHOT_GITHUB_CLIENT_SECRET=your-client-secret \
129
+ SNAPSHOT_SESSION_SECRET="$(openssl rand -base64 48)" \
130
+ SNAPSHOT_GITHUB_OWNER_LOGIN=your-github-login \
131
+ SNAPSHOT_SHARE_SITE_URL=https://ffffhx.github.io/codex-snapshots/ \
132
+ SNAPSHOT_SHARE_PUBLIC_API_URL=https://snapshots.example.com \
133
+ deploy/aliyun/install-share-api.sh
134
+ ```
135
+
136
+ The first run installs an HTTP bootstrap Nginx config if a TLS certificate is not present yet.
137
+
138
+ ## 5. Enable HTTPS manually
139
+
140
+ Issue a certificate after DNS and port `80` are working:
141
+
142
+ ```bash
143
+ sudo certbot --nginx -d snapshots.example.com
144
+ ```
145
+
146
+ Then re-run the installer so it switches Nginx to the committed HTTPS proxy template:
147
+
148
+ ```bash
149
+ sudo DOMAIN=snapshots.example.com \
150
+ SNAPSHOT_GITHUB_CLIENT_ID=your-client-id \
151
+ SNAPSHOT_GITHUB_CLIENT_SECRET=your-client-secret \
152
+ SNAPSHOT_SESSION_SECRET="$(openssl rand -base64 48)" \
153
+ SNAPSHOT_GITHUB_OWNER_LOGIN=your-github-login \
154
+ SNAPSHOT_SHARE_SITE_URL=https://ffffhx.github.io/codex-snapshots/ \
155
+ SNAPSHOT_SHARE_PUBLIC_API_URL=https://snapshots.example.com \
156
+ deploy/aliyun/install-share-api.sh
157
+ ```
158
+
159
+ Verify:
160
+
161
+ ```bash
162
+ curl https://snapshots.example.com/api/snapshots/health
163
+ curl https://snapshots.example.com/api/snapshots
164
+ ```
165
+
166
+ ## 6. Connect GitHub Pages
167
+
168
+ Set this repository variable in GitHub:
169
+
170
+ ```text
171
+ CODEX_SNAPSHOTS_PUBLIC_API_URL=https://snapshots.example.com
172
+ ```
173
+
174
+ You can set it and trigger the Pages workflow with GitHub CLI:
175
+
176
+ ```bash
177
+ deploy/aliyun/configure-github-pages-api.sh \
178
+ --api-url https://snapshots.example.com \
179
+ --repo ffffhx/codex-snapshots
180
+ ```
181
+
182
+ The helper refuses placeholder or local-only API URLs so the public site does not get configured to fetch from `127.0.0.1` or `snapshots.example.com`.
183
+ The Pages workflow runs the same validation before writing `site/assets/config.js`; empty is allowed, but invalid, example, localhost, and private-network API URLs fail the deploy step.
184
+
185
+ The Pages workflow writes `site/assets/config.js`, so the public homepage fetches:
186
+
187
+ ```text
188
+ https://snapshots.example.com/api/snapshots
189
+ ```
190
+
191
+ If this variable is missing, the public site shows that the share API is not configured instead of attempting to fetch from each visitor's `127.0.0.1`.
192
+
193
+ ## 7. Verify the public loop
194
+
195
+ After the Pages workflow finishes, run the read-only checks:
196
+
197
+ ```bash
198
+ node deploy/aliyun/verify-public-share.mjs \
199
+ --api-url https://snapshots.example.com \
200
+ --site-url https://ffffhx.github.io/codex-snapshots/
201
+ ```
202
+
203
+ This proves:
204
+
205
+ - the ECS API is reachable over HTTPS
206
+ - public reads work
207
+ - CORS allows the public GitHub Pages site
208
+ - the static site config points at the ECS API
209
+
210
+ GitHub OAuth publishing is verified from the browser: open the local viewer, click “发布分享”, log in with GitHub when prompted, and confirm the shared session appears on the public site. For legacy token mode only, add `--publish --token <same-token>` to create one small verification snapshot from the command line.
211
+
212
+ To verify only the ECS API before Pages has deployed, add `--skip-site-config`.
213
+ After configuring the local publisher, add `--check-local-config` to prove that `~/.codex-snapshots-agent.json` also points at the same Aliyun API and public site.
214
+
215
+ ## 8. Publish from your local viewer
216
+
217
+ Configure your local viewer with the public API and site:
218
+
219
+ ```bash
220
+ SNAPSHOT_SHARE_API_URL=https://snapshots.example.com \
221
+ SNAPSHOT_SHARE_SITE_URL=https://ffffhx.github.io/codex-snapshots/ \
222
+ deploy/aliyun/configure-local-publisher.sh
223
+ ```
224
+
225
+ The helper rejects placeholder and local-only API URLs before it writes `~/.codex-snapshots-agent.json`.
226
+ That file stores the public API/site URLs, so the local viewer knows which remote share API to use. With GitHub OAuth, the browser session cookie handles publish/delete auth after login.
227
+
228
+ Verify the local publisher config:
229
+
230
+ ```bash
231
+ node deploy/aliyun/verify-public-share.mjs \
232
+ --api-url https://snapshots.example.com \
233
+ --site-url https://ffffhx.github.io/codex-snapshots/ \
234
+ --skip-site-config \
235
+ --check-local-config
236
+ ```
237
+
238
+ Then start the local viewer:
239
+
240
+ ```bash
241
+ pnpm snapshot serve --port 4321
242
+ ```
243
+
244
+ For the macOS LaunchAgent, let the helper reinstall the daemon with the public API and site URLs:
245
+
246
+ ```bash
247
+ SNAPSHOT_SHARE_API_URL=https://snapshots.example.com \
248
+ SNAPSHOT_SHARE_SITE_URL=https://ffffhx.github.io/codex-snapshots/ \
249
+ deploy/aliyun/configure-local-publisher.sh --reinstall-daemon
250
+ ```
251
+
252
+ The local publisher config can also be edited manually in `~/.codex-snapshots-agent.json`:
253
+
254
+ ```json
255
+ {
256
+ "snapshotShareApiUrl": "https://snapshots.example.com",
257
+ "snapshotShareSiteUrl": "https://ffffhx.github.io/codex-snapshots"
258
+ }
259
+ ```
260
+
261
+ ## Operations
262
+
263
+ Useful ECS commands:
264
+
265
+ ```bash
266
+ sudo systemctl status codex-snapshot-share
267
+ sudo journalctl -u codex-snapshot-share -f
268
+ sudo nginx -t
269
+ sudo systemctl reload nginx
270
+ sudo cp /var/lib/codex-snapshots/shares.json ~/shares.json.backup
271
+ ```
272
+
273
+ You can run the same health checks from your local machine:
274
+
275
+ ```bash
276
+ deploy/aliyun/check-ecs-status.sh \
277
+ --ssh root@1.2.3.4 \
278
+ --domain snapshots.example.com
279
+ ```
280
+
281
+ This checks systemd status, local health, install-file permissions, that port `8787` is only listening locally, Nginx syntax/config, and the public `/api/snapshots/health` endpoint.
282
+
283
+ Back up the share data from ECS to your local checkout:
284
+
285
+ ```bash
286
+ deploy/aliyun/backup-share-data.sh \
287
+ --ssh root@1.2.3.4
288
+ ```
289
+
290
+ Restore a local backup to ECS:
291
+
292
+ ```bash
293
+ deploy/aliyun/restore-share-data.sh \
294
+ --ssh root@1.2.3.4 \
295
+ --file backups/codex-snapshots/shares-20260101T000000Z.json
296
+ ```
297
+
298
+ The restore script validates the JSON locally and saves the previous remote file next to the live data before replacing it.
299
+
300
+ The share data is stored in:
301
+
302
+ ```text
303
+ /var/lib/codex-snapshots/shares.json
304
+ ```
305
+
306
+ Local publisher checks:
307
+
308
+ ```bash
309
+ curl https://snapshots.example.com/api/snapshots/health
310
+ curl https://snapshots.example.com/api/snapshots
311
+ ```
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SSH_TARGET=""
5
+ SSH_OPTS=()
6
+ REMOTE_FILE="/var/lib/codex-snapshots/shares.json"
7
+ OUTPUT_DIR="backups/codex-snapshots"
8
+
9
+ usage() {
10
+ cat <<'EOF'
11
+ Usage:
12
+ deploy/aliyun/backup-share-data.sh --ssh root@1.2.3.4
13
+
14
+ Options:
15
+ --ssh TARGET SSH target, for example root@1.2.3.4.
16
+ --identity-file FILE SSH private key for the ECS host.
17
+ --port PORT SSH port.
18
+ --remote-file FILE Remote share data file. Defaults to /var/lib/codex-snapshots/shares.json.
19
+ --output-dir DIR Local backup directory. Defaults to backups/codex-snapshots.
20
+ -h, --help Show help.
21
+ EOF
22
+ }
23
+
24
+ while [[ $# -gt 0 ]]; do
25
+ case "$1" in
26
+ --ssh)
27
+ SSH_TARGET="${2:-}"
28
+ shift 2
29
+ ;;
30
+ --identity-file)
31
+ SSH_OPTS+=("-i" "${2:-}")
32
+ shift 2
33
+ ;;
34
+ --port)
35
+ SSH_OPTS+=("-p" "${2:-}")
36
+ shift 2
37
+ ;;
38
+ --remote-file)
39
+ REMOTE_FILE="${2:-}"
40
+ shift 2
41
+ ;;
42
+ --output-dir)
43
+ OUTPUT_DIR="${2:-}"
44
+ shift 2
45
+ ;;
46
+ -h|--help)
47
+ usage
48
+ exit 0
49
+ ;;
50
+ *)
51
+ echo "Unknown option: $1" >&2
52
+ usage >&2
53
+ exit 1
54
+ ;;
55
+ esac
56
+ done
57
+
58
+ if [[ -z "${SSH_TARGET}" ]]; then
59
+ echo "Missing --ssh target." >&2
60
+ usage >&2
61
+ exit 1
62
+ fi
63
+
64
+ shell_quote() {
65
+ printf "'"
66
+ printf "%s" "$1" | sed "s/'/'\"'\"'/g"
67
+ printf "'"
68
+ }
69
+
70
+ mkdir -p "${OUTPUT_DIR}"
71
+ timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
72
+ output_file="${OUTPUT_DIR}/shares-${timestamp}.json"
73
+ tmp_file="${output_file}.tmp"
74
+ remote_file_q="$(shell_quote "${REMOTE_FILE}")"
75
+
76
+ cleanup() {
77
+ rm -f "${tmp_file}"
78
+ }
79
+ trap cleanup EXIT
80
+
81
+ remote_cmd=$(
82
+ cat <<EOF
83
+ set -e
84
+ if [ "\$(id -u)" -eq 0 ]; then
85
+ cat ${remote_file_q}
86
+ else
87
+ sudo -n cat ${remote_file_q}
88
+ fi
89
+ EOF
90
+ )
91
+
92
+ SSH_CMD=(ssh)
93
+ if ((${#SSH_OPTS[@]})); then
94
+ SSH_CMD+=("${SSH_OPTS[@]}")
95
+ fi
96
+
97
+ "${SSH_CMD[@]}" "${SSH_TARGET}" "${remote_cmd}" > "${tmp_file}"
98
+
99
+ node -e '
100
+ const { readFileSync } = require("node:fs");
101
+ const file = process.argv[1];
102
+ JSON.parse(readFileSync(file, "utf8"));
103
+ ' "${tmp_file}"
104
+
105
+ mv "${tmp_file}" "${output_file}"
106
+ chmod 0600 "${output_file}"
107
+ trap - EXIT
108
+
109
+ echo "Backed up share data to ${output_file}"
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SSH_TARGET=""
5
+ SSH_OPTS=()
6
+ DOMAIN=""
7
+ API_URL="${SNAPSHOT_SHARE_PUBLIC_API_URL:-${SNAPSHOT_SHARE_API_URL:-}}"
8
+
9
+ usage() {
10
+ cat <<'EOF'
11
+ Usage:
12
+ deploy/aliyun/check-ecs-status.sh --ssh root@1.2.3.4 --domain snapshots.example.com
13
+
14
+ Options:
15
+ --ssh TARGET SSH target, for example root@1.2.3.4.
16
+ --domain DOMAIN Public API domain. Used to check https://DOMAIN/api/snapshots/health.
17
+ --api-url URL Public API URL. Overrides --domain for public health check.
18
+ --identity-file FILE SSH private key for the ECS host.
19
+ --port PORT SSH port.
20
+ -h, --help Show help.
21
+ EOF
22
+ }
23
+
24
+ while [[ $# -gt 0 ]]; do
25
+ case "$1" in
26
+ --ssh)
27
+ SSH_TARGET="${2:-}"
28
+ shift 2
29
+ ;;
30
+ --domain)
31
+ DOMAIN="${2:-}"
32
+ shift 2
33
+ ;;
34
+ --api-url)
35
+ API_URL="${2:-}"
36
+ shift 2
37
+ ;;
38
+ --identity-file)
39
+ SSH_OPTS+=("-i" "${2:-}")
40
+ shift 2
41
+ ;;
42
+ --port)
43
+ SSH_OPTS+=("-p" "${2:-}")
44
+ shift 2
45
+ ;;
46
+ -h|--help)
47
+ usage
48
+ exit 0
49
+ ;;
50
+ *)
51
+ echo "Unknown option: $1" >&2
52
+ usage >&2
53
+ exit 1
54
+ ;;
55
+ esac
56
+ done
57
+
58
+ if [[ -z "${SSH_TARGET}" ]]; then
59
+ echo "Missing --ssh target." >&2
60
+ usage >&2
61
+ exit 1
62
+ fi
63
+
64
+ if [[ -z "${API_URL}" && -n "${DOMAIN}" ]]; then
65
+ API_URL="https://${DOMAIN}"
66
+ fi
67
+
68
+ remote_cmd='
69
+ set -e
70
+ PATH="$PATH:/usr/local/sbin:/usr/sbin:/sbin"
71
+
72
+ run_root() {
73
+ if [ "$(id -u)" -eq 0 ]; then
74
+ "$@"
75
+ else
76
+ sudo -n "$@"
77
+ fi
78
+ }
79
+
80
+ echo "== systemd =="
81
+ systemctl is-enabled codex-snapshot-share.service || true
82
+ systemctl is-active codex-snapshot-share.service || true
83
+ systemctl --no-pager --lines=12 status codex-snapshot-share.service || true
84
+
85
+ echo
86
+ echo "== local health =="
87
+ curl --fail --show-error --silent --max-time 8 http://127.0.0.1:8787/api/snapshots/health
88
+ echo
89
+
90
+ echo
91
+ echo "== install files =="
92
+ run_root test -f /etc/codex-snapshots/share-api.env
93
+ run_root test -f /etc/systemd/system/codex-snapshot-share.service
94
+ run_root test -d /var/lib/codex-snapshots
95
+ run_root test -f /etc/nginx/conf.d/codex-snapshots.conf
96
+ if command -v stat >/dev/null 2>&1; then
97
+ run_root stat -c "env %a %U:%G %n" /etc/codex-snapshots/share-api.env
98
+ run_root stat -c "state %a %U:%G %n" /var/lib/codex-snapshots
99
+ fi
100
+
101
+ echo
102
+ echo "== local bind =="
103
+ if command -v ss >/dev/null 2>&1; then
104
+ ss -ltn | grep ":8787" || {
105
+ echo "share API is not listening on port 8787" >&2
106
+ exit 21
107
+ }
108
+ if ss -ltn | grep ":8787" | grep -Eq "0\\.0\\.0\\.0:8787|\\[::\\]:8787|\\*:8787"; then
109
+ echo "share API port 8787 is exposed beyond localhost" >&2
110
+ exit 22
111
+ fi
112
+ else
113
+ echo "ss command not found; skipped bind-scope check"
114
+ fi
115
+
116
+ echo
117
+ echo "== nginx =="
118
+ run_root nginx -t
119
+ run_root grep -q "proxy_pass http://127.0.0.1:8787" /etc/nginx/conf.d/codex-snapshots.conf
120
+ '
121
+
122
+ SSH_CMD=(ssh)
123
+ if ((${#SSH_OPTS[@]})); then
124
+ SSH_CMD+=("${SSH_OPTS[@]}")
125
+ fi
126
+
127
+ "${SSH_CMD[@]}" "${SSH_TARGET}" "${remote_cmd}"
128
+
129
+ if [[ -n "${API_URL}" ]]; then
130
+ echo
131
+ echo "== public health =="
132
+ HEALTH_PAYLOAD="$(curl --fail --show-error --silent --max-time 12 "${API_URL%/}/api/snapshots/health")"
133
+ printf "%s\n" "${HEALTH_PAYLOAD}"
134
+ if printf "%s" "${HEALTH_PAYLOAD}" | grep -q '"storage"'; then
135
+ echo "public health response exposes the storage path" >&2
136
+ exit 31
137
+ fi
138
+ echo
139
+
140
+ echo "== public CORS =="
141
+ CORS_HEADERS="$(curl --fail --show-error --silent --max-time 12 -D - -o /dev/null \
142
+ -H "Origin: https://ffffhx.github.io" \
143
+ "${API_URL%/}/api/snapshots?limit=1")"
144
+ printf "%s\n" "${CORS_HEADERS}" | grep -qi '^access-control-allow-origin: \*' || {
145
+ echo "public list response is missing access-control-allow-origin: *" >&2
146
+ exit 32
147
+ }
148
+ echo "CORS read check passed."
149
+ fi
@@ -0,0 +1,29 @@
1
+ # Copy to /etc/codex-snapshots/share-api.env on the ECS host.
2
+ # Generate a strong value, for example:
3
+ # openssl rand -base64 32
4
+ SNAPSHOT_SHARE_TOKEN=change-me
5
+
6
+ # Public GitHub Pages site that should render shared sessions.
7
+ SNAPSHOT_SHARE_SITE_URL=https://ffffhx.github.io/codex-snapshots/
8
+
9
+ # Public HTTPS API origin served by Nginx on the ECS host.
10
+ SNAPSHOT_SHARE_PUBLIC_API_URL=https://snapshots.example.com
11
+
12
+ # The public site path for the static share viewer.
13
+ SNAPSHOT_SHARE_VIEWER_PATH=/share/
14
+
15
+ # Persistent storage on the ECS host. Back this file up.
16
+ SNAPSHOT_SHARE_DATA_FILE=/var/lib/codex-snapshots/shares.json
17
+
18
+ # Keep writes/deletes authenticated. Public reads remain available.
19
+ SNAPSHOT_SHARE_ALLOW_ANONYMOUS=false
20
+
21
+ # Optional GitHub OAuth publish/delete auth.
22
+ # GitHub OAuth App callback URL:
23
+ # https://snapshots.example.com/api/auth/github/callback
24
+ # SNAPSHOT_GITHUB_CLIENT_ID=change-me
25
+ # SNAPSHOT_GITHUB_CLIENT_SECRET=change-me
26
+ # SNAPSHOT_SESSION_SECRET=change-me-generate-with-openssl-rand-base64-48
27
+ # SNAPSHOT_GITHUB_OWNER_LOGIN=your-github-login
28
+ # SNAPSHOT_GITHUB_OWNER_ID=
29
+ # SNAPSHOT_AUTH_ALLOWED_ORIGINS=https://ffffhx.github.io
@@ -0,0 +1,26 @@
1
+ [Unit]
2
+ Description=Codex Snapshots Share API
3
+ Documentation=https://github.com/ffffhx/codex-snapshots
4
+ After=network-online.target
5
+ Wants=network-online.target
6
+
7
+ [Service]
8
+ Type=simple
9
+ User=codexsnap
10
+ Group=codexsnap
11
+ WorkingDirectory=/opt/codex-snapshots
12
+ Environment=NODE_ENV=production
13
+ Environment=HOST=127.0.0.1
14
+ Environment=PORT=8787
15
+ EnvironmentFile=/etc/codex-snapshots/share-api.env
16
+ ExecStart=/usr/bin/env node /opt/codex-snapshots/server/share-api.mjs
17
+ Restart=always
18
+ RestartSec=3
19
+ NoNewPrivileges=true
20
+ PrivateTmp=true
21
+ ProtectHome=true
22
+ ProtectSystem=strict
23
+ ReadWritePaths=/var/lib/codex-snapshots
24
+
25
+ [Install]
26
+ WantedBy=multi-user.target