27 Commits

Author SHA1 Message Date
Claude 3f736a9ac3 niri: add full desktop stack (bar, launcher, notifications, configs)
Moves programs.niri into a dedicated niri.nix module and installs the
usual Wayland userland — alacritty, fuzzel, waybar, mako, swaybg,
grim/slurp/wl-clipboard, swaylock/swayidle/wlogout, xwayland-satellite,
plus nm-applet/pavucontrol/playerctl/brightnessctl.

Ships starter configs for niri (keybinds mirroring sway/KDE habits,
screenshots to ~/Pictures/Screenshots, media keys, workspace switching,
auto-lock via swayidle), waybar with a Catppuccin-ish style, mako, and
fuzzel — all wired up through home-manager's xdg.configFile.

https://claude.ai/code/session_01NrGdGEBRbDbWvHUb3Lho44
2026-04-23 14:57:14 +00:00
Claude 4a6258f8f9 desktop: enable niri compositor
Adds niri as an available Wayland session alongside Plasma6, selectable
from SDDM.

https://claude.ai/code/session_01NrGdGEBRbDbWvHUb3Lho44
2026-04-23 14:50:24 +00:00
chexit 5a5822b27e add openvpn 2026-04-23 17:46:22 +03:00
chexit 5afd18dc12 add thunderbird 2026-04-19 23:33:20 +03:00
chexit cb0bcc0cf8 add spotify 2026-04-19 19:25:32 +03:00
Vladimir be12ef4465 Merge pull request #8 from Chexit/claude/add-yami-dev-stack-zHOMn
feat(yami): add dataDir option for separate-disk state
2026-04-19 19:05:04 +03:00
chexit 6775a88ca8 feat(yami): add dataDir option for storing stack state on separate disk
- Bind-mounts /var/lib/{gitea,postgresql} under configurable dataDir
- gitea-runner intentionally excluded: its DynamicUser + StateDirectory
  conflicts with pre-existing mount points ("File exists")
- Runner token moved to /var/lib/gitea-runner-secret/ to avoid the same
  symlink/StateDirectory conflict inside /var/lib/gitea-runner
- Enable the stack on this host with dataDir = /mnt/ssd_110/yami
2026-04-19 18:54:28 +03:00
Vladimir 61b1cf5097 Merge pull request #4 from Chexit/claude/switch-to-fish-shell-LDSqe
Switch default shell from Zsh to Fish with Starship prompt
2026-04-19 18:29:56 +03:00
Vladimir da4ad4b025 Merge pull request #7 from Chexit/claude/add-yami-dev-stack-zHOMn
docs(yami): note that runner tokenFile must be env-file format (TOKEN…
2026-04-19 18:24:38 +03:00
chexit a83500e382 docs(yami): note that runner tokenFile must be env-file format (TOKEN=...) 2026-04-19 18:23:56 +03:00
Vladimir 7bf3369d53 Merge pull request #6 from Chexit/claude/add-yami-dev-stack-zHOMn
fix(yami): tmpfiles for runner token must use root (DynamicUser has n…
2026-04-19 18:18:11 +03:00
chexit ecf8b5974a fix(yami): tmpfiles for runner token must use root (DynamicUser has no static user) 2026-04-19 18:17:24 +03:00
Vladimir fac913504f Merge pull request #5 from Chexit/claude/add-yami-dev-stack-zHOMn
fix(yami): use services.gitea.lfs.enable instead of raw LFS_START_SERVER
2026-04-19 18:02:39 +03:00
chexit 6a2fb0970c fix(yami): use services.gitea.lfs.enable instead of raw LFS_START_SERVER
Raw LFS_START_SERVER made gitea try to generate and persist LFS_JWT_SECRET
into app.ini, which NixOS intentionally keeps read-only. The lfs.enable
helper lets the module own the secret and wire it in correctly.
2026-04-19 18:00:03 +03:00
Claude 11f8ed08e9 Add 1Password GUI and CLI via NixOS programs module
Enables programs._1password (CLI) and programs._1password-gui with
polkit authorization for user chexit. allowUnfree is already set,
so packages install without additional config.

https://claude.ai/code/session_01NS2hpapno51a1Ng7hWaQKy
2026-04-19 11:24:46 +00:00
Claude 393c2e72bd Switch default shell from zsh to fish with starship prompt
Replace programs.zsh (powerlevel10k theme) with programs.fish and
starship prompt integration. Fish provides autosuggestions and syntax
highlighting out of the box, eliminating the need for separate plugins.

https://claude.ai/code/session_01NS2hpapno51a1Ng7hWaQKy
2026-04-19 11:17:14 +00:00
Vladimir e0f403143e Merge pull request #3 from Chexit/claude/add-yami-dev-stack-zHOMn
feat: add Yami dev stack module (on-demand gitea+postgres+docker+runner)
2026-04-19 02:58:52 +03:00
Claude 27cb88d8a0 feat: add Yami dev stack module (on-demand gitea+postgres+docker+runner)
https://claude.ai/code/session_017dm8ypaaWNeA27ctmwq5dn
2026-04-18 23:00:41 +00:00
Claude 18480df83b Merge remote-tracking branch 'origin/feature/small-fixes' 2026-04-17 19:51:08 +00:00
Claude 1a1df93285 Merge branch 'feature/home-manager-flakes-gaming' 2026-04-17 19:46:56 +00:00
chexit a1f5215275 typo 2026-04-17 22:37:38 +03:00
chexit e38c6d0f1c add alias and flake.lock 2026-04-17 22:37:03 +03:00
chexit d02310b8fb add throne to packages and typos 2026-04-17 22:31:00 +03:00
Vladimir 2e4775d99f Merge pull request #1 from Chexit/feature/home-manager-flakes-gaming
feat: Home Manager (NixOS module), Flakes, gaming packages
2026-04-17 22:29:02 +03:00
Claude 23375d10b1 ci: free disk space before Nix build to prevent ENOSPC on NVIDIA/CUDA packages
GitHub Actions free runners have ~14 GB total; removing unused toolchains
(dotnet, android, CodeQL, boost) reclaims ~8-10 GB needed for the full
NixOS system build.

https://claude.ai/code/session_01JwjdjwvgMGW4TcpYi4MZ6n
2026-04-17 18:53:08 +00:00
chexit 7a1d4cddd0 fix 2026-04-17 21:35:05 +03:00
chexit 68d5d45d65 add github action for checking 2026-04-17 21:29:03 +03:00
13 changed files with 788 additions and 13 deletions
+46
View File
@@ -0,0 +1,46 @@
name: Check NixOS Config
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/share/swift /usr/local/lib/android \
/opt/ghc /opt/hostedtoolcache/CodeQL /usr/local/share/boost \
"$AGENT_TOOLSDIRECTORY"
sudo docker image prune --all --force || true
df -h
- name: Install Nix
uses: cachix/install-nix-action@v27
with:
nix_path: nixpkgs=channel:nixos-unstable
extra_nix_config: |
experimental-features = nix-command flakes
accept-flake-config = true
- name: Setup Cachix
uses: cachix/cachix-action@v15
with:
name: chexit
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Check flake syntax
run: nix flake check --no-build
- name: Build system config
run: |
nix build .#nixosConfigurations.chexit.config.system.build.toplevel \
--no-link \
--print-build-logs
+17 -2
View File
@@ -5,8 +5,10 @@
./hardware-configuration.nix ./hardware-configuration.nix
./packages.nix ./packages.nix
./desktop.nix ./desktop.nix
./niri.nix
./nvidia.nix ./nvidia.nix
./shell.nix ./shell.nix
./yami-dev-stack.nix
]; ];
nix = { nix = {
@@ -76,10 +78,16 @@
users.users.chexit = { users.users.chexit = {
isNormalUser = true; isNormalUser = true;
description = "chexit"; description = "chexit";
shell = pkgs.zsh; shell = pkgs.fish;
extraGroups = [ "networkmanager" "wheel" "docker" ]; extraGroups = [ "networkmanager" "wheel" "docker" ];
}; };
programs._1password.enable = true;
programs._1password-gui = {
enable = true;
polkitPolicyOwners = [ "chexit" ];
};
programs.throne = { programs.throne = {
enable = true; enable = true;
tunMode.enable = true; tunMode.enable = true;
@@ -94,7 +102,14 @@
programs.gamemode.enable = true; programs.gamemode.enable = true;
services.tailscale.enable = true; services.tailscale.enable = true;
virtualisation.docker.enable = true; # Docker is now managed by yami-dev-stack.nix when that module is enabled.
# services.yami-dev-stack = { enable = true; user = "chexit"; };
services.yami-dev-stack = {
enable = true;
user = "chexit";
dataDir = "/mnt/ssd_110/yami";
};
system.stateVersion = "25.11"; system.stateVersion = "25.11";
} }
Generated
+48
View File
@@ -0,0 +1,48 @@
{
"nodes": {
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1776454077,
"narHash": "sha256-7zSUFWsU0+jlD7WB3YAxQ84Z/iJurA5hKPm8EfEyGJk=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "565e5349208fe7d0831ef959103c9bafbeac0681",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1776169885,
"narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"home-manager": "home-manager",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+36 -8
View File
@@ -1,18 +1,46 @@
{ pkgs, ... }: { ... }:
{ {
home.username = "chexit"; home.username = "chexit";
home.homeDirectory = "/home/chexit"; home.homeDirectory = "/home/chexit";
home.stateVersion = "25.11"; home.stateVersion = "25.11";
programs.zsh = { programs.fish = {
enable = true; enable = true;
enableCompletion = true; shellAliases = {
autosuggestion.enable = true; rebuild = "sudo nixos-rebuild switch --flake ~/nixos-config#chexit";
syntaxHighlighting.enable = true; };
initContent = '' };
source ${pkgs.zsh-powerlevel10k}/share/zsh-powerlevel10k/powerlevel10k.zsh-theme
''; programs.starship = {
enable = true;
enableFishIntegration = true;
};
programs.alacritty = {
enable = true;
settings = {
window = {
padding = { x = 10; y = 10; };
opacity = 0.95;
decorations = "None";
};
font = {
normal = { family = "JetBrainsMono Nerd Font"; style = "Regular"; };
size = 11;
};
colors = {
primary = { background = "#1e1e2e"; foreground = "#cdd6f4"; };
};
};
};
xdg.configFile = {
"niri/config.kdl".source = ./niri/config.kdl;
"waybar/config.jsonc".source = ./niri/waybar/config.jsonc;
"waybar/style.css".source = ./niri/waybar/style.css;
"mako/config".source = ./niri/mako/config;
"fuzzel/fuzzel.ini".source = ./niri/fuzzel/fuzzel.ini;
}; };
programs.home-manager.enable = true; programs.home-manager.enable = true;
+43
View File
@@ -0,0 +1,43 @@
{ pkgs, ... }:
{
programs.niri.enable = true;
environment.systemPackages = with pkgs; [
# terminal + launcher
alacritty
fuzzel
# bar, notifications, wallpaper
waybar
mako
swaybg
# screenshots + clipboard
grim
slurp
swappy
wl-clipboard
# lock + idle
swaylock-effects
swayidle
wlogout
# XWayland shim for niri
xwayland-satellite
# tray + system utilities
networkmanagerapplet
pavucontrol
playerctl
brightnessctl
libnotify
xdg-utils
];
environment.sessionVariables = {
XDG_CURRENT_DESKTOP = "niri";
XDG_SESSION_DESKTOP = "niri";
};
}
+201
View File
@@ -0,0 +1,201 @@
// niri configuration
// docs: https://github.com/YaLTeR/niri/wiki/Configuration:-Overview
input {
keyboard {
xkb {
layout "us"
}
repeat-delay 300
repeat-rate 40
}
touchpad {
tap
dwt
natural-scroll
accel-speed 0.2
}
mouse {
accel-speed 0.0
}
warp-mouse-to-focus
}
output "*" {
// let niri autodetect; override here per-monitor if needed
}
layout {
gaps 12
center-focused-column "never"
preset-column-widths {
proportion 0.33333
proportion 0.5
proportion 0.66667
}
default-column-width { proportion 0.5; }
focus-ring {
width 2
active-color "#7fc8ff"
inactive-color "#3a3a3a"
}
border {
off
}
struts {
left 8
right 8
top 4
bottom 4
}
}
cursor {
xcursor-theme "Adwaita"
xcursor-size 24
}
prefer-no-csd
screenshot-path "~/Pictures/Screenshots/Screenshot-%Y-%m-%d-%H%M%S.png"
hotkey-overlay {
skip-at-startup
}
environment {
DISPLAY ":0"
QT_QPA_PLATFORM "wayland;xcb"
GDK_BACKEND "wayland,x11"
MOZ_ENABLE_WAYLAND "1"
_JAVA_AWT_WM_NONREPARENTING "1"
}
spawn-at-startup "xwayland-satellite"
spawn-at-startup "waybar"
spawn-at-startup "mako"
spawn-at-startup "swaybg" "-c" "#1e1e2e"
spawn-at-startup "nm-applet" "--indicator"
spawn-at-startup "swayidle" "-w" \
"timeout" "600" "swaylock -f" \
"timeout" "900" "niri msg action power-off-monitors" \
"before-sleep" "swaylock -f"
window-rule {
geometry-corner-radius 8
clip-to-geometry true
}
window-rule {
match app-id="^org\\.keepassxc\\.KeePassXC$"
match app-id="^1Password$"
block-out-from "screen-capture"
}
binds {
Mod+Shift+Slash { show-hotkey-overlay; }
// launchers
Mod+Return { spawn "alacritty"; }
Mod+D { spawn "fuzzel"; }
Mod+E { spawn "dolphin"; }
Mod+B { spawn "firefox"; }
Mod+Ctrl+L { spawn "swaylock" "-f"; }
Mod+Shift+Escape { spawn "wlogout"; }
// media keys
XF86AudioRaiseVolume allow-when-locked=true { spawn "wpctl" "set-volume" "-l" "1.0" "@DEFAULT_AUDIO_SINK@" "5%+"; }
XF86AudioLowerVolume allow-when-locked=true { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "5%-"; }
XF86AudioMute allow-when-locked=true { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; }
XF86AudioMicMute allow-when-locked=true { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle"; }
XF86AudioPlay { spawn "playerctl" "play-pause"; }
XF86AudioPause { spawn "playerctl" "play-pause"; }
XF86AudioNext { spawn "playerctl" "next"; }
XF86AudioPrev { spawn "playerctl" "previous"; }
XF86MonBrightnessUp { spawn "brightnessctl" "set" "5%+"; }
XF86MonBrightnessDown { spawn "brightnessctl" "set" "5%-"; }
// window management
Mod+Q { close-window; }
Mod+Left { focus-column-left; }
Mod+Right { focus-column-right; }
Mod+Up { focus-window-up; }
Mod+Down { focus-window-down; }
Mod+H { focus-column-left; }
Mod+L { focus-column-right; }
Mod+K { focus-window-up; }
Mod+J { focus-window-down; }
Mod+Ctrl+Left { move-column-left; }
Mod+Ctrl+Right { move-column-right; }
Mod+Ctrl+Up { move-window-up; }
Mod+Ctrl+Down { move-window-down; }
Mod+Ctrl+H { move-column-left; }
Mod+Ctrl+L { move-column-right; }
Mod+Ctrl+K { move-window-up; }
Mod+Ctrl+J { move-window-down; }
Mod+Home { focus-column-first; }
Mod+End { focus-column-last; }
Mod+Page_Down { focus-workspace-down; }
Mod+Page_Up { focus-workspace-up; }
Mod+U { focus-workspace-down; }
Mod+I { focus-workspace-up; }
Mod+Shift+Page_Down { move-column-to-workspace-down; }
Mod+Shift+Page_Up { move-column-to-workspace-up; }
Mod+Shift+U { move-column-to-workspace-down; }
Mod+Shift+I { move-column-to-workspace-up; }
Mod+1 { focus-workspace 1; }
Mod+2 { focus-workspace 2; }
Mod+3 { focus-workspace 3; }
Mod+4 { focus-workspace 4; }
Mod+5 { focus-workspace 5; }
Mod+6 { focus-workspace 6; }
Mod+7 { focus-workspace 7; }
Mod+8 { focus-workspace 8; }
Mod+9 { focus-workspace 9; }
Mod+Shift+1 { move-column-to-workspace 1; }
Mod+Shift+2 { move-column-to-workspace 2; }
Mod+Shift+3 { move-column-to-workspace 3; }
Mod+Shift+4 { move-column-to-workspace 4; }
Mod+Shift+5 { move-column-to-workspace 5; }
Mod+Shift+6 { move-column-to-workspace 6; }
Mod+Shift+7 { move-column-to-workspace 7; }
Mod+Shift+8 { move-column-to-workspace 8; }
Mod+Shift+9 { move-column-to-workspace 9; }
Mod+Comma { consume-window-into-column; }
Mod+Period { expel-window-from-column; }
Mod+R { switch-preset-column-width; }
Mod+F { maximize-column; }
Mod+Shift+F { fullscreen-window; }
Mod+C { center-column; }
Mod+Minus { set-column-width "-10%"; }
Mod+Equal { set-column-width "+10%"; }
Mod+Shift+Minus { set-window-height "-10%"; }
Mod+Shift+Equal { set-window-height "+10%"; }
// screenshots
Print { screenshot; }
Ctrl+Print { screenshot-screen; }
Alt+Print { screenshot-window; }
// session
Mod+Shift+E { quit; }
Mod+Shift+P { power-off-monitors; }
}
+23
View File
@@ -0,0 +1,23 @@
font=JetBrainsMono Nerd Font:size=12
dpi-aware=yes
terminal=alacritty
prompt=" "
icon-theme=Adwaita
width=40
lines=12
horizontal-pad=20
vertical-pad=14
inner-pad=10
[colors]
background=1e1e2eee
text=cdd6f4ff
match=f9e2afff
selection=89b4faff
selection-text=1e1e2eff
selection-match=1e1e2eff
border=89b4faff
[border]
width=2
radius=10
+20
View File
@@ -0,0 +1,20 @@
font=JetBrainsMono Nerd Font 10
background-color=#1e1e2eee
text-color=#cdd6f4
border-color=#89b4fa
border-size=2
border-radius=8
padding=12
margin=8
default-timeout=5000
ignore-timeout=1
max-visible=5
layer=overlay
anchor=top-right
[urgency=low]
border-color=#6c7086
[urgency=high]
border-color=#f38ba8
default-timeout=0
+77
View File
@@ -0,0 +1,77 @@
{
"layer": "top",
"position": "top",
"height": 30,
"spacing": 6,
"margin-top": 4,
"margin-left": 8,
"margin-right": 8,
"modules-left": ["niri/workspaces", "niri/window"],
"modules-center": ["clock"],
"modules-right": [
"tray",
"network",
"pulseaudio",
"cpu",
"memory",
"custom/power"
],
"niri/workspaces": {
"format": "{icon}",
"format-icons": {
"default": "",
"active": "",
"urgent": ""
}
},
"niri/window": {
"format": "{title}",
"max-length": 80,
"separate-outputs": true
},
"clock": {
"format": "{:%a %d %b %H:%M}",
"tooltip-format": "<tt><small>{calendar}</small></tt>"
},
"cpu": {
"format": " {usage}%",
"interval": 2
},
"memory": {
"format": " {used:0.1f}G",
"interval": 5
},
"network": {
"format-wifi": " {essid}",
"format-ethernet": " {ifname}",
"format-disconnected": "⚠ offline",
"tooltip-format": "{ifname} · {ipaddr}"
},
"pulseaudio": {
"format": "{icon} {volume}%",
"format-muted": " muted",
"format-icons": {
"default": ["", "", ""]
},
"on-click": "pavucontrol"
},
"tray": {
"icon-size": 16,
"spacing": 8
},
"custom/power": {
"format": "⏻",
"tooltip": false,
"on-click": "wlogout"
}
}
+62
View File
@@ -0,0 +1,62 @@
* {
font-family: "JetBrainsMono Nerd Font", "Geist", sans-serif;
font-size: 13px;
border: none;
border-radius: 0;
min-height: 0;
}
window#waybar {
background: rgba(30, 30, 46, 0.85);
color: #cdd6f4;
border-radius: 10px;
}
#workspaces button {
padding: 0 8px;
color: #9399b2;
background: transparent;
border-radius: 6px;
}
#workspaces button.active {
color: #1e1e2e;
background: #89b4fa;
}
#workspaces button.urgent {
background: #f38ba8;
color: #1e1e2e;
}
#window,
#clock,
#cpu,
#memory,
#network,
#pulseaudio,
#tray,
#custom-power {
padding: 0 10px;
margin: 4px 2px;
border-radius: 6px;
background: rgba(49, 50, 68, 0.6);
}
#clock {
color: #f9e2af;
font-weight: 600;
}
#custom-power {
color: #f38ba8;
padding: 0 12px;
}
#network.disconnected {
color: #f38ba8;
}
#pulseaudio.muted {
color: #6c7086;
}
+6 -2
View File
@@ -4,7 +4,7 @@
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
# системное # system
wget wget
git git
htop htop
@@ -14,11 +14,15 @@
kdePackages.qtstyleplugin-kvantum kdePackages.qtstyleplugin-kvantum
kdePackages.kate kdePackages.kate
# софт # soft
firefox firefox
vesktop vesktop
materialgram materialgram
termius termius
throne
spotify
thunderbird
openvpn
# dev # dev
vscode vscode
+1 -1
View File
@@ -1,5 +1,5 @@
{ ... }: { ... }:
{ {
programs.zsh.enable = true; programs.fish.enable = true;
} }
+208
View File
@@ -0,0 +1,208 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.yami-dev-stack;
in
{
options.services.yami-dev-stack = {
enable = mkEnableOption "Yami development stack (gitea, postgres, docker, runner)";
user = mkOption {
type = types.str;
description = "User that will own runner token and be added to docker group";
};
dataDir = mkOption {
type = types.nullOr types.path;
default = null;
example = "/mnt/ssd_110/yami";
description = ''
If set, bind-mounts /var/lib/{gitea,postgresql,gitea-runner} under this
path so all stack data lives on a separate disk. Directories are created
automatically; existing data must be rsync'd manually before enabling.
'';
};
};
config = mkIf cfg.enable (mkMerge [ {
# ── Systemd target ─────────────────────────────────────────────────────────
# Manual-only: nothing in wantedBy, so it never starts on boot.
systemd.targets.yami-dev = {
description = "Yami development stack (gitea, postgres, docker, runner)";
wantedBy = [ ];
};
# ── PostgreSQL 16 ──────────────────────────────────────────────────────────
services.postgresql = {
enable = true;
package = pkgs.postgresql_16;
ensureDatabases = [ "gitea" ];
ensureUsers = [
{
name = "gitea";
ensureDBOwnership = true;
}
];
};
# Pull postgres under yami-dev.target; stop it when target stops.
systemd.services.postgresql = {
wantedBy = mkForce [ "yami-dev.target" ];
partOf = [ "yami-dev.target" ];
};
# ── Gitea ──────────────────────────────────────────────────────────────────
services.gitea = {
enable = true;
appName = "Yami Git";
# LFS enabled via the NixOS helper so the module manages LFS_JWT_SECRET
# (writing it to app.ini, which is read-only by design).
lfs.enable = true;
database = {
type = "postgres";
socket = "/run/postgresql";
name = "gitea";
user = "gitea";
};
settings = {
server = {
DOMAIN = "localhost";
ROOT_URL = "http://localhost:3000/";
HTTP_ADDR = "0.0.0.0";
HTTP_PORT = 3000;
SSH_PORT = 2222;
START_SSH_SERVER = true;
};
service = {
DISABLE_REGISTRATION = true;
DEFAULT_KEEP_EMAIL_PRIVATE = true;
};
repository = {
DEFAULT_BRANCH = "main";
ENABLE_PUSH_CREATE_USER = true;
ENABLE_PUSH_CREATE_ORG = true;
};
actions = {
ENABLED = true;
DEFAULT_ACTIONS_URL = "github";
LOG_RETENTION_DAYS = 14;
ARTIFACT_RETENTION_DAYS = 30;
};
packages = {
ENABLED = true;
};
session = {
COOKIE_SECURE = false;
};
"cron.git_gc_repos" = {
ENABLED = true;
SCHEDULE = "@every 72h";
ARGS = "--aggressive --prune=now";
};
};
};
systemd.services.gitea = {
wantedBy = mkForce [ "yami-dev.target" ];
partOf = [ "yami-dev.target" ];
after = [ "postgresql.service" ];
requires = [ "postgresql.service" ];
};
# ── Docker ─────────────────────────────────────────────────────────────────
virtualisation.docker = {
enable = true;
enableOnBoot = false;
};
systemd.services.docker = {
wantedBy = mkForce [ "yami-dev.target" ];
partOf = [ "yami-dev.target" ];
};
users.users.${cfg.user}.extraGroups = [ "docker" ];
# ── Gitea Actions Runner ───────────────────────────────────────────────────
services.gitea-actions-runner.instances.default = {
enable = true;
name = "home-runner";
url = "http://localhost:3000";
# NOT under /var/lib/gitea-runner — that path is systemd-managed for the
# service (DynamicUser + StateDirectory means it becomes a symlink to
# /var/lib/private/gitea-runner). Pre-creating it ourselves breaks the
# service with "Failed to set up special execution directory: File exists".
tokenFile = "/var/lib/gitea-runner-secret/token";
labels = [
"ubuntu-latest:docker://node:20"
"native:host"
];
};
systemd.services."gitea-runner-default" = {
wantedBy = mkForce [ "yami-dev.target" ];
partOf = [ "yami-dev.target" ];
after = [ "gitea.service" "docker.service" ];
requires = [ "gitea.service" "docker.service" ];
};
# Runner uses DynamicUser, so there's no static gitea-runner user/group.
# Token lives in a sibling dir (not /var/lib/gitea-runner — that's systemd's
# StateDirectory and pre-creating it breaks DynamicUser bind setup).
# The file MUST be in env-file format, not a raw token:
# TOKEN=<paste-registration-token-from-gitea-ui>
# Get the token from Site Administration → Actions → Runners → Create new Runner.
systemd.tmpfiles.rules = [
"d /var/lib/gitea-runner-secret 0700 root root -"
];
# ── Shell aliases ──────────────────────────────────────────────────────────
environment.shellAliases = {
yami-up = "sudo systemctl start yami-dev.target && echo 'Gitea http://localhost:3000'";
yami-down = "sudo systemctl stop yami-dev.target && echo 'Yami dev stack stopped'";
yami-status = "systemctl status yami-dev.target gitea postgresql docker gitea-runner-default --no-pager";
yami-logs = "journalctl -u gitea -u gitea-runner-default -f";
};
# ── Firewall ───────────────────────────────────────────────────────────────
# Merged with existing rules (Steam etc.); does not overwrite them.
networking.firewall.allowedTCPPorts = [ 3000 2222 ];
}
# ── Optional: move all stack data to a separate disk via bind-mounts ─────────
(mkIf (cfg.dataDir != null) (
let
# gitea-runner intentionally NOT bind-mounted: its service uses
# DynamicUser + StateDirectory, which refuses to adopt an existing
# mount point ("Failed to set up special execution directory: File exists").
# Its data is tiny (token + .runner state) so it stays on the system disk.
subs = [ "gitea" "postgresql" ];
mkBind = name: {
name = "/var/lib/${name}";
value = {
device = "${cfg.dataDir}/${name}";
fsType = "none";
options = [ "bind" ];
};
};
in {
# Ensure target dirs exist on the data disk before mounts happen.
systemd.tmpfiles.rules = map (n: "d ${cfg.dataDir}/${n} 0755 root root -") subs
++ [ "d ${cfg.dataDir} 0755 root root -" ];
fileSystems = listToAttrs (map mkBind subs);
}
))
]);
}