clank-together/deploy/install.sh
Saty cf2e111a6c Peer review needs three agreeing verdicts either way; public crew views; safe updates
Peer review: one "different" no longer sends a function back. It stays in
review until three agents agree: three "same" send it to human review, three
"different" back to its author; mixed votes let further reviewers in. When a
function does go back (peers or a human), only that function goes; the rest
of its batch carries on and its approved part merges without it.

Public "Active agents" (who is working on what right now) and "Users"
(merged, submitted, peer reviews) sections, from /api/crew. No token labels
or account details.

Updates: deploy/update.sh copies and syntax-checks a new release next to the
running one, switches the /opt/togetherdecomp symlink in one step and only
then restarts what changed (the worker when idle); pages are live without a
restart. tools/deploy.sh ships HEAD (and with --td the td binaries).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNwtXPYqFu3oKaaXwYwNWW
2026-10-18 04:35:35 +02:00

119 lines
5 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
GHIDRA_VERSION=11.2.1
GHIDRA_URL=https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_11.2.1_build/ghidra_11.2.1_PUBLIC_20241105.zip
GHIDRA_SHA256=ce4db5117da0fbaf8f33863fec4f40902f754f06b68945a59fb1c0f9b1bc461c
SRC=$(cd "$(dirname "$0")/.." && pwd)
APP=/opt/togetherdecomp
DATA=/var/lib/togetherdecomp
echo "== packages"
export DEBIAN_FRONTEND=noninteractive
apt-get update -q
# binutils: objdump disassembles original functions for /api/original before a Ghidra export exists.
apt-get install -y -q git curl ca-certificates nftables caddy docker.io xz-utils binutils >/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 "== ghidra $GHIDRA_VERSION (server-side project and pseudocode)"
# The JDK, not just the runtime: Ghidra compiles its .java scripts.
apt-get install -y -q openjdk-21-jdk-headless unzip >/dev/null
GHIDRA_HOME="/opt/ghidra/ghidra_${GHIDRA_VERSION}_PUBLIC"
if [ ! -x "$GHIDRA_HOME/support/analyzeHeadless" ]; then
tmp=$(mktemp -d)
curl -fsSL "$GHIDRA_URL" -o "$tmp/ghidra.zip"
echo "$GHIDRA_SHA256 $tmp/ghidra.zip" | sha256sum -c - >/dev/null
mkdir -p /opt/ghidra
unzip -q -o "$tmp/ghidra.zip" -d /opt/ghidra
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
# Code updates go through update.sh (releases behind a symlink); here it only lays the code down.
if [ ! -e "$APP" ]; then
mkdir -p /opt/togetherdecomp-releases/initial
cp -a "$SRC/src" "$SRC/template" "$SRC/ghidra_scripts" "$SRC/toolchain" "$SRC/deploy" "$SRC/package.json" /opt/togetherdecomp-releases/initial/
chown -R root:root /opt/togetherdecomp-releases && chmod -R go-w,a+rX /opt/togetherdecomp-releases
ln -s /opt/togetherdecomp-releases/initial "$APP"
else
sh "$SRC/deploy/update.sh" "$SRC"
fi
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 -- env GHIDRA_HOME="$GHIDRA_HOME" $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;
c.ghidraHome = process.env.GHIDRA_HOME || c.ghidraHome || null;
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/togetherdecomp-worker.service" /etc/systemd/system/togetherdecomp-worker.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 togetherdecomp-worker.service caddy.service
systemctl restart togetherdecomp.service togetherdecomp-worker.service caddy.service
sleep 2
systemctl --no-pager --lines=5 status togetherdecomp.service | head -12
echo "== done: https://$HOST"