Compare commits
1 Commits
master
...
2024-08-12
Author | SHA1 | Date | |
---|---|---|---|
7a047702d5 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,5 +1,4 @@
|
|||||||
/build
|
.working
|
||||||
/.working
|
|
||||||
result
|
result
|
||||||
result-*
|
result-*
|
||||||
/secrets/local.nix
|
/secrets/local.nix
|
||||||
|
15
.sops.yaml
15
.sops.yaml
@@ -1,12 +1,10 @@
|
|||||||
keys:
|
keys:
|
||||||
- &user_desko_colin age1tnl4jfgacwkargzeqnhzernw29xx8mkv73xh6ufdyde6q7859slsnzf24x
|
- &user_desko_colin age1tnl4jfgacwkargzeqnhzernw29xx8mkv73xh6ufdyde6q7859slsnzf24x
|
||||||
- &user_flowy_colin age1nw3z25gn6l8gxneqw43tp8d2354c83d9sn3r0dqy5tapakdwhyvse0j2cc
|
|
||||||
- &user_lappy_colin age1j2pqnl8j0krdzk6npe93s4nnqrzwx978qrc0u570gzlamqpnje9sc8le2g
|
- &user_lappy_colin age1j2pqnl8j0krdzk6npe93s4nnqrzwx978qrc0u570gzlamqpnje9sc8le2g
|
||||||
- &user_servo_colin age1z8fauff34cdecr6sjkre260luzxcca05kpcwvhx988d306tpcejsp63znu
|
- &user_servo_colin age1z8fauff34cdecr6sjkre260luzxcca05kpcwvhx988d306tpcejsp63znu
|
||||||
- &user_moby_colin age1zsrsvd7j6l62fjxpfd2qnhqlk8wk4p8r0dtxpe4sdgnh2474095qdu7xj9
|
- &user_moby_colin age1zsrsvd7j6l62fjxpfd2qnhqlk8wk4p8r0dtxpe4sdgnh2474095qdu7xj9
|
||||||
- &host_crappy age1hl50ufuxnqy0jnk8fqeu4tclh4vte2xn2d59pxff0gun20vsmv5sp78chj
|
- &host_crappy age1hl50ufuxnqy0jnk8fqeu4tclh4vte2xn2d59pxff0gun20vsmv5sp78chj
|
||||||
- &host_desko age1vnw7lnfpdpjn62l3u5nyv5xt2c965k96p98kc43mcnyzpetrts9q54mc9v
|
- &host_desko age1vnw7lnfpdpjn62l3u5nyv5xt2c965k96p98kc43mcnyzpetrts9q54mc9v
|
||||||
- &host_flowy age1azm6carlm6tdjup37u5dr40585vjujajev70u4glwd9sv7swa99sk6mswx
|
|
||||||
- &host_lappy age1w7mectcjku6x3sd8plm8wkn2qfrhv9n6zhzlf329e2r2uycgke8qkf9dyn
|
- &host_lappy age1w7mectcjku6x3sd8plm8wkn2qfrhv9n6zhzlf329e2r2uycgke8qkf9dyn
|
||||||
- &host_servo age1tzlyex2z6t88tg9h82943e39shxhmqeyr7ywhlwpdjmyqsndv3qq27x0rf
|
- &host_servo age1tzlyex2z6t88tg9h82943e39shxhmqeyr7ywhlwpdjmyqsndv3qq27x0rf
|
||||||
- &host_moby age18vq5ktwgeaysucvw9t67drqmg5zd5c5k3le34yqxckkfj7wqdqgsd4ejmt
|
- &host_moby age18vq5ktwgeaysucvw9t67drqmg5zd5c5k3le34yqxckkfj7wqdqgsd4ejmt
|
||||||
@@ -15,13 +13,11 @@ creation_rules:
|
|||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *user_desko_colin
|
- *user_desko_colin
|
||||||
- *user_flowy_colin
|
|
||||||
- *user_lappy_colin
|
- *user_lappy_colin
|
||||||
- *user_servo_colin
|
- *user_servo_colin
|
||||||
- *user_moby_colin
|
- *user_moby_colin
|
||||||
- *host_crappy
|
- *host_crappy
|
||||||
- *host_desko
|
- *host_desko
|
||||||
- *host_flowy
|
|
||||||
- *host_lappy
|
- *host_lappy
|
||||||
- *host_servo
|
- *host_servo
|
||||||
- *host_moby
|
- *host_moby
|
||||||
@@ -29,7 +25,6 @@ creation_rules:
|
|||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *user_desko_colin
|
- *user_desko_colin
|
||||||
- *user_flowy_colin
|
|
||||||
- *user_lappy_colin
|
- *user_lappy_colin
|
||||||
- *user_servo_colin
|
- *user_servo_colin
|
||||||
- *host_servo
|
- *host_servo
|
||||||
@@ -37,28 +32,18 @@ creation_rules:
|
|||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *user_desko_colin
|
- *user_desko_colin
|
||||||
- *user_flowy_colin
|
|
||||||
- *user_lappy_colin
|
- *user_lappy_colin
|
||||||
- *host_desko
|
- *host_desko
|
||||||
- path_regex: secrets/flowy*
|
|
||||||
key_groups:
|
|
||||||
- age:
|
|
||||||
- *user_lappy_colin
|
|
||||||
- *user_flowy_colin
|
|
||||||
- *user_desko_colin
|
|
||||||
- *host_flowy
|
|
||||||
- path_regex: secrets/lappy*
|
- path_regex: secrets/lappy*
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *user_lappy_colin
|
- *user_lappy_colin
|
||||||
- *user_flowy_colin
|
|
||||||
- *user_desko_colin
|
- *user_desko_colin
|
||||||
- *host_lappy
|
- *host_lappy
|
||||||
- path_regex: secrets/moby*
|
- path_regex: secrets/moby*
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *user_desko_colin
|
- *user_desko_colin
|
||||||
- *user_flowy_colin
|
|
||||||
- *user_lappy_colin
|
- *user_lappy_colin
|
||||||
- *user_moby_colin
|
- *user_moby_colin
|
||||||
- *host_moby
|
- *host_moby
|
||||||
|
29
README.md
29
README.md
@@ -17,27 +17,24 @@ the only hard dependency for my exported pkgs/modules should be [nixpkgs][nixpkg
|
|||||||
building [hosts/](./hosts/) will require [sops][sops].
|
building [hosts/](./hosts/) will require [sops][sops].
|
||||||
|
|
||||||
you might specifically be interested in these files (elaborated further in #key-points-of-interest):
|
you might specifically be interested in these files (elaborated further in #key-points-of-interest):
|
||||||
- [my packages](./pkgs/by-name)
|
- ~~[`sxmo-utils`](./pkgs/additional/sxmo-utils/default.nix)~~
|
||||||
|
- these files will remain until my config settles down, but i no longer use or maintain SXMO.
|
||||||
- [my implementation of impermanence](./modules/persist/default.nix)
|
- [my implementation of impermanence](./modules/persist/default.nix)
|
||||||
- my way of deploying dotfiles/configuring programs per-user:
|
- my way of deploying dotfiles/configuring programs per-user:
|
||||||
- [modules/fs/](./modules/fs/default.nix)
|
- [modules/fs/](./modules/fs/default.nix)
|
||||||
- [modules/programs/](./modules/programs/default.nix)
|
- [modules/programs/](./modules/programs/default.nix)
|
||||||
- [modules/users/](./modules/users/default.nix)
|
- [modules/users/](./modules/users/default.nix)
|
||||||
|
|
||||||
if you find anything here genuinely useful, message me so that i can work to upstream it!
|
|
||||||
|
|
||||||
[nixpkgs]: https://github.com/NixOS/nixpkgs
|
[nixpkgs]: https://github.com/NixOS/nixpkgs
|
||||||
[sops]: https://github.com/Mic92/sops-nix
|
[sops]: https://github.com/Mic92/sops-nix
|
||||||
[uninsane-org]: https://uninsane.org
|
[uninsane-org]: https://uninsane.org
|
||||||
|
|
||||||
|
|
||||||
## Using This Repo In Your Own Config
|
## Using This Repo In Your Own Config
|
||||||
|
|
||||||
follow the instructions [here][NUR] to access my packages through the Nix User Repositories.
|
follow the instructions [here][NUR] to access my packages through the Nix User Repositories.
|
||||||
|
|
||||||
[NUR]: https://nur.nix-community.org/
|
[NUR]: https://nur.nix-community.org/
|
||||||
|
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
- `doc/`
|
- `doc/`
|
||||||
- instructions for tasks i find myself doing semi-occasionally in this repo.
|
- instructions for tasks i find myself doing semi-occasionally in this repo.
|
||||||
@@ -55,7 +52,7 @@ follow the instructions [here][NUR] to access my packages through the Nix User R
|
|||||||
- `pkgs/`
|
- `pkgs/`
|
||||||
- derivations for things not yet packaged in nixpkgs.
|
- derivations for things not yet packaged in nixpkgs.
|
||||||
- derivations for things from nixpkgs which i need to `override` for some reason.
|
- derivations for things from nixpkgs which i need to `override` for some reason.
|
||||||
- inline code for wholly custom packages (e.g. `pkgs/by-name/sane-scripts/` for CLI tools
|
- inline code for wholly custom packages (e.g. `pkgs/additional/sane-scripts/` for CLI tools
|
||||||
that are highly specific to my setup).
|
that are highly specific to my setup).
|
||||||
- `scripts/`
|
- `scripts/`
|
||||||
- scripts which aren't reachable on a deployed system, but may aid manual deployments.
|
- scripts which aren't reachable on a deployed system, but may aid manual deployments.
|
||||||
@@ -82,40 +79,44 @@ i.e. you might find value in using these in your own config:
|
|||||||
- populated with some statically-defined data
|
- populated with some statically-defined data
|
||||||
- populated according to some script
|
- populated according to some script
|
||||||
- created as a dependency of some service (e.g. `nginx`)
|
- created as a dependency of some service (e.g. `nginx`)
|
||||||
|
- values defined here are applied neither at evaluation time _nor_ at activation time.
|
||||||
|
- rather, they become systemd services.
|
||||||
|
- systemd manages dependencies
|
||||||
|
- e.g. link `/var/www -> /mnt/my-drive/www` only _after_ `/mnt/my-drive/www` appears)
|
||||||
- this is akin to using [Home Manager's][home-manager] file API -- the part which lets you
|
- this is akin to using [Home Manager's][home-manager] file API -- the part which lets you
|
||||||
statically define `~/.config` files -- just with a different philosophy.
|
statically define `~/.config` files -- just with a different philosophy.
|
||||||
namely, it avoids any custom activation scripts by leveraging `systemd-tmpfiles`.
|
|
||||||
- `modules/persist/`
|
- `modules/persist/`
|
||||||
- my implementation of impermanence, built atop the above `fs` module, with a few notable features:
|
- my alternative to the Impermanence module.
|
||||||
- no custom activation scripts or services (uses `systemd-tmpfiles` and `.mount` units)
|
- this builds atop `modules/fs/` to achieve things stock impermanence can't:
|
||||||
|
- persist things to encrypted storage which is unlocked at login time (pam_mount).
|
||||||
- "persist" cache directories -- to free up RAM -- but auto-wipe them on mount
|
- "persist" cache directories -- to free up RAM -- but auto-wipe them on mount
|
||||||
and encrypt them to ephemeral keys so they're unreadable post shutdown/unmount.
|
and encrypt them to ephemeral keys so they're unreadable post shutdown/unmount.
|
||||||
- persist to encrypted storage which is unlocked at login time.
|
|
||||||
- `modules/programs/`
|
- `modules/programs/`
|
||||||
- like nixpkgs' `programs` options, but allows both system-wide or per-user deployment.
|
- like nixpkgs' `programs` options, but allows both system-wide or per-user deployment.
|
||||||
- allows `fs` and `persist` config values to be gated behind program deployment:
|
- allows `fs` and `persist` config values to be gated behind program deployment:
|
||||||
- e.g. `/home/<user>/.mozilla/firefox` is persisted only for users who
|
- e.g. `/home/<user>/.mozilla/firefox` is persisted only for users who
|
||||||
`sane.programs.firefox.enableFor.user."<user>" = true;`
|
`sane.programs.firefox.enableFor.user."<user>" = true;`
|
||||||
- allows aggressive sandboxing any program:
|
- allows aggressive sandboxing any program:
|
||||||
- `sane.programs.firefox.sandbox.enable = true; # wraps the program so that it isolates itself into a new namespace when invoked`
|
- `sane.programs.firefox.sandbox.method = "bwrap"; # sandbox with bubblewrap`
|
||||||
- `sane.programs.firefox.sandbox.whitelistWayland = true; # allow it to render a wayland window`
|
- `sane.programs.firefox.sandbox.whitelistWayland = true; # allow it to render a wayland window`
|
||||||
- `sane.programs.firefox.sandbox.extraHomePaths = [ "Downloads" ]; # allow it read/write access to ~/Downloads`
|
- `sane.programs.firefox.sandbox.extraHomePaths = [ "Downloads" ]; # allow it read/write access to ~/Downloads`
|
||||||
- integrated with `fs` and `persist` modules so that programs' config files and persisted data stores are linked into the sandbox w/o any extra involvement.
|
- integrated with `fs` and `persist` modules so that programs' config files and persisted data stores are linked into the sandbox w/o any extra involvement.
|
||||||
- `modules/users/`
|
- `modules/users/`
|
||||||
- convenience layer atop the above modules so that you can just write
|
- convenience layer atop the above modules so that you can just write
|
||||||
`fs.".config/git"` instead of `fs."/home/colin/.config/git"`
|
`fs.".config/git"` instead of `fs."/home/colin/.config/git"`
|
||||||
- simplified `systemd.services` API
|
- per-user services managed by [s6-rc](https://www.skarnet.org/software/s6-rc/)
|
||||||
|
|
||||||
|
some things in here could easily find broader use. if you would find benefit in
|
||||||
|
them being factored out of my config, message me and we could work to make that happen.
|
||||||
|
|
||||||
[home-manager]: https://github.com/nix-community/home-manager
|
[home-manager]: https://github.com/nix-community/home-manager
|
||||||
|
|
||||||
|
|
||||||
## Mirrors
|
## Mirrors
|
||||||
|
|
||||||
this repo exists in a few known locations:
|
this repo exists in a few known locations:
|
||||||
- primary: <https://git.uninsane.org/colin/nix-files>
|
- primary: <https://git.uninsane.org/colin/nix-files>
|
||||||
- mirror: <https://github.com/nix-community/nur-combined/tree/master/repos/colinsane>
|
- mirror: <https://github.com/nix-community/nur-combined/tree/master/repos/colinsane>
|
||||||
|
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
if you want to contact me for questions, or collaborate to split something useful into a shared repo, etc,
|
if you want to contact me for questions, or collaborate to split something useful into a shared repo, etc,
|
||||||
|
149
TODO.md
149
TODO.md
@@ -1,15 +1,34 @@
|
|||||||
## BUGS
|
## BUGS
|
||||||
- alacritty Ctrl+N frequently fails to `cd` to the previous directory
|
|
||||||
- bunpen dbus sandboxing can't be *nested* (likely a problem in xdg-dbus-proxy)
|
|
||||||
- dissent has a memory leak (3G+ after 24hr)
|
|
||||||
- set a max memory use in the systemd service, to force it to restart as it leaks?
|
|
||||||
- `rmDbusServices` may break sandboxing
|
- `rmDbusServices` may break sandboxing
|
||||||
- e.g. if the package ships a systemd unit which references $out, then make-sandboxed won't properly update that unit.
|
- e.g. if the package ships a systemd unit which references $out, then make-sandboxed won't properly update that unit.
|
||||||
- `rmDbusServicesInPlace` is not affected
|
- `rmDbusServicesInPlace` is not affected
|
||||||
|
- when moby wlan is explicitly set down (via ip link set wlan0 down), /var/lib/hickory-dns/dhcp-configs doesn't get reset
|
||||||
|
- `ip monitor` can detect those manual link state changes (NM-dispatcher it seems cannot)
|
||||||
|
- or try dnsmasq?
|
||||||
|
- hickory-dns can't resolve `abs.twimg.com`
|
||||||
|
- hickory-dns can't resolve `social.kernel.org`
|
||||||
|
- hickory-dns can't resolve `pe.usps.com`
|
||||||
|
- hickory-dns can't resolve `social.seattle.wa.us`
|
||||||
|
- hickory-dns can't resolve `support.mozilla.org`
|
||||||
|
- sandbox: link cache means that if i update ~/.config/... files inline, sandboxed programs still see the old version
|
||||||
|
- mpv: continues to play past the end of some audio files
|
||||||
- mpv: audiocast has mpv sending its output to the builtin speakers unless manually changed
|
- mpv: audiocast has mpv sending its output to the builtin speakers unless manually changed
|
||||||
|
- `ssh` access doesn't grant same linux capabilities as login
|
||||||
- syshud (volume overlay): when casting with `blast`, syshud doesn't react to volume changes
|
- syshud (volume overlay): when casting with `blast`, syshud doesn't react to volume changes
|
||||||
|
- moby: after bringing the modem up, powering it down loses *complete* net connectivity (i.e. wlan is gone as well)
|
||||||
- dissent: if i launch it without net connectivity, it gets stuck at the login, and never tries again
|
- dissent: if i launch it without net connectivity, it gets stuck at the login, and never tries again
|
||||||
- newsflash on moby can't play videos
|
- calls: seems that it starts before net access, and then is forever disconnected (until i manually restart it)
|
||||||
|
- moby: kaslr is effectively disabled
|
||||||
|
- `dmesg | grep "KASLR disabled due to lack of seed"`
|
||||||
|
- fix by adding `kaslrseed` to uboot script before `booti`
|
||||||
|
- <https://github.com/armbian/build/pull/4352>
|
||||||
|
- not sure how that's supposed to work with tow-boot; maybe i should just update tow-boot
|
||||||
|
- moby: bpf is effectively disabled?
|
||||||
|
- `dmesg | grep 'systemd[1]: bpf-lsm: Failed to load BPF object: No such process'`
|
||||||
|
- `dmesg | grep 'hid_bpf: error while preloading HID BPF dispatcher: -22'`
|
||||||
|
- `s6` is not re-entrant
|
||||||
|
- so if the desktop crashes, the login process from `unl0kr` fails to re-launch the GUI
|
||||||
|
- newflash on moby can't play videos
|
||||||
- "open in browser" works though -- in mpv
|
- "open in browser" works though -- in mpv
|
||||||
- gnome-maps can't use geoclue *and* openstreetmap at the same time
|
- gnome-maps can't use geoclue *and* openstreetmap at the same time
|
||||||
- get gnome-maps to speak xdg-desktop-portal, and this will be fixed
|
- get gnome-maps to speak xdg-desktop-portal, and this will be fixed
|
||||||
@@ -17,50 +36,36 @@
|
|||||||
- see under "preferences", cookies are disabled
|
- see under "preferences", cookies are disabled
|
||||||
- prevents logging into websites (OpenStreetMap)
|
- prevents logging into websites (OpenStreetMap)
|
||||||
- works when sandbox is disabled
|
- works when sandbox is disabled
|
||||||
- rsync to ssh target fails because of restrictive sandboxing
|
|
||||||
- `/mnt/.servo_ftp` retries every 10s, endlessly, rather than doing a linear backoff
|
|
||||||
- repro by `systemctl stop sftpgo` on servo, then watching `mnt-.servo_ftp.{mount,timer}` on desko
|
|
||||||
- `ovpns` (and presumably `doof`) net namespaces aren't firewalled
|
|
||||||
- not great because things like `bitmagnet` expose unprotected admin APIs by default!
|
|
||||||
- moby: NetworkManager doesn't connect to network until _after_ `systemctl restart NetworkManager`
|
|
||||||
- probably a dependency ordering issue
|
|
||||||
- e.g. we try to bring up NetworkManager before bringing up `lo`
|
|
||||||
- could be a perms issue (over-restrictive sandboxing)
|
|
||||||
|
|
||||||
## REFACTORING:
|
## REFACTORING:
|
||||||
- fold hosts/modules/ into toplevel modules/
|
|
||||||
- add import checks to my Python nix-shell scripts
|
- add import checks to my Python nix-shell scripts
|
||||||
- consolidate ~/dev and ~/ref
|
- consolidate ~/dev and ~/ref
|
||||||
- ~/dev becomes a link to ~/ref/cat/mine
|
- ~/dev becomes a link to ~/ref/cat/mine
|
||||||
- fold hosts/common/home/ssh.nix -> hosts/common/users/colin.nix
|
- fold hosts/common/home/ssh.nix -> hosts/common/users/colin.nix
|
||||||
- don't hardcode IP addresses so much in servo
|
- don't hardcode IP addresses so much in servo
|
||||||
- modules/netns: migrate `sane.netns.$NS.services = [ FOO ]` option to be `systemd.services.$FOO.sane.netns = NS`
|
|
||||||
- then change the ExecStartPre check to not ping `ipinfo.net` or whatever.
|
|
||||||
either port all of `sane-ip-check` to use a self-hosted reflector,
|
|
||||||
or settle for something like `test -eq "$(ip route get ...)" "$expectedGateway"`
|
|
||||||
|
|
||||||
### sops/secrets
|
### sops/secrets
|
||||||
|
- rework secrets to leverage `sane.fs`
|
||||||
|
- remove sops activation script as it's covered by my systemd sane.fs impl
|
||||||
- user secrets could just use `gocryptfs`, like with ~/private?
|
- user secrets could just use `gocryptfs`, like with ~/private?
|
||||||
- can gocryptfs support nested filesystems, each with different perms (for desko, moby, etc)?
|
- can gocryptfs support nested filesystems, each with different perms (for desko, moby, etc)?
|
||||||
|
|
||||||
|
### roles
|
||||||
|
- allow any host to take the role of `uninsane.org`
|
||||||
|
- will make it easier to test new services?
|
||||||
|
|
||||||
### upstreaming
|
### upstreaming
|
||||||
- upstream blueprint-compiler cross fixes -> nixpkgs
|
- add updateScripts to all my packages in nixpkgs
|
||||||
- upstream cargo cross fixes -> nixpkgs
|
|
||||||
- upstream `gps-share` package -> nixpkgs
|
|
||||||
|
|
||||||
#### upstreaming to non-nixpkgs repos
|
#### upstreaming to non-nixpkgs repos
|
||||||
- gnome-calls: retry net connection when DNS is down
|
|
||||||
- gtk: build schemas even on cross compilation: <https://github.com/NixOS/nixpkgs/pull/247844>
|
- gtk: build schemas even on cross compilation: <https://github.com/NixOS/nixpkgs/pull/247844>
|
||||||
- linux: upstream PinePhonePro device trees
|
|
||||||
- nwg-panel: configurable media controls
|
|
||||||
- nwg-panel / playerctl hang fix (i think nwg-panel is what should be patched here)
|
|
||||||
|
|
||||||
|
|
||||||
## IMPROVEMENTS:
|
## IMPROVEMENTS:
|
||||||
- servo: expand /boot to 2 GiB like all other hosts
|
- kernels: ship the same kernel on every machine
|
||||||
- moby: port to systemd-boot
|
- then i can tune the kernels for hardening, without duplicating that work 4 times
|
||||||
- sane-deadlines: show day of the week for upcoming items
|
- zfs: replace this with something which doesn't require a custom kernel build
|
||||||
- and only show on "first" terminal opened; not on Ctrl+N terminals
|
- mpv: add media looping controls (e.g. loop song, loop playlist)
|
||||||
- curlftpfs: replace with something better
|
- curlftpfs: replace with something better
|
||||||
- safer (rust? actively maintained? sandboxable?)
|
- safer (rust? actively maintained? sandboxable?)
|
||||||
- handles spaces/symbols in filenames
|
- handles spaces/symbols in filenames
|
||||||
@@ -70,60 +75,45 @@
|
|||||||
- matrix room links *just work*.
|
- matrix room links *just work*.
|
||||||
- `network.protocol-handler.external.https = true` in about:config *seems* to do this,
|
- `network.protocol-handler.external.https = true` in about:config *seems* to do this,
|
||||||
but breaks some webpages (e.g. Pleroma)
|
but breaks some webpages (e.g. Pleroma)
|
||||||
- associate http(s)://*.pdf with my pdf handler
|
|
||||||
- can't do that because lots of applications don't handle URIs
|
|
||||||
- could workaround using a wrapper that downloads the file and then passes it to the program
|
|
||||||
- geary: replace with envelope
|
|
||||||
- likely requires updating envelope to a more recent version (for multi-accounting), and therefore updating libadwaita...
|
|
||||||
|
|
||||||
### security/resilience
|
### security/resilience
|
||||||
|
- enable `snapper` btrfs snapshots (`services.snapper`)
|
||||||
- /mnt/desko/home, etc, shouldn't include secrets (~/private)
|
- /mnt/desko/home, etc, shouldn't include secrets (~/private)
|
||||||
- 95% of its use is for remote media access and stuff which isn't in VCS (~/records)
|
- 95% of its use is for remote media access and stuff which isn't in VCS (~/records)
|
||||||
- harden systemd services:
|
|
||||||
- servo: `coturn.service`
|
|
||||||
- servo: `postgresql.service`
|
|
||||||
- servo: `postfix.service`
|
|
||||||
- servo: `prosody.service`
|
|
||||||
- servo: `slskd.service`
|
|
||||||
- desko: `usbmuxd.service`
|
|
||||||
- servo: `backup-torrents.service`
|
|
||||||
- servo: `dedupe-media.service`
|
|
||||||
- remove SGID /run/wrappers/bin/sendmail, and just add senders to `postdrop` group
|
|
||||||
- port all sane.programs to be sandboxed
|
- port all sane.programs to be sandboxed
|
||||||
- sandbox `nix`
|
- sandbox `nix`
|
||||||
- enforce that all `environment.packages` has a sandbox profile (or explicitly opts out)
|
- enforce that all `environment.packages` has a sandbox profile (or explicitly opts out)
|
||||||
- enforce granular dbus sandboxing (bunpen-dbus-*)
|
- revisit "non-sandboxable" apps and check that i'm not actually just missing mountpoints
|
||||||
- make gnome-keyring-daemon less monolithic
|
- LL_FS_RW=/ isn't enough -- need all mount points like `=/:/proc:/sys:...`.
|
||||||
- no reason every application with _a_ secret needs to see _all_ secrets
|
- ensure non-bin package outputs are linked for sandboxed apps
|
||||||
- check out oo7-daemon?
|
- i.e. `outputs.man`, `outputs.debug`, `outputs.doc`, ...
|
||||||
- also unix-pass based provider: <https://github.com/mdellweg/pass_secret_service>
|
- lock down dbus calls within the sandbox
|
||||||
|
- otherwise anyone can `systemd-run --user ...` to potentially escape a sandbox
|
||||||
|
- <https://github.com/flatpak/xdg-dbus-proxy>
|
||||||
|
- port sanebox to a compiled language (hare?)
|
||||||
|
- it adds like 50-70ms launch time _on my laptop_. i'd hate to know how much that is on the pinephone.
|
||||||
- make dconf stuff less monolithic
|
- make dconf stuff less monolithic
|
||||||
- i.e. per-app dconf profiles for those which need it. possible static config.
|
- i.e. per-app dconf profiles for those which need it. possible static config.
|
||||||
- flatpak/spectrum has some stuff to proxy dconf per-app
|
- flatpak/spectrum has some stuff to proxy dconf per-app
|
||||||
- rework `programs` API to be just an overlay which wraps each binary in an env with XDG_DATA_DIRS etc set & the config/state links placed in /nix/store instead of $HOME.
|
|
||||||
|
|
||||||
### user experience
|
### user experience
|
||||||
- setup a real calendar system, for recurring events
|
|
||||||
- rofi: sort items case-insensitively
|
- rofi: sort items case-insensitively
|
||||||
- rofi: enable mouse mode?
|
|
||||||
- mpv: add media looping controls (e.g. loop song, loop playlist)
|
|
||||||
- mpv: add/implement an extension to search youtube
|
|
||||||
- apparently `yt-dlp` does searching!
|
|
||||||
- replace starship prompt with something more efficient
|
- replace starship prompt with something more efficient
|
||||||
- watch `forkstat`: it does way too much
|
- watch `forkstat`: it does way too much
|
||||||
- cleanup nwg-panel so that it's not invoking swaync every second
|
- cleanup waybar/nwg-panel so that it's not invoking playerctl every 2 seconds
|
||||||
- nwg-panel: doesn't know that virtual-desktop 10/TV exists
|
- nwg-panel: doesn't know that virtual-desktop 10/TV exists
|
||||||
- install apps:
|
- install apps:
|
||||||
- display QR codes for WiFi endpoints: <https://linuxphoneapps.org/apps/noappid.wisperwind.wifi2qr/>
|
- display QR codes for WiFi endpoints: <https://linuxphoneapps.org/apps/noappid.wisperwind.wifi2qr/>
|
||||||
- shopping list (not in nixpkgs): <https://linuxphoneapps.org/apps/ro.hume.cosmin.shoppinglist/>
|
- shopping list (not in nixpkgs): <https://linuxphoneapps.org/apps/ro.hume.cosmin.shoppinglist/>
|
||||||
- offline Wikipedia (or, add to `wike`)
|
- offline Wikipedia (or, add to `wike`)
|
||||||
|
- offline docs viewer (gtk): <https://github.com/workbenchdev/Biblioteca>
|
||||||
- some type of games manager/launcher
|
- some type of games manager/launcher
|
||||||
- Gnome Highscore (retro games)?: <https://gitlab.gnome.org/World/highscore>
|
- Gnome Highscore (retro games)?: <https://gitlab.gnome.org/World/highscore>
|
||||||
|
- better maps for mobile (Osmin (QtQuick)? Pure Maps (Qt/Kirigami)?)
|
||||||
- note-taking app: <https://linuxphoneapps.org/categories/note-taking/>
|
- note-taking app: <https://linuxphoneapps.org/categories/note-taking/>
|
||||||
- Folio is nice, uses standard markdown, though it only supports flat repos
|
- Folio is nice, uses standard markdown, though it only supports flat repos
|
||||||
- OSK overlay specifically for mobile gaming
|
- OSK overlay specifically for mobile gaming
|
||||||
- i.e. mock joysticks, for use with SuperTux and SuperTuxKart
|
- i.e. mock joysticks, for use with SuperTux and SuperTuxKart
|
||||||
- game: Hedgewars
|
|
||||||
- install mobile-friendly games:
|
- install mobile-friendly games:
|
||||||
- Shattered Pixel Dungeon (nixpkgs `shattered-pixel-dungeon`; doesn't cross-compile b/c openjdk/libIDL) <https://github.com/ebolalex/shattered-pixel-dungeon>
|
- Shattered Pixel Dungeon (nixpkgs `shattered-pixel-dungeon`; doesn't cross-compile b/c openjdk/libIDL) <https://github.com/ebolalex/shattered-pixel-dungeon>
|
||||||
- UnCiv (Civ V clone; nixpkgs `unciv`; doesn't cross-compile): <https://github.com/yairm210/UnCiv>
|
- UnCiv (Civ V clone; nixpkgs `unciv`; doesn't cross-compile): <https://github.com/yairm210/UnCiv>
|
||||||
@@ -134,49 +124,56 @@
|
|||||||
- blurble (https://linuxphoneapps.org/games/app.drey.blurble/). nix: not as of 2024-02-05
|
- blurble (https://linuxphoneapps.org/games/app.drey.blurble/). nix: not as of 2024-02-05
|
||||||
- Trivia Quiz (https://linuxphoneapps.org/games/io.github.nokse22.trivia-quiz/)
|
- Trivia Quiz (https://linuxphoneapps.org/games/io.github.nokse22.trivia-quiz/)
|
||||||
- sane-sync-music: remove empty dirs
|
- sane-sync-music: remove empty dirs
|
||||||
- soulseek: install a CLI app usable over ssh
|
|
||||||
- moby: replace `spot` with its replacement, `riff` (<https://github.com/Diegovsky/riff>)
|
|
||||||
|
|
||||||
#### moby
|
#### moby
|
||||||
- moby: port battery support to something upstreamable
|
|
||||||
- moby: install transito/mobroute public transit app: <https://sr.ht/~mil/mobroute/> <https://git.sr.ht/~mil/transito>
|
|
||||||
- see: <https://github.com/NixOS/nixpkgs/pull/335613>
|
|
||||||
- moby: consider honeybee instead of gnome-calls for calling? <https://git.sr.ht/~anjan/honeybee>
|
|
||||||
- uses XMPP, so more NAT/WoWLAN-friendly
|
|
||||||
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
|
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
|
||||||
- fix cpupower for better power/perf
|
- fix cpupower for better power/perf
|
||||||
- `journalctl -u cpupower --boot` (problem is present on lappy, at least)
|
- `journalctl -u cpupower --boot` (problem is present on lappy, at least)
|
||||||
- use dynamic DRAM clocking to reduce power by 0.5W: <https://xnux.eu/log/083.html>
|
|
||||||
- coreboot implements DRAM training for rk3399: <https://gitlab.com/vicencb/kevinboot/-/blob/master/cb/sdram.c>
|
|
||||||
- moby: tune keyboard layout
|
- moby: tune keyboard layout
|
||||||
- SwayNC/nwg-panel: add option to change audio output
|
- SwayNC: add option to change audio output
|
||||||
- Newsflash: sync OPML on start, same way i do with gpodder
|
- moby: tune GPS
|
||||||
- better podcasting client?
|
- fix iio-sensor-proxy magnetometer scaling
|
||||||
- hardware upgrade (OnePlus)?
|
- tune QGPS setting in eg25-control, for less jitter?
|
||||||
|
- configure geoclue to do some smoothing?
|
||||||
|
- manually do smoothing, as some layer between mepo and geoclue?
|
||||||
|
- email wigle.net people to unlock API access
|
||||||
|
- moby: port `freshen-agps` timer service to s6 (maybe i want some `s6-cron` or something)
|
||||||
|
- moby: improve gPodder launch time
|
||||||
|
- moby: theme GTK apps (i.e. non-adwaita styles)
|
||||||
|
- especially, make the menubar collapsible
|
||||||
|
- try Gradience tool specifically for theming adwaita? <https://linuxphoneapps.org/apps/com.github.gradienceteam.gradience/>
|
||||||
|
|
||||||
#### non-moby
|
#### non-moby
|
||||||
- RSS: integrate a paywall bypass
|
- RSS: integrate a paywall bypass
|
||||||
- e.g. self-hosted [ladder](https://github.com/everywall/ladder) (like 12ft.io)
|
- e.g. self-hosted [ladder](https://github.com/everywall/ladder) (like 12ft.io)
|
||||||
- RSS: have podcasts get downloaded straight into ~/Videos/...
|
- RSS: have podcasts get downloaded straight into ~/Videos/...
|
||||||
- and strip the ads out using Whisper transcription + asking a LLM where the ad breaks are
|
- and strip the ads out using Whisper transcription + asking a LLM where the ad breaks are
|
||||||
- neovim: integrate ollama
|
- neovim: set up language server (lsp; rnix-lsp; nvim-lspconfig)
|
||||||
- neovim: better docsets (e.g. c++, glib)
|
- neovim: integrate LLMs
|
||||||
- firefox: persist history
|
- Helix: make copy-to-system clipboard be the default
|
||||||
|
- firefox/librewolf: persist history
|
||||||
- just not cookies or tabs
|
- just not cookies or tabs
|
||||||
|
- package Nix/NixOS docs for Zeal
|
||||||
|
- install [doc-browser](https://github.com/qwfy/doc-browser)
|
||||||
|
- this supports both dash (zeal) *and* the datasets from <https://devdocs.io> (which includes nix!)
|
||||||
|
- install [devhelp](https://wiki.gnome.org/Apps/Devhelp) (gnome)
|
||||||
- have xdg-open parse `<repo:...> URIs (or adjust them so that it _can_ parse)
|
- have xdg-open parse `<repo:...> URIs (or adjust them so that it _can_ parse)
|
||||||
- sane-bt-search: show details like 5.1 vs stereo, h264 vs h265
|
- sane-bt-search: show details like 5.1 vs stereo, h264 vs h265
|
||||||
- maybe just color these "keywords" in all search results?
|
- maybe just color these "keywords" in all search results?
|
||||||
- transmission: apply `sane-tag-media` path fix in `torrent-done` script
|
- transmission: apply `sane-tag-media` path fix in `torrent-done` script
|
||||||
- many .mkv files do appear to be tagged: i'd just need to add support in my own tooling
|
- many .mkv files do appear to be tagged: i'd just need to add support in my own tooling
|
||||||
- more aggressively cleanup non-media files after DL (ripper logos, info txts)
|
|
||||||
- uninsane.org: make URLs relative to allow local use (and as offline homepage)
|
- uninsane.org: make URLs relative to allow local use (and as offline homepage)
|
||||||
- email: fix so that local mail doesn't go to junk
|
- email: fix so that local mail doesn't go to junk
|
||||||
- git sendmail flow adds the DKIM signatures, but gets delivered locally w/o having the sig checked, so goes into Junk
|
- git sendmail flow adds the DKIM signatures, but gets delivered locally w/o having the sig checked, so goes into Junk
|
||||||
- could change junk filter from "no DKIM success" to explicit "DKIM failed"
|
- could change junk filter from "no DKIM success" to explicit "DKIM failed"
|
||||||
- add an auto-reply address (e.g. `reply-test@uninsane.org`) which reflects all incoming mail; use this (or a friend running this) for liveness checks
|
- add an auto-reply address (e.g. `reply-test@uninsane.org`) which reflects all incoming mail; use this (or a friend running this) for liveness checks
|
||||||
|
|
||||||
|
### perf
|
||||||
|
- add `pkgs.impure-cached.<foo>` package set to build things with ccache enabled
|
||||||
|
- every package here can be auto-generated, and marked with some env var so that it doesn't pollute the pure package set
|
||||||
|
- would be super handy for package prototyping!
|
||||||
|
|
||||||
## NEW FEATURES:
|
## NEW FEATURES:
|
||||||
- migrate Kodi box to nix
|
|
||||||
- migrate MAME cabinet to nix
|
- migrate MAME cabinet to nix
|
||||||
- boot it from PXE from servo?
|
- boot it from PXE from servo?
|
||||||
- enable IPv6
|
- enable IPv6
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{ ... }@args:
|
{ ... }@args:
|
||||||
let
|
let
|
||||||
sane-nix-files = import ./pkgs/by-name/sane-nix-files/package.nix { };
|
sane-nix-files = import ./pkgs/additional/sane-nix-files { };
|
||||||
in
|
in
|
||||||
import "${sane-nix-files}/impure.nix" args
|
import "${sane-nix-files}/impure.nix" args
|
||||||
|
@@ -1,10 +1,7 @@
|
|||||||
to add a host:
|
to add a host:
|
||||||
- create the new nix targets
|
- create the new nix targets
|
||||||
- hosts/by-name/HOST
|
- hosts/by-name/HOST
|
||||||
- let the toplevel (impure.nix) know about HOST
|
- let the toplevel (flake.nix) know about HOST
|
||||||
- let the other hosts know about this host (hosts/common/hosts.nix)
|
|
||||||
- let sops know about the host's pubkey (.sops.yaml)
|
|
||||||
- re-encrypt all sops keys in secrets/common
|
|
||||||
- build and flash an image
|
- build and flash an image
|
||||||
- optionally expand the rootfs
|
- optionally expand the rootfs
|
||||||
- `cfdisk /dev/sda2` -> resize partition
|
- `cfdisk /dev/sda2` -> resize partition
|
||||||
@@ -25,9 +22,4 @@ to add a host:
|
|||||||
- instructions in hosts/common/secrets.nix
|
- instructions in hosts/common/secrets.nix
|
||||||
- run `ssh-to-age` on user/host pubkeys
|
- run `ssh-to-age` on user/host pubkeys
|
||||||
- add age key to .sops.yaml
|
- add age key to .sops.yaml
|
||||||
- update encrypted secrets: `find secrets -type f -exec sops updatekeys -y '{}' ';'`
|
- update encrypted secrets: `sops updatekeys path/to/secret.yaml`
|
||||||
- setup wireguard keys
|
|
||||||
- `pk=$(wg genkey)`
|
|
||||||
- `echo "$pk" | sops encrypt --filename-override secrets/$(hostname)/wg-home.priv.bin --output secrets/$(hostname)/wg-home.priv.bin`
|
|
||||||
- `pub=$(echo "$pk" | wg pubkey)`
|
|
||||||
- add pubkey to hosts/common/hosts.nix
|
|
||||||
|
@@ -1,49 +0,0 @@
|
|||||||
## migrating a host to a new drive
|
|
||||||
### 1. copy persistent data off of the host:
|
|
||||||
```sh
|
|
||||||
$ mkdir -p mnt old/persist
|
|
||||||
$ mount /dev/$old mnt
|
|
||||||
$ rsync -arv mnt/persist/ old/persist/
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. flash the new drive
|
|
||||||
```
|
|
||||||
$ nix-build -A hosts.moby.img
|
|
||||||
$ dd if=$(readlink ./result) of=/dev/$new bs=4M oflag=direct conv=sync status=progress
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.1. expand the partition
|
|
||||||
```sh
|
|
||||||
$ cfdisk /dev/$new
|
|
||||||
# scroll to the last partition
|
|
||||||
> Resize
|
|
||||||
leave at default (max)
|
|
||||||
> Write
|
|
||||||
type "yes"
|
|
||||||
> Quit
|
|
||||||
```
|
|
||||||
### 3.2. expand the filesystem
|
|
||||||
```
|
|
||||||
$ mkdir -p /mnt/$new
|
|
||||||
$ mount /dev/$new /mnt/$new
|
|
||||||
$ btrfs filesystem resize max /mnt/$new
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. copy data onto the new host
|
|
||||||
```
|
|
||||||
$ mkdir /mnt/$new
|
|
||||||
$ mount /dev/$new /mnt/$new
|
|
||||||
# if you want to use btrfs snapshots (e.g. snapper), then create the data directory as a subvolume:
|
|
||||||
$ btrfs subvolume create /mnt/$new/persist
|
|
||||||
# restore the data
|
|
||||||
$ rsync -arv old/persist/ /mnt/$new/persist/
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. ensure/fix ownership
|
|
||||||
```
|
|
||||||
$ chmod -R a+rX /mnt/$new/nix
|
|
||||||
# or, let the nix daemon do it:
|
|
||||||
$ nix copy --no-check-sigs --to /mnt/$new $(nix-build -A hosts.moby)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. insert the disk into the system, and boot!
|
|
@@ -1,5 +1,5 @@
|
|||||||
## deploying to SD card
|
## deploying to SD card
|
||||||
- build a toplevel config: `nix build '.#hosts.moby.img'`
|
- build a toplevel config: `nix build '.#hostSystems.moby'`
|
||||||
- mount a system:
|
- mount a system:
|
||||||
- `mkdir -p root/{nix,boot}`
|
- `mkdir -p root/{nix,boot}`
|
||||||
- `mount /dev/sdX1 root/boot`
|
- `mount /dev/sdX1 root/boot`
|
||||||
|
@@ -1,19 +0,0 @@
|
|||||||
# MAME arcade cabinet
|
|
||||||
# Raspberry Pi 400:
|
|
||||||
# - quad-core Cortex-A72 @ 1.8 GHz (ARMv8-A 64; BCM2711)
|
|
||||||
# - 4GiB RAM
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./fs.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
sane.hal.rpi-400.enable = true;
|
|
||||||
sane.roles.client = true; # for WiFi creds
|
|
||||||
|
|
||||||
# TODO: port to `sane.programs` interface
|
|
||||||
services.xserver.desktopManager.kodi.enable = true;
|
|
||||||
|
|
||||||
# /boot space is at a premium, especially with uncompressed kernels. default was 20.
|
|
||||||
# boot.loader.generic-extlinux-compatible.configurationLimit = 10;
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
fileSystems."/nix" = {
|
|
||||||
device = "/dev/disk/by-uuid/cccccccc-aaaa-dddd-eeee-000020250621";
|
|
||||||
fsType = "btrfs";
|
|
||||||
options = [
|
|
||||||
"compress=zstd"
|
|
||||||
"defaults"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
fileSystems."/boot" = {
|
|
||||||
device = "/dev/disk/by-uuid/2025-0621";
|
|
||||||
fsType = "vfat";
|
|
||||||
};
|
|
||||||
}
|
|
@@ -30,4 +30,16 @@
|
|||||||
# sane.programs.guiApps.enableFor.user.colin = false;
|
# sane.programs.guiApps.enableFor.user.colin = false;
|
||||||
|
|
||||||
# sane.programs.pcGuiApps.enableFor.user.colin = false; #< errors!
|
# sane.programs.pcGuiApps.enableFor.user.colin = false; #< errors!
|
||||||
|
|
||||||
|
sane.programs.blueberry.enableFor.user.colin = false; # bluetooth manager: doesn't cross compile!
|
||||||
|
# sane.programs.brave.enableFor.user.colin = false; # 2024/06/03: fails eval if enabled on cross
|
||||||
|
# sane.programs.firefox.enableFor.user.colin = false; # 2024/06/03: this triggers an eval error in yarn stuff -- i'm doing IFD somewhere!!?
|
||||||
|
sane.programs.mepo.enableFor.user.colin = false; # 2024/06/04: doesn't cross compile (nodejs)
|
||||||
|
sane.programs.mercurial.enableFor.user.colin = false; # 2024/06/03: does not cross compile
|
||||||
|
sane.programs.nixpkgs-review.enableFor.user.colin = false; # 2024/06/03: OOMs when cross compiling
|
||||||
|
sane.programs.ntfy-sh.enableFor.user.colin = false; # 2024/06/04: doesn't cross compile (nodejs)
|
||||||
|
sane.programs.pwvucontrol.enableFor.user.colin = false; # 2024/06/03: doesn't cross compile (libspa-sys)
|
||||||
|
sane.programs."sane-scripts.bt-search".enableFor.user.colin = false; # 2024/06/03: does not cross compile
|
||||||
|
sane.programs.sequoia.enableFor.user.colin = false; # 2024/06/03: does not cross compile
|
||||||
|
sane.programs.zathura.enableFor.user.colin = false; # 2024/06/03: does not cross compile
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
{ config, lib, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./fs.nix
|
./fs.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
# firewall has to be open to allow clients to use services hosted on this device,
|
sane.services.hickory-dns.asSystemResolver = false; # TEMPORARY: TODO: re-enable hickory-dns
|
||||||
# like `ollama`
|
|
||||||
sane.ports.openFirewall = true;
|
|
||||||
|
|
||||||
# sane.programs.devPkgs.enableFor.user.colin = true;
|
# sane.programs.devPkgs.enableFor.user.colin = true;
|
||||||
# sane.guest.enable = true;
|
# sane.guest.enable = true;
|
||||||
|
|
||||||
@@ -25,33 +22,48 @@
|
|||||||
|
|
||||||
sane.roles.build-machine.enable = true;
|
sane.roles.build-machine.enable = true;
|
||||||
sane.roles.client = true;
|
sane.roles.client = true;
|
||||||
|
sane.roles.dev-machine = true;
|
||||||
sane.roles.pc = true;
|
sane.roles.pc = true;
|
||||||
sane.roles.work = true;
|
|
||||||
sane.services.ollama.enable = lib.mkIf (config.sane.maxBuildCost >= 3) true;
|
|
||||||
sane.services.wg-home.enable = true;
|
sane.services.wg-home.enable = true;
|
||||||
|
sane.services.wg-home.ip = config.sane.hosts.by-name."desko".wg-home.ip;
|
||||||
sane.ovpn.addrV4 = "172.26.55.21";
|
sane.ovpn.addrV4 = "172.26.55.21";
|
||||||
# sane.ovpn.addrV6 = "fd00:0000:1337:cafe:1111:1111:20c1:a73c";
|
# sane.ovpn.addrV6 = "fd00:0000:1337:cafe:1111:1111:20c1:a73c";
|
||||||
sane.services.rsync-net.enable = true;
|
sane.services.rsync-net.enable = true;
|
||||||
|
|
||||||
sane.nixcache.remote-builders.desko = false;
|
sane.nixcache.remote-builders.desko = false;
|
||||||
|
|
||||||
sane.programs.firefox.config.formFactor = "desktop";
|
|
||||||
|
|
||||||
sane.programs.sane-private-unlock-remote.enableFor.user.colin = true;
|
sane.programs.sane-private-unlock-remote.enableFor.user.colin = true;
|
||||||
sane.programs.sane-private-unlock-remote.config.hosts = [ "servo" ];
|
sane.programs.sane-private-unlock-remote.config.hosts = [ "servo" ];
|
||||||
|
|
||||||
sane.programs.sway.enableFor.user.colin = true;
|
sane.programs.sway.enableFor.user.colin = true;
|
||||||
|
sane.programs.iphoneUtils.enableFor.user.colin = true;
|
||||||
sane.programs.steam.enableFor.user.colin = true;
|
sane.programs.steam.enableFor.user.colin = true;
|
||||||
|
|
||||||
|
sane.programs.geary.config.autostart = true;
|
||||||
|
sane.programs.signal-desktop.config.autostart = true;
|
||||||
|
|
||||||
sane.programs.nwg-panel.config = {
|
sane.programs.nwg-panel.config = {
|
||||||
battery = false;
|
battery = false;
|
||||||
brightness = false;
|
brightness = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
sane.programs.mpv.config.defaultProfile = "high-quality";
|
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||||
|
|
||||||
# needed to use libimobiledevice/ifuse, for iphone sync
|
# needed to use libimobiledevice/ifuse, for iphone sync
|
||||||
services.usbmuxd.enable = true;
|
services.usbmuxd.enable = true;
|
||||||
|
|
||||||
hardware.amdgpu.opencl.enable = true; # desktop (AMD's opencl implementation AKA "ROCM"); probably required for ollama
|
# TODO: enable snapper (need to make `/nix` or `/nix/persist` a subvolume, somehow).
|
||||||
|
# default config: https://man.archlinux.org/man/snapper-configs.5
|
||||||
|
# defaults to something like:
|
||||||
|
# - hourly snapshots
|
||||||
|
# - auto cleanup; keep the last 10 hourlies, last 10 daylies, last 10 monthlys.
|
||||||
|
# to list snapshots: `sudo snapper --config nix list`
|
||||||
|
# to take a snapshot: `sudo snapper --config nix create`
|
||||||
|
# services.snapper.configs.nix = {
|
||||||
|
# # TODO: for the impermanent setup, we'd prefer to just do /nix/persist,
|
||||||
|
# # but that also requires setting up the persist dir as a subvol
|
||||||
|
# SUBVOLUME = "/nix";
|
||||||
|
# # TODO: ALLOW_USERS doesn't seem to work. still need `sudo snapper -c nix list`
|
||||||
|
# ALLOW_USERS = [ "colin" ];
|
||||||
|
# };
|
||||||
}
|
}
|
||||||
|
@@ -3,10 +3,10 @@
|
|||||||
{
|
{
|
||||||
# increase /tmp space (defaults to 50% of RAM) for building large nix things.
|
# increase /tmp space (defaults to 50% of RAM) for building large nix things.
|
||||||
# a cross-compiled kernel, particularly, will easily use 30+GB of tmp
|
# a cross-compiled kernel, particularly, will easily use 30+GB of tmp
|
||||||
fileSystems."/tmp".options = [ "size=128G" ];
|
fileSystems."/tmp".options = [ "size=64G" ];
|
||||||
|
|
||||||
fileSystems."/nix" = {
|
fileSystems."/nix" = {
|
||||||
device = "/dev/disk/by-uuid/dddddddd-eeee-5555-cccc-000020250527";
|
device = "/dev/disk/by-uuid/845d85bf-761d-431b-a406-e6f20909154f";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
options = [
|
options = [
|
||||||
"compress=zstd"
|
"compress=zstd"
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/boot" = {
|
fileSystems."/boot" = {
|
||||||
device = "/dev/disk/by-uuid/2025-0527";
|
device = "/dev/disk/by-uuid/5049-9AFD";
|
||||||
fsType = "vfat";
|
fsType = "vfat";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,58 +0,0 @@
|
|||||||
{ lib, pkgs, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./fs.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
sane.roles.client = true;
|
|
||||||
sane.roles.pc = true;
|
|
||||||
sane.roles.work = true;
|
|
||||||
sane.services.wg-home.enable = true;
|
|
||||||
# sane.ovpn.addrV4 = "172.23.119.72";
|
|
||||||
|
|
||||||
# sane.guest.enable = true;
|
|
||||||
|
|
||||||
sane.programs.sane-private-unlock-remote.enableFor.user.colin = true;
|
|
||||||
sane.programs.sane-private-unlock-remote.config.hosts = [ "servo" ];
|
|
||||||
|
|
||||||
sane.programs.firefox.config.formFactor = "laptop";
|
|
||||||
sane.programs.itgmania.enableFor.user.colin = true;
|
|
||||||
sane.programs.sway.enableFor.user.colin = true;
|
|
||||||
|
|
||||||
sops.secrets.colin-passwd.neededForUsers = true;
|
|
||||||
|
|
||||||
sane.services.rsync-net.enable = true;
|
|
||||||
|
|
||||||
# add an entry to boot into Windows, as if it had been launched directly from the BIOS.
|
|
||||||
boot.loader.systemd-boot.rebootForBitlocker = true;
|
|
||||||
boot.loader.systemd-boot.windows.primary.efiDeviceHandle = "HD0b";
|
|
||||||
|
|
||||||
system.activationScripts.makeDefaultBootEntry = {
|
|
||||||
text = let
|
|
||||||
makeDefaultBootEntry = pkgs.writeShellApplication {
|
|
||||||
name = "makeDefaultBootEntry";
|
|
||||||
runtimeInputs = with pkgs; [
|
|
||||||
efibootmgr
|
|
||||||
gnugrep
|
|
||||||
];
|
|
||||||
text = ''
|
|
||||||
# configure the EFI firmware to boot into NixOS by default.
|
|
||||||
# do this by querying the active boot entry, and just making that be the default.
|
|
||||||
# this is needed on flowy because enabling secure boot / booting into Windows
|
|
||||||
# resets the default boot order; manually reconfiguring that is tiresome.
|
|
||||||
efi=$(efibootmgr)
|
|
||||||
bootCurrent=$(echo "$efi" | grep '^BootCurrent: ')
|
|
||||||
bootCurrent=''${bootCurrent/BootCurrent: /}
|
|
||||||
bootOrder=$(echo "$efi" | grep '^BootOrder: ')
|
|
||||||
bootOrder=''${bootOrder/BootOrder: /}
|
|
||||||
if ! [[ "$bootOrder" =~ ^"$bootCurrent", ]]; then
|
|
||||||
# booted entry was not the default,
|
|
||||||
# so prepend it to the boot order:
|
|
||||||
newBootOrder="$bootCurrent,$bootOrder"
|
|
||||||
(set -x; efibootmgr -o "$newBootOrder")
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
in lib.getExe makeDefaultBootEntry;
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
fileSystems."/nix" = {
|
|
||||||
device = "/dev/disk/by-uuid/ffffffff-1111-0000-eeee-000020250531";
|
|
||||||
fsType = "btrfs";
|
|
||||||
options = [
|
|
||||||
"compress=zstd"
|
|
||||||
"defaults"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
fileSystems."/boot" = {
|
|
||||||
device = "/dev/disk/by-uuid/2025-0531";
|
|
||||||
fsType = "vfat";
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,37 +1,45 @@
|
|||||||
{ lib, ... }:
|
{ config, pkgs, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./fs.nix
|
./fs.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
sane.roles.client = true;
|
sane.roles.client = true;
|
||||||
|
sane.roles.dev-machine = true;
|
||||||
sane.roles.pc = true;
|
sane.roles.pc = true;
|
||||||
sane.services.wg-home.enable = true;
|
sane.services.wg-home.enable = true;
|
||||||
|
sane.services.wg-home.ip = config.sane.hosts.by-name."lappy".wg-home.ip;
|
||||||
sane.ovpn.addrV4 = "172.23.119.72";
|
sane.ovpn.addrV4 = "172.23.119.72";
|
||||||
# sane.ovpn.addrV6 = "fd00:0000:1337:cafe:1111:1111:0332:aa96/128";
|
# sane.ovpn.addrV6 = "fd00:0000:1337:cafe:1111:1111:0332:aa96/128";
|
||||||
|
|
||||||
# sane.guest.enable = true;
|
# sane.guest.enable = true;
|
||||||
|
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||||
|
|
||||||
sane.programs.sane-private-unlock-remote.enableFor.user.colin = true;
|
sane.programs.sane-private-unlock-remote.enableFor.user.colin = true;
|
||||||
sane.programs.sane-private-unlock-remote.config.hosts = [ "servo" ];
|
sane.programs.sane-private-unlock-remote.config.hosts = [ "servo" ];
|
||||||
|
|
||||||
sane.programs.firefox.config.formFactor = "laptop";
|
sane.programs.stepmania.enableFor.user.colin = true;
|
||||||
sane.programs.itgmania.enableFor.user.colin = true;
|
|
||||||
# sane.programs.stepmania.enableFor.user.colin = true; #< TODO: fix build
|
|
||||||
sane.programs.sway.enableFor.user.colin = true;
|
sane.programs.sway.enableFor.user.colin = true;
|
||||||
|
|
||||||
|
sane.programs.geary.config.autostart = true;
|
||||||
|
sane.programs.signal-desktop.config.autostart = true;
|
||||||
|
|
||||||
sops.secrets.colin-passwd.neededForUsers = true;
|
sops.secrets.colin-passwd.neededForUsers = true;
|
||||||
|
|
||||||
sane.services.rsync-net.enable = true;
|
sane.services.rsync-net.enable = true;
|
||||||
|
|
||||||
# starting 2024/09, under default settings (apparently 256 quantum), audio would crackle under load.
|
# TODO: enable snapper (need to make `/nix` or `/nix/persist` a subvolume, somehow).
|
||||||
# 1024 solves *most* crackles, but still noticable under heavier loads.
|
# default config: https://man.archlinux.org/man/snapper-configs.5
|
||||||
sane.programs.pipewire.config.min-quantum = 2048;
|
# defaults to something like:
|
||||||
|
# - hourly snapshots
|
||||||
# limit how many snapshots we keep, due to extremely limited disk space (TODO: remove this override after upgrading lappy hard drive)
|
# - auto cleanup; keep the last 10 hourlies, last 10 daylies, last 10 monthlys.
|
||||||
services.snapper.configs.root.TIMELINE_LIMIT_HOURLY = lib.mkForce 2;
|
# to list snapshots: `sudo snapper --config nix list`
|
||||||
services.snapper.configs.root.TIMELINE_LIMIT_DAILY = lib.mkForce 2;
|
# to take a snapshot: `sudo snapper --config nix create`
|
||||||
services.snapper.configs.root.TIMELINE_LIMIT_WEEKLY = lib.mkForce 0;
|
# services.snapper.configs.nix = {
|
||||||
services.snapper.configs.root.TIMELINE_LIMIT_MONTHLY = lib.mkForce 0;
|
# # TODO: for the impermanent setup, we'd prefer to just do /nix/persist,
|
||||||
services.snapper.configs.root.TIMELINE_LIMIT_YEARLY = lib.mkForce 0;
|
# # but that also requires setting up the persist dir as a subvol
|
||||||
|
# SUBVOLUME = "/nix";
|
||||||
|
# # TODO: ALLOW_USERS doesn't seem to work. still need `sudo snapper -c nix list`
|
||||||
|
# ALLOW_USERS = [ "colin" ];
|
||||||
|
# };
|
||||||
}
|
}
|
||||||
|
@@ -6,16 +6,17 @@
|
|||||||
# - Mobian wiki: <https://wiki.mobian-project.org/doku.php?id=start>
|
# - Mobian wiki: <https://wiki.mobian-project.org/doku.php?id=start>
|
||||||
# - recommended apps, chatrooms
|
# - recommended apps, chatrooms
|
||||||
|
|
||||||
{ ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./fs.nix
|
./fs.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
sane.hal.pine64-pinephone-pro.enable = true;
|
sane.hal.pine64.enable = true;
|
||||||
sane.roles.client = true;
|
sane.roles.client = true;
|
||||||
sane.roles.handheld = true;
|
sane.roles.handheld = true;
|
||||||
sane.services.wg-home.enable = true;
|
sane.services.wg-home.enable = true;
|
||||||
|
sane.services.wg-home.ip = config.sane.hosts.by-name."moby".wg-home.ip;
|
||||||
sane.ovpn.addrV4 = "172.24.87.255";
|
sane.ovpn.addrV4 = "172.24.87.255";
|
||||||
# sane.ovpn.addrV6 = "fd00:0000:1337:cafe:1111:1111:18cd:a72b";
|
# sane.ovpn.addrV6 = "fd00:0000:1337:cafe:1111:1111:18cd:a72b";
|
||||||
|
|
||||||
@@ -29,16 +30,20 @@
|
|||||||
|
|
||||||
sane.programs.sway.enableFor.user.colin = true;
|
sane.programs.sway.enableFor.user.colin = true;
|
||||||
sane.programs.sway.config.mod = "Mod1"; #< alt key instead of Super
|
sane.programs.sway.config.mod = "Mod1"; #< alt key instead of Super
|
||||||
|
sane.programs.blueberry.enableFor.user.colin = false; # bluetooth manager: doesn't cross compile!
|
||||||
|
sane.programs.fcitx5.enableFor.user.colin = false; # does not cross compile
|
||||||
|
sane.programs.mercurial.enableFor.user.colin = false; # does not cross compile
|
||||||
|
sane.programs.nvme-cli.enableFor.system = false; # does not cross compile (libhugetlbfs)
|
||||||
|
|
||||||
# enabled for easier debugging
|
# enabled for easier debugging
|
||||||
sane.programs.eg25-control.enableFor.user.colin = true;
|
sane.programs.eg25-control.enableFor.user.colin = true;
|
||||||
# sane.programs.rtl8723cs-wowlan.enableFor.user.colin = true;
|
# sane.programs.rtl8723cs-wowlan.enableFor.user.colin = true;
|
||||||
# sane.programs.eg25-manager.enableFor.user.colin = true;
|
|
||||||
|
|
||||||
# sane.programs.ntfy-sh.config.autostart = true;
|
# sane.programs.ntfy-sh.config.autostart = true;
|
||||||
sane.programs.dino.config.autostart = true;
|
sane.programs.dino.config.autostart = true;
|
||||||
sane.programs.signal-desktop.config.autostart = false;
|
sane.programs.signal-desktop.config.autostart = true;
|
||||||
sane.programs.geary.config.autostart = false;
|
# sane.programs.geary.config.autostart = true;
|
||||||
|
# sane.programs.calls.config.autostart = true;
|
||||||
|
|
||||||
sane.programs.pipewire.config = {
|
sane.programs.pipewire.config = {
|
||||||
# tune so Dino doesn't drop audio
|
# tune so Dino doesn't drop audio
|
||||||
@@ -56,12 +61,7 @@
|
|||||||
max-quantum = 8192;
|
max-quantum = 8192;
|
||||||
};
|
};
|
||||||
|
|
||||||
sane.programs.mpv.config.defaultProfile = "fast";
|
# /boot space is at a premium. default was 20.
|
||||||
|
# even 10 can be too much
|
||||||
# /boot space is at a premium, especially with uncompressed kernels. default was 20.
|
boot.loader.generic-extlinux-compatible.configurationLimit = 8;
|
||||||
# boot.loader.generic-extlinux-compatible.configurationLimit = 10;
|
|
||||||
|
|
||||||
# TODO: switch to systemd-boot
|
|
||||||
boot.loader.generic-extlinux-compatible.enable = true;
|
|
||||||
boot.loader.systemd-boot.enable = false;
|
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
{ ... }:
|
{ pkgs, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./fs.nix
|
./fs.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||||
sane.persist.enable = false; # what we mean here is that the image is immutable; `/` is still tmpfs.
|
sane.persist.enable = false; # what we mean here is that the image is immutable; `/` is still tmpfs.
|
||||||
sane.nixcache.enable = false; # don't want to be calling out to dead machines that we're *trying* to rescue
|
sane.nixcache.enable = false; # don't want to be calling out to dead machines that we're *trying* to rescue
|
||||||
|
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
{ ... }:
|
{ config, pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./fs.nix
|
./fs.nix
|
||||||
./net
|
./net.nix
|
||||||
./services
|
./services
|
||||||
./users
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# for administering services
|
# for administering services
|
||||||
@@ -22,14 +21,24 @@
|
|||||||
"sane-scripts.stop-all-servo"
|
"sane-scripts.stop-all-servo"
|
||||||
];
|
];
|
||||||
sane.services.dyn-dns.enable = true;
|
sane.services.dyn-dns.enable = true;
|
||||||
|
sane.services.hickory-dns.asSystemResolver = false; # TODO: enable once it's all working well
|
||||||
|
sane.services.wg-home.enable = true;
|
||||||
|
sane.services.wg-home.visibleToWan = true;
|
||||||
|
sane.services.wg-home.forwardToWan = true;
|
||||||
|
sane.services.wg-home.routeThroughServo = false;
|
||||||
|
sane.services.wg-home.ip = config.sane.hosts.by-name."servo".wg-home.ip;
|
||||||
|
sane.ovpn.addrV4 = "172.23.174.114";
|
||||||
|
# sane.ovpn.addrV6 = "fd00:0000:1337:cafe:1111:1111:8df3:14b0";
|
||||||
sane.nixcache.remote-builders.desko = false;
|
sane.nixcache.remote-builders.desko = false;
|
||||||
sane.nixcache.remote-builders.servo = false;
|
sane.nixcache.remote-builders.servo = false;
|
||||||
sane.services.rsync-net.enable = true;
|
sane.services.rsync-net.enable = true;
|
||||||
|
|
||||||
# automatically log in at the virtual consoles.
|
# automatically log in at the virtual consoles.
|
||||||
# using root here makes sure we always have an escape hatch.
|
# using root here makes sure we always have an escape hatch.
|
||||||
# XXX(2024-07-27): this is incompatible if using s6, which needs to auto-login as `colin` to start its user services.
|
# XXX(2024-07-27): this is incompatible with my s6-rc stuff, which needs to auto-login as `colin` to start its user services.
|
||||||
services.getty.autologinUser = "root";
|
# services.getty.autologinUser = "root";
|
||||||
|
|
||||||
|
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||||
|
|
||||||
# both transmission and ipfs try to set different net defaults.
|
# both transmission and ipfs try to set different net defaults.
|
||||||
# we just use the most aggressive of the two here:
|
# we just use the most aggressive of the two here:
|
||||||
|
@@ -1,9 +1,60 @@
|
|||||||
|
# zfs docs:
|
||||||
|
# - <https://nixos.wiki/wiki/ZFS>
|
||||||
|
# - <repo:nixos/nixpkgs:nixos/modules/tasks/filesystems/zfs.nix>
|
||||||
|
#
|
||||||
|
# zfs check health: `zpool status`
|
||||||
|
#
|
||||||
|
# zfs pool creation (requires `boot.supportedFilesystems = [ "zfs" ];`
|
||||||
|
# - 1. identify disk IDs: `ls -l /dev/disk/by-id`
|
||||||
|
# - 2. pool these disks: `zpool create -f -m legacy pool raidz ata-ST4000VN008-2DR166_WDH0VB45 ata-ST4000VN008-2DR166_WDH17616 ata-ST4000VN008-2DR166_WDH0VC8Q ata-ST4000VN008-2DR166_WDH17680`
|
||||||
|
# - legacy documented: <https://superuser.com/questions/790036/what-is-a-zfs-legacy-mount-point>
|
||||||
|
# - 3. enable acl support: `zfs set acltype=posixacl pool`
|
||||||
|
#
|
||||||
|
# import pools: `zpool import pool`
|
||||||
|
# show zfs datasets: `zfs list` (will be empty if haven't imported)
|
||||||
|
# show zfs properties (e.g. compression): `zfs get all pool`
|
||||||
|
# set zfs properties: `zfs set compression=on pool`
|
||||||
{ lib, pkgs, ... }:
|
{ lib, pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# hostId: not used for anything except zfs guardrail?
|
# hostId: not used for anything except zfs guardrail?
|
||||||
# [hex(ord(x)) for x in 'serv']
|
# [hex(ord(x)) for x in 'serv']
|
||||||
# networking.hostId = "73657276";
|
networking.hostId = "73657276";
|
||||||
|
boot.supportedFilesystems = [ "zfs" ];
|
||||||
|
# boot.zfs.enabled = true;
|
||||||
|
boot.zfs.forceImportRoot = false;
|
||||||
|
# scrub all zfs pools weekly:
|
||||||
|
services.zfs.autoScrub.enable = true;
|
||||||
|
boot.extraModprobeConfig = ''
|
||||||
|
### zfs_arc_max tunable:
|
||||||
|
# ZFS likes to use half the ram for its own cache and let the kernel push everything else to swap.
|
||||||
|
# so, reduce its cache size
|
||||||
|
# see: <https://askubuntu.com/a/1290387>
|
||||||
|
# see: <https://serverfault.com/a/1119083>
|
||||||
|
# see: <https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max>
|
||||||
|
# for all tunables, see: `man 4 zfs`
|
||||||
|
# to update these parameters without rebooting:
|
||||||
|
# - `echo '4294967296' | sane-sudo-redirect /sys/module/zfs/parameters/zfs_arc_max`
|
||||||
|
### zfs_bclone_enabled tunable
|
||||||
|
# this allows `cp --reflink=always FOO BAR` to work. i.e. shallow copies.
|
||||||
|
# it's unstable as of 2.2.3. led to *actual* corruption in 2.2.1, but hopefully better by now.
|
||||||
|
# - <https://github.com/openzfs/zfs/issues/405>
|
||||||
|
# note that `du -h` won't *always* show the reduced size for reflink'd files (?).
|
||||||
|
# `zpool get all | grep clone` seems to be the way to *actually* see how much data is being deduped
|
||||||
|
options zfs zfs_arc_max=4294967296 zfs_bclone_enabled=1
|
||||||
|
'';
|
||||||
|
# to be able to mount the pool like this, make sure to tell zfs to NOT manage it itself.
|
||||||
|
# otherwise local-fs.target will FAIL and you will be dropped into a rescue shell.
|
||||||
|
# - `zfs set mountpoint=legacy pool`
|
||||||
|
# if done correctly, the pool can be mounted before this `fileSystems` entry is created:
|
||||||
|
# - `sudo mount -t zfs pool /mnt/persist/pool`
|
||||||
|
fileSystems."/mnt/pool" = {
|
||||||
|
device = "pool";
|
||||||
|
fsType = "zfs";
|
||||||
|
options = [ "acl" ]; #< not sure if this `acl` flag is actually necessary. it mounts without it.
|
||||||
|
};
|
||||||
|
# services.zfs.zed = ... # TODO: zfs can send me emails when disks fail
|
||||||
|
sane.programs.sysadminUtils.suggestedPrograms = [ "zfs-tools" ];
|
||||||
|
|
||||||
sane.persist.stores."ext" = {
|
sane.persist.stores."ext" = {
|
||||||
origin = "/mnt/pool/persist";
|
origin = "/mnt/pool/persist";
|
||||||
@@ -16,7 +67,7 @@
|
|||||||
fileSystems."/tmp".options = [ "size=32G" ];
|
fileSystems."/tmp".options = [ "size=32G" ];
|
||||||
|
|
||||||
fileSystems."/nix" = {
|
fileSystems."/nix" = {
|
||||||
device = "/dev/disk/by-uuid/55555555-eeee-ffff-bbbb-000020250820";
|
device = "/dev/disk/by-uuid/cc81cca0-3cc7-4d82-a00c-6243af3e7776";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
options = [
|
options = [
|
||||||
"compress=zstd"
|
"compress=zstd"
|
||||||
@@ -25,39 +76,23 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/boot" = {
|
fileSystems."/boot" = {
|
||||||
device = "/dev/disk/by-uuid/2025-0820";
|
device = "/dev/disk/by-uuid/6EE3-4171";
|
||||||
fsType = "vfat";
|
fsType = "vfat";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/mnt/pool" = {
|
# slow, external storage (for archiving, etc)
|
||||||
# all btrfs devices of the same RAID volume use the same UUID.
|
fileSystems."/mnt/usb-hdd" = {
|
||||||
device = "UUID=40fc6e1d-ba41-44de-bbf3-1aa02c3441df";
|
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
options = [
|
options = [
|
||||||
# "compress=zstd" #< not much point in compressing... mostly videos and music; media.
|
"compress=zstd"
|
||||||
"defaults"
|
"defaults"
|
||||||
# `device=...` only needed if `btrfs scan` hasn't yet been run
|
|
||||||
# see: <https://askubuntu.com/a/484374>
|
|
||||||
# i don't know what guarantees NixOS/systemd make about that, so specifying all devices for now
|
|
||||||
# "device=/dev/disk/by-partuuid/14a7d00a-be53-2b4e-96f9-7e2c964674ec" #< removed 2024-11-24 (for capacity upgrade)
|
|
||||||
"device=/dev/disk/by-partuuid/409a147e-2282-49eb-87a7-c968032ede88" #< added 2024-11-24
|
|
||||||
# "device=/dev/disk/by-partuuid/6b86cc10-c3cc-ec4d-b20d-b6688f0959a6" #< removed 2025-06-04 (early drive failure; capacity upgrade)
|
|
||||||
# "device=/dev/disk/by-partuuid/7fd85cac-b6f3-8248-af4e-68e703d11020" #< removed 2024-11-13 (early drive failure)
|
|
||||||
"device=/dev/disk/by-partuuid/92ebbbfb-022f-427d-84d5-39349d4bc02a" #< added 2025-05-14
|
|
||||||
"device=/dev/disk/by-partuuid/9e6c06b0-4a39-4d69-813f-1f5992f62ed7" #< added 2025-06-05
|
|
||||||
"device=/dev/disk/by-partuuid/d9ad5ebc-0fc4-4d89-9fd0-619ce5210f1b" #< added 2024-11-13
|
|
||||||
# "device=/dev/disk/by-partuuid/ef0e5c7b-fccf-f444-bac4-534424326159" #< removed 2025-05-14 (early drive failure)
|
|
||||||
"nofail"
|
|
||||||
# "x-systemd.before=local-fs.target"
|
|
||||||
"x-systemd.device-bound=false" #< don't unmount when `device` disappears (i thought this was necessary, for drive replacement, but it might not be)
|
|
||||||
"x-systemd.device-timeout=60s"
|
|
||||||
"x-systemd.mount-timeout=60s"
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
sane.fs."/mnt/usb-hdd".mount = {};
|
||||||
|
|
||||||
# TODO: move this elsewhere and automate the ACLs!
|
|
||||||
# FIRST TIME SETUP FOR MEDIA DIRECTORY:
|
# FIRST TIME SETUP FOR MEDIA DIRECTORY:
|
||||||
# - set the group sticky bit: `sudo find /var/media -type d -exec chmod g+s {} +`
|
# - set the group stick bit: `sudo find /var/media -type d -exec chmod g+s {} +`
|
||||||
# - this ensures new files/dirs inherit the group of their parent dir (instead of the user who creates them)
|
# - this ensures new files/dirs inherit the group of their parent dir (instead of the user who creates them)
|
||||||
# - ensure everything under /var/media is mounted with `-o acl`, to support acls
|
# - ensure everything under /var/media is mounted with `-o acl`, to support acls
|
||||||
# - ensure all files are rwx by group: `setfacl --recursive --modify d:g::rwx /var/media`
|
# - ensure all files are rwx by group: `setfacl --recursive --modify d:g::rwx /var/media`
|
||||||
@@ -69,9 +104,8 @@
|
|||||||
mode = "0775";
|
mode = "0775";
|
||||||
}];
|
}];
|
||||||
sane.fs."/var/media/archive".dir = {};
|
sane.fs."/var/media/archive".dir = {};
|
||||||
sane.fs."/var/media/archive/temp".dir = {};
|
|
||||||
# this is file.text instead of symlink.text so that it may be read over a remote mount (where consumers might not have any /nix/store/.../README.md path)
|
# this is file.text instead of symlink.text so that it may be read over a remote mount (where consumers might not have any /nix/store/.../README.md path)
|
||||||
sane.fs."/var/media/archive/temp/README.md".file.text = ''
|
sane.fs."/var/media/archive/README.md".file.text = ''
|
||||||
this directory is for media i wish to remove from my library,
|
this directory is for media i wish to remove from my library,
|
||||||
but keep for a short time in case i reverse my decision.
|
but keep for a short time in case i reverse my decision.
|
||||||
treat it like a system trash can.
|
treat it like a system trash can.
|
||||||
@@ -81,6 +115,7 @@
|
|||||||
sane.fs."/var/media/Books/Books".dir = {};
|
sane.fs."/var/media/Books/Books".dir = {};
|
||||||
sane.fs."/var/media/Books/Visual".dir = {};
|
sane.fs."/var/media/Books/Visual".dir = {};
|
||||||
sane.fs."/var/media/collections".dir = {};
|
sane.fs."/var/media/collections".dir = {};
|
||||||
|
# sane.fs."/var/media/datasets".dir = {};
|
||||||
sane.fs."/var/media/freeleech".dir = {};
|
sane.fs."/var/media/freeleech".dir = {};
|
||||||
sane.fs."/var/media/Music".dir = {};
|
sane.fs."/var/media/Music".dir = {};
|
||||||
sane.fs."/var/media/Pictures".dir = {};
|
sane.fs."/var/media/Pictures".dir = {};
|
||||||
@@ -89,6 +124,13 @@
|
|||||||
sane.fs."/var/media/Videos/Shows".dir = {};
|
sane.fs."/var/media/Videos/Shows".dir = {};
|
||||||
sane.fs."/var/media/Videos/Talks".dir = {};
|
sane.fs."/var/media/Videos/Talks".dir = {};
|
||||||
|
|
||||||
|
# this is file.text instead of symlink.text so that it may be read over a remote mount (where consumers might not have any /nix/store/.../README.md path)
|
||||||
|
sane.fs."/var/lib/uninsane/datasets/README.md".file.text = ''
|
||||||
|
this directory may seem redundant with ../media/datasets. it isn't.
|
||||||
|
this directory exists on SSD, allowing for speedy access to specific datasets when necessary.
|
||||||
|
the contents should be a subset of what's in ../media/datasets.
|
||||||
|
'';
|
||||||
|
|
||||||
systemd.services.dedupe-media = {
|
systemd.services.dedupe-media = {
|
||||||
description = "transparently de-duplicate /var/media entries by using block-level hardlinks";
|
description = "transparently de-duplicate /var/media entries by using block-level hardlinks";
|
||||||
script = ''
|
script = ''
|
||||||
@@ -102,5 +144,28 @@
|
|||||||
OnUnitActiveSec = "720min";
|
OnUnitActiveSec = "720min";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# btrfs doesn't easily support swapfiles
|
||||||
|
# swapDevices = [
|
||||||
|
# { device = "/nix/persist/swapfile"; size = 4096; }
|
||||||
|
# ];
|
||||||
|
|
||||||
|
# this can be a partition. create with:
|
||||||
|
# fdisk <dev>
|
||||||
|
# n
|
||||||
|
# <default partno>
|
||||||
|
# <start>
|
||||||
|
# <end>
|
||||||
|
# t
|
||||||
|
# <partno>
|
||||||
|
# 19 # set part type to Linux swap
|
||||||
|
# w # write changes
|
||||||
|
# mkswap -L swap <part>
|
||||||
|
# swapDevices = [
|
||||||
|
# {
|
||||||
|
# label = "swap";
|
||||||
|
# # TODO: randomEncryption.enable = true;
|
||||||
|
# }
|
||||||
|
# ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
124
hosts/by-name/servo/net.nix
Normal file
124
hosts/by-name/servo/net.nix
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
portOpts = with lib; types.submodule {
|
||||||
|
options = {
|
||||||
|
visibleTo.ovpns = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
whether to forward inbound traffic on the OVPN vpn port to the corresponding localhost port.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
visibleTo.doof = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
whether to forward inbound traffic on the doofnet vpn port to the corresponding localhost port.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = with lib; {
|
||||||
|
sane.ports.ports = mkOption {
|
||||||
|
# add the `visibleTo.{doof,ovpns}` options
|
||||||
|
type = types.attrsOf portOpts;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
networking.domain = "uninsane.org";
|
||||||
|
systemd.network.networks."50-eth0" = {
|
||||||
|
matchConfig.Name = "eth0";
|
||||||
|
networkConfig.Address = [
|
||||||
|
"205.201.63.12/32"
|
||||||
|
"10.78.79.51/22"
|
||||||
|
];
|
||||||
|
networkConfig.DNS = [ "10.78.79.1" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.ports.openFirewall = true;
|
||||||
|
sane.ports.openUpnp = true;
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
# ];
|
||||||
|
|
||||||
|
# services.resolved.extraConfig = ''
|
||||||
|
# # docs: `man resolved.conf`
|
||||||
|
# # DNS servers to use via the `wg-ovpns` interface.
|
||||||
|
# # i hope that from the root ns, these aren't visible.
|
||||||
|
# DNS=46.227.67.134%wg-ovpns 192.165.9.158%wg-ovpns
|
||||||
|
# FallbackDNS=1.1.1.1 9.9.9.9
|
||||||
|
# '';
|
||||||
|
|
||||||
|
# tun-sea config
|
||||||
|
sane.dns.zones."uninsane.org".inet.A."doof.tunnel" = "205.201.63.12";
|
||||||
|
# sane.dns.zones."uninsane.org".inet.AAAA."doof.tunnel" = "2602:fce8:106::51"; #< TODO: enable IPv6
|
||||||
|
networking.wireguard.interfaces.wg-doof = {
|
||||||
|
privateKeyFile = config.sops.secrets.wg_doof_privkey.path;
|
||||||
|
# wg is active only in this namespace.
|
||||||
|
# run e.g. ip netns exec doof <some command like ping/curl/etc, it'll go through wg>
|
||||||
|
# sudo ip netns exec doof ping www.google.com
|
||||||
|
interfaceNamespace = "doof";
|
||||||
|
ips = [
|
||||||
|
"205.201.63.12"
|
||||||
|
# "2602:fce8:106::51/128" #< TODO: enable IPv6
|
||||||
|
];
|
||||||
|
peers = [
|
||||||
|
{
|
||||||
|
publicKey = "nuESyYEJ3YU0hTZZgAd7iHBz1ytWBVM5PjEL1VEoTkU=";
|
||||||
|
# TODO: configure DNS within the doof ns and use tun-sea.doof.net endpoint
|
||||||
|
# endpoint = "tun-sea.doof.net:53263";
|
||||||
|
endpoint = "205.201.63.44:53263";
|
||||||
|
allowedIPs = [ "0.0.0.0/0" "::/0" ];
|
||||||
|
persistentKeepalive = 25; #< keep the NAT alive
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
sane.netns.doof.hostVethIpv4 = "10.0.2.5";
|
||||||
|
sane.netns.doof.netnsVethIpv4 = "10.0.2.6";
|
||||||
|
sane.netns.doof.netnsPubIpv4 = "205.201.63.12";
|
||||||
|
sane.netns.doof.routeTable = 12;
|
||||||
|
|
||||||
|
# OVPN CONFIG (https://www.ovpn.com):
|
||||||
|
# DOCS: https://nixos.wiki/wiki/WireGuard
|
||||||
|
# if you `systemctl restart wireguard-wg-ovpns`, 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.wg-ovpns = {
|
||||||
|
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
|
||||||
|
interfaceNamespace = "ovpns";
|
||||||
|
ips = [ "185.157.162.178" ];
|
||||||
|
peers = [
|
||||||
|
{
|
||||||
|
publicKey = "SkkEZDCBde22KTs/Hc7FWvDBfdOCQA4YtBEuC3n5KGs=";
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
sane.netns.ovpns.hostVethIpv4 = "10.0.1.5";
|
||||||
|
sane.netns.ovpns.netnsVethIpv4 = "10.0.1.6";
|
||||||
|
sane.netns.ovpns.netnsPubIpv4 = "185.157.162.178";
|
||||||
|
sane.netns.ovpns.routeTable = 11;
|
||||||
|
sane.netns.ovpns.dns = "46.227.67.134"; #< DNS requests inside the namespace are forwarded here
|
||||||
|
};
|
||||||
|
}
|
@@ -1,60 +0,0 @@
|
|||||||
# debugging:
|
|
||||||
# - enable logs (shows handshake attempts)
|
|
||||||
# - `echo module wireguard +p | sane-sudo-redirect /sys/kernel/debug/dynamic_debug/control`
|
|
||||||
# - `sudo dmesg --follow`
|
|
||||||
# patterns: "Sending keepalive packet to peer NN (N.N.N.N:NNNNN)"
|
|
||||||
# patterns: "Sending handshake initiation to peer NN (N.N.N.N:NNNNN)"
|
|
||||||
# - when wg-doof and wg-ovpns stop routing traffic, restart with:
|
|
||||||
# - `systemctl restart netns-doof-wg`
|
|
||||||
# - handshaking:
|
|
||||||
# - `wg show` should *always* show "latest handshake: N", with N < 2 minutes ago.
|
|
||||||
{ lib, ... }:
|
|
||||||
let
|
|
||||||
portOpts = with lib; types.submodule {
|
|
||||||
options = {
|
|
||||||
visibleTo.ovpns = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = ''
|
|
||||||
whether to forward inbound traffic on the OVPN vpn port to the corresponding localhost port.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
visibleTo.doof = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = ''
|
|
||||||
whether to forward inbound traffic on the doofnet vpn port to the corresponding localhost port.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options = with lib; {
|
|
||||||
sane.ports.ports = mkOption {
|
|
||||||
# add the `visibleTo.{doof,ovpns}` options
|
|
||||||
type = types.attrsOf portOpts;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
./doof.nix
|
|
||||||
./ovpn.nix
|
|
||||||
./wg-home.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
config = {
|
|
||||||
networking.domain = "uninsane.org";
|
|
||||||
systemd.network.networks."50-eth0" = {
|
|
||||||
matchConfig.Name = "eth0";
|
|
||||||
networkConfig.Address = [
|
|
||||||
"205.201.63.12/32"
|
|
||||||
"10.78.79.51/22"
|
|
||||||
];
|
|
||||||
networkConfig.DNS = [ "10.78.79.1" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.ports.openFirewall = true;
|
|
||||||
sane.ports.openUpnp = true;
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,27 +0,0 @@
|
|||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
# tun-sea config
|
|
||||||
sane.dns.zones."uninsane.org".inet.A."doof.tunnel" = "205.201.63.12";
|
|
||||||
# sane.dns.zones."uninsane.org".inet.AAAA."doof.tunnel" = "2602:fce8:106::51"; #< TODO: enable IPv6 (i have /128)
|
|
||||||
|
|
||||||
# if the tunnel breaks, restart it manually:
|
|
||||||
# - `systemctl restart netns-doof.service`
|
|
||||||
sane.netns.doof = {
|
|
||||||
veth.initns.ipv4 = "10.0.2.5";
|
|
||||||
veth.netns.ipv4 = "10.0.2.6";
|
|
||||||
routeTable = 12;
|
|
||||||
# wg.port = 51821;
|
|
||||||
wg.privateKeyFile = config.sops.secrets.wg_doof_privkey.path;
|
|
||||||
wg.address.ipv4 = "205.201.63.12";
|
|
||||||
wg.peer.publicKey = "nuESyYEJ3YU0hTZZgAd7iHBz1ytWBVM5PjEL1VEoTkU=";
|
|
||||||
wg.peer.endpoint = "tun-sea.doof.net:53263";
|
|
||||||
# wg.peer.endpoint = "205.201.63.44:53263";
|
|
||||||
};
|
|
||||||
|
|
||||||
# inside doof, forward DNS requests back to the root machine
|
|
||||||
# this is fine: nothing inside the ns performs DNS except for wireguard,
|
|
||||||
# and we're not forwarding external DNS requests here
|
|
||||||
# XXX: ACTUALLY, CAN'T EASILY DO THAT BECAUSE HICKORY-DNS IS ALREADY USING PORT 53
|
|
||||||
# but that's ok, we don't really need DNS *inside* this namespace.
|
|
||||||
# sane.netns.doof.dns.ipv4 = config.sane.netns.doof.veth.netns.ipv4;
|
|
||||||
}
|
|
@@ -1,20 +0,0 @@
|
|||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
sane.ovpn.addrV4 = "172.23.174.114"; #< this applies to the dynamic VPNs -- NOT the static VPN
|
|
||||||
# sane.ovpn.addrV6 = "fd00:0000:1337:cafe:1111:1111:8df3:14b0";
|
|
||||||
|
|
||||||
# OVPN CONFIG (https://www.ovpn.com):
|
|
||||||
# DOCS: https://nixos.wiki/wiki/WireGuard
|
|
||||||
sane.netns.ovpns = {
|
|
||||||
veth.initns.ipv4 = "10.0.1.5";
|
|
||||||
veth.netns.ipv4 = "10.0.1.6";
|
|
||||||
routeTable = 11;
|
|
||||||
dns.ipv4 = "46.227.67.134"; #< DNS requests inside the namespace are forwarded here
|
|
||||||
# wg.port = 51822;
|
|
||||||
wg.privateKeyFile = config.sops.secrets.wg_ovpns_privkey.path;
|
|
||||||
wg.address.ipv4 = "146.70.100.165"; #< IP address for my end of the VPN tunnel. for OVPN public IPv4, this is also the public IP address.
|
|
||||||
wg.peer.publicKey = "xc9p/lf2uLg6IGDh54E0Pbc6WI/J9caaByhwD4Uiu0Q="; #< pubkey by which i can authenticate OVPN, varies per OVPN endpoint
|
|
||||||
wg.peer.endpoint = "vpn31.prd.losangeles.ovpn.com:9930";
|
|
||||||
# wg.peer.endpoint = "45.83.89.131:9930";
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
sane.services.wg-home.enable = true;
|
|
||||||
sane.services.wg-home.visibleToWan = true;
|
|
||||||
sane.services.wg-home.forwardToWan = true;
|
|
||||||
sane.services.wg-home.routeThroughServo = false;
|
|
||||||
services.unbound.settings.server.interface = [
|
|
||||||
# provide DNS to my wireguard clients
|
|
||||||
config.sane.hosts.by-name."servo".wg-home.ip
|
|
||||||
];
|
|
||||||
services.unbound.settings.server.access-control = [
|
|
||||||
"${config.sane.hosts.by-name."servo".wg-home.ip}/24 allow"
|
|
||||||
];
|
|
||||||
}
|
|
@@ -1,70 +0,0 @@
|
|||||||
# bitmagnet is a DHT crawler. it discovers publicly reachable torrents and indexes:
|
|
||||||
# - torrent's magnet URI
|
|
||||||
# - torrent's name
|
|
||||||
# - torrent's file list (the first 100 files, per torrent), including size and "type" (e.g. video)
|
|
||||||
# - seeder/leecher counts
|
|
||||||
# - torrent's size
|
|
||||||
# it provides a web UI to query these, especially a search form.
|
|
||||||
# data is stored in postgresql as `bitmagnet` db (`sudo -u bitmagnet psql`)
|
|
||||||
# after 30 days of operation:
|
|
||||||
# - 12m torrents discovered
|
|
||||||
# - 77GB database size => 6500B per torrent
|
|
||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
services.bitmagnet.enable = true;
|
|
||||||
sane.netns.ovpns.services = [ "bitmagnet" ];
|
|
||||||
sane.ports.ports."3334" = {
|
|
||||||
protocol = [ "tcp" "udp" ];
|
|
||||||
# visibleTo.ovpns = true; #< not needed: it runs in the ovpns namespace
|
|
||||||
description = "colin-bitmagnet";
|
|
||||||
};
|
|
||||||
|
|
||||||
services.bitmagnet.settings = {
|
|
||||||
# dht_crawler.scaling_factor: how rapidly to crawl the DHT.
|
|
||||||
# influences number of worker threads, buffer sizes, etc.
|
|
||||||
# default: 10.
|
|
||||||
# docs claim "diminishing returns" above 10, but seems weakly confident about that.
|
|
||||||
dht_crawler.scaling_factor = 64;
|
|
||||||
# http_server.local_address: `$addr:$port` to `listen` to.
|
|
||||||
# default is `:3333`, which listens on _all_ interfaces.
|
|
||||||
# the http server exposes unprotected admin endpoints though, so restrict to private interfaces:
|
|
||||||
http_server.local_address = "${config.sane.netns.ovpns.veth.netns.ipv4}:3333";
|
|
||||||
# tmdb.enabled: whether to query The Movie DataBase to resolve filename -> movie title.
|
|
||||||
# default: true.
|
|
||||||
# docs claim 1 query per second rate limit, unless you supply your own API key.
|
|
||||||
tmdb.enabled = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
# bitmagnet web client
|
|
||||||
# protected by passwd because it exposes some mutation operations:
|
|
||||||
# - queuing "jobs"
|
|
||||||
# - deleting torrent infos (in bulk)
|
|
||||||
# it uses graphql for _everything_, so no easy way to disable just the mutations (and remove the password) AFAICT.
|
|
||||||
services.nginx.virtualHosts."bitmagnet.uninsane.org" = {
|
|
||||||
# basicAuth is cleartext user/pw, so FORCE this to happen over SSL
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "http://${config.sane.netns.ovpns.veth.netns.ipv4}:3333";
|
|
||||||
recommendedProxySettings = true;
|
|
||||||
};
|
|
||||||
basicAuthFile = config.sops.secrets.bitmagnet_passwd.path;
|
|
||||||
};
|
|
||||||
sops.secrets."bitmagnet_passwd" = {
|
|
||||||
owner = config.users.users.nginx.name;
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.dns.zones."uninsane.org".inet.CNAME."bitmagnet" = "native";
|
|
||||||
|
|
||||||
systemd.services.bitmagnet = {
|
|
||||||
# hardening (systemd-analyze security bitmagnet). base nixos service is already partially hardened.
|
|
||||||
serviceConfig.CapabilityBoundingSet = "";
|
|
||||||
serviceConfig.SystemCallArchitectures = "native";
|
|
||||||
serviceConfig.PrivateDevices = true;
|
|
||||||
serviceConfig.PrivateUsers = true;
|
|
||||||
serviceConfig.ProtectProc = "invisible";
|
|
||||||
serviceConfig.ProcSubset = "pid";
|
|
||||||
serviceConfig.SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
|
|
||||||
};
|
|
||||||
}
|
|
@@ -104,6 +104,13 @@ in
|
|||||||
SRV."_turns._tcp" = "5 50 5349 turn";
|
SRV."_turns._tcp" = "5 50 5349 turn";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sane.derived-secrets."/var/lib/coturn/shared_secret.bin" = {
|
||||||
|
encoding = "base64";
|
||||||
|
# TODO: make this not globally readable
|
||||||
|
acl.mode = "0644";
|
||||||
|
};
|
||||||
|
sane.fs."/var/lib/coturn/shared_secret.bin".wantedBeforeBy = [ "coturn.service" ];
|
||||||
|
|
||||||
# provide access to certs
|
# provide access to certs
|
||||||
users.users.turnserver.extraGroups = [ "nginx" ];
|
users.users.turnserver.extraGroups = [ "nginx" ];
|
||||||
|
|
||||||
@@ -112,14 +119,9 @@ in
|
|||||||
services.coturn.cert = "/var/lib/acme/turn.uninsane.org/fullchain.pem";
|
services.coturn.cert = "/var/lib/acme/turn.uninsane.org/fullchain.pem";
|
||||||
services.coturn.pkey = "/var/lib/acme/turn.uninsane.org/key.pem";
|
services.coturn.pkey = "/var/lib/acme/turn.uninsane.org/key.pem";
|
||||||
|
|
||||||
# N.B.: prosody needs to read this shared secret
|
|
||||||
sops.secrets."coturn_shared_secret".owner = "turnserver";
|
|
||||||
sops.secrets."coturn_shared_secret".group = "turnserver";
|
|
||||||
sops.secrets."coturn_shared_secret".mode = "0440";
|
|
||||||
|
|
||||||
#v disable to allow unauthenticated access (or set `services.coturn.no-auth = true`)
|
#v disable to allow unauthenticated access (or set `services.coturn.no-auth = true`)
|
||||||
services.coturn.use-auth-secret = true;
|
services.coturn.use-auth-secret = true;
|
||||||
services.coturn.static-auth-secret-file = "/run/secrets/coturn_shared_secret";
|
services.coturn.static-auth-secret-file = "/var/lib/coturn/shared_secret.bin";
|
||||||
services.coturn.lt-cred-mech = true; #< XXX: use-auth-secret overrides lt-cred-mech
|
services.coturn.lt-cred-mech = true; #< XXX: use-auth-secret overrides lt-cred-mech
|
||||||
|
|
||||||
services.coturn.min-port = turnPortLow;
|
services.coturn.min-port = turnPortLow;
|
||||||
@@ -129,11 +131,11 @@ in
|
|||||||
"verbose"
|
"verbose"
|
||||||
# "Verbose" #< even MORE verbosity than "verbose" (it's TOO MUCH verbosity really)
|
# "Verbose" #< even MORE verbosity than "verbose" (it's TOO MUCH verbosity really)
|
||||||
"no-multicast-peers" # disables sending to IPv4 broadcast addresses (e.g. 224.0.0.0/3)
|
"no-multicast-peers" # disables sending to IPv4 broadcast addresses (e.g. 224.0.0.0/3)
|
||||||
# "listening-ip=${config.sane.netns.ovpns.veth.initns.ipv4}" "external-ip=${config.sane.netns.ovpns.wg.address.ipv4}" #< 2024/04/25: works, if running in root namespace
|
# "listening-ip=${config.sane.netns.ovpns.hostVethIpv4}" "external-ip=${config.sane.netns.ovpns.netnsPubIpv4}" #< 2024/04/25: works, if running in root namespace
|
||||||
"listening-ip=${config.sane.netns.ovpns.wg.address.ipv4}" "external-ip=${config.sane.netns.ovpns.wg.address.ipv4}"
|
"listening-ip=${config.sane.netns.ovpns.netnsPubIpv4}" "external-ip=${config.sane.netns.ovpns.netnsPubIpv4}"
|
||||||
|
|
||||||
# old attempts:
|
# old attempts:
|
||||||
# "external-ip=${config.sane.netns.ovpns.wg.address.ipv4}/${config.sane.netns.ovpns.veth.initns.ipv4}"
|
# "external-ip=${config.sane.netns.ovpns.netnsPubIpv4}/${config.sane.netns.ovpns.hostVethIpv4}"
|
||||||
# "listening-ip=10.78.79.51" # can be specified multiple times; omit for *
|
# "listening-ip=10.78.79.51" # can be specified multiple times; omit for *
|
||||||
# "external-ip=97.113.128.229/10.78.79.51"
|
# "external-ip=97.113.128.229/10.78.79.51"
|
||||||
# "external-ip=97.113.128.229"
|
# "external-ip=97.113.128.229"
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
# as of 2023/12/02: complete blockchain is 530 GiB (on-disk size may be larger)
|
# as of 2023/12/02: complete blockchain is 530 GiB (on-disk size may be larger)
|
||||||
# as of 2025/08/06: on-disk blockchain as reported by `du` is 732 GiB
|
|
||||||
#
|
#
|
||||||
# ports:
|
# ports:
|
||||||
# - 8333: for node-to-node communications
|
# - 8333: for node-to-node communications
|
||||||
@@ -23,7 +22,7 @@ let
|
|||||||
_bitcoindWithExternalIp = pkgs.writeShellScriptBin "bitcoind" ''
|
_bitcoindWithExternalIp = pkgs.writeShellScriptBin "bitcoind" ''
|
||||||
set -xeu
|
set -xeu
|
||||||
externalip="$(cat /var/lib/tor/onion/bitcoind/hostname)"
|
externalip="$(cat /var/lib/tor/onion/bitcoind/hostname)"
|
||||||
exec ${lib.getExe' bitcoind "bitcoind"} "-externalip=$externalip" "$@"
|
exec ${bitcoind}/bin/bitcoind "-externalip=$externalip" "$@"
|
||||||
'';
|
'';
|
||||||
# the package i provide to services.bitcoind ends up on system PATH, and used by other tools like clightning.
|
# the package i provide to services.bitcoind ends up on system PATH, and used by other tools like clightning.
|
||||||
# therefore, even though services.bitcoind only needs `bitcoind` binary, provide all the other bitcoin-related binaries (notably `bitcoin-cli`) as well:
|
# therefore, even though services.bitcoind only needs `bitcoind` binary, provide all the other bitcoin-related binaries (notably `bitcoin-cli`) as well:
|
||||||
@@ -73,18 +72,13 @@ in
|
|||||||
proxy=127.0.0.1:9050
|
proxy=127.0.0.1:9050
|
||||||
'';
|
'';
|
||||||
extraCmdlineOptions = [
|
extraCmdlineOptions = [
|
||||||
# `man bitcoind` for options
|
|
||||||
# "-assumevalid=0" # to perform script validation on all blocks, instead of just the latest checkpoint published by bitcoin-core
|
|
||||||
# "-debug"
|
# "-debug"
|
||||||
# "-debug=estimatefee"
|
# "-debug=estimatefee"
|
||||||
# "-debug=leveldb"
|
|
||||||
# "-debug=http"
|
# "-debug=http"
|
||||||
# "-debug=net"
|
# "-debug=net"
|
||||||
"-debug=proxy"
|
"-debug=proxy"
|
||||||
"-debug=rpc"
|
"-debug=rpc"
|
||||||
# "-debug=validation"
|
# "-debug=validation"
|
||||||
# "-reindex" # wipe chainstate, block index, other indices; rebuild from blk*.dat (takes 2.5hrs)
|
|
||||||
# "-reindex-chainstate" # wipe chainstate; rebuild from blk*.dat
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -115,24 +115,16 @@
|
|||||||
# - fee-per-satoshi=<ppm>
|
# - fee-per-satoshi=<ppm>
|
||||||
# - feature configs (i.e. experimental-xyz options)
|
# - feature configs (i.e. experimental-xyz options)
|
||||||
sane.services.clightning.extraConfig = ''
|
sane.services.clightning.extraConfig = ''
|
||||||
# log levels: "io", "trace", "debug", "info", "unusual", "broken"
|
# log levels: "io", "debug", "info", "unusual", "broken"
|
||||||
# log-level=info
|
log-level=info
|
||||||
# log-level=info:lightningd
|
# log-level=info:lightningd
|
||||||
# log-level=debug:lightningd
|
# log-level=debug:lightningd
|
||||||
log-level=debug
|
# log-level=debug
|
||||||
# log-level=io
|
|
||||||
|
|
||||||
disable-plugin=cln-xpay
|
|
||||||
|
|
||||||
# let me use `lightning-cli dev-*` subcommands, fucktards.
|
|
||||||
developer
|
|
||||||
# `developer` enables `dev-*` but *disables* the older commands. asshats.
|
|
||||||
allow-deprecated-apis=true
|
|
||||||
|
|
||||||
# peerswap:
|
# peerswap:
|
||||||
# - config example: <https://github.com/fort-nix/nix-bitcoin/pull/462/files#diff-b357d832705b8ce8df1f41934d613f79adb77c4cd5cd9e9eb12a163fca3e16c6>
|
# - config example: <https://github.com/fort-nix/nix-bitcoin/pull/462/files#diff-b357d832705b8ce8df1f41934d613f79adb77c4cd5cd9e9eb12a163fca3e16c6>
|
||||||
# XXX: peerswap crashes clightning on launch. stacktrace is useless.
|
# XXX: peerswap crashes clightning on launch. stacktrace is useless.
|
||||||
# plugin={lib.getExe' pkgs.peerswap "peerswap"}
|
# plugin={pkgs.peerswap}/bin/peerswap
|
||||||
# peerswap-db-path=/var/lib/clightning/peerswap/swaps
|
# peerswap-db-path=/var/lib/clightning/peerswap/swaps
|
||||||
# peerswap-policy-path=...
|
# peerswap-policy-path=...
|
||||||
'';
|
'';
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./bitmagnet.nix
|
|
||||||
./coturn.nix
|
./coturn.nix
|
||||||
./cryptocurrencies
|
./cryptocurrencies
|
||||||
./email
|
./email
|
||||||
@@ -9,25 +8,23 @@
|
|||||||
./freshrss.nix
|
./freshrss.nix
|
||||||
./export
|
./export
|
||||||
./hickory-dns.nix
|
./hickory-dns.nix
|
||||||
./gerbera.nix
|
|
||||||
./gitea.nix
|
./gitea.nix
|
||||||
./goaccess.nix
|
./goaccess.nix
|
||||||
./ipfs.nix
|
./ipfs.nix
|
||||||
./jackett
|
./jackett
|
||||||
./jellyfin
|
./jellyfin.nix
|
||||||
./kiwix-serve.nix
|
./kiwix-serve.nix
|
||||||
./komga.nix
|
./komga.nix
|
||||||
./lemmy.nix
|
./lemmy.nix
|
||||||
./matrix
|
./matrix
|
||||||
./minidlna.nix
|
|
||||||
./mumble.nix
|
|
||||||
./navidrome.nix
|
./navidrome.nix
|
||||||
./nginx
|
./nginx.nix
|
||||||
./nixos-prebuild.nix
|
./nixos-prebuild.nix
|
||||||
./ntfy
|
./ntfy
|
||||||
|
./ollama.nix
|
||||||
./pict-rs.nix
|
./pict-rs.nix
|
||||||
./pleroma.nix
|
./pleroma.nix
|
||||||
./postgresql
|
./postgres.nix
|
||||||
./prosody
|
./prosody
|
||||||
./slskd.nix
|
./slskd.nix
|
||||||
./transmission
|
./transmission
|
||||||
|
@@ -457,12 +457,13 @@ lib.mkIf false
|
|||||||
mod_version = {};
|
mod_version = {};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
sed = "${pkgs.gnused}/bin/sed";
|
||||||
in ''
|
in ''
|
||||||
ip=$(cat '${config.sane.services.dyn-dns.ipPath}')
|
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
|
# 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?
|
# TODO: factor this out into `sane-woop` helper?
|
||||||
rm -f /var/lib/ejabberd/ejabberd.yaml.new
|
rm -f /var/lib/ejabberd/ejabberd.yaml.new
|
||||||
${lib.getExe pkgs.gnused} "s/%ANATIVE%/$ip/g" ${config-in} > /var/lib/ejabberd/ejabberd.yaml.new
|
${sed} "s/%ANATIVE%/$ip/g" ${config-in} > /var/lib/ejabberd/ejabberd.yaml.new
|
||||||
mv /var/lib/ejabberd/ejabberd.yaml{.new,}
|
mv /var/lib/ejabberd/ejabberd.yaml{.new,}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
@@ -25,10 +25,10 @@
|
|||||||
#
|
#
|
||||||
# debugging: general connectivity issues
|
# debugging: general connectivity issues
|
||||||
# - test that inbound port 25 is unblocked:
|
# - test that inbound port 25 is unblocked:
|
||||||
# - `curl https://canyouseeme.org/ --data 'port=25&IP=$MX_IP' | grep 'see your service'`
|
# - `curl https://canyouseeme.org/ --data 'port=25&IP=185.157.162.178' | grep 'see your service'`
|
||||||
# - and retry with port 465, 587
|
# - and retry with port 465, 587
|
||||||
# - i think this API requires the queried IP match the source IP
|
# - i think this API requires the queried IP match the source IP
|
||||||
# - if necessary, `systemctl stop postfix` and `sudo nc -l $MX_IP 25`, then try https://canyouseeme.org
|
# - if necessary, `systemctl stop postfix` and `sudo nc -l 185.157.162.178 25`, then try https://canyouseeme.org
|
||||||
|
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
|
@@ -124,9 +124,7 @@
|
|||||||
# ];
|
# ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
environment.systemPackages = [
|
services.dovecot2.modules = [
|
||||||
# XXX(2025-03-16): dovecot loads modules from /run/current-system/sw/lib/dovecot/modules
|
|
||||||
# see: <https://github.com/NixOS/nixpkgs/pull/387642>
|
|
||||||
pkgs.dovecot_pigeonhole # enables sieve execution (?)
|
pkgs.dovecot_pigeonhole # enables sieve execution (?)
|
||||||
];
|
];
|
||||||
services.dovecot2.sieve = {
|
services.dovecot2.sieve = {
|
||||||
@@ -143,5 +141,5 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.dovecot.serviceConfig.RestartSec = lib.mkForce "15s"; # nixos defaults this to 1s
|
systemd.services.dovecot2.serviceConfig.RestartSec = lib.mkForce "15s"; # nixos defaults this to 1s
|
||||||
}
|
}
|
||||||
|
@@ -99,10 +99,8 @@ in
|
|||||||
services.postfix.hostname = "mx.uninsane.org";
|
services.postfix.hostname = "mx.uninsane.org";
|
||||||
services.postfix.origin = "uninsane.org";
|
services.postfix.origin = "uninsane.org";
|
||||||
services.postfix.destination = [ "localhost" "uninsane.org" ];
|
services.postfix.destination = [ "localhost" "uninsane.org" ];
|
||||||
services.postfix.config.smtpd_tls_chain_files = [
|
services.postfix.sslCert = "/var/lib/acme/mx.uninsane.org/fullchain.pem";
|
||||||
"/var/lib/acme/mx.uninsane.org/key.pem"
|
services.postfix.sslKey = "/var/lib/acme/mx.uninsane.org/key.pem";
|
||||||
"/var/lib/acme/mx.uninsane.org/fullchain.pem"
|
|
||||||
];
|
|
||||||
|
|
||||||
# see: `man 5 virtual`
|
# see: `man 5 virtual`
|
||||||
services.postfix.virtual = ''
|
services.postfix.virtual = ''
|
||||||
@@ -114,7 +112,7 @@ in
|
|||||||
# smtpd_milters = local:/run/opendkim/opendkim.sock
|
# smtpd_milters = local:/run/opendkim/opendkim.sock
|
||||||
# milter docs: http://www.postfix.org/MILTER_README.html
|
# milter docs: http://www.postfix.org/MILTER_README.html
|
||||||
# mail filters for receiving email and from authorized SMTP clients (i.e. via submission)
|
# mail filters for receiving email and from authorized SMTP clients (i.e. via submission)
|
||||||
# smtpd_milters = inet:$IP:8891
|
# smtpd_milters = inet:185.157.162.190:8891
|
||||||
# opendkim.sock will add a Authentication-Results header, with `dkim=pass|fail|...` value to received messages
|
# opendkim.sock will add a Authentication-Results header, with `dkim=pass|fail|...` value to received messages
|
||||||
smtpd_milters = "unix:/run/opendkim/opendkim.sock";
|
smtpd_milters = "unix:/run/opendkim/opendkim.sock";
|
||||||
# mail filters for sendmail
|
# mail filters for sendmail
|
||||||
@@ -164,13 +162,16 @@ in
|
|||||||
services.postfix.enableSubmissions = true;
|
services.postfix.enableSubmissions = true;
|
||||||
services.postfix.submissionsOptions = submissionOptions;
|
services.postfix.submissionsOptions = submissionOptions;
|
||||||
|
|
||||||
|
systemd.services.postfix.after = [ "wireguard-wg-ovpns.service" ];
|
||||||
|
systemd.services.postfix.partOf = [ "wireguard-wg-ovpns.service" ];
|
||||||
systemd.services.postfix.unitConfig.RequiresMountsFor = [
|
systemd.services.postfix.unitConfig.RequiresMountsFor = [
|
||||||
"/var/spool/mail" # spooky errors when postfix is run w/o this: `warning: connect #1 to subsystem private/proxymap: Connection refused`
|
"/var/spool/mail" # spooky errors when postfix is run w/o this: `warning: connect #1 to subsystem private/proxymap: Connection refused`
|
||||||
"/var/lib/opendkim"
|
"/var/lib/opendkim"
|
||||||
];
|
];
|
||||||
|
systemd.services.postfix.serviceConfig = {
|
||||||
# run these behind the OVPN static VPN
|
# run this behind the OVPN static VPN
|
||||||
sane.netns.ovpns.services = [ "opendkim" "postfix" ];
|
NetworkNamespacePath = "/run/netns/ovpns";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
#### OPENDKIM
|
#### OPENDKIM
|
||||||
@@ -189,7 +190,11 @@ in
|
|||||||
# keeping this the same as the hostname seems simplest
|
# keeping this the same as the hostname seems simplest
|
||||||
services.opendkim.selector = "mx";
|
services.opendkim.selector = "mx";
|
||||||
|
|
||||||
|
systemd.services.opendkim.after = [ "wireguard-wg-ovpns.service" ];
|
||||||
|
systemd.services.opendkim.partOf = [ "wireguard-wg-ovpns.service" ];
|
||||||
systemd.services.opendkim.serviceConfig = {
|
systemd.services.opendkim.serviceConfig = {
|
||||||
|
# run this behind the OVPN static VPN
|
||||||
|
NetworkNamespacePath = "/run/netns/ovpns";
|
||||||
# /run/opendkim/opendkim.sock needs to be rw by postfix
|
# /run/opendkim/opendkim.sock needs to be rw by postfix
|
||||||
UMask = lib.mkForce "0011";
|
UMask = lib.mkForce "0011";
|
||||||
};
|
};
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
fileSystems."/var/export/media" = {
|
fileSystems."/var/export/media" = {
|
||||||
# everything in here could be considered publicly readable (based on the viewer's legal jurisdiction)
|
# everything in here could be considered publicly readable (based on the viewer's legal jurisdiction)
|
||||||
device = "/var/media";
|
device = "/var/media";
|
||||||
options = [ "rbind" "nofail" ];
|
options = [ "rbind" ];
|
||||||
};
|
};
|
||||||
# fileSystems."/var/export/playground" = {
|
# fileSystems."/var/export/playground" = {
|
||||||
# device = config.fileSystems."/mnt/persist/ext".device;
|
# device = config.fileSystems."/mnt/persist/ext".device;
|
||||||
@@ -34,16 +34,18 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
sane.fs."/var/export/README.md" = {
|
sane.fs."/var/export/README.md" = {
|
||||||
|
wantedBy = [ "nfs.service" "sftpgo.service" ];
|
||||||
file.text = ''
|
file.text = ''
|
||||||
- media/ read-only: Videos, Music, Books, etc
|
- media/ read-only: Videos, Music, Books, etc
|
||||||
- playground/ read-write*: use it to share files with other users of this server, inaccessible from the www
|
- playground/ read-write: use it to share files with other users of this server, inaccessible from the www
|
||||||
*if you can't write to it, make sure you're connected to the WiFi and not mobile.
|
- pub/ read-only: content made to be shared with the www
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
sane.fs."/var/export/playground/README.md" = {
|
sane.fs."/var/export/playground/README.md" = {
|
||||||
|
wantedBy = [ "nfs.service" "sftpgo.service" ];
|
||||||
file.text = ''
|
file.text = ''
|
||||||
this directory is intentionally read+write by anyone with access.
|
this directory is intentionally read+write by anyone with access (i.e. on the LAN).
|
||||||
- share files
|
- share files
|
||||||
- write poetry
|
- write poetry
|
||||||
- be a friendly troll
|
- be a friendly troll
|
||||||
@@ -51,6 +53,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
sane.fs."/var/export/.public_for_test/test" = {
|
sane.fs."/var/export/.public_for_test/test" = {
|
||||||
|
wantedBy = [ "nfs.service" "sftpgo.service" ];
|
||||||
file.text = ''
|
file.text = ''
|
||||||
automated tests read this file to probe connectivity
|
automated tests read this file to probe connectivity
|
||||||
'';
|
'';
|
||||||
|
@@ -102,14 +102,14 @@ in
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
# binding this means any doof client can connect (TLS only)
|
# binding this means any doof client can connect (TLS only)
|
||||||
address = config.sane.netns.doof.veth.initns.ipv4;
|
address = config.sane.netns.doof.hostVethIpv4;
|
||||||
port = 990;
|
port = 990;
|
||||||
debug = true;
|
debug = true;
|
||||||
tls_mode = 2; # 2 = "implicit FTPS": client negotiates TLS before any FTP command.
|
tls_mode = 2; # 2 = "implicit FTPS": client negotiates TLS before any FTP command.
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
# binding this means any LAN client can connect via `ftp.uninsane.org` (TLS only)
|
# binding this means any LAN client can connect via `ftp.uninsane.org` (TLS only)
|
||||||
address = config.sane.netns.doof.wg.address.ipv4;
|
address = config.sane.netns.doof.netnsPubIpv4;
|
||||||
port = 990;
|
port = 990;
|
||||||
debug = true;
|
debug = true;
|
||||||
tls_mode = 2; # 2 = "implicit FTPS": client negotiates TLS before any FTP command.
|
tls_mode = 2; # 2 = "implicit FTPS": client negotiates TLS before any FTP command.
|
||||||
@@ -141,7 +141,7 @@ in
|
|||||||
};
|
};
|
||||||
data_provider = {
|
data_provider = {
|
||||||
driver = "memory";
|
driver = "memory";
|
||||||
external_auth_hook = lib.getExe external_auth_hook;
|
external_auth_hook = "${external_auth_hook}/bin/external_auth_hook";
|
||||||
# track_quota:
|
# track_quota:
|
||||||
# - 0: disable quota tracking
|
# - 0: disable quota tracking
|
||||||
# - 1: quota is updated on every upload/delete, even if user has no quota restriction
|
# - 1: quota is updated on every upload/delete, even if user has no quota restriction
|
||||||
@@ -158,15 +158,14 @@ in
|
|||||||
];
|
];
|
||||||
|
|
||||||
systemd.services.sftpgo = {
|
systemd.services.sftpgo = {
|
||||||
after = [ "network-online.target" ]; #< so that it reliably binds to all interfaces/netns's?
|
after = [ "network-online.target" ];
|
||||||
wants = [ "network-online.target" ];
|
wants = [ "network-online.target" ];
|
||||||
unitConfig.RequiresMountsFor = [
|
serviceConfig = {
|
||||||
"/var/export/media"
|
ReadWritePaths = [ "/var/export" ];
|
||||||
"/var/export/playground"
|
|
||||||
];
|
Restart = "always";
|
||||||
serviceConfig.ReadWritePaths = [ "/var/export" ];
|
RestartSec = "20s";
|
||||||
serviceConfig.Restart = "always";
|
UMask = lib.mkForce "0002";
|
||||||
serviceConfig.RestartSec = "20s";
|
};
|
||||||
serviceConfig.UMask = lib.mkForce "0002";
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -69,12 +69,8 @@ TRUSTED_CREDS = [
|
|||||||
# /etc/shadow style creds.
|
# /etc/shadow style creds.
|
||||||
# mkpasswd -m sha-512
|
# mkpasswd -m sha-512
|
||||||
# $<method>$<salt>$<hash>
|
# $<method>$<salt>$<hash>
|
||||||
"$6$Zq3c2u4ghUH4S6EP$pOuRt13sEKfX31OqPbbd1LuhS21C9MICMc94iRdTAgdAcJ9h95gQH/6Jf6Ie4Obb0oxQtojRJ1Pd/9QHOlFMW.", #< m. rocket boy
|
"$6$Zq3c2u4ghUH4S6EP$pOuRt13sEKfX31OqPbbd1LuhS21C9MICMc94iRdTAgdAcJ9h95gQH/6Jf6Ie4Obb0oxQtojRJ1Pd/9QHOlFMW." #< m. rocket boy
|
||||||
"$6$B0NLGNdCL51PNse1$46G.aA1ATWIv5v.jUsKf4F3NS7emV2jB2gkZ3MytZtMvw2pjniHmRl0fywRjKW9TuXTeK9T50v.H0f2BaQ4PT1", #< v. telephony
|
|
||||||
]
|
]
|
||||||
TRUSTED_VIEWING_OR_PLAYGROUND_CREDS = [
|
|
||||||
# "$6$iikDajz5b.YH1.on$tfSzzBEtX8IeDiJJXCasOTxRTd7cFDKXU6dhlWYVhK6xDeJhV2fh6bmm1WIHItjIth9Eh9zNgUB8xibMIWCm/." # fedi (2024-08-27); music appreciation
|
|
||||||
];
|
|
||||||
|
|
||||||
def mkAuthOk(username: str, permissions: dict[str, list[str]]) -> dict:
|
def mkAuthOk(username: str, permissions: dict[str, list[str]]) -> dict:
|
||||||
return dict(
|
return dict(
|
||||||
@@ -116,8 +112,8 @@ def isLan(ip: str) -> bool:
|
|||||||
def isWireguard(ip: str) -> bool:
|
def isWireguard(ip: str) -> bool:
|
||||||
return ip.startswith("10.0.10.")
|
return ip.startswith("10.0.10.")
|
||||||
|
|
||||||
def isTrustedCred(password: str, credlist: list[str] = TRUSTED_CREDS) -> bool:
|
def isTrustedCred(password: str) -> bool:
|
||||||
for cred in credlist:
|
for cred in TRUSTED_CREDS:
|
||||||
if passlib.hosts.linux_context.verify(password, cred):
|
if passlib.hosts.linux_context.verify(password, cred):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -134,22 +130,6 @@ def getAuthResponse(ip: str, username: str, password: str) -> dict:
|
|||||||
"/": PERM_RW,
|
"/": PERM_RW,
|
||||||
"/playground": PERM_RW,
|
"/playground": PERM_RW,
|
||||||
"/.public_for_test": PERM_RO,
|
"/.public_for_test": PERM_RO,
|
||||||
"/media/Music": PERM_RO, #< i am too picky about Music organization
|
|
||||||
})
|
|
||||||
if isTrustedCred(password, TRUSTED_VIEWING_OR_PLAYGROUND_CREDS) and username != "colin":
|
|
||||||
return mkAuthOk(username, permissions = {
|
|
||||||
# error prone, but... not the worst if i miss something
|
|
||||||
"/": PERM_LIST,
|
|
||||||
"/media/archive": PERM_DENY,
|
|
||||||
"/media/Books": PERM_RO,
|
|
||||||
"/media/collections": PERM_DENY,
|
|
||||||
"/media/games": PERM_RO,
|
|
||||||
"/media/Music": PERM_RO,
|
|
||||||
"/media/Pictures": PERM_RO,
|
|
||||||
"/media/torrents": PERM_DENY,
|
|
||||||
"/media/Videos": PERM_RO,
|
|
||||||
"/playground": PERM_RW,
|
|
||||||
"/.public_for_test": PERM_RO,
|
|
||||||
})
|
})
|
||||||
if isWireguard(ip):
|
if isWireguard(ip):
|
||||||
# allow any user from wireguard
|
# allow any user from wireguard
|
||||||
|
@@ -1,38 +0,0 @@
|
|||||||
# gerbera UPNP/media server
|
|
||||||
# accessible from TVs on the LAN
|
|
||||||
# unauthenticated admin and playback UI at http://servo:49152/
|
|
||||||
#
|
|
||||||
# supposedly does transcoding, but i poked at it for 10 minutes and couldn't get that working
|
|
||||||
#
|
|
||||||
# compatibility:
|
|
||||||
# - LG TV: music: all working
|
|
||||||
# - LG TV: videos: mixed
|
|
||||||
{ lib, ... }:
|
|
||||||
lib.mkIf false #< XXX(2024-11-17): WORKS, but no better than any other service; slow to index and transcoding doesn't work
|
|
||||||
{
|
|
||||||
sane.ports.ports."1900" = {
|
|
||||||
protocol = [ "udp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
description = "colin-upnp-for-gerbera";
|
|
||||||
};
|
|
||||||
sane.ports.ports."49152" = {
|
|
||||||
protocol = [ "tcp" "udp" ]; # TODO: is udp required?
|
|
||||||
visibleTo.lan = true;
|
|
||||||
description = "colin-gerbera-http";
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.persist.sys.byStore.plaintext = [
|
|
||||||
# persist the index database, since it takes a good 30 minutes to scan the media collection
|
|
||||||
{ user = "mediatomb"; group = "mediatomb"; mode = "0700"; path = "/var/lib/gerbera"; method = "bind"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
services.mediatomb.enable = true;
|
|
||||||
services.mediatomb.serverName = "servo";
|
|
||||||
services.mediatomb.transcoding = true;
|
|
||||||
services.mediatomb.mediaDirectories = [
|
|
||||||
{ path = "/var/media/Music"; recursive = true; hidden-files = false; }
|
|
||||||
{ path = "/var/media/Videos/Film"; recursive = true; hidden-files = false; }
|
|
||||||
{ path = "/var/media/Videos/Shows"; recursive = true; hidden-files = false; }
|
|
||||||
];
|
|
||||||
users.users.mediatomb.extraGroups = [ "media" ];
|
|
||||||
}
|
|
@@ -11,23 +11,15 @@
|
|||||||
|
|
||||||
services.gitea.enable = true;
|
services.gitea.enable = true;
|
||||||
services.gitea.user = "git"; # default is 'gitea'
|
services.gitea.user = "git"; # default is 'gitea'
|
||||||
|
services.gitea.database.type = "postgres";
|
||||||
|
services.gitea.database.user = "git";
|
||||||
services.gitea.appName = "Perfectly Sane Git";
|
services.gitea.appName = "Perfectly Sane Git";
|
||||||
# services.gitea.disableRegistration = true;
|
# services.gitea.disableRegistration = true;
|
||||||
|
|
||||||
services.gitea.database.createDatabase = false; # can only createDatabase if user ("git") == dbname ("gitea")
|
services.gitea.database.createDatabase = false; #< silence warning which wants db user and name to be equal
|
||||||
services.gitea.database.type = "postgres";
|
# TODO: remove this after merge: <https://github.com/NixOS/nixpkgs/pull/268849>
|
||||||
services.gitea.database.user = "git";
|
|
||||||
# createDatabase=false means manually specify the connection; see: <https://github.com/NixOS/nixpkgs/pull/268849>
|
|
||||||
services.gitea.database.name = "gitea";
|
|
||||||
services.gitea.database.socket = "/run/postgresql"; #< would have been set if createDatabase = true
|
services.gitea.database.socket = "/run/postgresql"; #< would have been set if createDatabase = true
|
||||||
|
|
||||||
services.postgresql.enable = true;
|
|
||||||
services.postgresql.ensureDatabases = [ "gitea" ];
|
|
||||||
services.postgresql.ensureUsers = [{
|
|
||||||
name = "git";
|
|
||||||
# ensureDBOwnership = true; # not possible if db name ("gitea") != db username ("git"); one-time manual setup required to grant user ownership of the relevant db
|
|
||||||
}];
|
|
||||||
|
|
||||||
# gitea doesn't create the git user
|
# gitea doesn't create the git user
|
||||||
users.users.git = {
|
users.users.git = {
|
||||||
description = "Gitea Service";
|
description = "Gitea Service";
|
||||||
@@ -94,7 +86,7 @@
|
|||||||
ENABLED = true;
|
ENABLED = true;
|
||||||
FROM = "notify.git@uninsane.org";
|
FROM = "notify.git@uninsane.org";
|
||||||
PROTOCOL = "sendmail";
|
PROTOCOL = "sendmail";
|
||||||
SENDMAIL_PATH = lib.getExe' pkgs.postfix "sendmail";
|
SENDMAIL_PATH = "${pkgs.postfix}/bin/sendmail";
|
||||||
SENDMAIL_ARGS = "--"; # most "sendmail" programs take options, "--" will prevent an email address being interpreted as an option.
|
SENDMAIL_ARGS = "--"; # most "sendmail" programs take options, "--" will prevent an email address being interpreted as an option.
|
||||||
};
|
};
|
||||||
time = {
|
time = {
|
||||||
@@ -104,75 +96,37 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.gitea.wants = [ "postgresql.service" ];
|
|
||||||
systemd.services.gitea.serviceConfig = {
|
systemd.services.gitea.serviceConfig = {
|
||||||
# nix default is AF_UNIX AF_INET AF_INET6.
|
# nix default is AF_UNIX AF_INET AF_INET6.
|
||||||
# we need more protos for sendmail to work. i thought it only needed +AF_LOCAL, but that didn't work.
|
# we need more protos for sendmail to work. i thought it only needed +AF_LOCAL, but that didn't work.
|
||||||
RestrictAddressFamilies = lib.mkForce "~";
|
RestrictAddressFamilies = lib.mkForce "~";
|
||||||
# add maildrop to allow sendmail to work
|
# add maildrop to allow sendmail to work
|
||||||
ReadWritePaths = [
|
ReadWritePaths = lib.mkForce [
|
||||||
"/var/lib/postfix/queue/maildrop"
|
"/var/lib/postfix/queue/maildrop"
|
||||||
|
"/var/lib/gitea"
|
||||||
];
|
];
|
||||||
# rate limit the restarts to prevent systemd from disabling it
|
|
||||||
RestartSec = 5;
|
|
||||||
RestartMaxDelaySec = 30;
|
|
||||||
StartLimitBurst = 120;
|
|
||||||
RestartSteps = 5;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# services.openssh.settings.UsePAM = true; #< required for `git` user to authenticate
|
services.openssh.settings.UsePAM = true; #< required for `git` user to authenticate
|
||||||
|
|
||||||
services.anubis.instances."git.uninsane.org" = {
|
|
||||||
settings.TARGET = "http://127.0.0.1:3000";
|
|
||||||
# allow IM clients/etc to show embeds/previews, else they just show "please verify you aren't a bot..."
|
|
||||||
botPolicy.openGraph.enabled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
# hosted git (web view and for `git <cmd>` use
|
# hosted git (web view and for `git <cmd>` use
|
||||||
# TODO: enable publog?
|
# TODO: enable publog?
|
||||||
services.nginx.virtualHosts."git.uninsane.org" = let
|
services.nginx.virtualHosts."git.uninsane.org" = {
|
||||||
# XXX(2025-07-24): gitea's still being crawled, even with robots.txt.
|
|
||||||
# the load is less than when Anthropic first started, but it's still pretty high (like 600%).
|
|
||||||
# place behind anubis to prevent AI crawlers from hogging my CPU (gitea is slow to render pages).
|
|
||||||
proxyPassHeavy = "http://unix:${config.services.anubis.instances."git.uninsane.org".settings.BIND}";
|
|
||||||
# but anubis breaks embeds, so only protect the expensive repos.
|
|
||||||
proxyPassLight = "http://127.0.0.1:3000";
|
|
||||||
proxyTo = proxy: root: {
|
|
||||||
proxyPass = proxy;
|
|
||||||
recommendedProxySettings = true;
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
forceSSL = true; # gitea complains if served over a different protocol than its config file says
|
forceSSL = true; # gitea complains if served over a different protocol than its config file says
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
# inherit kTLS;
|
# inherit kTLS;
|
||||||
extraConfig = ''
|
|
||||||
client_max_body_size 100m;
|
|
||||||
'';
|
|
||||||
|
|
||||||
locations."/" = {
|
locations."/" = {
|
||||||
proxyPass = proxyPassLight;
|
proxyPass = "http://127.0.0.1:3000";
|
||||||
recommendedProxySettings = true;
|
|
||||||
};
|
};
|
||||||
# selectively proxy the heavyweight items through anubis.
|
|
||||||
# a typical interaction is:
|
|
||||||
# nginx:/colin/linux -> anubis:/colin/linux -> browser is served a loading page
|
|
||||||
# -> nginx:.within.website/x/cmd/anubis/api/pass-challenge?response=... -> anubis:.within.website/x/cmd/anubis/api/pass-challenge?response=... -> browser is forwarded to /colin/linux
|
|
||||||
# -> nginx:/colin/linux -> anubis:/colin/linux -> gitea:/colin/linux -> browser is served the actual content
|
|
||||||
locations."/.within.website/" = proxyTo proxyPassHeavy;
|
|
||||||
locations."/colin/linux" = proxyTo proxyPassHeavy;
|
|
||||||
locations."/colin/nixpkgs" = proxyTo proxyPassHeavy;
|
|
||||||
locations."/colin/opencellid-mirror" = proxyTo proxyPassHeavy;
|
|
||||||
locations."/colin/podcastindex-db-mirror" = proxyTo proxyPassHeavy;
|
|
||||||
|
|
||||||
# fuck you @anthropic
|
# fuck you @anthropic
|
||||||
# locations."= /robots.txt".extraConfig = ''
|
locations."= /robots.txt".extraConfig = ''
|
||||||
# return 200 "User-agent: *\nDisallow: /\n";
|
return 200 "User-agent: *\nDisallow: /\n";
|
||||||
# '';
|
'';
|
||||||
# gitea serves all `raw` files as content-type: plain, but i'd like to serve them as their actual content type.
|
# gitea serves all `raw` files as content-type: plain, but i'd like to serve them as their actual content type.
|
||||||
# or at least, enough to make specific pages viewable (serving unoriginal content as arbitrary content type is dangerous).
|
# or at least, enough to make specific pages viewable (serving unoriginal content as arbitrary content type is dangerous).
|
||||||
locations."~ ^/colin/phone-case-cq/raw/.*.html" = {
|
locations."~ ^/colin/phone-case-cq/raw/.*.html" = {
|
||||||
proxyPass = proxyPassLight;
|
proxyPass = "http://127.0.0.1:3000";
|
||||||
recommendedProxySettings = true;
|
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
proxy_hide_header Content-Type;
|
proxy_hide_header Content-Type;
|
||||||
default_type text/html;
|
default_type text/html;
|
||||||
@@ -180,8 +134,7 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
locations."~ ^/colin/phone-case-cq/raw/.*.js" = {
|
locations."~ ^/colin/phone-case-cq/raw/.*.js" = {
|
||||||
proxyPass = proxyPassLight;
|
proxyPass = "http://127.0.0.1:3000";
|
||||||
recommendedProxySettings = true;
|
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
proxy_hide_header Content-Type;
|
proxy_hide_header Content-Type;
|
||||||
default_type text/html;
|
default_type text/html;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
{ lib, pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
lib.mkIf false #< 2024/09/30: disabled because i haven't used it in several months
|
|
||||||
{
|
{
|
||||||
# based on <https://bytes.fyi/real-time-goaccess-reports-with-nginx/>
|
# based on <https://bytes.fyi/real-time-goaccess-reports-with-nginx/>
|
||||||
# log-format setting can be derived with this tool if custom:
|
# log-format setting can be derived with this tool if custom:
|
||||||
@@ -11,7 +10,7 @@ lib.mkIf false #< 2024/09/30: disabled because i haven't used it in several mon
|
|||||||
description = "GoAccess server monitoring";
|
description = "GoAccess server monitoring";
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = ''
|
ExecStart = ''
|
||||||
${lib.getExe pkgs.goaccess} \
|
${pkgs.goaccess}/bin/goaccess \
|
||||||
-f /var/log/nginx/public.log \
|
-f /var/log/nginx/public.log \
|
||||||
--log-format=VCOMBINED \
|
--log-format=VCOMBINED \
|
||||||
--real-time-html \
|
--real-time-html \
|
||||||
@@ -23,7 +22,7 @@ lib.mkIf false #< 2024/09/30: disabled because i haven't used it in several mon
|
|||||||
--port=7890 \
|
--port=7890 \
|
||||||
-o /var/lib/goaccess/index.html
|
-o /var/lib/goaccess/index.html
|
||||||
'';
|
'';
|
||||||
ExecReload = "${lib.getExe' pkgs.coreutils "kill"} -HUP $MAINPID";
|
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
RestartSec = "10s";
|
RestartSec = "10s";
|
||||||
@@ -56,7 +55,6 @@ lib.mkIf false #< 2024/09/30: disabled because i haven't used it in several mon
|
|||||||
|
|
||||||
locations."/ws" = {
|
locations."/ws" = {
|
||||||
proxyPass = "http://127.0.0.1:7890";
|
proxyPass = "http://127.0.0.1:7890";
|
||||||
recommendedProxySettings = true;
|
|
||||||
# XXX not sure how much of this is necessary
|
# XXX not sure how much of this is necessary
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# TODO: split this file apart into smaller files to make it easier to understand
|
# TODO: split this file apart into smaller files to make it easier to understand
|
||||||
{ config, lib, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
dyn-dns = config.sane.services.dyn-dns;
|
dyn-dns = config.sane.services.dyn-dns;
|
||||||
@@ -55,7 +55,8 @@ in
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
services.hickory-dns.settings.zones = builtins.attrNames config.sane.dns.zones;
|
services.hickory-dns.settings.zones = [ "uninsane.org" ];
|
||||||
|
|
||||||
|
|
||||||
networking.nat.enable = true; #< TODO: try removing this?
|
networking.nat.enable = true; #< TODO: try removing this?
|
||||||
# networking.nat.extraCommands = ''
|
# networking.nat.extraCommands = ''
|
||||||
@@ -85,9 +86,9 @@ in
|
|||||||
sane.services.hickory-dns.enable = true;
|
sane.services.hickory-dns.enable = true;
|
||||||
sane.services.hickory-dns.instances = let
|
sane.services.hickory-dns.instances = let
|
||||||
mkSubstitutions = flavor: {
|
mkSubstitutions = flavor: {
|
||||||
"%ADOOF%" = config.sane.netns.doof.wg.address.ipv4;
|
"%ADOOF%" = config.sane.netns.doof.netnsPubIpv4;
|
||||||
"%ANATIVE%" = nativeAddrs."servo.${flavor}";
|
"%ANATIVE%" = nativeAddrs."servo.${flavor}";
|
||||||
"%AOVPNS%" = config.sane.netns.ovpns.wg.address.ipv4;
|
"%AOVPNS%" = config.sane.netns.ovpns.netnsPubIpv4;
|
||||||
"%AWAN%" = "$(cat '${dyn-dns.ipPath}')";
|
"%AWAN%" = "$(cat '${dyn-dns.ipPath}')";
|
||||||
"%CNAMENATIVE%" = "servo.${flavor}";
|
"%CNAMENATIVE%" = "servo.${flavor}";
|
||||||
};
|
};
|
||||||
@@ -96,37 +97,37 @@ in
|
|||||||
doof = {
|
doof = {
|
||||||
substitutions = mkSubstitutions "doof";
|
substitutions = mkSubstitutions "doof";
|
||||||
listenAddrsIpv4 = [
|
listenAddrsIpv4 = [
|
||||||
config.sane.netns.doof.veth.initns.ipv4
|
config.sane.netns.doof.hostVethIpv4
|
||||||
config.sane.netns.doof.wg.address.ipv4
|
config.sane.netns.doof.netnsPubIpv4
|
||||||
nativeAddrs."servo.lan"
|
nativeAddrs."servo.lan"
|
||||||
# config.sane.netns.ovpns.veth.initns.ipv4
|
# config.sane.netns.ovpns.hostVethIpv4
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
# hn = {
|
hn = {
|
||||||
# substitutions = mkSubstitutions "hn";
|
substitutions = mkSubstitutions "hn";
|
||||||
# listenAddrsIpv4 = [ nativeAddrs."servo.hn" ];
|
listenAddrsIpv4 = [ nativeAddrs."servo.hn" ];
|
||||||
# enableRecursiveResolver = true; #< allow wireguard clients to use this as their DNS resolver
|
enableRecursiveResolver = true; #< allow wireguard clients to use this as their DNS resolver
|
||||||
# # extraConfig = {
|
# extraConfig = {
|
||||||
# # zones = [
|
# zones = [
|
||||||
# # {
|
# {
|
||||||
# # # forward the root zone to the local DNS resolver
|
# # forward the root zone to the local DNS resolver
|
||||||
# # # to allow wireguard clients to use this as their DNS resolver
|
# # to allow wireguard clients to use this as their DNS resolver
|
||||||
# # zone = ".";
|
# zone = ".";
|
||||||
# # zone_type = "Forward";
|
# zone_type = "Forward";
|
||||||
# # stores = {
|
# stores = {
|
||||||
# # type = "forward";
|
# type = "forward";
|
||||||
# # name_servers = [
|
# name_servers = [
|
||||||
# # {
|
# {
|
||||||
# # socket_addr = "127.0.0.53:53";
|
# socket_addr = "127.0.0.53:53";
|
||||||
# # protocol = "udp";
|
# protocol = "udp";
|
||||||
# # trust_nx_responses = true;
|
# trust_nx_responses = true;
|
||||||
# # }
|
# }
|
||||||
# # ];
|
# ];
|
||||||
# # };
|
|
||||||
# # }
|
|
||||||
# # ];
|
|
||||||
# # };
|
|
||||||
# };
|
# };
|
||||||
|
# }
|
||||||
|
# ];
|
||||||
|
# };
|
||||||
|
};
|
||||||
# lan = {
|
# lan = {
|
||||||
# substitutions = mkSubstitutions "lan";
|
# substitutions = mkSubstitutions "lan";
|
||||||
# listenAddrsIpv4 = [ nativeAddrs."servo.lan" ];
|
# listenAddrsIpv4 = [ nativeAddrs."servo.lan" ];
|
||||||
@@ -140,10 +141,5 @@ in
|
|||||||
# };
|
# };
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.hickory-dns-doof.after = [
|
|
||||||
# service will fail to bind the veth, otherwise
|
|
||||||
"netns-doof-veth.service"
|
|
||||||
];
|
|
||||||
|
|
||||||
sane.services.dyn-dns.restartOnChange = lib.map (c: "${c.service}.service") (builtins.attrValues config.sane.services.hickory-dns.instances);
|
sane.services.dyn-dns.restartOnChange = lib.map (c: "${c.service}.service") (builtins.attrValues config.sane.services.hickory-dns.instances);
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,6 @@ lib.mkIf false # i don't actively use ipfs anymore
|
|||||||
|
|
||||||
locations."/" = {
|
locations."/" = {
|
||||||
proxyPass = "http://127.0.0.1:8080";
|
proxyPass = "http://127.0.0.1:8080";
|
||||||
recommendedProxySettings = true;
|
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Ipfs-Gateway-Prefix "";
|
proxy_set_header X-Ipfs-Gateway-Prefix "";
|
||||||
|
@@ -10,16 +10,15 @@ in
|
|||||||
];
|
];
|
||||||
services.jackett.enable = true;
|
services.jackett.enable = true;
|
||||||
|
|
||||||
# run this behind the OVPN static VPN
|
systemd.services.jackett.after = [ "wireguard-wg-ovpns.service" ];
|
||||||
sane.netns.ovpns.services = [ "jackett" ];
|
systemd.services.jackett.partOf = [ "wireguard-wg-ovpns.service" ];
|
||||||
systemd.services.jackett = {
|
systemd.services.jackett = {
|
||||||
serviceConfig.ExecStartPre = [
|
# run this behind the OVPN static VPN
|
||||||
# abort if public IP is not as expected
|
serviceConfig.NetworkNamespacePath = "/run/netns/ovpns";
|
||||||
"${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect ${config.sane.netns.ovpns.wg.address.ipv4}"
|
serviceConfig.ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect ${config.sane.netns.ovpns.netnsPubIpv4}" ]; # abort if public IP is not as expected
|
||||||
];
|
|
||||||
# patch in `--ListenPublic` so that it's reachable from the netns veth.
|
# patch in `--ListenPublic` so that it's reachable from the netns veth.
|
||||||
# this also makes it reachable from the VPN pub address. oh well.
|
# this also makes it reachable from the VPN pub address. oh well.
|
||||||
serviceConfig.ExecStart = lib.mkForce "${lib.getExe' cfg.package "Jackett"} --ListenPublic --NoUpdates --DataFolder '${cfg.dataDir}'";
|
serviceConfig.ExecStart = lib.mkForce "${cfg.package}/bin/Jackett --ListenPublic --NoUpdates --DataFolder '${cfg.dataDir}'";
|
||||||
serviceConfig.RestartSec = "30s";
|
serviceConfig.RestartSec = "30s";
|
||||||
|
|
||||||
# hardening (systemd-analyze security jackett)
|
# hardening (systemd-analyze security jackett)
|
||||||
@@ -56,7 +55,7 @@ in
|
|||||||
enableACME = true;
|
enableACME = true;
|
||||||
# inherit kTLS;
|
# inherit kTLS;
|
||||||
locations."/" = {
|
locations."/" = {
|
||||||
proxyPass = "http://${config.sane.netns.ovpns.veth.netns.ipv4}:9117";
|
proxyPass = "http://${config.sane.netns.ovpns.netnsVethIpv4}:9117";
|
||||||
recommendedProxySettings = true;
|
recommendedProxySettings = true;
|
||||||
};
|
};
|
||||||
locations."= /robots.txt".extraConfig = ''
|
locations."= /robots.txt".extraConfig = ''
|
||||||
|
127
hosts/by-name/servo/services/jellyfin.nix
Normal file
127
hosts/by-name/servo/services/jellyfin.nix
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# configuration options (today i don't store my config in nix):
|
||||||
|
#
|
||||||
|
# - jellyfin-web can be statically configured (result/share/jellyfin-web/config.json)
|
||||||
|
# - <https://jellyfin.org/docs/general/clients/web-config>
|
||||||
|
# - configure server list, plugins, "menuLinks", colors
|
||||||
|
#
|
||||||
|
# - jellfyin server is configured in /var/lib/jellfin/
|
||||||
|
# - root/default/<LibraryType>/
|
||||||
|
# - <LibraryName>.mblink: contains the directory name where this library lives
|
||||||
|
# - options.xml: contains preferences which were defined in the web UI during import
|
||||||
|
# - e.g. `EnablePhotos`, `EnableChapterImageExtraction`, etc.
|
||||||
|
# - config/encoding.xml: transcoder settings
|
||||||
|
# - config/system.xml: misc preferences like log file duration, audiobook resume settings, etc.
|
||||||
|
# - data/jellyfin.db: maybe account definitions? internal state?
|
||||||
|
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# https://jellyfin.org/docs/general/networking/index.html
|
||||||
|
sane.ports.ports."1900" = {
|
||||||
|
protocol = [ "udp" ];
|
||||||
|
visibleTo.lan = true;
|
||||||
|
description = "colin-upnp-for-jellyfin";
|
||||||
|
};
|
||||||
|
sane.ports.ports."7359" = {
|
||||||
|
protocol = [ "udp" ];
|
||||||
|
visibleTo.lan = true;
|
||||||
|
description = "colin-jellyfin-specific-client-discovery";
|
||||||
|
# ^ not sure if this is necessary: copied this port from nixos jellyfin.openFirewall
|
||||||
|
};
|
||||||
|
# not sure if 8096/8920 get used either:
|
||||||
|
sane.ports.ports."8096" = {
|
||||||
|
protocol = [ "tcp" ];
|
||||||
|
visibleTo.lan = true;
|
||||||
|
description = "colin-jellyfin-http-lan";
|
||||||
|
};
|
||||||
|
sane.ports.ports."8920" = {
|
||||||
|
protocol = [ "tcp" ];
|
||||||
|
visibleTo.lan = true;
|
||||||
|
description = "colin-jellyfin-https-lan";
|
||||||
|
};
|
||||||
|
|
||||||
|
sane.persist.sys.byStore.plaintext = [
|
||||||
|
{ user = "jellyfin"; group = "jellyfin"; mode = "0700"; path = "/var/lib/jellyfin"; method = "bind"; }
|
||||||
|
];
|
||||||
|
sane.fs."/var/lib/jellyfin/config/logging.json" = {
|
||||||
|
# "Emby.Dlna" logging: <https://jellyfin.org/docs/general/networking/dlna>
|
||||||
|
symlink.text = ''
|
||||||
|
{
|
||||||
|
"Serilog": {
|
||||||
|
"MinimumLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Override": {
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"System": "Warning",
|
||||||
|
"Emby.Dlna": "Debug",
|
||||||
|
"Emby.Dlna.Eventing": "Debug"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WriteTo": [
|
||||||
|
{
|
||||||
|
"Name": "Console",
|
||||||
|
"Args": {
|
||||||
|
"outputTemplate": "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Enrich": [ "FromLogContext", "WithThreadId" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
wantedBeforeBy = [ "jellyfin.service" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Jellyfin multimedia server
|
||||||
|
# this is mostly taken from the official jellfin.org docs
|
||||||
|
services.nginx.virtualHosts."jelly.uninsane.org" = {
|
||||||
|
forceSSL = 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.dns.zones."uninsane.org".inet.CNAME."jelly" = "native";
|
||||||
|
|
||||||
|
services.jellyfin.enable = true;
|
||||||
|
}
|
@@ -1,173 +0,0 @@
|
|||||||
# configuration options (today only a *subset* of the config is done in nix)
|
|
||||||
# - jellyfin-web can be statically configured (result/share/jellyfin-web/config.json)
|
|
||||||
# - <https://jellyfin.org/docs/general/clients/web-config>
|
|
||||||
# - configure server list, plugins, "menuLinks", colors
|
|
||||||
#
|
|
||||||
# - jellfyin server is configured in /var/lib/jellfin/
|
|
||||||
# - root/default/<LibraryType>/
|
|
||||||
# - <LibraryName>.mblink: contains the directory name where this library lives
|
|
||||||
# - options.xml: contains preferences which were defined in the web UI during import
|
|
||||||
# - e.g. `EnablePhotos`, `EnableChapterImageExtraction`, etc.
|
|
||||||
# - config/encoding.xml: transcoder settings
|
|
||||||
# - config/system.xml: misc preferences like log file duration, audiobook resume settings, etc.
|
|
||||||
# - data/jellyfin.db: maybe account definitions? internal state?
|
|
||||||
#
|
|
||||||
# N.B.: default install DOES NOT SUPPORT DLNA out of the box.
|
|
||||||
# one must install it as a "plugin", which can be done through the UI.
|
|
||||||
{ config, lib, ... }:
|
|
||||||
|
|
||||||
# lib.mkIf false #< XXX(2024-11-17): disabled because it hasn't been working for months; web UI hangs on load, TVs see no files
|
|
||||||
{
|
|
||||||
config = lib.mkIf (config.sane.maxBuildCost >= 2) {
|
|
||||||
# https://jellyfin.org/docs/general/networking/index.html
|
|
||||||
sane.ports.ports."1900" = {
|
|
||||||
protocol = [ "udp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
description = "colin-upnp-for-jellyfin";
|
|
||||||
};
|
|
||||||
sane.ports.ports."7359" = {
|
|
||||||
protocol = [ "udp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
description = "colin-jellyfin-specific-client-discovery";
|
|
||||||
# ^ not sure if this is necessary: copied this port from nixos jellyfin.openFirewall
|
|
||||||
};
|
|
||||||
# not sure if 8096/8920 get used either:
|
|
||||||
sane.ports.ports."8096" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
description = "colin-jellyfin-http-lan";
|
|
||||||
};
|
|
||||||
sane.ports.ports."8920" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
description = "colin-jellyfin-https-lan";
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.persist.sys.byStore.plaintext = [
|
|
||||||
{ user = "jellyfin"; group = "jellyfin"; mode = "0700"; path = "/var/lib/jellyfin/data"; method = "bind"; }
|
|
||||||
{ user = "jellyfin"; group = "jellyfin"; mode = "0700"; path = "/var/lib/jellyfin/metadata"; method = "bind"; }
|
|
||||||
# TODO: ship plugins statically, via nix. that'll be less fragile
|
|
||||||
{ user = "jellyfin"; group = "jellyfin"; mode = "0700"; path = "/var/lib/jellyfin/plugins/DLNA_5.0.0.0"; method = "bind"; }
|
|
||||||
{ user = "jellyfin"; group = "jellyfin"; mode = "0700"; path = "/var/lib/jellyfin/root"; method = "bind"; }
|
|
||||||
];
|
|
||||||
sane.persist.sys.byStore.ephemeral = [
|
|
||||||
{ user = "jellyfin"; group = "jellyfin"; mode = "0700"; path = "/var/lib/jellyfin/log"; method = "bind"; }
|
|
||||||
{ user = "jellyfin"; group = "jellyfin"; mode = "0700"; path = "/var/lib/jellyfin/transcodes"; method = "bind"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
services.jellyfin.enable = true;
|
|
||||||
users.users.jellyfin.extraGroups = [ "media" ];
|
|
||||||
|
|
||||||
sane.fs."/var/lib/jellyfin".dir.acl = {
|
|
||||||
user = "jellyfin";
|
|
||||||
group = "jellyfin";
|
|
||||||
mode = "0700";
|
|
||||||
};
|
|
||||||
|
|
||||||
# `"Jellyfin.Plugin.Dlna": "Debug"` logging: <https://jellyfin.org/docs/general/networking/dlna>
|
|
||||||
# TODO: switch Dlna back to 'Information' once satisfied with stability
|
|
||||||
sane.fs."/var/lib/jellyfin/config/logging.json".symlink.text = ''
|
|
||||||
{
|
|
||||||
"Serilog": {
|
|
||||||
"MinimumLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Override": {
|
|
||||||
"Microsoft": "Warning",
|
|
||||||
"System": "Warning",
|
|
||||||
"Jellyfin.Plugin.Dlna": "Debug"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"WriteTo": [
|
|
||||||
{
|
|
||||||
"Name": "Console",
|
|
||||||
"Args": {
|
|
||||||
"outputTemplate": "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Enrich": [ "FromLogContext", "WithThreadId" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
|
|
||||||
sane.fs."/var/lib/jellyfin/config/network.xml".file.text = ''
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<NetworkConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
||||||
<BaseUrl />
|
|
||||||
<EnableHttps>false</EnableHttps>
|
|
||||||
<RequireHttps>false</RequireHttps>
|
|
||||||
<InternalHttpPort>8096</InternalHttpPort>
|
|
||||||
<InternalHttpsPort>8920</InternalHttpsPort>
|
|
||||||
<PublicHttpPort>8096</PublicHttpPort>
|
|
||||||
<PublicHttpsPort>8920</PublicHttpsPort>
|
|
||||||
<AutoDiscovery>true</AutoDiscovery>
|
|
||||||
<EnableUPnP>false</EnableUPnP>
|
|
||||||
<EnableIPv4>true</EnableIPv4>
|
|
||||||
<EnableIPv6>false</EnableIPv6>
|
|
||||||
<EnableRemoteAccess>true</EnableRemoteAccess>
|
|
||||||
<LocalNetworkSubnets>
|
|
||||||
<string>10.78.76.0/22</string>
|
|
||||||
</LocalNetworkSubnets>
|
|
||||||
<KnownProxies>
|
|
||||||
<string>127.0.0.1</string>
|
|
||||||
<string>localhost</string>
|
|
||||||
<string>10.78.79.1</string>
|
|
||||||
</KnownProxies>
|
|
||||||
<IgnoreVirtualInterfaces>false</IgnoreVirtualInterfaces>
|
|
||||||
<VirtualInterfaceNames />
|
|
||||||
<EnablePublishedServerUriByRequest>false</EnablePublishedServerUriByRequest>
|
|
||||||
<PublishedServerUriBySubnet />
|
|
||||||
<RemoteIPFilter />
|
|
||||||
<IsRemoteIPFilterBlacklist>false</IsRemoteIPFilterBlacklist>
|
|
||||||
</NetworkConfiguration>
|
|
||||||
'';
|
|
||||||
|
|
||||||
# guest user id is `5ad194d60dca41de84b332950ffc4308`
|
|
||||||
sane.fs."/var/lib/jellyfin/plugins/configurations/Jellyfin.Plugin.Dlna.xml".file.text = ''
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<DlnaPluginConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
||||||
<EnablePlayTo>true</EnablePlayTo>
|
|
||||||
<ClientDiscoveryIntervalSeconds>60</ClientDiscoveryIntervalSeconds>
|
|
||||||
<BlastAliveMessages>true</BlastAliveMessages>
|
|
||||||
<AliveMessageIntervalSeconds>180</AliveMessageIntervalSeconds>
|
|
||||||
<SendOnlyMatchedHost>true</SendOnlyMatchedHost>
|
|
||||||
<DefaultUserId>5ad194d6-0dca-41de-84b3-32950ffc4308</DefaultUserId>
|
|
||||||
</DlnaPluginConfiguration>
|
|
||||||
'';
|
|
||||||
|
|
||||||
# fix LG TV to play more files.
|
|
||||||
# there are certain files for which it only supports Direct Play (not even "Direct Stream" -- but "Direct Play").
|
|
||||||
# this isn't a 100% fix: patching the profile allows e.g. Azumanga Daioh to play,
|
|
||||||
# but A Place Further Than the Universe still fails as before.
|
|
||||||
#
|
|
||||||
# profile is based on upstream: <https://github.com/jellyfin/jellyfin-plugin-dlna>
|
|
||||||
sane.fs."/var/lib/jellyfin/plugins/DLNA_5.0.0.0/profiles/LG Smart TV.xml".symlink.target = ./dlna/user/LG_Smart_TV.xml;
|
|
||||||
# XXX(2024-11-17): old method, but the file referenced seems not to be used and setting just it causes failures:
|
|
||||||
# > [DBG] Jellyfin.Plugin.Dlna.ContentDirectory.ContentDirectoryService: Not eligible for DirectPlay due to unsupported subtitles
|
|
||||||
# sane.fs."/var/lib/jellyfin/plugins/configurations/dlna/user/LG Smart TV.xml".symlink.target = ./dlna/user/LG_Smart_TV.xml;
|
|
||||||
|
|
||||||
systemd.services.jellyfin.unitConfig.RequiresMountsFor = [
|
|
||||||
"/var/media"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Jellyfin multimedia server
|
|
||||||
# this is mostly taken from the official jellfin.org docs
|
|
||||||
services.nginx.virtualHosts."jelly.uninsane.org" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
# inherit kTLS;
|
|
||||||
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "http://127.0.0.1:8096";
|
|
||||||
proxyWebsockets = true;
|
|
||||||
recommendedProxySettings = true;
|
|
||||||
# extraConfig = ''
|
|
||||||
# # Disable buffering when the nginx proxy gets very resource heavy upon streaming
|
|
||||||
# proxy_buffering off;
|
|
||||||
# '';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.dns.zones."uninsane.org".inet.CNAME."jelly" = "native";
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,91 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<Profile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
||||||
<Name>LG Smart TV</Name>
|
|
||||||
<Identification>
|
|
||||||
<ModelName>LG TV</ModelName>
|
|
||||||
<Headers />
|
|
||||||
</Identification>
|
|
||||||
<Manufacturer>Jellyfin</Manufacturer>
|
|
||||||
<ManufacturerUrl>https://github.com/jellyfin/jellyfin</ManufacturerUrl>
|
|
||||||
<ModelName>Jellyfin Server</ModelName>
|
|
||||||
<ModelDescription>UPnP/AV 1.0 Compliant Media Server</ModelDescription>
|
|
||||||
<ModelNumber>01</ModelNumber>
|
|
||||||
<ModelUrl>https://github.com/jellyfin/jellyfin</ModelUrl>
|
|
||||||
<EnableAlbumArtInDidl>false</EnableAlbumArtInDidl>
|
|
||||||
<EnableSingleAlbumArtLimit>false</EnableSingleAlbumArtLimit>
|
|
||||||
<EnableSingleSubtitleLimit>false</EnableSingleSubtitleLimit>
|
|
||||||
<SupportedMediaTypes>Audio,Photo,Video</SupportedMediaTypes>
|
|
||||||
<AlbumArtPn>JPEG_SM</AlbumArtPn>
|
|
||||||
<MaxAlbumArtWidth>480</MaxAlbumArtWidth>
|
|
||||||
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
|
|
||||||
<MaxIconWidth>48</MaxIconWidth>
|
|
||||||
<MaxIconHeight>48</MaxIconHeight>
|
|
||||||
<MaxStreamingBitrate>140000000</MaxStreamingBitrate>
|
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
|
||||||
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
|
||||||
<EnableMSMediaReceiverRegistrar>false</EnableMSMediaReceiverRegistrar>
|
|
||||||
<IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
|
|
||||||
<XmlRootAttributes />
|
|
||||||
<DirectPlayProfiles>
|
|
||||||
<DirectPlayProfile container="ts,mpegts,avi,mkv,m2ts" audioCodec="aac,ac3,eac3,mp3,dca,dts" videoCodec="h264,hevc" type="Video" />
|
|
||||||
<DirectPlayProfile container="mp4,m4v" audioCodec="aac,ac3,eac3,mp3,dca,dts" videoCodec="h264,mpeg4,hevc" type="Video" />
|
|
||||||
<DirectPlayProfile container="mp3" type="Audio" />
|
|
||||||
<DirectPlayProfile container="jpeg" type="Photo" />
|
|
||||||
<DirectPlayProfile container="" audioCodec="" videoCodec="" type="Video" />
|
|
||||||
</DirectPlayProfiles>
|
|
||||||
<TranscodingProfiles>
|
|
||||||
<TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
|
|
||||||
<TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac,mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
|
|
||||||
<TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
|
|
||||||
</TranscodingProfiles>
|
|
||||||
<ContainerProfiles>
|
|
||||||
<ContainerProfile type="Photo">
|
|
||||||
<Conditions>
|
|
||||||
<ProfileCondition condition="LessThanEqual" property="Width" value="1920" isRequired="true" />
|
|
||||||
<ProfileCondition condition="LessThanEqual" property="Height" value="1080" isRequired="true" />
|
|
||||||
</Conditions>
|
|
||||||
</ContainerProfile>
|
|
||||||
</ContainerProfiles>
|
|
||||||
<CodecProfiles>
|
|
||||||
<CodecProfile type="Video" codec="mpeg4">
|
|
||||||
<Conditions>
|
|
||||||
<ProfileCondition condition="LessThanEqual" property="Width" value="1920" isRequired="true" />
|
|
||||||
<ProfileCondition condition="LessThanEqual" property="Height" value="1080" isRequired="true" />
|
|
||||||
<ProfileCondition condition="LessThanEqual" property="VideoFramerate" value="30" isRequired="true" />
|
|
||||||
</Conditions>
|
|
||||||
<ApplyConditions />
|
|
||||||
</CodecProfile>
|
|
||||||
<CodecProfile type="Video" codec="h264">
|
|
||||||
<Conditions>
|
|
||||||
<ProfileCondition condition="LessThanEqual" property="Width" value="1920" isRequired="true" />
|
|
||||||
<ProfileCondition condition="LessThanEqual" property="Height" value="1080" isRequired="true" />
|
|
||||||
<ProfileCondition condition="LessThanEqual" property="VideoLevel" value="41" isRequired="true" />
|
|
||||||
</Conditions>
|
|
||||||
<ApplyConditions />
|
|
||||||
</CodecProfile>
|
|
||||||
<CodecProfile type="VideoAudio" codec="ac3,eac3,aac,mp3">
|
|
||||||
<Conditions>
|
|
||||||
<ProfileCondition condition="LessThanEqual" property="AudioChannels" value="6" isRequired="true" />
|
|
||||||
</Conditions>
|
|
||||||
<ApplyConditions />
|
|
||||||
</CodecProfile>
|
|
||||||
</CodecProfiles>
|
|
||||||
<ResponseProfiles>
|
|
||||||
<ResponseProfile container="m4v" type="Video" mimeType="video/mp4">
|
|
||||||
<Conditions />
|
|
||||||
</ResponseProfile>
|
|
||||||
<ResponseProfile container="ts,mpegts" type="Video" mimeType="video/mpeg">
|
|
||||||
<Conditions />
|
|
||||||
</ResponseProfile>
|
|
||||||
</ResponseProfiles>
|
|
||||||
<SubtitleProfiles>
|
|
||||||
<SubtitleProfile format="srt" method="Embed" />
|
|
||||||
<SubtitleProfile format="srt" method="External" />
|
|
||||||
</SubtitleProfiles>
|
|
||||||
</Profile>
|
|
@@ -1,42 +1,30 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
# how to update wikipedia snapshot:
|
||||||
|
# - browse for later snapshots:
|
||||||
|
# - <https://mirror.accum.se/mirror/wikimedia.org/other/kiwix/zim/wikipedia>
|
||||||
|
# - DL directly, or via rsync (resumable):
|
||||||
|
# - `rsync --progress --append-verify rsync://mirror.accum.se/mirror/wikimedia.org/other/kiwix/zim/wikipedia/wikipedia_en_all_maxi_2022-05.zim .`
|
||||||
|
|
||||||
|
{ ... }:
|
||||||
{
|
{
|
||||||
config = lib.mkIf (config.sane.maxBuildCost >= 3) {
|
sane.persist.sys.byStore.ext = [
|
||||||
|
{ user = "colin"; group = "users"; path = "/var/lib/kiwix"; method = "bind"; }
|
||||||
|
];
|
||||||
|
|
||||||
sane.services.kiwix-serve = {
|
sane.services.kiwix-serve = {
|
||||||
enable = true;
|
enable = true;
|
||||||
port = 8013;
|
port = 8013;
|
||||||
zimPaths = with pkgs.zimPackages; [
|
zimPaths = [ "/var/lib/kiwix/wikipedia_en_all_maxi_2023-11.zim" ];
|
||||||
alpinelinux_en_all_maxi.zimPath
|
|
||||||
archlinux_en_all_maxi.zimPath
|
|
||||||
bitcoin_en_all_maxi.zimPath
|
|
||||||
devdocs_en_nix.zimPath
|
|
||||||
gentoo_en_all_maxi.zimPath
|
|
||||||
# khanacademy_en_all.zimPath #< TODO: enable
|
|
||||||
openstreetmap-wiki_en_all_maxi.zimPath
|
|
||||||
psychonautwiki_en_all_maxi.zimPath
|
|
||||||
rationalwiki_en_all_maxi.zimPath
|
|
||||||
# wikipedia_en_100.zimPath
|
|
||||||
wikipedia_en_all_maxi.zimPath
|
|
||||||
# wikipedia_en_all_mini.zimPath
|
|
||||||
zimgit-food-preparation_en.zimPath
|
|
||||||
zimgit-medicine_en.zimPath
|
|
||||||
zimgit-post-disaster_en.zimPath
|
|
||||||
zimgit-water_en.zimPath
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx.virtualHosts."w.uninsane.org" = {
|
services.nginx.virtualHosts."w.uninsane.org" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
# inherit kTLS;
|
# inherit kTLS;
|
||||||
locations."/" = {
|
locations."/".proxyPass = "http://127.0.0.1:8013";
|
||||||
proxyPass = "http://127.0.0.1:8013";
|
|
||||||
recommendedProxySettings = true;
|
|
||||||
};
|
|
||||||
locations."= /robots.txt".extraConfig = ''
|
locations."= /robots.txt".extraConfig = ''
|
||||||
return 200 "User-agent: *\nDisallow: /\n";
|
return 200 "User-agent: *\nDisallow: /\n";
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
sane.dns.zones."uninsane.org".inet.CNAME."w" = "native";
|
sane.dns.zones."uninsane.org".inet.CNAME."w" = "native";
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
{ config, lib, ... }:
|
{ config, ... }:
|
||||||
let
|
let
|
||||||
svc-cfg = config.services.komga;
|
svc-cfg = config.services.komga;
|
||||||
inherit (svc-cfg) user group port stateDir;
|
inherit (svc-cfg) user group port stateDir;
|
||||||
in
|
in
|
||||||
lib.mkIf false #< 2024/09/30: disabled because i haven't used this for several months
|
|
||||||
{
|
{
|
||||||
sane.persist.sys.byStore.plaintext = [
|
sane.persist.sys.byStore.plaintext = [
|
||||||
{ inherit user group; mode = "0700"; path = stateDir; method = "bind"; }
|
{ inherit user group; mode = "0700"; path = stateDir; method = "bind"; }
|
||||||
@@ -17,7 +16,6 @@ lib.mkIf false #< 2024/09/30: disabled because i haven't used this for several
|
|||||||
enableACME = true;
|
enableACME = true;
|
||||||
locations."/" = {
|
locations."/" = {
|
||||||
proxyPass = "http://127.0.0.1:${builtins.toString port}";
|
proxyPass = "http://127.0.0.1:${builtins.toString port}";
|
||||||
recommendedProxySettings = true;
|
|
||||||
};
|
};
|
||||||
locations."= /robots.txt".extraConfig = ''
|
locations."= /robots.txt".extraConfig = ''
|
||||||
return 200 "User-agent: *\nDisallow: /\n";
|
return 200 "User-agent: *\nDisallow: /\n";
|
||||||
|
@@ -5,26 +5,27 @@
|
|||||||
|
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
let
|
let
|
||||||
|
inherit (builtins) toString;
|
||||||
|
inherit (lib) mkForce;
|
||||||
uiPort = 1234; # default ui port is 1234
|
uiPort = 1234; # default ui port is 1234
|
||||||
backendPort = 8536; # default backend port is 8536
|
backendPort = 8536; # default backend port is 8536
|
||||||
#^ i guess the "backend" port is used for federation?
|
#^ i guess the "backend" port is used for federation?
|
||||||
pict-rs = pkgs.pict-rs;
|
pict-rs = pkgs.pict-rs;
|
||||||
# pict-rs configuration is applied in this order:
|
# pict-rs = pkgs.pict-rs.overrideAttrs (upstream: {
|
||||||
# - via toml
|
# # as of v0.4.2, all non-GIF video is forcibly transcoded.
|
||||||
# - via env vars (overrides everything above)
|
# # that breaks lemmy, because of the request latency.
|
||||||
# - via CLI flags (overrides everything above)
|
# # and it eats up hella CPU.
|
||||||
# some of the CLI flags have defaults, making it the only actual way to configure certain things even when docs claim otherwise.
|
# # pict-rs is iffy around video altogether: mp4 seems the best supported.
|
||||||
# CLI args: <https://git.asonix.dog/asonix/pict-rs#user-content-running>
|
# # XXX: this patch no longer applies after 0.5.10 -> 0.5.11 update.
|
||||||
# TOML args: <https://git.asonix.dog/asonix/pict-rs/src/branch/main/pict-rs.toml>
|
# # git log is hard to parse, but *suggests* that video is natively supported
|
||||||
toml = pkgs.formats.toml { };
|
# # better than in the 0.4.2 days, e.g. 5fd59fc5b42d31559120dc28bfef4e5002fb509e
|
||||||
tomlConfig = toml.generate "pict-rs.toml" pictrsConfig;
|
# # "Change commandline flag to allow disabling video, since it is enabled by default"
|
||||||
pictrsConfig = {
|
# postPatch = (upstream.postPatch or "") + ''
|
||||||
media.process_timeout = 120;
|
# substituteInPlace src/validate.rs \
|
||||||
media.video.allow_audio = true;
|
# --replace-fail 'if transcode_options.needs_reencode() {' 'if false {'
|
||||||
media.video.max_frame_count = 30 * 60 * 60;
|
# '';
|
||||||
};
|
# });
|
||||||
in {
|
in {
|
||||||
config = lib.mkIf (config.sane.maxBuildCost >= 2) {
|
|
||||||
services.lemmy = {
|
services.lemmy = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings.hostname = "lemmy.uninsane.org";
|
settings.hostname = "lemmy.uninsane.org";
|
||||||
@@ -51,8 +52,8 @@ in {
|
|||||||
# - postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]
|
# - postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]
|
||||||
# LEMMY_DATABASE_URL = "postgres://lemmy@/run/postgresql"; # connection to server on socket "/run/postgresql/.s.PGSQL.5432" failed: FATAL: database "run/postgresql" does not exist
|
# LEMMY_DATABASE_URL = "postgres://lemmy@/run/postgresql"; # connection to server on socket "/run/postgresql/.s.PGSQL.5432" failed: FATAL: database "run/postgresql" does not exist
|
||||||
# LEMMY_DATABASE_URL = "postgres://lemmy?host=/run/postgresql"; # no PostgreSQL user name specified in startup packet
|
# LEMMY_DATABASE_URL = "postgres://lemmy?host=/run/postgresql"; # no PostgreSQL user name specified in startup packet
|
||||||
# LEMMY_DATABASE_URL = lib.mkForce "postgres://lemmy@?host=/run/postgresql"; # WORKS
|
# LEMMY_DATABASE_URL = mkForce "postgres://lemmy@?host=/run/postgresql"; # WORKS
|
||||||
LEMMY_DATABASE_URL = lib.mkForce "postgres://lemmy@/lemmy?host=/run/postgresql";
|
LEMMY_DATABASE_URL = mkForce "postgres://lemmy@/lemmy?host=/run/postgresql";
|
||||||
};
|
};
|
||||||
users.groups.lemmy = {};
|
users.groups.lemmy = {};
|
||||||
users.users.lemmy = {
|
users.users.lemmy = {
|
||||||
@@ -71,14 +72,10 @@ in {
|
|||||||
# fix to use a normal user so we can configure perms correctly
|
# fix to use a normal user so we can configure perms correctly
|
||||||
# XXX(2024-07-28): this hasn't been rigorously tested:
|
# XXX(2024-07-28): this hasn't been rigorously tested:
|
||||||
# possible that i've set something too strict and won't notice right away
|
# possible that i've set something too strict and won't notice right away
|
||||||
serviceConfig.DynamicUser = lib.mkForce false;
|
serviceConfig.DynamicUser = mkForce false;
|
||||||
serviceConfig.User = "lemmy";
|
serviceConfig.User = "lemmy";
|
||||||
serviceConfig.Group = "lemmy";
|
serviceConfig.Group = "lemmy";
|
||||||
|
|
||||||
# switch postgres from Requires -> Wants, so that postgres may restart without taking lemmy down with it.
|
|
||||||
requires = lib.mkForce [];
|
|
||||||
wants = [ "postgresql.service" ];
|
|
||||||
|
|
||||||
# hardening (systemd-analyze security lemmy)
|
# hardening (systemd-analyze security lemmy)
|
||||||
# a handful of these are specified in upstream nixpkgs, but mostly not
|
# a handful of these are specified in upstream nixpkgs, but mostly not
|
||||||
serviceConfig.LockPersonality = true;
|
serviceConfig.LockPersonality = true;
|
||||||
@@ -141,12 +138,18 @@ in {
|
|||||||
#v DO NOT REMOVE: defaults to 0.3, instead of latest, so always need to explicitly set this.
|
#v DO NOT REMOVE: defaults to 0.3, instead of latest, so always need to explicitly set this.
|
||||||
services.pict-rs.package = pict-rs;
|
services.pict-rs.package = pict-rs;
|
||||||
|
|
||||||
|
# pict-rs configuration is applied in this order:
|
||||||
|
# - via toml
|
||||||
|
# - via env vars (overrides everything above)
|
||||||
|
# - via CLI flags (overrides everything above)
|
||||||
|
# some of the CLI flags have defaults, making it the only actual way to configure certain things even when docs claim otherwise.
|
||||||
|
# CLI args: <https://git.asonix.dog/asonix/pict-rs#user-content-running>
|
||||||
systemd.services.pict-rs = {
|
systemd.services.pict-rs = {
|
||||||
serviceConfig.ExecStart = lib.mkForce (lib.concatStringsSep " " [
|
serviceConfig.ExecStart = lib.mkForce (lib.concatStringsSep " " [
|
||||||
(lib.getExe pict-rs)
|
"${lib.getBin pict-rs}/bin/pict-rs run"
|
||||||
"--config-file"
|
"--media-video-max-frame-count" (builtins.toString (30*60*60))
|
||||||
tomlConfig
|
"--media-process-timeout 120"
|
||||||
"run"
|
"--media-video-allow-audio" # allow audio
|
||||||
]);
|
]);
|
||||||
|
|
||||||
# hardening (systemd-analyze security pict-rs)
|
# hardening (systemd-analyze security pict-rs)
|
||||||
@@ -175,5 +178,4 @@ in {
|
|||||||
serviceConfig.SystemCallArchitectures = "native";
|
serviceConfig.SystemCallArchitectures = "native";
|
||||||
serviceConfig.SystemCallFilter = [ "@system-service" ];
|
serviceConfig.SystemCallFilter = [ "@system-service" ];
|
||||||
};
|
};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -12,9 +12,7 @@
|
|||||||
# - delete a notification destination by setting `kind` to `null` (otherwise, request is identical to above)
|
# - delete a notification destination by setting `kind` to `null` (otherwise, request is identical to above)
|
||||||
#
|
#
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
let
|
|
||||||
ntfy = config.services.ntfy-sh.enable;
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./discord-puppet.nix
|
./discord-puppet.nix
|
||||||
@@ -70,30 +68,21 @@ in
|
|||||||
config.sops.secrets."matrix_synapse_secrets.yaml".path
|
config.sops.secrets."matrix_synapse_secrets.yaml".path
|
||||||
];
|
];
|
||||||
|
|
||||||
# tune restart settings to ensure systemd doesn't disable it, and we don't overwhelm postgres
|
systemd.services.matrix-synapse.postStart = ''
|
||||||
systemd.services.matrix-synapse.serviceConfig.RestartSec = 5;
|
ACCESS_TOKEN=$(${pkgs.coreutils}/bin/cat ${config.sops.secrets.matrix_access_token.path})
|
||||||
systemd.services.matrix-synapse.serviceConfig.RestartMaxDelaySec = 20;
|
TOPIC=$(${pkgs.coreutils}/bin/cat ${config.sops.secrets.ntfy-sh-topic.path})
|
||||||
systemd.services.matrix-synapse.serviceConfig.StartLimitBurst = 120;
|
|
||||||
systemd.services.matrix-synapse.serviceConfig.RestartSteps = 3;
|
|
||||||
# switch postgres from Requires -> Wants, so that postgres may restart without taking matrix down with it.
|
|
||||||
systemd.services.matrix-synapse.requires = lib.mkForce [];
|
|
||||||
systemd.services.matrix-synapse.wants = [ "postgresql.service" ];
|
|
||||||
|
|
||||||
systemd.services.matrix-synapse.postStart = lib.optionalString ntfy ''
|
|
||||||
ACCESS_TOKEN=$(${lib.getExe' pkgs.coreutils "cat"} ${config.sops.secrets.matrix_access_token.path})
|
|
||||||
TOPIC=$(${lib.getExe' pkgs.coreutils "cat"} ${config.sops.secrets.ntfy-sh-topic.path})
|
|
||||||
|
|
||||||
echo "ensuring ntfy push gateway"
|
echo "ensuring ntfy push gateway"
|
||||||
${lib.getExe pkgs.curl} \
|
${pkgs.curl}/bin/curl \
|
||||||
--header "Authorization: Bearer $ACCESS_TOKEN" \
|
--header "Authorization: Bearer $ACCESS_TOKEN" \
|
||||||
--data "{ \"app_display_name\": \"ntfy-adapter\", \"app_id\": \"ntfy.uninsane.org\", \"data\": { \"url\": \"https://ntfy.uninsane.org/_matrix/push/v1/notify\", \"format\": \"event_id_only\" }, \"device_display_name\": \"ntfy-adapter\", \"kind\": \"http\", \"lang\": \"en-US\", \"profile_tag\": \"\", \"pushkey\": \"$TOPIC\" }" \
|
--data "{ \"app_display_name\": \"ntfy-adapter\", \"app_id\": \"ntfy.uninsane.org\", \"data\": { \"url\": \"https://ntfy.uninsane.org/_matrix/push/v1/notify\", \"format\": \"event_id_only\" }, \"device_display_name\": \"ntfy-adapter\", \"kind\": \"http\", \"lang\": \"en-US\", \"profile_tag\": \"\", \"pushkey\": \"$TOPIC\" }" \
|
||||||
localhost:8008/_matrix/client/v3/pushers/set
|
localhost:8008/_matrix/client/v3/pushers/set
|
||||||
|
|
||||||
echo "registered push gateways:"
|
echo "registered push gateways:"
|
||||||
${lib.getExe pkgs.curl} \
|
${pkgs.curl}/bin/curl \
|
||||||
--header "Authorization: Bearer $ACCESS_TOKEN" \
|
--header "Authorization: Bearer $ACCESS_TOKEN" \
|
||||||
localhost:8008/_matrix/client/v3/pushers \
|
localhost:8008/_matrix/client/v3/pushers \
|
||||||
| ${lib.getExe pkgs.jq} .
|
| ${pkgs.jq}/bin/jq .
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +112,6 @@ in
|
|||||||
|
|
||||||
locations."/" = {
|
locations."/" = {
|
||||||
proxyPass = "http://127.0.0.1:8008";
|
proxyPass = "http://127.0.0.1:8008";
|
||||||
recommendedProxySettings = true;
|
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
# allow uploading large files (matrix enforces a separate limit, downstream)
|
# allow uploading large files (matrix enforces a separate limit, downstream)
|
||||||
client_max_body_size 512m;
|
client_max_body_size 512m;
|
||||||
@@ -171,5 +159,5 @@ in
|
|||||||
owner = config.users.users.matrix-synapse.name;
|
owner = config.users.users.matrix-synapse.name;
|
||||||
};
|
};
|
||||||
# provide access to ntfy-sh-topic secret
|
# provide access to ntfy-sh-topic secret
|
||||||
users.users.matrix-synapse.extraGroups = lib.optionals ntfy [ "ntfy-sh" ];
|
users.users.matrix-synapse.extraGroups = [ "ntfy-sh" ];
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# config docs:
|
# config docs:
|
||||||
# - <https://github.com/matrix-org/matrix-appservice-irc/blob/develop/config.sample.yaml>
|
# - <https://github.com/matrix-org/matrix-appservice-irc/blob/develop/config.sample.yaml>
|
||||||
{ lib, ... }:
|
{ config, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
ircServer = { name, additionalAddresses ? [], ssl ? true, sasl ? true, port ? if ssl then 6697 else 6667 }: let
|
ircServer = { name, additionalAddresses ? [], ssl ? true, sasl ? true, port ? if ssl then 6697 else 6667 }: let
|
||||||
@@ -128,7 +128,6 @@ in
|
|||||||
|
|
||||||
ircService = {
|
ircService = {
|
||||||
logging.level = "warn"; # "error", "warn", "info", "debug"
|
logging.level = "warn"; # "error", "warn", "info", "debug"
|
||||||
mediaProxy.publicUrl = "https://irc.matrix.uninsane.org/media";
|
|
||||||
servers = {
|
servers = {
|
||||||
"irc.esper.net" = ircServer {
|
"irc.esper.net" = ircServer {
|
||||||
name = "esper";
|
name = "esper";
|
||||||
@@ -154,17 +153,8 @@ in
|
|||||||
# notable channels:
|
# notable channels:
|
||||||
# - #sxmo
|
# - #sxmo
|
||||||
# - #sxmo-offtopic
|
# - #sxmo-offtopic
|
||||||
# supposedly also available at <irc://37lnq2veifl4kar7.onion:6667/> (unofficial)
|
|
||||||
};
|
};
|
||||||
"irc.rizon.net" = ircServer { name = "Rizon"; };
|
"irc.rizon.net" = ircServer { name = "Rizon"; };
|
||||||
# "irc.sdf.org" = ircServer {
|
|
||||||
# # XXX(2024-11-06): seems it can't connect. "matrix-appservice-irc: WARN:Provisioner Provisioner only handles text 'yes'/'y' (from BASHy2-EU on irc.sdf.org)"
|
|
||||||
# # use instead? <https://lemmy.sdf.org/c/sdfpubnix>
|
|
||||||
# name = "sdf";
|
|
||||||
# # sasl = false;
|
|
||||||
# # notable channels (see: <https://sdf.org/?tutorials/irc-channels>)
|
|
||||||
# # - #sdf
|
|
||||||
# };
|
|
||||||
"wigle.net" = ircServer {
|
"wigle.net" = ircServer {
|
||||||
name = "WiGLE";
|
name = "WiGLE";
|
||||||
ssl = false;
|
ssl = false;
|
||||||
@@ -178,17 +168,4 @@ in
|
|||||||
# the service actively uses at least one of these, and both of them are fairly innocuous
|
# the service actively uses at least one of these, and both of them are fairly innocuous
|
||||||
SystemCallFilter = lib.mkForce "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @setuid @swap";
|
SystemCallFilter = lib.mkForce "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @setuid @swap";
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx.virtualHosts."irc.matrix.uninsane.org" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
locations."/media" = {
|
|
||||||
proxyPass = "http://127.0.0.1:11111";
|
|
||||||
recommendedProxySettings = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.dns.zones."uninsane.org".inet = {
|
|
||||||
CNAME."irc.matrix" = "native";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -1,39 +0,0 @@
|
|||||||
# - `man 5 minidlna.conf`
|
|
||||||
# - `man 8 minidlnad`
|
|
||||||
#
|
|
||||||
# this is an extremely simple (but limited) DLNA server:
|
|
||||||
# - no web UI
|
|
||||||
# - no runtime configuration -- just statically configure media directories instead
|
|
||||||
# - no transcoding
|
|
||||||
# compatibility:
|
|
||||||
# - LG TV: music: all working
|
|
||||||
# - LG TV: videos: mixed. i can't see the pattern; HEVC works; H.264 sometimes works.
|
|
||||||
{ lib, ... }:
|
|
||||||
lib.mkIf false #< XXX(2024-11-17): WORKS, but i'm trying gerbera instead for hopefully better transcoding
|
|
||||||
{
|
|
||||||
sane.ports.ports."1900" = {
|
|
||||||
protocol = [ "udp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
description = "colin-upnp-for-minidlna";
|
|
||||||
};
|
|
||||||
sane.ports.ports."8200" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
description = "colin-minidlna-http";
|
|
||||||
};
|
|
||||||
|
|
||||||
services.minidlna.enable = true;
|
|
||||||
|
|
||||||
services.minidlna.settings = {
|
|
||||||
media_dir = [
|
|
||||||
# A/V/P to restrict a directory to audio/video/pictures
|
|
||||||
"A,/var/media/Music"
|
|
||||||
"V,/var/media/Videos/Film"
|
|
||||||
# "V,/var/media/Videos/Milkbags"
|
|
||||||
"V,/var/media/Videos/Shows"
|
|
||||||
];
|
|
||||||
notify_interval = 60;
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.minidlna.extraGroups = [ "media" ];
|
|
||||||
}
|
|
@@ -1,66 +0,0 @@
|
|||||||
# murmur is the server component of mumble.
|
|
||||||
# - docs: <https://www.mumble.info/documentation/>
|
|
||||||
# - config docs: <https://www.mumble.info/documentation/administration/config-file/>
|
|
||||||
#
|
|
||||||
# default port is 64738 (UDP and TCP)
|
|
||||||
#
|
|
||||||
# FIRST-RUN:
|
|
||||||
# - login from mumble client as `SuperUser`, password taken from `journalctl -u murmur`.
|
|
||||||
# - login from another machine and right click on self -> 'Register'
|
|
||||||
# - as SuperUser, right click on server root -> edit
|
|
||||||
# - Groups tab: select "admin", then add the other registered user to the group.
|
|
||||||
# - log out as SuperUser and manage the server using that other user now.
|
|
||||||
#
|
|
||||||
# USAGE:
|
|
||||||
# - 'auth' group = any user who has registered a cert with the server.
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
sane.persist.sys.byStore.private = [
|
|
||||||
{ user = "murmur"; group = "murmur"; mode = "0700"; path = "/var/lib/murmur"; method = "bind"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
services.murmur.enable = true;
|
|
||||||
services.murmur.welcometext = "welcome to Colin's mumble voice chat server";
|
|
||||||
# max bandwidth (bps) **per user**. i believe this affects both voice and uploads?
|
|
||||||
# mumble defaults to 558000, but nixos service defaults to 72000.
|
|
||||||
services.murmur.bandwidth = 558000;
|
|
||||||
services.murmur.imgMsgLength = 8 * 1024 * 1024;
|
|
||||||
|
|
||||||
services.murmur.sslCert = "/var/lib/acme/mumble.uninsane.org/fullchain.pem";
|
|
||||||
services.murmur.sslKey = "/var/lib/acme/mumble.uninsane.org/key.pem";
|
|
||||||
services.murmur.sslCa = "/etc/ssl/certs/ca-bundle.crt";
|
|
||||||
|
|
||||||
# allow clients on the LAN to discover this server
|
|
||||||
services.murmur.bonjour = true;
|
|
||||||
|
|
||||||
# mumble has a public server listing.
|
|
||||||
# my server doesn't associate with that registry (unless i specify registerPassword).
|
|
||||||
# however these settings appear to affect how the server presents itself to clients, regardless of registration.
|
|
||||||
services.murmur.registerName = "mumble.uninsane.org";
|
|
||||||
services.murmur.registerUrl = "https://mumble.uninsane.org";
|
|
||||||
services.murmur.registerHostname = "mumble.uninsane.org";
|
|
||||||
|
|
||||||
# defaultchannel=ID makes it so that unauthenticated users are placed in some specific channel when they join
|
|
||||||
services.murmur.extraConfig = ''
|
|
||||||
defaultchannel=2
|
|
||||||
'';
|
|
||||||
|
|
||||||
users.users.murmur.extraGroups = [
|
|
||||||
"nginx" # provide access to certs
|
|
||||||
];
|
|
||||||
services.nginx.virtualHosts."mumble.uninsane.org" = {
|
|
||||||
# allow ACME to procure a cert via nginx for this domain
|
|
||||||
enableACME = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.dns.zones."uninsane.org".inet = {
|
|
||||||
CNAME."mumble" = "native";
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.ports.ports."64738" = {
|
|
||||||
protocol = [ "tcp" "udp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
visibleTo.doof = true;
|
|
||||||
description = "colin-mumble";
|
|
||||||
};
|
|
||||||
}
|
|
@@ -34,10 +34,7 @@ lib.mkIf false #< i don't actively use navidrome
|
|||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
# inherit kTLS;
|
# inherit kTLS;
|
||||||
locations."/" = {
|
locations."/".proxyPass = "http://127.0.0.1:4533";
|
||||||
proxyPass = "http://127.0.0.1:4533";
|
|
||||||
recommendedProxySettings = true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sane.dns.zones."uninsane.org".inet.CNAME."music" = "native";
|
sane.dns.zones."uninsane.org".inet.CNAME."music" = "native";
|
||||||
|
248
hosts/by-name/servo/services/nginx.nix
Normal file
248
hosts/by-name/servo/services/nginx.nix
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
# docs: <https://nixos.wiki/wiki/Nginx>
|
||||||
|
# docs: <https://nginx.org/en/docs/>
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# make the logs for this host "public" so that they show up in e.g. metrics
|
||||||
|
publog = vhost: lib.attrsets.unionOfDisjoint vhost {
|
||||||
|
extraConfig = (vhost.extraConfig or "") + ''
|
||||||
|
access_log /var/log/nginx/public.log vcombined;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# kTLS = true; # in-kernel TLS for better perf
|
||||||
|
in
|
||||||
|
{
|
||||||
|
|
||||||
|
sane.ports.ports."80" = {
|
||||||
|
protocol = [ "tcp" ];
|
||||||
|
visibleTo.lan = true;
|
||||||
|
visibleTo.ovpns = true; # so that letsencrypt can procure a cert for the mx record
|
||||||
|
visibleTo.doof = true;
|
||||||
|
description = "colin-http-uninsane.org";
|
||||||
|
};
|
||||||
|
sane.ports.ports."443" = {
|
||||||
|
protocol = [ "tcp" ];
|
||||||
|
visibleTo.lan = true;
|
||||||
|
visibleTo.doof = true;
|
||||||
|
description = "colin-https-uninsane.org";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.enable = true;
|
||||||
|
# nginxStable is one release behind nginxMainline.
|
||||||
|
# nginx itself recommends running mainline; nixos defaults to stable.
|
||||||
|
# services.nginx.package = pkgs.nginxMainline;
|
||||||
|
# XXX(2024-07-31): nixos defaults to zlib-ng -- supposedly more performant, but spams log with
|
||||||
|
# "gzip filter failed to use preallocated memory: ..."
|
||||||
|
services.nginx.package = pkgs.nginxMainline.override { zlib = pkgs.zlib; };
|
||||||
|
services.nginx.appendConfig = ''
|
||||||
|
# use 1 process per core.
|
||||||
|
# may want to increase worker_connections too, but `ulimit -n` must be increased first.
|
||||||
|
worker_processes auto;
|
||||||
|
'';
|
||||||
|
|
||||||
|
# this is the standard `combined` log format, with the addition of $host
|
||||||
|
# so that we have the virtualHost in the log.
|
||||||
|
# KEEP IN SYNC WITH GOACCESS
|
||||||
|
# goaccess calls this VCOMBINED:
|
||||||
|
# - <https://gist.github.com/jyap808/10570005>
|
||||||
|
services.nginx.commonHttpConfig = ''
|
||||||
|
log_format vcombined '$host:$server_port $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referrer" "$http_user_agent"';
|
||||||
|
access_log /var/log/nginx/private.log vcombined;
|
||||||
|
'';
|
||||||
|
# enables gzip and sets gzip_comp_level = 5
|
||||||
|
services.nginx.recommendedGzipSettings = true;
|
||||||
|
# enables zstd and sets zstd_comp_level = 9
|
||||||
|
services.nginx.recommendedZstdSettings = true;
|
||||||
|
# enables OCSP stapling (so clients don't need contact the OCSP server -- i do instead)
|
||||||
|
# - doesn't seem to, actually: <https://www.ssllabs.com/ssltest/analyze.html?d=uninsane.org>
|
||||||
|
# caches TLS sessions for 10m
|
||||||
|
services.nginx.recommendedTlsSettings = true;
|
||||||
|
# enables sendfile, tcp_nopush, tcp_nodelay, keepalive_timeout 65
|
||||||
|
services.nginx.recommendedOptimisation = true;
|
||||||
|
|
||||||
|
# web blog/personal site
|
||||||
|
# alternative way to link stuff into the share:
|
||||||
|
# sane.fs."/var/www/sites/uninsane.org/share/Ubunchu".mount.bind = "/var/media/Books/Visual/HiroshiSeo/Ubunchu";
|
||||||
|
# sane.fs."/var/media/Books/Visual/HiroshiSeo/Ubunchu".dir = {};
|
||||||
|
services.nginx.virtualHosts."uninsane.org" = publog {
|
||||||
|
# a lot of places hardcode https://uninsane.org,
|
||||||
|
# and then when we mix http + non-https, we get CORS violations
|
||||||
|
# and things don't look right. so force SSL.
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
# inherit kTLS;
|
||||||
|
# for OCSP stapling
|
||||||
|
sslTrustedCertificate = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
root = "${pkgs.uninsane-dot-org}/share/uninsane-dot-org";
|
||||||
|
tryFiles = "$uri $uri/ @fallback";
|
||||||
|
};
|
||||||
|
|
||||||
|
# unversioned files
|
||||||
|
locations."@fallback" = {
|
||||||
|
root = "/var/www/sites/uninsane.org";
|
||||||
|
};
|
||||||
|
|
||||||
|
# uninsane.org/share/foo => /var/www/sites/uninsane.org/share/foo.
|
||||||
|
# special-cased to enable directory listings
|
||||||
|
locations."/share" = {
|
||||||
|
root = "/var/www/sites/uninsane.org";
|
||||||
|
extraConfig = ''
|
||||||
|
# autoindex => render directory listings
|
||||||
|
autoindex on;
|
||||||
|
# don't follow any symlinks when serving files
|
||||||
|
# otherwise it allows a directory escape
|
||||||
|
disable_symlinks on;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
locations."/share/Milkbags/" = {
|
||||||
|
alias = "/var/media/Videos/Milkbags/";
|
||||||
|
extraConfig = ''
|
||||||
|
# autoindex => render directory listings
|
||||||
|
autoindex on;
|
||||||
|
# don't follow any symlinks when serving files
|
||||||
|
# otherwise it allows a directory escape
|
||||||
|
disable_symlinks on;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
locations."/share/Ubunchu/" = {
|
||||||
|
alias = "/var/media/Books/Visual/HiroshiSeo/Ubunchu/";
|
||||||
|
extraConfig = ''
|
||||||
|
# autoindex => render directory listings
|
||||||
|
autoindex on;
|
||||||
|
# don't follow any symlinks when serving files
|
||||||
|
# otherwise it allows a directory escape
|
||||||
|
disable_symlinks on;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# allow matrix users to discover that @user:uninsane.org is reachable via matrix.uninsane.org
|
||||||
|
locations."= /.well-known/matrix/server".extraConfig =
|
||||||
|
let
|
||||||
|
# use 443 instead of the default 8448 port to unite
|
||||||
|
# the client-server and server-server port for simplicity
|
||||||
|
server = { "m.server" = "matrix.uninsane.org:443"; };
|
||||||
|
in ''
|
||||||
|
add_header Content-Type application/json;
|
||||||
|
return 200 '${builtins.toJSON server}';
|
||||||
|
'';
|
||||||
|
locations."= /.well-known/matrix/client".extraConfig =
|
||||||
|
let
|
||||||
|
client = {
|
||||||
|
"m.homeserver" = { "base_url" = "https://matrix.uninsane.org"; };
|
||||||
|
"m.identity_server" = { "base_url" = "https://vector.im"; };
|
||||||
|
};
|
||||||
|
# ACAO required to allow element-web on any URL to request this json file
|
||||||
|
in ''
|
||||||
|
add_header Content-Type application/json;
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
return 200 '${builtins.toJSON client}';
|
||||||
|
'';
|
||||||
|
|
||||||
|
# static URLs might not be aware of .well-known (e.g. registration confirmation URLs),
|
||||||
|
# so hack around that.
|
||||||
|
locations."/_matrix" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8008";
|
||||||
|
};
|
||||||
|
locations."/_synapse" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8008";
|
||||||
|
};
|
||||||
|
|
||||||
|
# allow ActivityPub clients to discover how to reach @user@uninsane.org
|
||||||
|
# see: https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3361/
|
||||||
|
# not sure this makes sense while i run multiple AP services (pleroma, lemmy)
|
||||||
|
# locations."/.well-known/nodeinfo" = {
|
||||||
|
# proxyPass = "http://127.0.0.1:4000";
|
||||||
|
# extraConfig = pleromaExtraConfig;
|
||||||
|
# };
|
||||||
|
|
||||||
|
# redirect common feed URIs to the canonical feed
|
||||||
|
locations."= /atom".extraConfig = "return 301 /atom.xml;";
|
||||||
|
locations."= /feed".extraConfig = "return 301 /atom.xml;";
|
||||||
|
locations."= /feed.xml".extraConfig = "return 301 /atom.xml;";
|
||||||
|
locations."= /rss".extraConfig = "return 301 /atom.xml;";
|
||||||
|
locations."= /rss.xml".extraConfig = "return 301 /atom.xml;";
|
||||||
|
locations."= /blog/atom".extraConfig = "return 301 /atom.xml;";
|
||||||
|
locations."= /blog/atom.xml".extraConfig = "return 301 /atom.xml;";
|
||||||
|
locations."= /blog/feed".extraConfig = "return 301 /atom.xml;";
|
||||||
|
locations."= /blog/feed.xml".extraConfig = "return 301 /atom.xml;";
|
||||||
|
locations."= /blog/rss".extraConfig = "return 301 /atom.xml;";
|
||||||
|
locations."= /blog/rss.xml".extraConfig = "return 301 /atom.xml;";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
# serve any site not listed above, if it's static.
|
||||||
|
# because we define it dynamically, SSL isn't trivial. support only http
|
||||||
|
# documented <https://nginx.org/en/docs/http/ngx_http_core_module.html#server_name>
|
||||||
|
services.nginx.virtualHosts."~^(?<domain>.+)$" = {
|
||||||
|
default = true;
|
||||||
|
addSSL = true;
|
||||||
|
enableACME = false;
|
||||||
|
sslCertificate = "/var/www/certs/wildcard/cert.pem";
|
||||||
|
sslCertificateKey = "/var/www/certs/wildcard/key.pem";
|
||||||
|
# sslCertificate = "/var/lib/acme/.minica/cert.pem";
|
||||||
|
# sslCertificateKey = "/var/lib/acme/.minica/key.pem";
|
||||||
|
# serverName = null;
|
||||||
|
locations."/" = {
|
||||||
|
# somehow this doesn't escape -- i get error 400 if i:
|
||||||
|
# curl 'http://..' --resolve '..:80:127.0.0.1'
|
||||||
|
root = "/var/www/sites/$domain";
|
||||||
|
# tryFiles = "$domain/$uri $domain/$uri/ =404";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
security.acme.acceptTerms = true;
|
||||||
|
security.acme.defaults.email = "admin.acme@uninsane.org";
|
||||||
|
|
||||||
|
sane.persist.sys.byStore.plaintext = [
|
||||||
|
{ user = "acme"; group = "acme"; path = "/var/lib/acme"; method = "bind"; }
|
||||||
|
];
|
||||||
|
sane.persist.sys.byStore.private = [
|
||||||
|
{ user = "colin"; group = "users"; path = "/var/www/sites"; method = "bind"; }
|
||||||
|
];
|
||||||
|
sane.persist.sys.byStore.ephemeral = [
|
||||||
|
# logs *could* be persisted to private storage, but then there's the issue of
|
||||||
|
# "what if servo boots, isn't unlocked, and the whole / tmpfs is consumed by logs"
|
||||||
|
{ user = "nginx"; group = "nginx"; path = "/var/log/nginx"; method = "bind"; }
|
||||||
|
];
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
system.activationScripts.generate-x509-self-signed.text = ''
|
||||||
|
mkdir -p /var/www/certs/wildcard
|
||||||
|
test -f /var/www/certs/wildcard/key.pem || ${pkgs.openssl}/bin/openssl \
|
||||||
|
req -x509 -newkey rsa:4096 \
|
||||||
|
-keyout /var/www/certs/wildcard/key.pem \
|
||||||
|
-out /var/www/certs/wildcard/cert.pem \
|
||||||
|
-sha256 -nodes -days 3650 \
|
||||||
|
-addext 'subjectAltName=DNS:*' \
|
||||||
|
-subj '/CN=self-signed'
|
||||||
|
chmod 640 /var/www/certs/wildcard/{key,cert}.pem
|
||||||
|
chown root:nginx /var/www/certs/wildcard /var/www/certs/wildcard/{key,cert}.pem
|
||||||
|
'';
|
||||||
|
}
|
@@ -1,111 +0,0 @@
|
|||||||
# docs: <https://nixos.wiki/wiki/Nginx>
|
|
||||||
# docs: <https://nginx.org/en/docs/>
|
|
||||||
{ lib, pkgs, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./uninsane.org.nix
|
|
||||||
./waka.laka.osaka
|
|
||||||
];
|
|
||||||
|
|
||||||
sane.ports.ports."80" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
visibleTo.ovpns = true; # so that letsencrypt can procure a cert for the mx record
|
|
||||||
visibleTo.doof = true;
|
|
||||||
description = "colin-http-uninsane.org";
|
|
||||||
};
|
|
||||||
sane.ports.ports."443" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
visibleTo.doof = true;
|
|
||||||
description = "colin-https-uninsane.org";
|
|
||||||
};
|
|
||||||
|
|
||||||
services.nginx.enable = true;
|
|
||||||
|
|
||||||
users.users.nginx.extraGroups = [ "anubis" ];
|
|
||||||
# nginxStable is one release behind nginxMainline.
|
|
||||||
# nginx itself recommends running mainline; nixos defaults to stable.
|
|
||||||
# services.nginx.package = pkgs.nginxMainline;
|
|
||||||
# XXX(2024-07-31): nixos defaults to zlib-ng -- supposedly more performant, but spams log with
|
|
||||||
# "gzip filter failed to use preallocated memory: ..."
|
|
||||||
# XXX(2025-07-24): "gzip filter" spam is gone => use default nginx package
|
|
||||||
# services.nginx.package = pkgs.nginxMainline.override { zlib = pkgs.zlib; };
|
|
||||||
services.nginx.appendConfig = ''
|
|
||||||
# use 1 process per core.
|
|
||||||
# may want to increase worker_connections too, but `ulimit -n` must be increased first.
|
|
||||||
worker_processes auto;
|
|
||||||
'';
|
|
||||||
|
|
||||||
# this is the standard `combined` log format, with the addition of $host
|
|
||||||
# so that we have the virtualHost in the log.
|
|
||||||
# KEEP IN SYNC WITH GOACCESS
|
|
||||||
# goaccess calls this VCOMBINED:
|
|
||||||
# - <https://gist.github.com/jyap808/10570005>
|
|
||||||
services.nginx.commonHttpConfig = ''
|
|
||||||
log_format vcombined '$host:$server_port $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referrer" "$http_user_agent"';
|
|
||||||
access_log /var/log/nginx/private.log vcombined;
|
|
||||||
'';
|
|
||||||
# enables gzip and sets gzip_comp_level = 5
|
|
||||||
services.nginx.recommendedGzipSettings = true;
|
|
||||||
# enables zstd and sets zstd_comp_level = 9
|
|
||||||
# services.nginx.recommendedZstdSettings = true; #< XXX(2025-07-18): nginx zstd integration is unmaintained in NixOS
|
|
||||||
# enables OCSP stapling (so clients don't need contact the OCSP server -- i do instead)
|
|
||||||
# - doesn't seem to, actually: <https://www.ssllabs.com/ssltest/analyze.html?d=uninsane.org>
|
|
||||||
# caches TLS sessions for 10m
|
|
||||||
services.nginx.recommendedTlsSettings = true;
|
|
||||||
# enables sendfile, tcp_nopush, tcp_nodelay, keepalive_timeout 65
|
|
||||||
services.nginx.recommendedOptimisation = true;
|
|
||||||
|
|
||||||
|
|
||||||
# serve any site not otherwise declared, if it's static.
|
|
||||||
# because we define it dynamically, SSL isn't trivial. support only http
|
|
||||||
# documented <https://nginx.org/en/docs/http/ngx_http_core_module.html#server_name>
|
|
||||||
services.nginx.virtualHosts."~^(?<domain>.+)$" = {
|
|
||||||
default = true;
|
|
||||||
addSSL = true;
|
|
||||||
enableACME = false;
|
|
||||||
sslCertificate = "/var/www/certs/wildcard/cert.pem";
|
|
||||||
sslCertificateKey = "/var/www/certs/wildcard/key.pem";
|
|
||||||
# sslCertificate = "/var/lib/acme/.minica/cert.pem";
|
|
||||||
# sslCertificateKey = "/var/lib/acme/.minica/key.pem";
|
|
||||||
# serverName = null;
|
|
||||||
locations."/" = {
|
|
||||||
# somehow this doesn't escape -- i get error 400 if i:
|
|
||||||
# curl 'http://..' --resolve '..:80:127.0.0.1'
|
|
||||||
root = "/var/www/sites/$domain";
|
|
||||||
# tryFiles = "$domain/$uri $domain/$uri/ =404";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
security.acme.acceptTerms = true;
|
|
||||||
security.acme.defaults.email = "admin.acme@uninsane.org";
|
|
||||||
|
|
||||||
sane.persist.sys.byStore.plaintext = [
|
|
||||||
{ user = "acme"; group = "acme"; path = "/var/lib/acme"; method = "bind"; }
|
|
||||||
];
|
|
||||||
sane.persist.sys.byStore.private = [
|
|
||||||
{ user = "colin"; group = "users"; path = "/var/www/sites"; method = "bind"; }
|
|
||||||
];
|
|
||||||
sane.persist.sys.byStore.ephemeral = [
|
|
||||||
# logs *could* be persisted to private storage, but then there's the issue of
|
|
||||||
# "what if servo boots, isn't unlocked, and the whole / tmpfs is consumed by logs"
|
|
||||||
{ user = "nginx"; group = "nginx"; path = "/var/log/nginx"; method = "bind"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
system.activationScripts.generate-x509-self-signed.text = ''
|
|
||||||
mkdir -p /var/www/certs/wildcard
|
|
||||||
test -f /var/www/certs/wildcard/key.pem || ${lib.getExe pkgs.openssl} \
|
|
||||||
req -x509 -newkey rsa:4096 \
|
|
||||||
-keyout /var/www/certs/wildcard/key.pem \
|
|
||||||
-out /var/www/certs/wildcard/cert.pem \
|
|
||||||
-sha256 -nodes -days 3650 \
|
|
||||||
-addext 'subjectAltName=DNS:*' \
|
|
||||||
-subj '/CN=self-signed'
|
|
||||||
chmod 640 /var/www/certs/wildcard/{key,cert}.pem
|
|
||||||
chown root:nginx /var/www/certs/wildcard /var/www/certs/wildcard/{key,cert}.pem
|
|
||||||
'';
|
|
||||||
}
|
|
@@ -1,132 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
# alternative way to link stuff into the share:
|
|
||||||
# sane.fs."/var/www/sites/uninsane.org/share/Ubunchu".mount.bind = "/var/media/Books/Visual/HiroshiSeo/Ubunchu";
|
|
||||||
# sane.fs."/var/media/Books/Visual/HiroshiSeo/Ubunchu".dir = {};
|
|
||||||
services.nginx.virtualHosts."uninsane.org" = {
|
|
||||||
# a lot of places hardcode https://uninsane.org,
|
|
||||||
# and then when we mix http + non-https, we get CORS violations
|
|
||||||
# and things don't look right. so force SSL.
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
|
|
||||||
# extraConfig = ''
|
|
||||||
# # "public" log so requests show up in goaccess metrics
|
|
||||||
# access_log /var/log/nginx/public.log vcombined;
|
|
||||||
# '';
|
|
||||||
|
|
||||||
locations."/" = {
|
|
||||||
root = "${pkgs.uninsane-dot-org}/share/uninsane-dot-org";
|
|
||||||
tryFiles = "$uri $uri/ @fallback";
|
|
||||||
};
|
|
||||||
|
|
||||||
# unversioned files
|
|
||||||
locations."@fallback" = {
|
|
||||||
root = "/var/www/sites/uninsane.org";
|
|
||||||
extraConfig = ''
|
|
||||||
# instruct Google to not index these pages.
|
|
||||||
# see: <https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag>
|
|
||||||
add_header X-Robots-Tag 'none, noindex, nofollow';
|
|
||||||
|
|
||||||
# best-effort attempt to block archive.org from archiving these pages.
|
|
||||||
# reply with 403: Forbidden
|
|
||||||
# User Agent is *probably* "archive.org_bot"; maybe used to be "ia_archiver"
|
|
||||||
# source: <https://archive.org/details/archive.org_bot>
|
|
||||||
# additional UAs: <https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker>
|
|
||||||
#
|
|
||||||
# validate with: `curl -H 'User-Agent: "bot;archive.org_bot;like: something else"' -v https://uninsane.org/dne`
|
|
||||||
if ($http_user_agent ~* "(?:\b)archive.org_bot(?:\b)") {
|
|
||||||
return 403;
|
|
||||||
}
|
|
||||||
if ($http_user_agent ~* "(?:\b)archive.org(?:\b)") {
|
|
||||||
return 403;
|
|
||||||
}
|
|
||||||
if ($http_user_agent ~* "(?:\b)ia_archiver(?:\b)") {
|
|
||||||
return 403;
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# uninsane.org/share/foo => /var/www/sites/uninsane.org/share/foo.
|
|
||||||
# special-cased to enable directory listings
|
|
||||||
locations."/share" = {
|
|
||||||
root = "/var/www/sites/uninsane.org";
|
|
||||||
extraConfig = ''
|
|
||||||
# autoindex => render directory listings
|
|
||||||
autoindex on;
|
|
||||||
# don't follow any symlinks when serving files
|
|
||||||
# otherwise it allows a directory escape
|
|
||||||
disable_symlinks on;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
locations."/share/Milkbags/" = {
|
|
||||||
alias = "/var/media/Videos/Milkbags/";
|
|
||||||
extraConfig = ''
|
|
||||||
# autoindex => render directory listings
|
|
||||||
autoindex on;
|
|
||||||
# don't follow any symlinks when serving files
|
|
||||||
# otherwise it allows a directory escape
|
|
||||||
disable_symlinks on;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
locations."/share/Ubunchu/" = {
|
|
||||||
alias = "/var/media/Books/Visual/HiroshiSeo/Ubunchu/";
|
|
||||||
extraConfig = ''
|
|
||||||
# autoindex => render directory listings
|
|
||||||
autoindex on;
|
|
||||||
# don't follow any symlinks when serving files
|
|
||||||
# otherwise it allows a directory escape
|
|
||||||
disable_symlinks on;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# allow matrix users to discover that @user:uninsane.org is reachable via matrix.uninsane.org
|
|
||||||
locations."= /.well-known/matrix/server".extraConfig =
|
|
||||||
let
|
|
||||||
# use 443 instead of the default 8448 port to unite
|
|
||||||
# the client-server and server-server port for simplicity
|
|
||||||
server = { "m.server" = "matrix.uninsane.org:443"; };
|
|
||||||
in ''
|
|
||||||
add_header Content-Type application/json;
|
|
||||||
return 200 '${builtins.toJSON server}';
|
|
||||||
'';
|
|
||||||
locations."= /.well-known/matrix/client".extraConfig =
|
|
||||||
let
|
|
||||||
client = {
|
|
||||||
"m.homeserver" = { "base_url" = "https://matrix.uninsane.org"; };
|
|
||||||
"m.identity_server" = { "base_url" = "https://vector.im"; };
|
|
||||||
};
|
|
||||||
# ACAO required to allow element-web on any URL to request this json file
|
|
||||||
in ''
|
|
||||||
add_header Content-Type application/json;
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
return 200 '${builtins.toJSON client}';
|
|
||||||
'';
|
|
||||||
|
|
||||||
# static URLs might not be aware of .well-known (e.g. registration confirmation URLs),
|
|
||||||
# so hack around that.
|
|
||||||
locations."/_matrix".extraConfig = "return 301 https://matrix.uninsane.org$request_uri;";
|
|
||||||
locations."/_synapse".extraConfig = "return 301 https://matrix.uninsane.org$request_uri;";
|
|
||||||
|
|
||||||
# allow ActivityPub clients to discover how to reach @user@uninsane.org
|
|
||||||
# see: https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3361/
|
|
||||||
# not sure this makes sense while i run multiple AP services (pleroma, lemmy)
|
|
||||||
# locations."/.well-known/nodeinfo" = {
|
|
||||||
# proxyPass = "http://127.0.0.1:4000";
|
|
||||||
# extraConfig = pleromaExtraConfig;
|
|
||||||
# };
|
|
||||||
|
|
||||||
# redirect common feed URIs to the canonical feed
|
|
||||||
locations."= /atom".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /feed".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /feed.xml".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /rss".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /rss.xml".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/atom".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/atom.xml".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/feed".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/feed.xml".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/rss".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/rss.xml".extraConfig = "return 301 /atom.xml;";
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,35 +0,0 @@
|
|||||||
{ config, pkgs, ... }:
|
|
||||||
let
|
|
||||||
wakaLakaOsaka = pkgs.linkFarm "waka-laka-osaka" {
|
|
||||||
"index.html" = ./index.html;
|
|
||||||
"waka.laka.for.osaka.mp4" = pkgs.fetchurl {
|
|
||||||
# saved from: <https://www.youtube.com/watch?v=ehB_7bBKprY>
|
|
||||||
url = "https://uninsane.org/share/Milkbags/PG_Plays_Video_Games-Waka_Laka_For_Osaka_4K.mp4";
|
|
||||||
hash = "sha256-UW0qR4btX4pZ1bJp4Oxk20m3mvQGj9HweLKO27JBTFs=";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
services.nginx.virtualHosts."laka.osaka" = {
|
|
||||||
addSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
locations."/" = {
|
|
||||||
# redirect everything to waka.laka.osaka
|
|
||||||
return = "301 https://waka.laka.osaka$request_uri";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
services.nginx.virtualHosts."waka.laka.osaka" = {
|
|
||||||
addSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
locations."/" = {
|
|
||||||
root = wakaLakaOsaka;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.dns.zones."laka.osaka".inet = {
|
|
||||||
SOA."@" = config.sane.dns.zones."uninsane.org".inet.SOA."@";
|
|
||||||
A."@" = config.sane.dns.zones."uninsane.org".inet.A."@";
|
|
||||||
NS."@" = config.sane.dns.zones."uninsane.org".inet.NS."@";
|
|
||||||
CNAME."waka" = "native.uninsane.org.";
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,46 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width initial-scale=1" />
|
|
||||||
<meta name="description" content="Waka Laka (for Osaka)" />
|
|
||||||
<title>Waka Laka (for Osaka)</title>
|
|
||||||
<style>
|
|
||||||
html,body {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
* {
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
border: 0px;
|
|
||||||
}
|
|
||||||
.bg-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
min-width: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
position: fixed;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 50% 50%;
|
|
||||||
background-size: contain;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background-color: #000000;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- TODO: how to autoplay video _without_ it being muted? -->
|
|
||||||
<video class="bg-image" id="waka-video" width="1440" height="1080"
|
|
||||||
autoplay loop muted
|
|
||||||
onclick="document.getElementById('waka-video').muted = !document.getElementById('waka-video').muted;"
|
|
||||||
>
|
|
||||||
<!-- from https://www.youtube.com/watch?v=ehB_7bBKprY -->
|
|
||||||
<!-- original and more info at https://www.aquilinestudios.org/wakalaka.html -->
|
|
||||||
<source src="waka.laka.for.osaka.mp4" type="video/mp4">
|
|
||||||
</video>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -6,7 +6,7 @@ lib.optionalAttrs false # disabled until i can be sure it's not gonna OOM my se
|
|||||||
description = "build a nixos image with all updated deps";
|
description = "build a nixos image with all updated deps";
|
||||||
path = with pkgs; [ coreutils git nix ];
|
path = with pkgs; [ coreutils git nix ];
|
||||||
script = ''
|
script = ''
|
||||||
working=$(mktemp -d nixos-prebuild.XXXXXX --tmpdir)
|
working=$(mktemp -d /tmp/nixos-prebuild.XXXXXX)
|
||||||
pushd "$working"
|
pushd "$working"
|
||||||
git clone https://git.uninsane.org/colin/nix-files.git \
|
git clone https://git.uninsane.org/colin/nix-files.git \
|
||||||
&& cd nix-files \
|
&& cd nix-files \
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
# ntfy: UnifiedPush notification delivery system
|
# ntfy: UnifiedPush notification delivery system
|
||||||
# - used to get push notifications out of Matrix and onto a Phone (iOS, Android, or a custom client)
|
# - used to get push notifications out of Matrix and onto a Phone (iOS, Android, or a custom client)
|
||||||
{ config, lib, ... }:
|
{ config, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./ntfy-waiter.nix
|
./ntfy-waiter.nix
|
||||||
./ntfy-sh.nix
|
./ntfy-sh.nix
|
||||||
];
|
];
|
||||||
sops.secrets."ntfy-sh-topic" = lib.mkIf config.services.ntfy-sh.enable {
|
sops.secrets."ntfy-sh-topic" = {
|
||||||
mode = "0440";
|
mode = "0440";
|
||||||
owner = config.users.users.ntfy-sh.name;
|
owner = config.users.users.ntfy-sh.name;
|
||||||
group = config.users.users.ntfy-sh.name;
|
group = config.users.users.ntfy-sh.name;
|
||||||
|
@@ -29,7 +29,6 @@ let
|
|||||||
# at the IP layer, to enable e.g. wake-on-lan.
|
# at the IP layer, to enable e.g. wake-on-lan.
|
||||||
altPort = 2587;
|
altPort = 2587;
|
||||||
in
|
in
|
||||||
lib.mkIf false #< 2024/09/30: disabled because i haven't used it in several months
|
|
||||||
{
|
{
|
||||||
sane.persist.sys.byStore.private = [
|
sane.persist.sys.byStore.private = [
|
||||||
# not 100% necessary to persist this, but ntfy does keep a 12hr (by default) cache
|
# not 100% necessary to persist this, but ntfy does keep a 12hr (by default) cache
|
||||||
@@ -59,7 +58,7 @@ lib.mkIf false #< 2024/09/30: disabled because i haven't used it in several mon
|
|||||||
# note that this will fail upon first run, i.e. before ntfy has created its db.
|
# note that this will fail upon first run, i.e. before ntfy has created its db.
|
||||||
# just restart the service.
|
# just restart the service.
|
||||||
topic=$(cat ${config.sops.secrets.ntfy-sh-topic.path})
|
topic=$(cat ${config.sops.secrets.ntfy-sh-topic.path})
|
||||||
${lib.getExe' pkgs.ntfy-sh "ntfy"} access everyone "$topic" read-write
|
${pkgs.ntfy-sh}/bin/ntfy access everyone "$topic" read-write
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ let
|
|||||||
silence = port - portLow;
|
silence = port - portLow;
|
||||||
flags = lib.optional cfg.verbose "--verbose";
|
flags = lib.optional cfg.verbose "--verbose";
|
||||||
cli = [
|
cli = [
|
||||||
(lib.getExe cfg.package)
|
"${cfg.package}/bin/ntfy-waiter"
|
||||||
"--port"
|
"--port"
|
||||||
"${builtins.toString port}"
|
"${builtins.toString port}"
|
||||||
"--silence"
|
"--silence"
|
||||||
@@ -31,7 +31,7 @@ let
|
|||||||
ExecStart = lib.concatStringsSep " " cli;
|
ExecStart = lib.concatStringsSep " " cli;
|
||||||
};
|
};
|
||||||
after = [ "network.target" ];
|
after = [ "network.target" ];
|
||||||
wantedBy = [ "ntfy-sh.service" ];
|
wantedBy = [ "default.target" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -39,7 +39,7 @@ in
|
|||||||
options = with lib; {
|
options = with lib; {
|
||||||
sane.ntfy-waiter.enable = mkOption {
|
sane.ntfy-waiter.enable = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = config.services.ntfy-sh.enable;
|
default = true;
|
||||||
};
|
};
|
||||||
sane.ntfy-waiter.verbose = mkOption {
|
sane.ntfy-waiter.verbose = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
|
23
hosts/by-name/servo/services/ollama.nix
Normal file
23
hosts/by-name/servo/services/ollama.nix
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# ollama: <https://github.com/ollama/ollama>
|
||||||
|
# use: `ollama run llama3.1`
|
||||||
|
# or: `ollama run llama3.1:70b`
|
||||||
|
# or use a remote session: <https://github.com/ggozad/oterm>
|
||||||
|
{ lib, ... }:
|
||||||
|
lib.mkIf false #< WIP
|
||||||
|
{
|
||||||
|
sane.persist.sys.byStore.plaintext = [
|
||||||
|
{ user = "ollama"; group = "ollama"; path = "/var/lib/ollama"; method = "bind"; }
|
||||||
|
];
|
||||||
|
services.ollama.enable = true;
|
||||||
|
services.ollama.user = "ollama";
|
||||||
|
services.ollama.group = "ollama";
|
||||||
|
|
||||||
|
users.groups.ollama = {};
|
||||||
|
|
||||||
|
users.users.ollama = {
|
||||||
|
group = "ollama";
|
||||||
|
isSystemUser = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.ollama.serviceConfig.DynamicUser = lib.mkForce false;
|
||||||
|
}
|
@@ -10,11 +10,10 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
logLevel = "warning";
|
logLevel = "warn";
|
||||||
# logLevel = "debug";
|
# logLevel = "debug";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = lib.mkIf (config.sane.maxBuildCost >= 2) {
|
|
||||||
sane.persist.sys.byStore.private = [
|
sane.persist.sys.byStore.private = [
|
||||||
# contains media i've uploaded to the server
|
# contains media i've uploaded to the server
|
||||||
{ user = "pleroma"; group = "pleroma"; path = "/var/lib/pleroma"; method = "bind"; }
|
{ user = "pleroma"; group = "pleroma"; path = "/var/lib/pleroma"; method = "bind"; }
|
||||||
@@ -47,7 +46,7 @@ in
|
|||||||
config :pleroma, Pleroma.Emails.Mailer,
|
config :pleroma, Pleroma.Emails.Mailer,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
adapter: Swoosh.Adapters.Sendmail,
|
adapter: Swoosh.Adapters.Sendmail,
|
||||||
cmd_path: "${lib.getExe' pkgs.postfix "sendmail"}"
|
cmd_path: "${pkgs.postfix}/bin/sendmail"
|
||||||
|
|
||||||
config :pleroma, Pleroma.User,
|
config :pleroma, Pleroma.User,
|
||||||
restricted_nicknames: [ "admin", "uninsane", "root" ]
|
restricted_nicknames: [ "admin", "uninsane", "root" ]
|
||||||
@@ -89,12 +88,6 @@ in
|
|||||||
# strip metadata from uploaded images
|
# strip metadata from uploaded images
|
||||||
config :pleroma, Pleroma.Upload, filters: [Pleroma.Upload.Filter.Exiftool.StripLocation]
|
config :pleroma, Pleroma.Upload, filters: [Pleroma.Upload.Filter.Exiftool.StripLocation]
|
||||||
|
|
||||||
# fix log spam: <https://git.pleroma.social/pleroma/pleroma/-/issues/1659>
|
|
||||||
# specifically, remove LAN addresses from `reserved`
|
|
||||||
config :pleroma, Pleroma.Web.Plugs.RemoteIp,
|
|
||||||
enabled: true,
|
|
||||||
reserved: ["127.0.0.0/8", "::1/128", "fc00::/7", "172.16.0.0/12"]
|
|
||||||
|
|
||||||
# TODO: GET /api/pleroma/captcha is broken
|
# TODO: GET /api/pleroma/captcha is broken
|
||||||
# there was a nixpkgs PR to fix this around 2022/10 though.
|
# there was a nixpkgs PR to fix this around 2022/10 though.
|
||||||
config :pleroma, Pleroma.Captcha,
|
config :pleroma, Pleroma.Captcha,
|
||||||
@@ -143,10 +136,9 @@ in
|
|||||||
# something inside pleroma invokes `sh` w/o specifying it by path, so this is needed to allow pleroma to start
|
# something inside pleroma invokes `sh` w/o specifying it by path, so this is needed to allow pleroma to start
|
||||||
pkgs.bash
|
pkgs.bash
|
||||||
# used by Pleroma to strip geo tags from uploads
|
# used by Pleroma to strip geo tags from uploads
|
||||||
pkgs.exiftool
|
config.sane.programs.exiftool.package
|
||||||
# config.sane.programs.exiftool.package #< XXX(2024-10-20): breaks image uploading
|
|
||||||
# i saw some errors when pleroma was shutting down about it not being able to find `awk`. probably not critical
|
# i saw some errors when pleroma was shutting down about it not being able to find `awk`. probably not critical
|
||||||
# config.sane.programs.gawk.package
|
config.sane.programs.gawk.package
|
||||||
# needed for email operations like password reset
|
# needed for email operations like password reset
|
||||||
pkgs.postfix
|
pkgs.postfix
|
||||||
];
|
];
|
||||||
@@ -161,7 +153,7 @@ in
|
|||||||
# possible that i've set something too strict and won't notice right away
|
# possible that i've set something too strict and won't notice right away
|
||||||
# make sure to test:
|
# make sure to test:
|
||||||
# - image/media uploading
|
# - image/media uploading
|
||||||
serviceConfig.CapabilityBoundingSet = lib.mkForce [ "" "" ]; # nixos default is `~CAP_SYS_ADMIN`
|
serviceConfig.CapabilityBoundingSet = "~CAP_SYS_ADMIN"; #< TODO: reduce this. try: CAP_SYS_NICE CAP_DAC_READ_SEARCH CAP_SYS_CHROOT CAP_SETGID CAP_SETUID
|
||||||
serviceConfig.LockPersonality = true;
|
serviceConfig.LockPersonality = true;
|
||||||
serviceConfig.NoNewPrivileges = true;
|
serviceConfig.NoNewPrivileges = true;
|
||||||
serviceConfig.MemoryDenyWriteExecute = true;
|
serviceConfig.MemoryDenyWriteExecute = true;
|
||||||
@@ -207,7 +199,34 @@ in
|
|||||||
recommendedProxySettings = true;
|
recommendedProxySettings = true;
|
||||||
# documented: https://git.pleroma.social/pleroma/pleroma/-/blob/develop/installation/pleroma.nginx
|
# documented: https://git.pleroma.social/pleroma/pleroma/-/blob/develop/installation/pleroma.nginx
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
# client_max_body_size defines the maximum upload size
|
# 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;
|
||||||
|
|
||||||
|
# NB: this defines the maximum upload size
|
||||||
client_max_body_size 16m;
|
client_max_body_size 16m;
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
@@ -218,5 +237,4 @@ in
|
|||||||
sops.secrets."pleroma_secrets" = {
|
sops.secrets."pleroma_secrets" = {
|
||||||
owner = config.users.users.pleroma.name;
|
owner = config.users.users.pleroma.name;
|
||||||
};
|
};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{ lib, pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
GiB = n: MiB 1024*n;
|
GiB = n: MiB 1024*n;
|
||||||
@@ -29,10 +29,9 @@ in
|
|||||||
# - as `sudo su postgres`:
|
# - as `sudo su postgres`:
|
||||||
# - `cd /var/lib/postgreql`
|
# - `cd /var/lib/postgreql`
|
||||||
# - `psql -f state.sql`
|
# - `psql -f state.sql`
|
||||||
# (for a compressed dump: `gunzip --stdout state.sql.gz | psql`)
|
|
||||||
# - restart dependent services (maybe test one at a time)
|
# - restart dependent services (maybe test one at a time)
|
||||||
|
|
||||||
services.postgresql.package = pkgs.postgresql_16;
|
services.postgresql.package = pkgs.postgresql_15;
|
||||||
|
|
||||||
|
|
||||||
# XXX colin: for a proper deploy, we'd want to include something for Pleroma here too.
|
# XXX colin: for a proper deploy, we'd want to include something for Pleroma here too.
|
||||||
@@ -45,46 +44,34 @@ in
|
|||||||
# LC_CTYPE = "C";
|
# LC_CTYPE = "C";
|
||||||
# '';
|
# '';
|
||||||
|
|
||||||
services.postgresql.settings = {
|
|
||||||
# perf tuning
|
# perf tuning
|
||||||
# - for recommended values see: <https://pgtune.leopard.in.ua/>
|
# - for recommended values see: <https://pgtune.leopard.in.ua/>
|
||||||
# - for official docs (sparse), see: <https://www.postgresql.org/docs/11/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE>
|
# - for official docs (sparse), see: <https://www.postgresql.org/docs/11/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE>
|
||||||
# DB Version: 16
|
services.postgresql.settings = {
|
||||||
|
# DB Version: 15
|
||||||
# OS Type: linux
|
# OS Type: linux
|
||||||
# DB Type: web
|
# DB Type: web
|
||||||
# vvv artificially constrained because the server's resources are shared across maaany services
|
# Total Memory (RAM): 32 GB
|
||||||
# Total Memory (RAM): 12 GB
|
|
||||||
# CPUs num: 12
|
# CPUs num: 12
|
||||||
# Data Storage: ssd
|
# Data Storage: ssd
|
||||||
max_connections = 200;
|
max_connections = 200;
|
||||||
shared_buffers = "3GB";
|
shared_buffers = "8GB";
|
||||||
effective_cache_size = "9GB";
|
effective_cache_size = "24GB";
|
||||||
maintenance_work_mem = "768MB";
|
maintenance_work_mem = "2GB";
|
||||||
checkpoint_completion_target = 0.9;
|
checkpoint_completion_target = 0.9;
|
||||||
wal_buffers = "16MB";
|
wal_buffers = "16MB";
|
||||||
default_statistics_target = 100;
|
default_statistics_target = 100;
|
||||||
random_page_cost = 1.1;
|
random_page_cost = 1.1;
|
||||||
effective_io_concurrency = 200;
|
effective_io_concurrency = 200;
|
||||||
work_mem = "3932kB";
|
work_mem = "10485kB";
|
||||||
min_wal_size = "1GB";
|
min_wal_size = "1GB";
|
||||||
max_wal_size = "4GB";
|
max_wal_size = "4GB";
|
||||||
max_worker_processes = 12;
|
max_worker_processes = 12;
|
||||||
max_parallel_workers_per_gather = 4;
|
max_parallel_workers_per_gather = 4;
|
||||||
max_parallel_workers = 12;
|
max_parallel_workers = 12;
|
||||||
max_parallel_maintenance_workers = 4;
|
max_parallel_maintenance_workers = 4;
|
||||||
|
|
||||||
# DEBUG OPTIONS:
|
|
||||||
log_min_messages = "DEBUG1";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# regulate the restarts, so that systemd never disables it
|
|
||||||
systemd.services.postgresql.serviceConfig.Restart = lib.mkForce "on-failure";
|
|
||||||
systemd.services.postgresql.serviceConfig.RestartSec = 2;
|
|
||||||
systemd.services.postgresql.serviceConfig.RestartMaxDelaySec = 10;
|
|
||||||
systemd.services.postgresql.serviceConfig.RestartSteps = 4;
|
|
||||||
systemd.services.postgresql.serviceConfig.StartLimitBurst = 120;
|
|
||||||
# systemd.services.postgresql.serviceConfig.TimeoutStartSec = "14400s"; #< 14400 = 4 hours; recoveries are long
|
|
||||||
|
|
||||||
# daily backups to /var/backup
|
# daily backups to /var/backup
|
||||||
services.postgresqlBackup.enable = true;
|
services.postgresqlBackup.enable = true;
|
||||||
|
|
@@ -1,81 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# source: <https://gist.githubusercontent.com/troykelly/616df024050dd50744dde4a9579e152e/raw/fe84e53cedf0caa6903604894454629a15867439/reindex_and_refresh_collation.sh>
|
|
||||||
#
|
|
||||||
# run this whenever postgres complains like:
|
|
||||||
# > WARNING: database "gitea" has a collation version mismatch
|
|
||||||
# > DETAIL: The database was created using collation version 2.39, but the operating system provides version 2.40.
|
|
||||||
# > HINT: Rebuild all objects in this database that use the default collation and run ALTER DATABASE gitea REFRESH COLLATION VERSION, or build PostgreSQL with the right library version.
|
|
||||||
#
|
|
||||||
# this script checks which databases are in need of a collation update,
|
|
||||||
# and re-collates them as appropriate.
|
|
||||||
# invoking this script should have low perf impact in the non-upgrade case,
|
|
||||||
# so safe to do this as a cron job.
|
|
||||||
#
|
|
||||||
# invoke as postgres user
|
|
||||||
|
|
||||||
log_info() {
|
|
||||||
>&2 echo "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
list_databases() {
|
|
||||||
log_info "Retrieving list of databases from the PostgreSQL server..."
|
|
||||||
psql --dbname="postgres" -Atc \
|
|
||||||
"SELECT datname FROM pg_database WHERE datistemplate = false"
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh_collation_version() {
|
|
||||||
local db=$1
|
|
||||||
log_info "Refreshing collation version for database: $db..."
|
|
||||||
psql --dbname="$db" -c \
|
|
||||||
"ALTER DATABASE \"$db\" REFRESH COLLATION VERSION;"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_collation_mismatches() {
|
|
||||||
local error=
|
|
||||||
log_info "Checking for collation mismatches in all databases..."
|
|
||||||
# Loop through each database and check for mismatching collations in table columns.
|
|
||||||
while IFS= read -r db; do
|
|
||||||
if [ -n "$db" ]; then
|
|
||||||
log_info "Checking database: $db for collation mismatches..."
|
|
||||||
local mismatches=$(psql --dbname="$db" -Atc \
|
|
||||||
"SELECT 'Mismatch in table ' || table_name || ' column ' || column_name || ' with collation ' || collation_name
|
|
||||||
FROM information_schema.columns
|
|
||||||
WHERE collation_name IS NOT NULL AND collation_name <> 'default' AND table_schema = 'public'
|
|
||||||
EXCEPT
|
|
||||||
SELECT 'No mismatch - default collation of ' || datcollate || ' used.'
|
|
||||||
FROM pg_database WHERE datname = '$db';"
|
|
||||||
)
|
|
||||||
if [ -z "$mismatches" ]; then
|
|
||||||
log_info "No collation mismatches found in database: $db"
|
|
||||||
else
|
|
||||||
# Print an informational message to stderr.
|
|
||||||
log_info "Collation mismatches found in database: $db:"
|
|
||||||
log_info "$mismatches"
|
|
||||||
error=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -n "$error" ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
log_info "Starting the reindexing and collation refresh process for all databases..."
|
|
||||||
|
|
||||||
databases=$(list_databases)
|
|
||||||
|
|
||||||
if [ -z "$databases" ]; then
|
|
||||||
log_info "No databases found for reindexing or collation refresh. Please check connection details to PostgreSQL server."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for db in $databases; do
|
|
||||||
refresh_collation_version "$db"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Checking for collation mismatches after reindexing and collation refresh.
|
|
||||||
# Pass the list of databases to the check_collation_mismatches function through stdin.
|
|
||||||
echo "$databases" | check_collation_mismatches
|
|
||||||
|
|
||||||
log_info "Reindexing and collation refresh process completed."
|
|
@@ -49,7 +49,7 @@
|
|||||||
# - disable or fix bosh (jabber over http):
|
# - disable or fix bosh (jabber over http):
|
||||||
# - "certmanager: No certificate/key found for client_https port 0"
|
# - "certmanager: No certificate/key found for client_https port 0"
|
||||||
|
|
||||||
{ config, lib, pkgs, ... }:
|
{ lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
# enables very verbose logging
|
# enables very verbose logging
|
||||||
@@ -104,7 +104,6 @@ in
|
|||||||
users.users.prosody.extraGroups = [
|
users.users.prosody.extraGroups = [
|
||||||
"nginx" # provide access to certs
|
"nginx" # provide access to certs
|
||||||
"ntfy-sh" # access to secret ntfy topic
|
"ntfy-sh" # access to secret ntfy topic
|
||||||
"turnserver" # to access the coturn shared secret
|
|
||||||
];
|
];
|
||||||
|
|
||||||
security.acme.certs."uninsane.org".extraDomainNames = [
|
security.acme.certs."uninsane.org".extraDomainNames = [
|
||||||
@@ -150,8 +149,14 @@ in
|
|||||||
# pointing it to /var/lib/acme doesn't quite work because it expects the private key
|
# pointing it to /var/lib/acme doesn't quite work because it expects the private key
|
||||||
# to be named `privkey.pem` instead of acme's `key.pem`
|
# to be named `privkey.pem` instead of acme's `key.pem`
|
||||||
# <https://prosody.im/doc/certificates#automatic_location>
|
# <https://prosody.im/doc/certificates#automatic_location>
|
||||||
environment.etc."prosody/certs/uninsane.org/fullchain.pem".source = "/var/lib/acme/uninsane.org/fullchain.pem";
|
sane.fs."/etc/prosody/certs/uninsane.org/fullchain.pem" = {
|
||||||
environment.etc."prosody/certs/uninsane.org/privkey.pem".source = "/var/lib/acme/uninsane.org/key.pem";
|
symlink.target = "/var/lib/acme/uninsane.org/fullchain.pem";
|
||||||
|
wantedBeforeBy = [ "prosody.service" ];
|
||||||
|
};
|
||||||
|
sane.fs."/etc/prosody/certs/uninsane.org/privkey.pem" = {
|
||||||
|
symlink.target = "/var/lib/acme/uninsane.org/key.pem";
|
||||||
|
wantedBeforeBy = [ "prosody.service" ];
|
||||||
|
};
|
||||||
|
|
||||||
services.prosody = {
|
services.prosody = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@@ -173,7 +178,7 @@ in
|
|||||||
domain = "conference.xmpp.uninsane.org";
|
domain = "conference.xmpp.uninsane.org";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
httpFileShare.domain = "upload.xmpp.uninsane.org";
|
uploadHttp.domain = "upload.xmpp.uninsane.org";
|
||||||
|
|
||||||
virtualHosts = {
|
virtualHosts = {
|
||||||
# "Prosody requires at least one enabled VirtualHost to function. You can
|
# "Prosody requires at least one enabled VirtualHost to function. You can
|
||||||
@@ -237,7 +242,6 @@ in
|
|||||||
# legacy coturn integration
|
# legacy coturn integration
|
||||||
# see: <https://modules.prosody.im/mod_turncredentials.html>
|
# see: <https://modules.prosody.im/mod_turncredentials.html>
|
||||||
# "turncredentials"
|
# "turncredentials"
|
||||||
] ++ lib.optionals config.services.ntfy-sh.enable [
|
|
||||||
"sane_ntfy"
|
"sane_ntfy"
|
||||||
] ++ lib.optionals enableDebug [
|
] ++ lib.optionals enableDebug [
|
||||||
"stanza_debug" #< logs EVERY stanza as debug: <https://prosody.im/doc/modules/mod_stanza_debug>
|
"stanza_debug" #< logs EVERY stanza as debug: <https://prosody.im/doc/modules/mod_stanza_debug>
|
||||||
@@ -269,35 +273,18 @@ in
|
|||||||
s2s_direct_tls_ports = { 5270 }
|
s2s_direct_tls_ports = { 5270 }
|
||||||
|
|
||||||
turn_external_host = "turn.uninsane.org"
|
turn_external_host = "turn.uninsane.org"
|
||||||
turn_external_secret = readAll("/run/secrets/coturn_shared_secret")
|
turn_external_secret = readAll("/var/lib/coturn/shared_secret.bin")
|
||||||
-- turn_external_user = "prosody"
|
-- turn_external_user = "prosody"
|
||||||
|
|
||||||
-- legacy mod_turncredentials integration
|
-- legacy mod_turncredentials integration
|
||||||
-- turncredentials_host = "turn.uninsane.org"
|
-- turncredentials_host = "turn.uninsane.org"
|
||||||
-- turncredentials_secret = readAll("/run/secrets/coturn_shared_secret")
|
-- turncredentials_secret = readAll("/var/lib/coturn/shared_secret.bin")
|
||||||
|
|
||||||
|
ntfy_binary = "${pkgs.ntfy-sh}/bin/ntfy"
|
||||||
|
ntfy_topic = readAll("/run/secrets/ntfy-sh-topic")
|
||||||
|
|
||||||
-- s2s_require_encryption = true
|
-- s2s_require_encryption = true
|
||||||
-- c2s_require_encryption = true
|
-- c2s_require_encryption = true
|
||||||
'' + lib.optionalString config.services.ntfy-sh.enable ''
|
|
||||||
ntfy_binary = "${lib.getExe' pkgs.ntfy-sh "ntfy"}"
|
|
||||||
ntfy_topic = readAll("/run/secrets/ntfy-sh-topic")
|
|
||||||
'';
|
'';
|
||||||
checkConfig = false; # secrets aren't available at build time
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.prosody = {
|
|
||||||
# hardening (systemd-analyze security prosody)
|
|
||||||
serviceConfig.LockPersonality = true;
|
|
||||||
serviceConfig.NoNewPrivileges = true;
|
|
||||||
serviceConfig.PrivateUsers = true;
|
|
||||||
serviceConfig.ProcSubset = "pid";
|
|
||||||
serviceConfig.ProtectClock = true;
|
|
||||||
serviceConfig.ProtectKernelLogs = true;
|
|
||||||
serviceConfig.ProtectProc = "invisible";
|
|
||||||
serviceConfig.ProtectSystem = "strict";
|
|
||||||
serviceConfig.RemoveIPC = true;
|
|
||||||
serviceConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
|
|
||||||
serviceConfig.SystemCallArchitectures = "native";
|
|
||||||
serviceConfig.SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -34,9 +34,8 @@
|
|||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
locations."/" = {
|
locations."/" = {
|
||||||
proxyPass = "http://${config.sane.netns.ovpns.veth.netns.ipv4}:5030";
|
proxyPass = "http://${config.sane.netns.ovpns.netnsVethIpv4}:5030";
|
||||||
proxyWebsockets = true;
|
proxyWebsockets = true;
|
||||||
recommendedProxySettings = true;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,10 +73,7 @@
|
|||||||
systemd.services.slskd = {
|
systemd.services.slskd = {
|
||||||
# run this behind the OVPN static VPN
|
# run this behind the OVPN static VPN
|
||||||
serviceConfig.NetworkNamespacePath = "/run/netns/ovpns";
|
serviceConfig.NetworkNamespacePath = "/run/netns/ovpns";
|
||||||
serviceConfig.ExecStartPre = [
|
serviceConfig.ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect ${config.sane.netns.ovpns.netnsPubIpv4}" ]; # abort if public IP is not as expected
|
||||||
# abort if public IP is not as expected
|
|
||||||
"${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect ${config.sane.netns.ovpns.wg.address.ipv4}"
|
|
||||||
];
|
|
||||||
|
|
||||||
serviceConfig.Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
|
serviceConfig.Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
|
||||||
serviceConfig.RestartSec = "60s";
|
serviceConfig.RestartSec = "60s";
|
||||||
|
@@ -58,8 +58,8 @@ in
|
|||||||
# DOCUMENTATION/options list: <https://github.com/transmission/transmission/blob/main/docs/Editing-Configuration-Files.md#options>
|
# DOCUMENTATION/options list: <https://github.com/transmission/transmission/blob/main/docs/Editing-Configuration-Files.md#options>
|
||||||
|
|
||||||
# message-level = 3; #< enable for debug logging. 0-3, default is 2.
|
# message-level = 3; #< enable for debug logging. 0-3, default is 2.
|
||||||
# ovpns.veth.netns.ipv4 => allow rpc only from the root servo ns. it'll tunnel things to the net, if need be.
|
# ovpns.netnsVethIpv4 => allow rpc only from the root servo ns. it'll tunnel things to the net, if need be.
|
||||||
rpc-bind-address = config.sane.netns.ovpns.veth.netns.ipv4;
|
rpc-bind-address = config.sane.netns.ovpns.netnsVethIpv4;
|
||||||
#rpc-host-whitelist = "bt.uninsane.org";
|
#rpc-host-whitelist = "bt.uninsane.org";
|
||||||
#rpc-whitelist = "*.*.*.*";
|
#rpc-whitelist = "*.*.*.*";
|
||||||
rpc-authentication-required = true;
|
rpc-authentication-required = true;
|
||||||
@@ -70,7 +70,7 @@ in
|
|||||||
rpc-whitelist-enabled = false;
|
rpc-whitelist-enabled = false;
|
||||||
|
|
||||||
# force behind ovpns in case the NetworkNamespace fails somehow
|
# force behind ovpns in case the NetworkNamespace fails somehow
|
||||||
bind-address-ipv4 = config.sane.netns.ovpns.wg.address.ipv4;
|
bind-address-ipv4 = config.sane.netns.ovpns.netnsPubIpv4;
|
||||||
port-forwarding-enabled = false;
|
port-forwarding-enabled = false;
|
||||||
|
|
||||||
# hopefully, make the downloads world-readable
|
# hopefully, make the downloads world-readable
|
||||||
@@ -104,17 +104,16 @@ in
|
|||||||
# - TR_TORRENT_NAME - Name of torrent (not filename)
|
# - TR_TORRENT_NAME - Name of torrent (not filename)
|
||||||
# - TR_TORRENT_TRACKERS - A comma-delimited list of the torrent's trackers' announce URLs
|
# - TR_TORRENT_TRACKERS - A comma-delimited list of the torrent's trackers' announce URLs
|
||||||
script-torrent-done-enabled = true;
|
script-torrent-done-enabled = true;
|
||||||
script-torrent-done-filename = lib.getExe torrent-done;
|
script-torrent-done-filename = "${torrent-done}/bin/torrent-done";
|
||||||
};
|
};
|
||||||
|
|
||||||
# run this behind the OVPN static VPN
|
|
||||||
sane.netns.ovpns.services = [ "transmission" ];
|
|
||||||
systemd.services.transmission = {
|
systemd.services.transmission = {
|
||||||
|
after = [ "wireguard-wg-ovpns.service" ];
|
||||||
|
partOf = [ "wireguard-wg-ovpns.service" ];
|
||||||
environment.TR_DEBUG = "1";
|
environment.TR_DEBUG = "1";
|
||||||
serviceConfig.ExecStartPre = [
|
# run this behind the OVPN static VPN
|
||||||
# abort if public IP is not as expected
|
serviceConfig.NetworkNamespacePath = "/run/netns/ovpns";
|
||||||
"${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect ${config.sane.netns.ovpns.wg.address.ipv4}"
|
serviceConfig.ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect ${config.sane.netns.ovpns.netnsPubIpv4}" ]; # abort if public IP is not as expected
|
||||||
];
|
|
||||||
|
|
||||||
serviceConfig.Restart = "on-failure";
|
serviceConfig.Restart = "on-failure";
|
||||||
serviceConfig.RestartSec = "30s";
|
serviceConfig.RestartSec = "30s";
|
||||||
@@ -139,7 +138,7 @@ in
|
|||||||
systemd.services.backup-torrents = {
|
systemd.services.backup-torrents = {
|
||||||
description = "archive torrents to storage not owned by transmission";
|
description = "archive torrents to storage not owned by transmission";
|
||||||
script = ''
|
script = ''
|
||||||
${lib.getExe pkgs.rsync} -arv /var/lib/transmission/.config/transmission-daemon/torrents/ /var/backup/torrents/
|
${pkgs.rsync}/bin/rsync -arv /var/lib/transmission/.config/transmission-daemon/torrents/ /var/backup/torrents/
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
systemd.timers.backup-torrents = {
|
systemd.timers.backup-torrents = {
|
||||||
@@ -158,8 +157,7 @@ in
|
|||||||
# inherit kTLS;
|
# inherit kTLS;
|
||||||
locations."/" = {
|
locations."/" = {
|
||||||
# proxyPass = "http://ovpns.uninsane.org:9091";
|
# proxyPass = "http://ovpns.uninsane.org:9091";
|
||||||
proxyPass = "http://${config.sane.netns.ovpns.veth.netns.ipv4}:9091";
|
proxyPass = "http://${config.sane.netns.ovpns.netnsVethIpv4}:9091";
|
||||||
recommendedProxySettings = true;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -3,20 +3,8 @@
|
|||||||
|
|
||||||
# transmission invokes this with no args, and the following env vars:
|
# transmission invokes this with no args, and the following env vars:
|
||||||
# - TR_TORRENT_DIR: full path to the folder i told transmission to download it to.
|
# - TR_TORRENT_DIR: full path to the folder i told transmission to download it to.
|
||||||
# e.g. "/var/media/torrents/Videos/Film/Jason.Bourne-2016"
|
# e.g. /var/media/torrents/Videos/Film/Jason.Bourne-2016
|
||||||
# - TR_APP_VERSION
|
# optionally:
|
||||||
# - TR_TIME_LOCALTIME
|
|
||||||
# - TR_TORRENT_BYTES_DOWNLOADED
|
|
||||||
# - TR_TORRENT_HASH
|
|
||||||
# - TR_TORRENT_ID: local number to uniquely identify this torrent, used by e.g. transmission-remote.
|
|
||||||
# e.g. "67"
|
|
||||||
# - TR_TORRENT_LABELS
|
|
||||||
# - TR_TORRENT_NAME: file/folder name of the toplevel torrent item
|
|
||||||
# e.g. "Jason Bourne (2016) [2160p] [4K] [BluRay] [5.1] [YTS.MX]"
|
|
||||||
# - TR_TORRENT_PRIORITY
|
|
||||||
# - TR_TORRENT_TRACKERS
|
|
||||||
|
|
||||||
# optionally, set these variables for debugging (these are specific to my script and not used upstream):
|
|
||||||
# - TR_DRY_RUN=1
|
# - TR_DRY_RUN=1
|
||||||
# - TR_DEBUG=1
|
# - TR_DEBUG=1
|
||||||
|
|
||||||
@@ -36,7 +24,7 @@ debug() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "TR_TORRENT_DIR=$TR_TORRENT_DIR TR_TORRENT_NAME=$TR_TORRENT_NAME torrent-done $*"
|
echo "TR_TORRENT_DIR=$TR_TORRENT_DIR torrent-done $*"
|
||||||
|
|
||||||
if [[ "$TR_TORRENT_DIR" =~ ^.*freeleech.*$ ]]; then
|
if [[ "$TR_TORRENT_DIR" =~ ^.*freeleech.*$ ]]; then
|
||||||
# freeleech torrents have no place in my permanent library
|
# freeleech torrents have no place in my permanent library
|
||||||
@@ -45,35 +33,20 @@ if [[ "$TR_TORRENT_DIR" =~ ^.*freeleech.*$ ]]; then
|
|||||||
fi
|
fi
|
||||||
if ! [[ "$TR_TORRENT_DIR" =~ ^$DOWNLOAD_DIR/.*$ ]]; then
|
if ! [[ "$TR_TORRENT_DIR" =~ ^$DOWNLOAD_DIR/.*$ ]]; then
|
||||||
echo "unexpected torrent dir, aborting: $TR_TORRENT_DIR"
|
echo "unexpected torrent dir, aborting: $TR_TORRENT_DIR"
|
||||||
exit 1
|
exit 0
|
||||||
fi
|
|
||||||
|
|
||||||
TORRENT_PATH="$TR_TORRENT_DIR/$TR_TORRENT_NAME"
|
|
||||||
if [[ ! -e "$TORRENT_PATH" ]]; then
|
|
||||||
echo "torrent unexpectedly doesn't exist at $TORRENT_PATH. will try fallback"
|
|
||||||
TORRENT_PATH="$TR_TORRENT_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -d "$TORRENT_PATH" ]]; then
|
|
||||||
# trailing slash so that rsync copies the directory contents, without creating an extra toplevel dir.
|
|
||||||
TORRENT_PATH="$TORRENT_PATH/"
|
|
||||||
elif [[ ! -e "$TORRENT_PATH" ]]; then
|
|
||||||
echo "torrent unexpectedly doesn't exist at TR_TORRENT_DIR=$TORRENT_PATH: bailing"
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
REL_DIR="${TR_TORRENT_DIR#$DOWNLOAD_DIR/}"
|
REL_DIR="${TR_TORRENT_DIR#$DOWNLOAD_DIR/}"
|
||||||
MEDIA_DIR="/var/media/$REL_DIR"
|
MEDIA_DIR="/var/media/$REL_DIR"
|
||||||
|
|
||||||
destructive mkdir -p "$(dirname "$MEDIA_DIR")"
|
destructive mkdir -p "$(dirname "$MEDIA_DIR")"
|
||||||
destructive rsync -rlv "$TORRENT_PATH" "$MEDIA_DIR/"
|
destructive rsync -rlv "$TR_TORRENT_DIR/" "$MEDIA_DIR/"
|
||||||
# make the media rwx by anyone in the group
|
# make the media rwx by anyone in the group
|
||||||
destructive find "$MEDIA_DIR" -type d -exec setfacl --recursive --modify d:g::rwx,o::rx {} \;
|
destructive find "$MEDIA_DIR" -type d -exec setfacl --recursive --modify d:g::rwx,o::rx {} \;
|
||||||
destructive find "$MEDIA_DIR" -type d -exec chmod g+rw,a+rx {} \;
|
destructive find "$MEDIA_DIR" -type d -exec chmod g+rw,a+rx {} \;
|
||||||
destructive find "$MEDIA_DIR" -type f -exec chmod g+rw,a+r {} \;
|
destructive find "$MEDIA_DIR" -type f -exec chmod g+rw,a+r {} \;
|
||||||
|
|
||||||
# if there's a single directory inside the media dir, then inline that.
|
# if there's a single directory inside the media dir, then inline that
|
||||||
# TODO: this is probably obsolete now that i process TR_TORRENT_NAME
|
|
||||||
subdirs=("$MEDIA_DIR"/*)
|
subdirs=("$MEDIA_DIR"/*)
|
||||||
debug "top-level items in torrent dir:" "${subdirs[@]}"
|
debug "top-level items in torrent dir:" "${subdirs[@]}"
|
||||||
if [ ${#subdirs[@]} -eq 1 ]; then
|
if [ ${#subdirs[@]} -eq 1 ]; then
|
||||||
@@ -88,24 +61,10 @@ fi
|
|||||||
# -iname means "insensitive", but the syntax is NOT regex -- more similar to shell matching
|
# -iname means "insensitive", but the syntax is NOT regex -- more similar to shell matching
|
||||||
destructive find "$MEDIA_DIR/" -type f \(\
|
destructive find "$MEDIA_DIR/" -type f \(\
|
||||||
-iname '*downloaded?from*' \
|
-iname '*downloaded?from*' \
|
||||||
-o -iname '(xxxpav69).txt' \
|
-o -iname 'source.txt' \
|
||||||
-o -iname '*upcoming?releases*' \
|
-o -iname '*upcoming?releases*' \
|
||||||
-o -iname 'ETRG.mp4' \
|
-o -iname 'www.YTS*.jpg' \
|
||||||
-o -iname 'Encoded by*.txt' \
|
|
||||||
-o -iname 'PSArips.com.txt' \
|
|
||||||
-o -iname 'RARBG.com*' \
|
|
||||||
-o -iname 'RARBG.txt' \
|
|
||||||
-o -iname 'RARBG_DO_NOT_MIRROR.exe' \
|
|
||||||
-o -iname 'Tellytorrent.net.txt' \
|
|
||||||
-o -iname 'WWW.VPPV.LA.txt' \
|
|
||||||
-o -iname 'WWW.YIFY*.COM.jpg' \
|
-o -iname 'WWW.YIFY*.COM.jpg' \
|
||||||
-o -iname 'YIFY*.com.txt' \
|
-o -iname 'YIFY*.com.txt' \
|
||||||
-o -iname 'YTS*.com.txt' \
|
-o -iname 'YTS*.com.txt' \
|
||||||
-o -iname 'YTSYify*.txt' \
|
|
||||||
-o -iname 'www.YTS*.jpg' \
|
|
||||||
\) -exec rm {} \;
|
\) -exec rm {} \;
|
||||||
|
|
||||||
# might want to keep, might want to remove:
|
|
||||||
# -o -iname 'info.txt'
|
|
||||||
# -o -iname 'source.txt'
|
|
||||||
# -o -iname 'sample.mkv'
|
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./shelvacu.nix
|
|
||||||
];
|
|
||||||
}
|
|
@@ -1,65 +0,0 @@
|
|||||||
{ lib, pkgs, ... }:
|
|
||||||
{
|
|
||||||
users.users.shelvacu = {
|
|
||||||
isNormalUser = true;
|
|
||||||
home = "/home/shelvacu";
|
|
||||||
subUidRanges = [
|
|
||||||
{ startUid=300000; count=1; }
|
|
||||||
];
|
|
||||||
group = "users";
|
|
||||||
initialPassword = lib.mkDefault "";
|
|
||||||
shell = pkgs.bash;
|
|
||||||
|
|
||||||
openssh.authorizedKeys.keys = [
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKoy1TrmfhBGWtVedgOM1FB1oD2UdodN3LkBnnLx6Tug compute-deck"
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAxAFFxQMXAgi+0cmGaNE/eAkVfEl91wafUqFIuAkI5I compute-deck-root"
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINQ2c0GzlVMjV06CS7bWbCaAbzG2+7g5FCg/vClJPe0C fw"
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGHLPOxRd68+DJ/bYmqn0wsgwwIcMSMyuU1Ya16hCb/m fw-root"
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOre0FnYDm3arsFj9c/l5H2Q8mdmv7kmvq683pL4heru legtop"
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINznGot+L8kYoVQqdLV/R17XCd1ILMoDCILOg+I3s5wC pixel9pro-nod"
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDcRDekd8ZOYfQS5X95/yNof3wFYIbHqWeq4jY0+ywQX pro1x-nod"
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJNFbzt0NHVTaptBI38YtwLG+AsmeNYy0Nr5yX2zZEPE root@vacuInstaller toptop-root"
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICVeSzDkGTueZijB0xUa08e06ovAEwwZK/D+Cc7bo91g triple-dezert"
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOtwtao/TXbiuQOYJbousRPVesVcb/2nP0PCFUec0Nv8 triple-dezert-root"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
security.sudo.extraRules = [
|
|
||||||
{
|
|
||||||
users = [ "shelvacu" ];
|
|
||||||
runAs = "postgres";
|
|
||||||
commands = [
|
|
||||||
{
|
|
||||||
command = "ALL";
|
|
||||||
options = [ "NOPASSWD" ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
security.polkit.extraConfig = ''
|
|
||||||
// allow:
|
|
||||||
// - systemctl restart|start|stop SERVICE
|
|
||||||
polkit.addRule(function(action, subject) {
|
|
||||||
if (subject.user == "shelvacu" && action.id == "org.freedesktop.systemd1.manage-units") {
|
|
||||||
switch (action.lookup("verb")) {
|
|
||||||
// case "cancel":
|
|
||||||
// case "reenable":
|
|
||||||
case "restart":
|
|
||||||
// case "reload":
|
|
||||||
// case "reload-or-restart":
|
|
||||||
case "start":
|
|
||||||
case "stop":
|
|
||||||
// case "try-reload-or-restart":
|
|
||||||
// case "try-restart":
|
|
||||||
return polkit.Result.YES;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
'';
|
|
||||||
|
|
||||||
sane.persist.sys.byStore.private = [
|
|
||||||
{ path = "/home/shelvacu/persist"; user = "shelvacu"; group = "users"; mode = "0700"; }
|
|
||||||
];
|
|
||||||
}
|
|
@@ -3,13 +3,13 @@
|
|||||||
boot.initrd.supportedFilesystems = [ "ext4" "btrfs" "ext2" "ext3" "vfat" ];
|
boot.initrd.supportedFilesystems = [ "ext4" "btrfs" "ext2" "ext3" "vfat" ];
|
||||||
# useful emergency utils
|
# useful emergency utils
|
||||||
boot.initrd.extraUtilsCommands = ''
|
boot.initrd.extraUtilsCommands = ''
|
||||||
copy_bin_and_libs ${lib.getExe' pkgs.btrfs-progs "btrfstune"}
|
copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfstune
|
||||||
copy_bin_and_libs ${lib.getExe' pkgs.e2fsprogs "resize2fs"}
|
copy_bin_and_libs ${pkgs.util-linux}/bin/{cfdisk,lsblk,lscpu}
|
||||||
copy_bin_and_libs ${lib.getExe' pkgs.gptfdisk "{cgdisk,gdisk}"}
|
copy_bin_and_libs ${pkgs.gptfdisk}/bin/{cgdisk,gdisk}
|
||||||
copy_bin_and_libs ${lib.getExe' pkgs.mtools "mlabel"}
|
copy_bin_and_libs ${pkgs.smartmontools}/bin/smartctl
|
||||||
copy_bin_and_libs ${lib.getExe pkgs.nvme-cli}
|
copy_bin_and_libs ${pkgs.e2fsprogs}/bin/resize2fs
|
||||||
copy_bin_and_libs ${lib.getExe' pkgs.smartmontools "smartctl"}
|
'' + lib.optionalString pkgs.stdenv.hostPlatform.isx86_64 ''
|
||||||
copy_bin_and_libs ${lib.getExe' pkgs.util-linux "{cfdisk,lsblk,lscpu}"}
|
copy_bin_and_libs ${pkgs.nvme-cli}/bin/nvme # doesn't cross compile
|
||||||
'';
|
'';
|
||||||
boot.kernelParams = [
|
boot.kernelParams = [
|
||||||
"boot.shell_on_fail"
|
"boot.shell_on_fail"
|
||||||
@@ -25,11 +25,10 @@
|
|||||||
|
|
||||||
# moby has to run recent kernels (defined elsewhere).
|
# moby has to run recent kernels (defined elsewhere).
|
||||||
# meanwhile, kernel variation plays some minor role in things like sandboxing (landlock) and capabilities.
|
# meanwhile, kernel variation plays some minor role in things like sandboxing (landlock) and capabilities.
|
||||||
# - as of 2024/08/xx, my boot fails on 6.6, but works on 6.9 and (probably; recently) 6.8.
|
|
||||||
# simpler to keep near the latest kernel on all devices,
|
# simpler to keep near the latest kernel on all devices,
|
||||||
# and also makes certain that any weird system-level bugs i see aren't likely to be stale kernel bugs.
|
# and also makes certain that any weird system-level bugs i see aren't likely to be stale kernel bugs.
|
||||||
boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
|
# servo needs zfs though, which doesn't support every kernel.
|
||||||
# boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_testing;
|
boot.kernelPackages = lib.mkDefault pkgs.zfs.latestCompatibleLinuxPackages;
|
||||||
|
|
||||||
# hack in the `boot.shell_on_fail` arg since that doesn't always seem to work.
|
# hack in the `boot.shell_on_fail` arg since that doesn't always seem to work.
|
||||||
boot.initrd.preFailCommands = "allowShell=1";
|
boot.initrd.preFailCommands = "allowShell=1";
|
||||||
@@ -38,12 +37,7 @@
|
|||||||
boot.consoleLogLevel = 7;
|
boot.consoleLogLevel = 7;
|
||||||
|
|
||||||
boot.loader.grub.enable = lib.mkDefault false;
|
boot.loader.grub.enable = lib.mkDefault false;
|
||||||
# boot.loader.generic-extlinux-compatible.enable = lib.mkDefault true;
|
boot.loader.generic-extlinux-compatible.enable = lib.mkDefault true;
|
||||||
boot.loader.systemd-boot.enable = lib.mkDefault true;
|
|
||||||
boot.loader.systemd-boot.configurationLimit = lib.mkDefault 20;
|
|
||||||
boot.loader.systemd-boot.edk2-uefi-shell.enable = lib.mkDefault true;
|
|
||||||
boot.loader.systemd-boot.memtest86.enable = lib.mkDefault
|
|
||||||
(lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.memtest86plus);
|
|
||||||
|
|
||||||
hardware.enableAllFirmware = true; # firmware with licenses that don't allow for redistribution. fuck lawyers, fuck IP, give me the goddamn firmware.
|
hardware.enableAllFirmware = true; # firmware with licenses that don't allow for redistribution. fuck lawyers, fuck IP, give me the goddamn firmware.
|
||||||
# hardware.enableRedistributableFirmware = true; # proprietary but free-to-distribute firmware (extraneous to `enableAllFirmware` option)
|
# hardware.enableRedistributableFirmware = true; # proprietary but free-to-distribute firmware (extraneous to `enableAllFirmware` option)
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
{ lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./boot.nix
|
./boot.nix
|
||||||
./feeds.nix
|
./feeds.nix
|
||||||
./fs
|
./fs.nix
|
||||||
./home
|
./home
|
||||||
./hosts.nix
|
./hosts.nix
|
||||||
./ids.nix
|
./ids.nix
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
./programs
|
./programs
|
||||||
./quirks.nix
|
./quirks.nix
|
||||||
./secrets.nix
|
./secrets.nix
|
||||||
./snapper.nix
|
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
./systemd.nix
|
./systemd.nix
|
||||||
./users
|
./users
|
||||||
@@ -30,18 +29,8 @@
|
|||||||
sane.persist.enable = lib.mkDefault true;
|
sane.persist.enable = lib.mkDefault true;
|
||||||
sane.root-on-tmpfs = lib.mkDefault true;
|
sane.root-on-tmpfs = lib.mkDefault true;
|
||||||
sane.programs.sysadminUtils.enableFor.system = lib.mkDefault true;
|
sane.programs.sysadminUtils.enableFor.system = lib.mkDefault true;
|
||||||
sane.programs.sysadminExtraUtils.enableFor.system = lib.mkDefault true;
|
|
||||||
sane.programs.consoleUtils.enableFor.user.colin = lib.mkDefault true;
|
sane.programs.consoleUtils.enableFor.user.colin = lib.mkDefault true;
|
||||||
|
|
||||||
services.buffyboard.enable = true;
|
|
||||||
services.buffyboard.settings.theme.default = "pmos-light";
|
|
||||||
# services.buffyboard.settings.quirks.fbdev_force_refresh = true;
|
|
||||||
services.buffyboard.extraFlags = [ "--verbose" ];
|
|
||||||
|
|
||||||
# irqbalance monitors interrupt count (as a daemon) and assigns high-frequency interrupts to different CPUs.
|
|
||||||
# that reduces contention between simultaneously-fired interrupts.
|
|
||||||
services.irqbalance.enable = true;
|
|
||||||
|
|
||||||
# time.timeZone = "America/Los_Angeles";
|
# time.timeZone = "America/Los_Angeles";
|
||||||
time.timeZone = "Etc/UTC"; # DST is too confusing for me => use a stable timezone
|
time.timeZone = "Etc/UTC"; # DST is too confusing for me => use a stable timezone
|
||||||
|
|
||||||
@@ -52,7 +41,7 @@
|
|||||||
# source: <https://github.com/luishfonseca/dotfiles/blob/32c10e775d9ec7cc55e44592a060c1c9aadf113e/modules/upgrade-diff.nix>
|
# source: <https://github.com/luishfonseca/dotfiles/blob/32c10e775d9ec7cc55e44592a060c1c9aadf113e/modules/upgrade-diff.nix>
|
||||||
# modified to not error on boot (when /run/current-system doesn't exist)
|
# modified to not error on boot (when /run/current-system doesn't exist)
|
||||||
if [ -d /run/current-system ]; then
|
if [ -d /run/current-system ]; then
|
||||||
${lib.getExe pkgs.nvd} --nix-bin-dir=${pkgs.nix}/bin diff /run/current-system "$systemConfig"
|
${pkgs.nvd}/bin/nvd --nix-bin-dir=${pkgs.nix}/bin diff /run/current-system "$systemConfig"
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
@@ -1,15 +1,11 @@
|
|||||||
# where to find good stuff?
|
# where to find good stuff?
|
||||||
# - universal search/directory: <https://podcastindex.org>
|
# - universal search/directory: <https://podcastindex.org>
|
||||||
# - the full database is downloadable
|
|
||||||
# - find adjacent podcasts: <https://rephonic.com/graph>
|
|
||||||
# - charts: <https://rephonic.com/charts/apple/united-states/technology>
|
|
||||||
# - list of lists: <https://en.wikipedia.org/wiki/Category:Lists_of_podcasts>
|
# - list of lists: <https://en.wikipedia.org/wiki/Category:Lists_of_podcasts>
|
||||||
# - podcasts w/ a community: <https://lemmyverse.net/communities?query=podcast>
|
# - podcasts w/ a community: <https://lemmyverse.net/communities?query=podcast>
|
||||||
# - podcast recs:
|
# - podcast recs:
|
||||||
# - active lemmy: <https://slrpnk.net/c/podcasts>
|
# - active lemmy: <https://slrpnk.net/c/podcasts>
|
||||||
# - old thread: <https://lemmy.ml/post/1565858>
|
# - old thread: <https://lemmy.ml/post/1565858>
|
||||||
#
|
#
|
||||||
# - paywall bypass / bootlegs: <https://jumble.top/>
|
|
||||||
{ lib, sane-data, ... }:
|
{ lib, sane-data, ... }:
|
||||||
let
|
let
|
||||||
hourly = { freq = "hourly"; };
|
hourly = { freq = "hourly"; };
|
||||||
@@ -61,36 +57,25 @@ let
|
|||||||
};
|
};
|
||||||
|
|
||||||
podcasts = [
|
podcasts = [
|
||||||
(fromDb "404media.co/the-404-media-podcast" // tech)
|
|
||||||
(fromDb "acquiredlpbonussecretsecret.libsyn.com" // tech) # ACQ2 - more "Acquired" episodes
|
(fromDb "acquiredlpbonussecretsecret.libsyn.com" // tech) # ACQ2 - more "Acquired" episodes
|
||||||
(fromDb "adventofcomputing.com" // tech) # computing history
|
(fromDb "allinchamathjason.libsyn.com" // pol)
|
||||||
(fromDb "api.oyez.org/podcasts/oral-arguments/2015" // pol) # Supreme Court Oral Arguments ("2015" in URL means nothing -- it's still updated)
|
(fromDb "api.oyez.org/podcasts/oral-arguments/2015" // pol) # Supreme Court Oral Arguments ("2015" in URL means nothing -- it's still updated)
|
||||||
(fromDb "anchor.fm/s/34c7232c/podcast/rss" // tech) # Civboot -- https://anchor.fm/civboot
|
(fromDb "anchor.fm/s/34c7232c/podcast/rss" // tech) # Civboot -- https://anchor.fm/civboot
|
||||||
(fromDb "anchor.fm/s/2da69154/podcast/rss" // tech) # POD OF JAKE -- https://podofjake.com/
|
(fromDb "anchor.fm/s/2da69154/podcast/rss" // tech) # POD OF JAKE -- https://podofjake.com/
|
||||||
(fromDb "bluecityblues.org.podcastpage.io" // pol) # hosts overlap with Seattle Nice
|
|
||||||
(fromDb "buzzsprout.com/2126417" // tech) # Mystery AI Hype Theater 3000
|
|
||||||
(fromDb "cast.postmarketos.org" // tech)
|
(fromDb "cast.postmarketos.org" // tech)
|
||||||
(fromDb "congressionaldish.libsyn.com" // pol) # Jennifer Briney
|
(fromDb "congressionaldish.libsyn.com" // pol) # Jennifer Briney
|
||||||
(fromDb "craphound.com" // pol) # Cory Doctorow -- both podcast & text entries
|
(fromDb "craphound.com" // pol) # Cory Doctorow -- both podcast & text entries
|
||||||
(fromDb "darknetdiaries.com" // tech)
|
(fromDb "darknetdiaries.com" // tech)
|
||||||
(fromDb "dwarkeshpatel.com" // tech)
|
(fromDb "feed.podbean.com/matrixlive/feed.xml" // tech) # Matrix (chat) Live
|
||||||
(fromDb "feeds.99percentinvisible.org/99percentinvisible" // pol) # 99% Invisible -- also available here: <https://feeds.simplecast.com/BqbsxVfO>
|
(fromDb "feeds.99percentinvisible.org/99percentinvisible" // pol) # 99% Invisible -- also available here: <https://feeds.simplecast.com/BqbsxVfO>
|
||||||
(fromDb "feeds.acast.com/public/shows/lawfare" // pol) # <https://www.lawfaremedia.org/podcasts-multimedia/podcast/the-lawfare-podcast>
|
|
||||||
(fromDb "feeds.buzzsprout.com/2412334.rss") # Matt Stoller's _Organized Money_ <https://www.organizedmoney.fm/>
|
|
||||||
(fromDb "feeds.eff.org/howtofixtheinternet" // pol)
|
|
||||||
(fromDb "feeds.feedburner.com/80000HoursPodcast" // rat)
|
(fromDb "feeds.feedburner.com/80000HoursPodcast" // rat)
|
||||||
(fromDb "feeds.feedburner.com/dancarlin/history" // rat)
|
(fromDb "feeds.feedburner.com/dancarlin/history" // rat)
|
||||||
(fromDb "feeds.feedburner.com/radiolab" // pol) # Radiolab -- also available here, but ONLY OVER HTTP: <http://feeds.wnyc.org/radiolab>
|
(fromDb "feeds.feedburner.com/radiolab" // pol) # Radiolab -- also available here, but ONLY OVER HTTP: <http://feeds.wnyc.org/radiolab>
|
||||||
(fromDb "feeds.megaphone.fm/CHTAL4990341033" // pol) # ChinaTalk: https://www.chinatalk.media/podcast
|
|
||||||
(fromDb "feeds.megaphone.fm/GLT1412515089" // pol) # JRE: Joe Rogan Experience
|
|
||||||
(fromDb "feeds.megaphone.fm/behindthebastards" // pol) # also Maggie Killjoy
|
(fromDb "feeds.megaphone.fm/behindthebastards" // pol) # also Maggie Killjoy
|
||||||
(fromDb "feeds.megaphone.fm/cspantheweekly" // pol)
|
|
||||||
(fromDb "feeds.megaphone.fm/econ102") # Noah Smith + Erik Torenberg <https://www.podpage.com/econ102/>
|
|
||||||
(fromDb "feeds.megaphone.fm/history102") # <https://www.podpage.com/history-102-with-whatifalthist/>
|
|
||||||
(fromDb "feeds.megaphone.fm/recodedecode" // tech) # The Verge - Decoder
|
(fromDb "feeds.megaphone.fm/recodedecode" // tech) # The Verge - Decoder
|
||||||
(fromDb "feeds.megaphone.fm/thiswontlast" // tech) # <https://www.podpage.com/thiswontlast/>
|
(fromDb "feeds.simplecast.com/82FI35Px" // pol) # Ezra Klein Show
|
||||||
(fromDb "feeds.megaphone.fm/unexplainable")
|
|
||||||
(fromDb "feeds.simplecast.com/wgl4xEgL" // rat) # Econ Talk
|
(fromDb "feeds.simplecast.com/wgl4xEgL" // rat) # Econ Talk
|
||||||
|
(fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura
|
||||||
(fromDb "feeds.transistor.fm/acquired" // tech)
|
(fromDb "feeds.transistor.fm/acquired" // tech)
|
||||||
(fromDb "feeds.transistor.fm/complex-systems-with-patrick-mckenzie-patio11" // tech) # Patrick Mackenzie (from Bits About Money)
|
(fromDb "feeds.transistor.fm/complex-systems-with-patrick-mckenzie-patio11" // tech) # Patrick Mackenzie (from Bits About Money)
|
||||||
(fromDb "feeds.twit.tv/floss.xml" // tech)
|
(fromDb "feeds.twit.tv/floss.xml" // tech)
|
||||||
@@ -99,66 +84,45 @@ let
|
|||||||
(fromDb "hackerpublicradio.org" // tech)
|
(fromDb "hackerpublicradio.org" // tech)
|
||||||
(fromDb "lexfridman.com/podcast" // rat)
|
(fromDb "lexfridman.com/podcast" // rat)
|
||||||
(fromDb "linktr.ee/betteroffline" // pol)
|
(fromDb "linktr.ee/betteroffline" // pol)
|
||||||
(fromDb "linuxdevtime.com" // tech)
|
|
||||||
(fromDb "malicious.life" // tech)
|
|
||||||
(fromDb "mapspodcast.libsyn.com" // uncat) # Multidisciplinary Association for Psychedelic Studies
|
(fromDb "mapspodcast.libsyn.com" // uncat) # Multidisciplinary Association for Psychedelic Studies
|
||||||
(fromDb "motherearthnewsandfriends.libsyn.com" // uncat) # off-grid living
|
|
||||||
(fromDb "microarch.club" // tech)
|
(fromDb "microarch.club" // tech)
|
||||||
(fromDb "nocturnepodcast.org")
|
(fromDb "mintcast.org" // tech)
|
||||||
(fromDb "omegataupodcast.net" // tech) # 3/4 German; 1/4 eps are English
|
(fromDb "omegataupodcast.net" // tech) # 3/4 German; 1/4 eps are English
|
||||||
(fromDb "omny.fm/shows/cool-people-who-did-cool-stuff" // pol) # Maggie Killjoy -- referenced by Cory Doctorow
|
(fromDb "omny.fm/shows/cool-people-who-did-cool-stuff" // pol) # Maggie Killjoy -- referenced by Cory Doctorow
|
||||||
(fromDb "omny.fm/shows/money-stuff-the-podcast") # Matt Levine
|
(fromDb "omny.fm/shows/money-stuff-the-podcast") # Matt Levine
|
||||||
(fromDb "omny.fm/shows/stuff-you-should-know-1")
|
|
||||||
(fromDb "omny.fm/shows/the-dollop-with-dave-anthony-and-gareth-reynolds") # The Dollop history/comedy
|
(fromDb "omny.fm/shows/the-dollop-with-dave-anthony-and-gareth-reynolds") # The Dollop history/comedy
|
||||||
(fromDb "omny.fm/shows/weird-little-guys") # Cool Zone Media
|
(fromDb "omny.fm/shows/weird-little-guys") # Cool Zone Media
|
||||||
(fromDb "originstories.libsyn.com" // uncat)
|
(fromDb "originstories.libsyn.com" // uncat)
|
||||||
(fromDb "podcast.ergaster.org/@flintandsilicon" // tech) # Thib's podcast: public interest tech, gnome, etc: <https://fed.uninsane.org/users/$ALLO9MZ5g5CsQTCBH6>
|
|
||||||
(fromDb "pods.media/api/rss/feed/channel/unchained" // tech) # cryptocurrency happenings; rec via patio11
|
|
||||||
(fromDb "politicalorphanage.libsyn.com" // pol)
|
(fromDb "politicalorphanage.libsyn.com" // pol)
|
||||||
(fromDb "reverseengineering.libsyn.com/rss" // tech) # UnNamed Reverse Engineering Podcast
|
(fromDb "reverseengineering.libsyn.com/rss" // tech) # UnNamed Reverse Engineering Podcast
|
||||||
(fromDb "rss.acast.com/ft-tech-tonic" // tech) # Financial Time's: Tech Tonic
|
(fromDb "rss.acast.com/deconstructed") # The Intercept - Deconstructed
|
||||||
|
(fromDb "rss.acast.com/ft-tech-tonic" // tech)
|
||||||
|
(fromDb "rss.acast.com/intercepted-with-jeremy-scahill") # The Intercept - Intercepted
|
||||||
|
(fromDb "rss.art19.com/60-minutes" // pol)
|
||||||
(fromDb "rss.art19.com/the-portal" // rat) # Eric Weinstein
|
(fromDb "rss.art19.com/the-portal" // rat) # Eric Weinstein
|
||||||
(fromDb "seattlenice.buzzsprout.com" // pol) # Seattle Nice
|
(fromDb "seattlenice.buzzsprout.com" // pol)
|
||||||
(fromDb "speedboatdope.com" // pol) # Chapo Trap House (premium feed)
|
|
||||||
(fromDb "srslywrong.com" // pol)
|
(fromDb "srslywrong.com" // pol)
|
||||||
(fromDb "sharkbytes.transistor.fm" // tech) # Wireshark Podcast o_0
|
(fromDb "sharkbytes.transistor.fm" // tech) # Wireshark Podcast o_0
|
||||||
(fromDb "sharptech.fm/feed/podcast" // tech) # Ben Thompson
|
(fromDb "sharptech.fm/feed/podcast" // tech)
|
||||||
(fromDb "sscpodcast.libsyn.com" // rat) # Astral Codex Ten; Scott Alexander
|
(fromDb "sscpodcast.libsyn.com" // rat) # Astral Codex Ten
|
||||||
(fromDb "talesfromthebridge.buzzsprout.com" // tech) # Sci-Fi? has Peter Watts; author of No Moods, Ads or Cutesy Fucking Icons (rifters.com)
|
(fromDb "talesfromthebridge.buzzsprout.com" // tech) # Sci-Fi? has Peter Watts; author of No Moods, Ads or Cutesy Fucking Icons (rifters.com)
|
||||||
|
(fromDb "theamphour.com" // tech)
|
||||||
(fromDb "techtalesshow.com" // tech) # Corbin Davenport
|
(fromDb "techtalesshow.com" // tech) # Corbin Davenport
|
||||||
(fromDb "theamphour.com" // tech) # The Amp Hour
|
(fromDb "techwontsave.us" // pol) # rec by Cory Doctorow
|
||||||
(fromDb "the-ben-marc-show.simplecast.com" // tech // pol) # Ben Horowitz + Marc Andreessen; love to hate em
|
(fromDb "wakingup.libsyn.com" // pol) # Sam Harris
|
||||||
(fromDb "timclicks.dev/compose-podcast" // tech) # Rust-heavy dev interviews
|
(fromDb "werenotwrong.fireside.fm" // pol)
|
||||||
(fromDb "werenotwrong.fireside.fm" // pol) # We're Not Wrong
|
|
||||||
(fromDb "whycast.podcast.audio/@whycast" // tech) # What Hackers Yearn [for]: <https://why2025.org/>
|
|
||||||
(mkPod "https://sfconservancy.org/casts/the-corresponding-source/feeds/ogg/" // tech)
|
(mkPod "https://sfconservancy.org/casts/the-corresponding-source/feeds/ogg/" // tech)
|
||||||
|
|
||||||
# (fromDb "allinchamathjason.libsyn.com" // pol)
|
|
||||||
# (fromDb "feed.podbean.com/matrixlive/feed.xml" // tech) # Matrix (chat) Live
|
|
||||||
# (fromDb "feeds.libsyn.com/421877" // rat) # Less Wrong Curated
|
# (fromDb "feeds.libsyn.com/421877" // rat) # Less Wrong Curated
|
||||||
# (fromDb "feeds.megaphone.fm/hubermanlab" // uncat) # Daniel Huberman on sleep
|
# (fromDb "feeds.megaphone.fm/hubermanlab" // uncat) # Daniel Huberman on sleep
|
||||||
# (fromDb "feeds.simplecast.com/54nAGcIl" // pol) # The Daily
|
# (fromDb "feeds.simplecast.com/54nAGcIl" // pol) # The Daily
|
||||||
# (fromDb "feeds.simplecast.com/82FI35Px" // pol) # Ezra Klein Show
|
|
||||||
# (fromDb "feeds.simplecast.com/l2i9YnTd" // tech // pol) # Hard Fork (NYtimes tech)
|
# (fromDb "feeds.simplecast.com/l2i9YnTd" // tech // pol) # Hard Fork (NYtimes tech)
|
||||||
# (fromDb "feeds.simplecast.com/whlwDbyc" // tech) # Tech Lounge: <https://chrischinchilla.com/podcast/techlounge/>
|
|
||||||
# (fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura
|
|
||||||
# (fromDb "iheart.com/podcast/1119-away-days-podcast-reporti-275359753" // pol) # Away Days (Cool Zone Media)
|
|
||||||
# (fromDb "lastweekinai.com" // tech) # Last Week in AI
|
|
||||||
# (fromDb "mintcast.org" // tech)
|
|
||||||
# (fromDb "podcast.posttv.com/itunes/post-reports.xml" // pol)
|
# (fromDb "podcast.posttv.com/itunes/post-reports.xml" // pol)
|
||||||
# (fromDb "podcast.sustainoss.org" // tech) # "Sustainable tech", only... it somehow manages to avoid any tech which is actually sustainable, and most of the time doesn't even talk about Open Source Software (!). normie/surface-level/"feel good"
|
|
||||||
# (fromDb "podcast.thelinuxexp.com" // tech) # low-brow linux/foss PR announcements
|
# (fromDb "podcast.thelinuxexp.com" // tech) # low-brow linux/foss PR announcements
|
||||||
# (fromDb "politicspoliticspolitics.com" // pol) # don't judge me. Justin Robert Young.
|
|
||||||
# (fromDb "rss.acast.com/deconstructed") # The Intercept - Deconstructed
|
|
||||||
# (fromDb "rss.acast.com/intercepted-with-jeremy-scahill") # The Intercept - Intercepted
|
|
||||||
# (fromDb "rss.art19.com/60-minutes" // pol)
|
|
||||||
# (fromDb "rss.art19.com/your-welcome" // pol) # Michael Malice - Your Welcome -- also available here: <https://origin.podcastone.com/podcast?categoryID2=2232>
|
# (fromDb "rss.art19.com/your-welcome" // pol) # Michael Malice - Your Welcome -- also available here: <https://origin.podcastone.com/podcast?categoryID2=2232>
|
||||||
# (fromDb "rss.prod.firstlook.media/deconstructed/podcast.rss" // pol) #< possible URL rot
|
# (fromDb "rss.prod.firstlook.media/deconstructed/podcast.rss" // pol) #< possible URL rot
|
||||||
# (fromDb "rss.prod.firstlook.media/intercepted/podcast.rss" // pol) #< possible URL rot
|
# (fromDb "rss.prod.firstlook.media/intercepted/podcast.rss" // pol) #< possible URL rot
|
||||||
# (fromDb "sites.libsyn.com/438684" // humor) # Quorators - digging up *weird* Quota questions
|
|
||||||
# (fromDb "techwontsave.us" // pol) # rec by Cory Doctorow, but way too info-sparse
|
|
||||||
# (fromDb "trashfuturepodcast.podbean.com" // pol) # rec by Cory Doctorow, but way rambly
|
# (fromDb "trashfuturepodcast.podbean.com" // pol) # rec by Cory Doctorow, but way rambly
|
||||||
# (fromDb "wakingup.libsyn.com" // pol) # Sam Harris, but he just repeats himself now
|
|
||||||
# (mkPod "https://anchor.fm/s/21bc734/podcast/rss" // pol // infrequent) # Emerge: making sense of what's next -- <https://www.whatisemerging.com/emergepodcast>
|
# (mkPod "https://anchor.fm/s/21bc734/podcast/rss" // pol // infrequent) # Emerge: making sense of what's next -- <https://www.whatisemerging.com/emergepodcast>
|
||||||
# (mkPod "https://audioboom.com/channels/5097784.rss" // tech) # Lateral with Tom Scott
|
# (mkPod "https://audioboom.com/channels/5097784.rss" // tech) # Lateral with Tom Scott
|
||||||
# (mkPod "https://feeds.megaphone.fm/RUNMED9919162779" // pol // infrequent) # The Witch Trials of J.K. Rowling: <https://www.thefp.com/witchtrials>
|
# (mkPod "https://feeds.megaphone.fm/RUNMED9919162779" // pol // infrequent) # The Witch Trials of J.K. Rowling: <https://www.thefp.com/witchtrials>
|
||||||
@@ -166,11 +130,9 @@ let
|
|||||||
];
|
];
|
||||||
|
|
||||||
texts = [
|
texts = [
|
||||||
(fromDb "ergaster.org/blog" // tech) # Thib's blog: public interest tech, gnome, etc: <https://fed.uninsane.org/users/$ALLO9MZ5g5CsQTCBH6>
|
|
||||||
(fromDb "acoup.blog/feed") # history, states. author: <https://historians.social/@bretdevereaux/following>
|
(fromDb "acoup.blog/feed") # history, states. author: <https://historians.social/@bretdevereaux/following>
|
||||||
(fromDb "amosbbatto.wordpress.com" // tech)
|
(fromDb "amosbbatto.wordpress.com" // tech)
|
||||||
(fromDb "anish.lakhwara.com" // tech)
|
(fromDb "anish.lakhwara.com" // tech)
|
||||||
(fromDb "antipope.org") # Charles Stross
|
|
||||||
(fromDb "apenwarr.ca/log/rss.php" // tech) # CEO of tailscale
|
(fromDb "apenwarr.ca/log/rss.php" // tech) # CEO of tailscale
|
||||||
(fromDb "applieddivinitystudies.com" // rat)
|
(fromDb "applieddivinitystudies.com" // rat)
|
||||||
(fromDb "artemis.sh" // tech)
|
(fromDb "artemis.sh" // tech)
|
||||||
@@ -185,7 +147,6 @@ let
|
|||||||
(fromDb "blog.jmp.chat" // tech)
|
(fromDb "blog.jmp.chat" // tech)
|
||||||
(fromDb "blog.rust-lang.org" // tech)
|
(fromDb "blog.rust-lang.org" // tech)
|
||||||
(fromDb "blog.thalheim.io" // tech) # Mic92
|
(fromDb "blog.thalheim.io" // tech) # Mic92
|
||||||
(fromDb "blog.brixit.nl" // tech) # Martijn Braam
|
|
||||||
(fromDb "bunniestudios.com" // tech) # Bunnie Juang
|
(fromDb "bunniestudios.com" // tech) # Bunnie Juang
|
||||||
(fromDb "capitolhillseattle.com" // pol)
|
(fromDb "capitolhillseattle.com" // pol)
|
||||||
(fromDb "edwardsnowden.substack.com" // pol // text)
|
(fromDb "edwardsnowden.substack.com" // pol // text)
|
||||||
@@ -198,7 +159,6 @@ let
|
|||||||
(fromDb "interconnected.org/home/feed" // rat) # Matt Webb -- engineering-ish, but dreamy
|
(fromDb "interconnected.org/home/feed" // rat) # Matt Webb -- engineering-ish, but dreamy
|
||||||
(fromDb "jeffgeerling.com" // tech)
|
(fromDb "jeffgeerling.com" // tech)
|
||||||
(fromDb "jefftk.com" // tech)
|
(fromDb "jefftk.com" // tech)
|
||||||
(fromDb "justine.lol" // tech)
|
|
||||||
(fromDb "jwz.org/blog" // tech // pol) # DNA lounge guy, loooong-time blogger
|
(fromDb "jwz.org/blog" // tech // pol) # DNA lounge guy, loooong-time blogger
|
||||||
(fromDb "kill-the-newsletter.com/feeds/joh91bv7am2pnznv.xml" // pol) # Matt Levine - Money Stuff
|
(fromDb "kill-the-newsletter.com/feeds/joh91bv7am2pnznv.xml" // pol) # Matt Levine - Money Stuff
|
||||||
(fromDb "kosmosghost.github.io/index.xml" // tech)
|
(fromDb "kosmosghost.github.io/index.xml" // tech)
|
||||||
@@ -208,7 +168,6 @@ let
|
|||||||
(fromDb "mako.cc/copyrighteous" // tech // pol) # rec by Cory Doctorow
|
(fromDb "mako.cc/copyrighteous" // tech // pol) # rec by Cory Doctorow
|
||||||
(fromDb "mg.lol" // tech)
|
(fromDb "mg.lol" // tech)
|
||||||
(fromDb "mindingourway.com" // rat)
|
(fromDb "mindingourway.com" // rat)
|
||||||
(fromDb "momi.ca" // tech) # Anjan, pmOS
|
|
||||||
(fromDb "morningbrew.com/feed" // pol)
|
(fromDb "morningbrew.com/feed" // pol)
|
||||||
(fromDb "nixpkgs.news" // tech)
|
(fromDb "nixpkgs.news" // tech)
|
||||||
(fromDb "overcomingbias.com" // rat) # Robin Hanson
|
(fromDb "overcomingbias.com" // rat) # Robin Hanson
|
||||||
@@ -231,6 +190,7 @@ let
|
|||||||
(fromDb "slimemoldtimemold.com" // rat)
|
(fromDb "slimemoldtimemold.com" // rat)
|
||||||
(fromDb "spectrum.ieee.org" // tech)
|
(fromDb "spectrum.ieee.org" // tech)
|
||||||
(fromDb "stpeter.im/atom.xml" // pol)
|
(fromDb "stpeter.im/atom.xml" // pol)
|
||||||
|
(fromDb "thediff.co" // pol) # Byrne Hobart
|
||||||
(fromDb "thisweek.gnome.org" // tech)
|
(fromDb "thisweek.gnome.org" // tech)
|
||||||
(fromDb "tuxphones.com" // tech)
|
(fromDb "tuxphones.com" // tech)
|
||||||
(fromDb "uninsane.org" // tech)
|
(fromDb "uninsane.org" // tech)
|
||||||
@@ -242,14 +202,12 @@ let
|
|||||||
(fromDb "xorvoid.com" // tech)
|
(fromDb "xorvoid.com" // tech)
|
||||||
(fromDb "www.thebignewsletter.com" // pol)
|
(fromDb "www.thebignewsletter.com" // pol)
|
||||||
(mkSubstack "astralcodexten" // rat // daily) # Scott Alexander
|
(mkSubstack "astralcodexten" // rat // daily) # Scott Alexander
|
||||||
(mkSubstack "chlamchowder" // tech) # details CPU advancements
|
|
||||||
(mkSubstack "eliqian" // rat // weekly)
|
(mkSubstack "eliqian" // rat // weekly)
|
||||||
(mkSubstack "oversharing" // pol // daily)
|
(mkSubstack "oversharing" // pol // daily)
|
||||||
(mkSubstack "samkriss" // humor // infrequent)
|
(mkSubstack "samkriss" // humor // infrequent)
|
||||||
(mkText "http://benjaminrosshoffman.com/feed" // pol // weekly)
|
(mkText "http://benjaminrosshoffman.com/feed" // pol // weekly)
|
||||||
(mkText "http://boginjr.com/feed" // tech // infrequent)
|
(mkText "http://boginjr.com/feed" // tech // infrequent)
|
||||||
(mkText "https://forum.merveilles.town/rss.xml" // pol // infrequent) #quality RSS list here: <https://forum.merveilles.town/thread/57/share-your-rss-feeds%21-6/>
|
(mkText "https://forum.merveilles.town/rss.xml" // pol // infrequent) #quality RSS list here: <https://forum.merveilles.town/thread/57/share-your-rss-feeds%21-6/>
|
||||||
(mkText "https://icm.museum/rss20.xml" // tech // infrequent) # Interim Computer Museum
|
|
||||||
(mkText "https://jvns.ca/atom.xml" // tech // weekly) # Julia Evans
|
(mkText "https://jvns.ca/atom.xml" // tech // weekly) # Julia Evans
|
||||||
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
||||||
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent) # more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
|
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent) # more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
|
||||||
@@ -262,7 +220,6 @@ let
|
|||||||
# (fromDb "econlib.org" // pol)
|
# (fromDb "econlib.org" // pol)
|
||||||
# (fromDb "lesswrong.com" // rat)
|
# (fromDb "lesswrong.com" // rat)
|
||||||
# (fromDb "profectusmag.com" // pol) # some conservative/libertarian think tank
|
# (fromDb "profectusmag.com" // pol) # some conservative/libertarian think tank
|
||||||
# (fromDb "thediff.co" // pol) # Byrne Hobart; 80% is subscriber-only
|
|
||||||
# (fromDb "thesideview.co" // uncat) # spiritual journal; RSS items are stubs
|
# (fromDb "thesideview.co" // uncat) # spiritual journal; RSS items are stubs
|
||||||
# (fromDb "theregister.com" // tech)
|
# (fromDb "theregister.com" // tech)
|
||||||
# (fromDb "vitalik.ca" // tech) # moved to vitalik.eth.limo
|
# (fromDb "vitalik.ca" // tech) # moved to vitalik.eth.limo
|
||||||
@@ -275,32 +232,21 @@ let
|
|||||||
|
|
||||||
videos = [
|
videos = [
|
||||||
(fromDb "youtube.com/@Channel5YouTube" // pol)
|
(fromDb "youtube.com/@Channel5YouTube" // pol)
|
||||||
|
(fromDb "youtube.com/@ColdFusion")
|
||||||
(fromDb "youtube.com/@ContraPoints" // pol)
|
(fromDb "youtube.com/@ContraPoints" // pol)
|
||||||
(fromDb "youtube.com/@Exurb1a")
|
(fromDb "youtube.com/@Exurb1a")
|
||||||
(fromDb "youtube.com/@hbomberguy")
|
(fromDb "youtube.com/@hbomberguy")
|
||||||
(fromDb "youtube.com/@JackStauber")
|
(fromDb "youtube.com/@JackStauber")
|
||||||
(fromDb "youtube.com/@jaketran")
|
|
||||||
(fromDb "youtube.com/@kurzgesagt")
|
|
||||||
(fromDb "youtube.com/@mii_beta" // tech) # Baby Wogue / gnome reviewer
|
|
||||||
(fromDb "youtube.com/@Matrixdotorg" // tech) # Matrix Live
|
|
||||||
(fromDb "youtube.com/@NativLang")
|
(fromDb "youtube.com/@NativLang")
|
||||||
(fromDb "youtube.com/@PolyMatter")
|
(fromDb "youtube.com/@PolyMatter")
|
||||||
(fromDb "youtube.com/@scenesbyben" // pol) # video essays
|
|
||||||
(fromDb "youtube.com/@TechnologyConnections" // tech)
|
(fromDb "youtube.com/@TechnologyConnections" // tech)
|
||||||
(fromDb "youtube.com/@theodd1sout")
|
(fromDb "youtube.com/@TheB1M")
|
||||||
(fromDb "youtube.com/@TomScottGo")
|
(fromDb "youtube.com/@TomScottGo")
|
||||||
(fromDb "youtube.com/@TVW_Washington" // pol) # interviews with WA public officials
|
|
||||||
(fromDb "youtube.com/@veritasium")
|
|
||||||
(fromDb "youtube.com/@Vihart")
|
(fromDb "youtube.com/@Vihart")
|
||||||
(fromDb "youtube.com/@InnuendoStudios" // pol) # breaks down the nastier political strategies, from a "politics is power" angle
|
|
||||||
|
|
||||||
# (fromDb "youtube.com/@CasuallyExplained" // pol)
|
|
||||||
# (fromDb "youtube.com/@ColdFusion")
|
|
||||||
# (fromDb "youtube.com/@rossmanngroup" // pol // tech) # Louis Rossmann
|
|
||||||
# (fromDb "youtube.com/@TheB1M")
|
|
||||||
# (fromDb "youtube.com/@tested" // tech) # Adam Savage (uploads too frequently)
|
|
||||||
# (fromDb "youtube.com/@Vox")
|
# (fromDb "youtube.com/@Vox")
|
||||||
# (fromDb "youtube.com/@Vsauce") # they're all like 1-minute long videos now? what happened @Vsauce?
|
# (fromDb "youtube.com/@Vsauce") # they're all like 1-minute long videos now? what happened @Vsauce?
|
||||||
|
# (fromDb "youtube.com/@rossmanngroup" // pol // tech) # Louis Rossmann
|
||||||
];
|
];
|
||||||
|
|
||||||
images = [
|
images = [
|
||||||
|
375
hosts/common/fs.nix
Normal file
375
hosts/common/fs.nix
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
# docs
|
||||||
|
# - x-systemd options: <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
|
||||||
|
# - fuse options: `man mount.fuse`
|
||||||
|
|
||||||
|
{ config, lib, pkgs, sane-lib, utils, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
fsOpts = rec {
|
||||||
|
common = [
|
||||||
|
"_netdev"
|
||||||
|
"noatime"
|
||||||
|
# user: allow any user with access to the device to mount the fs.
|
||||||
|
# note that this requires a suid `mount` binary; see: <https://zameermanji.com/blog/2022/8/5/using-fuse-without-root-on-linux/>
|
||||||
|
"user"
|
||||||
|
"x-systemd.requires=network-online.target"
|
||||||
|
"x-systemd.after=network-online.target"
|
||||||
|
"x-systemd.mount-timeout=10s" # how long to wait for mount **and** how long to wait for unmount
|
||||||
|
];
|
||||||
|
# x-systemd.automount: mount the fs automatically *on first access*.
|
||||||
|
# creates a `path-to-mount.automount` systemd unit.
|
||||||
|
automount = [ "x-systemd.automount" ];
|
||||||
|
# noauto: don't mount as part of remote-fs.target.
|
||||||
|
# N.B.: `remote-fs.target` is a dependency of multi-user.target, itself of graphical.target.
|
||||||
|
# hence, omitting `noauto` can slow down boots.
|
||||||
|
noauto = [ "noauto" ];
|
||||||
|
# lazyMount: defer mounting until first access from userspace.
|
||||||
|
# see: `man systemd.automount`, `man automount`, `man autofs`
|
||||||
|
lazyMount = noauto ++ automount;
|
||||||
|
|
||||||
|
fuse = [
|
||||||
|
"allow_other" # allow users other than the one who mounts it to access it. needed, if systemd is the one mounting this fs (as root)
|
||||||
|
# allow_root: allow root to access files on this fs (if mounted by non-root, else it can always access them).
|
||||||
|
# N.B.: if both allow_root and allow_other are specified, then only allow_root takes effect.
|
||||||
|
# "allow_root"
|
||||||
|
# default_permissions: enforce local permissions check. CRUCIAL if using `allow_other`.
|
||||||
|
# w/o this, permissions mode of sshfs is like:
|
||||||
|
# - sshfs runs all remote commands as the remote user.
|
||||||
|
# - if a local user has local permissions to the sshfs mount, then their file ops are sent blindly across the tunnel.
|
||||||
|
# - `allow_other` allows *any* local user to access the mount, and hence any local user can now freely become the remote mapped user.
|
||||||
|
# with default_permissions, sshfs doesn't tunnel file ops from users until checking that said user could perform said op on an equivalent local fs.
|
||||||
|
"default_permissions"
|
||||||
|
];
|
||||||
|
fuseColin = fuse ++ [
|
||||||
|
"uid=1000"
|
||||||
|
"gid=100"
|
||||||
|
];
|
||||||
|
|
||||||
|
ssh = common ++ fuseColin ++ [
|
||||||
|
"identityfile=/home/colin/.ssh/id_ed25519"
|
||||||
|
# i *think* idmap=user means that `colin` on `localhost` and `colin` on the remote are actually treated as the same user, even if their uid/gid differs?
|
||||||
|
# i.e., local colin's id is translated to/from remote colin's id on every operation?
|
||||||
|
"idmap=user"
|
||||||
|
];
|
||||||
|
sshColin = ssh ++ fuseColin ++ [
|
||||||
|
# follow_symlinks: remote files which are symlinks are presented to the local system as ordinary files (as the target of the symlink).
|
||||||
|
# if the symlink target does not exist, the presentation is unspecified.
|
||||||
|
# symlinks which point outside the mount ARE followed. so this is more capable than `transform_symlinks`
|
||||||
|
"follow_symlinks"
|
||||||
|
# symlinks on the remote fs which are absolute paths are presented to the local system as relative symlinks pointing to the expected data on the remote fs.
|
||||||
|
# only symlinks which would point inside the mountpoint are translated.
|
||||||
|
"transform_symlinks"
|
||||||
|
];
|
||||||
|
# sshRoot = ssh ++ [
|
||||||
|
# # we don't transform_symlinks because that breaks the validity of remote /nix stores
|
||||||
|
# "sftp_server=/run/wrappers/bin/sudo\\040/run/current-system/sw/libexec/sftp-server"
|
||||||
|
# ];
|
||||||
|
# in the event of hunt NFS mounts, consider:
|
||||||
|
# - <https://unix.stackexchange.com/questions/31979/stop-broken-nfs-mounts-from-locking-a-directory>
|
||||||
|
|
||||||
|
# NFS options: <https://linux.die.net/man/5/nfs>
|
||||||
|
# actimeo=n = how long (in seconds) to cache file/dir attributes (default: 3-60s)
|
||||||
|
# bg = retry failed mounts in the background
|
||||||
|
# retry=n = for how many minutes `mount` will retry NFS mount operation
|
||||||
|
# intr = allow Ctrl+C to abort I/O (it will error with `EINTR`)
|
||||||
|
# soft = on "major timeout", report I/O error to userspace
|
||||||
|
# softreval = on "major timeout", service the request using known-stale cache results instead of erroring -- if such cache data exists
|
||||||
|
# retrans=n = how many times to retry a NFS request before giving userspace a "server not responding" error (default: 3)
|
||||||
|
# timeo=n = number of *deciseconds* to wait for a response before retrying it (default: 600)
|
||||||
|
# note: client uses a linear backup, so the second request will have double this timeout, then triple, etc.
|
||||||
|
# proto=udp = encapsulate protocol ops inside UDP packets instead of a TCP session.
|
||||||
|
# requires `nfsvers=3` and a kernel compiled with `NFS_DISABLE_UDP_SUPPORT=n`.
|
||||||
|
# UDP might be preferable to TCP because the latter is liable to hang for ~100s (kernel TCP timeout) after a link drop.
|
||||||
|
# however, even UDP has issues with `umount` hanging.
|
||||||
|
#
|
||||||
|
# N.B.: don't change these without first testing the behavior of sandboxed apps on a flaky network.
|
||||||
|
nfs = common ++ [
|
||||||
|
# "actimeo=5"
|
||||||
|
# "bg"
|
||||||
|
"retrans=1"
|
||||||
|
"retry=0"
|
||||||
|
# "intr"
|
||||||
|
"soft"
|
||||||
|
"softreval"
|
||||||
|
"timeo=30"
|
||||||
|
"nofail" # don't fail remote-fs.target when this mount fails (not an option for sshfs else would be common)
|
||||||
|
# "proto=udp" # default kernel config doesn't support NFS over UDP: <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1964093> (see comment 11).
|
||||||
|
# "nfsvers=3" # NFSv4+ doesn't support UDP at *all*. it's ok to omit nfsvers -- server + client will negotiate v3 based on udp requirement. but omitting causes confusing mount errors when the server is *offline*, because the client defaults to v4 and thinks the udp option is a config error.
|
||||||
|
# "x-systemd.idle-timeout=10" # auto-unmount after this much inactivity
|
||||||
|
];
|
||||||
|
|
||||||
|
# manually perform a ftp mount via e.g.
|
||||||
|
# curlftpfs -o ftpfs_debug=2,user=anonymous:anonymous,connect_timeout=10 -f -s ftp://servo-hn /mnt/my-ftp
|
||||||
|
ftp = common ++ fuseColin ++ [
|
||||||
|
# "ftpfs_debug=2"
|
||||||
|
"user=colin:ipauth"
|
||||||
|
# connect_timeout=10: casting shows to T.V. fails partway through about half the time
|
||||||
|
"connect_timeout=20"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
ifSshAuthorized = lib.mkIf config.sane.hosts.by-name."${config.networking.hostName}".ssh.authorized;
|
||||||
|
|
||||||
|
remoteHome = host: {
|
||||||
|
sane.programs.sshfs-fuse.enableFor.system = true;
|
||||||
|
system.fsPackages = [
|
||||||
|
config.sane.programs.sshfs-fuse.package
|
||||||
|
];
|
||||||
|
fileSystems."/mnt/${host}/home" = {
|
||||||
|
device = "sshfs#colin@${host}:/home/colin";
|
||||||
|
fsType = "fuse3";
|
||||||
|
options = fsOpts.sshColin ++ fsOpts.lazyMount ++ [
|
||||||
|
# drop_privileges: after `mount.fuse3` opens /dev/fuse, it will drop all capabilities before invoking sshfs
|
||||||
|
"drop_privileges"
|
||||||
|
"auto_unmount" #< ensures that when the fs exits, it releases its mountpoint. then systemd can recognize it as failed.
|
||||||
|
];
|
||||||
|
noCheck = true;
|
||||||
|
};
|
||||||
|
sane.fs."/mnt/${host}/home" = {
|
||||||
|
dir.acl.user = "colin";
|
||||||
|
dir.acl.group = "users";
|
||||||
|
dir.acl.mode = "0700";
|
||||||
|
wantedBy = [ "default.target" ];
|
||||||
|
mount.depends = [ "network-online.target" ];
|
||||||
|
mount.mountConfig.ExecSearchPath = [ "/run/current-system/sw/bin" ];
|
||||||
|
mount.mountConfig.User = "colin";
|
||||||
|
mount.mountConfig.AmbientCapabilities = "CAP_SETPCAP CAP_SYS_ADMIN";
|
||||||
|
# hardening (systemd-analyze security mnt-desko-home.mount):
|
||||||
|
# TODO: i can't use ProtectSystem=full here, because i can't create a new mount space; but...
|
||||||
|
# with drop_privileges, i *could* sandbox the actual `sshfs` program using e.g. bwrap
|
||||||
|
mount.mountConfig.CapabilityBoundingSet = "CAP_SETPCAP CAP_SYS_ADMIN";
|
||||||
|
mount.mountConfig.LockPersonality = true;
|
||||||
|
mount.mountConfig.MemoryDenyWriteExecute = true;
|
||||||
|
mount.mountConfig.NoNewPrivileges = true;
|
||||||
|
mount.mountConfig.ProtectClock = true;
|
||||||
|
mount.mountConfig.ProtectHostname = true;
|
||||||
|
mount.mountConfig.RemoveIPC = true;
|
||||||
|
mount.mountConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
|
||||||
|
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
|
||||||
|
# see `systemd-analyze filesystems` for a full list
|
||||||
|
mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse";
|
||||||
|
mount.mountConfig.RestrictRealtime = true;
|
||||||
|
mount.mountConfig.RestrictSUIDSGID = true;
|
||||||
|
mount.mountConfig.SystemCallArchitectures = "native";
|
||||||
|
mount.mountConfig.SystemCallFilter = [
|
||||||
|
"@system-service"
|
||||||
|
"@mount"
|
||||||
|
"~@chown"
|
||||||
|
"~@cpu-emulation"
|
||||||
|
"~@keyring"
|
||||||
|
# could remove almost all io calls, however one has to keep `open`, and `write`, to communicate with the fuse device.
|
||||||
|
# so that's pretty useless as a way to prevent write access
|
||||||
|
];
|
||||||
|
mount.mountConfig.IPAddressDeny = "any";
|
||||||
|
mount.mountConfig.IPAddressAllow = "10.0.0.0/8";
|
||||||
|
mount.mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
|
||||||
|
mount.mountConfig.DeviceAllow = "/dev/fuse";
|
||||||
|
# mount.mountConfig.RestrictNamespaces = true; #< my sshfs sandboxing uses bwrap
|
||||||
|
};
|
||||||
|
};
|
||||||
|
remoteServo = subdir: let
|
||||||
|
localPath = "/mnt/servo/${subdir}";
|
||||||
|
systemdName = utils.escapeSystemdPath localPath;
|
||||||
|
in {
|
||||||
|
sane.programs.curlftpfs.enableFor.system = true;
|
||||||
|
system.fsPackages = [
|
||||||
|
config.sane.programs.curlftpfs.package
|
||||||
|
];
|
||||||
|
fileSystems."${localPath}" = {
|
||||||
|
device = "curlftpfs#ftp://servo-hn:/${subdir}";
|
||||||
|
noCheck = true;
|
||||||
|
fsType = "fuse3";
|
||||||
|
options = fsOpts.ftp ++ fsOpts.noauto ++ [
|
||||||
|
# drop_privileges: after `mount.fuse3` opens /dev/fuse, it will drop all capabilities before invoking sshfs
|
||||||
|
"drop_privileges"
|
||||||
|
"auto_unmount" #< ensures that when the fs exits, it releases its mountpoint. then systemd can recognize it as failed.
|
||||||
|
];
|
||||||
|
# fsType = "nfs";
|
||||||
|
# options = fsOpts.nfs ++ fsOpts.lazyMount;
|
||||||
|
};
|
||||||
|
sane.fs."${localPath}" = {
|
||||||
|
dir.acl.user = "colin";
|
||||||
|
dir.acl.group = "users";
|
||||||
|
dir.acl.mode = "0750";
|
||||||
|
wantedBy = [ "default.target" ];
|
||||||
|
mount.depends = [ "network-online.target" "${systemdName}-reachable.service" ];
|
||||||
|
#VVV patch so that when the mount fails, we start a timer to remount it.
|
||||||
|
# and for a disconnection after a good mount (onSuccess), restart the timer to be more aggressive
|
||||||
|
mount.unitConfig.OnFailure = [ "${systemdName}.timer" ];
|
||||||
|
mount.unitConfig.OnSuccess = [ "${systemdName}-restart-timer.target" ];
|
||||||
|
|
||||||
|
mount.mountConfig.TimeoutSec = "10s";
|
||||||
|
mount.mountConfig.ExecSearchPath = [ "/run/current-system/sw/bin" ];
|
||||||
|
mount.mountConfig.User = "colin";
|
||||||
|
mount.mountConfig.AmbientCapabilities = "CAP_SETPCAP CAP_SYS_ADMIN";
|
||||||
|
# hardening (systemd-analyze security mnt-servo-playground.mount)
|
||||||
|
mount.mountConfig.CapabilityBoundingSet = "CAP_SETPCAP CAP_SYS_ADMIN";
|
||||||
|
mount.mountConfig.LockPersonality = true;
|
||||||
|
mount.mountConfig.MemoryDenyWriteExecute = true;
|
||||||
|
mount.mountConfig.NoNewPrivileges = true;
|
||||||
|
mount.mountConfig.ProtectClock = true;
|
||||||
|
mount.mountConfig.ProtectHostname = true;
|
||||||
|
mount.mountConfig.RemoveIPC = true;
|
||||||
|
mount.mountConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
|
||||||
|
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
|
||||||
|
# see `systemd-analyze filesystems` for a full list
|
||||||
|
mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse";
|
||||||
|
mount.mountConfig.RestrictRealtime = true;
|
||||||
|
mount.mountConfig.RestrictSUIDSGID = true;
|
||||||
|
mount.mountConfig.SystemCallArchitectures = "native";
|
||||||
|
mount.mountConfig.SystemCallFilter = [
|
||||||
|
"@system-service"
|
||||||
|
"@mount"
|
||||||
|
"~@chown"
|
||||||
|
"~@cpu-emulation"
|
||||||
|
"~@keyring"
|
||||||
|
# could remove almost all io calls, however one has to keep `open`, and `write`, to communicate with the fuse device.
|
||||||
|
# so that's pretty useless as a way to prevent write access
|
||||||
|
];
|
||||||
|
mount.mountConfig.IPAddressDeny = "any";
|
||||||
|
mount.mountConfig.IPAddressAllow = "10.0.10.5";
|
||||||
|
mount.mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
|
||||||
|
mount.mountConfig.DeviceAllow = "/dev/fuse";
|
||||||
|
# mount.mountConfig.RestrictNamespaces = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services."${systemdName}-reachable" = {
|
||||||
|
serviceConfig.ExecSearchPath = [ "/run/current-system/sw/bin" ];
|
||||||
|
serviceConfig.ExecStart = lib.escapeShellArgs [
|
||||||
|
"curlftpfs"
|
||||||
|
"ftp://servo-hn:/${subdir}"
|
||||||
|
"/dev/null"
|
||||||
|
"-o"
|
||||||
|
(lib.concatStringsSep "," ([ "exit_after_connect" ] ++ config.fileSystems."${localPath}".options))
|
||||||
|
];
|
||||||
|
serviceConfig.RemainAfterExit = true;
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
unitConfig.BindsTo = [ "${systemdName}.mount" ];
|
||||||
|
# hardening (systemd-analyze security mnt-servo-playground-reachable.service)
|
||||||
|
serviceConfig.AmbientCapabilities = "";
|
||||||
|
serviceConfig.CapabilityBoundingSet = "";
|
||||||
|
serviceConfig.DynamicUser = true;
|
||||||
|
serviceConfig.LockPersonality = true;
|
||||||
|
serviceConfig.MemoryDenyWriteExecute = true;
|
||||||
|
serviceConfig.NoNewPrivileges = true;
|
||||||
|
serviceConfig.PrivateDevices = true;
|
||||||
|
serviceConfig.PrivateMounts = true;
|
||||||
|
serviceConfig.PrivateTmp = true;
|
||||||
|
serviceConfig.PrivateUsers = true;
|
||||||
|
serviceConfig.ProcSubset = "all";
|
||||||
|
serviceConfig.ProtectClock = true;
|
||||||
|
serviceConfig.ProtectControlGroups = true;
|
||||||
|
serviceConfig.ProtectHome = true;
|
||||||
|
serviceConfig.ProtectKernelModules = true;
|
||||||
|
serviceConfig.ProtectProc = "invisible";
|
||||||
|
serviceConfig.ProtectSystem = "strict";
|
||||||
|
serviceConfig.RemoveIPC = true;
|
||||||
|
serviceConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
|
||||||
|
# serviceConfig.RestrictFileSystems = "@common-block @basic-api"; #< NOPE
|
||||||
|
serviceConfig.RestrictRealtime = true;
|
||||||
|
serviceConfig.RestrictSUIDSGID = true;
|
||||||
|
serviceConfig.SystemCallArchitectures = "native";
|
||||||
|
serviceConfig.SystemCallFilter = [
|
||||||
|
"@system-service"
|
||||||
|
"@mount"
|
||||||
|
"~@chown"
|
||||||
|
"~@cpu-emulation"
|
||||||
|
"~@keyring"
|
||||||
|
# "~@privileged" #< NOPE
|
||||||
|
"~@resources"
|
||||||
|
# could remove some more probably
|
||||||
|
];
|
||||||
|
serviceConfig.IPAddressDeny = "any";
|
||||||
|
serviceConfig.IPAddressAllow = "10.0.10.5";
|
||||||
|
serviceConfig.DevicePolicy = "closed";
|
||||||
|
# exceptions
|
||||||
|
serviceConfig.ProtectHostname = false;
|
||||||
|
serviceConfig.ProtectKernelLogs = false;
|
||||||
|
serviceConfig.ProtectKernelTunables = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.targets."${systemdName}-restart-timer" = {
|
||||||
|
# hack unit which, when started, stops the timer (if running), and then starts it again.
|
||||||
|
after = [ "${systemdName}.timer" ];
|
||||||
|
conflicts = [ "${systemdName}.timer" ];
|
||||||
|
upholds = [ "${systemdName}.timer" ];
|
||||||
|
unitConfig.StopWhenUnneeded = true;
|
||||||
|
};
|
||||||
|
systemd.timers."${systemdName}" = {
|
||||||
|
timerConfig.Unit = "${systemdName}.mount";
|
||||||
|
timerConfig.AccuracySec = "2s";
|
||||||
|
timerConfig.OnActiveSec = [
|
||||||
|
# try to remount at these timestamps, backing off gradually
|
||||||
|
# there seems to be an implicit mount attempt at t=0.
|
||||||
|
"10s"
|
||||||
|
"30s"
|
||||||
|
"60s"
|
||||||
|
"120s"
|
||||||
|
];
|
||||||
|
# cap the backoff to a fixed interval.
|
||||||
|
timerConfig.OnUnitActiveSec = [ "120s" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
lib.mkMerge [
|
||||||
|
{
|
||||||
|
# some services which use private directories error if the parent (/var/lib/private) isn't 700.
|
||||||
|
sane.fs."/var/lib/private".dir.acl.mode = "0700";
|
||||||
|
|
||||||
|
# in-memory compressed RAM
|
||||||
|
# defaults to compressing at most 50% size of RAM
|
||||||
|
# claimed compression ratio is about 2:1
|
||||||
|
# - but on moby w/ zstd default i see 4-7:1 (ratio lowers as it fills)
|
||||||
|
# note that idle overhead is about 0.05% of capacity (e.g. 2B per 4kB page)
|
||||||
|
# docs: <https://www.kernel.org/doc/Documentation/blockdev/zram.txt>
|
||||||
|
#
|
||||||
|
# to query effectiveness:
|
||||||
|
# `cat /sys/block/zram0/mm_stat`. whitespace separated fields:
|
||||||
|
# - *orig_data_size* (bytes)
|
||||||
|
# - *compr_data_size* (bytes)
|
||||||
|
# - mem_used_total (bytes)
|
||||||
|
# - mem_limit (bytes)
|
||||||
|
# - mem_used_max (bytes)
|
||||||
|
# - *same_pages* (pages which are e.g. all zeros (consumes no additional mem))
|
||||||
|
# - *pages_compacted* (pages which have been freed thanks to compression)
|
||||||
|
# - huge_pages (incompressible)
|
||||||
|
#
|
||||||
|
# see also:
|
||||||
|
# - `man zramctl`
|
||||||
|
zramSwap.enable = true;
|
||||||
|
# how much ram can be swapped into the zram device.
|
||||||
|
# this shouldn't be higher than the observed compression ratio.
|
||||||
|
# the default is 50% (why?)
|
||||||
|
# 100% should be "guaranteed" safe so long as the data is even *slightly* compressible.
|
||||||
|
# but it decreases working memory under the heaviest of loads by however much space the compressed memory occupies (e.g. 50% if 2:1; 25% if 4:1)
|
||||||
|
zramSwap.memoryPercent = 100;
|
||||||
|
|
||||||
|
# environment.pathsToLink = [
|
||||||
|
# # needed to achieve superuser access for user-mounted filesystems (see sshRoot above)
|
||||||
|
# # we can only link whole directories here, even though we're only interested in pkgs.openssh
|
||||||
|
# "/libexec"
|
||||||
|
# ];
|
||||||
|
|
||||||
|
programs.fuse.userAllowOther = true; #< necessary for `allow_other` or `allow_root` options.
|
||||||
|
}
|
||||||
|
|
||||||
|
(ifSshAuthorized (remoteHome "crappy"))
|
||||||
|
(ifSshAuthorized (remoteHome "desko"))
|
||||||
|
(ifSshAuthorized (remoteHome "lappy"))
|
||||||
|
(ifSshAuthorized (remoteHome "moby"))
|
||||||
|
(ifSshAuthorized (remoteHome "servo"))
|
||||||
|
# this granularity of servo media mounts is necessary to support sandboxing:
|
||||||
|
# for flaky mounts, we can only bind the mountpoint itself into the sandbox,
|
||||||
|
# so it's either this or unconditionally bind all of media/.
|
||||||
|
(remoteServo "media/archive")
|
||||||
|
(remoteServo "media/Books")
|
||||||
|
(remoteServo "media/collections")
|
||||||
|
# (remoteServo "media/datasets")
|
||||||
|
(remoteServo "media/games")
|
||||||
|
(remoteServo "media/Music")
|
||||||
|
(remoteServo "media/Pictures/macros")
|
||||||
|
(remoteServo "media/torrents")
|
||||||
|
(remoteServo "media/Videos")
|
||||||
|
(remoteServo "playground")
|
||||||
|
]
|
||||||
|
|
@@ -1,51 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./remote-home.nix
|
|
||||||
./remote-servo.nix
|
|
||||||
];
|
|
||||||
# some services which use private directories error if the parent (/var/lib/private) isn't 700.
|
|
||||||
sane.fs."/var/lib/private".dir.acl.mode = "0700";
|
|
||||||
|
|
||||||
|
|
||||||
# allocate a proper /tmp fs, else its capacity will be limited as per impermanence defaults (i.e. 1 GB).
|
|
||||||
fileSystems."/tmp" = {
|
|
||||||
device = "none";
|
|
||||||
fsType = "tmpfs";
|
|
||||||
options = [
|
|
||||||
"mode=777"
|
|
||||||
"defaults"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# in-memory compressed RAM
|
|
||||||
# defaults to compressing at most 50% size of RAM
|
|
||||||
# claimed compression ratio is about 2:1
|
|
||||||
# - but on moby w/ zstd default i see 4-7:1 (ratio lowers as it fills)
|
|
||||||
# note that idle overhead is about 0.05% of capacity (e.g. 2B per 4kB page)
|
|
||||||
# docs: <https://www.kernel.org/doc/Documentation/blockdev/zram.txt>
|
|
||||||
#
|
|
||||||
# to query effectiveness:
|
|
||||||
# `cat /sys/block/zram0/mm_stat`. whitespace separated fields:
|
|
||||||
# - *orig_data_size* (bytes)
|
|
||||||
# - *compr_data_size* (bytes)
|
|
||||||
# - mem_used_total (bytes)
|
|
||||||
# - mem_limit (bytes)
|
|
||||||
# - mem_used_max (bytes)
|
|
||||||
# - *same_pages* (pages which are e.g. all zeros (consumes no additional mem))
|
|
||||||
# - *pages_compacted* (pages which have been freed thanks to compression)
|
|
||||||
# - huge_pages (incompressible)
|
|
||||||
#
|
|
||||||
# see also:
|
|
||||||
# - `man zramctl`
|
|
||||||
zramSwap.enable = true;
|
|
||||||
# how much ram can be swapped into the zram device.
|
|
||||||
# this shouldn't be higher than the observed compression ratio.
|
|
||||||
# the default is 50% (why?)
|
|
||||||
# 100% should be "guaranteed" safe so long as the data is even *slightly* compressible.
|
|
||||||
# but it decreases working memory under the heaviest of loads by however much space the compressed memory occupies (e.g. 50% if 2:1; 25% if 4:1)
|
|
||||||
zramSwap.memoryPercent = 100;
|
|
||||||
|
|
||||||
programs.fuse.userAllowOther = true; #< necessary for `allow_other` or `allow_root` options.
|
|
||||||
}
|
|
||||||
|
|
@@ -1,76 +0,0 @@
|
|||||||
# docs
|
|
||||||
# - x-systemd options: <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
|
|
||||||
# - fuse options: `man mount.fuse`
|
|
||||||
rec {
|
|
||||||
common = [
|
|
||||||
"_netdev"
|
|
||||||
"noatime"
|
|
||||||
# user: allow any user with access to the device to mount the fs.
|
|
||||||
# note that this requires a suid `mount` binary; see: <https://zameermanji.com/blog/2022/8/5/using-fuse-without-root-on-linux/>
|
|
||||||
"user"
|
|
||||||
"x-systemd.requires=network-online.target"
|
|
||||||
"x-systemd.after=network-online.target"
|
|
||||||
"x-systemd.mount-timeout=10s" # how long to wait for mount **and** how long to wait for unmount
|
|
||||||
# disable defaults: don't fail local-fs.target if this mount fails
|
|
||||||
"nofail"
|
|
||||||
];
|
|
||||||
# x-systemd.automount: mount the fs automatically *on first access*.
|
|
||||||
# creates a `path-to-mount.automount` systemd unit.
|
|
||||||
automount = [ "x-systemd.automount" ];
|
|
||||||
# noauto: don't mount as part of remote-fs.target.
|
|
||||||
# N.B.: `remote-fs.target` is a dependency of multi-user.target, itself of graphical.target.
|
|
||||||
# hence, omitting `noauto` can slow down boots.
|
|
||||||
noauto = [ "noauto" ];
|
|
||||||
# lazyMount: defer mounting until first access from userspace.
|
|
||||||
# see: `man systemd.automount`, `man automount`, `man autofs`
|
|
||||||
lazyMount = noauto ++ automount;
|
|
||||||
|
|
||||||
fuse = [
|
|
||||||
"allow_other" # allow users other than the one who mounts it to access it. needed, if systemd is the one mounting this fs (as root)
|
|
||||||
# allow_root: allow root to access files on this fs (if mounted by non-root, else it can always access them).
|
|
||||||
# N.B.: if both allow_root and allow_other are specified, then only allow_root takes effect.
|
|
||||||
# "allow_root"
|
|
||||||
# default_permissions: enforce local permissions check. CRUCIAL if using `allow_other`.
|
|
||||||
# w/o this, permissions mode of sshfs is like:
|
|
||||||
# - sshfs runs all remote commands as the remote user.
|
|
||||||
# - if a local user has local permissions to the sshfs mount, then their file ops are sent blindly across the tunnel.
|
|
||||||
# - `allow_other` allows *any* local user to access the mount, and hence any local user can now freely become the remote mapped user.
|
|
||||||
# with default_permissions, sshfs doesn't tunnel file ops from users until checking that said user could perform said op on an equivalent local fs.
|
|
||||||
"default_permissions"
|
|
||||||
"drop_privileges"
|
|
||||||
"auto_unmount" #< ensures that when the fs exits, it releases its mountpoint. then systemd can recognize it as failed.
|
|
||||||
];
|
|
||||||
fuseColin = fuse ++ [
|
|
||||||
"uid=1000"
|
|
||||||
"gid=100"
|
|
||||||
];
|
|
||||||
|
|
||||||
ssh = common ++ fuseColin ++ [
|
|
||||||
"identityfile=/home/colin/.ssh/id_ed25519"
|
|
||||||
# i *think* idmap=user means that `colin` on `localhost` and `colin` on the remote are actually treated as the same user, even if their uid/gid differs?
|
|
||||||
# i.e., local colin's id is translated to/from remote colin's id on every operation?
|
|
||||||
"idmap=user"
|
|
||||||
];
|
|
||||||
sshColin = ssh ++ fuseColin ++ [
|
|
||||||
# follow_symlinks: remote files which are symlinks are presented to the local system as ordinary files (as the target of the symlink).
|
|
||||||
# if the symlink target does not exist, the presentation is unspecified.
|
|
||||||
# symlinks which point outside the mount ARE followed. so this is more capable than `transform_symlinks`
|
|
||||||
"follow_symlinks"
|
|
||||||
# symlinks on the remote fs which are absolute paths are presented to the local system as relative symlinks pointing to the expected data on the remote fs.
|
|
||||||
# only symlinks which would point inside the mountpoint are translated.
|
|
||||||
"transform_symlinks"
|
|
||||||
];
|
|
||||||
# sshRoot = ssh ++ [
|
|
||||||
# # we don't transform_symlinks because that breaks the validity of remote /nix stores
|
|
||||||
# "sftp_server=/run/wrappers/bin/sudo\\040/run/current-system/sw/libexec/sftp-server"
|
|
||||||
# ];
|
|
||||||
|
|
||||||
# manually perform a ftp mount via e.g.
|
|
||||||
# curlftpfs -o ftpfs_debug=2,user=anonymous:anonymous,connect_timeout=10 -f -s ftp://servo-hn /mnt/my-ftp
|
|
||||||
ftp = common ++ fuseColin ++ [
|
|
||||||
# "ftpfs_debug=2"
|
|
||||||
"user=colin:ipauth"
|
|
||||||
# connect_timeout=10: casting shows to T.V. fails partway through about half the time
|
|
||||||
"connect_timeout=20"
|
|
||||||
];
|
|
||||||
}
|
|
@@ -1,85 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
let
|
|
||||||
fsOpts = import ./fs-opts.nix;
|
|
||||||
ifSshAuthorized = lib.mkIf (((config.sane.hosts.by-name."${config.networking.hostName}" or {}).ssh or {}).authorized or false);
|
|
||||||
|
|
||||||
remoteHome = name: { host ? name }: let
|
|
||||||
mountpoint = "/mnt/${name}/home";
|
|
||||||
device = "sshfs#colin@${host}:/home/colin";
|
|
||||||
fsType = "fuse3";
|
|
||||||
options = fsOpts.sshColin ++ fsOpts.lazyMount;
|
|
||||||
in {
|
|
||||||
sane.programs.sshfs-fuse.enableFor.system = true;
|
|
||||||
system.fsPackages = [
|
|
||||||
config.sane.programs.sshfs-fuse.package
|
|
||||||
];
|
|
||||||
fileSystems."${mountpoint}" = {
|
|
||||||
inherit device fsType options;
|
|
||||||
noCheck = true;
|
|
||||||
};
|
|
||||||
# tell systemd about the mount so that i can sandbox it
|
|
||||||
systemd.mounts = [{
|
|
||||||
where = mountpoint;
|
|
||||||
what = device;
|
|
||||||
type = fsType;
|
|
||||||
options = lib.concatStringsSep "," options;
|
|
||||||
wantedBy = [ "default.target" ];
|
|
||||||
after = [
|
|
||||||
"emergency.service"
|
|
||||||
"network-online.target"
|
|
||||||
];
|
|
||||||
requires = [ "network-online.target" ];
|
|
||||||
|
|
||||||
unitConfig.Conflicts = [
|
|
||||||
# emergency.service drops the user into a root shell;
|
|
||||||
# only accessible via physical TTY, but unmount sensitive data before that as a precaution.
|
|
||||||
"emergency.service"
|
|
||||||
];
|
|
||||||
|
|
||||||
# mountConfig.LazyUnmount = true; #< else it _ocassionally_ fails "target is busy"
|
|
||||||
|
|
||||||
mountConfig.ExecSearchPath = [ "/run/current-system/sw/bin" ];
|
|
||||||
mountConfig.User = "colin";
|
|
||||||
mountConfig.AmbientCapabilities = "CAP_SETPCAP CAP_SYS_ADMIN";
|
|
||||||
# hardening (systemd-analyze security mnt-desko-home.mount):
|
|
||||||
# TODO: i can't use ProtectSystem=full here, because i can't create a new mount space; but...
|
|
||||||
# with drop_privileges, i *could* sandbox the actual `sshfs` program using e.g. bwrap
|
|
||||||
mountConfig.CapabilityBoundingSet = "CAP_SETPCAP CAP_SYS_ADMIN";
|
|
||||||
mountConfig.LockPersonality = true;
|
|
||||||
mountConfig.MemoryDenyWriteExecute = true;
|
|
||||||
mountConfig.NoNewPrivileges = true;
|
|
||||||
mountConfig.ProtectClock = true;
|
|
||||||
mountConfig.ProtectHostname = true;
|
|
||||||
mountConfig.RemoveIPC = true;
|
|
||||||
mountConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
|
|
||||||
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
|
|
||||||
# see `systemd-analyze filesystems` for a full list
|
|
||||||
mountConfig.RestrictFileSystems = "@common-block @basic-api fuse";
|
|
||||||
mountConfig.RestrictRealtime = true;
|
|
||||||
mountConfig.RestrictSUIDSGID = true;
|
|
||||||
mountConfig.SystemCallArchitectures = "native";
|
|
||||||
mountConfig.SystemCallFilter = [
|
|
||||||
"@system-service"
|
|
||||||
"@mount"
|
|
||||||
"~@chown"
|
|
||||||
"~@cpu-emulation"
|
|
||||||
"~@keyring"
|
|
||||||
# could remove almost all io calls, however one has to keep `open`, and `write`, to communicate with the fuse device.
|
|
||||||
# so that's pretty useless as a way to prevent write access
|
|
||||||
];
|
|
||||||
mountConfig.IPAddressDeny = "any";
|
|
||||||
mountConfig.IPAddressAllow = "10.0.0.0/8";
|
|
||||||
mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
|
|
||||||
mountConfig.DeviceAllow = "/dev/fuse";
|
|
||||||
# mount.mountConfig.RestrictNamespaces = true; #< my sshfs sandboxing uses bwrap
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
lib.mkMerge [
|
|
||||||
(ifSshAuthorized (remoteHome "crappy" {}))
|
|
||||||
(ifSshAuthorized (remoteHome "desko" {}))
|
|
||||||
(ifSshAuthorized (remoteHome "flowy" {}))
|
|
||||||
# (ifSshAuthorized (remoteHome "lappy" {}))
|
|
||||||
(ifSshAuthorized (remoteHome "moby" { host = "moby-hn"; }))
|
|
||||||
(ifSshAuthorized (remoteHome "servo" {}))
|
|
||||||
]
|
|
@@ -1,132 +0,0 @@
|
|||||||
{ config, lib, utils, ... }:
|
|
||||||
let
|
|
||||||
fsOpts = import ./fs-opts.nix;
|
|
||||||
commonOptions = fsOpts.ftp ++ fsOpts.noauto;
|
|
||||||
mountpoint = "/mnt/.servo_ftp";
|
|
||||||
systemdName = utils.escapeSystemdPath mountpoint;
|
|
||||||
device = "curlftpfs#ftp://servo-hn:/";
|
|
||||||
fsType = "fuse3";
|
|
||||||
options = commonOptions ++ [
|
|
||||||
# systemd (or maybe fuse?) swallows stderr of mount units with no obvious fix.
|
|
||||||
# instead, use this flag to log the mount output to disk
|
|
||||||
"stderr_path=/var/log/curlftpfs/servo-hn.stderr"
|
|
||||||
];
|
|
||||||
|
|
||||||
remoteServo = subdir: {
|
|
||||||
# sane.fs."/mnt/servo/${subdir}".mount.bind = "/mnt/.servo_ftp/${subdir}";
|
|
||||||
systemd.mounts = [{
|
|
||||||
where = "/mnt/servo/${subdir}";
|
|
||||||
what = "/mnt/.servo_ftp/${subdir}";
|
|
||||||
options = "bind,nofail";
|
|
||||||
type = "auto";
|
|
||||||
|
|
||||||
after = [ "${systemdName}.mount" ];
|
|
||||||
upheldBy = [ "${systemdName}.mount" ]; #< start this mount whenever the underlying becomes available
|
|
||||||
bindsTo = [ "${systemdName}.mount" ]; #< stop this mount whenever the underlying disappears
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
lib.mkMerge [
|
|
||||||
{
|
|
||||||
sane.programs.curlftpfs.enableFor.system = true;
|
|
||||||
system.fsPackages = [
|
|
||||||
config.sane.programs.curlftpfs.package
|
|
||||||
];
|
|
||||||
|
|
||||||
sane.fs."/var/log/curlftpfs".dir.acl.mode = "0777";
|
|
||||||
|
|
||||||
fileSystems."/mnt/.servo_ftp" = {
|
|
||||||
inherit device fsType options;
|
|
||||||
noCheck = true;
|
|
||||||
};
|
|
||||||
systemd.mounts = [{
|
|
||||||
where = mountpoint;
|
|
||||||
what = device;
|
|
||||||
type = fsType;
|
|
||||||
options = lib.concatStringsSep "," options;
|
|
||||||
wantedBy = [ "default.target" ];
|
|
||||||
after = [ "network-online.target" ];
|
|
||||||
requires = [ "network-online.target" ];
|
|
||||||
|
|
||||||
#VVV patch so that when the mount fails, we start a timer to remount it.
|
|
||||||
# and for a disconnection after a good mount (onSuccess), restart the timer to be more aggressive
|
|
||||||
unitConfig.OnFailure = [ "${systemdName}.timer" ];
|
|
||||||
unitConfig.OnSuccess = [ "${systemdName}-restart-timer.target" ];
|
|
||||||
|
|
||||||
mountConfig.TimeoutSec = "10s";
|
|
||||||
mountConfig.ExecSearchPath = [ "/run/current-system/sw/bin" ];
|
|
||||||
mountConfig.User = "colin";
|
|
||||||
mountConfig.AmbientCapabilities = "CAP_SETPCAP CAP_SYS_ADMIN";
|
|
||||||
# hardening (systemd-analyze security mnt-servo-playground.mount)
|
|
||||||
mountConfig.CapabilityBoundingSet = "CAP_SETPCAP CAP_SYS_ADMIN";
|
|
||||||
mountConfig.LockPersonality = true;
|
|
||||||
mountConfig.MemoryDenyWriteExecute = true;
|
|
||||||
mountConfig.NoNewPrivileges = true;
|
|
||||||
mountConfig.ProtectClock = true;
|
|
||||||
mountConfig.ProtectHostname = true;
|
|
||||||
mountConfig.RemoveIPC = true;
|
|
||||||
mountConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
|
|
||||||
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
|
|
||||||
# see `systemd-analyze filesystems` for a full list
|
|
||||||
mountConfig.RestrictFileSystems = "@common-block @basic-api fuse";
|
|
||||||
mountConfig.RestrictRealtime = true;
|
|
||||||
mountConfig.RestrictSUIDSGID = true;
|
|
||||||
mountConfig.SystemCallArchitectures = "native";
|
|
||||||
mountConfig.SystemCallFilter = [
|
|
||||||
"@system-service"
|
|
||||||
"@mount"
|
|
||||||
"~@chown"
|
|
||||||
"~@cpu-emulation"
|
|
||||||
"~@keyring"
|
|
||||||
# could remove almost all io calls, however one has to keep `open`, and `write`, to communicate with the fuse device.
|
|
||||||
# so that's pretty useless as a way to prevent write access
|
|
||||||
];
|
|
||||||
mountConfig.IPAddressDeny = "any";
|
|
||||||
mountConfig.IPAddressAllow = "10.0.10.5";
|
|
||||||
mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
|
|
||||||
mountConfig.DeviceAllow = "/dev/fuse";
|
|
||||||
# mountConfig.RestrictNamespaces = true;
|
|
||||||
}];
|
|
||||||
|
|
||||||
systemd.targets."${systemdName}-restart-timer" = {
|
|
||||||
# hack unit which, when started, stops the timer (if running), and then starts it again.
|
|
||||||
after = [ "${systemdName}.timer" ];
|
|
||||||
conflicts = [ "${systemdName}.timer" ];
|
|
||||||
upholds = [ "${systemdName}.timer" ];
|
|
||||||
unitConfig.StopWhenUnneeded = true;
|
|
||||||
};
|
|
||||||
systemd.timers."${systemdName}" = {
|
|
||||||
timerConfig.Unit = "${systemdName}.mount";
|
|
||||||
timerConfig.AccuracySec = "2s";
|
|
||||||
timerConfig.OnActiveSec = [
|
|
||||||
# try to remount at these timestamps, backing off gradually
|
|
||||||
# there seems to be an implicit mount attempt at t=0.
|
|
||||||
"10s"
|
|
||||||
"30s"
|
|
||||||
"60s"
|
|
||||||
"120s"
|
|
||||||
];
|
|
||||||
# cap the backoff to a fixed interval.
|
|
||||||
timerConfig.OnUnitActiveSec = [ "120s" ];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
# this granularity of servo media mounts is necessary to support sandboxing. consider:
|
|
||||||
# 1. servo offline
|
|
||||||
# 2. launch a long-running app
|
|
||||||
# 3. servo comes online
|
|
||||||
# in order for the servo mount to be propagated into the app's namespace, we need to bind
|
|
||||||
# the root mountpoint into the app namespace. if we wish to only grant the app selective access
|
|
||||||
# to servo, we must create *multiple* mountpoints: /mnt/servo/FOO directories which always exist,
|
|
||||||
# and are individually bound to /mnt/.servo_ftp/FOO as the latter becomes available.
|
|
||||||
(remoteServo "media/archive")
|
|
||||||
(remoteServo "media/Books")
|
|
||||||
(remoteServo "media/collections")
|
|
||||||
# (remoteServo "media/datasets")
|
|
||||||
(remoteServo "media/games")
|
|
||||||
(remoteServo "media/Music")
|
|
||||||
(remoteServo "media/Pictures/macros")
|
|
||||||
(remoteServo "media/torrents")
|
|
||||||
(remoteServo "media/Videos")
|
|
||||||
(remoteServo "playground")
|
|
||||||
]
|
|
@@ -6,19 +6,13 @@
|
|||||||
"dev"
|
"dev"
|
||||||
"ref"
|
"ref"
|
||||||
"use"
|
"use"
|
||||||
"Books/Audiobooks"
|
|
||||||
"Books/Books"
|
|
||||||
"Books/Visual"
|
|
||||||
"Books/local"
|
"Books/local"
|
||||||
"Music"
|
"Music"
|
||||||
];
|
|
||||||
|
|
||||||
sane.user.persist.byStore.ephemeral = [
|
# this is persisted simply to save on RAM. mesa_shader_cache is < 10 MB.
|
||||||
# this is persisted simply to save on RAM. mesa_shader_cache_db is < 10 MB per boot.
|
# TODO: integrate with sane.programs.sandbox?
|
||||||
# TODO: see about removing this. the programs which benefit from shader caches should be configured to persist their _own_ dbs.
|
".cache/mesa_shader_cache"
|
||||||
".cache/mesa_shader_cache_db"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
sane.user.persist.byStore.private = [
|
sane.user.persist.byStore.private = [
|
||||||
"archive"
|
"archive"
|
||||||
"Pictures/albums"
|
"Pictures/albums"
|
||||||
@@ -37,15 +31,9 @@
|
|||||||
sane.user.fs = let
|
sane.user.fs = let
|
||||||
persistEnabled = config.sane.persist.enable;
|
persistEnabled = config.sane.persist.enable;
|
||||||
in {
|
in {
|
||||||
".persist/private" = lib.mkIf persistEnabled {
|
".persist/private" = lib.mkIf persistEnabled { symlink.target = config.sane.persist.stores.private.origin; };
|
||||||
symlink.target = "${config.sane.persist.stores.private.origin}/home/${config.sane.defaultUser}";
|
".persist/plaintext" = lib.mkIf persistEnabled { symlink.target = config.sane.persist.stores.plaintext.origin; };
|
||||||
};
|
".persist/ephemeral" = lib.mkIf persistEnabled { symlink.target = config.sane.persist.stores.ephemeral.origin; };
|
||||||
".persist/plaintext" = lib.mkIf persistEnabled {
|
|
||||||
symlink.target = "${config.sane.persist.stores.plaintext.origin}/home/${config.sane.defaultUser}";
|
|
||||||
};
|
|
||||||
".persist/ephemeral" = lib.mkIf persistEnabled {
|
|
||||||
symlink.target = "${config.sane.persist.stores.ephemeral.origin}/home/${config.sane.defaultUser}";
|
|
||||||
};
|
|
||||||
|
|
||||||
"nixos".symlink.target = "dev/nixos";
|
"nixos".symlink.target = "dev/nixos";
|
||||||
|
|
||||||
|
@@ -53,24 +53,19 @@ let
|
|||||||
(p: builtins.toString p.package)
|
(p: builtins.toString p.package)
|
||||||
(enabledProgramsWithPackage ++ [ { package=mimeappsListPkg; } ]);
|
(enabledProgramsWithPackage ++ [ { package=mimeappsListPkg; } ]);
|
||||||
}).overrideAttrs (orig: {
|
}).overrideAttrs (orig: {
|
||||||
# like normal symlinkJoin, but don't error if the path doesn't exist.
|
# like normal symlinkJoin, but don't error if the path doesn't exist
|
||||||
# additionally, remove `DBusActivatable=true` from any .desktop files encountered;
|
|
||||||
# my dbus session is sandboxed such that it can't activate services even if i thought that was a good idea.
|
|
||||||
buildCommand = ''
|
buildCommand = ''
|
||||||
mkdir -p $out/share/applications
|
mkdir -p $out/share/applications
|
||||||
for i in $(cat $pathsPath); do
|
for i in $(cat $pathsPath); do
|
||||||
if [ -e "$i/share/applications" ]; then
|
if [ -e "$i/share/applications" ]; then
|
||||||
local files=($(cd "$i/share/applications"; ls .))
|
${pkgs.buildPackages.xorg.lndir}/bin/lndir -silent $i/share/applications $out/share/applications
|
||||||
for f in "''${files[@]}"; do
|
|
||||||
sed '/DBusActivatable=true/d' $i/share/applications/$f > $out/share/applications/$f
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
runHook postBuild
|
runHook postBuild
|
||||||
'';
|
'';
|
||||||
postBuild = ''
|
postBuild = ''
|
||||||
# rebuild `mimeinfo.cache`, used by file openers to show the list of *all* apps, not just the user's defaults.
|
# rebuild `mimeinfo.cache`, used by file openers to show the list of *all* apps, not just the user's defaults.
|
||||||
${lib.getExe' pkgs.buildPackages.desktop-file-utils "update-desktop-database"} $out/share/applications
|
${pkgs.buildPackages.desktop-file-utils}/bin/update-desktop-database $out/share/applications
|
||||||
'';
|
'';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -2,12 +2,6 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
# TODO: this should be populated per-host
|
# TODO: this should be populated per-host
|
||||||
|
|
||||||
sane.hosts.by-name."cadey" = {
|
|
||||||
ssh.authorized = lib.mkDefault false;
|
|
||||||
lan-ip = "10.78.79.70";
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.hosts.by-name."crappy" = {
|
sane.hosts.by-name."crappy" = {
|
||||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMIvSQAGKqmymXIL4La9B00LPxBIqWAr5AsJxk3UQeY5";
|
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMIvSQAGKqmymXIL4La9B00LPxBIqWAr5AsJxk3UQeY5";
|
||||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMN0cpRAloCBOE5/2wuzgik35iNDv5KLceWMCVaa7DIQ";
|
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMN0cpRAloCBOE5/2wuzgik35iNDv5KLceWMCVaa7DIQ";
|
||||||
@@ -24,22 +18,14 @@
|
|||||||
lan-ip = "10.78.79.52";
|
lan-ip = "10.78.79.52";
|
||||||
};
|
};
|
||||||
|
|
||||||
sane.hosts.by-name."flowy" = {
|
sane.hosts.by-name."lappy" = {
|
||||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAa9U2+aUc5Kr6f2oeILAy2EC86W5OZSprmBb1F+8n7/";
|
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDpmFdNSVPRol5hkbbCivRhyeENzb9HVyf9KutGLP2Zu";
|
||||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMNuTITzc07mqYspWw6fqRw40ObxwnmWCwg188apHB/o";
|
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILSJnqmVl9/SYQ0btvGb0REwwWY8wkdkGXQZfn/1geEc";
|
||||||
wg-home.pubkey = "o6Vh+gHF87wAOOofgKKYIhV91kgDRnLvwnd5W2WHsDE=";
|
wg-home.pubkey = "FTUWGw2p4/cEcrrIE86PWVnqctbv8OYpw8Gt3+dC/lk=";
|
||||||
wg-home.ip = "10.0.10.56";
|
wg-home.ip = "10.0.10.20";
|
||||||
lan-ip = "10.78.79.56";
|
lan-ip = "10.78.79.53";
|
||||||
};
|
};
|
||||||
|
|
||||||
# sane.hosts.by-name."lappy" = {
|
|
||||||
# ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDpmFdNSVPRol5hkbbCivRhyeENzb9HVyf9KutGLP2Zu";
|
|
||||||
# ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILSJnqmVl9/SYQ0btvGb0REwwWY8wkdkGXQZfn/1geEc";
|
|
||||||
# wg-home.pubkey = "FTUWGw2p4/cEcrrIE86PWVnqctbv8OYpw8Gt3+dC/lk=";
|
|
||||||
# wg-home.ip = "10.0.10.20";
|
|
||||||
# lan-ip = "10.78.79.53";
|
|
||||||
# };
|
|
||||||
|
|
||||||
sane.hosts.by-name."moby" = {
|
sane.hosts.by-name."moby" = {
|
||||||
# ssh.authorized = lib.mkDefault false; # moby's too easy to hijack: don't let it ssh places
|
# ssh.authorized = lib.mkDefault false; # moby's too easy to hijack: don't let it ssh places
|
||||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrR+gePnl0nV/vy7I5BzrGeyVL+9eOuXHU1yNE3uCwU";
|
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrR+gePnl0nV/vy7I5BzrGeyVL+9eOuXHU1yNE3uCwU";
|
||||||
@@ -50,7 +36,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
sane.hosts.by-name."servo" = {
|
sane.hosts.by-name."servo" = {
|
||||||
# ssh.authorized = lib.mkDefault false; # servo presents too many services to the internet: easy atack vector
|
ssh.authorized = lib.mkDefault false; # servo presents too many services to the internet: easy atack vector
|
||||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPS1qFzKurAdB9blkWomq8gI1g0T3sTs9LsmFOj5VtqX";
|
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPS1qFzKurAdB9blkWomq8gI1g0T3sTs9LsmFOj5VtqX";
|
||||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOfdSmFkrVT6DhpgvFeQKm3Fh9VKZ9DbLYOPOJWYQ0E8";
|
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOfdSmFkrVT6DhpgvFeQKm3Fh9VKZ9DbLYOPOJWYQ0E8";
|
||||||
wg-home.pubkey = "roAw+IUFVtdpCcqa4khB385Qcv9l5JAB//730tyK4Wk=";
|
wg-home.pubkey = "roAw+IUFVtdpCcqa4khB385Qcv9l5JAB//730tyK4Wk=";
|
||||||
|
@@ -6,7 +6,6 @@
|
|||||||
{
|
{
|
||||||
# partially supported in nixpkgs <repo:nixos/nixpkgs:nixos/modules/misc/ids.nix>
|
# partially supported in nixpkgs <repo:nixos/nixpkgs:nixos/modules/misc/ids.nix>
|
||||||
sane.ids.networkmanager.uid = 57; #< nixpkgs unofficially reserves this, to match networkmanager's gid
|
sane.ids.networkmanager.uid = 57; #< nixpkgs unofficially reserves this, to match networkmanager's gid
|
||||||
sane.ids.mediatomb.uid = 187; # <repo:nixos/nixpkgs:nixos/modules/misc/ids.nix>
|
|
||||||
|
|
||||||
# legacy servo users, some are inconvenient to migrate
|
# legacy servo users, some are inconvenient to migrate
|
||||||
sane.ids.dhcpcd.gid = 991;
|
sane.ids.dhcpcd.gid = 991;
|
||||||
@@ -66,11 +65,6 @@
|
|||||||
sane.ids.plugdev.gid = 2421;
|
sane.ids.plugdev.gid = 2421;
|
||||||
sane.ids.ollama.uid = 2422;
|
sane.ids.ollama.uid = 2422;
|
||||||
sane.ids.ollama.gid = 2422;
|
sane.ids.ollama.gid = 2422;
|
||||||
sane.ids.bitmagnet.uid = 2423;
|
|
||||||
sane.ids.bitmagnet.gid = 2423;
|
|
||||||
sane.ids.anubis.uid = 2424;
|
|
||||||
sane.ids.anubis.gid = 2424;
|
|
||||||
sane.ids.shelvacu.uid = 5431;
|
|
||||||
|
|
||||||
sane.ids.colin.uid = 1000;
|
sane.ids.colin.uid = 1000;
|
||||||
sane.ids.guest.uid = 1100;
|
sane.ids.guest.uid = 1100;
|
||||||
@@ -87,16 +81,6 @@
|
|||||||
sane.ids.wireshark.gid = 2006;
|
sane.ids.wireshark.gid = 2006;
|
||||||
sane.ids.nixremote.uid = 2007;
|
sane.ids.nixremote.uid = 2007;
|
||||||
sane.ids.nixremote.gid = 2007;
|
sane.ids.nixremote.gid = 2007;
|
||||||
sane.ids.unbound.uid = 2008;
|
|
||||||
sane.ids.unbound.gid = 2008;
|
|
||||||
sane.ids.resolvconf.gid = 2009;
|
|
||||||
sane.ids.smartd.uid = 2010;
|
|
||||||
sane.ids.smartd.gid = 2010;
|
|
||||||
sane.ids.radicale.uid = 2011;
|
|
||||||
sane.ids.radicale.gid = 2011;
|
|
||||||
sane.ids.named.uid = 2012;
|
|
||||||
sane.ids.named.gid = 2012;
|
|
||||||
sane.ids.lpadmin.gid = 2013;
|
|
||||||
|
|
||||||
# found on graphical hosts
|
# found on graphical hosts
|
||||||
sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy
|
sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./dns
|
./dns.nix
|
||||||
./hostnames.nix
|
./hostnames.nix
|
||||||
./modemmanager.nix
|
./modemmanager.nix
|
||||||
./networkmanager.nix
|
./networkmanager.nix
|
||||||
./ntp.nix
|
|
||||||
./upnp.nix
|
./upnp.nix
|
||||||
./vpn.nix
|
./vpn.nix
|
||||||
];
|
];
|
||||||
|
@@ -20,14 +20,36 @@
|
|||||||
# - each namespace may use a different /etc/resolv.conf to specify different DNS servers
|
# - each namespace may use a different /etc/resolv.conf to specify different DNS servers
|
||||||
# - nscd breaks namespacing: the host nscd is unaware of the guest's /etc/resolv.conf, and so directs the guest's DNS requests to the host's servers.
|
# - nscd breaks namespacing: the host nscd is unaware of the guest's /etc/resolv.conf, and so directs the guest's DNS requests to the host's servers.
|
||||||
# - this is fixed by either removing `/var/run/nscd/socket` from the namespace, or disabling nscd altogether.
|
# - this is fixed by either removing `/var/run/nscd/socket` from the namespace, or disabling nscd altogether.
|
||||||
{ config, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
lib.mkMerge [
|
||||||
{
|
{
|
||||||
imports = [
|
sane.services.hickory-dns.enable = lib.mkDefault config.sane.services.hickory-dns.asSystemResolver;
|
||||||
./bind.nix
|
sane.services.hickory-dns.asSystemResolver = lib.mkDefault true;
|
||||||
./hickory-dns.nix
|
}
|
||||||
./unbound.nix
|
(lib.mkIf (!config.sane.services.hickory-dns.asSystemResolver) {
|
||||||
|
# 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 servo's 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?)
|
||||||
|
# TODO: improve hickory-dns recursive resolver and then remove this
|
||||||
|
services.resolved.enable = true; #< to disable, set ` = lib.mkForce false`, as other systemd features default to enabling `resolved`.
|
||||||
|
# without DNSSEC:
|
||||||
|
# - dig matrix.org => works
|
||||||
|
# - curl https://matrix.org => works
|
||||||
|
# with default DNSSEC:
|
||||||
|
# - dig matrix.org => works
|
||||||
|
# - curl https://matrix.org => fails
|
||||||
|
# i don't know why. this might somehow be interfering with the DNS run on this device (hickory-dns)
|
||||||
|
services.resolved.dnssec = "false";
|
||||||
|
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"
|
||||||
];
|
];
|
||||||
|
})
|
||||||
|
{
|
||||||
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
|
# 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
|
# in a way that's unaware of my VPN routing, so routes are frequently poor against
|
||||||
# services which advertise different IPs based on geolocation.
|
# services which advertise different IPs based on geolocation.
|
||||||
@@ -50,7 +72,6 @@
|
|||||||
services.nscd.enable = false;
|
services.nscd.enable = false;
|
||||||
# system.nssModules = lib.mkForce [];
|
# system.nssModules = lib.mkForce [];
|
||||||
sane.silencedAssertions = [''.*Loading NSS modules from system.nssModules.*requires services.nscd.enable being set to true.*''];
|
sane.silencedAssertions = [''.*Loading NSS modules from system.nssModules.*requires services.nscd.enable being set to true.*''];
|
||||||
|
|
||||||
# add NSS modules into their own subdirectory.
|
# add NSS modules into their own subdirectory.
|
||||||
# then i can add just the NSS modules library path to the global LD_LIBRARY_PATH, rather than ALL of /run/current-system/sw/lib.
|
# then i can add just the NSS modules library path to the global LD_LIBRARY_PATH, rather than ALL of /run/current-system/sw/lib.
|
||||||
# TODO: i'm doing this so as to achieve mdns DNS resolution (avahi). it would be better to just have hickory-dns delegate .local to avahi
|
# TODO: i'm doing this so as to achieve mdns DNS resolution (avahi). it would be better to just have hickory-dns delegate .local to avahi
|
||||||
@@ -69,3 +90,4 @@
|
|||||||
environment.variables.LD_LIBRARY_PATH = [ "/run/current-system/sw/lib/nss" ];
|
environment.variables.LD_LIBRARY_PATH = [ "/run/current-system/sw/lib/nss" ];
|
||||||
systemd.globalEnvironment.LD_LIBRARY_PATH = "/run/current-system/sw/lib/nss"; #< specifically for `geoclue.service`
|
systemd.globalEnvironment.LD_LIBRARY_PATH = "/run/current-system/sw/lib/nss"; #< specifically for `geoclue.service`
|
||||||
}
|
}
|
||||||
|
]
|
@@ -1,162 +0,0 @@
|
|||||||
# debugging:
|
|
||||||
# - /var/log/named/named.log
|
|
||||||
## config
|
|
||||||
# - `man named`
|
|
||||||
# - `man named.conf`
|
|
||||||
# - show defaults with: `named -C`
|
|
||||||
# - defaults live in <repo:isc-projects/bind:bin/named/config.c>
|
|
||||||
# - per-option docs live in <repo:isc-projects/bind:bind9/doc/arm/reference.rst>
|
|
||||||
#
|
|
||||||
## statistics
|
|
||||||
# - `netstat --statistics --udp`
|
|
||||||
# - `rdnc stats`? dumps to `named.stats` in named's PWD?
|
|
||||||
#
|
|
||||||
## interactive debugging
|
|
||||||
# - `systemctl stop bind`
|
|
||||||
# - `sudo /nix/store/0zpdy93sd3fgbxgvf8dsxhn8fbbya8d2-bind-9.18.28/sbin/named -g -u named -4 -c /nix/store/f1mp0myzmfms71h9vinwxpn2i9362a9a-named.conf`
|
|
||||||
# - `-g` = don't fork
|
|
||||||
# - `-u named` = start as superuser (to claim port 53), then drop to user `named`
|
|
||||||
#
|
|
||||||
{ config, lib, pkgs, ... }:
|
|
||||||
let
|
|
||||||
hostCfg = config.sane.hosts.by-name."${config.networking.hostName}" or null;
|
|
||||||
bindCfg = config.services.bind;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = lib.mkIf (!config.sane.services.hickory-dns.asSystemResolver) {
|
|
||||||
services.resolved.enable = lib.mkForce false;
|
|
||||||
|
|
||||||
networking.nameservers = [
|
|
||||||
# be compatible with systemd-resolved
|
|
||||||
# "127.0.0.53"
|
|
||||||
# or don't be compatible with systemd-resolved, but with libc and pasta instead
|
|
||||||
# see <pkgs/by-name/sane-scripts/src/sane-vpn>
|
|
||||||
"127.0.0.1"
|
|
||||||
# enable IPv6, or don't; unbound is spammy when IPv6 is enabled but unroutable
|
|
||||||
# "::1"
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.resolvconf.extraConfig = ''
|
|
||||||
# DNS serviced by `BIND` recursive resolver
|
|
||||||
name_servers='127.0.0.1'
|
|
||||||
'';
|
|
||||||
|
|
||||||
services.bind.enable = lib.mkDefault true;
|
|
||||||
services.bind.forwarders = []; #< don't forward queries to upstream resolvers
|
|
||||||
services.bind.cacheNetworks = [
|
|
||||||
"127.0.0.0/24"
|
|
||||||
"::1/128"
|
|
||||||
"10.0.10.0/24" #< wireguard clients (servo)
|
|
||||||
];
|
|
||||||
services.bind.listenOn = [
|
|
||||||
"127.0.0.1"
|
|
||||||
] ++ lib.optionals (hostCfg != null && hostCfg.wg-home.ip != null) [
|
|
||||||
# allow wireguard clients to use us as a recursive resolver (only needed for servo)
|
|
||||||
hostCfg.wg-home.ip
|
|
||||||
];
|
|
||||||
services.bind.listenOnIpv6 = [
|
|
||||||
# "::1"
|
|
||||||
];
|
|
||||||
|
|
||||||
services.bind.ipv4Only = true; # unbound is spammy when it tries IPv6 without a routable address
|
|
||||||
|
|
||||||
# when testing, deploy on a port other than 53
|
|
||||||
# services.bind.extraOptions = ''
|
|
||||||
# listen-on port 953 { any; };
|
|
||||||
# '';
|
|
||||||
|
|
||||||
# services.bind.extraArgs = [
|
|
||||||
# # -d = debug logging level: higher = more verbose
|
|
||||||
# "-d" "2"
|
|
||||||
# # -L = where to log. default is `named.run` in PWD -- unless running interactively in which case it logs to stdout
|
|
||||||
# "-L" "/var/log/named/named.log"
|
|
||||||
# ];
|
|
||||||
|
|
||||||
networking.resolvconf.useLocalResolver = false; #< we manage resolvconf explicitly, above
|
|
||||||
|
|
||||||
# TODO: how to exempt `pool.ntp.org` from DNSSEC checks, as i did when using unbound?
|
|
||||||
|
|
||||||
# allow runtime insertion of zones or other config changes:
|
|
||||||
# add your supplemental config as a toplevel file in /run/named/dhcp-configs/, then `systemctl restart bind`
|
|
||||||
services.bind.extraConfig = ''
|
|
||||||
include "/run/named/dhcp-configs.conf";
|
|
||||||
'';
|
|
||||||
services.bind.extraOptions = ''
|
|
||||||
// we can't guarantee that all forwarders support DNSSEC,
|
|
||||||
// and as of 2025-01-30 BIND9 gives no way to disable DNSSEC per-forwarder/zone,
|
|
||||||
// so just disable it globally
|
|
||||||
dnssec-validation no;
|
|
||||||
// XXX(2025-06-30): i need reverse DNS of private IP space such as 10.0.0.0/8.
|
|
||||||
// configuring those zones (done in a secrets/ file), unfortunately requires disabling
|
|
||||||
// ALL local entries for reserved zones (IN-ADDR.ARPA, IP6.ARPA, EMPTY.AS112.ARPA, HOME.ARPA, RESOLVER.ARPA).
|
|
||||||
// TODO: figure a better solution, as this likely causes reverse-DNS queries of LAN hosts to be sent to the WAN!
|
|
||||||
// - see <https://www.as112.net/>
|
|
||||||
empty-zones-enable no;
|
|
||||||
'';
|
|
||||||
# re-implement the nixos default bind config, but without `options { forwarders { }; };`,
|
|
||||||
# as having an empty `forwarders` at the top-level prevents me from forwarding the `.` zone in a separate statement
|
|
||||||
# (which i want to do to allow sane-vpn to forward all DNS).
|
|
||||||
services.bind.configFile = pkgs.writeText "named.conf" ''
|
|
||||||
include "/etc/bind/rndc.key";
|
|
||||||
controls {
|
|
||||||
inet 127.0.0.1 allow {localhost;} keys {"rndc-key";};
|
|
||||||
};
|
|
||||||
|
|
||||||
acl cachenetworks { ${lib.concatMapStrings (entry: " ${entry}; ") bindCfg.cacheNetworks} };
|
|
||||||
acl badnetworks { ${lib.concatMapStrings (entry: " ${entry}; ") bindCfg.blockedNetworks} };
|
|
||||||
|
|
||||||
options {
|
|
||||||
listen-on { ${lib.concatMapStrings (entry: " ${entry}; ") bindCfg.listenOn} };
|
|
||||||
listen-on-v6 { ${lib.concatMapStrings (entry: " ${entry}; ") bindCfg.listenOnIpv6} };
|
|
||||||
allow-query-cache { cachenetworks; };
|
|
||||||
blackhole { badnetworks; };
|
|
||||||
//v disable top-level forwards, so that i can do forwarding more generically in `zone FOO { ... }` directives.
|
|
||||||
// forward ${bindCfg.forward};
|
|
||||||
// forwarders { ${lib.concatMapStrings (entry: " ${entry}; ") bindCfg.forwarders} };
|
|
||||||
directory "${bindCfg.directory}";
|
|
||||||
pid-file "/run/named/named.pid";
|
|
||||||
${bindCfg.extraOptions}
|
|
||||||
};
|
|
||||||
|
|
||||||
// XXX(2025-06-18): some tools i use for work assume 'localhost' can be resolved by the system nameserver,
|
|
||||||
// and not just by /etc/hosts
|
|
||||||
zone "localhost" {
|
|
||||||
type master;
|
|
||||||
file "${pkgs.writeText "localhost" ''
|
|
||||||
$TTL 300
|
|
||||||
@ IN SOA localhost. root.localhost. (
|
|
||||||
202506181 ; Serial
|
|
||||||
28800 ; Refresh
|
|
||||||
7200 ; Retry
|
|
||||||
604800 ; Expire
|
|
||||||
86400) ; Minimum TTL
|
|
||||||
NS localhost.
|
|
||||||
|
|
||||||
localhost. A 127.0.0.1
|
|
||||||
AAAA ::1
|
|
||||||
''}";
|
|
||||||
};
|
|
||||||
|
|
||||||
${bindCfg.extraConfig}
|
|
||||||
'';
|
|
||||||
|
|
||||||
systemd.services.bind.serviceConfig.ExecStartPre = pkgs.writeShellScript "named-generate-config" ''
|
|
||||||
mkdir -p /run/named/dhcp-configs
|
|
||||||
chmod g+w /run/named/dhcp-configs
|
|
||||||
echo "// FILE GENERATED BY bind.service's ExecStartPre: CHANGES TO THIS FILE WILL BE OVERWRITTEN" > /run/named/dhcp-configs.conf
|
|
||||||
for c in $(ls /run/named/dhcp-configs/); do
|
|
||||||
cat "/run/named/dhcp-configs/$c" >> /run/named/dhcp-configs.conf
|
|
||||||
done
|
|
||||||
'';
|
|
||||||
systemd.services.bind.serviceConfig.ReadWritePaths = [
|
|
||||||
"/var/log/named"
|
|
||||||
];
|
|
||||||
|
|
||||||
sane.persist.sys.byPath."/var/log/named" = {
|
|
||||||
store = "ephemeral";
|
|
||||||
method = "symlink";
|
|
||||||
acl.mode = "0750";
|
|
||||||
acl.user = "named";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,31 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
lib.mkIf false #< XXX(2024-10-xx): hickory-dns recursive resolution is too immature; switched to `unbound`
|
|
||||||
(lib.mkMerge [
|
|
||||||
{
|
|
||||||
sane.services.hickory-dns.enable = lib.mkDefault config.sane.services.hickory-dns.asSystemResolver;
|
|
||||||
# sane.services.hickory-dns.asSystemResolver = lib.mkDefault true;
|
|
||||||
}
|
|
||||||
(lib.mkIf (!config.sane.services.hickory-dns.asSystemResolver && config.sane.services.hickory-dns.enable) {
|
|
||||||
# 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 servo's 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?)
|
|
||||||
# TODO: improve hickory-dns recursive resolver and then remove this
|
|
||||||
services.resolved.enable = true; #< to disable, set ` = lib.mkForce false`, as other systemd features default to enabling `resolved`.
|
|
||||||
# without DNSSEC:
|
|
||||||
# - dig matrix.org => works
|
|
||||||
# - curl https://matrix.org => works
|
|
||||||
# with default DNSSEC:
|
|
||||||
# - dig matrix.org => works
|
|
||||||
# - curl https://matrix.org => fails
|
|
||||||
# i don't know why. this might somehow be interfering with the DNS run on this device (hickory-dns)
|
|
||||||
services.resolved.dnssec = "false";
|
|
||||||
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"
|
|
||||||
];
|
|
||||||
})
|
|
||||||
])
|
|
@@ -1,93 +0,0 @@
|
|||||||
# `man unbound.conf` for info on settings
|
|
||||||
# it's REALLY EASY to combine settings in a way that produce bad effects.
|
|
||||||
# generally, prefer to stay close to defaults unless there's a compelling reason to differ.
|
|
||||||
{ config, lib, ... }:
|
|
||||||
lib.optionalAttrs false #< XXX(2024-12-29): unbound caches failed DNS resolutions, just randomly breaks connectivity daily
|
|
||||||
{
|
|
||||||
config = lib.mkIf (!config.sane.services.hickory-dns.asSystemResolver) {
|
|
||||||
services.resolved.enable = lib.mkForce false;
|
|
||||||
|
|
||||||
networking.nameservers = [
|
|
||||||
# be compatible with systemd-resolved
|
|
||||||
# "127.0.0.53"
|
|
||||||
# or don't be compatible with systemd-resolved, but with libc and pasta instead
|
|
||||||
# see <pkgs/by-name/sane-scripts/src/sane-vpn>
|
|
||||||
"127.0.0.1"
|
|
||||||
# enable IPv6, or don't, because having just a single name server makes monkey-patching it easier
|
|
||||||
# "::1"
|
|
||||||
];
|
|
||||||
networking.resolvconf.extraConfig = ''
|
|
||||||
# DNS serviced by `unbound` recursive resolver
|
|
||||||
name_servers='127.0.0.1'
|
|
||||||
'';
|
|
||||||
|
|
||||||
# resolve DNS recursively with Unbound.
|
|
||||||
services.unbound.enable = lib.mkDefault true;
|
|
||||||
services.unbound.resolveLocalQueries = false; #< disable, so that i can manage networking.nameservers manually
|
|
||||||
services.unbound.settings.server.interface = [ "127.0.0.1" ];
|
|
||||||
services.unbound.settings.server.access-control = [ "127.0.0.0/8 allow" ];
|
|
||||||
|
|
||||||
# allow control via `unbound-control`. user must be a member of the `unbound` Unix group.
|
|
||||||
services.unbound.localControlSocketPath = "/run/unbound/unbound.ctl";
|
|
||||||
|
|
||||||
# exempt `pool.ntp.org` from DNSSEC checks to avoid a circular dependency between DNS resolution and NTP.
|
|
||||||
# without this, if the RTC fails, then both time and DNS are unrecoverable.
|
|
||||||
services.unbound.settings.server.domain-insecure = config.networking.timeServers;
|
|
||||||
|
|
||||||
# XXX(2024-12-03): BUG: during boot (before network is up), or during network blips, Unbound will
|
|
||||||
# receive a query, fail to evaluate it, and then resolve future identical queries with a no-answers response for the next ~15m.
|
|
||||||
# this *appears* to be some bug in Unbound's "infra-cache", as evidenced by `unbound-control flush_infra all`.
|
|
||||||
#
|
|
||||||
# the infra cache is a per-nameserver liveness and latency cache which Unbound uses to decide which of N applicable nameservers to route a given query to.
|
|
||||||
#
|
|
||||||
# there is apparently NO simple solution.
|
|
||||||
# the closest fix is to reduce the TTL of the infra-cache (`infra-host-ttl`) so as to limit the duration of this error.
|
|
||||||
# tried, but failed fixes:
|
|
||||||
# - server.harden-dnssec-stripped = false
|
|
||||||
# - services.unbound.enableRootTrustAnchor = false; #< disable DNSSEC
|
|
||||||
# - server.trust-anchor-file = "${pkgs.dns-root-data}/root.key"; #< hardcode root keys instead of dynamically probing them
|
|
||||||
# - server.disable-dnssec-lame-check = true;
|
|
||||||
# - server.infra-keep-probing = true; #< if unbound fails to reach a host (NS), it by default *does not try again* for 900s. keep-probing tells it to keep trying, with a backoff.
|
|
||||||
# - server.infra-cache-min-rtt = 1000;
|
|
||||||
# - server.infra-cache-max-rtt = 1000;
|
|
||||||
#
|
|
||||||
# see also:
|
|
||||||
# - <https://forum.opnsense.org/index.php?topic=32852.0>
|
|
||||||
# - <https://unbound.docs.nlnetlabs.nl/en/latest/reference/history/info-timeout-server-selection.html>
|
|
||||||
#
|
|
||||||
services.unbound.settings.server.infra-host-ttl = 30; #< cache each NS's liveness for a max of 30s
|
|
||||||
|
|
||||||
# perf tuning; see: <https://unbound.docs.nlnetlabs.nl/en/latest/topics/core/performance.html>
|
|
||||||
# resource usage:
|
|
||||||
# - defaults (num-threads = 1; so-{rcvbuf,sndbuf} = 0, prefetch = false): 12.7M memory usage
|
|
||||||
# - num-threads = 2: 17.2M memory usage
|
|
||||||
# - num-threads = 4: 26.2M memory usage
|
|
||||||
# - num-threads = 4; so-{rcvbuf,sndbuf}=4m: 26.7M memory usage
|
|
||||||
# - prefetch = true: no increased memory; supposed 10% increase in traffic
|
|
||||||
#
|
|
||||||
# # i suspect most operations are async; the only serialized bits are either CPU or possibly local IO (i.e. syscalls to write sockets).
|
|
||||||
# # threading is probably only rarely helpful
|
|
||||||
# services.unbound.settings.server.num-threads = 4;
|
|
||||||
#
|
|
||||||
# higher so-rcvbuf means less likely to drop client queries...
|
|
||||||
# default is `cat /proc/sys/net/core/wmem_default`, i.e. 208k
|
|
||||||
# services.unbound.settings.server.so-rcvbuf = "1m";
|
|
||||||
# services.unbound.settings.server.so-sndbuf = "1m";
|
|
||||||
#
|
|
||||||
# `prefetch`: prefetch RRs which are about to expire from the cache, to keep them primed.
|
|
||||||
# services.unbound.settings.server.prefetch = true;
|
|
||||||
|
|
||||||
# if a resolution fails, or takes excessively long, reply with expired cache entries
|
|
||||||
# see: <https://unbound.docs.nlnetlabs.nl/en/latest/topics/core/serve-stale.html#rfc-8767>
|
|
||||||
services.unbound.settings.server.serve-expired = true;
|
|
||||||
services.unbound.settings.server.serve-expired-ttl = 86400; #< don't serve any records more outdated than this
|
|
||||||
services.unbound.settings.server.serve-expired-client-timeout = 2800; #< only serve expired records if the client has been waiting this long, ms
|
|
||||||
|
|
||||||
# `cache-max-negative-ttl`: intended to limit damage during networking flakes, but instead seems to cause unbound to cache error responses it *wouldn't* otherwise cache
|
|
||||||
# services.unbound.settings.server.cache-max-negative-ttl = 60;
|
|
||||||
|
|
||||||
# `user-caps-for-id`: randomizes casing to avoid spoofing, but causes unbound to reply with no results to queries after boot (likely a infra-cache issue)
|
|
||||||
# services.unbound.settings.server.use-caps-for-id = true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@@ -1,10 +1,29 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
{ config, pkgs, ... }:
|
||||||
{
|
let
|
||||||
|
# networkmanager = pkgs.networkmanager;
|
||||||
|
networkmanager = pkgs.networkmanager.overrideAttrs (upstream: {
|
||||||
|
# src = pkgs.fetchFromGitea {
|
||||||
|
# domain = "git.uninsane.org";
|
||||||
|
# owner = "colin";
|
||||||
|
# repo = "NetworkManager";
|
||||||
|
# # patched to fix polkit permissions (with `nmcli`) when NetworkManager runs as user networkmanager
|
||||||
|
# rev = "dev-sane-1.48.0";
|
||||||
|
# hash = "sha256-vGmOKtwVItxjYioZJlb1og3K6u9s4rcmDnjAPLBC3ao=";
|
||||||
|
# };
|
||||||
|
patches = (upstream.patches or []) ++ [
|
||||||
|
(pkgs.fetchpatch {
|
||||||
|
name = "polkit: add owner annotations to all actions";
|
||||||
|
url = "https://git.uninsane.org/colin/NetworkManager/commit/a01293861fa24201ffaeb84c07f1c71136c49759.patch";
|
||||||
|
hash = "sha256-th1/M2slo7rjkVBwETZII53Lmhyw8OMS0aT9QYI5Uvk=";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
});
|
||||||
|
# split the package into `daemon` and `nmcli` outputs, because the networkmanager *service*
|
||||||
|
# doesn't need `nmcli`/`nmtui` tooling
|
||||||
|
networkmanager-split = pkgs.networkmanager-split.override { inherit networkmanager; };
|
||||||
|
in {
|
||||||
networking.networkmanager.enable = true;
|
networking.networkmanager.enable = true;
|
||||||
# systemd-networkd-wait-online.service reliably fails on lappy. docs don't match behavior. shit software.
|
systemd.network.wait-online.enable = false; # systemd-networkd-wait-online.service reliably fails on lappy. docs don't match behavior. shit software.
|
||||||
# XXX(2025-07-18): `systemd-networkd-wait-online.service` also fails on desko (timeout).
|
|
||||||
systemd.network.wait-online.enable = false;
|
|
||||||
|
|
||||||
# plugins mostly add support for establishing different VPN connections.
|
# plugins mostly add support for establishing different VPN connections.
|
||||||
# the default plugin set includes mostly proprietary VPNs:
|
# the default plugin set includes mostly proprietary VPNs:
|
||||||
# - fortisslvpn (Fortinet)
|
# - fortisslvpn (Fortinet)
|
||||||
@@ -17,9 +36,10 @@
|
|||||||
#
|
#
|
||||||
# i don't use these, and notably they drag in huge dependency sets and don't cross compile well.
|
# i don't use these, and notably they drag in huge dependency sets and don't cross compile well.
|
||||||
# e.g. openconnect drags in webkitgtk (for SSO)!
|
# e.g. openconnect drags in webkitgtk (for SSO)!
|
||||||
networking.networkmanager.plugins = lib.mkForce [];
|
# networking.networkmanager.plugins = lib.mkForce [];
|
||||||
|
networking.networkmanager.enableDefaultPlugins = false;
|
||||||
|
|
||||||
networking.networkmanager.package = pkgs.networkmanager-split.daemon.overrideAttrs (upstream: {
|
networking.networkmanager.package = networkmanager-split.daemon.overrideAttrs (upstream: {
|
||||||
# postPatch = (upstream.postPatch or "") + ''
|
# postPatch = (upstream.postPatch or "") + ''
|
||||||
# substituteInPlace src/{core/org.freedesktop.NetworkManager,nm-dispatcher/nm-dispatcher}.conf --replace-fail \
|
# substituteInPlace src/{core/org.freedesktop.NetworkManager,nm-dispatcher/nm-dispatcher}.conf --replace-fail \
|
||||||
# 'user="root"' 'user="networkmanager"'
|
# 'user="root"' 'user="networkmanager"'
|
||||||
@@ -28,9 +48,7 @@
|
|||||||
# allow the bus to owned by either root or networkmanager users
|
# allow the bus to owned by either root or networkmanager users
|
||||||
# use the group here, that way ordinary users can be elevated to control networkmanager
|
# use the group here, that way ordinary users can be elevated to control networkmanager
|
||||||
# (via e.g. `nmcli`)
|
# (via e.g. `nmcli`)
|
||||||
confs=(nm-dispatcher.conf)
|
for f in org.freedesktop.NetworkManager.conf nm-dispatcher.conf ; do
|
||||||
confs+=(org.freedesktop.NetworkManager.conf)
|
|
||||||
for f in "''${confs[@]}" ; do
|
|
||||||
substitute $out/share/dbus-1/system.d/$f \
|
substitute $out/share/dbus-1/system.d/$f \
|
||||||
$out/share/dbus-1/system.d/networkmanager-$f \
|
$out/share/dbus-1/system.d/networkmanager-$f \
|
||||||
--replace-fail 'user="root"' 'group="networkmanager"'
|
--replace-fail 'user="root"' 'group="networkmanager"'
|
||||||
@@ -48,23 +66,15 @@
|
|||||||
serviceConfig.User = "networkmanager";
|
serviceConfig.User = "networkmanager";
|
||||||
serviceConfig.Group = "networkmanager";
|
serviceConfig.Group = "networkmanager";
|
||||||
serviceConfig.AmbientCapabilities = [
|
serviceConfig.AmbientCapabilities = [
|
||||||
"CAP_KILL" #< required, else `nmcli d disconnect blah` says "Unable to determine UID of the request"
|
|
||||||
"CAP_NET_ADMIN"
|
"CAP_NET_ADMIN"
|
||||||
"CAP_NET_RAW"
|
"CAP_NET_RAW"
|
||||||
"CAP_NET_BIND_SERVICE"
|
"CAP_NET_BIND_SERVICE"
|
||||||
|
|
||||||
# "CAP_DAC_OVERRIDE"
|
|
||||||
# "CAP_SYS_MODULE"
|
|
||||||
# "CAP_AUDIT_WRITE" #< allow writing to the audit log (optional)
|
|
||||||
# "CAP_KILL"
|
|
||||||
];
|
];
|
||||||
serviceConfig.CapabilityBoundingSet = [
|
serviceConfig.CapabilityBoundingSet = [
|
||||||
"CAP_KILL" #< required, else `nmcli d disconnect blah` says "Unable to determine UID of the request"
|
# "CAP_DAC_OVERRIDE"
|
||||||
"CAP_NET_ADMIN"
|
"CAP_NET_ADMIN"
|
||||||
"CAP_NET_RAW" #< required, else `libndp: ndp_sock_open: Failed to create ICMP6 socket.`
|
"CAP_NET_RAW" #< required, else `libndp: ndp_sock_open: Failed to create ICMP6 socket.`
|
||||||
"CAP_NET_BIND_SERVICE" #< this *does* seem to be necessary, though i don't understand why. DHCP?
|
"CAP_NET_BIND_SERVICE" #< this *does* seem to be necessary, though i don't understand why. DHCP?
|
||||||
|
|
||||||
# "CAP_DAC_OVERRIDE"
|
|
||||||
# "CAP_SYS_MODULE"
|
# "CAP_SYS_MODULE"
|
||||||
# "CAP_AUDIT_WRITE" #< allow writing to the audit log (optional)
|
# "CAP_AUDIT_WRITE" #< allow writing to the audit log (optional)
|
||||||
# "CAP_KILL"
|
# "CAP_KILL"
|
||||||
@@ -82,9 +92,9 @@
|
|||||||
serviceConfig.ProtectHostname = true; # probably not upstreamable: prevents changing hostname
|
serviceConfig.ProtectHostname = true; # probably not upstreamable: prevents changing hostname
|
||||||
serviceConfig.ProtectKernelLogs = true; # disable /proc/kmsg, /dev/kmsg
|
serviceConfig.ProtectKernelLogs = true; # disable /proc/kmsg, /dev/kmsg
|
||||||
serviceConfig.ProtectKernelModules = true; # syscall filter to prevent module calls (probably not upstreamable: NM will want to load modules like `ppp`)
|
serviceConfig.ProtectKernelModules = true; # syscall filter to prevent module calls (probably not upstreamable: NM will want to load modules like `ppp`)
|
||||||
# serviceConfig.ProtectKernelTunables = true; # causes errors/warnings when opening files in /proc/sys/net/...; also breaks IPv6 SLAAC / link-local address creation!
|
serviceConfig.ProtectKernelTunables = true; # but NM might need to write /proc/sys/net/...
|
||||||
serviceConfig.ProtectProc = "invisible";
|
serviceConfig.ProtectProc = "invisible";
|
||||||
serviceConfig.ProcSubset = "all";
|
serviceConfig.ProcSubset = "pid";
|
||||||
serviceConfig.ProtectSystem = "strict"; # makes read-only: all but /dev, /proc, /sys.
|
serviceConfig.ProtectSystem = "strict"; # makes read-only: all but /dev, /proc, /sys.
|
||||||
serviceConfig.RemoveIPC = true;
|
serviceConfig.RemoveIPC = true;
|
||||||
serviceConfig.RestrictAddressFamilies = [
|
serviceConfig.RestrictAddressFamilies = [
|
||||||
@@ -130,7 +140,7 @@
|
|||||||
#VVV so that /var/lib/hickory-dns will exist (the hook needs to write here).
|
#VVV so that /var/lib/hickory-dns will exist (the hook needs to write here).
|
||||||
# but this creates a cycle: hickory-dns-localhost > network.target > NetworkManager-dispatcher > hickory-dns-localhost.
|
# but this creates a cycle: hickory-dns-localhost > network.target > NetworkManager-dispatcher > hickory-dns-localhost.
|
||||||
# (seemingly) impossible to remove the network.target dep on NetworkManager-dispatcher.
|
# (seemingly) impossible to remove the network.target dep on NetworkManager-dispatcher.
|
||||||
# before would be to have the dispatcher not write hickory-dns files
|
# beffore would be to have the dispatcher not write hickory-dns files
|
||||||
# but rather just its own, and create a .path unit which restarts hickory-dns appropriately.
|
# but rather just its own, and create a .path unit which restarts hickory-dns appropriately.
|
||||||
# after = [ "hickory-dns-localhost.service" ];
|
# after = [ "hickory-dns-localhost.service" ];
|
||||||
# serviceConfig.ExecStart = [
|
# serviceConfig.ExecStart = [
|
||||||
@@ -206,7 +216,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
networking.networkmanager.settings = {
|
networking.networkmanager.settings = {
|
||||||
# docs: `man 5 NetworkManager.conf`
|
|
||||||
# keyfile.path = where networkmanager should look for connection credentials
|
# keyfile.path = where networkmanager should look for connection credentials
|
||||||
keyfile.path = "/var/lib/NetworkManager/system-connections";
|
keyfile.path = "/var/lib/NetworkManager/system-connections";
|
||||||
|
|
||||||
@@ -218,30 +227,18 @@
|
|||||||
|
|
||||||
# main.dhcp = "internal"; #< default
|
# main.dhcp = "internal"; #< default
|
||||||
# main.dns controls what to do when NM gets a DNS server via DHCP
|
# main.dns controls what to do when NM gets a DNS server via DHCP
|
||||||
# - "default": NM manages /etc/resolv.conf itself.
|
# - "none" (populate /run/NetworkManager/resolv.conf with DHCP settings)
|
||||||
# - "none": NM doesn't manage /etc/resolv.conf, but does populate /run/NetworkManager/resolv.conf with DHCP settings
|
# - "internal" (?)
|
||||||
# - "systemd-resolved": tell systemd-resolved about it, and point /run/NetworkManager/resolv.conf -> systemd
|
# - "systemd-resolved" (tell systemd-resolved about it, and point /run/NetworkManager/resolv.conf -> systemd)
|
||||||
# - without this, systemd-resolved won't be able to resolve anything (because it has no upstream servers)
|
# without this, systemd-resolved won't be able to resolve anything (because it has no upstream servers)
|
||||||
# - (empty): perform a best-guess for how to manage /etc/resolv.conf
|
|
||||||
# -> if /etc/resolv.conf is a symlink to systemd-resolved, then behaves as "systemd-resolved".
|
|
||||||
# -> else, behaves as "default".
|
|
||||||
# note that NM's resolv.conf isn't (necessarily) /etc/resolv.conf -- that is managed by nixos (via symlinking)
|
# note that NM's resolv.conf isn't (necessarily) /etc/resolv.conf -- that is managed by nixos (via symlinking)
|
||||||
main.dns = let
|
main.dns = if config.services.resolved.enable then
|
||||||
dns = if config.services.resolved.enable then
|
|
||||||
"systemd-resolved"
|
"systemd-resolved"
|
||||||
else if
|
else if config.sane.services.hickory-dns.enable && config.sane.services.hickory-dns.asSystemResolver then
|
||||||
(config.sane.services.hickory-dns.enable && config.sane.services.hickory-dns.asSystemResolver)
|
|
||||||
|| (config.services.unbound.enable && config.services.unbound.resolveLocalQueries)
|
|
||||||
|| config.services.bind.enable # bind config isn't easily inspectable; assume that it's acting as local resolver
|
|
||||||
then
|
|
||||||
"none"
|
"none"
|
||||||
else
|
else
|
||||||
# omitting the option instructs NM to do a "best guess".
|
"internal"
|
||||||
# this is nearly equivalent to "default", however NM will do checks like "is /etc/resolv.conf a symlink to systemd-resolved", etc,
|
|
||||||
# to actually try to understand the environment.
|
|
||||||
null
|
|
||||||
;
|
;
|
||||||
in lib.mkIf (dns != null) dns;
|
|
||||||
main.systemd-resolved = false;
|
main.systemd-resolved = false;
|
||||||
};
|
};
|
||||||
environment.etc."NetworkManager/system-connections".source = "/var/lib/NetworkManager/system-connections";
|
environment.etc."NetworkManager/system-connections".source = "/var/lib/NetworkManager/system-connections";
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
# NTP and DNS/DNSSEC have a chicken-and-egg issue:
|
|
||||||
# - NTP needs to resolve DNS to know how to query the servers (`0.nixos.pool.ntp.org`, etc)
|
|
||||||
# - DNS needs to have a semi-accurate clock to validate DNSSEC for resolutions
|
|
||||||
#
|
|
||||||
# nixos and systemd-timesyncd overcome this in the default installation by:
|
|
||||||
# - setting `SYSTEMD_NSS_RESOLVE_VALIDATE=0` in the systemd-timesyncd.service unit file
|
|
||||||
# - systemd nss module which plumbs that to systemd-resolved
|
|
||||||
# that ONLY WORKS if using systemd-resolved.
|
|
||||||
#
|
|
||||||
# my alternative fix here is to hardcode a list of fallback NTP IP addresses, to use when DNS resolution of the primaries fails.
|
|
||||||
#
|
|
||||||
# lastly, the clock can be manually set:
|
|
||||||
# - `systemctl stop systemd-timesyncd`
|
|
||||||
# - `sudo timedatectl --adjust-system-clock set-time '2024-01-01 00:00:01 UTC'`
|
|
||||||
# - `systemctl start systemd-timesyncd`
|
|
||||||
#
|
|
||||||
# XXX(2024-12-03): i fixed the NTP-DNS circularity by exempting `pool.ntp.org` from DNSSEC validation in unbound conf
|
|
||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
# services.timesyncd.servers = config.networking.timeServers;
|
|
||||||
# services.timesyncd.fallbackServers = [
|
|
||||||
# "129.6.15.28" # time-a-g.nist.gov
|
|
||||||
# "132.163.97.1" # time-a-wwv.nist.gov
|
|
||||||
# "132.163.96.1" # time-a-b.nist.gov
|
|
||||||
# "128.138.140.44" # utcnist.colorado.edu
|
|
||||||
# "162.159.200.1" # time.cloudflare.com
|
|
||||||
# ];
|
|
||||||
|
|
||||||
# more feature-complete NTP implementations exist, like `chrony`, should i ever wish to also be a NTP **server**:
|
|
||||||
# services.chrony.enable = true;
|
|
||||||
# services.chrony.enableNTS = true;
|
|
||||||
}
|
|
@@ -7,8 +7,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
networking.firewall.extraCommands = with pkgs; ''
|
networking.firewall.extraCommands = with pkgs; ''
|
||||||
# after an outgoing SSDP query to the multicast address (dest port=1900, src port=any),
|
# after an outgoing SSDP query to the multicast address, open FW for incoming responses.
|
||||||
# open FW for incoming responses (i.e. accept any packet, so long as it's sent to the port we sent from).
|
|
||||||
# necessary for anything DLNA, especially go2tv
|
# necessary for anything DLNA, especially go2tv
|
||||||
# source: <https://serverfault.com/a/911286>
|
# source: <https://serverfault.com/a/911286>
|
||||||
# context: <https://github.com/alexballas/go2tv/issues/72>
|
# context: <https://github.com/alexballas/go2tv/issues/72>
|
||||||
@@ -17,10 +16,5 @@
|
|||||||
${ipset}/bin/ipset create -! upnp hash:ip,port timeout 10
|
${ipset}/bin/ipset create -! upnp hash:ip,port timeout 10
|
||||||
${iptables}/bin/iptables -A OUTPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j SET --add-set upnp src,src --exist
|
${iptables}/bin/iptables -A OUTPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j SET --add-set upnp src,src --exist
|
||||||
${iptables}/bin/iptables -A INPUT -p udp -m set --match-set upnp dst,dst -j ACCEPT
|
${iptables}/bin/iptables -A INPUT -p udp -m set --match-set upnp dst,dst -j ACCEPT
|
||||||
|
|
||||||
# IPv6 ruleset. ff02::/16 means *any* link-local multicast group (so this is probably more broad than it needs to be)
|
|
||||||
${ipset}/bin/ipset create -! upnp6 hash:ip,port timeout 10 family inet6
|
|
||||||
${iptables}/bin/ip6tables -A OUTPUT -d ff02::/16 -p udp -m udp --dport 1900 -j SET --add-set upnp6 src,src --exist
|
|
||||||
${iptables}/bin/ip6tables -A INPUT -p udp -m set --match-set upnp6 dst,dst -j ACCEPT
|
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
# - generate config @ OVPN.com
|
# - generate config @ OVPN.com
|
||||||
# - copy the Address, PublicKey, Endpoint from OVPN's config
|
# - copy the Address, PublicKey, Endpoint from OVPN's config
|
||||||
|
|
||||||
{ config, lib, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
let
|
let
|
||||||
# N.B.: OVPN issues each key (i.e. device) a different IP (addrV4), and requires you use it.
|
# N.B.: OVPN issues each key (i.e. device) a different IP (addrV4), and requires you use it.
|
||||||
# the IP it issues can be used to connect to any of their VPNs.
|
# the IP it issues can be used to connect to any of their VPNs.
|
||||||
|
@@ -55,17 +55,7 @@
|
|||||||
# this is actually a no-op, and the real action happens in assigning `nix.settings.nix-path`.
|
# this is actually a no-op, and the real action happens in assigning `nix.settings.nix-path`.
|
||||||
nix.nixPath = (lib.optionals (config.sane.maxBuildCost >= 2) [
|
nix.nixPath = (lib.optionals (config.sane.maxBuildCost >= 2) [
|
||||||
"nixpkgs=${pkgs.path}"
|
"nixpkgs=${pkgs.path}"
|
||||||
]) ++ (let
|
]) ++ [
|
||||||
# XXX(2024-09-02): nix 2.24.4 errors when nixpkgs-overlays includes a symlink component:
|
|
||||||
# "error: path '/home/colin/dev' is a symlink"
|
|
||||||
# apparently nix has to explicitly handle symlinks in every place it might encounter them,
|
|
||||||
# so the fixes inside nix for this are manual and fragile. dereference it ourselves:
|
|
||||||
dev = if (config.sane.fs."/home/colin/dev" or {}) != {} then
|
|
||||||
config.sane.fs."/home/colin/dev".symlink.target
|
|
||||||
else
|
|
||||||
"/home/colin/dev"
|
|
||||||
;
|
|
||||||
in [
|
|
||||||
# note the import starts at repo root: this allows `./overlay/default.nix` to access the stuff at the root
|
# note the import starts at repo root: this allows `./overlay/default.nix` to access the stuff at the root
|
||||||
# "nixpkgs-overlays=${../../..}/hosts/common/nix-path/overlay"
|
# "nixpkgs-overlays=${../../..}/hosts/common/nix-path/overlay"
|
||||||
# as long as my system itself doesn't rely on NIXPKGS at runtime, we can point the overlays to git
|
# as long as my system itself doesn't rely on NIXPKGS at runtime, we can point the overlays to git
|
||||||
@@ -74,9 +64,8 @@
|
|||||||
# it's an impurity that touches way more than i need and tends to cause hard-to-debug eval issues
|
# it's an impurity that touches way more than i need and tends to cause hard-to-debug eval issues
|
||||||
# when it goes wrong. should i port my `nix-shell` scripts to something more tailored to my uses
|
# when it goes wrong. should i port my `nix-shell` scripts to something more tailored to my uses
|
||||||
# and then delete `nixpkgs-overlays`?
|
# and then delete `nixpkgs-overlays`?
|
||||||
# "nixpkgs-overlays=/home/colin/dev/nixos/integrations/nixpkgs/nixpkgs-overlays.nix"
|
"nixpkgs-overlays=/home/colin/dev/nixos/integrations/nixpkgs/nixpkgs-overlays.nix"
|
||||||
"nixpkgs-overlays=${dev}/nixos/integrations/nixpkgs/nixpkgs-overlays.nix"
|
];
|
||||||
]);
|
|
||||||
|
|
||||||
# ensure new deployments have a source of this repo with which they can bootstrap.
|
# ensure new deployments have a source of this repo with which they can bootstrap.
|
||||||
# this however changes on every commit and can be slow to copy for e.g. `moby`.
|
# this however changes on every commit and can be slow to copy for e.g. `moby`.
|
||||||
|
@@ -6,15 +6,11 @@ let
|
|||||||
# nixpkgs' pam hardcodes unix_chkpwd path to the /run/wrappers one,
|
# nixpkgs' pam hardcodes unix_chkpwd path to the /run/wrappers one,
|
||||||
# but i don't want the wrapper, so undo that.
|
# but i don't want the wrapper, so undo that.
|
||||||
# ideally i would patch this via an overlay, but pam is in the bootstrap so that forces a full rebuild.
|
# ideally i would patch this via an overlay, but pam is in the bootstrap so that forces a full rebuild.
|
||||||
# see: <repo:nixos/nixpkgs:pkgs/by-name/li/linux-pam/package.nix>
|
postPatch = (if upstream.postPatch != null then upstream.postPatch else "") + ''
|
||||||
postPatch = (upstream.postPatch or "") + ''
|
substituteInPlace modules/pam_unix/Makefile.am --replace-fail \
|
||||||
substituteInPlace modules/module-meson.build --replace-fail \
|
|
||||||
"/run/wrappers/bin/unix_chkpwd" "$out/bin/unix_chkpwd"
|
"/run/wrappers/bin/unix_chkpwd" "$out/bin/unix_chkpwd"
|
||||||
'';
|
'';
|
||||||
});
|
});
|
||||||
# `mkDefault` is `mkOverride 1000`.
|
|
||||||
# `mkOverrideDefault` will override `mkDefault` values, but not ordinary values.
|
|
||||||
mkOverrideDefault = lib.mkOverride 900;
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# remove a few items from /run/wrappers we don't need.
|
# remove a few items from /run/wrappers we don't need.
|
||||||
@@ -63,7 +59,7 @@ in
|
|||||||
"userdel"
|
"userdel"
|
||||||
"usermod"
|
"usermod"
|
||||||
# from <repo:nixos/nixpkgs:nixos/modules/system/boot/systemd/user.nix>
|
# from <repo:nixos/nixpkgs:nixos/modules/system/boot/systemd/user.nix>
|
||||||
# "systemd-user" #< N.B.: this causes the `systemd --user` service manager to fail 224/PAM!
|
"systemd-user" #< N.B.: this causes the `systemd --user` service manager to not be started!
|
||||||
]));
|
]));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -110,7 +106,6 @@ in
|
|||||||
conveniencePackages = [
|
conveniencePackages = [
|
||||||
config.boot.kernelPackages.cpupower # <repo:nixos/nixpkgs:nixos/modules/tasks/cpu-freq.nix> places it on PATH for convenience if powerManagement.cpuFreqGovernor is set
|
config.boot.kernelPackages.cpupower # <repo:nixos/nixpkgs:nixos/modules/tasks/cpu-freq.nix> places it on PATH for convenience if powerManagement.cpuFreqGovernor is set
|
||||||
pkgs.kbd # <repo:nixos/nixpkgs:nixos/modules/config/console.nix> places it on PATH as part of console/virtual TTYs, but probably not needed unless you want to set console fonts
|
pkgs.kbd # <repo:nixos/nixpkgs:nixos/modules/config/console.nix> places it on PATH as part of console/virtual TTYs, but probably not needed unless you want to set console fonts
|
||||||
pkgs.nixos-firewall-tool # <repo:nixos/nixpkgs:nixos/modules/services/networking/firewall.nix> for end-user management of the firewall? cool but doesn't cross-compile
|
|
||||||
];
|
];
|
||||||
in lib.filter (p: ! builtins.elem p (requiredPackages ++ conveniencePackages));
|
in lib.filter (p: ! builtins.elem p (requiredPackages ++ conveniencePackages));
|
||||||
};
|
};
|
||||||
@@ -139,20 +134,11 @@ in
|
|||||||
environment.variables.NIXPKGS_CONFIG = lib.mkForce "";
|
environment.variables.NIXPKGS_CONFIG = lib.mkForce "";
|
||||||
# XDG_CONFIG_DIRS defaults to "/etc/xdg", which doesn't exist.
|
# XDG_CONFIG_DIRS defaults to "/etc/xdg", which doesn't exist.
|
||||||
# in practice, pam appends the values i want to XDG_CONFIG_DIRS, though this approach causes an extra leading `:`
|
# in practice, pam appends the values i want to XDG_CONFIG_DIRS, though this approach causes an extra leading `:`
|
||||||
# XXX(2025-06-06): some nixpkgs' systemd services actually depend on the default XDG_CONFIG_DIRS=/etc/xdg!
|
environment.sessionVariables.XDG_CONFIG_DIRS = lib.mkForce [];
|
||||||
# specifically: `services.bitmagnet`
|
|
||||||
# environment.sessionVariables.XDG_CONFIG_DIRS = lib.mkForce [];
|
|
||||||
# XCURSOR_PATH: defaults to `[ "$HOME/.icons" "$HOME/.local/share/icons" ]`, neither of which i use, just adding noise.
|
# XCURSOR_PATH: defaults to `[ "$HOME/.icons" "$HOME/.local/share/icons" ]`, neither of which i use, just adding noise.
|
||||||
# see: <repo:nixos/nixpkgs:nixos/modules/config/xdg/icons.nix>
|
# see: <repo:nixos/nixpkgs:nixos/modules/config/xdg/icons.nix>
|
||||||
environment.sessionVariables.XCURSOR_PATH = lib.mkForce [];
|
environment.sessionVariables.XCURSOR_PATH = lib.mkForce [];
|
||||||
|
|
||||||
environment.shellAliases = {
|
|
||||||
# unset default aliases; see <repo:nixos/nixpkgs:nixos/modules/config/shells-environment.nix>
|
|
||||||
ls = mkOverrideDefault null;
|
|
||||||
ll = mkOverrideDefault null;
|
|
||||||
l = mkOverrideDefault null;
|
|
||||||
};
|
|
||||||
|
|
||||||
# disable nixos' portal module, otherwise /share/applications gets linked into the system and complicates things (sandboxing).
|
# disable nixos' portal module, otherwise /share/applications gets linked into the system and complicates things (sandboxing).
|
||||||
# instead, i manage portals myself via the sane.programs API (e.g. sane.programs.xdg-desktop-portal).
|
# instead, i manage portals myself via the sane.programs API (e.g. sane.programs.xdg-desktop-portal).
|
||||||
xdg.portal.enable = false;
|
xdg.portal.enable = false;
|
||||||
@@ -165,6 +151,7 @@ in
|
|||||||
|
|
||||||
# nix.channel.enable: populates `/nix/var/nix/profiles/per-user/root/channels`, `/root/.nix-channels`, `$HOME/.nix-defexpr/channels`
|
# nix.channel.enable: populates `/nix/var/nix/profiles/per-user/root/channels`, `/root/.nix-channels`, `$HOME/.nix-defexpr/channels`
|
||||||
# <repo:nixos/nixpkgs:nixos/modules/config/nix-channel.nix>
|
# <repo:nixos/nixpkgs:nixos/modules/config/nix-channel.nix>
|
||||||
|
# TODO: may want to recreate NIX_PATH, nix.settings.nix-path
|
||||||
nix.channel.enable = false;
|
nix.channel.enable = false;
|
||||||
|
|
||||||
# environment.stub-ld: populate /lib/ld-linux.so with an object that unconditionally errors on launch,
|
# environment.stub-ld: populate /lib/ld-linux.so with an object that unconditionally errors on launch,
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
# Terminal UI mail client
|
# Terminal UI mail client
|
||||||
{ pkgs, ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
sane.programs.aerc = {
|
sane.programs.aerc = {
|
||||||
packageUnwrapped = pkgs.aerc.override {
|
sandbox.method = "bwrap";
|
||||||
withNotmuch = false; #< not used; regularly fails to build
|
sandbox.wrapperType = "inplace"; #< /share/aerc/aerc.conf refers to other /share files by absolute path
|
||||||
};
|
|
||||||
sandbox.wrapperType = "inplace"; #< /share/aerc/aerc.conf mentions (in comments) other (non-sandboxed) /share files by absolute path
|
|
||||||
sandbox.net = "clearnet";
|
sandbox.net = "clearnet";
|
||||||
secrets.".config/aerc/accounts.conf" = ../../../secrets/common/aerc_accounts.conf.bin;
|
secrets.".config/aerc/accounts.conf" = ../../../secrets/common/aerc_accounts.conf.bin;
|
||||||
mime.associations."x-scheme-handler/mailto" = "aerc.desktop";
|
mime.associations."x-scheme-handler/mailto" = "aerc.desktop";
|
||||||
|
@@ -26,9 +26,6 @@ in
|
|||||||
[font]
|
[font]
|
||||||
size = ${builtins.toString cfg.config.fontSize}
|
size = ${builtins.toString cfg.config.fontSize}
|
||||||
|
|
||||||
[cursor.style]
|
|
||||||
blinking = "Always"
|
|
||||||
|
|
||||||
[[keyboard.bindings]]
|
[[keyboard.bindings]]
|
||||||
mods = "Control"
|
mods = "Control"
|
||||||
key = "N"
|
key = "N"
|
||||||
|
@@ -1,63 +0,0 @@
|
|||||||
# alpaca: ollama llm client
|
|
||||||
# - super simple, easy UI
|
|
||||||
#
|
|
||||||
# shortcomings (as of 6.1.7, 2025-07-23):
|
|
||||||
# - doesn't seem to do any prompt tuning;
|
|
||||||
# inherits all the pathologies of the underlying model (e.g. makes up citations)
|
|
||||||
#
|
|
||||||
# it creates a config dir, `~/.config/com.jeffser.Alpaca`, but apparently empty
|
|
||||||
#
|
|
||||||
# TODO: configure ollama connection details statically
|
|
||||||
# - until then, on first run:
|
|
||||||
# - select the non-"managed" ollama option.
|
|
||||||
# - connect to http://10.0.10.22:11434
|
|
||||||
# TODO: update the nix package 6.1.7 -> 7.5.2
|
|
||||||
# - i.e. review <https://github.com/NixOS/nixpkgs/pull/420698>
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
sane.programs.alpaca = {
|
|
||||||
packageUnwrapped = (pkgs.alpaca.override {
|
|
||||||
# ollama is only added to `PATH`; since i'm using it via http, remove it here.
|
|
||||||
# fixes cross compilation & simplifies closure.
|
|
||||||
ollama = null;
|
|
||||||
python3Packages = pkgs.python3Packages // {
|
|
||||||
# XXX(2025-07-23): does not cross compile (markitdown -> pydub -> ... -> opencv)
|
|
||||||
markitdown = null;
|
|
||||||
};
|
|
||||||
}).overrideAttrs (upstream: {
|
|
||||||
postPatch = (upstream.postPatch or "") + ''
|
|
||||||
# for nulled dependencies (above), patch so the application only errors
|
|
||||||
# at runtime, on first attempted use.
|
|
||||||
substituteInPlace src/widgets/attachments.py \
|
|
||||||
--replace-fail 'from markitdown' '# from markitdown'
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
buildCost = 2; #< liable to break cross during updates; not important enough to block deploy over
|
|
||||||
|
|
||||||
sandbox.net = "all"; # maybe only needs wireguard, actually
|
|
||||||
sandbox.whitelistWayland = true;
|
|
||||||
sandbox.mesaCacheDir = ".cache/com.jeffser.Alpaca/mesa";
|
|
||||||
|
|
||||||
sandbox.whitelistDbus.user.own = [ "com.jeffser.Alpaca" ];
|
|
||||||
sandbox.whitelistPortal = [
|
|
||||||
"OpenURI"
|
|
||||||
];
|
|
||||||
sandbox.whitelistSendNotifications = true;
|
|
||||||
|
|
||||||
persist.byStore.ephemeral = [
|
|
||||||
".cache/com.jeffser.Alpaca" #< ?
|
|
||||||
];
|
|
||||||
|
|
||||||
persist.byStore.private = [
|
|
||||||
# alpaca.db: sqlite3 database with the following tables:
|
|
||||||
# - attachment
|
|
||||||
# - chat
|
|
||||||
# - instances
|
|
||||||
# - message
|
|
||||||
# - model_preferences
|
|
||||||
# - preferences
|
|
||||||
# - tool_parameters
|
|
||||||
".local/share/com.jeffser.Alpaca"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,30 +1,46 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
let
|
let
|
||||||
cfg = config.sane.programs.alsa-ucm-conf;
|
cfg = config.sane.programs.alsa-ucm-conf;
|
||||||
deprioritize = pkg: pkg.overrideAttrs (base: {
|
|
||||||
meta = (base.meta or {}) // {
|
|
||||||
# let the other alsa ucm packages override configs from this one
|
|
||||||
priority = ((base.meta or {}).priority or 10) + 20;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
alsa-ucm-latest = pkgs.alsa-ucm-conf.overrideAttrs (upstream: rec {
|
|
||||||
# XXX(2025-07-18): see <https://github.com/NixOS/nixpkgs/pull/414818>
|
|
||||||
version = "1.2.14";
|
|
||||||
src = lib.warnIf (lib.versionAtLeast upstream.version "1.2.14") "upstream alsa-ucm-conf is up to date with my own: remove override?" pkgs.fetchurl {
|
|
||||||
url = "mirror://alsa/lib/alsa-ucm-conf-${version}.tar.bz2";
|
|
||||||
hash = "sha256-MumAn1ktkrl4qhAy41KTwzuNDx7Edfk3Aiw+6aMGnCE=";
|
|
||||||
};
|
|
||||||
installPhase = lib.replaceStrings
|
|
||||||
[ ''for file in "''${files[@]}"'' ]
|
|
||||||
[ ''for file in ucm2/common/ctl/led.conf'' ]
|
|
||||||
upstream.installPhase
|
|
||||||
;
|
|
||||||
});
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
sane.programs.alsa-ucm-conf = {
|
sane.programs.alsa-ucm-conf = {
|
||||||
packageUnwrapped = deprioritize pkgs.alsa-ucm-conf;
|
configOption = with lib; mkOption {
|
||||||
# packageUnwrapped = deprioritize alsa-ucm-latest;
|
default = {};
|
||||||
|
type = types.submodule {
|
||||||
|
options.preferEarpiece = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# upstream alsa ships with PinePhone audio configs, but they don't actually produce sound.
|
||||||
|
# - still true as of 2024-05-26
|
||||||
|
# - see: <https://github.com/alsa-project/alsa-ucm-conf/pull/134>
|
||||||
|
#
|
||||||
|
# we can substitute working UCM conf in two ways:
|
||||||
|
# 1. nixpkgs' override for the `alsa-ucm-conf` package
|
||||||
|
# - that forces a rebuild of ~500 packages (including webkitgtk).
|
||||||
|
# 2. set ALSA_CONFIG_UCM2 = /path/to/ucm2 in the relevant places
|
||||||
|
# - e.g. pulsewire service.
|
||||||
|
# - easy to miss places, though.
|
||||||
|
#
|
||||||
|
# alsa-ucm-pinephone-manjaro (2024-05-26):
|
||||||
|
# - headphones work
|
||||||
|
# - "internal earpiece" works
|
||||||
|
# - "internal speaker" is silent (maybe hardware issue)
|
||||||
|
# - 3.5mm connection is flapping when playing to my car, which eventually breaks audio and requires restarting wireplumber
|
||||||
|
# packageUnwrapped = pkgs.alsa-ucm-pinephone-manjaro.override {
|
||||||
|
# inherit (cfg.config) preferEarpiece;
|
||||||
|
# };
|
||||||
|
# alsa-ucm-pinephone-pmos (2024-05-26):
|
||||||
|
# - headphones work
|
||||||
|
# - "internal earpiece" works
|
||||||
|
# - "internal speaker" is silent (maybe hardware issue)
|
||||||
|
packageUnwrapped = pkgs.alsa-ucm-pinephone-pmos.override {
|
||||||
|
inherit (cfg.config) preferEarpiece;
|
||||||
|
};
|
||||||
|
|
||||||
sandbox.enable = false; #< only provides $out/share/alsa
|
sandbox.enable = false; #< only provides $out/share/alsa
|
||||||
|
|
||||||
# alsa-lib package only looks in its $out/share/alsa to find runtime config data, by default.
|
# alsa-lib package only looks in its $out/share/alsa to find runtime config data, by default.
|
||||||
@@ -32,8 +48,6 @@ in
|
|||||||
# this is particularly needed by wireplumber;
|
# this is particularly needed by wireplumber;
|
||||||
# also *maybe* pipewire and pipewire-pulse.
|
# also *maybe* pipewire and pipewire-pulse.
|
||||||
# taken from <repo:nixos/mobile-nixos:modules/quirks/audio.nix>
|
# taken from <repo:nixos/mobile-nixos:modules/quirks/audio.nix>
|
||||||
# the other option is to `override` pkgs.alsa-ucm-conf,
|
|
||||||
# but that triggers 500+ rebuilds
|
|
||||||
env.ALSA_CONFIG_UCM2 = "/run/current-system/sw/share/alsa/ucm2";
|
env.ALSA_CONFIG_UCM2 = "/run/current-system/sw/share/alsa/ucm2";
|
||||||
|
|
||||||
enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
|
enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user