Compare commits
140 Commits
wip/imperm
...
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 |
40
flake.lock
generated
40
flake.lock
generated
@@ -36,21 +36,6 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"impermanence": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1668668915,
|
|
||||||
"narHash": "sha256-QjY4ZZbs9shwO4LaLpvlU2bO9J1juYhO9NtV3nrbnYQ=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "impermanence",
|
|
||||||
"rev": "5df9108b346f8a42021bf99e50de89c9caa251c3",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "impermanence",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mobile-nixos": {
|
"mobile-nixos": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
@@ -69,11 +54,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1671722432,
|
"lastModified": 1672953546,
|
||||||
"narHash": "sha256-ojcZUekIQeOZkHHzR81st7qxX99dB1Eaaq6PU5MNeKc=",
|
"narHash": "sha256-oz757DnJ1ITvwyTovuwG3l9cX6j9j6/DH9eH+cXFJmc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "652e92b8064949a11bc193b90b74cb727f2a1405",
|
"rev": "a518c77148585023ff56022f09c4b2c418a51ef5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -84,11 +69,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-stable": {
|
"nixpkgs-stable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1671883564,
|
"lastModified": 1673163619,
|
||||||
"narHash": "sha256-C15oAtyupmLB3coZY7qzEHXjhtUx/+77olVdqVMruAg=",
|
"narHash": "sha256-B33PFBL64ZgTWgMnhFL3jgheAN/DjHPsZ1Ih3z0VE5I=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "dac57a4eccf1442e8bf4030df6fcbb55883cb682",
|
"rev": "8c54d842d9544361aac5f5b212ba04e4089e8efe",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -99,11 +84,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-stable_2": {
|
"nixpkgs-stable_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1671923641,
|
"lastModified": 1673100377,
|
||||||
"narHash": "sha256-flPauiL5UrfRJD+1oAcEefpEIUqTqnyKScWe/UUU+lE=",
|
"narHash": "sha256-mT76pTd0YFxT6CwtPhDgHJhuIgLY+ZLSMiQpBufwMG4=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "939c05a176b8485971463c18c44f48e56a7801c9",
|
"rev": "9f11a2df77cb945c115ae2a65f53f38121597d73",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -116,7 +101,6 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
"impermanence": "impermanence",
|
|
||||||
"mobile-nixos": "mobile-nixos",
|
"mobile-nixos": "mobile-nixos",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"nixpkgs-stable": "nixpkgs-stable",
|
"nixpkgs-stable": "nixpkgs-stable",
|
||||||
@@ -132,11 +116,11 @@
|
|||||||
"nixpkgs-stable": "nixpkgs-stable_2"
|
"nixpkgs-stable": "nixpkgs-stable_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1671937829,
|
"lastModified": 1673147300,
|
||||||
"narHash": "sha256-YtaNB+mLw0d67JFYNjRWM+/AL3JCXuD/DGlnTlyX1tY=",
|
"narHash": "sha256-gR9OEfTzWfL6vG0qkbn1TlBAOlg4LuW8xK/u0V41Ihc=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "855b8d51fc3991bd817978f0f093aa6ae0fae738",
|
"rev": "2253120d2a6147e57bafb5c689e086221df8032f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@@ -18,7 +18,6 @@
|
|||||||
url = "github:Mic92/sops-nix";
|
url = "github:Mic92/sops-nix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
impermanence.url = "github:nix-community/impermanence";
|
|
||||||
uninsane = {
|
uninsane = {
|
||||||
url = "git+https://git.uninsane.org/colin/uninsane";
|
url = "git+https://git.uninsane.org/colin/uninsane";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
@@ -32,7 +31,6 @@
|
|||||||
mobile-nixos,
|
mobile-nixos,
|
||||||
home-manager,
|
home-manager,
|
||||||
sops-nix,
|
sops-nix,
|
||||||
impermanence,
|
|
||||||
uninsane
|
uninsane
|
||||||
}: let
|
}: let
|
||||||
patchedPkgs = system: nixpkgs.legacyPackages.${system}.applyPatches {
|
patchedPkgs = system: nixpkgs.legacyPackages.${system}.applyPatches {
|
||||||
@@ -54,12 +52,10 @@
|
|||||||
in (nixosSystem {
|
in (nixosSystem {
|
||||||
# by default the local system is the same as the target, employing emulation when they differ
|
# by default the local system is the same as the target, employing emulation when they differ
|
||||||
system = target;
|
system = target;
|
||||||
specialArgs = { inherit mobile-nixos home-manager impermanence; };
|
|
||||||
modules = [
|
modules = [
|
||||||
./modules
|
./modules
|
||||||
(import ./hosts/instantiate.nix name)
|
(import ./hosts/instantiate.nix name)
|
||||||
home-manager.nixosModule
|
home-manager.nixosModule
|
||||||
impermanence.nixosModule
|
|
||||||
sops-nix.nixosModules.sops
|
sops-nix.nixosModules.sops
|
||||||
{
|
{
|
||||||
nixpkgs.overlays = [
|
nixpkgs.overlays = [
|
||||||
@@ -77,10 +73,6 @@
|
|||||||
# gocryptfs = cross.gocryptfs;
|
# gocryptfs = cross.gocryptfs;
|
||||||
|
|
||||||
# pinned packages:
|
# 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,18 +1,16 @@
|
|||||||
{ lib, pkgs, ... }:
|
{ lib, pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# TODO: don't need to depend on binsh if we were to use a nix-style shebang
|
# persist external pairings by default
|
||||||
system.activationScripts.linkBluetoothKeys = let
|
sane.persist.sys.plaintext = [ "/var/lib/bluetooth" ];
|
||||||
unwrapped = ../../scripts/install-bluetooth;
|
|
||||||
install-bluetooth = pkgs.writeShellApplication {
|
sane.fs."/var/lib/bluetooth".generated.acl.mode = "0700";
|
||||||
name = "install-bluetooth";
|
sane.fs."/var/lib/bluetooth/.secrets.stamp" = {
|
||||||
runtimeInputs = with pkgs; [ coreutils gnused ];
|
wantedBeforeBy = [ "bluetooth.service" ];
|
||||||
text = ''${unwrapped} "$@"'';
|
# 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 + ''
|
||||||
in (lib.stringAfter
|
touch "/var/lib/bluetooth/.secrets.stamp"
|
||||||
[ "setupSecrets" "binsh" ]
|
'';
|
||||||
''
|
generated.script.scriptArgs = [ "/run/secrets/bt" ];
|
||||||
${install-bluetooth}/bin/install-bluetooth /run/secrets/bt
|
};
|
||||||
''
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@@ -2,9 +2,11 @@
|
|||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./bluetooth.nix
|
./bluetooth.nix
|
||||||
|
./feeds.nix
|
||||||
./fs.nix
|
./fs.nix
|
||||||
./hardware
|
./hardware
|
||||||
./i2p.nix
|
./i2p.nix
|
||||||
|
./ids.nix
|
||||||
./machine-id.nix
|
./machine-id.nix
|
||||||
./net.nix
|
./net.nix
|
||||||
./secrets.nix
|
./secrets.nix
|
||||||
@@ -18,12 +20,11 @@
|
|||||||
sane.packages.enableConsolePkgs = true;
|
sane.packages.enableConsolePkgs = true;
|
||||||
sane.packages.enableSystemPkgs = true;
|
sane.packages.enableSystemPkgs = true;
|
||||||
|
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
"/var/log"
|
"/var/log"
|
||||||
"/var/backup" # for e.g. postgres dumps
|
"/var/backup" # for e.g. postgres dumps
|
||||||
# TODO: move elsewhere
|
# TODO: move elsewhere
|
||||||
"/var/lib/alsa" # preserve output levels, default devices
|
"/var/lib/alsa" # preserve output levels, default devices
|
||||||
"/var/lib/bluetooth" # preserve bluetooth handshakes
|
|
||||||
"/var/lib/colord" # preserve color calibrations (?)
|
"/var/lib/colord" # preserve color calibrations (?)
|
||||||
"/var/lib/machines" # maybe not needed, but would be painful to add a VM and forget.
|
"/var/lib/machines" # maybe not needed, but would be painful to add a VM and forget.
|
||||||
];
|
];
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
{ lib }:
|
{ ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
hourly = { freq = "hourly"; };
|
hourly = { freq = "hourly"; };
|
||||||
daily = { freq = "daily"; };
|
daily = { freq = "daily"; };
|
||||||
@@ -13,24 +12,15 @@ let
|
|||||||
tech = { cat = "tech"; };
|
tech = { cat = "tech"; };
|
||||||
uncat = { cat = "uncat"; };
|
uncat = { cat = "uncat"; };
|
||||||
|
|
||||||
text = { format = "text"; };
|
|
||||||
image = { format = "image"; };
|
|
||||||
podcast = { format = "podcast"; };
|
|
||||||
|
|
||||||
mkRss = format: url: { inherit url format; } // uncat // infrequent;
|
mkRss = format: url: { inherit url format; } // uncat // infrequent;
|
||||||
# format-specific helpers
|
# format-specific helpers
|
||||||
mkText = mkRss text;
|
mkText = mkRss "text";
|
||||||
mkImg = mkRss image;
|
mkImg = mkRss "image";
|
||||||
mkPod = mkRss podcast;
|
mkPod = mkRss "podcast";
|
||||||
|
|
||||||
# host-specific helpers
|
# host-specific helpers
|
||||||
mkSubstack = subdomain: mkText "https://${subdomain}.substack.com/feed";
|
mkSubstack = subdomain: { substack = subdomain; };
|
||||||
|
|
||||||
# merge the attrs `new` into each value of the attrs `addTo`
|
|
||||||
addAttrs = new: addTo: builtins.mapAttrs (k: v: v // new) addTo;
|
|
||||||
# for each value in `attrs`, add a value to the child attrs which holds its key within the parent attrs.
|
|
||||||
withInverseMapping = key: attrs: builtins.mapAttrs (k: v: v // { "${key}" = k; }) attrs;
|
|
||||||
in rec {
|
|
||||||
podcasts = [
|
podcasts = [
|
||||||
(mkPod "https://lexfridman.com/feed/podcast/" // rat // weekly)
|
(mkPod "https://lexfridman.com/feed/podcast/" // rat // weekly)
|
||||||
## Astral Codex Ten
|
## Astral Codex Ten
|
||||||
@@ -149,46 +139,13 @@ in rec {
|
|||||||
images = [
|
images = [
|
||||||
(mkImg "https://www.smbc-comics.com/comic/rss" // humor // daily)
|
(mkImg "https://www.smbc-comics.com/comic/rss" // humor // daily)
|
||||||
(mkImg "https://xkcd.com/atom.xml" // humor // daily)
|
(mkImg "https://xkcd.com/atom.xml" // humor // daily)
|
||||||
(mkImg "http://dilbert.com/feed" // humor // daily)
|
(mkImg "https://pbfcomics.com/feed" // humor // infrequent)
|
||||||
|
# (mkImg "http://dilbert.com/feed" // humor // daily)
|
||||||
|
|
||||||
# ART
|
# ART
|
||||||
(mkImg "https://miniature-calendar.com/feed" // art // daily)
|
(mkImg "https://miniature-calendar.com/feed" // art // daily)
|
||||||
];
|
];
|
||||||
|
in
|
||||||
all = texts ++ images ++ podcasts;
|
{
|
||||||
|
sane.feeds = texts ++ images ++ podcasts;
|
||||||
# return only the feed items which match this category (e.g. "tech")
|
|
||||||
filterCat = cat: feeds: builtins.filter (item: item.cat == cat) feeds;
|
|
||||||
# return only the feed items which match this format (e.g. "podcast")
|
|
||||||
filterFormat = format: feeds: builtins.filter (item: item.format == format) feeds;
|
|
||||||
|
|
||||||
# transform a list of feeds into an attrs mapping cat => [ feed0 feed1 ... ]
|
|
||||||
partitionByCat = feeds: builtins.groupBy (f: f.cat) feeds;
|
|
||||||
|
|
||||||
# represents a single RSS feed.
|
|
||||||
opmlTerminal = feed: ''<outline xmlUrl="${feed.url}" type="rss"/>'';
|
|
||||||
# a list of RSS feeds.
|
|
||||||
opmlTerminals = feeds: lib.strings.concatStringsSep "\n" (builtins.map opmlTerminal feeds);
|
|
||||||
# one node which packages some flat grouping of terminals.
|
|
||||||
opmlGroup = title: feeds: ''
|
|
||||||
<outline text="${title}" title="${title}">
|
|
||||||
${opmlTerminals feeds}
|
|
||||||
</outline>
|
|
||||||
'';
|
|
||||||
# a list of groups (`groupMap` is an attrs mapping groupName => [ feed0 feed1 ... ]).
|
|
||||||
opmlGroups = groupMap: lib.strings.concatStringsSep "\n" (
|
|
||||||
builtins.attrValues (builtins.mapAttrs opmlGroup groupMap)
|
|
||||||
);
|
|
||||||
# top-level OPML file which could be consumed by something else.
|
|
||||||
opmlTopLevel = body: ''
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<opml version="2.0">
|
|
||||||
<body>
|
|
||||||
${body}
|
|
||||||
</body>
|
|
||||||
</opml>
|
|
||||||
'';
|
|
||||||
|
|
||||||
# **primary API**: generate a OPML file from the provided feeds
|
|
||||||
feedsToOpml = feeds: opmlTopLevel (opmlGroups (partitionByCat feeds));
|
|
||||||
}
|
}
|
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;
|
||||||
|
}
|
@@ -31,19 +31,13 @@
|
|||||||
General.RoamThreshold5G = "-52"; # default -76
|
General.RoamThreshold5G = "-52"; # default -76
|
||||||
};
|
};
|
||||||
|
|
||||||
# TODO: don't need to depend on binsh if we were to use a nix-style shebang
|
sane.fs."/var/lib/iwd/.secrets.psk.stamp" = {
|
||||||
system.activationScripts.linkIwdKeys = let
|
wantedBeforeBy = [ "iwd.service" ];
|
||||||
unwrapped = ../../scripts/install-iwd;
|
generated.acl.mode = "0600";
|
||||||
install-iwd = pkgs.writeShellApplication {
|
# XXX: install-iwd uses sed, but that's part of the default systemd unit path, it seems
|
||||||
name = "install-iwd";
|
generated.script.script = builtins.readFile ../../scripts/install-iwd + ''
|
||||||
runtimeInputs = with pkgs; [ coreutils gnused ];
|
touch "/var/lib/iwd/.secrets.psk.stamp"
|
||||||
text = ''${unwrapped} "$@"'';
|
'';
|
||||||
};
|
generated.script.scriptArgs = [ "/run/secrets/iwd" "/var/lib/iwd" ];
|
||||||
in (lib.stringAfter
|
};
|
||||||
[ "setupSecrets" "binsh" ]
|
|
||||||
''
|
|
||||||
mkdir -p /var/lib/iwd
|
|
||||||
${install-iwd}/bin/install-iwd /run/secrets/iwd /var/lib/iwd
|
|
||||||
''
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,24 @@
|
|||||||
{ config, lib, ... }:
|
{ config, lib, sane-data, sane-lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
environment.etc."ssh/host_keys".source = "/nix/persist/etc/ssh/host_keys";
|
sane.ssh.pubkeys =
|
||||||
|
let
|
||||||
services.openssh.hostKeys = [
|
# path is a DNS-style path like [ "org" "uninsane" "root" ]
|
||||||
{ type = "rsa"; bits = 4096; path = "/etc/ssh/host_keys/ssh_host_rsa_key"; }
|
keyNameForPath = path:
|
||||||
{ type = "ed25519"; path = "/etc/ssh/host_keys/ssh_host_ed25519_key"; }
|
let
|
||||||
];
|
rev = lib.reverseList path;
|
||||||
|
name = builtins.head rev;
|
||||||
|
host = lib.concatStringsSep "." (builtins.tail rev);
|
||||||
|
in
|
||||||
|
"${name}@${host}";
|
||||||
|
|
||||||
|
# [{ 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
|
# installer docs: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/installation-device.nix
|
||||||
with lib;
|
with lib;
|
||||||
let
|
let
|
||||||
cfg = config.sane.users;
|
cfg = config.sane.users;
|
||||||
# see nixpkgs/nixos/modules/services/networking/dhcpcd.nix
|
fs = sane-lib.fs;
|
||||||
hasDHCP = config.networking.dhcpcd.enable &&
|
|
||||||
(config.networking.useDHCP || any (i: i.useDHCP == true) (attrValues config.networking.interfaces));
|
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
@@ -28,8 +25,7 @@ in
|
|||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
home = "/home/colin";
|
home = "/home/colin";
|
||||||
createHome = true;
|
createHome = true;
|
||||||
homeMode = "700";
|
homeMode = "0700";
|
||||||
uid = config.sane.allocations.colin-uid;
|
|
||||||
# i don't get exactly what this is, but nixos defaults to this non-deterministically
|
# i don't get exactly what this is, but nixos defaults to this non-deterministically
|
||||||
# in /var/lib/nixos/auto-subuid-map and i don't want that.
|
# in /var/lib/nixos/auto-subuid-map and i don't want that.
|
||||||
subUidRanges = [
|
subUidRanges = [
|
||||||
@@ -52,53 +48,65 @@ in
|
|||||||
passwordFile = lib.mkIf (config.sops.secrets ? "colin-passwd") config.sops.secrets.colin-passwd.path;
|
passwordFile = lib.mkIf (config.sops.secrets ? "colin-passwd") config.sops.secrets.colin-passwd.path;
|
||||||
|
|
||||||
shell = pkgs.zsh;
|
shell = pkgs.zsh;
|
||||||
openssh.authorizedKeys.keys = builtins.attrValues (import ../../modules/pubkeys.nix).users;
|
|
||||||
|
|
||||||
|
# mount encrypted stuff at login
|
||||||
# some other nix pam users:
|
# some other nix pam users:
|
||||||
# - <https://github.com/g00pix/nixconf/blob/32c04f6fa843fed97639dd3f09e157668d3eea1f/profiles/sshfs.nix>
|
# - <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/lourkeur/distro/blob/11173454c6bb50f7ccab28cc2c757dca21446d1d/nixos/profiles/users/louis-full.nix>
|
||||||
# - <https://github.com/dnr/sample-nix-code/blob/03494480c1fae550c033aa54fd96aeb3827761c5/nixos/laptop.nix>
|
# - <https://github.com/dnr/sample-nix-code/blob/03494480c1fae550c033aa54fd96aeb3827761c5/nixos/laptop.nix>
|
||||||
pamMount = {
|
pamMount = let
|
||||||
# mount encrypted stuff at login
|
priv = config.fileSystems."/home/colin/private";
|
||||||
# requires that login password == fs encryption password
|
in {
|
||||||
fstype = "fuse";
|
fstype = priv.fsType;
|
||||||
path = "gocryptfs#/nix/persist/home/colin/private";
|
path = priv.device;
|
||||||
# path = "${pkgs.gocryptfs}/bin/gocryptfs#/nix/persist/home/colin/private";
|
mountpoint = priv.mountPoint;
|
||||||
# fstype = "fuse.gocryptfs";
|
options = builtins.concatStringsSep "," priv.options;
|
||||||
# path = "/nix/persist/home/colin/private";
|
|
||||||
mountpoint = "/home/colin/private";
|
|
||||||
# without allow_other, *root* isn't allowed to list anything in ~/private.
|
|
||||||
# which is weird (root can just `su colin`), but probably doesn't *hurt* anything -- right?
|
|
||||||
options="nodev,nosuid,quiet"; # allow_other
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# required for PAM to find gocryptfs
|
|
||||||
security.pam.mount.additionalSearchPaths = [ pkgs.gocryptfs ];
|
|
||||||
security.pam.mount.enable = 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;
|
|
||||||
|
|
||||||
sane.impermanence.home-dirs = [
|
# ensure ~ perms are known to sane.fs module.
|
||||||
# cache is probably too big to fit on the tmpfs
|
# TODO: this is generic enough to be lifted up into sane.fs itself.
|
||||||
# { directory = ".cache"; encryptedClearOnBoot = true; }
|
sane.fs."/home/colin".dir.acl = {
|
||||||
{ directory = ".cache/mozilla"; encryptedClearOnBoot = true; }
|
user = "colin";
|
||||||
|
group = config.users.users.colin.group;
|
||||||
|
mode = config.users.users.colin.homeMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.persist.home.plaintext = [
|
||||||
|
"archive"
|
||||||
|
"dev"
|
||||||
|
# TODO: records should be private
|
||||||
|
"records"
|
||||||
|
"ref"
|
||||||
|
"tmp"
|
||||||
|
"use"
|
||||||
|
"Music"
|
||||||
|
"Pictures"
|
||||||
|
"Videos"
|
||||||
|
|
||||||
".cargo"
|
".cargo"
|
||||||
".rustup"
|
".rustup"
|
||||||
# TODO: move this to ~/private!
|
|
||||||
".local/share/keyrings"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
sane.impermanence.dirs = mkIf cfg.guest.enable [
|
# convenience
|
||||||
{ user = "guest"; group = "users"; directory = "/home/guest"; }
|
sane.fs."/home/colin/knowledge" = fs.wantedSymlinkTo "/home/colin/private/knowledge";
|
||||||
|
sane.fs."/home/colin/nixos" = fs.wantedSymlinkTo "/home/colin/dev/nixos";
|
||||||
|
sane.fs."/home/colin/Videos/servo" = fs.wantedSymlinkTo "/mnt/servo-media/Videos";
|
||||||
|
sane.fs."/home/colin/Videos/servo-incomplete" = fs.wantedSymlinkTo "/mnt/servo-media/incomplete";
|
||||||
|
sane.fs."/home/colin/Music/servo" = fs.wantedSymlinkTo "/mnt/servo-media/Music";
|
||||||
|
|
||||||
|
# used by password managers, e.g. unix `pass`
|
||||||
|
sane.fs."/home/colin/.password-store" = fs.wantedSymlinkTo "/home/colin/knowledge/secrets/accounts";
|
||||||
|
|
||||||
|
sane.persist.sys.plaintext = mkIf cfg.guest.enable [
|
||||||
|
# intentionally allow other users to write to the guest folder
|
||||||
|
{ directory = "/home/guest"; user = "guest"; group = "users"; mode = "0775"; }
|
||||||
];
|
];
|
||||||
users.users.guest = mkIf cfg.guest.enable {
|
users.users.guest = mkIf cfg.guest.enable {
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
home = "/home/guest";
|
home = "/home/guest";
|
||||||
uid = config.sane.allocations.guest-uid;
|
|
||||||
subUidRanges = [
|
subUidRanges = [
|
||||||
{ startUid=200000; count=1; }
|
{ startUid=200000; count=1; }
|
||||||
];
|
];
|
||||||
@@ -110,13 +118,6 @@ in
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
users.users.dhcpcd = mkIf hasDHCP {
|
|
||||||
uid = config.sane.allocations.dhcpcd-uid;
|
|
||||||
};
|
|
||||||
users.groups.dhcpcd = mkIf hasDHCP {
|
|
||||||
gid = config.sane.allocations.dhcpcd-gid;
|
|
||||||
};
|
|
||||||
|
|
||||||
security.sudo = {
|
security.sudo = {
|
||||||
enable = true;
|
enable = true;
|
||||||
wheelNeedsPassword = false;
|
wheelNeedsPassword = false;
|
||||||
@@ -127,31 +128,5 @@ in
|
|||||||
permitRootLogin = "no";
|
permitRootLogin = "no";
|
||||||
passwordAuthentication = false;
|
passwordAuthentication = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
# affix some UIDs which were historically auto-generated
|
|
||||||
users.users.sshd.uid = config.sane.allocations.sshd-uid;
|
|
||||||
users.groups.polkituser.gid = config.sane.allocations.polkituser-gid;
|
|
||||||
users.groups.sshd.gid = config.sane.allocations.sshd-gid;
|
|
||||||
users.groups.systemd-coredump.gid = config.sane.allocations.systemd-coredump-gid;
|
|
||||||
users.users.nscd.uid = config.sane.allocations.nscd-uid;
|
|
||||||
users.groups.nscd.gid = config.sane.allocations.nscd-gid;
|
|
||||||
users.users.systemd-oom.uid = config.sane.allocations.systemd-oom-uid;
|
|
||||||
users.groups.systemd-oom.gid = config.sane.allocations.systemd-oom-gid;
|
|
||||||
|
|
||||||
# guarantee determinism in uid/gid generation for users:
|
|
||||||
assertions = let
|
|
||||||
uidAssertions = builtins.attrValues (builtins.mapAttrs (name: user: {
|
|
||||||
assertion = user.uid != null;
|
|
||||||
message = "non-deterministic uid detected for: ${name}";
|
|
||||||
}) config.users.users);
|
|
||||||
gidAssertions = builtins.attrValues (builtins.mapAttrs (name: group: {
|
|
||||||
assertion = group.gid != null;
|
|
||||||
message = "non-deterministic gid detected for: ${name}";
|
|
||||||
}) config.users.groups);
|
|
||||||
autoSubAssertions = builtins.attrValues (builtins.mapAttrs (name: user: {
|
|
||||||
assertion = !user.autoSubUidGidRange;
|
|
||||||
message = "non-deterministic subUids/Guids detected for: ${name}";
|
|
||||||
}) config.users.users);
|
|
||||||
in uidAssertions ++ gidAssertions ++ autoSubAssertions;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -10,15 +10,13 @@
|
|||||||
sane.services.duplicity.enable = true;
|
sane.services.duplicity.enable = true;
|
||||||
sane.services.nixserve.enable = true;
|
sane.services.nixserve.enable = true;
|
||||||
sane.services.nixserve.sopsFile = ../../secrets/desko.yaml;
|
sane.services.nixserve.sopsFile = ../../secrets/desko.yaml;
|
||||||
sane.impermanence.enable = true;
|
sane.persist.enable = true;
|
||||||
|
|
||||||
boot.loader.efi.canTouchEfiVariables = false;
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||||
|
|
||||||
# needed to use libimobiledevice/ifuse, for iphone sync
|
# needed to use libimobiledevice/ifuse, for iphone sync
|
||||||
services.usbmuxd.enable = true;
|
services.usbmuxd.enable = true;
|
||||||
users.users.usbmux.uid = config.sane.allocations.usbmux-uid;
|
|
||||||
users.groups.usbmux.gid = config.sane.allocations.usbmux-gid;
|
|
||||||
|
|
||||||
sops.secrets.colin-passwd = {
|
sops.secrets.colin-passwd = {
|
||||||
sopsFile = ../../secrets/desko.yaml;
|
sopsFile = ../../secrets/desko.yaml;
|
||||||
@@ -52,7 +50,7 @@
|
|||||||
remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play
|
remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play
|
||||||
dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server
|
dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server
|
||||||
};
|
};
|
||||||
sane.impermanence.home-dirs = [
|
sane.persist.home.plaintext = [
|
||||||
".steam"
|
".steam"
|
||||||
".local/share/Steam"
|
".local/share/Steam"
|
||||||
];
|
];
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.root-on-tmpfs = true;
|
sane.persist.root-on-tmpfs = true;
|
||||||
# we need a /tmp for building large nix things.
|
# we need a /tmp for building large nix things.
|
||||||
# a cross-compiled kernel, particularly, will easily use 30+GB of tmp
|
# a cross-compiled kernel, particularly, will easily use 30+GB of tmp
|
||||||
fileSystems."/tmp" = {
|
fileSystems."/tmp" = {
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
# sane.users.guest.enable = true;
|
# sane.users.guest.enable = true;
|
||||||
sane.gui.sway.enable = true;
|
sane.gui.sway.enable = true;
|
||||||
sane.impermanence.enable = true;
|
sane.persist.enable = true;
|
||||||
sane.nixcache.enable = true;
|
sane.nixcache.enable = true;
|
||||||
boot.loader.efi.canTouchEfiVariables = false;
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.root-on-tmpfs = true;
|
sane.persist.root-on-tmpfs = true;
|
||||||
# we need a /tmp of default size (half RAM) for building large nix things
|
# we need a /tmp of default size (half RAM) for building large nix things
|
||||||
fileSystems."/tmp" = {
|
fileSystems."/tmp" = {
|
||||||
device = "none";
|
device = "none";
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{ config, pkgs, lib, mobile-nixos, ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./firmware.nix
|
./firmware.nix
|
||||||
@@ -24,8 +24,9 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# usability compromises
|
# usability compromises
|
||||||
sane.impermanence.home-dirs = [
|
sane.web-browser.persistCache = "private";
|
||||||
config.sane.web-browser.dotDir
|
sane.web-browser.persistData = "private";
|
||||||
|
sane.persist.home.plaintext = [
|
||||||
".config/pulse" # persist pulseaudio volume
|
".config/pulse" # persist pulseaudio volume
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
sane.nixcache.enable = true;
|
sane.nixcache.enable = true;
|
||||||
sane.impermanence.enable = true;
|
sane.persist.enable = true;
|
||||||
sane.gui.phosh.enable = true;
|
sane.gui.phosh.enable = true;
|
||||||
|
|
||||||
boot.loader.efi.canTouchEfiVariables = false;
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.root-on-tmpfs = true;
|
sane.persist.root-on-tmpfs = true;
|
||||||
fileSystems."/nix" = {
|
fileSystems."/nix" = {
|
||||||
device = "/dev/disk/by-uuid/1f1271f8-53ce-4081-8a29-60a4a6b5d6f9";
|
device = "/dev/disk/by-uuid/1f1271f8-53ce-4081-8a29-60a4a6b5d6f9";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
|
@@ -8,9 +8,6 @@
|
|||||||
boot.loader.efi.canTouchEfiVariables = false;
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||||
|
|
||||||
users.users.dhcpcd.uid = config.sane.allocations.dhcpcd-uid;
|
|
||||||
users.groups.dhcpcd.gid = config.sane.allocations.dhcpcd-gid;
|
|
||||||
|
|
||||||
# docs: https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion
|
# docs: https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion
|
||||||
system.stateVersion = "21.05";
|
system.stateVersion = "21.05";
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
pkgs.matrix-synapse
|
pkgs.matrix-synapse
|
||||||
pkgs.freshrss
|
pkgs.freshrss
|
||||||
];
|
];
|
||||||
sane.impermanence.enable = true;
|
sane.persist.enable = true;
|
||||||
sane.services.dyn-dns.enable = true;
|
sane.services.dyn-dns.enable = true;
|
||||||
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade
|
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.root-on-tmpfs = true;
|
sane.persist.root-on-tmpfs = true;
|
||||||
# we need a /tmp for building large nix things
|
# we need a /tmp for building large nix things
|
||||||
fileSystems."/tmp" = {
|
fileSystems."/tmp" = {
|
||||||
device = "none";
|
device = "none";
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# slow, external storage (for archiving, etc)
|
# slow, external storage (for archiving, etc)
|
||||||
fileSystems."/nix/persist/ext" = {
|
fileSystems."/mnt/persist/ext" = {
|
||||||
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
options = [
|
options = [
|
||||||
@@ -36,27 +36,31 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
sane.impermanence.dirs = [
|
sane.persist.stores."ext" = {
|
||||||
|
origin = "/mnt/persist/ext/persist";
|
||||||
|
storeDescription = "external HDD storage";
|
||||||
|
};
|
||||||
|
sane.fs."/mnt/persist/ext".mount = {};
|
||||||
|
|
||||||
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: this is overly broad; only need media and share directories to be persisted
|
# TODO: this is overly broad; only need media and share directories to be persisted
|
||||||
{ user = "colin"; group = "users"; directory = "/var/lib/uninsane"; }
|
{ user = "colin"; group = "users"; directory = "/var/lib/uninsane"; }
|
||||||
];
|
];
|
||||||
# direct these media directories to external storage
|
# make sure large media is stored to the HDD
|
||||||
environment.persistence."/nix/persist/ext/persist" = {
|
sane.persist.sys.ext = [
|
||||||
directories = [
|
{
|
||||||
({
|
user = "colin";
|
||||||
user = "colin";
|
group = "users";
|
||||||
group = "users";
|
mode = "0777";
|
||||||
mode = "0777";
|
directory = "/var/lib/uninsane/media/Videos";
|
||||||
directory = "/var/lib/uninsane/media/Videos";
|
}
|
||||||
})
|
{
|
||||||
({
|
user = "colin";
|
||||||
user = "colin";
|
group = "users";
|
||||||
group = "users";
|
mode = "0777";
|
||||||
mode = "0777";
|
directory = "/var/lib/uninsane/media/freeleech";
|
||||||
directory = "/var/lib/uninsane/media/freeleech";
|
}
|
||||||
})
|
];
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# in-memory compressed RAM (seems to be dynamically sized)
|
# in-memory compressed RAM (seems to be dynamically sized)
|
||||||
# zramSwap = {
|
# zramSwap = {
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
# XXX: avatar support works in MUCs but not DMs
|
# XXX: avatar support works in MUCs but not DMs
|
||||||
# lib.mkIf false
|
# lib.mkIf false
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "ejabberd"; group = "ejabberd"; directory = "/var/lib/ejabberd"; }
|
{ user = "ejabberd"; group = "ejabberd"; directory = "/var/lib/ejabberd"; }
|
||||||
];
|
];
|
||||||
networking.firewall.allowedTCPPorts = [
|
networking.firewall.allowedTCPPorts = [
|
||||||
@@ -75,33 +75,33 @@
|
|||||||
|
|
||||||
sane.services.trust-dns.zones."uninsane.org".inet = {
|
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||||
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
|
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
|
||||||
A."xmpp" = [ "%NATIVE%" ];
|
A."xmpp" = "%NATIVE%";
|
||||||
CNAME."muc.xmpp" = [ "xmpp" ];
|
CNAME."muc.xmpp" = "xmpp";
|
||||||
CNAME."pubsub.xmpp" = [ "xmpp" ];
|
CNAME."pubsub.xmpp" = "xmpp";
|
||||||
CNAME."upload.xmpp" = [ "xmpp" ];
|
CNAME."upload.xmpp" = "xmpp";
|
||||||
CNAME."vjid.xmpp" = [ "xmpp" ];
|
CNAME."vjid.xmpp" = "xmpp";
|
||||||
|
|
||||||
# _Service._Proto.Name TTL Class SRV Priority Weight Port Target
|
# _Service._Proto.Name TTL Class SRV Priority Weight Port Target
|
||||||
# - <https://xmpp.org/extensions/xep-0368.html>
|
# - <https://xmpp.org/extensions/xep-0368.html>
|
||||||
# something's requesting the SRV records for muc.xmpp, so let's include it
|
# 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 @)
|
# nothing seems to request XMPP SRVs for the other records (except @)
|
||||||
# lower numerical priority field tells clients to prefer this method
|
# lower numerical priority field tells clients to prefer this method
|
||||||
SRV."_xmpps-client._tcp.muc.xmpp" = [ "3 50 5223 xmpp" ];
|
SRV."_xmpps-client._tcp.muc.xmpp" = "3 50 5223 xmpp";
|
||||||
SRV."_xmpps-server._tcp.muc.xmpp" = [ "3 50 5270 xmpp" ];
|
SRV."_xmpps-server._tcp.muc.xmpp" = "3 50 5270 xmpp";
|
||||||
SRV."_xmpp-client._tcp.muc.xmpp" = [ "5 50 5222 xmpp" ];
|
SRV."_xmpp-client._tcp.muc.xmpp" = "5 50 5222 xmpp";
|
||||||
SRV."_xmpp-server._tcp.muc.xmpp" = [ "5 50 5269 xmpp" ];
|
SRV."_xmpp-server._tcp.muc.xmpp" = "5 50 5269 xmpp";
|
||||||
|
|
||||||
SRV."_xmpps-client._tcp" = [ "3 50 5223 xmpp" ];
|
SRV."_xmpps-client._tcp" = "3 50 5223 xmpp";
|
||||||
SRV."_xmpps-server._tcp" = [ "3 50 5270 xmpp" ];
|
SRV."_xmpps-server._tcp" = "3 50 5270 xmpp";
|
||||||
SRV."_xmpp-client._tcp" = [ "5 50 5222 xmpp" ];
|
SRV."_xmpp-client._tcp" = "5 50 5222 xmpp";
|
||||||
SRV."_xmpp-server._tcp" = [ "5 50 5269 xmpp" ];
|
SRV."_xmpp-server._tcp" = "5 50 5269 xmpp";
|
||||||
|
|
||||||
SRV."_stun._udp" = [ "5 50 3478 xmpp" ];
|
SRV."_stun._udp" = "5 50 3478 xmpp";
|
||||||
SRV."_stun._tcp" = [ "5 50 3478 xmpp" ];
|
SRV."_stun._tcp" = "5 50 3478 xmpp";
|
||||||
SRV."_stuns._tcp" = [ "5 50 5349 xmpp" ];
|
SRV."_stuns._tcp" = "5 50 5349 xmpp";
|
||||||
SRV."_turn._udp" = [ "5 50 3478 xmpp" ];
|
SRV."_turn._udp" = "5 50 3478 xmpp";
|
||||||
SRV."_turn._tcp" = [ "5 50 3478 xmpp" ];
|
SRV."_turn._tcp" = "5 50 3478 xmpp";
|
||||||
SRV."_turns._tcp" = [ "5 50 5349 xmpp" ];
|
SRV."_turns._tcp" = "5 50 5349 xmpp";
|
||||||
};
|
};
|
||||||
|
|
||||||
# TODO: allocate UIDs/GIDs ?
|
# TODO: allocate UIDs/GIDs ?
|
||||||
|
@@ -9,19 +9,17 @@
|
|||||||
# $ sudo -u freshrss -g freshrss FRESHRSS_DATA_PATH=/var/lib/freshrss ./result/cli/export-opml-for-user.php --user admin
|
# $ sudo -u freshrss -g freshrss FRESHRSS_DATA_PATH=/var/lib/freshrss ./result/cli/export-opml-for-user.php --user admin
|
||||||
# ```
|
# ```
|
||||||
|
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, sane-lib, ... }:
|
||||||
{
|
{
|
||||||
sops.secrets.freshrss_passwd = {
|
sops.secrets.freshrss_passwd = {
|
||||||
sopsFile = ../../../secrets/servo.yaml;
|
sopsFile = ../../../secrets/servo.yaml;
|
||||||
owner = config.users.users.freshrss.name;
|
owner = config.users.users.freshrss.name;
|
||||||
mode = "400";
|
mode = "0400";
|
||||||
};
|
};
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "freshrss"; group = "freshrss"; directory = "/var/lib/freshrss"; }
|
{ user = "freshrss"; group = "freshrss"; directory = "/var/lib/freshrss"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
users.users.freshrss.uid = config.sane.allocations.freshrss-uid;
|
|
||||||
users.groups.freshrss.gid = config.sane.allocations.freshrss-gid;
|
|
||||||
services.freshrss.enable = true;
|
services.freshrss.enable = true;
|
||||||
services.freshrss.baseUrl = "https://rss.uninsane.org";
|
services.freshrss.baseUrl = "https://rss.uninsane.org";
|
||||||
services.freshrss.virtualHost = "rss.uninsane.org";
|
services.freshrss.virtualHost = "rss.uninsane.org";
|
||||||
@@ -29,9 +27,11 @@
|
|||||||
|
|
||||||
systemd.services.freshrss-import-feeds =
|
systemd.services.freshrss-import-feeds =
|
||||||
let
|
let
|
||||||
|
feeds = sane-lib.feeds;
|
||||||
fresh = config.systemd.services.freshrss-config;
|
fresh = config.systemd.services.freshrss-config;
|
||||||
feeds = import ../../../modules/home-manager/feeds.nix { inherit lib; };
|
all-feeds = config.sane.feeds;
|
||||||
opml = pkgs.writeText "sane-freshrss.opml" (feeds.feedsToOpml feeds.all);
|
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
|
||||||
|
opml = pkgs.writeText "sane-freshrss.opml" (feeds.feedsToOpml wanted-feeds);
|
||||||
in {
|
in {
|
||||||
inherit (fresh) wantedBy environment;
|
inherit (fresh) wantedBy environment;
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
@@ -57,5 +57,5 @@
|
|||||||
# the routing is handled by services.freshrss.virtualHost
|
# 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, ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? could be more granular
|
# TODO: mode? could be more granular
|
||||||
{ user = "git"; group = "gitea"; directory = "/var/lib/gitea"; }
|
{ user = "git"; group = "gitea"; directory = "/var/lib/gitea"; }
|
||||||
];
|
];
|
||||||
users.groups.gitea.gid = config.sane.allocations.gitea-gid;
|
|
||||||
services.gitea.enable = true;
|
services.gitea.enable = true;
|
||||||
services.gitea.user = "git"; # default is 'gitea'
|
services.gitea.user = "git"; # default is 'gitea'
|
||||||
services.gitea.database.type = "postgres";
|
services.gitea.database.type = "postgres";
|
||||||
@@ -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
|
lib.mkIf false # i don't actively use ipfs anymore
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? could be more granular
|
# TODO: mode? could be more granular
|
||||||
{ user = "261"; group = "261"; directory = "/var/lib/ipfs"; }
|
{ user = "261"; group = "261"; directory = "/var/lib/ipfs"; }
|
||||||
];
|
];
|
||||||
@@ -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.ipfs.enable = true;
|
||||||
services.kubo.localDiscovery = true;
|
services.kubo.localDiscovery = true;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? we only need this to save Indexer creds ==> migrate to config?
|
# TODO: mode? we only need this to save Indexer creds ==> migrate to config?
|
||||||
{ user = "root"; group = "root"; directory = "/var/lib/jackett"; }
|
{ user = "root"; group = "root"; directory = "/var/lib/jackett"; }
|
||||||
];
|
];
|
||||||
@@ -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 = [
|
networking.firewall.allowedUDPPorts = [
|
||||||
1900 7359 # DLNA: https://jellyfin.org/docs/general/networking/index.html
|
1900 7359 # DLNA: https://jellyfin.org/docs/general/networking/index.html
|
||||||
];
|
];
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? could be more granular
|
# TODO: mode? could be more granular
|
||||||
{ user = "jellyfin"; group = "jellyfin"; directory = "/var/lib/jellyfin"; }
|
{ user = "jellyfin"; group = "jellyfin"; directory = "/var/lib/jellyfin"; }
|
||||||
];
|
];
|
||||||
@@ -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;
|
services.jellyfin.enable = true;
|
||||||
}
|
}
|
||||||
|
@@ -13,5 +13,5 @@
|
|||||||
locations."/".proxyPass = "http://127.0.0.1:8013";
|
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
|
# ./irc.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/matrix-synapse"; }
|
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/matrix-synapse"; }
|
||||||
];
|
];
|
||||||
services.matrix-synapse.enable = true;
|
services.matrix-synapse.enable = true;
|
||||||
@@ -122,8 +122,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
sane.services.trust-dns.zones."uninsane.org".inet = {
|
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||||
CNAME."matrix" = [ "native" ];
|
CNAME."matrix" = "native";
|
||||||
CNAME."web.matrix" = [ "native" ];
|
CNAME."web.matrix" = "native";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/mx-puppet-discord"; }
|
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/mx-puppet-discord"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ config, lib, ... }:
|
{ config, lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode?
|
# TODO: mode?
|
||||||
# user and group are both "matrix-appservice-irc"
|
# user and group are both "matrix-appservice-irc"
|
||||||
{ user = "993"; group = "992"; directory = "/var/lib/matrix-appservice-irc"; }
|
{ user = "993"; group = "992"; directory = "/var/lib/matrix-appservice-irc"; }
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "navidrome"; group = "navidrome"; directory = "/var/lib/private/navidrome"; }
|
# TODO: we don't have a static user allocated for navidrome!
|
||||||
|
# the chown would happen too early for us to set static perms
|
||||||
|
"/var/lib/private/navidrome"
|
||||||
|
# { user = "navidrome"; group = "navidrome"; directory = "/var/lib/private/navidrome"; }
|
||||||
];
|
];
|
||||||
services.navidrome.enable = true;
|
services.navidrome.enable = true;
|
||||||
services.navidrome.settings = {
|
services.navidrome.settings = {
|
||||||
@@ -22,5 +25,5 @@
|
|||||||
locations."/".proxyPass = "http://127.0.0.1:4533";
|
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
|
# docs: https://nixos.wiki/wiki/Nginx
|
||||||
{ config, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
# make the logs for this host "public" so that they show up in e.g. metrics
|
# make the logs for this host "public" so that they show up in e.g. metrics
|
||||||
publog = vhost: vhost // {
|
publog = vhost: lib.attrsets.unionOfDisjoint vhost {
|
||||||
extraConfig = (vhost.extraConfig or "") + ''
|
extraConfig = (vhost.extraConfig or "") + ''
|
||||||
access_log /var/log/nginx/public.log vcombined;
|
access_log /var/log/nginx/public.log vcombined;
|
||||||
'';
|
'';
|
||||||
@@ -120,9 +120,7 @@ in
|
|||||||
security.acme.acceptTerms = true;
|
security.acme.acceptTerms = true;
|
||||||
security.acme.defaults.email = "admin.acme@uninsane.org";
|
security.acme.defaults.email = "admin.acme@uninsane.org";
|
||||||
|
|
||||||
users.users.acme.uid = config.sane.allocations.acme-uid;
|
sane.persist.sys.plaintext = [
|
||||||
users.groups.acme.gid = config.sane.allocations.acme-gid;
|
|
||||||
sane.impermanence.dirs = [
|
|
||||||
# TODO: mode?
|
# TODO: mode?
|
||||||
{ user = "acme"; group = "acme"; directory = "/var/lib/acme"; }
|
{ user = "acme"; group = "acme"; directory = "/var/lib/acme"; }
|
||||||
{ user = "colin"; group = "users"; directory = "/var/www/sites"; }
|
{ user = "colin"; group = "users"; directory = "/var/www/sites"; }
|
||||||
|
@@ -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.enable = true;
|
||||||
sane.services.nixserve.sopsFile = ../../../secrets/servo.yaml;
|
sane.services.nixserve.sopsFile = ../../../secrets/servo.yaml;
|
||||||
|
@@ -6,12 +6,10 @@
|
|||||||
{ config, pkgs, ... }:
|
{ config, pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? could be more granular
|
# TODO: mode? could be more granular
|
||||||
{ user = "pleroma"; group = "pleroma"; directory = "/var/lib/pleroma"; }
|
{ user = "pleroma"; group = "pleroma"; directory = "/var/lib/pleroma"; }
|
||||||
];
|
];
|
||||||
users.users.pleroma.uid = config.sane.allocations.pleroma-uid;
|
|
||||||
users.groups.pleroma.gid = config.sane.allocations.pleroma-gid;
|
|
||||||
services.pleroma.enable = true;
|
services.pleroma.enable = true;
|
||||||
services.pleroma.secretConfigFile = config.sops.secrets.pleroma_secrets.path;
|
services.pleroma.secretConfigFile = config.sops.secrets.pleroma_secrets.path;
|
||||||
services.pleroma.configs = [
|
services.pleroma.configs = [
|
||||||
@@ -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 = {
|
sops.secrets.pleroma_secrets = {
|
||||||
sopsFile = ../../../secrets/servo.yaml;
|
sopsFile = ../../../secrets/servo.yaml;
|
||||||
|
@@ -16,7 +16,7 @@ let
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? could be more granular
|
# TODO: mode? could be more granular
|
||||||
{ user = "opendkim"; group = "opendkim"; directory = "/var/lib/opendkim"; }
|
{ user = "opendkim"; group = "opendkim"; directory = "/var/lib/opendkim"; }
|
||||||
{ user = "root"; group = "root"; directory = "/var/lib/postfix"; }
|
{ user = "root"; group = "root"; directory = "/var/lib/postfix"; }
|
||||||
@@ -45,22 +45,22 @@ in
|
|||||||
|
|
||||||
|
|
||||||
sane.services.trust-dns.zones."uninsane.org".inet = {
|
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
|
# XXX: RFC's specify that the MX record CANNOT BE A CNAME
|
||||||
A."mx" = [ "185.157.162.178" ];
|
A."mx" = "185.157.162.178";
|
||||||
CNAME."imap" = [ "native" ];
|
CNAME."imap" = "native";
|
||||||
|
|
||||||
# Sender Policy Framework:
|
# Sender Policy Framework:
|
||||||
# +mx => mail passes if it originated from the MX
|
# +mx => mail passes if it originated from the MX
|
||||||
# +a => mail passes if it originated from the A address of this domain
|
# +a => mail passes if it originated from the A address of this domain
|
||||||
# +ip4:.. => mail passes if it originated from this IP
|
# +ip4:.. => mail passes if it originated from this IP
|
||||||
# -all => mail fails if none of these conditions were met
|
# -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:
|
# DKIM public key:
|
||||||
TXT."mx._domainkey" = [
|
TXT."mx._domainkey" =
|
||||||
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkSyMufc2KrRx3j17e/LyB+3eYSBRuEFT8PUka8EDX04QzCwDPdkwgnj3GNDvnB5Ktb05Cf2SJ/S1OLqNsINxJRWtkVfZd/C339KNh9wrukMKRKNELL9HLUw0bczOI4gKKFqyrRE9qm+4csCMAR79Te9FCjGV/jVnrkLdPT0GtFwIDAQAB"
|
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkSyMufc2KrRx3j17e/LyB+3eYSBRuEFT8PUka8EDX04QzCwDPdkwgnj3GNDvnB5Ktb05Cf2SJ/S1OLqNsINxJRWtkVfZd/C339KNh9wrukMKRKNELL9HLUw0bczOI4gKKFqyrRE9qm+4csCMAR79Te9FCjGV/jVnrkLdPT0GtFwIDAQAB"
|
||||||
];
|
;
|
||||||
|
|
||||||
# DMARC fields <https://datatracker.ietf.org/doc/html/rfc7489>:
|
# DMARC fields <https://datatracker.ietf.org/doc/html/rfc7489>:
|
||||||
# p=none|quarantine|reject: what to do with failures
|
# p=none|quarantine|reject: what to do with failures
|
||||||
@@ -75,9 +75,9 @@ in
|
|||||||
# pct = sampling ratio for punishing failures (default 100 for 100%)
|
# pct = sampling ratio for punishing failures (default 100 for 100%)
|
||||||
# rf = report format
|
# rf = report format
|
||||||
# ri = report interval
|
# 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"
|
"v=DMARC1;p=quarantine;sp=reject;rua=mailto:admin+mail@uninsane.org;ruf=mailto:admin+mail@uninsane.org;fo=1:d:s"
|
||||||
];
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
services.postfix.enable = true;
|
services.postfix.enable = true;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode?
|
# TODO: mode?
|
||||||
{ user = "postgres"; group = "postgres"; directory = "/var/lib/postgresql"; }
|
{ user = "postgres"; group = "postgres"; directory = "/var/lib/postgresql"; }
|
||||||
];
|
];
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
# nixnet runs ejabberd, so revisiting that.
|
# nixnet runs ejabberd, so revisiting that.
|
||||||
lib.mkIf false
|
lib.mkIf false
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
{ user = "prosody"; group = "prosody"; directory = "/var/lib/prosody"; }
|
{ user = "prosody"; group = "prosody"; directory = "/var/lib/prosody"; }
|
||||||
];
|
];
|
||||||
networking.firewall.allowedTCPPorts = [
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.impermanence.dirs = [
|
sane.persist.sys.plaintext = [
|
||||||
# TODO: mode? we need this specifically for the stats tracking in .config/
|
# TODO: mode? we need this specifically for the stats tracking in .config/
|
||||||
{ user = "transmission"; group = "transmission"; directory = "/var/lib/transmission"; }
|
{ user = "transmission"; group = "transmission"; directory = "/var/lib/transmission"; }
|
||||||
];
|
];
|
||||||
@@ -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,25 +21,25 @@
|
|||||||
# Retry = how long secondary NS should wait until re-querying master after a failure (must be < Refresh)
|
# 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)
|
# Expire = how long secondary NS should continue to reply to queries after master fails (> Refresh + Retry)
|
||||||
sane.services.trust-dns.zones."uninsane.org".inet = {
|
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||||
SOA."@" = [''
|
SOA."@" = ''
|
||||||
ns1.uninsane.org. admin-dns.uninsane.org. (
|
ns1.uninsane.org. admin-dns.uninsane.org. (
|
||||||
2022122101 ; Serial
|
2022122101 ; Serial
|
||||||
4h ; Refresh
|
4h ; Refresh
|
||||||
30m ; Retry
|
30m ; Retry
|
||||||
7d ; Expire
|
7d ; Expire
|
||||||
5m) ; Negative response TTL
|
5m) ; Negative response TTL
|
||||||
''];
|
'';
|
||||||
TXT."rev" = [ "2022122101" ];
|
TXT."rev" = "2022122101";
|
||||||
|
|
||||||
# XXX NS records must also not be CNAME
|
# 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.
|
# 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.
|
# so, org. can specify ns2/ns3 as being to the VPN, with no mention of ns1. we provide ns1 here.
|
||||||
A."ns1" = [ "%NATIVE%" ];
|
A."ns1" = "%NATIVE%";
|
||||||
A."ns2" = [ "185.157.162.178" ];
|
A."ns2" = "185.157.162.178";
|
||||||
A."ns3" = [ "185.157.162.178" ];
|
A."ns3" = "185.157.162.178";
|
||||||
A."ovpns" = [ "185.157.162.178" ];
|
A."ovpns" = "185.157.162.178";
|
||||||
A."native" = [ "%NATIVE%" ];
|
A."native" = "%NATIVE%";
|
||||||
A."@" = [ "%NATIVE%" ];
|
A."@" = "%NATIVE%";
|
||||||
NS."@" = [
|
NS."@" = [
|
||||||
"ns1.uninsane.org."
|
"ns1.uninsane.org."
|
||||||
"ns2.uninsane.org."
|
"ns2.uninsane.org."
|
||||||
|
@@ -11,8 +11,6 @@ lib.mkIf false
|
|||||||
sopsFile = ../../../secrets/servo.yaml;
|
sopsFile = ../../../secrets/servo.yaml;
|
||||||
};
|
};
|
||||||
|
|
||||||
users.users.mediawiki.uid = config.sane.allocations.mediawiki-uid;
|
|
||||||
|
|
||||||
services.mediawiki.enable = true;
|
services.mediawiki.enable = true;
|
||||||
services.mediawiki.name = "Uninsane Wiki";
|
services.mediawiki.name = "Uninsane Wiki";
|
||||||
services.mediawiki.passwordFile = config.sops.secrets.mediawiki_pw.path;
|
services.mediawiki.passwordFile = config.sops.secrets.mediawiki_pw.path;
|
||||||
|
@@ -12,7 +12,6 @@
|
|||||||
home = "/var/lib/gitea";
|
home = "/var/lib/gitea";
|
||||||
useDefaultShell = true;
|
useDefaultShell = true;
|
||||||
group = "gitea";
|
group = "gitea";
|
||||||
uid = config.sane.allocations.git-uid;
|
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
# sendmail access (not 100% sure if this is necessary)
|
# sendmail access (not 100% sure if this is necessary)
|
||||||
extraGroups = [ "postdrop" ];
|
extraGroups = [ "postdrop" ];
|
||||||
|
@@ -1,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,16 +1,23 @@
|
|||||||
{ ... }:
|
{ lib, utils, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./allocations.nix
|
./feeds.nix
|
||||||
./fs.nix
|
./fs
|
||||||
./gui
|
./gui
|
||||||
./home-manager
|
./home-manager
|
||||||
|
./ids.nix
|
||||||
./packages.nix
|
./packages.nix
|
||||||
./image.nix
|
./image.nix
|
||||||
./impermanence
|
|
||||||
./nixcache.nix
|
./nixcache.nix
|
||||||
|
./persist
|
||||||
./services
|
./services
|
||||||
./sops.nix
|
./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.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
155
modules/fs.nix
155
modules/fs.nix
@@ -1,155 +0,0 @@
|
|||||||
{ config, lib, pkgs, utils, ... }:
|
|
||||||
with lib;
|
|
||||||
let
|
|
||||||
cfg = config.sane.fs;
|
|
||||||
|
|
||||||
# sane.fs."<path>" top-level options
|
|
||||||
fsEntry = types.submodule ({ name, ...}: let
|
|
||||||
parent = parentDir name;
|
|
||||||
has-parent = hasParent name;
|
|
||||||
parent-cfg = if has-parent then cfg."${parent}" else {};
|
|
||||||
in {
|
|
||||||
options = {
|
|
||||||
dir = mkOption {
|
|
||||||
type = mkDirEntryType (parent-cfg.dir or {
|
|
||||||
user = "root";
|
|
||||||
group = "root";
|
|
||||||
mode = "0755";
|
|
||||||
});
|
|
||||||
};
|
|
||||||
depends = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
description = "list of systemd services needed to be run before this service";
|
|
||||||
default = [];
|
|
||||||
};
|
|
||||||
service = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = "name of the systemd service which ensures this entry";
|
|
||||||
default = "ensure-${utils.escapeSystemdPath name}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = {
|
|
||||||
# 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`).
|
|
||||||
depends = if has-parent then [ "${parent-cfg.service}.service" ] else [];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
# sane.fs."<path>".dir sub-options
|
|
||||||
mkDirEntryType = defaults: types.submodule {
|
|
||||||
options = {
|
|
||||||
user = mkOption {
|
|
||||||
type = types.str; # TODO: use uid?
|
|
||||||
};
|
|
||||||
group = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
};
|
|
||||||
mode = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkDefault defaults;
|
|
||||||
};
|
|
||||||
|
|
||||||
# given a fsEntry definition, output the `config` attrs it generates.
|
|
||||||
mkFsConfig = path: opt: {
|
|
||||||
systemd.services."${opt.service}" = {
|
|
||||||
description = "prepare ${path}";
|
|
||||||
script = ensure-dir-script;
|
|
||||||
scriptArgs = "${path} ${opt.dir.user} ${opt.dir.group} ${opt.dir.mode}";
|
|
||||||
serviceConfig.Type = "oneshot";
|
|
||||||
after = opt.depends;
|
|
||||||
wants = 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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# systemd/shell script used to create and set perms for a specific dir
|
|
||||||
ensure-dir-script = ''
|
|
||||||
path="$1"
|
|
||||||
user="$2"
|
|
||||||
group="$3"
|
|
||||||
mode="$4"
|
|
||||||
|
|
||||||
if ! test -d "$path"
|
|
||||||
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 "$path" || test -d "$path"
|
|
||||||
fi
|
|
||||||
chmod "$mode" "$path"
|
|
||||||
chown "$user:$group" "$path"
|
|
||||||
'';
|
|
||||||
|
|
||||||
# split the string path into a list of string components.
|
|
||||||
# root directory "/" becomes the empty list [].
|
|
||||||
# implicitly performs normalization so that:
|
|
||||||
# splitPath "a//b/" => ["a" "b"]
|
|
||||||
# splitPath "/a/b" => ["a" "b"]
|
|
||||||
splitPath = str: builtins.filter (seg: (builtins.isString seg) && seg != "" ) (builtins.split "/" str);
|
|
||||||
# return a string path, with leading slash but no trailing slash
|
|
||||||
joinPathAbs = comps: "/" + (builtins.concatStringsSep "/" comps);
|
|
||||||
concatPaths = paths: joinPathAbs (builtins.concatLists (builtins.map (p: splitPath p) paths));
|
|
||||||
# normalize the given path
|
|
||||||
normPath = str: joinPathAbs (splitPath str);
|
|
||||||
# return the parent directory. doesn't care about leading/trailing slashes.
|
|
||||||
# the parent of "/" is "/".
|
|
||||||
parentDir = str: normPath (builtins.dirOf (normPath str));
|
|
||||||
hasParent = str: (parentDir str) != (normPath str);
|
|
||||||
|
|
||||||
# return all ancestors of this path.
|
|
||||||
# e.g. ancestorsOf "/foo/bar/baz" => [ "/" "/foo" "/foo/bar" ]
|
|
||||||
ancestorsOf = path: if hasParent path then
|
|
||||||
ancestorsOf (parentDir path) ++ [ (parentDir path) ]
|
|
||||||
else
|
|
||||||
[ ]
|
|
||||||
;
|
|
||||||
|
|
||||||
# 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 == normPath p) (builtins.attrNames tree);
|
|
||||||
};
|
|
||||||
|
|
||||||
in {
|
|
||||||
options = {
|
|
||||||
sane.fs = mkOption {
|
|
||||||
# type = types.attrsOf fsEntry;
|
|
||||||
type = fsTree;
|
|
||||||
default = {};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = let
|
|
||||||
cfgs = builtins.attrValues (builtins.mapAttrs mkFsConfig cfg);
|
|
||||||
in {
|
|
||||||
# we can't lib.mkMerge at the top-level, so do it per-attribute
|
|
||||||
systemd = lib.mkMerge (catAttrs "systemd" cfgs);
|
|
||||||
};
|
|
||||||
}
|
|
357
modules/fs/default.nix
Normal file
357
modules/fs/default.nix
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
{ config, lib, pkgs, utils, sane-lib, ... }:
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
path-lib = sane-lib.path;
|
||||||
|
sane-types = sane-lib.types;
|
||||||
|
cfg = config.sane.fs;
|
||||||
|
|
||||||
|
mountNameFor = path: "${utils.escapeSystemdPath path}.mount";
|
||||||
|
serviceNameFor = path: "ensure-${utils.escapeSystemdPath path}";
|
||||||
|
|
||||||
|
# sane.fs."<path>" top-level options
|
||||||
|
fsEntry = types.submodule ({ name, config, ...}: let
|
||||||
|
parent = path-lib.parent name;
|
||||||
|
has-parent = path-lib.hasParent name;
|
||||||
|
parent-cfg = if has-parent then cfg."${parent}" else {};
|
||||||
|
parent-acl = if has-parent then parent-cfg.generated.acl else {};
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
dir = mkOption {
|
||||||
|
type = types.nullOr dirEntry;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
symlink = mkOption {
|
||||||
|
type = types.nullOr (symlinkEntryFor name);
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
generated = mkOption {
|
||||||
|
type = generatedEntry;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
mount = mkOption {
|
||||||
|
type = types.nullOr (mountEntryFor name);
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
wantedBy = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
list of units or targets which, when activated, should trigger this fs entry to be created.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
wantedBeforeBy = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
list of units or targets which, when activated, should first start and wait for this fs entry to be created.
|
||||||
|
if this unit fails, it will not block the targets in this list.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
unit = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "name of the systemd unit which ensures this entry";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = let
|
||||||
|
default-acl = {
|
||||||
|
user = lib.mkDefault (parent-acl.user or "root");
|
||||||
|
group = lib.mkDefault (parent-acl.group or "root");
|
||||||
|
mode = lib.mkDefault (parent-acl.mode or "0755");
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
# we put this here instead of as a `default` to ensure that users who specify additional
|
||||||
|
# dependencies still get a dep on the parent (unless they assign with `mkForce`).
|
||||||
|
generated.depends = if has-parent then [ parent-cfg.unit ] else [];
|
||||||
|
|
||||||
|
# populate generated items from `dir` or `symlink` shorthands
|
||||||
|
generated.acl = lib.mkMerge [
|
||||||
|
default-acl
|
||||||
|
(lib.mkIf (config.dir != null)
|
||||||
|
(sane-lib.filterNonNull config.dir.acl))
|
||||||
|
(lib.mkIf (config.symlink != null)
|
||||||
|
(sane-lib.filterNonNull config.symlink.acl))
|
||||||
|
];
|
||||||
|
|
||||||
|
# actually generate the item
|
||||||
|
generated.script = lib.mkMerge [
|
||||||
|
(lib.mkIf (config.dir != null) (ensureDirScript name config.dir))
|
||||||
|
(lib.mkIf (config.symlink != null) (ensureSymlinkScript name config.symlink))
|
||||||
|
];
|
||||||
|
|
||||||
|
# make the unit file which generates the underlying thing available so that `mount` can use it.
|
||||||
|
generated.unit = (serviceNameFor name) + ".service";
|
||||||
|
|
||||||
|
# if we were asked to mount, make sure we create the dir that we mount over
|
||||||
|
dir = lib.mkIf (config.mount != null) {};
|
||||||
|
|
||||||
|
# if defaulted, this module is responsible for finalizing the entry.
|
||||||
|
# the user could override this if, say, they finalize some aspect of the entry
|
||||||
|
# with a custom service.
|
||||||
|
unit = lib.mkDefault (
|
||||||
|
if config.mount != null then
|
||||||
|
config.mount.unit
|
||||||
|
else
|
||||||
|
config.generated.unit
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
# options which can be set in dir/symlink generated items,
|
||||||
|
# with intention that they just propagate down
|
||||||
|
propagatedGenerateMod = {
|
||||||
|
options = {
|
||||||
|
acl = mkOption {
|
||||||
|
type = sane-types.aclOverride;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# sane.fs."<path>".dir sub-options
|
||||||
|
# takes no special options
|
||||||
|
dirEntry = types.submodule propagatedGenerateMod;
|
||||||
|
|
||||||
|
symlinkEntryFor = path: types.submodule ({ config, ...}: {
|
||||||
|
options = {
|
||||||
|
inherit (propagatedGenerateMod.options) acl;
|
||||||
|
target = mkOption {
|
||||||
|
type = types.coercedTo types.package toString types.str;
|
||||||
|
description = "fs path to link to";
|
||||||
|
};
|
||||||
|
text = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "create a file in the /nix/store with the provided text and use that as the target";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
target = lib.mkIf (config.text != null) (
|
||||||
|
pkgs.writeText (path-lib.leaf path) config.text
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
generatedEntry = types.submodule {
|
||||||
|
options = {
|
||||||
|
acl = mkOption {
|
||||||
|
type = sane-types.acl;
|
||||||
|
};
|
||||||
|
depends = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
description = ''
|
||||||
|
list of systemd units needed to be run before this item can be generated.
|
||||||
|
'';
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
script.script = mkOption {
|
||||||
|
type = types.lines;
|
||||||
|
};
|
||||||
|
script.scriptArgs = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
unit = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "name of the systemd unit which ensures this directory";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# sane.fs."<path>".mount sub-options
|
||||||
|
mountEntryFor = path: types.submodule {
|
||||||
|
options = {
|
||||||
|
bind = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
description = "fs path to bind-mount from";
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
depends = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
description = ''
|
||||||
|
list of systemd units needed to be run before this entry can be mounted
|
||||||
|
'';
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
unit = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "name of the systemd unit which mounts this path";
|
||||||
|
default = mountNameFor path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mkGeneratedConfig = path: opt: let
|
||||||
|
gen-opt = opt.generated;
|
||||||
|
wrapper = generateWrapperScript path gen-opt;
|
||||||
|
in {
|
||||||
|
systemd.services."${serviceNameFor path}" = {
|
||||||
|
description = "prepare ${path}";
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
|
||||||
|
script = wrapper.script;
|
||||||
|
scriptArgs = builtins.concatStringsSep " " wrapper.scriptArgs;
|
||||||
|
|
||||||
|
after = gen-opt.depends;
|
||||||
|
wants = gen-opt.depends;
|
||||||
|
# prevent systemd making this unit implicitly dependent on sysinit.target.
|
||||||
|
# see: <https://www.freedesktop.org/software/systemd/man/systemd.special.html>
|
||||||
|
unitConfig.DefaultDependencies = "no";
|
||||||
|
|
||||||
|
before = opt.wantedBeforeBy;
|
||||||
|
wantedBy = opt.wantedBy ++ opt.wantedBeforeBy;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# given a mountEntry definition, evaluate its toplevel `config` output.
|
||||||
|
mkMountConfig = path: opt: (let
|
||||||
|
device = config.fileSystems."${path}".device;
|
||||||
|
underlying = cfg."${device}";
|
||||||
|
isBind = opt.mount.bind != null;
|
||||||
|
ifBind = lib.mkIf isBind;
|
||||||
|
# before mounting:
|
||||||
|
# - create the target directory
|
||||||
|
# - prepare the source directory -- assuming it's not an external device
|
||||||
|
# - satisfy any user-specified prerequisites ("depends")
|
||||||
|
requires = [ opt.generated.unit ]
|
||||||
|
++ (if lib.hasPrefix "/dev/disk/" device then [] else [ underlying.unit ])
|
||||||
|
++ opt.mount.depends;
|
||||||
|
in {
|
||||||
|
fileSystems."${path}" = {
|
||||||
|
device = ifBind opt.mount.bind;
|
||||||
|
options = (if isBind then ["bind"] else [])
|
||||||
|
++ [
|
||||||
|
# disable defaults: don't require this to be mount as part of local-fs.target
|
||||||
|
# we'll handle that stuff precisely.
|
||||||
|
"noauto"
|
||||||
|
"nofail"
|
||||||
|
# x-systemd options documented here:
|
||||||
|
# - <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
|
||||||
|
]
|
||||||
|
++ (builtins.map (unit: "x-systemd.requires=${unit}") requires)
|
||||||
|
++ (builtins.map (unit: "x-systemd.before=${unit}") opt.wantedBeforeBy)
|
||||||
|
++ (builtins.map (unit: "x-systemd.wanted-by=${unit}") (opt.wantedBy ++ opt.wantedBeforeBy));
|
||||||
|
noCheck = ifBind true;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
mkFsConfig = path: opt: lib.mkMerge [
|
||||||
|
(mkGeneratedConfig path opt)
|
||||||
|
(lib.mkIf (opt.mount != null) (mkMountConfig path opt))
|
||||||
|
];
|
||||||
|
|
||||||
|
generateWrapperScript = path: gen-opt: {
|
||||||
|
script = ''
|
||||||
|
fspath="$1"
|
||||||
|
acluser="$2"
|
||||||
|
aclgroup="$3"
|
||||||
|
aclmode="$4"
|
||||||
|
shift 4
|
||||||
|
|
||||||
|
# ensure any things created by the user script have the desired mode.
|
||||||
|
# chmod doesn't work on symlinks, so we *have* to use this umask approach.
|
||||||
|
decmask=$(( 0777 - "$aclmode" ))
|
||||||
|
octmask=$(printf "%o" "$decmask")
|
||||||
|
umask "$octmask"
|
||||||
|
|
||||||
|
# try to chmod/chown the result even if the user script errors
|
||||||
|
_status=0
|
||||||
|
trap "_status=\$?" ERR
|
||||||
|
|
||||||
|
${gen-opt.script.script}
|
||||||
|
|
||||||
|
# claim ownership of the new thing (DON'T traverse symlinks)
|
||||||
|
chown --no-dereference "$acluser:$aclgroup" "$fspath"
|
||||||
|
# AS LONG AS IT'S NOT A SYMLINK, try to fix perms in case the entity existed before this script was called
|
||||||
|
if ! test -L "$fspath"
|
||||||
|
then
|
||||||
|
chmod "$aclmode" "$fspath"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit "$_status"
|
||||||
|
'';
|
||||||
|
scriptArgs = [ path gen-opt.acl.user gen-opt.acl.group gen-opt.acl.mode ] ++ gen-opt.script.scriptArgs;
|
||||||
|
};
|
||||||
|
|
||||||
|
# systemd/shell script used to create and set perms for a specific dir
|
||||||
|
ensureDirScript = path: dir-cfg: {
|
||||||
|
script = ''
|
||||||
|
dirpath="$1"
|
||||||
|
|
||||||
|
if ! test -d "$dirpath"
|
||||||
|
then
|
||||||
|
# if the directory *doesn't* exist, try creating it
|
||||||
|
# if we fail to create it, ensure we raced with something else and that it's actually a directory
|
||||||
|
mkdir "$dirpath" || test -d "$dirpath"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
scriptArgs = [ path ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# systemd/shell script used to create a symlink
|
||||||
|
ensureSymlinkScript = path: link-cfg: {
|
||||||
|
script = ''
|
||||||
|
lnfrom="$1"
|
||||||
|
lnto="$2"
|
||||||
|
|
||||||
|
# ln is clever when there's something else at the place we want to create the link
|
||||||
|
# only create the link if nothing's there or what is there is another link,
|
||||||
|
# otherwise you'll get links at unexpected fs locations
|
||||||
|
! test -e "$lnfrom" || test -L "$lnfrom" && ln -sf --no-dereference "$lnto" "$lnfrom"
|
||||||
|
'';
|
||||||
|
scriptArgs = [ path link-cfg.target ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# return all ancestors of this path.
|
||||||
|
# e.g. ancestorsOf "/foo/bar/baz" => [ "/" "/foo" "/foo/bar" ]
|
||||||
|
ancestorsOf = path: lib.init (path-lib.walk "/" path);
|
||||||
|
|
||||||
|
# attrsOf fsEntry type which for every entry ensures that all ancestor entries are created.
|
||||||
|
# we do this with a custom type to ensure that users can access `config.sane.fs."/parent/path"`
|
||||||
|
# when inferred.
|
||||||
|
fsTree = let
|
||||||
|
baseType = types.attrsOf fsEntry;
|
||||||
|
# merge is called once, with all collected `sane.fs` definitions passed and we coalesce those
|
||||||
|
# into a single value `x` as if the user had wrote simply `sane.fs = x` in a single location.
|
||||||
|
# so option defaulting and such happens *after* `merge` is called.
|
||||||
|
merge = loc: defs: let
|
||||||
|
# loc is the location of the option holding this type, e.g. ["sane" "fs"].
|
||||||
|
# each def is an { value = attrsOf fsEntry instance; file = "..."; }
|
||||||
|
pathsForDef = def: attrNames def.value;
|
||||||
|
origPaths = concatLists (builtins.map pathsForDef defs);
|
||||||
|
extraPaths = concatLists (builtins.map ancestorsOf origPaths);
|
||||||
|
extraDefs = builtins.map (p: {
|
||||||
|
file = ./.;
|
||||||
|
value = {
|
||||||
|
"${p}".dir = {};
|
||||||
|
};
|
||||||
|
}) extraPaths;
|
||||||
|
in
|
||||||
|
baseType.merge loc (defs ++ extraDefs);
|
||||||
|
in
|
||||||
|
lib.mkOptionType {
|
||||||
|
inherit merge;
|
||||||
|
name = "fsTree";
|
||||||
|
description = "attrset representation of a file-system tree";
|
||||||
|
# ensure that every path is in canonical form, else we might get duplicates and subtle errors
|
||||||
|
check = tree: builtins.all (p: p == path-lib.norm p) (builtins.attrNames tree);
|
||||||
|
};
|
||||||
|
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
sane.fs = mkOption {
|
||||||
|
# type = types.attrsOf fsEntry;
|
||||||
|
type = fsTree;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config =
|
||||||
|
let
|
||||||
|
configs = lib.mapAttrsToList mkFsConfig cfg;
|
||||||
|
take = f: {
|
||||||
|
systemd.services = f.systemd.services;
|
||||||
|
fileSystems = f.fileSystems;
|
||||||
|
};
|
||||||
|
in take (sane-lib.mkTypedMerge take configs);
|
||||||
|
}
|
@@ -23,7 +23,9 @@ in
|
|||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
sane.packages.enableGuiPkgs = lib.mkDefault true;
|
sane.packages.enableGuiPkgs = lib.mkDefault true;
|
||||||
# all GUIs use network manager?
|
|
||||||
users.users.nm-iodine.uid = config.sane.allocations.nm-iodine-uid;
|
# preserve backlight brightness across power cycles
|
||||||
|
# see `man systemd-backlight`
|
||||||
|
sane.persist.sys.plaintext = [ "/var/lib/systemd/backlight" ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -15,15 +15,6 @@ in
|
|||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
sane.gui.enable = true;
|
sane.gui.enable = true;
|
||||||
|
|
||||||
users.users.avahi.uid = config.sane.allocations.avahi-uid;
|
|
||||||
users.groups.avahi.gid = config.sane.allocations.avahi-gid;
|
|
||||||
users.users.colord.uid = config.sane.allocations.colord-uid;
|
|
||||||
users.groups.colord.gid = config.sane.allocations.colord-gid;
|
|
||||||
users.users.geoclue.uid = config.sane.allocations.geoclue-uid;
|
|
||||||
users.groups.geoclue.gid = config.sane.allocations.geoclue-gid;
|
|
||||||
users.users.rtkit.uid = config.sane.allocations.rtkit-uid;
|
|
||||||
users.groups.rtkit.gid = config.sane.allocations.rtkit-gid;
|
|
||||||
|
|
||||||
# start gnome/gdm on boot
|
# start gnome/gdm on boot
|
||||||
services.xserver.enable = true;
|
services.xserver.enable = true;
|
||||||
services.xserver.desktopManager.gnome.enable = true;
|
services.xserver.desktopManager.gnome.enable = true;
|
||||||
|
@@ -24,16 +24,6 @@ in
|
|||||||
{
|
{
|
||||||
sane.gui.enable = true;
|
sane.gui.enable = true;
|
||||||
|
|
||||||
users.users.avahi.uid = config.sane.allocations.avahi-uid;
|
|
||||||
users.users.colord.uid = config.sane.allocations.colord-uid;
|
|
||||||
users.users.geoclue.uid = config.sane.allocations.geoclue-uid;
|
|
||||||
users.users.rtkit.uid = config.sane.allocations.rtkit-uid;
|
|
||||||
users.groups.avahi.gid = config.sane.allocations.avahi-gid;
|
|
||||||
users.groups.colord.gid = config.sane.allocations.colord-gid;
|
|
||||||
users.groups.feedbackd.gid = config.sane.allocations.feedbackd-gid;
|
|
||||||
users.groups.geoclue.gid = config.sane.allocations.geoclue-gid;
|
|
||||||
users.groups.rtkit.gid = config.sane.allocations.rtkit-gid;
|
|
||||||
|
|
||||||
# docs: https://github.com/NixOS/nixpkgs/blob/nixos-22.05/nixos/modules/services/x11/desktop-managers/phosh.nix
|
# docs: https://github.com/NixOS/nixpkgs/blob/nixos-22.05/nixos/modules/services/x11/desktop-managers/phosh.nix
|
||||||
services.xserver.desktopManager.phosh = {
|
services.xserver.desktopManager.phosh = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
@@ -2,6 +2,7 @@ https://search.nixos.org/options?channel=unstable&query=
|
|||||||
https://search.nixos.org/packages?channel=unstable&query=
|
https://search.nixos.org/packages?channel=unstable&query=
|
||||||
https://nixos.wiki/index.php?go=Go&search=
|
https://nixos.wiki/index.php?go=Go&search=
|
||||||
https://github.com/nixos/nixpkgs/pulls?q=
|
https://github.com/nixos/nixpkgs/pulls?q=
|
||||||
|
https://nur.nix-community.org/
|
||||||
https://nix-community.github.io/home-manager/options.html
|
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://w.uninsane.org/viewer#search?books.name=wikipedia_en_all_maxi_2022-05&pattern=
|
||||||
https://jackett.uninsane.org/UI/Dashboard#search=
|
https://jackett.uninsane.org/UI/Dashboard#search=
|
||||||
|
@@ -22,14 +22,15 @@ in
|
|||||||
};
|
};
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
sane.gui.enable = true;
|
sane.gui.enable = true;
|
||||||
users.users.greeter.uid = config.sane.allocations.greeter-uid;
|
|
||||||
users.groups.greeter.gid = config.sane.allocations.greeter-gid;
|
|
||||||
programs.sway = {
|
programs.sway = {
|
||||||
# we configure sway with home-manager, but this enable gets us e.g. opengl and fonts
|
# we configure sway with home-manager, but this enable gets us e.g. opengl and fonts
|
||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# alternatively, could use SDDM
|
# instead of using `services.greetd`, can instead use SDDM by swapping in these lines.
|
||||||
|
# services.xserver.displayManager.sddm.enable = true;
|
||||||
|
# services.xserver.enable = true;
|
||||||
services.greetd = let
|
services.greetd = let
|
||||||
swayConfig-greeter = pkgs.writeText "greetd-sway-config" ''
|
swayConfig-greeter = pkgs.writeText "greetd-sway-config" ''
|
||||||
# `-l` activates layer-shell mode.
|
# `-l` activates layer-shell mode.
|
||||||
@@ -71,13 +72,24 @@ in
|
|||||||
pulse.enable = true;
|
pulse.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
hardware.bluetooth.enable = true;
|
|
||||||
services.blueman.enable = true;
|
|
||||||
|
|
||||||
networking.useDHCP = false;
|
networking.useDHCP = false;
|
||||||
networking.networkmanager.enable = true;
|
networking.networkmanager.enable = true;
|
||||||
networking.wireless.enable = lib.mkForce false;
|
networking.wireless.enable = lib.mkForce false;
|
||||||
|
|
||||||
|
hardware.bluetooth.enable = true;
|
||||||
|
services.blueman.enable = true;
|
||||||
|
# gsd provides Rfkill, which is required for the bluetooth pane in gnome-control-center to work
|
||||||
|
services.gnome.gnome-settings-daemon.enable = true;
|
||||||
|
# start the components of gsd we need at login
|
||||||
|
systemd.user.targets."org.gnome.SettingsDaemon.Rfkill".wantedBy = [ "graphical-session.target" ];
|
||||||
|
# go ahead and `systemctl --user cat gnome-session-initialized.target`. i dare you.
|
||||||
|
# the only way i can figure out how to get Rfkill to actually load is to just disable all the shit it depends on.
|
||||||
|
# it doesn't actually seem to need ANY of them in the first place T_T
|
||||||
|
systemd.user.targets."gnome-session-initialized".enable = false;
|
||||||
|
# bluez can't connect to audio devices unless pipewire is running.
|
||||||
|
# a system service can't depend on a user service, so just launch it at graphical-session
|
||||||
|
systemd.user.services."pipewire".wantedBy = [ "graphical-session.target" ];
|
||||||
|
|
||||||
sane.home-manager.windowManager.sway = {
|
sane.home-manager.windowManager.sway = {
|
||||||
enable = true;
|
enable = true;
|
||||||
wrapperFeatures.gtk = true;
|
wrapperFeatures.gtk = true;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# Terminal UI mail client
|
# Terminal UI mail client
|
||||||
{ config, lib, ... }:
|
{ config, lib, sane-lib, ... }:
|
||||||
|
|
||||||
lib.mkIf config.sane.home-manager.enable
|
lib.mkIf config.sane.home-manager.enable
|
||||||
{
|
{
|
||||||
@@ -8,9 +8,5 @@ lib.mkIf config.sane.home-manager.enable
|
|||||||
sopsFile = ../../secrets/universal/aerc_accounts.conf;
|
sopsFile = ../../secrets/universal/aerc_accounts.conf;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
home-manager.users.colin = let sysconfig = config; in { config, ... }: {
|
sane.fs."/home/colin/.config/aerc/accounts.conf" = sane-lib.fs.wantedSymlinkTo config.sops.secrets.aerc_accounts.path;
|
||||||
# aerc TUI mail client
|
|
||||||
xdg.configFile."aerc/accounts.conf".source =
|
|
||||||
config.lib.file.mkOutOfStoreSymlink sysconfig.sops.secrets.aerc_accounts.path;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -9,22 +9,21 @@
|
|||||||
with lib;
|
with lib;
|
||||||
let
|
let
|
||||||
cfg = config.sane.home-manager;
|
cfg = config.sane.home-manager;
|
||||||
# extract package from `sane.packages.enabledUserPkgs`
|
# extract `pkg` from `sane.packages.enabledUserPkgs`
|
||||||
pkg-list = pkgspec: builtins.map (e: e.pkg or e) pkgspec;
|
pkg-list = pkgspec: builtins.map (e: e.pkg) pkgspec;
|
||||||
# extract `dir` from `sane.packages.enabledUserPkgs`
|
|
||||||
dir-list = pkgspec: builtins.concatLists (builtins.map (e: e.dir or []) pkgspec);
|
|
||||||
private-list = pkgspec: builtins.concatLists (builtins.map (e: e.private or []) pkgspec);
|
|
||||||
feeds = import ./feeds.nix { inherit lib; };
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./aerc.nix
|
./aerc.nix
|
||||||
./firefox.nix
|
./firefox.nix
|
||||||
|
./gfeeds.nix
|
||||||
./git.nix
|
./git.nix
|
||||||
|
./gpodder.nix
|
||||||
|
./keyring.nix
|
||||||
./kitty.nix
|
./kitty.nix
|
||||||
./mpv.nix
|
./mpv.nix
|
||||||
./nb.nix
|
|
||||||
./neovim.nix
|
./neovim.nix
|
||||||
|
./newsflash.nix
|
||||||
./splatmoji.nix
|
./splatmoji.nix
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
./sublime-music.nix
|
./sublime-music.nix
|
||||||
@@ -51,18 +50,6 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
sane.impermanence.home-dirs = [
|
|
||||||
"archive"
|
|
||||||
"dev"
|
|
||||||
"records"
|
|
||||||
"ref"
|
|
||||||
"tmp"
|
|
||||||
"use"
|
|
||||||
"Music"
|
|
||||||
"Pictures"
|
|
||||||
"Videos"
|
|
||||||
] ++ (dir-list config.sane.packages.enabledUserPkgs);
|
|
||||||
|
|
||||||
home-manager.useGlobalPkgs = true;
|
home-manager.useGlobalPkgs = true;
|
||||||
home-manager.useUserPackages = true;
|
home-manager.useUserPackages = true;
|
||||||
|
|
||||||
@@ -82,35 +69,6 @@ in
|
|||||||
home.username = "colin";
|
home.username = "colin";
|
||||||
home.homeDirectory = "/home/colin";
|
home.homeDirectory = "/home/colin";
|
||||||
|
|
||||||
home.activation = {
|
|
||||||
initKeyring = {
|
|
||||||
after = ["writeBoundary"];
|
|
||||||
before = [];
|
|
||||||
data = "${../../scripts/init-keyring}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
home.file = let
|
|
||||||
privates = builtins.listToAttrs (
|
|
||||||
builtins.map (path: {
|
|
||||||
name = path;
|
|
||||||
value = { source = config.lib.file.mkOutOfStoreSymlink "/home/colin/private/${path}"; };
|
|
||||||
})
|
|
||||||
(private-list sysconfig.sane.packages.enabledUserPkgs)
|
|
||||||
);
|
|
||||||
in {
|
|
||||||
# convenience
|
|
||||||
"knowledge".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/private/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/knowledge/secrets/accounts";
|
|
||||||
} // privates;
|
|
||||||
|
|
||||||
# XDG defines things like ~/Desktop, ~/Downloads, etc.
|
# XDG defines things like ~/Desktop, ~/Downloads, etc.
|
||||||
# these clutter the home, so i mostly don't use them.
|
# these clutter the home, so i mostly don't use them.
|
||||||
xdg.userDirs = {
|
xdg.userDirs = {
|
||||||
@@ -130,7 +88,7 @@ in
|
|||||||
# - `xdg-mime query filetype path/to/thing.ext`
|
# - `xdg-mime query filetype path/to/thing.ext`
|
||||||
xdg.mimeApps.enable = true;
|
xdg.mimeApps.enable = true;
|
||||||
xdg.mimeApps.defaultApplications = let
|
xdg.mimeApps.defaultApplications = let
|
||||||
www = sysconfig.sane.web-browser.desktop;
|
www = sysconfig.sane.web-browser.browser.desktop;
|
||||||
pdf = "org.gnome.Evince.desktop";
|
pdf = "org.gnome.Evince.desktop";
|
||||||
md = "obsidian.desktop";
|
md = "obsidian.desktop";
|
||||||
thumb = "org.gnome.gThumb.desktop";
|
thumb = "org.gnome.gThumb.desktop";
|
||||||
@@ -173,54 +131,14 @@ in
|
|||||||
# <item oor:path="/org.openoffice.Setup/Product"><prop oor:name="LastTimeGetInvolvedShown" oor:op="fuse"><value>1667693880</value></prop></item>
|
# <item oor:path="/org.openoffice.Setup/Product"><prop oor:name="LastTimeGetInvolvedShown" oor:op="fuse"><value>1667693880</value></prop></item>
|
||||||
|
|
||||||
|
|
||||||
|
programs = lib.mkMerge [
|
||||||
xdg.configFile."gpodderFeeds.opml".text = with feeds;
|
{
|
||||||
feedsToOpml feeds.podcasts;
|
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
|
||||||
# news-flash RSS viewer
|
nix-index.enable = true;
|
||||||
xdg.configFile."newsflashFeeds.opml".text = with feeds;
|
}
|
||||||
feedsToOpml (feeds.texts ++ feeds.images);
|
cfg.programs
|
||||||
|
];
|
||||||
# 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;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
# many of the settings below won't have effect without those patches.
|
# many of the settings below won't have effect without those patches.
|
||||||
# see: https://gitlab.com/librewolf-community/settings/-/blob/master/distribution/policies.json
|
# see: https://gitlab.com/librewolf-community/settings/-/blob/master/distribution/policies.json
|
||||||
|
|
||||||
{ config, lib, pkgs, ...}:
|
{ config, lib, pkgs, sane-lib, ...}:
|
||||||
with lib;
|
with lib;
|
||||||
let
|
let
|
||||||
cfg = config.sane.web-browser;
|
cfg = config.sane.web-browser;
|
||||||
@@ -19,22 +19,24 @@ let
|
|||||||
# });
|
# });
|
||||||
libName = "librewolf";
|
libName = "librewolf";
|
||||||
dotDir = ".librewolf";
|
dotDir = ".librewolf";
|
||||||
|
cacheDir = ".cache/librewolf"; # TODO: is it?
|
||||||
desktop = "librewolf.desktop";
|
desktop = "librewolf.desktop";
|
||||||
};
|
};
|
||||||
firefoxSettings = {
|
firefoxSettings = {
|
||||||
browser = pkgs.firefox-esr-unwrapped;
|
browser = pkgs.firefox-esr-unwrapped;
|
||||||
libName = "firefox";
|
libName = "firefox";
|
||||||
dotDir = ".mozilla/firefox";
|
dotDir = ".mozilla/firefox";
|
||||||
|
cacheDir = ".cache/mozilla";
|
||||||
desktop = "firefox.desktop";
|
desktop = "firefox.desktop";
|
||||||
};
|
};
|
||||||
defaultSettings = firefoxSettings;
|
defaultSettings = firefoxSettings;
|
||||||
# defaultSettings = librewolfSettings;
|
# defaultSettings = librewolfSettings;
|
||||||
|
|
||||||
package = pkgs.wrapFirefox cfg.browser {
|
package = pkgs.wrapFirefox cfg.browser.browser {
|
||||||
# inherit the default librewolf.cfg
|
# inherit the default librewolf.cfg
|
||||||
# it can be further customized via ~/.librewolf/librewolf.overrides.cfg
|
# it can be further customized via ~/.librewolf/librewolf.overrides.cfg
|
||||||
inherit (pkgs.librewolf-unwrapped) extraPrefsFiles;
|
inherit (pkgs.librewolf-unwrapped) extraPrefsFiles;
|
||||||
inherit (cfg) libName;
|
inherit (cfg.browser) libName;
|
||||||
|
|
||||||
extraNativeMessagingHosts = [ pkgs.browserpass ];
|
extraNativeMessagingHosts = [ pkgs.browserpass ];
|
||||||
# extraNativeMessagingHosts = [ pkgs.gopass-native-messaging-host ];
|
# extraNativeMessagingHosts = [ pkgs.gopass-native-messaging-host ];
|
||||||
@@ -55,9 +57,9 @@ let
|
|||||||
# get names from:
|
# get names from:
|
||||||
# - ~/ref/nix-community/nur-combined/repos/rycee/pkgs/firefox-addons/generated-firefox-addons.nix
|
# - ~/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'`
|
# `wget ...xpi`; `unar ...xpi`; `cat */manifest.json | jq '.browser_specific_settings.gecko.id'`
|
||||||
(addon "ublock-origin" "uBlock0@raymondhill.net" "sha256-+xc4lcdsOwXxMsr4enFsdePbIb6GHq0bFLpqvH5xXos=")
|
(addon "ublock-origin" "uBlock0@raymondhill.net" "sha256-a/ivUmY1P6teq9x0dt4CbgHt+3kBsEMMXlOfZ5Hx7cg=")
|
||||||
(addon "sponsorblock" "sponsorBlocker@ajay.app" "sha256-30F8oDIgshXVY7YKgnfoc1tUTHfgeFbzXISJuVJs0AM=")
|
(addon "sponsorblock" "sponsorBlocker@ajay.app" "sha256-d2K3ufvurWnYVzqLbyR//MgejybkY9exitAf9RdLNRo=")
|
||||||
(addon "bypass-paywalls-clean" "{d133e097-46d9-4ecc-9903-fa6a722a6e0e}" "sha256-7ZDkG8O1rEYdh/La0PLi9tp92JxYeQvaOFt/BmnDv3U=")
|
(addon "bypass-paywalls-clean" "{d133e097-46d9-4ecc-9903-fa6a722a6e0e}" "sha256-t6Q335Nq60mDILPmzem+DT5KflleAPVJL3bsaA+UL0g=")
|
||||||
(addon "sidebery" "{3c078156-979c-498b-8990-85f7987dd929}" "sha256-YONfK/rIjlsrTgRHIt3km07Q7KnpIW89Z9r92ZSCc6w=")
|
(addon "sidebery" "{3c078156-979c-498b-8990-85f7987dd929}" "sha256-YONfK/rIjlsrTgRHIt3km07Q7KnpIW89Z9r92ZSCc6w=")
|
||||||
(addon "ether-metamask" "webextension@metamask.io" "sha256-G+MwJDOcsaxYSUXjahHJmkWnjLeQ0Wven8DU/lGeMzA=")
|
(addon "ether-metamask" "webextension@metamask.io" "sha256-G+MwJDOcsaxYSUXjahHJmkWnjLeQ0Wven8DU/lGeMzA=")
|
||||||
(addon "ublacklist" "@ublacklist" "sha256-vHe/7EYOzcKeAbTElmt0Rb4E2rX0f3JgXThJaUmaz+M=")
|
(addon "ublacklist" "@ublacklist" "sha256-vHe/7EYOzcKeAbTElmt0Rb4E2rX0f3JgXThJaUmaz+M=")
|
||||||
@@ -103,43 +105,57 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
sane.web-browser = mkOption {
|
sane.web-browser.browser = mkOption {
|
||||||
default = defaultSettings;
|
default = defaultSettings;
|
||||||
type = types.attrs;
|
type = types.attrs;
|
||||||
};
|
};
|
||||||
|
sane.web-browser.persistData = mkOption {
|
||||||
|
description = "optional store name to which persist browsing data (like history)";
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
sane.web-browser.persistCache = mkOption {
|
||||||
|
description = "optional store name to which persist browser cache";
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = "cryptClearOnBoot";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
config = lib.mkIf config.sane.home-manager.enable {
|
|
||||||
# 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.
|
config = lib.mkIf config.sane.home-manager.enable {
|
||||||
# specifically, enable the GDPR cookie prompt blocker.
|
|
||||||
# data.toOverwrite.filterLists is additive (i.e. it supplements the default filters)
|
# uBlock filter list configuration.
|
||||||
# this configuration method is documented here:
|
# specifically, enable the GDPR cookie prompt blocker.
|
||||||
# - <https://github.com/gorhill/uBlock/issues/2986#issuecomment-364035002>
|
# data.toOverwrite.filterLists is additive (i.e. it supplements the default filters)
|
||||||
# the specific attribute path is found via scraping ublock code here:
|
# this configuration method is documented here:
|
||||||
# - <https://github.com/gorhill/uBlock/blob/master/src/js/storage.js>
|
# - <https://github.com/gorhill/uBlock/issues/2986#issuecomment-364035002>
|
||||||
# - <https://github.com/gorhill/uBlock/blob/master/assets/assets.json>
|
# the specific attribute path is found via scraping ublock code here:
|
||||||
home.file."${cfg.dotDir}/managed-storage/uBlock0@raymondhill.net.json".text = ''
|
# - <https://github.com/gorhill/uBlock/blob/master/src/js/storage.js>
|
||||||
{
|
# - <https://github.com/gorhill/uBlock/blob/master/assets/assets.json>
|
||||||
"name": "uBlock0@raymondhill.net",
|
sane.fs."/home/colin/${cfg.browser.dotDir}/managed-storage/uBlock0@raymondhill.net.json" = sane-lib.fs.wantedText ''
|
||||||
"description": "ignored",
|
{
|
||||||
"type": "storage",
|
"name": "uBlock0@raymondhill.net",
|
||||||
"data": {
|
"description": "ignored",
|
||||||
"toOverwrite": "{\"filterLists\": [\"fanboy-cookiemonster\"]}"
|
"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.
|
sane.fs."/home/colin/${cfg.browser.dotDir}/${cfg.browser.libName}.overrides.cfg" = sane-lib.fs.wantedText ''
|
||||||
// see: <https://librewolf.net/docs/faq/#im-getting-sec_error_ocsp_server_error-what-can-i-do>
|
// if we can't query the revocation status of a SSL cert because the issuer is offline,
|
||||||
defaultPref("security.OCSP.require", false);
|
// 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/knowledge";
|
|
||||||
home.file.".nb/.current".text = "knowledge";
|
|
||||||
home.file.".nbrc".text = ''
|
|
||||||
# manage with `nb settings`
|
|
||||||
export NB_AUTO_SYNC=0
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
lib.mkIf config.sane.home-manager.enable
|
lib.mkIf config.sane.home-manager.enable
|
||||||
{
|
{
|
||||||
sane.impermanence.home-dirs = [ ".cache/vim-swap" ];
|
# private because there could be sensitive things in the swap
|
||||||
|
sane.persist.home.private = [ ".cache/vim-swap" ];
|
||||||
|
|
||||||
home-manager.users.colin.programs.neovim = {
|
home-manager.users.colin.programs.neovim = {
|
||||||
# neovim: https://github.com/neovim/neovim
|
# neovim: https://github.com/neovim/neovim
|
||||||
|
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:
|
# borrows from:
|
||||||
# - default config: <https://github.com/cspeterson/splatmoji/blob/master/splatmoji.config>
|
# - default config: <https://github.com/cspeterson/splatmoji/blob/master/splatmoji.config>
|
||||||
# - wayland: <https://github.com/cspeterson/splatmoji/issues/32#issuecomment-830862566>
|
# - wayland: <https://github.com/cspeterson/splatmoji/issues/32#issuecomment-830862566>
|
||||||
{ pkgs, ... }:
|
{ pkgs, sane-lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
home-manager.users.colin = {
|
sane.persist.home.plaintext = [ ".local/state/splatmoji" ];
|
||||||
xdg.configFile."splatmoji/splatmoji.config".text = ''
|
sane.fs."/home/colin/.config/splatmoji/splatmoji.config" = sane-lib.fs.wantedText ''
|
||||||
history_file=/home/colin/.local/state/splatmoji/history
|
history_file=/home/colin/.local/state/splatmoji/history
|
||||||
history_length=5
|
history_length=5
|
||||||
# TODO: wayland equiv
|
# TODO: wayland equiv
|
||||||
paste_command=xdotool key ctrl+v
|
paste_command=xdotool key ctrl+v
|
||||||
# rofi_command=${pkgs.wofi}/bin/wofi --dmenu --insensitive --cache-file /dev/null
|
# rofi_command=${pkgs.wofi}/bin/wofi --dmenu --insensitive --cache-file /dev/null
|
||||||
rofi_command=${pkgs.fuzzel}/bin/fuzzel -d -i -w 60
|
rofi_command=${pkgs.fuzzel}/bin/fuzzel -d -i -w 60
|
||||||
xdotool_command=${pkgs.wtype}/bin/wtype
|
xdotool_command=${pkgs.wtype}/bin/wtype
|
||||||
# TODO: wayland equiv
|
# TODO: wayland equiv
|
||||||
xsel_command=xsel -b -i
|
xsel_command=xsel -b -i
|
||||||
'';
|
'';
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +1,23 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, sane-lib, ... }:
|
||||||
|
|
||||||
lib.mkIf config.sane.home-manager.enable
|
with lib;
|
||||||
{
|
let
|
||||||
home-manager.users.colin = let
|
host = config.networking.hostName;
|
||||||
host = config.networking.hostName;
|
user-pubkey = config.sane.ssh.pubkeys."colin@${host}".asUserKey;
|
||||||
user_pubkey = (import ../pubkeys.nix).users."${host}";
|
host-keys = filter (k: k.user == "root") (attrValues config.sane.ssh.pubkeys);
|
||||||
known_hosts_text = builtins.concatStringsSep
|
known-hosts-text = concatStringsSep
|
||||||
"\n"
|
"\n"
|
||||||
(builtins.attrValues (import ../pubkeys.nix).hosts);
|
(map (k: k.asHostKey) host-keys)
|
||||||
in { config, ...}: {
|
;
|
||||||
# ssh key is stored in private storage
|
in lib.mkIf config.sane.home-manager.enable {
|
||||||
home.file.".ssh/id_ed25519".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/private/.ssh/id_ed25519";
|
# ssh key is stored in private storage
|
||||||
home.file.".ssh/id_ed25519.pub".text = user_pubkey;
|
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;
|
users.users.colin.openssh.authorizedKeys.keys =
|
||||||
# this optionally accepts multiple known_hosts paths, separated by space.
|
let
|
||||||
programs.ssh.userKnownHostsFile = builtins.toString (pkgs.writeText "known_hosts" known_hosts_text);
|
user-keys = filter (k: k.user == "colin") (attrValues config.sane.ssh.pubkeys);
|
||||||
};
|
in
|
||||||
|
map (k: k.asUserKey) user-keys;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{ config, lib, ... }:
|
{ config, lib, sane-lib, ... }:
|
||||||
|
|
||||||
lib.mkIf config.sane.home-manager.enable
|
lib.mkIf config.sane.home-manager.enable
|
||||||
{
|
{
|
||||||
@@ -8,9 +8,5 @@ lib.mkIf config.sane.home-manager.enable
|
|||||||
sopsFile = ../../secrets/universal/sublime_music_config.json.bin;
|
sopsFile = ../../secrets/universal/sublime_music_config.json.bin;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
home-manager.users.colin = let sysconfig = config; in { config, ... }: {
|
sane.fs."/home/colin/.config/sublime-music/config.json" = sane-lib.fs.wantedSymlinkTo config.sops.secrets.sublime_music_config.path;
|
||||||
# sublime music player
|
|
||||||
xdg.configFile."sublime-music/config.json".source =
|
|
||||||
config.lib.file.mkOutOfStoreSymlink sysconfig.sops.secrets.sublime_music_config.path;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,18 @@
|
|||||||
{ config, lib, ... }:
|
{ config, lib, sane-lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
feeds = sane-lib.feeds;
|
||||||
|
all-feeds = config.sane.feeds;
|
||||||
|
wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds;
|
||||||
|
podcast-urls = lib.concatStringsSep "|" (
|
||||||
|
builtins.map (feed: feed.url) wanted-feeds
|
||||||
|
);
|
||||||
|
in
|
||||||
lib.mkIf config.sane.home-manager.enable
|
lib.mkIf config.sane.home-manager.enable
|
||||||
{
|
{
|
||||||
home-manager.users.colin.xdg.configFile."vlc/vlcrc".text =
|
sane.fs."/home/colin/.config/vlc/vlcrc" = sane-lib.fs.wantedText ''
|
||||||
let
|
|
||||||
feeds = import ./feeds.nix { inherit lib; };
|
|
||||||
podcastUrls = lib.strings.concatStringsSep "|" (
|
|
||||||
builtins.map (feed: feed.url) feeds.podcasts
|
|
||||||
);
|
|
||||||
in ''
|
|
||||||
[podcast]
|
[podcast]
|
||||||
podcast-urls=${podcastUrls}
|
podcast-urls=${podcast-urls}
|
||||||
[core]
|
[core]
|
||||||
metadata-network-access=0
|
metadata-network-access=0
|
||||||
[qt]
|
[qt]
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
lib.mkIf config.sane.home-manager.enable
|
lib.mkIf config.sane.home-manager.enable
|
||||||
{
|
{
|
||||||
sane.impermanence.home-dirs = [
|
sane.persist.home.plaintext = [
|
||||||
# we don't need to full zsh dir -- just the history file --
|
# 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.
|
# 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?
|
# TODO: should be private?
|
||||||
|
89
modules/ids.nix
Normal file
89
modules/ids.nix
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{ lib, config, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
cfg = config.sane.ids;
|
||||||
|
id = types.submodule {
|
||||||
|
options = {
|
||||||
|
uid = mkOption {
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
gid = mkOption {
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
userOpts = { name, ... }: {
|
||||||
|
config =
|
||||||
|
let
|
||||||
|
ent-ids = cfg."${name}" or {};
|
||||||
|
uid = ent-ids.uid or null;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
uid = lib.mkIf (uid != null) uid;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
groupOpts = { name, ... }: {
|
||||||
|
config =
|
||||||
|
let
|
||||||
|
ent-ids = cfg."${name}" or {};
|
||||||
|
gid = ent-ids.gid or null;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
gid = lib.mkIf (gid != null) gid;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
sane.ids = mkOption {
|
||||||
|
type = types.attrsOf id;
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
mapping from user/group name to gids/uids you expect that entity to have.
|
||||||
|
for users/groups created elsewhere *without* an id, this is used to provide them a fixed/stable id.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# these get merged with the nixpkgs options.
|
||||||
|
users.users = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule userOpts);
|
||||||
|
};
|
||||||
|
users.groups = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule groupOpts);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
# guarantee determinism in uid/gid generation for users:
|
||||||
|
assertions = lib.mkMerge [
|
||||||
|
(
|
||||||
|
lib.mapAttrsToList
|
||||||
|
(name: user: {
|
||||||
|
assertion = user.uid != null;
|
||||||
|
message = "non-deterministic uid detected for: ${name}";
|
||||||
|
})
|
||||||
|
config.users.users
|
||||||
|
)
|
||||||
|
(
|
||||||
|
lib.mapAttrsToList
|
||||||
|
(name: group: {
|
||||||
|
assertion = group.gid != null;
|
||||||
|
message = "non-deterministic gid detected for: ${name}";
|
||||||
|
})
|
||||||
|
config.users.groups
|
||||||
|
)
|
||||||
|
(
|
||||||
|
lib.mapAttrsToList
|
||||||
|
(name: user: {
|
||||||
|
assertion = !user.autoSubUidGidRange;
|
||||||
|
message = "non-deterministic subUids/Guids detected for: ${name}";
|
||||||
|
})
|
||||||
|
config.users.users
|
||||||
|
)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
{ config, lib, pkgs, mobile-nixos, utils, ... }:
|
{ config, lib, pkgs, utils, ... }:
|
||||||
|
# TODO: replace mobile-nixos parts with Disko <https://github.com/nix-community/disko>
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
let
|
let
|
||||||
@@ -9,7 +10,7 @@ in
|
|||||||
sane.image.enable = mkOption {
|
sane.image.enable = mkOption {
|
||||||
default = true;
|
default = true;
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
description = "whether to enable image targets. this doesn't mean they'll be built unless you specifically reference the target.";
|
description = "whether to enable image targets. even so they won't be built unless you specifically reference the `system.build.img` target.";
|
||||||
};
|
};
|
||||||
# packages whose contents should be copied directly into the /boot partition.
|
# packages whose contents should be copied directly into the /boot partition.
|
||||||
# e.g. EFI loaders, u-boot bootloader, etc.
|
# e.g. EFI loaders, u-boot bootloader, etc.
|
||||||
@@ -78,7 +79,9 @@ in
|
|||||||
"ext4" = pkgs.imageBuilder.fileSystem.makeExt4;
|
"ext4" = pkgs.imageBuilder.fileSystem.makeExt4;
|
||||||
"btrfs" = pkgs.imageBuilder.fileSystem.makeBtrfs;
|
"btrfs" = pkgs.imageBuilder.fileSystem.makeBtrfs;
|
||||||
};
|
};
|
||||||
in {
|
in
|
||||||
|
lib.mkIf cfg.enable
|
||||||
|
{
|
||||||
system.build.img-without-firmware = with pkgs; imageBuilder.diskImage.makeGPT {
|
system.build.img-without-firmware = with pkgs; imageBuilder.diskImage.makeGPT {
|
||||||
name = "nixos";
|
name = "nixos";
|
||||||
diskID = vfatUuidFromFs bootFs;
|
diskID = vfatUuidFromFs bootFs;
|
||||||
|
@@ -1,232 +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
|
|
||||||
{ config, lib, pkgs, utils, ... }:
|
|
||||||
|
|
||||||
with lib;
|
|
||||||
let
|
|
||||||
cfg = config.sane.impermanence;
|
|
||||||
getStore = { encryptedClearOnBoot, ... }: (
|
|
||||||
if encryptedClearOnBoot then {
|
|
||||||
device = "/mnt/impermanence/crypt/clearedonboot";
|
|
||||||
underlying = {
|
|
||||||
path = "/nix/persist/crypt/clearedonboot";
|
|
||||||
# TODO: consider moving this to /tmp, but that requires tmp be mounted first?
|
|
||||||
type = "gocryptfs";
|
|
||||||
key = "/mnt/impermanence/crypt/clearedonboot.key";
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
device = "/nix/persist";
|
|
||||||
# device = "/mnt/impermenanence/persist/plain";
|
|
||||||
# underlying = {
|
|
||||||
# path = "/nix/persist";
|
|
||||||
# type = "bind";
|
|
||||||
# };
|
|
||||||
}
|
|
||||||
);
|
|
||||||
home-dir-defaults = {
|
|
||||||
user = "colin";
|
|
||||||
group = "users";
|
|
||||||
mode = "0755";
|
|
||||||
relativeTo = "/home/colin";
|
|
||||||
};
|
|
||||||
sys-dir-defaults = {
|
|
||||||
user = "root";
|
|
||||||
group = "root";
|
|
||||||
mode = "0755";
|
|
||||||
relativeTo = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
# turn a path into a name suitable for systemd
|
|
||||||
cleanName = utils.escapeSystemdPath;
|
|
||||||
|
|
||||||
# split the string path into a list of string components.
|
|
||||||
# root directory "/" becomes the empty list [].
|
|
||||||
# implicitly performs normalization so that:
|
|
||||||
# splitPath "a//b/" => ["a" "b"]
|
|
||||||
# splitPath "/a/b" => ["a" "b"]
|
|
||||||
splitPath = str: builtins.filter (seg: (builtins.isString seg) && seg != "" ) (builtins.split "/" str);
|
|
||||||
# return a string path, with leading slash but no trailing slash
|
|
||||||
joinPathAbs = comps: "/" + (builtins.concatStringsSep "/" comps);
|
|
||||||
concatPaths = paths: joinPathAbs (builtins.concatLists (builtins.map (p: splitPath p) paths));
|
|
||||||
|
|
||||||
dirOptions = defaults: types.submodule {
|
|
||||||
options = {
|
|
||||||
encryptedClearOnBoot = mkOption {
|
|
||||||
default = false;
|
|
||||||
type = types.bool;
|
|
||||||
};
|
|
||||||
directory = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
};
|
|
||||||
user = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = defaults.user;
|
|
||||||
};
|
|
||||||
group = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = defaults.group;
|
|
||||||
};
|
|
||||||
mode = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = defaults.mode;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
mkDirsOption = defaults: mkOption {
|
|
||||||
default = [];
|
|
||||||
type = types.listOf (types.coercedTo types.str (d: { directory = d; }) (dirOptions defaults));
|
|
||||||
# apply = map (d: if isString d then { directory = d; } else d);
|
|
||||||
};
|
|
||||||
|
|
||||||
# expand user options with more context
|
|
||||||
ingestDirOption = defaults: opt: {
|
|
||||||
inherit (opt) user group mode;
|
|
||||||
directory = concatPaths [ defaults.relativeTo opt.directory ];
|
|
||||||
|
|
||||||
## helpful context
|
|
||||||
store = builtins.addErrorContext ''while ingestDirOption on ${opt.directory} with attrs ${builtins.concatStringsSep " " (attrNames opt)}''
|
|
||||||
(getStore opt);
|
|
||||||
};
|
|
||||||
|
|
||||||
ingestDirOptions = defaults: opts: builtins.map (ingestDirOption defaults) opts;
|
|
||||||
ingested-home-dirs = ingestDirOptions home-dir-defaults cfg.home-dirs;
|
|
||||||
ingested-sys-dirs = ingestDirOptions sys-dir-defaults cfg.dirs;
|
|
||||||
ingested-dirs = ingested-home-dirs ++ ingested-sys-dirs;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
sane.impermanence.enable = mkOption {
|
|
||||||
default = false;
|
|
||||||
type = types.bool;
|
|
||||||
};
|
|
||||||
sane.impermanence.root-on-tmpfs = mkOption {
|
|
||||||
default = false;
|
|
||||||
type = types.bool;
|
|
||||||
description = "define / to be a tmpfs. make sure to mount some other device to /nix";
|
|
||||||
};
|
|
||||||
sane.impermanence.home-dirs = mkDirsOption home-dir-defaults;
|
|
||||||
sane.impermanence.dirs = mkDirsOption sys-dir-defaults;
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
./root-on-tmpfs.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
config = mkIf cfg.enable (lib.mkMerge [
|
|
||||||
{
|
|
||||||
# TODO: move to sane.fs, to auto-ensure all user dirs?
|
|
||||||
sane.fs."/home/colin".dir = {
|
|
||||||
user = "colin";
|
|
||||||
group = config.users.users.colin.group;
|
|
||||||
mode = config.users.users.colin.homeMode;
|
|
||||||
};
|
|
||||||
|
|
||||||
# without this, we get `fusermount: fuse device not found, try 'modprobe fuse' first`.
|
|
||||||
# - that only happens after a activation-via-boot -- not activation-after-rebuild-switch.
|
|
||||||
# it seems likely that systemd loads `fuse` by default. see:
|
|
||||||
# - </etc/systemd/system/sysinit.target.wants/sys-fs-fuse-connections.mount>
|
|
||||||
# - triggers: /etc/systemd/system/modprobe@.service
|
|
||||||
# - calls `modprobe`
|
|
||||||
# note: even `boot.kernelModules = ...` isn't enough: that option creates /etc/modules-load.d/, which is ingested only by systemd.
|
|
||||||
# note: `boot.initrd.availableKernelModules` ALSO isn't enough: idk why.
|
|
||||||
# TODO: might not be necessary now we're using fileSystems and systemd
|
|
||||||
boot.initrd.kernelModules = [ "fuse" ];
|
|
||||||
|
|
||||||
# TODO: convert this to a systemd unit file?
|
|
||||||
system.activationScripts.prepareEncryptedClearedOnBoot =
|
|
||||||
let
|
|
||||||
script = pkgs.writeShellApplication {
|
|
||||||
name = "prepareEncryptedClearedOnBoot";
|
|
||||||
runtimeInputs = with pkgs; [ gocryptfs ];
|
|
||||||
text = ''
|
|
||||||
backing="$1"
|
|
||||||
passfile="$2"
|
|
||||||
if ! test -e "$passfile"
|
|
||||||
then
|
|
||||||
tmpdir=$(dirname "$passfile")
|
|
||||||
mkdir -p "$backing" "$tmpdir"
|
|
||||||
# if the key doesn't exist, it's probably not mounted => delete the backing dir
|
|
||||||
rm -rf "''${backing:?}"/*
|
|
||||||
# generate key. we can "safely" keep it around for the lifetime of this boot
|
|
||||||
dd if=/dev/random bs=128 count=1 | base64 --wrap=0 > "$passfile"
|
|
||||||
# initialize the crypt store
|
|
||||||
gocryptfs -quiet -passfile "$passfile" -init "$backing"
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
store = getStore { encryptedClearOnBoot = true; };
|
|
||||||
in {
|
|
||||||
text = ''${script}/bin/prepareEncryptedClearedOnBoot ${store.underlying.path} ${store.underlying.key}'';
|
|
||||||
};
|
|
||||||
|
|
||||||
fileSystems = let
|
|
||||||
store = getStore { encryptedClearOnBoot = true; };
|
|
||||||
in {
|
|
||||||
"${store.device}" = {
|
|
||||||
device = store.underlying.path;
|
|
||||||
fsType = "fuse.gocryptfs";
|
|
||||||
options = [
|
|
||||||
"nodev"
|
|
||||||
"nosuid"
|
|
||||||
"allow_other"
|
|
||||||
"passfile=${store.underlying.key}"
|
|
||||||
"defaults"
|
|
||||||
];
|
|
||||||
noCheck = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.systemPackages = [ pkgs.gocryptfs ]; # fuse needs to find gocryptfs
|
|
||||||
}
|
|
||||||
|
|
||||||
(
|
|
||||||
let cfgFor = opt:
|
|
||||||
let
|
|
||||||
# systemd creates <path>.mount services for every fileSystems entry.
|
|
||||||
# <path> gets escaped as part of that: this code tries to guess that escaped name here.
|
|
||||||
# backing-mount = cleanName opt.store.device;
|
|
||||||
mount-service = cleanName opt.directory;
|
|
||||||
backing-path = concatPaths [ opt.store.device opt.directory ];
|
|
||||||
|
|
||||||
dir-service = config.sane.fs."${opt.directory}".service;
|
|
||||||
backing-service = config.sane.fs."${backing-path}".service;
|
|
||||||
in {
|
|
||||||
# create destination and backing directory, with correct perms
|
|
||||||
sane.fs."${opt.directory}".dir = {
|
|
||||||
inherit (opt) user group mode;
|
|
||||||
};
|
|
||||||
sane.fs."${backing-path}".dir = {
|
|
||||||
inherit (opt) user group mode;
|
|
||||||
};
|
|
||||||
# define the mountpoint.
|
|
||||||
fileSystems."${opt.directory}" = {
|
|
||||||
device = backing-path;
|
|
||||||
options = [
|
|
||||||
"bind"
|
|
||||||
# "x-systemd.requires=${backing-mount}.mount" # this should be implicit
|
|
||||||
"x-systemd.after=${backing-service}"
|
|
||||||
"x-systemd.after=${dir-service}"
|
|
||||||
# `wants` doesn't seem to make it to the service file here :-(
|
|
||||||
# "x-systemd.wants=${backing-service}"
|
|
||||||
# "x-systemd.wants=${dir-service}"
|
|
||||||
];
|
|
||||||
# fsType = "bind";
|
|
||||||
noCheck = true;
|
|
||||||
};
|
|
||||||
systemd.services."${backing-service}".wantedBy = [ "${mount-service}.mount" ];
|
|
||||||
systemd.services."${dir-service}".wantedBy = [ "${mount-service}.mount" ];
|
|
||||||
|
|
||||||
};
|
|
||||||
cfgs = builtins.map cfgFor ingested-dirs;
|
|
||||||
in {
|
|
||||||
fileSystems = lib.mkMerge (catAttrs "fileSystems" cfgs);
|
|
||||||
sane.fs = lib.mkMerge (catAttrs "fs" (catAttrs "sane" cfgs));
|
|
||||||
systemd = lib.mkMerge (catAttrs "systemd" cfgs);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
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;
|
||||||
|
}
|
@@ -85,7 +85,7 @@ let
|
|||||||
# XXX by default fractal stores its state in ~/.local/share/<UUID>.
|
# XXX by default fractal stores its state in ~/.local/share/<UUID>.
|
||||||
# after logging in, manually change ~/.local/share/keyrings/... to point it to some predictable subdir.
|
# after logging in, manually change ~/.local/share/keyrings/... to point it to some predictable subdir.
|
||||||
# then reboot (so that libsecret daemon re-loads the keyring...?)
|
# then reboot (so that libsecret daemon re-loads the keyring...?)
|
||||||
{ pkg = fractal-latest; private = [ ".local/share/fractal" ]; }
|
# { pkg = fractal-latest; private = [ ".local/share/fractal" ]; }
|
||||||
# { pkg = fractal-next; private = [ ".local/share/fractal" ]; }
|
# { pkg = fractal-next; private = [ ".local/share/fractal" ]; }
|
||||||
|
|
||||||
gajim # XMPP client
|
gajim # XMPP client
|
||||||
@@ -144,7 +144,7 @@ let
|
|||||||
# possible to pass config as a CLI arg (sublime-music -c config.json)
|
# possible to pass config as a CLI arg (sublime-music -c config.json)
|
||||||
# { pkg = sublime-music; dir = [ ".local/share/sublime-music" ]; }
|
# { pkg = sublime-music; dir = [ ".local/share/sublime-music" ]; }
|
||||||
{ pkg = sublime-music-mobile; dir = [ ".local/share/sublime-music" ]; }
|
{ pkg = sublime-music-mobile; dir = [ ".local/share/sublime-music" ]; }
|
||||||
tdesktop # broken on phosh
|
{ pkg = tdesktop; private = [ ".local/share/TelegramDesktop" ]; } # broken on phosh
|
||||||
|
|
||||||
{ pkg = tokodon; private = [ ".cache/KDE/tokodon" ]; }
|
{ pkg = tokodon; private = [ ".cache/KDE/tokodon" ]; }
|
||||||
|
|
||||||
@@ -264,13 +264,20 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toPkgSpec = types.coercedTo types.package (p: { pkg = p; }) pkgSpec;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
# packages to deploy to the user's home
|
# packages to deploy to the user's home
|
||||||
sane.packages.extraUserPkgs = mkOption {
|
sane.packages.extraUserPkgs = mkOption {
|
||||||
default = [ ];
|
default = [ ];
|
||||||
type = types.listOf (types.either types.package pkgSpec);
|
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 {
|
sane.packages.enableConsolePkgs = mkOption {
|
||||||
default = false;
|
default = false;
|
||||||
@@ -297,16 +304,18 @@ in
|
|||||||
sane.packages.enabledUserPkgs = mkOption {
|
sane.packages.enabledUserPkgs = mkOption {
|
||||||
default = cfg.extraUserPkgs
|
default = cfg.extraUserPkgs
|
||||||
++ (if cfg.enableConsolePkgs then consolePkgs else [])
|
++ (if cfg.enableConsolePkgs then consolePkgs else [])
|
||||||
++ (if cfg.enableGuiPkgs then guiPkgs else [])
|
++ (if cfg.enableGuiPkgs then guiPkgs ++ cfg.extraGuiPkgs else [])
|
||||||
++ (if cfg.enableDevPkgs then devPkgs else [])
|
++ (if cfg.enableDevPkgs then devPkgs else [])
|
||||||
;
|
;
|
||||||
type = types.listOf (types.either types.package types.attrs);
|
type = types.listOf toPkgSpec;
|
||||||
description = "generated from other config options";
|
description = "generated from other config options";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
environment.systemPackages = mkIf cfg.enableSystemPkgs systemPkgs;
|
environment.systemPackages = mkIf cfg.enableSystemPkgs systemPkgs;
|
||||||
|
sane.persist.home.plaintext = concatLists (map (p: p.dir) cfg.enabledUserPkgs);
|
||||||
|
sane.persist.home.private = concatLists (map (p: p.private) cfg.enabledUserPkgs);
|
||||||
# XXX: this might not be necessary. try removing this and cacert.unbundled?
|
# 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/*";
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@@ -1,7 +1,7 @@
|
|||||||
{ config, lib, ... }:
|
{ config, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.sane.impermanence;
|
cfg = config.sane.persist;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
fileSystems."/" = lib.mkIf (cfg.enable && cfg.root-on-tmpfs) {
|
fileSystems."/" = lib.mkIf (cfg.enable && cfg.root-on-tmpfs) {
|
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 {
|
config = mkIf cfg.enable {
|
||||||
# we need this mostly because of the size of duplicity's cache
|
# we need this mostly because of the size of duplicity's cache
|
||||||
sane.impermanence.dirs = [ "/var/lib/duplicity" ];
|
# TODO: move to cryptClearOnBoot and update perms
|
||||||
|
sane.persist.sys.plaintext = [ "/var/lib/duplicity" ];
|
||||||
|
|
||||||
services.duplicity.enable = true;
|
services.duplicity.enable = true;
|
||||||
services.duplicity.targetUrl = "$DUPLICITY_URL";
|
services.duplicity.targetUrl = "$DUPLICITY_URL";
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
# TODO: consider using this library for .zone file generation:
|
||||||
|
# - <https://github.com/kirelagin/dns.nix>
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
let
|
let
|
||||||
cfg = config.sane.services.trust-dns;
|
cfg = config.sane.services.trust-dns;
|
||||||
@@ -49,6 +52,13 @@ let
|
|||||||
}) cfg.zones
|
}) 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
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
@@ -88,37 +98,37 @@ in
|
|||||||
};
|
};
|
||||||
inet = {
|
inet = {
|
||||||
SOA = mkOption {
|
SOA = mkOption {
|
||||||
type = types.attrsOf (types.listOf types.str);
|
type = types.attrsOf (listOrUnit types.str);
|
||||||
description = "Start of Authority record(s)";
|
description = "Start of Authority record(s)";
|
||||||
default = {};
|
default = {};
|
||||||
};
|
};
|
||||||
A = mkOption {
|
A = mkOption {
|
||||||
type = types.attrsOf (types.listOf types.str);
|
type = types.attrsOf (listOrUnit types.str);
|
||||||
description = "IPv4 address record(s)";
|
description = "IPv4 address record(s)";
|
||||||
default = {};
|
default = {};
|
||||||
};
|
};
|
||||||
CNAME = mkOption {
|
CNAME = mkOption {
|
||||||
type = types.attrsOf (types.listOf types.str);
|
type = types.attrsOf (listOrUnit types.str);
|
||||||
description = "canonical name record(s)";
|
description = "canonical name record(s)";
|
||||||
default = {};
|
default = {};
|
||||||
};
|
};
|
||||||
MX = mkOption {
|
MX = mkOption {
|
||||||
type = types.attrsOf (types.listOf types.str);
|
type = types.attrsOf (listOrUnit types.str);
|
||||||
description = "mail exchanger record(s)";
|
description = "mail exchanger record(s)";
|
||||||
default = {};
|
default = {};
|
||||||
};
|
};
|
||||||
NS = mkOption {
|
NS = mkOption {
|
||||||
type = types.attrsOf (types.listOf types.str);
|
type = types.attrsOf (listOrUnit types.str);
|
||||||
description = "name server record(s)";
|
description = "name server record(s)";
|
||||||
default = {};
|
default = {};
|
||||||
};
|
};
|
||||||
SRV = mkOption {
|
SRV = mkOption {
|
||||||
type = types.attrsOf (types.listOf types.str);
|
type = types.attrsOf (listOrUnit types.str);
|
||||||
description = "service record(s)";
|
description = "service record(s)";
|
||||||
default = {};
|
default = {};
|
||||||
};
|
};
|
||||||
TXT = mkOption {
|
TXT = mkOption {
|
||||||
type = types.attrsOf (types.listOf types.str);
|
type = types.attrsOf (listOrUnit types.str);
|
||||||
description = "text record(s)";
|
description = "text record(s)";
|
||||||
default = {};
|
default = {};
|
||||||
};
|
};
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
}
|
12
pkgs/sane-scripts/src/sane-shutdown
Executable file
12
pkgs/sane-scripts/src/sane-shutdown
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
target="$1"
|
||||||
|
host="$(hostname)"
|
||||||
|
if [ "$host" = "$target" ]
|
||||||
|
then
|
||||||
|
sudo shutdown now
|
||||||
|
else
|
||||||
|
echo "WRONG MACHINE. you're on $host."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
sudo systemctl stop matrix-appservice-irc mx-puppet-discord
|
sudo systemctl stop matrix-appservice-irc mx-puppet-discord
|
||||||
sudo systemctl stop pleroma gitea matrix-synapse jellyfin transmission jackett
|
sudo systemctl stop pleroma gitea matrix-synapse jellyfin transmission jackett
|
||||||
|
sudo systemctl stop ejabberd goaccess i2p kiwix-serve navidrome
|
||||||
# TODO: stop the freshrss timer
|
# TODO: stop the freshrss timer
|
||||||
sudo systemctl stop phpfpm-freshrss
|
sudo systemctl stop phpfpm-freshrss
|
||||||
sudo systemctl stop dovecot2 opendkin postfix
|
sudo systemctl stop dovecot2 opendkin postfix
|
||||||
@@ -8,4 +9,5 @@ sudo systemctl stop nginx
|
|||||||
sudo systemctl stop postgresql
|
sudo systemctl stop postgresql
|
||||||
sudo systemctl stop duplicity.timer
|
sudo systemctl stop duplicity.timer
|
||||||
sudo systemctl stop duplicity
|
sudo systemctl stop duplicity
|
||||||
|
sudo systemctl stop trust-dns
|
||||||
sudo systemctl stop wireguard-wg0
|
sudo systemctl stop wireguard-wg0
|
||||||
|
@@ -2,18 +2,19 @@
|
|||||||
# initializes the default libsecret keyring (used by gnome-keyring) if not already initialized.
|
# initializes the default libsecret keyring (used by gnome-keyring) if not already initialized.
|
||||||
# this initializes it to be plaintext/unencrypted.
|
# this initializes it to be plaintext/unencrypted.
|
||||||
|
|
||||||
if [ -f ~/.local/share/keyrings/default ]
|
ringdir=/home/colin/private/.local/share/keyrings
|
||||||
|
if test -f "$ringdir/default"
|
||||||
then
|
then
|
||||||
echo 'keyring already initialized: not doing anything'
|
echo 'keyring already initialized: not doing anything'
|
||||||
exit 0
|
else
|
||||||
|
keyring="$ringdir/Default_keyring.keyring"
|
||||||
|
|
||||||
|
echo 'initializing default user keyring:' "$keyring.new"
|
||||||
|
echo '[keyring]' > "$keyring.new"
|
||||||
|
echo 'display-name=Default keyring' >> "$keyring.new"
|
||||||
|
echo 'lock-on-idle=false' >> "$keyring.new"
|
||||||
|
echo 'lock-after=false' >> "$keyring.new"
|
||||||
|
chown colin:users "$keyring.new"
|
||||||
|
# closest to an atomic update we can achieve
|
||||||
|
mv "$keyring.new" "$keyring" && echo -n "Default_keyring" > "$ringdir/default"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
keyring=~/.local/share/keyrings/Default_keyring.keyring
|
|
||||||
|
|
||||||
echo 'initializing default user keyring:' "$keyring.new"
|
|
||||||
echo '[keyring]' > "$keyring.new"
|
|
||||||
echo 'display-name=Default keyring' >> "$keyring.new"
|
|
||||||
echo 'lock-on-idle=false' >> "$keyring.new"
|
|
||||||
echo 'lock-after=false' >> "$keyring.new"
|
|
||||||
# closest to an atomic update we can achieve
|
|
||||||
mv "$keyring.new" "$keyring" && echo -n "Default_keyring" > ~/.local/share/keyrings/default
|
|
||||||
|
@@ -1,25 +1,37 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# usage: install-bluetooth <source_dir> <dest_dir>
|
# usage: install-bluetooth <source_dir> <destdir>
|
||||||
# source_dir contains plain-text files of any filename.
|
# source_dir contains plain-text files of any filename.
|
||||||
# for each file, this extracts the MAC and creates a symlink in dest_dir which
|
# for each file, this extracts the MAC and creates a symlink in destdir which
|
||||||
# points to the original file, using the MAC name as file path
|
# points to the original file, using the MAC name as file path
|
||||||
#
|
#
|
||||||
# bluetooth connection structure is /var/lib/bluetooth/<HOST_MAC>/<DEVICE_MAX>/{attributes,info}
|
# bluetooth connection structure is /var/lib/bluetooth/<HOST_MAC>/<DEVICE_MAX>/{attributes,info}
|
||||||
#
|
# bluetoothd/main.conf options can be found here:
|
||||||
set -ex
|
# - <https://pythonhosted.org/BT-Manager/config.html>
|
||||||
|
# can be set via nixos' `hardware.bluetooth.settings`
|
||||||
|
|
||||||
src_dir="$1"
|
srcdir="$1"
|
||||||
dest_dir="$2"
|
destdir="$2"
|
||||||
|
|
||||||
if [ "x$dest_dir" = "x" ]
|
if [ "x$destdir" = "x" ]
|
||||||
then
|
then
|
||||||
|
devmac=$(cat /sys/kernel/debug/bluetooth/hci0/identity | cut -f 1 -d' ' | tr "a-z" "A-Z")
|
||||||
# default to the first MAC address on the host
|
# default to the first MAC address on the host
|
||||||
dest_dir="/var/lib/bluetooth/$(ls /var/lib/bluetooth)"
|
destdir="/var/lib/bluetooth/$devmac"
|
||||||
|
test -d "$destdir" || mkdir "$destdir" || test -d "$destdir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for f in $(ls "$src_dir")
|
for f in $(ls "$srcdir")
|
||||||
do
|
do
|
||||||
mac=$(sed -rn 's/# MAC=(.*)/\1/p' "$src_dir/$f")
|
mac=$(sed -rn 's/# MAC=(.*)/\1/p' "$srcdir/$f")
|
||||||
mkdir -p "$dest_dir/$mac"
|
condir="$destdir/$mac"
|
||||||
ln -sf "$src_dir/$f" "$dest_dir/$mac/info"
|
if ! test -f "$condir/info"
|
||||||
|
then
|
||||||
|
# don't *overwrite* pairings. instead, only copy the device data if the host doesn't yet know about it.
|
||||||
|
# unfortunately, it seems that for most BT devices i can't share link keys across hosts.
|
||||||
|
# perhaps i could using `bdaddr` to force a shared host MAC across all hosts, but that doesn't work for all manufacturers.
|
||||||
|
# instead, my bluetooth "secrets" are mostly just a list of MACs i want a host to trust.
|
||||||
|
mkdir "$condir"
|
||||||
|
cp "$srcdir/$f" "$condir/info"
|
||||||
|
touch "$condir/attributes"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"data": "ENC[AES256_GCM,data:639/SycbUOC4kwBAJvk2sQCBfnXZrYDzucXFXysOW8DOPU7nZwsdWKmB+41zvLg3vS5GyPTzMco/EaytPbGj+HvRqyKEYCJvBY/ZOoPpAZKltqMkkIS449OFy3vuOS46TPLQ1c5AgsBnHuSFrGGcVXRs+yP1A4rRcn5Au3Z7hPujZA/W3lUR+/Cp4UYc2tk1w2kpWD/SXLqkSX2XTRNPXb1rOva6xxllUiFuG7Yt1a7KfjrkaF4GJK5TNWgGVrY1N7IKzC75Sh1x5wvumF7A1mwLE1I+sa55Sn9qtGsmWwfsDBN+9SRg7uCUipTCrAEAgJK07QdN5P0ZFrQlxqNGn0i5BvKSxn+GK+f7xJIHjb+sexQgNMFQ56V83Zej8U5HQ9vyrx7EHpE44UneUQQ8FkckQVWyhSGs3MVSkfbznb3L2WnOCEogb0xft56pXPOG/K4q7uLkOwUtgcq0Jlf+gZZmuXRoVnNQc//o0u296CF8AFnxA/MJJc67bkONITjlekB7f23NFoywhzyX4RYjjbflRkMF8BRu0RDuynGu0xLZLLlK8QM4uJfpPyH8L2ThiKRbYO7OGaKCYJy6fYx+ViUVsRf3hpHMiDn8yGHxrqABOTv6HSIZvIKo3G4Kn5iF/GPxWQISTZxE7YdYhgUf61UpZEfkgw==,iv:jqWb8k8f8jKscWPwcZy9o9QmOJKG38m9ukbeBDX3IN8=,tag:vZh6J2mtUhaoiwpn17l80g==,type:str]",
|
"data": "ENC[AES256_GCM,data:WbIbrAEaBV1ye3Vzxt5DybxNzsQgqsGmRRG8GWibq3DTv8zeJ0hBAWKJHA6BZvK3gxoAOCr+jEsKq/hIcaZkth0G+otQI8UWd8aUC0O+MQMT7bXXu7g+uI/VwofZTu/+AwceBfnyPdfWcmORobbzWABaNxT0OC4tZ+369u37QRmp9Nny9sQALaXW5cUS2kK2ajouWhXaXUt0hVhDTYULDWm7tNaqEFG1sTpg75AGlXfDcvRUNP+FHs1ISfNPgHf2QrPDD48xOCT/weNAy1xIMqS8Ecns5XGRt2sitTz0O5Lz2B5b436GdlsxcGfFv+RATpLGY7DtEirmfPyNybRbxdIrV3oceJO8op2TRmaDR9D0zhF2vtPb470z8lCrU7uyooc+b9mUx2tX3b3FLQy9ukSS2QG6TleVGxznrqO2XYFvse1OZMVDFdXjhn0CaiCHc0f51SNEunS5gLMFpcBvI20FHntT9uj7nwV4yCOGVlWAziyUtK7w/mbcACvOWhwjrN3D4LXQ9QPGSuWBqni9Dsk7de+uArqYP//zqSotJbv1qS1TpB7QYrlJf9VaJ/eLxwAAM7AxEt2So5sNbreh+zZi5cAhaudyEb1fNSf7suACqnHk1BhXoQtRh93ngetMmneQlsXMZGM=,iv:6kYaqB/TMZdvns3Gv0nO0yp+LQBGSS2xn979FfXffvc=,tag:QzfQi4YXhGWLjPIWng1HAg==,type:str]",
|
||||||
"sops": {
|
"sops": {
|
||||||
"kms": null,
|
"kms": null,
|
||||||
"gcp_kms": null,
|
"gcp_kms": null,
|
||||||
@@ -39,8 +39,8 @@
|
|||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzYnMxbDNUR2xyV3B5VzFQ\nM1IxQzV5OXM5L1VYdFRYWUt0cWl4ZUdsQVNJCmRjUjdPMmhoaEFmUUxrVmJCRlFl\nNzZqY3p0YUF3T2lYdysvakx4WVg0bFUKLS0tIFFlazJzb3hmVXNyUU5leUFKL3p0\nNlN0TGxVbGtoUHFtK3hBS2RiYUViVFEKii4w04zeDD6HWURzmAhJdxNdNmQgsPw/\nawI6HSVbbmEGXyL23Pe0oultY8k/ZVE4oHRKBkHh00XoCZM/Ye6neA==\n-----END AGE ENCRYPTED FILE-----\n"
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzYnMxbDNUR2xyV3B5VzFQ\nM1IxQzV5OXM5L1VYdFRYWUt0cWl4ZUdsQVNJCmRjUjdPMmhoaEFmUUxrVmJCRlFl\nNzZqY3p0YUF3T2lYdysvakx4WVg0bFUKLS0tIFFlazJzb3hmVXNyUU5leUFKL3p0\nNlN0TGxVbGtoUHFtK3hBS2RiYUViVFEKii4w04zeDD6HWURzmAhJdxNdNmQgsPw/\nawI6HSVbbmEGXyL23Pe0oultY8k/ZVE4oHRKBkHh00XoCZM/Ye6neA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lastmodified": "2022-12-03T10:23:14Z",
|
"lastmodified": "2023-01-07T11:04:42Z",
|
||||||
"mac": "ENC[AES256_GCM,data:KBm0rXwAGPa0ZkqGI9K3rW5B4vJ1FLmITa8xV5WR1SG2MlSqvCqSj4Qe5kxcIc3AqqHF2W+LDaJ0f1fXOCVqWRe1mi/LJyYgPERL5Hn3iOHty9g984Q/QSGvH13O7eY/Fuk2h0mpIX4pOhdpW74qlp1zYDXqUswsKW7ERTTRf6E=,iv:maE+9/OgdgYNX4F/MrzIpJr+/XXyFSayC1YX382oc2Y=,tag:NmrKXA9AjnoTXrQThnvxvg==,type:str]",
|
"mac": "ENC[AES256_GCM,data:QiNqZSB5WIVroTQKWxt73NLGvv13waePyMcQ3OJaecaOZQiXGhuq9Ojwnk+I2DSs7X8Nv10VilHk97kYNgTjsNdWmXHqtSY0LKbbMoJpzPoF42MCPSv8g5tLOnIR095Ihu8Ntw+FdOsl0rqa9ipqJFFswOpGI/xamcsLtpRnQnQ=,iv:i4YCULu9YJR5zLomeAYpzvFG7SB9x+4wWPhaiFGlTQQ=,tag:xLbmIhg7hPZnHvQVhbgDpQ==,type:str]",
|
||||||
"pgp": null,
|
"pgp": null,
|
||||||
"unencrypted_suffix": "_unencrypted",
|
"unencrypted_suffix": "_unencrypted",
|
||||||
"version": "3.7.3"
|
"version": "3.7.3"
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"data": "ENC[AES256_GCM,data:ou55VGY+beKMouNj4qQaBOAZK/5UKu6A521lNW2i0KlSmgJ8qQ501lesy0bEmDkZqqhluP8XE5FZLwEXvqqMh/TBuN1OkCsQis53/M1s0g==,iv:Ir5uD1P8OlHlcjGCHVkUHr0AjoXzd7kOcAeajo66hUE=,tag:m+rReK9o/8TG4LBkNN1ZZQ==,type:str]",
|
"data": "ENC[AES256_GCM,data:OaFr+OOaBxi0PaApOYLUjJ0NgD5ABBQOaf6KpR9rheE2d1pQNa0jqnD4/ttqJrq8JjZT2Y6GDSwM5gPM,iv:TuyQPPDXM8cJU/GhJpdvxwB8+v6JavHcA+vmLHA3/74=,tag:V6RTKw6Cot4B4sK1JcRGmA==,type:str]",
|
||||||
"sops": {
|
"sops": {
|
||||||
"kms": null,
|
"kms": null,
|
||||||
"gcp_kms": null,
|
"gcp_kms": null,
|
||||||
@@ -39,8 +39,8 @@
|
|||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAraXJQOHR6ZzE5TjNQYmpB\nSStEQS9mcUpMSXlFQ05DcllFSjNOT1pWdVJZCmtSL3FkZ2Q1cU1Fc1dZbG13eXJC\nTXJkN0NzWTlDOEFMRGNQUG5HQUNUVDgKLS0tIGRwcmVxS0lNQ09GdmxKY2pkQ2Yz\nSkpZam1ZQUN1L1FZZ010ZlhUV1N4VlkKqsFAE+xZ24IMzIFjbsgANdjiGwVZk5rq\n66y00bjw+uj6WOwQuE1I9WcYDhCXEUQB9u4Q+hzejaFzCJ90N/WF4w==\n-----END AGE ENCRYPTED FILE-----\n"
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAraXJQOHR6ZzE5TjNQYmpB\nSStEQS9mcUpMSXlFQ05DcllFSjNOT1pWdVJZCmtSL3FkZ2Q1cU1Fc1dZbG13eXJC\nTXJkN0NzWTlDOEFMRGNQUG5HQUNUVDgKLS0tIGRwcmVxS0lNQ09GdmxKY2pkQ2Yz\nSkpZam1ZQUN1L1FZZ010ZlhUV1N4VlkKqsFAE+xZ24IMzIFjbsgANdjiGwVZk5rq\n66y00bjw+uj6WOwQuE1I9WcYDhCXEUQB9u4Q+hzejaFzCJ90N/WF4w==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lastmodified": "2022-10-08T03:39:12Z",
|
"lastmodified": "2023-01-07T03:06:02Z",
|
||||||
"mac": "ENC[AES256_GCM,data:4Rr2iqmzLtE9i45Hn10wuf8unKt+YNAYTF3RWwEW1AjN+pF7ZvwMbrUutRCb6uMxCQUyNl+adfFRu8Xae0/SqFBfdAPxzeQZGrBjb384seLrNS0XyUacfdoSCczrRUF8+F3mIHetaJCd2jOpoh5HotoSN3fx+nZNhD+56XmJBr0=,iv:YlDMimhG+a9Wzq0ZN0tnZ1gH69e7olyHGWhIV2/4K64=,tag:GjVzbNa/NdzVmdPyE5etXw==,type:str]",
|
"mac": "ENC[AES256_GCM,data:L3wY2ZdR1ASbLbKXiipWfBiQ5cumItuiL1+TwTJhU5ZtxLe6SMUyhckvuX8hczlFPUlJQJDCwpgVBs9C6GRAU45jzHYmpcfF30auiRT2dF/2doH9yiYZoF7JtbTas0Kvt1yxlPfuTi5mFuJGAKDOw6+a5ayQHYlK3/RxAUn0yPc=,iv:U/vlmvI1l4u92eUDXRphS0tscLOlWorOdmT7wDwGbAM=,tag:bQayboRgsMKT6akDq+rzQw==,type:str]",
|
||||||
"pgp": null,
|
"pgp": null,
|
||||||
"unencrypted_suffix": "_unencrypted",
|
"unencrypted_suffix": "_unencrypted",
|
||||||
"version": "3.7.3"
|
"version": "3.7.3"
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"data": "ENC[AES256_GCM,data:6DbXAd9wFIdEBBdiesGiJ8ddyQ5p65XpnitIqItIBcR6taZ20HwrwAmCmDbsxPJ0FSDUnIzzsEdN3ad44e4tQW/o8iLNqRBMMB2rXLJyOiOFDg==,iv:ocfbDt0nLB+1CGSMh82XzLZEDHV3tZD6qCKDR//nIk8=,tag:S2hJR3rK2G6WJCQTBO61sw==,type:str]",
|
"data": "ENC[AES256_GCM,data:xyD4tqHo7IUxPvJnZi0tiFXeTXVCnFJlCTtz1YUxcDh6pXYhUmsxudDM9/V/1FsUQHCrq/TtccdjPrPWChv+ty1/dIdUeGNyEZ73nOUamahmvfEtvXuTP0KOLy68BQHRImkomXlqaYRamEyPkMwaqUABQ3XD5UwwFZWZ+mhdbQsOPxUHpgFz2kL1nYPlueQG1XzSy+ZhWH5GPfu3GRN2XNOMeTlKhy9q,iv:yrwtQz+K9UHIvPT099uyJFrnAvfVzqYrT8mTEYUaJPQ=,tag:fYd4b7wwbfKEylISUsyajQ==,type:str]",
|
||||||
"sops": {
|
"sops": {
|
||||||
"kms": null,
|
"kms": null,
|
||||||
"gcp_kms": null,
|
"gcp_kms": null,
|
||||||
@@ -39,8 +39,8 @@
|
|||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByTXlSVVhxczNIRGIwZEdW\nSm14aFYzTEFoSGt2SzZKc21OaVpTVmNrSXd3Cis5UTRQMzJSaVdwTkdrQmxLSlRp\nUXBGZ0huUUJnVHVHaUtyUGI4cXdrTVkKLS0tIHVWeEVsOXRRTFRZalI4bWdwcy9a\nV1EwTHhqemRFVHlZR3N4SGRibDhWZzAKVfqqfrKPWtxnIgdvgo7yTe24dleOZAIZ\nZKFCZ3NqibMaRI324E2PrJSAij0lNJyulxpLx4chA7yN84v4vuQToA==\n-----END AGE ENCRYPTED FILE-----\n"
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByTXlSVVhxczNIRGIwZEdW\nSm14aFYzTEFoSGt2SzZKc21OaVpTVmNrSXd3Cis5UTRQMzJSaVdwTkdrQmxLSlRp\nUXBGZ0huUUJnVHVHaUtyUGI4cXdrTVkKLS0tIHVWeEVsOXRRTFRZalI4bWdwcy9a\nV1EwTHhqemRFVHlZR3N4SGRibDhWZzAKVfqqfrKPWtxnIgdvgo7yTe24dleOZAIZ\nZKFCZ3NqibMaRI324E2PrJSAij0lNJyulxpLx4chA7yN84v4vuQToA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lastmodified": "2022-12-26T09:13:29Z",
|
"lastmodified": "2023-01-02T12:37:44Z",
|
||||||
"mac": "ENC[AES256_GCM,data:/bKnjVzoiyqz+HD+rT76tQiT8+bqmJfEonFK9z+c+6uDFGCLeockZ5WIHcULU3VU1kfgmkr9R8vlArIYN5vrEm8g6jS8iQgcehjGiqbF5KQHDIarHzBJdqa3ca3G98BF3HlaMYR/hpWquR7sLBcsayf6LcHdGCqiP5TnERd0TzY=,iv:TanC7jAdbH1UXNFbNN6dAOL4hiJY1U0GRWdPmaiY/Sg=,tag:gNsXTb2BTZiOhBoQmcJVDw==,type:str]",
|
"mac": "ENC[AES256_GCM,data:VXycD0JG1nPGFefI6gsG2zQh7NjG+bKCyMjyfWkRJyjomJlGaLMDF/8iUAhRHGgBuAmhZuu8nyZHky8F9CEgtktpY4/b/b3eH4NVuWlQ04MrpO24RrRgwyN+WrtG4FWEnbA4QtOLu64pTMQlQgRseL30u+RNQ6eT+ycx71/6r6A=,iv:YtRe37O4Zht148zbjplIKbUfVvghYDH2ErDbKJN2qdc=,tag:AKjzatu7Iy49Dg8lkwiWpA==,type:str]",
|
||||||
"pgp": null,
|
"pgp": null,
|
||||||
"unencrypted_suffix": "_unencrypted",
|
"unencrypted_suffix": "_unencrypted",
|
||||||
"version": "3.7.3"
|
"version": "3.7.3"
|
||||||
|
Reference in New Issue
Block a user