Files
marvin-turnstone/start-turnstone.sh
T
marvin 653532a72a Stable turnstone node identity for the console's cluster view
Default node_id is {hostname}_{random-per-boot}, so every boot
inserted a new services row and the console probed stale ones (409
node_id mismatch -> 'not found' node pages). Pin TURNSTONE_NODE_ID
(one upserted row) and TURNSTONE_ADVERTISE_URL to loopback — the
guest's slirp IPv6 lands in the SSRF guard's never-allowed lane.
Also drop the futile /etc/hostname baking (podman regenerates it at
boco build).
2026-09-28 22:00:03 +02:00

73 lines
3.4 KiB
Bash
Executable File

#!/bin/bash
# VM entrypoint, exec'd as PID 1 by /boco/init (image runs as root).
# Starts sshd for ops access, prepares virtiofs share ownership, then
# drops to the marvin user and execs turnstone-server.
set -euo pipefail
export HOME=/home/marvin
# The boco kernel boots with hostname "(none)" (no systemd to read
# /etc/hostname) — set it live so turnstone node IDs and service URLs
# are sane (turnstone-vm_xxxx instead of (none)_xxxx). The Arch base
# has no `hostname` binary, so use the syscall via python.
python3 -c 'import socket; socket.sethostname("turnstone-vm")' || true
# sshd: host keys baked per-boot, ops channel on guest port 22
ssh-keygen -A
mkdir -p /run/sshd
/usr/sbin/sshd
# virtiofs shares appear root-owned (host dir is owned by the host user,
# which maps to guest root) — hand them to marvin so the server and
# agent tools can write. Also ensure ~/.config stays marvin-owned:
# boco-init creates mount-point dirs as root, and rootless podman
# refuses to run when $HOME/.config is not owned by the user.
mkdir -p /var/lib/turnstone /home/marvin/.config
chown marvin:marvin /var/lib/turnstone /home/marvin/.config || true
chown -R marvin:marvin /home/marvin/work || true
# Rootless container networking (pasta/slirp4netns) needs TUN access;
# the guest has no udev, so devtmpfs leaves it root-only.
if [ ! -e /dev/net/tun ]; then
mkdir -p /dev/net
[ -c /dev/net/tun ] || mknod /dev/net/tun c 10 200
fi
chmod 666 /dev/net/tun || true
# Seed the admin account (no-op when the user already exists — password
# is ignored on promotion). Runs as marvin so DB files it creates are
# marvin-owned. Override via image env or boot cmd:
# boco boot ... --cmd 'ADMIN_PASSWORD=... start-turnstone.sh'
ADMIN_USER="${ADMIN_USER:-marvin}"
ADMIN_PASSWORD="${ADMIN_PASSWORD:-marvin-turnstone}"
if [ "$ADMIN_PASSWORD" = "marvin-turnstone" ]; then
echo "start-turnstone: WARNING — default admin password in use; set ADMIN_PASSWORD to change it"
fi
setpriv --reuid=marvin --regid=marvin --init-groups \
/usr/local/bin/turnstone-admin create-admin \
--username "$ADMIN_USER" \
--password "$ADMIN_PASSWORD" \
|| echo "start-turnstone: admin seeding failed (continuing)"
# Console (cluster dashboard + admin panel) as a background daemon under
# marvin — PID 1 stays the server, so if the server dies the VM halts.
# Shares the same config.toml/database as the server (WAL multi-writer).
setpriv --reuid=marvin --regid=marvin --init-groups \
/usr/local/bin/turnstone-console --host 0.0.0.0 --port 8090 &
echo "start-turnstone: console starting on :8090"
# NOTE: turnstone reads $HOME/.config/turnstone/config.toml as marvin.
#
# Stable node identity: node_id defaults to {hostname}_{random-per-boot},
# so every boot inserts a NEW services row and the console probes stale
# ones (409 node_id mismatch → "unreachable"). Pin both:
# - TURNSTONE_NODE_ID: one registry row, upserted every boot
# - TURNSTONE_ADVERTISE_URL: loopback — the console's collector probes
# the registered URL, and the guest's slirp IPv6 (fec0::/fe80::)
# lands in the SSRF guard's never-allowed lane, which got the node
# refused outright.
export TURNSTONE_NODE_ID="${TURNSTONE_NODE_ID:-turnstone-vm-main}"
export TURNSTONE_ADVERTISE_URL="${TURNSTONE_ADVERTISE_URL:-http://127.0.0.1:8080}"
exec setpriv --reuid=marvin --regid=marvin --init-groups \
/usr/local/bin/turnstone-server --host 0.0.0.0 --port 8080