A FreeBSD you install from a USB stick and end up with a working home media server. CopperBSD is FreeBSD 15.1 with a graphical installer, your choice of desktop, and the CopperArch media server built in: streaming apps, download apps behind a WireGuard VPN with a kill-switch, a native dashboard, and a nightly routine that keeps the machine healthy.
Download: the latest ISO is on the
releases page.
Version: see VERSION · changelog
- Boot the ISO and a graphical installer starts. Pick a desktop, a disk and a user name.
- Reboot into FreeBSD with that desktop.
- On first boot it sets up the media server by itself: one jail per app, the VPN jail and kill-switch, the nightly routine, and the dashboard.
- Open the dashboard to see everything, add your VPN, and fix problems with one click.
The system
- FreeBSD 15.1 on ZFS, delivered as a bootable live ISO (UEFI).
- A native Qt 6 graphical installer; no text menus.
- Five desktops to choose from: KDE Plasma, GNOME, XFCE, Niri and MangoWC.
- An Update button that fetches the latest CopperBSD from GitHub and applies it in place, keeping your media, settings and profile.
Media and downloads
- Jellyfin and Plex for streaming; Sonarr, Radarr and Prowlarr to find and organise films and series; Bazarr for subtitles; qBittorrent to download; Seerr for requests; Caddy as the reverse proxy.
- Each app runs in its own jail, installed from FreeBSD's own packages.
- A spare disk in the machine is never touched without you asking.
VPN
- Kernel WireGuard in a dedicated jail. Works with any provider that gives you a WireGuard config, or with a server of your own.
- The download apps are installed by default and stay offline until you add a VPN from the dashboard. If the tunnel ever drops, a pf kill-switch cuts them off; they never fall back to your real address.
- Change VPN provider by pasting a new
.conffile.
Dashboard (a native app, not a web page)
- A terminal dock beside the panels, as on the Linux edition: Claude Code, AI questions and system tools in a pane that takes a quarter, half, three quarters or all of the window.
- CPU, memory, swap, temperatures and disks, live every 2 seconds.
- Every jail, service, the VPN and the media library, refreshed every 10 seconds.
- A Needs attention list with a Fix button next to each problem and a Fix all. Fixes come from a fixed whitelist.
- Refresh, Update and Reboot; a schedule dialog for the nightly run; an app launcher and System / Network pages under More.
Self-healing
- Nightly: ZFS pool and disk health, stopped jails and services, the VPN tunnel, package updates with a ZFS snapshot taken first, clean-up of old downloads and caches.
- Nightly security check: pf on, no VPN leak (a leaking download jail is
stopped), secret files such as the WireGuard key owner-only, what the
internet can actually reach (probed from outside through the VPN), sshd,
and
pkg auditfor known-vulnerable packages on the host and in every jail, upgrading them when a fixed version is out. Pending kernel patches are flagged for a reboot.
The dashboard
| Changing VPN provider | Nightly schedule | More menu |
|---|---|---|
![]() |
![]() |
![]() |
The dashboard screenshots were taken on FreeBSD 15.1 with the app fed demonstration data: host name, addresses and downloads are made up.
The installer, running from the live ISO in a QEMU/KVM virtual machine (UEFI, 2026-10-09).
| Welcome | Choose your desktop |
|---|---|
![]() |
![]() |
| Installation destination | Create your account |
|---|---|
![]() |
![]() |
- freebsd-media-setup-with-dash: the media server on its own, for an existing FreeBSD machine.
- CopperArch-Linux-Media: the same server on Linux, with Docker.
CopperBSD is a fork of freebsd-media-setup-with-dash, restructured as a
distribution build. The media-server application under project/ is a copy
of that project, refreshed with tools/sync-project.sh rather than edited
here; the few things CopperBSD does differently (its own dashboard, no
automatic pooling of spare disks) are in project/lib/distro.py.
project/UPSTREAM records the version the copy was taken from.
./build.sh- On a FreeBSD host it builds directly.
- On Linux/macOS it provisions an ephemeral FreeBSD builder VM (QEMU) and builds inside it — you do not set up FreeBSD yourself.
- With no QEMU, push to GitHub and run the Build CopperBSD ISO Action
(
.github/workflows/build-iso.yml): it provisions a FreeBSD VM and uploads the ISO artifact. Zero local setup.
Output: build/output/CopperBSD-15.1-amd64.iso (+ .sha256).
FROM_SOURCE=1 ./build.sh builds the base with buildworld from the pinned
source (faithful, slow). FROM_SOURCE=0 (default in CI) uses the 15.1 release
sets — the same pinned release, much faster.
./test.shRuns the host-agnostic static suite always; once an ISO exists it also runs ISO structural validation and a QEMU boot test.
freebsd/ (pinned releng/15.1 @ e38a7085f3a8)
→ fetch full source at that commit
→ base system into a live root (buildworld | release sets)
→ pkg: KDE Plasma 6, Qt 6, Bastille, python, caddy, wireguard, ttyd …
→ build installer/frontend (Qt) → /usr/local/bin/copperbsd-installer
→ bake project/ → /usr/local/freebsd-media-setup (installed system has it)
→ overlays/ → live autologin that autostarts the installer
→ /usr/freebsd-dist (base.txz/kernel.txz) for the installer to extract
→ mkisoimages.sh -b → bootable UEFI+BIOS live ISO
The installer (installer/frontend, Qt/QML) drives FreeBSD's own bsdinstall
subcommands (installer/backend/copperarch-install.sh) — verified against the
pinned source — so ZFS/partitioning is FreeBSD's, wrapped in a modern graphical
flow (Welcome · Language · Keyboard · Network · Disk · User · Summary · Install).
On first boot of the installed system, the project's desktop + media stack come
up automatically.
copper-dashboard (native Qt 6/QML, dashboard/) has the same layout and
functions as the CopperArch Linux and FreeBSD dashboards — it is the same
design drawn natively instead of in a browser:
- Header — host, uptime, how fresh the data is, a verdict pill that repairs whatever is failing when clicked, and Refresh / Update / Reboot.
- Panels — Needs attention (with one-click fixes and "Fix all"), CPU & memory, Thermals & fans, Jails, Storage, Media library, Services & VPN, Daily routine. Four columns on a desktop, two or one in a narrow window.
- Repairs — the same closed whitelist the other editions use (start or
restart a jail, restart the VPN group or tunnel, Prowlarr test-all,
Cloudflare unblock,
pkg upgrade, quick routine pass), with a live log and a two-click Cancel. - Services & VPN names the VPN that is active. Change VPN provider takes a pasted WireGuard config from any provider or from a WireGuard server of your own (pre-shared keys included) and an optional name for it.
- Update → Update CopperBSD compares the installed version with the
latest on GitHub and, when asked, downloads it, replaces the media-server
project and the dashboard source, rebuilds the dashboard if it changed and
re-applies the media stack with your saved profile
(
/usr/local/libexec/copperbsd-update;--checkonly looks). - More keeps the app launcher and the local System / Network pages.
It talks over loopback HTTP to two pieces the media project installs: a
collector (status-collect.py, every 10 seconds; CPU, memory, swap and disks are read live every 2) and a root-run repair server
(status-dashboard-server.py, 127.0.0.1:8099). Without the media stack
there is no data to show and the dashboard says so.
The strip at the bottom right is the Linux edition's dock: a pane picker, an Ask box and off / ¼ / ½ / ¾ / full. Changing the width never restarts what is running; off stops it, except that an AI agent keeps working in the background (tmux) and is picked up again when its pane is reopened. Ctrl+` steps through the widths.
- The terminal is
ttydon127.0.0.1:7682, run as the desktop user (never root) from that user's crontab, shown in the one web view the dashboard has (QtWebEngine). It refuses other origins, and the address can only choose from the fixed list indashboard-pane.sh— it cannot pass a command. - The picker lists only what this machine can run. Out of the box that is Claude Code, then Shell, Processes, Jail stats, System log, Dashboard log, Disk usage and Daily routine.
- Claude Code is installed with the media server (
pkg install claude-code; sign in the first time it opens). FreeBSD packages it as a Linux program, so the installer also switches on Linux binary support (linux_enable=YES), which downloads the Linux base system — about 600 MB in all. If that fails the dock simply comes without the Claude Code pane. - Ask sends one question to Claude Code or, with an OpenRouter key in
~/.config/status-dashboard/deepseek.env, to ChatGPT, Gemini, Hy4, GLM, DeepSeek, Minimax or Qwen, with the current price shown. - The Linux edition's interactive ChatGPT / Gemini / free-model panes run
through
opencode, which has no FreeBSD package; they are left out of the picker untilopencodeis on the machine. - If
qt6-webenginecannot be installed the dashboard is built without the dock and says so; everything else works.
Tested how: the dashboard has been compiled and run on a real FreeBSD 15.1 machine (a ZFS virtual machine with the full media stack installed), on a virtual display: the panels, the narrow layouts, the dialogs, the repairs and a real update from one version to the next. It has not been run on a physical screen.
Per the project's own rule: nothing below is claimed working unless it was run.
Tested 2026-10-14, from start to finish. The ISO was built by CI
(.github/workflows/build-iso.yml), downloaded, checksum-verified, and booted
in QEMU/KVM with UEFI firmware on an NVMe disk. For each desktop the graphical
installer was driven page by page to a real install, the machine was rebooted
into the installed system, first boot ran to the end, and the desktop was
logged into through the login screen.
| Thing | Status | Evidence |
|---|---|---|
| Static validation | TESTED, PASS | ./test.sh → 83 passed, 0 failed |
| Qt installer compiles | TESTED | built inside the live root by every CI build; also built and run on Linux (Qt 6.8) to check the pages |
| ISO actually built | TESTED | CI, FreeBSD 15.1 VM, every push; iso-validate.sh checks contents and that the live scripts are executable |
| ISO boots to the installer | TESTED | CI boots every ISO (tests/ci-boot-test.sh) and fails unless the installer is running; locally ~29 s from power-on, cursor visible immediately |
| Graphical installer writes a disk | TESTED | every page driven with the mouse and keyboard; ZFS install to NVMe; "Installation complete" |
| Keyboard page | TESTED | picking uk switches the live session: the test field types £"# |
| First boot (desktop + dashboard + media stack) | TESTED | about 10–14 minutes; every step reported, no warnings |
| KDE Plasma | TESTED | login → Plasma (X11); dashboard opens by itself: 0 failed, 0 warnings, 10/10 jails |
| GNOME | TESTED | "GNOME on Xorg" preselected; login → GNOME Shell; dashboard opens by itself. GNOME's screen lock is off under SDDM (GNOME only locks with GDM) |
| XFCE | TESTED | login → XFCE; dashboard opens by itself; after a reboot the greeter is 1920x1080 with the Xfce session preselected and all ten jails back |
| Niri + QuickShell | TESTED, without a GPU | installs; niri validate accepts the config; Niri runs (nested, software rendering) with the QuickShell bar, swaybg, mako and the dashboard. Not run on real KMS hardware |
| MangoWC + QuickShell | INSTALL TESTED ONLY | packages, config and session install; MangoWC's renderer needs a DRM GPU, so it could not run in the VM. Not run on real KMS hardware |
| All of the above | NOT TESTED | the run was stopped before it finished; every desktop in it was tested on its own (above) |
| Nightly NVMe SMART check | TESTED | reads /dev/nvmeN; healthy NVMe reported OK, no false "failed" |
Niri and MangoWC need an AMD or Intel GPU: SDDM lists Wayland sessions only
when /dev/dri exists, and the installer says so.
Not run: a physical machine, a real GPU (all desktops above ran on the UEFI framebuffer), Wi-Fi, a commercial VPN provider, Nextcloud, public access.
The original authoring sandbox had no FreeBSD tooling, so the first table in this project marked the installer build, the ISO build and the boot test as NOT TESTED. Those are all tested now (above). Every CI-built ISO from 0.1.0 to 0.1.10 stopped at a text login (the live scripts had lost their executable bit); that was found by the boot test and fixed in 0.1.11.
Update, 2026-10-09. The table above describes the original authoring
sandbox. Since then an ISO has been built (CopperBSD-15.1-amd64.iso, SHA256
46576a86c7abc1800701f00cca70c48d492b0901684d626100df9c8f39401fde) and booted
in QEMU/KVM with UEFI firmware: it reaches the graphical installer, which was
stepped through from Welcome to the account page (screenshots above). A full
install to disk and the installed system's first boot were not run in that
session. ./test.sh gives 49 passed, 1 failed in this checkout — the failing
check is the vendored freebsd/src-pinned/ subset, which is not included here
(the build fetches the source at the pinned commit).
Update, 2026-10-12 — the installed system, run for real. Everything below
was run on FreeBSD 15.1-RELEASE (ZFS root, QEMU/KVM), staged the way the
installer stages it and driven by the real copperbsd_firstboot functions:
| Thing | Result |
|---|---|
| First-boot media install, from a pristine system | every step OK: host packages, Bastille, jail NAT, zroot/media, six media jails, Seerr built from source, VPN jail + qBittorrent/Sonarr/Radarr, nightly routine, dashboard backend |
| Every app answers on its jail address | Jellyfin, Plex, Prowlarr, Bazarr, Seerr, Caddy, qBittorrent, Sonarr, Radarr |
| A second disk in the machine | left untouched |
| No VPN config yet | the three download jails have no internet access at all |
| VPN added through the dashboard's API (WireGuard, with a pre-shared key) | tunnel up; the download jails reach the internet through it, including name lookups and a 9 MB download |
| Tunnel taken down | the download jails are cut off again; everything else stays online |
| Reboot | all ten jails, both firewall layers and the tunnel come back by themselves |
| Installer run again | converges without rebuilding Seerr or dropping the kill-switch; repairs jails an interrupted run left without their packages |
| Nightly routine, full pass | 42 OK, 0 warnings, 0 failures; pre-upgrade snapshots taken |
| Native dashboard | compiles and installs; on a virtual display it shows the live system as "all systems OK"; restart-a-jail, restart-the-tunnel, change-schedule and update-check all work through it |
| Terminal dock | built with QtWebEngine 6.11 by the first-boot build; ttyd runs as the desktop user on loopback only; Shell and Jail stats panes open from the picker; Claude Code is installed by the installer (removed first, to prove it), still there after a reboot, starts in the dock and keeps running after off; a plain pane is gone the moment the dock is closed; Ask reaches Claude Code |
./test.sh static checks |
71 passed, 0 failed |
Not run in that session: the graphical installer writing to a disk and the desktop download (both tested since, see Test status), the dashboard on a real screen, a real commercial VPN provider (the test used a WireGuard server of its own), Nextcloud, public access, and the dock's AI panes with a signed-in Claude account or an OpenRouter key.
- FreeBSD source commit:
e38a7085f3a8ecd317c947591fe42b5ca9ab317b(releng/15.1, GitHub mirror;git.FreeBSD.orgis proxy-blocked here — seefreebsd/SOURCE.md). - Project parts incorporated: the whole media server (
lib/,templates/,stacks/,vpn/,profiles/,install.py,rebuild) → baked to/usr/local/freebsd-media-setup; dashboard, Nextcloud, VPN/pf, self-heal all included. - Installer created: native Qt 6/QML graphical installer (
installer/frontend) + absdinstall-driving backend (installer/backend). - How it integrates with FreeBSD: the GUI collects answers and runs real
bsdinstall zfsboot/mount/distextract/bootconfig/config+ a chroot post-install; no partitioning is reimplemented. - Build command:
./build.sh - Test command:
./test.sh - ISO actually produced? YES — by CI on every push (
build-iso.yml), or./build.shon a FreeBSD host / the QEMU builder. - ISO filename:
build/output/CopperBSD-15.1-amd64.iso - ISO checksum: emitted next to every build (
.sha256); the tested 2026-10-14 build was27351ad01bb3db851ad1c802b5c5d9616ba5b6ed3430c9fae121239a5605f72e. - ISO boot-tested? YES — CI boots every ISO (
tests/ci-boot-test.sh), and it was booted and installed by hand in QEMU/KVM (see Test status). - Full install tested? YES — GUI install to disk, first boot and login for KDE, GNOME, XFCE and Niri (Niri without a GPU); MangoWC install only; the "All of the above" choice not run (see Test status).
- Unfinished: testing on physical hardware with a real GPU (needed for Niri/MangoWC proper).
CopperBSD/
├── freebsd/ pinned FreeBSD 15.1 source subset + COMMIT + SOURCE.md
├── project/ the media-server application (synced from upstream)
├── tools/ sync-project.sh (refresh project/ from upstream)
├── installer/
│ ├── frontend/ Qt 6/QML graphical installer
│ ├── backend/ bsdinstall-driving install driver
│ └── configuration/ installerconfig + firstboot
├── overlays/ live-ISO files (autologin → installer, live markers)
├── release/ release.conf (stock release.sh path, optional)
├── build/ config.sh, fetch-src.sh, in-vm-build.sh, freebsd-builder.sh
├── tests/ static-checks.sh, iso-validate.sh, boot-test.sh
├── build.sh test.sh one-command build / test
└── ARCHITECTURE.md component-by-component origin + Linux-ism audit
See build/MANIFEST.md for full traceability (what differs from upstream 15.1).








