Compare commits
342 Commits
ryzen-serv
...
wip/feeds
Author | SHA1 | Date | |
---|---|---|---|
cdc881e887 | |||
33967554a5 | |||
5af55ecdbf | |||
6ca3e7086e | |||
ca62f1b62f | |||
eef66df36d | |||
9ca6a1c907 | |||
dbb78088f4 | |||
f17ae1ca7b | |||
b2774a4004 | |||
0ae548d47c | |||
760505db20 | |||
71fc1a2fd7 | |||
a457fc1416 | |||
2c0b0f6947 | |||
f10de6c2c4 | |||
a6be200a82 | |||
fb57e9aa5b | |||
f5acbbd830 | |||
af77417531 | |||
eea80b575d | |||
6a209d27fd | |||
e8f778fecd | |||
488036beb3 | |||
00b681eca5 | |||
72d589cb2d | |||
ea5552daa7 | |||
fb7d94209c | |||
8f5b92685b | |||
32a4cb19fd | |||
031cfa2bcd | |||
e93fbea1e6 | |||
85a2fbc38a | |||
9e902c8eb2 | |||
dc15091ea7 | |||
c063ecd047 | |||
70a43c770d | |||
cc9e2d8e15 | |||
bb41fb95fe | |||
d852adf806 | |||
5443542cba | |||
81effb01a3 | |||
83f416999f | |||
dd34883246 | |||
e47f9e38ce | |||
0f0b728911 | |||
1839f87a4e | |||
53edf4e6af | |||
fb6e0ddb34 | |||
0a48d79174 | |||
b6208e1a19 | |||
e46ab4ec14 | |||
19c254c266 | |||
1d0cadce85 | |||
e8342b8044 | |||
40e642bfc3 | |||
f008565e22 | |||
4ea2835d9d | |||
493d317bb1 | |||
e446bfba58 | |||
a7bac5de18 | |||
b0950e90f4 | |||
d8cd0e1f57 | |||
fd7d67ee05 | |||
1a712b4d47 | |||
4520e1d1f5 | |||
841a2a3bcb | |||
fe816e9110 | |||
426e0c3ae2 | |||
a95b91a556 | |||
837e5438c3 | |||
8217b22c86 | |||
0b35ce4dec | |||
413f9a171b | |||
43a46af43b | |||
1a0f05bfd6 | |||
c18dd9636d | |||
0977721af5 | |||
122d3cd7e4 | |||
cd5f8054c0 | |||
3db388b105 | |||
2ba6116f10 | |||
592d17b725 | |||
4d9c15f9b8 | |||
abced7dd0d | |||
5c42365912 | |||
247ad326b2 | |||
170008f345 | |||
2c48e61854 | |||
f89f756489 | |||
c0da19951b | |||
5fb67306e4 | |||
5533b586d7 | |||
68c2eb7363 | |||
fd79026366 | |||
a76471cb1f | |||
c94b8299a6 | |||
175bc0709f | |||
7b02477486 | |||
d7c8638fea | |||
9d7d1acc80 | |||
787857d27f | |||
9c248a8a31 | |||
829680fb00 | |||
a9ee26388c | |||
2960b895b6 | |||
933063115b | |||
afe684ca2c | |||
93f1411522 | |||
01e44c1f7f | |||
618e9bd2fa | |||
fbc39d0584 | |||
2d7b3750cd | |||
e6ccd2e4f7 | |||
d4bf491e9c | |||
5a2bbcce3b | |||
327e6b536f | |||
bace7403e7 | |||
57f5521ef3 | |||
9e32211c12 | |||
edf6bd4455 | |||
a9a14786f9 | |||
eade5fe16e | |||
be222c1d70 | |||
88a33dd5de | |||
875e923197 | |||
54dd643cf0 | |||
3c726f148b | |||
e225e2e704 | |||
cf0bf8190e | |||
b8f7f68d4c | |||
7a3aae8c97 | |||
89e519810d | |||
0e920230ba | |||
6ffae00e17 | |||
be19985440 | |||
f7e3e7294a | |||
d745e3c1ee | |||
c1890ce82b | |||
53a0b621d8 | |||
aeb2f63d65 | |||
528ffdb58e | |||
b6887b305e | |||
08dfc80c98 | |||
5a273213f6 | |||
0a6d88dfc1 | |||
50dfd482cf | |||
9743aee79d | |||
0819899102 | |||
d3ff68217e | |||
1a96859994 | |||
af92a2250e | |||
d00f9b15d7 | |||
aa1c1f40cb | |||
530b2d6385 | |||
e6919dd16f | |||
760f2ac66d | |||
8e5ca11259 | |||
121936620a | |||
f5b49e014c | |||
4bdb34775d | |||
f5fbc206f5 | |||
a9096f3312 | |||
67cddecab4 | |||
9a002c99eb | |||
a0ac7fa98d | |||
b03043e513 | |||
0713e3bad1 | |||
d3a3f39756 | |||
a7d9e5cc54 | |||
13f3b322b0 | |||
5c25330891 | |||
dc6dc2e475 | |||
c4352fa9bb | |||
2c6629a658 | |||
c0496b25b5 | |||
9e0346c329 | |||
364a598324 | |||
c6850aff23 | |||
730ef272d1 | |||
16fa1e0eda | |||
51a96525d9 | |||
7b01822ee7 | |||
f9aa36a620 | |||
9b75d8705b | |||
217ecec250 | |||
6c7ca7630a | |||
1f99d44288 | |||
f1aa685a03 | |||
2b31fc8776 | |||
0c35e2b3c1 | |||
77b8d0ddc0 | |||
84f23c602e | |||
ea5fbc63cf | |||
69361ee9a2 | |||
1808d153b2 | |||
b3ad0f8f1f | |||
c745612cfd | |||
278cc98c6d | |||
fac661af15 | |||
65777c70ad | |||
09c524a5b1 | |||
0db7f0857a | |||
38befe502c | |||
55e09c2dbf | |||
bd699c887c | |||
2de6f7d364 | |||
d60e5264f3 | |||
c66699b697 | |||
97044bf70e | |||
3122334a41 | |||
0b2faef989 | |||
8acd6ca4f1 | |||
8169f7c6b2 | |||
cd1aa0b376 | |||
72b627100c | |||
567c08460a | |||
9b66aecf1b | |||
16cb3b83a2 | |||
970438be8a | |||
51da29555e | |||
8a745a9b8a | |||
3505f3b9f3 | |||
444595e847 | |||
3e1407c30b | |||
0a744117a4 | |||
a2935cedaa | |||
22e46d52c2 | |||
1e0c213adf | |||
3e1340ed61 | |||
341dd3f2b2 | |||
1c9caa40bd | |||
3be15c6d05 | |||
8e8168ec28 | |||
28397807fc | |||
42ebb9a155 | |||
a8a4b8e739 | |||
2550601179 | |||
199a49755a | |||
8c7700688f | |||
8fe304d6c1 | |||
700fef7df3 | |||
01db7e1f23 | |||
df6e8f1562 | |||
1f0a40c81f | |||
995b41d1e8 | |||
7674735d42 | |||
329693c9ce | |||
5ae3bb2f6c | |||
e0b1aef127 | |||
9b8363dfb4 | |||
58ad87df8e | |||
5fc894cda9 | |||
07e6ec2533 | |||
005a79e680 | |||
0f5279bbca | |||
e9b3b7ebab | |||
7a83c1d6df | |||
46788fe565 | |||
a473ef6db3 | |||
3627d47f12 | |||
115f8d7054 | |||
ac44b04d99 | |||
afff0aff19 | |||
f0086dc5bd | |||
acabd34f28 | |||
d0e6b82739 | |||
dc09b7b9b2 | |||
38c5b82a08 | |||
89def1a073 | |||
ad2ed370d9 | |||
3e8f7a9ba2 | |||
028ecfe93f | |||
c5ac792c13 | |||
bd1624bef9 | |||
3ae53d7f32 | |||
e7f2d41b1f | |||
3394a79e2b | |||
b01501663d | |||
cbd5ccd1c8 | |||
cf857eaf9f | |||
3a7eb294c7 | |||
2ccb470adc | |||
0a2a929507 | |||
2014d5ce77 | |||
041adb7092 | |||
a979521a98 | |||
77881be955 | |||
0450b4d9a6 | |||
edea64a41c | |||
90e479592f | |||
62d83d94f2 | |||
52bbe4e9f4 | |||
ab176b8d4b | |||
62df4492a3 | |||
f4ed194abc | |||
6420c9fd16 | |||
86245b460b | |||
bf1ba786b3 | |||
35a896a3e2 | |||
b4314bd919 | |||
4696209822 | |||
c3957d81c2 | |||
8a5be00c93 | |||
c2db9fe28e | |||
ccaac901f7 | |||
7f285a8254 | |||
b0b82a3d88 | |||
b0664d81ab | |||
8ba52bb9cd | |||
20f0a19e25 | |||
9dc17a3874 | |||
2992644901 | |||
d5d89a10b9 | |||
f7d9fdfe04 | |||
c42aa2847b | |||
768c5c910f | |||
8790a7d9fd | |||
7c36a0d522 | |||
977a80d59e | |||
63c92a44ed | |||
bf838ea203 | |||
e8a7a1dc75 | |||
992efc1093 | |||
d320fa39f3 | |||
e40156ed9a | |||
656837c810 | |||
0533ea1cc2 | |||
a1911f3001 | |||
24967c53a7 | |||
8b9c18aee1 | |||
8d3acb104a | |||
69eacf6c4d | |||
d7ad414a9c | |||
533b0a91bd | |||
56d87da650 | |||
3f33b2cb76 | |||
f8a1df790f | |||
e94186e9c9 | |||
82d11a7ae1 | |||
0253774622 | |||
2f45c57310 |
76
flake.lock
generated
76
flake.lock
generated
@@ -36,29 +36,14 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"impermanence": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1668668915,
|
|
||||||
"narHash": "sha256-QjY4ZZbs9shwO4LaLpvlU2bO9J1juYhO9NtV3nrbnYQ=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "impermanence",
|
|
||||||
"rev": "5df9108b346f8a42021bf99e50de89c9caa251c3",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "impermanence",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mobile-nixos": {
|
"mobile-nixos": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1668897543,
|
"lastModified": 1670131242,
|
||||||
"narHash": "sha256-1bjvy5zi/6KDzhN3ihOUEA6y5FFEOf5xvIbf65RWIh0=",
|
"narHash": "sha256-T/o1/3gffr010fsqgNshs1NJJjsnUYvQnUZgm6hilsY=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "mobile-nixos",
|
"repo": "mobile-nixos",
|
||||||
"rev": "25eec596116553112681d72ee4880107fc3957fa",
|
"rev": "5ee45cc1f8e43f4af14ee17ccef9156b0db8cd77",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -69,11 +54,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1668994630,
|
"lastModified": 1672953546,
|
||||||
"narHash": "sha256-1lqx6HLyw6fMNX/hXrrETG1vMvZRGm2XVC9O/Jt0T6c=",
|
"narHash": "sha256-oz757DnJ1ITvwyTovuwG3l9cX6j9j6/DH9eH+cXFJmc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "af50806f7c6ab40df3e6b239099e8f8385f6c78b",
|
"rev": "a518c77148585023ff56022f09c4b2c418a51ef5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -82,41 +67,40 @@
|
|||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-22_05": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1668908668,
|
|
||||||
"narHash": "sha256-oimCE4rY7Btuo/VYmA8khIyTHSMV7qUWTpz9w8yc9LQ=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "b68a6a27adb452879ab66c0eaac0c133e32823b2",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "release-22.05",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs-stable": {
|
"nixpkgs-stable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1668984258,
|
"lastModified": 1673163619,
|
||||||
"narHash": "sha256-0gDMJ2T3qf58xgcSbYoXiRGUkPWmKyr5C3vcathWhKs=",
|
"narHash": "sha256-B33PFBL64ZgTWgMnhFL3jgheAN/DjHPsZ1Ih3z0VE5I=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "cf63ade6f74bbc9d2a017290f1b2e33e8fbfa70a",
|
"rev": "8c54d842d9544361aac5f5b212ba04e4089e8efe",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"id": "nixpkgs",
|
"id": "nixpkgs",
|
||||||
"ref": "nixos-22.05",
|
"ref": "nixos-22.11",
|
||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixpkgs-stable_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1673100377,
|
||||||
|
"narHash": "sha256-mT76pTd0YFxT6CwtPhDgHJhuIgLY+ZLSMiQpBufwMG4=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "9f11a2df77cb945c115ae2a65f53f38121597d73",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "release-22.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
"impermanence": "impermanence",
|
|
||||||
"mobile-nixos": "mobile-nixos",
|
"mobile-nixos": "mobile-nixos",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"nixpkgs-stable": "nixpkgs-stable",
|
"nixpkgs-stable": "nixpkgs-stable",
|
||||||
@@ -129,14 +113,14 @@
|
|||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"nixpkgs-22_05": "nixpkgs-22_05"
|
"nixpkgs-stable": "nixpkgs-stable_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1668915833,
|
"lastModified": 1673147300,
|
||||||
"narHash": "sha256-7VYPiDJZdGct8Nl3kKhg580XZfoRcViO+zUGPkfBsqM=",
|
"narHash": "sha256-gR9OEfTzWfL6vG0qkbn1TlBAOlg4LuW8xK/u0V41Ihc=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "f72e050c3ef148b1131a0d2df55385c045e4166b",
|
"rev": "2253120d2a6147e57bafb5c689e086221df8032f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
24
flake.nix
24
flake.nix
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs-stable.url = "nixpkgs/nixos-22.05";
|
nixpkgs-stable.url = "nixpkgs/nixos-22.11";
|
||||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||||
mobile-nixos = {
|
mobile-nixos = {
|
||||||
url = "github:nixos/mobile-nixos";
|
url = "github:nixos/mobile-nixos";
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
url = "github:Mic92/sops-nix";
|
url = "github:Mic92/sops-nix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
impermanence.url = "github:nix-community/impermanence";
|
|
||||||
uninsane = {
|
uninsane = {
|
||||||
url = "git+https://git.uninsane.org/colin/uninsane";
|
url = "git+https://git.uninsane.org/colin/uninsane";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
@@ -32,13 +31,15 @@
|
|||||||
mobile-nixos,
|
mobile-nixos,
|
||||||
home-manager,
|
home-manager,
|
||||||
sops-nix,
|
sops-nix,
|
||||||
impermanence,
|
|
||||||
uninsane
|
uninsane
|
||||||
}: let
|
}: let
|
||||||
patchedPkgs = system: nixpkgs.legacyPackages.${system}.applyPatches {
|
patchedPkgs = system: nixpkgs.legacyPackages.${system}.applyPatches {
|
||||||
name = "nixpkgs-patched-uninsane";
|
name = "nixpkgs-patched-uninsane";
|
||||||
src = nixpkgs;
|
src = nixpkgs;
|
||||||
patches = import ./nixpatches/list.nix nixpkgs.legacyPackages.${system}.fetchpatch;
|
patches = import ./nixpatches/list.nix {
|
||||||
|
inherit (nixpkgs.legacyPackages.${system}) fetchpatch;
|
||||||
|
inherit (nixpkgs.lib) fakeHash;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
# return something which behaves like `pkgs`, for the provided system
|
# return something which behaves like `pkgs`, for the provided system
|
||||||
# `local` = architecture of builder. `target` = architecture of the system beying deployed to
|
# `local` = architecture of builder. `target` = architecture of the system beying deployed to
|
||||||
@@ -51,12 +52,10 @@
|
|||||||
in (nixosSystem {
|
in (nixosSystem {
|
||||||
# by default the local system is the same as the target, employing emulation when they differ
|
# by default the local system is the same as the target, employing emulation when they differ
|
||||||
system = target;
|
system = target;
|
||||||
specialArgs = { inherit mobile-nixos home-manager impermanence; };
|
|
||||||
modules = [
|
modules = [
|
||||||
./modules
|
./modules
|
||||||
(import ./hosts/instantiate.nix name)
|
(import ./hosts/instantiate.nix name)
|
||||||
home-manager.nixosModule
|
home-manager.nixosModule
|
||||||
impermanence.nixosModule
|
|
||||||
sops-nix.nixosModules.sops
|
sops-nix.nixosModules.sops
|
||||||
{
|
{
|
||||||
nixpkgs.overlays = [
|
nixpkgs.overlays = [
|
||||||
@@ -69,8 +68,11 @@
|
|||||||
# the config can explicitly pull such packages from `pkgs.cross` to do more efficient cross-compilation.
|
# the config can explicitly pull such packages from `pkgs.cross` to do more efficient cross-compilation.
|
||||||
cross = (nixpkgsFor local target) // (customPackagesFor local target);
|
cross = (nixpkgsFor local target) // (customPackagesFor local target);
|
||||||
stable = import nixpkgs-stable { system = target; };
|
stable = import nixpkgs-stable { system = target; };
|
||||||
|
|
||||||
# cross-compatible packages
|
# cross-compatible packages
|
||||||
# gocryptfs = cross.gocryptfs;
|
# gocryptfs = cross.gocryptfs;
|
||||||
|
|
||||||
|
# pinned packages:
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -116,6 +118,16 @@
|
|||||||
x86_64-linux = allPkgsFor "x86_64-linux";
|
x86_64-linux = allPkgsFor "x86_64-linux";
|
||||||
aarch64-linux = allPkgsFor "aarch64-linux";
|
aarch64-linux = allPkgsFor "aarch64-linux";
|
||||||
};
|
};
|
||||||
|
templates = {
|
||||||
|
python-data = {
|
||||||
|
# initialize with:
|
||||||
|
# - `nix flake init -t '/home/colin/dev/nixos/#python-data'`
|
||||||
|
# then enter with:
|
||||||
|
# - `nix develop`
|
||||||
|
path = ./templates/python-data;
|
||||||
|
description = "python environment for data processing";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
hosts/common/bluetooth.nix
Normal file
16
hosts/common/bluetooth.nix
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# persist external pairings by default
|
||||||
|
sane.persist.sys.plaintext = [ "/var/lib/bluetooth" ];
|
||||||
|
|
||||||
|
sane.fs."/var/lib/bluetooth".generated.acl.mode = "0700";
|
||||||
|
sane.fs."/var/lib/bluetooth/.secrets.stamp" = {
|
||||||
|
wantedBeforeBy = [ "bluetooth.service" ];
|
||||||
|
# XXX: install-bluetooth uses sed, but that's part of the default systemd unit path, it seems
|
||||||
|
generated.script.script = builtins.readFile ../../scripts/install-bluetooth + ''
|
||||||
|
touch "/var/lib/bluetooth/.secrets.stamp"
|
||||||
|
'';
|
||||||
|
generated.script.scriptArgs = [ "/run/secrets/bt" ];
|
||||||
|
};
|
||||||
|
}
|
@@ -1,8 +1,12 @@
|
|||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
./bluetooth.nix
|
||||||
|
./feeds.nix
|
||||||
./fs.nix
|
./fs.nix
|
||||||
./hardware
|
./hardware
|
||||||
|
./i2p.nix
|
||||||
|
./ids.nix
|
||||||
./machine-id.nix
|
./machine-id.nix
|
||||||
./net.nix
|
./net.nix
|
||||||
./secrets.nix
|
./secrets.nix
|
||||||
@@ -16,6 +20,15 @@
|
|||||||
sane.packages.enableConsolePkgs = true;
|
sane.packages.enableConsolePkgs = true;
|
||||||
sane.packages.enableSystemPkgs = true;
|
sane.packages.enableSystemPkgs = true;
|
||||||
|
|
||||||
|
sane.persist.sys.plaintext = [
|
||||||
|
"/var/log"
|
||||||
|
"/var/backup" # for e.g. postgres dumps
|
||||||
|
# TODO: move elsewhere
|
||||||
|
"/var/lib/alsa" # preserve output levels, default devices
|
||||||
|
"/var/lib/colord" # preserve color calibrations (?)
|
||||||
|
"/var/lib/machines" # maybe not needed, but would be painful to add a VM and forget.
|
||||||
|
];
|
||||||
|
|
||||||
nixpkgs.config.allowUnfree = true;
|
nixpkgs.config.allowUnfree = true;
|
||||||
|
|
||||||
# time.timeZone = "America/Los_Angeles";
|
# time.timeZone = "America/Los_Angeles";
|
||||||
@@ -56,19 +69,8 @@
|
|||||||
};
|
};
|
||||||
# enable zsh completions
|
# enable zsh completions
|
||||||
environment.pathsToLink = [ "/share/zsh" ];
|
environment.pathsToLink = [ "/share/zsh" ];
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
# required for pam_mount
|
|
||||||
gocryptfs
|
|
||||||
];
|
|
||||||
|
|
||||||
# link debug symbols into /run/current-system/sw/lib/debug
|
# link debug symbols into /run/current-system/sw/lib/debug
|
||||||
# hopefully picked up by gdb automatically?
|
# hopefully picked up by gdb automatically?
|
||||||
environment.enableDebugInfo = true;
|
environment.enableDebugInfo = true;
|
||||||
|
|
||||||
security.pam.mount.enable = true;
|
|
||||||
# security.pam.mount.debugLevel = 1;
|
|
||||||
# security.pam.enableSSHAgentAuth = true; # ??
|
|
||||||
# needed for `allow_other` in e.g. gocryptfs mounts
|
|
||||||
# or i guess going through mount.fuse sets suid so that's not necessary?
|
|
||||||
# programs.fuse.userAllowOther = true;
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
{ lib }:
|
{ ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
hourly = { freq = "hourly"; };
|
hourly = { freq = "hourly"; };
|
||||||
daily = { freq = "daily"; };
|
daily = { freq = "daily"; };
|
||||||
@@ -13,24 +12,15 @@ let
|
|||||||
tech = { cat = "tech"; };
|
tech = { cat = "tech"; };
|
||||||
uncat = { cat = "uncat"; };
|
uncat = { cat = "uncat"; };
|
||||||
|
|
||||||
text = { format = "text"; };
|
|
||||||
image = { format = "image"; };
|
|
||||||
podcast = { format = "podcast"; };
|
|
||||||
|
|
||||||
mkRss = format: url: { inherit url format; } // uncat // infrequent;
|
mkRss = format: url: { inherit url format; } // uncat // infrequent;
|
||||||
# format-specific helpers
|
# format-specific helpers
|
||||||
mkText = mkRss text;
|
mkText = mkRss "text";
|
||||||
mkImg = mkRss image;
|
mkImg = mkRss "image";
|
||||||
mkPod = mkRss podcast;
|
mkPod = mkRss "podcast";
|
||||||
|
|
||||||
# host-specific helpers
|
# host-specific helpers
|
||||||
mkSubstack = subdomain: mkText "https://${subdomain}.substack.com/feed";
|
mkSubstack = subdomain: { substack = subdomain; };
|
||||||
|
|
||||||
# merge the attrs `new` into each value of the attrs `addTo`
|
|
||||||
addAttrs = new: addTo: builtins.mapAttrs (k: v: v // new) addTo;
|
|
||||||
# for each value in `attrs`, add a value to the child attrs which holds its key within the parent attrs.
|
|
||||||
withInverseMapping = key: attrs: builtins.mapAttrs (k: v: v // { "${key}" = k; }) attrs;
|
|
||||||
in rec {
|
|
||||||
podcasts = [
|
podcasts = [
|
||||||
(mkPod "https://lexfridman.com/feed/podcast/" // rat // weekly)
|
(mkPod "https://lexfridman.com/feed/podcast/" // rat // weekly)
|
||||||
## Astral Codex Ten
|
## Astral Codex Ten
|
||||||
@@ -63,6 +53,10 @@ in rec {
|
|||||||
(mkPod "https://www.cbsnews.com/latest/rss/60-minutes" // pol // infrequent)
|
(mkPod "https://www.cbsnews.com/latest/rss/60-minutes" // pol // infrequent)
|
||||||
## The Verge - Decoder
|
## The Verge - Decoder
|
||||||
(mkPod "https://feeds.megaphone.fm/recodedecode" // tech // weekly)
|
(mkPod "https://feeds.megaphone.fm/recodedecode" // tech // weekly)
|
||||||
|
## Matrix (chat) Live
|
||||||
|
(mkPod "https://feed.podbean.com/matrixlive/feed.xml" // tech // weekly)
|
||||||
|
## Michael Malice - Your Welcome
|
||||||
|
(mkPod "https://www.podcastone.com/podcast?categoryID2=2232" // pol // weekly)
|
||||||
];
|
];
|
||||||
|
|
||||||
texts = [
|
texts = [
|
||||||
@@ -97,6 +91,7 @@ in rec {
|
|||||||
(mkText "https://idiomdrottning.org/feed.xml" // uncat // daily)
|
(mkText "https://idiomdrottning.org/feed.xml" // uncat // daily)
|
||||||
(mkText "https://anish.lakhwara.com/home.html" // tech // weekly)
|
(mkText "https://anish.lakhwara.com/home.html" // tech // weekly)
|
||||||
(mkText "https://www.jefftk.com/news.rss" // tech // daily)
|
(mkText "https://www.jefftk.com/news.rss" // tech // daily)
|
||||||
|
(mkText "https://pomeroyb.com/feed.xml" // tech // infrequent)
|
||||||
|
|
||||||
# (TECH; POL) COMMENTATORS
|
# (TECH; POL) COMMENTATORS
|
||||||
(mkSubstack "edwardsnowden" // pol // infrequent)
|
(mkSubstack "edwardsnowden" // pol // infrequent)
|
||||||
@@ -114,6 +109,7 @@ in rec {
|
|||||||
(mkText "https://blog.dshr.org/rss.xml" // pol // weekly)
|
(mkText "https://blog.dshr.org/rss.xml" // pol // weekly)
|
||||||
## Matt Levine
|
## Matt Levine
|
||||||
(mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly)
|
(mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly)
|
||||||
|
(mkText "https://stpeter.im/atom.xml" // pol // weekly)
|
||||||
|
|
||||||
# RATIONALITY/PHILOSOPHY/ETC
|
# RATIONALITY/PHILOSOPHY/ETC
|
||||||
(mkSubstack "samkriss" // humor // infrequent)
|
(mkSubstack "samkriss" // humor // infrequent)
|
||||||
@@ -133,53 +129,23 @@ in rec {
|
|||||||
## Sean Carroll
|
## Sean Carroll
|
||||||
(mkText "https://www.preposterousuniverse.com/rss" // rat // infrequent)
|
(mkText "https://www.preposterousuniverse.com/rss" // rat // infrequent)
|
||||||
|
|
||||||
|
## mostly dating topics. not advice, or humor, but looking through a social lens
|
||||||
|
(mkText "https://putanumonit.com/feed" // rat // infrequent)
|
||||||
|
|
||||||
# CODE
|
# CODE
|
||||||
(mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
|
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
|
||||||
];
|
];
|
||||||
|
|
||||||
images = [
|
images = [
|
||||||
(mkImg "https://www.smbc-comics.com/comic/rss" // humor // daily)
|
(mkImg "https://www.smbc-comics.com/comic/rss" // humor // daily)
|
||||||
(mkImg "https://xkcd.com/atom.xml" // humor // daily)
|
(mkImg "https://xkcd.com/atom.xml" // humor // daily)
|
||||||
(mkImg "http://dilbert.com/feed" // humor // daily)
|
(mkImg "https://pbfcomics.com/feed" // humor // infrequent)
|
||||||
|
# (mkImg "http://dilbert.com/feed" // humor // daily)
|
||||||
|
|
||||||
# ART
|
# ART
|
||||||
(mkImg "https://miniature-calendar.com/feed" // art // daily)
|
(mkImg "https://miniature-calendar.com/feed" // art // daily)
|
||||||
];
|
];
|
||||||
|
in
|
||||||
all = texts ++ images ++ podcasts;
|
{
|
||||||
|
sane.feeds = texts ++ images ++ podcasts;
|
||||||
# return only the feed items which match this category (e.g. "tech")
|
|
||||||
filterCat = cat: feeds: builtins.filter (item: item.cat == cat) feeds;
|
|
||||||
# return only the feed items which match this format (e.g. "podcast")
|
|
||||||
filterFormat = format: feeds: builtins.filter (item: item.format == format) feeds;
|
|
||||||
|
|
||||||
# transform a list of feeds into an attrs mapping cat => [ feed0 feed1 ... ]
|
|
||||||
partitionByCat = feeds: builtins.groupBy (f: f.cat) feeds;
|
|
||||||
|
|
||||||
# represents a single RSS feed.
|
|
||||||
opmlTerminal = feed: ''<outline xmlUrl="${feed.url}" type="rss"/>'';
|
|
||||||
# a list of RSS feeds.
|
|
||||||
opmlTerminals = feeds: lib.strings.concatStringsSep "\n" (builtins.map opmlTerminal feeds);
|
|
||||||
# one node which packages some flat grouping of terminals.
|
|
||||||
opmlGroup = title: feeds: ''
|
|
||||||
<outline text="${title}" title="${title}">
|
|
||||||
${opmlTerminals feeds}
|
|
||||||
</outline>
|
|
||||||
'';
|
|
||||||
# a list of groups (`groupMap` is an attrs mapping groupName => [ feed0 feed1 ... ]).
|
|
||||||
opmlGroups = groupMap: lib.strings.concatStringsSep "\n" (
|
|
||||||
builtins.attrValues (builtins.mapAttrs opmlGroup groupMap)
|
|
||||||
);
|
|
||||||
# top-level OPML file which could be consumed by something else.
|
|
||||||
opmlTopLevel = body: ''
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<opml version="2.0">
|
|
||||||
<body>
|
|
||||||
${body}
|
|
||||||
</body>
|
|
||||||
</opml>
|
|
||||||
'';
|
|
||||||
|
|
||||||
# **primary API**: generate a OPML file from the provided feeds
|
|
||||||
feedsToOpml = feeds: opmlTopLevel (opmlGroups (partitionByCat feeds));
|
|
||||||
}
|
}
|
4
hosts/common/i2p.nix
Normal file
4
hosts/common/i2p.nix
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
services.i2p.enable = true;
|
||||||
|
}
|
60
hosts/common/ids.nix
Normal file
60
hosts/common/ids.nix
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{ ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# legacy servo users, some are inconvenient to migrate
|
||||||
|
sane.ids.dhcpcd.gid = 991;
|
||||||
|
sane.ids.dhcpcd.uid = 992;
|
||||||
|
sane.ids.gitea.gid = 993;
|
||||||
|
sane.ids.git.uid = 994;
|
||||||
|
sane.ids.jellyfin.gid = 994;
|
||||||
|
sane.ids.pleroma.gid = 995;
|
||||||
|
sane.ids.jellyfin.uid = 996;
|
||||||
|
sane.ids.acme.gid = 996;
|
||||||
|
sane.ids.pleroma.uid = 997;
|
||||||
|
sane.ids.acme.uid = 998;
|
||||||
|
|
||||||
|
# greetd (used by sway)
|
||||||
|
sane.ids.greeter.uid = 999;
|
||||||
|
sane.ids.greeter.gid = 999;
|
||||||
|
|
||||||
|
# new servo users
|
||||||
|
sane.ids.freshrss.uid = 2401;
|
||||||
|
sane.ids.freshrss.gid = 2401;
|
||||||
|
sane.ids.mediawiki.uid = 2402;
|
||||||
|
|
||||||
|
sane.ids.colin.uid = 1000;
|
||||||
|
sane.ids.guest.uid = 1100;
|
||||||
|
|
||||||
|
# found on all hosts
|
||||||
|
sane.ids.sshd.uid = 2001; # 997
|
||||||
|
sane.ids.sshd.gid = 2001; # 997
|
||||||
|
sane.ids.polkituser.gid = 2002; # 998
|
||||||
|
sane.ids.systemd-coredump.gid = 2003; # 996
|
||||||
|
sane.ids.nscd.uid = 2004;
|
||||||
|
sane.ids.nscd.gid = 2004;
|
||||||
|
sane.ids.systemd-oom.uid = 2005;
|
||||||
|
sane.ids.systemd-oom.gid = 2005;
|
||||||
|
|
||||||
|
# found on graphical hosts
|
||||||
|
sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy
|
||||||
|
|
||||||
|
# found on desko host
|
||||||
|
# from services.usbmuxd
|
||||||
|
sane.ids.usbmux.uid = 2204;
|
||||||
|
sane.ids.usbmux.gid = 2204;
|
||||||
|
|
||||||
|
|
||||||
|
# originally found on moby host
|
||||||
|
# gnome core-shell
|
||||||
|
sane.ids.avahi.uid = 2304;
|
||||||
|
sane.ids.avahi.gid = 2304;
|
||||||
|
sane.ids.colord.uid = 2305;
|
||||||
|
sane.ids.colord.gid = 2305;
|
||||||
|
sane.ids.geoclue.uid = 2306;
|
||||||
|
sane.ids.geoclue.gid = 2306;
|
||||||
|
# gnome core-os-services
|
||||||
|
sane.ids.rtkit.uid = 2307;
|
||||||
|
sane.ids.rtkit.gid = 2307;
|
||||||
|
# phosh
|
||||||
|
sane.ids.feedbackd.gid = 2308;
|
||||||
|
}
|
@@ -1,11 +1,16 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
# we wan't an /etc/machine-id which is consistent across boot so that `journalctl` will actually show us
|
# /etc/machine-id is a globally unique identifier used for:
|
||||||
# logs from previous boots.
|
# - systemd-networkd: DHCP lease renewal (instead of keying by the MAC address)
|
||||||
# maybe there's a config option for this (since persistent machine-id is bad for reasons listed in impermanence.nix),
|
# - systemd-journald: to filter logs by host
|
||||||
# but for now generate it from ssh keys.
|
# - chromium (potentially to track re-installations)
|
||||||
|
# - gdbus; system services that might upgrade to AF_LOCAL if both services can confirm they're on the same machine
|
||||||
|
# because of e.g. the chromium use, we *don't want* to persist this.
|
||||||
|
# however, `journalctl` won't show logs from previous boots unless the machine-ids match.
|
||||||
|
# so for now, generate something unique from the host ssh key.
|
||||||
|
# TODO: move this into modules?
|
||||||
system.activationScripts.machine-id = {
|
system.activationScripts.machine-id = {
|
||||||
deps = [ "persist-ssh-host-keys" ];
|
deps = [ "etc" ];
|
||||||
text = "sha256sum /etc/ssh/host_keys/ssh_host_ed25519_key | cut -c 1-32 > /etc/machine-id";
|
text = "sha256sum /etc/ssh/host_keys/ssh_host_ed25519_key | cut -c 1-32 > /etc/machine-id";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -31,49 +31,13 @@
|
|||||||
General.RoamThreshold5G = "-52"; # default -76
|
General.RoamThreshold5G = "-52"; # default -76
|
||||||
};
|
};
|
||||||
|
|
||||||
# TODO: don't need to depend on binsh if we were to use a nix-style shebang
|
sane.fs."/var/lib/iwd/.secrets.psk.stamp" = {
|
||||||
system.activationScripts.linkIwdKeys = let
|
wantedBeforeBy = [ "iwd.service" ];
|
||||||
unwrapped = ../../scripts/install-iwd;
|
generated.acl.mode = "0600";
|
||||||
install-iwd = pkgs.writeShellApplication {
|
# XXX: install-iwd uses sed, but that's part of the default systemd unit path, it seems
|
||||||
name = "install-iwd";
|
generated.script.script = builtins.readFile ../../scripts/install-iwd + ''
|
||||||
runtimeInputs = with pkgs; [ coreutils gnused ];
|
touch "/var/lib/iwd/.secrets.psk.stamp"
|
||||||
text = ''${unwrapped} "$@"'';
|
'';
|
||||||
};
|
generated.script.scriptArgs = [ "/run/secrets/iwd" "/var/lib/iwd" ];
|
||||||
in (lib.stringAfter
|
|
||||||
[ "setupSecrets" "binsh" ]
|
|
||||||
''
|
|
||||||
mkdir -p /var/lib/iwd
|
|
||||||
${install-iwd}/bin/install-iwd /run/secrets/iwd /var/lib/iwd
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
# TODO: use a glob, or a list, or something?
|
|
||||||
sops.secrets."iwd/community-university.psk" = {
|
|
||||||
sopsFile = ../../secrets/universal/net/community-university.psk.bin;
|
|
||||||
format = "binary";
|
|
||||||
};
|
|
||||||
sops.secrets."iwd/friend-libertarian-dod.psk" = {
|
|
||||||
sopsFile = ../../secrets/universal/net/friend-libertarian-dod.psk.bin;
|
|
||||||
format = "binary";
|
|
||||||
};
|
|
||||||
sops.secrets."iwd/friend-rationalist-empathist.psk" = {
|
|
||||||
sopsFile = ../../secrets/universal/net/friend-rationalist-empathist.psk.bin;
|
|
||||||
format = "binary";
|
|
||||||
};
|
|
||||||
sops.secrets."iwd/home-bedroom.psk" = {
|
|
||||||
sopsFile = ../../secrets/universal/net/home-bedroom.psk.bin;
|
|
||||||
format = "binary";
|
|
||||||
};
|
|
||||||
sops.secrets."iwd/home-shared-24G.psk" = {
|
|
||||||
sopsFile = ../../secrets/universal/net/home-shared-24G.psk.bin;
|
|
||||||
format = "binary";
|
|
||||||
};
|
|
||||||
sops.secrets."iwd/home-shared.psk" = {
|
|
||||||
sopsFile = ../../secrets/universal/net/home-shared.psk.bin;
|
|
||||||
format = "binary";
|
|
||||||
};
|
|
||||||
sops.secrets."iwd/iphone" = {
|
|
||||||
sopsFile = ../../secrets/universal/net/iphone.psk.bin;
|
|
||||||
format = "binary";
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -33,10 +33,6 @@
|
|||||||
# You can avoid this by adding a string to the full path instead, i.e.
|
# You can avoid this by adding a string to the full path instead, i.e.
|
||||||
# sops.defaultSopsFile = "/root/.sops/secrets/example.yaml";
|
# sops.defaultSopsFile = "/root/.sops/secrets/example.yaml";
|
||||||
sops.defaultSopsFile = ../../secrets/universal.yaml;
|
sops.defaultSopsFile = ../../secrets/universal.yaml;
|
||||||
# This will automatically import SSH keys as age keys
|
|
||||||
sops.age.sshKeyPaths = [
|
|
||||||
"/etc/ssh/host_keys/ssh_host_ed25519_key"
|
|
||||||
];
|
|
||||||
sops.gnupg.sshKeyPaths = []; # disable RSA key import
|
sops.gnupg.sshKeyPaths = []; # disable RSA key import
|
||||||
# This is using an age key that is expected to already be in the filesystem
|
# This is using an age key that is expected to already be in the filesystem
|
||||||
# sops.age.keyFile = "/home/colin/.ssh/age.pub";
|
# sops.age.keyFile = "/home/colin/.ssh/age.pub";
|
||||||
@@ -48,6 +44,81 @@
|
|||||||
# owner = config.users.users.colin.name;
|
# owner = config.users.users.colin.name;
|
||||||
# };
|
# };
|
||||||
# sops.secrets."myservice/my_subdir/my_secret" = {};
|
# sops.secrets."myservice/my_subdir/my_secret" = {};
|
||||||
|
|
||||||
|
## universal secrets
|
||||||
|
# TODO: glob these?
|
||||||
|
|
||||||
|
sops.secrets."jackett_apikey" = {
|
||||||
|
sopsFile = ../../secrets/universal.yaml;
|
||||||
|
owner = config.users.users.colin.name;
|
||||||
|
};
|
||||||
|
sops.secrets."router_passwd" = {
|
||||||
|
sopsFile = ../../secrets/universal.yaml;
|
||||||
|
};
|
||||||
|
sops.secrets."wg_ovpnd_us_privkey" = {
|
||||||
|
sopsFile = ../../secrets/universal.yaml;
|
||||||
|
};
|
||||||
|
sops.secrets."wg_ovpnd_us-atl_privkey" = {
|
||||||
|
sopsFile = ../../secrets/universal.yaml;
|
||||||
|
};
|
||||||
|
sops.secrets."wg_ovpnd_us-mi_privkey" = {
|
||||||
|
sopsFile = ../../secrets/universal.yaml;
|
||||||
|
};
|
||||||
|
sops.secrets."wg_ovpnd_ukr_privkey" = {
|
||||||
|
sopsFile = ../../secrets/universal.yaml;
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets."snippets" = {
|
||||||
|
sopsFile = ../../secrets/universal/snippets.bin;
|
||||||
|
format = "binary";
|
||||||
|
owner = config.users.users.colin.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets."bt/car" = {
|
||||||
|
sopsFile = ../../secrets/universal/bt/car.bin;
|
||||||
|
format = "binary";
|
||||||
|
};
|
||||||
|
sops.secrets."bt/earbuds" = {
|
||||||
|
sopsFile = ../../secrets/universal/bt/earbuds.bin;
|
||||||
|
format = "binary";
|
||||||
|
};
|
||||||
|
sops.secrets."bt/portable-speaker" = {
|
||||||
|
sopsFile = ../../secrets/universal/bt/portable-speaker.bin;
|
||||||
|
format = "binary";
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets."iwd/community-university.psk" = {
|
||||||
|
sopsFile = ../../secrets/universal/net/community-university.psk.bin;
|
||||||
|
format = "binary";
|
||||||
|
};
|
||||||
|
sops.secrets."iwd/friend-libertarian-dod.psk" = {
|
||||||
|
sopsFile = ../../secrets/universal/net/friend-libertarian-dod.psk.bin;
|
||||||
|
format = "binary";
|
||||||
|
};
|
||||||
|
sops.secrets."iwd/friend-rationalist-empathist.psk" = {
|
||||||
|
sopsFile = ../../secrets/universal/net/friend-rationalist-empathist.psk.bin;
|
||||||
|
format = "binary";
|
||||||
|
};
|
||||||
|
sops.secrets."iwd/home-bedroom.psk" = {
|
||||||
|
sopsFile = ../../secrets/universal/net/home-bedroom.psk.bin;
|
||||||
|
format = "binary";
|
||||||
|
};
|
||||||
|
sops.secrets."iwd/home-shared-24G.psk" = {
|
||||||
|
sopsFile = ../../secrets/universal/net/home-shared-24G.psk.bin;
|
||||||
|
format = "binary";
|
||||||
|
};
|
||||||
|
sops.secrets."iwd/home-shared.psk" = {
|
||||||
|
sopsFile = ../../secrets/universal/net/home-shared.psk.bin;
|
||||||
|
format = "binary";
|
||||||
|
};
|
||||||
|
sops.secrets."iwd/iphone" = {
|
||||||
|
sopsFile = ../../secrets/universal/net/iphone.psk.bin;
|
||||||
|
format = "binary";
|
||||||
|
};
|
||||||
|
sops.secrets."iwd/parents" = {
|
||||||
|
sopsFile = ../../secrets/universal/net/parents.psk.bin;
|
||||||
|
format = "binary";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,21 +1,24 @@
|
|||||||
{ ... }:
|
{ config, lib, sane-data, sane-lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# we place the host keys (which we want to be persisted) into their own directory so that we can
|
sane.ssh.pubkeys =
|
||||||
# bind mount that whole directory instead of doing it per-file.
|
let
|
||||||
# otherwise, this is identical to nixos defaults
|
# path is a DNS-style path like [ "org" "uninsane" "root" ]
|
||||||
sane.impermanence.service-dirs = [ "/etc/ssh/host_keys" ];
|
keyNameForPath = path:
|
||||||
|
let
|
||||||
|
rev = lib.reverseList path;
|
||||||
|
name = builtins.head rev;
|
||||||
|
host = lib.concatStringsSep "." (builtins.tail rev);
|
||||||
|
in
|
||||||
|
"${name}@${host}";
|
||||||
|
|
||||||
# we can't naively `mount /etc/ssh/host_keys` directly,
|
# [{ path :: [String], value :: String }] for the keys we want to install
|
||||||
# as /etc/fstab may not be populated yet (since that file depends on e.g. activationScripts.users)
|
globalKeys = sane-lib.flattenAttrs sane-data.keys;
|
||||||
# we can't even depend on impermanence's `createPersistentStorageDirs` to create the source/target directories
|
localKeys = sane-lib.flattenAttrs sane-data.keys.org.uninsane.local;
|
||||||
# since that also depends on `users`.
|
in lib.mkMerge (builtins.map
|
||||||
system.activationScripts.persist-ssh-host-keys.text = ''
|
({ path, value }: {
|
||||||
mkdir -p /etc/ssh/host_keys
|
"${keyNameForPath path}" = value;
|
||||||
mount --bind /nix/persist/etc/ssh/host_keys /etc/ssh/host_keys
|
})
|
||||||
'';
|
(globalKeys ++ localKeys)
|
||||||
|
);
|
||||||
services.openssh.hostKeys = [
|
|
||||||
{ type = "rsa"; bits = 4096; path = "/etc/ssh/host_keys/ssh_host_rsa_key"; }
|
|
||||||
{ type = "ed25519"; path = "/etc/ssh/host_keys/ssh_host_ed25519_key"; }
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
{ config, pkgs, lib, ... }:
|
{ config, pkgs, lib, sane-lib, ... }:
|
||||||
|
|
||||||
# installer docs: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/installation-device.nix
|
# installer docs: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/installation-device.nix
|
||||||
with lib;
|
with lib;
|
||||||
let
|
let
|
||||||
cfg = config.sane.users;
|
cfg = config.sane.users;
|
||||||
# see nixpkgs/nixos/modules/services/networking/dhcpcd.nix
|
fs = sane-lib.fs;
|
||||||
hasDHCP = config.networking.dhcpcd.enable &&
|
|
||||||
(config.networking.useDHCP || any (i: i.useDHCP == true) (attrValues config.networking.interfaces));
|
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
@@ -27,7 +24,8 @@ in
|
|||||||
# sets group to "users" (?)
|
# sets group to "users" (?)
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
home = "/home/colin";
|
home = "/home/colin";
|
||||||
uid = config.sane.allocations.colin-uid;
|
createHome = true;
|
||||||
|
homeMode = "0700";
|
||||||
# i don't get exactly what this is, but nixos defaults to this non-deterministically
|
# i don't get exactly what this is, but nixos defaults to this non-deterministically
|
||||||
# in /var/lib/nixos/auto-subuid-map and i don't want that.
|
# in /var/lib/nixos/auto-subuid-map and i don't want that.
|
||||||
subUidRanges = [
|
subUidRanges = [
|
||||||
@@ -50,36 +48,65 @@ in
|
|||||||
passwordFile = lib.mkIf (config.sops.secrets ? "colin-passwd") config.sops.secrets.colin-passwd.path;
|
passwordFile = lib.mkIf (config.sops.secrets ? "colin-passwd") config.sops.secrets.colin-passwd.path;
|
||||||
|
|
||||||
shell = pkgs.zsh;
|
shell = pkgs.zsh;
|
||||||
openssh.authorizedKeys.keys = builtins.attrValues (import ../../modules/pubkeys.nix).users;
|
|
||||||
|
|
||||||
pamMount = {
|
|
||||||
# mount encrypted stuff at login
|
# mount encrypted stuff at login
|
||||||
# requires that login password == fs encryption password
|
# some other nix pam users:
|
||||||
# fstype = "fuse";
|
# - <https://github.com/g00pix/nixconf/blob/32c04f6fa843fed97639dd3f09e157668d3eea1f/profiles/sshfs.nix>
|
||||||
# path = "${pkgs.gocryptfs}/bin/gocryptfs#/nix/persist/home/colin/private";
|
# - <https://github.com/lourkeur/distro/blob/11173454c6bb50f7ccab28cc2c757dca21446d1d/nixos/profiles/users/louis-full.nix>
|
||||||
fstype = "fuse.gocryptfs";
|
# - <https://github.com/dnr/sample-nix-code/blob/03494480c1fae550c033aa54fd96aeb3827761c5/nixos/laptop.nix>
|
||||||
path = "/nix/persist/home/colin/private";
|
pamMount = let
|
||||||
mountpoint = "/home/colin/private";
|
priv = config.fileSystems."/home/colin/private";
|
||||||
options="nodev,nosuid,quiet,allow_other";
|
in {
|
||||||
|
fstype = priv.fsType;
|
||||||
|
path = priv.device;
|
||||||
|
mountpoint = priv.mountPoint;
|
||||||
|
options = builtins.concatStringsSep "," priv.options;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sane.impermanence.home-dirs = [
|
security.pam.mount.enable = true;
|
||||||
# cache is probably too big to fit on the tmpfs
|
|
||||||
# TODO: we could bind-mount it to something which gets cleared per boot, though.
|
# ensure ~ perms are known to sane.fs module.
|
||||||
".cache"
|
# TODO: this is generic enough to be lifted up into sane.fs itself.
|
||||||
|
sane.fs."/home/colin".dir.acl = {
|
||||||
|
user = "colin";
|
||||||
|
group = config.users.users.colin.group;
|
||||||
|
mode = config.users.users.colin.homeMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.persist.home.plaintext = [
|
||||||
|
"archive"
|
||||||
|
"dev"
|
||||||
|
# TODO: records should be private
|
||||||
|
"records"
|
||||||
|
"ref"
|
||||||
|
"tmp"
|
||||||
|
"use"
|
||||||
|
"Music"
|
||||||
|
"Pictures"
|
||||||
|
"Videos"
|
||||||
|
|
||||||
".cargo"
|
".cargo"
|
||||||
".rustup"
|
".rustup"
|
||||||
".local/share/keyrings"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
sane.impermanence.service-dirs = mkIf cfg.guest.enable [
|
# convenience
|
||||||
{ user = "guest"; group = "users"; directory = "/home/guest"; }
|
sane.fs."/home/colin/knowledge" = fs.wantedSymlinkTo "/home/colin/private/knowledge";
|
||||||
|
sane.fs."/home/colin/nixos" = fs.wantedSymlinkTo "/home/colin/dev/nixos";
|
||||||
|
sane.fs."/home/colin/Videos/servo" = fs.wantedSymlinkTo "/mnt/servo-media/Videos";
|
||||||
|
sane.fs."/home/colin/Videos/servo-incomplete" = fs.wantedSymlinkTo "/mnt/servo-media/incomplete";
|
||||||
|
sane.fs."/home/colin/Music/servo" = fs.wantedSymlinkTo "/mnt/servo-media/Music";
|
||||||
|
|
||||||
|
# used by password managers, e.g. unix `pass`
|
||||||
|
sane.fs."/home/colin/.password-store" = fs.wantedSymlinkTo "/home/colin/knowledge/secrets/accounts";
|
||||||
|
|
||||||
|
sane.persist.sys.plaintext = mkIf cfg.guest.enable [
|
||||||
|
# intentionally allow other users to write to the guest folder
|
||||||
|
{ directory = "/home/guest"; user = "guest"; group = "users"; mode = "0775"; }
|
||||||
];
|
];
|
||||||
users.users.guest = mkIf cfg.guest.enable {
|
users.users.guest = mkIf cfg.guest.enable {
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
home = "/home/guest";
|
home = "/home/guest";
|
||||||
uid = config.sane.allocations.guest-uid;
|
|
||||||
subUidRanges = [
|
subUidRanges = [
|
||||||
{ startUid=200000; count=1; }
|
{ startUid=200000; count=1; }
|
||||||
];
|
];
|
||||||
@@ -91,13 +118,6 @@ in
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
users.users.dhcpcd = mkIf hasDHCP {
|
|
||||||
uid = config.sane.allocations.dhcpcd-uid;
|
|
||||||
};
|
|
||||||
users.groups.dhcpcd = mkIf hasDHCP {
|
|
||||||
gid = config.sane.allocations.dhcpcd-gid;
|
|
||||||
};
|
|
||||||
|
|
||||||
security.sudo = {
|
security.sudo = {
|
||||||
enable = true;
|
enable = true;
|
||||||
wheelNeedsPassword = false;
|
wheelNeedsPassword = false;
|
||||||
@@ -108,31 +128,5 @@ in
|
|||||||
permitRootLogin = "no";
|
permitRootLogin = "no";
|
||||||
passwordAuthentication = false;
|
passwordAuthentication = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
# affix some UIDs which were historically auto-generated
|
|
||||||
users.users.sshd.uid = config.sane.allocations.sshd-uid;
|
|
||||||
users.groups.polkituser.gid = config.sane.allocations.polkituser-gid;
|
|
||||||
users.groups.sshd.gid = config.sane.allocations.sshd-gid;
|
|
||||||
users.groups.systemd-coredump.gid = config.sane.allocations.systemd-coredump-gid;
|
|
||||||
users.users.nscd.uid = config.sane.allocations.nscd-uid;
|
|
||||||
users.groups.nscd.gid = config.sane.allocations.nscd-gid;
|
|
||||||
users.users.systemd-oom.uid = config.sane.allocations.systemd-oom-uid;
|
|
||||||
users.groups.systemd-oom.gid = config.sane.allocations.systemd-oom-gid;
|
|
||||||
|
|
||||||
# guarantee determinism in uid/gid generation for users:
|
|
||||||
assertions = let
|
|
||||||
uidAssertions = builtins.attrValues (builtins.mapAttrs (name: user: {
|
|
||||||
assertion = user.uid != null;
|
|
||||||
message = "non-deterministic uid detected for: ${name}";
|
|
||||||
}) config.users.users);
|
|
||||||
gidAssertions = builtins.attrValues (builtins.mapAttrs (name: group: {
|
|
||||||
assertion = group.gid != null;
|
|
||||||
message = "non-deterministic gid detected for: ${name}";
|
|
||||||
}) config.users.groups);
|
|
||||||
autoSubAssertions = builtins.attrValues (builtins.mapAttrs (name: user: {
|
|
||||||
assertion = !user.autoSubUidGidRange;
|
|
||||||
message = "non-deterministic subUids/Guids detected for: ${name}";
|
|
||||||
}) config.users.users);
|
|
||||||
in uidAssertions ++ gidAssertions ++ autoSubAssertions;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,58 +1,66 @@
|
|||||||
{ config, ... }:
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
# to add a new OVPN VPN:
|
||||||
|
# - generate a privkey `wg genkey`
|
||||||
|
# - add this key to `sops secrets/universal.yaml`
|
||||||
|
# - upload pubkey to OVPN.com
|
||||||
|
# - generate config @ OVPN.com
|
||||||
|
# - copy the Address, PublicKey, Endpoint from OVPN's config
|
||||||
|
# N.B.: maximum interface name in Linux is 15 characters.
|
||||||
|
let
|
||||||
|
def-ovpn = name: { endpoint, publicKey, address }: {
|
||||||
|
networking.wg-quick.interfaces."ovpnd-${name}" = {
|
||||||
|
inherit address;
|
||||||
|
privateKeyFile = config.sops.secrets."wg_ovpnd_${name}_privkey".path;
|
||||||
|
dns = [
|
||||||
|
"46.227.67.134"
|
||||||
|
"192.165.9.158"
|
||||||
|
];
|
||||||
|
peers = [
|
||||||
{
|
{
|
||||||
networking.wg-quick.interfaces.ovpnd-us = {
|
allowedIPs = [
|
||||||
|
"0.0.0.0/0"
|
||||||
|
"::/0"
|
||||||
|
];
|
||||||
|
inherit endpoint publicKey;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
# to start: `systemctl start wg-quick-ovpnd-${name}`
|
||||||
|
autostart = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in lib.mkMerge [
|
||||||
|
(def-ovpn "us" {
|
||||||
|
endpoint = "vpn31.prd.losangeles.ovpn.com:9929";
|
||||||
|
publicKey = "VW6bEWMOlOneta1bf6YFE25N/oMGh1E1UFBCfyggd0k=";
|
||||||
address = [
|
address = [
|
||||||
"172.27.237.218/32"
|
"172.27.237.218/32"
|
||||||
"fd00:0000:1337:cafe:1111:1111:ab00:4c8f/128"
|
"fd00:0000:1337:cafe:1111:1111:ab00:4c8f/128"
|
||||||
];
|
];
|
||||||
dns = [
|
})
|
||||||
"46.227.67.134"
|
# NB: us-* share the same wg key and link-local addrs, but distinct public addresses
|
||||||
"192.165.9.158"
|
(def-ovpn "us-atl" {
|
||||||
|
endpoint = "vpn18.prd.atlanta.ovpn.com:9929";
|
||||||
|
publicKey = "Dpg/4v5s9u0YbrXukfrMpkA+XQqKIFpf8ZFgyw0IkE0=";
|
||||||
|
address = [
|
||||||
|
"172.21.182.178/32"
|
||||||
|
"fd00:0000:1337:cafe:1111:1111:cfcb:27e3/128"
|
||||||
];
|
];
|
||||||
peers = [
|
})
|
||||||
{
|
(def-ovpn "us-mi" {
|
||||||
allowedIPs = [
|
endpoint = "vpn34.prd.miami.ovpn.com:9929";
|
||||||
"0.0.0.0/0"
|
publicKey = "VtJz2irbu8mdkIQvzlsYhU+k9d55or9mx4A2a14t0V0=";
|
||||||
"::/0"
|
address = [
|
||||||
|
"172.21.182.178/32"
|
||||||
|
"fd00:0000:1337:cafe:1111:1111:cfcb:27e3/128"
|
||||||
];
|
];
|
||||||
endpoint = "vpn31.prd.losangeles.ovpn.com:9929";
|
})
|
||||||
publicKey = "VW6bEWMOlOneta1bf6YFE25N/oMGh1E1UFBCfyggd0k=";
|
(def-ovpn "ukr" {
|
||||||
}
|
endpoint = "vpn96.prd.kyiv.ovpn.com:9929";
|
||||||
];
|
publicKey = "CjZcXDxaaKpW8b5As1EcNbI6+42A6BjWahwXDCwfVFg=";
|
||||||
privateKeyFile = config.sops.secrets.wg_ovpnd_us_privkey.path;
|
|
||||||
# to start: `systemctl start wg-quick-ovpnd-us`
|
|
||||||
autostart = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.wg-quick.interfaces.ovpnd-ukr = {
|
|
||||||
address = [
|
address = [
|
||||||
"172.18.180.159/32"
|
"172.18.180.159/32"
|
||||||
"fd00:0000:1337:cafe:1111:1111:ec5c:add3/128"
|
"fd00:0000:1337:cafe:1111:1111:ec5c:add3/128"
|
||||||
];
|
];
|
||||||
dns = [
|
})
|
||||||
"46.227.67.134"
|
]
|
||||||
"192.165.9.158"
|
|
||||||
];
|
|
||||||
peers = [
|
|
||||||
{
|
|
||||||
allowedIPs = [
|
|
||||||
"0.0.0.0/0"
|
|
||||||
"::/0"
|
|
||||||
];
|
|
||||||
endpoint = "vpn96.prd.kyiv.ovpn.com:9929";
|
|
||||||
publicKey = "CjZcXDxaaKpW8b5As1EcNbI6+42A6BjWahwXDCwfVFg=";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
privateKeyFile = config.sops.secrets.wg_ovpnd_ukr_privkey.path;
|
|
||||||
# to start: `systemctl start wg-quick-ovpnd-ukr`
|
|
||||||
autostart = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
sops.secrets."wg_ovpnd_us_privkey" = {
|
|
||||||
sopsFile = ../../secrets/universal.yaml;
|
|
||||||
};
|
|
||||||
sops.secrets."wg_ovpnd_ukr_privkey" = {
|
|
||||||
sopsFile = ../../secrets/universal.yaml;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@@ -10,21 +10,22 @@
|
|||||||
sane.services.duplicity.enable = true;
|
sane.services.duplicity.enable = true;
|
||||||
sane.services.nixserve.enable = true;
|
sane.services.nixserve.enable = true;
|
||||||
sane.services.nixserve.sopsFile = ../../secrets/desko.yaml;
|
sane.services.nixserve.sopsFile = ../../secrets/desko.yaml;
|
||||||
sane.impermanence.enable = true;
|
sane.persist.enable = true;
|
||||||
|
|
||||||
boot.loader.efi.canTouchEfiVariables = false;
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||||
|
|
||||||
# needed to use libimobiledevice/ifuse, for iphone sync
|
# needed to use libimobiledevice/ifuse, for iphone sync
|
||||||
services.usbmuxd.enable = true;
|
services.usbmuxd.enable = true;
|
||||||
users.users.usbmux.uid = config.sane.allocations.usbmux-uid;
|
|
||||||
users.groups.usbmux.gid = config.sane.allocations.usbmux-gid;
|
|
||||||
|
|
||||||
sops.secrets.colin-passwd = {
|
sops.secrets.colin-passwd = {
|
||||||
sopsFile = ../../secrets/desko.yaml;
|
sopsFile = ../../secrets/desko.yaml;
|
||||||
neededForUsers = true;
|
neededForUsers = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# don't enable wifi by default: it messes with connectivity.
|
||||||
|
systemd.services.iwd.enable = false;
|
||||||
|
|
||||||
# default config: https://man.archlinux.org/man/snapper-configs.5
|
# default config: https://man.archlinux.org/man/snapper-configs.5
|
||||||
# defaults to something like:
|
# defaults to something like:
|
||||||
# - hourly snapshots
|
# - hourly snapshots
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play
|
remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play
|
||||||
dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server
|
dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server
|
||||||
};
|
};
|
||||||
sane.impermanence.home-dirs = [
|
sane.persist.home.plaintext = [
|
||||||
".steam"
|
".steam"
|
||||||
".local/share/Steam"
|
".local/share/Steam"
|
||||||
];
|
];
|
||||||
|
@@ -1,16 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# root is a tmpfs so that we have an ephemeral system ("impermanence" handles the state)
|
sane.persist.root-on-tmpfs = true;
|
||||||
fileSystems."/" = {
|
|
||||||
device = "none";
|
|
||||||
fsType = "tmpfs";
|
|
||||||
options = [
|
|
||||||
"mode=755"
|
|
||||||
"size=1G"
|
|
||||||
"defaults"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
# we need a /tmp for building large nix things.
|
# we need a /tmp for building large nix things.
|
||||||
# a cross-compiled kernel, particularly, will easily use 30+GB of tmp
|
# a cross-compiled kernel, particularly, will easily use 30+GB of tmp
|
||||||
fileSystems."/tmp" = {
|
fileSystems."/tmp" = {
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
# sane.users.guest.enable = true;
|
# sane.users.guest.enable = true;
|
||||||
sane.gui.sway.enable = true;
|
sane.gui.sway.enable = true;
|
||||||
sane.impermanence.enable = true;
|
sane.persist.enable = true;
|
||||||
sane.nixcache.enable = true;
|
sane.nixcache.enable = true;
|
||||||
boot.loader.efi.canTouchEfiVariables = false;
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||||
|
@@ -1,16 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# root is a tmpfs so that we have an ephemeral system ("impermanence" handles the state)
|
sane.persist.root-on-tmpfs = true;
|
||||||
fileSystems."/" = {
|
|
||||||
device = "none";
|
|
||||||
fsType = "tmpfs";
|
|
||||||
options = [
|
|
||||||
"mode=755"
|
|
||||||
"size=1G"
|
|
||||||
"defaults"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
# we need a /tmp of default size (half RAM) for building large nix things
|
# we need a /tmp of default size (half RAM) for building large nix things
|
||||||
fileSystems."/tmp" = {
|
fileSystems."/tmp" = {
|
||||||
device = "none";
|
device = "none";
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{ config, pkgs, lib, mobile-nixos, ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./firmware.nix
|
./firmware.nix
|
||||||
@@ -24,8 +24,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# usability compromises
|
# usability compromises
|
||||||
sane.impermanence.home-dirs = [
|
sane.web-browser.persistCache = "private";
|
||||||
config.sane.web-browser.dotDir
|
sane.web-browser.persistData = "private";
|
||||||
|
sane.persist.home.plaintext = [
|
||||||
|
".config/pulse" # persist pulseaudio volume
|
||||||
];
|
];
|
||||||
|
|
||||||
# sane.packages.enableGuiPkgs = false; # XXX faster builds/imaging for debugging
|
# sane.packages.enableGuiPkgs = false; # XXX faster builds/imaging for debugging
|
||||||
@@ -34,12 +36,14 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
sane.nixcache.enable = true;
|
sane.nixcache.enable = true;
|
||||||
sane.impermanence.enable = true;
|
sane.persist.enable = true;
|
||||||
sane.gui.phosh.enable = true;
|
sane.gui.phosh.enable = true;
|
||||||
|
|
||||||
boot.loader.efi.canTouchEfiVariables = false;
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
# /boot space is at a premium. default was 20.
|
# /boot space is at a premium. default was 20.
|
||||||
boot.loader.generic-extlinux-compatible.configurationLimit = 10;
|
# even 10 can be too much
|
||||||
|
# TODO: compress moby kernels!
|
||||||
|
boot.loader.generic-extlinux-compatible.configurationLimit = 8;
|
||||||
# mobile.bootloader.enable = false;
|
# mobile.bootloader.enable = false;
|
||||||
# mobile.boot.stage-1.enable = false;
|
# mobile.boot.stage-1.enable = false;
|
||||||
# boot.initrd.systemd.enable = false;
|
# boot.initrd.systemd.enable = false;
|
||||||
|
@@ -1,17 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# root is a tmpfs so that we have an ephemeral system ("impermanence" handles the state)
|
sane.persist.root-on-tmpfs = true;
|
||||||
fileSystems."/" = {
|
|
||||||
device = "none";
|
|
||||||
fsType = "tmpfs";
|
|
||||||
options = [
|
|
||||||
"mode=755"
|
|
||||||
"size=1G"
|
|
||||||
"defaults"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
fileSystems."/nix" = {
|
fileSystems."/nix" = {
|
||||||
device = "/dev/disk/by-uuid/1f1271f8-53ce-4081-8a29-60a4a6b5d6f9";
|
device = "/dev/disk/by-uuid/1f1271f8-53ce-4081-8a29-60a4a6b5d6f9";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
|
@@ -8,9 +8,6 @@
|
|||||||
boot.loader.efi.canTouchEfiVariables = false;
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||||
|
|
||||||
users.users.dhcpcd.uid = config.sane.allocations.dhcpcd-uid;
|
|
||||||
users.groups.dhcpcd.gid = config.sane.allocations.dhcpcd-gid;
|
|
||||||
|
|
||||||
# docs: https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion
|
# docs: https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion
|
||||||
system.stateVersion = "21.05";
|
system.stateVersion = "21.05";
|
||||||
}
|
}
|
||||||
|
@@ -13,10 +13,9 @@
|
|||||||
pkgs.matrix-synapse
|
pkgs.matrix-synapse
|
||||||
pkgs.freshrss
|
pkgs.freshrss
|
||||||
];
|
];
|
||||||
sane.impermanence.enable = true;
|
sane.persist.enable = true;
|
||||||
|
sane.services.dyn-dns.enable = true;
|
||||||
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade
|
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade
|
||||||
sane.services.nixserve.enable = true;
|
|
||||||
sane.services.nixserve.sopsFile = ../../secrets/servo.yaml;
|
|
||||||
|
|
||||||
boot.loader.efi.canTouchEfiVariables = false;
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||||
|
@@ -1,16 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# root is a tmpfs so that we have an ephemeral system ("impermanence" handles the state)
|
sane.persist.root-on-tmpfs = true;
|
||||||
fileSystems."/" = {
|
|
||||||
device = "none";
|
|
||||||
fsType = "tmpfs";
|
|
||||||
options = [
|
|
||||||
"mode=755"
|
|
||||||
"size=1G"
|
|
||||||
"defaults"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
# we need a /tmp for building large nix things
|
# we need a /tmp for building large nix things
|
||||||
fileSystems."/tmp" = {
|
fileSystems."/tmp" = {
|
||||||
device = "none";
|
device = "none";
|
||||||
@@ -36,7 +27,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# slow, external storage (for archiving, etc)
|
# slow, external storage (for archiving, etc)
|
||||||
fileSystems."/nix/persist/ext" = {
|
fileSystems."/mnt/persist/ext" = {
|
||||||
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
options = [
|
options = [
|
||||||
@@ -45,27 +36,31 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.stores."ext" = {
|
||||||
|
origin = "/mnt/persist/ext/persist";
|
||||||
|
storeDescription = "external HDD storage";
|
||||||
|
};
|
||||||
|
sane.fs."/mnt/persist/ext".mount = {};
|
||||||
|
|
||||||
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: this is overly broad; only need media and share directories to be persisted
|
# TODO: this is overly broad; only need media and share directories to be persisted
|
||||||
{ user = "colin"; group = "users"; directory = "/var/lib/uninsane"; }
|
{ user = "colin"; group = "users"; directory = "/var/lib/uninsane"; }
|
||||||
];
|
];
|
||||||
# direct these media directories to external storage
|
# make sure large media is stored to the HDD
|
||||||
environment.persistence."/nix/persist/ext/persist" = {
|
sane.persist.sys.ext = [
|
||||||
directories = [
|
{
|
||||||
({
|
|
||||||
user = "colin";
|
user = "colin";
|
||||||
group = "users";
|
group = "users";
|
||||||
mode = "0777";
|
mode = "0777";
|
||||||
directory = "/var/lib/uninsane/media/Videos";
|
directory = "/var/lib/uninsane/media/Videos";
|
||||||
})
|
}
|
||||||
({
|
{
|
||||||
user = "colin";
|
user = "colin";
|
||||||
group = "users";
|
group = "users";
|
||||||
mode = "0777";
|
mode = "0777";
|
||||||
directory = "/var/lib/uninsane/media/freeleech";
|
directory = "/var/lib/uninsane/media/freeleech";
|
||||||
})
|
}
|
||||||
];
|
];
|
||||||
};
|
|
||||||
|
|
||||||
# in-memory compressed RAM (seems to be dynamically sized)
|
# in-memory compressed RAM (seems to be dynamically sized)
|
||||||
# zramSwap = {
|
# zramSwap = {
|
||||||
|
@@ -13,83 +13,155 @@
|
|||||||
|
|
||||||
# networking.firewall.enable = false;
|
# networking.firewall.enable = false;
|
||||||
networking.firewall.enable = true;
|
networking.firewall.enable = true;
|
||||||
# TODO: split these into the submodules
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
# this is needed to forward packets from the VPN to the host
|
||||||
25 # SMTP
|
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
|
||||||
80 # HTTP
|
|
||||||
143 # IMAP
|
# unless we add interface-specific settings for each VPN, we have to define nameservers globally.
|
||||||
443 # HTTPS
|
# networking.nameservers = [
|
||||||
465 # SMTPS
|
# "1.1.1.1"
|
||||||
587 # SMTPS/submission
|
# "9.9.9.9"
|
||||||
993 # IMAPS
|
# ];
|
||||||
4001 # IPFS
|
|
||||||
];
|
# use systemd's stub resolver.
|
||||||
networking.firewall.allowedUDPPorts = [
|
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
|
||||||
1900 7359 # DLNA: https://jellyfin.org/docs/general/networking/index.html
|
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets
|
||||||
4001 # IPFS
|
# in the ovnps namespace to use the provider's DNS resolvers.
|
||||||
|
# a weakness is we can only query 1 NS at a time (unless we were to clone the packets?)
|
||||||
|
# there also seems to be some cache somewhere that's shared between the two namespaces.
|
||||||
|
# i think this is a libc thing. might need to leverage proper cgroups to _really_ kill it.
|
||||||
|
# - getent ahostsv4 www.google.com
|
||||||
|
# - try fix: <https://serverfault.com/questions/765989/connect-to-3rd-party-vpn-server-but-dont-use-it-as-the-default-route/766290#766290>
|
||||||
|
services.resolved.enable = true;
|
||||||
|
networking.nameservers = [
|
||||||
|
# use systemd-resolved resolver
|
||||||
|
# full resolver (which understands /etc/hosts) lives on 127.0.0.53
|
||||||
|
# stub resolver (just forwards upstream) lives on 127.0.0.54
|
||||||
|
"127.0.0.53"
|
||||||
];
|
];
|
||||||
|
|
||||||
# we need to use externally-visible nameservers in order for VPNs to be able to resolve hosts.
|
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
|
||||||
networking.nameservers = [
|
# in a way that's unaware of my VPN routing, so routes are frequently poor against
|
||||||
"1.1.1.1"
|
# services which advertise different IPs based on geolocation.
|
||||||
"9.9.9.9"
|
# nscd claims to be usable without a cache, but in practice i can't get it to not cache!
|
||||||
];
|
# nsncd is the Name Service NON-Caching Daemon. it's a drop-in that doesn't cache;
|
||||||
|
# this is OK on the host -- because systemd-resolved caches. it's probably sub-optimal
|
||||||
|
# in the netns and we query upstream DNS more often than needed. hm.
|
||||||
|
# TODO: run a separate recursive resolver in each namespace.
|
||||||
|
services.nscd.enableNsncd = true;
|
||||||
|
|
||||||
|
# services.resolved.extraConfig = ''
|
||||||
|
# # docs: `man resolved.conf`
|
||||||
|
# # DNS servers to use via the `wg0` interface.
|
||||||
|
# # i hope that from the root ns, these aren't visible.
|
||||||
|
# DNS=46.227.67.134%wg0 192.165.9.158%wg0
|
||||||
|
# FallbackDNS=1.1.1.1 9.9.9.9
|
||||||
|
# '';
|
||||||
|
|
||||||
# OVPN CONFIG (https://www.ovpn.com):
|
# OVPN CONFIG (https://www.ovpn.com):
|
||||||
# DOCS: https://nixos.wiki/wiki/WireGuard
|
# DOCS: https://nixos.wiki/wiki/WireGuard
|
||||||
|
# if you `systemctl restart wireguard-wg0`, make sure to also restart any other services in `NetworkNamespacePath = .../ovpns`.
|
||||||
|
# TODO: why not create the namespace as a seperate operation (nix config for that?)
|
||||||
networking.wireguard.enable = true;
|
networking.wireguard.enable = true;
|
||||||
networking.wireguard.interfaces.wg0 = {
|
networking.wireguard.interfaces.wg0 = let
|
||||||
|
ip = "${pkgs.iproute2}/bin/ip";
|
||||||
|
in-ns = "${ip} netns exec ovpns";
|
||||||
|
iptables = "${pkgs.iptables}/bin/iptables";
|
||||||
|
veth-host-ip = "10.0.1.5";
|
||||||
|
veth-local-ip = "10.0.1.6";
|
||||||
|
vpn-ip = "185.157.162.178";
|
||||||
|
# DNS = 46.227.67.134, 192.165.9.158, 2a07:a880:4601:10f0:cd45::1, 2001:67c:750:1:cafe:cd45::1
|
||||||
|
vpn-dns = "46.227.67.134";
|
||||||
|
in {
|
||||||
privateKeyFile = config.sops.secrets.wg_ovpns_privkey.path;
|
privateKeyFile = config.sops.secrets.wg_ovpns_privkey.path;
|
||||||
# wg is active only in this namespace.
|
# wg is active only in this namespace.
|
||||||
# run e.g. ip netns exec ovpns <some command like ping/curl/etc, it'll go through wg>
|
# run e.g. ip netns exec ovpns <some command like ping/curl/etc, it'll go through wg>
|
||||||
# sudo ip netns exec ovpns ping www.google.com
|
# sudo ip netns exec ovpns ping www.google.com
|
||||||
# note: without the namespace, you'll need to add a specific route through eth0 for the peer (185.157.162.178/32)
|
|
||||||
interfaceNamespace = "ovpns";
|
interfaceNamespace = "ovpns";
|
||||||
preSetup = "${pkgs.iproute2}/bin/ip netns add ovpns || true";
|
|
||||||
postShutdown = "${pkgs.iproute2}/bin/ip netns delete ovpns";
|
|
||||||
ips = [
|
ips = [
|
||||||
"185.157.162.178/32"
|
"185.157.162.178/32"
|
||||||
];
|
];
|
||||||
peers = [
|
peers = [
|
||||||
{
|
{
|
||||||
publicKey = "SkkEZDCBde22KTs/Hc7FWvDBfdOCQA4YtBEuC3n5KGs=";
|
publicKey = "SkkEZDCBde22KTs/Hc7FWvDBfdOCQA4YtBEuC3n5KGs=";
|
||||||
endpoint = "vpn36.prd.amsterdam.ovpn.com:9930";
|
endpoint = "185.157.162.10:9930";
|
||||||
|
# alternatively: use hostname, but that presents bootstrapping issues (e.g. if host net flakes)
|
||||||
|
# endpoint = "vpn36.prd.amsterdam.ovpn.com:9930";
|
||||||
allowedIPs = [ "0.0.0.0/0" ];
|
allowedIPs = [ "0.0.0.0/0" ];
|
||||||
# nixOS says this is important for keeping NATs active
|
# nixOS says this is important for keeping NATs active
|
||||||
persistentKeepalive = 25;
|
persistentKeepalive = 25;
|
||||||
|
# re-executes wg this often. docs hint that this might help wg notice DNS/hostname changes.
|
||||||
|
# so, maybe that helps if we specify endpoint as a domain name
|
||||||
|
# dynamicEndpointRefreshSeconds = 30;
|
||||||
|
# when refresh fails, try it again after this period instead.
|
||||||
|
# TODO: not avail until nixpkgs upgrade
|
||||||
|
# dynamicEndpointRefreshRestartSeconds = 5;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
preSetup = "" + ''
|
||||||
|
${ip} netns add ovpns || echo "ovpns already exists"
|
||||||
systemd.services.wg0veth = {
|
'';
|
||||||
description = "veth pair to allow communication between host and wg0 netns";
|
postShutdown = "" + ''
|
||||||
after = [ "wireguard-wg0.service" ];
|
${in-ns} ip link del ovpns-veth-b || echo "couldn't delete ovpns-veth-b"
|
||||||
wantedBy = [ "multi-user.target" ];
|
${ip} link del ovpns-veth-a || echo "couldn't delete ovpns-veth-a"
|
||||||
serviceConfig = {
|
${ip} netns delete ovpns || echo "couldn't delete ovpns"
|
||||||
Type = "oneshot";
|
# restore rules/routes
|
||||||
RemainAfterExit = true;
|
${ip} rule del from ${veth-host-ip} lookup ovpns pref 50 || echo "couldn't delete init -> ovpns rule"
|
||||||
|
${ip} route del default via ${veth-local-ip} dev ovpns-veth-a proto kernel src ${veth-host-ip} metric 1002 table ovpns || echo "couldn't delete init -> ovpns route"
|
||||||
ExecStart = with pkgs; writeScript "wg0veth-start" ''
|
${ip} rule add from all lookup local pref 0
|
||||||
#!${bash}/bin/bash
|
${ip} rule del from all lookup local pref 100
|
||||||
|
'';
|
||||||
|
postSetup = "" + ''
|
||||||
|
# DOCS:
|
||||||
|
# - some of this approach is described here: <https://josephmuia.ca/2018-05-16-net-namespaces-veth-nat/>
|
||||||
|
# - iptables primer: <https://danielmiessler.com/study/iptables/>
|
||||||
# create veth pair
|
# create veth pair
|
||||||
${iproute2}/bin/ip link add ovpns-veth-a type veth peer name ovpns-veth-b
|
${ip} link add ovpns-veth-a type veth peer name ovpns-veth-b
|
||||||
${iproute2}/bin/ip addr add 10.0.1.5/24 dev ovpns-veth-a
|
${ip} addr add ${veth-host-ip}/24 dev ovpns-veth-a
|
||||||
${iproute2}/bin/ip link set ovpns-veth-a up
|
${ip} link set ovpns-veth-a up
|
||||||
# mv veth-b into the ovpns namespace
|
|
||||||
${iproute2}/bin/ip link set ovpns-veth-b netns ovpns
|
|
||||||
${iproute2}/bin/ip -n ovpns addr add 10.0.1.6/24 dev ovpns-veth-b
|
|
||||||
${iproute2}/bin/ip -n ovpns link set ovpns-veth-b up
|
|
||||||
# forward HTTP traffic, which we need for letsencrypt to work
|
|
||||||
${iproute2}/bin/ip netns exec ovpns ${socat}/bin/socat TCP4-LISTEN:80,reuseaddr,fork,su=nobody TCP4:10.0.1.5:80 &
|
|
||||||
'';
|
|
||||||
|
|
||||||
ExecStop = with pkgs; writeScript "wg0veth-stop" ''
|
# mv veth-b into the ovpns namespace
|
||||||
#!${bash}/bin/bash
|
${ip} link set ovpns-veth-b netns ovpns
|
||||||
${iproute2}/bin/ip -n wg0 link del ovpns-veth-b
|
${in-ns} ip addr add ${veth-local-ip}/24 dev ovpns-veth-b
|
||||||
${iproute2}/bin/ip link del ovpns-veth-a
|
${in-ns} ip link set ovpns-veth-b up
|
||||||
|
|
||||||
|
# make it so traffic originating from the host side of the veth
|
||||||
|
# is sent over the veth no matter its destination.
|
||||||
|
${ip} rule add from ${veth-host-ip} lookup ovpns pref 50
|
||||||
|
# for traffic originating at the host veth to the WAN, use the veth as our gateway
|
||||||
|
# not sure if the metric 1002 matters.
|
||||||
|
${ip} route add default via ${veth-local-ip} dev ovpns-veth-a proto kernel src ${veth-host-ip} metric 1002 table ovpns
|
||||||
|
# give the default route lower priority
|
||||||
|
${ip} rule add from all lookup local pref 100
|
||||||
|
${ip} rule del from all lookup local pref 0
|
||||||
|
|
||||||
|
# bridge HTTP traffic:
|
||||||
|
# any external port-80 request sent to the VPN addr will be forwarded to the rootns.
|
||||||
|
# this exists so LetsEncrypt can procure a cert for the MX over http.
|
||||||
|
# TODO: we could use _acme_challence.mx.uninsane.org CNAME to avoid this forwarding
|
||||||
|
# - <https://community.letsencrypt.org/t/where-does-letsencrypt-resolve-dns-from/37607/8>
|
||||||
|
${in-ns} ${iptables} -A PREROUTING -t nat -p tcp --dport 80 -m iprange --dst-range ${vpn-ip} \
|
||||||
|
-j DNAT --to-destination ${veth-host-ip}:80
|
||||||
|
|
||||||
|
# we also bridge DNS traffic
|
||||||
|
${in-ns} ${iptables} -A PREROUTING -t nat -p udp --dport 53 -m iprange --dst-range ${vpn-ip} \
|
||||||
|
-j DNAT --to-destination ${veth-host-ip}:53
|
||||||
|
${in-ns} ${iptables} -A PREROUTING -t nat -p tcp --dport 53 -m iprange --dst-range ${vpn-ip} \
|
||||||
|
-j DNAT --to-destination ${veth-host-ip}:53
|
||||||
|
|
||||||
|
# in order to access DNS in this netns, we need to route it to the VPN's nameservers
|
||||||
|
# - alternatively, we could fix DNS servers like 1.1.1.1.
|
||||||
|
${in-ns} ${iptables} -A OUTPUT -t nat -p udp --dport 53 -m iprange --dst-range 127.0.0.53 \
|
||||||
|
-j DNAT --to-destination ${vpn-dns}:53
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
# create a new routing table that we can use to proxy traffic out of the root namespace
|
||||||
|
# through the ovpns namespace, and to the WAN via VPN.
|
||||||
|
networking.iproute2.rttablesExtraConfig = ''
|
||||||
|
5 ovpns
|
||||||
|
'';
|
||||||
|
networking.iproute2.enable = true;
|
||||||
|
|
||||||
sops.secrets."wg_ovpns_privkey" = {
|
sops.secrets."wg_ovpns_privkey" = {
|
||||||
sopsFile = ../../secrets/servo.yaml;
|
sopsFile = ../../secrets/servo.yaml;
|
||||||
|
31
hosts/servo/services/ddns-afraid.nix
Normal file
31
hosts/servo/services/ddns-afraid.nix
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
# using manual ddns now
|
||||||
|
lib.mkIf false
|
||||||
|
{
|
||||||
|
systemd.services.ddns-afraid = {
|
||||||
|
description = "update dynamic DNS entries for freedns.afraid.org";
|
||||||
|
serviceConfig = {
|
||||||
|
EnvironmentFile = config.sops.secrets.ddns_afraid.path;
|
||||||
|
# TODO: ProtectSystem = "strict";
|
||||||
|
# TODO: ProtectHome = "full";
|
||||||
|
# TODO: PrivateTmp = true;
|
||||||
|
};
|
||||||
|
script = let
|
||||||
|
curl = "${pkgs.curl}/bin/curl -4";
|
||||||
|
in ''
|
||||||
|
${curl} "https://freedns.afraid.org/dynamic/update.php?$AFRAID_KEY"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
systemd.timers.ddns-afraid = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnStartupSec = "2min";
|
||||||
|
OnUnitActiveSec = "10min";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets."ddns_afraid" = {
|
||||||
|
sopsFile = ../../../secrets/servo.yaml;
|
||||||
|
};
|
||||||
|
}
|
@@ -1,5 +1,7 @@
|
|||||||
{ config, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
# we use manual DDNS now
|
||||||
|
lib.mkIf false
|
||||||
{
|
{
|
||||||
systemd.services.ddns-he = {
|
systemd.services.ddns-he = {
|
||||||
description = "update dynamic DNS entries for HurricaneElectric";
|
description = "update dynamic DNS entries for HurricaneElectric";
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
./ddns-afraid.nix
|
||||||
./ddns-he.nix
|
./ddns-he.nix
|
||||||
./ejabberd.nix
|
./ejabberd.nix
|
||||||
./freshrss.nix
|
./freshrss.nix
|
||||||
@@ -9,13 +10,17 @@
|
|||||||
./ipfs.nix
|
./ipfs.nix
|
||||||
./jackett.nix
|
./jackett.nix
|
||||||
./jellyfin.nix
|
./jellyfin.nix
|
||||||
|
./kiwix-serve.nix
|
||||||
./matrix
|
./matrix
|
||||||
./navidrome.nix
|
./navidrome.nix
|
||||||
|
./nixserve.nix
|
||||||
./nginx.nix
|
./nginx.nix
|
||||||
./pleroma.nix
|
./pleroma.nix
|
||||||
./postfix.nix
|
./postfix.nix
|
||||||
./postgres.nix
|
./postgres.nix
|
||||||
./prosody.nix
|
./prosody.nix
|
||||||
./transmission.nix
|
./transmission.nix
|
||||||
|
./trust-dns.nix
|
||||||
|
./wikipedia.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -1,48 +1,393 @@
|
|||||||
# docs:
|
# docs:
|
||||||
# - <https://docs.ejabberd.im/admin/configuration/basic>
|
# - <https://docs.ejabberd.im/admin/configuration/basic>
|
||||||
{ lib, ... }:
|
# example configs:
|
||||||
|
# - <https://github.com/vkleen/machines/blob/138a2586ce185d7cf201d4e1fe898c83c4af52eb/hosts/europium/ejabberd.nix>
|
||||||
|
# - <https://github.com/Mic92/stockholm/blob/675ef0088624c9de1cb531f318446316884a9d3d/tv/3modules/ejabberd/default.nix>
|
||||||
|
# - <https://github.com/buffet/tararice/blob/master/programs/ejabberd.nix>
|
||||||
|
# - enables STUN and TURN
|
||||||
|
# - only over UDP 3478, not firewall-forwarding any TURN port range
|
||||||
|
# - uses stun_disco module (but with no options)
|
||||||
|
# - <https://github.com/leo60228/dotfiles/blob/39b3abba3009bdc31413d4757ca2f882a33eec8b/files/ejabberd.yml>
|
||||||
|
# - <https://github.com/Mic92/dotfiles/blob/ddf0f4821f554f7667fc803344657367c55fb9e6/nixos/eve/modules/ejabberd.nix>
|
||||||
|
# - <nixpkgs:nixos/tests/xmpp/ejabberd.nix>
|
||||||
|
# - 2013: <https://github.com/processone/ejabberd/blob/master/ejabberd.yml.example>
|
||||||
|
#
|
||||||
|
# compliance tests:
|
||||||
|
# - <https://compliance.conversations.im/server/uninsane.org/#xep0352>
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
# XXX disabled: fails to start because of `mnesia_tm` dependency
|
# XXX: avatar support works in MUCs but not DMs
|
||||||
# lib.mkIf false
|
# lib.mkIf false
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "ejabberd"; group = "ejabberd"; directory = "/var/lib/ejabberd"; }
|
{ user = "ejabberd"; group = "ejabberd"; directory = "/var/lib/ejabberd"; }
|
||||||
];
|
];
|
||||||
networking.firewall.allowedTCPPorts = [
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
3478 # STUN/TURN
|
||||||
5222 # XMPP client -> server
|
5222 # XMPP client -> server
|
||||||
|
5223 # XMPPS client -> server (XMPP over TLS)
|
||||||
5269 # XMPP server -> server
|
5269 # XMPP server -> server
|
||||||
|
5270 # XMPPS server -> server (XMPP over TLS)
|
||||||
|
5280 # bosh
|
||||||
|
5281 # bosh (https) ??
|
||||||
|
5349 # STUN/TURN (TLS)
|
||||||
|
5443 # web services (file uploads, websockets, admin)
|
||||||
];
|
];
|
||||||
|
networking.firewall.allowedUDPPorts = [
|
||||||
|
3478 # STUN/TURN
|
||||||
|
];
|
||||||
|
networking.firewall.allowedTCPPortRanges = [{
|
||||||
|
from = 49152; # TURN
|
||||||
|
to = 65535;
|
||||||
|
}];
|
||||||
|
networking.firewall.allowedUDPPortRanges = [{
|
||||||
|
from = 49152; # TURN
|
||||||
|
to = 65535;
|
||||||
|
}];
|
||||||
|
|
||||||
# provide access to certs
|
# provide access to certs
|
||||||
users.users.ejabberd.extraGroups = [ "nginx" ];
|
users.users.ejabberd.extraGroups = [ "nginx" ];
|
||||||
|
|
||||||
|
security.acme.certs."uninsane.org".extraDomainNames = [
|
||||||
|
"xmpp.uninsane.org"
|
||||||
|
"muc.xmpp.uninsane.org"
|
||||||
|
"pubsub.xmpp.uninsane.org"
|
||||||
|
"upload.xmpp.uninsane.org"
|
||||||
|
"vjid.xmpp.uninsane.org"
|
||||||
|
];
|
||||||
|
|
||||||
|
# exists so the XMPP server's cert can obtain altNames for all its resources
|
||||||
|
services.nginx.virtualHosts."xmpp.uninsane.org" = {
|
||||||
|
useACMEHost = "uninsane.org";
|
||||||
|
};
|
||||||
|
services.nginx.virtualHosts."muc.xmpp.uninsane.org" = {
|
||||||
|
useACMEHost = "uninsane.org";
|
||||||
|
};
|
||||||
|
services.nginx.virtualHosts."pubsub.xmpp.uninsane.org" = {
|
||||||
|
useACMEHost = "uninsane.org";
|
||||||
|
};
|
||||||
|
services.nginx.virtualHosts."upload.xmpp.uninsane.org" = {
|
||||||
|
useACMEHost = "uninsane.org";
|
||||||
|
};
|
||||||
|
services.nginx.virtualHosts."vjid.xmpp.uninsane.org" = {
|
||||||
|
useACMEHost = "uninsane.org";
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||||
|
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
|
||||||
|
A."xmpp" = "%NATIVE%";
|
||||||
|
CNAME."muc.xmpp" = "xmpp";
|
||||||
|
CNAME."pubsub.xmpp" = "xmpp";
|
||||||
|
CNAME."upload.xmpp" = "xmpp";
|
||||||
|
CNAME."vjid.xmpp" = "xmpp";
|
||||||
|
|
||||||
|
# _Service._Proto.Name TTL Class SRV Priority Weight Port Target
|
||||||
|
# - <https://xmpp.org/extensions/xep-0368.html>
|
||||||
|
# something's requesting the SRV records for muc.xmpp, so let's include it
|
||||||
|
# nothing seems to request XMPP SRVs for the other records (except @)
|
||||||
|
# lower numerical priority field tells clients to prefer this method
|
||||||
|
SRV."_xmpps-client._tcp.muc.xmpp" = "3 50 5223 xmpp";
|
||||||
|
SRV."_xmpps-server._tcp.muc.xmpp" = "3 50 5270 xmpp";
|
||||||
|
SRV."_xmpp-client._tcp.muc.xmpp" = "5 50 5222 xmpp";
|
||||||
|
SRV."_xmpp-server._tcp.muc.xmpp" = "5 50 5269 xmpp";
|
||||||
|
|
||||||
|
SRV."_xmpps-client._tcp" = "3 50 5223 xmpp";
|
||||||
|
SRV."_xmpps-server._tcp" = "3 50 5270 xmpp";
|
||||||
|
SRV."_xmpp-client._tcp" = "5 50 5222 xmpp";
|
||||||
|
SRV."_xmpp-server._tcp" = "5 50 5269 xmpp";
|
||||||
|
|
||||||
|
SRV."_stun._udp" = "5 50 3478 xmpp";
|
||||||
|
SRV."_stun._tcp" = "5 50 3478 xmpp";
|
||||||
|
SRV."_stuns._tcp" = "5 50 5349 xmpp";
|
||||||
|
SRV."_turn._udp" = "5 50 3478 xmpp";
|
||||||
|
SRV."_turn._tcp" = "5 50 3478 xmpp";
|
||||||
|
SRV."_turns._tcp" = "5 50 5349 xmpp";
|
||||||
|
};
|
||||||
|
|
||||||
# TODO: allocate UIDs/GIDs ?
|
# TODO: allocate UIDs/GIDs ?
|
||||||
services.ejabberd.enable = true;
|
services.ejabberd.enable = true;
|
||||||
services.ejabberd.configFile = builtins.toFile "ejabberd.yaml" ''
|
services.ejabberd.configFile = "/var/lib/ejabberd/ejabberd.yaml";
|
||||||
|
systemd.services.ejabberd.preStart = let
|
||||||
|
config-in = pkgs.writeTextFile {
|
||||||
|
name = "ejabberd.yaml.in";
|
||||||
|
text = ''
|
||||||
hosts:
|
hosts:
|
||||||
- uninsane.org
|
- uninsane.org
|
||||||
|
|
||||||
# none | emergency | alert | critical | error | warning | notice | info | debug
|
# none | emergency | alert | critical | error | warning | notice | info | debug
|
||||||
loglevel: debug
|
loglevel: debug
|
||||||
|
# loglevel: info
|
||||||
|
# loglevel: notice
|
||||||
|
|
||||||
acme:
|
acme:
|
||||||
auto: false
|
auto: false
|
||||||
certfiles:
|
certfiles:
|
||||||
- /var/lib/acme/uninsane.org/fullchain.pem
|
- /var/lib/acme/uninsane.org/full.pem
|
||||||
- /var/lib/acme/uninsane.org/key.pem
|
# ca_file: ${pkgs.cacert.unbundled}/etc/ssl/certs/
|
||||||
|
# ca_file: ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
|
||||||
|
|
||||||
pam_userinfotype: jid
|
pam_userinfotype: jid
|
||||||
|
|
||||||
|
acl:
|
||||||
|
admin:
|
||||||
|
user:
|
||||||
|
- "colin@uninsane.org"
|
||||||
|
local:
|
||||||
|
user_regexp: ""
|
||||||
|
loopback:
|
||||||
|
ip:
|
||||||
|
- 127.0.0.0/8
|
||||||
|
- ::1/128
|
||||||
|
|
||||||
|
access_rules:
|
||||||
|
local:
|
||||||
|
allow: local
|
||||||
|
c2s_access:
|
||||||
|
allow: all
|
||||||
|
announce:
|
||||||
|
allow: admin
|
||||||
|
configure:
|
||||||
|
allow: admin
|
||||||
|
muc_create:
|
||||||
|
allow: local
|
||||||
|
pubsub_createnode_access:
|
||||||
|
allow: all
|
||||||
|
trusted_network:
|
||||||
|
allow: loopback
|
||||||
|
|
||||||
|
# docs: <https://docs.ejabberd.im/admin/configuration/basic/#shaper-rules>
|
||||||
|
shaper_rules:
|
||||||
|
# setting this to above 1 may break outgoing messages
|
||||||
|
# - maybe some servers rate limit? or just don't understand simultaneous connections?
|
||||||
|
max_s2s_connections: 1
|
||||||
|
max_user_sessions: 10
|
||||||
|
max_user_offline_messages: 5000
|
||||||
|
c2s_shaper:
|
||||||
|
fast: all
|
||||||
|
s2s_shaper:
|
||||||
|
med: all
|
||||||
|
|
||||||
|
# docs: <https://docs.ejabberd.im/admin/configuration/basic/#shapers>
|
||||||
|
# this limits the bytes/sec.
|
||||||
|
# for example, burst: 3_000_000 and rate: 100_000 means:
|
||||||
|
# - each client has a BW budget that accumulates 100kB/sec and is capped at 3 MB
|
||||||
|
shaper:
|
||||||
|
fast: 1000000
|
||||||
|
med: 500000
|
||||||
|
# fast:
|
||||||
|
# - rate: 1000000
|
||||||
|
# - burst_size: 10000000
|
||||||
|
# med:
|
||||||
|
# - rate: 500000
|
||||||
|
# - burst_size: 5000000
|
||||||
|
|
||||||
# see: <https://docs.ejabberd.im/admin/configuration/listen/>
|
# see: <https://docs.ejabberd.im/admin/configuration/listen/>
|
||||||
# TODO: host web admin panel
|
# s2s_use_starttls: true
|
||||||
|
s2s_use_starttls: optional
|
||||||
|
# lessens 504: remote-server-timeout errors
|
||||||
|
# see: <https://github.com/processone/ejabberd/issues/3105#issuecomment-562182967>
|
||||||
|
negotiation_timeout: 60
|
||||||
|
|
||||||
listen:
|
listen:
|
||||||
-
|
-
|
||||||
port: 5222
|
port: 5222
|
||||||
module: ejabberd_c2s
|
module: ejabberd_c2s
|
||||||
|
shaper: c2s_shaper
|
||||||
starttls: true
|
starttls: true
|
||||||
|
access: c2s_access
|
||||||
|
-
|
||||||
|
port: 5223
|
||||||
|
module: ejabberd_c2s
|
||||||
|
shaper: c2s_shaper
|
||||||
|
tls: true
|
||||||
|
access: c2s_access
|
||||||
-
|
-
|
||||||
port: 5269
|
port: 5269
|
||||||
module: ejabberd_s2s_in
|
module: ejabberd_s2s_in
|
||||||
starttls: true
|
shaper: s2s_shaper
|
||||||
|
-
|
||||||
|
port: 5270
|
||||||
|
module: ejabberd_s2s_in
|
||||||
|
shaper: s2s_shaper
|
||||||
|
tls: true
|
||||||
|
-
|
||||||
|
port: 5443
|
||||||
|
module: ejabberd_http
|
||||||
|
tls: true
|
||||||
|
request_handlers:
|
||||||
|
/admin: ejabberd_web_admin # TODO: ensure this actually works
|
||||||
|
/api: mod_http_api # ejabberd API endpoint (to control server)
|
||||||
|
/bosh: mod_bosh
|
||||||
|
/upload: mod_http_upload
|
||||||
|
/ws: ejabberd_http_ws
|
||||||
|
# /.well-known/host-meta: mod_host_meta
|
||||||
|
# /.well-known/host-meta.json: mod_host_meta
|
||||||
|
-
|
||||||
|
# STUN+TURN TCP
|
||||||
|
# note that the full port range should be forwarded ("not NAT'd")
|
||||||
|
# `use_turn=true` enables both TURN *and* STUN
|
||||||
|
port: 3478
|
||||||
|
module: ejabberd_stun
|
||||||
|
transport: tcp
|
||||||
|
use_turn: true
|
||||||
|
turn_min_port: 49152
|
||||||
|
turn_max_port: 65535
|
||||||
|
turn_ipv4_address: %NATIVE%
|
||||||
|
-
|
||||||
|
# STUN+TURN UDP
|
||||||
|
port: 3478
|
||||||
|
module: ejabberd_stun
|
||||||
|
transport: udp
|
||||||
|
use_turn: true
|
||||||
|
turn_min_port: 49152
|
||||||
|
turn_max_port: 65535
|
||||||
|
turn_ipv4_address: %NATIVE%
|
||||||
|
-
|
||||||
|
# STUN+TURN TLS over TCP
|
||||||
|
port: 5349
|
||||||
|
module: ejabberd_stun
|
||||||
|
transport: tcp
|
||||||
|
tls: true
|
||||||
|
certfile: /var/lib/acme/uninsane.org/full.pem
|
||||||
|
use_turn: true
|
||||||
|
turn_min_port: 49152
|
||||||
|
turn_max_port: 65535
|
||||||
|
turn_ipv4_address: %NATIVE%
|
||||||
|
|
||||||
|
# TODO: enable mod_fail2ban
|
||||||
|
# TODO(low): look into mod_http_fileserver for serving macros?
|
||||||
|
modules:
|
||||||
|
# mod_adhoc: {}
|
||||||
|
# mod_announce:
|
||||||
|
# access: admin
|
||||||
|
# allows users to set avatars in vCard
|
||||||
|
# - <https://docs.ejabberd.im/admin/configuration/modules/#mod-avatar>
|
||||||
|
mod_avatar: {}
|
||||||
|
mod_caps: {} # for mod_pubsub
|
||||||
|
mod_carboncopy: {} # allows multiple clients to receive a user's message
|
||||||
|
# queues messages when recipient is offline, including PEP and presence messages.
|
||||||
|
# compliance test suggests this be enabled
|
||||||
|
mod_client_state: {}
|
||||||
|
# mod_conversejs: TODO: enable once on 21.12
|
||||||
|
# allows clients like Dino to discover where to upload files
|
||||||
|
mod_disco:
|
||||||
|
server_info:
|
||||||
|
-
|
||||||
|
modules: all
|
||||||
|
name: abuse-addresses
|
||||||
|
urls:
|
||||||
|
- "mailto:admin.xmpp@uninsane.org"
|
||||||
|
- "xmpp:colin@uninsane.org"
|
||||||
|
-
|
||||||
|
modules: all
|
||||||
|
name: admin-addresses
|
||||||
|
urls:
|
||||||
|
- "mailto:admin.xmpp@uninsane.org"
|
||||||
|
- "xmpp:colin@uninsane.org"
|
||||||
|
mod_http_upload:
|
||||||
|
host: upload.xmpp.uninsane.org
|
||||||
|
hosts:
|
||||||
|
- upload.xmpp.uninsane.org
|
||||||
|
put_url: "https://@HOST@:5443/upload"
|
||||||
|
dir_mode: "0750"
|
||||||
|
file_mode: "0750"
|
||||||
|
rm_on_unregister: false
|
||||||
|
# allow discoverability of BOSH and websocket endpoints
|
||||||
|
# TODO: enable once on ejabberd 22.05 (presently 21.04)
|
||||||
|
# mod_host_meta: {}
|
||||||
|
mod_jidprep: {} # probably not needed: lets clients normalize jids
|
||||||
|
mod_last: {} # allow other users to know when i was last online
|
||||||
|
mod_mam:
|
||||||
|
# Mnesia is limited to 2GB, better to use an SQL backend
|
||||||
|
# For small servers SQLite is a good fit and is very easy
|
||||||
|
# to configure. Uncomment this when you have SQL configured:
|
||||||
|
# db_type: sql
|
||||||
|
assume_mam_usage: true
|
||||||
|
default: always
|
||||||
|
mod_muc:
|
||||||
|
access:
|
||||||
|
- allow
|
||||||
|
access_admin:
|
||||||
|
- allow: admin
|
||||||
|
access_create: muc_create
|
||||||
|
access_persistent: muc_create
|
||||||
|
access_mam:
|
||||||
|
- allow
|
||||||
|
history_size: 100 # messages to show new participants
|
||||||
|
host: muc.xmpp.uninsane.org
|
||||||
|
hosts:
|
||||||
|
- muc.xmpp.uninsane.org
|
||||||
|
default_room_options:
|
||||||
|
anonymous: false
|
||||||
|
lang: en
|
||||||
|
persistent: true
|
||||||
|
mam: true
|
||||||
|
mod_muc_admin: {}
|
||||||
|
mod_offline: # store messages for a user when they're offline (TODO: understand multi-client workflow?)
|
||||||
|
access_max_user_messages: max_user_offline_messages
|
||||||
|
store_groupchat: true
|
||||||
|
mod_ping: {}
|
||||||
|
mod_privacy: {} # deprecated, but required for `ejabberctl export_piefxis`
|
||||||
|
mod_private: {} # allow local clients to persist arbitrary data on my server
|
||||||
|
# push notifications to services integrated with e.g. Apple/Android.
|
||||||
|
# default is for a maximum amount of PII to be withheld, since these push notifs
|
||||||
|
# generally traverse 3rd party services. can opt to include message body, etc, though.
|
||||||
|
mod_push: {}
|
||||||
|
# i don't fully understand what this does, but it seems aimed at making push notifs more reliable.
|
||||||
|
mod_push_keepalive: {}
|
||||||
|
mod_roster:
|
||||||
|
versioning: true
|
||||||
|
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-s2s-dialback>
|
||||||
|
# s2s dialback to verify inbound messages
|
||||||
|
# unclear to what degree the XMPP network requires this
|
||||||
|
mod_s2s_dialback: {}
|
||||||
|
mod_shared_roster: {} # creates groups for @all, @online, and anything manually administered?
|
||||||
|
mod_stream_mgmt:
|
||||||
|
resend_on_timeout: if_offline # resend undelivered messages if the origin client is offline
|
||||||
|
# fallback for when DNS-based STUN discovery is unsupported.
|
||||||
|
# - see: <https://xmpp.org/extensions/xep-0215.html>
|
||||||
|
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-stun-disco>
|
||||||
|
# people say to just keep this defaulted (i guess ejabberd knows to return its `host` option of uninsane.org?)
|
||||||
|
mod_stun_disco: {}
|
||||||
|
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-vcard>
|
||||||
|
mod_vcard:
|
||||||
|
allow_return_all: true # all users are discoverable (?)
|
||||||
|
host: vjid.xmpp.uninsane.org
|
||||||
|
hosts:
|
||||||
|
- vjid.xmpp.uninsane.org
|
||||||
|
search: true
|
||||||
|
mod_vcard_xupdate: {} # needed for avatars
|
||||||
|
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-pubsub>
|
||||||
|
mod_pubsub: # needed for avatars
|
||||||
|
access_createnode: pubsub_createnode_access
|
||||||
|
host: pubsub.xmpp.uninsane.org
|
||||||
|
hosts:
|
||||||
|
- pubsub.xmpp.uninsane.org
|
||||||
|
ignore_pep_from_offline: false
|
||||||
|
last_item_cache: true
|
||||||
|
plugins:
|
||||||
|
- pep
|
||||||
|
- flat
|
||||||
|
force_node_config:
|
||||||
|
# ensure client bookmarks are private
|
||||||
|
storage:bookmarks:
|
||||||
|
access_model: whitelist
|
||||||
|
urn:xmpp:avatar:data:
|
||||||
|
access_model: open
|
||||||
|
urn:xmpp:avatar:metadata:
|
||||||
|
access_model: open
|
||||||
|
mod_version: {}
|
||||||
'';
|
'';
|
||||||
|
};
|
||||||
|
sed = "${pkgs.gnused}/bin/sed";
|
||||||
|
in ''
|
||||||
|
ip=$(cat '${config.sane.services.dyn-dns.ipPath}')
|
||||||
|
# config is 444 (not 644), so we want to write out-of-place and then atomically move
|
||||||
|
# TODO: factor this out into `sane-woop` helper?
|
||||||
|
rm -f /var/lib/ejabberd/ejabberd.yaml.new
|
||||||
|
${sed} "s/%NATIVE%/$ip/" ${config-in} > /var/lib/ejabberd/ejabberd.yaml.new
|
||||||
|
mv /var/lib/ejabberd/ejabberd.yaml{.new,}
|
||||||
|
'';
|
||||||
|
|
||||||
|
sane.services.dyn-dns.restartOnChange = [ "ejabberd.service" ];
|
||||||
}
|
}
|
||||||
|
@@ -9,19 +9,17 @@
|
|||||||
# $ sudo -u freshrss -g freshrss FRESHRSS_DATA_PATH=/var/lib/freshrss ./result/cli/export-opml-for-user.php --user admin
|
# $ sudo -u freshrss -g freshrss FRESHRSS_DATA_PATH=/var/lib/freshrss ./result/cli/export-opml-for-user.php --user admin
|
||||||
# ```
|
# ```
|
||||||
|
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, sane-lib, ... }:
|
||||||
{
|
{
|
||||||
sops.secrets.freshrss_passwd = {
|
sops.secrets.freshrss_passwd = {
|
||||||
sopsFile = ../../../secrets/servo.yaml;
|
sopsFile = ../../../secrets/servo.yaml;
|
||||||
owner = config.users.users.freshrss.name;
|
owner = config.users.users.freshrss.name;
|
||||||
mode = "400";
|
mode = "0400";
|
||||||
};
|
};
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "freshrss"; group = "freshrss"; directory = "/var/lib/freshrss"; }
|
{ user = "freshrss"; group = "freshrss"; directory = "/var/lib/freshrss"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
users.users.freshrss.uid = config.sane.allocations.freshrss-uid;
|
|
||||||
users.groups.freshrss.gid = config.sane.allocations.freshrss-gid;
|
|
||||||
services.freshrss.enable = true;
|
services.freshrss.enable = true;
|
||||||
services.freshrss.baseUrl = "https://rss.uninsane.org";
|
services.freshrss.baseUrl = "https://rss.uninsane.org";
|
||||||
services.freshrss.virtualHost = "rss.uninsane.org";
|
services.freshrss.virtualHost = "rss.uninsane.org";
|
||||||
@@ -29,9 +27,11 @@
|
|||||||
|
|
||||||
systemd.services.freshrss-import-feeds =
|
systemd.services.freshrss-import-feeds =
|
||||||
let
|
let
|
||||||
|
feeds = sane-lib.feeds;
|
||||||
fresh = config.systemd.services.freshrss-config;
|
fresh = config.systemd.services.freshrss-config;
|
||||||
feeds = import ../../../modules/home-manager/feeds.nix { inherit lib; };
|
all-feeds = config.sane.feeds;
|
||||||
opml = pkgs.writeText "sane-freshrss.opml" (feeds.feedsToOpml feeds.all);
|
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
|
||||||
|
opml = pkgs.writeText "sane-freshrss.opml" (feeds.feedsToOpml wanted-feeds);
|
||||||
in {
|
in {
|
||||||
inherit (fresh) wantedBy environment;
|
inherit (fresh) wantedBy environment;
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
@@ -49,4 +49,13 @@
|
|||||||
# the default ("*:0/5") is to run every 5 minutes.
|
# the default ("*:0/5") is to run every 5 minutes.
|
||||||
# `systemctl list-timers` to show
|
# `systemctl list-timers` to show
|
||||||
systemd.services.freshrss-updater.startAt = lib.mkForce "*:3/30";
|
systemd.services.freshrss-updater.startAt = lib.mkForce "*:3/30";
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."rss.uninsane.org" = {
|
||||||
|
addSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
# the routing is handled by services.freshrss.virtualHost
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."rss" = "native";
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
{ config, pkgs, lib, ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? could be more granular
|
# TODO: mode? could be more granular
|
||||||
{ user = "git"; group = "gitea"; directory = "/var/lib/gitea"; }
|
{ user = "git"; group = "gitea"; directory = "/var/lib/gitea"; }
|
||||||
];
|
];
|
||||||
users.groups.gitea.gid = config.sane.allocations.gitea-gid;
|
|
||||||
services.gitea.enable = true;
|
services.gitea.enable = true;
|
||||||
services.gitea.user = "git"; # default is 'gitea'
|
services.gitea.user = "git"; # default is 'gitea'
|
||||||
services.gitea.database.type = "postgres";
|
services.gitea.database.type = "postgres";
|
||||||
@@ -72,4 +71,18 @@
|
|||||||
"/var/lib/gitea"
|
"/var/lib/gitea"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# hosted git (web view and for `git <cmd>` use
|
||||||
|
# TODO: enable publog?
|
||||||
|
services.nginx.virtualHosts."git.uninsane.org" = {
|
||||||
|
forceSSL = true; # gitea complains if served over a different protocol than its config file says
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3000";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."git" = "native";
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
|
RestartSec = "10s";
|
||||||
|
|
||||||
# hardening
|
# hardening
|
||||||
WorkingDirectory = "/tmp";
|
WorkingDirectory = "/tmp";
|
||||||
@@ -42,4 +43,26 @@
|
|||||||
after = [ "network.target" ];
|
after = [ "network.target" ];
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# server statistics
|
||||||
|
services.nginx.virtualHosts."sink.uninsane.org" = {
|
||||||
|
addSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
root = "/var/lib/uninsane/sink";
|
||||||
|
|
||||||
|
locations."/ws" = {
|
||||||
|
proxyPass = "http://127.0.0.1:7890";
|
||||||
|
# XXX not sure how much of this is necessary
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_read_timeout 7d;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."sink" = "native";
|
||||||
}
|
}
|
||||||
|
@@ -6,12 +6,36 @@
|
|||||||
# - number of open peer connections:
|
# - number of open peer connections:
|
||||||
# - sudo -u ipfs -g ipfs ipfs -c /var/lib/ipfs/ swarm peers | wc -l
|
# - sudo -u ipfs -g ipfs ipfs -c /var/lib/ipfs/ swarm peers | wc -l
|
||||||
|
|
||||||
{ ... }:
|
{ lib, ... }:
|
||||||
|
|
||||||
|
lib.mkIf false # i don't actively use ipfs anymore
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? could be more granular
|
# TODO: mode? could be more granular
|
||||||
{ user = "261"; group = "261"; directory = "/var/lib/ipfs"; }
|
{ user = "261"; group = "261"; directory = "/var/lib/ipfs"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ 4001 ];
|
||||||
|
networking.firewall.allowedUDPPorts = [ 4001 ];
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."ipfs.uninsane.org" = {
|
||||||
|
# don't default to ssl upgrades, since this may be dnslink'd from a different domain.
|
||||||
|
# ideally we'd disable ssl entirely, but some places assume it?
|
||||||
|
addSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8080";
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Ipfs-Gateway-Prefix "";
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."ipfs" = "native";
|
||||||
|
|
||||||
# services.ipfs.enable = true;
|
# services.ipfs.enable = true;
|
||||||
services.kubo.localDiscovery = true;
|
services.kubo.localDiscovery = true;
|
||||||
services.kubo.settings = {
|
services.kubo.settings = {
|
||||||
|
@@ -1,18 +1,32 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? we only need this to save Indexer creds ==> migrate to config?
|
# TODO: mode? we only need this to save Indexer creds ==> migrate to config?
|
||||||
{ user = "root"; group = "root"; directory = "/var/lib/jackett"; }
|
{ user = "root"; group = "root"; directory = "/var/lib/jackett"; }
|
||||||
];
|
];
|
||||||
services.jackett.enable = true;
|
services.jackett.enable = true;
|
||||||
|
|
||||||
systemd.services.jackett.after = ["wg0veth.service"];
|
systemd.services.jackett.after = [ "wireguard-wg0.service" ];
|
||||||
|
systemd.services.jackett.partOf = [ "wireguard-wg0.service" ];
|
||||||
systemd.services.jackett.serviceConfig = {
|
systemd.services.jackett.serviceConfig = {
|
||||||
# run this behind the OVPN static VPN
|
# run this behind the OVPN static VPN
|
||||||
NetworkNamespacePath = "/run/netns/ovpns";
|
NetworkNamespacePath = "/run/netns/ovpns";
|
||||||
# patch jackett to listen on the public interfaces
|
# patch jackett to listen on the public interfaces
|
||||||
# ExecStart = lib.mkForce "${pkgs.jackett}/bin/Jackett --NoUpdates --DataFolder /var/lib/jackett/.config/Jackett --ListenPublic";
|
# ExecStart = lib.mkForce "${pkgs.jackett}/bin/Jackett --NoUpdates --DataFolder /var/lib/jackett/.config/Jackett --ListenPublic";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# jackett torrent search
|
||||||
|
services.nginx.virtualHosts."jackett.uninsane.org" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
locations."/" = {
|
||||||
|
# proxyPass = "http://ovpns.uninsane.org:9117";
|
||||||
|
proxyPass = "http://10.0.1.6:9117";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."jackett" = "native";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,14 +1,67 @@
|
|||||||
{ config, ... }:
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
# TODO: re-enable after migrating media dir to /var/lib/uninsane/media
|
||||||
|
# else it's too spammy
|
||||||
|
lib.mkIf false
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
networking.firewall.allowedUDPPorts = [
|
||||||
|
1900 7359 # DLNA: https://jellyfin.org/docs/general/networking/index.html
|
||||||
|
];
|
||||||
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? could be more granular
|
# TODO: mode? could be more granular
|
||||||
{ user = "jellyfin"; group = "jellyfin"; directory = "/var/lib/jellyfin"; }
|
{ user = "jellyfin"; group = "jellyfin"; directory = "/var/lib/jellyfin"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
# users.users.jellyfin.uid = config.sane.allocations.jellyfin-uid;
|
# Jellyfin multimedia server
|
||||||
# users.groups.jellyfin.gid = config.sane.allocations.jellyfin-gid;
|
# this is mostly taken from the official jellfin.org docs
|
||||||
# TODO: re-enable after migrating media dir to /var/lib/uninsane/media
|
services.nginx.virtualHosts."jelly.uninsane.org" = {
|
||||||
# else it's too spammy
|
addSSL = true;
|
||||||
# services.jellyfin.enable = true;
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8096";
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Protocol $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $http_host;
|
||||||
|
|
||||||
|
# Disable buffering when the nginx proxy gets very resource heavy upon streaming
|
||||||
|
proxy_buffering off;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# locations."/web/" = {
|
||||||
|
# proxyPass = "http://127.0.0.1:8096/web/index.html";
|
||||||
|
# extraConfig = ''
|
||||||
|
# proxy_set_header Host $host;
|
||||||
|
# proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
# proxy_set_header X-Forwarded-Protocol $scheme;
|
||||||
|
# proxy_set_header X-Forwarded-Host $http_host;
|
||||||
|
# '';
|
||||||
|
# };
|
||||||
|
locations."/socket" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8096";
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Protocol $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $http_host;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."jelly" = "native";
|
||||||
|
|
||||||
|
services.jellyfin.enable = true;
|
||||||
}
|
}
|
||||||
|
17
hosts/servo/services/kiwix-serve.nix
Normal file
17
hosts/servo/services/kiwix-serve.nix
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
sane.services.kiwix-serve = {
|
||||||
|
enable = true;
|
||||||
|
port = 8013;
|
||||||
|
zimPaths = [ "/var/lib/uninsane/www-archive/wikipedia_en_all_maxi_2022-05.zim" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."w.uninsane.org" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
locations."/".proxyPass = "http://127.0.0.1:8013";
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."w" = "native";
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
# docs: https://nixos.wiki/wiki/Matrix
|
# docs: https://nixos.wiki/wiki/Matrix
|
||||||
# docs: https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-synapse
|
# docs: https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-synapse
|
||||||
{ config, lib, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
# ./irc.nix
|
# ./irc.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/matrix-synapse"; }
|
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/matrix-synapse"; }
|
||||||
];
|
];
|
||||||
services.matrix-synapse.enable = true;
|
services.matrix-synapse.enable = true;
|
||||||
@@ -77,6 +77,55 @@
|
|||||||
# create a token with limited uses:
|
# create a token with limited uses:
|
||||||
# curl -d '{ "uses_allowed": 1 }' --header "Authorization: Bearer <my_token>" localhost:8008/_synapse/admin/v1/registration_tokens/new
|
# curl -d '{ "uses_allowed": 1 }' --header "Authorization: Bearer <my_token>" localhost:8008/_synapse/admin/v1/registration_tokens/new
|
||||||
|
|
||||||
|
# matrix chat server
|
||||||
|
# TODO: was `publog`
|
||||||
|
services.nginx.virtualHosts."matrix.uninsane.org" = {
|
||||||
|
addSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
|
||||||
|
# TODO colin: replace this with something helpful to the viewer
|
||||||
|
# locations."/".extraConfig = ''
|
||||||
|
# return 404;
|
||||||
|
# '';
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8008";
|
||||||
|
};
|
||||||
|
# redirect browsers to the web client.
|
||||||
|
# i don't think native matrix clients ever fetch the root.
|
||||||
|
# ideally this would be put behind some user-agent test though.
|
||||||
|
locations."= /" = {
|
||||||
|
return = "301 https://web.matrix.uninsane.org";
|
||||||
|
};
|
||||||
|
|
||||||
|
# locations."/_matrix" = {
|
||||||
|
# proxyPass = "http://127.0.0.1:8008";
|
||||||
|
# };
|
||||||
|
};
|
||||||
|
|
||||||
|
# matrix web client
|
||||||
|
# docs: https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-element-web
|
||||||
|
services.nginx.virtualHosts."web.matrix.uninsane.org" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
|
||||||
|
root = pkgs.element-web.override {
|
||||||
|
conf = {
|
||||||
|
default_server_config."m.homeserver" = {
|
||||||
|
"base_url" = "https://matrix.uninsane.org";
|
||||||
|
"server_name" = "uninsane.org";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||||
|
CNAME."matrix" = "native";
|
||||||
|
CNAME."web.matrix" = "native";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
sops.secrets.matrix_synapse_secrets = {
|
sops.secrets.matrix_synapse_secrets = {
|
||||||
sopsFile = ../../../../secrets/servo.yaml;
|
sopsFile = ../../../../secrets/servo.yaml;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/mx-puppet-discord"; }
|
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/mx-puppet-discord"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ config, lib, ... }:
|
{ config, lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode?
|
# TODO: mode?
|
||||||
# user and group are both "matrix-appservice-irc"
|
# user and group are both "matrix-appservice-irc"
|
||||||
{ user = "993"; group = "992"; directory = "/var/lib/matrix-appservice-irc"; }
|
{ user = "993"; group = "992"; directory = "/var/lib/matrix-appservice-irc"; }
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "navidrome"; group = "navidrome"; directory = "/var/lib/private/navidrome"; }
|
# TODO: we don't have a static user allocated for navidrome!
|
||||||
|
# the chown would happen too early for us to set static perms
|
||||||
|
"/var/lib/private/navidrome"
|
||||||
|
# { user = "navidrome"; group = "navidrome"; directory = "/var/lib/private/navidrome"; }
|
||||||
];
|
];
|
||||||
services.navidrome.enable = true;
|
services.navidrome.enable = true;
|
||||||
services.navidrome.settings = {
|
services.navidrome.settings = {
|
||||||
@@ -14,4 +17,13 @@
|
|||||||
AutoImportPlaylists = false;
|
AutoImportPlaylists = false;
|
||||||
ScanSchedule = "@every 1h";
|
ScanSchedule = "@every 1h";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."music.uninsane.org" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
locations."/".proxyPass = "http://127.0.0.1:4533";
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."music" = "native";
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,20 @@
|
|||||||
# docs: https://nixos.wiki/wiki/Nginx
|
# docs: https://nixos.wiki/wiki/Nginx
|
||||||
{ config, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
# make the logs for this host "public" so that they show up in e.g. metrics
|
# make the logs for this host "public" so that they show up in e.g. metrics
|
||||||
publog = vhost: vhost // {
|
publog = vhost: lib.attrsets.unionOfDisjoint vhost {
|
||||||
extraConfig = (vhost.extraConfig or "") + ''
|
extraConfig = (vhost.extraConfig or "") + ''
|
||||||
access_log /var/log/nginx/public.log vcombined;
|
access_log /var/log/nginx/public.log vcombined;
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
kTLS = true; # in-kernel TLS for better perf
|
# kTLS = true; # in-kernel TLS for better perf
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||||
|
|
||||||
services.nginx.enable = true;
|
services.nginx.enable = true;
|
||||||
services.nginx.appendConfig = ''
|
services.nginx.appendConfig = ''
|
||||||
# use 1 process per core.
|
# use 1 process per core.
|
||||||
@@ -31,6 +34,7 @@ in
|
|||||||
# sets gzip_comp_level = 5
|
# sets gzip_comp_level = 5
|
||||||
services.nginx.recommendedGzipSettings = true;
|
services.nginx.recommendedGzipSettings = true;
|
||||||
# enables OCSP stapling (so clients don't need contact the OCSP server -- i do instead)
|
# enables OCSP stapling (so clients don't need contact the OCSP server -- i do instead)
|
||||||
|
# - doesn't seem to, actually: <https://www.ssllabs.com/ssltest/analyze.html?d=uninsane.org>
|
||||||
# caches TLS sessions for 10m
|
# caches TLS sessions for 10m
|
||||||
services.nginx.recommendedTlsSettings = true;
|
services.nginx.recommendedTlsSettings = true;
|
||||||
# enables sendfile, tcp_nopush, tcp_nodelay, keepalive_timeout 65
|
# enables sendfile, tcp_nopush, tcp_nodelay, keepalive_timeout 65
|
||||||
@@ -44,7 +48,9 @@ in
|
|||||||
# and things don't look right. so force SSL.
|
# and things don't look right. so force SSL.
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
inherit kTLS;
|
# inherit kTLS;
|
||||||
|
# for OCSP stapling
|
||||||
|
sslTrustedCertificate = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||||
|
|
||||||
# uninsane.org/share/foo => /var/lib/uninsane/root/share/foo.
|
# uninsane.org/share/foo => /var/lib/uninsane/root/share/foo.
|
||||||
# yes, nginx does not strip the prefix when evaluating against the root.
|
# yes, nginx does not strip the prefix when evaluating against the root.
|
||||||
@@ -90,256 +96,71 @@ in
|
|||||||
# };
|
# };
|
||||||
};
|
};
|
||||||
|
|
||||||
# server statistics
|
|
||||||
services.nginx.virtualHosts."sink.uninsane.org" = {
|
|
||||||
addSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
inherit kTLS;
|
|
||||||
root = "/var/lib/uninsane/sink";
|
|
||||||
|
|
||||||
locations."/ws" = {
|
|
||||||
proxyPass = "http://127.0.0.1:7890";
|
|
||||||
# XXX not sure how much of this is necessary
|
|
||||||
extraConfig = ''
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection $connection_upgrade;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 7d;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
# Pleroma server and web interface
|
|
||||||
services.nginx.virtualHosts."fed.uninsane.org" = publog {
|
|
||||||
forceSSL = true; # pleroma redirects to https anyway
|
|
||||||
enableACME = true;
|
|
||||||
inherit kTLS;
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "http://127.0.0.1:4000";
|
|
||||||
# documented: https://git.pleroma.social/pleroma/pleroma/-/blob/develop/installation/pleroma.nginx
|
|
||||||
extraConfig = ''
|
|
||||||
# XXX colin: this block is in the nixos examples: i don't understand all of it
|
|
||||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
|
||||||
add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
|
|
||||||
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
|
|
||||||
add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
|
|
||||||
if ($request_method = OPTIONS) {
|
|
||||||
return 204;
|
|
||||||
}
|
|
||||||
|
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
|
||||||
add_header X-Permitted-Cross-Domain-Policies none;
|
|
||||||
add_header X-Frame-Options DENY;
|
|
||||||
add_header X-Content-Type-Options nosniff;
|
|
||||||
add_header Referrer-Policy same-origin;
|
|
||||||
add_header X-Download-Options noopen;
|
|
||||||
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
# proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
|
|
||||||
# colin: added this due to Pleroma complaining in its logs
|
|
||||||
# proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
|
|
||||||
client_max_body_size 16m;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# transmission web client
|
|
||||||
services.nginx.virtualHosts."bt.uninsane.org" = {
|
|
||||||
# basicAuth is literally cleartext user/pw, so FORCE this to happen over SSL
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
inherit kTLS;
|
|
||||||
locations."/" = {
|
|
||||||
# proxyPass = "http://ovpns.uninsane.org:9091";
|
|
||||||
proxyPass = "http://10.0.1.6:9091";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# jackett torrent search
|
|
||||||
services.nginx.virtualHosts."jackett.uninsane.org" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
inherit kTLS;
|
|
||||||
locations."/" = {
|
|
||||||
# proxyPass = "http://ovpns.uninsane.org:9117";
|
|
||||||
proxyPass = "http://10.0.1.6:9117";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# matrix chat server
|
|
||||||
services.nginx.virtualHosts."matrix.uninsane.org" = publog {
|
|
||||||
addSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
inherit kTLS;
|
|
||||||
|
|
||||||
# TODO colin: replace this with something helpful to the viewer
|
|
||||||
# locations."/".extraConfig = ''
|
|
||||||
# return 404;
|
|
||||||
# '';
|
|
||||||
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "http://127.0.0.1:8008";
|
|
||||||
};
|
|
||||||
# redirect browsers to the web client.
|
|
||||||
# i don't think native matrix clients ever fetch the root.
|
|
||||||
# ideally this would be put behind some user-agent test though.
|
|
||||||
locations."= /" = {
|
|
||||||
return = "301 https://web.matrix.uninsane.org";
|
|
||||||
};
|
|
||||||
|
|
||||||
# locations."/_matrix" = {
|
|
||||||
# proxyPass = "http://127.0.0.1:8008";
|
|
||||||
# };
|
|
||||||
};
|
|
||||||
|
|
||||||
# matrix web client
|
|
||||||
# docs: https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-element-web
|
|
||||||
services.nginx.virtualHosts."web.matrix.uninsane.org" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
inherit kTLS;
|
|
||||||
|
|
||||||
root = pkgs.element-web.override {
|
|
||||||
conf = {
|
|
||||||
default_server_config."m.homeserver" = {
|
|
||||||
"base_url" = "https://matrix.uninsane.org";
|
|
||||||
"server_name" = "uninsane.org";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# hosted git (web view and for `git <cmd>` use
|
|
||||||
services.nginx.virtualHosts."git.uninsane.org" = publog {
|
|
||||||
forceSSL = true; # gitea complains if served over a different protocol than its config file says
|
|
||||||
enableACME = true;
|
|
||||||
inherit kTLS;
|
|
||||||
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "http://127.0.0.1:3000";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Jellyfin multimedia server
|
|
||||||
# this is mostly taken from the official jellfin.org docs
|
|
||||||
services.nginx.virtualHosts."jelly.uninsane.org" = {
|
|
||||||
addSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
inherit kTLS;
|
|
||||||
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "http://127.0.0.1:8096";
|
|
||||||
extraConfig = ''
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Protocol $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
|
||||||
|
|
||||||
# Disable buffering when the nginx proxy gets very resource heavy upon streaming
|
|
||||||
proxy_buffering off;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
# locations."/web/" = {
|
|
||||||
# proxyPass = "http://127.0.0.1:8096/web/index.html";
|
|
||||||
# extraConfig = ''
|
|
||||||
# proxy_set_header Host $host;
|
|
||||||
# proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
# proxy_set_header X-Forwarded-Protocol $scheme;
|
|
||||||
# proxy_set_header X-Forwarded-Host $http_host;
|
|
||||||
# '';
|
|
||||||
# };
|
|
||||||
locations."/socket" = {
|
|
||||||
proxyPass = "http://127.0.0.1:8096";
|
|
||||||
extraConfig = ''
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Protocol $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.nginx.virtualHosts."music.uninsane.org" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
inherit kTLS;
|
|
||||||
locations."/".proxyPass = "http://127.0.0.1:4533";
|
|
||||||
};
|
|
||||||
|
|
||||||
services.nginx.virtualHosts."rss.uninsane.org" = {
|
|
||||||
addSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
inherit kTLS;
|
|
||||||
# the routing is handled by freshrss.nix
|
|
||||||
};
|
|
||||||
|
|
||||||
services.nginx.virtualHosts."ipfs.uninsane.org" = {
|
|
||||||
# don't default to ssl upgrades, since this may be dnslink'd from a different domain.
|
|
||||||
# ideally we'd disable ssl entirely, but some places assume it?
|
|
||||||
addSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
inherit kTLS;
|
|
||||||
|
|
||||||
|
# serve any site not listed above, if it's static.
|
||||||
|
# because we define it dynamically, SSL isn't trivial. support only http
|
||||||
|
# documented <https://nginx.org/en/docs/http/ngx_http_core_module.html#server_name>
|
||||||
|
services.nginx.virtualHosts."~^(?<domain>.+)$" = {
|
||||||
default = true;
|
default = true;
|
||||||
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "http://127.0.0.1:8080";
|
|
||||||
extraConfig = ''
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Ipfs-Gateway-Prefix "";
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# exists only to manage certs for dovecot
|
|
||||||
services.nginx.virtualHosts."imap.uninsane.org" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
};
|
|
||||||
# exists only to manage certs for Postfix
|
|
||||||
services.nginx.virtualHosts."mx.uninsane.org" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
};
|
|
||||||
services.nginx.virtualHosts."nixcache.uninsane.org" = {
|
|
||||||
addSSL = true;
|
addSSL = true;
|
||||||
enableACME = true;
|
enableACME = false;
|
||||||
inherit kTLS;
|
sslCertificate = "/var/www/certs/wildcard/cert.pem";
|
||||||
# serverAliases = [ "nixcache" ];
|
sslCertificateKey = "/var/www/certs/wildcard/key.pem";
|
||||||
locations."/".extraConfig = ''
|
# sslCertificate = "/var/lib/acme/.minica/cert.pem";
|
||||||
proxy_pass http://localhost:${toString config.services.nix-serve.port};
|
# sslCertificateKey = "/var/lib/acme/.minica/key.pem";
|
||||||
proxy_set_header Host $host;
|
# serverName = null;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
locations."/" = {
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
# somehow this doesn't escape -- i get error 400 if i:
|
||||||
'';
|
# curl 'http://..' --resolve '..:80:127.0.0.1'
|
||||||
|
root = "/var/www/sites/$domain";
|
||||||
|
# tryFiles = "$domain/$uri $domain/$uri/ =404";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
security.acme.acceptTerms = true;
|
security.acme.acceptTerms = true;
|
||||||
security.acme.defaults.email = "admin.acme@uninsane.org";
|
security.acme.defaults.email = "admin.acme@uninsane.org";
|
||||||
|
|
||||||
users.users.acme.uid = config.sane.allocations.acme-uid;
|
sane.persist.sys.plaintext = [
|
||||||
users.groups.acme.gid = config.sane.allocations.acme-gid;
|
|
||||||
sane.impermanence.service-dirs = [
|
|
||||||
# TODO: mode?
|
# TODO: mode?
|
||||||
{ user = "acme"; group = "acme"; directory = "/var/lib/acme"; }
|
{ user = "acme"; group = "acme"; directory = "/var/lib/acme"; }
|
||||||
|
{ user = "colin"; group = "users"; directory = "/var/www/sites"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# let's encrypt default chain looks like:
|
||||||
|
# - End-entity certificate ← R3 ← ISRG Root X1 ← DST Root CA X3
|
||||||
|
# - <https://community.letsencrypt.org/t/production-chain-changes/150739>
|
||||||
|
# DST Root CA X3 expired in 2021 (?)
|
||||||
|
# the alternative chain is:
|
||||||
|
# - End-entity certificate ← R3 ← ISRG Root X1 (self-signed)
|
||||||
|
# using this alternative chain grants more compatibility for services like ejabberd
|
||||||
|
# but might decrease compatibility with very old clients that don't get updates (e.g. old android, iphone <= 4).
|
||||||
|
# security.acme.defaults.extraLegoFlags = [
|
||||||
|
security.acme.certs."uninsane.org" = rec {
|
||||||
|
# ISRG Root X1 results in lets encrypt sending the same chain as default,
|
||||||
|
# just without the final ISRG Root X1 ← DST Root CA X3 link.
|
||||||
|
# i.e. we could alternative clip the last item and achieve the exact same thing.
|
||||||
|
extraLegoRunFlags = [
|
||||||
|
"--preferred-chain" "ISRG Root X1"
|
||||||
|
];
|
||||||
|
extraLegoRenewFlags = extraLegoRunFlags;
|
||||||
|
};
|
||||||
|
# TODO: alternatively, we could clip the last cert IF it's expired,
|
||||||
|
# optionally outputting that to a new cert file.
|
||||||
|
# security.acme.defaults.postRun = "";
|
||||||
|
|
||||||
|
# create a self-signed SSL certificate for use with literally any domain.
|
||||||
|
# browsers will reject this, but proxies and local testing tools can be configured
|
||||||
|
# to accept it.
|
||||||
|
system.activationScripts.generate-x509-self-signed.text = ''
|
||||||
|
mkdir -p /var/www/certs/wildcard
|
||||||
|
test -f /var/www/certs/wildcard/key.pem || ${pkgs.openssl}/bin/openssl \
|
||||||
|
req -x509 -newkey rsa:4096 \
|
||||||
|
-keyout /var/www/certs/wildcard/key.pem \
|
||||||
|
-out /var/www/certs/wildcard/cert.pem \
|
||||||
|
-sha256 -nodes -days 3650 \
|
||||||
|
-addext 'subjectAltName=DNS:*' \
|
||||||
|
-subj '/CN=self-signed'
|
||||||
|
chmod 640 /var/www/certs/wildcard/{key,cert}.pem
|
||||||
|
chown root:nginx /var/www/certs/wildcard /var/www/certs/wildcard/{key,cert}.pem
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
|
21
hosts/servo/services/nixserve.nix
Normal file
21
hosts/servo/services/nixserve.nix
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{ config, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
services.nginx.virtualHosts."nixcache.uninsane.org" = {
|
||||||
|
addSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
# serverAliases = [ "nixcache" ];
|
||||||
|
locations."/".extraConfig = ''
|
||||||
|
proxy_pass http://localhost:${toString config.services.nix-serve.port};
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."nixcache" = "native";
|
||||||
|
|
||||||
|
sane.services.nixserve.enable = true;
|
||||||
|
sane.services.nixserve.sopsFile = ../../../secrets/servo.yaml;
|
||||||
|
}
|
@@ -6,12 +6,10 @@
|
|||||||
{ config, pkgs, ... }:
|
{ config, pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? could be more granular
|
# TODO: mode? could be more granular
|
||||||
{ user = "pleroma"; group = "pleroma"; directory = "/var/lib/pleroma"; }
|
{ user = "pleroma"; group = "pleroma"; directory = "/var/lib/pleroma"; }
|
||||||
];
|
];
|
||||||
users.users.pleroma.uid = config.sane.allocations.pleroma-uid;
|
|
||||||
users.groups.pleroma.gid = config.sane.allocations.pleroma-gid;
|
|
||||||
services.pleroma.enable = true;
|
services.pleroma.enable = true;
|
||||||
services.pleroma.secretConfigFile = config.sops.secrets.pleroma_secrets.path;
|
services.pleroma.secretConfigFile = config.sops.secrets.pleroma_secrets.path;
|
||||||
services.pleroma.configs = [
|
services.pleroma.configs = [
|
||||||
@@ -127,6 +125,7 @@
|
|||||||
systemd.services.pleroma.serviceConfig = {
|
systemd.services.pleroma.serviceConfig = {
|
||||||
# postgres can be slow to service early requests, preventing pleroma from starting on the first try
|
# postgres can be slow to service early requests, preventing pleroma from starting on the first try
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
|
RestartSec = "10s";
|
||||||
};
|
};
|
||||||
|
|
||||||
# systemd.services.pleroma.serviceConfig = {
|
# systemd.services.pleroma.serviceConfig = {
|
||||||
@@ -136,6 +135,50 @@
|
|||||||
# CapabilityBoundingSet = lib.mkForce "~";
|
# CapabilityBoundingSet = lib.mkForce "~";
|
||||||
# };
|
# };
|
||||||
|
|
||||||
|
# Pleroma server and web interface
|
||||||
|
# TODO: enable publog?
|
||||||
|
services.nginx.virtualHosts."fed.uninsane.org" = {
|
||||||
|
forceSSL = true; # pleroma redirects to https anyway
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:4000";
|
||||||
|
# documented: https://git.pleroma.social/pleroma/pleroma/-/blob/develop/installation/pleroma.nginx
|
||||||
|
extraConfig = ''
|
||||||
|
# XXX colin: this block is in the nixos examples: i don't understand all of it
|
||||||
|
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
|
||||||
|
add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
|
||||||
|
if ($request_method = OPTIONS) {
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
add_header X-Permitted-Cross-Domain-Policies none;
|
||||||
|
add_header X-Frame-Options DENY;
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
add_header Referrer-Policy same-origin;
|
||||||
|
add_header X-Download-Options noopen;
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
# proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
# colin: added this due to Pleroma complaining in its logs
|
||||||
|
# proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
client_max_body_size 16m;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."fed" = "native";
|
||||||
|
|
||||||
sops.secrets.pleroma_secrets = {
|
sops.secrets.pleroma_secrets = {
|
||||||
sopsFile = ../../../secrets/servo.yaml;
|
sopsFile = ../../../secrets/servo.yaml;
|
||||||
owner = config.users.users.pleroma.name;
|
owner = config.users.users.pleroma.name;
|
||||||
|
@@ -16,7 +16,7 @@ let
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? could be more granular
|
# TODO: mode? could be more granular
|
||||||
{ user = "opendkim"; group = "opendkim"; directory = "/var/lib/opendkim"; }
|
{ user = "opendkim"; group = "opendkim"; directory = "/var/lib/opendkim"; }
|
||||||
{ user = "root"; group = "root"; directory = "/var/lib/postfix"; }
|
{ user = "root"; group = "root"; directory = "/var/lib/postfix"; }
|
||||||
@@ -25,6 +25,61 @@ in
|
|||||||
# "/var/lib/dhparams" # https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/dhparams.nix
|
# "/var/lib/dhparams" # https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/dhparams.nix
|
||||||
# "/var/lib/dovecot"
|
# "/var/lib/dovecot"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
25 # SMTP
|
||||||
|
143 # IMAP
|
||||||
|
465 # SMTPS
|
||||||
|
587 # SMTPS/submission
|
||||||
|
993 # IMAPS
|
||||||
|
];
|
||||||
|
|
||||||
|
# exists only to manage certs for dovecot
|
||||||
|
services.nginx.virtualHosts."imap.uninsane.org" = {
|
||||||
|
enableACME = true;
|
||||||
|
};
|
||||||
|
# exists only to manage certs for Postfix
|
||||||
|
services.nginx.virtualHosts."mx.uninsane.org" = {
|
||||||
|
enableACME = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||||
|
MX."@" = "10 mx.uninsane.org.";
|
||||||
|
# XXX: RFC's specify that the MX record CANNOT BE A CNAME
|
||||||
|
A."mx" = "185.157.162.178";
|
||||||
|
CNAME."imap" = "native";
|
||||||
|
|
||||||
|
# Sender Policy Framework:
|
||||||
|
# +mx => mail passes if it originated from the MX
|
||||||
|
# +a => mail passes if it originated from the A address of this domain
|
||||||
|
# +ip4:.. => mail passes if it originated from this IP
|
||||||
|
# -all => mail fails if none of these conditions were met
|
||||||
|
TXT."@" = "v=spf1 a mx -all";
|
||||||
|
|
||||||
|
# DKIM public key:
|
||||||
|
TXT."mx._domainkey" =
|
||||||
|
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkSyMufc2KrRx3j17e/LyB+3eYSBRuEFT8PUka8EDX04QzCwDPdkwgnj3GNDvnB5Ktb05Cf2SJ/S1OLqNsINxJRWtkVfZd/C339KNh9wrukMKRKNELL9HLUw0bczOI4gKKFqyrRE9qm+4csCMAR79Te9FCjGV/jVnrkLdPT0GtFwIDAQAB"
|
||||||
|
;
|
||||||
|
|
||||||
|
# DMARC fields <https://datatracker.ietf.org/doc/html/rfc7489>:
|
||||||
|
# p=none|quarantine|reject: what to do with failures
|
||||||
|
# sp = p but for subdomains
|
||||||
|
# rua = where to send aggregrate reports
|
||||||
|
# ruf = where to send individual failure reports
|
||||||
|
# fo=0|1|d|s controls WHEN to send failure reports
|
||||||
|
# (1=on bad alignment; d=on DKIM failure; s=on SPF failure);
|
||||||
|
# Additionally:
|
||||||
|
# adkim=r|s (is DKIM relaxed [default] or strict)
|
||||||
|
# aspf=r|s (is SPF relaxed [default] or strict)
|
||||||
|
# pct = sampling ratio for punishing failures (default 100 for 100%)
|
||||||
|
# rf = report format
|
||||||
|
# ri = report interval
|
||||||
|
TXT."_dmarc" =
|
||||||
|
"v=DMARC1;p=quarantine;sp=reject;rua=mailto:admin+mail@uninsane.org;ruf=mailto:admin+mail@uninsane.org;fo=1:d:s"
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
services.postfix.enable = true;
|
services.postfix.enable = true;
|
||||||
services.postfix.hostname = "mx.uninsane.org";
|
services.postfix.hostname = "mx.uninsane.org";
|
||||||
services.postfix.origin = "uninsane.org";
|
services.postfix.origin = "uninsane.org";
|
||||||
@@ -55,7 +110,8 @@ in
|
|||||||
services.postfix.enableSubmissions = true;
|
services.postfix.enableSubmissions = true;
|
||||||
services.postfix.submissionsOptions = submissionOptions;
|
services.postfix.submissionsOptions = submissionOptions;
|
||||||
|
|
||||||
systemd.services.postfix.after = [ "wg0veth.service" ];
|
systemd.services.postfix.after = [ "wireguard-wg0.service" ];
|
||||||
|
systemd.services.postfix.partOf = [ "wireguard-wg0.service" ];
|
||||||
systemd.services.postfix.serviceConfig = {
|
systemd.services.postfix.serviceConfig = {
|
||||||
# run this behind the OVPN static VPN
|
# run this behind the OVPN static VPN
|
||||||
NetworkNamespacePath = "/run/netns/ovpns";
|
NetworkNamespacePath = "/run/netns/ovpns";
|
||||||
@@ -76,7 +132,8 @@ in
|
|||||||
# keeping this the same as the hostname seems simplest
|
# keeping this the same as the hostname seems simplest
|
||||||
services.opendkim.selector = "mx";
|
services.opendkim.selector = "mx";
|
||||||
|
|
||||||
systemd.services.opendkim.after = [ "wg0veth.service" ];
|
systemd.services.opendkim.after = [ "wireguard-wg0.service" ];
|
||||||
|
systemd.services.opendkim.partOf = [ "wireguard-wg0.service" ];
|
||||||
systemd.services.opendkim.serviceConfig = {
|
systemd.services.opendkim.serviceConfig = {
|
||||||
# run this behind the OVPN static VPN
|
# run this behind the OVPN static VPN
|
||||||
NetworkNamespacePath = "/run/netns/ovpns";
|
NetworkNamespacePath = "/run/netns/ovpns";
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode?
|
# TODO: mode?
|
||||||
{ user = "postgres"; group = "postgres"; directory = "/var/lib/postgresql"; }
|
{ user = "postgres"; group = "postgres"; directory = "/var/lib/postgresql"; }
|
||||||
];
|
];
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
# example configs:
|
||||||
|
# - <https://github.com/kittywitch/nixfiles/blob/main/services/prosody.nix>
|
||||||
# create users with:
|
# create users with:
|
||||||
# - `sudo -u prosody prosodyctl adduser colin@uninsane.org`
|
# - `sudo -u prosody prosodyctl adduser colin@uninsane.org`
|
||||||
|
|
||||||
@@ -7,13 +9,13 @@
|
|||||||
# nixnet runs ejabberd, so revisiting that.
|
# nixnet runs ejabberd, so revisiting that.
|
||||||
lib.mkIf false
|
lib.mkIf false
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "prosody"; group = "prosody"; directory = "/var/lib/prosody"; }
|
{ user = "prosody"; group = "prosody"; directory = "/var/lib/prosody"; }
|
||||||
];
|
];
|
||||||
networking.firewall.allowedTCPPorts = [
|
networking.firewall.allowedTCPPorts = [
|
||||||
5222 # XMPP client -> server
|
5222 # XMPP client -> server
|
||||||
5269 # XMPP server -> server
|
5269 # XMPP server -> server
|
||||||
5280 # Prosody HTTP port (necessary?)
|
5280 # bosh
|
||||||
5281 # Prosody HTTPS port (necessary?)
|
5281 # Prosody HTTPS port (necessary?)
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ lib.mkIf false
|
|||||||
# c2s_require_encryption = true
|
# c2s_require_encryption = true
|
||||||
# '';
|
# '';
|
||||||
|
|
||||||
# extraModules = [ "private" "vcard" "privacy" "compression" "component" "muc" "pep" "adhoc" "lastactivity" "admin_adhoc" "blocklist"];
|
extraModules = [ "private" "vcard" "privacy" "compression" "component" "muc" "pep" "adhoc" "lastactivity" "admin_adhoc" "blocklist"];
|
||||||
|
|
||||||
ssl.cert = "/var/lib/acme/uninsane.org/fullchain.pem";
|
ssl.cert = "/var/lib/acme/uninsane.org/fullchain.pem";
|
||||||
ssl.key = "/var/lib/acme/uninsane.org/key.pem";
|
ssl.key = "/var/lib/acme/uninsane.org/key.pem";
|
||||||
@@ -51,7 +53,7 @@ lib.mkIf false
|
|||||||
domain = "localhost";
|
domain = "localhost";
|
||||||
enabled = true;
|
enabled = true;
|
||||||
};
|
};
|
||||||
"uninsane.org" = {
|
"xmpp.uninsane.org" = {
|
||||||
domain = "uninsane.org";
|
domain = "uninsane.org";
|
||||||
enabled = true;
|
enabled = true;
|
||||||
ssl.cert = "/var/lib/acme/uninsane.org/fullchain.pem";
|
ssl.cert = "/var/lib/acme/uninsane.org/fullchain.pem";
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ ... }:
|
{ pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.service-dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? we need this specifically for the stats tracking in .config/
|
# TODO: mode? we need this specifically for the stats tracking in .config/
|
||||||
{ user = "transmission"; group = "transmission"; directory = "/var/lib/transmission"; }
|
{ user = "transmission"; group = "transmission"; directory = "/var/lib/transmission"; }
|
||||||
];
|
];
|
||||||
@@ -40,11 +40,41 @@
|
|||||||
# transmission will by default not allow the world to read its files.
|
# transmission will by default not allow the world to read its files.
|
||||||
services.transmission.downloadDirPermissions = "775";
|
services.transmission.downloadDirPermissions = "775";
|
||||||
|
|
||||||
systemd.services.transmission.after = ["wg0veth.service"];
|
systemd.services.transmission.after = [ "wireguard-wg0.service" ];
|
||||||
|
systemd.services.transmission.partOf = [ "wireguard-wg0.service" ];
|
||||||
systemd.services.transmission.serviceConfig = {
|
systemd.services.transmission.serviceConfig = {
|
||||||
# run this behind the OVPN static VPN
|
# run this behind the OVPN static VPN
|
||||||
NetworkNamespacePath = "/run/netns/ovpns";
|
NetworkNamespacePath = "/run/netns/ovpns";
|
||||||
LogLevelMax = "warning";
|
LogLevelMax = "warning";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# service to automatically backup torrents i add to transmission
|
||||||
|
systemd.services.backup-torrents = {
|
||||||
|
description = "archive torrents to storage not owned by transmission";
|
||||||
|
script = ''
|
||||||
|
${pkgs.rsync}/bin/rsync -arv /var/lib/transmission/.config/transmission-daemon/torrents/ /var/backup/torrents/
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
systemd.timers.backup-torrents = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnStartupSec = "11min";
|
||||||
|
OnUnitActiveSec = "240min";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# transmission web client
|
||||||
|
services.nginx.virtualHosts."bt.uninsane.org" = {
|
||||||
|
# basicAuth is literally cleartext user/pw, so FORCE this to happen over SSL
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
locations."/" = {
|
||||||
|
# proxyPass = "http://ovpns.uninsane.org:9091";
|
||||||
|
proxyPass = "http://10.0.1.6:9091";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."bt" = "native";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
66
hosts/servo/services/trust-dns.nix
Normal file
66
hosts/servo/services/trust-dns.nix
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{ config, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
sane.services.trust-dns.enable = true;
|
||||||
|
|
||||||
|
sane.services.trust-dns.listenAddrsIPv4 = [
|
||||||
|
# specify each address explicitly, instead of using "*".
|
||||||
|
# this ensures responses are sent from the address at which the request was received.
|
||||||
|
"192.168.0.5"
|
||||||
|
"10.0.1.5"
|
||||||
|
];
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".TTL = 900;
|
||||||
|
|
||||||
|
# SOA record structure: <https://en.wikipedia.org/wiki/SOA_record#Structure>
|
||||||
|
# SOA MNAME RNAME (... rest)
|
||||||
|
# MNAME = Master name server for this zone. this is where update requests should be sent.
|
||||||
|
# RNAME = admin contact (encoded email address)
|
||||||
|
# Serial = YYYYMMDDNN, where N is incremented every time this file changes, to trigger secondary NS to re-fetch it.
|
||||||
|
# Refresh = how frequently secondary NS should query master
|
||||||
|
# Retry = how long secondary NS should wait until re-querying master after a failure (must be < Refresh)
|
||||||
|
# Expire = how long secondary NS should continue to reply to queries after master fails (> Refresh + Retry)
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||||
|
SOA."@" = ''
|
||||||
|
ns1.uninsane.org. admin-dns.uninsane.org. (
|
||||||
|
2022122101 ; Serial
|
||||||
|
4h ; Refresh
|
||||||
|
30m ; Retry
|
||||||
|
7d ; Expire
|
||||||
|
5m) ; Negative response TTL
|
||||||
|
'';
|
||||||
|
TXT."rev" = "2022122101";
|
||||||
|
|
||||||
|
# XXX NS records must also not be CNAME
|
||||||
|
# it's best that we keep this identical, or a superset of, what org. lists as our NS.
|
||||||
|
# so, org. can specify ns2/ns3 as being to the VPN, with no mention of ns1. we provide ns1 here.
|
||||||
|
A."ns1" = "%NATIVE%";
|
||||||
|
A."ns2" = "185.157.162.178";
|
||||||
|
A."ns3" = "185.157.162.178";
|
||||||
|
A."ovpns" = "185.157.162.178";
|
||||||
|
A."native" = "%NATIVE%";
|
||||||
|
A."@" = "%NATIVE%";
|
||||||
|
NS."@" = [
|
||||||
|
"ns1.uninsane.org."
|
||||||
|
"ns2.uninsane.org."
|
||||||
|
"ns3.uninsane.org."
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.services.trust-dns.zones."uninsane.org".file =
|
||||||
|
"/var/lib/trust-dns/uninsane.org.zone";
|
||||||
|
|
||||||
|
systemd.services.trust-dns.preStart = let
|
||||||
|
sed = "${pkgs.gnused}/bin/sed";
|
||||||
|
zone-dir = "/var/lib/trust-dns";
|
||||||
|
zone-out = "${zone-dir}/uninsane.org.zone";
|
||||||
|
zone-template = pkgs.writeText "uninsane.org.zone.in" config.sane.services.trust-dns.generatedZones."uninsane.org";
|
||||||
|
in ''
|
||||||
|
# make WAN records available to trust-dns
|
||||||
|
mkdir -p ${zone-dir}
|
||||||
|
ip=$(cat '${config.sane.services.dyn-dns.ipPath}')
|
||||||
|
${sed} s/%NATIVE%/$ip/ ${zone-template} > ${zone-out}
|
||||||
|
'';
|
||||||
|
|
||||||
|
sane.services.dyn-dns.restartOnChange = [ "trust-dns.service" ];
|
||||||
|
}
|
31
hosts/servo/services/wikipedia.nix
Normal file
31
hosts/servo/services/wikipedia.nix
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# docs: <https://nixos.wiki/wiki/MediaWiki>
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
# XXX: working to host wikipedia with kiwix instead of mediawiki
|
||||||
|
# mediawiki does more than i need and isn't obviously superior in any way
|
||||||
|
# except that the dumps are more frequent/up-to-date.
|
||||||
|
lib.mkIf false
|
||||||
|
{
|
||||||
|
sops.secrets."mediawiki_pw" = {
|
||||||
|
owner = config.users.users.mediawiki.name;
|
||||||
|
sopsFile = ../../../secrets/servo.yaml;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.mediawiki.enable = true;
|
||||||
|
services.mediawiki.name = "Uninsane Wiki";
|
||||||
|
services.mediawiki.passwordFile = config.sops.secrets.mediawiki_pw.path;
|
||||||
|
services.mediawiki.extraConfig = ''
|
||||||
|
# Disable anonymous editing
|
||||||
|
$wgGroupPermissions['*']['edit'] = false;
|
||||||
|
'';
|
||||||
|
services.mediawiki.virtualHost.listen = [
|
||||||
|
{
|
||||||
|
ip = "127.0.0.1";
|
||||||
|
port = 8013;
|
||||||
|
ssl = false;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
services.mediawiki.virtualHost.hostName = "w.uninsane.org";
|
||||||
|
services.mediawiki.virtualHost.adminAddr = "admin+mediawiki@uninsane.org";
|
||||||
|
# services.mediawiki.extensions = TODO: wikipedia sync extension?
|
||||||
|
}
|
@@ -12,7 +12,6 @@
|
|||||||
home = "/var/lib/gitea";
|
home = "/var/lib/gitea";
|
||||||
useDefaultShell = true;
|
useDefaultShell = true;
|
||||||
group = "gitea";
|
group = "gitea";
|
||||||
uid = config.sane.allocations.git-uid;
|
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
# sendmail access (not 100% sure if this is necessary)
|
# sendmail access (not 100% sure if this is necessary)
|
||||||
extraGroups = [ "postdrop" ];
|
extraGroups = [ "postdrop" ];
|
||||||
|
@@ -1,61 +0,0 @@
|
|||||||
{ lib, ... }:
|
|
||||||
|
|
||||||
with lib;
|
|
||||||
let
|
|
||||||
mkId = id: mkOption {
|
|
||||||
default = id;
|
|
||||||
type = types.int;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
# legacy servo users, some are inconvenient to migrate
|
|
||||||
sane.allocations.dhcpcd-gid = mkId 991;
|
|
||||||
sane.allocations.dhcpcd-uid = mkId 992;
|
|
||||||
sane.allocations.gitea-gid = mkId 993;
|
|
||||||
sane.allocations.git-uid = mkId 994;
|
|
||||||
sane.allocations.jellyfin-gid = mkId 994;
|
|
||||||
sane.allocations.pleroma-gid = mkId 995;
|
|
||||||
sane.allocations.jellyfin-uid = mkId 996;
|
|
||||||
sane.allocations.acme-gid = mkId 996;
|
|
||||||
sane.allocations.pleroma-uid = mkId 997;
|
|
||||||
sane.allocations.acme-uid = mkId 998;
|
|
||||||
sane.allocations.greeter-uid = mkId 999;
|
|
||||||
sane.allocations.greeter-gid = mkId 999;
|
|
||||||
|
|
||||||
sane.allocations.freshrss-uid = mkId 2401;
|
|
||||||
sane.allocations.freshrss-gid = mkId 2401;
|
|
||||||
|
|
||||||
sane.allocations.colin-uid = mkId 1000;
|
|
||||||
sane.allocations.guest-uid = mkId 1100;
|
|
||||||
|
|
||||||
# found on all hosts
|
|
||||||
sane.allocations.sshd-uid = mkId 2001; # 997
|
|
||||||
sane.allocations.sshd-gid = mkId 2001; # 997
|
|
||||||
sane.allocations.polkituser-gid = mkId 2002; # 998
|
|
||||||
sane.allocations.systemd-coredump-gid = mkId 2003; # 996
|
|
||||||
sane.allocations.nscd-uid = mkId 2004;
|
|
||||||
sane.allocations.nscd-gid = mkId 2004;
|
|
||||||
sane.allocations.systemd-oom-uid = mkId 2005;
|
|
||||||
sane.allocations.systemd-oom-gid = mkId 2005;
|
|
||||||
|
|
||||||
# found on graphical hosts
|
|
||||||
sane.allocations.nm-iodine-uid = mkId 2101; # desko/moby/lappy
|
|
||||||
|
|
||||||
# found on desko host
|
|
||||||
sane.allocations.usbmux-uid = mkId 2204;
|
|
||||||
sane.allocations.usbmux-gid = mkId 2204;
|
|
||||||
|
|
||||||
|
|
||||||
# originally found on moby host
|
|
||||||
sane.allocations.avahi-uid = mkId 2304;
|
|
||||||
sane.allocations.avahi-gid = mkId 2304;
|
|
||||||
sane.allocations.colord-uid = mkId 2305;
|
|
||||||
sane.allocations.colord-gid = mkId 2305;
|
|
||||||
sane.allocations.geoclue-uid = mkId 2306;
|
|
||||||
sane.allocations.geoclue-gid = mkId 2306;
|
|
||||||
sane.allocations.rtkit-uid = mkId 2307;
|
|
||||||
sane.allocations.rtkit-gid = mkId 2307;
|
|
||||||
sane.allocations.feedbackd-gid = mkId 2308;
|
|
||||||
};
|
|
||||||
}
|
|
12
modules/data/default.nix
Normal file
12
modules/data/default.nix
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# this directory contains data of a factual nature.
|
||||||
|
# for example, public ssh keys, GPG keys, DNS-type name mappings.
|
||||||
|
#
|
||||||
|
# don't put things like fully-specific ~/.config files in here,
|
||||||
|
# even if they're "relatively unopinionated".
|
||||||
|
|
||||||
|
moduleArgs:
|
||||||
|
|
||||||
|
{
|
||||||
|
feeds = import ./feeds moduleArgs;
|
||||||
|
keys = import ./keys.nix;
|
||||||
|
}
|
58
modules/data/feeds/default.nix
Normal file
58
modules/data/feeds/default.nix
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (builtins) concatLists concatStringsSep foldl' fromJSON map readDir readFile;
|
||||||
|
inherit (lib) init mapAttrsToList removePrefix removeSuffix splitString;
|
||||||
|
inherit (lib.attrsets) recursiveUpdate setAttrByPath;
|
||||||
|
inherit (lib.filesystem) listFilesRecursive;
|
||||||
|
|
||||||
|
# given a path to a .json file relative to sources, construct the best feed object we can.
|
||||||
|
# the .json file could be empty, in which case we make assumptions about the feed based
|
||||||
|
# on its fs path.
|
||||||
|
# Type: feedFromSourcePath :: String -> { path = [String]; value = feed; }
|
||||||
|
feedFromSourcePath = json-path:
|
||||||
|
let
|
||||||
|
canonical-name = removeSuffix "/default" (lib.removeSuffix ".json" json-path);
|
||||||
|
default-url = "https://${canonical-name}";
|
||||||
|
attr-path = splitString "/" canonical-name;
|
||||||
|
feed-details = { url = default-url; } // (tryImportJson (./sources/${json-path}));
|
||||||
|
in { path = attr-path; value = mkFeed feed-details; };
|
||||||
|
|
||||||
|
# TODO: for now, feeds are just ordinary Attrs.
|
||||||
|
# in the future, we'd like to set them up with an update script.
|
||||||
|
mkFeed = { url, ... }@details: details;
|
||||||
|
|
||||||
|
# return an AttrSet representing the json at the provided path,
|
||||||
|
# or {} if the path is empty.
|
||||||
|
tryImportJson = path:
|
||||||
|
let
|
||||||
|
as-str = readFile path;
|
||||||
|
in
|
||||||
|
if as-str == "" then
|
||||||
|
{}
|
||||||
|
else
|
||||||
|
fromJSON as-str;
|
||||||
|
|
||||||
|
sources = enumerateFilePaths ./sources;
|
||||||
|
|
||||||
|
# like `lib.listFilesRecursive` but does not mangle paths.
|
||||||
|
# Type: enumerateFilePaths :: path -> [String]
|
||||||
|
enumerateFilePaths = base:
|
||||||
|
concatLists (
|
||||||
|
mapAttrsToList
|
||||||
|
(name: type:
|
||||||
|
if type == "directory" then
|
||||||
|
# enumerate this directory and then prefix each result with the directory's name
|
||||||
|
map (e: "${name}/${e}") (enumerateFilePaths (base + "/${name}"))
|
||||||
|
else
|
||||||
|
[ name ]
|
||||||
|
)
|
||||||
|
(readDir base)
|
||||||
|
);
|
||||||
|
|
||||||
|
# like listToAttrs, except takes { path, value } pairs instead of { name, value } pairs.
|
||||||
|
# Type: listToAttrsByPath :: [{ path = [String]; value = Any; }] -> Attrs
|
||||||
|
listToAttrsByPath = items:
|
||||||
|
foldl' (acc: { path, value }: recursiveUpdate acc (setAttrByPath path value)) {} items;
|
||||||
|
in
|
||||||
|
listToAttrsByPath (map feedFromSourcePath sources)
|
0
modules/data/feeds/sources/xkcd.com/default.json
Normal file
0
modules/data/feeds/sources/xkcd.com/default.json
Normal file
24
modules/data/keys.nix
Normal file
24
modules/data/keys.nix
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# hierarchical, DNS-like mapping from <name> => ssh host/user for that name.
|
||||||
|
# host keys are represented as user keys, just with the user specified as "root".
|
||||||
|
|
||||||
|
{
|
||||||
|
org.uninsane = rec {
|
||||||
|
root = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOfdSmFkrVT6DhpgvFeQKm3Fh9VKZ9DbLYOPOJWYQ0E8";
|
||||||
|
git.root = root;
|
||||||
|
|
||||||
|
local = {
|
||||||
|
# machine aliases i specify on my lan; not actually asserted as DNS
|
||||||
|
desko.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPU5GlsSfbaarMvDA20bxpSZGWviEzXGD8gtrIowc1pX";
|
||||||
|
desko.root = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFw9NoRaYrM6LbDd3aFBc4yyBlxGQn8HjeHd/dZ3CfHk";
|
||||||
|
|
||||||
|
lappy.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDpmFdNSVPRol5hkbbCivRhyeENzb9HVyf9KutGLP2Zu";
|
||||||
|
lappy.root = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILSJnqmVl9/SYQ0btvGb0REwwWY8wkdkGXQZfn/1geEc";
|
||||||
|
|
||||||
|
moby.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrR+gePnl0nV/vy7I5BzrGeyVL+9eOuXHU1yNE3uCwU";
|
||||||
|
moby.root = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1N/IT3nQYUD+dBlU1sTEEVMxfOyMkrrDeyHcYgnJvw";
|
||||||
|
|
||||||
|
servo.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPS1qFzKurAdB9blkWomq8gI1g0T3sTs9LsmFOj5VtqX";
|
||||||
|
servo.root = root;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
@@ -1,14 +1,23 @@
|
|||||||
{ ... }:
|
{ lib, utils, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./allocations.nix
|
./feeds.nix
|
||||||
|
./fs
|
||||||
./gui
|
./gui
|
||||||
./home-manager
|
./home-manager
|
||||||
|
./ids.nix
|
||||||
./packages.nix
|
./packages.nix
|
||||||
./image.nix
|
./image.nix
|
||||||
./impermanence.nix
|
|
||||||
./nixcache.nix
|
./nixcache.nix
|
||||||
|
./persist
|
||||||
./services
|
./services
|
||||||
|
./sops.nix
|
||||||
|
./ssh.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
_module.args = {
|
||||||
|
sane-lib = import ./lib { inherit lib utils; };
|
||||||
|
sane-data = import ./data { inherit lib; };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
51
modules/feeds.nix
Normal file
51
modules/feeds.nix
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
feed = types.submodule ({ config, ... }: {
|
||||||
|
options = {
|
||||||
|
freq = mkOption {
|
||||||
|
type = types.enum [ "hourly" "daily" "weekly" "infrequent" ];
|
||||||
|
default = "infrequent";
|
||||||
|
};
|
||||||
|
cat = mkOption {
|
||||||
|
type = types.enum [ "art" "humor" "pol" "rat" "tech" "uncat" ];
|
||||||
|
default = "uncat";
|
||||||
|
};
|
||||||
|
format = mkOption {
|
||||||
|
type = types.enum [ "text" "image" "podcast" ];
|
||||||
|
default = "text";
|
||||||
|
};
|
||||||
|
url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
url to a RSS feed
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
substack = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
if the feed is a substack domain, just enter the subdomain here and the url/format field can be populated automatically
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = lib.mkIf (config.substack != null) {
|
||||||
|
url = "https://${config.substack}.substack.com/feed";
|
||||||
|
format = "text";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# we don't explicitly generate anything from the feeds here.
|
||||||
|
# instead, config.sane.feeds is used by a variety of services at their definition site.
|
||||||
|
options = {
|
||||||
|
sane.feeds = mkOption {
|
||||||
|
type = types.listOf feed;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
RSS feeds indexed by a human-readable name.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
357
modules/fs/default.nix
Normal file
357
modules/fs/default.nix
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
{ config, lib, pkgs, utils, sane-lib, ... }:
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
path-lib = sane-lib.path;
|
||||||
|
sane-types = sane-lib.types;
|
||||||
|
cfg = config.sane.fs;
|
||||||
|
|
||||||
|
mountNameFor = path: "${utils.escapeSystemdPath path}.mount";
|
||||||
|
serviceNameFor = path: "ensure-${utils.escapeSystemdPath path}";
|
||||||
|
|
||||||
|
# sane.fs."<path>" top-level options
|
||||||
|
fsEntry = types.submodule ({ name, config, ...}: let
|
||||||
|
parent = path-lib.parent name;
|
||||||
|
has-parent = path-lib.hasParent name;
|
||||||
|
parent-cfg = if has-parent then cfg."${parent}" else {};
|
||||||
|
parent-acl = if has-parent then parent-cfg.generated.acl else {};
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
dir = mkOption {
|
||||||
|
type = types.nullOr dirEntry;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
symlink = mkOption {
|
||||||
|
type = types.nullOr (symlinkEntryFor name);
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
generated = mkOption {
|
||||||
|
type = generatedEntry;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
mount = mkOption {
|
||||||
|
type = types.nullOr (mountEntryFor name);
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
wantedBy = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
list of units or targets which, when activated, should trigger this fs entry to be created.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
wantedBeforeBy = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
list of units or targets which, when activated, should first start and wait for this fs entry to be created.
|
||||||
|
if this unit fails, it will not block the targets in this list.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
unit = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "name of the systemd unit which ensures this entry";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = let
|
||||||
|
default-acl = {
|
||||||
|
user = lib.mkDefault (parent-acl.user or "root");
|
||||||
|
group = lib.mkDefault (parent-acl.group or "root");
|
||||||
|
mode = lib.mkDefault (parent-acl.mode or "0755");
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
# we put this here instead of as a `default` to ensure that users who specify additional
|
||||||
|
# dependencies still get a dep on the parent (unless they assign with `mkForce`).
|
||||||
|
generated.depends = if has-parent then [ parent-cfg.unit ] else [];
|
||||||
|
|
||||||
|
# populate generated items from `dir` or `symlink` shorthands
|
||||||
|
generated.acl = lib.mkMerge [
|
||||||
|
default-acl
|
||||||
|
(lib.mkIf (config.dir != null)
|
||||||
|
(sane-lib.filterNonNull config.dir.acl))
|
||||||
|
(lib.mkIf (config.symlink != null)
|
||||||
|
(sane-lib.filterNonNull config.symlink.acl))
|
||||||
|
];
|
||||||
|
|
||||||
|
# actually generate the item
|
||||||
|
generated.script = lib.mkMerge [
|
||||||
|
(lib.mkIf (config.dir != null) (ensureDirScript name config.dir))
|
||||||
|
(lib.mkIf (config.symlink != null) (ensureSymlinkScript name config.symlink))
|
||||||
|
];
|
||||||
|
|
||||||
|
# make the unit file which generates the underlying thing available so that `mount` can use it.
|
||||||
|
generated.unit = (serviceNameFor name) + ".service";
|
||||||
|
|
||||||
|
# if we were asked to mount, make sure we create the dir that we mount over
|
||||||
|
dir = lib.mkIf (config.mount != null) {};
|
||||||
|
|
||||||
|
# if defaulted, this module is responsible for finalizing the entry.
|
||||||
|
# the user could override this if, say, they finalize some aspect of the entry
|
||||||
|
# with a custom service.
|
||||||
|
unit = lib.mkDefault (
|
||||||
|
if config.mount != null then
|
||||||
|
config.mount.unit
|
||||||
|
else
|
||||||
|
config.generated.unit
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
# options which can be set in dir/symlink generated items,
|
||||||
|
# with intention that they just propagate down
|
||||||
|
propagatedGenerateMod = {
|
||||||
|
options = {
|
||||||
|
acl = mkOption {
|
||||||
|
type = sane-types.aclOverride;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# sane.fs."<path>".dir sub-options
|
||||||
|
# takes no special options
|
||||||
|
dirEntry = types.submodule propagatedGenerateMod;
|
||||||
|
|
||||||
|
symlinkEntryFor = path: types.submodule ({ config, ...}: {
|
||||||
|
options = {
|
||||||
|
inherit (propagatedGenerateMod.options) acl;
|
||||||
|
target = mkOption {
|
||||||
|
type = types.coercedTo types.package toString types.str;
|
||||||
|
description = "fs path to link to";
|
||||||
|
};
|
||||||
|
text = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "create a file in the /nix/store with the provided text and use that as the target";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
target = lib.mkIf (config.text != null) (
|
||||||
|
pkgs.writeText (path-lib.leaf path) config.text
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
generatedEntry = types.submodule {
|
||||||
|
options = {
|
||||||
|
acl = mkOption {
|
||||||
|
type = sane-types.acl;
|
||||||
|
};
|
||||||
|
depends = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
description = ''
|
||||||
|
list of systemd units needed to be run before this item can be generated.
|
||||||
|
'';
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
script.script = mkOption {
|
||||||
|
type = types.lines;
|
||||||
|
};
|
||||||
|
script.scriptArgs = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
unit = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "name of the systemd unit which ensures this directory";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# sane.fs."<path>".mount sub-options
|
||||||
|
mountEntryFor = path: types.submodule {
|
||||||
|
options = {
|
||||||
|
bind = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
description = "fs path to bind-mount from";
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
depends = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
description = ''
|
||||||
|
list of systemd units needed to be run before this entry can be mounted
|
||||||
|
'';
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
unit = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "name of the systemd unit which mounts this path";
|
||||||
|
default = mountNameFor path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mkGeneratedConfig = path: opt: let
|
||||||
|
gen-opt = opt.generated;
|
||||||
|
wrapper = generateWrapperScript path gen-opt;
|
||||||
|
in {
|
||||||
|
systemd.services."${serviceNameFor path}" = {
|
||||||
|
description = "prepare ${path}";
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
|
||||||
|
script = wrapper.script;
|
||||||
|
scriptArgs = builtins.concatStringsSep " " wrapper.scriptArgs;
|
||||||
|
|
||||||
|
after = gen-opt.depends;
|
||||||
|
wants = gen-opt.depends;
|
||||||
|
# prevent systemd making this unit implicitly dependent on sysinit.target.
|
||||||
|
# see: <https://www.freedesktop.org/software/systemd/man/systemd.special.html>
|
||||||
|
unitConfig.DefaultDependencies = "no";
|
||||||
|
|
||||||
|
before = opt.wantedBeforeBy;
|
||||||
|
wantedBy = opt.wantedBy ++ opt.wantedBeforeBy;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# given a mountEntry definition, evaluate its toplevel `config` output.
|
||||||
|
mkMountConfig = path: opt: (let
|
||||||
|
device = config.fileSystems."${path}".device;
|
||||||
|
underlying = cfg."${device}";
|
||||||
|
isBind = opt.mount.bind != null;
|
||||||
|
ifBind = lib.mkIf isBind;
|
||||||
|
# before mounting:
|
||||||
|
# - create the target directory
|
||||||
|
# - prepare the source directory -- assuming it's not an external device
|
||||||
|
# - satisfy any user-specified prerequisites ("depends")
|
||||||
|
requires = [ opt.generated.unit ]
|
||||||
|
++ (if lib.hasPrefix "/dev/disk/" device then [] else [ underlying.unit ])
|
||||||
|
++ opt.mount.depends;
|
||||||
|
in {
|
||||||
|
fileSystems."${path}" = {
|
||||||
|
device = ifBind opt.mount.bind;
|
||||||
|
options = (if isBind then ["bind"] else [])
|
||||||
|
++ [
|
||||||
|
# disable defaults: don't require this to be mount as part of local-fs.target
|
||||||
|
# we'll handle that stuff precisely.
|
||||||
|
"noauto"
|
||||||
|
"nofail"
|
||||||
|
# x-systemd options documented here:
|
||||||
|
# - <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
|
||||||
|
]
|
||||||
|
++ (builtins.map (unit: "x-systemd.requires=${unit}") requires)
|
||||||
|
++ (builtins.map (unit: "x-systemd.before=${unit}") opt.wantedBeforeBy)
|
||||||
|
++ (builtins.map (unit: "x-systemd.wanted-by=${unit}") (opt.wantedBy ++ opt.wantedBeforeBy));
|
||||||
|
noCheck = ifBind true;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
mkFsConfig = path: opt: lib.mkMerge [
|
||||||
|
(mkGeneratedConfig path opt)
|
||||||
|
(lib.mkIf (opt.mount != null) (mkMountConfig path opt))
|
||||||
|
];
|
||||||
|
|
||||||
|
generateWrapperScript = path: gen-opt: {
|
||||||
|
script = ''
|
||||||
|
fspath="$1"
|
||||||
|
acluser="$2"
|
||||||
|
aclgroup="$3"
|
||||||
|
aclmode="$4"
|
||||||
|
shift 4
|
||||||
|
|
||||||
|
# ensure any things created by the user script have the desired mode.
|
||||||
|
# chmod doesn't work on symlinks, so we *have* to use this umask approach.
|
||||||
|
decmask=$(( 0777 - "$aclmode" ))
|
||||||
|
octmask=$(printf "%o" "$decmask")
|
||||||
|
umask "$octmask"
|
||||||
|
|
||||||
|
# try to chmod/chown the result even if the user script errors
|
||||||
|
_status=0
|
||||||
|
trap "_status=\$?" ERR
|
||||||
|
|
||||||
|
${gen-opt.script.script}
|
||||||
|
|
||||||
|
# claim ownership of the new thing (DON'T traverse symlinks)
|
||||||
|
chown --no-dereference "$acluser:$aclgroup" "$fspath"
|
||||||
|
# AS LONG AS IT'S NOT A SYMLINK, try to fix perms in case the entity existed before this script was called
|
||||||
|
if ! test -L "$fspath"
|
||||||
|
then
|
||||||
|
chmod "$aclmode" "$fspath"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit "$_status"
|
||||||
|
'';
|
||||||
|
scriptArgs = [ path gen-opt.acl.user gen-opt.acl.group gen-opt.acl.mode ] ++ gen-opt.script.scriptArgs;
|
||||||
|
};
|
||||||
|
|
||||||
|
# systemd/shell script used to create and set perms for a specific dir
|
||||||
|
ensureDirScript = path: dir-cfg: {
|
||||||
|
script = ''
|
||||||
|
dirpath="$1"
|
||||||
|
|
||||||
|
if ! test -d "$dirpath"
|
||||||
|
then
|
||||||
|
# if the directory *doesn't* exist, try creating it
|
||||||
|
# if we fail to create it, ensure we raced with something else and that it's actually a directory
|
||||||
|
mkdir "$dirpath" || test -d "$dirpath"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
scriptArgs = [ path ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# systemd/shell script used to create a symlink
|
||||||
|
ensureSymlinkScript = path: link-cfg: {
|
||||||
|
script = ''
|
||||||
|
lnfrom="$1"
|
||||||
|
lnto="$2"
|
||||||
|
|
||||||
|
# ln is clever when there's something else at the place we want to create the link
|
||||||
|
# only create the link if nothing's there or what is there is another link,
|
||||||
|
# otherwise you'll get links at unexpected fs locations
|
||||||
|
! test -e "$lnfrom" || test -L "$lnfrom" && ln -sf --no-dereference "$lnto" "$lnfrom"
|
||||||
|
'';
|
||||||
|
scriptArgs = [ path link-cfg.target ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# return all ancestors of this path.
|
||||||
|
# e.g. ancestorsOf "/foo/bar/baz" => [ "/" "/foo" "/foo/bar" ]
|
||||||
|
ancestorsOf = path: lib.init (path-lib.walk "/" path);
|
||||||
|
|
||||||
|
# attrsOf fsEntry type which for every entry ensures that all ancestor entries are created.
|
||||||
|
# we do this with a custom type to ensure that users can access `config.sane.fs."/parent/path"`
|
||||||
|
# when inferred.
|
||||||
|
fsTree = let
|
||||||
|
baseType = types.attrsOf fsEntry;
|
||||||
|
# merge is called once, with all collected `sane.fs` definitions passed and we coalesce those
|
||||||
|
# into a single value `x` as if the user had wrote simply `sane.fs = x` in a single location.
|
||||||
|
# so option defaulting and such happens *after* `merge` is called.
|
||||||
|
merge = loc: defs: let
|
||||||
|
# loc is the location of the option holding this type, e.g. ["sane" "fs"].
|
||||||
|
# each def is an { value = attrsOf fsEntry instance; file = "..."; }
|
||||||
|
pathsForDef = def: attrNames def.value;
|
||||||
|
origPaths = concatLists (builtins.map pathsForDef defs);
|
||||||
|
extraPaths = concatLists (builtins.map ancestorsOf origPaths);
|
||||||
|
extraDefs = builtins.map (p: {
|
||||||
|
file = ./.;
|
||||||
|
value = {
|
||||||
|
"${p}".dir = {};
|
||||||
|
};
|
||||||
|
}) extraPaths;
|
||||||
|
in
|
||||||
|
baseType.merge loc (defs ++ extraDefs);
|
||||||
|
in
|
||||||
|
lib.mkOptionType {
|
||||||
|
inherit merge;
|
||||||
|
name = "fsTree";
|
||||||
|
description = "attrset representation of a file-system tree";
|
||||||
|
# ensure that every path is in canonical form, else we might get duplicates and subtle errors
|
||||||
|
check = tree: builtins.all (p: p == path-lib.norm p) (builtins.attrNames tree);
|
||||||
|
};
|
||||||
|
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
sane.fs = mkOption {
|
||||||
|
# type = types.attrsOf fsEntry;
|
||||||
|
type = fsTree;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config =
|
||||||
|
let
|
||||||
|
configs = lib.mapAttrsToList mkFsConfig cfg;
|
||||||
|
take = f: {
|
||||||
|
systemd.services = f.systemd.services;
|
||||||
|
fileSystems = f.fileSystems;
|
||||||
|
};
|
||||||
|
in take (sane-lib.mkTypedMerge take configs);
|
||||||
|
}
|
@@ -23,7 +23,9 @@ in
|
|||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
sane.packages.enableGuiPkgs = lib.mkDefault true;
|
sane.packages.enableGuiPkgs = lib.mkDefault true;
|
||||||
# all GUIs use network manager?
|
|
||||||
users.users.nm-iodine.uid = config.sane.allocations.nm-iodine-uid;
|
# preserve backlight brightness across power cycles
|
||||||
|
# see `man systemd-backlight`
|
||||||
|
sane.persist.sys.plaintext = [ "/var/lib/systemd/backlight" ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -15,15 +15,6 @@ in
|
|||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
sane.gui.enable = true;
|
sane.gui.enable = true;
|
||||||
|
|
||||||
users.users.avahi.uid = config.sane.allocations.avahi-uid;
|
|
||||||
users.groups.avahi.gid = config.sane.allocations.avahi-gid;
|
|
||||||
users.users.colord.uid = config.sane.allocations.colord-uid;
|
|
||||||
users.groups.colord.gid = config.sane.allocations.colord-gid;
|
|
||||||
users.users.geoclue.uid = config.sane.allocations.geoclue-uid;
|
|
||||||
users.groups.geoclue.gid = config.sane.allocations.geoclue-gid;
|
|
||||||
users.users.rtkit.uid = config.sane.allocations.rtkit-uid;
|
|
||||||
users.groups.rtkit.gid = config.sane.allocations.rtkit-gid;
|
|
||||||
|
|
||||||
# start gnome/gdm on boot
|
# start gnome/gdm on boot
|
||||||
services.xserver.enable = true;
|
services.xserver.enable = true;
|
||||||
services.xserver.desktopManager.gnome.enable = true;
|
services.xserver.desktopManager.gnome.enable = true;
|
||||||
|
@@ -24,16 +24,6 @@ in
|
|||||||
{
|
{
|
||||||
sane.gui.enable = true;
|
sane.gui.enable = true;
|
||||||
|
|
||||||
users.users.avahi.uid = config.sane.allocations.avahi-uid;
|
|
||||||
users.users.colord.uid = config.sane.allocations.colord-uid;
|
|
||||||
users.users.geoclue.uid = config.sane.allocations.geoclue-uid;
|
|
||||||
users.users.rtkit.uid = config.sane.allocations.rtkit-uid;
|
|
||||||
users.groups.avahi.gid = config.sane.allocations.avahi-gid;
|
|
||||||
users.groups.colord.gid = config.sane.allocations.colord-gid;
|
|
||||||
users.groups.feedbackd.gid = config.sane.allocations.feedbackd-gid;
|
|
||||||
users.groups.geoclue.gid = config.sane.allocations.geoclue-gid;
|
|
||||||
users.groups.rtkit.gid = config.sane.allocations.rtkit-gid;
|
|
||||||
|
|
||||||
# docs: https://github.com/NixOS/nixpkgs/blob/nixos-22.05/nixos/modules/services/x11/desktop-managers/phosh.nix
|
# docs: https://github.com/NixOS/nixpkgs/blob/nixos-22.05/nixos/modules/services/x11/desktop-managers/phosh.nix
|
||||||
services.xserver.desktopManager.phosh = {
|
services.xserver.desktopManager.phosh = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
12
modules/gui/snippets.txt
Normal file
12
modules/gui/snippets.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
https://search.nixos.org/options?channel=unstable&query=
|
||||||
|
https://search.nixos.org/packages?channel=unstable&query=
|
||||||
|
https://nixos.wiki/index.php?go=Go&search=
|
||||||
|
https://github.com/nixos/nixpkgs/pulls?q=
|
||||||
|
https://nur.nix-community.org/
|
||||||
|
https://nix-community.github.io/home-manager/options.html
|
||||||
|
https://w.uninsane.org/viewer#search?books.name=wikipedia_en_all_maxi_2022-05&pattern=
|
||||||
|
https://jackett.uninsane.org/UI/Dashboard#search=
|
||||||
|
https://fed.uninsane.org
|
||||||
|
https://bt.uninsane.org
|
||||||
|
https://sci-hub.se
|
||||||
|
https://news.ycombinator.com
|
@@ -22,14 +22,15 @@ in
|
|||||||
};
|
};
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
sane.gui.enable = true;
|
sane.gui.enable = true;
|
||||||
users.users.greeter.uid = config.sane.allocations.greeter-uid;
|
|
||||||
users.groups.greeter.gid = config.sane.allocations.greeter-gid;
|
|
||||||
programs.sway = {
|
programs.sway = {
|
||||||
# we configure sway with home-manager, but this enable gets us e.g. opengl and fonts
|
# we configure sway with home-manager, but this enable gets us e.g. opengl and fonts
|
||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# alternatively, could use SDDM
|
# instead of using `services.greetd`, can instead use SDDM by swapping in these lines.
|
||||||
|
# services.xserver.displayManager.sddm.enable = true;
|
||||||
|
# services.xserver.enable = true;
|
||||||
services.greetd = let
|
services.greetd = let
|
||||||
swayConfig-greeter = pkgs.writeText "greetd-sway-config" ''
|
swayConfig-greeter = pkgs.writeText "greetd-sway-config" ''
|
||||||
# `-l` activates layer-shell mode.
|
# `-l` activates layer-shell mode.
|
||||||
@@ -71,18 +72,50 @@ in
|
|||||||
pulse.enable = true;
|
pulse.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
hardware.bluetooth.enable = true;
|
|
||||||
services.blueman.enable = true;
|
|
||||||
|
|
||||||
networking.useDHCP = false;
|
networking.useDHCP = false;
|
||||||
networking.networkmanager.enable = true;
|
networking.networkmanager.enable = true;
|
||||||
networking.wireless.enable = lib.mkForce false;
|
networking.wireless.enable = lib.mkForce false;
|
||||||
|
|
||||||
|
hardware.bluetooth.enable = true;
|
||||||
|
services.blueman.enable = true;
|
||||||
|
# gsd provides Rfkill, which is required for the bluetooth pane in gnome-control-center to work
|
||||||
|
services.gnome.gnome-settings-daemon.enable = true;
|
||||||
|
# start the components of gsd we need at login
|
||||||
|
systemd.user.targets."org.gnome.SettingsDaemon.Rfkill".wantedBy = [ "graphical-session.target" ];
|
||||||
|
# go ahead and `systemctl --user cat gnome-session-initialized.target`. i dare you.
|
||||||
|
# the only way i can figure out how to get Rfkill to actually load is to just disable all the shit it depends on.
|
||||||
|
# it doesn't actually seem to need ANY of them in the first place T_T
|
||||||
|
systemd.user.targets."gnome-session-initialized".enable = false;
|
||||||
|
# bluez can't connect to audio devices unless pipewire is running.
|
||||||
|
# a system service can't depend on a user service, so just launch it at graphical-session
|
||||||
|
systemd.user.services."pipewire".wantedBy = [ "graphical-session.target" ];
|
||||||
|
|
||||||
sane.home-manager.windowManager.sway = {
|
sane.home-manager.windowManager.sway = {
|
||||||
enable = true;
|
enable = true;
|
||||||
wrapperFeatures.gtk = true;
|
wrapperFeatures.gtk = true;
|
||||||
config = rec {
|
config = let
|
||||||
terminal = "${pkgs.kitty}/bin/kitty";
|
fuzzel = "${pkgs.fuzzel}/bin/fuzzel";
|
||||||
|
sed = "${pkgs.gnused}/bin/sed";
|
||||||
|
wtype = "${pkgs.wtype}/bin/wtype";
|
||||||
|
kitty = "${pkgs.kitty}/bin/kitty";
|
||||||
|
lock-cmd = "${pkgs.swaylock}/bin/swaylock --indicator-idle-visible --indicator-radius 100 --indicator-thickness 30";
|
||||||
|
vol-up-cmd = "${pkgs.pulsemixer}/bin/pulsemixer --change-volume +5";
|
||||||
|
vol-down-cmd = "${pkgs.pulsemixer}/bin/pulsemixer --change-volume -5";
|
||||||
|
mute-cmd = "${pkgs.pulsemixer}/bin/pulsemixer --toggle-mute";
|
||||||
|
brightness-up-cmd = "${pkgs.brightnessctl}/bin/brightnessctl set +2%";
|
||||||
|
brightness-down-cmd = "${pkgs.brightnessctl}/bin/brightnessctl set 2%-";
|
||||||
|
screenshot-cmd = "${pkgs.sway-contrib.grimshot}/bin/grimshot copy area";
|
||||||
|
# "bookmarking"/snippets inspired by Luke Smith:
|
||||||
|
# - <https://www.youtube.com/watch?v=d_11QaTlf1I>
|
||||||
|
snip-file = ./snippets.txt;
|
||||||
|
# TODO: querying sops here breaks encapsulation
|
||||||
|
list-snips = "cat ${snip-file} ${config.sops.secrets.snippets.path}";
|
||||||
|
strip-comments = "${sed} 's/ #.*$//'";
|
||||||
|
snip-cmd = "${wtype} $(${list-snips} | ${fuzzel} -d -i -w 60 | ${strip-comments})";
|
||||||
|
# TODO: next splatmoji release should allow `-s none` to disable skin tones
|
||||||
|
emoji-cmd = "${pkgs.splatmoji}/bin/splatmoji -s medium-light type";
|
||||||
|
in rec {
|
||||||
|
terminal = kitty;
|
||||||
window = {
|
window = {
|
||||||
border = 3; # pixel boundary between windows
|
border = 3; # pixel boundary between windows
|
||||||
hideEdgeBorders = "smart"; # don't show border if only window on workspace
|
hideEdgeBorders = "smart"; # don't show border if only window on workspace
|
||||||
@@ -103,7 +136,7 @@ in
|
|||||||
modifier = "Mod1";
|
modifier = "Mod1";
|
||||||
# list of launchers: https://www.reddit.com/r/swaywm/comments/v39hxa/your_favorite_launcher/
|
# list of launchers: https://www.reddit.com/r/swaywm/comments/v39hxa/your_favorite_launcher/
|
||||||
# menu = "${pkgs.dmenu}/bin/dmenu_path";
|
# menu = "${pkgs.dmenu}/bin/dmenu_path";
|
||||||
menu = "${pkgs.fuzzel}/bin/fuzzel";
|
menu = fuzzel;
|
||||||
# menu = "${pkgs.albert}/bin/albert";
|
# menu = "${pkgs.albert}/bin/albert";
|
||||||
left = "h";
|
left = "h";
|
||||||
down = "j";
|
down = "j";
|
||||||
@@ -114,7 +147,9 @@ in
|
|||||||
"${modifier}+Return" = "exec ${terminal}";
|
"${modifier}+Return" = "exec ${terminal}";
|
||||||
"${modifier}+Shift+q" = "kill";
|
"${modifier}+Shift+q" = "kill";
|
||||||
"${modifier}+d" = "exec ${menu}";
|
"${modifier}+d" = "exec ${menu}";
|
||||||
"${modifier}+l" = "exec ${pkgs.swaylock}/bin/swaylock --indicator-idle-visible --indicator-radius 100 --indicator-thickness 30";
|
"${modifier}+s" = "exec ${snip-cmd}";
|
||||||
|
"${modifier}+l" = "exec ${lock-cmd}";
|
||||||
|
"${modifier}+slash" = "exec ${emoji-cmd}";
|
||||||
|
|
||||||
# "${modifier}+${left}" = "focus left";
|
# "${modifier}+${left}" = "focus left";
|
||||||
# "${modifier}+${down}" = "focus down";
|
# "${modifier}+${down}" = "focus down";
|
||||||
@@ -141,7 +176,7 @@ in
|
|||||||
"${modifier}+f" = "fullscreen toggle";
|
"${modifier}+f" = "fullscreen toggle";
|
||||||
"${modifier}+a" = "focus parent";
|
"${modifier}+a" = "focus parent";
|
||||||
|
|
||||||
"${modifier}+s" = "layout stacking";
|
# "${modifier}+s" = "layout stacking";
|
||||||
"${modifier}+w" = "layout tabbed";
|
"${modifier}+w" = "layout tabbed";
|
||||||
"${modifier}+e" = "layout toggle split";
|
"${modifier}+e" = "layout toggle split";
|
||||||
|
|
||||||
@@ -185,19 +220,20 @@ in
|
|||||||
"exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'";
|
"exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'";
|
||||||
|
|
||||||
"${modifier}+r" = "mode resize";
|
"${modifier}+r" = "mode resize";
|
||||||
} // {
|
|
||||||
# media keys
|
# media keys
|
||||||
XF86MonBrightnessDown = ''exec "${pkgs.brightnessctl}/bin/brightnessctl set 2%-"'';
|
XF86MonBrightnessDown = "exec ${brightness-down-cmd}";
|
||||||
XF86MonBrightnessUp = ''exec "${pkgs.brightnessctl}/bin/brightnessctl set +2%"'';
|
XF86MonBrightnessUp = "exec ${brightness-up-cmd}";
|
||||||
|
|
||||||
XF86AudioRaiseVolume = "exec '${pkgs.pulsemixer}/bin/pulsemixer --change-volume +5'";
|
# TODO: hook into a visual prompt to display volume?
|
||||||
XF86AudioLowerVolume = "exec '${pkgs.pulsemixer}/bin/pulsemixer --change-volume -5'";
|
XF86AudioRaiseVolume = "exec ${vol-up-cmd}";
|
||||||
XF86AudioMute = "exec '${pkgs.pulsemixer}/bin/pulsemixer --toggle-mute'";
|
XF86AudioLowerVolume = "exec ${vol-down-cmd}";
|
||||||
|
XF86AudioMute = "exec ${mute-cmd}";
|
||||||
|
|
||||||
"${modifier}+Page_Up" = "exec '${pkgs.pulsemixer}/bin/pulsemixer --change-volume +5'";
|
"${modifier}+Page_Up" = "exec ${vol-up-cmd}";
|
||||||
"${modifier}+Page_Down" = "exec '${pkgs.pulsemixer}/bin/pulsemixer --change-volume -5'";
|
"${modifier}+Page_Down" = "exec ${vol-down-cmd}";
|
||||||
|
|
||||||
"${modifier}+Print" = "exec '${pkgs.sway-contrib.grimshot}/bin/grimshot copy area'";
|
"${modifier}+Print" = "exec ${screenshot-cmd}";
|
||||||
};
|
};
|
||||||
|
|
||||||
# mostly defaults:
|
# mostly defaults:
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# Terminal UI mail client
|
# Terminal UI mail client
|
||||||
{ config, lib, ... }:
|
{ config, lib, sane-lib, ... }:
|
||||||
|
|
||||||
lib.mkIf config.sane.home-manager.enable
|
lib.mkIf config.sane.home-manager.enable
|
||||||
{
|
{
|
||||||
@@ -8,9 +8,5 @@ lib.mkIf config.sane.home-manager.enable
|
|||||||
sopsFile = ../../secrets/universal/aerc_accounts.conf;
|
sopsFile = ../../secrets/universal/aerc_accounts.conf;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
home-manager.users.colin = let sysconfig = config; in { config, ... }: {
|
sane.fs."/home/colin/.config/aerc/accounts.conf" = sane-lib.fs.wantedSymlinkTo config.sops.secrets.aerc_accounts.path;
|
||||||
# aerc TUI mail client
|
|
||||||
xdg.configFile."aerc/accounts.conf".source =
|
|
||||||
config.lib.file.mkOutOfStoreSymlink sysconfig.sops.secrets.aerc_accounts.path;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -9,27 +9,26 @@
|
|||||||
with lib;
|
with lib;
|
||||||
let
|
let
|
||||||
cfg = config.sane.home-manager;
|
cfg = config.sane.home-manager;
|
||||||
# extract package from `sane.packages.enabledUserPkgs`
|
# extract `pkg` from `sane.packages.enabledUserPkgs`
|
||||||
pkg-list = pkgspec: builtins.map (e: e.pkg or e) pkgspec;
|
pkg-list = pkgspec: builtins.map (e: e.pkg) pkgspec;
|
||||||
# extract `dir` from `sane.packages.enabledUserPkgs`
|
|
||||||
dir-list = pkgspec: builtins.concatLists (builtins.map (e: if e ? "dir" then [ e.dir ] else []) pkgspec);
|
|
||||||
private-list = pkgspec: builtins.concatLists (builtins.map (e: if e ? "private" then [ e.private ] else []) pkgspec);
|
|
||||||
feeds = import ./feeds.nix { inherit lib; };
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./aerc.nix
|
./aerc.nix
|
||||||
./discord.nix
|
|
||||||
./firefox.nix
|
./firefox.nix
|
||||||
|
./gfeeds.nix
|
||||||
./git.nix
|
./git.nix
|
||||||
|
./gpodder.nix
|
||||||
|
./keyring.nix
|
||||||
./kitty.nix
|
./kitty.nix
|
||||||
./mpv.nix
|
./mpv.nix
|
||||||
./nb.nix
|
|
||||||
./neovim.nix
|
./neovim.nix
|
||||||
|
./newsflash.nix
|
||||||
|
./splatmoji.nix
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
./sublime-music.nix
|
./sublime-music.nix
|
||||||
./vlc.nix
|
./vlc.nix
|
||||||
./zsh.nix
|
./zsh
|
||||||
];
|
];
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
@@ -51,18 +50,6 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
sane.impermanence.home-dirs = [
|
|
||||||
"archive"
|
|
||||||
"dev"
|
|
||||||
"records"
|
|
||||||
"ref"
|
|
||||||
"tmp"
|
|
||||||
"use"
|
|
||||||
"Music"
|
|
||||||
"Pictures"
|
|
||||||
"Videos"
|
|
||||||
] ++ (dir-list config.sane.packages.enabledUserPkgs);
|
|
||||||
|
|
||||||
home-manager.useGlobalPkgs = true;
|
home-manager.useGlobalPkgs = true;
|
||||||
home-manager.useUserPackages = true;
|
home-manager.useUserPackages = true;
|
||||||
|
|
||||||
@@ -82,35 +69,6 @@ in
|
|||||||
home.username = "colin";
|
home.username = "colin";
|
||||||
home.homeDirectory = "/home/colin";
|
home.homeDirectory = "/home/colin";
|
||||||
|
|
||||||
home.activation = {
|
|
||||||
initKeyring = {
|
|
||||||
after = ["writeBoundary"];
|
|
||||||
before = [];
|
|
||||||
data = "${../../scripts/init-keyring}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
home.file = let
|
|
||||||
privates = builtins.listToAttrs (
|
|
||||||
builtins.map (path: {
|
|
||||||
name = path;
|
|
||||||
value = { source = config.lib.file.mkOutOfStoreSymlink "/home/colin/private/${path}"; };
|
|
||||||
})
|
|
||||||
(private-list sysconfig.sane.packages.enabledUserPkgs)
|
|
||||||
);
|
|
||||||
in {
|
|
||||||
# convenience
|
|
||||||
"knowledge".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/dev/knowledge";
|
|
||||||
"nixos".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/dev/nixos";
|
|
||||||
"Videos/servo".source = config.lib.file.mkOutOfStoreSymlink "/mnt/servo-media/Videos";
|
|
||||||
"Videos/servo-incomplete".source = config.lib.file.mkOutOfStoreSymlink "/mnt/servo-media/incomplete";
|
|
||||||
"Music/servo".source = config.lib.file.mkOutOfStoreSymlink "/mnt/servo-media/Music";
|
|
||||||
|
|
||||||
# used by password managers, e.g. unix `pass`
|
|
||||||
".password-store".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/dev/knowledge/secrets/accounts";
|
|
||||||
} // privates;
|
|
||||||
|
|
||||||
# XDG defines things like ~/Desktop, ~/Downloads, etc.
|
# XDG defines things like ~/Desktop, ~/Downloads, etc.
|
||||||
# these clutter the home, so i mostly don't use them.
|
# these clutter the home, so i mostly don't use them.
|
||||||
xdg.userDirs = {
|
xdg.userDirs = {
|
||||||
@@ -130,7 +88,7 @@ in
|
|||||||
# - `xdg-mime query filetype path/to/thing.ext`
|
# - `xdg-mime query filetype path/to/thing.ext`
|
||||||
xdg.mimeApps.enable = true;
|
xdg.mimeApps.enable = true;
|
||||||
xdg.mimeApps.defaultApplications = let
|
xdg.mimeApps.defaultApplications = let
|
||||||
www = sysconfig.sane.web-browser.desktop;
|
www = sysconfig.sane.web-browser.browser.desktop;
|
||||||
pdf = "org.gnome.Evince.desktop";
|
pdf = "org.gnome.Evince.desktop";
|
||||||
md = "obsidian.desktop";
|
md = "obsidian.desktop";
|
||||||
thumb = "org.gnome.gThumb.desktop";
|
thumb = "org.gnome.gThumb.desktop";
|
||||||
@@ -173,54 +131,14 @@ in
|
|||||||
# <item oor:path="/org.openoffice.Setup/Product"><prop oor:name="LastTimeGetInvolvedShown" oor:op="fuse"><value>1667693880</value></prop></item>
|
# <item oor:path="/org.openoffice.Setup/Product"><prop oor:name="LastTimeGetInvolvedShown" oor:op="fuse"><value>1667693880</value></prop></item>
|
||||||
|
|
||||||
|
|
||||||
|
programs = lib.mkMerge [
|
||||||
xdg.configFile."gpodderFeeds.opml".text = with feeds;
|
{
|
||||||
feedsToOpml feeds.podcasts;
|
|
||||||
|
|
||||||
# news-flash RSS viewer
|
|
||||||
xdg.configFile."newsflashFeeds.opml".text = with feeds;
|
|
||||||
feedsToOpml (feeds.texts ++ feeds.images);
|
|
||||||
|
|
||||||
# gnome feeds RSS viewer
|
|
||||||
xdg.configFile."org.gabmus.gfeeds.json".text =
|
|
||||||
let
|
|
||||||
myFeeds = feeds.texts ++ feeds.images;
|
|
||||||
in builtins.toJSON {
|
|
||||||
# feed format is a map from URL to a dict,
|
|
||||||
# with dict["tags"] a list of string tags.
|
|
||||||
feeds = builtins.foldl' (acc: feed: acc // {
|
|
||||||
"${feed.url}".tags = [ feed.cat feed.freq ];
|
|
||||||
}) {} myFeeds;
|
|
||||||
dark_reader = false;
|
|
||||||
new_first = true;
|
|
||||||
# windowsize = {
|
|
||||||
# width = 350;
|
|
||||||
# height = 650;
|
|
||||||
# };
|
|
||||||
max_article_age_days = 90;
|
|
||||||
enable_js = false;
|
|
||||||
max_refresh_threads = 3;
|
|
||||||
# saved_items = {};
|
|
||||||
# read_items = [];
|
|
||||||
show_read_items = true;
|
|
||||||
full_article_title = true;
|
|
||||||
# views: "webview", "reader", "rsscont"
|
|
||||||
default_view = "rsscont";
|
|
||||||
open_links_externally = true;
|
|
||||||
full_feed_name = false;
|
|
||||||
refresh_on_startup = true;
|
|
||||||
tags = lib.lists.unique (
|
|
||||||
(builtins.catAttrs "cat" myFeeds) ++ (builtins.catAttrs "freq" myFeeds)
|
|
||||||
);
|
|
||||||
open_youtube_externally = false;
|
|
||||||
media_player = "vlc"; # default: mpv
|
|
||||||
};
|
|
||||||
|
|
||||||
programs = {
|
|
||||||
home-manager.enable = true; # this lets home-manager manage dot-files in user dirs, i think
|
home-manager.enable = true; # this lets home-manager manage dot-files in user dirs, i think
|
||||||
# "command not found" will cause the command to be searched in nixpkgs
|
# "command not found" will cause the command to be searched in nixpkgs
|
||||||
nix-index.enable = true;
|
nix-index.enable = true;
|
||||||
} // cfg.programs;
|
}
|
||||||
|
cfg.programs
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
|
|
||||||
lib.mkIf config.sane.home-manager.enable
|
|
||||||
{
|
|
||||||
# TODO: this should only be enabled on gui devices
|
|
||||||
# make Discord usable even when client is "outdated"
|
|
||||||
home-manager.users.colin.xdg.configFile."discord/settings.json".text = ''
|
|
||||||
{
|
|
||||||
"SKIP_HOST_UPDATE": true
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
}
|
|
@@ -6,7 +6,7 @@
|
|||||||
# many of the settings below won't have effect without those patches.
|
# many of the settings below won't have effect without those patches.
|
||||||
# see: https://gitlab.com/librewolf-community/settings/-/blob/master/distribution/policies.json
|
# see: https://gitlab.com/librewolf-community/settings/-/blob/master/distribution/policies.json
|
||||||
|
|
||||||
{ config, lib, pkgs, ...}:
|
{ config, lib, pkgs, sane-lib, ...}:
|
||||||
with lib;
|
with lib;
|
||||||
let
|
let
|
||||||
cfg = config.sane.web-browser;
|
cfg = config.sane.web-browser;
|
||||||
@@ -19,22 +19,24 @@ let
|
|||||||
# });
|
# });
|
||||||
libName = "librewolf";
|
libName = "librewolf";
|
||||||
dotDir = ".librewolf";
|
dotDir = ".librewolf";
|
||||||
|
cacheDir = ".cache/librewolf"; # TODO: is it?
|
||||||
desktop = "librewolf.desktop";
|
desktop = "librewolf.desktop";
|
||||||
};
|
};
|
||||||
firefoxSettings = {
|
firefoxSettings = {
|
||||||
browser = pkgs.firefox-esr-unwrapped;
|
browser = pkgs.firefox-esr-unwrapped;
|
||||||
libName = "firefox";
|
libName = "firefox";
|
||||||
dotDir = ".mozilla/firefox";
|
dotDir = ".mozilla/firefox";
|
||||||
|
cacheDir = ".cache/mozilla";
|
||||||
desktop = "firefox.desktop";
|
desktop = "firefox.desktop";
|
||||||
};
|
};
|
||||||
defaultSettings = firefoxSettings;
|
defaultSettings = firefoxSettings;
|
||||||
# defaultSettings = librewolfSettings;
|
# defaultSettings = librewolfSettings;
|
||||||
|
|
||||||
package = pkgs.wrapFirefox cfg.browser {
|
package = pkgs.wrapFirefox cfg.browser.browser {
|
||||||
# inherit the default librewolf.cfg
|
# inherit the default librewolf.cfg
|
||||||
# it can be further customized via ~/.librewolf/librewolf.overrides.cfg
|
# it can be further customized via ~/.librewolf/librewolf.overrides.cfg
|
||||||
inherit (pkgs.librewolf-unwrapped) extraPrefsFiles;
|
inherit (pkgs.librewolf-unwrapped) extraPrefsFiles;
|
||||||
inherit (cfg) libName;
|
inherit (cfg.browser) libName;
|
||||||
|
|
||||||
extraNativeMessagingHosts = [ pkgs.browserpass ];
|
extraNativeMessagingHosts = [ pkgs.browserpass ];
|
||||||
# extraNativeMessagingHosts = [ pkgs.gopass-native-messaging-host ];
|
# extraNativeMessagingHosts = [ pkgs.gopass-native-messaging-host ];
|
||||||
@@ -43,6 +45,7 @@ let
|
|||||||
addon = name: extid: hash: pkgs.fetchFirefoxAddon {
|
addon = name: extid: hash: pkgs.fetchFirefoxAddon {
|
||||||
inherit name hash;
|
inherit name hash;
|
||||||
url = "https://addons.mozilla.org/firefox/downloads/latest/${name}/latest.xpi";
|
url = "https://addons.mozilla.org/firefox/downloads/latest/${name}/latest.xpi";
|
||||||
|
# extid can be found by unar'ing the above xpi, and copying browser_specific_settings.gecko.id field
|
||||||
fixedExtid = extid;
|
fixedExtid = extid;
|
||||||
};
|
};
|
||||||
localAddon = pkg: pkgs.fetchFirefoxAddon {
|
localAddon = pkg: pkgs.fetchFirefoxAddon {
|
||||||
@@ -51,11 +54,16 @@ let
|
|||||||
fixedExtid = pkg.extid;
|
fixedExtid = pkg.extid;
|
||||||
};
|
};
|
||||||
in [
|
in [
|
||||||
(addon "ublock-origin" "uBlock0@raymondhill.net" "sha256-C+VQyaJ8BA0ErXGVTdnppJZ6J9SP+izf6RFxdS4VJoU=")
|
# get names from:
|
||||||
(addon "sponsorblock" "sponsorBlocker@ajay.app" "sha256-au5GGn22n4i6VrdOKqNMOrWdMoVCcpLdjO2wwRvyx7E=")
|
# - ~/ref/nix-community/nur-combined/repos/rycee/pkgs/firefox-addons/generated-firefox-addons.nix
|
||||||
(addon "bypass-paywalls-clean" "{d133e097-46d9-4ecc-9903-fa6a722a6e0e}" "sha256-m14onUlnpLDPHezA/soKygcc76tF1fLG52tM/LkbAXQ=")
|
# `wget ...xpi`; `unar ...xpi`; `cat */manifest.json | jq '.browser_specific_settings.gecko.id'`
|
||||||
|
(addon "ublock-origin" "uBlock0@raymondhill.net" "sha256-a/ivUmY1P6teq9x0dt4CbgHt+3kBsEMMXlOfZ5Hx7cg=")
|
||||||
|
(addon "sponsorblock" "sponsorBlocker@ajay.app" "sha256-d2K3ufvurWnYVzqLbyR//MgejybkY9exitAf9RdLNRo=")
|
||||||
|
(addon "bypass-paywalls-clean" "{d133e097-46d9-4ecc-9903-fa6a722a6e0e}" "sha256-t6Q335Nq60mDILPmzem+DT5KflleAPVJL3bsaA+UL0g=")
|
||||||
(addon "sidebery" "{3c078156-979c-498b-8990-85f7987dd929}" "sha256-YONfK/rIjlsrTgRHIt3km07Q7KnpIW89Z9r92ZSCc6w=")
|
(addon "sidebery" "{3c078156-979c-498b-8990-85f7987dd929}" "sha256-YONfK/rIjlsrTgRHIt3km07Q7KnpIW89Z9r92ZSCc6w=")
|
||||||
(addon "ether-metamask" "webextension@metamask.io" "sha256-dnpwKpNF0KgHMAlz5btkkZySjMsnrXECS35ClkD2XHc=")
|
(addon "ether-metamask" "webextension@metamask.io" "sha256-G+MwJDOcsaxYSUXjahHJmkWnjLeQ0Wven8DU/lGeMzA=")
|
||||||
|
(addon "ublacklist" "@ublacklist" "sha256-vHe/7EYOzcKeAbTElmt0Rb4E2rX0f3JgXThJaUmaz+M=")
|
||||||
|
(addon "i2p-in-private-browsing" "i2ppb@eyedeekay.github.io" "sha256-dJcJ3jxeAeAkRvhODeIVrCflvX+S4E0wT/PyYzQBQWs=")
|
||||||
# (addon "browserpass-ce" "browserpass@maximbaz.com" "sha256-sXgUBbRvMnRpeIW1MTkmTcoqtW/8RDXAkxAq1evFkpc=")
|
# (addon "browserpass-ce" "browserpass@maximbaz.com" "sha256-sXgUBbRvMnRpeIW1MTkmTcoqtW/8RDXAkxAq1evFkpc=")
|
||||||
(localAddon pkgs.browserpass-extension)
|
(localAddon pkgs.browserpass-extension)
|
||||||
];
|
];
|
||||||
@@ -97,18 +105,23 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
sane.web-browser = mkOption {
|
sane.web-browser.browser = mkOption {
|
||||||
default = defaultSettings;
|
default = defaultSettings;
|
||||||
type = types.attrs;
|
type = types.attrs;
|
||||||
};
|
};
|
||||||
|
sane.web-browser.persistData = mkOption {
|
||||||
|
description = "optional store name to which persist browsing data (like history)";
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
};
|
};
|
||||||
|
sane.web-browser.persistCache = mkOption {
|
||||||
|
description = "optional store name to which persist browser cache";
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = "cryptClearOnBoot";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
config = lib.mkIf config.sane.home-manager.enable {
|
config = lib.mkIf config.sane.home-manager.enable {
|
||||||
# XXX: although home-manager calls this option `firefox`, we can use other browsers and it still mostly works.
|
|
||||||
home-manager.users.colin = lib.mkIf (config.sane.gui.enable) {
|
|
||||||
programs.firefox = {
|
|
||||||
enable = true;
|
|
||||||
inherit package;
|
|
||||||
};
|
|
||||||
|
|
||||||
# uBlock filter list configuration.
|
# uBlock filter list configuration.
|
||||||
# specifically, enable the GDPR cookie prompt blocker.
|
# specifically, enable the GDPR cookie prompt blocker.
|
||||||
@@ -118,7 +131,7 @@ in
|
|||||||
# the specific attribute path is found via scraping ublock code here:
|
# the specific attribute path is found via scraping ublock code here:
|
||||||
# - <https://github.com/gorhill/uBlock/blob/master/src/js/storage.js>
|
# - <https://github.com/gorhill/uBlock/blob/master/src/js/storage.js>
|
||||||
# - <https://github.com/gorhill/uBlock/blob/master/assets/assets.json>
|
# - <https://github.com/gorhill/uBlock/blob/master/assets/assets.json>
|
||||||
home.file."${cfg.dotDir}/managed-storage/uBlock0@raymondhill.net.json".text = ''
|
sane.fs."/home/colin/${cfg.browser.dotDir}/managed-storage/uBlock0@raymondhill.net.json" = sane-lib.fs.wantedText ''
|
||||||
{
|
{
|
||||||
"name": "uBlock0@raymondhill.net",
|
"name": "uBlock0@raymondhill.net",
|
||||||
"description": "ignored",
|
"description": "ignored",
|
||||||
@@ -128,12 +141,21 @@ in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
home.file."${cfg.dotDir}/${cfg.libName}.overrides.cfg".text = ''
|
sane.fs."/home/colin/${cfg.browser.dotDir}/${cfg.browser.libName}.overrides.cfg" = sane-lib.fs.wantedText ''
|
||||||
// if we can't query the revocation status of a SSL cert because the issuer is offline,
|
// if we can't query the revocation status of a SSL cert because the issuer is offline,
|
||||||
// treat it as unrevoked.
|
// treat it as unrevoked.
|
||||||
// see: <https://librewolf.net/docs/faq/#im-getting-sec_error_ocsp_server_error-what-can-i-do>
|
// see: <https://librewolf.net/docs/faq/#im-getting-sec_error_ocsp_server_error-what-can-i-do>
|
||||||
defaultPref("security.OCSP.require", false);
|
defaultPref("security.OCSP.require", false);
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
sane.packages.extraGuiPkgs = [ package ];
|
||||||
|
# flood the cache to disk to avoid it taking up too much tmp
|
||||||
|
sane.persist.home.byPath."${cfg.browser.cacheDir}" = lib.mkIf (cfg.persistCache != null) {
|
||||||
|
store = cfg.persistCache;
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.persist.home.byPath."${cfg.browser.dotDir}" = lib.mkIf (cfg.persistData != null) {
|
||||||
|
store = cfg.persistData;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
42
modules/home-manager/gfeeds.nix
Normal file
42
modules/home-manager/gfeeds.nix
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# gnome feeds RSS viewer
|
||||||
|
{ config, lib, sane-lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
feeds = sane-lib.feeds;
|
||||||
|
all-feeds = config.sane.feeds;
|
||||||
|
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
|
||||||
|
in {
|
||||||
|
sane.fs."/home/colin/.config/org.gabmus.gfeeds.json" = sane-lib.fs.wantedText (
|
||||||
|
builtins.toJSON {
|
||||||
|
# feed format is a map from URL to a dict,
|
||||||
|
# with dict["tags"] a list of string tags.
|
||||||
|
feeds = sane-lib.mapToAttrs (feed: {
|
||||||
|
name = feed.url;
|
||||||
|
value.tags = [ feed.cat feed.freq ];
|
||||||
|
}) wanted-feeds;
|
||||||
|
dark_reader = false;
|
||||||
|
new_first = true;
|
||||||
|
# windowsize = {
|
||||||
|
# width = 350;
|
||||||
|
# height = 650;
|
||||||
|
# };
|
||||||
|
max_article_age_days = 90;
|
||||||
|
enable_js = false;
|
||||||
|
max_refresh_threads = 3;
|
||||||
|
# saved_items = {};
|
||||||
|
# read_items = [];
|
||||||
|
show_read_items = true;
|
||||||
|
full_article_title = true;
|
||||||
|
# views: "webview", "reader", "rsscont"
|
||||||
|
default_view = "rsscont";
|
||||||
|
open_links_externally = true;
|
||||||
|
full_feed_name = false;
|
||||||
|
refresh_on_startup = true;
|
||||||
|
tags = lib.unique (
|
||||||
|
(builtins.catAttrs "cat" wanted-feeds) ++ (builtins.catAttrs "freq" wanted-feeds)
|
||||||
|
);
|
||||||
|
open_youtube_externally = false;
|
||||||
|
media_player = "vlc"; # default: mpv
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
12
modules/home-manager/gpodder.nix
Normal file
12
modules/home-manager/gpodder.nix
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# gnome feeds RSS viewer
|
||||||
|
{ config, sane-lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
feeds = sane-lib.feeds;
|
||||||
|
all-feeds = config.sane.feeds;
|
||||||
|
wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds;
|
||||||
|
in {
|
||||||
|
sane.fs."/home/colin/.config/gpodderFeeds.opml" = sane-lib.fs.wantedText (
|
||||||
|
feeds.feedsToOpml wanted-feeds
|
||||||
|
);
|
||||||
|
}
|
11
modules/home-manager/keyring.nix
Normal file
11
modules/home-manager/keyring.nix
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{ config, lib, sane-lib, ... }:
|
||||||
|
|
||||||
|
lib.mkIf config.sane.home-manager.enable
|
||||||
|
{
|
||||||
|
sane.persist.home.private = [ ".local/share/keyrings" ];
|
||||||
|
|
||||||
|
sane.fs."/home/colin/private/.local/share/keyrings/default" = {
|
||||||
|
generated.script.script = builtins.readFile ../../scripts/init-keyring;
|
||||||
|
wantedBy = [ config.sane.fs."/home/colin/private".unit ];
|
||||||
|
};
|
||||||
|
}
|
@@ -1,27 +0,0 @@
|
|||||||
# nb is a CLI-drive Personal Knowledge Manager
|
|
||||||
# - <https://xwmx.github.io/nb/>
|
|
||||||
#
|
|
||||||
# it's pretty opinionated:
|
|
||||||
# - autocommits (to git) excessively (disable-able)
|
|
||||||
# - inserts its own index files to give deterministic names to files
|
|
||||||
#
|
|
||||||
# it offers a primitive web-server
|
|
||||||
# and it offers some CLI query tools
|
|
||||||
|
|
||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
# lib.mkIf config.sane.home-manager.enable
|
|
||||||
lib.mkIf false # XXX disabled!
|
|
||||||
{
|
|
||||||
sane.packages.extraUserPkgs = [ pkgs.nb ];
|
|
||||||
|
|
||||||
home-manager.users.colin = { config, ... }: {
|
|
||||||
# nb markdown/personal knowledge manager
|
|
||||||
home.file.".nb/knowledge".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/dev/knowledge";
|
|
||||||
home.file.".nb/.current".text = "knowledge";
|
|
||||||
home.file.".nbrc".text = ''
|
|
||||||
# manage with `nb settings`
|
|
||||||
export NB_AUTO_SYNC=0
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
lib.mkIf config.sane.home-manager.enable
|
lib.mkIf config.sane.home-manager.enable
|
||||||
{
|
{
|
||||||
sane.impermanence.home-dirs = [ ".cache/vim-swap" ];
|
# private because there could be sensitive things in the swap
|
||||||
|
sane.persist.home.private = [ ".cache/vim-swap" ];
|
||||||
|
|
||||||
home-manager.users.colin.programs.neovim = {
|
home-manager.users.colin.programs.neovim = {
|
||||||
# neovim: https://github.com/neovim/neovim
|
# neovim: https://github.com/neovim/neovim
|
||||||
@@ -33,15 +34,6 @@ lib.mkIf config.sane.home-manager.enable
|
|||||||
" autocmd Syntax tex set conceallevel=2
|
" autocmd Syntax tex set conceallevel=2
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
# nabla renders inline math in any document, but it's buggy.
|
|
||||||
# https://github.com/jbyuki/nabla.nvim
|
|
||||||
# ({
|
|
||||||
# plugin = pkgs.nabla;
|
|
||||||
# type = "lua";
|
|
||||||
# config = ''
|
|
||||||
# require'nabla'.enable_virt()
|
|
||||||
# '';
|
|
||||||
# })
|
|
||||||
# treesitter syntax highlighting: https://nixos.wiki/wiki/Tree_sitters
|
# treesitter syntax highlighting: https://nixos.wiki/wiki/Tree_sitters
|
||||||
# docs: https://github.com/nvim-treesitter/nvim-treesitter
|
# docs: https://github.com/nvim-treesitter/nvim-treesitter
|
||||||
# config taken from: https://github.com/i077/system/blob/master/modules/home/neovim/default.nix
|
# config taken from: https://github.com/i077/system/blob/master/modules/home/neovim/default.nix
|
||||||
|
12
modules/home-manager/newsflash.nix
Normal file
12
modules/home-manager/newsflash.nix
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# news-flash RSS viewer
|
||||||
|
{ config, sane-lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
feeds = sane-lib.feeds;
|
||||||
|
all-feeds = config.sane.feeds;
|
||||||
|
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
|
||||||
|
in {
|
||||||
|
sane.fs."/home/colin/.config/newsflashFeeds.opml" = sane-lib.fs.wantedText (
|
||||||
|
feeds.feedsToOpml wanted-feeds
|
||||||
|
);
|
||||||
|
}
|
19
modules/home-manager/splatmoji.nix
Normal file
19
modules/home-manager/splatmoji.nix
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# borrows from:
|
||||||
|
# - default config: <https://github.com/cspeterson/splatmoji/blob/master/splatmoji.config>
|
||||||
|
# - wayland: <https://github.com/cspeterson/splatmoji/issues/32#issuecomment-830862566>
|
||||||
|
{ pkgs, sane-lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
sane.persist.home.plaintext = [ ".local/state/splatmoji" ];
|
||||||
|
sane.fs."/home/colin/.config/splatmoji/splatmoji.config" = sane-lib.fs.wantedText ''
|
||||||
|
history_file=/home/colin/.local/state/splatmoji/history
|
||||||
|
history_length=5
|
||||||
|
# TODO: wayland equiv
|
||||||
|
paste_command=xdotool key ctrl+v
|
||||||
|
# rofi_command=${pkgs.wofi}/bin/wofi --dmenu --insensitive --cache-file /dev/null
|
||||||
|
rofi_command=${pkgs.fuzzel}/bin/fuzzel -d -i -w 60
|
||||||
|
xdotool_command=${pkgs.wtype}/bin/wtype
|
||||||
|
# TODO: wayland equiv
|
||||||
|
xsel_command=xsel -b -i
|
||||||
|
'';
|
||||||
|
}
|
@@ -1,20 +1,23 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, sane-lib, ... }:
|
||||||
|
|
||||||
lib.mkIf config.sane.home-manager.enable
|
with lib;
|
||||||
{
|
let
|
||||||
home-manager.users.colin = let
|
|
||||||
host = config.networking.hostName;
|
host = config.networking.hostName;
|
||||||
user_pubkey = (import ../pubkeys.nix).users."${host}";
|
user-pubkey = config.sane.ssh.pubkeys."colin@${host}".asUserKey;
|
||||||
known_hosts_text = builtins.concatStringsSep
|
host-keys = filter (k: k.user == "root") (attrValues config.sane.ssh.pubkeys);
|
||||||
|
known-hosts-text = concatStringsSep
|
||||||
"\n"
|
"\n"
|
||||||
(builtins.attrValues (import ../pubkeys.nix).hosts);
|
(map (k: k.asHostKey) host-keys)
|
||||||
in { config, ...}: {
|
;
|
||||||
|
in lib.mkIf config.sane.home-manager.enable {
|
||||||
# ssh key is stored in private storage
|
# ssh key is stored in private storage
|
||||||
home.file.".ssh/id_ed25519".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/private/.ssh/id_ed25519";
|
sane.persist.home.private = [ ".ssh/id_ed25519" ];
|
||||||
home.file.".ssh/id_ed25519.pub".text = user_pubkey;
|
sane.fs."/home/colin/.ssh/id_ed25519.pub" = sane-lib.fs.wantedText user-pubkey;
|
||||||
|
sane.fs."/home/colin/.ssh/known_hosts" = sane-lib.fs.wantedText known-hosts-text;
|
||||||
|
|
||||||
programs.ssh.enable = true;
|
users.users.colin.openssh.authorizedKeys.keys =
|
||||||
# this optionally accepts multiple known_hosts paths, separated by space.
|
let
|
||||||
programs.ssh.userKnownHostsFile = builtins.toString (pkgs.writeText "known_hosts" known_hosts_text);
|
user-keys = filter (k: k.user == "colin") (attrValues config.sane.ssh.pubkeys);
|
||||||
};
|
in
|
||||||
|
map (k: k.asUserKey) user-keys;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{ config, lib, ... }:
|
{ config, lib, sane-lib, ... }:
|
||||||
|
|
||||||
lib.mkIf config.sane.home-manager.enable
|
lib.mkIf config.sane.home-manager.enable
|
||||||
{
|
{
|
||||||
@@ -8,9 +8,5 @@ lib.mkIf config.sane.home-manager.enable
|
|||||||
sopsFile = ../../secrets/universal/sublime_music_config.json.bin;
|
sopsFile = ../../secrets/universal/sublime_music_config.json.bin;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
home-manager.users.colin = let sysconfig = config; in { config, ... }: {
|
sane.fs."/home/colin/.config/sublime-music/config.json" = sane-lib.fs.wantedSymlinkTo config.sops.secrets.sublime_music_config.path;
|
||||||
# sublime music player
|
|
||||||
xdg.configFile."sublime-music/config.json".source =
|
|
||||||
config.lib.file.mkOutOfStoreSymlink sysconfig.sops.secrets.sublime_music_config.path;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,18 @@
|
|||||||
{ config, lib, ... }:
|
{ config, lib, sane-lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
feeds = sane-lib.feeds;
|
||||||
|
all-feeds = config.sane.feeds;
|
||||||
|
wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds;
|
||||||
|
podcast-urls = lib.concatStringsSep "|" (
|
||||||
|
builtins.map (feed: feed.url) wanted-feeds
|
||||||
|
);
|
||||||
|
in
|
||||||
lib.mkIf config.sane.home-manager.enable
|
lib.mkIf config.sane.home-manager.enable
|
||||||
{
|
{
|
||||||
home-manager.users.colin.xdg.configFile."vlc/vlcrc".text =
|
sane.fs."/home/colin/.config/vlc/vlcrc" = sane-lib.fs.wantedText ''
|
||||||
let
|
|
||||||
feeds = import ./feeds.nix { inherit lib; };
|
|
||||||
podcastUrls = lib.strings.concatStringsSep "|" (
|
|
||||||
builtins.map (feed: feed.url) feeds.podcasts
|
|
||||||
);
|
|
||||||
in ''
|
|
||||||
[podcast]
|
[podcast]
|
||||||
podcast-urls=${podcastUrls}
|
podcast-urls=${podcast-urls}
|
||||||
[core]
|
[core]
|
||||||
metadata-network-access=0
|
metadata-network-access=0
|
||||||
[qt]
|
[qt]
|
||||||
|
@@ -1,63 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
|
|
||||||
lib.mkIf config.sane.home-manager.enable
|
|
||||||
{
|
|
||||||
# we don't need to full zsh dir -- just the history file --
|
|
||||||
# but zsh will sometimes backup the history file and we get fewer errors if we do proper mounts instead of symlinks.
|
|
||||||
sane.impermanence.home-dirs = [ ".local/share/zsh" ];
|
|
||||||
|
|
||||||
home-manager.users.colin.programs.zsh = {
|
|
||||||
enable = true;
|
|
||||||
enableSyntaxHighlighting = true;
|
|
||||||
enableVteIntegration = true;
|
|
||||||
history.ignorePatterns = [ "rm *" ];
|
|
||||||
dotDir = ".config/zsh";
|
|
||||||
history.path = "/home/colin/.local/share/zsh/history";
|
|
||||||
|
|
||||||
initExtraBeforeCompInit = ''
|
|
||||||
# p10k instant prompt
|
|
||||||
# run p10k configure to configure, but it can't write out its file :-(
|
|
||||||
POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD=true
|
|
||||||
'';
|
|
||||||
initExtra = ''
|
|
||||||
# zmv is a way to do rich moves/renames, with pattern matching/substitution.
|
|
||||||
# see for an example: <https://filipe.kiss.ink/zmv-zsh-rename/>
|
|
||||||
autoload -Uz zmv
|
|
||||||
|
|
||||||
# disable `rm *` confirmations
|
|
||||||
setopt rmstarsilent
|
|
||||||
|
|
||||||
function nd() {
|
|
||||||
mkdir -p "$1";
|
|
||||||
pushd "$1";
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
|
|
||||||
# prezto = oh-my-zsh fork; controls prompt, auto-completion, etc.
|
|
||||||
# see: https://github.com/sorin-ionescu/prezto
|
|
||||||
prezto = {
|
|
||||||
enable = true;
|
|
||||||
pmodules = [
|
|
||||||
"environment"
|
|
||||||
"terminal"
|
|
||||||
"editor"
|
|
||||||
"history"
|
|
||||||
"directory"
|
|
||||||
"spectrum"
|
|
||||||
"utility"
|
|
||||||
"completion"
|
|
||||||
"prompt"
|
|
||||||
"git"
|
|
||||||
];
|
|
||||||
prompt.theme = "powerlevel10k";
|
|
||||||
utility.safeOps = false; # disable `mv` confirmation (and supposedly `rm`, too)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
home-manager.users.colin.home.shellAliases = {
|
|
||||||
":q" = "exit";
|
|
||||||
# common typos
|
|
||||||
"cd.." = "cd ..";
|
|
||||||
"cd../" = "cd ../";
|
|
||||||
};
|
|
||||||
}
|
|
108
modules/home-manager/zsh/default.nix
Normal file
108
modules/home-manager/zsh/default.nix
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
lib.mkIf config.sane.home-manager.enable
|
||||||
|
{
|
||||||
|
sane.persist.home.plaintext = [
|
||||||
|
# we don't need to full zsh dir -- just the history file --
|
||||||
|
# but zsh will sometimes backup the history file and we get fewer errors if we do proper mounts instead of symlinks.
|
||||||
|
# TODO: should be private?
|
||||||
|
".local/share/zsh"
|
||||||
|
# cache gitstatus otherwise p10k fetched it from the net EVERY BOOT
|
||||||
|
".cache/gitstatus"
|
||||||
|
];
|
||||||
|
|
||||||
|
home-manager.users.colin.programs.zsh = {
|
||||||
|
enable = true;
|
||||||
|
enableSyntaxHighlighting = true;
|
||||||
|
enableVteIntegration = true;
|
||||||
|
history.ignorePatterns = [ "rm *" ];
|
||||||
|
dotDir = ".config/zsh";
|
||||||
|
history.path = "/home/colin/.local/share/zsh/history";
|
||||||
|
|
||||||
|
# defaultKeymap = "vicmd"; # vim normal mode (cmd mode)
|
||||||
|
|
||||||
|
# powerlevel10k prompt config
|
||||||
|
# p10k.zsh is the auto-generated config, and i overwrite those defaults here, below.
|
||||||
|
initExtraBeforeCompInit = (builtins.readFile ./p10k.zsh) + ''
|
||||||
|
# powerlevel10k launches a gitstatusd daemon to accelerate git prompt queries.
|
||||||
|
# this keeps open file handles for any git repo i touch for 60 minutes (by default).
|
||||||
|
# that prevents unmounting whatever device the git repo is on -- particularly problematic for ~/private.
|
||||||
|
# i can disable gitstatusd and get slower fallback git queries:
|
||||||
|
# - either universally
|
||||||
|
# - or selectively by path
|
||||||
|
# see: <https://github.com/romkatv/powerlevel10k/issues/246>
|
||||||
|
typeset -g POWERLEVEL9K_VCS_DISABLED_DIR_PATTERN='(/home/colin/private/*|/home/colin/knowledge/*)'
|
||||||
|
# typeset -g POWERLEVEL9K_DISABLE_GITSTATUS=true
|
||||||
|
|
||||||
|
# show user@host also when logged into the current machine.
|
||||||
|
# default behavior is to show it only over ssh.
|
||||||
|
typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_CONTENT_EXPANSION='$P9K_CONTENT'
|
||||||
|
'';
|
||||||
|
|
||||||
|
initExtra = ''
|
||||||
|
# zmv is a way to do rich moves/renames, with pattern matching/substitution.
|
||||||
|
# see for an example: <https://filipe.kiss.ink/zmv-zsh-rename/>
|
||||||
|
autoload -Uz zmv
|
||||||
|
|
||||||
|
# disable `rm *` confirmations
|
||||||
|
setopt rmstarsilent
|
||||||
|
|
||||||
|
function nd() {
|
||||||
|
mkdir -p "$1";
|
||||||
|
pushd "$1";
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# prezto = oh-my-zsh fork; controls prompt, auto-completion, etc.
|
||||||
|
# see: https://github.com/sorin-ionescu/prezto
|
||||||
|
prezto = {
|
||||||
|
enable = true;
|
||||||
|
pmodules = [
|
||||||
|
# configures jobs to persist after shell exit; other basic niceties
|
||||||
|
"environment"
|
||||||
|
# auto-titles terminal (e.g. based on cwd)
|
||||||
|
"terminal"
|
||||||
|
# configures shortcuts like Ctrl+U=undo, Ctrl+L=clear
|
||||||
|
"editor"
|
||||||
|
# adds `history-stat` alias, setopts for good history defaults
|
||||||
|
"history"
|
||||||
|
# sets AUTO_CD, adds `d` alias to list directory stack, and `1`-`9` to cd that far back the stack
|
||||||
|
"directory"
|
||||||
|
# helpers for term colors and styling. used by prompts? might be unnecessary
|
||||||
|
"spectrum"
|
||||||
|
# configures aliases like `ll`, `la`, disables globbing for things like rsync
|
||||||
|
# adds aliases like `get` to fetch a file. also adds `http-serve` alias??
|
||||||
|
"utility"
|
||||||
|
# tab completion. requires `utility` module prior to loading
|
||||||
|
# TODO: enable AUTO_PARAM_SLASH
|
||||||
|
"completion"
|
||||||
|
"prompt"
|
||||||
|
# TODO: enable syntax-highlighting ?
|
||||||
|
];
|
||||||
|
prompt.theme = "powerlevel10k";
|
||||||
|
utility.safeOps = false; # disable `mv` confirmation (and supposedly `rm`, too)
|
||||||
|
# editor.keymap = "vi";
|
||||||
|
};
|
||||||
|
|
||||||
|
dirHashes = {
|
||||||
|
# convenient `cd`-isms
|
||||||
|
"3rd" = "/home/colin/dev/3rd";
|
||||||
|
"dev" = "/home/colin/dev";
|
||||||
|
"knowledge" = "/home/colin/knowledge";
|
||||||
|
"nixos" = "/home/colin/nixos";
|
||||||
|
"nixpkgs" = "/home/colin/dev/3rd/nixpkgs";
|
||||||
|
"ref" = "/home/colin/ref";
|
||||||
|
"secrets" = "/home/colin/knowledge/secrets";
|
||||||
|
"tmp" = "/home/colin/tmp";
|
||||||
|
"uninsane" = "/home/colin/dev/uninsane";
|
||||||
|
"Videos" = "/home/colin/Videos";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
home-manager.users.colin.home.shellAliases = {
|
||||||
|
":q" = "exit";
|
||||||
|
# common typos
|
||||||
|
"cd.." = "cd ..";
|
||||||
|
"cd../" = "cd ../";
|
||||||
|
};
|
||||||
|
}
|
1635
modules/home-manager/zsh/p10k.zsh
Normal file
1635
modules/home-manager/zsh/p10k.zsh
Normal file
File diff suppressed because it is too large
Load Diff
89
modules/ids.nix
Normal file
89
modules/ids.nix
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{ lib, config, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
cfg = config.sane.ids;
|
||||||
|
id = types.submodule {
|
||||||
|
options = {
|
||||||
|
uid = mkOption {
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
gid = mkOption {
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
userOpts = { name, ... }: {
|
||||||
|
config =
|
||||||
|
let
|
||||||
|
ent-ids = cfg."${name}" or {};
|
||||||
|
uid = ent-ids.uid or null;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
uid = lib.mkIf (uid != null) uid;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
groupOpts = { name, ... }: {
|
||||||
|
config =
|
||||||
|
let
|
||||||
|
ent-ids = cfg."${name}" or {};
|
||||||
|
gid = ent-ids.gid or null;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
gid = lib.mkIf (gid != null) gid;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
sane.ids = mkOption {
|
||||||
|
type = types.attrsOf id;
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
mapping from user/group name to gids/uids you expect that entity to have.
|
||||||
|
for users/groups created elsewhere *without* an id, this is used to provide them a fixed/stable id.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# these get merged with the nixpkgs options.
|
||||||
|
users.users = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule userOpts);
|
||||||
|
};
|
||||||
|
users.groups = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule groupOpts);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
# guarantee determinism in uid/gid generation for users:
|
||||||
|
assertions = lib.mkMerge [
|
||||||
|
(
|
||||||
|
lib.mapAttrsToList
|
||||||
|
(name: user: {
|
||||||
|
assertion = user.uid != null;
|
||||||
|
message = "non-deterministic uid detected for: ${name}";
|
||||||
|
})
|
||||||
|
config.users.users
|
||||||
|
)
|
||||||
|
(
|
||||||
|
lib.mapAttrsToList
|
||||||
|
(name: group: {
|
||||||
|
assertion = group.gid != null;
|
||||||
|
message = "non-deterministic gid detected for: ${name}";
|
||||||
|
})
|
||||||
|
config.users.groups
|
||||||
|
)
|
||||||
|
(
|
||||||
|
lib.mapAttrsToList
|
||||||
|
(name: user: {
|
||||||
|
assertion = !user.autoSubUidGidRange;
|
||||||
|
message = "non-deterministic subUids/Guids detected for: ${name}";
|
||||||
|
})
|
||||||
|
config.users.users
|
||||||
|
)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
{ config, lib, pkgs, mobile-nixos, utils, ... }:
|
{ config, lib, pkgs, utils, ... }:
|
||||||
|
# TODO: replace mobile-nixos parts with Disko <https://github.com/nix-community/disko>
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
let
|
let
|
||||||
@@ -9,7 +10,7 @@ in
|
|||||||
sane.image.enable = mkOption {
|
sane.image.enable = mkOption {
|
||||||
default = true;
|
default = true;
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
description = "whether to enable image targets. this doesn't mean they'll be built unless you specifically reference the target.";
|
description = "whether to enable image targets. even so they won't be built unless you specifically reference the `system.build.img` target.";
|
||||||
};
|
};
|
||||||
# packages whose contents should be copied directly into the /boot partition.
|
# packages whose contents should be copied directly into the /boot partition.
|
||||||
# e.g. EFI loaders, u-boot bootloader, etc.
|
# e.g. EFI loaders, u-boot bootloader, etc.
|
||||||
@@ -78,7 +79,9 @@ in
|
|||||||
"ext4" = pkgs.imageBuilder.fileSystem.makeExt4;
|
"ext4" = pkgs.imageBuilder.fileSystem.makeExt4;
|
||||||
"btrfs" = pkgs.imageBuilder.fileSystem.makeBtrfs;
|
"btrfs" = pkgs.imageBuilder.fileSystem.makeBtrfs;
|
||||||
};
|
};
|
||||||
in {
|
in
|
||||||
|
lib.mkIf cfg.enable
|
||||||
|
{
|
||||||
system.build.img-without-firmware = with pkgs; imageBuilder.diskImage.makeGPT {
|
system.build.img-without-firmware = with pkgs; imageBuilder.diskImage.makeGPT {
|
||||||
name = "nixos";
|
name = "nixos";
|
||||||
diskID = vfatUuidFromFs bootFs;
|
diskID = vfatUuidFromFs bootFs;
|
||||||
|
@@ -1,94 +0,0 @@
|
|||||||
# borrows from:
|
|
||||||
# https://xeiaso.net/blog/paranoid-nixos-2021-07-18
|
|
||||||
# https://elis.nu/blog/2020/05/nixos-tmpfs-as-root/
|
|
||||||
# https://github.com/nix-community/impermanence
|
|
||||||
{ lib, config, impermanence, ... }:
|
|
||||||
|
|
||||||
with lib;
|
|
||||||
let
|
|
||||||
cfg = config.sane.impermanence;
|
|
||||||
# taken from sops-nix code: checks if any secrets are needed to create /etc/shadow
|
|
||||||
secretsForUsers = (lib.filterAttrs (_: v: v.neededForUsers) config.sops.secrets) != {};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
sane.impermanence.enable = mkOption {
|
|
||||||
default = false;
|
|
||||||
type = types.bool;
|
|
||||||
};
|
|
||||||
sane.impermanence.home-dirs = mkOption {
|
|
||||||
default = [];
|
|
||||||
type = types.listOf (types.either types.str (types.attrsOf types.str));
|
|
||||||
};
|
|
||||||
sane.impermanence.service-dirs = mkOption {
|
|
||||||
default = [];
|
|
||||||
type = types.listOf (types.either types.str (types.attrsOf types.str));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = let
|
|
||||||
map-dir = defaults: dir: if isString dir then
|
|
||||||
map-dir defaults { directory = "${defaults.directory}${dir}"; }
|
|
||||||
else
|
|
||||||
defaults // dir
|
|
||||||
;
|
|
||||||
map-dirs = defaults: dirs: builtins.map (map-dir defaults) dirs;
|
|
||||||
|
|
||||||
map-home-dirs = map-dirs { user = "colin"; group = "users"; mode = "0755"; directory = "/home/colin/"; };
|
|
||||||
map-sys-dirs = map-dirs { user = "root"; group = "root"; mode = "0755"; directory = ""; };
|
|
||||||
|
|
||||||
in mkIf cfg.enable {
|
|
||||||
sane.image.extraDirectories = [ "/nix/persist/var/log" ];
|
|
||||||
environment.persistence."/nix/persist" = {
|
|
||||||
directories = (map-home-dirs cfg.home-dirs) ++ (map-sys-dirs [
|
|
||||||
# NB: this `0700` here clobbers the perms for /persist/etc, breaking boot on freshly-deployed devices
|
|
||||||
# { mode = "0700"; directory = "/etc/NetworkManager/system-connections"; }
|
|
||||||
# "/etc/nixos"
|
|
||||||
# "/etc/ssh" # persist only the specific files we want, instead
|
|
||||||
"/var/log"
|
|
||||||
"/var/backup" # for e.g. postgres dumps
|
|
||||||
# "/var/lib/AccountsService" # not sure what this is, but it's empty
|
|
||||||
"/var/lib/alsa" # preserve output levels, default devices
|
|
||||||
# "/var/lib/blueman" # files aren't human readable
|
|
||||||
"/var/lib/bluetooth" # preserve bluetooth handshakes
|
|
||||||
"/var/lib/colord" # preserve color calibrations (?)
|
|
||||||
# "/var/lib/dhclient" # empty on lappy; dunno about desko
|
|
||||||
# "/var/lib/fwupd" # not sure why this would need persistent state
|
|
||||||
# "/var/lib/geoclue" # empty on lappy
|
|
||||||
# "/var/lib/lockdown" # empty on desko; might store secrets after iOS handshake?
|
|
||||||
# "/var/lib/logrotate.status" # seems redundant with what's in /var/log?
|
|
||||||
"/var/lib/machines" # maybe not needed, but would be painful to add a VM and forget.
|
|
||||||
# "/var/lib/misc" # empty on lappy
|
|
||||||
# "/var/lib/NetworkManager" # looks to be mostly impermanent state?
|
|
||||||
# "/var/lib/NetworkManager-fortisslvpn" # empty on lappy
|
|
||||||
# "/var/lib/nixos" # has some uid/gid maps, but we enforce these to be deterministic.
|
|
||||||
# "/var/lib/PackageKit" # wtf is this?
|
|
||||||
# "/var/lib/power-profiles-daemon" # redundant with nixos declarations
|
|
||||||
# "/var/lib/private" # empty on lappy
|
|
||||||
# "/var/lib/systemd" # nothing obviously necessary
|
|
||||||
# "/var/lib/udisks2" # empty on lappy
|
|
||||||
# "/var/lib/upower" # historic charge data. unnecessary, but maybe used somewhere?
|
|
||||||
#
|
|
||||||
# servo additions:
|
|
||||||
] ++ cfg.service-dirs);
|
|
||||||
# /etc/machine-id is a globally unique identifier used for:
|
|
||||||
# - systemd-networkd: DHCP lease renewal (instead of keying by the MAC address)
|
|
||||||
# - systemd-journald: to filter logs by host
|
|
||||||
# - chromium (potentially to track re-installations)
|
|
||||||
# - gdbus; system services that might upgrade to AF_LOCAL if both services can confirm they're on the same machine
|
|
||||||
# of these, systemd-networkd is the only legitimate case to persist the machine-id.
|
|
||||||
# depersisting it should be "safe"; edge-cases like systemd-networkd can be directed to use some other ID if necessary.
|
|
||||||
# nixos-impermanence shows binding the host ssh priv key to this; i could probably hash the host key into /etc/machine-id if necessary.
|
|
||||||
# files = [ "/etc/machine-id" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# secret decoding depends on /etc/ssh keys, which may be persisted
|
|
||||||
system.activationScripts.setupSecrets.deps = [ "persist-ssh-host-keys" ];
|
|
||||||
system.activationScripts.setupSecretsForUsers = lib.mkIf secretsForUsers {
|
|
||||||
deps = [ "persist-ssh-host-keys" ];
|
|
||||||
};
|
|
||||||
# populated by ssh.nix, which persists /etc/ssh/host_keys
|
|
||||||
system.activationScripts.persist-ssh-host-keys.text = lib.mkDefault "";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
72
modules/lib/default.nix
Normal file
72
modules/lib/default.nix
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
{ lib, ... }@moduleArgs:
|
||||||
|
|
||||||
|
let
|
||||||
|
sane-lib = rec {
|
||||||
|
feeds = import ./feeds.nix moduleArgs;
|
||||||
|
fs = import ./fs.nix moduleArgs;
|
||||||
|
merge = import ./merge.nix ({ inherit sane-lib; } // moduleArgs);
|
||||||
|
path = import ./path.nix moduleArgs;
|
||||||
|
types = import ./types.nix moduleArgs;
|
||||||
|
|
||||||
|
# re-exports
|
||||||
|
inherit (merge) mkTypedMerge;
|
||||||
|
|
||||||
|
# like `builtins.listToAttrs` but any duplicated `name` throws error on access.
|
||||||
|
# Type: listToDisjointAttrs :: [{ name :: String, value :: Any }] -> AttrSet
|
||||||
|
listToDisjointAttrs = l: joinAttrsets (builtins.map nameValueToAttrs l);
|
||||||
|
|
||||||
|
# true if p is a prefix of l (even if p == l)
|
||||||
|
# Type: isPrefixOfList :: [Any] -> [Any] -> bool
|
||||||
|
isPrefixOfList = p: l: (lib.sublist 0 (lib.length p) l) == p;
|
||||||
|
|
||||||
|
# merges N attrsets
|
||||||
|
# Type: flattenAttrsList :: [AttrSet] -> AttrSet
|
||||||
|
joinAttrsets = l: lib.foldl' lib.attrsets.unionOfDisjoint {} l;
|
||||||
|
|
||||||
|
# evaluate a `{ name, value }` pair in the same way that `listToAttrs` does.
|
||||||
|
# Type: nameValueToAttrs :: { name :: String, value :: Any } -> Any
|
||||||
|
nameValueToAttrs = { name, value }: {
|
||||||
|
"${name}" = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
# if `maybe-null` is non-null, yield that. else, return the `default`.
|
||||||
|
withDefault = default: maybe-null: if maybe-null != null then
|
||||||
|
maybe-null
|
||||||
|
else
|
||||||
|
default;
|
||||||
|
|
||||||
|
# removes null entries from the provided AttrSet. acts recursively.
|
||||||
|
# Type: filterNonNull :: AttrSet -> AttrSet
|
||||||
|
filterNonNull = attrs: lib.filterAttrsRecursive (n: v: v != null) attrs;
|
||||||
|
|
||||||
|
# return only the subset of `attrs` whose name is in the provided set.
|
||||||
|
# Type: filterByName :: [String] -> AttrSet
|
||||||
|
filterByName = names: attrs: lib.filterAttrs
|
||||||
|
(name: value: builtins.elem name names)
|
||||||
|
attrs;
|
||||||
|
|
||||||
|
# transform a list into an AttrSet via a function which maps an element to a { name, value } pair.
|
||||||
|
# it's an error for the same name to be specified more than once
|
||||||
|
# Type: mapToAttrs :: (a -> { name :: String, value :: Any }) -> [a] -> AttrSet
|
||||||
|
mapToAttrs = f: list: listToDisjointAttrs (builtins.map f list);
|
||||||
|
|
||||||
|
# flatten a nested AttrSet into a list of { path = [String]; value } items.
|
||||||
|
# the output contains only non-attr leafs.
|
||||||
|
# so e.g. { a.b = 1; } -> [ { path = ["a" "b"]; value = 1; } ]
|
||||||
|
# but e.g. { a.b = {}; } -> []
|
||||||
|
#
|
||||||
|
# Type: flattenAttrs :: AttrSet[AttrSet|Any] -> [{ path :: String, value :: Any }]
|
||||||
|
flattenAttrs = flattenAttrs' [];
|
||||||
|
flattenAttrs' = path: value: if builtins.isAttrs value then (
|
||||||
|
builtins.concatLists (
|
||||||
|
lib.mapAttrsToList
|
||||||
|
(name: flattenAttrs' (path ++ [ name ]))
|
||||||
|
value
|
||||||
|
)
|
||||||
|
) else [
|
||||||
|
{
|
||||||
|
inherit path value;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in sane-lib
|
38
modules/lib/feeds.nix
Normal file
38
modules/lib/feeds.nix
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
# PRIMARY API: generate a OPML file from a list of feeds
|
||||||
|
feedsToOpml = feeds: opmlTopLevel (opmlGroups (partitionByCat feeds));
|
||||||
|
|
||||||
|
# only keep feeds whose category is one of the provided
|
||||||
|
filterByFormat = fmts: builtins.filter (feed: builtins.elem feed.format fmts);
|
||||||
|
|
||||||
|
## INTERNAL APIS
|
||||||
|
|
||||||
|
# transform a list of feeds into an attrs mapping cat => [ feed0 feed1 ... ]
|
||||||
|
partitionByCat = feeds: builtins.groupBy (f: f.cat) feeds;
|
||||||
|
|
||||||
|
# represents a single RSS feed.
|
||||||
|
opmlTerminal = feed: ''<outline xmlUrl="${feed.url}" type="rss"/>'';
|
||||||
|
# a list of RSS feeds.
|
||||||
|
opmlTerminals = feeds: lib.concatStringsSep "\n" (builtins.map opmlTerminal feeds);
|
||||||
|
# one node which packages some flat grouping of terminals.
|
||||||
|
opmlGroup = title: feeds: ''
|
||||||
|
<outline text="${title}" title="${title}">
|
||||||
|
${opmlTerminals feeds}
|
||||||
|
</outline>
|
||||||
|
'';
|
||||||
|
# a list of groups (`groupMap` is an attrs mapping groupName => [ feed0 feed1 ... ]).
|
||||||
|
opmlGroups = groupMap: lib.concatStringsSep "\n" (
|
||||||
|
builtins.attrValues (builtins.mapAttrs opmlGroup groupMap)
|
||||||
|
);
|
||||||
|
# top-level OPML file which could be consumed by something else.
|
||||||
|
opmlTopLevel = body: ''
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<opml version="2.0">
|
||||||
|
<body>
|
||||||
|
${body}
|
||||||
|
</body>
|
||||||
|
</opml>
|
||||||
|
'';
|
||||||
|
}
|
9
modules/lib/fs.nix
Normal file
9
modules/lib/fs.nix
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
wanted = lib.attrsets.unionOfDisjoint { wantedBeforeBy = [ "multi-user.target" ]; };
|
||||||
|
wantedSymlink = symlink: wanted { inherit symlink; };
|
||||||
|
wantedSymlinkTo = target: wantedSymlink { inherit target; };
|
||||||
|
wantedText = text: wantedSymlink { inherit text; };
|
||||||
|
}
|
||||||
|
|
100
modules/lib/merge.nix
Normal file
100
modules/lib/merge.nix
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{ lib, sane-lib, ... }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
# type-checked `lib.mkMerge`, intended to be usable at the top of a file.
|
||||||
|
# `take` is a function which defines a spec enforced against every item to be merged.
|
||||||
|
# for example:
|
||||||
|
# take = f: { x = f.x; y.z = f.y.z; };
|
||||||
|
# - the output is guaranteed to have an `x` attribute and a `y.z` attribute and nothing else.
|
||||||
|
# - each output is a `lib.mkMerge` of the corresponding paths across the input lists.
|
||||||
|
# - if an item in the input list defines an attr not captured by `f`, this function will throw.
|
||||||
|
#
|
||||||
|
# Type: mkTypedMerge :: (Attrs -> Attrs) -> [Attrs] -> Attrs
|
||||||
|
mkTypedMerge = take: l:
|
||||||
|
let
|
||||||
|
pathsToMerge = findTerminalPaths take [];
|
||||||
|
discharged = dischargeAll l pathsToMerge;
|
||||||
|
merged = builtins.map (p: lib.setAttrByPath p (mergeAtPath p discharged)) pathsToMerge;
|
||||||
|
in
|
||||||
|
assert builtins.all (assertNoExtraPaths pathsToMerge) discharged;
|
||||||
|
sane-lib.joinAttrsets merged;
|
||||||
|
|
||||||
|
# `take` is as in mkTypedMerge. this function queries which items `take` is interested in.
|
||||||
|
# for example:
|
||||||
|
# take = f: { x = f.x; y.z = f.y.z; };
|
||||||
|
# - for `path == []` we return the toplevel attr names: [ "x" "y"]
|
||||||
|
# - for `path == [ "y" ]` we return [ "z" ]
|
||||||
|
# - for `path == [ "x" ]` or `path == [ "y" "z" ]` we return []
|
||||||
|
#
|
||||||
|
# Type: findSubNames :: (Attrs -> Attrs) -> [String] -> [String]
|
||||||
|
findSubNames = take: path:
|
||||||
|
let
|
||||||
|
# define the current path, but nothing more.
|
||||||
|
curLevel = lib.setAttrByPath path {};
|
||||||
|
# `take curLevel` will act one of two ways here:
|
||||||
|
# - { $path = f.$path; } => { $path = {}; };
|
||||||
|
# - { $path.subAttr = f.$path.subAttr; } => { $path = { subAttr = ?; }; }
|
||||||
|
# so, index $path into the output of `take`,
|
||||||
|
# and if it has any attrs (like `subAttr`) that means we're interested in those too.
|
||||||
|
nextLevel = lib.getAttrFromPath path (take curLevel);
|
||||||
|
in
|
||||||
|
builtins.attrNames nextLevel;
|
||||||
|
|
||||||
|
# computes a list of all terminal paths that `take` is interested in,
|
||||||
|
# where each path is a list of attr names to descend to reach that terminal.
|
||||||
|
# Type: findTerminalPaths :: (Attrs -> Attrs) -> [String] -> [[String]]
|
||||||
|
findTerminalPaths = take: path:
|
||||||
|
let
|
||||||
|
subNames = findSubNames take path;
|
||||||
|
in if subNames == [] then
|
||||||
|
[ path ]
|
||||||
|
else
|
||||||
|
lib.concatMap (name: findTerminalPaths take (path ++ [name])) subNames;
|
||||||
|
|
||||||
|
# ensures that all nodes in the attrset from the root to and including the given path
|
||||||
|
# are ordinary attrs -- if they exist.
|
||||||
|
# this has to return a list of Attrs, in case any portion of the path was previously merged.
|
||||||
|
# by extension, each returned item is a subset of the original item, and might not have *all* the paths that the original has.
|
||||||
|
# Type: dischargeToPath :: [String] -> Attrs -> [Attrs]
|
||||||
|
dischargeToPath = path: i:
|
||||||
|
let
|
||||||
|
items = lib.pushDownProperties i;
|
||||||
|
# now items is a list where every element is undecorated at the toplevel.
|
||||||
|
# e.g. each item is an ordinary attrset or primitive.
|
||||||
|
# we still need to discharge the *rest* of the path though, for every item.
|
||||||
|
name = lib.head path;
|
||||||
|
downstream = lib.tail path;
|
||||||
|
dischargeDownstream = it: if path != [] && it ? name then
|
||||||
|
builtins.map (v: it // { "${name}" = v; }) (dischargeToPath downstream it."${name}")
|
||||||
|
else
|
||||||
|
[ it ];
|
||||||
|
in
|
||||||
|
lib.concatMap dischargeDownstream items;
|
||||||
|
|
||||||
|
# discharge many items but only over one path.
|
||||||
|
# Type: dischargeItemsToPaths :: [Attrs] -> String -> [Attrs]
|
||||||
|
dischargeItemsToPath = l: path: builtins.concatMap (dischargeToPath path) l;
|
||||||
|
|
||||||
|
# Type: dischargeAll :: [Attrs] -> [String] -> [Attrs]
|
||||||
|
dischargeAll = l: paths:
|
||||||
|
builtins.foldl' dischargeItemsToPath l paths;
|
||||||
|
|
||||||
|
# merges all present values for the provided path
|
||||||
|
# Type: mergeAtPath :: [String] -> [Attrs] -> (lib.mkMerge)
|
||||||
|
mergeAtPath = path: l:
|
||||||
|
let
|
||||||
|
itemsToMerge = builtins.filter (lib.hasAttrByPath path) l;
|
||||||
|
in lib.mkMerge (builtins.map (lib.getAttrFromPath path) itemsToMerge);
|
||||||
|
|
||||||
|
# check that attrset `i` contains no terminals other than those specified in (or direct ancestors of) paths
|
||||||
|
assertNoExtraPaths = paths: i:
|
||||||
|
let
|
||||||
|
# since the act of discharging should have forced all the relevant data out to the leaves,
|
||||||
|
# we just set each expected terminal to null (initializing the parents when necessary)
|
||||||
|
# and that gives a standard value for any fully-consumed items that we can do equality comparisons with.
|
||||||
|
wipePath = acc: path: lib.recursiveUpdate acc (lib.setAttrByPath path null);
|
||||||
|
remainder = builtins.foldl' wipePath i paths;
|
||||||
|
expected-remainder = builtins.foldl' wipePath {} paths;
|
||||||
|
in
|
||||||
|
assert remainder == expected-remainder; true;
|
||||||
|
}
|
44
modules/lib/path.nix
Normal file
44
modules/lib/path.nix
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{ lib, utils, ... }:
|
||||||
|
|
||||||
|
let path = rec {
|
||||||
|
|
||||||
|
# split the string path into a list of string components.
|
||||||
|
# root directory "/" becomes the empty list [].
|
||||||
|
# implicitly performs normalization so that:
|
||||||
|
# split "a//b/" => ["a" "b"]
|
||||||
|
# split "/a/b" => ["a" "b"]
|
||||||
|
split = str: builtins.filter (seg: seg != "") (lib.splitString "/" str);
|
||||||
|
# given an array of components, returns the equivalent string path
|
||||||
|
join = comps: "/" + (builtins.concatStringsSep "/" comps);
|
||||||
|
# given an a sequence of string paths, concatenates them into one long string path
|
||||||
|
concat = paths: path.join (builtins.concatLists (builtins.map path.split paths));
|
||||||
|
# normalize the given path
|
||||||
|
norm = str: path.join (path.split str);
|
||||||
|
# return the parent directory. doesn't care about leading/trailing slashes.
|
||||||
|
# the parent of "/" is "/".
|
||||||
|
parent = str: path.norm (builtins.dirOf (path.norm str));
|
||||||
|
hasParent = str: (path.parent str) != (path.norm str);
|
||||||
|
# return the path from `from` to `to`, but keeping absolute form
|
||||||
|
# e.g. `pathFrom "/home/colin" "/home/colin/foo/bar"` -> "/foo/bar"
|
||||||
|
|
||||||
|
# return the last path component; error on the empty path
|
||||||
|
leaf = str: lib.last (split str);
|
||||||
|
|
||||||
|
from = start: end: let
|
||||||
|
s = path.norm start;
|
||||||
|
e = path.norm end;
|
||||||
|
in (
|
||||||
|
assert lib.hasPrefix s e;
|
||||||
|
"/" + (lib.removePrefix s e)
|
||||||
|
);
|
||||||
|
|
||||||
|
# yield every node between start and end, including each the endpoints
|
||||||
|
# e.g. walk "/foo" "/foo/bar/baz" => [ "/foo" "/foo/bar" "/foo/bar/baz" ]
|
||||||
|
# XXX: assumes input paths are normalized
|
||||||
|
walk = start: end: if start == end then
|
||||||
|
[ start ]
|
||||||
|
else
|
||||||
|
(walk start (parent end)) ++ [ end ]
|
||||||
|
;
|
||||||
|
};
|
||||||
|
in path
|
42
modules/lib/types.nix
Normal file
42
modules/lib/types.nix
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
rec {
|
||||||
|
# "Access Control List", only it's just a user:group and file mode
|
||||||
|
# compatible with `chown` and `chmod`
|
||||||
|
aclMod = {
|
||||||
|
options = {
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str; # TODO: use uid?
|
||||||
|
};
|
||||||
|
group = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
mode = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
acl = types.submodule aclMod;
|
||||||
|
|
||||||
|
# this is acl, but doesn't require to be fully specified.
|
||||||
|
# a typical use case is when there's a complete acl, and the user
|
||||||
|
# wants to override just one attribute of it.
|
||||||
|
aclOverrideMod = {
|
||||||
|
options = {
|
||||||
|
user = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
group = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
mode = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
aclOverride = types.submodule aclOverrideMod;
|
||||||
|
}
|
@@ -4,6 +4,11 @@ with lib;
|
|||||||
with pkgs;
|
with pkgs;
|
||||||
let
|
let
|
||||||
cfg = config.sane.packages;
|
cfg = config.sane.packages;
|
||||||
|
|
||||||
|
imagemagick = pkgs.imagemagick.override {
|
||||||
|
ghostscriptSupport = true;
|
||||||
|
};
|
||||||
|
|
||||||
consolePkgs = [
|
consolePkgs = [
|
||||||
backblaze-b2
|
backblaze-b2
|
||||||
cdrtools
|
cdrtools
|
||||||
@@ -12,11 +17,13 @@ let
|
|||||||
efivar
|
efivar
|
||||||
flashrom
|
flashrom
|
||||||
fwupd
|
fwupd
|
||||||
|
ghostscript # TODO: imagemagick wrapper should add gs to PATH
|
||||||
gnupg
|
gnupg
|
||||||
gocryptfs
|
gocryptfs
|
||||||
gopass
|
gopass
|
||||||
gopass-jsonapi
|
gopass-jsonapi
|
||||||
ifuse
|
ifuse
|
||||||
|
imagemagick
|
||||||
ipfs
|
ipfs
|
||||||
libimobiledevice
|
libimobiledevice
|
||||||
libsecret # for managing user keyrings
|
libsecret # for managing user keyrings
|
||||||
@@ -59,18 +66,18 @@ let
|
|||||||
celluloid # mpv frontend
|
celluloid # mpv frontend
|
||||||
chromium
|
chromium
|
||||||
clinfo
|
clinfo
|
||||||
{ pkg = dino; private = ".local/share/dino"; }
|
{ pkg = dino; private = [ ".local/share/dino" ]; }
|
||||||
electrum
|
electrum
|
||||||
|
|
||||||
# creds/session keys, etc
|
# creds/session keys, etc
|
||||||
{ pkg = element-desktop; private = ".config/Element"; }
|
{ pkg = element-desktop; private = [ ".config/Element" ]; }
|
||||||
# `emote` will show a first-run dialog based on what's in this directory.
|
# `emote` will show a first-run dialog based on what's in this directory.
|
||||||
# mostly, it just keeps a LRU of previously-used emotes to optimize display order.
|
# mostly, it just keeps a LRU of previously-used emotes to optimize display order.
|
||||||
# TODO: package [smile](https://github.com/mijorus/smile) for probably a better mobile experience.
|
# TODO: package [smile](https://github.com/mijorus/smile) for probably a better mobile experience.
|
||||||
{ pkg = emote; dir = ".local/share/Emote"; }
|
{ pkg = emote; dir = [ ".local/share/Emote" ]; }
|
||||||
evince # works on phosh
|
evince # works on phosh
|
||||||
|
|
||||||
# { pkg = fluffychat-moby; dir = ".local/share/chat.fluffy.fluffychat"; } # TODO: ship normal fluffychat on non-moby?
|
# { pkg = fluffychat-moby; dir = [ ".local/share/chat.fluffy.fluffychat" ]; } # TODO: ship normal fluffychat on non-moby?
|
||||||
|
|
||||||
foliate
|
foliate
|
||||||
font-manager
|
font-manager
|
||||||
@@ -78,8 +85,10 @@ let
|
|||||||
# XXX by default fractal stores its state in ~/.local/share/<UUID>.
|
# XXX by default fractal stores its state in ~/.local/share/<UUID>.
|
||||||
# after logging in, manually change ~/.local/share/keyrings/... to point it to some predictable subdir.
|
# after logging in, manually change ~/.local/share/keyrings/... to point it to some predictable subdir.
|
||||||
# then reboot (so that libsecret daemon re-loads the keyring...?)
|
# then reboot (so that libsecret daemon re-loads the keyring...?)
|
||||||
{ pkg = fractal-next; private = ".local/share/fractal"; }
|
# { pkg = fractal-latest; private = [ ".local/share/fractal" ]; }
|
||||||
|
# { pkg = fractal-next; private = [ ".local/share/fractal" ]; }
|
||||||
|
|
||||||
|
gajim # XMPP client
|
||||||
gimp # broken on phosh
|
gimp # broken on phosh
|
||||||
gnome.cheese
|
gnome.cheese
|
||||||
gnome.dconf-editor
|
gnome.dconf-editor
|
||||||
@@ -93,7 +102,7 @@ let
|
|||||||
gnome.gnome-terminal # works on phosh
|
gnome.gnome-terminal # works on phosh
|
||||||
gnome.gnome-weather
|
gnome.gnome-weather
|
||||||
|
|
||||||
{ pkg = gpodder-configured; dir = "gPodder/Downloads"; }
|
{ pkg = gpodder-configured; dir = [ "gPodder/Downloads" ]; }
|
||||||
|
|
||||||
gthumb
|
gthumb
|
||||||
handbrake
|
handbrake
|
||||||
@@ -106,15 +115,21 @@ let
|
|||||||
lollypop
|
lollypop
|
||||||
mesa-demos
|
mesa-demos
|
||||||
|
|
||||||
{ pkg = mpv; dir = ".config/mpv/watch_later"; }
|
{ pkg = mpv; dir = [ ".config/mpv/watch_later" ]; }
|
||||||
|
|
||||||
networkmanagerapplet
|
networkmanagerapplet
|
||||||
|
|
||||||
# not strictly necessary, but allows caching articles; offline use, etc.
|
# not strictly necessary, but allows caching articles; offline use, etc.
|
||||||
{ pkg = newsflash; dir = ".local/share/news-flash"; }
|
{ pkg = newsflash; dir = [ ".local/share/news-flash" ]; }
|
||||||
|
|
||||||
|
{ pkg = nheko; private = [
|
||||||
|
".config/nheko" # config file (including client token)
|
||||||
|
".cache/nheko" # media cache
|
||||||
|
".local/share/nheko" # per-account state database
|
||||||
|
]; }
|
||||||
|
|
||||||
# settings (electron app). TODO: can i manage these settings with home-manager?
|
# settings (electron app). TODO: can i manage these settings with home-manager?
|
||||||
{ pkg = obsidian; dir = ".config/obsidian"; }
|
{ pkg = obsidian; dir = [ ".config/obsidian" ]; }
|
||||||
|
|
||||||
pavucontrol
|
pavucontrol
|
||||||
# picard # music tagging
|
# picard # music tagging
|
||||||
@@ -127,13 +142,14 @@ let
|
|||||||
# it doesn't obey a conventional ~/Music/{Artist}/{Album}/{Track} notation, so no symlinking
|
# it doesn't obey a conventional ~/Music/{Artist}/{Album}/{Track} notation, so no symlinking
|
||||||
# config (e.g. server connection details) is persisted in ~/.config/sublime-music/config.json
|
# config (e.g. server connection details) is persisted in ~/.config/sublime-music/config.json
|
||||||
# possible to pass config as a CLI arg (sublime-music -c config.json)
|
# possible to pass config as a CLI arg (sublime-music -c config.json)
|
||||||
{ pkg = sublime-music; dir = ".local/share/sublime-music"; }
|
# { pkg = sublime-music; dir = [ ".local/share/sublime-music" ]; }
|
||||||
tdesktop # broken on phosh
|
{ pkg = sublime-music-mobile; dir = [ ".local/share/sublime-music" ]; }
|
||||||
|
{ pkg = tdesktop; private = [ ".local/share/TelegramDesktop" ]; } # broken on phosh
|
||||||
|
|
||||||
{ pkg = tokodon; dir = ".cache/KDE/tokodon"; }
|
{ pkg = tokodon; private = [ ".cache/KDE/tokodon" ]; }
|
||||||
|
|
||||||
# vlc remembers play position in ~/.config/vlc/vlc-qt-interface.conf
|
# vlc remembers play position in ~/.config/vlc/vlc-qt-interface.conf
|
||||||
{ pkg = vlc; dir = ".config/vlc"; }
|
{ pkg = vlc; dir = [ ".config/vlc" ]; }
|
||||||
|
|
||||||
whalebird # pleroma client. input is broken on phosh
|
whalebird # pleroma client. input is broken on phosh
|
||||||
xdg-utils # for xdg-open
|
xdg-utils # for xdg-open
|
||||||
@@ -148,41 +164,44 @@ let
|
|||||||
# XXX 2022-07-31: fix to allow links to open in default web-browser:
|
# XXX 2022-07-31: fix to allow links to open in default web-browser:
|
||||||
# https://github.com/NixOS/nixpkgs/issues/78961
|
# https://github.com/NixOS/nixpkgs/issues/78961
|
||||||
nss = pkgs.nss_latest;
|
nss = pkgs.nss_latest;
|
||||||
}); in { pkg = discord; dir = ".config/discord"; })
|
}); in { pkg = discord; private = [ ".config/discord" ]; })
|
||||||
|
|
||||||
# kaiteki # Pleroma client
|
# kaiteki # Pleroma client
|
||||||
# gnome.zenity # for kaiteki (it will use qarma, kdialog, or zenity)
|
# gnome.zenity # for kaiteki (it will use qarma, kdialog, or zenity)
|
||||||
|
# gpt2tc # XXX: unreliable mirror
|
||||||
|
|
||||||
logseq
|
logseq
|
||||||
losslesscut-bin
|
losslesscut-bin
|
||||||
makemkv
|
makemkv
|
||||||
|
|
||||||
# actual monero blockchain (not wallet/etc; safe to delete, just slow to regenerate)
|
# actual monero blockchain (not wallet/etc; safe to delete, just slow to regenerate)
|
||||||
{ pkg = monero-gui; dir = ".bitmonero"; }
|
{ pkg = monero-gui; dir = [ ".bitmonero" ]; }
|
||||||
|
|
||||||
# creds, media
|
# creds, media
|
||||||
{ pkg = signal-desktop; dir = ".config/Signal"; }
|
{ pkg = signal-desktop; private = [ ".config/Signal" ]; }
|
||||||
|
|
||||||
# creds. TODO: can i manage this with home-manager?
|
# creds. TODO: can i manage this with home-manager?
|
||||||
{ pkg = spotify; dir = ".config/spotify"; }
|
{ pkg = spotify; dir = [ ".config/spotify" ]; }
|
||||||
|
|
||||||
# hardenedMalloc solves a crash at startup
|
# hardenedMalloc solves a crash at startup
|
||||||
(tor-browser-bundle-bin.override { useHardenedMalloc = false; })
|
(tor-browser-bundle-bin.override { useHardenedMalloc = false; })
|
||||||
|
|
||||||
# zcash coins. safe to delete, just slow to regenerate (10-60 minutes)
|
# zcash coins. safe to delete, just slow to regenerate (10-60 minutes)
|
||||||
{ pkg = zecwallet-lite; private = ".zcash"; }
|
{ pkg = zecwallet-lite; private = [ ".zcash" ]; }
|
||||||
] else []);
|
] else []);
|
||||||
|
|
||||||
# general-purpose utilities that we want any user to be able to access
|
# general-purpose utilities that we want any user to be able to access
|
||||||
# (specifically: root, in case of rescue)
|
# (specifically: root, in case of rescue)
|
||||||
systemPkgs = [
|
systemPkgs = [
|
||||||
btrfs-progs
|
btrfs-progs
|
||||||
|
cacert.unbundled # some services require unbundled /etc/ssl/certs
|
||||||
cryptsetup
|
cryptsetup
|
||||||
dig
|
dig
|
||||||
efibootmgr
|
efibootmgr
|
||||||
fatresize
|
fatresize
|
||||||
fd
|
fd
|
||||||
file
|
file
|
||||||
|
gawk
|
||||||
gptfdisk
|
gptfdisk
|
||||||
hdparm
|
hdparm
|
||||||
htop
|
htop
|
||||||
@@ -200,10 +219,14 @@ let
|
|||||||
parted
|
parted
|
||||||
pciutils
|
pciutils
|
||||||
powertop
|
powertop
|
||||||
|
pstree
|
||||||
ripgrep
|
ripgrep
|
||||||
screen
|
screen
|
||||||
smartmontools
|
smartmontools
|
||||||
socat
|
socat
|
||||||
|
strace
|
||||||
|
tcpdump
|
||||||
|
tree
|
||||||
usbutils
|
usbutils
|
||||||
wget
|
wget
|
||||||
];
|
];
|
||||||
@@ -223,15 +246,38 @@ let
|
|||||||
rustup
|
rustup
|
||||||
swig
|
swig
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pkgSpec = types.submodule {
|
||||||
|
options = {
|
||||||
|
pkg = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
};
|
||||||
|
dir = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = "list of home-relative paths to persist for this package";
|
||||||
|
};
|
||||||
|
private = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = "list of home-relative paths to persist (in encrypted format) for this package";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
toPkgSpec = types.coercedTo types.package (p: { pkg = p; }) pkgSpec;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
# packages to deploy to the user's home
|
# packages to deploy to the user's home
|
||||||
sane.packages.extraUserPkgs = mkOption {
|
sane.packages.extraUserPkgs = mkOption {
|
||||||
default = [ ];
|
default = [ ];
|
||||||
# each entry can be either a package, or attrs:
|
type = types.listOf toPkgSpec;
|
||||||
# { pkg = package; dir = optional string; private = optional string };
|
};
|
||||||
type = types.listOf (types.either types.package types.attrs);
|
sane.packages.extraGuiPkgs = mkOption {
|
||||||
|
default = [ ];
|
||||||
|
type = types.listOf toPkgSpec;
|
||||||
|
description = "packages to only ship if gui's enabled";
|
||||||
};
|
};
|
||||||
sane.packages.enableConsolePkgs = mkOption {
|
sane.packages.enableConsolePkgs = mkOption {
|
||||||
default = false;
|
default = false;
|
||||||
@@ -258,15 +304,19 @@ in
|
|||||||
sane.packages.enabledUserPkgs = mkOption {
|
sane.packages.enabledUserPkgs = mkOption {
|
||||||
default = cfg.extraUserPkgs
|
default = cfg.extraUserPkgs
|
||||||
++ (if cfg.enableConsolePkgs then consolePkgs else [])
|
++ (if cfg.enableConsolePkgs then consolePkgs else [])
|
||||||
++ (if cfg.enableGuiPkgs then guiPkgs else [])
|
++ (if cfg.enableGuiPkgs then guiPkgs ++ cfg.extraGuiPkgs else [])
|
||||||
++ (if cfg.enableDevPkgs then devPkgs else [])
|
++ (if cfg.enableDevPkgs then devPkgs else [])
|
||||||
;
|
;
|
||||||
type = types.listOf (types.either types.package types.attrs);
|
type = types.listOf toPkgSpec;
|
||||||
description = "generated from other config options";
|
description = "generated from other config options";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
environment.systemPackages = mkIf cfg.enableSystemPkgs systemPkgs;
|
environment.systemPackages = mkIf cfg.enableSystemPkgs systemPkgs;
|
||||||
|
sane.persist.home.plaintext = concatLists (map (p: p.dir) cfg.enabledUserPkgs);
|
||||||
|
sane.persist.home.private = concatLists (map (p: p.private) cfg.enabledUserPkgs);
|
||||||
|
# XXX: this might not be necessary. try removing this and cacert.unbundled?
|
||||||
|
environment.etc."ssl/certs".source = mkIf cfg.enableSystemPkgs "${pkgs.cacert.unbundled}/etc/ssl/certs/*";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
18
modules/persist/computed.nix
Normal file
18
modules/persist/computed.nix
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{ config, lib, sane-lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
path = sane-lib.path;
|
||||||
|
cfg = config.sane.persist;
|
||||||
|
|
||||||
|
withPrefix = relativeTo: entries: lib.mapAttrs' (fspath: value: {
|
||||||
|
name = path.concat [ relativeTo fspath ];
|
||||||
|
inherit value;
|
||||||
|
}) entries;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# merge the `byPath` mappings from both `home` and `sys` into one namespace
|
||||||
|
sane.persist.byPath = lib.mkMerge [
|
||||||
|
(withPrefix "/home/colin" cfg.home.byPath)
|
||||||
|
(withPrefix "/" cfg.sys.byPath)
|
||||||
|
];
|
||||||
|
}
|
254
modules/persist/default.nix
Normal file
254
modules/persist/default.nix
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# borrows from:
|
||||||
|
# https://xeiaso.net/blog/paranoid-nixos-2021-07-18
|
||||||
|
# https://elis.nu/blog/2020/05/nixos-tmpfs-as-root/
|
||||||
|
# https://github.com/nix-community/impermanence
|
||||||
|
{ config, lib, pkgs, utils, sane-lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
path = sane-lib.path;
|
||||||
|
sane-types = sane-lib.types;
|
||||||
|
cfg = config.sane.persist;
|
||||||
|
|
||||||
|
storeType = types.submodule {
|
||||||
|
options = {
|
||||||
|
storeDescription = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
an optional description of the store, which is rendered like
|
||||||
|
{store.name}: {store.storeDescription}
|
||||||
|
for example, a store named "private" could have description "ecnrypted to the user's password and decrypted on login".
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
origin = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
prefix = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/";
|
||||||
|
description = ''
|
||||||
|
optional prefix to strip from children when stored here.
|
||||||
|
for example, prefix="/var/private" and mountpoint="/mnt/crypt/private"
|
||||||
|
would cause /var/private/www/root to be stored at /mnt/crypt/private/www/root instead of
|
||||||
|
/mnt/crypt/private/var/private/www/root.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
defaultMethod = mkOption {
|
||||||
|
type = types.enum [ "bind" "symlink" ];
|
||||||
|
default = "bind";
|
||||||
|
description = ''
|
||||||
|
preferred way to link items from the store into the fs
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
defaultOrdering.wantedBeforeBy = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ "local-fs.target" ];
|
||||||
|
description = ''
|
||||||
|
list of units or targets which would prefer that everything in this store
|
||||||
|
be initialized before they run, but failing to do so should not error the items in this list.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
defaultOrdering.wantedBy = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
list of units or targets which, upon activation, should activate all units in this store.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# allows a user to specify the store either by name or as an attrset
|
||||||
|
coercedToStore = types.coercedTo types.str (s: cfg.stores."${s}") storeType;
|
||||||
|
|
||||||
|
# options common to all entries, whether they're keyed by path or store
|
||||||
|
entryOpts = {
|
||||||
|
options = {
|
||||||
|
acl = mkOption {
|
||||||
|
type = sane-types.aclOverride;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
method = mkOption {
|
||||||
|
type = types.nullOr (types.enum [ "bind" "symlink" ]);
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
how to link the store entry into the fs
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# options for a single mountpoint / persistence where the store is specified externally
|
||||||
|
entryInStore = types.submodule [
|
||||||
|
entryOpts
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
directory = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
# allow "bar/baz" as shorthand for { directory = "bar/baz"; }
|
||||||
|
entryInStoreOrShorthand = types.coercedTo
|
||||||
|
types.str
|
||||||
|
(d: { directory = d; })
|
||||||
|
entryInStore;
|
||||||
|
|
||||||
|
# allow the user to provide the `acl` field inline: we pop acl sub-attributes placed at the
|
||||||
|
# toplevel and move them into an `acl` attribute.
|
||||||
|
convertInlineAcl = to: types.coercedTo
|
||||||
|
types.attrs
|
||||||
|
(orig: lib.recursiveUpdate
|
||||||
|
(builtins.removeAttrs orig ["user" "group" "mode" ])
|
||||||
|
{
|
||||||
|
acl = sane-lib.filterByName ["user" "group" "mode"] (orig.acl or {});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
to;
|
||||||
|
|
||||||
|
# entry where the path is specified externally
|
||||||
|
entryAtPath = types.submodule [
|
||||||
|
entryOpts
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
store = mkOption {
|
||||||
|
type = coercedToStore;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
# this submodule creates one attr per store, so that the user can specify something like:
|
||||||
|
# <option>.private.".cache/vim" = { mode = "0700"; };
|
||||||
|
# to place ".cache/vim" into the private store and create with the appropriate mode
|
||||||
|
dirsSubModule = types.submodule ({ config, ... }: {
|
||||||
|
options = lib.attrsets.unionOfDisjoint
|
||||||
|
(mapAttrs (store: store-cfg: mkOption {
|
||||||
|
default = [];
|
||||||
|
type = types.listOf (convertInlineAcl entryInStoreOrShorthand);
|
||||||
|
description = let
|
||||||
|
suffix = if store-cfg.storeDescription != null then
|
||||||
|
": ${store-cfg.storeDescription}"
|
||||||
|
else "";
|
||||||
|
in "directories to persist in ${store}${suffix}";
|
||||||
|
}) cfg.stores)
|
||||||
|
{
|
||||||
|
byPath = mkOption {
|
||||||
|
type = types.attrsOf (convertInlineAcl entryAtPath);
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
map of <path> => <path config> for all paths to be persisted.
|
||||||
|
this is computed from the other options, but users can also set it explicitly (useful for overriding)
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = let
|
||||||
|
# set the `store` attribute on one dir attrset
|
||||||
|
annotateWithStore = store: dir: {
|
||||||
|
"${dir.directory}".store = store;
|
||||||
|
};
|
||||||
|
# convert an `entryInStore` to an `entryAtPath` (less the `store` item)
|
||||||
|
dirToAttrs = dir: {
|
||||||
|
"${dir.directory}" = builtins.removeAttrs dir ["directory"];
|
||||||
|
};
|
||||||
|
store-names = attrNames cfg.stores;
|
||||||
|
# :: (store -> entry -> AttrSet) -> [AttrSet]
|
||||||
|
applyToAllStores = f: lib.concatMap
|
||||||
|
(store: map (f store) config."${store}")
|
||||||
|
store-names;
|
||||||
|
in {
|
||||||
|
byPath = lib.mkMerge (concatLists [
|
||||||
|
(applyToAllStores (store: dirToAttrs))
|
||||||
|
(applyToAllStores annotateWithStore)
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
sane.persist.enable = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
};
|
||||||
|
sane.persist.root-on-tmpfs = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = "define / fs root to be a tmpfs. make sure to mount some other device to /nix";
|
||||||
|
};
|
||||||
|
sane.persist.home = mkOption {
|
||||||
|
description = "directories to persist to disk, relative to a user's home ~";
|
||||||
|
default = {};
|
||||||
|
type = dirsSubModule;
|
||||||
|
};
|
||||||
|
sane.persist.sys = mkOption {
|
||||||
|
description = "directories to persist to disk, relative to the fs root /";
|
||||||
|
default = {};
|
||||||
|
type = dirsSubModule;
|
||||||
|
};
|
||||||
|
sane.persist.byPath = mkOption {
|
||||||
|
type = types.attrsOf (convertInlineAcl entryAtPath);
|
||||||
|
description = ''
|
||||||
|
map of <path> => <path config> for all paths to be persisted.
|
||||||
|
this is computed from the other options, but users can also set it explicitly (useful for overriding)
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
sane.persist.stores = mkOption {
|
||||||
|
type = types.attrsOf storeType;
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
map from human-friendly name to a fs sub-tree from which files are linked into the logical fs.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
./computed.nix
|
||||||
|
./root-on-tmpfs.nix
|
||||||
|
./stores
|
||||||
|
];
|
||||||
|
|
||||||
|
config = let
|
||||||
|
cfgFor = fspath: opt:
|
||||||
|
let
|
||||||
|
store = opt.store;
|
||||||
|
method = (sane-lib.withDefault store.defaultMethod) opt.method;
|
||||||
|
fsPathToStoreRelPath = fspath: path.from store.prefix fspath;
|
||||||
|
fsPathToBackingPath = fspath: path.concat [ store.origin (fsPathToStoreRelPath fspath) ];
|
||||||
|
in lib.mkMerge [
|
||||||
|
{
|
||||||
|
# create destination dir, with correct perms
|
||||||
|
sane.fs."${fspath}" = {
|
||||||
|
inherit (store.defaultOrdering) wantedBy wantedBeforeBy;
|
||||||
|
} // (lib.optionalAttrs (method == "bind") {
|
||||||
|
# inherit perms & make sure we don't mount until after the mount point is setup correctly.
|
||||||
|
dir.acl = opt.acl;
|
||||||
|
mount.bind = fsPathToBackingPath fspath;
|
||||||
|
}) // (lib.optionalAttrs (method == "symlink") {
|
||||||
|
symlink.acl = opt.acl;
|
||||||
|
symlink.target = fsPathToBackingPath fspath;
|
||||||
|
});
|
||||||
|
|
||||||
|
# create the backing path as a dir
|
||||||
|
sane.fs."${fsPathToBackingPath fspath}".dir = {};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
# default each item along the backing path to have the same acl as the location it would be mounted.
|
||||||
|
sane.fs = lib.mkMerge (builtins.map
|
||||||
|
(fsSubpath: {
|
||||||
|
"${fsPathToBackingPath fsSubpath}" = {
|
||||||
|
generated.acl = config.sane.fs."${fsSubpath}".generated.acl;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
(path.walk store.prefix fspath)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
];
|
||||||
|
configs = lib.mapAttrsToList cfgFor cfg.byPath;
|
||||||
|
take = f: { sane.fs = f.sane.fs; };
|
||||||
|
in mkIf cfg.enable (
|
||||||
|
take (sane-lib.mkTypedMerge take configs)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
16
modules/persist/root-on-tmpfs.nix
Normal file
16
modules/persist/root-on-tmpfs.nix
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.sane.persist;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
fileSystems."/" = lib.mkIf (cfg.enable && cfg.root-on-tmpfs) {
|
||||||
|
device = "none";
|
||||||
|
fsType = "tmpfs";
|
||||||
|
options = [
|
||||||
|
"mode=755"
|
||||||
|
"size=1G"
|
||||||
|
"defaults"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
74
modules/persist/stores/crypt.nix
Normal file
74
modules/persist/stores/crypt.nix
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{ config, lib, pkgs, utils, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
store = rec {
|
||||||
|
device = "/mnt/persist/crypt/clearedonboot";
|
||||||
|
underlying = {
|
||||||
|
path = "/nix/persist/crypt/clearedonboot";
|
||||||
|
# TODO: consider moving this to /tmp, but that requires tmp be mounted first?
|
||||||
|
key = "/mnt/persist/crypt/clearedonboot.key";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
lib.mkIf config.sane.persist.enable
|
||||||
|
{
|
||||||
|
sane.persist.stores."cryptClearOnBoot" = {
|
||||||
|
storeDescription = ''
|
||||||
|
stored to disk, but encrypted to an in-memory key and cleared on every boot
|
||||||
|
so that it's unreadable after power-off
|
||||||
|
'';
|
||||||
|
origin = store.device;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
fileSystems."${store.device}" = {
|
||||||
|
device = store.underlying.path;
|
||||||
|
fsType = "fuse.gocryptfs";
|
||||||
|
options = [
|
||||||
|
"nodev"
|
||||||
|
"nosuid"
|
||||||
|
"allow_other"
|
||||||
|
"passfile=${store.underlying.key}"
|
||||||
|
"defaults"
|
||||||
|
];
|
||||||
|
noCheck = true;
|
||||||
|
};
|
||||||
|
# let sane.fs know about our fileSystem and automatically add the appropriate dependencies
|
||||||
|
sane.fs."${store.device}".mount = {
|
||||||
|
# technically the dependency on the keyfile is extraneous because that *happens* to
|
||||||
|
# be needed to init the store.
|
||||||
|
depends = let
|
||||||
|
cryptfile = config.sane.fs."${store.underlying.path}/gocryptfs.conf";
|
||||||
|
keyfile = config.sane.fs."${store.underlying.key}";
|
||||||
|
in [ keyfile.unit cryptfile.unit ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# let sane.fs know how to initialize the gocryptfs store,
|
||||||
|
# and that it MUST do so
|
||||||
|
sane.fs."${store.underlying.path}/gocryptfs.conf".generated = {
|
||||||
|
script.script = ''
|
||||||
|
backing="$1"
|
||||||
|
passfile="$2"
|
||||||
|
# clear the backing store
|
||||||
|
# TODO: we should verify that it's not mounted anywhere...
|
||||||
|
rm -rf "''${backing:?}"/*
|
||||||
|
${pkgs.gocryptfs}/bin/gocryptfs -quiet -passfile "$passfile" -init "$backing"
|
||||||
|
'';
|
||||||
|
script.scriptArgs = [ store.underlying.path store.underlying.key ];
|
||||||
|
# we need the key in order to initialize the store
|
||||||
|
depends = [ config.sane.fs."${store.underlying.key}".unit ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# let sane.fs know how to generate the key for gocryptfs
|
||||||
|
sane.fs."${store.underlying.key}".generated = {
|
||||||
|
script.script = ''
|
||||||
|
dd if=/dev/random bs=128 count=1 | base64 --wrap=0 > "$1"
|
||||||
|
'';
|
||||||
|
script.scriptArgs = [ store.underlying.key ];
|
||||||
|
# no need for anyone else to be able to read the key
|
||||||
|
acl.mode = "0400";
|
||||||
|
};
|
||||||
|
|
||||||
|
# TODO: could add this *specifically* to the .mount file for the encrypted fs?
|
||||||
|
system.fsPackages = [ pkgs.gocryptfs ]; # fuse needs to find gocryptfs
|
||||||
|
}
|
9
modules/persist/stores/default.nix
Normal file
9
modules/persist/stores/default.nix
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{ ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./crypt.nix
|
||||||
|
./plaintext.nix
|
||||||
|
./private.nix
|
||||||
|
];
|
||||||
|
}
|
11
modules/persist/stores/plaintext.nix
Normal file
11
modules/persist/stores/plaintext.nix
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.sane.persist;
|
||||||
|
in lib.mkIf cfg.enable {
|
||||||
|
sane.persist.stores."plaintext" = {
|
||||||
|
origin = "/nix/persist";
|
||||||
|
};
|
||||||
|
# TODO: needed?
|
||||||
|
# sane.fs."/nix".mount = {};
|
||||||
|
}
|
49
modules/persist/stores/private.nix
Normal file
49
modules/persist/stores/private.nix
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{ config, lib, pkgs, utils, ... }:
|
||||||
|
|
||||||
|
lib.mkIf config.sane.persist.enable
|
||||||
|
{
|
||||||
|
sane.persist.stores."private" = {
|
||||||
|
storeDescription = ''
|
||||||
|
encrypted to the user's password and auto-unlocked at login
|
||||||
|
'';
|
||||||
|
origin = "/home/colin/private";
|
||||||
|
# files stored under here *must* have the /home/colin prefix.
|
||||||
|
# internally, this prefix is removed so that e.g.
|
||||||
|
# /home/colin/foo/bar when stored in `private` is visible at
|
||||||
|
# /home/colin/private/foo/bar
|
||||||
|
prefix = "/home/colin";
|
||||||
|
defaultOrdering = let
|
||||||
|
private-unit = config.sane.fs."/home/colin/private".unit;
|
||||||
|
in {
|
||||||
|
# auto create only after ~/private is mounted
|
||||||
|
wantedBy = [ private-unit ];
|
||||||
|
# we can't create things in private before local-fs.target
|
||||||
|
wantedBeforeBy = [ ];
|
||||||
|
};
|
||||||
|
defaultMethod = "symlink";
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/home/colin/private" = {
|
||||||
|
device = "/nix/persist/home/colin/private";
|
||||||
|
fsType = "fuse.gocryptfs";
|
||||||
|
options = [
|
||||||
|
"noauto" # don't try to mount, until the user logs in!
|
||||||
|
"nofail"
|
||||||
|
"allow_other" # root ends up being the user that mounts this, so need to make it visible to `colin`.
|
||||||
|
"nodev"
|
||||||
|
"nosuid"
|
||||||
|
"quiet"
|
||||||
|
"defaults"
|
||||||
|
];
|
||||||
|
noCheck = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# let sane.fs know about the mount
|
||||||
|
sane.fs."/home/colin/private".mount = {};
|
||||||
|
# it also needs to know that the underlying device is an ordinary folder
|
||||||
|
sane.fs."/nix/persist/home/colin/private".dir = {};
|
||||||
|
|
||||||
|
# TODO: could add this *specifically* to the .mount file for the encrypted fs?
|
||||||
|
system.fsPackages = [ pkgs.gocryptfs ]; # fuse needs to find gocryptfs
|
||||||
|
}
|
||||||
|
|
@@ -1,34 +0,0 @@
|
|||||||
# create ssh key by running:
|
|
||||||
# - `ssh-keygen -t ed25519`
|
|
||||||
let
|
|
||||||
withHost = host: key: "${host} ${key}";
|
|
||||||
withUser = user: key: "${key} ${user}";
|
|
||||||
|
|
||||||
keys = rec {
|
|
||||||
lappy = {
|
|
||||||
host = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILSJnqmVl9/SYQ0btvGb0REwwWY8wkdkGXQZfn/1geEc";
|
|
||||||
users.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDpmFdNSVPRol5hkbbCivRhyeENzb9HVyf9KutGLP2Zu";
|
|
||||||
};
|
|
||||||
desko = {
|
|
||||||
host = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFw9NoRaYrM6LbDd3aFBc4yyBlxGQn8HjeHd/dZ3CfHk";
|
|
||||||
users.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPU5GlsSfbaarMvDA20bxpSZGWviEzXGD8gtrIowc1pX";
|
|
||||||
};
|
|
||||||
servo = {
|
|
||||||
host = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOfdSmFkrVT6DhpgvFeQKm3Fh9VKZ9DbLYOPOJWYQ0E8";
|
|
||||||
users.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPS1qFzKurAdB9blkWomq8gI1g0T3sTs9LsmFOj5VtqX";
|
|
||||||
};
|
|
||||||
moby = {
|
|
||||||
host = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1N/IT3nQYUD+dBlU1sTEEVMxfOyMkrrDeyHcYgnJvw";
|
|
||||||
users.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrR+gePnl0nV/vy7I5BzrGeyVL+9eOuXHU1yNE3uCwU";
|
|
||||||
};
|
|
||||||
|
|
||||||
"uninsane.org" = servo;
|
|
||||||
"git.uninsane.org" = servo;
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
# map hostname -> something suitable for known_keys
|
|
||||||
hosts = builtins.mapAttrs (host: keys: withHost host keys.host) keys;
|
|
||||||
# map hostname -> something suitable for authorized_keys to allow access to colin@<hostname>
|
|
||||||
users = builtins.mapAttrs (host: keys: withUser "colin@${host}" keys.users.colin) keys;
|
|
||||||
}
|
|
||||||
|
|
@@ -2,6 +2,9 @@
|
|||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./duplicity.nix
|
./duplicity.nix
|
||||||
|
./dyn-dns.nix
|
||||||
|
./kiwix-serve.nix
|
||||||
./nixserve.nix
|
./nixserve.nix
|
||||||
|
./trust-dns.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,8 @@ in
|
|||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
# we need this mostly because of the size of duplicity's cache
|
# we need this mostly because of the size of duplicity's cache
|
||||||
sane.impermanence.service-dirs = [ "/var/lib/duplicity" ];
|
# TODO: move to cryptClearOnBoot and update perms
|
||||||
|
sane.persist.sys.plaintext = [ "/var/lib/duplicity" ];
|
||||||
|
|
||||||
services.duplicity.enable = true;
|
services.duplicity.enable = true;
|
||||||
services.duplicity.targetUrl = "$DUPLICITY_URL";
|
services.duplicity.targetUrl = "$DUPLICITY_URL";
|
||||||
|
91
modules/services/dyn-dns.nix
Normal file
91
modules/services/dyn-dns.nix
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
cfg = config.sane.services.dyn-dns;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
sane.services.dyn-dns = {
|
||||||
|
enable = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
};
|
||||||
|
|
||||||
|
ipPath = mkOption {
|
||||||
|
default = "/var/lib/uninsane/wan.txt";
|
||||||
|
type = types.str;
|
||||||
|
description = "where to store the latest WAN IPv4 address";
|
||||||
|
};
|
||||||
|
|
||||||
|
ipCmd = mkOption {
|
||||||
|
default = "${pkgs.sane-scripts}/bin/sane-ip-check-router-wan";
|
||||||
|
type = types.path;
|
||||||
|
description = "command to run to query the current WAN IP";
|
||||||
|
};
|
||||||
|
|
||||||
|
interval = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "10min";
|
||||||
|
description = "systemd time string for how frequently to re-check the IP";
|
||||||
|
};
|
||||||
|
|
||||||
|
restartOnChange = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = "list of systemd unit files to restart when the IP changes";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.services.dyn-dns = {
|
||||||
|
description = "update this host's record of its WAN IP";
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
restartTriggers = [(builtins.toJSON cfg)];
|
||||||
|
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = cfg.restartOnChange;
|
||||||
|
before = cfg.restartOnChange;
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
mkdir -p "$(dirname '${cfg.ipPath}')"
|
||||||
|
newIp=$(${cfg.ipCmd})
|
||||||
|
oldIp=$(cat '${cfg.ipPath}' || true)
|
||||||
|
# systemd path units are triggered on any file write action,
|
||||||
|
# regardless of content change. so only update the file if our IP *actually* changed
|
||||||
|
if [ "$newIp" != "$oldIp" ]
|
||||||
|
then
|
||||||
|
echo "$newIp" > '${cfg.ipPath}'
|
||||||
|
echo "WAN ip changed $oldIp -> $newIp"
|
||||||
|
fi
|
||||||
|
exit $(test -f '${cfg.ipPath}')
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.dyn-dns = {
|
||||||
|
# if anything wants dyn-dns.service, they surely want the timer too.
|
||||||
|
wantedBy = [ "dyn-dns.service" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnUnitActiveSec = cfg.interval;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.paths.dyn-dns-watcher = {
|
||||||
|
before = [ "dyn-dns.timer" ];
|
||||||
|
wantedBy = [ "dyn-dns.timer" ];
|
||||||
|
pathConfig = {
|
||||||
|
Unit = "dyn-dns-reactor.service";
|
||||||
|
PathChanged = [ cfg.ipPath ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.dyn-dns-reactor = {
|
||||||
|
description = "react to the system's WAN IP changing";
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
script = if cfg.restartOnChange != [] then ''
|
||||||
|
${pkgs.systemd}/bin/systemctl restart ${toString cfg.restartOnChange}
|
||||||
|
'' else "${pkgs.coreutils}/bin/true";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
55
modules/services/kiwix-serve.nix
Normal file
55
modules/services/kiwix-serve.nix
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
cfg = config.sane.services.kiwix-serve;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
sane.services.kiwix-serve = {
|
||||||
|
enable = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
};
|
||||||
|
package = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
default = pkgs.kiwix-tools;
|
||||||
|
defaultText = literalExpression "pkgs.kiwix-tools";
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
The package that provides `bin/kiwix-serve`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 80;
|
||||||
|
description = lib.mdDoc "Port number to listen on.";
|
||||||
|
};
|
||||||
|
listenAddress = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
IP address to listen on. Listens on all available addresses if unspecified.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
zimPaths = mkOption {
|
||||||
|
type = types.nonEmptyListOf (types.either types.str types.path);
|
||||||
|
description = lib.mdDoc "ZIM file path(s)";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.services.kiwix-serve = {
|
||||||
|
description = "Deliver ZIM file(s) articles via HTTP";
|
||||||
|
serviceConfig = let
|
||||||
|
maybeListenAddress = lib.optionals (cfg.listenAddress != null) ["-l" cfg.listenAddress];
|
||||||
|
args = maybeListenAddress ++ ["-p" cfg.port] ++ cfg.zimPaths;
|
||||||
|
in {
|
||||||
|
ExecStart = "${cfg.package}/bin/kiwix-serve ${lib.escapeShellArgs args}";
|
||||||
|
Type = "simple";
|
||||||
|
};
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user