Boot live installer session and narrow install-time contract (Sam & Codex)

This commit is contained in:
Sam & Claude 2026-05-12 12:17:59 +02:00 committed by 123kupola
parent d59cc76f30
commit 3a9954f9ec
21 changed files with 359 additions and 175 deletions

View file

@ -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 (510 min)
4. **Desktop boots** (if display detected) or headless mode (VPS/cloud)

View file

@ -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)

276
build.sh
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
};
// ============================================================================

View file

@ -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"
}

View file

@ -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
}
}
}
}
}

View file

@ -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"

View file

@ -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 "╔════════════════════════════════════════════════════════════════╗"

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -4,4 +4,4 @@ xf86-input-libinput
xf86-video-intel
drm-kmod
dbus
hal
# `hal` was removed from modern FreeBSD package repos.

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,3 @@
# Live installer runtime — needed on the USB image itself
qt6-base
qt6-declarative

View file

@ -4,9 +4,8 @@ lumina-core
lumina-themes
lumina-calculator
lumina-archiver
lumina-filemanager
lumina-fm
lumina-screenshot
lumina-open
openbox
libxcb
libxdg-basedir

View file

@ -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

View file

@ -4,6 +4,6 @@
nvidia-driver-390
nvidia-driver-470
nvidia-driver-590
nvidia-driver-580
nvidia-settings
nvidia-xconfig