#!/bin/sh usage() { cat <<'USAGE_EOF' Usage: bookworm-pup64__bootstrap apply USAGE_EOF } die() { echo "ERROR: $*" >&2 exit 1 } [ "$#" -eq 1 ] || { usage exit 64 } [ "$1" = 'apply' ] || { usage exit 64 } [ "$(id -u)" -eq 0 ] || die 'This bootstrap must run as root' BIN_DIR='/root/bin-puppy' LOCAL_BIN='/usr/local/bin' RC_LOCAL='/etc/rc.d/rc.local' mkdir -p "$BIN_DIR" "$LOCAL_BIN" || die 'Could not create bin directories' echo '=== BookwormPup64 bootstrap ===' echo "Hostname: $(hostname)" case "$(hostname)" in puppypc*|localhost) echo echo 'WARNING: hostname still looks like a Puppy default.' echo 'mDNS will publish the current hostname.' ;; esac # ============================================================ # Install canonical copy of this bootstrap # ============================================================ SELF="$(readlink -f "$0" 2>/dev/null)" if [ -f "$SELF" ] && [ "$SELF" != "$BIN_DIR/bookworm-pup64__bootstrap" ] then cp -f \ "$SELF" \ "$BIN_DIR/bookworm-pup64__bootstrap" || die 'Could not install canonical bootstrap copy' fi chmod +x "$BIN_DIR/bookworm-pup64__bootstrap" ln -sf \ "$BIN_DIR/bookworm-pup64__bootstrap" \ "$LOCAL_BIN/bookworm-pup64__bootstrap" # ============================================================ # Migrate old Puppy toolbox out of /root/bin # ============================================================ LEGACY_PUPPY_BIN='/root/bin' if [ "$LEGACY_PUPPY_BIN" != "$BIN_DIR" ] && [ -d "$LEGACY_PUPPY_BIN" ] && [ ! -d "$LEGACY_PUPPY_BIN/.git" ] then echo echo '=== Migrating legacy Puppy toolbox away from /root/bin ===' for LEGACY_PUPPY_FILE in \ bookworm-pup64__bootstrap \ bookworm-pup64_allow-ssh-all \ bookworm-pup64_external-display-only \ bookworm-pup64_mount-data-docs \ bookworm-pup64_readme.md \ bookworm-pup64_save-now \ bookworm-pup64_setup-external-display-only \ bookworm-pup64_setup-mdns \ bookworm-pup64_setup-shell \ bookworm-pup64_setup-sshd \ bookworm-pup64_start-mdns \ bookworm-pup64_install-personal-bin \ pup64seed.sh \ sync-up.sh do rm -f "$LEGACY_PUPPY_BIN/$LEGACY_PUPPY_FILE" done rmdir "$LEGACY_PUPPY_BIN" 2>/dev/null || true fi # ============================================================ # bookworm-pup64_setup-sshd # ============================================================ cat > "$BIN_DIR/bookworm-pup64_setup-sshd" <<'SCRIPT_EOF' #!/bin/sh die() { echo "ERROR: $*" >&2 exit 1 } echo '=== OpenSSH setup ===' OPENSSH_CLIENT_WAS_HELD=0 LIBSSL3_WAS_HELD=0 apt-mark showhold | grep -Fxq openssh-client && OPENSSH_CLIENT_WAS_HELD=1 apt-mark showhold | grep -Fxq libssl3 && LIBSSL3_WAS_HELD=1 restore_holds() { echo echo '=== Re-applying Puppy package holds ===' if [ "$OPENSSH_CLIENT_WAS_HELD" -eq 1 ]; then apt-mark hold openssh-client >/dev/null 2>&1 || true fi if [ "$LIBSSL3_WAS_HELD" -eq 1 ]; then apt-mark hold libssl3 >/dev/null 2>&1 || true fi } trap restore_holds 0 echo echo '=== Temporarily removing required holds ===' if [ "$OPENSSH_CLIENT_WAS_HELD" -eq 1 ]; then apt-mark unhold openssh-client || die 'Could not unhold openssh-client' fi if [ "$LIBSSL3_WAS_HELD" -eq 1 ]; then apt-mark unhold libssl3 || die 'Could not unhold libssl3' fi echo echo '=== Updating package metadata ===' apt-get update || die 'apt-get update failed' echo echo '=== Installing OpenSSH ===' apt-get install -y --no-install-recommends \ openssh-server \ iproute2 || die 'Could not install OpenSSH dependencies' echo echo '=== Installing GitHub authorized keys ===' mkdir -p /root/.ssh || die 'Could not create /root/.ssh' chmod 700 /root/.ssh KEYS_TMP='/root/.ssh/authorized_keys.new' rm -f "$KEYS_TMP" if command -v curl >/dev/null 2>&1; then curl -fsSL \ https://github.com/fernando-reis-guimaraes.keys \ > "$KEYS_TMP" || die 'Could not download GitHub public keys with curl' elif command -v wget >/dev/null 2>&1; then wget -qO "$KEYS_TMP" \ https://github.com/fernando-reis-guimaraes.keys || die 'Could not download GitHub public keys with wget' else die 'Neither curl nor wget is available' fi grep -Eq \ '^(ssh-rsa|ssh-ed25519|ecdsa-sha2-)' \ "$KEYS_TMP" || die 'Downloaded file contains no valid SSH public key' chmod 600 "$KEYS_TMP" mv -f \ "$KEYS_TMP" \ /root/.ssh/authorized_keys || die 'Could not install authorized_keys' echo echo '=== Configuring sshd ===' mkdir -p /etc/ssh/sshd_config.d || die 'Could not create sshd_config.d' cat > /etc/ssh/sshd_config.d/99-puppy.conf <<'SSHD_EOF' PermitRootLogin prohibit-password PubkeyAuthentication yes PasswordAuthentication no KbdInteractiveAuthentication no AllowAgentForwarding yes SSHD_EOF mkdir -p /run/sshd ssh-keygen -A || die 'Could not generate SSH host keys' SSHD_BIN="$(command -v sshd)" [ -n "$SSHD_BIN" ] || die 'sshd executable not found' "$SSHD_BIN" -t || die 'sshd configuration is invalid' echo echo '=== Starting sshd ===' if [ -x /etc/init.d/ssh ]; then /etc/init.d/ssh restart || "$SSHD_BIN" || die 'Could not start sshd' else pkill -x sshd 2>/dev/null || true "$SSHD_BIN" || die 'Could not start sshd' fi echo echo '=== Installing sshd boot hook ===' RC_LOCAL='/etc/rc.d/rc.local' touch "$RC_LOCAL" || die 'Could not access rc.local' sed -i \ '/# BEGIN BOOKWORM PUP64 SSHD/,/# END BOOKWORM PUP64 SSHD/d' \ "$RC_LOCAL" cat >> "$RC_LOCAL" <<'RC_EOF' # BEGIN BOOKWORM PUP64 SSHD ( if ! pgrep -x sshd >/dev/null 2>&1; then mkdir -p /run/sshd if [ -x /etc/init.d/ssh ]; then /etc/init.d/ssh start else /usr/sbin/sshd fi fi ) >/tmp/bookworm-pup64_sshd.log 2>&1 & # END BOOKWORM PUP64 SSHD RC_EOF chmod +x "$RC_LOCAL" echo echo '=== Verification ===' pgrep -a sshd || die 'sshd is not running' ss -lntp | grep ':22 ' || die 'sshd is not listening on TCP/22' wc -l /root/.ssh/authorized_keys echo echo '=== SUCCESS ===' echo 'OpenSSH is running with public-key-only root authentication.' SCRIPT_EOF # ============================================================ # bookworm-pup64_allow-ssh-all # ============================================================ cat > "$BIN_DIR/bookworm-pup64_allow-ssh-all" <<'SCRIPT_EOF' #!/bin/sh die() { echo "ERROR: $*" >&2 exit 1 } WAIT_MODE=0 if [ "$1" = '--wait' ]; then WAIT_MODE=1 fi echo '=== Allow SSH from all reachable networks ===' if [ "$WAIT_MODE" -eq 1 ]; then echo 'Waiting for Puppy firewall...' COUNT=0 while ! iptables -nL tcp_inbound >/dev/null 2>&1; do COUNT=$((COUNT + 1)) if [ "$COUNT" -ge 30 ]; then die 'tcp_inbound chain did not appear after 30 seconds' fi sleep 1 done fi iptables -nL tcp_inbound >/dev/null 2>&1 || die 'tcp_inbound chain does not exist' echo echo '=== TCP Wrappers ===' cat > /etc/hosts.allow <<'HOSTS_EOF' sshd: ALL ALL: LOCAL HOSTS_EOF cat > /etc/hosts.deny <<'HOSTS_EOF' ALL: ALL HOSTS_EOF echo echo '=== IPv4 firewall ===' while iptables -C tcp_inbound \ -p tcp \ -s 192.168.1.0/24 \ --dport 22 \ -j ACCEPT \ >/dev/null 2>&1 do iptables -D tcp_inbound \ -p tcp \ -s 192.168.1.0/24 \ --dport 22 \ -j ACCEPT || die 'Could not remove legacy LAN-only SSH rule' done while iptables -C tcp_inbound \ -p tcp \ --dport 22 \ -j ACCEPT \ >/dev/null 2>&1 do iptables -D tcp_inbound \ -p tcp \ --dport 22 \ -j ACCEPT || die 'Could not remove existing IPv4 SSH rule' done iptables -I tcp_inbound 1 \ -p tcp \ --dport 22 \ -j ACCEPT || die 'Could not allow IPv4 SSH' echo echo '=== IPv6 firewall ===' while ip6tables -C INPUT \ -p tcp \ --dport 22 \ -j ACCEPT \ >/dev/null 2>&1 do ip6tables -D INPUT \ -p tcp \ --dport 22 \ -j ACCEPT || die 'Could not remove existing IPv6 SSH rule' done ip6tables -A INPUT \ -p tcp \ --dport 22 \ -j ACCEPT || die 'Could not allow IPv6 SSH' echo echo '=== Installing boot hook ===' RC_LOCAL='/etc/rc.d/rc.local' touch "$RC_LOCAL" || die 'Could not access rc.local' sed -i \ '/# BEGIN local SSH LAN firewall/,/# END local SSH LAN firewall/d' \ "$RC_LOCAL" sed -i \ '/# BEGIN SSH ALL FIREWALL/,/# END SSH ALL FIREWALL/d' \ "$RC_LOCAL" cat >> "$RC_LOCAL" <<'RC_EOF' # BEGIN SSH ALL FIREWALL /root/bin-puppy/bookworm-pup64_allow-ssh-all --wait >/tmp/bookworm-pup64_allow-ssh-all.log 2>&1 & # END SSH ALL FIREWALL RC_EOF chmod +x "$RC_LOCAL" echo echo '=== Verification ===' iptables -L tcp_inbound -n -v --line-numbers echo ip6tables -L INPUT -n -v --line-numbers | grep -E 'Chain|dpt:22|tcp' || true echo ss -lntp | grep ':22 ' || die 'sshd is not listening on TCP/22' echo echo '=== SUCCESS ===' echo 'SSH TCP/22 is allowed from all reachable IPv4 and IPv6 networks.' SCRIPT_EOF # ============================================================ # bookworm-pup64_start-mdns # ============================================================ cat > "$BIN_DIR/bookworm-pup64_start-mdns" <<'SCRIPT_EOF' #!/bin/sh die() { echo "ERROR: $*" >&2 exit 1 } echo '=== Starting mDNS ===' command -v avahi-daemon >/dev/null 2>&1 || die 'avahi-daemon is not installed' echo echo '=== Waiting for Puppy firewall ===' COUNT=0 while ! iptables -nL udp_inbound >/dev/null 2>&1; do COUNT=$((COUNT + 1)) if [ "$COUNT" -ge 30 ]; then die 'IPv4 udp_inbound chain did not appear after 30 seconds' fi sleep 1 done echo echo '=== IPv4 mDNS firewall ===' if ! iptables -C udp_inbound \ -p udp \ -d 224.0.0.251 \ --dport 5353 \ -j ACCEPT \ >/dev/null 2>&1 then iptables -I udp_inbound 1 \ -p udp \ -d 224.0.0.251 \ --dport 5353 \ -j ACCEPT || die 'Could not allow IPv4 mDNS' fi echo echo '=== IPv6 mDNS firewall ===' if ip6tables -nL udp_inbound >/dev/null 2>&1; then if ! ip6tables -C udp_inbound \ -p udp \ -d ff02::fb \ --dport 5353 \ -j ACCEPT \ >/dev/null 2>&1 then ip6tables -I udp_inbound 1 \ -p udp \ -d ff02::fb \ --dport 5353 \ -j ACCEPT || die 'Could not allow IPv6 mDNS' fi fi echo echo '=== Ensuring D-Bus ===' if ! pgrep -x dbus-daemon >/dev/null 2>&1; then if [ -x /etc/init.d/dbus ]; then /etc/init.d/dbus start || die 'Could not start D-Bus' else die 'D-Bus is not running and no init script exists' fi fi echo echo '=== Ensuring Avahi is running ===' if avahi-daemon --check >/dev/null 2>&1; then echo 'Avahi is already running.' else echo 'Starting Avahi...' if [ -x /etc/init.d/avahi-daemon ]; then /etc/init.d/avahi-daemon start || die 'Could not start avahi-daemon' else avahi-daemon -D || die 'Could not start avahi-daemon' fi fi COUNT=0 while ! avahi-daemon --check >/dev/null 2>&1; do COUNT=$((COUNT + 1)) if [ "$COUNT" -ge 10 ]; then die 'avahi-daemon did not become ready' fi sleep 1 done echo echo '=== Verification ===' pgrep -a avahi-daemon || die 'avahi-daemon is not running' ss -lunp | grep ':5353 ' || die 'Nothing is listening on UDP/5353' echo echo '=== SUCCESS ===' echo "mDNS hostname: $(hostname).local" SCRIPT_EOF # ============================================================ # bookworm-pup64_setup-mdns # ============================================================ cat > "$BIN_DIR/bookworm-pup64_setup-mdns" <<'SCRIPT_EOF' #!/bin/sh die() { echo "ERROR: $*" >&2 exit 1 } CONFIG='/etc/avahi/avahi-daemon.conf' CONFIG_TMP='/tmp/bookworm-pup64_avahi-daemon.conf.new' RC_LOCAL='/etc/rc.d/rc.local' echo '=== Installing mDNS / Avahi ===' apt-get update || die 'apt-get update failed' apt-get install -y --no-install-recommends \ avahi-daemon \ avahi-utils || die 'Could not install Avahi' HOSTNAME="$(hostname)" [ -n "$HOSTNAME" ] || die 'Hostname is empty' echo echo "=== Preparing configuration for ${HOSTNAME}.local ===" cat > "$CONFIG_TMP" <> "$RC_LOCAL" <<'RC_EOF' # BEGIN MDNS /root/bin-puppy/bookworm-pup64_start-mdns >/tmp/bookworm-pup64_start-mdns.log 2>&1 & # END MDNS RC_EOF chmod +x "$RC_LOCAL" if [ "$CONFIG_CHANGED" -eq 1 ] && avahi-daemon --check >/dev/null 2>&1 then echo echo '=== Restarting Avahi because configuration changed ===' avahi-daemon --kill || die 'Could not stop existing avahi-daemon' COUNT=0 while avahi-daemon --check >/dev/null 2>&1; do COUNT=$((COUNT + 1)) if [ "$COUNT" -ge 10 ]; then die 'avahi-daemon did not stop' fi sleep 1 done fi echo echo '=== Ensuring mDNS runtime ===' /root/bin-puppy/bookworm-pup64_start-mdns || die 'bookworm-pup64_start-mdns failed' echo echo '=== SUCCESS ===' echo "Configured mDNS hostname: ${HOSTNAME}.local" SCRIPT_EOF # ============================================================ # bookworm-pup64_mount-data-docs # ============================================================ cat > "$BIN_DIR/bookworm-pup64_mount-data-docs" <<'SCRIPT_EOF' #!/bin/sh die() { echo "ERROR: $*" >&2 exit 1 } DATA_ROOT='/data' VENTOY_TARGET='/data/ventoy' DOCS_SOURCE='/data/ventoy/docs' DOCS_TARGET='/data/docs' WAIT_MODE=0 if [ "$1" = '--wait' ]; then WAIT_MODE=1 fi detect_device() { DEVICE='' for CANDIDATE in /dev/mapper/*; do [ -b "$CANDIDATE" ] || continue LABEL="$( blkid -s LABEL -o value "$CANDIDATE" 2>/dev/null )" if [ "$LABEL" = 'Ventoy' ]; then DEVICE="$CANDIDATE" return 0 fi done DEVICE="$(blkid -L Ventoy 2>/dev/null)" [ -n "$DEVICE" ] } echo '=== Detecting Ventoy data partition ===' if [ "$WAIT_MODE" -eq 1 ]; then COUNT=0 while ! detect_device; do COUNT=$((COUNT + 1)) if [ "$COUNT" -ge 30 ]; then die 'Ventoy data partition did not appear after 30 seconds' fi sleep 1 done else detect_device || die 'Could not find filesystem labeled Ventoy' fi [ -b "$DEVICE" ] || die "$DEVICE is not a block device" echo "Device: $DEVICE" mkdir -p "$DATA_ROOT" || die "Could not create $DATA_ROOT" CURRENT_TARGET="$( findmnt -rn -S "$DEVICE" -o TARGET 2>/dev/null | grep -v "^${DOCS_TARGET}$" | head -n 1 )" if [ "$CURRENT_TARGET" = "$VENTOY_TARGET" ]; then echo "$DEVICE is already mounted at $VENTOY_TARGET." elif [ "$CURRENT_TARGET" = "$DATA_ROOT" ]; then echo "$DEVICE is currently mounted directly at $DATA_ROOT." echo 'Unmounting and remounting at the canonical mount point...' if mountpoint -q "$DOCS_TARGET"; then umount "$DOCS_TARGET" || die "Could not unmount $DOCS_TARGET" fi umount "$DATA_ROOT" || die "Could not unmount $DATA_ROOT" mkdir -p "$VENTOY_TARGET" "$DOCS_TARGET" || die 'Could not create mount points' mount -o rw \ "$DEVICE" \ "$VENTOY_TARGET" || die "Could not mount $DEVICE at $VENTOY_TARGET" elif [ -n "$CURRENT_TARGET" ]; then echo "$DEVICE is currently mounted at $CURRENT_TARGET." echo "Moving mount to $VENTOY_TARGET..." mkdir -p "$VENTOY_TARGET" || die "Could not create $VENTOY_TARGET" mount --move \ "$CURRENT_TARGET" \ "$VENTOY_TARGET" || die "Could not move $CURRENT_TARGET to $VENTOY_TARGET" else echo "Mounting $DEVICE at $VENTOY_TARGET..." mkdir -p "$VENTOY_TARGET" || die "Could not create $VENTOY_TARGET" mount -o rw \ "$DEVICE" \ "$VENTOY_TARGET" || die "Could not mount $DEVICE at $VENTOY_TARGET" fi echo echo '=== Preparing durable docs mount ===' mkdir -p "$DOCS_SOURCE" "$DOCS_TARGET" || die 'Could not create docs directories' if mountpoint -q "$DOCS_TARGET"; then SOURCE_ID="$(stat -c '%d:%i' "$DOCS_SOURCE")" TARGET_ID="$(stat -c '%d:%i' "$DOCS_TARGET")" [ "$SOURCE_ID" = "$TARGET_ID" ] || die "$DOCS_TARGET is mounted from a different location" echo "$DOCS_TARGET is already correctly mounted." else mount --bind \ "$DOCS_SOURCE" \ "$DOCS_TARGET" || die "Could not bind $DOCS_SOURCE at $DOCS_TARGET" fi echo echo '=== Verification ===' findmnt "$VENTOY_TARGET" || die "$VENTOY_TARGET verification failed" findmnt "$DOCS_TARGET" || die "$DOCS_TARGET verification failed" echo df -h "$VENTOY_TARGET" echo echo '=== SUCCESS ===' echo "Ventoy storage: $VENTOY_TARGET" echo "Durable documents: $DOCS_TARGET" SCRIPT_EOF # ============================================================ # bookworm-pup64_external-display-only # ============================================================ cat > "$BIN_DIR/bookworm-pup64_external-display-only" <<'SCRIPT_EOF' #!/bin/sh usage() { cat <<'USAGE_EOF' Usage: bookworm-pup64_external-display-only status bookworm-pup64_external-display-only apply USAGE_EOF } die() { echo "ERROR: $*" >&2 exit 1 } DISPLAY="${DISPLAY:-:0}" export DISPLAY is_internal_output() { case "$1" in eDP*|EDP*|LVDS*|lvds*|DSI*|dsi*) return 0 ;; *) return 1 ;; esac } get_outputs() { xrandr --query 2>/dev/null | awk '$2 == "connected" || $2 == "disconnected" {print $1}' } get_connected_outputs() { xrandr --query 2>/dev/null | awk '$2 == "connected" {print $1}' } find_external_output() { PRIMARY="$( xrandr --query 2>/dev/null | awk '$2 == "connected" && $3 == "primary" {print $1; exit}' )" if [ -n "$PRIMARY" ] && ! is_internal_output "$PRIMARY" then echo "$PRIMARY" return 0 fi for OUTPUT in $(get_connected_outputs); do if ! is_internal_output "$OUTPUT"; then echo "$OUTPUT" return 0 fi done return 1 } status() { echo '=== X display ===' echo "DISPLAY=$DISPLAY" echo echo '=== XRandR ===' xrandr --query || die 'Could not query XRandR' echo echo '=== Internal outputs ===' INTERNAL_FOUND=0 for OUTPUT in $(get_outputs); do if is_internal_output "$OUTPUT"; then echo "$OUTPUT" INTERNAL_FOUND=1 fi done if [ "$INTERNAL_FOUND" -eq 0 ]; then echo 'none detected' fi echo echo '=== Connected external outputs ===' EXTERNAL_FOUND=0 for OUTPUT in $(get_connected_outputs); do if ! is_internal_output "$OUTPUT"; then echo "$OUTPUT" EXTERNAL_FOUND=1 fi done if [ "$EXTERNAL_FOUND" -eq 0 ]; then echo 'none detected' fi } apply() { echo '=== External-display-only mode ===' echo "DISPLAY=$DISPLAY" xrandr --query >/dev/null 2>&1 || die 'Could not connect to X display' EXTERNAL="$(find_external_output)" || die 'No connected external display detected. Nothing was changed.' echo "Primary external display: $EXTERNAL" echo echo '=== Enabling external display first ===' xrandr \ --output "$EXTERNAL" \ --auto \ --primary \ --pos 0x0 || die "Could not enable external display $EXTERNAL" echo echo '=== Disabling internal laptop displays ===' INTERNAL_FOUND=0 for OUTPUT in $(get_outputs); do if is_internal_output "$OUTPUT"; then echo "Disabling: $OUTPUT" xrandr \ --output "$OUTPUT" \ --off || die "Could not disable internal display $OUTPUT" INTERNAL_FOUND=1 fi done if [ "$INTERNAL_FOUND" -eq 0 ]; then echo 'No internal display connector was detected.' fi echo echo '=== Final layout ===' xrandr --query echo echo '=== SUCCESS ===' echo "Primary display: $EXTERNAL" echo 'Internal laptop display outputs are disabled.' } [ "$#" -eq 1 ] || { usage exit 64 } case "$1" in status) status ;; apply) apply ;; *) usage exit 64 ;; esac SCRIPT_EOF # ============================================================ # bookworm-pup64_setup-external-display-only # ============================================================ cat > "$BIN_DIR/bookworm-pup64_setup-external-display-only" <<'SCRIPT_EOF' #!/bin/sh die() { echo "ERROR: $*" >&2 exit 1 } STARTUP_DIR='/root/Startup' STARTUP_SCRIPT='/root/Startup/bookworm-pup64_external-display-only' DISPLAY_CLI='/root/bin-puppy/bookworm-pup64_external-display-only' [ -x "$DISPLAY_CLI" ] || die "$DISPLAY_CLI is missing or not executable" mkdir -p "$STARTUP_DIR" || die "Could not create $STARTUP_DIR" cat > "$STARTUP_SCRIPT" <<'STARTUP_EOF' #!/bin/sh LOG='/tmp/bookworm-pup64_external-display-only.log' COUNT=0 { while [ "$COUNT" -lt 10 ]; do if DISPLAY=:0 /root/bin-puppy/bookworm-pup64_external-display-only apply; then exit 0 fi COUNT=$((COUNT + 1)) sleep 1 done echo 'External display policy was not applied after 10 attempts.' exit 0 } > "$LOG" 2>&1 STARTUP_EOF chmod +x "$STARTUP_SCRIPT" || die "Could not chmod $STARTUP_SCRIPT" sh -n "$STARTUP_SCRIPT" || die "Syntax error in $STARTUP_SCRIPT" echo '=== External display Startup hook ===' echo "$STARTUP_SCRIPT" echo if [ -S /tmp/.X11-unix/X0 ]; then echo '=== Applying external display policy to current X session ===' if DISPLAY=:0 "$DISPLAY_CLI" apply; then echo 'Current X session updated.' else echo 'WARNING: external display policy was not applied to the current X session.' echo 'The Startup hook remains installed for future graphical sessions.' fi else echo 'No active X display :0 detected.' echo 'The Startup hook will apply the policy when X starts.' fi echo echo '=== SUCCESS ===' echo 'External-display-only Startup policy is installed.' SCRIPT_EOF # ============================================================ # bookworm-pup64_setup-shell # ============================================================ cat > "$BIN_DIR/bookworm-pup64_setup-shell" <<'SCRIPT_EOF' #!/bin/sh die() { echo "ERROR: $*" >&2 exit 1 } [ "$(id -u)" -eq 0 ] || die 'This setup must run as root' OH_MY_ZSH_DIR='/root/.oh-my-zsh' INSTALLER='/tmp/oh-my-zsh-install.sh' INSTALLER_NEW='/tmp/oh-my-zsh-install.sh.new' INSTALLER_URL='https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh' echo '=== Shell tooling ===' echo echo '=== Installing git and zsh ===' apt-get update || die 'apt-get update failed' apt-get install -y --no-install-recommends \ git \ zsh || die 'Could not install git/zsh dependencies' echo echo '=== Installing Oh My Zsh ===' if [ -d "$OH_MY_ZSH_DIR/.git" ]; then echo "Oh My Zsh is already installed at $OH_MY_ZSH_DIR." else rm -f "$INSTALLER_NEW" if command -v curl >/dev/null 2>&1; then curl -fsSL \ "$INSTALLER_URL" \ -o "$INSTALLER_NEW" || die 'Could not download official Oh My Zsh installer with curl' elif command -v wget >/dev/null 2>&1; then wget -qO "$INSTALLER_NEW" \ "$INSTALLER_URL" || die 'Could not download official Oh My Zsh installer with wget' else die 'Neither curl nor wget is available' fi sh -n "$INSTALLER_NEW" || die 'Downloaded Oh My Zsh installer has invalid shell syntax' mv -f "$INSTALLER_NEW" "$INSTALLER" || die 'Could not prepare Oh My Zsh installer' chmod 700 "$INSTALLER" RUNZSH=no CHSH=no KEEP_ZSHRC=yes \ sh "$INSTALLER" --unattended || die 'Official Oh My Zsh installer failed' fi if [ ! -f /root/.zshrc ]; then [ -f "$OH_MY_ZSH_DIR/templates/zshrc.zsh-template" ] || die 'Oh My Zsh zshrc template is missing' cp -f \ "$OH_MY_ZSH_DIR/templates/zshrc.zsh-template" \ /root/.zshrc || die 'Could not create /root/.zshrc' fi ZSH_BIN="$(command -v zsh)" [ -n "$ZSH_BIN" ] || die 'zsh executable not found after installation' CURRENT_SHELL="$(getent passwd root | cut -d: -f7)" if [ "$CURRENT_SHELL" != "$ZSH_BIN" ]; then if command -v chsh >/dev/null 2>&1; then chsh -s "$ZSH_BIN" root || die 'Could not set root default shell with chsh' elif command -v usermod >/dev/null 2>&1; then usermod -s "$ZSH_BIN" root || die 'Could not set root default shell with usermod' else die 'Neither chsh nor usermod is available to set the default shell' fi fi echo echo '=== Verification ===' git --version || die 'git verification failed' zsh --version || die 'zsh verification failed' [ -d "$OH_MY_ZSH_DIR" ] || die 'Oh My Zsh directory is missing' [ -f /root/.zshrc ] || die '/root/.zshrc is missing' echo "root shell: $(getent passwd root | cut -d: -f7)" echo echo '=== SUCCESS ===' echo 'git, zsh and Oh My Zsh are installed.' SCRIPT_EOF # ============================================================ # bookworm-pup64_install-personal-bin # ============================================================ cat > "$BIN_DIR/bookworm-pup64_install-personal-bin" <<'SCRIPT_EOF' #!/bin/sh die() { echo "ERROR: $*" >&2 exit 1 } usage() { cat <<'USAGE_EOF' Usage: bookworm-pup64_install-personal-bin Run this after connecting with SSH agent forwarding, for example: ssh -A root@ne56r.local bookworm-pup64_install-personal-bin USAGE_EOF } [ "$#" -eq 0 ] || { usage exit 64 } [ "$(id -u)" -eq 0 ] || die 'This installer must run as root' REPO='git@github.com:fernando-reis-guimaraes/bin.git' TARGET='/root/bin' command -v git >/dev/null 2>&1 || die 'git is not installed. Run bookworm-pup64_setup-shell first.' command -v ssh-add >/dev/null 2>&1 || die 'ssh-add is unavailable' [ -n "${SSH_AUTH_SOCK:-}" ] || die 'SSH_AUTH_SOCK is empty. Reconnect using ssh -A.' [ -S "$SSH_AUTH_SOCK" ] || die "SSH agent socket does not exist: $SSH_AUTH_SOCK" ssh-add -L >/dev/null 2>&1 || die 'No SSH identities are available from the forwarded agent.' if [ -e "$TARGET" ]; then if [ -d "$TARGET/.git" ]; then ORIGIN="$(git -C "$TARGET" remote get-url origin 2>/dev/null)" [ "$ORIGIN" = "$REPO" ] || die "$TARGET already contains a different Git repository: ${ORIGIN:-unknown}" echo "Personal bin repo is already installed at $TARGET." git -C "$TARGET" status --short --branch exit 0 fi if [ -d "$TARGET" ] && [ -z "$(ls -A "$TARGET" 2>/dev/null)" ]; then rmdir "$TARGET" || die "Could not remove empty directory $TARGET before clone" else die "$TARGET already exists and is not the expected Git repository" fi fi echo '=== Installing personal bin repository ===' echo "Repository: $REPO" echo "Target: $TARGET" echo GIT_SSH_COMMAND='ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new' \ git clone \ "$REPO" \ "$TARGET" || die 'Git clone failed. Confirm that the SSH agent was forwarded and has GitHub access.' ORIGIN="$(git -C "$TARGET" remote get-url origin 2>/dev/null)" [ "$ORIGIN" = "$REPO" ] || die "Unexpected origin after clone: ${ORIGIN:-unknown}" echo echo '=== SUCCESS ===' echo "Personal bin repository installed at $TARGET" git -C "$TARGET" status --short --branch SCRIPT_EOF # ============================================================ # pup64seed.sh # ============================================================ cat > "$BIN_DIR/pup64seed.sh" <<'SCRIPT_EOF' #!/bin/sh usage() { cat <<'USAGE_EOF' Usage: pup64seed.sh USAGE_EOF } die() { echo "ERROR: $*" >&2 exit 1 } [ "$#" -eq 0 ] || { usage exit 64 } [ "$(id -u)" -eq 0 ] || die 'This seed must run as root' BOOTSTRAP_URL='https://web.storage.umapps.net/pup64init.sh' BOOTSTRAP_TMP='/tmp/pup64init.sh' BOOTSTRAP_NEW='/tmp/pup64init.sh.new' if [ -r /etc/DISTRO_SPECS ]; then . /etc/DISTRO_SPECS [ "$DISTRO_NAME" = 'BookwormPup64' ] || die "Unsupported distro: ${DISTRO_NAME:-unknown}" fi rm -f "$BOOTSTRAP_NEW" echo '=== BookwormPup64 Ventoy seed ===' echo "Bootstrap: $BOOTSTRAP_URL" echo if command -v curl >/dev/null 2>&1; then curl -fsSL \ "$BOOTSTRAP_URL" \ > "$BOOTSTRAP_NEW" || die 'Could not download bootstrap with curl' elif command -v wget >/dev/null 2>&1; then wget -qO "$BOOTSTRAP_NEW" \ "$BOOTSTRAP_URL" || die 'Could not download bootstrap with wget' else die 'Neither curl nor wget is available' fi [ -s "$BOOTSTRAP_NEW" ] || die 'Downloaded bootstrap is empty' sh -n "$BOOTSTRAP_NEW" || die 'Downloaded bootstrap has invalid shell syntax' mv -f "$BOOTSTRAP_NEW" "$BOOTSTRAP_TMP" || die 'Could not install downloaded bootstrap' chmod +x "$BOOTSTRAP_TMP" echo 'Bootstrap downloaded and syntax-validated.' echo 'Applying...' echo exec "$BOOTSTRAP_TMP" apply SCRIPT_EOF # ============================================================ # bookworm-pup64_save-now # ============================================================ cat > "$BIN_DIR/bookworm-pup64_save-now" <<'SCRIPT_EOF' #!/bin/sh die() { echo "ERROR: $*" >&2 exit 1 } VENTOY_MOUNT='/data/ventoy' EXCLUDE_FILE='/root/.config/save-exclude.lst' SOURCE='/initrd/pup_rw' TEMP='' cleanup() { if [ -n "$TEMP" ] && [ -f "$TEMP" ]; then rm -f "$TEMP" fi } trap cleanup 0 echo '=== Puppy live save ===' [ -r /etc/DISTRO_SPECS ] || die '/etc/DISTRO_SPECS not found' . /etc/DISTRO_SPECS [ -n "$DISTRO_FILE_PREFIX" ] || die 'DISTRO_FILE_PREFIX is empty' mountpoint -q "$VENTOY_MOUNT" || die "$VENTOY_MOUNT is not mounted. Run bookworm-pup64_mount-data-docs first." [ -e "$SOURCE" ] || die "$SOURCE does not exist" SOURCE="$(readlink -f "$SOURCE")" [ -d "$SOURCE" ] || die "Writable Puppy layer does not exist: $SOURCE" OUTPUT="${VENTOY_MOUNT}/${DISTRO_FILE_PREFIX}save66.tar.gz" TEMP="${OUTPUT}.new" echo "Source: $SOURCE" echo "Output: $OUTPUT" echo echo '=== Current writable layer ===' du -sh "$SOURCE" || die 'Could not inspect writable layer' echo echo '=== Destination free space ===' df -h "$VENTOY_MOUNT" || die 'Could not inspect destination filesystem' echo echo '=== Creating temporary save archive ===' rm -f "$TEMP" if [ -s "$EXCLUDE_FILE" ]; then tar czf "$TEMP" \ -X "$EXCLUDE_FILE" \ -C "$SOURCE" . || die 'Could not create save archive' else tar czf "$TEMP" \ -C "$SOURCE" . || die 'Could not create save archive' fi echo echo '=== Verifying temporary archive ===' gzip -t "$TEMP" || die 'gzip verification failed' tar tzf "$TEMP" >/dev/null || die 'tar verification failed' echo echo '=== Flushing temporary archive ===' sync || die 'Could not flush temporary archive' echo echo '=== Atomically replacing persistent save ===' mv -f \ "$TEMP" \ "$OUTPUT" || die 'Could not install new save archive' TEMP='' echo echo '=== Flushing archive and rename metadata ===' echo 'SUCCESS will only be reported after sync returns.' sync || die 'Final sync failed' echo 'Filesystem flush completed.' echo echo '=== Final archive ===' ls -lh "$OUTPUT" || die 'Saved archive is missing after sync' sha256sum "$OUTPUT" || die 'Could not checksum saved archive' echo echo '=== SUCCESS ===' echo 'Save archive is validated and fully flushed.' echo "$OUTPUT" SCRIPT_EOF # ============================================================ # sync-up.sh # ============================================================ cat > "$BIN_DIR/sync-up.sh" <<'SCRIPT_EOF' #!/bin/sh exec rsync -avhi \ /root/bin-puppy/ \ root@storage.umapps.net:/root/bin-puppy/ SCRIPT_EOF # ============================================================ # README # ============================================================ cat > "$BIN_DIR/bookworm-pup64_readme.md" <<'README_EOF' # BookwormPup64 Stateless Bootstrap, Ventoy Seed, and Optional Persistence ## Primary model This machine is designed to work well without Puppy persistence. The default operational model is: ```text BookwormPup64 ISO | v clean MEMDISK boot | v manual network connection | v pup64seed.sh from Ventoy | v https://web.storage.umapps.net/pup64init.sh | v bookworm-pup64__bootstrap apply | v fully provisioned RAM session ``` The bootstrap is the configuration source of truth. Persistent Puppy save archives remain available as an optional feature, not a requirement for rebuilding the machine. Durable user data lives on the Ventoy data partition: ```text /data/ventoy -> complete Ventoy data filesystem /data/docs -> bind mount of /data/ventoy/docs ``` --- ## Clean boot Boot: ```text Ventoy -> Ctrl+D / MEMDISK -> BookwormPup64 -> default/normal Puppy boot -> DO NOT select Copy to RAM ``` Ventoy MEMDISK has already copied the ISO itself to RAM. A clean boot without a loaded Puppy save archive normally starts as: ```text PUPMODE=5 ``` Verify: ```sh grep -E '^(PUPMODE|PDEV1|PUPSFS|PUPSAVE|PSAVE|PUNIONFS)=' \ /etc/rc.d/PUPSTATE ``` Before running the seed/bootstrap, connect Ethernet or Wi-Fi manually. The bootstrap requires Internet access for Debian packages and GitHub public SSH keys. --- ## Ventoy seed The bootstrap generates: ```text /root/bin-puppy/pup64seed.sh ``` Copy it manually to the root of the Ventoy data partition: ```sh cp -f \ /root/bin-puppy/pup64seed.sh \ /data/ventoy/pup64seed.sh sync ``` The seed intentionally remains tiny. It: 1. verifies that it is running as root; 2. verifies BookwormPup64 when distro metadata is available; 3. downloads: `https://web.storage.umapps.net/pup64init.sh`; 4. validates the downloaded script with `sh -n`; 5. executes: `pup64init.sh apply`. On a future clean boot, mount/open the Ventoy data partition and run: ```sh ./pup64seed.sh ``` The seed itself does not configure Wi-Fi. Network access must already be available. --- ## Bootstrap responsibilities `bookworm-pup64__bootstrap apply` creates all canonical tools and converges the current RAM session to the desired machine state. It configures: - OpenSSH; - root public-key-only login; - SSH agent forwarding for the manual personal-bin installer; - GitHub authorized keys from `fernando-reis-guimaraes`; - SSH TCP/22 firewall access on all reachable IPv4 and IPv6 networks; - TCP Wrappers policy; - Avahi/mDNS and `.local`; - UDP/5353 firewall access for mDNS; - `/data/ventoy`; - `/data/docs`; - boot hooks in `/etc/rc.d/rc.local`; - external-display-only policy for laptops with a phantom/internal panel; - `/root/Startup/bookworm-pup64_external-display-only`; - the Ventoy seed; - git, zsh and Oh My Zsh; - the manual personal-bin Git/SSH installer; - all `/root/bin-puppy` helper CLIs; - optional Puppy save support. The bootstrap is intended to be idempotent and safe to rerun. --- ## External-display-only policy The NE56R can expose the disconnected laptop panel to X as an active `LVDS` display. X may then create a spanning desktop in which the JWM tray and ROX desktop icons are placed on the invisible internal panel. The bootstrap creates: ```text /root/bin-puppy/bookworm-pup64_external-display-only /root/bin-puppy/bookworm-pup64_setup-external-display-only /root/Startup/bookworm-pup64_external-display-only ``` Inspect current display state: ```sh bookworm-pup64_external-display-only status ``` Apply immediately: ```sh bookworm-pup64_external-display-only apply ``` The policy: 1. requires a connected external display; 2. enables the chosen external display first; 3. makes it primary at `0x0`; 4. disables outputs whose names look internal: `eDP*`, `EDP*`, `LVDS*`, `lvds*`, `DSI*`, or `dsi*`; 5. refuses to disable anything if no connected external display exists. The `/root/Startup` hook reapplies this after the graphical desktop starts. --- ## SSH Install/configure OpenSSH: ```sh bookworm-pup64_setup-sshd ``` Allow SSH from every reachable network: ```sh bookworm-pup64_allow-ssh-all ``` SSH policy: ```text TCP/22 allowed root + public key allowed root + password disabled password auth disabled keyboard-interactive disabled ``` TCP Wrappers: ```text /etc/hosts.allow: sshd: ALL ALL: LOCAL /etc/hosts.deny: ALL: ALL ``` --- ## mDNS / Bonjour Install/configure: ```sh bookworm-pup64_setup-mdns ``` Ensure runtime state: ```sh bookworm-pup64_start-mdns ``` Test from macOS: ```sh dns-sd -G v4v6 .local ssh root@.local ``` `dig .local` is not the correct mDNS test. --- ## Durable data Run: ```sh bookworm-pup64_mount-data-docs ``` Layout: ```text /data/ ├── ventoy/ -> complete Ventoy filesystem └── docs/ -> bind mount of /data/ventoy/docs ``` The bootstrap installs an automatic boot hook using: ```sh bookworm-pup64_mount-data-docs --wait ``` --- ## Shell tooling and personal `~/bin` The Puppy-specific provisioning toolbox lives in: ```text /root/bin-puppy/ ``` `/root/bin` is intentionally reserved for the personal GitHub repository: ```text git@github.com:fernando-reis-guimaraes/bin.git ``` The bootstrap installs `git`, `zsh`, and Oh My Zsh using the official Oh My Zsh installation script. The helper is: ```sh bookworm-pup64_setup-shell ``` The personal repository is deliberately **not** cloned during bootstrap, because its Git SSH authentication is expected to come from a forwarded SSH agent. Connect with agent forwarding: ```sh ssh -A root@.local ``` Then install the personal repo: ```sh bookworm-pup64_install-personal-bin ``` The helper clones with Git SSH into: ```text /root/bin ``` It requires a working forwarded agent and refuses to replace an unrelated existing `/root/bin` directory. --- ## Optional Puppy save archive The save helper remains available: ```sh bookworm-pup64_save-now ``` It writes: ```text /data/ventoy/dpupbw64save66.tar.gz ``` The filename is derived from `DISTRO_FILE_PREFIX`. The helper: 1. archives `/initrd/pup_rw`; 2. validates gzip; 3. validates tar; 4. calls `sync`; 5. atomically replaces the previous archive; 6. calls `sync` again; 7. reports success only after the final flush returns. ### Automatic save policy The bootstrap automatically calls `bookworm-pup64_save-now` only when the current Puppy boot reports: ```text PUPMODE=66 ``` A clean stateless boot using: ```text PUPMODE=5 ``` is deliberately **not** auto-saved. This keeps the default clean-boot + bootstrap architecture stateless. You can still manually call `bookworm-pup64_save-now` in a compatible live session when you explicitly want a checkpoint. --- ## CLI inventory Canonical files: ```text /root/bin-puppy/ ├── bookworm-pup64__bootstrap ├── bookworm-pup64_allow-ssh-all ├── bookworm-pup64_external-display-only ├── bookworm-pup64_mount-data-docs ├── bookworm-pup64_readme.md ├── bookworm-pup64_save-now ├── bookworm-pup64_setup-external-display-only ├── bookworm-pup64_setup-shell ├── bookworm-pup64_install-personal-bin ├── bookworm-pup64_setup-mdns ├── bookworm-pup64_setup-sshd ├── bookworm-pup64_start-mdns ├── pup64seed.sh └── sync-up.sh ``` Executable BookwormPup64 CLIs are also symlinked into `/usr/local/bin`. --- ## Sync the Puppy toolbox Run: ```sh /root/bin-puppy/sync-up.sh ``` It sends: ```text /root/bin-puppy/ -> root@storage.umapps.net:/root/bin-puppy/ ``` It does not upload the Puppy save archive. --- ## Bootstrap usage ```text bookworm-pup64__bootstrap apply ``` Calling it with no parameters or any other parameter prints usage and returns a non-zero status. --- ## Not included yet Deliberately outside the current bootstrap: - Wi-Fi credential automation; - Tailscale installation/configuration; - automatic upload of Puppy save archives; - ICMP echo enablement; - automatic shutdown/reboot; - safe physical Ventoy eject helper. README_EOF # ============================================================ # Permissions and command symlinks # ============================================================ echo echo '=== Installing command symlinks ===' for SCRIPT in \ bookworm-pup64_setup-sshd \ bookworm-pup64_allow-ssh-all \ bookworm-pup64_setup-mdns \ bookworm-pup64_start-mdns \ bookworm-pup64_mount-data-docs \ bookworm-pup64_external-display-only \ bookworm-pup64_setup-external-display-only \ bookworm-pup64_setup-shell \ bookworm-pup64_install-personal-bin \ bookworm-pup64_save-now do chmod +x "$BIN_DIR/$SCRIPT" || die "Could not chmod $SCRIPT" sh -n "$BIN_DIR/$SCRIPT" || die "Syntax error in $SCRIPT" ln -sf \ "$BIN_DIR/$SCRIPT" \ "$LOCAL_BIN/$SCRIPT" || die "Could not link $SCRIPT" done chmod +x \ "$BIN_DIR/pup64seed.sh" \ "$BIN_DIR/sync-up.sh" sh -n "$BIN_DIR/pup64seed.sh" || die 'Syntax error in pup64seed.sh' sh -n "$BIN_DIR/sync-up.sh" || die 'Syntax error in sync-up.sh' # ============================================================ # Clean legacy helpers # ============================================================ echo echo '=== Removing obsolete helpers ===' for OLD in \ setup-sshd \ allow-ssh-all \ allow-ssh-lan \ setup-mdns \ start-mdns \ mount-data \ mount-data-docs \ puppy-save-now \ diagnose-ssh-network \ bookworm-pup64_puppy-save-now do rm -f \ "$BIN_DIR/$OLD" \ "$LOCAL_BIN/$OLD" done # ============================================================ # Provision SSH first # ============================================================ echo echo '=== APPLY: OpenSSH ===' "$BIN_DIR/bookworm-pup64_setup-sshd" || die 'OpenSSH setup failed' # ============================================================ # Firewall + TCP Wrappers # ============================================================ echo echo '=== APPLY: SSH reachability ===' "$BIN_DIR/bookworm-pup64_allow-ssh-all" || die 'SSH firewall setup failed' # ============================================================ # mDNS # ============================================================ echo echo '=== APPLY: mDNS ===' "$BIN_DIR/bookworm-pup64_setup-mdns" || die 'mDNS setup failed' # ============================================================ # Shell tooling # ============================================================ echo echo '=== APPLY: git + zsh + Oh My Zsh ===' "$BIN_DIR/bookworm-pup64_setup-shell" || die 'Shell tooling setup failed' # ============================================================ # Optional rsync dependency # ============================================================ echo echo '=== APPLY: rsync dependency ===' if ! command -v rsync >/dev/null 2>&1; then apt-get install -y --no-install-recommends rsync || die 'Could not install rsync' fi # ============================================================ # Durable data # ============================================================ echo echo '=== APPLY: durable data mounts ===' "$BIN_DIR/bookworm-pup64_mount-data-docs" || die 'Could not mount persistent data' echo echo '=== Installing data mount boot hook ===' touch "$RC_LOCAL" || die 'Could not access rc.local' sed -i \ '/# BEGIN BOOKWORM PUP64 DATA/,/# END BOOKWORM PUP64 DATA/d' \ "$RC_LOCAL" cat >> "$RC_LOCAL" <<'RC_EOF' # BEGIN BOOKWORM PUP64 DATA /root/bin-puppy/bookworm-pup64_mount-data-docs --wait >/tmp/bookworm-pup64_mount-data-docs.log 2>&1 & # END BOOKWORM PUP64 DATA RC_EOF chmod +x "$RC_LOCAL" # ============================================================ # External display policy # ============================================================ echo echo '=== APPLY: external display policy ===' "$BIN_DIR/bookworm-pup64_setup-external-display-only" || die 'Could not install external display policy' # ============================================================ # Optional automatic Puppy save # ============================================================ echo echo '=== APPLY: optional Puppy save archive ===' AUTO_SAVE_STATUS='skipped' CURRENT_PUPMODE="$( sed -n "s/^PUPMODE=['\"]*\([0-9][0-9]*\)['\"]*$/\1/p" \ /etc/rc.d/PUPSTATE 2>/dev/null | head -n 1 )" if [ "$CURRENT_PUPMODE" = '66' ]; then if mountpoint -q /data/ventoy && [ -d /initrd/pup_rw ] then "$BIN_DIR/bookworm-pup64_save-now" || die 'Could not save Puppy state' AUTO_SAVE_STATUS='saved' else echo 'WARNING: PUPMODE=66 but save prerequisites are not available.' echo 'Automatic save skipped; bootstrap will continue.' fi else echo "Automatic save skipped: PUPMODE=${CURRENT_PUPMODE:-unknown}." echo 'Automatic save is enabled only for PUPMODE=66.' echo 'Manual checkpoints remain available through bookworm-pup64_save-now.' fi # ============================================================ # Final verification # ============================================================ echo echo '=== Final verification ===' echo echo '-- Hostname --' hostname echo echo '-- Addresses --' ip -br addr echo echo '-- SSH --' ss -lntp | grep ':22 ' || die 'SSH listener disappeared' echo echo '-- mDNS --' ss -lunp | grep ':5353 ' || die 'mDNS listener disappeared' echo echo '-- TCP Wrappers --' cat /etc/hosts.allow cat /etc/hosts.deny echo echo '-- Data mounts --' findmnt /data/ventoy || die '/data/ventoy is not mounted' findmnt /data/docs || die '/data/docs is not mounted' echo echo '-- Puppy mode --' grep -E '^(PUPMODE|PDEV1|PUPSFS|PUPSAVE|PSAVE|PUNIONFS)=' \ /etc/rc.d/PUPSTATE || true echo echo '-- External display policy --' ls -l /root/Startup/bookworm-pup64_external-display-only || die 'External display Startup hook is missing' if [ -S /tmp/.X11-unix/X0 ]; then DISPLAY=:0 "$BIN_DIR/bookworm-pup64_external-display-only" status || true fi echo echo '-- Ventoy seed --' ls -l "$BIN_DIR/pup64seed.sh" || die 'pup64seed.sh is missing' echo echo '-- Automatic save --' echo "$AUTO_SAVE_STATUS" echo echo '-- Canonical files --' ls -lah "$BIN_DIR" echo echo '=== BOOTSTRAP SUCCESS ===' echo echo "SSH: ssh root@$(hostname).local" echo echo "Automatic Puppy save: $AUTO_SAVE_STATUS" echo echo 'Ventoy seed generated at:' echo ' /root/bin-puppy/pup64seed.sh' echo echo 'Copy it manually to Ventoy when desired:' echo ' cp -f /root/bin-puppy/pup64seed.sh /data/ventoy/pup64seed.sh' echo ' sync' echo echo 'Default operational model:' echo ' clean MEMDISK boot' echo ' -> connect network' echo ' -> run pup64seed.sh from Ventoy' echo ' -> online bootstrap converges the RAM session' echo echo 'Personal ~/bin repo (manual, after ssh -A):' echo ' bookworm-pup64_install-personal-bin'