Compare commits
2 Commits
wip-swaync
...
wip/flake-
Author | SHA1 | Date | |
---|---|---|---|
05649d1a22 | |||
cc5d706c1b |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,5 +1,2 @@
|
||||
/keep
|
||||
result
|
||||
result-*
|
||||
/secrets/local.nix
|
||||
/working
|
||||
|
@@ -8,7 +8,7 @@ keys:
|
||||
- &host_servo age1tzlyex2z6t88tg9h82943e39shxhmqeyr7ywhlwpdjmyqsndv3qq27x0rf
|
||||
- &host_moby age18vq5ktwgeaysucvw9t67drqmg5zd5c5k3le34yqxckkfj7wqdqgsd4ejmt
|
||||
creation_rules:
|
||||
- path_regex: secrets/common*
|
||||
- path_regex: secrets/universal*
|
||||
key_groups:
|
||||
- age:
|
||||
- *user_desko_colin
|
||||
@@ -26,19 +26,19 @@ creation_rules:
|
||||
- *user_lappy_colin
|
||||
- *user_servo_colin
|
||||
- *host_servo
|
||||
- path_regex: secrets/desko*
|
||||
- path_regex: secrets/desko.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *user_desko_colin
|
||||
- *user_lappy_colin
|
||||
- *host_desko
|
||||
- path_regex: secrets/lappy*
|
||||
- path_regex: secrets/lappy.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *user_lappy_colin
|
||||
- *user_desko_colin
|
||||
- *host_lappy
|
||||
- path_regex: secrets/moby*
|
||||
- path_regex: secrets/moby.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *user_desko_colin
|
||||
|
123
README.md
123
README.md
@@ -1,123 +0,0 @@
|
||||
## What's Here
|
||||
|
||||
this is the top-level repo from which i configure/deploy all my NixOS machines:
|
||||
- desktop
|
||||
- laptop
|
||||
- server
|
||||
- mobile phone (Pinephone)
|
||||
|
||||
everything outside of <./hosts/> and <./secrets/> is intended for export, to be importable for use by 3rd parties.
|
||||
the only hard dependency for my exported pkgs/modules should be [nixpkgs][nixpkgs].
|
||||
building <./hosts/> will require [sops][sops].
|
||||
|
||||
you might specifically be interested in these files (elaborated further in #key-points-of-interest):
|
||||
- [`sxmo-utils-latest`](./pkgs/additional/sxmo-utils/default.nix)
|
||||
- [example SXMO deployment](./hosts/modules/gui/sxmo/default.nix)
|
||||
- [my implementation of impermanence](./modules/persist/default.nix)
|
||||
- my way of deploying dotfiles/configuring programs per-user:
|
||||
- <./modules/fs/default.nix>
|
||||
- <./modules/programs.nix>
|
||||
- <./modules/users.nix>
|
||||
|
||||
[nixpkgs]: https://github.com/NixOS/nixpkgs
|
||||
[sops]: https://github.com/Mic92/sops-nix
|
||||
[uninsane-org]: https://uninsane.org
|
||||
|
||||
## Using This Repo In Your Own Config
|
||||
|
||||
this should be a pretty "standard" flake. just reference it, and import either
|
||||
- `nixosModules.sane` (for the modules)
|
||||
- `overlays.pkgs` (for the packages)
|
||||
|
||||
or follow the instructions [here][NUR] to use it via the Nix User Repositories.
|
||||
|
||||
[NUR]: https://nur.nix-community.org/
|
||||
|
||||
## Layout
|
||||
- `doc/`
|
||||
- instructions for tasks i find myself doing semi-occasionally in this repo.
|
||||
- `hosts/`
|
||||
- the bulk of config which isn't factored with external use in mind.
|
||||
- that is, if you were to add this repo to a flake.nix for your own use,
|
||||
you won't likely be depending on anything in this directory.
|
||||
- `integrations/`
|
||||
- code intended for consumption by external tools (e.g. the Nix User Repos)
|
||||
- `modules/`
|
||||
- config which is gated behind `enable` flags, in similar style to nixpkgs'
|
||||
`nixos/` directory.
|
||||
- if you depend on this repo, it's most likely for something in this directory.
|
||||
- `nixpatches/`
|
||||
- literally, diffs i apply atop upstream nixpkgs before performing further eval.
|
||||
- `overlays/`
|
||||
- exposed via the `overlays` output in `flake.nix`.
|
||||
- predominantly a list of `callPackage` directives.
|
||||
- `pkgs/`
|
||||
- derivations for things not yet packaged in nixpkgs.
|
||||
- derivations for things from nixpkgs which i need to `override` for some reason.
|
||||
- inline code for wholly custom packages (e.g. `pkgs/additional/sane-scripts/` for CLI tools
|
||||
that are highly specific to my setup).
|
||||
- `scripts/`
|
||||
- scripts which aren't reachable on a deployed system, but may aid manual deployments
|
||||
- `secrets/`
|
||||
- encrypted keys, API tokens, anything which one or more of my machines needs
|
||||
read access to but shouldn't be world-readable.
|
||||
- not much to see here
|
||||
- `templates/`
|
||||
- exposed via the `templates` output in `flake.nix`.
|
||||
- used to instantiate short-lived environments.
|
||||
- used to auto-fill the boiler-plate portions of new packages.
|
||||
|
||||
|
||||
## Key Points of Interest
|
||||
|
||||
i.e. you might find value in using these in your own config:
|
||||
|
||||
- `modules/fs/`
|
||||
- use this to statically define leafs and nodes anywhere in the filesystem,
|
||||
not just inside `/nix/store`.
|
||||
- e.g. specify that `/var/www` should be:
|
||||
- owned by a specific user/group
|
||||
- set to a specific mode
|
||||
- symlinked to some other path
|
||||
- populated with some statically-defined data
|
||||
- populated according to some script
|
||||
- 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
|
||||
statically define `~/.config` files -- just with a different philosophy.
|
||||
- `modules/persist/`
|
||||
- my alternative to the Impermanence module.
|
||||
- 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
|
||||
and encrypt them to ephemeral keys so they're unreadable post shutdown/unmount.
|
||||
- `modules/programs.nix`
|
||||
- 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:
|
||||
- e.g. `/home/<user>/.mozilla/firefox` is persisted only for users who
|
||||
`sane.programs.firefox.enableFor.user."<user>" = true;`
|
||||
- `modules/users.nix`
|
||||
- convenience layer atop the above modules so that you can just write
|
||||
`fs.".config/git"` instead of `fs."/home/colin/.config/git"`
|
||||
|
||||
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
|
||||
|
||||
## Mirrors
|
||||
|
||||
this repo exists in a few known locations:
|
||||
- primary: <https://git.uninsane.org/colin/nix-files>
|
||||
- mirror: <https://github.com/nix-community/nur-combined/tree/master/repos/colinsane>
|
||||
|
||||
## Contact
|
||||
|
||||
if you want to contact me for questions, or collaborate to split something useful into a shared repo, etc,
|
||||
you can reach me via any method listed [here](https://uninsane.org/about).
|
||||
patches, for this repo or any other i host, will be warmly welcomed in any manner you see fit:
|
||||
`git send-email`, DM'ing the patch over Matrix/Lemmy/ActivityPub/etc, even a literal PR where you
|
||||
link me to your own clone.
|
113
TODO.md
113
TODO.md
@@ -1,113 +0,0 @@
|
||||
## BUGS
|
||||
- why i need to manually restart `wireguard-wg-ovpns` on servo periodically
|
||||
- else DNS fails
|
||||
- ringer (i.e. dino incoming call) doesn't prevent moby from sleeping
|
||||
|
||||
## REFACTORING:
|
||||
|
||||
### sops/secrets
|
||||
- attach secrets to the thing they're used by (sane.programs)
|
||||
- rework secrets to leverage `sane.fs`
|
||||
- remove sops activation script as it's covered by my systemd sane.fs impl
|
||||
|
||||
### roles
|
||||
- allow any host to take the role of `uninsane.org`
|
||||
- will make it easier to test new services?
|
||||
|
||||
### upstreaming
|
||||
- split out a sxmo module usable by NUR consumers
|
||||
- bump nodejs version in lemmy-ui
|
||||
- add updateScripts to all my packages in nixpkgs
|
||||
- fix lightdm-mobile-greeter for newer libhandy
|
||||
- port zecwallet-lite to a from-source build
|
||||
- REVIEW/integrate jellyfin dataDir config: <https://github.com/NixOS/nixpkgs/pull/233617>
|
||||
- remove `libsForQt5.callPackage` broadly: <https://github.com/NixOS/nixpkgs/issues/180841>
|
||||
|
||||
#### upstreaming to non-nixpkgs repos
|
||||
- gtk: build schemas even on cross compilation: <https://github.com/NixOS/nixpkgs/pull/247844>
|
||||
- sxmo: add new app entries
|
||||
|
||||
|
||||
## IMPROVEMENTS:
|
||||
### security/resilience
|
||||
- matrix/ntfy: automatically add the ntfy.uninsane.org push URL as part of synapse launch
|
||||
- ntfy: use a more secure topic
|
||||
- validate duplicity backups!
|
||||
- encrypt more ~ dirs (~/archives, ~/records, ..?)
|
||||
- best to do this after i know for sure i have good backups
|
||||
- have `sane.programs` be wrapped such that they run in a cgroup?
|
||||
- at least, only give them access to the portion of the fs they *need*.
|
||||
- Android takes approach of giving each app its own user: could hack that in here.
|
||||
- **systemd-run** takes a command and runs it in a temporary scope (cgroup)
|
||||
- presumably uses the same options as systemd services
|
||||
- see e.g. <https://github.com/NixOS/nixpkgs/issues/113903#issuecomment-857296349>
|
||||
- flatpak does this, somehow
|
||||
- apparmor? SElinux? (desktop) "portals"?
|
||||
- see Spectrum OS; Alyssa Ross; etc
|
||||
- bubblewrap-based sandboxing: <https://github.com/nixpak/nixpak>
|
||||
- canaries for important services
|
||||
- e.g. daily email checks; daily backup checks
|
||||
- integrate `nix check` into Gitea actions?
|
||||
|
||||
### user experience
|
||||
#### moby
|
||||
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
|
||||
- install apps:
|
||||
- display QR codes for WiFi endpoints: <https://linuxphoneapps.org/apps/noappid.wisperwind.wifi2qr/>
|
||||
- shopping list: <https://linuxphoneapps.org/apps/ro.hume.cosmin.shoppinglist/>
|
||||
- offline Wikipedia
|
||||
- SwayNC:
|
||||
- don't show MPRIS if no players detected
|
||||
- this is a problem of playerctld, i guess
|
||||
- add option to change audio output
|
||||
- fix colors (red alert) to match overall theme
|
||||
- extend width to 100% of portrait mode
|
||||
- moby: tune GPS
|
||||
- run only geoclue, and not gpsd, to save power?
|
||||
- tune QGPS setting in eg25-control, for less jitter?
|
||||
- direct mepo to prefer gpsd, with fallback to geoclue, for better accuracy?
|
||||
- configure geoclue to do some smoothing?
|
||||
- manually do smoothing, as some layer between mepo and geoclue/gpsd?
|
||||
- moby: show battery state on ssh login
|
||||
- moby: improve gPodder launch time
|
||||
- sxmo: port to swaybar like i use on desktop
|
||||
- users in #sxmo claim it's way better perf
|
||||
- sxmo: fix youtube scripts (package youtube-cli)
|
||||
- moby: theme GTK apps (i.e. non-adwaita styles)
|
||||
- combine multiple icon themes to get one which has the full icon set?
|
||||
- get adwaita-icon-theme to ship everything even when cross-compiled?
|
||||
- especially, make the menubar collapsible
|
||||
- try Gradience tool specifically for theming adwaita? <https://linuxphoneapps.org/apps/com.github.gradienceteam.gradience/>
|
||||
- phog: remove the gnome-shell runtime dependency to save hella closure size
|
||||
|
||||
#### non-moby
|
||||
- neovim: set up language server (lsp; rnix-lsp; nvim-lspconfig)
|
||||
- Helix: make copy-to-system clipboard be the default
|
||||
- firefox/librewolf: persist history
|
||||
- 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)
|
||||
- sane-bt-search: show details like 5.1 vs stereo, h264 vs h265
|
||||
- maybe just color these "keywords" in all search results?
|
||||
- uninsane.org: make URLs relative to allow local use (and as offline homepage)
|
||||
- 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
|
||||
- could change junk filter from "no DKIM success" to explicit "DKIM failed"
|
||||
|
||||
### 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!
|
||||
- get moby to build without binfmt emulation (i.e. make all emulation explicit)
|
||||
- then i can distribute builds across servo + desko, and also allow servo to pull packages from desko w/o worrying about purity
|
||||
|
||||
|
||||
## NEW FEATURES:
|
||||
- migrate MAME cabinet to nix
|
||||
- boot it from PXE from servo?
|
||||
- deploy to new server, and use it as a remote builder
|
||||
- enable IPv6
|
||||
- package lemonade lemmy app: <https://linuxphoneapps.org/apps/ml.mdwalters.lemonade/>
|
@@ -1,9 +0,0 @@
|
||||
# limited, non-flake interface to this repo.
|
||||
# this file exposes the same view into `pkgs` which the flake would see when evaluated.
|
||||
#
|
||||
# the primary purpose of this file is so i can run `updateScript`s which expect
|
||||
# the root to be `default.nix`
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
pkgs.appendOverlays [
|
||||
(import ./overlays/all.nix)
|
||||
]
|
@@ -1,13 +0,0 @@
|
||||
to ship `pkgs.foo` on some host, either:
|
||||
- add it as an entry in `suggestedPrograms` to the appropriate category in `hosts/common/programs/assorted.nix`, or
|
||||
- `sane.programs.foo.enableFor.user.colin = true` in `hosts/by-name/myhost/default.nix`
|
||||
|
||||
if the program needs customization (persistence, configs, secrets):
|
||||
- add a file for it at `hosts/common/programs/<foo>.nix`
|
||||
- set the options, `sane.programs.foo.{fs,persist}`
|
||||
|
||||
if it's unclear what fs paths a program uses:
|
||||
- run one of these commands, launch the program, run it again, and `diff`:
|
||||
- `du -x --apparent-size ~`
|
||||
- `find ~ -xdev`
|
||||
- or, inspect the whole tmpfs root with `ncdu -x /`
|
123
flake.lock
generated
123
flake.lock
generated
@@ -1,15 +1,12 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1687709756,
|
||||
"narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -18,46 +15,100 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1667907331,
|
||||
"narHash": "sha256-bHkAwkYlBjkupPUFcQjimNS8gxWSWjOTevEuwdnp5m0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "6639e3a837fc5deb6f99554072789724997bc8e5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "release-22.05",
|
||||
"repo": "home-manager",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"mobile-nixos": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1694749521,
|
||||
"narHash": "sha256-MiVokKlpcJmfoGuWAMeW1En7gZ5hk0rCQArYm6P9XCc=",
|
||||
"lastModified": 1670131242,
|
||||
"narHash": "sha256-T/o1/3gffr010fsqgNshs1NJJjsnUYvQnUZgm6hilsY=",
|
||||
"owner": "nixos",
|
||||
"repo": "mobile-nixos",
|
||||
"rev": "d25d3b87e7f300d8066e31d792337d9cd7ecd23b",
|
||||
"rev": "5ee45cc1f8e43f4af14ee17ccef9156b0db8cd77",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "d25d3b87e7f300d8066e31d792337d9cd7ecd23b",
|
||||
"repo": "mobile-nixos",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs-unpatched"
|
||||
],
|
||||
"patches": []
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1,
|
||||
"narHash": "sha256-d3XSehPFkNwvwlOYy7gch0NLxOgdXuV7j5r/Qsn7kHc=",
|
||||
"path": "nixpatches",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"path": "nixpatches",
|
||||
"type": "path"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1698544399,
|
||||
"narHash": "sha256-vhRmPyEyoPkrXF2iykBsWHA05MIaOSmMRLMF7Hul6+s=",
|
||||
"lastModified": 1673704454,
|
||||
"narHash": "sha256-5Wdj1MgdOgn3+dMFIBtg+IAYZApjF8JzwLWDPieg0C4=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a83ed85c14fcf242653df6f4b0974b7e1c73c6c6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-22.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable_2": {
|
||||
"locked": {
|
||||
"lastModified": 1673740915,
|
||||
"narHash": "sha256-MMH8zONfqahgHly3K8/A++X34800rajA/XgZ2DzNL/M=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d87c5d8c41c9b3b39592563242f3a448b5cc4bc9",
|
||||
"rev": "7c65528c3f8462b902e09d1ccca23bb9034665c2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "release-23.05",
|
||||
"ref": "release-22.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1698611440,
|
||||
"narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=",
|
||||
"lastModified": 1673631141,
|
||||
"narHash": "sha256-AprpYQ5JvLS4wQG/ghm2UriZ9QZXvAwh1HlgA/6ZEVQ=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735",
|
||||
"rev": "befc83905c965adfd33e5cae49acb0351f6e0404",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -69,7 +120,10 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"home-manager": "home-manager",
|
||||
"mobile-nixos": "mobile-nixos",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-stable": "nixpkgs-stable",
|
||||
"nixpkgs-unpatched": "nixpkgs-unpatched",
|
||||
"sops-nix": "sops-nix",
|
||||
"uninsane-dot-org": "uninsane-dot-org"
|
||||
@@ -78,16 +132,16 @@
|
||||
"sops-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs-unpatched"
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
"nixpkgs-stable": "nixpkgs-stable_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1698548647,
|
||||
"narHash": "sha256-7c03OjBGqnwDW0FBaBc+NjfEBxMkza+dxZGJPyIzfFE=",
|
||||
"lastModified": 1673752321,
|
||||
"narHash": "sha256-EFfXY1ZHJq4FNaNQA9x0djtu/jiOhBbT0Xi+BT06cJw=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "632c3161a6cc24142c8e3f5529f5d81042571165",
|
||||
"rev": "e18eefd2b133a58309475298052c341c08470717",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -96,34 +150,19 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"uninsane-dot-org": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"nixpkgs-unpatched"
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1698634059,
|
||||
"narHash": "sha256-+Oyv6vDyCtBzab/5cTG0nUrHD9gj7KgGfD4D1Rn4fCk=",
|
||||
"lastModified": 1666870107,
|
||||
"narHash": "sha256-b9eXZxSwhzdJI5uQgfrMhu4SY2POrPkinUg7F5gQVYo=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "2419750ca98fc04af42c91e50c49a29c68d465d2",
|
||||
"revCount": 210,
|
||||
"rev": "80c6ec95bd430e29d231cf745f19279bb76fb382",
|
||||
"revCount": 164,
|
||||
"type": "git",
|
||||
"url": "https://git.uninsane.org/colin/uninsane"
|
||||
},
|
||||
|
446
flake.nix
446
flake.nix
@@ -4,8 +4,6 @@
|
||||
# - this is marginally the case with schemes like `github:nixos/nixpkgs`.
|
||||
# - given the *existing* `git+https://` scheme, i propose expressing github URLs similarly:
|
||||
# - `github+https://github.com/nixos/nixpkgs/tree/nixos-22.11`
|
||||
# - this would allow for the same optimizations as today's `github:nixos/nixpkgs`, but without obscuring the source.
|
||||
# a code reader could view the source being referenced simply by clicking the https:// portion of that URI.
|
||||
# - need some way to apply local patches to inputs.
|
||||
#
|
||||
#
|
||||
@@ -14,160 +12,96 @@
|
||||
# - Flake RFC: <https://github.com/tweag/rfcs/blob/flakes/rfcs/0049-flakes.md>
|
||||
# - Discussion: <https://github.com/NixOS/rfcs/pull/49>
|
||||
# - <https://serokell.io/blog/practical-nix-flakes>
|
||||
#
|
||||
#
|
||||
# COMMON OPERATIONS:
|
||||
# - update a specific flake input:
|
||||
# - `nix flake lock --update-input nixpkgs`
|
||||
|
||||
{
|
||||
# XXX: use the `github:` scheme instead of the more readable git+https: because it's *way* more efficient
|
||||
# preferably, i would rewrite the human-readable https URLs to nix-specific github: URLs with a helper,
|
||||
# but `inputs` is required to be a strict attrset: not an expression.
|
||||
inputs = {
|
||||
# branch workflow:
|
||||
# - daily:
|
||||
# - nixos-unstable cut from master after enough packages have been built in caches.
|
||||
# - every 6 hours:
|
||||
# - master auto-merged into staging.
|
||||
# - staging-next auto-merged into staging.
|
||||
# - manually, approximately once per month:
|
||||
# - staging-next is cut from staging.
|
||||
# - staging-next merged into master.
|
||||
#
|
||||
# which branch to source from?
|
||||
# - for everyday development, prefer `nixos-unstable` branch, as it provides good caching.
|
||||
# - if need to test bleeding updates (e.g. if submitting code into staging):
|
||||
# - use `staging-next` if it's been cut (i.e. if there's an active staging-next -> master PR)
|
||||
# - use `staging` if no staging-next branch has been cut.
|
||||
#
|
||||
# <https://github.com/nixos/nixpkgs/tree/nixos-22.11>
|
||||
nixpkgs-stable.url = "github:nixos/nixpkgs?ref=nixos-22.11";
|
||||
|
||||
# <https://github.com/nixos/nixpkgs/tree/nixos-unstable>
|
||||
nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=staging-next";
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=staging";
|
||||
|
||||
nixpkgs = {
|
||||
url = "path:nixpatches";
|
||||
inputs.nixpkgs.follows = "nixpkgs-unpatched";
|
||||
# XXX: `path:` urls have poor UX in that they still get "locked" and require manual updates as if they were remote.
|
||||
# by linking back to ourselves here, we can update `nixpatches/list.nix` *without* having to run `nix flake update` afterward.
|
||||
inputs.patches.follows = "";
|
||||
};
|
||||
mobile-nixos = {
|
||||
# <https://github.com/nixos/mobile-nixos>
|
||||
# only used for building disk images, not relevant after deployment
|
||||
# TODO: replace with something else. commit `0f3ac0bef1aea70254a3bae35e3cc2561623f4c1`
|
||||
# replaces the imageBuilder with a "new implementation from celun" and wildly breaks my use.
|
||||
# pinning to d25d3b... is equivalent to holding at 2023-09-15
|
||||
url = "github:nixos/mobile-nixos?ref=d25d3b87e7f300d8066e31d792337d9cd7ecd23b";
|
||||
url = "github:nixos/mobile-nixos";
|
||||
flake = false;
|
||||
};
|
||||
home-manager = {
|
||||
# <https://github.com/nix-community/home-manager/tree/release-22.05>
|
||||
url = "github:nix-community/home-manager?ref=release-22.05";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
sops-nix = {
|
||||
# <https://github.com/Mic92/sops-nix>
|
||||
# used to distribute secrets to my hosts
|
||||
url = "github:Mic92/sops-nix";
|
||||
# inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.nixpkgs.follows = "nixpkgs-unpatched";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
uninsane-dot-org = {
|
||||
# provides the package to deploy <https://uninsane.org>, used only when building the servo host
|
||||
url = "git+https://git.uninsane.org/colin/uninsane";
|
||||
# inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.nixpkgs.follows = "nixpkgs-unpatched";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
nixpkgs-stable,
|
||||
nixpkgs-unpatched,
|
||||
mobile-nixos,
|
||||
home-manager,
|
||||
sops-nix,
|
||||
uninsane-dot-org,
|
||||
...
|
||||
}@inputs:
|
||||
uninsane-dot-org
|
||||
}:
|
||||
let
|
||||
inherit (builtins) attrNames elem listToAttrs map mapAttrs;
|
||||
# redefine some nixpkgs `lib` functions to avoid the infinite recursion
|
||||
# of if we tried to use patched `nixpkgs.lib` as part of the patching process.
|
||||
mapAttrs' = f: set:
|
||||
listToAttrs (map (attr: f attr set.${attr}) (attrNames set));
|
||||
optionalAttrs = cond: attrs: if cond then attrs else {};
|
||||
# mapAttrs but without the `name` argument
|
||||
mapAttrValues = f: mapAttrs (_: f);
|
||||
nixpkgsCompiledBy = local: nixpkgs.legacyPackages."${local}";
|
||||
|
||||
# rather than apply our nixpkgs patches as a flake input, do that here instead.
|
||||
# this (temporarily?) resolves the bad UX wherein a subflake residing in the same git
|
||||
# repo as the main flake causes the main flake to have an unstable hash.
|
||||
nixpkgs = (import ./nixpatches/flake.nix).outputs {
|
||||
self = nixpkgs;
|
||||
nixpkgs = nixpkgs-unpatched;
|
||||
} // {
|
||||
# provide values that nixpkgs ordinarily sources from the flake.lock file,
|
||||
# inaccessible to it here because of the import-from-derivation.
|
||||
# rev and shortRev seem to not always exist (e.g. if the working tree is dirty),
|
||||
# so those are made conditional.
|
||||
#
|
||||
# these values impact the name of a produced nixos system. having date/rev in the
|
||||
# `readlink /run/current-system` store path helps debuggability.
|
||||
inherit (self) lastModifiedDate lastModified;
|
||||
} // optionalAttrs (self ? rev) {
|
||||
inherit (self) rev;
|
||||
} // optionalAttrs (self ? shortRev) {
|
||||
inherit (self) shortRev;
|
||||
};
|
||||
|
||||
nixpkgsCompiledBy = system: nixpkgs.legacyPackages."${system}";
|
||||
|
||||
evalHost = { name, local, target }: nixpkgs.lib.nixosSystem {
|
||||
evalHost = { name, local, target }:
|
||||
let
|
||||
# XXX: we'd prefer to use `nixosSystem = (nixpkgsCompiledBy target).nixos`
|
||||
# but it doesn't propagate config to the underlying pkgs, meaning it doesn't let you use
|
||||
# non-free packages even after setting nixpkgs.allowUnfree.
|
||||
# XXX: patch using the target -- not local -- otherwise the target will
|
||||
# need to emulate the host in order to rebuild!
|
||||
nixosSystem = import ((nixpkgsCompiledBy target).path + "/nixos/lib/eval-config.nix");
|
||||
in
|
||||
(nixosSystem {
|
||||
# we use pkgs built for and *by* the target, i.e. emulation, by default.
|
||||
# cross compilation only happens on explicit access to `pkgs.cross`
|
||||
system = target;
|
||||
modules = [
|
||||
{
|
||||
nixpkgs = (if (local != null) then {
|
||||
buildPlatform = local;
|
||||
} else {}) // {
|
||||
# TODO: does the earlier `system` arg to nixosSystem make its way here?
|
||||
hostPlatform.system = target;
|
||||
};
|
||||
# nixpkgs.buildPlatform = local; # set by instantiate.nix instead
|
||||
# nixpkgs.config.replaceStdenv = { pkgs }: pkgs.ccacheStdenv;
|
||||
}
|
||||
(import ./hosts/instantiate.nix { hostName = name; })
|
||||
(import ./hosts/instantiate.nix { localSystem = local; hostName = name; })
|
||||
self.nixosModules.default
|
||||
self.nixosModules.passthru
|
||||
{
|
||||
nixpkgs.overlays = [
|
||||
self.overlays.default
|
||||
self.overlays.passthru
|
||||
self.overlays.sane-all
|
||||
self.overlays.pins
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
});
|
||||
in {
|
||||
nixosConfigurations =
|
||||
let
|
||||
hosts = {
|
||||
servo = { name = "servo"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
desko = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
lappy = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
moby = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; };
|
||||
rescue = { name = "rescue"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
};
|
||||
# cross-compiled builds: instead of emulating the host, build using a cross-compiler.
|
||||
# - these are faster to *build* than the emulated variants (useful when tweaking packages),
|
||||
# - but fewer of their packages can be found in upstream caches.
|
||||
cross = mapAttrValues evalHost hosts;
|
||||
emulated = mapAttrValues
|
||||
({name, local, target}: evalHost {
|
||||
inherit name target;
|
||||
local = null;
|
||||
})
|
||||
hosts;
|
||||
prefixAttrs = prefix: attrs: mapAttrs'
|
||||
(name: value: {
|
||||
name = prefix + name;
|
||||
inherit value;
|
||||
})
|
||||
attrs;
|
||||
in
|
||||
(prefixAttrs "cross-" cross) //
|
||||
(prefixAttrs "emulated-" emulated) // {
|
||||
# prefer native builds for these machines:
|
||||
inherit (emulated) servo desko lappy rescue;
|
||||
# prefer cross-compiled builds for these machines:
|
||||
inherit (cross) moby;
|
||||
nixosConfigurations = {
|
||||
servo = evalHost { name = "servo"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
desko = evalHost { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
lappy = evalHost { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
moby = evalHost { name = "moby"; local = "aarch64-linux"; target = "aarch64-linux"; };
|
||||
# special cross-compiled variant, to speed up deploys from an x86 box to the arm target
|
||||
# note that these *do* produce different store paths, because the closure for the tools used to cross compile
|
||||
# v.s. emulate differ.
|
||||
# so deploying foo-cross and then foo incurs some rebuilding.
|
||||
moby-cross = evalHost { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; };
|
||||
rescue = evalHost { name = "rescue"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
};
|
||||
|
||||
# unofficial output
|
||||
@@ -184,30 +118,22 @@
|
||||
# - if fs wasn't resized automatically, then `sudo btrfs filesystem resize max /`
|
||||
# - checkout this flake into /etc/nixos AND UPDATE THE FS UUIDS.
|
||||
# - `nixos-rebuild --flake './#<host>' switch`
|
||||
imgs = mapAttrValues (host: host.config.system.build.img) self.nixosConfigurations;
|
||||
imgs = builtins.mapAttrs (_: host-dfn: host-dfn.config.system.build.img) self.nixosConfigurations;
|
||||
|
||||
# unofficial output
|
||||
hostPkgs = mapAttrValues (host: host.config.system.build.pkgs) self.nixosConfigurations;
|
||||
hostPrograms = mapAttrValues (host: mapAttrValues (p: p.package) host.config.sane.programs) self.nixosConfigurations;
|
||||
|
||||
overlays = {
|
||||
# N.B.: `nix flake check` requires every overlay to take `final: prev:` at defn site,
|
||||
# hence the weird redundancy.
|
||||
default = final: prev: self.overlays.pkgs final prev;
|
||||
sane-all = final: prev: import ./overlays/all.nix final prev;
|
||||
disable-flakey-tests = final: prev: import ./overlays/disable-flakey-tests.nix final prev;
|
||||
pkgs = final: prev: import ./overlays/pkgs.nix final prev;
|
||||
pins = final: prev: import ./overlays/pins.nix final prev;
|
||||
preferences = final: prev: import ./overlays/preferences.nix final prev;
|
||||
optimizations = final: prev: import ./overlays/optimizations.nix final prev;
|
||||
passthru = final: prev:
|
||||
overlays = rec {
|
||||
default = pkgs;
|
||||
pkgs = import ./overlays/pkgs.nix;
|
||||
pins = import ./overlays/pins.nix; # TODO: move to `nixpatches/` input
|
||||
passthru =
|
||||
let
|
||||
stable = next: prev: {
|
||||
stable = nixpkgs-stable.legacyPackages."${prev.stdenv.hostPlatform.system}";
|
||||
};
|
||||
mobile = (import "${mobile-nixos}/overlay/overlay.nix");
|
||||
uninsane = uninsane-dot-org.overlay;
|
||||
in
|
||||
(mobile final prev)
|
||||
// (uninsane final prev)
|
||||
;
|
||||
next: prev:
|
||||
(stable next prev) // (mobile next prev) // (uninsane next prev);
|
||||
};
|
||||
|
||||
nixosModules = rec {
|
||||
@@ -215,6 +141,7 @@
|
||||
sane = import ./modules;
|
||||
passthru = { ... }: {
|
||||
imports = [
|
||||
home-manager.nixosModule
|
||||
sops-nix.nixosModules.sops
|
||||
];
|
||||
};
|
||||
@@ -231,260 +158,39 @@
|
||||
aarch64-linux = allPkgsFor "aarch64-linux";
|
||||
};
|
||||
|
||||
# extract only our own packages from the full set.
|
||||
# because of `nix flake check`, we flatten the package set and only surface x86_64-linux packages.
|
||||
packages = mapAttrs
|
||||
(system: allPkgs:
|
||||
allPkgs.lib.filterAttrs (name: pkg:
|
||||
# keep only packages which will pass `nix flake check`, i.e. keep only:
|
||||
# - derivations (not package sets)
|
||||
# - packages that build for the given platform
|
||||
(! elem name [ "feeds" "pythonPackagesExtensions" ])
|
||||
&& (allPkgs.lib.meta.availableOn allPkgs.stdenv.hostPlatform pkg)
|
||||
)
|
||||
(
|
||||
# expose sane packages and chosen inputs (uninsane.org)
|
||||
(import ./pkgs { pkgs = allPkgs; }) // {
|
||||
inherit (allPkgs) uninsane-dot-org;
|
||||
}
|
||||
)
|
||||
)
|
||||
# self.legacyPackages;
|
||||
{ inherit (self.legacyPackages) x86_64-linux; }
|
||||
;
|
||||
# extract only our own packages from the full set
|
||||
packages = builtins.mapAttrs
|
||||
(_: full: full.sane // { inherit (full) sane uninsane-dot-org; })
|
||||
self.legacyPackages;
|
||||
|
||||
apps."x86_64-linux" =
|
||||
let
|
||||
pkgs = self.legacyPackages."x86_64-linux";
|
||||
sanePkgs = import ./pkgs { inherit pkgs; };
|
||||
deployScript = host: addr: action: pkgs.writeShellScript "deploy-${host}" ''
|
||||
nix build '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} $@
|
||||
sudo nix sign-paths -r -k /run/secrets/nix_serve_privkey $(readlink ./result-${host})
|
||||
|
||||
# XXX: this triggers another config eval & (potentially) build.
|
||||
# if the config changed between these invocations, the above signatures might not apply to the deployed config.
|
||||
# let the user handle that edge case by re-running this whole command
|
||||
nixos-rebuild --flake '.#${host}' ${action} --target-host colin@${addr} --use-remote-sudo $@
|
||||
'';
|
||||
|
||||
# pkg updating.
|
||||
# a cleaner alternative lives here: <https://discourse.nixos.org/t/how-can-i-run-the-updatescript-of-personal-packages/25274/2>
|
||||
mkUpdater = attrPath: {
|
||||
type = "app";
|
||||
program = let
|
||||
pkg = pkgs.lib.getAttrFromPath attrPath sanePkgs;
|
||||
strAttrPath = pkgs.lib.concatStringsSep "." attrPath;
|
||||
commandArgv = pkg.updateScript.command or pkg.updateScript;
|
||||
command = pkgs.lib.escapeShellArgs commandArgv;
|
||||
in builtins.toString (pkgs.writeShellScript "update-${strAttrPath}" ''
|
||||
export UPDATE_NIX_NAME=${pkg.name}
|
||||
export UPDATE_NIX_PNAME=${pkg.pname}
|
||||
export UPDATE_NIX_OLD_VERSION=${pkg.version}
|
||||
export UPDATE_NIX_ATTR_PATH=${strAttrPath}
|
||||
${command}
|
||||
'');
|
||||
};
|
||||
mkUpdatersNoAliases = opts: basePath: pkgs.lib.concatMapAttrs
|
||||
(name: pkg:
|
||||
if pkg.recurseForDerivations or false then {
|
||||
"${name}" = mkUpdaters opts (basePath ++ [ name ]);
|
||||
} else if pkg.updateScript or null != null then {
|
||||
"${name}" = mkUpdater (basePath ++ [ name ]);
|
||||
} else {}
|
||||
)
|
||||
(pkgs.lib.getAttrFromPath basePath sanePkgs);
|
||||
mkUpdaters = { ignore ? [] }@opts: basePath:
|
||||
let
|
||||
updaters = mkUpdatersNoAliases opts basePath;
|
||||
invokeUpdater = name: pkg:
|
||||
let
|
||||
fullPath = basePath ++ [ name ];
|
||||
doUpdateByDefault = !builtins.elem fullPath ignore;
|
||||
|
||||
# in case `name` has a `.` in it, we have to quote it
|
||||
escapedPath = builtins.map (p: ''"${p}"'') fullPath;
|
||||
updatePath = builtins.concatStringsSep "." ([ "update" "pkgs" ] ++ escapedPath);
|
||||
in pkgs.lib.optionalString doUpdateByDefault (
|
||||
pkgs.lib.escapeShellArgs [
|
||||
"nix" "run" ".#${updatePath}"
|
||||
]
|
||||
);
|
||||
in {
|
||||
update-feeds = {
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript
|
||||
(builtins.concatStringsSep "-" (["update"] ++ basePath))
|
||||
(builtins.concatStringsSep
|
||||
"\n"
|
||||
(pkgs.lib.mapAttrsToList invokeUpdater updaters)
|
||||
)
|
||||
);
|
||||
} // updaters;
|
||||
in {
|
||||
help = {
|
||||
type = "app";
|
||||
program = let
|
||||
helpMsg = builtins.toFile "nixos-config-help-message" ''
|
||||
commands:
|
||||
- `nix run '.#help'`
|
||||
- show this message
|
||||
- `nix run '.#update.pkgs'`
|
||||
- updates every package
|
||||
- `nix run '.#update.feeds'`
|
||||
- updates metadata for all feeds
|
||||
- `nix run '.#init-feed' <url>`
|
||||
- `nix run '.#deploy-{lappy,moby,moby-test,servo}' [nixos-rebuild args ...]`
|
||||
- `nix run '.#check'`
|
||||
- make sure all systems build; NUR evaluates
|
||||
|
||||
specific build targets of interest:
|
||||
- `nix build '.#imgs.rescue'`
|
||||
'';
|
||||
in builtins.toString (pkgs.writeShellScript "nixos-config-help" ''
|
||||
cat ${helpMsg}
|
||||
echo ""
|
||||
echo "complete flake structure:"
|
||||
nix flake show --option allow-import-from-derivation true
|
||||
'');
|
||||
program = "${pkgs.feeds.passthru.updateScript}";
|
||||
};
|
||||
update.pkgs = mkUpdaters { ignore = [ ["feeds"] ]; } [];
|
||||
update.feeds = mkUpdaters {} [ "feeds" ];
|
||||
|
||||
init-feed = {
|
||||
type = "app";
|
||||
program = "${pkgs.feeds.init-feed}";
|
||||
};
|
||||
|
||||
deploy-lappy = {
|
||||
type = "app";
|
||||
program = ''${deployScript "lappy" "lappy" "switch"}'';
|
||||
};
|
||||
deploy-moby-test = {
|
||||
type = "app";
|
||||
program = ''${deployScript "moby" "moby-hn" "test"}'';
|
||||
};
|
||||
deploy-moby = {
|
||||
type = "app";
|
||||
program = ''${deployScript "moby" "moby-hn" "switch"}'';
|
||||
};
|
||||
deploy-servo = {
|
||||
type = "app";
|
||||
program = ''${deployScript "servo" "servo" "switch"}'';
|
||||
};
|
||||
|
||||
sync-moby = {
|
||||
# copy music from the current device to moby
|
||||
# TODO: should i actually sync from /mnt/servo-media/Music instead of the local drive?
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-to-moby" ''
|
||||
sudo mount /mnt/moby-home
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music ~/Music /mnt/moby-home/Music
|
||||
'');
|
||||
};
|
||||
|
||||
sync-lappy = {
|
||||
# copy music from servo to lappy
|
||||
# can run this from any device that has ssh access to lappy
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-to-lappy" ''
|
||||
sudo mount /mnt/lappy-home
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music /mnt/servo-media/Music /mnt/lappy-home/Music
|
||||
'');
|
||||
};
|
||||
|
||||
check = {
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "check-all" ''
|
||||
nix run '.#check.nur'
|
||||
RC0=$?
|
||||
nix run '.#check.host-configs'
|
||||
RC1=$?
|
||||
nix run '.#check.rescue'
|
||||
RC2=$?
|
||||
echo "nur: $RC0"
|
||||
echo "host-configs: $RC1"
|
||||
echo "rescue: $RC2"
|
||||
exit $(($RC0 | $RC1 | $RC2))
|
||||
'');
|
||||
};
|
||||
|
||||
check.nur = {
|
||||
# `nix run '.#check-nur'`
|
||||
# validates that my repo can be included in the Nix User Repository
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "check-nur" ''
|
||||
cd ${./.}/integrations/nur
|
||||
NIX_PATH= NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1 nix-env -f . -qa \* --meta --xml \
|
||||
--allowed-uris https://static.rust-lang.org \
|
||||
--option restrict-eval true \
|
||||
--option allow-import-from-derivation true \
|
||||
--drv-path --show-trace \
|
||||
-I nixpkgs=$(nix-instantiate --find-file nixpkgs) \
|
||||
-I ../../ \
|
||||
| tee # tee to prevent interactive mode
|
||||
'');
|
||||
};
|
||||
|
||||
check.host-configs = {
|
||||
type = "app";
|
||||
program = let
|
||||
checkHost = host: ''
|
||||
nix build -v '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} -j2 $@
|
||||
RC_${host}=$?
|
||||
'';
|
||||
in builtins.toString (pkgs.writeShellScript
|
||||
"check-host-configs"
|
||||
''
|
||||
${checkHost "desko"}
|
||||
${checkHost "lappy"}
|
||||
${checkHost "servo"}
|
||||
${checkHost "moby"}
|
||||
${checkHost "rescue"}
|
||||
echo "desko: $RC_desko"
|
||||
echo "lappy: $RC_lappy"
|
||||
echo "servo: $RC_servo"
|
||||
echo "moby: $RC_moby"
|
||||
echo "rescue: $RC_rescue"
|
||||
exit $(($RC_desko | $RC_lappy | $RC_servo | $RC_moby | $RC_rescue))
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
check.rescue = {
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "check-rescue" ''
|
||||
nix build -v '.#imgs.rescue' --out-link ./result-rescue-img -j2
|
||||
'');
|
||||
program = "${pkgs.feeds.passthru.initFeedScript}";
|
||||
};
|
||||
};
|
||||
|
||||
templates = {
|
||||
env.python-data = {
|
||||
python-data = {
|
||||
# initialize with:
|
||||
# - `nix flake init -t '/home/colin/dev/nixos/#env.python-data'`
|
||||
# - `nix flake init -t '/home/colin/dev/nixos/#python-data'`
|
||||
# then enter with:
|
||||
# - `nix develop`
|
||||
path = ./templates/env/python-data;
|
||||
path = ./templates/python-data;
|
||||
description = "python environment for data processing";
|
||||
};
|
||||
pkgs.rust-inline = {
|
||||
# initialize with:
|
||||
# - `nix flake init -t '/home/colin/dev/nixos/#pkgs.rust-inline'`
|
||||
path = ./templates/pkgs/rust-inline;
|
||||
description = "rust package and development environment (inline rust sources)";
|
||||
};
|
||||
pkgs.rust = {
|
||||
# initialize with:
|
||||
# - `nix flake init -t '/home/colin/dev/nixos/#pkgs.rust'`
|
||||
path = ./templates/pkgs/rust;
|
||||
description = "rust package fit to ship in nixpkgs";
|
||||
};
|
||||
pkgs.make = {
|
||||
# initialize with:
|
||||
# - `nix flake init -t '/home/colin/dev/nixos/#pkgs.make'`
|
||||
path = ./templates/pkgs/make;
|
||||
description = "default Makefile-based derivation";
|
||||
};
|
||||
};
|
||||
|
||||
# unofficial output; used by inputs.nixpatches
|
||||
nixpatches = import ./nixpatches/list.nix;
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -1,7 +0,0 @@
|
||||
## directory structure
|
||||
- by-name/<hostname>: configuration which is evaluated _only_ for the given hostname
|
||||
- common/: configuration which applies to all hosts
|
||||
- modules/: nixpkgs-style modules which may be used by multiple hosts, but configured separately per host.
|
||||
- ideally no module here has effect unless `enable`d
|
||||
- however, `enable` may default to true
|
||||
- and in practice some of these modules surely aren't fully "disableable"
|
@@ -1,43 +0,0 @@
|
||||
# doesn't actually *enable* anything,
|
||||
# but sets up any modules such that if they *were* enabled, they'll act as expected.
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.gui.sxmo = {
|
||||
greeter = "greetd-sway-gtkgreet";
|
||||
noidle = true; #< power button requires 1s hold, which makes it impractical to be dealing with.
|
||||
settings = {
|
||||
# XXX: make sure the user is part of the `input` group!
|
||||
SXMO_LISGD_INPUT_DEVICE = "/dev/input/by-id/usb-Wacom_Co._Ltd._Pen_and_multitouch_sensor-event-if00";
|
||||
# these identifiers are from `swaymsg -t get_inputs`
|
||||
SXMO_VOLUME_BUTTON = "1:1:AT_Translated_Set_2_keyboard";
|
||||
# SXMO_VOLUME_BUTTON = "none";
|
||||
# N.B.: thinkpad's power button requires a full second press to do anything
|
||||
SXMO_POWER_BUTTON = "0:1:Power_Button";
|
||||
# SXMO_POWER_BUTTON = "none";
|
||||
SXMO_DISABLE_LEDS = "1";
|
||||
SXMO_UNLOCK_IDLE_TIME = "120"; # default
|
||||
# sxmo tries to determine device type from /proc/device-tree/compatible,
|
||||
# but that doesn't seem to exist on NixOS? (or maybe it just doesn't exist
|
||||
# on non-aarch64 builds).
|
||||
# the device type informs (at least):
|
||||
# - SXMO_WIFI_MODULE
|
||||
# - SXMO_RTW_SCAN_INTERVAL
|
||||
# - SXMO_SYS_FILES
|
||||
# - SXMO_TOUCHSCREEN_ID
|
||||
# - SXMO_MONITOR
|
||||
# - SXMO_ALSA_CONTROL_NAME
|
||||
# - SXMO_SWAY_SCALE
|
||||
# see <repo:mil/sxmo-utils:scripts/deviceprofiles>
|
||||
# SXMO_DEVICE_NAME = "pine64,pinephone-1.2";
|
||||
# if sxmo doesn't know the device, it can't decide whether to use one_button or three_button mode
|
||||
# and so it just wouldn't handle any button inputs (sxmo_hook_inputhandler.sh not on path)
|
||||
SXMO_DEVICE_NAME = "three_button_touchscreen";
|
||||
};
|
||||
package = (pkgs.sxmo-utils-latest.override { preferSystemd = true; }).overrideAttrs (base: {
|
||||
postPatch = (base.postPatch or "") + ''
|
||||
# after volume-button navigation mode, restore full keyboard functionality
|
||||
cp ${./xkb_mobile_normal_buttons} ./configs/xkb/xkb_mobile_normal_buttons
|
||||
'';
|
||||
});
|
||||
};
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
xkb_keymap {
|
||||
xkb_keycodes { include "evdev+aliases(qwerty)" };
|
||||
xkb_types { include "complete" };
|
||||
xkb_compat { include "complete" };
|
||||
xkb_symbols { include "pc+us+inet(evdev)" };
|
||||
xkb_geometry { include "pc(pc105)" };
|
||||
};
|
@@ -1,174 +0,0 @@
|
||||
# Pinephone
|
||||
# other setups to reference:
|
||||
# - <https://hamblingreen.gitlab.io/2022/03/02/my-pinephone-setup.html>
|
||||
# - sxmo Arch user. lots of app recommendations
|
||||
#
|
||||
# wikis, resources, ...:
|
||||
# - Linux Phone Apps: <https://linuxphoneapps.org/>
|
||||
# - massive mobile-friendly app database
|
||||
# - Mobian wiki: <https://wiki.mobian-project.org/doku.php?id=start>
|
||||
# - recommended apps, chatrooms
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
imports = [
|
||||
./bootloader.nix
|
||||
./fs.nix
|
||||
./gps.nix
|
||||
./kernel.nix
|
||||
./polyfill.nix
|
||||
];
|
||||
|
||||
sane.roles.client = true;
|
||||
sane.zsh.showDeadlines = false; # unlikely to act on them when in shell
|
||||
sane.services.wg-home.enable = true;
|
||||
sane.services.wg-home.ip = config.sane.hosts.by-name."moby".wg-home.ip;
|
||||
|
||||
# XXX colin: phosh doesn't work well with passwordless login,
|
||||
# so set this more reliable default password should anything go wrong
|
||||
users.users.colin.initialPassword = "147147";
|
||||
services.getty.autologinUser = "root"; # allows for emergency maintenance?
|
||||
|
||||
sops.secrets.colin-passwd.neededForUsers = true;
|
||||
|
||||
sane.gui.sxmo.enable = true;
|
||||
sane.programs.guiApps.suggestedPrograms = [ "handheldGuiApps" ];
|
||||
# sane.programs.consoleUtils.enableFor.user.colin = false;
|
||||
# sane.programs.guiApps.enableFor.user.colin = false;
|
||||
sane.programs.blueberry.enableFor.user.colin = false; # bluetooth manager: doesn't cross compile!
|
||||
sane.programs.mercurial.enableFor.user.colin = false; # does not cross compile
|
||||
sane.programs.sequoia.enableFor.user.colin = false;
|
||||
sane.programs.tuiApps.enableFor.user.colin = false; # visidata, others, don't compile well
|
||||
# disabled for faster deploys
|
||||
sane.programs.soundconverter.enableFor.user.colin = false;
|
||||
|
||||
# enabled for easier debugging
|
||||
sane.programs.eg25-control.enableFor.user.colin = true;
|
||||
sane.programs.rtl8723cs-wowlan.enableFor.user.colin = true;
|
||||
|
||||
# sane.programs.ntfy-sh.config.autostart = true;
|
||||
sane.programs.dino.config.autostart = true;
|
||||
# sane.programs.calls.config.autostart = true;
|
||||
sane.programs.mpv.config.vo = "wlshm"; #< see hosts/common/programs/mpv.nix for details
|
||||
|
||||
sane.programs.firefox.mime.priority = 300; # prefer other browsers when possible
|
||||
# HACK/TODO: make `programs.P.env.VAR` behave according to `mime.priority`
|
||||
sane.programs.firefox.env = lib.mkForce {};
|
||||
sane.programs.epiphany.env.BROWSER = "epiphany";
|
||||
sane.programs.firefox.enableFor.user.colin = false; # use epiphany instead
|
||||
|
||||
# note the .conf.d approach: using ~/.config/pipewire/pipewire.conf directly breaks all audio,
|
||||
# presumably because that deletes the defaults entirely whereas the .conf.d approach selectively overrides defaults
|
||||
sane.user.fs.".config/pipewire/pipewire.conf.d/10-fix-dino-mic-cutout.conf".symlink.text = ''
|
||||
# config docs: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-PipeWire#properties>
|
||||
# useful to run `pw-top` to see that these settings are actually having effect,
|
||||
# and `pw-metadata` to see if any settings conflict (e.g. max-quantum < min-quantum)
|
||||
#
|
||||
# restart pipewire after editing these files:
|
||||
# - `systemctl --user restart pipewire`
|
||||
# - pipewire users will likely stop outputting audio until they are also restarted
|
||||
#
|
||||
# there's seemingly two buffers for the mic (see: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#pipewire-buffering-explained>)
|
||||
# 1. Pipewire buffering out of the driver and into its own member.
|
||||
# 2. Pipewire buffering into Dino.
|
||||
# the latter is fixed at 10ms by Dino, difficult to override via runtime config.
|
||||
# the former defaults low (e.g. 512 samples)
|
||||
# this default configuration causes the mic to regularly drop out entirely for a couple seconds at a time during a call,
|
||||
# presumably because the system can't keep up (pw-top shows incrementing counter in ERR column).
|
||||
# `pw-metadata -n settings 0 clock.force-quantum 1024` reduces to about 1 error per second.
|
||||
# `pw-metadata -n settings 0 clock.force-quantum 2048` reduces to 1 error every < 10s.
|
||||
# pipewire default config includes `clock.power-of-two-quantum = true`
|
||||
context.properties = {
|
||||
default.clock.min-quantum = 2048
|
||||
default.clock.max-quantum = 8192
|
||||
}
|
||||
'';
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
# /boot space is at a premium. default was 20.
|
||||
# even 10 can be too much
|
||||
boot.loader.generic-extlinux-compatible.configurationLimit = 8;
|
||||
# mobile.bootloader.enable = false;
|
||||
# mobile.boot.stage-1.enable = false;
|
||||
# boot.initrd.systemd.enable = false;
|
||||
# boot.initrd.services.swraid.enable = false; # attempt to fix dm_mod stuff
|
||||
|
||||
# hardware.firmware makes the referenced files visible to the kernel, for whenever a driver explicitly asks for them.
|
||||
# these files are visible from userspace by following `/sys/module/firmware_class/parameters/path`
|
||||
#
|
||||
# mobile-nixos' /lib/firmware includes:
|
||||
# rtl_bt (bluetooth)
|
||||
# anx7688-fw.bin (USB-C chip: power negotiation, HDMI/dock)
|
||||
# ov5640_af.bin (camera module)
|
||||
# hardware.firmware = [ config.mobile.device.firmware ];
|
||||
# hardware.firmware = [ pkgs.rtl8723cs-firmware ];
|
||||
hardware.firmware = [
|
||||
(pkgs.linux-firmware-megous.override {
|
||||
# rtl_bt = false probably means no bluetooth connectivity.
|
||||
# N.B.: DON'T RE-ENABLE without first confirming that wake-on-lan works during suspend (rtcwake).
|
||||
# it seems the rtl_bt stuff ("bluetooth coexist") might make wake-on-LAN radically more flaky.
|
||||
rtl_bt = false;
|
||||
})
|
||||
];
|
||||
|
||||
system.stateVersion = "21.11";
|
||||
|
||||
# defined: https://www.freedesktop.org/software/systemd/man/machine-info.html
|
||||
# XXX colin: not sure which, if any, software makes use of this
|
||||
environment.etc."machine-info".text = ''
|
||||
CHASSIS="handset"
|
||||
'';
|
||||
|
||||
# enable rotation sensor
|
||||
hardware.sensor.iio.enable = true;
|
||||
|
||||
# inject specialized alsa configs via the environment.
|
||||
# specifically, this gets the pinephone headphones & internal earpiece working.
|
||||
# see pkgs/patched/alsa-ucm-conf for more info.
|
||||
environment.variables.ALSA_CONFIG_UCM2 = "/run/current-system/sw/share/alsa/ucm2";
|
||||
environment.pathsToLink = [ "/share/alsa/ucm2" ];
|
||||
environment.systemPackages = [
|
||||
(pkgs.alsa-ucm-conf-sane.override {
|
||||
# internal speaker has a tendency to break :(
|
||||
preferEarpiece = true;
|
||||
})
|
||||
];
|
||||
systemd = let
|
||||
ucm-env = config.environment.variables.ALSA_CONFIG_UCM2;
|
||||
in {
|
||||
# cribbed from <repo:nixos/mobile-nixos:modules/quirks/audio.nix>
|
||||
|
||||
# pipewire
|
||||
user.services.pipewire.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
user.services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
user.services.wireplumber.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
services.pipewire.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
services.wireplumber.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
|
||||
# pulseaudio
|
||||
# user.services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
# services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
|
||||
|
||||
# TODO: move elsewhere...
|
||||
services.ModemManager.serviceConfig = {
|
||||
# N.B.: the extra "" in ExecStart serves to force upstream ExecStart to be ignored
|
||||
ExecStart = [ "" "${pkgs.modemmanager}/bin/ModemManager --debug" ];
|
||||
# --debug sets DEBUG level logging: so reset
|
||||
ExecStartPost = [ "${pkgs.modemmanager}/bin/mmcli --set-logging=INFO" ];
|
||||
};
|
||||
};
|
||||
|
||||
services.udev.extraRules = let
|
||||
chmod = "${pkgs.coreutils}/bin/chmod";
|
||||
chown = "${pkgs.coreutils}/bin/chown";
|
||||
in ''
|
||||
# make Pinephone flashlight writable by user.
|
||||
# taken from postmarketOS: <repo:postmarketOS/pmaports:device/main/device-pine64-pinephone/60-flashlight.rules>
|
||||
SUBSYSTEM=="leds", DEVPATH=="*/*:flash", RUN+="${chmod} g+w /sys%p/brightness /sys%p/flash_strobe", RUN+="${chown} :video /sys%p/brightness /sys%p/flash_strobe"
|
||||
|
||||
# make Pinephone front LEDs writable by user.
|
||||
SUBSYSTEM=="leds", DEVPATH=="*/*:indicator", RUN+="${chmod} g+w /sys%p/brightness", RUN+="${chown} :video /sys%p/brightness"
|
||||
'';
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
# pinephone GPS happens in EG25 modem
|
||||
# serial control interface to modem is /dev/ttyUSB2
|
||||
# after enabling GPS, readout is /dev/ttyUSB1
|
||||
#
|
||||
# minimal process to enable modem and GPS:
|
||||
# - `echo 1 > /sys/class/modem-power/modem-power/device/powered`
|
||||
# - `screen /dev/ttyUSB2 115200`
|
||||
# - `AT+QGPSCFG="nmeasrc",1`
|
||||
# - `AT+QGPS=1`
|
||||
# this process is automated by my `eg25-control` program and services (`eg25-control-powered`, `eg25-control-gps`)
|
||||
# - see the `modules/` directory further up this repository.
|
||||
#
|
||||
# now, something like `gpsd` can directly read from /dev/ttyUSB1,
|
||||
# or geoclue can query the GPS directly through modem-manager
|
||||
#
|
||||
# initial GPS fix can take 15+ minutes.
|
||||
# meanwhile, services like eg25-manager or eg25-control-freshen-agps can speed this up by uploading assisted GPS data to the modem.
|
||||
#
|
||||
# support/help:
|
||||
# - geoclue, gnome-maps
|
||||
# - irc: #gnome-maps on irc.gimp.org
|
||||
# - Matrix: #gnome-maps:gnome.org (unclear if bridged to IRC)
|
||||
#
|
||||
# programs to pair this with:
|
||||
# - `satellite-gtk`: <https://codeberg.org/tpikonen/satellite>
|
||||
# - shows/tracks which satellites the GPS is connected to; useful to understand fix characteristics
|
||||
# - `gnome-maps`: uses geoclue, has route planning
|
||||
# - `mepo`: uses gpsd, minimalist, flaky, and buttons are kinda hard to activate on mobile
|
||||
# - puremaps?
|
||||
# - osmin?
|
||||
#
|
||||
# known/outstanding bugs:
|
||||
# - `systemctl start eg25-control-gps` can the hang the whole system (2023/10/06)
|
||||
# - i think it's actually `eg25-control-powered` which does this (started by the gps)
|
||||
# - best guess is modem draws so much power at launch that other parts of the system see undervoltage
|
||||
# - workaround is to hard power-cycle the system. the modem may not bring up after reboot: leave unpowered for 60s and boot again.
|
||||
#
|
||||
# future work:
|
||||
# - integrate with [wigle](https://www.wigle.net/) for offline equivalent to Mozilla Location Services
|
||||
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
# test gpsd with `gpspipe -w -n 10 2> /dev/null | grep -m 1 TPV | jq '.lat, .lon' | tr '\n' ' '`
|
||||
# ^ should return <lat> <long>
|
||||
services.gpsd.enable = true;
|
||||
services.gpsd.devices = [ "/dev/ttyUSB1" ];
|
||||
|
||||
# test geoclue2 by building `geoclue2-with-demo-agent`
|
||||
# and running "${geoclue2-with-demo-agent}/libexec/geoclue-2.0/demos/where-am-i"
|
||||
# note that geoclue is dbus-activated, and auto-stops after 60s with no caller
|
||||
services.geoclue2.enable = true;
|
||||
services.geoclue2.appConfig.where-am-i = {
|
||||
# this is the default "agent", shipped by geoclue package: allow it to use location
|
||||
isAllowed = true;
|
||||
isSystem = false;
|
||||
# XXX: setting users != [] might be causing `where-am-i` to time out
|
||||
users = [
|
||||
# restrict to only one set of users. empty array (default) means "allow any user to access geolocation".
|
||||
(builtins.toString config.users.users.colin.uid)
|
||||
];
|
||||
};
|
||||
systemd.services.geoclue.after = lib.mkForce []; #< defaults to network-online, but not all my sources require network
|
||||
users.users.geoclue.extraGroups = [
|
||||
"dialout" # TODO: figure out if dialout is required. that's for /dev/ttyUSB1, but geoclue probably doesn't read that?
|
||||
];
|
||||
|
||||
sane.services.eg25-control.enable = true;
|
||||
sane.programs.where-am-i.enableFor.user.colin = true;
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
dmesg = "${pkgs.util-linux}/bin/dmesg";
|
||||
grep = "${pkgs.gnugrep}/bin/grep";
|
||||
modprobe = "${pkgs.kmod}/bin/modprobe";
|
||||
ensureHWReady = ''
|
||||
# common boot failure:
|
||||
# blank screen (no backlight even), with the following log:
|
||||
# ```syslog
|
||||
# sun8i-dw-hdmi 1ee0000.hdmi: Couldn't get the HDMI PHY
|
||||
# ...
|
||||
# sun4i-drm display-engine: Couldn't bind all pipelines components
|
||||
# ...
|
||||
# sun8i-dw-hdmi: probe of 1ee0000.hdmi failed with error -17
|
||||
# ```
|
||||
#
|
||||
# in particular, that `probe ... failed` occurs *only* on failed boots
|
||||
# (the other messages might sometimes occur even on successful runs?)
|
||||
#
|
||||
# reloading the sun8i hdmi driver usually gets the screen on, showing boot text.
|
||||
# then restarting display-manager.service gets us to the login.
|
||||
#
|
||||
# NB: the above log is default level. though less specific, there's a `err` level message that also signals this:
|
||||
# sun4i-drm display-engine: failed to bind 1ee0000.hdmi (ops sun8i_dw_hdmi_ops [sun8i_drm_hdmi]): -17
|
||||
# NB: this is the most common, but not the only, failure mode for `display-manager`.
|
||||
# another error seems characterized by these dmesg logs, in which reprobing sun8i_drm_hdmi does not fix:
|
||||
# ```syslog
|
||||
# sun6i-mipi-dsi 1ca0000.dsi: Couldn't get the MIPI D-PHY
|
||||
# sun4i-drm display-engine: Couldn't bind all pipelines components
|
||||
# sun6i-mipi-dsi 1ca0000.dsi: Couldn't register our component
|
||||
# ```
|
||||
|
||||
if (${dmesg} --kernel --level err --color=never --notime | ${grep} -q 'sun4i-drm display-engine: failed to bind 1ee0000.hdmi')
|
||||
then
|
||||
echo "reprobing sun8i_drm_hdmi"
|
||||
# if a command here fails it errors the whole service, so prefer to log instead
|
||||
${modprobe} -r sun8i_drm_hdmi || echo "failed to unload sun8i_drm_hdmi"
|
||||
${modprobe} sun8i_drm_hdmi || echo "failed to load sub8i_drm_hdmi"
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
boot.kernelPackages = pkgs.linuxPackagesFor pkgs.linux-megous;
|
||||
# boot.kernelPackages = pkgs.linuxPackagesFor pkgs.linux-manjaro;
|
||||
# boot.kernelPackages = pkgs.linuxPackagesFor pkgs.linux_latest;
|
||||
|
||||
# alternatively, apply patches directly to stock nixos kernel:
|
||||
# boot.kernelPatches = manjaroPatches ++ [
|
||||
# (patchDefconfig kernelConfig)
|
||||
# ];
|
||||
|
||||
# configure nixos to build a compressed kernel image, since it doesn't usually do that for aarch64 target.
|
||||
# without this i run out of /boot space in < 10 generations
|
||||
nixpkgs.hostPlatform.linux-kernel = {
|
||||
# defaults:
|
||||
name = "aarch64-multiplatform";
|
||||
baseConfig = "defconfig";
|
||||
DTB = true;
|
||||
autoModules = true;
|
||||
preferBuiltin = true;
|
||||
# extraConfig = ...
|
||||
# ^-- raspberry pi stuff: we don't need it.
|
||||
|
||||
# target = "Image"; # <-- default
|
||||
target = "Image.gz"; # <-- compress the kernel image
|
||||
# target = "zImage"; # <-- confuses other parts of nixos :-(
|
||||
};
|
||||
|
||||
# disable proximity sensor.
|
||||
# the filtering/calibration is bad that it causes the screen to go fully dark at times.
|
||||
boot.blacklistedKernelModules = [ "stk3310" ];
|
||||
|
||||
boot.kernelParams = [
|
||||
# without this some GUI apps fail: `DRM_IOCTL_MODE_CREATE_DUMB failed: Cannot allocate memory`
|
||||
# this is because they can't allocate enough video ram.
|
||||
# see related nixpkgs issue: <https://github.com/NixOS/nixpkgs/issues/260222>
|
||||
#
|
||||
# the default CMA seems to be 32M.
|
||||
# i was running fine with 256MB from 2022/07-ish through 2022/12-ish, but then the phone quit reliably coming back from sleep (phosh): maybe a memory leak?
|
||||
# `cat /proc/meminfo` to see CmaTotal/CmaFree if interested in tuning this.
|
||||
"cma=512M"
|
||||
# 2023/10/20: potential fix for the lima (GPU) timeout bugs:
|
||||
# - <https://gitlab.com/postmarketOS/pmaports/-/issues/805#note_890467824>
|
||||
"lima.sched_timeout_ms=2000"
|
||||
];
|
||||
|
||||
services.xserver.displayManager.job.preStart = ensureHWReady;
|
||||
systemd.services.greetd.preStart = ensureHWReady;
|
||||
}
|
@@ -1,96 +0,0 @@
|
||||
# this file configures preferences per program, without actually enabling any programs.
|
||||
# the goal is to separate the place where we decide *what* to use (i.e. `sane.programs.firefox.enable = true` -- at the toplevel)
|
||||
# from where we specific how that thing should behave *if* it's in use.
|
||||
#
|
||||
# NixOS backgrounds:
|
||||
# - <https://github.com/NixOS/nixos-artwork>
|
||||
# - <https://github.com/NixOS/nixos-artwork/issues/50> (colorful; unmerged)
|
||||
# - <https://github.com/NixOS/nixos-artwork/pull/60/files> (desktop-oriented; clean; unmerged)
|
||||
# - <https://itsfoss.com/content/images/2023/04/nixos-tutorials.png>
|
||||
|
||||
{ lib, pkgs, sane-lib, ... }:
|
||||
{
|
||||
sane.programs.firefox.config = {
|
||||
# compromise impermanence for the sake of usability
|
||||
persistCache = "private";
|
||||
persistData = "private";
|
||||
|
||||
# i don't do crypto stuff on moby
|
||||
addons.ether-metamask.enable = false;
|
||||
# sidebery UX doesn't make sense on small screen
|
||||
addons.sidebery.enable = false;
|
||||
};
|
||||
sane.programs.swaynotificationcenter.config = {
|
||||
backlight = "backlight"; # /sys/class/backlight/*backlight*/brightness
|
||||
};
|
||||
|
||||
sane.gui.sxmo = {
|
||||
nogesture = true;
|
||||
settings = {
|
||||
### hardware: touch screen
|
||||
SXMO_LISGD_INPUT_DEVICE = "/dev/input/by-path/platform-1c2ac00.i2c-event";
|
||||
# vol and power are detected correctly by upstream
|
||||
|
||||
### preferences
|
||||
DEFAULT_COUNTRY = "US";
|
||||
|
||||
SXMO_AUTOROTATE = "1"; # enable auto-rotation at launch. has no meaning in stock/upstream sxmo-utils
|
||||
|
||||
# BEMENU lines (wayland DMENU):
|
||||
# - camera is 9th entry
|
||||
# - flashlight is 10th entry
|
||||
# - config is 14th entry. inside that:
|
||||
# - autorotate is 11th entry
|
||||
# - system menu is 19th entry
|
||||
# - close is 20th entry
|
||||
# - power is 15th entry
|
||||
# - close is 16th entry
|
||||
SXMO_BEMENU_LANDSCAPE_LINES = "11"; # default 8
|
||||
SXMO_BEMENU_PORTRAIT_LINES = "16"; # default 16
|
||||
SXMO_LOCK_IDLE_TIME = "15"; # how long between screenoff -> lock -> back to screenoff (default: 8)
|
||||
# gravity: how far to tilt the device before the screen rotates
|
||||
# for a given setting, normal <-> invert requires more movement then left <-> right
|
||||
# i.e. the settingd doesn't feel completely symmetric
|
||||
# SXMO_ROTATION_GRAVITY default is 16374
|
||||
# SXMO_ROTATION_GRAVITY = "12800"; # uncomfortably high
|
||||
# SXMO_ROTATION_GRAVITY = "12500"; # kinda uncomfortable when walking
|
||||
SXMO_ROTATION_GRAVITY = "12000";
|
||||
SXMO_SCREENSHOT_DIR = "/home/colin/Pictures"; # default: "$HOME"
|
||||
|
||||
# sway/wayland scaling:
|
||||
# - conflicting info out there on how scaling actually works
|
||||
# at the least, for things where it matters (mpv), it seems like scale settings have 0 effect on perf
|
||||
# ways to enforce scaling:
|
||||
# - <https://wiki.archlinux.org/title/HiDPI>
|
||||
# - `swaymsg -- output DSI-1 scale 2.0` (scales everything)
|
||||
# - `dconf write /org/gnome/desktop/interface/text-scaling-factor 2.0` (scales ONLY TEXT)
|
||||
# - `GDK_DPI_SCALE=2.0` (scales ONLY TEXT)
|
||||
#
|
||||
# application notes:
|
||||
# - cozy: in landscape, playback position is not visible unless scale <= 1.7
|
||||
# - if in a tab, then scale 1.6 is the max
|
||||
# SXMO_SWAY_SCALE = "1.5"; # hard to press gPodder icons
|
||||
SXMO_SWAY_SCALE = "1.6";
|
||||
# SXMO_SWAY_SCALE = "1.8";
|
||||
# SXMO_SWAY_SCALE = "2";
|
||||
SXMO_WORKSPACE_WRAPPING = "5"; # how many workspaces. default: 4
|
||||
|
||||
# wvkbd layers:
|
||||
# - full
|
||||
# - landscape
|
||||
# - special (e.g. coding symbols like ~)
|
||||
# - emoji
|
||||
# - nav
|
||||
# - simple (like landscape, but no parens/tab/etc; even fewer chars)
|
||||
# - simplegrid (simple, but grid layout)
|
||||
# - dialer (digits)
|
||||
# - cyrillic
|
||||
# - arabic
|
||||
# - persian
|
||||
# - greek
|
||||
# - georgian
|
||||
WVKBD_LANDSCAPE_LAYERS = "landscape,special,emoji";
|
||||
WVKBD_LAYERS = "full,special,emoji";
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
./fs.nix
|
||||
];
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||
sane.persist.enable = false;
|
||||
sane.nixcache.enable = false; # don't want to be calling out to dead machines that we're *trying* to rescue
|
||||
|
||||
# auto-login at shell
|
||||
services.getty.autologinUser = "colin";
|
||||
# users.users.colin.initialPassword = "colin";
|
||||
|
||||
# docs: https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion
|
||||
system.stateVersion = "21.05";
|
||||
}
|
@@ -1,116 +0,0 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.persist.root-on-tmpfs = true;
|
||||
# increase /tmp space (defaults to 50% of RAM) for building large nix things.
|
||||
# even the stock `nixpkgs.linux` consumes > 16 GB of tmp
|
||||
fileSystems."/tmp".options = [ "size=32G" ];
|
||||
|
||||
fileSystems."/nix" = {
|
||||
device = "/dev/disk/by-uuid/cc81cca0-3cc7-4d82-a00c-6243af3e7776";
|
||||
fsType = "btrfs";
|
||||
options = [
|
||||
"compress=zstd"
|
||||
"defaults"
|
||||
];
|
||||
};
|
||||
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/disk/by-uuid/6EE3-4171";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
# slow, external storage (for archiving, etc)
|
||||
fileSystems."/mnt/persist/ext" = {
|
||||
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
||||
fsType = "btrfs";
|
||||
options = [
|
||||
"compress=zstd"
|
||||
"defaults"
|
||||
];
|
||||
};
|
||||
|
||||
sane.persist.stores."ext" = {
|
||||
origin = "/mnt/persist/ext/persist";
|
||||
storeDescription = "external HDD storage";
|
||||
};
|
||||
sane.fs."/mnt/persist/ext".mount = {};
|
||||
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: this is overly broad; only need media and share directories to be persisted
|
||||
{ user = "colin"; group = "users"; path = "/var/lib/uninsane"; }
|
||||
];
|
||||
# force some problematic directories to always get correct permissions:
|
||||
sane.fs."/var/lib/uninsane/media".dir.acl = {
|
||||
user = "colin"; group = "media"; mode = "0775";
|
||||
};
|
||||
sane.fs."/var/lib/uninsane/media/archive".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/archive/README.md".file.text = ''
|
||||
this directory is for media i wish to remove from my library,
|
||||
but keep for a short time in case i reverse my decision.
|
||||
treat it like a system trash can.
|
||||
'';
|
||||
sane.fs."/var/lib/uninsane/media/Books".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Books/Audiobooks".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Books/Books".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Books/Visual".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/collections".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/datasets".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/freeleech".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Music".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Pictures".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Videos".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Videos/Film".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Videos/Shows".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Videos/Talks".dir = {};
|
||||
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.
|
||||
'';
|
||||
# make sure large media is stored to the HDD
|
||||
sane.persist.sys.ext = [
|
||||
{
|
||||
user = "colin";
|
||||
group = "users";
|
||||
mode = "0777";
|
||||
path = "/var/lib/uninsane/media/Videos";
|
||||
}
|
||||
{
|
||||
user = "colin";
|
||||
group = "users";
|
||||
mode = "0777";
|
||||
path = "/var/lib/uninsane/media/freeleech";
|
||||
}
|
||||
{
|
||||
user = "colin";
|
||||
group = "users";
|
||||
mode = "0777";
|
||||
path = "/var/lib/uninsane/media/datasets";
|
||||
}
|
||||
];
|
||||
|
||||
# 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;
|
||||
# }
|
||||
# ];
|
||||
}
|
||||
|
@@ -1,233 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
portOpts = with lib; types.submodule {
|
||||
options = {
|
||||
visibleTo.ovpn = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options = with lib; {
|
||||
sane.ports.ports = mkOption {
|
||||
# add the `visibleTo.ovpn` option
|
||||
type = types.attrsOf portOpts;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
networking.domain = "uninsane.org";
|
||||
|
||||
sane.ports.openFirewall = true;
|
||||
sane.ports.openUpnp = true;
|
||||
|
||||
# view refused packets with: `sudo journalctl -k`
|
||||
# networking.firewall.logRefusedPackets = true;
|
||||
|
||||
# The global useDHCP flag is deprecated, therefore explicitly set to false here.
|
||||
# Per-interface useDHCP will be mandatory in the future, so this generated config
|
||||
# replicates the default behaviour.
|
||||
networking.useDHCP = false;
|
||||
networking.interfaces.eth0.useDHCP = true;
|
||||
# XXX colin: probably don't need this. wlan0 won't be populated unless i touch a value in networking.interfaces.wlan0
|
||||
networking.wireless.enable = false;
|
||||
|
||||
# this is needed to forward packets from the VPN to the host
|
||||
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
|
||||
|
||||
# unless we add interface-specific settings for each VPN, we have to define nameservers globally.
|
||||
# networking.nameservers = [
|
||||
# "1.1.1.1"
|
||||
# "9.9.9.9"
|
||||
# ];
|
||||
|
||||
# use systemd's stub resolver.
|
||||
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
|
||||
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets
|
||||
# in the ovnps namespace to use the provider's DNS resolvers.
|
||||
# a weakness is we can only query 1 NS at a time (unless we were to clone the packets?)
|
||||
# there also seems to be some cache somewhere that's shared between the two namespaces.
|
||||
# i think this is a libc thing. might need to leverage proper cgroups to _really_ kill it.
|
||||
# - getent ahostsv4 www.google.com
|
||||
# - try fix: <https://serverfault.com/questions/765989/connect-to-3rd-party-vpn-server-but-dont-use-it-as-the-default-route/766290#766290>
|
||||
services.resolved.enable = true;
|
||||
# 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 (trust-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
|
||||
# in a way that's unaware of my VPN routing, so routes are frequently poor against
|
||||
# services which advertise different IPs based on geolocation.
|
||||
# nscd claims to be usable without a cache, but in practice i can't get it to not cache!
|
||||
# nsncd is the Name Service NON-Caching Daemon. it's a drop-in that doesn't cache;
|
||||
# this is OK on the host -- because systemd-resolved caches. it's probably sub-optimal
|
||||
# in the netns and we query upstream DNS more often than needed. hm.
|
||||
# TODO: run a separate recursive resolver in each namespace.
|
||||
services.nscd.enableNsncd = true;
|
||||
|
||||
# services.resolved.extraConfig = ''
|
||||
# # docs: `man resolved.conf`
|
||||
# # DNS servers to use via the `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
|
||||
# '';
|
||||
|
||||
# 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 = let
|
||||
ip = "${pkgs.iproute2}/bin/ip";
|
||||
in-ns = "${ip} netns exec ovpns";
|
||||
iptables = "${pkgs.iptables}/bin/iptables";
|
||||
veth-host-ip = "10.0.1.5";
|
||||
veth-local-ip = "10.0.1.6";
|
||||
vpn-ip = "185.157.162.178";
|
||||
# DNS = 46.227.67.134, 192.165.9.158, 2a07:a880:4601:10f0:cd45::1, 2001:67c:750:1:cafe:cd45::1
|
||||
vpn-dns = "46.227.67.134";
|
||||
bridgePort = port: proto: ''
|
||||
${in-ns} ${iptables} -A PREROUTING -t nat -p ${proto} --dport ${port} -m iprange --dst-range ${vpn-ip} \
|
||||
-j DNAT --to-destination ${veth-host-ip}
|
||||
'';
|
||||
bridgeStatements = lib.foldlAttrs
|
||||
(acc: port: portCfg: acc ++ (builtins.map (bridgePort port) portCfg.protocol))
|
||||
[]
|
||||
config.sane.ports.ports;
|
||||
in {
|
||||
privateKeyFile = config.sops.secrets.wg_ovpns_privkey.path;
|
||||
# wg is active only in this namespace.
|
||||
# run e.g. ip netns exec ovpns <some command like ping/curl/etc, it'll go through wg>
|
||||
# sudo ip netns exec ovpns ping www.google.com
|
||||
interfaceNamespace = "ovpns";
|
||||
ips = [
|
||||
"185.157.162.178/32"
|
||||
];
|
||||
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;
|
||||
}
|
||||
];
|
||||
preSetup = ''
|
||||
${ip} netns add ovpns || echo "ovpns already exists"
|
||||
'';
|
||||
postShutdown = ''
|
||||
${in-ns} ip link del ovpns-veth-b || echo "couldn't delete ovpns-veth-b"
|
||||
${ip} link del ovpns-veth-a || echo "couldn't delete ovpns-veth-a"
|
||||
${ip} netns delete ovpns || echo "couldn't delete ovpns"
|
||||
# restore rules/routes
|
||||
${ip} rule del from ${veth-host-ip} lookup ovpns pref 50 || echo "couldn't delete init -> ovpns rule"
|
||||
${ip} route del default via ${veth-local-ip} dev ovpns-veth-a proto kernel src ${veth-host-ip} metric 1002 table ovpns || echo "couldn't delete init -> ovpns route"
|
||||
${ip} rule add from all lookup local pref 0
|
||||
${ip} rule del from all lookup local pref 100
|
||||
'';
|
||||
postSetup = ''
|
||||
# DOCS:
|
||||
# - some of this approach is described here: <https://josephmuia.ca/2018-05-16-net-namespaces-veth-nat/>
|
||||
# - iptables primer: <https://danielmiessler.com/study/iptables/>
|
||||
# create veth pair
|
||||
${ip} link add ovpns-veth-a type veth peer name ovpns-veth-b
|
||||
${ip} addr add ${veth-host-ip}/24 dev ovpns-veth-a
|
||||
${ip} link set ovpns-veth-a up
|
||||
|
||||
# mv veth-b into the ovpns namespace
|
||||
${ip} link set ovpns-veth-b netns ovpns
|
||||
${in-ns} ip addr add ${veth-local-ip}/24 dev ovpns-veth-b
|
||||
${in-ns} ip link set ovpns-veth-b up
|
||||
|
||||
# make it so traffic originating from the host side of the veth
|
||||
# is sent over the veth no matter its destination.
|
||||
${ip} rule add from ${veth-host-ip} lookup ovpns pref 50
|
||||
# for traffic originating at the host veth to the WAN, use the veth as our gateway
|
||||
# not sure if the metric 1002 matters.
|
||||
${ip} route add default via ${veth-local-ip} dev ovpns-veth-a proto kernel src ${veth-host-ip} metric 1002 table ovpns
|
||||
# give the default route lower priority
|
||||
${ip} rule add from all lookup local pref 100
|
||||
${ip} rule del from all lookup local pref 0
|
||||
|
||||
# in order to access DNS in this netns, we need to route it to the VPN's nameservers
|
||||
# - alternatively, we could fix DNS servers like 1.1.1.1.
|
||||
${in-ns} ${iptables} -A OUTPUT -t nat -p udp --dport 53 -m iprange --dst-range 127.0.0.53 \
|
||||
-j DNAT --to-destination ${vpn-dns}:53
|
||||
'' + (lib.concatStringsSep "\n" bridgeStatements);
|
||||
};
|
||||
|
||||
# create a new routing table that we can use to proxy traffic out of the root namespace
|
||||
# through the ovpns namespace, and to the WAN via VPN.
|
||||
networking.iproute2.rttablesExtraConfig = ''
|
||||
5 ovpns
|
||||
'';
|
||||
networking.iproute2.enable = true;
|
||||
|
||||
|
||||
# HURRICANE ELECTRIC CONFIG:
|
||||
# networking.sits = {
|
||||
# hurricane = {
|
||||
# remote = "216.218.226.238";
|
||||
# local = "192.168.0.5";
|
||||
# # local = "10.0.0.5";
|
||||
# # remote = "10.0.0.1";
|
||||
# # local = "10.0.0.22";
|
||||
# dev = "eth0";
|
||||
# ttl = 255;
|
||||
# };
|
||||
# };
|
||||
# networking.interfaces."hurricane".ipv6 = {
|
||||
# addresses = [
|
||||
# # mx.uninsane.org (publically routed /64)
|
||||
# {
|
||||
# address = "2001:470:b:465::1";
|
||||
# prefixLength = 128;
|
||||
# }
|
||||
# # client addr
|
||||
# # {
|
||||
# # address = "2001:470:a:466::2";
|
||||
# # prefixLength = 64;
|
||||
# # }
|
||||
# ];
|
||||
# routes = [
|
||||
# {
|
||||
# address = "::";
|
||||
# prefixLength = 0;
|
||||
# # via = "2001:470:a:466::1";
|
||||
# }
|
||||
# ];
|
||||
# };
|
||||
|
||||
# # after configuration, we want the hurricane device to look like this:
|
||||
# # hurricane: flags=209<UP,POINTOPOINT,RUNNING,NOARP> mtu 1480
|
||||
# # inet6 2001:470:a:450::2 prefixlen 64 scopeid 0x0<global>
|
||||
# # inet6 fe80::c0a8:16 prefixlen 64 scopeid 0x20<link>
|
||||
# # sit txqueuelen 1000 (IPv6-in-IPv4)
|
||||
# # test with:
|
||||
# # curl --interface hurricane http://[2607:f8b0:400a:80b::2004]
|
||||
# # ping 2607:f8b0:400a:80b::2004
|
||||
};
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cweb-cfg = config.services.calibre-web;
|
||||
inherit (cweb-cfg) user group;
|
||||
inherit (cweb-cfg.listen) ip port;
|
||||
svc-dir = "/var/lib/${cweb-cfg.dataDir}";
|
||||
in
|
||||
# XXX: disabled because of runtime errors like:
|
||||
# > File "/nix/store/c7jqvx980nlg9xhxi065cba61r2ain9y-calibre-web-0.6.19/lib/python3.10/site-packages/calibreweb/cps/db.py", line 926, in speaking_language
|
||||
# > languages = self.session.query(Languages) \
|
||||
# > AttributeError: 'NoneType' object has no attribute 'query'
|
||||
lib.mkIf false
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ inherit user group; mode = "0700"; path = svc-dir; }
|
||||
];
|
||||
|
||||
services.calibre-web.enable = true;
|
||||
services.calibre-web.listen.ip = "127.0.0.1";
|
||||
# XXX: externally populate `${svc-dir}/metadata.db` (once) from
|
||||
# <https://github.com/janeczku/calibre-web/blob/master/library/metadata.db>
|
||||
# i don't know why you have to do this??
|
||||
# services.calibre-web.options.calibreLibrary = svc-dir;
|
||||
|
||||
services.nginx.virtualHosts."calibre.uninsane.org" = {
|
||||
addSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://${ip}:${builtins.toString port}";
|
||||
};
|
||||
};
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."calibre" = "native";
|
||||
}
|
@@ -1,124 +0,0 @@
|
||||
# TURN/STUN NAT traversal service
|
||||
# commonly used to establish realtime calls with prosody, or possibly matrix/synapse
|
||||
#
|
||||
# - <https://github.com/coturn/coturn/>
|
||||
# - `man turnserver`
|
||||
# - config docs: <https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf>
|
||||
#
|
||||
# N.B. during operation it's NORMAL to see "error 401".
|
||||
# during session creation:
|
||||
# - client sends Allocate request
|
||||
# - server replies error 401, providing a realm and nonce
|
||||
# - client uses realm + nonce + shared secret to construct an auth key & call Allocate again
|
||||
# - server replies Allocate Success Response
|
||||
# - source: <https://stackoverflow.com/a/66643135>
|
||||
#
|
||||
# N.B. this safest implementation routes all traffic THROUGH A VPN
|
||||
# - that adds a lot of latency, but in practice turns out to be inconsequential.
|
||||
# i guess ICE allows clients to prefer the other party's lower-latency server, in practice?
|
||||
# - still, this is the "safe" implementation because STUN works with IP addresses instead of domain names:
|
||||
# 1. client A queries the STUN server to determine its own IP address/port.
|
||||
# 2. client A tells client B which IP address/port client A is visible on.
|
||||
# 3. client B contacts that IP address/port
|
||||
# this only works so long as the IP address/port which STUN server sees client A on is publicly routable.
|
||||
# that is NOT the case when the STUN server and client A are on the same LAN
|
||||
# even if client A contacts the STUN server via its WAN address with port reflection enabled.
|
||||
# hence, there's no obvious way to put the STUN server on the same LAN as either client and expect the rest to work.
|
||||
{ lib, ... }:
|
||||
let
|
||||
# TODO: this range could be larger, but right now that's costly because each element is its own UPnP forward
|
||||
# TURN port range (inclusive)
|
||||
turnPortLow = 49152;
|
||||
turnPortHigh = 49167;
|
||||
turnPortRange = lib.range turnPortLow turnPortHigh;
|
||||
in
|
||||
{
|
||||
sane.ports.ports = lib.mkMerge ([
|
||||
{
|
||||
"3478" = {
|
||||
# this is the "control" port.
|
||||
# i.e. no client data is forwarded through it, but it's where clients request tunnels.
|
||||
protocol = [ "tcp" "udp" ];
|
||||
# visibleTo.lan = true;
|
||||
# visibleTo.wan = true;
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-stun-turn";
|
||||
};
|
||||
"5349" = {
|
||||
# the other port 3478 also supports TLS/DTLS, but presumably clients wanting TLS will default 5349
|
||||
protocol = [ "tcp" ];
|
||||
# visibleTo.lan = true;
|
||||
# visibleTo.wan = true;
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-stun-turn-over-tls";
|
||||
};
|
||||
}
|
||||
] ++ (builtins.map
|
||||
(port: {
|
||||
"${builtins.toString port}" = let
|
||||
count = port - turnPortLow + 1;
|
||||
numPorts = turnPortHigh - turnPortLow + 1;
|
||||
in {
|
||||
protocol = [ "tcp" "udp" ];
|
||||
# visibleTo.lan = true;
|
||||
# visibleTo.wan = true;
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-turn-${builtins.toString count}-of-${builtins.toString numPorts}";
|
||||
};
|
||||
})
|
||||
turnPortRange
|
||||
));
|
||||
|
||||
services.nginx.virtualHosts."turn.uninsane.org" = {
|
||||
# allow ACME to procure a cert via nginx for this domain
|
||||
enableACME = true;
|
||||
};
|
||||
sane.dns.zones."uninsane.org".inet = {
|
||||
# CNAME."turn" = "servo.wan";
|
||||
# CNAME."turn" = "ovpns";
|
||||
# CNAME."turn" = "native";
|
||||
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
|
||||
A."turn" = "%AOVPNS%";
|
||||
# A."turn" = "%AWAN%";
|
||||
|
||||
SRV."_stun._udp" = "5 50 3478 turn";
|
||||
SRV."_stun._tcp" = "5 50 3478 turn";
|
||||
SRV."_stuns._tcp" = "5 50 5349 turn";
|
||||
SRV."_turn._udp" = "5 50 3478 turn";
|
||||
SRV."_turn._tcp" = "5 50 3478 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
|
||||
users.users.turnserver.extraGroups = [ "nginx" ];
|
||||
|
||||
services.coturn.enable = true;
|
||||
services.coturn.realm = "turn.uninsane.org";
|
||||
services.coturn.cert = "/var/lib/acme/turn.uninsane.org/fullchain.pem";
|
||||
services.coturn.pkey = "/var/lib/acme/turn.uninsane.org/key.pem";
|
||||
services.coturn.use-auth-secret = true;
|
||||
services.coturn.static-auth-secret-file = "/var/lib/coturn/shared_secret.bin";
|
||||
services.coturn.lt-cred-mech = true;
|
||||
services.coturn.min-port = turnPortLow;
|
||||
services.coturn.max-port = turnPortHigh;
|
||||
# services.coturn.secure-stun = true;
|
||||
services.coturn.extraConfig = lib.concatStringsSep "\n" [
|
||||
"verbose"
|
||||
# "Verbose" #< even MORE verbosity than "verbose"
|
||||
# "no-multicast-peers" # disables sending to IPv4 broadcast addresses (e.g. 224.0.0.0/3)
|
||||
"listening-ip=10.0.1.5"
|
||||
# "external-ip=185.157.162.178/10.0.1.5"
|
||||
"external-ip=185.157.162.178"
|
||||
# "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"
|
||||
# "mobility" # "mobility with ICE (MICE) specs support" (?)
|
||||
];
|
||||
}
|
@@ -1,471 +0,0 @@
|
||||
# docs:
|
||||
# - <https://docs.ejabberd.im/admin/configuration/basic>
|
||||
# example configs:
|
||||
# - <https://github.com/vkleen/machines/blob/138a2586ce185d7cf201d4e1fe898c83c4af52eb/hosts/europium/ejabberd.nix>
|
||||
# - <https://github.com/Mic92/stockholm/blob/675ef0088624c9de1cb531f318446316884a9d3d/tv/3modules/ejabberd/default.nix>
|
||||
# - <https://github.com/buffet/tararice/blob/master/programs/ejabberd.nix>
|
||||
# - enables STUN and TURN
|
||||
# - only over UDP 3478, not firewall-forwarding any TURN port range
|
||||
# - uses stun_disco module (but with no options)
|
||||
# - <https://github.com/leo60228/dotfiles/blob/39b3abba3009bdc31413d4757ca2f882a33eec8b/files/ejabberd.yml>
|
||||
# - <https://github.com/Mic92/dotfiles/blob/ddf0f4821f554f7667fc803344657367c55fb9e6/nixos/eve/modules/ejabberd.nix>
|
||||
# - <nixpkgs:nixos/tests/xmpp/ejabberd.nix>
|
||||
# - 2013: <https://github.com/processone/ejabberd/blob/master/ejabberd.yml.example>
|
||||
#
|
||||
# compliance tests:
|
||||
# - <https://compliance.conversations.im/server/uninsane.org/#xep0352>
|
||||
#
|
||||
# administration:
|
||||
# - `sudo -u ejabberd ejabberdctl help`
|
||||
#
|
||||
# federation/support matrix:
|
||||
# - avatars
|
||||
# - nixnet.services + dino: works in MUCs but not DMs (as of 2023 H1)
|
||||
# - movim.eu + dino: works in DMs, MUCs untested (as of 2023/08/29)
|
||||
# - calls
|
||||
# - local + dino: audio, video, works in DMs (as of 2023/08/29)
|
||||
# - movim.eu + dino: audio, video, works in DMs, no matter which side initiates (as of 2023/08/30)
|
||||
# - +native-cell-number@cheogram.com + dino: audio works in DMs, no matter which side initiates (as of 2023/09/01)
|
||||
# - can receive calls even if sender isn't in my roster
|
||||
# - this is presumably using JMP.chat's SIP servers, which then convert it to XMPP call
|
||||
#
|
||||
# bugs:
|
||||
# - 2023/09/01: will randomly stop federating. `systemctl restart ejabberd` fixes, but takes 10 minutes.
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
# TODO: this range could be larger, but right now that's costly because each element is its own UPnP forward
|
||||
# TURN port range (inclusive)
|
||||
turnPortLow = 49152;
|
||||
turnPortHigh = 49167;
|
||||
turnPortRange = lib.range turnPortLow turnPortHigh;
|
||||
in
|
||||
# XXX(2023/10/15): disabled in favor of Prosody.
|
||||
# everything configured below was fine: used ejabberd for several months.
|
||||
lib.mkIf false
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "ejabberd"; group = "ejabberd"; path = "/var/lib/ejabberd"; }
|
||||
];
|
||||
sane.ports.ports = lib.mkMerge ([
|
||||
{
|
||||
"3478" = {
|
||||
protocol = [ "tcp" "udp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-stun-turn";
|
||||
};
|
||||
"5222" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-client-to-server";
|
||||
};
|
||||
"5223" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpps-client-to-server"; # XMPP over TLS
|
||||
};
|
||||
"5269" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-server-to-server";
|
||||
};
|
||||
"5270" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpps-server-to-server"; # XMPP over TLS
|
||||
};
|
||||
"5280" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-bosh";
|
||||
};
|
||||
"5281" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-bosh-https";
|
||||
};
|
||||
"5349" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-stun-turn-over-tls";
|
||||
};
|
||||
"5443" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-web-services"; # file uploads, websockets, admin
|
||||
};
|
||||
}
|
||||
] ++ (builtins.map
|
||||
(port: {
|
||||
"${builtins.toString port}" = let
|
||||
count = port - turnPortLow + 1;
|
||||
numPorts = turnPortHigh - turnPortLow + 1;
|
||||
in {
|
||||
protocol = [ "tcp" "udp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-turn-${builtins.toString count}-of-${builtins.toString numPorts}";
|
||||
};
|
||||
})
|
||||
turnPortRange
|
||||
));
|
||||
|
||||
# this ejabberd config uses builtin STUN/TURN server, so hack to ensure no other implementation fights for ports
|
||||
services.coturn.enable = false;
|
||||
|
||||
# provide access to certs
|
||||
# TODO: this should just be `acme`. then we also add nginx to the `acme` group.
|
||||
# why is /var/lib/acme/* owned by `nginx` group??
|
||||
users.users.ejabberd.extraGroups = [ "nginx" ];
|
||||
|
||||
security.acme.certs."uninsane.org".extraDomainNames = [
|
||||
"xmpp.uninsane.org"
|
||||
"muc.xmpp.uninsane.org"
|
||||
"pubsub.xmpp.uninsane.org"
|
||||
"upload.xmpp.uninsane.org"
|
||||
"vjid.xmpp.uninsane.org"
|
||||
];
|
||||
|
||||
# exists so the XMPP server's cert can obtain altNames for all its resources
|
||||
services.nginx.virtualHosts."xmpp.uninsane.org" = {
|
||||
useACMEHost = "uninsane.org";
|
||||
};
|
||||
services.nginx.virtualHosts."muc.xmpp.uninsane.org" = {
|
||||
useACMEHost = "uninsane.org";
|
||||
};
|
||||
services.nginx.virtualHosts."pubsub.xmpp.uninsane.org" = {
|
||||
useACMEHost = "uninsane.org";
|
||||
};
|
||||
services.nginx.virtualHosts."upload.xmpp.uninsane.org" = {
|
||||
useACMEHost = "uninsane.org";
|
||||
};
|
||||
services.nginx.virtualHosts."vjid.xmpp.uninsane.org" = {
|
||||
useACMEHost = "uninsane.org";
|
||||
};
|
||||
|
||||
sane.dns.zones."uninsane.org".inet = {
|
||||
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
|
||||
A."xmpp" = "%ANATIVE%";
|
||||
CNAME."muc.xmpp" = "xmpp";
|
||||
CNAME."pubsub.xmpp" = "xmpp";
|
||||
CNAME."upload.xmpp" = "xmpp";
|
||||
CNAME."vjid.xmpp" = "xmpp";
|
||||
|
||||
# _Service._Proto.Name TTL Class SRV Priority Weight Port Target
|
||||
# - <https://xmpp.org/extensions/xep-0368.html>
|
||||
# something's requesting the SRV records for muc.xmpp, so let's include it
|
||||
# nothing seems to request XMPP SRVs for the other records (except @)
|
||||
# lower numerical priority field tells clients to prefer this method
|
||||
SRV."_xmpps-client._tcp.muc.xmpp" = "3 50 5223 xmpp";
|
||||
SRV."_xmpps-server._tcp.muc.xmpp" = "3 50 5270 xmpp";
|
||||
SRV."_xmpp-client._tcp.muc.xmpp" = "5 50 5222 xmpp";
|
||||
SRV."_xmpp-server._tcp.muc.xmpp" = "5 50 5269 xmpp";
|
||||
|
||||
SRV."_xmpps-client._tcp" = "3 50 5223 xmpp";
|
||||
SRV."_xmpps-server._tcp" = "3 50 5270 xmpp";
|
||||
SRV."_xmpp-client._tcp" = "5 50 5222 xmpp";
|
||||
SRV."_xmpp-server._tcp" = "5 50 5269 xmpp";
|
||||
|
||||
SRV."_stun._udp" = "5 50 3478 xmpp";
|
||||
SRV."_stun._tcp" = "5 50 3478 xmpp";
|
||||
SRV."_stuns._tcp" = "5 50 5349 xmpp";
|
||||
SRV."_turn._udp" = "5 50 3478 xmpp";
|
||||
SRV."_turn._tcp" = "5 50 3478 xmpp";
|
||||
SRV."_turns._tcp" = "5 50 5349 xmpp";
|
||||
};
|
||||
|
||||
# TODO: allocate UIDs/GIDs ?
|
||||
services.ejabberd.enable = true;
|
||||
services.ejabberd.configFile = "/var/lib/ejabberd/ejabberd.yaml";
|
||||
systemd.services.ejabberd.preStart = let
|
||||
config-in = pkgs.writeText "ejabberd.yaml.in" (lib.generators.toYAML {} {
|
||||
hosts = [ "uninsane.org" ];
|
||||
# none | emergency | alert | critical | error | warning | notice | info | debug
|
||||
loglevel = "debug";
|
||||
acme.auto = false;
|
||||
certfiles = [ "/var/lib/acme/uninsane.org/full.pem" ];
|
||||
# ca_file = "${pkgs.cacert.unbundled}/etc/ssl/certs/";
|
||||
# ca_file = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||
|
||||
pam_userinfotype = "jid";
|
||||
acl = {
|
||||
admin.user = [ "colin@uninsane.org" ];
|
||||
local.user_regexp = "";
|
||||
loopback.ip = [ "127.0.0.0/8" "::1/128" ];
|
||||
};
|
||||
|
||||
access_rules = {
|
||||
local.allow = "local";
|
||||
c2s_access.allow = "all";
|
||||
announce.allow = "admin";
|
||||
configure.allow = "admin";
|
||||
muc_create.allow = "local";
|
||||
pubsub_createnode_access.allow = "all";
|
||||
trusted_network.allow = "loopback";
|
||||
};
|
||||
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/basic/#shaper-rules>
|
||||
shaper_rules = {
|
||||
# setting this to above 1 may break outgoing messages
|
||||
# - maybe some servers rate limit? or just don't understand simultaneous connections?
|
||||
max_s2s_connections = 1;
|
||||
max_user_sessions = 10;
|
||||
max_user_offline_messages = 5000;
|
||||
c2s_shaper.fast = "all";
|
||||
s2s_shaper.med = "all";
|
||||
};
|
||||
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/basic/#shapers>
|
||||
# this limits the bytes/sec.
|
||||
# for example, burst: 3_000_000 and rate: 100_000 means:
|
||||
# - each client has a BW budget that accumulates 100kB/sec and is capped at 3 MB
|
||||
shaper.fast = 1000000;
|
||||
shaper.med = 500000;
|
||||
# shaper.fast.rate = 1000000;
|
||||
# shaper.fast.burst_size = 10000000;
|
||||
# shaper.med.rate = 500000;
|
||||
# shaper.med.burst_size = 5000000;
|
||||
|
||||
# see: <https://docs.ejabberd.im/admin/configuration/listen/>
|
||||
# s2s_use_starttls = true;
|
||||
s2s_use_starttls = "optional";
|
||||
# lessens 504: remote-server-timeout errors
|
||||
# see: <https://github.com/processone/ejabberd/issues/3105#issuecomment-562182967>
|
||||
negotiation_timeout = 60;
|
||||
|
||||
listen = [
|
||||
{
|
||||
port = 5222;
|
||||
module = "ejabberd_c2s";
|
||||
shaper = "c2s_shaper";
|
||||
starttls = true;
|
||||
access = "c2s_access";
|
||||
}
|
||||
{
|
||||
port = 5223;
|
||||
module = "ejabberd_c2s";
|
||||
shaper = "c2s_shaper";
|
||||
tls = true;
|
||||
access = "c2s_access";
|
||||
}
|
||||
{
|
||||
port = 5269;
|
||||
module = "ejabberd_s2s_in";
|
||||
shaper = "s2s_shaper";
|
||||
}
|
||||
{
|
||||
port = 5270;
|
||||
module = "ejabberd_s2s_in";
|
||||
shaper = "s2s_shaper";
|
||||
tls = true;
|
||||
}
|
||||
{
|
||||
port = 5443;
|
||||
module = "ejabberd_http";
|
||||
tls = true;
|
||||
request_handlers = {
|
||||
"/admin" = "ejabberd_web_admin"; # TODO: ensure this actually works
|
||||
"/api" = "mod_http_api"; # ejabberd API endpoint (to control server)
|
||||
"/bosh" = "mod_bosh";
|
||||
"/upload" = "mod_http_upload";
|
||||
"/ws" = "ejabberd_http_ws";
|
||||
# "/.well-known/host-meta" = "mod_host_meta";
|
||||
# "/.well-known/host-meta.json" = "mod_host_meta";
|
||||
};
|
||||
}
|
||||
{
|
||||
# STUN+TURN TCP
|
||||
# note that the full port range should be forwarded ("not NAT'd")
|
||||
# `use_turn=true` enables both TURN *and* STUN
|
||||
port = 3478;
|
||||
module = "ejabberd_stun";
|
||||
transport = "tcp";
|
||||
use_turn = true;
|
||||
turn_min_port = turnPortLow;
|
||||
turn_max_port = turnPortHigh;
|
||||
turn_ipv4_address = "%ANATIVE%";
|
||||
}
|
||||
{
|
||||
# STUN+TURN UDP
|
||||
port = 3478;
|
||||
module = "ejabberd_stun";
|
||||
transport = "udp";
|
||||
use_turn = true;
|
||||
turn_min_port = turnPortLow;
|
||||
turn_max_port = turnPortHigh;
|
||||
turn_ipv4_address = "%ANATIVE%";
|
||||
}
|
||||
{
|
||||
# STUN+TURN TLS over TCP
|
||||
port = 5349;
|
||||
module = "ejabberd_stun";
|
||||
transport = "tcp";
|
||||
tls = true;
|
||||
certfile = "/var/lib/acme/uninsane.org/full.pem";
|
||||
use_turn = true;
|
||||
turn_min_port = turnPortLow;
|
||||
turn_max_port = turnPortHigh;
|
||||
turn_ipv4_address = "%ANATIVE%";
|
||||
}
|
||||
];
|
||||
|
||||
# TODO: enable mod_fail2ban
|
||||
# TODO(low): look into mod_http_fileserver for serving macros?
|
||||
modules = {
|
||||
# mod_adhoc = {};
|
||||
# mod_announce = {
|
||||
# access = "admin";
|
||||
# };
|
||||
# allows users to set avatars in vCard
|
||||
# - <https://docs.ejabberd.im/admin/configuration/modules/#mod-avatar>
|
||||
mod_avatar = {};
|
||||
mod_caps = {}; # for mod_pubsub
|
||||
mod_carboncopy = {}; # allows multiple clients to receive a user's message
|
||||
# queues messages when recipient is offline, including PEP and presence messages.
|
||||
# compliance test suggests this be enabled
|
||||
mod_client_state = {};
|
||||
|
||||
# mod_conversejs: TODO: enable once on 21.12
|
||||
# allows clients like Dino to discover where to upload files
|
||||
mod_disco.server_info = [
|
||||
{
|
||||
modules = "all";
|
||||
name = "abuse-addresses";
|
||||
urls = [
|
||||
"mailto:admin.xmpp@uninsane.org"
|
||||
"xmpp:colin@uninsane.org"
|
||||
];
|
||||
}
|
||||
{
|
||||
modules = "all";
|
||||
name = "admin-addresses";
|
||||
urls = [
|
||||
"mailto:admin.xmpp@uninsane.org"
|
||||
"xmpp:colin@uninsane.org"
|
||||
];
|
||||
}
|
||||
];
|
||||
mod_http_upload = {
|
||||
host = "upload.xmpp.uninsane.org";
|
||||
hosts = [ "upload.xmpp.uninsane.org" ];
|
||||
put_url = "https://@HOST@:5443/upload";
|
||||
dir_mode = "0750";
|
||||
file_mode = "0750";
|
||||
rm_on_unregister = false;
|
||||
};
|
||||
# allow discoverability of BOSH and websocket endpoints
|
||||
# TODO: enable once on ejabberd 22.05 (presently 21.04)
|
||||
# mod_host_meta = {};
|
||||
mod_jidprep = {}; # probably not needed: lets clients normalize jids
|
||||
mod_last = {}; # allow other users to know when i was last online
|
||||
mod_mam = {
|
||||
# Mnesia is limited to 2GB, better to use an SQL backend
|
||||
# For small servers SQLite is a good fit and is very easy
|
||||
# to configure. Uncomment this when you have SQL configured:
|
||||
# db_type: sql
|
||||
assume_mam_usage = true;
|
||||
default = "always";
|
||||
};
|
||||
mod_muc = {
|
||||
access = [ "allow" ];
|
||||
access_admin = { allow = "admin"; };
|
||||
access_create = "muc_create";
|
||||
access_persistent = "muc_create";
|
||||
access_mam = [ "allow" ];
|
||||
history_size = 100; # messages to show new participants
|
||||
host = "muc.xmpp.uninsane.org";
|
||||
hosts = [ "muc.xmpp.uninsane.org" ];
|
||||
default_room_options = {
|
||||
anonymous = false;
|
||||
lang = "en";
|
||||
persistent = true;
|
||||
mam = true;
|
||||
};
|
||||
};
|
||||
mod_muc_admin = {};
|
||||
mod_offline = {
|
||||
# store messages for a user when they're offline (TODO: understand multi-client workflow?)
|
||||
access_max_user_messages = "max_user_offline_messages";
|
||||
store_groupchat = true;
|
||||
};
|
||||
mod_ping = {};
|
||||
mod_privacy = {}; # deprecated, but required for `ejabberctl export_piefxis`
|
||||
mod_private = {}; # allow local clients to persist arbitrary data on my server
|
||||
# push notifications to services integrated with e.g. Apple/Android.
|
||||
# default is for a maximum amount of PII to be withheld, since these push notifs
|
||||
# generally traverse 3rd party services. can opt to include message body, etc, though.
|
||||
mod_push = {};
|
||||
# i don't fully understand what this does, but it seems aimed at making push notifs more reliable.
|
||||
mod_push_keepalive = {};
|
||||
mod_roster = {
|
||||
versioning = true;
|
||||
};
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-s2s-dialback>
|
||||
# s2s dialback to verify inbound messages
|
||||
# unclear to what degree the XMPP network requires this
|
||||
mod_s2s_dialback = {};
|
||||
mod_shared_roster = {}; # creates groups for @all, @online, and anything manually administered?
|
||||
mod_stream_mgmt = {
|
||||
# resend undelivered messages if the origin client is offline
|
||||
resend_on_timeout = "if_offline";
|
||||
};
|
||||
# fallback for when DNS-based STUN discovery is unsupported.
|
||||
# - see: <https://xmpp.org/extensions/xep-0215.html>
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-stun-disco>
|
||||
# people say to just keep this defaulted (i guess ejabberd knows to return its `host` option of uninsane.org?)
|
||||
mod_stun_disco = {};
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-vcard>
|
||||
mod_vcard = {
|
||||
allow_return_all = true; # all users are discoverable (?)
|
||||
host = "vjid.xmpp.uninsane.org";
|
||||
hosts = [ "vjid.xmpp.uninsane.org" ];
|
||||
search = true;
|
||||
};
|
||||
mod_vcard_xupdate = {}; # needed for avatars
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-pubsub>
|
||||
mod_pubsub = {
|
||||
#^ needed for avatars
|
||||
access_createnode = "pubsub_createnode_access";
|
||||
host = "pubsub.xmpp.uninsane.org";
|
||||
hosts = [ "pubsub.xmpp.uninsane.org" ];
|
||||
ignore_pep_from_offline = false;
|
||||
last_item_cache = true;
|
||||
plugins = [
|
||||
"pep"
|
||||
"flat"
|
||||
];
|
||||
force_node_config = {
|
||||
# ensure client bookmarks are private
|
||||
"storage:bookmarks:" = {
|
||||
"access_model" = "whitelist";
|
||||
};
|
||||
"urn:xmpp:avatar:data" = {
|
||||
"access_model" = "open";
|
||||
};
|
||||
"urn:xmpp:avatar:metadata" = {
|
||||
"access_model" = "open";
|
||||
};
|
||||
};
|
||||
};
|
||||
mod_version = {};
|
||||
};
|
||||
});
|
||||
sed = "${pkgs.gnused}/bin/sed";
|
||||
in ''
|
||||
ip=$(cat '${config.sane.services.dyn-dns.ipPath}')
|
||||
# config is 444 (not 644), so we want to write out-of-place and then atomically move
|
||||
# TODO: factor this out into `sane-woop` helper?
|
||||
rm -f /var/lib/ejabberd/ejabberd.yaml.new
|
||||
${sed} "s/%ANATIVE%/$ip/g" ${config-in} > /var/lib/ejabberd/ejabberd.yaml.new
|
||||
mv /var/lib/ejabberd/ejabberd.yaml{.new,}
|
||||
'';
|
||||
|
||||
sane.services.dyn-dns.restartOnChange = [ "ejabberd.service" ];
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
# nix configs to reference:
|
||||
# - <https://gitlab.com/simple-nixos-mailserver/nixos-mailserver>
|
||||
# - <https://github.com/nix-community/nur-combined/-/tree/master/repos/eh5/machines/srv-m/mail-rspamd.nix>
|
||||
# - postfix / dovecot / rspamd / stalwart-jmap / sogo
|
||||
#
|
||||
# rspamd:
|
||||
# - nixos: <https://nixos.wiki/wiki/Rspamd>
|
||||
# - guide: <https://rspamd.com/doc/quickstart.html>
|
||||
# - non-nixos example: <https://dataswamp.org/~solene/2021-07-13-smtpd-rspamd.html>
|
||||
#
|
||||
#
|
||||
# my rough understanding of the pieces:
|
||||
# - postfix handles SMTP protocol with the rest of the world.
|
||||
# - dovecot implements IMAP protocol.
|
||||
# - client auth (i.e. validate that user@uninsane.org is who they claim)
|
||||
# - "folders" (INBOX, JUNK) are internal to dovecot?
|
||||
# or where do folders live, on-disk?
|
||||
#
|
||||
# - non-local clients (i.e. me) interact with BOTH postfix and dovecot, but primarily dovecot:
|
||||
# - mail reading is done via IMAP (so, dovecot)
|
||||
# - mail sending is done via SMTP/submission port (so, postfix)
|
||||
# - but postfix delegates authorization of that outgoing mail to dovecot, on the server side
|
||||
#
|
||||
# - local clients (i.e. sendmail) interact only with postfix
|
||||
#
|
||||
# debugging: general connectivity issues
|
||||
# - test that inbound port 25 is unblocked:
|
||||
# - `curl https://canyouseeme.org/ --data 'port=25&IP=185.157.162.178' | grep 'see your service'`
|
||||
# - and retry with port 465, 587
|
||||
# - i think this API requires the queried IP match the source IP
|
||||
# - if necessary, `systemctl stop postfix` and `sudo nc -l 185.157.162.178 25`, then try https://canyouseeme.org
|
||||
|
||||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
./dovecot.nix
|
||||
./postfix.nix
|
||||
];
|
||||
|
||||
|
||||
#### SPAM FILTERING
|
||||
# services.rspamd.enable = true;
|
||||
# services.rspamd.postfix.enable = true;
|
||||
}
|
@@ -1,142 +0,0 @@
|
||||
# dovecot config options: <https://doc.dovecot.org/configuration_manual/>
|
||||
#
|
||||
# sieve docs:
|
||||
# - sieve language examples: <https://doc.dovecot.org/configuration_manual/sieve/examples/>
|
||||
# - sieve protocol/language: <https://proton.me/support/sieve-advanced-custom-filters>
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
sane.ports.ports."143" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-imap-imap.uninsane.org";
|
||||
};
|
||||
sane.ports.ports."993" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-imaps-imap.uninsane.org";
|
||||
};
|
||||
|
||||
# exists only to manage certs for dovecot
|
||||
services.nginx.virtualHosts."imap.uninsane.org" = {
|
||||
enableACME = true;
|
||||
};
|
||||
|
||||
sane.dns.zones."uninsane.org".inet = {
|
||||
CNAME."imap" = "native";
|
||||
};
|
||||
|
||||
sops.secrets."dovecot_passwd" = {
|
||||
owner = config.users.users.dovecot2.name;
|
||||
# TODO: debug why mail can't be sent without this being world-readable
|
||||
mode = "0444";
|
||||
};
|
||||
|
||||
# inspired by https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/
|
||||
services.dovecot2.enable = true;
|
||||
# services.dovecot2.enableLmtp = true;
|
||||
services.dovecot2.sslServerCert = "/var/lib/acme/imap.uninsane.org/fullchain.pem";
|
||||
services.dovecot2.sslServerKey = "/var/lib/acme/imap.uninsane.org/key.pem";
|
||||
services.dovecot2.enablePAM = false;
|
||||
|
||||
# sieve scripts require me to set a user for... idk why?
|
||||
services.dovecot2.mailUser = "colin";
|
||||
services.dovecot2.mailGroup = "users";
|
||||
users.users.colin.isSystemUser = lib.mkForce false;
|
||||
|
||||
services.dovecot2.extraConfig =
|
||||
let
|
||||
passwdFile = config.sops.secrets.dovecot_passwd.path;
|
||||
in
|
||||
''
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = ${passwdFile}
|
||||
}
|
||||
userdb {
|
||||
driver = passwd-file
|
||||
args = ${passwdFile}
|
||||
}
|
||||
|
||||
# allow postfix to query our auth db
|
||||
service auth {
|
||||
unix_listener auth {
|
||||
mode = 0660
|
||||
user = postfix
|
||||
group = postfix
|
||||
}
|
||||
}
|
||||
auth_mechanisms = plain login
|
||||
|
||||
# accept incoming messaging from postfix
|
||||
# service lmtp {
|
||||
# unix_listener dovecot-lmtp {
|
||||
# mode = 0600
|
||||
# user = postfix
|
||||
# group = postfix
|
||||
# }
|
||||
# }
|
||||
|
||||
# plugin {
|
||||
# sieve_plugins = sieve_imapsieve
|
||||
# }
|
||||
|
||||
mail_debug = yes
|
||||
auth_debug = yes
|
||||
# verbose_ssl = yes
|
||||
'';
|
||||
|
||||
services.dovecot2.mailboxes = {
|
||||
# special-purpose mailboxes: "All" "Archive" "Drafts" "Flagged" "Junk" "Sent" "Trash"
|
||||
# RFC6154 describes these special mailboxes: https://www.ietf.org/rfc/rfc6154.html
|
||||
# how these boxes are treated is 100% up to the client and server to decide.
|
||||
# client behavior:
|
||||
# iOS
|
||||
# - Drafts: ?
|
||||
# - Sent: works
|
||||
# - Trash: works
|
||||
# - Junk: works ("mark" -> "move to Junk")
|
||||
# aerc
|
||||
# - Drafts: works
|
||||
# - Sent: works
|
||||
# - Trash: no; deleted messages are actually deleted
|
||||
# use `:move trash` instead
|
||||
# - Junk: ?
|
||||
# Sent mailbox: all sent messages are copied to it. unclear if this happens server-side or client-side.
|
||||
Drafts = { specialUse = "Drafts"; auto = "create"; };
|
||||
Sent = { specialUse = "Sent"; auto = "create"; };
|
||||
Trash = { specialUse = "Trash"; auto = "create"; };
|
||||
Junk = { specialUse = "Junk"; auto = "create"; };
|
||||
};
|
||||
|
||||
services.dovecot2.mailPlugins = {
|
||||
perProtocol = {
|
||||
# imap.enable = [
|
||||
# "imap_sieve"
|
||||
# ];
|
||||
lda.enable = [
|
||||
"sieve"
|
||||
];
|
||||
# lmtp.enable = [
|
||||
# "sieve"
|
||||
# ];
|
||||
};
|
||||
};
|
||||
services.dovecot2.modules = [
|
||||
pkgs.dovecot_pigeonhole # enables sieve execution (?)
|
||||
];
|
||||
services.dovecot2.sieveScripts = {
|
||||
# if any messages fail to pass (or lack) DKIM, move them to Junk
|
||||
# XXX the key name ("after") is only used to order sieve execution/ordering
|
||||
after = builtins.toFile "ensuredkim.sieve" ''
|
||||
require "fileinto";
|
||||
|
||||
if not header :contains "Authentication-Results" "dkim=pass" {
|
||||
fileinto "Junk";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
@@ -1,53 +0,0 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
./nfs.nix
|
||||
./sftpgo.nix
|
||||
];
|
||||
|
||||
users.groups.export = {};
|
||||
|
||||
fileSystems."/var/export/media" = {
|
||||
# everything in here could be considered publicly readable (based on the viewer's legal jurisdiction)
|
||||
device = "/var/lib/uninsane/media";
|
||||
options = [ "rbind" ];
|
||||
};
|
||||
# fileSystems."/var/export/playground" = {
|
||||
# device = config.fileSystems."/mnt/persist/ext".device;
|
||||
# fsType = "btrfs";
|
||||
# options = [
|
||||
# "subvol=export-playground"
|
||||
# "compress=zstd"
|
||||
# "defaults"
|
||||
# ];
|
||||
# };
|
||||
# N.B.: the backing directory should be manually created here **as a btrfs subvolume** and with a quota.
|
||||
# - `sudo btrfs subvolume create /mnt/persist/ext/persist/var/export/playground`
|
||||
# - `sudo btrfs quota enable /mnt/persist/ext/persist/var/export/playground`
|
||||
# - `sudo btrfs quota rescan -sw /mnt/persist/ext/persist/var/export/playground`
|
||||
# to adjust the limits (which apply at the block layer, i.e. post-compression):
|
||||
# - `sudo btrfs qgroup limit 20G /mnt/persist/ext/persist/var/export/playground`
|
||||
# to query the quota/status:
|
||||
# - `sudo btrfs qgroup show -re /var/export/playground`
|
||||
sane.persist.sys.ext = [
|
||||
{ user = "root"; group = "export"; mode = "0775"; path = "/var/export/playground"; }
|
||||
];
|
||||
|
||||
sane.fs."/var/export/README.md" = {
|
||||
wantedBy = [ "nfs.service" "sftpgo.service" ];
|
||||
file.text = ''
|
||||
- media/ read-only: Videos, Music, Books, etc
|
||||
- playground/ read-write: use it to share files with other users of this server
|
||||
'';
|
||||
};
|
||||
|
||||
sane.fs."/var/export/playground/README.md" = {
|
||||
wantedBy = [ "nfs.service" "sftpgo.service" ];
|
||||
file.text = ''
|
||||
this directory is intentionally read+write by anyone with access (i.e. on the LAN).
|
||||
- share files
|
||||
- write poetry
|
||||
- be a friendly troll
|
||||
'';
|
||||
};
|
||||
}
|
@@ -1,110 +0,0 @@
|
||||
# docs:
|
||||
# - <https://nixos.wiki/wiki/NFS>
|
||||
# - <https://wiki.gentoo.org/wiki/Nfs-utils>
|
||||
# system files:
|
||||
# - /etc/exports
|
||||
# system services:
|
||||
# - nfs-server.service
|
||||
# - nfs-idmapd.service
|
||||
# - nfs-mountd.service
|
||||
# - nfsdcld.service
|
||||
# - rpc-statd.service
|
||||
# - rpcbind.service
|
||||
#
|
||||
# TODO: force files to be 755, or 750.
|
||||
# - could maybe be done with some mount option?
|
||||
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
services.nfs.server.enable = true;
|
||||
|
||||
# see which ports NFS uses with:
|
||||
# - `rpcinfo -p`
|
||||
sane.ports.ports."111" = {
|
||||
protocol = [ "tcp" "udp" ];
|
||||
visibleTo.lan = true;
|
||||
description = "NFS server portmapper";
|
||||
};
|
||||
sane.ports.ports."2049" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
description = "NFS server";
|
||||
};
|
||||
sane.ports.ports."4000" = {
|
||||
protocol = [ "udp" ];
|
||||
visibleTo.lan = true;
|
||||
description = "NFS server status daemon";
|
||||
};
|
||||
sane.ports.ports."4001" = {
|
||||
protocol = [ "tcp" "udp" ];
|
||||
visibleTo.lan = true;
|
||||
description = "NFS server lock daemon";
|
||||
};
|
||||
sane.ports.ports."4002" = {
|
||||
protocol = [ "tcp" "udp" ];
|
||||
visibleTo.lan = true;
|
||||
description = "NFS server mount daemon";
|
||||
};
|
||||
|
||||
# NFS4 allows these to float, but NFS3 mandates specific ports, so fix them for backwards compat.
|
||||
services.nfs.server.lockdPort = 4001;
|
||||
services.nfs.server.mountdPort = 4002;
|
||||
services.nfs.server.statdPort = 4000;
|
||||
|
||||
# format:
|
||||
# fspoint visibility(options)
|
||||
# options:
|
||||
# - see: <https://wiki.gentoo.org/wiki/Nfs-utils#Exports>
|
||||
# - see [man 5 exports](https://linux.die.net/man/5/exports)
|
||||
# - insecure: require clients use src port > 1024
|
||||
# - rw, ro (default)
|
||||
# - async, sync (default)
|
||||
# - no_subtree_check (default), subtree_check: verify not just that files requested by the client live
|
||||
# in the expected fs, but also that they live under whatever subdirectory of that fs is exported.
|
||||
# - no_root_squash, root_squash (default): map requests from uid 0 to user `nobody`.
|
||||
# - crossmnt: reveal filesystems that are mounted under this endpoint
|
||||
# - fsid: must be zero for the root export
|
||||
# - fsid=root is alias for fsid=0
|
||||
# - mountpoint[=/path]: only export the directory if it's a mountpoint. used to avoid exporting failed mounts.
|
||||
# - all_squash: rewrite all client requests such that they come from anonuid/anongid
|
||||
# - any files a user creates are owned by local anonuid/anongid.
|
||||
# - users can read any local file which anonuid/anongid would be able to read.
|
||||
# - users can't chown to/away from anonuid/anongid.
|
||||
# - users can chmod files they own, to anything (making them unreadable to non-`nfsuser` export users, like FTP).
|
||||
# - `stat` remains unchanged, returning the real UIDs/GIDs to the client.
|
||||
# - thus programs which check `uid` or `gid` before trying an operation may incorrectly conclude they can't perform some op.
|
||||
#
|
||||
# 10.0.0.0/8 to export both to LAN (readonly, unencrypted) and wg vpn (read-write, encrypted)
|
||||
services.nfs.server.exports =
|
||||
let
|
||||
fmtExport = { export, baseOpts, extraLanOpts ? [], extraVpnOpts ? [] }:
|
||||
let
|
||||
always = [ "subtree_check" ];
|
||||
lanOpts = always ++ baseOpts ++ extraLanOpts;
|
||||
vpnOpts = always ++ baseOpts ++ extraVpnOpts;
|
||||
in "${export} 10.78.79.0/22(${lib.concatStringsSep "," lanOpts}) 10.0.10.0/24(${lib.concatStringsSep "," vpnOpts})";
|
||||
in lib.concatStringsSep "\n" [
|
||||
(fmtExport {
|
||||
export = "/var/export";
|
||||
baseOpts = [ "crossmnt" "fsid=root" ];
|
||||
extraLanOpts = [ "ro" ];
|
||||
extraVpnOpts = [ "rw" "no_root_squash" ];
|
||||
})
|
||||
(fmtExport {
|
||||
export = "/var/export/playground";
|
||||
baseOpts = [
|
||||
"mountpoint"
|
||||
"all_squash"
|
||||
"rw"
|
||||
"anonuid=${builtins.toString config.users.users.nfsuser.uid}"
|
||||
"anongid=${builtins.toString config.users.groups.export.gid}"
|
||||
];
|
||||
})
|
||||
];
|
||||
|
||||
users.users.nfsuser = {
|
||||
description = "virtual user for anonymous NFS operations";
|
||||
group = "export";
|
||||
isSystemUser = true;
|
||||
};
|
||||
}
|
@@ -1,184 +0,0 @@
|
||||
# docs:
|
||||
# - <https://github.com/drakkan/sftpgo>
|
||||
# - config options: <https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md>
|
||||
# - config defaults: <https://github.com/drakkan/sftpgo/blob/main/sftpgo.json>
|
||||
# - nixos options: <repo:nixos/nixpkgs:nixos/modules/services/web-apps/sftpgo.nix>
|
||||
# - nixos example: <repo:nixos/nixpkgs:nixos/tests/sftpgo.nix>
|
||||
#
|
||||
# sftpgo is a FTP server that also supports WebDAV, SFTP, and web clients.
|
||||
#
|
||||
# TODO: change umask so sftpgo-created files default to 644.
|
||||
# - it does indeed appear that the 600 is not something sftpgo is explicitly doing.
|
||||
|
||||
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
let
|
||||
# user permissions:
|
||||
# - see <repo:drakkan/sftpgo:internal/dataprovider/user.go>
|
||||
# - "*" = grant all permissions
|
||||
# - read-only perms:
|
||||
# - "list" = list files and directories
|
||||
# - "download"
|
||||
# - rw perms:
|
||||
# - "upload"
|
||||
# - "overwrite" = allow uploads to replace existing files
|
||||
# - "delete" = delete files and directories
|
||||
# - "delete_files"
|
||||
# - "delete_dirs"
|
||||
# - "rename" = rename files and directories
|
||||
# - "rename_files"
|
||||
# - "rename_dirs"
|
||||
# - "create_dirs"
|
||||
# - "create_symlinks"
|
||||
# - "chmod"
|
||||
# - "chown"
|
||||
# - "chtimes" = change atime/mtime (access and modification times)
|
||||
#
|
||||
# home_dir:
|
||||
# - it seems (empirically) that a user can't cd above their home directory.
|
||||
# though i don't have a reference for that in the docs.
|
||||
authResponseSuccess = {
|
||||
status = 1;
|
||||
username = "anonymous";
|
||||
expiration_date = 0;
|
||||
home_dir = "/var/export";
|
||||
# uid/gid 0 means to inherit sftpgo uid.
|
||||
# - i.e. users can't read files which Linux user `sftpgo` can't read
|
||||
# - uploaded files belong to Linux user `sftpgo`
|
||||
# other uid/gid values aren't possible for localfs backend, unless i let sftpgo use `sudo`.
|
||||
uid = 0;
|
||||
gid = 0;
|
||||
# uid = 65534;
|
||||
# gid = 65534;
|
||||
max_sessions = 0;
|
||||
# quota_*: 0 means to not use SFTP's quota system
|
||||
quota_size = 0;
|
||||
quota_files = 0;
|
||||
permissions = {
|
||||
"/" = [ "list" "download" ];
|
||||
"/playground" = [
|
||||
# read-only:
|
||||
"list"
|
||||
"download"
|
||||
# write:
|
||||
"upload"
|
||||
"overwrite"
|
||||
"delete"
|
||||
"rename"
|
||||
"create_dirs"
|
||||
"create_symlinks"
|
||||
# intentionally omitted:
|
||||
# "chmod"
|
||||
# "chown"
|
||||
# "chtimes"
|
||||
];
|
||||
};
|
||||
upload_bandwidth = 0;
|
||||
download_bandwidth = 0;
|
||||
filters = {
|
||||
allowed_ip = [];
|
||||
denied_ip = [];
|
||||
};
|
||||
public_keys = [];
|
||||
# other fields:
|
||||
# ? groups
|
||||
# ? virtual_folders
|
||||
};
|
||||
authResponseFail = {
|
||||
username = "";
|
||||
};
|
||||
authSuccessJson = pkgs.writeText "sftp-auth-success.json" (builtins.toJSON authResponseSuccess);
|
||||
authFailJson = pkgs.writeText "sftp-auth-fail.json" (builtins.toJSON authResponseFail);
|
||||
unwrappedAuthProgram = pkgs.static-nix-shell.mkBash {
|
||||
pname = "sftpgo_external_auth_hook";
|
||||
src = ./.;
|
||||
pkgs = [ "coreutils" ];
|
||||
};
|
||||
authProgram = pkgs.writeShellScript "sftpgo-auth-hook" ''
|
||||
${unwrappedAuthProgram}/bin/sftpgo_external_auth_hook ${authFailJson} ${authSuccessJson}
|
||||
'';
|
||||
in
|
||||
{
|
||||
# Client initiates a FTP "control connection" on port 21.
|
||||
# - this handles the client -> server commands, and the server -> client status, but not the actual data
|
||||
# - file data, directory listings, etc need to be transferred on an ephemeral "data port".
|
||||
# - 50000-50100 is a common port range for this.
|
||||
sane.ports.ports = {
|
||||
"21" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
description = "colin-FTP server";
|
||||
};
|
||||
} // (sane-lib.mapToAttrs
|
||||
(port: {
|
||||
name = builtins.toString port;
|
||||
value = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
description = "colin-FTP server data port range";
|
||||
};
|
||||
})
|
||||
(lib.range 50000 50100)
|
||||
);
|
||||
|
||||
services.sftpgo = {
|
||||
enable = true;
|
||||
group = "export";
|
||||
settings = {
|
||||
ftpd = {
|
||||
bindings = [
|
||||
{
|
||||
# binding this means any wireguard client can connect
|
||||
address = "10.0.10.5";
|
||||
port = 21;
|
||||
debug = true;
|
||||
}
|
||||
{
|
||||
# binding this means any LAN client can connect
|
||||
address = "10.78.79.51";
|
||||
port = 21;
|
||||
debug = true;
|
||||
}
|
||||
];
|
||||
|
||||
# active mode is susceptible to "bounce attacks", without much benefit over passive mode
|
||||
disable_active_mode = true;
|
||||
hash_support = true;
|
||||
passive_port_range = {
|
||||
start = 50000;
|
||||
end = 50100;
|
||||
};
|
||||
|
||||
banner = ''
|
||||
Welcome, friends, to Colin's read-only FTP server! Also available via NFS on the same host.
|
||||
Username: "anonymous"
|
||||
Password: "anonymous"
|
||||
CONFIGURE YOUR CLIENT FOR "PASSIVE" mode, e.g. `ftp --passive uninsane.org`
|
||||
Please let me know if anything's broken or not as it should be. Otherwise, browse and DL freely :)
|
||||
'';
|
||||
|
||||
};
|
||||
data_provider = {
|
||||
driver = "memory";
|
||||
external_auth_hook = "${authProgram}";
|
||||
# track_quota:
|
||||
# - 0: disable quota tracking
|
||||
# - 1: quota is updated on every upload/delete, even if user has no quota restriction
|
||||
# - 2: quota is updated on every upload/delete, but only if user/folder has a quota restriction (default, i think)
|
||||
# track_quota = 2;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
users.users.sftpgo.extraGroups = [ "export" ];
|
||||
|
||||
systemd.services.sftpgo.serviceConfig = {
|
||||
ReadOnlyPaths = [ "/var/export" ];
|
||||
ReadWritePaths = [ "/var/export/playground" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p coreutils
|
||||
# vim: set filetype=bash :
|
||||
#
|
||||
# available environment variables:
|
||||
# - SFTPGO_AUTHD_USERNAME
|
||||
# - SFTPGO_AUTHD_USER
|
||||
# - SFTPGO_AUTHD_IP
|
||||
# - SFTPGO_AUTHD_PROTOCOL = { "DAV", "FTP", "HTTP", "SSH" }
|
||||
# - SFTPGO_AUTHD_PASSWORD
|
||||
# - SFTPGO_AUTHD_PUBLIC_KEY
|
||||
# - SFTPGO_AUTHD_KEYBOARD_INTERACTIVE
|
||||
# - SFTPGO_AUTHD_TLS_CERT
|
||||
#
|
||||
#
|
||||
# call with <script_name> /path/to/fail/response.json /path/to/success/response.json
|
||||
|
||||
|
||||
if [ "$SFTPGO_AUTHD_USERNAME" = "anonymous" ]; then
|
||||
cat "$2"
|
||||
else
|
||||
cat "$1"
|
||||
fi
|
@@ -1,127 +0,0 @@
|
||||
# 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"; }
|
||||
];
|
||||
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" = {
|
||||
addSSL = true;
|
||||
enableACME = true;
|
||||
# inherit kTLS;
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:8096";
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Protocol $scheme;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
|
||||
# Disable buffering when the nginx proxy gets very resource heavy upon streaming
|
||||
proxy_buffering off;
|
||||
'';
|
||||
};
|
||||
# locations."/web/" = {
|
||||
# proxyPass = "http://127.0.0.1:8096/web/index.html";
|
||||
# extraConfig = ''
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# proxy_set_header X-Forwarded-Protocol $scheme;
|
||||
# proxy_set_header X-Forwarded-Host $http_host;
|
||||
# '';
|
||||
# };
|
||||
locations."/socket" = {
|
||||
proxyPass = "http://127.0.0.1:8096";
|
||||
extraConfig = ''
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Protocol $scheme;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."jelly" = "native";
|
||||
|
||||
services.jellyfin.enable = true;
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
svc-cfg = config.services.komga;
|
||||
inherit (svc-cfg) user group port stateDir;
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ inherit user group; mode = "0700"; path = stateDir; }
|
||||
];
|
||||
|
||||
services.komga.enable = true;
|
||||
services.komga.port = 11319; # chosen at random
|
||||
|
||||
services.nginx.virtualHosts."komga.uninsane.org" = {
|
||||
addSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:${builtins.toString port}";
|
||||
};
|
||||
};
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."komga" = "native";
|
||||
}
|
@@ -1,85 +0,0 @@
|
||||
# docs:
|
||||
# - <repo:LemmyNet/lemmy:docker/federation/nginx.conf>
|
||||
# - <repo:LemmyNet/lemmy:docker/nginx.conf>
|
||||
# - <repo:LemmyNet/lemmy-ansible:templates/nginx.conf>
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
inherit (builtins) toString;
|
||||
inherit (lib) mkForce;
|
||||
uiPort = 1234; # default ui port is 1234
|
||||
backendPort = 8536; # default backend port is 8536
|
||||
#^ i guess the "backend" port is used for federation?
|
||||
pict-rs = pkgs.pict-rs.overrideAttrs (upstream: {
|
||||
# as of v 0.4.2, all non-GIF video is forcibly transcoded.
|
||||
# that breaks lemmy, because of the request latency.
|
||||
# and it eats up hella CPU.
|
||||
# pict-rs is iffy around video altogether: mp4 seems the best supported.
|
||||
postPatch = (upstream.postPatch or "") + ''
|
||||
substituteInPlace src/validate.rs \
|
||||
--replace 'if transcode_options.needs_reencode() {' 'if false {'
|
||||
'';
|
||||
});
|
||||
in {
|
||||
services.lemmy = {
|
||||
enable = true;
|
||||
settings.hostname = "lemmy.uninsane.org";
|
||||
# federation.debug forces outbound federation queries to be run synchronously
|
||||
# N.B.: this option might not be read for 0.17.0+? <https://github.com/LemmyNet/lemmy/blob/c32585b03429f0f76d1e4ff738786321a0a9df98/RELEASES.md#upgrade-instructions>
|
||||
# settings.federation.debug = true;
|
||||
settings.port = backendPort;
|
||||
ui.port = uiPort;
|
||||
database.createLocally = true;
|
||||
nginx.enable = true;
|
||||
};
|
||||
|
||||
systemd.services.lemmy.serviceConfig = {
|
||||
# fix to use a normal user so we can configure perms correctly
|
||||
DynamicUser = mkForce false;
|
||||
User = "lemmy";
|
||||
Group = "lemmy";
|
||||
};
|
||||
systemd.services.lemmy.environment = {
|
||||
RUST_BACKTRACE = "full";
|
||||
# RUST_LOG = "debug";
|
||||
# RUST_LOG = "trace";
|
||||
# upstream defaults LEMMY_DATABASE_URL = "postgres:///lemmy?host=/run/postgresql";
|
||||
# - Postgres complains that we didn't specify a user
|
||||
# lemmy formats the url as:
|
||||
# - postgres://{user}:{password}@{host}:{port}/{database}
|
||||
# SO suggests (https://stackoverflow.com/questions/3582552/what-is-the-format-for-the-postgresql-connection-string-url):
|
||||
# - 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?host=/run/postgresql"; # no PostgreSQL user name specified in startup packet
|
||||
# LEMMY_DATABASE_URL = mkForce "postgres://lemmy@?host=/run/postgresql"; # WORKS
|
||||
LEMMY_DATABASE_URL = mkForce "postgres://lemmy@/lemmy?host=/run/postgresql";
|
||||
};
|
||||
users.groups.lemmy = {};
|
||||
users.users.lemmy = {
|
||||
group = "lemmy";
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."lemmy.uninsane.org" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."lemmy" = "native";
|
||||
|
||||
#v DO NOT REMOVE: defaults to 0.3, instead of latest, so always need to explicitly set this.
|
||||
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.serviceConfig.ExecStart = lib.mkForce (lib.concatStringsSep " " [
|
||||
"${lib.getBin pict-rs}/bin/pict-rs run"
|
||||
"--media-max-frame-count" (builtins.toString (30*60*60))
|
||||
"--media-process-timeout 120"
|
||||
"--media-enable-full-video true" # allow audio
|
||||
]);
|
||||
}
|
@@ -1,165 +0,0 @@
|
||||
# docs: <https://nixos.wiki/wiki/Matrix>
|
||||
# docs: <https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-synapse>
|
||||
# example config: <https://github.com/matrix-org/synapse/blob/develop/docs/sample_config.yaml>
|
||||
#
|
||||
# ENABLING PUSH NOTIFICATIONS (with UnifiedPush/ntfy):
|
||||
# - Matrix "pushers" API spec: <https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3pushersset>
|
||||
# - first, view notification settings:
|
||||
# - obtain your client's auth token. e.g. Element -> profile -> help/about -> access token.
|
||||
# - `curl --header 'Authorization: Bearer <your_access_token>' localhost:8008/_matrix/client/v3/pushers | jq .`
|
||||
# - enable a new notification destination:
|
||||
# - `curl --header "Authorization: Bearer <your_access_token>" --data '{ "app_display_name": "<topic>", "app_id": "ntfy.uninsane.org", "data": { "url": "https://ntfy.uninsane.org/_matrix/push/v1/notify", "format": "event_id_only" }, "device_display_name": "<topic>", "kind": "http", "lang": "en-US", "profile_tag": "", "pushkey": "<topic>" }' localhost:8008/_matrix/client/v3/pushers/set`
|
||||
# - delete a notification destination by setting `kind` to `null` (otherwise, request is identical to above)
|
||||
#
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./discord-puppet.nix
|
||||
./irc.nix
|
||||
./signal.nix
|
||||
];
|
||||
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/matrix-synapse"; }
|
||||
];
|
||||
services.matrix-synapse.enable = true;
|
||||
services.matrix-synapse.settings = {
|
||||
# this changes the default log level from INFO to WARN.
|
||||
# maybe there's an easier way?
|
||||
log_config = ./synapse-log_level.yaml;
|
||||
server_name = "uninsane.org";
|
||||
|
||||
# services.matrix-synapse.enable_registration_captcha = true;
|
||||
# services.matrix-synapse.enable_registration_without_verification = true;
|
||||
enable_registration = true;
|
||||
# services.matrix-synapse.registration_shared_secret = "<shared key goes here>";
|
||||
|
||||
# default for listeners is port = 8448, tls = true, x_forwarded = false.
|
||||
# we change this because the server is situated behind nginx.
|
||||
listeners = [
|
||||
{
|
||||
port = 8008;
|
||||
bind_addresses = [ "127.0.0.1" ];
|
||||
type = "http";
|
||||
tls = false;
|
||||
x_forwarded = true;
|
||||
resources = [
|
||||
{
|
||||
names = [ "client" "federation" ];
|
||||
compress = false;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
ip_range_whitelist = [
|
||||
# to communicate with ntfy.uninsane.org push notifs.
|
||||
# TODO: move this to some non-shared loopback device: we don't want Matrix spouting http requests to *anything* on this machine
|
||||
"10.78.79.51"
|
||||
];
|
||||
|
||||
x_forwarded = true; # because we proxy matrix behind nginx
|
||||
max_upload_size = "100M"; # default is "50M"
|
||||
|
||||
admin_contact = "admin.matrix@uninsane.org";
|
||||
registrations_require_3pid = [ "email" ];
|
||||
};
|
||||
|
||||
services.matrix-synapse.extraConfigFiles = [
|
||||
config.sops.secrets."matrix_synapse_secrets.yaml".path
|
||||
];
|
||||
|
||||
systemd.services.matrix-synapse.postStart = ''
|
||||
ACCESS_TOKEN=$(${pkgs.coreutils}/bin/cat ${config.sops.secrets.matrix_access_token.path})
|
||||
TOPIC=$(${pkgs.coreutils}/bin/cat ${config.sops.secrets.ntfy-sh-topic.path})
|
||||
|
||||
echo "ensuring ntfy push gateway"
|
||||
${pkgs.curl}/bin/curl \
|
||||
--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\" }" \
|
||||
localhost:8008/_matrix/client/v3/pushers/set
|
||||
|
||||
echo "registered push gateways:"
|
||||
${pkgs.curl}/bin/curl \
|
||||
--header "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
localhost:8008/_matrix/client/v3/pushers \
|
||||
| ${pkgs.jq}/bin/jq .
|
||||
'';
|
||||
|
||||
|
||||
# new users may be registered on the CLI:
|
||||
# register_new_matrix_user -c /nix/store/8n6kcka37jhmi4qpd2r03aj71pkyh21s-homeserver.yaml http://localhost:8008
|
||||
#
|
||||
# or provide an registration token then can use to register through the client.
|
||||
# docs: https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/registration_tokens.md
|
||||
# first, grab your own user's access token (Help & About section in Element). then:
|
||||
# curl --header "Authorization: Bearer <my_token>" localhost:8008/_synapse/admin/v1/registration_tokens
|
||||
# create a token with unlimited uses:
|
||||
# curl -d '{}' --header "Authorization: Bearer <my_token>" localhost:8008/_synapse/admin/v1/registration_tokens/new
|
||||
# create a token with limited uses:
|
||||
# curl -d '{ "uses_allowed": 1 }' --header "Authorization: Bearer <my_token>" localhost:8008/_synapse/admin/v1/registration_tokens/new
|
||||
|
||||
# matrix chat server
|
||||
# TODO: was `publog`
|
||||
services.nginx.virtualHosts."matrix.uninsane.org" = {
|
||||
addSSL = true;
|
||||
enableACME = true;
|
||||
# inherit kTLS;
|
||||
|
||||
# TODO colin: replace this with something helpful to the viewer
|
||||
# locations."/".extraConfig = ''
|
||||
# return 404;
|
||||
# '';
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:8008";
|
||||
extraConfig = ''
|
||||
# allow uploading large files (matrix enforces a separate limit, downstream)
|
||||
client_max_body_size 512m;
|
||||
'';
|
||||
};
|
||||
# redirect browsers to the web client.
|
||||
# i don't think native matrix clients ever fetch the root.
|
||||
# ideally this would be put behind some user-agent test though.
|
||||
locations."= /" = {
|
||||
return = "301 https://web.matrix.uninsane.org";
|
||||
};
|
||||
|
||||
# locations."/_matrix" = {
|
||||
# proxyPass = "http://127.0.0.1:8008";
|
||||
# };
|
||||
};
|
||||
|
||||
# matrix web client
|
||||
# docs: https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-element-web
|
||||
services.nginx.virtualHosts."web.matrix.uninsane.org" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
# inherit kTLS;
|
||||
|
||||
root = pkgs.element-web.override {
|
||||
conf = {
|
||||
default_server_config."m.homeserver" = {
|
||||
"base_url" = "https://matrix.uninsane.org";
|
||||
"server_name" = "uninsane.org";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sane.dns.zones."uninsane.org".inet = {
|
||||
CNAME."matrix" = "native";
|
||||
CNAME."web.matrix" = "native";
|
||||
};
|
||||
|
||||
|
||||
sops.secrets."matrix_synapse_secrets.yaml" = {
|
||||
owner = config.users.users.matrix-synapse.name;
|
||||
};
|
||||
sops.secrets."matrix_access_token" = {
|
||||
owner = config.users.users.matrix-synapse.name;
|
||||
};
|
||||
# provide access to ntfy-sh-topic secret
|
||||
users.users.matrix-synapse.extraGroups = [ "ntfy-sh" ];
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
diff --git a/src/irc/ConnectionInstance.ts b/src/irc/ConnectionInstance.ts
|
||||
index 688036ca..3373fa27 100644
|
||||
--- a/src/irc/ConnectionInstance.ts
|
||||
+++ b/src/irc/ConnectionInstance.ts
|
||||
@@ -149,7 +149,7 @@ export class ConnectionInstance {
|
||||
if (this.dead) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
- ircReason = ircReason || reason;
|
||||
+ ircReason = "bye"; // don't reveal through the IRC quit message that we're a bridge
|
||||
log.info(
|
||||
"disconnect()ing %s@%s - %s", this.nick, this.domain, reason
|
||||
);
|
@@ -1,50 +0,0 @@
|
||||
diff --git a/config.schema.yml b/config.schema.yml
|
||||
index 2e71c8d6..42ba8ba1 100644
|
||||
--- a/config.schema.yml
|
||||
+++ b/config.schema.yml
|
||||
@@ -433,7 +433,7 @@ properties:
|
||||
type: "boolean"
|
||||
realnameFormat:
|
||||
type: "string"
|
||||
- enum: ["mxid","reverse-mxid"]
|
||||
+ enum: ["mxid","reverse-mxid","localpart"]
|
||||
ipv6:
|
||||
type: "object"
|
||||
properties:
|
||||
diff --git a/src/irc/IdentGenerator.ts b/src/irc/IdentGenerator.ts
|
||||
index 7a2b5cf1..50f7815a 100644
|
||||
--- a/src/irc/IdentGenerator.ts
|
||||
+++ b/src/irc/IdentGenerator.ts
|
||||
@@ -74,6 +74,9 @@ export class IdentGenerator {
|
||||
else if (server.getRealNameFormat() === "reverse-mxid") {
|
||||
realname = IdentGenerator.sanitiseRealname(IdentGenerator.switchAroundMxid(matrixUser));
|
||||
}
|
||||
+ else if (server.getRealNameFormat() == "localpart") {
|
||||
+ realname = IdentGenerator.sanitiseRealname(matrixUser.localpart);
|
||||
+ }
|
||||
else {
|
||||
throw Error('Invalid value for realNameFormat');
|
||||
}
|
||||
diff --git a/src/irc/IrcServer.ts b/src/irc/IrcServer.ts
|
||||
index 2af73ab4..895b9783 100644
|
||||
--- a/src/irc/IrcServer.ts
|
||||
+++ b/src/irc/IrcServer.ts
|
||||
@@ -101,7 +101,7 @@ export interface IrcServerConfig {
|
||||
};
|
||||
lineLimit: number;
|
||||
userModes?: string;
|
||||
- realnameFormat?: "mxid"|"reverse-mxid";
|
||||
+ realnameFormat?: "mxid"|"reverse-mxid"|"localpart";
|
||||
pingTimeoutMs: number;
|
||||
pingRateMs: number;
|
||||
kickOn: {
|
||||
@@ -289,7 +289,7 @@ export class IrcServer {
|
||||
return this.config.ircClients.userModes || "";
|
||||
}
|
||||
|
||||
- public getRealNameFormat(): "mxid"|"reverse-mxid" {
|
||||
+ public getRealNameFormat(): "mxid"|"reverse-mxid"|"localpart" {
|
||||
return this.config.ircClients.realnameFormat || "mxid";
|
||||
}
|
||||
|
||||
|
@@ -1,168 +0,0 @@
|
||||
# config docs:
|
||||
# - <https://github.com/matrix-org/matrix-appservice-irc/blob/develop/config.sample.yaml>
|
||||
# probably want to remove that.
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
ircServer = { name, additionalAddresses ? [], sasl ? true, port ? 6697 }: let
|
||||
lowerName = lib.toLower name;
|
||||
in {
|
||||
# XXX sasl: appservice doesn't support NickServ identification (only SASL, or PASS if sasl = false)
|
||||
inherit name additionalAddresses sasl port;
|
||||
ssl = true;
|
||||
botConfig = {
|
||||
# bot has no presence in IRC channel; only real Matrix users
|
||||
enabled = false;
|
||||
# this is the IRC username/nickname *of the bot* (not visible in channels): not of the end-user.
|
||||
# the irc username/nick of a mapped Matrix user is determined further down in `ircClients` section.
|
||||
# if `enabled` is false, then this name probably never shows up on the IRC side (?)
|
||||
nick = "uninsane";
|
||||
username = "uninsane";
|
||||
joinChannelsIfNoUsers = false;
|
||||
};
|
||||
dynamicChannels = {
|
||||
enabled = true;
|
||||
aliasTemplate = "#irc_${lowerName}_$CHANNEL";
|
||||
published = false; # false => irc rooms aren't listed in homeserver public rooms list
|
||||
federate = false; # false => Matrix users from other homeservers can't join IRC channels
|
||||
};
|
||||
ircClients = {
|
||||
nickTemplate = "$LOCALPARTsane"; # @colin:uninsane.org (Matrix) -> colinsane (IRC)
|
||||
realnameFormat = "reverse-mxid"; # @colin:uninsane.org (Matrix) -> org.uninsane:colin (IRC)
|
||||
# realnameFormat = "localpart"; # @colin:uninsane.org (Matrix) -> colin (IRC) -- but requires the mxid patch below
|
||||
# by default, Matrix will convert messages greater than (3) lines into a pastebin-like URL to send to IRC.
|
||||
lineLimit = 20;
|
||||
# Rizon in particular allows only 4 connections from one IP before a 30min ban.
|
||||
# that's effectively reduced to 2 during a netsplit, or maybe during a restart.
|
||||
# - https://wiki.rizon.net/index.php?title=Connection/Session_Limit_Exemptions
|
||||
# especially, misconfigurations elsewhere in this config may cause hundreds of connections
|
||||
# so this is a safeguard.
|
||||
maxClients = 2;
|
||||
# don't have the bridge disconnect me from IRC when idle.
|
||||
idleTimeout = 0;
|
||||
concurrentReconnectLimit = 2;
|
||||
reconnectIntervalMs = 60000;
|
||||
kickOn = {
|
||||
# remove Matrix user from room when...
|
||||
channelJoinFailure = false;
|
||||
ircConnectionFailure = false;
|
||||
userQuit = true;
|
||||
};
|
||||
};
|
||||
matrixClients = {
|
||||
userTemplate = "@irc_${lowerName}_$NICK"; # the :uninsane.org part is appended automatically
|
||||
};
|
||||
|
||||
# this will let this user message the appservice with `!join #<IRCChannel>` and the rest "Just Works"
|
||||
"@colin:uninsane.org" = "admin";
|
||||
|
||||
membershipLists = {
|
||||
enabled = true;
|
||||
global = {
|
||||
ircToMatrix = {
|
||||
initial = true;
|
||||
incremental = true;
|
||||
requireMatrixJoined = false;
|
||||
};
|
||||
matrixToIrc = {
|
||||
initial = true;
|
||||
incremental = true;
|
||||
};
|
||||
};
|
||||
ignoreIdleUsersOnStartup = {
|
||||
enabled = false; # false => always bridge users, even if idle
|
||||
};
|
||||
};
|
||||
# sync room description?
|
||||
bridgeInfoState = {
|
||||
enabled = true;
|
||||
initial = true;
|
||||
};
|
||||
|
||||
# for per-user IRC password:
|
||||
# - invite @irc_${lowerName}_NickServ:uninsane.org to a DM and type `help` => register
|
||||
# - invite the matrix-appservice-irc user to a DM and type `!help` => add PW to database
|
||||
# to validate that i'm authenticated on the IRC network, DM @irc_${lowerName}_NickServ:uninsane.org:
|
||||
# - send: `STATUS colinsane`
|
||||
# - response should be `3`: "user recognized as owner via password identification"
|
||||
# passwordEncryptionKeyPath = "/path/to/privkey"; # appservice will generate its own if unspecified
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
nixpkgs.overlays = [
|
||||
(next: prev: {
|
||||
matrix-appservice-irc = prev.matrix-appservice-irc.overrideAttrs (super: {
|
||||
patches = super.patches or [] ++ [
|
||||
./irc-no-reveal-bridge.patch
|
||||
# ./irc-no-reveal-mxid.patch
|
||||
];
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: mode?
|
||||
{ user = "matrix-appservice-irc"; group = "matrix-appservice-irc"; path = "/var/lib/matrix-appservice-irc"; }
|
||||
];
|
||||
|
||||
# XXX: matrix-appservice-irc PreStart tries to chgrp the registration.yml to matrix-synapse,
|
||||
# which requires matrix-appservice-irc to be of that group
|
||||
users.users.matrix-appservice-irc.extraGroups = [ "matrix-synapse" ];
|
||||
# weird race conditions around registration.yml mean we want matrix-synapse to be of matrix-appservice-irc group too.
|
||||
users.users.matrix-synapse.extraGroups = [ "matrix-appservice-irc" ];
|
||||
|
||||
services.matrix-synapse.settings.app_service_config_files = [
|
||||
"/var/lib/matrix-appservice-irc/registration.yml" # auto-created by irc appservice
|
||||
];
|
||||
|
||||
services.matrix-appservice-irc.enable = true;
|
||||
services.matrix-appservice-irc.registrationUrl = "http://127.0.0.1:8009";
|
||||
services.matrix-appservice-irc.settings = {
|
||||
homeserver = {
|
||||
url = "http://127.0.0.1:8008";
|
||||
dropMatrixMessagesAfterSecs = 300;
|
||||
domain = "uninsane.org";
|
||||
enablePresence = true;
|
||||
bindPort = 9999;
|
||||
bindHost = "127.0.0.1";
|
||||
};
|
||||
|
||||
ircService = {
|
||||
servers = {
|
||||
"irc.esper.net" = ircServer {
|
||||
name = "esper";
|
||||
sasl = false;
|
||||
# notable channels:
|
||||
# - #merveilles
|
||||
};
|
||||
"irc.libera.chat" = ircServer {
|
||||
name = "libera";
|
||||
sasl = false;
|
||||
# notable channels:
|
||||
# - #hare
|
||||
# - #mnt-reform
|
||||
};
|
||||
"irc.myanonamouse.net" = ircServer {
|
||||
name = "MyAnonamouse";
|
||||
additionalAddresses = [ "irc2.myanonamouse.net" ];
|
||||
sasl = false;
|
||||
};
|
||||
"irc.oftc.net" = ircServer {
|
||||
name = "oftc";
|
||||
sasl = false;
|
||||
# notable channels:
|
||||
# - #sxmo
|
||||
# - #sxmo-offtopic
|
||||
};
|
||||
"irc.rizon.net" = ircServer { name = "Rizon"; };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.matrix-appservice-irc.serviceConfig = {
|
||||
# XXX 2023/06/20: nixos specifies this + @aio and @memlock as forbidden
|
||||
# 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";
|
||||
};
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
# config options:
|
||||
# - <https://github.com/mautrix/signal/blob/master/mautrix_signal/example-config.yaml>
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "mautrix-signal"; group = "mautrix-signal"; path = "/var/lib/mautrix-signal"; }
|
||||
{ user = "signald"; group = "signald"; path = "/var/lib/signald"; }
|
||||
];
|
||||
|
||||
# allow synapse to read the registration file
|
||||
users.users.matrix-synapse.extraGroups = [ "mautrix-signal" ];
|
||||
|
||||
services.signald.enable = true;
|
||||
services.mautrix-signal.enable = true;
|
||||
services.mautrix-signal.environmentFile =
|
||||
config.sops.secrets.mautrix_signal_env.path;
|
||||
|
||||
services.mautrix-signal.settings.signal.socket_path = "/run/signald/signald.sock";
|
||||
services.mautrix-signal.settings.homeserver.domain = "uninsane.org";
|
||||
services.mautrix-signal.settings.bridge.permissions."@colin:uninsane.org" = "admin";
|
||||
services.matrix-synapse.settings.app_service_config_files = [
|
||||
# auto-created by mautrix-signal service
|
||||
"/var/lib/mautrix-signal/signal-registration.yaml"
|
||||
];
|
||||
|
||||
systemd.services.mautrix-signal.serviceConfig = {
|
||||
# allow communication to signald
|
||||
SupplementaryGroups = [ "signald" ];
|
||||
ReadWritePaths = [ "/run/signald" ];
|
||||
};
|
||||
|
||||
sops.secrets."mautrix_signal_env" = {
|
||||
mode = "0440";
|
||||
owner = config.users.users.mautrix-signal.name;
|
||||
group = config.users.users.matrix-synapse.name;
|
||||
};
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
# ntfy: UnifiedPush notification delivery system
|
||||
# - used to get push notifications out of Matrix and onto a Phone (iOS, Android, or a custom client)
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
./ntfy-waiter.nix
|
||||
./ntfy-sh.nix
|
||||
];
|
||||
sops.secrets."ntfy-sh-topic" = {
|
||||
mode = "0440";
|
||||
owner = config.users.users.ntfy-sh.name;
|
||||
group = config.users.users.ntfy-sh.name;
|
||||
};
|
||||
}
|
@@ -1,92 +0,0 @@
|
||||
# ntfy: UnifiedPush notification delivery system
|
||||
# - used to get push notifications out of Matrix and onto a Phone (iOS, Android, or a custom client)
|
||||
#
|
||||
# config options:
|
||||
# - <https://docs.ntfy.sh/config/#config-options>
|
||||
#
|
||||
# usage:
|
||||
# - ntfy sub https://ntfy.uninsane.org/TOPIC
|
||||
# - ntfy pub https://ntfy.uninsane.org/TOPIC "my message"
|
||||
# in production, TOPIC is a shared secret between the publisher (Matrix homeserver) and the subscriber (phone)
|
||||
#
|
||||
# administering:
|
||||
# - sudo -u ntfy-sh ntfy access
|
||||
#
|
||||
# debugging:
|
||||
# - make sure that the keepalives are good:
|
||||
# - on the subscriber machine, run `lsof -i4` to find the port being used
|
||||
# - `sudo tcpdump tcp port <p>`
|
||||
# - shouldn't be too spammy
|
||||
#
|
||||
# matrix integration:
|
||||
# - the user must manually point synapse to the ntfy endpoint:
|
||||
# - `curl --header "Authorization: <your_token>" --data '{ "app_display_name": "sane-nix moby", "app_id": "ntfy.uninsane.org", "data": { "url": "https://ntfy.uninsane.org/_matrix/push/v1/notify", "format": "event_id_only" }, "device_display_name": "sane-nix moby", "kind": "http", "lang": "en-US", "profile_tag": "", "pushkey": "https://ntfy.uninsane.org/TOPIC" }' localhost:8008/_matrix/client/v3/pushers/set`
|
||||
# where the token is grabbed from Element's help&about page when logged in
|
||||
# - to remove, send this `curl` with `"kind": null`
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
# subscribers need a non-443 public port to listen on as a way to easily differentiate this traffic
|
||||
# at the IP layer, to enable e.g. wake-on-lan.
|
||||
altPort = 2587;
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# not 100% necessary to persist this, but ntfy does keep a 12hr (by default) cache
|
||||
# for pushing notifications to users who become offline.
|
||||
# ACLs also live here.
|
||||
{ user = "ntfy-sh"; group ="ntfy-sh"; path = "/var/lib/ntfy-sh"; }
|
||||
];
|
||||
|
||||
services.ntfy-sh.enable = true;
|
||||
services.ntfy-sh.settings = {
|
||||
base-url = "https://ntfy.uninsane.org";
|
||||
behind-proxy = true; # not sure if needed
|
||||
# keepalive interval is a ntfy-specific keepalive thing, where it sends actual data down the wire.
|
||||
# it's not simple TCP keepalive.
|
||||
# defaults to 45s.
|
||||
# note that the client may still do its own TCP-level keepalives, typically every 30s
|
||||
keepalive-interval = "15m";
|
||||
log-level = "trace"; # trace, debug, info (default), warn, error
|
||||
auth-default-access = "deny-all";
|
||||
};
|
||||
systemd.services.ntfy-sh.serviceConfig.DynamicUser = lib.mkForce false;
|
||||
systemd.services.ntfy-sh.preStart = ''
|
||||
# make this specific topic read-write by world
|
||||
# it would be better to use the token system, but that's extra complexity for e.g.
|
||||
# how do i plumb a secret into the Matrix notification pusher
|
||||
#
|
||||
# note that this will fail upon first run, i.e. before ntfy has created its db.
|
||||
# just restart the service.
|
||||
topic=$(cat ${config.sops.secrets.ntfy-sh-topic.path})
|
||||
${pkgs.ntfy-sh}/bin/ntfy access everyone "$topic" read-write
|
||||
'';
|
||||
|
||||
|
||||
services.nginx.virtualHosts."ntfy.uninsane.org" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
listen = [
|
||||
{ addr = "0.0.0.0"; port = altPort; ssl = true; }
|
||||
{ addr = "0.0.0.0"; port = 443; ssl = true; }
|
||||
{ addr = "0.0.0.0"; port = 80; ssl = false; }
|
||||
];
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:2586";
|
||||
proxyWebsockets = true; #< support websocket upgrades. without that, `ntfy sub` hangs silently
|
||||
recommendedProxySettings = true; #< adds headers so ntfy logs include the real IP
|
||||
extraConfig = ''
|
||||
# absurdly long timeout (86400s=24h) so that we never hang up on clients.
|
||||
# make sure the client is smart enough to detect a broken proxy though!
|
||||
proxy_read_timeout 86400s;
|
||||
'';
|
||||
};
|
||||
};
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."ntfy" = "native";
|
||||
|
||||
sane.ports.ports."${builtins.toString altPort}" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-ntfy.uninsane.org";
|
||||
};
|
||||
}
|
@@ -1,151 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p ntfy-sh
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LISTEN_QUEUE = 3
|
||||
WAKE_MESSAGE = b'notification\n'
|
||||
|
||||
class Client:
|
||||
def __init__(self, sock, addr_info, live_after: float):
|
||||
self.live_after = live_after
|
||||
self.sock = sock
|
||||
self.addr_info = addr_info
|
||||
|
||||
def __cmp__(self, other: 'Client'):
|
||||
return cmp(self.addr_info, other.addr_info)
|
||||
|
||||
def try_notify(self, message: bytes) -> bool:
|
||||
"""
|
||||
returns true if we send a packet to notify client.
|
||||
fals otherwise (e.g. the socket is dead).
|
||||
"""
|
||||
ttl = self.live_after - time.time()
|
||||
if ttl > 0:
|
||||
logger.debug(f"sleeping {ttl:.2f}s until client {self.addr_info} is ready to receive notification")
|
||||
time.sleep(ttl)
|
||||
|
||||
try:
|
||||
self.sock.sendall(message)
|
||||
except Exception as e:
|
||||
logger.warning(f"failed to notify client {self.addr_info} {e}")
|
||||
return False
|
||||
else:
|
||||
logger.info(f"successfully notified {self.addr_info}: {message}")
|
||||
return True
|
||||
|
||||
class Adapter:
|
||||
def __init__(self, host: str, port: int, silence: int, topic: str):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.silence = silence
|
||||
self.topic = topic
|
||||
self.clients = set()
|
||||
|
||||
def log_clients(self):
|
||||
clients_str = '\n'.join(f' {c.addr_info}' for c in self.clients)
|
||||
logger.debug(f"clients alive ({len(self.clients)}):\n{clients_str}")
|
||||
|
||||
def add_client(self, client: Client):
|
||||
# it's a little bit risky to keep more than one client at the same IP address,
|
||||
# because it's possible a notification comes in and we ring the old connection,
|
||||
# even when the new connection says "don't ring yet".
|
||||
for c in set(self.clients):
|
||||
if c.addr_info[0] == client.addr_info[0]:
|
||||
logger.info(f"purging old client before adding new one at same address: {c.addr_info} -> {client.addr_info}")
|
||||
self.clients.remove(c)
|
||||
|
||||
logger.info(f"accepted client at {client.addr_info}")
|
||||
self.clients.add(client)
|
||||
|
||||
def listener_loop(self):
|
||||
logger.info(f"listening for connections on {self.host}:{self.port}")
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.bind((self.host, self.port))
|
||||
s.listen(LISTEN_QUEUE)
|
||||
while True:
|
||||
conn, addr_info = s.accept()
|
||||
self.add_client(Client(conn, addr_info, live_after = time.time() + self.silence))
|
||||
|
||||
def notify_clients(self, message: bytes = WAKE_MESSAGE):
|
||||
# notify every client, and drop any which have disconnected.
|
||||
# note that we notify based on age (oldest -> youngest)
|
||||
# because notifying young clients might entail sleeping until they're ready.
|
||||
clients = sorted(self.clients, key=lambda c: (c.live_after, c.addr_info))
|
||||
|
||||
dead_clients = [
|
||||
c for c in clients if not c.try_notify(message)
|
||||
]
|
||||
for c in dead_clients:
|
||||
self.clients.remove(c)
|
||||
|
||||
self.log_clients()
|
||||
|
||||
def notify_loop(self):
|
||||
logger.info("waiting for notification events")
|
||||
ntfy_proc = subprocess.Popen(
|
||||
[
|
||||
"ntfy",
|
||||
"sub",
|
||||
f"https://ntfy.uninsane.org/{self.topic}"
|
||||
],
|
||||
stdout=subprocess.PIPE
|
||||
)
|
||||
for line in iter(ntfy_proc.stdout.readline, b''):
|
||||
logger.debug(f"received notification: {line}")
|
||||
self.notify_clients()
|
||||
|
||||
def get_topic() -> str:
|
||||
return open('/run/secrets/ntfy-sh-topic', 'rt').read().strip()
|
||||
|
||||
def run_forever(callable):
|
||||
try:
|
||||
callable()
|
||||
except Exception as e:
|
||||
logger.error(f"{callable} failed: {e}")
|
||||
else:
|
||||
logger.error(f"{callable} unexpectedly returned")
|
||||
# sys.exit(1)
|
||||
os._exit(1) # sometimes `sys.exit()` doesn't actually exit...
|
||||
|
||||
def main():
|
||||
logging.basicConfig()
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
parser = argparse.ArgumentParser(description="accept connections and notify the other end upon ntfy activity, with a guaranteed amount of silence")
|
||||
parser.add_argument('--verbose', action='store_true')
|
||||
parser.add_argument('--host', type=str, default='')
|
||||
parser.add_argument('--port', type=int)
|
||||
parser.add_argument('--silence', type=int, help="number of seconds to remain silent upon accepting a connection")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
adapter = Adapter(args.host, args.port, args.silence, get_topic())
|
||||
|
||||
listener_loop = threading.Thread(target=run_forever, name="listener_loop", args=(adapter.listener_loop,))
|
||||
notify_loop = threading.Thread(target=run_forever, name="notify_loop", args=(adapter.notify_loop,))
|
||||
|
||||
# TODO: this method of exiting seems to sometimes leave the listener behind (?)
|
||||
# preventing anyone else from re-binding the port.
|
||||
listener_loop.start()
|
||||
notify_loop.start()
|
||||
listener_loop.join()
|
||||
notify_loop.join()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@@ -1,72 +0,0 @@
|
||||
# service which adapts ntfy-sh into something suitable specifically for the Pinephone's
|
||||
# wake-on-lan (WoL) feature.
|
||||
# notably, it provides a mechanism by which the caller can be confident of an interval in which
|
||||
# zero traffic will occur on the TCP connection, thus allowing it to enter sleep w/o fear of hitting
|
||||
# race conditions in the Pinephone WoL feature.
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.ntfy-waiter;
|
||||
portLow = 5550;
|
||||
portHigh = 5559;
|
||||
portRange = lib.range portLow portHigh;
|
||||
numPorts = portHigh - portLow + 1;
|
||||
mkService = port: let
|
||||
silence = port - portLow;
|
||||
flags = lib.optional cfg.verbose "--verbose";
|
||||
cli = [
|
||||
"${cfg.package}/bin/ntfy-waiter"
|
||||
"--port"
|
||||
"${builtins.toString port}"
|
||||
"--silence"
|
||||
"${builtins.toString silence}"
|
||||
] ++ flags;
|
||||
in {
|
||||
"ntfy-waiter-${builtins.toString silence}" = {
|
||||
# TODO: run not as root (e.g. as ntfy-sh)
|
||||
description = "wait for notification, with ${builtins.toString silence} seconds of guaranteed silence";
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
ExecStart = lib.concatStringsSep " " cli;
|
||||
};
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "default.target" ];
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options = with lib; {
|
||||
sane.ntfy-waiter.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
sane.ntfy-waiter.verbose = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
sane.ntfy-waiter.package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.static-nix-shell.mkPython3Bin {
|
||||
pname = "ntfy-waiter";
|
||||
src = ./.;
|
||||
pkgs = [ "ntfy-sh" ];
|
||||
};
|
||||
description = ''
|
||||
exposed to provide an attr-path by which one may build the package for manual testing.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
sane.ports.ports = lib.mkMerge (lib.forEach portRange (port: {
|
||||
"${builtins.toString port}" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-notification-waiter-${builtins.toString (port+1)}-of-${builtins.toString numPorts}";
|
||||
};
|
||||
}));
|
||||
systemd.services = lib.mkMerge (builtins.map mkService portRange);
|
||||
};
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
# pict-rs is an image database/store used by Lemmy.
|
||||
# i don't explicitly activate it here -- just adjust its defaults to be a bit friendlier
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.services.pict-rs;
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = lib.mkIf cfg.enable [
|
||||
{ user = "pict-rs"; group = "pict-rs"; path = cfg.dataDir; }
|
||||
];
|
||||
|
||||
systemd.services.pict-rs.serviceConfig = {
|
||||
# fix to use a normal user so we can configure perms correctly
|
||||
DynamicUser = lib.mkForce false;
|
||||
User = "pict-rs";
|
||||
Group = "pict-rs";
|
||||
};
|
||||
users.groups.pict-rs = {};
|
||||
users.users.pict-rs = {
|
||||
group = "pict-rs";
|
||||
isSystemUser = true;
|
||||
};
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
let
|
||||
GiB = n: MiB 1024*n;
|
||||
MiB = n: KiB 1024*n;
|
||||
KiB = n: 1024*n;
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: mode?
|
||||
{ user = "postgres"; group = "postgres"; path = "/var/lib/postgresql"; }
|
||||
];
|
||||
services.postgresql.enable = true;
|
||||
|
||||
# HOW TO UPDATE:
|
||||
# postgres version updates are manual and require intervention.
|
||||
# - `sane-stop-all-servo`
|
||||
# - `systemctl start postgresql`
|
||||
# - as `sudo su postgres`:
|
||||
# - `cd /var/log/postgresql`
|
||||
# - `pg_dumpall > state.sql`
|
||||
# - `echo placeholder > <new_version>` # to prevent state from being created earlier than we want
|
||||
# - then, atomically:
|
||||
# - update the `services.postgresql.package` here
|
||||
# - `dataDir` is atomically updated to match package; don't touch
|
||||
# - `nixos-rebuild --flake . switch ; sane-stop-all-servo`
|
||||
# - `sudo rm -rf /var/lib/postgresql/<new_version>`
|
||||
# - `systemctl start postgresql`
|
||||
# - as `sudo su postgres`:
|
||||
# - `cd /var/lib/postgreql`
|
||||
# - `psql -f state.sql`
|
||||
# - restart dependent services (maybe test one at a time)
|
||||
|
||||
services.postgresql.package = pkgs.postgresql_15;
|
||||
|
||||
|
||||
# XXX colin: for a proper deploy, we'd want to include something for Pleroma here too.
|
||||
# services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" ''
|
||||
# CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD '<password goes here>';
|
||||
# CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
|
||||
# TEMPLATE template0
|
||||
# ENCODING = "UTF8"
|
||||
# LC_COLLATE = "C"
|
||||
# LC_CTYPE = "C";
|
||||
# '';
|
||||
|
||||
# perf tuning
|
||||
# - 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>
|
||||
services.postgresql.settings = {
|
||||
# DB Version: 15
|
||||
# OS Type: linux
|
||||
# DB Type: web
|
||||
# Total Memory (RAM): 32 GB
|
||||
# CPUs num: 12
|
||||
# Data Storage: ssd
|
||||
max_connections = 200;
|
||||
shared_buffers = "8GB";
|
||||
effective_cache_size = "24GB";
|
||||
maintenance_work_mem = "2GB";
|
||||
checkpoint_completion_target = 0.9;
|
||||
wal_buffers = "16MB";
|
||||
default_statistics_target = 100;
|
||||
random_page_cost = 1.1;
|
||||
effective_io_concurrency = 200;
|
||||
work_mem = "10485kB";
|
||||
min_wal_size = "1GB";
|
||||
max_wal_size = "4GB";
|
||||
max_worker_processes = 12;
|
||||
max_parallel_workers_per_gather = 4;
|
||||
max_parallel_workers = 12;
|
||||
max_parallel_maintenance_workers = 4;
|
||||
};
|
||||
|
||||
# daily backups to /var/backup
|
||||
services.postgresqlBackup.enable = true;
|
||||
|
||||
# common admin operations:
|
||||
# sudo systemctl start postgresql
|
||||
# sudo -u postgres psql
|
||||
# > \l # lists all databases
|
||||
# > \du # lists all roles
|
||||
# > \c pleroma # connects to database by name
|
||||
# > \d # shows all tables
|
||||
# > \q # exits psql
|
||||
# dump/restore (-F t = tar):
|
||||
# sudo -u postgres pg_dump -F t pleroma > /backup/pleroma-db.tar
|
||||
# sudo -u postgres -g postgres pg_restore -d pleroma /backup/pleroma-db.tar
|
||||
}
|
@@ -1,289 +0,0 @@
|
||||
# example configs:
|
||||
# - official: <https://prosody.im/doc/example_config>
|
||||
# - nixos: <https://github.com/kittywitch/nixfiles/blob/main/services/prosody.nix>
|
||||
# config options:
|
||||
# - <https://prosody.im/doc/configure>
|
||||
#
|
||||
# modules:
|
||||
# - main: <https://prosody.im/doc/modules>
|
||||
# - community: <https://modules.prosody.im/index.html>
|
||||
#
|
||||
# debugging:
|
||||
# - logging:
|
||||
# - enable `stanza_debug` module
|
||||
# - enable `log.debug = "*syslog"` in extraConfig
|
||||
# - interactive:
|
||||
# - `telnet localhost 5582` (this is equal to `prosodyctl shell` -- but doesn't hang)
|
||||
# - `watch:stanzas(target_spec, filter)` -> to log stanzas, for version > 0.12
|
||||
# - console docs: <https://prosody.im/doc/console>
|
||||
# - can modify/inspect arbitrary internals (lua) by prefixing line with `> `
|
||||
# - e.g. `> _G` to print all globals
|
||||
#
|
||||
# sanity checks:
|
||||
# - `sudo -u prosody -g prosody prosodyctl check connectivity`
|
||||
# - `sudo -u prosody -g prosody prosodyctl check turn`
|
||||
# - `sudo -u prosody -g prosody prosodyctl check turn -v --ping=stun.conversations.im`
|
||||
# - checks that my stun/turn server is usable by clients of conversations.im (?)
|
||||
# - `sudo -u prosody -g prosody prosodyctl check` (dns, config, certs)
|
||||
#
|
||||
#
|
||||
# create users with:
|
||||
# - `sudo -u prosody prosodyctl adduser colin@uninsane.org`
|
||||
#
|
||||
#
|
||||
# federation/support matrix:
|
||||
# - nixnet.services (runs ejabberd):
|
||||
# - WORKS: sending and receiving PMs and calls (2023/10/15)
|
||||
# - N.B.: it didn't originally work; was solved by disabling the lua-unbound DNS option & forcing the system/local resolver
|
||||
# - cheogram (XMPP <-> SMS gateway):
|
||||
# - WORKS: sending and receiving PMs, images (2023/10/15)
|
||||
# - PARTIAL: calls (xmpp -> tel works; tel -> xmpp fails)
|
||||
# - maybe i need to setup stun/turn
|
||||
#
|
||||
# TODO:
|
||||
# - enable push notifications (mod_cloud_notify)
|
||||
# - optimize coturn (e.g. move off of the VPN!)
|
||||
# - ensure muc is working
|
||||
# - enable file uploads
|
||||
# - "upload.xmpp.uninsane.org:http_upload: URL: <https://upload.xmpp.uninsane.org:5281/upload> - Ensure this can be reached by users"
|
||||
# - disable or fix bosh (jabber over http):
|
||||
# - "certmanager: No certificate/key found for client_https port 0"
|
||||
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
# enables very verbose logging
|
||||
enableDebug = false;
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "prosody"; group = "prosody"; path = "/var/lib/prosody"; }
|
||||
];
|
||||
sane.ports.ports."5000" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-prosody-fileshare-proxy65";
|
||||
};
|
||||
sane.ports.ports."5222" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-client-to-server";
|
||||
};
|
||||
sane.ports.ports."5223" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpps-client-to-server"; # XMPP over TLS
|
||||
};
|
||||
sane.ports.ports."5269" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-server-to-server";
|
||||
};
|
||||
sane.ports.ports."5270" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpps-server-to-server"; # XMPP over TLS
|
||||
};
|
||||
sane.ports.ports."5280" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-bosh";
|
||||
};
|
||||
sane.ports.ports."5281" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-xmpp-prosody-https"; # necessary?
|
||||
};
|
||||
|
||||
users.users.prosody.extraGroups = [
|
||||
"nginx" # provide access to certs
|
||||
"ntfy-sh" # access to secret ntfy topic
|
||||
];
|
||||
|
||||
security.acme.certs."uninsane.org".extraDomainNames = [
|
||||
"xmpp.uninsane.org"
|
||||
"conference.xmpp.uninsane.org"
|
||||
"upload.xmpp.uninsane.org"
|
||||
];
|
||||
|
||||
# exists so the XMPP server's cert can obtain altNames for all its resources
|
||||
services.nginx.virtualHosts."xmpp.uninsane.org" = {
|
||||
useACMEHost = "uninsane.org";
|
||||
};
|
||||
services.nginx.virtualHosts."conference.xmpp.uninsane.org" = {
|
||||
useACMEHost = "uninsane.org";
|
||||
};
|
||||
services.nginx.virtualHosts."upload.xmpp.uninsane.org" = {
|
||||
useACMEHost = "uninsane.org";
|
||||
};
|
||||
|
||||
sane.dns.zones."uninsane.org".inet = {
|
||||
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
|
||||
A."xmpp" = "%ANATIVE%";
|
||||
CNAME."conference.xmpp" = "xmpp";
|
||||
CNAME."upload.xmpp" = "xmpp";
|
||||
|
||||
# _Service._Proto.Name TTL Class SRV Priority Weight Port Target
|
||||
# - <https://xmpp.org/extensions/xep-0368.html>
|
||||
# something's requesting the SRV records for conference.xmpp, so let's include it
|
||||
# nothing seems to request XMPP SRVs for the other records (except @)
|
||||
# lower numerical priority field tells clients to prefer this method
|
||||
SRV."_xmpps-client._tcp.conference.xmpp" = "3 50 5223 xmpp";
|
||||
SRV."_xmpps-server._tcp.conference.xmpp" = "3 50 5270 xmpp";
|
||||
SRV."_xmpp-client._tcp.conference.xmpp" = "5 50 5222 xmpp";
|
||||
SRV."_xmpp-server._tcp.conference.xmpp" = "5 50 5269 xmpp";
|
||||
|
||||
SRV."_xmpps-client._tcp" = "3 50 5223 xmpp";
|
||||
SRV."_xmpps-server._tcp" = "3 50 5270 xmpp";
|
||||
SRV."_xmpp-client._tcp" = "5 50 5222 xmpp";
|
||||
SRV."_xmpp-server._tcp" = "5 50 5269 xmpp";
|
||||
};
|
||||
|
||||
# help Prosody find its certificates.
|
||||
# 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`
|
||||
# <https://prosody.im/doc/certificates#automatic_location>
|
||||
sane.fs."/etc/prosody/certs/uninsane.org/fullchain.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 = {
|
||||
enable = true;
|
||||
package = pkgs.prosody.override {
|
||||
# XXX(2023/10/15): build without lua-unbound support.
|
||||
# this forces Prosody to fall back to the default Lua DNS resolver, which seems more reliable.
|
||||
# fixes errors like "unbound.queryXYZUV: Resolver error: out of memory"
|
||||
# related: <https://issues.prosody.im/1737#comment-11>
|
||||
lua.withPackages = selector: pkgs.lua.withPackages (p:
|
||||
selector (p // { luaunbound = null; })
|
||||
);
|
||||
# withCommunityModules = [ "turncredentials" ];
|
||||
};
|
||||
admins = [ "colin@uninsane.org" ];
|
||||
# allowRegistration = false; # defaults to false
|
||||
|
||||
muc = [
|
||||
{
|
||||
domain = "conference.xmpp.uninsane.org";
|
||||
}
|
||||
];
|
||||
uploadHttp.domain = "upload.xmpp.uninsane.org";
|
||||
|
||||
virtualHosts = {
|
||||
# "Prosody requires at least one enabled VirtualHost to function. You can
|
||||
# safely remove or disable 'localhost' once you have added another."
|
||||
# localhost = {
|
||||
# domain = "localhost";
|
||||
# enabled = true;
|
||||
# };
|
||||
"xmpp.uninsane.org" = {
|
||||
domain = "uninsane.org";
|
||||
enabled = true;
|
||||
};
|
||||
};
|
||||
|
||||
## modules:
|
||||
# these are enabled by default, via <repo:nixos/nixpkgs:/pkgs/servers/xmpp/prosody/default.nix>
|
||||
# - cloud_notify
|
||||
# - http_upload
|
||||
# - vcard_muc
|
||||
# these are enabled by the module defaults (services.prosody.modules.<foo>)
|
||||
# - admin_adhoc
|
||||
# - blocklist
|
||||
# - bookmarks
|
||||
# - carbons
|
||||
# - cloud_notify
|
||||
# - csi
|
||||
# - dialback
|
||||
# - disco
|
||||
# - http_files
|
||||
# - mam
|
||||
# - pep
|
||||
# - ping
|
||||
# - private
|
||||
# - XEP-0049: let clients store arbitrary (private) data on the server
|
||||
# - proxy65
|
||||
# - XEP-0065: allow server to proxy file transfers between two clients who are behind NAT
|
||||
# - register
|
||||
# - roster
|
||||
# - saslauth
|
||||
# - smacks
|
||||
# - time
|
||||
# - tls
|
||||
# - uptime
|
||||
# - vcard_legacy
|
||||
# - version
|
||||
|
||||
extraPluginPaths = [ ./modules ];
|
||||
|
||||
extraModules = [
|
||||
# admin_shell: allows `prosodyctl shell` to work
|
||||
# see: <https://prosody.im/doc/modules/mod_admin_shell>
|
||||
# see: <https://prosody.im/doc/console>
|
||||
"admin_shell"
|
||||
"admin_telnet" #< needed by admin_shell
|
||||
# lastactivity: XEP-0012: allow users to query how long another user has been idle for
|
||||
# - not sure why i enabled this; think it was in someone's config i referenced
|
||||
"lastactivity"
|
||||
# allows prosody to share TURN/STUN secrets with XMPP clients to provide them access to the coturn server.
|
||||
# see: <https://prosody.im/doc/coturn>
|
||||
"turn_external"
|
||||
# legacy coturn integration
|
||||
# see: <https://modules.prosody.im/mod_turncredentials.html>
|
||||
# "turncredentials"
|
||||
"sane_ntfy"
|
||||
] ++ lib.optionals enableDebug [
|
||||
"stanza_debug" #< logs EVERY stanza as debug: <https://prosody.im/doc/modules/mod_stanza_debug>
|
||||
];
|
||||
|
||||
extraConfig = ''
|
||||
local function readAll(file)
|
||||
local f = assert(io.open(file, "rb"))
|
||||
local content = f:read("*all")
|
||||
f:close()
|
||||
-- remove trailing newline
|
||||
return string.gsub(content, "%s+", "")
|
||||
end
|
||||
|
||||
-- logging docs:
|
||||
-- - <https://prosody.im/doc/logging>
|
||||
-- - <https://prosody.im/doc/advanced_logging>
|
||||
-- levels: debug, info, warn, error
|
||||
log = {
|
||||
${if enableDebug then "debug" else "info"} = "*syslog";
|
||||
}
|
||||
|
||||
-- see: <https://prosody.im/doc/certificates#automatic_location>
|
||||
-- try to solve: "certmanager: Error indexing certificate directory /etc/prosody/certs: cannot open /etc/prosody/certs: No such file or directory"
|
||||
-- only, this doesn't work because prosody doesn't like acme's naming scheme
|
||||
-- certificates = "/var/lib/acme"
|
||||
|
||||
c2s_direct_tls_ports = { 5223 }
|
||||
s2s_direct_tls_ports = { 5270 }
|
||||
|
||||
turn_external_host = "turn.uninsane.org"
|
||||
turn_external_secret = readAll("/var/lib/coturn/shared_secret.bin")
|
||||
-- turn_external_user = "prosody"
|
||||
|
||||
-- legacy mod_turncredentials integration
|
||||
-- turncredentials_host = "turn.uninsane.org"
|
||||
-- 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
|
||||
-- c2s_require_encryption = true
|
||||
'';
|
||||
};
|
||||
}
|
@@ -1,52 +0,0 @@
|
||||
-- simple proof-of-concept Prosody module
|
||||
-- module development guide: <https://prosody.im/doc/developers/modules>
|
||||
-- module API docs: <https://prosody.im/doc/developers/moduleapi>
|
||||
--
|
||||
-- much of this code is lifted from Prosody's own `mod_cloud_notify`
|
||||
|
||||
local jid = require"util.jid";
|
||||
|
||||
local ntfy = module:get_option_string("ntfy_binary", "ntfy");
|
||||
local ntfy_topic = module:get_option_string("ntfy_topic", "xmpp");
|
||||
|
||||
module:log("info", "initialized");
|
||||
|
||||
local function is_urgent(stanza)
|
||||
if stanza.name == "message" then
|
||||
if stanza:get_child("propose", "urn:xmpp:jingle-message:0") then
|
||||
return true, "jingle call";
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function publish_ntfy(message)
|
||||
-- message should be the message to publish
|
||||
local ntfy_url = string.format("https://ntfy.uninsane.org/%s", ntfy_topic)
|
||||
local cmd = string.format("%s pub %q %q", ntfy, ntfy_url, message)
|
||||
module.log("debug", "invoking ntfy: %s", cmd)
|
||||
local success, reason, code = os.execute(cmd)
|
||||
if not success then
|
||||
module:log("warn", "ntfy failed: %s => %s %d", cmd, reason, code)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function archive_message_added(event)
|
||||
-- event is: { origin = origin, stanza = stanza, for_user = store_user, id = id }
|
||||
local stanza = event.stanza;
|
||||
local to = stanza.attr.to;
|
||||
to = to and jid.split(to) or event.origin.username;
|
||||
|
||||
-- only notify if the stanza destination is the mam user we store it for
|
||||
if event.for_user == to then
|
||||
local is_urgent_stanza, urgent_reason = is_urgent(event.stanza);
|
||||
|
||||
if is_urgent_stanza then
|
||||
module:log("info", "urgent push for %s (%s)", to, urgent_reason);
|
||||
publish_ntfy(urgent_reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module:hook("archive-message-added", archive_message_added);
|
@@ -1,208 +0,0 @@
|
||||
# TODO: split this file apart into smaller files to make it easier to understand
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
nativeAddrs = lib.mapAttrs (_name: builtins.head) config.sane.dns.zones."uninsane.org".inet.A;
|
||||
bindOvpn = "10.0.1.5";
|
||||
in lib.mkMerge [
|
||||
{
|
||||
services.trust-dns.enable = true;
|
||||
|
||||
# don't bind to IPv6 until i explicitly test that stack
|
||||
services.trust-dns.settings.listen_addrs_ipv6 = [];
|
||||
services.trust-dns.quiet = true;
|
||||
# services.trust-dns.debug = true;
|
||||
|
||||
sane.ports.ports."53" = {
|
||||
protocol = [ "udp" "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-dns-hosting";
|
||||
};
|
||||
|
||||
sane.dns.zones."uninsane.org".TTL = 900;
|
||||
|
||||
# SOA record structure: <https://en.wikipedia.org/wiki/SOA_record#Structure>
|
||||
# SOA MNAME RNAME (... rest)
|
||||
# MNAME = Master name server for this zone. this is where update requests should be sent.
|
||||
# RNAME = admin contact (encoded email address)
|
||||
# Serial = YYYYMMDDNN, where N is incremented every time this file changes, to trigger secondary NS to re-fetch it.
|
||||
# Refresh = how frequently secondary NS should query master
|
||||
# Retry = how long secondary NS should wait until re-querying master after a failure (must be < Refresh)
|
||||
# Expire = how long secondary NS should continue to reply to queries after master fails (> Refresh + Retry)
|
||||
sane.dns.zones."uninsane.org".inet = {
|
||||
SOA."@" = ''
|
||||
ns1.uninsane.org. admin-dns.uninsane.org. (
|
||||
2023092101 ; Serial
|
||||
4h ; Refresh
|
||||
30m ; Retry
|
||||
7d ; Expire
|
||||
5m) ; Negative response TTL
|
||||
'';
|
||||
TXT."rev" = "2023092101";
|
||||
|
||||
CNAME."native" = "%CNAMENATIVE%";
|
||||
A."@" = "%ANATIVE%";
|
||||
A."servo.wan" = "%AWAN%";
|
||||
A."servo.lan" = config.sane.hosts.by-name."servo".lan-ip;
|
||||
A."servo.hn" = config.sane.hosts.by-name."servo".wg-home.ip;
|
||||
|
||||
# XXX NS records must also not be CNAME
|
||||
# it's best that we keep this identical, or a superset of, what org. lists as our NS.
|
||||
# so, org. can specify ns2/ns3 as being to the VPN, with no mention of ns1. we provide ns1 here.
|
||||
A."ns1" = "%ANATIVE%";
|
||||
A."ns2" = "185.157.162.178";
|
||||
A."ns3" = "185.157.162.178";
|
||||
A."ovpns" = "185.157.162.178";
|
||||
NS."@" = [
|
||||
"ns1.uninsane.org."
|
||||
"ns2.uninsane.org."
|
||||
"ns3.uninsane.org."
|
||||
];
|
||||
};
|
||||
|
||||
services.trust-dns.settings.zones = [ "uninsane.org" ];
|
||||
|
||||
# TODO: can i transform this into some sort of service group?
|
||||
# have `systemctl restart trust-dns.service` restart all the individual services?
|
||||
systemd.services.trust-dns.serviceConfig = {
|
||||
DynamicUser = lib.mkForce false;
|
||||
User = "trust-dns";
|
||||
Group = "trust-dns";
|
||||
wantedBy = lib.mkForce [];
|
||||
};
|
||||
systemd.services.trust-dns.enable = false;
|
||||
|
||||
users.groups.trust-dns = {};
|
||||
users.users.trust-dns = {
|
||||
group = "trust-dns";
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
# sane.services.dyn-dns.restartOnChange = [ "trust-dns.service" ];
|
||||
|
||||
networking.nat.enable = true;
|
||||
networking.nat.extraCommands = ''
|
||||
# redirect incoming DNS requests from LAN addresses
|
||||
# to the LAN-specialized DNS service
|
||||
# N.B.: use the `nixos-*` chains instead of e.g. PREROUTING
|
||||
# because they get cleanly reset across activations or `systemctl restart firewall`
|
||||
# instead of accumulating cruft
|
||||
iptables -t nat -A nixos-nat-pre -p udp --dport 53 \
|
||||
-m iprange --src-range 10.78.76.0-10.78.79.255 \
|
||||
-j DNAT --to-destination :1053
|
||||
iptables -t nat -A nixos-nat-pre -p tcp --dport 53 \
|
||||
-m iprange --src-range 10.78.76.0-10.78.79.255 \
|
||||
-j DNAT --to-destination :1053
|
||||
'';
|
||||
sane.ports.ports."1053" = {
|
||||
# because the NAT above redirects in nixos-nat-pre, LAN requests behave as though they arrived on the external interface at the redirected port.
|
||||
# TODO: try nixos-nat-post instead?
|
||||
# TODO: or, don't NAT from port 53 -> port 1053, but rather nat from LAN addr to a loopback addr.
|
||||
# - this is complicated in that loopback is a different interface than eth0, so rewriting the destination address would cause the packets to just be dropped by the interface
|
||||
protocol = [ "udp" "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
description = "colin-redirected-dns-for-lan-namespace";
|
||||
};
|
||||
}
|
||||
{
|
||||
systemd.services =
|
||||
let
|
||||
sed = "${pkgs.gnused}/bin/sed";
|
||||
stateDir = "/var/lib/trust-dns";
|
||||
zoneTemplate = pkgs.writeText "uninsane.org.zone.in" config.sane.dns.zones."uninsane.org".rendered;
|
||||
|
||||
zoneDirFor = flavor: "${stateDir}/${flavor}";
|
||||
zoneFor = flavor: "${zoneDirFor flavor}/uninsane.org.zone";
|
||||
mkTrustDnsService = opts: flavor: let
|
||||
flags = let baseCfg = config.services.trust-dns; in
|
||||
(lib.optional baseCfg.debug "--debug") ++ (lib.optional baseCfg.quiet "--quiet");
|
||||
flagsStr = builtins.concatStringsSep " " flags;
|
||||
|
||||
anative = nativeAddrs."servo.${flavor}";
|
||||
|
||||
toml = pkgs.formats.toml { };
|
||||
configTemplate = opts.config or (toml.generate "trust-dns-${flavor}.toml" (
|
||||
(
|
||||
lib.filterAttrsRecursive (_: v: v != null) config.services.trust-dns.settings
|
||||
) // {
|
||||
listen_addrs_ipv4 = opts.listen or [ anative ];
|
||||
}
|
||||
));
|
||||
configFile = "${stateDir}/${flavor}-config.toml";
|
||||
|
||||
port = opts.port or 53;
|
||||
in {
|
||||
description = "trust-dns Domain Name Server (serving ${flavor})";
|
||||
unitConfig.Documentation = "https://trust-dns.org/";
|
||||
|
||||
preStart = ''
|
||||
wan=$(cat '${config.sane.services.dyn-dns.ipPath}')
|
||||
${sed} s/%AWAN%/$wan/ ${configTemplate} > ${configFile}
|
||||
'' + lib.optionalString (!opts ? config) ''
|
||||
mkdir -p ${zoneDirFor flavor}
|
||||
${sed} \
|
||||
-e s/%CNAMENATIVE%/servo.${flavor}/ \
|
||||
-e s/%ANATIVE%/${anative}/ \
|
||||
-e s/%AWAN%/$wan/ \
|
||||
-e s/%AOVPNS%/185.157.162.178/ \
|
||||
${zoneTemplate} > ${zoneFor flavor}
|
||||
'';
|
||||
serviceConfig = config.systemd.services.trust-dns.serviceConfig // {
|
||||
ExecStart = ''
|
||||
${pkgs.trust-dns}/bin/${pkgs.trust-dns.meta.mainProgram} \
|
||||
--port ${builtins.toString port} \
|
||||
--zonedir ${zoneDirFor flavor}/ \
|
||||
--config ${configFile} ${flagsStr}
|
||||
'';
|
||||
};
|
||||
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
in {
|
||||
trust-dns-wan = mkTrustDnsService { listen = [ nativeAddrs."servo.lan" bindOvpn ]; } "wan";
|
||||
trust-dns-lan = mkTrustDnsService { port = 1053; } "lan";
|
||||
trust-dns-hn = mkTrustDnsService { port = 1053; } "hn";
|
||||
trust-dns-hn-resolver = mkTrustDnsService {
|
||||
config = pkgs.writeText "hn-resolver-config.toml" ''
|
||||
# i host a resolver in the wireguard VPN so that clients can resolve DNS through the VPN.
|
||||
# (that's what this file achieves).
|
||||
#
|
||||
# one would expect this resolver could host the authoritative zone for `uninsane.org`, and then forward everything else to the system resolver...
|
||||
# and while that works for `dig`, it breaks for `nslookup` (and so `ssh`, etc).
|
||||
#
|
||||
# DNS responses include a flag for if the responding server is the authority of the zone queried.
|
||||
# it seems that default Linux stub resolvers either:
|
||||
# - expect DNSSEC when the response includes that bit, or
|
||||
# - expect A records to be in the `answer` section instead of `additional` section.
|
||||
# or perhaps something more nuanced. but for `nslookup` to be reliable, it has to talk to an
|
||||
# instance of trust-dns which is strictly a resolver, with no authority.
|
||||
# hence, this config: a resolver which forwards to the actual authority.
|
||||
|
||||
listen_addrs_ipv4 = ["${nativeAddrs."servo.hn"}"]
|
||||
listen_addrs_ipv6 = []
|
||||
|
||||
[[zones]]
|
||||
zone = "uninsane.org"
|
||||
zone_type = "Forward"
|
||||
stores = { type = "forward", name_servers = [{ socket_addr = "${nativeAddrs."servo.hn"}:1053", protocol = "udp", trust_nx_responses = true }] }
|
||||
|
||||
[[zones]]
|
||||
# forward the root zone to the local DNS resolver
|
||||
zone = "."
|
||||
zone_type = "Forward"
|
||||
stores = { type = "forward", name_servers = [{ socket_addr = "127.0.0.53:53", protocol = "udp", trust_nx_responses = true }] }
|
||||
'';
|
||||
} "hn-resolver";
|
||||
};
|
||||
|
||||
sane.services.dyn-dns.restartOnChange = [
|
||||
"trust-dns-wan.service"
|
||||
"trust-dns-lan.service"
|
||||
"trust-dns-hn.service"
|
||||
# "trust-dns-hn-resolver.service" # doesn't need restart because it doesn't know about WAN IP
|
||||
];
|
||||
}
|
||||
]
|
16
hosts/common/bluetooth.nix
Normal file
16
hosts/common/bluetooth.nix
Normal file
@@ -0,0 +1,16 @@
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
# persist external pairings by default
|
||||
sane.persist.sys.plaintext = [ "/var/lib/bluetooth" ];
|
||||
|
||||
sane.fs."/var/lib/bluetooth".generated.acl.mode = "0700";
|
||||
sane.fs."/var/lib/bluetooth/.secrets.stamp" = {
|
||||
wantedBeforeBy = [ "bluetooth.service" ];
|
||||
# XXX: install-bluetooth uses sed, but that's part of the default systemd unit path, it seems
|
||||
generated.script.script = builtins.readFile ../../scripts/install-bluetooth + ''
|
||||
touch "/var/lib/bluetooth/.secrets.stamp"
|
||||
'';
|
||||
generated.script.scriptArgs = [ "/run/secrets/bt" ];
|
||||
};
|
||||
}
|
22
hosts/common/cross.nix
Normal file
22
hosts/common/cross.nix
Normal file
@@ -0,0 +1,22 @@
|
||||
{ config, ... }:
|
||||
|
||||
let
|
||||
mkCrossFrom = localSystem: pkgs: import pkgs.path {
|
||||
inherit localSystem;
|
||||
crossSystem = pkgs.stdenv.hostPlatform.system;
|
||||
inherit (config.nixpkgs) config overlays;
|
||||
};
|
||||
in
|
||||
{
|
||||
# the configuration of which specific package set `pkgs.cross` refers to happens elsewhere;
|
||||
# here we just define them all.
|
||||
nixpkgs.overlays = [
|
||||
(next: prev: {
|
||||
# non-emulated packages build *from* local *for* target.
|
||||
# for large packages like the linux kernel which are expensive to build under emulation,
|
||||
# the config can explicitly pull such packages from `pkgs.cross` to do more efficient cross-compilation.
|
||||
crossFrom."x86_64-linux" = mkCrossFrom "x86_64-linux" next;
|
||||
crossFrom."aarch64-linux" = mkCrossFrom "aarch64-linux" next;
|
||||
})
|
||||
];
|
||||
}
|
@@ -1,100 +1,80 @@
|
||||
{ lib, pkgs, ... }:
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
./bluetooth.nix
|
||||
./cross.nix
|
||||
./feeds.nix
|
||||
./fs.nix
|
||||
./hardware
|
||||
./home
|
||||
./hosts.nix
|
||||
./i2p.nix
|
||||
./ids.nix
|
||||
./machine-id.nix
|
||||
./net.nix
|
||||
./nix-path
|
||||
./persist.nix
|
||||
./programs
|
||||
./secrets.nix
|
||||
./ssh.nix
|
||||
./users
|
||||
./users.nix
|
||||
./vpn.nix
|
||||
];
|
||||
|
||||
sane.home-manager.enable = true;
|
||||
sane.nixcache.enable-trusted-keys = true;
|
||||
sane.nixcache.enable = lib.mkDefault true;
|
||||
sane.persist.enable = lib.mkDefault true;
|
||||
sane.programs.sysadminUtils.enableFor.system = lib.mkDefault true;
|
||||
sane.programs.consoleUtils.enableFor.user.colin = lib.mkDefault true;
|
||||
sane.packages.enableConsolePkgs = true;
|
||||
sane.packages.enableSystemPkgs = true;
|
||||
|
||||
sane.persist.sys.plaintext = [
|
||||
"/var/log"
|
||||
"/var/backup" # for e.g. postgres dumps
|
||||
# TODO: move elsewhere
|
||||
"/var/lib/alsa" # preserve output levels, default devices
|
||||
"/var/lib/colord" # preserve color calibrations (?)
|
||||
"/var/lib/machines" # maybe not needed, but would be painful to add a VM and forget.
|
||||
];
|
||||
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
nixpkgs.config.allowBroken = true; # NIXPKGS_ALLOW_BROKEN
|
||||
|
||||
# time.timeZone = "America/Los_Angeles";
|
||||
time.timeZone = "Etc/UTC"; # DST is too confusing for me => use a stable timezone
|
||||
|
||||
# allow `nix flake ...` command
|
||||
# TODO: is this still required?
|
||||
nix.extraOptions = ''
|
||||
experimental-features = nix-command flakes
|
||||
'';
|
||||
# hardlinks identical files in the nix store to save 25-35% disk space.
|
||||
# unclear _when_ this occurs. it's not a service.
|
||||
# does the daemon continually scan the nix store?
|
||||
# does the builder use some content-addressed db to efficiently dedupe?
|
||||
nix.settings.auto-optimise-store = true;
|
||||
# allow `nix-shell` (and probably nix-index?) to locate our patched and custom packages
|
||||
nix.nixPath = [
|
||||
"nixpkgs=${pkgs.path}"
|
||||
"nixpkgs-overlays=${../..}/overlays"
|
||||
];
|
||||
|
||||
services.journald.extraConfig = ''
|
||||
# docs: `man journald.conf`
|
||||
# merged journald config is deployed to /etc/systemd/journald.conf
|
||||
[Journal]
|
||||
# disable journal compression because the underlying fs is compressed
|
||||
Compress=no
|
||||
'';
|
||||
|
||||
systemd.services.nix-daemon.serviceConfig = {
|
||||
# the nix-daemon manages nix builders
|
||||
# kill nix-daemon subprocesses when systemd-oomd detects an out-of-memory condition
|
||||
# see:
|
||||
# - nixos PR that enabled systemd-oomd: <https://github.com/NixOS/nixpkgs/pull/169613>
|
||||
# - systemd's docs on these properties: <https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#ManagedOOMSwap=auto%7Ckill>
|
||||
#
|
||||
# systemd's docs warn that without swap, systemd-oomd might not be able to react quick enough to save the system.
|
||||
# see `man oomd.conf` for further tunables that may help.
|
||||
#
|
||||
# alternatively, apply this more broadly with `systemd.oomd.enableSystemSlice = true` or `enableRootSlice`
|
||||
# TODO: also apply this to the guest user's slice (user-1100.slice)
|
||||
# TODO: also apply this to distccd
|
||||
ManagedOOMMemoryPressure = "kill";
|
||||
ManagedOOMSwap = "kill";
|
||||
# TODO: move this into home-manager?
|
||||
fonts = {
|
||||
enableDefaultFonts = true;
|
||||
fonts = with pkgs; [ font-awesome twitter-color-emoji hack-font ];
|
||||
fontconfig.enable = true;
|
||||
fontconfig.defaultFonts = {
|
||||
emoji = [ "Font Awesome 6 Free" "Twitter Color Emoji" ];
|
||||
monospace = [ "Hack" ];
|
||||
serif = [ "DejaVu Serif" ];
|
||||
sansSerif = [ "DejaVu Sans" ];
|
||||
};
|
||||
|
||||
|
||||
system.activationScripts.nixClosureDiff = {
|
||||
supportsDryActivation = true;
|
||||
text = ''
|
||||
# show which packages changed versions or are new/removed in this upgrade
|
||||
# source: <https://github.com/luishfonseca/dotfiles/blob/32c10e775d9ec7cc55e44592a060c1c9aadf113e/modules/upgrade-diff.nix>
|
||||
${pkgs.nvd}/bin/nvd --nix-bin-dir=${pkgs.nix}/bin diff /run/current-system "$systemConfig"
|
||||
'';
|
||||
};
|
||||
|
||||
# disable non-required packages like nano, perl, rsync, strace
|
||||
environment.defaultPackages = [];
|
||||
|
||||
# dconf docs: <https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/desktop_migration_and_administration_guide/profiles>
|
||||
# this lets programs temporarily write user-level dconf settings (aka gsettings).
|
||||
# they're written to ~/.config/dconf/user, unless `DCONF_PROFILE` is set to something other than the default of /etc/dconf/profile/user
|
||||
# find keys/values with `dconf dump /`
|
||||
programs.dconf.enable = true;
|
||||
programs.dconf.packages = [
|
||||
(pkgs.writeTextFile {
|
||||
name = "dconf-user-profile";
|
||||
destination = "/etc/dconf/profile/user";
|
||||
text = ''
|
||||
user-db:user
|
||||
system-db:site
|
||||
'';
|
||||
})
|
||||
];
|
||||
# sane.programs.glib.enableFor.user.colin = true; # for `gsettings`
|
||||
# programs.vim.defaultEditor = true;
|
||||
environment.variables = {
|
||||
EDITOR = "vim";
|
||||
# git claims it should use EDITOR, but it doesn't!
|
||||
GIT_EDITOR = "vim";
|
||||
# TODO: these should be moved to `home.sessionVariables` (home-manager)
|
||||
# Electron apps should use native wayland backend:
|
||||
# https://nixos.wiki/wiki/Slack#Wayland
|
||||
# Discord under sway crashes with this.
|
||||
# NIXOS_OZONE_WL = "1";
|
||||
# LIBGL_ALWAYS_SOFTWARE = "1";
|
||||
};
|
||||
# enable zsh completions
|
||||
environment.pathsToLink = [ "/share/zsh" ];
|
||||
|
||||
# link debug symbols into /run/current-system/sw/lib/debug
|
||||
# hopefully picked up by gdb automatically?
|
||||
|
@@ -1,13 +1,3 @@
|
||||
# where to find good stuff?
|
||||
# - podcasts w/ a community: <https://lemmyverse.net/communities?query=podcast>
|
||||
# - podcast rec thread: <https://lemmy.ml/post/1565858>
|
||||
#
|
||||
# candidates:
|
||||
# - The Nonlinear Library (podcast): <https://forum.effectivealtruism.org/posts/JTZTBienqWEAjGDRv/listen-to-more-ea-content-with-the-nonlinear-library>
|
||||
# - has ~10 posts per day, text-to-speech; i would need better tagging before adding this
|
||||
# - <https://www.metaculus.com/questions/11102/introducing-the-metaculus-journal-podcast/>
|
||||
# - dead since 2022/10 - 2023/03
|
||||
|
||||
{ lib, sane-data, ... }:
|
||||
let
|
||||
hourly = { freq = "hourly"; };
|
||||
@@ -23,7 +13,6 @@ let
|
||||
uncat = { cat = "uncat"; };
|
||||
|
||||
text = { format = "text"; };
|
||||
img = { format = "image"; };
|
||||
|
||||
mkRss = format: url: { inherit url format; } // uncat // infrequent;
|
||||
# format-specific helpers
|
||||
@@ -40,16 +29,16 @@ let
|
||||
in {
|
||||
url = raw.url;
|
||||
# not sure the exact mapping with velocity here: entries per day?
|
||||
freq = lib.mkIf (raw.velocity or 0 != 0) (lib.mkDefault (
|
||||
if raw.velocity > 2 then
|
||||
freq = lib.mkDefault (
|
||||
if raw.velocity or 0 > 2 then
|
||||
"hourly"
|
||||
else if raw.velocity > 0.5 then
|
||||
else if raw.velocity or 0 > 0.5 then
|
||||
"daily"
|
||||
else if raw.velocity > 0.1 then
|
||||
else if raw.velocity or 0 > 0.1 then
|
||||
"weekly"
|
||||
else
|
||||
"infrequent"
|
||||
));
|
||||
);
|
||||
} // lib.optionalAttrs (raw.is_podcast or false) {
|
||||
format = "podcast";
|
||||
} // lib.optionalAttrs (raw.title or "" != "") {
|
||||
@@ -57,184 +46,132 @@ let
|
||||
};
|
||||
|
||||
podcasts = [
|
||||
(fromDb "acquiredlpbonussecretsecret.libsyn.com" // tech) # ACQ2 - more "Acquired" episodes
|
||||
(fromDb "allinchamathjason.libsyn.com" // pol)
|
||||
(fromDb "anchor.fm/s/34c7232c/podcast/rss" // tech) # Civboot -- https://anchor.fm/civboot
|
||||
(fromDb "cast.postmarketos.org" // tech)
|
||||
(fromDb "congressionaldish.libsyn.com" // pol) # Jennifer Briney
|
||||
(fromDb "craphound.com" // pol) # Cory Doctorow -- both podcast & text entries
|
||||
(fromDb "darknetdiaries.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.feedburner.com/80000HoursPodcast" // 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.libsyn.com/421877" // rat) # Less Wrong Curated
|
||||
(fromDb "feeds.megaphone.fm/behindthebastards" // pol) # also Maggie Killjoy
|
||||
(fromDb "feeds.megaphone.fm/hubermanlab" // uncat) # Daniel Huberman on sleep
|
||||
(fromDb "feeds.megaphone.fm/recodedecode" // tech) # The Verge - Decoder
|
||||
(fromDb "feeds.simplecast.com/54nAGcIl" // pol) # The Daily
|
||||
(fromDb "feeds.simplecast.com/82FI35Px" // pol) # Ezra Klein Show
|
||||
(fromDb "feeds.simplecast.com/wgl4xEgL" // rat) # Econ Talk
|
||||
(fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura
|
||||
(fromDb "feeds.transistor.fm/acquired" // tech)
|
||||
(fromDb "lexfridman.com/podcast" // rat)
|
||||
(fromDb "mapspodcast.libsyn.com" // uncat) # Multidisciplinary Association for Psychedelic Studies
|
||||
(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
|
||||
# (mkPod "https://lexfridman.com/feed/podcast/" // rat // weekly)
|
||||
## Astral Codex Ten
|
||||
(fromDb "sscpodcast.libsyn.com" // rat)
|
||||
## Econ Talk
|
||||
(fromDb "feeds.simplecast.com/wgl4xEgL" // rat)
|
||||
## Cory Doctorow -- both podcast & text entries
|
||||
(fromDb "craphound.com" // pol)
|
||||
(mkPod "https://congressionaldish.libsyn.com/rss" // pol // infrequent)
|
||||
## Civboot -- https://anchor.fm/civboot
|
||||
(fromDb "anchor.fm/s/34c7232c/podcast/rss" // tech)
|
||||
(fromDb "feeds.feedburner.com/80000HoursPodcast" // rat)
|
||||
(fromDb "allinchamathjason.libsyn.com" // pol)
|
||||
(fromDb "acquired.libsyn.com" // tech)
|
||||
# The Intercept - Deconstructed; also available: <rss.acast.com/deconstructed>
|
||||
(fromDb "rss.prod.firstlook.media/deconstructed/podcast.rss" // pol)
|
||||
## The Daily
|
||||
(mkPod "https://feeds.simplecast.com/54nAGcIl" // pol // daily)
|
||||
# The Intercept - Intercepted; also available: <https://rss.acast.com/intercepted-with-jeremy-scahill>
|
||||
(fromDb "rss.prod.firstlook.media/intercepted/podcast.rss" // pol)
|
||||
(fromDb "podcast.posttv.com/itunes/post-reports.xml" // pol)
|
||||
(fromDb "podcast.thelinuxexp.com" // tech)
|
||||
(fromDb "politicalorphanage.libsyn.com" // pol)
|
||||
(fromDb "reverseengineering.libsyn.com/rss" // tech) # UnNamed Reverse Engineering Podcast
|
||||
(fromDb "rss.acast.com/deconstructed") # The Intercept - Deconstructed
|
||||
## Eric Weinstein
|
||||
(fromDb "rss.art19.com/the-portal" // rat)
|
||||
(fromDb "darknetdiaries.com" // tech)
|
||||
## Radiolab -- also available here, but ONLY OVER HTTP: <http://feeds.wnyc.org/radiolab>
|
||||
(fromDb "feeds.feedburner.com/radiolab" // pol)
|
||||
## Sam Harris
|
||||
(fromDb "wakingup.libsyn.com" // pol)
|
||||
## 99% Invisible -- also available here: <https://feeds.simplecast.com/BqbsxVfO>
|
||||
(fromDb "feeds.99percentinvisible.org/99percentinvisible" // pol)
|
||||
(fromDb "rss.acast.com/ft-tech-tonic" // tech)
|
||||
(fromDb "rss.acast.com/intercepted-with-jeremy-scahill") # The Intercept - Intercepted
|
||||
(fromDb "feeds.feedburner.com/dancarlin/history" // rat)
|
||||
(fromDb "rss.art19.com/60-minutes" // pol)
|
||||
(fromDb "rss.art19.com/the-portal" // rat) # Eric Weinstein
|
||||
(fromDb "seattlenice.buzzsprout.com" // pol)
|
||||
(fromDb "sharkbytes.transistor.fm" // tech) # Wireshark Podcast o_0
|
||||
(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 "techwontsave.us" // pol) # rec by Cory Doctorow
|
||||
# (fromDb "trashfuturepodcast.podbean.com" // pol) # rec by Cory Doctorow, but way rambly
|
||||
(fromDb "wakingup.libsyn.com" // pol) # Sam Harris
|
||||
(fromDb "werenotwrong.fireside.fm" // pol)
|
||||
|
||||
# (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/intercepted/podcast.rss" // pol) #< possible URL rot
|
||||
# (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://feeds.megaphone.fm/RUNMED9919162779" // pol // infrequent) # The Witch Trials of J.K. Rowling: <https://www.thefp.com/witchtrials>
|
||||
# (mkPod "https://podcasts.la.utexas.edu/this-is-democracy/feed/podcast/" // pol // weekly)
|
||||
## The Verge - Decoder
|
||||
(fromDb "feeds.megaphone.fm/recodedecode" // tech)
|
||||
## Matrix (chat) Live
|
||||
(fromDb "feed.podbean.com/matrixlive/feed.xml" // tech)
|
||||
## Michael Malice - Your Welcome -- also available here: <https://origin.podcastone.com/podcast?categoryID2=2232>
|
||||
(fromDb "rss.art19.com/your-welcome" // pol)
|
||||
];
|
||||
|
||||
texts = [
|
||||
# AGGREGATORS (> 1 post/day)
|
||||
(fromDb "lwn.net" // tech)
|
||||
# (fromDb "lesswrong.com" // rat)
|
||||
# (fromDb "econlib.org" // pol)
|
||||
(fromDb "lesswrong.com" // rat)
|
||||
(fromDb "econlib.org" // pol)
|
||||
|
||||
# AGGREGATORS (< 1 post/day)
|
||||
(fromDb "palladiummag.com" // uncat)
|
||||
(fromDb "profectusmag.com" // uncat)
|
||||
(fromDb "semiaccurate.com" // tech)
|
||||
(mkText "https://palladiummag.com/feed" // uncat // weekly)
|
||||
(mkText "https://profectusmag.com/feed" // uncat // weekly)
|
||||
(mkText "https://semiaccurate.com/feed" // tech // weekly)
|
||||
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
||||
(fromDb "tuxphones.com" // tech)
|
||||
(fromDb "spectrum.ieee.org" // tech)
|
||||
# (fromDb "theregister.com" // tech)
|
||||
(fromDb "thisweek.gnome.org" // tech)
|
||||
# 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)
|
||||
(mkText "https://nixos.org/blog/stories-rss.xml" // tech // weekly)
|
||||
## n.b.: 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)
|
||||
(mkText "https://spectrum.ieee.org/rss" // tech // weekly)
|
||||
|
||||
## No Moods, Ads or Cutesy Fucking Icons
|
||||
(fromDb "rifters.com/crawl" // uncat)
|
||||
(mkText "https://www.rifters.com/crawl/?feed=rss2" // uncat // weekly)
|
||||
|
||||
# DEVELOPERS
|
||||
(fromDb "blog.jmp.chat" // tech)
|
||||
(fromDb "uninsane.org" // tech)
|
||||
(fromDb "ascii.textfiles.com" // tech) # Jason Scott
|
||||
(fromDb "xn--gckvb8fzb.com" // tech)
|
||||
(fromDb "amosbbatto.wordpress.com" // tech)
|
||||
(fromDb "fasterthanli.me" // tech)
|
||||
(fromDb "mg.lol" // tech)
|
||||
# (fromDb "drewdevault.com" // tech)
|
||||
## Ken Shirriff
|
||||
(fromDb "righto.com" // tech)
|
||||
## shared blog by a few NixOS devs, notably onny
|
||||
(fromDb "project-insanity.org" // tech)
|
||||
## Vitalik Buterin
|
||||
(fromDb "vitalik.ca" // tech)
|
||||
(mkText "https://vitalik.ca/feed.xml" // tech // infrequent)
|
||||
## ian (Sanctuary)
|
||||
(fromDb "sagacioussuricata.com" // tech)
|
||||
(fromDb "artemis.sh" // tech)
|
||||
(mkText "https://sagacioussuricata.com/feed.xml" // tech // infrequent)
|
||||
## Bunnie Juang
|
||||
(fromDb "bunniestudios.com" // tech)
|
||||
(fromDb "blog.danieljanus.pl" // tech)
|
||||
(fromDb "ianthehenry.com" // tech)
|
||||
(fromDb "bitbashing.io" // tech)
|
||||
(fromDb "idiomdrottning.org" // uncat)
|
||||
(mkText "http://boginjr.com/feed" // tech // infrequent)
|
||||
(mkText "https://www.bunniestudios.com/blog/?feed=rss2" // tech // infrequent)
|
||||
(mkText "https://blog.danieljanus.pl/atom.xml" // tech // infrequent)
|
||||
(mkText "https://ianthehenry.com/feed.xml" // tech // infrequent)
|
||||
(mkText "https://bitbashing.io/feed.xml" // tech // infrequent)
|
||||
(mkText "https://idiomdrottning.org/feed.xml" // uncat // daily)
|
||||
(mkText "https://anish.lakhwara.com/home.html" // tech // weekly)
|
||||
(fromDb "jefftk.com" // tech)
|
||||
(fromDb "pomeroyb.com" // tech)
|
||||
(fromDb "harihareswara.net" // tech // pol) # rec by Cory Doctorow
|
||||
(fromDb "mako.cc/copyrighteous" // tech // pol) # rec by Cory Doctorow
|
||||
# (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
|
||||
|
||||
# TECH PROJECTS
|
||||
(fromDb "blog.rust-lang.org" // tech)
|
||||
(mkText "https://www.jefftk.com/news.rss" // tech // daily)
|
||||
(mkText "https://pomeroyb.com/feed.xml" // tech // infrequent)
|
||||
|
||||
# (TECH; POL) COMMENTATORS
|
||||
## Matt Webb -- engineering-ish, but dreamy
|
||||
(fromDb "interconnected.org/home/feed" // rat)
|
||||
(fromDb "edwardsnowden.substack.com" // pol // text)
|
||||
## Julia Evans
|
||||
(mkText "https://jvns.ca/atom.xml" // tech // weekly)
|
||||
(mkText "http://benjaminrosshoffman.com/feed" // pol // weekly)
|
||||
## Ben Thompson
|
||||
(mkText "https://www.stratechery.com/rss" // pol // weekly)
|
||||
## Balaji
|
||||
(fromDb "balajis.com" // pol)
|
||||
(fromDb "ben-evans.com/benedictevans" // pol)
|
||||
(fromDb "lynalden.com" // pol)
|
||||
(fromDb "austinvernon.site" // tech)
|
||||
(mkText "https://balajis.com/rss" // pol // weekly)
|
||||
(mkText "https://www.ben-evans.com/benedictevans/rss.xml" // pol // weekly)
|
||||
(mkText "https://www.lynalden.com/feed" // pol // infrequent)
|
||||
(mkText "https://austinvernon.site/rss.xml" // tech // infrequent)
|
||||
(mkSubstack "oversharing" // pol // daily)
|
||||
(mkSubstack "byrnehobart" // pol // infrequent)
|
||||
# (mkSubstack "doomberg" // tech // weekly) # articles are all pay-walled
|
||||
(mkSubstack "doomberg" // tech // weekly)
|
||||
## David Rosenthal
|
||||
(fromDb "blog.dshr.org" // pol)
|
||||
(mkText "https://blog.dshr.org/rss.xml" // pol // weekly)
|
||||
## Matt Levine
|
||||
(mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly)
|
||||
(fromDb "stpeter.im/atom.xml" // pol)
|
||||
## Peter Saint-Andre -- side project of stpeter.im
|
||||
(fromDb "philosopher.coach" // rat)
|
||||
(fromDb "morningbrew.com/feed" // pol)
|
||||
(mkText "https://stpeter.im/atom.xml" // pol // weekly)
|
||||
|
||||
# RATIONALITY/PHILOSOPHY/ETC
|
||||
(mkSubstack "samkriss" // humor // infrequent)
|
||||
(fromDb "unintendedconsequenc.es" // rat)
|
||||
(fromDb "applieddivinitystudies.com" // rat)
|
||||
(fromDb "slimemoldtimemold.com" // rat)
|
||||
(fromDb "richardcarrier.info" // rat)
|
||||
(fromDb "gwern.net" // rat)
|
||||
(mkText "https://unintendedconsequenc.es/feed" // rat // infrequent)
|
||||
(mkText "https://applieddivinitystudies.com/atom.xml" // rat // weekly)
|
||||
(mkText "https://slimemoldtimemold.com/feed.xml" // rat // weekly)
|
||||
(mkText "https://www.richardcarrier.info/feed" // rat // weekly)
|
||||
(mkText "https://www.gwern.net/feed.xml" // uncat // infrequent)
|
||||
## Jason Crawford
|
||||
(fromDb "rootsofprogress.org" // rat)
|
||||
(mkText "https://rootsofprogress.org/feed.xml" // rat // weekly)
|
||||
## Robin Hanson
|
||||
(fromDb "overcomingbias.com" // rat)
|
||||
(mkText "https://www.overcomingbias.com/feed" // rat // daily)
|
||||
## Scott Alexander
|
||||
(mkSubstack "astralcodexten" // rat // daily)
|
||||
## Paul Christiano
|
||||
(fromDb "sideways-view.com" // rat)
|
||||
(mkText "https://sideways-view.com/feed" // rat // infrequent)
|
||||
## Sean Carroll
|
||||
(fromDb "preposterousuniverse.com" // rat)
|
||||
(mkSubstack "eliqian" // rat // weekly)
|
||||
(mkText "https://acoup.blog/feed" // rat // weekly)
|
||||
(fromDb "mindingourway.com" // rat)
|
||||
(mkText "https://www.preposterousuniverse.com/rss" // rat // infrequent)
|
||||
|
||||
## mostly dating topics. not advice, or humor, but looking through a social lens
|
||||
(fromDb "putanumonit.com" // rat)
|
||||
|
||||
# LOCAL
|
||||
(fromDb "capitolhillseattle.com" // pol)
|
||||
(mkText "https://putanumonit.com/feed" // rat // infrequent)
|
||||
|
||||
# CODE
|
||||
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
|
||||
];
|
||||
|
||||
images = [
|
||||
(fromDb "smbc-comics.com" // img // humor)
|
||||
(fromDb "xkcd.com" // img // humor)
|
||||
(fromDb "turnoff.us" // img // humor)
|
||||
(fromDb "pbfcomics.com" // img // humor)
|
||||
(mkImg "https://www.smbc-comics.com/comic/rss" // humor // daily)
|
||||
(mkImg "https://xkcd.com/atom.xml" // humor // daily)
|
||||
(mkImg "https://pbfcomics.com/feed" // humor // infrequent)
|
||||
# (mkImg "http://dilbert.com/feed" // humor // daily)
|
||||
(fromDb "poorlydrawnlines.com/feed" // img // humor)
|
||||
|
||||
# ART
|
||||
(fromDb "miniature-calendar.com" // img // art // daily)
|
||||
(mkImg "https://miniature-calendar.com/feed" // art // daily)
|
||||
];
|
||||
in
|
||||
{
|
||||
|
@@ -1,143 +1,74 @@
|
||||
# docs
|
||||
# - x-systemd options: <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
|
||||
{ pkgs, ... }:
|
||||
|
||||
{ lib, pkgs, sane-lib, ... }:
|
||||
|
||||
let
|
||||
fsOpts = rec {
|
||||
common = [
|
||||
let sshOpts = rec {
|
||||
fsType = "fuse.sshfs";
|
||||
optionsBase = [
|
||||
"x-systemd.automount"
|
||||
"_netdev"
|
||||
"noatime"
|
||||
"user" # allow any user with access to the device to mount the fs
|
||||
"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
|
||||
];
|
||||
auto = [ "x-systemd.automount" ];
|
||||
noauto = [ "noauto" ]; # don't mount as part of remote-fs.target
|
||||
wg = [
|
||||
"x-systemd.requires=wireguard-wg-home.service"
|
||||
"x-systemd.after=wireguard-wg-home.service"
|
||||
];
|
||||
|
||||
ssh = common ++ [
|
||||
"user"
|
||||
"identityfile=/home/colin/.ssh/id_ed25519"
|
||||
"allow_other"
|
||||
"default_permissions"
|
||||
];
|
||||
sshColin = ssh ++ [
|
||||
optionsColin = optionsBase ++ [
|
||||
"transform_symlinks"
|
||||
"idmap=user"
|
||||
"uid=1000"
|
||||
"gid=100"
|
||||
];
|
||||
sshRoot = ssh ++ [
|
||||
|
||||
optionsRoot = optionsBase ++ [
|
||||
# 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
|
||||
# soft = on "major timeout", report I/O error to userspace
|
||||
# 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.
|
||||
nfs = common ++ [
|
||||
# "actimeo=10"
|
||||
"bg"
|
||||
"retrans=4"
|
||||
"retry=0"
|
||||
"soft"
|
||||
"timeo=15"
|
||||
"nofail" # don't fail remote-fs.target when this mount fails (not an option for sshfs else would be common)
|
||||
];
|
||||
};
|
||||
remoteHome = host: {
|
||||
fileSystems."/mnt/${host}-home" = {
|
||||
device = "colin@${host}:/home/colin";
|
||||
fsType = "fuse.sshfs";
|
||||
options = fsOpts.sshColin ++ fsOpts.noauto;
|
||||
noCheck = true;
|
||||
};
|
||||
sane.fs."/mnt/${host}-home" = sane-lib.fs.wantedDir;
|
||||
};
|
||||
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;
|
||||
|
||||
# fileSystems."/mnt/servo-nfs" = {
|
||||
# device = "servo-hn:/";
|
||||
# noCheck = true;
|
||||
# fsType = "nfs";
|
||||
# options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg;
|
||||
# };
|
||||
fileSystems."/mnt/servo-nfs/media" = {
|
||||
device = "servo-hn:/media";
|
||||
noCheck = true;
|
||||
fsType = "nfs";
|
||||
options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg;
|
||||
};
|
||||
fileSystems."/mnt/servo-nfs/playground" = {
|
||||
device = "servo-hn:/playground";
|
||||
noCheck = true;
|
||||
fsType = "nfs";
|
||||
options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg;
|
||||
};
|
||||
# fileSystems."/mnt/servo-media-nfs" = {
|
||||
# device = "servo-hn:/media";
|
||||
# noCheck = true;
|
||||
# fsType = "nfs";
|
||||
# options = fsOpts.common ++ fsOpts.auto;
|
||||
# };
|
||||
sane.fs."/mnt/servo-media" = sane-lib.fs.wantedSymlinkTo "/mnt/servo-nfs/media";
|
||||
|
||||
environment.pathsToLink = [
|
||||
# needed to achieve superuser access for user-mounted filesystems (see optionsRoot above)
|
||||
# we can only link whole directories here, even though we're only interested in pkgs.openssh
|
||||
"/libexec"
|
||||
];
|
||||
|
||||
fileSystems."/mnt/servo-media-wan" = {
|
||||
device = "colin@uninsane.org:/var/lib/uninsane/media";
|
||||
inherit (sshOpts) fsType;
|
||||
options = sshOpts.optionsColin;
|
||||
noCheck = true;
|
||||
};
|
||||
fileSystems."/mnt/servo-media-lan" = {
|
||||
device = "colin@servo:/var/lib/uninsane/media";
|
||||
inherit (sshOpts) fsType;
|
||||
options = sshOpts.optionsColin;
|
||||
noCheck = true;
|
||||
};
|
||||
fileSystems."/mnt/servo-root-wan" = {
|
||||
device = "colin@uninsane.org:/";
|
||||
inherit (sshOpts) fsType;
|
||||
options = sshOpts.optionsRoot;
|
||||
noCheck = true;
|
||||
};
|
||||
fileSystems."/mnt/servo-root-lan" = {
|
||||
device = "colin@servo:/";
|
||||
inherit (sshOpts) fsType;
|
||||
options = sshOpts.optionsRoot;
|
||||
noCheck = true;
|
||||
};
|
||||
fileSystems."/mnt/desko-home" = {
|
||||
device = "colin@desko:/home/colin";
|
||||
inherit (sshOpts) fsType;
|
||||
options = sshOpts.optionsColin;
|
||||
noCheck = true;
|
||||
};
|
||||
fileSystems."/mnt/desko-root" = {
|
||||
device = "colin@desko:/";
|
||||
inherit (sshOpts) fsType;
|
||||
options = sshOpts.optionsRoot;
|
||||
noCheck = true;
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.sshfs-fuse
|
||||
];
|
||||
}
|
||||
|
||||
(remoteHome "desko")
|
||||
(remoteHome "lappy")
|
||||
(remoteHome "moby")
|
||||
]
|
||||
|
||||
|
40
hosts/common/hardware/all.nix
Normal file
40
hosts/common/hardware/all.nix
Normal file
@@ -0,0 +1,40 @@
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
boot.initrd.supportedFilesystems = [ "ext4" "btrfs" "ext2" "ext3" "vfat" ];
|
||||
# useful emergency utils
|
||||
boot.initrd.extraUtilsCommands = ''
|
||||
copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfstune
|
||||
'';
|
||||
boot.kernelParams = [ "boot.shell_on_fail" ];
|
||||
# other kernelParams:
|
||||
# "boot.trace"
|
||||
# "systemd.log_level=debug"
|
||||
# "systemd.log_target=console"
|
||||
|
||||
# hack in the `boot.shell_on_fail` arg since that doesn't always seem to work.
|
||||
boot.initrd.preFailCommands = "allowShell=1";
|
||||
|
||||
# default: 4 (warn). 7 is debug
|
||||
boot.consoleLogLevel = 7;
|
||||
|
||||
boot.loader.grub.enable = lib.mkDefault false;
|
||||
boot.loader.generic-extlinux-compatible.enable = lib.mkDefault true;
|
||||
|
||||
# non-free firmware
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
services.fwupd.enable = true;
|
||||
|
||||
# powertop will default to putting USB devices -- including HID -- to sleep after TWO SECONDS
|
||||
powerManagement.powertop.enable = false;
|
||||
|
||||
# services.snapper.configs = {
|
||||
# root = {
|
||||
# subvolume = "/";
|
||||
# extraConfig = {
|
||||
# ALLOW_USERS = "colin";
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
# services.snapper.snapshotInterval = "daily";
|
||||
}
|
@@ -1,73 +1,8 @@
|
||||
{ lib, pkgs, ... }:
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./all.nix
|
||||
./x86_64.nix
|
||||
];
|
||||
|
||||
boot.initrd.supportedFilesystems = [ "ext4" "btrfs" "ext2" "ext3" "vfat" ];
|
||||
# useful emergency utils
|
||||
boot.initrd.extraUtilsCommands = ''
|
||||
copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfstune
|
||||
copy_bin_and_libs ${pkgs.util-linux}/bin/{cfdisk,lsblk,lscpu}
|
||||
copy_bin_and_libs ${pkgs.gptfdisk}/bin/{cgdisk,gdisk}
|
||||
copy_bin_and_libs ${pkgs.smartmontools}/bin/smartctl
|
||||
copy_bin_and_libs ${pkgs.nvme-cli}/bin/nvme
|
||||
copy_bin_and_libs ${pkgs.e2fsprogs}/bin/resize2fs
|
||||
'';
|
||||
boot.kernelParams = [
|
||||
"boot.shell_on_fail"
|
||||
#v experimental full pre-emption for hopefully better call/audio latency on moby.
|
||||
# also toggleable at runtime via /sys/kernel/debug/sched/preempt
|
||||
# defaults to preempt=voluntary
|
||||
# "preempt=full"
|
||||
];
|
||||
# other kernelParams:
|
||||
# "boot.trace"
|
||||
# "systemd.log_level=debug"
|
||||
# "systemd.log_target=console"
|
||||
|
||||
# hack in the `boot.shell_on_fail` arg since that doesn't always seem to work.
|
||||
boot.initrd.preFailCommands = "allowShell=1";
|
||||
|
||||
# default: 4 (warn). 7 is debug
|
||||
boot.consoleLogLevel = 7;
|
||||
|
||||
boot.loader.grub.enable = lib.mkDefault false;
|
||||
boot.loader.generic-extlinux-compatible.enable = lib.mkDefault true;
|
||||
|
||||
# non-free firmware
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
# powertop will default to putting USB devices -- including HID -- to sleep after TWO SECONDS
|
||||
powerManagement.powertop.enable = false;
|
||||
# linux CPU governor: <https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt>
|
||||
# - options:
|
||||
# - "powersave" => force CPU to always run at lowest supported frequency
|
||||
# - "performance" => force CPU to always run at highest frequency
|
||||
# - "ondemand" => adjust frequency based on load
|
||||
# - "conservative" (ondemand but slower to adjust)
|
||||
# - "schedutil"
|
||||
# - "userspace"
|
||||
# - not all options are available for all platforms
|
||||
# - intel (intel_pstate) appears to manage scaling w/o intervention/control from the OS.
|
||||
# - AMD (acpi-cpufreq) appears to manage scaling via the OS *or* HW. but the ondemand defaults never put it to max hardware frequency.
|
||||
# - qualcomm (cpufreq-dt) appears to manage scaling *only* via the OS. ondemand governor exercises the full range.
|
||||
# - query details with `sudo cpupower frequency-info`
|
||||
powerManagement.cpuFreqGovernor = "ondemand";
|
||||
|
||||
services.logind.extraConfig = ''
|
||||
# don’t shutdown when power button is short-pressed
|
||||
HandlePowerKey=ignore
|
||||
'';
|
||||
|
||||
# services.snapper.configs = {
|
||||
# root = {
|
||||
# subvolume = "/";
|
||||
# extraConfig = {
|
||||
# ALLOW_USERS = "colin";
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
# services.snapper.snapshotInterval = "daily";
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
{
|
||||
config = lib.mkIf (pkgs.system == "x86_64-linux") {
|
||||
config = mkIf (pkgs.system == "x86_64-linux") {
|
||||
boot.initrd.availableKernelModules = [
|
||||
"xhci_pci" "ahci" "sd_mod" "sdhci_pci" # nixos-generate-config defaults
|
||||
"usb_storage" # rpi needed this to boot from usb storage, i think.
|
||||
@@ -9,7 +10,17 @@
|
||||
# efi_pstore evivars
|
||||
];
|
||||
|
||||
# enable cross compilation
|
||||
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
|
||||
# nixpkgs.config.allowUnsupportedSystem = true;
|
||||
# nixpkgs.crossSystem.system = "aarch64-linux";
|
||||
|
||||
powerManagement.cpuFreqGovernor = "powersave";
|
||||
hardware.cpu.amd.updateMicrocode = true; # desktop
|
||||
hardware.cpu.intel.updateMicrocode = true; # laptop
|
||||
|
||||
hardware.opengl.driSupport = true;
|
||||
# For 32 bit applications
|
||||
hardware.opengl.driSupport32Bit = true;
|
||||
};
|
||||
}
|
||||
|
@@ -1,9 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
./keyring
|
||||
./mime.nix
|
||||
./ssh.nix
|
||||
./xdg-dirs.nix
|
||||
];
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{ config, pkgs, sane-lib, ... }:
|
||||
|
||||
let
|
||||
init-keyring = pkgs.static-nix-shell.mkBash {
|
||||
pname = "init-keyring";
|
||||
src = ./.;
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.user.persist.byStore.private = [ ".local/share/keyrings" ];
|
||||
|
||||
sane.user.fs."private/.local/share/keyrings/default" = {
|
||||
generated.command = [ "${init-keyring}/bin/init-keyring" ];
|
||||
wantedBy = [ config.sane.fs."/home/colin/private".unit ];
|
||||
wantedBeforeBy = [ ]; # don't created this as part of `multi-user.target`
|
||||
};
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
{ config, lib, ...}:
|
||||
|
||||
let
|
||||
# ProgramConfig -> { "<mime-type>" = { priority, desktop }; }
|
||||
weightedMimes = prog: builtins.mapAttrs (_key: desktop: { priority = prog.mime.priority; desktop = desktop; }) prog.mime.associations;
|
||||
# [ { "<mime-type>" = { priority, desktop } ]; } ] -> { "<mime-type>" = [ { priority, desktop } ... ]; }
|
||||
mergeMimes = mimes: lib.foldAttrs (item: acc: [item] ++ acc) [] mimes;
|
||||
# [ { priority, desktop } ... ] -> Self
|
||||
sortOneMimeType = associations: builtins.sort (l: r: assert l.priority != r.priority; l.priority < r.priority) associations;
|
||||
sortMimes = mimes: builtins.mapAttrs (_k: sortOneMimeType) mimes;
|
||||
removePriorities = mimes: builtins.mapAttrs (_k: associations: builtins.map (a: a.desktop) associations) mimes;
|
||||
|
||||
# [ ProgramConfig ]
|
||||
enabledPrograms = builtins.filter (p: p.enabled) (builtins.attrValues config.sane.programs);
|
||||
# [ { "<mime-type>" = { prority, desktop } ]
|
||||
enabledWeightedMimes = builtins.map weightedMimes enabledPrograms;
|
||||
in
|
||||
{
|
||||
# the xdg mime type for a file can be found with:
|
||||
# - `xdg-mime query filetype path/to/thing.ext`
|
||||
# the default handler for a mime type can be found with:
|
||||
# - `xdg-mime query default <mimetype>` (e.g. x-scheme-handler/http)
|
||||
#
|
||||
# we can have single associations or a list of associations.
|
||||
# there's also options to *remove* [non-default] associations from specific apps
|
||||
xdg.mime.enable = true;
|
||||
xdg.mime.defaultApplications = removePriorities (sortMimes (mergeMimes enabledWeightedMimes));
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
# TODO: this should be moved to users/colin.nix
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
host = config.networking.hostName;
|
||||
user-pubkey-full = config.sane.ssh.pubkeys."colin@${host}" or {};
|
||||
user-pubkey = user-pubkey-full.asUserKey or null;
|
||||
host-keys = lib.filter (k: k.user == "root") (lib.attrValues config.sane.ssh.pubkeys);
|
||||
known-hosts-text = lib.concatStringsSep
|
||||
"\n"
|
||||
(builtins.map (k: k.asHostKey) host-keys)
|
||||
;
|
||||
in
|
||||
{
|
||||
# ssh key is stored in private storage
|
||||
sane.user.persist.byStore.private = [
|
||||
{ type = "file"; path = ".ssh/id_ed25519"; }
|
||||
];
|
||||
sane.user.fs.".ssh/id_ed25519.pub" = lib.mkIf (user-pubkey != null) {
|
||||
symlink.text = user-pubkey;
|
||||
};
|
||||
sane.user.fs.".ssh/known_hosts".symlink.text = known-hosts-text;
|
||||
|
||||
users.users.colin.openssh.authorizedKeys.keys =
|
||||
let
|
||||
user-keys = lib.filter (k: k.user == "colin") (lib.attrValues config.sane.ssh.pubkeys);
|
||||
in
|
||||
builtins.map (k: k.asUserKey) user-keys;
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
# XDG defines things like ~/Desktop, ~/Downloads, etc.
|
||||
# these clutter the home, so i mostly don't use them.
|
||||
sane.user.fs.".config/user-dirs.dirs".symlink.text = ''
|
||||
XDG_DESKTOP_DIR="$HOME/.xdg/Desktop"
|
||||
XDG_DOCUMENTS_DIR="$HOME/dev"
|
||||
XDG_DOWNLOAD_DIR="$HOME/tmp"
|
||||
XDG_MUSIC_DIR="$HOME/Music"
|
||||
XDG_PICTURES_DIR="$HOME/Pictures"
|
||||
XDG_PUBLICSHARE_DIR="$HOME/.xdg/Public"
|
||||
XDG_TEMPLATES_DIR="$HOME/.xdg/Templates"
|
||||
XDG_VIDEOS_DIR="$HOME/Videos"
|
||||
'';
|
||||
|
||||
# prevent `xdg-user-dirs-update` from overriding/updating our config
|
||||
# see <https://manpages.ubuntu.com/manpages/bionic/man5/user-dirs.conf.5.html>
|
||||
sane.user.fs.".config/user-dirs.conf".symlink.text = "enabled=False";
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
{ lib, ... }:
|
||||
|
||||
{
|
||||
# TODO: this should be populated per-host
|
||||
sane.hosts.by-name."desko" = {
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPU5GlsSfbaarMvDA20bxpSZGWviEzXGD8gtrIowc1pX";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFw9NoRaYrM6LbDd3aFBc4yyBlxGQn8HjeHd/dZ3CfHk";
|
||||
wg-home.pubkey = "17PMZssYi0D4t2d0vbmhjBKe1sGsE8kT8/dod0Q2CXc=";
|
||||
wg-home.ip = "10.0.10.22";
|
||||
lan-ip = "10.78.79.52";
|
||||
};
|
||||
|
||||
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" = {
|
||||
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.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1N/IT3nQYUD+dBlU1sTEEVMxfOyMkrrDeyHcYgnJvw";
|
||||
wg-home.pubkey = "I7XIR1hm8bIzAtcAvbhWOwIAabGkuEvbWH/3kyIB1yA=";
|
||||
wg-home.ip = "10.0.10.48";
|
||||
lan-ip = "10.78.79.54";
|
||||
};
|
||||
|
||||
sane.hosts.by-name."servo" = {
|
||||
ssh.authorized = lib.mkDefault false; # servo presents too many services to the internet: easy atack vector
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPS1qFzKurAdB9blkWomq8gI1g0T3sTs9LsmFOj5VtqX";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOfdSmFkrVT6DhpgvFeQKm3Fh9VKZ9DbLYOPOJWYQ0E8";
|
||||
wg-home.pubkey = "roAw+IUFVtdpCcqa4khB385Qcv9l5JAB//730tyK4Wk=";
|
||||
wg-home.ip = "10.0.10.5";
|
||||
wg-home.endpoint = "uninsane.org:51820";
|
||||
lan-ip = "10.78.79.51";
|
||||
};
|
||||
}
|
4
hosts/common/i2p.nix
Normal file
4
hosts/common/i2p.nix
Normal file
@@ -0,0 +1,4 @@
|
||||
{ ... }:
|
||||
{
|
||||
services.i2p.enable = true;
|
||||
}
|
@@ -1,6 +1,3 @@
|
||||
# TODO: migrate to nixpkgs `config.ids.uids`
|
||||
# - note that nixpkgs' `config.ids.uids` is strictly a database: it doesn't set anything by default
|
||||
# whereas our impl sets the gid/uid of the user/group specified if they exist.
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
@@ -15,8 +12,6 @@
|
||||
sane.ids.acme.gid = 996;
|
||||
sane.ids.pleroma.uid = 997;
|
||||
sane.ids.acme.uid = 998;
|
||||
sane.ids.matrix-appservice-irc.uid = 993;
|
||||
sane.ids.matrix-appservice-irc.gid = 992;
|
||||
|
||||
# greetd (used by sway)
|
||||
sane.ids.greeter.uid = 999;
|
||||
@@ -26,29 +21,6 @@
|
||||
sane.ids.freshrss.uid = 2401;
|
||||
sane.ids.freshrss.gid = 2401;
|
||||
sane.ids.mediawiki.uid = 2402;
|
||||
sane.ids.signald.uid = 2403;
|
||||
sane.ids.signald.gid = 2403;
|
||||
sane.ids.mautrix-signal.uid = 2404;
|
||||
sane.ids.mautrix-signal.gid = 2404;
|
||||
sane.ids.navidrome.uid = 2405;
|
||||
sane.ids.navidrome.gid = 2405;
|
||||
sane.ids.calibre-web.uid = 2406;
|
||||
sane.ids.calibre-web.gid = 2406;
|
||||
sane.ids.komga.uid = 2407;
|
||||
sane.ids.komga.gid = 2407;
|
||||
sane.ids.lemmy.uid = 2408;
|
||||
sane.ids.lemmy.gid = 2408;
|
||||
sane.ids.pict-rs.uid = 2409;
|
||||
sane.ids.pict-rs.gid = 2409;
|
||||
sane.ids.sftpgo.uid = 2410;
|
||||
sane.ids.sftpgo.gid = 2410;
|
||||
sane.ids.trust-dns.uid = 2411;
|
||||
sane.ids.trust-dns.gid = 2411;
|
||||
sane.ids.export.gid = 2412;
|
||||
sane.ids.nfsuser.uid = 2413;
|
||||
sane.ids.media.gid = 2414;
|
||||
sane.ids.ntfy-sh.uid = 2415;
|
||||
sane.ids.ntfy-sh.gid = 2415;
|
||||
|
||||
sane.ids.colin.uid = 1000;
|
||||
sane.ids.guest.uid = 1100;
|
||||
@@ -57,12 +29,11 @@
|
||||
sane.ids.sshd.uid = 2001; # 997
|
||||
sane.ids.sshd.gid = 2001; # 997
|
||||
sane.ids.polkituser.gid = 2002; # 998
|
||||
sane.ids.systemd-coredump.gid = 2003; # 996 # 2023/02/12-2023/02/28: upstream temporarily specified this as 151
|
||||
sane.ids.systemd-coredump.gid = 2003; # 996
|
||||
sane.ids.nscd.uid = 2004;
|
||||
sane.ids.nscd.gid = 2004;
|
||||
sane.ids.systemd-oom.uid = 2005;
|
||||
sane.ids.systemd-oom.gid = 2005;
|
||||
sane.ids.wireshark.gid = 2006;
|
||||
|
||||
# found on graphical hosts
|
||||
sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy
|
||||
@@ -86,8 +57,4 @@
|
||||
sane.ids.rtkit.gid = 2307;
|
||||
# phosh
|
||||
sane.ids.feedbackd.gid = 2308;
|
||||
|
||||
# new moby users
|
||||
sane.ids.eg25-control.uid = 2309;
|
||||
sane.ids.eg25-control.gid = 2309;
|
||||
}
|
||||
|
@@ -1,6 +1,16 @@
|
||||
{ lib, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
# if using router's DNS, these mappings will already exist.
|
||||
# if using a different DNS provider (which servo does), then we need to explicity provide them.
|
||||
# ugly hack. would be better to get servo to somehow use the router's DNS
|
||||
networking.hosts = {
|
||||
"192.168.0.5" = [ "servo" ];
|
||||
"192.168.0.20" = [ "lappy" ];
|
||||
"192.168.0.22" = [ "desko" ];
|
||||
"192.168.0.48" = [ "moby" ];
|
||||
};
|
||||
|
||||
# the default backend is "wpa_supplicant".
|
||||
# wpa_supplicant reliably picks weak APs to connect to.
|
||||
# see: <https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/474>
|
||||
@@ -11,37 +21,23 @@
|
||||
# - `man iwd.config` for global config
|
||||
# - `man iwd.network` for per-SSID config
|
||||
# use `iwctl` to control
|
||||
# networking.networkmanager.wifi.backend = "iwd";
|
||||
# networking.wireless.iwd.enable = true;
|
||||
# networking.wireless.iwd.settings = {
|
||||
# # auto-connect to a stronger network if signal drops below this value
|
||||
# # bedroom -> bedroom connection is -35 to -40 dBm
|
||||
# # bedroom -> living room connection is -60 dBm
|
||||
# General.RoamThreshold = "-52"; # default -70
|
||||
# General.RoamThreshold5G = "-52"; # default -76
|
||||
# };
|
||||
networking.networkmanager.wifi.backend = "iwd";
|
||||
networking.wireless.iwd.enable = true;
|
||||
networking.wireless.iwd.settings = {
|
||||
# auto-connect to a stronger network if signal drops below this value
|
||||
# bedroom -> bedroom connection is -35 to -40 dBm
|
||||
# bedroom -> living room connection is -60 dBm
|
||||
General.RoamThreshold = "-52"; # default -70
|
||||
General.RoamThreshold5G = "-52"; # default -76
|
||||
};
|
||||
|
||||
# plugins mostly add support for establishing different VPN connections.
|
||||
# the default plugin set includes mostly proprietary VPNs:
|
||||
# - fortisslvpn (Fortinet)
|
||||
# - iodine (DNS tunnels)
|
||||
# - l2tp
|
||||
# - openconnect (Cisco Anyconnect / Juniper / ocserv)
|
||||
# - openvpn
|
||||
# - vpnc (Cisco VPN)
|
||||
# - sstp
|
||||
#
|
||||
# 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)!
|
||||
networking.networkmanager.plugins = lib.mkForce [];
|
||||
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
1900 # to received UPnP advertisements. required by sane-ip-check-upnp
|
||||
];
|
||||
|
||||
# keyfile.path = where networkmanager should look for connection credentials
|
||||
networking.networkmanager.extraConfig = ''
|
||||
[keyfile]
|
||||
path=/var/lib/NetworkManager/system-connections
|
||||
sane.fs."/var/lib/iwd/.secrets.psk.stamp" = {
|
||||
wantedBeforeBy = [ "iwd.service" ];
|
||||
generated.acl.mode = "0600";
|
||||
# XXX: install-iwd uses sed, but that's part of the default systemd unit path, it seems
|
||||
generated.script.script = builtins.readFile ../../scripts/install-iwd + ''
|
||||
touch "/var/lib/iwd/.secrets.psk.stamp"
|
||||
'';
|
||||
generated.script.scriptArgs = [ "/run/secrets/iwd" "/var/lib/iwd" ];
|
||||
};
|
||||
}
|
||||
|
@@ -1,13 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
# allow `nix-shell` (and probably nix-index?) to locate our patched and custom packages
|
||||
nix.nixPath = [
|
||||
"nixpkgs=${pkgs.path}"
|
||||
# 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"
|
||||
# as long as my system itself doesn't rely on NIXPKGS at runtime, we can point the overlays to git
|
||||
# to avoid switching so much during development
|
||||
"nixpkgs-overlays=/home/colin/dev/nixos/hosts/common/nix-path/overlay"
|
||||
];
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
# XXX: NIX_PATH=...:nixpkgs-overlays=... will import every overlay in the directory
|
||||
# so we prefer to give it a directory with just this *one* overlay, otherwise it imports conflicting overlays
|
||||
# and gets stuck in a loop until it OOMs
|
||||
import ../../../../overlays/all.nix
|
@@ -1,16 +0,0 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.persist.stores.private.origin = "/home/colin/private";
|
||||
# store /home/colin/a/b in /home/private/a/b instead of /home/private/home/colin/a/b
|
||||
sane.persist.stores.private.prefix = "/home/colin";
|
||||
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: these should be private.. somehow
|
||||
"/var/log"
|
||||
"/var/backup" # for e.g. postgres dumps
|
||||
];
|
||||
sane.persist.sys.byStore.cryptClearOnBoot = [
|
||||
"/var/lib/systemd/coredump"
|
||||
];
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
# Terminal UI mail client
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.programs.aerc = {
|
||||
secrets.".config/aerc/accounts.conf" = ../../../secrets/common/aerc_accounts.conf.bin;
|
||||
mime.associations."x-scheme-handler/mailto" = "aerc.desktop";
|
||||
};
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
# alacritty terminal emulator
|
||||
# - config options: <https://github.com/alacritty/alacritty/blob/master/extra/man/alacritty.5.scd>
|
||||
# - `man 5 alacritty`
|
||||
# - defaults: <https://github.com/alacritty/alacritty/releases> -> alacritty.yml
|
||||
# - irc: #alacritty on libera.chat
|
||||
{ lib, ... }:
|
||||
{
|
||||
sane.programs.alacritty = {
|
||||
env.TERMINAL = lib.mkDefault "alacritty";
|
||||
# note: alacritty will switch to .toml config in 13.0 release
|
||||
# - run `alacritty migrate` to convert the yaml to toml
|
||||
fs.".config/alacritty/alacritty.yml".symlink.text = ''
|
||||
font:
|
||||
size: 14
|
||||
|
||||
key_bindings:
|
||||
- { key: N, mods: Control, action: CreateNewWindow }
|
||||
- { key: PageUp, mods: Control, action: ScrollPageUp }
|
||||
- { key: PageDown, mods: Control, action: ScrollPageDown }
|
||||
- { key: PageUp, mods: Control|Shift, action: ScrollPageUp }
|
||||
- { key: PageDown, mods: Control|Shift, action: ScrollPageDown }
|
||||
'';
|
||||
};
|
||||
}
|
@@ -1,277 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
declPackageSet = pkgs: {
|
||||
package = null;
|
||||
suggestedPrograms = pkgs;
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.programs = {
|
||||
# PACKAGE SETS
|
||||
"sane-scripts.backup" = declPackageSet [
|
||||
"sane-scripts.backup-ls"
|
||||
"sane-scripts.backup-restore"
|
||||
];
|
||||
"sane-scripts.bittorrent" = declPackageSet [
|
||||
"sane-scripts.bt-add"
|
||||
"sane-scripts.bt-rm"
|
||||
"sane-scripts.bt-search"
|
||||
"sane-scripts.bt-show"
|
||||
];
|
||||
"sane-scripts.dev" = declPackageSet [
|
||||
"sane-scripts.dev-cargo-loop"
|
||||
"sane-scripts.git-init"
|
||||
];
|
||||
"sane-scripts.cli" = declPackageSet [
|
||||
"sane-scripts.deadlines"
|
||||
"sane-scripts.find-dotfiles"
|
||||
"sane-scripts.ip-check"
|
||||
"sane-scripts.ip-reconnect"
|
||||
"sane-scripts.private-change-passwd"
|
||||
"sane-scripts.private-do"
|
||||
"sane-scripts.private-init"
|
||||
"sane-scripts.private-lock"
|
||||
"sane-scripts.private-unlock"
|
||||
"sane-scripts.rcp"
|
||||
"sane-scripts.reboot"
|
||||
"sane-scripts.reclaim-boot-space"
|
||||
"sane-scripts.reclaim-disk-space"
|
||||
"sane-scripts.secrets-dump"
|
||||
"sane-scripts.secrets-unlock"
|
||||
"sane-scripts.secrets-update-keys"
|
||||
"sane-scripts.shutdown"
|
||||
"sane-scripts.ssl-dump"
|
||||
"sane-scripts.sudo-redirect"
|
||||
"sane-scripts.sync-from-servo"
|
||||
"sane-scripts.vpn"
|
||||
"sane-scripts.which"
|
||||
"sane-scripts.wipe-browser"
|
||||
"sane-scripts.wipe-flare"
|
||||
"sane-scripts.wipe-fractal"
|
||||
];
|
||||
"sane-scripts.sys-utils" = declPackageSet [
|
||||
"sane-scripts.ip-port-forward"
|
||||
"sane-scripts.sync-music"
|
||||
];
|
||||
|
||||
|
||||
sysadminUtils = declPackageSet [
|
||||
"btrfs-progs"
|
||||
"cacert.unbundled" # some services require unbundled /etc/ssl/certs
|
||||
"cryptsetup"
|
||||
"ddrescue"
|
||||
"dig"
|
||||
"dtc" # device tree [de]compiler
|
||||
"e2fsprogs" # resize2fs
|
||||
"efibootmgr"
|
||||
"ethtool"
|
||||
"fatresize"
|
||||
"fd"
|
||||
"file"
|
||||
# "fwupd"
|
||||
"gawk"
|
||||
"gdb" # to debug segfaults
|
||||
"git"
|
||||
"gptfdisk" # gdisk
|
||||
"hdparm"
|
||||
"htop"
|
||||
"iftop"
|
||||
"inetutils" # for telnet
|
||||
"iotop"
|
||||
"iptables"
|
||||
"iw"
|
||||
"jq"
|
||||
"killall"
|
||||
"lsof"
|
||||
"miniupnpc"
|
||||
"nano"
|
||||
# "ncdu" # ncurses disk usage. doesn't cross compile (zig)
|
||||
"neovim"
|
||||
"netcat"
|
||||
"nethogs"
|
||||
"nmap"
|
||||
"nvme-cli" # nvme
|
||||
"openssl"
|
||||
"parted"
|
||||
"pciutils"
|
||||
"powertop"
|
||||
"pstree"
|
||||
"ripgrep"
|
||||
"screen"
|
||||
"smartmontools" # smartctl
|
||||
"socat"
|
||||
"strace"
|
||||
"subversion"
|
||||
"tcpdump"
|
||||
"tree"
|
||||
"usbutils"
|
||||
"util-linux" # lsblk, lscpu, etc
|
||||
"wget"
|
||||
"wirelesstools" # iwlist
|
||||
];
|
||||
sysadminExtraUtils = declPackageSet [
|
||||
"backblaze-b2"
|
||||
"duplicity"
|
||||
"sane-scripts.backup"
|
||||
"sqlite" # to debug sqlite3 databases
|
||||
];
|
||||
|
||||
# TODO: split these into smaller groups.
|
||||
# - moby doesn't want a lot of these.
|
||||
# - categories like
|
||||
# - dev?
|
||||
# - debugging?
|
||||
consoleUtils = declPackageSet [
|
||||
"alsaUtils" # for aplay, speaker-test
|
||||
"binutils-unwrapped" # for strings; though this brings 80MB of unrelated baggage too
|
||||
# "cdrtools"
|
||||
"clinfo"
|
||||
"dmidecode"
|
||||
"dtrx" # `unar` alternative, "Do The Right eXtraction"
|
||||
"efivar"
|
||||
"eza" # a better 'ls'
|
||||
# "flashrom"
|
||||
"git" # needed as a user package, for config.
|
||||
# "gnupg"
|
||||
# "gocryptfs"
|
||||
# "gopass"
|
||||
# "gopass-jsonapi"
|
||||
"helix" # text editor
|
||||
"libsecret" # for managing user keyrings. TODO: what needs this? lift into the consumer
|
||||
"lm_sensors" # for sensors-detect. TODO: what needs this? lift into the consumer
|
||||
"lshw"
|
||||
# "memtester"
|
||||
"mercurial" # hg
|
||||
"neovim" # needed as a user package, for swap persistence
|
||||
# "nettools"
|
||||
# "networkmanager"
|
||||
# "nixos-generators"
|
||||
"nmon"
|
||||
# "node2nix"
|
||||
# "oathToolkit" # for oathtool
|
||||
# "ponymix"
|
||||
"pulsemixer"
|
||||
"python3-repl"
|
||||
# "python3Packages.eyeD3" # music tagging
|
||||
"ripgrep" # needed as a user package so that its user-level config file can be installed
|
||||
"rsync"
|
||||
"sane-scripts.bittorrent"
|
||||
"sane-scripts.cli"
|
||||
"snapper"
|
||||
"sops"
|
||||
"speedtest-cli"
|
||||
# "ssh-to-age"
|
||||
"sudo"
|
||||
# "tageditor" # music tagging
|
||||
# "unar"
|
||||
"unzip"
|
||||
"wireguard-tools"
|
||||
"xdg-utils" # for xdg-open
|
||||
# "yarn"
|
||||
"zsh"
|
||||
];
|
||||
|
||||
desktopConsoleUtils = declPackageSet [
|
||||
"gh" # MS GitHub cli
|
||||
"nix-index"
|
||||
"nixpkgs-review"
|
||||
"sane-scripts.dev"
|
||||
"sequoia"
|
||||
];
|
||||
|
||||
consoleMediaUtils = declPackageSet [
|
||||
"ffmpeg"
|
||||
"imagemagick"
|
||||
"sox"
|
||||
"yt-dlp"
|
||||
];
|
||||
|
||||
tuiApps = declPackageSet [
|
||||
"aerc" # email client
|
||||
"msmtp" # sendmail
|
||||
"offlineimap" # email mailbox sync
|
||||
"sfeed" # RSS fetcher
|
||||
"visidata" # TUI spreadsheet viewer/editor
|
||||
"w3m" # web browser
|
||||
];
|
||||
|
||||
iphoneUtils = declPackageSet [
|
||||
"ifuse"
|
||||
"ipfs"
|
||||
"libimobiledevice"
|
||||
"sane-scripts.sync-from-iphone"
|
||||
];
|
||||
|
||||
devPkgs = declPackageSet [
|
||||
"cargo"
|
||||
"clang"
|
||||
"lua"
|
||||
"nodejs"
|
||||
"patchelf"
|
||||
"rustc"
|
||||
"tree-sitter"
|
||||
];
|
||||
|
||||
|
||||
# INDIVIDUAL PACKAGE DEFINITIONS
|
||||
|
||||
cargo.persist.byStore.plaintext = [ ".cargo" ];
|
||||
|
||||
# creds, but also 200 MB of node modules, etc
|
||||
discord.persist.byStore.private = [ ".config/discord" ];
|
||||
|
||||
# `emote` will show a first-run dialog based on what's in this directory.
|
||||
# mostly, it just keeps a LRU of previously-used emotes to optimize display order.
|
||||
# TODO: package [smile](https://github.com/mijorus/smile) for probably a better mobile experience.
|
||||
emote.persist.byStore.plaintext = [ ".local/share/Emote" ];
|
||||
|
||||
fluffychat-moby.persist.byStore.plaintext = [ ".local/share/chat.fluffy.fluffychat" ];
|
||||
|
||||
font-manager.package = pkgs.font-manager.override {
|
||||
# build without the "Google Fonts" integration feature, to save closure / avoid webkitgtk_4_0
|
||||
withWebkit = false;
|
||||
};
|
||||
|
||||
# MS GitHub stores auth token in .config
|
||||
# TODO: we can populate gh's stuff statically; it even lets us use the same oauth across machines
|
||||
gh.persist.byStore.private = [ ".config/gh" ];
|
||||
|
||||
"gnome.gnome-maps".persist.byStore.plaintext = [ ".cache/shumate" ];
|
||||
"gnome.gnome-maps".persist.byStore.private = [ ".local/share/maps-places.json" ];
|
||||
|
||||
# actual monero blockchain (not wallet/etc; safe to delete, just slow to regenerate)
|
||||
# XXX: is it really safe to persist this? it doesn't have info that could de-anonymize if captured?
|
||||
monero-gui.persist.byStore.plaintext = [ ".bitmonero" ];
|
||||
|
||||
mumble.persist.byStore.private = [ ".local/share/Mumble" ];
|
||||
|
||||
# settings (electron app)
|
||||
obsidian.persist.byStore.plaintext = [ ".config/obsidian" ];
|
||||
|
||||
python3-repl.package = pkgs.python3.withPackages (ps: with ps; [
|
||||
requests
|
||||
]);
|
||||
|
||||
# creds, media
|
||||
signal-desktop.persist.byStore.private = [ ".config/Signal" ];
|
||||
|
||||
# printer/filament settings
|
||||
slic3r.persist.byStore.plaintext = [ ".Slic3r" ];
|
||||
|
||||
tdesktop.persist.byStore.private = [ ".local/share/TelegramDesktop" ];
|
||||
|
||||
tokodon.persist.byStore.private = [ ".cache/KDE/tokodon" ];
|
||||
|
||||
whalebird.persist.byStore.private = [ ".config/Whalebird" ];
|
||||
|
||||
yarn.persist.byStore.plaintext = [ ".cache/yarn" ];
|
||||
|
||||
# zcash coins. safe to delete, just slow to regenerate (10-60 minutes)
|
||||
zecwallet-lite.persist.byStore.private = [ ".zcash" ];
|
||||
};
|
||||
|
||||
programs.feedbackd = lib.mkIf config.sane.programs.feedbackd.enabled {
|
||||
enable = true;
|
||||
};
|
||||
}
|
@@ -1,103 +0,0 @@
|
||||
{ lib, pkgs, ... }:
|
||||
# notable bemenu options:
|
||||
# - see `bemenu --help` for all
|
||||
# -P, --prefix text to show before highlighted item.
|
||||
# --scrollbar display scrollbar. (none (default), always, autohide)
|
||||
# -H, --line-height defines the height to make each menu line (0 = default height). (wx)
|
||||
# -M, --margin defines the empty space on either side of the menu. (wx)
|
||||
# -W, --width-factor defines the relative width factor of the menu (from 0 to 1). (wx)
|
||||
# -B, --border defines the width of the border in pixels around the menu. (wx)
|
||||
# -R --border-radius defines the radius of the border around the menu (0 = no curved borders).
|
||||
# --ch defines the height of the cursor (0 = scales with line height). (wx)
|
||||
# --cw defines the width of the cursor. (wx)
|
||||
# --hp defines the horizontal padding for the entries in single line mode. (wx)
|
||||
# --fn defines the font to be used ('name [size]'). (wx)
|
||||
# --tb defines the title background color. (wx)
|
||||
# --tf defines the title foreground color. (wx)
|
||||
# --fb defines the filter background color. (wx)
|
||||
# --ff defines the filter foreground color. (wx)
|
||||
# --nb defines the normal background color. (wx)
|
||||
# --nf defines the normal foreground color. (wx)
|
||||
# --hb defines the highlighted background color. (wx)
|
||||
# --hf defines the highlighted foreground color. (wx)
|
||||
# --fbb defines the feedback background color. (wx)
|
||||
# --fbf defines the feedback foreground color. (wx)
|
||||
# --sb defines the selected background color. (wx)
|
||||
# --sf defines the selected foreground color. (wx)
|
||||
# --ab defines the alternating background color. (wx)
|
||||
# --af defines the alternating foreground color. (wx)
|
||||
# --scb defines the scrollbar background color. (wx)
|
||||
# --scf defines the scrollbar foreground color. (wx)
|
||||
# --bdr defines the border color. (wx)
|
||||
#
|
||||
# colors are specified as `#RRGGBB`
|
||||
# defaults:
|
||||
# --ab "#222222"
|
||||
# --af "#bbbbbb"
|
||||
# --bdr "#005577"
|
||||
# --border 3
|
||||
# --cb "#222222"
|
||||
# --center
|
||||
# --cf "#bbbbbb"
|
||||
# --fb "#222222"
|
||||
# --fbb "#eeeeee"
|
||||
# --fbf "#222222"
|
||||
# --ff "#bbbbbb"
|
||||
# --fixed-height
|
||||
# --fn 'Sxmo 14'
|
||||
# --hb "#005577"
|
||||
# --hf "#eeeeee"
|
||||
# --line-height 20
|
||||
# --list 16
|
||||
# --margin 40
|
||||
# --nb "#222222"
|
||||
# --nf "#bbbbbb"
|
||||
# --no-overlap
|
||||
# --no-spacing
|
||||
# --sb "#323232"
|
||||
# --scb "#005577"
|
||||
# --scf "#eeeeee"
|
||||
# --scrollbar autohide
|
||||
# --tb "#005577"
|
||||
# --tf "#eeeeee"
|
||||
# --wrap
|
||||
let
|
||||
bg = "#1d1721"; # slight purple
|
||||
fg0 = "#d8d8d8"; # inactive text (light grey)
|
||||
fg1 = "#ffffff"; # active text (white)
|
||||
accent0 = "#1f5e54"; # darker but saturated teal
|
||||
accent1 = "#418379"; # teal (matches nixos-bg)
|
||||
accent2 = "#5b938a"; # brighter but muted teal
|
||||
bemenuArgs = [
|
||||
"--wrap --scrollbar autohide --fixed-height"
|
||||
"--center --margin 45"
|
||||
"--no-spacing"
|
||||
# XXX: font size doesn't seem to take effect (would prefer larger)
|
||||
"--fn 'monospace 14' --line-height 22 --border 3"
|
||||
"--bdr '${accent0}'" # border
|
||||
"--scf '${accent2}' --scb '${accent0}'" # scrollbar
|
||||
"--tb '${accent0}' --tf '${fg0}'" # title
|
||||
"--fb '${accent0}' --ff '${fg1}'" # filter (i.e. text that's been entered)
|
||||
"--hb '${accent1}' --hf '${fg1}'" # selected item
|
||||
"--nb '${bg}' --nf '${fg0}'" # normal lines (even)
|
||||
"--ab '${bg}' --af '${fg0}'" # alternated lines (odd)
|
||||
"--cf '${accent0}' --cb '${accent0}'" # cursor (not very useful)
|
||||
];
|
||||
bemenuOpts = lib.concatStringsSep " " bemenuArgs;
|
||||
in
|
||||
{
|
||||
sane.programs.bemenu = {
|
||||
package = pkgs.bemenu.overrideAttrs (upstream: {
|
||||
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ [
|
||||
pkgs.makeWrapper
|
||||
];
|
||||
# can alternatively be specified as CLI flags
|
||||
postInstall = (upstream.postInstall or "") + ''
|
||||
wrapProgram $out/bin/bemenu \
|
||||
--set BEMENU_OPTS "${bemenuOpts}"
|
||||
wrapProgram $out/bin/bemenu-run \
|
||||
--set BEMENU_OPTS "${bemenuOpts}"
|
||||
'';
|
||||
});
|
||||
};
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.brave = {
|
||||
persist.byStore.cryptClearOnBoot = [
|
||||
".cache/BraveSoftware"
|
||||
".config/BraveSoftware"
|
||||
];
|
||||
};
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
# GNOME calls
|
||||
# - <https://gitlab.gnome.org/GNOME/calls>
|
||||
# - both a dialer and a call handler.
|
||||
# - uses callaudiod dbus package.
|
||||
#
|
||||
# initial JMP.chat configuration:
|
||||
# - message @cheogram.com "reset sip account" (this is not destructive, despite the name)
|
||||
# - the bot will reply with auto-generated username/password plus a SIP server endpoint.
|
||||
# just copy those into gnome-calls' GUI configurator
|
||||
# - now gnome-calls can do outbound calls. inbound calls requires more chatting with the help bot
|
||||
#
|
||||
# my setup here is still very WIP.
|
||||
# open questions:
|
||||
# - can i receive calls even with GUI closed?
|
||||
# - e.g. activated by callaudiod?
|
||||
# - looks like `gnome-calls --daemon` does that?
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.calls;
|
||||
in
|
||||
{
|
||||
sane.programs.calls = {
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
persist.byStore.private = [
|
||||
# ".cache/folks" # contact avatars?
|
||||
# ".config/calls"
|
||||
".local/share/calls" # call "records"
|
||||
# .local/share/folks # contacts?
|
||||
];
|
||||
secrets.".config/calls/sip-account.cfg" = ../../../secrets/common/gnome_calls_sip-account.cfg.bin;
|
||||
suggestedPrograms = [
|
||||
"feedbackd" # needs `phone-incoming-call`, in particular
|
||||
];
|
||||
|
||||
services.gnome-calls = {
|
||||
# TODO: prevent gnome-calls from daemonizing when started manually
|
||||
description = "gnome-calls daemon to monitor incoming SIP calls";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||
serviceConfig = {
|
||||
# add --verbose for more debugging
|
||||
ExecStart = "${cfg.package}/bin/gnome-calls --daemon";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
environment.G_MESSAGES_DEBUG = "all";
|
||||
};
|
||||
};
|
||||
programs.calls = lib.mkIf cfg.enabled {
|
||||
enable = true;
|
||||
};
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
# cantata is a mpd frontend.
|
||||
# before launching it, run `mopidy` in some tab
|
||||
# TODO: auto-launch mopidy when cantata launches?
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.cantata = {
|
||||
persist.byStore.plaintext = [
|
||||
".cache/cantata" # album art
|
||||
".local/share/cantata/library" # library index (?)
|
||||
];
|
||||
fs.".config/cantata/cantata.conf".symlink.text = ''
|
||||
[General]
|
||||
fetchCovers=true
|
||||
storeCoversInMpdDir=false
|
||||
version=2.5.0
|
||||
|
||||
[Connection]
|
||||
allowLocalStreaming=true
|
||||
applyReplayGain=true
|
||||
autoUpdate=false
|
||||
dir=~/Music
|
||||
host=localhost
|
||||
partition=
|
||||
passwd=
|
||||
port=6600
|
||||
replayGain=off
|
||||
streamUrl=
|
||||
|
||||
[LibraryPage]
|
||||
artist\gridZoom=100
|
||||
artist\searchActive=false
|
||||
artist\viewMode=detailedtree
|
||||
'';
|
||||
suggestedPrograms = [ "mopidy" ];
|
||||
};
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
chattyNoOauth = pkgs.chatty.override {
|
||||
# the OAuth feature (presumably used for web-based logins) pulls a full webkitgtk.
|
||||
# especially when using the gtk3 version of evolution-data-server, it's an ancient webkitgtk_4_1.
|
||||
# disable OAuth for a faster build & smaller closure
|
||||
evolution-data-server = pkgs.evolution-data-server.override {
|
||||
enableOAuth2 = false;
|
||||
gnome-online-accounts = pkgs.gnome-online-accounts.override {
|
||||
# disables the upstream "goabackend" feature -- presumably "Gnome Online Accounts Backend"
|
||||
# frees us from webkit_4_1, in turn.
|
||||
enableBackend = false;
|
||||
gvfs = pkgs.gvfs.override {
|
||||
# saves 20 minutes of build time, for unused feature
|
||||
samba = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
chatty-latest = pkgs.chatty-latest.override {
|
||||
evolution-data-server-gtk4 = pkgs.evolution-data-server-gtk4.override {
|
||||
gnome-online-accounts = pkgs.gnome-online-accounts.override {
|
||||
# disables the upstream "goabackend" feature -- presumably "Gnome Online Accounts Backend"
|
||||
# frees us from webkit_4_1, in turn.
|
||||
enableBackend = false;
|
||||
gvfs = pkgs.gvfs.override {
|
||||
# saves 20 minutes of build time and cross issues, for unused feature
|
||||
samba = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.programs.chatty = {
|
||||
# package = chattyNoOauth;
|
||||
package = chatty-latest;
|
||||
suggestedPrograms = [ "gnome-keyring" ];
|
||||
persist.byStore.private = [
|
||||
".local/share/chatty" # matrix avatars and files
|
||||
# not just XMPP; without this Chatty will regenerate its device-id every boot.
|
||||
# .purple/ contains XMPP *and* Matrix auth, logs, avatar cache, and a bit more
|
||||
".purple"
|
||||
];
|
||||
};
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash
|
||||
|
||||
full=$(cat /sys/class/power_supply/axp20x-battery/charge_full_design)
|
||||
rate=$(cat /sys/class/power_supply/axp20x-battery/current_now)
|
||||
perc=$(cat /sys/class/power_supply/axp20x-battery/capacity)
|
||||
perc_left=$((100 - $perc))
|
||||
# these icons come from sxmo; they only render in nerdfonts
|
||||
bat_dis=""
|
||||
bat_chg=""
|
||||
|
||||
fmt_minutes() {
|
||||
# args: <battery symbol> <text if ludicrous estimate> <estimated minutes to full/empty>
|
||||
if [[ $3 -gt 1440 ]]; then
|
||||
printf "%s %s" "$1" "$2" # more than 1d
|
||||
else
|
||||
hr=$(($3 / 60))
|
||||
hr_in_min=$(($hr * 60))
|
||||
min=$(($3 - $hr_in_min))
|
||||
printf "%s %dh%02dm" "$1" "$hr" "$min"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ $rate -lt 0 ]]; then
|
||||
# discharging
|
||||
fmt_minutes "$bat_dis" '∞' "$(($full * 60 * $perc / (-100 * $rate)))"
|
||||
elif [[ $rate -gt 0 ]]; then
|
||||
# charging
|
||||
fmt_minutes "$bat_chg" '100%' "$(($full * 60 * $perc_left / (100 * $rate)))"
|
||||
else
|
||||
echo "$bat_dis $perc%"
|
||||
fi
|
@@ -1,50 +0,0 @@
|
||||
-- docs: <https://conky.cc/variables>
|
||||
-- color names are X11 colors: <https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart>
|
||||
-- - can also use #rrggbb syntax
|
||||
-- example configs: <https://forum.manjaro.org/t/conky-showcase-2022/97123>
|
||||
-- example configs: <https://www.reddit.com/r/Conkyporn/>
|
||||
|
||||
conky.config = {
|
||||
out_to_wayland = true,
|
||||
update_interval = 10,
|
||||
|
||||
alignment = 'middle_middle',
|
||||
own_window_type = 'desktop',
|
||||
-- own_window_argb_value: opacity of the background (0-255)
|
||||
own_window_argb_value = 0,
|
||||
-- own_window_argb_value = 92,
|
||||
-- own_window_colour = '#beebe5', -- beebe5 matches nixos flake bg color
|
||||
|
||||
-- "border" pads the entire conky window
|
||||
-- this can be used to control the extent of the own_window background
|
||||
border_inner_margin = 8,
|
||||
-- optionally, actually draw borders
|
||||
-- draw_borders = true,
|
||||
|
||||
-- shades are drop-shadows, outline is the centered version. both apply to text only
|
||||
draw_shades = true,
|
||||
draw_outline = false,
|
||||
default_shade_color = '#beebe5',
|
||||
default_outline_color = '#beebe5',
|
||||
|
||||
font = 'sans-serif:size=8',
|
||||
use_xft = true,
|
||||
|
||||
default_color = '#ffffff',
|
||||
color1 = '000000',
|
||||
color2 = '404040',
|
||||
}
|
||||
|
||||
-- texeci <interval_sec> <cmd>: run the command periodically, _in a separate thread_ so as not to block rendering
|
||||
conky.text = [[
|
||||
${color1}${shadecolor 707070}${font sans-serif:size=50:style=Bold}${alignc}${exec date +"%H:%M"}${font}
|
||||
${color2}${shadecolor a4d7d0}${font sans-serif:size=20}${alignc}${exec date +"%a %d %b"}${font}
|
||||
|
||||
|
||||
${color1}${shadecolor}${font sans-serif:size=22:style=Bold}${alignc}${exec @bat@ }${font}
|
||||
${color1}${shadecolor}${font sans-serif:size=20:style=Bold}${alignc}${texeci 600 @weather@ }${font}
|
||||
|
||||
|
||||
${color2}${shadecolor a4d7d0}${font sans-serif:size=16}${alignc}⇅ ${downspeedf wlan0}K/s${font}
|
||||
${font sans-serif:size=16}${alignc}☵ $memperc% $cpu%${font}
|
||||
]]
|
@@ -1,34 +0,0 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
sane.programs.conky = {
|
||||
fs.".config/conky/conky.conf".symlink.target =
|
||||
let
|
||||
battery_estimate = pkgs.static-nix-shell.mkBash {
|
||||
pname = "battery_estimate";
|
||||
src = ./.;
|
||||
};
|
||||
in pkgs.substituteAll {
|
||||
src = ./conky.conf;
|
||||
bat = "${battery_estimate}/bin/battery_estimate";
|
||||
weather = "timeout 20 ${pkgs.sane-weather}/bin/sane-weather";
|
||||
};
|
||||
|
||||
services.conky = {
|
||||
description = "conky dynamic desktop background";
|
||||
wantedBy = [ "default.target" ];
|
||||
# XXX: should be part of graphical-session.target, but whatever mix of greetd/sway
|
||||
# i'm using means that target's never reached...
|
||||
# wantedBy = [ "graphical-session.target" ];
|
||||
# partOf = [ "graphical-session.target" ];
|
||||
|
||||
serviceConfig.ExecStart = "${config.sane.programs.conky.package}/bin/conky";
|
||||
serviceConfig.Type = "simple";
|
||||
serviceConfig.Restart = "on-failure";
|
||||
serviceConfig.RestartSec = "10s";
|
||||
# serviceConfig.Slice = "session.slice";
|
||||
|
||||
# don't start conky until after sway
|
||||
preStart = ''test -n "$SWAYSOCK"'';
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.programs.cozy = {
|
||||
# cozy uses a sqlite db for its config and exposes no CLI options other than --help and --debug
|
||||
persist.byStore.plaintext = [
|
||||
".local/share/cozy" # sqlite db (config & index?)
|
||||
".cache/cozy" # offline cache
|
||||
];
|
||||
};
|
||||
}
|
@@ -1,79 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./aerc.nix
|
||||
./alacritty.nix
|
||||
./assorted.nix
|
||||
./bemenu.nix
|
||||
./brave.nix
|
||||
./calls.nix
|
||||
./cantata.nix
|
||||
./chatty.nix
|
||||
./conky
|
||||
./cozy.nix
|
||||
./dino.nix
|
||||
./element-desktop.nix
|
||||
./epiphany.nix
|
||||
./evince.nix
|
||||
./feedbackd.nix
|
||||
./firefox.nix
|
||||
./flare-signal.nix
|
||||
./fontconfig.nix
|
||||
./fractal.nix
|
||||
./fwupd.nix
|
||||
./g4music.nix
|
||||
./gajim.nix
|
||||
./geary.nix
|
||||
./git.nix
|
||||
./gnome-feeds.nix
|
||||
./gnome-keyring.nix
|
||||
./gnome-weather.nix
|
||||
./gpodder.nix
|
||||
./gthumb.nix
|
||||
./helix.nix
|
||||
./imagemagick.nix
|
||||
./jellyfin-media-player.nix
|
||||
./komikku.nix
|
||||
./koreader
|
||||
./libreoffice.nix
|
||||
./lemoa.nix
|
||||
./mako.nix
|
||||
./megapixels.nix
|
||||
./mepo.nix
|
||||
./mopidy.nix
|
||||
./mpv.nix
|
||||
./msmtp.nix
|
||||
./neovim.nix
|
||||
./newsflash.nix
|
||||
./nheko.nix
|
||||
./nix-index.nix
|
||||
./ntfy-sh.nix
|
||||
./obsidian.nix
|
||||
./offlineimap.nix
|
||||
./playerctl.nix
|
||||
./rhythmbox.nix
|
||||
./ripgrep.nix
|
||||
./sfeed.nix
|
||||
./splatmoji.nix
|
||||
./spotify.nix
|
||||
./steam.nix
|
||||
./stepmania.nix
|
||||
./sublime-music.nix
|
||||
./swaynotificationcenter.nix
|
||||
./tangram.nix
|
||||
./tor-browser-bundle-bin.nix
|
||||
./tuba.nix
|
||||
./vlc.nix
|
||||
./wireshark.nix
|
||||
./xarchiver.nix
|
||||
./zeal.nix
|
||||
./zsh
|
||||
];
|
||||
|
||||
config = {
|
||||
# XXX: this might not be necessary. try removing this and cacert.unbundled (servo)?
|
||||
environment.etc."ssl/certs".source = "${pkgs.cacert.unbundled}/etc/ssl/certs/*";
|
||||
|
||||
};
|
||||
}
|
@@ -1,71 +0,0 @@
|
||||
# usage:
|
||||
# - start a DM with a rando via
|
||||
# - '+' -> 'start conversation'
|
||||
# - add a user to your roster via
|
||||
# - '+' -> 'start conversation' -> '+' (opens the "add contact" dialog)
|
||||
# - this triggers a popup on the remote side asking them for confirmation
|
||||
# - after the remote's confirmation there will be a local popup for you to allow them to add you to their roster
|
||||
# - to make a call:
|
||||
# - ensure the other party is in your roster
|
||||
# - open a DM with the party
|
||||
# - click the phone icon at top (only visible if other party is in your roster)
|
||||
#
|
||||
# dino can be autostarted on login -- useful to ensure that i always receive calls and notifications --
|
||||
# but at present it has no "start in tray" type of option: it must render a window.
|
||||
#
|
||||
# outstanding bugs:
|
||||
# - mic is sometimes disabled at call start despite presenting as enabled
|
||||
# - fix is to toggle it off -> on in the Dino UI
|
||||
# - default mic gain is WAY TOO MUCH (heavily distorted)
|
||||
# - TODO: dino should have more optimal niceness/priority to ensure it can process its buffers
|
||||
# - possibly this is solved by enabling RealtimeKit (rtkit)
|
||||
# - TODO: see if Dino calls work better with `echo full > /sys/kernel/debug/sched/preempt`
|
||||
#
|
||||
# probably fixed:
|
||||
# - once per 1-2 minutes dino will temporarily drop mic input:
|
||||
# - `rtp-WRNING: plugin.vala:148: Warning in pipeline: Can't record audio fast enough
|
||||
# - this was *partially* fixed by bumping the pipewire mic buffer to 2048 samples (from ~512)
|
||||
# - this was further fixed by setting PULSE_LATENCY_MSEC=20.
|
||||
# - possibly Dino should be updated internally: `info.rate / 100` -> `info.rate / 50`.
|
||||
# - i think that affects the batching for echo cancellation, adaptive gain control, etc.
|
||||
#
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.dino;
|
||||
in
|
||||
{
|
||||
sane.programs.dino = {
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
persist.byStore.private = [ ".local/share/dino" ];
|
||||
|
||||
services.dino = {
|
||||
description = "auto-start and maintain dino XMPP connection";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/dino";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
|
||||
# audio buffering; see: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#pipewire-buffering-explained>
|
||||
# dino defaults to 10ms mic buffer, which causes underruns, which Dino handles *very* poorly
|
||||
# as in, the other end of the call will just not receive sound from us for a couple seconds.
|
||||
# pipewire uses power-of-two buffering for the mic itself. that would put us at 21.33 ms, but this env var supports only whole numbers (21ms ends up not power-of-two).
|
||||
# also, Dino's likely still doing things in 10ms batches internally anyway.
|
||||
environment.PULSE_LATENCY_MSEC = "20";
|
||||
|
||||
# note that debug logging during calls produces so much journal spam that it pegs the CPU and causes dropped audio
|
||||
# environment.G_MESSAGES_DEBUG = "all";
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
# debugging tips:
|
||||
# - if element opens but does not render:
|
||||
# - `element-desktop --disable-gpu --in-process-gpu`
|
||||
# - <https://github.com/vector-im/element-desktop/issues/1029#issuecomment-1632688224>
|
||||
# - `rm -rf ~/.config/Element/GPUCache`
|
||||
# - <https://github.com/NixOS/nixpkgs/issues/244486>
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.element-desktop = {
|
||||
# creds/session keys, etc
|
||||
persist.byStore.private = [ ".config/Element" ];
|
||||
|
||||
suggestedPrograms = [ "gnome-keyring" ];
|
||||
};
|
||||
}
|
@@ -1,48 +0,0 @@
|
||||
# epiphany web browser
|
||||
# - GTK4/webkitgtk
|
||||
#
|
||||
# usability notes:
|
||||
# - touch-based scroll works well (for moby)
|
||||
# - URL bar constantly resets cursor to the start of the line as i type
|
||||
# - maybe due to the URLbar suggestions getting in the way
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.epiphany = {
|
||||
# XXX(2023/07/08): running on moby without `WEBKIT_DISABLE_SANDBOX...` fails, with:
|
||||
# - `bwrap: Can't make symlink at /var/run: File exists`
|
||||
# this could be due to:
|
||||
# - epiphany is somewhere following a symlink into /var/run instead of /run
|
||||
# - (nothing in `env` or in this repo touches /var/run)
|
||||
# - no xdg-desktop-portal is installed (unlikely)
|
||||
#
|
||||
# a few other users have hit this, in different contexts:
|
||||
# - <https://gitlab.gnome.org/GNOME/gnome-builder/-/issues/1164>
|
||||
# - <https://github.com/flatpak/flatpak/issues/3477>
|
||||
# - <https://github.com/NixOS/nixpkgs/issues/197085>
|
||||
#
|
||||
# TODO: consider `WEBKIT_USE_SINGLE_WEB_PROCESS=1` for better perf
|
||||
# - this runs all tabs in 1 process. which is fine, if i'm not a heavy multi-tabber
|
||||
package = pkgs.epiphany.overrideAttrs (upstream: {
|
||||
preFixup = ''
|
||||
gappsWrapperArgs+=(
|
||||
--set WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS "1"
|
||||
);
|
||||
'' + (upstream.preFixup or "");
|
||||
});
|
||||
persist.byStore.private = [
|
||||
".cache/epiphany"
|
||||
".local/share/epiphany"
|
||||
# also .config/epiphany, but appears empty
|
||||
];
|
||||
mime.priority = 200; # default priority is 100: install epiphany only as a fallback
|
||||
mime.associations = let
|
||||
desktop = "org.gnome.Epiphany.desktop";
|
||||
in {
|
||||
"text/html" = desktop;
|
||||
"x-scheme-handler/http" = desktop;
|
||||
"x-scheme-handler/https" = desktop;
|
||||
"x-scheme-handler/about" = desktop;
|
||||
"x-scheme-handler/unknown" = desktop;
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.evince.mime.associations."application/pdf" = "org.gnome.Evince.desktop";
|
||||
}
|
@@ -1,114 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.feedbackd;
|
||||
in
|
||||
{
|
||||
sane.programs.feedbackd = {
|
||||
package = pkgs.rmDbusServices pkgs.feedbackd;
|
||||
|
||||
configOption = with lib; mkOption {
|
||||
type = types.submodule {
|
||||
options.proxied = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
whether to use a sound theme in which common application events are muted
|
||||
with the intent that a proxy (notification daemon) with knowledge of this
|
||||
modification will "speak" on behalf of all applications.
|
||||
'';
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
};
|
||||
|
||||
# N.B.: feedbackd will load ~/.config/feedbackd/themes/default.json by default
|
||||
# - but using that would forbid `parent-theme = "default"`
|
||||
# the default theme ships support for these events:
|
||||
# - alarm-clock-elapsed
|
||||
# - battery-caution
|
||||
# - bell-terminal
|
||||
# - button-pressed
|
||||
# - button-released
|
||||
# - camera-focus
|
||||
# - camera-shutter
|
||||
# - message-missed-email
|
||||
# - message-missed-instant
|
||||
# - message-missed-notification
|
||||
# - message-missed-sms
|
||||
# - message-new-email
|
||||
# - message-new-instant
|
||||
# - message-new-sms
|
||||
# - message-sent-instant
|
||||
# - phone-failure
|
||||
# - phone-hangup
|
||||
# - phone-incoming-call
|
||||
# - phone-missed-call
|
||||
# - phone-outgoing-busy
|
||||
# - screen-capture
|
||||
# - theme-demo
|
||||
# - timeout-completed
|
||||
# - window-close
|
||||
fs.".config/feedbackd/themes/proxied.json".symlink.text = builtins.toJSON {
|
||||
name = "proxied";
|
||||
parent-theme = "default";
|
||||
profiles = [
|
||||
{
|
||||
name = "full";
|
||||
feedbacks = [
|
||||
# forcibly disable normal events which we'd prefer for the notification daemon (e.g. swaync) to handle
|
||||
{
|
||||
event-name = "message-new-instant";
|
||||
type = "Dummy";
|
||||
}
|
||||
{
|
||||
event-name = "proxied-message-new-instant";
|
||||
type = "Sound";
|
||||
effect = "message-new-instant";
|
||||
}
|
||||
# re-define sounds from the default theme which we'd like to pass through w/o proxying.
|
||||
# i guess this means i'm not inheriting the default theme :|
|
||||
{
|
||||
event-name = "phone-incoming-call";
|
||||
type = "Sound";
|
||||
effect = "phone-incoming-call";
|
||||
}
|
||||
{
|
||||
event-name = "alarm-clock-elapsed";
|
||||
type = "Sound";
|
||||
effect = "alarm-clock-elapsed";
|
||||
}
|
||||
{
|
||||
event-name = "timeout-completed";
|
||||
type = "Sound";
|
||||
effect = "complete";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.feedbackd = {
|
||||
description = "feedbackd audio/vibration/led controller";
|
||||
wantedBy = [ "default.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/libexec/feedbackd";
|
||||
Type = "simple";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
environment = {
|
||||
G_MESSAGES_DEBUG = "all";
|
||||
} // (lib.optionalAttrs cfg.config.proxied {
|
||||
FEEDBACK_THEME = "/home/colin/.config/feedbackd/themes/proxied.json";
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
services.udev.packages = lib.mkIf cfg.enabled [
|
||||
# ships udev rules for `feedbackd` group to be able to control vibrator and LEDs
|
||||
cfg.package
|
||||
];
|
||||
users.groups = lib.mkIf cfg.enabled {
|
||||
feedbackd = {};
|
||||
};
|
||||
}
|
@@ -1,268 +0,0 @@
|
||||
# common settings to toggle (at runtime, in about:config):
|
||||
# > security.ssl.require_safe_negotiation
|
||||
|
||||
# librewolf is a forked firefox which patches firefox to allow more things
|
||||
# (like default search engines) to be configurable at runtime.
|
||||
# many of the settings below won't have effect without those patches.
|
||||
# see: https://gitlab.com/librewolf-community/settings/-/blob/master/distribution/policies.json
|
||||
|
||||
{ config, lib, pkgs, ...}:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.sane.programs.firefox.config;
|
||||
mobile-prefs = lib.optionals false pkgs.librewolf-pmos-mobile.extraPrefsFiles;
|
||||
# allow easy switching between firefox and librewolf with `defaultSettings`, below
|
||||
librewolfSettings = {
|
||||
browser = pkgs.librewolf-unwrapped;
|
||||
extraPrefsFiles = pkgs.librewolf-unwrapped.extraPrefsFiles ++ mobile-prefs;
|
||||
libName = "librewolf";
|
||||
dotDir = ".librewolf";
|
||||
cacheDir = ".cache/librewolf";
|
||||
desktop = "librewolf.desktop";
|
||||
};
|
||||
firefoxSettings = {
|
||||
browser = pkgs.firefox-esr-unwrapped;
|
||||
extraPrefsFiles = mobile-prefs;
|
||||
libName = "firefox";
|
||||
dotDir = ".mozilla/firefox";
|
||||
cacheDir = ".cache/mozilla";
|
||||
desktop = "firefox.desktop";
|
||||
};
|
||||
# defaultSettings = firefoxSettings;
|
||||
defaultSettings = librewolfSettings;
|
||||
|
||||
addon = name: extid: hash: pkgs.fetchFirefoxAddon {
|
||||
inherit name hash;
|
||||
url = "https://addons.mozilla.org/firefox/downloads/latest/${name}/latest.xpi";
|
||||
# extid can be found by unar'ing the above xpi, and copying browser_specific_settings.gecko.id field
|
||||
fixedExtid = extid;
|
||||
};
|
||||
localAddon = pkg: pkgs.fetchFirefoxAddon {
|
||||
inherit (pkg) name;
|
||||
src = "${pkg}/share/mozilla/extensions/\\{ec8030f7-c20a-464f-9b0e-13a3a9e97384\\}/${pkg.extid}.xpi";
|
||||
fixedExtid = pkg.extid;
|
||||
};
|
||||
|
||||
package = pkgs.wrapFirefox cfg.browser.browser {
|
||||
# inherit the default librewolf.cfg
|
||||
# it can be further customized via ~/.librewolf/librewolf.overrides.cfg
|
||||
inherit (cfg.browser) extraPrefsFiles libName;
|
||||
|
||||
extraNativeMessagingHosts = optional cfg.addons.browserpass-extension.enable pkgs.browserpass;
|
||||
# extraNativeMessagingHosts = [ pkgs.gopass-native-messaging-host ];
|
||||
|
||||
nixExtensions = concatMap (ext: optional ext.enable ext.package) (attrValues cfg.addons);
|
||||
|
||||
extraPolicies = {
|
||||
FirefoxHome = {
|
||||
Search = true;
|
||||
Pocket = false;
|
||||
Snippets = false;
|
||||
TopSites = false;
|
||||
Highlights = false;
|
||||
};
|
||||
NoDefaultBookmarks = true;
|
||||
OfferToSaveLogins = false;
|
||||
OfferToSaveLoginsDefault = false;
|
||||
PasswordManagerEnabled = false;
|
||||
SearchEngines = {
|
||||
Default = "DuckDuckGo";
|
||||
};
|
||||
UserMessaging = {
|
||||
ExtensionRecommendations = false;
|
||||
FeatureRecommendations = false;
|
||||
SkipOnboarding = true;
|
||||
UrlbarInterventions = false;
|
||||
WhatsNew = false;
|
||||
};
|
||||
|
||||
# these were taken from Librewolf
|
||||
AppUpdateURL = "https://localhost";
|
||||
DisableAppUpdate = true;
|
||||
OverrideFirstRunPage = "";
|
||||
OverridePostUpdatePage = "";
|
||||
DisableSystemAddonUpdate = true;
|
||||
DisableFirefoxStudies = true;
|
||||
DisableTelemetry = true;
|
||||
DisableFeedbackCommands = true;
|
||||
DisablePocket = true;
|
||||
DisableSetDesktopBackground = false;
|
||||
|
||||
# remove many default search providers
|
||||
# XXX this seems to prevent the `nixExtensions` from taking effect
|
||||
# Extensions.Uninstall = [
|
||||
# "google@search.mozilla.org"
|
||||
# "bing@search.mozilla.org"
|
||||
# "amazondotcom@search.mozilla.org"
|
||||
# "ebay@search.mozilla.org"
|
||||
# "twitter@search.mozilla.org"
|
||||
# ];
|
||||
# XXX doesn't seem to have any effect...
|
||||
# docs: https://github.com/mozilla/policy-templates#homepage
|
||||
# Homepage = {
|
||||
# HomepageURL = "https://uninsane.org/";
|
||||
# StartPage = "homepage";
|
||||
# };
|
||||
# NewTabPage = true;
|
||||
};
|
||||
# extraPrefs = ...
|
||||
};
|
||||
|
||||
addonOpts = types.submodule {
|
||||
options = {
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
};
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
configOpts = {
|
||||
options = {
|
||||
browser = mkOption {
|
||||
default = defaultSettings;
|
||||
type = types.anything;
|
||||
};
|
||||
persistData = mkOption {
|
||||
description = "optional store name to which persist browsing data (like history)";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
persistCache = mkOption {
|
||||
description = "optional store name to which persist browser cache";
|
||||
type = types.nullOr types.str;
|
||||
default = "cryptClearOnBoot";
|
||||
};
|
||||
addons = mkOption {
|
||||
type = types.attrsOf addonOpts;
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
config = mkMerge [
|
||||
({
|
||||
sane.programs.firefox.configOption = mkOption {
|
||||
type = types.submodule configOpts;
|
||||
default = {};
|
||||
};
|
||||
sane.programs.firefox.config.addons = {
|
||||
browserpass-extension = {
|
||||
package = pkgs.firefox-extensions.browserpass-extension;
|
||||
enable = lib.mkDefault true;
|
||||
};
|
||||
bypass-paywalls-clean = {
|
||||
package = pkgs.firefox-extensions.bypass-paywalls-clean;
|
||||
enable = lib.mkDefault true;
|
||||
};
|
||||
ether-metamask = {
|
||||
package = pkgs.firefox-extensions.ether-metamask;
|
||||
enable = lib.mkDefault false; # until i can disable the first-run notification
|
||||
};
|
||||
i2p-in-private-browsing = {
|
||||
package = pkgs.firefox-extensions.i2p-in-private-browsing;
|
||||
enable = lib.mkDefault config.services.i2p.enable;
|
||||
};
|
||||
sidebery = {
|
||||
package = pkgs.firefox-extensions.sidebery;
|
||||
enable = lib.mkDefault true;
|
||||
};
|
||||
sponsorblock = {
|
||||
package = pkgs.firefox-extensions.sponsorblock;
|
||||
enable = lib.mkDefault true;
|
||||
};
|
||||
ublacklist = {
|
||||
package = pkgs.firefox-extensions.ublacklist;
|
||||
enable = lib.mkDefault true;
|
||||
};
|
||||
ublock-origin = {
|
||||
package = pkgs.firefox-extensions.ublock-origin;
|
||||
enable = lib.mkDefault true;
|
||||
};
|
||||
};
|
||||
})
|
||||
({
|
||||
sane.programs.firefox = {
|
||||
inherit package;
|
||||
|
||||
mime.associations = let
|
||||
inherit (cfg.browser) desktop;
|
||||
in {
|
||||
"text/html" = desktop;
|
||||
"x-scheme-handler/http" = desktop;
|
||||
"x-scheme-handler/https" = desktop;
|
||||
"x-scheme-handler/about" = desktop;
|
||||
"x-scheme-handler/unknown" = desktop;
|
||||
};
|
||||
|
||||
# env.BROWSER = "${package}/bin/${cfg.browser.libName}";
|
||||
env.BROWSER = cfg.browser.libName; # used by misc tools like xdg-email, as fallback
|
||||
|
||||
# uBlock filter list configuration.
|
||||
# specifically, enable the GDPR cookie prompt blocker.
|
||||
# data.toOverwrite.filterLists is additive (i.e. it supplements the default filters)
|
||||
# this configuration method is documented here:
|
||||
# - <https://github.com/gorhill/uBlock/issues/2986#issuecomment-364035002>
|
||||
# the specific attribute path is found via scraping ublock code here:
|
||||
# - <https://github.com/gorhill/uBlock/blob/master/src/js/storage.js>
|
||||
# - <https://github.com/gorhill/uBlock/blob/master/assets/assets.json>
|
||||
fs."${cfg.browser.dotDir}/managed-storage/uBlock0@raymondhill.net.json".symlink.text = ''
|
||||
{
|
||||
"name": "uBlock0@raymondhill.net",
|
||||
"description": "ignored",
|
||||
"type": "storage",
|
||||
"data": {
|
||||
"toOverwrite": "{\"filterLists\": [\"fanboy-cookiemonster\"]}"
|
||||
}
|
||||
}
|
||||
'';
|
||||
# TODO: this is better suited in `extraPrefs` during `wrapFirefox` call
|
||||
fs."${cfg.browser.dotDir}/${cfg.browser.libName}.overrides.cfg".symlink.text = ''
|
||||
// if we can't query the revocation status of a SSL cert because the issuer is offline,
|
||||
// treat it as unrevoked.
|
||||
// see: <https://librewolf.net/docs/faq/#im-getting-sec_error_ocsp_server_error-what-can-i-do>
|
||||
defaultPref("security.OCSP.require", false);
|
||||
|
||||
// scrollbar configuration, see: <https://artemis.sh/2023/10/12/scrollbars.html>
|
||||
// style=4 gives rectangular scrollbars
|
||||
// could also enable "always show scrollbars" in about:preferences -- not sure what the actual pref name for that is
|
||||
defaultPref("widget.non-native-theme.scrollbar.size.override", 50);
|
||||
defaultPref("widget.non-native-theme.scrollbar.style", 4);
|
||||
'';
|
||||
fs."${cfg.browser.dotDir}/default".dir = {};
|
||||
# instruct Firefox to put the profile in a predictable directory (so we can do things like persist just it).
|
||||
# XXX: the directory *must* exist, even if empty; Firefox will not create the directory itself.
|
||||
fs."${cfg.browser.dotDir}/profiles.ini".symlink.text = ''
|
||||
[Profile0]
|
||||
Name=default
|
||||
IsRelative=1
|
||||
Path=default
|
||||
Default=1
|
||||
|
||||
[General]
|
||||
StartWithLastProfile=1
|
||||
'';
|
||||
};
|
||||
})
|
||||
(mkIf config.sane.programs.firefox.enabled {
|
||||
# TODO: move the persistence into the sane.programs API (above)
|
||||
# flush the cache to disk to avoid it taking up too much tmp.
|
||||
sane.user.persist.byPath."${cfg.browser.cacheDir}".store =
|
||||
if (cfg.persistData != null) then
|
||||
cfg.persistData
|
||||
else
|
||||
"cryptClearOnBoot"
|
||||
;
|
||||
|
||||
sane.user.persist.byPath."${cfg.browser.dotDir}/default".store =
|
||||
if (cfg.persistData != null) then
|
||||
cfg.persistData
|
||||
else
|
||||
"cryptClearOnBoot"
|
||||
;
|
||||
})
|
||||
];
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
# Flare is a 3rd-party GTK4 Signal app.
|
||||
# UI is effectively a clone of Fractal.
|
||||
#
|
||||
# compatibility:
|
||||
# - desko: works fine. pairs, and exchanges contact list (but not message history) with the paired device. exchanges future messages fine.
|
||||
# - moby (cross compiled flare-signal-nixified): nope. it pairs, but can only *receive* messages and never *send* them.
|
||||
# - even `rsync`ing the data and keyrings from desko -> moby, still fails in that same manner.
|
||||
# - console shows error messages. quite possibly an endianness mismatch somewhere
|
||||
# - moby (partially-emulated flare-signal): works! pairs and can send/receive messages, same as desko.
|
||||
#
|
||||
# error signatures (to reset, run `sane-wipe-fractal`):
|
||||
# - upon sending a message, the other side receives it, but Signal desktop gets "A message from Colin could not be delivered" and the local CLI shows:
|
||||
# ```
|
||||
# ERROR libsignal_service::websocket] SignalWebSocket: Websocket error: SignalWebSocket: end of application request stream; socket closing
|
||||
# ERROR presage::manager] Error opening envelope: ProtobufDecodeError(DecodeError { description: "invalid tag value: 0", stack: [("Content", "data_message")] }), message will be skipped!
|
||||
# ERROR presage::manager] Error opening envelope: ProtobufDecodeError(DecodeError { description: "invalid tag value: 0", stack: [("Content", "data_message")] }), message will be skipped!
|
||||
# ```
|
||||
# - this occurs on moby, desko, `flare-signal` and `flare-signal-nixified`
|
||||
# - the Websocket error seems to be unrelated, occurs during normal/good operation
|
||||
# - related issues: <https://github.com/whisperfish/presage/issues/152>
|
||||
# error when sending from Flare to other Flare device:
|
||||
# - ```
|
||||
# ERROR libsignal_protocol::session_cipher] Message from <UUID>.3 failed to decrypt; sender ratchet public key <key> message counter 1
|
||||
# No current session
|
||||
# ERROR presage::manager] Error opening envelope: SignalProtocolError(InvalidKyberPreKeyId), message will be skipped!
|
||||
# ```
|
||||
# - but signal iOS will still read it.
|
||||
#
|
||||
# well, seems to have unpredictable errors particularly when being used on multiple devices.
|
||||
# desktop _seems_ more reliable than on mobile, but not confident.
|
||||
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.flare-signal = {
|
||||
package = pkgs.flare-signal-nixified;
|
||||
# package = pkgs.flare-signal;
|
||||
persist.byStore.private = [
|
||||
# everything: conf, state, files, all opaque
|
||||
".local/share/flare"
|
||||
# also persists a secret in ~/.local/share/keyrings. reset with:
|
||||
# - `secret-tool search --all --unlock 'xdg:schema' 'de.schmidhuberj.Flare'`
|
||||
# - `secret-tool clear 'xdg:schema' 'de.schmidhuberj.Flare'`
|
||||
# and it persists some dconf settings (e.g. device name). reset with:
|
||||
# - `dconf reset -f /de/schmidhuberj/Flare/`.
|
||||
];
|
||||
};
|
||||
}
|
@@ -1,68 +0,0 @@
|
||||
# to preview fonts:
|
||||
# - `font-manager` (gui)
|
||||
# - useful to determine official name; codepoint support
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
# nerdfonts takes popular open fonts and patches them to support a wider range of glyphs, notably emoji.
|
||||
# any nerdfonts font includes icons such as these:
|
||||
# - (battery charging)
|
||||
# - (brightness)
|
||||
# - (gps / crosshairs)
|
||||
# - (music note)
|
||||
# - (message bubble)
|
||||
# - (phone)
|
||||
# - (weather/sun-behind-clouds)
|
||||
# used particularly by sxmo utilities, but also a few of my own (e.g. conky)
|
||||
#
|
||||
# nerdfonts is very heavy. each font is 20-900 MiB (2 MiB per "variation")
|
||||
# lots of redundant data inside there, but no deduplication except whatever nix or the fs does implicitly.
|
||||
wantedNerdfonts = [
|
||||
# used explicitly by SXMO
|
||||
# "DejaVuSansMono" # 25 MiB
|
||||
# good terminal/coding font. grab via nerdfonts for more emoji/unicode support
|
||||
"Hack" # 26 MiB
|
||||
"Noto" # 861 MiB
|
||||
];
|
||||
nerdfontPkgs = builtins.map
|
||||
(f: pkgs.nerdfonts.override { fonts = [ f ]; })
|
||||
wantedNerdfonts;
|
||||
in
|
||||
{
|
||||
fonts = lib.mkIf config.sane.programs.fontconfig.enabled {
|
||||
fontconfig.enable = true;
|
||||
fontconfig.defaultFonts = {
|
||||
emoji = [
|
||||
"Noto Color Emoji"
|
||||
"Font Awesome 6 Free"
|
||||
"Font Awesome 6 Brands"
|
||||
];
|
||||
monospace = [
|
||||
"Hack Nerd Font Propo"
|
||||
# "DejaVuSansM Nerd Font Propo"
|
||||
"NotoMono Nerd Font Propo"
|
||||
];
|
||||
serif = [
|
||||
"NotoSerif Nerd Font"
|
||||
"DejaVu Serif"
|
||||
];
|
||||
sansSerif = [
|
||||
"NotoSans Nerd Font"
|
||||
"DejaVu Sans"
|
||||
];
|
||||
};
|
||||
#vvv enables dejavu_fonts, freefont_ttf, gyre-fonts, liberation_ttf, unifont, noto-fonts-emoji
|
||||
enableDefaultPackages = false;
|
||||
packages = with pkgs; [
|
||||
# TODO: reduce this font set.
|
||||
# - probably need only one of dejavu/freefont/liberation
|
||||
dejavu_fonts # 10 MiB; DejaVu {Sans,Serif,Sans Mono,Math TeX Gyre}; also available as a NerdFonts (Sans Mono only)
|
||||
font-awesome # 2 MiB; Font Awesome 6 {Free,Brands}
|
||||
freefont_ttf # 11 MiB; Free{Mono,Sans,Serif}
|
||||
gyre-fonts # 4 MiB; Tex Gyre *; ttf substitutes for standard PostScript fonts
|
||||
# hack-font # 1 MiB; Hack; also available as a NerdFonts
|
||||
liberation_ttf # 4 MiB; Liberation {Mono,Sans,Serif}; also available as a NerdFonts
|
||||
noto-fonts-color-emoji # 10 Mib; Noto Color Emoji
|
||||
unifont # 16 MiB; Unifont; provides LOTS of unicode coverage
|
||||
] ++ nerdfontPkgs;
|
||||
};
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
# Fractal: GTK4 instant messenger client for the Matrix protocol
|
||||
#
|
||||
# very susceptible to state corruption during hard power-cycles.
|
||||
# if it stalls while launching, especially with a brief message at bottom
|
||||
# "unable to open store"
|
||||
# then:
|
||||
# - remove ~/.local/share/stable/*
|
||||
# - this might give I/O error, in which case remove the corresponding path under
|
||||
# /nix/persist/home/colin/private (which can be found by correlating timestamps/sizes with that in ~/private/.local/share/stable).
|
||||
# - reboot (maybe necessary).
|
||||
# - now you can send messages, and read messages in unencrypted rooms, but not read messages from encrypted rooms.
|
||||
# to fix encrypted message receipt:
|
||||
# - start from above (fractal closed, no ~/.local/share/stable/*)
|
||||
# - in ~/.local/share/keyrings/Default_keyring.keyring:
|
||||
# - find the entry that says "display-name=Fractal: Matrix credentials for <mxid>"
|
||||
# - remove that entry and all associated entries (i.e. ones with same number but different :attributeN)
|
||||
# - REBOOT. otherwise keyring stuff seems to stay cached in RAM
|
||||
# - login to Fractal. give an hour to sync.
|
||||
# - it'll kick you back to a page asking you to cross-sign. open FluffyChat and do the emoji compare. success!
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.fractal;
|
||||
in
|
||||
{
|
||||
sane.programs.fractal = {
|
||||
package = pkgs.fractal-nixified;
|
||||
# package = pkgs.fractal-latest;
|
||||
# package = pkgs.fractal-next;
|
||||
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
persist.byStore.private = [
|
||||
# XXX by default fractal stores its state in ~/.local/share/<build-profile>/<UUID>.
|
||||
".local/share/hack" # for debug-like builds
|
||||
".local/share/stable" # for normal releases
|
||||
];
|
||||
|
||||
suggestedPrograms = [ "gnome-keyring" ];
|
||||
|
||||
services.fractal = {
|
||||
description = "auto-start and maintain fractal Matrix connection";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/fractal";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
# environment.G_MESSAGES_DEBUG = "all";
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
services.fwupd = lib.mkIf config.sane.programs.fwupd.enabled {
|
||||
# enables the dbus service, which i think the frontend speaks to.
|
||||
enable = true;
|
||||
};
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
# N.B.: requires first-run setup on moby:
|
||||
# - UI will render transparent
|
||||
# - click the hamburger (top-right: immediately left from close button)
|
||||
# > Preferences
|
||||
# > Background-blur mode: change from "Always" to "Never"
|
||||
#
|
||||
# the background blur is probably some dconf setting somewhere.
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.g4music = {
|
||||
persist.byStore.plaintext = [
|
||||
# index?
|
||||
".cache/com.github.neithern.g4music"
|
||||
];
|
||||
};
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.gajim = {
|
||||
persist.byStore.private = [
|
||||
# avatars, thumbnails...
|
||||
".cache/gajim"
|
||||
# sqlite database labeled "settings". definitely includes UI theming
|
||||
".config/gajim"
|
||||
# omemo keys, downloads, logs
|
||||
".local/share/gajim"
|
||||
];
|
||||
};
|
||||
}
|
@@ -1,55 +0,0 @@
|
||||
# geary is a gtk3 email client.
|
||||
# outstanding issues:
|
||||
# - it uses webkitgtk_4_1, which is expensive to build.
|
||||
# could be upgraded to webkitgtk latest if upgraded to gtk4
|
||||
# <https://gitlab.gnome.org/GNOME/geary/-/issues/1212>
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs."gnome.geary" = {
|
||||
persist.byStore.private = [
|
||||
# attachments, and email -- contained in a sqlite db
|
||||
".local/share/geary"
|
||||
# also `.cache/geary/web-resources`, which tends to stay << 1 MiB
|
||||
];
|
||||
fs.".config/geary/account_01/geary.ini".symlink.text = ''
|
||||
[Metadata]
|
||||
version=1
|
||||
status=enabled
|
||||
|
||||
[Account]
|
||||
ordinal=2
|
||||
label=
|
||||
# 14 = "fetch last 14d of mail every time i connect"
|
||||
# -1 = "fetch *all* mail"
|
||||
prefetch_days=-1
|
||||
save_drafts=true
|
||||
save_sent=true
|
||||
use_signature=false
|
||||
signature=
|
||||
sender_mailboxes=colin@uninsane.org;
|
||||
service_provider=other
|
||||
|
||||
[Folders]
|
||||
archive_folder=Archive;
|
||||
drafts_folder=
|
||||
sent_folder=
|
||||
junk_folder=
|
||||
trash_folder=
|
||||
|
||||
[Incoming]
|
||||
login=colin
|
||||
remember_password=true
|
||||
host=imap.uninsane.org
|
||||
port=993
|
||||
transport_security=transport
|
||||
credentials=custom
|
||||
|
||||
[Outgoing]
|
||||
remember_password=true
|
||||
host=mx.uninsane.org
|
||||
port=465
|
||||
transport_security=transport
|
||||
credentials=use-incoming
|
||||
'';
|
||||
};
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
# TODO: use formats.gitIni or lib.generators.toGitINI
|
||||
# - see: <repo:nixos/nixpkgs:pkgs/pkgs-lib/formats.nix>
|
||||
mkCfg = lib.generators.toINI { };
|
||||
in
|
||||
{
|
||||
sane.programs.git.fs.".config/git/config".symlink.text = mkCfg {
|
||||
# top-level options documented:
|
||||
# - <https://git-scm.com/docs/git-config#_variables>
|
||||
|
||||
user.name = "Colin";
|
||||
user.email = "colin@uninsane.org";
|
||||
|
||||
alias.br = "branch";
|
||||
alias.co = "checkout";
|
||||
alias.cp = "cherry-pick";
|
||||
alias.d = "difftool";
|
||||
alias.dif = "diff"; # common typo
|
||||
alias.difsum = "diff --compact-summary"; #< show only the list of files which changed, not contents
|
||||
alias.rb = "rebase";
|
||||
alias.st = "status";
|
||||
alias.stat = "status";
|
||||
|
||||
# difftastic docs:
|
||||
# - <https://difftastic.wilfred.me.uk/git.html>
|
||||
diff.tool = "difftastic";
|
||||
difftool.prompt = false;
|
||||
"difftool \"difftastic\"".cmd = ''${pkgs.difftastic}/bin/difft "$LOCAL" "$REMOTE"'';
|
||||
# now run `git difftool` to use difftastic git
|
||||
|
||||
# render dates as YYYY-MM-DD HH:MM:SS +TZ
|
||||
log.date = "iso";
|
||||
|
||||
sendemail.annotate = "yes";
|
||||
sendemail.confirm = "always";
|
||||
|
||||
stash.showPatch = true;
|
||||
};
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
# gnome feeds RSS viewer
|
||||
{ config, lib, sane-lib, ... }:
|
||||
|
||||
let
|
||||
feeds = sane-lib.feeds;
|
||||
all-feeds = config.sane.feeds;
|
||||
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
|
||||
in {
|
||||
sane.programs.gnome-feeds.fs.".config/org.gabmus.gfeeds.json".symlink.text = builtins.toJSON {
|
||||
# feed format is a map from URL to a dict,
|
||||
# with dict["tags"] a list of string tags.
|
||||
feeds = sane-lib.mapToAttrs (feed: {
|
||||
name = feed.url;
|
||||
value.tags = [ feed.cat feed.freq ];
|
||||
}) wanted-feeds;
|
||||
dark_reader = false;
|
||||
new_first = true;
|
||||
# windowsize = {
|
||||
# width = 350;
|
||||
# height = 650;
|
||||
# };
|
||||
max_article_age_days = 90;
|
||||
enable_js = false;
|
||||
max_refresh_threads = 3;
|
||||
# saved_items = {};
|
||||
# read_items = [];
|
||||
show_read_items = true;
|
||||
full_article_title = true;
|
||||
# views: "webview", "reader", "rsscont"
|
||||
default_view = "rsscont";
|
||||
open_links_externally = true;
|
||||
full_feed_name = false;
|
||||
refresh_on_startup = true;
|
||||
tags = lib.unique (
|
||||
(builtins.catAttrs "cat" wanted-feeds) ++ (builtins.catAttrs "freq" wanted-feeds)
|
||||
);
|
||||
open_youtube_externally = false;
|
||||
media_player = "vlc"; # default: mpv
|
||||
};
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
sane.programs.gnome-keyring = {
|
||||
package = pkgs.gnome.gnome-keyring;
|
||||
};
|
||||
# adds gnome-keyring as a xdg-data-portal (xdg.portal)
|
||||
services.gnome.gnome-keyring = lib.mkIf config.sane.programs.gnome-keyring.enabled {
|
||||
enable = true;
|
||||
};
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
# preferences are saved via dconf; see `dconf dump /`
|
||||
# cache dir is just for weather data (or maybe a http cache)
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.gnome-weather = {
|
||||
persist.byStore.plaintext = [
|
||||
".cache/libgweather"
|
||||
];
|
||||
};
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
# help:
|
||||
# - #gpodder on irc.libera.chat
|
||||
{ config, pkgs, sane-lib, ... }:
|
||||
|
||||
let
|
||||
feeds = sane-lib.feeds;
|
||||
all-feeds = config.sane.feeds;
|
||||
wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds;
|
||||
in {
|
||||
sane.programs.gpodder = {
|
||||
package = pkgs.gpodder-adaptive-configured.overrideAttrs (base: {
|
||||
# environment variables:
|
||||
# - GPODDER_HOME (defaults to "~/gPodder")
|
||||
# - GPODDER_DOWNLOAD_DIR (defaults to "$GPODDER_HOME/Downloads")
|
||||
# - GPODDER_WRITE_LOGS ("yes" or "no")
|
||||
# - GPODDER_EXTENSIONS
|
||||
# - GPODDER_DISABLE_EXTENSIONS ("yes" or "no")
|
||||
extraMakeWrapperArgs = (base.extraMakeWrapperArgs or []) ++ [
|
||||
"--set" "GPODDER_HOME" "~/.local/share/gPodder"
|
||||
];
|
||||
});
|
||||
# package = pkgs.gpodder-configured;
|
||||
fs.".config/gpodderFeeds.opml".symlink.text = feeds.feedsToOpml wanted-feeds;
|
||||
|
||||
# XXX: we preserve the whole thing because if we only preserve gPodder/Downloads
|
||||
# then startup is SLOW during feed import, and we might end up with zombie eps in the dl dir.
|
||||
persist.byStore.plaintext = [ ".local/share/gPodder" ];
|
||||
};
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.gthumb = {
|
||||
# compile without webservices to avoid the expensive webkitgtk dependency
|
||||
package = pkgs.gthumb.override { withWebservices = false; };
|
||||
mime.associations = {
|
||||
"image/gif" = "org.gnome.gThumb.desktop";
|
||||
"image/heif" = "org.gnome.gThumb.desktop"; # apple codec
|
||||
"image/png" = "org.gnome.gThumb.desktop";
|
||||
"image/jpeg" = "org.gnome.gThumb.desktop";
|
||||
"image/svg+xml" = "org.gnome.gThumb.desktop";
|
||||
"image/webp" = "org.gnome.gThumb.desktop";
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
# Helix text editor
|
||||
# debug log: `~/.cache/helix/helix.log`
|
||||
# binary name is `hx`
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.helix = {
|
||||
# grammars need to be persisted when developing them
|
||||
# - `hx --grammar fetch` and `hx --grammar build`
|
||||
# but otherwise, they ship as part of HELIX_RUNTIME, in the nix store
|
||||
# persist.byStore.plaintext = [ ".config/helix/runtime/grammars" ];
|
||||
fs.".config/helix/config.toml".symlink.text = ''
|
||||
# docs: <https://docs.helix-editor.com/configuration.html>
|
||||
[editor.soft-wrap]
|
||||
enable = true
|
||||
|
||||
[editor.whitespace.render]
|
||||
space = "all"
|
||||
tab = "all"
|
||||
newline = "none"
|
||||
'';
|
||||
};
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.imagemagick = {
|
||||
package = pkgs.imagemagick.override {
|
||||
ghostscriptSupport = true;
|
||||
};
|
||||
suggestedPrograms = [ "ghostscript" ];
|
||||
};
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
sane.programs.jellyfin-media-player = {
|
||||
# package = pkgs.jellyfin-media-player;
|
||||
# qt6 version is slightly buggy, but also most qtwebengine apps (e.g. zeal) are on qt5
|
||||
# so using qt6 would force yet *another* qtwebengine compile.
|
||||
# package = pkgs.jellyfin-media-player-qt6;
|
||||
|
||||
# jellyfin stores things in a bunch of directories: this one persists auth info.
|
||||
# it *might* be possible to populate this externally (it's Qt stuff), but likely to
|
||||
# be fragile and take an hour+ to figure out.
|
||||
persist.byStore.plaintext = [ ".local/share/Jellyfin Media Player" ];
|
||||
};
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.komikku = {
|
||||
secrets.".local/share/komikku/keyrings/plaintext.keyring" = ../../../secrets/common/komikku_accounts.json.bin;
|
||||
# downloads end up here, and without the toplevel database komikku doesn't know they exist.
|
||||
persist.byStore.plaintext = [ ".local/share/komikku" ];
|
||||
};
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user