diff --git a/AGENTS.md b/AGENTS.md index fa09456..f6e97bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,6 +18,8 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla ## Gotchas +- Actual Budget's NixOS service runs `actual-server` with `WorkingDirectory = services.actual.settings.dataDir`, and the `actual-server` CLI wrapper sets `ACTUAL_CONFIG_PATH=./config.json` when that current directory contains `config.json`, overriding the NixOS module's generated config path before the server loads configuration. + When migrating old community-script state to the normal upstream `/data` shape, move or rewrite a copied `/data/config.json` so it cannot shadow the declarative module settings. - Subagent completion delivery is non-blocking through immediate spawn, milestone notifications, retained terminal entries, and `subagent_list` or `subagent_result` retrieval. `subagent_wait` intentionally blocks the parent tool call until its condition or timeout, so do not use it merely to keep background work alive during an interactive workflow. - Nixvim's flake input following the root nixpkgs source does not make its Home Manager module reuse the host's `pkgs` instance. diff --git a/guests/actualbudget.nix b/guests/actualbudget.nix new file mode 100644 index 0000000..f22edd2 --- /dev/null +++ b/guests/actualbudget.nix @@ -0,0 +1,50 @@ +{ + config, + lib, + my, + ... +}: +let + cfg = config.guests.actualbudget; + dataDir = "/data"; +in +{ + imports = [ + (my.guest { + name = "actualbudget"; + interior = { + modules.services.actualbudget = { + enable = true; + inherit dataDir; + }; + }; + }) + ]; + + options.guests.actualbudget = { + dataPath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/srv/actualbudget"; + description = "Host path bind-mounted as Actual Budget's data directory."; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.dataPath != null; + message = "guests.actualbudget.dataPath must point at the host directory that stores Actual Budget data."; + } + ]; + + guests.actualbudget = { + mounts.${dataDir}.hostPath = cfg.dataPath; + limits = { + memory = lib.mkDefault "2G"; + cpu = lib.mkDefault "200%"; + tasksMax = lib.mkDefault 512; + }; + }; + }; +} diff --git a/hosts/pikachu/default.nix b/hosts/pikachu/default.nix index 4ba4cae..43adb6f 100644 --- a/hosts/pikachu/default.nix +++ b/hosts/pikachu/default.nix @@ -20,14 +20,45 @@ systemd.network = { enable = true; - networks."10-uplink" = { - matchConfig.MACAddress = "78:55:36:07:af:49"; - networkConfig.DHCP = "yes"; - linkConfig.RequiredForOnline = "routable"; + netdevs = { + "40-br-vlan20".netdevConfig = { + Name = "br-vlan20"; + Kind = "bridge"; + }; + "40-vlan20" = { + netdevConfig = { + Name = "vlan20"; + Kind = "vlan"; + }; + vlanConfig.Id = 20; + }; + }; + + networks = { + "10-uplink" = { + matchConfig = { + Name = "enp2s0"; + MACAddress = "78:55:36:07:af:49"; + }; + networkConfig.DHCP = "yes"; + vlan = [ "vlan20" ]; + linkConfig.RequiredForOnline = "routable"; + }; + "40-vlan20" = { + matchConfig.Name = "vlan20"; + networkConfig.Bridge = "br-vlan20"; + linkConfig.RequiredForOnline = "no"; + }; + "40-br-vlan20" = { + matchConfig.Name = "br-vlan20"; + linkConfig.RequiredForOnline = "no"; + }; }; }; networking.useDHCP = false; + modules.network.vlans = [ 20 ]; + boot.zfs.forceImportRoot = false; modules.zfs = { @@ -43,6 +74,13 @@ modules.git.enable = true; modules.toolkit.enable = true; + guests.actualbudget = { + enable = true; + vlan = 20; + mac = "BC:24:11:C1:CD:28"; + dataPath = "/pikachu/data/actualbudget"; + }; + environment.systemPackages = with pkgs; [ pciutils smartmontools diff --git a/modules/services/actualbudget.nix b/modules/services/actualbudget.nix new file mode 100644 index 0000000..4ed8b44 --- /dev/null +++ b/modules/services/actualbudget.nix @@ -0,0 +1,61 @@ +{ + config, + lib, + ... +}: +let + cfg = config.modules.services.actualbudget; +in +{ + options.modules.services.actualbudget = { + enable = lib.mkEnableOption "Actual Budget"; + + port = lib.mkOption { + type = lib.types.port; + default = 5006; + description = "Port Actual Budget listens on."; + }; + + dataDir = lib.mkOption { + type = lib.types.str; + default = "/data"; + description = "Directory where Actual Budget stores server and user files."; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "actual"; + description = "User account that runs Actual Budget."; + }; + + uid = lib.mkOption { + type = lib.types.ints.positive; + default = 20001; + description = "Fixed uid for the Actual Budget service user."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "storage"; + description = "Group account that owns Actual Budget's writable state."; + }; + }; + + config = lib.mkIf cfg.enable { + users.users.${cfg.user} = { + isSystemUser = true; + uid = cfg.uid; + group = cfg.group; + }; + + services.actual = { + enable = true; + openFirewall = true; + user = cfg.user; + group = cfg.group; + settings = { + inherit (cfg) port dataDir; + }; + }; + }; +}