From 3a9954f9ec6391aba0dd0639e5a7f3d9a5f3fcd2 Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Tue, 12 May 2026 12:17:59 +0200 Subject: [PATCH] Boot live installer session and narrow install-time contract (Sam & Codex) --- README.md | 2 +- build.cfg | 6 +- build.sh | 276 +++++++++++++++++- doc/ISO-LIVE-GUI-SETUP-PROPOSAL.md | 7 +- firstboot/firstboot.sh | 19 +- firstboot/gui/qml-installer/main.cpp | 51 ++-- .../gui/qml-installer/pages/CompletePage.qml | 2 +- .../gui/qml-installer/pages/UserPage.qml | 79 +---- firstboot/gui/test-config-format.sh | 2 +- firstboot/integration-test.sh | 5 +- firstboot/setup-import.sh | 28 +- .../clawdie-live-installer-launch.sh | 19 ++ live/installer-session/lightdm-live.conf | 11 + live/installer-session/xprofile | 5 + packages/pkg-list-desktop-base.txt | 2 +- packages/pkg-list-host.txt | 4 +- packages/pkg-list-jails.txt | 4 +- packages/pkg-list-live-installer.txt | 3 + packages/pkg-list-lumina.txt | 3 +- packages/pkg-list-nvidia-590.txt | 4 +- packages/pkg-list-nvidia-all.txt | 2 +- 21 files changed, 359 insertions(+), 175 deletions(-) create mode 100755 live/installer-session/clawdie-live-installer-launch.sh create mode 100644 live/installer-session/lightdm-live.conf create mode 100755 live/installer-session/xprofile create mode 100644 packages/pkg-list-live-installer.txt diff --git a/README.md b/README.md index fdf027dd..8d184ef5 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ sudo ./build.sh --skip-fetch - Wizard screen 1: Tailscale auth key (pre-filled if baked into build.cfg) - Wizard screen 2: Assistant name + domain - Wizard screen 3: Timezone - - Optional: LLM provider, Telegram + - Provider keys, Telegram, and browser sign-in are configured after first boot in the controlplane - Setup runs automatically (5–10 min) 4. **Desktop boots** (if display detected) or headless mode (VPS/cloud) diff --git a/build.cfg b/build.cfg index 44e554dd..f90420ee 100644 --- a/build.cfg +++ b/build.cfg @@ -4,8 +4,10 @@ FREEBSD_VERSION="15.0-RELEASE" FREEBSD_ARCH="amd64" -FREEBSD_MEMSTICK_URL="https://download.freebsd.org/releases/ISO-IMAGES/15.0/FreeBSD-${FREEBSD_VERSION}-${FREEBSD_ARCH}-memstick.img" -FREEBSD_MEMSTICK_SHA256_URL="${FREEBSD_MEMSTICK_URL}.SHA256" +FREEBSD_RELEASE_SERIES="${FREEBSD_VERSION%-RELEASE}" +FREEBSD_ISO_BASE_URL="https://download.freebsd.org/releases/${FREEBSD_ARCH}/${FREEBSD_ARCH}/ISO-IMAGES/${FREEBSD_RELEASE_SERIES}" +FREEBSD_MEMSTICK_URL="${FREEBSD_ISO_BASE_URL}/FreeBSD-${FREEBSD_VERSION}-${FREEBSD_ARCH}-memstick.img" +FREEBSD_MEMSTICK_SHA256_URL="${FREEBSD_ISO_BASE_URL}/CHECKSUM.SHA256-FreeBSD-${FREEBSD_VERSION}-${FREEBSD_ARCH}" # Output image # User-facing date format: DD.mmm.YYYY (per AGENTS.md convention) diff --git a/build.sh b/build.sh index d359ef13..02788d96 100755 --- a/build.sh +++ b/build.sh @@ -15,6 +15,7 @@ # Requirements (run on FreeBSD host): # pkg install curl # for fetching # pkg install node24 npm-node24 # to bundle clawdie-ai node_modules for offline firstboot +# pkg install qt6-base qt6-declarative qt6-buildtools # to build the live QML installer # pkg install (root) # for step 5-6 (mdconfig, mount) # # The tmp/packages/ directory produced here is dual-purpose: @@ -30,6 +31,7 @@ PKG_REPO_DIR="${TMP_DIR}/packages" NPM_GLOBALS_DIR="${TMP_DIR}/npm-globals" CACHE_DIR="${TMP_DIR}/cache" OUTPUT_DIR="${TMP_DIR}/output" +LIVE_SESSION_DIR="${SCRIPT_DIR}/live/installer-session" . "${SCRIPT_DIR}/build.cfg" @@ -96,10 +98,230 @@ pkg_list_all() { "${PKG_LIST_DIR}/pkg-list-jails.txt" \ "${PKG_LIST_DIR}/pkg-list-desktop-base.txt" \ "${PKG_LIST_DIR}/pkg-list-lumina.txt" \ + "${PKG_LIST_DIR}/pkg-list-live-installer.txt" \ "${PKG_LIST_DIR}/pkg-list-nvidia-all.txt" \ | grep -v '^#' | grep -v '^$' | sort -u } +pkg_list_live_installer() { + cat \ + "${PKG_LIST_DIR}/pkg-list-desktop-base.txt" \ + "${PKG_LIST_DIR}/pkg-list-lumina.txt" \ + "${PKG_LIST_DIR}/pkg-list-live-installer.txt" \ + | grep -v '^#' \ + | grep -v '^$' \ + | grep -v -E '^(hal|lumina-filemanager|lumina-open)$' \ + | sort -u +} + +set_config_line() { + _file="$1" + _assignment="$2" + _name=$(echo "$_assignment" | cut -d= -f1) + mkdir -p "$(dirname "$_file")" + touch "$_file" + if grep -q "^${_name}=" "$_file" 2>/dev/null; then + sed -i '' "s|^${_name}=.*|${_assignment}|" "$_file" + else + echo "$_assignment" >> "$_file" + fi +} + +pkg_archive_for() { + _pkg_name="$1" + find "${PKG_REPO_DIR}/All" -type f -name "${_pkg_name}-*.pkg" | sort | tail -1 +} + +mount_memstick_rootfs() { + _memstick_mount="$1" + _memstick_slice_img="${CACHE_DIR}/memstick-freebsd-slice.img" + _memstick_ufs_img="${CACHE_DIR}/memstick-rootfs.img" + + mkdir -p "$_memstick_mount" + rm -f "$_memstick_slice_img" "$_memstick_ufs_img" + + MD_SRC=$(mdconfig -a -t vnode -f "$MEMSTICK") + + _slice_meta=$( + fdisk "/dev/${MD_SRC}" \ + | awk ' + /sysid 165 / { want = 1; next } + want && $1 == "start" { + gsub(",", "", $2) + gsub(",", "", $4) + print $2 " " $4 + exit + } + ' + ) + _slice_start=$(echo "$_slice_meta" | awk '{print $1}') + _slice_size=$(echo "$_slice_meta" | awk '{print $2}') + if [ -z "${_slice_start:-}" ] || [ -z "${_slice_size:-}" ]; then + echo "ERROR: could not determine FreeBSD slice start in ${MEMSTICK}" + mdconfig -d -u "${MD_SRC}" 2>/dev/null || true + exit 1 + fi + + truncate -s "$((_slice_size * 512))" "$_memstick_slice_img" + dd if="$MEMSTICK" of="$_memstick_slice_img" bs=512 skip="$_slice_start" conv=sparse status=none + + MD_SLICE=$(mdconfig -a -t vnode -f "$_memstick_slice_img") + _ufs_meta=$( + bsdlabel "/dev/${MD_SLICE}" 2>/dev/null \ + | awk '$1 == "a:" { print $2 " " $3; exit }' + ) + _ufs_size=$(echo "$_ufs_meta" | awk '{print $1}') + _ufs_offset=$(echo "$_ufs_meta" | awk '{print $2}') + if [ -z "${_ufs_size:-}" ] || [ -z "${_ufs_offset:-}" ]; then + echo "ERROR: could not determine UFS root partition offset in ${MEMSTICK}" + mdconfig -d -u "${MD_SLICE}" 2>/dev/null || true + mdconfig -d -u "${MD_SRC}" 2>/dev/null || true + exit 1 + fi + + truncate -s "$((_ufs_size * 512))" "$_memstick_ufs_img" + dd if="$_memstick_slice_img" of="$_memstick_ufs_img" bs=512 skip="$_ufs_offset" conv=sparse status=none + + MD_ROOTFS=$(mdconfig -a -t vnode -f "$_memstick_ufs_img") + mount -r -t ufs "/dev/${MD_ROOTFS}" "$_memstick_mount" +} + +cleanup_memstick_rootfs() { + _memstick_mount="$1" + + umount "$_memstick_mount" 2>/dev/null || true + [ -n "${MD_ROOTFS:-}" ] && mdconfig -d -u "${MD_ROOTFS}" 2>/dev/null || true + [ -n "${MD_SLICE:-}" ] && mdconfig -d -u "${MD_SLICE}" 2>/dev/null || true + [ -n "${MD_SRC:-}" ] && mdconfig -d -u "${MD_SRC}" 2>/dev/null || true + rm -f "${CACHE_DIR}/memstick-freebsd-slice.img" "${CACHE_DIR}/memstick-rootfs.img" + MD_ROOTFS="" + MD_SLICE="" + MD_SRC="" +} + +verify_memstick_cache() { + if [ ! -f "$MEMSTICK" ] || [ ! -f "${MEMSTICK}.SHA256" ]; then + return 1 + fi + _expected_sha=$( + awk -v image="$(basename "$MEMSTICK")" ' + $2 == "(" image ")" { print $4; exit } + ' "${MEMSTICK}.SHA256" + ) + [ -n "${_expected_sha:-}" ] || return 1 + [ "$(/sbin/sha256 -q "$MEMSTICK")" = "$_expected_sha" ] +} + +install_image_bootcode() { + _image_md="$1" + _image_root="$2" + + if [ ! -f "${_image_root}/boot/mbr" ] || [ ! -f "${_image_root}/boot/boot" ]; then + echo "ERROR: missing bootcode files in ${_image_root}/boot" + exit 1 + fi + + gpart bootcode -b "${_image_root}/boot/mbr" "/dev/${_image_md}" + gpart bootcode -b "${_image_root}/boot/boot" "/dev/${_image_md}s1" +} + +build_live_qml_installer() { + QML_BUILD_DIR="${CACHE_DIR}/qml-installer-build" + QML_SOURCE_DIR="${SCRIPT_DIR}/firstboot/gui/qml-installer" + QML_INSTALLER_BIN="${CACHE_DIR}/clawdie-qml-installer" + + if ! command -v qmake6 >/dev/null 2>&1; then + echo "ERROR: qmake6 not found on build host." + echo "Install Qt6 build tools, then rerun build.sh." + echo "Example (FreeBSD): sudo pkg install -y qt6-base qt6-declarative qt6-buildtools" + exit 1 + fi + + echo "==> [5b/7] Building live QML installer..." + rm -rf "$QML_BUILD_DIR" + mkdir -p "$QML_BUILD_DIR" + + ( + cd "$QML_BUILD_DIR" + qmake6 "${QML_SOURCE_DIR}/qml-installer.pro" + make + ) + + cp "${QML_BUILD_DIR}/clawdie-qml-installer" "$QML_INSTALLER_BIN" + chmod +x "$QML_INSTALLER_BIN" +} + +install_live_runtime_packages() { + echo " Installing live GUI runtime packages into image..." + + _pkg_files="" + for _pkg in $(pkg_list_live_installer); do + _archive=$(pkg_archive_for "$_pkg") + if [ -z "${_archive:-}" ]; then + echo "ERROR: missing package archive for ${_pkg}" + exit 1 + fi + _pkg_files="${_pkg_files} ${_archive}" + done + + mkdir -p "${MOUNT_POINT}/dev" "${MOUNT_POINT}/proc" + _mounted_devfs=0 + _mounted_procfs=0 + + if mount -t devfs devfs "${MOUNT_POINT}/dev"; then + _mounted_devfs=1 + fi + if mount -t procfs proc "${MOUNT_POINT}/proc"; then + _mounted_procfs=1 + fi + + if ! env ASSUME_ALWAYS_YES=yes HANDLE_RC_SCRIPTS=no \ + /usr/local/sbin/pkg-static -o PKG_TRIGGERS_ENABLE=false -r "$MOUNT_POINT" add -f ${_pkg_files}; then + [ "$_mounted_procfs" -eq 1 ] && umount "${MOUNT_POINT}/proc" 2>/dev/null || true + [ "$_mounted_devfs" -eq 1 ] && umount "${MOUNT_POINT}/dev" 2>/dev/null || true + echo "ERROR: failed to install live GUI runtime packages into image" + exit 1 + fi + + [ "$_mounted_procfs" -eq 1 ] && umount "${MOUNT_POINT}/proc" 2>/dev/null || true + [ "$_mounted_devfs" -eq 1 ] && umount "${MOUNT_POINT}/dev" 2>/dev/null || true +} + +configure_live_installer_session() { + echo " Configuring live installer session..." + + mkdir -p "${MOUNT_POINT}/usr/local/bin" + install -m 0755 "${CACHE_DIR}/clawdie-qml-installer" \ + "${MOUNT_POINT}/usr/local/bin/clawdie-qml-installer" + install -m 0755 "${LIVE_SESSION_DIR}/clawdie-live-installer-launch.sh" \ + "${MOUNT_POINT}/usr/local/bin/clawdie-live-installer-launch.sh" + + mkdir -p "${MOUNT_POINT}/usr/local/etc/lightdm/lightdm.conf.d" + install -m 0644 "${LIVE_SESSION_DIR}/lightdm-live.conf" \ + "${MOUNT_POINT}/usr/local/etc/lightdm/lightdm.conf.d/50-clawdie-live.conf" + + if ! /usr/sbin/pw -R "$MOUNT_POINT" usershow clawdie-installer >/dev/null 2>&1; then + /usr/sbin/pw -R "$MOUNT_POINT" useradd clawdie-installer \ + -m \ + -s /bin/sh \ + -c "Clawdie Live Installer" + fi + + mkdir -p "${MOUNT_POINT}/home/clawdie-installer" + install -m 0755 "${LIVE_SESSION_DIR}/xprofile" \ + "${MOUNT_POINT}/home/clawdie-installer/.xprofile" + cat > "${MOUNT_POINT}/home/clawdie-installer/.dmrc" <<'EOF' +[Desktop] +Session=lumina +EOF + chroot "$MOUNT_POINT" chown -R clawdie-installer:clawdie-installer /home/clawdie-installer + + set_config_line "${MOUNT_POINT}/etc/rc.conf" 'dbus_enable="YES"' + set_config_line "${MOUNT_POINT}/etc/rc.conf" 'seatd_enable="YES"' + set_config_line "${MOUNT_POINT}/etc/rc.conf" 'lightdm_enable="YES"' + set_config_line "${MOUNT_POINT}/etc/rc.conf" 'display_manager="lightdm"' +} + # --- step 1: fetch FreeBSD memstick --- MEMSTICK="${CACHE_DIR}/FreeBSD-${FREEBSD_VERSION}-${FREEBSD_ARCH}-memstick.img" if [ "$SKIP_FETCH" -eq 0 ] || [ ! -f "$MEMSTICK" ]; then @@ -107,8 +329,13 @@ if [ "$SKIP_FETCH" -eq 0 ] || [ ! -f "$MEMSTICK" ]; then mkdir -p "$CACHE_DIR" curl -L --progress-bar -o "$MEMSTICK" "$FREEBSD_MEMSTICK_URL" curl -L -o "${MEMSTICK}.SHA256" "$FREEBSD_MEMSTICK_SHA256_URL" - sha256 -c "${MEMSTICK}.SHA256" || { echo "ERROR: checksum mismatch on memstick"; exit 1; } + verify_memstick_cache || { echo "ERROR: checksum mismatch on memstick"; exit 1; } else + if ! verify_memstick_cache; then + echo "ERROR: cached FreeBSD memstick failed checksum verification." + echo "Remove ${MEMSTICK} and rerun without --skip-fetch." + exit 1 + fi echo "==> [1/7] FreeBSD memstick cached." fi @@ -239,7 +466,14 @@ if [ "$SKIP_FETCH" -eq 0 ] || [ ! -f "$CLAWDIE_TARBALL_ISO" ]; then ( cd "$AI_SRC_DIR" - npm ci --no-audit --no-fund + if ! npm ci --no-audit --no-fund; then + echo " WARN: npm ci failed for Clawdie-AI v${CLAWDIE_VERSION}; falling back to npm install." + echo " WARN: This usually means the published release tarball is out of sync with package-lock.json." + if ! npm install --no-audit --no-fund; then + echo " WARN: npm install failed due to dependency resolution; retrying with --legacy-peer-deps." + npm install --no-audit --no-fund --legacy-peer-deps + fi + fi ) mv "$AI_SRC_DIR" "${STAGE_AI_DIR}/Clawdie-AI" @@ -294,18 +528,21 @@ if [ ! -f "$WORK_IMG" ]; then # Mount memstick read-only to extract base system MEMSTICK_MNT="${CACHE_DIR}/memstick-src" mkdir -p "$MEMSTICK_MNT" - MD_SRC=$(mdconfig -a -t vnode -f "$MEMSTICK") - mount -r -t ufs /dev/${MD_SRC}s2a "$MEMSTICK_MNT" + mount_memstick_rootfs "$MEMSTICK_MNT" echo " Copying base system from memstick..." # Copy all files from memstick to new image (excluding package cache) - tar -C "$MEMSTICK_MNT" -cf - . | tar -C "$MOUNT_POINT" -xf - + ( + cd "$MEMSTICK_MNT" + pax -rw -pe . "$MOUNT_POINT" + ) # Cleanup memstick mount - umount "$MEMSTICK_MNT" - mdconfig -d -u ${MD_SRC} + cleanup_memstick_rootfs "$MEMSTICK_MNT" rm -rf "$MEMSTICK_MNT" + install_image_bootcode "$MD" "$MOUNT_POINT" + # Store MD device for later cleanup echo "$MD" > "${CACHE_DIR}/.md_device" else @@ -323,14 +560,17 @@ else echo " Extracting base system from memstick..." MEMSTICK_MNT="${CACHE_DIR}/memstick-src" mkdir -p "$MEMSTICK_MNT" - MD_SRC=$(mdconfig -a -t vnode -f "$MEMSTICK") - mount -r -t ufs /dev/${MD_SRC}s2a "$MEMSTICK_MNT" + mount_memstick_rootfs "$MEMSTICK_MNT" - tar -C "$MEMSTICK_MNT" -cf - . | tar -C "$MOUNT_POINT" -xf - + ( + cd "$MEMSTICK_MNT" + pax -rw -pe . "$MOUNT_POINT" + ) - umount "$MEMSTICK_MNT" - mdconfig -d -u ${MD_SRC} + cleanup_memstick_rootfs "$MEMSTICK_MNT" rm -rf "$MEMSTICK_MNT" + + install_image_bootcode "$MD" "$MOUNT_POINT" fi # Store MD device for later cleanup @@ -340,12 +580,22 @@ fi # --- step 6: inject payload --- echo "==> [6/7] Injecting payload..." +build_live_qml_installer + # Create share directory on USB USB_SHARE="${MOUNT_POINT}/usr/local/share/clawdie-iso" mkdir -p "$USB_SHARE" +# Step 1 switches the live boot entrypoint away from auto-running +# /etc/installerconfig. Keep the script in USB_SHARE for later explicit +# bsdinstall invocation, but remove any legacy boot-time auto script. +rm -f "${MOUNT_POINT}/etc/installerconfig" + +install_live_runtime_packages +configure_live_installer_session + # Copy payload -cp "${SCRIPT_DIR}/installerconfig" "${MOUNT_POINT}/etc/installerconfig" +cp "${SCRIPT_DIR}/installerconfig" "${USB_SHARE}/installerconfig" cp -r "${SCRIPT_DIR}/firstboot" "${USB_SHARE}/" cp -r "${PKG_REPO_DIR}" "${USB_SHARE}/" if [ -d "$NPM_GLOBALS_DIR" ]; then diff --git a/doc/ISO-LIVE-GUI-SETUP-PROPOSAL.md b/doc/ISO-LIVE-GUI-SETUP-PROPOSAL.md index 33d36110..dfada62f 100644 --- a/doc/ISO-LIVE-GUI-SETUP-PROPOSAL.md +++ b/doc/ISO-LIVE-GUI-SETUP-PROPOSAL.md @@ -138,10 +138,9 @@ during baremetal install. Phase 1 validation should be narrow and practical: -- LLM key ping: validate only providers for which the operator actually entered - a key. -- Telegram test: validate bot token format and optionally send a test message. - Disk selection: display model, size, and minimum capacity warning. +- Tailscale auth key: validate format only when the operator entered one. +- SSH public key: validate basic key shape when the operator entered one. - If the live environment has no working network, validation should be skippable with a warning rather than a hard block. @@ -176,7 +175,7 @@ This replaces the current overloading of `/tmp/clawdie-install.conf` as both UI contract and install execution input. This also avoids broadening the current `/tmp` exemption in `AGENTS.md`, which -is intentionally limited to the legacy GUI-firstboot handoff and progress files. +is intentionally limited to the current GUI-firstboot handoff and progress files. ### 5. Commit step diff --git a/firstboot/firstboot.sh b/firstboot/firstboot.sh index cfaf4d72..3ff4bcdc 100644 --- a/firstboot/firstboot.sh +++ b/firstboot/firstboot.sh @@ -233,16 +233,6 @@ fi SSH_PUBLIC_KEY=$(_dialog --inputbox \ "SSH public key (optional — paste ssh-ed25519 or ssh-rsa):" 12 70 "") - ANTHROPIC_API_KEY=$(_dialog --passwordbox \ - "Anthropic API Key (optional).\n\n" \ - "Get from: console.anthropic.com\n" \ - "Leave blank to configure later." 12 70 "") - - CLAUDE_CODE_OAUTH_TOKEN=$(_dialog --passwordbox \ - "Claude Code OAuth Token (optional).\n\n" \ - "Run 'claude setup-token' elsewhere, paste token here.\n" \ - "Leave blank to configure later." 12 70 "") - # Defaults: all jails enabled, no local LLM (can be enabled post-install) : "${AGENT_GENDER:=f}" FEATURE_GIT="YES" @@ -261,8 +251,6 @@ fi SUMMARY_MSG+="Locale: ${SYSTEM_LOCALE}\n" SUMMARY_MSG+="Keymap: ${KEYMAP}\n" SUMMARY_MSG+="SSH key: $([ -n "$SSH_PUBLIC_KEY" ] && echo "✓ Provided" || echo "✗ None")\n" - SUMMARY_MSG+="Claude API: $([ -n "$ANTHROPIC_API_KEY" ] && echo "✓ Provided" || echo "✗ None")\n" - SUMMARY_MSG+="Claude OAuth: $([ -n "$CLAUDE_CODE_OAUTH_TOKEN" ] && echo "✓ Provided" || echo "✗ None")\n" if [ "${FEATURE_TAILSCALE}" = "YES" ]; then if [ -n "$TAILSCALE_AUTHKEY" ]; then SUMMARY_MSG+="Tailscale: ✓ Enabled (auth key provided)\n" @@ -272,6 +260,8 @@ fi else SUMMARY_MSG+="Tailscale: ✗ Disabled (SSH on public port 22)\n" fi + SUMMARY_MSG+="Provider keys: configure after first boot in the controlplane\n" + SUMMARY_MSG+="Telegram: configure after first boot in the controlplane\n" SUMMARY_MSG+="\nProceed with installation?" if ! _dialog --yesno "$SUMMARY_MSG" 16 70; then @@ -284,12 +274,9 @@ fi export CLAWDIE_BOOT_MODE POOL_NAME export ASSISTANT_NAME AGENT_GENDER AGENT_DOMAIN TZ SSH_PUBLIC_KEY -export HOSTNAME INSTALL_MODE PROFILE OPENROUTER_API_KEY TELEGRAM_ADMIN_ID TELEGRAM_ADMIN_IDS +export HOSTNAME INSTALL_MODE PROFILE export SYSTEM_LOCALE DISPLAY_LOCALE ASSISTANT_LOCALE KEYMAP -export PI_TUI_PROVIDER PI_TUI_MODEL ZAI_API_KEY OPENROUTER_API_KEY ANTHROPIC_API_KEY -export CLAUDE_CODE_OAUTH_TOKEN export EMBED_BASE_URL EMBED_MODEL EMBED_API_KEY EMBED_DIMENSIONS -export TELEGRAM_BOT_TOKEN TELEGRAM_ADMIN_IDS FEATURE_TELEGRAM export FEATURE_TAILSCALE TAILSCALE_AUTHKEY export CODE_HOSTING_MODE FEATURE_GIT FEATURE_GITEA FORGEJO_DISK_ESTIMATE export LOCAL_LLM_PROVIDER FEATURE_OLLAMA FEATURE_LLAMA_CPP FEATURE_OLLAMA_HPP diff --git a/firstboot/gui/qml-installer/main.cpp b/firstboot/gui/qml-installer/main.cpp index cefb74ed..518137f4 100644 --- a/firstboot/gui/qml-installer/main.cpp +++ b/firstboot/gui/qml-installer/main.cpp @@ -299,8 +299,6 @@ class InstallerBackend : public QObject { Q_PROPERTY(bool installNvidia READ installNvidia WRITE setInstallNvidia NOTIFY installNvidiaChanged) Q_PROPERTY(bool installLLM READ installLLM WRITE setInstallLLM NOTIFY installLLMChanged) Q_PROPERTY(bool installationStarted READ installationStarted NOTIFY installationStartedChanged) - Q_PROPERTY(QString anthropicApiKey READ anthropicApiKey WRITE setAnthropicApiKey NOTIFY anthropicApiKeyChanged) - Q_PROPERTY(QString claudeOAuthToken READ claudeOAuthToken WRITE setClaudeOAuthToken NOTIFY claudeOAuthTokenChanged) public: InstallerBackend(QObject *parent = nullptr) : QObject(parent) {} @@ -387,22 +385,6 @@ public: bool installationStarted() const { return m_installationStarted; } - QString anthropicApiKey() const { return m_anthropicApiKey; } - void setAnthropicApiKey(const QString &key) { - if (key != m_anthropicApiKey) { - m_anthropicApiKey = key; - emit anthropicApiKeyChanged(); - } - } - - QString claudeOAuthToken() const { return m_claudeOAuthToken; } - void setClaudeOAuthToken(const QString &token) { - if (token != m_claudeOAuthToken) { - m_claudeOAuthToken = token; - emit claudeOAuthTokenChanged(); - } - } - Q_INVOKABLE void goNext() { if (m_currentPage < 7) setCurrentPage(m_currentPage + 1); @@ -417,6 +399,28 @@ public: if (m_installationStarted) return false; + if (qEnvironmentVariableIsSet("CLAWDIE_LIVE_SESSION")) { + QFile logFile("/var/log/clawdie-firstboot.log"); + if (logFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream log(&logFile); + log << "[live-installer] Live GUI session bootstrapped successfully.\n"; + log << "[live-installer] Commit path is not implemented in this build yet.\n"; + log << "[live-installer] Stop here and continue with Step 2/3 implementation.\n"; + logFile.close(); + } + + QFile progressFile("/var/log/clawdie-firstboot.progress"); + if (progressFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream progress(&progressFile); + progress << "ERROR=live-session-bootstrap-only\n"; + progressFile.close(); + } + + m_installationStarted = true; + emit installationStartedChanged(); + return true; + } + QFile configFile("/tmp/clawdie-install.conf"); if (!configFile.open(QIODevice::WriteOnly | QIODevice::Text)) { qWarning() << "Failed to create config file:" << configFile.errorString(); @@ -430,7 +434,7 @@ public: out << "# User settings\n"; out << "ASSISTANT_NAME=\"" << m_username << "\"\n"; out << "AGENT_DOMAIN=\"" << m_username.toLower() << ".home.arpa\"\n"; - out << "CLAWDIE_PASSWORD=\"" << m_password << "\"\n\n"; + out << "CLAWDIE_USER_PASSWORD=\"" << m_password << "\"\n\n"; out << "# Installation settings\n"; out << "INSTALL_DISK=\"" << m_selectedDisk << "\"\n"; @@ -453,10 +457,7 @@ public: out << "TAILSCALE_AUTHKEY=\"\"\n"; out << "FEATURE_GIT=\"YES\"\n"; out << "FEATURE_GITEA=\"NO\"\n"; - out << "CODE_HOSTING_MODE=\"git\"\n\n"; - out << "# Claude authentication\n"; - out << "ANTHROPIC_API_KEY=\"" << m_anthropicApiKey << "\"\n"; - out << "CLAUDE_CODE_OAUTH_TOKEN=\"" << m_claudeOAuthToken << "\"\n"; + out << "CODE_HOSTING_MODE=\"git\"\n"; configFile.close(); @@ -509,8 +510,6 @@ public: void installNvidiaChanged(); void installLLMChanged(); void installationStartedChanged(); - void anthropicApiKeyChanged(); - void claudeOAuthTokenChanged(); private: int m_currentPage = 0; @@ -524,8 +523,6 @@ private: bool m_installNvidia = true; bool m_installLLM = false; bool m_installationStarted = false; - QString m_anthropicApiKey; - QString m_claudeOAuthToken; }; // ============================================================================ diff --git a/firstboot/gui/qml-installer/pages/CompletePage.qml b/firstboot/gui/qml-installer/pages/CompletePage.qml index fbdaa8d3..6c8e32c1 100644 --- a/firstboot/gui/qml-installer/pages/CompletePage.qml +++ b/firstboot/gui/qml-installer/pages/CompletePage.qml @@ -67,7 +67,7 @@ ColumnLayout { } Text { - text: tracker.success ? "You can now log in with the credentials you created." : "You may retry the installation or seek support." + text: tracker.success ? "Finish machine bootstrap first. Provider keys, Telegram, and browser sign-in are configured after boot." : "You may retry the installation or seek support." font.pixelSize: 12 color: "#666666" } diff --git a/firstboot/gui/qml-installer/pages/UserPage.qml b/firstboot/gui/qml-installer/pages/UserPage.qml index d4751483..934b1152 100644 --- a/firstboot/gui/qml-installer/pages/UserPage.qml +++ b/firstboot/gui/qml-installer/pages/UserPage.qml @@ -7,7 +7,7 @@ ColumnLayout { // Title Text { - text: "Create User Account" + text: "Identity and Access" font.pixelSize: 24 font.bold: true color: "#333333" @@ -15,7 +15,7 @@ ColumnLayout { // Instructions Text { - text: "Set up the primary user account for your Clawdie AI system." + text: "Set the assistant name and the initial local password for the clawdie account. Provider keys, Telegram, and site logins are configured after first boot in the controlplane." font.pixelSize: 13 color: "#666666" wrapMode: Text.WordWrap @@ -41,7 +41,7 @@ ColumnLayout { spacing: 5 Text { - text: "Username" + text: "Assistant Name" font.pixelSize: 13 font.bold: true color: "#333333" @@ -51,7 +51,7 @@ ColumnLayout { id: usernameField Layout.fillWidth: true text: backend.username - placeholderText: "clawdie" + placeholderText: "Clawdie" font.pixelSize: 13 onEditingFinished: { @@ -113,77 +113,6 @@ ColumnLayout { } } - // Separator - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#eeeeee" - } - - Text { - text: "Claude Authentication (optional)" - font.pixelSize: 13 - font.bold: true - color: "#666666" - } - - Text { - text: "Provide at least one to enable Claude Code on first boot." - font.pixelSize: 11 - color: "#999999" - Layout.fillWidth: true - wrapMode: Text.WordWrap - } - - // Anthropic API Key - ColumnLayout { - Layout.fillWidth: true - spacing: 5 - - Text { - text: "Anthropic API Key" - font.pixelSize: 13 - font.bold: true - color: "#333333" - } - - TextField { - id: anthropicKeyField - Layout.fillWidth: true - placeholderText: "sk-ant-..." - echoMode: TextField.Password - font.pixelSize: 13 - - onEditingFinished: { - backend.anthropicApiKey = text - } - } - } - - // Claude Code OAuth Token - ColumnLayout { - Layout.fillWidth: true - spacing: 5 - - Text { - text: "Claude Code OAuth Token" - font.pixelSize: 13 - font.bold: true - color: "#333333" - } - - TextField { - id: claudeOAuthField - Layout.fillWidth: true - placeholderText: "Run 'claude setup-token' elsewhere" - echoMode: TextField.Password - font.pixelSize: 13 - - onEditingFinished: { - backend.claudeOAuthToken = text - } - } - } } } diff --git a/firstboot/gui/test-config-format.sh b/firstboot/gui/test-config-format.sh index 08952457..4fd8edfa 100755 --- a/firstboot/gui/test-config-format.sh +++ b/firstboot/gui/test-config-format.sh @@ -14,7 +14,7 @@ cat > /tmp/test-clawdie-install.conf << 'EOF' # User settings ASSISTANT_NAME="testuser" AGENT_DOMAIN="testuser.home.arpa" -CLAWDIE_PASSWORD="testpass123" +CLAWDIE_USER_PASSWORD="testpass123" # Installation settings INSTALL_DISK="da0" diff --git a/firstboot/integration-test.sh b/firstboot/integration-test.sh index 6cab8510..9816b5cc 100755 --- a/firstboot/integration-test.sh +++ b/firstboot/integration-test.sh @@ -13,6 +13,7 @@ cd "$TESTDIR" # Setup test environment mkdir -p "$TESTDIR/etc/pkg/repos" mkdir -p "$TESTDIR/etc/profile.d" +mkdir -p "$TESTDIR/boot" mkdir -p "$TESTDIR/var/log" mkdir -p "$TESTDIR/var/cache/pkg/bastille" mkdir -p "$TESTDIR/mnt/media/packages" @@ -55,6 +56,8 @@ export CLAWDIE_USB_REPO_CONF="$PKG_CONF_DIR/Clawdie-USB.conf" export USB_PKG_PATH="$TESTDIR/mnt/media/packages" export BASTILLE_PKG_CACHE="$TESTDIR/var/cache/pkg/bastille" export RC_CONF="$TESTDIR/etc/rc.conf" +export LOADER_CONF="$TESTDIR/boot/loader.conf" +export SYSCTL_CONF="$TESTDIR/etc/sysctl.conf" export HOSTNAME_FILE="$TESTDIR/etc/hostname" export PROFILE_DIR="$TESTDIR/etc/profile.d" export LOG_FILE="$TESTDIR/var/log/integration.log" @@ -76,7 +79,7 @@ export KEYMAP="sl.kbd" export DETECTED_GPU="intel" # Init -touch "$LOG_FILE" "$PROGRESS_FILE" "$RC_CONF" +touch "$LOG_FILE" "$PROGRESS_FILE" "$RC_CONF" "$LOADER_CONF" "$SYSCTL_CONF" echo "" echo "╔════════════════════════════════════════════════════════════════╗" diff --git a/firstboot/setup-import.sh b/firstboot/setup-import.sh index a2b7d5c7..3abd968a 100644 --- a/firstboot/setup-import.sh +++ b/firstboot/setup-import.sh @@ -114,7 +114,7 @@ clawdie_setup_import_parse_file() { _value=$(printf '%s' "$_value" | sed 's/^"//; s/"$//; s/^'\''//; s/'\''$//') case "$_key" in - SETUP_SCHEMA_VERSION|ISO_RELEASE|ISO_GIT_COMMIT|ZAI_API_KEY|ZAI_API_BASE|OPENROUTER_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN|TELEGRAM_BOT_TOKEN|TELEGRAM_ADMIN_ID|INSTALL_MODE|ASSISTANT_NAME|PROFILE|TIMEZONE|HOSTNAME|ZFS_POOL|ZFS_LAYOUT|ZFS_DATA_DISKS|ZFS_HOT_SPARES|ZFS_PREFIX|OPERATOR_EMAIL|OPERATOR_PASSWORD|SSH_AUTHORIZED_KEY|CLAWDIE_USER_PASSWORD|SYSTEM_LOCALE|KEYMAP) + SETUP_SCHEMA_VERSION|ISO_RELEASE|ISO_GIT_COMMIT|INSTALL_MODE|ASSISTANT_NAME|AGENT_DOMAIN|PROFILE|TIMEZONE|HOSTNAME|ZFS_POOL|ZFS_LAYOUT|ZFS_DATA_DISKS|ZFS_HOT_SPARES|ZFS_PREFIX|OPERATOR_EMAIL|OPERATOR_PASSWORD|SSH_PUBLIC_KEY|SSH_AUTHORIZED_KEY|CLAWDIE_USER_PASSWORD|SYSTEM_LOCALE|KEYMAP|TAILSCALE_AUTHKEY) eval "$_key=\$_value" export "$_key" ;; @@ -148,28 +148,9 @@ clawdie_setup_import_apply_defaults() { ASSISTANT_LOCALE="${ASSISTANT_LOCALE:-$SYSTEM_LOCALE}" KEYMAP="${KEYMAP:-us}" - if [ -n "${TELEGRAM_ADMIN_ID:-}" ] && [ -z "${TELEGRAM_ADMIN_IDS:-}" ]; then - TELEGRAM_ADMIN_IDS="$TELEGRAM_ADMIN_ID" + if [ -n "${SSH_AUTHORIZED_KEY:-}" ] && [ -z "${SSH_PUBLIC_KEY:-}" ]; then + SSH_PUBLIC_KEY="$SSH_AUTHORIZED_KEY" fi - if [ -n "${TELEGRAM_BOT_TOKEN:-}" ]; then - FEATURE_TELEGRAM="${FEATURE_TELEGRAM:-YES}" - fi - if [ -n "${ZAI_API_KEY:-}" ]; then - PI_TUI_PROVIDER="${PI_TUI_PROVIDER:-zai}" - PI_TUI_MODEL="${PI_TUI_MODEL:-glm-5}" - fi - if [ -n "${OPENROUTER_API_KEY:-}" ]; then - PI_TUI_PROVIDER="${PI_TUI_PROVIDER:-openrouter}" - PI_TUI_MODEL="${PI_TUI_MODEL:-openai/o3}" - fi - if [ -n "${OPENAI_API_KEY:-}" ]; then - : "${PI_TUI_PROVIDER:=openai}" - fi - if [ -n "${ANTHROPIC_API_KEY:-}" ]; then - : "${PI_TUI_PROVIDER:=anthropic}" - fi - PI_TUI_PROVIDER="${PI_TUI_PROVIDER:-zai}" - PI_TUI_MODEL="${PI_TUI_MODEL:-glm-5}" if [ -n "${TAILSCALE_AUTHKEY:-}" ]; then FEATURE_TAILSCALE="${FEATURE_TAILSCALE:-YES}" fi @@ -195,8 +176,7 @@ clawdie_setup_import_apply_defaults() { esac export TZ HOSTNAME AGENT_DOMAIN SYSTEM_LOCALE DISPLAY_LOCALE ASSISTANT_LOCALE KEYMAP - export TELEGRAM_ADMIN_IDS FEATURE_TELEGRAM FEATURE_TAILSCALE - export PI_TUI_PROVIDER PI_TUI_MODEL + export SSH_PUBLIC_KEY FEATURE_TAILSCALE export POOL_NAME CLAWDIE_BOOT_MODE CLAWDIE_BOOT_MODE_PRESET } diff --git a/live/installer-session/clawdie-live-installer-launch.sh b/live/installer-session/clawdie-live-installer-launch.sh new file mode 100755 index 00000000..ad3dfa9c --- /dev/null +++ b/live/installer-session/clawdie-live-installer-launch.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +set -eu + +RUNTIME_DIR="/var/run/clawdie-installer" +LOG_FILE="/var/log/clawdie-live-installer.log" +INSTALLER_BIN="/usr/local/bin/clawdie-qml-installer" + +mkdir -p "$RUNTIME_DIR" +touch "$LOG_FILE" + +if pgrep -fx "$INSTALLER_BIN" >/dev/null 2>&1; then + exit 0 +fi + +echo "$(date '+%Y-%m-%d %H:%M:%S') starting live installer session" >> "$LOG_FILE" + +export CLAWDIE_LIVE_SESSION=1 +exec "$INSTALLER_BIN" >> "$LOG_FILE" 2>&1 diff --git a/live/installer-session/lightdm-live.conf b/live/installer-session/lightdm-live.conf new file mode 100644 index 00000000..0ee482a0 --- /dev/null +++ b/live/installer-session/lightdm-live.conf @@ -0,0 +1,11 @@ +[LightDM] +logind-check-gio=false +sessions-directory=/usr/local/share/xsessions + +[Seat:*] +autologin-user=clawdie-installer +autologin-user-timeout=0 +user-session=lumina +greeter-session=lightdm-gtk-greeter +greeter-hide-users=false +allow-user-switching=false diff --git a/live/installer-session/xprofile b/live/installer-session/xprofile new file mode 100755 index 00000000..bd08fb42 --- /dev/null +++ b/live/installer-session/xprofile @@ -0,0 +1,5 @@ +#!/bin/sh + +if ! pgrep -fx /usr/local/bin/clawdie-qml-installer >/dev/null 2>&1; then + /usr/local/bin/clawdie-live-installer-launch.sh & +fi diff --git a/packages/pkg-list-desktop-base.txt b/packages/pkg-list-desktop-base.txt index 25880ced..27223cbd 100644 --- a/packages/pkg-list-desktop-base.txt +++ b/packages/pkg-list-desktop-base.txt @@ -4,4 +4,4 @@ xf86-input-libinput xf86-video-intel drm-kmod dbus -hal +# `hal` was removed from modern FreeBSD package repos. diff --git a/packages/pkg-list-host.txt b/packages/pkg-list-host.txt index c5b7a067..1504c11a 100644 --- a/packages/pkg-list-host.txt +++ b/packages/pkg-list-host.txt @@ -40,7 +40,6 @@ dejavu # Wayland display stack (desktop installs) seatd -weston cage wayvnc waypipe @@ -49,4 +48,5 @@ xwayland # bhyve VM management (optional, included for full offline capability) vm-bhyve grub2-bhyve -uefi-edk2-bhyve +# Current repo package name; provides BHYVE_UEFI.fd for VM boot. +edk2-bhyve-g202508 diff --git a/packages/pkg-list-jails.txt b/packages/pkg-list-jails.txt index 0e04c45c..09574259 100644 --- a/packages/pkg-list-jails.txt +++ b/packages/pkg-list-jails.txt @@ -17,7 +17,7 @@ postgresql18-client # db-jail postgresql18-server postgresql18-contrib -pgvector +postgresql18-pgvector # worker-jail cage @@ -25,7 +25,7 @@ chromium # management-jail (observability) victoria-metrics -grafana10 +grafana # ollama-jail (optional local inference) ollama diff --git a/packages/pkg-list-live-installer.txt b/packages/pkg-list-live-installer.txt new file mode 100644 index 00000000..ec93fe5a --- /dev/null +++ b/packages/pkg-list-live-installer.txt @@ -0,0 +1,3 @@ +# Live installer runtime — needed on the USB image itself +qt6-base +qt6-declarative diff --git a/packages/pkg-list-lumina.txt b/packages/pkg-list-lumina.txt index 8cb71cb6..5e580ed6 100644 --- a/packages/pkg-list-lumina.txt +++ b/packages/pkg-list-lumina.txt @@ -4,9 +4,8 @@ lumina-core lumina-themes lumina-calculator lumina-archiver -lumina-filemanager +lumina-fm lumina-screenshot -lumina-open openbox libxcb libxdg-basedir diff --git a/packages/pkg-list-nvidia-590.txt b/packages/pkg-list-nvidia-590.txt index 6608784c..6a4743d6 100644 --- a/packages/pkg-list-nvidia-590.txt +++ b/packages/pkg-list-nvidia-590.txt @@ -1,3 +1,3 @@ -# NVIDIA driver 590.x (Maxwell & newer: GTX 750 Ti+, RTX 20/30/40) -nvidia-driver-590 +# Current closest repo branch to the prior 590 target. +nvidia-driver-580 nvidia-settings diff --git a/packages/pkg-list-nvidia-all.txt b/packages/pkg-list-nvidia-all.txt index 3b9ba91b..9e318649 100644 --- a/packages/pkg-list-nvidia-all.txt +++ b/packages/pkg-list-nvidia-all.txt @@ -4,6 +4,6 @@ nvidia-driver-390 nvidia-driver-470 -nvidia-driver-590 +nvidia-driver-580 nvidia-settings nvidia-xconfig