on a85287d help sign in
vibebox/src/ssh.sh
198 lines 5.2 kB history raw
1#!/bin/sh
2set -eu
4SSH_USER="__SSH_USER__"
5PROJECT_NAME="__PROJECT_NAME__"
6KEY_PATH="__KEY_PATH__"
8diag() { echo "[vibebox][diag] $*" >&2; }
10# Extract default route facts (no hardcode)
11default_route_line() { ip -4 route show default 2>/dev/null | head -n 1 || true; }
12default_dev() {
13 default_route_line | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}'
15default_gw() {
16 default_route_line | awk '{for(i=1;i<=NF;i++) if($i=="via"){print $(i+1); exit}}'
19dump_diag() {
20 diag "=== default route ==="
21 default_route_line >&2 || true
23 diag "=== systemctl status ssh ==="
24 systemctl status ssh --no-pager >&2 || true
26 diag "=== journalctl -u ssh (tail) ==="
27 journalctl -u ssh -n 120 --no-pager >&2 || true
29 diag "=== sshd -t ==="
30 sshd -t >&2 || true
32 diag "=== sshd -T (listen/addressfamily) ==="
33 sshd -T 2>/dev/null | egrep -i '^(listenaddress|addressfamily|port|permitrootlogin|allowusers)\b' >&2 || true
35 diag "=== listeners on :22 ==="
36 ss -lntp 2>/dev/null | awk 'NR==1 || $4 ~ /:22$/' >&2 || true
38 diag "=== ip -br addr ==="
39 ip -br addr >&2 || true
41 diag "=== ip route ==="
42 ip route >&2 || true
44 gw="$(default_gw || true)"
45 if [ -n "$gw" ]; then
46 diag "=== ping default gateway ($gw) ==="
47 ping -c1 -W1 "$gw" >/dev/null 2>&1 && diag "ping gw OK" || diag "ping gw FAIL"
48 fi
51# 1) tmpfs mount
52TARGET="/root/${PROJECT_NAME}/.vibebox"
53if [ -d "$TARGET" ] && ! mountpoint -q "$TARGET"; then
54 mount -t tmpfs tmpfs "$TARGET"
55fi
57# 2) user + authorized_keys
58if ! id -u "$SSH_USER" >/dev/null 2>&1; then
59 useradd -m -s /bin/bash -U "$SSH_USER"
60 usermod -aG sudo "$SSH_USER" || true
61fi
63install -d -m 700 -o "$SSH_USER" -g "$SSH_USER" "/home/${SSH_USER}/.ssh"
64install -m 600 -o "$SSH_USER" -g "$SSH_USER" "$KEY_PATH" "/home/${SSH_USER}/.ssh/authorized_keys"
66USER_HOME="$(getent passwd "$SSH_USER" | cut -d: -f6 2>/dev/null || true)"
67if [ -z "$USER_HOME" ]; then
68 USER_HOME="/home/${SSH_USER}"
69fi
71run_as_user() {
72 runuser -u "$SSH_USER" -- env HOME="$USER_HOME" "$@"
75# Ensure user cache dirs are writable (mise writes under ~/.cache/mise)
76install -d -m 700 -o "$SSH_USER" -g "$SSH_USER" "${USER_HOME}/.cache"
77install -d -m 700 -o "$SSH_USER" -g "$SSH_USER" "${USER_HOME}/.cache/mise"
78chown -R "$SSH_USER:$SSH_USER" "${USER_HOME}/.cache" 2>/dev/null || true
80# Home mount links (config-driven)
81__VIBEBOX_HOME_LINKS__
83# Vibebox shell commands
84install -d -m 755 /etc/profile.d
85cat > /etc/profile.d/vibebox.sh <<'VIBEBOX_SHELL_EOF'
86__VIBEBOX_SHELL_SCRIPT__
87VIBEBOX_SHELL_EOF
88chmod 644 /etc/profile.d/vibebox.sh
90if ! grep -q "vibebox-aliases" "${USER_HOME}/.bashrc" 2>/dev/null; then
91 {
92 echo ""
93 echo "# vibebox-aliases"
94 echo ". /etc/profile.d/vibebox.sh"
95 } >> "${USER_HOME}/.bashrc"
96fi
98# Install Mise
99MISE_BIN="${USER_HOME}/.local/bin/mise"
100if [ ! -x "$MISE_BIN" ] && ! command -v mise >/dev/null 2>&1; then
101 run_as_user sh -c 'curl https://mise.run | sh'
102fi
103echo 'eval "$(~/.local/bin/mise activate bash)"' >> "${USER_HOME}/.bashrc"
105export PATH="${USER_HOME}/.local/bin:/usr/local/bin:$PATH"
107mkdir -p "${USER_HOME}/.config/mise"
109cat > "${USER_HOME}/.config/mise/config.toml" <<MISE
110 [settings]
111 # Always use the venv created by uv, if available in directory
112 python.uv_venv_auto = true
113 experimental = true
114 idiomatic_version_file_enable_tools = ["rust"]
116 [tools]
117 uv = "0.9.25"
118 node = "24.13.0"
119 "npm:@openai/codex" = "latest"
120 "npm:@anthropic-ai/claude-code" = "latest"
121MISE
123touch "${USER_HOME}/.config/mise/mise.lock"
124if [ -x "$MISE_BIN" ]; then
125 run_as_user "$MISE_BIN" install
126else
127 run_as_user mise install
128fi
129chown -R "$SSH_USER:$SSH_USER" "${USER_HOME}/.cache" "${USER_HOME}/.config/mise" "${USER_HOME}/.local/share/mise" 2>/dev/null || true
131# 3) start ssh (don't swallow failures)
132# If ssh is already active, don't force start/restart.
133if ! systemctl is-active --quiet ssh; then
134 if ! systemctl start ssh; then
135 diag "systemctl start ssh failed"
136 dump_diag
137 exit 1
138 fi
139fi
141# 4) obtain stable IPv4 on the default-route interface (wait up to ~30s)
142ip_on_dev() {
143 dev="$1"
144 ip -4 -o addr show dev "$dev" scope global 2>/dev/null \
145 | awk '{print $4}' | cut -d/ -f1 | head -n 1 || true
148ip=""
149dev=""
150gw=""
151t=0
152while [ "$t" -lt 60 ]; do
153 dev="$(default_dev || true)"
154 gw="$(default_gw || true)"
155 if [ -n "$dev" ]; then
156 ip="$(ip_on_dev "$dev")"
157 else
158 ip=""
159 fi
161 if [ -n "$dev" ] && [ -n "$ip" ]; then
162 # optional: if a gateway exists, require it to answer to avoid "ip exists but link dead"
163 if [ -z "$gw" ] || ping -c1 -W1 "$gw" >/dev/null 2>&1; then
164 break
165 fi
166 fi
168 t=$((t+1))
169 sleep 0.5
170done
172if [ -z "$dev" ] || [ -z "$ip" ]; then
173 diag "no stable IPv4 on default route interface"
174 dump_diag
175 exit 1
176fi
178# 5) strong verify: ssh must listen externally (0.0.0.0:22 or $ip:22 or [::]:22)
179listens_ok() {
180 ss -lnt 2>/dev/null \
181 | awk 'NR>1 {print $4}' \
182 | grep -Eq "^(0\.0\.0\.0:22|\\[::\\]:22|${ip}:22)$"
185i=0
186while ! listens_ok && [ "$i" -lt 80 ]; do # ~8s
187 i=$((i+1))
188 sleep 0.1
189done
191if ! listens_ok; then
192 diag "sshd not listening on 0.0.0.0:22 / ${ip}:22"
193 dump_diag
194 exit 1
195fi
197echo VIBEBOX_SSH_READY
198echo "VIBEBOX_IPV4=$ip"