~james/core

core/e2e/up -rwxr-xr-x 5.9 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
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
#!/bin/sh
# starts the localinfra stack the way e2e needs it, with the web as a production build.
# --check says whether it is up and its web build is current: 0 yes, 1 not up, 2 web out of date
# a live pc stack wins over compose, so this check has to come first
set -eu
here="$(cd "$(dirname "$0")" && pwd)"
cd "$here/.."

E2E_WEB_SRC="$(node "$here/harness/web-hash.ts")"
export E2E_WEB_SRC

pcdir="$PWD/localinfra/pc"
state="$(here="$pcdir" && . "$pcdir/bin/env.sh" && echo "$TNGL_STATE")"
sock="$state/run/pc.sock"
pc() { curl -sf --unix-socket "$sock" "$@"; }
pc_running() { pc http://localhost/live >/dev/null 2>&1; }

stack="${E2E_STACK:-}"
if [ -z "$stack" ]; then
	if pc_running; then
		stack=pc
	elif command -v podman >/dev/null 2>&1 || command -v docker >/dev/null 2>&1; then
		stack=compose
	else
		stack=pc
	fi
fi

if [ "${1:-}" = "--stack" ]; then
	echo "$stack"
	exit
fi

# only the browser knows these names, so curl needs --resolve to reach caddy
alice_resolves() {
	curl -sf --cacert localinfra/certs/root.crt --resolve pds.tngl.boltless.dev:443:127.0.0.1 \
		"https://pds.tngl.boltless.dev/xrpc/com.atproto.identity.resolveHandle?handle=alice.pds.tngl.boltless.dev" >/dev/null
}

if [ "$stack" = pc ]; then
	log="$state/up.log"
	launcher=0
	# a live stack only rebuilds the web. its other services have to come from this tree
	backend="$(git log -1 --format=%H -- . ':(exclude)web' ':(exclude)e2e')"
	# setup writes the source hash first, then the env checksum
	built="$(cut -d ' ' -f 1 "$state/data/web/built" 2>/dev/null || true)"

	if [ "${1:-}" = "--check" ]; then
		pc_running || exit 1
		[ "$built" = "$E2E_WEB_SRC" ] || exit 2
		exit
	fi

	if pc_running; then
		if [ "$(cat "$state/backend" 2>/dev/null)" != "$backend" ]; then
			echo "the running stack's services are not from this tree, stop it with: curl --unix-socket $sock -X POST http://localhost/project/stop" >&2
			exit 1
		fi
		if [ "$built" != "$E2E_WEB_SRC" ]; then
			(here="$pcdir" && . "$pcdir/bin/env.sh" && sh "$pcdir/services/web/setup")
			pc -X POST http://localhost/process/restart/web >/dev/null
			# a restart keeps reporting the old exit code until the new process is up, so poll for ready
			tries=0
			until pc http://localhost/process/web | grep -q '"is_ready":"Ready"'; do
				tries=$((tries + 1))
				[ "$tries" -lt 300 ] || { echo "the web did not come back after its restart" >&2; exit 1; }
				sleep 1
			done
		fi
	else
		mkdir -p "$state"
		echo "$backend" >"$state/backend"
		# pipeline specs need a spindle, and compose always runs one. the sites specs need the worker and sitesd
		for profile in spindle sites; do
			case ",${TNGL_PROFILES:-}," in
			*,"$profile",*) ;;
			*) TNGL_PROFILES=${TNGL_PROFILES:+$TNGL_PROFILES,}$profile ;;
			esac
		done
		export TNGL_PROFILES
		# not -D, linux port forwarders and slirp die with the foreground process
		# own session, so a caller killing its process group doesn't take the stack down
		own_session() {
			if command -v setsid >/dev/null 2>&1; then exec setsid "$@"; fi
			exec perl -MPOSIX -e 'POSIX::setsid(); exec @ARGV' "$@"
		}
		if command -v nix >/dev/null 2>&1; then
			own_session nix run .#localinfra -- -t=false >"$log" 2>&1 &
		else
			own_session "$pcdir/bin/up" -t=false >"$log" 2>&1 &
		fi
		launcher=$!
	fi

	node -e '
		const http = require("node:http");
		const list = () =>
			new Promise((resolve, reject) =>
				http
					.get({ socketPath: process.argv[1], path: "/processes" }, (res) => {
						let body = "";
						res.on("data", (chunk) => (body += chunk));
						res.on("end", () => resolve(JSON.parse(body).data));
					})
					.on("error", reject)
			);
		const failed = (p) => p.status === "Error" || (p.status === "Completed" && p.exit_code !== 0);
		const launcher = Number(process.argv[2]);
		const deadline = Date.now() + Number(process.argv[3]) * 1000;
		const launcherGone = () => {
			if (!launcher) return false;
			try {
				process.kill(launcher, 0);
				return false;
			} catch {
				return true;
			}
		};
		(async () => {
			for (;;) {
				const all = await list().catch(() => []);
				const bad = all.find(failed);
				if (bad) {
					console.error(`${bad.name} failed, its log is in the stack`);
					process.exit(1);
				}
				if (all.length && all.every((p) => p.status === "Completed" || p.is_ready === "Ready")) return;
				if (launcherGone() || Date.now() > deadline) {
					console.error("the stack did not come up");
					process.exit(1);
				}
				await new Promise((resolve) => setTimeout(resolve, 2000));
			}
		})();
	' "$sock" "$launcher" "${E2E_UP_TIMEOUT:-3600}" || {
		[ "$launcher" = 0 ] || tail -n 30 "$log" >&2
		exit 1
	}

	if ! alice_resolves; then
		echo "alice doesn't resolve, the stack's init-data hasn't run" >&2
		exit 1
	fi
	exit
fi

# the web is a production build here, so no page waits on a module compiling when it is first asked for
web="-f localinfra/docker-compose.web-build.yml"
if command -v podman >/dev/null 2>&1; then
	engine=podman
	compose="podman compose -f docker-compose.yml $web"
else
	# docker doesn't publish ports from an internal network, which caddy sits on
	engine=docker
	compose="docker compose -f docker-compose.yml -f localinfra/docker-compose.docker.yml $web"
fi

if [ "${1:-}" = "--check" ]; then
	id=$($compose ps --status running -q web 2>/dev/null || true)
	[ -n "$id" ] || exit 1
	# the dev server answers too, but every first page waits on it compiling
	$engine inspect --format '{{join .Config.Cmd " "}}' "$id" | grep -q "vite build" || exit 1
	built="$($engine inspect --format '{{index .Config.Labels "e2e.web-src"}}' "$id")"
	[ "$built" = "$E2E_WEB_SRC" ] || exit 2
	exit
fi

if [ ! -f localinfra/certs/root.crt ]; then
	echo "no dev ca yet, run ./localinfra/scripts/make-certs.sh and trust localinfra/certs/root.crt" >&2
	exit 1
fi

# compose only recreates the web when this label value changes
$compose up -d --wait

if ! alice_resolves; then
	echo "alice doesn't resolve, the stack's init-data hasn't run" >&2
	exit 1
fi