Compare commits
75 Commits
staging/ni
...
wip/feeds
Author | SHA1 | Date | |
---|---|---|---|
cdc881e887 | |||
33967554a5 | |||
5af55ecdbf | |||
6ca3e7086e | |||
ca62f1b62f | |||
eef66df36d | |||
9ca6a1c907 | |||
dbb78088f4 | |||
f17ae1ca7b | |||
b2774a4004 | |||
0ae548d47c | |||
760505db20 | |||
71fc1a2fd7 | |||
a457fc1416 | |||
2c0b0f6947 | |||
f10de6c2c4 | |||
a6be200a82 | |||
fb57e9aa5b | |||
f5acbbd830 | |||
af77417531 | |||
eea80b575d | |||
6a209d27fd | |||
e8f778fecd | |||
488036beb3 | |||
00b681eca5 | |||
72d589cb2d | |||
ea5552daa7 | |||
fb7d94209c | |||
8f5b92685b | |||
32a4cb19fd | |||
031cfa2bcd | |||
e93fbea1e6 | |||
85a2fbc38a | |||
9e902c8eb2 | |||
dc15091ea7 | |||
c063ecd047 | |||
70a43c770d | |||
cc9e2d8e15 | |||
bb41fb95fe | |||
d852adf806 | |||
5443542cba | |||
81effb01a3 | |||
83f416999f | |||
dd34883246 | |||
e47f9e38ce | |||
0f0b728911 | |||
1839f87a4e | |||
53edf4e6af | |||
fb6e0ddb34 | |||
0a48d79174 | |||
b6208e1a19 | |||
e46ab4ec14 | |||
19c254c266 | |||
1d0cadce85 | |||
e8342b8044 | |||
40e642bfc3 | |||
f008565e22 | |||
4ea2835d9d | |||
493d317bb1 | |||
e446bfba58 | |||
a7bac5de18 | |||
b0950e90f4 | |||
d8cd0e1f57 | |||
fd7d67ee05 | |||
1a712b4d47 | |||
4520e1d1f5 | |||
841a2a3bcb | |||
fe816e9110 | |||
426e0c3ae2 | |||
a95b91a556 | |||
837e5438c3 | |||
8217b22c86 | |||
0b35ce4dec | |||
413f9a171b | |||
43a46af43b |
24
flake.lock
generated
24
flake.lock
generated
@@ -54,11 +54,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1672791794,
|
||||
"narHash": "sha256-mqGPpGmwap0Wfsf3o2b6qHJW1w2kk/I6cGCGIU+3t6o=",
|
||||
"lastModified": 1672953546,
|
||||
"narHash": "sha256-oz757DnJ1ITvwyTovuwG3l9cX6j9j6/DH9eH+cXFJmc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9813adc7f7c0edd738c6bdd8431439688bb0cb3d",
|
||||
"rev": "a518c77148585023ff56022f09c4b2c418a51ef5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -69,11 +69,11 @@
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1672844754,
|
||||
"narHash": "sha256-o26WabuHABQsaHxxmIrR3AQRqDFUEdLckLXkVCpIjSU=",
|
||||
"lastModified": 1673163619,
|
||||
"narHash": "sha256-B33PFBL64ZgTWgMnhFL3jgheAN/DjHPsZ1Ih3z0VE5I=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e9ade2c8240e00a4784fac282a502efff2786bdc",
|
||||
"rev": "8c54d842d9544361aac5f5b212ba04e4089e8efe",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -84,11 +84,11 @@
|
||||
},
|
||||
"nixpkgs-stable_2": {
|
||||
"locked": {
|
||||
"lastModified": 1672500394,
|
||||
"narHash": "sha256-yzwBzCoeRBoRzm7ySHhm72kBG0QjgFalLz2FY48iLI4=",
|
||||
"lastModified": 1673100377,
|
||||
"narHash": "sha256-mT76pTd0YFxT6CwtPhDgHJhuIgLY+ZLSMiQpBufwMG4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "feda52be1d59f13b9aa02f064b4f14784b9a06c8",
|
||||
"rev": "9f11a2df77cb945c115ae2a65f53f38121597d73",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -116,11 +116,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1672543202,
|
||||
"narHash": "sha256-nlCUtcIZxaBqUBG1GyaXhZmfyG5WK4e6LqypP8llX9E=",
|
||||
"lastModified": 1673147300,
|
||||
"narHash": "sha256-gR9OEfTzWfL6vG0qkbn1TlBAOlg4LuW8xK/u0V41Ihc=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "b35586cc5abacd4eba9ead138b53e2a60920f781",
|
||||
"rev": "2253120d2a6147e57bafb5c689e086221df8032f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@@ -1,18 +1,16 @@
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
# TODO: don't need to depend on binsh if we were to use a nix-style shebang
|
||||
system.activationScripts.linkBluetoothKeys = let
|
||||
unwrapped = ../../scripts/install-bluetooth;
|
||||
install-bluetooth = pkgs.writeShellApplication {
|
||||
name = "install-bluetooth";
|
||||
runtimeInputs = with pkgs; [ coreutils gnused ];
|
||||
text = ''${unwrapped} "$@"'';
|
||||
};
|
||||
in (lib.stringAfter
|
||||
[ "setupSecrets" "binsh" ]
|
||||
''
|
||||
${install-bluetooth}/bin/install-bluetooth /run/secrets/bt
|
||||
''
|
||||
);
|
||||
# persist external pairings by default
|
||||
sane.persist.sys.plaintext = [ "/var/lib/bluetooth" ];
|
||||
|
||||
sane.fs."/var/lib/bluetooth".generated.acl.mode = "0700";
|
||||
sane.fs."/var/lib/bluetooth/.secrets.stamp" = {
|
||||
wantedBeforeBy = [ "bluetooth.service" ];
|
||||
# XXX: install-bluetooth uses sed, but that's part of the default systemd unit path, it seems
|
||||
generated.script.script = builtins.readFile ../../scripts/install-bluetooth + ''
|
||||
touch "/var/lib/bluetooth/.secrets.stamp"
|
||||
'';
|
||||
generated.script.scriptArgs = [ "/run/secrets/bt" ];
|
||||
};
|
||||
}
|
||||
|
@@ -2,9 +2,11 @@
|
||||
{
|
||||
imports = [
|
||||
./bluetooth.nix
|
||||
./feeds.nix
|
||||
./fs.nix
|
||||
./hardware
|
||||
./i2p.nix
|
||||
./ids.nix
|
||||
./machine-id.nix
|
||||
./net.nix
|
||||
./secrets.nix
|
||||
@@ -18,12 +20,11 @@
|
||||
sane.packages.enableConsolePkgs = true;
|
||||
sane.packages.enableSystemPkgs = true;
|
||||
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
"/var/log"
|
||||
"/var/backup" # for e.g. postgres dumps
|
||||
# TODO: move elsewhere
|
||||
"/var/lib/alsa" # preserve output levels, default devices
|
||||
"/var/lib/bluetooth" # preserve bluetooth handshakes
|
||||
"/var/lib/colord" # preserve color calibrations (?)
|
||||
"/var/lib/machines" # maybe not needed, but would be painful to add a VM and forget.
|
||||
];
|
||||
|
@@ -1,5 +1,4 @@
|
||||
{ lib }:
|
||||
|
||||
{ ... }:
|
||||
let
|
||||
hourly = { freq = "hourly"; };
|
||||
daily = { freq = "daily"; };
|
||||
@@ -13,24 +12,15 @@ let
|
||||
tech = { cat = "tech"; };
|
||||
uncat = { cat = "uncat"; };
|
||||
|
||||
text = { format = "text"; };
|
||||
image = { format = "image"; };
|
||||
podcast = { format = "podcast"; };
|
||||
|
||||
mkRss = format: url: { inherit url format; } // uncat // infrequent;
|
||||
# format-specific helpers
|
||||
mkText = mkRss text;
|
||||
mkImg = mkRss image;
|
||||
mkPod = mkRss podcast;
|
||||
mkText = mkRss "text";
|
||||
mkImg = mkRss "image";
|
||||
mkPod = mkRss "podcast";
|
||||
|
||||
# host-specific helpers
|
||||
mkSubstack = subdomain: mkText "https://${subdomain}.substack.com/feed";
|
||||
mkSubstack = subdomain: { substack = subdomain; };
|
||||
|
||||
# merge the attrs `new` into each value of the attrs `addTo`
|
||||
addAttrs = new: addTo: builtins.mapAttrs (k: v: v // new) addTo;
|
||||
# for each value in `attrs`, add a value to the child attrs which holds its key within the parent attrs.
|
||||
withInverseMapping = key: attrs: builtins.mapAttrs (k: v: v // { "${key}" = k; }) attrs;
|
||||
in rec {
|
||||
podcasts = [
|
||||
(mkPod "https://lexfridman.com/feed/podcast/" // rat // weekly)
|
||||
## Astral Codex Ten
|
||||
@@ -149,46 +139,13 @@ in rec {
|
||||
images = [
|
||||
(mkImg "https://www.smbc-comics.com/comic/rss" // humor // daily)
|
||||
(mkImg "https://xkcd.com/atom.xml" // humor // daily)
|
||||
(mkImg "http://dilbert.com/feed" // humor // daily)
|
||||
(mkImg "https://pbfcomics.com/feed" // humor // infrequent)
|
||||
# (mkImg "http://dilbert.com/feed" // humor // daily)
|
||||
|
||||
# ART
|
||||
(mkImg "https://miniature-calendar.com/feed" // art // daily)
|
||||
];
|
||||
|
||||
all = texts ++ images ++ podcasts;
|
||||
|
||||
# return only the feed items which match this category (e.g. "tech")
|
||||
filterCat = cat: feeds: builtins.filter (item: item.cat == cat) feeds;
|
||||
# return only the feed items which match this format (e.g. "podcast")
|
||||
filterFormat = format: feeds: builtins.filter (item: item.format == format) feeds;
|
||||
|
||||
# transform a list of feeds into an attrs mapping cat => [ feed0 feed1 ... ]
|
||||
partitionByCat = feeds: builtins.groupBy (f: f.cat) feeds;
|
||||
|
||||
# represents a single RSS feed.
|
||||
opmlTerminal = feed: ''<outline xmlUrl="${feed.url}" type="rss"/>'';
|
||||
# a list of RSS feeds.
|
||||
opmlTerminals = feeds: lib.strings.concatStringsSep "\n" (builtins.map opmlTerminal feeds);
|
||||
# one node which packages some flat grouping of terminals.
|
||||
opmlGroup = title: feeds: ''
|
||||
<outline text="${title}" title="${title}">
|
||||
${opmlTerminals feeds}
|
||||
</outline>
|
||||
'';
|
||||
# a list of groups (`groupMap` is an attrs mapping groupName => [ feed0 feed1 ... ]).
|
||||
opmlGroups = groupMap: lib.strings.concatStringsSep "\n" (
|
||||
builtins.attrValues (builtins.mapAttrs opmlGroup groupMap)
|
||||
);
|
||||
# top-level OPML file which could be consumed by something else.
|
||||
opmlTopLevel = body: ''
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<opml version="2.0">
|
||||
<body>
|
||||
${body}
|
||||
</body>
|
||||
</opml>
|
||||
'';
|
||||
|
||||
# **primary API**: generate a OPML file from the provided feeds
|
||||
feedsToOpml = feeds: opmlTopLevel (opmlGroups (partitionByCat feeds));
|
||||
in
|
||||
{
|
||||
sane.feeds = texts ++ images ++ podcasts;
|
||||
}
|
60
hosts/common/ids.nix
Normal file
60
hosts/common/ids.nix
Normal file
@@ -0,0 +1,60 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
# legacy servo users, some are inconvenient to migrate
|
||||
sane.ids.dhcpcd.gid = 991;
|
||||
sane.ids.dhcpcd.uid = 992;
|
||||
sane.ids.gitea.gid = 993;
|
||||
sane.ids.git.uid = 994;
|
||||
sane.ids.jellyfin.gid = 994;
|
||||
sane.ids.pleroma.gid = 995;
|
||||
sane.ids.jellyfin.uid = 996;
|
||||
sane.ids.acme.gid = 996;
|
||||
sane.ids.pleroma.uid = 997;
|
||||
sane.ids.acme.uid = 998;
|
||||
|
||||
# greetd (used by sway)
|
||||
sane.ids.greeter.uid = 999;
|
||||
sane.ids.greeter.gid = 999;
|
||||
|
||||
# new servo users
|
||||
sane.ids.freshrss.uid = 2401;
|
||||
sane.ids.freshrss.gid = 2401;
|
||||
sane.ids.mediawiki.uid = 2402;
|
||||
|
||||
sane.ids.colin.uid = 1000;
|
||||
sane.ids.guest.uid = 1100;
|
||||
|
||||
# found on all hosts
|
||||
sane.ids.sshd.uid = 2001; # 997
|
||||
sane.ids.sshd.gid = 2001; # 997
|
||||
sane.ids.polkituser.gid = 2002; # 998
|
||||
sane.ids.systemd-coredump.gid = 2003; # 996
|
||||
sane.ids.nscd.uid = 2004;
|
||||
sane.ids.nscd.gid = 2004;
|
||||
sane.ids.systemd-oom.uid = 2005;
|
||||
sane.ids.systemd-oom.gid = 2005;
|
||||
|
||||
# found on graphical hosts
|
||||
sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy
|
||||
|
||||
# found on desko host
|
||||
# from services.usbmuxd
|
||||
sane.ids.usbmux.uid = 2204;
|
||||
sane.ids.usbmux.gid = 2204;
|
||||
|
||||
|
||||
# originally found on moby host
|
||||
# gnome core-shell
|
||||
sane.ids.avahi.uid = 2304;
|
||||
sane.ids.avahi.gid = 2304;
|
||||
sane.ids.colord.uid = 2305;
|
||||
sane.ids.colord.gid = 2305;
|
||||
sane.ids.geoclue.uid = 2306;
|
||||
sane.ids.geoclue.gid = 2306;
|
||||
# gnome core-os-services
|
||||
sane.ids.rtkit.uid = 2307;
|
||||
sane.ids.rtkit.gid = 2307;
|
||||
# phosh
|
||||
sane.ids.feedbackd.gid = 2308;
|
||||
}
|
@@ -31,19 +31,13 @@
|
||||
General.RoamThreshold5G = "-52"; # default -76
|
||||
};
|
||||
|
||||
# TODO: don't need to depend on binsh if we were to use a nix-style shebang
|
||||
system.activationScripts.linkIwdKeys = let
|
||||
unwrapped = ../../scripts/install-iwd;
|
||||
install-iwd = pkgs.writeShellApplication {
|
||||
name = "install-iwd";
|
||||
runtimeInputs = with pkgs; [ coreutils gnused ];
|
||||
text = ''${unwrapped} "$@"'';
|
||||
};
|
||||
in (lib.stringAfter
|
||||
[ "setupSecrets" "binsh" ]
|
||||
''
|
||||
mkdir -p /var/lib/iwd
|
||||
${install-iwd}/bin/install-iwd /run/secrets/iwd /var/lib/iwd
|
||||
''
|
||||
);
|
||||
sane.fs."/var/lib/iwd/.secrets.psk.stamp" = {
|
||||
wantedBeforeBy = [ "iwd.service" ];
|
||||
generated.acl.mode = "0600";
|
||||
# XXX: install-iwd uses sed, but that's part of the default systemd unit path, it seems
|
||||
generated.script.script = builtins.readFile ../../scripts/install-iwd + ''
|
||||
touch "/var/lib/iwd/.secrets.psk.stamp"
|
||||
'';
|
||||
generated.script.scriptArgs = [ "/run/secrets/iwd" "/var/lib/iwd" ];
|
||||
};
|
||||
}
|
||||
|
@@ -1,10 +1,24 @@
|
||||
{ config, lib, ... }:
|
||||
{ config, lib, sane-data, sane-lib, ... }:
|
||||
|
||||
{
|
||||
environment.etc."ssh/host_keys".source = "/nix/persist/etc/ssh/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"; }
|
||||
];
|
||||
sane.ssh.pubkeys =
|
||||
let
|
||||
# path is a DNS-style path like [ "org" "uninsane" "root" ]
|
||||
keyNameForPath = path:
|
||||
let
|
||||
rev = lib.reverseList path;
|
||||
name = builtins.head rev;
|
||||
host = lib.concatStringsSep "." (builtins.tail rev);
|
||||
in
|
||||
"${name}@${host}";
|
||||
|
||||
# [{ 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,16 +1,10 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
{ config, pkgs, lib, sane-lib, ... }:
|
||||
|
||||
# installer docs: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/installation-device.nix
|
||||
with lib;
|
||||
let
|
||||
cfg = config.sane.users;
|
||||
# see nixpkgs/nixos/modules/services/networking/dhcpcd.nix
|
||||
hasDHCP = config.networking.dhcpcd.enable &&
|
||||
(config.networking.useDHCP || any (i: i.useDHCP == true) (attrValues config.networking.interfaces));
|
||||
mkSymlink = target: {
|
||||
symlink.target = target;
|
||||
wantedBeforeBy = [ "multi-user.target" ];
|
||||
};
|
||||
fs = sane-lib.fs;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
@@ -32,7 +26,6 @@ in
|
||||
home = "/home/colin";
|
||||
createHome = true;
|
||||
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.
|
||||
subUidRanges = [
|
||||
@@ -55,7 +48,6 @@ in
|
||||
passwordFile = lib.mkIf (config.sops.secrets ? "colin-passwd") config.sops.secrets.colin-passwd.path;
|
||||
|
||||
shell = pkgs.zsh;
|
||||
openssh.authorizedKeys.keys = builtins.attrValues (import ../../modules/pubkeys.nix).users;
|
||||
|
||||
# mount encrypted stuff at login
|
||||
# some other nix pam users:
|
||||
@@ -82,7 +74,7 @@ in
|
||||
mode = config.users.users.colin.homeMode;
|
||||
};
|
||||
|
||||
sane.impermanence.dirs.home.plaintext = [
|
||||
sane.persist.home.plaintext = [
|
||||
"archive"
|
||||
"dev"
|
||||
# TODO: records should be private
|
||||
@@ -96,34 +88,25 @@ in
|
||||
|
||||
".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
|
||||
];
|
||||
|
||||
# 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";
|
||||
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" = mkSymlink "/home/colin/knowledge/secrets/accounts";
|
||||
sane.fs."/home/colin/.password-store" = fs.wantedSymlinkTo "/home/colin/knowledge/secrets/accounts";
|
||||
|
||||
sane.impermanence.dirs.sys.plaintext = mkIf cfg.guest.enable [
|
||||
sane.persist.sys.plaintext = mkIf cfg.guest.enable [
|
||||
# intentionally allow other users to write to the guest folder
|
||||
{ directory = "/home/guest"; user = "guest"; group = "users"; mode = "0775"; }
|
||||
];
|
||||
users.users.guest = mkIf cfg.guest.enable {
|
||||
isNormalUser = true;
|
||||
home = "/home/guest";
|
||||
uid = config.sane.allocations.guest-uid;
|
||||
subUidRanges = [
|
||||
{ startUid=200000; count=1; }
|
||||
];
|
||||
@@ -135,13 +118,6 @@ in
|
||||
];
|
||||
};
|
||||
|
||||
users.users.dhcpcd = mkIf hasDHCP {
|
||||
uid = config.sane.allocations.dhcpcd-uid;
|
||||
};
|
||||
users.groups.dhcpcd = mkIf hasDHCP {
|
||||
gid = config.sane.allocations.dhcpcd-gid;
|
||||
};
|
||||
|
||||
security.sudo = {
|
||||
enable = true;
|
||||
wheelNeedsPassword = false;
|
||||
@@ -152,31 +128,5 @@ in
|
||||
permitRootLogin = "no";
|
||||
passwordAuthentication = false;
|
||||
};
|
||||
|
||||
# affix some UIDs which were historically auto-generated
|
||||
users.users.sshd.uid = config.sane.allocations.sshd-uid;
|
||||
users.groups.polkituser.gid = config.sane.allocations.polkituser-gid;
|
||||
users.groups.sshd.gid = config.sane.allocations.sshd-gid;
|
||||
users.groups.systemd-coredump.gid = config.sane.allocations.systemd-coredump-gid;
|
||||
users.users.nscd.uid = config.sane.allocations.nscd-uid;
|
||||
users.groups.nscd.gid = config.sane.allocations.nscd-gid;
|
||||
users.users.systemd-oom.uid = config.sane.allocations.systemd-oom-uid;
|
||||
users.groups.systemd-oom.gid = config.sane.allocations.systemd-oom-gid;
|
||||
|
||||
# guarantee determinism in uid/gid generation for users:
|
||||
assertions = let
|
||||
uidAssertions = builtins.attrValues (builtins.mapAttrs (name: user: {
|
||||
assertion = user.uid != null;
|
||||
message = "non-deterministic uid detected for: ${name}";
|
||||
}) config.users.users);
|
||||
gidAssertions = builtins.attrValues (builtins.mapAttrs (name: group: {
|
||||
assertion = group.gid != null;
|
||||
message = "non-deterministic gid detected for: ${name}";
|
||||
}) config.users.groups);
|
||||
autoSubAssertions = builtins.attrValues (builtins.mapAttrs (name: user: {
|
||||
assertion = !user.autoSubUidGidRange;
|
||||
message = "non-deterministic subUids/Guids detected for: ${name}";
|
||||
}) config.users.users);
|
||||
in uidAssertions ++ gidAssertions ++ autoSubAssertions;
|
||||
};
|
||||
}
|
||||
|
@@ -10,15 +10,13 @@
|
||||
sane.services.duplicity.enable = true;
|
||||
sane.services.nixserve.enable = true;
|
||||
sane.services.nixserve.sopsFile = ../../secrets/desko.yaml;
|
||||
sane.impermanence.enable = true;
|
||||
sane.persist.enable = true;
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||
|
||||
# needed to use libimobiledevice/ifuse, for iphone sync
|
||||
services.usbmuxd.enable = true;
|
||||
users.users.usbmux.uid = config.sane.allocations.usbmux-uid;
|
||||
users.groups.usbmux.gid = config.sane.allocations.usbmux-gid;
|
||||
|
||||
sops.secrets.colin-passwd = {
|
||||
sopsFile = ../../secrets/desko.yaml;
|
||||
@@ -52,7 +50,7 @@
|
||||
remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play
|
||||
dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server
|
||||
};
|
||||
sane.impermanence.dirs.home.plaintext = [
|
||||
sane.persist.home.plaintext = [
|
||||
".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.
|
||||
# a cross-compiled kernel, particularly, will easily use 30+GB of tmp
|
||||
fileSystems."/tmp" = {
|
||||
|
@@ -8,7 +8,7 @@
|
||||
|
||||
# sane.users.guest.enable = true;
|
||||
sane.gui.sway.enable = true;
|
||||
sane.impermanence.enable = true;
|
||||
sane.persist.enable = true;
|
||||
sane.nixcache.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||
|
@@ -1,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
|
||||
fileSystems."/tmp" = {
|
||||
device = "none";
|
||||
|
@@ -24,11 +24,9 @@
|
||||
};
|
||||
|
||||
# usability compromises
|
||||
sane.impermanence.dirs.home.private = [
|
||||
config.sane.web-browser.dotDir
|
||||
config.sane.web-browser.cacheDir
|
||||
];
|
||||
sane.impermanence.dirs.home.plaintext = [
|
||||
sane.web-browser.persistCache = "private";
|
||||
sane.web-browser.persistData = "private";
|
||||
sane.persist.home.plaintext = [
|
||||
".config/pulse" # persist pulseaudio volume
|
||||
];
|
||||
|
||||
@@ -38,7 +36,7 @@
|
||||
];
|
||||
|
||||
sane.nixcache.enable = true;
|
||||
sane.impermanence.enable = true;
|
||||
sane.persist.enable = true;
|
||||
sane.gui.phosh.enable = true;
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.root-on-tmpfs = true;
|
||||
sane.persist.root-on-tmpfs = true;
|
||||
fileSystems."/nix" = {
|
||||
device = "/dev/disk/by-uuid/1f1271f8-53ce-4081-8a29-60a4a6b5d6f9";
|
||||
fsType = "btrfs";
|
||||
|
@@ -8,9 +8,6 @@
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||
|
||||
users.users.dhcpcd.uid = config.sane.allocations.dhcpcd-uid;
|
||||
users.groups.dhcpcd.gid = config.sane.allocations.dhcpcd-gid;
|
||||
|
||||
# docs: https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion
|
||||
system.stateVersion = "21.05";
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@
|
||||
pkgs.matrix-synapse
|
||||
pkgs.freshrss
|
||||
];
|
||||
sane.impermanence.enable = true;
|
||||
sane.persist.enable = true;
|
||||
sane.services.dyn-dns.enable = true;
|
||||
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.root-on-tmpfs = true;
|
||||
sane.persist.root-on-tmpfs = true;
|
||||
# we need a /tmp for building large nix things
|
||||
fileSystems."/tmp" = {
|
||||
device = "none";
|
||||
@@ -27,7 +27,7 @@
|
||||
};
|
||||
|
||||
# slow, external storage (for archiving, etc)
|
||||
fileSystems."/mnt/impermanence/ext" = {
|
||||
fileSystems."/mnt/persist/ext" = {
|
||||
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
||||
fsType = "btrfs";
|
||||
options = [
|
||||
@@ -36,18 +36,18 @@
|
||||
];
|
||||
};
|
||||
|
||||
sane.impermanence.stores."ext" = {
|
||||
origin = "/mnt/impermanence/ext/persist";
|
||||
sane.persist.stores."ext" = {
|
||||
origin = "/mnt/persist/ext/persist";
|
||||
storeDescription = "external HDD storage";
|
||||
};
|
||||
sane.fs."/mnt/impermanence/ext".mount = {};
|
||||
sane.fs."/mnt/persist/ext".mount = {};
|
||||
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: this is overly broad; only need media and share directories to be persisted
|
||||
{ user = "colin"; group = "users"; directory = "/var/lib/uninsane"; }
|
||||
];
|
||||
# make sure large media is stored to the HDD
|
||||
sane.impermanence.dirs.sys.ext = [
|
||||
sane.persist.sys.ext = [
|
||||
{
|
||||
user = "colin";
|
||||
group = "users";
|
||||
|
@@ -19,7 +19,7 @@
|
||||
# XXX: avatar support works in MUCs but not DMs
|
||||
# lib.mkIf false
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
{ user = "ejabberd"; group = "ejabberd"; directory = "/var/lib/ejabberd"; }
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
|
@@ -9,19 +9,17 @@
|
||||
# $ sudo -u freshrss -g freshrss FRESHRSS_DATA_PATH=/var/lib/freshrss ./result/cli/export-opml-for-user.php --user admin
|
||||
# ```
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
{
|
||||
sops.secrets.freshrss_passwd = {
|
||||
sopsFile = ../../../secrets/servo.yaml;
|
||||
owner = config.users.users.freshrss.name;
|
||||
mode = "0400";
|
||||
};
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
{ user = "freshrss"; group = "freshrss"; directory = "/var/lib/freshrss"; }
|
||||
];
|
||||
|
||||
users.users.freshrss.uid = config.sane.allocations.freshrss-uid;
|
||||
users.groups.freshrss.gid = config.sane.allocations.freshrss-gid;
|
||||
services.freshrss.enable = true;
|
||||
services.freshrss.baseUrl = "https://rss.uninsane.org";
|
||||
services.freshrss.virtualHost = "rss.uninsane.org";
|
||||
@@ -29,9 +27,11 @@
|
||||
|
||||
systemd.services.freshrss-import-feeds =
|
||||
let
|
||||
feeds = sane-lib.feeds;
|
||||
fresh = config.systemd.services.freshrss-config;
|
||||
feeds = import ../../../modules/home-manager/feeds.nix { inherit lib; };
|
||||
opml = pkgs.writeText "sane-freshrss.opml" (feeds.feedsToOpml feeds.all);
|
||||
all-feeds = config.sane.feeds;
|
||||
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
|
||||
opml = pkgs.writeText "sane-freshrss.opml" (feeds.feedsToOpml wanted-feeds);
|
||||
in {
|
||||
inherit (fresh) wantedBy environment;
|
||||
serviceConfig = {
|
||||
|
@@ -1,11 +1,10 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "git"; group = "gitea"; directory = "/var/lib/gitea"; }
|
||||
];
|
||||
users.groups.gitea.gid = config.sane.allocations.gitea-gid;
|
||||
services.gitea.enable = true;
|
||||
services.gitea.user = "git"; # default is 'gitea'
|
||||
services.gitea.database.type = "postgres";
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
lib.mkIf false # i don't actively use ipfs anymore
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "261"; group = "261"; directory = "/var/lib/ipfs"; }
|
||||
];
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? we only need this to save Indexer creds ==> migrate to config?
|
||||
{ user = "root"; group = "root"; directory = "/var/lib/jackett"; }
|
||||
];
|
||||
|
@@ -7,7 +7,7 @@ lib.mkIf false
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
1900 7359 # DLNA: https://jellyfin.org/docs/general/networking/index.html
|
||||
];
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "jellyfin"; group = "jellyfin"; directory = "/var/lib/jellyfin"; }
|
||||
];
|
||||
@@ -63,7 +63,5 @@ lib.mkIf false
|
||||
|
||||
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;
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@
|
||||
# ./irc.nix
|
||||
];
|
||||
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/matrix-synapse"; }
|
||||
];
|
||||
services.matrix-synapse.enable = true;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/mx-puppet-discord"; }
|
||||
];
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode?
|
||||
# user and group are both "matrix-appservice-irc"
|
||||
{ user = "993"; group = "992"; directory = "/var/lib/matrix-appservice-irc"; }
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: we don't have a static user allocated for navidrome!
|
||||
# the chown would happen too early for us to set static perms
|
||||
"/var/lib/private/navidrome"
|
||||
|
@@ -1,9 +1,9 @@
|
||||
# docs: https://nixos.wiki/wiki/Nginx
|
||||
{ config, pkgs, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
# make the logs for this host "public" so that they show up in e.g. metrics
|
||||
publog = vhost: vhost // {
|
||||
publog = vhost: lib.attrsets.unionOfDisjoint vhost {
|
||||
extraConfig = (vhost.extraConfig or "") + ''
|
||||
access_log /var/log/nginx/public.log vcombined;
|
||||
'';
|
||||
@@ -120,9 +120,7 @@ in
|
||||
security.acme.acceptTerms = true;
|
||||
security.acme.defaults.email = "admin.acme@uninsane.org";
|
||||
|
||||
users.users.acme.uid = config.sane.allocations.acme-uid;
|
||||
users.groups.acme.gid = config.sane.allocations.acme-gid;
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode?
|
||||
{ user = "acme"; group = "acme"; directory = "/var/lib/acme"; }
|
||||
{ user = "colin"; group = "users"; directory = "/var/www/sites"; }
|
||||
|
@@ -6,12 +6,10 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "pleroma"; group = "pleroma"; directory = "/var/lib/pleroma"; }
|
||||
];
|
||||
users.users.pleroma.uid = config.sane.allocations.pleroma-uid;
|
||||
users.groups.pleroma.gid = config.sane.allocations.pleroma-gid;
|
||||
services.pleroma.enable = true;
|
||||
services.pleroma.secretConfigFile = config.sops.secrets.pleroma_secrets.path;
|
||||
services.pleroma.configs = [
|
||||
|
@@ -16,7 +16,7 @@ let
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "opendkim"; group = "opendkim"; directory = "/var/lib/opendkim"; }
|
||||
{ user = "root"; group = "root"; directory = "/var/lib/postfix"; }
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode?
|
||||
{ user = "postgres"; group = "postgres"; directory = "/var/lib/postgresql"; }
|
||||
];
|
||||
|
@@ -9,7 +9,7 @@
|
||||
# nixnet runs ejabberd, so revisiting that.
|
||||
lib.mkIf false
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
{ user = "prosody"; group = "prosody"; directory = "/var/lib/prosody"; }
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
sane.impermanence.dirs.sys.plaintext = [
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? we need this specifically for the stats tracking in .config/
|
||||
{ user = "transmission"; group = "transmission"; directory = "/var/lib/transmission"; }
|
||||
];
|
||||
|
@@ -11,8 +11,6 @@ lib.mkIf false
|
||||
sopsFile = ../../../secrets/servo.yaml;
|
||||
};
|
||||
|
||||
users.users.mediawiki.uid = config.sane.allocations.mediawiki-uid;
|
||||
|
||||
services.mediawiki.enable = true;
|
||||
services.mediawiki.name = "Uninsane Wiki";
|
||||
services.mediawiki.passwordFile = config.sops.secrets.mediawiki_pw.path;
|
||||
|
@@ -12,7 +12,6 @@
|
||||
home = "/var/lib/gitea";
|
||||
useDefaultShell = true;
|
||||
group = "gitea";
|
||||
uid = config.sane.allocations.git-uid;
|
||||
isSystemUser = true;
|
||||
# sendmail access (not 100% sure if this is necessary)
|
||||
extraGroups = [ "postdrop" ];
|
||||
|
@@ -1,63 +0,0 @@
|
||||
{ lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
mkId = id: mkOption {
|
||||
default = id;
|
||||
type = types.int;
|
||||
};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
# legacy servo users, some are inconvenient to migrate
|
||||
sane.allocations.dhcpcd-gid = mkId 991;
|
||||
sane.allocations.dhcpcd-uid = mkId 992;
|
||||
sane.allocations.gitea-gid = mkId 993;
|
||||
sane.allocations.git-uid = mkId 994;
|
||||
sane.allocations.jellyfin-gid = mkId 994;
|
||||
sane.allocations.pleroma-gid = mkId 995;
|
||||
sane.allocations.jellyfin-uid = mkId 996;
|
||||
sane.allocations.acme-gid = mkId 996;
|
||||
sane.allocations.pleroma-uid = mkId 997;
|
||||
sane.allocations.acme-uid = mkId 998;
|
||||
sane.allocations.greeter-uid = mkId 999;
|
||||
sane.allocations.greeter-gid = mkId 999;
|
||||
|
||||
# new servo users
|
||||
sane.allocations.freshrss-uid = mkId 2401;
|
||||
sane.allocations.freshrss-gid = mkId 2401;
|
||||
sane.allocations.mediawiki-uid = mkId 2402;
|
||||
|
||||
sane.allocations.colin-uid = mkId 1000;
|
||||
sane.allocations.guest-uid = mkId 1100;
|
||||
|
||||
# found on all hosts
|
||||
sane.allocations.sshd-uid = mkId 2001; # 997
|
||||
sane.allocations.sshd-gid = mkId 2001; # 997
|
||||
sane.allocations.polkituser-gid = mkId 2002; # 998
|
||||
sane.allocations.systemd-coredump-gid = mkId 2003; # 996
|
||||
sane.allocations.nscd-uid = mkId 2004;
|
||||
sane.allocations.nscd-gid = mkId 2004;
|
||||
sane.allocations.systemd-oom-uid = mkId 2005;
|
||||
sane.allocations.systemd-oom-gid = mkId 2005;
|
||||
|
||||
# found on graphical hosts
|
||||
sane.allocations.nm-iodine-uid = mkId 2101; # desko/moby/lappy
|
||||
|
||||
# found on desko host
|
||||
sane.allocations.usbmux-uid = mkId 2204;
|
||||
sane.allocations.usbmux-gid = mkId 2204;
|
||||
|
||||
|
||||
# originally found on moby host
|
||||
sane.allocations.avahi-uid = mkId 2304;
|
||||
sane.allocations.avahi-gid = mkId 2304;
|
||||
sane.allocations.colord-uid = mkId 2305;
|
||||
sane.allocations.colord-gid = mkId 2305;
|
||||
sane.allocations.geoclue-uid = mkId 2306;
|
||||
sane.allocations.geoclue-gid = mkId 2306;
|
||||
sane.allocations.rtkit-uid = mkId 2307;
|
||||
sane.allocations.rtkit-gid = mkId 2307;
|
||||
sane.allocations.feedbackd-gid = mkId 2308;
|
||||
};
|
||||
}
|
12
modules/data/default.nix
Normal file
12
modules/data/default.nix
Normal file
@@ -0,0 +1,12 @@
|
||||
# this directory contains data of a factual nature.
|
||||
# for example, public ssh keys, GPG keys, DNS-type name mappings.
|
||||
#
|
||||
# don't put things like fully-specific ~/.config files in here,
|
||||
# even if they're "relatively unopinionated".
|
||||
|
||||
moduleArgs:
|
||||
|
||||
{
|
||||
feeds = import ./feeds moduleArgs;
|
||||
keys = import ./keys.nix;
|
||||
}
|
58
modules/data/feeds/default.nix
Normal file
58
modules/data/feeds/default.nix
Normal file
@@ -0,0 +1,58 @@
|
||||
{ lib, ... }:
|
||||
|
||||
let
|
||||
inherit (builtins) concatLists concatStringsSep foldl' fromJSON map readDir readFile;
|
||||
inherit (lib) init mapAttrsToList removePrefix removeSuffix splitString;
|
||||
inherit (lib.attrsets) recursiveUpdate setAttrByPath;
|
||||
inherit (lib.filesystem) listFilesRecursive;
|
||||
|
||||
# given a path to a .json file relative to sources, construct the best feed object we can.
|
||||
# the .json file could be empty, in which case we make assumptions about the feed based
|
||||
# on its fs path.
|
||||
# Type: feedFromSourcePath :: String -> { path = [String]; value = feed; }
|
||||
feedFromSourcePath = json-path:
|
||||
let
|
||||
canonical-name = removeSuffix "/default" (lib.removeSuffix ".json" json-path);
|
||||
default-url = "https://${canonical-name}";
|
||||
attr-path = splitString "/" canonical-name;
|
||||
feed-details = { url = default-url; } // (tryImportJson (./sources/${json-path}));
|
||||
in { path = attr-path; value = mkFeed feed-details; };
|
||||
|
||||
# TODO: for now, feeds are just ordinary Attrs.
|
||||
# in the future, we'd like to set them up with an update script.
|
||||
mkFeed = { url, ... }@details: details;
|
||||
|
||||
# return an AttrSet representing the json at the provided path,
|
||||
# or {} if the path is empty.
|
||||
tryImportJson = path:
|
||||
let
|
||||
as-str = readFile path;
|
||||
in
|
||||
if as-str == "" then
|
||||
{}
|
||||
else
|
||||
fromJSON as-str;
|
||||
|
||||
sources = enumerateFilePaths ./sources;
|
||||
|
||||
# like `lib.listFilesRecursive` but does not mangle paths.
|
||||
# Type: enumerateFilePaths :: path -> [String]
|
||||
enumerateFilePaths = base:
|
||||
concatLists (
|
||||
mapAttrsToList
|
||||
(name: type:
|
||||
if type == "directory" then
|
||||
# enumerate this directory and then prefix each result with the directory's name
|
||||
map (e: "${name}/${e}") (enumerateFilePaths (base + "/${name}"))
|
||||
else
|
||||
[ name ]
|
||||
)
|
||||
(readDir base)
|
||||
);
|
||||
|
||||
# like listToAttrs, except takes { path, value } pairs instead of { name, value } pairs.
|
||||
# Type: listToAttrsByPath :: [{ path = [String]; value = Any; }] -> Attrs
|
||||
listToAttrsByPath = items:
|
||||
foldl' (acc: { path, value }: recursiveUpdate acc (setAttrByPath path value)) {} items;
|
||||
in
|
||||
listToAttrsByPath (map feedFromSourcePath sources)
|
0
modules/data/feeds/sources/xkcd.com/default.json
Normal file
0
modules/data/feeds/sources/xkcd.com/default.json
Normal file
24
modules/data/keys.nix
Normal file
24
modules/data/keys.nix
Normal file
@@ -0,0 +1,24 @@
|
||||
# hierarchical, DNS-like mapping from <name> => ssh host/user for that name.
|
||||
# host keys are represented as user keys, just with the user specified as "root".
|
||||
|
||||
{
|
||||
org.uninsane = rec {
|
||||
root = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOfdSmFkrVT6DhpgvFeQKm3Fh9VKZ9DbLYOPOJWYQ0E8";
|
||||
git.root = root;
|
||||
|
||||
local = {
|
||||
# machine aliases i specify on my lan; not actually asserted as DNS
|
||||
desko.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPU5GlsSfbaarMvDA20bxpSZGWviEzXGD8gtrIowc1pX";
|
||||
desko.root = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFw9NoRaYrM6LbDd3aFBc4yyBlxGQn8HjeHd/dZ3CfHk";
|
||||
|
||||
lappy.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDpmFdNSVPRol5hkbbCivRhyeENzb9HVyf9KutGLP2Zu";
|
||||
lappy.root = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILSJnqmVl9/SYQ0btvGb0REwwWY8wkdkGXQZfn/1geEc";
|
||||
|
||||
moby.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrR+gePnl0nV/vy7I5BzrGeyVL+9eOuXHU1yNE3uCwU";
|
||||
moby.root = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1N/IT3nQYUD+dBlU1sTEEVMxfOyMkrrDeyHcYgnJvw";
|
||||
|
||||
servo.colin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPS1qFzKurAdB9blkWomq8gI1g0T3sTs9LsmFOj5VtqX";
|
||||
servo.root = root;
|
||||
};
|
||||
};
|
||||
}
|
@@ -2,19 +2,22 @@
|
||||
|
||||
{
|
||||
imports = [
|
||||
./allocations.nix
|
||||
./feeds.nix
|
||||
./fs
|
||||
./gui
|
||||
./home-manager
|
||||
./ids.nix
|
||||
./packages.nix
|
||||
./image.nix
|
||||
./impermanence
|
||||
./nixcache.nix
|
||||
./persist
|
||||
./services
|
||||
./sops.nix
|
||||
./ssh.nix
|
||||
];
|
||||
|
||||
_module.args = {
|
||||
sane-lib = import ./lib { inherit lib utils; };
|
||||
sane-data = import ./data { inherit lib; };
|
||||
};
|
||||
}
|
||||
|
51
modules/feeds.nix
Normal file
51
modules/feeds.nix
Normal file
@@ -0,0 +1,51 @@
|
||||
{ lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
feed = types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
freq = mkOption {
|
||||
type = types.enum [ "hourly" "daily" "weekly" "infrequent" ];
|
||||
default = "infrequent";
|
||||
};
|
||||
cat = mkOption {
|
||||
type = types.enum [ "art" "humor" "pol" "rat" "tech" "uncat" ];
|
||||
default = "uncat";
|
||||
};
|
||||
format = mkOption {
|
||||
type = types.enum [ "text" "image" "podcast" ];
|
||||
default = "text";
|
||||
};
|
||||
url = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
url to a RSS feed
|
||||
'';
|
||||
};
|
||||
substack = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
if the feed is a substack domain, just enter the subdomain here and the url/format field can be populated automatically
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = lib.mkIf (config.substack != null) {
|
||||
url = "https://${config.substack}.substack.com/feed";
|
||||
format = "text";
|
||||
};
|
||||
});
|
||||
in
|
||||
{
|
||||
# we don't explicitly generate anything from the feeds here.
|
||||
# instead, config.sane.feeds is used by a variety of services at their definition site.
|
||||
options = {
|
||||
sane.feeds = mkOption {
|
||||
type = types.listOf feed;
|
||||
default = [];
|
||||
description = ''
|
||||
RSS feeds indexed by a human-readable name.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
@@ -21,7 +21,7 @@ let
|
||||
default = null;
|
||||
};
|
||||
symlink = mkOption {
|
||||
type = types.nullOr symlinkEntry;
|
||||
type = types.nullOr (symlinkEntryFor name);
|
||||
default = null;
|
||||
};
|
||||
generated = mkOption {
|
||||
@@ -81,6 +81,9 @@ let
|
||||
# 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.
|
||||
@@ -108,15 +111,25 @@ let
|
||||
# takes no special options
|
||||
dirEntry = types.submodule propagatedGenerateMod;
|
||||
|
||||
symlinkEntry = types.submodule {
|
||||
symlinkEntryFor = path: types.submodule ({ config, ...}: {
|
||||
options = {
|
||||
inherit (propagatedGenerateMod.options) acl;
|
||||
target = mkOption {
|
||||
type = types.str;
|
||||
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 = {
|
||||
@@ -222,25 +235,11 @@ let
|
||||
});
|
||||
|
||||
|
||||
mkFsConfig = path: opt: mergeTopLevel [
|
||||
mkFsConfig = path: opt: lib.mkMerge [
|
||||
(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"
|
||||
@@ -295,19 +294,17 @@ let
|
||||
lnfrom="$1"
|
||||
lnto="$2"
|
||||
|
||||
ln -sf --no-dereference "$lnto" "$lnfrom"
|
||||
# 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" ]
|
||||
# TODO: move this to path-lib?
|
||||
ancestorsOf = path: if path-lib.hasParent path then
|
||||
ancestorsOf (path-lib.parent path) ++ [ (path-lib.parent path) ]
|
||||
else
|
||||
[ ]
|
||||
;
|
||||
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"`
|
||||
@@ -349,5 +346,12 @@ in {
|
||||
};
|
||||
};
|
||||
|
||||
config = mergeTopLevel (lib.mapAttrsToList mkFsConfig cfg);
|
||||
config =
|
||||
let
|
||||
configs = lib.mapAttrsToList mkFsConfig cfg;
|
||||
take = f: {
|
||||
systemd.services = f.systemd.services;
|
||||
fileSystems = f.fileSystems;
|
||||
};
|
||||
in take (sane-lib.mkTypedMerge take configs);
|
||||
}
|
||||
|
@@ -23,7 +23,9 @@ in
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
sane.packages.enableGuiPkgs = lib.mkDefault true;
|
||||
# all GUIs use network manager?
|
||||
users.users.nm-iodine.uid = config.sane.allocations.nm-iodine-uid;
|
||||
|
||||
# preserve backlight brightness across power cycles
|
||||
# see `man systemd-backlight`
|
||||
sane.persist.sys.plaintext = [ "/var/lib/systemd/backlight" ];
|
||||
};
|
||||
}
|
||||
|
@@ -15,15 +15,6 @@ in
|
||||
config = mkIf cfg.enable {
|
||||
sane.gui.enable = true;
|
||||
|
||||
users.users.avahi.uid = config.sane.allocations.avahi-uid;
|
||||
users.groups.avahi.gid = config.sane.allocations.avahi-gid;
|
||||
users.users.colord.uid = config.sane.allocations.colord-uid;
|
||||
users.groups.colord.gid = config.sane.allocations.colord-gid;
|
||||
users.users.geoclue.uid = config.sane.allocations.geoclue-uid;
|
||||
users.groups.geoclue.gid = config.sane.allocations.geoclue-gid;
|
||||
users.users.rtkit.uid = config.sane.allocations.rtkit-uid;
|
||||
users.groups.rtkit.gid = config.sane.allocations.rtkit-gid;
|
||||
|
||||
# start gnome/gdm on boot
|
||||
services.xserver.enable = true;
|
||||
services.xserver.desktopManager.gnome.enable = true;
|
||||
|
@@ -24,16 +24,6 @@ in
|
||||
{
|
||||
sane.gui.enable = true;
|
||||
|
||||
users.users.avahi.uid = config.sane.allocations.avahi-uid;
|
||||
users.users.colord.uid = config.sane.allocations.colord-uid;
|
||||
users.users.geoclue.uid = config.sane.allocations.geoclue-uid;
|
||||
users.users.rtkit.uid = config.sane.allocations.rtkit-uid;
|
||||
users.groups.avahi.gid = config.sane.allocations.avahi-gid;
|
||||
users.groups.colord.gid = config.sane.allocations.colord-gid;
|
||||
users.groups.feedbackd.gid = config.sane.allocations.feedbackd-gid;
|
||||
users.groups.geoclue.gid = config.sane.allocations.geoclue-gid;
|
||||
users.groups.rtkit.gid = config.sane.allocations.rtkit-gid;
|
||||
|
||||
# docs: https://github.com/NixOS/nixpkgs/blob/nixos-22.05/nixos/modules/services/x11/desktop-managers/phosh.nix
|
||||
services.xserver.desktopManager.phosh = {
|
||||
enable = true;
|
||||
|
@@ -2,6 +2,7 @@ https://search.nixos.org/options?channel=unstable&query=
|
||||
https://search.nixos.org/packages?channel=unstable&query=
|
||||
https://nixos.wiki/index.php?go=Go&search=
|
||||
https://github.com/nixos/nixpkgs/pulls?q=
|
||||
https://nur.nix-community.org/
|
||||
https://nix-community.github.io/home-manager/options.html
|
||||
https://w.uninsane.org/viewer#search?books.name=wikipedia_en_all_maxi_2022-05&pattern=
|
||||
https://jackett.uninsane.org/UI/Dashboard#search=
|
||||
|
@@ -22,14 +22,15 @@ in
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
sane.gui.enable = true;
|
||||
users.users.greeter.uid = config.sane.allocations.greeter-uid;
|
||||
users.groups.greeter.gid = config.sane.allocations.greeter-gid;
|
||||
|
||||
programs.sway = {
|
||||
# we configure sway with home-manager, but this enable gets us e.g. opengl and fonts
|
||||
enable = true;
|
||||
};
|
||||
|
||||
# alternatively, could use SDDM
|
||||
# instead of using `services.greetd`, can instead use SDDM by swapping in these lines.
|
||||
# services.xserver.displayManager.sddm.enable = true;
|
||||
# services.xserver.enable = true;
|
||||
services.greetd = let
|
||||
swayConfig-greeter = pkgs.writeText "greetd-sway-config" ''
|
||||
# `-l` activates layer-shell mode.
|
||||
@@ -71,13 +72,24 @@ in
|
||||
pulse.enable = true;
|
||||
};
|
||||
|
||||
hardware.bluetooth.enable = true;
|
||||
services.blueman.enable = true;
|
||||
|
||||
networking.useDHCP = false;
|
||||
networking.networkmanager.enable = true;
|
||||
networking.wireless.enable = lib.mkForce false;
|
||||
|
||||
hardware.bluetooth.enable = true;
|
||||
services.blueman.enable = true;
|
||||
# gsd provides Rfkill, which is required for the bluetooth pane in gnome-control-center to work
|
||||
services.gnome.gnome-settings-daemon.enable = true;
|
||||
# start the components of gsd we need at login
|
||||
systemd.user.targets."org.gnome.SettingsDaemon.Rfkill".wantedBy = [ "graphical-session.target" ];
|
||||
# go ahead and `systemctl --user cat gnome-session-initialized.target`. i dare you.
|
||||
# the only way i can figure out how to get Rfkill to actually load is to just disable all the shit it depends on.
|
||||
# it doesn't actually seem to need ANY of them in the first place T_T
|
||||
systemd.user.targets."gnome-session-initialized".enable = false;
|
||||
# bluez can't connect to audio devices unless pipewire is running.
|
||||
# a system service can't depend on a user service, so just launch it at graphical-session
|
||||
systemd.user.services."pipewire".wantedBy = [ "graphical-session.target" ];
|
||||
|
||||
sane.home-manager.windowManager.sway = {
|
||||
enable = true;
|
||||
wrapperFeatures.gtk = true;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Terminal UI mail client
|
||||
{ config, lib, ... }:
|
||||
{ config, lib, sane-lib, ... }:
|
||||
|
||||
lib.mkIf config.sane.home-manager.enable
|
||||
{
|
||||
@@ -8,9 +8,5 @@ lib.mkIf config.sane.home-manager.enable
|
||||
sopsFile = ../../secrets/universal/aerc_accounts.conf;
|
||||
format = "binary";
|
||||
};
|
||||
home-manager.users.colin = let sysconfig = config; in { config, ... }: {
|
||||
# aerc TUI mail client
|
||||
xdg.configFile."aerc/accounts.conf".source =
|
||||
config.lib.file.mkOutOfStoreSymlink sysconfig.sops.secrets.aerc_accounts.path;
|
||||
};
|
||||
sane.fs."/home/colin/.config/aerc/accounts.conf" = sane-lib.fs.wantedSymlinkTo config.sops.secrets.aerc_accounts.path;
|
||||
}
|
||||
|
@@ -11,17 +11,19 @@ let
|
||||
cfg = config.sane.home-manager;
|
||||
# extract `pkg` from `sane.packages.enabledUserPkgs`
|
||||
pkg-list = pkgspec: builtins.map (e: e.pkg) pkgspec;
|
||||
feeds = import ./feeds.nix { inherit lib; };
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./aerc.nix
|
||||
./firefox.nix
|
||||
./gfeeds.nix
|
||||
./git.nix
|
||||
./gpodder.nix
|
||||
./keyring.nix
|
||||
./kitty.nix
|
||||
./mpv.nix
|
||||
./nb.nix
|
||||
./neovim.nix
|
||||
./newsflash.nix
|
||||
./splatmoji.nix
|
||||
./ssh.nix
|
||||
./sublime-music.nix
|
||||
@@ -67,14 +69,6 @@ in
|
||||
home.username = "colin";
|
||||
home.homeDirectory = "/home/colin";
|
||||
|
||||
home.activation = {
|
||||
initKeyring = {
|
||||
after = ["writeBoundary"];
|
||||
before = [];
|
||||
data = "${../../scripts/init-keyring}";
|
||||
};
|
||||
};
|
||||
|
||||
# XDG defines things like ~/Desktop, ~/Downloads, etc.
|
||||
# these clutter the home, so i mostly don't use them.
|
||||
xdg.userDirs = {
|
||||
@@ -94,7 +88,7 @@ in
|
||||
# - `xdg-mime query filetype path/to/thing.ext`
|
||||
xdg.mimeApps.enable = true;
|
||||
xdg.mimeApps.defaultApplications = let
|
||||
www = sysconfig.sane.web-browser.desktop;
|
||||
www = sysconfig.sane.web-browser.browser.desktop;
|
||||
pdf = "org.gnome.Evince.desktop";
|
||||
md = "obsidian.desktop";
|
||||
thumb = "org.gnome.gThumb.desktop";
|
||||
@@ -137,54 +131,14 @@ in
|
||||
# <item oor:path="/org.openoffice.Setup/Product"><prop oor:name="LastTimeGetInvolvedShown" oor:op="fuse"><value>1667693880</value></prop></item>
|
||||
|
||||
|
||||
|
||||
xdg.configFile."gpodderFeeds.opml".text = with feeds;
|
||||
feedsToOpml feeds.podcasts;
|
||||
|
||||
# news-flash RSS viewer
|
||||
xdg.configFile."newsflashFeeds.opml".text = with feeds;
|
||||
feedsToOpml (feeds.texts ++ feeds.images);
|
||||
|
||||
# gnome feeds RSS viewer
|
||||
xdg.configFile."org.gabmus.gfeeds.json".text =
|
||||
let
|
||||
myFeeds = feeds.texts ++ feeds.images;
|
||||
in builtins.toJSON {
|
||||
# feed format is a map from URL to a dict,
|
||||
# with dict["tags"] a list of string tags.
|
||||
feeds = builtins.foldl' (acc: feed: acc // {
|
||||
"${feed.url}".tags = [ feed.cat feed.freq ];
|
||||
}) {} myFeeds;
|
||||
dark_reader = false;
|
||||
new_first = true;
|
||||
# windowsize = {
|
||||
# width = 350;
|
||||
# height = 650;
|
||||
# };
|
||||
max_article_age_days = 90;
|
||||
enable_js = false;
|
||||
max_refresh_threads = 3;
|
||||
# saved_items = {};
|
||||
# read_items = [];
|
||||
show_read_items = true;
|
||||
full_article_title = true;
|
||||
# views: "webview", "reader", "rsscont"
|
||||
default_view = "rsscont";
|
||||
open_links_externally = true;
|
||||
full_feed_name = false;
|
||||
refresh_on_startup = true;
|
||||
tags = lib.lists.unique (
|
||||
(builtins.catAttrs "cat" myFeeds) ++ (builtins.catAttrs "freq" myFeeds)
|
||||
);
|
||||
open_youtube_externally = false;
|
||||
media_player = "vlc"; # default: mpv
|
||||
};
|
||||
|
||||
programs = {
|
||||
home-manager.enable = true; # this lets home-manager manage dot-files in user dirs, i think
|
||||
# "command not found" will cause the command to be searched in nixpkgs
|
||||
nix-index.enable = true;
|
||||
} // cfg.programs;
|
||||
programs = lib.mkMerge [
|
||||
{
|
||||
home-manager.enable = true; # this lets home-manager manage dot-files in user dirs, i think
|
||||
# "command not found" will cause the command to be searched in nixpkgs
|
||||
nix-index.enable = true;
|
||||
}
|
||||
cfg.programs
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
# many of the settings below won't have effect without those patches.
|
||||
# see: https://gitlab.com/librewolf-community/settings/-/blob/master/distribution/policies.json
|
||||
|
||||
{ config, lib, pkgs, ...}:
|
||||
{ config, lib, pkgs, sane-lib, ...}:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.sane.web-browser;
|
||||
@@ -32,11 +32,11 @@ let
|
||||
defaultSettings = firefoxSettings;
|
||||
# defaultSettings = librewolfSettings;
|
||||
|
||||
package = pkgs.wrapFirefox cfg.browser {
|
||||
package = pkgs.wrapFirefox cfg.browser.browser {
|
||||
# inherit the default librewolf.cfg
|
||||
# it can be further customized via ~/.librewolf/librewolf.overrides.cfg
|
||||
inherit (pkgs.librewolf-unwrapped) extraPrefsFiles;
|
||||
inherit (cfg) libName;
|
||||
inherit (cfg.browser) libName;
|
||||
|
||||
extraNativeMessagingHosts = [ pkgs.browserpass ];
|
||||
# extraNativeMessagingHosts = [ pkgs.gopass-native-messaging-host ];
|
||||
@@ -105,43 +105,57 @@ let
|
||||
in
|
||||
{
|
||||
options = {
|
||||
sane.web-browser = mkOption {
|
||||
sane.web-browser.browser = mkOption {
|
||||
default = defaultSettings;
|
||||
type = types.attrs;
|
||||
};
|
||||
sane.web-browser.persistData = mkOption {
|
||||
description = "optional store name to which persist browsing data (like history)";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
sane.web-browser.persistCache = mkOption {
|
||||
description = "optional store name to which persist browser cache";
|
||||
type = types.nullOr types.str;
|
||||
default = "cryptClearOnBoot";
|
||||
};
|
||||
};
|
||||
config = lib.mkIf config.sane.home-manager.enable {
|
||||
# XXX: although home-manager calls this option `firefox`, we can use other browsers and it still mostly works.
|
||||
home-manager.users.colin = lib.mkIf (config.sane.gui.enable) {
|
||||
programs.firefox = {
|
||||
enable = true;
|
||||
inherit package;
|
||||
};
|
||||
|
||||
# uBlock filter list configuration.
|
||||
# specifically, enable the GDPR cookie prompt blocker.
|
||||
# data.toOverwrite.filterLists is additive (i.e. it supplements the default filters)
|
||||
# this configuration method is documented here:
|
||||
# - <https://github.com/gorhill/uBlock/issues/2986#issuecomment-364035002>
|
||||
# the specific attribute path is found via scraping ublock code here:
|
||||
# - <https://github.com/gorhill/uBlock/blob/master/src/js/storage.js>
|
||||
# - <https://github.com/gorhill/uBlock/blob/master/assets/assets.json>
|
||||
home.file."${cfg.dotDir}/managed-storage/uBlock0@raymondhill.net.json".text = ''
|
||||
{
|
||||
"name": "uBlock0@raymondhill.net",
|
||||
"description": "ignored",
|
||||
"type": "storage",
|
||||
"data": {
|
||||
"toOverwrite": "{\"filterLists\": [\"fanboy-cookiemonster\"]}"
|
||||
}
|
||||
}
|
||||
'';
|
||||
home.file."${cfg.dotDir}/${cfg.libName}.overrides.cfg".text = ''
|
||||
// if we can't query the revocation status of a SSL cert because the issuer is offline,
|
||||
// treat it as unrevoked.
|
||||
// see: <https://librewolf.net/docs/faq/#im-getting-sec_error_ocsp_server_error-what-can-i-do>
|
||||
defaultPref("security.OCSP.require", false);
|
||||
'';
|
||||
config = lib.mkIf config.sane.home-manager.enable {
|
||||
|
||||
# uBlock filter list configuration.
|
||||
# specifically, enable the GDPR cookie prompt blocker.
|
||||
# data.toOverwrite.filterLists is additive (i.e. it supplements the default filters)
|
||||
# this configuration method is documented here:
|
||||
# - <https://github.com/gorhill/uBlock/issues/2986#issuecomment-364035002>
|
||||
# the specific attribute path is found via scraping ublock code here:
|
||||
# - <https://github.com/gorhill/uBlock/blob/master/src/js/storage.js>
|
||||
# - <https://github.com/gorhill/uBlock/blob/master/assets/assets.json>
|
||||
sane.fs."/home/colin/${cfg.browser.dotDir}/managed-storage/uBlock0@raymondhill.net.json" = sane-lib.fs.wantedText ''
|
||||
{
|
||||
"name": "uBlock0@raymondhill.net",
|
||||
"description": "ignored",
|
||||
"type": "storage",
|
||||
"data": {
|
||||
"toOverwrite": "{\"filterLists\": [\"fanboy-cookiemonster\"]}"
|
||||
}
|
||||
}
|
||||
'';
|
||||
sane.fs."/home/colin/${cfg.browser.dotDir}/${cfg.browser.libName}.overrides.cfg" = sane-lib.fs.wantedText ''
|
||||
// if we can't query the revocation status of a SSL cert because the issuer is offline,
|
||||
// treat it as unrevoked.
|
||||
// see: <https://librewolf.net/docs/faq/#im-getting-sec_error_ocsp_server_error-what-can-i-do>
|
||||
defaultPref("security.OCSP.require", false);
|
||||
'';
|
||||
|
||||
sane.packages.extraGuiPkgs = [ package ];
|
||||
# flood the cache to disk to avoid it taking up too much tmp
|
||||
sane.persist.home.byPath."${cfg.browser.cacheDir}" = lib.mkIf (cfg.persistCache != null) {
|
||||
store = cfg.persistCache;
|
||||
};
|
||||
|
||||
sane.persist.home.byPath."${cfg.browser.dotDir}" = lib.mkIf (cfg.persistData != null) {
|
||||
store = cfg.persistData;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
42
modules/home-manager/gfeeds.nix
Normal file
42
modules/home-manager/gfeeds.nix
Normal file
@@ -0,0 +1,42 @@
|
||||
# gnome feeds RSS viewer
|
||||
{ config, lib, sane-lib, ... }:
|
||||
|
||||
let
|
||||
feeds = sane-lib.feeds;
|
||||
all-feeds = config.sane.feeds;
|
||||
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
|
||||
in {
|
||||
sane.fs."/home/colin/.config/org.gabmus.gfeeds.json" = sane-lib.fs.wantedText (
|
||||
builtins.toJSON {
|
||||
# feed format is a map from URL to a dict,
|
||||
# with dict["tags"] a list of string tags.
|
||||
feeds = sane-lib.mapToAttrs (feed: {
|
||||
name = feed.url;
|
||||
value.tags = [ feed.cat feed.freq ];
|
||||
}) wanted-feeds;
|
||||
dark_reader = false;
|
||||
new_first = true;
|
||||
# windowsize = {
|
||||
# width = 350;
|
||||
# height = 650;
|
||||
# };
|
||||
max_article_age_days = 90;
|
||||
enable_js = false;
|
||||
max_refresh_threads = 3;
|
||||
# saved_items = {};
|
||||
# read_items = [];
|
||||
show_read_items = true;
|
||||
full_article_title = true;
|
||||
# views: "webview", "reader", "rsscont"
|
||||
default_view = "rsscont";
|
||||
open_links_externally = true;
|
||||
full_feed_name = false;
|
||||
refresh_on_startup = true;
|
||||
tags = lib.unique (
|
||||
(builtins.catAttrs "cat" wanted-feeds) ++ (builtins.catAttrs "freq" wanted-feeds)
|
||||
);
|
||||
open_youtube_externally = false;
|
||||
media_player = "vlc"; # default: mpv
|
||||
}
|
||||
);
|
||||
}
|
12
modules/home-manager/gpodder.nix
Normal file
12
modules/home-manager/gpodder.nix
Normal file
@@ -0,0 +1,12 @@
|
||||
# gnome feeds RSS viewer
|
||||
{ config, sane-lib, ... }:
|
||||
|
||||
let
|
||||
feeds = sane-lib.feeds;
|
||||
all-feeds = config.sane.feeds;
|
||||
wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds;
|
||||
in {
|
||||
sane.fs."/home/colin/.config/gpodderFeeds.opml" = sane-lib.fs.wantedText (
|
||||
feeds.feedsToOpml wanted-feeds
|
||||
);
|
||||
}
|
11
modules/home-manager/keyring.nix
Normal file
11
modules/home-manager/keyring.nix
Normal file
@@ -0,0 +1,11 @@
|
||||
{ config, lib, sane-lib, ... }:
|
||||
|
||||
lib.mkIf config.sane.home-manager.enable
|
||||
{
|
||||
sane.persist.home.private = [ ".local/share/keyrings" ];
|
||||
|
||||
sane.fs."/home/colin/private/.local/share/keyrings/default" = {
|
||||
generated.script.script = builtins.readFile ../../scripts/init-keyring;
|
||||
wantedBy = [ config.sane.fs."/home/colin/private".unit ];
|
||||
};
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
# nb is a CLI-drive Personal Knowledge Manager
|
||||
# - <https://xwmx.github.io/nb/>
|
||||
#
|
||||
# it's pretty opinionated:
|
||||
# - autocommits (to git) excessively (disable-able)
|
||||
# - inserts its own index files to give deterministic names to files
|
||||
#
|
||||
# it offers a primitive web-server
|
||||
# and it offers some CLI query tools
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# lib.mkIf config.sane.home-manager.enable
|
||||
lib.mkIf false # XXX disabled!
|
||||
{
|
||||
sane.packages.extraUserPkgs = [ pkgs.nb ];
|
||||
|
||||
home-manager.users.colin = { config, ... }: {
|
||||
# nb markdown/personal knowledge manager
|
||||
home.file.".nb/knowledge".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/knowledge";
|
||||
home.file.".nb/.current".text = "knowledge";
|
||||
home.file.".nbrc".text = ''
|
||||
# manage with `nb settings`
|
||||
export NB_AUTO_SYNC=0
|
||||
'';
|
||||
};
|
||||
}
|
@@ -3,7 +3,7 @@
|
||||
lib.mkIf config.sane.home-manager.enable
|
||||
{
|
||||
# private because there could be sensitive things in the swap
|
||||
sane.impermanence.dirs.home.private = [ ".cache/vim-swap" ];
|
||||
sane.persist.home.private = [ ".cache/vim-swap" ];
|
||||
|
||||
home-manager.users.colin.programs.neovim = {
|
||||
# neovim: https://github.com/neovim/neovim
|
||||
|
12
modules/home-manager/newsflash.nix
Normal file
12
modules/home-manager/newsflash.nix
Normal file
@@ -0,0 +1,12 @@
|
||||
# news-flash RSS viewer
|
||||
{ config, sane-lib, ... }:
|
||||
|
||||
let
|
||||
feeds = sane-lib.feeds;
|
||||
all-feeds = config.sane.feeds;
|
||||
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
|
||||
in {
|
||||
sane.fs."/home/colin/.config/newsflashFeeds.opml" = sane-lib.fs.wantedText (
|
||||
feeds.feedsToOpml wanted-feeds
|
||||
);
|
||||
}
|
@@ -1,20 +1,19 @@
|
||||
# borrows from:
|
||||
# - default config: <https://github.com/cspeterson/splatmoji/blob/master/splatmoji.config>
|
||||
# - wayland: <https://github.com/cspeterson/splatmoji/issues/32#issuecomment-830862566>
|
||||
{ pkgs, ... }:
|
||||
{ pkgs, sane-lib, ... }:
|
||||
|
||||
{
|
||||
home-manager.users.colin = {
|
||||
xdg.configFile."splatmoji/splatmoji.config".text = ''
|
||||
history_file=/home/colin/.local/state/splatmoji/history
|
||||
history_length=5
|
||||
# TODO: wayland equiv
|
||||
paste_command=xdotool key ctrl+v
|
||||
# rofi_command=${pkgs.wofi}/bin/wofi --dmenu --insensitive --cache-file /dev/null
|
||||
rofi_command=${pkgs.fuzzel}/bin/fuzzel -d -i -w 60
|
||||
xdotool_command=${pkgs.wtype}/bin/wtype
|
||||
# TODO: wayland equiv
|
||||
xsel_command=xsel -b -i
|
||||
'';
|
||||
};
|
||||
sane.persist.home.plaintext = [ ".local/state/splatmoji" ];
|
||||
sane.fs."/home/colin/.config/splatmoji/splatmoji.config" = sane-lib.fs.wantedText ''
|
||||
history_file=/home/colin/.local/state/splatmoji/history
|
||||
history_length=5
|
||||
# TODO: wayland equiv
|
||||
paste_command=xdotool key ctrl+v
|
||||
# rofi_command=${pkgs.wofi}/bin/wofi --dmenu --insensitive --cache-file /dev/null
|
||||
rofi_command=${pkgs.fuzzel}/bin/fuzzel -d -i -w 60
|
||||
xdotool_command=${pkgs.wtype}/bin/wtype
|
||||
# TODO: wayland equiv
|
||||
xsel_command=xsel -b -i
|
||||
'';
|
||||
}
|
||||
|
@@ -1,20 +1,23 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
|
||||
lib.mkIf config.sane.home-manager.enable
|
||||
{
|
||||
home-manager.users.colin = let
|
||||
host = config.networking.hostName;
|
||||
user_pubkey = (import ../pubkeys.nix).users."${host}";
|
||||
known_hosts_text = builtins.concatStringsSep
|
||||
"\n"
|
||||
(builtins.attrValues (import ../pubkeys.nix).hosts);
|
||||
in { config, ...}: {
|
||||
# ssh key is stored in private storage
|
||||
home.file.".ssh/id_ed25519".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/private/.ssh/id_ed25519";
|
||||
home.file.".ssh/id_ed25519.pub".text = user_pubkey;
|
||||
with lib;
|
||||
let
|
||||
host = config.networking.hostName;
|
||||
user-pubkey = config.sane.ssh.pubkeys."colin@${host}".asUserKey;
|
||||
host-keys = filter (k: k.user == "root") (attrValues config.sane.ssh.pubkeys);
|
||||
known-hosts-text = concatStringsSep
|
||||
"\n"
|
||||
(map (k: k.asHostKey) host-keys)
|
||||
;
|
||||
in lib.mkIf config.sane.home-manager.enable {
|
||||
# ssh key is stored in private storage
|
||||
sane.persist.home.private = [ ".ssh/id_ed25519" ];
|
||||
sane.fs."/home/colin/.ssh/id_ed25519.pub" = sane-lib.fs.wantedText user-pubkey;
|
||||
sane.fs."/home/colin/.ssh/known_hosts" = sane-lib.fs.wantedText known-hosts-text;
|
||||
|
||||
programs.ssh.enable = true;
|
||||
# this optionally accepts multiple known_hosts paths, separated by space.
|
||||
programs.ssh.userKnownHostsFile = builtins.toString (pkgs.writeText "known_hosts" known_hosts_text);
|
||||
};
|
||||
users.users.colin.openssh.authorizedKeys.keys =
|
||||
let
|
||||
user-keys = filter (k: k.user == "colin") (attrValues config.sane.ssh.pubkeys);
|
||||
in
|
||||
map (k: k.asUserKey) user-keys;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{ config, lib, ... }:
|
||||
{ config, lib, sane-lib, ... }:
|
||||
|
||||
lib.mkIf config.sane.home-manager.enable
|
||||
{
|
||||
@@ -8,9 +8,5 @@ lib.mkIf config.sane.home-manager.enable
|
||||
sopsFile = ../../secrets/universal/sublime_music_config.json.bin;
|
||||
format = "binary";
|
||||
};
|
||||
home-manager.users.colin = let sysconfig = config; in { config, ... }: {
|
||||
# sublime music player
|
||||
xdg.configFile."sublime-music/config.json".source =
|
||||
config.lib.file.mkOutOfStoreSymlink sysconfig.sops.secrets.sublime_music_config.path;
|
||||
};
|
||||
sane.fs."/home/colin/.config/sublime-music/config.json" = sane-lib.fs.wantedSymlinkTo config.sops.secrets.sublime_music_config.path;
|
||||
}
|
||||
|
@@ -1,16 +1,18 @@
|
||||
{ config, lib, ... }:
|
||||
{ config, lib, sane-lib, ... }:
|
||||
|
||||
let
|
||||
feeds = sane-lib.feeds;
|
||||
all-feeds = config.sane.feeds;
|
||||
wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds;
|
||||
podcast-urls = lib.concatStringsSep "|" (
|
||||
builtins.map (feed: feed.url) wanted-feeds
|
||||
);
|
||||
in
|
||||
lib.mkIf config.sane.home-manager.enable
|
||||
{
|
||||
home-manager.users.colin.xdg.configFile."vlc/vlcrc".text =
|
||||
let
|
||||
feeds = import ./feeds.nix { inherit lib; };
|
||||
podcastUrls = lib.strings.concatStringsSep "|" (
|
||||
builtins.map (feed: feed.url) feeds.podcasts
|
||||
);
|
||||
in ''
|
||||
sane.fs."/home/colin/.config/vlc/vlcrc" = sane-lib.fs.wantedText ''
|
||||
[podcast]
|
||||
podcast-urls=${podcastUrls}
|
||||
podcast-urls=${podcast-urls}
|
||||
[core]
|
||||
metadata-network-access=0
|
||||
[qt]
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
lib.mkIf config.sane.home-manager.enable
|
||||
{
|
||||
sane.impermanence.dirs.home.plaintext = [
|
||||
sane.persist.home.plaintext = [
|
||||
# we don't need to full zsh dir -- just the history file --
|
||||
# but zsh will sometimes backup the history file and we get fewer errors if we do proper mounts instead of symlinks.
|
||||
# TODO: should be private?
|
||||
|
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
|
||||
)
|
||||
];
|
||||
};
|
||||
}
|
@@ -79,7 +79,9 @@ in
|
||||
"ext4" = pkgs.imageBuilder.fileSystem.makeExt4;
|
||||
"btrfs" = pkgs.imageBuilder.fileSystem.makeBtrfs;
|
||||
};
|
||||
in {
|
||||
in
|
||||
lib.mkIf cfg.enable
|
||||
{
|
||||
system.build.img-without-firmware = with pkgs; imageBuilder.diskImage.makeGPT {
|
||||
name = "nixos";
|
||||
diskID = vfatUuidFromFs bootFs;
|
||||
|
@@ -1,191 +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, sane-lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
path = sane-lib.path;
|
||||
sane-types = sane-lib.types;
|
||||
cfg = config.sane.impermanence;
|
||||
|
||||
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.
|
||||
'';
|
||||
};
|
||||
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.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# 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;
|
||||
};
|
||||
|
||||
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 = {
|
||||
sane.impermanence.enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
sane.impermanence.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.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.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
./root-on-tmpfs.nix
|
||||
./stores
|
||||
];
|
||||
|
||||
config = let
|
||||
cfgFor = opt:
|
||||
let
|
||||
store = opt.store;
|
||||
store-rel-path = path.from store.prefix opt.directory;
|
||||
backing-path = path.concat [ store.origin store-rel-path ];
|
||||
|
||||
# pass through the perm/mode overrides
|
||||
dir-acl = sane-lib.filterNonNull {
|
||||
inherit (opt) user group mode;
|
||||
};
|
||||
in {
|
||||
# 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;
|
||||
};
|
||||
};
|
||||
in mkIf cfg.enable {
|
||||
sane.fs = lib.mkMerge (map (d: (cfgFor d).sane.fs) cfg.dirs.all);
|
||||
};
|
||||
}
|
||||
|
@@ -1,31 +0,0 @@
|
||||
{ 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;
|
||||
};
|
||||
}
|
@@ -1,8 +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;
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
{ 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:
|
||||
@@ -19,6 +20,10 @@ let path = rec {
|
||||
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;
|
||||
@@ -26,5 +31,14 @@ let path = rec {
|
||||
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
|
||||
|
@@ -264,13 +264,20 @@ let
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
toPkgSpec = types.coercedTo types.package (p: { pkg = p; }) pkgSpec;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
# packages to deploy to the user's home
|
||||
sane.packages.extraUserPkgs = mkOption {
|
||||
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 {
|
||||
default = false;
|
||||
@@ -297,18 +304,18 @@ in
|
||||
sane.packages.enabledUserPkgs = mkOption {
|
||||
default = cfg.extraUserPkgs
|
||||
++ (if cfg.enableConsolePkgs then consolePkgs else [])
|
||||
++ (if cfg.enableGuiPkgs then guiPkgs else [])
|
||||
++ (if cfg.enableGuiPkgs then guiPkgs ++ cfg.extraGuiPkgs else [])
|
||||
++ (if cfg.enableDevPkgs then devPkgs else [])
|
||||
;
|
||||
type = types.listOf (types.coercedTo types.package (p: { pkg = p; }) pkgSpec);
|
||||
type = types.listOf toPkgSpec;
|
||||
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);
|
||||
sane.persist.home.plaintext = concatLists (map (p: p.dir) cfg.enabledUserPkgs);
|
||||
sane.persist.home.private = concatLists (map (p: p.private) cfg.enabledUserPkgs);
|
||||
# XXX: this might not be necessary. try removing this and cacert.unbundled?
|
||||
environment.etc."ssl/certs".source = mkIf cfg.enableSystemPkgs "${pkgs.cacert.unbundled}/etc/ssl/certs/*";
|
||||
};
|
||||
|
18
modules/persist/computed.nix
Normal file
18
modules/persist/computed.nix
Normal file
@@ -0,0 +1,18 @@
|
||||
{ config, lib, sane-lib, ... }:
|
||||
|
||||
let
|
||||
path = sane-lib.path;
|
||||
cfg = config.sane.persist;
|
||||
|
||||
withPrefix = relativeTo: entries: lib.mapAttrs' (fspath: value: {
|
||||
name = path.concat [ relativeTo fspath ];
|
||||
inherit value;
|
||||
}) entries;
|
||||
in
|
||||
{
|
||||
# merge the `byPath` mappings from both `home` and `sys` into one namespace
|
||||
sane.persist.byPath = lib.mkMerge [
|
||||
(withPrefix "/home/colin" cfg.home.byPath)
|
||||
(withPrefix "/" cfg.sys.byPath)
|
||||
];
|
||||
}
|
254
modules/persist/default.nix
Normal file
254
modules/persist/default.nix
Normal file
@@ -0,0 +1,254 @@
|
||||
# borrows from:
|
||||
# https://xeiaso.net/blog/paranoid-nixos-2021-07-18
|
||||
# https://elis.nu/blog/2020/05/nixos-tmpfs-as-root/
|
||||
# https://github.com/nix-community/impermanence
|
||||
{ config, lib, pkgs, utils, sane-lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
path = sane-lib.path;
|
||||
sane-types = sane-lib.types;
|
||||
cfg = config.sane.persist;
|
||||
|
||||
storeType = types.submodule {
|
||||
options = {
|
||||
storeDescription = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
an optional description of the store, which is rendered like
|
||||
{store.name}: {store.storeDescription}
|
||||
for example, a store named "private" could have description "ecnrypted to the user's password and decrypted on login".
|
||||
'';
|
||||
};
|
||||
origin = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
prefix = mkOption {
|
||||
type = types.str;
|
||||
default = "/";
|
||||
description = ''
|
||||
optional prefix to strip from children when stored here.
|
||||
for example, prefix="/var/private" and mountpoint="/mnt/crypt/private"
|
||||
would cause /var/private/www/root to be stored at /mnt/crypt/private/www/root instead of
|
||||
/mnt/crypt/private/var/private/www/root.
|
||||
'';
|
||||
};
|
||||
defaultMethod = mkOption {
|
||||
type = types.enum [ "bind" "symlink" ];
|
||||
default = "bind";
|
||||
description = ''
|
||||
preferred way to link items from the store into the fs
|
||||
'';
|
||||
};
|
||||
defaultOrdering.wantedBeforeBy = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "local-fs.target" ];
|
||||
description = ''
|
||||
list of units or targets which would prefer that everything in this store
|
||||
be initialized before they run, but failing to do so should not error the items in this list.
|
||||
'';
|
||||
};
|
||||
defaultOrdering.wantedBy = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
list of units or targets which, upon activation, should activate all units in this store.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# allows a user to specify the store either by name or as an attrset
|
||||
coercedToStore = types.coercedTo types.str (s: cfg.stores."${s}") storeType;
|
||||
|
||||
# options common to all entries, whether they're keyed by path or store
|
||||
entryOpts = {
|
||||
options = {
|
||||
acl = mkOption {
|
||||
type = sane-types.aclOverride;
|
||||
default = {};
|
||||
};
|
||||
method = mkOption {
|
||||
type = types.nullOr (types.enum [ "bind" "symlink" ]);
|
||||
default = null;
|
||||
description = ''
|
||||
how to link the store entry into the fs
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# options for a single mountpoint / persistence where the store is specified externally
|
||||
entryInStore = types.submodule [
|
||||
entryOpts
|
||||
{
|
||||
options = {
|
||||
directory = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
# allow "bar/baz" as shorthand for { directory = "bar/baz"; }
|
||||
entryInStoreOrShorthand = types.coercedTo
|
||||
types.str
|
||||
(d: { directory = d; })
|
||||
entryInStore;
|
||||
|
||||
# allow the user to provide the `acl` field inline: we pop acl sub-attributes placed at the
|
||||
# toplevel and move them into an `acl` attribute.
|
||||
convertInlineAcl = to: types.coercedTo
|
||||
types.attrs
|
||||
(orig: lib.recursiveUpdate
|
||||
(builtins.removeAttrs orig ["user" "group" "mode" ])
|
||||
{
|
||||
acl = sane-lib.filterByName ["user" "group" "mode"] (orig.acl or {});
|
||||
}
|
||||
)
|
||||
to;
|
||||
|
||||
# entry where the path is specified externally
|
||||
entryAtPath = types.submodule [
|
||||
entryOpts
|
||||
{
|
||||
options = {
|
||||
store = mkOption {
|
||||
type = coercedToStore;
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
# this submodule creates one attr per store, so that the user can specify something like:
|
||||
# <option>.private.".cache/vim" = { mode = "0700"; };
|
||||
# to place ".cache/vim" into the private store and create with the appropriate mode
|
||||
dirsSubModule = types.submodule ({ config, ... }: {
|
||||
options = lib.attrsets.unionOfDisjoint
|
||||
(mapAttrs (store: store-cfg: mkOption {
|
||||
default = [];
|
||||
type = types.listOf (convertInlineAcl entryInStoreOrShorthand);
|
||||
description = let
|
||||
suffix = if store-cfg.storeDescription != null then
|
||||
": ${store-cfg.storeDescription}"
|
||||
else "";
|
||||
in "directories to persist in ${store}${suffix}";
|
||||
}) cfg.stores)
|
||||
{
|
||||
byPath = mkOption {
|
||||
type = types.attrsOf (convertInlineAcl entryAtPath);
|
||||
default = {};
|
||||
description = ''
|
||||
map of <path> => <path config> for all paths to be persisted.
|
||||
this is computed from the other options, but users can also set it explicitly (useful for overriding)
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = let
|
||||
# set the `store` attribute on one dir attrset
|
||||
annotateWithStore = store: dir: {
|
||||
"${dir.directory}".store = store;
|
||||
};
|
||||
# convert an `entryInStore` to an `entryAtPath` (less the `store` item)
|
||||
dirToAttrs = dir: {
|
||||
"${dir.directory}" = builtins.removeAttrs dir ["directory"];
|
||||
};
|
||||
store-names = attrNames cfg.stores;
|
||||
# :: (store -> entry -> AttrSet) -> [AttrSet]
|
||||
applyToAllStores = f: lib.concatMap
|
||||
(store: map (f store) config."${store}")
|
||||
store-names;
|
||||
in {
|
||||
byPath = lib.mkMerge (concatLists [
|
||||
(applyToAllStores (store: dirToAttrs))
|
||||
(applyToAllStores annotateWithStore)
|
||||
]);
|
||||
};
|
||||
});
|
||||
in
|
||||
{
|
||||
options = {
|
||||
sane.persist.enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
sane.persist.root-on-tmpfs = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "define / fs root to be a tmpfs. make sure to mount some other device to /nix";
|
||||
};
|
||||
sane.persist.home = mkOption {
|
||||
description = "directories to persist to disk, relative to a user's home ~";
|
||||
default = {};
|
||||
type = dirsSubModule;
|
||||
};
|
||||
sane.persist.sys = mkOption {
|
||||
description = "directories to persist to disk, relative to the fs root /";
|
||||
default = {};
|
||||
type = dirsSubModule;
|
||||
};
|
||||
sane.persist.byPath = mkOption {
|
||||
type = types.attrsOf (convertInlineAcl entryAtPath);
|
||||
description = ''
|
||||
map of <path> => <path config> for all paths to be persisted.
|
||||
this is computed from the other options, but users can also set it explicitly (useful for overriding)
|
||||
'';
|
||||
};
|
||||
sane.persist.stores = mkOption {
|
||||
type = types.attrsOf storeType;
|
||||
default = {};
|
||||
description = ''
|
||||
map from human-friendly name to a fs sub-tree from which files are linked into the logical fs.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
./computed.nix
|
||||
./root-on-tmpfs.nix
|
||||
./stores
|
||||
];
|
||||
|
||||
config = let
|
||||
cfgFor = fspath: opt:
|
||||
let
|
||||
store = opt.store;
|
||||
method = (sane-lib.withDefault store.defaultMethod) opt.method;
|
||||
fsPathToStoreRelPath = fspath: path.from store.prefix fspath;
|
||||
fsPathToBackingPath = fspath: path.concat [ store.origin (fsPathToStoreRelPath fspath) ];
|
||||
in lib.mkMerge [
|
||||
{
|
||||
# create destination dir, with correct perms
|
||||
sane.fs."${fspath}" = {
|
||||
inherit (store.defaultOrdering) wantedBy wantedBeforeBy;
|
||||
} // (lib.optionalAttrs (method == "bind") {
|
||||
# inherit perms & make sure we don't mount until after the mount point is setup correctly.
|
||||
dir.acl = opt.acl;
|
||||
mount.bind = fsPathToBackingPath fspath;
|
||||
}) // (lib.optionalAttrs (method == "symlink") {
|
||||
symlink.acl = opt.acl;
|
||||
symlink.target = fsPathToBackingPath fspath;
|
||||
});
|
||||
|
||||
# create the backing path as a dir
|
||||
sane.fs."${fsPathToBackingPath fspath}".dir = {};
|
||||
}
|
||||
{
|
||||
# default each item along the backing path to have the same acl as the location it would be mounted.
|
||||
sane.fs = lib.mkMerge (builtins.map
|
||||
(fsSubpath: {
|
||||
"${fsPathToBackingPath fsSubpath}" = {
|
||||
generated.acl = config.sane.fs."${fsSubpath}".generated.acl;
|
||||
};
|
||||
})
|
||||
(path.walk store.prefix fspath)
|
||||
);
|
||||
}
|
||||
];
|
||||
configs = lib.mapAttrsToList cfgFor cfg.byPath;
|
||||
take = f: { sane.fs = f.sane.fs; };
|
||||
in mkIf cfg.enable (
|
||||
take (sane-lib.mkTypedMerge take configs)
|
||||
);
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.sane.impermanence;
|
||||
cfg = config.sane.persist;
|
||||
in
|
||||
{
|
||||
fileSystems."/" = lib.mkIf (cfg.enable && cfg.root-on-tmpfs) {
|
@@ -2,17 +2,17 @@
|
||||
|
||||
let
|
||||
store = rec {
|
||||
device = "/mnt/impermanence/crypt/clearedonboot";
|
||||
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/impermanence/crypt/clearedonboot.key";
|
||||
key = "/mnt/persist/crypt/clearedonboot.key";
|
||||
};
|
||||
};
|
||||
in
|
||||
lib.mkIf config.sane.impermanence.enable
|
||||
lib.mkIf config.sane.persist.enable
|
||||
{
|
||||
sane.impermanence.stores."cryptClearOnBoot" = {
|
||||
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
|
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
|
||||
];
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.sane.impermanence;
|
||||
cfg = config.sane.persist;
|
||||
in lib.mkIf cfg.enable {
|
||||
sane.impermanence.stores."plaintext" = {
|
||||
sane.persist.stores."plaintext" = {
|
||||
origin = "/nix/persist";
|
||||
};
|
||||
# TODO: needed?
|
@@ -1,8 +1,8 @@
|
||||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
lib.mkIf config.sane.impermanence.enable
|
||||
lib.mkIf config.sane.persist.enable
|
||||
{
|
||||
sane.impermanence.stores."private" = {
|
||||
sane.persist.stores."private" = {
|
||||
storeDescription = ''
|
||||
encrypted to the user's password and auto-unlocked at login
|
||||
'';
|
||||
@@ -20,6 +20,7 @@ lib.mkIf config.sane.impermanence.enable
|
||||
# we can't create things in private before local-fs.target
|
||||
wantedBeforeBy = [ ];
|
||||
};
|
||||
defaultMethod = "symlink";
|
||||
};
|
||||
|
||||
fileSystems."/home/colin/private" = {
|
@@ -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;
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ in
|
||||
config = mkIf cfg.enable {
|
||||
# we need this mostly because of the size of duplicity's cache
|
||||
# TODO: move to cryptClearOnBoot and update perms
|
||||
sane.impermanence.dirs.sys.plaintext = [ "/var/lib/duplicity" ];
|
||||
sane.persist.sys.plaintext = [ "/var/lib/duplicity" ];
|
||||
|
||||
services.duplicity.enable = true;
|
||||
services.duplicity.targetUrl = "$DUPLICITY_URL";
|
||||
|
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
|
||||
|
@@ -2,18 +2,19 @@
|
||||
# initializes the default libsecret keyring (used by gnome-keyring) if not already initialized.
|
||||
# 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
|
||||
echo 'keyring already initialized: not doing anything'
|
||||
exit 0
|
||||
echo 'keyring already initialized: not doing anything'
|
||||
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
|
||||
|
||||
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
|
||||
# usage: install-bluetooth <source_dir> <dest_dir>
|
||||
# usage: install-bluetooth <source_dir> <destdir>
|
||||
# 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
|
||||
#
|
||||
# bluetooth connection structure is /var/lib/bluetooth/<HOST_MAC>/<DEVICE_MAX>/{attributes,info}
|
||||
#
|
||||
set -ex
|
||||
# bluetoothd/main.conf options can be found here:
|
||||
# - <https://pythonhosted.org/BT-Manager/config.html>
|
||||
# can be set via nixos' `hardware.bluetooth.settings`
|
||||
|
||||
src_dir="$1"
|
||||
dest_dir="$2"
|
||||
srcdir="$1"
|
||||
destdir="$2"
|
||||
|
||||
if [ "x$dest_dir" = "x" ]
|
||||
if [ "x$destdir" = "x" ]
|
||||
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
|
||||
dest_dir="/var/lib/bluetooth/$(ls /var/lib/bluetooth)"
|
||||
destdir="/var/lib/bluetooth/$devmac"
|
||||
test -d "$destdir" || mkdir "$destdir" || test -d "$destdir"
|
||||
fi
|
||||
|
||||
for f in $(ls "$src_dir")
|
||||
for f in $(ls "$srcdir")
|
||||
do
|
||||
mac=$(sed -rn 's/# MAC=(.*)/\1/p' "$src_dir/$f")
|
||||
mkdir -p "$dest_dir/$mac"
|
||||
ln -sf "$src_dir/$f" "$dest_dir/$mac/info"
|
||||
mac=$(sed -rn 's/# MAC=(.*)/\1/p' "$srcdir/$f")
|
||||
condir="$destdir/$mac"
|
||||
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
|
||||
|
@@ -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": {
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2022-12-03T10:23:14Z",
|
||||
"mac": "ENC[AES256_GCM,data:KBm0rXwAGPa0ZkqGI9K3rW5B4vJ1FLmITa8xV5WR1SG2MlSqvCqSj4Qe5kxcIc3AqqHF2W+LDaJ0f1fXOCVqWRe1mi/LJyYgPERL5Hn3iOHty9g984Q/QSGvH13O7eY/Fuk2h0mpIX4pOhdpW74qlp1zYDXqUswsKW7ERTTRf6E=,iv:maE+9/OgdgYNX4F/MrzIpJr+/XXyFSayC1YX382oc2Y=,tag:NmrKXA9AjnoTXrQThnvxvg==,type:str]",
|
||||
"lastmodified": "2023-01-07T11:04:42Z",
|
||||
"mac": "ENC[AES256_GCM,data:QiNqZSB5WIVroTQKWxt73NLGvv13waePyMcQ3OJaecaOZQiXGhuq9Ojwnk+I2DSs7X8Nv10VilHk97kYNgTjsNdWmXHqtSY0LKbbMoJpzPoF42MCPSv8g5tLOnIR095Ihu8Ntw+FdOsl0rqa9ipqJFFswOpGI/xamcsLtpRnQnQ=,iv:i4YCULu9YJR5zLomeAYpzvFG7SB9x+4wWPhaiFGlTQQ=,tag:xLbmIhg7hPZnHvQVhbgDpQ==,type:str]",
|
||||
"pgp": null,
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"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": {
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2022-10-08T03:39:12Z",
|
||||
"mac": "ENC[AES256_GCM,data:4Rr2iqmzLtE9i45Hn10wuf8unKt+YNAYTF3RWwEW1AjN+pF7ZvwMbrUutRCb6uMxCQUyNl+adfFRu8Xae0/SqFBfdAPxzeQZGrBjb384seLrNS0XyUacfdoSCczrRUF8+F3mIHetaJCd2jOpoh5HotoSN3fx+nZNhD+56XmJBr0=,iv:YlDMimhG+a9Wzq0ZN0tnZ1gH69e7olyHGWhIV2/4K64=,tag:GjVzbNa/NdzVmdPyE5etXw==,type:str]",
|
||||
"lastmodified": "2023-01-07T03:06:02Z",
|
||||
"mac": "ENC[AES256_GCM,data:L3wY2ZdR1ASbLbKXiipWfBiQ5cumItuiL1+TwTJhU5ZtxLe6SMUyhckvuX8hczlFPUlJQJDCwpgVBs9C6GRAU45jzHYmpcfF30auiRT2dF/2doH9yiYZoF7JtbTas0Kvt1yxlPfuTi5mFuJGAKDOw6+a5ayQHYlK3/RxAUn0yPc=,iv:U/vlmvI1l4u92eUDXRphS0tscLOlWorOdmT7wDwGbAM=,tag:bQayboRgsMKT6akDq+rzQw==,type:str]",
|
||||
"pgp": null,
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.7.3"
|
||||
|
Reference in New Issue
Block a user