-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdeploy.sh
More file actions
executable file
·209 lines (188 loc) · 9.42 KB
/
Copy pathdeploy.sh
File metadata and controls
executable file
·209 lines (188 loc) · 9.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#!/usr/bin/env bash
# Deploy GenX-DOS to the genx-dos.fun web host (Namecheap shared / cPanel /
# LiteSpeed) over rsync-on-SSH.
#
# What ships is decided by build-portable.sh, NOT by this script. That file
# calls itself the single source of truth for the shipping file set, and a
# second exclude list here would silently drift from it the first time either
# changed — so this builds the portable set into a temp dir and mirrors that.
# Change what reaches the web host by editing build-portable.sh.
#
# The target is a required argument rather than a default, because the only
# thing separating a routine staging push from overwriting the live public
# site is which word you type. Live also asks for confirmation unless -y.
#
# Usage:
# ./deploy.sh staging # push to staging.genx-dos.fun (HTTP only)
# ./deploy.sh live # push to genx-dos.fun, with a confirm prompt
# ./deploy.sh live -n # dry run: report what would change, send nothing
# ./deploy.sh live -y # skip the confirm prompt (for unattended use)
set -euo pipefail
cd "$(dirname "$(realpath "$0")")"
# ---- host ---------------------------------------------------------------
SSH_USER=genxuuaw
SSH_HOST=199.188.201.33
SSH_PORT=21098 # Namecheap shared hosting, NOT 22
SSH_KEY="${HOME}/.ssh/genx-dos-deploy"
# ---- arguments ----------------------------------------------------------
TARGET=""; DRYRUN=0; ASSUME_YES=0
for a in "$@"; do
case "$a" in
live|staging) TARGET="$a" ;;
-n|--dry-run) DRYRUN=1 ;;
-y|--yes) ASSUME_YES=1 ;;
-h|--help) awk 'NR>1 && /^#/ { sub(/^# ?/,""); print; next } NR>1 { exit }' "$0"; exit 0 ;;
*) echo "Unknown argument: $a (try --help)" >&2; exit 2 ;;
esac
done
if [[ -z "$TARGET" ]]; then
echo "Specify a target: ./deploy.sh staging or ./deploy.sh live" >&2
exit 2
fi
case "$TARGET" in
live) REMOTE_DIR=public_html ; URL=https://genx-dos.fun ;;
staging) REMOTE_DIR=staging ; URL=http://staging.genx-dos.fun ;;
esac
[[ -f "$SSH_KEY" ]] || { echo "Deploy key missing: $SSH_KEY" >&2; exit 1; }
# The key carries a passphrase, and BatchMode below means ssh will never prompt
# for it — it just fails with "Permission denied (publickey)", which reads like
# the key was revoked rather than merely locked. Say which it is, before a build
# runs. `ssh-keygen -y -P ""` succeeds only on an unencrypted key.
if ! ssh-keygen -y -P "" -f "$SSH_KEY" >/dev/null 2>&1; then
KEY_FP=$(ssh-keygen -lf "$SSH_KEY.pub" 2>/dev/null | awk '{print $2}')
if ! ssh-add -l 2>/dev/null | grep -qF "$KEY_FP"; then
echo "The deploy key is passphrase-protected and is not loaded in your ssh-agent." >&2
echo "Unlock it once for this session, then run this again:" >&2
echo " ssh-add $SSH_KEY" >&2
exit 1
fi
fi
# LogLevel=ERROR suppresses the client's post-quantum key-exchange warning,
# which fires on every connection and buries the transfer summary.
SSH_CMD="ssh -i $SSH_KEY -p $SSH_PORT -o BatchMode=yes -o ConnectTimeout=15 -o LogLevel=ERROR"
# ---- build the shipping set --------------------------------------------
# Built fresh every run rather than mirroring _Portable, so a stale portable
# folder can never become a stale website.
STAGE_DIR=$(mktemp -d /tmp/genx-deploy.XXXXXX)
trap 'rm -rf "$STAGE_DIR"' EXIT
echo "Building the shipping file set..."
./build-portable.sh "$STAGE_DIR" >/dev/null
# The one place a web deploy legitimately differs from the portable build:
# GenX-DOS.sh/.bat start a local web server for a downloaded copy of the site.
# On a host that already serves the site they do nothing except sit there
# publicly downloadable, so they ship in the zip but not to the web.
rm -f "$STAGE_DIR/GenX-DOS.sh" "$STAGE_DIR/GenX-DOS.bat"
# The mirror image of the line above: .htaccess is host configuration, so it is
# excluded from the release zip (which is served by python3 -m http.server and
# would ignore it) but must reach the web host.
cp .htaccess "$STAGE_DIR/.htaccess"
# Optional local-only overlay. Anything in this file is appended to the deployed
# copy of genx-repo-link.js, so it reaches the live site but is in neither the
# repo nor the release zip. Appended rather than patched in, so the repo's own
# copy of that script can change without the overlay going stale. Absent on a
# fresh clone, where nothing happens and the site is simply the repo.
PILL_EXTRA="${HOME}/.config/genx-dos/pill-extra.js"
if [[ -f "$PILL_EXTRA" ]]; then
printf '\n' >> "$STAGE_DIR/systems/_shared/genx-repo-link.js"
cat "$PILL_EXTRA" >> "$STAGE_DIR/systems/_shared/genx-repo-link.js"
echo " + local pill overlay applied (not in the repo)"
fi
# Same idea for the home page, but as markup rather than script: index.html
# carries default-src 'none' and loads no JavaScript at all, so an injected
# <script> would be blocked by its own policy. The fragment is spliced into the
# "Go further" card list instead.
HOME_EXTRA="${HOME}/.config/genx-dos/home-extra.html"
if [[ -f "$HOME_EXTRA" ]]; then
python3 - "$STAGE_DIR/index.html" "$HOME_EXTRA" <<'PYEOF'
import sys, pathlib
page, frag = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2]).read_text()
s = page.read_text()
# Anchor on the card list itself, then its FIRST closing div. Anchoring on
# '</div></section>' alone matched four places and put the card in the wrong
# section entirely.
start = s.index('<div class="links">')
i = s.index('\n </div>', start) + 1
page.write_text(s[:i] + frag + s[i:])
PYEOF
echo " + local home-page overlay applied (not in the repo)"
fi
echo " $(find "$STAGE_DIR" -type f | wc -l) files, $(du -sh "$STAGE_DIR" | cut -f1)"
# ---- deploy -------------------------------------------------------------
# protect = present on the server, absent from the build, must survive --delete:
# .well-known SSL/domain validation — removing it can break cert renewal
# cgi-bin created by cPanel
# nc_assets, parking-page.shtml.old Namecheap leftovers (live only)
# robots.txt staging's noindex guard; the repo has no robots.txt, so
# without this the first staging deploy makes it indexable
# --chmod normalises permissions on the receiving side. Without it, -a
# preserves whatever the local file happens to carry: prompt/img/ami-logo.png
# sat at 600 locally, which git does not track and GitHub Pages ignores, so it
# only surfaced here as a 403 on the AMIBIOS logo. Serving is not a place where
# local modes should matter.
RSYNC_OPTS=(-az --delete --human-readable --chmod=D755,F644
--filter='protect .well-known/'
--filter='protect cgi-bin/'
--filter='protect nc_assets/'
--filter='protect parking-page.shtml.old'
--filter='protect robots.txt')
if [[ $DRYRUN -eq 1 ]]; then
echo
echo "DRY RUN — $TARGET ($URL). Nothing will be sent."
rsync "${RSYNC_OPTS[@]}" -n --itemize-changes -e "$SSH_CMD" \
"$STAGE_DIR"/ "$SSH_USER@$SSH_HOST:$REMOTE_DIR/" \
| grep -vE '^(sending|sent|total|$)' | head -60
echo
echo "(showing at most 60 changes)"
exit 0
fi
if [[ "$TARGET" == live && $ASSUME_YES -eq 0 ]]; then
echo
echo "About to overwrite the LIVE public site at $URL."
read -rp "Type 'deploy' to continue: " confirm
[[ "$confirm" == "deploy" ]] || { echo "Aborted."; exit 1; }
fi
echo
echo "Deploying to $TARGET ($URL)..."
rsync "${RSYNC_OPTS[@]}" --info=stats2 -e "$SSH_CMD" \
"$STAGE_DIR"/ "$SSH_USER@$SSH_HOST:$REMOTE_DIR/" \
| grep -E 'Number of (created|deleted|regular)|Total transferred' | sed 's/^/ /'
# ---- verify -------------------------------------------------------------
# A deploy that reports success while the site 404s is the failure mode worth
# catching — an earlier upload left directory entries as zero-byte files and
# every path under them broke, with nothing in the transfer log to show it.
echo
echo "Verifying..."
fail=0
for path in / /prompt/ /prompt/img/ami-logo.png /styles/genx.css /docs/wiki/ /systems/bbcmicro/dist/index.html; do
# curl exits non-zero AND prints 000 on a resolve failure, so a `|| echo 000`
# fallback would concatenate into "000000". Capture, then default if empty.
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 25 "$URL$path" 2>/dev/null) || true
[[ -z "$code" ]] && code=000
if [[ "$code" == 200 ]]; then
printf ' \033[32mok\033[0m %s\n' "$path"
else
printf ' \033[31m%s\033[0m %s\n' "$code" "$path"; fail=1
fi
done
wasm=$(curl -sI --max-time 25 "$URL/systems/atari400/atari800.wasm" 2>/dev/null \
| grep -i '^content-type:' | tr -d '\r' | awk '{print $2}') || true
if [[ "$wasm" == "application/wasm" ]]; then
printf ' \033[32mok\033[0m .wasm served as application/wasm\n'
else
printf ' \033[31mbad\033[0m .wasm served as "%s" — emulators will not start\n' "$wasm"; fail=1
fi
echo
if [[ $fail -eq 0 ]]; then
echo "Deployed to $URL"
elif ! getent hosts "${URL#*://}" >/dev/null 2>&1; then
# Files went up fine; this machine just can't resolve the name yet. A newly
# created subdomain gets negative-cached locally well after the
# authoritative server has the record.
echo "Files deployed. Checks could not run: ${URL#*://} does not resolve here yet."
echo "The authoritative server may already have it — verify with:"
echo " dig +short A ${URL#*://} @dns1.namecheaphosting.com"
echo " curl --resolve ${URL#*://}:80:$SSH_HOST $URL/"
else
echo "Deployed, but checks failed above — inspect before announcing." >&2
exit 1
fi