Polish ringtail NixOS config and add documentation #208

Merged
eblume merged 30 commits from feature/ringtail-nixos into main 2026-02-18 17:53:48 -08:00
11 changed files with 413 additions and 8 deletions

View file

@ -1,6 +1,8 @@
import dagger
from dagger import dag, function, object_type
NIX_IMAGE = "nixos/nix:2.33.3"
@object_type
class BlumeopsCi:
@ -67,3 +69,26 @@ class BlumeopsCi:
)
.file(f"/docs-{version}.tar.gz")
)
@function
async def flake_lock(
self, src: dagger.Directory, flake_path: str = "nixos/ringtail"
) -> dagger.File:
"""Resolve flake inputs and return updated flake.lock."""
return await (
dag.container()
.from_(NIX_IMAGE)
.with_directory("/workspace", src)
.with_workdir(f"/workspace/{flake_path}")
.with_exec(
[
"nix",
"--extra-experimental-features",
"nix-command flakes",
"flake",
"lock",
"--accept-flake-config",
]
)
.file(f"/workspace/{flake_path}/flake.lock")
)

View file

@ -8,7 +8,7 @@
ansible.builtin.git:
repo: "https://forge.ops.eblu.me/eblume/blumeops.git"
dest: /etc/blumeops
version: main
version: "{{ ringtail_commit | default('main') }}"
force: true
register: _repo
@ -16,7 +16,7 @@
ansible.builtin.command:
cmd: nixos-rebuild switch --flake /etc/blumeops/nixos/ringtail#ringtail
register: _rebuild
changed_when: "'activating the configuration' in _rebuild.stdout"
changed_when: "'activating the configuration' in _rebuild.stderr"
when: _repo.changed
- name: Verify tailscale is connected

View file

@ -0,0 +1 @@
Ringtail post-install: NixOS config (sway with Catppuccin Macchiato theme, fish, 1Password, Steam, LibreWolf, Bluetooth audio, chezmoi, dev tools, nix-ld), Dagger flake-lock pipeline, improved provision-ringtail workflow, services-check integration, and reference documentation.

View file

@ -1,6 +1,6 @@
---
title: Hosts
modified: 2026-02-10
modified: 2026-02-18
tags:
- infrastructure
---
@ -16,6 +16,7 @@ All devices connected via [Tailscale](https://login.tailscale.com/) tailnet `tai
| **Indri** | Mac Mini M1, 2020 - Primary server | [[indri|Details]] |
| **Gilbert** | MacBook Air M4, 2025 - Workstation | [[gilbert|Details]] |
| **[[sifaka|Sifaka]]** | Synology NAS - Storage & backups | [[sifaka|Details]] |
| **[[ringtail|Ringtail]]** | Custom PC, NixOS - Service host & gaming | [[ringtail|Details]] |
| **Mouse** | MacBook Air M2 - Allison's laptop | - |
| **UniFi** | UniFi Express 7 - Home WiFi | [[unifi|Details]] |
| **Dwarf** | iPad Air - Employer-provided, off tailnet | - |

View file

@ -0,0 +1,61 @@
---
title: Ringtail
modified: 2026-02-18
tags:
- infrastructure
- host
---
# Ringtail
Service host and gaming PC. Custom-built PC running NixOS.
## Specifications
| Property | Value |
|----------|-------|
| **Motherboard** | ASUS ROG Crosshair VI Hero (Wi-Fi AC) |
| **CPU** | AMD Ryzen 7 1700X (8-core/16-thread, 3.4 GHz) |
| **RAM** | 32 GB DDR4 (4x8 GB Corsair Vengeance CMK16GX4M2B3200C16, 3200 MT/s DOCP) |
| **GPU** | NVIDIA GeForce RTX 4080 (AD103, 16 GB VRAM) |
| **Monitor** | HP OMEN 27i IPS (2560x1440, 165 Hz, DisplayPort) |
| **Storage (boot)** | Samsung 970 PRO 1TB NVMe |
| **Storage (SATA)** | Samsung 850 EVO 1TB (`/mnt/games`), 850 EVO 500GB (`/mnt/storage1`), 840 PRO 120GB (`/mnt/storage2`) |
| **Peripherals** | Das Keyboard 4, Logitech MX Master 3, 8BitDo Ultimate 2 controller |
| **OS** | NixOS 25.11 (Sway/Wayland) |
| **Tailscale hostname** | `ringtail.tail8d86e.ts.net` |
## Software
Managed declaratively via `nixos/ringtail/configuration.nix`. Home-manager handles ringtail-specific sway/waybar config; chezmoi manages cross-platform dotfiles.
- **Desktop:** Sway (Wayland, Catppuccin Macchiato theme) with waybar and wezterm
- **Browser:** LibreWolf
- **Gaming:** Steam (library on `/mnt/games`), 8BitDo controller via Steam Input
- **Audio:** Edifier R1280DBs (Bluetooth), PipeWire
- **Secrets:** 1Password CLI + GUI (NixOS modules for polkit/setgid integration)
- **Runtimes:** mise manages Node, Python, Rust, .NET; nix-ld enables dynamically linked binaries
- **Dotfiles:** `chezmoi init eblume && chezmoi apply`
## Deployment
```fish
mise run provision-ringtail
```
This updates `flake.lock` via Dagger, verifies the current commit is pushed to forge, then deploys the exact commit via ansible. If the lockfile changed, it stages the file and exits so you can commit and re-run.
## Maintenance Notes
**1Password:** Desktop app must be running for `op` CLI. Use `$mod+Shift+minus` to send to scratchpad.
**NVIDIA:** Proprietary drivers. Sway launched with `--unsupported-gpu` via greetd.
**No TPM:** `systemd.tpm2.enable = false` prevents 90s boot delay.
**RAM speed:** Running at 3200 MT/s via DOCP 1 (BIOS 8902+).
## Related
- [[hosts]] - Device inventory
- [[tailscale]] - Network configuration

View file

@ -47,6 +47,7 @@ Host inventory and network configuration.
- [[hosts|Hosts]] - Device inventory
- [[indri]] - Primary server
- [[ringtail]] - Service host & gaming PC
- [[gilbert]] - Development workstation
- [[tailscale]] - ACLs, groups, tags
- [[gandi]] - DNS hosting for `eblu.me`

View file

@ -5,5 +5,27 @@ set -euo pipefail
export MISE_TASK_OUTPUT=interleave
# Update flake.lock via Dagger before deploying
echo "Updating nixos/ringtail/flake.lock..."
dagger call flake-lock --src=. --flake-path=nixos/ringtail \
export --path=nixos/ringtail/flake.lock
if ! git diff --quiet nixos/ringtail/flake.lock; then
git add nixos/ringtail/flake.lock
echo "flake.lock changed and staged. Commit, push, and re-run."
exit 1
fi
COMMIT=$(git rev-parse HEAD)
REMOTE_REF=$(git ls-remote origin "$(git rev-parse --abbrev-ref HEAD)" 2>/dev/null | awk '{print $1}')
if [[ "$REMOTE_REF" != "$COMMIT" ]]; then
echo "ERROR: Current commit $COMMIT is not pushed to forge."
echo "Push your changes first: git push"
exit 1
fi
echo "Deploying commit $COMMIT to ringtail..."
cd ansible
ansible-playbook playbooks/ringtail.yml "$@"
ansible-playbook playbooks/ringtail.yml -e "ringtail_commit=$COMMIT" "$@"

View file

@ -83,6 +83,11 @@ check_http "CV" "https://cv.ops.eblu.me/"
check_http "Ntfy" "https://ntfy.ops.eblu.me/v1/health"
check_http "Frigate" "https://nvr.ops.eblu.me/api/version"
echo ""
echo "Ringtail (NixOS):"
check_service "ssh" "ssh -o ConnectTimeout=5 ringtail true"
check_service "tailscale" "ssh ringtail 'tailscale status --self --json' | jq -e '.Self.Online' > /dev/null"
echo ""
echo "Public services (via Fly.io):"
check_http "Docs (public)" "https://docs.eblu.me/"

View file

@ -1,5 +1,9 @@
{ config, pkgs, ... }:
{ config, pkgs, lib, ... }:
let
# Libraries needed by mise-compiled runtimes (python-build, etc.)
buildDeps = with pkgs; [ zlib readline bzip2 xz libffi ncurses sqlite openssl ];
in
{
# Allow unfree packages (NVIDIA drivers, Steam)
nixpkgs.config.allowUnfree = true;
@ -8,6 +12,9 @@
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# No TPM module on this board
systemd.tpm2.enable = false;
# Networking
networking.hostName = "ringtail";
networking.networkmanager.enable = true;
@ -66,6 +73,23 @@
pulse.enable = true;
};
# Bluetooth
hardware.bluetooth = {
enable = true;
powerOnBoot = true;
};
services.blueman.enable = true;
# Fish shell
programs.fish.enable = true;
# 1Password (modules handle CLI group/setgid and polkit for GUI integration)
programs._1password.enable = true;
programs._1password-gui = {
enable = true;
polkitPolicyOwners = [ "eblume" ];
};
# Steam
programs.steam = {
enable = true;
@ -90,7 +114,7 @@
# User account
users.users.eblume = {
isNormalUser = true;
initialPassword = "changeme";
shell = pkgs.fish;
extraGroups = [ "wheel" "networkmanager" "video" ];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILmh1SSCdDAyu3vkSQH7kAXEPDi8APyjo9JXDTjtha2j"
@ -105,6 +129,244 @@
htop
curl
wget
chezmoi
neovim
eza
fd
fzf
zoxide
starship
atuin
bat
ripgrep
mise
gcc
gnumake
pkg-config
openssl
gnupg
unzip
fuzzel
pulseaudio
librewolf
];
# Allow running dynamically linked binaries (mise-installed runtimes, etc.)
programs.nix-ld.enable = true;
programs.nix-ld.libraries = buildDeps ++ [ pkgs.icu ];
# Compile-time flags for mise python-build and similar source builds
environment.sessionVariables = {
PKG_CONFIG_PATH = lib.makeSearchPath "lib/pkgconfig" (map lib.getDev buildDeps);
CFLAGS = lib.concatMapStringsSep " " (p: "-I${lib.getDev p}/include") buildDeps;
LDFLAGS = lib.concatMapStringsSep " " (p: "-L${lib.getLib p}/lib") buildDeps;
};
# Fonts
fonts.packages = with pkgs; [
nerd-fonts.victor-mono
];
# Home Manager (minimal — chezmoi owns dotfiles, this is ringtail-specific)
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.eblume = {
home.stateVersion = "25.11";
wayland.windowManager.sway = {
enable = true;
checkConfig = false;
config = {
terminal = "wezterm";
modifier = "Mod4";
fonts = {
names = [ "VictorMono Nerd Font" ];
size = 10.0;
};
bars = [{ command = "waybar"; }];
gaps = {
inner = 8;
outer = 4;
};
window = {
border = 2;
titlebar = false;
};
colors = {
focused = {
border = "#8aadf4";
background = "#24273a";
text = "#cad3f5";
indicator = "#c6a0f6";
childBorder = "#8aadf4";
};
focusedInactive = {
border = "#494d64";
background = "#1e2030";
text = "#a5adcb";
indicator = "#494d64";
childBorder = "#494d64";
};
unfocused = {
border = "#363a4f";
background = "#1e2030";
text = "#6e738d";
indicator = "#363a4f";
childBorder = "#363a4f";
};
urgent = {
border = "#ed8796";
background = "#24273a";
text = "#cad3f5";
indicator = "#ed8796";
childBorder = "#ed8796";
};
};
input = {
"*" = {
xkb_options = "ctrl:nocaps";
};
};
output = {
"DP-1" = {
mode = "2560x1440@165Hz";
adaptive_sync = "on";
bg = "~/.config/sway/wallpaper.jpg fill";
};
};
keybindings = let mod = "Mod4"; in {
"${mod}+Return" = "exec wezterm";
"${mod}+Shift+q" = "kill";
"${mod}+d" = "exec wmenu-run";
"${mod}+space" = "exec fuzzel";
"${mod}+Shift+c" = "reload";
"--locked XF86AudioMute" = "exec pactl set-sink-mute @DEFAULT_SINK@ toggle";
"--locked XF86AudioLowerVolume" = "exec pactl set-sink-volume @DEFAULT_SINK@ -5%";
"--locked XF86AudioRaiseVolume" = "exec pactl set-sink-volume @DEFAULT_SINK@ +5%";
"--locked XF86AudioMicMute" = "exec pactl set-source-mute @DEFAULT_SOURCE@ toggle";
};
startup = [
{ command = "1password"; }
{ command = "steam"; }
];
};
};
programs.fuzzel = {
enable = true;
settings = {
main = {
font = "VictorMono Nerd Font:size=14";
terminal = "wezterm";
width = 40;
horizontal-pad = 16;
vertical-pad = 8;
border-radius = 8;
border-width = 2;
};
colors = {
background = "24273add";
text = "cad3f5ff";
match = "8aadf4ff";
selection = "363a4fff";
selection-text = "cad3f5ff";
selection-match = "8aadf4ff";
border = "8aadf4ff";
};
};
};
programs.waybar = {
enable = true;
settings = [{
layer = "top";
position = "top";
height = 30;
modules-left = [ "sway/workspaces" "sway/mode" ];
modules-center = [ "sway/window" ];
modules-right = [ "pulseaudio" "bluetooth" "network" "clock" "tray" ];
tray = { spacing = 8; };
clock = { format = "{:%a %b %d %H:%M}"; };
network = {
interval = 2;
format-ethernet = "{bandwidthDownBits} down {bandwidthUpBits} up";
format-wifi = "{essid} {bandwidthDownBits} down {bandwidthUpBits} up";
format-disconnected = "disconnected";
};
pulseaudio = {
format = "{icon} {volume}%";
format-muted = " muted";
format-icons = {
headphone = "";
default = [ "" "" "" ];
};
};
}];
style = ''
* {
font-family: "VictorMono Nerd Font";
font-size: 13px;
border: none;
border-radius: 0;
min-height: 0;
}
window#waybar {
background-color: rgba(30, 32, 48, 0.9);
color: #cad3f5;
margin: 4px 4px 0 4px;
}
#workspaces button {
padding: 0 8px;
margin: 0 2px;
color: #6e738d;
background: transparent;
border-radius: 4px;
}
#workspaces button.focused {
color: #8aadf4;
background: #363a4f;
border-bottom: 2px solid #8aadf4;
}
#workspaces button.urgent {
color: #ed8796;
}
#window {
color: #a5adcb;
}
#bluetooth {
color: #8aadf4;
}
#bluetooth.off, #bluetooth.disabled {
color: #6e738d;
}
#clock, #network, #pulseaudio, #bluetooth, #tray {
padding: 0 12px;
margin: 4px 2px;
color: #cad3f5;
background: #363a4f;
border-radius: 4px;
}
#clock {
color: #8aadf4;
}
#pulseaudio {
color: #f5a97f;
}
#network {
color: #a6da95;
}
#network.disconnected {
color: #ed8796;
}
'';
};
};
# Ensure mounted drives are owned by eblume
systemd.tmpfiles.rules = [
"d /mnt/games 0755 eblume users -"
"d /mnt/storage1 0755 eblume users -"
"d /mnt/storage2 0755 eblume users -"
];
# Enable nix flakes

View file

@ -20,6 +20,27 @@
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1770260404,
"narHash": "sha256-3iVX1+7YUIt23hBx1WZsUllhbmP2EnXrV8tCRbLxHc8=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "0d782ee42c86b196acff08acfbf41bb7d13eed5b",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "release-25.11",
"repo": "home-manager",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1771208521,
@ -39,6 +60,7 @@
"root": {
"inputs": {
"disko": "disko",
"home-manager": "home-manager",
"nixpkgs": "nixpkgs"
}
}

View file

@ -1,5 +1,5 @@
{
description = "NixOS configuration for ringtail (gaming/compute workstation)";
description = "NixOS configuration for ringtail (service host & gaming PC)";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
@ -7,13 +7,18 @@
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
home-manager = {
url = "github:nix-community/home-manager/release-25.11";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, disko, ... }: {
outputs = { nixpkgs, disko, home-manager, ... }: {
nixosConfigurations.ringtail = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
disko.nixosModules.disko
home-manager.nixosModules.home-manager
./disk-config.nix
./hardware-configuration.nix
./configuration.nix