Compare commits

47 Commits

Author SHA1 Message Date
a9ea135cb9 Add ghidra, wireshark
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-10-12 22:00:44 +01:00
b8699ba0b6 Add usbutils
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-10-12 21:16:37 +01:00
eb3c301ef6 Fuck you nvidia
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-10-06 18:10:15 +01:00
a75875a311 Update
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-10-06 12:03:06 +00:00
822044423e Update nvf quit keybind
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-10-02 10:07:51 +00:00
63d2dd2e93 Relax smart temperature warnings
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-10-01 08:14:40 +01:00
8235bd4cdf Fix syncthing
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-30 13:25:52 +01:00
492b643d8b Add immich
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-30 10:13:59 +01:00
6ce084b652 Add nix-community cache
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-30 09:09:35 +01:00
c870442536 Switch to IONOS
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-29 20:57:37 +01:00
81b3faaf3e Update consent duration
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-29 20:49:14 +01:00
2f286e25bc Fix nvim img-clip notification
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-29 08:56:57 +00:00
871a8dcdbf Update TV DNS whitelist
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-27 10:44:07 +01:00
5191357fcd Add signal
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-27 09:47:31 +01:00
f1d0a8b2df Update nvf, add lazygit
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-26 12:59:03 +00:00
116de857eb Update nvf
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-25 10:42:25 +00:00
80bde87757 Add dig, httpie
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-24 14:19:12 +00:00
82496be4b3 Edit git, neovim
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-24 12:59:23 +00:00
fbe424384c Update
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-24 12:29:54 +00:00
3dba5ed833 Silence shellcheck
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-23 00:10:35 +01:00
e41e8c2078 Add plex
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-22 23:53:30 +01:00
248432b132 Refactor public ip handling
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-22 10:54:59 +01:00
3bf23f860a Update comentario
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-22 09:58:06 +01:00
fc8e2db679 Add beta media endpoint
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-19 21:13:11 +01:00
183b5e334f Add vps ssh config
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-19 14:14:37 +01:00
496027b505 Update recyclarr profiles
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-16 11:25:33 +01:00
35fd86138d Add systemd unit alerts
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-16 09:36:21 +01:00
88eead5aa4 Fix grafana notifications
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 22:26:25 +01:00
8e21efdc53 Fix inverted grafana panel
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 17:48:44 +01:00
71e13f1408 Update VPN
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 17:42:55 +01:00
f72943c905 Add media notifications
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 16:36:49 +01:00
4cd670bb27 Add grafana alerts
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 15:34:52 +01:00
310950de42 Update littlelink
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 12:08:07 +01:00
d418acb16a Make gitea two-factor
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 12:08:07 +01:00
8f2cea6abf Add blog
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 12:08:07 +01:00
43b6159feb Add gitea runner image
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 12:08:07 +01:00
6b38429bac 80TiB
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 12:08:07 +01:00
615524070b Update grafana dashboards
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 12:08:06 +01:00
1727785180 Add declarative attic cache
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 12:08:06 +01:00
ffafc81ed1 Add authelia consent duration
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 12:07:54 +01:00
367d65e1ba Add comentario
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-15 12:07:53 +01:00
bab9115537 Add nix-fast-build
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-10 20:19:11 +01:00
f960808cc7 Add workaround for wsl systemd bus issue
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-10 09:49:43 +00:00
24d31f6881 Add steam on jupiter
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-07 14:12:17 +01:00
1d3a3cc805 Lobotomize jupiter cpu
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-07 00:28:07 +01:00
2c3abfa403 Add grafana system & traefik dashboards
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-06 17:22:56 +01:00
4f3bf154c0 Fix substituter settings
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-09-05 12:31:41 +01:00
156 changed files with 8104 additions and 3946 deletions

View File

@@ -25,7 +25,6 @@ NixOS dotfiles and configuration for various hosts and users.
- [`remove-host.sh`](./scripts/remove-host.sh): Remove references to a host.
- [`update-keys.sh`](./scripts/update-keys.sh): Update the encryption keys in all relevant files using `sops.yaml` configurations.
- [`update.sh`](./scripts/update.sh): Update flake and all packages.
- [`cache.sh`](./scripts/cache.sh): Build all `nixosConfiguration`s and push them to `attic`.
Any `options.nix` files create custom option definitions when present.

114
flake.lock generated
View File

@@ -10,11 +10,11 @@
]
},
"locked": {
"lastModified": 1756487002,
"narHash": "sha256-hN9RfNXy53qAkT68T+IYZpl68uE1uPOVMkw0MqC43KA=",
"lastModified": 1759227262,
"narHash": "sha256-ibKJckw+KWH6n+pscOA7DWImanr988zKB7R2Z6ZEMLM=",
"owner": "aylur",
"repo": "ags",
"rev": "8ff792dba6cc82eed10e760f551075564dd0a407",
"rev": "f68a0d03fbb94f4beacedd922ffaa0bf0f10397a",
"type": "github"
},
"original": {
@@ -30,11 +30,11 @@
]
},
"locked": {
"lastModified": 1756474652,
"narHash": "sha256-iiBU6itpEqE0spXeNJ3uJTfioSyKYjt5bNepykpDXTE=",
"lastModified": 1759688436,
"narHash": "sha256-EfTrJse33t3RP//DqESkTMCpMSdIi/wxxfa12+eP5jo=",
"owner": "aylur",
"repo": "astal",
"rev": "20bd8318e4136fbd3d4eb2d64dbabc3acbc915dd",
"rev": "12c15b44608422e494c387aba6adc1ab6315d925",
"type": "github"
},
"original": {
@@ -121,11 +121,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1754487366,
"narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=",
"lastModified": 1759362264,
"narHash": "sha256-wfG0S7pltlYyZTM+qqlhJ7GMw2fTF4mLKCIVhLii/4M=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18",
"rev": "758cf7296bee11f1706a574c77d072b8a7baa881",
"type": "github"
},
"original": {
@@ -183,11 +183,11 @@
]
},
"locked": {
"lastModified": 1756579987,
"narHash": "sha256-duCce8zGsaMsrqqOmLOsuaV1PVIw/vXWnKuLKZClsGg=",
"lastModified": 1759711004,
"narHash": "sha256-B39NxeKCnK3DJlmJKIts6njcXcVVASLUChDNmRl4dxQ=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "99a69bdf8a3c6bf038c4121e9c4b6e99706a187a",
"rev": "6f4021da5d2bb5ea7cb782ff413ecb7062066820",
"type": "github"
},
"original": {
@@ -212,11 +212,11 @@
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1754297745,
"narHash": "sha256-aD6/scLN3L4ZszmNbhhd3JQ9Pzv1ScYFphz14wHinfs=",
"lastModified": 1756744479,
"narHash": "sha256-EyZXusK/wRD3V9vDh00W2Re3Eg8UQ+LjVBQrrH9dq1U=",
"owner": "nix-community",
"repo": "lanzaboote",
"rev": "892cbdca865d6b42f9c0d222fe309f7720259855",
"rev": "747b7912f49e2885090c83364d88cf853a020ac1",
"type": "github"
},
"original": {
@@ -235,11 +235,11 @@
]
},
"locked": {
"lastModified": 1755506074,
"narHash": "sha256-SztuKbAPppW5grMJLSGO5rBCXEWCOfhb39cPDONEUfo=",
"lastModified": 1758632667,
"narHash": "sha256-C0aBPv8vqTI1QNVhygZxL0f49UERx2UejVdtyz67jhs=",
"ref": "refs/heads/main",
"rev": "ac85b6f608ed88d424621ec30f3848d621383487",
"revCount": 6,
"rev": "5e0737c20f3c265dbff604170a6433cc1e1a4b41",
"revCount": 8,
"type": "git",
"url": "https://git.karaolidis.com/karaolidis/nix-lib.git"
},
@@ -250,11 +250,11 @@
},
"mnw": {
"locked": {
"lastModified": 1748710831,
"narHash": "sha256-eZu2yH3Y2eA9DD3naKWy/sTxYS5rPK2hO7vj8tvUCSU=",
"lastModified": 1758834834,
"narHash": "sha256-Y7IvY4F8vajZyp3WGf+KaiIVwondEkMFkt92Cr9NZmg=",
"owner": "Gerg-L",
"repo": "mnw",
"rev": "cff958a4e050f8d917a6ff3a5624bc4681c6187d",
"rev": "cfbc7d1cc832e318d0863a5fc91d940a96034001",
"type": "github"
},
"original": {
@@ -289,11 +289,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1756542300,
"narHash": "sha256-tlOn88coG5fzdyqz6R93SQL5Gpq+m/DsWpekNFhqPQk=",
"lastModified": 1759381078,
"narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d7600c775f877cd87b4f5a831c28aa94137377aa",
"rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee",
"type": "github"
},
"original": {
@@ -305,11 +305,11 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1753579242,
"narHash": "sha256-zvaMGVn14/Zz8hnp4VWT9xVnhc8vuL3TStRqwk22biA=",
"lastModified": 1754788789,
"narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "0f36c44e01a6129be94e3ade315a5883f0228a6e",
"rev": "a73b9c743612e4244d865a2fdee11865283c04e6",
"type": "github"
},
"original": {
@@ -328,11 +328,11 @@
]
},
"locked": {
"lastModified": 1756630008,
"narHash": "sha256-weZiVKbiWQzTifm6qCxzhxghEu5mbh9mWNUdkzOLCR0=",
"lastModified": 1759742968,
"narHash": "sha256-yk56xZpanCPlhowzIEdS2GfPDG0yQ4kE/j85lJbAX1Y=",
"owner": "nix-community",
"repo": "NUR",
"rev": "f6a5a7b60dd6065e78ef06390767e689ffa3c23f",
"rev": "9ea4f672c7138273a4131dd25038da49306685b8",
"type": "github"
},
"original": {
@@ -358,11 +358,11 @@
]
},
"locked": {
"lastModified": 1755463179,
"narHash": "sha256-5Ggb1Mhf7ZlRgGi2puCa2PvWs6KbMnWBlW6KW7Vf79Y=",
"lastModified": 1759469269,
"narHash": "sha256-DP833ejGUNRRHsJOB3WRTaWWXLNucaDga2ju/fGe+sc=",
"owner": "NotAShelf",
"repo": "nvf",
"rev": "03833118267ad32226b014b360692bdce9d6e082",
"rev": "e48638aef3a95377689de0ef940443c64f870a09",
"type": "github"
},
"original": {
@@ -381,11 +381,11 @@
]
},
"locked": {
"lastModified": 1756052001,
"narHash": "sha256-dlLqyHxqiFAoIwshKe9X3PzXcJ+up88Qb2JVQswFaNE=",
"lastModified": 1758268943,
"narHash": "sha256-ufkrvMWvS+tgzs5H5iRZn/okuvmSzRLeBf+zUxES6YE=",
"owner": "icewind1991",
"repo": "nvidia-patch-nixos",
"rev": "780af7357d942fad2ddd9f325615a5f6ea7e37ee",
"rev": "e7358911c8f611eb1eb8e0758aa668d4d2d55cd9",
"type": "github"
},
"original": {
@@ -422,11 +422,11 @@
},
"quadlet-nix": {
"locked": {
"lastModified": 1754008153,
"narHash": "sha256-MYT1mDtSkiVg343agxgBFsnuNU3xS8vRy399JXX1Vw0=",
"lastModified": 1758631655,
"narHash": "sha256-EGeZ963L7xsNAY7snvP1JHQe7LWLVCM6f49+PzWjhEE=",
"owner": "SEIAROTg",
"repo": "quadlet-nix",
"rev": "1b2d27d460d8c7e4da5ba44ede463b427160b5c4",
"rev": "2ebe01b175e2e1e6de3f172d23f0c3b88713eec9",
"type": "github"
},
"original": {
@@ -495,11 +495,11 @@
]
},
"locked": {
"lastModified": 1755532656,
"narHash": "sha256-xYb5dJej3emyr4oWWAhkMP8rPc3kdVOXGZcIbAx1Y/I=",
"lastModified": 1759752146,
"narHash": "sha256-g30leL+8jLxkYWiM5W2RjnhGyqBtErmeOX3ELK5CRAQ=",
"ref": "refs/heads/main",
"rev": "b01f3f8456903cb1bde9637cc23b456b47354138",
"revCount": 11,
"rev": "bc1564ea3eb472f7b843e3237da0d1cd2f6f8e37",
"revCount": 14,
"type": "git",
"url": "ssh://git@karaolidis.com/karaolidis/nix-sas.git"
},
@@ -511,11 +511,11 @@
"secrets": {
"flake": false,
"locked": {
"lastModified": 1756900832,
"narHash": "sha256-sMne4dvYzcdbDVcMPY6NLVHiZbgjtDrxttKG0Vig8WQ=",
"lastModified": 1759165833,
"narHash": "sha256-EYAVKr7gGY7MDmgPIYsW3yk96q51UT1vtzlupR8paKg=",
"ref": "refs/heads/main",
"rev": "adac63f6daffb4e14ce0fb94e93eb987e2460064",
"revCount": 38,
"rev": "a5c1c552628492281e05e99458f1ca3ec272b448",
"revCount": 48,
"type": "git",
"url": "ssh://git@karaolidis.com/karaolidis/nix-secrets.git"
},
@@ -531,11 +531,11 @@
]
},
"locked": {
"lastModified": 1754988908,
"narHash": "sha256-t+voe2961vCgrzPFtZxha0/kmFSHFobzF00sT8p9h0U=",
"lastModified": 1759635238,
"narHash": "sha256-UvzKi02LMFP74csFfwLPAZ0mrE7k6EiYaKecplyX9Qk=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "3223c7a92724b5d804e9988c6b447a0d09017d48",
"rev": "6e5a38e08a2c31ae687504196a230ae00ea95133",
"type": "github"
},
"original": {
@@ -554,11 +554,11 @@
]
},
"locked": {
"lastModified": 1756614537,
"narHash": "sha256-qyszmZO9CEKAlj5NBQo1AIIADm5Fgqs5ZggW1sU1TVo=",
"lastModified": 1759638324,
"narHash": "sha256-bj0L3n2UWE/DjqFjsydWsSzO74+dqUA4tiOX4At6LbM=",
"owner": "Gerg-L",
"repo": "spicetify-nix",
"rev": "374eb5d97092b97f7aaafd58a2012943b388c0df",
"rev": "c39a58510e55c4970e57176ab14b722a978e5f01",
"type": "github"
},
"original": {
@@ -589,11 +589,11 @@
]
},
"locked": {
"lastModified": 1755934250,
"narHash": "sha256-CsDojnMgYsfshQw3t4zjRUkmMmUdZGthl16bXVWgRYU=",
"lastModified": 1758728421,
"narHash": "sha256-ySNJ008muQAds2JemiyrWYbwbG+V7S5wg3ZVKGHSFu8=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "74e1a52d5bd9430312f8d1b8b0354c92c17453e5",
"rev": "5eda4ee8121f97b218f7cc73f5172098d458f1d1",
"type": "github"
},
"original": {

View File

@@ -189,7 +189,32 @@
devShells.${system} = import ./hosts/common/shells { inherit pkgs; };
packages.${system} = import ./packages { inherit pkgs; };
formatter.${system} = treefmt.config.build.wrapper;
checks.${system}.formatting = treefmt.config.build.check inputs.self;
checks.${system} =
let
nixosConfigurations =
pkgs.lib.mapAttrs'
(
name: config:
pkgs.lib.nameValuePair "nixosConfiguration-${name}" config.config.system.build.toplevel
)
((pkgs.lib.filterAttrs (_: config: config.pkgs.system == system)) inputs.self.nixosConfigurations);
packages = pkgs.lib.mapAttrs' (
name: pkgs.lib.nameValuePair "package-${name}"
) inputs.self.packages.${system};
overlayPackages = pkgs.lib.mapAttrs' (n: pkgs.lib.nameValuePair "overlayPackage-${n}") (
import ./overlays/packages.nix { inherit pkgs; }
);
devShells = pkgs.lib.mapAttrs' (
name: pkgs.lib.nameValuePair "devShell-${name}"
) inputs.self.devShells.${system};
formatter.formatting = treefmt.config.build.check inputs.self;
in
nixosConfigurations // packages // overlayPackages // devShells // formatter;
}
);
}

View File

@@ -3,5 +3,6 @@
imports = [
./cpu/options.nix
./impermanence/options.nix
./networking/options.nix
];
}

View File

@@ -0,0 +1,17 @@
{ lib, ... }:
{
options.networking =
with lib;
with types;
{
publicIPv4 = mkOption {
type = nullOr str;
description = "The public IPv4 address of this device.";
};
publicIPv6 = mkOption {
type = nullOr str;
description = "The public IPv6 address of this device.";
};
};
}

View File

@@ -42,9 +42,14 @@
"flakes"
];
download-buffer-size = 524288000;
substituters = lib.mkBefore [ "https://nix.karaolidis.com/main" ];
trusted-substituters = lib.mkBefore [ "https://nix.karaolidis.com/main" ];
trusted-public-keys = lib.mkBefore [ "main:nJVRBnv73MDkwuV5sgm52m4E2ImOhWHvY12qzjPegAk=" ];
substituters = lib.mkMerge [
(lib.mkBefore [ "https://nix.karaolidis.com/main" ])
(lib.mkAfter [ "https://nix-community.cachix.org/" ])
];
trusted-public-keys = lib.mkBefore [
"nix.karaolidis.com:1yz1tIVLGDEOFC1p/uYtR4Sx+nIbdYDqsDv4kkV0uyk="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
netrc-file = config.sops.templates.nix-netrc.path;
};

View File

@@ -0,0 +1,4 @@
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [ usbutils ];
}

View File

@@ -13,7 +13,7 @@ in
sops = {
secrets."nix/cache/nix.karaolidis.com".sopsFile = "${inputs.secrets}/domains/personal/secrets.yaml";
templates."attic" = {
templates.attic = {
content = builtins.readFile (
(pkgs.formats.toml { }).generate "config.toml" {
default-server = "main";

View File

@@ -0,0 +1,8 @@
{ user, home }:
{ pkgs, ... }:
{
home-manager.users.${user}.home.packages = with pkgs; [
curl
httpie
];
}

View File

@@ -26,7 +26,10 @@ in
push.autoSetupRemote = true;
core.fsmonitor = true;
feature.manyFiles = true;
fetch.writeCommitGraph = true;
fetch = {
prune = true;
writeCommitGraph = true;
};
http.cookiefile = "${home}/.config/git/cookies";
advice.detachedHead = false;
};
@@ -40,6 +43,10 @@ in
}
);
};
aliases = {
adog = "log --all --decorate --oneline --graph";
};
};
home = {

View File

@@ -21,16 +21,14 @@
systemd.user.startServices = true;
nix.settings = {
use-xdg-base-directories = true;
experimental-features = [
"nix-command"
"flakes"
];
download-buffer-size = 524288000;
substituters = lib.mkBefore [ "https://nix.karaolidis.com/main" ];
trusted-substituters = lib.mkBefore [ "https://nix.karaolidis.com/main" ];
trusted-public-keys = lib.mkBefore [ "main:nJVRBnv73MDkwuV5sgm52m4E2ImOhWHvY12qzjPegAk=" ];
netrc-file = config.sops.templates.nix-netrc.path;
inherit (config.nix.settings)
use-xdg-base-directories
experimental-features
download-buffer-size
substituters
trusted-public-keys
netrc-file
;
};
};
};

View File

@@ -7,6 +7,7 @@
ipset
ethtool
tcpdump
dig
ipcalc
];
}

View File

@@ -0,0 +1,20 @@
{ user, home }:
{ ... }:
{
environment.persistence."/persist/state"."${home}/.local/state/lazygit" = { };
home-manager.users.${user}.programs.lazygit = {
enable = true;
settings = {
gui = {
showBottomLine = false;
nerdFontsVersion = "3";
animateExplosion = false;
};
disableStartupPopups = true;
};
};
}

View File

@@ -27,11 +27,15 @@
vimAlias = true;
autocomplete = {
blink-cmp.enable = true;
blink-cmp = {
enable = true;
setupOpts = {
signature.enabled = true;
};
};
};
binds = {
# hardtime-nvim.enable = true;
whichKey.enable = true;
};
@@ -45,34 +49,23 @@
comment-nvim.enable = true;
};
# dashboard = {
# alpha.enable = true;
# };
dashboard = {
alpha.enable = true;
};
filetree = {
neo-tree = {
enable = true;
setupOpts = {
git_status_async = true;
window.mappings = lib.generators.mkLuaInline ''
{
["<space>"] = "noop",
}
'';
};
diagnostics = {
enable = true;
config = {
virtual_text = true;
signs = true;
};
};
# formatter = {
# conform-nvim.enable = true;
# };
git = {
enable = true;
# git-conflict.enable = true;
git-conflict.enable = true;
gitsigns.enable = true;
# neogit.enable = true;
vim-fugitive.enable = true;
};
languages = {
@@ -116,15 +109,12 @@
lsp = {
enable = true;
formatOnSave = true;
# nvim-docs-view.enable = true;
# otter-nvim.enable = true;
# trouble.enable = true;
otter-nvim = {
enable = true;
setupOpts.handle_leading_whitespace = true;
};
};
# minimap = {
# codewindow.enable = true;
# };
notify = {
nvim-notify.enable = true;
};
@@ -136,16 +126,8 @@
smartindent = true;
};
# projects = {
# project-nvim.enable = true;
# };
searchCase = "smart";
# snippets = {
# luasnip.enable = true;
# };
tabline = {
nvimBufferline = {
enable = true;
@@ -160,20 +142,24 @@
telescope = {
enable = true;
setupOpts.defaults.file_ignore_patterns = [
"node_modules"
"%.venv/"
"%.git/"
"dist/"
"build/"
"target/"
"result/"
];
setupOpts.defaults = {
wrap_results = true;
file_ignore_patterns = [
"node_modules"
"%.venv/"
"%.git/"
"dist/"
"build/"
"target/"
"result/"
];
};
};
terminal = {
toggleterm = {
enable = true;
lazygit.enable = true;
setupOpts.winbar.enabled = false;
};
};
@@ -186,41 +172,39 @@
};
ui = {
# breadcrumbs = {
# enable = true;
# navbuddy.enable = true;
# };
colorizer.enable = true;
# fastaction.enable = true;
# illuminate.enable = true;
illuminate.enable = true;
};
undoFile.enable = true;
utility = {
# diffview-nvim.enable = true;
# icon-picker.enable = true;
# images = {
# img-clip.enable = true;
# };
# mkdir.enable = true;
images = {
img-clip = {
enable = true;
setupOpts.default.verbose = false;
};
};
mkdir.enable = true;
motion = {
precognition.enable = true;
};
# nvim-biscuits.enable = true;
# smart-splits.enable = true;
surround.enable = true;
# undotree.enable = true;
# yazi-nvim.enable = true;
undotree.enable = true;
yazi-nvim = {
enable = true;
setupOpts.open_for_directories = true;
};
};
visuals = {
# cinnamon-nvim.enable = true;
# fidget-nvim.enable = true;
# highlight-undo.enable = true;
highlight-undo = {
enable = true;
setupOpts.duration = 250;
};
indent-blankline.enable = true;
nvim-cursorline.enable = true;
# nvim-scrollbar.enable = true;
nvim-scrollbar.enable = true;
nvim-web-devicons.enable = true;
};
@@ -267,23 +251,16 @@
{
mode = [ "n" ];
key = "<leader>wq";
action = "<cmd>wq<CR>";
action = "<cmd>x<CR>";
silent = true;
desc = "Save & Quit";
}
{
mode = [ "n" ];
key = "<leader>ee";
action = "<cmd>Neotree toggle<CR>";
key = "<leader>be";
action = "<cmd>enew<CR>";
silent = true;
desc = "Toggle Neo-tree";
}
{
mode = [ "n" ];
key = "<leader>ef";
action = "<cmd>Neotree reveal<CR>";
silent = true;
desc = "Reveal file in Neo-tree";
desc = "New buffer";
}
];
};

View File

@@ -4,7 +4,10 @@
environment.persistence."/persist/cache"."${home}/.cache/nix" = { };
home-manager.users.${user} = {
home.packages = with pkgs; [ nurl ];
home.packages = with pkgs; [
nix-fast-build
nurl
];
programs.zsh.shellAliases = {
nrs = "sudo nixos-rebuild switch --flake .#$(hostname) --show-trace";

View File

@@ -29,7 +29,6 @@
enable = true;
key = config.sops.secrets."syncthing/key".path;
cert = config.sops.secrets."syncthing/cert".path;
extraOptions = [ "-no-default-folder" ];
settings = {
options.urAccepted = -1;

View File

@@ -8,6 +8,8 @@
settings = {
theme = "matugen";
default_mode = "locked";
pane_frames = false;
copy_command = "wl-copy";

View File

@@ -10,7 +10,7 @@ let
in
{
home-manager.users.${user} = {
programs.rofi.plugins = with pkgs; [ rofi-emoji-wayland ];
programs.rofi.plugins = with pkgs; [ rofi-emoji ];
wayland.windowManager.hyprland.settings.bind = [
# Super + Shift + :

View File

@@ -3,6 +3,7 @@
{
programs.gamescope = {
enable = true;
capSysNice = true;
args = [
"--rt"
"-f"

View File

@@ -1,30 +0,0 @@
{ user, home }:
{
config,
lib,
pkgs,
...
}:
# https://bonkmaykr.xyz/content/discovery_lin.htm
{
boot.kernel.sysctl."vm.max_map_count" = 1048576;
security.pam.loginLimits = [
{
domain = user;
item = "nofile";
type = "soft";
value = 200000;
}
{
domain = user;
item = "nofile";
type = "hard";
value = 200000;
}
];
home-manager.users.${user}.wayland.windowManager.hyprland.settings.env = [
"__GL_SHADER_DISK_CACHE_SKIP_CLEANUP,1"
];
}

View File

@@ -0,0 +1,7 @@
{ user, home }:
{ ... }:
{
programs.ghidra.enable = true;
environment.persistence."/persist/state"."${home}/.config/ghidra" = { };
}

View File

@@ -99,6 +99,8 @@
"$mod, mouse:273, resizewindow"
];
gesture = [ "3, horizontal, workspace" ];
input = {
accel_profile = "flat";
kb_layout = "us,gr";
@@ -114,8 +116,6 @@
};
gestures = {
workspace_swipe = true;
workspace_swipe_min_fingers = true;
workspace_swipe_forever = true;
workspace_swipe_cancel_ratio = 0.2;
};

View File

@@ -14,7 +14,7 @@ in
home-manager.users.${user} = {
programs.rofi = {
enable = true;
package = pkgs.rofi-wayland;
package = pkgs.rofi;
};
home.file.${hmConfig.programs.rofi.configPath}.enable = false;

View File

@@ -0,0 +1,7 @@
{ user, home }:
{ pkgs, ... }:
{
environment.persistence."/persist/state"."${home}/.config/Signal" = { };
home-manager.users.${user}.home.packages = with pkgs; [ signal-desktop ];
}

View File

@@ -0,0 +1,17 @@
{ user, home }:
{ pkgs, ... }:
{
programs.wireshark = {
enable = true;
dumpcap.enable = true;
usbmon.enable = true;
};
boot.kernelModules = [ "usbmon" ];
users.users.${user}.extraGroups = [ "wireshark" ];
environment.persistence."/persist/state"."${home}/.config/wireshark" = { };
home-manager.users.${user}.home.packages = with pkgs; [ wireshark ];
}

View File

@@ -36,7 +36,7 @@ in
programs = {
go = {
enable = true;
goPath = ".local/share/go";
env.GOPATH = "${home}/.local/share/go";
};
gradle = {

View File

@@ -131,6 +131,12 @@ in
identityFile = "${home}/.ssh/ssh_personal_ed25519_key";
};
"vps.karaolidis.com" = {
hostname = "vps.karaolidis.com";
user = "root";
identityFile = "${home}/.ssh/ssh_personal_ed25519_key";
};
"github.com" = {
hostname = "github.com";
user = "git";

View File

@@ -16,6 +16,7 @@ in
(import ../../../common/configs/user/console/attic { inherit user home; })
(import ../../../common/configs/user/console/btop { inherit user home; })
(import ../../../common/configs/user/console/curl { inherit user home; })
(import ../../../common/configs/user/console/dive { inherit user home; })
(import ../../../common/configs/user/console/fastfetch { inherit user home; })
(import ../../../common/configs/user/console/ffmpeg { inherit user home; })
@@ -26,6 +27,7 @@ in
(import ../../../common/configs/user/console/ip { inherit user home; })
(import ../../../common/configs/user/console/jq { inherit user home; })
(import ../../../common/configs/user/console/kubernetes { inherit user home; })
(import ../../../common/configs/user/console/lazygit { inherit user home; })
(import ../../../common/configs/user/console/lsof { inherit user home; })
(import ../../../common/configs/user/console/mprocs { inherit user home; })
(import ../../../common/configs/user/console/ncdu { inherit user home; })

View File

@@ -45,6 +45,7 @@
../common/configs/system/system
../common/configs/system/timezone
../common/configs/system/upower
../common/configs/system/usb
../common/configs/system/users
../common/configs/system/zsh

View File

@@ -62,10 +62,6 @@
name = "alc285-fixup";
patch = ./gu605c-spi-cs-gpio/alc285-fixup.patch;
}
{
name = "iwlwifi-no-disable-all-chans";
patch = ./iwlwifi/iwlwifi-no-disable-all-chans.patch;
}
];
initrd = {
@@ -88,7 +84,6 @@
services = {
xserver.videoDrivers = [ "nvidia" ];
fstrim.enable = true;
tlp.settings.DISK_DEVICES = lib.mkDefault "nvme0n1 nvme1n1";
asusd = {
enable = true;

View File

@@ -1,26 +0,0 @@
diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c
index 6adcfa6e214a..4512d846629c 100644
--- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c
+++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c
@@ -622,7 +622,7 @@ int iwl_fill_lari_config(struct iwl_fw_runtime *fwrt,
cmd->oem_uhb_allow_bitmap = cpu_to_le32(value);
ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_FORCE_DISABLE_CHANNELS, &value);
- if (!ret)
+ if (!ret && value != 0xFFFFFFFF)
cmd->force_disable_channels_bitmap = cpu_to_le32(value);
ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_ENERGY_DETECTION_THRESHOLD,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c
index a75af8c1e8ab..e055a946b9e6 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c
@@ -259,7 +259,7 @@ void iwl_mld_configure_lari(struct iwl_mld *mld)
cmd.oem_uhb_allow_bitmap = cpu_to_le32(value);
ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_FORCE_DISABLE_CHANNELS, &value);
- if (!ret)
+ if (!ret && value != 0xFFFFFFFF)
cmd.force_disable_channels_bitmap = cpu_to_le32(value);
ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_ENERGY_DETECTION_THRESHOLD,

View File

@@ -75,6 +75,12 @@ in
identityFile = "${home}/.ssh/ssh_personal_ed25519_key";
};
"vps.karaolidis.com" = {
hostname = "vps.karaolidis.com";
user = "root";
identityFile = "${home}/.ssh/ssh_personal_ed25519_key";
};
"github.com" = {
hostname = "github.com";
user = "git";

View File

@@ -18,6 +18,7 @@ in
(import ../../../common/configs/user/console/attic { inherit user home; })
(import ../../../common/configs/user/console/brightnessctl { inherit user home; })
(import ../../../common/configs/user/console/btop { inherit user home; })
(import ../../../common/configs/user/console/curl { inherit user home; })
(import ../../../common/configs/user/console/dive { inherit user home; })
(import ../../../common/configs/user/console/fastfetch { inherit user home; })
(import ../../../common/configs/user/console/ffmpeg { inherit user home; })
@@ -27,6 +28,7 @@ in
(import ../../../common/configs/user/console/imagemagick { inherit user home; })
(import ../../../common/configs/user/console/ip { inherit user home; })
(import ../../../common/configs/user/console/jq { inherit user home; })
(import ../../../common/configs/user/console/lazygit { inherit user home; })
(import ../../../common/configs/user/console/libvirt { inherit user home; })
(import ../../../common/configs/user/console/lsof { inherit user home; })
(import ../../../common/configs/user/console/mprocs { inherit user home; })
@@ -64,11 +66,11 @@ in
(import ../../../common/configs/user/gui/firefox { inherit user home; })
(import ../../../common/configs/user/gui/gaming/gamemode { inherit user home; })
(import ../../../common/configs/user/gui/gaming/gamescope { inherit user home; })
(import ../../../common/configs/user/gui/gaming/performance { inherit user home; })
(import ../../../common/configs/user/gui/gaming/steam { inherit user home; })
(import ../../../common/configs/user/gui/gaming/prismlauncher { inherit user home; })
(import ../../../common/configs/user/gui/gaming/proton { inherit user home; })
(import ../../../common/configs/user/gui/gaming/wivrn { inherit user home; })
(import ../../../common/configs/user/gui/ghidra { inherit user home; })
(import ../../../common/configs/user/gui/gtk { inherit user home; })
(import ../../../common/configs/user/gui/hypridle { inherit user home; })
(import ../../../common/configs/user/gui/hyprland { inherit user home; })
@@ -86,11 +88,13 @@ in
(import ../../../common/configs/user/gui/qt { inherit user home; })
(import ../../../common/configs/user/gui/rofi { inherit user home; })
(import ../../../common/configs/user/gui/rquickshare { inherit user home; })
(import ../../../common/configs/user/gui/signal { inherit user home; })
(import ../../../common/configs/user/gui/swww { inherit user home; })
(import ../../../common/configs/user/gui/theme { inherit user home; })
(import ../../../common/configs/user/gui/transmission { inherit user home; })
(import ../../../common/configs/user/gui/vscode { inherit user home; })
(import ../../../common/configs/user/gui/wev { inherit user home; })
(import ../../../common/configs/user/gui/wireshark { inherit user home; })
(import ../../../common/configs/user/gui/wl-clipboard { inherit user home; })
(import ../../../common/configs/user/gui/x11 { inherit user home; })
(import ../../../common/configs/user/gui/xdg { inherit user home; })

View File

@@ -37,6 +37,7 @@
../common/configs/system/sudo
../common/configs/system/system
../common/configs/system/timezone
../common/configs/system/usb
../common/configs/system/users
../common/configs/system/zsh

View File

@@ -74,6 +74,12 @@ in
identityFile = "${home}/.ssh/ssh_personal_ed25519_key";
};
"vps.karaolidis.com" = {
hostname = "vps.karaolidis.com";
user = "root";
identityFile = "${home}/.ssh/ssh_personal_ed25519_key";
};
"github.com" = {
hostname = "github.com";
user = "git";

View File

@@ -2,7 +2,6 @@
let
jupiterConfig = inputs.self.nixosConfigurations.jupiter.config;
wireguardPort = 51821;
jupiterPublicIPv4 = "51.89.210.124";
in
{
boot.kernel.sysctl = {
@@ -29,7 +28,7 @@ in
name = "jupiter";
allowedIPs = [
"10.0.0.2/32"
"${jupiterPublicIPv4}/32"
"${jupiterConfig.networking.publicIPv4}/32"
];
publicKey = builtins.readFile "${inputs.secrets}/hosts/jupiter/wireguard_key.pub";
}

View File

@@ -33,7 +33,10 @@
./configs/wireguard
];
networking.hostName = "jupiter-vps";
networking = {
hostName = "jupiter-vps";
publicIPv4 = "217.154.55.15";
};
environment.impermanence.enable = lib.mkForce false;

View File

@@ -1,7 +1,7 @@
{
disko.devices = {
disk.main = {
device = "/dev/sda";
device = "/dev/vda";
type = "disk";
content = {
type = "gpt";

View File

@@ -17,13 +17,13 @@ Remember to update [format.nix](format.nix).
### Adding a new drive
Create a `format.nix` containing the new disk layout. Do not include already existing disks in this file, nor the global pool.
```
parted /dev/sdd -- mklabel gpt
parted /dev/sdd -- mkpart primary 0% 100%
cryptsetup luksFormat /dev/sdd
cryptsetup open /dev/sdd storage2
disko -m "destroy" --yes-wipe-all-disks format.nix
disko -m "format" --yes-wipe-all-disks format.nix
btrfs device add /dev/mapper/storage2 /mnt/storage
btrfs balance start -dconvert=raid1 -mconvert=raid1 /mnt/storage
btrfs balance start -dconvert=raid1 -mconvert=raid1 /mnt/storage --bg
```
### Removing an old drive

View File

@@ -117,62 +117,87 @@ in
filters = [ ];
whitelist_filters = [ ];
user_rules = [
"||*^"
# Personal
"@@||karaolidis.com^$important"
# Connectivity Check
"@@||clients3.google.com^"
"@@||clients.l.google.com^"
"@@||connectivitycheck.gstatic.com^"
"@@||connectivitycheck.android.com^"
# NTP
"@@||pool.ntp.org^$important"
"@@||time.android.com^$important"
"@@||time.akamai.com^$important"
# Plex
"@@||plex.tv^$important"
"@@||plex.direct^$important"
# YouTube
"@@||youtube.com^$important"
"@@||yt.be^$important"
"@@||ytimg.com^$important"
"@@||googlevideo.com^$important"
# YouTube Extensions
"@@||returnyoutubedislikeapi.com^$important"
"@@||sponsor.ajay.app^$important"
# Google Misc
"@@||accounts.google.com^$important"
"@@||www.gstatic.com^$important"
"@@||content-autofill.googleapis.com^$important"
# Google Play
"@@||play.google.com^$important"
"@@||android.googleapis.com^$important"
"@@||androidtvsetupwraithfe-pa.googleapis.com^$important"
"@@||play-fe.googleapis.com^$important"
"@@||play-lh.googleusercontent.com^$important"
"@@||play.googleapis.com^$important"
"@@||android.apis.google.com^$important"
"@@||playatoms-pa.googleapis.com^$important"
"@@||gvt1.com^$important"
# Spotify
"@@||spotify.com^$important"
"@@||spotify.dev^$important"
"@@||scdn.co^$important"
"@@||tospotify.com^$important"
"@@||spotifycdn.com^$important"
# Twitch
"@@||twitch.tv^$important"
"@@||ttvnw.net^$important"
"@@||static-cdn.jtvnw.net^$important"
# Cosmote TV
"@@||account.cosmote.gr^$important"
"@@||cosmotetvott.gr^$important"
"@@||msvdn.net^$important"
"@@||theplatform.eu^$important"
"@@||theplatform.com^$important"
filtering.rewrites = [
{
domain = "beta.media.karaolidis.com";
answer = inboundGateway;
}
];
user_rules =
let
domains = [
# Personal
"beta.media.karaolidis.com"
# Connectivity Check
"clients3.google.com"
"clients.l.google.com"
"connectivitycheck.gstatic.com"
"connectivitycheck.android.com"
# NTP
"pool.ntp.org"
"time.android.com"
"time.akamai.com"
# Plex
"plex.tv"
"plex.direct"
# YouTube
"youtube.com"
"yt.be"
"ytimg.com"
"googlevideo.com"
# YouTube Extensions
"returnyoutubedislikeapi.com"
"sponsor.ajay.app"
# Google Misc
"accounts.google.com"
"www.gstatic.com"
"content-autofill.googleapis.com"
# Google Play
"play.google.com"
"android.googleapis.com"
"androidtvsetupwraithfe-pa.googleapis.com"
"play-fe.googleapis.com"
"play-lh.googleusercontent.com"
"play.googleapis.com"
"android.apis.google.com"
"playatoms-pa.googleapis.com"
"gvt1.com"
# Spotify
"spotify.com"
"spotify.dev"
"scdn.co"
"tospotify.com"
"spotifycdn.com"
# Twitch
"twitch.tv"
"ttvnw.net"
"static-cdn.jtvnw.net"
# Cosmote TV
"account.cosmote.gr"
"cosmotetvott.gr"
"msvdn.net"
"theplatform.eu"
"theplatform.com"
# Releases
"github.com"
"release-assets.githubusercontent.com"
];
in
[ "||*^" ] ++ (map (domain: "@@||${domain}^$important") domains);
schema_version = 29;
};
in

View File

@@ -7,8 +7,6 @@
let
jupiterVpsConfig = inputs.self.nixosConfigurations.jupiter-vps.config;
wireguardPort = jupiterVpsConfig.networking.wireguard.interfaces.wg0.listenPort;
jupiterVpsPublicIPv4 = "51.75.170.190";
jupiterPublicIPv4 = "51.89.210.124";
in
{
sops.secrets."wireguard/client/vps" = { };
@@ -29,21 +27,21 @@ in
{
ips = [
"10.0.0.2/24"
"${jupiterPublicIPv4}/32"
"${config.networking.publicIPv4}/32"
];
privateKeyFile = config.sops.secrets."wireguard/client/vps".path;
inherit table;
postSetup = [ "${ip} rule add from ${jupiterPublicIPv4} table ${table}" ];
postShutdown = [ "${ip} rule del from ${jupiterPublicIPv4} table ${table}" ];
postSetup = [ "${ip} rule add from ${config.networking.publicIPv4} table ${table}" ];
postShutdown = [ "${ip} rule del from ${config.networking.publicIPv4} table ${table}" ];
peers = [
{
name = "jupiter-vps";
allowedIPs = [ "0.0.0.0/0" ];
publicKey = builtins.readFile "${inputs.secrets}/hosts/jupiter-vps/wireguard_key.pub";
endpoint = "${jupiterVpsPublicIPv4}:${builtins.toString wireguardPort}";
endpoint = "${jupiterVpsConfig.networking.publicIPv4}:${builtins.toString wireguardPort}";
persistentKeepalive = 25;
}
];

View File

@@ -13,11 +13,13 @@
../common/configs/system
../common/configs/system/bluetooth
../common/configs/system/boot
../common/configs/system/brightnessctl
../common/configs/system/btrbk
../common/configs/system/btrfs
../common/configs/system/documentation
../common/configs/system/getty
../common/configs/system/git
../common/configs/system/impermanence
../common/configs/system/lanzaboote
@@ -25,8 +27,10 @@
../common/configs/system/networkmanager
../common/configs/system/nix
../common/configs/system/nix-cleanup
../common/configs/system/nix-ld
../common/configs/system/nixpkgs
../common/configs/system/ntp
../common/configs/system/pipewire
../common/configs/system/podman
../common/configs/system/power
../common/configs/system/smartmontools
@@ -35,6 +39,7 @@
../common/configs/system/sshd
../common/configs/system/sudo
../common/configs/system/system
../common/configs/system/usb
../common/configs/system/users
../common/configs/system/zsh
@@ -45,15 +50,21 @@
./users/storm
./users/nick
./users/tv
];
networking.hostName = "jupiter";
networking = {
hostName = "jupiter";
publicIPv4 = "87.106.36.59";
};
boot.initrd = {
luks.devices = {
main.keyFile = "/usb/keyfile";
storage0.keyFile = "/usb/keyfile";
storage1.keyFile = "/usb/keyfile";
storage2.keyFile = "/usb/keyfile";
storage3.keyFile = "/usb/keyfile";
};
systemd.contents."/etc/fstab".text = ''

View File

@@ -123,10 +123,66 @@
settings = {
allowDiscards = true;
};
};
};
};
};
};
storage2 = {
device = "/dev/disk/by-id/ata-TOSHIBA_MG11ACA24TE_7512A15XF6AL";
type = "disk";
content = {
type = "gpt";
partitions = {
root = {
name = "root";
size = "100%";
content = {
name = "storage2";
type = "luks";
passwordFile = "/tmp/keyfile";
settings = {
allowDiscards = true;
};
};
};
};
};
};
storage3 = {
device = "/dev/disk/by-id/ata-TOSHIBA_MG11ACA24TE_7512A192F6AL";
type = "disk";
content = {
type = "gpt";
partitions = {
root = {
name = "root";
size = "100%";
content = {
name = "storage3";
type = "luks";
passwordFile = "/tmp/keyfile";
settings = {
allowDiscards = true;
};
content = {
type = "btrfs";
extraArgs = [ "-f -L storage -m raid1 -d raid1 /dev/mapper/storage0" ];
extraArgs = [
"-f"
"-L"
"storage"
"-m"
"raid1"
"-d"
"raid1"
"/dev/mapper/storage0"
"/dev/mapper/storage1"
"/dev/mapper/storage2"
# Implicit /dev/mapper/storage3
];
subvolumes =
let
mountOptions = [

View File

@@ -1,7 +1,6 @@
{
config,
pkgs,
lib,
inputs,
...
}:
@@ -23,12 +22,22 @@
# FIXME: https://github.com/icewind1991/nvidia-patch-nixos/issues/9
package =
let
nvidiaStable = config.boot.kernelPackages.nvidiaPackages.stable;
# FIXME: HDMI Crash, God knows when it will be reported and/or fixed
nvidiaStable = config.boot.kernelPackages.nvidiaPackages.mkDriver {
version = "580.82.09";
sha256_64bit = "sha256-Puz4MtouFeDgmsNMKdLHoDgDGC+QRXh6NVysvltWlbc=";
sha256_aarch64 = "sha256-6tHiAci9iDTKqKrDIjObeFdtrlEwjxOHJpHfX4GMEGQ=";
openSha256 = "sha256-YB+mQD+oEDIIDa+e8KX1/qOlQvZMNKFrI5z3CoVKUjs=";
settingsSha256 = "sha256-um53cr2Xo90VhZM1bM2CH4q9b/1W2YOqUcvXPV6uw2s=";
persistencedSha256 = "sha256-lbYSa97aZ+k0CISoSxOMLyyMX//Zg2Raym6BC4COipU=";
};
maybeFbc =
if builtins.hasAttr nvidiaStable.version pkgs.nvidia-patch-list.fbc then
pkgs.nvidia-patch.patch-fbc nvidiaStable
else
nvidiaStable;
nvidiaStableFinal =
if builtins.hasAttr nvidiaStable.version pkgs.nvidia-patch-list.nvenc then
pkgs.nvidia-patch.patch-nvenc maybeFbc
@@ -54,8 +63,6 @@
graphics = {
enable32Bit = true;
extraPackages = with pkgs; [
amdvlk
driversi686Linux.amdvlk
rocmPackages.clr
rocmPackages.clr.icd
];
@@ -65,8 +72,17 @@
config.virtualisation.containerd.enable || config.virtualisation.podman.enable;
};
# Lobotomize CPU so that it stays below boiling temps
powerManagement = {
cpuFreqGovernor = "conservative";
cpufreq = {
min = 403488;
max = 4465261;
};
};
boot = {
kernelParams = [ "amd_pstate=active" ];
kernelParams = [ "amd_pstate=passive" ];
kernelModules = [ "kvm-amd" ];
initrd.kernelModules = [
"nvme"
@@ -84,15 +100,11 @@
];
};
nixpkgs.config = {
cudaSupport = true;
rocmSupport = true;
};
nixpkgs.config.cudaSupport = true;
services = {
xserver.videoDrivers = [ "nvidia" ];
fstrim.enable = true;
tlp.settings.DISK_DEVICES = lib.mkDefault "nvme0n1 nvme1n1";
logind.settings.Login.HandleLidSwitch = "ignore";
};
}

View File

@@ -16,6 +16,7 @@ in
"attic/postgresql".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"attic/rs256".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"attic/admin".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"attic/keypairs/main".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
};
templates = {
@@ -27,7 +28,7 @@ in
ATTIC_TOKEN=${hmConfig.sops.placeholder."attic/admin"}
'';
attic.content = builtins.readFile (
attic-server.content = builtins.readFile (
(pkgs.formats.toml { }).generate "server.toml" {
listen = "[::]:8080";
@@ -57,7 +58,7 @@ in
garbage-collection = {
interval = "12 hours";
default-retention-period = "1 month";
default-retention-period = "6 months";
};
jwt.signing.token-rs256-secret-base64 = hmConfig.sops.placeholder."attic/rs256";
@@ -83,10 +84,20 @@ in
networks.attic.ref
networks.traefik.ref
];
volumes = [
"/mnt/storage/private/storm/containers/storage/volumes/attic/_data:/var/lib/attic"
"${hmConfig.sops.templates.attic.path}:/etc/attic/server.toml"
];
volumes =
let
postStart = pkgs.writeTextFile {
name = "post-start.sh";
executable = true;
text = builtins.readFile ./post-start.sh;
};
in
[
"/mnt/storage/private/storm/containers/storage/volumes/attic/_data:/var/lib/attic"
"${hmConfig.sops.templates.attic-server.path}:/etc/attic/server.toml:ro"
"${hmConfig.sops.secrets."attic/keypairs/main".path}:/etc/attic/keypairs/main:ro"
"${postStart}:/etc/attic/post-start.sh:ro"
];
environmentFiles = [ hmConfig.sops.templates.attic-env.path ];
exec = [
"--config"

View File

@@ -5,13 +5,17 @@ attic login main https://nix.karaolidis.com/ "$ATTIC_TOKEN"
CACHE_NAME="main"
while true; do
set +o errexit
out=$(attic cache info "$CACHE_NAME" 2>&1)
status=$?
set -o errexit
if [ $status -eq 0 ]; then
attic cache configure "$CACHE_NAME" --keypair-path "/etc/attic/keypairs/$CACHE_NAME"
break
elif echo "$out" | grep -q "NoSuchCache"; then
attic cache create "$CACHE_NAME"
attic cache create "$CACHE_NAME" --keypair-path "/etc/attic/keypairs/$CACHE_NAME"
break
elif echo "$out" | grep -q "404"; then
sleep 0.1
else

View File

@@ -132,10 +132,11 @@ in
"media"
"vaultwarden"
"nextcloud"
"jellyfin"
"gitea"
"outline"
"shlink"
"comentario"
"immich"
];
};
}

View File

@@ -0,0 +1,66 @@
{ user, home }:
{
config,
inputs,
lib,
pkgs,
...
}:
let
hmConfig = config.home-manager.users.${user};
inherit (hmConfig.virtualisation.quadlet) volumes networks;
in
{
home-manager.users.${user} = {
sops = {
secrets."blog/apiKey".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
templates.blog-receiver-env.content = ''
AUTH_KEY=${hmConfig.sops.placeholder."blog/apiKey"}
'';
};
virtualisation.quadlet = {
volumes.blog = { };
containers = {
blog.containerConfig = {
image = "docker-archive:${pkgs.dockerImages.nginx}";
networks = [ networks.traefik.ref ];
volumes = [ "${volumes.blog.ref}:/var/www/nginx:ro" ];
labels = [
"traefik.enable=true"
"traefik.http.routers.blog.rule=Host(`blog.karaolidis.com`)"
"traefik.http.routers.root.rule=Host(`karaolidis.com`) || Host(`www.karaolidis.com`)"
"traefik.http.routers.root.middlewares=redirect-root-to-blog"
"traefik.http.routers.root.service=noop@internal"
"traefik.http.middlewares.redirect-root-to-blog.redirectregex.regex=^https://(www\.)?karaolidis\.com(/.*)?$"
"traefik.http.middlewares.redirect-root-to-blog.redirectregex.replacement=https://blog.karaolidis.com$${2}"
"traefik.http.middlewares.redirect-root-to-blog.redirectregex.permanent=false"
];
};
blog-receiver = {
containerConfig = {
image = "docker-archive:${pkgs.dockerImages.nginx-receiver}";
networks = [ networks.traefik.ref ];
volumes = [ "${volumes.blog.ref}:/var/www/nginx" ];
environments = {
TARGET_DIR = "/var/www/nginx";
SUBPATH = "/upload";
};
environmentFiles = [ hmConfig.sops.templates.blog-receiver-env.path ];
labels = [
"traefik.enable=true"
"traefik.http.routers.blog-receiver.rule=Host(`blog.karaolidis.com`) && PathPrefix(`/upload`)"
];
};
unitConfig.After = [ "sops-nix.service" ];
};
};
};
};
}

View File

@@ -0,0 +1,171 @@
{ user, home }:
{
config,
inputs,
pkgs,
lib,
...
}:
let
hmConfig = config.home-manager.users.${user};
inherit (hmConfig.virtualisation.quadlet) containers volumes networks;
autheliaClientId = "d16NArYYcTbDU0YTQEwmlvzWAzJKhbIbe4s8wGENSRTK40gvAwGbYO0fCSq4rh6pjNxI0ZuH1cM8XnADCgSV9SHRzgX9MqcFre5r";
in
{
home-manager.users.${user} = {
sops = {
secrets = {
"comentario/postgresql".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"comentario/authelia/password".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"comentario/authelia/digest".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"comentario/smtp".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
};
templates = {
comentario-postgresql-env.content = ''
POSTGRES_PASSWORD=${hmConfig.sops.placeholder."comentario/postgresql"}
'';
comentario-secrets.content = builtins.readFile (
(pkgs.formats.yaml { }).generate "secrets.yaml" {
postgres = {
host = "comentario-postgresql";
port = 5432;
database = "comentario";
username = "comentario";
password = hmConfig.sops.placeholder."comentario/postgresql";
};
smtpServer = {
host = "smtp.protonmail.ch";
port = 587;
username = "jupiter@karaolidis.com";
password = hmConfig.sops.placeholder."comentario/smtp";
};
idp.oidc = [
{
id = "authelia";
name = "Authelia";
url = "https://id.karaolidis.com";
scopes = [
"openid"
"profile"
"email"
"is_admin"
];
key = autheliaClientId;
secret = hmConfig.sops.placeholder."comentario/authelia/password";
superuserClaim = "is_admin";
}
];
}
);
authelia-comentario.content = builtins.readFile (
(pkgs.formats.yaml { }).generate "comentario.yaml" {
identity_providers.oidc = {
authorization_policies.comentario = {
default_policy = "deny";
rules = [
{
policy = "one_factor";
subject = "group:comentario";
}
];
};
clients = [
{
client_id = autheliaClientId;
client_name = "Comentario";
client_secret = hmConfig.sops.placeholder."comentario/authelia/digest";
redirect_uris = [ "https://comments.karaolidis.com/api/oauth/oidc:authelia/callback" ];
authorization_policy = "comentario";
claims_policy = "is_admin";
scopes = [
"openid"
"profile"
"email"
"is_admin"
];
pre_configured_consent_duration = "1 year";
}
];
};
}
);
};
};
virtualisation.quadlet = {
networks.comentario = { };
volumes.comentario-postgresql = { };
containers = {
comentario = {
containerConfig = {
image = "docker-archive:${pkgs.dockerImages.comentario}";
networks = [
networks.comentario.ref
networks.traefik.ref
];
volumes =
let
config = (pkgs.formats.yaml { }).generate "config.yaml" {
baseUrl = "https://comments.karaolidis.com";
log.noColor = true;
dynamicConfigDefaults.auth = {
emailUpdate.enabled = true;
signup = {
confirm.commenter = false;
enabled = false;
sso.enabled = true;
};
};
};
in
[
"${config}:/etc/comentario/config.yaml:ro"
"${hmConfig.sops.templates.comentario-secrets.path}:/etc/comentario/secrets.yaml:ro"
];
labels = [
"traefik.enable=true"
"traefik.http.routers.comentario.rule=Host(`comments.karaolidis.com`)"
];
};
unitConfig = {
After = [
"${containers.comentario-postgresql._serviceName}.service"
"sops-nix.service"
];
Requires = [ "${containers.comentario-postgresql._serviceName}.service" ];
};
};
comentario-postgresql = {
containerConfig = {
image = "docker-archive:${pkgs.dockerImages.postgresql}";
networks = [ networks.comentario.ref ];
volumes = [ "${volumes.comentario-postgresql.ref}:/var/lib/postgresql/data" ];
environments = {
POSTGRES_DB = "comentario";
POSTGRES_USER = "comentario";
};
environmentFiles = [ hmConfig.sops.templates.comentario-postgresql-env.path ];
};
unitConfig.After = [ "sops-nix.service" ];
};
authelia.containerConfig.volumes = [
"${hmConfig.sops.templates.authelia-comentario.path}:/etc/authelia/conf.d/comentario.yaml:ro"
];
};
};
};
}

View File

@@ -12,8 +12,11 @@ in
imports = [
(import ./attic { inherit user home; })
(import ./authelia { inherit user home; })
(import ./blog { inherit user home; })
(import ./comentario { inherit user home; })
(import ./gitea { inherit user home; })
(import ./grafana { inherit user home; })
(import ./immich { inherit user home; })
(import ./littlelink { inherit user home; })
(import ./lore { inherit user home; })
(import ./media { inherit user home; })

View File

@@ -61,7 +61,12 @@ in
home-manager.users.${user} =
let
autheliaClientId = "I2ZYDFGWP1bzfiauXe94IaiReZF6SqoEskSp6phoL2L8l16Cq7YX3Vr4pkQOSYfNDOwuFjTRIpqQ8eAqK0M93NeEgpr8YoPhKHyR";
inherit (hmConfig.virtualisation.quadlet) containers volumes networks;
inherit (hmConfig.virtualisation.quadlet)
containers
volumes
networks
images
;
in
{
sops = {
@@ -178,7 +183,7 @@ in
default_policy = "deny";
rules = [
{
policy = "one_factor";
policy = "two_factor";
subject = "group:gitea";
}
];
@@ -191,6 +196,7 @@ in
client_secret = hmConfig.sops.placeholder."gitea/authelia/digest";
redirect_uris = [ "https://git.karaolidis.com/user/oauth2/authelia/callback" ];
authorization_policy = "gitea";
pre_configured_consent_duration = "1 year";
}
];
};
@@ -213,6 +219,16 @@ in
gitea-act-runner-cache = { };
};
images.gitea-act-runner-worker.imageConfig = {
image = "docker-archive:${pkgs.dockerImages.gitea-act-runner-worker}";
tag =
let
name = pkgs.dockerImages.gitea-act-runner-worker.passthru.buildArgs.name;
tag = pkgs.dockerImages.gitea-act-runner-worker.passthru.imageTag;
in
"localhost/${name}:${tag}";
};
containers = {
gitea = {
containerConfig = {
@@ -277,8 +293,22 @@ in
volumes =
let
uid = builtins.toString config.users.users.${user}.uid;
runnerConfig = (pkgs.formats.yaml { }).generate "config.yaml" {
runner = {
file = "/var/lib/gitea-act-runner/registration";
capacity = 4;
labels = [ "nix:docker://${images.gitea-act-runner-worker.imageConfig.tag}" ];
};
cache.dir = "/tmp/gitea-act-runner/";
container = {
privileged = true;
docker_host = "-";
};
};
in
[
"${runnerConfig}:/etc/gitea-act-runner/config.yaml:ro"
"/run/user/${uid}/podman/podman.sock:/var/run/docker.sock"
"${volumes.gitea-act-runner-data.ref}:/var/lib/gitea-act-runner"
"${volumes.gitea-act-runner-cache.ref}:/tmp/gitea-act-runner"

View File

@@ -0,0 +1,10 @@
apiVersion: 1
policies:
- orgId: 1
receiver: ntfy.sh
group_by:
- grafana_folder
- alertname
group_wait: 0s
group_interval: 1m
repeat_interval: 1h

View File

@@ -0,0 +1,454 @@
apiVersion: 1
groups:
- orgId: 1
name: Default
folder: System
interval: 10s
rules:
- uid: cpu-usage
title: CPU Usage
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
editorMode: code
expr: 1 - avg by(hostname) (rate(node_cpu_seconds_total{mode="idle"}[1h]))
instant: true
intervalMs: 1000
legendFormat: __auto
maxDataPoints: 43200
range: false
refId: A
- refId: C
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 0.9
type: gt
operator:
type: and
query:
params:
- C
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: A
intervalMs: 1000
maxDataPoints: 43200
refId: C
type: threshold
noDataState: NoData
execErrState: Error
for: 30m
keepFiringFor: 5m
isPaused: false
notification_settings:
receiver: ntfy.sh
- uid: memory-usage
title: Memory Usage
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
editorMode: code
expr: 1 - (node_memory_MemAvailable_bytes{} / node_memory_MemTotal_bytes{})
instant: true
intervalMs: 1000
legendFormat: __auto
maxDataPoints: 43200
range: false
refId: A
- refId: C
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 0.9
type: gt
operator:
type: and
query:
params:
- C
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: A
intervalMs: 1000
maxDataPoints: 43200
refId: C
type: threshold
noDataState: NoData
execErrState: Error
for: 5m
isPaused: false
notification_settings:
receiver: ntfy.sh
- uid: cpu-temperature
title: CPU Temperature
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
editorMode: code
expr: node_hwmon_temp_celsius{chip="pci0000:00_0000:00:18_3", sensor="temp1"}
instant: true
intervalMs: 1000
legendFormat: __auto
maxDataPoints: 43200
range: false
refId: A
- refId: C
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 75
type: gt
operator:
type: and
query:
params:
- C
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: A
intervalMs: 1000
maxDataPoints: 43200
refId: C
type: threshold
noDataState: NoData
execErrState: Error
for: 30m
keepFiringFor: 5m
isPaused: false
notification_settings:
receiver: ntfy.sh
- uid: amabient-temperature
title: Ambient Temperature
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
editorMode: code
expr: avg(node_hwmon_temp_celsius{chip="thermal_thermal_zone0"})
instant: true
intervalMs: 1000
legendFormat: __auto
maxDataPoints: 43200
range: false
refId: A
- refId: C
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 70
type: gt
operator:
type: and
query:
params:
- C
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: A
intervalMs: 1000
maxDataPoints: 43200
refId: C
type: threshold
noDataState: NoData
execErrState: Error
for: 15m
keepFiringFor: 5m
isPaused: false
notification_settings:
receiver: ntfy.sh
- uid: smart-status
title: SMART Status
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
editorMode: code
expr: smartctl_device_smart_status
instant: true
intervalMs: 1000
legendFormat: __auto
maxDataPoints: 43200
range: false
refId: A
- refId: C
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 1
type: lt
operator:
type: and
query:
params:
- C
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: A
intervalMs: 1000
maxDataPoints: 43200
refId: C
type: threshold
noDataState: NoData
execErrState: Error
keepFiringFor: 1h
isPaused: false
notification_settings:
receiver: ntfy.sh
- uid: smart-errors
title: SMART Errors
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
editorMode: code
expr: "sum(\n increase(smartctl_device_attribute{attribute_value_type=\"raw\", attribute_name=~\"Raw_Read_Error_Rate|Seek_Error_Rate|Offline_Uncorrectable\"}[1h])\n) + \nsum(\n increase(smartctl_device_media_errors[1h])\n)"
instant: true
intervalMs: 1000
legendFormat: __auto
maxDataPoints: 43200
range: false
refId: A
- refId: C
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 0
type: gt
operator:
type: and
query:
params:
- C
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: A
intervalMs: 1000
maxDataPoints: 43200
refId: C
type: threshold
noDataState: NoData
execErrState: Error
keepFiringFor: 1h
isPaused: false
notification_settings:
receiver: ntfy.sh
- uid: smart-temperature
title: SMART Temperature
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
editorMode: code
expr: smartctl_device_temperature
instant: true
intervalMs: 1000
legendFormat: __auto
maxDataPoints: 43200
range: false
refId: A
- refId: C
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 50
type: gt
operator:
type: and
query:
params:
- C
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: A
intervalMs: 1000
maxDataPoints: 43200
refId: C
type: threshold
noDataState: NoData
execErrState: Error
for: 15m
keepFiringFor: 5m
isPaused: false
notification_settings:
receiver: ntfy.sh
- uid: btrfs-errors
title: BTRFS Errors
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
editorMode: code
expr: |-
sum by (btrfs_dev_uuid) (
increase(node_btrfs_device_errors_total[1h])
)
instant: true
intervalMs: 1000
legendFormat: __auto
maxDataPoints: 43200
range: false
refId: A
- refId: C
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 0
type: gt
operator:
type: and
query:
params:
- C
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: A
intervalMs: 1000
maxDataPoints: 43200
refId: C
type: threshold
noDataState: NoData
execErrState: Error
isPaused: false
notification_settings:
receiver: ntfy.sh
- uid: systemd-units
title: SystemD Units
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
editorMode: code
expr: node_systemd_units{state="failed"}
instant: true
intervalMs: 1000
legendFormat: __auto
maxDataPoints: 43200
range: false
refId: A
- refId: C
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 0
type: gt
operator:
type: and
query:
params:
- C
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: A
intervalMs: 1000
maxDataPoints: 43200
refId: C
type: threshold
noDataState: NoData
execErrState: Error
keepFiringFor: 1h
isPaused: false
notification_settings:
receiver: ntfy.sh

View File

@@ -0,0 +1,872 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 2,
"links": [],
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
},
"unit": "bytes"
},
"overrides": []
},
"gridPos": {
"h": 6,
"w": 4,
"x": 0,
"y": 0
},
"id": 9,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.1",
"targets": [
{
"editorMode": "code",
"expr": "sum(increase(traefik_entrypoint_requests_bytes_total{hostname=\"$hostname\"}[$__range]))",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Requests Received",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 12,
"w": 10,
"x": 4,
"y": 0
},
"id": 3,
"options": {
"legend": {
"calcs": ["mean"],
"displayMode": "table",
"placement": "right",
"showLegend": true,
"sortBy": "Mean",
"sortDesc": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "desc"
}
},
"pluginVersion": "12.1.1",
"targets": [
{
"editorMode": "code",
"expr": "rate(traefik_entrypoint_requests_total{hostname=\"$hostname\"}[$__rate_interval])",
"legendFormat": "{{entrypoint}}/{{protocol}}: {{method}}: {{code}}",
"range": true,
"refId": "A"
}
],
"title": "Entrypoint Requests",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
},
"unit": "binBps"
},
"overrides": [
{
"matcher": {
"id": "byFrameRefID",
"options": "B"
},
"properties": [
{
"id": "custom.transform",
"value": "negative-Y"
}
]
}
]
},
"gridPos": {
"h": 12,
"w": 10,
"x": 14,
"y": 0
},
"id": 2,
"options": {
"legend": {
"calcs": ["mean"],
"displayMode": "table",
"placement": "right",
"showLegend": true,
"sortBy": "Mean",
"sortDesc": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "desc"
}
},
"pluginVersion": "12.1.1",
"targets": [
{
"editorMode": "code",
"expr": "rate(traefik_entrypoint_requests_bytes_total{hostname=\"$hostname\"}[$__rate_interval])",
"legendFormat": "req: {{entrypoint}}/{{protocol}}: {{method}}: {{code}}",
"range": true,
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "rate(traefik_entrypoint_responses_bytes_total{hostname=\"$hostname\"}[$__rate_interval])",
"hide": false,
"instant": false,
"legendFormat": "res: {{entrypoint}}/{{protocol}}: {{method}}: {{code}}",
"range": true,
"refId": "B"
}
],
"title": "Entrypoint Bytes",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
},
"unit": "bytes"
},
"overrides": []
},
"gridPos": {
"h": 6,
"w": 4,
"x": 0,
"y": 6
},
"id": 10,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.1",
"targets": [
{
"editorMode": "code",
"expr": "sum(increase(traefik_entrypoint_responses_bytes_total{hostname=\"$hostname\"}[$__range]))",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Responses Sent",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 12,
"w": 12,
"x": 0,
"y": 12
},
"id": 5,
"options": {
"legend": {
"calcs": ["mean"],
"displayMode": "table",
"placement": "right",
"showLegend": true,
"sortBy": "Mean",
"sortDesc": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "desc"
}
},
"pluginVersion": "12.1.1",
"targets": [
{
"editorMode": "code",
"expr": "rate(traefik_service_requests_total{hostname=\"$hostname\"}[$__rate_interval])",
"legendFormat": "{{service}}/{{protocol}}: {{method}}: {{code}}",
"range": true,
"refId": "A"
}
],
"title": "Service Requests",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
},
"unit": "binBps"
},
"overrides": [
{
"matcher": {
"id": "byFrameRefID",
"options": "B"
},
"properties": [
{
"id": "custom.transform",
"value": "negative-Y"
}
]
}
]
},
"gridPos": {
"h": 12,
"w": 12,
"x": 12,
"y": 12
},
"id": 7,
"options": {
"legend": {
"calcs": ["mean"],
"displayMode": "table",
"placement": "right",
"showLegend": true,
"sortBy": "Mean",
"sortDesc": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "desc"
}
},
"pluginVersion": "12.1.1",
"targets": [
{
"editorMode": "code",
"expr": "rate(traefik_service_requests_bytes_total{hostname=\"$hostname\"}[$__rate_interval])",
"legendFormat": "req: {{service}}/{{protocol}}: {{method}}: {{code}}",
"range": true,
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "rate(traefik_service_responses_bytes_total{hostname=\"$hostname\"}[$__rate_interval])",
"hide": false,
"instant": false,
"legendFormat": "res: {{service}}/{{protocol}}: {{method}}: {{code}}",
"range": true,
"refId": "B"
}
],
"title": "Service Bytes",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 12,
"w": 6,
"x": 0,
"y": 24
},
"id": 8,
"options": {
"legend": {
"calcs": ["mean"],
"displayMode": "table",
"placement": "right",
"showLegend": true,
"sortBy": "Mean",
"sortDesc": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "desc"
}
},
"pluginVersion": "12.1.1",
"targets": [
{
"editorMode": "code",
"expr": "rate(authelia_request{hostname=\"$hostname\"}[$__rate_interval])",
"legendFormat": "{{method}}: {{code}}",
"range": true,
"refId": "A"
}
],
"title": "Auth Requests",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 12,
"w": 6,
"x": 6,
"y": 24
},
"id": 4,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "desc"
}
},
"pluginVersion": "12.1.1",
"targets": [
{
"editorMode": "code",
"expr": "traefik_open_connections{hostname=\"$hostname\"}",
"legendFormat": "{{entrypoint}}",
"range": true,
"refId": "A"
}
],
"title": "Connections",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"log": 2,
"type": "log"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 12,
"w": 12,
"x": 12,
"y": 24
},
"id": 6,
"options": {
"legend": {
"calcs": ["mean"],
"displayMode": "table",
"placement": "right",
"showLegend": true,
"sortBy": "Mean",
"sortDesc": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "desc"
}
},
"pluginVersion": "12.1.1",
"targets": [
{
"editorMode": "code",
"expr": "sum by(service) (rate(traefik_service_request_duration_seconds_sum{hostname=\"$hostname\"}[$__rate_interval]))\n",
"legendFormat": "{{service}}",
"range": true,
"refId": "A"
}
],
"title": "Request Duration",
"type": "timeseries"
}
],
"preload": false,
"refresh": "30s",
"schemaVersion": 41,
"tags": [],
"templating": {
"list": [
{
"current": {
"text": "jupiter",
"value": "jupiter"
},
"definition": "label_values(hostname)",
"name": "hostname",
"options": [],
"query": {
"qryType": 1,
"query": "label_values(hostname)",
"refId": "PrometheusVariableQueryEditor-VariableQuery"
},
"refresh": 1,
"regex": "",
"type": "query"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Traefik",
"uid": "traefik",
"version": 1
}

View File

@@ -18,6 +18,7 @@ in
"grafana/authelia/password".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"grafana/authelia/digest".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"grafana/smtp".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"ntfy/tokens/jupiter/grafana".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
};
templates = {
@@ -32,6 +33,7 @@ in
authorization_policy = "admin_one_factor";
require_pkce = true;
pkce_challenge_method = "S256";
pre_configured_consent_duration = "1 year";
}
];
}
@@ -113,6 +115,37 @@ in
};
}
);
grafana-to-ntfy-env.content = ''
BAUTH_PASS=${hmConfig.sops.placeholder."ntfy/tokens/jupiter/grafana"}
NTFY_BAUTH_PASS=${hmConfig.sops.placeholder."ntfy/tokens/jupiter/grafana"}
'';
grafana-contact-points.content = builtins.readFile (
(pkgs.formats.yaml { }).generate "contact-points.yaml" {
apiVersion = 1;
contactPoints = [
{
orgId = 1;
name = "ntfy.sh";
receivers = [
{
uid = "ntfy";
type = "webhook";
settings = {
httpMethod = "POST";
url = "http://grafana-to-ntfy:8080";
username = "jupiter";
password = hmConfig.sops.placeholder."ntfy/tokens/jupiter/grafana";
headers = { };
};
disableResolveMessage = false;
}
];
}
];
}
);
};
};
@@ -127,7 +160,30 @@ in
networks.grafana.ref
networks.traefik.ref
];
volumes = [ "${hmConfig.sops.templates.grafana.path}:/etc/grafana/grafana.ini" ];
volumes =
let
dashboards = (pkgs.formats.yaml { }).generate "default.yaml" {
apiVersion = 1;
providers = [
{
name = "Default";
folder = "System";
type = "file";
url = "http://prometheus:9090";
options.path = "/var/lib/grafana/dashboards";
}
];
};
in
[
"${hmConfig.sops.templates.grafana.path}:/etc/grafana/grafana.ini:ro"
"${dashboards}:/etc/grafana/conf/provisioning/dashboards/default.yaml:ro"
"${./dashboards}:/var/lib/grafana/dashboards:ro"
"${./alerting/policies.yaml}:/etc/grafana/conf/provisioning/alerting/policies.yaml:ro"
"${./alerting/rules.yaml}:/etc/grafana/conf/provisioning/alerting/rules.yaml:ro"
"${hmConfig.sops.templates.grafana-contact-points.path}:/etc/grafana/conf/provisioning/alerting/contact-points.yaml:ro"
];
labels = [
"traefik.enable=true"
"traefik.http.routers.grafana.rule=Host(`stats.karaolidis.com`)"
@@ -142,6 +198,16 @@ in
networks = [ networks.grafana.ref ];
};
grafana-to-ntfy.containerConfig = {
image = "docker-archive:${pkgs.dockerImages.grafana-to-ntfy}";
networks = [ networks.grafana.ref ];
environments = {
"NTFY_URL" = "https://ntfy.karaolidis.com/grafana";
"BAUTH_USER" = "jupiter";
};
environmentFiles = [ hmConfig.sops.templates.grafana-to-ntfy-env.path ];
};
authelia.containerConfig.volumes = [
"${hmConfig.sops.templates.authelia-grafana.path}:/etc/authelia/conf.d/grafana.yaml:ro"
];

View File

@@ -0,0 +1,215 @@
{ user, home }:
{
config,
inputs,
pkgs,
lib,
...
}:
let
hmConfig = config.home-manager.users.${user};
inherit (hmConfig.virtualisation.quadlet) volumes containers networks;
autheliaClientId = "kwrm5k1Bgwqd4BCXiWp0feL6adpthOn0GGgQ9iIVW7IH1UIj7bA2HVj9Jv42hUheoYoE8wWJpQi8woPomrSJIauTmsBMMFTTrI6r";
in
{
home-manager.users.${user} = {
sops = {
secrets = {
"immich/smtp".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"immich/postgresql".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"immich/admin".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"immich/authelia/password".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"immich/authelia/digest".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
};
templates = {
immich-postgresql-env.content = ''
POSTGRES_PASSWORD=${hmConfig.sops.placeholder."immich/postgresql"}
'';
immich-env.content = ''
DB_PASSWORD=${hmConfig.sops.placeholder."immich/postgresql"}
IMMICH_ADMIN_PASSWORD=${hmConfig.sops.placeholder."immich/admin"}
'';
immich.content = builtins.readFile (
(pkgs.formats.json { }).generate "config.json" {
ffmpeg = {
accel = "nvenc";
accelDecode = true;
};
oauth = {
enabled = true;
buttonText = "Login with Authelia";
clientId = autheliaClientId;
clientSecret = hmConfig.sops.placeholder."immich/authelia/password";
issuerUrl = "https://id.karaolidis.com/.well-known/openid-configuration";
scope = lib.strings.concatStringsSep " " [
"openid"
"profile"
"email"
];
};
passwordLogin.enabled = true;
newVersionCheck.enabled = false;
library.watch.enabled = true;
server.externalDomain = "https://photos.karaolidis.com";
notifications.smtp = {
enabled = true;
from = "jupiter@karaolidis.com";
transport = {
host = "smtp.protonmail.ch";
port = 587;
username = "jupiter@karaolidis.com";
password = hmConfig.sops.placeholder."immich/smtp";
};
};
}
);
authelia-immich.content = builtins.readFile (
(pkgs.formats.yaml { }).generate "immich.yaml" {
identity_providers.oidc = {
authorization_policies.immich = {
default_policy = "deny";
rules = [
{
policy = "one_factor";
subject = "group:immich";
}
];
};
clients = [
{
client_id = autheliaClientId;
client_name = "immich";
client_secret = hmConfig.sops.placeholder."immich/authelia/digest";
redirect_uris = [
"https://photos.karaolidis.com/auth/login"
"https://photos.karaolidis.com/user-settings"
"app.immich:///oauth-callback"
];
authorization_policy = "immich";
scopes = [
"openid"
"profile"
"email"
];
token_endpoint_auth_method = "client_secret_post";
pre_configured_consent_duration = "1 year";
}
];
};
}
);
};
};
systemd.user.tmpfiles.rules = [
"d /mnt/storage/private/storm/containers/storage/volumes/immich/_data 700 storm storm"
];
virtualisation.quadlet = {
networks.immich = { };
volumes = {
immich-redis = { };
immich-postgresql = { };
immich-machine-learning-cache = { };
};
containers = {
immich = {
containerConfig = {
image = "docker-archive:${pkgs.dockerImages.immich}";
volumes =
let
postStart = pkgs.writeTextFile {
name = "post-start.sh";
executable = true;
text = builtins.readFile ./post-start.sh;
};
in
[
"${hmConfig.sops.templates.immich.path}:/etc/immich/config.json:ro"
"${postStart}:/etc/immich/post-start.sh:ro"
"/mnt/storage/private/storm/containers/storage/volumes/immich/_data:/var/lib/immich"
];
networks = [
networks.immich.ref
networks.traefik.ref
];
labels = [
"traefik.enable=true"
"traefik.http.routers.immich.rule=Host(`photos.karaolidis.com`)"
];
environments = {
DB_HOSTNAME = "immich-postgresql";
DB_USERNAME = "immich";
DB_DATABASE_NAME = "immich";
REDIS_HOSTNAME = "immich-redis";
IMMICH_ADMIN_EMAIL = "jupiter@karaolidis.com";
IMMICH_ADMIN_NAME = "Admin";
};
environmentFiles = [ hmConfig.sops.templates.immich-env.path ];
podmanArgs = [ "--cdi-spec-dir=/run/cdi" ];
devices = [ "nvidia.com/gpu=all" ];
};
unitConfig = {
After = [
"${containers.immich-postgresql._serviceName}.service"
"${containers.immich-redis._serviceName}.service"
"sops-nix.service"
];
Requires = [
"${containers.immich-postgresql._serviceName}.service"
"${containers.immich-redis._serviceName}.service"
];
};
};
immich-machine-learning.containerConfig = {
image = "docker-archive:${pkgs.dockerImages.immich-machine-learning}";
volumes = [ "${volumes.immich-machine-learning-cache.ref}:/tmp/immich-machine-learning" ];
networks = [ networks.immich.ref ];
podmanArgs = [ "--cdi-spec-dir=/run/cdi" ];
devices = [ "nvidia.com/gpu=all" ];
};
immich-postgresql = {
containerConfig = {
image = "docker-archive:${pkgs.dockerImages.postgresql-vectorchord}";
networks = [ networks.immich.ref ];
volumes = [ "${volumes.immich-postgresql.ref}:/var/lib/postgresql/data" ];
environments = {
POSTGRES_DB = "immich";
POSTGRES_USER = "immich";
};
environmentFiles = [ hmConfig.sops.templates.immich-postgresql-env.path ];
};
unitConfig.After = [ "sops-nix.service" ];
};
immich-redis.containerConfig = {
image = "docker-archive:${pkgs.dockerImages.redis}";
networks = [ networks.immich.ref ];
volumes = [ "${volumes.immich-redis.ref}:/var/lib/redis" ];
exec = [ "--save 60 1" ];
};
authelia.containerConfig.volumes = [
"${hmConfig.sops.templates.authelia-immich.path}:/etc/authelia/conf.d/immich.yaml:ro"
];
};
};
};
}

View File

@@ -0,0 +1,22 @@
# shellcheck shell=sh
IMMICH_HOST="${IMMICH_HOST:-http://localhost:2283}"
IMMICH_ADMIN_NAME="${IMMICH_ADMIN_NAME:-Admin}"
until response="$(curl -sf "$IMMICH_HOST/api/server/config")"; do
echo "Waiting for Immich to be ready..."
sleep 1
done
is_initialized="$(echo "$response" | jq -r '.isInitialized')"
if [ "$is_initialized" = "false" ]; then
curl -sf "$IMMICH_HOST/api/auth/admin-sign-up" \
-X POST \
-H 'Content-Type: application/json' \
--data-raw '{
"email":"'"$IMMICH_ADMIN_EMAIL"'",
"password":"'"$IMMICH_ADMIN_PASSWORD"'",
"name":"'"$IMMICH_ADMIN_NAME"'"
}'
fi

View File

@@ -38,7 +38,7 @@ in
AVATAR_ALT = "Nick Karaolidis Profile Picture";
BUTTON_ORDER = lib.strings.concatStringsSep "," [
"GHOST"
"BLOG"
"EMAIL"
"LINKED_IN"
"GITEA"
@@ -50,7 +50,14 @@ in
"STEAM"
];
GHOST = "https://blog.karaolidis.com/";
CUSTOM_BUTTON_TEXT = "Blog";
CUSTOM_BUTTON_URL = "https://blog.karaolidis.com";
CUSTOM_BUTTON_COLOR = "#000000";
CUSTOM_BUTTON_TEXT_COLOR = "#ffffff";
CUSTOM_BUTTON_ALT_TEXT = "Blog";
CUSTOM_BUTTON_NAME = "BLOG";
CUSTOM_BUTTON_ICON = "fas fa-pen-nib";
EMAIL = "nick@karaolidis.com";
EMAIL_TEXT = "E-mail";
LINKED_IN = "https://www.linkedin.com/in/nikolaos-karaolidis";

View File

@@ -1,5 +1,10 @@
{ user, home }:
{ config, ... }:
{
config,
inputs,
pkgs,
...
}:
let
hmConfig = config.home-manager.users.${user};
@@ -17,7 +22,7 @@ let
in
{
imports = [
(import ./jellyfin { inherit user home; })
(import ./plex { inherit user home; })
(import ./jellyseerr {
inherit
user
@@ -56,6 +61,34 @@ in
"d /mnt/storage/private/storm/containers/storage/volumes/media/_data/libraries/anime/shows 755 storm storm"
];
virtualisation.quadlet.networks.media = { };
sops.secrets."ntfy/tokens/jupiter/media".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
virtualisation.quadlet = {
networks.media = { };
containers.authelia.containerConfig.volumes =
let
mediaConfig = (pkgs.formats.yaml { }).generate "media.yaml" {
access_control.rules = [
{
domain = "beta.media.karaolidis.com";
policy = "one_factor";
resources = [ "^/manage([/?].*)?$" ];
subject = [ "group:media" ];
}
{
domain = "beta.media.karaolidis.com";
policy = "deny";
resources = [ "^/manage([/?].*)?$" ];
}
{
domain = "beta.media.karaolidis.com";
policy = "bypass";
}
];
};
in
[ "${mediaConfig}:/etc/authelia/conf.d/media.yaml:ro" ];
};
};
}

View File

@@ -1,150 +0,0 @@
{ user, home }:
{
config,
inputs,
pkgs,
...
}:
let
hmConfig = config.home-manager.users.${user};
inherit (hmConfig.virtualisation.quadlet) volumes networks;
jellyfinAutheliaClientId = "59TRpNutxEeRRCAZbDsK7rsnrA5NC69HAdAO45CEfc740xl4hgIacDy2u03oiFc89Exb67udBQvmfwxgeAQtJPiNAJxA5OzGmdQf";
in
{
home-manager.users.${user} = {
sops = {
secrets = {
"jellyfin/admin".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"jellyfin/authelia/password".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"jellyfin/authelia/digest".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"opensubtitles/username".sopsFile = "${inputs.secrets}/domains/personal/secrets.yaml";
"opensubtitles/password".sopsFile = "${inputs.secrets}/domains/personal/secrets.yaml";
};
templates = {
jellyfin-env.content = ''
JELLYFIN_ADMIN_PASSWORD=${hmConfig.sops.placeholder."jellyfin/admin"}
JELLYFIN_OIDC_SECRET=${hmConfig.sops.placeholder."jellyfin/authelia/password"}
OPENSUBTITLES_USERNAME=${hmConfig.sops.placeholder."opensubtitles/username"}
OPENSUBTITLES_PASSWORD=${hmConfig.sops.placeholder."opensubtitles/password"}
'';
authelia-jellyfin.content = builtins.readFile (
(pkgs.formats.yaml { }).generate "jellyfin.yaml" {
identity_providers.oidc = {
authorization_policies.jellyfin = {
default_policy = "deny";
rules = [
{
policy = "one_factor";
subject = "group:jellyfin";
}
];
};
clients = [
{
client_id = jellyfinAutheliaClientId;
client_name = "Jellyfin";
client_secret = hmConfig.sops.placeholder."jellyfin/authelia/digest";
redirect_uris = [ "https://media.karaolidis.com/sso/OID/redirect/authelia" ];
authorization_policy = "jellyfin";
require_pkce = true;
pkce_challenge_method = "S256";
scopes = [
"openid"
"profile"
"groups"
];
token_endpoint_auth_method = "client_secret_post";
}
];
};
}
);
};
};
virtualisation.quadlet = {
networks.jellyfin = { };
volumes = {
jellyfin-config = { };
jellyfin-data = { };
jellyfin-metadata = { };
jellyfin-root = { };
jellyfin-log = { };
jellyfin-cache = { };
};
containers = {
jellyfin = {
containerConfig = {
image = "docker-archive:${pkgs.dockerImages.jellyfin}";
networks = [
networks.jellyfin.ref
networks.traefik.ref
];
volumes =
let
setup = pkgs.writeTextFile {
name = "setup.sh";
executable = true;
text = builtins.readFile ./setup.sh;
};
in
[
"/mnt/storage/private/storm/containers/storage/volumes/media/_data:/var/lib/media"
"${setup}:/etc/jellyfin/setup.sh:ro"
"${./libraries}:/etc/jellyfin/libraries:ro"
"${volumes.jellyfin-config.ref}:/etc/jellyfin"
"${volumes.jellyfin-data.ref}:/var/lib/jellyfin/data"
"${volumes.jellyfin-metadata.ref}:/var/lib/jellyfin/metadata"
"${volumes.jellyfin-root.ref}:/var/lib/jellyfin/root"
"${volumes.jellyfin-log.ref}:/var/log/jellyfin"
"${volumes.jellyfin-cache.ref}:/tmp/jellyfin"
];
environments.JELLYFIN_OIDC_CLIENT_ID = jellyfinAutheliaClientId;
environmentFiles = [ hmConfig.sops.templates.jellyfin-env.path ];
labels = [
"traefik.enable=true"
"traefik.http.routers.jellyfin.rule=Host(`media.karaolidis.com`)"
];
podmanArgs = [ "--cdi-spec-dir=/run/cdi" ];
devices = [ "nvidia.com/gpu=all" ];
};
unitConfig.After = [ "sops-nix.service" ];
};
authelia.containerConfig.volumes =
let
mediaConfig = (pkgs.formats.yaml { }).generate "media.yaml" {
access_control.rules = [
{
domain = "media.karaolidis.com";
policy = "one_factor";
resources = [ "^/manage([/?].*)?$" ];
subject = [ "group:media" ];
}
{
domain = "media.karaolidis.com";
policy = "deny";
resources = [ "^/manage([/?].*)?$" ];
}
{
domain = "media.karaolidis.com";
policy = "bypass";
}
];
};
in
[
"${mediaConfig}:/etc/authelia/conf.d/media.yaml:ro"
"${hmConfig.sops.templates.authelia-jellyfin.path}:/etc/authelia/conf.d/jellyfin.yaml:ro"
];
};
};
};
}

View File

@@ -1,128 +0,0 @@
{
"LibraryOptions": {
"Enabled": true,
"EnableArchiveMediaFiles": false,
"EnablePhotos": true,
"EnableRealtimeMonitor": true,
"EnableLUFSScan": true,
"ExtractTrickplayImagesDuringLibraryScan": false,
"SaveTrickplayWithMedia": true,
"EnableTrickplayImageExtraction": true,
"ExtractChapterImagesDuringLibraryScan": false,
"EnableChapterImageExtraction": true,
"EnableInternetProviders": true,
"SaveLocalMetadata": true,
"EnableAutomaticSeriesGrouping": false,
"PreferredMetadataLanguage": "en",
"MetadataCountryCode": "JP",
"SeasonZeroDisplayName": "Specials",
"AutomaticRefreshIntervalDays": 30,
"EnableEmbeddedTitles": false,
"EnableEmbeddedExtrasTitles": false,
"EnableEmbeddedEpisodeInfos": false,
"AllowEmbeddedSubtitles": "AllowAll",
"SkipSubtitlesIfEmbeddedSubtitlesPresent": false,
"SkipSubtitlesIfAudioTrackMatches": false,
"SaveSubtitlesWithMedia": true,
"SaveLyricsWithMedia": false,
"RequirePerfectSubtitleMatch": true,
"AutomaticallyAddToCollection": true,
"PreferNonstandardArtistsTag": false,
"UseCustomTagDelimiters": false,
"MetadataSavers": ["Nfo"],
"TypeOptions": [
{
"Type": "Movie",
"MetadataFetchers": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB"
],
"MetadataFetcherOrder": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB"
],
"ImageFetchers": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageFetcherOrder": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "1",
"MinWidth": "1280"
}
]
}
],
"LocalMetadataReaderOrder": ["Nfo"],
"SubtitleDownloadLanguages": [],
"CustomTagDelimiters": ["/", "|", ";", "\\"],
"DelimiterWhitelist": [],
"DisabledSubtitleFetchers": [],
"SubtitleFetcherOrder": [],
"DisabledLyricFetchers": [],
"LyricFetcherOrder": [],
"PathInfos": [
{
"Path": "/var/lib/media/libraries/anime/films"
}
]
}
}

View File

@@ -1,128 +0,0 @@
{
"LibraryOptions": {
"Enabled": true,
"EnableArchiveMediaFiles": false,
"EnablePhotos": true,
"EnableRealtimeMonitor": true,
"EnableLUFSScan": true,
"ExtractTrickplayImagesDuringLibraryScan": false,
"SaveTrickplayWithMedia": true,
"EnableTrickplayImageExtraction": true,
"ExtractChapterImagesDuringLibraryScan": false,
"EnableChapterImageExtraction": true,
"EnableInternetProviders": true,
"SaveLocalMetadata": true,
"EnableAutomaticSeriesGrouping": false,
"PreferredMetadataLanguage": "en",
"MetadataCountryCode": "US",
"SeasonZeroDisplayName": "Specials",
"AutomaticRefreshIntervalDays": 30,
"EnableEmbeddedTitles": false,
"EnableEmbeddedExtrasTitles": false,
"EnableEmbeddedEpisodeInfos": false,
"AllowEmbeddedSubtitles": "AllowAll",
"SkipSubtitlesIfEmbeddedSubtitlesPresent": false,
"SkipSubtitlesIfAudioTrackMatches": false,
"SaveSubtitlesWithMedia": true,
"SaveLyricsWithMedia": false,
"RequirePerfectSubtitleMatch": true,
"AutomaticallyAddToCollection": true,
"PreferNonstandardArtistsTag": false,
"UseCustomTagDelimiters": false,
"MetadataSavers": ["Nfo"],
"TypeOptions": [
{
"Type": "Movie",
"MetadataFetchers": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB"
],
"MetadataFetcherOrder": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB"
],
"ImageFetchers": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageFetcherOrder": [
"TheMovieDb",
"The Open Movie Database",
"TheTVDB",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "1",
"MinWidth": "1280"
}
]
}
],
"LocalMetadataReaderOrder": ["Nfo"],
"SubtitleDownloadLanguages": [],
"CustomTagDelimiters": ["/", "|", ";", "\\"],
"DelimiterWhitelist": [],
"DisabledSubtitleFetchers": [],
"SubtitleFetcherOrder": [],
"DisabledLyricFetchers": [],
"LyricFetcherOrder": [],
"PathInfos": [
{
"Path": "/var/lib/media/libraries/films"
}
]
}
}

View File

@@ -1,204 +0,0 @@
{
"LibraryOptions": {
"Enabled": true,
"EnableArchiveMediaFiles": false,
"EnablePhotos": true,
"EnableRealtimeMonitor": true,
"EnableLUFSScan": true,
"ExtractTrickplayImagesDuringLibraryScan": false,
"SaveTrickplayWithMedia": true,
"EnableTrickplayImageExtraction": true,
"ExtractChapterImagesDuringLibraryScan": false,
"EnableChapterImageExtraction": true,
"EnableInternetProviders": true,
"SaveLocalMetadata": true,
"EnableAutomaticSeriesGrouping": true,
"PreferredMetadataLanguage": "en",
"MetadataCountryCode": "JP",
"SeasonZeroDisplayName": "Specials",
"AutomaticRefreshIntervalDays": 30,
"EnableEmbeddedTitles": false,
"EnableEmbeddedExtrasTitles": false,
"EnableEmbeddedEpisodeInfos": false,
"AllowEmbeddedSubtitles": "AllowAll",
"SkipSubtitlesIfEmbeddedSubtitlesPresent": false,
"SkipSubtitlesIfAudioTrackMatches": false,
"SaveSubtitlesWithMedia": true,
"SaveLyricsWithMedia": false,
"RequirePerfectSubtitleMatch": true,
"AutomaticallyAddToCollection": false,
"PreferNonstandardArtistsTag": false,
"UseCustomTagDelimiters": false,
"MetadataSavers": ["Nfo"],
"TypeOptions": [
{
"Type": "Series",
"MetadataFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Missing Episode Fetcher"
],
"MetadataFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Missing Episode Fetcher"
],
"ImageFetchers": ["TheTVDB", "TheMovieDb"],
"ImageFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "1",
"MinWidth": "1280"
}
]
},
{
"Type": "Season",
"MetadataFetchers": ["TheTVDB", "TheMovieDb"],
"MetadataFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageFetchers": ["TheTVDB", "TheMovieDb"],
"ImageFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "0",
"MinWidth": "1280"
}
]
},
{
"Type": "Episode",
"MetadataFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database"
],
"MetadataFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database"
],
"ImageFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Embedded Image Extractor",
"Screen Grabber"
]
}
],
"LocalMetadataReaderOrder": ["Nfo"],
"SubtitleDownloadLanguages": [],
"CustomTagDelimiters": ["/", "|", ";", "\\"],
"DelimiterWhitelist": [],
"DisabledSubtitleFetchers": [],
"SubtitleFetcherOrder": [],
"DisabledLyricFetchers": [],
"LyricFetcherOrder": [],
"PathInfos": [
{
"Path": "/var/lib/media/libraries/anime/shows"
}
]
}
}

View File

@@ -1,204 +0,0 @@
{
"LibraryOptions": {
"Enabled": true,
"EnableArchiveMediaFiles": false,
"EnablePhotos": true,
"EnableRealtimeMonitor": true,
"EnableLUFSScan": true,
"ExtractTrickplayImagesDuringLibraryScan": false,
"SaveTrickplayWithMedia": true,
"EnableTrickplayImageExtraction": true,
"ExtractChapterImagesDuringLibraryScan": false,
"EnableChapterImageExtraction": true,
"EnableInternetProviders": true,
"SaveLocalMetadata": true,
"EnableAutomaticSeriesGrouping": true,
"PreferredMetadataLanguage": "en",
"MetadataCountryCode": "US",
"SeasonZeroDisplayName": "Specials",
"AutomaticRefreshIntervalDays": 30,
"EnableEmbeddedTitles": false,
"EnableEmbeddedExtrasTitles": false,
"EnableEmbeddedEpisodeInfos": false,
"AllowEmbeddedSubtitles": "AllowAll",
"SkipSubtitlesIfEmbeddedSubtitlesPresent": false,
"SkipSubtitlesIfAudioTrackMatches": false,
"SaveSubtitlesWithMedia": true,
"SaveLyricsWithMedia": false,
"RequirePerfectSubtitleMatch": true,
"AutomaticallyAddToCollection": false,
"PreferNonstandardArtistsTag": false,
"UseCustomTagDelimiters": false,
"MetadataSavers": ["Nfo"],
"TypeOptions": [
{
"Type": "Series",
"MetadataFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Missing Episode Fetcher"
],
"MetadataFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Missing Episode Fetcher"
],
"ImageFetchers": ["TheTVDB", "TheMovieDb"],
"ImageFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "1",
"MinWidth": "1280"
}
]
},
{
"Type": "Season",
"MetadataFetchers": ["TheTVDB", "TheMovieDb"],
"MetadataFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageFetchers": ["TheTVDB", "TheMovieDb"],
"ImageFetcherOrder": ["TheTVDB", "TheMovieDb"],
"ImageOptions": [
{
"Type": "Primary",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Art",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "BoxRear",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Banner",
"Limit": 1,
"MinWidth": 0
},
{
"Type": "Box",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Disc",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Logo",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Menu",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Thumb",
"Limit": 0,
"MinWidth": 0
},
{
"Type": "Backdrop",
"Limit": "0",
"MinWidth": "1280"
}
]
},
{
"Type": "Episode",
"MetadataFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database"
],
"MetadataFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database"
],
"ImageFetchers": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Embedded Image Extractor",
"Screen Grabber"
],
"ImageFetcherOrder": [
"TheTVDB",
"TheMovieDb",
"The Open Movie Database",
"Embedded Image Extractor",
"Screen Grabber"
]
}
],
"LocalMetadataReaderOrder": ["Nfo"],
"SubtitleDownloadLanguages": [],
"CustomTagDelimiters": ["/", "|", ";", "\\"],
"DelimiterWhitelist": [],
"DisabledSubtitleFetchers": [],
"SubtitleFetcherOrder": [],
"DisabledLyricFetchers": [],
"LyricFetcherOrder": [],
"PathInfos": [
{
"Path": "/var/lib/media/libraries/shows"
}
]
}
}

View File

@@ -1,214 +0,0 @@
# shellcheck shell=sh
JELLYFIN_HOST="${JELLYFIN_HOST:-http://localhost:8096}"
JELLYFIN_ADMIN_USERNAME="${JELLYFIN_ADMIN_USERNAME:-admin}"
until response="$(curl -sf "$JELLYFIN_HOST/System/Info/Public")"; do
echo "Waiting for Jellyfin to be ready..."
sleep 1
done
setup="$(echo "$response" | jq -r '.StartupWizardCompleted')"
if [ "$setup" = "false" ]; then
curl -sf "$JELLYFIN_HOST/Startup/Configuration" \
-X POST \
-H 'Content-Type: application/json' \
--data-raw '{"UICulture":"en-US","MetadataCountryCode":"US","PreferredMetadataLanguage":"en"}'
curl -sf "$JELLYFIN_HOST/Startup/User"
curl -sf "$JELLYFIN_HOST/Startup/User" \
-X POST \
-H 'Content-Type: application/json' \
--data-raw '{"Name":"'"$JELLYFIN_ADMIN_USERNAME"'","Password":"'"$JELLYFIN_ADMIN_PASSWORD"'"}'
curl -sf "$JELLYFIN_HOST/Startup/RemoteAccess" \
-X POST \
-H 'Content-Type: application/json' \
--data-raw '{"EnableRemoteAccess":true,"EnableAutomaticPortMapping":false}'
curl -sf "$JELLYFIN_HOST/Startup/Complete" \
-X POST
fi
token="$(curl -sf "$JELLYFIN_HOST/Users/AuthenticateByName" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Client="jellyfin-init", Device="sh", DeviceId="sh", Version="1.0"' \
--data-raw '{"Username":"'"$JELLYFIN_ADMIN_USERNAME"'","Pw":"'"$JELLYFIN_ADMIN_PASSWORD"'"}' \
| jq -r '.AccessToken')"
curl -sf "$JELLYFIN_HOST/System/Configuration" \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
| jq '.EnableMetrics = true
| .ServerName = "jupiter"
| .RemoteClientBitrateLimit = 1024000000
| .TrickplayOptions.EnableHwAcceleration = true
| .TrickplayOptions.EnableHwEncoding = true' \
| curl -sf "$JELLYFIN_HOST/System/Configuration" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @-
curl -sf "$JELLYFIN_HOST/System/Configuration/encoding" \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
| jq '.EnableThrottling = true
| .HardwareAccelerationType = "nvenc"
| .EnableTonemapping = true
| .EnableDecodingColorDepth12HevcRext = true
| .AllowHevcEncoding = true
| .HardwareDecodingCodecs = ["h264", "hevc", "mpeg2video", "mpeg4", "vc1", "vp8", "vp9", "av1"]' \
| curl -sf "$JELLYFIN_HOST/System/Configuration/encoding" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @-
curl -sf "$JELLYFIN_HOST/Plugins/c83d86bb-a1e0-4c35-a113-e2101cf4ee6b/Configuration" \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
| jq '.AnalyzeSeasonZero = true
| .AnalyzeMovies = true' \
| curl -sf "$JELLYFIN_HOST/Plugins/c83d86bb-a1e0-4c35-a113-e2101cf4ee6b/Configuration" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @-
curl -sf "$JELLYFIN_HOST/Plugins/4b9ed42f-5185-48b5-9803-6ff2989014c4/Configuration" \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
| jq --arg username "$OPENSUBTITLES_USERNAME" \
--arg password "$OPENSUBTITLES_PASSWORD" \
'.Username = $username
| .Password = $password' \
| curl -sf "$JELLYFIN_HOST/Plugins/4b9ed42f-5185-48b5-9803-6ff2989014c4/Configuration" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @-
curl -sf "$JELLYFIN_HOST/Plugins/b8715ed1-6c47-4528-9ad3-f72deb539cd4/Configuration" \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
| jq '.IncludeAdult = true' \
| curl -sf "$JELLYFIN_HOST/Plugins/b8715ed1-6c47-4528-9ad3-f72deb539cd4/Configuration" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @-
curl -sf "$JELLYFIN_HOST/Plugins/505ce9d1-d916-42fa-86ca-673ef241d7df/Configuration" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @- <<EOF
{
"SamlConfigs": {},
"OidConfigs": {
"authelia": {
"OidProviderName": "authelia",
"OidEndpoint": "https://id.karaolidis.com",
"OidClientId": "$JELLYFIN_OIDC_CLIENT_ID",
"OidSecret": "$JELLYFIN_OIDC_SECRET",
"RoleClaim": "groups",
"DefaultUsernameClaim": "preferred_username",
"Enabled": true,
"EnableAuthorization": true,
"EnableAllFolders": true,
"EnableFolderRoles": false,
"EnableLiveTvRoles": false,
"EnableLiveTv": false,
"EnableLiveTvManagement": false,
"DisableHttps": false,
"DoNotValidateEndpoints": false,
"DoNotValidateIssuerName": false,
"Roles": [
"jellyfin"
],
"AdminRoles": [
"admin"
],
"LiveTvRoles": [],
"LiveTvManagementRoles": [],
"OidScopes": [
"groups"
],
"EnabledFolders": [],
"FolderRoleMapping": [],
"SchemeOverride": "https"
}
}
}
EOF
# https://github.com/9p4/jellyfin-plugin-sso/issues/16#issuecomment-2953811762
custom_css=$(cat <<EOF
a.raised.emby-button,
.loginDisclaimerContainer,
.loginDisclaimer,
.manualLoginForm {
all: unset;
}
.btnQuick,
.btnSelectServer,
.btnForgotPassword,
a.raised.emby-button,
.emby-button.block,
.loginDisclaimerContainer,
.loginDisclaimer {
margin-left: auto;
margin-right: auto;
margin-bottom: 1em;
color: inherit !important;
}
.btnForgotPassword {
display: none !important;
}
.manualLoginForm > :not(:first-child) {
display: none !important;
}
EOF
)
login_disclaimer=$(cat <<EOF
<form action="https://media.karaolidis.com/sso/OID/start/authelia">
<button class="raised block emby-button button-submit">
Sign in with Authelia
</button>
</form>
EOF
)
curl -sf "$JELLYFIN_HOST/System/Configuration/branding" \
-H "Authorization: MediaBrowser Token=$token" |
jq --arg custom_css "$custom_css" \
--arg login_disclaimer "$login_disclaimer" \
'.CustomCss = $custom_css | .LoginDisclaimer = $login_disclaimer' |
curl -sf "$JELLYFIN_HOST/System/Configuration/branding" \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: MediaBrowser Token=$token" \
--data-binary @-
existing_libraries="$(curl -sf "$JELLYFIN_HOST/Library/VirtualFolders" \
-H 'Authorization: MediaBrowser Token="'"$token"'"')"
find /etc/jellyfin/libraries -name "*.json" | sort -V | while IFS= read -r filepath; do
collectionType=$(jq -rn --arg s "$(basename "$(dirname "$filepath")")" '$s|@uri')
name=$(jq -rn --arg s "$(basename "$filepath" .json)" '$s|@uri')
if echo "$existing_libraries" | jq -e --arg name "$name" 'any(.[]; .Name | @uri == $name)'; then
echo "Skipping existing virtual folder: $name"
continue
fi
echo "Creating virtual folder: $name"
curl -sf "$JELLYFIN_HOST/Library/VirtualFolders?collectionType=$collectionType&name=$name" \
-X POST \
-H "Content-Type: application/json" \
-H 'Authorization: MediaBrowser Token="'"$token"'"' \
--data-binary @"$filepath"
done

View File

@@ -14,24 +14,14 @@
let
hmConfig = config.home-manager.users.${user};
inherit (hmConfig.virtualisation.quadlet) containers volumes networks;
arrs = radarrs ++ sonarrs;
jellyseerrAutheliaClientId = "s8QyVqBdiEStH5WXeEYNSrEh8ls2xHif0qyTGbC7V8nHNcqHi5NhqHUapCHuVFT4kEtngqgLry2SKOKepQl3AiqCWlhTjlIxr7LI";
in
{
home-manager.users.${user} = {
sops = {
secrets = {
"jellyseerr/smtp".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"jellyseerr/authelia/password".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"jellyseerr/authelia/digest".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
};
secrets."jellyseerr/smtp".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
templates = {
jellyseerr-env.content = ''
JELLYFIN_ADMIN_PASSWORD=${hmConfig.sops.placeholder."jellyfin/admin"}
'';
jellyseerr.content = builtins.readFile (
(pkgs.formats.json { }).generate "setings.json" {
main = {
@@ -43,38 +33,51 @@ in
# 32 | 4194304 | 67108864
defaultPermissions = 71303200;
localLogin = false;
mediaServerLogin = false;
oidcLogin = true;
newPlexLogin = false;
mediaServerType = 2;
mediaServerLogin = true;
oidcLogin = false;
newPlexLogin = true;
mediaServerType = 1;
partialRequestsEnabled = true;
enableSpecialEpisodes = true;
};
jellyfin = {
plex = {
name = "jupiter";
ip = "jellyfin";
port = 8096;
externalHostname = "https://media.karaolidis.com";
jellyfinForgotPasswordUrl = "https://id.karaolidis.com/reset-password/step1";
ip = "beta.media.karaolidis.com";
port = 443;
useSsl = true;
libraries = [
{
id = "1";
name = "Films";
enabled = true;
type = "movie";
}
{
id = "2";
name = "Shows";
enabled = true;
type = "show";
}
{
id = "3";
name = "Films (Anime)";
enabled = true;
type = "movie";
}
{
id = "4";
name = "Shows (Anime)";
enabled = true;
type = "show";
}
];
externalHostname = "https://beta.media.karaolidis.com";
webAppUrl = "https://beta.media.karaolidis.com";
machineId = hmConfig.sops.placeholder."plex/processedMachineIdentifier";
};
oidc.providers = [
{
slug = "authelia";
name = "Authelia";
issuerUrl = "https://id.karaolidis.com";
clientId = jellyseerrAutheliaClientId;
clientSecret = hmConfig.sops.placeholder."jellyseerr/authelia/password";
scopes = lib.strings.concatStringsSep " " [
"openid"
"profile"
"email"
"groups"
];
newUserLogin = true;
}
];
jellyfin = { };
radarr = [ ];
@@ -82,54 +85,36 @@ in
public.initialized = true;
notifications.agents.email = {
enabled = true;
options = {
emailFrom = "jupiter@karaolidis.com";
smtpHost = "smtp.protonmail.ch";
smtpPort = 587;
authUser = "jupiter@karaolidis.com";
authPass = hmConfig.sops.placeholder."jellyseerr/smtp";
senderName = "Jellyseerr";
notifications.agents = {
email = {
enabled = true;
options = {
emailFrom = "jupiter@karaolidis.com";
smtpHost = "smtp.protonmail.ch";
smtpPort = 587;
authUser = "jupiter@karaolidis.com";
authPass = hmConfig.sops.placeholder."jellyseerr/smtp";
senderName = "Jellyseerr";
};
};
ntfy = {
enabled = true;
embedPoster = true;
types = 2970;
options = {
url = "https://ntfy.karaolidis.com";
topic = "media";
authMethodUsernamePassword = false;
authMethodToken = true;
token = hmConfig.sops.placeholder."ntfy/tokens/jupiter/media";
};
};
};
network.trustProxy = true;
}
);
authelia-jellyseerr.content = builtins.readFile (
(pkgs.formats.yaml { }).generate "jellyseerr.yaml" {
identity_providers.oidc = {
authorization_policies.jellyseerr = {
default_policy = "deny";
rules = [
{
policy = "one_factor";
subject = "group:jellyfin";
}
];
};
clients = [
{
client_id = jellyseerrAutheliaClientId;
client_name = "jellyseerr";
client_secret = hmConfig.sops.placeholder."jellyseerr/authelia/digest";
redirect_uris = [ "https://request.karaolidis.com/login?provider=authelia&callback=true" ];
authorization_policy = "jellyseerr";
scopes = [
"openid"
"email"
"profile"
"groups"
];
token_endpoint_auth_method = "client_secret_post";
}
];
};
}
);
}
// builtins.listToAttrs (
builtins.map (arr: {
@@ -144,57 +129,49 @@ in
virtualisation.quadlet = {
volumes.jellyseerr = { };
containers = {
jellyseerr = {
containerConfig = {
image = "docker-archive:${pkgs.dockerImages.jellyseerr}";
networks = [
networks.jellyfin.ref
networks.media.ref
networks.traefik.ref
];
volumes =
let
preStart = pkgs.writeTextFile {
name = "pre-start.sh";
executable = true;
text = builtins.readFile ./pre-start.sh;
};
in
[
"${preStart}:/etc/jellyseerr/pre-start.sh:ro"
"${hmConfig.sops.templates.jellyseerr.path}:/etc/jellyseerr/settings.default.json:ro"
"${volumes.jellyseerr.ref}:/var/lib/jellyseerr"
]
++ builtins.map (
radarr:
"${
hmConfig.sops.templates."jellyseerr-${radarr.hostName}".path
}:/etc/jellyseerr/apps/radarr/${radarr.hostName}.json:ro"
) radarrs
++ builtins.map (
sonarr:
"${
hmConfig.sops.templates."jellyseerr-${sonarr.hostName}".path
}:/etc/jellyseerr/apps/sonarr/${sonarr.hostName}.json:ro"
) sonarrs;
environmentFiles = [ hmConfig.sops.templates.jellyseerr-env.path ];
labels = [
"traefik.enable=true"
"traefik.http.routers.jellyseerr.rule=Host(`request.karaolidis.com`)"
];
};
unitConfig.After =
containers.jellyseerr = {
containerConfig = {
image = "docker-archive:${pkgs.dockerImages.jellyseerr}";
networks = [
networks.media.ref
networks.traefik.ref
];
volumes =
let
arrServices = builtins.map (arr: "${containers.${arr.hostName}._serviceName}.service") arrs;
preStart = pkgs.writeTextFile {
name = "pre-start.sh";
executable = true;
text = builtins.readFile ./pre-start.sh;
};
in
[ "sops-nix.service" ] ++ arrServices;
[
"${preStart}:/etc/jellyseerr/pre-start.sh:ro"
"${hmConfig.sops.templates.jellyseerr.path}:/etc/jellyseerr/settings.default.json:ro"
"${volumes.jellyseerr.ref}:/var/lib/jellyseerr"
]
++ builtins.map (
radarr:
"${
hmConfig.sops.templates."jellyseerr-${radarr.hostName}".path
}:/etc/jellyseerr/apps/radarr/${radarr.hostName}.json:ro"
) radarrs
++ builtins.map (
sonarr:
"${
hmConfig.sops.templates."jellyseerr-${sonarr.hostName}".path
}:/etc/jellyseerr/apps/sonarr/${sonarr.hostName}.json:ro"
) sonarrs;
labels = [
"traefik.enable=true"
"traefik.http.routers.jellyseerr.rule=Host(`request.karaolidis.com`)"
];
};
authelia.containerConfig.volumes = [
"${hmConfig.sops.templates.authelia-jellyseerr.path}:/etc/authelia/conf.d/jellyseerr.yaml:ro"
];
unitConfig.After =
let
arrServices = builtins.map (arr: "${containers.${arr.hostName}._serviceName}.service") arrs;
in
[ "sops-nix.service" ] ++ arrServices;
};
};
};

View File

@@ -1,59 +1,5 @@
# shellcheck shell=sh
JELLYFIN_HOST="${JELLYFIN_HOST:-http://jellyfin:8096}"
JELLYFIN_ADMIN_USERNAME="${JELLYFIN_ADMIN_USERNAME:-admin}"
until public="$(curl -sf "$JELLYFIN_HOST/System/Info/Public")"; do
echo "Waiting for Jellyfin to be ready..."
sleep 1
done
until [ "$(echo "$public" | jq -r '.StartupWizardCompleted')" = "true" ]; do
echo "Waiting for Jellyfin setup wizard to finish..."
sleep 1
public="$(curl -sf "${JELLYFIN_HOST}/System/Info/Public")"
done
token="$(curl -sf "$JELLYFIN_HOST/Users/AuthenticateByName" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: MediaBrowser Client="jellyseerr-init", Device="sh", DeviceId="sh", Version="1.0"' \
--data-raw '{"Username":"'"$JELLYFIN_ADMIN_USERNAME"'","Pw":"'"$JELLYFIN_ADMIN_PASSWORD"'"}' \
| jq -r '.AccessToken')"
keys="$(curl -sf "$JELLYFIN_HOST/Auth/Keys" \
-H 'Authorization: MediaBrowser Token="'"$token"'"')"
jellyseerr_key="$(echo "$keys" | jq -r '.Items[] | select(.AppName=="Jellyseerr") | .AccessToken')"
if [ -z "$jellyseerr_key" ] || [ "$jellyseerr_key" = "null" ]; then
curl -sf "$JELLYFIN_HOST/Auth/Keys?App=Jellyseerr" \
-X POST \
-H 'Authorization: MediaBrowser Token="'"$token"'"'
keys="$(curl -sf "$JELLYFIN_HOST/Auth/Keys" \
-H 'Authorization: MediaBrowser Token="'"$token"'"')"
jellyseerr_key="$(echo "$keys" | jq -r '.Items[] | select(.AppName=="Jellyseerr") | .AccessToken')"
fi
serverId="$(echo "$public" | jq -r '.Id')"
libraries="$(curl -sf "$JELLYFIN_HOST/Library/VirtualFolders" \
-H 'Authorization: MediaBrowser Token="'"$token"'"')"
libraries="$(
echo "$libraries" | jq '[
.[]
| {
id: .ItemId,
name: .Name,
enabled: .LibraryOptions.Enabled,
type: ( .CollectionType | if . == "movies" then "movie" elif . == "tvshows" then "show" else . end )
}
]'
)"
try_forever() {
until "$@" 2>&1; do
echo "Try failed: $* - retrying in 1s"
@@ -129,15 +75,9 @@ done
tmpfile=$(mktemp)
jq -s \
--argjson libs "$libraries" \
--argjson radarr "$radarr_json" \
--argjson sonarr "$sonarr_json" \
--arg serverId "$serverId" \
--arg apiKey "$jellyseerr_key" \
'.[0] * .[1]
| .jellyfin.serverId = $serverId
| .jellyfin.apiKey = $apiKey
| .jellyfin.libraries = $libs
| .radarr = $radarr
| .sonarr = $sonarr' \
/var/lib/jellyseerr/settings.json \

View File

@@ -0,0 +1,109 @@
{ user, home }:
{
config,
inputs,
pkgs,
...
}:
let
hmConfig = config.home-manager.users.${user};
inherit (hmConfig.virtualisation.quadlet) volumes networks;
in
{
networking.firewall.allowedTCPPorts = [ 32400 ];
home-manager.users.${user} = {
sops = {
secrets = {
"plex/machineIdentifier".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"plex/processedMachineIdentifier".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"plex/anonymousMachineIdentifier".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"plex/certificateUuid".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"plex/plexOnlineToken".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
};
templates.plex.content = ''
<?xml version="1.0" encoding="utf-8"?>
<Preferences
MachineIdentifier="${hmConfig.sops.placeholder."plex/machineIdentifier"}"
ProcessedMachineIdentifier="${hmConfig.sops.placeholder."plex/processedMachineIdentifier"}"
AnonymousMachineIdentifier="${hmConfig.sops.placeholder."plex/anonymousMachineIdentifier"}"
CertificateUUID="${hmConfig.sops.placeholder."plex/certificateUuid"}"
PlexOnlineToken="${hmConfig.sops.placeholder."plex/plexOnlineToken"}"
FriendlyName="jupiter"
PlexOnlineUsername="karaolidis"
PlexOnlineMail="nick@karaolidis.com"
PlexOnlineHome="1"
PublishServerOnPlexOnlineKey="1"
AcceptedEULA="1"
DlnaEnabled="0"
customConnections="https://beta.media.karaolidis.com:443"
secureConnections="1"
IPNetworkType="v4only"
PushNotificationsEnabled="1"
logDebug="0"
sendCrashReports="0"
WanTotalMaxUploadRate="1000000"
GdmEnabled="0"
RelayEnabled="0"
FSEventLibraryPartialScanEnabled="1"
FSEventLibraryUpdatesEnabled="1"
GenerateAdMarkerBehavior="asap"
GenerateBIFBehavior="asap"
GenerateChapterThumbBehavior="asap"
GenerateVADBehavior="asap"
LoudnessAnalysisBehavior="asap"
MusicAnalysisBehavior="asap"
ScheduledLibraryUpdatesEnabled="1"
watchMusicSections="1"
HardwareDevicePath="10de:24dd:17aa:3a54@0000:01:00.0"
OptimizerTranscodeCountLimit="0"
ButlerTaskRefreshLibraries="1"
CinemaTrailersFromBluRay="1"
CinemaTrailersFromTheater="1"
CinemaTrailersType="0"
/>
'';
};
virtualisation.quadlet = {
networks.plex = { };
volumes.plex = { };
containers.plex = {
containerConfig = {
image = "docker-archive:${pkgs.dockerImages.plex}";
networks = [
networks.plex.ref
networks.traefik.ref
];
volumes =
let
postStart = pkgs.writeTextFile {
name = "post-start.sh";
executable = true;
text = builtins.readFile ./post-start.sh;
};
in
[
"${hmConfig.sops.templates.plex.path}:/etc/plex/Preferences.xml:ro"
"${postStart}:/etc/plex/post-start.sh:ro"
"/mnt/storage/private/storm/containers/storage/volumes/media/_data:/var/lib/media"
"${volumes.plex.ref}:/var/lib/plex"
];
labels = [
"traefik.enable=true"
"traefik.http.routers.plex.rule=Host(`beta.media.karaolidis.com`)"
];
podmanArgs = [ "--cdi-spec-dir=/run/cdi" ];
devices = [ "nvidia.com/gpu=all" ];
addCapabilities = [ "SYS_ADMIN" ];
publishPorts = [ "32400:32400/tcp" ];
};
unitConfig.After = [ "sops-nix.service" ];
};
};
};
}

View File

@@ -0,0 +1,112 @@
# shellcheck shell=sh
HOST="http://localhost:32400"
TOKEN="$(getPref "PlexOnlineToken")"
try_forever() {
until "$@" 2>&1; do
echo "Try failed: $* - retrying in 1s"
sleep 1
done
}
wait_for_api() {
try_forever curl -sf -H "X-Plex-Token: $TOKEN" "$HOST/identity"
echo "API is up!"
}
call() {
method="$1"
path="$2"
params="${3:-}"
curl -sf \
-X "$method" \
-H "Accept: application/json" \
-H "X-Plex-Token: $TOKEN" \
"$HOST/$path?$params"
}
get_resources() {
endpoint="$1"
call GET "$endpoint" | jq -r '.MediaContainer.Directory // []'
}
get_resource_id() {
endpoint="$1"
ident_field="$2"
name="$3"
get_resources "$endpoint" | jq -r --arg field "$ident_field" --arg name "$name" '.[] | select(.[$field] == $name) | .key // empty'
}
upsert_resource() {
endpoint="$1"
ident_field="$2"
name="$3"
params="$4"
id="$(get_resource_id "$endpoint" "$ident_field" "$name")"
if [ -n "$id" ] && [ "$id" != "null" ]; then
echo "Updating library '$name' (id=$id)"
call PUT "$endpoint/$id" "$params"
else
echo "Creating library '$name'"
call POST "$endpoint" "$params"
fi
}
prune_resources() {
endpoint="$1"
ident_field="$2"
shift 2
keep_list="$(printf '%s\n' "$@" | jq -R . | jq -s .)"
resources="$(get_resources "$endpoint")"
printf '%s' "$resources" | jq -c '.[]' | while IFS= read -r library; do
name="$(printf '%s' "$library" | jq -r --arg field "$ident_field" '.[$field]')"
id="$(printf '%s' "$library" | jq -r '.key')"
found="$(printf '%s' "$keep_list" | jq -r --arg name "$name" 'index($name)')"
if [ "$found" = "null" ]; then
echo "Deleting extra library '$name' (id=$id)"
call DELETE "$endpoint/$id" || echo "failed to delete $name, continuing"
fi
done
}
build_films_payload() {
cat <<-EOF
name=Films&type=movie&agent=tv.plex.agents.movie&scanner=Plex%20Movie&language=en-US&location=%2Fvar%2Flib%2Fmedia%2Flibraries%2Ffilms&prefs%5BuseRedbandTrailers%5D=1&prefs%5BincludeAdultContent%5D=1&prefs%5BautoCollectionThreshold%5D=2&prefs%5BcollectionMode%5D=1&prefs%5BenableAdMarkerGeneration%5D=2
EOF
}
build_shows_payload() {
cat <<-EOF
name=Shows&type=show&agent=tv.plex.agents.series&scanner=Plex%20TV%20Series&language=en-US&location=%2Fvar%2Flib%2Fmedia%2Flibraries%2Fshows&prefs%5BshowOrdering%5D=aired&prefs%5BuseSeasonTitles%5D=1&prefs%5BuseRedbandTrailers%5D=1&prefs%5BincludeAdultContent%5D=1&prefs%5BcollectionMode%5D=1&prefs%5BenableAdMarkerGeneration%5D=2
EOF
}
build_anime_films_payload() {
cat <<-EOF
name=Films%20%28Anime%29&type=movie&agent=tv.plex.agents.movie&scanner=Plex%20Movie&language=en-US&location=%2Fvar%2Flib%2Fmedia%2Flibraries%2Fanime%2Ffilms&prefs%5Bcountry%5D=JP&prefs%5BuseRedbandTrailers%5D=1&prefs%5BincludeAdultContent%5D=1&prefs%5BautoCollectionThreshold%5D=2&prefs%5BcollectionMode%5D=1&prefs%5BenableAdMarkerGeneration%5D=2
EOF
}
build_anime_shows_payload() {
cat <<-EOF
name=Shows%20%28Anime%29&type=show&agent=tv.plex.agents.series&scanner=Plex%20TV%20Series&language=en-US&location=%2Fvar%2Flib%2Fmedia%2Flibraries%2Fanime%2Fshows&prefs%5Bcountry%5D=JP&prefs%5BshowOrdering%5D=aired&prefs%5BuseSeasonTitles%5D=1&prefs%5BuseRedbandTrailers%5D=1&prefs%5BincludeAdultContent%5D=1&prefs%5BcollectionMode%5D=1&prefs%5BenableAdMarkerGeneration%5D=2
EOF
}
wait_for_api
try_forever upsert_resource "library/sections" "title" "Films" "$(build_films_payload)"
try_forever upsert_resource "library/sections" "title" "Shows" "$(build_shows_payload)"
try_forever upsert_resource "library/sections" "title" "Films (Anime)" "$(build_anime_films_payload)"
try_forever upsert_resource "library/sections" "title" "Shows (Anime)" "$(build_anime_shows_payload)"
prune_resources "library/sections" "title" "Films" "Shows" "Films (Anime)" "Shows (Anime)"

View File

@@ -23,6 +23,7 @@ in
templates = {
prowlarr-env.content = ''
API_KEY=${hmConfig.sops.placeholder."prowlarr/apiKey"}
NTFY_TOKEN=${hmConfig.sops.placeholder."ntfy/tokens/jupiter/media"}
'';
}
// builtins.listToAttrs (
@@ -77,7 +78,7 @@ in
environmentFiles = [ hmConfig.sops.templates.prowlarr-env.path ];
labels = [
"traefik.enable=true"
"traefik.http.routers.prowlarr.rule=Host(`media.karaolidis.com`) && PathPrefix(`/manage/indexers`)"
"traefik.http.routers.prowlarr.rule=Host(`beta.media.karaolidis.com`) && PathPrefix(`/manage/indexers`)"
"traefik.http.routers.prowlarr.middlewares=authelia@docker"
];
};

View File

@@ -205,8 +205,34 @@ build_cardigann_indexer_payload() {
'
}
build_ntfy_payload() {
cat <<-EOF
{
"onGrab": false,
"onHealthIssue": true,
"onHealthRestored": true,
"onApplicationUpdate": false,
"includeManualGrabs": false,
"includeHealthWarnings": false,
"name": "ntfy.sh",
"fields": [
{ "name": "serverUrl", "value": "https://ntfy.karaolidis.com" },
{ "name": "accessToken", "value": "$NTFY_TOKEN" },
{ "name": "topics", "value": "media" },
{ "name": "priority", "value": 3 },
{ "name": "tags", "value": "satellite" },
],
"implementation": "Ntfy",
"configContract": "NtfySettings"
}
EOF
}
wait_for_api
try_forever upsert_resource "notification" "name" "ntfy.sh" "$(build_ntfy_payload)"
prune_resources "notification" "name" "ntfy.sh"
try_forever upsert_resource "downloadclient" "name" "Transmission" "$(build_transmission_payload)"
prune_resources "downloadclient" "name" "Transmission"

View File

@@ -83,7 +83,7 @@ rec {
activeDirectory = "/var/lib/media/libraries${mediaFolderBase}";
minimumAvailability = "released";
isDefault = !isAnime;
externalUrl = "https://media.karaolidis.com${urlBase}";
externalUrl = "https://beta.media.karaolidis.com${urlBase}";
syncEnabled = true;
};
}

View File

@@ -20,6 +20,7 @@ rec {
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
};
# https://github.com/recyclarr/config-templates/blob/master/radarr/templates/anime-radarr.yml
recyclarrConfig = mkRecyclarrConfig {
inherit hostName port urlBase;

View File

@@ -25,6 +25,7 @@ rec {
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
# https://github.com/recyclarr/config-templates/blob/master/radarr/templates/uhd-bluray-web.yml
extraConfig = {
include = [
{ template = "radarr-quality-definition-movie"; }
@@ -72,9 +73,9 @@ rec {
}
{
trash_ids = [
"923b6abef9b17f937fab56cfcf89e1f1" # DV (WEBDL)
"b17886cb4158d9fea189859409975758" # HDR10Plus Boost
"55a5b50cb416dea5a50c4955896217ab" # DV HDR10+ Boost
"923b6abef9b17f937fab56cfcf89e1f1" # DV (w/o HDR fallback)
"b337d6812e06c200ec9a2d3cfa9d20a7" # DV Boost
"caa37d0df9c348912df1fb1d88f9273a" # HDR10+ Boost
];
assign_scores_to = [ { name = "UHD Bluray + WEB"; } ];
}

View File

@@ -25,6 +25,7 @@ rec {
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
# https://github.com/recyclarr/config-templates/blob/master/radarr/templates/hd-bluray-web.yml
extraConfig = {
include = [
{ template = "radarr-quality-definition-movie"; }

View File

@@ -28,6 +28,7 @@ in
name = "${radarr.hostName}-env";
value.content = ''
API_KEY=${hmConfig.sops.placeholder."${radarr.hostName}/apiKey"}
NTFY_TOKEN=${hmConfig.sops.placeholder."ntfy/tokens/jupiter/media"}
'';
}) radarrs
);
@@ -81,7 +82,7 @@ in
environmentFiles = [ hmConfig.sops.templates."${radarr.hostName}-env".path ];
labels = [
"traefik.enable=true"
"traefik.http.routers.${radarr.hostName}.rule=Host(`media.karaolidis.com`) && PathPrefix(`${radarr.urlBase}`)"
"traefik.http.routers.${radarr.hostName}.rule=Host(`beta.media.karaolidis.com`) && PathPrefix(`${radarr.urlBase}`)"
"traefik.http.routers.${radarr.hostName}.middlewares=authelia@docker"
];
};

View File

@@ -146,8 +146,41 @@ build_rootfolder_payload() {
EOF
}
build_ntfy_payload() {
cat <<-EOF
{
"onGrab": true,
"onDownload": true,
"onUpgrade": true,
"onRename": false,
"onMovieAdded": true,
"onMovieDelete": true,
"onMovieFileDelete": true,
"onMovieFileDeleteForUpgrade": false,
"onHealthIssue": true,
"includeHealthWarnings": false,
"onHealthRestored": true,
"onApplicationUpdate": false,
"onManualInteractionRequired": true,
"name": "ntfy.sh",
"fields": [
{ "name": "serverUrl", "value": "https://ntfy.karaolidis.com" },
{ "name": "accessToken", "value": "$NTFY_TOKEN" },
{ "name": "priority", "value": 2 },
{ "name": "topics", "value": ["media"] },
{ "name": "tags", "value": ["clapper"] }
],
"implementation": "Ntfy",
"configContract": "NtfySettings"
}
EOF
}
wait_for_api
try_forever upsert_resource "notification" "name" "ntfy.sh" "$(build_ntfy_payload)"
prune_resources "notification" "name" "ntfy.sh"
if [ -n "$ROOT_FOLDER" ]; then
insert_or_skip_resource "rootfolder" "path" "$ROOT_FOLDER" "$(build_rootfolder_payload)"
prune_resources "rootfolder" "path" "$ROOT_FOLDER"

View File

@@ -85,7 +85,7 @@ rec {
activeAnimeDirectory = "/var/lib/media/libraries${mediaFolderBase}";
isDefault = !isAnime;
enableSeasonFolders = true;
externalUrl = "https://media.karaolidis.com${urlBase}";
externalUrl = "https://beta.media.karaolidis.com${urlBase}";
syncEnabled = true;
};
}

View File

@@ -20,6 +20,7 @@ rec {
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
};
# https://github.com/recyclarr/config-templates/blob/master/sonarr/templates/anime-sonarr-v4.yml
recyclarrConfig = mkRecyclarrConfig {
inherit hostName port urlBase;

View File

@@ -20,6 +20,7 @@ rec {
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
};
# https://github.com/recyclarr/config-templates/blob/master/sonarr/templates/web-2160p-v4.yml
recyclarrConfig = mkRecyclarrConfig {
inherit hostName port urlBase;
@@ -35,9 +36,9 @@ rec {
custom_formats = [
{
trash_ids = [
"9b27ab6498ec0f31a3353992e19434ca" # DV (WEBDL)
"0dad0a507451acddd754fe6dc3a7f5e7" # HDR10+ Boost
"385e9e8581d33133c3961bdcdeffb7b4" # DV HDR10+ Boost
"9b27ab6498ec0f31a3353992e19434ca" # DV (w/o HDR fallback)
"0c4b99df9206d2cfac3c05ab897dd62a" # HDR10+ Boost
"7c3a61a9c6cb04f52f1544be6d44a026" # DV Boost
];
assign_scores_to = [ { name = "WEB-2160p"; } ];
}

View File

@@ -20,6 +20,7 @@ rec {
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
};
# https://github.com/recyclarr/config-templates/blob/master/sonarr/templates/web-1080p-v4.yml
recyclarrConfig = mkRecyclarrConfig {
inherit hostName port urlBase;

View File

@@ -28,6 +28,7 @@ in
name = "${sonarr.hostName}-env";
value.content = ''
API_KEY=${hmConfig.sops.placeholder."${sonarr.hostName}/apiKey"}
NTFY_TOKEN=${hmConfig.sops.placeholder."ntfy/tokens/jupiter/media"}
'';
}) sonarrs
);
@@ -81,7 +82,7 @@ in
environmentFiles = [ hmConfig.sops.templates."${sonarr.hostName}-env".path ];
labels = [
"traefik.enable=true"
"traefik.http.routers.${sonarr.hostName}.rule=Host(`media.karaolidis.com`) && PathPrefix(`${sonarr.urlBase}`)"
"traefik.http.routers.${sonarr.hostName}.rule=Host(`beta.media.karaolidis.com`) && PathPrefix(`${sonarr.urlBase}`)"
"traefik.http.routers.${sonarr.hostName}.middlewares=authelia@docker"
];
};

View File

@@ -146,8 +146,42 @@ build_rootfolder_payload() {
EOF
}
build_ntfy_payload() {
cat <<-EOF
{
"onGrab": true,
"onDownload": true,
"onUpgrade": true,
"onImportComplete": false,
"onRename": false,
"onSeriesAdd": true,
"onSeriesDelete": true,
"onEpisodeFileDelete": true,
"onEpisodeFileDeleteForUpgrade": false,
"onHealthIssue": true,
"includeHealthWarnings": false,
"onHealthRestored": true,
"onApplicationUpdate": false,
"onManualInteractionRequired": true,
"name": "ntfy.sh",
"fields": [
{ "name": "serverUrl", "value": "https://ntfy.karaolidis.com" },
{ "name": "accessToken", "value": "$NTFY_TOKEN" },
{ "name": "priority", "value": 2 },
{ "name": "topics", "value": ["media"] },
{ "name": "tags", "value": ["tv"] }
],
"implementation": "Ntfy",
"configContract": "NtfySettings"
}
EOF
}
wait_for_api
try_forever upsert_resource "notification" "name" "ntfy.sh" "$(build_ntfy_payload)"
prune_resources "notification" "name" "ntfy.sh"
if [ -n "$ROOT_FOLDER" ]; then
insert_or_skip_resource "rootfolder" "path" "$ROOT_FOLDER" "$(build_rootfolder_payload)"
prune_resources "rootfolder" "path" "$ROOT_FOLDER"

View File

@@ -33,8 +33,7 @@ in
volumes =
let
config = (pkgs.formats.json { }).generate "settings.override.json" {
ratio-limit-enabled = true;
ratio-limit = 5;
ratio-limit-enabled = false;
download-queue-enabled = false;
peer-limit-per-torrent = 100;
peer-limit-global = 1000;
@@ -50,12 +49,12 @@ in
"/mnt/storage/private/storm/containers/storage/volumes/media/_data:/var/lib/media"
];
environments = {
WIREGUARD_PUBLIC_KEY = "zctOjv4DH2gzXtLQy86Tp0vnT+PNpMsxecd2vUX/i0U=";
WIREGUARD_ENDPOINT = "146.70.179.50:51820";
WIREGUARD_PUBLIC_KEY = "jSrxQTLrvNPkljpa+F0OZT53mgTTwQA65oTMkqf382A=";
WIREGUARD_ENDPOINT = "46.29.25.4:51820";
};
labels = [
"traefik.enable=true"
"traefik.http.routers.transmission.rule=Host(`media.karaolidis.com`) && PathPrefix(`/manage/torrents`)"
"traefik.http.routers.transmission.rule=Host(`beta.media.karaolidis.com`) && PathPrefix(`/manage/torrents`)"
"traefik.http.routers.transmission.middlewares=authelia@docker"
];
};

View File

@@ -137,6 +137,7 @@ in
"groups"
"is_admin"
];
pre_configured_consent_duration = "1 year";
}
];
};
@@ -169,14 +170,14 @@ in
];
volumes =
let
post-setup = pkgs.writeTextFile {
postSetup = pkgs.writeTextFile {
name = "post-setup.sh";
executable = true;
text = builtins.readFile ./post-setup.sh;
};
in
[
"${post-setup}:/etc/nextcloud/post-setup.sh:ro"
"${postSetup}:/etc/nextcloud/post-setup.sh:ro"
"/mnt/storage/private/storm/containers/storage/volumes/nextcloud-data/_data:/var/lib/nextcloud"
"${volumes.nextcloud-log.ref}:/var/log/nextcloud"
"${volumes.nextcloud-config.ref}:/var/www/nextcloud/config"

View File

@@ -17,6 +17,9 @@ in
"ntfy/webPush/publicKey".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"ntfy/webPush/privateKey".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"ntfy/users/karaolidis".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"ntfy/users/jupiter".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"ntfy/tokens/jupiter/grafana".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"ntfy/tokens/jupiter/media".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
};
templates = {
@@ -43,7 +46,20 @@ in
auth-default-access = "deny-all";
auth-startup-queries = dbStartupQueries;
auth-users = [ "karaolidis:${hmConfig.sops.placeholder."ntfy/users/karaolidis"}:admin" ];
auth-users = [
"jupiter:${hmConfig.sops.placeholder."ntfy/users/jupiter"}:user"
"karaolidis:${hmConfig.sops.placeholder."ntfy/users/karaolidis"}:admin"
];
auth-access = [
"jupiter:grafana:wo"
"jupiter:media:wo"
];
auth-tokens = [
"jupiter:${hmConfig.sops.placeholder."ntfy/tokens/jupiter/grafana"}"
"jupiter:${hmConfig.sops.placeholder."ntfy/tokens/jupiter/media"}"
];
behind-proxy = true;

View File

@@ -65,6 +65,7 @@ in
];
response_types = [ "code" ];
token_endpoint_auth_method = "client_secret_post";
pre_configured_consent_duration = "1 year";
}
];
};

View File

@@ -10,16 +10,10 @@ let
inherit (hmConfig.virtualisation.quadlet) networks volumes containers;
in
{
networking.firewall = {
allowedTCPPorts = [
80
443
];
allowedUDPPorts = [
80
443
];
};
networking.firewall.allowedTCPPorts = [
80
443
];
home-manager.users.${user} = {
sops = {
@@ -76,11 +70,12 @@ in
"--entrypoints.https.http.tls=true"
"--entrypoints.https.http.tls.certResolver=letsencrypt"
"--entrypoints.https.http.tls.domains[0].main=karaolidis.com"
"--entrypoints.https.http.tls.domains[0].sans=*.karaolidis.com,*.tunnel.karaolidis.com,*.gaming.karaolidis.com"
"--entrypoints.https.http.tls.domains[0].sans=*.karaolidis.com,*.tunnel.karaolidis.com,*.gaming.karaolidis.com,beta.media.karaolidis.com"
"--entrypoints.https.http.tls.domains[1].main=krlds.com"
"--entrypoints.https.http.tls.domains[1].sans=*.krlds.com"
"--entryPoints.https.http3"
"--entrypoints.https.http.middlewares=compress@docker,security-headers@docker"
"--entrypoints.https.transport.respondingTimeouts.readTimeout=0s"
"--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
"--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
@@ -115,7 +110,7 @@ in
"traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
"traefik.http.middlewares.security-headers.headers.stsPreload=true"
"traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
"traefik.http.middlewares.security-headers.headers.frameDeny=true"
"traefik.http.middlewares.security-headers.headers.customFrameOptionsValue=SAMEORIGIN"
];
environmentFiles = [ hmConfig.sops.templates.traefik-env.path ];
};

View File

@@ -64,6 +64,7 @@ in
"offline_access"
];
response_types = [ "code" ];
pre_configured_consent_duration = "1 year";
}
];
};

View File

@@ -0,0 +1,23 @@
{ user, home }:
{ lib, ... }:
{
programs.steam.gamescopeSession = {
enable = true;
args = [
"-O"
"HDMI-A-1"
"-W"
"3840"
"-H"
"2160"
"--hdr-enabled"
"--adaptive-sync"
];
};
home-manager.users.${user}.programs.zsh.loginExtra = lib.mkAfter ''
if [[ "$(tty)" = "/dev/tty1" ]]; then
exec steam-gamescope
fi
'';
}

View File

@@ -0,0 +1,60 @@
{
config,
lib,
inputs,
...
}:
let
# FIXME: https://github.com/NixOS/nixpkgs/issues/24570
# FIXME: https://github.com/NixOS/nixpkgs/issues/305643
user = "tv";
home = "/home/tv";
in
{
imports = [
(import ../../../common/configs/user { inherit user home; })
(import ../../../common/configs/user/console/brightnessctl { inherit user home; })
(import ../../../common/configs/user/console/home-manager { inherit user home; })
(import ../../../common/configs/user/console/sops { inherit user home; })
(import ../../../common/configs/user/console/xdg { inherit user home; })
(import ../../../common/configs/user/console/zsh { inherit user home; })
(import ../../../common/configs/user/gui/gaming/gamemode { inherit user home; })
(import ../../../common/configs/user/gui/gaming/gamescope { inherit user home; })
(import ../../../common/configs/user/gui/gaming/steam { inherit user home; })
(import ./configs/gui/steam { inherit user home; })
];
# mkpasswd -s
sops.secrets."${user}-password" = {
sopsFile = "${inputs.secrets}/domains/personal/secrets.yaml";
key = "password";
neededForUsers = true;
};
users.users.${user} = {
inherit home;
isNormalUser = true;
email = "nick@karaolidis.com";
fullName = "Nikolaos Karaolidis (TV)";
description = "Nikolaos Karaolidis (TV)";
hashedPasswordFile = config.sops.secrets."${user}-password".path;
extraGroups = [
"wheel"
"networkmanager"
"storage"
];
linger = true;
uid = lib.strings.toInt (builtins.readFile ./uid);
openssh.authorizedKeys.keyFiles = [ "${inputs.secrets}/domains/personal/id_ed25519.pub" ];
};
services.getty.autologinUser = user;
home-manager.users.${user}.home = {
username = user;
homeDirectory = home;
};
}

View File

@@ -0,0 +1 @@
1001

View File

@@ -0,0 +1,162 @@
diff --git a/client/src/cli.rs b/client/src/cli.rs
index ee86783..e96b1b6 100644
--- a/client/src/cli.rs
+++ b/client/src/cli.rs
@@ -9,6 +9,7 @@ use enum_as_inner::EnumAsInner;
use crate::command::cache::{self, Cache};
use crate::command::get_closure::{self, GetClosure};
+use crate::command::key::{self, Key};
use crate::command::login::{self, Login};
use crate::command::push::{self, Push};
use crate::command::r#use::{self, Use};
@@ -30,6 +31,7 @@ pub enum Command {
Push(Push),
Cache(Cache),
WatchStore(WatchStore),
+ Key(Key),
#[clap(hide = true)]
GetClosure(GetClosure),
@@ -57,6 +59,7 @@ pub async fn run() -> Result<()> {
Command::Cache(_) => cache::run(opts).await,
Command::WatchStore(_) => watch_store::run(opts).await,
Command::GetClosure(_) => get_closure::run(opts).await,
+ Command::Key(_) => key::run(opts).await,
}
}
diff --git a/client/src/command/cache.rs b/client/src/command/cache.rs
index af01378..af24d8c 100644
--- a/client/src/command/cache.rs
+++ b/client/src/command/cache.rs
@@ -7,8 +7,11 @@ use crate::api::ApiClient;
use crate::cache::CacheRef;
use crate::cli::Opts;
use crate::config::Config;
-use attic::api::v1::cache_config::{
- CacheConfig, CreateCacheRequest, KeypairConfig, RetentionPeriodConfig,
+use attic::{
+ api::v1::cache_config::{
+ CacheConfig, CreateCacheRequest, KeypairConfig, RetentionPeriodConfig,
+ },
+ signing::NixKeypair,
};
/// Manage caches on an Attic server.
@@ -72,6 +75,12 @@ struct Create {
default_value = "cache.nixos.org-1"
)]
upstream_cache_key_names: Vec<String>,
+
+ /// The signing keypair to use for the cache.
+ ///
+ /// If not specified, a new keypair will be generated.
+ #[clap(long)]
+ keypair_path: Option<String>,
}
/// Configure a cache.
@@ -91,6 +100,14 @@ struct Configure {
#[clap(long)]
regenerate_keypair: bool,
+ /// Set a keypair for the cache.
+ ///
+ /// The server-side signing key will be set to the
+ /// specified keypair. This is useful for setting up
+ /// a cache with a pre-existing keypair.
+ #[clap(long, conflicts_with = "regenerate_keypair")]
+ keypair_path: Option<String>,
+
/// Make the cache public.
///
/// Use `--private` to make it private.
@@ -179,9 +196,15 @@ async fn create_cache(sub: Create) -> Result<()> {
let (server_name, server, cache) = config.resolve_cache(&sub.cache)?;
let api = ApiClient::from_server_config(server.clone())?;
+ let keypair = if let Some(keypair_path) = &sub.keypair_path {
+ let contents = std::fs::read_to_string(keypair_path)?;
+ KeypairConfig::Keypair(NixKeypair::from_str(&contents)?)
+ } else {
+ KeypairConfig::Generate
+ };
+
let request = CreateCacheRequest {
- // TODO: Make this configurable?
- keypair: KeypairConfig::Generate,
+ keypair,
is_public: sub.public,
priority: sub.priority,
store_dir: sub.store_dir,
@@ -230,6 +253,10 @@ async fn configure_cache(sub: Configure) -> Result<()> {
if sub.regenerate_keypair {
patch.keypair = Some(KeypairConfig::Generate);
+ } else if let Some(keypair_path) = &sub.keypair_path {
+ let contents = std::fs::read_to_string(keypair_path)?;
+ let keypair = KeypairConfig::Keypair(NixKeypair::from_str(&contents)?);
+ patch.keypair = Some(keypair);
}
patch.store_dir = sub.store_dir;
diff --git a/client/src/command/key.rs b/client/src/command/key.rs
new file mode 100644
index 0000000..807d8a7
--- /dev/null
+++ b/client/src/command/key.rs
@@ -0,0 +1,42 @@
+use anyhow::Result;
+use clap::{Parser, Subcommand};
+
+use crate::cli::Opts;
+use attic::signing::NixKeypair;
+
+/// Manage signing keys.
+#[derive(Debug, Parser)]
+pub struct Key {
+ #[clap(subcommand)]
+ command: KeyCommand,
+}
+
+#[derive(Debug, Subcommand)]
+enum KeyCommand {
+ Generate(Generate),
+}
+
+/// Generate a key.
+#[derive(Debug, Clone, Parser)]
+pub struct Generate {
+ /// Name of the key (must not contain colons).
+ name: String,
+}
+
+pub async fn run(opts: Opts) -> Result<()> {
+ let sub = opts.command.as_key().unwrap();
+ match &sub.command {
+ KeyCommand::Generate(sub) => generate_key(sub).await,
+ }
+}
+
+async fn generate_key(sub: &Generate) -> Result<()> {
+ let keypair = NixKeypair::generate(&sub.name)?;
+
+ println!("🔑 Generated keypair \"{}\"", sub.name);
+ println!();
+ println!(" Private key: {}", keypair.export_keypair());
+ println!(" Public key: {}", keypair.export_public_key());
+
+ Ok(())
+}
diff --git a/client/src/command/mod.rs b/client/src/command/mod.rs
index cca423f..26b105a 100644
--- a/client/src/command/mod.rs
+++ b/client/src/command/mod.rs
@@ -1,5 +1,6 @@
pub mod cache;
pub mod get_closure;
+pub mod key;
pub mod login;
pub mod push;
pub mod r#use;

View File

@@ -1,5 +1,11 @@
final: prev:
# FIXME: https://github.com/zhaofengli/attic/pull/280
prev.attic-client.overrideAttrs (oldAttrs: {
patches = oldAttrs.patches or [ ] ++ [ ./stdout-logging.patch ];
patches = oldAttrs.patches or [ ] ++ [
# fix: log non-errors to stdout
(builtins.fetchurl {
url = "https://github.com/zhaofengli/attic/pull/280.patch";
sha256 = "sha256:0j6ay6d9is7053sq5njakjmlpwk24db296rma694jggpl19ibxjv";
})
./declarative-key-pair.patch
];
})

View File

@@ -1,321 +0,0 @@
diff --git a/client/src/command/cache.rs b/client/src/command/cache.rs
index af01378..0602b3b 100644
--- a/client/src/command/cache.rs
+++ b/client/src/command/cache.rs
@@ -189,7 +189,7 @@ async fn create_cache(sub: Create) -> Result<()> {
};
api.create_cache(cache, request).await?;
- eprintln!(
+ println!(
"✨ Created cache \"{}\" on \"{}\"",
cache.as_str(),
server_name.as_str()
@@ -239,7 +239,7 @@ async fn configure_cache(sub: Configure) -> Result<()> {
let api = ApiClient::from_server_config(server.clone())?;
api.configure_cache(cache, &patch).await?;
- eprintln!(
+ println!(
"✅ Configured \"{}\" on \"{}\"",
cache.as_str(),
server_name.as_str()
@@ -254,12 +254,12 @@ async fn destroy_cache(sub: Destroy) -> Result<()> {
let (server_name, server, cache) = config.resolve_cache(&sub.cache)?;
if !sub.no_confirm {
- eprintln!("When you destory a cache:");
- eprintln!();
- eprintln!("1. Everyone will lose access.");
- eprintln!("2. The underlying data won't be deleted immediately.");
- eprintln!("3. You may not be able to create a cache of the same name.");
- eprintln!();
+ println!("When you destory a cache:");
+ println!();
+ println!("1. Everyone will lose access.");
+ println!("2. The underlying data won't be deleted immediately.");
+ println!("3. You may not be able to create a cache of the same name.");
+ println!();
let answer: String = Input::new()
.with_prompt(format!(
@@ -278,7 +278,7 @@ async fn destroy_cache(sub: Destroy) -> Result<()> {
let api = ApiClient::from_server_config(server.clone())?;
api.destroy_cache(cache).await?;
- eprintln!("🗑️ The cache was destroyed.");
+ println!("🗑️ The cache was destroyed.");
Ok(())
}
@@ -291,40 +291,40 @@ async fn show_cache_config(sub: Info) -> Result<()> {
let cache_config = api.get_cache_config(cache).await?;
if let Some(is_public) = cache_config.is_public {
- eprintln!(" Public: {}", is_public);
+ println!(" Public: {}", is_public);
}
if let Some(public_key) = cache_config.public_key {
- eprintln!(" Public Key: {}", public_key);
+ println!(" Public Key: {}", public_key);
}
if let Some(substituter_endpoint) = cache_config.substituter_endpoint {
- eprintln!("Binary Cache Endpoint: {}", substituter_endpoint);
+ println!("Binary Cache Endpoint: {}", substituter_endpoint);
}
if let Some(api_endpoint) = cache_config.api_endpoint {
- eprintln!(" API Endpoint: {}", api_endpoint);
+ println!(" API Endpoint: {}", api_endpoint);
}
if let Some(store_dir) = cache_config.store_dir {
- eprintln!(" Store Directory: {}", store_dir);
+ println!(" Store Directory: {}", store_dir);
}
if let Some(priority) = cache_config.priority {
- eprintln!(" Priority: {}", priority);
+ println!(" Priority: {}", priority);
}
if let Some(upstream_cache_key_names) = cache_config.upstream_cache_key_names {
- eprintln!(" Upstream Cache Keys: {:?}", upstream_cache_key_names);
+ println!(" Upstream Cache Keys: {:?}", upstream_cache_key_names);
}
if let Some(retention_period) = cache_config.retention_period {
match retention_period {
RetentionPeriodConfig::Period(period) => {
- eprintln!(" Retention Period: {:?}", period);
+ println!(" Retention Period: {:?}", period);
}
RetentionPeriodConfig::Global => {
- eprintln!(" Retention Period: Global Default");
+ println!(" Retention Period: Global Default");
}
}
}
diff --git a/client/src/command/login.rs b/client/src/command/login.rs
index 9abcea7..6cadd59 100644
--- a/client/src/command/login.rs
+++ b/client/src/command/login.rs
@@ -28,7 +28,7 @@ pub async fn run(opts: Opts) -> Result<()> {
let mut config_m = config.as_mut();
if let Some(server) = config_m.servers.get_mut(&sub.name) {
- eprintln!("✍️ Overwriting server \"{}\"", sub.name.as_str());
+ println!("✍️ Overwriting server \"{}\"", sub.name.as_str());
server.endpoint = sub.endpoint.to_owned();
@@ -38,7 +38,7 @@ pub async fn run(opts: Opts) -> Result<()> {
});
}
} else {
- eprintln!("✍️ Configuring server \"{}\"", sub.name.as_str());
+ println!("✍️ Configuring server \"{}\"", sub.name.as_str());
config_m.servers.insert(
sub.name.to_owned(),
diff --git a/client/src/command/push.rs b/client/src/command/push.rs
index b2bb661..5d39549 100644
--- a/client/src/command/push.rs
+++ b/client/src/command/push.rs
@@ -91,7 +91,7 @@ impl PushContext {
return Ok(());
} else {
- eprintln!("⚙️ Pushing {num_missing_paths} paths to \"{cache}\" on \"{server}\" ({num_already_cached} already cached, {num_upstream} in upstream)...",
+ println!("⚙️ Pushing {num_missing_paths} paths to \"{cache}\" on \"{server}\" ({num_already_cached} already cached, {num_upstream} in upstream)...",
cache = self.cache_name.as_str(),
server = self.server_name.as_str(),
num_missing_paths = plan.store_path_map.len(),
diff --git a/client/src/command/use.rs b/client/src/command/use.rs
index 37d8cd6..d87f65e 100644
--- a/client/src/command/use.rs
+++ b/client/src/command/use.rs
@@ -34,15 +34,15 @@ pub async fn run(opts: Opts) -> Result<()> {
let public_key = cache_config.public_key
.ok_or_else(|| anyhow!("The server did not tell us which public key it uses. Is signing managed by the client?"))?;
- eprintln!(
+ println!(
"Configuring Nix to use \"{cache}\" on \"{server_name}\":",
cache = cache.as_str(),
server_name = server_name.as_str(),
);
// Modify nix.conf
- eprintln!("+ Substituter: {}", substituter);
- eprintln!("+ Trusted Public Key: {}", public_key);
+ println!("+ Substituter: {}", substituter);
+ println!("+ Trusted Public Key: {}", public_key);
let mut nix_config = NixConfig::load().await?;
nix_config.add_substituter(&substituter);
@@ -50,7 +50,7 @@ pub async fn run(opts: Opts) -> Result<()> {
// Modify netrc
if let Some(token) = server.token()? {
- eprintln!("+ Access Token");
+ println!("+ Access Token");
let mut nix_netrc = NixNetrc::load().await?;
let host = Url::parse(&substituter)?
diff --git a/client/src/command/watch_store.rs b/client/src/command/watch_store.rs
index 24eaf7a..aec0c33 100644
--- a/client/src/command/watch_store.rs
+++ b/client/src/command/watch_store.rs
@@ -91,7 +91,7 @@ pub async fn run(opts: Opts) -> Result<()> {
watcher.watch(&store_dir, RecursiveMode::NonRecursive)?;
- eprintln!(
+ println!(
"👀 Pushing new store paths to \"{cache}\" on \"{server}\"",
cache = cache.as_str(),
server = server_name.as_str(),
diff --git a/client/src/push.rs b/client/src/push.rs
index 309bd4b..2fea414 100644
--- a/client/src/push.rs
+++ b/client/src/push.rs
@@ -595,7 +595,7 @@ pub async fn upload_path(
};
mp.suspend(|| {
- eprintln!(
+ println!(
"✅ {} ({})",
path.as_os_str().to_string_lossy(),
info_string
diff --git a/server/src/database/migration/m20230112_000004_migrate_nar_remote_files_to_chunks.rs b/server/src/database/migration/m20230112_000004_migrate_nar_remote_files_to_chunks.rs
index 42d70a6..6bbe585 100644
--- a/server/src/database/migration/m20230112_000004_migrate_nar_remote_files_to_chunks.rs
+++ b/server/src/database/migration/m20230112_000004_migrate_nar_remote_files_to_chunks.rs
@@ -24,7 +24,7 @@ impl MigrationTrait for Migration {
// When this migration is run, we assume that there are no
// preexisting chunks.
- eprintln!("* Migrating NARs to chunks...");
+ println!("* Migrating NARs to chunks...");
// Add a temporary column into `chunk` to store the related `nar_id`.
manager
diff --git a/server/src/database/migration/m20230112_000005_drop_old_nar_columns.rs b/server/src/database/migration/m20230112_000005_drop_old_nar_columns.rs
index 9d29b66..7436b4a 100644
--- a/server/src/database/migration/m20230112_000005_drop_old_nar_columns.rs
+++ b/server/src/database/migration/m20230112_000005_drop_old_nar_columns.rs
@@ -16,7 +16,7 @@ impl MigrationName for Migration {
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
- eprintln!("* Migrating NAR schema...");
+ println!("* Migrating NAR schema...");
if manager.get_database_backend() == DatabaseBackend::Sqlite {
// Just copy all data to a new table
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 0314e69..89644e1 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -217,7 +217,7 @@ async fn fallback(_: Uri) -> ServerResult<()> {
/// Runs the API server.
pub async fn run_api_server(cli_listen: Option<SocketAddr>, config: Config) -> Result<()> {
- eprintln!("Starting API server...");
+ println!("Starting API server...");
let state = StateInner::new(config).await;
@@ -239,7 +239,7 @@ pub async fn run_api_server(cli_listen: Option<SocketAddr>, config: Config) -> R
.layer(TraceLayer::new_for_http())
.layer(CatchPanicLayer::new());
- eprintln!("Listening on {:?}...", listen);
+ println!("Listening on {:?}...", listen);
let listener = TcpListener::bind(&listen).await?;
@@ -256,7 +256,7 @@ pub async fn run_api_server(cli_listen: Option<SocketAddr>, config: Config) -> R
/// Runs database migrations.
pub async fn run_migrations(config: Config) -> Result<()> {
- eprintln!("Running migrations...");
+ println!("Running migrations...");
let state = StateInner::new(config).await;
let db = state.database().await?;
diff --git a/server/src/main.rs b/server/src/main.rs
index c5f08df..3a37c23 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -121,14 +121,14 @@ fn init_logging(tokio_console: bool) {
.init();
if tokio_console {
- eprintln!("Note: tokio-console is enabled");
+ println!("Note: tokio-console is enabled");
}
}
fn dump_version() {
#[cfg(debug_assertions)]
- eprintln!("Attic Server {} (debug)", env!("CARGO_PKG_VERSION"));
+ println!("Attic Server {} (debug)", env!("CARGO_PKG_VERSION"));
#[cfg(not(debug_assertions))]
- eprintln!("Attic Server {} (release)", env!("CARGO_PKG_VERSION"));
+ println!("Attic Server {} (release)", env!("CARGO_PKG_VERSION"));
}
diff --git a/server/src/oobe.rs b/server/src/oobe.rs
index d3d912d..98ef88c 100644
--- a/server/src/oobe.rs
+++ b/server/src/oobe.rs
@@ -77,25 +77,25 @@ pub async fn run_oobe() -> Result<()> {
token.encode(&SignatureType::RS256(key), &None, &None)?
};
- eprintln!();
- eprintln!("-----------------");
- eprintln!("Welcome to Attic!");
- eprintln!();
- eprintln!("A simple setup using SQLite and local storage has been configured for you in:");
- eprintln!();
- eprintln!(" {}", config_path.to_str().unwrap());
- eprintln!();
- eprintln!("Run the following command to log into this server:");
- eprintln!();
- eprintln!(" attic login local http://localhost:8080 {root_token}");
- eprintln!();
- eprintln!("Documentations and guides:");
- eprintln!();
- eprintln!(" https://docs.attic.rs");
- eprintln!();
- eprintln!("Enjoy!");
- eprintln!("-----------------");
- eprintln!();
+ println!();
+ println!("-----------------");
+ println!("Welcome to Attic!");
+ println!();
+ println!("A simple setup using SQLite and local storage has been configured for you in:");
+ println!();
+ println!(" {}", config_path.to_str().unwrap());
+ println!();
+ println!("Run the following command to log into this server:");
+ println!();
+ println!(" attic login local http://localhost:8080 {root_token}");
+ println!();
+ println!("Documentations and guides:");
+ println!();
+ println!(" https://docs.attic.rs");
+ println!();
+ println!("Enjoy!");
+ println!("-----------------");
+ println!();
Ok(())
}

View File

@@ -3,7 +3,7 @@ final: prev:
android-tools = import ./android-tools final prev;
attic-client = import ./attic-client final prev;
darktable = import ./darktable final prev;
hyprland = import ./hyprland final prev;
go-swagger = import ./go-swagger final prev;
mpv = import ./mpv final prev;
spicetify-cli = import ./spicetify-cli final prev;
tea = import ./tea final prev;
@@ -20,9 +20,12 @@ final: prev:
flaresolverr = final.docker-image-flaresolverr;
gitea = final.docker-image-gitea;
gitea-act-runner = final.docker-image-gitea-act-runner;
grafana = final.docker-image-grafana;
gitea-act-runner-worker = final.docker-image-gitea-act-runner-worker;
grafana-image-renderer = final.docker-image-grafana-image-renderer;
jellyfin = final.docker-image-jellyfin;
grafana-to-ntfy = final.docker-image-grafana-to-ntfy;
grafana = final.docker-image-grafana;
immich = final.docker-image-immich;
immich-machine-learning = final.docker-image-immich-machine-learning;
jellyseerr = final.docker-image-jellyseerr;
littlelink-server = final.docker-image-littlelink-server;
mariadb = final.docker-image-mariadb;
@@ -33,7 +36,9 @@ final: prev:
ntfy = final.docker-image-ntfy;
oidcwarden = final.docker-image-oidcwarden;
outline = final.docker-image-outline;
plex = final.docker-image-plex;
postgresql = final.docker-image-postgresql;
postgresql-vectorchord = final.docker-image-postgresql-vectorchord;
prometheus = final.docker-image-prometheus;
prometheus-fail2ban-exporter = final.docker-image-prometheus-fail2ban-exporter;
prometheus-node-exporter = final.docker-image-prometheus-node-exporter;
@@ -51,19 +56,6 @@ final: prev:
transmission-protonvpn = final.docker-image-transmission-protonvpn;
whoami = final.docker-image-whoami;
};
jellyfinPlugins = prev.jellyfinPlugins or { } // {
bookshelf = final.jellyfin-plugin-bookshelf-bin;
intro-skipper = final.jellyfin-plugin-intro-skipper-bin;
opensubtitles = final.jellyfin-plugin-opensubtitles-bin;
playbackreporting = final.jellyfin-plugin-playbackreporting-bin;
reports = final.jellyfin-plugin-reports-bin;
sso = final.jellyfin-plugin-sso-bin;
subtitleextract = final.jellyfin-plugin-subtitleextract-bin;
tmdbboxsets = final.jellyfin-plugin-tmdbboxsets-bin;
tvdb = final.jellyfin-plugin-tvdb-bin;
};
obsidianPlugins = prev.obsidianPlugins or { } // {
better-word-count = final.obsidian-plugin-better-word-count;
dataview = final.obsidian-plugin-dataview;

View File

@@ -0,0 +1,13 @@
final: prev:
# FIXME: https://github.com/go-swagger/go-swagger/issues/3220
# FIXME: https://github.com/go-swagger/go-swagger/issues/3229
prev.go-swagger.overrideAttrs (oldAttrs: {
src = final.fetchFromGitHub {
owner = "go-swagger";
repo = "go-swagger";
rev = "717e3cb29becaaf00e56953556c6d80f8a01b286";
hash = "sha256-IuIVc7NwfXSBQ2tojD4LY7I18k5MJaVeDDPsi/OBFL0=";
};
vendorHash = "sha256-x3fTIXmI5NnOKph1D84MHzf1Kod+WLYn1JtnWLr4x+U=";
})

View File

@@ -1,4 +0,0 @@
final: prev:
prev.hyprland.overrideAttrs (oldAttrs: {
patches = oldAttrs.patches or [ ] ++ [ ./fix-maxwidth-resolution-mode.patch ];
})

View File

@@ -1,13 +0,0 @@
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
index 635c7977..80093c0d 100644
--- a/src/config/ConfigManager.cpp
+++ b/src/config/ConfigManager.cpp
@@ -2091,6 +2091,8 @@ bool CMonitorRuleParser::parseMode(const std::string& value) {
m_rule.resolution = Vector2D(-1, -1);
else if (value.starts_with("highres"))
m_rule.resolution = Vector2D(-1, -2);
+ else if (value.starts_with("maxwidth"))
+ m_rule.resolution = Vector2D(-1, -3);
else if (parseModeLine(value, m_rule.drmMode)) {
m_rule.resolution = Vector2D(m_rule.drmMode.hdisplay, m_rule.drmMode.vdisplay);
m_rule.refreshRate = float(m_rule.drmMode.vrefresh) / 1000;

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