Compare commits
7 Commits
wg-dev
...
wip-sxmo-s
Author | SHA1 | Date | |
---|---|---|---|
b50ee5dcea | |||
aaa8424c9e | |||
65123aa963 | |||
97d14f4c2c | |||
3e7fc56c86 | |||
346ca57c93 | |||
47e23c1ff3 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
.working
|
/keep
|
||||||
result
|
result
|
||||||
result-*
|
result-*
|
||||||
/secrets/local.nix
|
/secrets/local.nix
|
||||||
|
/working
|
||||||
|
@@ -11,7 +11,7 @@ the only hard dependency for my exported pkgs/modules should be [nixpkgs][nixpkg
|
|||||||
building <./hosts/> will require [sops][sops].
|
building <./hosts/> will require [sops][sops].
|
||||||
|
|
||||||
you might specifically be interested in these files (elaborated further in #key-points-of-interest):
|
you might specifically be interested in these files (elaborated further in #key-points-of-interest):
|
||||||
- [`sxmo-utils`](./pkgs/additional/sxmo-utils/default.nix)
|
- [`sxmo-utils-latest`](./pkgs/additional/sxmo-utils/default.nix)
|
||||||
- [example SXMO deployment](./hosts/modules/gui/sxmo/default.nix)
|
- [example SXMO deployment](./hosts/modules/gui/sxmo/default.nix)
|
||||||
- [my implementation of impermanence](./modules/persist/default.nix)
|
- [my implementation of impermanence](./modules/persist/default.nix)
|
||||||
- my way of deploying dotfiles/configuring programs per-user:
|
- my way of deploying dotfiles/configuring programs per-user:
|
||||||
|
59
TODO.md
59
TODO.md
@@ -1,13 +1,10 @@
|
|||||||
## BUGS
|
## BUGS
|
||||||
- nixpkgs date is incorrect (1970.01.01...)
|
- why i need to manually restart `wireguard-wg-ovpns` on servo periodically
|
||||||
- ringer (i.e. dino incoming call) doesn't prevent moby from sleeping
|
- else DNS fails
|
||||||
- `nix` operations from lappy hang when `desko` is unreachable
|
- fix epiphany URL bar input on moby
|
||||||
- could at least direct the cache to `http://desko-hn:5001`
|
|
||||||
|
|
||||||
## REFACTORING:
|
## REFACTORING:
|
||||||
|
|
||||||
- fold hosts/common/home/ssh.nix -> hosts/common/users/colin.nix
|
|
||||||
|
|
||||||
### sops/secrets
|
### sops/secrets
|
||||||
- attach secrets to the thing they're used by (sane.programs)
|
- attach secrets to the thing they're used by (sane.programs)
|
||||||
- rework secrets to leverage `sane.fs`
|
- rework secrets to leverage `sane.fs`
|
||||||
@@ -24,9 +21,11 @@
|
|||||||
- fix lightdm-mobile-greeter for newer libhandy
|
- fix lightdm-mobile-greeter for newer libhandy
|
||||||
- port zecwallet-lite to a from-source build
|
- port zecwallet-lite to a from-source build
|
||||||
- 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>
|
||||||
|
|
||||||
#### upstreaming to non-nixpkgs repos
|
#### upstreaming to non-nixpkgs repos
|
||||||
- gtk: build schemas even on cross compilation: <https://github.com/NixOS/nixpkgs/pull/247844>
|
- gtk: build schemas even on cross compilation: <https://github.com/NixOS/nixpkgs/pull/247844>
|
||||||
|
- sxmo: add new app entries
|
||||||
|
|
||||||
|
|
||||||
## IMPROVEMENTS:
|
## IMPROVEMENTS:
|
||||||
@@ -48,31 +47,13 @@
|
|||||||
- 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?
|
||||||
|
|
||||||
### faster/better deployments
|
|
||||||
- remove audacity's dependency on webkitgtk (via wxwidgets)
|
|
||||||
|
|
||||||
### user experience
|
### user experience
|
||||||
- install apps:
|
|
||||||
- display QR codes for WiFi endpoints: <https://linuxphoneapps.org/apps/noappid.wisperwind.wifi2qr/>
|
|
||||||
- shopping list (not in nixpkgs): <https://linuxphoneapps.org/apps/ro.hume.cosmin.shoppinglist/>
|
|
||||||
- offline Wikipedia (or, add to `wike`)
|
|
||||||
- offline docs viewer (gtk): <https://github.com/workbenchdev/Biblioteca>
|
|
||||||
- some type of games manager/launcher
|
|
||||||
- Gnome Highscore (retro games)?: <https://gitlab.gnome.org/World/highscore>
|
|
||||||
- better maps for mobile (Osmin (QtQuick)? Pure Maps (Qt/Kirigami)? Gnome Maps is improved in 45)
|
|
||||||
- note-taking app: <https://linuxphoneapps.org/categories/note-taking/>
|
|
||||||
- OSK overlay specifically for mobile gaming
|
|
||||||
- i.e. mock joysticks, for use with SuperTux and SuperTuxKart
|
|
||||||
- install mobile-friendly games:
|
|
||||||
- Shattered Pixel Dungeon (nixpkgs `shattered-pixel-dungeon`; doesn't cross-compile b/c openjdk/libIDL) <https://github.com/ebolalex/shattered-pixel-dungeon>
|
|
||||||
- UnCiv (Civ V clone; nixpkgs `unciv`; doesn't cross-compile): <https://github.com/yairm210/UnCiv>
|
|
||||||
- Simon Tatham's Puzzle Collection (not in nixpkgs) <https://git.tartarus.org/?p=simon/puzzles.git>
|
|
||||||
- Shootin Stars (Godot; not in nixpkgs) <https://gitlab.com/greenbeast/shootin-stars>
|
|
||||||
- numberlink (generic name for Flow Free). not packaged in Nix
|
|
||||||
- Neverball (https://neverball.org/screenshots.php). nix: as `neverball`
|
|
||||||
|
|
||||||
#### moby
|
#### moby
|
||||||
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
|
- 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:
|
- SwayNC:
|
||||||
- don't show MPRIS if no players detected
|
- don't show MPRIS if no players detected
|
||||||
- this is a problem of playerctld, i guess
|
- this is a problem of playerctld, i guess
|
||||||
@@ -86,14 +67,23 @@
|
|||||||
- manually do smoothing, as some layer between mepo and geoclue/gpsd?
|
- manually do smoothing, as some layer between mepo and geoclue/gpsd?
|
||||||
- moby: show battery state on ssh login
|
- moby: show battery state on ssh login
|
||||||
- moby: improve gPodder launch time
|
- 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)
|
||||||
|
- sxmo: don't put all deps on PATH
|
||||||
|
- maybe: use resholve to hard-code them
|
||||||
|
- this is the most "correct", but least patchable
|
||||||
|
- maybe: express each invocation as a function in sxmo_common.sh
|
||||||
|
- this will require some patching to handle `exec <foo>` style
|
||||||
|
- maybe: save original PATH and reset it before invoking user files
|
||||||
- moby: theme GTK apps (i.e. non-adwaita styles)
|
- 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
|
- especially, make the menubar collapsible
|
||||||
- try Gradience tool specifically for theming adwaita? <https://linuxphoneapps.org/apps/com.github.gradienceteam.gradience/>
|
- 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
|
- phog: remove the gnome-shell runtime dependency to save hella closure size
|
||||||
|
|
||||||
#### non-moby
|
#### non-moby
|
||||||
- RSS: integrate a paywall bypass
|
|
||||||
- e.g. self-hosted [ladder](https://github.com/everywall/ladder) (like 12ft.io)
|
|
||||||
- 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
|
||||||
@@ -114,10 +104,17 @@
|
|||||||
- 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!
|
||||||
- fix desko so it doesn't dispatch so many build jobs to servo by default
|
- 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)
|
||||||
|
- 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/>
|
- package lemonade lemmy app: <https://linuxphoneapps.org/apps/ml.mdwalters.lemonade/>
|
||||||
|
80
flake.lock
generated
80
flake.lock
generated
@@ -1,45 +1,46 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"mobile-nixos": {
|
"flake-utils": {
|
||||||
"flake": false,
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1694749521,
|
"lastModified": 1687709756,
|
||||||
"narHash": "sha256-MiVokKlpcJmfoGuWAMeW1En7gZ5hk0rCQArYm6P9XCc=",
|
"narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
|
||||||
"owner": "nixos",
|
"owner": "numtide",
|
||||||
"repo": "mobile-nixos",
|
"repo": "flake-utils",
|
||||||
"rev": "d25d3b87e7f300d8066e31d792337d9cd7ecd23b",
|
"rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "numtide",
|
||||||
"ref": "d25d3b87e7f300d8066e31d792337d9cd7ecd23b",
|
"repo": "flake-utils",
|
||||||
"repo": "mobile-nixos",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-next-unpatched": {
|
"mobile-nixos": {
|
||||||
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1705233677,
|
"lastModified": 1696124168,
|
||||||
"narHash": "sha256-eq3VE8QGJsunqqF/BlLslWE1gASp4Hlgp0c78coxat0=",
|
"narHash": "sha256-EzGHYAR7rozQQLZEHbKEcb5VpUFGoxwEsM0OWfW4wqU=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "mobile-nixos",
|
||||||
"rev": "724e39ebb9b8eda97f17d423f66fbc5a991f4f8d",
|
"rev": "7cee346c3f8e73b25b1cfbf7a086a7652c11e0f3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"ref": "staging-next",
|
"repo": "mobile-nixos",
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-stable": {
|
"nixpkgs-stable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1705033721,
|
"lastModified": 1696123266,
|
||||||
"narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=",
|
"narHash": "sha256-S6MZEneQeE4M/E/C8SMnr7B7oBnjH/hbm96Kak5hAAI=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea",
|
"rev": "dbe90e63a36762f1fbde546e26a84af774a32455",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -51,16 +52,16 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-unpatched": {
|
"nixpkgs-unpatched": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1705254014,
|
"lastModified": 1696375444,
|
||||||
"narHash": "sha256-4RrVNEqxeji4vqDgzSl7JoCD6a0ag5LF9zXFndtqrpE=",
|
"narHash": "sha256-Sv0ICt/pXfpnFhTGYTsX6lUr1SljnuXWejYTI2ZqHa4=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6c08fe3ccf437d8b26bec010fd925ddd6bb0d0d5",
|
"rev": "81e8f48ebdecf07aab321182011b067aafc78896",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"ref": "master",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -68,7 +69,6 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"mobile-nixos": "mobile-nixos",
|
"mobile-nixos": "mobile-nixos",
|
||||||
"nixpkgs-next-unpatched": "nixpkgs-next-unpatched",
|
|
||||||
"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"
|
||||||
@@ -82,11 +82,11 @@
|
|||||||
"nixpkgs-stable": "nixpkgs-stable"
|
"nixpkgs-stable": "nixpkgs-stable"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1705201153,
|
"lastModified": 1696320910,
|
||||||
"narHash": "sha256-y0/a4IMDZrc7lAkR7Gcm5R3W2iCBiARHnYZe6vkmiNE=",
|
"narHash": "sha256-fbuEc6wylH+0VxG48lhPBK+SQJHfo2lusUwWHZNipIM=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "70dd0d521f7849338e487a219c1a07c429a66d77",
|
"rev": "746c7fa1a64c1671a4bf287737c27fdc7101c4c2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -95,18 +95,34 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"uninsane-dot-org": {
|
"uninsane-dot-org": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs-unpatched"
|
"nixpkgs-unpatched"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1704082841,
|
"lastModified": 1696306988,
|
||||||
"narHash": "sha256-4g3lePnUALb8B1m3rEDD6rrZAq2pTN4qaSnStbd676U=",
|
"narHash": "sha256-I/OyJxIxu0n5h1eFqwVw0C6wTN3ewBXp2lGAdo1ur70=",
|
||||||
"ref": "refs/heads/master",
|
"ref": "refs/heads/master",
|
||||||
"rev": "4a1fa488e64e6c87c6c951e3fafb2684692f64d3",
|
"rev": "1f588493031168d92a1e60705f26aaf4b2cdc07e",
|
||||||
"revCount": 234,
|
"revCount": 208,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.uninsane.org/colin/uninsane"
|
"url": "https://git.uninsane.org/colin/uninsane"
|
||||||
},
|
},
|
||||||
|
269
flake.nix
269
flake.nix
@@ -29,32 +29,27 @@
|
|||||||
# - daily:
|
# - daily:
|
||||||
# - nixos-unstable cut from master after enough packages have been built in caches.
|
# - nixos-unstable cut from master after enough packages have been built in caches.
|
||||||
# - every 6 hours:
|
# - every 6 hours:
|
||||||
# - master auto-merged into staging and staging-next
|
# - master auto-merged into staging.
|
||||||
# - staging-next auto-merged into staging.
|
# - staging-next auto-merged into staging.
|
||||||
# - manually, approximately once per month:
|
# - manually, approximately once per month:
|
||||||
# - staging-next is cut from staging.
|
# - staging-next is cut from staging.
|
||||||
# - staging-next merged into master.
|
# - staging-next merged into master.
|
||||||
#
|
#
|
||||||
# which branch to source from?
|
# which branch to source from?
|
||||||
# - nixos-unstable: for everyday development; it provides good caching
|
# - for everyday development, prefer `nixos-unstable` branch, as it provides good caching.
|
||||||
# - master: temporarily if i'm otherwise cherry-picking lots of already-applied patches
|
# - if need to test bleeding updates (e.g. if submitting code into staging):
|
||||||
# - staging-next: if testing stuff that's been PR'd into staging, i.e. base library updates.
|
# - use `staging-next` if it's been cut (i.e. if there's an active staging-next -> master PR)
|
||||||
# - staging: maybe if no staging-next -> master PR has been cut yet?
|
# - use `staging` if no staging-next branch has been cut.
|
||||||
#
|
#
|
||||||
# <https://github.com/nixos/nixpkgs/tree/nixos-unstable>
|
# <https://github.com/nixos/nixpkgs/tree/nixos-unstable>
|
||||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=master";
|
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=staging-next";
|
||||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-staging";
|
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=staging";
|
||||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-staging-next";
|
|
||||||
nixpkgs-next-unpatched.url = "github:nixos/nixpkgs?ref=staging-next";
|
|
||||||
|
|
||||||
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
|
# only used for building disk images, not relevant after deployment
|
||||||
# TODO: replace with something else. commit `0f3ac0bef1aea70254a3bae35e3cc2561623f4c1`
|
url = "github:nixos/mobile-nixos";
|
||||||
# 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 = {
|
||||||
@@ -75,7 +70,6 @@
|
|||||||
outputs = {
|
outputs = {
|
||||||
self,
|
self,
|
||||||
nixpkgs-unpatched,
|
nixpkgs-unpatched,
|
||||||
nixpkgs-next-unpatched ? nixpkgs-unpatched,
|
|
||||||
mobile-nixos,
|
mobile-nixos,
|
||||||
sops-nix,
|
sops-nix,
|
||||||
uninsane-dot-org,
|
uninsane-dot-org,
|
||||||
@@ -94,9 +88,9 @@
|
|||||||
# 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.
|
||||||
patchNixpkgs = variant: nixpkgs: (import ./nixpatches/flake.nix).outputs {
|
nixpkgs = (import ./nixpatches/flake.nix).outputs {
|
||||||
inherit variant nixpkgs;
|
self = nixpkgs;
|
||||||
self = patchNixpkgs variant nixpkgs;
|
nixpkgs = nixpkgs-unpatched;
|
||||||
} // {
|
} // {
|
||||||
# provide values that nixpkgs ordinarily sources from the flake.lock file,
|
# provide values that nixpkgs ordinarily sources from the flake.lock file,
|
||||||
# inaccessible to it here because of the import-from-derivation.
|
# inaccessible to it here because of the import-from-derivation.
|
||||||
@@ -112,24 +106,21 @@
|
|||||||
inherit (self) shortRev;
|
inherit (self) shortRev;
|
||||||
};
|
};
|
||||||
|
|
||||||
nixpkgs' = patchNixpkgs "master" nixpkgs-unpatched;
|
nixpkgsCompiledBy = system: nixpkgs.legacyPackages."${system}";
|
||||||
nixpkgsCompiledBy = system: nixpkgs'.legacyPackages."${system}";
|
|
||||||
|
|
||||||
evalHost = { name, local, target, light ? false, nixpkgs ? nixpkgs' }: nixpkgs.lib.nixosSystem {
|
evalHost = { name, local, target }: nixpkgs.lib.nixosSystem {
|
||||||
system = target;
|
system = target;
|
||||||
modules = [
|
modules = [
|
||||||
{
|
{
|
||||||
nixpkgs.buildPlatform.system = local;
|
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;
|
# nixpkgs.config.replaceStdenv = { pkgs }: pkgs.ccacheStdenv;
|
||||||
}
|
}
|
||||||
(optionalAttrs (local != target) {
|
|
||||||
# XXX(2023/12/11): cache.nixos.org uses `system = ...` instead of `hostPlatform.system`, and that choice impacts the closure of every package.
|
|
||||||
# so avoid specifying hostPlatform.system on non-cross builds, so i can use upstream caches.
|
|
||||||
nixpkgs.hostPlatform.system = target;
|
|
||||||
})
|
|
||||||
(optionalAttrs light {
|
|
||||||
sane.enableSlowPrograms = false;
|
|
||||||
})
|
|
||||||
(import ./hosts/instantiate.nix { hostName = name; })
|
(import ./hosts/instantiate.nix { hostName = name; })
|
||||||
self.nixosModules.default
|
self.nixosModules.default
|
||||||
self.nixosModules.passthru
|
self.nixosModules.passthru
|
||||||
@@ -142,24 +133,39 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
nixosConfigurations = let
|
nixosConfigurations =
|
||||||
|
let
|
||||||
hosts = {
|
hosts = {
|
||||||
servo = { name = "servo"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
servo = { name = "servo"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||||
desko = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
desko = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||||
desko-light = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; light = true; };
|
|
||||||
lappy = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
lappy = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||||
lappy-light = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; light = true; };
|
|
||||||
moby = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; };
|
moby = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; };
|
||||||
moby-light = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; light = true; };
|
|
||||||
rescue = { name = "rescue"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
rescue = { name = "rescue"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||||
};
|
};
|
||||||
hostsNext = mapAttrs' (h: v: {
|
# cross-compiled builds: instead of emulating the host, build using a cross-compiler.
|
||||||
name = "${h}-next";
|
# - these are faster to *build* than the emulated variants (useful when tweaking packages),
|
||||||
value = v // { nixpkgs = patchNixpkgs "staging-next" nixpkgs-next-unpatched; };
|
# - but fewer of their packages can be found in upstream caches.
|
||||||
}) hosts;
|
cross = mapAttrValues evalHost hosts;
|
||||||
in mapAttrValues evalHost (
|
emulated = mapAttrValues
|
||||||
hosts // hostsNext
|
({name, local, target}: evalHost {
|
||||||
);
|
inherit name target;
|
||||||
|
local = null;
|
||||||
|
})
|
||||||
|
hosts;
|
||||||
|
prefixAttrs = prefix: attrs: mapAttrs'
|
||||||
|
(name: value: {
|
||||||
|
name = prefix + name;
|
||||||
|
inherit value;
|
||||||
|
})
|
||||||
|
attrs;
|
||||||
|
in
|
||||||
|
(prefixAttrs "cross-" cross) //
|
||||||
|
(prefixAttrs "emulated-" emulated) // {
|
||||||
|
# prefer native builds for these machines:
|
||||||
|
inherit (emulated) servo desko lappy rescue;
|
||||||
|
# prefer cross-compiled builds for these machines:
|
||||||
|
inherit (cross) moby;
|
||||||
|
};
|
||||||
|
|
||||||
# unofficial output
|
# unofficial output
|
||||||
# this produces a EFI-bootable .img file (GPT with a /boot partition and a system (/ or /nix) partition).
|
# this produces a EFI-bootable .img file (GPT with a /boot partition and a system (/ or /nix) partition).
|
||||||
@@ -178,12 +184,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
|
||||||
hostConfigs = mapAttrValues (host: host.config) self.nixosConfigurations;
|
host-pkgs = mapAttrValues (host: host.config.system.build.pkgs) self.nixosConfigurations;
|
||||||
hostSystems = mapAttrValues (host: host.config.system.build.toplevel) self.nixosConfigurations;
|
host-programs = mapAttrValues (host: mapAttrValues (p: p.package) host.config.sane.programs) self.nixosConfigurations;
|
||||||
hostPkgs = mapAttrValues (host: host.config.system.build.pkgs) self.nixosConfigurations;
|
|
||||||
hostPrograms = mapAttrValues (host: mapAttrValues (p: p.package) host.config.sane.programs) self.nixosConfigurations;
|
|
||||||
|
|
||||||
patched.nixpkgs = nixpkgs';
|
|
||||||
|
|
||||||
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,
|
||||||
@@ -198,7 +200,7 @@
|
|||||||
passthru = final: prev:
|
passthru = final: prev:
|
||||||
let
|
let
|
||||||
mobile = (import "${mobile-nixos}/overlay/overlay.nix");
|
mobile = (import "${mobile-nixos}/overlay/overlay.nix");
|
||||||
uninsane = uninsane-dot-org.overlays.default;
|
uninsane = uninsane-dot-org.overlay;
|
||||||
in
|
in
|
||||||
(mobile final prev)
|
(mobile final prev)
|
||||||
// (uninsane final prev)
|
// (uninsane final prev)
|
||||||
@@ -229,27 +231,23 @@
|
|||||||
# extract only our own packages from the full set.
|
# extract only our own packages from the full set.
|
||||||
# because of `nix flake check`, we flatten the package set and only surface x86_64-linux packages.
|
# because of `nix flake check`, we flatten the package set and only surface x86_64-linux packages.
|
||||||
packages = mapAttrs
|
packages = mapAttrs
|
||||||
(system: passthruPkgs: passthruPkgs.lib.filterAttrs
|
(system: allPkgs:
|
||||||
(name: pkg:
|
allPkgs.lib.filterAttrs (name: pkg:
|
||||||
# keep only packages which will pass `nix flake check`, i.e. keep only:
|
# keep only packages which will pass `nix flake check`, i.e. keep only:
|
||||||
# - derivations (not package sets)
|
# - derivations (not package sets)
|
||||||
# - packages that build for the given platform
|
# - packages that build for the given platform
|
||||||
(! elem name [ "feeds" "pythonPackagesExtensions" ])
|
(! elem name [ "feeds" "pythonPackagesExtensions" ])
|
||||||
&& (passthruPkgs.lib.meta.availableOn passthruPkgs.stdenv.hostPlatform pkg)
|
&& (allPkgs.lib.meta.availableOn allPkgs.stdenv.hostPlatform pkg)
|
||||||
)
|
)
|
||||||
(
|
(
|
||||||
# expose sane packages and chosen inputs (uninsane.org)
|
# expose sane packages and chosen inputs (uninsane.org)
|
||||||
(import ./pkgs { pkgs = passthruPkgs; }) // {
|
(import ./pkgs { pkgs = allPkgs; }) // {
|
||||||
inherit (passthruPkgs) uninsane-dot-org;
|
inherit (allPkgs) uninsane-dot-org;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# self.legacyPackages;
|
# self.legacyPackages;
|
||||||
{
|
{ inherit (self.legacyPackages) x86_64-linux; }
|
||||||
x86_64-linux = (nixpkgsCompiledBy "x86_64-linux").appendOverlays [
|
|
||||||
self.overlays.passthru
|
|
||||||
];
|
|
||||||
}
|
|
||||||
;
|
;
|
||||||
|
|
||||||
apps."x86_64-linux" =
|
apps."x86_64-linux" =
|
||||||
@@ -257,27 +255,17 @@
|
|||||||
pkgs = self.legacyPackages."x86_64-linux";
|
pkgs = self.legacyPackages."x86_64-linux";
|
||||||
sanePkgs = import ./pkgs { inherit pkgs; };
|
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})
|
||||||
|
|
||||||
# XXX: this triggers another config eval & (potentially) build.
|
# XXX: this triggers another config eval & (potentially) build.
|
||||||
# if the config changed between these invocations, the above signatures might not apply to the deployed config.
|
# if the config changed between these invocations, the above signatures might not apply to the deployed config.
|
||||||
# let the user handle that edge case by re-running this whole command.
|
# let the user handle that edge case by re-running this whole command
|
||||||
# N.B.: `--fast` option here is critical to cross-compiled deployments: without it the build machine will try to invoke the host machine's `nix` binary.
|
nixos-rebuild --flake '.#${host}' ${action} --target-host colin@${addr} --use-remote-sudo $@
|
||||||
# TODO: solve this by replacing the nixos-build invocation with:
|
|
||||||
# - nix-copy-closure --to $host $result
|
|
||||||
# - on target: nix-env set -p /nix/var/nix/profiles/system $result
|
|
||||||
# - on target: $result/bin/switch-to-configuration
|
|
||||||
nixos-rebuild --flake '.#${host}' ${action} --target-host colin@${addr} --use-remote-sudo "$@" --fast
|
|
||||||
'';
|
'';
|
||||||
deployApp = host: addr: action: {
|
|
||||||
type = "app";
|
|
||||||
program = ''${deployScript host addr action}'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# pkg updating.
|
# pkg updating.
|
||||||
# a cleaner alternative lives here: <https://discourse.nixos.org/t/how-can-i-run-the-updatescript-of-personal-packages/25274/2>
|
# a cleaner alternative lives here: <https://discourse.nixos.org/t/how-can-i-run-the-updatescript-of-personal-packages/25274/2>
|
||||||
# mkUpdater :: [ String ] -> { type = "app"; program = path; }
|
|
||||||
mkUpdater = attrPath: {
|
mkUpdater = attrPath: {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = let
|
program = let
|
||||||
@@ -302,7 +290,7 @@
|
|||||||
} else {}
|
} else {}
|
||||||
)
|
)
|
||||||
(pkgs.lib.getAttrFromPath basePath sanePkgs);
|
(pkgs.lib.getAttrFromPath basePath sanePkgs);
|
||||||
mkUpdaters = { ignore ? [], flakePrefix ? [] }@opts: basePath:
|
mkUpdaters = { ignore ? [] }@opts: basePath:
|
||||||
let
|
let
|
||||||
updaters = mkUpdatersNoAliases opts basePath;
|
updaters = mkUpdatersNoAliases opts basePath;
|
||||||
invokeUpdater = name: pkg:
|
invokeUpdater = name: pkg:
|
||||||
@@ -312,7 +300,7 @@
|
|||||||
|
|
||||||
# in case `name` has a `.` in it, we have to quote it
|
# in case `name` has a `.` in it, we have to quote it
|
||||||
escapedPath = builtins.map (p: ''"${p}"'') fullPath;
|
escapedPath = builtins.map (p: ''"${p}"'') fullPath;
|
||||||
updatePath = builtins.concatStringsSep "." (flakePrefix ++ escapedPath);
|
updatePath = builtins.concatStringsSep "." ([ "update" "pkgs" ] ++ escapedPath);
|
||||||
in pkgs.lib.optionalString doUpdateByDefault (
|
in pkgs.lib.optionalString doUpdateByDefault (
|
||||||
pkgs.lib.escapeShellArgs [
|
pkgs.lib.escapeShellArgs [
|
||||||
"nix" "run" ".#${updatePath}"
|
"nix" "run" ".#${updatePath}"
|
||||||
@@ -320,9 +308,8 @@
|
|||||||
);
|
);
|
||||||
in {
|
in {
|
||||||
type = "app";
|
type = "app";
|
||||||
# top-level app just invokes the updater of everything one layer below it
|
|
||||||
program = builtins.toString (pkgs.writeShellScript
|
program = builtins.toString (pkgs.writeShellScript
|
||||||
(builtins.concatStringsSep "-" (flakePrefix ++ basePath))
|
(builtins.concatStringsSep "-" (["update"] ++ basePath))
|
||||||
(builtins.concatStringsSep
|
(builtins.concatStringsSep
|
||||||
"\n"
|
"\n"
|
||||||
(pkgs.lib.mapAttrsToList invokeUpdater updaters)
|
(pkgs.lib.mapAttrsToList invokeUpdater updaters)
|
||||||
@@ -342,12 +329,9 @@
|
|||||||
- `nix run '.#update.feeds'`
|
- `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.{desko,lappy,moby,servo}[-light][.test]' [nixos-rebuild args ...]`
|
- `nix run '.#deploy-{lappy,moby,moby-test,servo}' [nixos-rebuild args ...]`
|
||||||
- `nix run '.#check'`
|
- `nix run '.#check'`
|
||||||
- make sure all systems build; NUR evaluates
|
- 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}
|
||||||
@@ -356,70 +340,48 @@
|
|||||||
nix flake show --option allow-import-from-derivation true
|
nix flake show --option allow-import-from-derivation true
|
||||||
'');
|
'');
|
||||||
};
|
};
|
||||||
# wrangle some names to get package updaters which refer back into the flake, but also conditionally ignore certain paths (e.g. sane.feeds).
|
update.pkgs = mkUpdaters { ignore = [ ["feeds"] ]; } [];
|
||||||
# TODO: better design
|
update.feeds = mkUpdaters {} [ "feeds" ];
|
||||||
update = rec {
|
|
||||||
_impl.pkgs.sane = mkUpdaters { flakePrefix = [ "update" "_impl" "pkgs" ]; ignore = [ [ "sane" "feeds" ] ]; } [ "sane" ];
|
|
||||||
pkgs = _impl.pkgs.sane;
|
|
||||||
_impl.feeds.sane.feeds = mkUpdaters { flakePrefix = [ "update" "_impl" "feeds" ]; } [ "sane" "feeds" ];
|
|
||||||
feeds = _impl.feeds.sane.feeds;
|
|
||||||
};
|
|
||||||
|
|
||||||
init-feed = {
|
init-feed = {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = "${pkgs.feeds.init-feed}";
|
program = "${pkgs.feeds.init-feed}";
|
||||||
};
|
};
|
||||||
|
|
||||||
deploy = {
|
deploy-lappy = {
|
||||||
lappy = deployApp "lappy" "lappy" "switch";
|
|
||||||
lappy-light = deployApp "lappy-light" "lappy" "switch";
|
|
||||||
moby = deployApp "moby" "moby" "switch";
|
|
||||||
moby-light = deployApp "moby-light" "moby" "switch";
|
|
||||||
moby-test = deployApp "moby" "moby" "test";
|
|
||||||
servo = deployApp "servo" "servo" "switch";
|
|
||||||
};
|
|
||||||
|
|
||||||
sync = {
|
|
||||||
type = "app";
|
type = "app";
|
||||||
program = builtins.toString (pkgs.writeShellScript "sync-all" ''
|
program = ''${deployScript "lappy" "lappy" "switch"}'';
|
||||||
RC_lappy=$(nix run '.#sync.lappy' -- "$@")
|
|
||||||
RC_moby=$(nix run '.#sync.moby' -- "$@")
|
|
||||||
RC_desko=$(nix run '.#sync.desko' -- "$@")
|
|
||||||
|
|
||||||
echo "lappy: $RC_lappy"
|
|
||||||
echo "moby: $RC_moby"
|
|
||||||
echo "desko: $RC_desko"
|
|
||||||
'');
|
|
||||||
};
|
};
|
||||||
|
deploy-moby-test = {
|
||||||
sync.desko = {
|
|
||||||
# copy music from servo to desko
|
|
||||||
# can run this from any device that has ssh access to desko and servo
|
|
||||||
type = "app";
|
type = "app";
|
||||||
program = builtins.toString (pkgs.writeShellScript "sync-to-desko" ''
|
program = ''${deployScript "moby" "moby-hn" "test"}'';
|
||||||
sudo mount /mnt/desko-home
|
|
||||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music --compat /mnt/servo-media/Music /mnt/desko-home/Music "$@"
|
|
||||||
'');
|
|
||||||
};
|
};
|
||||||
|
deploy-moby = {
|
||||||
sync.lappy = {
|
|
||||||
# copy music from servo to lappy
|
|
||||||
# can run this from any device that has ssh access to lappy and servo
|
|
||||||
type = "app";
|
type = "app";
|
||||||
program = builtins.toString (pkgs.writeShellScript "sync-to-lappy" ''
|
program = ''${deployScript "moby" "moby-hn" "switch"}'';
|
||||||
sudo mount /mnt/lappy-home
|
};
|
||||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music --compress --compat /mnt/servo-media/Music /mnt/lappy-home/Music "$@"
|
deploy-servo = {
|
||||||
'');
|
type = "app";
|
||||||
|
program = ''${deployScript "servo" "servo" "switch"}'';
|
||||||
};
|
};
|
||||||
|
|
||||||
sync.moby = {
|
sync-moby = {
|
||||||
# copy music from servo to moby
|
# copy music from the current device to moby
|
||||||
# can run this from any device that has ssh access to moby and servo
|
# TODO: should i actually sync from /mnt/servo-media/Music instead of the local drive?
|
||||||
type = "app";
|
type = "app";
|
||||||
program = builtins.toString (pkgs.writeShellScript "sync-to-moby" ''
|
program = builtins.toString (pkgs.writeShellScript "sync-to-moby" ''
|
||||||
sudo mount /mnt/moby-home
|
sudo mount /mnt/moby-home
|
||||||
# N.B.: limited by network/disk -> reduce job count to improve pause/resume behavior
|
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music ~/Music /mnt/moby-home/Music
|
||||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music --compress --compat --jobs 4 /mnt/servo-media/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
|
||||||
'');
|
'');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -428,14 +390,11 @@
|
|||||||
program = builtins.toString (pkgs.writeShellScript "check-all" ''
|
program = builtins.toString (pkgs.writeShellScript "check-all" ''
|
||||||
nix run '.#check.nur'
|
nix run '.#check.nur'
|
||||||
RC0=$?
|
RC0=$?
|
||||||
nix run '.#check.hostConfigs'
|
nix run '.#check.host-configs'
|
||||||
RC1=$?
|
RC1=$?
|
||||||
nix run '.#check.rescue'
|
|
||||||
RC2=$?
|
|
||||||
echo "nur: $RC0"
|
echo "nur: $RC0"
|
||||||
echo "hostConfigs: $RC1"
|
echo "host-configs: $RC1"
|
||||||
echo "rescue: $RC2"
|
exit $(($RC0 | $RC1))
|
||||||
exit $(($RC0 | $RC1 | $RC2))
|
|
||||||
'');
|
'');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -450,70 +409,36 @@
|
|||||||
--option restrict-eval true \
|
--option restrict-eval true \
|
||||||
--option allow-import-from-derivation true \
|
--option allow-import-from-derivation true \
|
||||||
--drv-path --show-trace \
|
--drv-path --show-trace \
|
||||||
-I nixpkgs=${nixpkgs-unpatched} \
|
-I nixpkgs=$(nix-instantiate --find-file nixpkgs) \
|
||||||
-I ../../ \
|
-I ../../ \
|
||||||
| tee # tee to prevent interactive mode
|
| tee # tee to prevent interactive mode
|
||||||
'');
|
'');
|
||||||
};
|
};
|
||||||
|
|
||||||
check.hostConfigs = {
|
check.host-configs = {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = let
|
program = let
|
||||||
checkHost = host: let
|
checkHost = host: ''
|
||||||
shellHost = pkgs.lib.replaceStrings [ "-" ] [ "_" ] host;
|
nix build '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} -j2 $@
|
||||||
in ''
|
RC_${host}=$?
|
||||||
nix build -v '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} -j2 "$@"
|
|
||||||
RC_${shellHost}=$?
|
|
||||||
'';
|
'';
|
||||||
in builtins.toString (pkgs.writeShellScript
|
in builtins.toString (pkgs.writeShellScript
|
||||||
"check-host-configs"
|
"check-host-configs"
|
||||||
''
|
''
|
||||||
# build minimally-usable hosts first, then their full image.
|
|
||||||
# this gives me a minimal image i can deploy or copy over, early.
|
|
||||||
${checkHost "desko-light"}
|
|
||||||
${checkHost "moby-light"}
|
|
||||||
${checkHost "lappy-light"}
|
|
||||||
|
|
||||||
${checkHost "desko"}
|
${checkHost "desko"}
|
||||||
${checkHost "lappy"}
|
${checkHost "lappy"}
|
||||||
${checkHost "servo"}
|
${checkHost "servo"}
|
||||||
${checkHost "moby"}
|
${checkHost "moby"}
|
||||||
${checkHost "rescue"}
|
${checkHost "rescue"}
|
||||||
|
|
||||||
# still want to build the -light variants first so as to avoid multiple simultaneous webkitgtk builds
|
|
||||||
${checkHost "desko-light-next"}
|
|
||||||
${checkHost "moby-light-next"}
|
|
||||||
|
|
||||||
${checkHost "desko-next"}
|
|
||||||
${checkHost "lappy-next"}
|
|
||||||
${checkHost "servo-next"}
|
|
||||||
${checkHost "moby-next"}
|
|
||||||
${checkHost "rescue-next"}
|
|
||||||
|
|
||||||
echo "desko: $RC_desko"
|
echo "desko: $RC_desko"
|
||||||
echo "lappy: $RC_lappy"
|
echo "lappy: $RC_lappy"
|
||||||
echo "servo: $RC_servo"
|
echo "servo: $RC_servo"
|
||||||
echo "moby: $RC_moby"
|
echo "moby: $RC_moby"
|
||||||
echo "rescue: $RC_rescue"
|
echo "rescue: $RC_rescue"
|
||||||
|
|
||||||
echo "desko-next: $RC_desko_next"
|
|
||||||
echo "lappy-next: $RC_lappy_next"
|
|
||||||
echo "servo-next: $RC_servo_next"
|
|
||||||
echo "moby-next: $RC_moby_next"
|
|
||||||
echo "rescue-next: $RC_rescue_next"
|
|
||||||
|
|
||||||
# i don't really care if the -next hosts fail. i build them mostly to keep the cache fresh/ready
|
|
||||||
exit $(($RC_desko | $RC_lappy | $RC_servo | $RC_moby | $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
|
|
||||||
'');
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
templates = {
|
templates = {
|
||||||
|
@@ -9,34 +9,25 @@
|
|||||||
# services.distccd.enable = true;
|
# services.distccd.enable = true;
|
||||||
# sane.programs.distcc.enableFor.user.guest = true;
|
# sane.programs.distcc.enableFor.user.guest = true;
|
||||||
|
|
||||||
# TODO: remove emulation, but need to fix nixos-rebuild to moby for that.
|
|
||||||
# sane.roles.build-machine.emulation = true;
|
|
||||||
|
|
||||||
sops.secrets.colin-passwd.neededForUsers = true;
|
sops.secrets.colin-passwd.neededForUsers = true;
|
||||||
|
|
||||||
sane.ports.openFirewall = true; # for e.g. nix-serve
|
|
||||||
|
|
||||||
sane.roles.build-machine.enable = true;
|
sane.roles.build-machine.enable = true;
|
||||||
|
sane.roles.ac = true;
|
||||||
sane.roles.client = true;
|
sane.roles.client = true;
|
||||||
sane.roles.dev-machine = true;
|
sane.roles.dev-machine = true;
|
||||||
sane.roles.pc = true;
|
|
||||||
sane.services.wg-home.enable = true;
|
sane.services.wg-home.enable = true;
|
||||||
sane.services.wg-home.ip = config.sane.hosts.by-name."desko".wg-home.ip;
|
sane.services.wg-home.ip = config.sane.hosts.by-name."desko".wg-home.ip;
|
||||||
sane.services.duplicity.enable = true;
|
sane.services.duplicity.enable = true;
|
||||||
sane.services.nixserve.secretKeyFile = config.sops.secrets.nix_serve_privkey.path;
|
sane.services.nixserve.secretKeyFile = config.sops.secrets.nix_serve_privkey.path;
|
||||||
|
|
||||||
sane.nixcache.substituters.desko = false;
|
|
||||||
sane.nixcache.remote-builders.desko = false;
|
|
||||||
|
|
||||||
sane.gui.sway.enable = true;
|
sane.gui.sway.enable = true;
|
||||||
sane.programs.iphoneUtils.enableFor.user.colin = true;
|
sane.programs.iphoneUtils.enableFor.user.colin = true;
|
||||||
sane.programs.steam.enableFor.user.colin = true;
|
sane.programs.steam.enableFor.user.colin = true;
|
||||||
|
|
||||||
|
sane.programs.guiApps.suggestedPrograms = [ "desktopGuiApps" ];
|
||||||
|
sane.programs.consoleUtils.suggestedPrograms = [ "consoleMediaUtils" "desktopConsoleUtils" ];
|
||||||
# sane.programs.devPkgs.enableFor.user.colin = true;
|
# sane.programs.devPkgs.enableFor.user.colin = true;
|
||||||
|
|
||||||
sane.programs.signal-desktop.config.autostart = true;
|
|
||||||
sane.programs."gnome.geary".config.autostart = 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 ];
|
||||||
|
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
|
sane.persist.root-on-tmpfs = true;
|
||||||
# increase /tmp space (defaults to 50% of RAM) for building large nix things.
|
# increase /tmp space (defaults to 50% of RAM) for building large nix things.
|
||||||
# a cross-compiled kernel, particularly, will easily use 30+GB of tmp
|
# a cross-compiled kernel, particularly, will easily use 30+GB of tmp
|
||||||
fileSystems."/tmp".options = [ "size=64G" ];
|
fileSystems."/tmp".options = [ "size=64G" ];
|
||||||
|
|
||||||
fileSystems."/nix" = {
|
fileSystems."/nix" = {
|
||||||
# device = "/dev/disk/by-uuid/0ab0770b-7734-4167-88d9-6e4e20bb2a56";
|
# device = "/dev/disk/by-uuid/985a0a32-da52-4043-9df7-615adec2e4ff";
|
||||||
device = "/dev/disk/by-uuid/845d85bf-761d-431b-a406-e6f20909154f";
|
device = "/dev/disk/by-uuid/0ab0770b-7734-4167-88d9-6e4e20bb2a56";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
options = [
|
options = [
|
||||||
"compress=zstd"
|
"compress=zstd"
|
||||||
@@ -16,8 +17,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/boot" = {
|
fileSystems."/boot" = {
|
||||||
# device = "/dev/disk/by-uuid/41B6-BAEF";
|
# device = "/dev/disk/by-uuid/CAA7-E7D2";
|
||||||
device = "/dev/disk/by-uuid/5049-9AFD";
|
device = "/dev/disk/by-uuid/41B6-BAEF";
|
||||||
fsType = "vfat";
|
fsType = "vfat";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
sane.roles.client = true;
|
sane.roles.client = true;
|
||||||
sane.roles.dev-machine = true;
|
sane.roles.dev-machine = true;
|
||||||
sane.roles.pc = true;
|
|
||||||
sane.services.wg-home.enable = true;
|
sane.services.wg-home.enable = true;
|
||||||
sane.services.wg-home.ip = config.sane.hosts.by-name."lappy".wg-home.ip;
|
sane.services.wg-home.ip = config.sane.hosts.by-name."lappy".wg-home.ip;
|
||||||
|
|
||||||
@@ -16,7 +15,11 @@
|
|||||||
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.programs.stepmania.enableFor.user.colin = true;
|
sane.programs.guiApps.suggestedPrograms = [
|
||||||
|
"desktopGuiApps"
|
||||||
|
"stepmania"
|
||||||
|
];
|
||||||
|
sane.programs.consoleUtils.suggestedPrograms = [ "consoleMediaUtils" "desktopConsoleUtils" ];
|
||||||
|
|
||||||
sops.secrets.colin-passwd.neededForUsers = true;
|
sops.secrets.colin-passwd.neededForUsers = true;
|
||||||
|
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
|
sane.persist.root-on-tmpfs = true;
|
||||||
|
|
||||||
fileSystems."/nix" = {
|
fileSystems."/nix" = {
|
||||||
device = "/dev/disk/by-uuid/75230e56-2c69-4e41-b03e-68475f119980";
|
device = "/dev/disk/by-uuid/75230e56-2c69-4e41-b03e-68475f119980";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
|
@@ -22,6 +22,7 @@
|
|||||||
# the device type informs (at least):
|
# the device type informs (at least):
|
||||||
# - SXMO_WIFI_MODULE
|
# - SXMO_WIFI_MODULE
|
||||||
# - SXMO_RTW_SCAN_INTERVAL
|
# - SXMO_RTW_SCAN_INTERVAL
|
||||||
|
# - SXMO_SYS_FILES
|
||||||
# - SXMO_TOUCHSCREEN_ID
|
# - SXMO_TOUCHSCREEN_ID
|
||||||
# - SXMO_MONITOR
|
# - SXMO_MONITOR
|
||||||
# - SXMO_ALSA_CONTROL_NAME
|
# - SXMO_ALSA_CONTROL_NAME
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
# and so it just wouldn't handle any button inputs (sxmo_hook_inputhandler.sh not on path)
|
# and so it just wouldn't handle any button inputs (sxmo_hook_inputhandler.sh not on path)
|
||||||
SXMO_DEVICE_NAME = "three_button_touchscreen";
|
SXMO_DEVICE_NAME = "three_button_touchscreen";
|
||||||
};
|
};
|
||||||
package = (pkgs.sxmo-utils.override { preferSystemd = true; }).overrideAttrs (base: {
|
package = (pkgs.sxmo-utils-latest.override { preferSystemd = true; }).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
|
||||||
|
@@ -20,10 +20,15 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
sane.roles.client = true;
|
sane.roles.client = true;
|
||||||
sane.roles.handheld = true;
|
|
||||||
sane.zsh.showDeadlines = false; # unlikely to act on them when in shell
|
sane.zsh.showDeadlines = false; # unlikely to act on them when in shell
|
||||||
sane.services.wg-home.enable = true;
|
sane.services.wg-home.enable = true;
|
||||||
sane.services.wg-home.ip = config.sane.hosts.by-name."moby".wg-home.ip;
|
sane.services.wg-home.ip = config.sane.hosts.by-name."moby".wg-home.ip;
|
||||||
|
sane.wowlan.enable = true;
|
||||||
|
sane.wowlan.patterns = [
|
||||||
|
{ ipv4.destPort = 22; } # wake on SSH
|
||||||
|
{ ipv4.srcPort = 2587; } # wake on `ntfy-sh` push from servo
|
||||||
|
{ arp.queryIp = [ 10 78 79 54 ]; } # wake when somebody is doing an ARP query against us
|
||||||
|
];
|
||||||
|
|
||||||
# XXX colin: phosh doesn't work well with passwordless login,
|
# XXX colin: phosh doesn't work well with passwordless login,
|
||||||
# so set this more reliable default password should anything go wrong
|
# so set this more reliable default password should anything go wrong
|
||||||
@@ -33,27 +38,25 @@
|
|||||||
sops.secrets.colin-passwd.neededForUsers = true;
|
sops.secrets.colin-passwd.neededForUsers = true;
|
||||||
|
|
||||||
sane.gui.sxmo.enable = true;
|
sane.gui.sxmo.enable = true;
|
||||||
|
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.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.nvme-cli.enableFor.system = false; # does not cross compile (libhugetlbfs)
|
sane.programs.tuiApps.enableFor.user.colin = false; # visidata, others, don't compile well
|
||||||
|
# disabled for faster deploys
|
||||||
# enabled for easier debugging
|
sane.programs.soundconverter.enableFor.user.colin = false;
|
||||||
sane.programs.eg25-control.enableFor.user.colin = true;
|
sane.programs.eg25-control.enableFor.user.colin = true;
|
||||||
sane.programs.rtl8723cs-wowlan.enableFor.user.colin = true;
|
|
||||||
|
|
||||||
# sane.programs.ntfy-sh.config.autostart = true;
|
sane.programs.ntfy-sh.config.autostart = true;
|
||||||
sane.programs.dino.config.autostart = true;
|
sane.programs.dino.config.autostart = true;
|
||||||
sane.programs.signal-desktop.config.autostart = true;
|
|
||||||
# sane.programs."gnome.geary".config.autostart = true;
|
|
||||||
# sane.programs.calls.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
|
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
|
||||||
|
|
||||||
# note the .conf.d approach: using ~/.config/pipewire/pipewire.conf directly breaks all audio,
|
# note the .conf.d approach: using ~/.config/pipewire/pipewire.conf directly breaks all audio,
|
||||||
# presumably because that deletes the defaults entirely whereas the .conf.d approach selectively overrides defaults
|
# presumably because that deletes the defaults entirely whereas the .conf.d approach selectively overrides defaults
|
||||||
@@ -82,6 +85,8 @@
|
|||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
# sane.programs.mpv.enableFor.user.colin = true;
|
||||||
|
|
||||||
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.
|
||||||
# even 10 can be too much
|
# even 10 can be too much
|
||||||
@@ -90,6 +95,16 @@
|
|||||||
# 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.
|
# 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`
|
# these files are visible from userspace by following `/sys/module/firmware_class/parameters/path`
|
||||||
@@ -100,14 +115,7 @@
|
|||||||
# 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 = [
|
hardware.firmware = [ pkgs.linux-firmware-megous ];
|
||||||
(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";
|
||||||
|
|
||||||
@@ -125,12 +133,7 @@
|
|||||||
# 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 {
|
|
||||||
# internal speaker has a tendency to break :(
|
|
||||||
preferEarpiece = true;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
systemd = let
|
systemd = let
|
||||||
ucm-env = config.environment.variables.ALSA_CONFIG_UCM2;
|
ucm-env = config.environment.variables.ALSA_CONFIG_UCM2;
|
||||||
in {
|
in {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
|
sane.persist.root-on-tmpfs = true;
|
||||||
fileSystems."/nix" = {
|
fileSystems."/nix" = {
|
||||||
device = "/dev/disk/by-uuid/1f1271f8-53ce-4081-8a29-60a4a6b5d6f9";
|
device = "/dev/disk/by-uuid/1f1271f8-53ce-4081-8a29-60a4a6b5d6f9";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
|
@@ -66,25 +66,6 @@ in
|
|||||||
# 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>
|
|
||||||
# TODO(2023/12/03): remove once mesa 23.3.1 lands: <https://github.com/NixOS/nixpkgs/pull/265740>
|
|
||||||
#
|
|
||||||
# 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;
|
services.xserver.displayManager.job.preStart = ensureHWReady;
|
||||||
systemd.services.greetd.preStart = ensureHWReady;
|
systemd.services.greetd.preStart = ensureHWReady;
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,92 @@
|
|||||||
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
|
### 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 '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)
|
||||||
|
];
|
||||||
DEFAULT_COUNTRY = "US";
|
DEFAULT_COUNTRY = "US";
|
||||||
|
|
||||||
SXMO_AUTOROTATE = "1"; # enable auto-rotation at launch. has no meaning in stock/upstream sxmo-utils
|
SXMO_AUTOROTATE = "1"; # enable auto-rotation at launch. has no meaning in stock/upstream sxmo-utils
|
||||||
@@ -56,22 +141,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
|
||||||
|
|
||||||
|
@@ -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";
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
fileSystems."/nix" = {
|
fileSystems."/" = {
|
||||||
device = "/dev/disk/by-uuid/44445555-6666-7777-8888-999900001111";
|
device = "/dev/disk/by-uuid/44445555-6666-7777-8888-999900001111";
|
||||||
fsType = "ext4";
|
fsType = "ext4";
|
||||||
};
|
};
|
||||||
|
@@ -14,11 +14,12 @@
|
|||||||
signaldctl.enableFor.user.colin = true;
|
signaldctl.enableFor.user.colin = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sane.roles.ac = true;
|
||||||
sane.roles.build-machine.enable = true;
|
sane.roles.build-machine.enable = true;
|
||||||
|
sane.roles.build-machine.emulation = false;
|
||||||
sane.zsh.showDeadlines = false; # ~/knowledge doesn't always exist
|
sane.zsh.showDeadlines = false; # ~/knowledge doesn't always exist
|
||||||
sane.programs.consoleUtils.suggestedPrograms = [
|
sane.programs.consoleUtils.suggestedPrograms = [
|
||||||
"consoleMediaUtils" # notably, for go2tv / casting
|
"desktopConsoleUtils"
|
||||||
"pcConsoleUtils"
|
|
||||||
"sane-scripts.stop-all-servo"
|
"sane-scripts.stop-all-servo"
|
||||||
];
|
];
|
||||||
sane.services.dyn-dns.enable = true;
|
sane.services.dyn-dns.enable = true;
|
||||||
@@ -29,8 +30,6 @@
|
|||||||
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;
|
||||||
sane.nixcache.remote-builders.desko = false;
|
|
||||||
sane.nixcache.remote-builders.servo = false;
|
|
||||||
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade
|
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade
|
||||||
|
|
||||||
# automatically log in at the virtual consoles.
|
# automatically log in at the virtual consoles.
|
||||||
|
@@ -1,57 +1,7 @@
|
|||||||
# zfs docs:
|
|
||||||
# - <https://nixos.wiki/wiki/ZFS>
|
|
||||||
# - <repo:nixos/nixpkgs:nixos/modules/tasks/filesystems/zfs.nix>
|
|
||||||
#
|
|
||||||
# zfs check health: `zpool status`
|
|
||||||
#
|
|
||||||
# zfs pool creation (requires `boot.supportedFilesystems = [ "zfs" ];`
|
|
||||||
# - 1. identify disk IDs: `ls -l /dev/disk/by-id`
|
|
||||||
# - 2. pool these disks: `zpool create -f -m legacy pool raidz ata-ST4000VN008-2DR166_WDH0VB45 ata-ST4000VN008-2DR166_WDH17616 ata-ST4000VN008-2DR166_WDH0VC8Q ata-ST4000VN008-2DR166_WDH17680`
|
|
||||||
# - legacy documented: <https://superuser.com/questions/790036/what-is-a-zfs-legacy-mount-point>
|
|
||||||
#
|
|
||||||
# import pools: `zpool import pool`
|
|
||||||
# show zfs datasets: `zfs list` (will be empty if haven't imported)
|
|
||||||
# show zfs properties (e.g. compression): `zfs get all pool`
|
|
||||||
# set zfs properties: `zfs set compression=on pool`
|
|
||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# hostId: not used for anything except zfs guardrail?
|
sane.persist.root-on-tmpfs = true;
|
||||||
# [hex(ord(x)) for x in 'serv']
|
|
||||||
networking.hostId = "73657276";
|
|
||||||
boot.supportedFilesystems = [ "zfs" ];
|
|
||||||
# boot.zfs.enabled = true;
|
|
||||||
boot.zfs.forceImportRoot = false;
|
|
||||||
# scrub all zfs pools weekly:
|
|
||||||
services.zfs.autoScrub.enable = true;
|
|
||||||
boot.extraModprobeConfig = ''
|
|
||||||
# ZFS likes to use half the ram for its own cache and let the kernel push everything else to swap.
|
|
||||||
# so, reduce its cache size
|
|
||||||
# see: <https://askubuntu.com/a/1290387>
|
|
||||||
# see: <https://serverfault.com/a/1119083>
|
|
||||||
# see: <https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max>
|
|
||||||
# for all tunables, see: `man 4 zfs`
|
|
||||||
# to update these parameters without rebooting:
|
|
||||||
# - `echo '4294967296' | sane-sudo-redirect /sys/module/zfs/parameters/zfs_arc_max`
|
|
||||||
options zfs zfs_arc_max=4294967296
|
|
||||||
'';
|
|
||||||
# to be able to mount the pool like this, make sure to tell zfs to NOT manage it itself.
|
|
||||||
# otherwise local-fs.target will FAIL and you will be dropped into a rescue shell.
|
|
||||||
# - `zfs set mountpoint=legacy pool`
|
|
||||||
# if done correctly, the pool can be mounted before this `fileSystems` entry is created:
|
|
||||||
# - `sudo mount -t zfs pool /mnt/persist/pool`
|
|
||||||
fileSystems."/mnt/pool" = {
|
|
||||||
device = "pool";
|
|
||||||
fsType = "zfs";
|
|
||||||
};
|
|
||||||
# services.zfs.zed = ... # TODO: zfs can send me emails when disks fail
|
|
||||||
sane.programs.sysadminUtils.suggestedPrograms = [ "zfs" ];
|
|
||||||
|
|
||||||
sane.persist.stores."ext" = {
|
|
||||||
origin = "/mnt/pool/persist";
|
|
||||||
storeDescription = "external HDD storage";
|
|
||||||
};
|
|
||||||
|
|
||||||
# increase /tmp space (defaults to 50% of RAM) for building large nix things.
|
# increase /tmp space (defaults to 50% of RAM) for building large nix things.
|
||||||
# even the stock `nixpkgs.linux` consumes > 16 GB of tmp
|
# even the stock `nixpkgs.linux` consumes > 16 GB of tmp
|
||||||
fileSystems."/tmp".options = [ "size=32G" ];
|
fileSystems."/tmp".options = [ "size=32G" ];
|
||||||
@@ -71,7 +21,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# slow, external storage (for archiving, etc)
|
# slow, external storage (for archiving, etc)
|
||||||
fileSystems."/mnt/usb-hdd" = {
|
fileSystems."/mnt/persist/ext" = {
|
||||||
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
options = [
|
options = [
|
||||||
@@ -79,9 +29,14 @@
|
|||||||
"defaults"
|
"defaults"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
sane.fs."/mnt/usb-hdd".mount = {};
|
|
||||||
|
|
||||||
sane.persist.sys.byStore.plaintext = [
|
sane.persist.stores."ext" = {
|
||||||
|
origin = "/mnt/persist/ext/persist";
|
||||||
|
storeDescription = "external HDD storage";
|
||||||
|
};
|
||||||
|
sane.fs."/mnt/persist/ext".mount = {};
|
||||||
|
|
||||||
|
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"; }
|
||||||
];
|
];
|
||||||
@@ -114,7 +69,7 @@
|
|||||||
the contents should be a subset of what's in ../media/datasets.
|
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.byStore.ext = [
|
sane.persist.sys.ext = [
|
||||||
{
|
{
|
||||||
user = "colin";
|
user = "colin";
|
||||||
group = "users";
|
group = "users";
|
||||||
|
@@ -1,24 +1,6 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
{ config, pkgs, ... }:
|
||||||
|
|
||||||
let
|
|
||||||
portOpts = with lib; types.submodule {
|
|
||||||
options = {
|
|
||||||
visibleTo.ovpn = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
options = with lib; {
|
|
||||||
sane.ports.ports = mkOption {
|
|
||||||
# add the `visibleTo.ovpn` option
|
|
||||||
type = types.attrsOf portOpts;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
networking.domain = "uninsane.org";
|
networking.domain = "uninsane.org";
|
||||||
|
|
||||||
sane.ports.openFirewall = true;
|
sane.ports.openFirewall = true;
|
||||||
@@ -27,7 +9,9 @@ in
|
|||||||
# view refused packets with: `sudo journalctl -k`
|
# view refused packets with: `sudo journalctl -k`
|
||||||
# networking.firewall.logRefusedPackets = true;
|
# networking.firewall.logRefusedPackets = true;
|
||||||
|
|
||||||
# these useDHCP lines are legacy from the auto-generated config. might be safe to remove now?
|
# The global useDHCP flag is deprecated, therefore explicitly set to false here.
|
||||||
|
# Per-interface useDHCP will be mandatory in the future, so this generated config
|
||||||
|
# replicates the default behaviour.
|
||||||
networking.useDHCP = false;
|
networking.useDHCP = false;
|
||||||
networking.interfaces.eth0.useDHCP = true;
|
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
|
# XXX colin: probably don't need this. wlan0 won't be populated unless i touch a value in networking.interfaces.wlan0
|
||||||
@@ -42,6 +26,41 @@ in
|
|||||||
# "9.9.9.9"
|
# "9.9.9.9"
|
||||||
# ];
|
# ];
|
||||||
|
|
||||||
|
# use systemd's stub resolver.
|
||||||
|
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
|
||||||
|
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets
|
||||||
|
# in the ovnps namespace to use the provider's DNS resolvers.
|
||||||
|
# a weakness is we can only query 1 NS at a time (unless we were to clone the packets?)
|
||||||
|
# there also seems to be some cache somewhere that's shared between the two namespaces.
|
||||||
|
# i think this is a libc thing. might need to leverage proper cgroups to _really_ kill it.
|
||||||
|
# - getent ahostsv4 www.google.com
|
||||||
|
# - try fix: <https://serverfault.com/questions/765989/connect-to-3rd-party-vpn-server-but-dont-use-it-as-the-default-route/766290#766290>
|
||||||
|
services.resolved.enable = true;
|
||||||
|
# without DNSSEC:
|
||||||
|
# - dig matrix.org => works
|
||||||
|
# - curl https://matrix.org => works
|
||||||
|
# with default DNSSEC:
|
||||||
|
# - dig matrix.org => works
|
||||||
|
# - curl https://matrix.org => fails
|
||||||
|
# i don't know why. this might somehow be interfering with the DNS run on this device (trust-dns)
|
||||||
|
services.resolved.dnssec = "false";
|
||||||
|
networking.nameservers = [
|
||||||
|
# use systemd-resolved resolver
|
||||||
|
# full resolver (which understands /etc/hosts) lives on 127.0.0.53
|
||||||
|
# stub resolver (just forwards upstream) lives on 127.0.0.54
|
||||||
|
"127.0.0.53"
|
||||||
|
];
|
||||||
|
|
||||||
|
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
|
||||||
|
# in a way that's unaware of my VPN routing, so routes are frequently poor against
|
||||||
|
# services which advertise different IPs based on geolocation.
|
||||||
|
# nscd claims to be usable without a cache, but in practice i can't get it to not cache!
|
||||||
|
# nsncd is the Name Service NON-Caching Daemon. it's a drop-in that doesn't cache;
|
||||||
|
# this is OK on the host -- because systemd-resolved caches. it's probably sub-optimal
|
||||||
|
# in the netns and we query upstream DNS more often than needed. hm.
|
||||||
|
# TODO: run a separate recursive resolver in each namespace.
|
||||||
|
services.nscd.enableNsncd = true;
|
||||||
|
|
||||||
# services.resolved.extraConfig = ''
|
# services.resolved.extraConfig = ''
|
||||||
# # docs: `man resolved.conf`
|
# # docs: `man resolved.conf`
|
||||||
# # DNS servers to use via the `wg-ovpns` interface.
|
# # DNS servers to use via the `wg-ovpns` interface.
|
||||||
@@ -64,14 +83,6 @@ in
|
|||||||
vpn-ip = "185.157.162.178";
|
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
|
# 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";
|
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 {
|
in {
|
||||||
privateKeyFile = config.sops.secrets.wg_ovpns_privkey.path;
|
privateKeyFile = config.sops.secrets.wg_ovpns_privkey.path;
|
||||||
# wg is active only in this namespace.
|
# wg is active only in this namespace.
|
||||||
@@ -98,10 +109,10 @@ in
|
|||||||
# dynamicEndpointRefreshRestartSeconds = 5;
|
# dynamicEndpointRefreshRestartSeconds = 5;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
preSetup = ''
|
preSetup = "" + ''
|
||||||
${ip} netns add ovpns || echo "ovpns already exists"
|
${ip} netns add ovpns || echo "ovpns already exists"
|
||||||
'';
|
'';
|
||||||
postShutdown = ''
|
postShutdown = "" + ''
|
||||||
${in-ns} ip link del ovpns-veth-b || echo "couldn't delete ovpns-veth-b"
|
${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} link del ovpns-veth-a || echo "couldn't delete ovpns-veth-a"
|
||||||
${ip} netns delete ovpns || echo "couldn't delete ovpns"
|
${ip} netns delete ovpns || echo "couldn't delete ovpns"
|
||||||
@@ -111,7 +122,7 @@ in
|
|||||||
${ip} rule add from all lookup local pref 0
|
${ip} rule add from all lookup local pref 0
|
||||||
${ip} rule del from all lookup local pref 100
|
${ip} rule del from all lookup local pref 100
|
||||||
'';
|
'';
|
||||||
postSetup = ''
|
postSetup = "" + ''
|
||||||
# DOCS:
|
# DOCS:
|
||||||
# - some of this approach is described here: <https://josephmuia.ca/2018-05-16-net-namespaces-veth-nat/>
|
# - some of this approach is described here: <https://josephmuia.ca/2018-05-16-net-namespaces-veth-nat/>
|
||||||
# - iptables primer: <https://danielmiessler.com/study/iptables/>
|
# - iptables primer: <https://danielmiessler.com/study/iptables/>
|
||||||
@@ -135,11 +146,25 @@ in
|
|||||||
${ip} rule add from all lookup local pref 100
|
${ip} rule add from all lookup local pref 100
|
||||||
${ip} rule del from all lookup local pref 0
|
${ip} rule del from all lookup local pref 0
|
||||||
|
|
||||||
|
# bridge HTTP traffic:
|
||||||
|
# any external port-80 request sent to the VPN addr will be forwarded to the rootns.
|
||||||
|
# this exists so LetsEncrypt can procure a cert for the MX over http.
|
||||||
|
# TODO: we could use _acme_challence.mx.uninsane.org CNAME to avoid this forwarding
|
||||||
|
# - <https://community.letsencrypt.org/t/where-does-letsencrypt-resolve-dns-from/37607/8>
|
||||||
|
${in-ns} ${iptables} -A PREROUTING -t nat -p tcp --dport 80 -m iprange --dst-range ${vpn-ip} \
|
||||||
|
-j DNAT --to-destination ${veth-host-ip}:80
|
||||||
|
|
||||||
|
# we also bridge DNS traffic
|
||||||
|
${in-ns} ${iptables} -A PREROUTING -t nat -p udp --dport 53 -m iprange --dst-range ${vpn-ip} \
|
||||||
|
-j DNAT --to-destination ${veth-host-ip}
|
||||||
|
${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
|
# 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.
|
# - 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 \
|
${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
|
-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
|
# create a new routing table that we can use to proxy traffic out of the root namespace
|
||||||
@@ -192,5 +217,4 @@ in
|
|||||||
# # test with:
|
# # test with:
|
||||||
# # curl --interface hurricane http://[2607:f8b0:400a:80b::2004]
|
# # curl --interface hurricane http://[2607:f8b0:400a:80b::2004]
|
||||||
# # ping 2607:f8b0:400a:80b::2004
|
# # ping 2607:f8b0:400a:80b::2004
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -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; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -1,124 +0,0 @@
|
|||||||
# TURN/STUN NAT traversal service
|
|
||||||
# commonly used to establish realtime calls with prosody, or possibly matrix/synapse
|
|
||||||
#
|
|
||||||
# - <https://github.com/coturn/coturn/>
|
|
||||||
# - `man turnserver`
|
|
||||||
# - config docs: <https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf>
|
|
||||||
#
|
|
||||||
# N.B. during operation it's NORMAL to see "error 401".
|
|
||||||
# during session creation:
|
|
||||||
# - client sends Allocate request
|
|
||||||
# - server replies error 401, providing a realm and nonce
|
|
||||||
# - client uses realm + nonce + shared secret to construct an auth key & call Allocate again
|
|
||||||
# - server replies Allocate Success Response
|
|
||||||
# - source: <https://stackoverflow.com/a/66643135>
|
|
||||||
#
|
|
||||||
# N.B. this safest implementation routes all traffic THROUGH A VPN
|
|
||||||
# - that adds a lot of latency, but in practice turns out to be inconsequential.
|
|
||||||
# i guess ICE allows clients to prefer the other party's lower-latency server, in practice?
|
|
||||||
# - still, this is the "safe" implementation because STUN works with IP addresses instead of domain names:
|
|
||||||
# 1. client A queries the STUN server to determine its own IP address/port.
|
|
||||||
# 2. client A tells client B which IP address/port client A is visible on.
|
|
||||||
# 3. client B contacts that IP address/port
|
|
||||||
# this only works so long as the IP address/port which STUN server sees client A on is publicly routable.
|
|
||||||
# that is NOT the case when the STUN server and client A are on the same LAN
|
|
||||||
# even if client A contacts the STUN server via its WAN address with port reflection enabled.
|
|
||||||
# hence, there's no obvious way to put the STUN server on the same LAN as either client and expect the rest to work.
|
|
||||||
{ lib, ... }:
|
|
||||||
let
|
|
||||||
# TODO: this range could be larger, but right now that's costly because each element is its own UPnP forward
|
|
||||||
# TURN port range (inclusive)
|
|
||||||
turnPortLow = 49152;
|
|
||||||
turnPortHigh = 49167;
|
|
||||||
turnPortRange = lib.range turnPortLow turnPortHigh;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
sane.ports.ports = lib.mkMerge ([
|
|
||||||
{
|
|
||||||
"3478" = {
|
|
||||||
# this is the "control" port.
|
|
||||||
# i.e. no client data is forwarded through it, but it's where clients request tunnels.
|
|
||||||
protocol = [ "tcp" "udp" ];
|
|
||||||
# visibleTo.lan = true;
|
|
||||||
# visibleTo.wan = true;
|
|
||||||
visibleTo.ovpn = true;
|
|
||||||
description = "colin-stun-turn";
|
|
||||||
};
|
|
||||||
"5349" = {
|
|
||||||
# the other port 3478 also supports TLS/DTLS, but presumably clients wanting TLS will default 5349
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
# visibleTo.lan = true;
|
|
||||||
# visibleTo.wan = true;
|
|
||||||
visibleTo.ovpn = true;
|
|
||||||
description = "colin-stun-turn-over-tls";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
] ++ (builtins.map
|
|
||||||
(port: {
|
|
||||||
"${builtins.toString port}" = let
|
|
||||||
count = port - turnPortLow + 1;
|
|
||||||
numPorts = turnPortHigh - turnPortLow + 1;
|
|
||||||
in {
|
|
||||||
protocol = [ "tcp" "udp" ];
|
|
||||||
# visibleTo.lan = true;
|
|
||||||
# visibleTo.wan = true;
|
|
||||||
visibleTo.ovpn = true;
|
|
||||||
description = "colin-turn-${builtins.toString count}-of-${builtins.toString numPorts}";
|
|
||||||
};
|
|
||||||
})
|
|
||||||
turnPortRange
|
|
||||||
));
|
|
||||||
|
|
||||||
services.nginx.virtualHosts."turn.uninsane.org" = {
|
|
||||||
# allow ACME to procure a cert via nginx for this domain
|
|
||||||
enableACME = true;
|
|
||||||
};
|
|
||||||
sane.dns.zones."uninsane.org".inet = {
|
|
||||||
# CNAME."turn" = "servo.wan";
|
|
||||||
# CNAME."turn" = "ovpns";
|
|
||||||
# CNAME."turn" = "native";
|
|
||||||
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
|
|
||||||
A."turn" = "%AOVPNS%";
|
|
||||||
# A."turn" = "%AWAN%";
|
|
||||||
|
|
||||||
SRV."_stun._udp" = "5 50 3478 turn";
|
|
||||||
SRV."_stun._tcp" = "5 50 3478 turn";
|
|
||||||
SRV."_stuns._tcp" = "5 50 5349 turn";
|
|
||||||
SRV."_turn._udp" = "5 50 3478 turn";
|
|
||||||
SRV."_turn._tcp" = "5 50 3478 turn";
|
|
||||||
SRV."_turns._tcp" = "5 50 5349 turn";
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.derived-secrets."/var/lib/coturn/shared_secret.bin" = {
|
|
||||||
encoding = "base64";
|
|
||||||
# TODO: make this not globally readable
|
|
||||||
acl.mode = "0644";
|
|
||||||
};
|
|
||||||
sane.fs."/var/lib/coturn/shared_secret.bin".wantedBeforeBy = [ "coturn.service" ];
|
|
||||||
|
|
||||||
# provide access to certs
|
|
||||||
users.users.turnserver.extraGroups = [ "nginx" ];
|
|
||||||
|
|
||||||
services.coturn.enable = true;
|
|
||||||
services.coturn.realm = "turn.uninsane.org";
|
|
||||||
services.coturn.cert = "/var/lib/acme/turn.uninsane.org/fullchain.pem";
|
|
||||||
services.coturn.pkey = "/var/lib/acme/turn.uninsane.org/key.pem";
|
|
||||||
services.coturn.use-auth-secret = true;
|
|
||||||
services.coturn.static-auth-secret-file = "/var/lib/coturn/shared_secret.bin";
|
|
||||||
services.coturn.lt-cred-mech = true;
|
|
||||||
services.coturn.min-port = turnPortLow;
|
|
||||||
services.coturn.max-port = turnPortHigh;
|
|
||||||
# services.coturn.secure-stun = true;
|
|
||||||
services.coturn.extraConfig = lib.concatStringsSep "\n" [
|
|
||||||
"verbose"
|
|
||||||
# "Verbose" #< even MORE verbosity than "verbose"
|
|
||||||
# "no-multicast-peers" # disables sending to IPv4 broadcast addresses (e.g. 224.0.0.0/3)
|
|
||||||
"listening-ip=10.0.1.5"
|
|
||||||
# "external-ip=185.157.162.178/10.0.1.5"
|
|
||||||
"external-ip=185.157.162.178"
|
|
||||||
# "listening-ip=10.78.79.51" # can be specified multiple times; omit for *
|
|
||||||
# "external-ip=97.113.128.229/10.78.79.51"
|
|
||||||
# "external-ip=97.113.128.229"
|
|
||||||
# "mobility" # "mobility with ICE (MICE) specs support" (?)
|
|
||||||
];
|
|
||||||
}
|
|
@@ -1,84 +0,0 @@
|
|||||||
# as of 2023/12/02: complete blockchain is 530 GiB (on-disk size may be larger)
|
|
||||||
#
|
|
||||||
# ports:
|
|
||||||
# - 8333: for node-to-node communications
|
|
||||||
# - 8332: rpc (client-to-node)
|
|
||||||
#
|
|
||||||
# rpc setup:
|
|
||||||
# - generate a password
|
|
||||||
# - use: <https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py>
|
|
||||||
# (rpcauth.py is not included in the `'.#bitcoin'` package result)
|
|
||||||
# - `wget https://raw.githubusercontent.com/bitcoin/bitcoin/master/share/rpcauth/rpcauth.py`
|
|
||||||
# - `python ./rpcauth.py colin`
|
|
||||||
# - copy the hash here. it's SHA-256, so safe to be public.
|
|
||||||
# - add "rpcuser=colin" and "rpcpassword=<output>" to secrets/servo/bitcoin.conf (i.e. ~/.bitcoin/bitcoin.conf)
|
|
||||||
# - bitcoin.conf docs: <https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md>
|
|
||||||
# - validate with `bitcoin-cli -netinfo`
|
|
||||||
{ config, lib, pkgs, sane-lib, ... }:
|
|
||||||
let
|
|
||||||
# wrapper to run bitcoind with the tor onion address as externalip (computed at runtime)
|
|
||||||
_bitcoindWithExternalIp = with pkgs; writeShellScriptBin "bitcoind" ''
|
|
||||||
externalip="$(cat /var/lib/tor/onion/bitcoind/hostname)"
|
|
||||||
exec ${bitcoind}/bin/bitcoind "-externalip=$externalip" "$@"
|
|
||||||
'';
|
|
||||||
# the package i provide to services.bitcoind ends up on system PATH, and used by other tools like clightning.
|
|
||||||
# therefore, even though services.bitcoind only needs `bitcoind` binary, provide all the other bitcoin-related binaries (notably `bitcoin-cli`) as well:
|
|
||||||
bitcoindWithExternalIp = with pkgs; symlinkJoin {
|
|
||||||
name = "bitcoind-with-external-ip";
|
|
||||||
paths = [ _bitcoindWithExternalIp bitcoind ];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
sane.persist.sys.byStore.ext = [
|
|
||||||
# /var/lib/monero/lmdb is what consumes most of the space
|
|
||||||
{ user = "bitcoind-mainnet"; group = "bitcoind-mainnet"; path = "/var/lib/bitcoind-mainnet"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
# sane.ports.ports."8333" = {
|
|
||||||
# # this allows other nodes and clients to download blocks from me.
|
|
||||||
# protocol = [ "tcp" ];
|
|
||||||
# visibleTo.wan = true;
|
|
||||||
# description = "colin-bitcoin";
|
|
||||||
# };
|
|
||||||
|
|
||||||
services.tor.relay.onionServices.bitcoind = {
|
|
||||||
version = 3;
|
|
||||||
map = [{
|
|
||||||
# by default tor will route public tor port P to 127.0.0.1:P.
|
|
||||||
# so if this port is the same as clightning would natively use, then no further config is needed here.
|
|
||||||
# see: <https://2019.www.torproject.org/docs/tor-manual.html.en#HiddenServicePort>
|
|
||||||
port = 8333;
|
|
||||||
# target.port; target.addr; #< set if tor port != clightning port
|
|
||||||
}];
|
|
||||||
# allow "tor" group (i.e. bitcoind-mainnet) to read /var/lib/tor/onion/bitcoind/hostname
|
|
||||||
settings.HiddenServiceDirGroupReadable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
services.bitcoind.mainnet = {
|
|
||||||
enable = true;
|
|
||||||
package = bitcoindWithExternalIp;
|
|
||||||
rpc.users.colin = {
|
|
||||||
# see docs at top of file for how to generate this
|
|
||||||
passwordHMAC = "30002c05d82daa210550e17a182db3f3$6071444151281e1aa8a2729f75e3e2d224e9d7cac3974810dab60e7c28ffaae4";
|
|
||||||
};
|
|
||||||
extraConfig = ''
|
|
||||||
# don't load the wallet, and disable wallet RPC calls
|
|
||||||
disablewallet=1
|
|
||||||
# proxy all outbound traffic through Tor
|
|
||||||
proxy=127.0.0.1:9050
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.bitcoind-mainnet.extraGroups = [ "tor" ];
|
|
||||||
|
|
||||||
systemd.services.bitcoind-mainnet.serviceConfig.RestartSec = "30s"; #< default is 0
|
|
||||||
|
|
||||||
sane.users.colin.fs.".bitcoin/bitcoin.conf" = sane-lib.fs.wantedSymlinkTo config.sops.secrets."bitcoin.conf".path;
|
|
||||||
sops.secrets."bitcoin.conf" = {
|
|
||||||
mode = "0600";
|
|
||||||
owner = "colin";
|
|
||||||
group = "users";
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.programs.bitcoind.enableFor.user.colin = true; # for debugging/administration: `bitcoin-cli`
|
|
||||||
}
|
|
@@ -1,766 +0,0 @@
|
|||||||
#!/usr/bin/env nix-shell
|
|
||||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ps.pyln-client ])"
|
|
||||||
|
|
||||||
# pyln-client docs: <https://github.com/ElementsProject/lightning/tree/master/contrib/pyln-client>
|
|
||||||
# terminology:
|
|
||||||
# - "scid": "Short Channel ID", e.g. 123456x7890x0
|
|
||||||
# from this id, we can locate the actual channel, its peers, and its parameters
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import math
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from pyln.client import LightningRpc, Millisatoshi, RpcError
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
RPC_FILE = "/var/lib/clightning/bitcoin/lightning-rpc"
|
|
||||||
# CLTV (HLTC delta) of the final hop
|
|
||||||
# set this too low and you might get inadvertent channel closures (?)
|
|
||||||
CLTV = 18
|
|
||||||
|
|
||||||
# for every sequentally failed transaction, delay this much before trying again.
|
|
||||||
# note that the initial route building process can involve 10-20 "transient" failures, as it discovers dead channels.
|
|
||||||
TX_FAIL_BACKOFF = 0.8
|
|
||||||
MAX_SEQUENTIAL_JOB_FAILURES = 200
|
|
||||||
|
|
||||||
class LoopError(Enum):
|
|
||||||
""" error when trying to loop sats, or when unable to calculate a route for the loop """
|
|
||||||
TRANSIENT = "TRANSIENT" # try again, we'll maybe find a different route
|
|
||||||
NO_ROUTE = "NO_ROUTE"
|
|
||||||
|
|
||||||
class RouteError(Enum):
|
|
||||||
""" error when calculated a route """
|
|
||||||
HAS_BASE_FEE = "HAS_BASE_FEE"
|
|
||||||
NO_ROUTE = "NO_ROUTE"
|
|
||||||
|
|
||||||
class Metrics:
|
|
||||||
looped_msat: int = 0
|
|
||||||
sendpay_fail: int = 0
|
|
||||||
sendpay_succeed: int = 0
|
|
||||||
own_bad_channel: int = 0
|
|
||||||
no_route: int = 0
|
|
||||||
in_ch_unsatisfiable: int = 0
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"looped:{self.looped_msat}, tx:{self.sendpay_succeed}, tx_fail:{self.sendpay_fail}, own_bad_ch:{self.own_bad_channel}, no_route:{self.no_route}, in_ch_restricted:{self.in_ch_unsatisfiable}"
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TxBounds:
|
|
||||||
max_msat: int
|
|
||||||
min_msat: int = 0
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"TxBounds({self.min_msat} <= msat <= {self.max_msat})"
|
|
||||||
|
|
||||||
def is_satisfiable(self) -> bool:
|
|
||||||
return self.min_msat <= self.max_msat
|
|
||||||
|
|
||||||
def raise_max_to_be_satisfiable(self) -> "Self":
|
|
||||||
if self.max_msat < self.min_msat:
|
|
||||||
logger.debug(f"raising max_msat to be consistent: {self.max_msat} -> {self.min_msat}")
|
|
||||||
return TxBounds(self.min_msat, self.min_msat)
|
|
||||||
return TxBounds(min_msat=self.min_msat, max_msat=self.max_msat)
|
|
||||||
|
|
||||||
def intersect(self, other: "TxBounds") -> "Self":
|
|
||||||
return TxBounds(
|
|
||||||
min_msat=max(self.min_msat, other.min_msat),
|
|
||||||
max_msat=min(self.max_msat, other.max_msat),
|
|
||||||
)
|
|
||||||
|
|
||||||
def restrict_to_htlc(self, ch: "LocalChannel", why: str = "") -> "Self":
|
|
||||||
"""
|
|
||||||
apply min/max HTLC size restrictions of the given channel.
|
|
||||||
"""
|
|
||||||
if ch:
|
|
||||||
why = why or ch.directed_scid_to_me
|
|
||||||
if why: why = f"{why}: "
|
|
||||||
|
|
||||||
new_min, new_max = self.min_msat, self.max_msat
|
|
||||||
if ch.htlc_minimum_to_me > self.min_msat:
|
|
||||||
new_min = ch.htlc_minimum_to_me
|
|
||||||
logger.debug(f"{why}raising min_msat due to HTLC requirements: {self.min_msat} -> {new_min}")
|
|
||||||
if ch.htlc_maximum_to_me < self.max_msat:
|
|
||||||
new_max = ch.htlc_maximum_to_me
|
|
||||||
logger.debug(f"{why}lowering max_msat due to HTLC requirements: {self.max_msat} -> {new_max}")
|
|
||||||
return TxBounds(min_msat=new_min, max_msat=new_max)
|
|
||||||
|
|
||||||
def restrict_to_zero_fees(self, ch: "LocalChannel"=None, base: int=0, ppm: int=0, why:str = "") -> "Self":
|
|
||||||
"""
|
|
||||||
restrict tx size such that PPM fees are zero.
|
|
||||||
if the channel has a base fee, then `max_msat` is forced to 0.
|
|
||||||
"""
|
|
||||||
if ch:
|
|
||||||
why = why or ch.directed_scid_to_me
|
|
||||||
self = self.restrict_to_zero_fees(base=ch.to_me["base_fee_millisatoshi"], ppm=ch.to_me["fee_per_millionth"], why=why)
|
|
||||||
|
|
||||||
if why: why = f"{why}: "
|
|
||||||
|
|
||||||
new_max = self.max_msat
|
|
||||||
ppm_max = math.ceil(1000000 / ppm) - 1 if ppm != 0 else new_max
|
|
||||||
if ppm_max < new_max:
|
|
||||||
logger.debug(f"{why}decreasing max_msat due to fee ppm: {new_max} -> {ppm_max}")
|
|
||||||
new_max = ppm_max
|
|
||||||
|
|
||||||
if base != 0:
|
|
||||||
logger.debug(f"{why}free route impossible: channel has base fees")
|
|
||||||
new_max = 0
|
|
||||||
|
|
||||||
return TxBounds(min_msat=self.min_msat, max_msat=new_max)
|
|
||||||
|
|
||||||
|
|
||||||
class LocalChannel:
|
|
||||||
def __init__(self, channels: list, rpc: "RpcHelper"):
|
|
||||||
assert 0 < len(channels) <= 2, f"unexpected: channel count: {channels}"
|
|
||||||
out = None
|
|
||||||
in_ = None
|
|
||||||
for c in channels:
|
|
||||||
if c["source"] == rpc.self_id:
|
|
||||||
assert out is None, f"unexpected: multiple channels from self: {channels}"
|
|
||||||
out = c
|
|
||||||
if c["destination"] == rpc.self_id:
|
|
||||||
assert in_ is None, f"unexpected: multiple channels to self: {channels}"
|
|
||||||
in_ = c
|
|
||||||
|
|
||||||
# assert out is not None, f"no channel from self: {channels}"
|
|
||||||
# assert in_ is not None, f"no channel to self: {channels}"
|
|
||||||
|
|
||||||
if out and in_:
|
|
||||||
assert out["destination"] == in_["source"], f"channel peers are asymmetric?! {channels}"
|
|
||||||
assert out["short_channel_id"] == in_["short_channel_id"], f"channel ids differ?! {channels}"
|
|
||||||
|
|
||||||
self.from_me = out
|
|
||||||
self.to_me = in_
|
|
||||||
self.remote_node = rpc.node(self.remote_peer)
|
|
||||||
self.peer_ch = rpc.peerchannel(self.scid, self.remote_peer)
|
|
||||||
self.forwards_from_me = rpc.rpc.listforwards(out_channel=self.scid, status="settled")["forwards"]
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return self.to_str(with_scid=True, with_bal_ratio=True, with_cost=False, with_ppm_theirs=False)
|
|
||||||
|
|
||||||
def to_str(
|
|
||||||
self,
|
|
||||||
with_peer_id:bool = False,
|
|
||||||
with_scid:bool = False,
|
|
||||||
with_bal_msat:bool = False,
|
|
||||||
with_bal_ratio:bool = False,
|
|
||||||
with_cost:bool = False,
|
|
||||||
with_ppm_theirs:bool = False,
|
|
||||||
with_ppm_mine:bool = False,
|
|
||||||
with_profits:bool = True,
|
|
||||||
with_payments:bool = False,
|
|
||||||
) -> str:
|
|
||||||
base_flag = "*" if not self.online or self.base_fee_to_me != 0 else ""
|
|
||||||
alias = f"({self.remote_alias}){base_flag}"
|
|
||||||
peerid = f" {self.remote_peer}" if with_peer_id else ""
|
|
||||||
scid = f" scid:{self.scid:>13}" if with_scid else ""
|
|
||||||
bal = f" S:{int(self.sendable):11}/R:{int(self.receivable):11}" if with_bal_msat else ""
|
|
||||||
ratio = f" MINE:{(100*self.send_ratio):>8.4f}%" if with_bal_ratio else ""
|
|
||||||
payments = f" OUT:{int(self.out_fulfilled_msat):>11}/IN:{int(self.in_fulfilled_msat):>11}" if with_payments else ""
|
|
||||||
profits = f" P$:{int(self.fees_lifetime_mine):>8}" if with_profits else ""
|
|
||||||
cost = f" COST:{self.opportunity_cost_lent:>8}" if with_cost else ""
|
|
||||||
ppm_theirs = self.ppm_to_me if self.to_me else "N/A"
|
|
||||||
ppm_theirs = f" PPM_THEIRS:{ppm_theirs:>6}" if with_ppm_theirs else ""
|
|
||||||
ppm_mine = self.ppm_from_me if self.from_me else "N/A"
|
|
||||||
ppm_mine = f" PPM_MINE:{ppm_mine:>6}" if with_ppm_mine else ""
|
|
||||||
return f"channel{alias:30}{peerid}{scid}{bal}{ratio}{payments}{profits}{cost}{ppm_theirs}{ppm_mine}"
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def online(self) -> bool:
|
|
||||||
return self.from_me and self.to_me
|
|
||||||
|
|
||||||
@property
|
|
||||||
def remote_peer(self) -> str:
|
|
||||||
if self.from_me:
|
|
||||||
return self.from_me["destination"]
|
|
||||||
else:
|
|
||||||
return self.to_me["source"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def remote_alias(self) -> str:
|
|
||||||
return self.remote_node["alias"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def scid(self) -> str:
|
|
||||||
if self.from_me:
|
|
||||||
return self.from_me["short_channel_id"]
|
|
||||||
else:
|
|
||||||
return self.to_me["short_channel_id"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def htlc_minimum_to_me(self) -> Millisatoshi:
|
|
||||||
return self.to_me["htlc_minimum_msat"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def htlc_minimum_from_me(self) -> Millisatoshi:
|
|
||||||
return self.from_me["htlc_minimum_msat"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def htlc_minimum(self) -> Millisatoshi:
|
|
||||||
return max(self.htlc_minimum_to_me, self.htlc_minimum_from_me)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def htlc_maximum_to_me(self) -> Millisatoshi:
|
|
||||||
return self.to_me["htlc_maximum_msat"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def htlc_maximum_from_me(self) -> Millisatoshi:
|
|
||||||
return self.from_me["htlc_maximum_msat"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def htlc_maximum(self) -> Millisatoshi:
|
|
||||||
return min(self.htlc_maximum_to_me, self.htlc_maximum_from_me)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def direction_to_me(self) -> int:
|
|
||||||
return self.to_me["direction"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def direction_from_me(self) -> int:
|
|
||||||
return self.from_me["direction"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def directed_scid_to_me(self) -> str:
|
|
||||||
return f"{self.scid}/{self.direction_to_me}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def directed_scid_from_me(self) -> str:
|
|
||||||
return f"{self.scid}/{self.direction_from_me}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def delay_them(self) -> str:
|
|
||||||
return self.to_me["delay"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def delay_me(self) -> str:
|
|
||||||
return self.from_me["delay"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ppm_to_me(self) -> int:
|
|
||||||
return self.to_me["fee_per_millionth"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ppm_from_me(self) -> int:
|
|
||||||
return self.from_me["fee_per_millionth"]
|
|
||||||
# return self.peer_ch["fee_proportional_millionths"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def base_fee_to_me(self) -> int:
|
|
||||||
return self.to_me["base_fee_millisatoshi"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def receivable(self) -> int:
|
|
||||||
return self.peer_ch["receivable_msat"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sendable(self) -> int:
|
|
||||||
return self.peer_ch["spendable_msat"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def in_fulfilled_msat(self) -> Millisatoshi:
|
|
||||||
return self.peer_ch["in_fulfilled_msat"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def out_fulfilled_msat(self) -> Millisatoshi:
|
|
||||||
return self.peer_ch["out_fulfilled_msat"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fees_lifetime_mine(self) -> Millisatoshi:
|
|
||||||
return sum(fwd["fee_msat"] for fwd in self.forwards_from_me)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def send_ratio(self) -> float:
|
|
||||||
cap = self.receivable + self.sendable
|
|
||||||
return self.sendable / cap
|
|
||||||
|
|
||||||
@property
|
|
||||||
def opportunity_cost_lent(self) -> int:
|
|
||||||
""" how much msat did we gain by pushing their channel to its current balance? """
|
|
||||||
return int(self.receivable * self.ppm_from_me / 1000000)
|
|
||||||
|
|
||||||
class RpcHelper:
|
|
||||||
def __init__(self, rpc: LightningRpc):
|
|
||||||
self.rpc = rpc
|
|
||||||
self.self_id = rpc.getinfo()["id"]
|
|
||||||
|
|
||||||
def localchannel(self, scid: str) -> LocalChannel:
|
|
||||||
listchan = self.rpc.listchannels(scid)
|
|
||||||
# this assertion would probably indicate a typo in the scid
|
|
||||||
assert listchan and listchan.get("channels", []) != [], f"bad listchannels for {scid}: {listchan}"
|
|
||||||
return LocalChannel(listchan["channels"], self)
|
|
||||||
|
|
||||||
def node(self, id: str) -> dict:
|
|
||||||
nodes = self.rpc.listnodes(id)["nodes"]
|
|
||||||
assert len(nodes) == 1, f"unexpected: multiple nodes for {id}: {nodes}"
|
|
||||||
return nodes[0]
|
|
||||||
|
|
||||||
def peerchannel(self, scid: str, peer_id: str) -> dict:
|
|
||||||
peerchannels = self.rpc.listpeerchannels(peer_id)["channels"]
|
|
||||||
channels = [c for c in peerchannels if c["short_channel_id"] == scid]
|
|
||||||
assert len(channels) == 1, f"expected exactly 1 channel, got: {channels}"
|
|
||||||
return channels[0]
|
|
||||||
|
|
||||||
def try_getroute(self, *args, **kwargs) -> dict | None:
|
|
||||||
""" wrapper for getroute which returns None instead of error if no route exists """
|
|
||||||
try:
|
|
||||||
route = self.rpc.getroute(*args, **kwargs)
|
|
||||||
except RpcError as e:
|
|
||||||
logger.debug(f"rpc failed: {e}")
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
route = route["route"]
|
|
||||||
if route == []: return None
|
|
||||||
return route
|
|
||||||
|
|
||||||
class LoopRouter:
|
|
||||||
def __init__(self, rpc: RpcHelper, metrics: Metrics = None):
|
|
||||||
self.rpc = rpc
|
|
||||||
self.metrics = metrics or Metrics()
|
|
||||||
self.bad_channels = [] # list of directed scid
|
|
||||||
self.nonzero_base_channels = [] # list of directed scid
|
|
||||||
|
|
||||||
def drop_caches(self) -> None:
|
|
||||||
logger.info("LoopRouter.drop_caches()")
|
|
||||||
self.bad_channels = []
|
|
||||||
|
|
||||||
def _get_directed_scid(self, scid: str, direction: int) -> dict:
|
|
||||||
channels = self.rpc.rpc.listchannels(scid)["channels"]
|
|
||||||
channels = [c for c in channels if c["direction"] == direction]
|
|
||||||
assert len(channels) == 1, f"expected exactly 1 channel: {channels}"
|
|
||||||
return channels[0]
|
|
||||||
|
|
||||||
def loop_once(self, out_scid: str, in_scid: str, bounds: TxBounds) -> LoopError|int:
|
|
||||||
out_ch = self.rpc.localchannel(out_scid)
|
|
||||||
in_ch = self.rpc.localchannel(in_scid)
|
|
||||||
|
|
||||||
if out_ch.directed_scid_from_me in self.bad_channels or in_ch.directed_scid_to_me in self.bad_channels:
|
|
||||||
logger.info(f"loop {out_scid} -> {in_scid} failed in our own channel")
|
|
||||||
self.metrics.own_bad_channel += 1
|
|
||||||
return LoopError.TRANSIENT
|
|
||||||
|
|
||||||
# bounds = bounds.restrict_to_htlc(out_ch) # htlc bounds seem to be enforced only in the outward direction
|
|
||||||
bounds = bounds.restrict_to_htlc(in_ch)
|
|
||||||
bounds = bounds.restrict_to_zero_fees(in_ch)
|
|
||||||
if not bounds.is_satisfiable():
|
|
||||||
self.metrics.in_ch_unsatisfiable += 1
|
|
||||||
return LoopError.NO_ROUTE
|
|
||||||
|
|
||||||
logger.debug(f"route with bounds {bounds}")
|
|
||||||
route = self.route(out_ch, in_ch, bounds)
|
|
||||||
logger.debug(f"route: {route}")
|
|
||||||
if route == RouteError.NO_ROUTE:
|
|
||||||
self.metrics.no_route += 1
|
|
||||||
return LoopError.NO_ROUTE
|
|
||||||
elif route == RouteError.HAS_BASE_FEE:
|
|
||||||
# try again with a different route
|
|
||||||
return LoopError.TRANSIENT
|
|
||||||
|
|
||||||
amount_msat = route[0]["amount_msat"]
|
|
||||||
invoice_id = f"loop-{time.time():.6f}".replace(".", "_")
|
|
||||||
invoice_desc = f"bal {out_scid}:{in_scid}"
|
|
||||||
invoice = self.rpc.rpc.invoice("any", invoice_id, invoice_desc)
|
|
||||||
logger.debug(f"invoice: {invoice}")
|
|
||||||
|
|
||||||
payment = self.rpc.rpc.sendpay(route, invoice["payment_hash"], invoice_id, amount_msat, invoice["bolt11"], invoice["payment_secret"])
|
|
||||||
logger.debug(f"sent: {payment}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
wait = self.rpc.rpc.waitsendpay(invoice["payment_hash"])
|
|
||||||
logger.debug(f"result: {wait}")
|
|
||||||
except RpcError as e:
|
|
||||||
self.metrics.sendpay_fail += 1
|
|
||||||
err_data = e.error["data"]
|
|
||||||
err_scid, err_dir = err_data["erring_channel"], err_data["erring_direction"]
|
|
||||||
err_directed_scid = f"{err_scid}/{err_dir}"
|
|
||||||
logger.debug(f"ch failed, adding to excludes: {err_directed_scid}; {e.error}")
|
|
||||||
self.bad_channels.append(err_directed_scid)
|
|
||||||
return LoopError.TRANSIENT
|
|
||||||
else:
|
|
||||||
self.metrics.sendpay_succeed += 1
|
|
||||||
self.metrics.looped_msat += int(amount_msat)
|
|
||||||
return int(amount_msat)
|
|
||||||
|
|
||||||
def route(self, out_ch: LocalChannel, in_ch: LocalChannel, bounds: TxBounds) -> list[dict] | RouteError:
|
|
||||||
exclude = [
|
|
||||||
# ensure the payment doesn't cross either channel in reverse.
|
|
||||||
# note that this doesn't preclude it from taking additional trips through self, with other peers.
|
|
||||||
# out_ch.directed_scid_to_me,
|
|
||||||
# in_ch.directed_scid_from_me,
|
|
||||||
|
|
||||||
# alternatively, never route through self. this avoids a class of logic error, like what to do with fees i charge "myself".
|
|
||||||
self.rpc.self_id
|
|
||||||
] + self.bad_channels + self.nonzero_base_channels
|
|
||||||
|
|
||||||
out_peer = out_ch.remote_peer
|
|
||||||
in_peer = in_ch.remote_peer
|
|
||||||
|
|
||||||
route_or_bounds = bounds
|
|
||||||
while isinstance(route_or_bounds, TxBounds):
|
|
||||||
old_bounds = route_or_bounds
|
|
||||||
route_or_bounds = self._find_partial_route(out_peer, in_peer, old_bounds, exclude=exclude)
|
|
||||||
if route_or_bounds == old_bounds:
|
|
||||||
return RouteError.NO_ROUTE
|
|
||||||
|
|
||||||
if isinstance(route_or_bounds, RouteError):
|
|
||||||
return route_or_bounds
|
|
||||||
|
|
||||||
route = self._add_route_endpoints(route_or_bounds, out_ch, in_ch)
|
|
||||||
return route
|
|
||||||
|
|
||||||
def _find_partial_route(self, out_peer: str, in_peer: str, bounds: TxBounds, exclude: list[str]=[]) -> list[dict] | RouteError | TxBounds:
|
|
||||||
route = self.rpc.try_getroute(in_peer, amount_msat=bounds.max_msat, riskfactor=0, fromid=out_peer, exclude=exclude, cltv=CLTV)
|
|
||||||
if route is None:
|
|
||||||
logger.debug(f"no route for {bounds.max_msat}msat {out_peer} -> {in_peer}")
|
|
||||||
return RouteError.NO_ROUTE
|
|
||||||
|
|
||||||
send_msat = route[0]["amount_msat"]
|
|
||||||
if send_msat != Millisatoshi(bounds.max_msat):
|
|
||||||
logger.debug(f"found route with non-zero fee: {send_msat} -> {bounds.max_msat}. {route}")
|
|
||||||
|
|
||||||
error = None
|
|
||||||
for hop in route:
|
|
||||||
hop_scid = hop["channel"]
|
|
||||||
hop_dir = hop["direction"]
|
|
||||||
directed_scid = f"{hop_scid}/{hop_dir}"
|
|
||||||
ch = self._get_directed_scid(hop_scid, hop_dir)
|
|
||||||
if ch["base_fee_millisatoshi"] != 0:
|
|
||||||
self.nonzero_base_channels.append(directed_scid)
|
|
||||||
error = RouteError.HAS_BASE_FEE
|
|
||||||
bounds = bounds.restrict_to_zero_fees(ppm=ch["fee_per_millionth"], why=directed_scid)
|
|
||||||
|
|
||||||
return bounds.raise_max_to_be_satisfiable() if error is None else error
|
|
||||||
|
|
||||||
return route
|
|
||||||
|
|
||||||
def _add_route_endpoints(self, route, out_ch: LocalChannel, in_ch: LocalChannel):
|
|
||||||
inbound_hop = dict(
|
|
||||||
id=self.rpc.self_id,
|
|
||||||
channel=in_ch.scid,
|
|
||||||
direction=in_ch.direction_to_me,
|
|
||||||
amount_msat=route[-1]["amount_msat"],
|
|
||||||
delay=route[-1]["delay"],
|
|
||||||
style="tlv",
|
|
||||||
)
|
|
||||||
route = self._add_route_delay(route, in_ch.delay_them) + [ inbound_hop ]
|
|
||||||
|
|
||||||
outbound_hop = dict(
|
|
||||||
id=out_ch.remote_peer,
|
|
||||||
channel=out_ch.scid,
|
|
||||||
direction=out_ch.direction_from_me,
|
|
||||||
amount_msat=route[0]["amount_msat"],
|
|
||||||
delay=route[0]["delay"] + out_ch.delay_them,
|
|
||||||
style="tlv",
|
|
||||||
)
|
|
||||||
route = [ outbound_hop ] + route
|
|
||||||
return route
|
|
||||||
|
|
||||||
def _add_route_delay(self, route: list[dict], delay: int) -> list[dict]:
|
|
||||||
return [ dict(hop, delay=hop["delay"] + delay) for hop in route ]
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LoopJob:
|
|
||||||
out: str # scid
|
|
||||||
in_: str # scid
|
|
||||||
amount: int
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LoopJobIdle:
|
|
||||||
sec: int = 10
|
|
||||||
|
|
||||||
class LoopJobDone(Enum):
|
|
||||||
COMPLETED = "COMPLETED"
|
|
||||||
ABORTED = "ABORTED"
|
|
||||||
|
|
||||||
class AbstractLoopRunner:
|
|
||||||
def __init__(self, looper: LoopRouter, bounds: TxBounds, parallelism: int):
|
|
||||||
self.looper = looper
|
|
||||||
self.bounds = bounds
|
|
||||||
self.parallelism = parallelism
|
|
||||||
self.bounds_map = {} # map (out:str, in_:str) -> TxBounds. it's a cache so we don't have to try 10 routes every time.
|
|
||||||
|
|
||||||
def pop_job(self) -> LoopJob | LoopJobIdle | LoopJobDone:
|
|
||||||
raise NotImplemented # abstract method
|
|
||||||
|
|
||||||
def finished_job(self, job: LoopJob, progress: int|LoopError) -> None:
|
|
||||||
raise NotImplemented # abstract method
|
|
||||||
|
|
||||||
def run_to_completion(self, exit_on_any_completed:bool = False) -> None:
|
|
||||||
self.exiting = False
|
|
||||||
self.exit_on_any_completed = exit_on_any_completed
|
|
||||||
if self.parallelism == 1:
|
|
||||||
# run inline to aid debugging
|
|
||||||
self._worker_thread()
|
|
||||||
else:
|
|
||||||
with ThreadPoolExecutor(max_workers=self.parallelism) as executor:
|
|
||||||
_ = list(executor.map(lambda _i: self._try_invoke(self._worker_thread), range(self.parallelism)))
|
|
||||||
|
|
||||||
def drop_caches(self) -> None:
|
|
||||||
logger.info("AbstractLoopRunner.drop_caches()")
|
|
||||||
self.looper.drop_caches()
|
|
||||||
self.bounds_map = {}
|
|
||||||
|
|
||||||
|
|
||||||
def _try_invoke(self, f, *args) -> None:
|
|
||||||
"""
|
|
||||||
try to invoke `f` with the provided `args`, and log if it fails.
|
|
||||||
this overcomes the issue that background tasks which fail via Exception otherwise do so silently.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
f(*args)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"task failed: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def _worker_thread(self) -> None:
|
|
||||||
while not self.exiting:
|
|
||||||
job = self.pop_job()
|
|
||||||
logger.debug(f"popped job: {job}")
|
|
||||||
if isinstance(job, LoopJobDone):
|
|
||||||
return self._worker_finished(job)
|
|
||||||
|
|
||||||
if isinstance(job, LoopJobIdle):
|
|
||||||
logger.debug(f"idling for {job.sec}")
|
|
||||||
time.sleep(job.sec)
|
|
||||||
continue
|
|
||||||
|
|
||||||
result = self._execute_job(job)
|
|
||||||
logger.debug(f"finishing job {job} with {result}")
|
|
||||||
self.finished_job(job, result)
|
|
||||||
|
|
||||||
def _execute_job(self, job: LoopJob) -> LoopError|int:
|
|
||||||
bounds = self.bounds_map.get((job.out, job.in_), self.bounds)
|
|
||||||
bounds = bounds.intersect(TxBounds(max_msat=job.amount))
|
|
||||||
if not bounds.is_satisfiable():
|
|
||||||
logger.debug(f"TxBounds for job are unsatisfiable; skipping: {bounds} {job}")
|
|
||||||
return LoopError.NO_ROUTE
|
|
||||||
|
|
||||||
amt_looped = self.looper.loop_once(job.out, job.in_, bounds)
|
|
||||||
if amt_looped in (0, LoopError.NO_ROUTE, LoopError.TRANSIENT):
|
|
||||||
return amt_looped
|
|
||||||
|
|
||||||
logger.info(f"looped {amt_looped} from {job.out} -> {job.in_}")
|
|
||||||
bounds = bounds.intersect(TxBounds(max_msat=amt_looped))
|
|
||||||
|
|
||||||
self.bounds_map[(job.out, job.in_)] = bounds
|
|
||||||
return amt_looped
|
|
||||||
|
|
||||||
def _worker_finished(self, job: LoopJobDone) -> None:
|
|
||||||
if job == LoopJobDone.COMPLETED and self.exit_on_any_completed:
|
|
||||||
logger.debug(f"worker completed -> exiting pool")
|
|
||||||
self.exiting = True
|
|
||||||
|
|
||||||
class LoopPairState:
|
|
||||||
# TODO: use this in MultiLoopBalancer, or stop shoving state in here and put it on LoopBalancer instead.
|
|
||||||
def __init__(self, out: str, in_: str, amount: int):
|
|
||||||
self.out = out
|
|
||||||
self.in_ = in_
|
|
||||||
self.amount_target = amount
|
|
||||||
self.amount_looped = 0
|
|
||||||
self.amount_outstanding = 0
|
|
||||||
self.tx_fail_count = 0
|
|
||||||
self.route_fail_count = 0
|
|
||||||
self.last_job_start_time = None
|
|
||||||
self.failed_tx_throttler = 0 # increase by one every time we fail, decreases more gradually, when we succeed
|
|
||||||
|
|
||||||
class LoopBalancer(AbstractLoopRunner):
|
|
||||||
def __init__(self, out: str, in_: str, amount: int, looper: LoopRouter, bounds: TxBounds, parallelism: int=1):
|
|
||||||
super().__init__(looper, bounds, parallelism)
|
|
||||||
self.state = LoopPairState(out, in_, amount)
|
|
||||||
|
|
||||||
def pop_job(self) -> LoopJob | LoopJobIdle | LoopJobDone:
|
|
||||||
if self.state.tx_fail_count + 10*self.state.route_fail_count >= MAX_SEQUENTIAL_JOB_FAILURES:
|
|
||||||
logger.info(f"giving up ({self.state.out} -> {self.state.in_}): {self.state.tx_fail_count} tx failures, {self.state.route_fail_count} route failures")
|
|
||||||
return LoopJobDone.ABORTED
|
|
||||||
|
|
||||||
if self.state.tx_fail_count + self.state.route_fail_count > 0:
|
|
||||||
# N.B.: last_job_start_time is guaranteed to have been set by now
|
|
||||||
idle_until = self.state.last_job_start_time + TX_FAIL_BACKOFF*self.state.failed_tx_throttler
|
|
||||||
idle_for = idle_until - time.time()
|
|
||||||
if self.state.amount_outstanding != 0 or idle_for > 0:
|
|
||||||
# when we hit transient failures, restrict to just one job in flight at a time.
|
|
||||||
# this is aimed for the initial route building, where multiple jobs in flight is just useless,
|
|
||||||
# but it's not a bad idea for network blips, etc, either.
|
|
||||||
logger.info(f"throttling ({self.state.out} -> {self.state.in_}) for {idle_for:.0f}: {self.state.tx_fail_count} tx failures, {self.state.route_fail_count} route failures")
|
|
||||||
return LoopJobIdle(idle_for) if idle_for > 0 else LoopJobIdle()
|
|
||||||
|
|
||||||
amount_avail = self.state.amount_target - self.state.amount_looped - self.state.amount_outstanding
|
|
||||||
if amount_avail < self.bounds.min_msat:
|
|
||||||
if self.state.amount_outstanding == 0: return LoopJobDone.COMPLETED
|
|
||||||
return LoopJobIdle() # sending out another job would risk over-transferring
|
|
||||||
amount_this_job = min(amount_avail, self.bounds.max_msat)
|
|
||||||
|
|
||||||
self.state.amount_outstanding += amount_this_job
|
|
||||||
self.state.last_job_start_time = time.time()
|
|
||||||
return LoopJob(out=self.state.out, in_=self.state.in_, amount=amount_this_job)
|
|
||||||
|
|
||||||
def finished_job(self, job: LoopJob, progress: int) -> None:
|
|
||||||
self.state.amount_outstanding -= job.amount
|
|
||||||
if progress == LoopError.NO_ROUTE:
|
|
||||||
self.state.route_fail_count += 1
|
|
||||||
self.state.failed_tx_throttler += 10
|
|
||||||
elif progress == LoopError.TRANSIENT:
|
|
||||||
self.state.tx_fail_count += 1
|
|
||||||
self.state.failed_tx_throttler += 1
|
|
||||||
else:
|
|
||||||
self.state.amount_looped += progress
|
|
||||||
self.state.tx_fail_count = 0
|
|
||||||
self.state.route_fail_count = 0
|
|
||||||
self.state.failed_tx_throttler = max(0, self.state.failed_tx_throttler - 0.2)
|
|
||||||
logger.info(f"loop progressed ({job.out} -> {job.in_}) {progress}: {self.state.amount_looped} of {self.state.amount_target}")
|
|
||||||
|
|
||||||
class MultiLoopBalancer(AbstractLoopRunner):
|
|
||||||
"""
|
|
||||||
multiplexes jobs between multiple LoopBalancers.
|
|
||||||
note that the child LoopBalancers don't actually execute the jobs -- just produce them.
|
|
||||||
"""
|
|
||||||
def __init__(self, looper: LoopRouter, bounds: TxBounds, parallelism: int=1):
|
|
||||||
super().__init__(looper, bounds, parallelism)
|
|
||||||
self.loops = []
|
|
||||||
# job_index: increments on every job so we can grab jobs evenly from each LoopBalancer.
|
|
||||||
# in that event that producers are idling, it can actually increment more than once,
|
|
||||||
# so don't take this too literally
|
|
||||||
self.job_index = 0
|
|
||||||
|
|
||||||
def add_loop(self, out: LocalChannel, in_: LocalChannel, amount: int) -> None:
|
|
||||||
"""
|
|
||||||
start looping sats from out -> in_
|
|
||||||
"""
|
|
||||||
assert not any(l.state.out == out.scid and l.state.in_ == in_.scid for l in self.loops), f"tried to add duplicate loops from {out} -> {in_}"
|
|
||||||
logger.info(f"looping from ({out}) to ({in_})")
|
|
||||||
self.loops.append(LoopBalancer(out.scid, in_.scid, amount, self.looper, self.bounds, self.parallelism))
|
|
||||||
|
|
||||||
def pop_job(self) -> LoopJob | LoopJobIdle | LoopJobDone:
|
|
||||||
# N.B.: this can be called in parallel, so try to be consistent enough to not crash
|
|
||||||
|
|
||||||
idle_job = None
|
|
||||||
abort_job = None
|
|
||||||
for i, _ in enumerate(self.loops):
|
|
||||||
loop = self.loops[(self.job_index + i) % len(self.loops)]
|
|
||||||
self.job_index += 1
|
|
||||||
job = loop.pop_job()
|
|
||||||
if isinstance(job, LoopJob):
|
|
||||||
return job
|
|
||||||
if isinstance(job, LoopJobIdle):
|
|
||||||
idle_job = LoopJobIdle(min(job.sec, idle_job.sec)) if idle_job is not None else job
|
|
||||||
if job == LoopJobDone.ABORTED:
|
|
||||||
abort_job = job
|
|
||||||
|
|
||||||
# either there's a task to idle, or we have to terminate.
|
|
||||||
# if terminating, terminate ABORTED if any job aborted, else COMPLETED
|
|
||||||
if idle_job is not None: return idle_job
|
|
||||||
if abort_job is not None: return abort_job
|
|
||||||
return LoopJobDone.COMPLETED
|
|
||||||
|
|
||||||
def finished_job(self, job: LoopJob, progress: int) -> None:
|
|
||||||
# this assumes (enforced externally) that we have only one loop for a given out/in_ pair
|
|
||||||
for l in self.loops:
|
|
||||||
if l.state.out == job.out and l.state.in_ == job.in_:
|
|
||||||
l.finished_job(job, progress)
|
|
||||||
|
|
||||||
logger.info(f"total: {self.looper.metrics}")
|
|
||||||
|
|
||||||
|
|
||||||
def balance_loop(rpc: RpcHelper, out: str, in_: str, amount_msat: int, min_msat: int, max_msat: int, parallelism: int):
|
|
||||||
looper = LoopRouter(rpc)
|
|
||||||
bounds = TxBounds(min_msat=min_msat, max_msat=max_msat)
|
|
||||||
balancer = LoopBalancer(out, in_, amount_msat, looper, bounds, parallelism)
|
|
||||||
|
|
||||||
balancer.run_to_completion()
|
|
||||||
|
|
||||||
def autobalance_once(rpc: RpcHelper, metrics: Metrics, bounds: TxBounds, parallelism: int) -> bool:
|
|
||||||
"""
|
|
||||||
autobalances all channels.
|
|
||||||
returns True if channels are balanced (or as balanced as can be); False if in need of further balancing
|
|
||||||
"""
|
|
||||||
looper = LoopRouter(rpc, metrics)
|
|
||||||
balancer = MultiLoopBalancer(looper, bounds, parallelism)
|
|
||||||
|
|
||||||
channels = []
|
|
||||||
for peerch in rpc.rpc.listpeerchannels()["channels"]:
|
|
||||||
try:
|
|
||||||
channels.append(rpc.localchannel(peerch["short_channel_id"]))
|
|
||||||
except:
|
|
||||||
logger.info(f"NO CHANNELS for {peerch['peer_id']}")
|
|
||||||
|
|
||||||
channels = [ch for ch in channels if ch.online and ch.base_fee_to_me == 0]
|
|
||||||
give_to = [ ch for ch in channels if ch.send_ratio > 0.95 ]
|
|
||||||
take_from = [ ch for ch in channels if ch.send_ratio < 0.20 ]
|
|
||||||
|
|
||||||
if give_to == [] and take_from == []:
|
|
||||||
return True
|
|
||||||
|
|
||||||
for to in give_to:
|
|
||||||
for from_ in take_from:
|
|
||||||
balancer.add_loop(to, from_, 10000000)
|
|
||||||
|
|
||||||
balancer.run_to_completion(exit_on_any_completed=True)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def autobalance(rpc: RpcHelper, min_msat: int, max_msat: int, parallelism: int):
|
|
||||||
bounds = TxBounds(min_msat=min_msat, max_msat=max_msat)
|
|
||||||
metrics = Metrics()
|
|
||||||
while not autobalance_once(rpc, metrics, bounds, parallelism):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def show_status(rpc: RpcHelper, full: bool=False):
|
|
||||||
"""
|
|
||||||
show a table of channel balances between peers.
|
|
||||||
"""
|
|
||||||
for peerch in rpc.rpc.listpeerchannels()["channels"]:
|
|
||||||
try:
|
|
||||||
ch = rpc.localchannel(peerch["short_channel_id"])
|
|
||||||
except:
|
|
||||||
print(f"{peerch['peer_id']} scid:{peerch['short_channel_id']} state:{peerch['state']} NO CHANNELS")
|
|
||||||
else:
|
|
||||||
print(ch.to_str(with_scid=True, with_bal_ratio=True, with_payments=True, with_cost=full, with_ppm_theirs=True, with_ppm_mine=True, with_peer_id=full))
|
|
||||||
|
|
||||||
def main():
|
|
||||||
logging.basicConfig()
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="rebalance lightning channel balances")
|
|
||||||
parser.add_argument("--verbose", action="store_true", help="more logging")
|
|
||||||
parser.add_argument("--min-msat", default="999", help="min transaction size")
|
|
||||||
parser.add_argument("--max-msat", default="1000000", help="max transaction size")
|
|
||||||
parser.add_argument("--jobs", default="1", help="how many HTLCs to keep in-flight at once")
|
|
||||||
subparsers = parser.add_subparsers(help="action")
|
|
||||||
|
|
||||||
status_parser = subparsers.add_parser("status")
|
|
||||||
status_parser.set_defaults(action="status")
|
|
||||||
status_parser.add_argument("--full", action="store_true", help="more info per channel")
|
|
||||||
|
|
||||||
loop_parser = subparsers.add_parser("loop")
|
|
||||||
loop_parser.set_defaults(action="loop")
|
|
||||||
loop_parser.add_argument("out", help="peer id to send tx through")
|
|
||||||
loop_parser.add_argument("in_", help="peer id to receive tx through")
|
|
||||||
loop_parser.add_argument("amount", help="total amount of msat to loop")
|
|
||||||
|
|
||||||
autobal_parser = subparsers.add_parser("autobalance")
|
|
||||||
autobal_parser.set_defaults(action="autobalance")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.verbose:
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
rpc = RpcHelper(LightningRpc(RPC_FILE))
|
|
||||||
|
|
||||||
if args.action == "status":
|
|
||||||
show_status(rpc, full=args.full)
|
|
||||||
|
|
||||||
if args.action == "loop":
|
|
||||||
balance_loop(rpc, out=args.out, in_=args.in_, amount_msat=int(args.amount), min_msat=int(args.min_msat), max_msat=int(args.max_msat), parallelism=int(args.jobs))
|
|
||||||
|
|
||||||
if args.action == "autobalance":
|
|
||||||
autobalance(rpc, min_msat=int(args.min_msat), max_msat=int(args.max_msat), parallelism=int(args.jobs))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@@ -1,135 +0,0 @@
|
|||||||
# clightning is an implementation of Bitcoin's Lightning Network.
|
|
||||||
# as such, this assumes that `services.bitcoin` is enabled.
|
|
||||||
# docs:
|
|
||||||
# - tor clightning config: <https://docs.corelightning.org/docs/tor>
|
|
||||||
# - `lightning-cli` and subcommands: <https://docs.corelightning.org/reference/lightning-cli>
|
|
||||||
# - `man lightningd-config`
|
|
||||||
#
|
|
||||||
# management/setup/use:
|
|
||||||
# - guide: <https://github.com/ElementsProject/lightning>
|
|
||||||
#
|
|
||||||
# debugging:
|
|
||||||
# - `lightning-cli getlog debug`
|
|
||||||
# - `lightning-cli listpays` -> show payments this node sent
|
|
||||||
# - `lightning-cli listinvoices` -> show payments this node received
|
|
||||||
#
|
|
||||||
# first, acquire peers:
|
|
||||||
# - `lightning-cli connect id@host`
|
|
||||||
# where `id` is the node's pubkey, and `host` is perhaps an ip:port tuple, or a hash.onion:port tuple.
|
|
||||||
# for testing, choose any node listed on <https://1ml.com>
|
|
||||||
# - `lightning-cli listpeers`
|
|
||||||
# should show the new peer, with `connected: true`
|
|
||||||
#
|
|
||||||
# then, fund the clightning wallet
|
|
||||||
# - `lightning-cli newaddr`
|
|
||||||
#
|
|
||||||
# then, open channels
|
|
||||||
# - `lightning-cli connect ...`
|
|
||||||
# - `lightning-cli fundchannel <node_id> <amount_in_satoshis>`
|
|
||||||
#
|
|
||||||
# who to federate with?
|
|
||||||
# - a lot of the larger nodes allow hands-free channel creation
|
|
||||||
# - either inbound or outbound, sometimes paid
|
|
||||||
# - find nodes on:
|
|
||||||
# - <https://terminal.lightning.engineering/>
|
|
||||||
# - <https://1ml.com>
|
|
||||||
# - tor nodes: <https://1ml.com/node?order=capacity&iponionservice=true>
|
|
||||||
# - <https://lightningnetwork.plus>
|
|
||||||
# - <https://mempool.space/lightning>
|
|
||||||
# - <https://amboss.space>
|
|
||||||
# - a few tor-capable nodes which allow channel creation:
|
|
||||||
# - <https://c-otto.de/>
|
|
||||||
# - <https://cyberdyne.sh/>
|
|
||||||
# - <https://yalls.org/about/>
|
|
||||||
# - <https://coincept.com/>
|
|
||||||
# - more resources: <https://www.lopp.net/lightning-information.html>
|
|
||||||
# - node routability: https://hashxp.org/lightning/node/<id>
|
|
||||||
# - especially, acquire inbound liquidity via lightningnetwork.plus's swap feature
|
|
||||||
# - most of the opportunities are gated behind a minimum connection or capacity requirement
|
|
||||||
#
|
|
||||||
# tune payment parameters
|
|
||||||
# - `lightning-cli setchannel <id> [feebase] [feeppm] [htlcmin] [htlcmax] [enforcedelay] [ignorefeelimits]`
|
|
||||||
# - e.g. `lightning-cli setchannel all 0 10`
|
|
||||||
# - it's suggested that feebase=0 simplifies routing.
|
|
||||||
#
|
|
||||||
# teardown:
|
|
||||||
# - `lightning-cli withdraw <bc1... dest addr> <amount in satoshis> [feerate]`
|
|
||||||
#
|
|
||||||
# sanity:
|
|
||||||
# - `lightning-cli listfunds`
|
|
||||||
#
|
|
||||||
# to receive a payment (do as `clightning` user):
|
|
||||||
# - `lightning-cli invoice <amount in millisatoshi> <label> <description>`
|
|
||||||
# - specify amount as `any` if undetermined
|
|
||||||
# - then give the resulting bolt11 URI to the payer
|
|
||||||
# to send a payment:
|
|
||||||
# - `lightning-cli pay <bolt11 URI>`
|
|
||||||
# - or `lightning-cli pay <bolt11 URI> [amount_msat] [label] [riskfactor] [maxfeepercent] ...`
|
|
||||||
# - amount_msat must be "null" if the bolt11 URI specifies a value
|
|
||||||
# - riskfactor defaults to 10
|
|
||||||
# - maxfeepercent defaults to 0.5
|
|
||||||
# - label is a human-friendly label for my records
|
|
||||||
|
|
||||||
{ config, pkgs, ... }:
|
|
||||||
{
|
|
||||||
sane.persist.sys.byStore.ext = [
|
|
||||||
{ user = "clightning"; group = "clightning"; mode = "0710"; path = "/var/lib/clightning"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
# `lightning-cli` finds its RPC file via `~/.lightning/bitcoin/lightning-rpc`, to message the daemon
|
|
||||||
sane.user.fs.".lightning".symlink.target = "/var/lib/clightning";
|
|
||||||
|
|
||||||
# see bitcoin.nix for how to generate this
|
|
||||||
services.bitcoind.mainnet.rpc.users.clightning.passwordHMAC =
|
|
||||||
"befcb82d9821049164db5217beb85439$2c31ac7db3124612e43893ae13b9527dbe464ab2d992e814602e7cb07dc28985";
|
|
||||||
|
|
||||||
sane.services.clightning.enable = true;
|
|
||||||
sane.services.clightning.proxy = "127.0.0.1:9050"; # proxy outgoing traffic through tor
|
|
||||||
# sane.services.clightning.publicAddress = "statictor:127.0.0.1:9051";
|
|
||||||
sane.services.clightning.getPublicAddressCmd = "cat /var/lib/tor/onion/clightning/hostname";
|
|
||||||
|
|
||||||
services.tor.relay.onionServices.clightning = {
|
|
||||||
version = 3;
|
|
||||||
map = [{
|
|
||||||
# by default tor will route public tor port P to 127.0.0.1:P.
|
|
||||||
# so if this port is the same as clightning would natively use, then no further config is needed here.
|
|
||||||
# see: <https://2019.www.torproject.org/docs/tor-manual.html.en#HiddenServicePort>
|
|
||||||
port = 9735;
|
|
||||||
# target.port; target.addr; #< set if tor port != clightning port
|
|
||||||
}];
|
|
||||||
# allow "tor" group (i.e. clightning) to read /var/lib/tor/onion/clightning/hostname
|
|
||||||
settings.HiddenServiceDirGroupReadable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
# must be in "tor" group to read /var/lib/tor/onion/*/hostname
|
|
||||||
users.users.clightning.extraGroups = [ "tor" ];
|
|
||||||
|
|
||||||
systemd.services.clightning.after = [ "tor.service" ];
|
|
||||||
|
|
||||||
# lightning-config contains fields from here:
|
|
||||||
# - <https://docs.corelightning.org/docs/configuration>
|
|
||||||
# secret config includes:
|
|
||||||
# - bitcoin-rpcpassword
|
|
||||||
# - alias=nodename
|
|
||||||
# - rgb=rrggbb
|
|
||||||
# - fee-base=<millisatoshi>
|
|
||||||
# - fee-per-satoshi=<ppm>
|
|
||||||
# - feature configs (i.e. experimental-xyz options)
|
|
||||||
sane.services.clightning.extraConfig = ''
|
|
||||||
log-level=debug:lightningd
|
|
||||||
# peerswap:
|
|
||||||
# - config example: <https://github.com/fort-nix/nix-bitcoin/pull/462/files#diff-b357d832705b8ce8df1f41934d613f79adb77c4cd5cd9e9eb12a163fca3e16c6>
|
|
||||||
# XXX: peerswap crashes clightning on launch. stacktrace is useless.
|
|
||||||
# plugin=${pkgs.peerswap}/bin/peerswap
|
|
||||||
# peerswap-db-path=/var/lib/clightning/peerswap/swaps
|
|
||||||
# peerswap-policy-path=...
|
|
||||||
'';
|
|
||||||
sane.services.clightning.extraConfigFiles = [ config.sops.secrets."lightning-config".path ];
|
|
||||||
sops.secrets."lightning-config" = {
|
|
||||||
mode = "0640";
|
|
||||||
owner = "clightning";
|
|
||||||
group = "clightning";
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.programs.clightning.enableFor.user.colin = true; # for debugging/admin: `lightning-cli`
|
|
||||||
}
|
|
@@ -1,10 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./bitcoin.nix
|
|
||||||
./clightning.nix
|
|
||||||
./i2p.nix
|
|
||||||
./monero.nix
|
|
||||||
./tor.nix
|
|
||||||
];
|
|
||||||
}
|
|
@@ -1,4 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
services.i2p.enable = true;
|
|
||||||
}
|
|
@@ -1,31 +0,0 @@
|
|||||||
# as of 2023/11/26: complete downloaded blockchain should be 200GiB on disk, give or take.
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
sane.persist.sys.byStore.ext = [
|
|
||||||
# /var/lib/monero/lmdb is what consumes most of the space
|
|
||||||
{ user = "monero"; group = "monero"; path = "/var/lib/monero"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
services.monero.enable = true;
|
|
||||||
services.monero.limits.upload = 5000; # in kB/s
|
|
||||||
services.monero.extraConfig = ''
|
|
||||||
# see: monero doc/ANONYMITY_NETWORKS.md
|
|
||||||
#
|
|
||||||
# "If any anonymity network is enabled, transactions being broadcast that lack a valid 'context'
|
|
||||||
# (i.e. the transaction did not come from a P2P connection) will only be sent to peers on anonymity networks."
|
|
||||||
#
|
|
||||||
# i think this means that setting tx-proxy here ensures any transactions sent locally to my node (via RPC)
|
|
||||||
# will be sent over an anonymity network.
|
|
||||||
tx-proxy=i2p,127.0.0.1:9000
|
|
||||||
tx-proxy=tor,127.0.0.1:9050
|
|
||||||
'';
|
|
||||||
|
|
||||||
# monero ports: <https://monero.stackexchange.com/questions/604/what-ports-does-monero-use-rpc-p2p-etc>
|
|
||||||
# - 18080 = "P2P" monero node <-> monero node connections
|
|
||||||
# - 18081 = "RPC" monero client -> monero node connections
|
|
||||||
sane.ports.ports."18080" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.wan = true;
|
|
||||||
description = "colin-monero-p2p";
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
# tor settings: <https://2019.www.torproject.org/docs/tor-manual.html.en>
|
|
||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
# tor hidden service hostnames aren't deterministic, so persist.
|
|
||||||
# might be able to get away with just persisting /var/lib/tor/onion, not sure.
|
|
||||||
sane.persist.sys.byStore.plaintext = [
|
|
||||||
{ user = "tor"; group = "tor"; mode = "0710"; path = "/var/lib/tor"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
# tor: `tor.enable` doesn't start a relay, exit node, proxy, etc. it's minimal.
|
|
||||||
# tor.client.enable configures a torsocks proxy, accessible *only* to localhost.
|
|
||||||
# at 127.0.0.1:9050
|
|
||||||
services.tor.enable = true;
|
|
||||||
services.tor.client.enable = true;
|
|
||||||
|
|
||||||
# in order for services to read /var/lib/tor/onion/*/hostname, they must be able to traverse /var/lib/tor,
|
|
||||||
# and /var/lib/tor must have g+x.
|
|
||||||
# DataDirectoryGroupReadable causes tor to use g+rx, technically more than we need, but all the files are 600 so it's fine.
|
|
||||||
services.tor.settings.DataDirectoryGroupReadable = true;
|
|
||||||
# StateDirectoryMode defaults to 0700, and thereby prevents the onion hostnames from being group readable
|
|
||||||
systemd.services.tor.serviceConfig.StateDirectoryMode = lib.mkForce "0710";
|
|
||||||
users.users.tor.homeMode = "0710"; # home mode defaults to 0700, causing readability problems, enforced by nixos "users" activation script
|
|
||||||
|
|
||||||
services.tor.settings.SafeLogging = false; # show actual .onion names in the syslog, else debugging is impossible
|
|
||||||
}
|
|
27
hosts/by-name/servo/services/ddns-afraid.nix
Normal file
27
hosts/by-name/servo/services/ddns-afraid.nix
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
# using manual ddns now
|
||||||
|
lib.mkIf false
|
||||||
|
{
|
||||||
|
systemd.services.ddns-afraid = {
|
||||||
|
description = "update dynamic DNS entries for freedns.afraid.org";
|
||||||
|
serviceConfig = {
|
||||||
|
EnvironmentFile = config.sops.secrets."ddns_afraid.env".path;
|
||||||
|
# TODO: ProtectSystem = "strict";
|
||||||
|
# TODO: ProtectHome = "full";
|
||||||
|
# TODO: PrivateTmp = true;
|
||||||
|
};
|
||||||
|
script = let
|
||||||
|
curl = "${pkgs.curl}/bin/curl -4";
|
||||||
|
in ''
|
||||||
|
${curl} "https://freedns.afraid.org/dynamic/update.php?$AFRAID_KEY"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
systemd.timers.ddns-afraid = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnStartupSec = "2min";
|
||||||
|
OnUnitActiveSec = "10min";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
30
hosts/by-name/servo/services/ddns-he.nix
Normal file
30
hosts/by-name/servo/services/ddns-he.nix
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
# we use manual DDNS now
|
||||||
|
lib.mkIf false
|
||||||
|
{
|
||||||
|
systemd.services.ddns-he = {
|
||||||
|
description = "update dynamic DNS entries for HurricaneElectric";
|
||||||
|
serviceConfig = {
|
||||||
|
EnvironmentFile = config.sops.secrets."ddns_he.env".path;
|
||||||
|
# TODO: ProtectSystem = "strict";
|
||||||
|
# TODO: ProtectHome = "full";
|
||||||
|
# TODO: PrivateTmp = true;
|
||||||
|
};
|
||||||
|
# HE DDNS API is documented: https://dns.he.net/docs.html
|
||||||
|
script = let
|
||||||
|
crl = "${pkgs.curl}/bin/curl -4";
|
||||||
|
in ''
|
||||||
|
${crl} "https://he.uninsane.org:$HE_PASSPHRASE@dyn.dns.he.net/nic/update?hostname=he.uninsane.org"
|
||||||
|
${crl} "https://native.uninsane.org:$HE_PASSPHRASE@dyn.dns.he.net/nic/update?hostname=native.uninsane.org"
|
||||||
|
${crl} "https://uninsane.org:$HE_PASSPHRASE@dyn.dns.he.net/nic/update?hostname=uninsane.org"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
systemd.timers.ddns-he = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnStartupSec = "2min";
|
||||||
|
OnUnitActiveSec = "10min";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
@@ -2,8 +2,8 @@
|
|||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./calibre.nix
|
./calibre.nix
|
||||||
./coturn.nix
|
./ddns-afraid.nix
|
||||||
./cryptocurrencies
|
./ddns-he.nix
|
||||||
./email
|
./email
|
||||||
./ejabberd.nix
|
./ejabberd.nix
|
||||||
./freshrss.nix
|
./freshrss.nix
|
||||||
@@ -19,14 +19,12 @@
|
|||||||
./matrix
|
./matrix
|
||||||
./navidrome.nix
|
./navidrome.nix
|
||||||
./nginx.nix
|
./nginx.nix
|
||||||
./nixos-prebuild.nix
|
|
||||||
./nixserve.nix
|
./nixserve.nix
|
||||||
./ntfy
|
./ntfy.nix
|
||||||
./pict-rs.nix
|
./pict-rs.nix
|
||||||
./pleroma.nix
|
./pleroma.nix
|
||||||
./postgres.nix
|
./postgres.nix
|
||||||
./prosody
|
./prosody.nix
|
||||||
./slskd.nix
|
|
||||||
./transmission.nix
|
./transmission.nix
|
||||||
./trust-dns.nix
|
./trust-dns.nix
|
||||||
./wikipedia.nix
|
./wikipedia.nix
|
||||||
|
@@ -40,11 +40,8 @@ let
|
|||||||
turnPortHigh = 49167;
|
turnPortHigh = 49167;
|
||||||
turnPortRange = lib.range turnPortLow turnPortHigh;
|
turnPortRange = lib.range turnPortLow turnPortHigh;
|
||||||
in
|
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 = lib.mkMerge ([
|
||||||
@@ -117,9 +114,6 @@ lib.mkIf false
|
|||||||
turnPortRange
|
turnPortRange
|
||||||
));
|
));
|
||||||
|
|
||||||
# this ejabberd config uses builtin STUN/TURN server, so hack to ensure no other implementation fights for ports
|
|
||||||
services.coturn.enable = false;
|
|
||||||
|
|
||||||
# provide access to certs
|
# 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.
|
||||||
# why is /var/lib/acme/* owned by `nginx` group??
|
# why is /var/lib/acme/* owned by `nginx` group??
|
||||||
|
@@ -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
|
|
||||||
|
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
|
@@ -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" = {
|
||||||
|
@@ -29,7 +29,7 @@
|
|||||||
# - `sudo btrfs qgroup limit 20G /mnt/persist/ext/persist/var/export/playground`
|
# - `sudo btrfs qgroup limit 20G /mnt/persist/ext/persist/var/export/playground`
|
||||||
# to query the quota/status:
|
# to query the quota/status:
|
||||||
# - `sudo btrfs qgroup show -re /var/export/playground`
|
# - `sudo btrfs qgroup show -re /var/export/playground`
|
||||||
sane.persist.sys.byStore.ext = [
|
sane.persist.sys.ext = [
|
||||||
{ user = "root"; group = "export"; mode = "0775"; path = "/var/export/playground"; }
|
{ user = "root"; group = "export"; mode = "0775"; path = "/var/export/playground"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -172,15 +172,8 @@ in
|
|||||||
|
|
||||||
users.users.sftpgo.extraGroups = [ "export" ];
|
users.users.sftpgo.extraGroups = [ "export" ];
|
||||||
|
|
||||||
systemd.services.sftpgo = {
|
systemd.services.sftpgo.serviceConfig = {
|
||||||
after = [ "network-online.target" ];
|
|
||||||
wants = [ "network-online.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
ReadOnlyPaths = [ "/var/export" ];
|
ReadOnlyPaths = [ "/var/export" ];
|
||||||
ReadWritePaths = [ "/var/export/playground" ];
|
ReadWritePaths = [ "/var/export/playground" ];
|
||||||
|
|
||||||
Restart = "always";
|
|
||||||
RestartSec = "20s";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -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"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -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"; }
|
||||||
];
|
];
|
||||||
@@ -13,10 +13,6 @@
|
|||||||
services.gitea.appName = "Perfectly Sane Git";
|
services.gitea.appName = "Perfectly Sane Git";
|
||||||
# services.gitea.disableRegistration = true;
|
# services.gitea.disableRegistration = true;
|
||||||
|
|
||||||
services.gitea.database.createDatabase = false; #< silence warning which wants db user and name to be equal
|
|
||||||
# TODO: remove this after merge: <https://github.com/NixOS/nixpkgs/pull/268849>
|
|
||||||
services.gitea.database.socket = "/run/postgresql"; #< would have been set if createDatabase = true
|
|
||||||
|
|
||||||
# gitea doesn't create the git user
|
# gitea doesn't create the git user
|
||||||
users.users.git = {
|
users.users.git = {
|
||||||
description = "Gitea Service";
|
description = "Gitea Service";
|
||||||
|
@@ -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"; }
|
||||||
];
|
];
|
||||||
|
@@ -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"; }
|
||||||
];
|
];
|
||||||
|
@@ -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" = {
|
||||||
|
@@ -1,19 +1,9 @@
|
|||||||
# how to update wikipedia snapshot:
|
|
||||||
# - browse for later snapshots:
|
|
||||||
# - <https://mirror.accum.se/mirror/wikimedia.org/other/kiwix/zim/wikipedia>
|
|
||||||
# - DL directly, or via rsync (resumable):
|
|
||||||
# - `rsync --progress --append-verify rsync://mirror.accum.se/mirror/wikimedia.org/other/kiwix/zim/wikipedia/wikipedia_en_all_maxi_2022-05.zim .`
|
|
||||||
|
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
sane.persist.sys.byStore.ext = [
|
|
||||||
{ user = "colin"; group = "users"; path = "/var/lib/kiwix"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
sane.services.kiwix-serve = {
|
sane.services.kiwix-serve = {
|
||||||
enable = true;
|
enable = true;
|
||||||
port = 8013;
|
port = 8013;
|
||||||
zimPaths = [ "/var/lib/kiwix/wikipedia_en_all_maxi_2023-11.zim" ];
|
zimPaths = [ "/var/lib/uninsane/www-archive/wikipedia_en_all_maxi_2022-05.zim" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx.virtualHosts."w.uninsane.org" = {
|
services.nginx.virtualHosts."w.uninsane.org" = {
|
||||||
|
@@ -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; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -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,7 +10,7 @@
|
|||||||
./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;
|
||||||
@@ -70,23 +60,25 @@
|
|||||||
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 +149,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" ];
|
|
||||||
}
|
}
|
||||||
|
@@ -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"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -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"; }
|
||||||
];
|
];
|
||||||
|
@@ -1,10 +1,8 @@
|
|||||||
# config options:
|
# config options:
|
||||||
# - <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, lib, pkgs, ... }:
|
{ config, pkgs, ... }:
|
||||||
|
|
||||||
lib.mkIf false # disabled 2024/01/11: i don't use it, and pkgs.mautrix-signal had some API changes
|
|
||||||
{
|
{
|
||||||
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"; }
|
||||||
];
|
];
|
||||||
|
@@ -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;
|
||||||
|
@@ -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
|
||||||
@@ -54,10 +53,8 @@ in
|
|||||||
services.nginx.recommendedOptimisation = true;
|
services.nginx.recommendedOptimisation = true;
|
||||||
|
|
||||||
# web blog/personal site
|
# web blog/personal site
|
||||||
# alternative way to link stuff into the share:
|
|
||||||
# sane.fs."/var/lib/uninsane/share/Ubunchu".mount.bind = "/var/lib/uninsane/media/Books/Visual/HiroshiSeo/Ubunchu";
|
|
||||||
# sane.fs."/var/lib/uninsane/media/Books/Visual/HiroshiSeo/Ubunchu".dir = {};
|
|
||||||
services.nginx.virtualHosts."uninsane.org" = publog {
|
services.nginx.virtualHosts."uninsane.org" = publog {
|
||||||
|
root = "${pkgs.uninsane-dot-org}/share/uninsane-dot-org";
|
||||||
# a lot of places hardcode https://uninsane.org,
|
# a lot of places hardcode https://uninsane.org,
|
||||||
# and then when we mix http + non-https, we get CORS violations
|
# and then when we mix http + non-https, we get CORS violations
|
||||||
# and things don't look right. so force SSL.
|
# and things don't look right. so force SSL.
|
||||||
@@ -67,28 +64,9 @@ in
|
|||||||
# for OCSP stapling
|
# for OCSP stapling
|
||||||
sslTrustedCertificate = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
sslTrustedCertificate = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||||
|
|
||||||
locations."/" = {
|
# uninsane.org/share/foo => /var/lib/uninsane/root/share/foo.
|
||||||
root = "${pkgs.uninsane-dot-org}/share/uninsane-dot-org";
|
# yes, nginx does not strip the prefix when evaluating against the root.
|
||||||
tryFiles = "$uri $uri/ @fallback";
|
locations."/share".root = "/var/lib/uninsane/root";
|
||||||
};
|
|
||||||
|
|
||||||
# unversioned files
|
|
||||||
locations."@fallback" = {
|
|
||||||
root = "/var/www/sites/uninsane.org";
|
|
||||||
};
|
|
||||||
|
|
||||||
# uninsane.org/share/foo => /var/www/sites/uninsane.org/share/foo.
|
|
||||||
# special-cased to enable directory listings
|
|
||||||
locations."/share" = {
|
|
||||||
root = "/var/www/sites/uninsane.org";
|
|
||||||
extraConfig = ''
|
|
||||||
# autoindex => render directory listings
|
|
||||||
autoindex on;
|
|
||||||
# don't follow any symlinks when serving files
|
|
||||||
# otherwise it allows a directory escape
|
|
||||||
disable_symlinks on;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# allow matrix users to discover that @user:uninsane.org is reachable via matrix.uninsane.org
|
# allow matrix users to discover that @user:uninsane.org is reachable via matrix.uninsane.org
|
||||||
locations."= /.well-known/matrix/server".extraConfig =
|
locations."= /.well-known/matrix/server".extraConfig =
|
||||||
@@ -129,19 +107,6 @@ in
|
|||||||
# proxyPass = "http://127.0.0.1:4000";
|
# proxyPass = "http://127.0.0.1:4000";
|
||||||
# extraConfig = pleromaExtraConfig;
|
# extraConfig = pleromaExtraConfig;
|
||||||
# };
|
# };
|
||||||
|
|
||||||
# redirect common feed URIs to the canonical feed
|
|
||||||
locations."= /atom".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /feed".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /feed.xml".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /rss".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /rss.xml".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/atom".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/atom.xml".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/feed".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/feed.xml".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/rss".extraConfig = "return 301 /atom.xml;";
|
|
||||||
locations."= /blog/rss.xml".extraConfig = "return 301 /atom.xml;";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -168,7 +133,8 @@ 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?
|
||||||
{ 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"; }
|
||||||
];
|
];
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
{ lib, pkgs, ... }:
|
|
||||||
|
|
||||||
lib.optionalAttrs false # disabled until i can be sure it's not gonna OOM my server in the middle of the night
|
|
||||||
{
|
|
||||||
systemd.services.nixos-prebuild = {
|
|
||||||
description = "build a nixos image with all updated deps";
|
|
||||||
path = with pkgs; [ coreutils git nix ];
|
|
||||||
script = ''
|
|
||||||
working=$(mktemp -d /tmp/nixos-prebuild.XXXXXX)
|
|
||||||
pushd "$working"
|
|
||||||
git clone https://git.uninsane.org/colin/nix-files.git \
|
|
||||||
&& cd nix-files \
|
|
||||||
&& nix flake update \
|
|
||||||
|| true
|
|
||||||
RC=$(nix run "$working/nix-files#check" -- -j1 --cores 5 --builders "")
|
|
||||||
popd
|
|
||||||
rm -rf "$working"
|
|
||||||
exit "$RC"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.timers.nixos-prebuild = {
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
timerConfig.OnCalendar = "11,23:00:00";
|
|
||||||
};
|
|
||||||
}
|
|
@@ -30,7 +30,7 @@ let
|
|||||||
altPort = 2587;
|
altPort = 2587;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
sane.persist.sys.byStore.plaintext = [
|
sane.persist.sys.plaintext = [
|
||||||
# not 100% necessary to persist this, but ntfy does keep a 12hr (by default) cache
|
# not 100% necessary to persist this, but ntfy does keep a 12hr (by default) cache
|
||||||
# for pushing notifications to users who become offline.
|
# for pushing notifications to users who become offline.
|
||||||
# ACLs also live here.
|
# ACLs also live here.
|
||||||
@@ -61,6 +61,12 @@ in
|
|||||||
${pkgs.ntfy-sh}/bin/ntfy access everyone "$topic" read-write
|
${pkgs.ntfy-sh}/bin/ntfy access everyone "$topic" read-write
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
sops.secrets."ntfy-sh-topic" = {
|
||||||
|
mode = "0440";
|
||||||
|
owner = config.users.users.ntfy-sh.name;
|
||||||
|
group = config.users.users.ntfy-sh.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
services.nginx.virtualHosts."ntfy.uninsane.org" = {
|
services.nginx.virtualHosts."ntfy.uninsane.org" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
@@ -1,14 +0,0 @@
|
|||||||
# ntfy: UnifiedPush notification delivery system
|
|
||||||
# - used to get push notifications out of Matrix and onto a Phone (iOS, Android, or a custom client)
|
|
||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./ntfy-waiter.nix
|
|
||||||
./ntfy-sh.nix
|
|
||||||
];
|
|
||||||
sops.secrets."ntfy-sh-topic" = {
|
|
||||||
mode = "0440";
|
|
||||||
owner = config.users.users.ntfy-sh.name;
|
|
||||||
group = config.users.users.ntfy-sh.name;
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,151 +0,0 @@
|
|||||||
#!/usr/bin/env nix-shell
|
|
||||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p ntfy-sh
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
LISTEN_QUEUE = 3
|
|
||||||
WAKE_MESSAGE = b'notification\n'
|
|
||||||
|
|
||||||
class Client:
|
|
||||||
def __init__(self, sock, addr_info, live_after: float):
|
|
||||||
self.live_after = live_after
|
|
||||||
self.sock = sock
|
|
||||||
self.addr_info = addr_info
|
|
||||||
|
|
||||||
def __cmp__(self, other: 'Client'):
|
|
||||||
return cmp(self.addr_info, other.addr_info)
|
|
||||||
|
|
||||||
def try_notify(self, message: bytes) -> bool:
|
|
||||||
"""
|
|
||||||
returns true if we send a packet to notify client.
|
|
||||||
fals otherwise (e.g. the socket is dead).
|
|
||||||
"""
|
|
||||||
ttl = self.live_after - time.time()
|
|
||||||
if ttl > 0:
|
|
||||||
logger.debug(f"sleeping {ttl:.2f}s until client {self.addr_info} is ready to receive notification")
|
|
||||||
time.sleep(ttl)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.sock.sendall(message)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"failed to notify client {self.addr_info} {e}")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
logger.info(f"successfully notified {self.addr_info}: {message}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
class Adapter:
|
|
||||||
def __init__(self, host: str, port: int, silence: int, topic: str):
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
self.silence = silence
|
|
||||||
self.topic = topic
|
|
||||||
self.clients = set()
|
|
||||||
|
|
||||||
def log_clients(self):
|
|
||||||
clients_str = '\n'.join(f' {c.addr_info}' for c in self.clients)
|
|
||||||
logger.debug(f"clients alive ({len(self.clients)}):\n{clients_str}")
|
|
||||||
|
|
||||||
def add_client(self, client: Client):
|
|
||||||
# it's a little bit risky to keep more than one client at the same IP address,
|
|
||||||
# because it's possible a notification comes in and we ring the old connection,
|
|
||||||
# even when the new connection says "don't ring yet".
|
|
||||||
for c in set(self.clients):
|
|
||||||
if c.addr_info[0] == client.addr_info[0]:
|
|
||||||
logger.info(f"purging old client before adding new one at same address: {c.addr_info} -> {client.addr_info}")
|
|
||||||
self.clients.remove(c)
|
|
||||||
|
|
||||||
logger.info(f"accepted client at {client.addr_info}")
|
|
||||||
self.clients.add(client)
|
|
||||||
|
|
||||||
def listener_loop(self):
|
|
||||||
logger.info(f"listening for connections on {self.host}:{self.port}")
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
s.bind((self.host, self.port))
|
|
||||||
s.listen(LISTEN_QUEUE)
|
|
||||||
while True:
|
|
||||||
conn, addr_info = s.accept()
|
|
||||||
self.add_client(Client(conn, addr_info, live_after = time.time() + self.silence))
|
|
||||||
|
|
||||||
def notify_clients(self, message: bytes = WAKE_MESSAGE):
|
|
||||||
# notify every client, and drop any which have disconnected.
|
|
||||||
# note that we notify based on age (oldest -> youngest)
|
|
||||||
# because notifying young clients might entail sleeping until they're ready.
|
|
||||||
clients = sorted(self.clients, key=lambda c: (c.live_after, c.addr_info))
|
|
||||||
|
|
||||||
dead_clients = [
|
|
||||||
c for c in clients if not c.try_notify(message)
|
|
||||||
]
|
|
||||||
for c in dead_clients:
|
|
||||||
self.clients.remove(c)
|
|
||||||
|
|
||||||
self.log_clients()
|
|
||||||
|
|
||||||
def notify_loop(self):
|
|
||||||
logger.info("waiting for notification events")
|
|
||||||
ntfy_proc = subprocess.Popen(
|
|
||||||
[
|
|
||||||
"ntfy",
|
|
||||||
"sub",
|
|
||||||
f"https://ntfy.uninsane.org/{self.topic}"
|
|
||||||
],
|
|
||||||
stdout=subprocess.PIPE
|
|
||||||
)
|
|
||||||
for line in iter(ntfy_proc.stdout.readline, b''):
|
|
||||||
logger.debug(f"received notification: {line}")
|
|
||||||
self.notify_clients()
|
|
||||||
|
|
||||||
def get_topic() -> str:
|
|
||||||
return open('/run/secrets/ntfy-sh-topic', 'rt').read().strip()
|
|
||||||
|
|
||||||
def run_forever(callable):
|
|
||||||
try:
|
|
||||||
callable()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{callable} failed: {e}")
|
|
||||||
else:
|
|
||||||
logger.error(f"{callable} unexpectedly returned")
|
|
||||||
# sys.exit(1)
|
|
||||||
os._exit(1) # sometimes `sys.exit()` doesn't actually exit...
|
|
||||||
|
|
||||||
def main():
|
|
||||||
logging.basicConfig()
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="accept connections and notify the other end upon ntfy activity, with a guaranteed amount of silence")
|
|
||||||
parser.add_argument('--verbose', action='store_true')
|
|
||||||
parser.add_argument('--host', type=str, default='')
|
|
||||||
parser.add_argument('--port', type=int)
|
|
||||||
parser.add_argument('--silence', type=int, help="number of seconds to remain silent upon accepting a connection")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.verbose:
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
|
||||||
else:
|
|
||||||
logging.getLogger().setLevel(logging.INFO)
|
|
||||||
|
|
||||||
adapter = Adapter(args.host, args.port, args.silence, get_topic())
|
|
||||||
|
|
||||||
listener_loop = threading.Thread(target=run_forever, name="listener_loop", args=(adapter.listener_loop,))
|
|
||||||
notify_loop = threading.Thread(target=run_forever, name="notify_loop", args=(adapter.notify_loop,))
|
|
||||||
|
|
||||||
# TODO: this method of exiting seems to sometimes leave the listener behind (?)
|
|
||||||
# preventing anyone else from re-binding the port.
|
|
||||||
listener_loop.start()
|
|
||||||
notify_loop.start()
|
|
||||||
listener_loop.join()
|
|
||||||
notify_loop.join()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@@ -1,72 +0,0 @@
|
|||||||
# service which adapts ntfy-sh into something suitable specifically for the Pinephone's
|
|
||||||
# wake-on-lan (WoL) feature.
|
|
||||||
# notably, it provides a mechanism by which the caller can be confident of an interval in which
|
|
||||||
# zero traffic will occur on the TCP connection, thus allowing it to enter sleep w/o fear of hitting
|
|
||||||
# race conditions in the Pinephone WoL feature.
|
|
||||||
{ config, lib, pkgs, ... }:
|
|
||||||
let
|
|
||||||
cfg = config.sane.ntfy-waiter;
|
|
||||||
portLow = 5550;
|
|
||||||
portHigh = 5559;
|
|
||||||
portRange = lib.range portLow portHigh;
|
|
||||||
numPorts = portHigh - portLow + 1;
|
|
||||||
mkService = port: let
|
|
||||||
silence = port - portLow;
|
|
||||||
flags = lib.optional cfg.verbose "--verbose";
|
|
||||||
cli = [
|
|
||||||
"${cfg.package}/bin/ntfy-waiter"
|
|
||||||
"--port"
|
|
||||||
"${builtins.toString port}"
|
|
||||||
"--silence"
|
|
||||||
"${builtins.toString silence}"
|
|
||||||
] ++ flags;
|
|
||||||
in {
|
|
||||||
"ntfy-waiter-${builtins.toString silence}" = {
|
|
||||||
# TODO: run not as root (e.g. as ntfy-sh)
|
|
||||||
description = "wait for notification, with ${builtins.toString silence} seconds of guaranteed silence";
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "simple";
|
|
||||||
Restart = "always";
|
|
||||||
RestartSec = "5s";
|
|
||||||
ExecStart = lib.concatStringsSep " " cli;
|
|
||||||
};
|
|
||||||
after = [ "network.target" ];
|
|
||||||
wantedBy = [ "default.target" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options = with lib; {
|
|
||||||
sane.ntfy-waiter.enable = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
};
|
|
||||||
sane.ntfy-waiter.verbose = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
};
|
|
||||||
sane.ntfy-waiter.package = mkOption {
|
|
||||||
type = types.package;
|
|
||||||
default = pkgs.static-nix-shell.mkPython3Bin {
|
|
||||||
pname = "ntfy-waiter";
|
|
||||||
src = ./.;
|
|
||||||
pkgs = [ "ntfy-sh" ];
|
|
||||||
};
|
|
||||||
description = ''
|
|
||||||
exposed to provide an attr-path by which one may build the package for manual testing.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
sane.ports.ports = lib.mkMerge (lib.forEach portRange (port: {
|
|
||||||
"${builtins.toString port}" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
visibleTo.wan = true;
|
|
||||||
description = "colin-notification-waiter-${builtins.toString (port - portLow + 1)}-of-${builtins.toString numPorts}";
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
systemd.services = lib.mkMerge (builtins.map mkService portRange);
|
|
||||||
};
|
|
||||||
}
|
|
@@ -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; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
@@ -6,7 +6,7 @@ let
|
|||||||
KiB = n: 1024*n;
|
KiB = n: 1024*n;
|
||||||
in
|
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"; }
|
||||||
];
|
];
|
||||||
|
81
hosts/by-name/servo/services/prosody.nix
Normal file
81
hosts/by-name/servo/services/prosody.nix
Normal 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
@@ -1,289 +0,0 @@
|
|||||||
# example configs:
|
|
||||||
# - official: <https://prosody.im/doc/example_config>
|
|
||||||
# - nixos: <https://github.com/kittywitch/nixfiles/blob/main/services/prosody.nix>
|
|
||||||
# config options:
|
|
||||||
# - <https://prosody.im/doc/configure>
|
|
||||||
#
|
|
||||||
# modules:
|
|
||||||
# - main: <https://prosody.im/doc/modules>
|
|
||||||
# - community: <https://modules.prosody.im/index.html>
|
|
||||||
#
|
|
||||||
# debugging:
|
|
||||||
# - logging:
|
|
||||||
# - enable `stanza_debug` module
|
|
||||||
# - enable `log.debug = "*syslog"` in extraConfig
|
|
||||||
# - interactive:
|
|
||||||
# - `telnet localhost 5582` (this is equal to `prosodyctl shell` -- but doesn't hang)
|
|
||||||
# - `watch:stanzas(target_spec, filter)` -> to log stanzas, for version > 0.12
|
|
||||||
# - console docs: <https://prosody.im/doc/console>
|
|
||||||
# - can modify/inspect arbitrary internals (lua) by prefixing line with `> `
|
|
||||||
# - e.g. `> _G` to print all globals
|
|
||||||
#
|
|
||||||
# sanity checks:
|
|
||||||
# - `sudo -u prosody -g prosody prosodyctl check connectivity`
|
|
||||||
# - `sudo -u prosody -g prosody prosodyctl check turn`
|
|
||||||
# - `sudo -u prosody -g prosody prosodyctl check turn -v --ping=stun.conversations.im`
|
|
||||||
# - checks that my stun/turn server is usable by clients of conversations.im (?)
|
|
||||||
# - `sudo -u prosody -g prosody prosodyctl check` (dns, config, certs)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# create users with:
|
|
||||||
# - `sudo -u prosody prosodyctl adduser colin@uninsane.org`
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# federation/support matrix:
|
|
||||||
# - nixnet.services (runs ejabberd):
|
|
||||||
# - WORKS: sending and receiving PMs and calls (2023/10/15)
|
|
||||||
# - N.B.: it didn't originally work; was solved by disabling the lua-unbound DNS option & forcing the system/local resolver
|
|
||||||
# - cheogram (XMPP <-> SMS gateway):
|
|
||||||
# - WORKS: sending and receiving PMs, images (2023/10/15)
|
|
||||||
# - PARTIAL: calls (xmpp -> tel works; tel -> xmpp fails)
|
|
||||||
# - maybe i need to setup stun/turn
|
|
||||||
#
|
|
||||||
# TODO:
|
|
||||||
# - enable push notifications (mod_cloud_notify)
|
|
||||||
# - optimize coturn (e.g. move off of the VPN!)
|
|
||||||
# - ensure muc is working
|
|
||||||
# - enable file uploads
|
|
||||||
# - "upload.xmpp.uninsane.org:http_upload: URL: <https://upload.xmpp.uninsane.org:5281/upload> - Ensure this can be reached by users"
|
|
||||||
# - disable or fix bosh (jabber over http):
|
|
||||||
# - "certmanager: No certificate/key found for client_https port 0"
|
|
||||||
|
|
||||||
{ lib, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
# enables very verbose logging
|
|
||||||
enableDebug = false;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
sane.persist.sys.byStore.plaintext = [
|
|
||||||
{ user = "prosody"; group = "prosody"; path = "/var/lib/prosody"; }
|
|
||||||
];
|
|
||||||
sane.ports.ports."5000" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
visibleTo.wan = true;
|
|
||||||
description = "colin-xmpp-prosody-fileshare-proxy65";
|
|
||||||
};
|
|
||||||
sane.ports.ports."5222" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
visibleTo.wan = true;
|
|
||||||
description = "colin-xmpp-client-to-server";
|
|
||||||
};
|
|
||||||
sane.ports.ports."5223" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
visibleTo.wan = true;
|
|
||||||
description = "colin-xmpps-client-to-server"; # XMPP over TLS
|
|
||||||
};
|
|
||||||
sane.ports.ports."5269" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.wan = true;
|
|
||||||
description = "colin-xmpp-server-to-server";
|
|
||||||
};
|
|
||||||
sane.ports.ports."5270" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.wan = true;
|
|
||||||
description = "colin-xmpps-server-to-server"; # XMPP over TLS
|
|
||||||
};
|
|
||||||
sane.ports.ports."5280" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
visibleTo.wan = true;
|
|
||||||
description = "colin-xmpp-bosh";
|
|
||||||
};
|
|
||||||
sane.ports.ports."5281" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
visibleTo.lan = true;
|
|
||||||
visibleTo.wan = true;
|
|
||||||
description = "colin-xmpp-prosody-https"; # necessary?
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.prosody.extraGroups = [
|
|
||||||
"nginx" # provide access to certs
|
|
||||||
"ntfy-sh" # access to secret ntfy topic
|
|
||||||
];
|
|
||||||
|
|
||||||
security.acme.certs."uninsane.org".extraDomainNames = [
|
|
||||||
"xmpp.uninsane.org"
|
|
||||||
"conference.xmpp.uninsane.org"
|
|
||||||
"upload.xmpp.uninsane.org"
|
|
||||||
];
|
|
||||||
|
|
||||||
# exists so the XMPP server's cert can obtain altNames for all its resources
|
|
||||||
services.nginx.virtualHosts."xmpp.uninsane.org" = {
|
|
||||||
useACMEHost = "uninsane.org";
|
|
||||||
};
|
|
||||||
services.nginx.virtualHosts."conference.xmpp.uninsane.org" = {
|
|
||||||
useACMEHost = "uninsane.org";
|
|
||||||
};
|
|
||||||
services.nginx.virtualHosts."upload.xmpp.uninsane.org" = {
|
|
||||||
useACMEHost = "uninsane.org";
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.dns.zones."uninsane.org".inet = {
|
|
||||||
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
|
|
||||||
A."xmpp" = "%ANATIVE%";
|
|
||||||
CNAME."conference.xmpp" = "xmpp";
|
|
||||||
CNAME."upload.xmpp" = "xmpp";
|
|
||||||
|
|
||||||
# _Service._Proto.Name TTL Class SRV Priority Weight Port Target
|
|
||||||
# - <https://xmpp.org/extensions/xep-0368.html>
|
|
||||||
# something's requesting the SRV records for conference.xmpp, so let's include it
|
|
||||||
# nothing seems to request XMPP SRVs for the other records (except @)
|
|
||||||
# lower numerical priority field tells clients to prefer this method
|
|
||||||
SRV."_xmpps-client._tcp.conference.xmpp" = "3 50 5223 xmpp";
|
|
||||||
SRV."_xmpps-server._tcp.conference.xmpp" = "3 50 5270 xmpp";
|
|
||||||
SRV."_xmpp-client._tcp.conference.xmpp" = "5 50 5222 xmpp";
|
|
||||||
SRV."_xmpp-server._tcp.conference.xmpp" = "5 50 5269 xmpp";
|
|
||||||
|
|
||||||
SRV."_xmpps-client._tcp" = "3 50 5223 xmpp";
|
|
||||||
SRV."_xmpps-server._tcp" = "3 50 5270 xmpp";
|
|
||||||
SRV."_xmpp-client._tcp" = "5 50 5222 xmpp";
|
|
||||||
SRV."_xmpp-server._tcp" = "5 50 5269 xmpp";
|
|
||||||
};
|
|
||||||
|
|
||||||
# help Prosody find its certificates.
|
|
||||||
# pointing it to /var/lib/acme doesn't quite work because it expects the private key
|
|
||||||
# to be named `privkey.pem` instead of acme's `key.pem`
|
|
||||||
# <https://prosody.im/doc/certificates#automatic_location>
|
|
||||||
sane.fs."/etc/prosody/certs/uninsane.org/fullchain.pem" = {
|
|
||||||
symlink.target = "/var/lib/acme/uninsane.org/fullchain.pem";
|
|
||||||
wantedBeforeBy = [ "prosody.service" ];
|
|
||||||
};
|
|
||||||
sane.fs."/etc/prosody/certs/uninsane.org/privkey.pem" = {
|
|
||||||
symlink.target = "/var/lib/acme/uninsane.org/key.pem";
|
|
||||||
wantedBeforeBy = [ "prosody.service" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
services.prosody = {
|
|
||||||
enable = true;
|
|
||||||
package = pkgs.prosody.override {
|
|
||||||
# XXX(2023/10/15): build without lua-unbound support.
|
|
||||||
# this forces Prosody to fall back to the default Lua DNS resolver, which seems more reliable.
|
|
||||||
# fixes errors like "unbound.queryXYZUV: Resolver error: out of memory"
|
|
||||||
# related: <https://issues.prosody.im/1737#comment-11>
|
|
||||||
lua.withPackages = selector: pkgs.lua.withPackages (p:
|
|
||||||
selector (p // { luaunbound = null; })
|
|
||||||
);
|
|
||||||
# withCommunityModules = [ "turncredentials" ];
|
|
||||||
};
|
|
||||||
admins = [ "colin@uninsane.org" ];
|
|
||||||
# allowRegistration = false; # defaults to false
|
|
||||||
|
|
||||||
muc = [
|
|
||||||
{
|
|
||||||
domain = "conference.xmpp.uninsane.org";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
uploadHttp.domain = "upload.xmpp.uninsane.org";
|
|
||||||
|
|
||||||
virtualHosts = {
|
|
||||||
# "Prosody requires at least one enabled VirtualHost to function. You can
|
|
||||||
# safely remove or disable 'localhost' once you have added another."
|
|
||||||
# localhost = {
|
|
||||||
# domain = "localhost";
|
|
||||||
# enabled = true;
|
|
||||||
# };
|
|
||||||
"xmpp.uninsane.org" = {
|
|
||||||
domain = "uninsane.org";
|
|
||||||
enabled = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
## modules:
|
|
||||||
# these are enabled by default, via <repo:nixos/nixpkgs:/pkgs/servers/xmpp/prosody/default.nix>
|
|
||||||
# - cloud_notify
|
|
||||||
# - http_upload
|
|
||||||
# - vcard_muc
|
|
||||||
# these are enabled by the module defaults (services.prosody.modules.<foo>)
|
|
||||||
# - admin_adhoc
|
|
||||||
# - blocklist
|
|
||||||
# - bookmarks
|
|
||||||
# - carbons
|
|
||||||
# - cloud_notify
|
|
||||||
# - csi
|
|
||||||
# - dialback
|
|
||||||
# - disco
|
|
||||||
# - http_files
|
|
||||||
# - mam
|
|
||||||
# - pep
|
|
||||||
# - ping
|
|
||||||
# - private
|
|
||||||
# - XEP-0049: let clients store arbitrary (private) data on the server
|
|
||||||
# - proxy65
|
|
||||||
# - XEP-0065: allow server to proxy file transfers between two clients who are behind NAT
|
|
||||||
# - register
|
|
||||||
# - roster
|
|
||||||
# - saslauth
|
|
||||||
# - smacks
|
|
||||||
# - time
|
|
||||||
# - tls
|
|
||||||
# - uptime
|
|
||||||
# - vcard_legacy
|
|
||||||
# - version
|
|
||||||
|
|
||||||
extraPluginPaths = [ ./modules ];
|
|
||||||
|
|
||||||
extraModules = [
|
|
||||||
# admin_shell: allows `prosodyctl shell` to work
|
|
||||||
# see: <https://prosody.im/doc/modules/mod_admin_shell>
|
|
||||||
# see: <https://prosody.im/doc/console>
|
|
||||||
"admin_shell"
|
|
||||||
"admin_telnet" #< needed by admin_shell
|
|
||||||
# lastactivity: XEP-0012: allow users to query how long another user has been idle for
|
|
||||||
# - not sure why i enabled this; think it was in someone's config i referenced
|
|
||||||
"lastactivity"
|
|
||||||
# allows prosody to share TURN/STUN secrets with XMPP clients to provide them access to the coturn server.
|
|
||||||
# see: <https://prosody.im/doc/coturn>
|
|
||||||
"turn_external"
|
|
||||||
# legacy coturn integration
|
|
||||||
# see: <https://modules.prosody.im/mod_turncredentials.html>
|
|
||||||
# "turncredentials"
|
|
||||||
"sane_ntfy"
|
|
||||||
] ++ lib.optionals enableDebug [
|
|
||||||
"stanza_debug" #< logs EVERY stanza as debug: <https://prosody.im/doc/modules/mod_stanza_debug>
|
|
||||||
];
|
|
||||||
|
|
||||||
extraConfig = ''
|
|
||||||
local function readAll(file)
|
|
||||||
local f = assert(io.open(file, "rb"))
|
|
||||||
local content = f:read("*all")
|
|
||||||
f:close()
|
|
||||||
-- remove trailing newline
|
|
||||||
return string.gsub(content, "%s+", "")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- logging docs:
|
|
||||||
-- - <https://prosody.im/doc/logging>
|
|
||||||
-- - <https://prosody.im/doc/advanced_logging>
|
|
||||||
-- levels: debug, info, warn, error
|
|
||||||
log = {
|
|
||||||
${if enableDebug then "debug" else "info"} = "*syslog";
|
|
||||||
}
|
|
||||||
|
|
||||||
-- see: <https://prosody.im/doc/certificates#automatic_location>
|
|
||||||
-- try to solve: "certmanager: Error indexing certificate directory /etc/prosody/certs: cannot open /etc/prosody/certs: No such file or directory"
|
|
||||||
-- only, this doesn't work because prosody doesn't like acme's naming scheme
|
|
||||||
-- certificates = "/var/lib/acme"
|
|
||||||
|
|
||||||
c2s_direct_tls_ports = { 5223 }
|
|
||||||
s2s_direct_tls_ports = { 5270 }
|
|
||||||
|
|
||||||
turn_external_host = "turn.uninsane.org"
|
|
||||||
turn_external_secret = readAll("/var/lib/coturn/shared_secret.bin")
|
|
||||||
-- turn_external_user = "prosody"
|
|
||||||
|
|
||||||
-- legacy mod_turncredentials integration
|
|
||||||
-- turncredentials_host = "turn.uninsane.org"
|
|
||||||
-- turncredentials_secret = readAll("/var/lib/coturn/shared_secret.bin")
|
|
||||||
|
|
||||||
ntfy_binary = "${pkgs.ntfy-sh}/bin/ntfy"
|
|
||||||
ntfy_topic = readAll("/run/secrets/ntfy-sh-topic")
|
|
||||||
|
|
||||||
-- s2s_require_encryption = true
|
|
||||||
-- c2s_require_encryption = true
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,52 +0,0 @@
|
|||||||
-- simple proof-of-concept Prosody module
|
|
||||||
-- module development guide: <https://prosody.im/doc/developers/modules>
|
|
||||||
-- module API docs: <https://prosody.im/doc/developers/moduleapi>
|
|
||||||
--
|
|
||||||
-- much of this code is lifted from Prosody's own `mod_cloud_notify`
|
|
||||||
|
|
||||||
local jid = require"util.jid";
|
|
||||||
|
|
||||||
local ntfy = module:get_option_string("ntfy_binary", "ntfy");
|
|
||||||
local ntfy_topic = module:get_option_string("ntfy_topic", "xmpp");
|
|
||||||
|
|
||||||
module:log("info", "initialized");
|
|
||||||
|
|
||||||
local function is_urgent(stanza)
|
|
||||||
if stanza.name == "message" then
|
|
||||||
if stanza:get_child("propose", "urn:xmpp:jingle-message:0") then
|
|
||||||
return true, "jingle call";
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function publish_ntfy(message)
|
|
||||||
-- message should be the message to publish
|
|
||||||
local ntfy_url = string.format("https://ntfy.uninsane.org/%s", ntfy_topic)
|
|
||||||
local cmd = string.format("%s pub %q %q", ntfy, ntfy_url, message)
|
|
||||||
module.log("debug", "invoking ntfy: %s", cmd)
|
|
||||||
local success, reason, code = os.execute(cmd)
|
|
||||||
if not success then
|
|
||||||
module:log("warn", "ntfy failed: %s => %s %d", cmd, reason, code)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function archive_message_added(event)
|
|
||||||
-- event is: { origin = origin, stanza = stanza, for_user = store_user, id = id }
|
|
||||||
local stanza = event.stanza;
|
|
||||||
local to = stanza.attr.to;
|
|
||||||
to = to and jid.split(to) or event.origin.username;
|
|
||||||
|
|
||||||
-- only notify if the stanza destination is the mam user we store it for
|
|
||||||
if event.for_user == to then
|
|
||||||
local is_urgent_stanza, urgent_reason = is_urgent(event.stanza);
|
|
||||||
|
|
||||||
if is_urgent_stanza then
|
|
||||||
module:log("info", "urgent push for %s (%s)", to, urgent_reason);
|
|
||||||
publish_ntfy(urgent_reason)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
module:hook("archive-message-added", archive_message_added);
|
|
@@ -1,74 +0,0 @@
|
|||||||
# Soulseek daemon (p2p file sharing with an emphasis on Music)
|
|
||||||
# docs: <https://github.com/slskd/slskd/blob/master/docs/config.md>
|
|
||||||
#
|
|
||||||
# config precedence (higher precedence overrules lower precedence):
|
|
||||||
# - Default Values < Environment Variables < YAML Configuraiton File < Command Line Arguments
|
|
||||||
{ config, lib, ... }:
|
|
||||||
{
|
|
||||||
sane.persist.sys.byStore.plaintext = [
|
|
||||||
{ user = "slskd"; group = "slskd"; path = "/var/lib/slskd"; }
|
|
||||||
];
|
|
||||||
sops.secrets."slskd_env" = {
|
|
||||||
owner = config.users.users.slskd.name;
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.slskd.extraGroups = [ "media" ];
|
|
||||||
|
|
||||||
sane.ports.ports."50000" = {
|
|
||||||
protocol = [ "tcp" ];
|
|
||||||
# not visible to WAN: i run this in a separate netns
|
|
||||||
visibleTo.ovpn = true;
|
|
||||||
description = "colin-soulseek";
|
|
||||||
};
|
|
||||||
|
|
||||||
sane.dns.zones."uninsane.org".inet.CNAME."soulseek" = "native";
|
|
||||||
|
|
||||||
services.nginx.virtualHosts."soulseek.uninsane.org" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "http://10.0.1.6:5001";
|
|
||||||
proxyWebsockets = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.slskd.enable = true;
|
|
||||||
# env file, for auth (SLSKD_SLSK_PASSWORD, SLSKD_SLSK_USERNAME)
|
|
||||||
services.slskd.environmentFile = config.sops.secrets.slskd_env.path;
|
|
||||||
services.slskd.settings = {
|
|
||||||
soulseek.diagnostic_level = "Debug"; # one of "None"|"Warning"|"Info"|"Debug"
|
|
||||||
shares.directories = [
|
|
||||||
# folders to share
|
|
||||||
# syntax: <https://github.com/slskd/slskd/blob/master/docs/config.md#directories>
|
|
||||||
# [Alias]/path/on/disk
|
|
||||||
# NOTE: Music library is quick to scan; videos take a solid 10min to scan.
|
|
||||||
# TODO: re-enable the other libraries
|
|
||||||
# "[Audioooks]/var/lib/uninsane/media/Books/Audiobooks"
|
|
||||||
# "[Books]/var/lib/uninsane/media/Books/Books"
|
|
||||||
# "[Manga]/var/lib/uninsane/media/Books/Visual"
|
|
||||||
# "[games]/var/lib/uninsane/media/games"
|
|
||||||
"[Music]/var/lib/uninsane/media/Music"
|
|
||||||
# "[Film]/var/lib/uninsane/media/Videos/Film"
|
|
||||||
# "[Shows]/var/lib/uninsane/media/Videos/Shows"
|
|
||||||
];
|
|
||||||
# directories.downloads = "..." # TODO
|
|
||||||
# directories.incomplete = "..." # TODO
|
|
||||||
# what unit is this? kbps??
|
|
||||||
global.upload.speed_limit = 32000;
|
|
||||||
web.logging = true;
|
|
||||||
# debug = true;
|
|
||||||
flags.no_logo = true; # don't show logo at start
|
|
||||||
# flags.volatile = true; # store searches and active transfers in RAM (completed transfers still go to disk). rec for btrfs/zfs
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.slskd = {
|
|
||||||
serviceConfig = {
|
|
||||||
# run this behind the OVPN static VPN
|
|
||||||
NetworkNamespacePath = "/run/netns/ovpns";
|
|
||||||
Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
|
|
||||||
RestartSec = "60s";
|
|
||||||
Group = "media";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,7 +1,7 @@
|
|||||||
{ config, pkgs, ... }:
|
{ config, 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 = config.users.users.transmission.group; path = "/var/lib/transmission"; }
|
||||||
];
|
];
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
# transmission will by default not allow the world to read its files.
|
# transmission will by default not allow the world to read its files.
|
||||||
services.transmission.downloadDirPermissions = "775";
|
services.transmission.downloadDirPermissions = "775";
|
||||||
services.transmission.extraFlags = [
|
services.transmission.extraFlags = [
|
||||||
# "--log-level=debug"
|
"--log-level=debug"
|
||||||
];
|
];
|
||||||
|
|
||||||
services.transmission.settings = {
|
services.transmission.settings = {
|
||||||
@@ -39,9 +39,9 @@
|
|||||||
encryption = 2;
|
encryption = 2;
|
||||||
|
|
||||||
# units in kBps
|
# units in kBps
|
||||||
speed-limit-down = 12000;
|
speed-limit-down = 3000;
|
||||||
speed-limit-down-enabled = true;
|
speed-limit-down-enabled = true;
|
||||||
speed-limit-up = 800;
|
speed-limit-up = 600;
|
||||||
speed-limit-up-enabled = true;
|
speed-limit-up-enabled = true;
|
||||||
|
|
||||||
# see: https://git.zknt.org/mirror/transmission/commit/cfce6e2e3a9b9d31a9dafedd0bdc8bf2cdb6e876?lang=bg-BG
|
# see: https://git.zknt.org/mirror/transmission/commit/cfce6e2e3a9b9d31a9dafedd0bdc8bf2cdb6e876?lang=bg-BG
|
||||||
@@ -91,10 +91,5 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
sane.dns.zones."uninsane.org".inet.CNAME."bt" = "native";
|
sane.dns.zones."uninsane.org".inet.CNAME."bt" = "native";
|
||||||
sane.ports.ports."51413" = {
|
|
||||||
protocol = [ "tcp" "udp" ];
|
|
||||||
visibleTo.ovpn = true;
|
|
||||||
description = "colin-bittorrent";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,15 +11,12 @@ in lib.mkMerge [
|
|||||||
# 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;
|
||||||
# FIXME(2023/11/26): services.trust-dns.debug doesn't log requests: use RUST_LOG=debug env for that.
|
|
||||||
# - see: <https://github.com/hickory-dns/hickory-dns/issues/2082>
|
|
||||||
# services.trust-dns.debug = true;
|
# services.trust-dns.debug = true;
|
||||||
|
|
||||||
sane.ports.ports."53" = {
|
sane.ports.ports."53" = {
|
||||||
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";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -148,12 +145,11 @@ in lib.mkMerge [
|
|||||||
-e s/%CNAMENATIVE%/servo.${flavor}/ \
|
-e s/%CNAMENATIVE%/servo.${flavor}/ \
|
||||||
-e s/%ANATIVE%/${anative}/ \
|
-e s/%ANATIVE%/${anative}/ \
|
||||||
-e s/%AWAN%/$wan/ \
|
-e s/%AWAN%/$wan/ \
|
||||||
-e s/%AOVPNS%/185.157.162.178/ \
|
|
||||||
${zoneTemplate} > ${zoneFor flavor}
|
${zoneTemplate} > ${zoneFor flavor}
|
||||||
'';
|
'';
|
||||||
serviceConfig = config.systemd.services.trust-dns.serviceConfig // {
|
serviceConfig = config.systemd.services.trust-dns.serviceConfig // {
|
||||||
ExecStart = ''
|
ExecStart = ''
|
||||||
${pkgs.trust-dns}/bin/${pkgs.trust-dns.meta.mainProgram} \
|
${pkgs.trust-dns}/bin/trust-dns \
|
||||||
--port ${builtins.toString port} \
|
--port ${builtins.toString port} \
|
||||||
--zonedir ${zoneDirFor flavor}/ \
|
--zonedir ${zoneDirFor flavor}/ \
|
||||||
--config ${configFile} ${flagsStr}
|
--config ${configFile} ${flagsStr}
|
||||||
|
@@ -8,66 +8,38 @@
|
|||||||
./hosts.nix
|
./hosts.nix
|
||||||
./ids.nix
|
./ids.nix
|
||||||
./machine-id.nix
|
./machine-id.nix
|
||||||
./net
|
./net.nix
|
||||||
./nix-path
|
./nix-path
|
||||||
./persist.nix
|
./persist.nix
|
||||||
./programs
|
./programs
|
||||||
./secrets.nix
|
./secrets.nix
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
./users
|
./users
|
||||||
|
./vpn.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
sane.nixcache.enable-trusted-keys = true;
|
sane.nixcache.enable-trusted-keys = true;
|
||||||
sane.nixcache.enable = lib.mkDefault true;
|
sane.nixcache.enable = lib.mkDefault true;
|
||||||
sane.persist.enable = lib.mkDefault true;
|
sane.persist.enable = lib.mkDefault true;
|
||||||
sane.root-on-tmpfs = lib.mkDefault true;
|
|
||||||
sane.programs.sysadminUtils.enableFor.system = lib.mkDefault true;
|
sane.programs.sysadminUtils.enableFor.system = lib.mkDefault true;
|
||||||
sane.programs.consoleUtils.enableFor.user.colin = lib.mkDefault true;
|
sane.programs.consoleUtils.enableFor.user.colin = lib.mkDefault true;
|
||||||
|
|
||||||
nixpkgs.config.allowUnfree = true; # NIXPKGS_ALLOW_UNFREE=1
|
nixpkgs.config.allowUnfree = true;
|
||||||
nixpkgs.config.allowBroken = true; # NIXPKGS_ALLOW_BROKEN=1
|
nixpkgs.config.allowBroken = true; # NIXPKGS_ALLOW_BROKEN
|
||||||
|
|
||||||
# time.timeZone = "America/Los_Angeles";
|
# time.timeZone = "America/Los_Angeles";
|
||||||
time.timeZone = "Etc/UTC"; # DST is too confusing for me => use a stable timezone
|
time.timeZone = "Etc/UTC"; # DST is too confusing for me => use a stable timezone
|
||||||
|
|
||||||
nix.extraOptions = ''
|
|
||||||
# see: `man nix.conf`
|
|
||||||
# useful when a remote builder has a faster internet connection than me
|
|
||||||
builders-use-substitutes = true # default: false
|
|
||||||
# maximum seconds to wait when connecting to binary substituter
|
|
||||||
connect-timeout = 3 # default: 0
|
|
||||||
# download-attempts = 5 # default: 5
|
|
||||||
# allow `nix flake ...` command
|
# allow `nix flake ...` command
|
||||||
|
# TODO: is this still required?
|
||||||
|
nix.extraOptions = ''
|
||||||
experimental-features = nix-command flakes
|
experimental-features = nix-command flakes
|
||||||
# whether to build from source when binary substitution fails
|
|
||||||
fallback = true # default: false
|
|
||||||
# whether to keep building dependencies if any other one fails
|
|
||||||
keep-going = true # default: false
|
|
||||||
# whether to keep build-only dependencies of GC roots (e.g. C compiler) when doing GC
|
|
||||||
keep-outputs = true # default: false
|
|
||||||
# how many lines to show from failed build
|
|
||||||
log-lines = 30 # default: 10
|
|
||||||
# narinfo-cache-negative-ttl = 3600 # default: 3600
|
|
||||||
# whether to use ~/.local/state/nix/profile instead of ~/.nix-profile, etc
|
|
||||||
use-xdg-base-directories = true # default: false
|
|
||||||
# whether to warn if repository has uncommited changes
|
|
||||||
warn-dirty = false # default: true
|
|
||||||
'';
|
'';
|
||||||
# hardlinks identical files in the nix store to save 25-35% disk space.
|
# hardlinks identical files in the nix store to save 25-35% disk space.
|
||||||
# unclear _when_ this occurs. it's not a service.
|
# unclear _when_ this occurs. it's not a service.
|
||||||
# does the daemon continually scan the nix store?
|
# does the daemon continually scan the nix store?
|
||||||
# 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;
|
||||||
# TODO: see if i can remove this?
|
|
||||||
nix.settings.trusted-users = [ "root" ];
|
|
||||||
|
|
||||||
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
|
||||||
@@ -92,21 +64,7 @@
|
|||||||
text = ''
|
text = ''
|
||||||
# show which packages changed versions or are new/removed in this upgrade
|
# show which packages changed versions or are new/removed in this upgrade
|
||||||
# source: <https://github.com/luishfonseca/dotfiles/blob/32c10e775d9ec7cc55e44592a060c1c9aadf113e/modules/upgrade-diff.nix>
|
# source: <https://github.com/luishfonseca/dotfiles/blob/32c10e775d9ec7cc55e44592a060c1c9aadf113e/modules/upgrade-diff.nix>
|
||||||
# modified to not error on boot (when /run/current-system doesn't exist)
|
|
||||||
if [ -d /run/current-system ]; then
|
|
||||||
${pkgs.nvd}/bin/nvd --nix-bin-dir=${pkgs.nix}/bin diff /run/current-system "$systemConfig"
|
${pkgs.nvd}/bin/nvd --nix-bin-dir=${pkgs.nix}/bin diff /run/current-system "$systemConfig"
|
||||||
fi
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
system.activationScripts.notifyActive = {
|
|
||||||
text = ''
|
|
||||||
# send a notification to any sway users logged in, that the system has been activated/upgraded.
|
|
||||||
# this probably doesn't work if more than one sway session exists on the system.
|
|
||||||
_notifyActiveSwaySock="$(echo /run/user/*/sway-ipc.*.sock)"
|
|
||||||
if [ -e "$_notifyActiveSwaySock" ]; then
|
|
||||||
SWAYSOCK="$_notifyActiveSwaySock" ${pkgs.sway}/bin/swaymsg -- exec \
|
|
||||||
"${pkgs.libnotify}/bin/notify-send 'nixos activated' 'version: $(cat $systemConfig/nixos-version)'"
|
|
||||||
fi
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -50,8 +50,6 @@ let
|
|||||||
else
|
else
|
||||||
"infrequent"
|
"infrequent"
|
||||||
));
|
));
|
||||||
} // lib.optionalAttrs (lib.hasPrefix "https://www.youtube.com/" raw.url) {
|
|
||||||
format = "video";
|
|
||||||
} // lib.optionalAttrs (raw.is_podcast or false) {
|
} // lib.optionalAttrs (raw.is_podcast or false) {
|
||||||
format = "podcast";
|
format = "podcast";
|
||||||
} // lib.optionalAttrs (raw.title or "" != "") {
|
} // lib.optionalAttrs (raw.title or "" != "") {
|
||||||
@@ -59,176 +57,213 @@ 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 "anchor.fm/s/2da69154/podcast/rss" // tech) # POD OF JAKE -- https://podofjake.com/
|
|
||||||
(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.simplecast.com/l2i9YnTd" // tech // pol) # Hard Fork (NYtimes tech)
|
|
||||||
(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 "omny.fm/shows/the-dollop-with-dave-anthony-and-gareth-reynolds") # The Dollop history/comedy
|
(fromDb "feeds.libsyn.com/421877" // rat)
|
||||||
(fromDb "originstories.libsyn.com" // uncat)
|
## Econ Talk
|
||||||
(fromDb "podcast.posttv.com/itunes/post-reports.xml" // pol)
|
(fromDb "feeds.simplecast.com/wgl4xEgL" // rat)
|
||||||
# (fromDb "podcast.thelinuxexp.com" // tech) # low-brow linux/foss PR announcements
|
## Cory Doctorow -- both podcast & text entries
|
||||||
(fromDb "politicalorphanage.libsyn.com" // pol)
|
(fromDb "craphound.com" // pol)
|
||||||
(fromDb "reverseengineering.libsyn.com/rss" // tech) # UnNamed Reverse Engineering Podcast
|
## Maggie Killjoy -- referenced by Cory Doctorow
|
||||||
(fromDb "rss.acast.com/deconstructed") # The Intercept - Deconstructed
|
(fromDb "omny.fm/shows/cool-people-who-did-cool-stuff" // pol)
|
||||||
(fromDb "rss.acast.com/ft-tech-tonic" // tech)
|
## also Maggie Killjoy
|
||||||
(fromDb "rss.acast.com/intercepted-with-jeremy-scahill") # The Intercept - Intercepted
|
(fromDb "feeds.megaphone.fm/behindthebastards" // pol)
|
||||||
(fromDb "rss.art19.com/60-minutes" // pol)
|
## Jennifer Briney
|
||||||
(fromDb "rss.art19.com/the-portal" // rat) # Eric Weinstein
|
(fromDb "congressionaldish.libsyn.com" // pol)
|
||||||
(fromDb "seattlenice.buzzsprout.com" // pol)
|
|
||||||
(fromDb "srslywrong.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 "werenotwrong.fireside.fm" // pol)
|
||||||
|
(fromDb "politicalorphanage.libsyn.com" // 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)
|
||||||
|
## 3/4 German; 1/4 eps are English
|
||||||
|
(fromDb "omegataupodcast.net" // tech)
|
||||||
|
## Lateral with Tom Scott
|
||||||
|
(mkPod "https://audioboom.com/channels/5097784.rss" // tech)
|
||||||
];
|
];
|
||||||
|
|
||||||
texts = [
|
texts = [
|
||||||
(fromDb "amosbbatto.wordpress.com" // tech)
|
# AGGREGATORS (> 1 post/day)
|
||||||
(fromDb "applieddivinitystudies.com" // rat)
|
|
||||||
(fromDb "artemis.sh" // tech)
|
|
||||||
(fromDb "ascii.textfiles.com" // tech) # Jason Scott
|
|
||||||
(fromDb "austinvernon.site" // tech)
|
|
||||||
(fromDb "balajis.com" // pol) # Balaji
|
|
||||||
(fromDb "ben-evans.com/benedictevans" // pol)
|
|
||||||
(fromDb "bitbashing.io" // tech)
|
|
||||||
(fromDb "bitsaboutmoney.com" // uncat)
|
|
||||||
(fromDb "blog.danieljanus.pl" // tech)
|
|
||||||
(fromDb "blog.dshr.org" // pol) # David Rosenthal
|
|
||||||
(fromDb "blog.jmp.chat" // tech)
|
|
||||||
(fromDb "blog.rust-lang.org" // tech)
|
|
||||||
(fromDb "blog.thalheim.io" // tech) # Mic92
|
|
||||||
(fromDb "bunniestudios.com" // tech) # Bunnie Juang
|
|
||||||
(fromDb "capitolhillseattle.com" // pol)
|
|
||||||
# (fromDb "drewdevault.com" // tech)
|
|
||||||
# (fromDb "econlib.org" // pol)
|
|
||||||
(fromDb "edwardsnowden.substack.com" // pol // text)
|
|
||||||
(fromDb "fasterthanli.me" // tech)
|
|
||||||
(fromDb "gwern.net" // rat)
|
|
||||||
(fromDb "harihareswara.net" // tech // pol) # rec by Cory Doctorow
|
|
||||||
(fromDb "ianthehenry.com" // tech)
|
|
||||||
(fromDb "idiomdrottning.org" // uncat)
|
|
||||||
(fromDb "interconnected.org/home/feed" // rat) # Matt Webb -- engineering-ish, but dreamy
|
|
||||||
(fromDb "jeffgeerling.com" // tech)
|
|
||||||
(fromDb "jefftk.com" // tech)
|
|
||||||
(fromDb "kosmosghost.github.io/index.xml" // tech)
|
|
||||||
# (fromDb "lesswrong.com" // rat)
|
|
||||||
(fromDb "linmob.net" // tech)
|
|
||||||
(fromDb "lwn.net" // tech)
|
(fromDb "lwn.net" // tech)
|
||||||
(fromDb "lynalden.com" // pol)
|
# (fromDb "lesswrong.com" // rat)
|
||||||
(fromDb "mako.cc/copyrighteous" // tech // pol) # rec by Cory Doctorow
|
# (fromDb "econlib.org" // pol)
|
||||||
(fromDb "mg.lol" // tech)
|
|
||||||
(fromDb "mindingourway.com" // rat)
|
# AGGREGATORS (< 1 post/day)
|
||||||
(fromDb "morningbrew.com/feed" // pol)
|
|
||||||
(fromDb "overcomingbias.com" // rat) # Robin Hanson
|
|
||||||
(fromDb "palladiummag.com" // uncat)
|
(fromDb "palladiummag.com" // uncat)
|
||||||
(fromDb "philosopher.coach" // rat) # Peter Saint-Andre -- side project of stpeter.im
|
|
||||||
(fromDb "pomeroyb.com" // tech)
|
|
||||||
(fromDb "preposterousuniverse.com" // rat) # Sean Carroll
|
|
||||||
(fromDb "profectusmag.com" // uncat)
|
(fromDb "profectusmag.com" // uncat)
|
||||||
(fromDb "project-insanity.org" // tech) # shared blog by a few NixOS devs, notably onny
|
|
||||||
(fromDb "putanumonit.com" // rat) # mostly dating topics. not advice, or humor, but looking through a social lens
|
|
||||||
(fromDb "richardcarrier.info" // rat)
|
|
||||||
(fromDb "rifters.com/crawl" // uncat) # No Moods, Ads or Cutesy Fucking Icons
|
|
||||||
(fromDb "righto.com" // tech) # Ken Shirriff
|
|
||||||
(fromDb "rootsofprogress.org" // rat) # Jason Crawford
|
|
||||||
(fromDb "sagacioussuricata.com" // tech) # ian (Sanctuary)
|
|
||||||
(fromDb "semiaccurate.com" // tech)
|
(fromDb "semiaccurate.com" // tech)
|
||||||
(fromDb "sideways-view.com" // rat) # Paul Christiano
|
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
||||||
(fromDb "slimemoldtimemold.com" // rat)
|
(fromDb "tuxphones.com" // tech)
|
||||||
(fromDb "spectrum.ieee.org" // tech)
|
(fromDb "spectrum.ieee.org" // tech)
|
||||||
(fromDb "stpeter.im/atom.xml" // pol)
|
|
||||||
# (fromDb "theregister.com" // tech)
|
# (fromDb "theregister.com" // tech)
|
||||||
(fromDb "thisweek.gnome.org" // tech)
|
(fromDb "thisweek.gnome.org" // tech)
|
||||||
(fromDb "tuxphones.com" // tech)
|
# more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
|
||||||
|
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent)
|
||||||
|
(mkText "https://nixos.org/blog/stories-rss.xml" // tech // weekly)
|
||||||
|
## n.b.: quality RSS list here: <https://forum.merveilles.town/thread/57/share-your-rss-feeds%21-6/>
|
||||||
|
(mkText "https://forum.merveilles.town/rss.xml" // pol // infrequent)
|
||||||
|
|
||||||
|
## No Moods, Ads or Cutesy Fucking Icons
|
||||||
|
(fromDb "rifters.com/crawl" // uncat)
|
||||||
|
|
||||||
|
# DEVELOPERS
|
||||||
|
(fromDb "blog.jmp.chat" // tech)
|
||||||
(fromDb "uninsane.org" // tech)
|
(fromDb "uninsane.org" // tech)
|
||||||
(fromDb "unintendedconsequenc.es" // rat)
|
(fromDb "ascii.textfiles.com" // tech) # Jason Scott
|
||||||
# (fromDb "vitalik.ca" // tech) # moved to vitalik.eth.limo
|
|
||||||
(fromDb "vitalik.eth.limo" // tech) # Vitalik Buterin
|
|
||||||
(fromDb "webcurious.co.uk" // uncat)
|
|
||||||
(fromDb "xn--gckvb8fzb.com" // tech)
|
(fromDb "xn--gckvb8fzb.com" // tech)
|
||||||
(mkSubstack "astralcodexten" // rat // daily) # Scott Alexander
|
(fromDb "amosbbatto.wordpress.com" // tech)
|
||||||
|
(fromDb "fasterthanli.me" // tech)
|
||||||
|
(fromDb "mg.lol" // tech)
|
||||||
|
# (fromDb "drewdevault.com" // tech)
|
||||||
|
## Ken Shirriff
|
||||||
|
(fromDb "righto.com" // tech)
|
||||||
|
## shared blog by a few NixOS devs, notably onny
|
||||||
|
(fromDb "project-insanity.org" // tech)
|
||||||
|
## Vitalik Buterin
|
||||||
|
(fromDb "vitalik.ca" // tech)
|
||||||
|
## ian (Sanctuary)
|
||||||
|
(fromDb "sagacioussuricata.com" // tech)
|
||||||
|
## Bunnie Juang
|
||||||
|
(fromDb "bunniestudios.com" // tech)
|
||||||
|
(fromDb "blog.danieljanus.pl" // tech)
|
||||||
|
(fromDb "ianthehenry.com" // tech)
|
||||||
|
(fromDb "bitbashing.io" // tech)
|
||||||
|
(fromDb "idiomdrottning.org" // uncat)
|
||||||
|
(mkText "http://boginjr.com/feed" // tech // infrequent)
|
||||||
|
(mkText "https://anish.lakhwara.com/home.html" // tech // weekly)
|
||||||
|
(fromDb "jefftk.com" // tech)
|
||||||
|
(fromDb "pomeroyb.com" // tech)
|
||||||
|
# (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
|
||||||
|
|
||||||
|
# TECH PROJECTS
|
||||||
|
(fromDb "blog.rust-lang.org" // tech)
|
||||||
|
|
||||||
|
# (TECH; POL) COMMENTATORS
|
||||||
|
## Matt Webb -- engineering-ish, but dreamy
|
||||||
|
(fromDb "interconnected.org/home/feed" // rat)
|
||||||
|
(fromDb "edwardsnowden.substack.com" // pol // text)
|
||||||
|
## Julia Evans
|
||||||
|
(mkText "https://jvns.ca/atom.xml" // tech // weekly)
|
||||||
|
(mkText "http://benjaminrosshoffman.com/feed" // pol // weekly)
|
||||||
|
## Ben Thompson
|
||||||
|
(mkText "https://www.stratechery.com/rss" // pol // weekly)
|
||||||
|
## Balaji
|
||||||
|
(fromDb "balajis.com" // pol)
|
||||||
|
(fromDb "ben-evans.com/benedictevans" // pol)
|
||||||
|
(fromDb "lynalden.com" // pol)
|
||||||
|
(fromDb "austinvernon.site" // tech)
|
||||||
|
(mkSubstack "oversharing" // pol // daily)
|
||||||
(mkSubstack "byrnehobart" // pol // infrequent)
|
(mkSubstack "byrnehobart" // pol // infrequent)
|
||||||
# (mkSubstack "doomberg" // tech // weekly) # articles are all pay-walled
|
# (mkSubstack "doomberg" // tech // weekly) # articles are all pay-walled
|
||||||
(mkSubstack "eliqian" // rat // weekly)
|
## David Rosenthal
|
||||||
(mkSubstack "oversharing" // pol // daily)
|
(fromDb "blog.dshr.org" // pol)
|
||||||
(mkSubstack "samkriss" // humor // infrequent)
|
## Matt Levine
|
||||||
(mkText "http://benjaminrosshoffman.com/feed" // pol // weekly)
|
(mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly)
|
||||||
(mkText "http://boginjr.com/feed" // tech // infrequent)
|
(fromDb "stpeter.im/atom.xml" // pol)
|
||||||
(mkText "https://acoup.blog/feed" // rat // weekly)
|
## Peter Saint-Andre -- side project of stpeter.im
|
||||||
(mkText "https://anish.lakhwara.com/home.html" // tech // weekly)
|
(fromDb "philosopher.coach" // rat)
|
||||||
(mkText "https://forum.merveilles.town/rss.xml" // pol // infrequent) #quality RSS list here: <https://forum.merveilles.town/thread/57/share-your-rss-feeds%21-6/>
|
(fromDb "morningbrew.com/feed" // pol)
|
||||||
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
|
|
||||||
(mkText "https://jvns.ca/atom.xml" // tech // weekly) # Julia Evans
|
|
||||||
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
|
||||||
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent) # more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
|
|
||||||
(mkText "https://nixos.org/blog/stories-rss.xml" // tech // weekly)
|
|
||||||
# (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
|
|
||||||
(mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly) # Matt Levine
|
|
||||||
(mkText "https://www.stratechery.com/rss" // pol // weekly) # Ben Thompson
|
|
||||||
];
|
|
||||||
|
|
||||||
videos = [
|
# RATIONALITY/PHILOSOPHY/ETC
|
||||||
(fromDb "youtube.com/@Channel5YouTube" // pol)
|
(mkSubstack "samkriss" // humor // infrequent)
|
||||||
(fromDb "youtube.com/@ColdFusion")
|
(fromDb "unintendedconsequenc.es" // rat)
|
||||||
(fromDb "youtube.com/@ContraPoints" // pol)
|
(fromDb "applieddivinitystudies.com" // rat)
|
||||||
(fromDb "youtube.com/@Exurb1a")
|
(fromDb "slimemoldtimemold.com" // rat)
|
||||||
(fromDb "youtube.com/@hbomberguy")
|
(fromDb "richardcarrier.info" // rat)
|
||||||
(fromDb "youtube.com/@JackStauber")
|
(fromDb "gwern.net" // rat)
|
||||||
(fromDb "youtube.com/@PolyMatter")
|
## Jason Crawford
|
||||||
# (fromDb "youtube.com/@rossmanngroup" // pol // tech) # Louis Rossmann
|
(fromDb "rootsofprogress.org" // rat)
|
||||||
(fromDb "youtube.com/@TechnologyConnections" // tech)
|
## Robin Hanson
|
||||||
(fromDb "youtube.com/@TheB1M")
|
(fromDb "overcomingbias.com" // rat)
|
||||||
(fromDb "youtube.com/@TomScottGo")
|
## Scott Alexander
|
||||||
(fromDb "youtube.com/@Vihart")
|
(mkSubstack "astralcodexten" // rat // daily)
|
||||||
(fromDb "youtube.com/@Vox")
|
## Paul Christiano
|
||||||
(fromDb "youtube.com/@Vsauce")
|
(fromDb "sideways-view.com" // rat)
|
||||||
|
## Sean Carroll
|
||||||
|
(fromDb "preposterousuniverse.com" // rat)
|
||||||
|
(mkSubstack "eliqian" // rat // weekly)
|
||||||
|
(mkText "https://acoup.blog/feed" // rat // weekly)
|
||||||
|
(fromDb "mindingourway.com" // rat)
|
||||||
|
|
||||||
|
## mostly dating topics. not advice, or humor, but looking through a social lens
|
||||||
|
(fromDb "putanumonit.com" // rat)
|
||||||
|
|
||||||
|
# LOCAL
|
||||||
|
(fromDb "capitolhillseattle.com" // pol)
|
||||||
|
|
||||||
|
# CODE
|
||||||
|
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
|
||||||
];
|
];
|
||||||
|
|
||||||
images = [
|
images = [
|
||||||
(fromDb "miniature-calendar.com" // img // art // daily)
|
|
||||||
(fromDb "pbfcomics.com" // img // humor)
|
|
||||||
(fromDb "poorlydrawnlines.com/feed" // img // humor)
|
|
||||||
(fromDb "smbc-comics.com" // img // humor)
|
(fromDb "smbc-comics.com" // img // humor)
|
||||||
(fromDb "turnoff.us" // img // humor)
|
|
||||||
(fromDb "xkcd.com" // img // humor)
|
(fromDb "xkcd.com" // img // humor)
|
||||||
|
(fromDb "turnoff.us" // img // humor)
|
||||||
|
(fromDb "pbfcomics.com" // img // humor)
|
||||||
|
# (mkImg "http://dilbert.com/feed" // humor // daily)
|
||||||
|
(fromDb "poorlydrawnlines.com/feed" // img // humor)
|
||||||
|
|
||||||
|
# ART
|
||||||
|
(fromDb "miniature-calendar.com" // img // art // daily)
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
sane.feeds = texts ++ images ++ podcasts ++ videos;
|
sane.feeds = texts ++ images ++ podcasts;
|
||||||
|
|
||||||
assertions = builtins.map
|
assertions = builtins.map
|
||||||
(p: {
|
(p: {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
{ lib, pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
@@ -9,20 +9,8 @@
|
|||||||
# useful emergency utils
|
# useful emergency utils
|
||||||
boot.initrd.extraUtilsCommands = ''
|
boot.initrd.extraUtilsCommands = ''
|
||||||
copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfstune
|
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.e2fsprogs}/bin/resize2fs
|
|
||||||
'' + lib.optionalString pkgs.stdenv.hostPlatform.isx86_64 ''
|
|
||||||
copy_bin_and_libs ${pkgs.nvme-cli}/bin/nvme # doesn't cross compile
|
|
||||||
'';
|
'';
|
||||||
boot.kernelParams = [
|
boot.kernelParams = [ "boot.shell_on_fail" ];
|
||||||
"boot.shell_on_fail"
|
|
||||||
#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:
|
# other kernelParams:
|
||||||
# "boot.trace"
|
# "boot.trace"
|
||||||
# "systemd.log_level=debug"
|
# "systemd.log_level=debug"
|
||||||
@@ -40,12 +28,6 @@
|
|||||||
# non-free firmware
|
# non-free firmware
|
||||||
hardware.enableRedistributableFirmware = true;
|
hardware.enableRedistributableFirmware = true;
|
||||||
|
|
||||||
# default is 252274, which is too low particularly for servo.
|
|
||||||
# manifests as spurious "No space left on device" when trying to install watches,
|
|
||||||
# e.g. in dyn-dns by `systemctl start dyn-dns-watcher.path`.
|
|
||||||
# see: <https://askubuntu.com/questions/828779/failed-to-add-run-systemd-ask-password-to-directory-watch-no-space-left-on-dev>
|
|
||||||
boot.kernel.sysctl."fs.inotify.max_user_watches" = 1048576;
|
|
||||||
|
|
||||||
# powertop will default to putting USB devices -- including HID -- to sleep after TWO SECONDS
|
# powertop will default to putting USB devices -- including HID -- to sleep after TWO SECONDS
|
||||||
powerManagement.powertop.enable = false;
|
powerManagement.powertop.enable = false;
|
||||||
# linux CPU governor: <https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt>
|
# linux CPU governor: <https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt>
|
||||||
@@ -64,19 +46,10 @@
|
|||||||
powerManagement.cpuFreqGovernor = "ondemand";
|
powerManagement.cpuFreqGovernor = "ondemand";
|
||||||
|
|
||||||
services.logind.extraConfig = ''
|
services.logind.extraConfig = ''
|
||||||
# see: `man logind.conf`
|
# don’t shutdown when power button is short-pressed
|
||||||
# don’t shutdown when power button is short-pressed (commonly done an accident, or by cats).
|
HandlePowerKey=ignore
|
||||||
# but do on long-press: useful to gracefully power-off server.
|
|
||||||
HandlePowerKey=lock
|
|
||||||
HandlePowerKeyLongPress=poweroff
|
|
||||||
HandleLidSwitch=lock
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# some packages build only if binfmt *isn't* present
|
|
||||||
nix.settings.system-features = lib.mkIf (config.boot.binfmt.emulatedSystems == []) [
|
|
||||||
"no-binfmt"
|
|
||||||
];
|
|
||||||
|
|
||||||
# services.snapper.configs = {
|
# services.snapper.configs = {
|
||||||
# root = {
|
# root = {
|
||||||
# subvolume = "/";
|
# subvolume = "/";
|
||||||
|
@@ -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" ];
|
||||||
|
@@ -1,32 +1,19 @@
|
|||||||
{ config, lib, ...}:
|
{ config, lib, ...}:
|
||||||
|
|
||||||
let
|
let
|
||||||
# [ ProgramConfig ]
|
|
||||||
enabledPrograms = builtins.filter
|
|
||||||
(p: p.enabled)
|
|
||||||
(builtins.attrValues config.sane.programs);
|
|
||||||
|
|
||||||
# [ { "<mime-type>" = { prority, desktop } ]
|
|
||||||
enabledWeightedMimes = builtins.map weightedMimes enabledPrograms;
|
|
||||||
|
|
||||||
# ProgramConfig -> { "<mime-type>" = { priority, desktop }; }
|
# ProgramConfig -> { "<mime-type>" = { priority, desktop }; }
|
||||||
weightedMimes = prog: builtins.mapAttrs
|
weightedMimes = prog: builtins.mapAttrs (_key: desktop: { priority = prog.mime.priority; desktop = desktop; }) prog.mime.associations;
|
||||||
(_key: desktop: {
|
|
||||||
priority = prog.mime.priority; desktop = desktop;
|
|
||||||
})
|
|
||||||
prog.mime.associations;
|
|
||||||
|
|
||||||
# [ { "<mime-type>" = { priority, desktop } ]; } ] -> { "<mime-type>" = [ { priority, desktop } ... ]; }
|
# [ { "<mime-type>" = { priority, desktop } ]; } ] -> { "<mime-type>" = [ { priority, desktop } ... ]; }
|
||||||
mergeMimes = mimes: lib.foldAttrs (item: acc: [item] ++ acc) [] mimes;
|
mergeMimes = mimes: lib.foldAttrs (item: acc: [item] ++ acc) [] mimes;
|
||||||
|
|
||||||
# [ { priority, desktop } ... ] -> Self
|
# [ { priority, desktop } ... ] -> Self
|
||||||
sortOneMimeType = associations: builtins.sort
|
sortOneMimeType = associations: builtins.sort (l: r: assert l.priority != r.priority; l.priority < r.priority) associations;
|
||||||
(l: r: assert l.priority != r.priority; l.priority < r.priority)
|
|
||||||
associations;
|
|
||||||
sortMimes = mimes: builtins.mapAttrs (_k: sortOneMimeType) mimes;
|
sortMimes = mimes: builtins.mapAttrs (_k: sortOneMimeType) mimes;
|
||||||
removePriorities = mimes: builtins.mapAttrs
|
removePriorities = mimes: builtins.mapAttrs (_k: associations: builtins.map (a: a.desktop) associations) mimes;
|
||||||
(_k: associations: builtins.map (a: a.desktop) associations)
|
|
||||||
mimes;
|
# [ ProgramConfig ]
|
||||||
|
enabledPrograms = builtins.filter (p: p.enabled) (builtins.attrValues config.sane.programs);
|
||||||
|
# [ { "<mime-type>" = { prority, desktop } ]
|
||||||
|
enabledWeightedMimes = builtins.map weightedMimes enabledPrograms;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# the xdg mime type for a file can be found with:
|
# the xdg mime type for a file can be found with:
|
||||||
@@ -38,6 +25,4 @@ in
|
|||||||
# there's also options to *remove* [non-default] associations from specific apps
|
# there's also options to *remove* [non-default] associations from specific apps
|
||||||
xdg.mime.enable = true;
|
xdg.mime.enable = true;
|
||||||
xdg.mime.defaultApplications = removePriorities (sortMimes (mergeMimes enabledWeightedMimes));
|
xdg.mime.defaultApplications = removePriorities (sortMimes (mergeMimes enabledWeightedMimes));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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) {
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
sane.hosts.by-name."moby" = {
|
sane.hosts.by-name."moby" = {
|
||||||
# ssh.authorized = lib.mkDefault false; # moby's too easy to hijack: don't let it ssh places
|
ssh.authorized = lib.mkDefault false; # moby's too easy to hijack: don't let it ssh places
|
||||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrR+gePnl0nV/vy7I5BzrGeyVL+9eOuXHU1yNE3uCwU";
|
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrR+gePnl0nV/vy7I5BzrGeyVL+9eOuXHU1yNE3uCwU";
|
||||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1N/IT3nQYUD+dBlU1sTEEVMxfOyMkrrDeyHcYgnJvw";
|
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1N/IT3nQYUD+dBlU1sTEEVMxfOyMkrrDeyHcYgnJvw";
|
||||||
wg-home.pubkey = "I7XIR1hm8bIzAtcAvbhWOwIAabGkuEvbWH/3kyIB1yA=";
|
wg-home.pubkey = "I7XIR1hm8bIzAtcAvbhWOwIAabGkuEvbWH/3kyIB1yA=";
|
||||||
@@ -36,10 +36,4 @@
|
|||||||
wg-home.endpoint = "uninsane.org:51820";
|
wg-home.endpoint = "uninsane.org:51820";
|
||||||
lan-ip = "10.78.79.51";
|
lan-ip = "10.78.79.51";
|
||||||
};
|
};
|
||||||
|
|
||||||
sane.hosts.by-name."supercap" = {
|
|
||||||
ssh.authorized = false;
|
|
||||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHf/mqqkX45EWAcquV04MC3SUljTApdclH1gjI19F+PA";
|
|
||||||
lan-ip = "10.78.79.232";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -49,14 +49,6 @@
|
|||||||
sane.ids.media.gid = 2414;
|
sane.ids.media.gid = 2414;
|
||||||
sane.ids.ntfy-sh.uid = 2415;
|
sane.ids.ntfy-sh.uid = 2415;
|
||||||
sane.ids.ntfy-sh.gid = 2415;
|
sane.ids.ntfy-sh.gid = 2415;
|
||||||
sane.ids.monero.uid = 2416;
|
|
||||||
sane.ids.monero.gid = 2416;
|
|
||||||
sane.ids.slskd.uid = 2417;
|
|
||||||
sane.ids.slskd.gid = 2417;
|
|
||||||
sane.ids.bitcoind-mainnet.uid = 2418;
|
|
||||||
sane.ids.bitcoind-mainnet.gid = 2418;
|
|
||||||
sane.ids.clightning.uid = 2419;
|
|
||||||
sane.ids.clightning.gid = 2419;
|
|
||||||
|
|
||||||
sane.ids.colin.uid = 1000;
|
sane.ids.colin.uid = 1000;
|
||||||
sane.ids.guest.uid = 1100;
|
sane.ids.guest.uid = 1100;
|
||||||
@@ -71,8 +63,6 @@
|
|||||||
sane.ids.systemd-oom.uid = 2005;
|
sane.ids.systemd-oom.uid = 2005;
|
||||||
sane.ids.systemd-oom.gid = 2005;
|
sane.ids.systemd-oom.gid = 2005;
|
||||||
sane.ids.wireshark.gid = 2006;
|
sane.ids.wireshark.gid = 2006;
|
||||||
sane.ids.nixremote.uid = 2007;
|
|
||||||
sane.ids.nixremote.gid = 2007;
|
|
||||||
|
|
||||||
# found on graphical hosts
|
# found on graphical hosts
|
||||||
sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy
|
sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy
|
||||||
|
@@ -1,12 +1,6 @@
|
|||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
|
||||||
./dns.nix
|
|
||||||
./hostnames.nix
|
|
||||||
./upnp.nix
|
|
||||||
./vpn.nix
|
|
||||||
];
|
|
||||||
# the default backend is "wpa_supplicant".
|
# the default backend is "wpa_supplicant".
|
||||||
# wpa_supplicant reliably picks weak APs to connect to.
|
# wpa_supplicant reliably picks weak APs to connect to.
|
||||||
# see: <https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/474>
|
# see: <https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/474>
|
||||||
@@ -41,6 +35,10 @@
|
|||||||
# e.g. openconnect drags in webkitgtk (for SSO)!
|
# e.g. openconnect drags in webkitgtk (for SSO)!
|
||||||
networking.networkmanager.plugins = lib.mkForce [];
|
networking.networkmanager.plugins = lib.mkForce [];
|
||||||
|
|
||||||
|
networking.firewall.allowedUDPPorts = [
|
||||||
|
1900 # to received UPnP advertisements. required by sane-ip-check-upnp
|
||||||
|
];
|
||||||
|
|
||||||
# keyfile.path = where networkmanager should look for connection credentials
|
# keyfile.path = where networkmanager should look for connection credentials
|
||||||
networking.networkmanager.extraConfig = ''
|
networking.networkmanager.extraConfig = ''
|
||||||
[keyfile]
|
[keyfile]
|
@@ -1,37 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
# use systemd's stub resolver.
|
|
||||||
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
|
|
||||||
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets
|
|
||||||
# in the ovnps namespace to use the provider's DNS resolvers.
|
|
||||||
# a weakness is we can only query 1 NS at a time (unless we were to clone the packets?)
|
|
||||||
# there also seems to be some cache somewhere that's shared between the two namespaces.
|
|
||||||
# i think this is a libc thing. might need to leverage proper cgroups to _really_ kill it.
|
|
||||||
# - getent ahostsv4 www.google.com
|
|
||||||
# - try fix: <https://serverfault.com/questions/765989/connect-to-3rd-party-vpn-server-but-dont-use-it-as-the-default-route/766290#766290>
|
|
||||||
services.resolved.enable = true;
|
|
||||||
# without DNSSEC:
|
|
||||||
# - dig matrix.org => works
|
|
||||||
# - curl https://matrix.org => works
|
|
||||||
# with default DNSSEC:
|
|
||||||
# - dig matrix.org => works
|
|
||||||
# - curl https://matrix.org => fails
|
|
||||||
# i don't know why. this might somehow be interfering with the DNS run on this device (trust-dns)
|
|
||||||
services.resolved.dnssec = "false";
|
|
||||||
networking.nameservers = [
|
|
||||||
# use systemd-resolved resolver
|
|
||||||
# full resolver (which understands /etc/hosts) lives on 127.0.0.53
|
|
||||||
# stub resolver (just forwards upstream) lives on 127.0.0.54
|
|
||||||
"127.0.0.53"
|
|
||||||
];
|
|
||||||
|
|
||||||
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
|
|
||||||
# in a way that's unaware of my VPN routing, so routes are frequently poor against
|
|
||||||
# services which advertise different IPs based on geolocation.
|
|
||||||
# nscd claims to be usable without a cache, but in practice i can't get it to not cache!
|
|
||||||
# nsncd is the Name Service NON-Caching Daemon. it's a drop-in that doesn't cache;
|
|
||||||
# this is OK on the host -- because systemd-resolved caches. it's probably sub-optimal
|
|
||||||
# in the netns and we query upstream DNS more often than needed. hm.
|
|
||||||
# TODO: run a separate recursive resolver in each namespace.
|
|
||||||
services.nscd.enableNsncd = true;
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
# give each host a shortname that all the other hosts know, to allow easy comms.
|
|
||||||
networking.hosts = lib.mkMerge (builtins.map
|
|
||||||
(host: let
|
|
||||||
cfg = config.sane.hosts.by-name."${host}";
|
|
||||||
in {
|
|
||||||
"${cfg.lan-ip}" = [ host ];
|
|
||||||
} // lib.optionalAttrs (cfg.wg-home.ip != null) {
|
|
||||||
"${cfg.wg-home.ip}" = [ "${host}-hn" ];
|
|
||||||
})
|
|
||||||
(builtins.attrNames config.sane.hosts.by-name)
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,20 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
networking.firewall.allowedUDPPorts = [
|
|
||||||
# to receive UPnP advertisements. required by sane-ip-check.
|
|
||||||
# N.B. sane-ip-check isn't query/response based. it needs to receive on port 1900 -- not receive responses FROM port 1900.
|
|
||||||
1900
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.firewall.extraCommands = with pkgs; ''
|
|
||||||
# after an outgoing SSDP query to the multicast address, open FW for incoming responses.
|
|
||||||
# necessary for anything DLNA, especially go2tv
|
|
||||||
# source: <https://serverfault.com/a/911286>
|
|
||||||
# context: <https://github.com/alexballas/go2tv/issues/72>
|
|
||||||
|
|
||||||
# ipset -! means "don't fail if set already exists"
|
|
||||||
${ipset}/bin/ipset create -! upnp hash:ip,port timeout 10
|
|
||||||
${iptables}/bin/iptables -A OUTPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j SET --add-set upnp src,src --exist
|
|
||||||
${iptables}/bin/iptables -A INPUT -p udp -m set --match-set upnp dst,dst -j ACCEPT
|
|
||||||
'';
|
|
||||||
}
|
|
@@ -1,4 +1,4 @@
|
|||||||
{ pkgs, sane-lib, ... }:
|
{ pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# allow `nix-shell` (and probably nix-index?) to locate our patched and custom packages
|
# allow `nix-shell` (and probably nix-index?) to locate our patched and custom packages
|
||||||
@@ -10,7 +10,4 @@
|
|||||||
# to avoid switching so much during development
|
# to avoid switching so much during development
|
||||||
"nixpkgs-overlays=/home/colin/dev/nixos/hosts/common/nix-path/overlay"
|
"nixpkgs-overlays=/home/colin/dev/nixos/hosts/common/nix-path/overlay"
|
||||||
];
|
];
|
||||||
|
|
||||||
# ensure new deployments have a source of this repo with which they can bootstrap.
|
|
||||||
environment.etc."nixos".source = ../../..;
|
|
||||||
}
|
}
|
||||||
|
@@ -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"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -1,99 +0,0 @@
|
|||||||
# discord gtk3 client
|
|
||||||
{ config, lib, pkgs, ... }:
|
|
||||||
let
|
|
||||||
cfg = config.sane.programs.abaddon;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
sane.programs.abaddon = {
|
|
||||||
configOption = with lib; mkOption {
|
|
||||||
default = {};
|
|
||||||
type = types.submodule {
|
|
||||||
options.autostart = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
package = pkgs.abaddon.overrideAttrs (upstream: {
|
|
||||||
patches = (upstream.patches or []) ++ [
|
|
||||||
(pkgs.fetchpatch {
|
|
||||||
url = "https://git.uninsane.org/colin/abaddon/commit/eb551f188d34679f75adcbc83cb8d5beb4d19fd6.patch";
|
|
||||||
name = ''"view members" default to false'';
|
|
||||||
hash = "sha256-9BX8iO86CU1lNrKS1G2BjDR+3IlV9bmhRNTsLrxChwQ=";
|
|
||||||
})
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
suggestedPrograms = [ "gnome-keyring" ];
|
|
||||||
|
|
||||||
fs.".config/abaddon/abaddon.ini".symlink.text = ''
|
|
||||||
# see abaddon README.md for options.
|
|
||||||
# at time of writing:
|
|
||||||
# | Setting | Type | Default | Description |
|
|
||||||
# |[discord]------|---------|---------|--------------------------------------------------------------------------------------------------|
|
|
||||||
# | `gateway` | string | | override url for Discord gateway. must be json format and use zlib stream compression |
|
|
||||||
# | `api_base` | string | | override base url for Discord API |
|
|
||||||
# | `memory_db` | boolean | false | if true, Discord data will be kept in memory as opposed to on disk |
|
|
||||||
# | `token` | string | | Discord token used to login, this can be set from the menu |
|
|
||||||
# | `prefetch` | boolean | false | if true, new messages will cause the avatar and image attachments to be automatically downloaded |
|
|
||||||
# | `autoconnect` | boolean | false | autoconnect to discord |
|
|
||||||
# |[http]--------|--------|---------|---------------------------------------------------------------------------------------------|
|
|
||||||
# | `user_agent` | string | | sets the user-agent to use in HTTP requests to the Discord API (not including media/images) |
|
|
||||||
# | `concurrent` | int | 20 | how many images can be concurrently retrieved |
|
|
||||||
# |[gui}------------------------|---------|---------|----------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
# | `member_list_discriminator` | boolean | true | show user discriminators in the member list |
|
|
||||||
# | `stock_emojis` | boolean | true | allow abaddon to substitute unicode emojis with images from emojis.bin, must be false to allow GTK to render emojis itself |
|
|
||||||
# | `custom_emojis` | boolean | true | download and use custom Discord emojis |
|
|
||||||
# | `css` | string | | path to the main CSS file |
|
|
||||||
# | `animations` | boolean | true | use animated images where available (e.g. server icons, emojis, avatars). false means static images will be used |
|
|
||||||
# | `animated_guild_hover_only` | boolean | true | only animate guild icons when the guild is being hovered over |
|
|
||||||
# | `owner_crown` | boolean | true | show a crown next to the owner |
|
|
||||||
# | `unreads` | boolean | true | show unread indicators and mention badges |
|
|
||||||
# | `save_state` | boolean | true | save the state of the gui (active channels, tabs, expanded channels) |
|
|
||||||
# | `alt_menu` | boolean | false | keep the menu hidden unless revealed with alt key |
|
|
||||||
# | `hide_to_tray` | boolean | false | hide abaddon to the system tray on window close |
|
|
||||||
# | `show_deleted_indicator` | boolean | true | show \[deleted\] indicator next to deleted messages instead of actually deleting the message |
|
|
||||||
# | `font_scale` | double | | scale font rendering. 1 is unchanged |
|
|
||||||
# |[style]------------------|--------|-----------------------------------------------------|
|
|
||||||
# | `linkcolor` | string | color to use for links in messages |
|
|
||||||
# | `expandercolor` | string | color to use for the expander in the channel list |
|
|
||||||
# | `nsfwchannelcolor` | string | color to use for NSFW channels in the channel list |
|
|
||||||
# | `channelcolor` | string | color to use for SFW channels in the channel list |
|
|
||||||
# | `mentionbadgecolor` | string | background color for mention badges |
|
|
||||||
# | `mentionbadgetextcolor` | string | color to use for number displayed on mention badges |
|
|
||||||
# | `unreadcolor` | string | color to use for the unread indicator |
|
|
||||||
# |[notifications]|---------|--------------------------|-------------------------------------------------------------------------------|
|
|
||||||
# | `enabled` | boolean | true (if not on Windows) | Enable desktop notifications |
|
|
||||||
# | `playsound` | boolean | true | Enable notification sounds. Requires ENABLE_NOTIFICATION_SOUNDS=TRUE in CMake |
|
|
||||||
# |[voice]--|--------|------------------------------------|------------------------------------------------------------|
|
|
||||||
# | `vad` | string | rnnoise if enabled, gate otherwise | Method used for voice activity detection. Changeable in UI |
|
|
||||||
# |[windows]|---------|---------|-------------------------|
|
|
||||||
# | `hideconsole` | boolean | true | Hide console on startup |
|
|
||||||
|
|
||||||
# N.B.: abaddon writes this file itself (and even when i don't change anything internally).
|
|
||||||
# it prefers no spaces around the equal sign.
|
|
||||||
[discord]
|
|
||||||
autoconnect=true
|
|
||||||
|
|
||||||
[notifications]
|
|
||||||
# playsound: i manage sounds via swaync
|
|
||||||
playsound=false
|
|
||||||
'';
|
|
||||||
|
|
||||||
persist.byStore.private = [
|
|
||||||
".cache/abaddon"
|
|
||||||
];
|
|
||||||
|
|
||||||
services.abaddon = {
|
|
||||||
description = "unofficial Discord chat client";
|
|
||||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
ExecStart = "${cfg.package}/bin/abaddon";
|
|
||||||
Type = "simple";
|
|
||||||
Restart = "always";
|
|
||||||
RestartSec = "20s";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@@ -7,34 +7,18 @@
|
|||||||
{
|
{
|
||||||
sane.programs.alacritty = {
|
sane.programs.alacritty = {
|
||||||
env.TERMINAL = lib.mkDefault "alacritty";
|
env.TERMINAL = lib.mkDefault "alacritty";
|
||||||
fs.".config/alacritty/alacritty.toml".symlink.text = ''
|
# note: alacritty will switch to .toml config in 13.0 release
|
||||||
[font]
|
# - run `alacritty migrate` to convert the yaml to toml
|
||||||
size = 14
|
fs.".config/alacritty/alacritty.yml".symlink.text = ''
|
||||||
|
font:
|
||||||
|
size: 14
|
||||||
|
|
||||||
[[keyboard.bindings]]
|
key_bindings:
|
||||||
mods = "Control"
|
- { key: N, mods: Control, action: CreateNewWindow }
|
||||||
key = "N"
|
- { key: PageUp, mods: Control, action: ScrollPageUp }
|
||||||
action = "CreateNewWindow"
|
- { key: PageDown, mods: Control, action: ScrollPageDown }
|
||||||
|
- { key: PageUp, mods: Control|Shift, action: ScrollPageUp }
|
||||||
[[keyboard.bindings]]
|
- { key: PageDown, mods: Control|Shift, action: ScrollPageDown }
|
||||||
mods = "Control"
|
|
||||||
key = "PageUp"
|
|
||||||
action = "ScrollPageUp"
|
|
||||||
|
|
||||||
[[keyboard.bindings]]
|
|
||||||
mods = "Control"
|
|
||||||
key = "PageDown"
|
|
||||||
action = "ScrollPageDown"
|
|
||||||
|
|
||||||
[[keyboard.bindings]]
|
|
||||||
mods = "Control|Shift"
|
|
||||||
key = "PageUp"
|
|
||||||
action = "ScrollPageUp"
|
|
||||||
|
|
||||||
[[keyboard.bindings]]
|
|
||||||
mods = "Control|Shift"
|
|
||||||
key = "PageDown"
|
|
||||||
action = "ScrollPageDown"
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
sane.programs.animatch = {
|
|
||||||
persist.byStore.plaintext = [
|
|
||||||
# game progress
|
|
||||||
".config/Holy Pangolin/Animatch"
|
|
||||||
".local/share/Holy Pangolin/Animatch" # i think this one might be wrong
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
@@ -20,7 +20,6 @@ in
|
|||||||
"sane-scripts.bt-show"
|
"sane-scripts.bt-show"
|
||||||
];
|
];
|
||||||
"sane-scripts.dev" = declPackageSet [
|
"sane-scripts.dev" = declPackageSet [
|
||||||
"sane-scripts.clone"
|
|
||||||
"sane-scripts.dev-cargo-loop"
|
"sane-scripts.dev-cargo-loop"
|
||||||
"sane-scripts.git-init"
|
"sane-scripts.git-init"
|
||||||
];
|
];
|
||||||
@@ -42,12 +41,12 @@ in
|
|||||||
"sane-scripts.secrets-unlock"
|
"sane-scripts.secrets-unlock"
|
||||||
"sane-scripts.secrets-update-keys"
|
"sane-scripts.secrets-update-keys"
|
||||||
"sane-scripts.shutdown"
|
"sane-scripts.shutdown"
|
||||||
|
"sane-scripts.ssl-dump"
|
||||||
"sane-scripts.sudo-redirect"
|
"sane-scripts.sudo-redirect"
|
||||||
"sane-scripts.sync-from-servo"
|
"sane-scripts.sync-from-servo"
|
||||||
"sane-scripts.tag-music"
|
|
||||||
"sane-scripts.vpn"
|
"sane-scripts.vpn"
|
||||||
"sane-scripts.which"
|
"sane-scripts.which"
|
||||||
"sane-scripts.wipe"
|
"sane-scripts.wipe-browser"
|
||||||
];
|
];
|
||||||
"sane-scripts.sys-utils" = declPackageSet [
|
"sane-scripts.sys-utils" = declPackageSet [
|
||||||
"sane-scripts.ip-port-forward"
|
"sane-scripts.ip-port-forward"
|
||||||
@@ -59,10 +58,8 @@ 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
|
"dtc" # device tree [de]compiler
|
||||||
"e2fsprogs" # resize2fs
|
|
||||||
"efibootmgr"
|
"efibootmgr"
|
||||||
"ethtool"
|
"ethtool"
|
||||||
"fatresize"
|
"fatresize"
|
||||||
@@ -70,9 +67,8 @@ in
|
|||||||
"file"
|
"file"
|
||||||
# "fwupd"
|
# "fwupd"
|
||||||
"gawk"
|
"gawk"
|
||||||
"gdb" # to debug segfaults
|
|
||||||
"git"
|
"git"
|
||||||
"gptfdisk" # gdisk
|
"gptfdisk"
|
||||||
"hdparm"
|
"hdparm"
|
||||||
"htop"
|
"htop"
|
||||||
"iftop"
|
"iftop"
|
||||||
@@ -90,7 +86,6 @@ in
|
|||||||
"netcat"
|
"netcat"
|
||||||
"nethogs"
|
"nethogs"
|
||||||
"nmap"
|
"nmap"
|
||||||
"nvme-cli" # nvme
|
|
||||||
"openssl"
|
"openssl"
|
||||||
"parted"
|
"parted"
|
||||||
"pciutils"
|
"pciutils"
|
||||||
@@ -98,18 +93,15 @@ 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
|
||||||
"xq" # jq for XML
|
|
||||||
# "zfs" # doesn't cross-compile (requires samba)
|
|
||||||
];
|
];
|
||||||
sysadminExtraUtils = declPackageSet [
|
sysadminExtraUtils = declPackageSet [
|
||||||
"backblaze-b2"
|
"backblaze-b2"
|
||||||
@@ -125,13 +117,12 @@ 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
|
"binutils" # 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"
|
||||||
@@ -143,8 +134,6 @@ in
|
|||||||
"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
|
|
||||||
"mimeo" # like xdg-open
|
|
||||||
"neovim" # needed as a user package, for swap persistence
|
"neovim" # needed as a user package, for swap persistence
|
||||||
# "nettools"
|
# "nettools"
|
||||||
# "networkmanager"
|
# "networkmanager"
|
||||||
@@ -154,7 +143,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"
|
||||||
@@ -169,12 +158,13 @@ in
|
|||||||
# "unar"
|
# "unar"
|
||||||
"unzip"
|
"unzip"
|
||||||
"wireguard-tools"
|
"wireguard-tools"
|
||||||
|
"xdg-terminal-exec"
|
||||||
"xdg-utils" # for xdg-open
|
"xdg-utils" # for xdg-open
|
||||||
# "yarn"
|
# "yarn"
|
||||||
"zsh"
|
"zsh"
|
||||||
];
|
];
|
||||||
|
|
||||||
pcConsoleUtils = declPackageSet [
|
desktopConsoleUtils = declPackageSet [
|
||||||
"gh" # MS GitHub cli
|
"gh" # MS GitHub cli
|
||||||
"nix-index"
|
"nix-index"
|
||||||
"nixpkgs-review"
|
"nixpkgs-review"
|
||||||
@@ -183,19 +173,17 @@ in
|
|||||||
];
|
];
|
||||||
|
|
||||||
consoleMediaUtils = declPackageSet [
|
consoleMediaUtils = declPackageSet [
|
||||||
"catt" # cast videos to chromecast
|
|
||||||
"ffmpeg"
|
"ffmpeg"
|
||||||
"go2tv" # cast videos to UPNP/DLNA device (i.e. tv).
|
|
||||||
"imagemagick"
|
"imagemagick"
|
||||||
"sox"
|
"sox"
|
||||||
"yt-dlp"
|
"yt-dlp"
|
||||||
];
|
];
|
||||||
|
|
||||||
pcTuiApps = declPackageSet [
|
tuiApps = declPackageSet [
|
||||||
"aerc" # email client
|
"aerc" # email client
|
||||||
"msmtp" # sendmail
|
"msmtp" # sendmail
|
||||||
"offlineimap" # email mailbox sync
|
"offlineimap" # email mailbox sync
|
||||||
# "sfeed" # RSS fetcher
|
"sfeed" # RSS fetcher
|
||||||
"visidata" # TUI spreadsheet viewer/editor
|
"visidata" # TUI spreadsheet viewer/editor
|
||||||
"w3m" # web browser
|
"w3m" # web browser
|
||||||
];
|
];
|
||||||
@@ -210,9 +198,7 @@ in
|
|||||||
devPkgs = declPackageSet [
|
devPkgs = declPackageSet [
|
||||||
"cargo"
|
"cargo"
|
||||||
"clang"
|
"clang"
|
||||||
"lua"
|
|
||||||
"nodejs"
|
"nodejs"
|
||||||
"patchelf"
|
|
||||||
"rustc"
|
"rustc"
|
||||||
"tree-sitter"
|
"tree-sitter"
|
||||||
];
|
];
|
||||||
@@ -220,22 +206,17 @@ in
|
|||||||
|
|
||||||
# INDIVIDUAL PACKAGE DEFINITIONS
|
# INDIVIDUAL PACKAGE DEFINITIONS
|
||||||
|
|
||||||
cargo.persist.byStore.plaintext = [ ".cargo" ];
|
cargo.persist.plaintext = [ ".cargo" ];
|
||||||
|
|
||||||
# auth token, preferences
|
|
||||||
delfin.persist.byStore.private = [ ".config/delfin" ];
|
|
||||||
|
|
||||||
# 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" ];
|
||||||
|
|
||||||
endless-sky.persist.byStore.plaintext = [ ".local/share/endless-sky" ];
|
|
||||||
|
|
||||||
# `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 {
|
font-manager.package = pkgs.font-manager.override {
|
||||||
# build without the "Google Fonts" integration feature, to save closure / avoid webkitgtk_4_0
|
# build without the "Google Fonts" integration feature, to save closure / avoid webkitgtk_4_0
|
||||||
@@ -244,44 +225,45 @@ in
|
|||||||
|
|
||||||
# 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-2048.persist.byStore.plaintext = [ ".local/share/gnome-2048/scores" ];
|
"gnome.gnome-maps".persist.plaintext = [ ".cache/shumate" ];
|
||||||
|
"gnome.gnome-maps".persist.private = [ ".local/share/maps-places.json" ];
|
||||||
"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; [
|
# creds, media
|
||||||
requests
|
signal-desktop.persist.private = [ ".config/Signal" ];
|
||||||
]);
|
|
||||||
|
|
||||||
shattered-pixel-dungeon.persist.byStore.plaintext = [ ".local/share/.shatteredpixel/shattered-pixel-dungeon" ];
|
|
||||||
|
|
||||||
# printer/filament settings
|
# printer/filament settings
|
||||||
slic3r.persist.byStore.plaintext = [ ".Slic3r" ];
|
slic3r.persist.plaintext = [ ".Slic3r" ];
|
||||||
|
|
||||||
space-cadet-pinball.persist.byStore.plaintext = [ ".local/share/SpaceCadetPinball" ];
|
# creds, widevine .so download. TODO: could easily manage these statically.
|
||||||
|
spotify.persist.plaintext = [ ".config/spotify" ];
|
||||||
|
|
||||||
superTux.persist.byStore.plaintext = [ ".local/share/supertux2" ];
|
tdesktop.persist.private = [ ".local/share/TelegramDesktop" ];
|
||||||
|
|
||||||
tdesktop.persist.byStore.private = [ ".local/share/TelegramDesktop" ];
|
tokodon.persist.private = [ ".cache/KDE/tokodon" ];
|
||||||
|
|
||||||
tokodon.persist.byStore.private = [ ".cache/KDE/tokodon" ];
|
# 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;
|
||||||
|
};
|
||||||
|
|
||||||
vvvvvv.persist.byStore.plaintext = [ ".local/share/VVVVVV" ];
|
whalebird.persist.private = [ ".config/Whalebird" ];
|
||||||
|
|
||||||
whalebird.persist.byStore.private = [ ".config/Whalebird" ];
|
yarn.persist.plaintext = [ ".cache/yarn" ];
|
||||||
|
|
||||||
yarn.persist.byStore.plaintext = [ ".cache/yarn" ];
|
# zcash coins. safe to delete, just slow to regenerate (10-60 minutes)
|
||||||
|
zecwallet-lite.persist.private = [ ".zcash" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
programs.feedbackd = lib.mkIf config.sane.programs.feedbackd.enabled {
|
programs.feedbackd = lib.mkIf config.sane.programs.feedbackd.enabled {
|
||||||
|
@@ -1,22 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
sane.programs.audacity = {
|
|
||||||
package = pkgs.audacity.override {
|
|
||||||
# wxGTK32 uses webkitgtk-4.0.
|
|
||||||
# audacity doesn't actually need webkit though, so diable to reduce closure
|
|
||||||
wxGTK32 = pkgs.wxGTK32.override {
|
|
||||||
withWebKit = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# disable first-run splash screen
|
|
||||||
fs.".config/audacity/audacity.cfg".file.text = ''
|
|
||||||
PrefsVersion=1.1.1r1
|
|
||||||
[GUI]
|
|
||||||
ShowSplashScreen=0
|
|
||||||
[Version]
|
|
||||||
Major=3
|
|
||||||
Minor=4
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,103 +0,0 @@
|
|||||||
{ lib, pkgs, ... }:
|
|
||||||
# notable bemenu options:
|
|
||||||
# - see `bemenu --help` for all
|
|
||||||
# -P, --prefix text to show before highlighted item.
|
|
||||||
# --scrollbar display scrollbar. (none (default), always, autohide)
|
|
||||||
# -H, --line-height defines the height to make each menu line (0 = default height). (wx)
|
|
||||||
# -M, --margin defines the empty space on either side of the menu. (wx)
|
|
||||||
# -W, --width-factor defines the relative width factor of the menu (from 0 to 1). (wx)
|
|
||||||
# -B, --border defines the width of the border in pixels around the menu. (wx)
|
|
||||||
# -R --border-radius defines the radius of the border around the menu (0 = no curved borders).
|
|
||||||
# --ch defines the height of the cursor (0 = scales with line height). (wx)
|
|
||||||
# --cw defines the width of the cursor. (wx)
|
|
||||||
# --hp defines the horizontal padding for the entries in single line mode. (wx)
|
|
||||||
# --fn defines the font to be used ('name [size]'). (wx)
|
|
||||||
# --tb defines the title background color. (wx)
|
|
||||||
# --tf defines the title foreground color. (wx)
|
|
||||||
# --fb defines the filter background color. (wx)
|
|
||||||
# --ff defines the filter foreground color. (wx)
|
|
||||||
# --nb defines the normal background color. (wx)
|
|
||||||
# --nf defines the normal foreground color. (wx)
|
|
||||||
# --hb defines the highlighted background color. (wx)
|
|
||||||
# --hf defines the highlighted foreground color. (wx)
|
|
||||||
# --fbb defines the feedback background color. (wx)
|
|
||||||
# --fbf defines the feedback foreground color. (wx)
|
|
||||||
# --sb defines the selected background color. (wx)
|
|
||||||
# --sf defines the selected foreground color. (wx)
|
|
||||||
# --ab defines the alternating background color. (wx)
|
|
||||||
# --af defines the alternating foreground color. (wx)
|
|
||||||
# --scb defines the scrollbar background color. (wx)
|
|
||||||
# --scf defines the scrollbar foreground color. (wx)
|
|
||||||
# --bdr defines the border color. (wx)
|
|
||||||
#
|
|
||||||
# colors are specified as `#RRGGBB`
|
|
||||||
# defaults:
|
|
||||||
# --ab "#222222"
|
|
||||||
# --af "#bbbbbb"
|
|
||||||
# --bdr "#005577"
|
|
||||||
# --border 3
|
|
||||||
# --cb "#222222"
|
|
||||||
# --center
|
|
||||||
# --cf "#bbbbbb"
|
|
||||||
# --fb "#222222"
|
|
||||||
# --fbb "#eeeeee"
|
|
||||||
# --fbf "#222222"
|
|
||||||
# --ff "#bbbbbb"
|
|
||||||
# --fixed-height
|
|
||||||
# --fn 'Sxmo 14'
|
|
||||||
# --hb "#005577"
|
|
||||||
# --hf "#eeeeee"
|
|
||||||
# --line-height 20
|
|
||||||
# --list 16
|
|
||||||
# --margin 40
|
|
||||||
# --nb "#222222"
|
|
||||||
# --nf "#bbbbbb"
|
|
||||||
# --no-overlap
|
|
||||||
# --no-spacing
|
|
||||||
# --sb "#323232"
|
|
||||||
# --scb "#005577"
|
|
||||||
# --scf "#eeeeee"
|
|
||||||
# --scrollbar autohide
|
|
||||||
# --tb "#005577"
|
|
||||||
# --tf "#eeeeee"
|
|
||||||
# --wrap
|
|
||||||
let
|
|
||||||
bg = "#1d1721"; # slight purple
|
|
||||||
fg0 = "#d8d8d8"; # inactive text (light grey)
|
|
||||||
fg1 = "#ffffff"; # active text (white)
|
|
||||||
accent0 = "#1f5e54"; # darker but saturated teal
|
|
||||||
accent1 = "#418379"; # teal (matches nixos-bg)
|
|
||||||
accent2 = "#5b938a"; # brighter but muted teal
|
|
||||||
bemenuArgs = [
|
|
||||||
"--wrap --scrollbar autohide --fixed-height"
|
|
||||||
"--center --margin 45"
|
|
||||||
"--no-spacing"
|
|
||||||
# XXX: font size doesn't seem to take effect (would prefer larger)
|
|
||||||
"--fn 'monospace 14' --line-height 22 --border 3"
|
|
||||||
"--bdr '${accent0}'" # border
|
|
||||||
"--scf '${accent2}' --scb '${accent0}'" # scrollbar
|
|
||||||
"--tb '${accent0}' --tf '${fg0}'" # title
|
|
||||||
"--fb '${accent0}' --ff '${fg1}'" # filter (i.e. text that's been entered)
|
|
||||||
"--hb '${accent1}' --hf '${fg1}'" # selected item
|
|
||||||
"--nb '${bg}' --nf '${fg0}'" # normal lines (even)
|
|
||||||
"--ab '${bg}' --af '${fg0}'" # alternated lines (odd)
|
|
||||||
"--cf '${accent0}' --cb '${accent0}'" # cursor (not very useful)
|
|
||||||
];
|
|
||||||
bemenuOpts = lib.concatStringsSep " " bemenuArgs;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
sane.programs.bemenu = {
|
|
||||||
package = pkgs.bemenu.overrideAttrs (upstream: {
|
|
||||||
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ [
|
|
||||||
pkgs.makeWrapper
|
|
||||||
];
|
|
||||||
# can alternatively be specified as CLI flags
|
|
||||||
postInstall = (upstream.postInstall or "") + ''
|
|
||||||
wrapProgram $out/bin/bemenu \
|
|
||||||
--set BEMENU_OPTS "${bemenuOpts}"
|
|
||||||
wrapProgram $out/bin/bemenu-run \
|
|
||||||
--set BEMENU_OPTS "${bemenuOpts}"
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
sane.programs.brave = {
|
|
||||||
persist.byStore.cryptClearOnBoot = [
|
|
||||||
".cache/BraveSoftware"
|
|
||||||
".config/BraveSoftware"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
@@ -30,7 +30,7 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
persist.byStore.private = [
|
persist.private = [
|
||||||
# ".cache/folks" # contact avatars?
|
# ".cache/folks" # contact avatars?
|
||||||
# ".config/calls"
|
# ".config/calls"
|
||||||
".local/share/calls" # call "records"
|
".local/share/calls" # call "records"
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
sane.programs.cantata = {
|
sane.programs.cantata = {
|
||||||
persist.byStore.plaintext = [
|
persist.plaintext = [
|
||||||
".cache/cantata" # album art
|
".cache/cantata" # album art
|
||||||
".local/share/cantata/library" # library index (?)
|
".local/share/cantata/library" # library index (?)
|
||||||
];
|
];
|
||||||
|
@@ -1,29 +0,0 @@
|
|||||||
# use like:
|
|
||||||
# - catt -d lgtv_chrome cast ./path/to.mp4
|
|
||||||
#
|
|
||||||
# support matrix:
|
|
||||||
# - webm: audio only
|
|
||||||
# - mp4: audio + video
|
|
||||||
{ config, lib, ... }:
|
|
||||||
let
|
|
||||||
cfg = config.sane.programs.catt;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
sane.programs.catt = {
|
|
||||||
fs.".config/catt/catt.cfg".symlink.text = ''
|
|
||||||
[options]
|
|
||||||
device = lgtv_chrome
|
|
||||||
|
|
||||||
[aliases]
|
|
||||||
lgtv_chrome = 10.78.79.106
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# necessary to cast local files
|
|
||||||
networking.firewall.allowedTCPPortRanges = lib.mkIf cfg.enabled [
|
|
||||||
{
|
|
||||||
from = 45000;
|
|
||||||
to = 47000;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
@@ -1,10 +1,42 @@
|
|||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
chattyNoOauth = pkgs.chatty.override {
|
||||||
|
# the OAuth feature (presumably used for web-based logins) pulls a full webkitgtk.
|
||||||
|
# especially when using the gtk3 version of evolution-data-server, it's an ancient webkitgtk_4_1.
|
||||||
|
# disable OAuth for a faster build & smaller closure
|
||||||
|
evolution-data-server = pkgs.evolution-data-server.override {
|
||||||
|
enableOAuth2 = false;
|
||||||
|
gnome-online-accounts = pkgs.gnome-online-accounts.override {
|
||||||
|
# disables the upstream "goabackend" feature -- presumably "Gnome Online Accounts Backend"
|
||||||
|
# frees us from webkit_4_1, in turn.
|
||||||
|
enableBackend = false;
|
||||||
|
gvfs = pkgs.gvfs.override {
|
||||||
|
# saves 20 minutes of build time, for unused feature
|
||||||
|
samba = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
chatty-latest = pkgs.chatty-latest.override {
|
||||||
|
evolution-data-server-gtk4 = pkgs.evolution-data-server-gtk4.override {
|
||||||
|
gnome-online-accounts = pkgs.gnome-online-accounts.override {
|
||||||
|
# disables the upstream "goabackend" feature -- presumably "Gnome Online Accounts Backend"
|
||||||
|
# frees us from webkit_4_1, in turn.
|
||||||
|
enableBackend = false;
|
||||||
|
gvfs = pkgs.gvfs.override {
|
||||||
|
# saves 20 minutes of build time and cross issues, for unused feature
|
||||||
|
samba = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
sane.programs.chatty = {
|
sane.programs.chatty = {
|
||||||
# package = chattyNoOauth;
|
# package = chattyNoOauth;
|
||||||
package = pkgs.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.
|
# not just XMPP; without this Chatty will regenerate its device-id every boot.
|
||||||
# .purple/ contains XMPP *and* Matrix auth, logs, avatar cache, and a bit more
|
# .purple/ contains XMPP *and* Matrix auth, logs, avatar cache, and a bit more
|
||||||
|
@@ -1,184 +1,32 @@
|
|||||||
#!/bin/sh
|
|
||||||
#!/usr/bin/env nix-shell
|
#!/usr/bin/env nix-shell
|
||||||
#!nix-shell -i bash
|
#!nix-shell -i bash
|
||||||
|
|
||||||
usage() {
|
full=$(cat /sys/class/power_supply/axp20x-battery/charge_full_design)
|
||||||
echo "usage: battery_estimate [options...]"
|
rate=$(cat /sys/class/power_supply/axp20x-battery/current_now)
|
||||||
echo
|
perc=$(cat /sys/class/power_supply/axp20x-battery/capacity)
|
||||||
echo "pretty-prints a battery estimate (icon to indicate state, and a duration estimate)"
|
perc_left=$((100 - $perc))
|
||||||
echo
|
|
||||||
echo "options:"
|
|
||||||
echo " --debug: output additional information, to stderr"
|
|
||||||
echo " --minute-suffix <string>: use the provided string as a minutes suffix"
|
|
||||||
echo " --hour-suffix <string>: use the provided string as an hours suffix"
|
|
||||||
echo " --icon-suffix <string>: use the provided string as an icon suffix"
|
|
||||||
echo " --percent-suffix <string>: use the provided string when displaying percents"
|
|
||||||
}
|
|
||||||
|
|
||||||
# these icons come from sxmo; they only render in nerdfonts
|
# these icons come from sxmo; they only render in nerdfonts
|
||||||
icon_bat_chg=("" "" "" "")
|
bat_dis=""
|
||||||
icon_bat_dis=("" "" "" "")
|
bat_chg=""
|
||||||
suffix_icon=" " # thin space
|
|
||||||
suffix_percent="%"
|
|
||||||
# suffix_icon=" "
|
|
||||||
|
|
||||||
# render time like: 2ʰ08ᵐ
|
|
||||||
# unicode sub/super-scripts: <https://en.wikipedia.org/wiki/Unicode_subscripts_and_superscripts>
|
|
||||||
# symbol_hr="ʰ"
|
|
||||||
# symbol_min="ᵐ"
|
|
||||||
|
|
||||||
# render time like: 2ₕ08ₘ
|
|
||||||
# symbol_hr="ₕ"
|
|
||||||
# symbol_min="ₘ"
|
|
||||||
|
|
||||||
# render time like: 2h08m
|
|
||||||
# symbol_hr="h"
|
|
||||||
# symbol_min="m"
|
|
||||||
|
|
||||||
# render time like: 2:08
|
|
||||||
# symbol_hr=":"
|
|
||||||
# symbol_min=
|
|
||||||
|
|
||||||
# render time like: 2꞉08⧗
|
|
||||||
symbol_hr="꞉"
|
|
||||||
symbol_min="⧗"
|
|
||||||
# variants:
|
|
||||||
# symbol_hr=":"
|
|
||||||
# symbol_min="⧖"
|
|
||||||
# symbol_min="⌛"
|
|
||||||
|
|
||||||
# render time like: 2'08"
|
|
||||||
# symbol_hr="'"
|
|
||||||
# symbol_min='"'
|
|
||||||
|
|
||||||
log() {
|
|
||||||
if [ "$BATTERY_ESTIMATE_DEBUG" = "1" ]; then
|
|
||||||
printf "$@" >&2
|
|
||||||
echo >&2
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
render_icon() {
|
|
||||||
# args:
|
|
||||||
# 1: "chg" or "dis"
|
|
||||||
# 2: current battery percentage
|
|
||||||
level=$(($2 / 25))
|
|
||||||
level=$(($level > 3 ? 3 : $level))
|
|
||||||
level=$(($level < 0 ? 0 : $level))
|
|
||||||
log "icon: %s %d" "$1" "$level"
|
|
||||||
if [ "$1" = "dis" ]; then
|
|
||||||
printf "%s" "${icon_bat_dis[$level]}"
|
|
||||||
elif [ "$1" = "chg" ]; then
|
|
||||||
printf "%s" "${icon_bat_chg[$level]}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
try_path() {
|
|
||||||
# assigns output variables:
|
|
||||||
# - perc, perc_from_full (0-100)
|
|
||||||
# - full, rate (pos means charging)
|
|
||||||
if [ -f "$1/capacity" ]; then
|
|
||||||
log "perc, perc_from_full from %s" "$1/capacity"
|
|
||||||
perc=$(cat "$1/capacity")
|
|
||||||
perc_from_full=$((100 - $perc))
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$1/charge_full_design" ] && [ -f "$1/current_now" ]; then
|
|
||||||
log "full, rate from %s and %s" "$1/charge_full_design" "$1/current_now"
|
|
||||||
# current is positive when charging
|
|
||||||
full=$(cat "$1/charge_full_design")
|
|
||||||
rate=$(cat "$1/current_now")
|
|
||||||
elif [ -f "$1/energy_full" ] && [ -f "$1/power_now" ]; then
|
|
||||||
log "full, rate from %s and %s" "$1/energy_full" "$1/power_now"
|
|
||||||
# power_now is positive when discharging
|
|
||||||
full=$(cat "$1/energy_full")
|
|
||||||
rate=-$(cat "$1/power_now")
|
|
||||||
elif [ -f "$1/energy_full" ] && [ -f "$1/energy_now" ]; then
|
|
||||||
log "full, rate from %s and %s" "$1/energy_full" "$1/energy_now"
|
|
||||||
log " this is a compatibility path for legacy Thinkpad batteries which do not populate the 'power_now' field, and incorrectly populate 'energy_now' with power info"
|
|
||||||
# energy_now is positive when discharging
|
|
||||||
full=$(cat "$1/energy_full")
|
|
||||||
rate=-$(cat "$1/energy_now")
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
try_all_paths() {
|
|
||||||
try_path "/sys/class/power_supply/axp20x-battery" # Pinephone
|
|
||||||
try_path "/sys/class/power_supply/BAT0" # Thinkpad
|
|
||||||
log "perc: %d, perc_from_full: %d" "$perc" "$perc_from_full"
|
|
||||||
log "full: %f, rate: %f" "$full" "$rate"
|
|
||||||
log " rate > 0 means charging, else discharging"
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt_minutes() {
|
fmt_minutes() {
|
||||||
# args:
|
|
||||||
# 1: icon to render
|
|
||||||
# 2: string to show if charge/discharge time is indefinite
|
|
||||||
# 3: minutes to stable state (i.e. to full charge or full discharge)
|
|
||||||
# - we work in minutes instead of hours for precision: bash math is integer-only
|
|
||||||
log "charge/discharge time: %f min" "$3"
|
|
||||||
# args: <battery symbol> <text if ludicrous estimate> <estimated minutes to full/empty>
|
# args: <battery symbol> <text if ludicrous estimate> <estimated minutes to full/empty>
|
||||||
if [ -n "$3" ] && [ "$3" -lt 1440 ]; then
|
if [[ $3 -gt 1440 ]]; then
|
||||||
|
printf "%s %s" "$1" "$2" # more than 1d
|
||||||
|
else
|
||||||
hr=$(($3 / 60))
|
hr=$(($3 / 60))
|
||||||
hr_in_min=$(($hr * 60))
|
hr_in_min=$(($hr * 60))
|
||||||
min=$(($3 - $hr_in_min))
|
min=$(($3 - $hr_in_min))
|
||||||
printf "%s%s%d%s%02d%s" "$1" "$suffix_icon" "$hr" "$symbol_hr" "$min" "$symbol_min"
|
printf "%s %dh%02dm" "$1" "$hr" "$min"
|
||||||
else
|
|
||||||
log "charge/discharge duration > 1d"
|
|
||||||
printf "%s%s%s" "$1" "$suffix_icon" "$2" # more than 1d
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
pretty_output() {
|
if [[ $rate -lt 0 ]]; then
|
||||||
if [ -n "$perc" ]; then
|
# discharging
|
||||||
duration=""
|
fmt_minutes "$bat_dis" '∞' "$(($full * 60 * $perc / (-100 * $rate)))"
|
||||||
if [ "$rate" -gt 0 ]; then
|
elif [[ $rate -gt 0 ]]; then
|
||||||
log "charging"
|
# charging
|
||||||
icon="$(render_icon chg $perc)"
|
fmt_minutes "$bat_chg" '100%' "$(($full * 60 * $perc_left / (100 * $rate)))"
|
||||||
duration="$(($full * 60 * $perc_from_full / (100 * $rate)))"
|
|
||||||
else
|
else
|
||||||
log "discharging"
|
echo "$bat_dis $perc%"
|
||||||
icon="$(render_icon dis $perc)"
|
|
||||||
if [ "$rate" -lt 0 ]; then
|
|
||||||
duration="$(($full * 60 * $perc / (-100 * $rate)))"
|
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
fmt_minutes "$icon" "$perc$suffix_percent" "$duration"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
while [ "$#" -gt 0 ]; do
|
|
||||||
case "$1" in
|
|
||||||
"--debug")
|
|
||||||
shift
|
|
||||||
BATTERY_ESTIMATE_DEBUG=1
|
|
||||||
;;
|
|
||||||
"--icon-suffix")
|
|
||||||
shift
|
|
||||||
suffix_icon="$1"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
"--hour-suffix")
|
|
||||||
shift
|
|
||||||
symbol_hr="$1"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
"--minute-suffix")
|
|
||||||
shift
|
|
||||||
symbol_min="$1"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
"--percent-suffix")
|
|
||||||
shift
|
|
||||||
suffix_percent="$1"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
try_all_paths
|
|
||||||
pretty_output
|
|
||||||
|
@@ -3,11 +3,6 @@
|
|||||||
-- - can also use #rrggbb syntax
|
-- - can also use #rrggbb syntax
|
||||||
-- example configs: <https://forum.manjaro.org/t/conky-showcase-2022/97123>
|
-- example configs: <https://forum.manjaro.org/t/conky-showcase-2022/97123>
|
||||||
-- example configs: <https://www.reddit.com/r/Conkyporn/>
|
-- example configs: <https://www.reddit.com/r/Conkyporn/>
|
||||||
--
|
|
||||||
-- exec options:
|
|
||||||
-- `exec <cmd>` => executes the command, synchronously, renders its output as text
|
|
||||||
-- `texeci <interval_sec> <cmd>` => executes the command periodically, async (to not block render), renders as text
|
|
||||||
-- `pexec <cmd>` => executes the command, synchronously, parses its output
|
|
||||||
|
|
||||||
conky.config = {
|
conky.config = {
|
||||||
out_to_wayland = true,
|
out_to_wayland = true,
|
||||||
@@ -40,43 +35,16 @@ conky.config = {
|
|||||||
color2 = '404040',
|
color2 = '404040',
|
||||||
}
|
}
|
||||||
|
|
||||||
vars = {
|
-- texeci <interval_sec> <cmd>: run the command periodically, _in a separate thread_ so as not to block rendering
|
||||||
-- kBps = 'K/s',
|
|
||||||
kBps = 'ᴷᐟˢ',
|
|
||||||
-- percent = '%',
|
|
||||||
-- percent = '﹪',
|
|
||||||
percent = '٪',
|
|
||||||
-- percent = '⁒',
|
|
||||||
-- percent = '%',
|
|
||||||
icon_suffix = nil,
|
|
||||||
hour_suffix = nil,
|
|
||||||
minute_suffix = '${font sans-serif:size=14}${color2}⧗',
|
|
||||||
}
|
|
||||||
|
|
||||||
bat_args = ""
|
|
||||||
if vars.icon_suffix ~= nil then
|
|
||||||
bat_args = bat_args .. " --icon-suffix '" .. vars.icon_suffix .. "'"
|
|
||||||
end
|
|
||||||
if vars.hour_suffix ~= nil then
|
|
||||||
bat_args = bat_args .. " --hour-suffix '" .. vars.hour_suffix .. "'"
|
|
||||||
end
|
|
||||||
if vars.minute_suffix ~= nil then
|
|
||||||
bat_args = bat_args .. " --minute-suffix '" .. vars.minute_suffix .. "'"
|
|
||||||
end
|
|
||||||
if vars.percent ~= nil then
|
|
||||||
bat_args = bat_args .. " --percent-suffix '" .. vars.percent .. "'"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- N.B.: `[[ <text> ]]` is Lua's multiline string literal
|
|
||||||
conky.text = [[
|
conky.text = [[
|
||||||
${color1}${shadecolor 707070}${font sans-serif:size=50:style=Bold}${alignc}${exec date +"%H:%M"}${font}
|
${color1}${shadecolor 707070}${font sans-serif:size=50:style=Bold}${alignc}${exec date +"%H:%M"}${font}
|
||||||
${color2}${shadecolor a4d7d0}${font sans-serif:size=20}${alignc}${exec date +"%a %d %b"}${font}
|
${color2}${shadecolor a4d7d0}${font sans-serif:size=20}${alignc}${exec date +"%a %d %b"}${font}
|
||||||
|
|
||||||
|
|
||||||
${color1}${shadecolor}${font sans-serif:size=22:style=Bold}${alignc}${execp @bat@ ]] .. bat_args .. [[ }${font}
|
${color1}${shadecolor}${font sans-serif:size=22:style=Bold}${alignc}${exec @bat@ }${font}
|
||||||
${color1}${shadecolor}${font sans-serif:size=20:style=Bold}${alignc}${texeci 600 @weather@ }${font}
|
${color1}${shadecolor}${font sans-serif:size=20:style=Bold}${alignc}${texeci 600 @weather@ }${font}
|
||||||
|
|
||||||
|
|
||||||
${color2}${shadecolor a4d7d0}${font sans-serif:size=16}${alignc}⇅ ${downspeedf wlan0}]] .. vars.kBps .. [[${font}
|
${color2}${shadecolor a4d7d0}${font sans-serif:size=16}${alignc}⇅ ${downspeedf wlan0}K/s${font}
|
||||||
${font sans-serif:size=16}${alignc}☵ $memperc]] .. vars.percent .. [[ $cpu]] .. vars.percent .. [[${font}
|
${font sans-serif:size=16}${alignc}☵ $memperc% $cpu%${font}
|
||||||
]]
|
]]
|
||||||
|
@@ -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
|
||||||
];
|
];
|
||||||
|
@@ -2,42 +2,31 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./abaddon.nix
|
|
||||||
./aerc.nix
|
./aerc.nix
|
||||||
./alacritty.nix
|
./alacritty.nix
|
||||||
./animatch.nix
|
|
||||||
./assorted.nix
|
./assorted.nix
|
||||||
./audacity.nix
|
|
||||||
./bemenu.nix
|
|
||||||
./brave.nix
|
|
||||||
./calls.nix
|
./calls.nix
|
||||||
./cantata.nix
|
./cantata.nix
|
||||||
./catt.nix
|
|
||||||
./chatty.nix
|
./chatty.nix
|
||||||
./conky
|
./conky
|
||||||
./cozy.nix
|
./cozy.nix
|
||||||
./dialect.nix
|
|
||||||
./dino.nix
|
./dino.nix
|
||||||
./element-desktop.nix
|
./element-desktop.nix
|
||||||
./epiphany.nix
|
./epiphany.nix
|
||||||
./evince.nix
|
./evince.nix
|
||||||
./feedbackd.nix
|
./feedbackd.nix
|
||||||
./firefox.nix
|
./firefox.nix
|
||||||
./flare-signal.nix
|
|
||||||
./fontconfig.nix
|
./fontconfig.nix
|
||||||
./fractal.nix
|
./fractal.nix
|
||||||
./fwupd.nix
|
./fwupd.nix
|
||||||
./g4music.nix
|
./g4music.nix
|
||||||
./gajim.nix
|
./gajim.nix
|
||||||
./geary.nix
|
|
||||||
./git.nix
|
./git.nix
|
||||||
./gnome-feeds.nix
|
./gnome-feeds.nix
|
||||||
./gnome-keyring.nix
|
./gnome-keyring.nix
|
||||||
./gnome-weather.nix
|
./gnome-weather.nix
|
||||||
./go2tv.nix
|
|
||||||
./gpodder.nix
|
./gpodder.nix
|
||||||
./gthumb.nix
|
./gthumb.nix
|
||||||
./gtkcord4.nix
|
|
||||||
./helix.nix
|
./helix.nix
|
||||||
./imagemagick.nix
|
./imagemagick.nix
|
||||||
./jellyfin-media-player.nix
|
./jellyfin-media-player.nix
|
||||||
@@ -45,49 +34,34 @@
|
|||||||
./koreader
|
./koreader
|
||||||
./libreoffice.nix
|
./libreoffice.nix
|
||||||
./lemoa.nix
|
./lemoa.nix
|
||||||
./loupe.nix
|
|
||||||
./mako.nix
|
./mako.nix
|
||||||
|
./megapixels.nix
|
||||||
./mepo.nix
|
./mepo.nix
|
||||||
./mimeo
|
|
||||||
./mopidy.nix
|
./mopidy.nix
|
||||||
./mpv.nix
|
./mpv.nix
|
||||||
./msmtp.nix
|
./msmtp.nix
|
||||||
./nautilus.nix
|
|
||||||
./neovim.nix
|
./neovim.nix
|
||||||
./newsflash.nix
|
./newsflash.nix
|
||||||
./nheko.nix
|
./nheko.nix
|
||||||
./nix-index.nix
|
./nix-index.nix
|
||||||
./notejot.nix
|
|
||||||
./ntfy-sh.nix
|
./ntfy-sh.nix
|
||||||
./obsidian.nix
|
./obsidian.nix
|
||||||
./offlineimap.nix
|
./offlineimap.nix
|
||||||
./open-in-mpv.nix
|
|
||||||
./planify.nix
|
|
||||||
./playerctl.nix
|
./playerctl.nix
|
||||||
./rhythmbox.nix
|
./rhythmbox.nix
|
||||||
./ripgrep.nix
|
./ripgrep.nix
|
||||||
./sfeed.nix
|
./sfeed.nix
|
||||||
./signal-desktop.nix
|
|
||||||
./splatmoji.nix
|
./splatmoji.nix
|
||||||
./spot.nix
|
|
||||||
./spotify.nix
|
|
||||||
./steam.nix
|
./steam.nix
|
||||||
./stepmania.nix
|
./stepmania.nix
|
||||||
./sublime-music.nix
|
./sublime-music.nix
|
||||||
./supertuxkart.nix
|
|
||||||
./sway-autoscaler
|
|
||||||
./swaynotificationcenter.nix
|
./swaynotificationcenter.nix
|
||||||
./tangram.nix
|
./tangram.nix
|
||||||
./tor-browser-bundle-bin.nix
|
|
||||||
./tuba.nix
|
./tuba.nix
|
||||||
./vlc.nix
|
./vlc.nix
|
||||||
./wike.nix
|
|
||||||
./wine.nix
|
|
||||||
./wireshark.nix
|
./wireshark.nix
|
||||||
./wob.nix
|
|
||||||
./xarchiver.nix
|
./xarchiver.nix
|
||||||
./zeal.nix
|
./zeal.nix
|
||||||
./zecwallet-lite.nix
|
|
||||||
./zsh
|
./zsh
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
sane.programs.dialect = {
|
|
||||||
package = pkgs.dialect.overrideAttrs (upstream: {
|
|
||||||
# TODO: send upstream
|
|
||||||
# TODO: figure out how to get audio working
|
|
||||||
# TODO: move to runtimeDependencies?
|
|
||||||
buildInputs = upstream.buildInputs ++ [
|
|
||||||
pkgs.glib-networking # for TLS
|
|
||||||
];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
@@ -18,8 +18,6 @@
|
|||||||
# - fix is to toggle it off -> on in the Dino UI
|
# - fix is to toggle it off -> on in the Dino UI
|
||||||
# - default mic gain is WAY TOO MUCH (heavily distorted)
|
# - default mic gain is WAY TOO MUCH (heavily distorted)
|
||||||
# - TODO: dino should have more optimal niceness/priority to ensure it can process its buffers
|
# - 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:
|
# probably fixed:
|
||||||
# - once per 1-2 minutes dino will temporarily drop mic input:
|
# - once per 1-2 minutes dino will temporarily drop mic input:
|
||||||
@@ -40,15 +38,15 @@ in
|
|||||||
type = types.submodule {
|
type = types.submodule {
|
||||||
options.autostart = mkOption {
|
options.autostart = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
persist.byStore.private = [ ".local/share/dino" ];
|
persist.private = [ ".local/share/dino" ];
|
||||||
|
|
||||||
services.dino = {
|
services.dino = {
|
||||||
description = "dino XMPP client";
|
description = "auto-start and maintain dino XMPP connection";
|
||||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = "${cfg.package}/bin/dino";
|
ExecStart = "${cfg.package}/bin/dino";
|
||||||
|
@@ -4,16 +4,11 @@
|
|||||||
# - <https://github.com/vector-im/element-desktop/issues/1029#issuecomment-1632688224>
|
# - <https://github.com/vector-im/element-desktop/issues/1029#issuecomment-1632688224>
|
||||||
# - `rm -rf ~/.config/Element/GPUCache`
|
# - `rm -rf ~/.config/Element/GPUCache`
|
||||||
# - <https://github.com/NixOS/nixpkgs/issues/244486>
|
# - <https://github.com/NixOS/nixpkgs/issues/244486>
|
||||||
{ pkgs, ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
sane.programs.element-desktop = {
|
sane.programs.element-desktop = {
|
||||||
package = pkgs.element-desktop.override {
|
|
||||||
# use pre-build electron because otherwise it takes 4 hrs to build from source.
|
|
||||||
electron = pkgs.electron-bin;
|
|
||||||
};
|
|
||||||
|
|
||||||
# creds/session keys, etc
|
# creds/session keys, etc
|
||||||
persist.byStore.private = [ ".config/Element" ];
|
persist.private = [ ".config/Element" ];
|
||||||
|
|
||||||
suggestedPrograms = [ "gnome-keyring" ];
|
suggestedPrograms = [ "gnome-keyring" ];
|
||||||
};
|
};
|
||||||
|
@@ -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
|
||||||
|
@@ -1,6 +1,3 @@
|
|||||||
# test with e.g.
|
|
||||||
# - `fbcli --event proxied-message-new-instant`
|
|
||||||
|
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
let
|
let
|
||||||
cfg = config.sane.programs.feedbackd;
|
cfg = config.sane.programs.feedbackd;
|
||||||
|
@@ -13,14 +13,7 @@ let
|
|||||||
mobile-prefs = lib.optionals false pkgs.librewolf-pmos-mobile.extraPrefsFiles;
|
mobile-prefs = lib.optionals false pkgs.librewolf-pmos-mobile.extraPrefsFiles;
|
||||||
# allow easy switching between firefox and librewolf with `defaultSettings`, below
|
# allow easy switching between firefox and librewolf with `defaultSettings`, below
|
||||||
librewolfSettings = {
|
librewolfSettings = {
|
||||||
browser = pkgs.librewolf-unwrapped.overrideAttrs (upstream: {
|
browser = pkgs.librewolf-unwrapped;
|
||||||
# TEMP(2023/11/21): fix eval bug in wrapFirefox
|
|
||||||
# see: <https://github.com/NixOS/nixpkgs/pull/244591>
|
|
||||||
passthru = upstream.passthru // {
|
|
||||||
requireSigning = false;
|
|
||||||
allowAddonSideload = true;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
extraPrefsFiles = pkgs.librewolf-unwrapped.extraPrefsFiles ++ mobile-prefs;
|
extraPrefsFiles = pkgs.librewolf-unwrapped.extraPrefsFiles ++ mobile-prefs;
|
||||||
libName = "librewolf";
|
libName = "librewolf";
|
||||||
dotDir = ".librewolf";
|
dotDir = ".librewolf";
|
||||||
@@ -38,16 +31,25 @@ let
|
|||||||
# defaultSettings = firefoxSettings;
|
# defaultSettings = firefoxSettings;
|
||||||
defaultSettings = librewolfSettings;
|
defaultSettings = librewolfSettings;
|
||||||
|
|
||||||
package = (pkgs.wrapFirefox cfg.browser.browser {
|
addon = name: extid: hash: pkgs.fetchFirefoxAddon {
|
||||||
|
inherit name hash;
|
||||||
|
url = "https://addons.mozilla.org/firefox/downloads/latest/${name}/latest.xpi";
|
||||||
|
# extid can be found by unar'ing the above xpi, and copying browser_specific_settings.gecko.id field
|
||||||
|
fixedExtid = extid;
|
||||||
|
};
|
||||||
|
localAddon = pkg: pkgs.fetchFirefoxAddon {
|
||||||
|
inherit (pkg) name;
|
||||||
|
src = "${pkg}/share/mozilla/extensions/\\{ec8030f7-c20a-464f-9b0e-13a3a9e97384\\}/${pkg.extid}.xpi";
|
||||||
|
fixedExtid = pkg.extid;
|
||||||
|
};
|
||||||
|
|
||||||
|
package = pkgs.wrapFirefox cfg.browser.browser {
|
||||||
# inherit the default librewolf.cfg
|
# inherit the default librewolf.cfg
|
||||||
# it can be further customized via ~/.librewolf/librewolf.overrides.cfg
|
# it can be further customized via ~/.librewolf/librewolf.overrides.cfg
|
||||||
inherit (cfg.browser) extraPrefsFiles libName;
|
inherit (cfg.browser) extraPrefsFiles libName;
|
||||||
|
|
||||||
nativeMessagingHosts = lib.optionals cfg.addons.browserpass-extension.enable [
|
extraNativeMessagingHosts = optional cfg.addons.browserpass-extension.enable pkgs.browserpass;
|
||||||
pkgs.browserpass
|
# extraNativeMessagingHosts = [ pkgs.gopass-native-messaging-host ];
|
||||||
] ++ lib.optionals cfg.addons.fxCast.enable [
|
|
||||||
pkgs.fx-cast-bridge
|
|
||||||
];
|
|
||||||
|
|
||||||
nixExtensions = concatMap (ext: optional ext.enable ext.package) (attrValues cfg.addons);
|
nixExtensions = concatMap (ext: optional ext.enable ext.package) (attrValues cfg.addons);
|
||||||
|
|
||||||
@@ -104,17 +106,7 @@ let
|
|||||||
# NewTabPage = true;
|
# NewTabPage = true;
|
||||||
};
|
};
|
||||||
# extraPrefs = ...
|
# extraPrefs = ...
|
||||||
}).overrideAttrs (base: {
|
};
|
||||||
# de-associate `ctrl+shift+c` from activating the devtools.
|
|
||||||
# based on <https://stackoverflow.com/a/54260938>
|
|
||||||
buildCommand = (base.buildCommand or "") + ''
|
|
||||||
mkdir omni
|
|
||||||
${pkgs.buildPackages.unzip}/bin/unzip $out/lib/${cfg.browser.libName}/browser/omni.ja -d omni
|
|
||||||
rm $out/lib/${cfg.browser.libName}/browser/omni.ja
|
|
||||||
${pkgs.buildPackages.gnused}/bin/sed -i s'/devtools-commandkey-inspector = C/devtools-commandkey-inspector = VK_F12/' omni/localization/en-US/devtools/startup/key-shortcuts.ftl
|
|
||||||
pushd omni; ${pkgs.buildPackages.zip}/bin/zip $out/lib/${cfg.browser.libName}/browser/omni.ja -r ./*; popd
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
|
|
||||||
addonOpts = types.submodule {
|
addonOpts = types.submodule {
|
||||||
options = {
|
options = {
|
||||||
@@ -158,16 +150,6 @@ in
|
|||||||
default = {};
|
default = {};
|
||||||
};
|
};
|
||||||
sane.programs.firefox.config.addons = {
|
sane.programs.firefox.config.addons = {
|
||||||
fxCast = {
|
|
||||||
# add a menu to cast to chromecast devices, but it doesn't seem to work very well.
|
|
||||||
# right click (or shift+rc) a video, then select "cast".
|
|
||||||
# - asciinema.org: icon appears, but glitches when clicked.
|
|
||||||
# - youtube.com: no icon appears, even when site is whitelisted.
|
|
||||||
# future: maybe better to have browser open all videos in mpv, and then use mpv for casting.
|
|
||||||
# see e.g. `ff2mpv`, `open-in-mpv` (both are packaged in nixpkgs)
|
|
||||||
package = pkgs.firefox-extensions.fx_cast;
|
|
||||||
enable = lib.mkDefault false;
|
|
||||||
};
|
|
||||||
browserpass-extension = {
|
browserpass-extension = {
|
||||||
package = pkgs.firefox-extensions.browserpass-extension;
|
package = pkgs.firefox-extensions.browserpass-extension;
|
||||||
enable = lib.mkDefault true;
|
enable = lib.mkDefault true;
|
||||||
@@ -176,10 +158,6 @@ in
|
|||||||
package = pkgs.firefox-extensions.bypass-paywalls-clean;
|
package = pkgs.firefox-extensions.bypass-paywalls-clean;
|
||||||
enable = lib.mkDefault true;
|
enable = lib.mkDefault true;
|
||||||
};
|
};
|
||||||
ctrl-shift-c-should-copy = {
|
|
||||||
package = pkgs.firefox-extensions.ctrl-shift-c-should-copy;
|
|
||||||
enable = lib.mkDefault false; # prefer patching firefox source code, so it works in more places
|
|
||||||
};
|
|
||||||
ether-metamask = {
|
ether-metamask = {
|
||||||
package = pkgs.firefox-extensions.ether-metamask;
|
package = pkgs.firefox-extensions.ether-metamask;
|
||||||
enable = lib.mkDefault false; # until i can disable the first-run notification
|
enable = lib.mkDefault false; # until i can disable the first-run notification
|
||||||
@@ -188,10 +166,6 @@ in
|
|||||||
package = pkgs.firefox-extensions.i2p-in-private-browsing;
|
package = pkgs.firefox-extensions.i2p-in-private-browsing;
|
||||||
enable = lib.mkDefault config.services.i2p.enable;
|
enable = lib.mkDefault config.services.i2p.enable;
|
||||||
};
|
};
|
||||||
open-in-mpv = {
|
|
||||||
package = pkgs.firefox-extensions.open-in-mpv;
|
|
||||||
enable = lib.mkDefault config.sane.programs.open-in-mpv.enabled;
|
|
||||||
};
|
|
||||||
sidebery = {
|
sidebery = {
|
||||||
package = pkgs.firefox-extensions.sidebery;
|
package = pkgs.firefox-extensions.sidebery;
|
||||||
enable = lib.mkDefault true;
|
enable = lib.mkDefault true;
|
||||||
@@ -214,10 +188,6 @@ in
|
|||||||
sane.programs.firefox = {
|
sane.programs.firefox = {
|
||||||
inherit package;
|
inherit package;
|
||||||
|
|
||||||
suggestedPrograms = [
|
|
||||||
"open-in-mpv"
|
|
||||||
];
|
|
||||||
|
|
||||||
mime.associations = let
|
mime.associations = let
|
||||||
inherit (cfg.browser) desktop;
|
inherit (cfg.browser) desktop;
|
||||||
in {
|
in {
|
||||||
@@ -255,25 +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
|
|
||||||
// note that too-large scrollbars (like 50px wide) tend to obscure content (and make buttons unclickable)
|
|
||||||
defaultPref("widget.non-native-theme.scrollbar.size.override", 20);
|
|
||||||
defaultPref("widget.non-native-theme.scrollbar.style", 4);
|
|
||||||
|
|
||||||
// disable inertial/kinetic/momentum scrolling because it just gets in the way on touchpads
|
|
||||||
// source: <https://kparal.wordpress.com/2019/10/31/disabling-kinetic-scrolling-in-firefox/>
|
|
||||||
defaultPref("apz.gtk.kinetic_scroll.enabled", false);
|
|
||||||
|
|
||||||
// auto-dispatch mpv:// URIs to xdg-open without prompting.
|
|
||||||
// can do this with other protocols too (e.g. matrix?). see about:config for common handlers.
|
|
||||||
defaultPref("network.protocol-handler.external.mpv", true);
|
|
||||||
// element:// for Element matrix client
|
|
||||||
defaultPref("network.protocol-handler.external.element", true);
|
|
||||||
// matrix: for Nheko matrix client
|
|
||||||
defaultPref("network.protocol-handler.external.matrix", true);
|
|
||||||
'';
|
'';
|
||||||
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).
|
||||||
|
@@ -1,77 +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.
|
|
||||||
#
|
|
||||||
### debugging:
|
|
||||||
# - `RUST_LOG=flare=trace flare`
|
|
||||||
#
|
|
||||||
### error signatures (to reset, run `sane-wipe flare`):
|
|
||||||
#### 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.
|
|
||||||
#
|
|
||||||
#### HTTP 405 when linking flare to iOS signal:
|
|
||||||
# [DEBUG libsignal_service_hyper::push_service] HTTP request PUT https://chat.signal.org/v1/devices/{uuid}.{timestamp?}:{b64-string}
|
|
||||||
# [TRACE libsignal_service_hyper::push_service] Unhandled response 405 with body: {"code":405,"message":"HTTP 405 Method Not Allowed"}
|
|
||||||
# [ERROR flare::gui::error_dialog] ErrorDialog displaying error: Something unexpected happened with the signal backend. Please retry later.
|
|
||||||
# [TRACE flare::gui::error_dialog] ErrorDialog full error: Presage(
|
|
||||||
# ProvisioningError(
|
|
||||||
# ServiceError(
|
|
||||||
# UnhandledResponseCode {
|
|
||||||
# http_code: 405,
|
|
||||||
# },
|
|
||||||
# ),
|
|
||||||
# ),
|
|
||||||
# )
|
|
||||||
# flare matrix suggests the signal endpoint has changed:
|
|
||||||
# - "/v1/device/link instead of confirming via /v1/devices/{I'd}"
|
|
||||||
# - this endpoint is declared in libsignal-service-rs (used both by flare and presage)
|
|
||||||
# - libsignal-service/src/provisioning/manager.rs
|
|
||||||
# - libsignal-service issues a put_json to that URL (i.e. HTTP PUT)
|
|
||||||
# - libsignal-service is "based on" the official rust signal API <https://github.com/signalapp/libsignal>
|
|
||||||
# - did these guys recently change it?
|
|
||||||
# - no, but Signal-Desktop did. see ccb5eb0dd2 from 2023/08/29
|
|
||||||
# - that's a fairly involved change.
|
|
||||||
# - signalcli is reporting this same error: <https://github.com/AsamK/signal-cli/issues/1399>
|
|
||||||
# - Mixin Messenger / libsignal_protocol_dart doesn't seem to be reporting any issue
|
|
||||||
# - <https://github.com/MixinNetwork/flutter-app>
|
|
||||||
#
|
|
||||||
# well, seems to have unpredictable errors particularly when being used on multiple devices.
|
|
||||||
# desktop _seems_ more reliable than on mobile, but not confident.
|
|
||||||
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
sane.programs.flare-signal = {
|
|
||||||
package = pkgs.flare-signal-nixified;
|
|
||||||
# package = pkgs.flare-signal;
|
|
||||||
persist.byStore.private = [
|
|
||||||
# everything: conf, state, files, all opaque
|
|
||||||
".local/share/flare"
|
|
||||||
# also persists a secret in ~/.local/share/keyrings. reset with:
|
|
||||||
# - `secret-tool search --all --unlock 'xdg:schema' 'de.schmidhuberj.Flare'`
|
|
||||||
# - `secret-tool clear 'xdg:schema' 'de.schmidhuberj.Flare'`
|
|
||||||
# and it persists some dconf settings (e.g. device name). reset with:
|
|
||||||
# - `dconf reset -f /de/schmidhuberj/Flare/`.
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,29 +1,10 @@
|
|||||||
# Fractal: GTK4 instant messenger client for the Matrix protocol
|
|
||||||
#
|
|
||||||
# very susceptible to state corruption during hard power-cycles.
|
|
||||||
# if it stalls while launching, especially with a brief message at bottom
|
|
||||||
# "unable to open store"
|
|
||||||
# then:
|
|
||||||
# - remove ~/.local/share/stable/*
|
|
||||||
# - this might give I/O error, in which case remove the corresponding path under
|
|
||||||
# /nix/persist/home/colin/private (which can be found by correlating timestamps/sizes with that in ~/private/.local/share/stable).
|
|
||||||
# - reboot (maybe necessary).
|
|
||||||
# - now you can send messages, and read messages in unencrypted rooms, but not read messages from encrypted rooms.
|
|
||||||
# to fix encrypted message receipt:
|
|
||||||
# - start from above (fractal closed, no ~/.local/share/stable/*)
|
|
||||||
# - in ~/.local/share/keyrings/Default_keyring.keyring:
|
|
||||||
# - find the entry that says "display-name=Fractal: Matrix credentials for <mxid>"
|
|
||||||
# - remove that entry and all associated entries (i.e. ones with same number but different :attributeN)
|
|
||||||
# - REBOOT. otherwise keyring stuff seems to stay cached in RAM
|
|
||||||
# - login to Fractal. give an hour to sync.
|
|
||||||
# - it'll kick you back to a page asking you to cross-sign. open FluffyChat and do the emoji compare. success!
|
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
let
|
let
|
||||||
cfg = config.sane.programs.fractal;
|
cfg = config.sane.programs.fractal;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
sane.programs.fractal = {
|
sane.programs.fractal = {
|
||||||
package = pkgs.fractal-nixified.optimized;
|
package = pkgs.fractal-nixified;
|
||||||
# package = pkgs.fractal-latest;
|
# package = pkgs.fractal-latest;
|
||||||
# package = pkgs.fractal-next;
|
# package = pkgs.fractal-next;
|
||||||
|
|
||||||
@@ -37,11 +18,10 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
persist.byStore.private = [
|
persist.private = [
|
||||||
# XXX by default fractal stores its state in ~/.local/share/<build-profile>/<UUID>.
|
# XXX by default fractal stores its state in ~/.local/share/<build-profile>/<UUID>.
|
||||||
".local/share/hack" # for debug-like builds
|
".local/share/hack" # for debug-like builds
|
||||||
".local/share/stable" # for normal releases
|
".local/share/stable" # for normal releases
|
||||||
".local/share/fractal" # for version 5+, i think?
|
|
||||||
];
|
];
|
||||||
|
|
||||||
suggestedPrograms = [ "gnome-keyring" ];
|
suggestedPrograms = [ "gnome-keyring" ];
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user