Compare commits

..

2 Commits

Author SHA1 Message Date
282ef0f8d3 cross: fix enableParallelBuilding to work 2023-08-05 09:04:02 +00:00
45578b2f00 build bonsai/hare/qbe without relying on binfmt emulation 2023-08-05 08:05:50 +00:00
383 changed files with 7306 additions and 92532 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,2 @@
/keep
result result
result-*
/secrets/local.nix /secrets/local.nix
/working

View File

@@ -4,35 +4,17 @@ this is the top-level repo from which i configure/deploy all my NixOS machines:
- desktop - desktop
- laptop - laptop
- server - server
- mobile phone (Pinephone) - mobile phone
everything outside of <./hosts/> and <./secrets/> is intended for export, to be importable for use by 3rd parties. i enjoy a monorepo approach. this repo references [nixpkgs][nixpkgs], a couple 3rd party
the only hard dependency for my exported pkgs/modules should be [nixpkgs][nixpkgs]. nix modules like [sops][sops], the sources for [uninsane.org][uninsane-org], and that's
building <./hosts/> will require [sops][sops]. about it. custom derivations and modules (some of which i try to upstream) live
directly here; even the sources for those packages is often kept here too.
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 [nixpkgs]: https://github.com/NixOS/nixpkgs
[sops]: https://github.com/Mic92/sops-nix [sops]: https://github.com/Mic92/sops-nix
[uninsane-org]: https://uninsane.org [uninsane-org]: https://uninsane.org
## Using This Repo In Your Own Config
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 ## Layout
- `doc/` - `doc/`
- instructions for tasks i find myself doing semi-occasionally in this repo. - instructions for tasks i find myself doing semi-occasionally in this repo.
@@ -108,6 +90,12 @@ them being factored out of my config, message me and we could work to make that
[home-manager]: https://github.com/nix-community/home-manager [home-manager]: https://github.com/nix-community/home-manager
## 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)
## Mirrors ## Mirrors
this repo exists in a few known locations: this repo exists in a few known locations:

88
TODO.md
View File

@@ -1,7 +1,7 @@
## BUGS ## BUGS
- why i need to manually restart `wireguard-wg-ovpns` on servo periodically - why i need to manually restart `wireguard-wg-ovpns` on servo periodically
- else DNS fails - else DNS fails
- ringer (i.e. dino incoming call) doesn't prevent moby from sleeping - fix epiphany URL bar input on moby
## REFACTORING: ## REFACTORING:
@@ -12,7 +12,7 @@
### roles ### roles
- allow any host to take the role of `uninsane.org` - allow any host to take the role of `uninsane.org`
- will make it easier to test new services? - will make it easier to test new services?
### upstreaming ### upstreaming
- split out a sxmo module usable by NUR consumers - split out a sxmo module usable by NUR consumers
@@ -23,75 +23,41 @@
- REVIEW/integrate jellyfin dataDir config: <https://github.com/NixOS/nixpkgs/pull/233617> - REVIEW/integrate jellyfin dataDir config: <https://github.com/NixOS/nixpkgs/pull/233617>
- remove `libsForQt5.callPackage` broadly: <https://github.com/NixOS/nixpkgs/issues/180841> - 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: ## IMPROVEMENTS:
### security/resilience ### 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! - validate duplicity backups!
- encrypt more ~ dirs (~/archives, ~/records, ..?) - encrypt more ~ dirs (~/archives, ~/records, ..?)
- best to do this after i know for sure i have good backups - 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? - 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*. - 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. - 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) - **systemd-run** takes a command and runs it in a temporary scope (cgroup)
- presumably uses the same options as systemd services - presumably uses the same options as systemd services
- see e.g. <https://github.com/NixOS/nixpkgs/issues/113903#issuecomment-857296349> - see e.g. <https://github.com/NixOS/nixpkgs/issues/113903#issuecomment-857296349>
- flatpak does this, somehow - flatpak does this, somehow
- apparmor? SElinux? (desktop) "portals"? - apparmor? SElinux? (desktop) "portals"?
- see Spectrum OS; Alyssa Ross; etc - see Spectrum OS; Alyssa Ross; etc
- bubblewrap-based sandboxing: <https://github.com/nixpak/nixpak> - bubblewrap-based sandboxing: <https://github.com/nixpak/nixpak>
- canaries for important services - canaries for important services
- e.g. daily email checks; daily backup checks - e.g. daily email checks; daily backup checks
- integrate `nix check` into Gitea actions? - integrate `nix check` into Gitea actions?
### user experience ### 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) - neovim: set up language server (lsp; rnix-lsp; nvim-lspconfig)
- Helix: make copy-to-system clipboard be the default - Helix: make copy-to-system clipboard be the default
- firefox/librewolf: persist history - firefox/librewolf: persist history
- just not cookies or tabs - just not cookies or tabs
- moby: improve gPodder launch time
- moby: theme GTK apps (i.e. non-adwaita styles)
- especially, make the menubar collapsible
- try Gradience tool specifically for theming adwaita? <https://linuxphoneapps.org/apps/com.github.gradienceteam.gradience/>
- package Nix/NixOS docs for Zeal - package Nix/NixOS docs for Zeal
- install [doc-browser](https://github.com/qwfy/doc-browser) - install [doc-browser](https://github.com/qwfy/doc-browser)
- this supports both dash (zeal) *and* the datasets from <https://devdocs.io> (which includes nix!) - this supports both dash (zeal) *and* the datasets from <https://devdocs.io> (which includes nix!)
- install [devhelp](https://wiki.gnome.org/Apps/Devhelp) (gnome) - install [devhelp](https://wiki.gnome.org/Apps/Devhelp) (gnome)
- have xdg-open parse `<repo:...> URIs (or adjust them so that it _can_ parse) - have xdg-open parse `<repo:...> URIs (or adjust them so that it _can_ parse)
- sane-bt-search: show details like 5.1 vs stereo, h264 vs h265 - sane-bt-search: show details like 5.1 vs stereo, h264 vs h265
- maybe just color these "keywords" in all search results?
- uninsane.org: make URLs relative to allow local use (and as offline homepage) - uninsane.org: make URLs relative to allow local use (and as offline homepage)
- email: fix so that local mail doesn't go to junk - email: fix so that local mail doesn't go to junk
- git sendmail flow adds the DKIM signatures, but gets delivered locally w/o having the sig checked, so goes into Junk - git sendmail flow adds the DKIM signatures, but gets delivered locally w/o having the sig checked, so goes into Junk
@@ -99,15 +65,17 @@
### perf ### perf
- add `pkgs.impure-cached.<foo>` package set to build things with ccache enabled - 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 - 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! - would be super handy for package prototyping!
- why does nixos-rebuild switch take 5 minutes when net is flakey?
- trying to auto-mount servo?
- something to do with systemd services restarting/stalling
- maybe wireguard & its refresh operation, specifically?
- get moby to build without binfmt emulation (i.e. make all emulation explicit) - 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 - then i can distribute builds across servo + desko, and also allow servo to pull packages from desko w/o worrying about purity
## NEW FEATURES: ## NEW FEATURES:
- migrate MAME cabinet to nix - migrate MAME cabinet to nix
- boot it from PXE from servo? - boot it from PXE from servo?
- deploy to new server, and use it as a remote builder
- enable IPv6 - enable IPv6
- package lemonade lemmy app: <https://linuxphoneapps.org/apps/ml.mdwalters.lemonade/>

View File

@@ -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)
]

69
flake.lock generated
View File

@@ -21,27 +21,59 @@
"mobile-nixos": { "mobile-nixos": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1694749521, "lastModified": 1690059310,
"narHash": "sha256-MiVokKlpcJmfoGuWAMeW1En7gZ5hk0rCQArYm6P9XCc=", "narHash": "sha256-4zcoDp8wwZVfGSzXltC5x+eH4kDWC/eJpyQNgr7shAA=",
"owner": "nixos", "owner": "nixos",
"repo": "mobile-nixos", "repo": "mobile-nixos",
"rev": "d25d3b87e7f300d8066e31d792337d9cd7ecd23b", "rev": "56fc9f9619f305f0865354975a98d22410eed127",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "nixos",
"ref": "d25d3b87e7f300d8066e31d792337d9cd7ecd23b",
"repo": "mobile-nixos", "repo": "mobile-nixos",
"type": "github" "type": "github"
} }
}, },
"nixpkgs-stable": { "nix-serve": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": { "locked": {
"lastModified": 1698544399, "lastModified": 1687251388,
"narHash": "sha256-vhRmPyEyoPkrXF2iykBsWHA05MIaOSmMRLMF7Hul6+s=", "narHash": "sha256-E9cVlgeCvzPbA/G3mCDCzz8TdRwXyGYzIjmwcvIfghg=",
"owner": "edolstra",
"repo": "nix-serve",
"rev": "d6df5bd8584f37e22cff627db2fc4058a4aab5ee",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "nix-serve",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1606086654,
"narHash": "sha256-VFl+3eGIMqNp7cyOMJ6TjM/+UcsLKtodKoYexrlTJMI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "d87c5d8c41c9b3b39592563242f3a448b5cc4bc9", "rev": "19db3e5ea2777daa874563b5986288151f502e27",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-20.09",
"type": "indirect"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1690066826,
"narHash": "sha256-6L2qb+Zc0BFkh72OS9uuX637gniOjzU6qCDBpjB2LGY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ce45b591975d070044ca24e3003c830d26fea1c8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -53,11 +85,11 @@
}, },
"nixpkgs-unpatched": { "nixpkgs-unpatched": {
"locked": { "locked": {
"lastModified": 1698611440, "lastModified": 1691006197,
"narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=", "narHash": "sha256-DbtxVWPt+ZP5W0Usg7jAyTomIM//c3Jtfa59Ht7AV8s=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735", "rev": "66aedfd010204949cb225cf749be08cb13ce1813",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -70,6 +102,7 @@
"root": { "root": {
"inputs": { "inputs": {
"mobile-nixos": "mobile-nixos", "mobile-nixos": "mobile-nixos",
"nix-serve": "nix-serve",
"nixpkgs-unpatched": "nixpkgs-unpatched", "nixpkgs-unpatched": "nixpkgs-unpatched",
"sops-nix": "sops-nix", "sops-nix": "sops-nix",
"uninsane-dot-org": "uninsane-dot-org" "uninsane-dot-org": "uninsane-dot-org"
@@ -83,11 +116,11 @@
"nixpkgs-stable": "nixpkgs-stable" "nixpkgs-stable": "nixpkgs-stable"
}, },
"locked": { "locked": {
"lastModified": 1698548647, "lastModified": 1690199016,
"narHash": "sha256-7c03OjBGqnwDW0FBaBc+NjfEBxMkza+dxZGJPyIzfFE=", "narHash": "sha256-yTLL72q6aqGmzHq+C3rDp3rIjno7EJZkFLof6Ika7cE=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "632c3161a6cc24142c8e3f5529f5d81042571165", "rev": "c36df4fe4bf4bb87759b1891cab21e7a05219500",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -119,11 +152,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1698634059, "lastModified": 1691106178,
"narHash": "sha256-+Oyv6vDyCtBzab/5cTG0nUrHD9gj7KgGfD4D1Rn4fCk=", "narHash": "sha256-3mZ9gTvMpbZA9ea9ovoQpn2wKuQY0QZ7MDdEjArYdAQ=",
"ref": "refs/heads/master", "ref": "refs/heads/master",
"rev": "2419750ca98fc04af42c91e50c49a29c68d465d2", "rev": "f4d91aa201b6e49af690f250d4786bd1d8b4dcfd",
"revCount": 210, "revCount": 205,
"type": "git", "type": "git",
"url": "https://git.uninsane.org/colin/uninsane" "url": "https://git.uninsane.org/colin/uninsane"
}, },

210
flake.nix
View File

@@ -4,8 +4,6 @@
# - this is marginally the case with schemes like `github:nixos/nixpkgs`. # - this is marginally the case with schemes like `github:nixos/nixpkgs`.
# - given the *existing* `git+https://` scheme, i propose expressing github URLs similarly: # - given the *existing* `git+https://` scheme, i propose expressing github URLs similarly:
# - `github+https://github.com/nixos/nixpkgs/tree/nixos-22.11` # - `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. # - need some way to apply local patches to inputs.
# #
# #
@@ -48,26 +46,24 @@
mobile-nixos = { mobile-nixos = {
# <https://github.com/nixos/mobile-nixos> # <https://github.com/nixos/mobile-nixos>
# only used for building disk images, not relevant after deployment url = "github:nixos/mobile-nixos";
# 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";
flake = false; flake = false;
}; };
sops-nix = { sops-nix = {
# <https://github.com/Mic92/sops-nix> # <https://github.com/Mic92/sops-nix>
# used to distribute secrets to my hosts
url = "github:Mic92/sops-nix"; url = "github:Mic92/sops-nix";
# inputs.nixpkgs.follows = "nixpkgs"; # inputs.nixpkgs.follows = "nixpkgs";
inputs.nixpkgs.follows = "nixpkgs-unpatched"; inputs.nixpkgs.follows = "nixpkgs-unpatched";
}; };
uninsane-dot-org = { 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"; url = "git+https://git.uninsane.org/colin/uninsane";
# inputs.nixpkgs.follows = "nixpkgs"; # inputs.nixpkgs.follows = "nixpkgs";
inputs.nixpkgs.follows = "nixpkgs-unpatched"; inputs.nixpkgs.follows = "nixpkgs-unpatched";
}; };
nix-serve = {
# <https://github.com/edolstra/nix-serve>
url = "github:edolstra/nix-serve";
};
}; };
outputs = { outputs = {
@@ -76,37 +72,21 @@
mobile-nixos, mobile-nixos,
sops-nix, sops-nix,
uninsane-dot-org, uninsane-dot-org,
nix-serve,
... ...
}@inputs: }@inputs:
let let
inherit (builtins) attrNames elem listToAttrs map mapAttrs; 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: mapAttrs' = f: set:
listToAttrs (map (attr: f attr set.${attr}) (attrNames set)); listToAttrs (map (attr: f attr set.${attr}) (attrNames set));
optionalAttrs = cond: attrs: if cond then attrs else {};
# mapAttrs but without the `name` argument # mapAttrs but without the `name` argument
mapAttrValues = f: mapAttrs (_: f); mapAttrValues = f: mapAttrs (_: f);
# rather than apply our nixpkgs patches as a flake input, do that here instead. # 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 # 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. # repo as the main flake causes the main flake to have an unstable hash.
nixpkgs = (import ./nixpatches/flake.nix).outputs { nixpkgs = (import ./nixpatches/flake.nix).outputs {
self = nixpkgs; self = nixpkgs;
nixpkgs = nixpkgs-unpatched; 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}"; nixpkgsCompiledBy = system: nixpkgs.legacyPackages."${system}";
@@ -114,17 +94,7 @@
evalHost = { name, local, target }: nixpkgs.lib.nixosSystem { evalHost = { name, local, target }: nixpkgs.lib.nixosSystem {
system = target; system = target;
modules = [ modules = [
{ (import ./hosts/instantiate.nix { localSystem = local; hostName = name; })
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; })
self.nixosModules.default self.nixosModules.default
self.nixosModules.passthru self.nixosModules.passthru
{ {
@@ -133,6 +103,12 @@
self.overlays.sane-all self.overlays.sane-all
]; ];
} }
({ lib, ... }: {
# TODO: does the earlier `system` arg to nixosSystem make its way here?
nixpkgs.hostPlatform.system = target;
# nixpkgs.buildPlatform = local; # set by instantiate.nix instead
# nixpkgs.config.replaceStdenv = { pkgs }: pkgs.ccacheStdenv;
})
]; ];
}; };
in { in {
@@ -187,8 +163,8 @@
imgs = mapAttrValues (host: host.config.system.build.img) self.nixosConfigurations; imgs = mapAttrValues (host: host.config.system.build.img) self.nixosConfigurations;
# unofficial output # unofficial output
hostPkgs = mapAttrValues (host: host.config.system.build.pkgs) self.nixosConfigurations; host-pkgs = mapAttrValues (host: host.config.system.build.pkgs) self.nixosConfigurations;
hostPrograms = mapAttrValues (host: mapAttrValues (p: p.package) host.config.sane.programs) self.nixosConfigurations; host-programs = mapAttrValues (host: mapAttrValues (p: p.package) host.config.sane.programs) self.nixosConfigurations;
overlays = { overlays = {
# N.B.: `nix flake check` requires every overlay to take `final: prev:` at defn site, # N.B.: `nix flake check` requires every overlay to take `final: prev:` at defn site,
@@ -204,9 +180,17 @@
let let
mobile = (import "${mobile-nixos}/overlay/overlay.nix"); mobile = (import "${mobile-nixos}/overlay/overlay.nix");
uninsane = uninsane-dot-org.overlay; uninsane = uninsane-dot-org.overlay;
# nix-serve' = nix-serve.overlay;
nix-serve' = next: prev: {
# XXX(2023/03/02): upstream isn't compatible with modern `nix`. probably the perl bindings.
# - we use the package built against `nixpkgs` specified in its flake rather than use its overlay,
# to get around this.
inherit (nix-serve.packages."${next.system}") nix-serve;
};
in in
(mobile final prev) (mobile final prev)
// (uninsane final prev) // (uninsane final prev)
// (nix-serve' final prev)
; ;
}; };
@@ -256,7 +240,6 @@
apps."x86_64-linux" = apps."x86_64-linux" =
let let
pkgs = self.legacyPackages."x86_64-linux"; pkgs = self.legacyPackages."x86_64-linux";
sanePkgs = import ./pkgs { inherit pkgs; };
deployScript = host: addr: action: pkgs.writeShellScript "deploy-${host}" '' deployScript = host: addr: action: pkgs.writeShellScript "deploy-${host}" ''
nix build '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${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}) sudo nix sign-paths -r -k /run/secrets/nix_serve_privkey $(readlink ./result-${host})
@@ -266,59 +249,6 @@
# let the user handle that edge case by re-running this whole command # 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 $@ 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 {
type = "app";
program = builtins.toString (pkgs.writeShellScript
(builtins.concatStringsSep "-" (["update"] ++ basePath))
(builtins.concatStringsSep
"\n"
(pkgs.lib.mapAttrsToList invokeUpdater updaters)
)
);
} // updaters;
in { in {
help = { help = {
type = "app"; type = "app";
@@ -327,31 +257,24 @@
commands: commands:
- `nix run '.#help'` - `nix run '.#help'`
- show this message - show this message
- `nix run '.#update.pkgs'` - `nix run '.#update-feeds'`
- updates every package
- `nix run '.#update.feeds'`
- updates metadata for all feeds - updates metadata for all feeds
- `nix run '.#init-feed' <url>` - `nix run '.#init-feed' <url>`
- `nix run '.#deploy-{lappy,moby,moby-test,servo}' [nixos-rebuild args ...]` - `nix run '.#deploy-{lappy,moby,moby-test,servo}' [nixos-rebuild args ...]`
- `nix run '.#check'` - `nix run '.#check-nur'`
- make sure all systems build; NUR evaluates
specific build targets of interest:
- `nix build '.#imgs.rescue'`
''; '';
in builtins.toString (pkgs.writeShellScript "nixos-config-help" '' in builtins.toString (pkgs.writeShellScript "nixos-config-help" ''
cat ${helpMsg} cat ${helpMsg}
echo ""
echo "complete flake structure:"
nix flake show --option allow-import-from-derivation true
''); '');
}; };
update.pkgs = mkUpdaters { ignore = [ ["feeds"] ]; } []; update-feeds = {
update.feeds = mkUpdaters {} [ "feeds" ]; type = "app";
program = "${pkgs.feeds.updateScript}";
};
init-feed = { init-feed = {
type = "app"; type = "app";
program = "${pkgs.feeds.init-feed}"; program = "${pkgs.feeds.initFeedScript}";
}; };
deploy-lappy = { deploy-lappy = {
@@ -371,43 +294,7 @@
program = ''${deployScript "servo" "servo" "switch"}''; program = ''${deployScript "servo" "servo" "switch"}'';
}; };
sync-moby = { check-nur = {
# 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'` # `nix run '.#check-nur'`
# validates that my repo can be included in the Nix User Repository # validates that my repo can be included in the Nix User Repository
type = "app"; type = "app";
@@ -419,40 +306,7 @@
--option allow-import-from-derivation true \ --option allow-import-from-derivation true \
--drv-path --show-trace \ --drv-path --show-trace \
-I nixpkgs=$(nix-instantiate --find-file nixpkgs) \ -I nixpkgs=$(nix-instantiate --find-file nixpkgs) \
-I ../../ \ -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
''); '');
}; };
}; };

View File

@@ -3,15 +3,13 @@
{ pkgs, ... }: { pkgs, ... }:
{ {
sane.gui.sxmo = { sane.gui.sxmo = {
greeter = "greetd-sway-gtkgreet"; greeter = "sway";
noidle = true; #< power button requires 1s hold, which makes it impractical to be dealing with.
settings = { settings = {
# XXX: make sure the user is part of the `input` group! # 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"; 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` # these identifiers are from `swaymsg -t get_inputs`
SXMO_VOLUME_BUTTON = "1:1:AT_Translated_Set_2_keyboard"; SXMO_VOLUME_BUTTON = "1:1:AT_Translated_Set_2_keyboard";
# SXMO_VOLUME_BUTTON = "none"; # 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 = "0:1:Power_Button";
# SXMO_POWER_BUTTON = "none"; # SXMO_POWER_BUTTON = "none";
SXMO_DISABLE_LEDS = "1"; SXMO_DISABLE_LEDS = "1";
@@ -29,11 +27,8 @@
# - SXMO_SWAY_SCALE # - SXMO_SWAY_SCALE
# see <repo:mil/sxmo-utils:scripts/deviceprofiles> # see <repo:mil/sxmo-utils:scripts/deviceprofiles>
# SXMO_DEVICE_NAME = "pine64,pinephone-1.2"; # 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: { package = pkgs.sxmo-utils.overrideAttrs (base: {
postPatch = (base.postPatch or "") + '' postPatch = (base.postPatch or "") + ''
# after volume-button navigation mode, restore full keyboard functionality # after volume-button navigation mode, restore full keyboard functionality
cp ${./xkb_mobile_normal_buttons} ./configs/xkb/xkb_mobile_normal_buttons cp ${./xkb_mobile_normal_buttons} ./configs/xkb/xkb_mobile_normal_buttons

View File

@@ -31,58 +31,27 @@
sops.secrets.colin-passwd.neededForUsers = true; sops.secrets.colin-passwd.neededForUsers = true;
sane.user.persist.plaintext = [
# TODO: make this just generally conditional upon pulse being enabled?
".config/pulse" # persist pulseaudio volume
];
sane.gui.sxmo.enable = true; sane.gui.sxmo.enable = true;
sane.programs.guiApps.suggestedPrograms = [ "handheldGuiApps" ]; sane.programs.guiApps.suggestedPrograms = [ "handheldGuiApps" ];
# sane.programs.consoleUtils.enableFor.user.colin = false; # sane.programs.consoleUtils.enableFor.user.colin = false;
# sane.programs.guiApps.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.sequoia.enableFor.user.colin = false;
sane.programs.tuiApps.enableFor.user.colin = false; # visidata, others, don't compile well sane.programs.tuiApps.enableFor.user.colin = false; # visidata, others, don't compile well
# disabled for faster deploys # disabled for faster deploys
sane.programs.soundconverter.enableFor.user.colin = false; sane.programs.soundconverter.enableFor.user.colin = false;
# enabled for easier debugging # sane.programs.firefox.mime.priority = 300; # prefer other browsers when possible
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` # HACK/TODO: make `programs.P.env.VAR` behave according to `mime.priority`
sane.programs.firefox.env = lib.mkForce {}; # sane.programs.firefox.env = lib.mkForce {};
sane.programs.epiphany.env.BROWSER = "epiphany"; # sane.programs.epiphany.env.BROWSER = "epiphany";
sane.programs.firefox.enableFor.user.colin = false; # use epiphany instead # sane.programs.firefox.enableFor.user.colin = false; # use epiphany instead
# note the .conf.d approach: using ~/.config/pipewire/pipewire.conf directly breaks all audio, # sane.programs.mpv.enableFor.user.colin = true;
# 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.loader.efi.canTouchEfiVariables = false;
# /boot space is at a premium. default was 20. # /boot space is at a premium. default was 20.
@@ -92,24 +61,23 @@
# mobile.boot.stage-1.enable = false; # mobile.boot.stage-1.enable = false;
# boot.initrd.systemd.enable = false; # boot.initrd.systemd.enable = false;
# boot.initrd.services.swraid.enable = false; # attempt to fix dm_mod stuff # boot.initrd.services.swraid.enable = false; # attempt to fix dm_mod stuff
# disable proximity sensor.
# the filtering/calibration is bad that it causes the screen to go fully dark at times.
boot.blacklistedKernelModules = [ "stk3310" ];
# 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.
# 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: maybe a memory leak?
# `cat /proc/meminfo` to see CmaTotal/CmaFree if interested in tuning this.
boot.kernelParams = [ "cma=512M" ];
# 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: # mobile-nixos' /lib/firmware includes:
# rtl_bt (bluetooth) # rtl_bt (bluetooth)
# anx7688-fw.bin (USB-C chip: power negotiation, HDMI/dock) # anx7688-fw.bin (USB-C -> HDMI bridge)
# ov5640_af.bin (camera module) # ov5640_af.bin (camera module)
# hardware.firmware = [ config.mobile.device.firmware ]; # hardware.firmware = [ config.mobile.device.firmware ];
# hardware.firmware = [ pkgs.rtl8723cs-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"; system.stateVersion = "21.11";
@@ -127,17 +95,16 @@
# see pkgs/patched/alsa-ucm-conf for more info. # see pkgs/patched/alsa-ucm-conf for more info.
environment.variables.ALSA_CONFIG_UCM2 = "/run/current-system/sw/share/alsa/ucm2"; environment.variables.ALSA_CONFIG_UCM2 = "/run/current-system/sw/share/alsa/ucm2";
environment.pathsToLink = [ "/share/alsa/ucm2" ]; environment.pathsToLink = [ "/share/alsa/ucm2" ];
environment.systemPackages = [ environment.systemPackages = [ pkgs.alsa-ucm-conf-sane ];
(pkgs.alsa-ucm-conf-sane.override { systemd =
# internal speaker has a tendency to break :( let ucm-env = config.environment.variables.ALSA_CONFIG_UCM2;
preferEarpiece = true; in {
})
];
systemd = let
ucm-env = config.environment.variables.ALSA_CONFIG_UCM2;
in {
# cribbed from <repo:nixos/mobile-nixos:modules/quirks/audio.nix> # cribbed from <repo:nixos/mobile-nixos:modules/quirks/audio.nix>
# pulseaudio
user.services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
# pipewire # pipewire
user.services.pipewire.environment.ALSA_CONFIG_UCM2 = ucm-env; user.services.pipewire.environment.ALSA_CONFIG_UCM2 = ucm-env;
user.services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env; user.services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env;
@@ -145,19 +112,6 @@
services.pipewire.environment.ALSA_CONFIG_UCM2 = ucm-env; services.pipewire.environment.ALSA_CONFIG_UCM2 = ucm-env;
services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env; services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env;
services.wireplumber.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 services.udev.extraRules = let
@@ -171,4 +125,39 @@
# make Pinephone front LEDs writable by user. # make Pinephone front LEDs writable by user.
SUBSYSTEM=="leds", DEVPATH=="*/*:indicator", RUN+="${chmod} g+w /sys%p/brightness", RUN+="${chown} :video /sys%p/brightness" SUBSYSTEM=="leds", DEVPATH=="*/*:indicator", RUN+="${chmod} g+w /sys%p/brightness", RUN+="${chown} :video /sys%p/brightness"
''; '';
hardware.opengl.driSupport = true;
services.xserver.displayManager.job.preStart = let
dmesg = "${pkgs.util-linux}/bin/dmesg";
grep = "${pkgs.gnugrep}/bin/grep";
modprobe = "${pkgs.kmod}/bin/modprobe";
in ''
# 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
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
'';
} }

View File

@@ -7,63 +7,18 @@
# - `screen /dev/ttyUSB2 115200` # - `screen /dev/ttyUSB2 115200`
# - `AT+QGPSCFG="nmeasrc",1` # - `AT+QGPSCFG="nmeasrc",1`
# - `AT+QGPS=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, # 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. # 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. # meanwhile, services like eg25-manager can speed this up by uploading assisted GPS data to the modem.
# #
# support/help: # geoclue somehow fits in here as a geospatial provider that leverages GPS and also other sources like radio towers
# - 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.enable = true;
services.gpsd.devices = [ "/dev/ttyUSB1" ]; services.gpsd.devices = [ "/dev/ttyUSB1" ];
# test geoclue2 by building `geoclue2-with-demo-agent` # TODO: enable eg25-manager, and bring online both the modem and GPS on boot
# 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;
} }

View File

@@ -1,56 +1,71 @@
{ pkgs, ... }: { lib, pkgs, ... }:
let let
dmesg = "${pkgs.util-linux}/bin/dmesg"; # use the last commit on the 5.18 branch (5.18.14)
grep = "${pkgs.gnugrep}/bin/grep"; # manjaro's changes between kernel patch versions tend to be minimal if any.
modprobe = "${pkgs.kmod}/bin/modprobe"; manjaroBase = "https://gitlab.manjaro.org/manjaro-arm/packages/core/linux/-/raw/25bd828cd47b1c6e09fcbcf394a649b89d2876dd";
ensureHWReady = '' manjaroPatch = name: sha256: {
# common boot failure: inherit name;
# blank screen (no backlight even), with the following log: patch = pkgs.fetchpatch {
# ```syslog inherit name;
# sun8i-dw-hdmi 1ee0000.hdmi: Couldn't get the HDMI PHY url = "${manjaroBase}/${name}?inline=false";
# ... inherit sha256;
# 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') # the idea for patching off Manjaro's kernel comes from jakewaksbaum:
then # - https://git.sr.ht/~jakewaksbaum/pi/tree/af20aae5653545d6e67a459b59ee3e1ca8a680b0/item/kernel/default.nix
echo "reprobing sun8i_drm_hdmi" # - he later abandoned this, i think because he's using the Pinephone Pro which received mainline support.
# if a command here fails it errors the whole service, so prefer to log instead manjaroPatches = [
${modprobe} -r sun8i_drm_hdmi || echo "failed to unload sun8i_drm_hdmi" (manjaroPatch
${modprobe} sun8i_drm_hdmi || echo "failed to load sub8i_drm_hdmi" "1001-arm64-dts-allwinner-add-hdmi-sound-to-pine-devices.patch"
fi "sha256-DApd791A+AxB28Ven/MVAyuyVphdo8KQDx8O7oxVPnc="
''; )
# these patches below are critical to enable wifi (RTL8723CS)
# - the alternative is a wholly forked kernel by megi/megous:
# - https://xnux.eu/howtos/build-pinephone-kernel.html#toc-how-to-build-megi-s-pinehpone-kernel
# - i don't know if these patches are based on megi's or original
(manjaroPatch
"2001-Bluetooth-Add-new-quirk-for-broken-local-ext-features.patch"
"sha256-CExhJuUWivegxPdnzKINEsKrMFx/m/1kOZFmlZ2SEOc="
)
(manjaroPatch
"2002-Bluetooth-btrtl-add-support-for-the-RTL8723CS.patch"
"sha256-dDdvOphTcP/Aog93HyH+L9m55laTgtjndPSE4/rnzUA="
)
(manjaroPatch
"2004-arm64-dts-allwinner-enable-bluetooth-pinetab-pinepho.patch"
"sha256-o43P3WzXyHK1PF+Kdter4asuyGAEKO6wf5ixcco2kCQ="
)
# XXX: this one has a Makefile, which hardcodes /sbin/depmod:
# - drivers/staging/rtl8723cs/Makefile
# - not sure if this is problematic?
(manjaroPatch
"2005-staging-add-rtl8723cs-driver.patch"
"sha256-6ywm3dQQ5JYl60CLKarxlSUukwi4QzqctCj3tVgzFbo="
)
];
in in
{ {
# use Megi's kernel:
# even with the Manjaro patches, stock 5.18 has a few issues on Pinephone:
# - no battery charging
# - phone rotation sensor is off by 90 degrees
# - ambient light sensor causes screen brightness to be shakey
# - phosh greeter may not appear after wake from sleep
boot.kernelPackages = pkgs.linuxPackagesFor pkgs.linux-megous; 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: # alternatively, use nixos' kernel and add the stuff we want:
# # cross-compilation optimization:
# boot.kernelPackages =
# let p = (import nixpkgs { localSystem = "x86_64-linux"; });
# in p.pkgsCross.aarch64-multiplatform.linuxPackages_5_18;
# # non-cross:
# # boot.kernelPackages = pkgs.linuxPackages_5_18;
# boot.kernelPatches = manjaroPatches ++ [ # boot.kernelPatches = manjaroPatches ++ [
# (patchDefconfig kernelConfig) # (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 = { nixpkgs.hostPlatform.linux-kernel = {
# defaults: # defaults:
name = "aarch64-multiplatform"; name = "aarch64-multiplatform";
@@ -65,25 +80,4 @@ in
target = "Image.gz"; # <-- compress the kernel image target = "Image.gz"; # <-- compress the kernel image
# target = "zImage"; # <-- confuses other parts of nixos :-( # 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;
} }

View File

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

@@ -9,6 +9,13 @@
# - <https://itsfoss.com/content/images/2023/04/nixos-tutorials.png> # - <https://itsfoss.com/content/images/2023/04/nixos-tutorials.png>
{ lib, pkgs, sane-lib, ... }: { lib, pkgs, sane-lib, ... }:
let
# TODO: generate this from the .svg
# bg = ./nixos-bg-02.png;
bg = pkgs.runCommand "nixos-bg.png" { nativeBuildInputs = [ pkgs.inkscape ]; } ''
inkscape ${./nixos-bg-02.svg} -o $out
'';
in
{ {
sane.programs.firefox.config = { sane.programs.firefox.config = {
# compromise impermanence for the sake of usability # compromise impermanence for the sake of usability
@@ -20,9 +27,6 @@
# sidebery UX doesn't make sense on small screen # sidebery UX doesn't make sense on small screen
addons.sidebery.enable = false; addons.sidebery.enable = false;
}; };
sane.programs.swaynotificationcenter.config = {
backlight = "backlight"; # /sys/class/backlight/*backlight*/brightness
};
sane.gui.sxmo = { sane.gui.sxmo = {
nogesture = true; nogesture = true;
@@ -31,10 +35,93 @@
SXMO_LISGD_INPUT_DEVICE = "/dev/input/by-path/platform-1c2ac00.i2c-event"; SXMO_LISGD_INPUT_DEVICE = "/dev/input/by-path/platform-1c2ac00.i2c-event";
# vol and power are detected correctly by upstream # 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 ### preferences
# 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
BEMENU_OPTS = 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
in lib.concatStringsSep " " [
"--wrap --scrollbar autohide --fixed-height"
"--center --margin 45"
"--no-spacing"
# XXX: font size doesn't seem to take effect (would prefer larger)
"--fn 'Sxmo 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)
];
DEFAULT_COUNTRY = "US";
# BEMENU lines (wayland DMENU): # BEMENU lines (wayland DMENU):
# - camera is 9th entry # - camera is 9th entry
@@ -47,6 +134,7 @@
# - close is 16th entry # - close is 16th entry
SXMO_BEMENU_LANDSCAPE_LINES = "11"; # default 8 SXMO_BEMENU_LANDSCAPE_LINES = "11"; # default 8
SXMO_BEMENU_PORTRAIT_LINES = "16"; # default 16 SXMO_BEMENU_PORTRAIT_LINES = "16"; # default 16
SXMO_BG_IMG = "${bg}";
SXMO_LOCK_IDLE_TIME = "15"; # how long between screenoff -> lock -> back to screenoff (default: 8) 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 # gravity: how far to tilt the device before the screen rotates
# for a given setting, normal <-> invert requires more movement then left <-> right # for a given setting, normal <-> invert requires more movement then left <-> right
@@ -56,22 +144,9 @@
# SXMO_ROTATION_GRAVITY = "12500"; # kinda uncomfortable when walking # SXMO_ROTATION_GRAVITY = "12500"; # kinda uncomfortable when walking
SXMO_ROTATION_GRAVITY = "12000"; SXMO_ROTATION_GRAVITY = "12000";
SXMO_SCREENSHOT_DIR = "/home/colin/Pictures"; # default: "$HOME" SXMO_SCREENSHOT_DIR = "/home/colin/Pictures"; # default: "$HOME"
# test new scales by running `swaymsg -- output DSI-1 scale x.y`
# 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.5"; # hard to press gPodder icons
SXMO_SWAY_SCALE = "1.6"; SXMO_SWAY_SCALE = "1.8";
# SXMO_SWAY_SCALE = "1.8";
# SXMO_SWAY_SCALE = "2"; # SXMO_SWAY_SCALE = "2";
SXMO_WORKSPACE_WRAPPING = "5"; # how many workspaces. default: 4 SXMO_WORKSPACE_WRAPPING = "5"; # how many workspaces. default: 4
@@ -92,5 +167,13 @@
WVKBD_LANDSCAPE_LAYERS = "landscape,special,emoji"; WVKBD_LANDSCAPE_LAYERS = "landscape,special,emoji";
WVKBD_LAYERS = "full,special,emoji"; WVKBD_LAYERS = "full,special,emoji";
}; };
package = pkgs.sxmo-utils.overrideAttrs (base: {
postPatch = (base.postPatch or "") + ''
cat <<EOF >> ./configs/default_hooks/sxmo_hook_start.sh
# rotate UI based on physical display angle by default
sxmo_daemons.sh start autorotate sxmo_autorotate.sh
EOF
'';
});
}; };
} }

View File

@@ -4,15 +4,12 @@
./fs.nix ./fs.nix
]; ];
boot.loader.generic-extlinux-compatible.enable = true;
boot.loader.efi.canTouchEfiVariables = false; boot.loader.efi.canTouchEfiVariables = false;
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ]; sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
sane.persist.enable = false; # sane.persist.enable = false; # TODO: disable (but run `nix flake check` to ensure it works!)
sane.nixcache.enable = false; # don't want to be calling out to dead machines that we're *trying* to rescue sane.nixcache.enable = false; # don't want to be calling out to dead machines that we're *trying* to rescue
# 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 # docs: https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion
system.stateVersion = "21.05"; system.stateVersion = "21.05";
} }

View File

@@ -24,9 +24,7 @@
]; ];
sane.services.dyn-dns.enable = true; sane.services.dyn-dns.enable = true;
sane.services.wg-home.enable = true; sane.services.wg-home.enable = true;
sane.services.wg-home.visibleToWan = true; sane.services.wg-home.enableWan = true;
sane.services.wg-home.forwardToWan = true;
sane.services.wg-home.routeThroughServo = false;
sane.services.wg-home.ip = config.sane.hosts.by-name."servo".wg-home.ip; sane.services.wg-home.ip = config.sane.hosts.by-name."servo".wg-home.ip;
sane.nixcache.substituters.servo = false; sane.nixcache.substituters.servo = false;
sane.nixcache.substituters.desko = false; sane.nixcache.substituters.desko = false;

View File

@@ -2,9 +2,6 @@
{ {
sane.persist.root-on-tmpfs = true; 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" = { fileSystems."/nix" = {
device = "/dev/disk/by-uuid/cc81cca0-3cc7-4d82-a00c-6243af3e7776"; device = "/dev/disk/by-uuid/cc81cca0-3cc7-4d82-a00c-6243af3e7776";
@@ -36,38 +33,10 @@
}; };
sane.fs."/mnt/persist/ext".mount = {}; sane.fs."/mnt/persist/ext".mount = {};
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
# TODO: this is overly broad; only need media and share directories to be persisted # TODO: this is overly broad; only need media and share directories to be persisted
{ user = "colin"; group = "users"; path = "/var/lib/uninsane"; } { 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 # make sure large media is stored to the HDD
sane.persist.sys.ext = [ sane.persist.sys.ext = [
{ {

View File

@@ -1,233 +1,220 @@
{ config, lib, pkgs, ... }: { config, pkgs, ... }:
let
portOpts = with lib; types.submodule {
options = {
visibleTo.ovpn = mkOption {
type = types.bool;
default = false;
};
};
};
in
{ {
options = with lib; { networking.domain = "uninsane.org";
sane.ports.ports = mkOption {
# add the `visibleTo.ovpn` option
type = types.attrsOf portOpts;
};
};
config = { sane.ports.openFirewall = true;
networking.domain = "uninsane.org"; sane.ports.openUpnp = true;
sane.ports.openFirewall = true; # view refused packets with: `sudo journalctl -k`
sane.ports.openUpnp = true; # networking.firewall.logRefusedPackets = true;
# view refused packets with: `sudo journalctl -k` # The global useDHCP flag is deprecated, therefore explicitly set to false here.
# networking.firewall.logRefusedPackets = true; # 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;
# The global useDHCP flag is deprecated, therefore explicitly set to false here. # this is needed to forward packets from the VPN to the host
# Per-interface useDHCP will be mandatory in the future, so this generated config boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
# 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 # unless we add interface-specific settings for each VPN, we have to define nameservers globally.
boot.kernel.sysctl."net.ipv4.ip_forward" = 1; # networking.nameservers = [
# "1.1.1.1"
# "9.9.9.9"
# ];
# unless we add interface-specific settings for each VPN, we have to define nameservers globally. # use systemd's stub resolver.
# networking.nameservers = [ # /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
# "1.1.1.1" # instead, running the stub resolver on a known address in the root ns lets us rewrite packets
# "9.9.9.9" # 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"
];
# use systemd's stub resolver. # nscd -- the Name Service Caching Daemon -- caches DNS query responses
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link). # in a way that's unaware of my VPN routing, so routes are frequently poor against
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets # services which advertise different IPs based on geolocation.
# in the ovnps namespace to use the provider's DNS resolvers. # nscd claims to be usable without a cache, but in practice i can't get it to not cache!
# a weakness is we can only query 1 NS at a time (unless we were to clone the packets?) # nsncd is the Name Service NON-Caching Daemon. it's a drop-in that doesn't cache;
# there also seems to be some cache somewhere that's shared between the two namespaces. # this is OK on the host -- because systemd-resolved caches. it's probably sub-optimal
# i think this is a libc thing. might need to leverage proper cgroups to _really_ kill it. # in the netns and we query upstream DNS more often than needed. hm.
# - getent ahostsv4 www.google.com # TODO: run a separate recursive resolver in each namespace.
# - try fix: <https://serverfault.com/questions/765989/connect-to-3rd-party-vpn-server-but-dont-use-it-as-the-default-route/766290#766290> services.nscd.enableNsncd = true;
services.resolved.enable = true;
# without DNSSEC: # services.resolved.extraConfig = ''
# - dig matrix.org => works # # docs: `man resolved.conf`
# - curl https://matrix.org => works # # DNS servers to use via the `wg-ovpns` interface.
# with default DNSSEC: # # i hope that from the root ns, these aren't visible.
# - dig matrix.org => works # DNS=46.227.67.134%wg-ovpns 192.165.9.158%wg-ovpns
# - curl https://matrix.org => fails # FallbackDNS=1.1.1.1 9.9.9.9
# 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 = [ # OVPN CONFIG (https://www.ovpn.com):
# use systemd-resolved resolver # DOCS: https://nixos.wiki/wiki/WireGuard
# full resolver (which understands /etc/hosts) lives on 127.0.0.53 # if you `systemctl restart wireguard-wg-ovpns`, make sure to also restart any other services in `NetworkNamespacePath = .../ovpns`.
# stub resolver (just forwards upstream) lives on 127.0.0.54 # TODO: why not create the namespace as a seperate operation (nix config for that?)
"127.0.0.53" 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";
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 = [
# 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 publicKey = "SkkEZDCBde22KTs/Hc7FWvDBfdOCQA4YtBEuC3n5KGs=";
# services which advertise different IPs based on geolocation. endpoint = "185.157.162.10:9930";
# nscd claims to be usable without a cache, but in practice i can't get it to not cache! # alternatively: use hostname, but that presents bootstrapping issues (e.g. if host net flakes)
# nsncd is the Name Service NON-Caching Daemon. it's a drop-in that doesn't cache; # endpoint = "vpn36.prd.amsterdam.ovpn.com:9930";
# this is OK on the host -- because systemd-resolved caches. it's probably sub-optimal allowedIPs = [ "0.0.0.0/0" ];
# in the netns and we query upstream DNS more often than needed. hm. # nixOS says this is important for keeping NATs active
# TODO: run a separate recursive resolver in each namespace. persistentKeepalive = 25;
services.nscd.enableNsncd = true; # 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
# services.resolved.extraConfig = '' # dynamicEndpointRefreshSeconds = 30;
# # docs: `man resolved.conf` # when refresh fails, try it again after this period instead.
# # DNS servers to use via the `wg-ovpns` interface. # TODO: not avail until nixpkgs upgrade
# # i hope that from the root ns, these aren't visible. # dynamicEndpointRefreshRestartSeconds = 5;
# DNS=46.227.67.134%wg-ovpns 192.165.9.158%wg-ovpns }
# FallbackDNS=1.1.1.1 9.9.9.9 ];
# ''; preSetup = "" + ''
${ip} netns add ovpns || echo "ovpns already exists"
# 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; 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
# HURRICANE ELECTRIC CONFIG: # make it so traffic originating from the host side of the veth
# networking.sits = { # is sent over the veth no matter its destination.
# hurricane = { ${ip} rule add from ${veth-host-ip} lookup ovpns pref 50
# remote = "216.218.226.238"; # for traffic originating at the host veth to the WAN, use the veth as our gateway
# local = "192.168.0.5"; # not sure if the metric 1002 matters.
# # local = "10.0.0.5"; ${ip} route add default via ${veth-local-ip} dev ovpns-veth-a proto kernel src ${veth-host-ip} metric 1002 table ovpns
# # remote = "10.0.0.1"; # give the default route lower priority
# # local = "10.0.0.22"; ${ip} rule add from all lookup local pref 100
# dev = "eth0"; ${ip} rule del from all lookup local pref 0
# 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: # bridge HTTP traffic:
# # hurricane: flags=209<UP,POINTOPOINT,RUNNING,NOARP> mtu 1480 # any external port-80 request sent to the VPN addr will be forwarded to the rootns.
# # inet6 2001:470:a:450::2 prefixlen 64 scopeid 0x0<global> # this exists so LetsEncrypt can procure a cert for the MX over http.
# # inet6 fe80::c0a8:16 prefixlen 64 scopeid 0x20<link> # TODO: we could use _acme_challence.mx.uninsane.org CNAME to avoid this forwarding
# # sit txqueuelen 1000 (IPv6-in-IPv4) # - <https://community.letsencrypt.org/t/where-does-letsencrypt-resolve-dns-from/37607/8>
# # test with: ${in-ns} ${iptables} -A PREROUTING -t nat -p tcp --dport 80 -m iprange --dst-range ${vpn-ip} \
# # curl --interface hurricane http://[2607:f8b0:400a:80b::2004] -j DNAT --to-destination ${veth-host-ip}:80
# # ping 2607:f8b0:400a:80b::2004
# we also bridge DNS traffic
${in-ns} ${iptables} -A PREROUTING -t nat -p udp --dport 53 -m iprange --dst-range ${vpn-ip} \
-j DNAT --to-destination ${veth-host-ip}
${in-ns} ${iptables} -A PREROUTING -t nat -p tcp --dport 53 -m iprange --dst-range ${vpn-ip} \
-j DNAT --to-destination ${veth-host-ip}
# 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
'';
}; };
# 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
} }

View File

@@ -12,7 +12,7 @@ in
# > AttributeError: 'NoneType' object has no attribute 'query' # > AttributeError: 'NoneType' object has no attribute 'query'
lib.mkIf false lib.mkIf false
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
{ inherit user group; mode = "0700"; path = svc-dir; } { inherit user group; mode = "0700"; path = svc-dir; }
]; ];

View File

@@ -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" (?)
];
}

View File

@@ -2,13 +2,12 @@
{ {
imports = [ imports = [
./calibre.nix ./calibre.nix
./coturn.nix
./ddns-afraid.nix ./ddns-afraid.nix
./ddns-he.nix ./ddns-he.nix
./email ./email
./ejabberd.nix ./ejabberd.nix
./freshrss.nix ./freshrss.nix
./export ./ftp
./gitea.nix ./gitea.nix
./goaccess.nix ./goaccess.nix
./ipfs.nix ./ipfs.nix
@@ -19,13 +18,13 @@
./lemmy.nix ./lemmy.nix
./matrix ./matrix
./navidrome.nix ./navidrome.nix
./nginx.nix ./nfs.nix
./nixserve.nix ./nixserve.nix
./ntfy ./nginx.nix
./pict-rs.nix ./pict-rs.nix
./pleroma.nix ./pleroma.nix
./postgres.nix ./postgres.nix
./prosody ./prosody.nix
./transmission.nix ./transmission.nix
./trust-dns.nix ./trust-dns.nix
./wikipedia.nix ./wikipedia.nix

View File

@@ -14,111 +14,76 @@
# #
# compliance tests: # compliance tests:
# - <https://compliance.conversations.im/server/uninsane.org/#xep0352> # - <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, ... }: { config, lib, pkgs, ... }:
let # XXX: avatar support works in MUCs but not DMs
# TODO: this range could be larger, but right now that's costly because each element is its own UPnP forward # lib.mkIf false
# 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 = [ sane.persist.sys.plaintext = [
{ user = "ejabberd"; group = "ejabberd"; path = "/var/lib/ejabberd"; } { user = "ejabberd"; group = "ejabberd"; path = "/var/lib/ejabberd"; }
]; ];
sane.ports.ports = lib.mkMerge ([ sane.ports.ports."3478" = {
{ protocol = [ "tcp" "udp" ];
"3478" = { visibleTo.lan = true;
protocol = [ "tcp" "udp" ]; visibleTo.wan = true;
visibleTo.lan = true; description = "colin-xmpp-stun-turn";
visibleTo.wan = true; };
description = "colin-xmpp-stun-turn"; sane.ports.ports."5222" = {
}; protocol = [ "tcp" ];
"5222" = { visibleTo.lan = true;
protocol = [ "tcp" ]; visibleTo.wan = true;
visibleTo.lan = true; description = "colin-xmpp-client-to-server";
visibleTo.wan = true; };
description = "colin-xmpp-client-to-server"; sane.ports.ports."5223" = {
}; protocol = [ "tcp" ];
"5223" = { visibleTo.lan = true;
protocol = [ "tcp" ]; visibleTo.wan = true;
visibleTo.lan = true; description = "colin-xmpps-client-to-server"; # XMPP over TLS
visibleTo.wan = true; };
description = "colin-xmpps-client-to-server"; # XMPP over TLS sane.ports.ports."5269" = {
}; protocol = [ "tcp" ];
"5269" = { visibleTo.wan = true;
protocol = [ "tcp" ]; description = "colin-xmpp-server-to-server";
visibleTo.wan = true; };
description = "colin-xmpp-server-to-server"; sane.ports.ports."5270" = {
}; protocol = [ "tcp" ];
"5270" = { visibleTo.wan = true;
protocol = [ "tcp" ]; description = "colin-xmpps-server-to-server"; # XMPP over TLS
visibleTo.wan = true; };
description = "colin-xmpps-server-to-server"; # XMPP over TLS sane.ports.ports."5280" = {
}; protocol = [ "tcp" ];
"5280" = { visibleTo.lan = true;
protocol = [ "tcp" ]; visibleTo.wan = true;
visibleTo.lan = true; description = "colin-xmpp-bosh";
visibleTo.wan = true; };
description = "colin-xmpp-bosh"; sane.ports.ports."5281" = {
}; protocol = [ "tcp" ];
"5281" = { visibleTo.lan = true;
protocol = [ "tcp" ]; visibleTo.wan = true;
visibleTo.lan = true; description = "colin-xmpp-bosh-https";
visibleTo.wan = true; };
description = "colin-xmpp-bosh-https"; sane.ports.ports."5349" = {
}; protocol = [ "tcp" ];
"5349" = { visibleTo.lan = true;
protocol = [ "tcp" ]; visibleTo.wan = true;
visibleTo.lan = true; description = "colin-xmpp-stun-turn-over-tls";
visibleTo.wan = true; };
description = "colin-xmpp-stun-turn-over-tls"; sane.ports.ports."5443" = {
}; protocol = [ "tcp" ];
"5443" = { visibleTo.lan = true;
protocol = [ "tcp" ]; visibleTo.wan = true;
visibleTo.lan = true; description = "colin-xmpp-web-services"; # file uploads, websockets, admin
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 # TODO: forward these TURN ports!
services.coturn.enable = false; networking.firewall.allowedTCPPortRanges = [{
from = 49152; # TURN
to = 49408;
}];
networking.firewall.allowedUDPPortRanges = [{
from = 49152; # TURN
to = 49408;
}];
# provide access to certs # provide access to certs
# TODO: this should just be `acme`. then we also add nginx to the `acme` group. # TODO: this should just be `acme`. then we also add nginx to the `acme` group.
@@ -185,285 +150,284 @@ lib.mkIf false
services.ejabberd.enable = true; services.ejabberd.enable = true;
services.ejabberd.configFile = "/var/lib/ejabberd/ejabberd.yaml"; services.ejabberd.configFile = "/var/lib/ejabberd/ejabberd.yaml";
systemd.services.ejabberd.preStart = let systemd.services.ejabberd.preStart = let
config-in = pkgs.writeText "ejabberd.yaml.in" (lib.generators.toYAML {} { config-in = pkgs.writeTextFile {
hosts = [ "uninsane.org" ]; name = "ejabberd.yaml.in";
# none | emergency | alert | critical | error | warning | notice | info | debug text = ''
loglevel = "debug"; hosts:
acme.auto = false; - uninsane.org
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"; # none | emergency | alert | critical | error | warning | notice | info | debug
acl = { loglevel: debug
admin.user = [ "colin@uninsane.org" ]; # loglevel: info
local.user_regexp = ""; # loglevel: notice
loopback.ip = [ "127.0.0.0/8" "::1/128" ];
};
access_rules = { acme:
local.allow = "local"; auto: false
c2s_access.allow = "all"; certfiles:
announce.allow = "admin"; - /var/lib/acme/uninsane.org/full.pem
configure.allow = "admin"; # ca_file: ${pkgs.cacert.unbundled}/etc/ssl/certs/
muc_create.allow = "local"; # ca_file: ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
pubsub_createnode_access.allow = "all";
trusted_network.allow = "loopback";
};
# docs: <https://docs.ejabberd.im/admin/configuration/basic/#shaper-rules> pam_userinfotype: jid
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> acl:
# this limits the bytes/sec. admin:
# for example, burst: 3_000_000 and rate: 100_000 means: user:
# - each client has a BW budget that accumulates 100kB/sec and is capped at 3 MB - "colin@uninsane.org"
shaper.fast = 1000000; local:
shaper.med = 500000; user_regexp: ""
# shaper.fast.rate = 1000000; loopback:
# shaper.fast.burst_size = 10000000; ip:
# shaper.med.rate = 500000; - 127.0.0.0/8
# shaper.med.burst_size = 5000000; - ::1/128
# see: <https://docs.ejabberd.im/admin/configuration/listen/> access_rules:
# s2s_use_starttls = true; local:
s2s_use_starttls = "optional"; allow: local
# lessens 504: remote-server-timeout errors c2s_access:
# see: <https://github.com/processone/ejabberd/issues/3105#issuecomment-562182967> allow: all
negotiation_timeout = 60; announce:
allow: admin
configure:
allow: admin
muc_create:
allow: local
pubsub_createnode_access:
allow: all
trusted_network:
allow: loopback
listen = [ # docs: <https://docs.ejabberd.im/admin/configuration/basic/#shaper-rules>
{ shaper_rules:
port = 5222; # setting this to above 1 may break outgoing messages
module = "ejabberd_c2s"; # - maybe some servers rate limit? or just don't understand simultaneous connections?
shaper = "c2s_shaper"; max_s2s_connections: 1
starttls = true; max_user_sessions: 10
access = "c2s_access"; max_user_offline_messages: 5000
} c2s_shaper:
{ fast: all
port = 5223; s2s_shaper:
module = "ejabberd_c2s"; med: all
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 # docs: <https://docs.ejabberd.im/admin/configuration/basic/#shapers>
# TODO(low): look into mod_http_fileserver for serving macros? # this limits the bytes/sec.
modules = { # for example, burst: 3_000_000 and rate: 100_000 means:
# mod_adhoc = {}; # - each client has a BW budget that accumulates 100kB/sec and is capped at 3 MB
# mod_announce = { shaper:
# access = "admin"; fast: 1000000
# }; med: 500000
# allows users to set avatars in vCard # fast:
# - <https://docs.ejabberd.im/admin/configuration/modules/#mod-avatar> # - rate: 1000000
mod_avatar = {}; # - burst_size: 10000000
mod_caps = {}; # for mod_pubsub # med:
mod_carboncopy = {}; # allows multiple clients to receive a user's message # - rate: 500000
# queues messages when recipient is offline, including PEP and presence messages. # - burst_size: 5000000
# compliance test suggests this be enabled
mod_client_state = {};
# mod_conversejs: TODO: enable once on 21.12 # see: <https://docs.ejabberd.im/admin/configuration/listen/>
# allows clients like Dino to discover where to upload files # s2s_use_starttls: true
mod_disco.server_info = [ s2s_use_starttls: optional
{ # lessens 504: remote-server-timeout errors
modules = "all"; # see: <https://github.com/processone/ejabberd/issues/3105#issuecomment-562182967>
name = "abuse-addresses"; negotiation_timeout: 60
urls = [
"mailto:admin.xmpp@uninsane.org" listen:
"xmpp:colin@uninsane.org" -
]; port: 5222
} module: ejabberd_c2s
{ shaper: c2s_shaper
modules = "all"; starttls: true
name = "admin-addresses"; access: c2s_access
urls = [ -
"mailto:admin.xmpp@uninsane.org" port: 5223
"xmpp:colin@uninsane.org" module: ejabberd_c2s
]; shaper: c2s_shaper
} tls: true
]; access: c2s_access
mod_http_upload = { -
host = "upload.xmpp.uninsane.org"; port: 5269
hosts = [ "upload.xmpp.uninsane.org" ]; module: ejabberd_s2s_in
put_url = "https://@HOST@:5443/upload"; shaper: s2s_shaper
dir_mode = "0750"; -
file_mode = "0750"; port: 5270
rm_on_unregister = false; module: ejabberd_s2s_in
}; shaper: s2s_shaper
# allow discoverability of BOSH and websocket endpoints tls: true
# TODO: enable once on ejabberd 22.05 (presently 21.04) -
# mod_host_meta = {}; port: 5443
mod_jidprep = {}; # probably not needed: lets clients normalize jids module: ejabberd_http
mod_last = {}; # allow other users to know when i was last online tls: true
mod_mam = { request_handlers:
# Mnesia is limited to 2GB, better to use an SQL backend /admin: ejabberd_web_admin # TODO: ensure this actually works
# For small servers SQLite is a good fit and is very easy /api: mod_http_api # ejabberd API endpoint (to control server)
# to configure. Uncomment this when you have SQL configured: /bosh: mod_bosh
# db_type: sql /upload: mod_http_upload
assume_mam_usage = true; /ws: ejabberd_http_ws
default = "always"; # /.well-known/host-meta: mod_host_meta
}; # /.well-known/host-meta.json: mod_host_meta
mod_muc = { -
access = [ "allow" ]; # STUN+TURN TCP
access_admin = { allow = "admin"; }; # note that the full port range should be forwarded ("not NAT'd")
access_create = "muc_create"; # `use_turn=true` enables both TURN *and* STUN
access_persistent = "muc_create"; port: 3478
access_mam = [ "allow" ]; module: ejabberd_stun
history_size = 100; # messages to show new participants transport: tcp
host = "muc.xmpp.uninsane.org"; use_turn: true
hosts = [ "muc.xmpp.uninsane.org" ]; turn_min_port: 49152
default_room_options = { turn_max_port: 65535
anonymous = false; turn_ipv4_address: %ANATIVE%
lang = "en"; -
persistent = true; # STUN+TURN UDP
mam = true; port: 3478
}; module: ejabberd_stun
}; transport: udp
mod_muc_admin = {}; use_turn: true
mod_offline = { turn_min_port: 49152
# store messages for a user when they're offline (TODO: understand multi-client workflow?) turn_max_port: 65535
access_max_user_messages = "max_user_offline_messages"; turn_ipv4_address: %ANATIVE%
store_groupchat = true; -
}; # STUN+TURN TLS over TCP
mod_ping = {}; port: 5349
mod_privacy = {}; # deprecated, but required for `ejabberctl export_piefxis` module: ejabberd_stun
mod_private = {}; # allow local clients to persist arbitrary data on my server transport: tcp
# push notifications to services integrated with e.g. Apple/Android. tls: true
# default is for a maximum amount of PII to be withheld, since these push notifs certfile: /var/lib/acme/uninsane.org/full.pem
# generally traverse 3rd party services. can opt to include message body, etc, though. use_turn: true
mod_push = {}; turn_min_port: 49152
# i don't fully understand what this does, but it seems aimed at making push notifs more reliable. turn_max_port: 65535
mod_push_keepalive = {}; turn_ipv4_address: %ANATIVE%
mod_roster = {
versioning = true; # TODO: enable mod_fail2ban
}; # TODO(low): look into mod_http_fileserver for serving macros?
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-s2s-dialback> modules:
# s2s dialback to verify inbound messages # mod_adhoc: {}
# unclear to what degree the XMPP network requires this # mod_announce:
mod_s2s_dialback = {}; # access: admin
mod_shared_roster = {}; # creates groups for @all, @online, and anything manually administered? # allows users to set avatars in vCard
mod_stream_mgmt = { # - <https://docs.ejabberd.im/admin/configuration/modules/#mod-avatar>
# resend undelivered messages if the origin client is offline mod_avatar: {}
resend_on_timeout = "if_offline"; mod_caps: {} # for mod_pubsub
}; mod_carboncopy: {} # allows multiple clients to receive a user's message
# fallback for when DNS-based STUN discovery is unsupported. # queues messages when recipient is offline, including PEP and presence messages.
# - see: <https://xmpp.org/extensions/xep-0215.html> # compliance test suggests this be enabled
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-stun-disco> mod_client_state: {}
# people say to just keep this defaulted (i guess ejabberd knows to return its `host` option of uninsane.org?) # mod_conversejs: TODO: enable once on 21.12
mod_stun_disco = {}; # allows clients like Dino to discover where to upload files
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-vcard> mod_disco:
mod_vcard = { server_info:
allow_return_all = true; # all users are discoverable (?) -
host = "vjid.xmpp.uninsane.org"; modules: all
hosts = [ "vjid.xmpp.uninsane.org" ]; name: abuse-addresses
search = true; urls:
}; - "mailto:admin.xmpp@uninsane.org"
mod_vcard_xupdate = {}; # needed for avatars - "xmpp:colin@uninsane.org"
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-pubsub> -
mod_pubsub = { modules: all
#^ needed for avatars name: admin-addresses
access_createnode = "pubsub_createnode_access"; urls:
host = "pubsub.xmpp.uninsane.org"; - "mailto:admin.xmpp@uninsane.org"
hosts = [ "pubsub.xmpp.uninsane.org" ]; - "xmpp:colin@uninsane.org"
ignore_pep_from_offline = false; mod_http_upload:
last_item_cache = true; host: upload.xmpp.uninsane.org
plugins = [ hosts:
"pep" - upload.xmpp.uninsane.org
"flat" put_url: "https://@HOST@:5443/upload"
]; dir_mode: "0750"
force_node_config = { file_mode: "0750"
# ensure client bookmarks are private rm_on_unregister: false
"storage:bookmarks:" = { # allow discoverability of BOSH and websocket endpoints
"access_model" = "whitelist"; # TODO: enable once on ejabberd 22.05 (presently 21.04)
}; # mod_host_meta: {}
"urn:xmpp:avatar:data" = { mod_jidprep: {} # probably not needed: lets clients normalize jids
"access_model" = "open"; mod_last: {} # allow other users to know when i was last online
}; mod_mam:
"urn:xmpp:avatar:metadata" = { # Mnesia is limited to 2GB, better to use an SQL backend
"access_model" = "open"; # 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
mod_version = {}; default: always
}; mod_muc:
}); access:
- allow
access_admin:
- allow: admin
access_create: muc_create
access_persistent: muc_create
access_mam:
- allow
history_size: 100 # messages to show new participants
host: muc.xmpp.uninsane.org
hosts:
- muc.xmpp.uninsane.org
default_room_options:
anonymous: false
lang: en
persistent: true
mam: true
mod_muc_admin: {}
mod_offline: # store messages for a user when they're offline (TODO: understand multi-client workflow?)
access_max_user_messages: max_user_offline_messages
store_groupchat: true
mod_ping: {}
mod_privacy: {} # deprecated, but required for `ejabberctl export_piefxis`
mod_private: {} # allow local clients to persist arbitrary data on my server
# push notifications to services integrated with e.g. Apple/Android.
# default is for a maximum amount of PII to be withheld, since these push notifs
# generally traverse 3rd party services. can opt to include message body, etc, though.
mod_push: {}
# i don't fully understand what this does, but it seems aimed at making push notifs more reliable.
mod_push_keepalive: {}
mod_roster:
versioning: true
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-s2s-dialback>
# s2s dialback to verify inbound messages
# unclear to what degree the XMPP network requires this
mod_s2s_dialback: {}
mod_shared_roster: {} # creates groups for @all, @online, and anything manually administered?
mod_stream_mgmt:
resend_on_timeout: if_offline # resend undelivered messages if the origin client is offline
# fallback for when DNS-based STUN discovery is unsupported.
# - see: <https://xmpp.org/extensions/xep-0215.html>
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-stun-disco>
# people say to just keep this defaulted (i guess ejabberd knows to return its `host` option of uninsane.org?)
mod_stun_disco: {}
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-vcard>
mod_vcard:
allow_return_all: true # all users are discoverable (?)
host: vjid.xmpp.uninsane.org
hosts:
- vjid.xmpp.uninsane.org
search: true
mod_vcard_xupdate: {} # needed for avatars
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-pubsub>
mod_pubsub: # needed for avatars
access_createnode: pubsub_createnode_access
host: pubsub.xmpp.uninsane.org
hosts:
- pubsub.xmpp.uninsane.org
ignore_pep_from_offline: false
last_item_cache: true
plugins:
- pep
- flat
force_node_config:
# ensure client bookmarks are private
storage:bookmarks:
access_model: whitelist
urn:xmpp:avatar:data:
access_model: open
urn:xmpp:avatar:metadata:
access_model: open
mod_version: {}
'';
};
sed = "${pkgs.gnused}/bin/sed"; sed = "${pkgs.gnused}/bin/sed";
in '' in ''
ip=$(cat '${config.sane.services.dyn-dns.ipPath}') ip=$(cat '${config.sane.services.dyn-dns.ipPath}')
# config is 444 (not 644), so we want to write out-of-place and then atomically move # config is 444 (not 644), so we want to write out-of-place and then atomically move
# TODO: factor this out into `sane-woop` helper? # TODO: factor this out into `sane-woop` helper?
rm -f /var/lib/ejabberd/ejabberd.yaml.new rm -f /var/lib/ejabberd/ejabberd.yaml.new
${sed} "s/%ANATIVE%/$ip/g" ${config-in} > /var/lib/ejabberd/ejabberd.yaml.new ${sed} "s/%ANATIVE%/$ip/" ${config-in} > /var/lib/ejabberd/ejabberd.yaml.new
mv /var/lib/ejabberd/ejabberd.yaml{.new,} mv /var/lib/ejabberd/ejabberd.yaml{.new,}
''; '';

View File

@@ -22,13 +22,6 @@
# - but postfix delegates authorization of that outgoing mail to dovecot, on the server side # - but postfix delegates authorization of that outgoing mail to dovecot, on the server side
# #
# - local clients (i.e. sendmail) interact only with postfix # - 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
{ ... }: { ... }:
{ {

View File

@@ -18,7 +18,7 @@ let
}; };
in in
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
# TODO: mode? could be more granular # TODO: mode? could be more granular
{ user = "opendkim"; group = "opendkim"; path = "/var/lib/opendkim"; } { user = "opendkim"; group = "opendkim"; path = "/var/lib/opendkim"; }
{ user = "root"; group = "root"; path = "/var/lib/postfix"; } { user = "root"; group = "root"; path = "/var/lib/postfix"; }
@@ -28,25 +28,21 @@ in
# "/var/lib/dovecot" # "/var/lib/dovecot"
]; ];
# XXX(2023/10/20): opening these ports in the firewall has the OPPOSITE effect as intended. sane.ports.ports."25" = {
# these ports are only routable so long as they AREN'T opened. protocol = [ "tcp" ];
# probably some cursed interaction with network namespaces introduced after 2023/10/10. visibleTo.ovpn = true;
# sane.ports.ports."25" = { description = "colin-smtp-mx.uninsane.org";
# protocol = [ "tcp" ]; };
# # XXX visibleTo.lan effectively means "open firewall, but don't configure any NAT/forwarding" sane.ports.ports."465" = {
# visibleTo.lan = true; protocol = [ "tcp" ];
# description = "colin-smtp-mx.uninsane.org"; visibleTo.ovpn = true;
# }; description = "colin-smtps-mx.uninsane.org";
# sane.ports.ports."465" = { };
# protocol = [ "tcp" ]; sane.ports.ports."587" = {
# visibleTo.lan = true; protocol = [ "tcp" ];
# description = "colin-smtps-mx.uninsane.org"; visibleTo.ovpn = true;
# }; description = "colin-smtps-submission-mx.uninsane.org";
# sane.ports.ports."587" = { };
# protocol = [ "tcp" ];
# visibleTo.lan = true;
# description = "colin-smtps-submission-mx.uninsane.org";
# };
# exists only to manage certs for Postfix # exists only to manage certs for Postfix
services.nginx.virtualHosts."mx.uninsane.org" = { services.nginx.virtualHosts."mx.uninsane.org" = {

View File

@@ -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
'';
};
}

View File

@@ -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;
};
}

View File

@@ -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";
};
}

View File

@@ -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

View File

@@ -15,7 +15,7 @@
owner = config.users.users.freshrss.name; owner = config.users.users.freshrss.name;
mode = "0400"; mode = "0400";
}; };
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
{ user = "freshrss"; group = "freshrss"; path = "/var/lib/freshrss"; } { user = "freshrss"; group = "freshrss"; path = "/var/lib/freshrss"; }
]; ];

View File

@@ -0,0 +1,70 @@
# 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>
#
# sftpgo is a FTP server that also supports WebDAV, SFTP, and web clients.
{ lib, pkgs, sane-lib, ... }:
let
authProgram = pkgs.static-nix-shell.mkBash {
pname = "sftpgo_external_auth_hook";
src = ./.;
};
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;
settings = {
ftpd = {
bindings = [{
address = "10.0.10.5";
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.
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}/bin/sftpgo_external_auth_hook";
};
};
};
}

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash
# 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
#
# 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.
# TODO: don't reuse /var/nfs/export here. formalize this some other way.
if [ "$SFTPGO_AUTHD_USERNAME" = "anonymous" ]; then
echo '{'
echo ' "status":1,'
echo ' "username":"anonymous","expiration_date":0,'
echo ' "home_dir":"/var/nfs/export","uid":65534,"gid":65534,"max_sessions":0,"quota_size":0,"quota_files":100000,'
echo ' "permissions":{'
echo ' "/":["list", "download"]'
echo ' },'
echo ' "upload_bandwidth":0,"download_bandwidth":0,'
echo ' "filters":{"allowed_ip":[],"denied_ip":[]},"public_keys":[]'
echo '}'
else
echo '{"username":""}'
fi

View File

@@ -2,7 +2,7 @@
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
# TODO: mode? could be more granular # TODO: mode? could be more granular
{ user = "git"; group = "gitea"; path = "/var/lib/gitea"; } { user = "git"; group = "gitea"; path = "/var/lib/gitea"; }
]; ];

View File

@@ -10,7 +10,7 @@
lib.mkIf false # i don't actively use ipfs anymore lib.mkIf false # i don't actively use ipfs anymore
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
# TODO: mode? could be more granular # TODO: mode? could be more granular
{ user = "261"; group = "261"; path = "/var/lib/ipfs"; } { user = "261"; group = "261"; path = "/var/lib/ipfs"; }
]; ];

View File

@@ -1,7 +1,7 @@
{ ... }: { ... }:
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
# TODO: mode? we only need this to save Indexer creds ==> migrate to config? # TODO: mode? we only need this to save Indexer creds ==> migrate to config?
{ user = "root"; group = "root"; path = "/var/lib/jackett"; } { user = "root"; group = "root"; path = "/var/lib/jackett"; }
]; ];

View File

@@ -40,7 +40,7 @@
description = "colin-jellyfin-https-lan"; description = "colin-jellyfin-https-lan";
}; };
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
{ user = "jellyfin"; group = "jellyfin"; mode = "0700"; path = "/var/lib/jellyfin"; } { user = "jellyfin"; group = "jellyfin"; mode = "0700"; path = "/var/lib/jellyfin"; }
]; ];
sane.fs."/var/lib/jellyfin/config/logging.json" = { sane.fs."/var/lib/jellyfin/config/logging.json" = {

View File

@@ -4,7 +4,7 @@ let
inherit (svc-cfg) user group port stateDir; inherit (svc-cfg) user group port stateDir;
in in
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
{ inherit user group; mode = "0700"; path = stateDir; } { inherit user group; mode = "0700"; path = stateDir; }
]; ];

View File

@@ -3,23 +3,13 @@
# - <repo:LemmyNet/lemmy:docker/nginx.conf> # - <repo:LemmyNet/lemmy:docker/nginx.conf>
# - <repo:LemmyNet/lemmy-ansible:templates/nginx.conf> # - <repo:LemmyNet/lemmy-ansible:templates/nginx.conf>
{ config, lib, pkgs, ... }: { config, lib, ... }:
let let
inherit (builtins) toString; inherit (builtins) toString;
inherit (lib) mkForce; inherit (lib) mkForce;
uiPort = 1234; # default ui port is 1234 uiPort = 1234; # default ui port is 1234
backendPort = 8536; # default backend port is 8536 backendPort = 8536; # default backend port is 8536
#^ i guess the "backend" port is used for federation? # - i guess the "backend" port is used for federation?
pict-rs = pkgs.pict-rs.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 { in {
services.lemmy = { services.lemmy = {
enable = true; enable = true;
@@ -66,20 +56,4 @@ in {
}; };
sane.dns.zones."uninsane.org".inet.CNAME."lemmy" = "native"; 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
]);
} }

View File

@@ -1,16 +1,6 @@
# docs: <https://nixos.wiki/wiki/Matrix> # docs: <https://nixos.wiki/wiki/Matrix>
# docs: <https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-synapse> # 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> # 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, ... }: { config, lib, pkgs, ... }:
{ {
@@ -20,73 +10,67 @@
./signal.nix ./signal.nix
]; ];
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
{ user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/matrix-synapse"; } { user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/matrix-synapse"; }
]; ];
services.matrix-synapse.enable = true; services.matrix-synapse.enable = true;
services.matrix-synapse.settings = { # this changes the default log level from INFO to WARN.
# this changes the default log level from INFO to WARN. # maybe there's an easier way?
# maybe there's an easier way? services.matrix-synapse.settings.log_config = ./synapse-log_level.yaml;
log_config = ./synapse-log_level.yaml; services.matrix-synapse.settings.server_name = "uninsane.org";
server_name = "uninsane.org";
# services.matrix-synapse.enable_registration_captcha = true; # services.matrix-synapse.enable_registration_captcha = true;
# services.matrix-synapse.enable_registration_without_verification = true; # services.matrix-synapse.enable_registration_without_verification = true;
enable_registration = true; services.matrix-synapse.settings.enable_registration = true;
# services.matrix-synapse.registration_shared_secret = "<shared key goes here>"; # services.matrix-synapse.registration_shared_secret = "<shared key goes here>";
# default for listeners is port = 8448, tls = true, x_forwarded = false. # default for listeners is port = 8448, tls = true, x_forwarded = false.
# we change this because the server is situated behind nginx. # we change this because the server is situated behind nginx.
listeners = [ services.matrix-synapse.settings.listeners = [
{ {
port = 8008; port = 8008;
bind_addresses = [ "127.0.0.1" ]; bind_addresses = [ "127.0.0.1" ];
type = "http"; type = "http";
tls = false; tls = false;
x_forwarded = true; x_forwarded = true;
resources = [ resources = [
{ {
names = [ "client" "federation" ]; names = [ "client" "federation" ];
compress = false; compress = false;
} }
]; ];
} }
]; ];
ip_range_whitelist = [ services.matrix-synapse.settings.x_forwarded = true; # because we proxy matrix behind nginx
# to communicate with ntfy.uninsane.org push notifs. services.matrix-synapse.settings.max_upload_size = "100M"; # default is "50M"
# 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 services.matrix-synapse.settings.admin_contact = "admin.matrix@uninsane.org";
max_upload_size = "100M"; # default is "50M" services.matrix-synapse.settings.registrations_require_3pid = [ "email" ];
admin_contact = "admin.matrix@uninsane.org";
registrations_require_3pid = [ "email" ];
};
services.matrix-synapse.extraConfigFiles = [ services.matrix-synapse.extraConfigFiles = [
config.sops.secrets."matrix_synapse_secrets.yaml".path config.sops.secrets."matrix_synapse_secrets.yaml".path
]; ];
systemd.services.matrix-synapse.postStart = '' # services.matrix-synapse.extraConfigFiles = [builtins.toFile "matrix-synapse-extra-config" ''
ACCESS_TOKEN=$(${pkgs.coreutils}/bin/cat ${config.sops.secrets.matrix_access_token.path}) # admin_contact: "admin.matrix@uninsane.org"
TOPIC=$(${pkgs.coreutils}/bin/cat ${config.sops.secrets.ntfy-sh-topic.path}) # registrations_require_3pid:
# - email
echo "ensuring ntfy push gateway" # email:
${pkgs.curl}/bin/curl \ # smtp_host: "mx.uninsane.org"
--header "Authorization: Bearer $ACCESS_TOKEN" \ # smtp_port: 587
--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\" }" \ # smtp_user: "matrix-synapse"
localhost:8008/_matrix/client/v3/pushers/set # smtp_pass: "${secrets.matrix-synapse.smtp_pass}"
# require_transport_security: true
echo "registered push gateways:" # enable_tls: true
${pkgs.curl}/bin/curl \ # notif_from: "%(app)s <notify.matrix@uninsane.org>"
--header "Authorization: Bearer $ACCESS_TOKEN" \ # app_name: "Uninsane Matrix"
localhost:8008/_matrix/client/v3/pushers \ # enable_notifs: true
| ${pkgs.jq}/bin/jq . # validation_token_lifetime: 96h
''; # invite_client_location: "https://web.matrix.uninsane.org"
# subjects:
# email_validation: "[%(server_name)s] Validate your email"
# ''];
# new users may be registered on the CLI: # new users may be registered on the CLI:
# register_new_matrix_user -c /nix/store/8n6kcka37jhmi4qpd2r03aj71pkyh21s-homeserver.yaml http://localhost:8008 # register_new_matrix_user -c /nix/store/8n6kcka37jhmi4qpd2r03aj71pkyh21s-homeserver.yaml http://localhost:8008
@@ -157,9 +141,4 @@
sops.secrets."matrix_synapse_secrets.yaml" = { sops.secrets."matrix_synapse_secrets.yaml" = {
owner = config.users.users.matrix-synapse.name; 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" ];
} }

View File

@@ -5,7 +5,7 @@
# - recommended to use mautrix-discord: <https://github.com/NixOS/nixpkgs/pull/200462> # - recommended to use mautrix-discord: <https://github.com/NixOS/nixpkgs/pull/200462>
lib.mkIf false lib.mkIf false
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
{ user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/mx-puppet-discord"; } { user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/mx-puppet-discord"; }
]; ];

View File

@@ -101,7 +101,7 @@ in
}) })
]; ];
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
# TODO: mode? # TODO: mode?
{ user = "matrix-appservice-irc"; group = "matrix-appservice-irc"; path = "/var/lib/matrix-appservice-irc"; } { user = "matrix-appservice-irc"; group = "matrix-appservice-irc"; path = "/var/lib/matrix-appservice-irc"; }
]; ];
@@ -141,7 +141,6 @@ in
sasl = false; sasl = false;
# notable channels: # notable channels:
# - #hare # - #hare
# - #mnt-reform
}; };
"irc.myanonamouse.net" = ircServer { "irc.myanonamouse.net" = ircServer {
name = "MyAnonamouse"; name = "MyAnonamouse";

View File

@@ -2,7 +2,7 @@
# - <https://github.com/mautrix/signal/blob/master/mautrix_signal/example-config.yaml> # - <https://github.com/mautrix/signal/blob/master/mautrix_signal/example-config.yaml>
{ config, pkgs, ... }: { config, pkgs, ... }:
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
{ user = "mautrix-signal"; group = "mautrix-signal"; path = "/var/lib/mautrix-signal"; } { user = "mautrix-signal"; group = "mautrix-signal"; path = "/var/lib/mautrix-signal"; }
{ user = "signald"; group = "signald"; path = "/var/lib/signald"; } { user = "signald"; group = "signald"; path = "/var/lib/signald"; }
]; ];

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, ... }:
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
{ user = "navidrome"; group = "navidrome"; path = "/var/lib/navidrome"; } { user = "navidrome"; group = "navidrome"; path = "/var/lib/navidrome"; }
]; ];
services.navidrome.enable = true; services.navidrome.enable = true;

View File

@@ -0,0 +1,67 @@
# docs:
# - <https://nixos.wiki/wiki/NFS>
# - <https://wiki.gentoo.org/wiki/Nfs-utils>
{ ... }:
{
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
# - mountpoint[=/path]: only export the directory if it's a mountpoint. used to avoid exporting failed mounts.
#
# 10.0.0.0/8 to export (readonly) both to LAN (unencrypted) and wg vpn (encrypted)
services.nfs.server.exports = ''
/var/nfs/export 10.78.79.0/22(ro,crossmnt,fsid=0,subtree_check) 10.0.10.0/24(rw,no_root_squash,crossmnt,fsid=0,subtree_check)
'';
fileSystems."/var/nfs/export/media" = {
# everything in here could be considered publicly readable (based on the viewer's legal jurisdiction)
device = "/var/lib/uninsane/media";
options = [ "rbind" ];
};
}

View File

@@ -1,5 +1,4 @@
# docs: <https://nixos.wiki/wiki/Nginx> # docs: https://nixos.wiki/wiki/Nginx
# docs: <https://nginx.org/en/docs/>
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
@@ -134,7 +133,7 @@ in
security.acme.acceptTerms = true; security.acme.acceptTerms = true;
security.acme.defaults.email = "admin.acme@uninsane.org"; security.acme.defaults.email = "admin.acme@uninsane.org";
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
# TODO: mode? # TODO: mode?
{ user = "acme"; group = "acme"; path = "/var/lib/acme"; } { user = "acme"; group = "acme"; path = "/var/lib/acme"; }
{ user = "colin"; group = "users"; path = "/var/www/sites"; } { user = "colin"; group = "users"; path = "/var/www/sites"; }

View File

@@ -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;
};
}

View File

@@ -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";
};
}

View File

@@ -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()

View File

@@ -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);
};
}

View File

@@ -5,7 +5,7 @@ let
cfg = config.services.pict-rs; cfg = config.services.pict-rs;
in in
{ {
sane.persist.sys.byStore.plaintext = lib.mkIf cfg.enable [ sane.persist.sys.plaintext = lib.mkIf cfg.enable [
{ user = "pict-rs"; group = "pict-rs"; path = cfg.dataDir; } { user = "pict-rs"; group = "pict-rs"; path = cfg.dataDir; }
]; ];

View File

@@ -14,7 +14,7 @@ let
# logLevel = "debug"; # logLevel = "debug";
in in
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
{ user = "pleroma"; group = "pleroma"; path = "/var/lib/pleroma"; } { user = "pleroma"; group = "pleroma"; path = "/var/lib/pleroma"; }
]; ];
services.pleroma.enable = true; services.pleroma.enable = true;
@@ -63,7 +63,6 @@ in
database: "pleroma", database: "pleroma",
hostname: "localhost", hostname: "localhost",
pool_size: 10, pool_size: 10,
prepare: :named,
parameters: [ parameters: [
plan_cache_mode: "force_custom_plan" plan_cache_mode: "force_custom_plan"
] ]

View File

@@ -1,39 +1,12 @@
{ pkgs, ... }: { ... }:
let
GiB = n: MiB 1024*n;
MiB = n: KiB 1024*n;
KiB = n: 1024*n;
in
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
# TODO: mode? # TODO: mode?
{ user = "postgres"; group = "postgres"; path = "/var/lib/postgresql"; } { user = "postgres"; group = "postgres"; path = "/var/lib/postgresql"; }
]; ];
services.postgresql.enable = true; services.postgresql.enable = true;
# services.postgresql.dataDir = "/opt/postgresql/13";
# 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. # XXX colin: for a proper deploy, we'd want to include something for Pleroma here too.
# services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" '' # services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" ''
# CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD '<password goes here>'; # CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD '<password goes here>';
@@ -44,33 +17,10 @@ in
# LC_CTYPE = "C"; # LC_CTYPE = "C";
# ''; # '';
# perf tuning # TODO: perf tuning
# - for recommended values see: <https://pgtune.leopard.in.ua/> # - for recommended values see: <https://pgtune.leopard.in.ua/>
# - for official docs (sparse), see: <https://www.postgresql.org/docs/11/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE> # - for official docs (sparse), see: <https://www.postgresql.org/docs/11/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE>
services.postgresql.settings = { # 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 # daily backups to /var/backup
services.postgresqlBackup.enable = true; services.postgresqlBackup.enable = true;

View File

@@ -0,0 +1,81 @@
# example configs:
# - <https://github.com/kittywitch/nixfiles/blob/main/services/prosody.nix>
# create users with:
# - `sudo -u prosody prosodyctl adduser colin@uninsane.org`
{ lib, ... }:
# XXX disabled: doesn't send messages to nixnet.social (only receives them).
# nixnet runs ejabberd, so revisiting that.
lib.mkIf false
{
sane.persist.sys.plaintext = [
{ user = "prosody"; group = "prosody"; path = "/var/lib/prosody"; }
];
sane.ports.ports."5222" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-client-to-server";
};
sane.ports.ports."5269" = {
protocol = [ "tcp" ];
visibleTo.wan = true;
description = "colin-xmpp-server-to-server";
};
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?
};
# provide access to certs
users.users.prosody.extraGroups = [ "nginx" ];
security.acme.certs."uninsane.org".extraDomainNames = [
"conference.xmpp.uninsane.org"
"upload.xmpp.uninsane.org"
];
services.prosody = {
enable = true;
admins = [ "colin@uninsane.org" ];
# allowRegistration = false;
# extraConfig = ''
# s2s_require_encryption = true
# c2s_require_encryption = true
# '';
extraModules = [ "private" "vcard" "privacy" "compression" "component" "muc" "pep" "adhoc" "lastactivity" "admin_adhoc" "blocklist"];
ssl.cert = "/var/lib/acme/uninsane.org/fullchain.pem";
ssl.key = "/var/lib/acme/uninsane.org/key.pem";
muc = [
{
domain = "conference.xmpp.uninsane.org";
}
];
uploadHttp.domain = "upload.xmpp.uninsane.org";
virtualHosts = {
localhost = {
domain = "localhost";
enabled = true;
};
"xmpp.uninsane.org" = {
domain = "uninsane.org";
enabled = true;
ssl.cert = "/var/lib/acme/uninsane.org/fullchain.pem";
ssl.key = "/var/lib/acme/uninsane.org/key.pem";
};
};
};
}

View File

@@ -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
'';
};
}

View File

@@ -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);

View File

@@ -1,27 +1,12 @@
{ config, pkgs, ... }: { pkgs, ... }:
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
# TODO: mode? we need this specifically for the stats tracking in .config/ # TODO: mode? we need this specifically for the stats tracking in .config/
{ user = "transmission"; group = config.users.users.transmission.group; path = "/var/lib/transmission"; } { user = "transmission"; group = "transmission"; path = "/var/lib/transmission"; }
]; ];
users.users.transmission.extraGroups = [ "media" ];
services.transmission.enable = true; services.transmission.enable = true;
services.transmission.package = pkgs.transmission_4; #< 2023/09/06: nixpkgs `transmission` defaults to old 3.00
#v setting `group` this way doesn't tell transmission to `chown` the files it creates
# it's a nixpkgs setting which just runs the transmission daemon as this group
services.transmission.group = "media";
# transmission will by default not allow the world to read its files.
services.transmission.downloadDirPermissions = "775";
services.transmission.extraFlags = [
"--log-level=debug"
];
services.transmission.settings = { services.transmission.settings = {
# message-level = 3; #< enable for debug logging. 0-3, default is 2.
# 0.0.0.0 => allow rpc from any host: we gate it via firewall and auth requirement
rpc-bind-address = "0.0.0.0"; rpc-bind-address = "0.0.0.0";
#rpc-host-whitelist = "bt.uninsane.org"; #rpc-host-whitelist = "bt.uninsane.org";
#rpc-whitelist = "*.*.*.*"; #rpc-whitelist = "*.*.*.*";
@@ -32,8 +17,9 @@
rpc-password = "{503fc8928344f495efb8e1f955111ca5c862ce0656SzQnQ5"; rpc-password = "{503fc8928344f495efb8e1f955111ca5c862ce0656SzQnQ5";
rpc-whitelist-enabled = false; rpc-whitelist-enabled = false;
# download-dir = "/opt/uninsane/media/";
# hopefully, make the downloads world-readable # hopefully, make the downloads world-readable
# umask = 0; #< default is 2: i.e. deny writes from world umask = 0;
# force peer connections to be encrypted # force peer connections to be encrypted
encryption = 2; encryption = 2;
@@ -49,18 +35,17 @@
download-dir = "/var/lib/uninsane/media"; download-dir = "/var/lib/uninsane/media";
incomplete-dir = "/var/lib/uninsane/media/incomplete"; incomplete-dir = "/var/lib/uninsane/media/incomplete";
# transmission regularly fails to move stuff from the incomplete dir to the main one, so disable:
# TODO: uncomment this line!
incomplete-dir-enabled = false;
}; };
# transmission will by default not allow the world to read its files.
services.transmission.downloadDirPermissions = "775";
systemd.services.transmission.after = [ "wireguard-wg-ovpns.service" ]; systemd.services.transmission.after = [ "wireguard-wg-ovpns.service" ];
systemd.services.transmission.partOf = [ "wireguard-wg-ovpns.service" ]; systemd.services.transmission.partOf = [ "wireguard-wg-ovpns.service" ];
systemd.services.transmission.serviceConfig = { systemd.services.transmission.serviceConfig = {
# run this behind the OVPN static VPN # run this behind the OVPN static VPN
NetworkNamespacePath = "/run/netns/ovpns"; NetworkNamespacePath = "/run/netns/ovpns";
Restart = "on-failure"; LogLevelMax = "warning";
RestartSec = "30s";
}; };
# service to automatically backup torrents i add to transmission # service to automatically backup torrents i add to transmission

View File

@@ -1,13 +1,14 @@
# TODO: split this file apart into smaller files to make it easier to understand
{ config, lib, pkgs, ... }: { 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; services.trust-dns.enable = true;
services.trust-dns.settings.listen_addrs_ipv4 = [
# specify each address explicitly, instead of using "*".
# this ensures responses are sent from the address at which the request was received.
config.sane.hosts.by-name."servo".lan-ip
"10.0.1.5"
];
# don't bind to IPv6 until i explicitly test that stack # don't bind to IPv6 until i explicitly test that stack
services.trust-dns.settings.listen_addrs_ipv6 = []; services.trust-dns.settings.listen_addrs_ipv6 = [];
services.trust-dns.quiet = true; services.trust-dns.quiet = true;
@@ -17,7 +18,6 @@ in lib.mkMerge [
protocol = [ "udp" "tcp" ]; protocol = [ "udp" "tcp" ];
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true; visibleTo.wan = true;
visibleTo.ovpn = true;
description = "colin-dns-hosting"; description = "colin-dns-hosting";
}; };
@@ -34,19 +34,18 @@ in lib.mkMerge [
sane.dns.zones."uninsane.org".inet = { sane.dns.zones."uninsane.org".inet = {
SOA."@" = '' SOA."@" = ''
ns1.uninsane.org. admin-dns.uninsane.org. ( ns1.uninsane.org. admin-dns.uninsane.org. (
2023092101 ; Serial 2022122101 ; Serial
4h ; Refresh 4h ; Refresh
30m ; Retry 30m ; Retry
7d ; Expire 7d ; Expire
5m) ; Negative response TTL 5m) ; Negative response TTL
''; '';
TXT."rev" = "2023092101"; TXT."rev" = "2023052901";
CNAME."native" = "%CNAMENATIVE%"; CNAME."native" = "%CNAMENATIVE%";
A."@" = "%ANATIVE%"; A."@" = "%ANATIVE%";
A."servo.wan" = "%AWAN%"; A."wan" = "%AWAN%";
A."servo.lan" = config.sane.hosts.by-name."servo".lan-ip; 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 # 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. # it's best that we keep this identical, or a superset of, what org. lists as our NS.
@@ -64,23 +63,55 @@ in lib.mkMerge [
services.trust-dns.settings.zones = [ "uninsane.org" ]; services.trust-dns.settings.zones = [ "uninsane.org" ];
# TODO: can i transform this into some sort of service group? services.trust-dns.package =
# have `systemctl restart trust-dns.service` restart all the individual services? let
sed = "${pkgs.gnused}/bin/sed";
zone-dir = "/var/lib/trust-dns";
zone-wan = "${zone-dir}/wan/uninsane.org.zone";
zone-lan = "${zone-dir}/lan/uninsane.org.zone";
zone-template = pkgs.writeText "uninsane.org.zone.in" config.sane.dns.zones."uninsane.org".rendered;
in pkgs.writeShellScriptBin "named" ''
# compute wan/lan values
mkdir -p ${zone-dir}/{ovpn,wan,lan}
wan=$(cat '${config.sane.services.dyn-dns.ipPath}')
lan=${config.sane.hosts.by-name."servo".lan-ip}
# create specializations that resolve native.uninsane.org to different CNAMEs
${sed} s/%AWAN%/$wan/ ${zone-template} \
| ${sed} s/%CNAMENATIVE%/wan/ \
| ${sed} s/%ANATIVE%/$wan/ \
> ${zone-wan}
${sed} s/%AWAN%/$wan/ ${zone-template} \
| ${sed} s/%CNAMENATIVE%/servo.lan/ \
| ${sed} s/%ANATIVE%/$lan/ \
> ${zone-lan}
# launch the different interfaces, separately
${pkgs.trust-dns}/bin/named --port 53 --zonedir ${zone-dir}/wan/ $@ &
WANPID=$!
${pkgs.trust-dns}/bin/named --port 1053 --zonedir ${zone-dir}/lan/ $@ &
LANPID=$!
# wait until any of the processes exits, then kill them all and exit error
while kill -0 $WANPID $LANPID ; do
sleep 5
done
kill $WANPID $LANPID
exit 1
'';
systemd.services.trust-dns.serviceConfig = { systemd.services.trust-dns.serviceConfig = {
DynamicUser = lib.mkForce false; DynamicUser = lib.mkForce false;
User = "trust-dns"; User = "trust-dns";
Group = "trust-dns"; Group = "trust-dns";
wantedBy = lib.mkForce [];
}; };
systemd.services.trust-dns.enable = false;
users.groups.trust-dns = {}; users.groups.trust-dns = {};
users.users.trust-dns = { users.users.trust-dns = {
group = "trust-dns"; group = "trust-dns";
isSystemUser = true; isSystemUser = true;
}; };
# sane.services.dyn-dns.restartOnChange = [ "trust-dns.service" ]; sane.services.dyn-dns.restartOnChange = [ "trust-dns.service" ];
networking.nat.enable = true; networking.nat.enable = true;
networking.nat.extraCommands = '' networking.nat.extraCommands = ''
@@ -96,113 +127,12 @@ in lib.mkMerge [
-m iprange --src-range 10.78.76.0-10.78.79.255 \ -m iprange --src-range 10.78.76.0-10.78.79.255 \
-j DNAT --to-destination :1053 -j DNAT --to-destination :1053
''; '';
sane.ports.ports."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. # 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: 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" ]; protocol = [ "udp" "tcp" ];
visibleTo.lan = true; visibleTo.lan = true;
description = "colin-redirected-dns-for-lan-namespace"; 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
];
}
]

View File

@@ -3,9 +3,8 @@
imports = [ imports = [
./feeds.nix ./feeds.nix
./fs.nix ./fs.nix
./hardware ./hardware.nix
./home ./home
./hosts.nix
./ids.nix ./ids.nix
./machine-id.nix ./machine-id.nix
./net.nix ./net.nix
@@ -41,14 +40,6 @@
# does the builder use some content-addressed db to efficiently dedupe? # does the builder use some content-addressed db to efficiently dedupe?
nix.settings.auto-optimise-store = true; nix.settings.auto-optimise-store = true;
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 = { systemd.services.nix-daemon.serviceConfig = {
# the nix-daemon manages nix builders # the nix-daemon manages nix builders
# kill nix-daemon subprocesses when systemd-oomd detects an out-of-memory condition # kill nix-daemon subprocesses when systemd-oomd detects an out-of-memory condition

View File

@@ -1,5 +1,4 @@
# where to find good stuff? # where to find good stuff?
# - podcasts w/ a community: <https://lemmyverse.net/communities?query=podcast>
# - podcast rec thread: <https://lemmy.ml/post/1565858> # - podcast rec thread: <https://lemmy.ml/post/1565858>
# #
# candidates: # candidates:
@@ -57,56 +56,75 @@ let
}; };
podcasts = [ 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 "lexfridman.com/podcast" // rat)
(fromDb "mapspodcast.libsyn.com" // uncat) # Multidisciplinary Association for Psychedelic Studies ## Astral Codex Ten
(fromDb "omegataupodcast.net" // tech) # 3/4 German; 1/4 eps are English (fromDb "sscpodcast.libsyn.com" // rat)
(fromDb "omny.fm/shows/cool-people-who-did-cool-stuff" // pol) # Maggie Killjoy -- referenced by Cory Doctorow ## Less Wrong Curated
(fromDb "podcast.posttv.com/itunes/post-reports.xml" // pol) (fromDb "feeds.libsyn.com/421877" // rat)
(fromDb "podcast.thelinuxexp.com" // tech) ## Econ Talk
(fromDb "politicalorphanage.libsyn.com" // pol) (fromDb "feeds.simplecast.com/wgl4xEgL" // rat)
(fromDb "reverseengineering.libsyn.com/rss" // tech) # UnNamed Reverse Engineering Podcast ## Cory Doctorow -- both podcast & text entries
(fromDb "rss.acast.com/deconstructed") # The Intercept - Deconstructed (fromDb "craphound.com" // pol)
(fromDb "rss.acast.com/ft-tech-tonic" // tech) ## Maggie Killjoy -- referenced by Cory Doctorow
(fromDb "rss.acast.com/intercepted-with-jeremy-scahill") # The Intercept - Intercepted (fromDb "omny.fm/shows/cool-people-who-did-cool-stuff" // pol)
(fromDb "rss.art19.com/60-minutes" // pol) (fromDb "congressionaldish.libsyn.com" // 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) # (mkPod "https://podcasts.la.utexas.edu/this-is-democracy/feed/podcast/" // pol // weekly)
## Civboot -- https://anchor.fm/civboot
(fromDb "anchor.fm/s/34c7232c/podcast/rss" // tech)
## Emerge: making sense of what's next -- <https://www.whatisemerging.com/emergepodcast>
(mkPod "https://anchor.fm/s/21bc734/podcast/rss" // pol // infrequent)
(fromDb "feeds.feedburner.com/80000HoursPodcast" // rat)
## Daniel Huberman on sleep
(fromDb "feeds.megaphone.fm/hubermanlab" // uncat)
## Multidisciplinary Association for Psychedelic Studies
(fromDb "mapspodcast.libsyn.com" // uncat)
(fromDb "allinchamathjason.libsyn.com" // pol)
(fromDb "feeds.transistor.fm/acquired" // tech)
## ACQ2 - more "Acquired" episodes
(fromDb "acquiredlpbonussecretsecret.libsyn.com" // tech)
# The Intercept - Deconstructed
(fromDb "rss.acast.com/deconstructed")
# (fromDb "rss.prod.firstlook.media/deconstructed/podcast.rss" // pol) #< possible URL rot
## The Daily
(mkPod "https://feeds.simplecast.com/54nAGcIl" // pol // daily)
# The Intercept - Intercepted
(fromDb "rss.acast.com/intercepted-with-jeremy-scahill")
# (fromDb "rss.prod.firstlook.media/intercepted/podcast.rss" // pol) #< possible URL rot
(fromDb "podcast.posttv.com/itunes/post-reports.xml" // pol)
## 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 "feeds.feedburner.com/dancarlin/history" // rat)
(fromDb "rss.art19.com/60-minutes" // pol)
## The Verge - Decoder
(fromDb "feeds.megaphone.fm/recodedecode" // tech)
## Matrix (chat) Live
(fromDb "feed.podbean.com/matrixlive/feed.xml" // tech)
(fromDb "cast.postmarketos.org" // tech)
(fromDb "podcast.thelinuxexp.com" // tech)
## Michael Malice - Your Welcome -- also available here: <https://origin.podcastone.com/podcast?categoryID2=2232>
(fromDb "rss.art19.com/your-welcome" // pol)
(fromDb "seattlenice.buzzsprout.com" // pol)
## Sci-Fi? has Peter Watts; author of No Moods, Ads or Cutesy Fucking Icons (rifters.com)
(fromDb "talesfromthebridge.buzzsprout.com" // tech)
## UnNamed Reverse Engineering Podcast
(fromDb "reverseengineering.libsyn.com/rss" // tech)
## The Witch Trials of J.K. Rowling
## - <https://www.thefp.com/witchtrials>
(mkPod "https://feeds.megaphone.fm/RUNMED9919162779" // pol // infrequent)
## Atlas Obscura
(fromDb "feeds.simplecast.com/xKJ93w_w" // uncat)
## Ezra Klein Show
(fromDb "feeds.simplecast.com/82FI35Px" // pol)
## Wireshark Podcast o_0
(fromDb "sharkbytes.transistor.fm" // tech)
]; ];
texts = [ texts = [
@@ -122,7 +140,7 @@ let
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent) (mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
(fromDb "tuxphones.com" // tech) (fromDb "tuxphones.com" // tech)
(fromDb "spectrum.ieee.org" // tech) (fromDb "spectrum.ieee.org" // tech)
# (fromDb "theregister.com" // tech) (fromDb "theregister.com" // tech)
(fromDb "thisweek.gnome.org" // tech) (fromDb "thisweek.gnome.org" // tech)
# more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html> # 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/announcements-rss.xml" // tech // infrequent)
@@ -138,8 +156,6 @@ let
(fromDb "uninsane.org" // tech) (fromDb "uninsane.org" // tech)
(fromDb "ascii.textfiles.com" // tech) # Jason Scott (fromDb "ascii.textfiles.com" // tech) # Jason Scott
(fromDb "xn--gckvb8fzb.com" // tech) (fromDb "xn--gckvb8fzb.com" // tech)
(fromDb "amosbbatto.wordpress.com" // tech)
(fromDb "fasterthanli.me" // tech)
(fromDb "mg.lol" // tech) (fromDb "mg.lol" // tech)
# (fromDb "drewdevault.com" // tech) # (fromDb "drewdevault.com" // tech)
## Ken Shirriff ## Ken Shirriff
@@ -150,7 +166,6 @@ let
(fromDb "vitalik.ca" // tech) (fromDb "vitalik.ca" // tech)
## ian (Sanctuary) ## ian (Sanctuary)
(fromDb "sagacioussuricata.com" // tech) (fromDb "sagacioussuricata.com" // tech)
(fromDb "artemis.sh" // tech)
## Bunnie Juang ## Bunnie Juang
(fromDb "bunniestudios.com" // tech) (fromDb "bunniestudios.com" // tech)
(fromDb "blog.danieljanus.pl" // tech) (fromDb "blog.danieljanus.pl" // tech)
@@ -161,8 +176,6 @@ let
(mkText "https://anish.lakhwara.com/home.html" // tech // weekly) (mkText "https://anish.lakhwara.com/home.html" // tech // weekly)
(fromDb "jefftk.com" // tech) (fromDb "jefftk.com" // tech)
(fromDb "pomeroyb.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) # (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
# TECH PROJECTS # TECH PROJECTS
@@ -228,7 +241,6 @@ let
images = [ images = [
(fromDb "smbc-comics.com" // img // humor) (fromDb "smbc-comics.com" // img // humor)
(fromDb "xkcd.com" // img // humor) (fromDb "xkcd.com" // img // humor)
(fromDb "turnoff.us" // img // humor)
(fromDb "pbfcomics.com" // img // humor) (fromDb "pbfcomics.com" // img // humor)
# (mkImg "http://dilbert.com/feed" // humor // daily) # (mkImg "http://dilbert.com/feed" // humor // daily)
(fromDb "poorlydrawnlines.com/feed" // img // humor) (fromDb "poorlydrawnlines.com/feed" // img // humor)

View File

@@ -1,143 +1,136 @@
# docs # docs
# - x-systemd options: <https://www.freedesktop.org/software/systemd/man/systemd.mount.html> # - x-systemd options: <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
{ lib, pkgs, sane-lib, ... }: { pkgs, sane-lib, ... }:
let let fsOpts = rec {
fsOpts = rec { common = [
common = [ "_netdev"
"_netdev" "noatime"
"noatime" "user" # allow any user with access to the device to mount the fs
"user" # allow any user with access to the device to mount the fs "x-systemd.requires=network-online.target"
"x-systemd.requires=network-online.target" "x-systemd.after=network-online.target"
"x-systemd.after=network-online.target" "x-systemd.mount-timeout=10s" # how long to wait for mount **and** how long to wait for unmount
"x-systemd.mount-timeout=10s" # how long to wait for mount **and** how long to wait for unmount ];
]; auto = [ "x-systemd.automount" ];
auto = [ "x-systemd.automount" ]; noauto = [ "noauto" ]; # don't mount as part of remote-fs.target
noauto = [ "noauto" ]; # don't mount as part of remote-fs.target wg = [
wg = [ "x-systemd.requires=wireguard-wg-home.service"
"x-systemd.requires=wireguard-wg-home.service" "x-systemd.after=wireguard-wg-home.service"
"x-systemd.after=wireguard-wg-home.service" ];
];
ssh = common ++ [ ssh = common ++ [
"identityfile=/home/colin/.ssh/id_ed25519" "identityfile=/home/colin/.ssh/id_ed25519"
"allow_other" "allow_other"
"default_permissions" "default_permissions"
]; ];
sshColin = ssh ++ [ sshColin = ssh ++ [
"transform_symlinks" "transform_symlinks"
"idmap=user" "idmap=user"
"uid=1000" "uid=1000"
"gid=100" "gid=100"
]; ];
sshRoot = ssh ++ [ sshRoot = ssh ++ [
# we don't transform_symlinks because that breaks the validity of remote /nix stores # 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" "sftp_server=/run/wrappers/bin/sudo\\040/run/current-system/sw/libexec/sftp-server"
]; ];
# in the event of hunt NFS mounts, consider: # in the event of hunt NFS mounts, consider:
# - <https://unix.stackexchange.com/questions/31979/stop-broken-nfs-mounts-from-locking-a-directory> # - <https://unix.stackexchange.com/questions/31979/stop-broken-nfs-mounts-from-locking-a-directory>
# NFS options: <https://linux.die.net/man/5/nfs> # NFS options: <https://linux.die.net/man/5/nfs>
# actimeo=n = how long (in seconds) to cache file/dir attributes (default: 3-60s) # actimeo=n = how long (in seconds) to cache file/dir attributes (default: 3-60s)
# bg = retry failed mounts in the background # bg = retry failed mounts in the background
# retry=n = for how many minutes `mount` will retry NFS mount operation # retry=n = for how many minutes `mount` will retry NFS mount operation
# soft = on "major timeout", report I/O error to userspace # 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) # 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) # 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. # note: client uses a linear backup, so the second request will have double this timeout, then triple, etc.
nfs = common ++ [ nfs = common ++ [
# "actimeo=10" # "actimeo=10"
"bg" "bg"
"retrans=4" "retrans=4"
"retry=0" "retry=0"
"soft" "soft"
"timeo=15" "timeo=15"
"nofail" # don't fail remote-fs.target when this mount fails (not an option for sshfs else would be common) "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 in
lib.mkMerge [ {
{ # some services which use private directories error if the parent (/var/lib/private) isn't 700.
# 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";
sane.fs."/var/lib/private".dir.acl.mode = "0700";
# in-memory compressed RAM # in-memory compressed RAM
# defaults to compressing at most 50% size of RAM # defaults to compressing at most 50% size of RAM
# claimed compression ratio is about 2:1 # claimed compression ratio is about 2:1
# - but on moby w/ zstd default i see 4-7:1 (ratio lowers as it fills) # - 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) # 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> # docs: <https://www.kernel.org/doc/Documentation/blockdev/zram.txt>
# #
# to query effectiveness: # to query effectiveness:
# `cat /sys/block/zram0/mm_stat`. whitespace separated fields: # `cat /sys/block/zram0/mm_stat`. whitespace separated fields:
# - *orig_data_size* (bytes) # - *orig_data_size* (bytes)
# - *compr_data_size* (bytes) # - *compr_data_size* (bytes)
# - mem_used_total (bytes) # - mem_used_total (bytes)
# - mem_limit (bytes) # - mem_limit (bytes)
# - mem_used_max (bytes) # - mem_used_max (bytes)
# - *same_pages* (pages which are e.g. all zeros (consumes no additional mem)) # - *same_pages* (pages which are e.g. all zeros (consumes no additional mem))
# - *pages_compacted* (pages which have been freed thanks to compression) # - *pages_compacted* (pages which have been freed thanks to compression)
# - huge_pages (incompressible) # - huge_pages (incompressible)
# #
# see also: # see also:
# - `man zramctl` # - `man zramctl`
zramSwap.enable = true; zramSwap.enable = true;
# how much ram can be swapped into the zram device. # how much ram can be swapped into the zram device.
# this shouldn't be higher than the observed compression ratio. # this shouldn't be higher than the observed compression ratio.
# the default is 50% (why?) # the default is 50% (why?)
# 100% should be "guaranteed" safe so long as the data is even *slightly* compressible. # 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) # 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; zramSwap.memoryPercent = 100;
# fileSystems."/mnt/servo-nfs" = { # fileSystems."/mnt/servo-nfs" = {
# device = "servo-hn:/"; # device = "servo-hn:/";
# noCheck = true; # noCheck = true;
# fsType = "nfs"; # fsType = "nfs";
# options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg; # options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg;
# }; # };
fileSystems."/mnt/servo-nfs/media" = { fileSystems."/mnt/servo-nfs/media" = {
device = "servo-hn:/media"; device = "servo-hn:/media";
noCheck = true; noCheck = true;
fsType = "nfs"; fsType = "nfs";
options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg; options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg;
}; };
fileSystems."/mnt/servo-nfs/playground" = { # fileSystems."/mnt/servo-media-nfs" = {
device = "servo-hn:/playground"; # device = "servo-hn:/media";
noCheck = true; # noCheck = true;
fsType = "nfs"; # fsType = "nfs";
options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg; # options = fsOpts.common ++ fsOpts.auto;
}; # };
# fileSystems."/mnt/servo-media-nfs" = { sane.fs."/mnt/servo-media" = sane-lib.fs.wantedSymlinkTo "/mnt/servo-nfs/media";
# 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 = [ fileSystems."/mnt/desko-home" = {
# needed to achieve superuser access for user-mounted filesystems (see optionsRoot above) device = "colin@desko:/home/colin";
# we can only link whole directories here, even though we're only interested in pkgs.openssh fsType = "fuse.sshfs";
"/libexec" options = fsOpts.sshColin ++ fsOpts.noauto;
]; noCheck = true;
};
sane.fs."/mnt/desko-home" = sane-lib.fs.wantedDir;
fileSystems."/mnt/desko-root" = {
device = "colin@desko:/";
fsType = "fuse.sshfs";
options = fsOpts.sshRoot ++ fsOpts.noauto;
noCheck = true;
};
sane.fs."/mnt/desko-root" = sane-lib.fs.wantedDir;
environment.systemPackages = [ environment.pathsToLink = [
pkgs.sshfs-fuse # 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"
];
(remoteHome "desko") environment.systemPackages = [
(remoteHome "lappy") pkgs.sshfs-fuse
(remoteHome "moby") ];
] }

44
hosts/common/hardware.nix Normal file
View File

@@ -0,0 +1,44 @@
{ 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;
# powertop will default to putting USB devices -- including HID -- to sleep after TWO SECONDS
powerManagement.powertop.enable = false;
services.logind.extraConfig = ''
# dont shutdown when power button is short-pressed
HandlePowerKey=ignore
'';
# services.snapper.configs = {
# root = {
# subvolume = "/";
# extraConfig = {
# ALLOW_USERS = "colin";
# };
# };
# };
# services.snapper.snapshotInterval = "daily";
}

View File

@@ -1,73 +0,0 @@
{ lib, pkgs, ... }:
{
imports = [
./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 = ''
# dont shutdown when power button is short-pressed
HandlePowerKey=ignore
'';
# services.snapper.configs = {
# root = {
# subvolume = "/";
# extraConfig = {
# ALLOW_USERS = "colin";
# };
# };
# };
# services.snapper.snapshotInterval = "daily";
}

View File

@@ -7,7 +7,7 @@ let
}; };
in in
{ {
sane.user.persist.byStore.private = [ ".local/share/keyrings" ]; sane.user.persist.private = [ ".local/share/keyrings" ];
sane.user.fs."private/.local/share/keyrings/default" = { sane.user.fs."private/.local/share/keyrings/default" = {
generated.command = [ "${init-keyring}/bin/init-keyring" ]; generated.command = [ "${init-keyring}/bin/init-keyring" ];

View File

@@ -13,7 +13,7 @@ let
in in
{ {
# ssh key is stored in private storage # ssh key is stored in private storage
sane.user.persist.byStore.private = [ sane.user.persist.private = [
{ type = "file"; path = ".ssh/id_ed25519"; } { type = "file"; path = ".ssh/id_ed25519"; }
]; ];
sane.user.fs.".ssh/id_ed25519.pub" = lib.mkIf (user-pubkey != null) { sane.user.fs.".ssh/id_ed25519.pub" = lib.mkIf (user-pubkey != null) {

View File

@@ -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";
};
}

View File

@@ -44,11 +44,6 @@
sane.ids.sftpgo.gid = 2410; sane.ids.sftpgo.gid = 2410;
sane.ids.trust-dns.uid = 2411; sane.ids.trust-dns.uid = 2411;
sane.ids.trust-dns.gid = 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.colin.uid = 1000;
sane.ids.guest.uid = 1100; sane.ids.guest.uid = 1100;
@@ -86,8 +81,4 @@
sane.ids.rtkit.gid = 2307; sane.ids.rtkit.gid = 2307;
# phosh # phosh
sane.ids.feedbackd.gid = 2308; sane.ids.feedbackd.gid = 2308;
# new moby users
sane.ids.eg25-control.uid = 2309;
sane.ids.eg25-control.gid = 2309;
} }

View File

@@ -5,12 +5,12 @@
# store /home/colin/a/b in /home/private/a/b instead of /home/private/home/colin/a/b # 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.stores.private.prefix = "/home/colin";
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.plaintext = [
# TODO: these should be private.. somehow # TODO: these should be private.. somehow
"/var/log" "/var/log"
"/var/backup" # for e.g. postgres dumps "/var/backup" # for e.g. postgres dumps
]; ];
sane.persist.sys.byStore.cryptClearOnBoot = [ sane.persist.sys.cryptClearOnBoot = [
"/var/lib/systemd/coredump" "/var/lib/systemd/coredump"
]; ];
} }

View File

@@ -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 }
'';
};
}

View File

@@ -1,4 +1,4 @@
{ config, lib, pkgs, ... }: { pkgs, ... }:
let let
declPackageSet = pkgs: { declPackageSet = pkgs: {
@@ -44,15 +44,13 @@ in
"sane-scripts.ssl-dump" "sane-scripts.ssl-dump"
"sane-scripts.sudo-redirect" "sane-scripts.sudo-redirect"
"sane-scripts.sync-from-servo" "sane-scripts.sync-from-servo"
"sane-scripts.vpn" "sane-scripts.vpn-down"
"sane-scripts.vpn-up"
"sane-scripts.which" "sane-scripts.which"
"sane-scripts.wipe-browser" "sane-scripts.wipe-browser"
"sane-scripts.wipe-flare"
"sane-scripts.wipe-fractal"
]; ];
"sane-scripts.sys-utils" = declPackageSet [ "sane-scripts.sys-utils" = declPackageSet [
"sane-scripts.ip-port-forward" "sane-scripts.ip-port-forward"
"sane-scripts.sync-music"
]; ];
@@ -60,27 +58,21 @@ in
"btrfs-progs" "btrfs-progs"
"cacert.unbundled" # some services require unbundled /etc/ssl/certs "cacert.unbundled" # some services require unbundled /etc/ssl/certs
"cryptsetup" "cryptsetup"
"ddrescue"
"dig" "dig"
"dtc" # device tree [de]compiler
"e2fsprogs" # resize2fs
"efibootmgr" "efibootmgr"
"ethtool"
"fatresize" "fatresize"
"fd" "fd"
"file" "file"
# "fwupd" # "fwupd"
"gawk" "gawk"
"gdb" # to debug segfaults
"git" "git"
"gptfdisk" # gdisk "gptfdisk"
"hdparm" "hdparm"
"htop" "htop"
"iftop" "iftop"
"inetutils" # for telnet "inetutils" # for telnet
"iotop" "iotop"
"iptables" "iptables"
"iw"
"jq" "jq"
"killall" "killall"
"lsof" "lsof"
@@ -91,7 +83,6 @@ in
"netcat" "netcat"
"nethogs" "nethogs"
"nmap" "nmap"
"nvme-cli" # nvme
"openssl" "openssl"
"parted" "parted"
"pciutils" "pciutils"
@@ -99,14 +90,13 @@ in
"pstree" "pstree"
"ripgrep" "ripgrep"
"screen" "screen"
"smartmontools" # smartctl "smartmontools"
"socat" "socat"
"strace" "strace"
"subversion" "subversion"
"tcpdump" "tcpdump"
"tree" "tree"
"usbutils" "usbutils"
"util-linux" # lsblk, lscpu, etc
"wget" "wget"
"wirelesstools" # iwlist "wirelesstools" # iwlist
]; ];
@@ -124,13 +114,11 @@ in
# - debugging? # - debugging?
consoleUtils = declPackageSet [ consoleUtils = declPackageSet [
"alsaUtils" # for aplay, speaker-test "alsaUtils" # for aplay, speaker-test
"binutils-unwrapped" # for strings; though this brings 80MB of unrelated baggage too
# "cdrtools" # "cdrtools"
"clinfo" "clinfo"
"dmidecode" "dmidecode"
"dtrx" # `unar` alternative, "Do The Right eXtraction" "dtrx" # `unar` alternative, "Do The Right eXtraction"
"efivar" "efivar"
"eza" # a better 'ls'
# "flashrom" # "flashrom"
"git" # needed as a user package, for config. "git" # needed as a user package, for config.
# "gnupg" # "gnupg"
@@ -138,11 +126,11 @@ in
# "gopass" # "gopass"
# "gopass-jsonapi" # "gopass-jsonapi"
"helix" # text editor "helix" # text editor
"kitty" # TODO: move to GUI, but `ssh servo` from kitty sets `TERM=xterm-kitty` in the remove and breaks things
"libsecret" # for managing user keyrings. TODO: what needs this? lift into the consumer "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 "lm_sensors" # for sensors-detect. TODO: what needs this? lift into the consumer
"lshw" "lshw"
# "memtester" # "memtester"
"mercurial" # hg
"neovim" # needed as a user package, for swap persistence "neovim" # needed as a user package, for swap persistence
# "nettools" # "nettools"
# "networkmanager" # "networkmanager"
@@ -152,7 +140,7 @@ in
# "oathToolkit" # for oathtool # "oathToolkit" # for oathtool
# "ponymix" # "ponymix"
"pulsemixer" "pulsemixer"
"python3-repl" "python3"
# "python3Packages.eyeD3" # music tagging # "python3Packages.eyeD3" # music tagging
"ripgrep" # needed as a user package so that its user-level config file can be installed "ripgrep" # needed as a user package so that its user-level config file can be installed
"rsync" "rsync"
@@ -165,8 +153,8 @@ in
"sudo" "sudo"
# "tageditor" # music tagging # "tageditor" # music tagging
# "unar" # "unar"
"unzip"
"wireguard-tools" "wireguard-tools"
"xdg-terminal-exec"
"xdg-utils" # for xdg-open "xdg-utils" # for xdg-open
# "yarn" # "yarn"
"zsh" "zsh"
@@ -204,74 +192,63 @@ in
]; ];
devPkgs = declPackageSet [ devPkgs = declPackageSet [
"cargo"
"clang" "clang"
"lua"
"nodejs" "nodejs"
"patchelf"
"rustc"
"tree-sitter" "tree-sitter"
]; ];
# INDIVIDUAL PACKAGE DEFINITIONS # INDIVIDUAL PACKAGE DEFINITIONS
cargo.persist.byStore.plaintext = [ ".cargo" ]; dino.persist.private = [ ".local/share/dino" ];
# creds, but also 200 MB of node modules, etc # creds, but also 200 MB of node modules, etc
discord.persist.byStore.private = [ ".config/discord" ]; discord.persist.private = [ ".config/discord" ];
# `emote` will show a first-run dialog based on what's in this directory. # `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. # 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. # TODO: package [smile](https://github.com/mijorus/smile) for probably a better mobile experience.
emote.persist.byStore.plaintext = [ ".local/share/Emote" ]; emote.persist.plaintext = [ ".local/share/Emote" ];
fluffychat-moby.persist.byStore.plaintext = [ ".local/share/chat.fluffy.fluffychat" ]; fluffychat-moby.persist.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 # 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 # TODO: we can populate gh's stuff statically; it even lets us use the same oauth across machines
gh.persist.byStore.private = [ ".config/gh" ]; gh.persist.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) # 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? # 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" ]; monero-gui.persist.plaintext = [ ".bitmonero" ];
mumble.persist.byStore.private = [ ".local/share/Mumble" ]; mumble.persist.private = [ ".local/share/Mumble" ];
# settings (electron app) # settings (electron app)
obsidian.persist.byStore.plaintext = [ ".config/obsidian" ]; obsidian.persist.plaintext = [ ".config/obsidian" ];
python3-repl.package = pkgs.python3.withPackages (ps: with ps; [
requests
]);
# creds, media # creds, media
signal-desktop.persist.byStore.private = [ ".config/Signal" ]; signal-desktop.persist.private = [ ".config/Signal" ];
# printer/filament settings # printer/filament settings
slic3r.persist.byStore.plaintext = [ ".Slic3r" ]; slic3r.persist.plaintext = [ ".Slic3r" ];
tdesktop.persist.byStore.private = [ ".local/share/TelegramDesktop" ]; # creds, widevine .so download. TODO: could easily manage these statically.
spotify.persist.plaintext = [ ".config/spotify" ];
tokodon.persist.byStore.private = [ ".cache/KDE/tokodon" ]; tdesktop.persist.private = [ ".local/share/TelegramDesktop" ];
whalebird.persist.byStore.private = [ ".config/Whalebird" ]; tokodon.persist.private = [ ".cache/KDE/tokodon" ];
yarn.persist.byStore.plaintext = [ ".cache/yarn" ]; # hardenedMalloc solves an "unable to connect to Tor" error when pressing the "connect" button
# - still required as of 2023/07/14
tor-browser-bundle-bin.package = pkgs.tor-browser-bundle-bin.override {
useHardenedMalloc = false;
};
whalebird.persist.private = [ ".config/Whalebird" ];
yarn.persist.plaintext = [ ".cache/yarn" ];
# zcash coins. safe to delete, just slow to regenerate (10-60 minutes) # zcash coins. safe to delete, just slow to regenerate (10-60 minutes)
zecwallet-lite.persist.byStore.private = [ ".zcash" ]; zecwallet-lite.persist.private = [ ".zcash" ];
};
programs.feedbackd = lib.mkIf config.sane.programs.feedbackd.enabled {
enable = true;
}; };
} }

View File

@@ -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}"
'';
});
};
}

View File

@@ -1,9 +0,0 @@
{ ... }:
{
sane.programs.brave = {
persist.byStore.cryptClearOnBoot = [
".cache/BraveSoftware"
".config/BraveSoftware"
];
};
}

View File

@@ -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;
};
}

View File

@@ -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" ];
};
}

View File

@@ -24,7 +24,7 @@ let
# frees us from webkit_4_1, in turn. # frees us from webkit_4_1, in turn.
enableBackend = false; enableBackend = false;
gvfs = pkgs.gvfs.override { gvfs = pkgs.gvfs.override {
# saves 20 minutes of build time and cross issues, for unused feature # saves 20 minutes of build time, for unused feature
samba = null; samba = null;
}; };
}; };
@@ -36,11 +36,9 @@ in
# package = chattyNoOauth; # package = chattyNoOauth;
package = chatty-latest; package = chatty-latest;
suggestedPrograms = [ "gnome-keyring" ]; suggestedPrograms = [ "gnome-keyring" ];
persist.byStore.private = [ persist.private = [
".local/share/chatty" # matrix avatars and files ".local/share/chatty" # matrix avatars and files
# not just XMPP; without this Chatty will regenerate its device-id every boot. # ".purple" # XMPP stuff
# .purple/ contains XMPP *and* Matrix auth, logs, avatar cache, and a bit more
".purple"
]; ];
}; };
} }

View File

@@ -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"'';
};
};
}

View File

@@ -3,7 +3,7 @@
{ {
sane.programs.cozy = { sane.programs.cozy = {
# cozy uses a sqlite db for its config and exposes no CLI options other than --help and --debug # cozy uses a sqlite db for its config and exposes no CLI options other than --help and --debug
persist.byStore.plaintext = [ persist.plaintext = [
".local/share/cozy" # sqlite db (config & index?) ".local/share/cozy" # sqlite db (config & index?)
".cache/cozy" # offline cache ".cache/cozy" # offline cache
]; ];

View File

@@ -3,66 +3,45 @@
{ {
imports = [ imports = [
./aerc.nix ./aerc.nix
./alacritty.nix
./assorted.nix ./assorted.nix
./bemenu.nix
./brave.nix
./calls.nix
./cantata.nix
./chatty.nix ./chatty.nix
./conky
./cozy.nix ./cozy.nix
./dino.nix
./element-desktop.nix ./element-desktop.nix
./epiphany.nix ./epiphany.nix
./evince.nix ./evince.nix
./feedbackd.nix
./firefox.nix ./firefox.nix
./flare-signal.nix
./fontconfig.nix ./fontconfig.nix
./fractal.nix ./fractal.nix
./fwupd.nix ./fwupd.nix
./g4music.nix
./gajim.nix
./geary.nix
./git.nix ./git.nix
./gnome-feeds.nix ./gnome-feeds.nix
./gnome-keyring.nix ./gnome-keyring.nix
./gnome-weather.nix
./gpodder.nix ./gpodder.nix
./gthumb.nix ./gthumb.nix
./helix.nix ./helix.nix
./imagemagick.nix ./imagemagick.nix
./jellyfin-media-player.nix ./jellyfin-media-player.nix
./kitty
./komikku.nix ./komikku.nix
./koreader ./koreader
./libreoffice.nix ./libreoffice.nix
./lemoa.nix ./lemoa.nix
./mako.nix
./megapixels.nix ./megapixels.nix
./mepo.nix ./mepo.nix
./mopidy.nix
./mpv.nix ./mpv.nix
./msmtp.nix ./msmtp.nix
./neovim.nix ./neovim.nix
./newsflash.nix ./newsflash.nix
./nheko.nix ./nheko.nix
./nix-index.nix ./nix-index.nix
./ntfy-sh.nix
./obsidian.nix ./obsidian.nix
./offlineimap.nix ./offlineimap.nix
./playerctl.nix
./rhythmbox.nix
./ripgrep.nix ./ripgrep.nix
./sfeed.nix ./sfeed.nix
./splatmoji.nix ./splatmoji.nix
./spotify.nix
./steam.nix ./steam.nix
./stepmania.nix
./sublime-music.nix ./sublime-music.nix
./swaynotificationcenter.nix
./tangram.nix ./tangram.nix
./tor-browser-bundle-bin.nix
./tuba.nix ./tuba.nix
./vlc.nix ./vlc.nix
./wireshark.nix ./wireshark.nix

View File

@@ -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";
};
};
}

View File

@@ -1,14 +1,8 @@
# 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 = { sane.programs.element-desktop = {
# creds/session keys, etc # creds/session keys, etc
persist.byStore.private = [ ".config/Element" ]; persist.private = [ ".config/Element" ];
suggestedPrograms = [ "gnome-keyring" ]; suggestedPrograms = [ "gnome-keyring" ];
}; };

View File

@@ -8,7 +8,7 @@
{ pkgs, ... }: { pkgs, ... }:
{ {
sane.programs.epiphany = { sane.programs.epiphany = {
# XXX(2023/07/08): running on moby without `WEBKIT_DISABLE_SANDBOX...` fails, with: # XXX(2023/07/08): running on moby without this hack fails, with:
# - `bwrap: Can't make symlink at /var/run: File exists` # - `bwrap: Can't make symlink at /var/run: File exists`
# this could be due to: # this could be due to:
# - epiphany is somewhere following a symlink into /var/run instead of /run # - epiphany is somewhere following a symlink into /var/run instead of /run
@@ -19,9 +19,6 @@
# - <https://gitlab.gnome.org/GNOME/gnome-builder/-/issues/1164> # - <https://gitlab.gnome.org/GNOME/gnome-builder/-/issues/1164>
# - <https://github.com/flatpak/flatpak/issues/3477> # - <https://github.com/flatpak/flatpak/issues/3477>
# - <https://github.com/NixOS/nixpkgs/issues/197085> # - <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: { package = pkgs.epiphany.overrideAttrs (upstream: {
preFixup = '' preFixup = ''
gappsWrapperArgs+=( gappsWrapperArgs+=(
@@ -29,7 +26,7 @@
); );
'' + (upstream.preFixup or ""); '' + (upstream.preFixup or "");
}); });
persist.byStore.private = [ persist.private = [
".cache/epiphany" ".cache/epiphany"
".local/share/epiphany" ".local/share/epiphany"
# also .config/epiphany, but appears empty # also .config/epiphany, but appears empty

View File

@@ -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 = {};
};
}

View File

@@ -225,12 +225,6 @@ in
// treat it as unrevoked. // treat it as unrevoked.
// see: <https://librewolf.net/docs/faq/#im-getting-sec_error_ocsp_server_error-what-can-i-do> // see: <https://librewolf.net/docs/faq/#im-getting-sec_error_ocsp_server_error-what-can-i-do>
defaultPref("security.OCSP.require", false); 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 = {}; 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). # instruct Firefox to put the profile in a predictable directory (so we can do things like persist just it).

View File

@@ -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/`.
];
};
}

View File

@@ -1,68 +1,15 @@
# to preview fonts:
# - `font-manager` (gui)
# - useful to determine official name; codepoint support
{ config, lib, pkgs, ... }: { 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 { fonts = lib.mkIf config.sane.programs.fontconfig.enabled {
fontconfig.enable = true; fontconfig.enable = true;
fontconfig.defaultFonts = { fontconfig.defaultFonts = {
emoji = [ emoji = [ "Font Awesome 6 Free" "Noto Color Emoji" ];
"Noto Color Emoji" monospace = [ "Hack" ];
"Font Awesome 6 Free" serif = [ "DejaVu Serif" ];
"Font Awesome 6 Brands" sansSerif = [ "DejaVu Sans" ];
];
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 #vvv enables dejavu_fonts, freefont_ttf, gyre-fonts, liberation_ttf, unifont, noto-fonts-emoji
enableDefaultPackages = false; enableDefaultPackages = true;
packages = with pkgs; [ packages = with pkgs; [ font-awesome noto-fonts-emoji hack-font ];
# 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;
}; };
} }

View File

@@ -1,60 +1,12 @@
# Fractal: GTK4 instant messenger client for the Matrix protocol { pkgs, ... }:
#
# 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 = { sane.programs.fractal = {
package = pkgs.fractal-nixified;
# package = pkgs.fractal-latest; # package = pkgs.fractal-latest;
# package = pkgs.fractal-next; package = pkgs.fractal-next;
configOption = with lib; mkOption { # XXX by default fractal stores its state in ~/.local/share/stable/<UUID>.
default = {}; persist.private = [ ".local/share/stable" ];
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" ]; 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";
};
}; };
} }

View File

@@ -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"
];
};
}

View File

@@ -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"
];
};
}

View File

@@ -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
'';
};
}

View File

@@ -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"
];
};
}

View File

@@ -1,5 +1,4 @@
# help: # gnome feeds RSS viewer
# - #gpodder on irc.libera.chat
{ config, pkgs, sane-lib, ... }: { config, pkgs, sane-lib, ... }:
let let
@@ -8,22 +7,12 @@ let
wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds; wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds;
in { in {
sane.programs.gpodder = { sane.programs.gpodder = {
package = pkgs.gpodder-adaptive-configured.overrideAttrs (base: { package = pkgs.gpodder-adaptive-configured;
# 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; # package = pkgs.gpodder-configured;
fs.".config/gpodderFeeds.opml".symlink.text = feeds.feedsToOpml wanted-feeds; fs.".config/gpodderFeeds.opml".symlink.text = feeds.feedsToOpml wanted-feeds;
# XXX: we preserve the whole thing because if we only preserve gPodder/Downloads # 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. # 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" ]; persist.plaintext = [ "gPodder" ];
}; };
} }

View File

@@ -4,12 +4,10 @@
# compile without webservices to avoid the expensive webkitgtk dependency # compile without webservices to avoid the expensive webkitgtk dependency
package = pkgs.gthumb.override { withWebservices = false; }; package = pkgs.gthumb.override { withWebservices = false; };
mime.associations = { mime.associations = {
"image/gif" = "org.gnome.gThumb.desktop";
"image/heif" = "org.gnome.gThumb.desktop"; # apple codec "image/heif" = "org.gnome.gThumb.desktop"; # apple codec
"image/png" = "org.gnome.gThumb.desktop"; "image/png" = "org.gnome.gThumb.desktop";
"image/jpeg" = "org.gnome.gThumb.desktop"; "image/jpeg" = "org.gnome.gThumb.desktop";
"image/svg+xml" = "org.gnome.gThumb.desktop"; "image/svg+xml" = "org.gnome.gThumb.desktop";
"image/webp" = "org.gnome.gThumb.desktop";
}; };
}; };
} }

View File

@@ -7,7 +7,7 @@
# grammars need to be persisted when developing them # grammars need to be persisted when developing them
# - `hx --grammar fetch` and `hx --grammar build` # - `hx --grammar fetch` and `hx --grammar build`
# but otherwise, they ship as part of HELIX_RUNTIME, in the nix store # but otherwise, they ship as part of HELIX_RUNTIME, in the nix store
# persist.byStore.plaintext = [ ".config/helix/runtime/grammars" ]; # persist.plaintext = [ ".config/helix/runtime/grammars" ];
fs.".config/helix/config.toml".symlink.text = '' fs.".config/helix/config.toml".symlink.text = ''
# docs: <https://docs.helix-editor.com/configuration.html> # docs: <https://docs.helix-editor.com/configuration.html>
[editor.soft-wrap] [editor.soft-wrap]

View File

@@ -10,6 +10,6 @@
# jellyfin stores things in a bunch of directories: this one persists auth info. # 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 # it *might* be possible to populate this externally (it's Qt stuff), but likely to
# be fragile and take an hour+ to figure out. # be fragile and take an hour+ to figure out.
persist.byStore.plaintext = [ ".local/share/Jellyfin Media Player" ]; persist.plaintext = [ ".local/share/Jellyfin Media Player" ];
}; };
} }

View File

@@ -0,0 +1,47 @@
# vim:ft=kitty
## name: PaperColor Dark
## author: Nikyle Nguyen
## license: MIT
## blurb: Dark color scheme inspired by Google's Material Design
# special
foreground #d0d0d0
background #1c1c1c
cursor #d0d0d0
cursor_text_color background
# black
color0 #1c1c1c
color8 #585858
# red
color1 #af005f
color9 #5faf5f
# green
# "color2" is the green color used by ls to indicate executability
# both as text color
# or as bg color when the text is blue (color4)
color2 #246a28
color10 #2df200
# yellow
color3 #d7af5f
color11 #af87d7
# blue
color4 #78c6ef
color12 #ffaf00
# magenta
color5 #808080
color13 #ff5faf
# cyan
color6 #d7875f
color14 #00afaf
# white
color7 #d0d0d0
color15 #5f8787

View File

@@ -0,0 +1,73 @@
{ lib, ... }:
{
sane.programs.kitty = {
fs.".config/kitty/kitty.conf".symlink.text = ''
# docs: https://sw.kovidgoyal.net/kitty/conf/
# disable terminal bell (when e.g. you backspace too many times)
enable_audio_bell no
map ctrl+n new_os_window_with_cwd
include ${./PaperColor_dark.conf}
'';
env.TERMINAL = lib.mkDefault "kitty";
};
# include ${pkgs.kitty-themes}/themes/PaperColor_dark.conf
# THEME CHOICES:
# docs: https://github.com/kovidgoyal/kitty-themes
# theme = "1984 Light"; # dislike: awful, harsh blues/teals
# theme = "Adventure Time"; # dislike: harsh (dark)
# theme = "Atom One Light"; # GOOD: light theme. all color combos readable. not a huge fan of the blue.
# theme = "Belafonte Day"; # dislike: too low contrast for text colors
# theme = "Belafonte Night"; # better: dark theme that's easy on the eyes. all combos readable. low contrast.
# theme = "Catppuccin"; # dislike: a bit pale/low-contrast (dark)
# theme = "Desert"; # mediocre: colors are harsh
# theme = "Earthsong"; # BEST: dark theme. readable, good contrast. unique, but decent colors.
# theme = "Espresso Libre"; # better: dark theme. readable, but meh colors
# theme = "Forest Night"; # decent: very pastel. it's workable, but unconventional and muted/flat.
# theme = "Gruvbox Material Light Hard"; # mediocre light theme.
# theme = "kanagawabones"; # better: dark theme. colors are too background-y
# theme = "Kaolin Dark"; # dislike: too dark
# theme = "Kaolin Breeze"; # mediocre: not-too-harsh light theme, but some parts are poor contrast
# theme = "Later This Evening"; # mediocre: not-too-harsh dark theme, but cursor is poor contrast
# theme = "Material"; # decent: light theme, few colors.
# theme = "Mayukai"; # decent: not-too-harsh dark theme. the teal is a bit straining
# theme = "Nord"; # mediocre: pale background, low contrast
# theme = "One Half Light"; # better: not-too-harsh light theme. contrast could be better
# theme = "PaperColor Dark"; # BEST: dark theme, very readable still the colors are background-y
# theme = "Parasio Dark"; # dislike: too low contrast
# theme = "Pencil Light"; # better: not-too-harsh light theme. decent contrast.
# theme = "Pnevma"; # dislike: too low contrast
# theme = "Piatto Light"; # better: readable light theme. pleasing colors. powerline prompt is hard to read.
# theme = "Rosé Pine Dawn"; # GOOD: light theme. all color combinations are readable. it is very mild -- may need to manually tweak contrast. tasteful colors
# theme = "Rosé Pine Moon"; # GOOD: dark theme. tasteful colors. but background is a bit intense
# theme = "Sea Shells"; # mediocre. not all color combos are readable
# theme = "Solarized Light"; # mediocre: not-too-harsh light theme; GREAT background; but some colors are low contrast
# theme = "Solarized Dark Higher Contrast"; # better: dark theme, decent colors
# theme = "Sourcerer"; # mediocre: ugly colors
# theme = "Space Gray"; # mediocre: too muted
# theme = "Space Gray Eighties"; # better: all readable, decent colors
# theme = "Spacemacs"; # mediocre: too muted
# theme = "Spring"; # mediocre: readable light theme, but the teal is ugly.
# theme = "Srcery"; # better: highly readable. colors are ehhh
# theme = "Substrata"; # decent: nice colors, but a bit flat.
# theme = "Sundried"; # mediocre: the solar text makes me squint
# theme = "Symfonic"; # mediocre: the dark purple has low contrast to the black bg.
# theme = "Tango Light"; # dislike: teal is too grating
# theme = "Tokyo Night Day"; # medicore: too muted
# theme = "Tokyo Night"; # better: tasteful. a bit flat
# theme = "Tomorrow"; # GOOD: all color combinations are readable. contrast is slightly better than Rose. on the blander side
# theme = "Treehouse"; # dislike: the orange is harsh on my eyes.
# theme = "Urple"; # dislike: weird palette
# theme = "Warm Neon"; # decent: not-too-harsh dark theme. the green is a bit unattractive
# theme = "Wild Cherry"; # GOOD: dark theme: nice colors. a bit flat
# theme = "Xcodedark"; # dislike: bad palette
# theme = "citylights"; # decent: dark theme. some parts have just a bit low contrast
# theme = "neobones_light"; # better light theme. the background is maybe too muted
# theme = "vimbones";
# theme = "zenbones_dark"; # mediocre: readable, but meh colors
# theme = "zenbones_light"; # decent: light theme. all colors are readable. contrast is passable but not excellent. highlight color is BAD
# theme = "zenwritten_dark"; # mediocre: looks same as zenbones_dark
}

View File

@@ -3,6 +3,6 @@
sane.programs.komikku = { sane.programs.komikku = {
secrets.".local/share/komikku/keyrings/plaintext.keyring" = ../../../secrets/common/komikku_accounts.json.bin; 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. # downloads end up here, and without the toplevel database komikku doesn't know they exist.
persist.byStore.plaintext = [ ".local/share/komikku" ]; persist.plaintext = [ ".local/share/komikku" ];
}; };
} }

View File

@@ -26,8 +26,9 @@ in {
package = pkgs.koreader-from-src; package = pkgs.koreader-from-src;
# koreader applies these lua "patches" at boot: # koreader applies these lua "patches" at boot:
# - <https://github.com/koreader/koreader/wiki/User-patches> # - <https://github.com/koreader/koreader/wiki/User-patches>
# - 2023/10/29: koreader code hasn't changed, but somehow FTP browser seems usable even without the isConnected patch now. # - TODO: upstream this patch to koreader
# fs.".config/koreader/patches/2-colin-NetworkManager-isConnected.lua".symlink.target = "${./2-colin-NetworkManager-isConnected.lua}"; # fs.".config/koreader/patches".symlink.target = "${./.}";
fs.".config/koreader/patches/2-colin-NetworkManager-isConnected.lua".symlink.target = "${./2-colin-NetworkManager-isConnected.lua}";
# koreader news plugin, enabled by default. file format described here: # koreader news plugin, enabled by default. file format described here:
# - <repo:koreader/koreader:plugins/newsdownloader.koplugin/feed_config.lua> # - <repo:koreader/koreader:plugins/newsdownloader.koplugin/feed_config.lua>
@@ -36,14 +37,12 @@ in {
${lib.concatStringsSep ",\n " koreaderRssEntries} ${lib.concatStringsSep ",\n " koreaderRssEntries}
}--do NOT change this line }--do NOT change this line
''; '';
# easier to navigate via filebrowser than finding the news menu entry
fs."Books/rss-koreader".symlink.target = "../.config/koreader/news";
# koreader on aarch64 errors if there's no fonts directory (sandboxing thing, i guess) # koreader on aarch64 errors if there's no fonts directory (sandboxing thing, i guess)
fs.".local/share/fonts".dir = {}; fs.".local/share/fonts".dir = {};
# history, cache, dictionaries... # history, cache, dictionaries...
# could be more explicit if i symlinked the history.lua file to somewhere it can persist better. # could be more explicit if i symlinked the history.lua file to somewhere it can persist better.
persist.byStore.plaintext = [ ".config/koreader" ]; persist.plaintext = [ ".config/koreader" ];
}; };
} }

View File

@@ -2,6 +2,6 @@
{ {
sane.programs.lemoa = { sane.programs.lemoa = {
# creds # creds
persist.byStore.private = [ ".local/share/io.github.lemmygtk.lemoa" ]; persist.private = [ ".local/share/io.github.lemmygtk.lemoa" ];
}; };
} }

View File

@@ -1,67 +0,0 @@
# config docs:
# - `man 5 mako`
{ config, lib, pkgs, ... }:
{
sane.programs.mako = {
# we control mako as a systemd service, so have dbus not automatically activate it.
package = pkgs.rmDbusServices pkgs.mako;
fs.".config/mako/config".symlink.text = ''
# notification interaction mapping
# "on-touch" defaults to "dismiss", which isn't nice for touchscreens.
on-button-left=invoke-default-action
on-touch=invoke-default-action
on-button-middle=dismiss-group
max-visible=3
# layer:
# - overlay: shows notifs above all else, even full-screen windows
# - top: shows notifs above windows, but not if they're full-screen
# - bottom; background
layer=overlay
# notifications can be grouped by:
# - app-name
# - app-icon
# - summary
# - body
# possibly more: urgency, category, desktop-entry, ...
# to group by multiple fields, join with `,`
group-by=app-name
# BELOW IS SXMO DEFAULTS, modified very slightly.
# TODO: apply theme colors!
# default-timeout=15000
background-color=#ffffff
text-color=#000000
border-color=#000000
# group-by=app-name
[urgency=low]
# default-timeout=10000
background-color=#222222
text-color=#888888
[urgency=high]
default-timeout=0
background-color=#900000
text-color=#ffffff
background-color=#ff0000
'';
# mako supports activation via dbus (i.e. the daemon will be started on-demand when a
# dbus client tries to talk to it): that works out-of-the-box just by putting mako
# on environment.packages, but then logs are blackholed.
services.mako = {
description = "mako desktop notification daemon";
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...
serviceConfig.ExecStart = "${config.sane.programs.mako.package}/bin/mako";
serviceConfig.Type = "simple";
# mako will predictably fail if launched before the wayland server is fully initialized
serviceConfig.Restart = "on-failure";
serviceConfig.RestartSec = "10s";
};
};
}

View File

@@ -4,9 +4,9 @@
{ {
sane.programs.mepo = { sane.programs.mepo = {
persist.byStore.plaintext = [ ".cache/mepo/tiles" ]; persist.plaintext = [ ".cache/mepo/tiles" ];
# ~/.cache/mepo/savestate has precise coordinates and pins: keep those private # ~/.cache/mepo/savestate has precise coordinates and pins: keep those private
persist.byStore.private = [ persist.private = [
{ type = "file"; path = ".cache/mepo/savestate"; } { type = "file"; path = ".cache/mepo/savestate"; }
]; ];

View File

@@ -1,56 +0,0 @@
# chat: <https://mopidy.zulipchat.com/>
# config docs: <https://docs.mopidy.com/en/latest/config/>
# web client: <http://localhost:6680>
# mpd: hosted on `localhost:6600`, no password`
#
# dump config:
# - `mopidy config`
# update local file index with
# - `mopidy local scan`
#
# if running as service, those commands are `mopidy --config ... <command>`
# and config path is found by `systemctl cat mopidy`
{ config, lib, pkgs, ... }:
let
# TODO: upstream this as `mopidy.withExtensions`
# this is borrowed from the nixos mopidy service
mopidyWithExtensions = extensions: with pkgs; buildEnv {
name = "mopidy-with-extensions-${mopidy.version}";
paths = lib.closePropagation extensions;
pathsToLink = [ "/${mopidyPackages.python.sitePackages}" ];
nativeBuildInputs = [ makeWrapper ];
postBuild = ''
makeWrapper ${mopidy}/bin/mopidy $out/bin/mopidy \
--prefix PYTHONPATH : $out/${mopidyPackages.python.sitePackages}
'';
};
in
{
sane.programs.mopidy = {
package = mopidyWithExtensions (with pkgs; [
mopidy-iris # web client: <https://github.com/jaedb/Iris>
mopidy-jellyfin
mopidy-local
mopidy-mpd
mopidy-mpris
mopidy-spotify
# TODO: mopidy-podcast, mopidy-youtube
# alternate web clients:
# mopidy-moped: <https://github.com/martijnboland/moped>
# mopidy-muse: <https://github.com/cristianpb/muse>
]);
persist.byStore.plaintext = [
".local/share/mopidy/local" # thumbs, library db
];
persist.byStore.private = [
".local/share/mopidy/http" # cookie
];
secrets.".config/mopidy/mopidy.conf" = ../../../secrets/common/mopidy.conf.bin;
# other folders:
# - .cache/mopidy
# - .config/mopidy
};
}

View File

@@ -3,62 +3,22 @@
# - <https://github.com/mpv-player/mpv/wiki> # - <https://github.com/mpv-player/mpv/wiki>
# curated mpv mods/scripts/users: # curated mpv mods/scripts/users:
# - <https://github.com/stax76/awesome-mpv> # - <https://github.com/stax76/awesome-mpv>
{ config, lib, pkgs, ... }: { pkgs, ... }:
let
cfg = config.sane.programs.mpv;
in
{ {
sane.programs.mpv = { sane.programs.mpv = {
configOption = with lib; mkOption {
default = {};
type = types.submodule {
options.vo = mkOption {
type = types.nullOr types.str;
default = null;
description = "--vo=FOO flag to pass to mpv";
};
};
};
package = pkgs.wrapMpv pkgs.mpv-unwrapped { package = pkgs.wrapMpv pkgs.mpv-unwrapped {
youtubeSupport = false; #< XXX(2023/08/03): doesn't cross compile until next staging -> master merge
scripts = with pkgs.mpvScripts; [ scripts = with pkgs.mpvScripts; [
mpris mpris
# uosc # uosc
pkgs.mpv-uosc-latest pkgs.mpv-uosc-latest
]; ];
extraMakeWrapperArgs = lib.optionals (cfg.config.vo != null) [
# 2023/08/29: fixes an error where mpv on moby launches with the message
# "DRM_IOCTL_MODE_CREATE_DUMB failed: Cannot allocate memory"
# audio still works, and controls, screenshotting, etc -- just not the actual rendering
# this is likely a regression for mpv 0.36.0.
# the actual error message *appears* to come from the mesa library, but it's tough to trace.
#
# backend compatibility (2023/10/22):
# run with `--vo=help` to see a list of all output options.
# non-exhaustive (W=works, F=fails, A=audio-only, U=audio+ui only (no video))
# ? null Null video output
# A (default)
# A dmabuf-wayland Wayland dmabuf video output
# A libmpv render API for libmpv (mpv plays the audio, but doesn't even render a window)
# A vdpau VDPAU with X11
# F drm Direct Rendering Manager (software scaling)
# F gpu-next Video output based on libplacebo
# F vaapi VA API with X11
# F x11 X11 (software scaling)
# F xv X11/Xv
# U gpu Shader-based GPU Renderer
# W caca libcaca (terminal rendering)
# W sdl SDL 2.0 Renderer
# W wlshm Wayland SHM video output (software scaling)
"--add-flags" "--vo=${cfg.config.vo}"
];
}; };
persist.byStore.plaintext = [ ".local/state/mpv/watch_later" ]; persist.plaintext = [ ".config/mpv/watch_later" ];
fs.".config/mpv/input.conf".symlink.text = '' fs.".config/mpv/input.conf".symlink.text = ''
# let volume/power keys be interpreted by the system. # let volume keys be interpreted by the system.
# this is important for sxmo. # this is important for sxmo.
# mpv defaults is POWER = close, VOLUME_{UP,DOWN} = adjust application-level volume
POWER ignore
VOLUME_UP ignore VOLUME_UP ignore
VOLUME_DOWN ignore VOLUME_DOWN ignore
''; '';
@@ -66,10 +26,6 @@ in
save-position-on-quit=yes save-position-on-quit=yes
keep-open=yes keep-open=yes
# force GUI, even for tracks w/o album art
# see: <https://www.reddit.com/r/mpv/comments/rvrrpt/oscosdgui_and_arch_linux/>
player-operation-mode=pseudo-gui
# use uosc instead (for On Screen Controls) # use uosc instead (for On Screen Controls)
osc=no osc=no
# uosc provides its own seeking/volume indicators, so you also don't need this # uosc provides its own seeking/volume indicators, so you also don't need this
@@ -113,8 +69,7 @@ in
ui_scale=1.0 ui_scale=1.0
''; '';
# mime.priority = 200; # default = 100; 200 means to yield to other apps mime.priority = 200; # default = 100; 200 means to yield to other apps
mime.priority = 50; # default = 100; 50 in order to take precedence over vlc.
mime.associations."audio/flac" = "mpv.desktop"; mime.associations."audio/flac" = "mpv.desktop";
mime.associations."audio/mpeg" = "mpv.desktop"; mime.associations."audio/mpeg" = "mpv.desktop";
mime.associations."audio/x-vorbis+ogg" = "mpv.desktop"; mime.associations."audio/x-vorbis+ogg" = "mpv.desktop";

View File

@@ -87,14 +87,10 @@ in
{ {
# private because there could be sensitive things in the swap # private because there could be sensitive things in the swap
sane.programs.neovim = { sane.programs.neovim = {
persist.byStore.private = [ ".cache/vim-swap" ]; persist.private = [ ".cache/vim-swap" ];
env.EDITOR = "vim"; env.EDITOR = "vim";
# git claims it should use EDITOR, but it doesn't! # git claims it should use EDITOR, but it doesn't!
env.GIT_EDITOR = "vim"; env.GIT_EDITOR = "vim";
mime.priority = 200; # default=100 => yield to other, more specialized applications
mime.associations."application/schema+json" = "nvim.desktop";
mime.associations."plain/text" = "nvim.desktop";
mime.associations."text/markdown" = "nvim.desktop";
}; };
programs.neovim = mkIf config.sane.programs.neovim.enabled { programs.neovim = mkIf config.sane.programs.neovim.enabled {

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