Compare commits
69 Commits
staging/im
...
staging/ni
Author | SHA1 | Date | |
---|---|---|---|
1a0f05bfd6 | |||
c18dd9636d | |||
0977721af5 | |||
122d3cd7e4 | |||
cd5f8054c0 | |||
3db388b105 | |||
2ba6116f10 | |||
592d17b725 | |||
4d9c15f9b8 | |||
abced7dd0d | |||
5c42365912 | |||
247ad326b2 | |||
170008f345 | |||
2c48e61854 | |||
f89f756489 | |||
c0da19951b | |||
5fb67306e4 | |||
5533b586d7 | |||
68c2eb7363 | |||
fd79026366 | |||
a76471cb1f | |||
c94b8299a6 | |||
175bc0709f | |||
7b02477486 | |||
d7c8638fea | |||
9d7d1acc80 | |||
787857d27f | |||
9c248a8a31 | |||
829680fb00 | |||
a9ee26388c | |||
2960b895b6 | |||
933063115b | |||
afe684ca2c | |||
93f1411522 | |||
01e44c1f7f | |||
618e9bd2fa | |||
fbc39d0584 | |||
2d7b3750cd | |||
e6ccd2e4f7 | |||
d4bf491e9c | |||
5a2bbcce3b | |||
327e6b536f | |||
bace7403e7 | |||
57f5521ef3 | |||
9e32211c12 | |||
edf6bd4455 | |||
a9a14786f9 | |||
eade5fe16e | |||
be222c1d70 | |||
88a33dd5de | |||
875e923197 | |||
54dd643cf0 | |||
3c726f148b | |||
e225e2e704 | |||
cf0bf8190e | |||
b8f7f68d4c | |||
7a3aae8c97 | |||
89e519810d | |||
0e920230ba | |||
6ffae00e17 | |||
be19985440 | |||
f7e3e7294a | |||
d745e3c1ee | |||
c1890ce82b | |||
53a0b621d8 | |||
aeb2f63d65 | |||
528ffdb58e | |||
b6887b305e | |||
08dfc80c98 |
40
flake.lock
generated
40
flake.lock
generated
@@ -36,21 +36,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"impermanence": {
|
||||
"locked": {
|
||||
"lastModified": 1668668915,
|
||||
"narHash": "sha256-QjY4ZZbs9shwO4LaLpvlU2bO9J1juYhO9NtV3nrbnYQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "impermanence",
|
||||
"rev": "5df9108b346f8a42021bf99e50de89c9caa251c3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "impermanence",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"mobile-nixos": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
@@ -69,11 +54,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1671722432,
|
||||
"narHash": "sha256-ojcZUekIQeOZkHHzR81st7qxX99dB1Eaaq6PU5MNeKc=",
|
||||
"lastModified": 1672791794,
|
||||
"narHash": "sha256-mqGPpGmwap0Wfsf3o2b6qHJW1w2kk/I6cGCGIU+3t6o=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "652e92b8064949a11bc193b90b74cb727f2a1405",
|
||||
"rev": "9813adc7f7c0edd738c6bdd8431439688bb0cb3d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -84,11 +69,11 @@
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1671883564,
|
||||
"narHash": "sha256-C15oAtyupmLB3coZY7qzEHXjhtUx/+77olVdqVMruAg=",
|
||||
"lastModified": 1672844754,
|
||||
"narHash": "sha256-o26WabuHABQsaHxxmIrR3AQRqDFUEdLckLXkVCpIjSU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "dac57a4eccf1442e8bf4030df6fcbb55883cb682",
|
||||
"rev": "e9ade2c8240e00a4784fac282a502efff2786bdc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -99,11 +84,11 @@
|
||||
},
|
||||
"nixpkgs-stable_2": {
|
||||
"locked": {
|
||||
"lastModified": 1671923641,
|
||||
"narHash": "sha256-flPauiL5UrfRJD+1oAcEefpEIUqTqnyKScWe/UUU+lE=",
|
||||
"lastModified": 1672500394,
|
||||
"narHash": "sha256-yzwBzCoeRBoRzm7ySHhm72kBG0QjgFalLz2FY48iLI4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "939c05a176b8485971463c18c44f48e56a7801c9",
|
||||
"rev": "feda52be1d59f13b9aa02f064b4f14784b9a06c8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -116,7 +101,6 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"home-manager": "home-manager",
|
||||
"impermanence": "impermanence",
|
||||
"mobile-nixos": "mobile-nixos",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-stable": "nixpkgs-stable",
|
||||
@@ -132,11 +116,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1671937829,
|
||||
"narHash": "sha256-YtaNB+mLw0d67JFYNjRWM+/AL3JCXuD/DGlnTlyX1tY=",
|
||||
"lastModified": 1672543202,
|
||||
"narHash": "sha256-nlCUtcIZxaBqUBG1GyaXhZmfyG5WK4e6LqypP8llX9E=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "855b8d51fc3991bd817978f0f093aa6ae0fae738",
|
||||
"rev": "b35586cc5abacd4eba9ead138b53e2a60920f781",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@@ -18,7 +18,6 @@
|
||||
url = "github:Mic92/sops-nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
impermanence.url = "github:nix-community/impermanence";
|
||||
uninsane = {
|
||||
url = "git+https://git.uninsane.org/colin/uninsane";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
@@ -32,7 +31,6 @@
|
||||
mobile-nixos,
|
||||
home-manager,
|
||||
sops-nix,
|
||||
impermanence,
|
||||
uninsane
|
||||
}: let
|
||||
patchedPkgs = system: nixpkgs.legacyPackages.${system}.applyPatches {
|
||||
@@ -54,12 +52,10 @@
|
||||
in (nixosSystem {
|
||||
# by default the local system is the same as the target, employing emulation when they differ
|
||||
system = target;
|
||||
specialArgs = { inherit mobile-nixos home-manager impermanence; };
|
||||
modules = [
|
||||
./modules
|
||||
(import ./hosts/instantiate.nix name)
|
||||
home-manager.nixosModule
|
||||
impermanence.nixosModule
|
||||
sops-nix.nixosModules.sops
|
||||
{
|
||||
nixpkgs.overlays = [
|
||||
@@ -77,10 +73,6 @@
|
||||
# gocryptfs = cross.gocryptfs;
|
||||
|
||||
# pinned packages:
|
||||
# 2022/12/13: grpc does not build on aarch64-linux. https://github.com/NixOS/nixpkgs/issues/205887
|
||||
grpc = stable.grpc;
|
||||
# depends on grpc, so pinned.
|
||||
duplicity = stable.duplicity;
|
||||
})
|
||||
];
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@
|
||||
sane.packages.enableConsolePkgs = true;
|
||||
sane.packages.enableSystemPkgs = true;
|
||||
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
"/var/log"
|
||||
"/var/backup" # for e.g. postgres dumps
|
||||
# TODO: move elsewhere
|
||||
|
@@ -7,7 +7,10 @@ let
|
||||
# see nixpkgs/nixos/modules/services/networking/dhcpcd.nix
|
||||
hasDHCP = config.networking.dhcpcd.enable &&
|
||||
(config.networking.useDHCP || any (i: i.useDHCP == true) (attrValues config.networking.interfaces));
|
||||
|
||||
mkSymlink = target: {
|
||||
symlink.target = target;
|
||||
wantedBeforeBy = [ "multi-user.target" ];
|
||||
};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
@@ -28,7 +31,7 @@ in
|
||||
isNormalUser = true;
|
||||
home = "/home/colin";
|
||||
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
|
||||
# in /var/lib/nixos/auto-subuid-map and i don't want that.
|
||||
@@ -54,46 +57,68 @@ in
|
||||
shell = pkgs.zsh;
|
||||
openssh.authorizedKeys.keys = builtins.attrValues (import ../../modules/pubkeys.nix).users;
|
||||
|
||||
# mount encrypted stuff at login
|
||||
# some other nix pam users:
|
||||
# - <https://github.com/g00pix/nixconf/blob/32c04f6fa843fed97639dd3f09e157668d3eea1f/profiles/sshfs.nix>
|
||||
# - <https://github.com/lourkeur/distro/blob/11173454c6bb50f7ccab28cc2c757dca21446d1d/nixos/profiles/users/louis-full.nix>
|
||||
# - <https://github.com/dnr/sample-nix-code/blob/03494480c1fae550c033aa54fd96aeb3827761c5/nixos/laptop.nix>
|
||||
pamMount = {
|
||||
# mount encrypted stuff at login
|
||||
# requires that login password == fs encryption password
|
||||
fstype = "fuse";
|
||||
path = "gocryptfs#/nix/persist/home/colin/private";
|
||||
# path = "${pkgs.gocryptfs}/bin/gocryptfs#/nix/persist/home/colin/private";
|
||||
# fstype = "fuse.gocryptfs";
|
||||
# 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
|
||||
pamMount = let
|
||||
priv = config.fileSystems."/home/colin/private";
|
||||
in {
|
||||
fstype = priv.fsType;
|
||||
path = priv.device;
|
||||
mountpoint = priv.mountPoint;
|
||||
options = builtins.concatStringsSep "," priv.options;
|
||||
};
|
||||
};
|
||||
|
||||
# required for PAM to find gocryptfs
|
||||
security.pam.mount.additionalSearchPaths = [ pkgs.gocryptfs ];
|
||||
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 = [
|
||||
# cache is probably too big to fit on the tmpfs
|
||||
# { directory = ".cache"; encryptedClearOnBoot = true; }
|
||||
{ directory = ".cache/mozilla"; encryptedClearOnBoot = true; }
|
||||
# ensure ~ perms are known to sane.fs module.
|
||||
# TODO: this is generic enough to be lifted up into sane.fs itself.
|
||||
sane.fs."/home/colin".dir.acl = {
|
||||
user = "colin";
|
||||
group = config.users.users.colin.group;
|
||||
mode = config.users.users.colin.homeMode;
|
||||
};
|
||||
|
||||
sane.impermanence.dirs.home.plaintext = [
|
||||
"archive"
|
||||
"dev"
|
||||
# TODO: records should be private
|
||||
"records"
|
||||
"ref"
|
||||
"tmp"
|
||||
"use"
|
||||
"Music"
|
||||
"Pictures"
|
||||
"Videos"
|
||||
|
||||
".cargo"
|
||||
".rustup"
|
||||
# TODO: move this to ~/private!
|
||||
".local/share/keyrings"
|
||||
];
|
||||
# TODO: fix this ugly solution that allows moby to have firefox cache not erased every boot.
|
||||
sane.impermanence.dirs.home.cryptClearOnBoot = lib.mkIf (config.networking.hostName != "moby") [
|
||||
# cache is probably too big to fit on the tmpfs
|
||||
# ".cache"
|
||||
config.sane.web-browser.cacheDir
|
||||
];
|
||||
|
||||
sane.impermanence.dirs = mkIf cfg.guest.enable [
|
||||
{ user = "guest"; group = "users"; directory = "/home/guest"; }
|
||||
# convenience
|
||||
sane.fs."/home/colin/knowledge" = mkSymlink "/home/colin/private/knowledge";
|
||||
sane.fs."/home/colin/nixos" = mkSymlink "/home/colin/dev/nixos";
|
||||
sane.fs."/home/colin/Videos/servo" = mkSymlink "/mnt/servo-media/Videos";
|
||||
sane.fs."/home/colin/Videos/servo-incomplete" = mkSymlink "/mnt/servo-media/incomplete";
|
||||
sane.fs."/home/colin/Music/servo" = mkSymlink "/mnt/servo-media/Music";
|
||||
|
||||
# used by password managers, e.g. unix `pass`
|
||||
sane.fs."/home/colin/.password-store" = mkSymlink "/home/colin/knowledge/secrets/accounts";
|
||||
|
||||
sane.impermanence.dirs.sys.plaintext = mkIf cfg.guest.enable [
|
||||
# intentionally allow other users to write to the guest folder
|
||||
{ directory = "/home/guest"; user = "guest"; group = "users"; mode = "0775"; }
|
||||
];
|
||||
users.users.guest = mkIf cfg.guest.enable {
|
||||
isNormalUser = true;
|
||||
|
@@ -52,7 +52,7 @@
|
||||
remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play
|
||||
dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server
|
||||
};
|
||||
sane.impermanence.home-dirs = [
|
||||
sane.impermanence.dirs.home.plaintext = [
|
||||
".steam"
|
||||
".local/share/Steam"
|
||||
];
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{ config, pkgs, lib, mobile-nixos, ... }:
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
imports = [
|
||||
./firmware.nix
|
||||
@@ -24,8 +24,11 @@
|
||||
};
|
||||
|
||||
# usability compromises
|
||||
sane.impermanence.home-dirs = [
|
||||
sane.impermanence.dirs.home.private = [
|
||||
config.sane.web-browser.dotDir
|
||||
config.sane.web-browser.cacheDir
|
||||
];
|
||||
sane.impermanence.dirs.home.plaintext = [
|
||||
".config/pulse" # persist pulseaudio volume
|
||||
];
|
||||
|
||||
|
@@ -27,7 +27,7 @@
|
||||
};
|
||||
|
||||
# slow, external storage (for archiving, etc)
|
||||
fileSystems."/nix/persist/ext" = {
|
||||
fileSystems."/mnt/impermanence/ext" = {
|
||||
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
||||
fsType = "btrfs";
|
||||
options = [
|
||||
@@ -36,27 +36,31 @@
|
||||
];
|
||||
};
|
||||
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.stores."ext" = {
|
||||
origin = "/mnt/impermanence/ext/persist";
|
||||
storeDescription = "external HDD storage";
|
||||
};
|
||||
sane.fs."/mnt/impermanence/ext".mount = {};
|
||||
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: this is overly broad; only need media and share directories to be persisted
|
||||
{ user = "colin"; group = "users"; directory = "/var/lib/uninsane"; }
|
||||
];
|
||||
# direct these media directories to external storage
|
||||
environment.persistence."/nix/persist/ext/persist" = {
|
||||
directories = [
|
||||
({
|
||||
user = "colin";
|
||||
group = "users";
|
||||
mode = "0777";
|
||||
directory = "/var/lib/uninsane/media/Videos";
|
||||
})
|
||||
({
|
||||
user = "colin";
|
||||
group = "users";
|
||||
mode = "0777";
|
||||
directory = "/var/lib/uninsane/media/freeleech";
|
||||
})
|
||||
];
|
||||
};
|
||||
# make sure large media is stored to the HDD
|
||||
sane.impermanence.dirs.sys.ext = [
|
||||
{
|
||||
user = "colin";
|
||||
group = "users";
|
||||
mode = "0777";
|
||||
directory = "/var/lib/uninsane/media/Videos";
|
||||
}
|
||||
{
|
||||
user = "colin";
|
||||
group = "users";
|
||||
mode = "0777";
|
||||
directory = "/var/lib/uninsane/media/freeleech";
|
||||
}
|
||||
];
|
||||
|
||||
# in-memory compressed RAM (seems to be dynamically sized)
|
||||
# zramSwap = {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
# XXX: avatar support works in MUCs but not DMs
|
||||
# lib.mkIf false
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
{ user = "ejabberd"; group = "ejabberd"; directory = "/var/lib/ejabberd"; }
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
@@ -75,33 +75,33 @@
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
|
||||
A."xmpp" = [ "%NATIVE%" ];
|
||||
CNAME."muc.xmpp" = [ "xmpp" ];
|
||||
CNAME."pubsub.xmpp" = [ "xmpp" ];
|
||||
CNAME."upload.xmpp" = [ "xmpp" ];
|
||||
CNAME."vjid.xmpp" = [ "xmpp" ];
|
||||
A."xmpp" = "%NATIVE%";
|
||||
CNAME."muc.xmpp" = "xmpp";
|
||||
CNAME."pubsub.xmpp" = "xmpp";
|
||||
CNAME."upload.xmpp" = "xmpp";
|
||||
CNAME."vjid.xmpp" = "xmpp";
|
||||
|
||||
# _Service._Proto.Name TTL Class SRV Priority Weight Port Target
|
||||
# - <https://xmpp.org/extensions/xep-0368.html>
|
||||
# something's requesting the SRV records for muc.xmpp, so let's include it
|
||||
# nothing seems to request XMPP SRVs for the other records (except @)
|
||||
# lower numerical priority field tells clients to prefer this method
|
||||
SRV."_xmpps-client._tcp.muc.xmpp" = [ "3 50 5223 xmpp" ];
|
||||
SRV."_xmpps-server._tcp.muc.xmpp" = [ "3 50 5270 xmpp" ];
|
||||
SRV."_xmpp-client._tcp.muc.xmpp" = [ "5 50 5222 xmpp" ];
|
||||
SRV."_xmpp-server._tcp.muc.xmpp" = [ "5 50 5269 xmpp" ];
|
||||
SRV."_xmpps-client._tcp.muc.xmpp" = "3 50 5223 xmpp";
|
||||
SRV."_xmpps-server._tcp.muc.xmpp" = "3 50 5270 xmpp";
|
||||
SRV."_xmpp-client._tcp.muc.xmpp" = "5 50 5222 xmpp";
|
||||
SRV."_xmpp-server._tcp.muc.xmpp" = "5 50 5269 xmpp";
|
||||
|
||||
SRV."_xmpps-client._tcp" = [ "3 50 5223 xmpp" ];
|
||||
SRV."_xmpps-server._tcp" = [ "3 50 5270 xmpp" ];
|
||||
SRV."_xmpp-client._tcp" = [ "5 50 5222 xmpp" ];
|
||||
SRV."_xmpp-server._tcp" = [ "5 50 5269 xmpp" ];
|
||||
SRV."_xmpps-client._tcp" = "3 50 5223 xmpp";
|
||||
SRV."_xmpps-server._tcp" = "3 50 5270 xmpp";
|
||||
SRV."_xmpp-client._tcp" = "5 50 5222 xmpp";
|
||||
SRV."_xmpp-server._tcp" = "5 50 5269 xmpp";
|
||||
|
||||
SRV."_stun._udp" = [ "5 50 3478 xmpp" ];
|
||||
SRV."_stun._tcp" = [ "5 50 3478 xmpp" ];
|
||||
SRV."_stuns._tcp" = [ "5 50 5349 xmpp" ];
|
||||
SRV."_turn._udp" = [ "5 50 3478 xmpp" ];
|
||||
SRV."_turn._tcp" = [ "5 50 3478 xmpp" ];
|
||||
SRV."_turns._tcp" = [ "5 50 5349 xmpp" ];
|
||||
SRV."_stun._udp" = "5 50 3478 xmpp";
|
||||
SRV."_stun._tcp" = "5 50 3478 xmpp";
|
||||
SRV."_stuns._tcp" = "5 50 5349 xmpp";
|
||||
SRV."_turn._udp" = "5 50 3478 xmpp";
|
||||
SRV."_turn._tcp" = "5 50 3478 xmpp";
|
||||
SRV."_turns._tcp" = "5 50 5349 xmpp";
|
||||
};
|
||||
|
||||
# TODO: allocate UIDs/GIDs ?
|
||||
|
@@ -14,9 +14,9 @@
|
||||
sops.secrets.freshrss_passwd = {
|
||||
sopsFile = ../../../secrets/servo.yaml;
|
||||
owner = config.users.users.freshrss.name;
|
||||
mode = "400";
|
||||
mode = "0400";
|
||||
};
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
{ user = "freshrss"; group = "freshrss"; directory = "/var/lib/freshrss"; }
|
||||
];
|
||||
|
||||
@@ -57,5 +57,5 @@
|
||||
# the routing is handled by services.freshrss.virtualHost
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."rss" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."rss" = "native";
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "git"; group = "gitea"; directory = "/var/lib/gitea"; }
|
||||
];
|
||||
@@ -85,5 +85,5 @@
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."git" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."git" = "native";
|
||||
}
|
||||
|
@@ -64,5 +64,5 @@
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."sink" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."sink" = "native";
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
lib.mkIf false # i don't actively use ipfs anymore
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "261"; group = "261"; directory = "/var/lib/ipfs"; }
|
||||
];
|
||||
@@ -34,7 +34,7 @@ lib.mkIf false # i don't actively use ipfs anymore
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."ipfs" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."ipfs" = "native";
|
||||
|
||||
# services.ipfs.enable = true;
|
||||
services.kubo.localDiscovery = true;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: mode? we only need this to save Indexer creds ==> migrate to config?
|
||||
{ user = "root"; group = "root"; directory = "/var/lib/jackett"; }
|
||||
];
|
||||
@@ -27,6 +27,6 @@
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."jackett" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."jackett" = "native";
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ lib.mkIf false
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
1900 7359 # DLNA: https://jellyfin.org/docs/general/networking/index.html
|
||||
];
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "jellyfin"; group = "jellyfin"; directory = "/var/lib/jellyfin"; }
|
||||
];
|
||||
@@ -61,7 +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;
|
||||
|
@@ -13,5 +13,5 @@
|
||||
locations."/".proxyPass = "http://127.0.0.1:8013";
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."w" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."w" = "native";
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@
|
||||
# ./irc.nix
|
||||
];
|
||||
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/matrix-synapse"; }
|
||||
];
|
||||
services.matrix-synapse.enable = true;
|
||||
@@ -122,8 +122,8 @@
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||
CNAME."matrix" = [ "native" ];
|
||||
CNAME."web.matrix" = [ "native" ];
|
||||
CNAME."matrix" = "native";
|
||||
CNAME."web.matrix" = "native";
|
||||
};
|
||||
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/mx-puppet-discord"; }
|
||||
];
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: mode?
|
||||
# user and group are both "matrix-appservice-irc"
|
||||
{ user = "993"; group = "992"; directory = "/var/lib/matrix-appservice-irc"; }
|
||||
|
@@ -1,8 +1,11 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
{ user = "navidrome"; group = "navidrome"; directory = "/var/lib/private/navidrome"; }
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: we don't have a static user allocated for navidrome!
|
||||
# the chown would happen too early for us to set static perms
|
||||
"/var/lib/private/navidrome"
|
||||
# { user = "navidrome"; group = "navidrome"; directory = "/var/lib/private/navidrome"; }
|
||||
];
|
||||
services.navidrome.enable = true;
|
||||
services.navidrome.settings = {
|
||||
@@ -22,5 +25,5 @@
|
||||
locations."/".proxyPass = "http://127.0.0.1:4533";
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."music" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."music" = "native";
|
||||
}
|
||||
|
@@ -122,7 +122,7 @@ in
|
||||
|
||||
users.users.acme.uid = config.sane.allocations.acme-uid;
|
||||
users.groups.acme.gid = config.sane.allocations.acme-gid;
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: mode?
|
||||
{ user = "acme"; group = "acme"; directory = "/var/lib/acme"; }
|
||||
{ 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.sopsFile = ../../../secrets/servo.yaml;
|
||||
|
@@ -6,7 +6,7 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "pleroma"; group = "pleroma"; directory = "/var/lib/pleroma"; }
|
||||
];
|
||||
@@ -179,7 +179,7 @@
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."fed" = [ "native" ];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."fed" = "native";
|
||||
|
||||
sops.secrets.pleroma_secrets = {
|
||||
sopsFile = ../../../secrets/servo.yaml;
|
||||
|
@@ -16,7 +16,7 @@ let
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "opendkim"; group = "opendkim"; directory = "/var/lib/opendkim"; }
|
||||
{ user = "root"; group = "root"; directory = "/var/lib/postfix"; }
|
||||
@@ -45,22 +45,22 @@ in
|
||||
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||
MX."@" = [ "10 mx.uninsane.org." ];
|
||||
MX."@" = "10 mx.uninsane.org.";
|
||||
# XXX: RFC's specify that the MX record CANNOT BE A CNAME
|
||||
A."mx" = [ "185.157.162.178" ];
|
||||
CNAME."imap" = [ "native" ];
|
||||
A."mx" = "185.157.162.178";
|
||||
CNAME."imap" = "native";
|
||||
|
||||
# Sender Policy Framework:
|
||||
# +mx => mail passes if it originated from the MX
|
||||
# +a => mail passes if it originated from the A address of this domain
|
||||
# +ip4:.. => mail passes if it originated from this IP
|
||||
# -all => mail fails if none of these conditions were met
|
||||
TXT."@" = [ "v=spf1 a mx -all" ];
|
||||
TXT."@" = "v=spf1 a mx -all";
|
||||
|
||||
# DKIM public key:
|
||||
TXT."mx._domainkey" = [
|
||||
TXT."mx._domainkey" =
|
||||
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkSyMufc2KrRx3j17e/LyB+3eYSBRuEFT8PUka8EDX04QzCwDPdkwgnj3GNDvnB5Ktb05Cf2SJ/S1OLqNsINxJRWtkVfZd/C339KNh9wrukMKRKNELL9HLUw0bczOI4gKKFqyrRE9qm+4csCMAR79Te9FCjGV/jVnrkLdPT0GtFwIDAQAB"
|
||||
];
|
||||
;
|
||||
|
||||
# DMARC fields <https://datatracker.ietf.org/doc/html/rfc7489>:
|
||||
# p=none|quarantine|reject: what to do with failures
|
||||
@@ -75,9 +75,9 @@ in
|
||||
# pct = sampling ratio for punishing failures (default 100 for 100%)
|
||||
# rf = report format
|
||||
# ri = report interval
|
||||
TXT."_dmarc" = [
|
||||
TXT."_dmarc" =
|
||||
"v=DMARC1;p=quarantine;sp=reject;rua=mailto:admin+mail@uninsane.org;ruf=mailto:admin+mail@uninsane.org;fo=1:d:s"
|
||||
];
|
||||
;
|
||||
};
|
||||
|
||||
services.postfix.enable = true;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: mode?
|
||||
{ user = "postgres"; group = "postgres"; directory = "/var/lib/postgresql"; }
|
||||
];
|
||||
|
@@ -9,7 +9,7 @@
|
||||
# nixnet runs ejabberd, so revisiting that.
|
||||
lib.mkIf false
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
{ user = "prosody"; group = "prosody"; directory = "/var/lib/prosody"; }
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs = [
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
# TODO: mode? we need this specifically for the stats tracking in .config/
|
||||
{ user = "transmission"; group = "transmission"; directory = "/var/lib/transmission"; }
|
||||
];
|
||||
@@ -75,6 +75,6 @@
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."bt" = ["native"];
|
||||
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."bt" = "native";
|
||||
}
|
||||
|
||||
|
@@ -21,25 +21,25 @@
|
||||
# Retry = how long secondary NS should wait until re-querying master after a failure (must be < Refresh)
|
||||
# Expire = how long secondary NS should continue to reply to queries after master fails (> Refresh + Retry)
|
||||
sane.services.trust-dns.zones."uninsane.org".inet = {
|
||||
SOA."@" = [''
|
||||
SOA."@" = ''
|
||||
ns1.uninsane.org. admin-dns.uninsane.org. (
|
||||
2022122101 ; Serial
|
||||
4h ; Refresh
|
||||
30m ; Retry
|
||||
7d ; Expire
|
||||
5m) ; Negative response TTL
|
||||
''];
|
||||
TXT."rev" = [ "2022122101" ];
|
||||
'';
|
||||
TXT."rev" = "2022122101";
|
||||
|
||||
# XXX NS records must also not be CNAME
|
||||
# it's best that we keep this identical, or a superset of, what org. lists as our NS.
|
||||
# so, org. can specify ns2/ns3 as being to the VPN, with no mention of ns1. we provide ns1 here.
|
||||
A."ns1" = [ "%NATIVE%" ];
|
||||
A."ns2" = [ "185.157.162.178" ];
|
||||
A."ns3" = [ "185.157.162.178" ];
|
||||
A."ovpns" = [ "185.157.162.178" ];
|
||||
A."native" = [ "%NATIVE%" ];
|
||||
A."@" = [ "%NATIVE%" ];
|
||||
A."ns1" = "%NATIVE%";
|
||||
A."ns2" = "185.157.162.178";
|
||||
A."ns3" = "185.157.162.178";
|
||||
A."ovpns" = "185.157.162.178";
|
||||
A."native" = "%NATIVE%";
|
||||
A."@" = "%NATIVE%";
|
||||
NS."@" = [
|
||||
"ns1.uninsane.org."
|
||||
"ns2.uninsane.org."
|
||||
|
@@ -1,8 +1,9 @@
|
||||
{ ... }:
|
||||
{ lib, utils, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./allocations.nix
|
||||
./fs
|
||||
./gui
|
||||
./home-manager
|
||||
./packages.nix
|
||||
@@ -10,5 +11,10 @@
|
||||
./impermanence
|
||||
./nixcache.nix
|
||||
./services
|
||||
./sops.nix
|
||||
];
|
||||
|
||||
_module.args = {
|
||||
sane-lib = import ./lib { inherit lib utils; };
|
||||
};
|
||||
}
|
||||
|
353
modules/fs/default.nix
Normal file
353
modules/fs/default.nix
Normal file
@@ -0,0 +1,353 @@
|
||||
{ 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 symlinkEntry;
|
||||
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 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;
|
||||
|
||||
symlinkEntry = types.submodule {
|
||||
options = {
|
||||
inherit (propagatedGenerateMod.options) acl;
|
||||
target = mkOption {
|
||||
type = types.str;
|
||||
description = "fs path to link to";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
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: mergeTopLevel [
|
||||
(mkGeneratedConfig path opt)
|
||||
(lib.mkIf (opt.mount != null) (mkMountConfig path opt))
|
||||
];
|
||||
|
||||
# act as `config = lib.mkMerge [ a b ]` but in a way which avoids infinite recursion,
|
||||
# by extracting only specific options which are known to not be options in this module.
|
||||
mergeTopLevel = items: let
|
||||
# if one of the items is `lib.mkIf cond attrs`, we won't be able to index it until
|
||||
# after we "push down" the mkIf to each attr.
|
||||
indexable = lib.pushDownProperties (lib.mkMerge items);
|
||||
# transform (listOf attrs) to (attrsOf list) by grouping each toplevel attr across lists.
|
||||
top = lib.zipAttrsWith (name: lib.mkMerge) indexable;
|
||||
# extract known-good top-level items in a way which errors if a module tries to define something extra.
|
||||
extract = { fileSystems ? {}, systemd ? {} }@attrs: attrs;
|
||||
in {
|
||||
inherit (extract top) fileSystems systemd;
|
||||
};
|
||||
|
||||
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 -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" ]
|
||||
# TODO: move this to path-lib?
|
||||
ancestorsOf = path: if path-lib.hasParent path then
|
||||
ancestorsOf (path-lib.parent path) ++ [ (path-lib.parent 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 == path-lib.norm p) (builtins.attrNames tree);
|
||||
};
|
||||
|
||||
in {
|
||||
options = {
|
||||
sane.fs = mkOption {
|
||||
# type = types.attrsOf fsEntry;
|
||||
type = fsTree;
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
|
||||
config = mergeTopLevel (lib.mapAttrsToList mkFsConfig cfg);
|
||||
}
|
@@ -9,11 +9,8 @@
|
||||
with lib;
|
||||
let
|
||||
cfg = config.sane.home-manager;
|
||||
# extract package from `sane.packages.enabledUserPkgs`
|
||||
pkg-list = pkgspec: builtins.map (e: e.pkg or e) pkgspec;
|
||||
# extract `dir` from `sane.packages.enabledUserPkgs`
|
||||
dir-list = pkgspec: builtins.concatLists (builtins.map (e: e.dir or []) pkgspec);
|
||||
private-list = pkgspec: builtins.concatLists (builtins.map (e: e.private or []) pkgspec);
|
||||
# extract `pkg` from `sane.packages.enabledUserPkgs`
|
||||
pkg-list = pkgspec: builtins.map (e: e.pkg) pkgspec;
|
||||
feeds = import ./feeds.nix { inherit lib; };
|
||||
in
|
||||
{
|
||||
@@ -51,18 +48,6 @@ in
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
sane.impermanence.home-dirs = [
|
||||
"archive"
|
||||
"dev"
|
||||
"records"
|
||||
"ref"
|
||||
"tmp"
|
||||
"use"
|
||||
"Music"
|
||||
"Pictures"
|
||||
"Videos"
|
||||
] ++ (dir-list config.sane.packages.enabledUserPkgs);
|
||||
|
||||
home-manager.useGlobalPkgs = true;
|
||||
home-manager.useUserPackages = true;
|
||||
|
||||
@@ -90,27 +75,6 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
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.
|
||||
# these clutter the home, so i mostly don't use them.
|
||||
xdg.userDirs = {
|
||||
|
@@ -19,12 +19,14 @@ let
|
||||
# });
|
||||
libName = "librewolf";
|
||||
dotDir = ".librewolf";
|
||||
cacheDir = ".cache/librewolf"; # TODO: is it?
|
||||
desktop = "librewolf.desktop";
|
||||
};
|
||||
firefoxSettings = {
|
||||
browser = pkgs.firefox-esr-unwrapped;
|
||||
libName = "firefox";
|
||||
dotDir = ".mozilla/firefox";
|
||||
cacheDir = ".cache/mozilla";
|
||||
desktop = "firefox.desktop";
|
||||
};
|
||||
defaultSettings = firefoxSettings;
|
||||
@@ -55,9 +57,9 @@ let
|
||||
# get names from:
|
||||
# - ~/ref/nix-community/nur-combined/repos/rycee/pkgs/firefox-addons/generated-firefox-addons.nix
|
||||
# `wget ...xpi`; `unar ...xpi`; `cat */manifest.json | jq '.browser_specific_settings.gecko.id'`
|
||||
(addon "ublock-origin" "uBlock0@raymondhill.net" "sha256-+xc4lcdsOwXxMsr4enFsdePbIb6GHq0bFLpqvH5xXos=")
|
||||
(addon "sponsorblock" "sponsorBlocker@ajay.app" "sha256-30F8oDIgshXVY7YKgnfoc1tUTHfgeFbzXISJuVJs0AM=")
|
||||
(addon "bypass-paywalls-clean" "{d133e097-46d9-4ecc-9903-fa6a722a6e0e}" "sha256-7ZDkG8O1rEYdh/La0PLi9tp92JxYeQvaOFt/BmnDv3U=")
|
||||
(addon "ublock-origin" "uBlock0@raymondhill.net" "sha256-a/ivUmY1P6teq9x0dt4CbgHt+3kBsEMMXlOfZ5Hx7cg=")
|
||||
(addon "sponsorblock" "sponsorBlocker@ajay.app" "sha256-d2K3ufvurWnYVzqLbyR//MgejybkY9exitAf9RdLNRo=")
|
||||
(addon "bypass-paywalls-clean" "{d133e097-46d9-4ecc-9903-fa6a722a6e0e}" "sha256-t6Q335Nq60mDILPmzem+DT5KflleAPVJL3bsaA+UL0g=")
|
||||
(addon "sidebery" "{3c078156-979c-498b-8990-85f7987dd929}" "sha256-YONfK/rIjlsrTgRHIt3km07Q7KnpIW89Z9r92ZSCc6w=")
|
||||
(addon "ether-metamask" "webextension@metamask.io" "sha256-G+MwJDOcsaxYSUXjahHJmkWnjLeQ0Wven8DU/lGeMzA=")
|
||||
(addon "ublacklist" "@ublacklist" "sha256-vHe/7EYOzcKeAbTElmt0Rb4E2rX0f3JgXThJaUmaz+M=")
|
||||
|
@@ -2,7 +2,8 @@
|
||||
|
||||
lib.mkIf config.sane.home-manager.enable
|
||||
{
|
||||
sane.impermanence.home-dirs = [ ".cache/vim-swap" ];
|
||||
# private because there could be sensitive things in the swap
|
||||
sane.impermanence.dirs.home.private = [ ".cache/vim-swap" ];
|
||||
|
||||
home-manager.users.colin.programs.neovim = {
|
||||
# neovim: https://github.com/neovim/neovim
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
lib.mkIf config.sane.home-manager.enable
|
||||
{
|
||||
sane.impermanence.home-dirs = [
|
||||
sane.impermanence.dirs.home.plaintext = [
|
||||
# we don't need to full zsh dir -- just the history file --
|
||||
# but zsh will sometimes backup the history file and we get fewer errors if we do proper mounts instead of symlinks.
|
||||
# TODO: should be private?
|
||||
|
@@ -1,4 +1,5 @@
|
||||
{ config, lib, pkgs, mobile-nixos, utils, ... }:
|
||||
{ config, lib, pkgs, utils, ... }:
|
||||
# TODO: replace mobile-nixos parts with Disko <https://github.com/nix-community/disko>
|
||||
|
||||
with lib;
|
||||
let
|
||||
@@ -9,7 +10,7 @@ in
|
||||
sane.image.enable = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = "whether to enable image targets. this doesn't mean they'll be built unless you specifically reference the target.";
|
||||
description = "whether to enable image targets. even so they won't be built unless you specifically reference the `system.build.img` target.";
|
||||
};
|
||||
# packages whose contents should be copied directly into the /boot partition.
|
||||
# e.g. EFI loaders, u-boot bootloader, etc.
|
||||
|
@@ -2,139 +2,132 @@
|
||||
# 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, ... }:
|
||||
{ config, lib, pkgs, utils, sane-lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
path = sane-lib.path;
|
||||
sane-types = sane-lib.types;
|
||||
cfg = config.sane.impermanence;
|
||||
# taken from sops-nix code: checks if any secrets are needed to create /etc/shadow
|
||||
secrets-for-users = (lib.filterAttrs (_: v: v.neededForUsers) config.sops.secrets) != {};
|
||||
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));
|
||||
# normalize the given path
|
||||
normPath = str: joinPathAbs (splitPath str);
|
||||
# return the parent directory. doesn't care about leading/trailing slashes.
|
||||
parentDir = str: normPath (builtins.dirOf (normPath str));
|
||||
|
||||
dirOptions = defaults: types.submodule {
|
||||
storeType = types.submodule {
|
||||
options = {
|
||||
encryptedClearOnBoot = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
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".
|
||||
'';
|
||||
};
|
||||
directory = mkOption {
|
||||
origin = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
user = mkOption {
|
||||
prefix = mkOption {
|
||||
type = types.str;
|
||||
default = defaults.user;
|
||||
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.
|
||||
'';
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = defaults.group;
|
||||
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.
|
||||
'';
|
||||
};
|
||||
mode = mkOption {
|
||||
type = types.str;
|
||||
default = defaults.mode;
|
||||
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.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
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);
|
||||
|
||||
# options for a single mountpoint / persistence
|
||||
dirEntryOptions = {
|
||||
options = {
|
||||
directory = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
inherit (sane-types.aclOverrideMod.options) user group mode;
|
||||
};
|
||||
};
|
||||
contextualizedDir = types.submodule dirEntryOptions;
|
||||
# allow "bar/baz" as shorthand for { directory = "bar/baz"; }
|
||||
contextualizedDirOrShorthand = types.coercedTo
|
||||
types.str
|
||||
(d: { directory = d; })
|
||||
contextualizedDir;
|
||||
|
||||
# entry whose `directory` is always an absolute fs path
|
||||
# and has an associated `store`
|
||||
contextFreeDir = types.submodule [
|
||||
dirEntryOptions
|
||||
{
|
||||
options = {
|
||||
store = mkOption {
|
||||
type = storeType;
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
dirsSubModule = types.submodule {
|
||||
options = mapAttrs (store: store-cfg: mkOption {
|
||||
default = [];
|
||||
type = types.listOf contextualizedDirOrShorthand;
|
||||
description = let
|
||||
suffix = if store-cfg.storeDescription != null then
|
||||
": ${store-cfg.storeDescription}"
|
||||
else "";
|
||||
in "directories to persist in ${store}${suffix}";
|
||||
}) cfg.stores;
|
||||
};
|
||||
|
||||
# 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;
|
||||
|
||||
# include these anchor points as "virtual" nodes in below fs tree.
|
||||
home-dir = {
|
||||
inherit (home-dir-defaults) user group mode;
|
||||
directory = normPath home-dir-defaults.relativeTo;
|
||||
};
|
||||
root-dir = {
|
||||
inherit (sys-dir-defaults) user group mode;
|
||||
directory = normPath sys-dir-defaults.relativeTo;
|
||||
};
|
||||
|
||||
unexpanded-tree = builtins.listToAttrs (builtins.map
|
||||
(dir: {
|
||||
name = dir.directory;
|
||||
value = dir;
|
||||
})
|
||||
(ingested-dirs ++ [ home-dir root-dir ])
|
||||
);
|
||||
|
||||
# ensures the provided node and all parent nodes exist
|
||||
ensureNode = tree: path: (
|
||||
let
|
||||
parent-path = parentDir path;
|
||||
tree-with-parent = if parent-path == "/"
|
||||
then tree
|
||||
else ensureNode tree parent-path;
|
||||
parent = tree-with-parent."${parent-path}";
|
||||
# how to initialize this node if it doesn't exist explicitly.
|
||||
default-node = parent // { directory = path; };
|
||||
in
|
||||
{ "${path}" = default-node; } // tree-with-parent
|
||||
);
|
||||
|
||||
# finally, this tree has no orphan nodes
|
||||
expanded-tree = foldl' ensureNode unexpanded-tree (builtins.attrNames unexpanded-tree);
|
||||
dirsModule = types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
home = mkOption {
|
||||
description = "directories to persist to disk, relative to a user's home ~";
|
||||
default = {};
|
||||
type = dirsSubModule;
|
||||
};
|
||||
sys = mkOption {
|
||||
description = "directories to persist to disk, relative to the fs root /";
|
||||
default = {};
|
||||
type = dirsSubModule;
|
||||
};
|
||||
all = mkOption {
|
||||
type = types.listOf contextFreeDir;
|
||||
description = "all directories known to the config. auto-computed: users should not set this directly.";
|
||||
};
|
||||
};
|
||||
config = let
|
||||
mapDirs = relativeTo: store: dirs: (map
|
||||
(d: {
|
||||
inherit (d) user group mode;
|
||||
directory = path.concat [ relativeTo d.directory ];
|
||||
store = cfg.stores."${store}";
|
||||
})
|
||||
dirs
|
||||
);
|
||||
mapDirSets = relativeTo: dirsSubOptions: let
|
||||
# list where each elem is a list from calling mapDirs on one store at a time
|
||||
contextFreeDirSets = lib.mapAttrsToList (mapDirs relativeTo) dirsSubOptions;
|
||||
in
|
||||
builtins.concatLists contextFreeDirSets;
|
||||
in {
|
||||
all = (mapDirSets "/home/colin" config.home) ++ (mapDirSets "/" config.sys);
|
||||
};
|
||||
});
|
||||
in
|
||||
{
|
||||
options = {
|
||||
@@ -145,182 +138,54 @@ in
|
||||
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";
|
||||
description = "define / fs root to be a tmpfs. make sure to mount some other device to /nix";
|
||||
};
|
||||
sane.impermanence.dirs = mkOption {
|
||||
type = dirsModule;
|
||||
default = {};
|
||||
};
|
||||
sane.impermanence.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.
|
||||
'';
|
||||
};
|
||||
sane.impermanence.home-dirs = mkDirsOption home-dir-defaults;
|
||||
sane.impermanence.dirs = mkDirsOption sys-dir-defaults;
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (lib.mkMerge [
|
||||
(lib.mkIf cfg.root-on-tmpfs {
|
||||
fileSystems."/" = {
|
||||
device = "none";
|
||||
fsType = "tmpfs";
|
||||
options = [
|
||||
"mode=755"
|
||||
"size=1G"
|
||||
"defaults"
|
||||
];
|
||||
};
|
||||
})
|
||||
imports = [
|
||||
./root-on-tmpfs.nix
|
||||
./stores
|
||||
];
|
||||
|
||||
{
|
||||
# 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 =
|
||||
config = let
|
||||
cfgFor = opt:
|
||||
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}'';
|
||||
};
|
||||
store = opt.store;
|
||||
store-rel-path = path.from store.prefix opt.directory;
|
||||
backing-path = path.concat [ store.origin store-rel-path ];
|
||||
|
||||
fileSystems = let
|
||||
store = getStore { encryptedClearOnBoot = true; };
|
||||
# pass through the perm/mode overrides
|
||||
dir-acl = sane-lib.filterNonNull {
|
||||
inherit (opt) user group mode;
|
||||
};
|
||||
in {
|
||||
"${store.device}" = {
|
||||
device = store.underlying.path;
|
||||
fsType = "fuse.gocryptfs";
|
||||
options = [
|
||||
"nodev"
|
||||
"nosuid"
|
||||
"allow_other"
|
||||
"passfile=${store.underlying.key}"
|
||||
"defaults"
|
||||
];
|
||||
noCheck = true;
|
||||
# create destination and backing directory, with correct perms
|
||||
sane.fs."${opt.directory}" = {
|
||||
# inherit perms & make sure we don't mount until after the mount point is setup correctly.
|
||||
dir.acl = dir-acl;
|
||||
mount.bind = backing-path;
|
||||
inherit (store.defaultOrdering) wantedBy wantedBeforeBy;
|
||||
};
|
||||
sane.fs."${backing-path}" = {
|
||||
# ensure the backing path has same perms as the mount point.
|
||||
# TODO: maybe we want to do this, crawling all the way up to the store base?
|
||||
# that would simplify (remove) the code in stores/default.nix
|
||||
dir.acl = config.sane.fs."${opt.directory}".generated.acl;
|
||||
};
|
||||
};
|
||||
|
||||
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;
|
||||
perms-service = "impermanence-perms-${mount-service}";
|
||||
parent-mount-service = cleanName (parentDir opt.directory);
|
||||
parent-perms-service = "impermanence-perms-${parent-mount-service}";
|
||||
is-mount = opt ? store;
|
||||
backing-path = if is-mount then
|
||||
concatPaths [ opt.store.device opt.directory ]
|
||||
else
|
||||
opt.directory;
|
||||
in {
|
||||
fileSystems."${opt.directory}" = lib.mkIf is-mount {
|
||||
device = concatPaths [ opt.store.device opt.directory ];
|
||||
options = [
|
||||
"bind"
|
||||
# "x-systemd.requires=${backing-mount}.mount" # this should be implicit
|
||||
"x-systemd.after=${perms-service}.service"
|
||||
# `wants` doesn't seem to make it to the service file here :-(
|
||||
"x-systemd.wants=${perms-service}.service"
|
||||
];
|
||||
# fsType = "bind";
|
||||
noCheck = true;
|
||||
};
|
||||
|
||||
# create services which ensure the source directories exist and have correct ownership/perms before mounting
|
||||
systemd.services."${perms-service}" = let
|
||||
perms-script = pkgs.writeShellScript "impermanence-prepare-perms" ''
|
||||
backing="$1"
|
||||
path="$2"
|
||||
user="$3"
|
||||
group="$4"
|
||||
mode="$5"
|
||||
mkdir "$path" || test -d "$path"
|
||||
chmod "$mode" "$path"
|
||||
chown "$user:$group" "$path"
|
||||
|
||||
# XXX: fix up the permissions of the origin, otherwise it overwrites the mountpoint with defaults.
|
||||
# TODO: apply to the full $backing path? like, construct it entirely in parallel?
|
||||
if [ "$backing" != "$path" ]
|
||||
then
|
||||
mkdir -p "$backing"
|
||||
chmod "$mode" "$backing"
|
||||
chown "$user:$group" "$backing"
|
||||
fi
|
||||
'';
|
||||
in {
|
||||
description = "prepare permissions for ${opt.directory}";
|
||||
serviceConfig = {
|
||||
ExecStart = ''${perms-script} ${backing-path} ${opt.directory} ${opt.user} ${opt.group} ${opt.mode}'';
|
||||
Type = "oneshot";
|
||||
};
|
||||
unitConfig = {
|
||||
# prevent systemd making this unit implicitly dependent on sysinit.target.
|
||||
# see: <https://www.freedesktop.org/software/systemd/man/systemd.special.html>
|
||||
DefaultDependencies = "no";
|
||||
};
|
||||
wantedBy = lib.mkIf is-mount [ "${mount-service}.mount" ];
|
||||
after = lib.mkIf (opt.directory != "/") [ "${parent-perms-service}.service" ];
|
||||
wants = lib.mkIf (opt.directory != "/") [ "${parent-perms-service}.service" ];
|
||||
};
|
||||
};
|
||||
cfgs = builtins.map cfgFor (builtins.attrValues expanded-tree);
|
||||
# cfgs = builtins.map cfgFor ingested-dirs;
|
||||
# cfgs = [ (cfgFor (ingestDirOption home-dir-defaults ".cache")) ];
|
||||
# myMerge = items: builtins.foldl' (acc: new: acc // new) {} items;
|
||||
in {
|
||||
# fileSystems = myMerge (catAttrs "fileSystems" cfgs);
|
||||
fileSystems = lib.mkMerge (builtins.catAttrs "fileSystems" cfgs);
|
||||
systemd = lib.mkMerge (catAttrs "systemd" cfgs);
|
||||
}
|
||||
)
|
||||
|
||||
(lib.mkIf secrets-for-users {
|
||||
# secret decoding depends on /etc/ssh keys, so make sure those are present.
|
||||
system.activationScripts.setupSecretsForUsers = lib.mkIf secrets-for-users {
|
||||
deps = [ "etc" ];
|
||||
};
|
||||
system.activationScripts.etc.deps = lib.mkForce [];
|
||||
assertions = builtins.concatLists (builtins.attrValues (
|
||||
builtins.mapAttrs
|
||||
(path: value: [
|
||||
{
|
||||
assertion = (builtins.substring 0 1 value.user) == "+";
|
||||
message = "non-numeric user for /etc/${path}: ${value.user} prevents early /etc linking";
|
||||
}
|
||||
{
|
||||
assertion = (builtins.substring 0 1 value.group) == "+";
|
||||
message = "non-numeric group for /etc/${path}: ${value.group} prevents early /etc linking";
|
||||
}
|
||||
])
|
||||
config.environment.etc
|
||||
));
|
||||
})
|
||||
]);
|
||||
in mkIf cfg.enable {
|
||||
sane.fs = lib.mkMerge (map (d: (cfgFor d).sane.fs) cfg.dirs.all);
|
||||
};
|
||||
}
|
||||
|
||||
|
16
modules/impermanence/root-on-tmpfs.nix
Normal file
16
modules/impermanence/root-on-tmpfs.nix
Normal file
@@ -0,0 +1,16 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.sane.impermanence;
|
||||
in
|
||||
{
|
||||
fileSystems."/" = lib.mkIf (cfg.enable && cfg.root-on-tmpfs) {
|
||||
device = "none";
|
||||
fsType = "tmpfs";
|
||||
options = [
|
||||
"mode=755"
|
||||
"size=1G"
|
||||
"defaults"
|
||||
];
|
||||
};
|
||||
}
|
74
modules/impermanence/stores/crypt.nix
Normal file
74
modules/impermanence/stores/crypt.nix
Normal file
@@ -0,0 +1,74 @@
|
||||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
let
|
||||
store = rec {
|
||||
device = "/mnt/impermanence/crypt/clearedonboot";
|
||||
underlying = {
|
||||
path = "/nix/persist/crypt/clearedonboot";
|
||||
# TODO: consider moving this to /tmp, but that requires tmp be mounted first?
|
||||
key = "/mnt/impermanence/crypt/clearedonboot.key";
|
||||
};
|
||||
};
|
||||
in
|
||||
lib.mkIf config.sane.impermanence.enable
|
||||
{
|
||||
sane.impermanence.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
|
||||
}
|
31
modules/impermanence/stores/default.nix
Normal file
31
modules/impermanence/stores/default.nix
Normal file
@@ -0,0 +1,31 @@
|
||||
{ config, lib, sane-lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.sane.impermanence;
|
||||
path = sane-lib.path;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./crypt.nix
|
||||
./plaintext.nix
|
||||
./private.nix
|
||||
];
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# make sure that the store has the same acl as the main filesystem,
|
||||
# particularly for /home/colin.
|
||||
#
|
||||
# N.B.: we have a similar problem with all mounts:
|
||||
# <crypt>/.cache/mozilla won't inherit <plain>/.cache perms.
|
||||
# this is less of a problem though, since we don't really support overlapping mounts like that in the first place.
|
||||
# what is a problem is if the user specified some other dir we don't know about here.
|
||||
# like "/var", and then "/nix/persist/var" has different perms and something mounts funny.
|
||||
# TODO: just add assertions that sane.fs."${backing}/${dest}".dir == sane.fs."${dest}" for each mount point?
|
||||
sane.fs = lib.mapAttrs' (_name: store: let
|
||||
home-in-store = path.from store.prefix "/home/colin";
|
||||
in {
|
||||
name = path.concat [ store.origin home-in-store ];
|
||||
value.dir.acl = config.sane.fs."/home/colin".generated.acl;
|
||||
}) cfg.stores;
|
||||
};
|
||||
}
|
11
modules/impermanence/stores/plaintext.nix
Normal file
11
modules/impermanence/stores/plaintext.nix
Normal file
@@ -0,0 +1,11 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.sane.impermanence;
|
||||
in lib.mkIf cfg.enable {
|
||||
sane.impermanence.stores."plaintext" = {
|
||||
origin = "/nix/persist";
|
||||
};
|
||||
# TODO: needed?
|
||||
# sane.fs."/nix".mount = {};
|
||||
}
|
48
modules/impermanence/stores/private.nix
Normal file
48
modules/impermanence/stores/private.nix
Normal file
@@ -0,0 +1,48 @@
|
||||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
lib.mkIf config.sane.impermanence.enable
|
||||
{
|
||||
sane.impermanence.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 = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
|
8
modules/lib/default.nix
Normal file
8
modules/lib/default.nix
Normal file
@@ -0,0 +1,8 @@
|
||||
{ lib, ... }@moduleArgs:
|
||||
|
||||
{
|
||||
path = import ./path.nix moduleArgs;
|
||||
types = import ./types.nix moduleArgs;
|
||||
|
||||
filterNonNull = attrs: lib.filterAttrsRecursive (n: v: v != null) attrs;
|
||||
}
|
30
modules/lib/path.nix
Normal file
30
modules/lib/path.nix
Normal file
@@ -0,0 +1,30 @@
|
||||
{ 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"
|
||||
from = start: end: let
|
||||
s = path.norm start;
|
||||
e = path.norm end;
|
||||
in (
|
||||
assert lib.hasPrefix s e;
|
||||
"/" + (lib.removePrefix s e)
|
||||
);
|
||||
};
|
||||
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>.
|
||||
# after logging in, manually change ~/.local/share/keyrings/... to point it to some predictable subdir.
|
||||
# then reboot (so that libsecret daemon re-loads the keyring...?)
|
||||
{ pkg = fractal-latest; private = [ ".local/share/fractal" ]; }
|
||||
# { pkg = fractal-latest; private = [ ".local/share/fractal" ]; }
|
||||
# { pkg = fractal-next; private = [ ".local/share/fractal" ]; }
|
||||
|
||||
gajim # XMPP client
|
||||
@@ -144,7 +144,7 @@ let
|
||||
# possible to pass config as a CLI arg (sublime-music -c config.json)
|
||||
# { pkg = sublime-music; dir = [ ".local/share/sublime-music" ]; }
|
||||
{ pkg = sublime-music-mobile; dir = [ ".local/share/sublime-music" ]; }
|
||||
tdesktop # broken on phosh
|
||||
{ pkg = tdesktop; private = [ ".local/share/TelegramDesktop" ]; } # broken on phosh
|
||||
|
||||
{ pkg = tokodon; private = [ ".cache/KDE/tokodon" ]; }
|
||||
|
||||
@@ -300,13 +300,15 @@ in
|
||||
++ (if cfg.enableGuiPkgs then guiPkgs else [])
|
||||
++ (if cfg.enableDevPkgs then devPkgs else [])
|
||||
;
|
||||
type = types.listOf (types.either types.package types.attrs);
|
||||
type = types.listOf (types.coercedTo types.package (p: { pkg = p; }) pkgSpec);
|
||||
description = "generated from other config options";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
environment.systemPackages = mkIf cfg.enableSystemPkgs systemPkgs;
|
||||
sane.impermanence.dirs.home.plaintext = concatLists (map (p: p.dir) cfg.enabledUserPkgs);
|
||||
sane.impermanence.dirs.home.private = concatLists (map (p: p.private) cfg.enabledUserPkgs);
|
||||
# XXX: this might not be necessary. try removing this and cacert.unbundled?
|
||||
environment.etc."ssl/certs".source = mkIf cfg.enableSystemPkgs "${pkgs.cacert.unbundled}/etc/ssl/certs/*";
|
||||
};
|
||||
|
@@ -15,7 +15,8 @@ in
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# 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.impermanence.dirs.sys.plaintext = [ "/var/lib/duplicity" ];
|
||||
|
||||
services.duplicity.enable = true;
|
||||
services.duplicity.targetUrl = "$DUPLICITY_URL";
|
||||
|
@@ -1,5 +1,8 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# TODO: consider using this library for .zone file generation:
|
||||
# - <https://github.com/kirelagin/dns.nix>
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.sane.services.trust-dns;
|
||||
@@ -49,6 +52,13 @@ let
|
||||
}) cfg.zones
|
||||
);
|
||||
};
|
||||
|
||||
# (listOf ty) type which also accepts single-assignment of `ty`.
|
||||
# it's used to allow the user to write:
|
||||
# CNAME."foo" = "bar";
|
||||
# as shorthand for
|
||||
# CNAME."foo" = [ "bar" ];
|
||||
listOrUnit = ty: types.coercedTo ty (elem: [ elem ]) (types.listOf ty);
|
||||
in
|
||||
{
|
||||
options = {
|
||||
@@ -88,37 +98,37 @@ in
|
||||
};
|
||||
inet = {
|
||||
SOA = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "Start of Authority record(s)";
|
||||
default = {};
|
||||
};
|
||||
A = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "IPv4 address record(s)";
|
||||
default = {};
|
||||
};
|
||||
CNAME = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "canonical name record(s)";
|
||||
default = {};
|
||||
};
|
||||
MX = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "mail exchanger record(s)";
|
||||
default = {};
|
||||
};
|
||||
NS = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "name server record(s)";
|
||||
default = {};
|
||||
};
|
||||
SRV = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "service record(s)";
|
||||
default = {};
|
||||
};
|
||||
TXT = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
type = types.attrsOf (listOrUnit types.str);
|
||||
description = "text record(s)";
|
||||
default = {};
|
||||
};
|
||||
|
32
modules/sops.nix
Normal file
32
modules/sops.nix
Normal file
@@ -0,0 +1,32 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
# taken from sops-nix code: checks if any secrets are needed to create /etc/shadow
|
||||
secrets-for-users = (lib.filterAttrs (_: v: v.neededForUsers) config.sops.secrets) != {};
|
||||
sops-files = config.sops.age.sshKeyPaths ++ config.sops.gnupg.sshKeyPaths ++ [ config.sops.age.keyFile ];
|
||||
keys-in-etc = builtins.any (p: builtins.substring 0 5 p == "/etc/") sops-files;
|
||||
in
|
||||
{
|
||||
config = lib.mkIf (secrets-for-users && keys-in-etc) {
|
||||
# secret decoding depends on keys in /etc/ (like the ssh host key), so make sure those are present.
|
||||
system.activationScripts.setupSecretsForUsers = lib.mkIf secrets-for-users {
|
||||
deps = [ "etc" ];
|
||||
};
|
||||
# TODO: we should selectively remove "users" and "groups", but keep manually specified deps?
|
||||
system.activationScripts.etc.deps = lib.mkForce [];
|
||||
assertions = builtins.concatLists (builtins.attrValues (
|
||||
builtins.mapAttrs
|
||||
(path: value: [
|
||||
{
|
||||
assertion = (builtins.substring 0 1 value.user) == "+";
|
||||
message = "non-numeric user for /etc/${path}: ${value.user} prevents early /etc linking";
|
||||
}
|
||||
{
|
||||
assertion = (builtins.substring 0 1 value.group) == "+";
|
||||
message = "non-numeric group for /etc/${path}: ${value.group} prevents early /etc linking";
|
||||
}
|
||||
])
|
||||
config.environment.etc
|
||||
));
|
||||
};
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
sudo systemctl stop matrix-appservice-irc mx-puppet-discord
|
||||
sudo systemctl stop pleroma gitea matrix-synapse jellyfin transmission jackett
|
||||
sudo systemctl stop ejabberd goaccess i2p kiwix-serve navidrome
|
||||
# TODO: stop the freshrss timer
|
||||
sudo systemctl stop phpfpm-freshrss
|
||||
sudo systemctl stop dovecot2 opendkin postfix
|
||||
@@ -8,4 +9,5 @@ sudo systemctl stop nginx
|
||||
sudo systemctl stop postgresql
|
||||
sudo systemctl stop duplicity.timer
|
||||
sudo systemctl stop duplicity
|
||||
sudo systemctl stop trust-dns
|
||||
sudo systemctl stop wireguard-wg0
|
||||
|
@@ -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": {
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2022-12-26T09:13:29Z",
|
||||
"mac": "ENC[AES256_GCM,data:/bKnjVzoiyqz+HD+rT76tQiT8+bqmJfEonFK9z+c+6uDFGCLeockZ5WIHcULU3VU1kfgmkr9R8vlArIYN5vrEm8g6jS8iQgcehjGiqbF5KQHDIarHzBJdqa3ca3G98BF3HlaMYR/hpWquR7sLBcsayf6LcHdGCqiP5TnERd0TzY=,iv:TanC7jAdbH1UXNFbNN6dAOL4hiJY1U0GRWdPmaiY/Sg=,tag:gNsXTb2BTZiOhBoQmcJVDw==,type:str]",
|
||||
"lastmodified": "2023-01-02T12:37:44Z",
|
||||
"mac": "ENC[AES256_GCM,data:VXycD0JG1nPGFefI6gsG2zQh7NjG+bKCyMjyfWkRJyjomJlGaLMDF/8iUAhRHGgBuAmhZuu8nyZHky8F9CEgtktpY4/b/b3eH4NVuWlQ04MrpO24RrRgwyN+WrtG4FWEnbA4QtOLu64pTMQlQgRseL30u+RNQ6eT+ycx71/6r6A=,iv:YtRe37O4Zht148zbjplIKbUfVvghYDH2ErDbKJN2qdc=,tag:AKjzatu7Iy49Dg8lkwiWpA==,type:str]",
|
||||
"pgp": null,
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.7.3"
|
||||
|
Reference in New Issue
Block a user