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.
- package/README.md +101 -6
- package/bin/codex-snapshot.mjs +1 -6326
- package/deploy/aliyun/README.md +311 -0
- package/deploy/aliyun/backup-share-data.sh +109 -0
- package/deploy/aliyun/check-ecs-status.sh +149 -0
- package/deploy/aliyun/codex-snapshot-share.env.example +29 -0
- package/deploy/aliyun/codex-snapshot-share.service +26 -0
- package/deploy/aliyun/configure-github-pages-api.sh +141 -0
- package/deploy/aliyun/configure-local-publisher.sh +197 -0
- package/deploy/aliyun/deploy-to-ecs.sh +669 -0
- package/deploy/aliyun/deploy.env.example +52 -0
- package/deploy/aliyun/doctor.mjs +398 -0
- package/deploy/aliyun/install-share-api.sh +252 -0
- package/deploy/aliyun/install-system-deps.sh +84 -0
- package/deploy/aliyun/nginx-codex-snapshots.bootstrap.conf +34 -0
- package/deploy/aliyun/nginx-codex-snapshots.conf +52 -0
- package/deploy/aliyun/preflight.mjs +321 -0
- package/deploy/aliyun/restore-share-data.sh +141 -0
- package/deploy/aliyun/verify-public-share.mjs +404 -0
- package/dist/cli/codex-snapshot.mjs +2654 -0
- package/dist/core/privacy.js +81 -0
- package/dist/core/snapshot.js +1 -0
- package/dist/renderers/markdown.mjs +81 -0
- package/dist/renderers/transcript.js +195 -0
- package/dist/server/http.js +10 -0
- package/dist/server/local-security.js +66 -0
- package/dist/server/local-viewer-app.mjs +1670 -0
- package/dist/server/local-viewer.mjs +210 -0
- package/dist/server/share-api.mjs +1149 -0
- package/dist/server/share-store.js +136 -0
- package/dist/shared/sanitize.js +126 -0
- package/dist/shared/transcript.js +1 -0
- package/dist/sources/index.mjs +2 -0
- package/dist/sources/local-history.mjs +2221 -0
- package/package.json +42 -14
- package/scripts/build-site.mjs +71 -0
- package/scripts/launch-agent.mjs +19 -227
- package/scripts/serve-site.mjs +2 -2
- package/scripts/test-aliyun-deploy-config.sh +230 -0
- package/scripts/test-share-api.mjs +967 -0
- package/scripts/test-site-config.mjs +100 -0
- package/scripts/test-static-site.mjs +403 -0
- package/scripts/write-site-config.mjs +161 -0
- package/server/share-api.mjs +1 -771
- package/site/assets/config.js +3 -0
- package/site/assets/share.js +43 -106
- package/site/assets/site.css +3 -605
- package/site/assets/site.js +15 -92
- package/site/favicon.svg +7 -0
- package/site/index.html +3 -83
- package/site/share/index.html +3 -8
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
CONFIG_FILE="${CODEX_SNAPSHOTS_ALIYUN_CONFIG:-}"
|
|
5
|
+
|
|
6
|
+
for ((index = 1; index <= $#; index += 1)); do
|
|
7
|
+
if [[ "${!index}" == "--config" ]]; then
|
|
8
|
+
next_index=$((index + 1))
|
|
9
|
+
CONFIG_FILE="${!next_index:-}"
|
|
10
|
+
break
|
|
11
|
+
fi
|
|
12
|
+
done
|
|
13
|
+
|
|
14
|
+
if [[ -n "${CONFIG_FILE}" ]]; then
|
|
15
|
+
if [[ ! -f "${CONFIG_FILE}" ]]; then
|
|
16
|
+
echo "Config file not found: ${CONFIG_FILE}" >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
# shellcheck source=/dev/null
|
|
20
|
+
source "${CONFIG_FILE}"
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
SSH_TARGET="${SSH_TARGET:-${ALIYUN_SSH_TARGET:-}}"
|
|
24
|
+
DOMAIN="${DOMAIN:-${ALIYUN_DOMAIN:-}}"
|
|
25
|
+
SITE_URL="${SITE_URL:-${SNAPSHOT_SHARE_SITE_URL:-https://ffffhx.github.io/codex-snapshots/}}"
|
|
26
|
+
API_URL="${API_URL:-${SNAPSHOT_SHARE_PUBLIC_API_URL:-}}"
|
|
27
|
+
TOKEN="${TOKEN:-${SNAPSHOT_SHARE_TOKEN:-}}"
|
|
28
|
+
GITHUB_CLIENT_ID="${SNAPSHOT_GITHUB_CLIENT_ID:-${GITHUB_CLIENT_ID:-}}"
|
|
29
|
+
GITHUB_CLIENT_SECRET="${SNAPSHOT_GITHUB_CLIENT_SECRET:-${GITHUB_CLIENT_SECRET:-}}"
|
|
30
|
+
GITHUB_OWNER_LOGIN="${SNAPSHOT_GITHUB_OWNER_LOGIN:-${SNAPSHOT_GITHUB_OWNER:-}}"
|
|
31
|
+
GITHUB_OWNER_ID="${SNAPSHOT_GITHUB_OWNER_ID:-}"
|
|
32
|
+
SESSION_SECRET="${SNAPSHOT_SESSION_SECRET:-}"
|
|
33
|
+
AUTH_ALLOWED_ORIGINS="${SNAPSHOT_AUTH_ALLOWED_ORIGINS:-}"
|
|
34
|
+
REMOTE_DIR="${REMOTE_DIR:-/tmp/codex-snapshots-deploy}"
|
|
35
|
+
SSH_IDENTITY_FILE="${SSH_IDENTITY_FILE:-${ALIYUN_SSH_IDENTITY_FILE:-}}"
|
|
36
|
+
SSH_PORT="${SSH_PORT:-${ALIYUN_SSH_PORT:-}}"
|
|
37
|
+
SHARE_PORT="${SHARE_PORT:-${SNAPSHOT_SHARE_PORT:-8787}}"
|
|
38
|
+
PROXY_MODE="${PROXY_MODE:-${SNAPSHOT_SHARE_PROXY_MODE:-auto}}"
|
|
39
|
+
PUBLIC_PATH="${PUBLIC_PATH:-${SNAPSHOT_SHARE_PUBLIC_PATH:-}}"
|
|
40
|
+
GENERATE_TOKEN="${GENERATE_TOKEN:-0}"
|
|
41
|
+
ISSUE_CERT="${ISSUE_CERT:-0}"
|
|
42
|
+
CERTBOT_EMAIL="${CERTBOT_EMAIL:-}"
|
|
43
|
+
RUN_VERIFY="${RUN_VERIFY:-1}"
|
|
44
|
+
RUN_PREFLIGHT="${RUN_PREFLIGHT:-1}"
|
|
45
|
+
INSTALL_DEPS="${INSTALL_DEPS:-0}"
|
|
46
|
+
DRY_RUN="${DRY_RUN:-0}"
|
|
47
|
+
CONFIGURE_PAGES="${CONFIGURE_PAGES:-0}"
|
|
48
|
+
PAGES_REPO="${PAGES_REPO:-${GITHUB_REPOSITORY:-ffffhx/codex-snapshots}}"
|
|
49
|
+
PAGES_WORKFLOW="${PAGES_WORKFLOW:-pages.yml}"
|
|
50
|
+
WAIT_PAGES="${WAIT_PAGES:-0}"
|
|
51
|
+
CONFIGURE_LOCAL="${CONFIGURE_LOCAL:-0}"
|
|
52
|
+
REINSTALL_DAEMON="${REINSTALL_DAEMON:-0}"
|
|
53
|
+
|
|
54
|
+
usage() {
|
|
55
|
+
cat <<'EOF'
|
|
56
|
+
Usage:
|
|
57
|
+
deploy/aliyun/deploy-to-ecs.sh \
|
|
58
|
+
--ssh root@1.2.3.4 \
|
|
59
|
+
--domain snapshots.example.com \
|
|
60
|
+
--configure-local \
|
|
61
|
+
[--issue-cert --email you@example.com]
|
|
62
|
+
|
|
63
|
+
Options:
|
|
64
|
+
--ssh TARGET SSH target, for example root@1.2.3.4.
|
|
65
|
+
--config FILE Source deployment variables from a local env file.
|
|
66
|
+
--domain DOMAIN Public API domain pointing to the ECS public IP.
|
|
67
|
+
--token TOKEN Optional legacy publish token. Defaults to SNAPSHOT_SHARE_TOKEN or
|
|
68
|
+
~/.codex-snapshots-agent.json when present.
|
|
69
|
+
--generate-token Generate a strong legacy publish token for this run.
|
|
70
|
+
GitHub OAuth vars Set SNAPSHOT_GITHUB_CLIENT_ID, SNAPSHOT_GITHUB_CLIENT_SECRET,
|
|
71
|
+
SNAPSHOT_SESSION_SECRET, and SNAPSHOT_GITHUB_OWNER_LOGIN/ID
|
|
72
|
+
in the config file to require GitHub login for publish/delete.
|
|
73
|
+
--site-url URL Public static site URL. Defaults to https://ffffhx.github.io/codex-snapshots/.
|
|
74
|
+
--api-url URL Public API URL. Defaults to https://<domain>.
|
|
75
|
+
--remote-dir DIR Temporary remote deployment directory. Defaults to /tmp/codex-snapshots-deploy.
|
|
76
|
+
--identity-file FILE SSH private key for the ECS host.
|
|
77
|
+
--port PORT SSH port. Defaults to the ssh client default.
|
|
78
|
+
--service-port PORT Local share API port on ECS. Defaults to 8787.
|
|
79
|
+
--proxy-mode MODE Reverse proxy mode: auto, nginx, caddy, or none. Defaults to auto.
|
|
80
|
+
--public-path PATH Public path prefix for Caddy path proxy, for example /codex-snapshots.
|
|
81
|
+
--issue-cert Run certbot on the ECS host, then reinstall HTTPS Nginx config.
|
|
82
|
+
--email EMAIL Certbot email. Required with --issue-cert for non-interactive certbot.
|
|
83
|
+
--install-deps Install Node.js 20, Nginx, Certbot, Git, OpenSSL, and rsync on ECS first.
|
|
84
|
+
--dry-run Print the resolved deployment plan without connecting to ECS.
|
|
85
|
+
--no-preflight Skip DNS, SSH, and remote dependency checks before deploying.
|
|
86
|
+
--no-verify Skip final ECS API verification.
|
|
87
|
+
--configure-pages Set the GitHub Pages API variable and trigger the Pages workflow.
|
|
88
|
+
--repo OWNER/REPO GitHub repository for --configure-pages. Defaults to ffffhx/codex-snapshots.
|
|
89
|
+
--workflow FILE GitHub Pages workflow for --configure-pages. Defaults to pages.yml.
|
|
90
|
+
--wait-pages With --configure-pages, wait for Pages and run full public verification.
|
|
91
|
+
--configure-local Write the local viewer API/site config after deploy.
|
|
92
|
+
--reinstall-daemon With --configure-local, reinstall the macOS LaunchAgent.
|
|
93
|
+
-h, --help Show help.
|
|
94
|
+
EOF
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
while [[ $# -gt 0 ]]; do
|
|
98
|
+
case "$1" in
|
|
99
|
+
--ssh)
|
|
100
|
+
SSH_TARGET="${2:-}"
|
|
101
|
+
shift 2
|
|
102
|
+
;;
|
|
103
|
+
--config)
|
|
104
|
+
shift 2
|
|
105
|
+
;;
|
|
106
|
+
--domain)
|
|
107
|
+
DOMAIN="${2:-}"
|
|
108
|
+
shift 2
|
|
109
|
+
;;
|
|
110
|
+
--token)
|
|
111
|
+
TOKEN="${2:-}"
|
|
112
|
+
shift 2
|
|
113
|
+
;;
|
|
114
|
+
--generate-token)
|
|
115
|
+
GENERATE_TOKEN=1
|
|
116
|
+
shift
|
|
117
|
+
;;
|
|
118
|
+
--site-url)
|
|
119
|
+
SITE_URL="${2:-}"
|
|
120
|
+
shift 2
|
|
121
|
+
;;
|
|
122
|
+
--api-url)
|
|
123
|
+
API_URL="${2:-}"
|
|
124
|
+
shift 2
|
|
125
|
+
;;
|
|
126
|
+
--remote-dir)
|
|
127
|
+
REMOTE_DIR="${2:-}"
|
|
128
|
+
shift 2
|
|
129
|
+
;;
|
|
130
|
+
--identity-file)
|
|
131
|
+
SSH_IDENTITY_FILE="${2:-}"
|
|
132
|
+
shift 2
|
|
133
|
+
;;
|
|
134
|
+
--port)
|
|
135
|
+
SSH_PORT="${2:-}"
|
|
136
|
+
shift 2
|
|
137
|
+
;;
|
|
138
|
+
--service-port)
|
|
139
|
+
SHARE_PORT="${2:-}"
|
|
140
|
+
shift 2
|
|
141
|
+
;;
|
|
142
|
+
--proxy-mode)
|
|
143
|
+
PROXY_MODE="${2:-}"
|
|
144
|
+
shift 2
|
|
145
|
+
;;
|
|
146
|
+
--public-path)
|
|
147
|
+
PUBLIC_PATH="${2:-}"
|
|
148
|
+
shift 2
|
|
149
|
+
;;
|
|
150
|
+
--issue-cert)
|
|
151
|
+
ISSUE_CERT=1
|
|
152
|
+
shift
|
|
153
|
+
;;
|
|
154
|
+
--email)
|
|
155
|
+
CERTBOT_EMAIL="${2:-}"
|
|
156
|
+
shift 2
|
|
157
|
+
;;
|
|
158
|
+
--install-deps)
|
|
159
|
+
INSTALL_DEPS=1
|
|
160
|
+
shift
|
|
161
|
+
;;
|
|
162
|
+
--dry-run)
|
|
163
|
+
DRY_RUN=1
|
|
164
|
+
shift
|
|
165
|
+
;;
|
|
166
|
+
--no-preflight)
|
|
167
|
+
RUN_PREFLIGHT=0
|
|
168
|
+
shift
|
|
169
|
+
;;
|
|
170
|
+
--no-verify)
|
|
171
|
+
RUN_VERIFY=0
|
|
172
|
+
shift
|
|
173
|
+
;;
|
|
174
|
+
--configure-pages)
|
|
175
|
+
CONFIGURE_PAGES=1
|
|
176
|
+
shift
|
|
177
|
+
;;
|
|
178
|
+
--repo)
|
|
179
|
+
PAGES_REPO="${2:-}"
|
|
180
|
+
shift 2
|
|
181
|
+
;;
|
|
182
|
+
--workflow)
|
|
183
|
+
PAGES_WORKFLOW="${2:-}"
|
|
184
|
+
shift 2
|
|
185
|
+
;;
|
|
186
|
+
--wait-pages)
|
|
187
|
+
CONFIGURE_PAGES=1
|
|
188
|
+
WAIT_PAGES=1
|
|
189
|
+
shift
|
|
190
|
+
;;
|
|
191
|
+
--configure-local)
|
|
192
|
+
CONFIGURE_LOCAL=1
|
|
193
|
+
shift
|
|
194
|
+
;;
|
|
195
|
+
--reinstall-daemon)
|
|
196
|
+
CONFIGURE_LOCAL=1
|
|
197
|
+
REINSTALL_DAEMON=1
|
|
198
|
+
shift
|
|
199
|
+
;;
|
|
200
|
+
-h|--help)
|
|
201
|
+
usage
|
|
202
|
+
exit 0
|
|
203
|
+
;;
|
|
204
|
+
*)
|
|
205
|
+
echo "Unknown option: $1" >&2
|
|
206
|
+
usage >&2
|
|
207
|
+
exit 1
|
|
208
|
+
;;
|
|
209
|
+
esac
|
|
210
|
+
done
|
|
211
|
+
|
|
212
|
+
if [[ -z "${SSH_TARGET}" ]]; then
|
|
213
|
+
echo "Missing --ssh target." >&2
|
|
214
|
+
usage >&2
|
|
215
|
+
exit 1
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
if [[ -z "${DOMAIN}" ]]; then
|
|
219
|
+
echo "Missing --domain." >&2
|
|
220
|
+
usage >&2
|
|
221
|
+
exit 1
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
if [[ "${ISSUE_CERT}" -eq 1 && -z "${CERTBOT_EMAIL}" ]]; then
|
|
225
|
+
echo "--issue-cert requires --email for non-interactive certbot." >&2
|
|
226
|
+
exit 1
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
if [[ -z "${API_URL}" ]]; then
|
|
230
|
+
API_URL="https://${DOMAIN}"
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
if [[ -z "${PUBLIC_PATH}" ]]; then
|
|
234
|
+
PUBLIC_PATH="$(node -e 'const url = new URL(process.argv[1]); process.stdout.write(url.pathname === "/" ? "" : url.pathname.replace(/\/+$/, ""));' "${API_URL}")"
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
if [[ -n "${PUBLIC_PATH}" && "${PUBLIC_PATH}" != /* ]]; then
|
|
238
|
+
PUBLIC_PATH="/${PUBLIC_PATH}"
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
lowercase() {
|
|
242
|
+
printf "%s" "$1" | tr '[:upper:]' '[:lower:]'
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
is_placeholder_domain() {
|
|
246
|
+
local value
|
|
247
|
+
value="$(lowercase "$1")"
|
|
248
|
+
[[ "${value}" == "example.com" || "${value}" == *.example.com || "${value}" == "snapshots.example.com" ]]
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
url_host() {
|
|
252
|
+
local value="${1#http://}"
|
|
253
|
+
value="${value#https://}"
|
|
254
|
+
value="${value%%/*}"
|
|
255
|
+
if [[ "${value}" == \[*\]* ]]; then
|
|
256
|
+
value="${value#\[}"
|
|
257
|
+
value="${value%%\]*}"
|
|
258
|
+
else
|
|
259
|
+
value="${value%%:*}"
|
|
260
|
+
fi
|
|
261
|
+
lowercase "${value}"
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
is_http_url() {
|
|
265
|
+
[[ "$1" == http://* || "$1" == https://* ]]
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
is_ipv4_address() {
|
|
269
|
+
[[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
is_enabled() {
|
|
273
|
+
local value
|
|
274
|
+
value="$(lowercase "$1")"
|
|
275
|
+
[[ "${value}" == "1" || "${value}" == "true" || "${value}" == "yes" || "${value}" == "on" ]]
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
is_auto_token() {
|
|
279
|
+
local value
|
|
280
|
+
value="$(lowercase "$1")"
|
|
281
|
+
[[ "${value}" == "auto" || "${value}" == "generate" || "${value}" == "generated" ]]
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
generate_publish_token() {
|
|
285
|
+
node -e 'const { randomBytes } = require("node:crypto"); process.stdout.write(randomBytes(32).toString("base64url"));'
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
read_local_publish_token() {
|
|
289
|
+
node <<'NODE'
|
|
290
|
+
const fs = require("node:fs");
|
|
291
|
+
const os = require("node:os");
|
|
292
|
+
const path = require("node:path");
|
|
293
|
+
|
|
294
|
+
const candidates = [
|
|
295
|
+
process.env.CODEX_SNAPSHOTS_AGENT_FILE,
|
|
296
|
+
process.env.SNAPSHOT_SHARE_TOKEN_FILE,
|
|
297
|
+
path.join(os.homedir(), ".codex-snapshots-agent.json"),
|
|
298
|
+
].filter(Boolean);
|
|
299
|
+
|
|
300
|
+
for (const filePath of candidates) {
|
|
301
|
+
try {
|
|
302
|
+
const payload = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
303
|
+
const token = [
|
|
304
|
+
payload.snapshotShareToken,
|
|
305
|
+
payload.agentToken,
|
|
306
|
+
payload.token,
|
|
307
|
+
payload.uploadToken,
|
|
308
|
+
].find((value) => typeof value === "string" && value.trim());
|
|
309
|
+
if (token) {
|
|
310
|
+
process.stdout.write(token.trim());
|
|
311
|
+
process.exit(0);
|
|
312
|
+
}
|
|
313
|
+
} catch {}
|
|
314
|
+
}
|
|
315
|
+
NODE
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
TOKEN_GENERATED=0
|
|
319
|
+
if [[ -z "${TOKEN}" ]]; then
|
|
320
|
+
TOKEN="$(read_local_publish_token)"
|
|
321
|
+
fi
|
|
322
|
+
|
|
323
|
+
if is_enabled "${GENERATE_TOKEN}" || is_auto_token "${TOKEN}"; then
|
|
324
|
+
TOKEN="$(generate_publish_token)"
|
|
325
|
+
TOKEN_GENERATED=1
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
GITHUB_AUTH_ENABLED=0
|
|
329
|
+
if [[ -n "${GITHUB_CLIENT_ID}${GITHUB_CLIENT_SECRET}${GITHUB_OWNER_LOGIN}${GITHUB_OWNER_ID}" ]]; then
|
|
330
|
+
GITHUB_AUTH_ENABLED=1
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
if [[ "${GITHUB_AUTH_ENABLED}" -eq 1 && "${TOKEN}" == "change-me" ]]; then
|
|
334
|
+
TOKEN=""
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
if [[ "${GITHUB_AUTH_ENABLED}" -eq 1 && -z "${SESSION_SECRET}" ]]; then
|
|
338
|
+
SESSION_SECRET="$(node -e 'const { randomBytes } = require("node:crypto"); process.stdout.write(randomBytes(48).toString("base64url"));')"
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
if [[ -z "${TOKEN}" && "${GITHUB_AUTH_ENABLED}" -ne 1 ]]; then
|
|
342
|
+
echo "Missing --token, SNAPSHOT_SHARE_TOKEN, ~/.codex-snapshots-agent.json token, TOKEN=auto, or --generate-token." >&2
|
|
343
|
+
usage >&2
|
|
344
|
+
exit 1
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
validate_deploy_inputs() {
|
|
348
|
+
local errors=()
|
|
349
|
+
local ssh_host="${SSH_TARGET##*@}"
|
|
350
|
+
ssh_host="${ssh_host#\[}"
|
|
351
|
+
ssh_host="${ssh_host%%]*}"
|
|
352
|
+
ssh_host="${ssh_host%%:*}"
|
|
353
|
+
local api_host
|
|
354
|
+
api_host="$(url_host "${API_URL}")"
|
|
355
|
+
local site_host
|
|
356
|
+
site_host="$(url_host "${SITE_URL}")"
|
|
357
|
+
|
|
358
|
+
if [[ "${SSH_TARGET}" == "root@1.2.3.4" || "${ssh_host}" == "1.2.3.4" ]]; then
|
|
359
|
+
errors+=("SSH target still uses the placeholder root@1.2.3.4.")
|
|
360
|
+
fi
|
|
361
|
+
if is_placeholder_domain "${DOMAIN}"; then
|
|
362
|
+
errors+=("DOMAIN still uses the placeholder snapshots.example.com.")
|
|
363
|
+
fi
|
|
364
|
+
if is_placeholder_domain "${api_host}"; then
|
|
365
|
+
errors+=("API_URL still uses the placeholder https://snapshots.example.com.")
|
|
366
|
+
fi
|
|
367
|
+
if [[ "${api_host}" == "127.0.0.1" || "${api_host}" == "localhost" || "${api_host}" == "::1" ]]; then
|
|
368
|
+
errors+=("API_URL must be a public URL, not ${API_URL}.")
|
|
369
|
+
fi
|
|
370
|
+
if [[ "${site_host}" == "127.0.0.1" || "${site_host}" == "localhost" || "${site_host}" == "::1" ]]; then
|
|
371
|
+
errors+=("SITE_URL must be the public site URL, not ${SITE_URL}.")
|
|
372
|
+
fi
|
|
373
|
+
if ! is_http_url "${API_URL}"; then
|
|
374
|
+
errors+=("API_URL must start with http:// or https://.")
|
|
375
|
+
fi
|
|
376
|
+
if ! is_http_url "${SITE_URL}"; then
|
|
377
|
+
errors+=("SITE_URL must start with http:// or https://.")
|
|
378
|
+
fi
|
|
379
|
+
if [[ -n "${TOKEN}" ]]; then
|
|
380
|
+
if [[ "${TOKEN}" == "change-me" ]]; then
|
|
381
|
+
errors+=("TOKEN still uses the placeholder change-me.")
|
|
382
|
+
elif [[ "${#TOKEN}" -lt 16 ]]; then
|
|
383
|
+
errors+=("TOKEN should be at least 16 characters.")
|
|
384
|
+
fi
|
|
385
|
+
elif [[ "${GITHUB_AUTH_ENABLED}" -ne 1 ]]; then
|
|
386
|
+
errors+=("TOKEN is required unless GitHub OAuth is configured.")
|
|
387
|
+
fi
|
|
388
|
+
if [[ "${GITHUB_AUTH_ENABLED}" -eq 1 ]]; then
|
|
389
|
+
if [[ -z "${GITHUB_CLIENT_ID}" || -z "${GITHUB_CLIENT_SECRET}" ]]; then
|
|
390
|
+
errors+=("GitHub OAuth needs SNAPSHOT_GITHUB_CLIENT_ID and SNAPSHOT_GITHUB_CLIENT_SECRET.")
|
|
391
|
+
fi
|
|
392
|
+
if [[ -z "${GITHUB_OWNER_LOGIN}${GITHUB_OWNER_ID}" ]]; then
|
|
393
|
+
errors+=("GitHub OAuth needs SNAPSHOT_GITHUB_OWNER_LOGIN or SNAPSHOT_GITHUB_OWNER_ID.")
|
|
394
|
+
fi
|
|
395
|
+
if [[ "${SESSION_SECRET}" == "change-me" || "${#SESSION_SECRET}" -lt 32 ]]; then
|
|
396
|
+
errors+=("SNAPSHOT_SESSION_SECRET should be a real secret with at least 32 characters.")
|
|
397
|
+
fi
|
|
398
|
+
fi
|
|
399
|
+
if [[ "${ISSUE_CERT}" -eq 1 ]]; then
|
|
400
|
+
if [[ "${CERTBOT_EMAIL}" == "you@example.com" || "${CERTBOT_EMAIL}" != *@* ]]; then
|
|
401
|
+
errors+=("CERTBOT_EMAIL must be a real email when ISSUE_CERT=1.")
|
|
402
|
+
fi
|
|
403
|
+
if is_ipv4_address "${DOMAIN}"; then
|
|
404
|
+
errors+=("DOMAIN must be a DNS name, not an IP address, when ISSUE_CERT=1.")
|
|
405
|
+
fi
|
|
406
|
+
fi
|
|
407
|
+
if [[ ! "${PROXY_MODE}" =~ ^(auto|nginx|caddy|none)$ ]]; then
|
|
408
|
+
errors+=("PROXY_MODE must be auto, nginx, caddy, or none.")
|
|
409
|
+
fi
|
|
410
|
+
if [[ ! "${SHARE_PORT}" =~ ^[0-9]+$ || "${SHARE_PORT}" -le 0 || "${SHARE_PORT}" -gt 65535 ]]; then
|
|
411
|
+
errors+=("SHARE_PORT must be a valid TCP port.")
|
|
412
|
+
fi
|
|
413
|
+
if [[ "${PROXY_MODE}" == "caddy" && -z "${PUBLIC_PATH}" ]]; then
|
|
414
|
+
errors+=("PUBLIC_PATH is required when PROXY_MODE=caddy.")
|
|
415
|
+
fi
|
|
416
|
+
|
|
417
|
+
if [[ "${#errors[@]}" -gt 0 ]]; then
|
|
418
|
+
echo "Deployment config needs real values before continuing:" >&2
|
|
419
|
+
printf " - %s\n" "${errors[@]}" >&2
|
|
420
|
+
echo "Edit deploy/aliyun/deploy.env or pass the corresponding CLI flags." >&2
|
|
421
|
+
exit 1
|
|
422
|
+
fi
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
validate_deploy_inputs
|
|
426
|
+
|
|
427
|
+
shell_quote() {
|
|
428
|
+
printf "'"
|
|
429
|
+
printf "%s" "$1" | sed "s/'/'\"'\"'/g"
|
|
430
|
+
printf "'"
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
434
|
+
RSYNC_SSH=(ssh)
|
|
435
|
+
if [[ -n "${SSH_IDENTITY_FILE}" ]]; then
|
|
436
|
+
RSYNC_SSH+=("-i" "${SSH_IDENTITY_FILE}")
|
|
437
|
+
fi
|
|
438
|
+
if [[ -n "${SSH_PORT}" ]]; then
|
|
439
|
+
RSYNC_SSH+=("-p" "${SSH_PORT}")
|
|
440
|
+
fi
|
|
441
|
+
REMOTE_DIR_Q="$(shell_quote "${REMOTE_DIR}")"
|
|
442
|
+
DOMAIN_Q="$(shell_quote "${DOMAIN}")"
|
|
443
|
+
TOKEN_Q="$(shell_quote "${TOKEN}")"
|
|
444
|
+
GITHUB_CLIENT_ID_Q="$(shell_quote "${GITHUB_CLIENT_ID}")"
|
|
445
|
+
GITHUB_CLIENT_SECRET_Q="$(shell_quote "${GITHUB_CLIENT_SECRET}")"
|
|
446
|
+
GITHUB_OWNER_LOGIN_Q="$(shell_quote "${GITHUB_OWNER_LOGIN}")"
|
|
447
|
+
GITHUB_OWNER_ID_Q="$(shell_quote "${GITHUB_OWNER_ID}")"
|
|
448
|
+
SESSION_SECRET_Q="$(shell_quote "${SESSION_SECRET}")"
|
|
449
|
+
AUTH_ALLOWED_ORIGINS_Q="$(shell_quote "${AUTH_ALLOWED_ORIGINS}")"
|
|
450
|
+
SITE_URL_Q="$(shell_quote "${SITE_URL}")"
|
|
451
|
+
API_URL_Q="$(shell_quote "${API_URL}")"
|
|
452
|
+
CERTBOT_EMAIL_Q="$(shell_quote "${CERTBOT_EMAIL}")"
|
|
453
|
+
SHARE_PORT_Q="$(shell_quote "${SHARE_PORT}")"
|
|
454
|
+
PROXY_MODE_Q="$(shell_quote "${PROXY_MODE}")"
|
|
455
|
+
PUBLIC_PATH_Q="$(shell_quote "${PUBLIC_PATH}")"
|
|
456
|
+
|
|
457
|
+
echo "Deploying Codex Snapshots share API to ${SSH_TARGET}"
|
|
458
|
+
echo "Domain: ${DOMAIN}"
|
|
459
|
+
echo "API URL: ${API_URL}"
|
|
460
|
+
echo "Site URL: ${SITE_URL}"
|
|
461
|
+
if [[ "${TOKEN_GENERATED}" -eq 1 ]]; then
|
|
462
|
+
echo "Publish token: generated for this deployment"
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
if [[ "${DRY_RUN}" -eq 1 ]]; then
|
|
466
|
+
cat <<EOF
|
|
467
|
+
|
|
468
|
+
Resolved deployment plan:
|
|
469
|
+
SSH target: ${SSH_TARGET}
|
|
470
|
+
SSH identity file: ${SSH_IDENTITY_FILE:-"(default)"}
|
|
471
|
+
SSH port: ${SSH_PORT:-"(default)"}
|
|
472
|
+
Domain: ${DOMAIN}
|
|
473
|
+
API URL: ${API_URL}
|
|
474
|
+
Site URL: ${SITE_URL}
|
|
475
|
+
Remote dir: ${REMOTE_DIR}
|
|
476
|
+
Service port: ${SHARE_PORT}
|
|
477
|
+
Proxy mode: ${PROXY_MODE}
|
|
478
|
+
Public path: ${PUBLIC_PATH:-"(none)"}
|
|
479
|
+
Token: $(if [[ -z "${TOKEN}" ]]; then printf "not configured (GitHub OAuth mode)"; elif [[ "${TOKEN_GENERATED}" -eq 1 ]]; then printf "generated (%s chars)" "${#TOKEN}"; else printf "set (%s chars)" "${#TOKEN}"; fi)
|
|
480
|
+
GitHub OAuth: $([[ -n "${GITHUB_CLIENT_ID}${GITHUB_CLIENT_SECRET}" ]] && printf "configured" || printf "not configured")
|
|
481
|
+
GitHub site owner: ${GITHUB_OWNER_LOGIN:-${GITHUB_OWNER_ID:-"(unset)"}}
|
|
482
|
+
Install deps: ${INSTALL_DEPS}
|
|
483
|
+
Preflight: ${RUN_PREFLIGHT}
|
|
484
|
+
Issue cert: ${ISSUE_CERT}
|
|
485
|
+
Certbot email: ${CERTBOT_EMAIL:-"(unset)"}
|
|
486
|
+
Verify ECS API: ${RUN_VERIFY}
|
|
487
|
+
Configure Pages: ${CONFIGURE_PAGES}
|
|
488
|
+
Wait Pages: ${WAIT_PAGES}
|
|
489
|
+
Pages repo: ${PAGES_REPO}
|
|
490
|
+
Pages workflow: ${PAGES_WORKFLOW}
|
|
491
|
+
Configure local publisher: ${CONFIGURE_LOCAL}
|
|
492
|
+
Reinstall local daemon: ${REINSTALL_DAEMON}
|
|
493
|
+
EOF
|
|
494
|
+
exit 0
|
|
495
|
+
fi
|
|
496
|
+
|
|
497
|
+
if [[ "${INSTALL_DEPS}" -eq 1 ]]; then
|
|
498
|
+
remote_install_deps_cmd=$(
|
|
499
|
+
cat <<EOF
|
|
500
|
+
set -e
|
|
501
|
+
tmp_script="\$(mktemp /tmp/codex-snapshots-install-deps.XXXXXX.sh)"
|
|
502
|
+
cat > "\${tmp_script}"
|
|
503
|
+
chmod +x "\${tmp_script}"
|
|
504
|
+
if [ "\$(id -u)" -eq 0 ]; then
|
|
505
|
+
SNAPSHOT_SHARE_PROXY_MODE=${PROXY_MODE_Q} "\${tmp_script}"
|
|
506
|
+
else
|
|
507
|
+
sudo -n env SNAPSHOT_SHARE_PROXY_MODE=${PROXY_MODE_Q} "\${tmp_script}"
|
|
508
|
+
fi
|
|
509
|
+
rm -f "\${tmp_script}"
|
|
510
|
+
EOF
|
|
511
|
+
)
|
|
512
|
+
"${RSYNC_SSH[@]}" "${SSH_TARGET}" "${remote_install_deps_cmd}" < "${REPO_ROOT}/deploy/aliyun/install-system-deps.sh"
|
|
513
|
+
fi
|
|
514
|
+
|
|
515
|
+
if [[ "${RUN_PREFLIGHT}" -eq 1 ]]; then
|
|
516
|
+
preflight_args=(--domain "${DOMAIN}" --ssh "${SSH_TARGET}")
|
|
517
|
+
if [[ -n "${SSH_IDENTITY_FILE}" ]]; then
|
|
518
|
+
preflight_args+=(--identity-file "${SSH_IDENTITY_FILE}")
|
|
519
|
+
fi
|
|
520
|
+
if [[ -n "${SSH_PORT}" ]]; then
|
|
521
|
+
preflight_args+=(--port "${SSH_PORT}")
|
|
522
|
+
fi
|
|
523
|
+
if [[ "${ISSUE_CERT}" -eq 1 ]]; then
|
|
524
|
+
preflight_args+=(--require-certbot-nginx)
|
|
525
|
+
fi
|
|
526
|
+
if [[ "${PROXY_MODE}" == "caddy" ]]; then
|
|
527
|
+
preflight_args+=(--proxy-mode caddy)
|
|
528
|
+
fi
|
|
529
|
+
node "${REPO_ROOT}/deploy/aliyun/preflight.mjs" "${preflight_args[@]}"
|
|
530
|
+
fi
|
|
531
|
+
|
|
532
|
+
if ! command -v pnpm >/dev/null 2>&1; then
|
|
533
|
+
echo "pnpm is required to build dist assets before deploy." >&2
|
|
534
|
+
exit 1
|
|
535
|
+
fi
|
|
536
|
+
|
|
537
|
+
(cd "${REPO_ROOT}" && pnpm build)
|
|
538
|
+
|
|
539
|
+
"${RSYNC_SSH[@]}" "${SSH_TARGET}" "mkdir -p ${REMOTE_DIR_Q}"
|
|
540
|
+
rsync -az --delete \
|
|
541
|
+
-e "$(printf '%q ' "${RSYNC_SSH[@]}")" \
|
|
542
|
+
--exclude ".git" \
|
|
543
|
+
--exclude ".env" \
|
|
544
|
+
--exclude "node_modules" \
|
|
545
|
+
--exclude ".codex-snapshots" \
|
|
546
|
+
--exclude "backups" \
|
|
547
|
+
--exclude "deploy/aliyun/deploy.env" \
|
|
548
|
+
--exclude "*.pem" \
|
|
549
|
+
--exclude "*.key" \
|
|
550
|
+
"${REPO_ROOT}/" "${SSH_TARGET}:${REMOTE_DIR}/"
|
|
551
|
+
|
|
552
|
+
remote_install_cmd=$(
|
|
553
|
+
cat <<EOF
|
|
554
|
+
set -e
|
|
555
|
+
cd ${REMOTE_DIR_Q}
|
|
556
|
+
rm -f .env deploy/aliyun/deploy.env
|
|
557
|
+
rm -rf backups
|
|
558
|
+
if [ "\$(id -u)" -eq 0 ]; then
|
|
559
|
+
env DOMAIN=${DOMAIN_Q} SNAPSHOT_SHARE_TOKEN=${TOKEN_Q} SNAPSHOT_SHARE_SITE_URL=${SITE_URL_Q} SNAPSHOT_SHARE_PUBLIC_API_URL=${API_URL_Q} SNAPSHOT_GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID_Q} SNAPSHOT_GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET_Q} SNAPSHOT_GITHUB_OWNER_LOGIN=${GITHUB_OWNER_LOGIN_Q} SNAPSHOT_GITHUB_OWNER_ID=${GITHUB_OWNER_ID_Q} SNAPSHOT_SESSION_SECRET=${SESSION_SECRET_Q} SNAPSHOT_AUTH_ALLOWED_ORIGINS=${AUTH_ALLOWED_ORIGINS_Q} PORT=${SHARE_PORT_Q} SNAPSHOT_SHARE_PROXY_MODE=${PROXY_MODE_Q} SNAPSHOT_SHARE_PUBLIC_PATH=${PUBLIC_PATH_Q} deploy/aliyun/install-share-api.sh
|
|
560
|
+
else
|
|
561
|
+
sudo env DOMAIN=${DOMAIN_Q} SNAPSHOT_SHARE_TOKEN=${TOKEN_Q} SNAPSHOT_SHARE_SITE_URL=${SITE_URL_Q} SNAPSHOT_SHARE_PUBLIC_API_URL=${API_URL_Q} SNAPSHOT_GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID_Q} SNAPSHOT_GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET_Q} SNAPSHOT_GITHUB_OWNER_LOGIN=${GITHUB_OWNER_LOGIN_Q} SNAPSHOT_GITHUB_OWNER_ID=${GITHUB_OWNER_ID_Q} SNAPSHOT_SESSION_SECRET=${SESSION_SECRET_Q} SNAPSHOT_AUTH_ALLOWED_ORIGINS=${AUTH_ALLOWED_ORIGINS_Q} PORT=${SHARE_PORT_Q} SNAPSHOT_SHARE_PROXY_MODE=${PROXY_MODE_Q} SNAPSHOT_SHARE_PUBLIC_PATH=${PUBLIC_PATH_Q} deploy/aliyun/install-share-api.sh
|
|
562
|
+
fi
|
|
563
|
+
EOF
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
"${RSYNC_SSH[@]}" "${SSH_TARGET}" "${remote_install_cmd}"
|
|
567
|
+
|
|
568
|
+
if [[ "${ISSUE_CERT}" -eq 1 ]]; then
|
|
569
|
+
remote_cert_cmd=$(
|
|
570
|
+
cat <<EOF
|
|
571
|
+
set -e
|
|
572
|
+
if [ "\$(id -u)" -eq 0 ]; then
|
|
573
|
+
certbot --nginx -d ${DOMAIN_Q} --non-interactive --agree-tos --email ${CERTBOT_EMAIL_Q}
|
|
574
|
+
else
|
|
575
|
+
sudo certbot --nginx -d ${DOMAIN_Q} --non-interactive --agree-tos --email ${CERTBOT_EMAIL_Q}
|
|
576
|
+
fi
|
|
577
|
+
cd ${REMOTE_DIR_Q}
|
|
578
|
+
rm -f .env deploy/aliyun/deploy.env
|
|
579
|
+
rm -rf backups
|
|
580
|
+
if [ "\$(id -u)" -eq 0 ]; then
|
|
581
|
+
env DOMAIN=${DOMAIN_Q} SNAPSHOT_SHARE_TOKEN=${TOKEN_Q} SNAPSHOT_SHARE_SITE_URL=${SITE_URL_Q} SNAPSHOT_SHARE_PUBLIC_API_URL=${API_URL_Q} SNAPSHOT_GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID_Q} SNAPSHOT_GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET_Q} SNAPSHOT_GITHUB_OWNER_LOGIN=${GITHUB_OWNER_LOGIN_Q} SNAPSHOT_GITHUB_OWNER_ID=${GITHUB_OWNER_ID_Q} SNAPSHOT_SESSION_SECRET=${SESSION_SECRET_Q} SNAPSHOT_AUTH_ALLOWED_ORIGINS=${AUTH_ALLOWED_ORIGINS_Q} PORT=${SHARE_PORT_Q} SNAPSHOT_SHARE_PROXY_MODE=${PROXY_MODE_Q} SNAPSHOT_SHARE_PUBLIC_PATH=${PUBLIC_PATH_Q} deploy/aliyun/install-share-api.sh
|
|
582
|
+
else
|
|
583
|
+
sudo env DOMAIN=${DOMAIN_Q} SNAPSHOT_SHARE_TOKEN=${TOKEN_Q} SNAPSHOT_SHARE_SITE_URL=${SITE_URL_Q} SNAPSHOT_SHARE_PUBLIC_API_URL=${API_URL_Q} SNAPSHOT_GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID_Q} SNAPSHOT_GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET_Q} SNAPSHOT_GITHUB_OWNER_LOGIN=${GITHUB_OWNER_LOGIN_Q} SNAPSHOT_GITHUB_OWNER_ID=${GITHUB_OWNER_ID_Q} SNAPSHOT_SESSION_SECRET=${SESSION_SECRET_Q} SNAPSHOT_AUTH_ALLOWED_ORIGINS=${AUTH_ALLOWED_ORIGINS_Q} PORT=${SHARE_PORT_Q} SNAPSHOT_SHARE_PROXY_MODE=${PROXY_MODE_Q} SNAPSHOT_SHARE_PUBLIC_PATH=${PUBLIC_PATH_Q} deploy/aliyun/install-share-api.sh
|
|
584
|
+
fi
|
|
585
|
+
EOF
|
|
586
|
+
)
|
|
587
|
+
"${RSYNC_SSH[@]}" "${SSH_TARGET}" "${remote_cert_cmd}"
|
|
588
|
+
fi
|
|
589
|
+
|
|
590
|
+
if [[ "${RUN_VERIFY}" -eq 1 ]]; then
|
|
591
|
+
if [[ "${GITHUB_AUTH_ENABLED}" -eq 1 ]]; then
|
|
592
|
+
node "${REPO_ROOT}/deploy/aliyun/verify-public-share.mjs" \
|
|
593
|
+
--api-url "${API_URL}" \
|
|
594
|
+
--site-url "${SITE_URL}" \
|
|
595
|
+
--skip-site-config
|
|
596
|
+
echo "Skipped token publish verification because GitHub OAuth is configured; verify browser publishing after logging in with GitHub."
|
|
597
|
+
else
|
|
598
|
+
SNAPSHOT_SHARE_TOKEN="${TOKEN}" node "${REPO_ROOT}/deploy/aliyun/verify-public-share.mjs" \
|
|
599
|
+
--api-url "${API_URL}" \
|
|
600
|
+
--site-url "${SITE_URL}" \
|
|
601
|
+
--skip-site-config \
|
|
602
|
+
--publish
|
|
603
|
+
fi
|
|
604
|
+
fi
|
|
605
|
+
|
|
606
|
+
if [[ "${CONFIGURE_PAGES}" -eq 1 ]]; then
|
|
607
|
+
pages_args=(
|
|
608
|
+
--api-url "${API_URL}"
|
|
609
|
+
--repo "${PAGES_REPO}"
|
|
610
|
+
--workflow "${PAGES_WORKFLOW}"
|
|
611
|
+
)
|
|
612
|
+
if [[ "${WAIT_PAGES}" -eq 1 ]]; then
|
|
613
|
+
pages_args+=(--wait)
|
|
614
|
+
fi
|
|
615
|
+
"${REPO_ROOT}/deploy/aliyun/configure-github-pages-api.sh" "${pages_args[@]}"
|
|
616
|
+
|
|
617
|
+
if [[ "${WAIT_PAGES}" -eq 1 && "${RUN_VERIFY}" -eq 1 ]]; then
|
|
618
|
+
if [[ "${GITHUB_AUTH_ENABLED}" -eq 1 ]]; then
|
|
619
|
+
node "${REPO_ROOT}/deploy/aliyun/verify-public-share.mjs" \
|
|
620
|
+
--api-url "${API_URL}" \
|
|
621
|
+
--site-url "${SITE_URL}"
|
|
622
|
+
echo "Skipped token publish verification because GitHub OAuth is configured; verify browser publishing after logging in with GitHub."
|
|
623
|
+
else
|
|
624
|
+
SNAPSHOT_SHARE_TOKEN="${TOKEN}" node "${REPO_ROOT}/deploy/aliyun/verify-public-share.mjs" \
|
|
625
|
+
--api-url "${API_URL}" \
|
|
626
|
+
--site-url "${SITE_URL}" \
|
|
627
|
+
--publish
|
|
628
|
+
fi
|
|
629
|
+
fi
|
|
630
|
+
fi
|
|
631
|
+
|
|
632
|
+
if [[ "${CONFIGURE_LOCAL}" -eq 1 ]]; then
|
|
633
|
+
local_args=(
|
|
634
|
+
--api-url "${API_URL}"
|
|
635
|
+
--site-url "${SITE_URL}"
|
|
636
|
+
)
|
|
637
|
+
if [[ -n "${TOKEN}" ]]; then
|
|
638
|
+
local_args+=(--token "${TOKEN}")
|
|
639
|
+
fi
|
|
640
|
+
if [[ "${REINSTALL_DAEMON}" -eq 1 ]]; then
|
|
641
|
+
local_args+=(--reinstall-daemon)
|
|
642
|
+
fi
|
|
643
|
+
"${REPO_ROOT}/deploy/aliyun/configure-local-publisher.sh" "${local_args[@]}"
|
|
644
|
+
|
|
645
|
+
if [[ "${RUN_VERIFY}" -eq 1 ]]; then
|
|
646
|
+
node "${REPO_ROOT}/deploy/aliyun/verify-public-share.mjs" \
|
|
647
|
+
--api-url "${API_URL}" \
|
|
648
|
+
--site-url "${SITE_URL}" \
|
|
649
|
+
--skip-site-config \
|
|
650
|
+
--check-local-config
|
|
651
|
+
fi
|
|
652
|
+
fi
|
|
653
|
+
|
|
654
|
+
cat <<EOF
|
|
655
|
+
|
|
656
|
+
Deployment command finished.
|
|
657
|
+
|
|
658
|
+
Next:
|
|
659
|
+
1. Set GitHub repository variable:
|
|
660
|
+
CODEX_SNAPSHOTS_PUBLIC_API_URL=${API_URL}
|
|
661
|
+
or run:
|
|
662
|
+
deploy/aliyun/configure-github-pages-api.sh --api-url ${API_URL} --repo ffffhx/codex-snapshots
|
|
663
|
+
2. Trigger the GitHub Pages workflow and wait for it to finish.
|
|
664
|
+
3. Run the public site verification:
|
|
665
|
+
node deploy/aliyun/verify-public-share.mjs --api-url ${API_URL} --site-url ${SITE_URL}
|
|
666
|
+
4. Configure your local viewer's public API/site settings:
|
|
667
|
+
SNAPSHOT_SHARE_API_URL=${API_URL} SNAPSHOT_SHARE_SITE_URL=${SITE_URL} deploy/aliyun/configure-local-publisher.sh --reinstall-daemon
|
|
668
|
+
5. If GitHub OAuth is configured, publish once from the browser after logging in with GitHub.
|
|
669
|
+
EOF
|