-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserve.sh
More file actions
201 lines (186 loc) · 9.31 KB
/
Copy pathserve.sh
File metadata and controls
201 lines (186 loc) · 9.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#!/usr/bin/env bash
# Author: Nima Shafie
# =============================================================================
# serve.sh -- host the DevKit Manager for a whole team (Mode 1: shared server)
#
# Runs the devkit-ui bound to a network interface so team members can reach it
# from their own machines, prints the token-authenticated access URL to share,
# and (optionally) enables HTTPS. Tools installed through this server land on
# THIS host — see docs/DEPLOYMENT.md for the shared-host model.
#
# For a single user with no admin rights, use scripts/launch.sh instead
# (localhost, per-user install). See docs/DEPLOYMENT.md Mode 2.
#
# USAGE:
# bash scripts/serve.sh --tls # HTTPS on all interfaces
# bash scripts/serve.sh --port 9090 --tls # HTTPS on a custom port
# bash scripts/serve.sh --advertise devbox.corp.local --tls
#
# Binding to a network interface without --tls is refused unless you pass
# --insecure, so the access token is never sent in the clear by default.
#
# OPTIONS:
# --host <addr> Interface to bind (default: 0.0.0.0 = all interfaces)
# --advertise <name> Hostname/IP to put in the shared URL
# (default: auto-detected LAN address)
# --port <n> Port (default: devkit.config.json port, else 9090)
# --tls Serve HTTPS with an auto-generated self-signed cert
# --insecure Allow plaintext HTTP on a network interface (trusted LAN)
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BIND_HOST="0.0.0.0"
ADVERTISE=""
PORT=""
TLS=false
INSECURE=false
PASS_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--host) BIND_HOST="$2"; shift 2 ;;
--advertise) ADVERTISE="$2"; shift 2 ;;
--port) PORT="$2"; shift 2 ;;
--tls) TLS=true; shift ;;
--insecure) INSECURE=true; shift ;;
-h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) PASS_ARGS+=("$1"); shift ;;
esac
done
# --- Effective port: --port > config > 9090 -------------------------------
if [[ -z "$PORT" ]]; then
PORT="$(grep -oE '"port"[[:space:]]*:[[:space:]]*[0-9]+' "${REPO_ROOT}/devkit.config.json" 2>/dev/null \
| grep -oE '[0-9]+$' | head -1)"
PORT="${PORT:-9090}"
fi
# --- Ensure a stable auth token exists so we can print the URL up front ----
TOKEN_FILE="${REPO_ROOT}/.devkit-token"
if [[ ! -s "$TOKEN_FILE" ]]; then
if command -v openssl &>/dev/null; then
TOKEN="$(openssl rand -hex 32)"
elif [[ -r /dev/urandom ]]; then
TOKEN="$(head -c32 /dev/urandom | od -An -tx1 | tr -d ' \n')"
else
echo "ERROR: cannot generate a token (no openssl or /dev/urandom)." >&2; exit 1
fi
printf '%s\n' "$TOKEN" > "$TOKEN_FILE"
chmod 600 "$TOKEN_FILE" 2>/dev/null || true
fi
TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
# --- Figure out a reachable address for the shared URL --------------------
detect_ip() {
local ip=""
if command -v hostname &>/dev/null && hostname -I >/dev/null 2>&1; then
ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
fi
if [[ -z "$ip" ]] && command -v ipconfig >/dev/null 2>&1; then
ip="$(ipconfig 2>/dev/null | grep -iE 'IPv4' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -v '^127\.' | head -1)"
fi
[[ -z "$ip" ]] && ip="$(hostname 2>/dev/null || echo '<server-address>')"
echo "$ip"
}
[[ -z "$ADVERTISE" ]] && ADVERTISE="$(detect_ip)"
SCHEME="http"; $TLS && SCHEME="https"
# Refuse to expose the UI unencrypted on a network interface unless the operator
# explicitly opts in. A team server binds beyond loopback, so plaintext there
# would put the access token on the wire for anyone on the segment.
case "$BIND_HOST" in
127.0.0.1|localhost|::1|"") LOOPBACK=true ;;
*) LOOPBACK=false ;;
esac
if ! $TLS && ! $LOOPBACK && ! $INSECURE; then
echo "ERROR: refusing to serve unencrypted HTTP on ${BIND_HOST} (a network interface)." >&2
echo " Add --tls to enable HTTPS, or --insecure to override on a trusted network." >&2
exit 1
fi
ACCESS_URL="${SCHEME}://${ADVERTISE}:${PORT}/auth/bootstrap?devkit_token=${TOKEN}&next=/"
# The access URL embeds the token, so never emit it where a log collector would
# capture it (e.g. the systemd journal). Write it to an owner-only file, and
# print it to the console only when attached to an interactive terminal.
URL_FILE="${REPO_ROOT}/.devkit-access-url"
( umask 077; printf '%s\n' "$ACCESS_URL" > "$URL_FILE" )
chmod 600 "$URL_FILE" 2>/dev/null || true
echo ""
echo "================================================================================"
echo " airgap-cpp-devkit -- Team Server (Mode 1)"
echo "================================================================================"
echo " Binding : ${BIND_HOST}:${PORT} (${SCHEME})"
if [[ -t 1 ]]; then
echo " Share this with your team (token-authenticated, one click):"
echo ""
echo " ${ACCESS_URL}"
else
echo " Access URL (contains the token) written to an owner-only file:"
echo " ${URL_FILE}"
echo " Reveal it with: cat ${URL_FILE}"
fi
echo ""
echo " Notes:"
echo " - Tools installed via this UI land on THIS host (shared-host model)."
echo " - The token grants access. Rotate by deleting .devkit-token."
$TLS || echo " - Serving unencrypted HTTP (--insecure). Add --tls for HTTPS on untrusted networks."
echo " - Ctrl+C to stop."
echo "================================================================================"
echo ""
# --- Best-effort firewall reachability check --------------------------------
# A team server binds beyond loopback, but the host firewall may still drop the
# port — so the shareable URL just printed can be unreachable from a LAN/VLAN
# peer with no hint as to why. Probe the common Linux firewalls and warn (never
# fail: the check is advisory and cannot see upstream network ACLs).
_warn_firewall() {
$LOOPBACK && return 0 # loopback bind is never firewalled
case "$BIND_HOST" in *:*) return 0 ;; esac # skip IPv6 literal parsing
if command -v firewall-cmd &>/dev/null; then
# Capture the status with `|| st=$?` rather than a bare command + `$?`:
# under `set -euo pipefail` a bare non-zero-returning command aborts the
# whole script, so the old form killed serve.sh before it ever launched
# whenever firewalld returned non-zero (252 stopped, 253 polkit-denied) —
# exactly the non-root operators this check exists for.
local st=0
firewall-cmd --state &>/dev/null || st=$?
# firewall-cmd exit codes: 0 = running; 252 = not running; 253 = running
# but this user is NOT authorized to query it (polkit). The non-root
# operators who actually run serve.sh hit 253 — the old `&& firewall-cmd
# --state` gate silently skipped them, so the warning never fired.
if [[ $st -eq 252 ]]; then
return 0 # firewalld not running — nothing to warn about
fi
# An assignment from a command substitution takes the substitution's exit
# status, so a closed port (rc 1) would likewise abort under set -e — hence
# the same `|| rc=$?` guard here.
local out rc=0
out="$(firewall-cmd --query-port="${PORT}/tcp" 2>&1)" || rc=$?
# Select the message by EXIT CODE, not by matching English text: rc 253 is
# firewalld's "not authorized to query" (polkit), and firewall-cmd is
# localized — a text grep for 'authoriz' misfires on a non-English host and
# would wrongly tell the operator the port is closed. Keep the text match as
# a secondary heuristic for any other non-zero rc.
if [[ $rc -eq 0 ]]; then
return 0 # port is explicitly open
elif [[ $rc -eq 253 ]] || printf '%s' "$out" | grep -qiE 'authoriz'; then
echo " [??] firewalld is active but this user isn't authorized to query it."
echo " If ${PORT}/tcp is closed, LAN peers can't reach the URL above — can't tell from here."
echo " Check as admin: sudo firewall-cmd --query-port=${PORT}/tcp"
echo " Open it: sudo firewall-cmd --add-port=${PORT}/tcp (add --permanent to persist)"
echo ""
else
echo " [!!] firewalld is active and port ${PORT}/tcp is not open."
echo " Team members on the LAN may not be able to reach the URL above."
echo " Open it (as admin): firewall-cmd --add-port=${PORT}/tcp (add --permanent to persist)"
echo ""
fi
return 0
fi
if command -v ufw &>/dev/null && ufw status 2>/dev/null | grep -qi 'Status: active'; then
if ! ufw status 2>/dev/null | grep -qE "(^|[[:space:]])${PORT}(/tcp)?[[:space:]]+(ALLOW|ACCEPT)"; then
echo " [!!] ufw is active and port ${PORT}/tcp does not appear open."
echo " Team members on the LAN may not be able to reach the URL above."
echo " Open it (as admin): ufw allow ${PORT}/tcp"
echo ""
fi
fi
}
_warn_firewall
LAUNCH_ARGS=(--host "$BIND_HOST" --port "$PORT" --no-browser)
$TLS && LAUNCH_ARGS+=(--tls)
exec bash "${SCRIPT_DIR}/launch.sh" "${LAUNCH_ARGS[@]}" "${PASS_ARGS[@]+"${PASS_ARGS[@]}"}"