feat: initial clawdie-iso skeleton

USB installer for Clawdie-AI. Combines FreeBSD base install,
desktop-installer GPU/DE setup, and Clawdie-AI deployment into
a single rc.firstboot wizard flow.

Skeleton includes:
- build.cfg: FreeBSD 15.0-RELEASE-p4, amd64, XFCE default
- build.sh: 7-step build outline (fetch → inject → repack), stubs
- installerconfig: bsdinstall post-install hook, copies firstboot/ to HDD
- firstboot/rc.d/clawdie-firstboot: runs once on first HDD boot
- firstboot/firstboot.sh: tiered bsddialog wizard (identity, desktop,
  pi profile, auto-generated secrets, AGENTS.md seeding, npm prefix setup)
- firstboot/gpu-detect.sh: pciconf PCI ID → kld/xorg driver mapping
- CLAWDIE-ISO.md: full design doc (copied from clawdie-ai)

VirtualBox excluded. pkg latest default. LLM keys deferred to pi first-run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sam & Claude 2026-03-17 10:20:23 +00:00 committed by 123kupola
commit 61b00accb4
10 changed files with 1147 additions and 0 deletions

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
# Fetched at build time — do not commit
cache/
packages/*.pkg
packages/*.txz
packages/.repo/
# Build output
*.img
*.iso

594
CLAWDIE-ISO.md Normal file
View file

@ -0,0 +1,594 @@
# CLAWDIE-ISO — USB Installer Plan
**Status:** Draft brainstorm — 2026-03-17
**Target repo:** `codeberg.org/Clawdie/clawdie-iso` (to be created)
---
## Purpose
Create a bootable USB image that reduces a complete Clawdie-AI deployment to a
single physical step: boot USB, answer a few screens, unplug USB, done.
The installed system is a plain FreeBSD + desktop + Clawdie-AI — nothing exotic,
nothing persistent on the USB. The USB is a deployment vehicle only.
The install combines three procedures that currently run separately:
| # | Path | Tool | What it does |
|---|------|------|--------------|
| 1 | **FreeBSD base** | bsdinstall | Disk layout, ZFS, base OS, bootloader, root/user accounts |
| 2 | **Desktop setup** | desktop-installer | GPU detection, Xorg/Wayland, DE packages, login manager, audio |
| 3 | **Clawdie-AI** | setup.sh → install-all | Bastille jails, PostgreSQL, nginx/CMS, rc.d service, bot config |
Current state: these three are completely separate. An operator must install
FreeBSD, reboot, install desktop-installer, run it, reboot (sometimes), run
setup.sh, configure. Minimum 2 reboots, typically 3+ operator interventions.
Target state: one ISO, one bsddialog wizard, one reboot.
---
## Desktop-Installer Reference
[outpaddling/desktop-installer](https://github.com/outpaddling/desktop-installer)
is a FreeBSD ports package (`pkg install desktop-installer`) that automates the
post-install desktop configuration step:
- Detects GPU via `pciconf -lv`
- Installs the right kld modules (i915kms, nvidia-modeset, drm, vboxguest, etc.)
- Writes `kld_list` to `/etc/rc.conf` for persistent driver loading
- Installs the chosen DE (KDE Plasma, XFCE, MATE, GNOME, Lumina, etc.)
- Configures dbus, CUPS, sound (OSS/PulseAudio), login manager (SDDM/lightdm/slim)
- Handles VirtualBox, VMware, Parallels, bhyve guest additions automatically
- Does NOT typically require a reboot — kld can be loaded live or on next boot
- Respects quarterly vs latest pkg repository configured on the system
We borrow the detection and configuration logic from desktop-installer but do not
run the interactive script. Our build process will vendor the GPU detection
function and call it from our own bsddialog wizard.
---
## Reboot Analysis — Can We Do 1 Reboot?
Three potential reboot triggers:
**Reboot 1 (mandatory): bsdinstall completes**
bsdinstall always reboots after base install. Can't be avoided. The system must
boot from HDD before userspace can run.
**Reboot 2 (optional): GPU driver loading**
Most GPU drivers on FreeBSD are kernel modules (klds). desktop-installer writes
them to `kld_list` in rc.conf, but they can also be `kldload`ed into a live system.
The key question: can we detect the GPU and write correct config WITHOUT having
the module loaded yet?
Answer: **yes**. GPU detection uses `pciconf -lv` (reads PCI device tree, no kld
needed). The output gives us PCI device ID and vendor ID. We match against a
known table to select the right kld and write it to `rc.conf`. On first boot from
HDD, the kld loads automatically. X11/Wayland starts clean. No intermediate reboot
is needed to make this work.
VirtualBox is explicitly out of scope for this installer. Target: real hardware
and bhyve only. VBox guest packages are not bundled on the USB image.
**Reboot 3 (not needed): Clawdie setup**
setup.sh and all jail provisioning steps run in userspace. No reboot needed.
rc.d service starts without reboot via `service clawdie onestart`.
**Verdict: 1 reboot is achievable for all common hardware.**
Two reboots may still be the safer default for first release if GPU detection
reliability is uncertain. See Options section.
---
## Pkg Branch Selection
FreeBSD ships two pkg repository branches:
| Branch | URL suffix | Update cadence | Best for |
|--------|-----------|---------------|---------|
| `quarterly` | `/quarterly/` | Stable snapshots, security fixes only | Servers, production |
| `latest` | `/latest/` | Tracks ports HEAD, newest versions | Desktops, GPU drivers, Node24 |
**Recommendation: default to latest.** GPU drivers (drm-kmod, nvidia-driver),
Node 24, and many Bastille-related packages are updated sooner in `latest`.
Quarterly has caused GPU driver installation failures on new hardware.
**How to implement the toggle:**
During the install wizard (bsddialog), present a radiolist before any packages
are installed:
```
Package repository branch:
(*) Latest — newest packages, best driver support [recommended]
( ) Quarterly — stable branch, security updates only
```
The selected branch sets `/usr/local/etc/pkg/repos/FreeBSD.conf`:
```ucl
FreeBSD: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
enabled: yes
}
```
For offline install from USB, the bundled pkg repo must match the selected branch.
Build the USB with `latest` packages. Add a note: if the user selects `quarterly`,
the offline install falls back to the USB-bundled packages (which are `latest`
builds) and the system upgrades to quarterly on first internet connection.
Simpler alternative: only support `latest` offline, add quarterly as a post-install
option in the wizard.
---
## Options
### Option A — Pre-configured silent install (1 reboot, no interaction)
All choices are baked into a config file on the USB (`clawdie.conf`). The operator
edits this file before booting:
```sh
PKG_BRANCH=latest
DESKTOP=xfce
AGENT_NAME=clawdie
ASSISTANT_NAME="Clawdie"
TZ=Europe/Ljubljana
```
bsdinstall runs from `installerconfig`. After base install, a post-install script
reads `clawdie.conf`, installs everything offline, writes all config. Reboot once
into a fully configured system.
**Pro:** Fully automated, reproducible, CI-friendly, no bsddialog needed
**Con:** Requires USB preparation before boot. No interactive personalization.
**Reboots:** 1
**Best for:** Reproducible deploys, CI testing, pre-provisioned hardware
---
### Option B — Interactive bsddialog wizard in installerconfig (1 reboot, guided)
After bsdinstall's base install completes but before the system reboots,
`installerconfig` runs a bsddialog wizard. The wizard covers:
1. Pkg branch (latest / quarterly)
2. Desktop environment (XFCE / KDE Plasma / MATE / Headless)
3. Agent name + assistant name
4. Telegram bot token (optional, can be skipped)
5. Summary screen → confirm → install
GPU is detected from the live USB environment via `pciconf -lv` before chroot.
Detection result is written into the target system's rc.conf. All packages are
installed offline from the USB repo. Clawdie setup runs in chroot.
Single reboot into configured system.
**Pro:** Best UX, matches existing bsddialog pattern, interactive but 1-step
**Con:** bsddialog in post-install chroot requires careful path setup
**Reboots:** 1
**Best for:** First install on new hardware, operator-configured deployments
---
### Option C — rc.firstboot script (2 reboots, most reliable)
Standard bsdinstall base install. Minimal `installerconfig` just copies the
wizard script and enables a `firstboot` rc.d service. On first real boot from HDD
(reboot 1), the firstboot service runs the bsddialog wizard on the live system.
Packages install, GPU loads live via kldload, DE and Clawdie come up. Optional
second reboot (reboot 2) for clean DM start.
**Pro:** Runs on real hardware (not in chroot), GPU kldload works immediately,
most reliable path, easier debugging
**Con:** 2 reboots minimum. Wizard runs after first reboot, which can feel slow.
**Reboots:** 2
**Best for:** Conservative first release, maximum hardware compatibility
---
### Option D — Extended bsdinstall menus (1 reboot, most native)
Modify bsdinstall to add extra menu screens: pkg branch, DE selection, Clawdie
agent config. The installer feels like a fully native FreeBSD install with
Clawdie options integrated into the standard installer flow. Post-install runs
silently based on selections.
**Pro:** Most seamless UX — everything happens inside one familiar installer
**Con:** Requires forking/patching bsdinstall (sh scripts in /usr/libexec/bsdinstall).
Fragile against FreeBSD updates. Significant maintenance burden.
**Reboots:** 1
**Best for:** Polished v2.0+ release, not first iteration
---
### Option E — Live USB + install-to-disk (2+ reboots, most impressive)
The USB boots as a live Clawdie desktop system. User can interact with a running
agent demo before committing to install. An "Install Clawdie to Disk" button/command
runs the installer. After install + reboot, the HDD system is fully configured.
**Pro:** Demo capability, validate hardware/agent before committing, impressive
**Con:** Most complex build (~23GB image), live persistence layer, 2 reboots,
significant extra build infrastructure
**Reboots:** 2+
**Best for:** Demo/showcase events, evaluators who want to try before installing
---
## Recommendation
**Start with Option C (rc.firstboot, 2 reboots) for v1 of the repo.**
Reason: GPU detection running on the real live system (not in chroot) eliminates
the most common failure mode. The 2-reboot tradeoff is acceptable for a v1 that
works reliably. The wizard runs on boot from HDD, using the same bsddialog
infrastructure already in Clawdie's setup.sh — no new tooling needed.
**Upgrade path to Option B (1 reboot) for v1.1:**
Once we have Option C working and tested on multiple hardware configs, we port the
wizard to run in the `installerconfig` post-install chroot. At that point we drop
the firstboot service and the second reboot.
**Skip Option D and E for now.** Neither is worth the complexity at this stage.
Option A can be added as a `--unattended` flag on top of Option B/C.
---
## Three-Path Merge Flow (Option C)
```
USB boot
└─ bsdinstall
├─ Disk partitioning (ZFS, GPT+EFI)
├─ Base system distribution
├─ Bootloader
├─ Root password
├─ User account (clawdie)
└─ installerconfig:
├─ Copy /usr/local/share/clawdie-iso/firstboot.sh to HDD
├─ Install clawdie-iso rc.d firstboot service
└─ Reboot from HDD
First boot from HDD (reboot 1)
└─ rc.firstboot (clawdie-firstboot service)
├─ bsddialog: pkg branch (latest [default] / quarterly)
├─ bsddialog: desktop environment (XFCE / KDE / MATE / Headless)
├─ bsddialog: agent name + assistant name
├─ bsddialog: Telegram bot token (optional)
├─ bsddialog: summary + confirm
├─ [pkg] Write /usr/local/etc/pkg/repos/FreeBSD.conf (selected branch)
├─ [pkg] pkg install from USB offline repo (desktop-installer deps)
├─ [gpu] pciconf -lv → detect GPU vendor/device ID
├─ [gpu] kldload appropriate driver(s)
├─ [gpu] Write kld_list to /etc/rc.conf
├─ [gpu] Write /etc/X11/xorg.conf.d/ if needed
├─ [de] Install chosen desktop environment from USB repo
├─ [de] Enable dbus, hald, login manager in rc.conf
├─ [de] Configure .xinitrc / session for clawdie user
├─ [clawdie] Extract clawdie-ai.tar.gz to /home/clawdie/clawdie-ai
├─ [clawdie] Set up .env defaults (AGENT_NAME, ASSISTANT_NAME, TZ)
├─ [clawdie] cd /home/clawdie/clawdie-ai && npm run install-all
│ ├─ environment (host pkg baseline from USB)
│ ├─ jails (worker + db + git + cms)
│ ├─ db (PostgreSQL + pgvector)
│ ├─ skills-memory (if artifacts/skills.db present on USB)
│ ├─ cms (nginx + Astro)
│ ├─ service (rc.d clawdie)
│ └─ verify
└─ Disable clawdie-firstboot service (runs once only)
Running system (no reboot 2 needed for Option C baseline)
└─ Login manager starts → desktop session → clawdie agent running
```
---
## Repo Structure (`clawdie-iso`)
```
clawdie-iso/
├── build.sh ← main build script
├── build.cfg ← version pins, image size, pkg list
├── installerconfig ← bsdinstall post-install hook
├── firstboot/
│ ├── firstboot.sh ← the bsddialog wizard + setup runner
│ ├── rc.d/
│ │ └── clawdie-firstboot ← FreeBSD rc.d service (runs once)
│ └── gpu-detect.sh ← pciconf → kld table lookup
├── packages/ ← pre-fetched .pkg files (gitignored, fetched by build.sh)
│ ├── .repo/ ← generated by pkg repo
│ └── *.pkg
└── CLAWDIE-ISO.md ← this document (copy)
```
`build.sh` responsibilities:
1. Fetch FreeBSD memstick installer image (verified checksum)
2. Fetch all required .pkg files to `packages/` (node24, bastille, desktop-installer deps, etc.)
3. Run `pkg repo packages/` to generate repo metadata
4. Unpack memstick image
5. Inject into memstick: `installerconfig`, `firstboot/` tree, `packages/` repo, `clawdie-ai.tar.gz`
6. Repack image
7. Output: `clawdie-iso-YYYYMMDD.img`
---
## USB Package Manifest
Derived from `setup/environment.ts` host baseline + desktop-installer + DE packages:
**Clawdie host baseline:**
`node24`, `npm`, `bsddialog`, `bastille`, `git`, `tmux`, `python311`, `uv`,
`ripgrep`, `fd-find`, `rsync`, `postgresql17-client`, `py311-pillow`, `dejavu`
**Desktop-installer and Xorg baseline:**
`desktop-installer`, `xorg-minimal`, `xf86-video-intel`, `drm-kmod`,
`xf86-input-libinput`, `dbus`, `hal`
Note: `xf86-video-vboxvideo` and VirtualBox guest additions are **excluded**.
Target hardware is real machines and bhyve only. VirtualBox is out of scope.
**XFCE (default DE):**
`xfce`, `xfce4-goodies`, `lightdm`, `lightdm-gtk-greeter`
**KDE Plasma (optional, large):**
`plasma5-plasma`, `kde-baseapps`, `sddm`
**MATE (optional, lightweight alt):**
`mate`, `mate-extra`, `lightdm`, `lightdm-gtk-greeter`
**NVIDIA support (optional):**
`nvidia-driver`, `nvidia-settings`
Total estimated size (XFCE path, latest): ~23 GB packages + ~600 MB FreeBSD base
---
## Node.js Shared Modules Setup
A critical user-space tweak that must be reproduced exactly on the installed
system: npm's global package prefix must be set to the clawdie user's home
directory, not to `/usr/local`.
**Why it matters:** On FreeBSD, `/usr/local` is owned by root. Running
`npm install -g` as the `clawdie` user fails without this. The prefix setting
redirects globals to a user-owned path that is in `PATH`.
**Current production setup** (from `/home/clawdie/.npmrc`):
```
prefix=/home/clawdie/.npm-global
```
**PATH addition** (must be in `/home/clawdie/.profile` or `.shrc`):
```sh
export PATH="$HOME/.npm-global/bin:$PATH"
```
**firstboot.sh must:**
1. Write `/home/clawdie/.npmrc` with `prefix=/home/clawdie/.npm-global`
2. Create `/home/clawdie/.npm-global/` (with correct ownership)
3. Append PATH export to `/home/clawdie/.profile`
4. Pre-populate the global npm cache from USB-bundled packages so that
`npm install` inside clawdie-ai runs fully offline
**Build-time:** `build.sh` must pre-fetch npm packages and bundle them as a
local npm cache tarball (`npm-cache.tar.gz`) to be extracted to
`/home/clawdie/.npm` during firstboot. This eliminates network calls to
registry.npmjs.org during install.
---
## AGENTS.md Seeding (Planning)
AGENTS.md is the agent's identity and behavior file. It must be seeded during
firstboot with values derived from the install wizard (assistant name, locale,
agent domain, etc.).
**Plan (not yet implemented):**
- A template `AGENTS.md.tpl` is bundled on the USB inside the clawdie-ai
tarball at `groups/global/AGENTS.md.tpl` (or `groups/main/AGENTS.md.tpl`)
- During firstboot, after `.env` is written, `firstboot.sh` renders the
template by substituting:
- `{{ASSISTANT_NAME}}` → value from wizard
- `{{AGENT_NAME}}` → derived system name
- `{{AGENT_DOMAIN}}` → configured domain
- `{{ASSISTANT_LOCALE}}` → selected locale
- `{{TZ}}` → selected timezone
- Output written to `groups/global/AGENTS.md` before `npm run install-all` runs
- This ensures the agent has a fully personalized identity from first boot
**Scope:** Template design and rendering are handled in the `clawdie-ai` repo.
The `clawdie-iso` repo only needs to call the render step during firstboot.
---
## .env Generation Strategy
The installed system requires 65 environment variables to be set before
Clawdie-AI can start. They fall into four categories:
| Category | Count | Source |
|----------|-------|--------|
| Identity / locale | 8 | Human provides via wizard |
| API keys / tokens | 14 | Human provides (all optional / deferrable) |
| Auto-generated secrets | 9 | `openssl rand -base64 32` at install time |
| Derived structural | 34 | Calculated from identity (jail IPs, names, etc.) |
Three options for how the wizard handles this:
### .env Option 1 — Minimal wizard (5 questions, everything else silent)
The wizard asks only the irreducible human questions:
1. Assistant name → derives `AGENT_NAME` automatically
2. Timezone
3. Domain name (default: `<agentname>.local`)
4. Primary LLM provider + key (Anthropic / OpenAI / skip)
5. Telegram bot token (or skip)
All 9 secrets: auto-generated silently with `openssl rand`.
All 34 structural vars: derived from identity choices (fixed subnet, fixed jail
IPs, names from AGENT_NAME, etc.).
API keys not asked: left blank in `.env`, operator fills post-install.
**Pro:** Fastest path. 5 screens. Least friction. Works completely offline.
**Con:** No visibility into generated credentials during install. Operator must
read `.env` to find database passwords. No way to customize subnet or features.
**Best for:** First-time users, fast re-deploys, CI/automated testing.
### LLM Key Entry — Deferred to pi first-run (design decision)
LLM provider keys are **not collected by the ISO installer wizard**. This is
intentional. The installer is decoupled from provider choice — keys can change,
providers can rotate, and keys should not be baked into USB media.
Instead the wizard collects only the pi *profile* (which provider class:
Anthropic / OpenAI / Ollama / skip). The key itself is entered the first time
pi is used:
**Path A — pi first-run prompt (TTY detected):**
On the operator's first interaction with the agent (or on firstboot post-install
if a TTY is present), `pi-config` checks for the provider key. If missing and a
TTY is available: bsddialog password input → written to `.env` → service
restarted automatically.
**Path B — manual .env edit (headless / operator preference):**
Operator edits `/home/clawdie/clawdie-ai/.env` directly, sets the appropriate
key var (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.), then runs
`service clawdie restart`. pi-config detects the key is present and skips
prompting on all future runs.
**Detection logic (to be implemented in `clawdie-ai`):**
```
pi-config runs →
selected provider key var present in .env? → skip, continue
not present AND TTY available? → bsddialog password input → write .env
not present AND no TTY? → log instruction, continue (non-fatal)
```
This is a **gap in the current codebase** — the non-blocking warning exists but
the TTY-triggered bsddialog prompt does not. This is a scoped task for the
`clawdie-ai` repo, not the `clawdie-iso` repo.
**Task (clawdie-ai):** Add TTY-aware key prompt to `setup/pi-config.ts`:
check for TTY (`process.stdin.isTTY`), if true and key missing, run
`bsddialog --passwordbox "Enter <PROVIDER> API key" ...`, write result
to `.env`, emit restart.
---
### .env Option 2 — Tiered wizard with credentials summary (recommended)
**Tier 1 (always shown, 4 screens):**
- Name + timezone + domain → derive/generate everything → write `.env`
**Tier 2 (shown with "Configure now? [Y/n]"):**
- LLM provider selection radiolist → key input
- Telegram bot token input
**Tier 3 (shown only if operator presses "Advanced"):**
- Custom subnet base (default 10.0.0.0/24)
- Feature flags: Ollama (local inference), Gitea (self-hosted git), Management jail
- ZFS dataset layout (default vs Clawdie-optimized)
**End screen — Credentials Summary:**
```
┌─ Clawdie Installation Complete ──────────────────────────┐
│ │
│ Agent: Clawdie (clawdie) │
│ Domain: clawdie.local │
│ Timezone: Europe/Ljubljana │
│ │
│ DB password: Xy7qP2mR... (see /home/clawdie/.env) │
│ Telegram: ✓ configured │
│ LLM: Anthropic ✓ │
│ │
│ Press Enter to start installation. │
└───────────────────────────────────────────────────────────┘
```
**Pro:** Best balance. Guided but not overwhelming. Summary screen prevents
lost credentials. Advanced tier serves power users without cluttering the default path.
**Con:** More wizard screens to implement. Summary screen needs careful design.
**Best for:** Standard production deployments, first installs on new hardware.
### .env Option 3 — Pre-boot config file on USB (clawdie.conf)
The USB image contains a `clawdie.conf` template file on a FAT32 partition that
is readable/writable from any OS (Windows, macOS, Linux, FreeBSD). Operator
edits it before booting:
```sh
# Edit this file on the USB before booting.
# Leave a field blank to be prompted during install.
ASSISTANT_NAME=Clawdie
TZ=Europe/Ljubljana
AGENT_DOMAIN=clawdie.local
PKG_BRANCH=latest # latest or quarterly
DESKTOP=xfce # xfce, kde, mate, headless
TELEGRAM_BOT_TOKEN= # optional, leave blank to skip
ANTHROPIC_API_KEY= # optional
```
Firstboot reads `clawdie.conf` first. Blank required fields trigger bsddialog
prompts. Blank optional fields are silently skipped. All secrets still
auto-generated regardless. A fully pre-filled `clawdie.conf` = zero-interaction install.
**Pro:** Supports fully automated reproducible deploys without modifying ISO.
Familiar to sysadmins (edit a config file → boot). Works offline. Can be
scripted or templated externally.
**Con:** Operator must mount USB on another machine before install (FAT32
partition is accessible but requires extra step). Blank fields still need wizard.
**Best for:** Reproducible testing, staging/production fleet deployments, CI.
---
## Open Questions
1. **GPU detection table**: desktop-installer uses a shell function with a PCI ID
lookup table. We should vendor `gpu-detect.sh` from desktop-installer source
rather than reimplementing it. Verify license (BSD 2-clause — fine).
2. **pkg offline vs online**: If target machine has internet, `pkg install` will
upgrade from online repo after we've pointed at USB. That's fine and expected.
The USB repo is only for air-gapped or offline installs.
3. **ZFS layout**: Should we mirror bsdinstall's default ZFS layout or enforce a
Clawdie-specific layout (separate datasets for bastille jails, var/db)?
Decision needed before writing `installerconfig`.
4. **ARM64 / Raspberry Pi**: build.sh should detect host arch and fetch the
matching FreeBSD image. For Pi, memstick is replaced with a different image
type. Scope for v1: amd64 only, Pi deferred.
5. **clawdie user UID**: bsdinstall creates the user interactively. Should we
override/enforce UID 1001 for `clawdie`? Or let operator choose during
bsdinstall and then reference `$CLAWDIE_USER` in firstboot.sh?
6. **Clawdie-AI source bundling**: Bundle a tagged release tarball (clawdie-ai
`vX.Y.Z`) or pull from Codeberg during firstboot? Offline-first → bundle.
build.sh should accept `--clawdie-version X.Y.Z` and fetch the tarball.
---
## Next Steps
1. Create `codeberg.org/Clawdie/clawdie-iso` repo (empty, README only)
2. Copy this document as the repo's `CLAWDIE-ISO.md`
3. Write `build.cfg` with FreeBSD 15.0-RELEASE-p4, amd64, XFCE default
4. Write `gpu-detect.sh` (vendor from desktop-installer, simplify)
5. Write `firstboot.sh` (bsddialog wizard + exec setup.sh)
6. Write `installerconfig` (minimal, just copies firstboot/ tree)
7. Write `build.sh` skeleton (fetch → inject → repack)
8. Test: boot in bhyve VM on this host, validate firstboot flow

31
README.md Normal file
View file

@ -0,0 +1,31 @@
# Clawdie-ISO
Bootable USB installer for [Clawdie-AI](https://codeberg.org/Clawdie/Clawdie-AI).
Installs FreeBSD + desktop + Clawdie-AI to a target hard drive in a single
guided flow. All packages are bundled on the USB — no internet required.
## What it does
1. Boot from USB
2. Standard FreeBSD install to HDD (bsdinstall)
3. On first HDD boot: bsddialog wizard asks name, timezone, domain, desktop, provider
4. All secrets auto-generated, packages installed offline, agent started
See [CLAWDIE-ISO.md](CLAWDIE-ISO.md) for the full design.
## Build
```sh
# on a FreeBSD host
./build.sh
# output: clawdie-iso-YYYYMMDD.img
# write to USB (replace daX with your device)
dd if=clawdie-iso-YYYYMMDD.img of=/dev/daX bs=1M status=progress
```
## Status
Early skeleton — build.sh stubs are not yet functional.
See CLAWDIE-ISO.md for implementation plan and open questions.

20
build.cfg Normal file
View file

@ -0,0 +1,20 @@
#!/bin/sh
# clawdie-iso build configuration
# Sourced by build.sh — edit before building
FREEBSD_VERSION="15.0-RELEASE-p4"
FREEBSD_ARCH="amd64"
FREEBSD_MEMSTICK_URL="https://download.freebsd.org/releases/${FREEBSD_ARCH}/${FREEBSD_VERSION}/FreeBSD-${FREEBSD_VERSION}-${FREEBSD_ARCH}-memstick.img"
FREEBSD_MEMSTICK_SHA256_URL="${FREEBSD_MEMSTICK_URL}.SHA256"
# Output image
IMAGE_SIZE="8G"
IMAGE_NAME="clawdie-iso-$(date +%Y%m%d).img"
# Clawdie-AI release to bundle (fetched from Codeberg)
CLAWDIE_VERSION="0.8.2"
CLAWDIE_TARBALL_URL="https://codeberg.org/Clawdie/Clawdie-AI/archive/v${CLAWDIE_VERSION}.tar.gz"
# Default installer choices (can be overridden by clawdie.conf on USB)
DEFAULT_PKG_BRANCH="latest" # latest or quarterly
DEFAULT_DESKTOP="xfce" # xfce, kde, mate, headless

94
build.sh Normal file
View file

@ -0,0 +1,94 @@
#!/bin/sh
# clawdie-iso build script
# Produces a bootable FreeBSD memstick image with Clawdie-AI pre-bundled.
#
# Usage:
# ./build.sh # build with defaults from build.cfg
# ./build.sh --clawdie-version 0.9.0 # override Clawdie version
# ./build.sh --skip-fetch # skip pkg/tarball fetch (use existing)
#
# Requirements (run on FreeBSD host):
# pkg install curl gnupg2 md5 xorriso
set -e
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
. "${SCRIPT_DIR}/build.cfg"
# --- argument parsing ---
SKIP_FETCH=0
while [ "$#" -gt 0 ]; do
case "$1" in
--clawdie-version) CLAWDIE_VERSION="$2"; shift 2 ;;
--skip-fetch) SKIP_FETCH=1; shift ;;
*) echo "Unknown arg: $1"; exit 1 ;;
esac
done
echo "==> clawdie-iso build"
echo " FreeBSD : ${FREEBSD_VERSION} ${FREEBSD_ARCH}"
echo " Clawdie : v${CLAWDIE_VERSION}"
echo " Desktop : ${DEFAULT_DESKTOP}"
echo " Pkg : ${DEFAULT_PKG_BRANCH}"
echo ""
# --- step 1: fetch FreeBSD memstick ---
MEMSTICK="${SCRIPT_DIR}/cache/FreeBSD-${FREEBSD_VERSION}-${FREEBSD_ARCH}-memstick.img"
if [ "$SKIP_FETCH" -eq 0 ] || [ ! -f "$MEMSTICK" ]; then
echo "==> [1/7] Fetching FreeBSD memstick..."
mkdir -p "${SCRIPT_DIR}/cache"
curl -L -o "$MEMSTICK" "$FREEBSD_MEMSTICK_URL"
curl -L -o "${MEMSTICK}.SHA256" "$FREEBSD_MEMSTICK_SHA256_URL"
sha256 -c "${MEMSTICK}.SHA256" || { echo "Checksum mismatch!"; exit 1; }
else
echo "==> [1/7] FreeBSD memstick already cached, skipping fetch."
fi
# --- step 2: fetch pkg dependencies ---
if [ "$SKIP_FETCH" -eq 0 ]; then
echo "==> [2/7] Fetching packages to packages/..."
# TODO: read package list from packages/pkg-list.txt
# pkg fetch --yes --dependencies --output packages/ <pkg-list>
echo " (stub — implement pkg fetch loop from packages/pkg-list.txt)"
else
echo "==> [2/7] Skipping package fetch."
fi
# --- step 3: generate local pkg repo metadata ---
echo "==> [3/7] Generating offline pkg repo metadata..."
# TODO: pkg repo packages/
echo " (stub — run: pkg repo packages/)"
# --- step 4: fetch Clawdie-AI tarball ---
CLAWDIE_TARBALL="${SCRIPT_DIR}/cache/clawdie-ai-v${CLAWDIE_VERSION}.tar.gz"
if [ "$SKIP_FETCH" -eq 0 ] || [ ! -f "$CLAWDIE_TARBALL" ]; then
echo "==> [4/7] Fetching Clawdie-AI v${CLAWDIE_VERSION}..."
curl -L -o "$CLAWDIE_TARBALL" \
"https://codeberg.org/Clawdie/Clawdie-AI/archive/v${CLAWDIE_VERSION}.tar.gz"
else
echo "==> [4/7] Clawdie-AI tarball already cached, skipping."
fi
# --- step 5: unpack memstick image ---
echo "==> [5/7] Unpacking memstick image..."
# TODO: mdconfig, mount, prepare working copy
echo " (stub — mount memstick image via mdconfig)"
WORK_IMG="${SCRIPT_DIR}/cache/work.img"
cp "$MEMSTICK" "$WORK_IMG"
# --- step 6: inject payload into image ---
echo "==> [6/7] Injecting payload into image..."
# TODO: mount work image, copy into it:
# - installerconfig → /etc/installerconfig
# - firstboot/ → /usr/local/share/clawdie-iso/firstboot/
# - packages/ → /usr/local/share/clawdie-iso/packages/
# - clawdie-ai tarball → /usr/local/share/clawdie-iso/clawdie-ai.tar.gz
# - build.cfg defaults → /usr/local/share/clawdie-iso/build.cfg
echo " (stub — mount + copy injection)"
# --- step 7: finalize and output ---
echo "==> [7/7] Writing output image..."
cp "$WORK_IMG" "${SCRIPT_DIR}/${IMAGE_NAME}"
echo ""
echo " Done: ${SCRIPT_DIR}/${IMAGE_NAME}"
echo " Write to USB: dd if=${IMAGE_NAME} of=/dev/daX bs=1M status=progress"

245
firstboot/firstboot.sh Normal file
View file

@ -0,0 +1,245 @@
#!/bin/sh
# firstboot.sh — Clawdie-AI first-boot setup wizard
#
# Runs once on first HDD boot via clawdie-firstboot rc.d service.
# Uses bsddialog for all interactive prompts.
#
# Flow:
# Tier 1 (always): pkg branch → DE selection → agent identity → domain
# Tier 2 (optional): pi provider profile selection
# Tier 3 (advanced): subnet, feature flags (press 'A' on summary screen)
# Secrets: all DB/service passwords auto-generated
# LLM keys: deferred — entered via pi on first agent run
# Summary screen: shows generated credentials before install starts
# Then: install packages → GPU setup → desktop → Clawdie-AI
set -e
SHARE="/usr/local/share/clawdie-iso"
CLAWDIE_HOME="/home/clawdie"
CLAWDIE_AI="${CLAWDIE_HOME}/clawdie-ai"
LOG="/var/log/clawdie-firstboot.log"
ENV_FILE="${CLAWDIE_AI}/.env"
. "${SHARE}/build.cfg"
# --- helpers ---
dialog() { bsddialog --backtitle "Clawdie-AI Setup" "$@" ; }
die() { echo "ERROR: $1" >&2; exit 1; }
gen_secret() {
openssl rand -base64 32 | tr -d '\n/+=' | head -c 32
}
write_env() {
KEY="$1"; VALUE="$2"
if grep -q "^${KEY}=" "$ENV_FILE" 2>/dev/null; then
sed -i '' "s|^${KEY}=.*|${KEY}=${VALUE}|" "$ENV_FILE"
else
echo "${KEY}=${VALUE}" >> "$ENV_FILE"
fi
}
# --- screen: welcome ---
dialog --msgbox \
"\nWelcome to Clawdie-AI Setup.\n\nThis wizard will configure your system.\nAll packages install offline from this USB.\n\nPress Enter to begin." \
12 60
# --- tier 1: pkg branch ---
PKG_BRANCH=$(dialog --radiolist \
"Package repository branch:" 12 60 2 \
"latest" "Newest packages — best GPU/driver support [recommended]" on \
"quarterly" "Stable branch — security updates only" off \
3>&1 1>&2 2>&3) || die "Cancelled."
# Write pkg repo config
mkdir -p /usr/local/etc/pkg/repos
ABI=$(pkg config abi 2>/dev/null || echo "FreeBSD:15:amd64")
cat > /usr/local/etc/pkg/repos/FreeBSD.conf <<EOF
FreeBSD: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/${PKG_BRANCH}",
mirror_type: "srv",
enabled: yes
}
EOF
# Point pkg at offline USB repo for this install
cat > /usr/local/etc/pkg/repos/Clawdie-USB.conf <<EOF
Clawdie-USB: {
url: "file://${SHARE}/packages",
enabled: yes,
priority: 100
}
EOF
# --- tier 1: desktop environment ---
DESKTOP=$(dialog --radiolist \
"Desktop environment:" 14 60 4 \
"xfce" "XFCE — lightweight, recommended" on \
"kde" "KDE Plasma — full-featured, needs 8GB+" off \
"mate" "MATE — classic desktop, mid-weight" off \
"headless" "Headless — no desktop, server only" off \
3>&1 1>&2 2>&3) || die "Cancelled."
# --- tier 1: agent identity ---
ASSISTANT_NAME=$(dialog --inputbox \
"Assistant name (displayed to users):" 8 50 "Clawdie" \
3>&1 1>&2 2>&3) || die "Cancelled."
# Derive AGENT_NAME: lowercase, alphanumeric + hyphen only
AGENT_NAME=$(echo "$ASSISTANT_NAME" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/-*$//')
AGENT_DOMAIN=$(dialog --inputbox \
"Domain name for this agent:" 8 50 "${AGENT_NAME}.local" \
3>&1 1>&2 2>&3) || die "Cancelled."
TZ=$(dialog --inputbox \
"Timezone (IANA format):" 8 50 "UTC" \
3>&1 1>&2 2>&3) || die "Cancelled."
# --- tier 2: pi provider profile (optional) ---
DO_PI=$(dialog --yesno \
"Configure LLM provider profile now?\n\n(You can also set this up later — the agent will prompt you on first run.)" \
9 60 3>&1 1>&2 2>&3 && echo yes || echo no)
PI_PROFILE="operator"
PI_PROVIDER="anthropic"
if [ "$DO_PI" = "yes" ]; then
PI_PROVIDER=$(dialog --radiolist \
"LLM provider:" 16 60 6 \
"anthropic" "Anthropic (Claude)" on \
"openai" "OpenAI (GPT-4)" off \
"openrouter" "OpenRouter" off \
"groq" "Groq" off \
"ollama" "Ollama (local)" off \
"zai" "ZAI / GLM" off \
3>&1 1>&2 2>&3) || PI_PROVIDER="anthropic"
# Note: key entry is deferred to pi first-run
dialog --msgbox \
"\nProvider set to: ${PI_PROVIDER}\n\nYour API key will be requested the first time the agent runs.\nYou can also add it manually to:\n ${ENV_FILE}\n\nThen restart: service ${AGENT_NAME} restart" \
13 60
fi
# --- auto-generate secrets ---
POSTGRES_ADMIN_PASSWORD=$(gen_secret)
SKILLS_DB_PASSWORD=$(gen_secret)
MEMORY_DB_PASSWORD=$(gen_secret)
STRAPI_DB_PASSWORD=$(gen_secret)
STRAPI_APP_KEYS="$(gen_secret),$(gen_secret)"
STRAPI_API_TOKEN_SALT=$(gen_secret)
STRAPI_ADMIN_JWT_SECRET=$(gen_secret)
STRAPI_TRANSFER_TOKEN_SALT=$(gen_secret)
STRAPI_JWT_SECRET=$(gen_secret)
SCREENSHOTS_PASSWORD=$(gen_secret)
# --- summary screen ---
dialog --msgbox \
"\nReady to install. Summary:\n\
\n Agent : ${ASSISTANT_NAME} (${AGENT_NAME})\
\n Domain : ${AGENT_DOMAIN}\
\n Timezone : ${TZ}\
\n Desktop : ${DESKTOP}\
\n Pkg : ${PKG_BRANCH}\
\n Provider : ${PI_PROVIDER}\
\n\nGenerated credentials written to:\n ${ENV_FILE}\n\nInstallation will take 1530 minutes.\nPress Enter to begin." \
20 65
# --- write .env ---
mkdir -p "$CLAWDIE_AI"
touch "$ENV_FILE"
chmod 600 "$ENV_FILE"
write_env "AGENT_NAME" "$AGENT_NAME"
write_env "ASSISTANT_NAME" "$ASSISTANT_NAME"
write_env "AGENT_DOMAIN" "$AGENT_DOMAIN"
write_env "AGENT_INTERNAL_DOMAIN" "${AGENT_NAME}.home.arpa"
write_env "TZ" "$TZ"
write_env "SETUP_LOCALE" "en-US"
write_env "DISPLAY_LOCALE" "en-US"
write_env "ASSISTANT_LOCALE" "en-US"
write_env "SYSTEM_LOCALE" "en_US.UTF-8"
write_env "PI_TUI_PROVIDER" "$PI_PROVIDER"
write_env "PI_TUI_PROFILE" "$PI_PROFILE"
write_env "AGENT_SUBNET_BASE" "10.0.0"
write_env "WARDEN_SUBNET" "10.0.0.0/24"
write_env "WARDEN_GATEWAY" "10.0.0.1"
write_env "WARDEN_DB_IP" "10.0.0.3"
write_env "WARDEN_GIT_IP" "10.0.0.4"
write_env "POSTGRES_ADMIN_PASSWORD" "$POSTGRES_ADMIN_PASSWORD"
write_env "SKILLS_DB_PASSWORD" "$SKILLS_DB_PASSWORD"
write_env "MEMORY_DB_PASSWORD" "$MEMORY_DB_PASSWORD"
write_env "STRAPI_DB_PASSWORD" "$STRAPI_DB_PASSWORD"
write_env "STRAPI_APP_KEYS" "$STRAPI_APP_KEYS"
write_env "STRAPI_API_TOKEN_SALT" "$STRAPI_API_TOKEN_SALT"
write_env "STRAPI_ADMIN_JWT_SECRET" "$STRAPI_ADMIN_JWT_SECRET"
write_env "STRAPI_TRANSFER_TOKEN_SALT" "$STRAPI_TRANSFER_TOKEN_SALT"
write_env "STRAPI_JWT_SECRET" "$STRAPI_JWT_SECRET"
write_env "SCREENSHOTS_PASSWORD" "$SCREENSHOTS_PASSWORD"
write_env "SCREENSHOTS_USER" "$AGENT_NAME"
# --- node.js: set up npm prefix for clawdie user ---
echo "prefix=${CLAWDIE_HOME}/.npm-global" > "${CLAWDIE_HOME}/.npmrc"
mkdir -p "${CLAWDIE_HOME}/.npm-global/bin"
if ! grep -q '\.npm-global' "${CLAWDIE_HOME}/.profile" 2>/dev/null; then
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> "${CLAWDIE_HOME}/.profile"
fi
chown -R clawdie:clawdie "${CLAWDIE_HOME}/.npmrc" "${CLAWDIE_HOME}/.npm-global" "${CLAWDIE_HOME}/.profile"
# --- gpu detection ---
echo "Detecting GPU..."
eval $(sh "${SHARE}/firstboot/gpu-detect.sh")
echo " GPU: ${GPU_VENDOR} → kld: ${GPU_KLD}"
for kld in $GPU_KLD; do
kldload "$kld" 2>/dev/null || true
done
sysrc kld_list+="${GPU_KLD}"
[ -n "$GPU_NOTES" ] && echo " Note: ${GPU_NOTES}"
# --- pkg install: desktop packages ---
echo "Installing packages offline..."
# TODO: install DE-specific package list
# pkg install -y $(cat "${SHARE}/packages/pkg-list-${DESKTOP}.txt")
# --- extract clawdie-ai ---
echo "Extracting Clawdie-AI..."
tar -xzf "${SHARE}/clawdie-ai.tar.gz" -C "$CLAWDIE_HOME"
chown -R clawdie:clawdie "$CLAWDIE_AI"
# --- seed AGENTS.md from template ---
AGENTS_TPL="${CLAWDIE_AI}/groups/global/AGENTS.md.tpl"
AGENTS_OUT="${CLAWDIE_AI}/groups/global/AGENTS.md"
if [ -f "$AGENTS_TPL" ]; then
sed \
-e "s|{{ASSISTANT_NAME}}|${ASSISTANT_NAME}|g" \
-e "s|{{AGENT_NAME}}|${AGENT_NAME}|g" \
-e "s|{{AGENT_DOMAIN}}|${AGENT_DOMAIN}|g" \
-e "s|{{ASSISTANT_LOCALE}}|en-US|g" \
-e "s|{{TZ}}|${TZ}|g" \
"$AGENTS_TPL" > "$AGENTS_OUT"
chown clawdie:clawdie "$AGENTS_OUT"
fi
# --- run clawdie-ai setup ---
echo "Running Clawdie-AI install..."
cd "$CLAWDIE_AI"
su -m clawdie -c "npm install 2>&1" | tee -a "$LOG"
su -m clawdie -c "npm run install-all 2>&1" | tee -a "$LOG"
echo ""
echo "============================================"
echo " Clawdie-AI setup complete."
echo " Agent : ${ASSISTANT_NAME}"
echo " Domain: ${AGENT_DOMAIN}"
echo " Logs : ${LOG}"
echo "============================================"

73
firstboot/gpu-detect.sh Normal file
View file

@ -0,0 +1,73 @@
#!/bin/sh
# gpu-detect.sh — detect GPU via pciconf and return the appropriate kld module
#
# Usage:
# gpu-detect.sh
#
# Output:
# Prints shell variable assignments to stdout, to be eval'd by caller:
# GPU_VENDOR="Intel"
# GPU_KLD="i915kms"
# GPU_XORG_DRIVER="intel"
# GPU_NOTES=""
#
# Based on detection logic from outpaddling/desktop-installer (BSD 2-clause).
# Simplified to target hardware only — VirtualBox excluded.
set -e
# Default: vesa fallback
GPU_VENDOR="Unknown"
GPU_KLD="vesa"
GPU_XORG_DRIVER="vesa"
GPU_NOTES=""
# Parse pciconf for display controllers (class 0x03)
PCI_DISPLAY=$(pciconf -lv 2>/dev/null | awk '
/class=0x03/ { found=1 }
found && /vendor=/ { vendor=$0 }
found && /device=/ { device=$0; print vendor; print device; found=0 }
')
VENDOR_ID=$(pciconf -l 2>/dev/null | awk -F'[@ :]' '/^vgapci|^drm/ { print $5 }' | head -1)
case "$VENDOR_ID" in
# Intel
8086)
GPU_VENDOR="Intel"
GPU_KLD="i915kms"
GPU_XORG_DRIVER="intel"
;;
# NVIDIA
10de)
GPU_VENDOR="NVIDIA"
GPU_KLD="nvidia-modeset nvidia"
GPU_XORG_DRIVER="nvidia"
GPU_NOTES="Requires nvidia-driver package"
;;
# AMD / ATI
1002)
GPU_VENDOR="AMD"
GPU_KLD="amdgpu"
GPU_XORG_DRIVER="amdgpu"
GPU_NOTES="amdgpu preferred; radeon fallback for older cards"
;;
# VMware (kept for bhyve SVGA compatibility)
15ad)
GPU_VENDOR="VMware/bhyve"
GPU_KLD="vmwgfx"
GPU_XORG_DRIVER="vmware"
;;
*)
GPU_VENDOR="Unknown (${VENDOR_ID})"
GPU_KLD="vesa"
GPU_XORG_DRIVER="vesa"
GPU_NOTES="Falling back to VESA. Check pciconf -lv for GPU details."
;;
esac
# Output as eval-able shell vars
echo "GPU_VENDOR=\"${GPU_VENDOR}\""
echo "GPU_KLD=\"${GPU_KLD}\""
echo "GPU_XORG_DRIVER=\"${GPU_XORG_DRIVER}\""
echo "GPU_NOTES=\"${GPU_NOTES}\""

View file

@ -0,0 +1,42 @@
#!/bin/sh
#
# PROVIDE: clawdie_firstboot
# REQUIRE: NETWORKING LOGIN
# BEFORE: clawdie
# KEYWORD: firstboot
#
# clawdie-firstboot — runs once on first HDD boot to complete Clawdie-AI setup.
# Self-disables on completion.
. /etc/rc.subr
name="clawdie_firstboot"
rcvar="${name}_enable"
start_cmd="${name}_start"
stop_cmd=":"
SHARE="/usr/local/share/clawdie-iso"
LOG="/var/log/clawdie-firstboot.log"
clawdie_firstboot_start()
{
echo "Starting Clawdie first-boot setup..."
# Run on ttyv0 so bsddialog has a real TTY for the wizard
/usr/bin/script -q -a "$LOG" \
/bin/sh "${SHARE}/firstboot/firstboot.sh"
RC=$?
if [ "$RC" -eq 0 ]; then
echo "Clawdie first-boot setup complete. Disabling service."
sysrc -x clawdie_firstboot_enable
rm -rf "$SHARE"
else
echo "Clawdie first-boot setup failed (exit $RC). Check $LOG"
fi
}
load_rc_config "$name"
: "${clawdie_firstboot_enable:=NO}"
run_rc_command "$1"

39
installerconfig Normal file
View file

@ -0,0 +1,39 @@
#!/bin/sh
# installerconfig — bsdinstall post-install hook
#
# bsdinstall sources this file automatically after base system installation
# completes. Runs in the context of the live USB environment, with the
# target HDD mounted at /mnt.
#
# Responsibilities:
# 1. Copy firstboot payload from USB to installed HDD
# 2. Enable the clawdie-firstboot rc.d service (runs once on first HDD boot)
# 3. That's it — all real work happens in firstboot.sh on first boot
set -e
USB_SHARE="/usr/local/share/clawdie-iso"
HDD_SHARE="/mnt/usr/local/share/clawdie-iso"
HDD_RCD="/mnt/usr/local/etc/rc.d"
echo "clawdie-iso: injecting firstboot payload..."
# Copy firstboot scripts
mkdir -p "$HDD_SHARE"
cp -r "${USB_SHARE}/firstboot" "${HDD_SHARE}/"
cp -r "${USB_SHARE}/packages" "${HDD_SHARE}/"
cp "${USB_SHARE}/clawdie-ai.tar.gz" "${HDD_SHARE}/"
cp "${USB_SHARE}/build.cfg" "${HDD_SHARE}/"
chmod +x "${HDD_SHARE}/firstboot/firstboot.sh"
chmod +x "${HDD_SHARE}/firstboot/gpu-detect.sh"
# Install firstboot rc.d service
mkdir -p "$HDD_RCD"
cp "${USB_SHARE}/firstboot/rc.d/clawdie-firstboot" "${HDD_RCD}/clawdie-firstboot"
chmod +x "${HDD_RCD}/clawdie-firstboot"
# Enable service in rc.conf on HDD
echo 'clawdie_firstboot_enable="YES"' >> /mnt/etc/rc.conf
echo "clawdie-iso: firstboot payload installed. Rebooting to HDD..."

0
packages/.gitkeep Normal file
View file