docs: add guide on testing NaxOS on macOS with QEMU and local development
Validate Documentation / lint-docs (push) Successful in 14s

This commit is contained in:
Lukas Holzner
2026-09-04 15:48:04 +02:00
parent 38d4c781c3
commit 83ea2aab4f
9 changed files with 285 additions and 0 deletions
+1
View File
@@ -62,6 +62,7 @@ Curated 1-click self-hosted application catalog with hardware-accelerated Immich
### 2. User & Administrator Guides ### 2. User & Administrator Guides
- [Installation & Live ISO Guide](docs/guides/installation.md) - [Installation & Live ISO Guide](docs/guides/installation.md)
- [Testing NaxOS on macOS (QEMU & Local)](docs/guides/testing-on-mac.md)
- [Migration Guide: Transitioning from `nixos-lukas` to NaxOS](docs/guides/migration-from-nixos-lukas.md) - [Migration Guide: Transitioning from `nixos-lukas` to NaxOS](docs/guides/migration-from-nixos-lukas.md)
### 3. Developer Manual ### 3. Developer Manual
+284
View File
@@ -0,0 +1,284 @@
# Testing NaxOS on macOS
This guide provides step-by-step instructions for testing and running the complete **NaxOS** appliance on macOS. It covers full live virtual machine testing with QEMU, rapid local frontend/API development, and headless automated verification.
---
## Testing Methods Overview
| Method | Target Use Case | Requirements |
| :--- | :--- | :--- |
| **Method 1: Full Live QEMU VM** | Complete appliance test: Linux 6.18 kernel, real OpenZFS pool `tank`, live `naxos-api` daemon, and `naxos-ui` web interface. | `qemu-system-x86_64` (Homebrew or Nix) |
| **Method 2: Local UI / API Dev** | Rapid UI development, styling iteration, and component testing on macOS without a VM. | Node.js 20+, npm |
| **Method 3: Headless Verification** | Automated end-to-end browser capture and visual validation. | Python 3 + Playwright or Chromium |
---
## Method 1: Full Appliance VM Testing via QEMU
This method boots the actual NaxOS NixOS 26.05 operating system with Linux kernel 6.18, creates a virtual target disk, provisions a real OpenZFS pool (`tank`), and runs the full API daemon and Web UI with port forwarding to macOS.
### 1. Prerequisites
Install QEMU on macOS using Homebrew (or via Nix):
```bash
# Using Homebrew
brew install qemu
# Or if you use Nix on macOS:
nix-shell -p qemu
```
Verify that `qemu-system-x86_64` is available:
```bash
qemu-system-x86_64 --version
```
> [!NOTE]
> **Apple Silicon (M1/M2/M3/M4) Note**: When running `qemu-system-x86_64` on Apple Silicon Macs, QEMU uses TCG (Tiny Code Generator) software emulation. Because it is emulating x86_64 on ARM64, boot times will take approximately 1530 seconds.
---
### 2. Download the NaxOS Installation Media
Download the official bootable ISO from the Gitea package registry:
```bash
curl -fSL -o /tmp/naxos-installer.iso \
"https://git.lholz.de/api/packages/naxos/generic/naxos-installer-iso/1.0.0/naxos-installer-x86_64-linux.iso"
```
---
### 3. Prepare Boot Artifacts & Virtual Drive
Extract the direct-boot kernel and initrd from the ISO (or boot directly from the ISO cdrom):
```bash
# Create a 20 GB QCOW2 virtual drive for OpenZFS testing
qemu-img create -f qcow2 /tmp/naxos-target-disk.qcow2 20G
# Mount or extract the ISO to retrieve the kernel and initrd
mkdir -p /tmp/naxos-iso-mnt /tmp/naxos-boot
hdiutil attach -nobrowse -readonly -mountpoint /tmp/naxos-iso-mnt /tmp/naxos-installer.iso
cp -R /tmp/naxos-iso-mnt/boot /tmp/naxos-boot/
hdiutil detach /tmp/naxos-iso-mnt
```
Find the extracted kernel and initrd paths:
```bash
KERNEL=$(find /tmp/naxos-boot -name bzImage | head -n 1)
INITRD=$(find /tmp/naxos-boot -name initrd | head -n 1)
echo "Kernel: $KERNEL"
echo "Initrd: $INITRD"
```
---
### 4. Launch the NaxOS Virtual Appliance
Start QEMU with:
- **RAM**: 2048 MB (2 GB)
- **CPU**: 2 cores (`-smp 2`)
- **Storage**: VirtIO disk attached to `/tmp/naxos-target-disk.qcow2` (appears as `/dev/vda` in the VM)
- **Networking**: User-mode networking with port forwarding:
- Host port `18080` -> VM port `80` (NaxOS Web UI)
- Host port `18443` -> VM port `8443` (NaxOS HTTPS / API)
- **Serial Console**: Stdio redirected to your terminal for real-time kernel and shell access.
```bash
qemu-system-x86_64 \
-m 2048 \
-smp 2 \
-kernel "$KERNEL" \
-initrd "$INITRD" \
-append "init=/nix/store/*-nixos-system-nixos-*/init console=ttyS0,115200n8 loglevel=6" \
-drive file=/tmp/naxos-target-disk.qcow2,if=virtio,format=qcow2 \
-net nic,model=virtio \
-net user,hostfwd=tcp::18443-:8443,hostfwd=tcp::18080-:80 \
-nographic
```
Once booted, press **Enter** to get the root prompt:
```text
<<< Welcome to NixOS 26.05! >>>
[root@nixos:~]#
```
---
### 5. Initialize the OpenZFS Storage Pool in the VM
Inside the VM terminal, create the real OpenZFS pool `tank` on the virtual drive `/dev/vda`:
```bash
# Create OpenZFS pool
zpool create tank /dev/vda -f
# Create standard appliance datasets
zfs create tank/media
zfs create tank/backup
zfs create tank/container
# Verify pool health and status
zpool status
zpool list
zfs list
```
You should see:
```text
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
tank 19.5G 832K 19.5G - - 0% 0% 1.00x ONLINE -
```
---
### 6. Verify Real System Metrics & Declarative Rebuild
The NaxOS Management Daemon (`naxos-api`) queries real kernel interfaces rather than mocked data:
- **ZFS ARC Cache**: `/proc/spl/kstat/zfs/arcstats`
- **Uptime**: `/proc/uptime`
- **Memory**: `os.totalmem()` and `os.freemem()`
- **Disk I/O**: `/proc/diskstats`
- **Journal Logs**: Real `journalctl -n 100 -o json --no-pager`
Test the API daemon endpoints from inside the VM or from macOS:
```bash
# Check system metrics from your Mac:
curl http://localhost:18443/api/v1/system/metrics
# Check real ZFS pools:
curl http://localhost:18443/api/v1/storage/pools
# Check active system services:
curl http://localhost:18443/api/v1/system/services
```
#### Testing "Apply Changes" (Live SSE Stream)
Trigger a declarative system rebuild from macOS:
```bash
curl -N -X POST http://localhost:18443/api/v1/system/rebuild
```
You will receive real Server-Sent Events (SSE) streaming live steps:
1. `validating` - Synthesizing declarative NixOS configuration.
2. `building` - Executing `/run/current-system/sw/bin/nixos-rebuild dry-activate`.
3. `reloading` - Applying dataset quotas and reloading Samba shares.
4. `completed` - Generation activated successfully.
---
### 7. Accessing the Web Dashboard
Open your web browser on macOS and navigate to:
```text
http://localhost:18080
```
You will see:
1. **Glassmorphism Appliance Dashboard**: Live storage capacity, real ARC hit ratio (100%), ARC cache size, CPU and memory gauges.
2. **Storage Management**: Visual hierarchy of pool `tank`, datasets (`media`, `backup`, `container`), and snapshots.
3. **App Store**: Curated 1-click catalog (Immich, Jellyfin, Nextcloud, Vaultwarden).
4. **Apply Changes Modal**: Interactive rebuild viewer streaming real systemd activation logs.
---
### 8. Clean VM Shutdown
To cleanly unmount OpenZFS datasets, export the storage pool, and power down the virtual machine, execute inside the VM console:
```bash
poweroff
```
Or press `Ctrl+A` then `X` to exit QEMU immediately.
---
## Method 2: Local UI & API Rapid Development
To develop or preview the Web UI directly on macOS without launching QEMU:
### 1. Build and Run the Web UI
```bash
cd naxos-ui
# Install dependencies
npm install
# Start Vite development server
npm run dev
```
Open `http://localhost:5173` in your browser.
### 2. Previewing the Production SPA Build
```bash
cd naxos-ui
npm run build
npm run preview -- --port 3000
```
Open `http://localhost:3000` to inspect the production asset bundle.
### 3. Running the Management Daemon Locally (Mock Fallback)
If running `naxos-api` on macOS outside of Linux, it automatically falls back gracefully when Linux-specific paths (`/proc/spl/kstat/zfs/arcstats`, `zpool`) are unavailable:
```bash
cd naxos-api
npm install
npm run build
npm start
```
The API daemon listens on `http://localhost:8088`.
---
## Method 3: Headless Automated Browser Verification
To run headless end-to-end tests or re-generate screenshots automatically:
```bash
# Using Python Playwright
python3 -m pip install playwright
python3 -m playwright install chromium
# Run headless screenshot verification
python3 - << 'EOF'
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto("http://localhost:18080")
page.wait_for_selector("text=System Healthy")
page.screenshot(path="test-overview.png")
print("Screenshot captured successfully!")
browser.close()
EOF
```
---
## Troubleshooting & FAQ
### Port `18080` or `18443` is already in use
Check which process is listening on the port:
```bash
lsof -i :18080
lsof -i :18443
```
Kill the stale process or change the `-net user,hostfwd=tcp::18080-:80` parameter in the QEMU launch command to an alternate port (e.g. `hostfwd=tcp::28080-:80`).
### Strict MIME Type Errors (`Failed to load module script`)
Browsers enforce strict MIME checking for ES modules (`Content-Type: text/javascript; charset=utf-8`). If serving files via Python or custom static servers, ensure MIME types are explicitly configured:
```python
import mimetypes
mimetypes.add_type('application/javascript', '.js')
mimetypes.add_type('text/css', '.css')
```
The standard `naxos-api` and production `nginx` containers already include appropriate MIME headers out of the box.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 295 KiB