diff --git a/image-recipe/_archived/build-auto-installer-iso.sh b/image-recipe/_archived/build-auto-installer-iso.sh index f9b8da41..06d11c7e 100755 --- a/image-recipe/_archived/build-auto-installer-iso.sh +++ b/image-recipe/_archived/build-auto-installer-iso.sh @@ -1492,6 +1492,83 @@ mkdir -p "$ARCH_DIR/scripts" cp "$WORK_DIR/load-container-images.sh" "$ARCH_DIR/scripts/" cp "$WORK_DIR/archipelago-load-images.service" "$ARCH_DIR/scripts/" +# First-boot per-device secrets: the squashfs bakes one TLS key and one set +# of SSH host keys at build time, so every device flashed from the same image +# would share them. Regenerate both on the installed system's first boot, +# before the network-facing services come up. +echo " Creating first-boot secrets regeneration service..." +cat > "$WORK_DIR/archipelago-first-boot-secrets.service" <<'SECRETSSERVICE' +[Unit] +Description=Regenerate per-device secrets (TLS key, SSH host keys) +DefaultDependencies=no +After=local-fs.target +Before=ssh.service nginx.service archipelago.service +ConditionPathExists=!/var/lib/archipelago/.secrets-regenerated + +[Service] +Type=oneshot +ExecStart=/opt/archipelago/scripts/first-boot-secrets.sh +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target +SECRETSSERVICE + +cat > "$WORK_DIR/first-boot-secrets.sh" <<'SECRETSSCRIPT' +#!/bin/bash +# Replace image-baked secrets with device-unique ones on first boot. +# Never leaves the device without working keys: new material is generated +# to a staging path first and only swapped in on success. +set -u + +LOG=/var/log/archipelago-first-boot-secrets.log +MARKER=/var/lib/archipelago/.secrets-regenerated +[ -f "$MARKER" ] && exit 0 +mkdir -p /var/lib/archipelago +NODE_NAME=$(hostname 2>/dev/null || echo archipelago) + +echo "$(date): regenerating per-device secrets" >> "$LOG" + +# 1. Self-signed TLS: fresh keypair with this device's hostname in the SAN +# (server.set-name regenerates again if the node is renamed later) +mkdir -p /etc/archipelago/ssl +if openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -keyout /etc/archipelago/ssl/archipelago.key.new \ + -out /etc/archipelago/ssl/archipelago.crt.new \ + -subj "/C=XX/ST=Bitcoin/L=Node/O=Archipelago/CN=${NODE_NAME}" \ + -addext "subjectAltName=DNS:${NODE_NAME},DNS:${NODE_NAME}.local,DNS:archipelago,DNS:archipelago.local,DNS:localhost,IP:127.0.0.1" \ + >> "$LOG" 2>&1; then + mv /etc/archipelago/ssl/archipelago.key.new /etc/archipelago/ssl/archipelago.key + mv /etc/archipelago/ssl/archipelago.crt.new /etc/archipelago/ssl/archipelago.crt + chmod 600 /etc/archipelago/ssl/archipelago.key + echo "$(date): TLS keypair regenerated (CN=${NODE_NAME})" >> "$LOG" + systemctl try-reload-or-restart nginx >> "$LOG" 2>&1 || true +else + rm -f /etc/archipelago/ssl/archipelago.key.new /etc/archipelago/ssl/archipelago.crt.new + echo "$(date): WARNING: TLS regeneration failed, keeping baked key" >> "$LOG" +fi + +# 2. SSH host keys: generate a full fresh set in staging, then swap +STAGING=$(mktemp -d) +mkdir -p "$STAGING/etc/ssh" +if ssh-keygen -A -f "$STAGING" >> "$LOG" 2>&1 && ls "$STAGING"/etc/ssh/ssh_host_*_key >/dev/null 2>&1; then + rm -f /etc/ssh/ssh_host_* + mv "$STAGING"/etc/ssh/ssh_host_* /etc/ssh/ + echo "$(date): SSH host keys regenerated" >> "$LOG" + systemctl try-reload-or-restart ssh >> "$LOG" 2>&1 || true +else + echo "$(date): WARNING: ssh-keygen -A failed, keeping baked host keys" >> "$LOG" +fi +rm -rf "$STAGING" + +touch "$MARKER" +echo "$(date): per-device secrets done" >> "$LOG" +SECRETSSCRIPT + +chmod +x "$WORK_DIR/first-boot-secrets.sh" +cp "$WORK_DIR/first-boot-secrets.sh" "$ARCH_DIR/scripts/" +cp "$WORK_DIR/archipelago-first-boot-secrets.service" "$ARCH_DIR/scripts/" + # Tor setup: copy torrc and create first-boot setup script mkdir -p "$ARCH_DIR/scripts/tor" if [ -f "$SCRIPT_DIR/../../scripts/tor/torrc.template" ]; then @@ -2401,6 +2478,13 @@ if [ -d "$BOOT_MEDIA/archipelago/container-images" ]; then if [ -f "$BOOT_MEDIA/archipelago/scripts/archipelago-load-images.service" ]; then cp "$BOOT_MEDIA/archipelago/scripts/archipelago-load-images.service" /mnt/target/etc/systemd/system/ fi + if [ -f "$BOOT_MEDIA/archipelago/scripts/first-boot-secrets.sh" ]; then + cp "$BOOT_MEDIA/archipelago/scripts/first-boot-secrets.sh" /mnt/target/opt/archipelago/scripts/ + chmod +x /mnt/target/opt/archipelago/scripts/first-boot-secrets.sh + fi + if [ -f "$BOOT_MEDIA/archipelago/scripts/archipelago-first-boot-secrets.service" ]; then + cp "$BOOT_MEDIA/archipelago/scripts/archipelago-first-boot-secrets.service" /mnt/target/etc/systemd/system/ + fi if [ -f "$BOOT_MEDIA/archipelago/scripts/setup-tor.sh" ]; then cp "$BOOT_MEDIA/archipelago/scripts/setup-tor.sh" /mnt/target/opt/archipelago/scripts/ chmod +x /mnt/target/opt/archipelago/scripts/setup-tor.sh @@ -2942,8 +3026,8 @@ echo "" >> "$LOG" echo "=== Services ===" >> "$LOG" for svc in nginx archipelago archipelago-kiosk archipelago-load-images \ - archipelago-first-boot-containers archipelago-setup-tor \ - console-setup; do + archipelago-first-boot-containers archipelago-first-boot-secrets \ + archipelago-setup-tor console-setup; do STATUS=$(systemctl is-active "$svc" 2>/dev/null || echo "inactive") ENABLED=$(systemctl is-enabled "$svc" 2>/dev/null || echo "disabled") printf " %-40s %s / %s\n" "$svc" "$STATUS" "$ENABLED" >> "$LOG" @@ -3073,6 +3157,7 @@ chroot /mnt/target systemctl enable archipelago-bootstrap-switchover.timer 2>/de chroot /mnt/target systemctl enable archipelago.service 2>/dev/null || true chroot /mnt/target systemctl enable nginx.service 2>/dev/null || true chroot /mnt/target systemctl enable archipelago-load-images.service 2>/dev/null || true +chroot /mnt/target systemctl enable archipelago-first-boot-secrets.service 2>/dev/null || true chroot /mnt/target systemctl enable archipelago-setup-tor.service 2>/dev/null || true chroot /mnt/target systemctl enable archipelago-first-boot-containers.service 2>/dev/null || true chroot /mnt/target systemctl enable archipelago-kiosk.service 2>/dev/null || true @@ -3534,6 +3619,16 @@ else "$INSTALLER_ISO" fi +# Checksums: emit next to the ISO so releases can be verified and the +# release ceremony can sign them (publish step signs; the build host +# never holds the release key) +( + cd "$(dirname "$OUTPUT_ISO")" || exit 0 + ISO_NAME=$(basename "$OUTPUT_ISO") + sha256sum "$ISO_NAME" > "$ISO_NAME.sha256" + echo " Checksum: $(cat "$ISO_NAME.sha256")" +) + echo "" if [ "$UNBUNDLED" = "1" ]; then echo "UNBUNDLED AUTO-INSTALLER ISO CREATED" diff --git a/scripts/sign-iso-checksums.sh b/scripts/sign-iso-checksums.sh new file mode 100755 index 00000000..318fcc99 --- /dev/null +++ b/scripts/sign-iso-checksums.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Sign an ISO's checksums with the release root (counterpart to sign-manifest.sh). +# +# Run: bash scripts/sign-iso-checksums.sh path/to/archipelago-X.Y.Z.iso +# Then: paste your 24-word release master mnemonic, press Enter, then Ctrl-D. +# +# Writes .sha256.json next to the ISO — a JSON document carrying the +# artifact name, size and sha256, signed by the pinned release-root anchor +# (same detached-Ed25519 scheme as the OTA manifest and app catalog). +# Verify anywhere with: archipelago ceremony verify .sha256.json +# +# The mnemonic is read from the terminal only (never stored, never in shell +# history). The build host never holds the release key: build emits the plain +# .sha256; the publisher signs with this script during the ceremony. +set -euo pipefail + +ISO="${1:-}" +[ -n "$ISO" ] || { echo "Usage: $0 path/to/image.iso"; exit 1; } +[ -f "$ISO" ] || { echo "Error: $ISO not found"; exit 1; } + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +# Use ONLY a prebuilt signer — never compile here (compiling caused hangs in +# the earlier catalog ceremony). Prefer the repo's release build. +BIN="" +for candidate in "$REPO/core/target/release/archipelago" /tmp/archy-sign-bin/release/archipelago; do + if [[ -x "$candidate" ]]; then BIN="$candidate"; break; fi +done +if [[ -z "$BIN" ]]; then + echo "⏳ No prebuilt signer found. Build one first:" + echo " (cd core && cargo build --release -p archipelago)" + echo " Nothing was changed." + exit 0 +fi + +ISO_NAME="$(basename "$ISO")" +ISO_DIR="$(cd "$(dirname "$ISO")" && pwd)" +OUT="$ISO_DIR/$ISO_NAME.sha256.json" + +echo "Hashing $ISO_NAME (this can take a minute on a large ISO)..." +SHA256="$(sha256sum "$ISO" | awk '{print $1}')" +SIZE="$(wc -c < "$ISO" | tr -d ' ')" + +cat > "$OUT" <