Compare commits
205 Commits
staging/ni
...
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 | |||
55e09c2dbf | |||
bd699c887c | |||
d60e5264f3 |
40
flake.lock
generated
40
flake.lock
generated
@@ -36,21 +36,6 @@
|
||||
"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": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
@@ -69,11 +54,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1671359686,
|
||||
"narHash": "sha256-3MpC6yZo+Xn9cPordGz2/ii6IJpP2n8LE8e/ebUXLrs=",
|
||||
"lastModified": 1672953546,
|
||||
"narHash": "sha256-oz757DnJ1ITvwyTovuwG3l9cX6j9j6/DH9eH+cXFJmc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "04f574a1c0fde90b51bf68198e2297ca4e7cccf4",
|
||||
"rev": "a518c77148585023ff56022f09c4b2c418a51ef5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -84,11 +69,11 @@
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1671313200,
|
||||
"narHash": "sha256-itZTrtHeDJjV696+ur0/TzkTqb5y3Eb57WRLRPK3rwA=",
|
||||
"lastModified": 1673163619,
|
||||
"narHash": "sha256-B33PFBL64ZgTWgMnhFL3jgheAN/DjHPsZ1Ih3z0VE5I=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0938d73bb143f4ae037143572f11f4338c7b2d1c",
|
||||
"rev": "8c54d842d9544361aac5f5b212ba04e4089e8efe",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -99,11 +84,11 @@
|
||||
},
|
||||
"nixpkgs-stable_2": {
|
||||
"locked": {
|
||||
"lastModified": 1671459584,
|
||||
"narHash": "sha256-6wRK7xmeHfClJ0ICOkax1avLZVGTDqBodQlkl/opccY=",
|
||||
"lastModified": 1673100377,
|
||||
"narHash": "sha256-mT76pTd0YFxT6CwtPhDgHJhuIgLY+ZLSMiQpBufwMG4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "87b58217c9a05edcf7630b9be32570f889217aef",
|
||||
"rev": "9f11a2df77cb945c115ae2a65f53f38121597d73",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -116,7 +101,6 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"home-manager": "home-manager",
|
||||
"impermanence": "impermanence",
|
||||
"mobile-nixos": "mobile-nixos",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-stable": "nixpkgs-stable",
|
||||
@@ -132,11 +116,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1671472949,
|
||||
"narHash": "sha256-9iHSGpljCX+RypahQssBXPwkru9onfKfceCTeVrMpH4=",
|
||||
"lastModified": 1673147300,
|
||||
"narHash": "sha256-gR9OEfTzWfL6vG0qkbn1TlBAOlg4LuW8xK/u0V41Ihc=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "32840f16ffa0856cdf9503a8658f2dd42bf70342",
|
||||
"rev": "2253120d2a6147e57bafb5c689e086221df8032f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@@ -18,7 +18,6 @@
|
||||
url = "github:Mic92/sops-nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
impermanence.url = "github:nix-community/impermanence";
|
||||
uninsane = {
|
||||
url = "git+https://git.uninsane.org/colin/uninsane";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
@@ -32,7 +31,6 @@
|
||||
mobile-nixos,
|
||||
home-manager,
|
||||
sops-nix,
|
||||
impermanence,
|
||||
uninsane
|
||||
}: let
|
||||
patchedPkgs = system: nixpkgs.legacyPackages.${system}.applyPatches {
|
||||
@@ -54,12 +52,10 @@
|
||||
in (nixosSystem {
|
||||
# by default the local system is the same as the target, employing emulation when they differ
|
||||
system = target;
|
||||
specialArgs = { inherit mobile-nixos home-manager impermanence; };
|
||||
modules = [
|
||||
./modules
|
||||
(import ./hosts/instantiate.nix name)
|
||||
home-manager.nixosModule
|
||||
impermanence.nixosModule
|
||||
sops-nix.nixosModules.sops
|
||||
{
|
||||
nixpkgs.overlays = [
|
||||
@@ -77,10 +73,6 @@
|
||||
# gocryptfs = cross.gocryptfs;
|
||||
|
||||
# pinned packages:
|
||||
# 2022/12/13: grpc does not build on aarch64-linux. https://github.com/NixOS/nixpkgs/issues/205887
|
||||
grpc = stable.grpc;
|
||||
# depends on grpc, so pinned.
|
||||
duplicity = stable.duplicity;
|
||||
})
|
||||
];
|
||||
}
|
||||
|
@@ -1,32 +1,16 @@
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
# TODO: don't need to depend on binsh if we were to use a nix-style shebang
|
||||
system.activationScripts.linkBluetoothKeys = let
|
||||
unwrapped = ../../scripts/install-bluetooth;
|
||||
install-bluetooth = pkgs.writeShellApplication {
|
||||
name = "install-bluetooth";
|
||||
runtimeInputs = with pkgs; [ coreutils gnused ];
|
||||
text = ''${unwrapped} "$@"'';
|
||||
};
|
||||
in (lib.stringAfter
|
||||
[ "setupSecrets" "binsh" ]
|
||||
''
|
||||
${install-bluetooth}/bin/install-bluetooth /run/secrets/bt
|
||||
''
|
||||
);
|
||||
# persist external pairings by default
|
||||
sane.persist.sys.plaintext = [ "/var/lib/bluetooth" ];
|
||||
|
||||
# TODO: use a glob, or a list, or something?
|
||||
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";
|
||||
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" ];
|
||||
};
|
||||
}
|
||||
|
@@ -2,9 +2,11 @@
|
||||
{
|
||||
imports = [
|
||||
./bluetooth.nix
|
||||
./feeds.nix
|
||||
./fs.nix
|
||||
./hardware
|
||||
./i2p.nix
|
||||
./ids.nix
|
||||
./machine-id.nix
|
||||
./net.nix
|
||||
./secrets.nix
|
||||
@@ -18,6 +20,15 @@
|
||||
sane.packages.enableConsolePkgs = 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;
|
||||
|
||||
# time.timeZone = "America/Los_Angeles";
|
||||
@@ -58,19 +69,8 @@
|
||||
};
|
||||
# enable zsh completions
|
||||
environment.pathsToLink = [ "/share/zsh" ];
|
||||
environment.systemPackages = with pkgs; [
|
||||
# required for pam_mount
|
||||
gocryptfs
|
||||
];
|
||||
|
||||
# link debug symbols into /run/current-system/sw/lib/debug
|
||||
# hopefully picked up by gdb automatically?
|
||||
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
|
||||
hourly = { freq = "hourly"; };
|
||||
daily = { freq = "daily"; };
|
||||
@@ -13,24 +12,15 @@ let
|
||||
tech = { cat = "tech"; };
|
||||
uncat = { cat = "uncat"; };
|
||||
|
||||
text = { format = "text"; };
|
||||
image = { format = "image"; };
|
||||
podcast = { format = "podcast"; };
|
||||
|
||||
mkRss = format: url: { inherit url format; } // uncat // infrequent;
|
||||
# format-specific helpers
|
||||
mkText = mkRss text;
|
||||
mkImg = mkRss image;
|
||||
mkPod = mkRss podcast;
|
||||
mkText = mkRss "text";
|
||||
mkImg = mkRss "image";
|
||||
mkPod = mkRss "podcast";
|
||||
|
||||
# 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 = [
|
||||
(mkPod "https://lexfridman.com/feed/podcast/" // rat // weekly)
|
||||
## Astral Codex Ten
|
||||
@@ -139,53 +129,23 @@ in rec {
|
||||
## Sean Carroll
|
||||
(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
|
||||
(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 = [
|
||||
(mkImg "https://www.smbc-comics.com/comic/rss" // 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
|
||||
(mkImg "https://miniature-calendar.com/feed" // art // daily)
|
||||
];
|
||||
|
||||
all = 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));
|
||||
in
|
||||
{
|
||||
sane.feeds = texts ++ images ++ podcasts;
|
||||
}
|
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
|
||||
# logs from previous boots.
|
||||
# maybe there's a config option for this (since persistent machine-id is bad for reasons listed in impermanence.nix),
|
||||
# but for now generate it from ssh keys.
|
||||
# /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
|
||||
# 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 = {
|
||||
deps = [ "persist-ssh-host-keys" ];
|
||||
deps = [ "etc" ];
|
||||
text = "sha256sum /etc/ssh/host_keys/ssh_host_ed25519_key | cut -c 1-32 > /etc/machine-id";
|
||||
};
|
||||
}
|
||||
|
@@ -31,53 +31,13 @@
|
||||
General.RoamThreshold5G = "-52"; # default -76
|
||||
};
|
||||
|
||||
# TODO: don't need to depend on binsh if we were to use a nix-style shebang
|
||||
system.activationScripts.linkIwdKeys = let
|
||||
unwrapped = ../../scripts/install-iwd;
|
||||
install-iwd = pkgs.writeShellApplication {
|
||||
name = "install-iwd";
|
||||
runtimeInputs = with pkgs; [ coreutils gnused ];
|
||||
text = ''${unwrapped} "$@"'';
|
||||
};
|
||||
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";
|
||||
};
|
||||
|
||||
sops.secrets."router_passwd" = {
|
||||
sopsFile = ../../secrets/universal.yaml;
|
||||
sane.fs."/var/lib/iwd/.secrets.psk.stamp" = {
|
||||
wantedBeforeBy = [ "iwd.service" ];
|
||||
generated.acl.mode = "0600";
|
||||
# XXX: install-iwd uses sed, but that's part of the default systemd unit path, it seems
|
||||
generated.script.script = builtins.readFile ../../scripts/install-iwd + ''
|
||||
touch "/var/lib/iwd/.secrets.psk.stamp"
|
||||
'';
|
||||
generated.script.scriptArgs = [ "/run/secrets/iwd" "/var/lib/iwd" ];
|
||||
};
|
||||
}
|
||||
|
@@ -33,10 +33,6 @@
|
||||
# You can avoid this by adding a string to the full path instead, i.e.
|
||||
# sops.defaultSopsFile = "/root/.sops/secrets/example.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
|
||||
# This is using an age key that is expected to already be in the filesystem
|
||||
# sops.age.keyFile = "/home/colin/.ssh/age.pub";
|
||||
@@ -48,6 +44,81 @@
|
||||
# owner = config.users.users.colin.name;
|
||||
# };
|
||||
# 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,26 +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
|
||||
# bind mount that whole directory instead of doing it per-file.
|
||||
# otherwise, this is identical to nixos defaults
|
||||
sane.impermanence.service-dirs = [ "/etc/ssh/host_keys" ];
|
||||
sane.ssh.pubkeys =
|
||||
let
|
||||
# path is a DNS-style path like [ "org" "uninsane" "root" ]
|
||||
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,
|
||||
# as /etc/fstab may not be populated yet (since that file depends on e.g. activationScripts.users)
|
||||
# we can't even depend on impermanence's `createPersistentStorageDirs` to create the source/target directories
|
||||
# since that also depends on `users`.
|
||||
system.activationScripts.persist-ssh-host-keys.text = ''
|
||||
mkdir -p /etc/ssh/host_keys
|
||||
if ! (mountpoint /etc/ssh/host_keys)
|
||||
then
|
||||
# avoid mounting the keys more than once, otherwise we have a million _stacked_ entries.
|
||||
# TODO: should we just symlink? or find a way to make sure the existing mount is correct.
|
||||
mount --bind /nix/persist/etc/ssh/host_keys /etc/ssh/host_keys
|
||||
fi
|
||||
'';
|
||||
|
||||
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"; }
|
||||
];
|
||||
# [{ path :: [String], value :: String }] for the keys we want to install
|
||||
globalKeys = sane-lib.flattenAttrs sane-data.keys;
|
||||
localKeys = sane-lib.flattenAttrs sane-data.keys.org.uninsane.local;
|
||||
in lib.mkMerge (builtins.map
|
||||
({ path, value }: {
|
||||
"${keyNameForPath path}" = value;
|
||||
})
|
||||
(globalKeys ++ localKeys)
|
||||
);
|
||||
}
|
||||
|
@@ -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
|
||||
with lib;
|
||||
let
|
||||
cfg = config.sane.users;
|
||||
# see nixpkgs/nixos/modules/services/networking/dhcpcd.nix
|
||||
hasDHCP = config.networking.dhcpcd.enable &&
|
||||
(config.networking.useDHCP || any (i: i.useDHCP == true) (attrValues config.networking.interfaces));
|
||||
|
||||
fs = sane-lib.fs;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
@@ -27,7 +24,8 @@ in
|
||||
# sets group to "users" (?)
|
||||
isNormalUser = true;
|
||||
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
|
||||
# in /var/lib/nixos/auto-subuid-map and i don't want that.
|
||||
subUidRanges = [
|
||||
@@ -50,36 +48,65 @@ in
|
||||
passwordFile = lib.mkIf (config.sops.secrets ? "colin-passwd") config.sops.secrets.colin-passwd.path;
|
||||
|
||||
shell = pkgs.zsh;
|
||||
openssh.authorizedKeys.keys = builtins.attrValues (import ../../modules/pubkeys.nix).users;
|
||||
|
||||
pamMount = {
|
||||
# mount encrypted stuff at login
|
||||
# requires that login password == fs encryption password
|
||||
# fstype = "fuse";
|
||||
# path = "${pkgs.gocryptfs}/bin/gocryptfs#/nix/persist/home/colin/private";
|
||||
fstype = "fuse.gocryptfs";
|
||||
path = "/nix/persist/home/colin/private";
|
||||
mountpoint = "/home/colin/private";
|
||||
options="nodev,nosuid,quiet,allow_other";
|
||||
# mount encrypted stuff at login
|
||||
# some other nix pam users:
|
||||
# - <https://github.com/g00pix/nixconf/blob/32c04f6fa843fed97639dd3f09e157668d3eea1f/profiles/sshfs.nix>
|
||||
# - <https://github.com/lourkeur/distro/blob/11173454c6bb50f7ccab28cc2c757dca21446d1d/nixos/profiles/users/louis-full.nix>
|
||||
# - <https://github.com/dnr/sample-nix-code/blob/03494480c1fae550c033aa54fd96aeb3827761c5/nixos/laptop.nix>
|
||||
pamMount = let
|
||||
priv = config.fileSystems."/home/colin/private";
|
||||
in {
|
||||
fstype = priv.fsType;
|
||||
path = priv.device;
|
||||
mountpoint = priv.mountPoint;
|
||||
options = builtins.concatStringsSep "," priv.options;
|
||||
};
|
||||
};
|
||||
|
||||
sane.impermanence.home-dirs = [
|
||||
# cache is probably too big to fit on the tmpfs
|
||||
# TODO: we could bind-mount it to something which gets cleared per boot, though.
|
||||
".cache"
|
||||
security.pam.mount.enable = true;
|
||||
|
||||
# ensure ~ perms are known to sane.fs module.
|
||||
# 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"
|
||||
".rustup"
|
||||
".local/share/keyrings"
|
||||
];
|
||||
|
||||
sane.impermanence.service-dirs = mkIf cfg.guest.enable [
|
||||
{ user = "guest"; group = "users"; directory = "/home/guest"; }
|
||||
# convenience
|
||||
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 {
|
||||
isNormalUser = true;
|
||||
home = "/home/guest";
|
||||
uid = config.sane.allocations.guest-uid;
|
||||
subUidRanges = [
|
||||
{ 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 = {
|
||||
enable = true;
|
||||
wheelNeedsPassword = false;
|
||||
@@ -108,31 +128,5 @@ in
|
||||
permitRootLogin = "no";
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
@@ -28,9 +28,6 @@ let
|
||||
# to start: `systemctl start wg-quick-ovpnd-${name}`
|
||||
autostart = false;
|
||||
};
|
||||
sops.secrets."wg_ovpnd_${name}_privkey" = {
|
||||
sopsFile = ../../secrets/universal.yaml;
|
||||
};
|
||||
};
|
||||
in lib.mkMerge [
|
||||
(def-ovpn "us" {
|
||||
|
@@ -10,15 +10,13 @@
|
||||
sane.services.duplicity.enable = true;
|
||||
sane.services.nixserve.enable = true;
|
||||
sane.services.nixserve.sopsFile = ../../secrets/desko.yaml;
|
||||
sane.impermanence.enable = true;
|
||||
sane.persist.enable = true;
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||
|
||||
# needed to use libimobiledevice/ifuse, for iphone sync
|
||||
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 = {
|
||||
sopsFile = ../../secrets/desko.yaml;
|
||||
@@ -52,7 +50,7 @@
|
||||
remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play
|
||||
dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server
|
||||
};
|
||||
sane.impermanence.home-dirs = [
|
||||
sane.persist.home.plaintext = [
|
||||
".steam"
|
||||
".local/share/Steam"
|
||||
];
|
||||
|
@@ -1,16 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
# root is a tmpfs so that we have an ephemeral system ("impermanence" handles the state)
|
||||
fileSystems."/" = {
|
||||
device = "none";
|
||||
fsType = "tmpfs";
|
||||
options = [
|
||||
"mode=755"
|
||||
"size=1G"
|
||||
"defaults"
|
||||
];
|
||||
};
|
||||
sane.persist.root-on-tmpfs = true;
|
||||
# we need a /tmp for building large nix things.
|
||||
# a cross-compiled kernel, particularly, will easily use 30+GB of tmp
|
||||
fileSystems."/tmp" = {
|
||||
|
@@ -8,7 +8,7 @@
|
||||
|
||||
# sane.users.guest.enable = true;
|
||||
sane.gui.sway.enable = true;
|
||||
sane.impermanence.enable = true;
|
||||
sane.persist.enable = true;
|
||||
sane.nixcache.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
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)
|
||||
fileSystems."/" = {
|
||||
device = "none";
|
||||
fsType = "tmpfs";
|
||||
options = [
|
||||
"mode=755"
|
||||
"size=1G"
|
||||
"defaults"
|
||||
];
|
||||
};
|
||||
sane.persist.root-on-tmpfs = true;
|
||||
# we need a /tmp of default size (half RAM) for building large nix things
|
||||
fileSystems."/tmp" = {
|
||||
device = "none";
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{ config, pkgs, lib, mobile-nixos, ... }:
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
imports = [
|
||||
./firmware.nix
|
||||
@@ -24,8 +24,9 @@
|
||||
};
|
||||
|
||||
# usability compromises
|
||||
sane.impermanence.home-dirs = [
|
||||
config.sane.web-browser.dotDir
|
||||
sane.web-browser.persistCache = "private";
|
||||
sane.web-browser.persistData = "private";
|
||||
sane.persist.home.plaintext = [
|
||||
".config/pulse" # persist pulseaudio volume
|
||||
];
|
||||
|
||||
@@ -35,7 +36,7 @@
|
||||
];
|
||||
|
||||
sane.nixcache.enable = true;
|
||||
sane.impermanence.enable = true;
|
||||
sane.persist.enable = true;
|
||||
sane.gui.phosh.enable = true;
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
|
@@ -1,17 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
# root is a tmpfs so that we have an ephemeral system ("impermanence" handles the state)
|
||||
fileSystems."/" = {
|
||||
device = "none";
|
||||
fsType = "tmpfs";
|
||||
options = [
|
||||
"mode=755"
|
||||
"size=1G"
|
||||
"defaults"
|
||||
];
|
||||
};
|
||||
|
||||
sane.persist.root-on-tmpfs = true;
|
||||
fileSystems."/nix" = {
|
||||
device = "/dev/disk/by-uuid/1f1271f8-53ce-4081-8a29-60a4a6b5d6f9";
|
||||
fsType = "btrfs";
|
||||
|
@@ -8,9 +8,6 @@
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
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
|
||||
system.stateVersion = "21.05";
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@
|
||||
pkgs.matrix-synapse
|
||||
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
|
||||
|
||||
|
@@ -1,16 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
# root is a tmpfs so that we have an ephemeral system ("impermanence" handles the state)
|
||||
fileSystems."/" = {
|
||||
device = "none";
|
||||
fsType = "tmpfs";
|
||||
options = [
|
||||
"mode=755"
|
||||
"size=1G"
|
||||
"defaults"
|
||||
];
|
||||
};
|
||||
sane.persist.root-on-tmpfs = true;
|
||||
# we need a /tmp for building large nix things
|
||||
fileSystems."/tmp" = {
|
||||
device = "none";
|
||||
@@ -36,7 +27,7 @@
|
||||
};
|
||||
|
||||
# slow, external storage (for archiving, etc)
|
||||
fileSystems."/nix/persist/ext" = {
|
||||
fileSystems."/mnt/persist/ext" = {
|
||||
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
||||
fsType = "btrfs";
|
||||
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
|
||||
{ user = "colin"; group = "users"; directory = "/var/lib/uninsane"; }
|
||||
];
|
||||
# direct these media directories to external storage
|
||||
environment.persistence."/nix/persist/ext/persist" = {
|
||||
directories = [
|
||||
({
|
||||
user = "colin";
|
||||
group = "users";
|
||||
mode = "0777";
|
||||
directory = "/var/lib/uninsane/media/Videos";
|
||||
})
|
||||
({
|
||||
user = "colin";
|
||||
group = "users";
|
||||
mode = "0777";
|
||||
directory = "/var/lib/uninsane/media/freeleech";
|
||||
})
|
||||
];
|
||||
};
|
||||
# make sure large media is stored to the HDD
|
||||
sane.persist.sys.ext = [
|
||||
{
|
||||
user = "colin";
|
||||
group = "users";
|
||||
mode = "0777";
|
||||
directory = "/var/lib/uninsane/media/Videos";
|
||||
}
|
||||
{
|
||||
user = "colin";
|
||||
group = "users";
|
||||
mode = "0777";
|
||||
directory = "/var/lib/uninsane/media/freeleech";
|
||||
}
|
||||
];
|
||||
|
||||
# in-memory compressed RAM (seems to be dynamically sized)
|
||||
# zramSwap = {
|
||||
|
@@ -40,6 +40,16 @@
|
||||
"127.0.0.53"
|
||||
];
|
||||
|
||||
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
|
||||
# in a way that's unaware of my VPN routing, so routes are frequently poor against
|
||||
# services which advertise different IPs based on geolocation.
|
||||
# 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.
|
||||
|
@@ -3,46 +3,64 @@
|
||||
# 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/bc5b65509f4e622313af3f1f4be690628123f1f3/programs/ejabberd.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>
|
||||
{ lib, ... }:
|
||||
#
|
||||
# compliance tests:
|
||||
# - <https://compliance.conversations.im/server/uninsane.org/#xep0352>
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# XXX: avatar support works in MUCs but not DMs
|
||||
# lib.mkIf false
|
||||
{
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
{ user = "ejabberd"; group = "ejabberd"; directory = "/var/lib/ejabberd"; }
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
3478 # STUN
|
||||
5222 # XMPP client -> server
|
||||
5269 # XMPP server -> server
|
||||
3478 # STUN/TURN
|
||||
5222 # XMPP client -> server
|
||||
5223 # XMPPS client -> server (XMPP over TLS)
|
||||
5269 # XMPP server -> server
|
||||
5270 # XMPPS server -> server (XMPP over TLS)
|
||||
5280 # bosh
|
||||
5281 # bosh (https) ??
|
||||
5349 # STUN (TLS)
|
||||
5349 # STUN/TURN (TLS)
|
||||
5443 # web services (file uploads, websockets, admin)
|
||||
];
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
3478 # STUN
|
||||
3478 # STUN/TURN
|
||||
];
|
||||
networking.firewall.allowedTCPPortRanges = [{
|
||||
from = 49152; # TURN
|
||||
to = 65535;
|
||||
}];
|
||||
networking.firewall.allowedUDPPortRanges = [{
|
||||
from = 49152; # TURN
|
||||
to = 65535;
|
||||
}];
|
||||
|
||||
# provide access to certs
|
||||
users.users.ejabberd.extraGroups = [ "nginx" ];
|
||||
|
||||
security.acme.certs."uninsane.org".extraDomainNames = [
|
||||
"conference.xmpp.uninsane.org"
|
||||
"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."conference.xmpp.uninsane.org" = {
|
||||
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" = {
|
||||
@@ -56,242 +74,320 @@
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||
# XXX: toplevel xmpp might not actually be used/needed.
|
||||
CNAME."xmpp" = [ "native" ];
|
||||
CNAME."conference.xmpp" = [ "native" ];
|
||||
CNAME."pubsub.xmpp" = [ "native" ];
|
||||
CNAME."upload.xmpp" = [ "native" ];
|
||||
CNAME."vjid.xmpp" = [ "native" ];
|
||||
# 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
|
||||
SRV."_xmpp-client._tcp" = [ "0 0 5222 native" ];
|
||||
SRV."_xmpp-server._tcp" = [ "0 0 5269 native" ];
|
||||
SRV."_stun._udp" = [ "0 0 3478 native" ];
|
||||
SRV."_stun._tcp" = [ "0 0 3478 native" ];
|
||||
SRV."_stuns._tcp" = [ "0 0 5349 native" ];
|
||||
# _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 ?
|
||||
services.ejabberd.enable = true;
|
||||
services.ejabberd.configFile = builtins.toFile "ejabberd.yaml" ''
|
||||
hosts:
|
||||
- uninsane.org
|
||||
services.ejabberd.configFile = "/var/lib/ejabberd/ejabberd.yaml";
|
||||
systemd.services.ejabberd.preStart = let
|
||||
config-in = pkgs.writeTextFile {
|
||||
name = "ejabberd.yaml.in";
|
||||
text = ''
|
||||
hosts:
|
||||
- uninsane.org
|
||||
|
||||
# none | emergency | alert | critical | error | warning | notice | info | debug
|
||||
loglevel: debug
|
||||
# none | emergency | alert | critical | error | warning | notice | info | debug
|
||||
loglevel: debug
|
||||
# loglevel: info
|
||||
# loglevel: notice
|
||||
|
||||
acme:
|
||||
auto: false
|
||||
certfiles:
|
||||
- /var/lib/acme/uninsane.org/fullchain.pem
|
||||
- /var/lib/acme/uninsane.org/key.pem
|
||||
acme:
|
||||
auto: false
|
||||
certfiles:
|
||||
- /var/lib/acme/uninsane.org/full.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
|
||||
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: local
|
||||
trusted_network:
|
||||
allow: loopback
|
||||
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/#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
|
||||
# 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/>
|
||||
# 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
|
||||
# see: <https://docs.ejabberd.im/admin/configuration/listen/>
|
||||
# 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:
|
||||
-
|
||||
port: 5222
|
||||
module: ejabberd_c2s
|
||||
shaper: c2s_shaper
|
||||
starttls: true
|
||||
access: c2s_access
|
||||
-
|
||||
port: 5269
|
||||
module: ejabberd_s2s_in
|
||||
shaper: s2s_shaper
|
||||
-
|
||||
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
|
||||
-
|
||||
port: 3478
|
||||
module: ejabberd_stun
|
||||
transport: tcp
|
||||
-
|
||||
port: 3478
|
||||
module: ejabberd_stun
|
||||
transport: udp
|
||||
-
|
||||
port: 5349
|
||||
module: ejabberd_stun
|
||||
transport: tcp
|
||||
tls: true
|
||||
|
||||
# TODO: enable mod_client_state for net optimization
|
||||
# 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
|
||||
# mod_conversejs: TODO: enable once on 21.12
|
||||
# allows clients like Dino to discover where to upload files
|
||||
mod_disco:
|
||||
server_info:
|
||||
listen:
|
||||
-
|
||||
modules: all
|
||||
name: abuse-addresses
|
||||
urls:
|
||||
- "mailto:admin.xmpp@uninsane.org"
|
||||
- "xmpp:colin@uninsane.org"
|
||||
port: 5222
|
||||
module: ejabberd_c2s
|
||||
shaper: c2s_shaper
|
||||
starttls: true
|
||||
access: c2s_access
|
||||
-
|
||||
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: conference.xmpp.uninsane.org
|
||||
hosts:
|
||||
- conference.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
|
||||
mod_roster:
|
||||
versioning: true
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-s2s-dialback>
|
||||
# mod_s2s_dialback: {} # XXX: MIGHT need to enable this to federate with some servers
|
||||
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
|
||||
plugins:
|
||||
- pep
|
||||
# - flat
|
||||
force_node_config:
|
||||
# avoid buggy clients to make their bookmarks public
|
||||
# XXX: not sure if this is necessary: copying config from examples
|
||||
storage:bookmarks:
|
||||
access_model: whitelist
|
||||
mod_version: {}
|
||||
port: 5223
|
||||
module: ejabberd_c2s
|
||||
shaper: c2s_shaper
|
||||
tls: true
|
||||
access: c2s_access
|
||||
-
|
||||
port: 5269
|
||||
module: ejabberd_s2s_in
|
||||
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
|
||||
# ```
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
{
|
||||
sops.secrets.freshrss_passwd = {
|
||||
sopsFile = ../../../secrets/servo.yaml;
|
||||
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"; }
|
||||
];
|
||||
|
||||
users.users.freshrss.uid = config.sane.allocations.freshrss-uid;
|
||||
users.groups.freshrss.gid = config.sane.allocations.freshrss-gid;
|
||||
services.freshrss.enable = true;
|
||||
services.freshrss.baseUrl = "https://rss.uninsane.org";
|
||||
services.freshrss.virtualHost = "rss.uninsane.org";
|
||||
@@ -29,9 +27,11 @@
|
||||
|
||||
systemd.services.freshrss-import-feeds =
|
||||
let
|
||||
feeds = sane-lib.feeds;
|
||||
fresh = config.systemd.services.freshrss-config;
|
||||
feeds = import ../../../modules/home-manager/feeds.nix { inherit lib; };
|
||||
opml = pkgs.writeText "sane-freshrss.opml" (feeds.feedsToOpml feeds.all);
|
||||
all-feeds = config.sane.feeds;
|
||||
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
|
||||
opml = pkgs.writeText "sane-freshrss.opml" (feeds.feedsToOpml wanted-feeds);
|
||||
in {
|
||||
inherit (fresh) wantedBy environment;
|
||||
serviceConfig = {
|
||||
@@ -57,5 +57,5 @@
|
||||
# the routing is handled by services.freshrss.virtualHost
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."rss" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."rss" = "native";
|
||||
}
|
||||
|
@@ -1,11 +1,10 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "git"; group = "gitea"; directory = "/var/lib/gitea"; }
|
||||
];
|
||||
users.groups.gitea.gid = config.sane.allocations.gitea-gid;
|
||||
services.gitea.enable = true;
|
||||
services.gitea.user = "git"; # default is 'gitea'
|
||||
services.gitea.database.type = "postgres";
|
||||
@@ -85,5 +84,5 @@
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."git" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."git" = "native";
|
||||
}
|
||||
|
@@ -64,5 +64,5 @@
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."sink" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."sink" = "native";
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
lib.mkIf false # i don't actively use ipfs anymore
|
||||
{
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "261"; group = "261"; directory = "/var/lib/ipfs"; }
|
||||
];
|
||||
@@ -34,7 +34,7 @@ lib.mkIf false # i don't actively use ipfs anymore
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."ipfs" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."ipfs" = "native";
|
||||
|
||||
# services.ipfs.enable = true;
|
||||
services.kubo.localDiscovery = true;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? we only need this to save Indexer creds ==> migrate to config?
|
||||
{ user = "root"; group = "root"; directory = "/var/lib/jackett"; }
|
||||
];
|
||||
@@ -27,6 +27,6 @@
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."jackett" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."jackett" = "native";
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ lib.mkIf false
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
1900 7359 # DLNA: https://jellyfin.org/docs/general/networking/index.html
|
||||
];
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "jellyfin"; group = "jellyfin"; directory = "/var/lib/jellyfin"; }
|
||||
];
|
||||
@@ -61,9 +61,7 @@ lib.mkIf false
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."jelly" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."jelly" = "native";
|
||||
|
||||
# users.users.jellyfin.uid = config.sane.allocations.jellyfin-uid;
|
||||
# users.groups.jellyfin.gid = config.sane.allocations.jellyfin-gid;
|
||||
services.jellyfin.enable = true;
|
||||
}
|
||||
|
@@ -13,5 +13,5 @@
|
||||
locations."/".proxyPass = "http://127.0.0.1:8013";
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."w" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."w" = "native";
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@
|
||||
# ./irc.nix
|
||||
];
|
||||
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/matrix-synapse"; }
|
||||
];
|
||||
services.matrix-synapse.enable = true;
|
||||
@@ -122,8 +122,8 @@
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||
CNAME."matrix" = [ "native" ];
|
||||
CNAME."web.matrix" = [ "native" ];
|
||||
CNAME."matrix" = "native";
|
||||
CNAME."web.matrix" = "native";
|
||||
};
|
||||
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/mx-puppet-discord"; }
|
||||
];
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode?
|
||||
# user and group are both "matrix-appservice-irc"
|
||||
{ user = "993"; group = "992"; directory = "/var/lib/matrix-appservice-irc"; }
|
||||
|
@@ -1,8 +1,11 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.service-dirs = [
|
||||
{ user = "navidrome"; group = "navidrome"; directory = "/var/lib/private/navidrome"; }
|
||||
sane.persist.sys.plaintext = [
|
||||
# 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.settings = {
|
||||
@@ -22,5 +25,5 @@
|
||||
locations."/".proxyPass = "http://127.0.0.1:4533";
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."music" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."music" = "native";
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
# docs: https://nixos.wiki/wiki/Nginx
|
||||
{ config, pkgs, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
# 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 "") + ''
|
||||
access_log /var/log/nginx/public.log vcombined;
|
||||
'';
|
||||
@@ -120,14 +120,34 @@ in
|
||||
security.acme.acceptTerms = true;
|
||||
security.acme.defaults.email = "admin.acme@uninsane.org";
|
||||
|
||||
users.users.acme.uid = config.sane.allocations.acme-uid;
|
||||
users.groups.acme.gid = config.sane.allocations.acme-gid;
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode?
|
||||
{ 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.
|
||||
|
@@ -14,7 +14,7 @@
|
||||
'';
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."nixcache" = [ "native" ];
|
||||
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, ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ 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.secretConfigFile = config.sops.secrets.pleroma_secrets.path;
|
||||
services.pleroma.configs = [
|
||||
@@ -179,7 +177,7 @@
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."fed" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."fed" = "native";
|
||||
|
||||
sops.secrets.pleroma_secrets = {
|
||||
sopsFile = ../../../secrets/servo.yaml;
|
||||
|
@@ -16,7 +16,7 @@ let
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "opendkim"; group = "opendkim"; directory = "/var/lib/opendkim"; }
|
||||
{ user = "root"; group = "root"; directory = "/var/lib/postfix"; }
|
||||
@@ -45,22 +45,22 @@ in
|
||||
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||
MX."@" = [ "10 mx.uninsane.org." ];
|
||||
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" ];
|
||||
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" ];
|
||||
TXT."@" = "v=spf1 a mx -all";
|
||||
|
||||
# DKIM public key:
|
||||
TXT."mx._domainkey" = [
|
||||
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
|
||||
@@ -75,9 +75,9 @@ in
|
||||
# pct = sampling ratio for punishing failures (default 100 for 100%)
|
||||
# rf = report format
|
||||
# ri = report interval
|
||||
TXT."_dmarc" = [
|
||||
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;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode?
|
||||
{ user = "postgres"; group = "postgres"; directory = "/var/lib/postgresql"; }
|
||||
];
|
||||
|
@@ -9,7 +9,7 @@
|
||||
# nixnet runs ejabberd, so revisiting that.
|
||||
lib.mkIf false
|
||||
{
|
||||
sane.impermanence.service-dirs = [
|
||||
sane.persist.sys.plaintext = [
|
||||
{ user = "prosody"; group = "prosody"; directory = "/var/lib/prosody"; }
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
|
@@ -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/
|
||||
{ user = "transmission"; group = "transmission"; directory = "/var/lib/transmission"; }
|
||||
];
|
||||
@@ -75,6 +75,6 @@
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."bt" = ["native"];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."bt" = "native";
|
||||
}
|
||||
|
||||
|
@@ -21,23 +21,25 @@
|
||||
# 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."@" = [''
|
||||
SOA."@" = ''
|
||||
ns1.uninsane.org. admin-dns.uninsane.org. (
|
||||
2022121601 ; Serial
|
||||
2022122101 ; Serial
|
||||
4h ; Refresh
|
||||
30m ; Retry
|
||||
7d ; Expire
|
||||
5m) ; Negative response TTL
|
||||
''];
|
||||
TXT."rev" = [ "2022121801" ];
|
||||
'';
|
||||
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."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."
|
||||
@@ -45,19 +47,14 @@
|
||||
];
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".include = [
|
||||
"/var/lib/trust-dns/native.uninsane.org.zone"
|
||||
];
|
||||
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}/native.uninsane.org.zone";
|
||||
zone-template = pkgs.writeText "native.uninsane.org.zone.in" ''
|
||||
@ A %NATIVE%
|
||||
ns1 A %NATIVE%
|
||||
native A %NATIVE%
|
||||
'';
|
||||
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}
|
||||
|
@@ -11,8 +11,6 @@ lib.mkIf false
|
||||
sopsFile = ../../../secrets/servo.yaml;
|
||||
};
|
||||
|
||||
users.users.mediawiki.uid = config.sane.allocations.mediawiki-uid;
|
||||
|
||||
services.mediawiki.enable = true;
|
||||
services.mediawiki.name = "Uninsane Wiki";
|
||||
services.mediawiki.passwordFile = config.sops.secrets.mediawiki_pw.path;
|
||||
|
@@ -12,7 +12,6 @@
|
||||
home = "/var/lib/gitea";
|
||||
useDefaultShell = true;
|
||||
group = "gitea";
|
||||
uid = config.sane.allocations.git-uid;
|
||||
isSystemUser = true;
|
||||
# sendmail access (not 100% sure if this is necessary)
|
||||
extraGroups = [ "postdrop" ];
|
||||
|
@@ -1,63 +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;
|
||||
|
||||
# new servo users
|
||||
sane.allocations.freshrss-uid = mkId 2401;
|
||||
sane.allocations.freshrss-gid = mkId 2401;
|
||||
sane.allocations.mediawiki-uid = mkId 2402;
|
||||
|
||||
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 = [
|
||||
./allocations.nix
|
||||
./feeds.nix
|
||||
./fs
|
||||
./gui
|
||||
./home-manager
|
||||
./ids.nix
|
||||
./packages.nix
|
||||
./image.nix
|
||||
./impermanence.nix
|
||||
./nixcache.nix
|
||||
./persist
|
||||
./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 {
|
||||
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 {
|
||||
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
|
||||
services.xserver.enable = true;
|
||||
services.xserver.desktopManager.gnome.enable = true;
|
||||
|
@@ -24,16 +24,6 @@ in
|
||||
{
|
||||
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
|
||||
services.xserver.desktopManager.phosh = {
|
||||
enable = true;
|
||||
|
@@ -1,9 +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://jackett.uninsane.org
|
||||
https://bt.uninsane.org
|
||||
https://sci-hub.se
|
||||
https://news.ycombinator.com
|
||||
|
@@ -22,14 +22,15 @@ in
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
sane.gui.enable = true;
|
||||
users.users.greeter.uid = config.sane.allocations.greeter-uid;
|
||||
users.groups.greeter.gid = config.sane.allocations.greeter-gid;
|
||||
|
||||
programs.sway = {
|
||||
# we configure sway with home-manager, but this enable gets us e.g. opengl and fonts
|
||||
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
|
||||
swayConfig-greeter = pkgs.writeText "greetd-sway-config" ''
|
||||
# `-l` activates layer-shell mode.
|
||||
@@ -71,18 +72,30 @@ in
|
||||
pulse.enable = true;
|
||||
};
|
||||
|
||||
hardware.bluetooth.enable = true;
|
||||
services.blueman.enable = true;
|
||||
|
||||
networking.useDHCP = false;
|
||||
networking.networkmanager.enable = true;
|
||||
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 = {
|
||||
enable = true;
|
||||
wrapperFeatures.gtk = true;
|
||||
config = let
|
||||
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";
|
||||
@@ -95,7 +108,10 @@ in
|
||||
# "bookmarking"/snippets inspired by Luke Smith:
|
||||
# - <https://www.youtube.com/watch?v=d_11QaTlf1I>
|
||||
snip-file = ./snippets.txt;
|
||||
snip-cmd = "${wtype} $(cat ${snip-file} | ${fuzzel} -d -i)";
|
||||
# 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 {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Terminal UI mail client
|
||||
{ config, lib, ... }:
|
||||
{ config, lib, sane-lib, ... }:
|
||||
|
||||
lib.mkIf config.sane.home-manager.enable
|
||||
{
|
||||
@@ -8,9 +8,5 @@ lib.mkIf config.sane.home-manager.enable
|
||||
sopsFile = ../../secrets/universal/aerc_accounts.conf;
|
||||
format = "binary";
|
||||
};
|
||||
home-manager.users.colin = let sysconfig = config; in { config, ... }: {
|
||||
# aerc TUI mail client
|
||||
xdg.configFile."aerc/accounts.conf".source =
|
||||
config.lib.file.mkOutOfStoreSymlink sysconfig.sops.secrets.aerc_accounts.path;
|
||||
};
|
||||
sane.fs."/home/colin/.config/aerc/accounts.conf" = sane-lib.fs.wantedSymlinkTo config.sops.secrets.aerc_accounts.path;
|
||||
}
|
||||
|
@@ -9,28 +9,26 @@
|
||||
with lib;
|
||||
let
|
||||
cfg = config.sane.home-manager;
|
||||
# extract package from `sane.packages.enabledUserPkgs`
|
||||
pkg-list = pkgspec: builtins.map (e: e.pkg or e) 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; };
|
||||
# extract `pkg` from `sane.packages.enabledUserPkgs`
|
||||
pkg-list = pkgspec: builtins.map (e: e.pkg) pkgspec;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./aerc.nix
|
||||
./discord.nix
|
||||
./firefox.nix
|
||||
./gfeeds.nix
|
||||
./git.nix
|
||||
./gpodder.nix
|
||||
./keyring.nix
|
||||
./kitty.nix
|
||||
./mpv.nix
|
||||
./nb.nix
|
||||
./neovim.nix
|
||||
./newsflash.nix
|
||||
./splatmoji.nix
|
||||
./ssh.nix
|
||||
./sublime-music.nix
|
||||
./vlc.nix
|
||||
./zsh.nix
|
||||
./zsh
|
||||
];
|
||||
|
||||
options = {
|
||||
@@ -52,18 +50,6 @@ in
|
||||
};
|
||||
|
||||
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.useUserPackages = true;
|
||||
|
||||
@@ -83,35 +69,6 @@ in
|
||||
home.username = "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.
|
||||
# these clutter the home, so i mostly don't use them.
|
||||
xdg.userDirs = {
|
||||
@@ -131,7 +88,7 @@ in
|
||||
# - `xdg-mime query filetype path/to/thing.ext`
|
||||
xdg.mimeApps.enable = true;
|
||||
xdg.mimeApps.defaultApplications = let
|
||||
www = sysconfig.sane.web-browser.desktop;
|
||||
www = sysconfig.sane.web-browser.browser.desktop;
|
||||
pdf = "org.gnome.Evince.desktop";
|
||||
md = "obsidian.desktop";
|
||||
thumb = "org.gnome.gThumb.desktop";
|
||||
@@ -174,54 +131,14 @@ in
|
||||
# <item oor:path="/org.openoffice.Setup/Product"><prop oor:name="LastTimeGetInvolvedShown" oor:op="fuse"><value>1667693880</value></prop></item>
|
||||
|
||||
|
||||
|
||||
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
|
||||
# "command not found" will cause the command to be searched in nixpkgs
|
||||
nix-index.enable = true;
|
||||
} // cfg.programs;
|
||||
programs = lib.mkMerge [
|
||||
{
|
||||
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
|
||||
nix-index.enable = true;
|
||||
}
|
||||
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.
|
||||
# see: https://gitlab.com/librewolf-community/settings/-/blob/master/distribution/policies.json
|
||||
|
||||
{ config, lib, pkgs, ...}:
|
||||
{ config, lib, pkgs, sane-lib, ...}:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.sane.web-browser;
|
||||
@@ -19,22 +19,24 @@ let
|
||||
# });
|
||||
libName = "librewolf";
|
||||
dotDir = ".librewolf";
|
||||
cacheDir = ".cache/librewolf"; # TODO: is it?
|
||||
desktop = "librewolf.desktop";
|
||||
};
|
||||
firefoxSettings = {
|
||||
browser = pkgs.firefox-esr-unwrapped;
|
||||
libName = "firefox";
|
||||
dotDir = ".mozilla/firefox";
|
||||
cacheDir = ".cache/mozilla";
|
||||
desktop = "firefox.desktop";
|
||||
};
|
||||
defaultSettings = firefoxSettings;
|
||||
# defaultSettings = librewolfSettings;
|
||||
|
||||
package = pkgs.wrapFirefox cfg.browser {
|
||||
package = pkgs.wrapFirefox cfg.browser.browser {
|
||||
# inherit the default librewolf.cfg
|
||||
# it can be further customized via ~/.librewolf/librewolf.overrides.cfg
|
||||
inherit (pkgs.librewolf-unwrapped) extraPrefsFiles;
|
||||
inherit (cfg) libName;
|
||||
inherit (cfg.browser) libName;
|
||||
|
||||
extraNativeMessagingHosts = [ pkgs.browserpass ];
|
||||
# extraNativeMessagingHosts = [ pkgs.gopass-native-messaging-host ];
|
||||
@@ -55,9 +57,9 @@ let
|
||||
# get names from:
|
||||
# - ~/ref/nix-community/nur-combined/repos/rycee/pkgs/firefox-addons/generated-firefox-addons.nix
|
||||
# `wget ...xpi`; `unar ...xpi`; `cat */manifest.json | jq '.browser_specific_settings.gecko.id'`
|
||||
(addon "ublock-origin" "uBlock0@raymondhill.net" "sha256-+xc4lcdsOwXxMsr4enFsdePbIb6GHq0bFLpqvH5xXos=")
|
||||
(addon "sponsorblock" "sponsorBlocker@ajay.app" "sha256-30F8oDIgshXVY7YKgnfoc1tUTHfgeFbzXISJuVJs0AM=")
|
||||
(addon "bypass-paywalls-clean" "{d133e097-46d9-4ecc-9903-fa6a722a6e0e}" "sha256-7ZDkG8O1rEYdh/La0PLi9tp92JxYeQvaOFt/BmnDv3U=")
|
||||
(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 "ether-metamask" "webextension@metamask.io" "sha256-G+MwJDOcsaxYSUXjahHJmkWnjLeQ0Wven8DU/lGeMzA=")
|
||||
(addon "ublacklist" "@ublacklist" "sha256-vHe/7EYOzcKeAbTElmt0Rb4E2rX0f3JgXThJaUmaz+M=")
|
||||
@@ -103,43 +105,57 @@ let
|
||||
in
|
||||
{
|
||||
options = {
|
||||
sane.web-browser = mkOption {
|
||||
sane.web-browser.browser = mkOption {
|
||||
default = defaultSettings;
|
||||
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 {
|
||||
# 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.
|
||||
# specifically, enable the GDPR cookie prompt blocker.
|
||||
# data.toOverwrite.filterLists is additive (i.e. it supplements the default filters)
|
||||
# this configuration method is documented here:
|
||||
# - <https://github.com/gorhill/uBlock/issues/2986#issuecomment-364035002>
|
||||
# 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/assets/assets.json>
|
||||
home.file."${cfg.dotDir}/managed-storage/uBlock0@raymondhill.net.json".text = ''
|
||||
{
|
||||
"name": "uBlock0@raymondhill.net",
|
||||
"description": "ignored",
|
||||
"type": "storage",
|
||||
"data": {
|
||||
"toOverwrite": "{\"filterLists\": [\"fanboy-cookiemonster\"]}"
|
||||
}
|
||||
}
|
||||
'';
|
||||
home.file."${cfg.dotDir}/${cfg.libName}.overrides.cfg".text = ''
|
||||
// if we can't query the revocation status of a SSL cert because the issuer is offline,
|
||||
// treat it as unrevoked.
|
||||
// see: <https://librewolf.net/docs/faq/#im-getting-sec_error_ocsp_server_error-what-can-i-do>
|
||||
defaultPref("security.OCSP.require", false);
|
||||
'';
|
||||
config = lib.mkIf config.sane.home-manager.enable {
|
||||
|
||||
# uBlock filter list configuration.
|
||||
# specifically, enable the GDPR cookie prompt blocker.
|
||||
# data.toOverwrite.filterLists is additive (i.e. it supplements the default filters)
|
||||
# this configuration method is documented here:
|
||||
# - <https://github.com/gorhill/uBlock/issues/2986#issuecomment-364035002>
|
||||
# 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/assets/assets.json>
|
||||
sane.fs."/home/colin/${cfg.browser.dotDir}/managed-storage/uBlock0@raymondhill.net.json" = sane-lib.fs.wantedText ''
|
||||
{
|
||||
"name": "uBlock0@raymondhill.net",
|
||||
"description": "ignored",
|
||||
"type": "storage",
|
||||
"data": {
|
||||
"toOverwrite": "{\"filterLists\": [\"fanboy-cookiemonster\"]}"
|
||||
}
|
||||
}
|
||||
'';
|
||||
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,
|
||||
// treat it as unrevoked.
|
||||
// see: <https://librewolf.net/docs/faq/#im-getting-sec_error_ocsp_server_error-what-can-i-do>
|
||||
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
|
||||
{
|
||||
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 = {
|
||||
# neovim: https://github.com/neovim/neovim
|
||||
|
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
|
||||
);
|
||||
}
|
@@ -1,20 +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, ... }:
|
||||
{ pkgs, sane-lib, ... }:
|
||||
|
||||
{
|
||||
home-manager.users.colin = {
|
||||
xdg.configFile."splatmoji/splatmoji.config".text = ''
|
||||
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 80
|
||||
xdotool_command=${pkgs.wtype}/bin/wtype
|
||||
# TODO: wayland equiv
|
||||
xsel_command=xsel -b -i
|
||||
'';
|
||||
};
|
||||
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
|
||||
{
|
||||
home-manager.users.colin = let
|
||||
host = config.networking.hostName;
|
||||
user_pubkey = (import ../pubkeys.nix).users."${host}";
|
||||
known_hosts_text = builtins.concatStringsSep
|
||||
"\n"
|
||||
(builtins.attrValues (import ../pubkeys.nix).hosts);
|
||||
in { config, ...}: {
|
||||
# ssh key is stored in private storage
|
||||
home.file.".ssh/id_ed25519".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/private/.ssh/id_ed25519";
|
||||
home.file.".ssh/id_ed25519.pub".text = user_pubkey;
|
||||
with lib;
|
||||
let
|
||||
host = config.networking.hostName;
|
||||
user-pubkey = config.sane.ssh.pubkeys."colin@${host}".asUserKey;
|
||||
host-keys = filter (k: k.user == "root") (attrValues config.sane.ssh.pubkeys);
|
||||
known-hosts-text = concatStringsSep
|
||||
"\n"
|
||||
(map (k: k.asHostKey) host-keys)
|
||||
;
|
||||
in lib.mkIf config.sane.home-manager.enable {
|
||||
# ssh key is stored in private storage
|
||||
sane.persist.home.private = [ ".ssh/id_ed25519" ];
|
||||
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;
|
||||
# this optionally accepts multiple known_hosts paths, separated by space.
|
||||
programs.ssh.userKnownHostsFile = builtins.toString (pkgs.writeText "known_hosts" known_hosts_text);
|
||||
};
|
||||
users.users.colin.openssh.authorizedKeys.keys =
|
||||
let
|
||||
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
|
||||
{
|
||||
@@ -8,9 +8,5 @@ lib.mkIf config.sane.home-manager.enable
|
||||
sopsFile = ../../secrets/universal/sublime_music_config.json.bin;
|
||||
format = "binary";
|
||||
};
|
||||
home-manager.users.colin = let sysconfig = config; in { config, ... }: {
|
||||
# sublime music player
|
||||
xdg.configFile."sublime-music/config.json".source =
|
||||
config.lib.file.mkOutOfStoreSymlink sysconfig.sops.secrets.sublime_music_config.path;
|
||||
};
|
||||
sane.fs."/home/colin/.config/sublime-music/config.json" = sane-lib.fs.wantedSymlinkTo config.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
|
||||
{
|
||||
home-manager.users.colin.xdg.configFile."vlc/vlcrc".text =
|
||||
let
|
||||
feeds = import ./feeds.nix { inherit lib; };
|
||||
podcastUrls = lib.strings.concatStringsSep "|" (
|
||||
builtins.map (feed: feed.url) feeds.podcasts
|
||||
);
|
||||
in ''
|
||||
sane.fs."/home/colin/.config/vlc/vlcrc" = sane-lib.fs.wantedText ''
|
||||
[podcast]
|
||||
podcast-urls=${podcastUrls}
|
||||
podcast-urls=${podcast-urls}
|
||||
[core]
|
||||
metadata-network-access=0
|
||||
[qt]
|
||||
|
@@ -1,70 +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 ../";
|
||||
# convenient `cd`-isms
|
||||
"knowledge" = "cd ~/knowledge";
|
||||
"nixos" = "cd ~/nixos";
|
||||
"nixpkgs" = "cd ~/dev/3rd/nixpkgs";
|
||||
"ref" = "cd ~/ref";
|
||||
"secrets" = "cd ~/knowledge/secrets";
|
||||
"uninsane" = "cd ~/dev/uninsane";
|
||||
};
|
||||
}
|
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;
|
||||
let
|
||||
@@ -9,7 +10,7 @@ in
|
||||
sane.image.enable = mkOption {
|
||||
default = true;
|
||||
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.
|
||||
# e.g. EFI loaders, u-boot bootloader, etc.
|
||||
@@ -78,7 +79,9 @@ in
|
||||
"ext4" = pkgs.imageBuilder.fileSystem.makeExt4;
|
||||
"btrfs" = pkgs.imageBuilder.fileSystem.makeBtrfs;
|
||||
};
|
||||
in {
|
||||
in
|
||||
lib.mkIf cfg.enable
|
||||
{
|
||||
system.build.img-without-firmware = with pkgs; imageBuilder.diskImage.makeGPT {
|
||||
name = "nixos";
|
||||
diskID = vfatUuidFromFs bootFs;
|
||||
|
@@ -1,95 +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
|
||||
# TODO: if we changed the bluetooth installer to auto-discover the host MAC address, we could de-persist this
|
||||
"/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;
|
||||
}
|
@@ -66,18 +66,18 @@ let
|
||||
celluloid # mpv frontend
|
||||
chromium
|
||||
clinfo
|
||||
{ pkg = dino; private = ".local/share/dino"; }
|
||||
{ pkg = dino; private = [ ".local/share/dino" ]; }
|
||||
electrum
|
||||
|
||||
# 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.
|
||||
# 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.
|
||||
{ pkg = emote; dir = ".local/share/Emote"; }
|
||||
{ pkg = emote; dir = [ ".local/share/Emote" ]; }
|
||||
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
|
||||
font-manager
|
||||
@@ -85,7 +85,8 @@ let
|
||||
# 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.
|
||||
# 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
|
||||
@@ -101,7 +102,7 @@ let
|
||||
gnome.gnome-terminal # works on phosh
|
||||
gnome.gnome-weather
|
||||
|
||||
{ pkg = gpodder-configured; dir = "gPodder/Downloads"; }
|
||||
{ pkg = gpodder-configured; dir = [ "gPodder/Downloads" ]; }
|
||||
|
||||
gthumb
|
||||
handbrake
|
||||
@@ -114,15 +115,21 @@ let
|
||||
lollypop
|
||||
mesa-demos
|
||||
|
||||
{ pkg = mpv; dir = ".config/mpv/watch_later"; }
|
||||
{ pkg = mpv; dir = [ ".config/mpv/watch_later" ]; }
|
||||
|
||||
networkmanagerapplet
|
||||
|
||||
# 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?
|
||||
{ pkg = obsidian; dir = ".config/obsidian"; }
|
||||
{ pkg = obsidian; dir = [ ".config/obsidian" ]; }
|
||||
|
||||
pavucontrol
|
||||
# picard # music tagging
|
||||
@@ -135,14 +142,14 @@ let
|
||||
# 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
|
||||
# possible to pass config as a CLI arg (sublime-music -c config.json)
|
||||
# { pkg = sublime-music; dir = ".local/share/sublime-music"; }
|
||||
{ pkg = sublime-music-mobile; dir = ".local/share/sublime-music"; }
|
||||
tdesktop # broken on phosh
|
||||
# { pkg = sublime-music; dir = [ ".local/share/sublime-music" ]; }
|
||||
{ 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
|
||||
{ pkg = vlc; dir = ".config/vlc"; }
|
||||
{ pkg = vlc; dir = [ ".config/vlc" ]; }
|
||||
|
||||
whalebird # pleroma client. input is broken on phosh
|
||||
xdg-utils # for xdg-open
|
||||
@@ -157,7 +164,7 @@ let
|
||||
# XXX 2022-07-31: fix to allow links to open in default web-browser:
|
||||
# https://github.com/NixOS/nixpkgs/issues/78961
|
||||
nss = pkgs.nss_latest;
|
||||
}); in { pkg = discord; dir = ".config/discord"; })
|
||||
}); in { pkg = discord; private = [ ".config/discord" ]; })
|
||||
|
||||
# kaiteki # Pleroma client
|
||||
# gnome.zenity # for kaiteki (it will use qarma, kdialog, or zenity)
|
||||
@@ -168,25 +175,26 @@ let
|
||||
makemkv
|
||||
|
||||
# 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
|
||||
{ pkg = signal-desktop; dir = ".config/Signal"; }
|
||||
{ pkg = signal-desktop; private = [ ".config/Signal" ]; }
|
||||
|
||||
# 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
|
||||
(tor-browser-bundle-bin.override { useHardenedMalloc = false; })
|
||||
|
||||
# zcash coins. safe to delete, just slow to regenerate (10-60 minutes)
|
||||
{ pkg = zecwallet-lite; private = ".zcash"; }
|
||||
{ pkg = zecwallet-lite; private = [ ".zcash" ]; }
|
||||
] else []);
|
||||
|
||||
# general-purpose utilities that we want any user to be able to access
|
||||
# (specifically: root, in case of rescue)
|
||||
systemPkgs = [
|
||||
btrfs-progs
|
||||
cacert.unbundled # some services require unbundled /etc/ssl/certs
|
||||
cryptsetup
|
||||
dig
|
||||
efibootmgr
|
||||
@@ -238,15 +246,38 @@ let
|
||||
rustup
|
||||
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
|
||||
{
|
||||
options = {
|
||||
# packages to deploy to the user's home
|
||||
sane.packages.extraUserPkgs = mkOption {
|
||||
default = [ ];
|
||||
# each entry can be either a package, or attrs:
|
||||
# { pkg = package; dir = optional string; private = optional string };
|
||||
type = types.listOf (types.either types.package types.attrs);
|
||||
type = types.listOf toPkgSpec;
|
||||
};
|
||||
sane.packages.extraGuiPkgs = mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf toPkgSpec;
|
||||
description = "packages to only ship if gui's enabled";
|
||||
};
|
||||
sane.packages.enableConsolePkgs = mkOption {
|
||||
default = false;
|
||||
@@ -273,15 +304,19 @@ in
|
||||
sane.packages.enabledUserPkgs = mkOption {
|
||||
default = cfg.extraUserPkgs
|
||||
++ (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 [])
|
||||
;
|
||||
type = types.listOf (types.either types.package types.attrs);
|
||||
type = types.listOf toPkgSpec;
|
||||
description = "generated from other config options";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
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;
|
||||
}
|
||||
|
@@ -15,7 +15,8 @@ in
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# 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.targetUrl = "$DUPLICITY_URL";
|
||||
|
@@ -1,5 +1,8 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# TODO: consider using this library for .zone file generation:
|
||||
# - <https://github.com/kirelagin/dns.nix>
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.sane.services.trust-dns;
|
||||
@@ -22,27 +25,40 @@ let
|
||||
fmtIncludes = paths: concatStringsSep
|
||||
"\n"
|
||||
(map (path: "$INCLUDE ${path}") paths);
|
||||
|
||||
genZone = zcfg: ''
|
||||
$TTL ${toString zcfg.TTL}
|
||||
${fmtRecordAttrs "IN" "SOA" zcfg.inet.SOA}
|
||||
${fmtRecordAttrs "IN" "A" zcfg.inet.A}
|
||||
${fmtRecordAttrs "IN" "CNAME" zcfg.inet.CNAME}
|
||||
${fmtRecordAttrs "IN" "MX" zcfg.inet.MX}
|
||||
${fmtRecordAttrs "IN" "NS" zcfg.inet.NS}
|
||||
${fmtRecordAttrs "IN" "SRV" zcfg.inet.SRV}
|
||||
${fmtRecordAttrs "IN" "TXT" zcfg.inet.TXT}
|
||||
${fmtIncludes zcfg.include}
|
||||
${zcfg.extraConfig}
|
||||
'';
|
||||
|
||||
configFile = toml.generate "trust-dns.toml" {
|
||||
listen_addrs_ipv4 = cfg.listenAddrsIPv4;
|
||||
zones = attrValues (
|
||||
mapAttrs (zone: zcfg: {
|
||||
inherit zone;
|
||||
mapAttrs (zname: zcfg: rec {
|
||||
zone = if zcfg.name == null then zname else zcfg.name;
|
||||
zone_type = "Primary";
|
||||
file = pkgs.writeText "${zone}.zone" ''
|
||||
$TTL ${toString zcfg.TTL}
|
||||
${fmtRecordAttrs "IN" "SOA" zcfg.inet.SOA}
|
||||
${fmtRecordAttrs "IN" "A" zcfg.inet.A}
|
||||
${fmtRecordAttrs "IN" "CNAME" zcfg.inet.CNAME}
|
||||
${fmtRecordAttrs "IN" "MX" zcfg.inet.MX}
|
||||
${fmtRecordAttrs "IN" "NS" zcfg.inet.NS}
|
||||
${fmtRecordAttrs "IN" "SRV" zcfg.inet.SRV}
|
||||
${fmtRecordAttrs "IN" "TXT" zcfg.inet.TXT}
|
||||
${fmtIncludes zcfg.include}
|
||||
${zcfg.extraConfig}
|
||||
'';
|
||||
file = if zcfg.file == null then
|
||||
pkgs.writeText "${zone}.zone" (genZone zcfg)
|
||||
else
|
||||
zcfg.file;
|
||||
}) cfg.zones
|
||||
);
|
||||
};
|
||||
|
||||
# (listOf ty) type which also accepts single-assignment of `ty`.
|
||||
# it's used to allow the user to write:
|
||||
# CNAME."foo" = "bar";
|
||||
# as shorthand for
|
||||
# CNAME."foo" = [ "bar" ];
|
||||
listOrUnit = ty: types.coercedTo ty (elem: [ elem ]) (types.listOf ty);
|
||||
in
|
||||
{
|
||||
options = {
|
||||
@@ -60,6 +76,11 @@ in
|
||||
zones = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
description = "zone name. defaults to the attribute name in zones";
|
||||
default = null;
|
||||
};
|
||||
TTL = mkOption {
|
||||
type = types.int;
|
||||
description = "default TTL";
|
||||
@@ -77,50 +98,62 @@ in
|
||||
};
|
||||
inet = {
|
||||
SOA = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "Start of Authority record(s)";
|
||||
default = {};
|
||||
};
|
||||
A = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "IPv4 address record(s)";
|
||||
default = {};
|
||||
};
|
||||
CNAME = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "canonical name record(s)";
|
||||
default = {};
|
||||
};
|
||||
MX = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "mail exchanger record(s)";
|
||||
default = {};
|
||||
};
|
||||
NS = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "name server record(s)";
|
||||
default = {};
|
||||
};
|
||||
SRV = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "service record(s)";
|
||||
default = {};
|
||||
};
|
||||
TXT = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "text record(s)";
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
|
||||
file = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "instead of using the generated zone file, use the specified path";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
description = "Declarative zone config";
|
||||
};
|
||||
|
||||
generatedZones = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
description = "generated zone text for each zone";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
sane.services.trust-dns.generatedZones = mapAttrs (zone: zcfg: genZone zcfg) cfg.zones;
|
||||
networking.firewall.allowedTCPPorts = [ 53 ];
|
||||
networking.firewall.allowedUDPPorts = [ 53 ];
|
||||
|
||||
|
32
modules/sops.nix
Normal file
32
modules/sops.nix
Normal file
@@ -0,0 +1,32 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
# taken from sops-nix code: checks if any secrets are needed to create /etc/shadow
|
||||
secrets-for-users = (lib.filterAttrs (_: v: v.neededForUsers) config.sops.secrets) != {};
|
||||
sops-files = config.sops.age.sshKeyPaths ++ config.sops.gnupg.sshKeyPaths ++ [ config.sops.age.keyFile ];
|
||||
keys-in-etc = builtins.any (p: builtins.substring 0 5 p == "/etc/") sops-files;
|
||||
in
|
||||
{
|
||||
config = lib.mkIf (secrets-for-users && keys-in-etc) {
|
||||
# secret decoding depends on keys in /etc/ (like the ssh host key), so make sure those are present.
|
||||
system.activationScripts.setupSecretsForUsers = lib.mkIf secrets-for-users {
|
||||
deps = [ "etc" ];
|
||||
};
|
||||
# TODO: we should selectively remove "users" and "groups", but keep manually specified deps?
|
||||
system.activationScripts.etc.deps = lib.mkForce [];
|
||||
assertions = builtins.concatLists (builtins.attrValues (
|
||||
builtins.mapAttrs
|
||||
(path: value: [
|
||||
{
|
||||
assertion = (builtins.substring 0 1 value.user) == "+";
|
||||
message = "non-numeric user for /etc/${path}: ${value.user} prevents early /etc linking";
|
||||
}
|
||||
{
|
||||
assertion = (builtins.substring 0 1 value.group) == "+";
|
||||
message = "non-numeric group for /etc/${path}: ${value.group} prevents early /etc linking";
|
||||
}
|
||||
])
|
||||
config.environment.etc
|
||||
));
|
||||
};
|
||||
}
|
87
modules/ssh.nix
Normal file
87
modules/ssh.nix
Normal file
@@ -0,0 +1,87 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
key = types.submodule ({ name, config, ...}: {
|
||||
options = {
|
||||
typedPubkey = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
the pubkey with type attached.
|
||||
e.g. "ssh-ed25519 <base64>"
|
||||
'';
|
||||
};
|
||||
# type = mkOption {
|
||||
# type = types.str;
|
||||
# description = ''
|
||||
# the type of the key, e.g. "id_ed25519"
|
||||
# '';
|
||||
# };
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
the hostname of a key
|
||||
'';
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
the username of a key
|
||||
'';
|
||||
};
|
||||
asUserKey = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
append the "user@host" value to the pubkey to make it usable for ~/.ssh/id_<x>.pub or authorized_keys
|
||||
'';
|
||||
};
|
||||
asHostKey = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
prepend the "host" value to the pubkey to make it usable for ~/.ssh/known_hosts
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = rec {
|
||||
user = head (lib.splitString "@" name);
|
||||
host = last (lib.splitString "@" name);
|
||||
asUserKey = "${config.typedPubkey} ${name}";
|
||||
asHostKey = "${host} ${config.typedPubkey}";
|
||||
};
|
||||
});
|
||||
coercedToKey = types.coercedTo types.str (typedPubkey: {
|
||||
inherit typedPubkey;
|
||||
}) key;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
sane.ssh.pubkeys = mkOption {
|
||||
type = types.attrsOf coercedToKey;
|
||||
default = [];
|
||||
description = ''
|
||||
mapping from "user@host" to pubkey.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
# persist the host key
|
||||
# prefer specifying it via environment.etc since although it is generated per-host,
|
||||
# it's made to be immutable after generation. hence, a `persist`-style mount wouldn't be as great.
|
||||
environment.etc."ssh/host_keys".source = "/nix/persist/etc/ssh/host_keys";
|
||||
# sane.persist.sys.plaintext = [ "/etc/ssh/host_keys" ];
|
||||
|
||||
# let openssh find our host keys
|
||||
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"; }
|
||||
];
|
||||
|
||||
services.openssh.knownHosts =
|
||||
let
|
||||
host-keys = filter (k: k.user == "root") (attrValues config.sane.ssh.pubkeys);
|
||||
in lib.mkMerge (builtins.map (key: {
|
||||
"${key.host}".publicKey = key.typedPubkey;
|
||||
}) host-keys);
|
||||
};
|
||||
}
|
@@ -1,10 +1,10 @@
|
||||
{ stdenv, pkgs }:
|
||||
{ stdenv, tow-boot-rp4, raspberrypifw, raspberrypi-armstubs }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "bootpart-tow-boot-rpi-aarch64";
|
||||
version = "1";
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
buildInputs = with [
|
||||
tow-boot-rpi4 # for Tow-Boot.*.bin
|
||||
raspberrypifw # for bootcode.bin, *.dat, *.elf, *.dtb
|
||||
raspberrypi-armstubs # for armstub*
|
||||
@@ -15,7 +15,7 @@ stdenv.mkDerivation rec {
|
||||
|
||||
dontUnpack = true;
|
||||
|
||||
installPhase = with pkgs; ''
|
||||
installPhase = ''
|
||||
mkdir "$out"
|
||||
cp ${tow-boot-rpi4}/Tow-Boot.noenv.*.bin "$out"/
|
||||
cp -R ${raspberrypifw}/share/raspberrypi/boot/*.dtb "$out"/
|
||||
|
@@ -1,10 +1,14 @@
|
||||
{ stdenv, pkgs }:
|
||||
{ stdenv
|
||||
, ubootRaspberryPi4_64bit
|
||||
, raspberrypifw
|
||||
, raspberrypi-armstubs
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "bootpart-u-boot-rpi-aarch64";
|
||||
version = "1";
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
buildInputs = [
|
||||
ubootRaspberryPi4_64bit
|
||||
raspberrypifw # for bootcode.bin, *.dat, *.elf, *.dtb
|
||||
raspberrypi-armstubs # for armstub*
|
||||
@@ -15,7 +19,7 @@ stdenv.mkDerivation rec {
|
||||
|
||||
dontUnpack = true;
|
||||
|
||||
installPhase = with pkgs; ''
|
||||
installPhase = ''
|
||||
mkdir "$out"
|
||||
cp ${ubootRaspberryPi4_64bit}/u-boot.bin "$out"/
|
||||
cp ${ubootRaspberryPi4_64bit}/*.dtb "$out"/
|
||||
|
@@ -1,14 +1,14 @@
|
||||
{ stdenv, pkgs }:
|
||||
{ stdenv, syslinux }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "bootpart-uefi-x86_64";
|
||||
version = "1";
|
||||
|
||||
buildInputs = [ pkgs.syslinux ];
|
||||
buildInputs = [ syslinux ];
|
||||
|
||||
dontUnpack = true;
|
||||
|
||||
installPhase = with pkgs; ''
|
||||
installPhase = ''
|
||||
# populate the EFI directory with syslinux, and configure it to read that extlinux.conf file managed by nixos
|
||||
mkdir -p "$out/EFI/syslinux" "$out/EFI/BOOT" "$out/syslinux"
|
||||
cp -R "${syslinux}/share/syslinux/efi64"/* "$out/EFI/syslinux"
|
||||
|
@@ -1,8 +1,8 @@
|
||||
{ pkgs
|
||||
{ lib
|
||||
, browserpass
|
||||
, bash
|
||||
, fetchFromGitea
|
||||
, gnused
|
||||
, lib
|
||||
, sane-scripts
|
||||
, sops
|
||||
, stdenv
|
||||
@@ -26,7 +26,7 @@ let
|
||||
|
||||
};
|
||||
in
|
||||
(pkgs.browserpass.overrideAttrs (upstream: {
|
||||
(browserpass.overrideAttrs (upstream: {
|
||||
src = fetchFromGitea {
|
||||
domain = "git.uninsane.org";
|
||||
owner = "colin";
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user