feat(deploy): bootstrap can install Felis-nano only, chosen at the start prompt
bootstrap.sh now asks up front whether to install the full Felis control
plane or only Felis-nano, and grows a parallel install path for the
nano-only case.
- prompt_install_mode() runs right after OS detection and reads /dev/tty
(so it works under `curl ... | sudo bash`) offering [1] Felis / [2]
Felis-nano, default full. FELIS_INSTALL_MODE=full|nano skips the prompt
for non-interactive runs; no tty falls back to full.
- main_nano() installs only what nano needs: the felis binary (reusing
the embedded-binary / docker-build acquisition), a template felis.toml
carrying a commented [[auth_source]] example (Mojang-only until edited),
a felis-nano.service unit running `felis nano -config ... -listen ...`
under DynamicUser hardening, and a firewalld port-open for the listen
port. None of the k3s / Postgres / migrate / bundle steps run.
- write_nano_config is idempotent (leaves any existing config untouched)
and its template is valid as-is. summary_nano prints the hasJoined
endpoint and the Velocity -Dmojang.sessionserver flag, offering the
127.0.0.1 form when the proxy is on the same host.
Verified on WSL: `bash -n` clean; the emitted template loads via
config.LoadNano and the exact systemd ExecStart command serves 204 on a
miss ("Mojang + 0 third-party source(s)"); a duplicate-tag config still
exits non-zero citing "unique". Not exercised: a full main_nano run,
systemd activation of the unit, and shellcheck (unavailable in this env).
This commit is contained in:
1 file changed
+159
@@ -15,10 +15,20 @@
|
|||||||
# bootstrap in a TUI and then continues to the Owner/edge setup. This script
|
# bootstrap in a TUI and then continues to the Owner/edge setup. This script
|
||||||
# remains usable directly for raw host provisioning.
|
# remains usable directly for raw host provisioning.
|
||||||
#
|
#
|
||||||
|
# At the start it asks what to install:
|
||||||
|
# [1] Felis — the full control plane described above.
|
||||||
|
# [2] Felis-nano — ONLY the Yggdrasil hasJoined multiplexer (`felis nano`) as a
|
||||||
|
# systemd service: no k3s, no Postgres, no bundle. For a
|
||||||
|
# third-party server operator who just wants multi-Yggdrasil
|
||||||
|
# auth federation. Preselect non-interactively with
|
||||||
|
# FELIS_INSTALL_MODE=nano.
|
||||||
|
#
|
||||||
# The script is idempotent: re-running it converges rather than duplicating, and
|
# The script is idempotent: re-running it converges rather than duplicating, and
|
||||||
# generated secrets are persisted to /etc/felis/secrets.env so reruns reuse them.
|
# generated secrets are persisted to /etc/felis/secrets.env so reruns reuse them.
|
||||||
#
|
#
|
||||||
# Tunables (export before running to override the demo defaults):
|
# Tunables (export before running to override the demo defaults):
|
||||||
|
# FELIS_INSTALL_MODE full|nano — skip the prompt (default: ask on a tty, else full)
|
||||||
|
# FELIS_NANO_LISTEN listen addr for `felis nano` (default: 0.0.0.0:8081; nano mode)
|
||||||
# FELIS_REPO_URL git URL to build from (raw script mode only)
|
# FELIS_REPO_URL git URL to build from (raw script mode only)
|
||||||
# FELIS_REF branch/tag/sha (raw script mode only)
|
# FELIS_REF branch/tag/sha (raw script mode only)
|
||||||
# FELIS_IMAGE local image tag (default: felis:demo — never :latest)
|
# FELIS_IMAGE local image tag (default: felis:demo — never :latest)
|
||||||
@@ -37,6 +47,8 @@ FELIS_REF="${FELIS_REF:-main}"
|
|||||||
FELIS_IMAGE="${FELIS_IMAGE:-felis:demo}"
|
FELIS_IMAGE="${FELIS_IMAGE:-felis:demo}"
|
||||||
FELIS_EGRESS_MODE="${FELIS_EGRESS_MODE:-nodeport}"
|
FELIS_EGRESS_MODE="${FELIS_EGRESS_MODE:-nodeport}"
|
||||||
FELIS_PANEL_NODEPORT="${FELIS_PANEL_NODEPORT:-30443}"
|
FELIS_PANEL_NODEPORT="${FELIS_PANEL_NODEPORT:-30443}"
|
||||||
|
INSTALL_MODE="${FELIS_INSTALL_MODE:-}"
|
||||||
|
FELIS_NANO_LISTEN="${FELIS_NANO_LISTEN:-0.0.0.0:8081}"
|
||||||
PKG_LOCK_TIMEOUT="${PKG_LOCK_TIMEOUT:-${APT_LOCK_TIMEOUT:-900}}"
|
PKG_LOCK_TIMEOUT="${PKG_LOCK_TIMEOUT:-${APT_LOCK_TIMEOUT:-900}}"
|
||||||
APT_LOCK_TIMEOUT="${APT_LOCK_TIMEOUT:-$PKG_LOCK_TIMEOUT}"
|
APT_LOCK_TIMEOUT="${APT_LOCK_TIMEOUT:-$PKG_LOCK_TIMEOUT}"
|
||||||
|
|
||||||
@@ -56,6 +68,7 @@ PANEL_TLS_CERT="${STATE_DIR}/panel-tls.crt"
|
|||||||
PANEL_TLS_KEY="${STATE_DIR}/panel-tls.key"
|
PANEL_TLS_KEY="${STATE_DIR}/panel-tls.key"
|
||||||
SRC_DIR="/opt/felis/src"
|
SRC_DIR="/opt/felis/src"
|
||||||
HOST_BIN="/usr/local/bin/felis"
|
HOST_BIN="/usr/local/bin/felis"
|
||||||
|
NANO_SERVICE="/etc/systemd/system/felis-nano.service"
|
||||||
K3S_BIN_DIR="${K3S_BIN_DIR:-/usr/local/bin}"
|
K3S_BIN_DIR="${K3S_BIN_DIR:-/usr/local/bin}"
|
||||||
K3S_BIN="${K3S_BIN_DIR}/k3s"
|
K3S_BIN="${K3S_BIN_DIR}/k3s"
|
||||||
APT_LOCK_FILES=(
|
APT_LOCK_FILES=(
|
||||||
@@ -1038,9 +1051,155 @@ summary() {
|
|||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 10. Felis-nano install path — the auth multiplexer only: felis binary + a
|
||||||
|
# minimal [[auth_source]] config + a systemd unit running `felis nano`.
|
||||||
|
# No k3s, no Postgres, no control-plane bundle. Chosen at the top-of-run
|
||||||
|
# prompt (or FELIS_INSTALL_MODE=nano).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
prompt_install_mode() {
|
||||||
|
case "$INSTALL_MODE" in
|
||||||
|
full|nano) log "install mode: ${INSTALL_MODE} (from FELIS_INSTALL_MODE)"; return 0 ;;
|
||||||
|
"") ;;
|
||||||
|
*) die "FELIS_INSTALL_MODE must be 'full' or 'nano', got: ${INSTALL_MODE}" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# No override: ask on the controlling terminal. Under `curl | sudo bash` stdin
|
||||||
|
# is the script, so we must read /dev/tty, not stdin. No tty (CI/cloud-init) →
|
||||||
|
# default to a full install.
|
||||||
|
if [ ! -r /dev/tty ]; then
|
||||||
|
INSTALL_MODE="full"
|
||||||
|
log "no terminal for a prompt; defaulting to a full Felis install (set FELIS_INSTALL_MODE=nano to override)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\n'
|
||||||
|
printf 'What do you want to install on this host?\n'
|
||||||
|
printf ' [1] Felis — full control plane (k3s + Postgres + panel; orchestrates Minecraft servers)\n'
|
||||||
|
printf ' [2] Felis-nano — auth multiplexer only (federates Mojang + third-party Yggdrasil; no k3s/DB)\n'
|
||||||
|
local reply
|
||||||
|
while :; do
|
||||||
|
printf 'Choose [1/2] (default 1): '
|
||||||
|
IFS= read -r reply </dev/tty || reply=""
|
||||||
|
case "$reply" in
|
||||||
|
""|1|full|Felis|felis) INSTALL_MODE="full"; break ;;
|
||||||
|
2|nano|felis-nano|Felis-nano) INSTALL_MODE="nano"; break ;;
|
||||||
|
*) printf 'Please enter 1 or 2.\n' ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
log "install mode: ${INSTALL_MODE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
acquire_nano_binary() {
|
||||||
|
if bootstrap_from_tui; then
|
||||||
|
install_embedded_binary
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# Raw curl|bash: no binary yet. Build it from source with docker (the full
|
||||||
|
# path's proven build), then reclaim docker's RAM. ponytail: reuses the docker
|
||||||
|
# build rather than shipping a Go≥1.26 toolchain installer; a nano host that must
|
||||||
|
# stay docker-free would need a prebuilt-release download — add when asked.
|
||||||
|
install_docker
|
||||||
|
fetch_source
|
||||||
|
systemctl start docker
|
||||||
|
build_image_from_source
|
||||||
|
verify_image_starts
|
||||||
|
systemctl stop docker docker.socket 2>/dev/null || true
|
||||||
|
ok "felis binary on host at ${HOST_BIN}"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_nano_config() {
|
||||||
|
local target="${STATE_DIR}/felis.toml"
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
|
if [ -e "$target" ]; then
|
||||||
|
ok "config already present at ${target}; leaving it (edit it to add [[auth_source]] roots)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
cat > "$target" <<'EOF'
|
||||||
|
# Felis-nano — Yggdrasil hasJoined multiplexer.
|
||||||
|
# Mojang is always the first (identity) source, added in code — do NOT list it here.
|
||||||
|
# Add each third-party Yggdrasil root below (priority = order). url is the FULL
|
||||||
|
# hasJoined endpoint. After editing: sudo systemctl restart felis-nano
|
||||||
|
#
|
||||||
|
# [[auth_source]]
|
||||||
|
# tag = "littleskin"
|
||||||
|
# url = "https://littleskin.cn/api/yggdrasil/sessionserver/session/minecraft/hasJoined"
|
||||||
|
EOF
|
||||||
|
chmod 0644 "$target"
|
||||||
|
ok "wrote nano config template ${target} (edit it to add your Yggdrasil sources)"
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_nano_firewall() {
|
||||||
|
command -v firewall-cmd >/dev/null 2>&1 || return 0
|
||||||
|
systemctl is-active --quiet firewalld || return 0
|
||||||
|
local port="${FELIS_NANO_LISTEN##*:}"
|
||||||
|
log "opening firewalld port ${port}/tcp for felis-nano"
|
||||||
|
firewall-cmd --permanent --add-port="${port}/tcp"
|
||||||
|
firewall-cmd --reload
|
||||||
|
}
|
||||||
|
|
||||||
|
install_nano_service() {
|
||||||
|
# DynamicUser: no static account, ephemeral UID. nano writes nothing (logs go to
|
||||||
|
# journald) and only reads the world-readable felis.toml, so strict sandboxing fits.
|
||||||
|
cat > "$NANO_SERVICE" <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Felis-nano Yggdrasil hasJoined multiplexer
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=${HOST_BIN} nano -config ${STATE_DIR}/felis.toml -listen ${FELIS_NANO_LISTEN}
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
DynamicUser=yes
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now felis-nano
|
||||||
|
ok "felis-nano.service enabled and started (listen ${FELIS_NANO_LISTEN})"
|
||||||
|
}
|
||||||
|
|
||||||
|
summary_nano() {
|
||||||
|
local port="${FELIS_NANO_LISTEN##*:}"
|
||||||
|
echo
|
||||||
|
ok "Felis-nano deployed."
|
||||||
|
echo
|
||||||
|
systemctl --no-pager --full status felis-nano 2>/dev/null | head -n 6 || true
|
||||||
|
echo
|
||||||
|
log "hasJoined endpoint: http://${NODE_IP}:${port}/session/minecraft/hasJoined"
|
||||||
|
log "Point Velocity at it — add to the proxy JVM startup flags:"
|
||||||
|
log " -Dmojang.sessionserver=http://${NODE_IP}:${port}"
|
||||||
|
log " (Velocity on THIS host? use http://127.0.0.1:${port} instead — no firewall hop)"
|
||||||
|
log "Then edit ${STATE_DIR}/felis.toml to add your [[auth_source]] roots and run:"
|
||||||
|
log " sudo systemctl restart felis-nano"
|
||||||
|
log "Follow live login traffic with: sudo journalctl -u felis-nano -f"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
main_nano() {
|
||||||
|
detect_node_ip
|
||||||
|
install_base
|
||||||
|
acquire_nano_binary
|
||||||
|
write_nano_config
|
||||||
|
install_nano_service
|
||||||
|
configure_nano_firewall
|
||||||
|
summary_nano
|
||||||
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
validate_settings
|
validate_settings
|
||||||
detect_os
|
detect_os
|
||||||
|
prompt_install_mode
|
||||||
|
if [ "$INSTALL_MODE" = "nano" ]; then
|
||||||
|
main_nano
|
||||||
|
return
|
||||||
|
fi
|
||||||
pause_package_background_timers
|
pause_package_background_timers
|
||||||
detect_node_ip
|
detect_node_ip
|
||||||
ensure_swap
|
ensure_swap
|
||||||
|
|||||||
Reference in new issue
Block a user