Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Machine Management

These notes are still a work-in-progress and are currently largely for my personal use only.

Home-Manager Example

  1. Install Nix standalone:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
  1. Set proper Nix settings in /etc/nix/nix.conf:
substituters = https://cache.nixos.org/ https://github-public.cachix.org
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= github-public.cachix.org-1:xofQDaQZRkCqt+4FMyXS5D6RNenGcWwnpAXRXJ2Y5kc=
narinfo-cache-positive-ttl = 0
narinfo-cache-negative-ttl = 0
experimental-features = nix-command flakes auto-allocate-uids
  1. Add these Nix channels via nix-channel --add URL NAME:
$ nix-channel --list
home-manager https://github.com/nix-community/home-manager/archive/release-25.11.tar.gz
nixpkgs https://nixos.org/channels/nixos-25.11
  1. Install home-manager: https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone

Example home.nix file for personal use:

{ config, pkgs, lib, ... }:
let
  user = "andrew";
  homedir = "/home/${user}";
  anixsrc = ./path/to/sources/anixpkgs/.;
  claudeDefaults = import "${anixsrc}/pkgs/nixos/claude-defaults.nix";
in with import "${anixsrc}/pkgs/nixos/dependencies.nix"; {
  home.username = user;
  home.homeDirectory = homedir;
  programs.home-manager.enable = true;

  imports = [
    "${anixsrc}/pkgs/nixos/components/opts.nix"
    "${anixsrc}/pkgs/nixos/components/base-pkgs.nix"
    "${anixsrc}/pkgs/nixos/components/base-dev-pkgs.nix"
    "${anixsrc}/pkgs/nixos/components/claude-agent.nix"  # omit if not using Claude Code
    "${anixsrc}/pkgs/nixos/components/x86-rec-pkgs.nix"
    "${anixsrc}/pkgs/nixos/components/x86-graphical-pkgs.nix"
    "${anixsrc}/pkgs/nixos/components/x86-graphical-dev-pkgs.nix"
    "${anixsrc}/pkgs/nixos/components/x86-graphical-rec-pkgs.nix"
  ];

  mods.opts.standalone = true;
  mods.opts.homeDir = homedir;
  mods.opts.homeState = "23.05";
  mods.opts.browserExec = "google-chrome-stable";

  mods.claude = {
    marketplaces = claudeDefaults.marketplaces;
    plugins = claudeDefaults.plugins;
    permissionsAllow = claudeDefaults.permissionsAllow;
    hooks = claudeDefaults.hooks;
    skills = claudeDefaults.skills;
    mcpServers = [ claudeDefaults.mcpServers.notion claudeDefaults.mcpServers.wiki ];
    graphical = true;
  };
}

*-rec-* packages can be removed for non-recreational use.

Symlink to ~/.config/home-manager/home.nix.

Corresponding ~/.bashrc:

export NIX_PATH=$HOME/.nix-defexpr/channels:/nix/var/nix/profiles/per-user/root/channels${NIX_PATH:+:$NIX_PATH}
. "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh"
export NIXPKGS_ALLOW_UNFREE=1
# alias code='codium'
# eval "$(direnv hook bash)"

Personal Machine Installation Instructions

Sources

  • https://nixos.wiki/wiki/NixOS_Installation_Guide
  • https://alexherbo2.github.io/wiki/nixos/install-guide/
  1. Build the installation ISO with NIXPKGS_ALLOW_UNFREE=1 nix build .#nixosConfigurations.installer-personal.config.system.build.isoImage
  2. Plug in a USB stick large enough to accommodate the image.
  3. Find the right device with lsblk or fdisk -l. Replace /dev/sdX with the proper device (do not use /dev/sdX1 or partitions of the disk; use the whole disk /dev/sdX).
  4. Burn ISO to USB stick with dd if=result/iso/[...]linux.iso of=/dev/sdX bs=4M status=progress conv=fdatasync
  5. On the new machine, one-time boot UEFI into the USB stick on the computer (will need to disable Secure Boot from BIOS first)
  6. Login as the user andrew
  7. Connect to the internet
  8. Within the installer, run sudo anix-install
  9. If everything went well, reboot
  10. On the next reboot, login as user andrew again
  11. Connect to the internet
  12. Run anix-init
  13. Enjoy!

JetPack Machine Installation Instructions

  1. Ensure that the device has UEFI firmware installed. See https://github.com/anduril/jetpack-nixos.
  2. Build the installation ISO with nix build .#nixosConfigurations.installer-jetpack.config.system.build.isoImage
  3. Plug in a USB stick large enough to accommodate the image.
  4. Find the right device with lsblk or fdisk -l. Replace /dev/sdX with the proper device (do not use /dev/sdX1 or partitions of the disk; use the whole disk /dev/sdX).
  5. Burn ISO to USB stick with dd if=result/iso/[...]linux.iso of=/dev/sdX bs=4M status=progress conv=fdatasync
  6. Insert the USB drive into the Jetson device. On the AGX devkits, I’ve had the best luck plugging into the USB-C slot above the power barrel jack. You may need to try a few USB options until you find one that works with both the UEFI firmware and the Linux kernel.
  7. Press power / reset as needed. When prompted, press ESC to enter the UEFI firmware menu. In the “Boot Manager”, select the correct USB device and boot directly into it.
  8. Connect to the internet
  9. Within the installer, run sudo anix-install
  10. If everything went well, reboot
  11. On the next reboot, login as user andrew again
  12. Connect to the internet
  13. Run anix-init
  14. Enjoy!

Upgrading NixOS versions with anixpkgs

Aside from the source code changes in anixpkgs, ensure that your channels have been updated for the root user:

# e.g., upgrading to 25.11:
home-manager https://github.com/nix-community/home-manager/archive/release-25.11.tar.gz
nixos https://nixos.org/channels/nixos-25.11
nixpkgs https://nixos.org/channels/nixos-25.11

sudo nix-channel --update. Then upgrade with

anix-upgrade [source specification] --local --boot

Build a JetPack Installer ISO

Cross-compiled from x86_64. Requires binfmt support for aarch64 (enabled by default on NixOS with boot.binfmt.emulatedSystems).

nix build .#nixosConfigurations.installer-jetpack.config.system.build.isoImage
dd if=result/iso/[...]linux.iso of=/dev/sdX bs=4M status=progress conv=fdatasync

Build a NixOS ISO Image

TODO (untested); work out hardware configuration portion.

nixos-generate -f iso -c /path/to/personal/configuration.nix [-I nixpkgs=/path/to/alternative/nixpkgs]
sudo dd if=/path/to/nixos.iso of=/dev/sdX bs=4M conv=fsync status=progress

Local SSL Setup for HTTPS Access

For machines configured with runWebServer = true (like ATS), you can enable HTTPS access from devices on your local network (especially phones) to avoid browser security warnings.

Quick Start

  1. Generate SSL certificates on the server:

    generate-local-ssl-certs
    

    The script will auto-detect your LAN IP address and create certificates in ~/secrets/vpn/.

    If you need to specify a different IP address:

    generate-local-ssl-certs 192.168.1.100
    
  2. Install the CA certificate on your client devices:

    Transfer ~/secrets/vpn/rootCA.pem to your phone and install it:

    Android:

    • Settings → Security → Encryption & credentials → Install a certificate
    • Choose “CA certificate” and select rootCA.pem
    • Give it a name like “ATS Local CA”

    iPhone/iPad:

    • Email or AirDrop rootCA.pem to your device
    • Open the file to install the profile
    • Settings → General → VPN & Device Management → Install the profile
    • Settings → General → About → Certificate Trust Settings → Enable full trust
  3. Access your server via HTTPS:

    https://ats.local:443
    

    Or use HTTP if you prefer (no automatic redirect):

    http://ats.local:80
    

How It Works

  • The nginx server listens on both HTTP (port 80) and HTTPS (port 443)
  • There is no automatic redirect from HTTP to HTTPS - both protocols are supported
  • The certificates are valid for:
    • [hostname].local (e.g., ats.local)
    • *.[hostname].local (wildcard for subdomains)
    • localhost, 127.0.0.1, and your LAN IP address
  • Certificates are stored in ~/secrets/vpn/ and backed up by rcrsync
  • Certificates expire after ~825 days and can be regenerated anytime

Regenerating Certificates

If your server IP changes or certificates expire:

generate-local-ssl-certs [new-ip-address]

The script will backup old certificates and create new ones. You won’t need to reinstall the CA on your devices if you’re replacing server certificates signed by the same CA.

Troubleshooting

SSL warnings still appear:

  • Verify you installed rootCA.pem (the CA certificate), not chain.pem
  • On iOS, ensure you enabled full trust in Certificate Trust Settings
  • Try clearing browser cache or restarting the browser

Connection refused:

  • Check firewall allows ports 80 and 443: sudo iptables -L -n | grep -E '80|443'
  • Verify nginx is running: systemctl status nginx

See Local SSL Setup for complete documentation.

Vikunja Task Management (ATS Only)

ATS machines automatically include Vikunja, an open-source task management system designed for collaboration between you and Claude Code.

Accessing Vikunja

Once your ATS machine is running, Vikunja is accessible at:

  • Web UI (HTTPS): https://ats.local:3457/
  • Web UI (HTTP): http://ats.local:3457/
  • API: https://ats.local/vikunja/api/v1/ or http://ats.local/vikunja/api/v1/

The web UI is mobile-friendly and served through nginx on port 3457 with both HTTP and HTTPS support. The API is accessible at /vikunja/ on the default ports (80/443) due to how the frontend is built in nixpkgs.

Note: For HTTPS access to work without certificate warnings, you need to install the SSL certificate on your client devices. See the Local SSL Setup section above.

Initial Setup

  1. Create your first user (registration is disabled after first use for security):

    # Open https://ats.local:3457/ in a browser and register
    # After registration, the service will reject new registrations
    
  2. Get your API token for MCP integration:

    • Log in to Vikunja
    • Go to Settings → API Tokens
    • Create a new token and save it securely
  3. Configure Claude Code MCP (see MCP Integration section below)

Using Vikunja with Claude Code

Once you’ve configured the MCP integration (see below), Claude Code can directly interact with your Vikunja tasks during conversations.

Typical Workflow

You (via Web/Phone):

  1. Open https://ats.local:3457/ on your device
  2. Create projects for different areas (e.g., “Development”, “Personal”, “Research”)
  3. Create tasks with descriptions, priorities, and due dates
  4. Review and comment on tasks Claude has worked on

Claude Code (via MCP):

  1. Lists your tasks when you start a conversation: “What should I work on?”
  2. Creates subtasks as it breaks down complex work
  3. Updates task status and adds progress comments
  4. Marks tasks complete when finished
  5. Creates new tasks for follow-up work or issues discovered

Example Interactions

  • You: “What tasks do I have in the Development project?”

    • Claude lists all tasks with their status, priority, and descriptions
  • You: “Work on task 42”

    • Claude reads the task details and gets to work
    • Creates subtasks for each step
    • Adds comments with progress updates
    • Marks task complete when done
  • You: “Create a task to implement user authentication with JWT”

    • Claude creates the task with a detailed description
    • Can immediately start working on it if requested

MCP Integration

The Vikunja MCP server (vikunja-mcp-server) is automatically installed on ATS machines and provides direct integration between Claude Code and Vikunja.

Getting Your API Token

  1. Log in to Vikunja at https://ats.local:3457/
  2. Go to Settings → API Tokens
  3. Click “Create new token”
  4. Copy the generated token (you won’t be able to see it again!)

Configuring Claude Code

Option 1: Automatic Configuration (Recommended)

The easiest way is to store your API token in ~/secrets/vikunja/secrets.json on the ATS machine:

{
  "token": "your-api-token-here"
}

After adding the token, rebuild your system configuration to install the MCP server:

sudo nixos-rebuild switch

Then run the claude-setup script to register the MCP server:

claude-setup

The setup script will automatically:

  • Install Claude Code plugins
  • Register the Vikunja MCP server using your token from ~/secrets/vikunja/secrets.json
  • Configure other development tools (gh CLI, etc.)

To verify the MCP server is registered:

claude mcp list

Manual MCP Configuration

If you need to manually configure the Vikunja MCP server (without using claude-setup), you can use:

claude mcp add -s user \
  -e VIKUNJA_URL=https://ats.local:3457 \
  -e VIKUNJA_API_TOKEN=your-token-here \
  -- vikunja /run/current-system/sw/bin/vikunja-mcp-server

Note: Use HTTPS (https://ats.local:3457) for the URL to ensure secure API access.

Available MCP Tools

Once configured, Claude Code has native access to these Vikunja tools:

  • vikunja_list_projects, vikunja_get_project - Browse and view projects
  • vikunja_list_tasks, vikunja_get_task - View tasks
  • vikunja_create_task - Create new tasks
  • vikunja_update_task - Update task fields (title, description, priority, etc.)
  • vikunja_complete_task - Mark tasks as done
  • vikunja_add_comment, vikunja_get_comments - Add and view task comments

Usage Examples

With the MCP server configured, you can interact with Vikunja directly in Claude Code:

  • “What tasks do I have in the Development project?”
  • “Create a task in project 5 to implement user authentication”
  • “Add a comment to task 42 with the latest progress”
  • “Mark task 15 as complete”
  • “Update task 23’s priority to high”

Data Location

  • Database: /var/lib/vikunja/vikunja.db (SQLite)
  • Files: /var/lib/vikunja/files/
  • Logs: /var/lib/vikunja/logs/
  • Configuration: /etc/vikunja/config.yml

Backup

The Vikunja database is automatically backed up daily at midnight by the ats-vikunja-backup orchestrator job:

  • Copies /var/lib/vikunja/vikunja.db to ~/data/vikunja/vikunja.db
  • Syncs to cloud storage via rcrsync override data vikunja

You can manually trigger a backup with:

ssh andrew@ats.local
sudo systemctl start ats-vikunja-backup.service

Or manually copy the database:

ssh andrew@ats.local
sudo cp /var/lib/vikunja/vikunja.db ~/backups/vikunja-$(date +%Y%m%d).db

Architecture

Vikunja is served through nginx as a reverse proxy with HTTPS support:

  • Vikunja serves both the web UI and API from a single service on internal port 3456
  • Frontend accessible at https://ats.local:3457 (HTTPS) or http://ats.local:3457 (HTTP)
  • API accessible at https://ats.local/vikunja/api/v1/ (HTTPS) or http://ats.local/vikunja/api/v1/ (HTTP)
  • Internal port: 3456 (centrally managed in service-ports.nix)
  • External ports: 3457 (frontend with HTTPS/HTTP), 80/443 (API via /vikunja/)
  • SSL certificates: Same self-signed certificates as main web server (~/secrets/vpn/)

The nixpkgs Vikunja frontend is built with /vikunja/ as the hardcoded API base path, so we serve the frontend on port 3457 while also proxying /vikunja/ on ports 80/443 for API access. Both HTTP and HTTPS are supported without forced redirects.

ATS machines automatically include Navidrome, a self-hosted music streaming server compatible with the Subsonic API.

Accessing Navidrome

Once your ATS machine is running, Navidrome’s web UI is accessible at:

  • Web UI (HTTPS): https://ats.local/navidrome/
  • Web UI (HTTP): http://ats.local/navidrome/

It is served through nginx as a reverse proxy on the default ports (80/443) under the /navidrome path. The web UI is mobile-friendly.

Note: For HTTPS access to work without certificate warnings, you need to install the SSL certificate on your client devices. See the Local SSL Setup section above.

Initial Setup

On your first visit to https://ats.local/navidrome/, Navidrome prompts you to create an admin account. Create it, then log in. (There is no separate registration step — the first account becomes the administrator.)

Adding Music

Navidrome has no upload button in its web UI — it is a streaming server that scans a folder for music. To add songs you copy files into the music folder on the server, then let Navidrome scan them.

The music folder is ~/data/navidrome/music on ATS (owned by andrew:dev). Copy files into it however is convenient, for example:

# From your local machine, copy an album or directory over:
rsync -av "My Album/" andrew@ats.local:~/data/navidrome/music/

# Or a single file:
scp song.flac andrew@ats.local:~/data/navidrome/music/

Navidrome automatically rescans the folder on a schedule and picks up new files; you can also force an immediate scan from the web UI under Settings → (gear menu) → Quick Scan / Full Scan. Supported formats include MP3, FLAC, OGG, M4A/AAC, WAV, and more — metadata (artist/album/track/cover art) is read from the files’ tags.

Don’t add music via the cloud copy. ~/data is mirrored to box:data, but the nightly backup runs rcrsync override data navidrome (local → cloud, overwriting the cloud copy). Files added to the cloud side would be wiped on the next backup. Always add music to the folder on the server itself.

Connecting Third-Party Client Apps

Because Navidrome implements the Subsonic API, you can stream your library from any Subsonic-compatible app instead of (or in addition to) the web UI. Popular clients:

In the client, add a new Subsonic / Navidrome server with these settings:

SettingValue
Server URL / Addresshttp://ats.local/navidrome (or https://ats.local/navidrome)
Usernameyour Navidrome account username
Passwordyour Navidrome account password

Notes:

  • Use the base URL including the /navidrome path — the client appends the Subsonic /rest/ endpoints itself (the API lives at http://ats.local/navidrome/rest/).
  • If you use the https:// URL, the device must trust the ATS self-signed certificate (see Local SSL Setup); the http:// URL works without it on the LAN.
  • ats.local is resolved via mDNS, so the client must be on the same LAN as the ATS machine (or have the hostname otherwise reachable).

Data Location

  • Music library: ~/data/navidrome/music/
  • Database & cache: ~/data/navidrome/ (Navidrome’s navidrome.db and cache live here)

Both the data folder and the music folder live under ~/data so they are included in the daily cloud backup.

Backup

The Navidrome data directory is automatically backed up daily at midnight by the ats-navidrome-backup orchestrator job, which syncs ~/data/navidrome to cloud storage via rcrsync override data navidrome.

You can manually trigger a backup with:

ssh andrew@ats.local
sudo systemctl start ats-navidrome-backup.service

Architecture

Navidrome is served through nginx as a reverse proxy:

  • Runs as the andrew:dev user (so the music/data folders live in ~/data and remain readable by the backup job)
  • Internal service listens on 127.0.0.1:4533 (centrally managed in service-ports.nix)
  • Started with --baseurl /navidrome so all URLs are served under the /navidrome subpath, proxied on ports 80/443
  • SSL certificates: same self-signed certificates as the main web server (~/secrets/vpn/)

Video Downloader (ATS Only)

ATS machines include a web UI for downloading videos from YouTube, TikTok, and any other site supported by yt-dlp. Accessible at https://ats.local/videodl/.

Some sites (including TikTok) require authentication cookies for downloading. YouTube works without cookies for public videos, but cookies may be needed for age-restricted content or to avoid bot-detection.

1. Export cookies from your browser

Use the Get cookies.txt LOCALLY browser extension (Chrome/Firefox) to export cookies in Netscape format:

  1. Log in to the site (TikTok, YouTube, etc.) in your browser
  2. Click the extension icon while on that site
  3. Choose ExportCurrent Site (or All Sites to cover everything at once)
  4. Save the file

2. Place cookies.txt on the server

Copy or paste the file contents to:

~/configs/VideoDownloader/cookies.txt

The directory is created automatically when the service starts. The file is read on each download request, so no restart is needed after updating it.

3. Verify it works

Open https://ats.local/videodl/ in a browser, paste a video URL, and click Fetch.

Browser cookies expire periodically. When downloads start failing with authentication errors, re-export cookies.txt from your browser.

Architecture

  • Service: vdlserver.service (systemd), port 6060
  • Backend: yt-dlp subprocess, merges to mp4 via ffmpeg
  • Nginx proxy: /videodl/http://127.0.0.1:6060/videodl/
  • Settings: ~/configs/VideoDownloader/cookies.txt (syncs via rcrsync)
  • Temp downloads: /tmp/ttvd/<token>/ (cleaned up after each transfer)

Sunshine Game Streaming (Personal Machines)

Personal machines include Sunshine, a self-hosted game stream host for Moonlight clients. It exposes your play games as streamable apps, so you can play them remotely from a phone or tablet.

Sunshine starts automatically with your GNOME graphical session. After a fresh deploy or first boot, a one-time setup is required.

First-Time Setup

  1. Log out and back in after the initial deploy. This activates the input group membership needed for virtual gamepad/mouse/keyboard input forwarding.

  2. Open the Sunshine web UI in a browser:

    https://localhost:47990
    

    Accept the self-signed certificate warning.

  3. Create a username and password when prompted (first launch only).

  4. Pair Moonlight on your phone:

    • Open Moonlight and select your machine (advertised via Avahi as atorgesen-panasonic or atorgesen-inspiron)
    • When prompted for a PIN, go to the Sunshine web UI → PIN tab and enter it there
    • The pairing completes automatically
  5. Launch a game from Moonlight. The five games from play appear as individual apps.

  6. Set up inputs in Dolphin (by clicking to the controller setup menu). The “Device” to select for “GameCube Controller at Port 1” is SDL/0/Xbox One S Controller. You will need to relaunch the game for this to take effect.

  7. Preferred Moonlight Settings: Low latency is the most important thing:

    1. Resolution: 360p
    2. Frame Rate: 30 FPS
    3. Bitrate: 1.0 Mbps
    4. Touch Mode: Touchpad
    5. On-Screen Controls: Auto
    6. Optimize Game Settings: Yes
    7. Multi-Controller Mode: Single
    8. Swap A/B and X/Y Buttons: No
    9. Play Audio on PC: No
    10. Preferred Codec: Auto
    11. HDR (Beta): No
    12. Frame Pacing Preference: Lowest Latency
    13. Citrix X1 Mouse Support: No
    14. Statistics Overlay: No

How Memory Card Saving Works

The play script handles saves transparently — Sunshine only streams the display, audio, and input. The full save cycle runs on the host as normal:

  • Before launch: syncs ROM and memory card from cloud (rcrsync sync games)
  • After you quit the emulator: copies the memory card back and syncs to cloud

Quitting cleanly (not force-killing) is all that’s needed to preserve saves.

Troubleshooting

Sunshine not running:

systemctl --user status sunshine
systemctl --user start sunshine
journalctl --user -u sunshine -n 50

Moonlight can’t find the machine: Verify Avahi is advertising:

journalctl --user -u sunshine | grep -i avahi

Both devices must be on the same local network.

Controller input not working: The input group membership requires a fresh login session — log out and back in.

Display capture errors (CAP_SYS_ADMIN): The NixOS config sets capSysAdmin = true which installs a security wrapper on the Sunshine binary. If errors recur after a rebuild, verify:

getcap $(which sunshine)
# Should show: cap_sys_admin=p

ComfyUI Image Generation (personal-dell)

personal-dell runs ComfyUI, a node-based Stable Diffusion (SDXL) web UI for creating and modifying digital paintings on the GPU. Enabled via services.comfyui.enable = true and accessible at:

  • Web UI: https://atorgesen-dell.local/comfyui/ (also linked from the services landing page as “ComfyUI”)

The service runs in --lowvram mode because the RTX 500 Ada has only ~3.7 GB of usable VRAM. SDXL-Turbo / SDXL-Lightning checkpoints are recommended for usable speed; full SDXL base works but is slow.

Adding Models (manual step — required before first use)

Model checkpoints are not packaged in Nix (they are large and user-managed). They live under the service’s data directory and must be downloaded manually into the checkpoints subfolder:

mkdir -p /data/andrew/comfyui/models/checkpoints
cd /data/andrew/comfyui/models/checkpoints

# Example: SDXL-Turbo (~6.9 GB), a good fit for low-VRAM
curl -fL -C - -o sd_xl_turbo_1.0_fp16.safetensors \
  "https://huggingface.co/stabilityai/sdxl-turbo/resolve/main/sd_xl_turbo_1.0_fp16.safetensors"

ComfyUI rescans the checkpoints folder automatically (no service restart needed) — the new model appears in the Load Checkpoint node after a page refresh. Other model types go in sibling folders (models/loras/, models/vae/, models/controlnet/, etc.).

Creating and Modifying Paintings

  • Create: in the web UI, build (or load) a text→image graph — Load CheckpointCLIP Text Encode (positive/negative) → KSamplerVAE DecodeSave Image. For SDXL-Turbo use cfg ≈ 1.0, steps 1–4, sampler euler_ancestral. Generated images are written to /data/andrew/comfyui/output/.
  • Modify: drop an existing painting into /data/andrew/comfyui/input/, then run an img2img or inpaint graph (Load ImageVAE EncodeKSampler with reduced denoise).

Architecture

  • Service: comfyui.service (systemd, user andrew:dev), internal port 8188 (centrally managed in service-ports.nix)
  • Nginx proxy: /comfyui/http://127.0.0.1:8188 (ComfyUI uses relative asset paths, so the subpath works without a dedicated vhost). The location forwards the raw request path (from $request_uri, prefix stripped) plus the query string via custom variables, rather than a normal prefix-stripping proxy_pass. This preserves the %2F-encoded slashes in ComfyUI’s /userdata/{file} API (e.g. saving workflows/foo.json) — a prefix-stripping proxy decodes them and breaks saves with HTTP 405. The path and query are split ($path?$query) rather than passing $request_uri whole, so the ? is not percent-encoded into the filename, and $args is avoided so the config passes the gixy SSRF lint.
  • Data directory: /data/andrew/comfyui/models/, input/, output/, custom_nodes/, user/ (created automatically via systemd.tmpfiles)
  • Asset database: /data/andrew/comfyui/user/comfyui.db (SQLite) — set explicitly with --database-url because ComfyUI’s default DB path is relative to the read-only Nix store
  • Version: pinned to ComfyUI v0.11.0, the newest release predating the comfy-aimdo dependency (a compiled native wheel that does not package cleanly under Nix on Python 3.13). The three frontend asset packages (comfyui-frontend-package, comfyui-workflow-templates, comfyui-embedded-docs) and spandrel are packaged in anixpkgs.

Troubleshooting

Service not running / crash-looping:

systemctl status comfyui
journalctl -u comfyui -n 50

Model not showing up: Confirm the file is in /data/andrew/comfyui/models/checkpoints/ and refresh the browser. A comfy_kitchen import warning in the logs is expected and harmless (fp8/fp4 quantization is disabled; not needed for SDXL).

Out-of-memory during generation: Lower the resolution (SDXL-Turbo was trained at 512×512) or batch size; the service already runs --lowvram.

Miscellaneous

Cloud Syncing

The following mount points are recommended (using rclone to set up):

  • dropbox:secrets -> rclone copy -> ~/secrets
  • dropbox:configs-> rclone copy -> ~/configs
  • dropbox:Games -> rclone copy -> ~/games
  • box:data -> rclone copy -> ~/data
  • box:.devrc -> rclone copy -> ~/.devrc
  • drive:Documents -> rclone copy -> ~/Documents

Music with Tidal

If you haven’t already, run:

install-superdirt

Open VSCode, run sclang in the terminal (close with 0.exit), and open up a .tidal file and get to work.

Useful commands:

  • Install samples with e.g., tidal-download-samples eddyflux/crate crate
  • When sclang is running, associate with the correct audio device with sc-route-audio