feat(bootstrap): 安装结束后引导进入设置向导
This commit is contained in:
4 files changed
+188
-15
No files matched your search
+85
-13
@@ -32,6 +32,9 @@
|
||||
# export FELIS_INSTALL_MODE=nano; curl -fsSL <raw-url>/deploy/bootstrap.sh | sudo -E bash
|
||||
# FELIS_INSTALL_MODE full|nano — skip the prompt (default: ask on a tty, else full; nano
|
||||
# instead on a host that runs felis-nano and no full install)
|
||||
# FELIS_NO_SETUP 1 ends a full install at its summary. By default an install that
|
||||
# leaves no Owner account goes on into `felis setup` when it runs on
|
||||
# a terminal
|
||||
# FELIS_NANO_LISTEN listen addr for `felis nano` (default: the address an installed
|
||||
# felis-nano already uses, else 127.0.0.1:8081 — loopback only; set a
|
||||
# private-network IP to serve an off-host proxy)
|
||||
@@ -440,6 +443,10 @@ OFFSITE_ENV="${STATE_DIR}/offsite.env"
|
||||
# Where summary_offsite shows a newly generated off-site key: the operator's terminal alone.
|
||||
# stdout and stderr are what `2>&1 | tee install.log`, cloud-init and CI keep on disk.
|
||||
OFFSITE_KEY_TTY=/dev/tty
|
||||
# Where the setup console the installer starts at the end reads its keys (setup_terminal).
|
||||
SETUP_TTY=/dev/tty
|
||||
# Set by summary_next when the installer goes on into the setup console.
|
||||
SETUP_CONSOLE=0
|
||||
# Host copies of the credentials `felis setup` takes at the keyboard, one bare value per
|
||||
# file, mode 0600 (cmd/felis/hostcreds.go); apply_setup_credential_secrets applies their
|
||||
# Secrets from them on every run.
|
||||
@@ -588,13 +595,10 @@ on_error() {
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
local status=$? id path unit
|
||||
local status=$? id path
|
||||
restore_previous_host_binary "$status"
|
||||
if [ "$status" -ne 0 ]; then undo_postgres_move; fi
|
||||
for unit in "${PKG_TIMERS_TO_RESTORE[@]-}"; do
|
||||
[ -n "$unit" ] || continue
|
||||
systemctl start "$unit" >/dev/null 2>&1 || true
|
||||
done
|
||||
resume_package_background_timers
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
for id in "${DOCKER_CONTAINERS[@]-}"; do
|
||||
[ -n "$id" ] && docker rm "$id" >/dev/null 2>&1 || true
|
||||
@@ -828,6 +832,17 @@ pause_package_background_timers() {
|
||||
done
|
||||
}
|
||||
|
||||
# Starts the timers pause_package_background_timers stopped: from the EXIT cleanup, and
|
||||
# before the setup console, which stays open as long as the operator likes.
|
||||
resume_package_background_timers() {
|
||||
local unit
|
||||
for unit in "${PKG_TIMERS_TO_RESTORE[@]-}"; do
|
||||
[ -n "$unit" ] || continue
|
||||
systemctl start "$unit" >/dev/null 2>&1 || true
|
||||
done
|
||||
PKG_TIMERS_TO_RESTORE=()
|
||||
}
|
||||
|
||||
pkg_lock_files() {
|
||||
case "${PKG:-}" in
|
||||
apt) printf '%s\n' "${APT_LOCK_FILES[@]}" ;;
|
||||
@@ -5675,14 +5690,6 @@ summary() {
|
||||
log "The proxy authenticates against Mojang and forwards the verified profile to the"
|
||||
log "login gate; the backends are reachable in-cluster only. Follow it with:"
|
||||
log " sudo journalctl -u felis-velocity -f"
|
||||
if [ "${FELIS_BOOTSTRAP_FROM_TUI:-}" = "1" ]; then
|
||||
log "Returning to the setup console to create the Owner account and verify panel access."
|
||||
else
|
||||
log "Next: run 'sudo felis setup' on this host to create the Owner account."
|
||||
fi
|
||||
log "setup provisions the login/lobby servers, then asks the Owner to bind by joining"
|
||||
log "the proxy in Minecraft — that is what makes the Owner's admin identity a real"
|
||||
log "Mojang account rather than a password."
|
||||
log "Use 'sudo felis breakGlass' only for emergency local Owner recovery/reset."
|
||||
if [ -n "${PREVIOUS_FELIS_IMAGE:-}" ]; then
|
||||
echo
|
||||
@@ -5696,6 +5703,70 @@ summary() {
|
||||
summary_alerts
|
||||
summary_heartbeat
|
||||
echo
|
||||
summary_next
|
||||
echo
|
||||
}
|
||||
|
||||
# owner_state: whether the database holds a staff account (an Owner or an Admin), the test
|
||||
# `felis setup` makes to choose between the Owner wizard and its status screen (AdminExists
|
||||
# in internal/api/pgrepo.go). "unknown" when the database does not answer.
|
||||
owner_state() {
|
||||
local out
|
||||
out="$(pg_exec psql -XtA -U postgres -d "$DB_NAME" -c "SELECT EXISTS (SELECT 1 FROM users WHERE role IN ('admin', 'owner'))" 2>/dev/null || true)"
|
||||
case "$out" in
|
||||
t) echo yes ;;
|
||||
f) echo no ;;
|
||||
*) echo unknown ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# setup_terminal: whether the full-screen setup console has a terminal to draw on and to
|
||||
# read keys from. Under `curl | sudo bash` stdin is the script, so the console reads
|
||||
# SETUP_TTY; stdout must be the terminal itself, which `| tee install.log`, cloud-init and
|
||||
# CI are not. /dev/tty is mode 0666 everywhere, so only opening it tells.
|
||||
setup_terminal() { [ -t 1 ] && (: <"$SETUP_TTY") 2>/dev/null; }
|
||||
|
||||
# summary_next is the installer's last word: what the operator does now. It comes after the
|
||||
# warnings, so it is what the terminal is left showing. Until a staff account exists that is
|
||||
# `felis setup`, which creates the Owner; on a terminal the installer starts it itself
|
||||
# (start_setup_console), as the README's install line promises. With an Owner in place it
|
||||
# is the address to sign in at. Under felis setup the console carries on by itself.
|
||||
summary_next() {
|
||||
local owner rule="================================================================================"
|
||||
if bootstrap_from_tui; then
|
||||
log "Returning to the setup console to create the Owner account and verify panel access."
|
||||
return 0
|
||||
fi
|
||||
owner="$(owner_state)"
|
||||
log "$rule"
|
||||
if [ "$owner" = yes ]; then
|
||||
log "Felis is running. Sign in at https://$(auth_hostname admin_hostname "op.console.${FELIS_ROOT_DOMAIN}")"
|
||||
log "(https://${NODE_IP}:${FELIS_PANEL_NODEPORT} until the edge routes it there)."
|
||||
log "Email, edge and storage settings: sudo felis setup"
|
||||
log "$rule"
|
||||
return 0
|
||||
fi
|
||||
if [ "$owner" = no ] && [ -z "${FELIS_NO_SETUP:-}" ] && setup_terminal; then
|
||||
SETUP_CONSOLE=1
|
||||
log "Next: create the Owner account. The setup console starts now; if you leave it,"
|
||||
log "run sudo felis setup to come back to it."
|
||||
else
|
||||
log "Next: create the Owner account. Run on this host:"
|
||||
log " sudo felis setup"
|
||||
fi
|
||||
log "It starts the login and lobby servers, has you join ${NODE_IP}:${FELIS_GAME_PORT} in Minecraft to"
|
||||
log "bind your Mojang account as the Owner, then sets up how the panel is reached."
|
||||
log "$rule"
|
||||
}
|
||||
|
||||
# start_setup_console runs `felis setup` when summary_next said it would. The install has
|
||||
# succeeded by then, so the console's own failure never fails the run: the EXIT cleanup
|
||||
# would take that for a failed install and undo the database move.
|
||||
start_setup_console() {
|
||||
[ "$SETUP_CONSOLE" = 1 ] || return 0
|
||||
resume_package_background_timers
|
||||
"$HOST_BIN" setup <"$SETUP_TTY" \
|
||||
|| warn "the setup console exited with status $?; run 'sudo felis setup' to come back to it"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -6204,6 +6275,7 @@ main() {
|
||||
install_watchdog_timer
|
||||
mark_bootstrap_done
|
||||
summary
|
||||
start_setup_console
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -2428,10 +2428,107 @@ case "$(awk '/^validate_settings\(\) \{/,/^}/' "$BS")" in
|
||||
*) echo "FAIL validate_settings must call validate_heartbeat_url"; fails=$((fails + 1)) ;;
|
||||
esac
|
||||
case "$(awk '/^summary\(\) \{/,/^}/' "$BS")" in
|
||||
*summary_offsite*summary_alerts*summary_heartbeat*) echo "PASS the install's summary ends on the alerts, then the heartbeat" ;;
|
||||
*) echo "FAIL summary must call summary_alerts, then summary_heartbeat"; fails=$((fails + 1)) ;;
|
||||
*summary_offsite*summary_alerts*summary_heartbeat*summary_next*) echo "PASS the install's summary ends on the alerts, the heartbeat, then the next step" ;;
|
||||
*) echo "FAIL summary must call summary_alerts, summary_heartbeat, then summary_next"; fails=$((fails + 1)) ;;
|
||||
esac
|
||||
|
||||
# --- the installer's last word: what the operator does next ------------------------------
|
||||
# An install that leaves no Owner ends on `felis setup`, and starts it when there is a
|
||||
# terminal for it; one with an Owner ends on where to sign in. The setup console must never
|
||||
# start into a log (`| tee`, cloud-init, CI), and its own failure must not fail the install.
|
||||
case "$(awk '/^main\(\) \{/,/^}/' "$BS" | tail -n 3)" in
|
||||
" summary
|
||||
start_setup_console
|
||||
}") echo "PASS the full install ends on the summary, then the setup console" ;;
|
||||
*) echo "FAIL main must end with summary, then start_setup_console"; fails=$((fails + 1)) ;;
|
||||
esac
|
||||
nextblock=""
|
||||
for fn in bootstrap_from_tui resume_package_background_timers owner_state setup_terminal summary_next start_setup_console; do
|
||||
# A one-line function ends on its own line.
|
||||
b="$(awk -v fn="$fn" '$0 ~ "^" fn "\\(\\) \\{" { print; if (/\}$/) exit; on = 1; next } on { print } on && /^}/ { exit }' "$BS")"
|
||||
[ -n "$b" ] || { echo "FAIL: no ${fn} found in $BS"; exit 1; }
|
||||
[ "$(printf '%s\n' "$b" | wc -l)" -lt 40 ] \
|
||||
|| { echo "FAIL: the extracted block is not ${fn} -- did its closing brace move?"; exit 1; }
|
||||
nextblock="${nextblock}${b}
|
||||
"
|
||||
done
|
||||
next_dir="$(mktemp -d)"
|
||||
printf 'keys from the terminal\n' > "$next_dir/tty"
|
||||
cat > "$next_dir/felis" <<'XEOF'
|
||||
#!/bin/sh
|
||||
echo "felis $*" >> "$NEXT_JOURNAL"
|
||||
cat > "$NEXT_STDIN"
|
||||
exit "${SETUP_EXIT:-0}"
|
||||
XEOF
|
||||
chmod +x "$next_dir/felis"
|
||||
# run_next PG_ANSWER: summary_next then start_setup_console under the installer's own shell
|
||||
# options and ERR trap. PG_ANSWER is what psql prints, or "fail". TERMINAL=1 stands in for
|
||||
# a terminal on stdout, which a test's captured output never is.
|
||||
run_next() {
|
||||
: > "$next_dir/journal"
|
||||
: > "$next_dir/stdin"
|
||||
PG_ANSWER="$1" NEXT_JOURNAL="$next_dir/journal" NEXT_STDIN="$next_dir/stdin" \
|
||||
HOST_BIN="$next_dir/felis" SETUP_TTY="$next_dir/tty" bash -c '
|
||||
set -Eeuo pipefail
|
||||
trap "echo TRAP" ERR
|
||||
log() { printf "LOG: %s\n" "$*"; }; warn() { printf "WARN: %s\n" "$*"; }
|
||||
auth_hostname() { printf "%s" "$2"; }
|
||||
pg_exec() { echo "pg_exec $*" >> "$NEXT_JOURNAL"; [ "$PG_ANSWER" != fail ] || return 1; echo "$PG_ANSWER"; }
|
||||
systemctl() { echo "systemctl $*" >> "$NEXT_JOURNAL"; }
|
||||
DB_NAME=felis FELIS_ROOT_DOMAIN=example.net NODE_IP=10.0.0.5 FELIS_PANEL_NODEPORT=30443 FELIS_GAME_PORT=25565
|
||||
SETUP_CONSOLE=0
|
||||
PKG_TIMERS_TO_RESTORE=(apt-daily.timer)
|
||||
'"$nextblock"'
|
||||
if [ "${TERMINAL:-}" = 1 ]; then setup_terminal() { return 0; }; fi
|
||||
summary_next
|
||||
start_setup_console
|
||||
echo "exit 0"' 2>&1
|
||||
}
|
||||
started() { grep -q '^felis setup$' "$next_dir/journal"; }
|
||||
|
||||
out="$(TERMINAL=1 run_next t)"
|
||||
expect "with an Owner the summary ends on where to sign in" "LOG: Felis is running. Sign in at https://op.console.example.net" "$out"
|
||||
expect " and where to change settings" "LOG: Email, edge and storage settings: sudo felis setup" "$out"
|
||||
if started; then echo "FAIL with an Owner the setup console must not start: $out"; fails=$((fails + 1)); else echo "PASS and the setup console does not start"; fi
|
||||
admin_roles="$(grep -o "role IN ('admin', 'owner')" "$(dirname "$BS")/../internal/api/pgrepo.go" | head -n 1)"
|
||||
expect "the Owner test is felis setup's own (AdminExists)" "${admin_roles:-AdminExists query not found}" "$(cat "$next_dir/journal")"
|
||||
|
||||
out="$(TERMINAL=1 run_next f)"
|
||||
expect "with no Owner on a terminal the summary says the setup console starts" "LOG: Next: create the Owner account. The setup console starts now" "$out"
|
||||
expect " and how it binds the Owner" "has you join 10.0.0.5:25565 in Minecraft" "$out"
|
||||
if started; then echo "PASS and starts it"; else echo "FAIL the setup console did not start: $out"; fails=$((fails + 1)); fi
|
||||
expect " on the terminal's keys" "keys from the terminal" "$(cat "$next_dir/stdin")"
|
||||
case "$(cat "$next_dir/journal")" in
|
||||
*"systemctl start apt-daily.timer"*"felis setup"*) echo "PASS after the package timers are back" ;;
|
||||
*) echo "FAIL the package timers must restart before the setup console: $(cat "$next_dir/journal")"; fails=$((fails + 1)) ;;
|
||||
esac
|
||||
expect " and the install ends cleanly" "exit 0" "$out"
|
||||
|
||||
out="$(run_next f)"
|
||||
expect "with no Owner and no terminal the summary names the command" "LOG: sudo felis setup" "$out"
|
||||
if started; then echo "FAIL with no terminal the setup console must not start: $out"; fails=$((fails + 1)); else echo "PASS and the setup console does not start into the log"; fi
|
||||
|
||||
out="$(TERMINAL=1 FELIS_NO_SETUP=1 run_next f)"
|
||||
expect "FELIS_NO_SETUP names the command" "LOG: sudo felis setup" "$out"
|
||||
if started; then echo "FAIL FELIS_NO_SETUP must keep the setup console closed: $out"; fails=$((fails + 1)); else echo "PASS and keeps the setup console closed"; fi
|
||||
|
||||
out="$(TERMINAL=1 run_next fail)"
|
||||
expect "a database that does not answer names the command" "LOG: sudo felis setup" "$out"
|
||||
if started; then echo "FAIL an unknown Owner must not start the setup console: $out"; fails=$((fails + 1)); else echo "PASS and the setup console does not start"; fi
|
||||
|
||||
out="$(TERMINAL=1 FELIS_BOOTSTRAP_FROM_TUI=1 run_next f)"
|
||||
expect "under felis setup the console carries on" "LOG: Returning to the setup console to create the Owner account" "$out"
|
||||
if started; then echo "FAIL under felis setup a second console must not start: $out"; fails=$((fails + 1)); else echo "PASS and no second console starts"; fi
|
||||
|
||||
out="$(TERMINAL=1 SETUP_EXIT=3 run_next f)"
|
||||
expect "a setup console that fails says how to come back" "WARN: the setup console exited with status 3; run 'sudo felis setup' to come back to it" "$out"
|
||||
expect " and the install still succeeds" "exit 0" "$out"
|
||||
case "$out" in
|
||||
*TRAP*) echo "FAIL the setup console's failure must not reach the ERR trap: $out"; fails=$((fails + 1)) ;;
|
||||
*) echo "PASS without the ERR trap" ;;
|
||||
esac
|
||||
rm -rf "$next_dir"
|
||||
|
||||
# The watchdog alerts by mail only, through the [smtp] relay. An install without one must
|
||||
# say that its alerts are only logged; one with a relay must not cry wolf.
|
||||
sablock="$(awk '/^summary_alerts\(\) \{/,/^}/' "$BS")"
|
||||
|
||||
Reference in new issue
Block a user