Compare commits
372 Commits
dev-export
...
wip-sxmo-s
Author | SHA1 | Date | |
---|---|---|---|
b50ee5dcea | |||
aaa8424c9e | |||
65123aa963 | |||
97d14f4c2c | |||
3e7fc56c86 | |||
346ca57c93 | |||
47e23c1ff3 | |||
b6d2fbdf6d | |||
cf553b1386 | |||
e40cbaf1cf | |||
19b8c0c923 | |||
22e9a48edc | |||
a6b1c23e2b | |||
4a498ef1a9 | |||
4909127ec7 | |||
7a75cad65f | |||
168fcce157 | |||
03d3ea4965 | |||
e5125065d6 | |||
bc3ad7dfa5 | |||
2097c3ad77 | |||
56838a4867 | |||
d35fe126e3 | |||
a6ea5da7a1 | |||
98a6671e95 | |||
243a4c6f0d | |||
e84be3a7b2 | |||
5fdd6881a0 | |||
67192d89a9 | |||
b6c8b1948b | |||
3a71d26638 | |||
a586611aa0 | |||
d7120a14f4 | |||
7db8dabf8f | |||
d89287af11 | |||
b14daac0f8 | |||
f65aaf8852 | |||
5a84c9a585 | |||
464fca9679 | |||
6c6e1ee84b | |||
41d8c6681f | |||
4dbb656a34 | |||
8c4caab995 | |||
83586ce483 | |||
e20c4d01e6 | |||
01cad7b702 | |||
48715546e2 | |||
00b59f6985 | |||
d82d3e55cb | |||
f2c3f9fe52 | |||
2de6c01262 | |||
bbdc6f3aa9 | |||
16ee30b696 | |||
9c341c87d8 | |||
67a9134130 | |||
fe6d2f04c5 | |||
d138c99c61 | |||
290d6a8da5 | |||
09ed98c973 | |||
bc7dee6a80 | |||
4c708baf63 | |||
cc16fe85b0 | |||
7d63132c48 | |||
5acd704ae7 | |||
0c0948e8e1 | |||
6283384522 | |||
b70fc6841f | |||
97dd84ed71 | |||
7a6981253b | |||
9e78ec221b | |||
4a8d7ca1c3 | |||
b9f31c6f4b | |||
cd3bed023a | |||
0ad6b2bc1b | |||
54b0c1bfcf | |||
285dd6a1c9 | |||
1c5e2843a1 | |||
ce9b30767f | |||
d26fa5bec1 | |||
832ca52ccf | |||
c70176bfb2 | |||
cb3cf57465 | |||
dfaeb7b7de | |||
d3818b5e44 | |||
5b8850404b | |||
38fa73cfb7 | |||
43fc050eed | |||
f3423d45bd | |||
56866c1ac1 | |||
99ea6a59c5 | |||
eb5ebf94a7 | |||
bdf049d9e4 | |||
9205e076c5 | |||
60e5f6b41b | |||
3aa85c96b2 | |||
2f71d80c38 | |||
558a9f4cd0 | |||
9a6915a0ed | |||
d0feca0d57 | |||
36e9f0bcde | |||
a17fc1c76e | |||
84d8fb5339 | |||
2b9373e0fc | |||
2992d0db6b | |||
71b70712f8 | |||
10c7fc8e91 | |||
48971bb237 | |||
387b49a8b5 | |||
bc9bacb08f | |||
d44cf620c1 | |||
14cef8eb6c | |||
0bbe3e14c1 | |||
6df63d825a | |||
10e6436c34 | |||
aa3ee802d2 | |||
9a16b1cda7 | |||
ebbef901c1 | |||
1ef203ee07 | |||
ca645ed23d | |||
742ed50960 | |||
a60af4990a | |||
d2890ecbba | |||
36d8158414 | |||
642afd6f34 | |||
fad9c8f483 | |||
40a8fc50d9 | |||
21838afc0d | |||
8821c4edd7 | |||
a265dd28dd | |||
14bc8a1732 | |||
10dd18a42a | |||
691f009656 | |||
68f1af090e | |||
6412778b98 | |||
de12a2200e | |||
2600d6223c | |||
1ed1d8403d | |||
5e34d9e44d | |||
4f49c86d73 | |||
74309f8fa4 | |||
699c4301b4 | |||
c7c90a9fa3 | |||
e5d843b21f | |||
3ab943ab0b | |||
e8d2aeb3a6 | |||
28220ea8b4 | |||
9f47a29b43 | |||
46bb39332f | |||
e8bf83274f | |||
083bdad88f | |||
0e238ff2dd | |||
d0cbfaed44 | |||
791dc59ba2 | |||
457197f85b | |||
07ee54af3a | |||
865777b7ba | |||
7b38ec3f8f | |||
f8448d7d2f | |||
ba638c1533 | |||
130901d7f7 | |||
07c3fd8941 | |||
2d98bbf4d6 | |||
08acd9714f | |||
57c3abf2e1 | |||
2f12fd8ae7 | |||
69ab1c1b8f | |||
a2f4dc0b6c | |||
6d7ff7ea86 | |||
00d831e755 | |||
63d65a453c | |||
68e3bc932f | |||
6222998303 | |||
8d0678457e | |||
c7c669b8d4 | |||
e28cf3ebb5 | |||
4ea0256c56 | |||
bf52b65dd5 | |||
6de9b87f16 | |||
2b48adfbef | |||
7f944ad4a1 | |||
50045432fa | |||
cd4b700962 | |||
b98934693c | |||
e22fb7c6b7 | |||
dfbe5c5210 | |||
f3ed9a3452 | |||
57e35eeab1 | |||
e3e2af46a1 | |||
3a30b891be | |||
b69424983f | |||
37313183f5 | |||
86453b6873 | |||
c1d62bdbc2 | |||
bbe633ef2e | |||
201bfb922d | |||
9d1ebd38ce | |||
9dfcacf8a3 | |||
247b272986 | |||
072506c5d9 | |||
05bbc5d18f | |||
e51ca61bfe | |||
d3ad280731 | |||
85b043af37 | |||
0342594728 | |||
56e7484721 | |||
cd61a530cb | |||
f4c0e06b62 | |||
b4d748d87f | |||
107c07915e | |||
f493f005a9 | |||
fbafbd0d52 | |||
9215da61a3 | |||
61428a5c8b | |||
77906fb58b | |||
a79d021123 | |||
d85f5d88cd | |||
518d63c08d | |||
b254f0716b | |||
9e93a4cdce | |||
38f839fb60 | |||
09cee559eb | |||
f64af6675b | |||
9d71a08841 | |||
321cc62ca0 | |||
92bf5c3be2 | |||
43db1fed84 | |||
f81b76a975 | |||
81c16ec479 | |||
254da7e17b | |||
400739cd83 | |||
2f7655e1c1 | |||
c3a6943b7e | |||
fdc37c9f53 | |||
c73246d7c6 | |||
e03ae48ef6 | |||
cd1cfdd5db | |||
d87015836e | |||
71c01795f4 | |||
2291c89dbc | |||
1546304b4e | |||
a0e6efb409 | |||
bd18a6871c | |||
0f3f566d25 | |||
92451d1e28 | |||
a0c2ed38e6 | |||
649e5a2cab | |||
f2e51ef742 | |||
cf4c27a74c | |||
4cff9f99cb | |||
741264ec48 | |||
9ad1be40b2 | |||
910d0fa59e | |||
f54d5a68ff | |||
a359350d7e | |||
7bef6b4089 | |||
8011e78e21 | |||
8a6fcd92ae | |||
3e33313bf0 | |||
6138291a8d | |||
6addf5a3b2 | |||
2ead0201ab | |||
56ad2370dc | |||
3157ceb88b | |||
df2a2fe427 | |||
c55ea59c4f | |||
9cb28e037d | |||
90eeb380ef | |||
9472a5c5d4 | |||
d7884a9c8a | |||
3f10fbdf4d | |||
c5ccc0ab34 | |||
664bd473c3 | |||
8ef0926614 | |||
2298d1bfaa | |||
08857dd143 | |||
b26f7a5d2b | |||
4e997591dd | |||
fad3972554 | |||
755f844294 | |||
fd18da52a8 | |||
cc78c3c36e | |||
75009f6816 | |||
59f82cea27 | |||
0da8d282fe | |||
6b4bd5ea28 | |||
93ceef0163 | |||
eab0d656d3 | |||
c2d99603a8 | |||
f73b6b56a9 | |||
b65eca7dcf | |||
dec5826be8 | |||
b2393d4715 | |||
c86037e5d0 | |||
d7751fb300 | |||
9582ea2e0a | |||
d92b393f01 | |||
ea26899735 | |||
f8d807225f | |||
4c08609824 | |||
ccb11a4ecf | |||
7f8ce68182 | |||
edf936820a | |||
c6ab274dcf | |||
4d0c1811a3 | |||
ccb6f33b2f | |||
4484fd243e | |||
7f1cdae91a | |||
b763009821 | |||
f392c0c02b | |||
027086dd48 | |||
6eeca57694 | |||
cc9ff2a2b0 | |||
507753b3dc | |||
eaecb395cd | |||
6f5132633f | |||
1076289490 | |||
743f669b8c | |||
c12fc4bd57 | |||
9ab82904e6 | |||
45df0954f4 | |||
de685236a0 | |||
2aa8033a5f | |||
12b2fb6dfd | |||
aa5eb3988d | |||
5efeb6ca50 | |||
18eaebb7fc | |||
9ed3dd4f22 | |||
51ecf1b54b | |||
d1741c60dc | |||
f62c844aaf | |||
409baf0321 | |||
c3e37f7864 | |||
233a81c7d8 | |||
aca67b997a | |||
cddba3d35f | |||
14b0d1bd37 | |||
578162a266 | |||
ab776d7fc8 | |||
cd9f05b8e1 | |||
2bf978f845 | |||
b89212bcbd | |||
5498694729 | |||
7b5bf2969a | |||
e198c49a96 | |||
7f5811db9a | |||
5c3bb2293c | |||
59ac2061af | |||
905934cad2 | |||
e89805cd17 | |||
680ab2c189 | |||
10095e3ce5 | |||
a2b8e23eee | |||
0587c14af5 | |||
6a83e0ce6c | |||
72960aa963 | |||
5f4f047769 | |||
a880ba254b | |||
4d75c3d97a | |||
90511ed765 | |||
aa3b85511f | |||
5d90cbcc98 | |||
0525f99813 | |||
769019f2f5 | |||
dcaba0f0ee | |||
d33b6eec59 | |||
20aef83496 | |||
3cc4a1ea19 | |||
a41fefa906 | |||
c00bba3fcf | |||
63fab5899b | |||
357b6ef06e | |||
4fdf74fdbe |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
/keep
|
||||
result
|
||||
result-*
|
||||
/secrets/local.nix
|
||||
/working
|
||||
|
34
README.md
34
README.md
@@ -4,17 +4,35 @@ this is the top-level repo from which i configure/deploy all my NixOS machines:
|
||||
- desktop
|
||||
- laptop
|
||||
- server
|
||||
- mobile phone
|
||||
- mobile phone (Pinephone)
|
||||
|
||||
i enjoy a monorepo approach. this repo references [nixpkgs][nixpkgs], a couple 3rd party
|
||||
nix modules like [sops][sops], the sources for [uninsane.org][uninsane-org], and that's
|
||||
about it. custom derivations and modules (some of which i try to upstream) live
|
||||
directly here; even the sources for those packages is often kept here too.
|
||||
everything outside of <./hosts/> and <./secrets/> is intended for export, to be importable for use by 3rd parties.
|
||||
the only hard dependency for my exported pkgs/modules should be [nixpkgs][nixpkgs].
|
||||
building <./hosts/> will require [sops][sops].
|
||||
|
||||
you might specifically be interested in these files (elaborated further in #key-points-of-interest):
|
||||
- [`sxmo-utils-latest`](./pkgs/additional/sxmo-utils/default.nix)
|
||||
- [example SXMO deployment](./hosts/modules/gui/sxmo/default.nix)
|
||||
- [my implementation of impermanence](./modules/persist/default.nix)
|
||||
- my way of deploying dotfiles/configuring programs per-user:
|
||||
- <./modules/fs/default.nix>
|
||||
- <./modules/programs.nix>
|
||||
- <./modules/users.nix>
|
||||
|
||||
[nixpkgs]: https://github.com/NixOS/nixpkgs
|
||||
[sops]: https://github.com/Mic92/sops-nix
|
||||
[uninsane-org]: https://uninsane.org
|
||||
|
||||
## Using This Repo In Your Own Config
|
||||
|
||||
this should be a pretty "standard" flake. just reference it, and import either
|
||||
- `nixosModules.sane` (for the modules)
|
||||
- `overlays.pkgs` (for the packages)
|
||||
|
||||
or follow the instructions [here][NUR] to use it via the Nix User Repositories.
|
||||
|
||||
[NUR]: https://nur.nix-community.org/
|
||||
|
||||
## Layout
|
||||
- `doc/`
|
||||
- instructions for tasks i find myself doing semi-occasionally in this repo.
|
||||
@@ -90,12 +108,6 @@ them being factored out of my config, message me and we could work to make that
|
||||
|
||||
[home-manager]: https://github.com/nix-community/home-manager
|
||||
|
||||
## Using This Repo In Your Own Config
|
||||
|
||||
this should be a pretty "standard" flake. just reference it, and import either
|
||||
- `nixosModules.sane` (for the modules)
|
||||
- `overlays.pkgs` (for the packages)
|
||||
|
||||
## Mirrors
|
||||
|
||||
this repo exists in a few known locations:
|
||||
|
59
TODO.md
59
TODO.md
@@ -1,10 +1,7 @@
|
||||
## BUGS
|
||||
- mpv UI is sometimes blank for audio/podcasts?
|
||||
- i think it's when the audio file has no thumbnail?
|
||||
- why i need to manually restart `wireguard-wg-ovpns` on servo periodically
|
||||
- else DNS fails
|
||||
- fix epiphany URL bar input on moby
|
||||
- sxmo: wvkbd: missing font for icons on the 3rd page
|
||||
|
||||
## REFACTORING:
|
||||
|
||||
@@ -51,37 +48,57 @@
|
||||
- integrate `nix check` into Gitea actions?
|
||||
|
||||
### user experience
|
||||
- moby: sxmo: fix youtube scripts (package youtube-cli)
|
||||
#### moby
|
||||
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
|
||||
- install apps:
|
||||
- display QR codes for WiFi endpoints: <https://linuxphoneapps.org/apps/noappid.wisperwind.wifi2qr/>
|
||||
- shopping list: <https://linuxphoneapps.org/apps/ro.hume.cosmin.shoppinglist/>
|
||||
- offline Wikipedia
|
||||
- SwayNC:
|
||||
- don't show MPRIS if no players detected
|
||||
- this is a problem of playerctld, i guess
|
||||
- add option to change audio output
|
||||
- fix colors (red alert) to match overall theme
|
||||
- moby: tune GPS
|
||||
- run only geoclue, and not gpsd, to save power?
|
||||
- tune QGPS setting in eg25-control, for less jitter?
|
||||
- direct mepo to prefer gpsd, with fallback to geoclue, for better accuracy?
|
||||
- configure geoclue to do some smoothing?
|
||||
- manually do smoothing, as some layer between mepo and geoclue/gpsd?
|
||||
- neovim: set up language server (lsp; rnix-lsp; nvim-lspconfig)
|
||||
- Helix: make copy-to-system clipboard be the default
|
||||
- firefox/librewolf: persist history
|
||||
- just not cookies or tabs
|
||||
- moby: show battery state on ssh login
|
||||
- moby: improve gPodder launch time
|
||||
- moby: theme GTK apps (i.e. non-adwaita styles)
|
||||
- especially, make the menubar collapsible
|
||||
- try Gradience tool specifically for theming adwaita? <https://linuxphoneapps.org/apps/com.github.gradienceteam.gradience/>
|
||||
- package Nix/NixOS docs for Zeal
|
||||
- install [doc-browser](https://github.com/qwfy/doc-browser)
|
||||
- this supports both dash (zeal) *and* the datasets from <https://devdocs.io> (which includes nix!)
|
||||
- install [devhelp](https://wiki.gnome.org/Apps/Devhelp) (gnome)
|
||||
- have xdg-open parse `<repo:...> URIs (or adjust them so that it _can_ parse)
|
||||
- sane-bt-search: show details like 5.1 vs stereo, h264 vs h265
|
||||
- uninsane.org: make URLs relative to allow local use (and as offline homepage)
|
||||
- email: fix so that local mail doesn't go to junk
|
||||
- git sendmail flow adds the DKIM signatures, but gets delivered locally w/o having the sig checked, so goes into Junk
|
||||
- could change junk filter from "no DKIM success" to explicit "DKIM failed"
|
||||
- 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)
|
||||
- combine multiple icon themes to get one which has the full icon set?
|
||||
- get adwaita-icon-theme to ship everything even when cross-compiled?
|
||||
- especially, make the menubar collapsible
|
||||
- try Gradience tool specifically for theming adwaita? <https://linuxphoneapps.org/apps/com.github.gradienceteam.gradience/>
|
||||
- phog: remove the gnome-shell runtime dependency to save hella closure size
|
||||
|
||||
#### non-moby
|
||||
- neovim: set up language server (lsp; rnix-lsp; nvim-lspconfig)
|
||||
- Helix: make copy-to-system clipboard be the default
|
||||
- firefox/librewolf: persist history
|
||||
- just not cookies or tabs
|
||||
- package Nix/NixOS docs for Zeal
|
||||
- install [doc-browser](https://github.com/qwfy/doc-browser)
|
||||
- this supports both dash (zeal) *and* the datasets from <https://devdocs.io> (which includes nix!)
|
||||
- install [devhelp](https://wiki.gnome.org/Apps/Devhelp) (gnome)
|
||||
- have xdg-open parse `<repo:...> URIs (or adjust them so that it _can_ parse)
|
||||
- sane-bt-search: show details like 5.1 vs stereo, h264 vs h265
|
||||
- maybe just color these "keywords" in all search results?
|
||||
- uninsane.org: make URLs relative to allow local use (and as offline homepage)
|
||||
- email: fix so that local mail doesn't go to junk
|
||||
- git sendmail flow adds the DKIM signatures, but gets delivered locally w/o having the sig checked, so goes into Junk
|
||||
- could change junk filter from "no DKIM success" to explicit "DKIM failed"
|
||||
|
||||
### perf
|
||||
- add `pkgs.impure-cached.<foo>` package set to build things with ccache enabled
|
||||
|
9
default.nix
Normal file
9
default.nix
Normal file
@@ -0,0 +1,9 @@
|
||||
# limited, non-flake interface to this repo.
|
||||
# this file exposes the same view into `pkgs` which the flake would see when evaluated.
|
||||
#
|
||||
# the primary purpose of this file is so i can run `updateScript`s which expect
|
||||
# the root to be `default.nix`
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
pkgs.appendOverlays [
|
||||
(import ./overlays/all.nix)
|
||||
]
|
66
flake.lock
generated
66
flake.lock
generated
@@ -21,11 +21,11 @@
|
||||
"mobile-nixos": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1690059310,
|
||||
"narHash": "sha256-4zcoDp8wwZVfGSzXltC5x+eH4kDWC/eJpyQNgr7shAA=",
|
||||
"lastModified": 1696124168,
|
||||
"narHash": "sha256-EzGHYAR7rozQQLZEHbKEcb5VpUFGoxwEsM0OWfW4wqU=",
|
||||
"owner": "nixos",
|
||||
"repo": "mobile-nixos",
|
||||
"rev": "56fc9f9619f305f0865354975a98d22410eed127",
|
||||
"rev": "7cee346c3f8e73b25b1cfbf7a086a7652c11e0f3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -34,46 +34,13 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-serve": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1687251388,
|
||||
"narHash": "sha256-E9cVlgeCvzPbA/G3mCDCzz8TdRwXyGYzIjmwcvIfghg=",
|
||||
"owner": "edolstra",
|
||||
"repo": "nix-serve",
|
||||
"rev": "d6df5bd8584f37e22cff627db2fc4058a4aab5ee",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "nix-serve",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1606086654,
|
||||
"narHash": "sha256-VFl+3eGIMqNp7cyOMJ6TjM/+UcsLKtodKoYexrlTJMI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "19db3e5ea2777daa874563b5986288151f502e27",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-20.09",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1693097136,
|
||||
"narHash": "sha256-fBZSMdBaoZ0INFbyZ5s0DOF7zDNcLsLxgkwdDh3l9Pc=",
|
||||
"lastModified": 1696123266,
|
||||
"narHash": "sha256-S6MZEneQeE4M/E/C8SMnr7B7oBnjH/hbm96Kak5hAAI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9117c4e9dc117a6cd0319cca40f2349ed333669d",
|
||||
"rev": "dbe90e63a36762f1fbde546e26a84af774a32455",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -85,11 +52,11 @@
|
||||
},
|
||||
"nixpkgs-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1693377291,
|
||||
"narHash": "sha256-vYGY9bnqEeIncNarDZYhm6KdLKgXMS+HA2mTRaWEc80=",
|
||||
"lastModified": 1696375444,
|
||||
"narHash": "sha256-Sv0ICt/pXfpnFhTGYTsX6lUr1SljnuXWejYTI2ZqHa4=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e7f38be3775bab9659575f192ece011c033655f0",
|
||||
"rev": "81e8f48ebdecf07aab321182011b067aafc78896",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -102,7 +69,6 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"mobile-nixos": "mobile-nixos",
|
||||
"nix-serve": "nix-serve",
|
||||
"nixpkgs-unpatched": "nixpkgs-unpatched",
|
||||
"sops-nix": "sops-nix",
|
||||
"uninsane-dot-org": "uninsane-dot-org"
|
||||
@@ -116,11 +82,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1693404499,
|
||||
"narHash": "sha256-cx/7yvM/AP+o/3wPJmA9W9F+WHemJk5t+Xcr+Qwkqhg=",
|
||||
"lastModified": 1696320910,
|
||||
"narHash": "sha256-fbuEc6wylH+0VxG48lhPBK+SQJHfo2lusUwWHZNipIM=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "d9c5dc41c4b1f74c77f0dbffd0f3a4ebde447b7a",
|
||||
"rev": "746c7fa1a64c1671a4bf287737c27fdc7101c4c2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -152,11 +118,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1691106178,
|
||||
"narHash": "sha256-3mZ9gTvMpbZA9ea9ovoQpn2wKuQY0QZ7MDdEjArYdAQ=",
|
||||
"lastModified": 1696306988,
|
||||
"narHash": "sha256-I/OyJxIxu0n5h1eFqwVw0C6wTN3ewBXp2lGAdo1ur70=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "f4d91aa201b6e49af690f250d4786bd1d8b4dcfd",
|
||||
"revCount": 205,
|
||||
"rev": "1f588493031168d92a1e60705f26aaf4b2cdc07e",
|
||||
"revCount": 208,
|
||||
"type": "git",
|
||||
"url": "https://git.uninsane.org/colin/uninsane"
|
||||
},
|
||||
|
150
flake.nix
150
flake.nix
@@ -4,6 +4,8 @@
|
||||
# - this is marginally the case with schemes like `github:nixos/nixpkgs`.
|
||||
# - given the *existing* `git+https://` scheme, i propose expressing github URLs similarly:
|
||||
# - `github+https://github.com/nixos/nixpkgs/tree/nixos-22.11`
|
||||
# - this would allow for the same optimizations as today's `github:nixos/nixpkgs`, but without obscuring the source.
|
||||
# a code reader could view the source being referenced simply by clicking the https:// portion of that URI.
|
||||
# - need some way to apply local patches to inputs.
|
||||
#
|
||||
#
|
||||
@@ -46,24 +48,23 @@
|
||||
|
||||
mobile-nixos = {
|
||||
# <https://github.com/nixos/mobile-nixos>
|
||||
# only used for building disk images, not relevant after deployment
|
||||
url = "github:nixos/mobile-nixos";
|
||||
flake = false;
|
||||
};
|
||||
sops-nix = {
|
||||
# <https://github.com/Mic92/sops-nix>
|
||||
# used to distribute secrets to my hosts
|
||||
url = "github:Mic92/sops-nix";
|
||||
# inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.nixpkgs.follows = "nixpkgs-unpatched";
|
||||
};
|
||||
uninsane-dot-org = {
|
||||
# provides the package to deploy <https://uninsane.org>, used only when building the servo host
|
||||
url = "git+https://git.uninsane.org/colin/uninsane";
|
||||
# inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.nixpkgs.follows = "nixpkgs-unpatched";
|
||||
};
|
||||
nix-serve = {
|
||||
# <https://github.com/edolstra/nix-serve>
|
||||
url = "github:edolstra/nix-serve";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
@@ -72,21 +73,37 @@
|
||||
mobile-nixos,
|
||||
sops-nix,
|
||||
uninsane-dot-org,
|
||||
nix-serve,
|
||||
...
|
||||
}@inputs:
|
||||
let
|
||||
inherit (builtins) attrNames elem listToAttrs map mapAttrs;
|
||||
# redefine some nixpkgs `lib` functions to avoid the infinite recursion
|
||||
# of if we tried to use patched `nixpkgs.lib` as part of the patching process.
|
||||
mapAttrs' = f: set:
|
||||
listToAttrs (map (attr: f attr set.${attr}) (attrNames set));
|
||||
optionalAttrs = cond: attrs: if cond then attrs else {};
|
||||
# mapAttrs but without the `name` argument
|
||||
mapAttrValues = f: mapAttrs (_: f);
|
||||
|
||||
# rather than apply our nixpkgs patches as a flake input, do that here instead.
|
||||
# this (temporarily?) resolves the bad UX wherein a subflake residing in the same git
|
||||
# repo as the main flake causes the main flake to have an unstable hash.
|
||||
nixpkgs = (import ./nixpatches/flake.nix).outputs {
|
||||
self = nixpkgs;
|
||||
nixpkgs = nixpkgs-unpatched;
|
||||
} // {
|
||||
# provide values that nixpkgs ordinarily sources from the flake.lock file,
|
||||
# inaccessible to it here because of the import-from-derivation.
|
||||
# rev and shortRev seem to not always exist (e.g. if the working tree is dirty),
|
||||
# so those are made conditional.
|
||||
#
|
||||
# these values impact the name of a produced nixos system. having date/rev in the
|
||||
# `readlink /run/current-system` store path helps debuggability.
|
||||
inherit (self) lastModifiedDate lastModified;
|
||||
} // optionalAttrs (self ? rev) {
|
||||
inherit (self) rev;
|
||||
} // optionalAttrs (self ? shortRev) {
|
||||
inherit (self) shortRev;
|
||||
};
|
||||
|
||||
nixpkgsCompiledBy = system: nixpkgs.legacyPackages."${system}";
|
||||
@@ -184,17 +201,9 @@
|
||||
let
|
||||
mobile = (import "${mobile-nixos}/overlay/overlay.nix");
|
||||
uninsane = uninsane-dot-org.overlay;
|
||||
# nix-serve' = nix-serve.overlay;
|
||||
nix-serve' = next: prev: {
|
||||
# XXX(2023/03/02): upstream isn't compatible with modern `nix`. probably the perl bindings.
|
||||
# - we use the package built against `nixpkgs` specified in its flake rather than use its overlay,
|
||||
# to get around this.
|
||||
inherit (nix-serve.packages."${next.system}") nix-serve;
|
||||
};
|
||||
in
|
||||
(mobile final prev)
|
||||
// (uninsane final prev)
|
||||
// (nix-serve' final prev)
|
||||
;
|
||||
};
|
||||
|
||||
@@ -244,6 +253,7 @@
|
||||
apps."x86_64-linux" =
|
||||
let
|
||||
pkgs = self.legacyPackages."x86_64-linux";
|
||||
sanePkgs = import ./pkgs { inherit pkgs; };
|
||||
deployScript = host: addr: action: pkgs.writeShellScript "deploy-${host}" ''
|
||||
nix build '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} $@
|
||||
sudo nix sign-paths -r -k /run/secrets/nix_serve_privkey $(readlink ./result-${host})
|
||||
@@ -253,6 +263,59 @@
|
||||
# let the user handle that edge case by re-running this whole command
|
||||
nixos-rebuild --flake '.#${host}' ${action} --target-host colin@${addr} --use-remote-sudo $@
|
||||
'';
|
||||
|
||||
# pkg updating.
|
||||
# a cleaner alternative lives here: <https://discourse.nixos.org/t/how-can-i-run-the-updatescript-of-personal-packages/25274/2>
|
||||
mkUpdater = attrPath: {
|
||||
type = "app";
|
||||
program = let
|
||||
pkg = pkgs.lib.getAttrFromPath attrPath sanePkgs;
|
||||
strAttrPath = pkgs.lib.concatStringsSep "." attrPath;
|
||||
commandArgv = pkg.updateScript.command or pkg.updateScript;
|
||||
command = pkgs.lib.escapeShellArgs commandArgv;
|
||||
in builtins.toString (pkgs.writeShellScript "update-${strAttrPath}" ''
|
||||
export UPDATE_NIX_NAME=${pkg.name}
|
||||
export UPDATE_NIX_PNAME=${pkg.pname}
|
||||
export UPDATE_NIX_OLD_VERSION=${pkg.version}
|
||||
export UPDATE_NIX_ATTR_PATH=${strAttrPath}
|
||||
${command}
|
||||
'');
|
||||
};
|
||||
mkUpdatersNoAliases = opts: basePath: pkgs.lib.concatMapAttrs
|
||||
(name: pkg:
|
||||
if pkg.recurseForDerivations or false then {
|
||||
"${name}" = mkUpdaters opts (basePath ++ [ name ]);
|
||||
} else if pkg.updateScript or null != null then {
|
||||
"${name}" = mkUpdater (basePath ++ [ name ]);
|
||||
} else {}
|
||||
)
|
||||
(pkgs.lib.getAttrFromPath basePath sanePkgs);
|
||||
mkUpdaters = { ignore ? [] }@opts: basePath:
|
||||
let
|
||||
updaters = mkUpdatersNoAliases opts basePath;
|
||||
invokeUpdater = name: pkg:
|
||||
let
|
||||
fullPath = basePath ++ [ name ];
|
||||
doUpdateByDefault = !builtins.elem fullPath ignore;
|
||||
|
||||
# in case `name` has a `.` in it, we have to quote it
|
||||
escapedPath = builtins.map (p: ''"${p}"'') fullPath;
|
||||
updatePath = builtins.concatStringsSep "." ([ "update" "pkgs" ] ++ escapedPath);
|
||||
in pkgs.lib.optionalString doUpdateByDefault (
|
||||
pkgs.lib.escapeShellArgs [
|
||||
"nix" "run" ".#${updatePath}"
|
||||
]
|
||||
);
|
||||
in {
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript
|
||||
(builtins.concatStringsSep "-" (["update"] ++ basePath))
|
||||
(builtins.concatStringsSep
|
||||
"\n"
|
||||
(pkgs.lib.mapAttrsToList invokeUpdater updaters)
|
||||
)
|
||||
);
|
||||
} // updaters;
|
||||
in {
|
||||
help = {
|
||||
type = "app";
|
||||
@@ -261,24 +324,28 @@
|
||||
commands:
|
||||
- `nix run '.#help'`
|
||||
- show this message
|
||||
- `nix run '.#update-feeds'`
|
||||
- `nix run '.#update.pkgs'`
|
||||
- updates every package
|
||||
- `nix run '.#update.feeds'`
|
||||
- updates metadata for all feeds
|
||||
- `nix run '.#init-feed' <url>`
|
||||
- `nix run '.#deploy-{lappy,moby,moby-test,servo}' [nixos-rebuild args ...]`
|
||||
- `nix run '.#check-nur'`
|
||||
- `nix run '.#check'`
|
||||
- make sure all systems build; NUR evaluates
|
||||
'';
|
||||
in builtins.toString (pkgs.writeShellScript "nixos-config-help" ''
|
||||
cat ${helpMsg}
|
||||
echo ""
|
||||
echo "complete flake structure:"
|
||||
nix flake show --option allow-import-from-derivation true
|
||||
'');
|
||||
};
|
||||
update-feeds = {
|
||||
type = "app";
|
||||
program = "${pkgs.feeds.updateScript}";
|
||||
};
|
||||
update.pkgs = mkUpdaters { ignore = [ ["feeds"] ]; } [];
|
||||
update.feeds = mkUpdaters {} [ "feeds" ];
|
||||
|
||||
init-feed = {
|
||||
type = "app";
|
||||
program = "${pkgs.feeds.initFeedScript}";
|
||||
program = "${pkgs.feeds.init-feed}";
|
||||
};
|
||||
|
||||
deploy-lappy = {
|
||||
@@ -318,7 +385,20 @@
|
||||
'');
|
||||
};
|
||||
|
||||
check-nur = {
|
||||
check = {
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "check-all" ''
|
||||
nix run '.#check.nur'
|
||||
RC0=$?
|
||||
nix run '.#check.host-configs'
|
||||
RC1=$?
|
||||
echo "nur: $RC0"
|
||||
echo "host-configs: $RC1"
|
||||
exit $(($RC0 | $RC1))
|
||||
'');
|
||||
};
|
||||
|
||||
check.nur = {
|
||||
# `nix run '.#check-nur'`
|
||||
# validates that my repo can be included in the Nix User Repository
|
||||
type = "app";
|
||||
@@ -330,9 +410,35 @@
|
||||
--option allow-import-from-derivation true \
|
||||
--drv-path --show-trace \
|
||||
-I nixpkgs=$(nix-instantiate --find-file nixpkgs) \
|
||||
-I ../../
|
||||
-I ../../ \
|
||||
| tee # tee to prevent interactive mode
|
||||
'');
|
||||
};
|
||||
|
||||
check.host-configs = {
|
||||
type = "app";
|
||||
program = let
|
||||
checkHost = host: ''
|
||||
nix build '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} -j2 $@
|
||||
RC_${host}=$?
|
||||
'';
|
||||
in builtins.toString (pkgs.writeShellScript
|
||||
"check-host-configs"
|
||||
''
|
||||
${checkHost "desko"}
|
||||
${checkHost "lappy"}
|
||||
${checkHost "servo"}
|
||||
${checkHost "moby"}
|
||||
${checkHost "rescue"}
|
||||
echo "desko: $RC_desko"
|
||||
echo "lappy: $RC_lappy"
|
||||
echo "servo: $RC_servo"
|
||||
echo "moby: $RC_moby"
|
||||
echo "rescue: $RC_rescue"
|
||||
exit $(($RC_desko | $RC_lappy | $RC_servo | $RC_moby | $RC_rescue))
|
||||
''
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
templates = {
|
||||
|
@@ -3,13 +3,15 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.gui.sxmo = {
|
||||
greeter = "sway";
|
||||
greeter = "greetd-sway-gtkgreet";
|
||||
noidle = true; #< power button requires 1s hold, which makes it impractical to be dealing with.
|
||||
settings = {
|
||||
# XXX: make sure the user is part of the `input` group!
|
||||
SXMO_LISGD_INPUT_DEVICE = "/dev/input/by-id/usb-Wacom_Co._Ltd._Pen_and_multitouch_sensor-event-if00";
|
||||
# these identifiers are from `swaymsg -t get_inputs`
|
||||
SXMO_VOLUME_BUTTON = "1:1:AT_Translated_Set_2_keyboard";
|
||||
# SXMO_VOLUME_BUTTON = "none";
|
||||
# N.B.: thinkpad's power button requires a full second press to do anything
|
||||
SXMO_POWER_BUTTON = "0:1:Power_Button";
|
||||
# SXMO_POWER_BUTTON = "none";
|
||||
SXMO_DISABLE_LEDS = "1";
|
||||
@@ -27,8 +29,11 @@
|
||||
# - SXMO_SWAY_SCALE
|
||||
# see <repo:mil/sxmo-utils:scripts/deviceprofiles>
|
||||
# SXMO_DEVICE_NAME = "pine64,pinephone-1.2";
|
||||
# if sxmo doesn't know the device, it can't decide whether to use one_button or three_button mode
|
||||
# and so it just wouldn't handle any button inputs (sxmo_hook_inputhandler.sh not on path)
|
||||
SXMO_DEVICE_NAME = "three_button_touchscreen";
|
||||
};
|
||||
package = pkgs.sxmo-utils-latest.overrideAttrs (base: {
|
||||
package = (pkgs.sxmo-utils-latest.override { preferSystemd = true; }).overrideAttrs (base: {
|
||||
postPatch = (base.postPatch or "") + ''
|
||||
# after volume-button navigation mode, restore full keyboard functionality
|
||||
cp ${./xkb_mobile_normal_buttons} ./configs/xkb/xkb_mobile_normal_buttons
|
||||
|
@@ -23,6 +23,12 @@
|
||||
sane.zsh.showDeadlines = false; # unlikely to act on them when in shell
|
||||
sane.services.wg-home.enable = true;
|
||||
sane.services.wg-home.ip = config.sane.hosts.by-name."moby".wg-home.ip;
|
||||
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,
|
||||
# so set this more reliable default password should anything go wrong
|
||||
@@ -31,26 +37,53 @@
|
||||
|
||||
sops.secrets.colin-passwd.neededForUsers = true;
|
||||
|
||||
sane.user.persist.plaintext = [
|
||||
# TODO: make this just generally conditional upon pulse being enabled?
|
||||
".config/pulse" # persist pulseaudio volume
|
||||
];
|
||||
|
||||
sane.gui.sxmo.enable = true;
|
||||
sane.programs.guiApps.suggestedPrograms = [ "handheldGuiApps" ];
|
||||
# sane.programs.consoleUtils.enableFor.user.colin = false;
|
||||
# sane.programs.guiApps.enableFor.user.colin = false;
|
||||
sane.programs.blueberry.enableFor.user.colin = false; # bluetooth manager: doesn't cross compile!
|
||||
sane.programs.sequoia.enableFor.user.colin = false;
|
||||
sane.programs.tuiApps.enableFor.user.colin = false; # visidata, others, don't compile well
|
||||
# disabled for faster deploys
|
||||
sane.programs.soundconverter.enableFor.user.colin = false;
|
||||
sane.programs.eg25-control.enableFor.user.colin = true;
|
||||
|
||||
# sane.programs.firefox.mime.priority = 300; # prefer other browsers when possible
|
||||
sane.programs.ntfy-sh.config.autostart = true;
|
||||
sane.programs.dino.config.autostart = true;
|
||||
# sane.programs.calls.config.autostart = true;
|
||||
|
||||
sane.programs.firefox.mime.priority = 300; # prefer other browsers when possible
|
||||
# HACK/TODO: make `programs.P.env.VAR` behave according to `mime.priority`
|
||||
# sane.programs.firefox.env = lib.mkForce {};
|
||||
# sane.programs.epiphany.env.BROWSER = "epiphany";
|
||||
# sane.programs.firefox.enableFor.user.colin = false; # use epiphany instead
|
||||
sane.programs.firefox.env = lib.mkForce {};
|
||||
sane.programs.epiphany.env.BROWSER = "epiphany";
|
||||
sane.programs.firefox.enableFor.user.colin = false; # use epiphany instead
|
||||
|
||||
# note the .conf.d approach: using ~/.config/pipewire/pipewire.conf directly breaks all audio,
|
||||
# presumably because that deletes the defaults entirely whereas the .conf.d approach selectively overrides defaults
|
||||
sane.user.fs.".config/pipewire/pipewire.conf.d/10-fix-dino-mic-cutout.conf".symlink.text = ''
|
||||
# config docs: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-PipeWire#properties>
|
||||
# useful to run `pw-top` to see that these settings are actually having effect,
|
||||
# and `pw-metadata` to see if any settings conflict (e.g. max-quantum < min-quantum)
|
||||
#
|
||||
# restart pipewire after editing these files:
|
||||
# - `systemctl --user restart pipewire`
|
||||
# - pipewire users will likely stop outputting audio until they are also restarted
|
||||
#
|
||||
# there's seemingly two buffers for the mic (see: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#pipewire-buffering-explained>)
|
||||
# 1. Pipewire buffering out of the driver and into its own member.
|
||||
# 2. Pipewire buffering into Dino.
|
||||
# the latter is fixed at 10ms by Dino, difficult to override via runtime config.
|
||||
# the former defaults low (e.g. 512 samples)
|
||||
# this default configuration causes the mic to regularly drop out entirely for a couple seconds at a time during a call,
|
||||
# presumably because the system can't keep up (pw-top shows incrementing counter in ERR column).
|
||||
# `pw-metadata -n settings 0 clock.force-quantum 1024` reduces to about 1 error per second.
|
||||
# `pw-metadata -n settings 0 clock.force-quantum 2048` reduces to 1 error every < 10s.
|
||||
# pipewire default config includes `clock.power-of-two-quantum = true`
|
||||
context.properties = {
|
||||
default.clock.min-quantum = 2048
|
||||
default.clock.max-quantum = 8192
|
||||
}
|
||||
'';
|
||||
|
||||
# sane.programs.mpv.enableFor.user.colin = true;
|
||||
|
||||
@@ -73,12 +106,16 @@
|
||||
# `cat /proc/meminfo` to see CmaTotal/CmaFree if interested in tuning this.
|
||||
boot.kernelParams = [ "cma=512M" ];
|
||||
|
||||
# hardware.firmware makes the referenced files visible to the kernel, for whenever a driver explicitly asks for them.
|
||||
# these files are visible from userspace by following `/sys/module/firmware_class/parameters/path`
|
||||
#
|
||||
# mobile-nixos' /lib/firmware includes:
|
||||
# rtl_bt (bluetooth)
|
||||
# anx7688-fw.bin (USB-C -> HDMI bridge)
|
||||
# anx7688-fw.bin (USB-C chip: power negotiation, HDMI/dock)
|
||||
# ov5640_af.bin (camera module)
|
||||
# hardware.firmware = [ config.mobile.device.firmware ];
|
||||
hardware.firmware = [ pkgs.rtl8723cs-firmware ];
|
||||
# hardware.firmware = [ pkgs.rtl8723cs-firmware ];
|
||||
hardware.firmware = [ pkgs.linux-firmware-megous ];
|
||||
|
||||
system.stateVersion = "21.11";
|
||||
|
||||
@@ -97,15 +134,11 @@
|
||||
environment.variables.ALSA_CONFIG_UCM2 = "/run/current-system/sw/share/alsa/ucm2";
|
||||
environment.pathsToLink = [ "/share/alsa/ucm2" ];
|
||||
environment.systemPackages = [ pkgs.alsa-ucm-conf-sane ];
|
||||
systemd =
|
||||
let ucm-env = config.environment.variables.ALSA_CONFIG_UCM2;
|
||||
systemd = let
|
||||
ucm-env = config.environment.variables.ALSA_CONFIG_UCM2;
|
||||
in {
|
||||
# cribbed from <repo:nixos/mobile-nixos:modules/quirks/audio.nix>
|
||||
|
||||
# pulseaudio
|
||||
user.services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
|
||||
# pipewire
|
||||
user.services.pipewire.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
user.services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
@@ -114,6 +147,10 @@
|
||||
services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
services.wireplumber.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
|
||||
# pulseaudio
|
||||
# user.services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
# services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
|
||||
|
||||
|
||||
# TODO: move elsewhere...
|
||||
services.ModemManager.serviceConfig = {
|
||||
@@ -135,6 +172,4 @@
|
||||
# make Pinephone front LEDs writable by user.
|
||||
SUBSYSTEM=="leds", DEVPATH=="*/*:indicator", RUN+="${chmod} g+w /sys%p/brightness", RUN+="${chown} :video /sys%p/brightness"
|
||||
'';
|
||||
|
||||
hardware.opengl.driSupport = true;
|
||||
}
|
||||
|
@@ -7,15 +7,38 @@
|
||||
# - `screen /dev/ttyUSB2 115200`
|
||||
# - `AT+QGPSCFG="nmeasrc",1`
|
||||
# - `AT+QGPS=1`
|
||||
# this process is automated by my `eg25-control` program and services (`eg25-control-powered`, `eg25-control-gps`)
|
||||
# - see the `modules/` directory further up this repository.
|
||||
#
|
||||
# now, something like `gpsd` can directly read from /dev/ttyUSB1.
|
||||
# now, something like `gpsd` can directly read from /dev/ttyUSB1,
|
||||
# or geoclue can query the GPS directly through modem-manager
|
||||
#
|
||||
# initial GPS fix can take 15+ minutes.
|
||||
# meanwhile, services like eg25-manager can speed this up by uploading assisted GPS data to the modem.
|
||||
# meanwhile, services like eg25-manager or eg25-control-freshen-agps can speed this up by uploading assisted GPS data to the modem.
|
||||
#
|
||||
# geoclue somehow fits in here as a geospatial provider that leverages GPS and also other sources like radio towers
|
||||
# support/help:
|
||||
# - geoclue, gnome-maps
|
||||
# - irc: #gnome-maps on irc.gimp.org
|
||||
# - Matrix: #gnome-maps:gnome.org (unclear if bridged to IRC)
|
||||
#
|
||||
# programs to pair this with:
|
||||
# - `satellite-gtk`: <https://codeberg.org/tpikonen/satellite>
|
||||
# - shows/tracks which satellites the GPS is connected to; useful to understand fix characteristics
|
||||
# - `gnome-maps`: uses geoclue, has route planning
|
||||
# - `mepo`: uses gpsd, minimalist, flaky, and buttons are kinda hard to activate on mobile
|
||||
# - puremaps?
|
||||
# - osmin?
|
||||
#
|
||||
# known/outstanding bugs:
|
||||
# - `systemctl start eg25-control-gps` can the hang the whole system (2023/10/06)
|
||||
# - i think it's actually `eg25-control-powered` which does this (started by the gps)
|
||||
# - best guess is modem draws so much power at launch that other parts of the system see undervoltage
|
||||
# - workaround is to hard power-cycle the system. the modem may not bring up after reboot: leave unpowered for 60s and boot again.
|
||||
#
|
||||
# future work:
|
||||
# - integrate with [wigle](https://www.wigle.net/) for offline equivalent to Mozilla Location Services
|
||||
|
||||
{ lib, ... }:
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
# test gpsd with `gpspipe -w -n 10 2> /dev/null | grep -m 1 TPV | jq '.lat, .lon' | tr '\n' ' '`
|
||||
# ^ should return <lat> <long>
|
||||
@@ -23,20 +46,24 @@
|
||||
services.gpsd.devices = [ "/dev/ttyUSB1" ];
|
||||
|
||||
# test geoclue2 by building `geoclue2-with-demo-agent`
|
||||
# and running "${geoclue2-with-demo-agent}/libexec/geoclue-2/demos/where-am-i"
|
||||
# and running "${geoclue2-with-demo-agent}/libexec/geoclue-2.0/demos/where-am-i"
|
||||
# note that geoclue is dbus-activated, and auto-stops after 60s with no caller
|
||||
services.geoclue2.enable = true;
|
||||
services.geoclue2.appConfig.where-am-i = {
|
||||
# this is the default "agent", shipped by geoclue package: allow it to use location
|
||||
isAllowed = true;
|
||||
isSystem = false;
|
||||
# XXX: setting users != [] might be causing `where-am-i` to time out
|
||||
# users = [
|
||||
# # restrict to only one set of users. empty array (default) means "allow any user to access geolocation".
|
||||
# (builtins.toString config.users.users.colin.uid)
|
||||
# ];
|
||||
users = [
|
||||
# restrict to only one set of users. empty array (default) means "allow any user to access geolocation".
|
||||
(builtins.toString config.users.users.colin.uid)
|
||||
];
|
||||
};
|
||||
systemd.services.geoclue.after = lib.mkForce []; #< defaults to network-online, but not all my sources require network
|
||||
|
||||
users.users.geoclue.extraGroups = [
|
||||
"dialout" # TODO: figure out if dialout is required. that's for /dev/ttyUSB1, but geoclue probably doesn't read that?
|
||||
];
|
||||
|
||||
sane.services.eg25-control.enable = true;
|
||||
sane.programs.where-am-i.enableFor.user.colin = true;
|
||||
}
|
||||
|
@@ -9,13 +9,6 @@
|
||||
# - <https://itsfoss.com/content/images/2023/04/nixos-tutorials.png>
|
||||
|
||||
{ lib, pkgs, sane-lib, ... }:
|
||||
let
|
||||
# TODO: generate this from the .svg
|
||||
# bg = ./nixos-bg-02.png;
|
||||
bg = pkgs.runCommand "nixos-bg.png" { nativeBuildInputs = [ pkgs.inkscape ]; } ''
|
||||
inkscape ${./nixos-bg-02.svg} -o $out
|
||||
'';
|
||||
in
|
||||
{
|
||||
sane.programs.firefox.config = {
|
||||
# compromise impermanence for the sake of usability
|
||||
@@ -27,6 +20,9 @@ in
|
||||
# sidebery UX doesn't make sense on small screen
|
||||
addons.sidebery.enable = false;
|
||||
};
|
||||
sane.programs.swaynotificationcenter.config = {
|
||||
backlight = "backlight"; # /sys/class/backlight/*backlight*/brightness
|
||||
};
|
||||
|
||||
sane.gui.sxmo = {
|
||||
nogesture = true;
|
||||
@@ -111,7 +107,7 @@ in
|
||||
"--center --margin 45"
|
||||
"--no-spacing"
|
||||
# XXX: font size doesn't seem to take effect (would prefer larger)
|
||||
"--fn 'Sxmo 14' --line-height 22 --border 3"
|
||||
"--fn 'monospace 14' --line-height 22 --border 3"
|
||||
"--bdr '${accent0}'" # border
|
||||
"--scf '${accent2}' --scb '${accent0}'" # scrollbar
|
||||
"--tb '${accent0}' --tf '${fg0}'" # title
|
||||
@@ -123,6 +119,8 @@ in
|
||||
];
|
||||
DEFAULT_COUNTRY = "US";
|
||||
|
||||
SXMO_AUTOROTATE = "1"; # enable auto-rotation at launch. has no meaning in stock/upstream sxmo-utils
|
||||
|
||||
# BEMENU lines (wayland DMENU):
|
||||
# - camera is 9th entry
|
||||
# - flashlight is 10th entry
|
||||
@@ -134,7 +132,6 @@ in
|
||||
# - close is 16th entry
|
||||
SXMO_BEMENU_LANDSCAPE_LINES = "11"; # default 8
|
||||
SXMO_BEMENU_PORTRAIT_LINES = "16"; # default 16
|
||||
SXMO_BG_IMG = "${bg}";
|
||||
SXMO_LOCK_IDLE_TIME = "15"; # how long between screenoff -> lock -> back to screenoff (default: 8)
|
||||
# gravity: how far to tilt the device before the screen rotates
|
||||
# for a given setting, normal <-> invert requires more movement then left <-> right
|
||||
@@ -167,13 +164,5 @@ in
|
||||
WVKBD_LANDSCAPE_LAYERS = "landscape,special,emoji";
|
||||
WVKBD_LAYERS = "full,special,emoji";
|
||||
};
|
||||
package = pkgs.sxmo-utils-latest.overrideAttrs (base: {
|
||||
postPatch = (base.postPatch or "") + ''
|
||||
cat <<EOF >> ./configs/default_hooks/sxmo_hook_start.sh
|
||||
# rotate UI based on physical display angle by default
|
||||
sxmo_daemons.sh start autorotate sxmo_autorotate.sh
|
||||
EOF
|
||||
'';
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@@ -24,7 +24,9 @@
|
||||
];
|
||||
sane.services.dyn-dns.enable = true;
|
||||
sane.services.wg-home.enable = true;
|
||||
sane.services.wg-home.enableWan = true;
|
||||
sane.services.wg-home.visibleToWan = true;
|
||||
sane.services.wg-home.forwardToWan = true;
|
||||
sane.services.wg-home.routeThroughServo = false;
|
||||
sane.services.wg-home.ip = config.sane.hosts.by-name."servo".wg-home.ip;
|
||||
sane.nixcache.substituters.servo = false;
|
||||
sane.nixcache.substituters.desko = false;
|
||||
|
@@ -40,6 +40,34 @@
|
||||
# TODO: this is overly broad; only need media and share directories to be persisted
|
||||
{ user = "colin"; group = "users"; path = "/var/lib/uninsane"; }
|
||||
];
|
||||
# force some problematic directories to always get correct permissions:
|
||||
sane.fs."/var/lib/uninsane/media".dir.acl = {
|
||||
user = "colin"; group = "media"; mode = "0775";
|
||||
};
|
||||
sane.fs."/var/lib/uninsane/media/archive".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/archive/README.md".file.text = ''
|
||||
this directory is for media i wish to remove from my library,
|
||||
but keep for a short time in case i reverse my decision.
|
||||
treat it like a system trash can.
|
||||
'';
|
||||
sane.fs."/var/lib/uninsane/media/Books".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Books/Audiobooks".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Books/Books".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Books/Visual".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/collections".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/datasets".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/freeleech".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Music".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Pictures".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Videos".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Videos/Film".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Videos/Shows".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Videos/Talks".dir = {};
|
||||
sane.fs."/var/lib/uninsane/datasets/README.md".file.text = ''
|
||||
this directory may seem redundant with ../media/datasets. it isn't.
|
||||
this directory exists on SSD, allowing for speedy access to specific datasets when necessary.
|
||||
the contents should be a subset of what's in ../media/datasets.
|
||||
'';
|
||||
# make sure large media is stored to the HDD
|
||||
sane.persist.sys.ext = [
|
||||
{
|
||||
|
@@ -18,8 +18,9 @@
|
||||
./lemmy.nix
|
||||
./matrix
|
||||
./navidrome.nix
|
||||
./nixserve.nix
|
||||
./nginx.nix
|
||||
./nixserve.nix
|
||||
./ntfy.nix
|
||||
./pict-rs.nix
|
||||
./pleroma.nix
|
||||
./postgres.nix
|
||||
|
@@ -14,9 +14,25 @@
|
||||
#
|
||||
# compliance tests:
|
||||
# - <https://compliance.conversations.im/server/uninsane.org/#xep0352>
|
||||
#
|
||||
# administration:
|
||||
# - `sudo -u ejabberd ejabberdctl help`
|
||||
#
|
||||
# federation/support matrix:
|
||||
# - avatars
|
||||
# - nixnet.services + dino: works in MUCs but not DMs (as of 2023 H1)
|
||||
# - movim.eu + dino: works in DMs, MUCs untested (as of 2023/08/29)
|
||||
# - calls
|
||||
# - local + dino: audio, video, works in DMs (as of 2023/08/29)
|
||||
# - movim.eu + dino: audio, video, works in DMs, no matter which side initiates (as of 2023/08/30)
|
||||
# - +native-cell-number@cheogram.com + dino: audio works in DMs, no matter which side initiates (as of 2023/09/01)
|
||||
# - can receive calls even if sender isn't in my roster
|
||||
# - this is presumably using JMP.chat's SIP servers, which then convert it to XMPP call
|
||||
#
|
||||
# bugs:
|
||||
# - 2023/09/01: will randomly stop federating. `systemctl restart ejabberd` fixes, but takes 10 minutes.
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# XXX: avatar support works in MUCs but not DMs
|
||||
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)
|
||||
@@ -163,284 +179,285 @@ in
|
||||
services.ejabberd.enable = true;
|
||||
services.ejabberd.configFile = "/var/lib/ejabberd/ejabberd.yaml";
|
||||
systemd.services.ejabberd.preStart = let
|
||||
config-in = pkgs.writeTextFile {
|
||||
name = "ejabberd.yaml.in";
|
||||
text = ''
|
||||
hosts:
|
||||
- uninsane.org
|
||||
|
||||
config-in = pkgs.writeText "ejabberd.yaml.in" (lib.generators.toYAML {} {
|
||||
hosts = [ "uninsane.org" ];
|
||||
# none | emergency | alert | critical | error | warning | notice | info | debug
|
||||
loglevel: debug
|
||||
# loglevel: info
|
||||
# loglevel: notice
|
||||
loglevel = "debug";
|
||||
acme.auto = false;
|
||||
certfiles = [ "/var/lib/acme/uninsane.org/full.pem" ];
|
||||
# ca_file = "${pkgs.cacert.unbundled}/etc/ssl/certs/";
|
||||
# ca_file = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||
|
||||
acme:
|
||||
auto: false
|
||||
certfiles:
|
||||
- /var/lib/acme/uninsane.org/full.pem
|
||||
# ca_file: ${pkgs.cacert.unbundled}/etc/ssl/certs/
|
||||
# ca_file: ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
|
||||
pam_userinfotype = "jid";
|
||||
acl = {
|
||||
admin.user = [ "colin@uninsane.org" ];
|
||||
local.user_regexp = "";
|
||||
loopback.ip = [ "127.0.0.0/8" "::1/128" ];
|
||||
};
|
||||
|
||||
pam_userinfotype: jid
|
||||
|
||||
acl:
|
||||
admin:
|
||||
user:
|
||||
- "colin@uninsane.org"
|
||||
local:
|
||||
user_regexp: ""
|
||||
loopback:
|
||||
ip:
|
||||
- 127.0.0.0/8
|
||||
- ::1/128
|
||||
|
||||
access_rules:
|
||||
local:
|
||||
allow: local
|
||||
c2s_access:
|
||||
allow: all
|
||||
announce:
|
||||
allow: admin
|
||||
configure:
|
||||
allow: admin
|
||||
muc_create:
|
||||
allow: local
|
||||
pubsub_createnode_access:
|
||||
allow: all
|
||||
trusted_network:
|
||||
allow: loopback
|
||||
access_rules = {
|
||||
local.allow = "local";
|
||||
c2s_access.allow = "all";
|
||||
announce.allow = "admin";
|
||||
configure.allow = "admin";
|
||||
muc_create.allow = "local";
|
||||
pubsub_createnode_access.allow = "all";
|
||||
trusted_network.allow = "loopback";
|
||||
};
|
||||
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/basic/#shaper-rules>
|
||||
shaper_rules:
|
||||
shaper_rules = {
|
||||
# setting this to above 1 may break outgoing messages
|
||||
# - maybe some servers rate limit? or just don't understand simultaneous connections?
|
||||
max_s2s_connections: 1
|
||||
max_user_sessions: 10
|
||||
max_user_offline_messages: 5000
|
||||
c2s_shaper:
|
||||
fast: all
|
||||
s2s_shaper:
|
||||
med: all
|
||||
max_s2s_connections = 1;
|
||||
max_user_sessions = 10;
|
||||
max_user_offline_messages = 5000;
|
||||
c2s_shaper.fast = "all";
|
||||
s2s_shaper.med = "all";
|
||||
};
|
||||
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/basic/#shapers>
|
||||
# this limits the bytes/sec.
|
||||
# for example, burst: 3_000_000 and rate: 100_000 means:
|
||||
# - each client has a BW budget that accumulates 100kB/sec and is capped at 3 MB
|
||||
shaper:
|
||||
fast: 1000000
|
||||
med: 500000
|
||||
# fast:
|
||||
# - rate: 1000000
|
||||
# - burst_size: 10000000
|
||||
# med:
|
||||
# - rate: 500000
|
||||
# - burst_size: 5000000
|
||||
shaper.fast = 1000000;
|
||||
shaper.med = 500000;
|
||||
# shaper.fast.rate = 1000000;
|
||||
# shaper.fast.burst_size = 10000000;
|
||||
# shaper.med.rate = 500000;
|
||||
# shaper.med.burst_size = 5000000;
|
||||
|
||||
# see: <https://docs.ejabberd.im/admin/configuration/listen/>
|
||||
# s2s_use_starttls: true
|
||||
s2s_use_starttls: optional
|
||||
# s2s_use_starttls = true;
|
||||
s2s_use_starttls = "optional";
|
||||
# lessens 504: remote-server-timeout errors
|
||||
# see: <https://github.com/processone/ejabberd/issues/3105#issuecomment-562182967>
|
||||
negotiation_timeout: 60
|
||||
negotiation_timeout = 60;
|
||||
|
||||
listen:
|
||||
-
|
||||
port: 5222
|
||||
module: ejabberd_c2s
|
||||
shaper: c2s_shaper
|
||||
starttls: true
|
||||
access: c2s_access
|
||||
-
|
||||
port: 5223
|
||||
module: ejabberd_c2s
|
||||
shaper: c2s_shaper
|
||||
tls: true
|
||||
access: c2s_access
|
||||
-
|
||||
port: 5269
|
||||
module: ejabberd_s2s_in
|
||||
shaper: s2s_shaper
|
||||
-
|
||||
port: 5270
|
||||
module: ejabberd_s2s_in
|
||||
shaper: s2s_shaper
|
||||
tls: true
|
||||
-
|
||||
port: 5443
|
||||
module: ejabberd_http
|
||||
tls: true
|
||||
request_handlers:
|
||||
/admin: ejabberd_web_admin # TODO: ensure this actually works
|
||||
/api: mod_http_api # ejabberd API endpoint (to control server)
|
||||
/bosh: mod_bosh
|
||||
/upload: mod_http_upload
|
||||
/ws: ejabberd_http_ws
|
||||
# /.well-known/host-meta: mod_host_meta
|
||||
# /.well-known/host-meta.json: mod_host_meta
|
||||
-
|
||||
listen = [
|
||||
{
|
||||
port = 5222;
|
||||
module = "ejabberd_c2s";
|
||||
shaper = "c2s_shaper";
|
||||
starttls = true;
|
||||
access = "c2s_access";
|
||||
}
|
||||
{
|
||||
port = 5223;
|
||||
module = "ejabberd_c2s";
|
||||
shaper = "c2s_shaper";
|
||||
tls = true;
|
||||
access = "c2s_access";
|
||||
}
|
||||
{
|
||||
port = 5269;
|
||||
module = "ejabberd_s2s_in";
|
||||
shaper = "s2s_shaper";
|
||||
}
|
||||
{
|
||||
port = 5270;
|
||||
module = "ejabberd_s2s_in";
|
||||
shaper = "s2s_shaper";
|
||||
tls = true;
|
||||
}
|
||||
{
|
||||
port = 5443;
|
||||
module = "ejabberd_http";
|
||||
tls = true;
|
||||
request_handlers = {
|
||||
"/admin" = "ejabberd_web_admin"; # TODO: ensure this actually works
|
||||
"/api" = "mod_http_api"; # ejabberd API endpoint (to control server)
|
||||
"/bosh" = "mod_bosh";
|
||||
"/upload" = "mod_http_upload";
|
||||
"/ws" = "ejabberd_http_ws";
|
||||
# "/.well-known/host-meta" = "mod_host_meta";
|
||||
# "/.well-known/host-meta.json" = "mod_host_meta";
|
||||
};
|
||||
}
|
||||
{
|
||||
# STUN+TURN TCP
|
||||
# note that the full port range should be forwarded ("not NAT'd")
|
||||
# `use_turn=true` enables both TURN *and* STUN
|
||||
port: 3478
|
||||
module: ejabberd_stun
|
||||
transport: tcp
|
||||
use_turn: true
|
||||
turn_min_port: ${builtins.toString turnPortLow}
|
||||
turn_max_port: ${builtins.toString turnPortHigh}
|
||||
turn_ipv4_address: %ANATIVE%
|
||||
-
|
||||
port = 3478;
|
||||
module = "ejabberd_stun";
|
||||
transport = "tcp";
|
||||
use_turn = true;
|
||||
turn_min_port = turnPortLow;
|
||||
turn_max_port = turnPortHigh;
|
||||
turn_ipv4_address = "%ANATIVE%";
|
||||
}
|
||||
{
|
||||
# STUN+TURN UDP
|
||||
port: 3478
|
||||
module: ejabberd_stun
|
||||
transport: udp
|
||||
use_turn: true
|
||||
turn_min_port: ${builtins.toString turnPortLow}
|
||||
turn_max_port: ${builtins.toString turnPortHigh}
|
||||
turn_ipv4_address: %ANATIVE%
|
||||
-
|
||||
port = 3478;
|
||||
module = "ejabberd_stun";
|
||||
transport = "udp";
|
||||
use_turn = true;
|
||||
turn_min_port = turnPortLow;
|
||||
turn_max_port = turnPortHigh;
|
||||
turn_ipv4_address = "%ANATIVE%";
|
||||
}
|
||||
{
|
||||
# STUN+TURN TLS over TCP
|
||||
port: 5349
|
||||
module: ejabberd_stun
|
||||
transport: tcp
|
||||
tls: true
|
||||
certfile: /var/lib/acme/uninsane.org/full.pem
|
||||
use_turn: true
|
||||
turn_min_port: ${builtins.toString turnPortLow}
|
||||
turn_max_port: ${builtins.toString turnPortHigh}
|
||||
turn_ipv4_address: %ANATIVE%
|
||||
port = 5349;
|
||||
module = "ejabberd_stun";
|
||||
transport = "tcp";
|
||||
tls = true;
|
||||
certfile = "/var/lib/acme/uninsane.org/full.pem";
|
||||
use_turn = true;
|
||||
turn_min_port = turnPortLow;
|
||||
turn_max_port = turnPortHigh;
|
||||
turn_ipv4_address = "%ANATIVE%";
|
||||
}
|
||||
];
|
||||
|
||||
# TODO: enable mod_fail2ban
|
||||
# TODO(low): look into mod_http_fileserver for serving macros?
|
||||
modules:
|
||||
# mod_adhoc: {}
|
||||
# mod_announce:
|
||||
# access: admin
|
||||
modules = {
|
||||
# mod_adhoc = {};
|
||||
# mod_announce = {
|
||||
# access = "admin";
|
||||
# };
|
||||
# allows users to set avatars in vCard
|
||||
# - <https://docs.ejabberd.im/admin/configuration/modules/#mod-avatar>
|
||||
mod_avatar: {}
|
||||
mod_caps: {} # for mod_pubsub
|
||||
mod_carboncopy: {} # allows multiple clients to receive a user's message
|
||||
mod_avatar = {};
|
||||
mod_caps = {}; # for mod_pubsub
|
||||
mod_carboncopy = {}; # allows multiple clients to receive a user's message
|
||||
# queues messages when recipient is offline, including PEP and presence messages.
|
||||
# compliance test suggests this be enabled
|
||||
mod_client_state: {}
|
||||
mod_client_state = {};
|
||||
|
||||
# mod_conversejs: TODO: enable once on 21.12
|
||||
# allows clients like Dino to discover where to upload files
|
||||
mod_disco:
|
||||
server_info:
|
||||
-
|
||||
modules: all
|
||||
name: abuse-addresses
|
||||
urls:
|
||||
- "mailto:admin.xmpp@uninsane.org"
|
||||
- "xmpp:colin@uninsane.org"
|
||||
-
|
||||
modules: all
|
||||
name: admin-addresses
|
||||
urls:
|
||||
- "mailto:admin.xmpp@uninsane.org"
|
||||
- "xmpp:colin@uninsane.org"
|
||||
mod_http_upload:
|
||||
host: upload.xmpp.uninsane.org
|
||||
hosts:
|
||||
- upload.xmpp.uninsane.org
|
||||
put_url: "https://@HOST@:5443/upload"
|
||||
dir_mode: "0750"
|
||||
file_mode: "0750"
|
||||
rm_on_unregister: false
|
||||
mod_disco.server_info = [
|
||||
{
|
||||
modules = "all";
|
||||
name = "abuse-addresses";
|
||||
urls = [
|
||||
"mailto:admin.xmpp@uninsane.org"
|
||||
"xmpp:colin@uninsane.org"
|
||||
];
|
||||
}
|
||||
{
|
||||
modules = "all";
|
||||
name = "admin-addresses";
|
||||
urls = [
|
||||
"mailto:admin.xmpp@uninsane.org"
|
||||
"xmpp:colin@uninsane.org"
|
||||
];
|
||||
}
|
||||
];
|
||||
mod_http_upload = {
|
||||
host = "upload.xmpp.uninsane.org";
|
||||
hosts = [ "upload.xmpp.uninsane.org" ];
|
||||
put_url = "https://@HOST@:5443/upload";
|
||||
dir_mode = "0750";
|
||||
file_mode = "0750";
|
||||
rm_on_unregister = false;
|
||||
};
|
||||
# allow discoverability of BOSH and websocket endpoints
|
||||
# TODO: enable once on ejabberd 22.05 (presently 21.04)
|
||||
# mod_host_meta: {}
|
||||
mod_jidprep: {} # probably not needed: lets clients normalize jids
|
||||
mod_last: {} # allow other users to know when i was last online
|
||||
mod_mam:
|
||||
# mod_host_meta = {};
|
||||
mod_jidprep = {}; # probably not needed: lets clients normalize jids
|
||||
mod_last = {}; # allow other users to know when i was last online
|
||||
mod_mam = {
|
||||
# Mnesia is limited to 2GB, better to use an SQL backend
|
||||
# For small servers SQLite is a good fit and is very easy
|
||||
# to configure. Uncomment this when you have SQL configured:
|
||||
# db_type: sql
|
||||
assume_mam_usage: true
|
||||
default: always
|
||||
mod_muc:
|
||||
access:
|
||||
- allow
|
||||
access_admin:
|
||||
- allow: admin
|
||||
access_create: muc_create
|
||||
access_persistent: muc_create
|
||||
access_mam:
|
||||
- allow
|
||||
history_size: 100 # messages to show new participants
|
||||
host: muc.xmpp.uninsane.org
|
||||
hosts:
|
||||
- muc.xmpp.uninsane.org
|
||||
default_room_options:
|
||||
anonymous: false
|
||||
lang: en
|
||||
persistent: true
|
||||
mam: true
|
||||
mod_muc_admin: {}
|
||||
mod_offline: # store messages for a user when they're offline (TODO: understand multi-client workflow?)
|
||||
access_max_user_messages: max_user_offline_messages
|
||||
store_groupchat: true
|
||||
mod_ping: {}
|
||||
mod_privacy: {} # deprecated, but required for `ejabberctl export_piefxis`
|
||||
mod_private: {} # allow local clients to persist arbitrary data on my server
|
||||
assume_mam_usage = true;
|
||||
default = "always";
|
||||
};
|
||||
mod_muc = {
|
||||
access = [ "allow" ];
|
||||
access_admin = { allow = "admin"; };
|
||||
access_create = "muc_create";
|
||||
access_persistent = "muc_create";
|
||||
access_mam = [ "allow" ];
|
||||
history_size = 100; # messages to show new participants
|
||||
host = "muc.xmpp.uninsane.org";
|
||||
hosts = [ "muc.xmpp.uninsane.org" ];
|
||||
default_room_options = {
|
||||
anonymous = false;
|
||||
lang = "en";
|
||||
persistent = true;
|
||||
mam = true;
|
||||
};
|
||||
};
|
||||
mod_muc_admin = {};
|
||||
mod_offline = {
|
||||
# store messages for a user when they're offline (TODO: understand multi-client workflow?)
|
||||
access_max_user_messages = "max_user_offline_messages";
|
||||
store_groupchat = true;
|
||||
};
|
||||
mod_ping = {};
|
||||
mod_privacy = {}; # deprecated, but required for `ejabberctl export_piefxis`
|
||||
mod_private = {}; # allow local clients to persist arbitrary data on my server
|
||||
# push notifications to services integrated with e.g. Apple/Android.
|
||||
# default is for a maximum amount of PII to be withheld, since these push notifs
|
||||
# generally traverse 3rd party services. can opt to include message body, etc, though.
|
||||
mod_push: {}
|
||||
mod_push = {};
|
||||
# i don't fully understand what this does, but it seems aimed at making push notifs more reliable.
|
||||
mod_push_keepalive: {}
|
||||
mod_roster:
|
||||
versioning: true
|
||||
mod_push_keepalive = {};
|
||||
mod_roster = {
|
||||
versioning = true;
|
||||
};
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-s2s-dialback>
|
||||
# s2s dialback to verify inbound messages
|
||||
# unclear to what degree the XMPP network requires this
|
||||
mod_s2s_dialback: {}
|
||||
mod_shared_roster: {} # creates groups for @all, @online, and anything manually administered?
|
||||
mod_stream_mgmt:
|
||||
resend_on_timeout: if_offline # resend undelivered messages if the origin client is offline
|
||||
mod_s2s_dialback = {};
|
||||
mod_shared_roster = {}; # creates groups for @all, @online, and anything manually administered?
|
||||
mod_stream_mgmt = {
|
||||
# resend undelivered messages if the origin client is offline
|
||||
resend_on_timeout = "if_offline";
|
||||
};
|
||||
# fallback for when DNS-based STUN discovery is unsupported.
|
||||
# - see: <https://xmpp.org/extensions/xep-0215.html>
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-stun-disco>
|
||||
# people say to just keep this defaulted (i guess ejabberd knows to return its `host` option of uninsane.org?)
|
||||
mod_stun_disco: {}
|
||||
mod_stun_disco = {};
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-vcard>
|
||||
mod_vcard:
|
||||
allow_return_all: true # all users are discoverable (?)
|
||||
host: vjid.xmpp.uninsane.org
|
||||
hosts:
|
||||
- vjid.xmpp.uninsane.org
|
||||
search: true
|
||||
mod_vcard_xupdate: {} # needed for avatars
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-pubsub>
|
||||
mod_pubsub: # needed for avatars
|
||||
access_createnode: pubsub_createnode_access
|
||||
host: pubsub.xmpp.uninsane.org
|
||||
hosts:
|
||||
- pubsub.xmpp.uninsane.org
|
||||
ignore_pep_from_offline: false
|
||||
last_item_cache: true
|
||||
plugins:
|
||||
- pep
|
||||
- flat
|
||||
force_node_config:
|
||||
# ensure client bookmarks are private
|
||||
storage:bookmarks:
|
||||
access_model: whitelist
|
||||
urn:xmpp:avatar:data:
|
||||
access_model: open
|
||||
urn:xmpp:avatar:metadata:
|
||||
access_model: open
|
||||
mod_version: {}
|
||||
'';
|
||||
mod_vcard = {
|
||||
allow_return_all = true; # all users are discoverable (?)
|
||||
host = "vjid.xmpp.uninsane.org";
|
||||
hosts = [ "vjid.xmpp.uninsane.org" ];
|
||||
search = true;
|
||||
};
|
||||
mod_vcard_xupdate = {}; # needed for avatars
|
||||
# docs: <https://docs.ejabberd.im/admin/configuration/modules/#mod-pubsub>
|
||||
mod_pubsub = {
|
||||
#^ needed for avatars
|
||||
access_createnode = "pubsub_createnode_access";
|
||||
host = "pubsub.xmpp.uninsane.org";
|
||||
hosts = [ "pubsub.xmpp.uninsane.org" ];
|
||||
ignore_pep_from_offline = false;
|
||||
last_item_cache = true;
|
||||
plugins = [
|
||||
"pep"
|
||||
"flat"
|
||||
];
|
||||
force_node_config = {
|
||||
# ensure client bookmarks are private
|
||||
"storage:bookmarks:" = {
|
||||
"access_model" = "whitelist";
|
||||
};
|
||||
"urn:xmpp:avatar:data" = {
|
||||
"access_model" = "open";
|
||||
};
|
||||
"urn:xmpp:avatar:metadata" = {
|
||||
"access_model" = "open";
|
||||
};
|
||||
};
|
||||
};
|
||||
mod_version = {};
|
||||
};
|
||||
});
|
||||
sed = "${pkgs.gnused}/bin/sed";
|
||||
in ''
|
||||
ip=$(cat '${config.sane.services.dyn-dns.ipPath}')
|
||||
# config is 444 (not 644), so we want to write out-of-place and then atomically move
|
||||
# TODO: factor this out into `sane-woop` helper?
|
||||
rm -f /var/lib/ejabberd/ejabberd.yaml.new
|
||||
${sed} "s/%ANATIVE%/$ip/" ${config-in} > /var/lib/ejabberd/ejabberd.yaml.new
|
||||
${sed} "s/%ANATIVE%/$ip/g" ${config-in} > /var/lib/ejabberd/ejabberd.yaml.new
|
||||
mv /var/lib/ejabberd/ejabberd.yaml{.new,}
|
||||
'';
|
||||
|
||||
|
@@ -5,21 +5,33 @@
|
||||
./sftpgo.nix
|
||||
];
|
||||
|
||||
users.groups.export = {};
|
||||
|
||||
fileSystems."/var/export/media" = {
|
||||
# everything in here could be considered publicly readable (based on the viewer's legal jurisdiction)
|
||||
device = "/var/lib/uninsane/media";
|
||||
options = [ "rbind" ];
|
||||
};
|
||||
fileSystems."/var/export/playground" = {
|
||||
device = config.fileSystems."/mnt/persist/ext".device;
|
||||
fsType = "btrfs";
|
||||
options = [
|
||||
"subvol=export-playground"
|
||||
"compress=zstd"
|
||||
"defaults"
|
||||
# fileSystems."/var/export/playground" = {
|
||||
# device = config.fileSystems."/mnt/persist/ext".device;
|
||||
# fsType = "btrfs";
|
||||
# options = [
|
||||
# "subvol=export-playground"
|
||||
# "compress=zstd"
|
||||
# "defaults"
|
||||
# ];
|
||||
# };
|
||||
# N.B.: the backing directory should be manually created here **as a btrfs subvolume** and with a quota.
|
||||
# - `sudo btrfs subvolume create /mnt/persist/ext/persist/var/export/playground`
|
||||
# - `sudo btrfs quota enable /mnt/persist/ext/persist/var/export/playground`
|
||||
# - `sudo btrfs quota rescan -sw /mnt/persist/ext/persist/var/export/playground`
|
||||
# to adjust the limits (which apply at the block layer, i.e. post-compression):
|
||||
# - `sudo btrfs qgroup limit 20G /mnt/persist/ext/persist/var/export/playground`
|
||||
# to query the quota/status:
|
||||
# - `sudo btrfs qgroup show -re /var/export/playground`
|
||||
sane.persist.sys.ext = [
|
||||
{ user = "root"; group = "export"; mode = "0775"; path = "/var/export/playground"; }
|
||||
];
|
||||
};
|
||||
|
||||
sane.fs."/var/export/README.md" = {
|
||||
wantedBy = [ "nfs.service" "sftpgo.service" ];
|
||||
@@ -29,14 +41,13 @@
|
||||
'';
|
||||
};
|
||||
|
||||
# sane.fs."/var/lib/sftpgo/export/playground/README.md" = {
|
||||
# wantedBy = [ "nfs.service" "sftpgo.service" ];
|
||||
# file.text = ''
|
||||
# this directory is intentionally read+write by anyone.
|
||||
# there are no rules, except a server-level quota:
|
||||
# - share files
|
||||
# - write poetry
|
||||
# - be a friendly troll
|
||||
# '';
|
||||
# };
|
||||
sane.fs."/var/export/playground/README.md" = {
|
||||
wantedBy = [ "nfs.service" "sftpgo.service" ];
|
||||
file.text = ''
|
||||
this directory is intentionally read+write by anyone with access (i.e. on the LAN).
|
||||
- share files
|
||||
- write poetry
|
||||
- be a friendly troll
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
@@ -1,8 +1,20 @@
|
||||
# docs:
|
||||
# - <https://nixos.wiki/wiki/NFS>
|
||||
# - <https://wiki.gentoo.org/wiki/Nfs-utils>
|
||||
# system files:
|
||||
# - /etc/exports
|
||||
# system services:
|
||||
# - nfs-server.service
|
||||
# - nfs-idmapd.service
|
||||
# - nfs-mountd.service
|
||||
# - nfsdcld.service
|
||||
# - rpc-statd.service
|
||||
# - rpcbind.service
|
||||
#
|
||||
# TODO: force files to be 755, or 750.
|
||||
# - could maybe be done with some mount option?
|
||||
|
||||
{ ... }:
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
services.nfs.server.enable = true;
|
||||
|
||||
@@ -52,10 +64,47 @@
|
||||
# - no_root_squash, root_squash (default): map requests from uid 0 to user `nobody`.
|
||||
# - crossmnt: reveal filesystems that are mounted under this endpoint
|
||||
# - fsid: must be zero for the root export
|
||||
# - fsid=root is alias for fsid=0
|
||||
# - mountpoint[=/path]: only export the directory if it's a mountpoint. used to avoid exporting failed mounts.
|
||||
# - all_squash: rewrite all client requests such that they come from anonuid/anongid
|
||||
# - any files a user creates are owned by local anonuid/anongid.
|
||||
# - users can read any local file which anonuid/anongid would be able to read.
|
||||
# - users can't chown to/away from anonuid/anongid.
|
||||
# - users can chmod files they own, to anything (making them unreadable to non-`nfsuser` export users, like FTP).
|
||||
# - `stat` remains unchanged, returning the real UIDs/GIDs to the client.
|
||||
# - thus programs which check `uid` or `gid` before trying an operation may incorrectly conclude they can't perform some op.
|
||||
#
|
||||
# 10.0.0.0/8 to export (readonly) both to LAN (unencrypted) and wg vpn (encrypted)
|
||||
services.nfs.server.exports = ''
|
||||
/var/export 10.78.79.0/22(ro,crossmnt,fsid=0,subtree_check) 10.0.10.0/24(rw,no_root_squash,crossmnt,fsid=0,subtree_check)
|
||||
'';
|
||||
# 10.0.0.0/8 to export both to LAN (readonly, unencrypted) and wg vpn (read-write, encrypted)
|
||||
services.nfs.server.exports =
|
||||
let
|
||||
fmtExport = { export, baseOpts, extraLanOpts ? [], extraVpnOpts ? [] }:
|
||||
let
|
||||
always = [ "subtree_check" ];
|
||||
lanOpts = always ++ baseOpts ++ extraLanOpts;
|
||||
vpnOpts = always ++ baseOpts ++ extraVpnOpts;
|
||||
in "${export} 10.78.79.0/22(${lib.concatStringsSep "," lanOpts}) 10.0.10.0/24(${lib.concatStringsSep "," vpnOpts})";
|
||||
in lib.concatStringsSep "\n" [
|
||||
(fmtExport {
|
||||
export = "/var/export";
|
||||
baseOpts = [ "crossmnt" "fsid=root" ];
|
||||
extraLanOpts = [ "ro" ];
|
||||
extraVpnOpts = [ "rw" "no_root_squash" ];
|
||||
})
|
||||
(fmtExport {
|
||||
export = "/var/export/playground";
|
||||
baseOpts = [
|
||||
"mountpoint"
|
||||
"all_squash"
|
||||
"rw"
|
||||
"anonuid=${builtins.toString config.users.users.nfsuser.uid}"
|
||||
"anongid=${builtins.toString config.users.groups.export.gid}"
|
||||
];
|
||||
})
|
||||
];
|
||||
|
||||
users.users.nfsuser = {
|
||||
description = "virtual user for anonymous NFS operations";
|
||||
group = "export";
|
||||
isSystemUser = true;
|
||||
};
|
||||
}
|
||||
|
@@ -9,9 +9,6 @@
|
||||
#
|
||||
# TODO: change umask so sftpgo-created files default to 644.
|
||||
# - it does indeed appear that the 600 is not something sftpgo is explicitly doing.
|
||||
#
|
||||
# TODO: enforce a "quota" by placing /playground on a btrfs subvolume
|
||||
# - sane.persist API could expose a `subvolume` option to make this feel natural
|
||||
|
||||
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
@@ -126,6 +123,7 @@ in
|
||||
|
||||
services.sftpgo = {
|
||||
enable = true;
|
||||
group = "export";
|
||||
settings = {
|
||||
ftpd = {
|
||||
bindings = [
|
||||
@@ -172,22 +170,10 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
# fileSystems."/var/lib/sftpgo/export/media" = {
|
||||
# # everything in here could be considered publicly readable (based on the viewer's legal jurisdiction)
|
||||
# device = "/var/lib/uninsane/media";
|
||||
# options = [ "rbind" ];
|
||||
# };
|
||||
# sane.persist.sys.plaintext = [
|
||||
# { user = "sftpgo"; group = "sftpgo"; path = "/var/lib/sftpgo/export/playground"; }
|
||||
# ];
|
||||
# sane.fs."/var/lib/sftpgo/export/playground/README.md" = {
|
||||
# wantedBy = [ "sftpgo.service" ];
|
||||
# file.text = ''
|
||||
# this directory is intentionally read+write by anyone.
|
||||
# there are no rules, except a server-level quota:
|
||||
# - share files
|
||||
# - write poetry
|
||||
# - be a friendly troll
|
||||
# '';
|
||||
# };
|
||||
users.users.sftpgo.extraGroups = [ "export" ];
|
||||
|
||||
systemd.services.sftpgo.serviceConfig = {
|
||||
ReadOnlyPaths = [ "/var/export" ];
|
||||
ReadWritePaths = [ "/var/export/playground" ];
|
||||
};
|
||||
}
|
||||
|
@@ -14,19 +14,20 @@
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/matrix-synapse"; }
|
||||
];
|
||||
services.matrix-synapse.enable = true;
|
||||
services.matrix-synapse.settings = {
|
||||
# this changes the default log level from INFO to WARN.
|
||||
# maybe there's an easier way?
|
||||
services.matrix-synapse.settings.log_config = ./synapse-log_level.yaml;
|
||||
services.matrix-synapse.settings.server_name = "uninsane.org";
|
||||
log_config = ./synapse-log_level.yaml;
|
||||
server_name = "uninsane.org";
|
||||
|
||||
# services.matrix-synapse.enable_registration_captcha = true;
|
||||
# services.matrix-synapse.enable_registration_without_verification = true;
|
||||
services.matrix-synapse.settings.enable_registration = true;
|
||||
enable_registration = true;
|
||||
# services.matrix-synapse.registration_shared_secret = "<shared key goes here>";
|
||||
|
||||
# default for listeners is port = 8448, tls = true, x_forwarded = false.
|
||||
# we change this because the server is situated behind nginx.
|
||||
services.matrix-synapse.settings.listeners = [
|
||||
listeners = [
|
||||
{
|
||||
port = 8008;
|
||||
bind_addresses = [ "127.0.0.1" ];
|
||||
@@ -42,11 +43,18 @@
|
||||
}
|
||||
];
|
||||
|
||||
services.matrix-synapse.settings.x_forwarded = true; # because we proxy matrix behind nginx
|
||||
services.matrix-synapse.settings.max_upload_size = "100M"; # default is "50M"
|
||||
ip_range_whitelist = [
|
||||
# to communicate with ntfy.uninsane.org push notifs.
|
||||
# TODO: move this to some non-shared loopback device: we don't want Matrix spouting http requests to *anything* on this machine
|
||||
"10.78.79.51"
|
||||
];
|
||||
|
||||
services.matrix-synapse.settings.admin_contact = "admin.matrix@uninsane.org";
|
||||
services.matrix-synapse.settings.registrations_require_3pid = [ "email" ];
|
||||
x_forwarded = true; # because we proxy matrix behind nginx
|
||||
max_upload_size = "100M"; # default is "50M"
|
||||
|
||||
admin_contact = "admin.matrix@uninsane.org";
|
||||
registrations_require_3pid = [ "email" ];
|
||||
};
|
||||
|
||||
services.matrix-synapse.extraConfigFiles = [
|
||||
config.sops.secrets."matrix_synapse_secrets.yaml".path
|
||||
|
@@ -141,6 +141,7 @@ in
|
||||
sasl = false;
|
||||
# notable channels:
|
||||
# - #hare
|
||||
# - #mnt-reform
|
||||
};
|
||||
"irc.myanonamouse.net" = ircServer {
|
||||
name = "MyAnonamouse";
|
||||
|
98
hosts/by-name/servo/services/ntfy.nix
Normal file
98
hosts/by-name/servo/services/ntfy.nix
Normal file
@@ -0,0 +1,98 @@
|
||||
# ntfy: UnifiedPush notification delivery system
|
||||
# - used to get push notifications out of Matrix and onto a Phone (iOS, Android, or a custom client)
|
||||
#
|
||||
# config options:
|
||||
# - <https://docs.ntfy.sh/config/#config-options>
|
||||
#
|
||||
# usage:
|
||||
# - ntfy sub https://ntfy.uninsane.org/TOPIC
|
||||
# - ntfy pub https://ntfy.uninsane.org/TOPIC "my message"
|
||||
# in production, TOPIC is a shared secret between the publisher (Matrix homeserver) and the subscriber (phone)
|
||||
#
|
||||
# administering:
|
||||
# - sudo -u ntfy-sh ntfy access
|
||||
#
|
||||
# debugging:
|
||||
# - make sure that the keepalives are good:
|
||||
# - on the subscriber machine, run `lsof -i4` to find the port being used
|
||||
# - `sudo tcpdump tcp port <p>`
|
||||
# - shouldn't be too spammy
|
||||
#
|
||||
# matrix integration:
|
||||
# - the user must manually point synapse to the ntfy endpoint:
|
||||
# - `curl --header "Authorization: <your_token>" --data '{ "app_display_name": "sane-nix moby", "app_id": "ntfy.uninsane.org", "data": { "url": "https://ntfy.uninsane.org/_matrix/push/v1/notify", "format": "event_id_only" }, "device_display_name": "sane-nix moby", "kind": "http", "lang": "en-US", "profile_tag": "", "pushkey": "https://ntfy.uninsane.org/TOPIC" }' localhost:8008/_matrix/client/v3/pushers/set`
|
||||
# where the token is grabbed from Element's help&about page when logged in
|
||||
# - to remove, send this `curl` with `"kind": null`
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
# subscribers need a non-443 public port to listen on as a way to easily differentiate this traffic
|
||||
# at the IP layer, to enable e.g. wake-on-lan.
|
||||
altPort = 2587;
|
||||
in
|
||||
{
|
||||
sane.persist.sys.plaintext = [
|
||||
# not 100% necessary to persist this, but ntfy does keep a 12hr (by default) cache
|
||||
# for pushing notifications to users who become offline.
|
||||
# ACLs also live here.
|
||||
{ user = "ntfy-sh"; group ="ntfy-sh"; path = "/var/lib/ntfy-sh"; }
|
||||
];
|
||||
|
||||
services.ntfy-sh.enable = true;
|
||||
services.ntfy-sh.settings = {
|
||||
base-url = "https://ntfy.uninsane.org";
|
||||
behind-proxy = true; # not sure if needed
|
||||
# keepalive interval is a ntfy-specific keepalive thing, where it sends actual data down the wire.
|
||||
# it's not simple TCP keepalive.
|
||||
# defaults to 45s.
|
||||
# note that the client may still do its own TCP-level keepalives, typically every 30s
|
||||
keepalive-interval = "15m";
|
||||
log-level = "trace"; # trace, debug, info (default), warn, error
|
||||
auth-default-access = "deny-all";
|
||||
};
|
||||
systemd.services.ntfy-sh.serviceConfig.DynamicUser = lib.mkForce false;
|
||||
systemd.services.ntfy-sh.preStart = ''
|
||||
# make this specific topic read-write by world
|
||||
# it would be better to use the token system, but that's extra complexity for e.g.
|
||||
# how do i plumb a secret into the Matrix notification pusher
|
||||
#
|
||||
# note that this will fail upon first run, i.e. before ntfy has created its db.
|
||||
# just restart the service.
|
||||
topic=$(cat ${config.sops.secrets.ntfy-sh-topic.path})
|
||||
${pkgs.ntfy-sh}/bin/ntfy access everyone "$topic" read-write
|
||||
'';
|
||||
|
||||
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" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
listen = [
|
||||
{ addr = "0.0.0.0"; port = altPort; ssl = true; }
|
||||
{ addr = "0.0.0.0"; port = 443; ssl = true; }
|
||||
{ addr = "0.0.0.0"; port = 80; ssl = false; }
|
||||
];
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:2586";
|
||||
proxyWebsockets = true; #< support websocket upgrades. without that, `ntfy sub` hangs silently
|
||||
recommendedProxySettings = true; #< adds headers so ntfy logs include the real IP
|
||||
extraConfig = ''
|
||||
# absurdly long timeout (86400s=24h) so that we never hang up on clients.
|
||||
# make sure the client is smart enough to detect a broken proxy though!
|
||||
proxy_read_timeout 86400s;
|
||||
'';
|
||||
};
|
||||
};
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."ntfy" = "native";
|
||||
|
||||
sane.ports.ports."${builtins.toString altPort}" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-ntfy.uninsane.org";
|
||||
};
|
||||
}
|
@@ -1,12 +1,27 @@
|
||||
{ pkgs, ... }:
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
sane.persist.sys.plaintext = [
|
||||
# TODO: mode? we need this specifically for the stats tracking in .config/
|
||||
{ user = "transmission"; group = "transmission"; path = "/var/lib/transmission"; }
|
||||
{ user = "transmission"; group = config.users.users.transmission.group; path = "/var/lib/transmission"; }
|
||||
];
|
||||
users.users.transmission.extraGroups = [ "media" ];
|
||||
|
||||
services.transmission.enable = true;
|
||||
services.transmission.package = pkgs.transmission_4; #< 2023/09/06: nixpkgs `transmission` defaults to old 3.00
|
||||
#v setting `group` this way doesn't tell transmission to `chown` the files it creates
|
||||
# it's a nixpkgs setting which just runs the transmission daemon as this group
|
||||
services.transmission.group = "media";
|
||||
|
||||
# transmission will by default not allow the world to read its files.
|
||||
services.transmission.downloadDirPermissions = "775";
|
||||
services.transmission.extraFlags = [
|
||||
"--log-level=debug"
|
||||
];
|
||||
|
||||
services.transmission.settings = {
|
||||
# message-level = 3; #< enable for debug logging. 0-3, default is 2.
|
||||
# 0.0.0.0 => allow rpc from any host: we gate it via firewall and auth requirement
|
||||
rpc-bind-address = "0.0.0.0";
|
||||
#rpc-host-whitelist = "bt.uninsane.org";
|
||||
#rpc-whitelist = "*.*.*.*";
|
||||
@@ -17,9 +32,8 @@
|
||||
rpc-password = "{503fc8928344f495efb8e1f955111ca5c862ce0656SzQnQ5";
|
||||
rpc-whitelist-enabled = false;
|
||||
|
||||
# download-dir = "/opt/uninsane/media/";
|
||||
# hopefully, make the downloads world-readable
|
||||
umask = 0;
|
||||
# umask = 0; #< default is 2: i.e. deny writes from world
|
||||
|
||||
# force peer connections to be encrypted
|
||||
encryption = 2;
|
||||
@@ -35,17 +49,18 @@
|
||||
|
||||
download-dir = "/var/lib/uninsane/media";
|
||||
incomplete-dir = "/var/lib/uninsane/media/incomplete";
|
||||
|
||||
# transmission regularly fails to move stuff from the incomplete dir to the main one, so disable:
|
||||
# TODO: uncomment this line!
|
||||
incomplete-dir-enabled = false;
|
||||
};
|
||||
# transmission will by default not allow the world to read its files.
|
||||
services.transmission.downloadDirPermissions = "775";
|
||||
|
||||
systemd.services.transmission.after = [ "wireguard-wg-ovpns.service" ];
|
||||
systemd.services.transmission.partOf = [ "wireguard-wg-ovpns.service" ];
|
||||
systemd.services.transmission.serviceConfig = {
|
||||
# run this behind the OVPN static VPN
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
LogLevelMax = "warning";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "30s";
|
||||
};
|
||||
|
||||
# service to automatically backup torrents i add to transmission
|
||||
|
@@ -1,14 +1,13 @@
|
||||
# TODO: split this file apart into smaller files to make it easier to understand
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
nativeAddrs = lib.mapAttrs (_name: builtins.head) config.sane.dns.zones."uninsane.org".inet.A;
|
||||
bindOvpn = "10.0.1.5";
|
||||
in lib.mkMerge [
|
||||
{
|
||||
services.trust-dns.enable = true;
|
||||
|
||||
services.trust-dns.settings.listen_addrs_ipv4 = [
|
||||
# specify each address explicitly, instead of using "*".
|
||||
# this ensures responses are sent from the address at which the request was received.
|
||||
config.sane.hosts.by-name."servo".lan-ip
|
||||
"10.0.1.5"
|
||||
];
|
||||
# don't bind to IPv6 until i explicitly test that stack
|
||||
services.trust-dns.settings.listen_addrs_ipv6 = [];
|
||||
services.trust-dns.quiet = true;
|
||||
@@ -34,18 +33,19 @@
|
||||
sane.dns.zones."uninsane.org".inet = {
|
||||
SOA."@" = ''
|
||||
ns1.uninsane.org. admin-dns.uninsane.org. (
|
||||
2022122101 ; Serial
|
||||
2023092101 ; Serial
|
||||
4h ; Refresh
|
||||
30m ; Retry
|
||||
7d ; Expire
|
||||
5m) ; Negative response TTL
|
||||
'';
|
||||
TXT."rev" = "2023052901";
|
||||
TXT."rev" = "2023092101";
|
||||
|
||||
CNAME."native" = "%CNAMENATIVE%";
|
||||
A."@" = "%ANATIVE%";
|
||||
A."wan" = "%AWAN%";
|
||||
A."servo.wan" = "%AWAN%";
|
||||
A."servo.lan" = config.sane.hosts.by-name."servo".lan-ip;
|
||||
A."servo.hn" = config.sane.hosts.by-name."servo".wg-home.ip;
|
||||
|
||||
# XXX NS records must also not be CNAME
|
||||
# it's best that we keep this identical, or a superset of, what org. lists as our NS.
|
||||
@@ -63,55 +63,23 @@
|
||||
|
||||
services.trust-dns.settings.zones = [ "uninsane.org" ];
|
||||
|
||||
services.trust-dns.package =
|
||||
let
|
||||
sed = "${pkgs.gnused}/bin/sed";
|
||||
zone-dir = "/var/lib/trust-dns";
|
||||
zone-wan = "${zone-dir}/wan/uninsane.org.zone";
|
||||
zone-lan = "${zone-dir}/lan/uninsane.org.zone";
|
||||
zone-template = pkgs.writeText "uninsane.org.zone.in" config.sane.dns.zones."uninsane.org".rendered;
|
||||
in pkgs.writeShellScriptBin "named" ''
|
||||
# compute wan/lan values
|
||||
mkdir -p ${zone-dir}/{ovpn,wan,lan}
|
||||
wan=$(cat '${config.sane.services.dyn-dns.ipPath}')
|
||||
lan=${config.sane.hosts.by-name."servo".lan-ip}
|
||||
|
||||
# create specializations that resolve native.uninsane.org to different CNAMEs
|
||||
${sed} s/%AWAN%/$wan/ ${zone-template} \
|
||||
| ${sed} s/%CNAMENATIVE%/wan/ \
|
||||
| ${sed} s/%ANATIVE%/$wan/ \
|
||||
> ${zone-wan}
|
||||
${sed} s/%AWAN%/$wan/ ${zone-template} \
|
||||
| ${sed} s/%CNAMENATIVE%/servo.lan/ \
|
||||
| ${sed} s/%ANATIVE%/$lan/ \
|
||||
> ${zone-lan}
|
||||
|
||||
# launch the different interfaces, separately
|
||||
${pkgs.trust-dns}/bin/named --port 53 --zonedir ${zone-dir}/wan/ $@ &
|
||||
WANPID=$!
|
||||
${pkgs.trust-dns}/bin/named --port 1053 --zonedir ${zone-dir}/lan/ $@ &
|
||||
LANPID=$!
|
||||
|
||||
# wait until any of the processes exits, then kill them all and exit error
|
||||
while kill -0 $WANPID $LANPID ; do
|
||||
sleep 5
|
||||
done
|
||||
kill $WANPID $LANPID
|
||||
exit 1
|
||||
'';
|
||||
|
||||
# TODO: can i transform this into some sort of service group?
|
||||
# have `systemctl restart trust-dns.service` restart all the individual services?
|
||||
systemd.services.trust-dns.serviceConfig = {
|
||||
DynamicUser = lib.mkForce false;
|
||||
User = "trust-dns";
|
||||
Group = "trust-dns";
|
||||
wantedBy = lib.mkForce [];
|
||||
};
|
||||
systemd.services.trust-dns.enable = false;
|
||||
|
||||
users.groups.trust-dns = {};
|
||||
users.users.trust-dns = {
|
||||
group = "trust-dns";
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
sane.services.dyn-dns.restartOnChange = [ "trust-dns.service" ];
|
||||
# sane.services.dyn-dns.restartOnChange = [ "trust-dns.service" ];
|
||||
|
||||
networking.nat.enable = true;
|
||||
networking.nat.extraCommands = ''
|
||||
@@ -127,12 +95,112 @@
|
||||
-m iprange --src-range 10.78.76.0-10.78.79.255 \
|
||||
-j DNAT --to-destination :1053
|
||||
'';
|
||||
|
||||
sane.ports.ports."1053" = {
|
||||
# because the NAT above redirects in nixos-nat-pre, LAN requests behave as though they arrived on the external interface at the redirected port.
|
||||
# TODO: try nixos-nat-post instead?
|
||||
# TODO: or, don't NAT from port 53 -> port 1053, but rather nat from LAN addr to a loopback addr.
|
||||
# - this is complicated in that loopback is a different interface than eth0, so rewriting the destination address would cause the packets to just be dropped by the interface
|
||||
protocol = [ "udp" "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
description = "colin-redirected-dns-for-lan-namespace";
|
||||
};
|
||||
}
|
||||
{
|
||||
systemd.services =
|
||||
let
|
||||
sed = "${pkgs.gnused}/bin/sed";
|
||||
stateDir = "/var/lib/trust-dns";
|
||||
zoneTemplate = pkgs.writeText "uninsane.org.zone.in" config.sane.dns.zones."uninsane.org".rendered;
|
||||
|
||||
zoneDirFor = flavor: "${stateDir}/${flavor}";
|
||||
zoneFor = flavor: "${zoneDirFor flavor}/uninsane.org.zone";
|
||||
mkTrustDnsService = opts: flavor: let
|
||||
flags = let baseCfg = config.services.trust-dns; in
|
||||
(lib.optional baseCfg.debug "--debug") ++ (lib.optional baseCfg.quiet "--quiet");
|
||||
flagsStr = builtins.concatStringsSep " " flags;
|
||||
|
||||
anative = nativeAddrs."servo.${flavor}";
|
||||
|
||||
toml = pkgs.formats.toml { };
|
||||
configTemplate = opts.config or (toml.generate "trust-dns-${flavor}.toml" (
|
||||
(
|
||||
lib.filterAttrsRecursive (_: v: v != null) config.services.trust-dns.settings
|
||||
) // {
|
||||
listen_addrs_ipv4 = opts.listen or [ anative ];
|
||||
}
|
||||
));
|
||||
configFile = "${stateDir}/${flavor}-config.toml";
|
||||
|
||||
port = opts.port or 53;
|
||||
in {
|
||||
description = "trust-dns Domain Name Server (serving ${flavor})";
|
||||
unitConfig.Documentation = "https://trust-dns.org/";
|
||||
|
||||
preStart = ''
|
||||
wan=$(cat '${config.sane.services.dyn-dns.ipPath}')
|
||||
${sed} s/%AWAN%/$wan/ ${configTemplate} > ${configFile}
|
||||
'' + lib.optionalString (!opts ? config) ''
|
||||
mkdir -p ${zoneDirFor flavor}
|
||||
${sed} \
|
||||
-e s/%CNAMENATIVE%/servo.${flavor}/ \
|
||||
-e s/%ANATIVE%/${anative}/ \
|
||||
-e s/%AWAN%/$wan/ \
|
||||
${zoneTemplate} > ${zoneFor flavor}
|
||||
'';
|
||||
serviceConfig = config.systemd.services.trust-dns.serviceConfig // {
|
||||
ExecStart = ''
|
||||
${pkgs.trust-dns}/bin/trust-dns \
|
||||
--port ${builtins.toString port} \
|
||||
--zonedir ${zoneDirFor flavor}/ \
|
||||
--config ${configFile} ${flagsStr}
|
||||
'';
|
||||
};
|
||||
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
in {
|
||||
trust-dns-wan = mkTrustDnsService { listen = [ nativeAddrs."servo.lan" bindOvpn ]; } "wan";
|
||||
trust-dns-lan = mkTrustDnsService { port = 1053; } "lan";
|
||||
trust-dns-hn = mkTrustDnsService { port = 1053; } "hn";
|
||||
trust-dns-hn-resolver = mkTrustDnsService {
|
||||
config = pkgs.writeText "hn-resolver-config.toml" ''
|
||||
# i host a resolver in the wireguard VPN so that clients can resolve DNS through the VPN.
|
||||
# (that's what this file achieves).
|
||||
#
|
||||
# one would expect this resolver could host the authoritative zone for `uninsane.org`, and then forward everything else to the system resolver...
|
||||
# and while that works for `dig`, it breaks for `nslookup` (and so `ssh`, etc).
|
||||
#
|
||||
# DNS responses include a flag for if the responding server is the authority of the zone queried.
|
||||
# it seems that default Linux stub resolvers either:
|
||||
# - expect DNSSEC when the response includes that bit, or
|
||||
# - expect A records to be in the `answer` section instead of `additional` section.
|
||||
# or perhaps something more nuanced. but for `nslookup` to be reliable, it has to talk to an
|
||||
# instance of trust-dns which is strictly a resolver, with no authority.
|
||||
# hence, this config: a resolver which forwards to the actual authority.
|
||||
|
||||
listen_addrs_ipv4 = ["${nativeAddrs."servo.hn"}"]
|
||||
listen_addrs_ipv6 = []
|
||||
|
||||
[[zones]]
|
||||
zone = "uninsane.org"
|
||||
zone_type = "Forward"
|
||||
stores = { type = "forward", name_servers = [{ socket_addr = "${nativeAddrs."servo.hn"}:1053", protocol = "udp", trust_nx_responses = true }] }
|
||||
|
||||
[[zones]]
|
||||
# forward the root zone to the local DNS resolver
|
||||
zone = "."
|
||||
zone_type = "Forward"
|
||||
stores = { type = "forward", name_servers = [{ socket_addr = "127.0.0.53:53", protocol = "udp", trust_nx_responses = true }] }
|
||||
'';
|
||||
} "hn-resolver";
|
||||
};
|
||||
|
||||
sane.services.dyn-dns.restartOnChange = [
|
||||
"trust-dns-wan.service"
|
||||
"trust-dns-lan.service"
|
||||
"trust-dns-hn.service"
|
||||
# "trust-dns-hn-resolver.service" # doesn't need restart because it doesn't know about WAN IP
|
||||
];
|
||||
}
|
||||
]
|
||||
|
@@ -3,8 +3,9 @@
|
||||
imports = [
|
||||
./feeds.nix
|
||||
./fs.nix
|
||||
./hardware.nix
|
||||
./hardware
|
||||
./home
|
||||
./hosts.nix
|
||||
./ids.nix
|
||||
./machine-id.nix
|
||||
./net.nix
|
||||
|
@@ -73,7 +73,7 @@ let
|
||||
## Jennifer Briney
|
||||
(fromDb "congressionaldish.libsyn.com" // pol)
|
||||
(fromDb "werenotwrong.fireside.fm" // pol)
|
||||
(fromDb "usefulidiots.substack.com" // pol)
|
||||
(fromDb "politicalorphanage.libsyn.com" // pol)
|
||||
# (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)
|
||||
@@ -116,7 +116,7 @@ let
|
||||
(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 "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)
|
||||
@@ -150,7 +150,7 @@ let
|
||||
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
||||
(fromDb "tuxphones.com" // tech)
|
||||
(fromDb "spectrum.ieee.org" // tech)
|
||||
(fromDb "theregister.com" // tech)
|
||||
# (fromDb "theregister.com" // tech)
|
||||
(fromDb "thisweek.gnome.org" // tech)
|
||||
# more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
|
||||
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent)
|
||||
@@ -166,6 +166,8 @@ let
|
||||
(fromDb "uninsane.org" // tech)
|
||||
(fromDb "ascii.textfiles.com" // tech) # Jason Scott
|
||||
(fromDb "xn--gckvb8fzb.com" // tech)
|
||||
(fromDb "amosbbatto.wordpress.com" // tech)
|
||||
(fromDb "fasterthanli.me" // tech)
|
||||
(fromDb "mg.lol" // tech)
|
||||
# (fromDb "drewdevault.com" // tech)
|
||||
## Ken Shirriff
|
||||
@@ -251,6 +253,7 @@ let
|
||||
images = [
|
||||
(fromDb "smbc-comics.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)
|
||||
|
@@ -111,6 +111,12 @@ lib.mkMerge [
|
||||
fsType = "nfs";
|
||||
options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg;
|
||||
};
|
||||
fileSystems."/mnt/servo-nfs/playground" = {
|
||||
device = "servo-hn:/playground";
|
||||
noCheck = true;
|
||||
fsType = "nfs";
|
||||
options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg;
|
||||
};
|
||||
# fileSystems."/mnt/servo-media-nfs" = {
|
||||
# device = "servo-hn:/media";
|
||||
# noCheck = true;
|
||||
|
@@ -1,6 +1,10 @@
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./x86_64.nix
|
||||
];
|
||||
|
||||
boot.initrd.supportedFilesystems = [ "ext4" "btrfs" "ext2" "ext3" "vfat" ];
|
||||
# useful emergency utils
|
||||
boot.initrd.extraUtilsCommands = ''
|
||||
@@ -26,6 +30,20 @@
|
||||
|
||||
# powertop will default to putting USB devices -- including HID -- to sleep after TWO SECONDS
|
||||
powerManagement.powertop.enable = false;
|
||||
# linux CPU governor: <https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt>
|
||||
# - options:
|
||||
# - "powersave" => force CPU to always run at lowest supported frequency
|
||||
# - "performance" => force CPU to always run at highest frequency
|
||||
# - "ondemand" => adjust frequency based on load
|
||||
# - "conservative" (ondemand but slower to adjust)
|
||||
# - "schedutil"
|
||||
# - "userspace"
|
||||
# - not all options are available for all platforms
|
||||
# - intel (intel_pstate) appears to manage scaling w/o intervention/control from the OS.
|
||||
# - AMD (acpi-cpufreq) appears to manage scaling via the OS *or* HW. but the ondemand defaults never put it to max hardware frequency.
|
||||
# - qualcomm (cpufreq-dt) appears to manage scaling *only* via the OS. ondemand governor exercises the full range.
|
||||
# - query details with `sudo cpupower frequency-info`
|
||||
powerManagement.cpuFreqGovernor = "ondemand";
|
||||
|
||||
services.logind.extraConfig = ''
|
||||
# don’t shutdown when power button is short-pressed
|
@@ -9,12 +9,7 @@
|
||||
# efi_pstore evivars
|
||||
];
|
||||
|
||||
powerManagement.cpuFreqGovernor = "powersave";
|
||||
hardware.cpu.amd.updateMicrocode = true; # desktop
|
||||
hardware.cpu.intel.updateMicrocode = true; # laptop
|
||||
|
||||
hardware.opengl.driSupport = true;
|
||||
# For 32 bit applications
|
||||
hardware.opengl.driSupport32Bit = true;
|
||||
};
|
||||
}
|
39
hosts/common/hosts.nix
Normal file
39
hosts/common/hosts.nix
Normal file
@@ -0,0 +1,39 @@
|
||||
{ lib, ... }:
|
||||
|
||||
{
|
||||
# TODO: this should be populated per-host
|
||||
sane.hosts.by-name."desko" = {
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPU5GlsSfbaarMvDA20bxpSZGWviEzXGD8gtrIowc1pX";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFw9NoRaYrM6LbDd3aFBc4yyBlxGQn8HjeHd/dZ3CfHk";
|
||||
wg-home.pubkey = "17PMZssYi0D4t2d0vbmhjBKe1sGsE8kT8/dod0Q2CXc=";
|
||||
wg-home.ip = "10.0.10.22";
|
||||
lan-ip = "10.78.79.52";
|
||||
};
|
||||
|
||||
sane.hosts.by-name."lappy" = {
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDpmFdNSVPRol5hkbbCivRhyeENzb9HVyf9KutGLP2Zu";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILSJnqmVl9/SYQ0btvGb0REwwWY8wkdkGXQZfn/1geEc";
|
||||
wg-home.pubkey = "FTUWGw2p4/cEcrrIE86PWVnqctbv8OYpw8Gt3+dC/lk=";
|
||||
wg-home.ip = "10.0.10.20";
|
||||
lan-ip = "10.78.79.53";
|
||||
};
|
||||
|
||||
sane.hosts.by-name."moby" = {
|
||||
ssh.authorized = lib.mkDefault false; # moby's too easy to hijack: don't let it ssh places
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrR+gePnl0nV/vy7I5BzrGeyVL+9eOuXHU1yNE3uCwU";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1N/IT3nQYUD+dBlU1sTEEVMxfOyMkrrDeyHcYgnJvw";
|
||||
wg-home.pubkey = "I7XIR1hm8bIzAtcAvbhWOwIAabGkuEvbWH/3kyIB1yA=";
|
||||
wg-home.ip = "10.0.10.48";
|
||||
lan-ip = "10.78.79.54";
|
||||
};
|
||||
|
||||
sane.hosts.by-name."servo" = {
|
||||
ssh.authorized = lib.mkDefault false; # servo presents too many services to the internet: easy atack vector
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPS1qFzKurAdB9blkWomq8gI1g0T3sTs9LsmFOj5VtqX";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOfdSmFkrVT6DhpgvFeQKm3Fh9VKZ9DbLYOPOJWYQ0E8";
|
||||
wg-home.pubkey = "roAw+IUFVtdpCcqa4khB385Qcv9l5JAB//730tyK4Wk=";
|
||||
wg-home.ip = "10.0.10.5";
|
||||
wg-home.endpoint = "uninsane.org:51820";
|
||||
lan-ip = "10.78.79.51";
|
||||
};
|
||||
}
|
@@ -44,6 +44,11 @@
|
||||
sane.ids.sftpgo.gid = 2410;
|
||||
sane.ids.trust-dns.uid = 2411;
|
||||
sane.ids.trust-dns.gid = 2411;
|
||||
sane.ids.export.gid = 2412;
|
||||
sane.ids.nfsuser.uid = 2413;
|
||||
sane.ids.media.gid = 2414;
|
||||
sane.ids.ntfy-sh.uid = 2415;
|
||||
sane.ids.ntfy-sh.gid = 2415;
|
||||
|
||||
sane.ids.colin.uid = 1000;
|
||||
sane.ids.guest.uid = 1100;
|
||||
@@ -81,4 +86,8 @@
|
||||
sane.ids.rtkit.gid = 2307;
|
||||
# phosh
|
||||
sane.ids.feedbackd.gid = 2308;
|
||||
|
||||
# new moby users
|
||||
sane.ids.eg25-control.uid = 2309;
|
||||
sane.ids.eg25-control.gid = 2309;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{ pkgs, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
declPackageSet = pkgs: {
|
||||
@@ -44,8 +44,7 @@ in
|
||||
"sane-scripts.ssl-dump"
|
||||
"sane-scripts.sudo-redirect"
|
||||
"sane-scripts.sync-from-servo"
|
||||
"sane-scripts.vpn-down"
|
||||
"sane-scripts.vpn-up"
|
||||
"sane-scripts.vpn"
|
||||
"sane-scripts.which"
|
||||
"sane-scripts.wipe-browser"
|
||||
];
|
||||
@@ -60,7 +59,9 @@ in
|
||||
"cacert.unbundled" # some services require unbundled /etc/ssl/certs
|
||||
"cryptsetup"
|
||||
"dig"
|
||||
"dtc" # device tree [de]compiler
|
||||
"efibootmgr"
|
||||
"ethtool"
|
||||
"fatresize"
|
||||
"fd"
|
||||
"file"
|
||||
@@ -74,6 +75,7 @@ in
|
||||
"inetutils" # for telnet
|
||||
"iotop"
|
||||
"iptables"
|
||||
"iw"
|
||||
"jq"
|
||||
"killall"
|
||||
"lsof"
|
||||
@@ -115,6 +117,7 @@ in
|
||||
# - debugging?
|
||||
consoleUtils = declPackageSet [
|
||||
"alsaUtils" # for aplay, speaker-test
|
||||
"binutils" # for strings; though this brings 80MB of unrelated baggage too
|
||||
# "cdrtools"
|
||||
"clinfo"
|
||||
"dmidecode"
|
||||
@@ -127,7 +130,6 @@ in
|
||||
# "gopass"
|
||||
# "gopass-jsonapi"
|
||||
"helix" # text editor
|
||||
# "kitty" # XXX needs to be in consolueUtils because `ssh servo` from kitty sets `TERM=xterm-kitty` in the remote and breaks things
|
||||
"libsecret" # for managing user keyrings. TODO: what needs this? lift into the consumer
|
||||
"lm_sensors" # for sensors-detect. TODO: what needs this? lift into the consumer
|
||||
"lshw"
|
||||
@@ -154,6 +156,7 @@ in
|
||||
"sudo"
|
||||
# "tageditor" # music tagging
|
||||
# "unar"
|
||||
"unzip"
|
||||
"wireguard-tools"
|
||||
"xdg-terminal-exec"
|
||||
"xdg-utils" # for xdg-open
|
||||
@@ -193,14 +196,18 @@ in
|
||||
];
|
||||
|
||||
devPkgs = declPackageSet [
|
||||
"cargo"
|
||||
"clang"
|
||||
"nodejs"
|
||||
"rustc"
|
||||
"tree-sitter"
|
||||
];
|
||||
|
||||
|
||||
# INDIVIDUAL PACKAGE DEFINITIONS
|
||||
|
||||
cargo.persist.plaintext = [ ".cargo" ];
|
||||
|
||||
# creds, but also 200 MB of node modules, etc
|
||||
discord.persist.private = [ ".config/discord" ];
|
||||
|
||||
@@ -211,10 +218,18 @@ in
|
||||
|
||||
fluffychat-moby.persist.plaintext = [ ".local/share/chat.fluffy.fluffychat" ];
|
||||
|
||||
font-manager.package = pkgs.font-manager.override {
|
||||
# build without the "Google Fonts" integration feature, to save closure / avoid webkitgtk_4_0
|
||||
withWebkit = false;
|
||||
};
|
||||
|
||||
# MS GitHub stores auth token in .config
|
||||
# TODO: we can populate gh's stuff statically; it even lets us use the same oauth across machines
|
||||
gh.persist.private = [ ".config/gh" ];
|
||||
|
||||
"gnome.gnome-maps".persist.plaintext = [ ".cache/shumate" ];
|
||||
"gnome.gnome-maps".persist.private = [ ".local/share/maps-places.json" ];
|
||||
|
||||
# actual monero blockchain (not wallet/etc; safe to delete, just slow to regenerate)
|
||||
# XXX: is it really safe to persist this? it doesn't have info that could de-anonymize if captured?
|
||||
monero-gui.persist.plaintext = [ ".bitmonero" ];
|
||||
@@ -250,4 +265,8 @@ in
|
||||
# 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 {
|
||||
enable = true;
|
||||
};
|
||||
}
|
||||
|
61
hosts/common/programs/calls.nix
Normal file
61
hosts/common/programs/calls.nix
Normal file
@@ -0,0 +1,61 @@
|
||||
# GNOME calls
|
||||
# - <https://gitlab.gnome.org/GNOME/calls>
|
||||
# - both a dialer and a call handler.
|
||||
# - uses callaudiod dbus package.
|
||||
#
|
||||
# initial JMP.chat configuration:
|
||||
# - message @cheogram.com "reset sip account" (this is not destructive, despite the name)
|
||||
# - the bot will reply with auto-generated username/password plus a SIP server endpoint.
|
||||
# just copy those into gnome-calls' GUI configurator
|
||||
# - now gnome-calls can do outbound calls. inbound calls requires more chatting with the help bot
|
||||
#
|
||||
# my setup here is still very WIP.
|
||||
# open questions:
|
||||
# - can i receive calls even with GUI closed?
|
||||
# - e.g. activated by callaudiod?
|
||||
# - looks like `gnome-calls --daemon` does that?
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.calls;
|
||||
in
|
||||
{
|
||||
sane.programs.calls = {
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
persist.private = [
|
||||
# ".cache/folks" # contact avatars?
|
||||
# ".config/calls"
|
||||
".local/share/calls" # call "records"
|
||||
# .local/share/folks # contacts?
|
||||
];
|
||||
secrets.".config/calls/sip-account.cfg" = ../../../secrets/common/gnome_calls_sip-account.cfg.bin;
|
||||
suggestedPrograms = [
|
||||
"feedbackd" # needs `phone-incoming-call`, in particular
|
||||
];
|
||||
|
||||
services.gnome-calls = {
|
||||
# TODO: prevent gnome-calls from daemonizing when started manually
|
||||
description = "gnome-calls daemon to monitor incoming SIP calls";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||
serviceConfig = {
|
||||
# add --verbose for more debugging
|
||||
ExecStart = "${cfg.package}/bin/gnome-calls --daemon";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
environment.G_MESSAGES_DEBUG = "all";
|
||||
};
|
||||
};
|
||||
programs.calls = lib.mkIf cfg.enabled {
|
||||
enable = true;
|
||||
};
|
||||
}
|
@@ -24,7 +24,7 @@ let
|
||||
# frees us from webkit_4_1, in turn.
|
||||
enableBackend = false;
|
||||
gvfs = pkgs.gvfs.override {
|
||||
# saves 20 minutes of build time, for unused feature
|
||||
# saves 20 minutes of build time and cross issues, for unused feature
|
||||
samba = null;
|
||||
};
|
||||
};
|
||||
|
@@ -27,7 +27,7 @@ conky.config = {
|
||||
default_shade_color = '#beebe5',
|
||||
default_outline_color = '#beebe5',
|
||||
|
||||
font = 'Sxmo:size=8',
|
||||
font = 'sans-serif:size=8',
|
||||
use_xft = true,
|
||||
|
||||
default_color = '#ffffff',
|
||||
@@ -37,14 +37,14 @@ conky.config = {
|
||||
|
||||
-- texeci <interval_sec> <cmd>: run the command periodically, _in a separate thread_ so as not to block rendering
|
||||
conky.text = [[
|
||||
${color1}${shadecolor 707070}${font Sxmo:size=50:style=Bold}${alignc}${exec date +"%H:%M"}${font}
|
||||
${color2}${shadecolor a4d7d0}${font Sxmo:size=20}${alignc}${exec date +"%a %d %b"}${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}
|
||||
|
||||
|
||||
${color1}${shadecolor}${font Sxmo:size=22:style=Bold}${alignc}${exec @bat@ }${font}
|
||||
${color1}${shadecolor}${font Sxmo:size=20:style=Bold}${alignc}${texeci 600 @weather@ }${font}
|
||||
${color1}${shadecolor}${font sans-serif:size=22:style=Bold}${alignc}${exec @bat@ }${font}
|
||||
${color1}${shadecolor}${font sans-serif:size=20:style=Bold}${alignc}${texeci 600 @weather@ }${font}
|
||||
|
||||
|
||||
${color2}${shadecolor a4d7d0}${font Sxmo:size=16}${alignc}⇅ ${downspeedf wlan0}K/s${font}
|
||||
${font Sxmo:size=16}${alignc}☵ $memperc% $cpu%${font}
|
||||
${color2}${shadecolor a4d7d0}${font sans-serif:size=16}${alignc}⇅ ${downspeedf wlan0}K/s${font}
|
||||
${font sans-serif:size=16}${alignc}☵ $memperc% $cpu%${font}
|
||||
]]
|
34
hosts/common/programs/conky/default.nix
Normal file
34
hosts/common/programs/conky/default.nix
Normal file
@@ -0,0 +1,34 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
sane.programs.conky = {
|
||||
fs.".config/conky/conky.conf".symlink.target =
|
||||
let
|
||||
battery_estimate = pkgs.static-nix-shell.mkBash {
|
||||
pname = "battery_estimate";
|
||||
src = ./.;
|
||||
};
|
||||
in pkgs.substituteAll {
|
||||
src = ./conky.conf;
|
||||
bat = "${battery_estimate}/bin/battery_estimate";
|
||||
weather = "timeout 20 ${pkgs.sane-weather}/bin/sane-weather";
|
||||
};
|
||||
|
||||
services.conky = {
|
||||
description = "conky dynamic desktop background";
|
||||
wantedBy = [ "default.target" ];
|
||||
# XXX: should be part of graphical-session.target, but whatever mix of greetd/sway
|
||||
# i'm using means that target's never reached...
|
||||
# wantedBy = [ "graphical-session.target" ];
|
||||
# partOf = [ "graphical-session.target" ];
|
||||
|
||||
serviceConfig.ExecStart = "${config.sane.programs.conky.package}/bin/conky";
|
||||
serviceConfig.Type = "simple";
|
||||
serviceConfig.Restart = "on-failure";
|
||||
serviceConfig.RestartSec = "10s";
|
||||
# serviceConfig.Slice = "session.slice";
|
||||
|
||||
# don't start conky until after sway
|
||||
preStart = ''test -n "$SWAYSOCK"'';
|
||||
};
|
||||
};
|
||||
}
|
@@ -5,18 +5,22 @@
|
||||
./aerc.nix
|
||||
./alacritty.nix
|
||||
./assorted.nix
|
||||
./calls.nix
|
||||
./cantata.nix
|
||||
./chatty.nix
|
||||
./conky
|
||||
./cozy.nix
|
||||
./dino.nix
|
||||
./element-desktop.nix
|
||||
./epiphany.nix
|
||||
./evince.nix
|
||||
./feedbackd.nix
|
||||
./firefox.nix
|
||||
./fontconfig.nix
|
||||
./fractal.nix
|
||||
./fwupd.nix
|
||||
./g4music.nix
|
||||
./gajim.nix
|
||||
./git.nix
|
||||
./gnome-feeds.nix
|
||||
./gnome-keyring.nix
|
||||
@@ -26,11 +30,11 @@
|
||||
./helix.nix
|
||||
./imagemagick.nix
|
||||
./jellyfin-media-player.nix
|
||||
./kitty
|
||||
./komikku.nix
|
||||
./koreader
|
||||
./libreoffice.nix
|
||||
./lemoa.nix
|
||||
./mako.nix
|
||||
./megapixels.nix
|
||||
./mepo.nix
|
||||
./mopidy.nix
|
||||
@@ -40,14 +44,18 @@
|
||||
./newsflash.nix
|
||||
./nheko.nix
|
||||
./nix-index.nix
|
||||
./ntfy-sh.nix
|
||||
./obsidian.nix
|
||||
./offlineimap.nix
|
||||
./playerctl.nix
|
||||
./rhythmbox.nix
|
||||
./ripgrep.nix
|
||||
./sfeed.nix
|
||||
./splatmoji.nix
|
||||
./steam.nix
|
||||
./stepmania.nix
|
||||
./sublime-music.nix
|
||||
./swaynotificationcenter.nix
|
||||
./tangram.nix
|
||||
./tuba.nix
|
||||
./vlc.nix
|
||||
|
@@ -9,7 +9,61 @@
|
||||
# - ensure the other party is in your roster
|
||||
# - open a DM with the party
|
||||
# - click the phone icon at top (only visible if other party is in your roster)
|
||||
{ ... }:
|
||||
#
|
||||
# dino can be autostarted on login -- useful to ensure that i always receive calls and notifications --
|
||||
# but at present it has no "start in tray" type of option: it must render a window.
|
||||
#
|
||||
# outstanding bugs:
|
||||
# - mic is sometimes disabled at call start despite presenting as enabled
|
||||
# - fix is to toggle it off -> on in the Dino UI
|
||||
# - default mic gain is WAY TOO MUCH (heavily distorted)
|
||||
# - TODO: dino should have more optimal niceness/priority to ensure it can process its buffers
|
||||
#
|
||||
# probably fixed:
|
||||
# - once per 1-2 minutes dino will temporarily drop mic input:
|
||||
# - `rtp-WRNING: plugin.vala:148: Warning in pipeline: Can't record audio fast enough
|
||||
# - this was *partially* fixed by bumping the pipewire mic buffer to 2048 samples (from ~512)
|
||||
# - this was further fixed by setting PULSE_LATENCY_MSEC=20.
|
||||
# - possibly Dino should be updated internally: `info.rate / 100` -> `info.rate / 50`.
|
||||
# - i think that affects the batching for echo cancellation, adaptive gain control, etc.
|
||||
#
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.dino;
|
||||
in
|
||||
{
|
||||
sane.programs.dino.persist.private = [ ".local/share/dino" ];
|
||||
sane.programs.dino = {
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
persist.private = [ ".local/share/dino" ];
|
||||
|
||||
services.dino = {
|
||||
description = "auto-start and maintain dino XMPP connection";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/dino";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
|
||||
# audio buffering; see: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#pipewire-buffering-explained>
|
||||
# dino defaults to 10ms mic buffer, which causes underruns, which Dino handles *very* poorly
|
||||
# as in, the other end of the call will just not receive sound from us for a couple seconds.
|
||||
# pipewire uses power-of-two buffering for the mic itself. that would put us at 21.33 ms, but this env var supports only whole numbers (21ms ends up not power-of-two).
|
||||
# also, Dino's likely still doing things in 10ms batches internally anyway.
|
||||
environment.PULSE_LATENCY_MSEC = "20";
|
||||
|
||||
# note that debug logging during calls produces so much journal spam that it pegs the CPU and causes dropped audio
|
||||
# environment.G_MESSAGES_DEBUG = "all";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,3 +1,9 @@
|
||||
# debugging tips:
|
||||
# - if element opens but does not render:
|
||||
# - `element-desktop --disable-gpu --in-process-gpu`
|
||||
# - <https://github.com/vector-im/element-desktop/issues/1029#issuecomment-1632688224>
|
||||
# - `rm -rf ~/.config/Element/GPUCache`
|
||||
# - <https://github.com/NixOS/nixpkgs/issues/244486>
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.element-desktop = {
|
||||
|
114
hosts/common/programs/feedbackd.nix
Normal file
114
hosts/common/programs/feedbackd.nix
Normal file
@@ -0,0 +1,114 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.feedbackd;
|
||||
in
|
||||
{
|
||||
sane.programs.feedbackd = {
|
||||
package = pkgs.rmDbusServices pkgs.feedbackd;
|
||||
|
||||
configOption = with lib; mkOption {
|
||||
type = types.submodule {
|
||||
options.proxied = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
whether to use a sound theme in which common application events are muted
|
||||
with the intent that a proxy (notification daemon) with knowledge of this
|
||||
modification will "speak" on behalf of all applications.
|
||||
'';
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
};
|
||||
|
||||
# N.B.: feedbackd will load ~/.config/feedbackd/themes/default.json by default
|
||||
# - but using that would forbid `parent-theme = "default"`
|
||||
# the default theme ships support for these events:
|
||||
# - alarm-clock-elapsed
|
||||
# - battery-caution
|
||||
# - bell-terminal
|
||||
# - button-pressed
|
||||
# - button-released
|
||||
# - camera-focus
|
||||
# - camera-shutter
|
||||
# - message-missed-email
|
||||
# - message-missed-instant
|
||||
# - message-missed-notification
|
||||
# - message-missed-sms
|
||||
# - message-new-email
|
||||
# - message-new-instant
|
||||
# - message-new-sms
|
||||
# - message-sent-instant
|
||||
# - phone-failure
|
||||
# - phone-hangup
|
||||
# - phone-incoming-call
|
||||
# - phone-missed-call
|
||||
# - phone-outgoing-busy
|
||||
# - screen-capture
|
||||
# - theme-demo
|
||||
# - timeout-completed
|
||||
# - window-close
|
||||
fs.".config/feedbackd/themes/proxied.json".symlink.text = builtins.toJSON {
|
||||
name = "proxied";
|
||||
parent-theme = "default";
|
||||
profiles = [
|
||||
{
|
||||
name = "full";
|
||||
feedbacks = [
|
||||
# forcibly disable normal events which we'd prefer for the notification daemon (e.g. swaync) to handle
|
||||
{
|
||||
event-name = "message-new-instant";
|
||||
type = "Dummy";
|
||||
}
|
||||
{
|
||||
event-name = "proxied-message-new-instant";
|
||||
type = "Sound";
|
||||
effect = "message-new-instant";
|
||||
}
|
||||
# re-define sounds from the default theme which we'd like to pass through w/o proxying.
|
||||
# i guess this means i'm not inheriting the default theme :|
|
||||
{
|
||||
event-name = "phone-incoming-call";
|
||||
type = "Sound";
|
||||
effect = "phone-incoming-call";
|
||||
}
|
||||
{
|
||||
event-name = "alarm-clock-elapsed";
|
||||
type = "Sound";
|
||||
effect = "alarm-clock-elapsed";
|
||||
}
|
||||
{
|
||||
event-name = "timeout-completed";
|
||||
type = "Sound";
|
||||
effect = "complete";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.feedbackd = {
|
||||
description = "feedbackd audio/vibration/led controller";
|
||||
wantedBy = [ "default.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/libexec/feedbackd";
|
||||
Type = "simple";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
environment = {
|
||||
G_MESSAGES_DEBUG = "all";
|
||||
} // (lib.optionalAttrs cfg.config.proxied {
|
||||
FEEDBACK_THEME = "/home/colin/.config/feedbackd/themes/proxied.json";
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
services.udev.packages = lib.mkIf cfg.enabled [
|
||||
# ships udev rules for `feedbackd` group to be able to control vibrator and LEDs
|
||||
cfg.package
|
||||
];
|
||||
users.groups = lib.mkIf cfg.enabled {
|
||||
feedbackd = {};
|
||||
};
|
||||
}
|
@@ -1,15 +1,68 @@
|
||||
# to preview fonts:
|
||||
# - `font-manager` (gui)
|
||||
# - useful to determine official name; codepoint support
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
# nerdfonts takes popular open fonts and patches them to support a wider range of glyphs, notably emoji.
|
||||
# any nerdfonts font includes icons such as these:
|
||||
# - (battery charging)
|
||||
# - (brightness)
|
||||
# - (gps / crosshairs)
|
||||
# - (music note)
|
||||
# - (message bubble)
|
||||
# - (phone)
|
||||
# - (weather/sun-behind-clouds)
|
||||
# used particularly by sxmo utilities, but also a few of my own (e.g. conky)
|
||||
#
|
||||
# nerdfonts is very heavy. each font is 20-900 MiB (2 MiB per "variation")
|
||||
# lots of redundant data inside there, but no deduplication except whatever nix or the fs does implicitly.
|
||||
wantedNerdfonts = [
|
||||
# used explicitly by SXMO
|
||||
# "DejaVuSansMono" # 25 MiB
|
||||
# good terminal/coding font. grab via nerdfonts for more emoji/unicode support
|
||||
"Hack" # 26 MiB
|
||||
"Noto" # 861 MiB
|
||||
];
|
||||
nerdfontPkgs = builtins.map
|
||||
(f: pkgs.nerdfonts.override { fonts = [ f ]; })
|
||||
wantedNerdfonts;
|
||||
in
|
||||
{
|
||||
fonts = lib.mkIf config.sane.programs.fontconfig.enabled {
|
||||
fontconfig.enable = true;
|
||||
fontconfig.defaultFonts = {
|
||||
emoji = [ "Font Awesome 6 Free" "Noto Color Emoji" ];
|
||||
monospace = [ "Hack" ];
|
||||
serif = [ "DejaVu Serif" ];
|
||||
sansSerif = [ "DejaVu Sans" ];
|
||||
emoji = [
|
||||
"Noto Color Emoji"
|
||||
"Font Awesome 6 Free"
|
||||
"Font Awesome 6 Brands"
|
||||
];
|
||||
monospace = [
|
||||
"Hack Nerd Font Propo"
|
||||
# "DejaVuSansM Nerd Font Propo"
|
||||
"NotoMono Nerd Font Propo"
|
||||
];
|
||||
serif = [
|
||||
"NotoSerif Nerd Font"
|
||||
"DejaVu Serif"
|
||||
];
|
||||
sansSerif = [
|
||||
"NotoSans Nerd Font"
|
||||
"DejaVu Sans"
|
||||
];
|
||||
};
|
||||
#vvv enables dejavu_fonts, freefont_ttf, gyre-fonts, liberation_ttf, unifont, noto-fonts-emoji
|
||||
enableDefaultPackages = true;
|
||||
packages = with pkgs; [ font-awesome noto-fonts-emoji hack-font ];
|
||||
enableDefaultPackages = false;
|
||||
packages = with pkgs; [
|
||||
# TODO: reduce this font set.
|
||||
# - probably need only one of dejavu/freefont/liberation
|
||||
dejavu_fonts # 10 MiB; DejaVu {Sans,Serif,Sans Mono,Math TeX Gyre}; also available as a NerdFonts (Sans Mono only)
|
||||
font-awesome # 2 MiB; Font Awesome 6 {Free,Brands}
|
||||
freefont_ttf # 11 MiB; Free{Mono,Sans,Serif}
|
||||
gyre-fonts # 4 MiB; Tex Gyre *; ttf substitutes for standard PostScript fonts
|
||||
# hack-font # 1 MiB; Hack; also available as a NerdFonts
|
||||
liberation_ttf # 4 MiB; Liberation {Mono,Sans,Serif}; also available as a NerdFonts
|
||||
noto-fonts-color-emoji # 10 Mib; Noto Color Emoji
|
||||
unifont # 16 MiB; Unifont; provides LOTS of unicode coverage
|
||||
] ++ nerdfontPkgs;
|
||||
};
|
||||
}
|
||||
|
@@ -1,12 +1,41 @@
|
||||
{ pkgs, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.fractal;
|
||||
in
|
||||
{
|
||||
sane.programs.fractal = {
|
||||
package = pkgs.fractal-nixified;
|
||||
# package = pkgs.fractal-latest;
|
||||
package = pkgs.fractal-next;
|
||||
# package = pkgs.fractal-next;
|
||||
|
||||
# XXX by default fractal stores its state in ~/.local/share/stable/<UUID>.
|
||||
persist.private = [ ".local/share/stable" ];
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
persist.private = [
|
||||
# XXX by default fractal stores its state in ~/.local/share/<build-profile>/<UUID>.
|
||||
".local/share/hack" # for debug-like builds
|
||||
".local/share/stable" # for normal releases
|
||||
];
|
||||
|
||||
suggestedPrograms = [ "gnome-keyring" ];
|
||||
|
||||
services.fractal = {
|
||||
description = "auto-start and maintain fractal Matrix connection";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/fractal";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
# environment.G_MESSAGES_DEBUG = "all";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
13
hosts/common/programs/gajim.nix
Normal file
13
hosts/common/programs/gajim.nix
Normal file
@@ -0,0 +1,13 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.gajim = {
|
||||
persist.private = [
|
||||
# avatars, thumbnails...
|
||||
".cache/gajim"
|
||||
# sqlite database labeled "settings". definitely includes UI theming
|
||||
".config/gajim"
|
||||
# omemo keys, downloads, logs
|
||||
".local/share/gajim"
|
||||
];
|
||||
};
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
# vim:ft=kitty
|
||||
|
||||
## name: PaperColor Dark
|
||||
## author: Nikyle Nguyen
|
||||
## license: MIT
|
||||
## blurb: Dark color scheme inspired by Google's Material Design
|
||||
|
||||
# special
|
||||
foreground #d0d0d0
|
||||
background #1c1c1c
|
||||
cursor #d0d0d0
|
||||
cursor_text_color background
|
||||
|
||||
# black
|
||||
color0 #1c1c1c
|
||||
color8 #585858
|
||||
|
||||
# red
|
||||
color1 #af005f
|
||||
color9 #5faf5f
|
||||
|
||||
# green
|
||||
# "color2" is the green color used by ls to indicate executability
|
||||
# both as text color
|
||||
# or as bg color when the text is blue (color4)
|
||||
color2 #246a28
|
||||
color10 #2df200
|
||||
|
||||
# yellow
|
||||
color3 #d7af5f
|
||||
color11 #af87d7
|
||||
|
||||
# blue
|
||||
color4 #78c6ef
|
||||
color12 #ffaf00
|
||||
|
||||
# magenta
|
||||
color5 #808080
|
||||
color13 #ff5faf
|
||||
|
||||
# cyan
|
||||
color6 #d7875f
|
||||
color14 #00afaf
|
||||
|
||||
# white
|
||||
color7 #d0d0d0
|
||||
color15 #5f8787
|
@@ -1,73 +0,0 @@
|
||||
{ lib, ... }:
|
||||
|
||||
{
|
||||
sane.programs.kitty = {
|
||||
fs.".config/kitty/kitty.conf".symlink.text = ''
|
||||
# docs: https://sw.kovidgoyal.net/kitty/conf/
|
||||
# disable terminal bell (when e.g. you backspace too many times)
|
||||
enable_audio_bell no
|
||||
|
||||
map ctrl+n new_os_window_with_cwd
|
||||
include ${./PaperColor_dark.conf}
|
||||
'';
|
||||
env.TERMINAL = lib.mkDefault "kitty";
|
||||
};
|
||||
|
||||
# include ${pkgs.kitty-themes}/themes/PaperColor_dark.conf
|
||||
|
||||
# THEME CHOICES:
|
||||
# docs: https://github.com/kovidgoyal/kitty-themes
|
||||
# theme = "1984 Light"; # dislike: awful, harsh blues/teals
|
||||
# theme = "Adventure Time"; # dislike: harsh (dark)
|
||||
# theme = "Atom One Light"; # GOOD: light theme. all color combos readable. not a huge fan of the blue.
|
||||
# theme = "Belafonte Day"; # dislike: too low contrast for text colors
|
||||
# theme = "Belafonte Night"; # better: dark theme that's easy on the eyes. all combos readable. low contrast.
|
||||
# theme = "Catppuccin"; # dislike: a bit pale/low-contrast (dark)
|
||||
# theme = "Desert"; # mediocre: colors are harsh
|
||||
# theme = "Earthsong"; # BEST: dark theme. readable, good contrast. unique, but decent colors.
|
||||
# theme = "Espresso Libre"; # better: dark theme. readable, but meh colors
|
||||
# theme = "Forest Night"; # decent: very pastel. it's workable, but unconventional and muted/flat.
|
||||
# theme = "Gruvbox Material Light Hard"; # mediocre light theme.
|
||||
# theme = "kanagawabones"; # better: dark theme. colors are too background-y
|
||||
# theme = "Kaolin Dark"; # dislike: too dark
|
||||
# theme = "Kaolin Breeze"; # mediocre: not-too-harsh light theme, but some parts are poor contrast
|
||||
# theme = "Later This Evening"; # mediocre: not-too-harsh dark theme, but cursor is poor contrast
|
||||
# theme = "Material"; # decent: light theme, few colors.
|
||||
# theme = "Mayukai"; # decent: not-too-harsh dark theme. the teal is a bit straining
|
||||
# theme = "Nord"; # mediocre: pale background, low contrast
|
||||
# theme = "One Half Light"; # better: not-too-harsh light theme. contrast could be better
|
||||
# theme = "PaperColor Dark"; # BEST: dark theme, very readable still the colors are background-y
|
||||
# theme = "Parasio Dark"; # dislike: too low contrast
|
||||
# theme = "Pencil Light"; # better: not-too-harsh light theme. decent contrast.
|
||||
# theme = "Pnevma"; # dislike: too low contrast
|
||||
# theme = "Piatto Light"; # better: readable light theme. pleasing colors. powerline prompt is hard to read.
|
||||
# theme = "Rosé Pine Dawn"; # GOOD: light theme. all color combinations are readable. it is very mild -- may need to manually tweak contrast. tasteful colors
|
||||
# theme = "Rosé Pine Moon"; # GOOD: dark theme. tasteful colors. but background is a bit intense
|
||||
# theme = "Sea Shells"; # mediocre. not all color combos are readable
|
||||
# theme = "Solarized Light"; # mediocre: not-too-harsh light theme; GREAT background; but some colors are low contrast
|
||||
# theme = "Solarized Dark Higher Contrast"; # better: dark theme, decent colors
|
||||
# theme = "Sourcerer"; # mediocre: ugly colors
|
||||
# theme = "Space Gray"; # mediocre: too muted
|
||||
# theme = "Space Gray Eighties"; # better: all readable, decent colors
|
||||
# theme = "Spacemacs"; # mediocre: too muted
|
||||
# theme = "Spring"; # mediocre: readable light theme, but the teal is ugly.
|
||||
# theme = "Srcery"; # better: highly readable. colors are ehhh
|
||||
# theme = "Substrata"; # decent: nice colors, but a bit flat.
|
||||
# theme = "Sundried"; # mediocre: the solar text makes me squint
|
||||
# theme = "Symfonic"; # mediocre: the dark purple has low contrast to the black bg.
|
||||
# theme = "Tango Light"; # dislike: teal is too grating
|
||||
# theme = "Tokyo Night Day"; # medicore: too muted
|
||||
# theme = "Tokyo Night"; # better: tasteful. a bit flat
|
||||
# theme = "Tomorrow"; # GOOD: all color combinations are readable. contrast is slightly better than Rose. on the blander side
|
||||
# theme = "Treehouse"; # dislike: the orange is harsh on my eyes.
|
||||
# theme = "Urple"; # dislike: weird palette
|
||||
# theme = "Warm Neon"; # decent: not-too-harsh dark theme. the green is a bit unattractive
|
||||
# theme = "Wild Cherry"; # GOOD: dark theme: nice colors. a bit flat
|
||||
# theme = "Xcodedark"; # dislike: bad palette
|
||||
# theme = "citylights"; # decent: dark theme. some parts have just a bit low contrast
|
||||
# theme = "neobones_light"; # better light theme. the background is maybe too muted
|
||||
# theme = "vimbones";
|
||||
# theme = "zenbones_dark"; # mediocre: readable, but meh colors
|
||||
# theme = "zenbones_light"; # decent: light theme. all colors are readable. contrast is passable but not excellent. highlight color is BAD
|
||||
# theme = "zenwritten_dark"; # mediocre: looks same as zenbones_dark
|
||||
}
|
67
hosts/common/programs/mako.nix
Normal file
67
hosts/common/programs/mako.nix
Normal file
@@ -0,0 +1,67 @@
|
||||
# config docs:
|
||||
# - `man 5 mako`
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
sane.programs.mako = {
|
||||
# we control mako as a systemd service, so have dbus not automatically activate it.
|
||||
package = pkgs.rmDbusServices pkgs.mako;
|
||||
fs.".config/mako/config".symlink.text = ''
|
||||
# notification interaction mapping
|
||||
# "on-touch" defaults to "dismiss", which isn't nice for touchscreens.
|
||||
on-button-left=invoke-default-action
|
||||
on-touch=invoke-default-action
|
||||
on-button-middle=dismiss-group
|
||||
|
||||
max-visible=3
|
||||
# layer:
|
||||
# - overlay: shows notifs above all else, even full-screen windows
|
||||
# - top: shows notifs above windows, but not if they're full-screen
|
||||
# - bottom; background
|
||||
layer=overlay
|
||||
# notifications can be grouped by:
|
||||
# - app-name
|
||||
# - app-icon
|
||||
# - summary
|
||||
# - body
|
||||
# possibly more: urgency, category, desktop-entry, ...
|
||||
# to group by multiple fields, join with `,`
|
||||
group-by=app-name
|
||||
|
||||
# BELOW IS SXMO DEFAULTS, modified very slightly.
|
||||
# TODO: apply theme colors!
|
||||
|
||||
# default-timeout=15000
|
||||
background-color=#ffffff
|
||||
text-color=#000000
|
||||
border-color=#000000
|
||||
# group-by=app-name
|
||||
|
||||
[urgency=low]
|
||||
# default-timeout=10000
|
||||
background-color=#222222
|
||||
text-color=#888888
|
||||
|
||||
[urgency=high]
|
||||
default-timeout=0
|
||||
background-color=#900000
|
||||
text-color=#ffffff
|
||||
background-color=#ff0000
|
||||
'';
|
||||
|
||||
# mako supports activation via dbus (i.e. the daemon will be started on-demand when a
|
||||
# dbus client tries to talk to it): that works out-of-the-box just by putting mako
|
||||
# on environment.packages, but then logs are blackholed.
|
||||
services.mako = {
|
||||
description = "mako desktop notification daemon";
|
||||
wantedBy = [ "default.target" ];
|
||||
# XXX: should be part of graphical-session.target, but whatever mix of greetd/sway
|
||||
# i'm using means that target's never reached...
|
||||
|
||||
serviceConfig.ExecStart = "${config.sane.programs.mako.package}/bin/mako";
|
||||
serviceConfig.Type = "simple";
|
||||
# mako will predictably fail if launched before the wayland server is fully initialized
|
||||
serviceConfig.Restart = "on-failure";
|
||||
serviceConfig.RestartSec = "10s";
|
||||
};
|
||||
};
|
||||
}
|
@@ -8,7 +8,6 @@
|
||||
{
|
||||
sane.programs.mpv = {
|
||||
package = pkgs.wrapMpv pkgs.mpv-unwrapped {
|
||||
youtubeSupport = false; #< XXX(2023/08/03): doesn't cross compile until next staging -> master merge
|
||||
scripts = with pkgs.mpvScripts; [
|
||||
mpris
|
||||
# uosc
|
||||
@@ -38,10 +37,12 @@
|
||||
"--add-flags" "--vo=sdl"
|
||||
];
|
||||
};
|
||||
persist.plaintext = [ ".config/mpv/watch_later" ];
|
||||
persist.plaintext = [ ".local/state/mpv/watch_later" ];
|
||||
fs.".config/mpv/input.conf".symlink.text = ''
|
||||
# let volume keys be interpreted by the system.
|
||||
# let volume/power keys be interpreted by the system.
|
||||
# this is important for sxmo.
|
||||
# mpv defaults is POWER = close, VOLUME_{UP,DOWN} = adjust application-level volume
|
||||
POWER ignore
|
||||
VOLUME_UP ignore
|
||||
VOLUME_DOWN ignore
|
||||
'';
|
||||
@@ -96,7 +97,8 @@
|
||||
ui_scale=1.0
|
||||
'';
|
||||
|
||||
mime.priority = 200; # default = 100; 200 means to yield to other apps
|
||||
# mime.priority = 200; # default = 100; 200 means to yield to other apps
|
||||
mime.priority = 50; # default = 100; 50 in order to take precedence over vlc.
|
||||
mime.associations."audio/flac" = "mpv.desktop";
|
||||
mime.associations."audio/mpeg" = "mpv.desktop";
|
||||
mime.associations."audio/x-vorbis+ogg" = "mpv.desktop";
|
||||
|
@@ -91,6 +91,10 @@ in
|
||||
env.EDITOR = "vim";
|
||||
# git claims it should use EDITOR, but it doesn't!
|
||||
env.GIT_EDITOR = "vim";
|
||||
mime.priority = 200; # default=100 => yield to other, more specialized applications
|
||||
mime.associations."application/schema+json" = "nvim.desktop";
|
||||
mime.associations."plain/text" = "nvim.desktop";
|
||||
mime.associations."text/markdown" = "nvim.desktop";
|
||||
};
|
||||
|
||||
programs.neovim = mkIf config.sane.programs.neovim.enabled {
|
||||
|
40
hosts/common/programs/ntfy-sh.nix
Normal file
40
hosts/common/programs/ntfy-sh.nix
Normal file
@@ -0,0 +1,40 @@
|
||||
# notification system, used especially to remotely wake moby
|
||||
# source: <https://github.com/binwiederhier/ntfy>
|
||||
# docs: <https://docs.ntfy.sh/>
|
||||
#
|
||||
# send a test notification with:
|
||||
# - `ntfy pub "https://ntfy.uninsane.org/$(cat ~/.config/ntfy-sh/topic)" test`
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.ntfy-sh;
|
||||
in
|
||||
{
|
||||
sane.programs.ntfy-sh = {
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
secrets.".config/ntfy-sh/topic" = ../../../secrets/common/ntfy-sh-topic.bin;
|
||||
|
||||
services.ntfy-sub = {
|
||||
description = "listen for push-notifications";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||
path = [ cfg.package ];
|
||||
script = ''
|
||||
topic=$(cat ~/.config/ntfy-sh/topic)
|
||||
ntfy sub "https://ntfy.uninsane.org:2587/$topic"
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
14
hosts/common/programs/playerctl.nix
Normal file
14
hosts/common/programs/playerctl.nix
Normal file
@@ -0,0 +1,14 @@
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
sane.programs.playerctl.services.playerctld = {
|
||||
description = "playerctl daemon to keep track of which MPRIS players were recently active";
|
||||
documentation = [ "https://github.com/altdesktop/playerctl/issues/161" ];
|
||||
wantedBy = [ "default.target" ];
|
||||
serviceConfig.ExecStart = "${config.sane.programs.playerctl.package}/bin/playerctld";
|
||||
# serviceConfig.Type = "dbus";
|
||||
# serviceConfig.BusName = "org.mpris.MediaPlayer2.Player";
|
||||
serviceConfig.Type = "simple"; # playerctl also supports a --daemon option, idk if that's better
|
||||
serviceConfig.Restart = "on-failure";
|
||||
serviceConfig.RestartSec = "10s";
|
||||
};
|
||||
}
|
27
hosts/common/programs/stepmania.nix
Normal file
27
hosts/common/programs/stepmania.nix
Normal file
@@ -0,0 +1,27 @@
|
||||
# configuration:
|
||||
# - things like calibration data live in ~/.stepmania-5.1/Save/Preferences.ini
|
||||
# - GlobalOffsetSeconds = difference between audio and video delay.
|
||||
# Hit F6 twice in-game to being auto calibration
|
||||
# Usually the result will be negative (i.e. the higher the latency of the pad, the more negative the offset)
|
||||
# - SoundDevice: use pacmd list-sources and select alsa_output.pci-xxxxx
|
||||
# - VisualOffset: if video is coming LATE, then use a negative number
|
||||
#
|
||||
# songs/packs:
|
||||
# - find pad packs:
|
||||
# - <https://docs.google.com/spreadsheets/d/1F1IURV1UAYiICTLhAOKIJfwUN1iG12ZOufHZuDKiP48/edit#gid=27038621>
|
||||
# - https://www.reddit.com/r/Stepmania/comments/aku3lb/best_pad_packs_on_stepmaniaonlinenet_or_elsewhere/
|
||||
# - https://fitupyourstyle.com/
|
||||
# allows search by difficulty
|
||||
# - dl packs from <https://stepmaniaonline.net>
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.stepmania = {
|
||||
persist.plaintext = [
|
||||
".stepmania-5.1/Cache" #< otherwise gotta index all the songs every launch
|
||||
".stepmania-5.1/Save"
|
||||
];
|
||||
fs.".stepmania-5.1/Courses".symlink.target = "/mnt/servo-media/games/stepmania/Courses";
|
||||
fs.".stepmania-5.1/Songs".symlink.target = "/mnt/servo-media/games/stepmania/Songs";
|
||||
# TODO: setup ~/.stepmania-5.1/Themes
|
||||
};
|
||||
}
|
380
hosts/common/programs/swaynotificationcenter.nix
Normal file
380
hosts/common/programs/swaynotificationcenter.nix
Normal file
@@ -0,0 +1,380 @@
|
||||
# <https://github.com/ErikReider/SwayNotificationCenter>
|
||||
# sway notification daemon
|
||||
# alternative to mako, dunst, etc
|
||||
#
|
||||
# debugging:
|
||||
# - `journalctl --user -u swaync`
|
||||
# - `G_MESSAGES_DEBUG=all swaync`
|
||||
# - reveal notification center: `swaync-client -t -sw`
|
||||
#
|
||||
# configuration:
|
||||
# - defaults: /run/current-system/etc/profiles/per-user/colin/etc/xdg/swaync/
|
||||
# - `man 5 swaync`
|
||||
# - view document tree: `GTK_DEBUG=interactive swaync` (`systemctl stop --user swaync` first)
|
||||
# - examples:
|
||||
# - thread: <https://github.com/ErikReider/SwayNotificationCenter/discussions/183>
|
||||
# - buttons-grid and menubar: <https://gist.github.com/JannisPetschenka/fb00eec3efea9c7fff8c38a01ce5d507>
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.swaynotificationcenter;
|
||||
fbcli-wrapper = pkgs.writeShellApplication {
|
||||
name = "swaync-fbcli";
|
||||
runtimeInputs = [
|
||||
config.sane.programs.feedbackd.package
|
||||
pkgs.procps # for pkill
|
||||
cfg.package
|
||||
];
|
||||
text = ''
|
||||
# if in Do Not Disturb, don't do any feedback
|
||||
# TODO: better solution is to actually make use of feedbackd profiles.
|
||||
# i.e. set profile to `quiet` when in DnD mode
|
||||
if [ "$SWAYNC_URGENCY" != "Critical" ] && [ "$(swaync-client --get-dnd)" = "true" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
# kill children if killed, to allow that killing this parent process will end the real fbcli call
|
||||
cleanup() {
|
||||
echo "aborting fbcli notification (PID $child)"
|
||||
pkill -P "$child"
|
||||
exit 0 # exit cleanly to avoid swaync alerting a script failure
|
||||
}
|
||||
trap cleanup SIGINT SIGQUIT SIGTERM
|
||||
|
||||
# feedbackd stops playback when the caller exits
|
||||
# and fbcli will exit immediately if it has no stdin.
|
||||
# so spoof a stdin:
|
||||
/bin/sh -c "true | fbcli $*" &
|
||||
child=$!
|
||||
wait
|
||||
'';
|
||||
};
|
||||
fbcli = "${fbcli-wrapper}/bin/swaync-fbcli";
|
||||
|
||||
# we do this because swaync's exec naively splits the command on space to produce its argv, rather than parsing the shell.
|
||||
# [ "pkill" "-f" "fbcli" "--event" ... ] -> breaks pkill
|
||||
# [ "pkill" "-f" "fbcli --event ..." ] -> is what we want
|
||||
fbcli-stop-wrapper = pkgs.writeShellApplication {
|
||||
name = "fbcli-stop";
|
||||
runtimeInputs = [
|
||||
pkgs.procps # for pkill
|
||||
];
|
||||
text = ''
|
||||
pkill -e -f "${fbcli} $*"
|
||||
'';
|
||||
};
|
||||
fbcli-stop = "${fbcli-stop-wrapper}/bin/fbcli-stop";
|
||||
|
||||
kill-singleton_ = pkgs.writeShellApplication {
|
||||
name = "kill-singleton";
|
||||
runtimeInputs = [
|
||||
pkgs.procps # for pgrep
|
||||
pkgs.gnugrep
|
||||
];
|
||||
text = ''
|
||||
pids=$(pgrep --full "$*" | tr '\n' ' ') || true
|
||||
# only act if there's exactly one pid
|
||||
if echo "$pids" | grep -Eq '^[0-9]+ ?$'; then
|
||||
kill "$pids"
|
||||
else
|
||||
echo "kill-singleton: skipping because multiple pids match: $pids"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
kill-singleton = "${kill-singleton_}/bin/kill-singleton";
|
||||
|
||||
systemctl-toggle = pkgs.writeShellApplication {
|
||||
name = "systemctl-toggle";
|
||||
runtimeInputs = [
|
||||
pkgs.systemd
|
||||
];
|
||||
text = ''
|
||||
if systemctl is-active "$@"; then
|
||||
systemctl stop "$@"
|
||||
else
|
||||
systemctl start "$@"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.programs.swaynotificationcenter = {
|
||||
configOption = with lib; mkOption {
|
||||
type = types.submodule {
|
||||
options = {
|
||||
backlight = mkOption {
|
||||
type = types.str;
|
||||
default = "intel_backlight";
|
||||
description = ''
|
||||
name of entry in /sys/class/backlight which indicates the primary backlight.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
};
|
||||
# prevent dbus from automatically activating swaync so i can manage it as a systemd service instead
|
||||
package = pkgs.rmDbusServices (pkgs.swaynotificationcenter.overrideAttrs (upstream: {
|
||||
# allow toggle buttons:
|
||||
patches = (upstream.patches or []) ++ [
|
||||
(pkgs.fetchpatch {
|
||||
url = "https://github.com/ErikReider/SwayNotificationCenter/pull/304.patch";
|
||||
name = "Add toggle button";
|
||||
hash = "sha256-bove2EXc5FZ5nN1X1FYOn3czCgHG03ibIAupJNoctiM=";
|
||||
})
|
||||
(pkgs.fetchpatch {
|
||||
url = "https://git.uninsane.org/colin/SwayNotificationCenter/commit/f5d9405e040fc42ea98dc4d37202c85728d0d4fd.patch";
|
||||
name = "toggleButton: change active field to be a command";
|
||||
hash = "sha256-Y8fiZbAP9yGOVU3rOkZKO8TnPPlrGpINWYGaqeeNzF0=";
|
||||
})
|
||||
];
|
||||
}));
|
||||
suggestedPrograms = [ "feedbackd" ];
|
||||
fs.".config/swaync/style.css".symlink.text = ''
|
||||
/* avoid black-on-black text that the default style ships */
|
||||
window {
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
button {
|
||||
color: rgb(128, 128, 128);
|
||||
}
|
||||
button.active {
|
||||
color: rgb(255, 255, 255);
|
||||
background-color: rgb(0, 110, 190);
|
||||
}
|
||||
'';
|
||||
fs.".config/swaync/config.json".symlink.text = builtins.toJSON {
|
||||
"$schema" = "/etc/xdg/swaync/configSchema.json";
|
||||
positionX = "right";
|
||||
positionY = "top";
|
||||
layer = "overlay";
|
||||
control-center-layer = "top";
|
||||
layer-shell = true;
|
||||
cssPriority = "user"; # "application"|"user". "user" in order to override the system gtk theme.
|
||||
control-center-margin-top = 0;
|
||||
control-center-margin-bottom = 0;
|
||||
control-center-margin-right = 0;
|
||||
control-center-margin-left = 0;
|
||||
notification-2fa-action = true;
|
||||
notification-inline-replies = false;
|
||||
notification-icon-size = 64;
|
||||
notification-body-image-height = 100;
|
||||
notification-body-image-width = 200;
|
||||
timeout = 30;
|
||||
timeout-low = 5;
|
||||
timeout-critical = 0;
|
||||
fit-to-screen = true; #< have notification center take full vertical screen space
|
||||
control-center-width = 400;
|
||||
control-center-height = 600;
|
||||
notification-window-width = 400;
|
||||
keyboard-shortcuts = true;
|
||||
image-visibility = "when-available";
|
||||
transition-time = 100;
|
||||
hide-on-clear = true; #< hide control center when clicking "clear all"
|
||||
hide-on-action = true;
|
||||
script-fail-notify = true;
|
||||
scripts = {
|
||||
# a script can match regex on these fields. only fired if all listed fields match:
|
||||
# - app-name
|
||||
# - desktop-entry
|
||||
# - summary
|
||||
# - body
|
||||
# - urgency (Low/Normal/Critical)
|
||||
# - category
|
||||
# additionally, the script can be run either on receipt or action:
|
||||
# - run-on = "receive" or "action"
|
||||
# when script is run, these env vars are available:
|
||||
# - SWAYNC_BODY
|
||||
# - SWAYNC_DESKTOP_ENTRY
|
||||
# - SWAYNC_URGENCY
|
||||
# - SWAYNC_TIME
|
||||
# - SWAYNC_APP_NAME
|
||||
# - SWAYNC_CATEGORY
|
||||
# - SWAYNC_REPLACES_ID
|
||||
# - SWAYNC_ID
|
||||
# - SWAYNC_SUMMARY
|
||||
incoming-im = {
|
||||
# trigger notification sound on behalf of these IM clients.
|
||||
app-name = "(Chats|Dino|discord|Element|Fractal)";
|
||||
body = "^(?!Incoming call).*$"; #< don't match Dino Incoming calls
|
||||
exec = "${fbcli} --event proxied-message-new-instant";
|
||||
};
|
||||
incoming-call = {
|
||||
app-name = "Dino";
|
||||
body = "^Incoming call$";
|
||||
exec = "${fbcli} --event phone-incoming-call -t 20";
|
||||
};
|
||||
incoming-call-acted-on = {
|
||||
# when the notification is clicked, stop sounding the ringer
|
||||
app-name = "Dino";
|
||||
body = "^Incoming call$";
|
||||
run-on = "action";
|
||||
exec = "${fbcli-stop} --event phone-incoming-call -t 20";
|
||||
};
|
||||
timer-done = {
|
||||
# sxmo_timer.sh fires off notifications like "Done with 10m" when a 10minute timer completes.
|
||||
# it sends such a notification every second until dismissed
|
||||
app-name = "notify-send";
|
||||
summary = "^Done with .*$";
|
||||
# XXX: could use alarm-clock-elapsed, but that's got a duration > 1s
|
||||
# which isn't great for sxmo's 1s repeat.
|
||||
# TODO: maybe better to have sxmo only notify once, and handle this like with Dino's incoming call
|
||||
exec = "${fbcli} --event timeout-completed";
|
||||
};
|
||||
timer-done-acted-on = {
|
||||
# when the notification is clicked, kill whichever sxmo process is sending it
|
||||
app-name = "notify-send";
|
||||
summary = "^Done with .*$";
|
||||
run-on = "action";
|
||||
# process tree looks like:
|
||||
# - foot -T <...> /nix/store/.../sh /nix/store/.../.sxmo_timer.sh-wrapped timerrun <duration>
|
||||
# - /nix/store/.../sh /nix/store/.../.sxmo_timer.sh-wrapped timerrun duration
|
||||
# we want to match exactly one of those, reliably.
|
||||
# foot might not be foot, but alacritty, kitty, or any other terminal.
|
||||
exec = "${kill-singleton} ^[^ ]* ?[^ ]*sxmo_timer.sh(-wrapped)? timerrun";
|
||||
};
|
||||
};
|
||||
notification-visibility = {
|
||||
# match incoming notifications and decide if they should be visible.
|
||||
# map of rule-name => { criteria and effect };
|
||||
# keys:
|
||||
# - `state`: "ignored"|"muted"|"transient"|"enabled"
|
||||
# => which visibility to apply to matched notifications
|
||||
# => "ignored" behaves as if the notification was never sent.
|
||||
# => "muted" adds it to the sidebar & sets the notif indicator but doesn't display it on main display
|
||||
# - `override-urgency`: "unset"|"low"|"normal"|"critical"
|
||||
# => which urgency to apply to matched notifs
|
||||
# critera: each key is optional, value is regex; rule applies if *all* specified are matched
|
||||
# - `app-name`: string
|
||||
# - `desktop-entry`: string
|
||||
# - `summary`: string
|
||||
# - `body`: string
|
||||
# - `urgency`: "Low"|"Normal"|"Critical"
|
||||
# - `category`: string
|
||||
#
|
||||
# test rules by using `notify-send` (libnotify)
|
||||
sxmo-extraneous-daemons = {
|
||||
app-name = "notify-send";
|
||||
summary = "(sxmo_hook_lisgd|Autorotate) (Stopped|Started)";
|
||||
state = "ignored";
|
||||
};
|
||||
sxmo-extraneous-warnings = {
|
||||
app-name = "notify-send";
|
||||
# "Modem crashed! 30s recovery.": happens on sxmo_hook_postwake.sh (i.e. unlock)
|
||||
summary = "^Modem crashed.*$";
|
||||
state = "ignored";
|
||||
};
|
||||
sxmo-timer = {
|
||||
# force timer announcements to bypass DND
|
||||
app-name = "notify-send";
|
||||
summary = "^Done with .*$";
|
||||
override-urgency = "critical";
|
||||
};
|
||||
};
|
||||
widgets = [
|
||||
# what to show in the notification center (and in which order).
|
||||
# these are configurable further via `widget-config`.
|
||||
# besides these listed, there are general-purpose UI tools:
|
||||
# - label (show some text)
|
||||
# - buttons-grid (labels which trigger actions when clicked)
|
||||
# - menubar (tree of labels/actions)
|
||||
"title"
|
||||
"dnd"
|
||||
"inhibitors"
|
||||
"buttons-grid"
|
||||
"backlight"
|
||||
"volume"
|
||||
"mpris"
|
||||
"notifications"
|
||||
];
|
||||
widget-config = {
|
||||
backlight = {
|
||||
label = " ";
|
||||
device = cfg.config.backlight;
|
||||
};
|
||||
buttons-grid = {
|
||||
actions =
|
||||
# {
|
||||
# type = "toggle";
|
||||
# label = "feedbackd";
|
||||
# command = "${systemctl-toggle}/bin/systemctl-toggle --user feedbackd";
|
||||
# active = "${pkgs.systemd}/bin/systemctl is-active --user feedbackd.service";
|
||||
# }
|
||||
lib.optionals config.sane.programs.eg25-control.enabled [
|
||||
{
|
||||
type = "toggle";
|
||||
label = "gps";
|
||||
command = "/run/wrappers/bin/sudo ${systemctl-toggle}/bin/systemctl-toggle eg25-control-gps";
|
||||
active = "${pkgs.systemd}/bin/systemctl is-active eg25-control-gps.service";
|
||||
}
|
||||
] ++ [
|
||||
{
|
||||
type = "toggle";
|
||||
label = "vpn::hn";
|
||||
command = "/run/wrappers/bin/sudo ${systemctl-toggle}/bin/systemctl-toggle wg-quick-vpn-servo";
|
||||
active = "${pkgs.systemd}/bin/systemctl is-active wg-quick-vpn-servo.service";
|
||||
}
|
||||
] ++ lib.optionals config.sane.programs.calls.config.autostart [
|
||||
{
|
||||
type = "toggle";
|
||||
label = "SIP";
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user gnome-calls";
|
||||
active = "${pkgs.systemd}/bin/systemctl is-active --user gnome-calls";
|
||||
}
|
||||
] ++ lib.optionals config.sane.programs.dino.enabled [
|
||||
{
|
||||
type = "toggle";
|
||||
label = "XMPP"; # XMPP calls (jingle)
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user dino";
|
||||
active = "${pkgs.systemd}/bin/systemctl is-active --user dino";
|
||||
}
|
||||
] ++ lib.optionals config.sane.programs.fractal.enabled [
|
||||
{
|
||||
type = "toggle";
|
||||
label = "Matrix"; # Matrix messages
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user fractal";
|
||||
active = "${pkgs.systemd}/bin/systemctl is-active --user fractal";
|
||||
}
|
||||
];
|
||||
};
|
||||
dnd = {
|
||||
text = "Do Not Disturb";
|
||||
};
|
||||
inhibitors = {
|
||||
text = "Inhibitors";
|
||||
button-text = "Clear All";
|
||||
clear-all-button = true;
|
||||
};
|
||||
mpris = {
|
||||
image-size = 64;
|
||||
image-radius = 8;
|
||||
};
|
||||
title = {
|
||||
text = "Notifications";
|
||||
clear-all-button = true;
|
||||
button-text = "Clear All";
|
||||
};
|
||||
volume = {
|
||||
label = " ";
|
||||
};
|
||||
};
|
||||
};
|
||||
services.swaync = {
|
||||
# swaync ships its own service, but i want to add `environment` variables and flags for easier debugging.
|
||||
# seems that's not possible without defining an entire nix-native service (i.e. this).
|
||||
description = "Swaync desktop notification daemon";
|
||||
wantedBy = [ "default.target" ];
|
||||
serviceConfig.ExecStart = "${cfg.package}/bin/swaync";
|
||||
serviceConfig.Type = "simple";
|
||||
# serviceConfig.BusName = "org.freedesktop.Notifications";
|
||||
serviceConfig.Restart = "on-failure";
|
||||
serviceConfig.RestartSec = "10s";
|
||||
environment.G_MESSAGES_DEBUG = "all";
|
||||
};
|
||||
};
|
||||
|
||||
sane.programs.feedbackd.config = lib.mkIf cfg.enabled {
|
||||
# claim control over feedbackd: we'll proxy the sounds we want on behalf of notifying programs
|
||||
proxied = true;
|
||||
};
|
||||
}
|
@@ -1,4 +1,26 @@
|
||||
{ ... }:
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.tuba.suggestedPrograms = [ "gnome-keyring" ];
|
||||
sane.programs.tuba = {
|
||||
package = pkgs.tuba.overrideAttrs (upstream: {
|
||||
postInstall = (upstream.postInstall or "") + ''
|
||||
# ship a `tuba` alias to the actual tuba binary, since i can never remember its name
|
||||
ln -s $out/bin/dev.geopjr.Tuba $out/bin/tuba
|
||||
'';
|
||||
|
||||
preFixup = (upstream.preFixup or "") + ''
|
||||
# 2023/09/24: fix so i can upload media when creating a post.
|
||||
# see: <https://github.com/GeopJr/Tuba/issues/414#issuecomment-1732695845>
|
||||
gappsWrapperArgs+=(
|
||||
--prefix GDK_DEBUG , no-portals
|
||||
)
|
||||
'';
|
||||
# alternative to disabling portals is to remove the filters on FileDialogs.
|
||||
# done like so (but would want to apply to the other dialogs too)
|
||||
# postPatch = (upstream.postPatch or "") + ''
|
||||
# substituteInPlace src/Dialogs/ProfileEdit.vala \
|
||||
# --replace "default_filter = filter" ""
|
||||
# '';
|
||||
});
|
||||
suggestedPrograms = [ "gnome-keyring" ];
|
||||
};
|
||||
}
|
||||
|
@@ -24,7 +24,6 @@ let
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./p10k.nix
|
||||
./starship.nix
|
||||
];
|
||||
options = {
|
||||
@@ -34,11 +33,6 @@ in
|
||||
default = true;
|
||||
description = "show upcoming deadlines (from my PKM) upon shell init";
|
||||
};
|
||||
p10k = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "enable powerlevel10k prompt and prezto";
|
||||
};
|
||||
starship = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
@@ -55,10 +49,6 @@ in
|
||||
# but zsh will sometimes backup the history file and symlinking just the file messes things up
|
||||
".local/share/zsh"
|
||||
];
|
||||
persist.plaintext = [
|
||||
# cache gitstatus otherwise p10k fetches it from the net EVERY BOOT
|
||||
".cache/gitstatus"
|
||||
];
|
||||
|
||||
fs.".config/zsh/.zshrc".symlink.text = ''
|
||||
# zsh/prezto complains if zshrc doesn't exist or is empty;
|
||||
@@ -106,6 +96,9 @@ in
|
||||
# common typos
|
||||
"cd.." = "cd ..";
|
||||
"cd../" = "cd ../";
|
||||
# overcome poor defaults
|
||||
"lsof" = "lsof -P"; #< lsof: use port *numbers*, not names
|
||||
"tcpdump" = "tcpdump -n"; #< tcpdump: use port *numbers*, not names
|
||||
};
|
||||
setOptions = [
|
||||
# docs: `man zshoptions`
|
||||
|
@@ -1,75 +0,0 @@
|
||||
{ config, lib, pkgs, ...}:
|
||||
|
||||
let
|
||||
# powerlevel10k prompt config
|
||||
# p10k.zsh is the auto-generated config, and i overwrite those defaults here, below.
|
||||
p10k-overrides = ''
|
||||
# powerlevel10k launches a gitstatusd daemon to accelerate git prompt queries.
|
||||
# this keeps open file handles for any git repo i touch for 60 minutes (by default).
|
||||
# that prevents unmounting whatever device the git repo is on -- particularly problematic for ~/private.
|
||||
# i can disable gitstatusd and get slower fallback git queries:
|
||||
# - either universally
|
||||
# - or selectively by path
|
||||
# see: <https://github.com/romkatv/powerlevel10k/issues/246>
|
||||
typeset -g POWERLEVEL9K_VCS_DISABLED_DIR_PATTERN='(/home/colin/private/*|/home/colin/knowledge/*)'
|
||||
# typeset -g POWERLEVEL9K_DISABLE_GITSTATUS=true
|
||||
|
||||
# show user@host also when logged into the current machine.
|
||||
# default behavior is to show it only over ssh.
|
||||
typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_CONTENT_EXPANSION='$P9K_CONTENT'
|
||||
'';
|
||||
|
||||
prezto-init = ''
|
||||
source ${pkgs.zsh-autosuggestions}/share/zsh-autosuggestions/zsh-autosuggestions.zsh
|
||||
source ${pkgs.zsh-syntax-highlighting}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
|
||||
source ${pkgs.zsh-prezto}/share/zsh-prezto/init.zsh
|
||||
'';
|
||||
in {
|
||||
config = lib.mkIf config.sane.zsh.p10k {
|
||||
sane.programs.zsh = {
|
||||
# prezto = oh-my-zsh fork; controls prompt, auto-completion, etc.
|
||||
# see: https://github.com/sorin-ionescu/prezto
|
||||
# this file is auto-sourced by the prezto init.zsh script.
|
||||
# TODO: i should work to move away from prezto:
|
||||
# - it's FUCKING SLOW to initialize (that might also be powerlevel10k tho)
|
||||
# - it messes with my other `setopt`s
|
||||
fs.".config/zsh/.zpreztorc".symlink.text = ''
|
||||
zstyle ':prezto:*:*' color 'yes'
|
||||
zstyle ':prezto:module:utility' correct 'no' # prezto: don't setopt CORRECT
|
||||
|
||||
# modules (they ship with prezto):
|
||||
# ENVIRONMENT: configures jobs to persist after shell exit; other basic niceties
|
||||
# TERMINAL: auto-titles terminal (e.g. based on cwd)
|
||||
# EDITOR: configures shortcuts like Ctrl+U=undo, Ctrl+L=clear
|
||||
# HISTORY: `history-stat` alias, setopts for good history defaults
|
||||
# DIRECTORY: sets AUTO_CD, adds `d` alias to list directory stack, and `1`-`9` to cd that far back the stack. also overrides CLOBBER and some other options
|
||||
# SPECTRUM: helpers for term colors and styling. used by prompts? might be unnecessary
|
||||
# UTILITY: configures aliases like `ll`, `la`, disables globbing for things like rsync
|
||||
# adds aliases like `get` to fetch a file. also adds `http-serve` alias??
|
||||
# COMPLETION: tab completion. requires `utility` module prior to loading
|
||||
zstyle ':prezto:load' pmodule \
|
||||
'environment' \
|
||||
'terminal' \
|
||||
'editor' \
|
||||
'history' \
|
||||
'spectrum' \
|
||||
'utility' \
|
||||
'completion' \
|
||||
'prompt'
|
||||
|
||||
# default keymap. try also `vicmd` (vim normal mode, AKA "cmd mode") or `vi`.
|
||||
zstyle ':prezto:module:editor' key-bindings 'emacs'
|
||||
|
||||
zstyle ':prezto:module:prompt' theme 'powerlevel10k'
|
||||
|
||||
# disable `mv` confirmation (and `rm`, too, unfortunately)
|
||||
zstyle ':prezto:module:utility' safe-ops 'no'
|
||||
'';
|
||||
};
|
||||
|
||||
programs.zsh.interactiveShellInit = (builtins.readFile ./p10k.zsh)
|
||||
+ p10k-overrides
|
||||
+ prezto-init
|
||||
;
|
||||
};
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -28,21 +28,26 @@
|
||||
{ config, lib, sane-lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib.strings) hasSuffix removeSuffix;
|
||||
secretsForHost = host: let
|
||||
extraAttrsForPath = path: lib.optionalAttrs (sane-lib.path.isChild "guest" path && builtins.hasAttr "guest" config.users.users) {
|
||||
owner = "guest";
|
||||
};
|
||||
secretsInSrc = (
|
||||
if builtins.pathExists ../../secrets/${host} then
|
||||
sane-lib.enumerateFilePaths ../../secrets/${host}
|
||||
else
|
||||
[]
|
||||
);
|
||||
in sane-lib.joinAttrsets (
|
||||
map
|
||||
(path: lib.optionalAttrs (hasSuffix ".bin" path) (sane-lib.nameValueToAttrs {
|
||||
name = removeSuffix ".bin" path;
|
||||
(path: lib.optionalAttrs (lib.hasSuffix ".bin" path) (sane-lib.nameValueToAttrs {
|
||||
name = lib.removeSuffix ".bin" path;
|
||||
value = {
|
||||
sopsFile = ../../secrets/${host}/${path};
|
||||
format = "binary";
|
||||
} // (extraAttrsForPath path);
|
||||
}))
|
||||
(sane-lib.enumerateFilePaths ../../secrets/${host})
|
||||
secretsInSrc
|
||||
);
|
||||
in
|
||||
{
|
||||
|
@@ -16,10 +16,13 @@
|
||||
group = "users";
|
||||
extraGroups = [
|
||||
"dialout" # required for modem access (moby)
|
||||
"feedbackd"
|
||||
"export" # to read filesystem exports (servo)
|
||||
"feedbackd" # moby, so `fbcli` can control vibrator and LEDs
|
||||
"input" # for /dev/input/<xyz>: sxmo
|
||||
"media" # servo, for /var/lib/uninsane/media
|
||||
"networkmanager"
|
||||
"nixbuild"
|
||||
"systemd-journal" # allows to view other user's journals (esp system users)
|
||||
"transmission" # servo, to admin /var/lib/uninsane/media
|
||||
"video" # mobile; for LEDs & maybe for camera?
|
||||
"wheel"
|
||||
@@ -29,7 +32,7 @@
|
||||
# initial password is empty, in case anything goes wrong.
|
||||
# if `colin-passwd` (a password hash) is successfully found/decrypted, that becomes the password at boot.
|
||||
initialPassword = lib.mkDefault "";
|
||||
passwordFile = lib.mkIf (config.sops.secrets ? "colin-passwd") config.sops.secrets.colin-passwd.path;
|
||||
hashedPasswordFile = lib.mkIf (config.sops.secrets ? "colin-passwd") config.sops.secrets.colin-passwd.path;
|
||||
|
||||
shell = pkgs.zsh;
|
||||
|
||||
@@ -77,16 +80,18 @@
|
||||
# ".rustup"
|
||||
];
|
||||
|
||||
# fs.".cargo".symlink.target = "/tmp/colin-cargo";
|
||||
|
||||
# convenience
|
||||
fs."knowledge".symlink.target = "private/knowledge";
|
||||
fs."nixos".symlink.target = "dev/nixos";
|
||||
fs."Books/servo".symlink.target = "/mnt/servo-media/Books";
|
||||
fs."Videos/servo".symlink.target = "/mnt/servo-media/Videos";
|
||||
fs."Videos/servo-incomplete".symlink.target = "/mnt/servo-media/incomplete";
|
||||
# fs."Music/servo".symlink.target = "/mnt/servo-media/Music";
|
||||
fs."Pictures/servo-macros".symlink.target = "/mnt/servo-media/Pictures/macros";
|
||||
|
||||
# used by password managers, e.g. unix `pass`
|
||||
# TODO: move this to the specific programs which need it
|
||||
fs.".password-store".symlink.target = "knowledge/secrets/accounts";
|
||||
};
|
||||
}
|
||||
|
@@ -7,6 +7,8 @@
|
||||
./root.nix
|
||||
];
|
||||
|
||||
users.groups.media = {};
|
||||
|
||||
# Users are exactly these specified here;
|
||||
# old ones will be deleted (from /etc/passwd, etc) upon upgrade.
|
||||
users.mutableUsers = false;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{ config, lib, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# to add a new OVPN VPN:
|
||||
# - generate a privkey `wg genkey`
|
||||
@@ -8,14 +8,9 @@
|
||||
# - copy the Address, PublicKey, Endpoint from OVPN's config
|
||||
# N.B.: maximum interface name in Linux is 15 characters.
|
||||
let
|
||||
def-ovpn = name: { endpoint, publicKey, address }: {
|
||||
networking.wg-quick.interfaces."ovpnd-${name}" = {
|
||||
inherit address;
|
||||
privateKeyFile = config.sops.secrets."wg/ovpnd_${name}_privkey".path;
|
||||
dns = [
|
||||
"46.227.67.134"
|
||||
"192.165.9.158"
|
||||
];
|
||||
def-wg-vpn = name: { endpoint, publicKey, address, dns, privateKeyFile, extraOptions ? {} }: {
|
||||
networking.wg-quick.interfaces."${name}" = {
|
||||
inherit address privateKeyFile dns;
|
||||
peers = [
|
||||
{
|
||||
allowedIPs = [
|
||||
@@ -25,11 +20,38 @@ let
|
||||
inherit endpoint publicKey;
|
||||
}
|
||||
];
|
||||
# to start: `systemctl start wg-quick-ovpnd-${name}`
|
||||
# to start: `systemctl start wg-quick-${name}`
|
||||
autostart = false;
|
||||
} // extraOptions;
|
||||
};
|
||||
def-ovpn = name: { endpoint, publicKey, address }: def-wg-vpn "ovpnd-${name}" {
|
||||
inherit endpoint publicKey address;
|
||||
privateKeyFile = config.sops.secrets."wg/ovpnd_${name}_privkey".path;
|
||||
dns = [
|
||||
"46.227.67.134"
|
||||
"192.165.9.158"
|
||||
];
|
||||
};
|
||||
|
||||
# TODO: this should live in the same file as hosts/modules/wg-home.nix...
|
||||
def-servo = def-wg-vpn "vpn-servo" {
|
||||
endpoint = config.sane.hosts.by-name."servo".wg-home.endpoint;
|
||||
publicKey = config.sane.hosts.by-name."servo".wg-home.pubkey;
|
||||
address = [ config.sane.services.wg-home.ip ];
|
||||
dns = [
|
||||
config.sane.hosts.by-name."servo".wg-home.ip
|
||||
];
|
||||
privateKeyFile = config.networking.wireguard.interfaces.wg-home.privateKeyFile;
|
||||
extraOptions = {
|
||||
# wg-home and vpn-servo interfaces interfere with the result that when connected to both,
|
||||
# other wg-home users (lappy-hn, ...) aren't visible. disabling wg-home while the full
|
||||
# vpn-servo is active allows wg-home users to be reachable again
|
||||
preUp = "${pkgs.iproute2}/bin/ip link set wg-home down";
|
||||
postDown = "${pkgs.iproute2}/bin/ip link set wg-home up";
|
||||
};
|
||||
};
|
||||
in lib.mkMerge [
|
||||
(def-servo)
|
||||
(def-ovpn "us" {
|
||||
endpoint = "vpn31.prd.losangeles.ovpn.com:9929";
|
||||
publicKey = "VW6bEWMOlOneta1bf6YFE25N/oMGh1E1UFBCfyggd0k=";
|
||||
|
@@ -4,7 +4,6 @@
|
||||
imports = [
|
||||
./derived-secrets
|
||||
./gui
|
||||
./hardware
|
||||
./hostnames.nix
|
||||
./hosts.nix
|
||||
./nixcache.nix
|
||||
|
@@ -2,10 +2,12 @@
|
||||
{
|
||||
imports = [
|
||||
./gnome.nix
|
||||
./greetd.nix
|
||||
./gtk.nix
|
||||
./phosh.nix
|
||||
./sway
|
||||
./sxmo
|
||||
./theme
|
||||
];
|
||||
|
||||
sane.programs.guiApps = {
|
||||
@@ -17,20 +19,22 @@
|
||||
"tuiApps"
|
||||
] ++ [
|
||||
"alacritty" # terminal emulator
|
||||
"calls" # gnome calls (dialer/handler)
|
||||
# "celluloid" # mpv frontend
|
||||
"chatty" # matrix/xmpp/irc client
|
||||
"cozy" # audiobook player
|
||||
"dino" # XMPP client
|
||||
# "emote"
|
||||
"epiphany" # gnome's web browser
|
||||
"evince" # works on phosh
|
||||
"firefox"
|
||||
# "foliate" # e-book reader
|
||||
# "fractal" # matrix client
|
||||
"fractal" # matrix client
|
||||
"g4music" # local music player
|
||||
# "gnome.cheese"
|
||||
# "gnome-feeds" # RSS reader (with claimed mobile support)
|
||||
# "gnome.file-roller"
|
||||
# "gnome.gnome-maps" # works on phosh
|
||||
"gnome.gnome-maps"
|
||||
# "gnome-podcasts"
|
||||
# "gnome.gnome-system-monitor"
|
||||
# "gnome.gnome-terminal" # works on phosh
|
||||
@@ -44,7 +48,8 @@
|
||||
"mate.engrampa" # archive manager
|
||||
"mepo" # maps viewer
|
||||
"mpv"
|
||||
# "networkmanagerapplet"
|
||||
"networkmanagerapplet" # for nm-connection-editor: it's better than not having any gui!
|
||||
"ntfy-sh" # notification service
|
||||
# "newsflash"
|
||||
"pavucontrol"
|
||||
# "picard" # music tagging
|
||||
@@ -66,15 +71,14 @@
|
||||
"audacity"
|
||||
"blanket" # ambient noise generator
|
||||
"brave" # for the integrated wallet -- as a backup
|
||||
"cantata" # music player (mpd frontend)
|
||||
# "cantata" # music player (mpd frontend)
|
||||
# "chromium" # chromium takes hours to build. brave is chromium-based, distributed in binary form, so prefer it.
|
||||
"dino" # XMPP client
|
||||
"electrum"
|
||||
"element-desktop"
|
||||
# "font-manager" #< depends on webkitgtk4_0 (expensive to build)
|
||||
# "gajim" # XMPP client
|
||||
"font-manager"
|
||||
# "gajim" # XMPP client. cross build tries to import host gobject-introspection types (2023/09/01)
|
||||
"gimp" # broken on phosh
|
||||
"gnome.dconf-editor"
|
||||
# "gnome.dconf-editor"
|
||||
# "gnome.file-roller"
|
||||
"gnome.gnome-disk-utility"
|
||||
"gnome.nautilus" # file browser
|
||||
@@ -89,8 +93,8 @@
|
||||
"libreoffice" # TODO: replace with an office suite that uses saner packaging?
|
||||
"mumble"
|
||||
"nheko"
|
||||
"obsidian"
|
||||
"rhythmbox" # local music player
|
||||
# "obsidian"
|
||||
# "rhythmbox" # local music player
|
||||
"slic3r"
|
||||
"steam"
|
||||
"vlc"
|
||||
@@ -130,4 +134,13 @@
|
||||
"/var/lib/colord" # preserve color calibrations (?)
|
||||
"/var/lib/systemd/backlight" # backlight brightness
|
||||
];
|
||||
|
||||
hardware.opengl = lib.mkIf config.sane.programs.guiApps.enabled ({
|
||||
enable = true;
|
||||
driSupport = lib.mkDefault true;
|
||||
} // (lib.optionalAttrs pkgs.stdenv.isx86_64 {
|
||||
# for 32 bit applications
|
||||
# upstream nixpkgs forbids setting driSupport32Bit unless specifically x86_64 (so aarch64 isn't allowed)
|
||||
driSupport32Bit = lib.mkDefault true;
|
||||
}));
|
||||
}
|
||||
|
@@ -47,7 +47,6 @@ in
|
||||
# favorite-apps = [
|
||||
# "org.gnome.Nautilus.desktop"
|
||||
# "firefox.desktop"
|
||||
# "kitty.desktop"
|
||||
# # "org.gnome.Terminal.desktop"
|
||||
# ];
|
||||
# };
|
||||
|
128
hosts/modules/gui/greetd.nix
Normal file
128
hosts/modules/gui/greetd.nix
Normal file
@@ -0,0 +1,128 @@
|
||||
# greetd source/docs:
|
||||
# - <https://git.sr.ht/~kennylevinsen/greetd>
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
systemd-cat = "${pkgs.systemd}/bin/systemd-cat";
|
||||
runWithLogger = identifier: cmd: pkgs.writeShellScriptBin identifier ''
|
||||
echo "launching ${identifier}..." | ${systemd-cat} --identifier=${identifier}
|
||||
${cmd} 2>&1 | ${systemd-cat} --identifier=${identifier}
|
||||
'';
|
||||
cfg = config.sane.gui.greetd;
|
||||
in
|
||||
{
|
||||
options = with lib; {
|
||||
sane.gui.greetd.enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
sane.gui.greetd.session.command = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
name to use for the default session in syslog.
|
||||
'';
|
||||
};
|
||||
sane.gui.greetd.session.name = mkOption {
|
||||
default = "greetd-session";
|
||||
type = types.str;
|
||||
description = "name of session to use in logger";
|
||||
};
|
||||
sane.gui.greetd.session.user = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
# helpers for common things to layer on top of greetd
|
||||
sane.gui.greetd.sway.enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
use sway as a wayland compositor in which to host a graphical greeter like gtkgreet, phog, etc.
|
||||
'';
|
||||
};
|
||||
sane.gui.greetd.sway.greeterCmd = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
command for sway to `exec` that provides the actual graphical greeter.
|
||||
'';
|
||||
};
|
||||
sane.gui.greetd.sway.gtkgreet.enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
have sway launch gtkgreet instead of directly presenting a desktop.
|
||||
'';
|
||||
};
|
||||
sane.gui.greetd.sway.gtkgreet.session.command = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
command for gtkgreet to execute on successful authentication.
|
||||
'';
|
||||
};
|
||||
sane.gui.greetd.sway.gtkgreet.session.name = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
name to use for the default session in syslog and in the gtkgreet menu.
|
||||
note that this `sessionName` will become a binary on the user's PATH.
|
||||
'';
|
||||
};
|
||||
sane.gui.greetd.sway.gtkgreet.session.user = mkOption {
|
||||
type = types.str;
|
||||
default = "colin";
|
||||
description = ''
|
||||
name of user which one expects to login as.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable (lib.mkMerge [
|
||||
(lib.mkIf cfg.sway.enable {
|
||||
sane.gui.greetd.session = if cfg.sway.greeterCmd != null then {
|
||||
name = "sway-as-greeter";
|
||||
command = let
|
||||
swayAsGreeterConfig = pkgs.writeText "sway-as-greeter-config" ''
|
||||
exec ${cfg.sway.greeterCmd}
|
||||
'';
|
||||
in "${pkgs.sway}/bin/sway --debug --config ${swayAsGreeterConfig}";
|
||||
} else {
|
||||
name = "sway";
|
||||
user = lib.mkDefault "colin";
|
||||
command = "${pkgs.sway}/bin/sway --debug";
|
||||
};
|
||||
})
|
||||
(lib.mkIf cfg.sway.gtkgreet.enable (
|
||||
let
|
||||
inherit (cfg.sway.gtkgreet) session;
|
||||
sessionProvider = runWithLogger session.name session.command;
|
||||
in {
|
||||
# gtkgreet shows the --command argument in the UI
|
||||
# - so we want it to look nice (not a /nix/store/... path)
|
||||
# - to do that we put it in the user's PATH.
|
||||
sane.gui.greetd.sway.greeterCmd = "${pkgs.greetd.gtkgreet}/bin/gtkgreet --layer-shell --command ${session.name}";
|
||||
users.users.${session.user}.packages = [ sessionProvider ];
|
||||
}
|
||||
))
|
||||
|
||||
{
|
||||
services.greetd = {
|
||||
enable = true;
|
||||
|
||||
# i could have gtkgreet launch the session directly: but stdout/stderr gets dropped
|
||||
# settings.default_session.command = cfg.session.command;
|
||||
|
||||
# wrapper to launch with stdout/stderr redirected to system journal.
|
||||
settings.default_session.command = let
|
||||
launchWithLogger = runWithLogger cfg.session.name cfg.session.command;
|
||||
in "${launchWithLogger}/bin/${cfg.session.name}";
|
||||
};
|
||||
|
||||
# persisting fontconfig & mesa_shader_cache improves start time by ~5x
|
||||
users.users.greeter.home = "/var/lib/greeter";
|
||||
sane.persist.sys.plaintext = [
|
||||
{ user = "greeter"; group = "greeter"; path = "/var/lib/greeter/.cache/fontconfig"; }
|
||||
{ user = "greeter"; group = "greeter"; path = "/var/lib/greeter/.cache/mesa_shader_cache"; }
|
||||
];
|
||||
}
|
||||
]);
|
||||
}
|
@@ -180,8 +180,100 @@ let
|
||||
# other Tokyonight-* omitted
|
||||
};
|
||||
icon-theme = {
|
||||
# find icon themes via `nix-locate share/icons/Adwaita`
|
||||
# then determine the name here by building and `ls result/share/icons`
|
||||
# this misses quite a few icon themes that aren't Adwaita-based.
|
||||
# for those, try `nix-locate share/icons`?
|
||||
#
|
||||
# note that adwaita apps expect exactly the icon set provided by adwaita-icon-theme:
|
||||
# - most icon themes are supplementary to adwaita, rather than a full replacement.
|
||||
# - i.e. most themes, unless adwaita is also installed, will cause some missing icons inside apps.
|
||||
# - that's probably why so many themes here also symlink Adwaita
|
||||
# my accounting of "adwaita coverage" seems to be overoptimistic somehow
|
||||
# maybe some apps bundle adwaita themselves
|
||||
Adwaita = gnome.adwaita-icon-theme;
|
||||
HighContrast = gnome.gnome-themes-extra; # gtk-3.0
|
||||
Arc = arc-icon-theme; # 4.5/5, meh icon for "vertical ellipsis". 3/5 adwaita coverage
|
||||
elementary-xfce = elementary-xfce-icon-theme; # does not cross compile (2023/10/03)
|
||||
elementary-xfce-dark = elementary-xfce-icon-theme;
|
||||
elementary-xfce-darker = elementary-xfce-icon-theme;
|
||||
elementary-xfce-darkest = elementary-xfce-icon-theme;
|
||||
HighContrast = gnome.gnome-themes-extra; # 5/5. 5/5 adwaita coverage (4/5 cross)
|
||||
Humanity = humanity-icon-theme; # 5/5. 5/5 adwaita coverage (3.5/5 cross, unique in which icons work)
|
||||
Humanity-Dark = humanity-icon-theme;
|
||||
kora = kora-icon-theme;
|
||||
kora-light = kora-icon-theme;
|
||||
kora-light-panel = kora-icon-theme;
|
||||
kora-pgrey = kora-icon-theme;
|
||||
Numix = numix-icon-theme; # 4/5, meh icon for "back".
|
||||
Numix-Light = numix-icon-theme;
|
||||
Paper = paper-icon-theme; # 4/5, weird icon for "info". 5/5 adwaita coverage (3.5 cross, highly unique in which icons work)
|
||||
Paper-Mono-Dark = paper-icon-theme;
|
||||
Pop = pop-icon-theme; # 5/5. 2/5 adwaita coverage
|
||||
Tela-circle = tela-circle-icon-theme;
|
||||
Tela-circle-dark = tela-circle-icon-theme;
|
||||
Tela-circle-light = tela-circle-icon-theme;
|
||||
|
||||
# themes which don't symlink Adwaita
|
||||
BeautyLine = beauty-line-icon-theme; # 3.5/5. 4/5 adwaita coverage
|
||||
breeze = breeze-icons;
|
||||
breeze-dark = breeze-icons;
|
||||
Mint-X = cinnamon.mint-x-icons;
|
||||
# 10-ish other Mint-X variants omitted
|
||||
# cinnamon.mint-l-icons;
|
||||
# cinnamon.mint-y-icons;
|
||||
Colloid = colloid-icon-theme; # 4.5/5, thin. 5/5 adwaita coverage (3/5 cross)
|
||||
Colloid-dark = colloid-icon-theme;
|
||||
Colloid-light = colloid-icon-theme;
|
||||
bloom = deepin.deepin-icon-theme;
|
||||
# 4 other deepin editions omitted
|
||||
Dracula = dracula-icon-theme; # 4.5/5, a little thin. 4.5/5 adwaita coverage
|
||||
Faba = faba-icon-theme; # 4/5. 4/5 adwaita coverage
|
||||
Faba-Mono = faba-mono-icons;
|
||||
Faba-Mono-Dark = faba-mono-icons;
|
||||
Flat-Remix-Grey-Light = flat-remix-icon-theme; # 5/5. 5/5 adwaita coverage. builds on breeze, elementary
|
||||
# 20-ish other flat-remix editions omitted
|
||||
Fluent = fluent-icon-theme; # 5/5, though thin. 5/5 adwaita coverage (3/5 cross)
|
||||
Fluent-dark = fluent-icon-theme;
|
||||
gnome = gnome-icon-theme; # 3/5, icons are colored. 3/5 adwaita coverage
|
||||
hicolor = hicolor-icon-theme; # 2/5 adwaita coverage; using this forces application builtin icons
|
||||
la-capitaine-icon-theme = la-capitaine-icon-theme; # 4.5/5. 4.5/5 adwaita coverage. builds upon elementary
|
||||
Luna = luna-icons;
|
||||
# 5 other Luna variants omitted
|
||||
maia = maia-icon-theme; # 3/5, icons are colored. 2/5 adwaita coverage
|
||||
maia-dark = maia-icon-theme;
|
||||
# mate.mate-icon-theme-faenza
|
||||
mate = mate.mate-icon-theme; # 4.5/5. 4/5 adwaita coverage
|
||||
menta = mate.mate-icon-theme;
|
||||
Moka = moka-icon-theme; # 3/5, icons are colored. 3/5 adwaita coverage
|
||||
# nixos-icons;
|
||||
Nordzy = nordzy-icon-theme; # 5/5, thin. 5/5 adwaita coverage (3/5 cross)
|
||||
# 10-ish Nordzy editions omitted
|
||||
# numix-icon-theme-circle
|
||||
# numix-icon-theme-square
|
||||
oomox-gruvbox-dark = gruvbox-dark-icons-gtk;
|
||||
Oranchelo = oranchelo-icon-theme;
|
||||
# 3 other oranchelo editions omitted
|
||||
elementary = pantheon.elementary-icon-theme; # 4.5/5. 4.5/5 adwaita coverage
|
||||
Papirus = papirus-icon-theme; # 5/5. 5/5 adwaita coverage
|
||||
# 4 other Papirus editions omitted
|
||||
# papirus-maia-icon-theme
|
||||
Qogir = qogir-icon-theme; # 5/5, thin. 5/5 adwaita coverage (2.5/5 cross)
|
||||
# 5 other Qogir variants omitted
|
||||
rose-pine = rose-pine-icon-theme;
|
||||
rose-pine-dawn = rose-pine-icon-theme; # 5/5. 5/5 adwaita coverage (2.5 cross). looks a lot like Flat Remix...
|
||||
rose-pine-moon = rose-pine-icon-theme;
|
||||
SuperTinyIcons = super-tiny-icons; # 4/5. 2/5 adwaita coverage
|
||||
Tango = tango-icon-theme; # 2/5. 3/5 adwaita coverage -- mostly just forwards to gnome-icon-theme
|
||||
Tela = tela-icon-theme; # 5/5. 5/5 adwaita coverage
|
||||
# 30-ish other Tela editions omitted
|
||||
Vimix = vimix-icon-theme;
|
||||
# 15-ish other Vimix editions omitted
|
||||
WhiteSur = whitesur-icon-theme; # 4.5/5, thin & like iOS. 5/5 adwaita coverage (3.5/5 cross)
|
||||
WhiteSur-dark = whitesur-icon-theme;
|
||||
Rodent = xfce.xfce4-icon-theme;
|
||||
Zafiro-icons-Dark = zafiro-icons;
|
||||
Zafiro-icons-Light = zafiro-icons; # 5/5. 5/5 adwaita coverage
|
||||
|
||||
};
|
||||
};
|
||||
in
|
||||
|
@@ -1,48 +1,55 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# docs: https://nixos.wiki/wiki/Sway
|
||||
with lib;
|
||||
# sway-config docs: `man 5 sway`
|
||||
let
|
||||
cfg = config.sane.gui.sway;
|
||||
|
||||
# bare sway launcher
|
||||
sway-launcher = pkgs.writeShellScriptBin "sway-launcher" ''
|
||||
${pkgs.sway}/bin/sway --debug > /var/log/sway/sway.log 2>&1
|
||||
defaultPackage = let
|
||||
# `defaultPackage` exists to create a `sway.desktop` file
|
||||
# which will launch sway with our desired debugging facilities.
|
||||
# i.e. redirect output to syslog.
|
||||
scfg = config.programs.sway;
|
||||
systemd-cat = "${pkgs.systemd}/bin/systemd-cat";
|
||||
swayWithLogger = pkgs.writeShellScriptBin "sway-session" ''
|
||||
echo "launching sway-session (sway.desktop)..." | ${systemd-cat} --identifier=sway-session
|
||||
sway 2>&1 | ${systemd-cat} --identifier=sway-session
|
||||
'';
|
||||
# start sway and have it construct the gtkgreeter
|
||||
sway-as-greeter = pkgs.writeShellScriptBin "sway-as-greeter" ''
|
||||
${pkgs.sway}/bin/sway --debug --config ${sway-config-into-gtkgreet} > /var/log/sway/sway-as-greeter.log 2>&1
|
||||
origSway = (pkgs.sway.override {
|
||||
# this override is what `programs.nixos` would do internally if we left `package` unset.
|
||||
extraSessionCommands = scfg.extraSessionCommands;
|
||||
extraOptions = scfg.extraOptions;
|
||||
withBaseWrapper = scfg.wrapperFeatures.base;
|
||||
withGtkWrapper = scfg.wrapperFeatures.gtk;
|
||||
isNixOS = true;
|
||||
# TODO: `enableXWayland = ...`?
|
||||
});
|
||||
desktop-file = pkgs.runCommand "sway-desktop-wrapper" {} ''
|
||||
mkdir -p $out/share/wayland-sessions
|
||||
substitute ${origSway}/share/wayland-sessions/sway.desktop $out/share/wayland-sessions/sway.desktop \
|
||||
--replace 'Exec=sway' 'Exec=${swayWithLogger}/bin/sway-session'
|
||||
# XXX(2023/09/24) phog greeter (mobile greeter) will crash if DesktopNames is not set
|
||||
echo "DesktopNames=Sway" >> $out/share/wayland-sessions/sway.desktop
|
||||
'';
|
||||
# (config file for the above)
|
||||
sway-config-into-gtkgreet = pkgs.writeText "greetd-sway-config" ''
|
||||
exec "${gtkgreet-launcher}"
|
||||
'';
|
||||
# gtkgreet which launches a layered sway instance
|
||||
gtkgreet-launcher = pkgs.writeShellScript "gtkgreet-launcher" ''
|
||||
# NB: the "command" field here is run in the user's shell.
|
||||
# so that command must exist on the specific user's path who is logging in. it doesn't need to exist system-wide.
|
||||
${pkgs.greetd.gtkgreet}/bin/gtkgreet --layer-shell --command sway-launcher
|
||||
'';
|
||||
greeter-session = {
|
||||
# greeter session config
|
||||
command = "${sway-as-greeter}/bin/sway-as-greeter";
|
||||
# alternatives:
|
||||
# - TTY: `command = "${pkgs.greetd.greetd}/bin/agreety --cmd ${pkgs.sway}/bin/sway";`
|
||||
# - autologin: `command = "${pkgs.sway}/bin/sway"; user = "colin";`
|
||||
# - Dumb Login (doesn't work)": `command = "${pkgs.greetd.dlm}/bin/dlm";`
|
||||
in pkgs.symlinkJoin {
|
||||
inherit (origSway) name meta;
|
||||
# the order of these `paths` is suchs that the desktop-file should claim share/wayland-sessions/sway.deskop,
|
||||
# overriding whatever the origSway provides
|
||||
paths = [ desktop-file origSway ];
|
||||
passthru = {
|
||||
inherit (origSway.passthru) providedSessions;
|
||||
};
|
||||
greeterless-session = {
|
||||
# no greeter
|
||||
command = "${sway-launcher}/bin/sway-launcher";
|
||||
user = "colin";
|
||||
};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
options = with lib; {
|
||||
sane.gui.sway.enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
sane.gui.sway.package = mkOption {
|
||||
default = defaultPackage;
|
||||
type = types.package;
|
||||
};
|
||||
sane.gui.sway.useGreeter = mkOption {
|
||||
description = ''
|
||||
launch sway via a greeter (like greetd's gtkgreet).
|
||||
@@ -51,19 +58,124 @@ in
|
||||
default = true;
|
||||
type = types.bool;
|
||||
};
|
||||
sane.gui.sway.config = {
|
||||
extra_lines = mkOption {
|
||||
type = types.lines;
|
||||
description = ''
|
||||
extra lines to append to the sway config
|
||||
'';
|
||||
default = ''
|
||||
# XXX: sway needs exclusive control of XF86Audio{Raise,Lower}Volume, so assign this from a block that it can override.
|
||||
# TODO: factor the bindings out into proper options and be less hacky?
|
||||
bindsym --locked XF86AudioRaiseVolume exec $volume_up
|
||||
bindsym --locked XF86AudioLowerVolume exec $volume_down
|
||||
'';
|
||||
};
|
||||
config = mkMerge [
|
||||
background = mkOption {
|
||||
type = types.path;
|
||||
};
|
||||
font = mkOption {
|
||||
type = types.str;
|
||||
default = "pango:monospace 11";
|
||||
description = ''
|
||||
default font (for e.g. window titles)
|
||||
'';
|
||||
};
|
||||
mod = mkOption {
|
||||
type = types.str;
|
||||
default = "Mod4";
|
||||
description = ''
|
||||
Super key (for non-application shortcuts).
|
||||
- "Mod1" for Alt
|
||||
- "Mod4" for logo key
|
||||
'';
|
||||
};
|
||||
workspace_layout = mkOption {
|
||||
type = types.str;
|
||||
default = "default";
|
||||
description = ''
|
||||
how to arrange windows within new workspaces, by default:
|
||||
- "default" (split)
|
||||
- "tabbed"
|
||||
- etc
|
||||
'';
|
||||
};
|
||||
xwayland = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
whether or not to enable xwayland (allows running X11 apps on sway).
|
||||
some electron apps (e.g. element-desktop) require xwayland.
|
||||
'';
|
||||
};
|
||||
|
||||
# TODO: split these into their own option scope
|
||||
brightness_down_cmd = mkOption {
|
||||
type = types.str;
|
||||
default = "${pkgs.brightnessctl}/bin/brightnessctl set -2%";
|
||||
description = "command to run when use wants to decrease screen brightness";
|
||||
};
|
||||
brightness_up_cmd = mkOption {
|
||||
type = types.str;
|
||||
default = "${pkgs.brightnessctl}/bin/brightnessctl set +2%";
|
||||
description = "command to run when use wants to increase screen brightness";
|
||||
};
|
||||
screenshot_cmd = mkOption {
|
||||
type = types.str;
|
||||
default = "${pkgs.sway-contrib.grimshot}/bin/grimshot copy area";
|
||||
description = "command to run when user wants to take a screenshot";
|
||||
};
|
||||
};
|
||||
sane.gui.sway.waybar.extra_style = mkOption {
|
||||
type = types.lines;
|
||||
default = ''
|
||||
/* default font-size is about 14px, which is good for moby, but not quite for larger displays */
|
||||
window#waybar {
|
||||
font-size: 16px;
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
extra CSS rules to append to ~/.config/waybar/style.css
|
||||
'';
|
||||
};
|
||||
sane.gui.sway.waybar.top = mkOption {
|
||||
type = types.submodule {
|
||||
# `attrsOf types.anything` (v.s. plain `attrs`) causes merging of the toplevel items.
|
||||
# this allows for `waybar.top.x = lib.mkDefault a;` with `waybar.top.x = b;` to resolve to `b`.
|
||||
# but note that `waybar.top.x.y = <multiple assignment>` won't be handled as desired.
|
||||
freeformType = types.attrsOf types.anything;
|
||||
};
|
||||
default = {};
|
||||
description = ''
|
||||
Waybar configuration for the bar at the top of the display.
|
||||
see: <https://github.com/Alexays/Waybar/wiki/Configuration>
|
||||
example:
|
||||
```nix
|
||||
{
|
||||
height = 40;
|
||||
modules-left = [ "sway/workspaces" "sway/mode" ];
|
||||
...
|
||||
}
|
||||
```
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
{
|
||||
sane.programs.swayApps = {
|
||||
package = null;
|
||||
suggestedPrograms = [
|
||||
"guiApps"
|
||||
"conky" # for a nice background
|
||||
"splatmoji" # used by us, but 'enabling' it gets us persistence & cfg
|
||||
"swaylock"
|
||||
"swayidle"
|
||||
"wl-clipboard"
|
||||
"blueberry" # GUI bluetooth manager
|
||||
"mako" # notification daemon
|
||||
"playerctl" # for waybar & particularly to have playerctld running
|
||||
# "mako" # notification daemon
|
||||
"swaynotificationcenter" # notification daemon
|
||||
# # "pavucontrol"
|
||||
# "gnome.gnome-bluetooth" # XXX(2023/05/14): broken
|
||||
# "gnome.gnome-control-center" # XXX(2023/06/28): depends on webkitgtk4_1
|
||||
@@ -73,27 +185,39 @@ in
|
||||
|
||||
secrets.".config/sane-sway/snippets.txt" = ../../../../secrets/common/snippets.txt.bin;
|
||||
};
|
||||
|
||||
# default waybar
|
||||
sane.gui.sway.waybar.top = import ./waybar-top.nix { inherit lib pkgs; };
|
||||
}
|
||||
|
||||
(mkIf cfg.enable {
|
||||
(lib.mkIf cfg.enable {
|
||||
sane.programs.fontconfig.enableFor.system = true;
|
||||
sane.programs.swayApps.enableFor.user.colin = true;
|
||||
# we need the greeter's command to be on our PATH
|
||||
users.users.colin.packages = [ sway-launcher ];
|
||||
|
||||
sane.gui.gtk.enable = lib.mkDefault true;
|
||||
# sane.gui.gtk.gtk-theme = lib.mkDefault "Fluent-Light-compact";
|
||||
sane.gui.gtk.gtk-theme = lib.mkDefault "Tokyonight-Light-B";
|
||||
sane.gui.gtk.icon-theme = lib.mkDefault "HighContrast"; # 4/5 coverage on moby
|
||||
# sane.gui.gtk.icon-theme = lib.mkDefault "WhiteSur"; # 3.5/5 coverage on moby, but it provides a bunch for Fractal/Dino
|
||||
# sane.gui.gtk.icon-theme = lib.mkDefault "Humanity"; # 3.5/5 coverage on moby, but it provides the bookmark icon
|
||||
# sane.gui.gtk.icon-theme = lib.mkDefault "Paper"; # 3.5/5 coverage on moby, but it provides the bookmark icon
|
||||
# sane.gui.gtk.icon-theme = lib.mkDefault "Nordzy"; # 3/5 coverage on moby
|
||||
# sane.gui.gtk.icon-theme = lib.mkDefault "Fluent"; # 3/5 coverage on moby
|
||||
# sane.gui.gtk.icon-theme = lib.mkDefault "Colloid"; # 3/5 coverage on moby
|
||||
# sane.gui.gtk.icon-theme = lib.mkDefault "Qogir"; # 2.5/5 coverage on moby
|
||||
# sane.gui.gtk.icon-theme = lib.mkDefault "rose-pine-dawn"; # 2.5/5 coverage on moby
|
||||
# sane.gui.gtk.icon-theme = lib.mkDefault "Flat-Remix-Grey-Light"; # requires qtbase
|
||||
|
||||
# swap in these lines to use SDDM instead of `services.greetd`.
|
||||
# services.xserver.displayManager.sddm.enable = true;
|
||||
# services.xserver.enable = true;
|
||||
services.greetd = {
|
||||
# greetd source/docs:
|
||||
# - <https://git.sr.ht/~kennylevinsen/greetd>
|
||||
sane.gui.greetd = lib.mkIf cfg.useGreeter {
|
||||
enable = true;
|
||||
settings = {
|
||||
default_session = if cfg.useGreeter then greeter-session else greeterless-session;
|
||||
sway.enable = true; # have greetd launch a sway compositor in which we host a greeter
|
||||
sway.gtkgreet = {
|
||||
enable = true;
|
||||
session.name = "sway-on-gtkgreet";
|
||||
session.command = "${cfg.package}/bin/sway";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -103,8 +227,27 @@ in
|
||||
enable = true;
|
||||
alsa.enable = true;
|
||||
alsa.support32Bit = true; # ??
|
||||
# emulate pulseaudio for legacy apps (e.g. sxmo-utils)
|
||||
pulse.enable = true;
|
||||
};
|
||||
# persist per-device volume levels
|
||||
sane.user.persist.plaintext = [ ".local/state/wireplumber" ];
|
||||
|
||||
# persist per-device volume settings across power cycles.
|
||||
# pipewire sits atop the kernel ALSA API, so alsa-utils knows about device volumes.
|
||||
# but wireplumber also tries to do some of this
|
||||
# systemd.services.alsa-store = {
|
||||
# # based on <repo:nixos/nixpkgs:nixos/modules/services/audio/alsa.nix>
|
||||
# description = "Store Sound Card State";
|
||||
# wantedBy = [ "multi-user.target" ];
|
||||
# serviceConfig = {
|
||||
# Type = "oneshot";
|
||||
# RemainAfterExit = true;
|
||||
# ExecStart = "${pkgs.alsa-utils}/sbin/alsactl restore";
|
||||
# ExecStop = "${pkgs.alsa-utils}/sbin/alsactl store --ignore";
|
||||
# };
|
||||
# };
|
||||
# sane.persist.sys.plaintext = [ "/var/lib/alsa" ];
|
||||
|
||||
networking.useDHCP = false;
|
||||
networking.networkmanager.enable = true;
|
||||
@@ -124,20 +267,18 @@ in
|
||||
# a system service can't depend on a user service, so just launch it at graphical-session
|
||||
systemd.user.services."pipewire".wantedBy = [ "graphical-session.target" ];
|
||||
|
||||
sane.fs."/var/log/sway" = {
|
||||
dir.acl.mode = "0777";
|
||||
wantedBeforeBy = [ "greetd.service" "display-manager.service" ];
|
||||
};
|
||||
|
||||
programs.sway = {
|
||||
# provides xdg-desktop-portal-wlr, which exposes on dbus:
|
||||
# - org.freedesktop.impl.portal.ScreenCast
|
||||
# - org.freedesktop.impl.portal.Screenshot
|
||||
enable = true;
|
||||
extraPackages = []; # nixos adds swaylock, swayidle, foot, dmenu by default
|
||||
# extraOptions = [ "--debug" ];
|
||||
# "wrapGAppsHook wrapper to execute sway with required environment variables for GTK applications."
|
||||
wrapperFeatures.gtk = true;
|
||||
package = cfg.package;
|
||||
};
|
||||
programs.xwayland.enable = cfg.config.xwayland;
|
||||
# provide portals for:
|
||||
# - org.freedesktop.impl.portal.Access
|
||||
# - org.freedesktop.impl.portal.Account
|
||||
@@ -162,17 +303,20 @@ in
|
||||
})
|
||||
];
|
||||
|
||||
sane.user.fs.".config/sway/config".symlink.text =
|
||||
import ./sway-config.nix { inherit pkgs; };
|
||||
sane.user.fs = {
|
||||
".config/waybar/config".symlink.target =
|
||||
(pkgs.formats.json {}).generate "waybar-config.json" [
|
||||
({ layer = "top"; } // cfg.waybar.top)
|
||||
];
|
||||
|
||||
sane.user.fs.".config/waybar/config".symlink.target =
|
||||
let
|
||||
waybar-config = import ./waybar-config.nix { inherit pkgs; };
|
||||
in
|
||||
(pkgs.formats.json {}).generate "waybar-config.json" waybar-config;
|
||||
".config/waybar/style.css".symlink.text =
|
||||
(builtins.readFile ./waybar-style.css) + cfg.waybar.extra_style;
|
||||
|
||||
sane.user.fs.".config/waybar/style.css".symlink.text =
|
||||
builtins.readFile ./waybar-style.css;
|
||||
".config/sway/config".symlink.target = import ./sway-config.nix {
|
||||
inherit pkgs;
|
||||
inherit (cfg) config;
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
|
182
hosts/modules/gui/sway/sway-config
Normal file
182
hosts/modules/gui/sway/sway-config
Normal file
@@ -0,0 +1,182 @@
|
||||
# xwayland enable|disable|force
|
||||
# - enable: lazily launch xwayland on first client connection
|
||||
# - disable: never launch xwayland
|
||||
# - force: launch xwayland immediately on boot
|
||||
# XWayland exposes a X11 server that translates the protocol to a wayland backend, allowing legacy x11-only GUI apps.
|
||||
# XWayland uses about 35MB RSS even when idle
|
||||
xwayland @xwayland@
|
||||
|
||||
set $mod @mod@
|
||||
set $term @terminal_cmd@
|
||||
set $menu @launcher_cmd@
|
||||
set $emoji_picker @emoji_cmd@
|
||||
set $locker @lock_cmd@
|
||||
set $snippets_picker @snip_cmd@
|
||||
set $screenshot @screenshot_cmd@
|
||||
set $brightness_up @brightness_up_cmd@
|
||||
set $brightness_down @brightness_down_cmd@
|
||||
set $volume_up @vol_up_cmd@
|
||||
set $volume_down @vol_down_cmd@
|
||||
set $mute @mute_cmd@
|
||||
set $default_workspace_layout @workspace_layout@
|
||||
|
||||
### default font (for e.g. window titles)
|
||||
font @font@
|
||||
|
||||
### tab colors (#border #background #text [#indicator #childBorder])
|
||||
# focused & unfocused are the main interest
|
||||
# urgent is used when an inactive window wants attention (e.g. terminal rings a bell)
|
||||
# colors are synchronized with waybar and mpv
|
||||
client.focused #1f5e54 #418379 #ffffff
|
||||
client.focused_inactive #1f5e54 #5f676a #ffffff
|
||||
client.unfocused #1f5e54 #1f554c #b4b4b4
|
||||
client.urgent #ff8080 #ff8080 #ffffff
|
||||
|
||||
output '*' bg "@background@" fill
|
||||
|
||||
### pixel boundary between windows
|
||||
# hide_edge_borders --i3 means that single-window workspaces never show window bar
|
||||
hide_edge_borders --i3 smart
|
||||
default_border pixel 1
|
||||
titlebar_border_thickness 1
|
||||
# XX YY distance from edge of window title to edge of text
|
||||
# the YY distance here determines the heigh of the overall title
|
||||
titlebar_padding 12 1
|
||||
title_align center
|
||||
|
||||
### focus_wrapping: behavior when trying to focus past the edge of a container
|
||||
#### no => preserve last focus. helpful mostly when `focus_follows_mouse yes`
|
||||
focus_wrapping no
|
||||
focus_follows_mouse yes
|
||||
workspace_layout $default_workspace_layout
|
||||
|
||||
### key bindings
|
||||
floating_modifier $mod
|
||||
#### media keys
|
||||
bindsym $mod+Page_Up exec $volume_up
|
||||
bindsym $mod+Page_Down exec $volume_down
|
||||
# --locked means to keep the binding active even when display is locked/off
|
||||
bindsym --locked XF86AudioMute exec $mute
|
||||
bindsym --locked XF86AudioPlay exec @playerctl@ play-pause
|
||||
bindsym --locked XF86AudioStop exec @playerctl@ stop
|
||||
bindsym --locked XF86AudioNext exec @playerctl@ next
|
||||
bindsym --locked XF86AudioPrev exec @playerctl@ previous
|
||||
bindsym --locked XF86MonBrightnessUp exec $brightness_up
|
||||
bindsym --locked XF86MonBrightnessDown exec $brightness_down
|
||||
|
||||
#### special functions
|
||||
bindsym $mod+Print exec $screenshot
|
||||
bindsym $mod+l exec $locker
|
||||
bindsym $mod+s exec $snippets_picker
|
||||
bindsym $mod+slash exec $emoji_picker
|
||||
bindsym $mod+d exec $menu
|
||||
bindsym $mod+Return exec $term
|
||||
bindsym $mod+Shift+q kill
|
||||
bindsym $mod+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'
|
||||
bindsym $mod+Shift+c reload
|
||||
|
||||
#### layout
|
||||
bindsym $mod+b splith
|
||||
bindsym $mod+v splitv
|
||||
bindsym $mod+f fullscreen toggle
|
||||
bindsym $mod+a focus parent
|
||||
bindsym $mod+w layout tabbed
|
||||
bindsym $mod+e layout toggle split
|
||||
bindsym $mod+Shift+space floating toggle
|
||||
bindsym $mod+space focus mode_toggle
|
||||
bindsym $mod+r mode resize
|
||||
|
||||
#### movement
|
||||
bindsym $mod+Up focus up
|
||||
bindsym $mod+Down focus down
|
||||
bindsym $mod+Left focus left
|
||||
bindsym $mod+Right focus right
|
||||
bindsym $mod+Shift+Up move up
|
||||
bindsym $mod+Shift+Down move down
|
||||
bindsym $mod+Shift+Left move left
|
||||
bindsym $mod+Shift+Right move right
|
||||
|
||||
#### workspaces
|
||||
bindsym $mod+1 workspace number 1
|
||||
bindsym $mod+2 workspace number 2
|
||||
bindsym $mod+3 workspace number 3
|
||||
bindsym $mod+4 workspace number 4
|
||||
bindsym $mod+5 workspace number 5
|
||||
bindsym $mod+6 workspace number 6
|
||||
bindsym $mod+7 workspace number 7
|
||||
bindsym $mod+8 workspace number 8
|
||||
bindsym $mod+9 workspace number 9
|
||||
bindsym $mod+Shift+1 move container to workspace number 1
|
||||
bindsym $mod+Shift+2 move container to workspace number 2
|
||||
bindsym $mod+Shift+3 move container to workspace number 3
|
||||
bindsym $mod+Shift+4 move container to workspace number 4
|
||||
bindsym $mod+Shift+5 move container to workspace number 5
|
||||
bindsym $mod+Shift+6 move container to workspace number 6
|
||||
bindsym $mod+Shift+7 move container to workspace number 7
|
||||
bindsym $mod+Shift+8 move container to workspace number 8
|
||||
bindsym $mod+Shift+9 move container to workspace number 9
|
||||
|
||||
#### "scratchpad" = ??
|
||||
bindsym $mod+Shift+minus move scratchpad
|
||||
bindsym $mod+minus scratchpad show
|
||||
|
||||
mode "resize" {
|
||||
bindsym Down resize grow height 30 px
|
||||
bindsym Escape mode default
|
||||
bindsym Left resize shrink width 30 px
|
||||
bindsym Return mode default
|
||||
bindsym Right resize grow width 30 px
|
||||
bindsym Up resize shrink height 30 px
|
||||
bindsym h resize shrink width 30 px
|
||||
bindsym j resize grow height 30 px
|
||||
bindsym k resize shrink height 30 px
|
||||
bindsym l resize grow width 30 px
|
||||
}
|
||||
|
||||
bar {
|
||||
swaybar_command @waybar@
|
||||
}
|
||||
|
||||
### application-specific settings
|
||||
#### to obtain app-id's run `swaymsg -t get_tree`
|
||||
for_window [app_id="pinentry-.*"] floating true
|
||||
for_window [app_id="foot" title=".*sxmo/modem/.*/draft.txt.*"] resize set height 25
|
||||
for_window [title="megapixels"] inhibit_idle open
|
||||
# Dino (XMPP) is ordinarily started for the purpose of daemonizing (but still visible). keep it on a predictable, out-of-the-way workspace
|
||||
# TODO: could be neat to somehow do this in a way that it never steals focus from anything...
|
||||
for_window [app_id="im.dino.Dino"] move container to workspace number 1
|
||||
for_window [app_id="org.gnome.Fractal"] move container to workspace number 1
|
||||
|
||||
### displays
|
||||
## DESKTOP
|
||||
output "Samsung Electric Company S22C300 0x00007F35" {
|
||||
pos 0,0
|
||||
res 1920x1080
|
||||
}
|
||||
output "Goldstar Company Ltd LG ULTRAWIDE 0x00004E94" {
|
||||
pos 1920,0
|
||||
res 3440x1440
|
||||
}
|
||||
|
||||
## LAPTOP
|
||||
# sh/en TV
|
||||
output "Pioneer Electronic Corporation VSX-524 0x00000101" {
|
||||
pos 0,0
|
||||
res 1920x1080
|
||||
}
|
||||
# internal display
|
||||
output "Unknown 0x0637 0x00000000" {
|
||||
pos 1920,0
|
||||
res 1920x1080
|
||||
}
|
||||
|
||||
# XXX: needed for xdg-desktop-portal-* to work.
|
||||
# this is how we expose these env vars to user dbus services:
|
||||
# - DISPLAY
|
||||
# - WAYLAND_DISPLAY
|
||||
# - SWAYSOCK
|
||||
# - XDG_CURRENT_DESKTOP
|
||||
# for more, see: <repo:nixos/nixpkgs:nixos/modules/programs/wayland/sway.nix>
|
||||
include /etc/sway/config.d/*
|
||||
|
||||
@extra_lines@
|
@@ -1,181 +1,48 @@
|
||||
{ pkgs }:
|
||||
{ config, pkgs }:
|
||||
let
|
||||
fuzzel = "${pkgs.fuzzel}/bin/fuzzel";
|
||||
sed = "${pkgs.gnused}/bin/sed";
|
||||
wtype = "${pkgs.wtype}/bin/wtype";
|
||||
launcher-cmd = fuzzel;
|
||||
terminal-cmd = "${pkgs.xdg-terminal-exec}/bin/xdg-terminal-exec";
|
||||
lock-cmd = "${pkgs.swaylock}/bin/swaylock --indicator-idle-visible --indicator-radius 100 --indicator-thickness 30";
|
||||
vol-up-cmd = "${pkgs.pulsemixer}/bin/pulsemixer --change-volume +5";
|
||||
vol-down-cmd = "${pkgs.pulsemixer}/bin/pulsemixer --change-volume -5";
|
||||
mute-cmd = "${pkgs.pulsemixer}/bin/pulsemixer --toggle-mute";
|
||||
brightness-up-cmd = "${pkgs.brightnessctl}/bin/brightnessctl set +2%";
|
||||
brightness-down-cmd = "${pkgs.brightnessctl}/bin/brightnessctl set 2%-";
|
||||
screenshot-cmd = "${pkgs.sway-contrib.grimshot}/bin/grimshot copy area";
|
||||
launcher_cmd = fuzzel;
|
||||
terminal_cmd = "${pkgs.xdg-terminal-exec}/bin/xdg-terminal-exec";
|
||||
lock_cmd = "${pkgs.swaylock}/bin/swaylock --indicator-idle-visible --indicator-radius 100 --indicator-thickness 30";
|
||||
# TODO: use pipewire controls?
|
||||
vol_up_cmd = "${pkgs.pulsemixer}/bin/pulsemixer --change-volume +5";
|
||||
vol_down_cmd = "${pkgs.pulsemixer}/bin/pulsemixer --change-volume -5";
|
||||
mute_cmd = "${pkgs.pulsemixer}/bin/pulsemixer --toggle-mute";
|
||||
# "bookmarking"/snippets inspired by Luke Smith:
|
||||
# - <https://www.youtube.com/watch?v=d_11QaTlf1I>
|
||||
snip-file = ../snippets.txt;
|
||||
list-snips = "cat ${snip-file} ~/.config/sane-sway/snippets.txt";
|
||||
strip-comments = "${sed} 's/ #.*$//'";
|
||||
snip-cmd = "${wtype} $(${list-snips} | ${fuzzel} -d -i -w 60 | ${strip-comments})";
|
||||
snip_cmd = pkgs.writeShellScript "type_snippet.sh" ''
|
||||
snippet=$(cat ${../snippets.txt} ~/.config/sane-sway/snippets.txt | \
|
||||
${fuzzel} -d -i -w 60 | \
|
||||
${sed} 's/ #.*$//')
|
||||
${wtype} "$snippet"
|
||||
'';
|
||||
# TODO: splatmoji release > 1.2.0 should allow `-s none` to disable skin tones
|
||||
emoji-cmd = "${pkgs.splatmoji}/bin/splatmoji -s medium-light type";
|
||||
|
||||
# mod = "Mod1"; # Alt
|
||||
mod = "Mod4"; # Super
|
||||
in ''
|
||||
### default font
|
||||
font pango:monospace 8
|
||||
|
||||
### pixel boundary between windows
|
||||
default_border pixel 3
|
||||
default_floating_border pixel 2
|
||||
hide_edge_borders smart
|
||||
|
||||
### defaults
|
||||
focus_wrapping no
|
||||
focus_follows_mouse yes
|
||||
focus_on_window_activation smart
|
||||
mouse_warping output
|
||||
workspace_layout default
|
||||
workspace_auto_back_and_forth no
|
||||
|
||||
### default colors (#border #background #text #indicator #childBorder)
|
||||
client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577
|
||||
client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a
|
||||
client.unfocused #333333 #222222 #888888 #292d2e #222222
|
||||
client.urgent #2f343a #900000 #ffffff #900000 #900000
|
||||
client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c
|
||||
client.background #ffffff
|
||||
|
||||
### key bindings
|
||||
floating_modifier ${mod}
|
||||
## media keys
|
||||
bindsym XF86AudioRaiseVolume exec ${vol-up-cmd}
|
||||
bindsym XF86AudioLowerVolume exec ${vol-down-cmd}
|
||||
bindsym ${mod}+Page_Up exec ${vol-up-cmd}
|
||||
bindsym ${mod}+Page_Down exec ${vol-down-cmd}
|
||||
bindsym XF86AudioMute exec ${mute-cmd}
|
||||
bindsym XF86MonBrightnessUp exec ${brightness-up-cmd}
|
||||
bindsym XF86MonBrightnessDown exec ${brightness-down-cmd}
|
||||
## special functions
|
||||
bindsym ${mod}+Print exec ${screenshot-cmd}
|
||||
bindsym ${mod}+l exec ${lock-cmd}
|
||||
bindsym ${mod}+s exec ${snip-cmd}
|
||||
bindsym ${mod}+slash exec ${emoji-cmd}
|
||||
bindsym ${mod}+d exec ${launcher-cmd}
|
||||
bindsym ${mod}+Return exec ${terminal-cmd}
|
||||
bindsym ${mod}+Shift+q kill
|
||||
bindsym ${mod}+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'
|
||||
bindsym ${mod}+Shift+c reload
|
||||
## layout
|
||||
bindsym ${mod}+b splith
|
||||
bindsym ${mod}+v splitv
|
||||
bindsym ${mod}+f fullscreen toggle
|
||||
bindsym ${mod}+a focus parent
|
||||
bindsym ${mod}+w layout tabbed
|
||||
bindsym ${mod}+e layout toggle split
|
||||
bindsym ${mod}+Shift+space floating toggle
|
||||
bindsym ${mod}+space focus mode_toggle
|
||||
bindsym ${mod}+r mode resize
|
||||
## movement
|
||||
bindsym ${mod}+Up focus up
|
||||
bindsym ${mod}+Down focus down
|
||||
bindsym ${mod}+Left focus left
|
||||
bindsym ${mod}+Right focus right
|
||||
bindsym ${mod}+Shift+Up move up
|
||||
bindsym ${mod}+Shift+Down move down
|
||||
bindsym ${mod}+Shift+Left move left
|
||||
bindsym ${mod}+Shift+Right move right
|
||||
## workspaces
|
||||
bindsym ${mod}+1 workspace number 1
|
||||
bindsym ${mod}+2 workspace number 2
|
||||
bindsym ${mod}+3 workspace number 3
|
||||
bindsym ${mod}+4 workspace number 4
|
||||
bindsym ${mod}+5 workspace number 5
|
||||
bindsym ${mod}+6 workspace number 6
|
||||
bindsym ${mod}+7 workspace number 7
|
||||
bindsym ${mod}+8 workspace number 8
|
||||
bindsym ${mod}+9 workspace number 9
|
||||
bindsym ${mod}+Shift+1 move container to workspace number 1
|
||||
bindsym ${mod}+Shift+2 move container to workspace number 2
|
||||
bindsym ${mod}+Shift+3 move container to workspace number 3
|
||||
bindsym ${mod}+Shift+4 move container to workspace number 4
|
||||
bindsym ${mod}+Shift+5 move container to workspace number 5
|
||||
bindsym ${mod}+Shift+6 move container to workspace number 6
|
||||
bindsym ${mod}+Shift+7 move container to workspace number 7
|
||||
bindsym ${mod}+Shift+8 move container to workspace number 8
|
||||
bindsym ${mod}+Shift+9 move container to workspace number 9
|
||||
## "scratchpad" = ??
|
||||
bindsym ${mod}+Shift+minus move scratchpad
|
||||
bindsym ${mod}+minus scratchpad show
|
||||
|
||||
### defaults
|
||||
mode "resize" {
|
||||
bindsym Down resize grow height 10 px
|
||||
bindsym Escape mode default
|
||||
bindsym Left resize shrink width 10 px
|
||||
bindsym Return mode default
|
||||
bindsym Right resize grow width 10 px
|
||||
bindsym Up resize shrink height 10 px
|
||||
bindsym h resize shrink width 10 px
|
||||
bindsym j resize grow height 10 px
|
||||
bindsym k resize shrink height 10 px
|
||||
bindsym l resize grow width 10 px
|
||||
emoji_cmd = "${pkgs.splatmoji}/bin/splatmoji -s medium-light type";
|
||||
in pkgs.substituteAll {
|
||||
src = ./sway-config;
|
||||
inherit
|
||||
emoji_cmd
|
||||
launcher_cmd
|
||||
lock_cmd
|
||||
mute_cmd
|
||||
snip_cmd
|
||||
terminal_cmd
|
||||
vol_down_cmd
|
||||
vol_up_cmd
|
||||
;
|
||||
inherit (config)
|
||||
background
|
||||
brightness_down_cmd
|
||||
brightness_up_cmd
|
||||
extra_lines
|
||||
screenshot_cmd
|
||||
font
|
||||
mod
|
||||
workspace_layout
|
||||
;
|
||||
xwayland = if config.xwayland then "enable" else "disable";
|
||||
playerctl = "${pkgs.playerctl}/bin/playerctl";
|
||||
waybar = "${pkgs.waybar}/bin/waybar";
|
||||
}
|
||||
|
||||
### lightly modified bars
|
||||
bar {
|
||||
mode dock
|
||||
hidden_state hide
|
||||
position top
|
||||
status_command ${pkgs.i3status}/bin/i3status
|
||||
swaybar_command ${pkgs.waybar}/bin/waybar
|
||||
workspace_buttons yes
|
||||
strip_workspace_numbers no
|
||||
tray_output primary
|
||||
colors {
|
||||
background #000000
|
||||
statusline #ffffff
|
||||
separator #666666
|
||||
# #border #background #text
|
||||
focused_workspace #4c7899 #285577 #ffffff
|
||||
active_workspace #333333 #5f676a #ffffff
|
||||
inactive_workspace #333333 #222222 #888888
|
||||
urgent_workspace #2f343a #900000 #ffffff
|
||||
binding_mode #2f343a #900000 #ffffff
|
||||
}
|
||||
}
|
||||
|
||||
### displays
|
||||
## DESKTOP
|
||||
output "Samsung Electric Company S22C300 0x00007F35" {
|
||||
pos 0,0
|
||||
res 1920x1080
|
||||
}
|
||||
output "Goldstar Company Ltd LG ULTRAWIDE 0x00004E94" {
|
||||
pos 1920,0
|
||||
res 3440x1440
|
||||
}
|
||||
|
||||
## LAPTOP
|
||||
# sh/en TV
|
||||
output "Pioneer Electronic Corporation VSX-524 0x00000101" {
|
||||
pos 0,0
|
||||
res 1920x1080
|
||||
}
|
||||
# internal display
|
||||
output "Unknown 0x0637 0x00000000" {
|
||||
pos 1920,0
|
||||
res 1920x1080
|
||||
}
|
||||
|
||||
# XXX: needed for xdg-desktop-portal-* to work.
|
||||
# this is how we expose these env vars to user dbus services:
|
||||
# - DISPLAY
|
||||
# - WAYLAND_DISPLAY
|
||||
# - SWAYSOCK
|
||||
# - XDG_CURRENT_DESKTOP
|
||||
# for more, see: <repo:nixos/nixpkgs:nixos/modules/programs/wayland/sway.nix>
|
||||
include /etc/sway/config.d/*
|
||||
''
|
||||
|
@@ -1,67 +0,0 @@
|
||||
# docs: https://github.com/Alexays/Waybar/wiki/Configuration
|
||||
# format specifiers: https://fmt.dev/latest/syntax.html#syntax
|
||||
{ pkgs }:
|
||||
[
|
||||
{ # TOP BAR
|
||||
layer = "top";
|
||||
height = 40;
|
||||
modules-left = ["sway/workspaces" "sway/mode"];
|
||||
modules-center = ["sway/window"];
|
||||
modules-right = ["custom/mediaplayer" "clock" "battery" "cpu" "network"];
|
||||
"sway/window" = {
|
||||
max-length = 50;
|
||||
};
|
||||
# include song artist/title. source: https://www.reddit.com/r/swaywm/comments/ni0vso/waybar_spotify_tracktitle/
|
||||
"custom/mediaplayer" = {
|
||||
exec = pkgs.writeShellScript "waybar-mediaplayer" ''
|
||||
player_status=$(${pkgs.playerctl}/bin/playerctl status 2> /dev/null)
|
||||
if [ "$player_status" = "Playing" ]; then
|
||||
echo "$(${pkgs.playerctl}/bin/playerctl metadata artist) - $(${pkgs.playerctl}/bin/playerctl metadata title)"
|
||||
elif [ "$player_status" = "Paused" ]; then
|
||||
echo " $(${pkgs.playerctl}/bin/playerctl metadata artist) - $(${pkgs.playerctl}/bin/playerctl metadata title)"
|
||||
fi
|
||||
'';
|
||||
interval = 2;
|
||||
format = "{} ";
|
||||
# return-type = "json";
|
||||
on-click = "${pkgs.playerctl}/bin/playerctl play-pause";
|
||||
on-scroll-up = "${pkgs.playerctl}/bin/playerctl next";
|
||||
on-scroll-down = "${pkgs.playerctl}/bin/playerctl previous";
|
||||
};
|
||||
network = {
|
||||
# docs: https://github.com/Alexays/Waybar/blob/master/man/waybar-network.5.scd
|
||||
interval = 2;
|
||||
max-length = 40;
|
||||
# custom :> format specifier explained here: https://github.com/Alexays/Waybar/pull/472
|
||||
format-ethernet = " {bandwidthUpBits:>}▲ {bandwidthDownBits:>}▼";
|
||||
tooltip-format-ethernet = "{ifname} {bandwidthUpBits:>}▲ {bandwidthDownBits:>}▼";
|
||||
|
||||
format-wifi = "{ifname} ({signalStrength}%) {bandwidthUpBits:>}▲ {bandwidthDownBits:>}▼";
|
||||
tooltip-format-wifi = "{essid} ({signalStrength}%) {bandwidthUpBits:>}▲ {bandwidthDownBits:>}▼";
|
||||
|
||||
format-disconnected = "";
|
||||
};
|
||||
cpu = {
|
||||
format = " {usage:2}%";
|
||||
tooltip = false;
|
||||
};
|
||||
battery = {
|
||||
states = {
|
||||
good = 95;
|
||||
warning = 30;
|
||||
critical = 10;
|
||||
};
|
||||
format = "{icon} {capacity}%";
|
||||
format-icons = [
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
];
|
||||
};
|
||||
clock = {
|
||||
format-alt = "{:%a, %d. %b %H:%M}";
|
||||
};
|
||||
}
|
||||
]
|
17
hosts/modules/gui/sway/waybar-media
Executable file
17
hosts/modules/gui/sway/waybar-media
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p jq -p playerctl
|
||||
status=$(playerctl status 2> /dev/null | tr 'A-Z' 'a-z')
|
||||
if [ -z "$status" ]; then
|
||||
status="inactive"
|
||||
fi
|
||||
|
||||
artist=$(playerctl metadata artist 2> /dev/null)
|
||||
title=$(playerctl metadata title 2> /dev/null)
|
||||
|
||||
text=
|
||||
if [ -n "$title" ]; then
|
||||
text="$artist - $title"
|
||||
fi
|
||||
# waybar requires output to be on a single line.
|
||||
# `alt` key determines the icon
|
||||
jq --null-input --compact-output --arg status "$status" --arg text "$text" '{ "text": $text, "alt": $status }'
|
@@ -1,117 +1,5 @@
|
||||
/* style docs: https://github.com/Alexays/Waybar/wiki/Styling */
|
||||
|
||||
* {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* defaults below: https://github.com/Alexays/Waybar/blob/master/resources/style.css */
|
||||
window#waybar {
|
||||
background-color: rgba(43, 48, 59, 0.5);
|
||||
border-bottom: 3px solid rgba(100, 114, 125, 0.5);
|
||||
color: #ffffff;
|
||||
transition-property: background-color;
|
||||
transition-duration: .5s;
|
||||
}
|
||||
|
||||
window#waybar.hidden {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
/*
|
||||
window#waybar.empty {
|
||||
background-color: transparent;
|
||||
}
|
||||
window#waybar.solo {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
*/
|
||||
|
||||
window#waybar.termite {
|
||||
background-color: #3F3F3F;
|
||||
}
|
||||
|
||||
window#waybar.chromium {
|
||||
background-color: #000000;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#workspaces button {
|
||||
padding: 0 5px;
|
||||
background-color: transparent;
|
||||
color: #ffffff;
|
||||
/* Use box-shadow instead of border so the text isn't offset */
|
||||
box-shadow: inset 0 -3px transparent;
|
||||
/* Avoid rounded borders under each workspace name */
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
|
||||
#workspaces button:hover {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
box-shadow: inset 0 -3px #ffffff;
|
||||
}
|
||||
|
||||
#workspaces button.focused {
|
||||
background-color: #64727D;
|
||||
box-shadow: inset 0 -3px #ffffff;
|
||||
}
|
||||
|
||||
#workspaces button.urgent {
|
||||
background-color: #eb4d4b;
|
||||
}
|
||||
|
||||
#mode {
|
||||
background-color: #64727D;
|
||||
border-bottom: 3px solid #ffffff;
|
||||
}
|
||||
|
||||
#clock,
|
||||
#battery,
|
||||
#cpu,
|
||||
#memory,
|
||||
#disk,
|
||||
#temperature,
|
||||
#backlight,
|
||||
#network,
|
||||
#pulseaudio,
|
||||
#custom-media,
|
||||
#tray,
|
||||
#mode,
|
||||
#idle_inhibitor,
|
||||
#mpd {
|
||||
padding: 0 10px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
#window,
|
||||
#workspaces {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
/* If workspaces is the leftmost module, omit left margin */
|
||||
.modules-left > widget:first-child > #workspaces {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* If workspaces is the rightmost module, omit right margin */
|
||||
.modules-right > widget:last-child > #workspaces {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#clock {
|
||||
background-color: #64727D;
|
||||
}
|
||||
|
||||
#battery {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
#battery.charging, #battery.plugged {
|
||||
color: #ffffff;
|
||||
background-color: #26A65B;
|
||||
}
|
||||
/* defaults: https://github.com/Alexays/Waybar/blob/master/resources/style.css */
|
||||
|
||||
@keyframes blink {
|
||||
to {
|
||||
@@ -120,8 +8,75 @@ window#waybar.chromium {
|
||||
}
|
||||
}
|
||||
|
||||
window#waybar {
|
||||
background-color: #418379;
|
||||
border-bottom: 0px solid #1f5e54;
|
||||
color: #ffffff;
|
||||
transition-property: background-color;
|
||||
transition-duration: .2s;
|
||||
}
|
||||
|
||||
.modules-right {
|
||||
/* workspace buttons (LHS) get padding between it and the screen edge */
|
||||
/* replicate that same padding for whatever's on the RHS (i.e. the clock) */
|
||||
margin-right: 5px;
|
||||
}
|
||||
#workspaces button {
|
||||
padding: 0 5px;
|
||||
background-color: #418379;
|
||||
color: #ffffff;
|
||||
/* Use box-shadow instead of border so the text isn't offset */
|
||||
box-shadow: inset 0 0px #1f5e54;
|
||||
/* Avoid rounded borders under each workspace name */
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
/* keep min-width set here even if it has no apparent effect */
|
||||
/* many gtk themes override it to something large, which doesn't seem appropriate for this context */
|
||||
/* the default adwaita theme uses min-width: 16px */
|
||||
min-width: 16px;
|
||||
}
|
||||
|
||||
#workspaces button:hover {
|
||||
/* i don't want hover effects, so reset this styling to be the same as default button */
|
||||
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
|
||||
background: inherit;
|
||||
box-shadow: inherit;
|
||||
text-shadow: inherit;
|
||||
}
|
||||
|
||||
#workspaces button.focused {
|
||||
background-color: #63a89c;
|
||||
box-shadow: inset 0 0px #2c8274;
|
||||
}
|
||||
|
||||
#workspaces button.urgent {
|
||||
background-color: #e64291;
|
||||
}
|
||||
|
||||
#backlight,
|
||||
#battery,
|
||||
#clock,
|
||||
#cpu,
|
||||
#custom-media,
|
||||
#custom-swaync,
|
||||
#disk,
|
||||
#idle_inhibitor,
|
||||
#memory,
|
||||
#mode,
|
||||
#network,
|
||||
#pulseaudio,
|
||||
#temperature,
|
||||
#tray,
|
||||
#mpd {
|
||||
/* without padding the text/icons are unreadable */
|
||||
padding: 0 10px;
|
||||
/* need fixed-width for cpu/net/mem meaurements which change frequently */
|
||||
font-family: monospace;
|
||||
border-left: 1px solid #1f5e54;
|
||||
}
|
||||
|
||||
#battery.critical:not(.charging) {
|
||||
background-color: #f53c3c;
|
||||
background-color: #e64291;
|
||||
color: #ffffff;
|
||||
animation-name: blink;
|
||||
animation-duration: 0.5s;
|
||||
@@ -130,69 +85,8 @@ window#waybar.chromium {
|
||||
animation-direction: alternate;
|
||||
}
|
||||
|
||||
label:focus {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
#cpu {
|
||||
background-color: #2ecc71;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
#memory {
|
||||
background-color: #9b59b6;
|
||||
}
|
||||
|
||||
#disk {
|
||||
background-color: #964B00;
|
||||
}
|
||||
|
||||
#backlight {
|
||||
background-color: #90b1b1;
|
||||
}
|
||||
|
||||
#network {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
#network.disconnected {
|
||||
background-color: #f53c3c;
|
||||
}
|
||||
|
||||
#pulseaudio {
|
||||
background-color: #f1c40f;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
#pulseaudio.muted {
|
||||
background-color: #90b1b1;
|
||||
color: #2a5c45;
|
||||
}
|
||||
|
||||
#custom-media {
|
||||
background-color: #66cc99;
|
||||
color: #2a5c45;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
#custom-media.custom-spotify {
|
||||
background-color: #66cc99;
|
||||
}
|
||||
|
||||
#custom-media.custom-vlc {
|
||||
background-color: #ffa000;
|
||||
}
|
||||
|
||||
#temperature {
|
||||
background-color: #f0932b;
|
||||
}
|
||||
|
||||
#temperature.critical {
|
||||
background-color: #eb4d4b;
|
||||
}
|
||||
|
||||
#tray {
|
||||
background-color: #2980b9;
|
||||
background-color: #418379;
|
||||
}
|
||||
|
||||
#tray > .passive {
|
||||
@@ -201,56 +95,5 @@ label:focus {
|
||||
|
||||
#tray > .needs-attention {
|
||||
-gtk-icon-effect: highlight;
|
||||
background-color: #eb4d4b;
|
||||
background-color: #e64291;
|
||||
}
|
||||
|
||||
#idle_inhibitor {
|
||||
background-color: #2d3436;
|
||||
}
|
||||
|
||||
#idle_inhibitor.activated {
|
||||
background-color: #ecf0f1;
|
||||
color: #2d3436;
|
||||
}
|
||||
|
||||
#mpd {
|
||||
background-color: #66cc99;
|
||||
color: #2a5c45;
|
||||
}
|
||||
|
||||
#mpd.disconnected {
|
||||
background-color: #f53c3c;
|
||||
}
|
||||
|
||||
#mpd.stopped {
|
||||
background-color: #90b1b1;
|
||||
}
|
||||
|
||||
#mpd.paused {
|
||||
background-color: #51a37a;
|
||||
}
|
||||
|
||||
#language {
|
||||
background: #00b093;
|
||||
color: #740864;
|
||||
padding: 0 5px;
|
||||
margin: 0 5px;
|
||||
min-width: 16px;
|
||||
}
|
||||
|
||||
#keyboard-state {
|
||||
background: #97e1ad;
|
||||
color: #000000;
|
||||
padding: 0 0px;
|
||||
margin: 0 5px;
|
||||
min-width: 16px;
|
||||
}
|
||||
|
||||
#keyboard-state > label {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
#keyboard-state > label.locked {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
|
114
hosts/modules/gui/sway/waybar-top.nix
Normal file
114
hosts/modules/gui/sway/waybar-top.nix
Normal file
@@ -0,0 +1,114 @@
|
||||
# docs: <https://github.com/Alexays/Waybar/wiki/Configuration>
|
||||
# - custom modules: <https://github.com/Alexays/Waybar/wiki/Module:-Custom>
|
||||
# - format specifiers: <https://fmt.dev/latest/syntax.html#syntax>
|
||||
{ lib, pkgs }:
|
||||
let
|
||||
waybar-media = pkgs.static-nix-shell.mkBash {
|
||||
pname = "waybar-media";
|
||||
src = ./.;
|
||||
pkgs = [ "jq" "playerctl" ];
|
||||
};
|
||||
in
|
||||
{
|
||||
height = lib.mkDefault 40;
|
||||
modules-left = lib.mkDefault [ "sway/workspaces" ];
|
||||
modules-center = lib.mkDefault [ "sway/window" ];
|
||||
modules-right = lib.mkDefault [
|
||||
"custom/media"
|
||||
"custom/swaync"
|
||||
"clock"
|
||||
"battery"
|
||||
"memory"
|
||||
"cpu"
|
||||
"network"
|
||||
];
|
||||
|
||||
"sway/window" = {
|
||||
max-length = 50;
|
||||
};
|
||||
|
||||
"custom/media" = {
|
||||
# this module shows the actively playing song
|
||||
# - source: <https://www.reddit.com/r/swaywm/comments/ni0vso/waybar_spotify_tracktitle/>
|
||||
# - alternative: <https://github.com/Alexays/Waybar/wiki/Module:-MPRIS>
|
||||
# - alternative: <https://github.com/Alexays/Waybar/wiki/Module:-Custom#mpris-controller>
|
||||
#
|
||||
# N.B.: for this to behave well with multiple MPRIS clients,
|
||||
# `playerctld` must be enabled. see: <https://github.com/altdesktop/playerctl/issues/161>
|
||||
exec = "${waybar-media}/bin/waybar-media";
|
||||
return-type = "json";
|
||||
interval = 2;
|
||||
format = "{icon}{}";
|
||||
max-length = 50;
|
||||
format-icons = {
|
||||
playing = " ";
|
||||
paused = " ";
|
||||
inactive = "";
|
||||
};
|
||||
tooltip = false;
|
||||
on-click = "playerctl play-pause";
|
||||
on-scroll-up = "playerctl next";
|
||||
on-scroll-down = "playerctl previous";
|
||||
};
|
||||
"custom/swaync" = {
|
||||
# source: <https://github.com/ErikReider/SwayNotificationCenter#waybar-example>
|
||||
exec-if = "which swaync-client";
|
||||
exec = "swaync-client -swb";
|
||||
return-type = "json";
|
||||
escape = true;
|
||||
format = "{icon}"; # or "{icon} {}" to include notif count
|
||||
format-icons = {
|
||||
notification = "<span foreground='red'><sup></sup></span>";
|
||||
none = "";
|
||||
dnd-notification = "<span foreground='red'><sup></sup></span>";
|
||||
dnd-none = "";
|
||||
inhibited-notification = "<span foreground='red'><sup></sup></span>";
|
||||
inhibited-none = "";
|
||||
dnd-inhibited-notification = "<span foreground='red'><sup></sup></span>";
|
||||
dnd-inhibited-none = "";
|
||||
};
|
||||
tooltip = false;
|
||||
on-click = "swaync-client -t -sw";
|
||||
on-click-right = "swaync-client -d -sw";
|
||||
};
|
||||
network = {
|
||||
# docs: <https://github.com/Alexays/Waybar/blob/master/man/waybar-network.5.scd>
|
||||
interval = 2;
|
||||
max-length = 40;
|
||||
# custom :> format specifier explained here:
|
||||
# - <https://github.com/Alexays/Waybar/pull/472>
|
||||
format-ethernet = " {bandwidthUpBits:>}▲ {bandwidthDownBits:>}▼";
|
||||
tooltip-format-ethernet = "{ifname} {bandwidthUpBits:>}▲ {bandwidthDownBits:>}▼";
|
||||
|
||||
format-wifi = "{ifname} ({signalStrength}%) {bandwidthUpBits:>}▲ {bandwidthDownBits:>}▼";
|
||||
tooltip-format-wifi = "{essid} ({signalStrength}%) {bandwidthUpBits:>}▲ {bandwidthDownBits:>}▼";
|
||||
|
||||
format-disconnected = "";
|
||||
};
|
||||
cpu = {
|
||||
format = " {usage:2}%";
|
||||
tooltip = false;
|
||||
};
|
||||
memory = {
|
||||
format = "☵ {percentage:2}%";
|
||||
tooltip = false;
|
||||
};
|
||||
battery = {
|
||||
states = {
|
||||
good = 95;
|
||||
warning = 30;
|
||||
critical = 10;
|
||||
};
|
||||
format = "{icon} {capacity}%";
|
||||
format-icons = [
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
];
|
||||
};
|
||||
clock = {
|
||||
format-alt = "{:%a, %d. %b %H:%M}";
|
||||
};
|
||||
}
|
@@ -20,7 +20,8 @@
|
||||
#
|
||||
# sxmo technical overview:
|
||||
# - inputs
|
||||
# - dwm: handles vol/power buttons; hardcoded in config.h
|
||||
# - bonsaid: handles vol/power buttons
|
||||
# - it receives those buttons from dwm (if x11) harcoded in config.h or sway (if wayland)
|
||||
# - lisgd: handles gestures
|
||||
# - startup
|
||||
# - daemon based (lisgsd, idle_locker, statusbar_periodics)
|
||||
@@ -44,11 +45,11 @@
|
||||
# - menus: bemenu (if wayland), dmenu (if X)
|
||||
# - gestures: lisgd
|
||||
# - on-screen keyboard: wvkbd (if wayland), svkbd (if X)
|
||||
#
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.sane.gui.sxmo;
|
||||
package = cfg.package;
|
||||
knownKeyboards = {
|
||||
# map keyboard package name -> name of binary to invoke
|
||||
wvkbd = "wvkbd-mobintl";
|
||||
@@ -63,6 +64,33 @@ let
|
||||
echo "launching ${identifier}..." | ${systemd-cat} --identifier=${identifier}
|
||||
${cmd} 2>&1 | ${systemd-cat} --identifier=${identifier}
|
||||
'';
|
||||
|
||||
hookPkgs = {
|
||||
inputhandler = pkgs.static-nix-shell.mkBash {
|
||||
pname = "sxmo_hook_inputhandler.sh";
|
||||
src = ./hooks;
|
||||
pkgs = [ "coreutils" ];
|
||||
};
|
||||
postwake = pkgs.static-nix-shell.mkBash {
|
||||
pname = "sxmo_hook_postwake.sh";
|
||||
src = ./hooks;
|
||||
};
|
||||
rotate = pkgs.static-nix-shell.mkBash {
|
||||
pname = "sxmo_hook_rotate.sh";
|
||||
src = ./hooks;
|
||||
pkgs = [ "sway" ];
|
||||
};
|
||||
start = pkgs.static-nix-shell.mkBash {
|
||||
pname = "sxmo_hook_start.sh";
|
||||
src = ./hooks;
|
||||
pkgs = [ "systemd" "xdg-user-dirs" ];
|
||||
};
|
||||
suspend = pkgs.static-nix-shell.mkBash {
|
||||
pname = "sxmo_suspend.sh";
|
||||
src = ./hooks;
|
||||
pkgs = [ "coreutils" "util-linux" ];
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options = with lib; {
|
||||
@@ -73,10 +101,10 @@ in
|
||||
sane.gui.sxmo.greeter = mkOption {
|
||||
type = types.enum [
|
||||
"greetd-phog"
|
||||
"greetd-sway-gtkgreet"
|
||||
"greetd-sway-phog"
|
||||
"greetd-sxmo"
|
||||
"lightdm-mobile"
|
||||
"sway-gtkgreet"
|
||||
];
|
||||
# default = "lightdm-mobile";
|
||||
default = "greetd-sway-phog";
|
||||
@@ -88,20 +116,75 @@ in
|
||||
"greetd-sxmo" => launch sxmo directly from greetd, no auth.
|
||||
this means no keychain unlocked or encrypted home mounted.
|
||||
"lightdm-mobile" => keypad style greeter. can only enter digits 0-9 as password.
|
||||
"sway-gtkgreet" => layered sway greeter. behaves as if you booted to swaylock.
|
||||
this isn't practically usable on mobile because of keyboard input.
|
||||
also, it takes literally 6 minutes to appear
|
||||
"greetd-sway-gtkgreet" => layered sway greeter. keyboard-only user/pass input; impractical on mobile.
|
||||
'';
|
||||
};
|
||||
sane.gui.sxmo.package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.sxmo-utils-latest;
|
||||
default = pkgs.sxmo-utils-latest.override { preferSystemd = true; };
|
||||
description = ''
|
||||
sxmo base scripts and hooks collection.
|
||||
consider overriding the outputs under /share/sxmo/default_hooks
|
||||
to insert your own user scripts.
|
||||
'';
|
||||
};
|
||||
sane.gui.sxmo.hooks = mkOption {
|
||||
type = types.attrsOf types.path;
|
||||
default = {
|
||||
# default upstream hooks
|
||||
# additional hooks are in subdirectories like three_button_touchscreen/
|
||||
# - sxmo_hook_inputhandler.sh
|
||||
# - sxmo_hook_lock.sh
|
||||
# - sxmo_hook_postwake.sh
|
||||
# - sxmo_hook_screenoff.sh
|
||||
# - sxmo_hook_unlock.sh
|
||||
# by including hooks here, updating the sxmo package also updates the hooks
|
||||
# without requiring any reboot
|
||||
"sxmo_hook_apps.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_apps.sh";
|
||||
"sxmo_hook_block_suspend.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_block_suspend.sh";
|
||||
"sxmo_hook_call_audio.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_call_audio.sh";
|
||||
"sxmo_hook_contextmenu_fallback.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_contextmenu_fallback.sh";
|
||||
"sxmo_hook_contextmenu.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_contextmenu.sh";
|
||||
"sxmo_hook_desktop_widget.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_desktop_widget.sh";
|
||||
"sxmo_hook_discard.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_discard.sh";
|
||||
"sxmo_hook_hangup.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_hangup.sh";
|
||||
"sxmo_hook_icons.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_icons.sh";
|
||||
"sxmo_hook_lisgdstart.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_lisgdstart.sh";
|
||||
"sxmo_hook_logout.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_logout.sh";
|
||||
"sxmo_hook_missed_call.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_missed_call.sh";
|
||||
"sxmo_hook_mnc.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_mnc.sh";
|
||||
"sxmo_hook_modem.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_modem.sh";
|
||||
"sxmo_hook_mute_ring.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_mute_ring.sh";
|
||||
"sxmo_hook_network_down.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_network_down.sh";
|
||||
"sxmo_hook_network_pre_down.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_network_pre_down.sh";
|
||||
"sxmo_hook_network_pre_up.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_network_pre_up.sh";
|
||||
"sxmo_hook_network_up.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_network_up.sh";
|
||||
"sxmo_hook_notification.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_notification.sh";
|
||||
"sxmo_hook_notifications.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_notifications.sh";
|
||||
"sxmo_hook_pickup.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_pickup.sh";
|
||||
"sxmo_hook_restart_modem_daemons.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_restart_modem_daemons.sh";
|
||||
"sxmo_hook_ring.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_ring.sh";
|
||||
"sxmo_hook_rotate.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_rotate.sh";
|
||||
"sxmo_hook_scripts.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_scripts.sh";
|
||||
"sxmo_hook_sendsms.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_sendsms.sh";
|
||||
"sxmo_hook_smslog.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_smslog.sh";
|
||||
"sxmo_hook_sms.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_sms.sh";
|
||||
"sxmo_hook_start.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_start.sh";
|
||||
"sxmo_hook_statusbar.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_statusbar.sh";
|
||||
"sxmo_hook_stop.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_stop.sh";
|
||||
"sxmo_hook_tailtextlog.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_tailtextlog.sh";
|
||||
} // {
|
||||
# default hooks for this nix module, not upstreamable
|
||||
"sxmo_hook_inputhandler.sh" = "${hookPkgs.inputhandler}/bin/sxmo_hook_inputhandler.sh";
|
||||
"sxmo_hook_postwake.sh" = "${hookPkgs.postwake}/bin/sxmo_hook_postwake.sh";
|
||||
"sxmo_hook_rotate.sh" = "${hookPkgs.rotate}/bin/sxmo_hook_rotate.sh";
|
||||
"sxmo_hook_start.sh" = "${hookPkgs.start}/bin/sxmo_hook_start.sh";
|
||||
"sxmo_suspend.sh" = "${hookPkgs.suspend}/bin/sxmo_suspend.sh";
|
||||
};
|
||||
description = ''
|
||||
extra hooks to add with higher priority than the builtins
|
||||
'';
|
||||
};
|
||||
sane.gui.sxmo.terminal = mkOption {
|
||||
# type = types.nullOr (types.enum [ "foot" "st" "vte" ]);
|
||||
type = types.nullOr types.str;
|
||||
@@ -138,6 +221,7 @@ in
|
||||
SXMO_BAR_SHOW_BAT_PER = mkSettingsOpt "1" "show battery percentage in statusbar";
|
||||
SXMO_DISABLE_CONFIGVERSION_CHECK = mkSettingsOpt "1" "allow omitting the configversion line from user-provided sxmo dotfiles";
|
||||
SXMO_UNLOCK_IDLE_TIME = mkSettingsOpt "300" "how many seconds of inactivity before locking the screen"; # lock -> screenoff happens 8s later, not configurable
|
||||
# SXMO_WM = mkSettingsOpt "sway" "sway or dwm. ordinarily initialized by sxmo_{x,w}init.sh";
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
@@ -173,7 +257,8 @@ in
|
||||
|
||||
{
|
||||
# TODO: lift to option declaration
|
||||
sane.gui.sxmo.settings.TERMCMD = lib.mkIf (cfg.terminal != null)
|
||||
# N.B.: TERMCMD was renamed SXMO_TERMINAL on 2023/08/29
|
||||
sane.gui.sxmo.settings.SXMO_TERMINAL = lib.mkIf (cfg.terminal != null)
|
||||
(lib.mkDefault (knownTerminals."${cfg.terminal}" or cfg.terminal));
|
||||
sane.gui.sxmo.settings.KEYBOARD = lib.mkIf (cfg.keyboard != null)
|
||||
(lib.mkDefault (knownKeyboards."${cfg.keyboard}" or cfg.keyboard));
|
||||
@@ -181,64 +266,145 @@ in
|
||||
|
||||
(lib.mkIf cfg.enable (lib.mkMerge [
|
||||
{
|
||||
sane.gui.sway = {
|
||||
enable = true;
|
||||
# we manage the greeter ourselves (TODO: merge this into sway config as well)
|
||||
useGreeter = false;
|
||||
waybar.top = import ./waybar-top.nix;
|
||||
# reset extra waybar style
|
||||
waybar.extra_style = "";
|
||||
config = {
|
||||
# N.B. missing from upstream sxmo config here is:
|
||||
# - `bindsym $mod+g exec sxmo_hook_locker.sh`
|
||||
# - `bindsym $mod+t exec sxmo_appmenu.sh power`
|
||||
# - `bindsym $mod+i exec sxmo_wmmenu.sh windowswitcher`
|
||||
# - `bindsym $mod+p exec sxmo_appmenu.sh`
|
||||
# - `bindsym $mod+Shift+p exec sxmo_appmenu.sh sys`
|
||||
# - `input * xkb_options compose:ralt`
|
||||
# these could be added, but i don't see much benefit.
|
||||
font = "pango:monospace 10";
|
||||
mod = "Mod1"; # prefer Alt
|
||||
xwayland = false; # disable to reduce RAM usage.
|
||||
workspace_layout = "tabbed";
|
||||
|
||||
brightness_down_cmd = "sxmo_brightness.sh down";
|
||||
brightness_up_cmd = "sxmo_brightness.sh up";
|
||||
screenshot_cmd = "sxmo_screenshot.sh";
|
||||
extra_lines =
|
||||
let
|
||||
sxmo_init = pkgs.writeShellScript "sxmo_init.sh" ''
|
||||
# perform the same behavior as sxmo_{x,w}init.sh -- but without actually launching wayland/X11
|
||||
# this amounts to:
|
||||
# - setting env vars (e.g. getting the hooks onto PATH)
|
||||
# - placing default configs in ~ for sxmo-launched services (sxmo_migrate.sh)
|
||||
# - binding vol/power buttons (sxmo_swayinitconf.sh)
|
||||
# - launching sxmo_hook_start.sh
|
||||
#
|
||||
# the commands here are similar to upstream sxmo_winit.sh, but not identical and the ordering may be different
|
||||
|
||||
# profile may contain SXMO_DEVICE_NAME which is used by _sxmo_load_environment so load it early
|
||||
source "$XDG_CONFIG_HOME/sxmo/profile"
|
||||
# sourcing upstream sxmo_init.sh triggers _sxmo_load_environment
|
||||
# which ensures SXMO_* environment variables are set
|
||||
source ${package}/etc/profile.d/sxmo_init.sh
|
||||
# _sxmo_prepare_dirs ensures ~/.cache/sxmo & other XDG dirs exist with correct perms & owner
|
||||
_sxmo_prepare_dirs
|
||||
# migrate tells sxmo to provide the following default files:
|
||||
# - ~/.config/sxmo/profile
|
||||
# - ~/.config/fontconfig/conf.d/50-sxmo.conf
|
||||
# - ~/.config/sxmo/sway
|
||||
# - ~/.config/foot/foot.ini
|
||||
# - ~/.config/mako/config
|
||||
# - ~/.config/sxmo/bonsai_tree.json
|
||||
# - ~/.config/wob/wob.ini
|
||||
# - ~/.config/sxmo/conky.conf
|
||||
sxmo_migrate.sh sync
|
||||
# various things may have happened above that require me to re-load the profile here:
|
||||
# - _sxmo_load_environment sources a deviceprofile.sh file, which may override my profile settings.
|
||||
# very obvious if you set a non-default SXMO_SWAY_SCALE.
|
||||
# - sxmo_migrate.sh may have provided a default profile, if i failed to
|
||||
source "$XDG_CONFIG_HOME/sxmo/profile"
|
||||
# place my non-specialized hooks at higher precedence than the default device-hooks
|
||||
# alternative would be to move my hooks to ~/.config/sxmo/hooks/<device-name>.
|
||||
export PATH="$XDG_CONFIG_HOME/sxmo/hooks:$PATH"
|
||||
|
||||
# kill anything leftover from the previous sxmo run. this way we can (try to) be reentrant
|
||||
echo "sxmo_init: killing stale daemons (if active)"
|
||||
sxmo_daemons.sh stop all
|
||||
pkill bemenu
|
||||
pkill wvkbd
|
||||
pkill superd
|
||||
|
||||
# configure vol/power-button input mapping (upstream SXMO has this in sway config)
|
||||
echo "sxmo_init: configuring sway bindings/displays with:"
|
||||
echo "SXMO_POWER_BUTTON: $SXMO_POWER_BUTTON"
|
||||
echo "SXMO_VOLUME_BUTTON: $SXMO_VOLUME_BUTTON"
|
||||
echo "SXMO_SWAY_SCALE: $SXMO_SWAY_SCALE"
|
||||
sxmo_swayinitconf.sh
|
||||
|
||||
echo "sxmo_init: invoking sxmo_hook_start.sh with:"
|
||||
echo "PATH: $PATH"
|
||||
sxmo_hook_start.sh
|
||||
'';
|
||||
in ''
|
||||
# TODO: some of this is probably unnecessary
|
||||
mode "menu" {
|
||||
# just a placeholder for "menu" mode
|
||||
bindsym --input-device=1:1:1c21800.lradc XF86AudioMute exec nothing
|
||||
}
|
||||
bindsym button2 kill
|
||||
bindswitch lid:on exec sxmo_wm.sh dpms on
|
||||
bindswitch lid:off exec sxmo_wm.sh dpms off
|
||||
exec 'printf %s "$SWAYSOCK" > "$XDG_RUNTIME_DIR"/sxmo.swaysock'
|
||||
exec_always ${sxmo_init}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
sane.programs.sxmoApps.enableFor.user.colin = true;
|
||||
sane.gui.gtk.enable = lib.mkDefault true;
|
||||
|
||||
# sxmo internally uses doas instead of sudo
|
||||
security.doas.enable = true;
|
||||
security.doas.wheelNeedsPassword = false;
|
||||
|
||||
# TODO: move this further to the host-specific config?
|
||||
networking.useDHCP = false;
|
||||
networking.networkmanager.enable = true;
|
||||
networking.wireless.enable = lib.mkForce false;
|
||||
|
||||
hardware.bluetooth.enable = true;
|
||||
services.blueman.enable = true;
|
||||
|
||||
hardware.opengl.enable = true;
|
||||
|
||||
# TODO: nerdfonts is 4GB. it accepts an option to ship only some fonts: probably want to use that.
|
||||
fonts.packages = [ pkgs.nerdfonts ];
|
||||
|
||||
# lightdm-mobile-greeter: "The name org.a11y.Bus was not provided by any .service files"
|
||||
services.gnome.at-spi2-core.enable = true;
|
||||
|
||||
# sxmo has first-class support only for pulseaudio and alsa -- not pipewire.
|
||||
# however, pipewire can emulate pulseaudio support via `services.pipewire.pulse.enable = true`
|
||||
# after which the stock pulseaudio binaries magically work
|
||||
# administer with pw-cli, pw-mon, pw-top commands
|
||||
services.pipewire = {
|
||||
enable = true;
|
||||
alsa.enable = true;
|
||||
alsa.support32Bit = true; # ??
|
||||
pulse.enable = true;
|
||||
};
|
||||
systemd.user.services."pipewire".wantedBy = [ "graphical-session.target" ];
|
||||
|
||||
# TODO: could use `displayManager.sessionPackages`?
|
||||
environment.systemPackages = [
|
||||
cfg.package
|
||||
package
|
||||
pkgs.bonsai # sway (not sxmo) needs to exec `bonsaictl` by name (sxmo_swayinitconf.sh)
|
||||
] ++ lib.optionals (cfg.terminal != null) [ pkgs."${cfg.terminal}" ]
|
||||
++ lib.optionals (cfg.keyboard != null) [ pkgs."${cfg.keyboard}" ];
|
||||
|
||||
environment.sessionVariables = {
|
||||
XDG_DATA_DIRS = [
|
||||
# TODO: only need the share/sxmo directly linked
|
||||
"${cfg.package}/share"
|
||||
"${package}/share"
|
||||
];
|
||||
} // (lib.filterAttrs # certain settings are read before the `profile` is sourced
|
||||
(k: v: k == "SXMO_DISABLE_CONFIGVERSION_CHECK")
|
||||
} // (lib.filterAttrs (k: v:
|
||||
k == "SXMO_DISABLE_CONFIGVERSION_CHECK" # read before `profile` is sourced
|
||||
|| k == "SXMO_TERMINAL" # for apps launched via `swaymsg exec -- sxmo_terminal.sh ...`
|
||||
)
|
||||
cfg.settings
|
||||
);
|
||||
|
||||
# sxmo puts in /share/sxmo:
|
||||
# - profile.d/sxmo_init.sh
|
||||
# - appcfg/
|
||||
# - default_hooks/
|
||||
# - and more
|
||||
# environment.pathsToLink = [ "/share/sxmo" ];
|
||||
|
||||
systemd.services."sxmo-set-permissions" = {
|
||||
# TODO: some of these could be modified to be udev rules
|
||||
description = "configure specific /sys and /dev nodes to be writable by sxmo scripts";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${cfg.package}/bin/sxmo_setpermissions.sh";
|
||||
ExecStart = "${package}/bin/sxmo_setpermissions.sh";
|
||||
};
|
||||
wantedBy = [ "display-manager.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
# if superd fails to start a service within 100ms, it'll try to start again
|
||||
@@ -266,40 +432,92 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
sane.user.fs.".cache/sxmo/sxmo.noidle" = lib.mkIf cfg.noidle {
|
||||
sane.user.fs = lib.mkMerge [
|
||||
{
|
||||
# link the superd services into a place where systemd can find them.
|
||||
# the unit files should be compatible, except maybe for PATH handling
|
||||
# ".config/systemd/user/autocutsel-primary.service".symlink.target = "${package}/share/superd/services/autocutsel-primary.service";
|
||||
# ".config/systemd/user/autocutsel.service".symlink.target = "${package}/share/superd/services/autocutsel.service";
|
||||
# ".config/systemd/user/bonsaid.service".symlink.target = "${package}/share/superd/services/bonsaid.service";
|
||||
# # ".config/systemd/user/dunst.service".symlink.target = "${package}/share/superd/services/dunst.service";
|
||||
# # ".config/systemd/user/mako.service".symlink.target = "${package}/share/superd/services/mako.service";
|
||||
# ".config/systemd/user/mmsd-tng.service".symlink.target = "${package}/share/superd/services/mmsd-tng.service";
|
||||
# ".config/systemd/user/sxmo_autosuspend.service".symlink.target = "${package}/share/superd/services/sxmo_autosuspend.service";
|
||||
# ".config/systemd/user/sxmo_battery_monitor.service".symlink.target = "${package}/share/superd/services/sxmo_battery_monitor.service";
|
||||
# ".config/systemd/user/sxmo_conky.service".symlink.target = "${package}/share/superd/services/sxmo_conky.service";
|
||||
# ".config/systemd/user/sxmo_desktop_widget.service".symlink.target = "${package}/share/superd/services/sxmo_desktop_widget.service";
|
||||
# ".config/systemd/user/sxmo_hook_lisgd.service".symlink.target = "${package}/share/superd/services/sxmo_hook_lisgd.service";
|
||||
# ".config/systemd/user/sxmo_menumode_toggler.service".symlink.target = "${package}/share/superd/services/sxmo_menumode_toggler.service";
|
||||
# ".config/systemd/user/sxmo_modemmonitor.service".symlink.target = "${package}/share/superd/services/sxmo_modemmonitor.service";
|
||||
# ".config/systemd/user/sxmo_networkmonitor.service".symlink.target = "${package}/share/superd/services/sxmo_networkmonitor.service";
|
||||
# ".config/systemd/user/sxmo_notificationmonitor.service".symlink.target = "${package}/share/superd/services/sxmo_notificationmonitor.service";
|
||||
# ".config/systemd/user/sxmo_soundmonitor.service".symlink.target = "${package}/share/superd/services/sxmo_soundmonitor.service";
|
||||
# ".config/systemd/user/sxmo_wob.service".symlink.target = "${package}/share/superd/services/sxmo_wob.service";
|
||||
# ".config/systemd/user/sxmo-x11-status.service".symlink.target = "${package}/share/superd/services/sxmo-x11-status.service";
|
||||
# ".config/systemd/user/unclutter.service".symlink.target = "${package}/share/superd/services/unclutter.service";
|
||||
# ".config/systemd/user/unclutter-xfixes.service".symlink.target = "${package}/share/superd/services/unclutter-xfixes.service";
|
||||
# ".config/systemd/user/vvmd.service".symlink.target = "${package}/share/superd/services/vvmd.service";
|
||||
|
||||
# service code further below tells systemd to put ~/.config/sxmo/hooks on PATH, but it puts hooks/bin on PATH instead, so symlink that
|
||||
".config/sxmo/hooks/bin".symlink.target = ".";
|
||||
|
||||
".cache/sxmo/sxmo.noidle" = lib.mkIf cfg.noidle {
|
||||
symlink.text = "";
|
||||
};
|
||||
sane.user.fs.".cache/sxmo/sxmo.nogesture" = lib.mkIf cfg.nogesture {
|
||||
".cache/sxmo/sxmo.nogesture" = lib.mkIf cfg.nogesture {
|
||||
symlink.text = "";
|
||||
};
|
||||
sane.user.fs.".config/sxmo/profile".symlink.text = let
|
||||
".config/sxmo/profile".symlink.text = let
|
||||
mkKeyValue = key: value: ''export ${key}="${value}"'';
|
||||
in
|
||||
lib.generators.toKeyValue { inherit mkKeyValue; } cfg.settings;
|
||||
}
|
||||
(lib.mapAttrs' (name: value: {
|
||||
# sxmo's `_sxmo_load_environments` adds to PATH:
|
||||
# - ~/.config/sxmo/hooks/$SXMO_DEVICE_NAME
|
||||
# - ~/.config/sxmo/hooks
|
||||
name = ".config/sxmo/hooks/${name}";
|
||||
value.symlink.target = value;
|
||||
}) cfg.hooks)
|
||||
];
|
||||
|
||||
sane.user.fs.".config/sxmo/sway".symlink.target = pkgs.substituteAll {
|
||||
src = ./sway-config;
|
||||
waybar = "${pkgs.waybar}/bin/waybar";
|
||||
sane.user.services = let
|
||||
sxmoPath = [ "/home/colin/.config/sxmo/hooks" package ] ++ package.runtimeDeps ++ [
|
||||
"/run/current-system/sw" # for flock wrapper
|
||||
"/run/wrappers" # for doas
|
||||
];
|
||||
sxmoService = name: {
|
||||
description = "sxmo ${name}";
|
||||
path = sxmoPath;
|
||||
serviceConfig.ExecStart = "${pkgs.coreutils}/bin/env sxmo_${name}.sh";
|
||||
serviceConfig.Type = "simple";
|
||||
serviceConfig.Restart = "always";
|
||||
serviceConfig.RestartSec = "20s";
|
||||
};
|
||||
in {
|
||||
sxmo_autosuspend = sxmoService "autosuspend";
|
||||
sxmo_battery_monitor = sxmoService "battery_monitor";
|
||||
sxmo_desktop_widget = sxmoService "hook_desktop_widget";
|
||||
sxmo_hook_lisgd = sxmoService "hook_lisgdstart";
|
||||
sxmo_menumode_toggler = sxmoService "menumode_toggler";
|
||||
sxmo_modemmonitor = sxmoService "modemmonitor";
|
||||
sxmo_networkmonitor = sxmoService "networkmonitor";
|
||||
sxmo_notificationmonitor = sxmoService "notificationmonitor";
|
||||
sxmo_soundmonitor = sxmoService "soundmonitor";
|
||||
sxmo_wob = sxmoService "wob";
|
||||
sxmo-x11-status = sxmoService "status_xsetroot";
|
||||
|
||||
sane.user.fs.".config/waybar/config".symlink.target =
|
||||
let
|
||||
waybar-config = import ./waybar-config.nix { inherit pkgs; };
|
||||
in
|
||||
(pkgs.formats.json {}).generate "waybar-config.json" waybar-config;
|
||||
|
||||
sane.user.fs.".config/waybar/style.css".symlink.text =
|
||||
builtins.readFile ./waybar-style.css;
|
||||
|
||||
sane.user.fs.".config/sxmo/conky.conf".symlink.target = let
|
||||
battery_estimate = pkgs.static-nix-shell.mkBash {
|
||||
pname = "battery_estimate";
|
||||
src = ./.;
|
||||
bonsaid = {
|
||||
description = "programmable input dispatcher";
|
||||
path = sxmoPath;
|
||||
# systemd expands %E to $XDG_CONFIG_HOME
|
||||
serviceConfig.ExecStart = "${pkgs.bonsai}/bin/bonsaid -t %E/sxmo/bonsai_tree.json";
|
||||
serviceConfig.ExecStartPre = "${pkgs.coreutils}/bin/rm -f %t/bonsai"; # systemd expands %t to $XDG_RUNTIME_DIR
|
||||
serviceConfig.Type = "simple";
|
||||
serviceConfig.Restart = "always";
|
||||
serviceConfig.RestartSec = "5s";
|
||||
environment.SXMO_STATE = "/run/user/1000/sxmo.state"; #< TODO: this is $XDG_RUNTIME_DIR/sxmo.state
|
||||
};
|
||||
in pkgs.substituteAll {
|
||||
src = ./conky-config;
|
||||
bat = "${battery_estimate}/bin/battery_estimate";
|
||||
weather = "timeout 20 ${pkgs.sane-weather}/bin/sane-weather";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -320,7 +538,7 @@ in
|
||||
'';
|
||||
|
||||
displayManager.sessionPackages = with pkgs; [
|
||||
cfg.package # this gets share/wayland-sessions/swmo.desktop linked
|
||||
package # this gets share/wayland-sessions/swmo.desktop linked
|
||||
];
|
||||
|
||||
# taken from gui/phosh:
|
||||
@@ -334,78 +552,43 @@ in
|
||||
};
|
||||
})
|
||||
|
||||
(lib.mkIf (cfg.greeter == "sway-gtkgreet") {
|
||||
services.greetd = {
|
||||
(lib.mkIf (cfg.greeter == "greetd-sway-gtkgreet") {
|
||||
sane.gui.greetd = {
|
||||
enable = true;
|
||||
# borrowed from gui/sway
|
||||
settings.default_session.command =
|
||||
let
|
||||
# start sway and have it construct the gtkgreeter
|
||||
sway-as-greeter = runWithLogger "sway-as-greeter" "${pkgs.sway}/bin/sway --debug --config ${sway-config-into-gtkgreet}";
|
||||
# (config file for the above)
|
||||
sway-config-into-gtkgreet = pkgs.writeText "greetd-sway-config" ''
|
||||
exec "${gtkgreet-launcher}"
|
||||
'';
|
||||
# gtkgreet which launches a layered sway instance
|
||||
gtkgreet-launcher = pkgs.writeShellScript "gtkgreet-launcher" ''
|
||||
# NB: the "command" field here is run in the user's shell.
|
||||
# so that command must exist on the specific user's path who is logging in. it doesn't need to exist system-wide.
|
||||
${pkgs.greetd.gtkgreet}/bin/gtkgreet --layer-shell --command sxmo_winit.sh
|
||||
'';
|
||||
in "${sway-as-greeter}";
|
||||
sway.enable = true;
|
||||
sway.gtkgreet.enable = true;
|
||||
sway.gtkgreet.session.name = "sxmo-on-gtkgreet";
|
||||
sway.gtkgreet.session.command = "${pkgs.sway}/bin/sway --debug";
|
||||
};
|
||||
})
|
||||
|
||||
(lib.mkIf (cfg.greeter == "greetd-sway-phog") {
|
||||
services.greetd = {
|
||||
sane.gui.greetd = {
|
||||
enable = true;
|
||||
# borrowed from gui/sway
|
||||
settings.default_session.command =
|
||||
let
|
||||
# start sway and have it construct the greeter
|
||||
sway-as-greeter = runWithLogger "sway-as-greeter" "${pkgs.sway}/bin/sway --debug --config ${sway-config-into-phog}";
|
||||
# (config file for the above)
|
||||
sway-config-into-phog = pkgs.writeText "greetd-sway-config" ''
|
||||
exec "${pkgs.phog}/libexec/phog"
|
||||
'';
|
||||
in "${sway-as-greeter}";
|
||||
sway.enable = true;
|
||||
sway.greeterCmd = "${pkgs.phog}/libexec/phog";
|
||||
};
|
||||
# phog locates sxmo_winit.sh via <env>/share/wayland-sessions
|
||||
# phog locates sxmo_winit.sh (or sway.desktop) via <env>/share/wayland-sessions
|
||||
environment.pathsToLink = [ "/share/wayland-sessions" ];
|
||||
|
||||
# persisting fontconfig & mesa_shader_cache improves start time from like 6 minutes to 1 minute
|
||||
# TODO: this should apply to any greetd implementation
|
||||
users.users.greeter.home = "/var/lib/greeter";
|
||||
sane.persist.sys.plaintext = [
|
||||
{ user = "greeter"; group = "greeter"; path = "/var/lib/greeter/.cache/fontconfig"; }
|
||||
{ user = "greeter"; group = "greeter"; path = "/var/lib/greeter/.cache/mesa_shader_cache"; }
|
||||
];
|
||||
})
|
||||
|
||||
(lib.mkIf (cfg.greeter == "greetd-phog") {
|
||||
services.greetd = {
|
||||
sane.gui.greetd = {
|
||||
enable = true;
|
||||
|
||||
# launch directly: but stdout/stderr gets dropped
|
||||
# settings.default_session.command = "${pkgs.phog}/bin/phog";
|
||||
|
||||
# wrapper to launch phog and redirect logs to system journal.
|
||||
settings.default_session.command = let
|
||||
launch-phog = runWithLogger "phog" "${pkgs.phog}/bin/phog";
|
||||
in "${launch-phog}";
|
||||
session.name = "phog";
|
||||
session.command = "${pkgs.phog}/bin/phog";
|
||||
};
|
||||
# phog locates sxmo_winit.sh (or sway.desktop) via <env>/share/wayland-sessions
|
||||
environment.pathsToLink = [ "/share/wayland-sessions" ];
|
||||
})
|
||||
|
||||
(lib.mkIf (cfg.greeter == "greetd-sxmo") {
|
||||
services.greetd = {
|
||||
sane.gui.greetd = {
|
||||
enable = true;
|
||||
settings.default_session = {
|
||||
command = let
|
||||
launch-sxmo = runWithLogger "sxmo" "${cfg.package}/bin/sxmo_winit.sh";
|
||||
in "${launch-sxmo}";
|
||||
user = "colin";
|
||||
};
|
||||
session.name = "sxmo";
|
||||
# session.command = "${package}/bin/sxmo_winit.sh";
|
||||
session.command = "${pkgs.sway}/bin/sway --debug";
|
||||
session.user = "colin";
|
||||
};
|
||||
})
|
||||
|
||||
@@ -414,7 +597,7 @@ in
|
||||
# name = "sxmo";
|
||||
# desktopNames = [ "sxmo" ];
|
||||
# start = ''
|
||||
# ${cfg.package}/bin/sxmo_xinit.sh &
|
||||
# ${package}/bin/sxmo_xinit.sh &
|
||||
# waitPID=$!
|
||||
# '';
|
||||
# }];
|
||||
|
142
hosts/modules/gui/sxmo/hooks/sxmo_hook_inputhandler.sh
Executable file
142
hosts/modules/gui/sxmo/hooks/sxmo_hook_inputhandler.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p coreutils
|
||||
|
||||
# input map considerations
|
||||
# - using compound actions causes delays.
|
||||
# e.g. if volup->volup is a distinct action from volup, then single-volup action is forced to wait the maximum button delay.
|
||||
# - actions which are to be responsive should therefore have a dedicated key.
|
||||
# - a dedicated "kill" combo is important for unresponsive fullscreen apps, because appmenu doesn't show in those
|
||||
# - although better may be to force appmenu to show over FS apps
|
||||
# - bonsai mappings are static, so buttons can't benefit from non-compounding unless they're mapped accordingly for all lock states
|
||||
# - this limitation could be removed, but with work
|
||||
#
|
||||
# proposed future design:
|
||||
# - when unlocked:
|
||||
# - volup1 -> app menu
|
||||
# - voldown1 -> toggle keyboard
|
||||
# - pow1 -> volup1 -> volume up
|
||||
# - pow1 -> voldown1 -> volume down
|
||||
# - pow2 -> screen off
|
||||
# - pow3 -> kill app
|
||||
# - when locked:
|
||||
# - volup1 -> volume up
|
||||
# - voldown1 -> volume down
|
||||
# - pow1 -> screen on
|
||||
# - pow2 -> toggle player
|
||||
# benefits
|
||||
# - volup and voldown are able to be far more responsive
|
||||
# - which means faster vkbd, menus, volume adjustment (when locked)
|
||||
# limitations
|
||||
# - terminal is unmapped. that could be mapped to pow1?
|
||||
# - wm menu is unmapped. but i never used that much anyway
|
||||
|
||||
# increments to use for volume adjustment
|
||||
VOL_INCR_1=5
|
||||
VOL_INCR_2=10
|
||||
VOL_INCR_3=15
|
||||
|
||||
# replicating the naming from upstream sxmo_hook_inputhandler.sh...
|
||||
ACTION="$1"
|
||||
STATE=$(cat "$SXMO_STATE")
|
||||
|
||||
|
||||
handle_with() {
|
||||
echo "sxmo_hook_inputhandler.sh: STATE=$STATE ACTION=$ACTION: handle_with: $@"
|
||||
"$@"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# handle_with_state_toggle() {
|
||||
# # - unlock,lock => screenoff
|
||||
# # - screenoff => unlock
|
||||
# #
|
||||
# # probably not handling proximity* correctly here
|
||||
# case "$STATE" in
|
||||
# *lock)
|
||||
# respond_with sxmo_state_switch.sh set screenoff
|
||||
# *)
|
||||
# respond_with sxmo_state_switch.sh set unlock
|
||||
# esac
|
||||
# }
|
||||
|
||||
# state is one of:
|
||||
# - "unlock" => normal operation; display on and touchscreen on
|
||||
# - "screenoff" => display off and touchscreen off
|
||||
# - "lock" => display on but touchscreen disabled
|
||||
# - "proximity{lock,unlock}" => intended for when in a phone call
|
||||
|
||||
if [ "$STATE" = "unlock" ]; then
|
||||
case "$ACTION" in
|
||||
# powerbutton_one: intentional default to no-op
|
||||
# powerbutton_two: intentional default to screenoff
|
||||
"powerbutton_three")
|
||||
# power thrice: kill active window
|
||||
handle_with sxmo_killwindow.sh
|
||||
;;
|
||||
|
||||
"volup_one")
|
||||
# volume up once: app-specific menu w/ fallback to SXMO system menu
|
||||
handle_with sxmo_appmenu.sh
|
||||
;;
|
||||
# volup_two: intentionally defaulted for volume control
|
||||
"volup_three")
|
||||
# volume up thrice: DE menu
|
||||
handle_with sxmo_wmmenu.sh
|
||||
;;
|
||||
|
||||
"voldown_one")
|
||||
# volume down once: toggle keyboard
|
||||
handle_with sxmo_keyboard.sh toggle
|
||||
;;
|
||||
# voldown_two: intentionally defaulted for volume control
|
||||
"voldown_three")
|
||||
# volume down thrice: launch terminal
|
||||
handle_with sxmo_terminal.sh
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ "$STATE" = "screenoff" ]; then
|
||||
case "$ACTION" in
|
||||
"powerbutton_two")
|
||||
# power twice => toggle media player
|
||||
handle_with playerctl play-pause
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# default actions
|
||||
case "$ACTION" in
|
||||
"powerbutton_one")
|
||||
# power once => unlock
|
||||
handle_with sxmo_state_switch.sh set unlock
|
||||
;;
|
||||
"powerbutton_two")
|
||||
# power twice => screenoff
|
||||
handle_with sxmo_state_switch.sh set screenoff
|
||||
;;
|
||||
# powerbutton_three: intentional no-op because overloading the kill-window handler is risky
|
||||
|
||||
"volup_one")
|
||||
handle_with sxmo_audio.sh vol up "$VOL_INCR_1"
|
||||
;;
|
||||
"volup_two")
|
||||
handle_with sxmo_audio.sh vol up "$VOL_INCR_2"
|
||||
;;
|
||||
"volup_three")
|
||||
handle_with sxmo_audio.sh vol up "$VOL_INCR_3"
|
||||
;;
|
||||
|
||||
"voldown_one")
|
||||
handle_with sxmo_audio.sh vol down "$VOL_INCR_1"
|
||||
;;
|
||||
"voldown_two")
|
||||
handle_with sxmo_audio.sh vol down "$VOL_INCR_2"
|
||||
;;
|
||||
"voldown_three")
|
||||
handle_with sxmo_audio.sh vol down "$VOL_INCR_3"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
handle_with echo "no-op"
|
7
hosts/modules/gui/sxmo/hooks/sxmo_hook_postwake.sh
Executable file
7
hosts/modules/gui/sxmo/hooks/sxmo_hook_postwake.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash
|
||||
|
||||
# the default sxmo_postwake handler checks if the modem is offline
|
||||
# and if so installs a wakelock to block suspend for 30s.
|
||||
# that's a questionable place to install that logic, and i want to keep stuff
|
||||
# out of the wake-from-sleep critical path, so i'm overriding this hook to disable that.
|
16
hosts/modules/gui/sxmo/hooks/sxmo_hook_rotate.sh
Executable file
16
hosts/modules/gui/sxmo/hooks/sxmo_hook_rotate.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p sway
|
||||
|
||||
# called whenever sxmo_rotate.sh is invoked.
|
||||
# i.e. whenever the screen is rotated manually, or automatically if autorotate is enabled.
|
||||
# $1 = the new orientation
|
||||
# possible values are "normal", "invert", "left" and "right"
|
||||
|
||||
# exit fullscreen, if any app is in FS.
|
||||
# this benefits UX because:
|
||||
# - most of my FS use is in landscape mode
|
||||
# - when i toggle apps or desktops i always do so in portrait mode
|
||||
# - therefore when i rotate landscape -> portrait mode, i almost never want fullscreen anymore.
|
||||
if [ "$1" = "normal" ] || [ "$1" = "invert" ]; then
|
||||
swaymsg fullscreen disable
|
||||
fi
|
88
hosts/modules/gui/sxmo/hooks/sxmo_hook_start.sh
Executable file
88
hosts/modules/gui/sxmo/hooks/sxmo_hook_start.sh
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p systemd -p xdg-user-dirs
|
||||
# this is based on upstream sxmo-utils sxmo_hook_start.sh
|
||||
# but modified for nixos integration and specialize a bit to my needs
|
||||
. sxmo_common.sh
|
||||
|
||||
# Create xdg user directories, such as ~/Pictures
|
||||
xdg-user-dirs-update
|
||||
|
||||
sxmo_daemons.sh start daemon_manager
|
||||
|
||||
# Periodically update some status bar components
|
||||
sxmo_hook_statusbar.sh all
|
||||
sxmo_daemons.sh start statusbar_periodics sxmo_run_aligned.sh 60 \
|
||||
sxmo_hook_statusbar.sh periodics
|
||||
|
||||
# TODO: start these externally, via `wantedBy` in nix
|
||||
# don't: mako is managed externally
|
||||
# superctl start mako
|
||||
systemctl --user start sxmo_wob
|
||||
systemctl --user start sxmo_menumode_toggler
|
||||
systemctl --user start bonsaid
|
||||
# don't: sway background is managed externally
|
||||
# swaymsg output '*' bg "$SXMO_BG_IMG" fill
|
||||
|
||||
# To setup initial lock state
|
||||
sxmo_state_switch.sh set unlock
|
||||
|
||||
# Turn on auto-suspend
|
||||
if [ -w "/sys/power/wakeup_count" ] && [ -f "/sys/power/wake_lock" ]; then
|
||||
systemctl --user start sxmo_autosuspend
|
||||
fi
|
||||
|
||||
# Turn on lisgd
|
||||
if [ ! -e "$XDG_CACHE_HOME"/sxmo/sxmo.nogesture ]; then
|
||||
systemctl --user start sxmo_hook_lisgd
|
||||
fi
|
||||
|
||||
if [ "$(command -v ModemManager)" ]; then
|
||||
# Turn on the dbus-monitors for modem-related tasks
|
||||
systemctl --user start sxmo_modemmonitor
|
||||
|
||||
# place a wakelock for 120s to allow the modem to fully warm up (eg25 +
|
||||
# elogind/systemd would do this for us, but we don't use those.)
|
||||
sxmo_wakelock.sh lock sxmo_modem_warming_up 120s
|
||||
fi
|
||||
|
||||
# don't: conky is managed externally
|
||||
# superctl start sxmo_conky
|
||||
|
||||
# Monitor the battery
|
||||
systemctl --user start sxmo_battery_monitor
|
||||
|
||||
# It watch network changes and update the status bar icon by example
|
||||
systemctl --user start sxmo_networkmonitor
|
||||
|
||||
# The daemon that display notifications popup messages
|
||||
systemctl --user start sxmo_notificationmonitor
|
||||
|
||||
# monitor for headphone for statusbar
|
||||
systemctl --user start sxmo_soundmonitor
|
||||
|
||||
# rotate UI based on physical display angle by default
|
||||
if [ -n "$SXMO_AUTOROTATE" ]; then
|
||||
# TODO: this could use ~/.cache/sxmo/sxmo.autorotate like for lisgd above
|
||||
sxmo_daemons.sh start autorotate sxmo_autorotate.sh
|
||||
fi
|
||||
|
||||
# Play a funky startup tune if you want (disabled by default)
|
||||
#mpv --quiet --no-video ~/welcome.ogg &
|
||||
|
||||
# mmsd and vvmd
|
||||
if [ -f "${SXMO_MMS_BASE_DIR:-"$HOME"/.mms/modemmanager}/mms" ]; then
|
||||
systemctl --user start mmsd-tng
|
||||
fi
|
||||
|
||||
if [ -f "${SXMO_VVM_BASE_DIR:-"$HOME"/.vvm/modemmanager}/vvm" ]; then
|
||||
systemctl --user start vvmd
|
||||
fi
|
||||
|
||||
# add some warnings if things are not setup correctly
|
||||
if ! command -v "sxmo_deviceprofile_$SXMO_DEVICE_NAME.sh"; then
|
||||
sxmo_notify_user.sh --urgency=critical \
|
||||
"No deviceprofile found $SXMO_DEVICE_NAME. See: https://sxmo.org/deviceprofile"
|
||||
fi
|
||||
|
||||
sxmo_migrate.sh state || sxmo_notify_user.sh --urgency=critical \
|
||||
"Config needs migration" "$? file(s) in your sxmo configuration are out of date and disabled - using defaults until you migrate (run sxmo_migrate.sh)"
|
21
hosts/modules/gui/sxmo/hooks/sxmo_suspend.sh
Executable file
21
hosts/modules/gui/sxmo/hooks/sxmo_suspend.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p coreutils -p util-linux
|
||||
|
||||
# yeah, this isn't technically a hook, but the hook infrastructure isn't actually
|
||||
# restricted to stuff that starts with sxmo_hook_ ...
|
||||
#
|
||||
# this script is only called by sxmo_autosuspend, which is small, so if i wanted to
|
||||
# be more proper i could instead re-implement autosuspend + integrations.
|
||||
|
||||
. sxmo_common.sh
|
||||
|
||||
suspend_time=300
|
||||
|
||||
sxmo_log "calling suspend for duration: $suspend_time"
|
||||
|
||||
rtcwake -m mem -s "$suspend_time" || exit 1
|
||||
|
||||
sxmo_log "exited suspend: $(cat /proc/net/rtl8723cs/wlan0/wowlan_last_wake_reason)"
|
||||
|
||||
sxmo_hook_postwake.sh
|
||||
|
@@ -1,266 +0,0 @@
|
||||
# Read `man 5 sway` for a complete reference.
|
||||
|
||||
# TODO: use stock sxmo config & override via /etc/sway/config.d/*
|
||||
# especially, this will let me avoid issues around `configversion`
|
||||
|
||||
### Variables
|
||||
#
|
||||
# Mod4 = Logo key
|
||||
# Mod1 = Alt.
|
||||
set $mod Mod1
|
||||
# Home row direction keys, like vim
|
||||
set $left h
|
||||
set $down j
|
||||
set $up k
|
||||
set $right l
|
||||
# Your preferred terminal emulator
|
||||
set $term sxmo_terminal.sh
|
||||
# Your preferred application launcher
|
||||
# Note: pass the final command to swaymsg so that the resulting window can be opened
|
||||
# on the original workspace that the command was run on.
|
||||
set $menu bemenu-run
|
||||
|
||||
# xwayland enable|disable|force
|
||||
# - enable: lazily launch xwayland on first client connection
|
||||
# - disable: never launch xwayland
|
||||
# - force: launch xwayland immediately on boot
|
||||
# XWayland uses about 35MB RSS even when idle
|
||||
xwayland disable
|
||||
|
||||
font "Sxmo 10"
|
||||
|
||||
exec_always sxmo_swayinitconf.sh
|
||||
|
||||
exec_always dbus-update-activation-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
|
||||
|
||||
mode "menu" {
|
||||
bindsym --input-device=1:1:1c21800.lradc XF86AudioMute exec nothing # just a placeholder for "menu" mode
|
||||
}
|
||||
|
||||
hide_edge_borders smart
|
||||
default_border pixel 1
|
||||
titlebar_border_thickness 1
|
||||
# XX YY distance from edge of window title to edge of text
|
||||
# the YY distance here determines the heigh of the overall title
|
||||
titlebar_padding 12 1
|
||||
title_align center
|
||||
|
||||
# tabbed windows by default
|
||||
workspace_layout tabbed
|
||||
|
||||
### tab colors (#border #background #text [#indicator #childBorder])
|
||||
# focused & unfocused are the main interest
|
||||
# urgent is used when an inactive window wants attention (e.g. terminal rings a bell)
|
||||
# colors are synchronized with waybar and mpv
|
||||
client.focused #1f5e54 #418379 #ffffff
|
||||
client.focused_inactive #1f5e54 #5f676a #ffffff
|
||||
client.unfocused #1f5e54 #1f554c #b4b4b4
|
||||
client.urgent #ff8080 #ff8080 #ffffff
|
||||
|
||||
### Key bindings
|
||||
#
|
||||
# Basics:
|
||||
#
|
||||
input * xkb_options compose:ralt
|
||||
|
||||
# Start a terminal
|
||||
bindsym $mod+Return exec $term
|
||||
|
||||
# Launch appmenu
|
||||
bindsym $mod+p exec sxmo_appmenu.sh
|
||||
bindsym $mod+Shift+p exec sxmo_appmenu.sh sys
|
||||
|
||||
# Wm menu switcher
|
||||
bindsym $mod+i exec sxmo_wmmenu.sh windowswitcher
|
||||
|
||||
# Kill focused window
|
||||
bindsym $mod+Shift+q kill
|
||||
|
||||
# Start your launcher
|
||||
bindsym $mod+d exec $menu
|
||||
|
||||
# Drag floating windows by holding down $mod and left mouse button.
|
||||
# Resize them with right mouse button + $mod.
|
||||
# Despite the name, also works for non-floating windows.
|
||||
# Change normal to inverse to use left mouse button for resizing and right
|
||||
# mouse button for dragging.
|
||||
floating_modifier $mod normal
|
||||
|
||||
# Reload the configuration file
|
||||
bindsym $mod+Shift+c reload
|
||||
|
||||
# Exit sway (logs you out of your Wayland session)
|
||||
bindsym $mod+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'
|
||||
#
|
||||
# Moving around:
|
||||
#
|
||||
# Move your focus around
|
||||
bindsym $mod+$left focus left
|
||||
bindsym $mod+$down focus down
|
||||
bindsym $mod+$up focus up
|
||||
bindsym $mod+$right focus right
|
||||
# Or use $mod+[up|down|left|right]
|
||||
bindsym $mod+Left focus left
|
||||
bindsym $mod+Down focus down
|
||||
bindsym $mod+Up focus up
|
||||
bindsym $mod+Right focus right
|
||||
|
||||
# Move the focused window with the same, but add Shift
|
||||
bindsym $mod+Shift+$left move left
|
||||
bindsym $mod+Shift+$down move down
|
||||
bindsym $mod+Shift+$up move up
|
||||
bindsym $mod+Shift+$right move right
|
||||
# Ditto, with arrow keys
|
||||
bindsym $mod+Shift+Left move left
|
||||
bindsym $mod+Shift+Down move down
|
||||
bindsym $mod+Shift+Up move up
|
||||
bindsym $mod+Shift+Right move right
|
||||
|
||||
# Move the focused workspace to output
|
||||
bindsym $mod+Shift+Ctrl+$left move workspace output left
|
||||
bindsym $mod+Shift+Ctrl+$down move workspace output down
|
||||
bindsym $mod+Shift+Ctrl+$up move workspace output up
|
||||
bindsym $mod+Shift+Ctrl+$right move workspace output right
|
||||
#
|
||||
# Workspaces:
|
||||
#
|
||||
# Switch to workspace
|
||||
bindsym $mod+1 workspace number 1
|
||||
bindsym $mod+2 workspace number 2
|
||||
bindsym $mod+3 workspace number 3
|
||||
bindsym $mod+4 workspace number 4
|
||||
bindsym $mod+5 workspace number 5
|
||||
bindsym $mod+6 workspace number 6
|
||||
bindsym $mod+7 workspace number 7
|
||||
bindsym $mod+8 workspace number 8
|
||||
bindsym $mod+9 workspace number 9
|
||||
bindsym $mod+0 workspace number 10
|
||||
# Move focused container to workspace
|
||||
bindsym $mod+Shift+1 move container to workspace number 1
|
||||
bindsym $mod+Shift+2 move container to workspace number 2
|
||||
bindsym $mod+Shift+3 move container to workspace number 3
|
||||
bindsym $mod+Shift+4 move container to workspace number 4
|
||||
bindsym $mod+Shift+5 move container to workspace number 5
|
||||
bindsym $mod+Shift+6 move container to workspace number 6
|
||||
bindsym $mod+Shift+7 move container to workspace number 7
|
||||
bindsym $mod+Shift+8 move container to workspace number 8
|
||||
bindsym $mod+Shift+9 move container to workspace number 9
|
||||
bindsym $mod+Shift+0 move container to workspace number 10
|
||||
# Note: workspaces can have any name you want, not just numbers.
|
||||
# We just use 1-10 as the default.
|
||||
#
|
||||
# Layout stuff:
|
||||
#
|
||||
# You can "split" the current object of your focus with
|
||||
# $mod+b or $mod+v, for horizontal and vertical splits
|
||||
# respectively.
|
||||
bindsym $mod+b splith
|
||||
bindsym $mod+v splitv
|
||||
|
||||
# Switch the current container between different layout styles
|
||||
bindsym $mod+s layout stacking
|
||||
bindsym $mod+w layout tabbed
|
||||
bindsym $mod+e layout toggle split
|
||||
|
||||
# Make the current focus fullscreen
|
||||
bindsym $mod+f fullscreen
|
||||
|
||||
# Toggle the current focus between tiling and floating mode
|
||||
bindsym $mod+Shift+space floating toggle
|
||||
|
||||
# Swap focus between the tiling area and the floating area
|
||||
bindsym $mod+space focus mode_toggle
|
||||
|
||||
# Move focus to the parent container
|
||||
bindsym $mod+a focus parent
|
||||
|
||||
# Manual locker
|
||||
bindsym $mod+g exec sxmo_hook_locker.sh
|
||||
|
||||
# Shutdown
|
||||
bindsym $mod+t exec sxmo_appmenu.sh power
|
||||
#
|
||||
# Scratchpad:
|
||||
#
|
||||
# Sway has a "scratchpad", which is a bag of holding for windows.
|
||||
# You can send windows there and get them back later.
|
||||
|
||||
# Move the currently focused window to the scratchpad
|
||||
bindsym $mod+Shift+minus move scratchpad
|
||||
|
||||
# Show the next scratchpad window or hide the focused scratchpad window.
|
||||
# If there are multiple scratchpad windows, this command cycles through them.
|
||||
bindsym $mod+minus scratchpad show
|
||||
#
|
||||
# Resizing containers:
|
||||
#
|
||||
mode "resize" {
|
||||
# left will shrink the containers width
|
||||
# right will grow the containers width
|
||||
# up will shrink the containers height
|
||||
# down will grow the containers height
|
||||
bindsym $left resize shrink width 30px
|
||||
bindsym $down resize grow height 30px
|
||||
bindsym $up resize shrink height 30px
|
||||
bindsym $right resize grow width 30px
|
||||
|
||||
# Ditto, with arrow keys
|
||||
bindsym Left resize shrink width 30px
|
||||
bindsym Down resize grow height 30px
|
||||
bindsym Up resize shrink height 30px
|
||||
bindsym Right resize grow width 30px
|
||||
|
||||
# Return to default mode
|
||||
bindsym Return mode "default"
|
||||
bindsym Escape mode "default"
|
||||
}
|
||||
bindsym $mod+r mode "resize"
|
||||
|
||||
#
|
||||
# Status Bar:
|
||||
#
|
||||
# Read `man 5 sway-bar` for more information about this section.
|
||||
bar {
|
||||
position top
|
||||
|
||||
# When the status_command prints a new line to stdout, swaybar updates.
|
||||
# The default just shows the current date and time.
|
||||
status_command sxmo_status_watch.sh -o pango
|
||||
swaybar_command @waybar@
|
||||
|
||||
pango_markup enabled
|
||||
|
||||
colors {
|
||||
statusline #ffffff
|
||||
background #323232
|
||||
inactive_workspace #32323200 #32323200 #5c5c5c
|
||||
font "Sxmo 10"
|
||||
}
|
||||
}
|
||||
|
||||
for_window [app_id="pinentry-.*"] floating true
|
||||
for_window [app_id="foot" title=".*sxmo/modem/.*/draft.txt.*"] resize set height 25
|
||||
for_window [title="megapixels"] inhibit_idle open
|
||||
|
||||
# Need playerctl installed and running
|
||||
bindsym XF86AudioPlay exec playerctl play-pause
|
||||
bindsym XF86AudioStop exec playerctl stop
|
||||
bindsym XF86AudioNext exec playerctl next
|
||||
bindsym XF86AudioPrev exec playerctl previous
|
||||
|
||||
bindsym XF86MonBrightnessUp exec sxmo_brightness.sh up
|
||||
bindsym XF86MonBrightnessDown exec sxmo_brightness.sh down
|
||||
|
||||
bindsym Print exec sxmo_screenshot.sh
|
||||
|
||||
bindsym button2 kill
|
||||
|
||||
bindswitch lid:on exec sxmo_wm.sh dpms on
|
||||
bindswitch lid:off exec sxmo_wm.sh dpms off
|
||||
|
||||
|
||||
include /etc/sway/config.d/*
|
||||
|
||||
exec 'printf %s "$SWAYSOCK" > "$XDG_RUNTIME_DIR"/sxmo.swaysock'
|
||||
|
||||
exec sxmo_hook_start.sh
|
@@ -1,34 +0,0 @@
|
||||
# docs: https://github.com/Alexays/Waybar/wiki/Configuration
|
||||
# format specifiers: https://fmt.dev/latest/syntax.html#syntax
|
||||
{ pkgs }:
|
||||
[
|
||||
{ # TOP BAR
|
||||
layer = "top";
|
||||
height = 26;
|
||||
|
||||
modules-left = [ "sway/workspaces" ];
|
||||
modules-center = [ ];
|
||||
modules-right = [ "custom/sxmo" ];
|
||||
|
||||
"sway/workspaces" = {
|
||||
all-outputs = true;
|
||||
# force the bar to always show even empty workspaces
|
||||
persistent_workspaces = {
|
||||
"1" = [];
|
||||
"2" = [];
|
||||
"3" = [];
|
||||
"4" = [];
|
||||
"5" = [];
|
||||
};
|
||||
};
|
||||
|
||||
"custom/sxmo" = {
|
||||
# this gives wifi state, batter, mic/speaker, lockstate, time all as one widget.
|
||||
# a good starting point, but may want to split these apart later to make things configurable.
|
||||
# e.g. distinct vol-up & vol-down buttons next to the speaker?
|
||||
exec = "sxmo_status.sh";
|
||||
interval = 1;
|
||||
format = "{}";
|
||||
};
|
||||
}
|
||||
]
|
@@ -1,64 +0,0 @@
|
||||
/* style docs: https://github.com/Alexays/Waybar/wiki/Styling */
|
||||
/* defaults: https://github.com/Alexays/Waybar/blob/master/resources/style.css */
|
||||
|
||||
window#waybar {
|
||||
background-color: #418379;
|
||||
border-bottom: 0px solid #1f5e54;
|
||||
color: #ffffff;
|
||||
transition-property: background-color;
|
||||
transition-duration: .2s;
|
||||
}
|
||||
|
||||
.modules-right {
|
||||
/* workspace buttons (LHS) get padding between it and the screen edge */
|
||||
/* replicate that same padding for whatever's on the RHS (i.e. the clock) */
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#workspaces button {
|
||||
padding: 0 5px;
|
||||
background-color: #418379;
|
||||
color: #ffffff;
|
||||
/* Use box-shadow instead of border so the text isn't offset */
|
||||
box-shadow: inset 0 0px #1f5e54;
|
||||
/* Avoid rounded borders under each workspace name */
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
#workspaces button:hover {
|
||||
/* i don't want hover effects, so reset this styling to be the same as default button */
|
||||
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
|
||||
background: inherit;
|
||||
box-shadow: inherit;
|
||||
text-shadow: inherit;
|
||||
}
|
||||
|
||||
#workspaces button.focused {
|
||||
background-color: #63a89c;
|
||||
box-shadow: inset 0 0px #2c8274;
|
||||
}
|
||||
|
||||
#workspaces button.urgent {
|
||||
background-color: #e64291;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
to {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
#tray {
|
||||
background-color: #418379;
|
||||
}
|
||||
|
||||
#tray > .passive {
|
||||
-gtk-icon-effect: dim;
|
||||
}
|
||||
|
||||
#tray > .needs-attention {
|
||||
-gtk-icon-effect: highlight;
|
||||
background-color: #e64291;
|
||||
}
|
31
hosts/modules/gui/sxmo/waybar-top.nix
Normal file
31
hosts/modules/gui/sxmo/waybar-top.nix
Normal file
@@ -0,0 +1,31 @@
|
||||
# docs: https://github.com/Alexays/Waybar/wiki/Configuration
|
||||
# format specifiers: https://fmt.dev/latest/syntax.html#syntax
|
||||
# this is merged with the sway/waybar-top.nix defaults
|
||||
{
|
||||
height = 26;
|
||||
|
||||
modules-left = [ "sway/workspaces" ];
|
||||
modules-center = [ ];
|
||||
modules-right = [ "custom/swaync" "custom/sxmo" ];
|
||||
|
||||
"sway/workspaces" = {
|
||||
all-outputs = true;
|
||||
# force the bar to always show even empty workspaces
|
||||
persistent_workspaces = {
|
||||
"1" = [];
|
||||
"2" = [];
|
||||
"3" = [];
|
||||
"4" = [];
|
||||
"5" = [];
|
||||
};
|
||||
};
|
||||
|
||||
"custom/sxmo" = {
|
||||
# this gives wifi state, batter, mic/speaker, lockstate, time all as one widget.
|
||||
# a good starting point, but may want to split these apart later to make things configurable.
|
||||
# e.g. distinct vol-up & vol-down buttons next to the speaker?
|
||||
exec = "sxmo_status.sh";
|
||||
interval = 1;
|
||||
format = "{}";
|
||||
};
|
||||
}
|
28
hosts/modules/gui/theme/default.nix
Normal file
28
hosts/modules/gui/theme/default.nix
Normal file
@@ -0,0 +1,28 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.gui.theme.background;
|
||||
in
|
||||
{
|
||||
options = with lib; {
|
||||
sane.gui.theme.background = {
|
||||
svg = mkOption {
|
||||
type = types.path;
|
||||
default = ./nixos-bg-02.svg;
|
||||
};
|
||||
png = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.runCommand
|
||||
"nixos-bg.png"
|
||||
{ nativeBuildInputs = [ pkgs.inkscape ]; }
|
||||
''
|
||||
inkscape ${cfg.svg} -o $out
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
sane.gui.sway.config.background = lib.mkDefault cfg.png;
|
||||
sane.gui.sxmo.settings.SXMO_BG_IMG = lib.mkDefault (builtins.toString cfg.png);
|
||||
};
|
||||
}
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
@@ -1,7 +0,0 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./x86_64.nix
|
||||
];
|
||||
}
|
@@ -77,42 +77,4 @@ in
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
# TODO: this should be populated per-host
|
||||
sane.hosts.by-name."desko" = {
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPU5GlsSfbaarMvDA20bxpSZGWviEzXGD8gtrIowc1pX";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFw9NoRaYrM6LbDd3aFBc4yyBlxGQn8HjeHd/dZ3CfHk";
|
||||
wg-home.pubkey = "17PMZssYi0D4t2d0vbmhjBKe1sGsE8kT8/dod0Q2CXc=";
|
||||
wg-home.ip = "10.0.10.22";
|
||||
lan-ip = "10.78.79.52";
|
||||
};
|
||||
|
||||
sane.hosts.by-name."lappy" = {
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDpmFdNSVPRol5hkbbCivRhyeENzb9HVyf9KutGLP2Zu";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILSJnqmVl9/SYQ0btvGb0REwwWY8wkdkGXQZfn/1geEc";
|
||||
wg-home.pubkey = "FTUWGw2p4/cEcrrIE86PWVnqctbv8OYpw8Gt3+dC/lk=";
|
||||
wg-home.ip = "10.0.10.20";
|
||||
lan-ip = "10.78.79.53";
|
||||
};
|
||||
|
||||
sane.hosts.by-name."moby" = {
|
||||
ssh.authorized = lib.mkDefault false; # moby's too easy to hijack: don't let it ssh places
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrR+gePnl0nV/vy7I5BzrGeyVL+9eOuXHU1yNE3uCwU";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1N/IT3nQYUD+dBlU1sTEEVMxfOyMkrrDeyHcYgnJvw";
|
||||
wg-home.pubkey = "I7XIR1hm8bIzAtcAvbhWOwIAabGkuEvbWH/3kyIB1yA=";
|
||||
wg-home.ip = "10.0.10.48";
|
||||
lan-ip = "10.78.79.54";
|
||||
};
|
||||
|
||||
sane.hosts.by-name."servo" = {
|
||||
ssh.authorized = lib.mkDefault false; # servo presents too many services to the internet: easy atack vector
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPS1qFzKurAdB9blkWomq8gI1g0T3sTs9LsmFOj5VtqX";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOfdSmFkrVT6DhpgvFeQKm3Fh9VKZ9DbLYOPOJWYQ0E8";
|
||||
wg-home.pubkey = "roAw+IUFVtdpCcqa4khB385Qcv9l5JAB//730tyK4Wk=";
|
||||
wg-home.ip = "10.0.10.5";
|
||||
wg-home.endpoint = "uninsane.org:51820";
|
||||
lan-ip = "10.78.79.51";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,14 +1,16 @@
|
||||
# wireguard VPN which allows my devices to talk to eachother even when on physically different LANs
|
||||
# for wireguard docs, see:
|
||||
# - <https://nixos.wiki/wiki/WireGuard>
|
||||
# - <https://wiki.archlinux.org/title/WireGuard>
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (builtins) filter map;
|
||||
inherit (lib) concatMap mapAttrsToList mkIf mkMerge mkOption optionalAttrs types;
|
||||
cfg = config.sane.services.wg-home;
|
||||
server-cfg = config.sane.hosts.by-name."servo".wg-home;
|
||||
mkPeer = { ips, pubkey, endpoint }: {
|
||||
publicKey = pubkey;
|
||||
allowedIPs = map (k: "${k}/32") ips;
|
||||
} // (optionalAttrs (endpoint != null) {
|
||||
allowedIPs = builtins.map (k: "${k}/32") ips;
|
||||
} // (lib.optionalAttrs (endpoint != null) {
|
||||
inherit endpoint;
|
||||
# send keepalives every 25 seconds to keep NAT routes live.
|
||||
# only need to do this from client -> server though, i think.
|
||||
@@ -17,33 +19,49 @@ let
|
||||
dynamicEndpointRefreshSeconds = 600;
|
||||
});
|
||||
# make separate peers to route each given host
|
||||
mkClientPeers = hosts: map (p: mkPeer {
|
||||
mkClientPeers = hosts: builtins.map (p: mkPeer {
|
||||
inherit (p) pubkey endpoint;
|
||||
ips = [ p.ip ];
|
||||
}) hosts;
|
||||
# make a single peer which routes all the given hosts
|
||||
mkServerPeer = hosts: mkPeer {
|
||||
inherit (server-cfg) pubkey endpoint;
|
||||
ips = map (h: h.ip) hosts;
|
||||
ips = builtins.map (h: h.ip) hosts;
|
||||
};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
options = with lib; {
|
||||
sane.services.wg-home.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
sane.services.wg-home.enableWan = mkOption {
|
||||
sane.services.wg-home.visibleToWan = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "whether to make this port visible on the WAN";
|
||||
};
|
||||
sane.services.wg-home.forwardToWan = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
whether to forward packets from wireguard clients to the WAN,
|
||||
i.e. whether to act as a VPN exit node.
|
||||
'';
|
||||
};
|
||||
sane.services.wg-home.routeThroughServo = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
whether to contact peers by routing through a stationary server.
|
||||
should be true for all "clients", and false for that stationary server.
|
||||
'';
|
||||
};
|
||||
sane.services.wg-home.ip = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
config = lib.mkIf cfg.enable {
|
||||
# generate a (deterministic) wireguard private key
|
||||
sane.derived-secrets."/run/wg-home.priv" = {
|
||||
len = 32;
|
||||
@@ -58,12 +76,14 @@ in
|
||||
sane.ports.ports."51820" = {
|
||||
protocol = [ "udp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = cfg.enableWan;
|
||||
visibleTo.wan = cfg.visibleToWan;
|
||||
description = "colin-wireguard";
|
||||
};
|
||||
networking.wireguard.interfaces.wg-home = {
|
||||
networking.wireguard.interfaces.wg-home = lib.mkMerge [
|
||||
{
|
||||
listenPort = 51820;
|
||||
privateKeyFile = "/run/wg-home.priv";
|
||||
# TODO: this make this `wants` and `after`, instead of manually starting it
|
||||
preSetup =
|
||||
let
|
||||
gen-key = config.sane.fs."/run/wg-home.priv".unit;
|
||||
@@ -76,15 +96,26 @@ in
|
||||
|
||||
peers =
|
||||
let
|
||||
all-peers = mapAttrsToList (_: hostcfg: hostcfg.wg-home) config.sane.hosts.by-name;
|
||||
peer-list = filter (p: p.ip != null && p.ip != cfg.ip && p.pubkey != null) all-peers;
|
||||
all-peers = lib.mapAttrsToList (_: hostcfg: hostcfg.wg-home) config.sane.hosts.by-name;
|
||||
peer-list = builtins.filter (p: p.ip != null && p.ip != cfg.ip && p.pubkey != null) all-peers;
|
||||
in
|
||||
if cfg.ip == server-cfg.ip then
|
||||
# if we're the server, then we maintain the entire client list
|
||||
mkClientPeers peer-list
|
||||
if cfg.routeThroughServo then
|
||||
# if acting as a client, then maintain a single peer -- the server -- which does the actual routing
|
||||
[ (mkServerPeer peer-list) ]
|
||||
else
|
||||
# but if we're a client, we maintain a single peer -- the server -- which does the actual routing
|
||||
[ (mkServerPeer peer-list) ];
|
||||
};
|
||||
# if acting as a server, route to each peer individually
|
||||
mkClientPeers peer-list
|
||||
;
|
||||
}
|
||||
(lib.mkIf cfg.forwardToWan {
|
||||
# documented here: <https://nixos.wiki/wiki/WireGuard#Server_setup_2>
|
||||
postSetup = ''
|
||||
${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s ${cfg.ip}/24 -o eth0 -j MASQUERADE
|
||||
'';
|
||||
postShutdown = ''
|
||||
${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s ${cfg.ip}/24 -o eth0 -j MASQUERADE
|
||||
'';
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bozo": 0,
|
||||
"content_length": 567579,
|
||||
"content_length": 479140,
|
||||
"content_type": "text/xml; charset=utf-8",
|
||||
"description": "ACQ2 is Ben and David's conversations with expert founders and investors.",
|
||||
"favicon": "",
|
||||
@@ -10,14 +10,14 @@
|
||||
],
|
||||
"is_podcast": true,
|
||||
"is_push": true,
|
||||
"item_count": 91,
|
||||
"last_updated": "2023-05-09T06:51:48+00:00",
|
||||
"item_count": 95,
|
||||
"last_updated": "2023-09-18T05:11:04+00:00",
|
||||
"score": 24,
|
||||
"self_url": "https://feeds.transistor.fm/acq2",
|
||||
"site_name": "ACQ2: The Acquired Interviews",
|
||||
"site_name": "",
|
||||
"site_url": "https://feeds.transistor.fm",
|
||||
"title": "ACQ2: The Acquired Interviews",
|
||||
"title": "ACQ2 by Acquired",
|
||||
"url": "https://feeds.transistor.fm/acq2",
|
||||
"velocity": 0.054,
|
||||
"velocity": 0.052,
|
||||
"version": "rss20"
|
||||
}
|
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"bozo": 0,
|
||||
"content_length": 1030773,
|
||||
"content_length": 1423488,
|
||||
"content_type": "application/rss+xml; charset=utf-8",
|
||||
"description": "Industry veterans, degenerate gamblers & besties Chamath Palihapitiya, Jason Calacanis, David Sacks & David Friedberg cover all things economic, tech, political, social & poker.",
|
||||
"favicon": null,
|
||||
"favicon": "",
|
||||
"favicon_data_uri": "",
|
||||
"hubs": [],
|
||||
"is_podcast": true,
|
||||
"is_push": false,
|
||||
"item_count": 124,
|
||||
"last_seen": "2023-01-11T12:44:53.606606+00:00",
|
||||
"last_updated": "2023-01-06T10:51:00+00:00",
|
||||
"item_count": 160,
|
||||
"last_updated": "2023-09-22T22:51:00+00:00",
|
||||
"score": 18,
|
||||
"self_url": "https://allinchamathjason.libsyn.com/rss",
|
||||
"site_name": "All-In with Chamath, Jason, Sacks & Friedberg",
|
||||
"site_url": "https://allinchamathjason.libsyn.com",
|
||||
"title": "All-In with Chamath, Jason, Sacks & Friedberg",
|
||||
"url": "https://allinchamathjason.libsyn.com/rss",
|
||||
"velocity": 0.12,
|
||||
"velocity": 0.124,
|
||||
"version": "rss20"
|
||||
}
|
File diff suppressed because one or more lines are too long
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"bozo": 0,
|
||||
"content_length": 13316,
|
||||
"content_length": 14856,
|
||||
"content_type": "application/rss+xml; charset=utf-8",
|
||||
"description": "A podcast around the idea of creating a Civilizational Bootstrapper, a set of tools and technology that can be used to replicate the foundations of civilization along with itself.",
|
||||
"favicon": null,
|
||||
"favicon": "",
|
||||
"favicon_data_uri": "",
|
||||
"hubs": [
|
||||
"https://pubsubhubbub.appspot.com/"
|
||||
],
|
||||
"is_podcast": true,
|
||||
"is_push": true,
|
||||
"item_count": 6,
|
||||
"last_seen": "2023-01-11T16:11:01.720399+00:00",
|
||||
"last_updated": "2022-04-13T19:37:17+00:00",
|
||||
"item_count": 7,
|
||||
"last_updated": "2023-05-05T21:46:30+00:00",
|
||||
"score": 22,
|
||||
"self_url": "https://anchor.fm/s/34c7232c/podcast/rss",
|
||||
"site_name": "Anchor",
|
||||
"site_url": "https://anchor.fm",
|
||||
"site_name": "",
|
||||
"site_url": "",
|
||||
"title": "Civboot",
|
||||
"url": "https://anchor.fm/s/34c7232c/podcast/rss",
|
||||
"velocity": 0.009,
|
||||
"velocity": 0.006,
|
||||
"version": "rss20"
|
||||
}
|
@@ -9,13 +9,13 @@
|
||||
"is_podcast": false,
|
||||
"is_push": false,
|
||||
"item_count": 20,
|
||||
"last_updated": "2022-12-20T01:23:44.546000+00:00",
|
||||
"last_updated": "2023-08-02T08:22:33.539000+00:00",
|
||||
"score": 26,
|
||||
"self_url": "https://applieddivinitystudies.com/atom.xml",
|
||||
"site_name": "Applied Divinity Studies",
|
||||
"site_url": "https://applieddivinitystudies.com",
|
||||
"title": "Applied Divinity Studies",
|
||||
"url": "https://applieddivinitystudies.com/atom.xml",
|
||||
"velocity": 0.079,
|
||||
"url": "https://www.applieddivinitystudies.com/atom.xml",
|
||||
"velocity": 0.054,
|
||||
"version": "atom10"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bozo": 0,
|
||||
"content_length": 362082,
|
||||
"content_length": 395740,
|
||||
"content_type": "application/rss+xml; charset=utf-8",
|
||||
"description": "Jason Scott's Weblog",
|
||||
"favicon": "https://ascii.textfiles.com/wp-includes/images/w-logo-blue-white-bg.png",
|
||||
@@ -9,7 +9,7 @@
|
||||
"is_podcast": false,
|
||||
"is_push": false,
|
||||
"item_count": 20,
|
||||
"last_updated": "2023-03-06T07:13:07+00:00",
|
||||
"last_updated": "2023-06-05T02:38:25+00:00",
|
||||
"score": 14,
|
||||
"self_url": "https://ascii.textfiles.com/feed",
|
||||
"site_name": "ASCII by Jason Scott",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bozo": 0,
|
||||
"content_length": 19245,
|
||||
"content_length": 24700,
|
||||
"content_type": "text/xml; charset=utf-8",
|
||||
"description": "Austin Vernon's Blog",
|
||||
"favicon": "",
|
||||
@@ -8,14 +8,14 @@
|
||||
"hubs": [],
|
||||
"is_podcast": false,
|
||||
"is_push": false,
|
||||
"item_count": 42,
|
||||
"last_updated": "2023-01-05T00:00:00+00:00",
|
||||
"item_count": 54,
|
||||
"last_updated": "2023-09-08T00:00:00+00:00",
|
||||
"score": 24,
|
||||
"self_url": "",
|
||||
"site_name": "Austin Vernon - Austin Vernon's Blog",
|
||||
"site_url": "https://austinvernon.site",
|
||||
"title": "Austin Vernon",
|
||||
"url": "https://austinvernon.site/rss.xml",
|
||||
"velocity": 0.063,
|
||||
"velocity": 0.059,
|
||||
"version": "rss20"
|
||||
}
|
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"bozo": 0,
|
||||
"content_length": 453506,
|
||||
"content_type": "text/xml; charset=utf-8",
|
||||
"description": "Balaji Srinivasan's personal blog. Formerly CTO of Coinbase and General Partner at a16z, @balajis is an investor and founder.",
|
||||
"content_length": 592815,
|
||||
"content_type": "application/xml; charset=utf-8",
|
||||
"description": "A free newsletter by Balaji.",
|
||||
"favicon": "",
|
||||
"favicon_data_uri": "",
|
||||
"hubs": [],
|
||||
"is_podcast": false,
|
||||
"is_podcast": true,
|
||||
"is_push": false,
|
||||
"item_count": 15,
|
||||
"last_updated": "2022-04-28T18:22:11+00:00",
|
||||
"score": 16,
|
||||
"self_url": "https://balajis.com/rss/",
|
||||
"site_name": "Balaji Srinivasan",
|
||||
"item_count": 20,
|
||||
"last_updated": "2023-07-04T11:31:07+00:00",
|
||||
"score": 14,
|
||||
"self_url": "https://balajis.com/feed",
|
||||
"site_name": "balajis.com | Substack",
|
||||
"site_url": "https://balajis.com",
|
||||
"title": "Balaji Srinivasan",
|
||||
"url": "https://balajis.com/rss/",
|
||||
"velocity": 0.01,
|
||||
"title": "balajis.com",
|
||||
"url": "https://balajis.com/feed",
|
||||
"velocity": 0.022,
|
||||
"version": "rss20"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bozo": 0,
|
||||
"content_length": 213052,
|
||||
"content_length": 328802,
|
||||
"content_type": "application/rss+xml; charset=utf-8",
|
||||
"description": "",
|
||||
"favicon": "https://images.squarespace-cdn.com/content/v1/50363cf324ac8e905e7df861/ebdb4645-db93-4967-881d-db698ee59c2c/favicon.ico?format=100w",
|
||||
@@ -9,13 +9,13 @@
|
||||
"is_podcast": false,
|
||||
"is_push": false,
|
||||
"item_count": 20,
|
||||
"last_updated": "2022-12-14T09:43:49+00:00",
|
||||
"last_updated": "2023-08-27T13:29:00+00:00",
|
||||
"score": 8,
|
||||
"self_url": "",
|
||||
"site_name": "Benedict Evans",
|
||||
"site_url": "https://www.ben-evans.com",
|
||||
"title": "Essays - Benedict Evans",
|
||||
"url": "https://www.ben-evans.com/benedictevans?format=rss",
|
||||
"velocity": 0.033,
|
||||
"velocity": 0.027,
|
||||
"version": "rss20"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bozo": 0,
|
||||
"content_length": 339384,
|
||||
"content_length": 264830,
|
||||
"content_type": "application/xml; charset=utf-8",
|
||||
"description": "Yet another programming blog. Thoughts on software and related misadventures.",
|
||||
"favicon": "https://bitbashing.io/favicon.ico",
|
||||
@@ -9,7 +9,7 @@
|
||||
"is_podcast": false,
|
||||
"is_push": false,
|
||||
"item_count": 10,
|
||||
"last_updated": "2022-11-22T00:00:00+00:00",
|
||||
"last_updated": "2023-09-08T00:00:00+00:00",
|
||||
"score": 20,
|
||||
"self_url": "https://bitbashing.io/feed.xml",
|
||||
"site_name": "Bit Bashing",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bozo": 1,
|
||||
"content_length": 343256,
|
||||
"content_length": 436175,
|
||||
"content_type": "text/xml; charset=utf-8",
|
||||
"description": null,
|
||||
"favicon": "",
|
||||
@@ -8,14 +8,14 @@
|
||||
"hubs": [],
|
||||
"is_podcast": false,
|
||||
"is_push": false,
|
||||
"item_count": 60,
|
||||
"last_updated": "2022-11-07T00:00:00+00:00",
|
||||
"item_count": 63,
|
||||
"last_updated": "2023-09-09T00:00:00+00:00",
|
||||
"score": -4,
|
||||
"self_url": "",
|
||||
"site_name": "Daniel Janus \u2013 blog",
|
||||
"site_url": "https://blog.danieljanus.pl",
|
||||
"title": "code \u00b7 words \u00b7 emotions: Daniel Janus\u2019s blog",
|
||||
"url": "https://blog.danieljanus.pl/atom.xml",
|
||||
"velocity": 0.011,
|
||||
"velocity": 0.01,
|
||||
"version": "atom10"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bozo": 0,
|
||||
"content_length": 623592,
|
||||
"content_length": 555845,
|
||||
"content_type": "application/atom+xml; charset=utf-8",
|
||||
"description": "I'm David Rosenthal, and this is a place to discuss the work I'm doing in Digital Preservation.",
|
||||
"favicon": "",
|
||||
@@ -11,13 +11,13 @@
|
||||
"is_podcast": false,
|
||||
"is_push": true,
|
||||
"item_count": 25,
|
||||
"last_updated": "2023-01-10T17:59:42.157000+00:00",
|
||||
"last_updated": "2023-09-19T15:00:00.137000+00:00",
|
||||
"score": 20,
|
||||
"self_url": "https://www.blogger.com/feeds/4503292949532760618/posts/default",
|
||||
"site_name": "DSHR's Blog",
|
||||
"site_url": "https://blog.dshr.org",
|
||||
"title": "DSHR's Blog",
|
||||
"url": "https://blog.dshr.org/feeds/posts/default",
|
||||
"velocity": 0.35,
|
||||
"velocity": 0.308,
|
||||
"version": "atom10"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bozo": 0,
|
||||
"content_length": 96453,
|
||||
"content_length": 95645,
|
||||
"content_type": "application/atom+xml; charset=utf-8",
|
||||
"description": null,
|
||||
"favicon": "https://jmp.chat/favicon.ico",
|
||||
@@ -9,13 +9,13 @@
|
||||
"is_podcast": false,
|
||||
"is_push": false,
|
||||
"item_count": 10,
|
||||
"last_updated": "2023-04-11T14:59:18+00:00",
|
||||
"last_updated": "2023-09-13T20:30:00+00:00",
|
||||
"score": 16,
|
||||
"self_url": "http://blog.jmp.chat/atom.xml",
|
||||
"site_name": "JMP Blog",
|
||||
"site_url": "https://blog.jmp.chat",
|
||||
"title": "blog.jmp.chat's blog",
|
||||
"url": "https://blog.jmp.chat/atom.xml",
|
||||
"velocity": 0.093,
|
||||
"velocity": 0.06,
|
||||
"version": "atom10"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bozo": 0,
|
||||
"content_length": 76362,
|
||||
"content_length": 66707,
|
||||
"content_type": "application/xml; charset=utf-8",
|
||||
"description": "Empowering everyone to build reliable and efficient software.",
|
||||
"favicon": "https://blog.rust-lang.org/images/favicon-16x16.png",
|
||||
@@ -9,13 +9,13 @@
|
||||
"is_podcast": false,
|
||||
"is_push": false,
|
||||
"item_count": 10,
|
||||
"last_updated": "2023-03-09T00:00:00+00:00",
|
||||
"last_updated": "2023-09-22T00:00:00+00:00",
|
||||
"score": 20,
|
||||
"self_url": "https://blog.rust-lang.org/feed.xml",
|
||||
"site_name": "The Rust Programming Language Blog",
|
||||
"site_url": "https://blog.rust-lang.org",
|
||||
"title": "Rust Blog",
|
||||
"url": "https://blog.rust-lang.org/feed.xml",
|
||||
"velocity": 0.096,
|
||||
"velocity": 0.114,
|
||||
"version": "atom10"
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user