mirror of
https://github.com/robcholz/vibebox.git
synced 2026-04-01 00:10:15 +02:00
226 lines
5.6 KiB
Bash
226 lines
5.6 KiB
Bash
#!/bin/sh
|
|
set -eu
|
|
|
|
SSH_USER="__SSH_USER__"
|
|
PROJECT_NAME="__PROJECT_NAME__"
|
|
PROJECT_GUEST_DIR="__PROJECT_GUEST_DIR__"
|
|
KEY_PATH="__KEY_PATH__"
|
|
|
|
diag() { echo "[vibebox][diag] $*" >&2; }
|
|
|
|
# Extract default route facts (no hardcode)
|
|
default_route_line() { ip -4 route show default 2>/dev/null | head -n 1 || true; }
|
|
default_dev() {
|
|
default_route_line | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}'
|
|
}
|
|
default_gw() {
|
|
default_route_line | awk '{for(i=1;i<=NF;i++) if($i=="via"){print $(i+1); exit}}'
|
|
}
|
|
|
|
dump_diag() {
|
|
diag "=== default route ==="
|
|
default_route_line >&2 || true
|
|
|
|
diag "=== systemctl status ssh ==="
|
|
systemctl status ssh --no-pager >&2 || true
|
|
|
|
diag "=== journalctl -u ssh (tail) ==="
|
|
journalctl -u ssh -n 120 --no-pager >&2 || true
|
|
|
|
diag "=== sshd -t ==="
|
|
sshd -t >&2 || true
|
|
|
|
diag "=== sshd -T (listen/addressfamily) ==="
|
|
sshd -T 2>/dev/null | egrep -i '^(listenaddress|addressfamily|port|permitrootlogin|allowusers)\b' >&2 || true
|
|
|
|
diag "=== listeners on :22 ==="
|
|
ss -lntp 2>/dev/null | awk 'NR==1 || $4 ~ /:22$/' >&2 || true
|
|
|
|
diag "=== ip -br addr ==="
|
|
ip -br addr >&2 || true
|
|
|
|
diag "=== ip route ==="
|
|
ip route >&2 || true
|
|
|
|
gw="$(default_gw || true)"
|
|
if [ -n "$gw" ]; then
|
|
diag "=== ping default gateway ($gw) ==="
|
|
ping -c1 -W1 "$gw" >/dev/null 2>&1 && diag "ping gw OK" || diag "ping gw FAIL"
|
|
fi
|
|
}
|
|
|
|
# 1) tmpfs mount
|
|
TARGET="${PROJECT_GUEST_DIR}/.vibebox"
|
|
if [ -d "$TARGET" ] && ! mountpoint -q "$TARGET"; then
|
|
mount -t tmpfs tmpfs "$TARGET"
|
|
fi
|
|
|
|
# 2) user + authorized_keys
|
|
if ! id -u "$SSH_USER" >/dev/null 2>&1; then
|
|
useradd -m -s /bin/bash -U "$SSH_USER"
|
|
usermod -aG sudo "$SSH_USER" || true
|
|
fi
|
|
|
|
install -d -m 700 -o "$SSH_USER" -g "$SSH_USER" "/home/${SSH_USER}/.ssh"
|
|
install -m 600 -o "$SSH_USER" -g "$SSH_USER" "$KEY_PATH" "/home/${SSH_USER}/.ssh/authorized_keys"
|
|
|
|
USER_HOME="$(getent passwd "$SSH_USER" | cut -d: -f6 2>/dev/null || true)"
|
|
if [ -z "$USER_HOME" ]; then
|
|
USER_HOME="/home/${SSH_USER}"
|
|
fi
|
|
|
|
# Home mount links (config-driven)
|
|
__VIBEBOX_HOME_LINKS__
|
|
|
|
# Vibebox shell commands
|
|
install -d -m 755 /etc/profile.d
|
|
cat > /etc/profile.d/vibebox.sh <<'VIBEBOX_SHELL_EOF'
|
|
__VIBEBOX_SHELL_SCRIPT__
|
|
VIBEBOX_SHELL_EOF
|
|
chmod 644 /etc/profile.d/vibebox.sh
|
|
|
|
# Auto-cd into project for interactive shells
|
|
cat > /etc/profile.d/vibebox-project.sh <<'VIBEBOX_PROJECT_EOF'
|
|
case "$-" in
|
|
*i*)
|
|
project_home="${HOME}/__PROJECT_NAME__"
|
|
if [ "$USER" = "__SSH_USER__" ] && [ -d "$project_home" ]; then
|
|
cd "$project_home"
|
|
elif [ "$USER" = "__SSH_USER__" ] && [ -d "__PROJECT_GUEST_DIR__" ]; then
|
|
cd "__PROJECT_GUEST_DIR__"
|
|
fi
|
|
;;
|
|
esac
|
|
VIBEBOX_PROJECT_EOF
|
|
chmod 644 /etc/profile.d/vibebox-project.sh
|
|
|
|
if ! grep -q "vibebox-aliases" "${USER_HOME}/.bashrc" 2>/dev/null; then
|
|
{
|
|
echo ""
|
|
echo "# vibebox-aliases"
|
|
echo ". /etc/profile.d/vibebox.sh"
|
|
} >> "${USER_HOME}/.bashrc"
|
|
fi
|
|
|
|
# Install Mise
|
|
MISE_BIN="${USER_HOME}/.local/bin/mise"
|
|
mise_warn() { echo "[mise] $*" >&2; }
|
|
mise_ok() { command -v mise >/dev/null 2>&1 || [ -x "$MISE_BIN" ]; }
|
|
mise_install() {
|
|
if [ ! -x "$MISE_BIN" ] && ! command -v mise >/dev/null 2>&1; then
|
|
if ! curl https://mise.run | HOME="$USER_HOME" sh; then
|
|
mise_warn "mise install script failed (continuing)"
|
|
return 0
|
|
fi
|
|
fi
|
|
echo 'eval "$(~/.local/bin/mise activate bash)"' >> "${USER_HOME}/.bashrc"
|
|
|
|
export PATH="${USER_HOME}/.local/bin:/usr/local/bin:$PATH"
|
|
|
|
mkdir -p "${USER_HOME}/.config/mise"
|
|
|
|
cat > "${USER_HOME}/.config/mise/config.toml" <<MISE
|
|
[settings]
|
|
# Always use the venv created by uv, if available in directory
|
|
python.uv_venv_auto = true
|
|
experimental = true
|
|
idiomatic_version_file_enable_tools = ["rust"]
|
|
|
|
[tools]
|
|
uv = "0.9.25"
|
|
node = "24.13.0"
|
|
"npm:@openai/codex" = "latest"
|
|
"npm:@anthropic-ai/claude-code" = "latest"
|
|
MISE
|
|
|
|
touch "${USER_HOME}/.config/mise/mise.lock"
|
|
if [ -x "$MISE_BIN" ]; then
|
|
if ! HOME="$USER_HOME" "$MISE_BIN" install; then
|
|
mise_warn "mise install failed (continuing)"
|
|
return 0
|
|
fi
|
|
else
|
|
if ! HOME="$USER_HOME" mise install; then
|
|
mise_warn "mise install failed (continuing)"
|
|
return 0
|
|
fi
|
|
fi
|
|
}
|
|
|
|
mise_install || true
|
|
|
|
# 3) start ssh (don't swallow failures)
|
|
# If ssh is already active, don't force start/restart.
|
|
if ! systemctl is-active --quiet ssh; then
|
|
if ! systemctl start ssh; then
|
|
diag "systemctl start ssh failed"
|
|
dump_diag
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# 4) obtain stable IPv4 on the default-route interface (wait up to ~30s)
|
|
ip_on_dev() {
|
|
dev="$1"
|
|
ip -4 -o addr show dev "$dev" scope global 2>/dev/null \
|
|
| awk '{print $4}' | cut -d/ -f1 | head -n 1 || true
|
|
}
|
|
|
|
ip=""
|
|
dev=""
|
|
gw=""
|
|
t=0
|
|
while [ "$t" -lt 60 ]; do
|
|
dev="$(default_dev || true)"
|
|
gw="$(default_gw || true)"
|
|
if [ -n "$dev" ]; then
|
|
ip="$(ip_on_dev "$dev")"
|
|
else
|
|
ip=""
|
|
fi
|
|
|
|
if [ -n "$dev" ] && [ -n "$ip" ]; then
|
|
# optional: if a gateway exists, require it to answer to avoid "ip exists but link dead"
|
|
if [ -z "$gw" ] || ping -c1 -W1 "$gw" >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
fi
|
|
|
|
t=$((t+1))
|
|
sleep 0.5
|
|
done
|
|
|
|
if [ -z "$dev" ] || [ -z "$ip" ]; then
|
|
diag "no stable IPv4 on default route interface"
|
|
dump_diag
|
|
exit 1
|
|
fi
|
|
|
|
# 5) strong verify: ssh must listen externally (0.0.0.0:22 or $ip:22 or [::]:22)
|
|
listens_ok() {
|
|
ss -lnt 2>/dev/null \
|
|
| awk 'NR>1 {print $4}' \
|
|
| grep -Eq "^(0\.0\.0\.0:22|\\[::\\]:22|${ip}:22)$"
|
|
}
|
|
|
|
i=0
|
|
while ! listens_ok && [ "$i" -lt 80 ]; do # ~8s
|
|
i=$((i+1))
|
|
sleep 0.1
|
|
done
|
|
|
|
if ! listens_ok; then
|
|
diag "sshd not listening on 0.0.0.0:22 / ${ip}:22"
|
|
dump_diag
|
|
exit 1
|
|
fi
|
|
|
|
ip a
|
|
ip link
|
|
curl -s https://api.ipify.org ; echo
|
|
|
|
cat /etc/machine-id
|
|
|
|
echo VIBEBOX_SSH_READY
|
|
echo "VIBEBOX_IPV4=$ip"
|