Skip to content
CopperArchPublic

About

A FreeBSD 15.1 distribution with a home media server built in: bootable ISO, graphical installer, five desktops, Jellyfin/Plex/Sonarr/Radarr in jails, downloads locked behind WireGuard with a kill-switch, a native live dashboard with one-click fixes, and a nightly repair routine.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

CopperBSD

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

The CopperBSD dashboard

What it does

  1. Boot the ISO and a graphical installer starts. Pick a desktop, a disk and a user name.
  2. Reboot into FreeBSD with that desktop.
  3. 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.
  4. Open the dashboard to see everything, add your VPN, and fix problems with one click.

Features

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 .conf file.

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 audit for 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.

Screenshots

The dashboard

Changing VPN provider Nightly schedule More menu
Change VPN provider Schedule dialog 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
Installer welcome page Desktop selection page
Installation destination Create your account
Disk selection page Account page

Related projects

How it is put together

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 it (one command, no FreeBSD host required)

./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 it

./test.sh

Runs the host-agnostic static suite always; once an ISO exists it also runs ISO structural validation and a QEMU boot test.

What actually happens in the build

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.

The dashboard

CopperBSD dashboard

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; --check only 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 terminal dock

The terminal dock with Claude Code

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 ttyd on 127.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 in dashboard-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 until opencode is on the machine.
  • If qt6-webengine cannot 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.

Test status

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.

Earlier history

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.

§18 acceptance report

  1. FreeBSD source commit: e38a7085f3a8ecd317c947591fe42b5ca9ab317b (releng/15.1, GitHub mirror; git.FreeBSD.org is proxy-blocked here — see freebsd/SOURCE.md).
  2. 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.
  3. Installer created: native Qt 6/QML graphical installer (installer/frontend) + a bsdinstall-driving backend (installer/backend).
  4. 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.
  5. Build command: ./build.sh
  6. Test command: ./test.sh
  7. ISO actually produced? YES — by CI on every push (build-iso.yml), or ./build.sh on a FreeBSD host / the QEMU builder.
  8. ISO filename: build/output/CopperBSD-15.1-amd64.iso
  9. ISO checksum: emitted next to every build (.sha256); the tested 2026-10-14 build was 27351ad01bb3db851ad1c802b5c5d9616ba5b6ed3430c9fae121239a5605f72e.
  10. 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).
  11. 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).
  12. Unfinished: testing on physical hardware with a real GPU (needed for Niri/MangoWC proper).

Structure

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

About

A FreeBSD 15.1 distribution with a home media server built in: bootable ISO, graphical installer, five desktops, Jellyfin/Plex/Sonarr/Radarr in jails, downloads locked behind WireGuard with a kill-switch, a native live dashboard with one-click fixes, and a nightly repair routine.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages