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
210
211
212
213
214
215
216
#!/bin/bash
set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)"
SSH_TARGET="${SSH_TARGET:-root@fedproxy.com}"
SSH_KEY="${SSH_KEY:-$HOME/.ssh/id_ed25519_no_password}"
RELAY_HOSTNAME="${RELAY_HOSTNAME:-xrpc.fedproxy.com}"
SOCKET="${SOCKET:-/opt/caddy/xrpc-relay-server.sock}"
BASE="${BASE:-/opt/xrpc-relay-go}"
OUT_ROOT="${OUT_ROOT:-$HOME/.tmp/xrpc-relay-deploy}"
UNIT=xrpc-relay-server.service
DROPIN_DIR=/etc/systemd/system/xrpc-relay-server.service.d
DROPIN="$DROPIN_DIR/30-go.conf"
BIN_NAME=socialweb-computer
SSH_OPTS=(-o IdentitiesOnly=yes -i "$SSH_KEY" -o BatchMode=yes -o ConnectTimeout=20 -o ServerAliveInterval=15)
usage() {
cat <<EOF
usage: scripts/deploy-xrpc-relay.sh [--rollback | --status]
(no flag) build socialweb-computer for linux/amd64 (CGO off) from this checkout, ship it to
$SSH_TARGET:$BASE/<version>/, point $BASE/current at it, write $DROPIN
(the Go relay: internal ingress-relay --unix-socket $SOCKET --hostname $RELAY_HOSTNAME),
restart $UNIT and check it through https://$RELAY_HOSTNAME; a failed check rolls back
--rollback remove $DROPIN and restart $UNIT (back to the deno relay of the lower drop-ins)
--status print which relay serves $UNIT and the health of https://$RELAY_HOSTNAME; changes nothing
env: SSH_TARGET (root@fedproxy.com), SSH_KEY (~/.ssh/id_ed25519_no_password), RELAY_HOSTNAME,
SOCKET, BASE (/opt/xrpc-relay-go), OUT_ROOT (~/.tmp/xrpc-relay-deploy), NO_AUTO_ROLLBACK=1
EOF
}
say() {
echo "deploy-xrpc-relay: $*"
}
q() {
printf "%q" "$1"
}
remote() {
ssh "${SSH_OPTS[@]}" "$SSH_TARGET" "$@"
}
unit_state() {
remote "systemctl show $UNIT -p ActiveState -p SubState -p MainPID -p ExecMainStartTimestamp -p DropInPaths --no-pager; pid=\$(systemctl show $UNIT -p MainPID --value); if [ \"\$pid\" != 0 ]; then echo \"MainExe=\$(readlink /proc/\$pid/exe)\"; fi; echo \"Current=\$(readlink $(q "$BASE/current") || true)\"; if [ -S $(q "$SOCKET") ]; then echo \"Socket=\$(stat -c '%A %U:%G' $(q "$SOCKET"))\"; else echo Socket=missing; fi"
}
wait_socket() {
remote "for i in \$(seq 1 60); do [ -S $(q "$SOCKET") ] && exit 0; sleep 0.5; done; exit 1"
}
fetch() {
local path="$1" body code
body="$(curl -sS --max-time 15 -w '\n%{http_code}' "https://$RELAY_HOSTNAME$path" 2>/dev/null || true)"
code="${body##*$'\n'}"
body="${body%$'\n'*}"
printf '%s %s\n' "$code" "$body"
}
socket_health() {
remote "curl -sS --max-time 10 --unix-socket $(q "$SOCKET") -H $(q "Host: $RELAY_HOSTNAME") -w ' %{http_code}' http://relay/xrpc/_health || true"
}
restart() {
remote "systemctl daemon-reload && systemctl restart $UNIT"
}
report() {
say "unit:"
unit_state | sed 's/^/ /'
say "https://$RELAY_HOSTNAME/xrpc/_health: $(fetch /xrpc/_health)"
say "https://$RELAY_HOSTNAME/.well-known/did.json: $(fetch /.well-known/did.json)"
say "last journal lines:"
remote "journalctl -u $UNIT -n 8 --no-pager -o cat | cut -c1-240" | sed 's/^/ /'
}
rollback() {
say "removing $DROPIN and restarting $UNIT on the deno relay"
remote "rm -f $(q "$DROPIN")"
restart
if ! wait_socket; then
say "rollback: $SOCKET did not come back"
report
exit 1
fi
local did
did="$(fetch /.well-known/did.json)"
report
case "$did" in
200*) say "rolled back: the deno relay serves https://$RELAY_HOSTNAME" ;;
*)
say "rolled back, but https://$RELAY_HOSTNAME/.well-known/did.json answers $did"
exit 1
;;
esac
}
mode=deploy
case "${1:-}" in
"") ;;
--rollback) mode=rollback ;;
--status) mode=status ;;
-h | --help)
usage
exit 0
;;
*)
usage >&2
exit 2
;;
esac
command -v curl >/dev/null || {
say "curl is required for the health check"
exit 1
}
case "$mode" in
status)
report
exit 0
;;
rollback)
rollback
exit 0
;;
esac
full_commit="$(git -C "$REPO" rev-parse HEAD)"
commit="${full_commit:0:12}"
version="$commit"
if [ -n "$(git -C "$REPO" status --porcelain --untracked-files=no)" ]; then
version="$commit-dirty-$(date -u +%Y%m%dT%H%M%SZ)"
fi
out="$OUT_ROOT/$version"
mkdir -p "$out"
say "building $BIN_NAME $version for linux/amd64 (CGO_ENABLED=0)"
stamp=github.com/johnandersen777/socialweb-computer-kcp/internal/buildstamp
(cd "$REPO" && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -buildvcs=false \
-ldflags "-s -w -X $stamp.Version=$version -X $stamp.Commit=$full_commit" -o "$out/$BIN_NAME" ./cmd/socialweb-computer)
sha="$(sha256sum "$out/$BIN_NAME" | cut -d' ' -f1)"
say "built $out/$BIN_NAME sha256 $sha ($(du -h "$out/$BIN_NAME" | cut -f1))"
if [ "$(uname -s)/$(uname -m)" = "Linux/x86_64" ]; then
"$out/$BIN_NAME" internal ingress-relay --help >/dev/null 2>&1 || {
say "the built binary does not run internal ingress-relay --help"
exit 1
}
fi
dest="$BASE/$version"
say "shipping to $SSH_TARGET:$dest"
remote "install -d -m 0755 $(q "$dest") && cat > $(q "$dest/$BIN_NAME.partial") && chmod 0755 $(q "$dest/$BIN_NAME.partial") && mv $(q "$dest/$BIN_NAME.partial") $(q "$dest/$BIN_NAME")" <"$out/$BIN_NAME"
remote_sha="$(remote "sha256sum $(q "$dest/$BIN_NAME") | cut -d' ' -f1")"
[ "$remote_sha" = "$sha" ] || {
say "the shipped binary is $remote_sha, not $sha"
exit 1
}
remote "$(q "$dest/$BIN_NAME") internal ingress-relay --help >/dev/null 2>&1" || {
say "the shipped binary does not run on $SSH_TARGET"
exit 1
}
say "pointing $BASE/current at $version and writing $DROPIN"
remote "set -e
prev=\$(readlink $(q "$BASE/current") || true)
if [ -n \"\$prev\" ] && [ \"\$prev\" != $(q "$version") ]; then printf '%s\n' \"\$prev\" > $(q "$BASE/previous"); fi
ln -sfn $(q "$version") $(q "$BASE/current.new")
mv -T $(q "$BASE/current.new") $(q "$BASE/current")
install -d -m 0755 $(q "$DROPIN_DIR")
cat > $(q "$DROPIN.partial") <<'UNIT'
[Service]
Type=notify
NotifyAccess=main
WorkingDirectory=$BASE
ExecStart=
ExecStart=$BASE/current/$BIN_NAME internal ingress-relay --unix-socket $SOCKET --hostname $RELAY_HOSTNAME
TimeoutStopSec=20
UNIT
mv $(q "$DROPIN.partial") $(q "$DROPIN")"
say "restarting $UNIT"
if ! restart || ! wait_socket; then
say "$UNIT did not come up on the Go relay"
report
if [ "${NO_AUTO_ROLLBACK:-}" = 1 ]; then
say "NO_AUTO_ROLLBACK=1: left as is; scripts/deploy-xrpc-relay.sh --rollback restores the deno relay"
exit 1
fi
rollback
exit 1
fi
health="$(fetch /xrpc/_health)"
did="$(fetch /.well-known/did.json)"
via_socket="$(socket_health)"
report
say "via $SOCKET: $via_socket"
case "$health" in
200*"$commit"*)
case "$did" in
200*)
say "deployed: $version ($sha) serves https://$RELAY_HOSTNAME; rollback with scripts/deploy-xrpc-relay.sh --rollback"
exit 0
;;
esac
;;
esac
say "the health check failed: _health $health, did.json $did"
if [ "${NO_AUTO_ROLLBACK:-}" = 1 ]; then
say "NO_AUTO_ROLLBACK=1: left as is; scripts/deploy-xrpc-relay.sh --rollback restores the deno relay"
exit 1
fi
rollback
exit 1