120 lines
5.1 KiB
Bash
120 lines
5.1 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" "$SRC/package-lock.json" /opt/togetherdecomp-releases/initial/
|
|
(cd /opt/togetherdecomp-releases/initial && PATH="/opt/node/bin:$PATH" /opt/node/bin/npm ci --omit=dev --ignore-scripts)
|
|
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/clank\.rodecomp\.online/$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"
|