Compare commits

...

247 Commits

Author SHA1 Message Date
1a0f05bfd6 flake update: nixpkgs 2022-12-31 -> 2023-01-04
```
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/8ba56d7c0d7490680f2d51ba46a141eca7c46afa' (2022-12-31)
  → 'github:NixOS/nixpkgs/9813adc7f7c0edd738c6bdd8431439688bb0cb3d' (2023-01-04)
• Updated input 'nixpkgs-stable':
    'github:NixOS/nixpkgs/6a0d2701705c3cf6f42c15aa92b7885f1f8a477f' (2022-12-30)
  → 'github:NixOS/nixpkgs/e9ade2c8240e00a4784fac282a502efff2786bdc' (2023-01-04)
```
2023-01-06 08:57:39 +00:00
c18dd9636d fs: symlinking: fix recursive links (don't follow links when creating new ones) 2023-01-06 05:32:31 +00:00
0977721af5 moby: fix to preserve browser cache across boots 2023-01-04 13:27:20 +00:00
122d3cd7e4 impermanence: fix home perms in ~/private dir 2023-01-04 13:24:23 +00:00
cd5f8054c0 fs: rename "mountpt" -> "origin" to reflect that it doesnt have to be a device 2023-01-04 12:19:32 +00:00
3db388b105 servo: relocate ext device to /mnt/impermanence/ext and fixup deps 2023-01-04 12:12:30 +00:00
2ba6116f10 fs/impermanence: more precisely control unit dependencies/ordering 2023-01-04 11:22:26 +00:00
592d17b725 impermanence: crypt: simplify setup (experimental) 2023-01-04 09:28:59 +00:00
4d9c15f9b8 fs: fix file mode even if not newly created 2023-01-04 08:12:53 +00:00
abced7dd0d navidrome: don't try to chown to an invalid user 2023-01-04 08:00:04 +00:00
5c42365912 sane-stop-all-servo: add missing services 2023-01-04 07:49:21 +00:00
247ad326b2 freshrss: be conservative and use explicit octal mode bits 2023-01-04 07:14:54 +00:00
170008f345 home.files symlinks: port to sane.fs 2023-01-04 07:14:38 +00:00
2c48e61854 fs: fix mode of deployed files; simplify 2023-01-04 07:14:01 +00:00
f89f756489 fs: create symlinks at multi-user target by default 2023-01-04 06:32:53 +00:00
c0da19951b fs: fix symlink ownership/perms 2023-01-04 06:28:44 +00:00
5fb67306e4 fs: rework for dir to not be mandatory 2023-01-04 06:17:34 +00:00
5533b586d7 fs: lift depends out of dir, to toplevel 2023-01-04 04:32:20 +00:00
68c2eb7363 impermanence: clean up the deps for the crypt store 2023-01-04 04:22:17 +00:00
fd79026366 fs: fix loose mount dependency 2023-01-04 04:06:19 +00:00
a76471cb1f fs: simplify the mount configuration 2023-01-04 03:57:24 +00:00
c94b8299a6 fs: add experimental support for symlink entries 2023-01-04 02:51:07 +00:00
175bc0709f remove external impermanence library 2023-01-04 02:18:03 +00:00
7b02477486 servo: define /etc/persist via sane impermanence module 2023-01-04 02:15:43 +00:00
d7c8638fea impermanence: add a storeDescription field per store 2023-01-04 01:54:13 +00:00
9d7d1acc80 sane.impermanence.dirs.home.<store> is now auto-generated and options can vary across deployments 2023-01-04 01:45:05 +00:00
787857d27f firefox: update plugin hashes 2023-01-04 01:39:42 +00:00
9c248a8a31 impermanence: cleanup the dirs submodule 2023-01-04 01:34:19 +00:00
829680fb00 impermanence: simplify dir-acl handling by using a helper 2023-01-04 01:19:22 +00:00
a9ee26388c guest account: make home-dir writable by other users 2023-01-04 01:09:23 +00:00
2960b895b6 lib: lift acl type into sane-lib/types 2023-01-04 00:59:52 +00:00
933063115b moby: fix home-dirs for newer impermanence module 2023-01-04 00:47:48 +00:00
afe684ca2c modules: impermanence: use sane-lib.path 2023-01-03 14:55:27 +00:00
93f1411522 fs: split helpers out into sane-lib module 2023-01-03 14:20:02 +00:00
01e44c1f7f flake.nix: remove unused specialArgs 2023-01-03 14:18:57 +00:00
618e9bd2fa gocryptfs: place package in system.fsPackages instead of environment.systemPackages (it propagates anyway) 2023-01-03 12:00:49 +00:00
fbc39d0584 modules: fs: move to subdir 2023-01-03 08:27:28 +00:00
2d7b3750cd impermanence: split the /home/colin perms fix into more appropriate places 2023-01-03 08:25:43 +00:00
e6ccd2e4f7 impermanence: split plaintext store to own file (this will bypass some recursion in the next patch) 2023-01-03 08:02:03 +00:00
d4bf491e9c impermanence: improve docs 2023-01-03 07:45:19 +00:00
5a2bbcce3b move plaintext home-dirs out of home-manager module into users module 2023-01-03 07:35:42 +00:00
327e6b536f impermanence: large refactor, and experimental bind mounting of things from ~/private 2023-01-03 07:22:37 +00:00
bace7403e7 Merge branch 'staging/nixpkgs-2022-12-31' 2023-01-03 03:05:21 +00:00
57f5521ef3 grpc: unpin (seems to build OK) 2023-01-03 03:05:07 +00:00
9e32211c12 impermanence: cange "encryptedClearOnBoot" to a broader "store" argument
in the future it can support ~/private as a backing store
2023-01-03 03:04:19 +00:00
edf6bd4455 fs: add a "mount.bind" option & use it for impermanence bind-mounts 2023-01-03 02:45:23 +00:00
a9a14786f9 packages: disable fractal (unused, slow build) 2023-01-02 23:35:43 +00:00
eade5fe16e flake update: 2022-12-22 -> 2022-12-31
```
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/652e92b8064949a11bc193b90b74cb727f2a1405' (2022-12-22)
  → 'github:NixOS/nixpkgs/8ba56d7c0d7490680f2d51ba46a141eca7c46afa' (2022-12-31)
• Updated input 'nixpkgs-stable':
    'github:NixOS/nixpkgs/dac57a4eccf1442e8bf4030df6fcbb55883cb682' (2022-12-24)
  → 'github:NixOS/nixpkgs/6a0d2701705c3cf6f42c15aa92b7885f1f8a477f' (2022-12-30)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/855b8d51fc3991bd817978f0f093aa6ae0fae738' (2022-12-25)
  → 'github:Mic92/sops-nix/b35586cc5abacd4eba9ead138b53e2a60920f781' (2023-01-01)
• Updated input 'sops-nix/nixpkgs-stable':
    'github:NixOS/nixpkgs/939c05a176b8485971463c18c44f48e56a7801c9' (2022-12-24)
  → 'github:NixOS/nixpkgs/feda52be1d59f13b9aa02f064b4f14784b9a06c8' (2022-12-31)
```
2023-01-02 22:34:22 +00:00
be222c1d70 trust-dns: allow shorthand assignment of record lists 2023-01-02 13:23:52 +00:00
88a33dd5de snippets: add private links 2023-01-02 13:23:29 +00:00
875e923197 declare ~/private in fileSystems and reuse for pamMount 2023-01-02 11:34:02 +00:00
54dd643cf0 trust-dns: make a note about another DNS library we could draw from 2023-01-02 11:33:32 +00:00
3c726f148b remove some stale references to mobile-nixos 2023-01-02 10:00:20 +00:00
e225e2e704 modules/packages: directly set impermanence.home-dirs instead of working through home-manager 2023-01-02 07:45:05 +00:00
cf0bf8190e modules/packages: clean up loose typing of sane.packages 2023-01-02 07:16:16 +00:00
b8f7f68d4c packages: telegram: persist data in private storage 2023-01-02 07:06:58 +00:00
7a3aae8c97 fs: tidy 2022-12-31 12:38:50 +00:00
89e519810d impermanence: clean up the bind mounts 2022-12-31 12:31:49 +00:00
0e920230ba impermanence: fix systemd service ordering for crypt mount 2022-12-31 12:18:27 +00:00
6ffae00e17 fs: rename "service" option to "unit" option 2022-12-31 11:31:16 +00:00
be19985440 impermanence: crypt: more robust perms and ordering of backing device 2022-12-31 10:45:43 +00:00
f7e3e7294a impermanence: transform gocryptfs key generation from activation script to systemd unit 2022-12-31 10:15:08 +00:00
d745e3c1ee impermanence: remove fuse module: we don't need it now that we're mounting after activation 2022-12-31 09:13:31 +00:00
c1890ce82b impermanence: cleanup some previously verbose code 2022-12-31 09:09:51 +00:00
53a0b621d8 impermanence: use sane.fs to inherit permissions instead of specifying defaults here 2022-12-31 01:04:49 +00:00
aeb2f63d65 impermanence: defer to fs.nix module for permissions & dir creation 2022-12-31 00:38:15 +00:00
528ffdb58e add a new 'fs.nix' file i'll use to factor the impermanence stuff better 2022-12-30 14:45:34 +00:00
b6887b305e impermanence: split out the root-on-tmpfs stuff 2022-12-30 04:35:34 +00:00
08dfc80c98 impermanence: split out sops setup 2022-12-30 04:31:24 +00:00
5a273213f6 sops: remove sops.age.sshKeyPaths override: sops gets this from openssh config already 2022-12-30 03:49:31 +00:00
0a6d88dfc1 impermanence: simplify /etc/ssh/host_keys setup 2022-12-30 03:34:59 +00:00
50dfd482cf document plans for better handling of /etc/ssh 2022-12-29 19:19:51 +00:00
9743aee79d ssh keys: document the issues i'm seeing 2022-12-29 18:42:59 +00:00
0819899102 remove dead commented-out code 2022-12-29 18:34:03 +00:00
d3ff68217e impermanence: enable hyphenated folder names 2022-12-29 18:29:27 +00:00
1a96859994 impermanence: re-enable mpv watch_later dir 2022-12-29 18:10:40 +00:00
af92a2250e impermanence: fix up circular dependencies and permissions
this is now a proof of concept. still has some rough edges.
2022-12-29 18:03:41 +00:00
d00f9b15d7 impermanence: fix typo in permissions service 2022-12-29 17:16:27 +00:00
aa1c1f40cb WIP: impermanence rework (gut 3rd-party lib) 2022-12-29 16:38:58 +00:00
530b2d6385 impermanence: factor out some helpers for generating fileSystems and services 2022-12-29 08:42:15 +00:00
e6919dd16f impermanence: use systemd/fileSystems for the crypt mounts, instead of 3rd-party impermanence 2022-12-29 01:17:40 +00:00
760f2ac66d move ~/.cache into encrypted private dir 2022-12-29 01:17:40 +00:00
8e5ca11259 cleanup gocryptfs mounting
there's possibly some latent issues. i think my changes to the gocryptfs
package *might* not be necessary: if you work via the fuse front-door,
it's a lot harder to get it into these weird places.
2022-12-29 01:17:40 +00:00
121936620a impermanence: add support for encrypted clear-on-boot storage
this is useful for when we need to store files to disk purely due to
their size, but don't actually want them to be persisted.
2022-12-29 01:17:40 +00:00
f5b49e014c net: add parent's wifi 2022-12-29 00:57:36 +00:00
4bdb34775d consolidate filesystems./ across devices 2022-12-28 01:36:22 +00:00
f5fbc206f5 package signaldctl (partially tested)
it includes an extra `bin/generator` output: i'm not sure if this is
necessary yet or not.
2022-12-28 00:48:44 +00:00
a9096f3312 sane-scripts: remove /run/wrappers hack now that prologue is fixed in resholve 2022-12-26 10:02:51 +00:00
67cddecab4 Merge branch 'staging/nixpkgs-2022-12-22' 2022-12-26 09:30:21 +00:00
9a002c99eb python-data template: add requests module 2022-12-26 09:29:23 +00:00
a0ac7fa98d snippets: add secret snippets 2022-12-26 09:29:04 +00:00
b03043e513 add sane-bt-search script to search jackett/torrents 2022-12-26 09:05:26 +00:00
0713e3bad1 secrets: move bluetooth/vpn secret defn to toplevel nix file 2022-12-26 08:28:44 +00:00
d3a3f39756 move universal secrets out of net.nix -> secrets.nix 2022-12-26 08:09:58 +00:00
a7d9e5cc54 flake update: nixpkgs 2022-12-18 -> 2022-12-22
```
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/04f574a1c0fde90b51bf68198e2297ca4e7cccf4' (2022-12-18)
  → 'github:NixOS/nixpkgs/652e92b8064949a11bc193b90b74cb727f2a1405' (2022-12-22)
• Updated input 'nixpkgs-stable':
    'github:NixOS/nixpkgs/cbe419ed4c8f98bd82d169c321d339ea30904f1f' (2022-12-20)
  → 'github:NixOS/nixpkgs/dac57a4eccf1442e8bf4030df6fcbb55883cb682' (2022-12-24)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/32840f16ffa0856cdf9503a8658f2dd42bf70342' (2022-12-19)
  → 'github:Mic92/sops-nix/855b8d51fc3991bd817978f0f093aa6ae0fae738' (2022-12-25)
• Updated input 'sops-nix/nixpkgs-stable':
    'github:NixOS/nixpkgs/87b58217c9a05edcf7630b9be32570f889217aef' (2022-12-19)
  → 'github:NixOS/nixpkgs/939c05a176b8485971463c18c44f48e56a7801c9' (2022-12-24)
```
2022-12-26 05:19:58 +00:00
13f3b322b0 alias to cd ~/Videos 2022-12-26 04:40:21 +00:00
5c25330891 packages: add nheko matrix client 2022-12-26 03:52:04 +00:00
dc6dc2e475 discord: remove the SKIP_HOST_UPDATE hack. it's been upstreamed 2022-12-26 03:30:25 +00:00
c4352fa9bb packages: move Signal, Discord, Tokodon to private storage 2022-12-26 03:26:50 +00:00
2c6629a658 packages: allow specifying multiple dir and private paths 2022-12-26 03:02:19 +00:00
c0496b25b5 init-keyring: try to make atomic 2022-12-25 12:02:33 +00:00
9e0346c329 snippets: update jackett to be a search query 2022-12-25 08:43:29 +00:00
364a598324 pkgs: clean up the imports to not explicitly pass pkgs 2022-12-25 07:33:24 +00:00
c6850aff23 ship fractal-latest (git tip) instead of fractal-next 2022-12-25 07:23:09 +00:00
730ef272d1 feeds: add put a num on it; remove Kaiteki code feed 2022-12-25 03:39:44 +00:00
16fa1e0eda sane-date-math: convert to LR parser 2022-12-24 05:08:17 +00:00
51a96525d9 sane-date-math: use Productions as objects 2022-12-24 01:17:19 +00:00
7b01822ee7 some kind of sane-date-math date/time util thing. idk, parsers are fun ig 2022-12-23 15:57:56 +00:00
f9aa36a620 flake update: nixos-stable 2022-12-17 -> 2022-12-20
```

• Updated input 'nixpkgs-stable':
    'github:NixOS/nixpkgs/0938d73bb143f4ae037143572f11f4338c7b2d1c' (2022-12-17)
  → 'github:NixOS/nixpkgs/cbe419ed4c8f98bd82d169c321d339ea30904f1f' (2022-12-20)
```
2022-12-22 22:45:27 +00:00
9b75d8705b ejabberd: enable push notifications (verified working on iOS/Modal IM) 2022-12-22 14:12:15 +00:00
217ecec250 ejabberd: enable xmpps-{client,server} SRV records 2022-12-22 13:13:09 +00:00
6c7ca7630a zsh: add tmp alias for ~/tmp 2022-12-22 11:35:35 +00:00
1f99d44288 /home/colin: fix perms to 0700 2022-12-22 11:33:13 +00:00
f1aa685a03 fix p10k variable expansion 2022-12-22 11:00:49 +00:00
2b31fc8776 powerlevel10k: always show user/host 2022-12-22 10:58:02 +00:00
0c35e2b3c1 servo: enable nsncd 2022-12-22 10:34:47 +00:00
77b8d0ddc0 fuzzel: tune dialog widths 2022-12-22 10:10:03 +00:00
84f23c602e new snipper: nixos options search 2022-12-22 10:06:55 +00:00
ea5fbc63cf zsh/p10k: selectively disable gitstatus acceleration on ~/private/ 2022-12-21 14:13:20 +00:00
69361ee9a2 zsh: document prezto modules, switch dir aliases to dirHashes 2022-12-21 13:53:22 +00:00
1808d153b2 zsh: configure p10k 2022-12-21 13:08:23 +00:00
b3ad0f8f1f update ~/knowledge to live in ~/private 2022-12-21 08:52:27 +00:00
c745612cfd Merge branch 'master' of git.uninsane.org:colin/nix-files 2022-12-21 08:51:12 +00:00
278cc98c6d minor ejabberd config changes, simplify DNS %NATIVE% updating 2022-12-21 08:50:41 +00:00
fac661af15 new script: sane-git-init 2022-12-21 08:40:22 +00:00
65777c70ad snippets: add link to home-manager docs 2022-12-21 08:30:16 +00:00
09c524a5b1 Merge remote-tracking branch 'origin/staging/nixpkgs-2022-12-18' 2022-12-21 07:47:55 +00:00
0db7f0857a moby: reduce the number of configurations we keep in /boot 2022-12-21 06:33:50 +00:00
38befe502c new script to free space in /boot 2022-12-21 06:29:13 +00:00
55e09c2dbf ejabberd: port to dns-dns; add experimental STUN/TURN support
during startup it says:
```
Ignoring TLS-enabled STUN/TURN listener
```

and later
```
Invalid certificate in /var/lib/acme/uninsane.org/fullchain.pem: at line 61: certificate is signed by unknown CA
```

the invalid cert thing has always been here. it's for the root cert. idk
if i need to tell ejabberd that one's self-signed, or what.
2022-12-20 03:26:08 +00:00
bd699c887c sane-ssl-dump: new script to help debug ssl stuff 2022-12-20 03:25:07 +00:00
2de6f7d364 fix i2p to build on aarch64 2022-12-20 03:10:05 +00:00
d60e5264f3 don't bind-mount /etc/ssh/host_keys: symlink them instead 2022-12-20 00:04:09 +00:00
c66699b697 update nixpkgs: 2022-12-11 -> 2022-12-18; sops-nix
```
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/64e0bf055f9d25928c31fb12924e59ff8ce71e60' (2022-12-11)
  → 'github:NixOS/nixpkgs/04f574a1c0fde90b51bf68198e2297ca4e7cccf4' (2022-12-18)
• Updated input 'nixpkgs-stable':
    'github:NixOS/nixpkgs/06278c77b5d162e62df170fec307e83f1812d94b' (2022-12-12)
  → 'github:NixOS/nixpkgs/0938d73bb143f4ae037143572f11f4338c7b2d1c' (2022-12-17)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/da98a111623101c64474a14983d83dad8f09f93d' (2022-12-04)
  → 'github:Mic92/sops-nix/32840f16ffa0856cdf9503a8658f2dd42bf70342' (2022-12-19)
• Updated input 'sops-nix/nixpkgs-stable':
    'github:NixOS/nixpkgs/86370507cb20c905800527539fc049a2bf09c667' (2022-12-04)
  → 'github:NixOS/nixpkgs/87b58217c9a05edcf7630b9be32570f889217aef' (2022-12-19)
```
2022-12-19 22:42:58 +00:00
97044bf70e trust-dns: port to dyn-dns for determining WAN IP
although the systemd wantedBy directive is working,
`before` seems to be ignored when the unit fails. so on first run,
dyn-dns runs, fails (poor net connectivity), then trust-dns starts
(fails), then they both restart 10s later.

it's not great, but good enough. also, wan IP is persisted, so this
likely won't happen much in practice.
2022-12-19 13:12:23 +00:00
3122334a41 dyn-dns: fix to only react when the IP actually changes 2022-12-19 11:54:27 +00:00
0b2faef989 /etc/ssh/host_keys: fix endlessly stacked mounts
i believe this was mounting a new /etc/ssh/host_keys on every
activation, resulting in literally thousands of mounts and slowing down
later activations
2022-12-19 11:18:08 +00:00
8acd6ca4f1 create sane.services.dyn-dns to manage dynamic DNS stuff
not yet integrated into servo
2022-12-19 11:16:30 +00:00
8169f7c6b2 ddns-trust-dns: use ddns from router rather than ipinfo.io 2022-12-19 08:24:11 +00:00
cd1aa0b376 sane-vpn-*: reference sane-ip-check instead of duplicating 2022-12-19 06:19:13 +00:00
72b627100c sane-scripts: simplify recursively referencing sane scripts 2022-12-19 06:18:44 +00:00
567c08460a add sane-ip-check-router-wan to query WAN with a more trustworthy source 2022-12-19 05:59:44 +00:00
9b66aecf1b trust-dns: port the remaining records to a structured format
SRV and MX _could_ have more structure (priority, etc).
not sure the best path there (option submodule, i guess).
2022-12-19 04:38:43 +00:00
16cb3b83a2 trust-dns: more idiomatic way to define SOA records 2022-12-19 04:00:27 +00:00
970438be8a trust-dns: rename records option -> extraConfig
i'll be adding special options for records
2022-12-19 03:12:32 +00:00
51da29555e sane-ip-reconnect: fix issue where we'd reconnect to the existing, subpar network 2022-12-19 01:47:30 +00:00
8a745a9b8a ejabberd: enable STUN (with partial discovery support)
discovery is probably not working:
```
Won't auto-announce STUN/TURN service on port 3478 (udp) without public IP address, please specify 'turn_ipv4_address' and optionally 'turn_ipv6_address'
Won't auto-announce STUN/TURN service on port 3478 (tcp) without public IP address, please specify 'turn_ipv4_address' and optionally 'turn_ipv6_address'
```

no messages for the TLS implementation, so maybe that's working?
2022-12-19 01:22:20 +00:00
3505f3b9f3 ejabberd: provision cert for conference.xmpp.uninsane.org
i guess the cert already had that because of legacy prosody setup (?),
but we weren't setup so that new requests would work, i expect.

either that or all of these nginx entries aren't necessary?
2022-12-19 01:22:20 +00:00
444595e847 disable HE and afraid DDNS 2022-12-19 01:22:20 +00:00
3e1407c30b new script to reconnect to best wifi network 2022-12-19 00:29:48 +00:00
0a744117a4 rename sane-check-ip -> sane-ip-check 2022-12-18 23:54:41 +00:00
a2935cedaa snippets: add wikipedia search 2022-12-18 22:58:53 +00:00
22e46d52c2 trust-dns: distribute records across service files 2022-12-17 01:29:12 +00:00
1e0c213adf split webconfig into each service file 2022-12-17 00:52:48 +00:00
3e1340ed61 enable i2p in firefox 2022-12-16 22:15:19 +00:00
341dd3f2b2 new zsh alias: ref -> cd ~/ref 2022-12-16 20:56:48 +00:00
1c9caa40bd snippets: update nixos wiki to include search param 2022-12-16 20:35:33 +00:00
3be15c6d05 podcasts: add Michael Malice (is it any good? we'll see.) 2022-12-16 08:04:28 +00:00
8e8168ec28 add splatmoji package and sway config 2022-12-16 07:46:06 +00:00
28397807fc gpt2tc: disable, because the mirror is unreliable 2022-12-16 07:08:55 +00:00
42ebb9a155 sane-private-do: run a command with the private store unlocked; then re-lock it 2022-12-16 06:10:44 +00:00
a8a4b8e739 kiwix: serve the full english Wikipedia 2022-12-16 05:58:51 +00:00
2550601179 serve w.uninsane.org through kiwix-serve 2022-12-16 02:25:57 +00:00
199a49755a create a kiwix-serve service 2022-12-16 02:15:17 +00:00
8c7700688f nixpatches: add kiwix-tools package that's being upstreamed 2022-12-16 01:22:38 +00:00
8fe304d6c1 trust-dns: split the service into a generic config interface 2022-12-15 11:17:50 +00:00
700fef7df3 servo: mediawiki: remove dead commented-out code 2022-12-15 11:17:50 +00:00
01db7e1f23 servo: install mediawiki 2022-12-15 11:17:50 +00:00
df6e8f1562 Merge branch 'master' of git.uninsane.org:colin/nix-files 2022-12-15 09:59:53 +00:00
1f0a40c81f snippets: add nixos wiki 2022-12-15 09:54:32 +00:00
995b41d1e8 flake: update nixpkgs-stable 22.05 -> 22.11 2022-12-14 22:32:41 +00:00
7674735d42 Merge branch 'master' of git.uninsane.org:colin/nix-files 2022-12-14 12:28:58 +00:00
329693c9ce pin grpc & users, until the grpc aarch64 build is fixed 2022-12-14 12:27:24 +00:00
5ae3bb2f6c sane-rcp: allow a destination 2022-12-14 10:07:02 +00:00
e0b1aef127 snippets: add sci-hub 2022-12-14 09:52:07 +00:00
9b8363dfb4 firefox addons: bypass-paywalls-clean: update hash 2022-12-14 08:00:42 +00:00
58ad87df8e vpns: add us-mi[ami] 2022-12-13 04:26:00 +00:00
5fc894cda9 vpn: fix us-atlanta -> us-atl to match interface length limit 2022-12-13 04:13:01 +00:00
07e6ec2533 sane-scripts: better vpn factoring 2022-12-13 04:11:58 +00:00
005a79e680 vpn: factor out more helpers 2022-12-13 03:55:18 +00:00
0f5279bbca add us-atlanta VPN 2022-12-13 03:26:23 +00:00
e9b3b7ebab simplify ovpn impl 2022-12-13 03:17:27 +00:00
7a83c1d6df trust-dns: use upstream build 2022-12-13 02:03:09 +00:00
46788fe565 servo: make uninsane.org NS records consistent with upstream 2022-12-13 01:00:16 +00:00
a473ef6db3 flake update: nixpkgs: 2022-12-02 -> 2022-12-11; others
```
• Updated input 'mobile-nixos':
    'github:nixos/mobile-nixos/25eec596116553112681d72ee4880107fc3957fa' (2022-11-19)
  → 'github:nixos/mobile-nixos/5ee45cc1f8e43f4af14ee17ccef9156b0db8cd77' (2022-12-04)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/b72b8b94cf0c012b0252a9100a636cad69696666' (2022-12-02)
  → 'github:NixOS/nixpkgs/64e0bf055f9d25928c31fb12924e59ff8ce71e60' (2022-12-11)
• Updated input 'nixpkgs-stable':
    'github:NixOS/nixpkgs/5d7d1d5f742e6bb57dd2e3d7b433fb4010c7af22' (2022-12-02)
  → 'github:NixOS/nixpkgs/7b9eeb856cbf976482fa8d1cb295ea03fb3e1277' (2022-12-10)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/8295b8139ef7baadeb90c5cad7a40c4c9297ebf7' (2022-11-29)
  → 'github:Mic92/sops-nix/da98a111623101c64474a14983d83dad8f09f93d' (2022-12-04)
• Removed input 'sops-nix/nixpkgs-22_05'
• Added input 'sops-nix/nixpkgs-stable':
    'github:NixOS/nixpkgs/86370507cb20c905800527539fc049a2bf09c667' (2022-12-04)
```
2022-12-13 00:52:54 +00:00
3627d47f12 firefox: add uBlacklist 2022-12-13 00:44:38 +00:00
115f8d7054 servo: vpn services are part of 'wireguard-wg0'
this makes it so if we restart the wireguard connection, the services
themeselves _also_ restart. that should avoid leaving any of them in an
orphaned namespace
2022-12-12 11:53:34 +00:00
ac44b04d99 servo: trust-dns: note about maybe using dig instead of diff'ing the config 2022-12-12 11:35:47 +00:00
afff0aff19 servo: trust-dns: fix up the timers/ddns reliability 2022-12-12 11:33:20 +00:00
f0086dc5bd servo: trust-dns: implement some dynamic DNS shim 2022-12-12 10:30:08 +00:00
acabd34f28 servo: net: forward http requests from vpn -> host w/o NATing the source address
this ensures we have access to the source IP in our host-side logs
2022-12-12 05:21:29 +00:00
d0e6b82739 make it so wireguard-wg0 is restartable 2022-12-11 17:07:53 +00:00
dc09b7b9b2 Merge branch 'master' of git.uninsane.org:colin/nix-files 2022-12-11 16:48:16 +00:00
38c5b82a08 servo: fold wg0 setup into one single service
it doesn't restart cleanly (maybe i can't kill a netns while stuff lives
inside it?). problem for another day.
2022-12-11 16:46:55 +00:00
89def1a073 servo: remove dead net code 2022-12-11 16:15:43 +00:00
ad2ed370d9 servo: split the firewall rules across services 2022-12-11 16:12:23 +00:00
3e8f7a9ba2 servo: use ISP-provided DNS resolvers by default
this is really hacky and i hate it, but there's not a lot of good
options.
2022-12-11 16:03:41 +00:00
028ecfe93f snippets: add HN 2022-12-11 13:14:24 +00:00
c5ac792c13 servo: connect wg0 via IP addr instead of hostname
i think this fixes the connectivity issues i've seen.
2022-12-11 12:48:50 +00:00
bd1624bef9 servo: un-firewall tcp port 53 to fix trust-dns over TCP 2022-12-11 12:48:11 +00:00
3ae53d7f32 services: add RestartSec to anything which auto-restarts
this is to prevent rapid restart failures from killing the service
permanently.
2022-12-10 13:28:46 +00:00
e7f2d41b1f servo: forward DNS to root ns without NAT'ing the source address 2022-12-10 13:28:19 +00:00
3394a79e2b trust-dns: restart on failure
if the network isn't up, won't be able to bind to eth, and fails.
2022-12-10 13:02:17 +00:00
b01501663d trust-dns: listen on each address explicitly 2022-12-10 12:29:10 +00:00
cbd5ccd1c8 desko: disable wifi 2022-12-10 12:27:02 +00:00
cf857eaf9f zsh: more cd aliases (knowledge, secrets) 2022-12-10 12:16:16 +00:00
3a7eb294c7 servo: fix jackett DNS entry 2022-12-10 09:47:28 +00:00
2ccb470adc packages: add tcpdump 2022-12-10 02:56:00 +00:00
0a2a929507 Merge branch 'master' of git.uninsane.org:colin/nix-files 2022-12-09 14:18:40 +00:00
2014d5ce77 servo: bridge port 80/53 from ovpns to native using iptables instead of socat
i should probably narrow the rules to match specifically things destined
for the ovpns address, but for now this should work.
2022-12-09 14:16:48 +00:00
041adb7092 snippets: add nixos search URL 2022-12-09 01:25:24 +00:00
a979521a98 servo: enable ddns against freedns.afraid.org 2022-12-08 14:30:17 +00:00
77881be955 trust-dns: document SOA parameters 2022-12-08 14:23:35 +00:00
0450b4d9a6 trust-dns: fix SOA 2022-12-08 00:46:32 +00:00
edea64a41c trust-dns: move nameserver to subdomain ns1,ns2 2022-12-08 00:39:22 +00:00
90e479592f trust-dns: enable port 53 forward 2022-12-08 00:06:20 +00:00
62d83d94f2 add script to query public IP 2022-12-07 23:39:20 +00:00
52bbe4e9f4 trust-dns: don't restart on failure
for in case anything goes wrong
2022-12-07 12:17:03 +00:00
ab176b8d4b servo: enable trust-dns (experimental) 2022-12-07 12:15:35 +00:00
62df4492a3 Merge branch 'master' of git.uninsane.org:colin/nix-files 2022-12-07 09:47:03 +00:00
f4ed194abc package trust-dns 2022-12-07 09:45:11 +00:00
6420c9fd16 packages: add gajim (at least temporarily, for debugging) 2022-12-07 08:02:14 +00:00
86245b460b Merge branch 'master' of git.uninsane.org:colin/nix-files 2022-12-07 07:41:58 +00:00
bf1ba786b3 packages: add imagemagick (for convert) 2022-12-07 07:41:05 +00:00
35a896a3e2 shell aliases to cd to common places 2022-12-07 07:40:52 +00:00
b4314bd919 mess with XMPP stuff. ejabberd: enable mam, some other acl's that probably aren't used
prosody is still broken
2022-12-07 01:31:17 +00:00
4696209822 nixpatches: update aerc fix hash 2022-12-07 01:14:24 +00:00
c3957d81c2 ejabberd: enable MUC 2022-12-07 00:08:08 +00:00
8a5be00c93 sway: define a "snippets.txt" file for e.g. browser bookmarks 2022-12-06 11:12:27 +00:00
c2db9fe28e periodically archive my torrents so i don't lose them again 2022-12-06 07:17:19 +00:00
ccaac901f7 Merge branch 'master' of git.uninsane.org:colin/nix-files 2022-12-06 07:06:32 +00:00
7f285a8254 ejabberd: enable some more modules which don't conflict 2022-12-06 07:05:59 +00:00
b0b82a3d88 feeds: add Matrix Live podcast
haven't listened. just searching.
2022-12-06 06:58:59 +00:00
b0664d81ab ejabberd: enable mod_pubsub, mod_avatar
i'm able to do this without breaking federation now,
but it doesn't seem to fullly work.
2022-12-05 02:37:35 +00:00
8ba52bb9cd ejabberd: enable mod_{carboncopy,last,offline,private,stream_mgmt} 2022-12-05 02:16:28 +00:00
20f0a19e25 ejabberd: fix federation: disable mod_pubsub and mod_avatar
now i can send messages FROM uninsane.org again
2022-12-05 00:47:48 +00:00
9dc17a3874 ejabberd: enable avatar support
haven't tested that it federates properly -- only that Dino is able to
set it.
2022-12-04 12:38:47 +00:00
2992644901 bluetooth: persist bluetooth earbuds connection 2022-12-04 11:33:03 +00:00
d5d89a10b9 bluetooth: add key for connecting to my car 2022-12-04 10:56:50 +00:00
f7d9fdfe04 packages: add pstree 2022-12-04 03:42:54 +00:00
c42aa2847b aerc: apply patch to fix awk / message reading 2022-12-04 02:48:37 +00:00
768c5c910f update nixpkgs: 2022-11-27 -> 2022-12-02
```
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/a115bb9bd56831941be3776c8a94005867f316a7' (2022-11-27)
  → 'github:NixOS/nixpkgs/b72b8b94cf0c012b0252a9100a636cad69696666' (2022-12-02)
• Updated input 'nixpkgs-stable':
    'github:NixOS/nixpkgs/fecf05d4861f3985e8dee73f08bc82668ef75125' (2022-11-27)
  → 'github:NixOS/nixpkgs/5d7d1d5f742e6bb57dd2e3d7b433fb4010c7af22' (2022-12-02)
```
2022-12-03 22:56:00 +00:00
8790a7d9fd note about persisting bluetooth config 2022-12-03 11:35:03 +00:00
7c36a0d522 bluetooth: share connections across machines 2022-12-03 11:05:09 +00:00
977a80d59e Merge branch 'staging/moby-6.1.0-rc7' 2022-12-03 09:05:13 +00:00
63c92a44ed servo: ejabberd: enable file uploads 2022-12-03 08:57:10 +00:00
bf838ea203 packages: add tree as system package 2022-12-03 08:56:26 +00:00
120 changed files with 5763 additions and 974 deletions

76
flake.lock generated
View File

@@ -36,29 +36,14 @@
"type": "github"
}
},
"impermanence": {
"locked": {
"lastModified": 1668668915,
"narHash": "sha256-QjY4ZZbs9shwO4LaLpvlU2bO9J1juYhO9NtV3nrbnYQ=",
"owner": "nix-community",
"repo": "impermanence",
"rev": "5df9108b346f8a42021bf99e50de89c9caa251c3",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "impermanence",
"type": "github"
}
},
"mobile-nixos": {
"flake": false,
"locked": {
"lastModified": 1668897543,
"narHash": "sha256-1bjvy5zi/6KDzhN3ihOUEA6y5FFEOf5xvIbf65RWIh0=",
"lastModified": 1670131242,
"narHash": "sha256-T/o1/3gffr010fsqgNshs1NJJjsnUYvQnUZgm6hilsY=",
"owner": "nixos",
"repo": "mobile-nixos",
"rev": "25eec596116553112681d72ee4880107fc3957fa",
"rev": "5ee45cc1f8e43f4af14ee17ccef9156b0db8cd77",
"type": "github"
},
"original": {
@@ -69,11 +54,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1669542132,
"narHash": "sha256-DRlg++NJAwPh8io3ExBJdNW7Djs3plVI5jgYQ+iXAZQ=",
"lastModified": 1672791794,
"narHash": "sha256-mqGPpGmwap0Wfsf3o2b6qHJW1w2kk/I6cGCGIU+3t6o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a115bb9bd56831941be3776c8a94005867f316a7",
"rev": "9813adc7f7c0edd738c6bdd8431439688bb0cb3d",
"type": "github"
},
"original": {
@@ -82,41 +67,40 @@
"type": "indirect"
}
},
"nixpkgs-22_05": {
"locked": {
"lastModified": 1669513802,
"narHash": "sha256-AmTRNi8bHgJlmaNe3r5k+IMFbbXERM/KarqveMAZmsY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6649e08812f579581bfb4cada3ba01e30485c891",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-22.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1669546925,
"narHash": "sha256-Gvtk9agz88tBgqmCdHl5U7gYttTkiuEd8/Rq1Im0pTg=",
"lastModified": 1672844754,
"narHash": "sha256-o26WabuHABQsaHxxmIrR3AQRqDFUEdLckLXkVCpIjSU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fecf05d4861f3985e8dee73f08bc82668ef75125",
"rev": "e9ade2c8240e00a4784fac282a502efff2786bdc",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-22.05",
"ref": "nixos-22.11",
"type": "indirect"
}
},
"nixpkgs-stable_2": {
"locked": {
"lastModified": 1672500394,
"narHash": "sha256-yzwBzCoeRBoRzm7ySHhm72kBG0QjgFalLz2FY48iLI4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "feda52be1d59f13b9aa02f064b4f14784b9a06c8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-22.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"home-manager": "home-manager",
"impermanence": "impermanence",
"mobile-nixos": "mobile-nixos",
"nixpkgs": "nixpkgs",
"nixpkgs-stable": "nixpkgs-stable",
@@ -129,14 +113,14 @@
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-22_05": "nixpkgs-22_05"
"nixpkgs-stable": "nixpkgs-stable_2"
},
"locked": {
"lastModified": 1669714206,
"narHash": "sha256-9aiMbzRL8REsyi9U0eZ+lT4s7HaILA1gh9n2apKzLxU=",
"lastModified": 1672543202,
"narHash": "sha256-nlCUtcIZxaBqUBG1GyaXhZmfyG5WK4e6LqypP8llX9E=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "8295b8139ef7baadeb90c5cad7a40c4c9297ebf7",
"rev": "b35586cc5abacd4eba9ead138b53e2a60920f781",
"type": "github"
},
"original": {

View File

@@ -4,7 +4,7 @@
{
inputs = {
nixpkgs-stable.url = "nixpkgs/nixos-22.05";
nixpkgs-stable.url = "nixpkgs/nixos-22.11";
nixpkgs.url = "nixpkgs/nixos-unstable";
mobile-nixos = {
url = "github:nixos/mobile-nixos";
@@ -18,7 +18,6 @@
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
impermanence.url = "github:nix-community/impermanence";
uninsane = {
url = "git+https://git.uninsane.org/colin/uninsane";
inputs.nixpkgs.follows = "nixpkgs";
@@ -32,13 +31,15 @@
mobile-nixos,
home-manager,
sops-nix,
impermanence,
uninsane
}: let
patchedPkgs = system: nixpkgs.legacyPackages.${system}.applyPatches {
name = "nixpkgs-patched-uninsane";
src = nixpkgs;
patches = import ./nixpatches/list.nix nixpkgs.legacyPackages.${system}.fetchpatch;
patches = import ./nixpatches/list.nix {
inherit (nixpkgs.legacyPackages.${system}) fetchpatch;
inherit (nixpkgs.lib) fakeHash;
};
};
# return something which behaves like `pkgs`, for the provided system
# `local` = architecture of builder. `target` = architecture of the system beying deployed to
@@ -51,12 +52,10 @@
in (nixosSystem {
# by default the local system is the same as the target, employing emulation when they differ
system = target;
specialArgs = { inherit mobile-nixos home-manager impermanence; };
modules = [
./modules
(import ./hosts/instantiate.nix name)
home-manager.nixosModule
impermanence.nixosModule
sops-nix.nixosModules.sops
{
nixpkgs.overlays = [
@@ -69,8 +68,11 @@
# the config can explicitly pull such packages from `pkgs.cross` to do more efficient cross-compilation.
cross = (nixpkgsFor local target) // (customPackagesFor local target);
stable = import nixpkgs-stable { system = target; };
# cross-compatible packages
# gocryptfs = cross.gocryptfs;
# pinned packages:
})
];
}

View File

@@ -0,0 +1,18 @@
{ 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
''
);
}

View File

@@ -1,8 +1,10 @@
{ pkgs, ... }:
{
imports = [
./bluetooth.nix
./fs.nix
./hardware
./i2p.nix
./machine-id.nix
./net.nix
./secrets.nix
@@ -16,6 +18,16 @@
sane.packages.enableConsolePkgs = true;
sane.packages.enableSystemPkgs = true;
sane.impermanence.dirs.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.
];
nixpkgs.config.allowUnfree = true;
# time.timeZone = "America/Los_Angeles";
@@ -56,19 +68,8 @@
};
# enable zsh completions
environment.pathsToLink = [ "/share/zsh" ];
environment.systemPackages = with pkgs; [
# required for pam_mount
gocryptfs
];
# link debug symbols into /run/current-system/sw/lib/debug
# hopefully picked up by gdb automatically?
environment.enableDebugInfo = true;
security.pam.mount.enable = true;
# security.pam.mount.debugLevel = 1;
# security.pam.enableSSHAgentAuth = true; # ??
# needed for `allow_other` in e.g. gocryptfs mounts
# or i guess going through mount.fuse sets suid so that's not necessary?
# programs.fuse.userAllowOther = true;
}

4
hosts/common/i2p.nix Normal file
View File

@@ -0,0 +1,4 @@
{ ... }:
{
services.i2p.enable = true;
}

View File

@@ -1,11 +1,16 @@
{ ... }:
{
# we wan't an /etc/machine-id which is consistent across boot so that `journalctl` will actually show us
# logs from previous boots.
# maybe there's a config option for this (since persistent machine-id is bad for reasons listed in impermanence.nix),
# but for now generate it from ssh keys.
# /etc/machine-id is a globally unique identifier used for:
# - systemd-networkd: DHCP lease renewal (instead of keying by the MAC address)
# - systemd-journald: to filter logs by host
# - chromium (potentially to track re-installations)
# - gdbus; system services that might upgrade to AF_LOCAL if both services can confirm they're on the same machine
# because of e.g. the chromium use, we *don't want* to persist this.
# however, `journalctl` won't show logs from previous boots unless the machine-ids match.
# so for now, generate something unique from the host ssh key.
# TODO: move this into modules?
system.activationScripts.machine-id = {
deps = [ "persist-ssh-host-keys" ];
deps = [ "etc" ];
text = "sha256sum /etc/ssh/host_keys/ssh_host_ed25519_key | cut -c 1-32 > /etc/machine-id";
};
}

View File

@@ -46,34 +46,4 @@
${install-iwd}/bin/install-iwd /run/secrets/iwd /var/lib/iwd
''
);
# TODO: use a glob, or a list, or something?
sops.secrets."iwd/community-university.psk" = {
sopsFile = ../../secrets/universal/net/community-university.psk.bin;
format = "binary";
};
sops.secrets."iwd/friend-libertarian-dod.psk" = {
sopsFile = ../../secrets/universal/net/friend-libertarian-dod.psk.bin;
format = "binary";
};
sops.secrets."iwd/friend-rationalist-empathist.psk" = {
sopsFile = ../../secrets/universal/net/friend-rationalist-empathist.psk.bin;
format = "binary";
};
sops.secrets."iwd/home-bedroom.psk" = {
sopsFile = ../../secrets/universal/net/home-bedroom.psk.bin;
format = "binary";
};
sops.secrets."iwd/home-shared-24G.psk" = {
sopsFile = ../../secrets/universal/net/home-shared-24G.psk.bin;
format = "binary";
};
sops.secrets."iwd/home-shared.psk" = {
sopsFile = ../../secrets/universal/net/home-shared.psk.bin;
format = "binary";
};
sops.secrets."iwd/iphone" = {
sopsFile = ../../secrets/universal/net/iphone.psk.bin;
format = "binary";
};
}

View File

@@ -33,10 +33,6 @@
# You can avoid this by adding a string to the full path instead, i.e.
# sops.defaultSopsFile = "/root/.sops/secrets/example.yaml";
sops.defaultSopsFile = ../../secrets/universal.yaml;
# This will automatically import SSH keys as age keys
sops.age.sshKeyPaths = [
"/etc/ssh/host_keys/ssh_host_ed25519_key"
];
sops.gnupg.sshKeyPaths = []; # disable RSA key import
# This is using an age key that is expected to already be in the filesystem
# sops.age.keyFile = "/home/colin/.ssh/age.pub";
@@ -48,6 +44,81 @@
# owner = config.users.users.colin.name;
# };
# sops.secrets."myservice/my_subdir/my_secret" = {};
## universal secrets
# TODO: glob these?
sops.secrets."jackett_apikey" = {
sopsFile = ../../secrets/universal.yaml;
owner = config.users.users.colin.name;
};
sops.secrets."router_passwd" = {
sopsFile = ../../secrets/universal.yaml;
};
sops.secrets."wg_ovpnd_us_privkey" = {
sopsFile = ../../secrets/universal.yaml;
};
sops.secrets."wg_ovpnd_us-atl_privkey" = {
sopsFile = ../../secrets/universal.yaml;
};
sops.secrets."wg_ovpnd_us-mi_privkey" = {
sopsFile = ../../secrets/universal.yaml;
};
sops.secrets."wg_ovpnd_ukr_privkey" = {
sopsFile = ../../secrets/universal.yaml;
};
sops.secrets."snippets" = {
sopsFile = ../../secrets/universal/snippets.bin;
format = "binary";
owner = config.users.users.colin.name;
};
sops.secrets."bt/car" = {
sopsFile = ../../secrets/universal/bt/car.bin;
format = "binary";
};
sops.secrets."bt/earbuds" = {
sopsFile = ../../secrets/universal/bt/earbuds.bin;
format = "binary";
};
sops.secrets."bt/portable-speaker" = {
sopsFile = ../../secrets/universal/bt/portable-speaker.bin;
format = "binary";
};
sops.secrets."iwd/community-university.psk" = {
sopsFile = ../../secrets/universal/net/community-university.psk.bin;
format = "binary";
};
sops.secrets."iwd/friend-libertarian-dod.psk" = {
sopsFile = ../../secrets/universal/net/friend-libertarian-dod.psk.bin;
format = "binary";
};
sops.secrets."iwd/friend-rationalist-empathist.psk" = {
sopsFile = ../../secrets/universal/net/friend-rationalist-empathist.psk.bin;
format = "binary";
};
sops.secrets."iwd/home-bedroom.psk" = {
sopsFile = ../../secrets/universal/net/home-bedroom.psk.bin;
format = "binary";
};
sops.secrets."iwd/home-shared-24G.psk" = {
sopsFile = ../../secrets/universal/net/home-shared-24G.psk.bin;
format = "binary";
};
sops.secrets."iwd/home-shared.psk" = {
sopsFile = ../../secrets/universal/net/home-shared.psk.bin;
format = "binary";
};
sops.secrets."iwd/iphone" = {
sopsFile = ../../secrets/universal/net/iphone.psk.bin;
format = "binary";
};
sops.secrets."iwd/parents" = {
sopsFile = ../../secrets/universal/net/parents.psk.bin;
format = "binary";
};
}

View File

@@ -1,21 +1,10 @@
{ ... }:
{ config, lib, ... }:
{
# we place the host keys (which we want to be persisted) into their own directory so that we can
# bind mount that whole directory instead of doing it per-file.
# otherwise, this is identical to nixos defaults
sane.impermanence.service-dirs = [ "/etc/ssh/host_keys" ];
# we can't naively `mount /etc/ssh/host_keys` directly,
# as /etc/fstab may not be populated yet (since that file depends on e.g. activationScripts.users)
# we can't even depend on impermanence's `createPersistentStorageDirs` to create the source/target directories
# since that also depends on `users`.
system.activationScripts.persist-ssh-host-keys.text = ''
mkdir -p /etc/ssh/host_keys
mount --bind /nix/persist/etc/ssh/host_keys /etc/ssh/host_keys
'';
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"; }
];
}

View File

@@ -7,7 +7,10 @@ let
# see nixpkgs/nixos/modules/services/networking/dhcpcd.nix
hasDHCP = config.networking.dhcpcd.enable &&
(config.networking.useDHCP || any (i: i.useDHCP == true) (attrValues config.networking.interfaces));
mkSymlink = target: {
symlink.target = target;
wantedBeforeBy = [ "multi-user.target" ];
};
in
{
options = {
@@ -27,6 +30,8 @@ in
# sets group to "users" (?)
isNormalUser = true;
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.
@@ -52,29 +57,68 @@ in
shell = pkgs.zsh;
openssh.authorizedKeys.keys = builtins.attrValues (import ../../modules/pubkeys.nix).users;
pamMount = {
# mount encrypted stuff at login
# requires that login password == fs encryption password
# fstype = "fuse";
# path = "${pkgs.gocryptfs}/bin/gocryptfs#/nix/persist/home/colin/private";
fstype = "fuse.gocryptfs";
path = "/nix/persist/home/colin/private";
mountpoint = "/home/colin/private";
options="nodev,nosuid,quiet,allow_other";
# mount encrypted stuff at login
# some other nix pam users:
# - <https://github.com/g00pix/nixconf/blob/32c04f6fa843fed97639dd3f09e157668d3eea1f/profiles/sshfs.nix>
# - <https://github.com/lourkeur/distro/blob/11173454c6bb50f7ccab28cc2c757dca21446d1d/nixos/profiles/users/louis-full.nix>
# - <https://github.com/dnr/sample-nix-code/blob/03494480c1fae550c033aa54fd96aeb3827761c5/nixos/laptop.nix>
pamMount = let
priv = config.fileSystems."/home/colin/private";
in {
fstype = priv.fsType;
path = priv.device;
mountpoint = priv.mountPoint;
options = builtins.concatStringsSep "," priv.options;
};
};
sane.impermanence.home-dirs = [
# cache is probably too big to fit on the tmpfs
# TODO: we could bind-mount it to something which gets cleared per boot, though.
".cache"
security.pam.mount.enable = true;
# ensure ~ perms are known to sane.fs module.
# TODO: this is generic enough to be lifted up into sane.fs itself.
sane.fs."/home/colin".dir.acl = {
user = "colin";
group = config.users.users.colin.group;
mode = config.users.users.colin.homeMode;
};
sane.impermanence.dirs.home.plaintext = [
"archive"
"dev"
# TODO: records should be private
"records"
"ref"
"tmp"
"use"
"Music"
"Pictures"
"Videos"
".cargo"
".rustup"
# TODO: move this to ~/private!
".local/share/keyrings"
];
# TODO: fix this ugly solution that allows moby to have firefox cache not erased every boot.
sane.impermanence.dirs.home.cryptClearOnBoot = lib.mkIf (config.networking.hostName != "moby") [
# cache is probably too big to fit on the tmpfs
# ".cache"
config.sane.web-browser.cacheDir
];
sane.impermanence.service-dirs = mkIf cfg.guest.enable [
{ user = "guest"; group = "users"; directory = "/home/guest"; }
# convenience
sane.fs."/home/colin/knowledge" = mkSymlink "/home/colin/private/knowledge";
sane.fs."/home/colin/nixos" = mkSymlink "/home/colin/dev/nixos";
sane.fs."/home/colin/Videos/servo" = mkSymlink "/mnt/servo-media/Videos";
sane.fs."/home/colin/Videos/servo-incomplete" = mkSymlink "/mnt/servo-media/incomplete";
sane.fs."/home/colin/Music/servo" = mkSymlink "/mnt/servo-media/Music";
# used by password managers, e.g. unix `pass`
sane.fs."/home/colin/.password-store" = mkSymlink "/home/colin/knowledge/secrets/accounts";
sane.impermanence.dirs.sys.plaintext = mkIf cfg.guest.enable [
# intentionally allow other users to write to the guest folder
{ directory = "/home/guest"; user = "guest"; group = "users"; mode = "0775"; }
];
users.users.guest = mkIf cfg.guest.enable {
isNormalUser = true;

View File

@@ -1,58 +1,66 @@
{ config, ... }:
{ config, lib, ... }:
{
networking.wg-quick.interfaces.ovpnd-us = {
# to add a new OVPN VPN:
# - generate a privkey `wg genkey`
# - add this key to `sops secrets/universal.yaml`
# - upload pubkey to OVPN.com
# - generate config @ OVPN.com
# - copy the Address, PublicKey, Endpoint from OVPN's config
# N.B.: maximum interface name in Linux is 15 characters.
let
def-ovpn = name: { endpoint, publicKey, address }: {
networking.wg-quick.interfaces."ovpnd-${name}" = {
inherit address;
privateKeyFile = config.sops.secrets."wg_ovpnd_${name}_privkey".path;
dns = [
"46.227.67.134"
"192.165.9.158"
];
peers = [
{
allowedIPs = [
"0.0.0.0/0"
"::/0"
];
inherit endpoint publicKey;
}
];
# to start: `systemctl start wg-quick-ovpnd-${name}`
autostart = false;
};
};
in lib.mkMerge [
(def-ovpn "us" {
endpoint = "vpn31.prd.losangeles.ovpn.com:9929";
publicKey = "VW6bEWMOlOneta1bf6YFE25N/oMGh1E1UFBCfyggd0k=";
address = [
"172.27.237.218/32"
"fd00:0000:1337:cafe:1111:1111:ab00:4c8f/128"
];
dns = [
"46.227.67.134"
"192.165.9.158"
})
# NB: us-* share the same wg key and link-local addrs, but distinct public addresses
(def-ovpn "us-atl" {
endpoint = "vpn18.prd.atlanta.ovpn.com:9929";
publicKey = "Dpg/4v5s9u0YbrXukfrMpkA+XQqKIFpf8ZFgyw0IkE0=";
address = [
"172.21.182.178/32"
"fd00:0000:1337:cafe:1111:1111:cfcb:27e3/128"
];
peers = [
{
allowedIPs = [
"0.0.0.0/0"
"::/0"
];
endpoint = "vpn31.prd.losangeles.ovpn.com:9929";
publicKey = "VW6bEWMOlOneta1bf6YFE25N/oMGh1E1UFBCfyggd0k=";
}
})
(def-ovpn "us-mi" {
endpoint = "vpn34.prd.miami.ovpn.com:9929";
publicKey = "VtJz2irbu8mdkIQvzlsYhU+k9d55or9mx4A2a14t0V0=";
address = [
"172.21.182.178/32"
"fd00:0000:1337:cafe:1111:1111:cfcb:27e3/128"
];
privateKeyFile = config.sops.secrets.wg_ovpnd_us_privkey.path;
# to start: `systemctl start wg-quick-ovpnd-us`
autostart = false;
};
networking.wg-quick.interfaces.ovpnd-ukr = {
})
(def-ovpn "ukr" {
endpoint = "vpn96.prd.kyiv.ovpn.com:9929";
publicKey = "CjZcXDxaaKpW8b5As1EcNbI6+42A6BjWahwXDCwfVFg=";
address = [
"172.18.180.159/32"
"fd00:0000:1337:cafe:1111:1111:ec5c:add3/128"
];
dns = [
"46.227.67.134"
"192.165.9.158"
];
peers = [
{
allowedIPs = [
"0.0.0.0/0"
"::/0"
];
endpoint = "vpn96.prd.kyiv.ovpn.com:9929";
publicKey = "CjZcXDxaaKpW8b5As1EcNbI6+42A6BjWahwXDCwfVFg=";
}
];
privateKeyFile = config.sops.secrets.wg_ovpnd_ukr_privkey.path;
# to start: `systemctl start wg-quick-ovpnd-ukr`
autostart = false;
};
sops.secrets."wg_ovpnd_us_privkey" = {
sopsFile = ../../secrets/universal.yaml;
};
sops.secrets."wg_ovpnd_ukr_privkey" = {
sopsFile = ../../secrets/universal.yaml;
};
}
})
]

View File

@@ -25,6 +25,9 @@
neededForUsers = true;
};
# don't enable wifi by default: it messes with connectivity.
systemd.services.iwd.enable = false;
# default config: https://man.archlinux.org/man/snapper-configs.5
# defaults to something like:
# - hourly snapshots
@@ -49,7 +52,7 @@
remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play
dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server
};
sane.impermanence.home-dirs = [
sane.impermanence.dirs.home.plaintext = [
".steam"
".local/share/Steam"
];

View File

@@ -1,16 +1,7 @@
{ ... }:
{
# root is a tmpfs so that we have an ephemeral system ("impermanence" handles the state)
fileSystems."/" = {
device = "none";
fsType = "tmpfs";
options = [
"mode=755"
"size=1G"
"defaults"
];
};
sane.impermanence.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" = {

View File

@@ -1,16 +1,7 @@
{ ... }:
{
# root is a tmpfs so that we have an ephemeral system ("impermanence" handles the state)
fileSystems."/" = {
device = "none";
fsType = "tmpfs";
options = [
"mode=755"
"size=1G"
"defaults"
];
};
sane.impermanence.root-on-tmpfs = true;
# we need a /tmp of default size (half RAM) for building large nix things
fileSystems."/tmp" = {
device = "none";

View File

@@ -1,4 +1,4 @@
{ config, pkgs, lib, mobile-nixos, ... }:
{ config, pkgs, lib, ... }:
{
imports = [
./firmware.nix
@@ -24,8 +24,11 @@
};
# usability compromises
sane.impermanence.home-dirs = [
sane.impermanence.dirs.home.private = [
config.sane.web-browser.dotDir
config.sane.web-browser.cacheDir
];
sane.impermanence.dirs.home.plaintext = [
".config/pulse" # persist pulseaudio volume
];
@@ -40,7 +43,9 @@
boot.loader.efi.canTouchEfiVariables = false;
# /boot space is at a premium. default was 20.
boot.loader.generic-extlinux-compatible.configurationLimit = 10;
# even 10 can be too much
# TODO: compress moby kernels!
boot.loader.generic-extlinux-compatible.configurationLimit = 8;
# mobile.bootloader.enable = false;
# mobile.boot.stage-1.enable = false;
# boot.initrd.systemd.enable = false;

View File

@@ -1,17 +1,7 @@
{ ... }:
{
# root is a tmpfs so that we have an ephemeral system ("impermanence" handles the state)
fileSystems."/" = {
device = "none";
fsType = "tmpfs";
options = [
"mode=755"
"size=1G"
"defaults"
];
};
sane.impermanence.root-on-tmpfs = true;
fileSystems."/nix" = {
device = "/dev/disk/by-uuid/1f1271f8-53ce-4081-8a29-60a4a6b5d6f9";
fsType = "btrfs";

View File

@@ -14,9 +14,8 @@
pkgs.freshrss
];
sane.impermanence.enable = true;
sane.services.dyn-dns.enable = true;
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade
sane.services.nixserve.enable = true;
sane.services.nixserve.sopsFile = ../../secrets/servo.yaml;
boot.loader.efi.canTouchEfiVariables = false;
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];

View File

@@ -1,16 +1,7 @@
{ ... }:
{
# root is a tmpfs so that we have an ephemeral system ("impermanence" handles the state)
fileSystems."/" = {
device = "none";
fsType = "tmpfs";
options = [
"mode=755"
"size=1G"
"defaults"
];
};
sane.impermanence.root-on-tmpfs = true;
# we need a /tmp for building large nix things
fileSystems."/tmp" = {
device = "none";
@@ -36,7 +27,7 @@
};
# slow, external storage (for archiving, etc)
fileSystems."/nix/persist/ext" = {
fileSystems."/mnt/impermanence/ext" = {
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
fsType = "btrfs";
options = [
@@ -45,27 +36,31 @@
];
};
sane.impermanence.service-dirs = [
sane.impermanence.stores."ext" = {
origin = "/mnt/impermanence/ext/persist";
storeDescription = "external HDD storage";
};
sane.fs."/mnt/impermanence/ext".mount = {};
sane.impermanence.dirs.sys.plaintext = [
# TODO: this is overly broad; only need media and share directories to be persisted
{ user = "colin"; group = "users"; directory = "/var/lib/uninsane"; }
];
# direct these media directories to external storage
environment.persistence."/nix/persist/ext/persist" = {
directories = [
({
user = "colin";
group = "users";
mode = "0777";
directory = "/var/lib/uninsane/media/Videos";
})
({
user = "colin";
group = "users";
mode = "0777";
directory = "/var/lib/uninsane/media/freeleech";
})
];
};
# make sure large media is stored to the HDD
sane.impermanence.dirs.sys.ext = [
{
user = "colin";
group = "users";
mode = "0777";
directory = "/var/lib/uninsane/media/Videos";
}
{
user = "colin";
group = "users";
mode = "0777";
directory = "/var/lib/uninsane/media/freeleech";
}
];
# in-memory compressed RAM (seems to be dynamically sized)
# zramSwap = {

View File

@@ -13,83 +13,155 @@
# networking.firewall.enable = false;
networking.firewall.enable = true;
# TODO: split these into the submodules
networking.firewall.allowedTCPPorts = [
25 # SMTP
80 # HTTP
143 # IMAP
443 # HTTPS
465 # SMTPS
587 # SMTPS/submission
993 # IMAPS
4001 # IPFS
];
networking.firewall.allowedUDPPorts = [
1900 7359 # DLNA: https://jellyfin.org/docs/general/networking/index.html
4001 # IPFS
# this is needed to forward packets from the VPN to the host
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
# unless we add interface-specific settings for each VPN, we have to define nameservers globally.
# networking.nameservers = [
# "1.1.1.1"
# "9.9.9.9"
# ];
# use systemd's stub resolver.
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets
# in the ovnps namespace to use the provider's DNS resolvers.
# a weakness is we can only query 1 NS at a time (unless we were to clone the packets?)
# there also seems to be some cache somewhere that's shared between the two namespaces.
# i think this is a libc thing. might need to leverage proper cgroups to _really_ kill it.
# - getent ahostsv4 www.google.com
# - try fix: <https://serverfault.com/questions/765989/connect-to-3rd-party-vpn-server-but-dont-use-it-as-the-default-route/766290#766290>
services.resolved.enable = true;
networking.nameservers = [
# use systemd-resolved resolver
# full resolver (which understands /etc/hosts) lives on 127.0.0.53
# stub resolver (just forwards upstream) lives on 127.0.0.54
"127.0.0.53"
];
# we need to use externally-visible nameservers in order for VPNs to be able to resolve hosts.
networking.nameservers = [
"1.1.1.1"
"9.9.9.9"
];
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
# in a way that's unaware of my VPN routing, so routes are frequently poor against
# services which advertise different IPs based on geolocation.
# nscd claims to be usable without a cache, but in practice i can't get it to not cache!
# nsncd is the Name Service NON-Caching Daemon. it's a drop-in that doesn't cache;
# this is OK on the host -- because systemd-resolved caches. it's probably sub-optimal
# in the netns and we query upstream DNS more often than needed. hm.
# TODO: run a separate recursive resolver in each namespace.
services.nscd.enableNsncd = true;
# services.resolved.extraConfig = ''
# # docs: `man resolved.conf`
# # DNS servers to use via the `wg0` interface.
# # i hope that from the root ns, these aren't visible.
# DNS=46.227.67.134%wg0 192.165.9.158%wg0
# FallbackDNS=1.1.1.1 9.9.9.9
# '';
# OVPN CONFIG (https://www.ovpn.com):
# DOCS: https://nixos.wiki/wiki/WireGuard
# if you `systemctl restart wireguard-wg0`, make sure to also restart any other services in `NetworkNamespacePath = .../ovpns`.
# TODO: why not create the namespace as a seperate operation (nix config for that?)
networking.wireguard.enable = true;
networking.wireguard.interfaces.wg0 = {
networking.wireguard.interfaces.wg0 = let
ip = "${pkgs.iproute2}/bin/ip";
in-ns = "${ip} netns exec ovpns";
iptables = "${pkgs.iptables}/bin/iptables";
veth-host-ip = "10.0.1.5";
veth-local-ip = "10.0.1.6";
vpn-ip = "185.157.162.178";
# DNS = 46.227.67.134, 192.165.9.158, 2a07:a880:4601:10f0:cd45::1, 2001:67c:750:1:cafe:cd45::1
vpn-dns = "46.227.67.134";
in {
privateKeyFile = config.sops.secrets.wg_ovpns_privkey.path;
# wg is active only in this namespace.
# run e.g. ip netns exec ovpns <some command like ping/curl/etc, it'll go through wg>
# sudo ip netns exec ovpns ping www.google.com
# note: without the namespace, you'll need to add a specific route through eth0 for the peer (185.157.162.178/32)
interfaceNamespace = "ovpns";
preSetup = "${pkgs.iproute2}/bin/ip netns add ovpns || true";
postShutdown = "${pkgs.iproute2}/bin/ip netns delete ovpns";
ips = [
"185.157.162.178/32"
];
peers = [
{
publicKey = "SkkEZDCBde22KTs/Hc7FWvDBfdOCQA4YtBEuC3n5KGs=";
endpoint = "vpn36.prd.amsterdam.ovpn.com:9930";
endpoint = "185.157.162.10:9930";
# alternatively: use hostname, but that presents bootstrapping issues (e.g. if host net flakes)
# endpoint = "vpn36.prd.amsterdam.ovpn.com:9930";
allowedIPs = [ "0.0.0.0/0" ];
# nixOS says this is important for keeping NATs active
persistentKeepalive = 25;
# re-executes wg this often. docs hint that this might help wg notice DNS/hostname changes.
# so, maybe that helps if we specify endpoint as a domain name
# dynamicEndpointRefreshSeconds = 30;
# when refresh fails, try it again after this period instead.
# TODO: not avail until nixpkgs upgrade
# dynamicEndpointRefreshRestartSeconds = 5;
}
];
preSetup = "" + ''
${ip} netns add ovpns || echo "ovpns already exists"
'';
postShutdown = "" + ''
${in-ns} ip link del ovpns-veth-b || echo "couldn't delete ovpns-veth-b"
${ip} link del ovpns-veth-a || echo "couldn't delete ovpns-veth-a"
${ip} netns delete ovpns || echo "couldn't delete ovpns"
# restore rules/routes
${ip} rule del from ${veth-host-ip} lookup ovpns pref 50 || echo "couldn't delete init -> ovpns rule"
${ip} route del default via ${veth-local-ip} dev ovpns-veth-a proto kernel src ${veth-host-ip} metric 1002 table ovpns || echo "couldn't delete init -> ovpns route"
${ip} rule add from all lookup local pref 0
${ip} rule del from all lookup local pref 100
'';
postSetup = "" + ''
# DOCS:
# - some of this approach is described here: <https://josephmuia.ca/2018-05-16-net-namespaces-veth-nat/>
# - iptables primer: <https://danielmiessler.com/study/iptables/>
# create veth pair
${ip} link add ovpns-veth-a type veth peer name ovpns-veth-b
${ip} addr add ${veth-host-ip}/24 dev ovpns-veth-a
${ip} link set ovpns-veth-a up
# mv veth-b into the ovpns namespace
${ip} link set ovpns-veth-b netns ovpns
${in-ns} ip addr add ${veth-local-ip}/24 dev ovpns-veth-b
${in-ns} ip link set ovpns-veth-b up
# make it so traffic originating from the host side of the veth
# is sent over the veth no matter its destination.
${ip} rule add from ${veth-host-ip} lookup ovpns pref 50
# for traffic originating at the host veth to the WAN, use the veth as our gateway
# not sure if the metric 1002 matters.
${ip} route add default via ${veth-local-ip} dev ovpns-veth-a proto kernel src ${veth-host-ip} metric 1002 table ovpns
# give the default route lower priority
${ip} rule add from all lookup local pref 100
${ip} rule del from all lookup local pref 0
# bridge HTTP traffic:
# any external port-80 request sent to the VPN addr will be forwarded to the rootns.
# this exists so LetsEncrypt can procure a cert for the MX over http.
# TODO: we could use _acme_challence.mx.uninsane.org CNAME to avoid this forwarding
# - <https://community.letsencrypt.org/t/where-does-letsencrypt-resolve-dns-from/37607/8>
${in-ns} ${iptables} -A PREROUTING -t nat -p tcp --dport 80 -m iprange --dst-range ${vpn-ip} \
-j DNAT --to-destination ${veth-host-ip}:80
# we also bridge DNS traffic
${in-ns} ${iptables} -A PREROUTING -t nat -p udp --dport 53 -m iprange --dst-range ${vpn-ip} \
-j DNAT --to-destination ${veth-host-ip}:53
${in-ns} ${iptables} -A PREROUTING -t nat -p tcp --dport 53 -m iprange --dst-range ${vpn-ip} \
-j DNAT --to-destination ${veth-host-ip}:53
# in order to access DNS in this netns, we need to route it to the VPN's nameservers
# - alternatively, we could fix DNS servers like 1.1.1.1.
${in-ns} ${iptables} -A OUTPUT -t nat -p udp --dport 53 -m iprange --dst-range 127.0.0.53 \
-j DNAT --to-destination ${vpn-dns}:53
'';
};
systemd.services.wg0veth = {
description = "veth pair to allow communication between host and wg0 netns";
after = [ "wireguard-wg0.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = with pkgs; writeScript "wg0veth-start" ''
#!${bash}/bin/bash
# create veth pair
${iproute2}/bin/ip link add ovpns-veth-a type veth peer name ovpns-veth-b
${iproute2}/bin/ip addr add 10.0.1.5/24 dev ovpns-veth-a
${iproute2}/bin/ip link set ovpns-veth-a up
# mv veth-b into the ovpns namespace
${iproute2}/bin/ip link set ovpns-veth-b netns ovpns
${iproute2}/bin/ip -n ovpns addr add 10.0.1.6/24 dev ovpns-veth-b
${iproute2}/bin/ip -n ovpns link set ovpns-veth-b up
# forward HTTP traffic, which we need for letsencrypt to work
${iproute2}/bin/ip netns exec ovpns ${socat}/bin/socat TCP4-LISTEN:80,reuseaddr,fork,su=nobody TCP4:10.0.1.5:80 &
'';
ExecStop = with pkgs; writeScript "wg0veth-stop" ''
#!${bash}/bin/bash
${iproute2}/bin/ip -n wg0 link del ovpns-veth-b
${iproute2}/bin/ip link del ovpns-veth-a
'';
};
};
# create a new routing table that we can use to proxy traffic out of the root namespace
# through the ovpns namespace, and to the WAN via VPN.
networking.iproute2.rttablesExtraConfig = ''
5 ovpns
'';
networking.iproute2.enable = true;
sops.secrets."wg_ovpns_privkey" = {
sopsFile = ../../secrets/servo.yaml;

View File

@@ -0,0 +1,31 @@
{ config, lib, pkgs, ... }:
# using manual ddns now
lib.mkIf false
{
systemd.services.ddns-afraid = {
description = "update dynamic DNS entries for freedns.afraid.org";
serviceConfig = {
EnvironmentFile = config.sops.secrets.ddns_afraid.path;
# TODO: ProtectSystem = "strict";
# TODO: ProtectHome = "full";
# TODO: PrivateTmp = true;
};
script = let
curl = "${pkgs.curl}/bin/curl -4";
in ''
${curl} "https://freedns.afraid.org/dynamic/update.php?$AFRAID_KEY"
'';
};
systemd.timers.ddns-afraid = {
wantedBy = [ "multi-user.target" ];
timerConfig = {
OnStartupSec = "2min";
OnUnitActiveSec = "10min";
};
};
sops.secrets."ddns_afraid" = {
sopsFile = ../../../secrets/servo.yaml;
};
}

View File

@@ -1,5 +1,7 @@
{ config, pkgs, ... }:
{ config, lib, pkgs, ... }:
# we use manual DDNS now
lib.mkIf false
{
systemd.services.ddns-he = {
description = "update dynamic DNS entries for HurricaneElectric";

View File

@@ -1,6 +1,7 @@
{ ... }:
{
imports = [
./ddns-afraid.nix
./ddns-he.nix
./ejabberd.nix
./freshrss.nix
@@ -9,13 +10,17 @@
./ipfs.nix
./jackett.nix
./jellyfin.nix
./kiwix-serve.nix
./matrix
./navidrome.nix
./nixserve.nix
./nginx.nix
./pleroma.nix
./postfix.nix
./postgres.nix
./prosody.nix
./transmission.nix
./trust-dns.nix
./wikipedia.nix
];
}

View File

@@ -1,84 +1,393 @@
# docs:
# - <https://docs.ejabberd.im/admin/configuration/basic>
# example configs:
# - <https://github.com/vkleen/machines/blob/138a2586ce185d7cf201d4e1fe898c83c4af52eb/hosts/europium/ejabberd.nix>
# - <https://github.com/Mic92/stockholm/blob/675ef0088624c9de1cb531f318446316884a9d3d/tv/3modules/ejabberd/default.nix>
# - <https://github.com/buffet/tararice/blob/master/programs/ejabberd.nix>
# - enables STUN and TURN
# - only over UDP 3478, not firewall-forwarding any TURN port range
# - uses stun_disco module (but with no options)
# - <https://github.com/leo60228/dotfiles/blob/39b3abba3009bdc31413d4757ca2f882a33eec8b/files/ejabberd.yml>
# - <https://github.com/Mic92/dotfiles/blob/ddf0f4821f554f7667fc803344657367c55fb9e6/nixos/eve/modules/ejabberd.nix>
# - <nixpkgs:nixos/tests/xmpp/ejabberd.nix>
# - 2013: <https://github.com/processone/ejabberd/blob/master/ejabberd.yml.example>
{ lib, ... }:
#
# compliance tests:
# - <https://compliance.conversations.im/server/uninsane.org/#xep0352>
{ config, lib, pkgs, ... }:
# XXX disabled: fails to start because of `mnesia_tm` dependency
# XXX: avatar support works in MUCs but not DMs
# lib.mkIf false
{
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
{ user = "ejabberd"; group = "ejabberd"; directory = "/var/lib/ejabberd"; }
];
networking.firewall.allowedTCPPorts = [
5222 # XMPP client -> server
5269 # XMPP server -> server
3478 # STUN/TURN
5222 # XMPP client -> server
5223 # XMPPS client -> server (XMPP over TLS)
5269 # XMPP server -> server
5270 # XMPPS server -> server (XMPP over TLS)
5280 # bosh
5281 # bosh (https) ??
5349 # STUN/TURN (TLS)
5443 # web services (file uploads, websockets, admin)
];
networking.firewall.allowedUDPPorts = [
3478 # STUN/TURN
];
networking.firewall.allowedTCPPortRanges = [{
from = 49152; # TURN
to = 65535;
}];
networking.firewall.allowedUDPPortRanges = [{
from = 49152; # TURN
to = 65535;
}];
# provide access to certs
users.users.ejabberd.extraGroups = [ "nginx" ];
security.acme.certs."uninsane.org".extraDomainNames = [
"xmpp.uninsane.org"
"muc.xmpp.uninsane.org"
"pubsub.xmpp.uninsane.org"
"upload.xmpp.uninsane.org"
"vjid.xmpp.uninsane.org"
];
# exists so the XMPP server's cert can obtain altNames for all its resources
services.nginx.virtualHosts."xmpp.uninsane.org" = {
useACMEHost = "uninsane.org";
};
services.nginx.virtualHosts."muc.xmpp.uninsane.org" = {
useACMEHost = "uninsane.org";
};
services.nginx.virtualHosts."pubsub.xmpp.uninsane.org" = {
useACMEHost = "uninsane.org";
};
services.nginx.virtualHosts."upload.xmpp.uninsane.org" = {
useACMEHost = "uninsane.org";
};
services.nginx.virtualHosts."vjid.xmpp.uninsane.org" = {
useACMEHost = "uninsane.org";
};
sane.services.trust-dns.zones."uninsane.org".inet = {
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
A."xmpp" = "%NATIVE%";
CNAME."muc.xmpp" = "xmpp";
CNAME."pubsub.xmpp" = "xmpp";
CNAME."upload.xmpp" = "xmpp";
CNAME."vjid.xmpp" = "xmpp";
# _Service._Proto.Name TTL Class SRV Priority Weight Port Target
# - <https://xmpp.org/extensions/xep-0368.html>
# something's requesting the SRV records for muc.xmpp, so let's include it
# nothing seems to request XMPP SRVs for the other records (except @)
# lower numerical priority field tells clients to prefer this method
SRV."_xmpps-client._tcp.muc.xmpp" = "3 50 5223 xmpp";
SRV."_xmpps-server._tcp.muc.xmpp" = "3 50 5270 xmpp";
SRV."_xmpp-client._tcp.muc.xmpp" = "5 50 5222 xmpp";
SRV."_xmpp-server._tcp.muc.xmpp" = "5 50 5269 xmpp";
SRV."_xmpps-client._tcp" = "3 50 5223 xmpp";
SRV."_xmpps-server._tcp" = "3 50 5270 xmpp";
SRV."_xmpp-client._tcp" = "5 50 5222 xmpp";
SRV."_xmpp-server._tcp" = "5 50 5269 xmpp";
SRV."_stun._udp" = "5 50 3478 xmpp";
SRV."_stun._tcp" = "5 50 3478 xmpp";
SRV."_stuns._tcp" = "5 50 5349 xmpp";
SRV."_turn._udp" = "5 50 3478 xmpp";
SRV."_turn._tcp" = "5 50 3478 xmpp";
SRV."_turns._tcp" = "5 50 5349 xmpp";
};
# TODO: allocate UIDs/GIDs ?
services.ejabberd.enable = true;
services.ejabberd.configFile = builtins.toFile "ejabberd.yaml" ''
hosts:
- uninsane.org
services.ejabberd.configFile = "/var/lib/ejabberd/ejabberd.yaml";
systemd.services.ejabberd.preStart = let
config-in = pkgs.writeTextFile {
name = "ejabberd.yaml.in";
text = ''
hosts:
- uninsane.org
# none | emergency | alert | critical | error | warning | notice | info | debug
loglevel: debug
# none | emergency | alert | critical | error | warning | notice | info | debug
loglevel: debug
# loglevel: info
# loglevel: notice
acme:
auto: false
certfiles:
- /var/lib/acme/uninsane.org/fullchain.pem
- /var/lib/acme/uninsane.org/key.pem
acme:
auto: false
certfiles:
- /var/lib/acme/uninsane.org/full.pem
# ca_file: ${pkgs.cacert.unbundled}/etc/ssl/certs/
# ca_file: ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
pam_userinfotype: jid
pam_userinfotype: jid
# docs: <https://docs.ejabberd.im/admin/configuration/basic/#shaper-rules>
shaper_rules:
max_s2s_connections: 3
max_user_offline_messages: 5000
c2s_shaper:
fast: all
s2s_shaper:
med: all
acl:
admin:
user:
- "colin@uninsane.org"
local:
user_regexp: ""
loopback:
ip:
- 127.0.0.0/8
- ::1/128
# docs: <https://docs.ejabberd.im/admin/configuration/basic/#shapers>
# this limits the bytes/sec.
# for example, burst: 3_000_000 and rate: 100_000 means:
# - each client has a BW budget that accumulates 100kB/sec and is capped at 3 MB
shaper:
fast: 1000000
med: 500000
# fast:
# - rate: 1000000
# - burst_size: 10000000
# med:
# - rate: 500000
# - burst_size: 5000000
access_rules:
local:
allow: local
c2s_access:
allow: all
announce:
allow: admin
configure:
allow: admin
muc_create:
allow: local
pubsub_createnode_access:
allow: all
trusted_network:
allow: loopback
# see: <https://docs.ejabberd.im/admin/configuration/listen/>
# TODO: host web admin panel
s2s_use_starttls: true
listen:
-
port: 5222
module: ejabberd_c2s
shaper: c2s_shaper
starttls: true
-
port: 5269
module: ejabberd_s2s_in
shaper: s2s_shaper
-
port: 5280
module: ejabberd_http
request_handlers:
/admin: ejabberd_web_admin
/api: mod_http_api
/bosh: mod_bosh
/upload: mod_http_upload
/ws: ejabberd_http_ws
# docs: <https://docs.ejabberd.im/admin/configuration/basic/#shaper-rules>
shaper_rules:
# setting this to above 1 may break outgoing messages
# - maybe some servers rate limit? or just don't understand simultaneous connections?
max_s2s_connections: 1
max_user_sessions: 10
max_user_offline_messages: 5000
c2s_shaper:
fast: all
s2s_shaper:
med: all
# docs: <https://docs.ejabberd.im/admin/configuration/basic/#shapers>
# this limits the bytes/sec.
# for example, burst: 3_000_000 and rate: 100_000 means:
# - each client has a BW budget that accumulates 100kB/sec and is capped at 3 MB
shaper:
fast: 1000000
med: 500000
# fast:
# - rate: 1000000
# - burst_size: 10000000
# med:
# - rate: 500000
# - burst_size: 5000000
# see: <https://docs.ejabberd.im/admin/configuration/listen/>
# s2s_use_starttls: true
s2s_use_starttls: optional
# lessens 504: remote-server-timeout errors
# see: <https://github.com/processone/ejabberd/issues/3105#issuecomment-562182967>
negotiation_timeout: 60
listen:
-
port: 5222
module: ejabberd_c2s
shaper: c2s_shaper
starttls: true
access: c2s_access
-
port: 5223
module: ejabberd_c2s
shaper: c2s_shaper
tls: true
access: c2s_access
-
port: 5269
module: ejabberd_s2s_in
shaper: s2s_shaper
-
port: 5270
module: ejabberd_s2s_in
shaper: s2s_shaper
tls: true
-
port: 5443
module: ejabberd_http
tls: true
request_handlers:
/admin: ejabberd_web_admin # TODO: ensure this actually works
/api: mod_http_api # ejabberd API endpoint (to control server)
/bosh: mod_bosh
/upload: mod_http_upload
/ws: ejabberd_http_ws
# /.well-known/host-meta: mod_host_meta
# /.well-known/host-meta.json: mod_host_meta
-
# STUN+TURN TCP
# note that the full port range should be forwarded ("not NAT'd")
# `use_turn=true` enables both TURN *and* STUN
port: 3478
module: ejabberd_stun
transport: tcp
use_turn: true
turn_min_port: 49152
turn_max_port: 65535
turn_ipv4_address: %NATIVE%
-
# STUN+TURN UDP
port: 3478
module: ejabberd_stun
transport: udp
use_turn: true
turn_min_port: 49152
turn_max_port: 65535
turn_ipv4_address: %NATIVE%
-
# STUN+TURN TLS over TCP
port: 5349
module: ejabberd_stun
transport: tcp
tls: true
certfile: /var/lib/acme/uninsane.org/full.pem
use_turn: true
turn_min_port: 49152
turn_max_port: 65535
turn_ipv4_address: %NATIVE%
# TODO: enable mod_fail2ban
# TODO(low): look into mod_http_fileserver for serving macros?
modules:
# mod_adhoc: {}
# mod_announce:
# access: admin
# allows users to set avatars in vCard
# - <https://docs.ejabberd.im/admin/configuration/modules/#mod-avatar>
mod_avatar: {}
mod_caps: {} # for mod_pubsub
mod_carboncopy: {} # allows multiple clients to receive a user's message
# queues messages when recipient is offline, including PEP and presence messages.
# compliance test suggests this be enabled
mod_client_state: {}
# mod_conversejs: TODO: enable once on 21.12
# allows clients like Dino to discover where to upload files
mod_disco:
server_info:
-
modules: all
name: abuse-addresses
urls:
- "mailto:admin.xmpp@uninsane.org"
- "xmpp:colin@uninsane.org"
-
modules: all
name: admin-addresses
urls:
- "mailto:admin.xmpp@uninsane.org"
- "xmpp:colin@uninsane.org"
mod_http_upload:
host: upload.xmpp.uninsane.org
hosts:
- upload.xmpp.uninsane.org
put_url: "https://@HOST@:5443/upload"
dir_mode: "0750"
file_mode: "0750"
rm_on_unregister: false
# allow discoverability of BOSH and websocket endpoints
# TODO: enable once on ejabberd 22.05 (presently 21.04)
# mod_host_meta: {}
mod_jidprep: {} # probably not needed: lets clients normalize jids
mod_last: {} # allow other users to know when i was last online
mod_mam:
# Mnesia is limited to 2GB, better to use an SQL backend
# For small servers SQLite is a good fit and is very easy
# to configure. Uncomment this when you have SQL configured:
# db_type: sql
assume_mam_usage: true
default: always
mod_muc:
access:
- allow
access_admin:
- allow: admin
access_create: muc_create
access_persistent: muc_create
access_mam:
- allow
history_size: 100 # messages to show new participants
host: muc.xmpp.uninsane.org
hosts:
- muc.xmpp.uninsane.org
default_room_options:
anonymous: false
lang: en
persistent: true
mam: true
mod_muc_admin: {}
mod_offline: # store messages for a user when they're offline (TODO: understand multi-client workflow?)
access_max_user_messages: max_user_offline_messages
store_groupchat: true
mod_ping: {}
mod_privacy: {} # deprecated, but required for `ejabberctl export_piefxis`
mod_private: {} # allow local clients to persist arbitrary data on my server
# push notifications to services integrated with e.g. Apple/Android.
# default is for a maximum amount of PII to be withheld, since these push notifs
# generally traverse 3rd party services. can opt to include message body, etc, though.
mod_push: {}
# i don't fully understand what this does, but it seems aimed at making push notifs more reliable.
mod_push_keepalive: {}
mod_roster:
versioning: true
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-s2s-dialback>
# s2s dialback to verify inbound messages
# unclear to what degree the XMPP network requires this
mod_s2s_dialback: {}
mod_shared_roster: {} # creates groups for @all, @online, and anything manually administered?
mod_stream_mgmt:
resend_on_timeout: if_offline # resend undelivered messages if the origin client is offline
# fallback for when DNS-based STUN discovery is unsupported.
# - see: <https://xmpp.org/extensions/xep-0215.html>
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-stun-disco>
# people say to just keep this defaulted (i guess ejabberd knows to return its `host` option of uninsane.org?)
mod_stun_disco: {}
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-vcard>
mod_vcard:
allow_return_all: true # all users are discoverable (?)
host: vjid.xmpp.uninsane.org
hosts:
- vjid.xmpp.uninsane.org
search: true
mod_vcard_xupdate: {} # needed for avatars
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-pubsub>
mod_pubsub: # needed for avatars
access_createnode: pubsub_createnode_access
host: pubsub.xmpp.uninsane.org
hosts:
- pubsub.xmpp.uninsane.org
ignore_pep_from_offline: false
last_item_cache: true
plugins:
- pep
- flat
force_node_config:
# ensure client bookmarks are private
storage:bookmarks:
access_model: whitelist
urn:xmpp:avatar:data:
access_model: open
urn:xmpp:avatar:metadata:
access_model: open
mod_version: {}
'';
};
sed = "${pkgs.gnused}/bin/sed";
in ''
ip=$(cat '${config.sane.services.dyn-dns.ipPath}')
# config is 444 (not 644), so we want to write out-of-place and then atomically move
# TODO: factor this out into `sane-woop` helper?
rm -f /var/lib/ejabberd/ejabberd.yaml.new
${sed} "s/%NATIVE%/$ip/" ${config-in} > /var/lib/ejabberd/ejabberd.yaml.new
mv /var/lib/ejabberd/ejabberd.yaml{.new,}
'';
sane.services.dyn-dns.restartOnChange = [ "ejabberd.service" ];
}

View File

@@ -14,9 +14,9 @@
sops.secrets.freshrss_passwd = {
sopsFile = ../../../secrets/servo.yaml;
owner = config.users.users.freshrss.name;
mode = "400";
mode = "0400";
};
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
{ user = "freshrss"; group = "freshrss"; directory = "/var/lib/freshrss"; }
];
@@ -49,4 +49,13 @@
# the default ("*:0/5") is to run every 5 minutes.
# `systemctl list-timers` to show
systemd.services.freshrss-updater.startAt = lib.mkForce "*:3/30";
services.nginx.virtualHosts."rss.uninsane.org" = {
addSSL = true;
enableACME = true;
# inherit kTLS;
# the routing is handled by services.freshrss.virtualHost
};
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."rss" = "native";
}

View File

@@ -1,7 +1,7 @@
{ config, pkgs, lib, ... }:
{
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
# TODO: mode? could be more granular
{ user = "git"; group = "gitea"; directory = "/var/lib/gitea"; }
];
@@ -72,4 +72,18 @@
"/var/lib/gitea"
];
};
# hosted git (web view and for `git <cmd>` use
# TODO: enable publog?
services.nginx.virtualHosts."git.uninsane.org" = {
forceSSL = true; # gitea complains if served over a different protocol than its config file says
enableACME = true;
# inherit kTLS;
locations."/" = {
proxyPass = "http://127.0.0.1:3000";
};
};
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."git" = "native";
}

View File

@@ -25,6 +25,7 @@
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
Type = "simple";
Restart = "on-failure";
RestartSec = "10s";
# hardening
WorkingDirectory = "/tmp";
@@ -42,4 +43,26 @@
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
};
# server statistics
services.nginx.virtualHosts."sink.uninsane.org" = {
addSSL = true;
enableACME = true;
# inherit kTLS;
root = "/var/lib/uninsane/sink";
locations."/ws" = {
proxyPass = "http://127.0.0.1:7890";
# XXX not sure how much of this is necessary
extraConfig = ''
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_buffering off;
proxy_read_timeout 7d;
'';
};
};
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."sink" = "native";
}

View File

@@ -10,10 +10,32 @@
lib.mkIf false # i don't actively use ipfs anymore
{
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
# TODO: mode? could be more granular
{ user = "261"; group = "261"; directory = "/var/lib/ipfs"; }
];
networking.firewall.allowedTCPPorts = [ 4001 ];
networking.firewall.allowedUDPPorts = [ 4001 ];
services.nginx.virtualHosts."ipfs.uninsane.org" = {
# don't default to ssl upgrades, since this may be dnslink'd from a different domain.
# ideally we'd disable ssl entirely, but some places assume it?
addSSL = true;
enableACME = true;
# inherit kTLS;
locations."/" = {
proxyPass = "http://127.0.0.1:8080";
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Ipfs-Gateway-Prefix "";
'';
};
};
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."ipfs" = "native";
# services.ipfs.enable = true;
services.kubo.localDiscovery = true;
services.kubo.settings = {

View File

@@ -1,18 +1,32 @@
{ ... }:
{
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
# TODO: mode? we only need this to save Indexer creds ==> migrate to config?
{ user = "root"; group = "root"; directory = "/var/lib/jackett"; }
];
services.jackett.enable = true;
systemd.services.jackett.after = ["wg0veth.service"];
systemd.services.jackett.after = [ "wireguard-wg0.service" ];
systemd.services.jackett.partOf = [ "wireguard-wg0.service" ];
systemd.services.jackett.serviceConfig = {
# run this behind the OVPN static VPN
NetworkNamespacePath = "/run/netns/ovpns";
# patch jackett to listen on the public interfaces
# ExecStart = lib.mkForce "${pkgs.jackett}/bin/Jackett --NoUpdates --DataFolder /var/lib/jackett/.config/Jackett --ListenPublic";
};
# jackett torrent search
services.nginx.virtualHosts."jackett.uninsane.org" = {
forceSSL = true;
enableACME = true;
# inherit kTLS;
locations."/" = {
# proxyPass = "http://ovpns.uninsane.org:9117";
proxyPass = "http://10.0.1.6:9117";
};
};
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."jackett" = "native";
}

View File

@@ -1,14 +1,69 @@
{ config, ... }:
{ config, lib, ... }:
# TODO: re-enable after migrating media dir to /var/lib/uninsane/media
# else it's too spammy
lib.mkIf false
{
sane.impermanence.service-dirs = [
networking.firewall.allowedUDPPorts = [
1900 7359 # DLNA: https://jellyfin.org/docs/general/networking/index.html
];
sane.impermanence.dirs.sys.plaintext = [
# TODO: mode? could be more granular
{ user = "jellyfin"; group = "jellyfin"; directory = "/var/lib/jellyfin"; }
];
# Jellyfin multimedia server
# this is mostly taken from the official jellfin.org docs
services.nginx.virtualHosts."jelly.uninsane.org" = {
addSSL = true;
enableACME = true;
# inherit kTLS;
locations."/" = {
proxyPass = "http://127.0.0.1:8096";
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
# Disable buffering when the nginx proxy gets very resource heavy upon streaming
proxy_buffering off;
'';
};
# locations."/web/" = {
# proxyPass = "http://127.0.0.1:8096/web/index.html";
# extraConfig = ''
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_set_header X-Forwarded-Protocol $scheme;
# proxy_set_header X-Forwarded-Host $http_host;
# '';
# };
locations."/socket" = {
proxyPass = "http://127.0.0.1:8096";
extraConfig = ''
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
'';
};
};
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;
# TODO: re-enable after migrating media dir to /var/lib/uninsane/media
# else it's too spammy
# services.jellyfin.enable = true;
services.jellyfin.enable = true;
}

View File

@@ -0,0 +1,17 @@
{ ... }:
{
sane.services.kiwix-serve = {
enable = true;
port = 8013;
zimPaths = [ "/var/lib/uninsane/www-archive/wikipedia_en_all_maxi_2022-05.zim" ];
};
services.nginx.virtualHosts."w.uninsane.org" = {
forceSSL = true;
enableACME = true;
# inherit kTLS;
locations."/".proxyPass = "http://127.0.0.1:8013";
};
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."w" = "native";
}

View File

@@ -1,6 +1,6 @@
# docs: https://nixos.wiki/wiki/Matrix
# docs: https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-synapse
{ config, lib, ... }:
{ config, lib, pkgs, ... }:
{
imports = [
@@ -8,7 +8,7 @@
# ./irc.nix
];
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/matrix-synapse"; }
];
services.matrix-synapse.enable = true;
@@ -77,6 +77,55 @@
# create a token with limited uses:
# curl -d '{ "uses_allowed": 1 }' --header "Authorization: Bearer <my_token>" localhost:8008/_synapse/admin/v1/registration_tokens/new
# matrix chat server
# TODO: was `publog`
services.nginx.virtualHosts."matrix.uninsane.org" = {
addSSL = true;
enableACME = true;
# inherit kTLS;
# TODO colin: replace this with something helpful to the viewer
# locations."/".extraConfig = ''
# return 404;
# '';
locations."/" = {
proxyPass = "http://127.0.0.1:8008";
};
# redirect browsers to the web client.
# i don't think native matrix clients ever fetch the root.
# ideally this would be put behind some user-agent test though.
locations."= /" = {
return = "301 https://web.matrix.uninsane.org";
};
# locations."/_matrix" = {
# proxyPass = "http://127.0.0.1:8008";
# };
};
# matrix web client
# docs: https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-element-web
services.nginx.virtualHosts."web.matrix.uninsane.org" = {
forceSSL = true;
enableACME = true;
# inherit kTLS;
root = pkgs.element-web.override {
conf = {
default_server_config."m.homeserver" = {
"base_url" = "https://matrix.uninsane.org";
"server_name" = "uninsane.org";
};
};
};
};
sane.services.trust-dns.zones."uninsane.org".inet = {
CNAME."matrix" = "native";
CNAME."web.matrix" = "native";
};
sops.secrets.matrix_synapse_secrets = {
sopsFile = ../../../../secrets/servo.yaml;

View File

@@ -1,6 +1,6 @@
{ lib, ... }:
{
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/mx-puppet-discord"; }
];

View File

@@ -1,7 +1,7 @@
{ config, lib, ... }:
{
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
# TODO: mode?
# user and group are both "matrix-appservice-irc"
{ user = "993"; group = "992"; directory = "/var/lib/matrix-appservice-irc"; }

View File

@@ -1,8 +1,11 @@
{ ... }:
{
sane.impermanence.service-dirs = [
{ user = "navidrome"; group = "navidrome"; directory = "/var/lib/private/navidrome"; }
sane.impermanence.dirs.sys.plaintext = [
# TODO: we don't have a static user allocated for navidrome!
# the chown would happen too early for us to set static perms
"/var/lib/private/navidrome"
# { user = "navidrome"; group = "navidrome"; directory = "/var/lib/private/navidrome"; }
];
services.navidrome.enable = true;
services.navidrome.settings = {
@@ -14,4 +17,13 @@
AutoImportPlaylists = false;
ScanSchedule = "@every 1h";
};
services.nginx.virtualHosts."music.uninsane.org" = {
forceSSL = true;
enableACME = true;
# inherit kTLS;
locations."/".proxyPass = "http://127.0.0.1:4533";
};
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."music" = "native";
}

View File

@@ -9,9 +9,12 @@ let
'';
};
kTLS = true; # in-kernel TLS for better perf
# kTLS = true; # in-kernel TLS for better perf
in
{
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx.enable = true;
services.nginx.appendConfig = ''
# use 1 process per core.
@@ -45,7 +48,7 @@ in
# and things don't look right. so force SSL.
forceSSL = true;
enableACME = true;
inherit kTLS;
# inherit kTLS;
# for OCSP stapling
sslTrustedCertificate = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
@@ -93,246 +96,6 @@ in
# };
};
# server statistics
services.nginx.virtualHosts."sink.uninsane.org" = {
addSSL = true;
enableACME = true;
inherit kTLS;
root = "/var/lib/uninsane/sink";
locations."/ws" = {
proxyPass = "http://127.0.0.1:7890";
# XXX not sure how much of this is necessary
extraConfig = ''
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_buffering off;
proxy_read_timeout 7d;
'';
};
};
# Pleroma server and web interface
services.nginx.virtualHosts."fed.uninsane.org" = publog {
forceSSL = true; # pleroma redirects to https anyway
enableACME = true;
inherit kTLS;
locations."/" = {
proxyPass = "http://127.0.0.1:4000";
# documented: https://git.pleroma.social/pleroma/pleroma/-/blob/develop/installation/pleroma.nginx
extraConfig = ''
# XXX colin: this block is in the nixos examples: i don't understand all of it
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
if ($request_method = OPTIONS) {
return 204;
}
add_header X-XSS-Protection "1; mode=block";
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy same-origin;
add_header X-Download-Options noopen;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# proxy_set_header Host $http_host;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# colin: added this due to Pleroma complaining in its logs
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 16m;
'';
};
};
# transmission web client
services.nginx.virtualHosts."bt.uninsane.org" = {
# basicAuth is literally cleartext user/pw, so FORCE this to happen over SSL
forceSSL = true;
enableACME = true;
inherit kTLS;
locations."/" = {
# proxyPass = "http://ovpns.uninsane.org:9091";
proxyPass = "http://10.0.1.6:9091";
};
};
# jackett torrent search
services.nginx.virtualHosts."jackett.uninsane.org" = {
forceSSL = true;
enableACME = true;
inherit kTLS;
locations."/" = {
# proxyPass = "http://ovpns.uninsane.org:9117";
proxyPass = "http://10.0.1.6:9117";
};
};
# matrix chat server
services.nginx.virtualHosts."matrix.uninsane.org" = publog {
addSSL = true;
enableACME = true;
inherit kTLS;
# TODO colin: replace this with something helpful to the viewer
# locations."/".extraConfig = ''
# return 404;
# '';
locations."/" = {
proxyPass = "http://127.0.0.1:8008";
};
# redirect browsers to the web client.
# i don't think native matrix clients ever fetch the root.
# ideally this would be put behind some user-agent test though.
locations."= /" = {
return = "301 https://web.matrix.uninsane.org";
};
# locations."/_matrix" = {
# proxyPass = "http://127.0.0.1:8008";
# };
};
# matrix web client
# docs: https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-element-web
services.nginx.virtualHosts."web.matrix.uninsane.org" = {
forceSSL = true;
enableACME = true;
inherit kTLS;
root = pkgs.element-web.override {
conf = {
default_server_config."m.homeserver" = {
"base_url" = "https://matrix.uninsane.org";
"server_name" = "uninsane.org";
};
};
};
};
# hosted git (web view and for `git <cmd>` use
services.nginx.virtualHosts."git.uninsane.org" = publog {
forceSSL = true; # gitea complains if served over a different protocol than its config file says
enableACME = true;
inherit kTLS;
locations."/" = {
proxyPass = "http://127.0.0.1:3000";
};
};
# Jellyfin multimedia server
# this is mostly taken from the official jellfin.org docs
services.nginx.virtualHosts."jelly.uninsane.org" = {
addSSL = true;
enableACME = true;
inherit kTLS;
locations."/" = {
proxyPass = "http://127.0.0.1:8096";
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
# Disable buffering when the nginx proxy gets very resource heavy upon streaming
proxy_buffering off;
'';
};
# locations."/web/" = {
# proxyPass = "http://127.0.0.1:8096/web/index.html";
# extraConfig = ''
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_set_header X-Forwarded-Protocol $scheme;
# proxy_set_header X-Forwarded-Host $http_host;
# '';
# };
locations."/socket" = {
proxyPass = "http://127.0.0.1:8096";
extraConfig = ''
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
'';
};
};
services.nginx.virtualHosts."music.uninsane.org" = {
forceSSL = true;
enableACME = true;
inherit kTLS;
locations."/".proxyPass = "http://127.0.0.1:4533";
};
services.nginx.virtualHosts."rss.uninsane.org" = {
addSSL = true;
enableACME = true;
inherit kTLS;
# the routing is handled by freshrss.nix
};
services.nginx.virtualHosts."ipfs.uninsane.org" = {
# don't default to ssl upgrades, since this may be dnslink'd from a different domain.
# ideally we'd disable ssl entirely, but some places assume it?
addSSL = true;
enableACME = true;
inherit kTLS;
locations."/" = {
proxyPass = "http://127.0.0.1:8080";
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Ipfs-Gateway-Prefix "";
'';
};
};
# exists only to manage certs for dovecot
services.nginx.virtualHosts."imap.uninsane.org" = {
forceSSL = true;
enableACME = true;
};
# exists only to manage certs for Postfix
services.nginx.virtualHosts."mx.uninsane.org" = {
forceSSL = true;
enableACME = true;
};
services.nginx.virtualHosts."nixcache.uninsane.org" = {
addSSL = true;
enableACME = true;
inherit kTLS;
# serverAliases = [ "nixcache" ];
locations."/".extraConfig = ''
proxy_pass http://localhost:${toString config.services.nix-serve.port};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
'';
};
# serve any site not listed above, if it's static.
# because we define it dynamically, SSL isn't trivial. support only http
@@ -359,12 +122,34 @@ in
users.users.acme.uid = config.sane.allocations.acme-uid;
users.groups.acme.gid = config.sane.allocations.acme-gid;
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
# TODO: mode?
{ user = "acme"; group = "acme"; directory = "/var/lib/acme"; }
{ user = "colin"; group = "users"; directory = "/var/www/sites"; }
];
# let's encrypt default chain looks like:
# - End-entity certificate ← R3 ← ISRG Root X1 ← DST Root CA X3
# - <https://community.letsencrypt.org/t/production-chain-changes/150739>
# DST Root CA X3 expired in 2021 (?)
# the alternative chain is:
# - End-entity certificate ← R3 ← ISRG Root X1 (self-signed)
# using this alternative chain grants more compatibility for services like ejabberd
# but might decrease compatibility with very old clients that don't get updates (e.g. old android, iphone <= 4).
# security.acme.defaults.extraLegoFlags = [
security.acme.certs."uninsane.org" = rec {
# ISRG Root X1 results in lets encrypt sending the same chain as default,
# just without the final ISRG Root X1 ← DST Root CA X3 link.
# i.e. we could alternative clip the last item and achieve the exact same thing.
extraLegoRunFlags = [
"--preferred-chain" "ISRG Root X1"
];
extraLegoRenewFlags = extraLegoRunFlags;
};
# TODO: alternatively, we could clip the last cert IF it's expired,
# optionally outputting that to a new cert file.
# security.acme.defaults.postRun = "";
# create a self-signed SSL certificate for use with literally any domain.
# browsers will reject this, but proxies and local testing tools can be configured
# to accept it.

View File

@@ -0,0 +1,21 @@
{ config, ... }:
{
services.nginx.virtualHosts."nixcache.uninsane.org" = {
addSSL = true;
enableACME = true;
# inherit kTLS;
# serverAliases = [ "nixcache" ];
locations."/".extraConfig = ''
proxy_pass http://localhost:${toString config.services.nix-serve.port};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
'';
};
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."nixcache" = "native";
sane.services.nixserve.enable = true;
sane.services.nixserve.sopsFile = ../../../secrets/servo.yaml;
}

View File

@@ -6,7 +6,7 @@
{ config, pkgs, ... }:
{
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
# TODO: mode? could be more granular
{ user = "pleroma"; group = "pleroma"; directory = "/var/lib/pleroma"; }
];
@@ -127,6 +127,7 @@
systemd.services.pleroma.serviceConfig = {
# postgres can be slow to service early requests, preventing pleroma from starting on the first try
Restart = "on-failure";
RestartSec = "10s";
};
# systemd.services.pleroma.serviceConfig = {
@@ -136,6 +137,50 @@
# CapabilityBoundingSet = lib.mkForce "~";
# };
# Pleroma server and web interface
# TODO: enable publog?
services.nginx.virtualHosts."fed.uninsane.org" = {
forceSSL = true; # pleroma redirects to https anyway
enableACME = true;
# inherit kTLS;
locations."/" = {
proxyPass = "http://127.0.0.1:4000";
# documented: https://git.pleroma.social/pleroma/pleroma/-/blob/develop/installation/pleroma.nginx
extraConfig = ''
# XXX colin: this block is in the nixos examples: i don't understand all of it
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
if ($request_method = OPTIONS) {
return 204;
}
add_header X-XSS-Protection "1; mode=block";
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy same-origin;
add_header X-Download-Options noopen;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# proxy_set_header Host $http_host;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# colin: added this due to Pleroma complaining in its logs
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 16m;
'';
};
};
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."fed" = "native";
sops.secrets.pleroma_secrets = {
sopsFile = ../../../secrets/servo.yaml;
owner = config.users.users.pleroma.name;

View File

@@ -16,7 +16,7 @@ let
};
in
{
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
# TODO: mode? could be more granular
{ user = "opendkim"; group = "opendkim"; directory = "/var/lib/opendkim"; }
{ user = "root"; group = "root"; directory = "/var/lib/postfix"; }
@@ -25,6 +25,61 @@ in
# "/var/lib/dhparams" # https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/dhparams.nix
# "/var/lib/dovecot"
];
networking.firewall.allowedTCPPorts = [
25 # SMTP
143 # IMAP
465 # SMTPS
587 # SMTPS/submission
993 # IMAPS
];
# exists only to manage certs for dovecot
services.nginx.virtualHosts."imap.uninsane.org" = {
enableACME = true;
};
# exists only to manage certs for Postfix
services.nginx.virtualHosts."mx.uninsane.org" = {
enableACME = true;
};
sane.services.trust-dns.zones."uninsane.org".inet = {
MX."@" = "10 mx.uninsane.org.";
# XXX: RFC's specify that the MX record CANNOT BE A CNAME
A."mx" = "185.157.162.178";
CNAME."imap" = "native";
# Sender Policy Framework:
# +mx => mail passes if it originated from the MX
# +a => mail passes if it originated from the A address of this domain
# +ip4:.. => mail passes if it originated from this IP
# -all => mail fails if none of these conditions were met
TXT."@" = "v=spf1 a mx -all";
# DKIM public key:
TXT."mx._domainkey" =
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkSyMufc2KrRx3j17e/LyB+3eYSBRuEFT8PUka8EDX04QzCwDPdkwgnj3GNDvnB5Ktb05Cf2SJ/S1OLqNsINxJRWtkVfZd/C339KNh9wrukMKRKNELL9HLUw0bczOI4gKKFqyrRE9qm+4csCMAR79Te9FCjGV/jVnrkLdPT0GtFwIDAQAB"
;
# DMARC fields <https://datatracker.ietf.org/doc/html/rfc7489>:
# p=none|quarantine|reject: what to do with failures
# sp = p but for subdomains
# rua = where to send aggregrate reports
# ruf = where to send individual failure reports
# fo=0|1|d|s controls WHEN to send failure reports
# (1=on bad alignment; d=on DKIM failure; s=on SPF failure);
# Additionally:
# adkim=r|s (is DKIM relaxed [default] or strict)
# aspf=r|s (is SPF relaxed [default] or strict)
# pct = sampling ratio for punishing failures (default 100 for 100%)
# rf = report format
# ri = report interval
TXT."_dmarc" =
"v=DMARC1;p=quarantine;sp=reject;rua=mailto:admin+mail@uninsane.org;ruf=mailto:admin+mail@uninsane.org;fo=1:d:s"
;
};
services.postfix.enable = true;
services.postfix.hostname = "mx.uninsane.org";
services.postfix.origin = "uninsane.org";
@@ -55,7 +110,8 @@ in
services.postfix.enableSubmissions = true;
services.postfix.submissionsOptions = submissionOptions;
systemd.services.postfix.after = [ "wg0veth.service" ];
systemd.services.postfix.after = [ "wireguard-wg0.service" ];
systemd.services.postfix.partOf = [ "wireguard-wg0.service" ];
systemd.services.postfix.serviceConfig = {
# run this behind the OVPN static VPN
NetworkNamespacePath = "/run/netns/ovpns";
@@ -76,7 +132,8 @@ in
# keeping this the same as the hostname seems simplest
services.opendkim.selector = "mx";
systemd.services.opendkim.after = [ "wg0veth.service" ];
systemd.services.opendkim.after = [ "wireguard-wg0.service" ];
systemd.services.opendkim.partOf = [ "wireguard-wg0.service" ];
systemd.services.opendkim.serviceConfig = {
# run this behind the OVPN static VPN
NetworkNamespacePath = "/run/netns/ovpns";

View File

@@ -1,7 +1,7 @@
{ ... }:
{
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
# TODO: mode?
{ user = "postgres"; group = "postgres"; directory = "/var/lib/postgresql"; }
];

View File

@@ -1,3 +1,5 @@
# example configs:
# - <https://github.com/kittywitch/nixfiles/blob/main/services/prosody.nix>
# create users with:
# - `sudo -u prosody prosodyctl adduser colin@uninsane.org`
@@ -7,13 +9,13 @@
# nixnet runs ejabberd, so revisiting that.
lib.mkIf false
{
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
{ user = "prosody"; group = "prosody"; directory = "/var/lib/prosody"; }
];
networking.firewall.allowedTCPPorts = [
5222 # XMPP client -> server
5269 # XMPP server -> server
5280 # Prosody HTTP port (necessary?)
5280 # bosh
5281 # Prosody HTTPS port (necessary?)
];
@@ -34,7 +36,7 @@ lib.mkIf false
# c2s_require_encryption = true
# '';
# extraModules = [ "private" "vcard" "privacy" "compression" "component" "muc" "pep" "adhoc" "lastactivity" "admin_adhoc" "blocklist"];
extraModules = [ "private" "vcard" "privacy" "compression" "component" "muc" "pep" "adhoc" "lastactivity" "admin_adhoc" "blocklist"];
ssl.cert = "/var/lib/acme/uninsane.org/fullchain.pem";
ssl.key = "/var/lib/acme/uninsane.org/key.pem";
@@ -51,7 +53,7 @@ lib.mkIf false
domain = "localhost";
enabled = true;
};
"uninsane.org" = {
"xmpp.uninsane.org" = {
domain = "uninsane.org";
enabled = true;
ssl.cert = "/var/lib/acme/uninsane.org/fullchain.pem";

View File

@@ -1,7 +1,7 @@
{ ... }:
{ pkgs, ... }:
{
sane.impermanence.service-dirs = [
sane.impermanence.dirs.sys.plaintext = [
# TODO: mode? we need this specifically for the stats tracking in .config/
{ user = "transmission"; group = "transmission"; directory = "/var/lib/transmission"; }
];
@@ -40,11 +40,41 @@
# transmission will by default not allow the world to read its files.
services.transmission.downloadDirPermissions = "775";
systemd.services.transmission.after = ["wg0veth.service"];
systemd.services.transmission.after = [ "wireguard-wg0.service" ];
systemd.services.transmission.partOf = [ "wireguard-wg0.service" ];
systemd.services.transmission.serviceConfig = {
# run this behind the OVPN static VPN
NetworkNamespacePath = "/run/netns/ovpns";
LogLevelMax = "warning";
};
# service to automatically backup torrents i add to transmission
systemd.services.backup-torrents = {
description = "archive torrents to storage not owned by transmission";
script = ''
${pkgs.rsync}/bin/rsync -arv /var/lib/transmission/.config/transmission-daemon/torrents/ /var/backup/torrents/
'';
};
systemd.timers.backup-torrents = {
wantedBy = [ "multi-user.target" ];
timerConfig = {
OnStartupSec = "11min";
OnUnitActiveSec = "240min";
};
};
# transmission web client
services.nginx.virtualHosts."bt.uninsane.org" = {
# basicAuth is literally cleartext user/pw, so FORCE this to happen over SSL
forceSSL = true;
enableACME = true;
# inherit kTLS;
locations."/" = {
# proxyPass = "http://ovpns.uninsane.org:9091";
proxyPass = "http://10.0.1.6:9091";
};
};
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."bt" = "native";
}

View File

@@ -0,0 +1,66 @@
{ config, pkgs, ... }:
{
sane.services.trust-dns.enable = true;
sane.services.trust-dns.listenAddrsIPv4 = [
# specify each address explicitly, instead of using "*".
# this ensures responses are sent from the address at which the request was received.
"192.168.0.5"
"10.0.1.5"
];
sane.services.trust-dns.zones."uninsane.org".TTL = 900;
# SOA record structure: <https://en.wikipedia.org/wiki/SOA_record#Structure>
# SOA MNAME RNAME (... rest)
# MNAME = Master name server for this zone. this is where update requests should be sent.
# RNAME = admin contact (encoded email address)
# Serial = YYYYMMDDNN, where N is incremented every time this file changes, to trigger secondary NS to re-fetch it.
# Refresh = how frequently secondary NS should query master
# Retry = how long secondary NS should wait until re-querying master after a failure (must be < Refresh)
# Expire = how long secondary NS should continue to reply to queries after master fails (> Refresh + Retry)
sane.services.trust-dns.zones."uninsane.org".inet = {
SOA."@" = ''
ns1.uninsane.org. admin-dns.uninsane.org. (
2022122101 ; Serial
4h ; Refresh
30m ; Retry
7d ; Expire
5m) ; Negative response TTL
'';
TXT."rev" = "2022122101";
# XXX NS records must also not be CNAME
# it's best that we keep this identical, or a superset of, what org. lists as our NS.
# so, org. can specify ns2/ns3 as being to the VPN, with no mention of ns1. we provide ns1 here.
A."ns1" = "%NATIVE%";
A."ns2" = "185.157.162.178";
A."ns3" = "185.157.162.178";
A."ovpns" = "185.157.162.178";
A."native" = "%NATIVE%";
A."@" = "%NATIVE%";
NS."@" = [
"ns1.uninsane.org."
"ns2.uninsane.org."
"ns3.uninsane.org."
];
};
sane.services.trust-dns.zones."uninsane.org".file =
"/var/lib/trust-dns/uninsane.org.zone";
systemd.services.trust-dns.preStart = let
sed = "${pkgs.gnused}/bin/sed";
zone-dir = "/var/lib/trust-dns";
zone-out = "${zone-dir}/uninsane.org.zone";
zone-template = pkgs.writeText "uninsane.org.zone.in" config.sane.services.trust-dns.generatedZones."uninsane.org";
in ''
# make WAN records available to trust-dns
mkdir -p ${zone-dir}
ip=$(cat '${config.sane.services.dyn-dns.ipPath}')
${sed} s/%NATIVE%/$ip/ ${zone-template} > ${zone-out}
'';
sane.services.dyn-dns.restartOnChange = [ "trust-dns.service" ];
}

View File

@@ -0,0 +1,33 @@
# docs: <https://nixos.wiki/wiki/MediaWiki>
{ config, lib, ... }:
# XXX: working to host wikipedia with kiwix instead of mediawiki
# mediawiki does more than i need and isn't obviously superior in any way
# except that the dumps are more frequent/up-to-date.
lib.mkIf false
{
sops.secrets."mediawiki_pw" = {
owner = config.users.users.mediawiki.name;
sopsFile = ../../../secrets/servo.yaml;
};
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;
services.mediawiki.extraConfig = ''
# Disable anonymous editing
$wgGroupPermissions['*']['edit'] = false;
'';
services.mediawiki.virtualHost.listen = [
{
ip = "127.0.0.1";
port = 8013;
ssl = false;
}
];
services.mediawiki.virtualHost.hostName = "w.uninsane.org";
services.mediawiki.virtualHost.adminAddr = "admin+mediawiki@uninsane.org";
# services.mediawiki.extensions = TODO: wikipedia sync extension?
}

View File

@@ -23,8 +23,10 @@ in
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;

View File

@@ -1,14 +1,20 @@
{ ... }:
{ lib, utils, ... }:
{
imports = [
./allocations.nix
./fs
./gui
./home-manager
./packages.nix
./image.nix
./impermanence.nix
./impermanence
./nixcache.nix
./services
./sops.nix
];
_module.args = {
sane-lib = import ./lib { inherit lib utils; };
};
}

353
modules/fs/default.nix Normal file
View File

@@ -0,0 +1,353 @@
{ config, lib, pkgs, utils, sane-lib, ... }:
with lib;
let
path-lib = sane-lib.path;
sane-types = sane-lib.types;
cfg = config.sane.fs;
mountNameFor = path: "${utils.escapeSystemdPath path}.mount";
serviceNameFor = path: "ensure-${utils.escapeSystemdPath path}";
# sane.fs."<path>" top-level options
fsEntry = types.submodule ({ name, config, ...}: let
parent = path-lib.parent name;
has-parent = path-lib.hasParent name;
parent-cfg = if has-parent then cfg."${parent}" else {};
parent-acl = if has-parent then parent-cfg.generated.acl else {};
in {
options = {
dir = mkOption {
type = types.nullOr dirEntry;
default = null;
};
symlink = mkOption {
type = types.nullOr symlinkEntry;
default = null;
};
generated = mkOption {
type = generatedEntry;
default = {};
};
mount = mkOption {
type = types.nullOr (mountEntryFor name);
default = null;
};
wantedBy = mkOption {
type = types.listOf types.str;
default = [];
description = ''
list of units or targets which, when activated, should trigger this fs entry to be created.
'';
};
wantedBeforeBy = mkOption {
type = types.listOf types.str;
default = [];
description = ''
list of units or targets which, when activated, should first start and wait for this fs entry to be created.
if this unit fails, it will not block the targets in this list.
'';
};
unit = mkOption {
type = types.str;
description = "name of the systemd unit which ensures this entry";
};
};
config = let
default-acl = {
user = lib.mkDefault (parent-acl.user or "root");
group = lib.mkDefault (parent-acl.group or "root");
mode = lib.mkDefault (parent-acl.mode or "0755");
};
in {
# we put this here instead of as a `default` to ensure that users who specify additional
# dependencies still get a dep on the parent (unless they assign with `mkForce`).
generated.depends = if has-parent then [ parent-cfg.unit ] else [];
# populate generated items from `dir` or `symlink` shorthands
generated.acl = lib.mkMerge [
default-acl
(lib.mkIf (config.dir != null)
(sane-lib.filterNonNull config.dir.acl))
(lib.mkIf (config.symlink != null)
(sane-lib.filterNonNull config.symlink.acl))
];
# actually generate the item
generated.script = lib.mkMerge [
(lib.mkIf (config.dir != null) (ensureDirScript name config.dir))
(lib.mkIf (config.symlink != null) (ensureSymlinkScript name config.symlink))
];
# make the unit file which generates the underlying thing available so that `mount` can use it.
generated.unit = (serviceNameFor name) + ".service";
# if defaulted, this module is responsible for finalizing the entry.
# the user could override this if, say, they finalize some aspect of the entry
# with a custom service.
unit = lib.mkDefault (
if config.mount != null then
config.mount.unit
else
config.generated.unit
);
};
});
# options which can be set in dir/symlink generated items,
# with intention that they just propagate down
propagatedGenerateMod = {
options = {
acl = mkOption {
type = sane-types.aclOverride;
default = {};
};
};
};
# sane.fs."<path>".dir sub-options
# takes no special options
dirEntry = types.submodule propagatedGenerateMod;
symlinkEntry = types.submodule {
options = {
inherit (propagatedGenerateMod.options) acl;
target = mkOption {
type = types.str;
description = "fs path to link to";
};
};
};
generatedEntry = types.submodule {
options = {
acl = mkOption {
type = sane-types.acl;
};
depends = mkOption {
type = types.listOf types.str;
description = ''
list of systemd units needed to be run before this item can be generated.
'';
default = [];
};
script.script = mkOption {
type = types.lines;
};
script.scriptArgs = mkOption {
type = types.listOf types.str;
default = [];
};
unit = mkOption {
type = types.str;
description = "name of the systemd unit which ensures this directory";
};
};
};
# sane.fs."<path>".mount sub-options
mountEntryFor = path: types.submodule {
options = {
bind = mkOption {
type = types.nullOr types.str;
description = "fs path to bind-mount from";
default = null;
};
depends = mkOption {
type = types.listOf types.str;
description = ''
list of systemd units needed to be run before this entry can be mounted
'';
default = [];
};
unit = mkOption {
type = types.str;
description = "name of the systemd unit which mounts this path";
default = mountNameFor path;
};
};
};
mkGeneratedConfig = path: opt: let
gen-opt = opt.generated;
wrapper = generateWrapperScript path gen-opt;
in {
systemd.services."${serviceNameFor path}" = {
description = "prepare ${path}";
serviceConfig.Type = "oneshot";
script = wrapper.script;
scriptArgs = builtins.concatStringsSep " " wrapper.scriptArgs;
after = gen-opt.depends;
wants = gen-opt.depends;
# prevent systemd making this unit implicitly dependent on sysinit.target.
# see: <https://www.freedesktop.org/software/systemd/man/systemd.special.html>
unitConfig.DefaultDependencies = "no";
before = opt.wantedBeforeBy;
wantedBy = opt.wantedBy ++ opt.wantedBeforeBy;
};
};
# given a mountEntry definition, evaluate its toplevel `config` output.
mkMountConfig = path: opt: (let
device = config.fileSystems."${path}".device;
underlying = cfg."${device}";
isBind = opt.mount.bind != null;
ifBind = lib.mkIf isBind;
# before mounting:
# - create the target directory
# - prepare the source directory -- assuming it's not an external device
# - satisfy any user-specified prerequisites ("depends")
requires = [ opt.generated.unit ]
++ (if lib.hasPrefix "/dev/disk/" device then [] else [ underlying.unit ])
++ opt.mount.depends;
in {
fileSystems."${path}" = {
device = ifBind opt.mount.bind;
options = (if isBind then ["bind"] else [])
++ [
# disable defaults: don't require this to be mount as part of local-fs.target
# we'll handle that stuff precisely.
"noauto"
"nofail"
# x-systemd options documented here:
# - <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
]
++ (builtins.map (unit: "x-systemd.requires=${unit}") requires)
++ (builtins.map (unit: "x-systemd.before=${unit}") opt.wantedBeforeBy)
++ (builtins.map (unit: "x-systemd.wanted-by=${unit}") (opt.wantedBy ++ opt.wantedBeforeBy));
noCheck = ifBind true;
};
});
mkFsConfig = path: opt: mergeTopLevel [
(mkGeneratedConfig path opt)
(lib.mkIf (opt.mount != null) (mkMountConfig path opt))
];
# act as `config = lib.mkMerge [ a b ]` but in a way which avoids infinite recursion,
# by extracting only specific options which are known to not be options in this module.
mergeTopLevel = items: let
# if one of the items is `lib.mkIf cond attrs`, we won't be able to index it until
# after we "push down" the mkIf to each attr.
indexable = lib.pushDownProperties (lib.mkMerge items);
# transform (listOf attrs) to (attrsOf list) by grouping each toplevel attr across lists.
top = lib.zipAttrsWith (name: lib.mkMerge) indexable;
# extract known-good top-level items in a way which errors if a module tries to define something extra.
extract = { fileSystems ? {}, systemd ? {} }@attrs: attrs;
in {
inherit (extract top) fileSystems systemd;
};
generateWrapperScript = path: gen-opt: {
script = ''
fspath="$1"
acluser="$2"
aclgroup="$3"
aclmode="$4"
shift 4
# ensure any things created by the user script have the desired mode.
# chmod doesn't work on symlinks, so we *have* to use this umask approach.
decmask=$(( 0777 - "$aclmode" ))
octmask=$(printf "%o" "$decmask")
umask "$octmask"
# try to chmod/chown the result even if the user script errors
_status=0
trap "_status=\$?" ERR
${gen-opt.script.script}
# claim ownership of the new thing (DON'T traverse symlinks)
chown --no-dereference "$acluser:$aclgroup" "$fspath"
# AS LONG AS IT'S NOT A SYMLINK, try to fix perms in case the entity existed before this script was called
if ! test -L "$fspath"
then
chmod "$aclmode" "$fspath"
fi
exit "$_status"
'';
scriptArgs = [ path gen-opt.acl.user gen-opt.acl.group gen-opt.acl.mode ] ++ gen-opt.script.scriptArgs;
};
# systemd/shell script used to create and set perms for a specific dir
ensureDirScript = path: dir-cfg: {
script = ''
dirpath="$1"
if ! test -d "$dirpath"
then
# if the directory *doesn't* exist, try creating it
# if we fail to create it, ensure we raced with something else and that it's actually a directory
mkdir "$dirpath" || test -d "$dirpath"
fi
'';
scriptArgs = [ path ];
};
# systemd/shell script used to create a symlink
ensureSymlinkScript = path: link-cfg: {
script = ''
lnfrom="$1"
lnto="$2"
ln -sf --no-dereference "$lnto" "$lnfrom"
'';
scriptArgs = [ path link-cfg.target ];
};
# return all ancestors of this path.
# e.g. ancestorsOf "/foo/bar/baz" => [ "/" "/foo" "/foo/bar" ]
# TODO: move this to path-lib?
ancestorsOf = path: if path-lib.hasParent path then
ancestorsOf (path-lib.parent path) ++ [ (path-lib.parent path) ]
else
[ ]
;
# attrsOf fsEntry type which for every entry ensures that all ancestor entries are created.
# we do this with a custom type to ensure that users can access `config.sane.fs."/parent/path"`
# when inferred.
fsTree = let
baseType = types.attrsOf fsEntry;
# merge is called once, with all collected `sane.fs` definitions passed and we coalesce those
# into a single value `x` as if the user had wrote simply `sane.fs = x` in a single location.
# so option defaulting and such happens *after* `merge` is called.
merge = loc: defs: let
# loc is the location of the option holding this type, e.g. ["sane" "fs"].
# each def is an { value = attrsOf fsEntry instance; file = "..."; }
pathsForDef = def: attrNames def.value;
origPaths = concatLists (builtins.map pathsForDef defs);
extraPaths = concatLists (builtins.map ancestorsOf origPaths);
extraDefs = builtins.map (p: {
file = ./.;
value = {
"${p}".dir = {};
};
}) extraPaths;
in
baseType.merge loc (defs ++ extraDefs);
in
lib.mkOptionType {
inherit merge;
name = "fsTree";
description = "attrset representation of a file-system tree";
# ensure that every path is in canonical form, else we might get duplicates and subtle errors
check = tree: builtins.all (p: p == path-lib.norm p) (builtins.attrNames tree);
};
in {
options = {
sane.fs = mkOption {
# type = types.attrsOf fsEntry;
type = fsTree;
default = {};
};
};
config = mergeTopLevel (lib.mapAttrsToList mkFsConfig cfg);
}

11
modules/gui/snippets.txt Normal file
View File

@@ -0,0 +1,11 @@
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://nix-community.github.io/home-manager/options.html
https://w.uninsane.org/viewer#search?books.name=wikipedia_en_all_maxi_2022-05&pattern=
https://jackett.uninsane.org/UI/Dashboard#search=
https://fed.uninsane.org
https://bt.uninsane.org
https://sci-hub.se
https://news.ycombinator.com

View File

@@ -81,8 +81,29 @@ in
sane.home-manager.windowManager.sway = {
enable = true;
wrapperFeatures.gtk = true;
config = rec {
terminal = "${pkgs.kitty}/bin/kitty";
config = let
fuzzel = "${pkgs.fuzzel}/bin/fuzzel";
sed = "${pkgs.gnused}/bin/sed";
wtype = "${pkgs.wtype}/bin/wtype";
kitty = "${pkgs.kitty}/bin/kitty";
lock-cmd = "${pkgs.swaylock}/bin/swaylock --indicator-idle-visible --indicator-radius 100 --indicator-thickness 30";
vol-up-cmd = "${pkgs.pulsemixer}/bin/pulsemixer --change-volume +5";
vol-down-cmd = "${pkgs.pulsemixer}/bin/pulsemixer --change-volume -5";
mute-cmd = "${pkgs.pulsemixer}/bin/pulsemixer --toggle-mute";
brightness-up-cmd = "${pkgs.brightnessctl}/bin/brightnessctl set +2%";
brightness-down-cmd = "${pkgs.brightnessctl}/bin/brightnessctl set 2%-";
screenshot-cmd = "${pkgs.sway-contrib.grimshot}/bin/grimshot copy area";
# "bookmarking"/snippets inspired by Luke Smith:
# - <https://www.youtube.com/watch?v=d_11QaTlf1I>
snip-file = ./snippets.txt;
# TODO: querying sops here breaks encapsulation
list-snips = "cat ${snip-file} ${config.sops.secrets.snippets.path}";
strip-comments = "${sed} 's/ #.*$//'";
snip-cmd = "${wtype} $(${list-snips} | ${fuzzel} -d -i -w 60 | ${strip-comments})";
# TODO: next splatmoji release should allow `-s none` to disable skin tones
emoji-cmd = "${pkgs.splatmoji}/bin/splatmoji -s medium-light type";
in rec {
terminal = kitty;
window = {
border = 3; # pixel boundary between windows
hideEdgeBorders = "smart"; # don't show border if only window on workspace
@@ -103,7 +124,7 @@ in
modifier = "Mod1";
# list of launchers: https://www.reddit.com/r/swaywm/comments/v39hxa/your_favorite_launcher/
# menu = "${pkgs.dmenu}/bin/dmenu_path";
menu = "${pkgs.fuzzel}/bin/fuzzel";
menu = fuzzel;
# menu = "${pkgs.albert}/bin/albert";
left = "h";
down = "j";
@@ -114,7 +135,9 @@ in
"${modifier}+Return" = "exec ${terminal}";
"${modifier}+Shift+q" = "kill";
"${modifier}+d" = "exec ${menu}";
"${modifier}+l" = "exec ${pkgs.swaylock}/bin/swaylock --indicator-idle-visible --indicator-radius 100 --indicator-thickness 30";
"${modifier}+s" = "exec ${snip-cmd}";
"${modifier}+l" = "exec ${lock-cmd}";
"${modifier}+slash" = "exec ${emoji-cmd}";
# "${modifier}+${left}" = "focus left";
# "${modifier}+${down}" = "focus down";
@@ -141,7 +164,7 @@ in
"${modifier}+f" = "fullscreen toggle";
"${modifier}+a" = "focus parent";
"${modifier}+s" = "layout stacking";
# "${modifier}+s" = "layout stacking";
"${modifier}+w" = "layout tabbed";
"${modifier}+e" = "layout toggle split";
@@ -185,19 +208,20 @@ in
"exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'";
"${modifier}+r" = "mode resize";
} // {
# media keys
XF86MonBrightnessDown = ''exec "${pkgs.brightnessctl}/bin/brightnessctl set 2%-"'';
XF86MonBrightnessUp = ''exec "${pkgs.brightnessctl}/bin/brightnessctl set +2%"'';
XF86MonBrightnessDown = "exec ${brightness-down-cmd}";
XF86MonBrightnessUp = "exec ${brightness-up-cmd}";
XF86AudioRaiseVolume = "exec '${pkgs.pulsemixer}/bin/pulsemixer --change-volume +5'";
XF86AudioLowerVolume = "exec '${pkgs.pulsemixer}/bin/pulsemixer --change-volume -5'";
XF86AudioMute = "exec '${pkgs.pulsemixer}/bin/pulsemixer --toggle-mute'";
# TODO: hook into a visual prompt to display volume?
XF86AudioRaiseVolume = "exec ${vol-up-cmd}";
XF86AudioLowerVolume = "exec ${vol-down-cmd}";
XF86AudioMute = "exec ${mute-cmd}";
"${modifier}+Page_Up" = "exec '${pkgs.pulsemixer}/bin/pulsemixer --change-volume +5'";
"${modifier}+Page_Down" = "exec '${pkgs.pulsemixer}/bin/pulsemixer --change-volume -5'";
"${modifier}+Page_Up" = "exec ${vol-up-cmd}";
"${modifier}+Page_Down" = "exec ${vol-down-cmd}";
"${modifier}+Print" = "exec '${pkgs.sway-contrib.grimshot}/bin/grimshot copy area'";
"${modifier}+Print" = "exec ${screenshot-cmd}";
};
# mostly defaults:

View File

@@ -9,27 +9,24 @@
with lib;
let
cfg = config.sane.home-manager;
# extract package from `sane.packages.enabledUserPkgs`
pkg-list = pkgspec: builtins.map (e: e.pkg or e) pkgspec;
# extract `dir` from `sane.packages.enabledUserPkgs`
dir-list = pkgspec: builtins.concatLists (builtins.map (e: if e ? "dir" then [ e.dir ] else []) pkgspec);
private-list = pkgspec: builtins.concatLists (builtins.map (e: if e ? "private" then [ e.private ] else []) pkgspec);
# 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
./discord.nix
./firefox.nix
./git.nix
./kitty.nix
./mpv.nix
./nb.nix
./neovim.nix
./splatmoji.nix
./ssh.nix
./sublime-music.nix
./vlc.nix
./zsh.nix
./zsh
];
options = {
@@ -51,18 +48,6 @@ in
};
config = lib.mkIf cfg.enable {
sane.impermanence.home-dirs = [
"archive"
"dev"
"records"
"ref"
"tmp"
"use"
"Music"
"Pictures"
"Videos"
] ++ (dir-list config.sane.packages.enabledUserPkgs);
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
@@ -90,27 +75,6 @@ in
};
};
home.file = let
privates = builtins.listToAttrs (
builtins.map (path: {
name = path;
value = { source = config.lib.file.mkOutOfStoreSymlink "/home/colin/private/${path}"; };
})
(private-list sysconfig.sane.packages.enabledUserPkgs)
);
in {
# convenience
"knowledge".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/dev/knowledge";
"nixos".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/dev/nixos";
"Videos/servo".source = config.lib.file.mkOutOfStoreSymlink "/mnt/servo-media/Videos";
"Videos/servo-incomplete".source = config.lib.file.mkOutOfStoreSymlink "/mnt/servo-media/incomplete";
"Music/servo".source = config.lib.file.mkOutOfStoreSymlink "/mnt/servo-media/Music";
# used by password managers, e.g. unix `pass`
".password-store".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/dev/knowledge/secrets/accounts";
} // privates;
# XDG defines things like ~/Desktop, ~/Downloads, etc.
# these clutter the home, so i mostly don't use them.
xdg.userDirs = {

View File

@@ -1,12 +0,0 @@
{ config, lib, ... }:
lib.mkIf config.sane.home-manager.enable
{
# TODO: this should only be enabled on gui devices
# make Discord usable even when client is "outdated"
home-manager.users.colin.xdg.configFile."discord/settings.json".text = ''
{
"SKIP_HOST_UPDATE": true
}
'';
}

View File

@@ -63,6 +63,10 @@ in rec {
(mkPod "https://www.cbsnews.com/latest/rss/60-minutes" // pol // infrequent)
## The Verge - Decoder
(mkPod "https://feeds.megaphone.fm/recodedecode" // tech // weekly)
## Matrix (chat) Live
(mkPod "https://feed.podbean.com/matrixlive/feed.xml" // tech // weekly)
## Michael Malice - Your Welcome
(mkPod "https://www.podcastone.com/podcast?categoryID2=2232" // pol // weekly)
];
texts = [
@@ -135,8 +139,11 @@ in rec {
## Sean Carroll
(mkText "https://www.preposterousuniverse.com/rss" // rat // infrequent)
## mostly dating topics. not advice, or humor, but looking through a social lens
(mkText "https://putanumonit.com/feed" // rat // infrequent)
# CODE
(mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
];
images = [

View File

@@ -19,12 +19,14 @@ let
# });
libName = "librewolf";
dotDir = ".librewolf";
cacheDir = ".cache/librewolf"; # TODO: is it?
desktop = "librewolf.desktop";
};
firefoxSettings = {
browser = pkgs.firefox-esr-unwrapped;
libName = "firefox";
dotDir = ".mozilla/firefox";
cacheDir = ".cache/mozilla";
desktop = "firefox.desktop";
};
defaultSettings = firefoxSettings;
@@ -43,6 +45,7 @@ let
addon = name: extid: hash: pkgs.fetchFirefoxAddon {
inherit name hash;
url = "https://addons.mozilla.org/firefox/downloads/latest/${name}/latest.xpi";
# extid can be found by unar'ing the above xpi, and copying browser_specific_settings.gecko.id field
fixedExtid = extid;
};
localAddon = pkg: pkgs.fetchFirefoxAddon {
@@ -51,11 +54,16 @@ let
fixedExtid = pkg.extid;
};
in [
(addon "ublock-origin" "uBlock0@raymondhill.net" "sha256-+xc4lcdsOwXxMsr4enFsdePbIb6GHq0bFLpqvH5xXos=")
(addon "sponsorblock" "sponsorBlocker@ajay.app" "sha256-30F8oDIgshXVY7YKgnfoc1tUTHfgeFbzXISJuVJs0AM=")
(addon "bypass-paywalls-clean" "{d133e097-46d9-4ecc-9903-fa6a722a6e0e}" "sha256-aDBRpcOeMyROnXjmveHKm9zsPC+LzXCG0uhAqI1EWf0=")
# get names from:
# - ~/ref/nix-community/nur-combined/repos/rycee/pkgs/firefox-addons/generated-firefox-addons.nix
# `wget ...xpi`; `unar ...xpi`; `cat */manifest.json | jq '.browser_specific_settings.gecko.id'`
(addon "ublock-origin" "uBlock0@raymondhill.net" "sha256-a/ivUmY1P6teq9x0dt4CbgHt+3kBsEMMXlOfZ5Hx7cg=")
(addon "sponsorblock" "sponsorBlocker@ajay.app" "sha256-d2K3ufvurWnYVzqLbyR//MgejybkY9exitAf9RdLNRo=")
(addon "bypass-paywalls-clean" "{d133e097-46d9-4ecc-9903-fa6a722a6e0e}" "sha256-t6Q335Nq60mDILPmzem+DT5KflleAPVJL3bsaA+UL0g=")
(addon "sidebery" "{3c078156-979c-498b-8990-85f7987dd929}" "sha256-YONfK/rIjlsrTgRHIt3km07Q7KnpIW89Z9r92ZSCc6w=")
(addon "ether-metamask" "webextension@metamask.io" "sha256-G+MwJDOcsaxYSUXjahHJmkWnjLeQ0Wven8DU/lGeMzA=")
(addon "ublacklist" "@ublacklist" "sha256-vHe/7EYOzcKeAbTElmt0Rb4E2rX0f3JgXThJaUmaz+M=")
(addon "i2p-in-private-browsing" "i2ppb@eyedeekay.github.io" "sha256-dJcJ3jxeAeAkRvhODeIVrCflvX+S4E0wT/PyYzQBQWs=")
# (addon "browserpass-ce" "browserpass@maximbaz.com" "sha256-sXgUBbRvMnRpeIW1MTkmTcoqtW/8RDXAkxAq1evFkpc=")
(localAddon pkgs.browserpass-extension)
];

View File

@@ -17,7 +17,7 @@ lib.mkIf false # XXX disabled!
home-manager.users.colin = { config, ... }: {
# nb markdown/personal knowledge manager
home.file.".nb/knowledge".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/dev/knowledge";
home.file.".nb/knowledge".source = config.lib.file.mkOutOfStoreSymlink "/home/colin/knowledge";
home.file.".nb/.current".text = "knowledge";
home.file.".nbrc".text = ''
# manage with `nb settings`

View File

@@ -2,7 +2,8 @@
lib.mkIf config.sane.home-manager.enable
{
sane.impermanence.home-dirs = [ ".cache/vim-swap" ];
# private because there could be sensitive things in the swap
sane.impermanence.dirs.home.private = [ ".cache/vim-swap" ];
home-manager.users.colin.programs.neovim = {
# neovim: https://github.com/neovim/neovim

View File

@@ -0,0 +1,20 @@
# borrows from:
# - default config: <https://github.com/cspeterson/splatmoji/blob/master/splatmoji.config>
# - wayland: <https://github.com/cspeterson/splatmoji/issues/32#issuecomment-830862566>
{ pkgs, ... }:
{
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
'';
};
}

View File

@@ -1,63 +0,0 @@
{ config, lib, ... }:
lib.mkIf config.sane.home-manager.enable
{
# we don't need to full zsh dir -- just the history file --
# but zsh will sometimes backup the history file and we get fewer errors if we do proper mounts instead of symlinks.
sane.impermanence.home-dirs = [ ".local/share/zsh" ];
home-manager.users.colin.programs.zsh = {
enable = true;
enableSyntaxHighlighting = true;
enableVteIntegration = true;
history.ignorePatterns = [ "rm *" ];
dotDir = ".config/zsh";
history.path = "/home/colin/.local/share/zsh/history";
initExtraBeforeCompInit = ''
# p10k instant prompt
# run p10k configure to configure, but it can't write out its file :-(
POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD=true
'';
initExtra = ''
# zmv is a way to do rich moves/renames, with pattern matching/substitution.
# see for an example: <https://filipe.kiss.ink/zmv-zsh-rename/>
autoload -Uz zmv
# disable `rm *` confirmations
setopt rmstarsilent
function nd() {
mkdir -p "$1";
pushd "$1";
}
'';
# prezto = oh-my-zsh fork; controls prompt, auto-completion, etc.
# see: https://github.com/sorin-ionescu/prezto
prezto = {
enable = true;
pmodules = [
"environment"
"terminal"
"editor"
"history"
"directory"
"spectrum"
"utility"
"completion"
"prompt"
"git"
];
prompt.theme = "powerlevel10k";
utility.safeOps = false; # disable `mv` confirmation (and supposedly `rm`, too)
};
};
home-manager.users.colin.home.shellAliases = {
":q" = "exit";
# common typos
"cd.." = "cd ..";
"cd../" = "cd ../";
};
}

View File

@@ -0,0 +1,108 @@
{ config, lib, ... }:
lib.mkIf config.sane.home-manager.enable
{
sane.impermanence.dirs.home.plaintext = [
# we don't need to full zsh dir -- just the history file --
# but zsh will sometimes backup the history file and we get fewer errors if we do proper mounts instead of symlinks.
# TODO: should be private?
".local/share/zsh"
# cache gitstatus otherwise p10k fetched it from the net EVERY BOOT
".cache/gitstatus"
];
home-manager.users.colin.programs.zsh = {
enable = true;
enableSyntaxHighlighting = true;
enableVteIntegration = true;
history.ignorePatterns = [ "rm *" ];
dotDir = ".config/zsh";
history.path = "/home/colin/.local/share/zsh/history";
# defaultKeymap = "vicmd"; # vim normal mode (cmd mode)
# powerlevel10k prompt config
# p10k.zsh is the auto-generated config, and i overwrite those defaults here, below.
initExtraBeforeCompInit = (builtins.readFile ./p10k.zsh) + ''
# powerlevel10k launches a gitstatusd daemon to accelerate git prompt queries.
# this keeps open file handles for any git repo i touch for 60 minutes (by default).
# that prevents unmounting whatever device the git repo is on -- particularly problematic for ~/private.
# i can disable gitstatusd and get slower fallback git queries:
# - either universally
# - or selectively by path
# see: <https://github.com/romkatv/powerlevel10k/issues/246>
typeset -g POWERLEVEL9K_VCS_DISABLED_DIR_PATTERN='(/home/colin/private/*|/home/colin/knowledge/*)'
# typeset -g POWERLEVEL9K_DISABLE_GITSTATUS=true
# show user@host also when logged into the current machine.
# default behavior is to show it only over ssh.
typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_CONTENT_EXPANSION='$P9K_CONTENT'
'';
initExtra = ''
# zmv is a way to do rich moves/renames, with pattern matching/substitution.
# see for an example: <https://filipe.kiss.ink/zmv-zsh-rename/>
autoload -Uz zmv
# disable `rm *` confirmations
setopt rmstarsilent
function nd() {
mkdir -p "$1";
pushd "$1";
}
'';
# prezto = oh-my-zsh fork; controls prompt, auto-completion, etc.
# see: https://github.com/sorin-ionescu/prezto
prezto = {
enable = true;
pmodules = [
# configures jobs to persist after shell exit; other basic niceties
"environment"
# auto-titles terminal (e.g. based on cwd)
"terminal"
# configures shortcuts like Ctrl+U=undo, Ctrl+L=clear
"editor"
# adds `history-stat` alias, setopts for good history defaults
"history"
# sets AUTO_CD, adds `d` alias to list directory stack, and `1`-`9` to cd that far back the stack
"directory"
# helpers for term colors and styling. used by prompts? might be unnecessary
"spectrum"
# configures aliases like `ll`, `la`, disables globbing for things like rsync
# adds aliases like `get` to fetch a file. also adds `http-serve` alias??
"utility"
# tab completion. requires `utility` module prior to loading
# TODO: enable AUTO_PARAM_SLASH
"completion"
"prompt"
# TODO: enable syntax-highlighting ?
];
prompt.theme = "powerlevel10k";
utility.safeOps = false; # disable `mv` confirmation (and supposedly `rm`, too)
# editor.keymap = "vi";
};
dirHashes = {
# convenient `cd`-isms
"3rd" = "/home/colin/dev/3rd";
"dev" = "/home/colin/dev";
"knowledge" = "/home/colin/knowledge";
"nixos" = "/home/colin/nixos";
"nixpkgs" = "/home/colin/dev/3rd/nixpkgs";
"ref" = "/home/colin/ref";
"secrets" = "/home/colin/knowledge/secrets";
"tmp" = "/home/colin/tmp";
"uninsane" = "/home/colin/dev/uninsane";
"Videos" = "/home/colin/Videos";
};
};
home-manager.users.colin.home.shellAliases = {
":q" = "exit";
# common typos
"cd.." = "cd ..";
"cd../" = "cd ../";
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
{ config, lib, pkgs, mobile-nixos, utils, ... }:
{ config, lib, pkgs, utils, ... }:
# TODO: replace mobile-nixos parts with Disko <https://github.com/nix-community/disko>
with lib;
let
@@ -9,7 +10,7 @@ in
sane.image.enable = mkOption {
default = true;
type = types.bool;
description = "whether to enable image targets. this doesn't mean they'll be built unless you specifically reference the target.";
description = "whether to enable image targets. even so they won't be built unless you specifically reference the `system.build.img` target.";
};
# packages whose contents should be copied directly into the /boot partition.
# e.g. EFI loaders, u-boot bootloader, etc.

View File

@@ -1,94 +0,0 @@
# borrows from:
# https://xeiaso.net/blog/paranoid-nixos-2021-07-18
# https://elis.nu/blog/2020/05/nixos-tmpfs-as-root/
# https://github.com/nix-community/impermanence
{ lib, config, impermanence, ... }:
with lib;
let
cfg = config.sane.impermanence;
# taken from sops-nix code: checks if any secrets are needed to create /etc/shadow
secretsForUsers = (lib.filterAttrs (_: v: v.neededForUsers) config.sops.secrets) != {};
in
{
options = {
sane.impermanence.enable = mkOption {
default = false;
type = types.bool;
};
sane.impermanence.home-dirs = mkOption {
default = [];
type = types.listOf (types.either types.str (types.attrsOf types.str));
};
sane.impermanence.service-dirs = mkOption {
default = [];
type = types.listOf (types.either types.str (types.attrsOf types.str));
};
};
config = let
map-dir = defaults: dir: if isString dir then
map-dir defaults { directory = "${defaults.directory}${dir}"; }
else
defaults // dir
;
map-dirs = defaults: dirs: builtins.map (map-dir defaults) dirs;
map-home-dirs = map-dirs { user = "colin"; group = "users"; mode = "0755"; directory = "/home/colin/"; };
map-sys-dirs = map-dirs { user = "root"; group = "root"; mode = "0755"; directory = ""; };
in mkIf cfg.enable {
sane.image.extraDirectories = [ "/nix/persist/var/log" ];
environment.persistence."/nix/persist" = {
directories = (map-home-dirs cfg.home-dirs) ++ (map-sys-dirs [
# NB: this `0700` here clobbers the perms for /persist/etc, breaking boot on freshly-deployed devices
# { mode = "0700"; directory = "/etc/NetworkManager/system-connections"; }
# "/etc/nixos"
# "/etc/ssh" # persist only the specific files we want, instead
"/var/log"
"/var/backup" # for e.g. postgres dumps
# "/var/lib/AccountsService" # not sure what this is, but it's empty
"/var/lib/alsa" # preserve output levels, default devices
# "/var/lib/blueman" # files aren't human readable
"/var/lib/bluetooth" # preserve bluetooth handshakes
"/var/lib/colord" # preserve color calibrations (?)
# "/var/lib/dhclient" # empty on lappy; dunno about desko
# "/var/lib/fwupd" # not sure why this would need persistent state
# "/var/lib/geoclue" # empty on lappy
# "/var/lib/lockdown" # empty on desko; might store secrets after iOS handshake?
# "/var/lib/logrotate.status" # seems redundant with what's in /var/log?
"/var/lib/machines" # maybe not needed, but would be painful to add a VM and forget.
# "/var/lib/misc" # empty on lappy
# "/var/lib/NetworkManager" # looks to be mostly impermanent state?
# "/var/lib/NetworkManager-fortisslvpn" # empty on lappy
# "/var/lib/nixos" # has some uid/gid maps, but we enforce these to be deterministic.
# "/var/lib/PackageKit" # wtf is this?
# "/var/lib/power-profiles-daemon" # redundant with nixos declarations
# "/var/lib/private" # empty on lappy
# "/var/lib/systemd" # nothing obviously necessary
# "/var/lib/udisks2" # empty on lappy
# "/var/lib/upower" # historic charge data. unnecessary, but maybe used somewhere?
#
# servo additions:
] ++ cfg.service-dirs);
# /etc/machine-id is a globally unique identifier used for:
# - systemd-networkd: DHCP lease renewal (instead of keying by the MAC address)
# - systemd-journald: to filter logs by host
# - chromium (potentially to track re-installations)
# - gdbus; system services that might upgrade to AF_LOCAL if both services can confirm they're on the same machine
# of these, systemd-networkd is the only legitimate case to persist the machine-id.
# depersisting it should be "safe"; edge-cases like systemd-networkd can be directed to use some other ID if necessary.
# nixos-impermanence shows binding the host ssh priv key to this; i could probably hash the host key into /etc/machine-id if necessary.
# files = [ "/etc/machine-id" ];
};
# secret decoding depends on /etc/ssh keys, which may be persisted
system.activationScripts.setupSecrets.deps = [ "persist-ssh-host-keys" ];
system.activationScripts.setupSecretsForUsers = lib.mkIf secretsForUsers {
deps = [ "persist-ssh-host-keys" ];
};
# populated by ssh.nix, which persists /etc/ssh/host_keys
system.activationScripts.persist-ssh-host-keys.text = lib.mkDefault "";
};
}

View File

@@ -0,0 +1,191 @@
# 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);
};
}

View File

@@ -0,0 +1,16 @@
{ config, lib, ... }:
let
cfg = config.sane.impermanence;
in
{
fileSystems."/" = lib.mkIf (cfg.enable && cfg.root-on-tmpfs) {
device = "none";
fsType = "tmpfs";
options = [
"mode=755"
"size=1G"
"defaults"
];
};
}

View File

@@ -0,0 +1,74 @@
{ config, lib, pkgs, utils, ... }:
let
store = rec {
device = "/mnt/impermanence/crypt/clearedonboot";
underlying = {
path = "/nix/persist/crypt/clearedonboot";
# TODO: consider moving this to /tmp, but that requires tmp be mounted first?
key = "/mnt/impermanence/crypt/clearedonboot.key";
};
};
in
lib.mkIf config.sane.impermanence.enable
{
sane.impermanence.stores."cryptClearOnBoot" = {
storeDescription = ''
stored to disk, but encrypted to an in-memory key and cleared on every boot
so that it's unreadable after power-off
'';
origin = store.device;
};
fileSystems."${store.device}" = {
device = store.underlying.path;
fsType = "fuse.gocryptfs";
options = [
"nodev"
"nosuid"
"allow_other"
"passfile=${store.underlying.key}"
"defaults"
];
noCheck = true;
};
# let sane.fs know about our fileSystem and automatically add the appropriate dependencies
sane.fs."${store.device}".mount = {
# technically the dependency on the keyfile is extraneous because that *happens* to
# be needed to init the store.
depends = let
cryptfile = config.sane.fs."${store.underlying.path}/gocryptfs.conf";
keyfile = config.sane.fs."${store.underlying.key}";
in [ keyfile.unit cryptfile.unit ];
};
# let sane.fs know how to initialize the gocryptfs store,
# and that it MUST do so
sane.fs."${store.underlying.path}/gocryptfs.conf".generated = {
script.script = ''
backing="$1"
passfile="$2"
# clear the backing store
# TODO: we should verify that it's not mounted anywhere...
rm -rf "''${backing:?}"/*
${pkgs.gocryptfs}/bin/gocryptfs -quiet -passfile "$passfile" -init "$backing"
'';
script.scriptArgs = [ store.underlying.path store.underlying.key ];
# we need the key in order to initialize the store
depends = [ config.sane.fs."${store.underlying.key}".unit ];
};
# let sane.fs know how to generate the key for gocryptfs
sane.fs."${store.underlying.key}".generated = {
script.script = ''
dd if=/dev/random bs=128 count=1 | base64 --wrap=0 > "$1"
'';
script.scriptArgs = [ store.underlying.key ];
# no need for anyone else to be able to read the key
acl.mode = "0400";
};
# TODO: could add this *specifically* to the .mount file for the encrypted fs?
system.fsPackages = [ pkgs.gocryptfs ]; # fuse needs to find gocryptfs
}

View File

@@ -0,0 +1,31 @@
{ config, lib, sane-lib, ... }:
let
cfg = config.sane.impermanence;
path = sane-lib.path;
in
{
imports = [
./crypt.nix
./plaintext.nix
./private.nix
];
config = lib.mkIf cfg.enable {
# make sure that the store has the same acl as the main filesystem,
# particularly for /home/colin.
#
# N.B.: we have a similar problem with all mounts:
# <crypt>/.cache/mozilla won't inherit <plain>/.cache perms.
# this is less of a problem though, since we don't really support overlapping mounts like that in the first place.
# what is a problem is if the user specified some other dir we don't know about here.
# like "/var", and then "/nix/persist/var" has different perms and something mounts funny.
# TODO: just add assertions that sane.fs."${backing}/${dest}".dir == sane.fs."${dest}" for each mount point?
sane.fs = lib.mapAttrs' (_name: store: let
home-in-store = path.from store.prefix "/home/colin";
in {
name = path.concat [ store.origin home-in-store ];
value.dir.acl = config.sane.fs."/home/colin".generated.acl;
}) cfg.stores;
};
}

View File

@@ -0,0 +1,11 @@
{ config, lib, ... }:
let
cfg = config.sane.impermanence;
in lib.mkIf cfg.enable {
sane.impermanence.stores."plaintext" = {
origin = "/nix/persist";
};
# TODO: needed?
# sane.fs."/nix".mount = {};
}

View File

@@ -0,0 +1,48 @@
{ config, lib, pkgs, utils, ... }:
lib.mkIf config.sane.impermanence.enable
{
sane.impermanence.stores."private" = {
storeDescription = ''
encrypted to the user's password and auto-unlocked at login
'';
origin = "/home/colin/private";
# files stored under here *must* have the /home/colin prefix.
# internally, this prefix is removed so that e.g.
# /home/colin/foo/bar when stored in `private` is visible at
# /home/colin/private/foo/bar
prefix = "/home/colin";
defaultOrdering = let
private-unit = config.sane.fs."/home/colin/private".unit;
in {
# auto create only after ~/private is mounted
wantedBy = [ private-unit ];
# we can't create things in private before local-fs.target
wantedBeforeBy = [ ];
};
};
fileSystems."/home/colin/private" = {
device = "/nix/persist/home/colin/private";
fsType = "fuse.gocryptfs";
options = [
"noauto" # don't try to mount, until the user logs in!
"nofail"
"allow_other" # root ends up being the user that mounts this, so need to make it visible to `colin`.
"nodev"
"nosuid"
"quiet"
"defaults"
];
noCheck = true;
};
# let sane.fs know about the mount
sane.fs."/home/colin/private".mount = {};
# it also needs to know that the underlying device is an ordinary folder
sane.fs."/nix/persist/home/colin/private".dir = {};
# TODO: could add this *specifically* to the .mount file for the encrypted fs?
system.fsPackages = [ pkgs.gocryptfs ]; # fuse needs to find gocryptfs
}

8
modules/lib/default.nix Normal file
View File

@@ -0,0 +1,8 @@
{ lib, ... }@moduleArgs:
{
path = import ./path.nix moduleArgs;
types = import ./types.nix moduleArgs;
filterNonNull = attrs: lib.filterAttrsRecursive (n: v: v != null) attrs;
}

30
modules/lib/path.nix Normal file
View File

@@ -0,0 +1,30 @@
{ lib, utils, ... }:
let path = rec {
# split the string path into a list of string components.
# root directory "/" becomes the empty list [].
# implicitly performs normalization so that:
# split "a//b/" => ["a" "b"]
# split "/a/b" => ["a" "b"]
split = str: builtins.filter (seg: seg != "") (lib.splitString "/" str);
# given an array of components, returns the equivalent string path
join = comps: "/" + (builtins.concatStringsSep "/" comps);
# given an a sequence of string paths, concatenates them into one long string path
concat = paths: path.join (builtins.concatLists (builtins.map path.split paths));
# normalize the given path
norm = str: path.join (path.split str);
# return the parent directory. doesn't care about leading/trailing slashes.
# the parent of "/" is "/".
parent = str: path.norm (builtins.dirOf (path.norm str));
hasParent = str: (path.parent str) != (path.norm str);
# return the path from `from` to `to`, but keeping absolute form
# e.g. `pathFrom "/home/colin" "/home/colin/foo/bar"` -> "/foo/bar"
from = start: end: let
s = path.norm start;
e = path.norm end;
in (
assert lib.hasPrefix s e;
"/" + (lib.removePrefix s e)
);
};
in path

42
modules/lib/types.nix Normal file
View File

@@ -0,0 +1,42 @@
{ lib, ... }:
with lib;
rec {
# "Access Control List", only it's just a user:group and file mode
# compatible with `chown` and `chmod`
aclMod = {
options = {
user = mkOption {
type = types.str; # TODO: use uid?
};
group = mkOption {
type = types.str;
};
mode = mkOption {
type = types.str;
};
};
};
acl = types.submodule aclMod;
# this is acl, but doesn't require to be fully specified.
# a typical use case is when there's a complete acl, and the user
# wants to override just one attribute of it.
aclOverrideMod = {
options = {
user = mkOption {
type = types.nullOr types.str;
default = null;
};
group = mkOption {
type = types.nullOr types.str;
default = null;
};
mode = mkOption {
type = types.nullOr types.str;
default = null;
};
};
};
aclOverride = types.submodule aclOverrideMod;
}

View File

@@ -4,6 +4,11 @@ with lib;
with pkgs;
let
cfg = config.sane.packages;
imagemagick = pkgs.imagemagick.override {
ghostscriptSupport = true;
};
consolePkgs = [
backblaze-b2
cdrtools
@@ -12,11 +17,13 @@ let
efivar
flashrom
fwupd
ghostscript # TODO: imagemagick wrapper should add gs to PATH
gnupg
gocryptfs
gopass
gopass-jsonapi
ifuse
imagemagick
ipfs
libimobiledevice
libsecret # for managing user keyrings
@@ -59,18 +66,18 @@ let
celluloid # mpv frontend
chromium
clinfo
{ pkg = dino; private = ".local/share/dino"; }
{ pkg = dino; private = [ ".local/share/dino" ]; }
electrum
# creds/session keys, etc
{ pkg = element-desktop; private = ".config/Element"; }
{ pkg = element-desktop; private = [ ".config/Element" ]; }
# `emote` will show a first-run dialog based on what's in this directory.
# mostly, it just keeps a LRU of previously-used emotes to optimize display order.
# TODO: package [smile](https://github.com/mijorus/smile) for probably a better mobile experience.
{ pkg = emote; dir = ".local/share/Emote"; }
{ pkg = emote; dir = [ ".local/share/Emote" ]; }
evince # works on phosh
# { pkg = fluffychat-moby; dir = ".local/share/chat.fluffy.fluffychat"; } # TODO: ship normal fluffychat on non-moby?
# { pkg = fluffychat-moby; dir = [ ".local/share/chat.fluffy.fluffychat" ]; } # TODO: ship normal fluffychat on non-moby?
foliate
font-manager
@@ -78,8 +85,10 @@ let
# XXX by default fractal stores its state in ~/.local/share/<UUID>.
# after logging in, manually change ~/.local/share/keyrings/... to point it to some predictable subdir.
# then reboot (so that libsecret daemon re-loads the keyring...?)
{ pkg = fractal-next; private = ".local/share/fractal"; }
# { pkg = fractal-latest; private = [ ".local/share/fractal" ]; }
# { pkg = fractal-next; private = [ ".local/share/fractal" ]; }
gajim # XMPP client
gimp # broken on phosh
gnome.cheese
gnome.dconf-editor
@@ -93,7 +102,7 @@ let
gnome.gnome-terminal # works on phosh
gnome.gnome-weather
{ pkg = gpodder-configured; dir = "gPodder/Downloads"; }
{ pkg = gpodder-configured; dir = [ "gPodder/Downloads" ]; }
gthumb
handbrake
@@ -106,15 +115,21 @@ let
lollypop
mesa-demos
{ pkg = mpv; dir = ".config/mpv/watch_later"; }
{ pkg = mpv; dir = [ ".config/mpv/watch_later" ]; }
networkmanagerapplet
# not strictly necessary, but allows caching articles; offline use, etc.
{ pkg = newsflash; dir = ".local/share/news-flash"; }
{ pkg = newsflash; dir = [ ".local/share/news-flash" ]; }
{ pkg = nheko; private = [
".config/nheko" # config file (including client token)
".cache/nheko" # media cache
".local/share/nheko" # per-account state database
]; }
# settings (electron app). TODO: can i manage these settings with home-manager?
{ pkg = obsidian; dir = ".config/obsidian"; }
{ pkg = obsidian; dir = [ ".config/obsidian" ]; }
pavucontrol
# picard # music tagging
@@ -127,14 +142,14 @@ let
# it doesn't obey a conventional ~/Music/{Artist}/{Album}/{Track} notation, so no symlinking
# config (e.g. server connection details) is persisted in ~/.config/sublime-music/config.json
# possible to pass config as a CLI arg (sublime-music -c config.json)
# { pkg = sublime-music; dir = ".local/share/sublime-music"; }
{ pkg = sublime-music-mobile; dir = ".local/share/sublime-music"; }
tdesktop # broken on phosh
# { pkg = sublime-music; dir = [ ".local/share/sublime-music" ]; }
{ pkg = sublime-music-mobile; dir = [ ".local/share/sublime-music" ]; }
{ pkg = tdesktop; private = [ ".local/share/TelegramDesktop" ]; } # broken on phosh
{ pkg = tokodon; dir = ".cache/KDE/tokodon"; }
{ pkg = tokodon; private = [ ".cache/KDE/tokodon" ]; }
# vlc remembers play position in ~/.config/vlc/vlc-qt-interface.conf
{ pkg = vlc; dir = ".config/vlc"; }
{ pkg = vlc; dir = [ ".config/vlc" ]; }
whalebird # pleroma client. input is broken on phosh
xdg-utils # for xdg-open
@@ -149,36 +164,37 @@ let
# XXX 2022-07-31: fix to allow links to open in default web-browser:
# https://github.com/NixOS/nixpkgs/issues/78961
nss = pkgs.nss_latest;
}); in { pkg = discord; dir = ".config/discord"; })
}); in { pkg = discord; private = [ ".config/discord" ]; })
# kaiteki # Pleroma client
# gnome.zenity # for kaiteki (it will use qarma, kdialog, or zenity)
gpt2tc
# gpt2tc # XXX: unreliable mirror
logseq
losslesscut-bin
makemkv
# actual monero blockchain (not wallet/etc; safe to delete, just slow to regenerate)
{ pkg = monero-gui; dir = ".bitmonero"; }
{ pkg = monero-gui; dir = [ ".bitmonero" ]; }
# creds, media
{ pkg = signal-desktop; dir = ".config/Signal"; }
{ pkg = signal-desktop; private = [ ".config/Signal" ]; }
# creds. TODO: can i manage this with home-manager?
{ pkg = spotify; dir = ".config/spotify"; }
{ pkg = spotify; dir = [ ".config/spotify" ]; }
# hardenedMalloc solves a crash at startup
(tor-browser-bundle-bin.override { useHardenedMalloc = false; })
# zcash coins. safe to delete, just slow to regenerate (10-60 minutes)
{ pkg = zecwallet-lite; private = ".zcash"; }
{ pkg = zecwallet-lite; private = [ ".zcash" ]; }
] else []);
# general-purpose utilities that we want any user to be able to access
# (specifically: root, in case of rescue)
systemPkgs = [
btrfs-progs
cacert.unbundled # some services require unbundled /etc/ssl/certs
cryptsetup
dig
efibootmgr
@@ -203,11 +219,14 @@ let
parted
pciutils
powertop
pstree
ripgrep
screen
smartmontools
socat
strace
tcpdump
tree
usbutils
wget
];
@@ -227,15 +246,31 @@ let
rustup
swig
];
pkgSpec = types.submodule {
options = {
pkg = mkOption {
type = types.package;
};
dir = mkOption {
type = types.listOf types.str;
default = [];
description = "list of home-relative paths to persist for this package";
};
private = mkOption {
type = types.listOf types.str;
default = [];
description = "list of home-relative paths to persist (in encrypted format) for this package";
};
};
};
in
{
options = {
# packages to deploy to the user's home
sane.packages.extraUserPkgs = mkOption {
default = [ ];
# each entry can be either a package, or attrs:
# { pkg = package; dir = optional string; private = optional string };
type = types.listOf (types.either types.package types.attrs);
type = types.listOf (types.either types.package pkgSpec);
};
sane.packages.enableConsolePkgs = mkOption {
default = false;
@@ -265,12 +300,16 @@ in
++ (if cfg.enableGuiPkgs then guiPkgs else [])
++ (if cfg.enableDevPkgs then devPkgs else [])
;
type = types.listOf (types.either types.package types.attrs);
type = types.listOf (types.coercedTo types.package (p: { pkg = p; }) pkgSpec);
description = "generated from other config options";
};
};
config = {
environment.systemPackages = mkIf cfg.enableSystemPkgs systemPkgs;
sane.impermanence.dirs.home.plaintext = concatLists (map (p: p.dir) cfg.enabledUserPkgs);
sane.impermanence.dirs.home.private = concatLists (map (p: p.private) cfg.enabledUserPkgs);
# XXX: this might not be necessary. try removing this and cacert.unbundled?
environment.etc."ssl/certs".source = mkIf cfg.enableSystemPkgs "${pkgs.cacert.unbundled}/etc/ssl/certs/*";
};
}

View File

@@ -2,6 +2,9 @@
{
imports = [
./duplicity.nix
./dyn-dns.nix
./kiwix-serve.nix
./nixserve.nix
./trust-dns.nix
];
}

View File

@@ -15,7 +15,8 @@ in
config = mkIf cfg.enable {
# we need this mostly because of the size of duplicity's cache
sane.impermanence.service-dirs = [ "/var/lib/duplicity" ];
# TODO: move to cryptClearOnBoot and update perms
sane.impermanence.dirs.sys.plaintext = [ "/var/lib/duplicity" ];
services.duplicity.enable = true;
services.duplicity.targetUrl = "$DUPLICITY_URL";

View File

@@ -0,0 +1,91 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.sane.services.dyn-dns;
in
{
options = {
sane.services.dyn-dns = {
enable = mkOption {
default = false;
type = types.bool;
};
ipPath = mkOption {
default = "/var/lib/uninsane/wan.txt";
type = types.str;
description = "where to store the latest WAN IPv4 address";
};
ipCmd = mkOption {
default = "${pkgs.sane-scripts}/bin/sane-ip-check-router-wan";
type = types.path;
description = "command to run to query the current WAN IP";
};
interval = mkOption {
type = types.str;
default = "10min";
description = "systemd time string for how frequently to re-check the IP";
};
restartOnChange = mkOption {
type = types.listOf types.str;
default = [];
description = "list of systemd unit files to restart when the IP changes";
};
};
};
config = mkIf cfg.enable {
systemd.services.dyn-dns = {
description = "update this host's record of its WAN IP";
serviceConfig.Type = "oneshot";
restartTriggers = [(builtins.toJSON cfg)];
after = [ "network.target" ];
wantedBy = cfg.restartOnChange;
before = cfg.restartOnChange;
script = ''
mkdir -p "$(dirname '${cfg.ipPath}')"
newIp=$(${cfg.ipCmd})
oldIp=$(cat '${cfg.ipPath}' || true)
# systemd path units are triggered on any file write action,
# regardless of content change. so only update the file if our IP *actually* changed
if [ "$newIp" != "$oldIp" ]
then
echo "$newIp" > '${cfg.ipPath}'
echo "WAN ip changed $oldIp -> $newIp"
fi
exit $(test -f '${cfg.ipPath}')
'';
};
systemd.timers.dyn-dns = {
# if anything wants dyn-dns.service, they surely want the timer too.
wantedBy = [ "dyn-dns.service" ];
timerConfig = {
OnUnitActiveSec = cfg.interval;
};
};
systemd.paths.dyn-dns-watcher = {
before = [ "dyn-dns.timer" ];
wantedBy = [ "dyn-dns.timer" ];
pathConfig = {
Unit = "dyn-dns-reactor.service";
PathChanged = [ cfg.ipPath ];
};
};
systemd.services.dyn-dns-reactor = {
description = "react to the system's WAN IP changing";
serviceConfig.Type = "oneshot";
script = if cfg.restartOnChange != [] then ''
${pkgs.systemd}/bin/systemctl restart ${toString cfg.restartOnChange}
'' else "${pkgs.coreutils}/bin/true";
};
};
}

View File

@@ -0,0 +1,55 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.sane.services.kiwix-serve;
in
{
options = {
sane.services.kiwix-serve = {
enable = mkOption {
default = false;
type = types.bool;
};
package = mkOption {
type = types.package;
default = pkgs.kiwix-tools;
defaultText = literalExpression "pkgs.kiwix-tools";
description = lib.mdDoc ''
The package that provides `bin/kiwix-serve`.
'';
};
port = mkOption {
type = types.port;
default = 80;
description = lib.mdDoc "Port number to listen on.";
};
listenAddress = mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
IP address to listen on. Listens on all available addresses if unspecified.
'';
};
zimPaths = mkOption {
type = types.nonEmptyListOf (types.either types.str types.path);
description = lib.mdDoc "ZIM file path(s)";
};
};
};
config = mkIf cfg.enable {
systemd.services.kiwix-serve = {
description = "Deliver ZIM file(s) articles via HTTP";
serviceConfig = let
maybeListenAddress = lib.optionals (cfg.listenAddress != null) ["-l" cfg.listenAddress];
args = maybeListenAddress ++ ["-p" cfg.port] ++ cfg.zimPaths;
in {
ExecStart = "${cfg.package}/bin/kiwix-serve ${lib.escapeShellArgs args}";
Type = "simple";
};
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
};
};
}

View File

@@ -0,0 +1,177 @@
{ config, lib, pkgs, ... }:
# TODO: consider using this library for .zone file generation:
# - <https://github.com/kirelagin/dns.nix>
with lib;
let
cfg = config.sane.services.trust-dns;
toml = pkgs.formats.toml { };
fmtRecord = proto: rrtype: name: value: "${name}\t${proto}\t${rrtype}\t${value}";
fmtRecordList = proto: rrtype: name: values: concatStringsSep
"\n"
(map (fmtRecord proto rrtype name) values)
;
fmtRecordAttrs = proto: rrtype: rrAttrs:
concatStringsSep
"\n"
(
attrValues (
mapAttrs
(name: fmtRecordList proto rrtype name)
rrAttrs
)
);
fmtIncludes = paths: concatStringsSep
"\n"
(map (path: "$INCLUDE ${path}") paths);
genZone = zcfg: ''
$TTL ${toString zcfg.TTL}
${fmtRecordAttrs "IN" "SOA" zcfg.inet.SOA}
${fmtRecordAttrs "IN" "A" zcfg.inet.A}
${fmtRecordAttrs "IN" "CNAME" zcfg.inet.CNAME}
${fmtRecordAttrs "IN" "MX" zcfg.inet.MX}
${fmtRecordAttrs "IN" "NS" zcfg.inet.NS}
${fmtRecordAttrs "IN" "SRV" zcfg.inet.SRV}
${fmtRecordAttrs "IN" "TXT" zcfg.inet.TXT}
${fmtIncludes zcfg.include}
${zcfg.extraConfig}
'';
configFile = toml.generate "trust-dns.toml" {
listen_addrs_ipv4 = cfg.listenAddrsIPv4;
zones = attrValues (
mapAttrs (zname: zcfg: rec {
zone = if zcfg.name == null then zname else zcfg.name;
zone_type = "Primary";
file = if zcfg.file == null then
pkgs.writeText "${zone}.zone" (genZone zcfg)
else
zcfg.file;
}) cfg.zones
);
};
# (listOf ty) type which also accepts single-assignment of `ty`.
# it's used to allow the user to write:
# CNAME."foo" = "bar";
# as shorthand for
# CNAME."foo" = [ "bar" ];
listOrUnit = ty: types.coercedTo ty (elem: [ elem ]) (types.listOf ty);
in
{
options = {
sane.services.trust-dns = {
enable = mkOption {
default = false;
type = types.bool;
};
listenAddrsIPv4 = mkOption {
type = types.listOf types.str;
default = [];
description = "array of ipv4 addresses on which to listen for DNS queries";
};
# reference <nixpkgs:nixos/modules/services/web-servers/nginx/vhost-options.nix>
zones = mkOption {
type = types.attrsOf (types.submodule {
options = {
name = mkOption {
type = types.nullOr types.str;
description = "zone name. defaults to the attribute name in zones";
default = null;
};
TTL = mkOption {
type = types.int;
description = "default TTL";
default = 3600;
};
include = mkOption {
type = types.listOf types.str;
description = "paths of other zone files to $INCLUDE into this one";
default = [];
};
extraConfig = mkOption {
type = types.lines;
description = "extra lines to append to the zone file";
default = "";
};
inet = {
SOA = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "Start of Authority record(s)";
default = {};
};
A = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "IPv4 address record(s)";
default = {};
};
CNAME = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "canonical name record(s)";
default = {};
};
MX = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "mail exchanger record(s)";
default = {};
};
NS = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "name server record(s)";
default = {};
};
SRV = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "service record(s)";
default = {};
};
TXT = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "text record(s)";
default = {};
};
};
file = mkOption {
type = types.nullOr types.str;
default = null;
description = "instead of using the generated zone file, use the specified path";
};
};
});
default = {};
description = "Declarative zone config";
};
generatedZones = mkOption {
type = types.attrsOf types.str;
description = "generated zone text for each zone";
};
};
};
config = mkIf cfg.enable {
sane.services.trust-dns.generatedZones = mapAttrs (zone: zcfg: genZone zcfg) cfg.zones;
networking.firewall.allowedTCPPorts = [ 53 ];
networking.firewall.allowedUDPPorts = [ 53 ];
systemd.services.trust-dns = {
description = "trust-dns DNS server";
serviceConfig = {
ExecStart = ''
${pkgs.trust-dns}/bin/named \
--config ${configFile} \
--zonedir /
'';
Type = "simple";
Restart = "on-failure";
RestartSec = "10s";
# TODO: hardening (like, don't run as root!)
};
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
};
};
}

32
modules/sops.nix Normal file
View File

@@ -0,0 +1,32 @@
{ config, lib, ... }:
let
# taken from sops-nix code: checks if any secrets are needed to create /etc/shadow
secrets-for-users = (lib.filterAttrs (_: v: v.neededForUsers) config.sops.secrets) != {};
sops-files = config.sops.age.sshKeyPaths ++ config.sops.gnupg.sshKeyPaths ++ [ config.sops.age.keyFile ];
keys-in-etc = builtins.any (p: builtins.substring 0 5 p == "/etc/") sops-files;
in
{
config = lib.mkIf (secrets-for-users && keys-in-etc) {
# secret decoding depends on keys in /etc/ (like the ssh host key), so make sure those are present.
system.activationScripts.setupSecretsForUsers = lib.mkIf secrets-for-users {
deps = [ "etc" ];
};
# TODO: we should selectively remove "users" and "groups", but keep manually specified deps?
system.activationScripts.etc.deps = lib.mkForce [];
assertions = builtins.concatLists (builtins.attrValues (
builtins.mapAttrs
(path: value: [
{
assertion = (builtins.substring 0 1 value.user) == "+";
message = "non-numeric user for /etc/${path}: ${value.user} prevents early /etc linking";
}
{
assertion = (builtins.substring 0 1 value.group) == "+";
message = "non-numeric group for /etc/${path}: ${value.group} prevents early /etc linking";
}
])
config.environment.etc
));
};
}

View File

@@ -0,0 +1,26 @@
diff --git a/pkgs/tools/networking/i2p/default.nix b/pkgs/tools/networking/i2p/default.nix
index e835007fdc5..1406486c7d4 100644
--- a/pkgs/tools/networking/i2p/default.nix
+++ b/pkgs/tools/networking/i2p/default.nix
@@ -50,7 +50,7 @@ stdenv.mkDerivation rec {
binaryBytecode # source bundles dependencies as jars
];
license = licenses.gpl2;
- platforms = [ "x86_64-linux" "i686-linux" ];
+ platforms = [ "x86_64-linux" "i686-linux" "aarch64-linux" ];
maintainers = with maintainers; [ joelmo ];
};
}
diff --git a/pkgs/tools/system/java-service-wrapper/default.nix b/pkgs/tools/system/java-service-wrapper/default.nix
index 93d86a75d18..ab563085f93 100644
--- a/pkgs/tools/system/java-service-wrapper/default.nix
+++ b/pkgs/tools/system/java-service-wrapper/default.nix
@@ -44,7 +44,7 @@ stdenv.mkDerivation rec {
homepage = "https://wrapper.tanukisoftware.com/";
changelog = "https://wrapper.tanukisoftware.com/doc/english/release-notes.html#${version}";
license = licenses.gpl2Only;
- platforms = [ "x86_64-linux" "i686-linux" ];
+ platforms = [ "x86_64-linux" "i686-linux" "aarch64-linux" ];
maintainers = [ maintainers.suhr ];
};
}

View File

@@ -1,4 +1,4 @@
fetchpatch: [
{ fakeHash, fetchpatch }: [
# librewolf: build with `MOZ_REQUIRE_SIGNING=false`
(fetchpatch {
url = "https://github.com/NixOS/nixpkgs/pull/199134.diff";
@@ -6,6 +6,21 @@ fetchpatch: [
sha256 = "sha256-Ne4hyHQDwBHUlWo8Z3QyRdmEv1rYGOjFGxSfOAcLUvQ=";
})
# trust-dns: init at 0.22.0
(fetchpatch {
# https://git.uninsane.org/colin/nixpkgs/compare/master...pr-trust-dns.diff
url = "https://git.uninsane.org/colin/nixpkgs/commit/feee7e0357a74ab0510b2d113a3bdede1d509759.diff";
sha256 = "sha256-t4sG+xLDaxbJ/mV5G18N4ag8EC3IXPgtN5FJGANh1Dc=";
})
# kiwix-tools: init at 3.4.0
(fetchpatch {
url = "https://github.com/NixOS/nixpkgs/pull/206254.diff";
sha256 = "sha256-Z4V9mOv4HYg3kDnWoYcxz3ch03I/1USrLjzlq4X9YqI=";
})
./2022-12-19-i2p-aarch64.patch
# # kaiteki: init at 2022-09-03
# vendorHash changes too frequently (might not be reproducible).
# using local package defn until stabilized

View File

@@ -1,10 +1,10 @@
{ stdenv, pkgs }:
{ stdenv, tow-boot-rp4, raspberrypifw, raspberrypi-armstubs }:
stdenv.mkDerivation rec {
pname = "bootpart-tow-boot-rpi-aarch64";
version = "1";
buildInputs = with pkgs; [
buildInputs = with [
tow-boot-rpi4 # for Tow-Boot.*.bin
raspberrypifw # for bootcode.bin, *.dat, *.elf, *.dtb
raspberrypi-armstubs # for armstub*
@@ -15,7 +15,7 @@ stdenv.mkDerivation rec {
dontUnpack = true;
installPhase = with pkgs; ''
installPhase = ''
mkdir "$out"
cp ${tow-boot-rpi4}/Tow-Boot.noenv.*.bin "$out"/
cp -R ${raspberrypifw}/share/raspberrypi/boot/*.dtb "$out"/

View File

@@ -1,10 +1,14 @@
{ stdenv, pkgs }:
{ stdenv
, ubootRaspberryPi4_64bit
, raspberrypifw
, raspberrypi-armstubs
}:
stdenv.mkDerivation rec {
pname = "bootpart-u-boot-rpi-aarch64";
version = "1";
buildInputs = with pkgs; [
buildInputs = [
ubootRaspberryPi4_64bit
raspberrypifw # for bootcode.bin, *.dat, *.elf, *.dtb
raspberrypi-armstubs # for armstub*
@@ -15,7 +19,7 @@ stdenv.mkDerivation rec {
dontUnpack = true;
installPhase = with pkgs; ''
installPhase = ''
mkdir "$out"
cp ${ubootRaspberryPi4_64bit}/u-boot.bin "$out"/
cp ${ubootRaspberryPi4_64bit}/*.dtb "$out"/

View File

@@ -1,14 +1,14 @@
{ stdenv, pkgs }:
{ stdenv, syslinux }:
stdenv.mkDerivation rec {
pname = "bootpart-uefi-x86_64";
version = "1";
buildInputs = [ pkgs.syslinux ];
buildInputs = [ syslinux ];
dontUnpack = true;
installPhase = with pkgs; ''
installPhase = ''
# populate the EFI directory with syslinux, and configure it to read that extlinux.conf file managed by nixos
mkdir -p "$out/EFI/syslinux" "$out/EFI/BOOT" "$out/syslinux"
cp -R "${syslinux}/share/syslinux/efi64"/* "$out/EFI/syslinux"

View File

@@ -1,8 +1,8 @@
{ pkgs
{ lib
, browserpass
, bash
, fetchFromGitea
, gnused
, lib
, sane-scripts
, sops
, stdenv
@@ -26,7 +26,7 @@ let
};
in
(pkgs.browserpass.overrideAttrs (upstream: {
(browserpass.overrideAttrs (upstream: {
src = fetchFromGitea {
domain = "git.uninsane.org";
owner = "colin";

View File

@@ -1,6 +1,6 @@
{ pkgs }:
{ firefox-unwrapped }:
(pkgs.firefox-unwrapped.overrideAttrs (upstream: {
(firefox-unwrapped.overrideAttrs (upstream: {
# NB: firefox takes about 1hr to build on my 24-thread ryzen desktop
patches = (upstream.patches or []) ++ [
# see https://gitlab.com/librewolf-community/browser/source/-/blob/main/patches/sed-patches/allow-searchengines-non-esr.patch

View File

@@ -1,9 +1,9 @@
{ pkgs }:
{ symlinkJoin, fluffychat, makeWrapper }:
(pkgs.symlinkJoin {
(symlinkJoin {
name = "fluffychat-moby";
paths = [ pkgs.fluffychat ];
buildInputs = [ pkgs.makeWrapper ];
paths = [ fluffychat ];
buildInputs = [ makeWrapper ];
# ordinary fluffychat on moby displays blank window;
# > Failed to start Flutter renderer: Unable to create a GL context

View File

@@ -0,0 +1,18 @@
{ fractal-next, fetchFromGitLab, rustPlatform }:
(fractal-next.overrideAttrs (prev: rec {
version = "20221220";
src = fetchFromGitLab {
domain = "gitlab.gnome.org";
owner = "GNOME";
repo = "fractal";
rev = "d394badd0bf36c43026e01dbeb1cd7f881cf3440";
hash = "sha256-JgLrDrMLEh7302tZ3NOJ12dCMiSxGgecaUjcuDPcGSg=";
};
patches = [];
postPatch = "";
cargoDeps = rustPlatform.fetchCargoTarball {
inherit src;
hash = "sha256-hHsMcU6s42yKn2+LkWraLDhnmWNY72dL2cJoy6uoOKI=";
};
}))

View File

@@ -1,15 +1,20 @@
{ pkgs, lib, ... }:
{ fuse, gocryptfs, util-linux, lib }:
(pkgs.gocryptfs.overrideAttrs (upstream: {
(gocryptfs.overrideAttrs (upstream: {
# XXX `su colin` hangs when pam_mount tries to mount a gocryptfs system
# unless `logger` (util-linux) is accessible from gocryptfs.
# this is surprising: the code LOOKS like it's meant to handle logging failures.
# propagating util-linux through either `environment.systemPackages` or `security.pam.mount.additionalSearchPaths` DOES NOT WORK.
#
# TODO: see about upstreaming this
#
# additionally, we need /run/wrappers/bin EXPLICITLY in PATH, for when we run not as root.
# but we want to keep `fuse` for when we ARE running as root -- particularly during an activation script BEFORE the wrappers exist.
postInstall = ''
wrapProgram $out/bin/gocryptfs \
--suffix PATH : ${lib.makeBinPath [ pkgs.fuse pkgs.util-linux ]}
--suffix PATH : ${lib.makeBinPath [ util-linux ]} \
--suffix PATH : /run/wrappers/bin \
--suffix PATH : ${lib.makeBinPath [ fuse ]}
ln -s $out/bin/gocryptfs $out/bin/mount.fuse.gocryptfs
'';
}))

View File

@@ -1,12 +1,14 @@
{ pkgs
{ makeWrapper
, gpodder
, symlinkJoin
, writeShellScript
, config
}:
(pkgs.symlinkJoin {
(symlinkJoin {
name = "gpodder-configured";
paths = [ pkgs.gpodder ];
buildInputs = [ pkgs.makeWrapper ];
paths = [ gpodder ];
buildInputs = [ makeWrapper ];
# gpodder keeps all its feeds in a sqlite3 database.
# we can configure the feeds externally by wrapping gpodder and just instructing it to import

View File

@@ -1,6 +1,6 @@
{ pkgs }:
{ jackett }:
(pkgs.jackett.overrideAttrs (upstream: {
(jackett.overrideAttrs (upstream: {
# 2022-07-29: check phase segfaults on arm (with or without my patches)
doCheck = false;
patches = (upstream.patches or []) ++ [

View File

@@ -3,14 +3,14 @@
sane-scripts = prev.callPackage ./sane-scripts { };
tow-boot-pinephone = prev.callPackage ./tow-boot-pinephone { };
tow-boot-rpi4 = prev.callPackage ./tow-boot-rpi4 { };
bootpart-uefi-x86_64 = prev.callPackage ./bootpart-uefi-x86_64 { pkgs = prev; };
bootpart-uefi-x86_64 = prev.callPackage ./bootpart-uefi-x86_64 { };
bootpart-tow-boot-rpi-aarch64 = prev.callPackage ./bootpart-tow-boot-rpi-aarch64 {
# not sure why i can't just do pkgs = next here
pkgs = prev // { inherit tow-boot-rpi4; };
# not sure why i can't just do `next.callPackage` instead
inherit tow-boot-rpi4;
};
bootpart-u-boot-rpi-aarch64 = prev.callPackage ./bootpart-u-boot-rpi-aarch64 {
# not sure why i can't just do pkgs = next here
pkgs = prev // { inherit ubootRaspberryPi4_64bit; };
# not sure why i can't just do `next.callPackage` instead
inherit ubootRaspberryPi4_64bit;
};
rtl8723cs-firmware = prev.callPackage ./rtl8723cs-firmware { };
linux-megous = prev.callPackage ./linux-megous {
@@ -23,28 +23,33 @@
sublime-music-mobile = prev.callPackage ./sublime-music-mobile { };
#### customized packages
fluffychat-moby = prev.callPackage ./fluffychat-moby { pkgs = prev; };
gpodder-configured = prev.callPackage ./gpodder-configured { pkgs = prev; };
fluffychat-moby = prev.callPackage ./fluffychat-moby { };
gpodder-configured = prev.callPackage ./gpodder-configured { };
# nixos-unstable pleroma is too far out-of-date for our db
pleroma = prev.callPackage ./pleroma { };
# jackett doesn't allow customization of the bind address: this will probably always be here.
jackett = prev.callPackage ./jackett { pkgs = prev; };
jackett = prev.callPackage ./jackett { inherit (prev) jackett; };
# mozilla keeps nerfing itself and removing configuration options
firefox-unwrapped = prev.callPackage ./firefox-unwrapped { pkgs = prev; };
firefox-unwrapped = prev.callPackage ./firefox-unwrapped { };
# patch rpi uboot with something that fixes USB HDD boot
ubootRaspberryPi4_64bit = prev.callPackage ./ubootRaspberryPi4_64bit { pkgs = prev; };
ubootRaspberryPi4_64bit = prev.callPackage ./ubootRaspberryPi4_64bit { };
gocryptfs = prev.callPackage ./gocryptfs { pkgs = prev; };
gocryptfs = prev.callPackage ./gocryptfs { inherit (prev) gocryptfs; };
browserpass = prev.callPackage ./browserpass { pkgs = prev; inherit sane-scripts; };
browserpass = prev.callPackage ./browserpass { inherit (prev) browserpass; inherit sane-scripts; };
fractal-latest = prev.callPackage ./fractal-latest { };
#### TEMPORARY: PACKAGES WAITING TO BE UPSTREAMED
kaiteki = prev.callPackage ./kaiteki { };
lightdm-mobile-greeter = prev.callPackage ./lightdm-mobile-greeter { pkgs = next; };
lightdm-mobile-greeter = prev.callPackage ./lightdm-mobile-greeter { };
browserpass-extension = prev.callPackage ./browserpass-extension { };
gopass-native-messaging-host = prev.callPackage ./gopass-native-messaging-host { };
tokodon = prev.libsForQt5.callPackage ./tokodon { };
signaldctl = prev.callPackage ./signaldctl { };
splatmoji = prev.callPackage ./splatmoji { };
# trust-dns = prev.callPackage ./trust-dns { };
# kaiteki = prev.kaiteki;
})

View File

@@ -18,24 +18,34 @@ resholve.mkDerivation {
scripts = [ "bin/*" ];
interpreter = "${pkgs.bash}/bin/bash";
inputs = with pkgs; [
coreutils
# string is interpreted as relative path from @OUT@.
# this lets our scripts reference eachother.
# see: <https://github.com/abathur/resholve/issues/26>
"bin"
coreutils-full
curl
duplicity
file
findutils
git
gnugrep
gnused
gocryptfs
ifuse
inetutils
inotify-tools
iwd
jq
ncurses
oath-toolkit
openssh
openssl
rmlint
rsync
ssh-to-age
sops
sudo
systemd
util-linux
which
];
@@ -45,37 +55,52 @@ resholve.mkDerivation {
"/tmp/rmlint.sh" = true;
# intentionally escapes (into user code)
"$external_cmd" = true;
"$maybe_sudo" = true;
};
fake = {
external = [
# https://github.com/abathur/resholve/issues/29
"umount"
# "umount"
# "/run/wrappers/bin/sudo"
"sudo"
# these are used internally; probably a better fix
"sane-mount-servo"
"sane-private-unlock"
];
};
fix = {
# this replaces umount with the non-setuid-wrapper umount.
# not sure if/where that lack of suid causes problems.
umount = true;
};
prologue = "bin/sane-resholve-prologue";
# list of programs which *can* or *cannot* exec their arguments
execer = with pkgs; [
"cannot:${duplicity}/bin/duplicity"
"cannot:${git}/bin/git"
"cannot:${gocryptfs}/bin/gocryptfs"
"cannot:${ifuse}/bin/ifuse"
"cannot:${iwd}/bin/iwctl"
"cannot:${oath-toolkit}/bin/oathtool"
"cannot:${openssh}/bin/ssh-keygen"
"cannot:${rmlint}/bin/rmlint"
"cannot:${rsync}/bin/rsync"
"cannot:${sops}/bin/sops"
"cannot:${ssh-to-age}/bin/ssh-to-age"
"cannot:${systemd}/bin/systemctl"
];
};
};
patchPhase = ''
# remove python scripts
# TODO: figure out how to make resholve process only shell scripts
rm sane-bt-search
rm sane-date-math
rm sane-reclaim-boot-space
'';
installPhase = ''
mkdir -p "$out/bin"
cp -R * "$out"/bin/
mkdir -p $out/bin
cp -R * $out/bin/
'';
meta = {

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python
"""
usage: sane-bt-search <query_string>
searches Jackett for torrent files matching the title.
returns select results and magnet links
"""
from dataclasses import dataclass
from datetime import datetime
import natsort
import requests
import sys
import time
SERVICE = "https://jackett.uninsane.org"
ENDPOINTS = dict(
results="api/v2.0/indexers/all/results"
)
@dataclass(eq=True, order=True)
class Torrent:
seeders: int
pub_date: datetime
title: str
magnet: str
def __str__(self) -> str:
return f"{self.seeders}[S]\t{self.pub_date}\t{self.title}\t{self.magnet}"
@staticmethod
def from_dict(d: dict) -> 'Torrent':
seeders = d.get("Seeders")
pub_date = d.get("PublishDate")
title = d.get("Title")
magnet = d.get("MagnetUri")
if seeders is not None and pub_date is not None and title is not None and magnet is not None:
pub_date = datetime.fromisoformat(pub_date)
return Torrent(seeders, pub_date, title, magnet)
class Client:
def __init__(self):
self.apikey = open("/run/secrets/jackett_apikey").read()
def api_call(self, method: str, params: dict) -> dict:
endpoint = ENDPOINTS[method]
url = f"{SERVICE}/{endpoint}"
params = params.copy()
params.update(apikey=self.apikey, _=str(int(time.time())))
print(url)
print(params)
resp = requests.get(url, params=params)
print(resp)
return resp.json()
def query(self, q: str) -> list:
torrents = []
api_res = self.api_call("results", dict(Query=q))
for r in api_res["Results"]:
t = Torrent.from_dict(r)
if t is not None:
torrents.append(t)
return sorted(torrents, reverse=True)
q = " ".join(sys.argv[1:])
client = Client()
res = client.query(q)
for r in res[:5]:
print(r)

View File

@@ -0,0 +1,347 @@
#!/usr/bin/env python3
# i just went overboard playing around with parsers, is all.
# use this like `./sane-date-math 'today - 5d'`
# of course, it handles parentheses and operator precedence/associativity, so you can do sillier things like
# `./sane-date-math ' today - (1+3 *4 - ((0)) ) *7d '`
import abc
from datetime import datetime, timedelta
import sys
class Token:
def __init__(self, c: str):
self.c = c
def __repr__(self) -> str:
return f"{self.c!r}"
def __str__(self) -> str:
return self.c
def __eq__(self, other: 'Token') -> bool:
return self.c == other.c
PLUS = Token('+')
MINUS = Token('-')
ASTERISK = Token('*')
SPACE = Token(' ')
OPEN_PAREN = Token('(')
CLOSE_PAREN = Token(')')
UNDERSCORE = Token('_')
DIGITS = [Token(c) for c in '0123456789']
ALPHA_LOWER = [Token(c) for c in 'abcdefghijklmnopqrstuvwxyz']
ALPHA_UPPER = [Token(t.c.upper()) for t in ALPHA_LOWER]
ALPHA = ALPHA_LOWER + ALPHA_UPPER
ALPHA_UNDER = ALPHA + [UNDERSCORE]
ALPHA_NUM_UNDER = ALPHA_UNDER + DIGITS
class ParserContext:
def feed(self, token: Token) -> 'ParserContext':
return None # can't ingest the token
def upgrade(self) -> 'ParserContext':
return None # no upgrade path
class Parser:
"""
LR parser.
keeps exactly one root item, and for each input token
feeds it to the root, possibly "upgrading" the root N times
before it's able to be fed.
"""
def __init__(self, root: ParserContext):
self.root = root
def feed(self, token: Token) -> bool:
new_root = self.root.feed(token)
if new_root is not None:
self.root = new_root
return True
else:
# root can't directly accept this item.
# "upgrade" it and try again.
new_root = self.root.upgrade()
if new_root is None: return False
self.root = new_root
return self.feed(token)
def complete(self) -> ParserContext:
# upgrade the root as far as possible before returning
root = None
new_root = self.root
while new_root is not None:
root = new_root
new_root = root.upgrade()
return root
class ReprParserContext(ParserContext):
""" helper that gives a good default repr to most contexts """
def __init__(self, items: list = None):
self.items = items if items is not None else []
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.items!r})'
class BaseContext(ReprParserContext):
""" empty context; initial state of the parser """
def feed(self, token: Token) -> ParserContext:
if token == SPACE:
return self
if token == OPEN_PAREN:
return ParenContext(BaseContext())
if token in DIGITS:
return IntegerContext([token])
if token in ALPHA_UNDER:
return IdentifierContext([token])
class IdentifierContext(ReprParserContext):
""" context is an identifier like `today` """
def __init__(self, tokens: list):
super().__init__(tokens)
self.tokens = tokens
def feed(self, token: Token) -> ParserContext:
if token in ALPHA_NUM_UNDER:
return IdentifierContext(self.tokens + [token])
def upgrade(self) -> ParserContext:
return StrongValueContext(self)
class IntegerContext(ReprParserContext):
""" context is an integer like `45` """
def __init__(self, tokens: list):
super().__init__(tokens)
self.tokens = tokens
def feed(self, token: Token) -> ParserContext:
if token in DIGITS:
return IntegerContext(self.tokens + [token])
if token == Token('d'):
return DurationContext(self)
def upgrade(self) -> ParserContext:
# can't continue the integer; it becomes a value
return StrongValueContext(self)
class DurationContext(ReprParserContext):
""" context is a duration like `14d` """
def __init__(self, value: IntegerContext):
super().__init__([value])
self.value = value
def upgrade(self) -> ParserContext:
return StrongValueContext(self)
class BaseValueContext(ReprParserContext):
""" abstract base for types that can be used in compound expressions """
def __init__(self, value: ParserContext):
super().__init__([value])
self.value = value
def feed(self, token: Token) -> ParserContext:
if token == SPACE:
return self
class StrongValueContext(BaseValueContext):
"""
in the context of operators, a strong value is something which prefers
to not be grabbed by a lhs value.
so for example, strong values have the opportunity to initiate a multiply operation before the lhs closes an addition operation that this strong value is a part of
"""
def feed(self, token: Token) -> ParserContext:
if token == ASTERISK:
return BinaryOpContext(self, token, BaseContext())
return super().feed(token)
def upgrade(self) -> ParserContext:
return WeakValueContext(self.value)
class WeakValueContext(BaseValueContext):
def feed(self, token: Token) -> ParserContext:
if token == PLUS:
return BinaryOpContext(self, token, BaseContext())
if token == MINUS:
return BinaryOpContext(self, token, BaseContext())
return super().feed(token)
class BinaryOpContext(ReprParserContext):
""" context for a binary operation. the LHS and operator are parsed, but the rhs may not yet contain a value """
def __init__(self, lhs: BaseValueContext, oper: Token, rhs: ParserContext):
super().__init__([lhs, oper, rhs])
self.lhs = lhs
self.oper = oper
self.rhs = rhs
@property
def precedence_class(self) -> type:
if self.oper in [PLUS, MINUS]:
return WeakValueContext
if self.oper == ASTERISK:
return StrongValueContext
def feed(self, token: Token) -> ParserContext:
new_rhs = self.rhs.feed(token)
if new_rhs is not None:
return BinaryOpContext(self.lhs, self.oper, new_rhs)
def upgrade(self) -> ParserContext:
new_rhs = self.rhs.upgrade()
if new_rhs is None: return None
# upgrade self once the rhs has reach the required precedence compatible with this operator
new_self = BinaryOpContext(self.lhs, self.oper, new_rhs)
if isinstance(new_rhs, self.precedence_class):
return StrongValueContext(self) # close the operation
return new_self
class ParenContext(ReprParserContext):
""" context for a value contained within parentheses """
def __init__(self, inner: ParserContext):
super().__init__([inner])
self.inner = inner
def feed(self, token: Token) -> ParserContext:
new_inner = self.inner.feed(token)
if new_inner is not None:
return ParenContext(new_inner)
if token == CLOSE_PAREN and isinstance(self.inner, WeakValueContext):
return StrongValueContext(self)
def upgrade(self) -> ParserContext:
new_inner = self.inner.upgrade()
if new_inner is not None:
return ParenContext(new_inner)
## AstItems are produced from a ParserContext input
## ParserContext parse outputs are translated into `AstItem`s before evaluation
## so that we can operate on a higher-level tree that directly encodes native values like integers
class AstItem(metaclass=abc.ABCMeta):
@abc.abstractmethod
def eval(self, context: dict):
pass
@staticmethod
def decode_item(p: ParserContext) -> 'AstItem':
if isinstance(p, IntegerContext):
return Literal(AstItem.decode_integer(p))
if isinstance(p, DurationContext):
return Literal(timedelta(AstItem.decode_integer(p.value)))
if isinstance(p, IdentifierContext):
return Variable(AstItem.decode_identifier(p))
if isinstance(p, BaseValueContext):
return AstItem.decode_item(p.value)
if isinstance(p, BinaryOpContext):
return AstItem.decode_bin_op(
p.oper.c,
AstItem.decode_item(p.lhs),
AstItem.decode_item(p.rhs)
)
if isinstance(p, ParenContext):
return AstItem.decode_item(p.inner)
@staticmethod
def decode_integer(p: IntegerContext) -> int:
return int(''.join(t.c for t in p.tokens))
@staticmethod
def decode_identifier(p: IdentifierContext) -> str:
return ''.join(t.c for t in p.tokens)
@staticmethod
def decode_bin_op(ty: str, lhs: 'AstItem', rhs: 'AstItem') -> 'BinaryOp':
if ty == '+':
return AddOp(lhs, rhs)
if ty == '-':
return SubOp(lhs, rhs)
if ty == '*':
return MulOp(lhs, rhs)
class Literal(AstItem):
def __init__(self, v):
self.v = v
def __str__(self) -> str:
return str(self.v)
def eval(self, context: dict):
return self.v
class Variable(AstItem):
def __init__(self, name: str):
self.name = name
def __str__(self) -> str:
return self.name
def eval(self, context: dict):
return context[self.name]
class BinaryOp(AstItem):
def __init__(self, lhs, rhs):
self.lhs = lhs
self.rhs = rhs
class AddOp(BinaryOp):
def __str__(self):
return f"({self.lhs} + {self.rhs})"
def eval(self, context: dict):
return self.lhs.eval(context) + self.rhs.eval(context)
class SubOp(BinaryOp):
def __str__(self):
return f"({self.lhs} - {self.rhs})"
def eval(self, context: dict):
return self.lhs.eval(context) - self.rhs.eval(context)
class MulOp(BinaryOp):
def __str__(self):
return f"({self.lhs} * {self.rhs})"
def eval(self, context: dict):
return self.lhs.eval(context) * self.rhs.eval(context)
## toplevel routine. tokenize -> parse -> decode to AST -> evaluate
def tokenize(stream: str) -> list:
return [Token(char) for char in stream]
def parse(tokens: list) -> ParserContext:
parser = Parser(BaseContext())
for i, t in enumerate(tokens):
result = parser.feed(t)
# print(f"i={i}; t={t}; state: {ctx!r}")
assert result, f"unexpected token '{t}' at {i}; state: {parser.complete()!r}"
return parser.complete()
def evaluate(expr: str) -> object:
tok = tokenize(expr)
parse_tree = parse(tok)
print(parse_tree)
ast = AstItem.decode_item(parse_tree)
print(ast)
env = dict(
today=datetime.now()
)
return ast.eval(env)
if __name__ == '__main__':
expr = " ".join(sys.argv[1:])
print(evaluate(expr))

View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -x
# initialize a repository with each of my machines configured as remotes.
# it's assumed each machine stores the repo at the same fs path
path=$PWD
git init
git remote add desko "colin@desko:$path"
git remote add lappy "colin@lappy:$path"
git remote add moby "colin@moby:$path"
git remote add servo "colin@servo:$path"

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
curl https://ipinfo.io/ip
echo

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# query the WAN IP address OF MY ROUTER
# requires creds
passwd=$(sudo cat /run/secrets/router_passwd)
cookie=$(mktemp)
# authenticate
curl -s --insecure --cookie-jar $cookie \
--data "username=admin&password=$passwd" \
https://192.168.0.1
# query the WAN IP
curl -s --insecure --cookie $cookie \
-H "X-Requested-With: XMLHttpRequest" \
"https://192.168.0.1/cgi/cgi_action?Action=GetConnectionStatus" \
| jq -r .wan_status.ipaddr

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# reconnect to best wifi network.
# lappy frequently DCs from the ideal network
set -ex
sudo iwctl station wlan0 scan
sleep 5
# get networks. remove control characters (colors), then leading info, then take the top-rated network
networks=$(iwctl station wlan0 get-networks rssi-dbms | sed 's/\[[0-9;]*m//g' | sed 's/ [ >]*/ /g' | sed 's/^ //' | tail -n +5)
strengths=$(echo "$networks" | grep -o '\-[0-9][0-9]* *$')
best_strength=$(echo "$strengths" | sort -h | tail -n 1)
best_line=$(echo "$networks" | grep -- "$best_strength$")
# network names could have spaces in them if someone's evil, so rather than `cut`, we trim the `psk` and `db` columnds
best_network=$(echo "$best_line" | sed 's/ [a-z][a-z]* -[0-9][0-9]* *$//g')
sudo iwctl station wlan0 connect "$best_network"

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# unlock the ~/private store, run some command, and then re-lock the store
set -x
external_cmd=$@
sane-private-unlock
$external_cmd
exec sane-private-lock

View File

@@ -2,8 +2,7 @@
set -ex
# configure persistent, encrypted storage that is auto-mounted on login.
# this is a one-time setup and user should log out/back in after running it.
# mounts ~/private
mount=/home/colin/private
cipher="/nix/persist$mount"

View File

@@ -1,3 +1,10 @@
#!/usr/bin/env sh
# copy some remote file(s) to the working directory, with sane defaults
rsync -arv --progress --append-verify "$@" .
# rsync, with sane defaults
# + verbosity
# + default to cwd as destination if none is provided
dest=
if (( $# <= 1 )); then
# rsync to the current directory by default
dest='.'
fi
rsync -arv --progress --append-verify "$@" $dest

View File

@@ -0,0 +1,194 @@
#!/usr/bin/env python3
import os
import os.path
import subprocess
import sys
EXTLINUX_CONF = "/boot/extlinux/extlinux.conf"
class ConfItem:
pass
class ConfLine:
""" uninteresting line in the config """
def __init__(self, line: str):
self.line = line
def __str__(self) -> str:
return self.line
class ConfEntry:
""" boot entry, with label/linux/etc """
menu = linux = initrd = append = fdtdir = None
def __init__(self, label: str):
self.label = label
def format_attr(self, attr_name: str) -> str:
attr_val = getattr(self, attr_name)
assert attr_val is not None, f"not set: {attr_name}"
return f"{attr_name.upper()} {attr_val}"
def __str__(self) -> str:
return f"""
{self.format_attr("label")}
{self.format_attr("menu")}
{self.format_attr("linux")}
{self.format_attr("initrd")}
{self.format_attr("append")}
{self.format_attr("fdtdir")}
""".strip()
def parse(self, line: str) -> None:
split_at = line.index(" ")
directive, contents = line[:split_at], line[1+split_at:]
self.setattr(directive.lower(), contents)
def setattr(self, directive: str, contents: str) -> None:
assert getattr(self, directive) is None, f"attr already set: {directive} = {contents!r}"
setattr(self, directive, contents)
class UseTracker:
def __init__(self):
self.linux = []
self.initrd = []
self.fdtdir = []
self.sizes = {} # item: str -> [num_using, bytes]
@staticmethod
def from_items(entries: list) -> 'UseTracker':
me = UseTracker()
me.populate_from_fs()
for i in entries:
me.track(i)
return me
def populate_from_fs(self) -> None:
for entry in os.listdir("/boot/nixos"):
item = os.path.join("../nixos", entry)
self.sizes[item] = [0, self._get_size(item)]
def append_unique(self, list_: list, item: str) -> None:
if item not in list_:
list_.append(item)
self.sizes[item][0] += 1
def track(self, entry: ConfItem) -> None:
if isinstance(entry, ConfEntry):
self.append_unique(self.linux, entry.linux)
self.append_unique(self.initrd, entry.initrd)
self.append_unique(self.fdtdir, entry.fdtdir)
def get_use_count(self, item: str) -> int:
return self.sizes[item][0]
def get_size(self, item: str) -> int:
return self.sizes[item][1]
def used_size(self) -> int:
return sum(i[1] for i in self.sizes.values() if i[0] != 0)
def get_unused(self) -> list:
return [i for (i, v) in self.sizes.items() if v[0] == 0]
def unused_size(self) -> int:
return sum(self.get_size(i) for i in self.get_unused())
def _get_size(self, item: str) -> int:
path = os.path.join("/boot/extlinux", item)
du_output = subprocess.check_output(["du", "-b", "-c", path], text=True).strip()
last = du_output.split("\n")[-1]
size, label = last.split("\t")
assert label == "total", f"unexpected du output: {last}"
return int(size)
def print_use_by_cat(tracker: UseTracker, label: str) -> None:
items = getattr(tracker, label)
formatted_items = []
for item in items:
count, size = tracker.get_use_count(item), tracker.get_size(item)
formatted_items.append(f"\n {item}\n {count}x {size}")
print(f" {label}:{''.join(formatted_items)}")
def print_tracker_use(tracker: UseTracker) -> None:
print("in use:")
print_use_by_cat(tracker, "linux")
print_use_by_cat(tracker, "initrd")
print_use_by_cat(tracker, "fdtdir")
print("unused:")
for i in tracker.get_unused():
print(f" {i}\n {tracker.get_size(i)}")
print(f"used space: {tracker.used_size()}")
def delete_unused_from_disk(tracker: UseTracker) -> None:
for f in tracker.get_unused():
path = os.path.join("/boot/extlinux", f)
cmd = ["rm", "-r", "-f", path]
print(" ".join(cmd))
subprocess.check_output(cmd)
def parse_extlinux(contents: str) -> list:
items = []
active_entry = None
for line in contents.split("\n"):
if line.startswith("#") or line == "" or line.startswith("DEFAULT ") or line.startswith("MENU ") or line.startswith("TIMEOUT "):
items.append(ConfLine(line))
elif line.startswith("LABEL "):
items.append(ConfEntry(line[len("LABEL "):]))
elif line.startswith(" "):
items[-1].parse(line[2:])
else:
assert False, f"unknown directive {line!r}"
return items
def write_extlinux(contents: str) -> None:
with open(EXTLINUX_CONF, "r+") as new:
# backup file
with open("./extlinux.conf.back", "w") as back:
back.write(new.read())
new.seek(0)
new.write(new_extlinux)
new.truncate()
def dump_items(items: list) -> str:
return "\n".join(str(i) for i in items)
def prompt_continue() -> None:
if input("continue? [y/N] ").lower() != "y":
print("aborting")
sys.exit(0)
orig_extlinux = open(EXTLINUX_CONF, "r").read()
items = parse_extlinux(orig_extlinux)
tracker = UseTracker.from_items(items)
print_tracker_use(tracker)
print()
if tracker.get_unused():
print(f"recommended to delete unused items from disk to save {tracker.unused_size()}b")
prompt_continue()
else:
orig_tracker = tracker
rmcount = 0
while tracker.used_size() == orig_tracker.used_size():
item = items.pop()
rmcount += isinstance(item, ConfEntry)
tracker = UseTracker.from_items(items)
orig_size = orig_tracker.used_size()
new_size = tracker.used_size()
print(f"recommended to delete {rmcount} oldest entries to save {orig_size - new_size}b")
print_tracker_use(tracker)
prompt_continue()
print()
new_extlinux = dump_items(items)
print(f"new contents:\n{new_extlinux}")
prompt_continue()
write_extlinux(new_extlinux)
delete_unused_from_disk(tracker)

Some files were not shown because too many files have changed in this diff Show More