Compare commits

...
2 Commits
Author SHA1 Message Date
oqyude c05cc88843 restructuring 2026-10-01 15:14:37 +03:00
oqyude d49fd5a358 big refactoring 2026-10-01 14:16:17 +03:00
22 changed files with 772 additions and 631 deletions
+12 -15
View File
@@ -1,18 +1,15 @@
# Host: "default" (device: minimal)
#
# The host record lives in configurations/default.nix; this file is only the
# module body. `xlib` (identity, dirs, helpers) arrives as a module argument.
{ {
deviceType = "minimal"; inputs,
modules = [ ...
( }:
{ {
inputs, imports = [
... inputs.self.nixosModules.default
}:
{
imports = [
inputs.self.nixosModules.default
];
system.stateVersion = "26.05";
}
)
]; ];
system.stateVersion = "26.05";
} }
+64 -9
View File
@@ -1,18 +1,73 @@
{ inputs, ... }@flakeContext: { inputs, ... }@flakeContext:
let let
lib = inputs.nixpkgs.lib;
mkSystem = import ../lib/mkSystem.nix flakeContext; mkSystem = import ../lib/mkSystem.nix flakeContext;
xlibLib = import ../lib/xlib { inherit lib; };
# One record per host. The attribute name IS the hostname, so it is written
# exactly once; `hostname` is only needed where the attribute name is not
# the real hostname (the `default` entry).
#
# device device type, must be a key of `devices` in lib/xlib/device.nix
# modules module body for this host
hosts = {
default = {
hostname = "nixos";
device = "minimal";
modules = [ ./any.nix ];
};
atoridu = {
device = "primary";
modules = [ ./mini-pc.nix ];
};
rydiwo = {
device = "secondary";
modules = [ ./mini-laptop.nix ];
};
otreca = {
device = "vds";
modules = [ ./vds.nix ];
};
sapphira = {
device = "server";
modules = [ ./server.nix ];
};
wsl = {
device = "wsl";
modules = [ ./wsl.nix ];
};
};
mkHost =
name:
{
device,
modules,
hostname ? name,
...
}:
let
xlib = xlibLib.mkXlib {
inherit hostname;
type = device;
};
in
{
inherit xlib;
system = mkSystem { inherit xlib modules; };
};
in in
{ {
nixosConfigurations = { nixosConfigurations = lib.mapAttrs' (
default = mkSystem (import ./any.nix); # default name: spec: lib.nameValuePair name (mkHost name spec).system
atoridu = mkSystem (import ./mini-pc.nix); # atoridu ) hosts;
rydiwo = mkSystem (import ./mini-laptop.nix); # rydiwo
otreca = mkSystem (import ./vds.nix); # vds # Per-host xlib values, for code that lives outside the module system
sapphira = mkSystem (import ./server.nix); # sapphira # (deploy, overlays, pkgs).
wsl = mkSystem (import ./wsl.nix); # wsl xlib = lib.mapAttrs' (name: spec: lib.nameValuePair name (mkHost name spec).xlib) hosts;
};
nixOnDroidConfigurations = { nixOnDroidConfigurations = {
epral = import ./mobile.nix flakeContext; # epral (Android via nix-on-droid) epral = import ./mobile.nix flakeContext; # epral (Android device via nix-on-droid)
# Alias so a plain `nix-on-droid switch` from a local clone # Alias so a plain `nix-on-droid switch` from a local clone
# (~/.config/nix-on-droid) picks up the device config without `#epral`. # (~/.config/nix-on-droid) picks up the device config without `#epral`.
default = import ./mobile.nix flakeContext; default = import ./mobile.nix flakeContext;
+34 -38
View File
@@ -1,41 +1,37 @@
# Host: "rydiwo" (device: secondary)
#
# The host record lives in configurations/default.nix; this file is only the
# module body. `xlib` (identity, dirs, helpers) arrives as a module argument.
{ {
deviceType = "secondary"; lib,
hostname = "rydiwo"; pkgs,
modules = [ xlib,
( inputs,
{ ...
lib, }:
pkgs, {
xlib, imports = with inputs; [
inputs, nixos-hardware.nixosModules.chuwi-minibook-x
... ./hardware/mini-laptop.nix
}: self.nixosModules.default
{
imports = with inputs; [
nixos-hardware.nixosModules.chuwi-minibook-x
./hardware/mini-laptop.nix
self.nixosModules.default
];
boot = {
kernelPackages = lib.mkDefault pkgs.linuxPackages_xanmod_stable;
loader = {
systemd-boot.enable = lib.mkDefault true;
efi.canTouchEfiVariables = lib.mkDefault true;
};
};
fileSystems = xlib.helpers.mkNtfsMount {
path = xlib.dirs.lamet-drive;
uuid = "DC76BD3576BD116E";
mask = "0000";
};
xlib.ssh.enable = true;
hardware.intel-gpu-tools.enable = true;
system.stateVersion = "26.05";
}
)
]; ];
boot = {
kernelPackages = lib.mkDefault pkgs.linuxPackages_xanmod_stable;
loader = {
systemd-boot.enable = lib.mkDefault true;
efi.canTouchEfiVariables = lib.mkDefault true;
};
};
fileSystems = xlib.helpers.mkNtfsMount {
path = xlib.dirs.lamet-drive;
uuid = "DC76BD3576BD116E";
mask = "0000";
};
host.ssh.enable = true;
hardware.intel-gpu-tools.enable = true;
system.stateVersion = "26.05";
} }
+81 -80
View File
@@ -1,83 +1,84 @@
# Host: "atoridu" (device: primary)
#
# The host record lives in configurations/default.nix; this file is only the
# module body. `xlib` (identity, dirs, helpers) arrives as a module argument.
{ {
deviceType = "primary"; lib,
hostname = "atoridu"; pkgs,
modules = [ xlib,
( inputs,
{ ...
lib, }:
pkgs, {
xlib, imports = with inputs; [
inputs, ./hardware/mini-pc.nix
... ./disko/mini-pc.nix
}: ./hardware/logitech.nix
{ self.nixosModules.default
imports = with inputs; [
./hardware/mini-pc.nix
./disko/mini-pc.nix
./hardware/logitech.nix
self.nixosModules.default
];
fileSystems = lib.listToAttrs (
map (xlib.helpers.mkNtfsMount) [
{
path = xlib.dirs.therima-drive;
uuid = "C0A2DDEFA2DDEA44";
enable = false;
}
{
path = xlib.dirs.vetymae-drive;
uuid = "6408433908430A0E";
enable = false;
}
{
path = xlib.dirs.soptur-drive;
uuid = "C00C56E40C56D54E";
enable = false;
}
]
);
boot = {
kernelPackages = lib.mkDefault pkgs.linuxPackages_xanmod_stable;
loader = {
systemd-boot.enable = lib.mkDefault true;
efi.canTouchEfiVariables = lib.mkDefault true;
};
};
services.xserver = {
videoDrivers = [
"amdgpu"
];
};
services.pipewire = {
enable = lib.mkDefault true;
systemWide = true;
alsa.enable = false;
alsa.support32Bit = true;
pulse.enable = true;
jack.enable = true;
extraConfig.pipewire = {
"99-default.conf" = {
"context.properties" = {
"default.clock.rate" = 96000;
"default.clock.allowed-rates" = [
44100
48000
96000
];
"default.clock.quantum" = 1024;
"default.clock.min-quantum" = 256;
"default.clock.max-quantum" = 2048;
};
};
};
};
nixpkgs.config.pulseaudio = true;
system.stateVersion = "26.05";
}
)
]; ];
# mkNtfsMount returns a `{ "<path>" = { ... }; }` attrset (the shape
# fileSystems itself wants), so several mounts are combined with
# mergeAttrsList — not listToAttrs, which would demand `name`/`value`.
#
# These three ntfs3 drives are intentionally left unmounted. The entries
# are kept commented out rather than deleted, so restoring a drive is a
# matter of uncommenting its block. `enable = false` would declare a drive
# without mounting it; dropping the field mounts it.
fileSystems = lib.mergeAttrsList (
map (xlib.helpers.mkNtfsMount) [
# {
# path = xlib.dirs.therima-drive;
# uuid = "C0A2DDEFA2DDEA44";
# }
# {
# path = xlib.dirs.vetymae-drive;
# uuid = "6408433908430A0E";
# }
# {
# path = xlib.dirs.soptur-drive;
# uuid = "C00C56E40C56D54E";
# }
]
);
boot = {
kernelPackages = lib.mkDefault pkgs.linuxPackages_xanmod_stable;
loader = {
systemd-boot.enable = lib.mkDefault true;
efi.canTouchEfiVariables = lib.mkDefault true;
};
};
services.xserver = {
videoDrivers = [
"amdgpu"
];
};
services.pipewire = {
enable = lib.mkDefault true;
systemWide = true;
alsa.enable = false;
alsa.support32Bit = true;
pulse.enable = true;
jack.enable = true;
extraConfig.pipewire = {
"99-default.conf" = {
"context.properties" = {
"default.clock.rate" = 96000;
"default.clock.allowed-rates" = [
44100
48000
96000
];
"default.clock.quantum" = 1024;
"default.clock.min-quantum" = 256;
"default.clock.max-quantum" = 2048;
};
};
};
};
nixpkgs.config.pulseaudio = true;
system.stateVersion = "26.05";
} }
+9 -6
View File
@@ -9,6 +9,7 @@ let
# (essentials, users.nix, home-manager, sops-nix, disko, grub2-themes) # (essentials, users.nix, home-manager, sops-nix, disko, grub2-themes)
# and nixpkgs.overlays are skipped so it evaluates under nix-on-droid's # and nixpkgs.overlays are skipped so it evaluates under nix-on-droid's
# module system (class = "nixOnDroid"). # module system (class = "nixOnDroid").
xlib = import ../lib/xlib.nix { lib = inputs.nixpkgs.lib; };
nixOnDroidModule = nixOnDroidModule =
{ {
lib, lib,
@@ -21,11 +22,6 @@ let
inputs.self.nixosModules.strict inputs.self.nixosModules.strict
]; ];
xlib.device = {
type = "termux";
hostname = "epral";
};
# Login shell. nix-on-droid writes /etc/passwd from user.shell on every # Login shell. nix-on-droid writes /etc/passwd from user.shell on every
# activation, so `chsh` is useless here — set it in nix instead. # activation, so `chsh` is useless here — set it in nix instead.
# (default is bashInteractive) # (default is bashInteractive)
@@ -120,6 +116,13 @@ inputs.nix-on-droid.lib.nixOnDroidConfiguration {
nixOnDroidModule nixOnDroidModule
]; ];
extraSpecialArgs = { extraSpecialArgs = {
deviceType = "termux"; # `xlib` is the same value shape NixOS hosts get (lib/mkSystem.nix);
# the hostname lives here because nixOnDroidConfigurations is keyed by
# both "epral" and the "default" alias, so it cannot come from the
# attribute name.
xlib = xlib.mkXlib {
hostname = "epral";
type = "termux";
};
}; };
} }
+76 -80
View File
@@ -1,83 +1,79 @@
# Host: "sapphira" (device: server)
#
# The host record lives in configurations/default.nix; this file is only the
# module body. `xlib` (identity, dirs, helpers) arrives as a module argument.
{ {
deviceType = "server"; lib,
hostname = "sapphira"; pkgs,
modules = [ xlib,
( inputs,
{ ...
lib, }:
pkgs, {
xlib, imports = [
inputs, ./hardware/server.nix
... inputs.self.nixosModules.default
}:
{
imports = [
./hardware/server.nix
inputs.self.nixosModules.default
];
boot = {
# kernelPackages = pkgs.linuxPackages_xanmod_stable;
hardwareScan = true;
loader = {
systemd-boot.enable = lib.mkDefault true;
efi.canTouchEfiVariables = lib.mkDefault true;
};
};
hardware = {
bluetooth.enable = true;
graphics = {
enable = true;
extraPackages = with pkgs; [
intel-media-driver
intel-ocl
intel-vaapi-driver
];
};
intel-gpu-tools.enable = true;
};
fileSystems =
(xlib.helpers.mkExfatMount {
path = xlib.dirs.archive-drive;
label = "archive";
})
// (xlib.helpers.mkExfatMount {
path = xlib.dirs.mobile-drive;
uuid = "7EB1-DC99";
})
// (xlib.helpers.mkBindMount {
what = xlib.dirs.services-folder;
where = xlib.dirs.services-mnt-folder;
})
// {
# External drive
"${xlib.dirs.server-home}" = {
device = "/dev/disk/by-uuid/37e53ebc-5343-a94d-9fe2-0ca39e13a8de";
fsType = "ext4";
};
};
systemd.tmpfiles.rules = [
"z ${xlib.dirs.services-mnt-folder} 0777 root root -"
];
xlib.ssh.enable = true;
networking = {
networkmanager.enable = true;
firewall.enable = false;
# nameservers = [
# "192.168.1.1"
# "127.0.0.1"
# ];
};
system = {
stateVersion = "25.05";
};
}
)
]; ];
boot = {
# kernelPackages = pkgs.linuxPackages_xanmod_stable;
hardwareScan = true;
loader = {
systemd-boot.enable = lib.mkDefault true;
efi.canTouchEfiVariables = lib.mkDefault true;
};
};
hardware = {
bluetooth.enable = true;
graphics = {
enable = true;
extraPackages = with pkgs; [
intel-media-driver
intel-ocl
intel-vaapi-driver
];
};
intel-gpu-tools.enable = true;
};
fileSystems =
(xlib.helpers.mkExfatMount {
path = xlib.dirs.archive-drive;
label = "archive";
})
// (xlib.helpers.mkExfatMount {
path = xlib.dirs.mobile-drive;
uuid = "7EB1-DC99";
})
// (xlib.helpers.mkBindMount {
what = xlib.dirs.services-folder;
where = xlib.dirs.services-mnt-folder;
})
// {
# External drive
"${xlib.dirs.server-home}" = {
device = "/dev/disk/by-uuid/37e53ebc-5343-a94d-9fe2-0ca39e13a8de";
fsType = "ext4";
};
};
systemd.tmpfiles.rules = [
"z ${xlib.dirs.services-mnt-folder} 0777 root root -"
];
host.ssh.enable = true;
networking = {
networkmanager.enable = true;
firewall.enable = false;
# nameservers = [
# "192.168.1.1"
# "127.0.0.1"
# ];
};
system = {
stateVersion = "25.05";
};
} }
+112 -117
View File
@@ -1,122 +1,117 @@
# Host: "otreca" (device: vds)
#
# The host record lives in configurations/default.nix; this file is only the
# module body. `xlib` (identity, dirs, helpers) arrives as a module argument.
{ {
deviceType = "vds"; lib,
hostname = "otreca"; modulesPath,
modules = [ pkgs,
( xlib,
{ inputs,
config, ...
lib, }:
modulesPath, {
pkgs, imports = [
xlib, (modulesPath + "/installer/scan/not-detected.nix")
inputs, (modulesPath + "/profiles/qemu-guest.nix")
...
}:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
(modulesPath + "/profiles/qemu-guest.nix")
./disko/vds.nix ./disko/vds.nix
./hardware/vds.nix ./hardware/vds.nix
inputs.self.nixosModules.default inputs.self.nixosModules.default
];
boot = {
# kernelPackages = pkgs.linuxPackages_xanmod_stable;
hardwareScan = true;
loader = {
grub = {
enable = true;
device = "nodev";
useOSProber = false;
efiSupport = false;
};
systemd-boot.enable = lib.mkDefault false;
};
kernel.sysctl = {
"net.ipv4.tcp_syncookies" = 1;
"net.ipv4.tcp_max_syn_backlog" = 4096;
"net.ipv4.tcp_synack_retries" = 3;
"net.ipv4.tcp_syn_retries" = 3;
};
};
xlib.ssh.enable = true;
services.openssh.openFirewall = true;
services.tailscale = {
enable = true;
openFirewall = true;
};
networking = {
nameservers = [
"1.1.1.1"
"8.8.8.8"
];
networkmanager.enable = true;
tempAddresses = "disabled";
dhcpcd = {
enable = true;
IPv6rs = false;
};
firewall = {
enable = true;
allowPing = true;
};
nftables = {
enable = true;
ruleset = ''
table inet filter {
chain input {
type filter hook input priority 0;
# loopback
iif lo accept
# уже установленные
ct state established,related accept
# РЕЖЕМ SYN СРАЗУ
tcp flags syn tcp dport {80,443} limit rate 20/second burst 40 packets accept
tcp flags syn tcp dport {80,443} drop
# остальное по необходимости
}
}
'';
};
enableIPv6 = false;
interfaces.ens3 = {
useDHCP = true;
# ipv4.addresses = [
# {
# address = "31.57.158.109";
# prefixLength = 24;
# }
# ];
# ipv6.addresses = [
# {
# address = "2a13:7c00:6:102:f816:3eff:fe91:6b9e";
# prefixLength = 64;
# }
# ];
};
# defaultGateway = {
# address = "31.57.158.1";
# interface = "ens3";
# };
# defaultGateway6 = {
# address = "2a13:7c00:6:102::1";
# interface = "ens3";
# };
};
system = {
stateVersion = "25.05";
};
}
)
]; ];
boot = {
# kernelPackages = pkgs.linuxPackages_xanmod_stable;
hardwareScan = true;
loader = {
grub = {
enable = true;
device = "nodev";
useOSProber = false;
efiSupport = false;
};
systemd-boot.enable = lib.mkDefault false;
};
kernel.sysctl = {
"net.ipv4.tcp_syncookies" = 1;
"net.ipv4.tcp_max_syn_backlog" = 4096;
"net.ipv4.tcp_synack_retries" = 3;
"net.ipv4.tcp_syn_retries" = 3;
};
};
host.ssh.enable = true;
services.openssh.openFirewall = true;
services.tailscale = {
enable = true;
openFirewall = true;
};
networking = {
nameservers = [
"1.1.1.1"
"8.8.8.8"
];
networkmanager.enable = true;
tempAddresses = "disabled";
dhcpcd = {
enable = true;
IPv6rs = false;
};
firewall = {
enable = true;
allowPing = true;
};
nftables = {
enable = true;
ruleset = ''
table inet filter {
chain input {
type filter hook input priority 0;
# loopback
iif lo accept
# уже установленные
ct state established,related accept
# РЕЖЕМ SYN СРАЗУ
tcp flags syn tcp dport {80,443} limit rate 20/second burst 40 packets accept
tcp flags syn tcp dport {80,443} drop
# остальное по необходимости
}
}
'';
};
enableIPv6 = false;
interfaces.ens3 = {
useDHCP = true;
# ipv4.addresses = [
# {
# address = "31.57.158.109";
# prefixLength = 24;
# }
# ];
# ipv6.addresses = [
# {
# address = "2a13:7c00:6:102:f816:3eff:fe91:6b9e";
# prefixLength = 64;
# }
# ];
};
# defaultGateway = {
# address = "31.57.158.1";
# interface = "ens3";
# };
# defaultGateway6 = {
# address = "2a13:7c00:6:102::1";
# interface = "ens3";
# };
};
system = {
stateVersion = "25.05";
};
} }
+36 -41
View File
@@ -1,44 +1,39 @@
# Host: "wsl" (device: wsl)
#
# The host record lives in configurations/default.nix; this file is only the
# module body. `xlib` (identity, dirs, helpers) arrives as a module argument.
{ {
deviceType = "wsl"; lib,
hostname = "wsl"; modulesPath,
modules = [ pkgs,
( xlib,
{ inputs,
config, ...
lib, }:
pkgs, {
modulesPath, imports = [
xlib, inputs.nixos-wsl.nixosModules.default
inputs, inputs.self.nixosModules.default
...
}:
{
imports = [
inputs.nixos-wsl.nixosModules.default
inputs.self.nixosModules.default
];
hardware = {
graphics.enable = true;
};
networking = {
firewall = {
enable = false;
allowPing = true;
};
enableIPv6 = true;
};
wsl = {
enable = true;
startMenuLaunchers = true;
useWindowsDriver = true;
defaultUser = config.xlib.device.username;
};
system.stateVersion = "24.11";
}
)
]; ];
hardware = {
graphics.enable = true;
};
networking = {
firewall = {
enable = false;
allowPing = true;
};
enableIPv6 = true;
};
wsl = {
enable = true;
startMenuLaunchers = true;
useWindowsDriver = true;
defaultUser = xlib.device.username;
};
system.stateVersion = "24.11";
} }
+3 -1
View File
@@ -6,7 +6,9 @@ let
path = inputs.deploy-rs.lib.x86_64-linux.activate.nixos inputs.self.nixosConfigurations.${hostname}; path = inputs.deploy-rs.lib.x86_64-linux.activate.nixos inputs.self.nixosConfigurations.${hostname};
}; };
}; };
user = "${inputs.self.nixosConfigurations.default.config.xlib.device.username}"; # Login user for every deploy target. Read from the hoisted xlib instead of
# digging through a built NixOS configuration.
user = "${inputs.self.xlib.default.device.username}";
server = "sapphira"; server = "sapphira";
vds = "otreca"; vds = "otreca";
mini-laptop = "rydiwo"; mini-laptop = "rydiwo";
+1 -5
View File
@@ -53,11 +53,7 @@ let
imports = [ imports = [
(./. + "/${xlib.device.type}.nix") (./. + "/${xlib.device.type}.nix")
]; ];
headless = builtins.elem xlib.device.type [ headless = xlib.isHeadless;
"server"
"vds"
"wsl"
];
}; };
}; };
sharedModules = [ sharedModules = [
+14 -12
View File
@@ -2,26 +2,28 @@
inputs, inputs,
... ...
}: }:
# Builds a NixOS system from a host record.
#
# `xlib` is the pure host value (lib/xlib.nix `mkXlib`) built in
# configurations/default.nix. It is handed to every module as the `xlib`
# argument, so modules read plain `xlib.*` data instead of `config.xlib.*`
# and the host record stays the single source of truth.
{ {
deviceType, xlib,
hostname ? null,
modules ? [ ], modules ? [ ],
system ? "x86_64-linux", system ? "x86_64-linux",
...
}: }:
let let
lib = inputs.nixpkgs.lib; lib = inputs.nixpkgs.lib;
in in
lib.nixosSystem { lib.nixosSystem {
inherit system; inherit
modules = modules ++ [ system
{ modules
xlib.device = { ;
type = deviceType;
}
// lib.optionalAttrs (hostname != null) { inherit hostname; };
}
];
specialArgs = { specialArgs = {
inherit deviceType inputs; inherit inputs;
inherit xlib;
}; };
} }
+66
View File
@@ -0,0 +1,66 @@
# Pure host library: no module system involved.
#
# Aggregates the four concerns a host record is built from:
# device.nix identity + capability flags from the device type
# dirs.nix well-known paths, derived from username
# helpers.nix pure helper functions shared by modules
#
# `mkXlib` is called in flake-level code (configurations/default.nix) and
# handed to every module as the `xlib` argument via lib/mkSystem.nix, so
# modules read plain `xlib.*` values instead of `config.xlib.*` and nothing in
# xlib can be overridden per host — the host record is the only place to
# change it.
{
lib,
...
}:
let
inherit (import ./device.nix { inherit lib; })
devices
mkDevice
;
# dirs.nix is itself a function of `username`, not an attrset.
mkDirs = import ./dirs.nix;
helpers = (import ./helpers.nix { inherit lib; });
in
{
inherit
devices
helpers
mkDevice
mkDirs
;
# Full host record: identity + capability flags + well-known paths +
# shared helpers.
mkXlib =
{
hostname,
type,
username ? "oqyude",
}:
let
device = mkDevice {
inherit
hostname
type
username
;
};
in
{
device = {
inherit
hostname
type
username
;
};
isDesktop = device.isDesktop;
isHeadless = device.isHeadless;
dirs = mkDirs username;
inherit helpers;
};
}
+65
View File
@@ -0,0 +1,65 @@
{
lib,
...
}:
# Supported device types and the identity record built from one.
#
# Single source of truth for host identity: hostname, type, username and the
# capability flags derived from the type. Replaces the old `lib.types.enum`
# in modules/options.nix and the hand-written type lists in modules/default.nix
# and home/home.nix.
let
devices = {
minimal = {
desktop = false;
headless = false;
};
primary = {
desktop = true;
headless = false;
};
secondary = {
desktop = true;
headless = false;
};
server = {
desktop = false;
headless = true;
};
vds = {
desktop = false;
headless = true;
};
wsl = {
desktop = false;
headless = true;
};
termux = {
desktop = false;
headless = true;
};
};
in
{
inherit devices;
# Unknown device type fails here, at flake level, with the valid list.
mkDevice =
{
hostname,
type,
username ? "oqyude",
}:
let
capabilities = devices.${type} or (throw "xlib: unknown device type '${type}', expected one of ${lib.concatStringsSep ", " (builtins.attrNames devices)}");
in
{
inherit
hostname
type
username
;
isDesktop = capabilities.desktop;
isHeadless = capabilities.headless;
};
}
+34
View File
@@ -0,0 +1,34 @@
# Well-known paths. Everything derives from `username`, which is why the
# whole set can be computed outside the module system.
username:
let
user-home = "/home/${username}";
wsl-home = "/mnt/c/Users/${username}";
server-home = "${user-home}/External";
services-mnt-folder = "/mnt/services";
in
{
inherit
user-home
wsl-home
server-home
services-mnt-folder
;
user-storage = "${user-home}/Storage";
wsl-storage = "${wsl-home}/Storage";
server-credentials = "${server-home}/Credentials/server";
storage = "${server-home}/Storage";
calibre-library = "${server-home}/Books-Library";
services-folder = "${server-home}/Services";
services-nodes-folder = "${services-mnt-folder}/nodes";
postgresql-folder = "${services-mnt-folder}/postgresql";
music-library = "${user-home}/Music";
archive-drive = "/mnt/archive";
lamet-drive = "/mnt/lamet";
mobile-drive = "/mnt/mobile";
therima-drive = "/mnt/therima";
vetymae-drive = "/mnt/vetymae";
soptur-drive = "/mnt/soptur";
}
+5 -2
View File
@@ -2,8 +2,11 @@
lib, lib,
... ...
}: }:
# Shared pure helper functions for module definitions. # Pure helper functions for module definitions.
# Injected into every module via `xlib.helpers` (see options.nix). # Injected into every module via `xlib.helpers` (see default.nix).
#
# Defined in a `let` because they reference each other (mkTmpDirs uses
# mkTmpfile, mkServiceStorage uses mkTmpDirs + mkSystemdBind).
let let
# tmpfiles rule: "type dir mode user group -" # tmpfiles rule: "type dir mode user group -"
mkTmpfile = mkTmpfile =
+89 -80
View File
@@ -7,7 +7,11 @@
}: }:
let let
panel = "${xlib.dirs.services-nodes-folder}/${xlib.device.hostname}/3x-ui"; panel = "${xlib.dirs.services-nodes-folder}/${xlib.device.hostname}/3x-ui";
certDomain = xlib.services."3x-ui".certDomain or null; # Domain whose Let's Encrypt cert (at /var/lib/acme/<domain>/) gets mounted
# read-only into the 3x-ui container so the panel can terminate TLS itself.
# Null when 3x-ui serves plain HTTP and TLS is terminated by an upstream
# nginx.
certDomain = config.host."3x-ui".certDomain;
certMounts = certMounts =
if certDomain == null then if certDomain == null then
[ ] [ ]
@@ -28,94 +32,99 @@ let
]; ];
# VDS-only: nginx stream forwards host:443 → 127.0.0.1:15380 → # VDS-only: nginx stream forwards host:443 → 127.0.0.1:15380 →
# container:443, so Xray sees its REALITY inbound on port 443. # container:443, so Xray sees its REALITY inbound on port 443.
realityPorts = lib.optional xlib.services."3x-ui".reality443Forwarding "127.0.0.1:15380:443/tcp"; realityPorts = lib.optional config.host."3x-ui".reality443Forwarding "127.0.0.1:15380:443/tcp";
in in
{ {
virtualisation = { # `host."3x-ui"` options are declared in modules/options.nix: they are set
podman = { # by modules/server and modules/vds, so this module cannot be the only place
enable = true; # that knows they exist.
autoPrune = { config = {
virtualisation = {
podman = {
enable = true; enable = true;
flags = [ "--all" ]; autoPrune = {
}; enable = true;
dockerCompat = true; flags = [ "--all" ];
};
oci-containers = {
backend = "podman";
containers."3xui_app" = {
image = "ghcr.io/mhsanaei/3x-ui:latest";
environment = {
"XRAY_VMESS_AEAD_FORCED" = "false";
"XUI_ENABLE_FAIL2BAN" = "true";
"TZ" = "Europe/Moscow";
}; };
volumes = [ dockerCompat = true;
"${panel}/cert/:/root/cert:rw"
"${panel}/db/:/etc/x-ui:rw"
]
++ certMounts;
log-driver = "journald";
# Adding a new inbound through the 3x-ui panel on a port outside
# the 14380-15379 range requires extending basePorts and rebuilding.
ports = basePorts ++ realityPorts;
}; };
}; oci-containers = {
}; backend = "podman";
containers."3xui_app" = {
systemd = { image = "ghcr.io/mhsanaei/3x-ui:latest";
services = { environment = {
"podman-3xui_app" = { "XRAY_VMESS_AEAD_FORCED" = "false";
serviceConfig.Restart = lib.mkOverride 90 "always"; "XUI_ENABLE_FAIL2BAN" = "true";
partOf = [ "podman-compose-3x-ui-root.target" ]; "TZ" = "Europe/Moscow";
wantedBy = [ "podman-compose-3x-ui-root.target" ]; };
}; volumes = [
"podman-update-3xui_app" = { "${panel}/cert/:/root/cert:rw"
path = [ pkgs.podman ]; "${panel}/db/:/etc/x-ui:rw"
serviceConfig = { ]
Type = "oneshot"; ++ certMounts;
TimeoutSec = 300; log-driver = "journald";
# Adding a new inbound through the 3x-ui panel on a port outside
# the 14380-15379 range requires extending basePorts and rebuilding.
ports = basePorts ++ realityPorts;
}; };
script = ''
podman pull ghcr.io/mhsanaei/3x-ui:latest
systemctl restart podman-3xui_app.service
'';
}; };
}; };
# Starts/stops together with all 3x-ui compose resources.
targets."podman-compose-3x-ui-root" = {
unitConfig.Description = "Root target generated by compose2nix.";
wantedBy = [ "multi-user.target" ];
};
# timers."podman-update-3xui_app" = {
# wantedBy = [ "timers.target" ];
# timerConfig = {
# OnCalendar = "weekly";
# Persistent = true;
# };
# };
tmpfiles.rules = [
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-mnt-folder "0755" "root" "root")
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-nodes-folder "0755" "root" "root")
(xlib.helpers.mkTmpfile "d" "${xlib.dirs.services-nodes-folder}/${xlib.device.hostname}" "0755"
"root"
"root"
)
(xlib.helpers.mkTmpfile "d" panel "0755" "root" "root")
(xlib.helpers.mkTmpfile "d" "${panel}/db" "0755" "root" "root")
(xlib.helpers.mkTmpfile "d" "${panel}/cert" "0755" "root" "root")
# Relabel panel dir for SELinux so containers can access it.
(xlib.helpers.mkTmpfile "Z" panel "0755" "root" "root")
];
};
# Enable container name DNS for all Podman networks. systemd = {
networking.firewall = { services = {
interfaces = "podman-3xui_app" = {
let serviceConfig.Restart = lib.mkOverride 90 "always";
matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; partOf = [ "podman-compose-3x-ui-root.target" ];
in wantedBy = [ "podman-compose-3x-ui-root.target" ];
{ };
"${matchAll}".allowedUDPPorts = [ 53 ]; "podman-update-3xui_app" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
TimeoutSec = 300;
};
script = ''
podman pull ghcr.io/mhsanaei/3x-ui:latest
systemctl restart podman-3xui_app.service
'';
};
}; };
# Starts/stops together with all 3x-ui compose resources.
targets."podman-compose-3x-ui-root" = {
unitConfig.Description = "Root target generated by compose2nix.";
wantedBy = [ "multi-user.target" ];
};
# timers."podman-update-3xui_app" = {
# wantedBy = [ "timers.target" ];
# timerConfig = {
# OnCalendar = "weekly";
# Persistent = true;
# };
# };
tmpfiles.rules = [
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-mnt-folder "0755" "root" "root")
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-nodes-folder "0755" "root" "root")
(xlib.helpers.mkTmpfile "d" "${xlib.dirs.services-nodes-folder}/${xlib.device.hostname}" "0755"
"root"
"root"
)
(xlib.helpers.mkTmpfile "d" panel "0755" "root" "root")
(xlib.helpers.mkTmpfile "d" "${panel}/db" "0755" "root" "root")
(xlib.helpers.mkTmpfile "d" "${panel}/cert" "0755" "root" "root")
# Relabel panel dir for SELinux so containers can access it.
(xlib.helpers.mkTmpfile "Z" panel "0755" "root" "root")
];
};
# Enable container name DNS for all Podman networks.
networking.firewall = {
interfaces =
let
matchAll = if !config.networking.nftables.enable then "podman+" else "podman*";
in
{
"${matchAll}".allowedUDPPorts = [ 53 ];
};
};
}; };
} }
+9 -23
View File
@@ -3,24 +3,15 @@ let
# NixOS-only modules. termux runs nix-on-droid (its own module system, # NixOS-only modules. termux runs nix-on-droid (its own module system,
# class = "nixOnDroid"): options like services.*, users.*, sops.*, disko.* # class = "nixOnDroid"): options like services.*, users.*, sops.*, disko.*
# and nixpkgs.overlays (flake assertion) do not exist there. # and nixpkgs.overlays (flake assertion) do not exist there.
moduleArgs = config: { #
inherit inputs; # `xlib` arrives as a module argument (see lib/mkSystem.nix) and is plain
xlib = config.xlib; # data, not a module option, so nothing here has to declare or set it.
};
defaultModule = defaultModule =
{ {
config,
deviceType,
lib, lib,
xlib, xlib,
... ...
}: }:
let
isDesktop = builtins.elem deviceType [
"primary"
"secondary"
];
in
{ {
imports = imports =
with inputs; with inputs;
@@ -37,33 +28,28 @@ let
self.homeConfigurations.default.nixosModule # default homeConfigurations self.homeConfigurations.default.nixosModule # default homeConfigurations
disko.nixosModules.disko # disko module disko.nixosModules.disko # disko module
] ]
++ lib.optional isDesktop ./desktop # desktop class: primary/secondary # desktop class: primary/secondary
++ lib.optional xlib.isDesktop ./desktop
# device-type module dir; "minimal" has no extra modules # device-type module dir; "minimal" has no extra modules
++ lib.optional (!isDesktop && deviceType != "minimal") (./. + "/${deviceType}"); ++ lib.optional (!xlib.isDesktop && xlib.device.type != "minimal") (./. + "/${xlib.device.type}");
nixpkgs.overlays = with inputs; [ nixpkgs.overlays = with inputs; [
self.nixosOverlays.default self.nixosOverlays.default
]; ];
networking.hostName = lib.mkDefault config.xlib.device.hostname; networking.hostName = lib.mkDefault xlib.device.hostname;
_module.args = moduleArgs config;
}; };
strictModule = strictModule =
{ {
config,
deviceType,
lib,
xlib, xlib,
... ...
}: }:
{ {
imports = with inputs; [ imports = [
# ./essentials # ./essentials
# ./users.nix # ./users.nix
./options.nix ./options.nix
(./. + "/${deviceType}") (./. + "/${xlib.device.type}")
# sops-nix.nixosModules.sops # sops-nix.nixosModules.sops
]; ];
_module.args = moduleArgs config;
}; };
in in
{ {
+25 -15
View File
@@ -3,21 +3,31 @@
lib, lib,
... ...
}: }:
lib.mkIf config.xlib.ssh.enable { {
services.openssh = { options.host.ssh = {
enable = true; enable = lib.mkOption {
allowSFTP = true; type = lib.types.bool;
openFirewall = lib.mkDefault false; default = false;
hostKeys = [ description = "Enable the SSH server with the shared config below.";
{ };
path = "/etc/ssh/id_ed25519"; };
type = "ed25519";
} config = lib.mkIf config.host.ssh.enable {
]; services.openssh = {
settings = { enable = true;
PasswordAuthentication = false; allowSFTP = true;
PermitRootLogin = "yes"; openFirewall = lib.mkDefault false;
UsePAM = true; hostKeys = [
{
path = "/etc/ssh/id_ed25519";
type = "ed25519";
}
];
settings = {
PasswordAuthentication = false;
PermitRootLogin = "yes";
UsePAM = true;
};
}; };
}; };
} }
+33 -101
View File
@@ -1,109 +1,41 @@
{ {
config,
lib, lib,
... ...
}: }:
let # Cross-module options: declared here, not in the module that reads them.
# Option factory for the xlib.dirs namespace #
mkDir = # An option belongs in this file when at least one context *sets* it while
default: description: # another module *reads* it — the reader cannot be the only place that knows
lib.mkOption { # the option exists. `modules/essentials/ssh.nix` does not belong here: it
type = lib.types.str; # declares and reads `host.ssh.enable` itself, within one module.
inherit default description;
};
helpers = import ../lib/xlib.nix { inherit lib; };
in
{ {
options = { options.host."3x-ui" = {
xlib = { # Domain whose Let's Encrypt cert (at /var/lib/acme/<domain>/)
device = { # gets mounted read-only into the 3x-ui container so the panel
type = lib.mkOption { # can terminate TLS itself. Set null if 3x-ui serves plain HTTP
type = lib.types.enum [ # and TLS is terminated by an upstream nginx.
"minimal" certDomain = lib.mkOption {
"primary" type = lib.types.nullOr lib.types.str;
"secondary" default = null;
"server" example = "pubray1.zeroq.su";
"vds" description = ''
"wsl" Domain whose LE cert should be mounted into the 3x-ui
"termux" container at /root/cert/fullchain.pem and key.pem.
]; '';
default = "minimal"; };
description = "Type of device for this host."; # Publish host:15380 → container:443. Only nodes that host an
}; # Xray REALITY inbound on container:443 need this (so nginx
username = lib.mkOption { # stream can forward TLS to Xray via 127.0.0.1:15380 while Xray
type = lib.types.str; # itself sees incoming connections on its configured port 443).
default = "oqyude"; # Set false on nodes that only run the 3x-ui panel.
description = "Username for host."; reality443Forwarding = lib.mkOption {
}; type = lib.types.bool;
hostname = lib.mkOption { default = false;
type = lib.types.str; description = ''
default = "nixos"; When true, publish host:15380 → container:443 so Xray
description = "Hostname..."; inside the container can serve REALITY on its real
}; configured port 443 (nginx stream forwards 443 → 15380).
}; '';
ssh = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable SSH server with the standard config.";
};
};
dirs = {
user-home = mkDir "/home/${config.xlib.device.username}" "User home directory.";
user-storage = mkDir "${config.xlib.dirs.user-home}/Storage" "User storage directory.";
archive-drive = mkDir "/mnt/archive" "Archive drive mount point.";
lamet-drive = mkDir "/mnt/lamet" "Lamet drive mount point.";
mobile-drive = mkDir "/mnt/mobile" "Mobile drive mount point.";
therima-drive = mkDir "/mnt/therima" "Therima drive mount point.";
vetymae-drive = mkDir "/mnt/vetymae" "Vetymae drive mount point.";
soptur-drive = mkDir "/mnt/soptur" "Soptur drive mount point.";
wsl-home = mkDir "/mnt/c/Users/${config.xlib.device.username}" "WSL home directory.";
wsl-storage = mkDir "${config.xlib.dirs.wsl-home}/Storage" "WSL storage directory.";
server-home = mkDir "/home/${config.xlib.device.username}/External" "Server home directory.";
server-credentials = mkDir "${config.xlib.dirs.server-home}/Credentials/server" "Server credentials directory.";
storage = mkDir "${config.xlib.dirs.server-home}/Storage" "General storage directory.";
calibre-library = mkDir "${config.xlib.dirs.server-home}/Books-Library" "Calibre library directory.";
music-library = mkDir "${config.xlib.dirs.user-home}/Music" "Music library directory.";
services-folder = mkDir "${config.xlib.dirs.server-home}/Services" "All services folder.";
services-mnt-folder = mkDir "/mnt/services" "All services folder.";
services-nodes-folder = mkDir "${config.xlib.dirs.services-mnt-folder}/nodes" "All nodes folder.";
postgresql-folder = mkDir "${config.xlib.dirs.services-mnt-folder}/postgresql" "PostgreSQL service folder.";
};
helpers = lib.mkOption {
type = lib.types.anything;
default = helpers;
description = "Shared helper functions (see lib/xlib.nix).";
};
services."3x-ui" = {
# Domain whose Let's Encrypt cert (at /var/lib/acme/<domain>/)
# gets mounted read-only into the 3x-ui container so the panel
# can terminate TLS itself. Set null if 3x-ui serves plain HTTP
# and TLS is terminated by an upstream nginx.
certDomain = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "pubray1.zeroq.su";
description = ''
Domain whose LE cert should be mounted into the 3x-ui
container at /root/cert/fullchain.pem and key.pem.
'';
};
# Publish host:15380 → container:443. Only nodes that host an
# Xray REALITY inbound on container:443 need this (so nginx
# stream can forward TLS to Xray via 127.0.0.1:15380 while Xray
# itself sees incoming connections on its configured port 443).
# Set false on nodes that only run the 3x-ui panel.
reality443Forwarding = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
When true, publish host:15380 → container:443 so Xray
inside the container can serve REALITY on its real
configured port 443 (nginx stream forwards 443 → 15380).
'';
};
};
}; };
}; };
} }
+1 -1
View File
@@ -50,7 +50,7 @@
# there are other vhosts on the same port). Cert is still mounted in # there are other vhosts on the same port). Cert is still mounted in
# case 3x-ui is later reconfigured to terminate TLS itself (e.g. for # case 3x-ui is later reconfigured to terminate TLS itself (e.g. for
# direct node-API access); nginx doesn't have to use it. # direct node-API access); nginx doesn't have to use it.
xlib.services."3x-ui".certDomain = "x.zeroq.su"; host."3x-ui".certDomain = "x.zeroq.su";
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
(xlib.helpers.mkTmpfile "d" "/mnt" "0755" "root" "root") (xlib.helpers.mkTmpfile "d" "/mnt" "0755" "root" "root")
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-mnt-folder "0755" "root" "root") (xlib.helpers.mkTmpfile "d" xlib.dirs.services-mnt-folder "0755" "root" "root")
-2
View File
@@ -37,8 +37,6 @@ let
); );
in in
{ {
xlib.device.username = "oqyude";
users = { users = {
mutableUsers = false; mutableUsers = false;
users = { users = {
+1 -1
View File
@@ -14,7 +14,7 @@
]; ];
# VDS hosts the public-facing Xray REALITY inbound on container:443, # VDS hosts the public-facing Xray REALITY inbound on container:443,
# fronted by nginx stream on host:443 → host:15380 → container:443. # fronted by nginx stream on host:443 → host:15380 → container:443.
xlib.services."3x-ui" = { host."3x-ui" = {
certDomain = "pubray1.zeroq.su"; certDomain = "pubray1.zeroq.su";
reality443Forwarding = true; reality443Forwarding = true;
}; };