start
tcmts.deploy.server.start · #iax732z7 · sh file
#!/bin/sh
# tcm.codes: serve the codebase on the volume, making it the first time, and the site to anyone: the home page at /,
# the docs under /docs (their old addresses, at the root, redirected there: --moved), the web UI at /forge, and tcm's
# registry at /registry. Anyone can pull it (--public); TCM_TOKEN is the maintainer's, and contributors have tokens of their own
# (`tcm user add`). Railway terminates TLS in front of it, so it serves plain HTTP inside (--insecure); clients use the
# https:// domain. It never runs code for them. The examples that are apps run beside it, under the docs'
# /docs/examples/<name>/; the registry is a service of its own (tcm-hub), reached over Railway's private network.
set -eu
: "${TCM_TOKEN:?set TCM_TOKEN: the maintainer's token}"
TCM=/app/node_modules/tcm-ts/bin/tcm.js
DIR="${TCM_DIR:-/data/tcm-ts}"
EXAMPLES="${TCM_EXAMPLES_DIR:-/data/examples}"
# the docs site, and where it is
DOCS=/app/site/docs
AT=/docs
# the registry's server, at /registry (empty: none): its service is still named tcm-hub, from when the registry was
# called the hub (renaming it changes this private address)
REGISTRY="${TCM_REGISTRY_PROXY-http://tcm-hub.railway.internal:8080}"
# how often notes and polls start over, in seconds
RESET="${TCM_EXAMPLES_RESET:-21600}"
mkdir -p "$DIR" "$EXAMPLES"
cd "$DIR"
if [ ! -f .tcm/codebase.sqlite ]; then
node "$TCM" init > /dev/null
rm -f scratch.ts
fi
# a tcm with a new hash format moves the codebase to it first (history and all; the same hashes a clone migrating
# gets, so they go on syncing), keeping it as it was beside it
node "$TCM" migrate --if-needed
# Whether an example answers on a port (with node: the image has no curl).
answers() {
node -e 'fetch(process.argv[1]).then((r) => process.exit(r.status < 500 ? 0 : 1), () => process.exit(1))' \
"http://127.0.0.1:$1$AT/examples/$2/"
}
# An example, running: built from the site's download of it the first time (and again when the download or the tcm
# changes), then served by `tcm start` under /docs/examples/<name>/, on one of two ports: a new instance (a new version, a
# fresh start) starts on the other, and the one serving stops only once it answers, `tcm serve --proxy` asking
# whichever answers. So while a new version is built, the one before goes on serving, and the switch drops nothing.
# With none before (the first deploy), the path's page says it's starting. With reset=yes, a fresh instance with no
# data (tables, sessions) replaces it every six hours, so what visitors write doesn't stay.
run_example() {
name=$1 app=$2 port=$3 reset=$4
shift 4
flags="$*"
tgz=$DOCS/examples/$name.tar.gz
key=$(cat "$tgz" /app/tcm-ts.tgz | sha256sum | cut -c1-16)
dir=$EXAMPLES/$name-$key
serving="" at=""
# an instance from a directory on the port the one serving isn't on, replacing it once it answers; fails if it ends
# first (the one serving goes on)
replace() {
next=$port
if [ "$at" = "$port" ]; then next=$((port + 100)); fi
# (flags split into words on purpose)
# shellcheck disable=SC2086
(cd "$1" && exec node "$TCM" start "$app" --port "$next" --base "$AT/examples/$name" $flags) &
started=$!
while kill -0 "$started" 2>/dev/null && ! answers "$next" "$name"; do sleep 1; done
if ! kill -0 "$started" 2>/dev/null; then return 1; fi
if [ -n "$serving" ]; then
kill "$serving" 2>/dev/null || true
wait "$serving" 2>/dev/null || true
fi
serving=$started at=$next
}
before=""
# (a build is done when it says so: one stopped halfway, by a deploy, is built again)
if [ ! -f "$dir/.built" ]; then
for old in "$EXAMPLES/$name"-*; do
if [ "$old" != "$dir" ] && [ -f "$old/.built" ]; then before=$old; fi
done
if [ -n "$before" ]; then replace "$before" || true; fi
rm -rf "$dir"
work=$(mktemp -d)
tar -xzf "$tgz" -C "$work"
if TCM_BIN=$TCM TCM_CODEBASE= NO_TESTS=1 "$work/$name/setup.sh" "$dir" > "$EXAMPLES/$name.log" 2>&1; then
# its first build of the app, made before it's served
(cd "$dir" && node "$TCM" build "$app" > /dev/null 2>&1) || true
touch "$dir/.built"
else
echo "example $name didn't build: $EXAMPLES/$name.log"
rm -rf "$dir"
# (the one before goes on)
if [ -z "$before" ]; then return; fi
dir=$before before=""
fi
rm -rf "$work"
fi
while true; do
if ! replace "$dir"; then
sleep 5
continue
fi
rm -rf "$dir/.tcm/data.old"
for old in "$EXAMPLES/$name"-*; do
if [ "$old" != "$dir" ]; then rm -rf "$old"; fi
done
if [ "$reset" = yes ]; then
waited=0
while kill -0 "$serving" 2>/dev/null && [ "$waited" -lt "$RESET" ]; do
sleep 5
waited=$((waited + 5))
done
# the one serving keeps its data (open) while the fresh one starts without any
if kill -0 "$serving" 2>/dev/null; then
rm -rf "$dir/.tcm/data.old"
mv "$dir/.tcm/data" "$dir/.tcm/data.old" 2>/dev/null || true
fi
else
wait "$serving" || true
fi
done
}
# the apps among the examples, as the site lists them (`<name> <app> <reset> <flags>`), each on two ports of its own
proxies=""
port=7801
while read -r name app reset flags; do
[ -n "$name" ] || continue
# (flags split into words on purpose)
# shellcheck disable=SC2086
run_example "$name" "$app" "$port" "$reset" $flags &
proxies="$proxies${proxies:+,}$AT/examples/$name=http://127.0.0.1:$port|http://127.0.0.1:$((port + 100))"
port=$((port + 1))
done < "$DOCS/examples/apps.txt"
if [ -n "$REGISTRY" ]; then proxies="$proxies${proxies:+,}/registry=$REGISTRY"; fi
cd "$DIR"
exec node "$TCM" serve --host 0.0.0.0 --port "${PORT:-7737}" --insecure --no-run --public --site /app/site --moved "$AT" ${proxies:+--proxy "$proxies"}