Coordinator (accounts with approval, API tokens, tasks with leases, sandboxed MSVC verification against the original exe, peer review by three agents for non-identical code, human review, serial merge queue), the `td` contributor CLI with an MCP server for agents, pinned Ghidra integration, deployment (Caddy, systemd, nftables, Forgejo) and the wowdecomp 15890 migration tools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNwtXPYqFu3oKaaXwYwNWW
95 lines
3.6 KiB
Bash
95 lines
3.6 KiB
Bash
#!/bin/sh
|
|
# Installs or updates the Clank Together coordinator on Debian 13. Run as root
|
|
# from the unpacked source directory: sh deploy/install.sh <public-hostname>
|
|
set -eu
|
|
|
|
HOST="${1:?usage: install.sh <public-hostname>}"
|
|
NODE_VERSION=v22.21.0
|
|
SRC=$(cd "$(dirname "$0")/.." && pwd)
|
|
APP=/opt/togetherdecomp
|
|
DATA=/var/lib/togetherdecomp
|
|
|
|
echo "== packages"
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update -q
|
|
apt-get install -y -q git curl ca-certificates nftables caddy docker.io xz-utils >/dev/null
|
|
|
|
echo "== node $NODE_VERSION"
|
|
if [ "$(/opt/node/bin/node --version 2>/dev/null || true)" != "$NODE_VERSION" ]; then
|
|
tmp=$(mktemp -d)
|
|
curl -fsSL "https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-linux-x64.tar.xz" -o "$tmp/node.tar.xz"
|
|
curl -fsSL "https://nodejs.org/dist/$NODE_VERSION/SHASUMS256.txt" -o "$tmp/SHASUMS256.txt"
|
|
(cd "$tmp" && grep " node-$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sed 's/node-.*/node.tar.xz/' | sha256sum -c -)
|
|
rm -rf /opt/node && mkdir -p /opt/node
|
|
tar -xJf "$tmp/node.tar.xz" -C /opt/node --strip-components=1
|
|
rm -rf "$tmp"
|
|
fi
|
|
|
|
echo "== docker sandbox settings"
|
|
mkdir -p /etc/docker
|
|
cat > /etc/docker/daemon.json <<'EOF'
|
|
{
|
|
"userns-remap": "default",
|
|
"no-new-privileges": true,
|
|
"icc": false,
|
|
"live-restore": true,
|
|
"log-driver": "local"
|
|
}
|
|
EOF
|
|
systemctl restart docker
|
|
|
|
echo "== service user and code"
|
|
id togetherdecomp >/dev/null 2>&1 || useradd --system --home-dir "$DATA" --shell /usr/sbin/nologin togetherdecomp
|
|
usermod -aG docker togetherdecomp
|
|
rm -rf "$APP.new" && mkdir -p "$APP.new"
|
|
cp -a "$SRC/src" "$SRC/template" "$SRC/ghidra_scripts" "$SRC/toolchain" "$SRC/package.json" "$APP.new/"
|
|
chown -R root:root "$APP.new" && chmod -R go-w "$APP.new"
|
|
rm -rf "$APP.old"; [ -d "$APP" ] && mv "$APP" "$APP.old"; mv "$APP.new" "$APP"
|
|
|
|
mkdir -p "$DATA" && chown togetherdecomp:togetherdecomp "$DATA" && chmod 700 "$DATA"
|
|
NODE="/opt/node/bin/node --disable-warning=ExperimentalWarning"
|
|
if [ ! -f "$DATA/server.json" ]; then
|
|
runuser -u togetherdecomp -- $NODE "$APP/src/server/cli.js" init --data "$DATA"
|
|
fi
|
|
# Production settings, keeping anything else already configured.
|
|
UIDGID="$(id -u togetherdecomp):$(id -g togetherdecomp)"
|
|
runuser -u togetherdecomp -- $NODE -e '
|
|
const fs = require("fs"); const [file, host, user] = process.argv.slice(1);
|
|
const c = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
c.listen = { host: "127.0.0.1", port: 8080 };
|
|
c.publicUrl = "https://" + host;
|
|
c.trustProxy = true;
|
|
c.allowUnsandboxedRunners = false;
|
|
for (const r of Object.values(c.runners || {})) if (r.type === "docker") r.user = user;
|
|
fs.writeFileSync(file, JSON.stringify(c, null, 2) + "\n");
|
|
' "$DATA/server.json" "$HOST" "$UIDGID"
|
|
|
|
echo "== systemd"
|
|
install -m 0644 "$SRC/deploy/togetherdecomp.service" /etc/systemd/system/togetherdecomp.service
|
|
install -m 0644 "$SRC/deploy/nftables-td.nft" /etc/nftables-td.nft
|
|
cat > /etc/systemd/system/td-firewall.service <<'EOF'
|
|
[Unit]
|
|
Description=Clank Together host firewall
|
|
After=network-pre.target docker.service
|
|
Wants=network-pre.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
RemainAfterExit=yes
|
|
ExecStart=/usr/sbin/nft -f /etc/nftables-td.nft
|
|
ExecStop=/usr/sbin/nft delete table inet td_filter
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
sed "s/saty.dev/$HOST/" "$SRC/deploy/Caddyfile" > /etc/caddy/Caddyfile
|
|
caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable --now td-firewall.service
|
|
systemctl restart td-firewall.service
|
|
systemctl enable togetherdecomp.service caddy.service
|
|
systemctl restart togetherdecomp.service caddy.service
|
|
sleep 2
|
|
systemctl --no-pager --lines=5 status togetherdecomp.service | head -12
|
|
echo "== done: https://$HOST"
|