Compare commits

150 Commits
Author SHA1 Message Date
oqyude 509fd3dde0 kokoro-tts 2026-10-03 01:03:50 +03:00
oqyude 958247b22c soft coding 2026-10-02 23:05:00 +03:00
oqyude c05cc88843 restructuring 2026-10-01 15:14:37 +03:00
oqyude d49fd5a358 big refactoring 2026-10-01 14:16:17 +03:00
oqyude 417c7abda6 hide ports 2026-09-26 16:07:37 +03:00
oqyude ac561815ed 3x-ui fix 2026-09-24 22:49:52 +03:00
oqyudeandSisyphus 2be5b168ac tape-rotation fix
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-09-24 17:21:58 +03:00
oqyude eddf44fb02 tape-rotation res 2026-09-24 15:38:24 +03:00
oqyude caeb04142d onlyoffice regress 2026-09-23 23:17:39 +03:00
oqyude 1db2b0955b nextcloud fix 2026-09-23 22:23:13 +03:00
oqyude a8ddf9b8dc changes 2026-09-23 22:16:04 +03:00
oqyude bc3566d80e nextcloud35 2026-09-23 22:14:08 +03:00
oqyude 0fdf6c965c tape-rotation freezed 2026-09-23 22:05:13 +03:00
oqyude 5bccf7586d nix flake update 2026-09-23 22:03:40 +03:00
oqyude 2912581b99 tape rotation added 2026-09-23 21:58:39 +03:00
oqyude 72b4bdfbd8 glances fix 2026-09-22 18:31:00 +03:00
oqyude 44b85e1ebc cleaning 2026-09-22 18:05:10 +03:00
oqyude b57ca3eedf 3x-ui next 2026-09-16 16:09:51 +03:00
oqyude 309644eab2 fixes 2026-09-15 21:22:33 +03:00
oqyude ba5a2b378e nix flake update 2026-09-15 21:22:27 +03:00
oqyude 7441e7f98a 3x-ui regress 2026-09-15 00:54:31 +03:00
oqyude 99b5a8f1eb jray upd 2026-09-08 21:58:51 +03:00
oqyude 1c3a524b42 iperf3 added 2026-09-08 21:37:38 +03:00
oqyude be064aca66 nix flake update 2026-09-02 23:32:34 +03:00
oqyude 839b97d01a justray and usbtree 2026-09-02 17:08:18 +03:00
oqyude 482d32e1a6 microfix 2026-09-02 13:35:58 +03:00
oqyude e1d276097d refactoring 2026-08-29 04:05:38 +03:00
oqyude 7f5ea81f37 3x-ui: make module generic via xlib.services.3x-ui options
The 3x-ui container config was hardcoded for vds: it mounted the LE
cert for pubray1.zeroq.su and published host:15380→container:443 for
Xray REALITY. The server imports the same module but for x.zeroq.su
(no REALITY inbound, no cert needed by 3x-ui itself yet).

Add two options so each device picks what it needs:
  - xlib.services.3x-ui.certDomain: domain whose LE cert is mounted
    at /root/cert/{fullchain,key}.pem. null means no cert mount.
  - xlib.services.3x-ui.reality443Forwarding: when true, also publish
    host:15380→container:443 for nginx stream SNI-routed REALITY.

vds sets both. Server sets only certDomain (kept harmless; nginx
still terminates TLS for x.zeroq.su, so the mounted cert is unused
until/unless 3x-ui is reconfigured to terminate TLS itself).
2026-08-28 01:17:59 +03:00
oqyude 11af2c150a vds: drop pubrayx1.zeroq.su from SNI map — Xray now served under pubray1
All Xray REALITY clients already connect to VDS_IP via
pubray1.zeroq.su (or any of its subdomains). Removing the explicit
pubrayx1.zeroq.su → xray rule means the default route catches it.
This way we only have to publish one domain (pubray1.zeroq.su)
in subscriptions instead of two.

Companion change in x-ui.db (separate runbook step): subURI set
to https://pubray1.zeroq.su/subs/ so regenerated subscriptions
emit URLs under pubray1.zeroq.su, not x.zeroq.su.
2026-08-28 00:56:28 +03:00
oqyude 26e53e96bd vds: SNI-route TLS on 443 to Xray (15380→443) and 3x-ui panel (2049)
nginx stream + ssl_preread reads the ClientHello SNI and forwards the
raw TCP stream (no TLS termination) to either:
  - 3x-ui panel on 127.0.0.1:2049 (SNI=pubray1.zeroq.su)
  - Xray on 127.0.0.1:15380 (SNI=pubrayx1.zeroq.su or default)

podman maps host:15380 → container:443 so Xray inside sees the client
on port 443 (matching its REALITY config) even though the host-side
port from podman's perspective is 15380. Host:2049 still maps to
container:2049 — 3x-ui now terminates TLS itself using the Let's
Encrypt cert mounted from /var/lib/acme/pubray1.zeroq.su/.

x-ui.db: webCertFile, webKeyFile and webDomain set so the panel
answers HTTPS on 2049. nginx no longer owns a server block on 443 —
only an ACME-only vhost for cert renewal.

REALITY inbound on container:443 still needs to be created via the
panel UI (the xrayTemplateConfig doesn't have it yet). The host-side
and routing plumbing is ready for it.
2026-08-28 00:36:54 +03:00
oqyude 0c2b45ea6f Revert "vds/nginx: forward real client IP to 3x-ui"
This reverts commit 2cd636b6d4.
2026-08-28 00:12:14 +03:00
oqyude 2cd636b6d4 vds/nginx: forward real client IP to 3x-ui
With podman bridge networking, 3x-ui no longer sees the actual
client IP — it sees the bridge gateway. Without explicit
proxy_set_header directives, subscription URLs, geo-rules, logs
and fail2ban will all treat every request as coming from the same
IP.

Apply Host/X-Real-IP/X-Forwarded-For/X-Forwarded-Proto to all
3x-ui locations so the panel keeps working as if it were on
host network.
2026-08-27 23:28:41 +03:00
oqyude 2bc02c316d podman changes 2026-08-27 23:21:18 +03:00
oqyude 0bbb19b429 nix flake update 2026-08-24 01:33:25 +03:00
oqyude cbf731495a minecraft: use jdk25 for fabric 26.2 server 2026-08-12 00:22:53 +03:00
oqyude b933436a6e minecraft: use linkFarmFromDrvs for mods 2026-08-11 23:36:28 +03:00
oqyude 0585f234ba minecraft: add 26.2 mods (lithium/ferritecore/krypton) 2026-08-11 23:34:26 +03:00
oqyude 133db71db0 minecraft-server setup 2026-08-11 23:14:16 +03:00
oqyude 411c118500 br v4 2026-08-11 22:13:53 +03:00
oqyude 056e5895fe br v3 2026-08-11 04:03:42 +03:00
oqyude 843f0bafa1 br v2 2026-08-11 03:05:52 +03:00
oqyude 871fad26d4 big refactoring 2026-08-11 02:31:00 +03:00
oqyude cc12ab5bba refactoring 2026-08-10 03:36:19 +03:00
oqyude e66bbef553 3x-ui on server 2026-08-09 23:51:49 +03:00
oqyude 5b3da95fc2 path refactoring 2026-08-09 01:11:23 +03:00
oqyude 27d81a27e2 nix flake update 2026-08-08 19:24:19 +03:00
oqyude cedc856a02 server preparing migration 2026-08-08 19:24:14 +03:00
oqyude 5f6288bd15 unused code 2026-08-08 17:48:43 +03:00
oqyude 682ab4aa01 path moving 2026-08-07 20:40:27 +03:00
oqyude f61d45a279 termux-api: set CMAKE_POLICY_VERSION_MINIMUM=3.5
Upstream CMakeLists.txt declares cmake_minimum_required(3.0.0), but modern
CMake removed compatibility with < 3.5 and refuses to configure.
2026-08-07 19:32:08 +03:00
oqyude caad27900b termux: declarative ~/.ssh/config + termux-api package
- home/termux.nix: programs.ssh.settings with the 7 known hosts
  (replaces hand-copied ~/.ssh/config; ssh aliases z-s/z-st/z-o/z-ot
  removed, lamet/pubray-1 kept since they have no Host entry)
- modules/termux/termux-api.nix: build termux-api 0.59.1 (cmake,
  am resolved from PATH, shebangs fixed); adds termux-battery-status,
  termux-notification, termux-clipboard-*, etc. Needs the Termux:API
  Android app (com.termux.api from F-Droid) as the actual backend
- mobile.nix: enable android-integration.am (termux-am backend for am)
2026-08-07 19:28:29 +03:00
oqyude 1841f9394c termux: create channels/nixpkgs under real symlink target (dangling symlink blocks mkdir -p) 2026-08-07 03:37:43 +03:00
oqyude d5d62393e3 termux: create .nix-defexpr/channels/nixpkgs/.keep via activation (home-manager cannot link into symlinked dir) 2026-08-07 03:35:19 +03:00
oqyude 58c6e5d48e termux: drop tailscaled (cannot run in proot, SELinux netlink), export SVDIR=/etc/service in zshenv 2026-08-07 03:31:06 +03:00
oqyude 566bc12f00 supervisor termux 2026-08-07 03:03:47 +03:00
oqyude bc9b2dc792 sshd setup for termux 2026-08-07 01:38:46 +03:00
oqyude af90756661 termux setup 2026-08-06 22:57:19 +03:00
oqyude 1856f9e4fd droid: set user.shell to zsh (nix-on-droid manages passwd, chsh is useless) 2026-08-06 18:59:37 +03:00
oqyude 6b426eaa55 add termux device type with shared home-manager userspace module 2026-08-06 17:53:11 +03:00
oqyude 8434688515 nix-on-droid testing branch + lock 2026-08-06 16:19:48 +03:00
oqyude 2a8b15ce01 test 2026-08-06 14:42:27 +03:00
oqyude 30bf6f8da6 gitignore update 2026-08-06 11:53:51 +03:00
oqyude c846fa2321 add nix-on-droid epral config 2026-08-05 16:23:16 +03:00
oqyude 38948e0462 small changes 2026-08-05 11:57:07 +03:00
oqyude c9b15dea59 power settings for server 2026-08-04 11:26:31 +03:00
oqyude 868fa7cdd7 nix flake update 2026-08-03 11:19:04 +03:00
oqyude cb502e8972 coredns server fix 2026-07-31 10:52:24 +03:00
oqyude 5739ccfcaf nextcloud changes 2026-07-28 03:02:52 +03:00
oqyude 1f1b6efdf0 nix flake update 2026-07-25 23:51:36 +03:00
oqyude f1ac4662fd onlyoffice try to deny regress 2026-07-17 12:42:44 +03:00
oqyude 63c46c80ef navidrome setup 2026-07-17 12:32:29 +03:00
oqyude 521d922961 nextcloud update 2026-07-16 23:34:06 +03:00
oqyude e5e9dfd1de samba fixup 2026-07-16 17:33:19 +03:00
oqyude 867a3227b8 calibre fixup 2026-07-16 01:14:47 +03:00
oqyude 7e8cc45a85 nix flake update 2026-07-16 00:14:03 +03:00
oqyude 69c53ccd65 fixup for onlyoffice (disabling) 2026-07-16 00:12:52 +03:00
oqyude 536bdf801e homebox added 2026-07-15 23:08:18 +03:00
oqyude 9a2372caf8 nix flake update 2026-07-12 18:01:46 +03:00
oqyude 870f36ec9d opencode serve on su 2026-07-10 11:17:45 +03:00
oqyude 85ea78b4b8 varlib reconfig 2026-07-08 12:55:40 +03:00
oqyude 6079ccc25a nixpkgs unstable set 2026-07-07 12:19:07 +03:00
oqyude 5dd7a585a5 nix flake update 2026-07-06 01:56:06 +03:00
oqyude 7cbdd5860b proxy-suite added 2026-07-04 23:33:33 +03:00
oqyude f2740e87a0 node backup added 2026-07-04 22:54:38 +03:00
oqyude d8300035cf syncthing arpa 2026-07-03 21:15:55 +03:00
oqyude 1607482cb8 samba extend 2026-07-01 22:57:41 +03:00
oqyude 3ec5efb090 local dns test and chrony added 2026-06-29 23:03:17 +03:00
oqyude e2e0e57918 new era dns 2026-06-24 11:44:13 +03:00
oqyude 0893ad28e7 new era nextcloud 2026-06-24 03:15:07 +03:00
oqyude ee75c68ec3 dns fix 2026-06-24 02:30:55 +03:00
oqyude ba7b36f16e step-ca config 2026-06-24 00:15:03 +03:00
oqyude c77915d0d1 nix flake update 2026-06-21 10:43:21 +03:00
oqyude 86e74f585a dns-server setup 2026-06-17 12:25:24 +03:00
oqyude 3c3e3c75fb n8n removed 2026-06-15 22:35:21 +03:00
oqyude acd8b33a8b coredns added 2026-06-14 22:10:22 +03:00
oqyude 624b63bc02 n8n added 2026-06-14 19:05:58 +03:00
oqyude 544aafd919 step-ca added 2026-06-14 01:59:39 +03:00
oqyude 6468c6583e nix flake update 2026-06-13 00:11:38 +03:00
oqyude b2b4883627 try to setup gitea 2026-06-10 12:38:23 +03:00
oqyude ebd2e99066 try to fix onlyoffice
now its working in lan)
2026-06-09 23:13:35 +03:00
oqyude 7514df3df3 cpp tools in wsl 2026-06-08 21:56:04 +03:00
oqyude 8ca46a632c nix flake update 2026-06-05 16:50:35 +03:00
oqyude aee5162344 nextcloud for lan 2026-06-04 20:40:17 +03:00
oqyude b001652162 bentopdf added 2026-05-31 14:26:24 +03:00
oqyude e0e908c79d nix flake update 2026-05-30 18:15:23 +03:00
oqyude f6027f7b9a nix flake update 2026-05-25 20:18:58 +03:00
oqyude 4820c7d745 systemd units for rsync rewrite 2026-05-18 15:11:27 +03:00
oqyude 52e88c1da1 systemd-routine - prebuild 2026-05-18 14:19:51 +03:00
oqyude 98c923f98f nix flake update 2026-05-16 12:40:20 +03:00
oqyude cde8866383 win+space
староверим)
2026-05-06 13:01:22 +03:00
oqyude acf2452b84 beets env update 2026-05-06 12:39:54 +03:00
oqyude 81ab80c94a fixes 2026-05-05 20:30:00 +03:00
oqyude c752cb2e7f pcbu-desktop try 2026-05-04 20:23:20 +03:00
oqyude 397bf49326 nix-serve added 2026-05-04 09:19:53 +03:00
oqyude 2df6ee7c3a nix flake update and changed to nixos-unstable 2026-05-03 19:00:04 +03:00
oqyude 1d84fb7354 nix flake update 2026-05-02 11:27:32 +03:00
oqyude 86e20597a7 refact, beets 3.14py 2026-04-21 12:24:54 +03:00
oqyude 58d631c0fb something 2026-04-17 20:46:49 +03:00
oqyude da6aad4fcd beets changes 2026-04-17 12:57:49 +03:00
oqyude a319150b99 nix flake update 2026-04-17 11:56:10 +03:00
oqyude 94b7d30c02 syn ddos defence 2026-04-13 11:13:54 +03:00
oqyude 7f1f714e8c glances added 2026-04-11 12:54:52 +03:00
oqyude f5c6d40c89 systemd-mounts...
lix frozen-removed, rovr frozen-removed
2026-04-10 14:07:07 +03:00
oqyude fb1637c44e nix flake update 2026-04-10 11:31:20 +03:00
oqyude a5a2763f66 new domain 2026-04-10 10:57:20 +03:00
oqyude bcd4bcffd5 beets fixed 2026-04-07 01:05:23 +03:00
oqyude c17d01c3a1 nix flake update 2026-04-06 16:11:57 +03:00
oqyude 557351e27b remnawave setup pause 2026-04-06 15:57:45 +03:00
oqyude c4b52f942c try to setup peerix and removed 2026-04-06 15:53:31 +03:00
oqyude 4d54a3b6fb remnawave editing 2026-04-05 02:37:56 +03:00
oqyude c3f8acad12 remnawave init 2026-04-05 02:28:14 +03:00
oqyude cf77fa88bf n8n enable 2026-04-01 12:50:28 +03:00
oqyude efcb4232a5 try to setup onlyoffice 2026-03-31 01:47:42 +03:00
oqyude 5909a72654 sops and onlyoffice evolution 2026-03-30 15:50:00 +03:00
oqyude 7d731bd1c4 ref 2026-03-29 14:46:01 +03:00
oqyude 713bccc3b1 nix flake update 2026-03-29 12:57:26 +03:00
oqyude c8c7c68c04 some fix 2026-03-27 17:56:12 +03:00
oqyude 6297df804e nix flake update 2026-03-23 17:52:28 +03:00
oqyude 8797821d94 rovr package added 2026-03-16 23:22:18 +03:00
oqyude 6f278b36e7 disable unused 2026-03-16 18:22:17 +03:00
oqyude ce19d10585 try to setup tuckr 2026-03-16 18:21:24 +03:00
oqyude e7daeccb27 netdata enabled 2026-03-12 11:40:58 +03:00
oqyude be816fe3bd turn on swap 2026-03-10 15:43:16 +03:00
oqyude af373baecc refind is rofl
121

1

12

12

1

12

12

12

1

asd
2026-03-09 22:07:18 +03:00
oqyude efa1ca2f0f beets channel change 2026-03-09 20:06:00 +03:00
oqyude e36db0e4ed nix flake update 2026-03-09 19:44:12 +03:00
oqyude a24f20cefb unused flake inputs removed 2026-03-09 19:44:12 +03:00
oqyude 40d2d29055 refind bootloader appear 2026-03-09 19:44:12 +03:00
oqyude 3d3baf1780 try to setup fresh-editor, no result
1

1

1

1

1

12
2026-03-09 12:08:59 +03:00
oqyude f1a81a6408 Init 2026-03-09 10:50:12 +03:00
159 changed files with 5871 additions and 5008 deletions
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+2
View File
@@ -0,0 +1,2 @@
.vscode
.omo
+1 -1
View File
@@ -1 +1 @@
I'm a super newbie who just posted my stuff here. Now maybe simple newbie
I'm a super newbie who just posted my stuff here. Now maybe about intermediate
+13 -28
View File
@@ -1,30 +1,15 @@
{ inputs, ... }@flakeContext:
let
nixosModule =
{
config,
lib,
modulesPath,
pkgs,
xlib,
...
}:
{
imports = [
inputs.self.nixosModules.default
];
system = {
stateVersion = "26.05";
};
};
in
inputs.nixpkgs.lib.nixosSystem {
modules = [
nixosModule
# 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.
{
inputs,
...
}:
{
imports = [
inputs.self.nixosModules.default
];
system = "x86_64-linux";
specialArgs = {
deviceType = "minimal";
};
system.stateVersion = "26.05";
}
+71 -8
View File
@@ -1,12 +1,75 @@
{ inputs, ... }@flakeContext:
let
lib = inputs.nixpkgs.lib;
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
{
nixosConfigurations = {
default = import ./any.nix flakeContext; # default
atoridu = import ./mini-pc.nix flakeContext; # atoridu
rydiwo = import ./mini-laptop.nix flakeContext; # rydiwo
otreca = import ./vds.nix flakeContext; # vds
otreca-new = import ./vds-new.nix flakeContext; # vds-new
sapphira = import ./server.nix flakeContext; # sapphira
wsl = import ./wsl.nix flakeContext; # wsl
nixosConfigurations = lib.mapAttrs' (
name: spec: lib.nameValuePair name (mkHost name spec).system
) hosts;
# Per-host xlib values, for code that lives outside the module system
# (deploy, overlays, pkgs).
xlib = lib.mapAttrs' (name: spec: lib.nameValuePair name (mkHost name spec).xlib) hosts;
nixOnDroidConfigurations = {
epral = import ./mobile.nix flakeContext; # epral (Android device via nix-on-droid)
# Alias so a plain `nix-on-droid switch` from a local clone
# (~/.config/nix-on-droid) picks up the device config without `#epral`.
default = import ./mobile.nix flakeContext;
};
}
+1 -1
View File
@@ -18,7 +18,7 @@
};
};
swap = {
size = "2G";
size = "6G";
content = {
type = "swap";
};
+1 -1
View File
@@ -20,7 +20,7 @@
};
};
swap = {
size = "1G";
size = "4G";
content = {
type = "swap";
};
+5 -5
View File
@@ -14,11 +14,11 @@
boot = {
initrd = {
supportedFilesystems = [
"nfs"
"nfsv4"
"overlay"
];
# supportedFilesystems = [
# "nfs"
# "nfsv4"
# "overlay"
# ];
availableKernelModules = [
"nvme"
"xhci_pci"
+8 -3
View File
@@ -28,6 +28,7 @@
kernel = {
sysctl = {
"fs.inotify.max_user_watches" = "204800";
"net.ipv4.ip_forward" = 1;
};
};
kernelModules = [
@@ -51,9 +52,13 @@
};
};
# swapDevices = [
# { device = "/dev/disk/by-partlabel/disk-main-swap"; }
# ];
zramSwap = {
enable = true;
};
swapDevices = [
{ device = "/dev/disk/by-partlabel/disk-main-swap"; }
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
-23
View File
@@ -1,23 +0,0 @@
{
config,
lib,
pkgs,
modulesPath,
...
}:
{
fileSystems = {
"/" = {
device = lib.mkForce "/dev/disk/by-partlabel/disk-main-root"; # "/dev/disk/by-partlabel/disk-main-root";
fsType = "ext4";
};
};
# swapDevices = [
# { device = "/dev/disk/by-partlabel/disk-main-swap"; }
# ];
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
+7 -3
View File
@@ -13,9 +13,13 @@
};
};
# swapDevices = [
# { device = "/dev/disk/by-partlabel/disk-main-swap"; }
# ];
swapDevices = [
{ device = "/dev/disk/by-partlabel/disk-main-swap"; }
];
zramSwap = {
enable = true;
};
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+31 -137
View File
@@ -1,143 +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.
{
lib,
pkgs,
xlib,
inputs,
...
}@flakeContext:
let
nixosModule =
{
config,
lib,
pkgs,
xlib,
...
}:
{
xlib.device = {
type = "secondary";
hostname = "rydiwo";
};
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.dirs.lamet-drive}" = {
device = "/dev/disk/by-uuid/DC76BD3576BD116E";
fsType = "ntfs3";
options = [
"defaults"
"uid=1000"
"gid=1000"
"fmask=0000"
"dmask=0000"
"nofail"
];
};
hardware = {
bluetooth.enable = true;
};
networking = {
hostName = "${xlib.device.hostname}";
networkmanager.enable = true;
firewall.enable = false;
};
i18n = {
extraLocaleSettings = {
LC_ADDRESS = "ru_RU.UTF-8";
LC_IDENTIFICATION = "ru_RU.UTF-8";
LC_MEASUREMENT = "ru_RU.UTF-8";
LC_MONETARY = "ru_RU.UTF-8";
LC_NAME = "ru_RU.UTF-8";
LC_NUMERIC = "ru_RU.UTF-8";
LC_PAPER = "ru_RU.UTF-8";
LC_TELEPHONE = "ru_RU.UTF-8";
LC_TIME = "ru_RU.UTF-8";
};
};
services = {
xserver = {
videoDrivers = [
"nomodeset"
];
};
syncthing = {
enable = true;
systemService = true;
configDir = "${xlib.dirs.user-storage}/Syncthing/${config.system.name}";
dataDir = "${xlib.dirs.user-home}";
group = "users";
user = "${xlib.device.username}";
};
# 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;
# };
# };
# };
# };
thermald.enable = true;
earlyoom.enable = true;
openssh = {
enable = true;
allowSFTP = true;
hostKeys = [
{
path = "/etc/ssh/id_ed25519";
type = "ed25519";
}
];
settings = {
PasswordAuthentication = false;
PermitRootLogin = "yes";
UsePAM = true;
};
};
};
security = {
rtkit.enable = true;
};
hardware.intel-gpu-tools.enable = true;
system.stateVersion = "26.05";
};
in
inputs.nixpkgs.lib.nixosSystem {
modules = with inputs; [
nixosModule
}:
{
imports = with inputs; [
nixos-hardware.nixosModules.chuwi-minibook-x
./hardware/mini-laptop.nix
self.nixosModules.default
];
system = "x86_64-linux";
specialArgs = {
deviceType = "secondary";
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";
}
+78 -157
View File
@@ -1,163 +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.
{
lib,
pkgs,
xlib,
inputs,
...
}@flakeContext:
let
nixosModule =
{
config,
lib,
pkgs,
xlib,
...
}:
{
xlib.device = {
type = "primary";
hostname = "atoridu";
};
imports = with inputs; [
./hardware/mini-pc.nix
./disko/mini-pc.nix
./hardware/logitech.nix
self.nixosModules.default
];
fileSystems = {
"${xlib.dirs.therima-drive}" = {
enable = false;
device = "/dev/disk/by-uuid/C0A2DDEFA2DDEA44";
fsType = "ntfs3";
options = [
"defaults"
"uid=1000"
"gid=1000"
"fmask=0007"
"dmask=0007"
"nofail"
];
};
"${xlib.dirs.vetymae-drive}" = {
enable = false;
device = "/dev/disk/by-uuid/6408433908430A0E";
fsType = "ntfs3";
options = [
"defaults"
"uid=1000"
"gid=1000"
"fmask=0007"
"dmask=0007"
"nofail"
];
};
"${xlib.dirs.soptur-drive}" = {
enable = false;
device = "/dev/disk/by-uuid/C00C56E40C56D54E";
fsType = "ntfs3";
options = [
"defaults"
"uid=1000"
"gid=1000"
"fmask=0007"
"dmask=0007"
"nofail"
];
};
};
boot = {
kernelPackages = lib.mkDefault pkgs.linuxPackages_xanmod_stable;
#kernelParams = [ "usbcore.autosuspend=-1" ];
loader = {
systemd-boot.enable = lib.mkDefault true;
efi.canTouchEfiVariables = lib.mkDefault true;
};
};
hardware = {
bluetooth.enable = true;
};
# networking.firewall.allowedTCPPorts = [ ... ];
# networking.firewall.allowedUDPPorts = [ ... ];
networking = {
hostName = "${xlib.device.hostname}";
networkmanager.enable = true;
firewall.enable = false;
};
i18n = {
extraLocaleSettings = {
LC_ADDRESS = "ru_RU.UTF-8";
LC_IDENTIFICATION = "ru_RU.UTF-8";
LC_MEASUREMENT = "ru_RU.UTF-8";
LC_MONETARY = "ru_RU.UTF-8";
LC_NAME = "ru_RU.UTF-8";
LC_NUMERIC = "ru_RU.UTF-8";
LC_PAPER = "ru_RU.UTF-8";
LC_TELEPHONE = "ru_RU.UTF-8";
LC_TIME = "ru_RU.UTF-8";
};
};
services = {
#logrotate.checkConfig = false;
#power-profiles-daemon.enable = false;
xserver = {
videoDrivers = [
"amdgpu"
];
};
syncthing = {
enable = true;
systemService = true;
configDir = "${xlib.dirs.user-storage}/Syncthing/${config.system.name}";
dataDir = "${xlib.dirs.user-home}";
group = "users";
user = "${xlib.device.username}";
};
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;
};
};
};
};
thermald.enable = true;
earlyoom.enable = true;
};
nixpkgs.config.pulseaudio = true;
security = {
rtkit.enable = true;
};
system.stateVersion = "26.05";
};
in
inputs.nixpkgs.lib.nixosSystem {
modules = [
nixosModule
}:
{
imports = with inputs; [
./hardware/mini-pc.nix
./disko/mini-pc.nix
./hardware/logitech.nix
self.nixosModules.default
];
system = "x86_64-linux";
specialArgs = {
deviceType = "primary";
# 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";
}
+128
View File
@@ -0,0 +1,128 @@
{
inputs,
...
}@flakeContext:
let
# Host: epral (Android device via nix-on-droid, aarch64-linux)
# Integrates with the base defaultModule (imports.self.nixosModules.default),
# which is trimmed for the "termux" device type: NixOS-only modules
# (essentials, users.nix, home-manager, sops-nix, disko, grub2-themes)
# and nixpkgs.overlays are skipped so it evaluates under nix-on-droid's
# module system (class = "nixOnDroid").
xlib = import ../lib/xlib.nix { lib = inputs.nixpkgs.lib; };
nixOnDroidModule =
{
lib,
pkgs,
xlib,
...
}:
{
imports = [
inputs.self.nixosModules.strict
];
# Login shell. nix-on-droid writes /etc/passwd from user.shell on every
# activation, so `chsh` is useless here — set it in nix instead.
# (default is bashInteractive)
user.shell = "${pkgs.zsh}/bin/zsh";
# SSH user (matches the `User` entries in the client's ~/.ssh/config).
# Default is "nix-on-droid"; home stays at the read-only
# /data/data/com.termux.nix/files/home either way.
user.userName = xlib.device.username;
# Minimal termux settings (nix-on-droid options only:
# environment.*, nix.*, time.*, user.*, system.*, android-integration.*)
# user.userName defaults to "nix-on-droid"; set it to override.
# user.home is read-only: /data/data/com.termux.nix/files/home
# Simply install just the packages
environment.packages = with pkgs; [
# User-facing stuff that you really really want to have
vim # or some other editor, e.g. nano or neovim
nano
# Some common stuff that people expect to have
bzip2
diffutils
findutils
git
gnugrep
gnupg
gnused
gnutar
gzip
hostname
man
ncurses
openssh
procps
psmisc # provides killall (attr `killall` was removed from nixpkgs)
treefmt
tzdata
unzip
util-linux # renamed from utillinux
zip
];
# Backup etc files instead of failing to activate generation if a file already exists in /etc
environment.etcBackupExtension = ".bak";
# Shared userspace home-manager config (same cozy shell as on NixOS hosts).
# nix-on-droid forces home.username / home.homeDirectory from user.*,
# so the strict module must not set them.
# xlib is injected via home-manager.extraSpecialArgs (the HM submodule
# does not inherit the nix-on-droid module args).
home-manager = {
useGlobalPkgs = true;
backupFileExtension = "hm-bak";
extraSpecialArgs = {
inherit xlib;
};
config =
{ ... }:
{
imports = [
../home/termux.nix
];
home.stateVersion = "24.05";
};
};
# Read the changelog before changing this value
system.stateVersion = "24.05";
# Set up nix for flakes
nix.extraOptions = ''
experimental-features = nix-command flakes
'';
# Set your time zone
time.timeZone = "Europe/Moscow";
android-integration.termux-setup-storage.enable = true;
# Provides `am` (termux-am) — required by termux-api's broadcast backend.
android-integration.am.enable = true;
};
in
inputs.nix-on-droid.lib.nixOnDroidConfiguration {
pkgs = import inputs.nixpkgs {
system = "aarch64-linux";
};
modules = [
nixOnDroidModule
];
extraSpecialArgs = {
# `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";
};
};
}
+71 -129
View File
@@ -1,137 +1,79 @@
{ inputs, ... }@flakeContext:
let
nixosModule =
{
config,
lib,
pkgs,
xlib,
...
}:
{
xlib.device = {
type = "server";
hostname = "sapphira";
};
# 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.
{
lib,
pkgs,
xlib,
inputs,
...
}:
{
imports = [
./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;
};
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;
};
# swapDevices = [
# { device = "/dev/disk/by-partlabel/disk-main-swap"; }
# ];
fileSystems = {
# External drive
"${xlib.dirs.server-home}" = {
device = "/dev/disk/by-uuid/37e53ebc-5343-a94d-9fe2-0ca39e13a8de";
fsType = "ext4";
};
# Archive drive
"/mnt/archive" = {
device = "/dev/disk/by-label/archive";
fsType = "exfat";
options = [
"nofail"
"uid=1000"
"gid=1000"
];
};
# Mobile SD-Card
"/mnt/mobile" = {
device = "/dev/disk/by-uuid/7EB1-DC99";
fsType = "exfat";
options = [
"nofail"
"uid=1000"
"gid=1000"
];
};
"${xlib.dirs.services-mnt-folder}" = {
device = "${xlib.dirs.services-folder}";
options = [
"bind"
"nofail"
# "uid=1000"
# "gid=1000"
# "fmask=0000"
# "dmask=0000"
];
};
};
systemd.tmpfiles.rules = [
"z ${xlib.dirs.services-mnt-folder} 0777 root root -"
];
services = {
power-profiles-daemon.enable = lib.mkForce false;
earlyoom.enable = true;
auto-cpufreq.enable = false;
throttled.enable = true;
journald = {
extraConfig = ''
SystemMaxUse=512M
'';
};
openssh = {
enable = true;
allowSFTP = true;
hostKeys = [
{
path = "/etc/ssh/id_ed25519";
type = "ed25519";
}
];
settings = {
PasswordAuthentication = false;
PermitRootLogin = "yes";
UsePAM = true;
};
};
};
networking = {
hostName = "${xlib.device.hostname}";
networkmanager.enable = true;
firewall.enable = false;
};
system = {
stateVersion = "25.05";
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";
};
};
in
inputs.nixpkgs.lib.nixosSystem {
modules = [
nixosModule
systemd.tmpfiles.rules = [
"z ${xlib.dirs.services-mnt-folder} 0777 root root -"
];
system = "x86_64-linux";
specialArgs = {
deviceType = "server";
host.ssh.enable = true;
networking = {
networkmanager.enable = true;
firewall.enable = false;
# nameservers = [
# "192.168.1.1"
# "127.0.0.1"
# ];
};
system = {
stateVersion = "25.05";
};
}
-177
View File
@@ -1,177 +0,0 @@
{ inputs, ... }@flakeContext:
let
nixosModule =
{
config,
lib,
modulesPath,
pkgs,
xlib,
...
}:
{
xlib.device = {
type = "vds-new";
hostname = "otreca-new";
};
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
(modulesPath + "/profiles/qemu-guest.nix")
./disko/vds.nix
./hardware/vds.nix
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;
};
};
services = {
earlyoom.enable = true;
journald = {
extraConfig = ''
SystemMaxUse=512M
'';
};
samba = {
enable = true;
openFirewall = true;
settings = {
global = {
"invalid users" = [ ];
"passwd program" = "/run/wrappers/bin/passwd %u";
security = "user";
};
nixos = {
"path" = "/etc/nixos";
"browseable" = "yes";
"read only" = "no";
"valid users" = "${xlib.device.username}";
"guest ok" = "no";
"writable" = "yes";
"create mask" = 755;
"directory mask" = 755;
"force user" = "${xlib.device.username}";
"force group" = "users";
};
root = {
"path" = "/";
"browseable" = "yes";
"read only" = "no";
"valid users" = "${xlib.device.username}";
"guest ok" = "no";
"writable" = "yes";
#"create mask" = 0644;
#"directory mask" = 0644;
"force user" = "root";
"force group" = "root";
};
"${xlib.device.username}" = {
"path" = "/home/${xlib.device.username}";
"browseable" = "yes";
"read only" = "no";
"valid users" = "${xlib.device.username}";
"guest ok" = "no";
"writable" = "yes";
"create mask" = 700;
"directory mask" = 700;
"force user" = "${xlib.device.username}";
"force group" = "users";
};
};
};
openssh = {
enable = true;
allowSFTP = true;
openFirewall = true;
hostKeys = [
{
path = "/etc/ssh/id_ed25519";
type = "ed25519";
}
];
settings = {
PasswordAuthentication = false;
PermitRootLogin = "yes";
UsePAM = true;
};
};
tailscale = {
enable = true;
openFirewall = true;
};
};
networking = {
nameservers = [
"1.1.1.1"
"8.8.8.8"
"2001:4860:4860::8844"
"2001:4860:4860::8888"
"2606:4700:4700::1111"
"2606:4700:4700::1001"
];
hostName = "${xlib.device.hostname}";
networkmanager.enable = true;
tempAddresses = "disabled";
dhcpcd = {
enable = true;
IPv6rs = true;
};
firewall = {
enable = true;
allowPing = true;
};
enableIPv6 = true;
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";
};
};
in
inputs.nixpkgs.lib.nixosSystem {
modules = [
nixosModule
];
system = "x86_64-linux";
specialArgs = {
deviceType = "vds-new";
};
}
+112 -172
View File
@@ -1,177 +1,117 @@
{ inputs, ... }@flakeContext:
let
nixosModule =
{
config,
lib,
modulesPath,
pkgs,
xlib,
...
}:
{
xlib.device = {
type = "vds";
hostname = "otreca";
};
# 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.
{
lib,
modulesPath,
pkgs,
xlib,
inputs,
...
}:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
(modulesPath + "/profiles/qemu-guest.nix")
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
(modulesPath + "/profiles/qemu-guest.nix")
./disko/vds.nix
./hardware/vds.nix
./disko/vds.nix
./hardware/vds.nix
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;
};
};
services = {
earlyoom.enable = true;
journald = {
extraConfig = ''
SystemMaxUse=512M
'';
};
samba = {
enable = true;
openFirewall = true;
settings = {
global = {
"invalid users" = [ ];
"passwd program" = "/run/wrappers/bin/passwd %u";
security = "user";
};
nixos = {
"path" = "/etc/nixos";
"browseable" = "yes";
"read only" = "no";
"valid users" = "${xlib.device.username}";
"guest ok" = "no";
"writable" = "yes";
"create mask" = 755;
"directory mask" = 755;
"force user" = "${xlib.device.username}";
"force group" = "users";
};
root = {
"path" = "/";
"browseable" = "yes";
"read only" = "no";
"valid users" = "${xlib.device.username}";
"guest ok" = "no";
"writable" = "yes";
#"create mask" = 0644;
#"directory mask" = 0644;
"force user" = "root";
"force group" = "root";
};
"${xlib.device.username}" = {
"path" = "/home/${xlib.device.username}";
"browseable" = "yes";
"read only" = "no";
"valid users" = "${xlib.device.username}";
"guest ok" = "no";
"writable" = "yes";
"create mask" = 700;
"directory mask" = 700;
"force user" = "${xlib.device.username}";
"force group" = "users";
};
};
};
openssh = {
enable = true;
allowSFTP = true;
openFirewall = true;
hostKeys = [
{
path = "/etc/ssh/id_ed25519";
type = "ed25519";
}
];
settings = {
PasswordAuthentication = false;
PermitRootLogin = "yes";
UsePAM = true;
};
};
tailscale = {
enable = true;
openFirewall = true;
};
};
networking = {
nameservers = [
"1.1.1.1"
"8.8.8.8"
"2001:4860:4860::8844"
"2001:4860:4860::8888"
"2606:4700:4700::1111"
"2606:4700:4700::1001"
];
hostName = "${xlib.device.hostname}";
networkmanager.enable = true;
tempAddresses = "disabled";
dhcpcd = {
enable = true;
IPv6rs = true;
};
firewall = {
enable = true;
allowPing = true;
};
enableIPv6 = true;
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";
};
};
in
inputs.nixpkgs.lib.nixosSystem {
modules = [
nixosModule
inputs.self.nixosModules.default
];
system = "x86_64-linux";
specialArgs = {
deviceType = "vds";
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 -100
View File
@@ -1,103 +1,39 @@
{ inputs, ... }@flakeContext:
let
nixosModule =
{
config,
lib,
pkgs,
modulesPath,
xlib,
...
}:
{
xlib.device = {
type = "wsl";
hostname = "wsl";
};
imports = [
inputs.nixos-wsl.nixosModules.default
inputs.self.nixosModules.default
];
#zramSwap.enable = true;
services = {
journald = {
extraConfig = ''
SystemMaxUse=512M
'';
};
earlyoom.enable = true;
};
hardware = {
graphics.enable = true;
# amdgpu.opencl.enable = true;
# amdgpu.amdvlk.enable = true;
};
networking = {
# nameservers = [
# "1.1.1.1"
# "8.8.8.8"
# "2001:4860:4860::8844"
# "2001:4860:4860::8888"
# "2606:4700:4700::1111"
# "2606:4700:4700::1001"
# ];
hostName = "${xlib.device.hostname}";
# networkmanager.enable = true;
# tempAddresses = "disabled";
# dhcpcd = {
# enable = true;
# IPv6rs = true;
# };
firewall = {
enable = false;
allowPing = true;
};
enableIPv6 = true;
# interfaces.ens3 = {
# useDHCP = true;
# # ipv4.addresses = [
# # {
# # address = "31.57.158.109";
# # prefixLength = 24;
# # }
# # ];
# ipv6.addresses = [
# {
# address = "2a13:7c00:10:6:f816:3eff:fe36:fe1b";
# prefixLength = 64;
# }
# ];
# };
# # defaultGateway = {
# # address = "31.57.158.1";
# # interface = "ens3";
# # };
# defaultGateway6 = {
# address = "2a13:7c00:10:6::1";
# interface = "ens3";
# };
};
wsl = {
enable = true;
startMenuLaunchers = true;
useWindowsDriver = true;
defaultUser = config.xlib.device.username;
};
system.stateVersion = "24.11";
};
in
inputs.nixpkgs.lib.nixosSystem {
modules = [
nixosModule
# 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.
{
lib,
modulesPath,
pkgs,
xlib,
inputs,
...
}:
{
imports = [
inputs.nixos-wsl.nixosModules.default
inputs.self.nixosModules.default
];
system = "x86_64-linux";
specialArgs = {
deviceType = "wsl";
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 -3
View File
@@ -6,10 +6,11 @@ let
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";
vds = "otreca";
vds-new = "otreca-new";
mini-laptop = "rydiwo";
in
{
@@ -19,7 +20,6 @@ in
nodes = {
"${server}" = mkDeploy "${server}";
"${vds}" = mkDeploy "${vds}";
"${vds-new}" = mkDeploy "${vds-new}";
"${mini-laptop}" = mkDeploy "${mini-laptop}";
};
};
Generated
+305 -171
View File
@@ -1,26 +1,5 @@
{
"nodes": {
"compose2nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"onchg": "onchg"
},
"locked": {
"lastModified": 1768176895,
"narHash": "sha256-GvcYMsrvQ1yjehcKmnlniBQM8HP9U/v7qSvfnxj3VtA=",
"owner": "aksiksi",
"repo": "compose2nix",
"rev": "e36aecd3649f43d745a5f837bf91c27c4499e203",
"type": "github"
},
"original": {
"owner": "aksiksi",
"repo": "compose2nix",
"type": "github"
}
},
"deploy-rs": {
"inputs": {
"flake-compat": [
@@ -34,11 +13,11 @@
]
},
"locked": {
"lastModified": 1770019181,
"narHash": "sha256-hwsYgDnby50JNVpTRYlF3UR/Rrpt01OrxVuryF40CFY=",
"lastModified": 1789404474,
"narHash": "sha256-UXFQ7tFiwn8sPz0EV4CBB2PCf/ZiGIHWn/6MXk81Lxs=",
"owner": "serokell",
"repo": "deploy-rs",
"rev": "77c906c0ba56aabdbc72041bf9111b565cdd6171",
"rev": "e760371d631165e7d8de5b0dcf148e21ec4c16f0",
"type": "github"
},
"original": {
@@ -54,11 +33,11 @@
]
},
"locked": {
"lastModified": 1769524058,
"narHash": "sha256-zygdD6X1PcVNR2PsyK4ptzrVEiAdbMqLos7utrMDEWE=",
"lastModified": 1789770686,
"narHash": "sha256-uZkBR7yHdIKUFB5SZdfgh1qkGfI3XmYmI/lTiquxbck=",
"owner": "nix-community",
"repo": "disko",
"rev": "71a3fc97d80881e91710fe721f1158d3b96ae14d",
"rev": "725ea35e410ad83be4931d1bff7e090eacaf3563",
"type": "github"
},
"original": {
@@ -82,18 +61,21 @@
"type": "github"
}
},
"flake-utils": {
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1652776076,
"narHash": "sha256-gzTw/v1vj4dOVbpBSJX4J0DwUR6LIyXo7/SuuTJp1kM=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "04c1b180862888302ddfb2e3ad9eaa63afc60cf8",
"lastModified": 1788450739,
"narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
@@ -104,11 +86,11 @@
]
},
"locked": {
"lastModified": 1757136219,
"narHash": "sha256-tKU+vq34KHu/A2wD7WdgP5A4/RCmSD8hB0TyQAUlixA=",
"lastModified": 1788271742,
"narHash": "sha256-H4IIqM+fmq9t3ZPHGs9kiuoQzau9AhCGQBSfClqQ/44=",
"owner": "vinceliuice",
"repo": "grub2-themes",
"rev": "80dd04ddf3ba7b284a7b1a5df2b1e95ee2aad606",
"rev": "4c5a77125b93f833edc9bf7b14a899faa8ac79c6",
"type": "github"
},
"original": {
@@ -124,11 +106,11 @@
]
},
"locked": {
"lastModified": 1771037579,
"narHash": "sha256-NX5XuhGcsmk0oEII2PEtMRgvh2KaAv3/WWQsOpxAgR4=",
"lastModified": 1790128814,
"narHash": "sha256-6Gm9q+wW3E4Ey4F6wEbJAwaMsEK6hvCYfTW7yY64ZfA=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "05e6dc0f6ed936f918cb6f0f21f1dad1e4c53150",
"rev": "0b2f1129177f70c5f0f5d88bb53c49ca47d0bfc0",
"type": "github"
},
"original": {
@@ -137,56 +119,152 @@
"type": "github"
}
},
"musnix": {
"justray": {
"inputs": {
"flake-parts": [
"flake-parts"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1767232402,
"narHash": "sha256-li+h6crnhc5Zqs+M6pn7D7M0W9M63ECNennDjRgzioE=",
"owner": "musnix",
"repo": "musnix",
"rev": "d65f98e0b1f792365f1705653d7b2d266ceeff6e",
"lastModified": 1790165840,
"narHash": "sha256-nQ3uCXiUGTLD/UtuChc2XlStYg9a33PYrkDitmmqxW8=",
"owner": "luynrs",
"repo": "justray",
"rev": "4073f3fb223613e4d780dafad6f93b5c266061a2",
"type": "github"
},
"original": {
"owner": "musnix",
"repo": "musnix",
"owner": "luynrs",
"repo": "justray",
"type": "github"
}
},
"nix-pre-commit": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"compose2nix",
"onchg",
"nixpkgs"
]
},
"nfqws2-keenetic": {
"flake": false,
"locked": {
"lastModified": 1653259102,
"narHash": "sha256-XfCEu4zur/N2Dk4v8wFiQAgJ7bgNqPqwWp1vBXkeczM=",
"owner": "jmgilman",
"repo": "nix-pre-commit",
"rev": "6a99b2711c7eac9960939d8eb91e84322b22d50c",
"lastModified": 1789144514,
"narHash": "sha256-G+LvvXDqzYm8SOXNc3N+HIzvD9DR3J+WT+HHDdmjB0Y=",
"owner": "nfqws",
"repo": "nfqws2-keenetic",
"rev": "fa22c177b340e73d8b8a94b295af20e0228c4c22",
"type": "github"
},
"original": {
"owner": "jmgilman",
"repo": "nix-pre-commit",
"owner": "nfqws",
"repo": "nfqws2-keenetic",
"type": "github"
}
},
"nix-formatter-pack": {
"inputs": {
"nixpkgs": [
"nix-on-droid",
"nixpkgs"
],
"nmd": [
"nix-on-droid",
"nmd"
],
"nmt": "nmt"
},
"locked": {
"lastModified": 1705252799,
"narHash": "sha256-HgSTREh7VoXjGgNDwKQUYcYo13rPkltW7IitHrTPA5c=",
"owner": "Gerschtli",
"repo": "nix-formatter-pack",
"rev": "2de39dedd79aab14c01b9e2934842051a160ffa5",
"type": "github"
},
"original": {
"owner": "Gerschtli",
"repo": "nix-formatter-pack",
"type": "github"
}
},
"nix-minecraft": {
"inputs": {
"flake-compat": [
"flake-compat"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"nix-systems"
]
},
"locked": {
"lastModified": 1790137578,
"narHash": "sha256-luzO2Bo/RxUHqTPxBttSB1QVzM9Y5s+Iwpip6SjWdWo=",
"owner": "Infinidoge",
"repo": "nix-minecraft",
"rev": "2e6a1d1ceb4da6b6ffb3980bc7993b3d057cd4db",
"type": "github"
},
"original": {
"owner": "Infinidoge",
"repo": "nix-minecraft",
"type": "github"
}
},
"nix-on-droid": {
"inputs": {
"home-manager": [
"home-manager"
],
"nix-formatter-pack": "nix-formatter-pack",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-docs": "nixpkgs-docs",
"nixpkgs-for-bootstrap": "nixpkgs-for-bootstrap",
"nmd": "nmd"
},
"locked": {
"lastModified": 1772387862,
"narHash": "sha256-o7q9flWMCsFW2mkz8TQhvPUcwFczO7VX9I6U2u2vN4o=",
"owner": "nix-community",
"repo": "nix-on-droid",
"rev": "67b105336cb06b764366bfe241afa2352de6a926",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "testing",
"repo": "nix-on-droid",
"type": "github"
}
},
"nix-systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"nixos-hardware": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1770882871,
"narHash": "sha256-nw5g+xl3veea+maxJ2/81tMEA/rPq9aF1H5XF35X+OE=",
"lastModified": 1789978172,
"narHash": "sha256-FIRXajv1pPZ+l6On6ekkmcQ6le0Zi0ncRUoR3nB0vKA=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "af04cb78aa85b2a4d1c15fc7270347e0d0eda97b",
"rev": "9ebcb7766700d7e006d9505247bd7ce0426f4232",
"type": "github"
},
"original": {
@@ -206,11 +284,11 @@
]
},
"locked": {
"lastModified": 1770657009,
"narHash": "sha256-v/LA5ZSJ+JQYzMSKB4sySM0wKfsAqddNzzxLLnbsV/E=",
"lastModified": 1789164534,
"narHash": "sha256-DoYGPM6QpnYBLWj9gGw6ZwAzIX+HrAVov1BoT+8Jixo=",
"owner": "nix-community",
"repo": "NixOS-WSL",
"rev": "5b50ea1aaa14945d4794c80fcc99c4aa1db84d2d",
"rev": "72c92b11bb8289e6651c7fef29cc0a885fd6a255",
"type": "github"
},
"original": {
@@ -222,112 +300,103 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1770843696,
"narHash": "sha256-LovWTGDwXhkfCOmbgLVA10bvsi/P8eDDpRudgk68HA8=",
"lastModified": 1790046670,
"narHash": "sha256-MYiI+CzL0tuWgRPjGsKCDHqYs2T3OzMlMQWOYWG0qso=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2343bbb58f99267223bc2aac4fc9ea301a155a16",
"rev": "6774f7bc253789b113a4f39285dc0fa100abeacc",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-master": {
"nixpkgs-docs": {
"locked": {
"lastModified": 1771056776,
"narHash": "sha256-0l776LxthDY08ujQ1h83k9z6K5vBg1bGc415AWeFOOI=",
"lastModified": 1705957679,
"narHash": "sha256-Q8LJaVZGJ9wo33wBafvZSzapYsjOaNjP/pOnSiKVGHY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d22fe1660f1f1ccbd52c9d2c09e92fe3861dd691",
"rev": "9a333eaa80901efe01df07eade2c16d183761fa3",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "master",
"ref": "release-23.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"nixpkgs-for-bootstrap": {
"locked": {
"lastModified": 1770770419,
"narHash": "sha256-iKZMkr6Cm9JzWlRYW/VPoL0A9jVKtZYiU4zSrVeetIs=",
"lastModified": 1772047000,
"narHash": "sha256-7DaQVv4R97cii/Qdfy4tmDZMB2xxtyIvNGSwXBBhSmo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6c5e707c6b5339359a9a9e215c5e66d6d802fd7a",
"rev": "1267bb4920d0fc06ea916734c11b0bf004bbe17e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"rev": "1267bb4920d0fc06ea916734c11b0bf004bbe17e",
"type": "github"
}
},
"noctalia": {
"nixpkgs-lib": {
"locked": {
"lastModified": 1788057806,
"narHash": "sha256-DTQSMxzDWmT0zhguthvegnVkn7CFqGCv4IHCzk5ZUpM=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "596e2e3940e09b2abbeb03f75fa1828c57fcd72c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nmd": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
"nix-on-droid",
"nixpkgs-docs"
],
"scss-reset": "scss-reset"
},
"locked": {
"lastModified": 1771045170,
"narHash": "sha256-esBQIlClWRgYYvtYW27N79fCbOUkuFj3gxwJrb8WFX4=",
"owner": "noctalia-dev",
"repo": "noctalia-shell",
"rev": "92612c09a9dce53d5dd60e53f066160f1cdf13b4",
"type": "github"
"lastModified": 1705050560,
"narHash": "sha256-x3zzcdvhJpodsmdjqB4t5mkVW22V3wqHLOun0KRBzUI=",
"owner": "~rycee",
"repo": "nmd",
"rev": "66d9334933119c36f91a78d565c152a4fdc8d3d3",
"type": "sourcehut"
},
"original": {
"owner": "noctalia-dev",
"repo": "noctalia-shell",
"type": "github"
"owner": "~rycee",
"repo": "nmd",
"type": "sourcehut"
}
},
"nypkgs": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"nmt": {
"flake": false,
"locked": {
"lastModified": 1761401328,
"narHash": "sha256-1Mylp3ZHkft5Sg5VzMpRRvSNsuuO/Oj+cBqjkFoOnRg=",
"owner": "yunfachi",
"repo": "nypkgs",
"rev": "193c13630997d000e72e9ae6f6bfe9b71f5c4b3f",
"type": "github"
"lastModified": 1648075362,
"narHash": "sha256-u36WgzoA84dMVsGXzml4wZ5ckGgfnvS0ryzo/3zn/Pc=",
"owner": "rycee",
"repo": "nmt",
"rev": "d83601002c99b78c89ea80e5e6ba21addcfe12ae",
"type": "gitlab"
},
"original": {
"owner": "yunfachi",
"repo": "nypkgs",
"type": "github"
}
},
"onchg": {
"inputs": {
"nix-pre-commit": "nix-pre-commit",
"nixpkgs": [
"compose2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1720368454,
"narHash": "sha256-NUSw3G2gsQX8/G64/pDBb1oitM+x13m7nFRvpiI4a+s=",
"owner": "aksiksi",
"repo": "onchg-rs",
"rev": "c42b693d10920874b3644ef1502e33318409d69c",
"type": "github"
},
"original": {
"owner": "aksiksi",
"repo": "onchg-rs",
"type": "github"
"owner": "rycee",
"repo": "nmt",
"type": "gitlab"
}
},
"plasma-manager": {
@@ -340,11 +409,11 @@
]
},
"locked": {
"lastModified": 1770766818,
"narHash": "sha256-12RCFLyAedyMOdenUi7cN3ioJPEGjA/ZG1BLjugfUVs=",
"lastModified": 1785762349,
"narHash": "sha256-jZhZkzAwc7f3exzcTDJWP2WCAchCv0iNC3UF/QsahdQ=",
"owner": "nix-community",
"repo": "plasma-manager",
"rev": "44b928068359b7d2310a34de39555c63c93a2c90",
"rev": "a19a2a029fa180911bd89c554dca1616e10f4c1d",
"type": "github"
},
"original": {
@@ -353,29 +422,67 @@
"type": "github"
}
},
"proxy-suite": {
"inputs": {
"nfqws2-keenetic": "nfqws2-keenetic",
"nixpkgs": [
"nixpkgs"
],
"z2k": "z2k",
"zapret": "zapret"
},
"locked": {
"lastModified": 1790130850,
"narHash": "sha256-k7c+KGZmNLP0ZB9jnKKJUXUTvEJC8A6kHQmZlcN8kNQ=",
"owner": "FUFSoB",
"repo": "proxy-suite-flake",
"rev": "8f65fa9c255e9350fb09f3d3cc9054034918bf49",
"type": "github"
},
"original": {
"owner": "FUFSoB",
"repo": "proxy-suite-flake",
"type": "github"
}
},
"root": {
"inputs": {
"compose2nix": "compose2nix",
"deploy-rs": "deploy-rs",
"disko": "disko",
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"grub2-themes": "grub2-themes",
"home-manager": "home-manager",
"musnix": "musnix",
"justray": "justray",
"nix-minecraft": "nix-minecraft",
"nix-on-droid": "nix-on-droid",
"nix-systems": "nix-systems",
"nixos-hardware": "nixos-hardware",
"nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs",
"nixpkgs-master": "nixpkgs-master",
"nixpkgs-stable": "nixpkgs-stable",
"noctalia": "noctalia",
"nypkgs": "nypkgs",
"plasma-manager": "plasma-manager",
"proxy-suite": "proxy-suite",
"sops-nix": "sops-nix",
"utils": "utils",
"zapret": "zapret",
"zeroq-credentials": "zeroq-credentials"
}
},
"scss-reset": {
"flake": false,
"locked": {
"lastModified": 1631450058,
"narHash": "sha256-muDlZJPtXDIGevSEWkicPP0HQ6VtucbkMNygpGlBEUM=",
"owner": "andreymatin",
"repo": "scss-reset",
"rev": "0cf50e27a4e95e9bb5b1715eedf9c54dee1a5a91",
"type": "github"
},
"original": {
"owner": "andreymatin",
"repo": "scss-reset",
"type": "github"
}
},
"sops-nix": {
"inputs": {
"nixpkgs": [
@@ -383,11 +490,11 @@
]
},
"locked": {
"lastModified": 1770683991,
"narHash": "sha256-xVfPvXDf9QN3Eh9dV+Lw6IkWG42KSuQ1u2260HKvpnc=",
"lastModified": 1789890976,
"narHash": "sha256-GKwH3zpy7tartuJMG0Rv/xUsdetG1QLmTVv8UKgJLmA=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "8b89f44c2cc4581e402111d928869fe7ba9f7033",
"rev": "7214124c20c1542c90deb54af50e2f53ae02711f",
"type": "github"
},
"original": {
@@ -396,24 +503,11 @@
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
"systems": [
"nix-systems"
]
},
"locked": {
"lastModified": 1731533236,
@@ -429,18 +523,58 @@
"type": "github"
}
},
"zapret": {
"z2k": {
"flake": false,
"locked": {
"lastModified": 1767430655,
"narHash": "sha256-f9PricXeNm3lG1tk2TepPPY+wxM5y0ezo1HSzNn4BQ8=",
"owner": "oqyude",
"repo": "zapret-easyflake",
"rev": "302e77aae5fc6030a9c3bcc781d6514d87b19d11",
"lastModified": 1789186266,
"narHash": "sha256-d9gg7s66P3pkN7d4l72ryaGC9Ayoqg4tbHaoDNbDTT4=",
"owner": "necronicle",
"repo": "z2k",
"rev": "7beb9754d65a2cafd9c1d26d382bcb61d453d5b3",
"type": "github"
},
"original": {
"owner": "oqyude",
"repo": "zapret-easyflake",
"owner": "necronicle",
"ref": "z2k-enhanced",
"repo": "z2k",
"type": "github"
}
},
"zapret": {
"inputs": {
"nixpkgs": [
"proxy-suite",
"nixpkgs"
],
"zapret-flowseal": "zapret-flowseal"
},
"locked": {
"lastModified": 1788726932,
"narHash": "sha256-MehJgJpN7BGMaDES9I0aj/YG6JkRlSj5RiZDZfclNpc=",
"owner": "kartavkun",
"repo": "zapret-discord-youtube",
"rev": "64a8ee76f4f2e4a8d2751cb732f13448ee1e4fcf",
"type": "github"
},
"original": {
"owner": "kartavkun",
"repo": "zapret-discord-youtube",
"type": "github"
}
},
"zapret-flowseal": {
"flake": false,
"locked": {
"lastModified": 1788121958,
"narHash": "sha256-WMpxbtA2OH340e4uuXR0tcUW0D6V9Kzs0KI1iKqkXBM=",
"owner": "Flowseal",
"repo": "zapret-discord-youtube",
"rev": "6cec828910d0809863205702182a3557d9d0e8c3",
"type": "github"
},
"original": {
"owner": "Flowseal",
"repo": "zapret-discord-youtube",
"type": "github"
}
},
+63 -39
View File
@@ -3,15 +3,12 @@
inputs = {
# My
zeroq-credentials.url = "git+ssh://git@github.com/oqyude/zeroq-credentials.git"; # flake of creds
zapret.url = "github:oqyude/zapret-easyflake"; # stupid flake of zapret
# nixpkgs
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
# nixpkgs-last-unstable.url = "github:NixOS/nixpkgs/6b4955211758ba47fac850c040a27f23b9b4008f";
# nixpkgs-calibre.url = "github:NixOS/nixpkgs/e6f23dc08d3624daab7094b701aa3954923c6bbb";
nixpkgs-master.url = "github:NixOS/nixpkgs/master";
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.11";
#nixpkgs-fingerprint.url = "github:NixOS/nixpkgs/nixos-24.11";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# nixpkgs-master.url = "github:NixOS/nixpkgs/master";
# nixpkgs-last-unstable.url = "github:NixOS/nixpkgs/3497aa5c9457a9d88d71fa93a4a8368816fbeeba";
# nixpkgs-fingerprint.url = "github:NixOS/nixpkgs/nixos-24.11";
# nix-community
nixos-wsl = {
@@ -21,6 +18,13 @@
nixpkgs.follows = "nixpkgs";
};
};
nix-on-droid = {
url = "github:nix-community/nix-on-droid/testing"; # testing branch, used on the device
inputs = {
nixpkgs.follows = "nixpkgs";
home-manager.follows = "home-manager";
};
};
deploy-rs = {
url = "github:serokell/deploy-rs";
inputs = {
@@ -29,21 +33,33 @@
utils.follows = "utils";
};
};
justray = {
url = "github:luynrs/justray";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-parts.follows = "flake-parts";
};
};
utils.url = "github:numtide/flake-utils";
utils = {
url = "github:numtide/flake-utils";
# flake-utils тянет systems (nix-systems/default) сам -> наследуем корневой, чтобы не плодить дубль-узел в flake.lock
inputs.systems.follows = "nix-systems";
};
flake-compat.url = "github:edolstra/flake-compat";
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
# nixos-facter-modules.url = "github:numtide/nixos-facter-modules";
# flake-utils.url = "github:numtide/flake-utils";
# flake-parts.url = "github:hercules-ci/flake-parts";
# nur = {
# url = "github:nix-community/NUR";
# inputs.nixpkgs.follows = "nixpkgs";
# };
noctalia = {
url = "github:noctalia-dev/noctalia-shell";
flake-parts.url = "github:hercules-ci/flake-parts";
nixos-hardware = {
url = "github:NixOS/nixos-hardware/master";
# без follows nixos-hardware лочит свой собственный nixpkgs (две копии в lock/store)
inputs.nixpkgs.follows = "nixpkgs";
};
nix-systems.url = "github:nix-systems/default";
# nixos-facter-modules.url = "github:numtide/nixos-facter-modules";
# flake-utils.url = "github:numtide/flake-utils";
# noctalia = {
# url = "github:noctalia-dev/noctalia-shell";
# inputs.nixpkgs.follows = "nixpkgs";
# };
home-manager = {
url = "github:nix-community/home-manager"; # flake:home-manager
inputs.nixpkgs.follows = "nixpkgs";
@@ -60,14 +76,30 @@
home-manager.follows = "home-manager";
};
};
proxy-suite = {
url = "github:FUFSoB/proxy-suite-flake";
inputs.nixpkgs.follows = "nixpkgs";
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
grub2-themes = {
url = "github:vinceliuice/grub2-themes";
inputs.nixpkgs.follows = "nixpkgs";
};
nix-minecraft = {
url = "github:Infinidoge/nix-minecraft";
inputs = {
flake-compat.follows = "flake-compat";
nixpkgs.follows = "nixpkgs";
systems.follows = "nix-systems";
};
};
# nix-index-database = {
# url = "github:nix-community/nix-index-database";
# inputs.nixpkgs.follows = "nixpkgs";
# };
compose2nix = {
url = "github:aksiksi/compose2nix";
inputs.nixpkgs.follows = "nixpkgs";
};
# extras
# nix-gaming.url = "github:fufexan/nix-gaming";
@@ -78,23 +110,15 @@
# flake-compat.follows = "flake-compat";
# };
# };
musnix = {
url = "github:musnix/musnix";
inputs.nixpkgs.follows = "nixpkgs";
};
grub2-themes = {
url = "github:vinceliuice/grub2-themes";
inputs.nixpkgs.follows = "nixpkgs";
};
nypkgs = {
# https://github.com/yunfachi/nypkgs
url = "github:yunfachi/nypkgs";
inputs.nixpkgs.follows = "nixpkgs";
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
# musnix = {
# url = "github:musnix/musnix";
# inputs.nixpkgs.follows = "nixpkgs";
# };
# nypkgs = {
# # https://github.com/yunfachi/nypkgs
# url = "github:yunfachi/nypkgs";
# inputs.nixpkgs.follows = "nixpkgs";
# };
# stylix = {
# url = "github:danth/stylix";
# inputs = {
+1 -1
View File
@@ -5,7 +5,7 @@
imports = [
./gramps.nix
./streamrip.nix
./v2rayn.nix
# ./v2rayn.nix
./yt-dlp.nix
];
}
-11
View File
@@ -4,18 +4,7 @@
xlib,
...
}:
let
streamripPath = "${xlib.dirs.wsl-storage}/streamrip";
in
{
xdg = {
configFile = {
"streamrip" = {
source = config.lib.file.mkOutOfStoreSymlink streamripPath;
target = "streamrip";
};
};
};
home.packages = [
pkgs.streamrip
];
-12
View File
@@ -1,21 +1,9 @@
{
config,
pkgs,
xlib,
...
}:
let
streamripPath = "${xlib.dirs.wsl-storage}/streamrip";
in
{
# xdg = {
# configFile = {
# "streamrip" = {
# source = config.lib.file.mkOutOfStoreSymlink streamripPath;
# target = "streamrip";
# };
# };
# };
home.packages = [
pkgs.yt-dlp-light
];
+40 -45
View File
@@ -9,58 +9,53 @@ let
...
}:
let
mkHomeModule = username: {
imports = [
(./. + "/${xlib.device.type}.nix")
];
home = {
username = username;
stateVersion = lib.mkDefault "25.05";
homeDirectory =
if username == "root" then lib.mkDefault "/${username}" else lib.mkDefault "/home/${username}";
enableNixpkgsReleaseCheck = false;
mkUser =
username:
{
imports ? [ ],
headless ? false,
}:
{
inherit imports;
home = {
username = username;
stateVersion = lib.mkDefault "26.05";
homeDirectory =
if username == "root" then lib.mkDefault "/${username}" else lib.mkDefault "/home/${username}";
enableNixpkgsReleaseCheck = false;
};
# Headless hosts: no GUI user dirs
xdg = lib.mkIf headless {
enable = true;
autostart.enable = true;
userDirs = {
enable = true;
createDirectories = false;
desktop = null;
documents = null;
download = null;
music = null;
pictures = null;
publicShare = null;
templates = null;
videos = null;
};
};
};
};
mkRootModule = username: {
home = {
username = username;
stateVersion = lib.mkDefault "25.05";
homeDirectory =
if username == "root" then lib.mkDefault "/${username}" else lib.mkDefault "/home/${username}";
enableNixpkgsReleaseCheck = false;
};
};
mkOthersModule = username: {
imports = [
(./. + "/others/${xlib.device.type}.nix")
];
home = {
username = username;
stateVersion = lib.mkDefault "25.05";
homeDirectory =
if username == "root" then lib.mkDefault "/${username}" else lib.mkDefault "/home/${username}";
enableNixpkgsReleaseCheck = false;
};
};
in
{
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
users = {
root = mkRootModule "root";
"${xlib.device.username}" = mkHomeModule xlib.device.username;
}
//
lib.optionalAttrs
(builtins.elem xlib.device.type [
"test"
#"secondary"
#"primary"
])
{
snity = mkOthersModule "snity";
};
root = mkUser "root" { };
"${xlib.device.username}" = mkUser xlib.device.username {
imports = [
(./. + "/${xlib.device.type}.nix")
];
headless = xlib.isHeadless;
};
};
sharedModules = [
inputs.plasma-manager.homeModules.plasma-manager
];
-28
View File
@@ -8,25 +8,6 @@
programs = {
mangohud.enable = true;
keepassxc.enable = true;
zed-editor = {
enable = false;
extensions = [
"nix"
];
userSettings = {
"telemetry" = {
"diagnostics" = false;
"metrics" = false;
};
"ui_font_size" = 20;
"buffer_font_size" = 26;
"theme" = {
"mode" = "system";
"light" = "Ayu Light";
"dark" = "Ayu Dark";
};
};
};
};
services = {
kdeconnect.enable = true;
@@ -34,12 +15,6 @@
};
home = {
packages = with pkgs; [
# Surfing
# (brave.override {
# commandLineArgs = [
# "--password-store=basic" # on purpose to make it break "--password-store=gnome-libsecret"
# ];
# })
brave
v2rayn
@@ -48,8 +23,6 @@
# amdgpu_top
vscodium
ayugram-desktop
# vesktop
# discord
gramps
kdePackages.filelight
localsend
@@ -75,7 +48,6 @@
# Games
#ludusavi
#prismlauncher
steam
#lutris
# AI
-52
View File
@@ -1,52 +0,0 @@
{
config,
lib,
pkgs,
xlib,
...
}:
let
symlinksPaths = {
"/home/oqyude/Games/PrismLaunchers" = "${config.home.homeDirectory}/Games/PrismLaunchers";
"${config.home.homeDirectory}/Games/PrismLaunchers/${config.home.username}" =
".local/share/PrismLauncher";
};
mkLinks = lib.mapAttrs' (sourcePath: targetPath: {
name = targetPath;
value.source = config.lib.file.mkOutOfStoreSymlink "${sourcePath}";
}) symlinksPaths;
in
{
imports = [
../minimal.nix
../modules/packages.nix
../modules/plasma-manager.nix
];
xdg = {
enable = true;
autostart.enable = true;
userDirs = {
enable = true;
createDirectories = true;
desktop = "${config.xdg.dataHome}/desktop";
documents = null;
download = "${config.home.homeDirectory}/Downloads";
music = "${config.home.homeDirectory}/Music";
pictures = "${config.home.homeDirectory}/Pictures";
publicShare = "${config.home.homeDirectory}/Misc/Public";
templates = null;
videos = "${config.home.homeDirectory}/Pictures/Videos";
};
};
home = {
file = mkLinks;
pointerCursor = {
enable = true;
x11.enable = true;
gtk.enable = true;
size = 24;
name = "Qogir";
package = pkgs.qogir-icon-theme;
};
};
}
-52
View File
@@ -1,52 +0,0 @@
{
config,
lib,
pkgs,
xlib,
...
}:
let
symlinksPaths = {
"/home/oqyude/Games/PrismLaunchers" = "${config.home.homeDirectory}/Games/PrismLaunchers";
"${config.home.homeDirectory}/Games/PrismLaunchers/${config.home.username}" =
".local/share/PrismLauncher";
};
mkLinks = lib.mapAttrs' (sourcePath: targetPath: {
name = targetPath;
value.source = config.lib.file.mkOutOfStoreSymlink "${sourcePath}";
}) symlinksPaths;
in
{
imports = [
../minimal.nix
../modules/packages.nix
../modules/plasma-manager.nix
];
xdg = {
enable = true;
autostart.enable = true;
userDirs = {
enable = true;
createDirectories = true;
desktop = "${config.xdg.dataHome}/desktop";
documents = null;
download = "${config.home.homeDirectory}/Downloads";
music = "${config.home.homeDirectory}/Music";
pictures = "${config.home.homeDirectory}/Pictures";
publicShare = "${config.home.homeDirectory}/Misc/Public";
templates = null;
videos = "${config.home.homeDirectory}/Pictures/Videos";
};
};
home = {
file = mkLinks;
pointerCursor = {
enable = true;
x11.enable = true;
gtk.enable = true;
size = 24;
name = "Qogir";
package = pkgs.qogir-icon-theme;
};
};
}
+1 -5
View File
@@ -8,12 +8,8 @@
let
symlinksPaths = {
# cfg
"${xlib.dirs.user-storage}/ssh/config" = ".ssh/config";
"${xlib.dirs.user-storage}/beets" = ".config/beets";
"${xlib.dirs.user-storage}/ludusavi" = ".config/ludusavi";
"${xlib.dirs.user-storage}/solaar" = ".config/solaar";
"${xlib.dirs.user-storage}/easyeffects" = ".config/easyeffects";
"${xlib.dirs.user-storage}/KeePassXC" = ".config/keepassxc";
"${xlib.dirs.user-storage}/v2rayN" = ".local/share/v2rayN";
"/etc/nixos" = "Configuration";
@@ -36,7 +32,7 @@ in
./modules/dconf.nix
./modules/packages.nix
./modules/plasma-manager.nix
./modules/noctalia.nix
# ./modules/noctalia.nix
];
xdg = {
enable = true;
+2 -7
View File
@@ -8,18 +8,13 @@
let
symlinksPaths = {
# cfg
"${xlib.dirs.user-storage}/ssh/config" = ".ssh/config";
"${xlib.dirs.user-storage}/beets" = ".config/beets";
"${xlib.dirs.user-storage}/ludusavi" = ".config/ludusavi";
"${xlib.dirs.user-storage}/solaar" = ".config/solaar";
"${xlib.dirs.user-storage}/easyeffects" = ".config/easyeffects";
"${xlib.dirs.user-storage}/KeePassXC" = ".config/keepassxc";
"${xlib.dirs.user-storage}/v2rayN" = ".local/share/v2rayN";
"/etc/nixos" = "Configuration";
"${config.home.homeDirectory}/Games/PrismLaunchers/${config.home.username}" =
".local/share/PrismLauncher";
#"${xlib.dirs.lamet-drive}/Users/oqyude/Music" = "Music";
"${xlib.dirs.lamet-drive}/Users/${xlib.device.username}/Music" = "Music";
};
mkLinks = lib.mapAttrs' (sourcePath: targetPath: {
name = targetPath;
@@ -32,7 +27,7 @@ in
./modules/dconf.nix
./modules/packages.nix
./modules/plasma-manager.nix
./modules/noctalia.nix
# ./modules/noctalia.nix
];
xdg = {
enable = true;
+2 -28
View File
@@ -5,38 +5,12 @@
xlib,
...
}:
let
symlinksPaths = {
"${config.home.homeDirectory}/External/Music" = "Music";
"${xlib.dirs.storage}/beets" = ".config/beets";
"${xlib.dirs.storage}/ssh/config" = ".ssh/config";
"${xlib.dirs.storage}/ssh/known_hosts" = ".ssh/known_hosts";
};
mkLinks = lib.mapAttrs' (sourcePath: targetPath: {
name = targetPath;
value.source = config.lib.file.mkOutOfStoreSymlink "${sourcePath}";
}) symlinksPaths;
in
{
imports = [
./minimal.nix
];
home.file = mkLinks;
xdg = {
enable = true;
autostart.enable = true;
userDirs = {
enable = true;
createDirectories = false;
desktop = null;
documents = null;
download = null;
music = null;
pictures = null;
publicShare = null;
templates = null;
videos = null;
};
home.file = xlib.helpers.mkSymlinks config {
"${config.home.homeDirectory}/External/Music" = "Music";
};
home.activation = {
yaziSync = ''
+283
View File
@@ -0,0 +1,283 @@
{
config,
lib,
pkgs,
xlib,
...
}:
# Shared "strict" home-manager module.
# Works in BOTH contexts:
# - NixOS hosts (via home-manager.sharedModules or homeConfigurations)
# - nix-on-droid (via home-manager.config in droid/epral.nix)
# Only home-manager options are used here — no systemd.*, no services.*,
# no users.*, no environment.systemPackages. All paths are parameterized
# through config.home.homeDirectory so /home/oqyude (NixOS) and
# /data/data/com.termux.nix/files/home (termux) both work.
#
# NOTE: intentionally duplicates parts of modules/essentials/{shell,packages}.nix
# (which stay NixOS-only for now). When this module is wired into NixOS hosts
# via sharedModules, deduplicate those files.
{
home = {
packages = with pkgs; [
# Lazy (alias lc)
lazycli
# IDE
fresh-editor # EDITOR
# Base utils
curl
wget
fd
tree
dust
gdu
mc
rsync
jq
unzip
zip
zstd
# Net diagnostic
mtr
dnsutils
# Monitoring
htop
];
sessionVariables = {
TUCKR_HOME = "$HOME/Storage/dotfiles";
EDITOR = "fresh";
};
file = {
".nanorc".text = ''
set nowrap
set tabstospaces
set tabsize 2
'';
# Authorized keys for sshd (see modules/termux/default.nix).
# Declarative for now — the Store/.ssh symlink scheme is postponed.
".ssh/authorized_keys".text = ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKduJia+unaQQdN6X5syaHvnpIutO+yZwvfiCP4qKQ/P
'';
};
};
programs = {
# ---- Shell: zsh ----
zsh = {
enable = true;
enableCompletion = true;
syntaxHighlighting.enable = true;
history.size = 10000;
oh-my-zsh = {
enable = true;
theme = "robbyrussell";
};
loginExtra = "clear && fastfetch && cd ~/.config/nix-on-droid";
# .zshenv — sourced by zsh in ALL sessions incl. non-login ssh commands.
# runit from nixpkgs defaults to /var/service as SVDIR, but our tree
# lives at ~/service (symlinked as /etc/service). Export it so that
# `sv status sshd` works without qualifying the path.
envExtra = "export SVDIR=/etc/service";
initContent = ''
beet-p() {
local base="${config.home.homeDirectory}/.config/beets/My"
local rel
rel=$(realpath --relative-to="$base" "$PWD")
beet mod "path:$rel" playlist="$*"
}
beet-ims() {
beet im ./ -S $*
}
beet-path() {
realpath --relative-to="${config.home.homeDirectory}/.config/beets/My" "$1"
}
'';
shellAliases = {
# shell
ff = "clear && fastfetch";
l = "ls -l";
lg = "lazygit";
lc = "lazycli";
gp = "git pull";
ns = "nix-on-droid switch --flake ~/.config/nix-on-droid#${xlib.device.hostname}";
gp-ns = "gp && ns";
gc = "git add . && git commit -m 'dev: автокоммит $(date +'%Y-%m-%d %H:%M:%S')'";
y = "yazi";
nix-shellp = "nix-shell --run $SHELL -p";
beet-path-library = "realpath --relative-to='${config.home.homeDirectory}/.config/beets/My' .";
z-proxy = "export ALL_PROXY=socks5://localhost:10808";
zh-proxy = "export HTTPS_PROXY=http://localhost:10808 && export HTTP_PROXY=http://localhost:10808";
nix-dir = "cd ~/.config/nix-on-droid";
q-ssh = "sv-start"; # start all supervised services (sshd, ...); manage with `sv status sshd` etc
# beets
beet-ima = "beet im ./ -A";
# ssh (hosts live in programs.ssh.settings below)
# NOTE: lamet / pubray-1 have no Host entry yet — kept as aliases.
z-l = "ssh lamet";
z-lt = "ssh lamet-tailscale";
z-p-1 = "ssh pubray-1";
z-map-local-proxy = "ssh -R 10808:localhost:10808";
# Extras
plasma-manager = "nix run github:nix-community/plasma-manager";
pip2nix = "nix run github:nix-community/pip2nix --"; # https://github.com/nix-community/pip2nix
pip2nix-g = "nix run github:nix-community/pip2nix -- generate -r";
json2nix = "nix run github:sempruijs/json2nix";
};
};
# ---- Editor ----
# NOTE: programs.nano is a NixOS-only module (does not exist in
# home-manager); write ~/.nanorc directly instead.
# ---- TUI tools ----
bat.enable = true;
lazygit.enable = true;
fzf.enable = true;
btop.enable = true;
broot.enable = true;
bottom.enable = true;
fastfetch.enable = true;
yazi = {
enable = true;
# explicit: shared module must behave identically on NixOS (26.05, "y")
# and nix-on-droid (24.05, legacy "yy")
shellWrapperName = "y";
plugins = {
inherit (pkgs.yaziPlugins)
gitui
git
sudo
ouch
rsync
diff
mount
chmod
dupes
lazygit
toggle-pane
rich-preview
smart-filter
full-border
recycle-bin
;
};
flavors = {
nord = pkgs.yaziPlugins.nord;
};
theme = {
flavor = {
light = "nord";
dark = "nord";
};
};
keymap = {
mgr.prepend_keymap = [
{
on = [
"M"
];
run = "plugin mount";
desc = "Mount manager";
}
{
on = [
"g"
"i"
];
run = "plugin lazygit";
desc = "run lazygit";
}
{
run = "plugin ouch --args=zip";
on = [
"g"
"C"
];
desc = "Compress with ouch";
}
];
};
settings = {
mgr.ratio = [
1
1
4
];
};
};
# ---- VCS ----
git = {
enable = true;
settings = {
user = {
name = xlib.device.username;
email = "oqyude@gmail.com";
};
pull = {
rebase = true;
};
};
};
# ---- SSH ----
# Declarative ~/.ssh/config, same as the one previously copied by hand.
# matchBlocks is deprecated in current home-manager; use `settings`
# (bare attr names become `Host` headers, keys are upstream directives).
ssh = {
enable = true;
# Reproduce the old enableDefaultConfig values explicitly.
enableDefaultConfig = false;
settings = {
"*" = {
ForwardAgent = false;
AddKeysToAgent = "no";
Compression = false;
ServerAliveInterval = 0;
ServerAliveCountMax = 3;
HashKnownHosts = false;
UserKnownHostsFile = "~/.ssh/known_hosts";
ControlMaster = "no";
ControlPath = "~/.ssh/master-%r@%n:%p";
ControlPersist = "no";
};
sapphira = {
HostName = "192.168.1.20";
User = xlib.device.username;
};
sapphira-tailscale = {
HostName = "100.64.0.0";
User = xlib.device.username;
};
otreca-old = {
HostName = "217.60.3.12";
User = xlib.device.username;
};
otreca = {
HostName = "109.248.161.5";
User = xlib.device.username;
};
otreca-tailscale = {
HostName = "100.64.1.0";
User = xlib.device.username;
};
rydiwo = {
HostName = "192.168.1.102";
User = xlib.device.username;
};
epral = {
HostName = "192.168.1.101";
User = xlib.device.username;
Port = 8022;
};
};
};
};
}
-27
View File
@@ -1,27 +0,0 @@
{
config,
pkgs,
xlib,
...
}:
{
imports = [
./minimal.nix
];
xdg = {
enable = true;
autostart.enable = true;
userDirs = {
enable = true;
createDirectories = false;
desktop = null;
documents = null;
download = null;
music = null;
pictures = null;
publicShare = null;
templates = null;
videos = null;
};
};
}
-19
View File
@@ -1,27 +1,8 @@
{
config,
pkgs,
xlib,
...
}:
{
imports = [
./minimal.nix
];
xdg = {
enable = true;
autostart.enable = true;
userDirs = {
enable = true;
createDirectories = false;
desktop = null;
documents = null;
download = null;
music = null;
pictures = null;
publicShare = null;
templates = null;
videos = null;
};
};
}
+4 -30
View File
@@ -5,41 +5,15 @@
xlib,
...
}:
let
symlinksPaths = {
"${config.home.homeDirectory}/External/Music" = "Music";
"${xlib.dirs.wsl-home}" = "External";
"${xlib.dirs.wsl-storage}/beets" = ".config/beets";
"${xlib.dirs.wsl-storage}/ssh/config" = ".ssh/config";
"${xlib.dirs.wsl-storage}/ssh/known_hosts" = ".ssh/known_hosts";
"${xlib.dirs.wsl-storage}/flow" = ".config/flow";
};
mkLinks = lib.mapAttrs' (sourcePath: targetPath: {
name = targetPath;
value.source = config.lib.file.mkOutOfStoreSymlink "${sourcePath}";
}) symlinksPaths;
in
{
imports = [
./apps
./minimal.nix
];
home.file = mkLinks;
xdg = {
enable = true;
autostart.enable = true;
userDirs = {
enable = true;
createDirectories = false;
desktop = null;
documents = null;
download = null;
music = null;
pictures = null;
publicShare = null;
templates = null;
videos = null;
};
home.file = xlib.helpers.mkSymlinks config {
"${config.home.homeDirectory}/External/Music" = "Music";
"${xlib.dirs.wsl-home}" = "External";
"${xlib.dirs.wsl-storage}" = "Storage";
};
home.activation = {
yaziSync = ''
+29
View File
@@ -0,0 +1,29 @@
{
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.
{
xlib,
modules ? [ ],
system ? "x86_64-linux",
...
}:
let
lib = inputs.nixpkgs.lib;
in
lib.nixosSystem {
inherit
system
modules
;
specialArgs = {
inherit inputs;
inherit xlib;
};
}
+78
View File
@@ -0,0 +1,78 @@
# 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",
uid ? 1000,
gid ? 1000,
}:
let
device = mkDevice {
inherit
hostname
type
username
uid
gid
;
};
in
{
device = {
inherit
hostname
type
username
uid
gid
;
};
isDesktop = device.isDesktop;
isHeadless = device.isHeadless;
dirs = mkDirs username;
# Bind the host's ids into the mount helpers, so ntfs3/exfat options
# carry the same uid/gid the primary user actually has.
helpers = import ./helpers.nix {
inherit lib;
uid = device.uid;
gid = device.gid;
};
};
}
+75
View File
@@ -0,0 +1,75 @@
{
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",
# The primary user is pinned to 1000 rather than left to NixOS'
# nextfree logic: the mount helpers below write uid=/gid= into
# ntfs3/exfat options, and an NTFS/exFAT volume mounted with a
# different id shows every file as owned by `nobody`.
uid ? 1000,
gid ? 1000,
}:
let
capabilities =
devices.${type}
or (throw "xlib: unknown device type '${type}', expected one of ${lib.concatStringsSep ", " (builtins.attrNames devices)}");
in
{
inherit
hostname
type
username
uid
gid
;
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";
}
+161
View File
@@ -0,0 +1,161 @@
{
lib,
# The primary user's ids, bound from xlib.device by mkXlib. ntfs3/exfat
# volumes carry POSIX ids, so a mount using anything other than the real
# uid/gid shows every file as owned by `nobody`.
uid,
gid,
...
}:
# Pure helper functions for module definitions.
# 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
# tmpfiles rule: "type dir mode user group -"
mkTmpfile =
type: dir: mode: user: group:
"${type} ${dir} ${mode} ${user} ${group} -";
# several tmpfiles types for the same dir, e.g. ["d" "z"] or ["d" "Z"]
mkTmpDirs =
{
dir,
mode,
user,
group,
types ? [
"d"
"z"
],
}:
map (type: mkTmpfile type dir mode user group) types;
# fileSystems bind mount
mkBindMount =
{
what,
where,
}:
{
"${where}" = {
device = what;
fsType = "none";
options = [
"bind"
"nofail"
];
};
};
# systemd.mounts bind mount (automount variant)
mkSystemdBind =
{
what,
where,
}:
{
enable = true;
options = "bind,x-systemd.automount,nofail";
requires = [ "local-fs.target" ];
type = "none";
wantedBy = [ "multi-user.target" ];
inherit what where;
};
# Full "service storage" block: services-mnt source dir + /var/lib target,
# tmpfiles d/z + automount bind. Used as:
# storage = xlib.helpers.mkServiceStorage { name = "x"; user = "x"; group = "x"; };
# systemd = storage.systemd;
mkServiceStorage =
{
name,
user,
group,
mode ? "0755",
target ? "/var/lib/${name}",
base ? "/mnt/services",
}:
let
sourceDir = "${base}/${name}";
in
{
inherit sourceDir target;
systemd = {
tmpfiles.rules = mkTmpDirs {
dir = sourceDir;
inherit mode user group;
};
mounts = [
(mkSystemdBind {
what = sourceDir;
where = target;
})
];
};
};
# ntfs3 mount, e.g. fileSystems = mkNtfsMount { path = ...; uuid = ...; }
mkNtfsMount =
{
path,
uuid,
mask ? "0007",
enable ? null,
}:
{
"${path}" = {
device = "/dev/disk/by-uuid/${uuid}";
fsType = "ntfs3";
options = [
"defaults"
"uid=${toString uid}"
"gid=${toString gid}"
"fmask=${mask}"
"dmask=${mask}"
"nofail"
];
}
// lib.optionalAttrs (enable != null) { inherit enable; };
};
# exfat mount, e.g. fileSystems = mkExfatMount { path = ...; uuid = ...; }
mkExfatMount =
{
path,
uuid ? null,
label ? null,
}:
{
"${path}" = {
device = if uuid != null then "/dev/disk/by-uuid/${uuid}" else "/dev/disk/by-label/${label}";
fsType = "exfat";
options = [
"nofail"
"uid=${toString uid}"
"gid=${toString gid}"
];
};
};
# home-manager out-of-store symlinks: path = source (target name = attr name)
mkSymlinks =
config: paths:
lib.mapAttrs' (sourcePath: targetPath: {
name = targetPath;
value.source = config.lib.file.mkOutOfStoreSymlink "${sourcePath}";
}) paths;
in
{
inherit
mkTmpfile
mkTmpDirs
mkBindMount
mkSystemdBind
mkServiceStorage
mkNtfsMount
mkExfatMount
mkSymlinks
;
}
+130
View File
@@ -0,0 +1,130 @@
{
config,
lib,
pkgs,
xlib,
...
}:
let
panel = "${xlib.dirs.services-nodes-folder}/${xlib.device.hostname}/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.
# Null when 3x-ui serves plain HTTP and TLS is terminated by an upstream
# nginx.
certDomain = config.host."3x-ui".certDomain;
certMounts =
if certDomain == null then
[ ]
else
# LE cert mounted read-only so 3x-ui can terminate TLS itself.
# The 3x-ui settings table must point webCertFile / webKeyFile at
# /root/cert/fullchain.pem and /root/cert/key.pem.
map (f: "/var/lib/acme/${certDomain}/${f}:/root/cert/${f}:ro") [
"fullchain.pem"
"key.pem"
];
basePorts = [
# Local-only upstreams for the 3x-ui panel and subscription endpoint.
# The direct Xray inbound remains publicly reachable on 8443.
"127.0.0.1:2049:2049/tcp"
"127.0.0.1:2096:2096/tcp"
"0.0.0.0:8443:8443/tcp"
];
# VDS-only: nginx stream forwards host:443 → 127.0.0.1:15380 →
# container:443, so Xray sees its REALITY inbound on port 443.
realityPorts = lib.optional config.host."3x-ui".reality443Forwarding "127.0.0.1:15380:443/tcp";
in
{
# `host."3x-ui"` options are declared in modules/options.nix: they are set
# by modules/server and modules/vds, so this module cannot be the only place
# that knows they exist.
config = {
virtualisation = {
podman = {
enable = true;
autoPrune = {
enable = true;
flags = [ "--all" ];
};
dockerCompat = true;
};
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 = [
"${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;
};
};
};
systemd = {
services = {
"podman-3xui_app" = {
serviceConfig.Restart = lib.mkOverride 90 "always";
partOf = [ "podman-compose-3x-ui-root.target" ];
wantedBy = [ "podman-compose-3x-ui-root.target" ];
};
"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 ];
};
};
};
}
+116
View File
@@ -0,0 +1,116 @@
{
lib,
pkgs,
...
}:
let
# The image is built here rather than pulled: zaakirio/kokoro-ru is a
# Hugging Face repo, not a published OCI image, and its Russian G2P has to be
# driven through the repo's own ru_g2p.py.
#
# The build context goes through the store so the image is pinned to the
# config revision: edit a file, `nixos-rebuild`, and the unit below rebuilds
# and restarts. Reading the context off a checkout at runtime would leave the
# running container untraceable back to any config.
source = pkgs.linkFarm "kokoro-tts-source" [
{
name = "Dockerfile";
path = toString ./kokoro-tts/Dockerfile;
}
{
name = "app.py";
path = toString ./kokoro-tts/app.py;
}
{
name = "fetch_assets.py";
path = toString ./kokoro-tts/fetch_assets.py;
}
{
name = "requirements.txt";
path = toString ./kokoro-tts/requirements.txt;
}
];
image = "localhost/kokoro-tts:latest";
# Unchanged from the silero module, so whatever already points at
# http://127.0.0.1:9898/v1 keeps working without edits.
hostPort = 9898;
containerPort = 8000;
in
{
config = {
virtualisation = {
podman = {
enable = true;
autoPrune = {
enable = true;
flags = [ "--all" ];
};
dockerCompat = true;
};
oci-containers = {
backend = "podman";
containers.kokoro-tts = {
image = image;
ports = [
"127.0.0.1:${toString hostPort}:${toString containerPort}"
];
environment = {
# Inference is CPU-bound and already threaded inside torch; these
# keep it from oversubscribing a small machine.
KOKORO_THREADS = "4";
OMP_NUM_THREADS = "4";
MKL_NUM_THREADS = "4";
TZ = "Europe/Moscow";
};
# No volumes: the checkpoints, the acute-aware espeak data and
# ruaccent's ONNX models are all baked into the image, so the
# container needs neither a host directory nor the network to start.
log-driver = "journald";
};
};
};
systemd = {
services = {
# Runs before the container. BuildKit caches the expensive layers, so
# on every boot after the first this is a no-op that still verifies the
# image exists.
"podman-build-kokoro-tts" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
# First build pulls torch wheels plus ~700 MB of weights.
TimeoutSec = 3600;
};
script = ''
podman build -t ${image} ${source}
'';
wantedBy = [ "multi-user.target" ];
};
"podman-kokoro-tts" = {
# The image does not exist until the build above ran, and a `latest`
# tag must be re-pulled on rebuild, so ordering has to be explicit.
after = [ "podman-build-kokoro-tts.service" ];
requires = [ "podman-build-kokoro-tts.service" ];
serviceConfig.Restart = lib.mkOverride 90 "always";
wantedBy = [ "multi-user.target" ];
};
};
};
};
}
+56
View File
@@ -0,0 +1,56 @@
FROM python:3.12-slim-bookworm
# Pinned, not "main": a rebuild that only touched the Nix module must not
# silently pick up different weights. Bump these deliberately.
ARG KOKORO_RU_REPO=zaakirio/kokoro-ru
ARG KOKORO_RU_REVISION=d649c57b239b18c4c384378127cbf01dba039bc1
# Trim to "sveta" to halve the image: masha shares her checkpoint and dima is
# a second 327 MB one.
ARG KOKORO_RU_VOICES=sveta,masha,dima
ENV PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
HF_HUB_DISABLE_TELEMETRY=1 \
HF_HUB_DISABLE_SYMLINKS_WARNING=1 \
KOKORO_RU_REPO=${KOKORO_RU_REPO} \
KOKORO_RU_REVISION=${KOKORO_RU_REVISION} \
KOKORO_RU_VOICES=${KOKORO_RU_VOICES} \
KOKORO_MODEL_DIR=/app/kokoro-ru \
KOKORO_THREADS=4 \
OMP_NUM_THREADS=4 \
MKL_NUM_THREADS=4 \
TZ=Europe/Moscow
WORKDIR /app
# libgomp1 is torch's OpenMP runtime. espeak-ng comes from the espeakng-loader
# wheel rather than the distro package because the model needs its own
# recompiled ru_dict, and libsndfile is absent because WAV/PCM are written with
# stdlib `wave` while every other format goes through imageio-ffmpeg.
RUN apt-get update \
&& apt-get install -y --no-install-recommends libgomp1 \
&& rm -rf /var/lib/apt/lists/*
# CPU-only torch from its own index: the default PyPI wheel drags in ~2.5 GB of
# CUDA libraries for a machine that has no GPU.
RUN pip install --index-url https://download.pytorch.org/whl/cpu torch
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY app.py fetch_assets.py ./
# Bakes the checkpoints, the acute-aware espeak data and ruaccent's ONNX models
# into the layer, which is what lets the container start with no network and no
# writable volume.
RUN python fetch_assets.py
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --start-period=180s --retries=3 \
CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/healthz', timeout=4)"]
# No workers: the model is a shared in-process singleton, so a second worker
# would only mean a second copy of ~2 GB of weights.
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
+430
View File
@@ -0,0 +1,430 @@
"""OpenAI-compatible TTS API backed by zaakirio/kokoro-ru.
The model itself is language-blind: phoneme ids in, 24 kHz audio out. All the
Russian lives in the G2P front-end, and the one that matters is kokoro-ru's own
`ru_g2p.py` — RUAccent resolves lexical stress, ё and homographs, then an
acute-aware espeak-ng phonemizer turns that into IPA. Stock misaki Russian is
espeak-only and gets stress wrong often enough that the model reads as
non-native (measured 27% vs 22% round-trip WER, per the model card).
So: text -> RuG2P.phonemize -> KModel(ipa, voicepack[len(ipa) - 1]) -> waveform.
Endpoints
POST /v1/audio/speech OpenAI text-to-speech
GET /v1/models OpenAI model list
GET /v1/voices voice inventory (extension, not part of OpenAI)
GET /healthz readiness, 503 until the model is loaded
"""
from __future__ import annotations
import io
import logging
import os
import re
import subprocess
import sys
import threading
import wave
from contextlib import asynccontextmanager
from pathlib import Path
from typing import TYPE_CHECKING, Literal
import numpy as np
from fastapi import FastAPI
from fastapi.responses import JSONResponse, Response
from pydantic import BaseModel, ConfigDict, Field
if TYPE_CHECKING: # torch is imported lazily so /healthz answers during boot
import torch
MODEL_ID = "kokoro-ru"
SAMPLE_RATE = 24000
MODEL_DIR = Path(os.environ.get("KOKORO_MODEL_DIR", "/app/kokoro-ru"))
DEFAULT_VOICE = os.environ.get("KOKORO_DEFAULT_VOICE", "sveta")
THREADS = int(os.environ.get("KOKORO_THREADS", os.cpu_count() or 4))
# 2026-07-29, when the kokoro-ru revision we pin was published. Clients that
# cache on this treat any change as a new model, so it must stay stable.
MODEL_CREATED = 1785353253
# voice -> (checkpoint stem, gender). The checkpoint carries the timbre and the
# voicepack the identity, which is why sveta and masha share one file.
VOICE_SPECS: dict[str, tuple[str, str]] = {
"sveta": ("kokoro-ru-v2-base", "female"),
"masha": ("kokoro-ru-v2-base", "female"),
"dima": ("kokoro-ru-v2-dima", "male"),
}
# Clients that ship the OpenAI voice list (alloy, nova, echo, ...) send those
# names unless the user overrides them, so map them onto the three we have.
VOICE_ALIASES: dict[str, str] = {
"alloy": "sveta",
"ash": "sveta",
"ballad": "sveta",
"verse": "sveta",
"marin": "sveta",
"coral": "masha",
"sage": "masha",
"shimmer": "masha",
"cedar": "masha",
"echo": "dima",
"fable": "dima",
"onyx": "dima",
}
CONTENT_TYPES = {
"wav": "audio/wav",
"mp3": "audio/mpeg",
"opus": "audio/ogg",
"aac": "audio/aac",
"flac": "audio/flac",
"pcm": "audio/pcm",
}
# Everything except wav and pcm goes through ffmpeg; those two are byte-exact
# from the stdlib and need no encoder at all.
FFMPEG_ARGS = {
"mp3": ["-c:a", "libmp3lame", "-q:a", "2"],
"opus": ["-c:a", "libopus", "-b:a", "64k"],
"aac": ["-c:a", "aac", "-b:a", "128k"],
"flac": ["-c:a", "flac"],
}
FFMPEG_CONTAINERS = {"mp3": "mp3", "opus": "ogg", "aac": "adts", "flac": "flac"}
# Kokoro's Albert context is 510 tokens and KModel.forward asserts
# len(ids) + 2 <= 510, so 508 phonemes is the hard ceiling per forward pass.
MAX_PHONEMES = 508
# Roughly 300 characters of Russian lands near 400 phonemes, comfortably under
# the ceiling, and keeps a chunk short enough that a bad sentence is a short
# chunk.
CHUNK_CHARS = 300
# Silence inserted between chunks. Without it the concatenation clicks at every
# boundary because each forward pass starts and ends on a zero crossing.
CHUNK_GAP_S = 0.08
_SENTENCE_SPLIT = re.compile(r"(?<=[.!?…])\s+")
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
log = logging.getLogger("kokoro-ru")
def split_text(text: str, budget: int = CHUNK_CHARS) -> list[str]:
"""Split into sentence-bounded chunks, hard-cutting only as a last resort.
Phonemizing per sentence rather than per paragraph keeps RUAccent's stress
decisions local and gives the model a reset point at every full stop.
"""
chunks: list[str] = []
current = ""
for sentence in _SENTENCE_SPLIT.split(text.strip()):
sentence = sentence.strip()
while len(sentence) > budget:
if current:
chunks.append(current)
current = ""
chunks.append(sentence[:budget])
sentence = sentence[budget:].strip()
if not sentence:
continue
if len(current) + len(sentence) + 1 > budget:
# Guarded: when the first sentence fills the budget exactly, or the
# previous one was hard-cut down to nothing, `current` is empty and
# a bare append would queue a zero-length chunk.
if current:
chunks.append(current)
current = sentence
else:
current = f"{current} {sentence}".strip()
if current:
chunks.append(current)
return chunks
def split_phonemes(ps: str, limit: int = MAX_PHONEMES) -> list[str]:
"""Cut an over-long phoneme string on word boundaries."""
if len(ps) <= limit:
return [ps]
parts: list[str] = []
rest = ps
while len(rest) > limit:
cut = rest.rfind(" ", 0, limit)
if cut <= 0:
cut = limit
parts.append(rest[:cut].strip())
rest = rest[cut:].strip()
if rest:
parts.append(rest)
return [part for part in parts if part]
class KokoroRu:
"""Loaded model plus the G2P front-end, behind a single inference lock."""
def __init__(self) -> None:
self._torch: torch | None = None
self._g2p = None
self._models: dict[str, torch.nn.Module] = {}
self._packs: dict[str, torch.Tensor] = {}
# The Albert encoder and the iSTFTNet decoder keep per-call scratch
# buffers; concurrent forwards on one model interleave into them. The
# model is fast enough on CPU that serialising is not the bottleneck.
self._lock = threading.Lock()
def load(self) -> None:
import torch
from kokoro import KModel
torch.set_num_threads(THREADS)
self._torch = torch
# RuG2P is imported from the baked snapshot, not installed, and it
# resolves espeak-data/ plus kokoro-config.json next to itself.
sys.path.insert(0, str(MODEL_DIR))
from ru_g2p import RuG2P
self._g2p = RuG2P(
espeak_data=MODEL_DIR / "espeak-data",
vocab_path=MODEL_DIR / "kokoro-config.json",
)
for stem in sorted({stem for stem, _ in VOICE_SPECS.values()}):
checkpoint = MODEL_DIR / f"{stem}.pth"
if not checkpoint.exists():
log.warning("checkpoint %s missing, voices using it stay unavailable", checkpoint)
continue
# repo_id is only used to build the default model filename; passing
# both config and model keeps it from touching the HF cache at all.
self._models[stem] = KModel(
repo_id=str(MODEL_DIR),
config=str(MODEL_DIR / "config.json"),
model=str(checkpoint),
).eval()
log.info("loaded checkpoint %s", checkpoint.name)
for name in VOICE_SPECS:
pack = MODEL_DIR / "voices" / f"{name}.pt"
if pack.exists():
self._packs[name] = torch.load(str(pack), map_location="cpu", weights_only=True)
if not self.available_voices():
raise RuntimeError(f"no usable voices under {MODEL_DIR}")
def available_voices(self) -> list[str]:
return [
name
for name in VOICE_SPECS
if name in self._packs and VOICE_SPECS[name][0] in self._models
]
def phonemes(self, text: str):
for chunk in split_text(text):
ps, _oov = self._g2p.phonemize(chunk)
ps = ps.strip()
if ps:
yield from split_phonemes(ps)
def synthesize(self, text: str, voice: str, speed: float) -> np.ndarray:
torch = self._torch
assert torch is not None, "synthesize() before load()"
stem, _gender = VOICE_SPECS[voice]
model = self._models[stem]
pack = self._packs[voice]
gap = torch.zeros(int(CHUNK_GAP_S * SAMPLE_RATE), dtype=torch.float32)
pieces: list[torch.Tensor] = []
with self._lock:
for ps in self.phonemes(text):
# The style vector is picked by phoneme-string length, which is
# why the model sounds deterministic for identical text.
style = pack[len(ps) - 1]
# The packs ship as [510, 256]; KModel wants a batch of one.
if style.dim() == 1:
style = style.unsqueeze(0)
if pieces:
pieces.append(gap)
pieces.append(model(ps, style, speed, return_output=True).audio)
if not pieces:
return np.zeros(0, dtype=np.float32)
return torch.cat(pieces).numpy().astype(np.float32, copy=False)
def encode(audio: np.ndarray, fmt: str) -> bytes:
clipped = np.clip(audio, -1.0, 1.0)
if fmt == "pcm":
# OpenAI's pcm is raw signed 16-bit little-endian mono at 24 kHz.
return (clipped * 32767.0).astype("<i2").tobytes()
buffer = io.BytesIO()
with wave.open(buffer, "wb") as out:
out.setnchannels(1)
out.setsampwidth(2)
out.setframerate(SAMPLE_RATE)
out.writeframes((clipped * 32767.0).astype("<i2").tobytes())
wav = buffer.getvalue()
if fmt == "wav":
return wav
import imageio_ffmpeg
command = [
imageio_ffmpeg.get_ffmpeg_exe(),
"-hide_banner",
"-loglevel",
"error",
"-i",
"pipe:0",
"-ar",
str(SAMPLE_RATE),
"-ac",
"1",
*FFMPEG_ARGS[fmt],
"-f",
FFMPEG_CONTAINERS[fmt],
"pipe:1",
]
done = subprocess.run(command, input=wav, capture_output=True, check=False)
if done.returncode != 0:
raise RuntimeError(done.stderr.decode("utf-8", "replace").strip()[-400:])
return done.stdout
engine = KokoroRu()
state: dict[str, str | None] = {"status": "loading", "error": None}
def boot() -> None:
try:
engine.load()
state["status"] = "ready"
log.info("ready: voices=%s", ", ".join(engine.available_voices()))
except Exception as exc:
state["status"] = "error"
state["error"] = f"{type(exc).__name__}: {exc}"
log.exception("model failed to load")
@asynccontextmanager
async def lifespan(_app: FastAPI):
# Off the event loop: loading pulls ~700 MB of weights and runs three ONNX
# sessions, and /healthz has to stay answerable while it happens.
threading.Thread(target=boot, name="kokoro-load", daemon=True).start()
yield
app = FastAPI(title="kokoro-ru OpenAI TTS", version="1.0.0", lifespan=lifespan)
Format = Literal["mp3", "opus", "aac", "flac", "wav", "pcm"]
class SpeechRequest(BaseModel):
# `protected_namespaces` silences pydantic's warning about the `model_`
# prefix; `extra="ignore"` absorbs the fields newer OpenAI clients add
# (instructions, the legacy `format` alias) without failing the request.
model_config = ConfigDict(extra="ignore", protected_namespaces=())
input: str = Field(min_length=1)
model: str = MODEL_ID
voice: str | None = None
response_format: Format = "wav"
speed: float | None = Field(default=None, ge=0.25, le=4.0)
def fail(status: int, message: str, param: str | None = None, code: str | None = None) -> JSONResponse:
return JSONResponse(
status_code=status,
content={
"error": {
"message": message,
"type": "invalid_request_error" if status < 500 else "server_error",
"param": param,
"code": code,
}
},
)
def resolve_voice(requested: str | None) -> str | None:
name = (requested or DEFAULT_VOICE).strip().lower()
name = VOICE_ALIASES.get(name, name)
return name if name in engine.available_voices() else None
# response_model=None: the handler returns a Response subclass directly, and
# FastAPI would otherwise try to build a Pydantic model out of the union.
@app.post("/v1/audio/speech", response_model=None)
def create_speech(request: SpeechRequest) -> Response | JSONResponse:
if state["status"] != "ready":
return fail(503, f"model is not ready: {state['status']}", code="model_not_ready")
voice = resolve_voice(request.voice)
if voice is None:
available = ", ".join(engine.available_voices())
return fail(
400,
f"unknown voice {request.voice!r}; available: {available}",
param="voice",
code="unknown_voice",
)
try:
audio = engine.synthesize(request.input, voice, request.speed or 1.0)
except Exception as exc:
log.exception("synthesis failed")
return fail(500, f"synthesis failed: {exc}", code="synthesis_failed")
if audio.size == 0:
return fail(
400,
"input contains no speakable text for the Russian G2P",
param="input",
code="no_phonemes",
)
try:
payload = encode(audio, request.response_format)
except Exception as exc:
log.exception("encoding to %s failed", request.response_format)
return fail(500, f"encoding to {request.response_format} failed: {exc}", code="encoding_failed")
return Response(
content=payload,
media_type=CONTENT_TYPES[request.response_format],
headers={"model-id": MODEL_ID, "voice-id": voice},
)
@app.get("/v1/models")
def list_models() -> dict:
return {
"object": "list",
"data": [{"id": MODEL_ID, "object": "model", "created": MODEL_CREATED, "owned_by": "zaakirio"}],
}
@app.get("/v1/voices")
def list_voices() -> dict:
return {
"object": "list",
"ready": state["status"] == "ready",
"data": [
{"id": name, "object": "voice", "checkpoint": VOICE_SPECS[name][0], "gender": VOICE_SPECS[name][1]}
for name in engine.available_voices()
],
}
@app.get("/healthz")
def healthz() -> JSONResponse:
ready = state["status"] == "ready"
return JSONResponse(
status_code=200 if ready else 503,
content={
"status": state["status"],
"model": MODEL_ID,
"voices": engine.available_voices(),
"sample_rate": SAMPLE_RATE,
"error": state["error"],
},
)
@@ -0,0 +1,82 @@
"""Bake every kokoro-ru asset the server needs into the image.
Two things make a plain `FROM python` image useless for this model at runtime,
and both are fixed here at build time:
* kokoro-ru's checkpoints and its recompiled espeak-ng data live in the HF
cache by default, and the HF cache is part of the disposable container
layer, so every `podman run` would re-download ~700 MB.
* ruaccent writes its ONNX models, dictionaries and Koziev data into its own
`site-packages/ruaccent` directory. It only downloads when those files are
missing, so a single `load()` here means the runtime never touches the
network.
RuG2P resolves espeak-data/ and kokoro-config.json relative to ru_g2p.py, so
the snapshot layout has to stay flat inside KOKORO_MODEL_DIR.
"""
from __future__ import annotations
import logging
import os
from pathlib import Path
from huggingface_hub import snapshot_download
REPO = os.environ.get("KOKORO_RU_REPO", "zaakirio/kokoro-ru")
# A commit, not a branch: "main" would silently change the weights under a
# rebuild that only touched an unrelated line of the Nix module.
REVISION = os.environ.get("KOKORO_RU_REVISION", "main")
DEST = Path(os.environ.get("KOKORO_MODEL_DIR", "/app/kokoro-ru"))
VOICES = [v.strip() for v in os.environ.get("KOKORO_RU_VOICES", "sveta,masha,dima").split(",") if v.strip()]
# sveta and masha share one checkpoint and differ only by voicepack, so the two
# female voices cost one 327 MB download, not two.
CHECKPOINTS = {
"sveta": "kokoro-ru-v2-base.pth",
"masha": "kokoro-ru-v2-base.pth",
"dima": "kokoro-ru-v2-dima.pth",
}
PATTERNS = [
# KModel reads config.json; RuG2P reads kokoro-config.json for the phoneme
# vocab. They are not the same file and both are required.
"config.json",
"kokoro-config.json",
"ru_g2p.py",
# Stock espeak-ng ru_dict ignores combining-acute stress marks, which is the
# one thing this whole front-end exists to fix. The model repo ships a
# recompiled dictsource; there is no substitute to fall back to.
"espeak-data/**",
*(CHECKPOINTS[v] for v in VOICES if v in CHECKPOINTS),
*(f"voices/{v}.pt" for v in VOICES),
]
def main() -> None:
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
DEST.mkdir(parents=True, exist_ok=True)
snapshot_download(
repo_id=REPO,
revision=REVISION,
allow_patterns=PATTERNS,
local_dir=str(DEST),
)
logging.info("kokoro-ru assets in %s at %s", DEST, REVISION)
missing = [name for name in VOICES if not (DEST / "voices" / f"{name}.pt").exists()]
if missing:
raise SystemExit(f"voice packs missing after download: {missing}")
# Warm ruaccent into site-packages so `load()` short-circuits at runtime.
from ruaccent import RUAccent
accent = RUAccent()
accent.load(omograph_model_size="turbo3.1", use_dictionary=True, tiny_mode=False)
logging.info("ruaccent warm: %s", accent.process_all("Здравствуйте, как ваши дела?"))
if __name__ == "__main__":
main()
@@ -0,0 +1,21 @@
# torch is installed separately in the Dockerfile from the CPU-only index;
# do not add it here or pip would pull the ~2.5 GB CUDA build over it.
#
# ru_g2p.py (from zaakirio/kokoro-ru) imports three things stock kokoro does not
# pull on its own: the espeak-ng backend of misaki, ruaccent for stress, and the
# phonemizer fork whose EspeakWrapper misaki drives.
kokoro==0.9.4
misaki[en]>=0.9.4
phonemizer-fork
espeakng-loader
ruaccent
# kokoro's Albert encoder and ruaccent's ONNX exports both go through
# transformers. ru_g2p.py shims token_type_ids for v5, so the floor is what
# matters, not the ceiling.
transformers>=4.46
fastapi
uvicorn
imageio-ffmpeg
numpy>=1.26,<3
+121
View File
@@ -0,0 +1,121 @@
{
pkgs,
lib,
config,
xlib,
...
}:
{
# Runtime
virtualisation.podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
dockerSocket.enable = true;
defaultNetwork.settings.dns_enabled = true;
};
# 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 ];
};
virtualisation.oci-containers.backend = "podman";
# Containers
virtualisation.oci-containers.containers."openhands-app" = {
image = "ghcr.io/openhands/openhands:latest";
environment = {
"AGENT_SERVER_IMAGE_REPOSITORY" = "ghcr.io/openhands/agent-server";
"AGENT_SERVER_IMAGE_TAG" = "31536c8-python";
"WORKSPACE_MOUNT_PATH" = "${xlib.dirs.services-mnt-folder}/containers/openhands/workspace";
};
volumes = [
"${xlib.dirs.services-mnt-folder}/containers/openhands/userspace:/.openhands:rw"
"${xlib.dirs.services-mnt-folder}/containers/openhands/workspace:/opt/workspace_base:rw"
"/run/podman/podman.sock:/var/run/docker.sock:rw"
];
ports = [
"3000:3000/tcp"
];
log-driver = "journald";
extraOptions = [
# "--network=host"
"--add-host=host.docker.internal:host-gateway"
"--network-alias=openhands"
"--network=openhands_default"
];
};
systemd.services."podman-openhands-app" = {
serviceConfig = {
Restart = lib.mkOverride 90 "no";
};
after = [
"podman-network-openhands_default.service"
];
requires = [
"podman-network-openhands_default.service"
];
partOf = [
"podman-compose-openhands-root.target"
];
wantedBy = [
"podman-compose-openhands-root.target"
];
};
# Networks
systemd.services."podman-network-openhands_default" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "podman network rm -f openhands_default";
};
script = ''
podman network inspect openhands_default || podman network create openhands_default
'';
partOf = [ "podman-compose-openhands-root.target" ];
wantedBy = [ "podman-compose-openhands-root.target" ];
};
# Builds
# systemd.services."podman-build-openhands-app" = {
# enable = false;
# path = [
# pkgs.podman
# pkgs.git
# ];
# serviceConfig = {
# Type = "oneshot";
# TimeoutSec = 300;
# };
# script = ''
# cd ${xlib.dirs.services-mnt-folder}/containers/openhands/source
# podman build -t openhands:latest -f ./containers/app/Dockerfile .
# '';
# };
# Root service
# When started, this will automatically create all resources and start
# the containers. When stopped, this will teardown all resources.
systemd.targets."podman-compose-openhands-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
};
systemd.tmpfiles.rules = [
"d ${xlib.dirs.services-mnt-folder} 0755 root root -"
"d ${xlib.dirs.services-mnt-folder}/containers 0755 root root -"
"d ${xlib.dirs.services-mnt-folder}/containers/openhands 0755 root root -"
"d ${xlib.dirs.services-mnt-folder}/containers/openhands/userspace 0755 root root -"
"d ${xlib.dirs.services-mnt-folder}/containers/openhands/workspace 0755 root root -"
];
}
+15
View File
@@ -0,0 +1,15 @@
{
config,
lib,
pkgs,
inputs,
xlib,
...
}:
{
systemd.tmpfiles.rules = [
"d ${xlib.dirs.services-mnt-folder} 0755 root root -"
"d ${xlib.dirs.services-mnt-folder}/containers 0755 root root -"
"d ${xlib.dirs.services-mnt-folder}/containers/remnanode 0755 root root -"
];
}
@@ -0,0 +1,115 @@
# Auto-generated by compose2nix.
{
pkgs,
lib,
config,
...
}:
{
# Runtime
virtualisation.podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
};
# 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 ];
};
virtualisation.oci-containers.backend = "podman";
# Containers
virtualisation.oci-containers.containers."remnawave-panel-1" = {
image = "localhost/compose2nix/remnawave-panel-1";
environment = {
"API_INSTANCES" = "1";
"APP_PORT" = "3000";
"BANDWIDTH_USAGE_NOTIFICATIONS_ENABLED" = "false";
"BANDWIDTH_USAGE_NOTIFICATIONS_THRESHOLD" = "[60, 80]";
"CLOUDFLARE_TOKEN" = "ey...";
"DATABASE_URL" = "postgresql://remnawave:remnawave@remnawave-db:5432/postgres";
"FRONT_END_DOMAIN" = "*";
"IS_DOCS_ENABLED" = "false";
"IS_TELEGRAM_NOTIFICATIONS_ENABLED" = "false";
"JWT_API_TOKENS_SECRET" =
"787aa44c10130a9fa17ea3ea50c1248dd3e868f74941b96c09d608051399f88b95b67cd68d045aa39658b4b3fe933bf2b2c1437522498976f39f85ae1eab40da";
"JWT_AUTH_SECRET" =
"2bc14bacb6b82ce9e3ef69f8dd7bfb6b8a531f4f516902735d1d8f1bac8ff9b5077398f95b942b1adafc0ca1da4cdfd24a18539fa6eb26bee3f597a45deac94a";
"METRICS_PASS" = "admin";
"METRICS_PORT" = "3001";
"METRICS_USER" = "admin";
"NOT_CONNECTED_USERS_NOTIFICATIONS_AFTER_HOURS" = "[6, 24, 48]";
"NOT_CONNECTED_USERS_NOTIFICATIONS_ENABLED" = "false";
"PANEL_DOMAIN" = "rw.zeroq.ru";
"POSTGRES_DB" = "remnawave";
"POSTGRES_PASSWORD" = "gQLqOm2jK/Z1oBXCD18XSgr76M8ZqkVhHZbNKvZQXnY=";
"POSTGRES_USER" = "remnawave";
"REDIS_SOCKET" = "/var/run/valkey/valkey.sock";
"SCALAR_PATH" = "/scalar";
"SUB_PUBLIC_DOMAIN" = "rw.zeroq.ru/api/sub";
"SWAGGER_PATH" = "/docs";
# "TELEGRAM_BOT_TOKEN" = "change_me";
# "TELEGRAM_NOTIFY_CRM" = "change_me";
# "TELEGRAM_NOTIFY_NODES" = "change_me";
# "TELEGRAM_NOTIFY_SERVICE" = "change_me";
# "TELEGRAM_NOTIFY_TBLOCKER" = "change_me";
# "TELEGRAM_NOTIFY_USERS" = "change_me";
"WEBHOOK_ENABLED" = "false";
"WEBHOOK_SECRET_HEADER" = "vsmu67Kmg6R8FjIOF1WUY8LWBHie4scdEqrfsKmyf4IAf8dY3nFS0wwYHkhh6ZvQ";
"WEBHOOK_URL" = "https://your-webhook-url.com/endpoint";
};
ports = [
"3003:3003/tcp"
];
log-driver = "journald";
extraOptions = [
"--network-alias=remnawave-panel-1"
"--network=remnawavebackend_default"
];
};
systemd.services."podman-remnawave-panel-1" = {
serviceConfig = {
Restart = lib.mkOverride 90 "always";
};
partOf = [
"podman-compose-remnawave-root.target"
];
wantedBy = [
"podman-compose-remnawave-root.target"
];
};
# Builds
systemd.services."podman-build-remnawave-panel-1" = {
path = [
pkgs.podman
pkgs.git
];
serviceConfig = {
Type = "oneshot";
TimeoutSec = 300;
};
script = ''
cd /mnt/s/Deploy/remnawave-backend
podman build -t compose2nix/remnawave-panel-1 .
'';
};
# Root service
# When started, this will automatically create all resources and start
# the containers. When stopped, this will teardown all resources.
systemd.targets."podman-compose-remnawave-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
};
}
@@ -0,0 +1,290 @@
# Auto-generated by compose2nix.
{
pkgs,
lib,
config,
...
}:
{
# Runtime
virtualisation.podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
};
# 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 ];
};
virtualisation.oci-containers.backend = "podman";
# Containers
virtualisation.oci-containers.containers."remnawave" = {
image = "remnawave/backend:2";
environment = {
"API_INSTANCES" = "1";
"APP_PORT" = "3000";
"BANDWIDTH_USAGE_NOTIFICATIONS_ENABLED" = "false";
"BANDWIDTH_USAGE_NOTIFICATIONS_THRESHOLD" = "[60, 80]";
"CLOUDFLARE_TOKEN" = "ey...";
"DATABASE_URL" = "postgresql://remnawave:remnawave@remnawave-db:5432/postgres";
"FRONT_END_DOMAIN" = "*";
"IS_DOCS_ENABLED" = "false";
"IS_TELEGRAM_NOTIFICATIONS_ENABLED" = "false";
"JWT_API_TOKENS_SECRET" =
"787aa44c10130a9fa17ea3ea50c1248dd3e868f74941b96c09d608051399f88b95b67cd68d045aa39658b4b3fe933bf2b2c1437522498976f39f85ae1eab40da";
"JWT_AUTH_SECRET" =
"2bc14bacb6b82ce9e3ef69f8dd7bfb6b8a531f4f516902735d1d8f1bac8ff9b5077398f95b942b1adafc0ca1da4cdfd24a18539fa6eb26bee3f597a45deac94a";
"METRICS_PASS" = "admin";
"METRICS_PORT" = "3001";
"METRICS_USER" = "admin";
"NOT_CONNECTED_USERS_NOTIFICATIONS_AFTER_HOURS" = "[6, 24, 48]";
"NOT_CONNECTED_USERS_NOTIFICATIONS_ENABLED" = "false";
"PANEL_DOMAIN" = "rw.zeroq.ru";
"POSTGRES_DB" = "remnawave";
"POSTGRES_PASSWORD" = "gQLqOm2jK/Z1oBXCD18XSgr76M8ZqkVhHZbNKvZQXnY=";
"POSTGRES_USER" = "remnawave";
"REDIS_SOCKET" = "/var/run/valkey/valkey.sock";
"SCALAR_PATH" = "/scalar";
"SUB_PUBLIC_DOMAIN" = "rw.zeroq.ru/api/sub";
"SWAGGER_PATH" = "/docs";
"TELEGRAM_BOT_TOKEN" = "change_me";
"TELEGRAM_NOTIFY_CRM" = "change_me";
"TELEGRAM_NOTIFY_NODES" = "change_me";
"TELEGRAM_NOTIFY_SERVICE" = "change_me";
"TELEGRAM_NOTIFY_TBLOCKER" = "change_me";
"TELEGRAM_NOTIFY_USERS" = "change_me";
"WEBHOOK_ENABLED" = "false";
"WEBHOOK_SECRET_HEADER" = "vsmu67Kmg6R8FjIOF1WUY8LWBHie4scdEqrfsKmyf4IAf8dY3nFS0wwYHkhh6ZvQ";
"WEBHOOK_URL" = "https://your-webhook-url.com/endpoint";
};
volumes = [
"valkey-socket:/var/run/valkey:rw"
];
ports = [
"127.0.0.1:3000:3000/tcp"
"127.0.0.1:3001:3001/tcp"
];
dependsOn = [
"remnawave-db"
"remnawave-redis"
];
log-driver = "journald";
extraOptions = [
"--health-cmd=curl -f http://localhost:3001/health"
"--health-interval=30s"
"--health-retries=3"
"--health-start-period=30s"
"--health-timeout=5s"
"--hostname=remnawave"
"--network-alias=remnawave"
"--network=remnawave-network"
];
};
systemd.services."podman-remnawave" = {
serviceConfig = {
Restart = lib.mkOverride 90 "always";
};
after = [
"podman-network-remnawave-network.service"
"podman-volume-valkey-socket.service"
];
requires = [
"podman-network-remnawave-network.service"
"podman-volume-valkey-socket.service"
];
partOf = [
"podman-compose-remnawave-root.target"
];
wantedBy = [
"podman-compose-remnawave-root.target"
];
};
virtualisation.oci-containers.containers."remnawave-db" = {
image = "postgres:17.6";
environment = {
"API_INSTANCES" = "1";
"APP_PORT" = "3000";
"BANDWIDTH_USAGE_NOTIFICATIONS_ENABLED" = "false";
"BANDWIDTH_USAGE_NOTIFICATIONS_THRESHOLD" = "[60, 80]";
"CLOUDFLARE_TOKEN" = "ey...";
"DATABASE_URL" = "postgresql://remnawave:remnawave@remnawave-db:5432/postgres";
"FRONT_END_DOMAIN" = "*";
"IS_DOCS_ENABLED" = "false";
"IS_TELEGRAM_NOTIFICATIONS_ENABLED" = "false";
"JWT_API_TOKENS_SECRET" =
"787aa44c10130a9fa17ea3ea50c1248dd3e868f74941b96c09d608051399f88b95b67cd68d045aa39658b4b3fe933bf2b2c1437522498976f39f85ae1eab40da";
"JWT_AUTH_SECRET" =
"2bc14bacb6b82ce9e3ef69f8dd7bfb6b8a531f4f516902735d1d8f1bac8ff9b5077398f95b942b1adafc0ca1da4cdfd24a18539fa6eb26bee3f597a45deac94a";
"METRICS_PASS" = "admin";
"METRICS_PORT" = "3001";
"METRICS_USER" = "admin";
"NOT_CONNECTED_USERS_NOTIFICATIONS_AFTER_HOURS" = "[6, 24, 48]";
"NOT_CONNECTED_USERS_NOTIFICATIONS_ENABLED" = "false";
"PANEL_DOMAIN" = "rw.zeroq.ru";
"POSTGRES_DB" = "";
"POSTGRES_PASSWORD" = "";
"POSTGRES_USER" = "";
"REDIS_SOCKET" = "/var/run/valkey/valkey.sock";
"SCALAR_PATH" = "/scalar";
"SUB_PUBLIC_DOMAIN" = "rw.zeroq.ru/api/sub";
"SWAGGER_PATH" = "/docs";
"TELEGRAM_BOT_TOKEN" = "change_me";
"TELEGRAM_NOTIFY_CRM" = "change_me";
"TELEGRAM_NOTIFY_NODES" = "change_me";
"TELEGRAM_NOTIFY_SERVICE" = "change_me";
"TELEGRAM_NOTIFY_TBLOCKER" = "change_me";
"TELEGRAM_NOTIFY_USERS" = "change_me";
"TZ" = "UTC";
"WEBHOOK_ENABLED" = "false";
"WEBHOOK_SECRET_HEADER" = "vsmu67Kmg6R8FjIOF1WUY8LWBHie4scdEqrfsKmyf4IAf8dY3nFS0wwYHkhh6ZvQ";
"WEBHOOK_URL" = "https://your-webhook-url.com/endpoint";
};
volumes = [
"remnawave-db-data:/var/lib/postgresql/data:rw"
];
ports = [
"127.0.0.1:6767:5432/tcp"
];
log-driver = "journald";
extraOptions = [
"--health-cmd=pg_isready -U \${POSTGRES_USER} -d \${POSTGRES_DB}"
"--health-interval=3s"
"--health-retries=3"
"--health-timeout=10s"
"--hostname=remnawave-db"
"--network-alias=remnawave-db"
"--network=remnawave-network"
];
};
systemd.services."podman-remnawave-db" = {
serviceConfig = {
Restart = lib.mkOverride 90 "always";
};
after = [
"podman-network-remnawave-network.service"
"podman-volume-remnawave-db-data.service"
];
requires = [
"podman-network-remnawave-network.service"
"podman-volume-remnawave-db-data.service"
];
partOf = [
"podman-compose-remnawave-root.target"
];
wantedBy = [
"podman-compose-remnawave-root.target"
];
};
virtualisation.oci-containers.containers."remnawave-redis" = {
image = "valkey/valkey:9-alpine";
volumes = [
"valkey-socket:/var/run/valkey:rw"
];
cmd = [
"valkey-server"
"--save"
""
"--appendonly"
"no"
"--maxmemory-policy"
"noeviction"
"--loglevel"
"warning"
"--unixsocket"
"/var/run/valkey/valkey.sock"
"--unixsocketperm"
"777"
"--port"
"0"
];
log-driver = "journald";
extraOptions = [
"--health-cmd=[\"valkey-cli\", \"-s\", \"/var/run/valkey/valkey.sock\", \"ping\"]"
"--health-interval=3s"
"--health-retries=3"
"--health-timeout=3s"
"--hostname=remnawave-redis"
"--network-alias=remnawave-redis"
"--network=remnawave-network"
];
};
systemd.services."podman-remnawave-redis" = {
serviceConfig = {
Restart = lib.mkOverride 90 "always";
};
after = [
"podman-network-remnawave-network.service"
"podman-volume-valkey-socket.service"
];
requires = [
"podman-network-remnawave-network.service"
"podman-volume-valkey-socket.service"
];
partOf = [
"podman-compose-remnawave-root.target"
];
wantedBy = [
"podman-compose-remnawave-root.target"
];
};
# Networks
systemd.services."podman-network-remnawave-network" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "podman network rm -f remnawave-network";
};
script = ''
podman network inspect remnawave-network || podman network create remnawave-network --driver=bridge
'';
partOf = [ "podman-compose-remnawave-root.target" ];
wantedBy = [ "podman-compose-remnawave-root.target" ];
};
# Volumes
systemd.services."podman-volume-remnawave-db-data" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
podman volume inspect remnawave-db-data || podman volume create remnawave-db-data --driver=local
'';
partOf = [ "podman-compose-remnawave-root.target" ];
wantedBy = [ "podman-compose-remnawave-root.target" ];
};
systemd.services."podman-volume-valkey-socket" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
podman volume inspect valkey-socket || podman volume create valkey-socket --driver=local
'';
partOf = [ "podman-compose-remnawave-root.target" ];
wantedBy = [ "podman-compose-remnawave-root.target" ];
};
# Root service
# When started, this will automatically create all resources and start
# the containers. When stopped, this will teardown all resources.
systemd.targets."podman-compose-remnawave-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
};
}
+198
View File
@@ -0,0 +1,198 @@
{
config,
lib,
pkgs,
inputs,
xlib,
...
}:
{
# Runtime
virtualisation.podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
};
# 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 ];
};
virtualisation.oci-containers.backend = "podman";
# Containers
virtualisation.oci-containers.containers."remnawave-panel-1" = {
image = "ghcr.io/remnawave/backend:latest";
environment = {
"API_INSTANCES" = "1";
"APP_PORT" = "3000";
"BANDWIDTH_USAGE_NOTIFICATIONS_ENABLED" = "false";
"BANDWIDTH_USAGE_NOTIFICATIONS_THRESHOLD" = "[60, 80]";
"FRONT_END_DOMAIN" = "*";
"IS_DOCS_ENABLED" = "false";
"IS_TELEGRAM_NOTIFICATIONS_ENABLED" = "false";
"METRICS_PASS" = "admin";
"METRICS_PORT" = "3001";
"METRICS_USER" = "admin";
"NOT_CONNECTED_USERS_NOTIFICATIONS_AFTER_HOURS" = "[6, 24, 48]";
"NOT_CONNECTED_USERS_NOTIFICATIONS_ENABLED" = "false";
"PANEL_DOMAIN" = "rw.zeroq.su";
"POSTGRES_DB" = "remnawave";
"POSTGRES_USER" = "remnawave";
"REDIS_SOCKET" = "/var/run/valkey/valkey.sock";
"SCALAR_PATH" = "/scalar";
"SUB_PUBLIC_DOMAIN" = "rw.zeroq.su/api/sub";
"SWAGGER_PATH" = "/docs";
# "TELEGRAM_BOT_TOKEN" = "change_me";
# "TELEGRAM_NOTIFY_CRM" = "change_me";
# "TELEGRAM_NOTIFY_NODES" = "change_me";
# "TELEGRAM_NOTIFY_SERVICE" = "change_me";
# "TELEGRAM_NOTIFY_TBLOCKER" = "change_me";
# "TELEGRAM_NOTIFY_USERS" = "change_me";
"WEBHOOK_ENABLED" = "false";
# "WEBHOOK_URL" = "https://your-webhook-url.com/endpoint";
};
environmentFiles = [
"/run/secrets/remnawave-env"
];
ports = [
"3003:3003/tcp"
];
log-driver = "journald";
extraOptions = [
"--network-alias=remnawave-panel-1"
"--network=host" # "--network=remnawavebackend_default"
];
};
systemd.services."podman-remnawave-panel-1" = {
serviceConfig = {
Restart = lib.mkOverride 90 "always";
};
partOf = [
"podman-compose-remnawave-root.target"
];
wantedBy = [
"podman-compose-remnawave-root.target"
];
};
# Builds
# systemd.services."podman-build-remnawave-panel-1" = {
# path = [ pkgs.podman pkgs.git ];
# serviceConfig = {
# Type = "oneshot";
# TimeoutSec = 300;
# };
# script = ''
# cd /mnt/s/Deploy/remnawave-backend
# podman build -t compose2nix/remnawave-panel-1 .
# '';
# };
# Root service
# When started, this will automatically create all resources and start
# the containers. When stopped, this will teardown all resources.
systemd.targets."podman-compose-remnawave-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
};
services = {
postgresql = {
ensureDatabases = [ "remnawave" ];
ensureUsers = [
{
name = "remnawave";
ensureDBOwnership = true;
}
];
};
};
systemd.services = {
remnawave-env = {
description = "Generate remnawave env file";
requiredBy = [ "podman-remnawave-panel-1.service" ];
before = [ "podman-remnawave-panel-1.service" ];
serviceConfig = {
Type = "oneshot";
User = "root";
};
script = ''
cat > /run/secrets/remnawave-env <<EOF
DATABASE_URL=$(cat ${config.sops.secrets.DATABASE_URL.path})
DATABASE_PASSWORD=$(cat ${config.sops.secrets.DATABASE_PASSWORD.path})
JWT_AUTH_SECRET=$(cat ${config.sops.secrets.JWT_AUTH_SECRET.path})
JWT_API_TOKENS_SECRET=$(cat ${config.sops.secrets.JWT_API_TOKENS_SECRET.path})
WEBHOOK_SECRET_HEADER=$(cat ${config.sops.secrets.WEBHOOK_SECRET_HEADER.path})
EOF
chmod 600 /run/secrets/remnawave-env
'';
wantedBy = [ "multi-user.target" ];
};
remnawave-db-init = {
description = "Initialize Remnawave DB user";
after = [ "postgresql.service" ];
requires = [ "postgresql.service" ];
serviceConfig = {
Type = "oneshot";
User = "postgres";
};
script = ''
PASSWORD=$(cat ${config.sops.secrets.DATABASE_PASSWORD.path})
${pkgs.postgresql}/bin/psql -v ON_ERROR_STOP=1 <<EOF
DO \$\$
BEGIN
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname='remnawave') THEN
EXECUTE format('ALTER ROLE remnawave WITH PASSWORD %L', '$PASSWORD');
END IF;
END
\$\$ LANGUAGE plpgsql;
EOF
'';
wantedBy = [ "multi-user.target" ];
};
};
sops.secrets = {
DATABASE_PASSWORD = {
key = "DATABASE_PASSWORD";
sopsFile = ./secrets/remnawave.yaml;
owner = "postgres";
group = "postgres";
mode = "0400";
};
WEBHOOK_SECRET_HEADER = {
key = "WEBHOOK_SECRET_HEADER";
sopsFile = ./secrets/remnawave.yaml;
mode = "0400";
};
DATABASE_URL = {
key = "DATABASE_URL";
sopsFile = ./secrets/remnawave.yaml;
mode = "0400";
};
JWT_AUTH_SECRET = {
key = "JWT_AUTH_SECRET";
sopsFile = ./secrets/remnawave.yaml;
mode = "0400";
};
JWT_API_TOKENS_SECRET = {
key = "JWT_API_TOKENS_SECRET";
sopsFile = ./secrets/remnawave.yaml;
mode = "0400";
};
};
systemd.tmpfiles.rules = [
"d ${xlib.dirs.services-mnt-folder} 0755 root root -"
"d ${xlib.dirs.services-mnt-folder}/containers 0755 root root -"
"d ${xlib.dirs.services-mnt-folder}/containers/remnawave 0755 root root -"
];
}
+20
View File
@@ -0,0 +1,20 @@
DATABASE_PASSWORD: ENC[AES256_GCM,data:DRactR3j13q9zHFO0puGhBv09CX9YJc9KtFSLuOUVV/U7O/Nmh5Hb4ID0+A=,iv:5ErptccuQIVxfZKIcpfO5yVtcM0zE7kPn4v7kHctTP8=,tag:e3w8Rz+wGLTrxDSNftmkLw==,type:str]
WEBHOOK_SECRET_HEADER: ENC[AES256_GCM,data:ZJYKwG1a8JH0ODeRnrv395plPN7PA18+gi3R/ueGd/r8OrtbVGL8UnZ/6HgW9M+/jCGWNclD5mZfyRg3He6hDg==,iv:PIYCD2n5ED5T24JfG6xhrvStd6jySCoBHhA8hUFIEMk=,tag:WWpfI1q9l9R44FRNaqIiaA==,type:str]
DATABASE_URL: ENC[AES256_GCM,data:6plSDBUKyZVAO/djw3bPTthtS11yljwCGfQcIUqQetxROk5hwwVEGNMd1e6nGgS7eTtqJHW6uStkw58=,iv:RDjCVPDgPhMEbCriW0xjrxzcAolmyD55fbkD95LZMlE=,tag:ovH2D3eTXtHFmZba6u+IZg==,type:str]
JWT_AUTH_SECRET: ENC[AES256_GCM,data:rzsOoIwJwwzCd+QbelcWYjfe1Bt7Y1ihrEn9tsxNyZnfmVVIkpFC948ne3YhUZ0CXYEDJYen/SFQgyyWsPwTwZgcy11mIZnROh4vlOJvPWILB1IlVQF/JDDts3fvXfe9HQ7ujBwkw5uR/33Rm+yxeLHMWTsn644DZSyKFi53QqY=,iv:aB3meC8BeEsLmiF0UMjQ60xipjGTJ0Qg1XqRHNujPFE=,tag:s/YcehFUrArknqHlXo3MYw==,type:str]
JWT_API_TOKENS_SECRET: ENC[AES256_GCM,data:m6EtsdMNDRJk99LEYRgTk5rFNUYux4I2UWo/8AWy+2HJI8tRiOrBO284T3W/N+2/3fbty96sVB/SD8bjIIsxHij51sZTYi4+hdU7VxANGPdiMckKAXtvj3FMsVwrtW4MgRbH0j7taiDtnxVp6F3Cl7Sb0GamKFJjgAZnA3weN/8=,iv:rnNB1AzosstyF3c2pUcvYVTyUWcmo8Du+/b09OgcN9w=,tag:O81gnP2nXJ3JvgkivzVgkw==,type:str]
sops:
age:
- recipient: age13l2gtk0nzr484zprp7e0pkrt0ne0j4asyn2pjmlaw73nte7t7d8q4sqtxm
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3dWxEUDdhV2Z4V3JpNzNL
T0ZkYjlLWTNFV2c0Vm5Vb05xK09sQ0RxU0ZVCjhaSVhsSmoyZCtLYlNOVlNnTGFv
TTU1Y3I5U3UrcXhOOGt6U0hoSGw0YlUKLS0tIEJIbnJwNUk4Z0ZGNTRQRVFjWFhv
d0sreEpsMjV5M2JoRHFnVkpqeGhMM1EKX7K3Q2yj8EZuzCIxWIc+6Xeo+0lidPse
wstbeHV8ygWvOjIxjRGPOETQ17GLLl3eNEsk6P2gytZchmLkLYKKsA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-04-04T23:08:05Z"
mac: ENC[AES256_GCM,data:vWNFqNiWleqvRItVB0X5W/7e/F+LEWmfIKtnjbV5xwgyZ1jkP2N2wkw8CpzDNN5xwrkTdKfziGt+Psg8p72uMfvqns1lgQzvSbT3W8Di7bbIxgvwyBV8qCCpYn95ra/KRmV+oefhhr/1RlBN8wNb3oZI/m7sH8lv9d0sKw5SrE8=,iv:UAOifm4itrG6M3VKi7zelxL73lcpQkGXLSa/dk/hbvM=,tag:rzCK7Id3zQVF8VSDJV3nhg==,type:str]
unencrypted_suffix: _unencrypted
version: 3.12.2
@@ -0,0 +1,17 @@
JWT_SECRET=ENC[AES256_GCM,data:+ut+3v4KrckbDT5m85JhQd8x2ayF55Uy5FiEw8qTJoBgz7Zz+HprhxPB09RDd6CX11SrcsDcf6vW3wOE7IdeQA==,iv:c3GqspoQH2+2NQvsqip3bs4XW1PWSZtK+l7HzE83Qj8=,tag:hDqFgPa8gYLqPeA0svGWfw==,type:str]
ADMIN_PASSWORD=ENC[AES256_GCM,data:UxcSEO7opTme9DR4XM3/FQ==,iv:nSpFt7rVp0K+hAc3aAoorw5XDMNK0V+zeBw4GHwTsOs=,tag:fQ1u/WtlVfEhc7fZnLnboQ==,type:str]
APP_URL=ENC[AES256_GCM,data:OJAv0C1DHYbFltgXmcSG/s97Lf3qJovkcEsPA9Xr,iv:Fw9Mh/+dYgah+/OWPBtRRXkO42KXWvKIuylyXfcaRK8=,tag:a6A8xS9aRyjvEfZvSxgMuQ==,type:str]
CORS_ORIGINS=ENC[AES256_GCM,data:z4MvIbQcvk+aUaME/llV7rWaDtCFYIT0nVGtlD8j,iv:oAL5NcWOfez+vVbKoibUIOagePROKW+4QV81sK+Cets=,tag:+QftIwvmTADEFMEz+ZCh4A==,type:str]
SMTP_HOST=
SMTP_PORT=ENC[AES256_GCM,data:QnI=,iv:PQwsVoOnLmTrnpUTaQAEOX3VG2hTLm/qnZjwIOL9kac=,tag:SZxCnlr0qTxH65bZ53rvQQ==,type:str]
SMTP_USER=
SMTP_PASSWORD=
SMTP_FROM=ENC[AES256_GCM,data:TaHhXO7WVUzywpUxTr5l+IG7QfXY,iv:gLqOSZBBzC9v/BT+r+ENLjApTmLsra2jDbenJLHn4AY=,tag:HCmGtfnVIjDktQpTtUbc9A==,type:str]
NOTIFY_EMAIL=
TAPE_LABEL_REGEX=
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnb283UjRSRU1SYjBxZWlz\nOWw2cXM2TTU2QmdWUG5nUU9vWVZhUDZoelJRCi9McmV0Q05hNVpXSllsbUYwdkEw\nTnU3OWRCcFhrQzg4blhuRFJjdDVkUFkKLS0tIEt4Nzc2ZWtOL1VQQzVObzZWYURu\nWFUwVWp5OUhDME0xVlBRS3psdVBSd0EKsy77QR7CveXQdKlo+JeNSaNpnUh//AoP\nV+hbUSIj05Ws20rr9uk8uTDnjnc91r2vxGWxznXf6M9putZfARBdfQ==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age13l2gtk0nzr484zprp7e0pkrt0ne0j4asyn2pjmlaw73nte7t7d8q4sqtxm
sops_lastmodified=2026-09-24T13:11:03Z
sops_mac=ENC[AES256_GCM,data:xJO2u9jQM8XiVYekVvwN+iv3megGpf80F1ANib9Kro/kgvTQUZU14jmku8OjfRtjrFsM9b/cBr+ml0Z+MSknmwtR4D3mfRIa0yFfqmS/VZJs8SrH2+c/a8kYGhDNcWgAbx+u/tZB8q0QrQsbDYmN8yvsNwWi2ixMLKlv2thPIbA=,iv:CEgU5499Gr0gD+M5iSYJ315r7RU9RWKKapXywVCQivo=,tag:9YTO7K/zsINSOXfR6PaG8A==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_version=3.13.3
+188
View File
@@ -0,0 +1,188 @@
{
config,
lib,
pkgs,
xlib,
...
}:
# TapeRotation — web app for tracking and rotation of backup tape
# cartridges. https://github.com/ElizarovEugene/TapeRotation
#
# Podman adaptation of the upstream docker-compose deployment. Two
# containers on a shared "taperotation_default" network (mirrors the
# compose project network):
# - taperotation-backend: FastAPI/uvicorn on :8001, SQLite at /data,
# file attachments at /app/uploads
# - taperotation-frontend: nginx serving the built React app on :80,
# proxying /api to http://backend:8001
# The backend container gets a static IP on the shared network and the
# frontend maps "backend" → that IP via --add-host, because this host's
# CoreDNS service owns port 53 on every interface: the podman network DNS
# plugin (aardvark-dns) cannot bind on the network gateway, so a network
# with dns_enabled would refuse to attach containers.
#
# Published host port 5174 → container:80 for the web UI. Keep it out
# of networking.firewall like the other panel ports and front it with an
# nginx vhost, e.g. in server/nginx.nix:
# { domain = "tape-rotation.zeroq.su"; port = 5174; }
# and set APP_URL / CORS_ORIGINS in the sops-encrypted env file.
#
# Instance config lives in one sops-encrypted .env file (mirrors the
# upstream .env.example, sops-nix format = "dotenv", key = "" → whole
# file): sops modules/containers/secrets/tape-rotation.env
# On first boot the admin account is created from ADMIN_USERNAME /
# ADMIN_PASSWORD from that file.
let
panel = "${xlib.dirs.services-nodes-folder}/${xlib.device.hostname}/tape-rotation";
in
{
virtualisation = {
podman = {
enable = true;
autoPrune = {
enable = true;
flags = [ "--all" ];
};
dockerCompat = true;
};
oci-containers = {
backend = "podman";
containers = {
"taperotation-backend" = {
image = "docker.io/elizaroveugene/taperotation-backend:latest";
environment = {
"DATABASE_URL" = "sqlite:////data/taperotation.db";
"JWT_EXPIRE_MINUTES" = "480";
"ADMIN_USERNAME" = "admin";
"ADMIN_LANGUAGE" = "en";
"NOTIFY_DAYS_BEFORE" = "7";
"TZ" = "Europe/Moscow";
};
environmentFiles = [ "/run/secrets/tape-rotation-env" ];
volumes = [
"${panel}/db:/data:rw"
"${panel}/uploads:/app/uploads:rw"
];
log-driver = "journald";
extraOptions = [
"--network=taperotation_default"
# Static IP the frontend reaches "backend" at (see --add-host
# in the frontend container; network DNS is disabled).
"--ip=10.89.0.10"
];
};
"taperotation-frontend" = {
image = "docker.io/elizaroveugene/taperotation-frontend:latest";
ports = [
"0.0.0.0:5174:80/tcp"
];
log-driver = "journald";
extraOptions = [
"--network=taperotation_default"
# Baked-in nginx upstream is http://backend:8001; resolve it via
# /etc/hosts since the network has no DNS plugin.
"--add-host=backend:10.89.0.10"
];
};
};
};
};
# 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 ];
};
systemd = {
services = {
"podman-taperotation-backend" = {
serviceConfig.Restart = lib.mkOverride 90 "always";
after = [ "podman-network-taperotation_default.service" ];
requires = [ "podman-network-taperotation_default.service" ];
partOf = [ "podman-compose-tape-rotation-root.target" ];
wantedBy = [ "podman-compose-tape-rotation-root.target" ];
};
"podman-taperotation-frontend" = {
serviceConfig.Restart = lib.mkOverride 90 "always";
after = [
"podman-network-taperotation_default.service"
"podman-taperotation-backend.service"
];
requires = [ "podman-network-taperotation_default.service" ];
partOf = [ "podman-compose-tape-rotation-root.target" ];
wantedBy = [ "podman-compose-tape-rotation-root.target" ];
};
"podman-network-taperotation_default" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "podman network rm -f taperotation_default";
};
script = ''
# Always (re)create the stack network: the host's CoreDNS owns :53
# on every interface, so the network DNS plugin (aardvark-dns)
# can't bind on the gateway → --disable-dns. --subnet backs the
# backend's static IP. Recreate-on-start also self-heals after a
# `podman system prune` removed the (temporarily unused) network.
podman network rm -f taperotation_default >/dev/null 2>&1 || true
podman network create --disable-dns --subnet=10.89.0.0/24 taperotation_default
'';
partOf = [ "podman-compose-tape-rotation-root.target" ];
wantedBy = [ "podman-compose-tape-rotation-root.target" ];
};
"podman-update-taperotation" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
TimeoutSec = 300;
};
script = ''
podman pull docker.io/elizaroveugene/taperotation-backend:latest
podman pull docker.io/elizaroveugene/taperotation-frontend:latest
systemctl restart podman-taperotation-backend.service podman-taperotation-frontend.service
'';
};
};
# Starts/stops together with all TapeRotation containers.
targets."podman-compose-tape-rotation-root" = {
unitConfig.Description = "Root target generated by compose2nix.";
wantedBy = [ "multi-user.target" ];
};
# Enable automatic image updates:
# systemd.timers."podman-update-taperotation" = {
# 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}/uploads" "0755" "root" "root")
# Relabel panel dir for SELinux so containers can access it.
(xlib.helpers.mkTmpfile "Z" panel "0755" "root" "root")
];
};
sops.secrets."tape-rotation-env" = {
# key = "" → decrypt the whole file, not a single key.
# format = "dotenv" → the file IS one .env ready for environmentFiles:
# every non-comment KEY=VALUE line lands in the container environment.
key = "";
format = "dotenv";
sopsFile = ./secrets/tape-rotation.env;
mode = "0400";
};
}
+34 -36
View File
@@ -1,62 +1,60 @@
{ inputs, ... }@flakeContext:
let
# NixOS-only modules. termux runs nix-on-droid (its own module system,
# class = "nixOnDroid"): options like services.*, users.*, sops.*, disko.*
# and nixpkgs.overlays (flake assertion) do not exist there.
#
# `xlib` arrives as a module argument (see lib/mkSystem.nix) and is plain
# data, not a module option, so nothing here has to declare or set it.
defaultModule =
{
config,
lib,
xlib,
deviceType,
...
}:
{
imports = with inputs; [
./essentials
./users.nix
./options.nix
(./. + "/${deviceType}") # specific modules
imports =
with inputs;
[
./essentials
./options.nix
./users.nix
home-manager.nixosModules.home-manager # home-manager module
# nix-index-database.nixosModules.nix-index # nix-index module
grub2-themes.nixosModules.default # grub2 themes module
sops-nix.nixosModules.sops # sops module
self.homeConfigurations.default.nixosModule # default homeConfigurations
disko.nixosModules.disko # disko module
noctalia.nixosModules.default
home-manager.nixosModules.home-manager # home-manager module
# nix-index-database.nixosModules.nix-index # nix-index module
grub2-themes.nixosModules.default # grub2 themes module
sops-nix.nixosModules.sops # sops module
justray.nixosModules.default
self.homeConfigurations.default.nixosModule # default homeConfigurations
disko.nixosModules.disko # disko module
]
# desktop class: primary/secondary
++ lib.optional xlib.isDesktop ./desktop
# device-type module dir; "minimal" has no extra modules
++ lib.optional (!xlib.isDesktop && xlib.device.type != "minimal") (./. + "/${xlib.device.type}");
nixpkgs.overlays = with inputs; [
self.nixosOverlays.default
];
nixpkgs.overlays = [
inputs.self.nixosOverlays.default
];
_module.args = {
inputs = inputs;
xlib = config.xlib;
};
networking.hostName = lib.mkDefault xlib.device.hostname;
};
publicModule =
strictModule =
{
config,
lib,
xlib,
...
}:
{
imports = with inputs; [
./essentials
./users.nix
imports = [
# ./essentials
# ./users.nix
./options.nix
disko.nixosModules.disko # disko module
sops-nix.nixosModules.sops # sops module
(./. + "/${xlib.device.type}")
# sops-nix.nixosModules.sops
];
_module.args = {
inputs = inputs;
xlib = config.xlib;
};
};
in
{
nixosModules = {
default = defaultModule;
public = publicModule;
strict = strictModule;
};
}
+58 -19
View File
@@ -1,7 +1,9 @@
{
config,
inputs,
lib,
pkgs,
xlib,
...
}:
{
@@ -10,6 +12,54 @@
./theming.nix
];
# Things every desktop host has in common
hardware.bluetooth.enable = true;
i18n.extraLocaleSettings = {
LC_ADDRESS = "ru_RU.UTF-8";
LC_IDENTIFICATION = "ru_RU.UTF-8";
LC_MEASUREMENT = "ru_RU.UTF-8";
LC_MONETARY = "ru_RU.UTF-8";
LC_NAME = "ru_RU.UTF-8";
LC_NUMERIC = "ru_RU.UTF-8";
LC_PAPER = "ru_RU.UTF-8";
LC_TELEPHONE = "ru_RU.UTF-8";
LC_TIME = "ru_RU.UTF-8";
};
networking = {
networkmanager.enable = true;
firewall.enable = false;
};
security.rtkit.enable = true;
services = {
syncthing = {
enable = true;
systemService = true;
configDir = "${xlib.dirs.user-storage}/persist/Syncthing/${config.system.name}";
dataDir = "${xlib.dirs.user-home}";
group = "users";
user = "${xlib.device.username}";
};
thermald.enable = true;
xserver = {
enable = true;
xkb = {
layout = "us,ru";
variant = "";
# options = "grp:alt_shift_toggle";
};
};
libinput.enable = true;
colord.enable = true;
printing = {
enable = true;
cups-pdf.enable = true;
};
};
boot = {
plymouth = {
enable = true;
@@ -49,26 +99,15 @@
programs = {
dconf.enable = true;
gamemode.enable = true;
# steam.enable = true;
steam.enable = true;
xwayland.enable = true;
};
services = {
xserver = {
enable = true;
xkb = {
layout = "us,ru";
variant = "";
options = "grp:alt_shift_toggle";
};
};
libinput.enable = true;
colord.enable = true;
printing = {
enable = true;
cups-pdf.enable = true;
};
};
# environment.sessionVariables = {
# NIXOS_OZONE_WL = "1";
# environment = {
# systemPackages = [
# pkgs.pcbu-desktop
# ];
# # sessionVariables = {
# # NIXOS_OZONE_WL = "1";
# # };
# };
}
-1
View File
@@ -6,6 +6,5 @@
./kde.nix
# ./gnome.nix
# ./noctalia.nix
# ./xfce.nix
];
}
-36
View File
@@ -1,36 +0,0 @@
{
config,
lib,
pkgs,
...
}:
{
services.xserver.displayManager.lightdm.enable = true;
#services.displayManager.defaultSession = "lomiri";
# services.xserver.desktopManager.budgie.enable = true;
#services.xserver.displayManager.lightdm.greeters.lomiri.enable= true;
#services.desktopManager.lomiri.enable = true;
#-services.xserver.desktopManager.mate.enable = true;
#-services.xserver.desktopManager.lxqt.enable = true;
# services.xserver.desktopManager.lumina.enable = true;
# services.xserver.desktopManager.cde.enable = true;
# services.xserver.desktopManager.cinnamon.enable = true;
# services.xserver.desktopManager.enlightenment.enable = true;
# services.desktopManager.cosmic.xwayland.enable = true;
# services.desktopManager.cosmic.enable = true;
services.xserver = {
enable = true;
desktopManager = {
#xterm.enable = false;
xfce.enable = true;
xfce.enableWaylandSession = true;
};
};
#- services.xserver.desktopManager.pantheon.enable = true;
#- services.pantheon.apps.enable = true;
}
+2 -1
View File
@@ -7,7 +7,8 @@
./packages.nix
./services.nix
./settings.nix
# ./systemd-routine.nix
./ssh.nix
./systemd-routines.nix
./shell.nix
];
}
+31 -20
View File
@@ -2,13 +2,9 @@
config,
pkgs,
inputs,
xlib,
...
}:
let
master = import inputs.nixpkgs-master {
system = "x86_64-linux";
};
in
{
environment = {
systemPackages = with pkgs; [
@@ -16,7 +12,7 @@ in
btop
broot
bottom
fastfetchMinimal
fastfetch
# Encrypt
age
@@ -38,6 +34,12 @@ in
lazyjournal
systemctl-tui
# IDE
yaml-language-server
nil
fresh-editor
#flow-control
# Base
curl
# efibootmgr
@@ -53,7 +55,7 @@ in
wget
tree
dust
flow-control
tuckr
# Net Diagnostic
mtr
@@ -72,7 +74,7 @@ in
exfatprogs # for gparted exfat support
# Archivers
rar
# rar
unzip
zstd
zip
@@ -86,25 +88,32 @@ in
# To save
tuios
fresh-editor
# Test
jocalsend
lazydocker
dtop
bluetui
speedtest-cli
# jocalsend
tlrc
lazyssh
mcat
framework-tool-tui
bluetui
snitch
devenv
whosthere
devenv
# Test
rgx
net-tools
usbtree
iperf3
# lazydocker
# dtop
# framework-tool-tui
];
};
environment.variables.EDITOR = "fresh";
programs = {
# nix-ld.enable = true;
justray = {
enable = true;
};
nano = {
enable = true;
nanorc = ''
@@ -118,7 +127,6 @@ in
enable = false;
plugins = {
inherit (pkgs.yaziPlugins)
gitui
git
sudo
ouch
@@ -178,9 +186,12 @@ in
enable = true;
config = {
user = {
name = "oqyude";
name = xlib.device.username;
email = "oqyude@gmail.com";
};
pull = {
rebase = true;
};
};
};
lazygit.enable = true;
@@ -192,7 +203,7 @@ in
flake = "/etc/nixos";
clean = {
enable = true;
extraArgs = "--keep 3 --keep-since 2d";
extraArgs = "--keep 2 --keep-since 2d";
dates = "daily";
};
};
+17
View File
@@ -0,0 +1,17 @@
root-ca: ENC[AES256_GCM,data:5WxHCyp8sWl+XMpmMFQGfs+IhZx0r61JVFp7TJsn4pnCwV2KY/eyh6pEF+GF+P7eXALPEWGzdFYTzMJd6KJ+D+Ew0bLomIZ+KVhlxhgVzKG0xThrLpxSuyrrRFFfXzMz7C28v1HlBht6xtwKouPnxQcXDYTLyV52w7FrhWTXPuJ+TvaYDlHJcs3wHY5U+UiCoQZMcM8ddNkgbMqCu+/QC4G213EoJvjhQz+woXzIuqN8SYsOVmQ5su9Xy1sDMc8vIF+vl1Ax1E+T7ZqnE4E/7Y7HNzBQFpBGZjTb3JLUoyO4ALo0S36Yls12MWj/AZBmoBG8yL4wwA48FBHzGyH1aYtbWA/twJLDYzmu9saORW/TKIMrzeruxdufWU9sRs1f2BxpefAa2kZ8QTxhx2+3kUiOC1lipIjdMu9RSeq7q4HmKRTmuuXW087ere+IMhogh11RoshttB1W0/BU/dz6sYwAnCioSoOCd3n+WBGfuMNyeDjkpNJ4veg2HuU/K84rLFjNsxWnRfQML0BjUDHzFypA4dNIDo/FI0Z1cOrPojiqjwKt5Fb0XljTwfDQvkRtcZOQLVln7HfJlsU67YUCBMgJUJUTC3XKo82smZbz8JV1z8I9hVfnSfoEMiqNvDu3wKPGDpjWq0hAKD2R82keKe3Ji33Z2O4HK0e0Q7vSPP/Z5gIAay2S2aS2YLpqlvgzipqiLo2owzM2Mr4NrKRL9Vg36na7bLBPEoz+l9EK8EEiZaq+c8H2+dpmXpdmnUMQDF5yBxs4EU2M6Ga+X+2SYd1q9aZBT0yxykFkQrMUcA==,iv:Ee4tvSVLdk0Clh8vohbajEaKXJlQJjqOIu2lxfWueAI=,tag:oy99+HrMV1uGCdcAy7epug==,type:str]
intermediate-ca: ENC[AES256_GCM,data:NUMdfNy+Opi/UypY1N2N8pZZK99m3VouzLxA7EKwC2sd2Q24uEflRfucjWupAASp7duncfSjmC1jQxCQpSzwV7Qbq3+PkKD+MnrceVzz40G97C5jEKEJG6ln5RdwGX5FeIF9tZpb/gBvkWcyrPQ6wDEpzd9rCDGJgvPzg/PaFuluO6UXKpppyC9Zzl2x/dms+2pV2aQcR0rEPEuXKK7NKxc+Zk3HFgcfhe26mbayZXZxdxs44vwPqPKr6xmWRiERBlCoMEfjLSWAiVjau/RXsW+/5Mtu80rbiRUHUesdZYIX4hqfoj914Mp1gsQ+bihnfBQ3xYyxtskBfHO8+oZNvOcDhx7zl8Vo+giD4435EYEFWNxjzRtbXoR0CHu50obmDDjpPcUpkRNzjpJK3VEJM45SEA2EZyupmf+Y8o4uHmOg/xTciVHLFqVOnchb6pm9WPbkyMT9K/+O9XvN3yVJzab6se6DdnEB/8PeA4s51M0ONk6PrFXSGdfwos84BmD8uaAqGhp64sXpTuunhfcmo02b1pZ2OqlGnjam5ZJOuyJ6vPeVYB/o60Dl/w5QtdeWc93EgsxNRAOyxOsrtSKdQPuZgp7+/I4S6kYaRJS8S7mvqSNdtCdrov1vYSEoHDXtu2XaDaJrStHJsP1FmTAAmXhzeyfGosB9JX7sdOGRkskesREqkzJ5V7+O0k3cpHFJvIZIFyj12rwpPSnrB66BELAQVxkqeol2dfPWn+G1aG9dmtq3T7+C3M5wW7VE41mmVgUrx9o4/8Z3wkHkBxZXiUkN4LfFHcZXmvuHFqRYYBOUpTzxRCkW6dl/gvP0qedMR3/zplhe51Gux7IFW7/s0wvTWHqWrpxirpW+c4l/IGf53Ox7kt3D,iv:atbEM7iZALuT1UipYmxlH0k0FOPJ7VKrfPrmCs3X9Mo=,tag:w6l49PKgjlzTZa1mIVpI4A==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSamN6dkxYalBZc25EYThR
NnV0YW5oQWgrQWNhSE9Gemt3VGljaE0rTGcwCnYyV2JpdW1GbEZTRElDNFk2Y213
bk1FY1grM2tuc0UzV21ROG5BanpjbFEKLS0tIG83dnpNQzEvYVZ1ODArRjlYSFRY
WHp0NktOQVF0UW1KajhYM3U5WkZCNmsKrN8T73fg7JoT+7WheveOC3Jlxa79EFjs
ePfVY07TKEHsycFhNyjcsFCWMF2ddE7q28A+Sjg+C3SA+/cHO8U9lw==
-----END AGE ENCRYPTED FILE-----
recipient: age13l2gtk0nzr484zprp7e0pkrt0ne0j4asyn2pjmlaw73nte7t7d8q4sqtxm
lastmodified: "2026-06-23T20:42:27Z"
mac: ENC[AES256_GCM,data:XftvodNnD0YXmd1GsNt2w820CWYY3v2pXOtlFYE0k25kuKNeKqqGDgN4geTA/xh6eYMrDut6mryNHOOoWXTuU9r/NvpXotwT5/vW2s4Nq+Cd9XySceJn/Ja6aN8R5ICbq3BhmpmC3SCVXDTce4+MwIHeBmp+Lrff+mXvZ5cT1A4=,iv:FqI/KdPJFHOMHykeZj0oEcuIZsCBWF1WCK4saz9Es7g=,tag:IZ1xHkB4eqkp04L2iAbkOw==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.1
+8 -3
View File
@@ -1,12 +1,17 @@
{
config,
lib,
pkgs,
xlib,
...
}:
{
services.tailscale.enable = xlib.device.type != "wsl"; # true, if not wsl
# All real hosts (not the bare "minimal" test config) get OOM protection
# and a bounded journal.
services = {
tailscale.enable = xlib.device.type != "wsl"; # true, if not wsl
earlyoom.enable = lib.mkIf (xlib.device.type != "minimal") true;
journald.settings.Journal = lib.mkIf (xlib.device.type != "minimal") {
SystemMaxUse = "512M";
};
};
}
+63 -14
View File
@@ -1,6 +1,7 @@
{
config,
lib,
pkgs,
...
}:
{
@@ -8,33 +9,34 @@
system.nixos.label = "default";
nix = {
channel = {
enable = true;
};
# nixPath = [ "nixpkgs=flake:nixpkgs" ];
# package = pkgs.lixPackageSets.stable.lix; # maybe unstable
channel.enable = false;
nixPath = [ "nixpkgs=flake:nixpkgs" ];
settings = {
require-sigs = false;
substituters = [
"https://nix-cache.zeroq.su"
"https://cache.nixos.org"
"https://nix-community.cachix.org"
"https://mirror.yandex.ru/nixos"
"https://cache.nixos.kz"
"https://cache.xd0.zip"
# "https://cache.xd0.zip"
"https://nixos-cache-proxy.cofob.dev"
# "https://nixos-cache-proxy.sweetdogs.ru"
# "https://nixos-cache-proxy.elxreno.com"
# "https://nixos.snix.store" # https://nixos.snix.store/
];
trusted-public-keys = [
"nix-cache.zeroq.su:be5jFLkiwNyOep/McxSafB3jguBmztxx+oJ46ySyc/s="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
stalled-download-timeout = 4;
connect-timeout = 4;
stalled-download-timeout = 8;
connect-timeout = 8;
auto-optimise-store = true;
fallback = true;
# allow-import-from-derivation = false;
# keep-derivations = true;
# keep-outputs = true;
allow-import-from-derivation = true;
keep-derivations = false;
keep-outputs = false;
experimental-features = [
"flakes"
"nix-command"
@@ -43,10 +45,10 @@
};
nixpkgs = {
# flake = {
# setFlakeRegistry = false;
# setNixPath = false;
# };
flake = {
setFlakeRegistry = false;
setNixPath = false;
};
config.allowUnfree = true;
};
@@ -62,6 +64,34 @@
});
'';
};
pki.certificates = [
''
-----BEGIN CERTIFICATE-----
MIIBlDCCATmgAwIBAgIQUR+DM/LKIbokKxYPGZbCCjAKBggqhkjOPQQDAjAoMQ4w
DAYDVQQKEwVaZXJvUTEWMBQGA1UEAxMNWmVyb1EgUm9vdCBDQTAeFw0yNjA2MTMy
MjU2MDZaFw0zNjA2MTAyMjU2MDZaMCgxDjAMBgNVBAoTBVplcm9RMRYwFAYDVQQD
Ew1aZXJvUSBSb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgbhGtnlm
qd2Uv1B1VBSeg6NlXFMj4BG/k5gVu9bVFBK4cw9HVx21aHw9HhFW94P2KaySR6bu
K8tLDtzvs0xkyqNFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C
AQEwHQYDVR0OBBYEFH07/1blaqp0MVuSZIUHS9W3SjIrMAoGCCqGSM49BAMCA0kA
MEYCIQD1coTa7hqU1PAdnamAIgq1ApadDWpWfNaXPGiLCrkxTwIhAJhj/YSzqTJR
HvurdJ9m2glxV3rQHIUiVqKbQRcibObd
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBvjCCAWOgAwIBAgIRAMiJigRk8xbvHhCWN6a7D68wCgYIKoZIzj0EAwIwKDEO
MAwGA1UEChMFWmVyb1ExFjAUBgNVBAMTDVplcm9RIFJvb3QgQ0EwHhcNMjYwNjEz
MjI1NjA3WhcNMzYwNjEwMjI1NjA3WjAwMQ4wDAYDVQQKEwVaZXJvUTEeMBwGA1UE
AxMVWmVyb1EgSW50ZXJtZWRpYXRlIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
QgAE2gUlKZ/z9kt5RrdYZHnGE1TVVegn+aDmGpZk5uvF04O9k/sfjD6QE7VtjwNH
ervZKu3iBXGRg92ba0k369VJpKNmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB
/wQIMAYBAf8CAQAwHQYDVR0OBBYEFCtYg4LEAHPIMrDPO7lrxKuFvw4PMB8GA1Ud
IwQYMBaAFH07/1blaqp0MVuSZIUHS9W3SjIrMAoGCCqGSM49BAMCA0kAMEYCIQD2
nNNHqs9/mIstOxetObgg8eqbrPWHXEVQ9CDucNFmQAIhANXAz2z1Rc7hxc6er23W
I8TU6UQc8dledPvalDJLyGym
-----END CERTIFICATE-----
''
];
};
systemd.network.wait-online.enable = false;
@@ -73,4 +103,23 @@
"ru_RU.UTF-8/UTF-8"
];
};
# sops.secrets = {
# intermediate-ca = {
# format = "yaml";
# key = "intermediate-ca";
# sopsFile = ./secrets/settings.yaml;
# # owner = "nobody";
# # group = "nogroup";
# mode = "0700";
# };
# root-ca = {
# format = "yaml";
# key = "root-ca";
# sopsFile = ./secrets/settings.yaml;
# # owner = "nobody";
# # group = "nogroup";
# mode = "0700";
# };
# };
}
+14 -4
View File
@@ -1,6 +1,7 @@
{
config,
pkgs,
xlib,
...
}:
{
@@ -19,15 +20,18 @@
theme = "robbyrussell";
};
shellInit = ''
beet-n() {
echo "$*" | aichat -cer beets
}
beet-p() {
beet mod path:. playlist="$*"
local base="${xlib.dirs.user-home}/.config/beets/My"
local rel
rel=$(realpath --relative-to="$base" "$PWD")
beet mod "path:$rel" playlist="$*"
}
beet-ims() {
beet im ./ -S $*
}
beet-path() {
realpath --relative-to="${xlib.dirs.user-home}/.config/beets/My" "$1"
}
'';
shellAliases = {
# shell
@@ -39,9 +43,12 @@
gp = "git pull";
ns = "nh os switch";
gp-ns = "gp && ns";
gc = "git add . && git commit -m 'dev: автокоммит $(date +'%Y-%m-%d %H:%M:%S')'";
y = "yazi";
nix-shellp = "nix-shell --run $SHELL -p";
beet-path-library = "realpath --relative-to='${xlib.dirs.user-home}/.config/beets/My' .";
z-proxy = "export ALL_PROXY=socks5://localhost:10808";
zh-proxy = "export HTTPS_PROXY=http://localhost:10808 && export HTTP_PROXY=http://localhost:10808";
# beets
beet-ima = "beet im ./ -A";
@@ -66,4 +73,7 @@
json2nix = "nix run github:sempruijs/json2nix";
};
};
environment.sessionVariables = {
TUCKR_HOME = "$HOME/Storage/dotfiles";
};
}
+33
View File
@@ -0,0 +1,33 @@
{
config,
lib,
...
}:
{
options.host.ssh = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable the SSH server with the shared config below.";
};
};
config = lib.mkIf config.host.ssh.enable {
services.openssh = {
enable = true;
allowSFTP = true;
openFirewall = lib.mkDefault false;
hostKeys = [
{
path = "/etc/ssh/id_ed25519";
type = "ed25519";
}
];
settings = {
PasswordAuthentication = false;
PermitRootLogin = "yes";
UsePAM = true;
};
};
};
}
-26
View File
@@ -1,26 +0,0 @@
{
config,
xlib,
...
}:
{
systemd = {
services.nixos-auto-rebuild = {
description = "Auto rebuild NixOS config";
serviceConfig = {
Type = "oneshot";
User = "${xlib.device.username}";
WorkingDirectory = "/etc/nixos";
ExecStart = "gp-ns";
};
};
timers.nixos-auto-rebuild = {
description = "Run NixOS auto rebuild at 4am daily";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "*-*-* 04:00:00";
Persistent = true;
};
};
};
}
+39
View File
@@ -0,0 +1,39 @@
{
config,
pkgs,
xlib,
...
}:
{
systemd = {
services = {
nixos-prebuild = {
description = "Prebuild NixOS closure";
serviceConfig = {
CPUQuota = "20%";
User = xlib.device.username;
Group = "users";
Nice = 10;
Type = "oneshot";
WorkingDirectory = "/tmp";
Environment = [
"HOME=${xlib.dirs.user-home}"
];
ExecStart = ''
${pkgs.nix}/bin/nix build --no-link /etc/nixos#nixosConfigurations.${config.networking.hostName}.config.system.build.toplevel
'';
};
wantedBy = [ "multi-user.target" ];
};
};
timers = {
nixos-prebuild = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "*-*-* 04:00:00";
Persistent = true;
};
};
};
};
}
-31
View File
@@ -1,31 +0,0 @@
{ inputs, ... }@flakeContext:
{
config,
pkgs,
...
}:
{
systemd.services.zapret = {
enable = true;
description = "zapret complete";
unitConfig = {
After = [ "network-online.target" ];
Wants = [ "network-online.target" ];
};
wantedBy = [ "multi-user.target" ];
path = [ "/run/current-system/sw" ];
serviceConfig = {
Type = "simple";
Restart = "on-failure";
User = "root";
WorkingDirectory = "${inputs.zapret.script-dir}";
ExecStart = "/run/current-system/sw/bin/bash ./main_script.sh -nointeractive";
ExecStop = "/run/current-system/sw/bin/bash ./stop_and_clean_nft.sh";
};
};
environment = {
systemPackages = with pkgs; [
nftables
];
};
}
-9
View File
@@ -1,9 +0,0 @@
{
lib,
pkgs,
...
}:
{
# imports = [
# ];
}
+33 -120
View File
@@ -1,128 +1,41 @@
{
config,
lib,
...
}:
# Cross-module options: declared here, not in the module that reads them.
#
# An option belongs in this file when at least one context *sets* it while
# another module *reads* it — the reader cannot be the only place that knows
# the option exists. `modules/essentials/ssh.nix` does not belong here: it
# declares and reads `host.ssh.enable` itself, within one module.
{
options = {
xlib = {
device = {
type = lib.mkOption {
type = lib.types.enum [
"minimal"
"primary"
"secondary"
"server"
"vds"
"vds-new"
"wsl"
];
default = "minimal";
description = "Type of device for this host.";
};
username = lib.mkOption {
type = lib.types.str;
default = "oqyude";
description = "Username for host.";
};
hostname = lib.mkOption {
type = lib.types.str;
default = "nixos";
description = "Hostname...";
};
};
dirs = {
user-home = lib.mkOption {
type = lib.types.str;
default = "/home/${config.xlib.device.username}";
description = "User home directory.";
};
user-storage = lib.mkOption {
type = lib.types.str;
default = "${config.xlib.dirs.user-home}/Storage";
description = "User storage directory.";
};
archive-drive = lib.mkOption {
type = lib.types.str;
default = "/mnt/archive";
description = "Archive drive mount point.";
};
lamet-drive = lib.mkOption {
type = lib.types.str;
default = "/mnt/lamet";
description = "Lamet drive mount point.";
};
mobile-drive = lib.mkOption {
type = lib.types.str;
default = "/mnt/mobile";
description = "Mobile drive mount point.";
};
therima-drive = lib.mkOption {
type = lib.types.str;
default = "/mnt/therima";
description = "Therima drive mount point.";
};
vetymae-drive = lib.mkOption {
type = lib.types.str;
default = "/mnt/vetymae";
description = "Vetymae drive mount point.";
};
soptur-drive = lib.mkOption {
type = lib.types.str;
default = "/mnt/soptur";
description = "Soptur drive mount point.";
};
wsl-home = lib.mkOption {
type = lib.types.str;
default = "/mnt/c/Users/${config.xlib.device.username}";
description = "WSL home directory.";
};
wsl-storage = lib.mkOption {
type = lib.types.str;
default = "${config.xlib.dirs.wsl-home}/Storage";
description = "WSL storage directory.";
};
server-home = lib.mkOption {
type = lib.types.str;
default = "/home/${config.xlib.device.username}/External";
description = "Server home directory.";
};
server-credentials = lib.mkOption {
type = lib.types.str;
default = "${config.xlib.dirs.server-home}/Credentials/server";
description = "Server credentials directory.";
};
storage = lib.mkOption {
type = lib.types.str;
default = "${config.xlib.dirs.server-home}/Storage";
description = "General storage directory.";
};
calibre-library = lib.mkOption {
type = lib.types.str;
default = "${config.xlib.dirs.server-home}/Books-Library";
description = "Calibre library directory.";
};
music-library = lib.mkOption {
type = lib.types.str;
default = "${config.xlib.dirs.user-home}/Music";
description = "Music library directory.";
};
services-folder = lib.mkOption {
type = lib.types.str;
default = "${config.xlib.dirs.server-home}/Services";
description = "All services folder.";
};
services-mnt-folder = lib.mkOption {
type = lib.types.str;
default = "/mnt/services";
description = "All services folder.";
};
postgresql-folder = lib.mkOption {
type = lib.types.str;
default = "${config.xlib.dirs.services-mnt-folder}/postgresql";
description = "PostgreSQL service folder.";
};
};
options.host."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).
'';
};
};
}
-32
View File
@@ -1,32 +0,0 @@
{
config,
xlib,
pkgs,
...
}:
let
user = "snity";
in
{
users = {
users = {
"${user}" = {
name = "${user}";
isNormalUser = true;
group = "users";
description = "Snity";
hashedPassword = "$y$j9T$851xwObfIp7SYzIyFtH.k1$mNofT2sxEAV50Kxgmwvqc6Kj/3B/fJoPP8qgn./siEB";
homeMode = "700";
home = "/home/${user}";
extraGroups = [
"audio"
"disk"
"gamemode"
"networkmanager"
"pipewire"
"wheel"
];
};
};
};
}
+76
View File
@@ -0,0 +1,76 @@
{
config,
inputs,
lib,
pkgs,
xlib,
...
}:
let
beetsEnv = pkgs.python314.withPackages (
ps: with ps; [
anyio
beautifulsoup4
beetcamp
beets
certifi
charset-normalizer
colorama
confuse
discogs-client
filetype
h11
httpcore
httpx
httpx-socks
idna
jellyfish
langdetect
lap
llvmlite
mediafile
mutagen
numba
numpy
oauthlib
packaging
pillow
platformdirs
pycountry
pylast
pyrate-limiter
pysocks
python-dateutil
pyyaml
requests
requests-ratelimiter
scipy
six
socksio
soupsieve
typing-extensions
unidecode
urllib3
]
);
in
{
users = {
users = {
"${xlib.device.username}" = {
packages = [
beetsEnv
pkgs.mp3gain
pkgs.imagemagick
#ffmpeg
];
};
};
};
systemd.mounts = [
(xlib.helpers.mkSystemdBind {
what = "/home/${xlib.device.username}/Music";
where = "/home/${xlib.device.username}/.config/beets";
})
];
}
-9
View File
@@ -1,9 +0,0 @@
{
lib,
...
}:
{
imports = [
../desktop
];
}
-9
View File
@@ -1,9 +0,0 @@
{
lib,
...
}:
{
imports = [
../desktop
];
}
+28
View File
@@ -0,0 +1,28 @@
{
config,
pkgs,
...
}:
{
security = {
acme = {
acceptTerms = true;
defaults = {
email = "oqyude@gmail.com";
};
# certs = {
# "home.arpa" = {
# email = "oqyude@zeroq.su";
# domain = "*.home.arpa";
# server = "https://localhost:9000/acme/acme/directory";
# listenHTTP = ":80";
# dnsProvider = null;
# };
# # "turn.home.arpa" = {
# # listenHTTP = "127.0.0.1:80";
# # group = "turnserver";
# # };
# };
};
};
}
+19
View File
@@ -0,0 +1,19 @@
{
config,
inputs,
...
}:
{
services.bentopdf = {
enable = true;
domain = "pdf.private";
nginx = {
enable = true;
# virtualHost = {
# forceSSL = true;
# enableACME = true;
# };
};
# package = pkgs-stable.bentopdf;
};
}
+62 -13
View File
@@ -1,22 +1,71 @@
{
config,
xlib,
inputs,
pkgs,
xlib,
...
}:
let
# stable = import inputs.nixpkgs-previous {
# system = "x86_64-linux";
# };
libraryDir = "${xlib.dirs.services-mnt-folder}/calibre-web-library";
sourceDir = "${xlib.dirs.services-mnt-folder}/calibre-web";
targetDir = "/var/lib/calibre-web";
in
{
services.calibre-web = {
enable = true;
group = "users";
user = "${xlib.device.username}";
options = {
calibreLibrary = "${xlib.dirs.calibre-library}";
enableBookUploading = true;
enableKepubify = true;
enableBookConversion = false;
services = {
calibre-web = {
# package = stable.calibre-web;
enable = true;
# dataDir = "${xlib.dirs.services-mnt-folder}/calibre-web";
options = {
calibreLibrary = "${libraryDir}";
enableBookUploading = true;
enableKepubify = true;
enableBookConversion = false;
};
listen.ip = "0.0.0.0";
listen.port = 8083;
openFirewall = true;
};
listen.ip = "0.0.0.0";
listen.port = 8083;
openFirewall = true;
# calibre-server = {
# enable = true;
# port = 8091;
# host = "0.0.0.0";
# openFirewall = true;
# user = "calibre-web";
# group = "calibre-web";
# libraries = [
# "/var/lib/calibre-server"
# ];
# };
};
systemd.tmpfiles.rules =
xlib.helpers.mkTmpDirs {
dir = libraryDir;
mode = "0755";
user = "calibre-web";
group = "calibre-web";
types = [
"d"
"Z"
];
}
++ xlib.helpers.mkTmpDirs {
dir = sourceDir;
mode = "0755";
user = "calibre-web";
group = "calibre-web";
types = [
"d"
"Z"
];
};
fileSystems = xlib.helpers.mkBindMount {
what = sourceDir;
where = targetDir;
};
}
+10
View File
@@ -0,0 +1,10 @@
{
config,
pkgs,
...
}:
{
services.chrony = {
enable = true;
};
}
-28
View File
@@ -1,28 +0,0 @@
{
config,
lib,
pkgs,
inputs,
xlib,
...
}:
{
# fileSystems."${config.services.immich.mediaLocation}" = {
# device = "${xlib.dirs.services-folder}/immich";
# options = [
# "bind"
# "nofail"
# ];
# };
# systemd.tmpfiles.rules = [
# "z ${config.services.immich.mediaLocation} 0755 immich immich -"
# ];
# environment = {
# systemPackages = with pkgs; [
# immich-cli
# ];
# };
}
+63
View File
@@ -0,0 +1,63 @@
{
config,
pkgs,
...
}:
{
services.coredns = {
enable = true;
config = ''
zeroq.su:53 {
hosts {
109.248.161.5 x.zeroq.su
192.168.1.20 calibre.zeroq.su
192.168.1.20 dns.zeroq.su
192.168.1.20 flux.zeroq.su
192.168.1.20 git.zeroq.su
192.168.1.20 glances.zeroq.su
192.168.1.20 homebox.zeroq.su
192.168.1.20 immich.zeroq.su
192.168.1.20 kuma.zeroq.su
192.168.1.20 navidrome.zeroq.su
192.168.1.20 nextcloud.zeroq.su
192.168.1.20 office.zeroq.su
192.168.1.20 pdf.zeroq.su
192.168.1.20 syncthing.zeroq.su
192.168.1.20 talk.zeroq.su
192.168.1.20 turn.zeroq.su
fallthrough
}
cache 300
log
}
home.arpa:53 {
hosts {
192.168.1.100 vetymae.home.arpa
192.168.1.20 ca.home.arpa
192.168.1.20 calibre.home.arpa
192.168.1.20 dns.home.arpa
192.168.1.20 flux.home.arpa
192.168.1.20 git.home.arpa
192.168.1.20 glances.home.arpa
192.168.1.20 home.arpa
192.168.1.20 homebox.home.arpa
192.168.1.20 immich.home.arpa
192.168.1.20 kuma.home.arpa
192.168.1.20 navidrome.home.arpa
192.168.1.20 nextcloud.home.arpa
192.168.1.20 office.home.arpa
192.168.1.20 pdf.home.arpa
192.168.1.20 sapphira.home.arpa
192.168.1.20 syncthing.home.arpa
fallthrough
}
cache 300
log
}
.:53 {
forward . 192.168.1.1 1.1.1.1
cache 300
}
'';
};
}
+53
View File
@@ -0,0 +1,53 @@
{
config,
inputs,
lib,
pkgs,
xlib,
...
}:
# let
# acme-path = "/var/lib/acme";
# in
{
services.coturn = {
enable = false;
realm = "turn.home.arpa";
# cert = "${acme-path}/turn.home.arpa/fullchain.pem";
# pkey = "${acme-path}/turn.home.arpa/key.pem";
use-auth-secret = true;
static-auth-secret-file = config.sops.secrets.turn-secret.path;
no-cli = true;
listening-port = 3478; # TURN
# tls-listening-port = 5349; # TURNS
extraConfig = ''
min-port=49160
max-port=49200
'';
};
networking.firewall = {
allowedTCPPorts = [
3478
# 5349
];
allowedUDPPorts = [
3478
];
allowedUDPPortRanges = [
{
from = 49160;
to = 49200;
}
];
};
sops.secrets = {
turn-secret = {
format = "yaml";
key = "turn-secret";
sopsFile = ./secrets/coturn.yaml;
group = "nextcloud-spreed-signaling";
owner = "turnserver";
mode = "0440";
};
};
}
+34 -6
View File
@@ -1,30 +1,58 @@
{
lib,
xlib,
...
}:
{
imports = [
../software/beets
../containers/3x-ui.nix
../containers/tape-rotation.nix
../pkgs/beets.nix
./acme.nix
./bentopdf.nix
./calibre-web.nix
./containers
./chrony.nix
./coredns.nix
./gitea.nix
./glances.nix
./homebox.nix
./immich.nix
./miniflux.nix
./navidrome.nix
./nextcloud.nix
./nginx.nix
./open-webui.nix
./nix-serve.nix
./onlyoffice.nix
./postgresql.nix
./power.nix
./samba.nix
./stirling-pdf.nix
./syncthing.nix
./systemd.nix
./transmission.nix
./uptime-kuma.nix
# ../containers/remnawave.nix
# ./coturn.nix
# ./mealie.nix
# ./memos.nix
# ./minecraft.nix
# ./n8n.nix
# ./netdata.nix
# ./nfs.nix
# ./node-red.nix
# ./open-webui.nix
# ./rsync.nix
# ./step-ca.nix
# ./stirling-pdf.nix
# ./transmission.nix
# ./trilium.nix
# ./zerotier.nix
];
# Server's 3x-ui is the controller panel at x.zeroq.su (nginx HTTP
# terminates TLS upstream, no SNI-routing on 443 needed here because
# 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
# direct node-API access); nginx doesn't have to use it.
host."3x-ui".certDomain = "x.zeroq.su";
systemd.tmpfiles.rules = [
(xlib.helpers.mkTmpfile "d" "/mnt" "0755" "root" "root")
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-mnt-folder "0755" "root" "root")
];
}
+31
View File
@@ -0,0 +1,31 @@
{
config,
inputs,
lib,
pkgs,
xlib,
...
}:
{
services = {
gitea = {
enable = true;
stateDir = "${xlib.dirs.services-mnt-folder}/gitea";
appName = "ZeroQ Gitea Service";
settings = {
server = {
DOMAIN = "git.zeroq.su";
HTTP_PORT = 3000;
};
service.DISABLE_REGISTRATION = true;
};
};
};
systemd.tmpfiles.rules = xlib.helpers.mkTmpDirs {
dir = config.services.gitea.stateDir;
mode = "0755";
user = "gitea";
group = "gitea";
};
}
+15
View File
@@ -0,0 +1,15 @@
{
config,
lib,
pkgs,
...
}:
{
services = {
glances = {
enable = true;
openFirewall = true;
port = 61208;
};
};
}
+33
View File
@@ -0,0 +1,33 @@
{
config,
pkgs,
xlib,
...
}:
let
storage = xlib.helpers.mkServiceStorage {
name = "homebox";
user = "homebox";
group = "homebox";
};
in
{
services.homebox = {
enable = true;
settings = {
HBOX_WEB_HOST = "0.0.0.0";
HBOX_WEB_PORT = "7745";
HBOX_STORAGE_CONN_STRING = "file://${storage.target}";
HBOX_STORAGE_PREFIX_PATH = "data";
HBOX_DATABASE_DRIVER = "sqlite3";
HBOX_DATABASE_SQLITE_PATH = "${storage.target}/data/homebox.db?_pragma=busy_timeout=999&_pragma=journal_mode=WAL&_fk=1";
HBOX_OPTIONS_ALLOW_REGISTRATION = "true";
HBOX_OPTIONS_GITHUB_RELEASE_CHECK = "false";
HBOX_MODE = "production";
HOME = "${storage.target}";
TMPDIR = "${storage.target}/tmp";
};
};
systemd = storage.systemd;
}
+2 -20
View File
@@ -1,44 +1,26 @@
{
config,
inputs,
lib,
pkgs,
inputs,
xlib,
...
}:
let
master = import inputs.nixpkgs-master {
system = "x86_64-linux";
};
in
{
services = {
immich = {
enable = true;
# package = master.immich;
port = 2283;
host = "0.0.0.0";
openFirewall = true;
accelerationDevices = null;
machine-learning.enable = true;
mediaLocation = "${xlib.dirs.services-mnt-folder}/immich";
database = {
enableVectors = false;
enableVectorChord = true;
};
};
};
# fileSystems."${config.services.immich.mediaLocation}" = {
# device = "${xlib.dirs.services-folder}/immich";
# options = [
# "bind"
# "nofail"
# ];
# };
systemd.tmpfiles.rules = [
"z ${config.services.immich.mediaLocation} 0755 immich immich -"
(xlib.helpers.mkTmpfile "z" config.services.immich.mediaLocation "0755" "immich" "immich")
];
users.users.immich.extraGroups = [
+1 -1
View File
@@ -4,7 +4,7 @@
}:
{
services.mealie = {
enable = true;
enable = false;
listenAddress = "0.0.0.0";
port = 9000;
database.createLocally = true;
+2 -2
View File
@@ -5,7 +5,7 @@
}:
{
services.memos = {
enable = true;
enable = false;
openFirewall = true;
settings = {
MEMOS_MODE = "prod";
@@ -21,6 +21,6 @@
};
systemd.tmpfiles.rules = [
"z /mnt/services/memos 0750 memos memos -"
(xlib.helpers.mkTmpfile "z" "${xlib.dirs.services-mnt-folder}/memos" "0750" "memos" "memos")
];
}
+67
View File
@@ -0,0 +1,67 @@
{
config,
inputs,
pkgs,
xlib,
...
}:
let
storage = xlib.helpers.mkServiceStorage {
name = "minecraft";
user = "minecraft";
group = "minecraft";
mode = "770";
};
in
{
imports = [ inputs.nix-minecraft.nixosModules.minecraft-servers ];
nixpkgs.overlays = [ inputs.nix-minecraft.overlay ];
services.minecraft-servers = {
enable = true;
eula = true;
openFirewall = true;
dataDir = "/var/lib/minecraft";
servers = {
vanilla = {
enable = true;
package = pkgs.fabricServers.fabric-26_2.override {
jre_headless = pkgs.jdk25_headless;
};
jvmOpts = "-Xmx2G -Xms1G";
enableReload = true;
serverProperties = {
view-distance = 6;
simulation-distance = 4;
online-mode = false;
difficulty = 3;
gamemode = 1;
max-players = 5;
server-port = 25565;
motd = "ZeroQ сервак майна епта!";
enable-rcon = true;
"rcon.password" = "zeroq";
};
symlinks.mods = pkgs.linkFarmFromDrvs "mods" (
builtins.attrValues {
Lithium = pkgs.fetchurl {
name = "lithium-fabric-0.25.3+mc26.2.jar";
url = "https://cdn.modrinth.com/data/gvQqBUqZ/versions/f7vZ0VWU/lithium-fabric-0.25.3%2Bmc26.2.jar";
hash = "sha256-/d6S4jjoB1+JrX9wHyo9WFSviLqaZ2VxhKRAexBKxWM=";
};
FerriteCore = pkgs.fetchurl {
name = "ferritecore-9.0.0-fabric.jar";
url = "https://cdn.modrinth.com/data/uXXizFIs/versions/d5ddUdiB/ferritecore-9.0.0-fabric.jar";
hash = "sha256-ITlmxy7ZZ6zHOSvrKKhm+6MB/1a5l2wueAHC233mvyI=";
};
Krypton = pkgs.fetchurl {
name = "krypton-0.3.1.jar";
url = "https://cdn.modrinth.com/data/fQEb0iXm/versions/5WeL0Nkz/krypton-0.3.1.jar";
hash = "sha256-XqiQFWGXPSnlHnUUadUtkhAPNIq0YeEYb2cBLpNCDEg=";
};
}
);
};
};
};
systemd = storage.systemd;
}
-1
View File
@@ -12,7 +12,6 @@
CLEANUP_FREQUENCY = 48;
LISTEN_ADDR = "0.0.0.0:6061";
};
# adminCredentialsFile = "${inputs.zeroq-credentials}/services/miniflux/admin-pass.txt";
adminCredentialsFile = config.sops.secrets.minifluxenv.path;
};
+28
View File
@@ -0,0 +1,28 @@
{
config,
lib,
pkgs,
xlib,
inputs,
...
}:
let
storage = xlib.helpers.mkServiceStorage {
name = "n8n";
user = "nobody";
group = "nogroup";
};
in
{
services.n8n = {
enable = false;
environment = {
# N8N_USER_FOLDER = lib.mkForce "${sourceDir}";
N8N_SECURE_COOKIE = "false";
N8N_PORT = 5678;
};
openFirewall = true;
};
systemd = storage.systemd;
}
+32
View File
@@ -0,0 +1,32 @@
{
config,
inputs,
lib,
pkgs,
xlib,
...
}:
let
libraryDir = "${xlib.dirs.server-home}/Music";
pointDir = "/var/lib/services/navidrome-point";
in
{
services = {
navidrome = {
enable = true;
openFirewall = true;
# environmentFile = "";
settings = {
Address = "0.0.0.0";
Port = 4533;
MusicFolder = "${pointDir}";
};
};
};
systemd.mounts = [
(xlib.helpers.mkSystemdBind {
what = libraryDir;
where = pointDir;
})
];
}
+32
View File
@@ -0,0 +1,32 @@
{
config,
inputs,
lib,
pkgs,
...
}:
{
services = {
netdata = {
enable = false;
package = pkgs.netdata.override {
withCloudUi = true;
};
config = {
web = {
"allow connections from" = "localhost *";
"default port" = "19999";
"bind to" = "0.0.0.0";
};
};
# python = {
# enable = true;
# recommendedPythonPackages = true;
# };
};
};
networking.firewall.allowedTCPPorts = [
19999
];
}

Some files were not shown because too many files have changed in this diff Show More