Buildout plan · 27 Sep 2026

7900X Rig Buildout

A new always-on machine to carry local AI and Jellyfin — retiring the P2000 transcoding problem, the Docker Desktop session trap, and the last workload stranded on the Beelink.

CPU
Ryzen 9 7900X
GPU
RTX 4070 Ti · 12 GB
OS
Ubuntu Server 24.04 LTS
Network
192.168.4.220 static
Uptime
24/7

Settle these first

Three answers change what gets built. Everything else is decided.

Static IP — settled

192.168.4.220. Verified free by ping and by UniFi client records, and it sits in the server band beside the NAS at .218. Protect it by shrinking the VLAN 4 DHCP pool end from 192.168.4.254 to 192.168.4.200, which turns .201–.254 into static-only space. Safe to shrink: the only occupants are .215 and .218 (fixed reservations, honored outside the pool) and .230. Once the rig boots, add a UniFi fixed reservation for its MAC as a third lock.

Agent name

The dead-drop rule is one outbox per agent. The new box gets its own for-<name>.md and must never write for-nuc.md or for-beelink.md — two agents editing one file over SMB clobber each other silently.

Jellyfin 10.11.11 or 12.0

A migration is the natural moment to jump. 12.0 fixes unauthorized file access and XSS, and your server is internet-facing. It also rewrites the database on first boot, so the snapshot is the only way back. Both pre-flight hazards are already verified clear: no third-party plugins, and no usernames differing only by capitalization across your 26 users.

USB kit

Two downloads on your Windows machine. Nothing else goes on the stick.

ItemVersionWhere
Ubuntu Server ISO
Not Desktop. Not the "live" desktop image.
24.04 LTSubuntu.com/download/server
Rufus
Writes the USB. Balena Etcher works too.
latestrufus.ie

24.04 LTS is the recommendation over the newer 26.04 — it has two more years of NVIDIA and CUDA packaging shaken out, and every guide you'll hit for Ollama, Jellyfin and the Container Toolkit assumes it. Support runs to 2029 either way.

In Rufus: select the ISO, leave GPT / UEFI, write in ISO mode if prompted. Verify the download's SHA256 against the SHA256SUMS file on the Ubuntu releases page first — a corrupt ISO fails deep into the install, not at the start.

Before you boot it

In BIOS: enable EXPO/DOCP for your RAM, and disable Secure Boot. Secure Boot forces the NVIDIA driver through MOK enrollment — a blue key-management screen at first reboot that's easy to misread and get wrong. Turning it off avoids the whole detour.

00

NAS prep

Claude · now

Runs from the NUC before the machine exists. Additive and reversible.

Do this before anything gets write access

Your media has no snapshot protection at all — no periodic tasks, and the only snapshots on Kloud/Media are two taken by hand this month. Kloud/Immich, holding 257 GB of irreplaceable photos, has none either. A delete from the Jellyfin UI would be permanent.

DatasetFrequencyRetainCovers
Kloud/Mediadaily14 daysJellyfin UI deletes, *arr accidents
Kloud/Immichdaily30 daysPhotos — loss noticed late
Kloud/AppDatadaily14 daysApp configs and databases

Snapshots store only changed blocks, so on a library that barely changes this costs single-digit GB against 10.9 TiB.

Then the NFS export, scoped deliberately narrow:

path      /mnt/Kloud/Media/Jellyfin   # NOT all of /mnt/Kloud/Media
networks  192.168.4.0/24
access    read-write, mapall off

Exporting only the Jellyfin library means the new box can write what it legitimately manages and cannot reach usenet, AppData, or your personal backup folder at all.

01

Install Ubuntu

You · at the machine

Text-menu installer — arrow keys, space to toggle, Enter. No commands.

ScreenChoose
Installer updateContinue without updating
Install typeUbuntu Server — not "minimized"
NetworkEdit IPv4 → Manual. Subnet 192.168.4.0/24, 192.168.4.220, gateway 192.168.4.1, nameservers 192.168.4.1
ProxyLeave blank
MirrorDefault
StorageUse entire disk, LVM on — then see the trap below
ProfileYour name, a hostname, username, password
Ubuntu ProSkip
SSHInstall OpenSSH server — tick this. Everything afterwards depends on it
Featured snapsSelect none
The LVM trap

Ubuntu's guided LVM setup allocates only part of the disk to the root volume and leaves the rest unused in the volume group. You'll install on a 2 TB drive and find 100 GB. On the partition summary screen, select ubuntu-lv → Edit → set size to max. If you miss it, fix it after boot:

sudo lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv
sudo resize2fs /dev/ubuntu-vg/ubuntu-lv
df -h /
Don't

No desktop environment, and don't install VS Code on it. A display stack drags in hundreds of packages and can tangle with the NVIDIA driver, which here has exactly one job: CUDA and NVENC.

02

Driver, Docker, GPU

You · copy-paste

Two hard gates. Nothing proceeds past a failed one.

sudo apt update && sudo apt upgrade -y
sudo ubuntu-drivers install
sudo reboot
# after reboot
nvidia-smi
Gate 1

nvidia-smi must list the RTX 4070 Ti. If it doesn't, stop — every later step depends on it, and debugging here is far easier than after four more layers are stacked on top.

curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER      # log out and back in after this

# NVIDIA Container Toolkit — lets containers see the GPU
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
  | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
  | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
  | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt update && sudo apt install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

# Gate 2 — a container must see the card
docker run --rm --gpus all ubuntu nvidia-smi
Gate 2

The same listing, but from inside a container. This is the one that matters — Jellyfin, Ollama and Immich ML all reach the card this way.

Then Portainer, so container management is a browser tab rather than a terminal:

docker volume create portainer_data
docker run -d -p 9443:9443 --name portainer --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data portainer/portainer-ce:latest

Reach it at https://192.168.4.220:9443 and set the admin password within a few minutes of first start, or it locks itself and needs a restart.

03

Claude Code, natively

You · copy-paste

The last thing you do by hand. After this the agent takes over.

# Node 20 LTS — Ubuntu's apt node is too old
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs git
node -v

# Claude Code
npm install -g @anthropic-ai/claude-code
claude --version

# CIFS client, so the agent can reach the cross-agent dead-drop
sudo apt install -y cifs-utils

Run claude once in a terminal on the box to complete login, then add the machine as an SSH host in the Claude desktop app — that gives you the UI you prefer while the session runs on the server. No GUI is ever installed on the box itself.

Why native rather than SSH from the NUC

An agent installed here gets real file tools — Read, Edit, Grep — against this machine's actual filesystem, plus its own CLAUDE.md and memory. Me shelling in from the NUC would give a command pipe and nothing else; every file operation would degrade into cat and sed.

04

Hand off to the new agent

Paste this

First message to the fresh Claude Code session on the box. It carries the context that isn't discoverable from the machine itself.

Handoff brief — paste verbatim, fill the two blanks
You are the Claude Code agent on a new Ubuntu Server box in Kirov's home estate.
Read this fully before acting.

MACHINE
  Ryzen 9 7900X + RTX 4070 Ti (12 GB), Ubuntu Server 24.04 LTS, always on.
  IP 192.168.4.220 . Agent name ____________ .
  Role: local AI (Ollama) + Jellyfin. Later: Palworld, Immich ML.
  NO Blender on this box — that lives on the gaming rig.

ALREADY DONE — do not redo
  NVIDIA driver installed, `nvidia-smi` passes on host AND inside a container.
  Docker Engine + NVIDIA Container Toolkit installed. Portainer on :9443.

THE ESTATE
  TrueNAS R730xd  192.168.4.218  storage, Immich, *arr stack. Pool "Kloud".
  NUC             192.168.4.58   websites, Nginx Proxy Manager, DDNS. Agent "nuc".
  Beelink         192.168.4.59   empty, being repurposed for monitoring.
  Gaming rig      192.168.1.114  never a server; powers on and off.

CROSS-AGENT DEAD-DROP
  \\192.168.4.218\Media\AppData\claude-cross  (mount via cifs)
  ONE outbox per agent. Create your own for-<name>.md. NEVER write
  for-nuc.md or for-beelink.md — concurrent SMB writes clobber silently.
  No secrets in that folder; it is broadly readable on the LAN.

YOUR TASKS, IN ORDER
  1. Mount the NAS media over NFS, read-write:
       192.168.4.218:/mnt/Kloud/Media/Jellyfin -> /mnt/jellyfin
     Add to /etc/fstab with _netdev. Verify you can write a test file
     and then delete it.
  2. Deploy Jellyfin (linuxserver image) with runtime nvidia and
     PUID=3000 PGID=3000. NFS maps identity by NUMERIC uid/gid — the
     NAS files are owned by 3000. Any other uid gives permission errors
     that look like a broken export.
  3. Migrate the Jellyfin config: copy the WHOLE directory from
     192.168.4.218:/mnt/Kloud/Media/jellyfin-migration/config
     Do not rebuild from scratch — that folder carries watch history and
     26 user accounts. Then set transcoding to NVENC in the dashboard.
  4. Deploy Ollama with --gpus all, plus Open WebUI.
  5. Report back before any cutover. Do NOT repoint DNS or the proxy
     yourself — the NUC agent owns Nginx Proxy Manager.

RULES
  - Ask before anything destructive. Snapshots exist on the NAS but
    restoring is manual.
  - There is a folder on the Media share named "Peronal Backup  Claude
    dont go here". Never read, list, or touch it. Exclude it from every
    scan.
  - Report outcomes honestly, including partial failures.
05

Services

Agent · on the box

Media mount

sudo apt install -y nfs-common
sudo mkdir -p /mnt/jellyfin
# /etc/fstab
192.168.4.218:/mnt/Kloud/Media/Jellyfin  /mnt/jellyfin  nfs  rw,_netdev,noatime  0 0
sudo mount -a && touch /mnt/jellyfin/.wtest && rm /mnt/jellyfin/.wtest
The trap

NFS maps identity by numeric UID/GID, not username. Your files are owned by uid 3000, so every container touching them runs PUID=3000 / PGID=3000.

Jellyfin

services:
  jellyfin:
    image: lscr.io/linuxserver/jellyfin:10.11.11
    container_name: jellyfin
    runtime: nvidia
    environment:
      - PUID=3000
      - PGID=3000
      - TZ=America/Phoenix
      - NVIDIA_VISIBLE_DEVICES=all
    volumes:
      - /opt/jellyfin/config:/config
      - /mnt/jellyfin/movies:/data/movies
      - /mnt/jellyfin/tv:/data/tv
      - /mnt/jellyfin/music:/data/music
    ports: ["8096:8096"]
    restart: unless-stopped
What this ends

Ada NVENC needs no sysext, no legacy driver, and no reapplying after a TrueNAS update. It also takes Jellyfin off the TrueNAS app platform before 26.04 lands — the release that removes the REST API this estate is administered through.

Ollama + Open WebUI

docker run -d --gpus all -p 11434:11434 -v ollama:/root/.ollama \
  --name ollama --restart always ollama/ollama
docker exec -it ollama ollama pull llama3.1:8b

docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway \
  -v open-webui:/app/backend/data --name open-webui --restart always \
  ghcr.io/open-webui/open-webui:main

12 GB holds an 8B model at Q4 (~6 GB), or 14B at Q4 if you're careful. With no Blender here the model can stay resident — NVENC runs on a separate encoder block and costs a few hundred MB, so inference and transcoding coexist without contention.

Immich ML

Immich itself stays on the NAS with its database and storage. Only the ML service moves — run the immich-machine-learning image with the CUDA tag here, then point the NAS deployment's IMMICH_MACHINE_LEARNING_URL at http://192.168.4.220:3003. Face recognition and CLIP search currently run on 2014 Xeons against a 257 GB library; this is the largest visible improvement in the plan.

Palworld

Image ich777/steamcmd:palworld with USERNAME=anonymous — a personal Steam account fails, Palworld downloads anonymously. Keep the perf flags -useperfthreads -NoAsyncLoadingThread -UseMultithreadForDS, restore the world from the NAS backup, and rebuild the 6-hourly backup as a systemd timer rather than the interactive-login scheduled task the Beelink needed.

06

Cutover

You + NUC agent

Each line is one field, and each reverses on its own.

WhatFromTo
NPM proxy host 8 · jellyfin192.168.4.218:8096192.168.4.220:8096
UniFi forward · WAN UDP 38211192.168.4.59192.168.4.220
Immich ML endpointlocal (CPU)http://192.168.4.220:3003
TrueNAS Jellyfin appRUNNINGstop — keep, don't delete

Websockets and forced SSL are already set on proxy host 8, so SyncPlay and HTTPS carry over untouched — only the forward target changes.

Rollback

Revert proxy host 8 to 192.168.4.218:8096 and start the TrueNAS app again. That's the whole undo, which is why the old app gets stopped rather than deleted. For Jellyfin's config, restore from /mnt/Kloud/Media/.zfs/snapshot/<snap>/jellyfin-migration/config — copy that one folder, never roll back the whole dataset.

07

Afterwards

Estate

Estate reference

HostAddressRoleAgent
New rig192.168.4.220AI + Jellyfin + Palworld, always onplanned
TrueNAS R730xd192.168.4.218Storage, Immich, *arr stacknone
NUC192.168.4.58Websites, NPM, DDNSnuc
Beelink192.168.4.59Empty → monitoringbeelink
Gaming rig192.168.1.114Games + Blender. Never a server.none
PortServiceExposure
8096Jellyfinvia NPM, HTTPS forced
11434Ollama APILAN only
3000Open WebUILAN only
9443PortainerLAN only
38211/udpPalworld gameWAN forward
30443Palworld RESTLAN only
3003Immich MLLAN only — NAS calls it

Phase 00 can run today. Address settled: 192.168.4.220. Outstanding: the agent name.