Compare commits
1 Commits
wg-dev
...
wip-mootub
Author | SHA1 | Date | |
---|---|---|---|
371fc689f5 |
18
TODO.md
18
TODO.md
@@ -1,8 +1,9 @@
|
||||
## BUGS
|
||||
- nixpkgs date is incorrect (1970.01.01...)
|
||||
- ringer (i.e. dino incoming call) doesn't prevent moby from sleeping
|
||||
- Fractal opens links with non-preferred web browser
|
||||
- `nix` operations from lappy hang when `desko` is unreachable
|
||||
- could at least direct the cache to `http://desko-hn:5001`
|
||||
- waybar isn't visible on moby until after `swaymsg reload`
|
||||
|
||||
## REFACTORING:
|
||||
|
||||
@@ -27,6 +28,7 @@
|
||||
|
||||
#### upstreaming to non-nixpkgs repos
|
||||
- gtk: build schemas even on cross compilation: <https://github.com/NixOS/nixpkgs/pull/247844>
|
||||
- sxmo: add new app entries
|
||||
|
||||
|
||||
## IMPROVEMENTS:
|
||||
@@ -68,14 +70,14 @@
|
||||
- UnCiv (Civ V clone; nixpkgs `unciv`; doesn't cross-compile): <https://github.com/yairm210/UnCiv>
|
||||
- Simon Tatham's Puzzle Collection (not in nixpkgs) <https://git.tartarus.org/?p=simon/puzzles.git>
|
||||
- Shootin Stars (Godot; not in nixpkgs) <https://gitlab.com/greenbeast/shootin-stars>
|
||||
- numberlink (generic name for Flow Free). not packaged in Nix
|
||||
- Neverball (https://neverball.org/screenshots.php). nix: as `neverball`
|
||||
|
||||
#### moby
|
||||
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
|
||||
- SwayNC:
|
||||
- don't show MPRIS if no players detected
|
||||
- this is a problem of playerctld, i guess
|
||||
- also, the album icon when "Not playing" doesn't follow the size we give in the config
|
||||
- that means mpris always takes up excessive space on moby
|
||||
- add option to change audio output
|
||||
- fix colors (red alert) to match overall theme
|
||||
- moby: tune GPS
|
||||
@@ -86,7 +88,12 @@
|
||||
- manually do smoothing, as some layer between mepo and geoclue/gpsd?
|
||||
- moby: show battery state on ssh login
|
||||
- moby: improve gPodder launch time
|
||||
- sxmo: port to swaybar like i use on desktop
|
||||
- users in #sxmo claim it's way better perf
|
||||
- sxmo: fix youtube scripts (package youtube-cli)
|
||||
- moby: theme GTK apps (i.e. non-adwaita styles)
|
||||
- combine multiple icon themes to get one which has the full icon set?
|
||||
- get adwaita-icon-theme to ship everything even when cross-compiled?
|
||||
- especially, make the menubar collapsible
|
||||
- try Gradience tool specifically for theming adwaita? <https://linuxphoneapps.org/apps/com.github.gradienceteam.gradience/>
|
||||
- phog: remove the gnome-shell runtime dependency to save hella closure size
|
||||
@@ -114,10 +121,13 @@
|
||||
- add `pkgs.impure-cached.<foo>` package set to build things with ccache enabled
|
||||
- every package here can be auto-generated, and marked with some env var so that it doesn't pollute the pure package set
|
||||
- would be super handy for package prototyping!
|
||||
- fix desko so it doesn't dispatch so many build jobs to servo by default
|
||||
- get moby to build without binfmt emulation (i.e. make all emulation explicit)
|
||||
- then i can distribute builds across servo + desko, and also allow servo to pull packages from desko w/o worrying about purity
|
||||
|
||||
|
||||
## NEW FEATURES:
|
||||
- migrate MAME cabinet to nix
|
||||
- boot it from PXE from servo?
|
||||
- deploy to new server, and use it as a remote builder
|
||||
- enable IPv6
|
||||
- package lemonade lemmy app: <https://linuxphoneapps.org/apps/ml.mdwalters.lemonade/>
|
||||
|
77
flake.lock
generated
77
flake.lock
generated
@@ -1,5 +1,23 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"mobile-nixos": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
@@ -17,29 +35,13 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-next-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1705233677,
|
||||
"narHash": "sha256-eq3VE8QGJsunqqF/BlLslWE1gASp4Hlgp0c78coxat0=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "724e39ebb9b8eda97f17d423f66fbc5a991f4f8d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "staging-next",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1705033721,
|
||||
"narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=",
|
||||
"lastModified": 1700905716,
|
||||
"narHash": "sha256-w1vHn2MbGfdC+CrP3xLZ3scsI06N0iQLU7eTHIVEFGw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea",
|
||||
"rev": "dfb95385d21475da10b63da74ae96d89ab352431",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -51,11 +53,11 @@
|
||||
},
|
||||
"nixpkgs-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1705254014,
|
||||
"narHash": "sha256-4RrVNEqxeji4vqDgzSl7JoCD6a0ag5LF9zXFndtqrpE=",
|
||||
"lastModified": 1701180790,
|
||||
"narHash": "sha256-kYWcHsk2A1VUpiOvSo7Pq175WnSVeltspTGM2q+Cr3U=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6c08fe3ccf437d8b26bec010fd925ddd6bb0d0d5",
|
||||
"rev": "c9702bf40b036c0f1d3d5b0aaf3eee2bf920124c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -68,7 +70,6 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"mobile-nixos": "mobile-nixos",
|
||||
"nixpkgs-next-unpatched": "nixpkgs-next-unpatched",
|
||||
"nixpkgs-unpatched": "nixpkgs-unpatched",
|
||||
"sops-nix": "sops-nix",
|
||||
"uninsane-dot-org": "uninsane-dot-org"
|
||||
@@ -82,11 +83,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705201153,
|
||||
"narHash": "sha256-y0/a4IMDZrc7lAkR7Gcm5R3W2iCBiARHnYZe6vkmiNE=",
|
||||
"lastModified": 1701127353,
|
||||
"narHash": "sha256-qVNX0wOl0b7+I35aRu78xUphOyELh+mtUp1KBx89K1Q=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "70dd0d521f7849338e487a219c1a07c429a66d77",
|
||||
"rev": "b1edbf5c0464b4cced90a3ba6f999e671f0af631",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -95,18 +96,34 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"uninsane-dot-org": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"nixpkgs-unpatched"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1704082841,
|
||||
"narHash": "sha256-4g3lePnUALb8B1m3rEDD6rrZAq2pTN4qaSnStbd676U=",
|
||||
"lastModified": 1699515935,
|
||||
"narHash": "sha256-cJIuVrYorhIzG5pRFZb+ZtaKhTFD92ThC42SaxvSe/E=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "4a1fa488e64e6c87c6c951e3fafb2684692f64d3",
|
||||
"revCount": 234,
|
||||
"rev": "8a4273489d945f21d7e0ca6aac952460c7d4c391",
|
||||
"revCount": 216,
|
||||
"type": "git",
|
||||
"url": "https://git.uninsane.org/colin/uninsane"
|
||||
},
|
||||
|
175
flake.nix
175
flake.nix
@@ -29,7 +29,7 @@
|
||||
# - daily:
|
||||
# - nixos-unstable cut from master after enough packages have been built in caches.
|
||||
# - every 6 hours:
|
||||
# - master auto-merged into staging and staging-next
|
||||
# - master auto-merged into staging.
|
||||
# - staging-next auto-merged into staging.
|
||||
# - manually, approximately once per month:
|
||||
# - staging-next is cut from staging.
|
||||
@@ -44,9 +44,8 @@
|
||||
# <https://github.com/nixos/nixpkgs/tree/nixos-unstable>
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=master";
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-staging";
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-staging-next";
|
||||
nixpkgs-next-unpatched.url = "github:nixos/nixpkgs?ref=staging-next";
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=staging-next";
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=staging";
|
||||
|
||||
mobile-nixos = {
|
||||
# <https://github.com/nixos/mobile-nixos>
|
||||
@@ -75,7 +74,6 @@
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs-unpatched,
|
||||
nixpkgs-next-unpatched ? nixpkgs-unpatched,
|
||||
mobile-nixos,
|
||||
sops-nix,
|
||||
uninsane-dot-org,
|
||||
@@ -94,9 +92,9 @@
|
||||
# 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.
|
||||
patchNixpkgs = variant: nixpkgs: (import ./nixpatches/flake.nix).outputs {
|
||||
inherit variant nixpkgs;
|
||||
self = patchNixpkgs variant nixpkgs;
|
||||
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.
|
||||
@@ -112,21 +110,21 @@
|
||||
inherit (self) shortRev;
|
||||
};
|
||||
|
||||
nixpkgs' = patchNixpkgs "master" nixpkgs-unpatched;
|
||||
nixpkgsCompiledBy = system: nixpkgs'.legacyPackages."${system}";
|
||||
nixpkgsCompiledBy = system: nixpkgs.legacyPackages."${system}";
|
||||
|
||||
evalHost = { name, local, target, light ? false, nixpkgs ? nixpkgs' }: nixpkgs.lib.nixosSystem {
|
||||
evalHost = { name, local, target, light ? false }: nixpkgs.lib.nixosSystem {
|
||||
system = target;
|
||||
modules = [
|
||||
{
|
||||
nixpkgs.buildPlatform.system = local;
|
||||
nixpkgs = (if (local != null) then {
|
||||
buildPlatform = local;
|
||||
} else {}) // {
|
||||
# TODO: does the earlier `system` arg to nixosSystem make its way here?
|
||||
hostPlatform.system = target;
|
||||
};
|
||||
# nixpkgs.buildPlatform = local; # set by instantiate.nix instead
|
||||
# nixpkgs.config.replaceStdenv = { pkgs }: pkgs.ccacheStdenv;
|
||||
}
|
||||
(optionalAttrs (local != target) {
|
||||
# XXX(2023/12/11): cache.nixos.org uses `system = ...` instead of `hostPlatform.system`, and that choice impacts the closure of every package.
|
||||
# so avoid specifying hostPlatform.system on non-cross builds, so i can use upstream caches.
|
||||
nixpkgs.hostPlatform.system = target;
|
||||
})
|
||||
(optionalAttrs light {
|
||||
sane.enableSlowPrograms = false;
|
||||
})
|
||||
@@ -142,7 +140,8 @@
|
||||
];
|
||||
};
|
||||
in {
|
||||
nixosConfigurations = let
|
||||
nixosConfigurations =
|
||||
let
|
||||
hosts = {
|
||||
servo = { name = "servo"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
desko = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
@@ -153,13 +152,27 @@
|
||||
moby-light = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; light = true; };
|
||||
rescue = { name = "rescue"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
};
|
||||
hostsNext = mapAttrs' (h: v: {
|
||||
name = "${h}-next";
|
||||
value = v // { nixpkgs = patchNixpkgs "staging-next" nixpkgs-next-unpatched; };
|
||||
}) hosts;
|
||||
in mapAttrValues evalHost (
|
||||
hosts // hostsNext
|
||||
);
|
||||
# cross-compiled builds: instead of emulating the host, build using a cross-compiler.
|
||||
# - these are faster to *build* than the emulated variants (useful when tweaking packages),
|
||||
# - but fewer of their packages can be found in upstream caches.
|
||||
cross = mapAttrValues evalHost hosts;
|
||||
emulated = mapAttrValues
|
||||
(args: evalHost (args // { local = null; }))
|
||||
hosts;
|
||||
prefixAttrs = prefix: attrs: mapAttrs'
|
||||
(name: value: {
|
||||
name = prefix + name;
|
||||
inherit value;
|
||||
})
|
||||
attrs;
|
||||
in
|
||||
(prefixAttrs "cross-" cross) //
|
||||
(prefixAttrs "emulated-" emulated) // {
|
||||
# prefer native builds for these machines:
|
||||
inherit (emulated) servo desko desko-light lappy lappy-light rescue;
|
||||
# prefer cross-compiled builds for these machines:
|
||||
inherit (cross) moby moby-light;
|
||||
};
|
||||
|
||||
# unofficial output
|
||||
# this produces a EFI-bootable .img file (GPT with a /boot partition and a system (/ or /nix) partition).
|
||||
@@ -179,12 +192,9 @@
|
||||
|
||||
# unofficial output
|
||||
hostConfigs = mapAttrValues (host: host.config) self.nixosConfigurations;
|
||||
hostSystems = mapAttrValues (host: host.config.system.build.toplevel) self.nixosConfigurations;
|
||||
hostPkgs = mapAttrValues (host: host.config.system.build.pkgs) self.nixosConfigurations;
|
||||
hostPrograms = mapAttrValues (host: mapAttrValues (p: p.package) host.config.sane.programs) self.nixosConfigurations;
|
||||
|
||||
patched.nixpkgs = nixpkgs';
|
||||
|
||||
overlays = {
|
||||
# N.B.: `nix flake check` requires every overlay to take `final: prev:` at defn site,
|
||||
# hence the weird redundancy.
|
||||
@@ -198,7 +208,7 @@
|
||||
passthru = final: prev:
|
||||
let
|
||||
mobile = (import "${mobile-nixos}/overlay/overlay.nix");
|
||||
uninsane = uninsane-dot-org.overlays.default;
|
||||
uninsane = uninsane-dot-org.overlay;
|
||||
in
|
||||
(mobile final prev)
|
||||
// (uninsane final prev)
|
||||
@@ -229,27 +239,23 @@
|
||||
# extract only our own packages from the full set.
|
||||
# because of `nix flake check`, we flatten the package set and only surface x86_64-linux packages.
|
||||
packages = mapAttrs
|
||||
(system: passthruPkgs: passthruPkgs.lib.filterAttrs
|
||||
(name: pkg:
|
||||
(system: allPkgs:
|
||||
allPkgs.lib.filterAttrs (name: pkg:
|
||||
# keep only packages which will pass `nix flake check`, i.e. keep only:
|
||||
# - derivations (not package sets)
|
||||
# - packages that build for the given platform
|
||||
(! elem name [ "feeds" "pythonPackagesExtensions" ])
|
||||
&& (passthruPkgs.lib.meta.availableOn passthruPkgs.stdenv.hostPlatform pkg)
|
||||
&& (allPkgs.lib.meta.availableOn allPkgs.stdenv.hostPlatform pkg)
|
||||
)
|
||||
(
|
||||
# expose sane packages and chosen inputs (uninsane.org)
|
||||
(import ./pkgs { pkgs = passthruPkgs; }) // {
|
||||
inherit (passthruPkgs) uninsane-dot-org;
|
||||
(import ./pkgs { pkgs = allPkgs; }) // {
|
||||
inherit (allPkgs) uninsane-dot-org;
|
||||
}
|
||||
)
|
||||
)
|
||||
# self.legacyPackages;
|
||||
{
|
||||
x86_64-linux = (nixpkgsCompiledBy "x86_64-linux").appendOverlays [
|
||||
self.overlays.passthru
|
||||
];
|
||||
}
|
||||
{ inherit (self.legacyPackages) x86_64-linux; }
|
||||
;
|
||||
|
||||
apps."x86_64-linux" =
|
||||
@@ -257,18 +263,13 @@
|
||||
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} "$@"
|
||||
nix build '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} $@
|
||||
sudo nix sign-paths -r -k /run/secrets/nix_serve_privkey $(readlink ./result-${host})
|
||||
|
||||
# XXX: this triggers another config eval & (potentially) build.
|
||||
# if the config changed between these invocations, the above signatures might not apply to the deployed config.
|
||||
# let the user handle that edge case by re-running this whole command.
|
||||
# N.B.: `--fast` option here is critical to cross-compiled deployments: without it the build machine will try to invoke the host machine's `nix` binary.
|
||||
# TODO: solve this by replacing the nixos-build invocation with:
|
||||
# - nix-copy-closure --to $host $result
|
||||
# - on target: nix-env set -p /nix/var/nix/profiles/system $result
|
||||
# - on target: $result/bin/switch-to-configuration
|
||||
nixos-rebuild --flake '.#${host}' ${action} --target-host colin@${addr} --use-remote-sudo "$@" --fast
|
||||
# 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 $@
|
||||
'';
|
||||
deployApp = host: addr: action: {
|
||||
type = "app";
|
||||
@@ -379,47 +380,23 @@
|
||||
servo = deployApp "servo" "servo" "switch";
|
||||
};
|
||||
|
||||
sync = {
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-all" ''
|
||||
RC_lappy=$(nix run '.#sync.lappy' -- "$@")
|
||||
RC_moby=$(nix run '.#sync.moby' -- "$@")
|
||||
RC_desko=$(nix run '.#sync.desko' -- "$@")
|
||||
|
||||
echo "lappy: $RC_lappy"
|
||||
echo "moby: $RC_moby"
|
||||
echo "desko: $RC_desko"
|
||||
'');
|
||||
};
|
||||
|
||||
sync.desko = {
|
||||
# copy music from servo to desko
|
||||
# can run this from any device that has ssh access to desko and servo
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-to-desko" ''
|
||||
sudo mount /mnt/desko-home
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music --compat /mnt/servo-media/Music /mnt/desko-home/Music "$@"
|
||||
'');
|
||||
};
|
||||
|
||||
sync.lappy = {
|
||||
# copy music from servo to lappy
|
||||
# can run this from any device that has ssh access to lappy and servo
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-to-lappy" ''
|
||||
sudo mount /mnt/lappy-home
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music --compress --compat /mnt/servo-media/Music /mnt/lappy-home/Music "$@"
|
||||
'');
|
||||
};
|
||||
|
||||
sync.moby = {
|
||||
# copy music from servo to moby
|
||||
# can run this from any device that has ssh access to moby and servo
|
||||
sync-moby = {
|
||||
# copy music from the current device to moby
|
||||
# TODO: should i actually sync from /mnt/servo-media/Music instead of the local drive?
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-to-moby" ''
|
||||
sudo mount /mnt/moby-home
|
||||
# N.B.: limited by network/disk -> reduce job count to improve pause/resume behavior
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music --compress --compat --jobs 4 /mnt/servo-media/Music /mnt/moby-home/Music "$@"
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music ~/Music /mnt/moby-home/Music
|
||||
'');
|
||||
};
|
||||
|
||||
sync-lappy = {
|
||||
# copy music from servo to lappy
|
||||
# can run this from any device that has ssh access to lappy
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-to-lappy" ''
|
||||
sudo mount /mnt/lappy-home
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music /mnt/servo-media/Music /mnt/lappy-home/Music
|
||||
'');
|
||||
};
|
||||
|
||||
@@ -428,12 +405,12 @@
|
||||
program = builtins.toString (pkgs.writeShellScript "check-all" ''
|
||||
nix run '.#check.nur'
|
||||
RC0=$?
|
||||
nix run '.#check.hostConfigs'
|
||||
nix run '.#check.host-configs'
|
||||
RC1=$?
|
||||
nix run '.#check.rescue'
|
||||
RC2=$?
|
||||
echo "nur: $RC0"
|
||||
echo "hostConfigs: $RC1"
|
||||
echo "host-configs: $RC1"
|
||||
echo "rescue: $RC2"
|
||||
exit $(($RC0 | $RC1 | $RC2))
|
||||
'');
|
||||
@@ -450,19 +427,19 @@
|
||||
--option restrict-eval true \
|
||||
--option allow-import-from-derivation true \
|
||||
--drv-path --show-trace \
|
||||
-I nixpkgs=${nixpkgs-unpatched} \
|
||||
-I nixpkgs=$(nix-instantiate --find-file nixpkgs) \
|
||||
-I ../../ \
|
||||
| tee # tee to prevent interactive mode
|
||||
'');
|
||||
};
|
||||
|
||||
check.hostConfigs = {
|
||||
check.host-configs = {
|
||||
type = "app";
|
||||
program = let
|
||||
checkHost = host: let
|
||||
shellHost = pkgs.lib.replaceStrings [ "-" ] [ "_" ] host;
|
||||
in ''
|
||||
nix build -v '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} -j2 "$@"
|
||||
nix build -v '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} -j2 $@
|
||||
RC_${shellHost}=$?
|
||||
'';
|
||||
in builtins.toString (pkgs.writeShellScript
|
||||
@@ -480,29 +457,11 @@
|
||||
${checkHost "moby"}
|
||||
${checkHost "rescue"}
|
||||
|
||||
# still want to build the -light variants first so as to avoid multiple simultaneous webkitgtk builds
|
||||
${checkHost "desko-light-next"}
|
||||
${checkHost "moby-light-next"}
|
||||
|
||||
${checkHost "desko-next"}
|
||||
${checkHost "lappy-next"}
|
||||
${checkHost "servo-next"}
|
||||
${checkHost "moby-next"}
|
||||
${checkHost "rescue-next"}
|
||||
|
||||
echo "desko: $RC_desko"
|
||||
echo "lappy: $RC_lappy"
|
||||
echo "servo: $RC_servo"
|
||||
echo "moby: $RC_moby"
|
||||
echo "rescue: $RC_rescue"
|
||||
|
||||
echo "desko-next: $RC_desko_next"
|
||||
echo "lappy-next: $RC_lappy_next"
|
||||
echo "servo-next: $RC_servo_next"
|
||||
echo "moby-next: $RC_moby_next"
|
||||
echo "rescue-next: $RC_rescue_next"
|
||||
|
||||
# i don't really care if the -next hosts fail. i build them mostly to keep the cache fresh/ready
|
||||
exit $(($RC_desko | $RC_lappy | $RC_servo | $RC_moby | $RC_rescue))
|
||||
''
|
||||
);
|
||||
|
@@ -9,9 +9,6 @@
|
||||
# services.distccd.enable = true;
|
||||
# sane.programs.distcc.enableFor.user.guest = true;
|
||||
|
||||
# TODO: remove emulation, but need to fix nixos-rebuild to moby for that.
|
||||
# sane.roles.build-machine.emulation = true;
|
||||
|
||||
sops.secrets.colin-passwd.neededForUsers = true;
|
||||
|
||||
sane.ports.openFirewall = true; # for e.g. nix-serve
|
||||
|
@@ -22,6 +22,7 @@
|
||||
# the device type informs (at least):
|
||||
# - SXMO_WIFI_MODULE
|
||||
# - SXMO_RTW_SCAN_INTERVAL
|
||||
# - SXMO_SYS_FILES
|
||||
# - SXMO_TOUCHSCREEN_ID
|
||||
# - SXMO_MONITOR
|
||||
# - SXMO_ALSA_CONTROL_NAME
|
||||
|
@@ -36,6 +36,7 @@
|
||||
# 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.dialect.enableFor.user.colin = false; # drags in 700MB of x86 dependencies (e.g. gtk4)
|
||||
sane.programs.mercurial.enableFor.user.colin = false; # does not cross compile
|
||||
sane.programs.nvme-cli.enableFor.system = false; # does not cross compile (libhugetlbfs)
|
||||
|
||||
|
@@ -74,7 +74,6 @@ in
|
||||
# without this some GUI apps fail: `DRM_IOCTL_MODE_CREATE_DUMB failed: Cannot allocate memory`
|
||||
# this is because they can't allocate enough video ram.
|
||||
# see related nixpkgs issue: <https://github.com/NixOS/nixpkgs/issues/260222>
|
||||
# TODO(2023/12/03): remove once mesa 23.3.1 lands: <https://github.com/NixOS/nixpkgs/pull/265740>
|
||||
#
|
||||
# the default CMA seems to be 32M.
|
||||
# i was running fine with 256MB from 2022/07-ish through 2022/12-ish, but then the phone quit reliably coming back from sleep (phosh): maybe a memory leak?
|
||||
|
@@ -15,9 +15,9 @@
|
||||
};
|
||||
|
||||
sane.roles.build-machine.enable = true;
|
||||
sane.roles.build-machine.emulation = false;
|
||||
sane.zsh.showDeadlines = false; # ~/knowledge doesn't always exist
|
||||
sane.programs.consoleUtils.suggestedPrograms = [
|
||||
"consoleMediaUtils" # notably, for go2tv / casting
|
||||
"pcConsoleUtils"
|
||||
"sane-scripts.stop-all-servo"
|
||||
];
|
||||
|
@@ -1,57 +1,6 @@
|
||||
# zfs docs:
|
||||
# - <https://nixos.wiki/wiki/ZFS>
|
||||
# - <repo:nixos/nixpkgs:nixos/modules/tasks/filesystems/zfs.nix>
|
||||
#
|
||||
# zfs check health: `zpool status`
|
||||
#
|
||||
# zfs pool creation (requires `boot.supportedFilesystems = [ "zfs" ];`
|
||||
# - 1. identify disk IDs: `ls -l /dev/disk/by-id`
|
||||
# - 2. pool these disks: `zpool create -f -m legacy pool raidz ata-ST4000VN008-2DR166_WDH0VB45 ata-ST4000VN008-2DR166_WDH17616 ata-ST4000VN008-2DR166_WDH0VC8Q ata-ST4000VN008-2DR166_WDH17680`
|
||||
# - legacy documented: <https://superuser.com/questions/790036/what-is-a-zfs-legacy-mount-point>
|
||||
#
|
||||
# import pools: `zpool import pool`
|
||||
# show zfs datasets: `zfs list` (will be empty if haven't imported)
|
||||
# show zfs properties (e.g. compression): `zfs get all pool`
|
||||
# set zfs properties: `zfs set compression=on pool`
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
# hostId: not used for anything except zfs guardrail?
|
||||
# [hex(ord(x)) for x in 'serv']
|
||||
networking.hostId = "73657276";
|
||||
boot.supportedFilesystems = [ "zfs" ];
|
||||
# boot.zfs.enabled = true;
|
||||
boot.zfs.forceImportRoot = false;
|
||||
# scrub all zfs pools weekly:
|
||||
services.zfs.autoScrub.enable = true;
|
||||
boot.extraModprobeConfig = ''
|
||||
# ZFS likes to use half the ram for its own cache and let the kernel push everything else to swap.
|
||||
# so, reduce its cache size
|
||||
# see: <https://askubuntu.com/a/1290387>
|
||||
# see: <https://serverfault.com/a/1119083>
|
||||
# see: <https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max>
|
||||
# for all tunables, see: `man 4 zfs`
|
||||
# to update these parameters without rebooting:
|
||||
# - `echo '4294967296' | sane-sudo-redirect /sys/module/zfs/parameters/zfs_arc_max`
|
||||
options zfs zfs_arc_max=4294967296
|
||||
'';
|
||||
# to be able to mount the pool like this, make sure to tell zfs to NOT manage it itself.
|
||||
# otherwise local-fs.target will FAIL and you will be dropped into a rescue shell.
|
||||
# - `zfs set mountpoint=legacy pool`
|
||||
# if done correctly, the pool can be mounted before this `fileSystems` entry is created:
|
||||
# - `sudo mount -t zfs pool /mnt/persist/pool`
|
||||
fileSystems."/mnt/pool" = {
|
||||
device = "pool";
|
||||
fsType = "zfs";
|
||||
};
|
||||
# services.zfs.zed = ... # TODO: zfs can send me emails when disks fail
|
||||
sane.programs.sysadminUtils.suggestedPrograms = [ "zfs" ];
|
||||
|
||||
sane.persist.stores."ext" = {
|
||||
origin = "/mnt/pool/persist";
|
||||
storeDescription = "external HDD storage";
|
||||
};
|
||||
|
||||
# increase /tmp space (defaults to 50% of RAM) for building large nix things.
|
||||
# even the stock `nixpkgs.linux` consumes > 16 GB of tmp
|
||||
fileSystems."/tmp".options = [ "size=32G" ];
|
||||
@@ -71,7 +20,7 @@
|
||||
};
|
||||
|
||||
# slow, external storage (for archiving, etc)
|
||||
fileSystems."/mnt/usb-hdd" = {
|
||||
fileSystems."/mnt/persist/ext" = {
|
||||
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
||||
fsType = "btrfs";
|
||||
options = [
|
||||
@@ -79,7 +28,12 @@
|
||||
"defaults"
|
||||
];
|
||||
};
|
||||
sane.fs."/mnt/usb-hdd".mount = {};
|
||||
|
||||
sane.persist.stores."ext" = {
|
||||
origin = "/mnt/persist/ext/persist";
|
||||
storeDescription = "external HDD storage";
|
||||
};
|
||||
sane.fs."/mnt/persist/ext".mount = {};
|
||||
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: this is overly broad; only need media and share directories to be persisted
|
||||
|
@@ -27,7 +27,9 @@ in
|
||||
# view refused packets with: `sudo journalctl -k`
|
||||
# networking.firewall.logRefusedPackets = true;
|
||||
|
||||
# these useDHCP lines are legacy from the auto-generated config. might be safe to remove now?
|
||||
# The global useDHCP flag is deprecated, therefore explicitly set to false here.
|
||||
# Per-interface useDHCP will be mandatory in the future, so this generated config
|
||||
# replicates the default behaviour.
|
||||
networking.useDHCP = false;
|
||||
networking.interfaces.eth0.useDHCP = true;
|
||||
# XXX colin: probably don't need this. wlan0 won't be populated unless i touch a value in networking.interfaces.wlan0
|
||||
@@ -42,6 +44,41 @@ in
|
||||
# "9.9.9.9"
|
||||
# ];
|
||||
|
||||
# use systemd's stub resolver.
|
||||
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
|
||||
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets
|
||||
# in the ovnps namespace to use the provider's DNS resolvers.
|
||||
# a weakness is we can only query 1 NS at a time (unless we were to clone the packets?)
|
||||
# there also seems to be some cache somewhere that's shared between the two namespaces.
|
||||
# i think this is a libc thing. might need to leverage proper cgroups to _really_ kill it.
|
||||
# - getent ahostsv4 www.google.com
|
||||
# - try fix: <https://serverfault.com/questions/765989/connect-to-3rd-party-vpn-server-but-dont-use-it-as-the-default-route/766290#766290>
|
||||
services.resolved.enable = true;
|
||||
# without DNSSEC:
|
||||
# - dig matrix.org => works
|
||||
# - curl https://matrix.org => works
|
||||
# with default DNSSEC:
|
||||
# - dig matrix.org => works
|
||||
# - curl https://matrix.org => fails
|
||||
# i don't know why. this might somehow be interfering with the DNS run on this device (trust-dns)
|
||||
services.resolved.dnssec = "false";
|
||||
networking.nameservers = [
|
||||
# use systemd-resolved resolver
|
||||
# full resolver (which understands /etc/hosts) lives on 127.0.0.53
|
||||
# stub resolver (just forwards upstream) lives on 127.0.0.54
|
||||
"127.0.0.53"
|
||||
];
|
||||
|
||||
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
|
||||
# in a way that's unaware of my VPN routing, so routes are frequently poor against
|
||||
# services which advertise different IPs based on geolocation.
|
||||
# nscd claims to be usable without a cache, but in practice i can't get it to not cache!
|
||||
# nsncd is the Name Service NON-Caching Daemon. it's a drop-in that doesn't cache;
|
||||
# this is OK on the host -- because systemd-resolved caches. it's probably sub-optimal
|
||||
# in the netns and we query upstream DNS more often than needed. hm.
|
||||
# TODO: run a separate recursive resolver in each namespace.
|
||||
services.nscd.enableNsncd = true;
|
||||
|
||||
# services.resolved.extraConfig = ''
|
||||
# # docs: `man resolved.conf`
|
||||
# # DNS servers to use via the `wg-ovpns` interface.
|
||||
|
@@ -1,84 +0,0 @@
|
||||
# as of 2023/12/02: complete blockchain is 530 GiB (on-disk size may be larger)
|
||||
#
|
||||
# ports:
|
||||
# - 8333: for node-to-node communications
|
||||
# - 8332: rpc (client-to-node)
|
||||
#
|
||||
# rpc setup:
|
||||
# - generate a password
|
||||
# - use: <https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py>
|
||||
# (rpcauth.py is not included in the `'.#bitcoin'` package result)
|
||||
# - `wget https://raw.githubusercontent.com/bitcoin/bitcoin/master/share/rpcauth/rpcauth.py`
|
||||
# - `python ./rpcauth.py colin`
|
||||
# - copy the hash here. it's SHA-256, so safe to be public.
|
||||
# - add "rpcuser=colin" and "rpcpassword=<output>" to secrets/servo/bitcoin.conf (i.e. ~/.bitcoin/bitcoin.conf)
|
||||
# - bitcoin.conf docs: <https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md>
|
||||
# - validate with `bitcoin-cli -netinfo`
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
let
|
||||
# wrapper to run bitcoind with the tor onion address as externalip (computed at runtime)
|
||||
_bitcoindWithExternalIp = with pkgs; writeShellScriptBin "bitcoind" ''
|
||||
externalip="$(cat /var/lib/tor/onion/bitcoind/hostname)"
|
||||
exec ${bitcoind}/bin/bitcoind "-externalip=$externalip" "$@"
|
||||
'';
|
||||
# the package i provide to services.bitcoind ends up on system PATH, and used by other tools like clightning.
|
||||
# therefore, even though services.bitcoind only needs `bitcoind` binary, provide all the other bitcoin-related binaries (notably `bitcoin-cli`) as well:
|
||||
bitcoindWithExternalIp = with pkgs; symlinkJoin {
|
||||
name = "bitcoind-with-external-ip";
|
||||
paths = [ _bitcoindWithExternalIp bitcoind ];
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.ext = [
|
||||
# /var/lib/monero/lmdb is what consumes most of the space
|
||||
{ user = "bitcoind-mainnet"; group = "bitcoind-mainnet"; path = "/var/lib/bitcoind-mainnet"; }
|
||||
];
|
||||
|
||||
# sane.ports.ports."8333" = {
|
||||
# # this allows other nodes and clients to download blocks from me.
|
||||
# protocol = [ "tcp" ];
|
||||
# visibleTo.wan = true;
|
||||
# description = "colin-bitcoin";
|
||||
# };
|
||||
|
||||
services.tor.relay.onionServices.bitcoind = {
|
||||
version = 3;
|
||||
map = [{
|
||||
# by default tor will route public tor port P to 127.0.0.1:P.
|
||||
# so if this port is the same as clightning would natively use, then no further config is needed here.
|
||||
# see: <https://2019.www.torproject.org/docs/tor-manual.html.en#HiddenServicePort>
|
||||
port = 8333;
|
||||
# target.port; target.addr; #< set if tor port != clightning port
|
||||
}];
|
||||
# allow "tor" group (i.e. bitcoind-mainnet) to read /var/lib/tor/onion/bitcoind/hostname
|
||||
settings.HiddenServiceDirGroupReadable = true;
|
||||
};
|
||||
|
||||
services.bitcoind.mainnet = {
|
||||
enable = true;
|
||||
package = bitcoindWithExternalIp;
|
||||
rpc.users.colin = {
|
||||
# see docs at top of file for how to generate this
|
||||
passwordHMAC = "30002c05d82daa210550e17a182db3f3$6071444151281e1aa8a2729f75e3e2d224e9d7cac3974810dab60e7c28ffaae4";
|
||||
};
|
||||
extraConfig = ''
|
||||
# don't load the wallet, and disable wallet RPC calls
|
||||
disablewallet=1
|
||||
# proxy all outbound traffic through Tor
|
||||
proxy=127.0.0.1:9050
|
||||
'';
|
||||
};
|
||||
|
||||
users.users.bitcoind-mainnet.extraGroups = [ "tor" ];
|
||||
|
||||
systemd.services.bitcoind-mainnet.serviceConfig.RestartSec = "30s"; #< default is 0
|
||||
|
||||
sane.users.colin.fs.".bitcoin/bitcoin.conf" = sane-lib.fs.wantedSymlinkTo config.sops.secrets."bitcoin.conf".path;
|
||||
sops.secrets."bitcoin.conf" = {
|
||||
mode = "0600";
|
||||
owner = "colin";
|
||||
group = "users";
|
||||
};
|
||||
|
||||
sane.programs.bitcoind.enableFor.user.colin = true; # for debugging/administration: `bitcoin-cli`
|
||||
}
|
@@ -1,766 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ps.pyln-client ])"
|
||||
|
||||
# pyln-client docs: <https://github.com/ElementsProject/lightning/tree/master/contrib/pyln-client>
|
||||
# terminology:
|
||||
# - "scid": "Short Channel ID", e.g. 123456x7890x0
|
||||
# from this id, we can locate the actual channel, its peers, and its parameters
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import math
|
||||
import sys
|
||||
import time
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
from pyln.client import LightningRpc, Millisatoshi, RpcError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RPC_FILE = "/var/lib/clightning/bitcoin/lightning-rpc"
|
||||
# CLTV (HLTC delta) of the final hop
|
||||
# set this too low and you might get inadvertent channel closures (?)
|
||||
CLTV = 18
|
||||
|
||||
# for every sequentally failed transaction, delay this much before trying again.
|
||||
# note that the initial route building process can involve 10-20 "transient" failures, as it discovers dead channels.
|
||||
TX_FAIL_BACKOFF = 0.8
|
||||
MAX_SEQUENTIAL_JOB_FAILURES = 200
|
||||
|
||||
class LoopError(Enum):
|
||||
""" error when trying to loop sats, or when unable to calculate a route for the loop """
|
||||
TRANSIENT = "TRANSIENT" # try again, we'll maybe find a different route
|
||||
NO_ROUTE = "NO_ROUTE"
|
||||
|
||||
class RouteError(Enum):
|
||||
""" error when calculated a route """
|
||||
HAS_BASE_FEE = "HAS_BASE_FEE"
|
||||
NO_ROUTE = "NO_ROUTE"
|
||||
|
||||
class Metrics:
|
||||
looped_msat: int = 0
|
||||
sendpay_fail: int = 0
|
||||
sendpay_succeed: int = 0
|
||||
own_bad_channel: int = 0
|
||||
no_route: int = 0
|
||||
in_ch_unsatisfiable: int = 0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"looped:{self.looped_msat}, tx:{self.sendpay_succeed}, tx_fail:{self.sendpay_fail}, own_bad_ch:{self.own_bad_channel}, no_route:{self.no_route}, in_ch_restricted:{self.in_ch_unsatisfiable}"
|
||||
|
||||
@dataclass
|
||||
class TxBounds:
|
||||
max_msat: int
|
||||
min_msat: int = 0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"TxBounds({self.min_msat} <= msat <= {self.max_msat})"
|
||||
|
||||
def is_satisfiable(self) -> bool:
|
||||
return self.min_msat <= self.max_msat
|
||||
|
||||
def raise_max_to_be_satisfiable(self) -> "Self":
|
||||
if self.max_msat < self.min_msat:
|
||||
logger.debug(f"raising max_msat to be consistent: {self.max_msat} -> {self.min_msat}")
|
||||
return TxBounds(self.min_msat, self.min_msat)
|
||||
return TxBounds(min_msat=self.min_msat, max_msat=self.max_msat)
|
||||
|
||||
def intersect(self, other: "TxBounds") -> "Self":
|
||||
return TxBounds(
|
||||
min_msat=max(self.min_msat, other.min_msat),
|
||||
max_msat=min(self.max_msat, other.max_msat),
|
||||
)
|
||||
|
||||
def restrict_to_htlc(self, ch: "LocalChannel", why: str = "") -> "Self":
|
||||
"""
|
||||
apply min/max HTLC size restrictions of the given channel.
|
||||
"""
|
||||
if ch:
|
||||
why = why or ch.directed_scid_to_me
|
||||
if why: why = f"{why}: "
|
||||
|
||||
new_min, new_max = self.min_msat, self.max_msat
|
||||
if ch.htlc_minimum_to_me > self.min_msat:
|
||||
new_min = ch.htlc_minimum_to_me
|
||||
logger.debug(f"{why}raising min_msat due to HTLC requirements: {self.min_msat} -> {new_min}")
|
||||
if ch.htlc_maximum_to_me < self.max_msat:
|
||||
new_max = ch.htlc_maximum_to_me
|
||||
logger.debug(f"{why}lowering max_msat due to HTLC requirements: {self.max_msat} -> {new_max}")
|
||||
return TxBounds(min_msat=new_min, max_msat=new_max)
|
||||
|
||||
def restrict_to_zero_fees(self, ch: "LocalChannel"=None, base: int=0, ppm: int=0, why:str = "") -> "Self":
|
||||
"""
|
||||
restrict tx size such that PPM fees are zero.
|
||||
if the channel has a base fee, then `max_msat` is forced to 0.
|
||||
"""
|
||||
if ch:
|
||||
why = why or ch.directed_scid_to_me
|
||||
self = self.restrict_to_zero_fees(base=ch.to_me["base_fee_millisatoshi"], ppm=ch.to_me["fee_per_millionth"], why=why)
|
||||
|
||||
if why: why = f"{why}: "
|
||||
|
||||
new_max = self.max_msat
|
||||
ppm_max = math.ceil(1000000 / ppm) - 1 if ppm != 0 else new_max
|
||||
if ppm_max < new_max:
|
||||
logger.debug(f"{why}decreasing max_msat due to fee ppm: {new_max} -> {ppm_max}")
|
||||
new_max = ppm_max
|
||||
|
||||
if base != 0:
|
||||
logger.debug(f"{why}free route impossible: channel has base fees")
|
||||
new_max = 0
|
||||
|
||||
return TxBounds(min_msat=self.min_msat, max_msat=new_max)
|
||||
|
||||
|
||||
class LocalChannel:
|
||||
def __init__(self, channels: list, rpc: "RpcHelper"):
|
||||
assert 0 < len(channels) <= 2, f"unexpected: channel count: {channels}"
|
||||
out = None
|
||||
in_ = None
|
||||
for c in channels:
|
||||
if c["source"] == rpc.self_id:
|
||||
assert out is None, f"unexpected: multiple channels from self: {channels}"
|
||||
out = c
|
||||
if c["destination"] == rpc.self_id:
|
||||
assert in_ is None, f"unexpected: multiple channels to self: {channels}"
|
||||
in_ = c
|
||||
|
||||
# assert out is not None, f"no channel from self: {channels}"
|
||||
# assert in_ is not None, f"no channel to self: {channels}"
|
||||
|
||||
if out and in_:
|
||||
assert out["destination"] == in_["source"], f"channel peers are asymmetric?! {channels}"
|
||||
assert out["short_channel_id"] == in_["short_channel_id"], f"channel ids differ?! {channels}"
|
||||
|
||||
self.from_me = out
|
||||
self.to_me = in_
|
||||
self.remote_node = rpc.node(self.remote_peer)
|
||||
self.peer_ch = rpc.peerchannel(self.scid, self.remote_peer)
|
||||
self.forwards_from_me = rpc.rpc.listforwards(out_channel=self.scid, status="settled")["forwards"]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.to_str(with_scid=True, with_bal_ratio=True, with_cost=False, with_ppm_theirs=False)
|
||||
|
||||
def to_str(
|
||||
self,
|
||||
with_peer_id:bool = False,
|
||||
with_scid:bool = False,
|
||||
with_bal_msat:bool = False,
|
||||
with_bal_ratio:bool = False,
|
||||
with_cost:bool = False,
|
||||
with_ppm_theirs:bool = False,
|
||||
with_ppm_mine:bool = False,
|
||||
with_profits:bool = True,
|
||||
with_payments:bool = False,
|
||||
) -> str:
|
||||
base_flag = "*" if not self.online or self.base_fee_to_me != 0 else ""
|
||||
alias = f"({self.remote_alias}){base_flag}"
|
||||
peerid = f" {self.remote_peer}" if with_peer_id else ""
|
||||
scid = f" scid:{self.scid:>13}" if with_scid else ""
|
||||
bal = f" S:{int(self.sendable):11}/R:{int(self.receivable):11}" if with_bal_msat else ""
|
||||
ratio = f" MINE:{(100*self.send_ratio):>8.4f}%" if with_bal_ratio else ""
|
||||
payments = f" OUT:{int(self.out_fulfilled_msat):>11}/IN:{int(self.in_fulfilled_msat):>11}" if with_payments else ""
|
||||
profits = f" P$:{int(self.fees_lifetime_mine):>8}" if with_profits else ""
|
||||
cost = f" COST:{self.opportunity_cost_lent:>8}" if with_cost else ""
|
||||
ppm_theirs = self.ppm_to_me if self.to_me else "N/A"
|
||||
ppm_theirs = f" PPM_THEIRS:{ppm_theirs:>6}" if with_ppm_theirs else ""
|
||||
ppm_mine = self.ppm_from_me if self.from_me else "N/A"
|
||||
ppm_mine = f" PPM_MINE:{ppm_mine:>6}" if with_ppm_mine else ""
|
||||
return f"channel{alias:30}{peerid}{scid}{bal}{ratio}{payments}{profits}{cost}{ppm_theirs}{ppm_mine}"
|
||||
|
||||
|
||||
@property
|
||||
def online(self) -> bool:
|
||||
return self.from_me and self.to_me
|
||||
|
||||
@property
|
||||
def remote_peer(self) -> str:
|
||||
if self.from_me:
|
||||
return self.from_me["destination"]
|
||||
else:
|
||||
return self.to_me["source"]
|
||||
|
||||
@property
|
||||
def remote_alias(self) -> str:
|
||||
return self.remote_node["alias"]
|
||||
|
||||
@property
|
||||
def scid(self) -> str:
|
||||
if self.from_me:
|
||||
return self.from_me["short_channel_id"]
|
||||
else:
|
||||
return self.to_me["short_channel_id"]
|
||||
|
||||
@property
|
||||
def htlc_minimum_to_me(self) -> Millisatoshi:
|
||||
return self.to_me["htlc_minimum_msat"]
|
||||
|
||||
@property
|
||||
def htlc_minimum_from_me(self) -> Millisatoshi:
|
||||
return self.from_me["htlc_minimum_msat"]
|
||||
|
||||
@property
|
||||
def htlc_minimum(self) -> Millisatoshi:
|
||||
return max(self.htlc_minimum_to_me, self.htlc_minimum_from_me)
|
||||
|
||||
@property
|
||||
def htlc_maximum_to_me(self) -> Millisatoshi:
|
||||
return self.to_me["htlc_maximum_msat"]
|
||||
|
||||
@property
|
||||
def htlc_maximum_from_me(self) -> Millisatoshi:
|
||||
return self.from_me["htlc_maximum_msat"]
|
||||
|
||||
@property
|
||||
def htlc_maximum(self) -> Millisatoshi:
|
||||
return min(self.htlc_maximum_to_me, self.htlc_maximum_from_me)
|
||||
|
||||
@property
|
||||
def direction_to_me(self) -> int:
|
||||
return self.to_me["direction"]
|
||||
|
||||
@property
|
||||
def direction_from_me(self) -> int:
|
||||
return self.from_me["direction"]
|
||||
|
||||
@property
|
||||
def directed_scid_to_me(self) -> str:
|
||||
return f"{self.scid}/{self.direction_to_me}"
|
||||
|
||||
@property
|
||||
def directed_scid_from_me(self) -> str:
|
||||
return f"{self.scid}/{self.direction_from_me}"
|
||||
|
||||
@property
|
||||
def delay_them(self) -> str:
|
||||
return self.to_me["delay"]
|
||||
|
||||
@property
|
||||
def delay_me(self) -> str:
|
||||
return self.from_me["delay"]
|
||||
|
||||
@property
|
||||
def ppm_to_me(self) -> int:
|
||||
return self.to_me["fee_per_millionth"]
|
||||
|
||||
@property
|
||||
def ppm_from_me(self) -> int:
|
||||
return self.from_me["fee_per_millionth"]
|
||||
# return self.peer_ch["fee_proportional_millionths"]
|
||||
|
||||
@property
|
||||
def base_fee_to_me(self) -> int:
|
||||
return self.to_me["base_fee_millisatoshi"]
|
||||
|
||||
@property
|
||||
def receivable(self) -> int:
|
||||
return self.peer_ch["receivable_msat"]
|
||||
|
||||
@property
|
||||
def sendable(self) -> int:
|
||||
return self.peer_ch["spendable_msat"]
|
||||
|
||||
@property
|
||||
def in_fulfilled_msat(self) -> Millisatoshi:
|
||||
return self.peer_ch["in_fulfilled_msat"]
|
||||
|
||||
@property
|
||||
def out_fulfilled_msat(self) -> Millisatoshi:
|
||||
return self.peer_ch["out_fulfilled_msat"]
|
||||
|
||||
@property
|
||||
def fees_lifetime_mine(self) -> Millisatoshi:
|
||||
return sum(fwd["fee_msat"] for fwd in self.forwards_from_me)
|
||||
|
||||
@property
|
||||
def send_ratio(self) -> float:
|
||||
cap = self.receivable + self.sendable
|
||||
return self.sendable / cap
|
||||
|
||||
@property
|
||||
def opportunity_cost_lent(self) -> int:
|
||||
""" how much msat did we gain by pushing their channel to its current balance? """
|
||||
return int(self.receivable * self.ppm_from_me / 1000000)
|
||||
|
||||
class RpcHelper:
|
||||
def __init__(self, rpc: LightningRpc):
|
||||
self.rpc = rpc
|
||||
self.self_id = rpc.getinfo()["id"]
|
||||
|
||||
def localchannel(self, scid: str) -> LocalChannel:
|
||||
listchan = self.rpc.listchannels(scid)
|
||||
# this assertion would probably indicate a typo in the scid
|
||||
assert listchan and listchan.get("channels", []) != [], f"bad listchannels for {scid}: {listchan}"
|
||||
return LocalChannel(listchan["channels"], self)
|
||||
|
||||
def node(self, id: str) -> dict:
|
||||
nodes = self.rpc.listnodes(id)["nodes"]
|
||||
assert len(nodes) == 1, f"unexpected: multiple nodes for {id}: {nodes}"
|
||||
return nodes[0]
|
||||
|
||||
def peerchannel(self, scid: str, peer_id: str) -> dict:
|
||||
peerchannels = self.rpc.listpeerchannels(peer_id)["channels"]
|
||||
channels = [c for c in peerchannels if c["short_channel_id"] == scid]
|
||||
assert len(channels) == 1, f"expected exactly 1 channel, got: {channels}"
|
||||
return channels[0]
|
||||
|
||||
def try_getroute(self, *args, **kwargs) -> dict | None:
|
||||
""" wrapper for getroute which returns None instead of error if no route exists """
|
||||
try:
|
||||
route = self.rpc.getroute(*args, **kwargs)
|
||||
except RpcError as e:
|
||||
logger.debug(f"rpc failed: {e}")
|
||||
return None
|
||||
else:
|
||||
route = route["route"]
|
||||
if route == []: return None
|
||||
return route
|
||||
|
||||
class LoopRouter:
|
||||
def __init__(self, rpc: RpcHelper, metrics: Metrics = None):
|
||||
self.rpc = rpc
|
||||
self.metrics = metrics or Metrics()
|
||||
self.bad_channels = [] # list of directed scid
|
||||
self.nonzero_base_channels = [] # list of directed scid
|
||||
|
||||
def drop_caches(self) -> None:
|
||||
logger.info("LoopRouter.drop_caches()")
|
||||
self.bad_channels = []
|
||||
|
||||
def _get_directed_scid(self, scid: str, direction: int) -> dict:
|
||||
channels = self.rpc.rpc.listchannels(scid)["channels"]
|
||||
channels = [c for c in channels if c["direction"] == direction]
|
||||
assert len(channels) == 1, f"expected exactly 1 channel: {channels}"
|
||||
return channels[0]
|
||||
|
||||
def loop_once(self, out_scid: str, in_scid: str, bounds: TxBounds) -> LoopError|int:
|
||||
out_ch = self.rpc.localchannel(out_scid)
|
||||
in_ch = self.rpc.localchannel(in_scid)
|
||||
|
||||
if out_ch.directed_scid_from_me in self.bad_channels or in_ch.directed_scid_to_me in self.bad_channels:
|
||||
logger.info(f"loop {out_scid} -> {in_scid} failed in our own channel")
|
||||
self.metrics.own_bad_channel += 1
|
||||
return LoopError.TRANSIENT
|
||||
|
||||
# bounds = bounds.restrict_to_htlc(out_ch) # htlc bounds seem to be enforced only in the outward direction
|
||||
bounds = bounds.restrict_to_htlc(in_ch)
|
||||
bounds = bounds.restrict_to_zero_fees(in_ch)
|
||||
if not bounds.is_satisfiable():
|
||||
self.metrics.in_ch_unsatisfiable += 1
|
||||
return LoopError.NO_ROUTE
|
||||
|
||||
logger.debug(f"route with bounds {bounds}")
|
||||
route = self.route(out_ch, in_ch, bounds)
|
||||
logger.debug(f"route: {route}")
|
||||
if route == RouteError.NO_ROUTE:
|
||||
self.metrics.no_route += 1
|
||||
return LoopError.NO_ROUTE
|
||||
elif route == RouteError.HAS_BASE_FEE:
|
||||
# try again with a different route
|
||||
return LoopError.TRANSIENT
|
||||
|
||||
amount_msat = route[0]["amount_msat"]
|
||||
invoice_id = f"loop-{time.time():.6f}".replace(".", "_")
|
||||
invoice_desc = f"bal {out_scid}:{in_scid}"
|
||||
invoice = self.rpc.rpc.invoice("any", invoice_id, invoice_desc)
|
||||
logger.debug(f"invoice: {invoice}")
|
||||
|
||||
payment = self.rpc.rpc.sendpay(route, invoice["payment_hash"], invoice_id, amount_msat, invoice["bolt11"], invoice["payment_secret"])
|
||||
logger.debug(f"sent: {payment}")
|
||||
|
||||
try:
|
||||
wait = self.rpc.rpc.waitsendpay(invoice["payment_hash"])
|
||||
logger.debug(f"result: {wait}")
|
||||
except RpcError as e:
|
||||
self.metrics.sendpay_fail += 1
|
||||
err_data = e.error["data"]
|
||||
err_scid, err_dir = err_data["erring_channel"], err_data["erring_direction"]
|
||||
err_directed_scid = f"{err_scid}/{err_dir}"
|
||||
logger.debug(f"ch failed, adding to excludes: {err_directed_scid}; {e.error}")
|
||||
self.bad_channels.append(err_directed_scid)
|
||||
return LoopError.TRANSIENT
|
||||
else:
|
||||
self.metrics.sendpay_succeed += 1
|
||||
self.metrics.looped_msat += int(amount_msat)
|
||||
return int(amount_msat)
|
||||
|
||||
def route(self, out_ch: LocalChannel, in_ch: LocalChannel, bounds: TxBounds) -> list[dict] | RouteError:
|
||||
exclude = [
|
||||
# ensure the payment doesn't cross either channel in reverse.
|
||||
# note that this doesn't preclude it from taking additional trips through self, with other peers.
|
||||
# out_ch.directed_scid_to_me,
|
||||
# in_ch.directed_scid_from_me,
|
||||
|
||||
# alternatively, never route through self. this avoids a class of logic error, like what to do with fees i charge "myself".
|
||||
self.rpc.self_id
|
||||
] + self.bad_channels + self.nonzero_base_channels
|
||||
|
||||
out_peer = out_ch.remote_peer
|
||||
in_peer = in_ch.remote_peer
|
||||
|
||||
route_or_bounds = bounds
|
||||
while isinstance(route_or_bounds, TxBounds):
|
||||
old_bounds = route_or_bounds
|
||||
route_or_bounds = self._find_partial_route(out_peer, in_peer, old_bounds, exclude=exclude)
|
||||
if route_or_bounds == old_bounds:
|
||||
return RouteError.NO_ROUTE
|
||||
|
||||
if isinstance(route_or_bounds, RouteError):
|
||||
return route_or_bounds
|
||||
|
||||
route = self._add_route_endpoints(route_or_bounds, out_ch, in_ch)
|
||||
return route
|
||||
|
||||
def _find_partial_route(self, out_peer: str, in_peer: str, bounds: TxBounds, exclude: list[str]=[]) -> list[dict] | RouteError | TxBounds:
|
||||
route = self.rpc.try_getroute(in_peer, amount_msat=bounds.max_msat, riskfactor=0, fromid=out_peer, exclude=exclude, cltv=CLTV)
|
||||
if route is None:
|
||||
logger.debug(f"no route for {bounds.max_msat}msat {out_peer} -> {in_peer}")
|
||||
return RouteError.NO_ROUTE
|
||||
|
||||
send_msat = route[0]["amount_msat"]
|
||||
if send_msat != Millisatoshi(bounds.max_msat):
|
||||
logger.debug(f"found route with non-zero fee: {send_msat} -> {bounds.max_msat}. {route}")
|
||||
|
||||
error = None
|
||||
for hop in route:
|
||||
hop_scid = hop["channel"]
|
||||
hop_dir = hop["direction"]
|
||||
directed_scid = f"{hop_scid}/{hop_dir}"
|
||||
ch = self._get_directed_scid(hop_scid, hop_dir)
|
||||
if ch["base_fee_millisatoshi"] != 0:
|
||||
self.nonzero_base_channels.append(directed_scid)
|
||||
error = RouteError.HAS_BASE_FEE
|
||||
bounds = bounds.restrict_to_zero_fees(ppm=ch["fee_per_millionth"], why=directed_scid)
|
||||
|
||||
return bounds.raise_max_to_be_satisfiable() if error is None else error
|
||||
|
||||
return route
|
||||
|
||||
def _add_route_endpoints(self, route, out_ch: LocalChannel, in_ch: LocalChannel):
|
||||
inbound_hop = dict(
|
||||
id=self.rpc.self_id,
|
||||
channel=in_ch.scid,
|
||||
direction=in_ch.direction_to_me,
|
||||
amount_msat=route[-1]["amount_msat"],
|
||||
delay=route[-1]["delay"],
|
||||
style="tlv",
|
||||
)
|
||||
route = self._add_route_delay(route, in_ch.delay_them) + [ inbound_hop ]
|
||||
|
||||
outbound_hop = dict(
|
||||
id=out_ch.remote_peer,
|
||||
channel=out_ch.scid,
|
||||
direction=out_ch.direction_from_me,
|
||||
amount_msat=route[0]["amount_msat"],
|
||||
delay=route[0]["delay"] + out_ch.delay_them,
|
||||
style="tlv",
|
||||
)
|
||||
route = [ outbound_hop ] + route
|
||||
return route
|
||||
|
||||
def _add_route_delay(self, route: list[dict], delay: int) -> list[dict]:
|
||||
return [ dict(hop, delay=hop["delay"] + delay) for hop in route ]
|
||||
|
||||
@dataclass
|
||||
class LoopJob:
|
||||
out: str # scid
|
||||
in_: str # scid
|
||||
amount: int
|
||||
|
||||
@dataclass
|
||||
class LoopJobIdle:
|
||||
sec: int = 10
|
||||
|
||||
class LoopJobDone(Enum):
|
||||
COMPLETED = "COMPLETED"
|
||||
ABORTED = "ABORTED"
|
||||
|
||||
class AbstractLoopRunner:
|
||||
def __init__(self, looper: LoopRouter, bounds: TxBounds, parallelism: int):
|
||||
self.looper = looper
|
||||
self.bounds = bounds
|
||||
self.parallelism = parallelism
|
||||
self.bounds_map = {} # map (out:str, in_:str) -> TxBounds. it's a cache so we don't have to try 10 routes every time.
|
||||
|
||||
def pop_job(self) -> LoopJob | LoopJobIdle | LoopJobDone:
|
||||
raise NotImplemented # abstract method
|
||||
|
||||
def finished_job(self, job: LoopJob, progress: int|LoopError) -> None:
|
||||
raise NotImplemented # abstract method
|
||||
|
||||
def run_to_completion(self, exit_on_any_completed:bool = False) -> None:
|
||||
self.exiting = False
|
||||
self.exit_on_any_completed = exit_on_any_completed
|
||||
if self.parallelism == 1:
|
||||
# run inline to aid debugging
|
||||
self._worker_thread()
|
||||
else:
|
||||
with ThreadPoolExecutor(max_workers=self.parallelism) as executor:
|
||||
_ = list(executor.map(lambda _i: self._try_invoke(self._worker_thread), range(self.parallelism)))
|
||||
|
||||
def drop_caches(self) -> None:
|
||||
logger.info("AbstractLoopRunner.drop_caches()")
|
||||
self.looper.drop_caches()
|
||||
self.bounds_map = {}
|
||||
|
||||
|
||||
def _try_invoke(self, f, *args) -> None:
|
||||
"""
|
||||
try to invoke `f` with the provided `args`, and log if it fails.
|
||||
this overcomes the issue that background tasks which fail via Exception otherwise do so silently.
|
||||
"""
|
||||
try:
|
||||
f(*args)
|
||||
except Exception as e:
|
||||
logger.error(f"task failed: {e}")
|
||||
|
||||
|
||||
def _worker_thread(self) -> None:
|
||||
while not self.exiting:
|
||||
job = self.pop_job()
|
||||
logger.debug(f"popped job: {job}")
|
||||
if isinstance(job, LoopJobDone):
|
||||
return self._worker_finished(job)
|
||||
|
||||
if isinstance(job, LoopJobIdle):
|
||||
logger.debug(f"idling for {job.sec}")
|
||||
time.sleep(job.sec)
|
||||
continue
|
||||
|
||||
result = self._execute_job(job)
|
||||
logger.debug(f"finishing job {job} with {result}")
|
||||
self.finished_job(job, result)
|
||||
|
||||
def _execute_job(self, job: LoopJob) -> LoopError|int:
|
||||
bounds = self.bounds_map.get((job.out, job.in_), self.bounds)
|
||||
bounds = bounds.intersect(TxBounds(max_msat=job.amount))
|
||||
if not bounds.is_satisfiable():
|
||||
logger.debug(f"TxBounds for job are unsatisfiable; skipping: {bounds} {job}")
|
||||
return LoopError.NO_ROUTE
|
||||
|
||||
amt_looped = self.looper.loop_once(job.out, job.in_, bounds)
|
||||
if amt_looped in (0, LoopError.NO_ROUTE, LoopError.TRANSIENT):
|
||||
return amt_looped
|
||||
|
||||
logger.info(f"looped {amt_looped} from {job.out} -> {job.in_}")
|
||||
bounds = bounds.intersect(TxBounds(max_msat=amt_looped))
|
||||
|
||||
self.bounds_map[(job.out, job.in_)] = bounds
|
||||
return amt_looped
|
||||
|
||||
def _worker_finished(self, job: LoopJobDone) -> None:
|
||||
if job == LoopJobDone.COMPLETED and self.exit_on_any_completed:
|
||||
logger.debug(f"worker completed -> exiting pool")
|
||||
self.exiting = True
|
||||
|
||||
class LoopPairState:
|
||||
# TODO: use this in MultiLoopBalancer, or stop shoving state in here and put it on LoopBalancer instead.
|
||||
def __init__(self, out: str, in_: str, amount: int):
|
||||
self.out = out
|
||||
self.in_ = in_
|
||||
self.amount_target = amount
|
||||
self.amount_looped = 0
|
||||
self.amount_outstanding = 0
|
||||
self.tx_fail_count = 0
|
||||
self.route_fail_count = 0
|
||||
self.last_job_start_time = None
|
||||
self.failed_tx_throttler = 0 # increase by one every time we fail, decreases more gradually, when we succeed
|
||||
|
||||
class LoopBalancer(AbstractLoopRunner):
|
||||
def __init__(self, out: str, in_: str, amount: int, looper: LoopRouter, bounds: TxBounds, parallelism: int=1):
|
||||
super().__init__(looper, bounds, parallelism)
|
||||
self.state = LoopPairState(out, in_, amount)
|
||||
|
||||
def pop_job(self) -> LoopJob | LoopJobIdle | LoopJobDone:
|
||||
if self.state.tx_fail_count + 10*self.state.route_fail_count >= MAX_SEQUENTIAL_JOB_FAILURES:
|
||||
logger.info(f"giving up ({self.state.out} -> {self.state.in_}): {self.state.tx_fail_count} tx failures, {self.state.route_fail_count} route failures")
|
||||
return LoopJobDone.ABORTED
|
||||
|
||||
if self.state.tx_fail_count + self.state.route_fail_count > 0:
|
||||
# N.B.: last_job_start_time is guaranteed to have been set by now
|
||||
idle_until = self.state.last_job_start_time + TX_FAIL_BACKOFF*self.state.failed_tx_throttler
|
||||
idle_for = idle_until - time.time()
|
||||
if self.state.amount_outstanding != 0 or idle_for > 0:
|
||||
# when we hit transient failures, restrict to just one job in flight at a time.
|
||||
# this is aimed for the initial route building, where multiple jobs in flight is just useless,
|
||||
# but it's not a bad idea for network blips, etc, either.
|
||||
logger.info(f"throttling ({self.state.out} -> {self.state.in_}) for {idle_for:.0f}: {self.state.tx_fail_count} tx failures, {self.state.route_fail_count} route failures")
|
||||
return LoopJobIdle(idle_for) if idle_for > 0 else LoopJobIdle()
|
||||
|
||||
amount_avail = self.state.amount_target - self.state.amount_looped - self.state.amount_outstanding
|
||||
if amount_avail < self.bounds.min_msat:
|
||||
if self.state.amount_outstanding == 0: return LoopJobDone.COMPLETED
|
||||
return LoopJobIdle() # sending out another job would risk over-transferring
|
||||
amount_this_job = min(amount_avail, self.bounds.max_msat)
|
||||
|
||||
self.state.amount_outstanding += amount_this_job
|
||||
self.state.last_job_start_time = time.time()
|
||||
return LoopJob(out=self.state.out, in_=self.state.in_, amount=amount_this_job)
|
||||
|
||||
def finished_job(self, job: LoopJob, progress: int) -> None:
|
||||
self.state.amount_outstanding -= job.amount
|
||||
if progress == LoopError.NO_ROUTE:
|
||||
self.state.route_fail_count += 1
|
||||
self.state.failed_tx_throttler += 10
|
||||
elif progress == LoopError.TRANSIENT:
|
||||
self.state.tx_fail_count += 1
|
||||
self.state.failed_tx_throttler += 1
|
||||
else:
|
||||
self.state.amount_looped += progress
|
||||
self.state.tx_fail_count = 0
|
||||
self.state.route_fail_count = 0
|
||||
self.state.failed_tx_throttler = max(0, self.state.failed_tx_throttler - 0.2)
|
||||
logger.info(f"loop progressed ({job.out} -> {job.in_}) {progress}: {self.state.amount_looped} of {self.state.amount_target}")
|
||||
|
||||
class MultiLoopBalancer(AbstractLoopRunner):
|
||||
"""
|
||||
multiplexes jobs between multiple LoopBalancers.
|
||||
note that the child LoopBalancers don't actually execute the jobs -- just produce them.
|
||||
"""
|
||||
def __init__(self, looper: LoopRouter, bounds: TxBounds, parallelism: int=1):
|
||||
super().__init__(looper, bounds, parallelism)
|
||||
self.loops = []
|
||||
# job_index: increments on every job so we can grab jobs evenly from each LoopBalancer.
|
||||
# in that event that producers are idling, it can actually increment more than once,
|
||||
# so don't take this too literally
|
||||
self.job_index = 0
|
||||
|
||||
def add_loop(self, out: LocalChannel, in_: LocalChannel, amount: int) -> None:
|
||||
"""
|
||||
start looping sats from out -> in_
|
||||
"""
|
||||
assert not any(l.state.out == out.scid and l.state.in_ == in_.scid for l in self.loops), f"tried to add duplicate loops from {out} -> {in_}"
|
||||
logger.info(f"looping from ({out}) to ({in_})")
|
||||
self.loops.append(LoopBalancer(out.scid, in_.scid, amount, self.looper, self.bounds, self.parallelism))
|
||||
|
||||
def pop_job(self) -> LoopJob | LoopJobIdle | LoopJobDone:
|
||||
# N.B.: this can be called in parallel, so try to be consistent enough to not crash
|
||||
|
||||
idle_job = None
|
||||
abort_job = None
|
||||
for i, _ in enumerate(self.loops):
|
||||
loop = self.loops[(self.job_index + i) % len(self.loops)]
|
||||
self.job_index += 1
|
||||
job = loop.pop_job()
|
||||
if isinstance(job, LoopJob):
|
||||
return job
|
||||
if isinstance(job, LoopJobIdle):
|
||||
idle_job = LoopJobIdle(min(job.sec, idle_job.sec)) if idle_job is not None else job
|
||||
if job == LoopJobDone.ABORTED:
|
||||
abort_job = job
|
||||
|
||||
# either there's a task to idle, or we have to terminate.
|
||||
# if terminating, terminate ABORTED if any job aborted, else COMPLETED
|
||||
if idle_job is not None: return idle_job
|
||||
if abort_job is not None: return abort_job
|
||||
return LoopJobDone.COMPLETED
|
||||
|
||||
def finished_job(self, job: LoopJob, progress: int) -> None:
|
||||
# this assumes (enforced externally) that we have only one loop for a given out/in_ pair
|
||||
for l in self.loops:
|
||||
if l.state.out == job.out and l.state.in_ == job.in_:
|
||||
l.finished_job(job, progress)
|
||||
|
||||
logger.info(f"total: {self.looper.metrics}")
|
||||
|
||||
|
||||
def balance_loop(rpc: RpcHelper, out: str, in_: str, amount_msat: int, min_msat: int, max_msat: int, parallelism: int):
|
||||
looper = LoopRouter(rpc)
|
||||
bounds = TxBounds(min_msat=min_msat, max_msat=max_msat)
|
||||
balancer = LoopBalancer(out, in_, amount_msat, looper, bounds, parallelism)
|
||||
|
||||
balancer.run_to_completion()
|
||||
|
||||
def autobalance_once(rpc: RpcHelper, metrics: Metrics, bounds: TxBounds, parallelism: int) -> bool:
|
||||
"""
|
||||
autobalances all channels.
|
||||
returns True if channels are balanced (or as balanced as can be); False if in need of further balancing
|
||||
"""
|
||||
looper = LoopRouter(rpc, metrics)
|
||||
balancer = MultiLoopBalancer(looper, bounds, parallelism)
|
||||
|
||||
channels = []
|
||||
for peerch in rpc.rpc.listpeerchannels()["channels"]:
|
||||
try:
|
||||
channels.append(rpc.localchannel(peerch["short_channel_id"]))
|
||||
except:
|
||||
logger.info(f"NO CHANNELS for {peerch['peer_id']}")
|
||||
|
||||
channels = [ch for ch in channels if ch.online and ch.base_fee_to_me == 0]
|
||||
give_to = [ ch for ch in channels if ch.send_ratio > 0.95 ]
|
||||
take_from = [ ch for ch in channels if ch.send_ratio < 0.20 ]
|
||||
|
||||
if give_to == [] and take_from == []:
|
||||
return True
|
||||
|
||||
for to in give_to:
|
||||
for from_ in take_from:
|
||||
balancer.add_loop(to, from_, 10000000)
|
||||
|
||||
balancer.run_to_completion(exit_on_any_completed=True)
|
||||
return False
|
||||
|
||||
|
||||
def autobalance(rpc: RpcHelper, min_msat: int, max_msat: int, parallelism: int):
|
||||
bounds = TxBounds(min_msat=min_msat, max_msat=max_msat)
|
||||
metrics = Metrics()
|
||||
while not autobalance_once(rpc, metrics, bounds, parallelism):
|
||||
pass
|
||||
|
||||
def show_status(rpc: RpcHelper, full: bool=False):
|
||||
"""
|
||||
show a table of channel balances between peers.
|
||||
"""
|
||||
for peerch in rpc.rpc.listpeerchannels()["channels"]:
|
||||
try:
|
||||
ch = rpc.localchannel(peerch["short_channel_id"])
|
||||
except:
|
||||
print(f"{peerch['peer_id']} scid:{peerch['short_channel_id']} state:{peerch['state']} NO CHANNELS")
|
||||
else:
|
||||
print(ch.to_str(with_scid=True, with_bal_ratio=True, with_payments=True, with_cost=full, with_ppm_theirs=True, with_ppm_mine=True, with_peer_id=full))
|
||||
|
||||
def main():
|
||||
logging.basicConfig()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
parser = argparse.ArgumentParser(description="rebalance lightning channel balances")
|
||||
parser.add_argument("--verbose", action="store_true", help="more logging")
|
||||
parser.add_argument("--min-msat", default="999", help="min transaction size")
|
||||
parser.add_argument("--max-msat", default="1000000", help="max transaction size")
|
||||
parser.add_argument("--jobs", default="1", help="how many HTLCs to keep in-flight at once")
|
||||
subparsers = parser.add_subparsers(help="action")
|
||||
|
||||
status_parser = subparsers.add_parser("status")
|
||||
status_parser.set_defaults(action="status")
|
||||
status_parser.add_argument("--full", action="store_true", help="more info per channel")
|
||||
|
||||
loop_parser = subparsers.add_parser("loop")
|
||||
loop_parser.set_defaults(action="loop")
|
||||
loop_parser.add_argument("out", help="peer id to send tx through")
|
||||
loop_parser.add_argument("in_", help="peer id to receive tx through")
|
||||
loop_parser.add_argument("amount", help="total amount of msat to loop")
|
||||
|
||||
autobal_parser = subparsers.add_parser("autobalance")
|
||||
autobal_parser.set_defaults(action="autobalance")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
rpc = RpcHelper(LightningRpc(RPC_FILE))
|
||||
|
||||
if args.action == "status":
|
||||
show_status(rpc, full=args.full)
|
||||
|
||||
if args.action == "loop":
|
||||
balance_loop(rpc, out=args.out, in_=args.in_, amount_msat=int(args.amount), min_msat=int(args.min_msat), max_msat=int(args.max_msat), parallelism=int(args.jobs))
|
||||
|
||||
if args.action == "autobalance":
|
||||
autobalance(rpc, min_msat=int(args.min_msat), max_msat=int(args.max_msat), parallelism=int(args.jobs))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@@ -1,135 +0,0 @@
|
||||
# clightning is an implementation of Bitcoin's Lightning Network.
|
||||
# as such, this assumes that `services.bitcoin` is enabled.
|
||||
# docs:
|
||||
# - tor clightning config: <https://docs.corelightning.org/docs/tor>
|
||||
# - `lightning-cli` and subcommands: <https://docs.corelightning.org/reference/lightning-cli>
|
||||
# - `man lightningd-config`
|
||||
#
|
||||
# management/setup/use:
|
||||
# - guide: <https://github.com/ElementsProject/lightning>
|
||||
#
|
||||
# debugging:
|
||||
# - `lightning-cli getlog debug`
|
||||
# - `lightning-cli listpays` -> show payments this node sent
|
||||
# - `lightning-cli listinvoices` -> show payments this node received
|
||||
#
|
||||
# first, acquire peers:
|
||||
# - `lightning-cli connect id@host`
|
||||
# where `id` is the node's pubkey, and `host` is perhaps an ip:port tuple, or a hash.onion:port tuple.
|
||||
# for testing, choose any node listed on <https://1ml.com>
|
||||
# - `lightning-cli listpeers`
|
||||
# should show the new peer, with `connected: true`
|
||||
#
|
||||
# then, fund the clightning wallet
|
||||
# - `lightning-cli newaddr`
|
||||
#
|
||||
# then, open channels
|
||||
# - `lightning-cli connect ...`
|
||||
# - `lightning-cli fundchannel <node_id> <amount_in_satoshis>`
|
||||
#
|
||||
# who to federate with?
|
||||
# - a lot of the larger nodes allow hands-free channel creation
|
||||
# - either inbound or outbound, sometimes paid
|
||||
# - find nodes on:
|
||||
# - <https://terminal.lightning.engineering/>
|
||||
# - <https://1ml.com>
|
||||
# - tor nodes: <https://1ml.com/node?order=capacity&iponionservice=true>
|
||||
# - <https://lightningnetwork.plus>
|
||||
# - <https://mempool.space/lightning>
|
||||
# - <https://amboss.space>
|
||||
# - a few tor-capable nodes which allow channel creation:
|
||||
# - <https://c-otto.de/>
|
||||
# - <https://cyberdyne.sh/>
|
||||
# - <https://yalls.org/about/>
|
||||
# - <https://coincept.com/>
|
||||
# - more resources: <https://www.lopp.net/lightning-information.html>
|
||||
# - node routability: https://hashxp.org/lightning/node/<id>
|
||||
# - especially, acquire inbound liquidity via lightningnetwork.plus's swap feature
|
||||
# - most of the opportunities are gated behind a minimum connection or capacity requirement
|
||||
#
|
||||
# tune payment parameters
|
||||
# - `lightning-cli setchannel <id> [feebase] [feeppm] [htlcmin] [htlcmax] [enforcedelay] [ignorefeelimits]`
|
||||
# - e.g. `lightning-cli setchannel all 0 10`
|
||||
# - it's suggested that feebase=0 simplifies routing.
|
||||
#
|
||||
# teardown:
|
||||
# - `lightning-cli withdraw <bc1... dest addr> <amount in satoshis> [feerate]`
|
||||
#
|
||||
# sanity:
|
||||
# - `lightning-cli listfunds`
|
||||
#
|
||||
# to receive a payment (do as `clightning` user):
|
||||
# - `lightning-cli invoice <amount in millisatoshi> <label> <description>`
|
||||
# - specify amount as `any` if undetermined
|
||||
# - then give the resulting bolt11 URI to the payer
|
||||
# to send a payment:
|
||||
# - `lightning-cli pay <bolt11 URI>`
|
||||
# - or `lightning-cli pay <bolt11 URI> [amount_msat] [label] [riskfactor] [maxfeepercent] ...`
|
||||
# - amount_msat must be "null" if the bolt11 URI specifies a value
|
||||
# - riskfactor defaults to 10
|
||||
# - maxfeepercent defaults to 0.5
|
||||
# - label is a human-friendly label for my records
|
||||
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
sane.persist.sys.byStore.ext = [
|
||||
{ user = "clightning"; group = "clightning"; mode = "0710"; path = "/var/lib/clightning"; }
|
||||
];
|
||||
|
||||
# `lightning-cli` finds its RPC file via `~/.lightning/bitcoin/lightning-rpc`, to message the daemon
|
||||
sane.user.fs.".lightning".symlink.target = "/var/lib/clightning";
|
||||
|
||||
# see bitcoin.nix for how to generate this
|
||||
services.bitcoind.mainnet.rpc.users.clightning.passwordHMAC =
|
||||
"befcb82d9821049164db5217beb85439$2c31ac7db3124612e43893ae13b9527dbe464ab2d992e814602e7cb07dc28985";
|
||||
|
||||
sane.services.clightning.enable = true;
|
||||
sane.services.clightning.proxy = "127.0.0.1:9050"; # proxy outgoing traffic through tor
|
||||
# sane.services.clightning.publicAddress = "statictor:127.0.0.1:9051";
|
||||
sane.services.clightning.getPublicAddressCmd = "cat /var/lib/tor/onion/clightning/hostname";
|
||||
|
||||
services.tor.relay.onionServices.clightning = {
|
||||
version = 3;
|
||||
map = [{
|
||||
# by default tor will route public tor port P to 127.0.0.1:P.
|
||||
# so if this port is the same as clightning would natively use, then no further config is needed here.
|
||||
# see: <https://2019.www.torproject.org/docs/tor-manual.html.en#HiddenServicePort>
|
||||
port = 9735;
|
||||
# target.port; target.addr; #< set if tor port != clightning port
|
||||
}];
|
||||
# allow "tor" group (i.e. clightning) to read /var/lib/tor/onion/clightning/hostname
|
||||
settings.HiddenServiceDirGroupReadable = true;
|
||||
};
|
||||
|
||||
# must be in "tor" group to read /var/lib/tor/onion/*/hostname
|
||||
users.users.clightning.extraGroups = [ "tor" ];
|
||||
|
||||
systemd.services.clightning.after = [ "tor.service" ];
|
||||
|
||||
# lightning-config contains fields from here:
|
||||
# - <https://docs.corelightning.org/docs/configuration>
|
||||
# secret config includes:
|
||||
# - bitcoin-rpcpassword
|
||||
# - alias=nodename
|
||||
# - rgb=rrggbb
|
||||
# - fee-base=<millisatoshi>
|
||||
# - fee-per-satoshi=<ppm>
|
||||
# - feature configs (i.e. experimental-xyz options)
|
||||
sane.services.clightning.extraConfig = ''
|
||||
log-level=debug:lightningd
|
||||
# peerswap:
|
||||
# - config example: <https://github.com/fort-nix/nix-bitcoin/pull/462/files#diff-b357d832705b8ce8df1f41934d613f79adb77c4cd5cd9e9eb12a163fca3e16c6>
|
||||
# XXX: peerswap crashes clightning on launch. stacktrace is useless.
|
||||
# plugin=${pkgs.peerswap}/bin/peerswap
|
||||
# peerswap-db-path=/var/lib/clightning/peerswap/swaps
|
||||
# peerswap-policy-path=...
|
||||
'';
|
||||
sane.services.clightning.extraConfigFiles = [ config.sops.secrets."lightning-config".path ];
|
||||
sops.secrets."lightning-config" = {
|
||||
mode = "0640";
|
||||
owner = "clightning";
|
||||
group = "clightning";
|
||||
};
|
||||
|
||||
sane.programs.clightning.enableFor.user.colin = true; # for debugging/admin: `lightning-cli`
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
./bitcoin.nix
|
||||
./clightning.nix
|
||||
./i2p.nix
|
||||
./monero.nix
|
||||
./tor.nix
|
||||
];
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
services.i2p.enable = true;
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
# tor settings: <https://2019.www.torproject.org/docs/tor-manual.html.en>
|
||||
{ lib, ... }:
|
||||
{
|
||||
# tor hidden service hostnames aren't deterministic, so persist.
|
||||
# might be able to get away with just persisting /var/lib/tor/onion, not sure.
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "tor"; group = "tor"; mode = "0710"; path = "/var/lib/tor"; }
|
||||
];
|
||||
|
||||
# tor: `tor.enable` doesn't start a relay, exit node, proxy, etc. it's minimal.
|
||||
# tor.client.enable configures a torsocks proxy, accessible *only* to localhost.
|
||||
# at 127.0.0.1:9050
|
||||
services.tor.enable = true;
|
||||
services.tor.client.enable = true;
|
||||
|
||||
# in order for services to read /var/lib/tor/onion/*/hostname, they must be able to traverse /var/lib/tor,
|
||||
# and /var/lib/tor must have g+x.
|
||||
# DataDirectoryGroupReadable causes tor to use g+rx, technically more than we need, but all the files are 600 so it's fine.
|
||||
services.tor.settings.DataDirectoryGroupReadable = true;
|
||||
# StateDirectoryMode defaults to 0700, and thereby prevents the onion hostnames from being group readable
|
||||
systemd.services.tor.serviceConfig.StateDirectoryMode = lib.mkForce "0710";
|
||||
users.users.tor.homeMode = "0710"; # home mode defaults to 0700, causing readability problems, enforced by nixos "users" activation script
|
||||
|
||||
services.tor.settings.SafeLogging = false; # show actual .onion names in the syslog, else debugging is impossible
|
||||
}
|
27
hosts/by-name/servo/services/ddns-afraid.nix
Normal file
27
hosts/by-name/servo/services/ddns-afraid.nix
Normal file
@@ -0,0 +1,27 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# using manual ddns now
|
||||
lib.mkIf false
|
||||
{
|
||||
systemd.services.ddns-afraid = {
|
||||
description = "update dynamic DNS entries for freedns.afraid.org";
|
||||
serviceConfig = {
|
||||
EnvironmentFile = config.sops.secrets."ddns_afraid.env".path;
|
||||
# TODO: ProtectSystem = "strict";
|
||||
# TODO: ProtectHome = "full";
|
||||
# TODO: PrivateTmp = true;
|
||||
};
|
||||
script = let
|
||||
curl = "${pkgs.curl}/bin/curl -4";
|
||||
in ''
|
||||
${curl} "https://freedns.afraid.org/dynamic/update.php?$AFRAID_KEY"
|
||||
'';
|
||||
};
|
||||
systemd.timers.ddns-afraid = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
timerConfig = {
|
||||
OnStartupSec = "2min";
|
||||
OnUnitActiveSec = "10min";
|
||||
};
|
||||
};
|
||||
}
|
30
hosts/by-name/servo/services/ddns-he.nix
Normal file
30
hosts/by-name/servo/services/ddns-he.nix
Normal file
@@ -0,0 +1,30 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# we use manual DDNS now
|
||||
lib.mkIf false
|
||||
{
|
||||
systemd.services.ddns-he = {
|
||||
description = "update dynamic DNS entries for HurricaneElectric";
|
||||
serviceConfig = {
|
||||
EnvironmentFile = config.sops.secrets."ddns_he.env".path;
|
||||
# TODO: ProtectSystem = "strict";
|
||||
# TODO: ProtectHome = "full";
|
||||
# TODO: PrivateTmp = true;
|
||||
};
|
||||
# HE DDNS API is documented: https://dns.he.net/docs.html
|
||||
script = let
|
||||
crl = "${pkgs.curl}/bin/curl -4";
|
||||
in ''
|
||||
${crl} "https://he.uninsane.org:$HE_PASSPHRASE@dyn.dns.he.net/nic/update?hostname=he.uninsane.org"
|
||||
${crl} "https://native.uninsane.org:$HE_PASSPHRASE@dyn.dns.he.net/nic/update?hostname=native.uninsane.org"
|
||||
${crl} "https://uninsane.org:$HE_PASSPHRASE@dyn.dns.he.net/nic/update?hostname=uninsane.org"
|
||||
'';
|
||||
};
|
||||
systemd.timers.ddns-he = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
timerConfig = {
|
||||
OnStartupSec = "2min";
|
||||
OnUnitActiveSec = "10min";
|
||||
};
|
||||
};
|
||||
}
|
@@ -3,7 +3,8 @@
|
||||
imports = [
|
||||
./calibre.nix
|
||||
./coturn.nix
|
||||
./cryptocurrencies
|
||||
./ddns-afraid.nix
|
||||
./ddns-he.nix
|
||||
./email
|
||||
./ejabberd.nix
|
||||
./freshrss.nix
|
||||
@@ -17,9 +18,9 @@
|
||||
./komga.nix
|
||||
./lemmy.nix
|
||||
./matrix
|
||||
./monero.nix
|
||||
./navidrome.nix
|
||||
./nginx.nix
|
||||
./nixos-prebuild.nix
|
||||
./nixserve.nix
|
||||
./ntfy
|
||||
./pict-rs.nix
|
||||
|
@@ -172,15 +172,13 @@ in
|
||||
|
||||
users.users.sftpgo.extraGroups = [ "export" ];
|
||||
|
||||
systemd.services.sftpgo = {
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
systemd.services.sftpgo.serviceConfig = {
|
||||
ReadOnlyPaths = [ "/var/export" ];
|
||||
ReadWritePaths = [ "/var/export/playground" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,19 +1,9 @@
|
||||
# how to update wikipedia snapshot:
|
||||
# - browse for later snapshots:
|
||||
# - <https://mirror.accum.se/mirror/wikimedia.org/other/kiwix/zim/wikipedia>
|
||||
# - DL directly, or via rsync (resumable):
|
||||
# - `rsync --progress --append-verify rsync://mirror.accum.se/mirror/wikimedia.org/other/kiwix/zim/wikipedia/wikipedia_en_all_maxi_2022-05.zim .`
|
||||
|
||||
{ ... }:
|
||||
{
|
||||
sane.persist.sys.byStore.ext = [
|
||||
{ user = "colin"; group = "users"; path = "/var/lib/kiwix"; }
|
||||
];
|
||||
|
||||
sane.services.kiwix-serve = {
|
||||
enable = true;
|
||||
port = 8013;
|
||||
zimPaths = [ "/var/lib/kiwix/wikipedia_en_all_maxi_2023-11.zim" ];
|
||||
zimPaths = [ "/var/lib/uninsane/www-archive/wikipedia_en_all_maxi_2022-05.zim" ];
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."w.uninsane.org" = {
|
||||
|
@@ -1,8 +1,6 @@
|
||||
# config options:
|
||||
# - <https://github.com/mautrix/signal/blob/master/mautrix_signal/example-config.yaml>
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
lib.mkIf false # disabled 2024/01/11: i don't use it, and pkgs.mautrix-signal had some API changes
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "mautrix-signal"; group = "mautrix-signal"; path = "/var/lib/mautrix-signal"; }
|
||||
|
@@ -20,6 +20,12 @@
|
||||
tx-proxy=tor,127.0.0.1:9050
|
||||
'';
|
||||
|
||||
services.i2p.enable = true;
|
||||
# tor: `tor.enable` doesn't start a relay, exit node, proxy, etc. it's minimal.
|
||||
# tor.client.enable configures a torsocks proxy, accessible *only* to localhost.
|
||||
services.tor.enable = true;
|
||||
services.tor.client.enable = true;
|
||||
|
||||
# monero ports: <https://monero.stackexchange.com/questions/604/what-ports-does-monero-use-rpc-p2p-etc>
|
||||
# - 18080 = "P2P" monero node <-> monero node connections
|
||||
# - 18081 = "RPC" monero client -> monero node connections
|
@@ -54,10 +54,8 @@ in
|
||||
services.nginx.recommendedOptimisation = true;
|
||||
|
||||
# web blog/personal site
|
||||
# alternative way to link stuff into the share:
|
||||
# sane.fs."/var/lib/uninsane/share/Ubunchu".mount.bind = "/var/lib/uninsane/media/Books/Visual/HiroshiSeo/Ubunchu";
|
||||
# sane.fs."/var/lib/uninsane/media/Books/Visual/HiroshiSeo/Ubunchu".dir = {};
|
||||
services.nginx.virtualHosts."uninsane.org" = publog {
|
||||
root = "${pkgs.uninsane-dot-org}/share/uninsane-dot-org";
|
||||
# a lot of places hardcode https://uninsane.org,
|
||||
# and then when we mix http + non-https, we get CORS violations
|
||||
# and things don't look right. so force SSL.
|
||||
@@ -67,28 +65,9 @@ in
|
||||
# for OCSP stapling
|
||||
sslTrustedCertificate = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||
|
||||
locations."/" = {
|
||||
root = "${pkgs.uninsane-dot-org}/share/uninsane-dot-org";
|
||||
tryFiles = "$uri $uri/ @fallback";
|
||||
};
|
||||
|
||||
# unversioned files
|
||||
locations."@fallback" = {
|
||||
root = "/var/www/sites/uninsane.org";
|
||||
};
|
||||
|
||||
# uninsane.org/share/foo => /var/www/sites/uninsane.org/share/foo.
|
||||
# special-cased to enable directory listings
|
||||
locations."/share" = {
|
||||
root = "/var/www/sites/uninsane.org";
|
||||
extraConfig = ''
|
||||
# autoindex => render directory listings
|
||||
autoindex on;
|
||||
# don't follow any symlinks when serving files
|
||||
# otherwise it allows a directory escape
|
||||
disable_symlinks on;
|
||||
'';
|
||||
};
|
||||
# uninsane.org/share/foo => /var/lib/uninsane/root/share/foo.
|
||||
# yes, nginx does not strip the prefix when evaluating against the root.
|
||||
locations."/share".root = "/var/lib/uninsane/root";
|
||||
|
||||
# allow matrix users to discover that @user:uninsane.org is reachable via matrix.uninsane.org
|
||||
locations."= /.well-known/matrix/server".extraConfig =
|
||||
@@ -129,19 +108,6 @@ in
|
||||
# proxyPass = "http://127.0.0.1:4000";
|
||||
# extraConfig = pleromaExtraConfig;
|
||||
# };
|
||||
|
||||
# redirect common feed URIs to the canonical feed
|
||||
locations."= /atom".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /feed".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /feed.xml".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /rss".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /rss.xml".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/atom".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/atom.xml".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/feed".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/feed.xml".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/rss".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/rss.xml".extraConfig = "return 301 /atom.xml;";
|
||||
};
|
||||
|
||||
|
||||
@@ -169,6 +135,7 @@ in
|
||||
security.acme.defaults.email = "admin.acme@uninsane.org";
|
||||
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: mode?
|
||||
{ user = "acme"; group = "acme"; path = "/var/lib/acme"; }
|
||||
{ user = "colin"; group = "users"; path = "/var/www/sites"; }
|
||||
];
|
||||
|
@@ -1,26 +0,0 @@
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
lib.optionalAttrs false # disabled until i can be sure it's not gonna OOM my server in the middle of the night
|
||||
{
|
||||
systemd.services.nixos-prebuild = {
|
||||
description = "build a nixos image with all updated deps";
|
||||
path = with pkgs; [ coreutils git nix ];
|
||||
script = ''
|
||||
working=$(mktemp -d /tmp/nixos-prebuild.XXXXXX)
|
||||
pushd "$working"
|
||||
git clone https://git.uninsane.org/colin/nix-files.git \
|
||||
&& cd nix-files \
|
||||
&& nix flake update \
|
||||
|| true
|
||||
RC=$(nix run "$working/nix-files#check" -- -j1 --cores 5 --builders "")
|
||||
popd
|
||||
rm -rf "$working"
|
||||
exit "$RC"
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers.nixos-prebuild = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
timerConfig.OnCalendar = "11,23:00:00";
|
||||
};
|
||||
}
|
@@ -64,7 +64,7 @@ in
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-notification-waiter-${builtins.toString (port - portLow + 1)}-of-${builtins.toString numPorts}";
|
||||
description = "colin-notification-waiter-${builtins.toString (port+1)}-of-${builtins.toString numPorts}";
|
||||
};
|
||||
}));
|
||||
systemd.services = lib.mkMerge (builtins.map mkService portRange);
|
||||
|
@@ -57,7 +57,7 @@
|
||||
# what unit is this? kbps??
|
||||
global.upload.speed_limit = 32000;
|
||||
web.logging = true;
|
||||
# debug = true;
|
||||
debug = true;
|
||||
flags.no_logo = true; # don't show logo at start
|
||||
# flags.volatile = true; # store searches and active transfers in RAM (completed transfers still go to disk). rec for btrfs/zfs
|
||||
};
|
||||
@@ -66,8 +66,8 @@
|
||||
serviceConfig = {
|
||||
# run this behind the OVPN static VPN
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
|
||||
RestartSec = "60s";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "30s";
|
||||
Group = "media";
|
||||
};
|
||||
};
|
||||
|
@@ -16,7 +16,7 @@
|
||||
# transmission will by default not allow the world to read its files.
|
||||
services.transmission.downloadDirPermissions = "775";
|
||||
services.transmission.extraFlags = [
|
||||
# "--log-level=debug"
|
||||
"--log-level=debug"
|
||||
];
|
||||
|
||||
services.transmission.settings = {
|
||||
@@ -39,9 +39,9 @@
|
||||
encryption = 2;
|
||||
|
||||
# units in kBps
|
||||
speed-limit-down = 12000;
|
||||
speed-limit-down = 3000;
|
||||
speed-limit-down-enabled = true;
|
||||
speed-limit-up = 800;
|
||||
speed-limit-up = 600;
|
||||
speed-limit-up-enabled = true;
|
||||
|
||||
# see: https://git.zknt.org/mirror/transmission/commit/cfce6e2e3a9b9d31a9dafedd0bdc8bf2cdb6e876?lang=bg-BG
|
||||
|
@@ -5,16 +5,18 @@
|
||||
./fs.nix
|
||||
./hardware
|
||||
./home
|
||||
./hostnames.nix
|
||||
./hosts.nix
|
||||
./ids.nix
|
||||
./machine-id.nix
|
||||
./net
|
||||
./net.nix
|
||||
./nix-path
|
||||
./persist.nix
|
||||
./programs
|
||||
./secrets.nix
|
||||
./ssh.nix
|
||||
./users
|
||||
./vpn.nix
|
||||
];
|
||||
|
||||
sane.nixcache.enable-trusted-keys = true;
|
||||
@@ -92,21 +94,7 @@
|
||||
text = ''
|
||||
# show which packages changed versions or are new/removed in this upgrade
|
||||
# source: <https://github.com/luishfonseca/dotfiles/blob/32c10e775d9ec7cc55e44592a060c1c9aadf113e/modules/upgrade-diff.nix>
|
||||
# modified to not error on boot (when /run/current-system doesn't exist)
|
||||
if [ -d /run/current-system ]; then
|
||||
${pkgs.nvd}/bin/nvd --nix-bin-dir=${pkgs.nix}/bin diff /run/current-system "$systemConfig"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
system.activationScripts.notifyActive = {
|
||||
text = ''
|
||||
# send a notification to any sway users logged in, that the system has been activated/upgraded.
|
||||
# this probably doesn't work if more than one sway session exists on the system.
|
||||
_notifyActiveSwaySock="$(echo /run/user/*/sway-ipc.*.sock)"
|
||||
if [ -e "$_notifyActiveSwaySock" ]; then
|
||||
SWAYSOCK="$_notifyActiveSwaySock" ${pkgs.sway}/bin/swaymsg -- exec \
|
||||
"${pkgs.libnotify}/bin/notify-send 'nixos activated' 'version: $(cat $systemConfig/nixos-version)'"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
|
@@ -50,8 +50,6 @@ let
|
||||
else
|
||||
"infrequent"
|
||||
));
|
||||
} // lib.optionalAttrs (lib.hasPrefix "https://www.youtube.com/" raw.url) {
|
||||
format = "video";
|
||||
} // lib.optionalAttrs (raw.is_podcast or false) {
|
||||
format = "podcast";
|
||||
} // lib.optionalAttrs (raw.title or "" != "") {
|
||||
@@ -62,7 +60,6 @@ let
|
||||
(fromDb "acquiredlpbonussecretsecret.libsyn.com" // tech) # ACQ2 - more "Acquired" episodes
|
||||
(fromDb "allinchamathjason.libsyn.com" // pol)
|
||||
(fromDb "anchor.fm/s/34c7232c/podcast/rss" // tech) # Civboot -- https://anchor.fm/civboot
|
||||
(fromDb "anchor.fm/s/2da69154/podcast/rss" // tech) # POD OF JAKE -- https://podofjake.com/
|
||||
(fromDb "cast.postmarketos.org" // tech)
|
||||
(fromDb "congressionaldish.libsyn.com" // pol) # Jennifer Briney
|
||||
(fromDb "craphound.com" // pol) # Cory Doctorow -- both podcast & text entries
|
||||
@@ -74,22 +71,20 @@ let
|
||||
(fromDb "feeds.feedburner.com/radiolab" // pol) # Radiolab -- also available here, but ONLY OVER HTTP: <http://feeds.wnyc.org/radiolab>
|
||||
(fromDb "feeds.libsyn.com/421877" // rat) # Less Wrong Curated
|
||||
(fromDb "feeds.megaphone.fm/behindthebastards" // pol) # also Maggie Killjoy
|
||||
# (fromDb "feeds.megaphone.fm/hubermanlab" // uncat) # Daniel Huberman on sleep
|
||||
(fromDb "feeds.megaphone.fm/hubermanlab" // uncat) # Daniel Huberman on sleep
|
||||
(fromDb "feeds.megaphone.fm/recodedecode" // tech) # The Verge - Decoder
|
||||
(fromDb "feeds.simplecast.com/54nAGcIl" // pol) # The Daily
|
||||
(fromDb "feeds.simplecast.com/82FI35Px" // pol) # Ezra Klein Show
|
||||
(fromDb "feeds.simplecast.com/wgl4xEgL" // rat) # Econ Talk
|
||||
(fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura
|
||||
# (fromDb "feeds.simplecast.com/l2i9YnTd" // tech // pol) # Hard Fork (NYtimes tech)
|
||||
(fromDb "feeds.simplecast.com/l2i9YnTd" // tech // pol) # Hard Fork (NYtimes tech)
|
||||
(fromDb "feeds.transistor.fm/acquired" // tech)
|
||||
(fromDb "lexfridman.com/podcast" // rat)
|
||||
(fromDb "mapspodcast.libsyn.com" // uncat) # Multidisciplinary Association for Psychedelic Studies
|
||||
(fromDb "omegataupodcast.net" // tech) # 3/4 German; 1/4 eps are English
|
||||
(fromDb "omny.fm/shows/cool-people-who-did-cool-stuff" // pol) # Maggie Killjoy -- referenced by Cory Doctorow
|
||||
(fromDb "omny.fm/shows/the-dollop-with-dave-anthony-and-gareth-reynolds") # The Dollop history/comedy
|
||||
(fromDb "originstories.libsyn.com" // uncat)
|
||||
(fromDb "podcast.posttv.com/itunes/post-reports.xml" // pol)
|
||||
# (fromDb "podcast.thelinuxexp.com" // tech) # low-brow linux/foss PR announcements
|
||||
(fromDb "podcast.thelinuxexp.com" // tech)
|
||||
(fromDb "politicalorphanage.libsyn.com" // pol)
|
||||
(fromDb "reverseengineering.libsyn.com/rss" // tech) # UnNamed Reverse Engineering Podcast
|
||||
(fromDb "rss.acast.com/deconstructed") # The Intercept - Deconstructed
|
||||
@@ -98,7 +93,6 @@ let
|
||||
(fromDb "rss.art19.com/60-minutes" // pol)
|
||||
(fromDb "rss.art19.com/the-portal" // rat) # Eric Weinstein
|
||||
(fromDb "seattlenice.buzzsprout.com" // pol)
|
||||
(fromDb "srslywrong.com" // pol)
|
||||
(fromDb "sharkbytes.transistor.fm" // tech) # Wireshark Podcast o_0
|
||||
(fromDb "sscpodcast.libsyn.com" // rat) # Astral Codex Ten
|
||||
(fromDb "talesfromthebridge.buzzsprout.com" // tech) # Sci-Fi? has Peter Watts; author of No Moods, Ads or Cutesy Fucking Icons (rifters.com)
|
||||
@@ -117,118 +111,137 @@ let
|
||||
];
|
||||
|
||||
texts = [
|
||||
(fromDb "amosbbatto.wordpress.com" // tech)
|
||||
(fromDb "applieddivinitystudies.com" // rat)
|
||||
(fromDb "artemis.sh" // tech)
|
||||
(fromDb "ascii.textfiles.com" // tech) # Jason Scott
|
||||
(fromDb "austinvernon.site" // tech)
|
||||
(fromDb "balajis.com" // pol) # Balaji
|
||||
(fromDb "ben-evans.com/benedictevans" // pol)
|
||||
(fromDb "bitbashing.io" // tech)
|
||||
(fromDb "bitsaboutmoney.com" // uncat)
|
||||
(fromDb "blog.danieljanus.pl" // tech)
|
||||
(fromDb "blog.dshr.org" // pol) # David Rosenthal
|
||||
(fromDb "blog.jmp.chat" // tech)
|
||||
(fromDb "blog.rust-lang.org" // tech)
|
||||
(fromDb "blog.thalheim.io" // tech) # Mic92
|
||||
(fromDb "bunniestudios.com" // tech) # Bunnie Juang
|
||||
(fromDb "capitolhillseattle.com" // pol)
|
||||
# (fromDb "drewdevault.com" // tech)
|
||||
# (fromDb "econlib.org" // pol)
|
||||
(fromDb "edwardsnowden.substack.com" // pol // text)
|
||||
(fromDb "fasterthanli.me" // tech)
|
||||
(fromDb "gwern.net" // rat)
|
||||
(fromDb "harihareswara.net" // tech // pol) # rec by Cory Doctorow
|
||||
(fromDb "ianthehenry.com" // tech)
|
||||
(fromDb "idiomdrottning.org" // uncat)
|
||||
(fromDb "interconnected.org/home/feed" // rat) # Matt Webb -- engineering-ish, but dreamy
|
||||
(fromDb "jeffgeerling.com" // tech)
|
||||
(fromDb "jefftk.com" // tech)
|
||||
(fromDb "kosmosghost.github.io/index.xml" // tech)
|
||||
# (fromDb "lesswrong.com" // rat)
|
||||
(fromDb "linmob.net" // tech)
|
||||
# AGGREGATORS (> 1 post/day)
|
||||
(fromDb "lwn.net" // tech)
|
||||
(fromDb "lynalden.com" // pol)
|
||||
(fromDb "mako.cc/copyrighteous" // tech // pol) # rec by Cory Doctorow
|
||||
(fromDb "mg.lol" // tech)
|
||||
(fromDb "mindingourway.com" // rat)
|
||||
(fromDb "morningbrew.com/feed" // pol)
|
||||
(fromDb "overcomingbias.com" // rat) # Robin Hanson
|
||||
# (fromDb "lesswrong.com" // rat)
|
||||
# (fromDb "econlib.org" // pol)
|
||||
|
||||
# AGGREGATORS (< 1 post/day)
|
||||
(fromDb "palladiummag.com" // uncat)
|
||||
(fromDb "philosopher.coach" // rat) # Peter Saint-Andre -- side project of stpeter.im
|
||||
(fromDb "pomeroyb.com" // tech)
|
||||
(fromDb "preposterousuniverse.com" // rat) # Sean Carroll
|
||||
(fromDb "profectusmag.com" // uncat)
|
||||
(fromDb "project-insanity.org" // tech) # shared blog by a few NixOS devs, notably onny
|
||||
(fromDb "putanumonit.com" // rat) # mostly dating topics. not advice, or humor, but looking through a social lens
|
||||
(fromDb "richardcarrier.info" // rat)
|
||||
(fromDb "rifters.com/crawl" // uncat) # No Moods, Ads or Cutesy Fucking Icons
|
||||
(fromDb "righto.com" // tech) # Ken Shirriff
|
||||
(fromDb "rootsofprogress.org" // rat) # Jason Crawford
|
||||
(fromDb "sagacioussuricata.com" // tech) # ian (Sanctuary)
|
||||
(fromDb "semiaccurate.com" // tech)
|
||||
(fromDb "sideways-view.com" // rat) # Paul Christiano
|
||||
(fromDb "slimemoldtimemold.com" // rat)
|
||||
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
||||
(fromDb "tuxphones.com" // tech)
|
||||
(fromDb "spectrum.ieee.org" // tech)
|
||||
(fromDb "stpeter.im/atom.xml" // pol)
|
||||
# (fromDb "theregister.com" // tech)
|
||||
(fromDb "thisweek.gnome.org" // tech)
|
||||
(fromDb "tuxphones.com" // tech)
|
||||
# more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
|
||||
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent)
|
||||
(mkText "https://nixos.org/blog/stories-rss.xml" // tech // weekly)
|
||||
## n.b.: quality RSS list here: <https://forum.merveilles.town/thread/57/share-your-rss-feeds%21-6/>
|
||||
(mkText "https://forum.merveilles.town/rss.xml" // pol // infrequent)
|
||||
|
||||
## No Moods, Ads or Cutesy Fucking Icons
|
||||
(fromDb "rifters.com/crawl" // uncat)
|
||||
|
||||
# DEVELOPERS
|
||||
(fromDb "blog.jmp.chat" // tech)
|
||||
(fromDb "uninsane.org" // tech)
|
||||
(fromDb "unintendedconsequenc.es" // rat)
|
||||
# (fromDb "vitalik.ca" // tech) # moved to vitalik.eth.limo
|
||||
(fromDb "vitalik.eth.limo" // tech) # Vitalik Buterin
|
||||
(fromDb "webcurious.co.uk" // uncat)
|
||||
(fromDb "blog.thalheim.io" // tech) # Mic92
|
||||
(fromDb "ascii.textfiles.com" // tech) # Jason Scott
|
||||
(fromDb "xn--gckvb8fzb.com" // tech)
|
||||
(mkSubstack "astralcodexten" // rat // daily) # Scott Alexander
|
||||
(fromDb "amosbbatto.wordpress.com" // tech)
|
||||
(fromDb "fasterthanli.me" // tech)
|
||||
(fromDb "jeffgeerling.com" // tech)
|
||||
(fromDb "mg.lol" // tech)
|
||||
# (fromDb "drewdevault.com" // tech)
|
||||
## Ken Shirriff
|
||||
(fromDb "righto.com" // tech)
|
||||
## shared blog by a few NixOS devs, notably onny
|
||||
(fromDb "project-insanity.org" // tech)
|
||||
## Vitalik Buterin
|
||||
(fromDb "vitalik.ca" // tech)
|
||||
## ian (Sanctuary)
|
||||
(fromDb "sagacioussuricata.com" // tech)
|
||||
(fromDb "artemis.sh" // tech)
|
||||
## Bunnie Juang
|
||||
(fromDb "bunniestudios.com" // tech)
|
||||
(fromDb "blog.danieljanus.pl" // tech)
|
||||
(fromDb "ianthehenry.com" // tech)
|
||||
(fromDb "bitbashing.io" // tech)
|
||||
(fromDb "idiomdrottning.org" // uncat)
|
||||
(mkText "http://boginjr.com/feed" // tech // infrequent)
|
||||
(mkText "https://anish.lakhwara.com/home.html" // tech // weekly)
|
||||
(fromDb "jefftk.com" // tech)
|
||||
(fromDb "pomeroyb.com" // tech)
|
||||
(fromDb "harihareswara.net" // tech // pol) # rec by Cory Doctorow
|
||||
(fromDb "mako.cc/copyrighteous" // tech // pol) # rec by Cory Doctorow
|
||||
# (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
|
||||
|
||||
# TECH PROJECTS
|
||||
(fromDb "blog.rust-lang.org" // tech)
|
||||
|
||||
# (TECH; POL) COMMENTATORS
|
||||
## Matt Webb -- engineering-ish, but dreamy
|
||||
(fromDb "interconnected.org/home/feed" // rat)
|
||||
(fromDb "edwardsnowden.substack.com" // pol // text)
|
||||
## Julia Evans
|
||||
(mkText "https://jvns.ca/atom.xml" // tech // weekly)
|
||||
(mkText "http://benjaminrosshoffman.com/feed" // pol // weekly)
|
||||
## Ben Thompson
|
||||
(mkText "https://www.stratechery.com/rss" // pol // weekly)
|
||||
## Balaji
|
||||
(fromDb "balajis.com" // pol)
|
||||
(fromDb "ben-evans.com/benedictevans" // pol)
|
||||
(fromDb "lynalden.com" // pol)
|
||||
(fromDb "austinvernon.site" // tech)
|
||||
(mkSubstack "oversharing" // pol // daily)
|
||||
(mkSubstack "byrnehobart" // pol // infrequent)
|
||||
# (mkSubstack "doomberg" // tech // weekly) # articles are all pay-walled
|
||||
(mkSubstack "eliqian" // rat // weekly)
|
||||
(mkSubstack "oversharing" // pol // daily)
|
||||
(mkSubstack "samkriss" // humor // infrequent)
|
||||
(mkText "http://benjaminrosshoffman.com/feed" // pol // weekly)
|
||||
(mkText "http://boginjr.com/feed" // tech // infrequent)
|
||||
(mkText "https://acoup.blog/feed" // rat // weekly)
|
||||
(mkText "https://anish.lakhwara.com/home.html" // tech // weekly)
|
||||
(mkText "https://forum.merveilles.town/rss.xml" // pol // infrequent) #quality RSS list here: <https://forum.merveilles.town/thread/57/share-your-rss-feeds%21-6/>
|
||||
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
|
||||
(mkText "https://jvns.ca/atom.xml" // tech // weekly) # Julia Evans
|
||||
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
||||
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent) # more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
|
||||
(mkText "https://nixos.org/blog/stories-rss.xml" // tech // weekly)
|
||||
# (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
|
||||
(mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly) # Matt Levine
|
||||
(mkText "https://www.stratechery.com/rss" // pol // weekly) # Ben Thompson
|
||||
];
|
||||
## David Rosenthal
|
||||
(fromDb "blog.dshr.org" // pol)
|
||||
## Matt Levine
|
||||
(mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly)
|
||||
(fromDb "stpeter.im/atom.xml" // pol)
|
||||
## Peter Saint-Andre -- side project of stpeter.im
|
||||
(fromDb "philosopher.coach" // rat)
|
||||
(fromDb "morningbrew.com/feed" // pol)
|
||||
|
||||
videos = [
|
||||
(fromDb "youtube.com/@Channel5YouTube" // pol)
|
||||
(fromDb "youtube.com/@ColdFusion")
|
||||
(fromDb "youtube.com/@ContraPoints" // pol)
|
||||
(fromDb "youtube.com/@Exurb1a")
|
||||
(fromDb "youtube.com/@hbomberguy")
|
||||
(fromDb "youtube.com/@JackStauber")
|
||||
(fromDb "youtube.com/@PolyMatter")
|
||||
# (fromDb "youtube.com/@rossmanngroup" // pol // tech) # Louis Rossmann
|
||||
(fromDb "youtube.com/@TechnologyConnections" // tech)
|
||||
(fromDb "youtube.com/@TheB1M")
|
||||
(fromDb "youtube.com/@TomScottGo")
|
||||
(fromDb "youtube.com/@Vihart")
|
||||
(fromDb "youtube.com/@Vox")
|
||||
(fromDb "youtube.com/@Vsauce")
|
||||
# RATIONALITY/PHILOSOPHY/ETC
|
||||
(mkSubstack "samkriss" // humor // infrequent)
|
||||
(fromDb "unintendedconsequenc.es" // rat)
|
||||
(fromDb "applieddivinitystudies.com" // rat)
|
||||
(fromDb "slimemoldtimemold.com" // rat)
|
||||
(fromDb "richardcarrier.info" // rat)
|
||||
(fromDb "gwern.net" // rat)
|
||||
## Jason Crawford
|
||||
(fromDb "rootsofprogress.org" // rat)
|
||||
## Robin Hanson
|
||||
(fromDb "overcomingbias.com" // rat)
|
||||
## Scott Alexander
|
||||
(mkSubstack "astralcodexten" // rat // daily)
|
||||
## Paul Christiano
|
||||
(fromDb "sideways-view.com" // rat)
|
||||
## Sean Carroll
|
||||
(fromDb "preposterousuniverse.com" // rat)
|
||||
(mkSubstack "eliqian" // rat // weekly)
|
||||
(mkText "https://acoup.blog/feed" // rat // weekly)
|
||||
(fromDb "mindingourway.com" // rat)
|
||||
|
||||
## mostly dating topics. not advice, or humor, but looking through a social lens
|
||||
(fromDb "putanumonit.com" // rat)
|
||||
|
||||
# LOCAL
|
||||
(fromDb "capitolhillseattle.com" // pol)
|
||||
|
||||
# CODE
|
||||
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
|
||||
];
|
||||
|
||||
images = [
|
||||
(fromDb "miniature-calendar.com" // img // art // daily)
|
||||
(fromDb "pbfcomics.com" // img // humor)
|
||||
(fromDb "poorlydrawnlines.com/feed" // img // humor)
|
||||
(fromDb "smbc-comics.com" // img // humor)
|
||||
(fromDb "turnoff.us" // img // humor)
|
||||
(fromDb "xkcd.com" // img // humor)
|
||||
(fromDb "turnoff.us" // img // humor)
|
||||
(fromDb "pbfcomics.com" // img // humor)
|
||||
# (mkImg "http://dilbert.com/feed" // humor // daily)
|
||||
(fromDb "poorlydrawnlines.com/feed" // img // humor)
|
||||
|
||||
# ART
|
||||
(fromDb "miniature-calendar.com" // img // art // daily)
|
||||
];
|
||||
in
|
||||
{
|
||||
sane.feeds = texts ++ images ++ podcasts ++ videos;
|
||||
sane.feeds = texts ++ images ++ podcasts;
|
||||
|
||||
assertions = builtins.map
|
||||
(p: {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
@@ -40,12 +40,6 @@
|
||||
# non-free firmware
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
# default is 252274, which is too low particularly for servo.
|
||||
# manifests as spurious "No space left on device" when trying to install watches,
|
||||
# e.g. in dyn-dns by `systemctl start dyn-dns-watcher.path`.
|
||||
# see: <https://askubuntu.com/questions/828779/failed-to-add-run-systemd-ask-password-to-directory-watch-no-space-left-on-dev>
|
||||
boot.kernel.sysctl."fs.inotify.max_user_watches" = 1048576;
|
||||
|
||||
# powertop will default to putting USB devices -- including HID -- to sleep after TWO SECONDS
|
||||
powerManagement.powertop.enable = false;
|
||||
# linux CPU governor: <https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt>
|
||||
@@ -64,19 +58,10 @@
|
||||
powerManagement.cpuFreqGovernor = "ondemand";
|
||||
|
||||
services.logind.extraConfig = ''
|
||||
# see: `man logind.conf`
|
||||
# don’t shutdown when power button is short-pressed (commonly done an accident, or by cats).
|
||||
# but do on long-press: useful to gracefully power-off server.
|
||||
HandlePowerKey=lock
|
||||
HandlePowerKeyLongPress=poweroff
|
||||
HandleLidSwitch=lock
|
||||
# don’t shutdown when power button is short-pressed
|
||||
HandlePowerKey=ignore
|
||||
'';
|
||||
|
||||
# some packages build only if binfmt *isn't* present
|
||||
nix.settings.system-features = lib.mkIf (config.boot.binfmt.emulatedSystems == []) [
|
||||
"no-binfmt"
|
||||
];
|
||||
|
||||
# services.snapper.configs = {
|
||||
# root = {
|
||||
# subvolume = "/";
|
||||
|
@@ -1,32 +1,19 @@
|
||||
{ config, lib, ...}:
|
||||
|
||||
let
|
||||
# [ ProgramConfig ]
|
||||
enabledPrograms = builtins.filter
|
||||
(p: p.enabled)
|
||||
(builtins.attrValues config.sane.programs);
|
||||
|
||||
# [ { "<mime-type>" = { prority, desktop } ]
|
||||
enabledWeightedMimes = builtins.map weightedMimes enabledPrograms;
|
||||
|
||||
# ProgramConfig -> { "<mime-type>" = { priority, desktop }; }
|
||||
weightedMimes = prog: builtins.mapAttrs
|
||||
(_key: desktop: {
|
||||
priority = prog.mime.priority; desktop = desktop;
|
||||
})
|
||||
prog.mime.associations;
|
||||
|
||||
weightedMimes = prog: builtins.mapAttrs (_key: desktop: { priority = prog.mime.priority; desktop = desktop; }) prog.mime.associations;
|
||||
# [ { "<mime-type>" = { priority, desktop } ]; } ] -> { "<mime-type>" = [ { priority, desktop } ... ]; }
|
||||
mergeMimes = mimes: lib.foldAttrs (item: acc: [item] ++ acc) [] mimes;
|
||||
|
||||
# [ { priority, desktop } ... ] -> Self
|
||||
sortOneMimeType = associations: builtins.sort
|
||||
(l: r: assert l.priority != r.priority; l.priority < r.priority)
|
||||
associations;
|
||||
sortOneMimeType = associations: builtins.sort (l: r: assert l.priority != r.priority; l.priority < r.priority) associations;
|
||||
sortMimes = mimes: builtins.mapAttrs (_k: sortOneMimeType) mimes;
|
||||
removePriorities = mimes: builtins.mapAttrs
|
||||
(_k: associations: builtins.map (a: a.desktop) associations)
|
||||
mimes;
|
||||
removePriorities = mimes: builtins.mapAttrs (_k: associations: builtins.map (a: a.desktop) associations) mimes;
|
||||
|
||||
# [ ProgramConfig ]
|
||||
enabledPrograms = builtins.filter (p: p.enabled) (builtins.attrValues config.sane.programs);
|
||||
# [ { "<mime-type>" = { prority, desktop } ]
|
||||
enabledWeightedMimes = builtins.map weightedMimes enabledPrograms;
|
||||
in
|
||||
{
|
||||
# the xdg mime type for a file can be found with:
|
||||
@@ -38,6 +25,4 @@ in
|
||||
# there's also options to *remove* [non-default] associations from specific apps
|
||||
xdg.mime.enable = true;
|
||||
xdg.mime.defaultApplications = removePriorities (sortMimes (mergeMimes enabledWeightedMimes));
|
||||
|
||||
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
# TODO: move to hosts/common/
|
||||
{ config, lib, ... }:
|
||||
|
||||
{
|
@@ -19,7 +19,7 @@
|
||||
};
|
||||
|
||||
sane.hosts.by-name."moby" = {
|
||||
# ssh.authorized = lib.mkDefault false; # moby's too easy to hijack: don't let it ssh places
|
||||
ssh.authorized = lib.mkDefault false; # moby's too easy to hijack: don't let it ssh places
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrR+gePnl0nV/vy7I5BzrGeyVL+9eOuXHU1yNE3uCwU";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1N/IT3nQYUD+dBlU1sTEEVMxfOyMkrrDeyHcYgnJvw";
|
||||
wg-home.pubkey = "I7XIR1hm8bIzAtcAvbhWOwIAabGkuEvbWH/3kyIB1yA=";
|
||||
|
@@ -53,10 +53,6 @@
|
||||
sane.ids.monero.gid = 2416;
|
||||
sane.ids.slskd.uid = 2417;
|
||||
sane.ids.slskd.gid = 2417;
|
||||
sane.ids.bitcoind-mainnet.uid = 2418;
|
||||
sane.ids.bitcoind-mainnet.gid = 2418;
|
||||
sane.ids.clightning.uid = 2419;
|
||||
sane.ids.clightning.gid = 2419;
|
||||
|
||||
sane.ids.colin.uid = 1000;
|
||||
sane.ids.guest.uid = 1100;
|
||||
|
@@ -1,12 +1,6 @@
|
||||
{ lib, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./dns.nix
|
||||
./hostnames.nix
|
||||
./upnp.nix
|
||||
./vpn.nix
|
||||
];
|
||||
# the default backend is "wpa_supplicant".
|
||||
# wpa_supplicant reliably picks weak APs to connect to.
|
||||
# see: <https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/474>
|
||||
@@ -41,6 +35,10 @@
|
||||
# e.g. openconnect drags in webkitgtk (for SSO)!
|
||||
networking.networkmanager.plugins = lib.mkForce [];
|
||||
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
1900 # to received UPnP advertisements. required by sane-ip-check-upnp
|
||||
];
|
||||
|
||||
# keyfile.path = where networkmanager should look for connection credentials
|
||||
networking.networkmanager.extraConfig = ''
|
||||
[keyfile]
|
@@ -1,37 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
# use systemd's stub resolver.
|
||||
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
|
||||
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets
|
||||
# in the ovnps namespace to use the provider's DNS resolvers.
|
||||
# a weakness is we can only query 1 NS at a time (unless we were to clone the packets?)
|
||||
# there also seems to be some cache somewhere that's shared between the two namespaces.
|
||||
# i think this is a libc thing. might need to leverage proper cgroups to _really_ kill it.
|
||||
# - getent ahostsv4 www.google.com
|
||||
# - try fix: <https://serverfault.com/questions/765989/connect-to-3rd-party-vpn-server-but-dont-use-it-as-the-default-route/766290#766290>
|
||||
services.resolved.enable = true;
|
||||
# without DNSSEC:
|
||||
# - dig matrix.org => works
|
||||
# - curl https://matrix.org => works
|
||||
# with default DNSSEC:
|
||||
# - dig matrix.org => works
|
||||
# - curl https://matrix.org => fails
|
||||
# i don't know why. this might somehow be interfering with the DNS run on this device (trust-dns)
|
||||
services.resolved.dnssec = "false";
|
||||
networking.nameservers = [
|
||||
# use systemd-resolved resolver
|
||||
# full resolver (which understands /etc/hosts) lives on 127.0.0.53
|
||||
# stub resolver (just forwards upstream) lives on 127.0.0.54
|
||||
"127.0.0.53"
|
||||
];
|
||||
|
||||
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
|
||||
# in a way that's unaware of my VPN routing, so routes are frequently poor against
|
||||
# services which advertise different IPs based on geolocation.
|
||||
# nscd claims to be usable without a cache, but in practice i can't get it to not cache!
|
||||
# nsncd is the Name Service NON-Caching Daemon. it's a drop-in that doesn't cache;
|
||||
# this is OK on the host -- because systemd-resolved caches. it's probably sub-optimal
|
||||
# in the netns and we query upstream DNS more often than needed. hm.
|
||||
# TODO: run a separate recursive resolver in each namespace.
|
||||
services.nscd.enableNsncd = true;
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
# to receive UPnP advertisements. required by sane-ip-check.
|
||||
# N.B. sane-ip-check isn't query/response based. it needs to receive on port 1900 -- not receive responses FROM port 1900.
|
||||
1900
|
||||
];
|
||||
|
||||
networking.firewall.extraCommands = with pkgs; ''
|
||||
# after an outgoing SSDP query to the multicast address, open FW for incoming responses.
|
||||
# necessary for anything DLNA, especially go2tv
|
||||
# source: <https://serverfault.com/a/911286>
|
||||
# context: <https://github.com/alexballas/go2tv/issues/72>
|
||||
|
||||
# ipset -! means "don't fail if set already exists"
|
||||
${ipset}/bin/ipset create -! upnp hash:ip,port timeout 10
|
||||
${iptables}/bin/iptables -A OUTPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j SET --add-set upnp src,src --exist
|
||||
${iptables}/bin/iptables -A INPUT -p udp -m set --match-set upnp dst,dst -j ACCEPT
|
||||
'';
|
||||
}
|
@@ -10,7 +10,7 @@ in
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -22,6 +22,14 @@ in
|
||||
name = ''"view members" default to false'';
|
||||
hash = "sha256-9BX8iO86CU1lNrKS1G2BjDR+3IlV9bmhRNTsLrxChwQ=";
|
||||
})
|
||||
(pkgs.fetchpatch {
|
||||
# this makes it so Abaddon reports its app_name in notifications.
|
||||
# not 100% necessary; just a nice-to-have. maybe don't rely on it until it's merged upstream.
|
||||
# upstream PR: <https://github.com/uowuo/abaddon/pull/247>
|
||||
url = "https://git.uninsane.org/colin/abaddon/commit/18cd863fdbb5e6b1e9aaf9394dbd673d51839f30.patch";
|
||||
name = "set glib application name";
|
||||
hash = "sha256-IFYxf1D8hIsxgZehGd6hL3zJiBkPZfWGm+Faaa5ZFl4=";
|
||||
})
|
||||
];
|
||||
});
|
||||
|
||||
|
@@ -7,34 +7,18 @@
|
||||
{
|
||||
sane.programs.alacritty = {
|
||||
env.TERMINAL = lib.mkDefault "alacritty";
|
||||
fs.".config/alacritty/alacritty.toml".symlink.text = ''
|
||||
[font]
|
||||
size = 14
|
||||
# note: alacritty will switch to .toml config in 13.0 release
|
||||
# - run `alacritty migrate` to convert the yaml to toml
|
||||
fs.".config/alacritty/alacritty.yml".symlink.text = ''
|
||||
font:
|
||||
size: 14
|
||||
|
||||
[[keyboard.bindings]]
|
||||
mods = "Control"
|
||||
key = "N"
|
||||
action = "CreateNewWindow"
|
||||
|
||||
[[keyboard.bindings]]
|
||||
mods = "Control"
|
||||
key = "PageUp"
|
||||
action = "ScrollPageUp"
|
||||
|
||||
[[keyboard.bindings]]
|
||||
mods = "Control"
|
||||
key = "PageDown"
|
||||
action = "ScrollPageDown"
|
||||
|
||||
[[keyboard.bindings]]
|
||||
mods = "Control|Shift"
|
||||
key = "PageUp"
|
||||
action = "ScrollPageUp"
|
||||
|
||||
[[keyboard.bindings]]
|
||||
mods = "Control|Shift"
|
||||
key = "PageDown"
|
||||
action = "ScrollPageDown"
|
||||
key_bindings:
|
||||
- { key: N, mods: Control, action: CreateNewWindow }
|
||||
- { key: PageUp, mods: Control, action: ScrollPageUp }
|
||||
- { key: PageDown, mods: Control, action: ScrollPageDown }
|
||||
- { key: PageUp, mods: Control|Shift, action: ScrollPageUp }
|
||||
- { key: PageDown, mods: Control|Shift, action: ScrollPageDown }
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
@@ -44,10 +44,11 @@ in
|
||||
"sane-scripts.shutdown"
|
||||
"sane-scripts.sudo-redirect"
|
||||
"sane-scripts.sync-from-servo"
|
||||
"sane-scripts.tag-music"
|
||||
"sane-scripts.vpn"
|
||||
"sane-scripts.which"
|
||||
"sane-scripts.wipe"
|
||||
"sane-scripts.wipe-browser"
|
||||
"sane-scripts.wipe-flare"
|
||||
"sane-scripts.wipe-fractal"
|
||||
];
|
||||
"sane-scripts.sys-utils" = declPackageSet [
|
||||
"sane-scripts.ip-port-forward"
|
||||
@@ -109,7 +110,6 @@ in
|
||||
"wget"
|
||||
"wirelesstools" # iwlist
|
||||
"xq" # jq for XML
|
||||
# "zfs" # doesn't cross-compile (requires samba)
|
||||
];
|
||||
sysadminExtraUtils = declPackageSet [
|
||||
"backblaze-b2"
|
||||
@@ -144,7 +144,6 @@ in
|
||||
"lshw"
|
||||
# "memtester"
|
||||
"mercurial" # hg
|
||||
"mimeo" # like xdg-open
|
||||
"neovim" # needed as a user package, for swap persistence
|
||||
# "nettools"
|
||||
# "networkmanager"
|
||||
@@ -183,9 +182,7 @@ in
|
||||
];
|
||||
|
||||
consoleMediaUtils = declPackageSet [
|
||||
"catt" # cast videos to chromecast
|
||||
"ffmpeg"
|
||||
"go2tv" # cast videos to UPNP/DLNA device (i.e. tv).
|
||||
"imagemagick"
|
||||
"sox"
|
||||
"yt-dlp"
|
||||
@@ -222,9 +219,6 @@ in
|
||||
|
||||
cargo.persist.byStore.plaintext = [ ".cargo" ];
|
||||
|
||||
# auth token, preferences
|
||||
delfin.persist.byStore.private = [ ".config/delfin" ];
|
||||
|
||||
# creds, but also 200 MB of node modules, etc
|
||||
discord.persist.byStore.private = [ ".config/discord" ];
|
||||
|
||||
@@ -282,6 +276,9 @@ in
|
||||
whalebird.persist.byStore.private = [ ".config/Whalebird" ];
|
||||
|
||||
yarn.persist.byStore.plaintext = [ ".cache/yarn" ];
|
||||
|
||||
# zcash coins. safe to delete, just slow to regenerate (10-60 minutes)
|
||||
zecwallet-lite.persist.byStore.private = [ ".zcash" ];
|
||||
};
|
||||
|
||||
programs.feedbackd = lib.mkIf config.sane.programs.feedbackd.enabled {
|
||||
|
@@ -1,22 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.audacity = {
|
||||
package = pkgs.audacity.override {
|
||||
# wxGTK32 uses webkitgtk-4.0.
|
||||
# audacity doesn't actually need webkit though, so diable to reduce closure
|
||||
wxGTK32 = pkgs.wxGTK32.override {
|
||||
withWebKit = false;
|
||||
};
|
||||
};
|
||||
|
||||
# disable first-run splash screen
|
||||
fs.".config/audacity/audacity.cfg".file.text = ''
|
||||
PrefsVersion=1.1.1r1
|
||||
[GUI]
|
||||
ShowSplashScreen=0
|
||||
[Version]
|
||||
Major=3
|
||||
Minor=4
|
||||
'';
|
||||
};
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
# use like:
|
||||
# - catt -d lgtv_chrome cast ./path/to.mp4
|
||||
#
|
||||
# support matrix:
|
||||
# - webm: audio only
|
||||
# - mp4: audio + video
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.catt;
|
||||
in
|
||||
{
|
||||
sane.programs.catt = {
|
||||
fs.".config/catt/catt.cfg".symlink.text = ''
|
||||
[options]
|
||||
device = lgtv_chrome
|
||||
|
||||
[aliases]
|
||||
lgtv_chrome = 10.78.79.106
|
||||
'';
|
||||
};
|
||||
|
||||
# necessary to cast local files
|
||||
networking.firewall.allowedTCPPortRanges = lib.mkIf cfg.enabled [
|
||||
{
|
||||
from = 45000;
|
||||
to = 47000;
|
||||
}
|
||||
];
|
||||
}
|
@@ -1,8 +1,40 @@
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
chattyNoOauth = pkgs.chatty.override {
|
||||
# the OAuth feature (presumably used for web-based logins) pulls a full webkitgtk.
|
||||
# especially when using the gtk3 version of evolution-data-server, it's an ancient webkitgtk_4_1.
|
||||
# disable OAuth for a faster build & smaller closure
|
||||
evolution-data-server = pkgs.evolution-data-server.override {
|
||||
enableOAuth2 = false;
|
||||
gnome-online-accounts = pkgs.gnome-online-accounts.override {
|
||||
# disables the upstream "goabackend" feature -- presumably "Gnome Online Accounts Backend"
|
||||
# frees us from webkit_4_1, in turn.
|
||||
enableBackend = false;
|
||||
gvfs = pkgs.gvfs.override {
|
||||
# saves 20 minutes of build time, for unused feature
|
||||
samba = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
chatty-latest = pkgs.chatty-latest.override {
|
||||
evolution-data-server-gtk4 = pkgs.evolution-data-server-gtk4.override {
|
||||
gnome-online-accounts = pkgs.gnome-online-accounts.override {
|
||||
# disables the upstream "goabackend" feature -- presumably "Gnome Online Accounts Backend"
|
||||
# frees us from webkit_4_1, in turn.
|
||||
enableBackend = false;
|
||||
gvfs = pkgs.gvfs.override {
|
||||
# saves 20 minutes of build time and cross issues, for unused feature
|
||||
samba = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.programs.chatty = {
|
||||
# package = chattyNoOauth;
|
||||
package = pkgs.chatty-latest;
|
||||
package = chatty-latest;
|
||||
suggestedPrograms = [ "gnome-keyring" ];
|
||||
persist.byStore.private = [
|
||||
".local/share/chatty" # matrix avatars and files
|
||||
|
@@ -1,184 +1,52 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash
|
||||
|
||||
usage() {
|
||||
echo "usage: battery_estimate [options...]"
|
||||
echo
|
||||
echo "pretty-prints a battery estimate (icon to indicate state, and a duration estimate)"
|
||||
echo
|
||||
echo "options:"
|
||||
echo " --debug: output additional information, to stderr"
|
||||
echo " --minute-suffix <string>: use the provided string as a minutes suffix"
|
||||
echo " --hour-suffix <string>: use the provided string as an hours suffix"
|
||||
echo " --icon-suffix <string>: use the provided string as an icon suffix"
|
||||
echo " --percent-suffix <string>: use the provided string when displaying percents"
|
||||
}
|
||||
|
||||
# these icons come from sxmo; they only render in nerdfonts
|
||||
icon_bat_chg=("" "" "" "")
|
||||
icon_bat_dis=("" "" "" "")
|
||||
suffix_icon=" " # thin space
|
||||
suffix_percent="%"
|
||||
# suffix_icon=" "
|
||||
|
||||
# render time like: 2ʰ08ᵐ
|
||||
# unicode sub/super-scripts: <https://en.wikipedia.org/wiki/Unicode_subscripts_and_superscripts>
|
||||
# symbol_hr="ʰ"
|
||||
# symbol_min="ᵐ"
|
||||
|
||||
# render time like: 2ₕ08ₘ
|
||||
# symbol_hr="ₕ"
|
||||
# symbol_min="ₘ"
|
||||
|
||||
# render time like: 2h08m
|
||||
# symbol_hr="h"
|
||||
# symbol_min="m"
|
||||
|
||||
# render time like: 2:08
|
||||
# symbol_hr=":"
|
||||
# symbol_min=
|
||||
|
||||
# render time like: 2꞉08⧗
|
||||
symbol_hr="꞉"
|
||||
symbol_min="⧗"
|
||||
# variants:
|
||||
# symbol_hr=":"
|
||||
# symbol_min="⧖"
|
||||
# symbol_min="⌛"
|
||||
|
||||
# render time like: 2'08"
|
||||
# symbol_hr="'"
|
||||
# symbol_min='"'
|
||||
|
||||
log() {
|
||||
if [ "$BATTERY_ESTIMATE_DEBUG" = "1" ]; then
|
||||
printf "$@" >&2
|
||||
echo >&2
|
||||
fi
|
||||
}
|
||||
|
||||
render_icon() {
|
||||
# args:
|
||||
# 1: "chg" or "dis"
|
||||
# 2: current battery percentage
|
||||
level=$(($2 / 25))
|
||||
level=$(($level > 3 ? 3 : $level))
|
||||
level=$(($level < 0 ? 0 : $level))
|
||||
log "icon: %s %d" "$1" "$level"
|
||||
if [ "$1" = "dis" ]; then
|
||||
printf "%s" "${icon_bat_dis[$level]}"
|
||||
elif [ "$1" = "chg" ]; then
|
||||
printf "%s" "${icon_bat_chg[$level]}"
|
||||
fi
|
||||
}
|
||||
bat_dis=""
|
||||
bat_chg=""
|
||||
|
||||
try_path() {
|
||||
# assigns output variables:
|
||||
# - perc, perc_from_full (0-100)
|
||||
# returns:
|
||||
# - perc, perc_left (0-100)
|
||||
# - full, rate (pos means charging)
|
||||
if [ -f "$1/capacity" ]; then
|
||||
log "perc, perc_from_full from %s" "$1/capacity"
|
||||
perc=$(cat "$1/capacity")
|
||||
perc_from_full=$((100 - $perc))
|
||||
perc_left=$((100 - $perc))
|
||||
fi
|
||||
|
||||
if [ -f "$1/charge_full_design" ] && [ -f "$1/current_now" ]; then
|
||||
log "full, rate from %s and %s" "$1/charge_full_design" "$1/current_now"
|
||||
# current is positive when charging
|
||||
full=$(cat "$1/charge_full_design")
|
||||
rate=$(cat "$1/current_now")
|
||||
elif [ -f "$1/energy_full" ] && [ -f "$1/power_now" ]; then
|
||||
log "full, rate from %s and %s" "$1/energy_full" "$1/power_now"
|
||||
# power_now is positive when discharging
|
||||
full=$(cat "$1/energy_full")
|
||||
rate=-$(cat "$1/power_now")
|
||||
elif [ -f "$1/energy_full" ] && [ -f "$1/energy_now" ]; then
|
||||
log "full, rate from %s and %s" "$1/energy_full" "$1/energy_now"
|
||||
log " this is a compatibility path for legacy Thinkpad batteries which do not populate the 'power_now' field, and incorrectly populate 'energy_now' with power info"
|
||||
# energy_now is positive when discharging
|
||||
fi
|
||||
if [ -f "$1/energy_full" ] && [ -f "$1/energy_now" ]; then
|
||||
# energy is positive when discharging
|
||||
full=$(cat "$1/energy_full")
|
||||
rate=-$(cat "$1/energy_now")
|
||||
fi
|
||||
}
|
||||
|
||||
try_all_paths() {
|
||||
try_path "/sys/class/power_supply/axp20x-battery" # Pinephone
|
||||
try_path "/sys/class/power_supply/BAT0" # Thinkpad
|
||||
log "perc: %d, perc_from_full: %d" "$perc" "$perc_from_full"
|
||||
log "full: %f, rate: %f" "$full" "$rate"
|
||||
log " rate > 0 means charging, else discharging"
|
||||
}
|
||||
try_path "/sys/class/power_supply/axp20x-battery" # Pinephone
|
||||
try_path "/sys/class/power_supply/BAT0" # Thinkpad
|
||||
|
||||
fmt_minutes() {
|
||||
# args:
|
||||
# 1: icon to render
|
||||
# 2: string to show if charge/discharge time is indefinite
|
||||
# 3: minutes to stable state (i.e. to full charge or full discharge)
|
||||
# - we work in minutes instead of hours for precision: bash math is integer-only
|
||||
log "charge/discharge time: %f min" "$3"
|
||||
# args: <battery symbol> <text if ludicrous estimate> <estimated minutes to full/empty>
|
||||
if [ -n "$3" ] && [ "$3" -lt 1440 ]; then
|
||||
if [[ $3 -gt 1440 ]]; then
|
||||
printf "%s %s" "$1" "$2" # more than 1d
|
||||
else
|
||||
hr=$(($3 / 60))
|
||||
hr_in_min=$(($hr * 60))
|
||||
min=$(($3 - $hr_in_min))
|
||||
printf "%s%s%d%s%02d%s" "$1" "$suffix_icon" "$hr" "$symbol_hr" "$min" "$symbol_min"
|
||||
else
|
||||
log "charge/discharge duration > 1d"
|
||||
printf "%s%s%s" "$1" "$suffix_icon" "$2" # more than 1d
|
||||
printf "%s %dh%02dm" "$1" "$hr" "$min"
|
||||
fi
|
||||
}
|
||||
|
||||
pretty_output() {
|
||||
if [ -n "$perc" ]; then
|
||||
duration=""
|
||||
if [ "$rate" -gt 0 ]; then
|
||||
log "charging"
|
||||
icon="$(render_icon chg $perc)"
|
||||
duration="$(($full * 60 * $perc_from_full / (100 * $rate)))"
|
||||
else
|
||||
log "discharging"
|
||||
icon="$(render_icon dis $perc)"
|
||||
if [ "$rate" -lt 0 ]; then
|
||||
duration="$(($full * 60 * $perc / (-100 * $rate)))"
|
||||
fi
|
||||
fi
|
||||
fmt_minutes "$icon" "$perc$suffix_percent" "$duration"
|
||||
fi
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
"--debug")
|
||||
shift
|
||||
BATTERY_ESTIMATE_DEBUG=1
|
||||
;;
|
||||
"--icon-suffix")
|
||||
shift
|
||||
suffix_icon="$1"
|
||||
shift
|
||||
;;
|
||||
"--hour-suffix")
|
||||
shift
|
||||
symbol_hr="$1"
|
||||
shift
|
||||
;;
|
||||
"--minute-suffix")
|
||||
shift
|
||||
symbol_min="$1"
|
||||
shift
|
||||
;;
|
||||
"--percent-suffix")
|
||||
shift
|
||||
suffix_percent="$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
try_all_paths
|
||||
pretty_output
|
||||
if [[ $rate -lt 0 ]]; then
|
||||
# discharging
|
||||
fmt_minutes "$bat_dis" '∞' "$(($full * 60 * $perc / (-100 * $rate)))"
|
||||
elif [[ $rate -gt 0 ]]; then
|
||||
# charging
|
||||
fmt_minutes "$bat_chg" '100%' "$(($full * 60 * $perc_left / (100 * $rate)))"
|
||||
elif [[ "$perc" != "" ]]; then
|
||||
echo "$bat_dis $perc%"
|
||||
fi
|
||||
|
@@ -3,11 +3,6 @@
|
||||
-- - can also use #rrggbb syntax
|
||||
-- example configs: <https://forum.manjaro.org/t/conky-showcase-2022/97123>
|
||||
-- example configs: <https://www.reddit.com/r/Conkyporn/>
|
||||
--
|
||||
-- exec options:
|
||||
-- `exec <cmd>` => executes the command, synchronously, renders its output as text
|
||||
-- `texeci <interval_sec> <cmd>` => executes the command periodically, async (to not block render), renders as text
|
||||
-- `pexec <cmd>` => executes the command, synchronously, parses its output
|
||||
|
||||
conky.config = {
|
||||
out_to_wayland = true,
|
||||
@@ -40,43 +35,16 @@ conky.config = {
|
||||
color2 = '404040',
|
||||
}
|
||||
|
||||
vars = {
|
||||
-- kBps = 'K/s',
|
||||
kBps = 'ᴷᐟˢ',
|
||||
-- percent = '%',
|
||||
-- percent = '﹪',
|
||||
percent = '٪',
|
||||
-- percent = '⁒',
|
||||
-- percent = '%',
|
||||
icon_suffix = nil,
|
||||
hour_suffix = nil,
|
||||
minute_suffix = '${font sans-serif:size=14}${color2}⧗',
|
||||
}
|
||||
|
||||
bat_args = ""
|
||||
if vars.icon_suffix ~= nil then
|
||||
bat_args = bat_args .. " --icon-suffix '" .. vars.icon_suffix .. "'"
|
||||
end
|
||||
if vars.hour_suffix ~= nil then
|
||||
bat_args = bat_args .. " --hour-suffix '" .. vars.hour_suffix .. "'"
|
||||
end
|
||||
if vars.minute_suffix ~= nil then
|
||||
bat_args = bat_args .. " --minute-suffix '" .. vars.minute_suffix .. "'"
|
||||
end
|
||||
if vars.percent ~= nil then
|
||||
bat_args = bat_args .. " --percent-suffix '" .. vars.percent .. "'"
|
||||
end
|
||||
|
||||
-- N.B.: `[[ <text> ]]` is Lua's multiline string literal
|
||||
-- texeci <interval_sec> <cmd>: run the command periodically, _in a separate thread_ so as not to block rendering
|
||||
conky.text = [[
|
||||
${color1}${shadecolor 707070}${font sans-serif:size=50:style=Bold}${alignc}${exec date +"%H:%M"}${font}
|
||||
${color2}${shadecolor a4d7d0}${font sans-serif:size=20}${alignc}${exec date +"%a %d %b"}${font}
|
||||
|
||||
|
||||
${color1}${shadecolor}${font sans-serif:size=22:style=Bold}${alignc}${execp @bat@ ]] .. bat_args .. [[ }${font}
|
||||
${color1}${shadecolor}${font sans-serif:size=22:style=Bold}${alignc}${exec @bat@ }${font}
|
||||
${color1}${shadecolor}${font sans-serif:size=20:style=Bold}${alignc}${texeci 600 @weather@ }${font}
|
||||
|
||||
|
||||
${color2}${shadecolor a4d7d0}${font sans-serif:size=16}${alignc}⇅ ${downspeedf wlan0}]] .. vars.kBps .. [[${font}
|
||||
${font sans-serif:size=16}${alignc}☵ $memperc]] .. vars.percent .. [[ $cpu]] .. vars.percent .. [[${font}
|
||||
${color2}${shadecolor a4d7d0}${font sans-serif:size=16}${alignc}⇅ ${downspeedf wlan0}K/s${font}
|
||||
${font sans-serif:size=16}${alignc}☵ $memperc% $cpu%${font}
|
||||
]]
|
||||
|
@@ -7,12 +7,10 @@
|
||||
./alacritty.nix
|
||||
./animatch.nix
|
||||
./assorted.nix
|
||||
./audacity.nix
|
||||
./bemenu.nix
|
||||
./brave.nix
|
||||
./calls.nix
|
||||
./cantata.nix
|
||||
./catt.nix
|
||||
./chatty.nix
|
||||
./conky
|
||||
./cozy.nix
|
||||
@@ -34,7 +32,6 @@
|
||||
./gnome-feeds.nix
|
||||
./gnome-keyring.nix
|
||||
./gnome-weather.nix
|
||||
./go2tv.nix
|
||||
./gpodder.nix
|
||||
./gthumb.nix
|
||||
./gtkcord4.nix
|
||||
@@ -45,24 +42,19 @@
|
||||
./koreader
|
||||
./libreoffice.nix
|
||||
./lemoa.nix
|
||||
./loupe.nix
|
||||
./mako.nix
|
||||
./megapixels.nix
|
||||
./mepo.nix
|
||||
./mimeo
|
||||
./mopidy.nix
|
||||
./mpv.nix
|
||||
./msmtp.nix
|
||||
./nautilus.nix
|
||||
./neovim.nix
|
||||
./newsflash.nix
|
||||
./nheko.nix
|
||||
./nix-index.nix
|
||||
./notejot.nix
|
||||
./ntfy-sh.nix
|
||||
./obsidian.nix
|
||||
./offlineimap.nix
|
||||
./open-in-mpv.nix
|
||||
./planify.nix
|
||||
./playerctl.nix
|
||||
./rhythmbox.nix
|
||||
./ripgrep.nix
|
||||
@@ -84,10 +76,8 @@
|
||||
./wike.nix
|
||||
./wine.nix
|
||||
./wireshark.nix
|
||||
./wob.nix
|
||||
./xarchiver.nix
|
||||
./zeal.nix
|
||||
./zecwallet-lite.nix
|
||||
./zsh
|
||||
];
|
||||
|
||||
|
@@ -40,7 +40,7 @@ in
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@@ -1,6 +1,3 @@
|
||||
# test with e.g.
|
||||
# - `fbcli --event proxied-message-new-instant`
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.feedbackd;
|
||||
|
@@ -38,16 +38,25 @@ let
|
||||
# defaultSettings = firefoxSettings;
|
||||
defaultSettings = librewolfSettings;
|
||||
|
||||
package = (pkgs.wrapFirefox cfg.browser.browser {
|
||||
addon = name: extid: hash: pkgs.fetchFirefoxAddon {
|
||||
inherit name hash;
|
||||
url = "https://addons.mozilla.org/firefox/downloads/latest/${name}/latest.xpi";
|
||||
# extid can be found by unar'ing the above xpi, and copying browser_specific_settings.gecko.id field
|
||||
fixedExtid = extid;
|
||||
};
|
||||
localAddon = pkg: pkgs.fetchFirefoxAddon {
|
||||
inherit (pkg) name;
|
||||
src = "${pkg}/share/mozilla/extensions/\\{ec8030f7-c20a-464f-9b0e-13a3a9e97384\\}/${pkg.extid}.xpi";
|
||||
fixedExtid = pkg.extid;
|
||||
};
|
||||
|
||||
package = pkgs.wrapFirefox cfg.browser.browser {
|
||||
# inherit the default librewolf.cfg
|
||||
# it can be further customized via ~/.librewolf/librewolf.overrides.cfg
|
||||
inherit (cfg.browser) extraPrefsFiles libName;
|
||||
|
||||
nativeMessagingHosts = lib.optionals cfg.addons.browserpass-extension.enable [
|
||||
pkgs.browserpass
|
||||
] ++ lib.optionals cfg.addons.fxCast.enable [
|
||||
pkgs.fx-cast-bridge
|
||||
];
|
||||
extraNativeMessagingHosts = optional cfg.addons.browserpass-extension.enable pkgs.browserpass;
|
||||
# extraNativeMessagingHosts = [ pkgs.gopass-native-messaging-host ];
|
||||
|
||||
nixExtensions = concatMap (ext: optional ext.enable ext.package) (attrValues cfg.addons);
|
||||
|
||||
@@ -104,17 +113,7 @@ let
|
||||
# NewTabPage = true;
|
||||
};
|
||||
# extraPrefs = ...
|
||||
}).overrideAttrs (base: {
|
||||
# de-associate `ctrl+shift+c` from activating the devtools.
|
||||
# based on <https://stackoverflow.com/a/54260938>
|
||||
buildCommand = (base.buildCommand or "") + ''
|
||||
mkdir omni
|
||||
${pkgs.buildPackages.unzip}/bin/unzip $out/lib/${cfg.browser.libName}/browser/omni.ja -d omni
|
||||
rm $out/lib/${cfg.browser.libName}/browser/omni.ja
|
||||
${pkgs.buildPackages.gnused}/bin/sed -i s'/devtools-commandkey-inspector = C/devtools-commandkey-inspector = VK_F12/' omni/localization/en-US/devtools/startup/key-shortcuts.ftl
|
||||
pushd omni; ${pkgs.buildPackages.zip}/bin/zip $out/lib/${cfg.browser.libName}/browser/omni.ja -r ./*; popd
|
||||
'';
|
||||
});
|
||||
};
|
||||
|
||||
addonOpts = types.submodule {
|
||||
options = {
|
||||
@@ -158,16 +157,6 @@ in
|
||||
default = {};
|
||||
};
|
||||
sane.programs.firefox.config.addons = {
|
||||
fxCast = {
|
||||
# add a menu to cast to chromecast devices, but it doesn't seem to work very well.
|
||||
# right click (or shift+rc) a video, then select "cast".
|
||||
# - asciinema.org: icon appears, but glitches when clicked.
|
||||
# - youtube.com: no icon appears, even when site is whitelisted.
|
||||
# future: maybe better to have browser open all videos in mpv, and then use mpv for casting.
|
||||
# see e.g. `ff2mpv`, `open-in-mpv` (both are packaged in nixpkgs)
|
||||
package = pkgs.firefox-extensions.fx_cast;
|
||||
enable = lib.mkDefault false;
|
||||
};
|
||||
browserpass-extension = {
|
||||
package = pkgs.firefox-extensions.browserpass-extension;
|
||||
enable = lib.mkDefault true;
|
||||
@@ -176,10 +165,6 @@ in
|
||||
package = pkgs.firefox-extensions.bypass-paywalls-clean;
|
||||
enable = lib.mkDefault true;
|
||||
};
|
||||
ctrl-shift-c-should-copy = {
|
||||
package = pkgs.firefox-extensions.ctrl-shift-c-should-copy;
|
||||
enable = lib.mkDefault false; # prefer patching firefox source code, so it works in more places
|
||||
};
|
||||
ether-metamask = {
|
||||
package = pkgs.firefox-extensions.ether-metamask;
|
||||
enable = lib.mkDefault false; # until i can disable the first-run notification
|
||||
@@ -188,10 +173,6 @@ in
|
||||
package = pkgs.firefox-extensions.i2p-in-private-browsing;
|
||||
enable = lib.mkDefault config.services.i2p.enable;
|
||||
};
|
||||
open-in-mpv = {
|
||||
package = pkgs.firefox-extensions.open-in-mpv;
|
||||
enable = lib.mkDefault config.sane.programs.open-in-mpv.enabled;
|
||||
};
|
||||
sidebery = {
|
||||
package = pkgs.firefox-extensions.sidebery;
|
||||
enable = lib.mkDefault true;
|
||||
@@ -214,10 +195,6 @@ in
|
||||
sane.programs.firefox = {
|
||||
inherit package;
|
||||
|
||||
suggestedPrograms = [
|
||||
"open-in-mpv"
|
||||
];
|
||||
|
||||
mime.associations = let
|
||||
inherit (cfg.browser) desktop;
|
||||
in {
|
||||
@@ -262,18 +239,6 @@ in
|
||||
// note that too-large scrollbars (like 50px wide) tend to obscure content (and make buttons unclickable)
|
||||
defaultPref("widget.non-native-theme.scrollbar.size.override", 20);
|
||||
defaultPref("widget.non-native-theme.scrollbar.style", 4);
|
||||
|
||||
// disable inertial/kinetic/momentum scrolling because it just gets in the way on touchpads
|
||||
// source: <https://kparal.wordpress.com/2019/10/31/disabling-kinetic-scrolling-in-firefox/>
|
||||
defaultPref("apz.gtk.kinetic_scroll.enabled", false);
|
||||
|
||||
// auto-dispatch mpv:// URIs to xdg-open without prompting.
|
||||
// can do this with other protocols too (e.g. matrix?). see about:config for common handlers.
|
||||
defaultPref("network.protocol-handler.external.mpv", true);
|
||||
// element:// for Element matrix client
|
||||
defaultPref("network.protocol-handler.external.element", true);
|
||||
// matrix: for Nheko matrix client
|
||||
defaultPref("network.protocol-handler.external.matrix", true);
|
||||
'';
|
||||
fs."${cfg.browser.dotDir}/default".dir = {};
|
||||
# instruct Firefox to put the profile in a predictable directory (so we can do things like persist just it).
|
||||
|
@@ -1,18 +1,15 @@
|
||||
# Flare is a 3rd-party GTK4 Signal app.
|
||||
# UI is effectively a clone of Fractal.
|
||||
#
|
||||
### compatibility:
|
||||
# compatibility:
|
||||
# - desko: works fine. pairs, and exchanges contact list (but not message history) with the paired device. exchanges future messages fine.
|
||||
# - moby (cross compiled flare-signal-nixified): nope. it pairs, but can only *receive* messages and never *send* them.
|
||||
# - even `rsync`ing the data and keyrings from desko -> moby, still fails in that same manner.
|
||||
# - console shows error messages. quite possibly an endianness mismatch somewhere
|
||||
# - moby (partially-emulated flare-signal): works! pairs and can send/receive messages, same as desko.
|
||||
#
|
||||
### debugging:
|
||||
# - `RUST_LOG=flare=trace flare`
|
||||
#
|
||||
### error signatures (to reset, run `sane-wipe flare`):
|
||||
#### upon sending a message, the other side receives it, but Signal desktop gets "A message from Colin could not be delivered" and the local CLI shows:
|
||||
# error signatures (to reset, run `sane-wipe-fractal`):
|
||||
# - upon sending a message, the other side receives it, but Signal desktop gets "A message from Colin could not be delivered" and the local CLI shows:
|
||||
# ```
|
||||
# ERROR libsignal_service::websocket] SignalWebSocket: Websocket error: SignalWebSocket: end of application request stream; socket closing
|
||||
# ERROR presage::manager] Error opening envelope: ProtobufDecodeError(DecodeError { description: "invalid tag value: 0", stack: [("Content", "data_message")] }), message will be skipped!
|
||||
@@ -21,8 +18,7 @@
|
||||
# - this occurs on moby, desko, `flare-signal` and `flare-signal-nixified`
|
||||
# - the Websocket error seems to be unrelated, occurs during normal/good operation
|
||||
# - related issues: <https://github.com/whisperfish/presage/issues/152>
|
||||
#
|
||||
#### error when sending from Flare to other Flare device:
|
||||
# error when sending from Flare to other Flare device:
|
||||
# - ```
|
||||
# ERROR libsignal_protocol::session_cipher] Message from <UUID>.3 failed to decrypt; sender ratchet public key <key> message counter 1
|
||||
# No current session
|
||||
@@ -30,32 +26,6 @@
|
||||
# ```
|
||||
# - but signal iOS will still read it.
|
||||
#
|
||||
#### HTTP 405 when linking flare to iOS signal:
|
||||
# [DEBUG libsignal_service_hyper::push_service] HTTP request PUT https://chat.signal.org/v1/devices/{uuid}.{timestamp?}:{b64-string}
|
||||
# [TRACE libsignal_service_hyper::push_service] Unhandled response 405 with body: {"code":405,"message":"HTTP 405 Method Not Allowed"}
|
||||
# [ERROR flare::gui::error_dialog] ErrorDialog displaying error: Something unexpected happened with the signal backend. Please retry later.
|
||||
# [TRACE flare::gui::error_dialog] ErrorDialog full error: Presage(
|
||||
# ProvisioningError(
|
||||
# ServiceError(
|
||||
# UnhandledResponseCode {
|
||||
# http_code: 405,
|
||||
# },
|
||||
# ),
|
||||
# ),
|
||||
# )
|
||||
# flare matrix suggests the signal endpoint has changed:
|
||||
# - "/v1/device/link instead of confirming via /v1/devices/{I'd}"
|
||||
# - this endpoint is declared in libsignal-service-rs (used both by flare and presage)
|
||||
# - libsignal-service/src/provisioning/manager.rs
|
||||
# - libsignal-service issues a put_json to that URL (i.e. HTTP PUT)
|
||||
# - libsignal-service is "based on" the official rust signal API <https://github.com/signalapp/libsignal>
|
||||
# - did these guys recently change it?
|
||||
# - no, but Signal-Desktop did. see ccb5eb0dd2 from 2023/08/29
|
||||
# - that's a fairly involved change.
|
||||
# - signalcli is reporting this same error: <https://github.com/AsamK/signal-cli/issues/1399>
|
||||
# - Mixin Messenger / libsignal_protocol_dart doesn't seem to be reporting any issue
|
||||
# - <https://github.com/MixinNetwork/flutter-app>
|
||||
#
|
||||
# well, seems to have unpredictable errors particularly when being used on multiple devices.
|
||||
# desktop _seems_ more reliable than on mobile, but not confident.
|
||||
|
||||
|
@@ -23,7 +23,7 @@ let
|
||||
in
|
||||
{
|
||||
sane.programs.fractal = {
|
||||
package = pkgs.fractal-nixified.optimized;
|
||||
package = pkgs.fractal-nixified;
|
||||
# package = pkgs.fractal-latest;
|
||||
# package = pkgs.fractal-next;
|
||||
|
||||
|
@@ -13,7 +13,6 @@ in
|
||||
user.name = "Colin";
|
||||
user.email = "colin@uninsane.org";
|
||||
|
||||
alias.amend = "commit --amend --no-edit";
|
||||
alias.br = "branch";
|
||||
alias.co = "checkout";
|
||||
alias.cp = "cherry-pick";
|
||||
@@ -24,7 +23,6 @@ in
|
||||
alias.st = "status";
|
||||
alias.stat = "status";
|
||||
|
||||
diff.noprefix = true; #< don't show a/ or b/ prefixes in diffs
|
||||
# difftastic docs:
|
||||
# - <https://difftastic.wilfred.me.uk/git.html>
|
||||
diff.tool = "difftastic";
|
||||
|
@@ -1,38 +0,0 @@
|
||||
# TROUBLESHOOTING:
|
||||
# - turn the tv off and on again (no, really...)
|
||||
#
|
||||
# SANITY CHECKS:
|
||||
# - `go2tv -u 'https://uninsane.org/share/AmenBreak.mp4'`
|
||||
# - LGTV: works, but not seekable
|
||||
# - `go2tv -u 'https://youtu.be/p3G5IXn0K7A'`
|
||||
# - LGTV: FAILS ("this file cannot be recognized")
|
||||
# - no fix via transcoding, altering the URI, etc.
|
||||
# - workable if you use an invidious frontend, but you lose seeking.
|
||||
# - e.g. `go2tv -u 'https://inv.us.projectsegfau.lt/latest_version?id=qBzjHU_zEwM&itag=18'`
|
||||
# - e.g. `go2tv -tc -u 'https://yt.artemislena.eu/latest_version?id=qBzjHU_zEwM&itag=22'`
|
||||
# - sometimes transcoding is needed, sometimes not...
|
||||
# - `go2tv -v /mnt/servo-media/Videos/Shows/bebop/session1.mkv`
|
||||
# - LGTV: works
|
||||
# - `go2tv -tc -v /mnt/servo-media/Videos/Shows/bebop/session1.mkv`
|
||||
# - LGTV: works
|
||||
#
|
||||
# WHEN TO TRANSCODE:
|
||||
# - mkv container + mpeg-2 video + AC-3/48k stereo audio:
|
||||
# - LGTV: no transcoding needed
|
||||
# - mkv container + H.264 video + AAC/48k 5.1 audio:
|
||||
# - LGTV: no transcoding needed
|
||||
# - mp4 container + H.264 video + MP3/48k stereo audio:
|
||||
# - LGTV: no transcoding needed
|
||||
# - mp4 container + H.264 video + AAC/44k1 stereo audio:
|
||||
# - LGTV: no transcoding needed
|
||||
# - mkv container + H.265 video + E-AC-3/48k stereo audio:
|
||||
# - LGTV: no transcoding needed
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.go2tv;
|
||||
in
|
||||
{
|
||||
# for serving local files
|
||||
# see: go2tv/soapcalls/utils/iptools.go
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf cfg.enabled [ 3500 ];
|
||||
}
|
@@ -5,7 +5,7 @@
|
||||
let
|
||||
feeds = sane-lib.feeds;
|
||||
all-feeds = config.sane.feeds;
|
||||
wanted-feeds = feeds.filterByFormat [ "podcast" "video" ] all-feeds;
|
||||
wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds;
|
||||
in {
|
||||
sane.programs.gpodder = {
|
||||
package = pkgs.gpodder-adaptive-configured.overrideAttrs (base: {
|
||||
|
@@ -3,7 +3,6 @@
|
||||
sane.programs.gthumb = {
|
||||
# compile without webservices to avoid the expensive webkitgtk dependency
|
||||
package = pkgs.gthumb.override { withWebservices = false; };
|
||||
mime.priority = 200; # gthumb is kinda bloated image/gallery viewer
|
||||
mime.associations = {
|
||||
"image/gif" = "org.gnome.gThumb.desktop";
|
||||
"image/heif" = "org.gnome.gThumb.desktop"; # apple codec
|
||||
|
@@ -1,6 +1,3 @@
|
||||
# FIRST-TIME SETUP:
|
||||
# - disable notification sounds: hamburger menu in bottom-left -> preferences
|
||||
# - notification sounds can be handled by swaync
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.gtkcord4;
|
||||
@@ -12,26 +9,11 @@ in
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
package = pkgs.gtkcord4.overrideAttrs (upstream: {
|
||||
postConfigure = (upstream.postConfigure or "") + ''
|
||||
# gtkcord4 uses go-keyring to interface with the org.freedesktop.secrets provider (i.e. gnome-keyring).
|
||||
# go-keyring hardcodes `login.keyring` as the keyring to store secrets in, instead of reading `~/.local/share/keyring/default`.
|
||||
# `login.keyring` seems to be a special keyring preconfigured (by gnome-keyring) to encrypt everything to the user's password.
|
||||
# that's redundant with my fs-level encryption and makes the keyring less inspectable,
|
||||
# so patch gtkcord4 to use Default_keyring instead.
|
||||
# see:
|
||||
# - <https://github.com/diamondburned/gtkcord4/issues/139>
|
||||
# - <https://github.com/zalando/go-keyring/issues/46>
|
||||
substituteInPlace vendor/github.com/zalando/go-keyring/secret_service/secret_service.go \
|
||||
--replace '"login"' '"Default_keyring"'
|
||||
'';
|
||||
});
|
||||
|
||||
persist.byStore.private = [
|
||||
".cache/gtkcord4"
|
||||
".config/gtkcord4" # empty?
|
||||
|
@@ -1,40 +0,0 @@
|
||||
local logger = require("logger")
|
||||
logger.info("applying colin patch colin-impl-clipboard")
|
||||
|
||||
-- source: <https://github.com/ncopa/lua-shell/blob/master/shell.lua>
|
||||
local function shescape(arg)
|
||||
if arg:match("[^A-Za-z0-9_/:=-]") then
|
||||
arg = "'"..arg:gsub("'", "'\\''").."'"
|
||||
end
|
||||
return arg
|
||||
end
|
||||
|
||||
-- 2023/12/15: the default setClipboardText doesn't do anything
|
||||
-- frontend/device/sdl/device.lua calls frontend/device/input.lua is noop
|
||||
local input = require("ffi/input")
|
||||
input.setClipboardText = function(text)
|
||||
logger.info("input.setClipboardText")
|
||||
local cmd = "wl-copy " .. shescape(text)
|
||||
logger.info("invoke: " .. cmd)
|
||||
os.execute(cmd)
|
||||
end
|
||||
|
||||
-- 2023/12/15: the default ReaderLink "Copy" option (when clicking a URL) doesn't do anything
|
||||
-- patch so it calls `setClipboardText`
|
||||
local ReaderLink = require("apps/reader/modules/readerlink")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local _ = require("gettext")
|
||||
local orig_ReaderLink_init = ReaderLink.init;
|
||||
ReaderLink.init = function(self)
|
||||
orig_ReaderLink_init(self)
|
||||
self._external_link_buttons["10_copy"] = function(this, link_url)
|
||||
return {
|
||||
text = _("Copy"),
|
||||
callback = function()
|
||||
UIManager:close(this.external_link_dialog)
|
||||
input.setClipboardText(link_url)
|
||||
end,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@@ -1,21 +1,9 @@
|
||||
# docs:
|
||||
# - <https://koreader.rocks/user_guide/>
|
||||
# - <https://github.com/koreader/koreader/wiki>
|
||||
#
|
||||
# post-installation setup:
|
||||
# - download dictionaries:
|
||||
# - search icon > settings > dictionary settings > download dictionaries
|
||||
# - these are stored in `~/.config/koreader/data/dict`
|
||||
# - configure defaults:
|
||||
# - edit keys in ~/.config/koreader/settings.reader.lua
|
||||
# - default font size: `["copt_font_size"] = 28,`
|
||||
# - home dir: `["home_dir"] = "/home/colin/Books",`
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
|
||||
let
|
||||
feeds = sane-lib.feeds;
|
||||
allFeeds = config.sane.feeds;
|
||||
wantedFeeds = feeds.filterByFormat [ "text" ] allFeeds;
|
||||
wantedFeeds = feeds.filterByFormat [ "image" "text" ] allFeeds;
|
||||
koreaderRssEntries = builtins.map (feed:
|
||||
# format:
|
||||
# { "<rss/atom url>", limit = <int>, download_full_article=<bool>, include_images=<bool>, enable_filter=<bool>, filter_element = "<css selector>"},
|
||||
@@ -26,7 +14,7 @@ let
|
||||
# enable_filter = true => only render content that matches the filter_element css selector.
|
||||
let fields = [
|
||||
(lib.escapeShellArg feed.url)
|
||||
"limit = 20"
|
||||
"limit = 5"
|
||||
"download_full_article = true"
|
||||
"include_images = true"
|
||||
"enable_filter = false"
|
||||
@@ -38,13 +26,9 @@ in {
|
||||
package = pkgs.koreader-from-src;
|
||||
# koreader applies these lua "patches" at boot:
|
||||
# - <https://github.com/koreader/koreader/wiki/User-patches>
|
||||
# the naming is IMPORTANT. these must start with a `2-` in order to be invoked during the right initialization phase
|
||||
#
|
||||
# 2023/10/29: koreader code hasn't changed, but somehow FTP browser seems usable even without the isConnected patch now.
|
||||
# - 2023/10/29: koreader code hasn't changed, but somehow FTP browser seems usable even without the isConnected patch now.
|
||||
# fs.".config/koreader/patches/2-colin-NetworkManager-isConnected.lua".symlink.target = "${./2-colin-NetworkManager-isConnected.lua}";
|
||||
|
||||
fs.".config/koreader/patches/2-02-colin-impl-clipboard-ops.lua".symlink.target = "${./2-02-colin-impl-clipboard-ops.lua}";
|
||||
|
||||
# koreader news plugin, enabled by default. file format described here:
|
||||
# - <repo:koreader/koreader:plugins/newsdownloader.koplugin/feed_config.lua>
|
||||
fs.".config/koreader/news/feed_config.lua".symlink.text = ''
|
||||
|
@@ -1,14 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.loupe = {
|
||||
mime.associations = {
|
||||
"image/gif" = "org.gnome.Loupe.desktop";
|
||||
"image/heif" = "org.gnome.Loupe.desktop"; # apple codec
|
||||
"image/png" = "org.gnome.Loupe.desktop";
|
||||
"image/jpeg" = "org.gnome.Loupe.desktop";
|
||||
"image/svg+xml" = "org.gnome.Loupe.desktop";
|
||||
"image/webp" = "org.gnome.Loupe.desktop";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
11
hosts/common/programs/megapixels.nix
Normal file
11
hosts/common/programs/megapixels.nix
Normal file
@@ -0,0 +1,11 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.megapixels.package = pkgs.megapixels.override {
|
||||
# megapixels uses zbar to read barcodes.
|
||||
# zbar by default ships zbarcam-gtk and zbarcam-qt, neither of which megapixels needs.
|
||||
# but the latter takes a dep on qt, which bloats the closure and the build, so disable this feature.
|
||||
zbar = pkgs.zbar.override {
|
||||
enableVideo = false;
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
# mimeo is an exec dispatcher like xdg-open, but why allows mapping different URL regexes to different handlers.
|
||||
# my setup sets mimeo as the default http/https handler,
|
||||
# and from there it dispatches specialized rules, falling back to the original http/https handler if no URL specialization exists
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
mimeo-open-desktop = pkgs.static-nix-shell.mkPython3Bin {
|
||||
pname = "mimeo-open-desktop";
|
||||
src = ./.;
|
||||
pkgs = [ "mimeo" ];
|
||||
};
|
||||
|
||||
# [ProgramConfig]
|
||||
enabledPrograms = builtins.filter
|
||||
(p: p.enabled)
|
||||
(builtins.attrValues config.sane.programs);
|
||||
|
||||
# [ProgramConfig]
|
||||
sortedPrograms = builtins.sort
|
||||
(l: r: l.priority or 1000 < r.priority or 1000)
|
||||
enabledPrograms;
|
||||
|
||||
fmtAssoc = regex: desktop: ''
|
||||
${mimeo-open-desktop}/bin/mimeo-open-desktop ${desktop} %U
|
||||
${regex}
|
||||
'';
|
||||
assocs = builtins.map
|
||||
(program: lib.mapAttrsToList fmtAssoc program.mime.urlAssociations)
|
||||
sortedPrograms;
|
||||
assocs' = lib.flatten assocs;
|
||||
|
||||
fmtFallbackAssoc = mimeType: desktop: if mimeType == "x-scheme-handler/http" then ''
|
||||
${mimeo-open-desktop}/bin/mimeo-open-desktop ${desktop} %U
|
||||
^http://.*
|
||||
'' else if mimeType == "x-scheme-handler/https" then ''
|
||||
${mimeo-open-desktop}/bin/mimeo-open-desktop ${desktop} %U
|
||||
^https://.*
|
||||
'' else "";
|
||||
fmtFallbackAssoc' = mimeType: desktop:
|
||||
lib.optionalString (desktop != "mimeo.desktop") (fmtFallbackAssoc mimeType desktop);
|
||||
|
||||
fallbackAssocs = builtins.map
|
||||
(program: lib.mapAttrsToList fmtFallbackAssoc' program.mime.associations)
|
||||
sortedPrograms;
|
||||
fallbackAssocs' = lib.flatten fallbackAssocs;
|
||||
in
|
||||
{
|
||||
sane.programs.mimeo = {
|
||||
package = pkgs.mimeo.overridePythonAttrs (upstream: {
|
||||
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ [
|
||||
pkgs.copyDesktopItems
|
||||
];
|
||||
desktopItems = [
|
||||
(pkgs.makeDesktopItem {
|
||||
name = "mimeo";
|
||||
desktopName = "Mimeo";
|
||||
exec = "mimeo %U";
|
||||
comment = "Open files by MIME-type or file name using regular expressions.";
|
||||
})
|
||||
];
|
||||
|
||||
# upstream mimeo doesn't run preInstall/postInstall hooks, but we need that for the .desktop file
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
${upstream.installPhase}
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru = (upstream.passthru or {}) // {
|
||||
inherit mimeo-open-desktop;
|
||||
};
|
||||
});
|
||||
|
||||
fs.".config/mimeo/associations.txt".symlink.text = lib.concatStringsSep "\n" (assocs' ++ fallbackAssocs');
|
||||
mime.priority = 20;
|
||||
mime.associations."x-scheme-handler/http" = "mimeo.desktop";
|
||||
mime.associations."x-scheme-handler/https" = "mimeo.desktop";
|
||||
};
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p mimeo
|
||||
|
||||
# TODO: migrate nixpkgs mimeo to be `buildPythonPackage` to make it importable here.
|
||||
# see <doc/languages-frameworks/python.section.md>
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
desktop = sys.argv[1]
|
||||
opener_args = sys.argv[2:]
|
||||
|
||||
desktop_fields=subprocess.check_output([
|
||||
"mimeo",
|
||||
"--desk2field",
|
||||
"Exec",
|
||||
desktop
|
||||
]).decode('utf-8')
|
||||
# print(f"fields: {desktop_fields!r}")
|
||||
|
||||
desktop_exec = '\n'.join(desktop_fields.split('\n')[1:])
|
||||
# print(f"exec: {desktop_exec!r}")
|
||||
|
||||
# TODO: this is obviously not correct if any of the args included spaces
|
||||
desktop_argv = [f.strip() for f in desktop_exec.split(' ') if f.strip()]
|
||||
# print(f"desktop_argv: {desktop_argv!r}")
|
||||
|
||||
# fields explained: <https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s07.html>
|
||||
# - %U = all URLs
|
||||
# - %U = just the first URL
|
||||
substituted_argv = []
|
||||
for arg in desktop_argv:
|
||||
if arg == '%U':
|
||||
substituted_argv += opener_args
|
||||
elif arg == '%u':
|
||||
substituted_argv += opener_args[:1]
|
||||
else:
|
||||
substituted_argv += [arg]
|
||||
|
||||
# print(f"argv: {substituted_argv}")
|
||||
print(subprocess.check_output(substituted_argv).decode())
|
@@ -23,19 +23,16 @@ in
|
||||
package = pkgs.wrapMpv pkgs.mpv-unwrapped {
|
||||
scripts = with pkgs.mpvScripts; [
|
||||
mpris
|
||||
uosc
|
||||
# pkgs.mpv-uosc-latest
|
||||
# uosc
|
||||
pkgs.mpv-uosc-latest
|
||||
];
|
||||
extraMakeWrapperArgs = lib.optionals (cfg.config.vo != null) [
|
||||
# 2023/08/29: fixes an error where mpv on moby launches with the message
|
||||
# "DRM_IOCTL_MODE_CREATE_DUMB failed: Cannot allocate memory"
|
||||
# audio still works, and controls, screenshotting, etc -- just not the actual rendering
|
||||
#
|
||||
# this is likely a regression for mpv 0.36.0.
|
||||
# the actual error message *appears* to come from the mesa library, but it's tough to trace.
|
||||
#
|
||||
# TODO(2023/12/03): remove once mesa 23.3.1 lands: <https://github.com/NixOS/nixpkgs/pull/265740>
|
||||
#
|
||||
# backend compatibility (2023/10/22):
|
||||
# run with `--vo=help` to see a list of all output options.
|
||||
# non-exhaustive (W=works, F=fails, A=audio-only, U=audio+ui only (no video))
|
||||
@@ -57,46 +54,13 @@ in
|
||||
];
|
||||
};
|
||||
persist.byStore.plaintext = [ ".local/state/mpv/watch_later" ];
|
||||
fs.".config/mpv/input.conf".symlink.text = let
|
||||
execInTerm = "${pkgs.xdg-terminal-exec}/bin/xdg-terminal-exec";
|
||||
in ''
|
||||
# docs:
|
||||
# - <https://mpv.io/manual/master/#list-of-input-commands>
|
||||
# - script-binding: <https://mpv.io/manual/master/#command-interface-script-binding>
|
||||
# - properties: <https://mpv.io/manual/master/#property-list>
|
||||
|
||||
fs.".config/mpv/input.conf".symlink.text = ''
|
||||
# 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
|
||||
|
||||
# uosc menu
|
||||
# text after the shebang is parsed by uosc to construct the menu and names
|
||||
menu script-binding uosc/menu
|
||||
s script-binding uosc/subtitles #! Subtitles
|
||||
a script-binding uosc/audio #! Audio tracks
|
||||
q script-binding uosc/stream-quality #! Stream quality
|
||||
p script-binding uosc/items #! Playlist
|
||||
c script-binding uosc/chapters #! Chapters
|
||||
> script-binding uosc/next #! Navigation > Next
|
||||
< script-binding uosc/prev #! Navigation > Prev
|
||||
o script-binding uosc/open-file #! Navigation > Open file
|
||||
# set video-aspect-override "-1" #! Utils > Aspect ratio > Default
|
||||
# set video-aspect-override "16:9" #! Utils > Aspect ratio > 16:9
|
||||
# set video-aspect-override "4:3" #! Utils > Aspect ratio > 4:3
|
||||
# set video-aspect-override "2.35:1" #! Utils > Aspect ratio > 2.35:1
|
||||
# script-binding uosc/audio-device #! Utils > Audio devices
|
||||
# script-binding uosc/editions #! Utils > Editions
|
||||
ctrl+s async screenshot #! Utils > Screenshot
|
||||
alt+i script-binding uosc/keybinds #! Utils > Key bindings
|
||||
O script-binding uosc/show-in-directory #! Utils > Show in directory
|
||||
# script-binding uosc/open-config-directory #! Utils > Open config directory
|
||||
# set pause yes; run ${execInTerm} go2tv -v "''${stream-open-filename}" #! Cast
|
||||
# set pause yes; run ${execInTerm} go2tv -u "''${stream-open-filename}" #! Cast (...) > Stream
|
||||
# set pause yes; run go2tv #! Cast (...) > GUI
|
||||
# TODO: unify "Cast" and "Cast (stream)" options above.
|
||||
'';
|
||||
fs.".config/mpv/mpv.conf".symlink.text = ''
|
||||
save-position-on-quit=yes
|
||||
@@ -127,7 +91,6 @@ in
|
||||
in ''
|
||||
# docs:
|
||||
# - <https://github.com/tomasklaen/uosc>
|
||||
# - <https://github.com/tomasklaen/uosc/blob/main/src/uosc.conf>
|
||||
# - <https://superuser.com/questions/1775550/add-new-buttons-to-mpv-uosc-ui>
|
||||
timeline_style=bar
|
||||
timeline_persistency=paused,audio
|
||||
@@ -144,7 +107,8 @@ in
|
||||
|
||||
text_border=6.0
|
||||
font_bold=yes
|
||||
color=foreground=ff8080,background_text=ff8080
|
||||
background_text=ff8080
|
||||
foreground=ff8080
|
||||
|
||||
ui_scale=1.0
|
||||
'';
|
||||
@@ -158,11 +122,7 @@ in
|
||||
mime.associations."video/mp4" = "mpv.desktop";
|
||||
mime.associations."video/quicktime" = "mpv.desktop";
|
||||
mime.associations."video/webm" = "mpv.desktop";
|
||||
mime.associations."video/x-flv" = "mpv.desktop";
|
||||
mime.associations."video/x-matroska" = "mpv.desktop";
|
||||
mime.urlAssociations."^https?://(www.)?youtube.com/watch\?.*v=" = "mpv.desktop";
|
||||
mime.urlAssociations."^https?://(www.)?youtube.com/v/" = "mpv.desktop";
|
||||
mime.urlAssociations."^https?://(www.)?youtu.be/.+" = "mpv.desktop";
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -1,12 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs."gnome.nautilus" = {
|
||||
package = pkgs.gnome.nautilus.overrideAttrs (orig: {
|
||||
# enable the "Audio and Video Properties" pane. see: <https://nixos.wiki/wiki/Nautilus>
|
||||
buildInputs = orig.buildInputs ++ (with pkgs.gst_all_1; [
|
||||
gst-plugins-good
|
||||
gst-plugins-bad
|
||||
]);
|
||||
});
|
||||
};
|
||||
}
|
@@ -1,19 +1,12 @@
|
||||
# news-flash RSS viewer
|
||||
# - feeds have to be manually imported:
|
||||
# - Local RSS -> Import OPML -> ~/.config/newsflashFeeds.opml
|
||||
# - clicking article-embedded links doesn't work because of xdg portal stuff
|
||||
# - need to either run unsandboxed, or install a org.freedesktop.portal.OpenURI handler
|
||||
{ config, sane-lib, ... }:
|
||||
|
||||
let
|
||||
feeds = sane-lib.feeds;
|
||||
all-feeds = config.sane.feeds;
|
||||
# text/image: newsflash renders these natively
|
||||
# podcast/video: newsflash dispatches these to xdg-open
|
||||
wanted-feeds = feeds.filterByFormat [ "text" "image" "podcast" "video" ] all-feeds;
|
||||
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
|
||||
in {
|
||||
sane.programs.newsflash = {
|
||||
slowToBuild = true; # mainly for desktop: webkitgtk-6.0
|
||||
persist.byStore.plaintext = [ ".local/share/news-flash" ];
|
||||
fs.".config/newsflashFeeds.opml".symlink.text =
|
||||
feeds.feedsToOpml wanted-feeds
|
||||
|
@@ -1,8 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.notejot = {
|
||||
persist.byStore.private = [
|
||||
".local/share/io.github.lainsce.Notejot"
|
||||
];
|
||||
};
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.open-in-mpv = {
|
||||
# taken from <https://github.com/Baldomo/open-in-mpv>
|
||||
fs.".config/open-in-mpv/config.yml".symlink.text = ''
|
||||
players:
|
||||
mpv:
|
||||
name: mpv
|
||||
executable: mpv
|
||||
fullscreen: "--fs"
|
||||
pip: "--ontop --no-border --autofit=384x216 --geometry=98\\%:98\\%"
|
||||
enqueue: ""
|
||||
new_window: ""
|
||||
needs_ipc: true
|
||||
flag_overrides: {}
|
||||
'';
|
||||
};
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.planify = {
|
||||
persist.byStore.private = [
|
||||
# TODO items as a sqlite database
|
||||
".local/share/io.github.alainm23.planify"
|
||||
];
|
||||
|
||||
slowToBuild = true; # webkitgtk-6.0; slow for desktop
|
||||
};
|
||||
}
|
@@ -1,10 +1,3 @@
|
||||
# TODO(bug): signal-desktop is known to hang on exit.
|
||||
# particularly, it may fail to start (because e.g. there's no wayland session yet),
|
||||
# and it will try to exit -- after which the service would restart -- but it hangs w/ no GUI instead.
|
||||
# characterized by these log messages:
|
||||
#
|
||||
# Dec 03 13:46:23 moby signal-desktop[4097]: [4097:1203/134623.906367:ERROR:ozone_platform_x11.cc(240)] Missing X server or $DISPLAY
|
||||
# Dec 03 13:46:23 moby signal-desktop[4097]: [4097:1203/134623.909667:ERROR:env.cc(255)] The platform failed to initialize. Exiting.
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.signal-desktop;
|
||||
@@ -37,8 +30,6 @@ in
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
# for some reason the --ozone-platform-hint=auto flag fails when signal-desktop is launched from a service
|
||||
environment.NIXOS_OZONE_WL = "1";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -17,9 +17,6 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.swaynotificationcenter;
|
||||
|
||||
mprisIconSize = 48;
|
||||
|
||||
fbcli-wrapper = pkgs.writeShellApplication {
|
||||
name = "swaync-fbcli";
|
||||
runtimeInputs = [
|
||||
@@ -137,12 +134,6 @@ in
|
||||
hash = "sha256-Y8fiZbAP9yGOVU3rOkZKO8TnPPlrGpINWYGaqeeNzF0=";
|
||||
})
|
||||
];
|
||||
postPatch = (upstream.postPatch or "") + ''
|
||||
# XXX: this might actually be changing the DPI, not the scaling...
|
||||
# in that case, it might be possible to do this in CSS
|
||||
substituteInPlace src/controlCenter/widgets/mpris/mpris_player.ui \
|
||||
--replace '96' '${builtins.toString mprisIconSize}'
|
||||
'';
|
||||
}));
|
||||
suggestedPrograms = [ "feedbackd" ];
|
||||
fs.".config/swaync/style.css".symlink.text = ''
|
||||
@@ -227,27 +218,6 @@ in
|
||||
# - SWAYNC_REPLACES_ID
|
||||
# - SWAYNC_ID
|
||||
# - SWAYNC_SUMMARY
|
||||
|
||||
# rules to use for testing. trigger with:
|
||||
# - `notify-send test test:message` (etc)
|
||||
# should also be possible to trigger via any messaging app
|
||||
fbcli-test-im = {
|
||||
body = "test:message";
|
||||
exec = "${fbcli} --event proxied-message-new-instant";
|
||||
};
|
||||
fbcli-test-call = {
|
||||
body = "test:call";
|
||||
exec = "${fbcli} --event phone-incoming-call -t 20";
|
||||
};
|
||||
fbcli-test-call-stop = {
|
||||
body = "test:call-stop";
|
||||
exec = "${fbcli-stop} --event phone-incoming-call -t 20";
|
||||
};
|
||||
fbcli-test-timer = {
|
||||
body = "test:timer";
|
||||
exec = "${fbcli} --event timeout-completed";
|
||||
};
|
||||
|
||||
incoming-im-known-app-name = {
|
||||
# trigger notification sound on behalf of these IM clients.
|
||||
app-name = "(Chats|Dino|discord|Element|Fractal|gtkcord4)";
|
||||
@@ -397,21 +367,21 @@ in
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user geary";
|
||||
active = "${pkgs.systemd}/bin/systemctl is-active --user geary";
|
||||
}
|
||||
# ] ++ lib.optionals config.sane.programs.abaddon.enabled [
|
||||
# # XXX: disabled in favor of gtkcord4: abaddon has troubles auto-connecting at start
|
||||
# {
|
||||
# type = "toggle";
|
||||
# label = ""; # Discord chat client; icons: , 🎮
|
||||
# command = "${systemctl-toggle}/bin/systemctl-toggle --user abaddon";
|
||||
# active = "${pkgs.systemd}/bin/systemctl is-active --user abaddon";
|
||||
# }
|
||||
] ++ lib.optionals config.sane.programs.gtkcord4.enabled [
|
||||
] ++ lib.optionals config.sane.programs.abaddon.enabled [
|
||||
{
|
||||
type = "toggle";
|
||||
label = ""; # Discord chat client; icons: , 🎮
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user gtkcord4";
|
||||
active = "${pkgs.systemd}/bin/systemctl is-active --user gtkcord4";
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user abaddon";
|
||||
active = "${pkgs.systemd}/bin/systemctl is-active --user abaddon";
|
||||
}
|
||||
# ] ++ lib.optionals config.sane.programs.gtkcord4.enabled [
|
||||
# # XXX: disabled in favor of abaddon: gtkcord4 leaks memory
|
||||
# {
|
||||
# type = "toggle";
|
||||
# label = ""; # Discord chat client; icons: , 🎮
|
||||
# command = "${systemctl-toggle}/bin/systemctl-toggle --user gtkcord4";
|
||||
# active = "${pkgs.systemd}/bin/systemctl is-active --user gtkcord4";
|
||||
# }
|
||||
] ++ lib.optionals config.sane.programs.signal-desktop.enabled [
|
||||
{
|
||||
type = "toggle";
|
||||
@@ -444,7 +414,7 @@ in
|
||||
clear-all-button = true;
|
||||
};
|
||||
mpris = {
|
||||
image-size = mprisIconSize;
|
||||
image-size = 64;
|
||||
image-radius = 8;
|
||||
};
|
||||
title = {
|
||||
|
@@ -32,7 +32,6 @@ in
|
||||
mime.associations."video/mp4" = "vlc.desktop";
|
||||
mime.associations."video/quicktime" = "vlc.desktop";
|
||||
mime.associations."video/webm" = "vlc.desktop";
|
||||
mime.associations."video/x-flv" = "vlc.desktop";
|
||||
mime.associations."video/x-matroska" = "vlc.desktop";
|
||||
};
|
||||
}
|
||||
|
@@ -1,143 +0,0 @@
|
||||
# docs:
|
||||
# - <https://github.com/francma/wob/blob/master/wob.ini.5.scd>
|
||||
# - `wob -vv` to see config defaults
|
||||
#
|
||||
# the wob services defined here are largely based on those from SXMO.
|
||||
#
|
||||
# this should arguably be just a (user) service. nothing actually needs `wob` on the PATH.
|
||||
#
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.wob;
|
||||
in
|
||||
{
|
||||
sane.programs.wob = {
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
options.sock = mkOption {
|
||||
type = types.str;
|
||||
default = "sxmo.wobsock";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fs.".config/wob/wob.ini".symlink.text = ''
|
||||
timeout = 900
|
||||
|
||||
anchor = top right
|
||||
orientation = vertical
|
||||
# margin top right bottom left
|
||||
# note that wob is "aware" of the sway bar, so margin 0 never overlaps it.
|
||||
# however it's not aware of sway's window title
|
||||
margin = 54 3 54 3
|
||||
|
||||
height = 164
|
||||
width = 30
|
||||
|
||||
border_offset = 0
|
||||
border_size = 2
|
||||
bar_padding = 0
|
||||
|
||||
# very light teal, derived from conky background
|
||||
bar_color = e1f0efDC
|
||||
background_color = 000000B4
|
||||
border_color = 000000C8
|
||||
|
||||
overflow_bar_color = FF4040DC
|
||||
overflow_background_color = FFFFFFC8
|
||||
overflow_border_color = FF4040DC
|
||||
'';
|
||||
|
||||
services.wob = {
|
||||
description = "Wayland Overlay Bar (renders volume/backlight levels)";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||
serviceConfig = {
|
||||
# ExecStart = "${cfg.package}/bin/wob";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
path = [ cfg.package ];
|
||||
script = ''
|
||||
wobsock="$XDG_RUNTIME_DIR/${cfg.config.sock}"
|
||||
rm -f "$wobsock" || true
|
||||
mkfifo "$wobsock" && wob <> "$wobsock"
|
||||
|
||||
# TODO: cleanup should be done in a systemd OnFailure, or OnExit, or whatever
|
||||
rm -f "$wobsock"
|
||||
'';
|
||||
};
|
||||
|
||||
services.wob-pulse = {
|
||||
description = "notify wob when pulseaudio volume changes";
|
||||
wantedBy = [ "wob.service" ];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
path = with pkgs; [
|
||||
# coreutils
|
||||
gnugrep
|
||||
gnused
|
||||
pulseaudio
|
||||
];
|
||||
|
||||
# environment.WOB_VERBOSE = "1";
|
||||
|
||||
script = ''
|
||||
|
||||
debug() {
|
||||
printf "$@" >&2
|
||||
printf "\n" >&2
|
||||
}
|
||||
verbose() {
|
||||
if [ "$WOB_VERBOSE" = "1" ]; then
|
||||
debug "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
volismuted() {
|
||||
pactl get-sink-mute @DEFAULT_SINK@ | grep -q "Mute: yes"
|
||||
}
|
||||
|
||||
volget() {
|
||||
if volismuted; then
|
||||
verbose "muted"
|
||||
printf "0"
|
||||
else
|
||||
pactl get-sink-volume @DEFAULT_SINK@ | head -n1 | cut -d'/' -f2 | sed 's/ //g' | sed 's/\%//'
|
||||
fi
|
||||
}
|
||||
|
||||
notify_volume_change() {
|
||||
verbose "notify_volume_change"
|
||||
vol=$(volget)
|
||||
verbose "got volume: %d -> %d" "$lastvol" "$vol"
|
||||
if [ "$vol" != "$lastvol" ]; then
|
||||
debug "notify wob: %d -> %d" "$lastvol" "$vol"
|
||||
printf "%s\n" "$vol" > "$XDG_RUNTIME_DIR/${cfg.config.sock}"
|
||||
fi
|
||||
lastvol="$vol"
|
||||
}
|
||||
|
||||
pactl subscribe | while read -r line; do
|
||||
verbose "pactl says: %s" "$line"
|
||||
case "$line" in
|
||||
"Event 'change' on sink "*)
|
||||
notify_volume_change
|
||||
;;
|
||||
"Event 'change' on source "*)
|
||||
# microphone volume changed. ignore.
|
||||
;;
|
||||
esac
|
||||
done
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
# zcash wallet
|
||||
#
|
||||
# N.B.: zecwallet is UNMAINTAINED. as of 2024/01/01 it requires manual intervention to sync:
|
||||
# 1. launch it, and wait 5min for it to timeout.
|
||||
# 2. set the node address to https://lwd3.zcash-infra.com:9067
|
||||
# 3. close the application and re-open
|
||||
# more info:
|
||||
# - <https://forum.zcashcommunity.com/t/zecwallet-lightwalletd-server-continuation/45659/>
|
||||
# - <https://status.zcash-infra.com/>
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.zecwallet-lite = {
|
||||
# zcash coins. safe to delete, just slow to regenerate (10-60 minutes)
|
||||
persist.byStore.private = [ ".zcash" ];
|
||||
};
|
||||
}
|
@@ -85,16 +85,6 @@ in
|
||||
# emulate bash keybindings
|
||||
bindkey -e
|
||||
|
||||
# fixup bindings not handled by bash, see: <https://wiki.archlinux.org/title/Zsh#Key_bindings>
|
||||
# `bindkey -e` seems to define most of the `key` array. everything in the Arch defaults except for these:
|
||||
key[Backspace]="''${terminfo[kbs]}"
|
||||
key[Control-Left]="''${terminfo[kLFT5]}"
|
||||
key[Control-Right]="''${terminfo[kRIT5]}"
|
||||
key[Shift-Tab]="''${terminfo[kcbt]}"
|
||||
bindkey -- "''${key[Delete]}" delete-char
|
||||
bindkey -- "''${key[Control-Left]}" backward-word
|
||||
bindkey -- "''${key[Control-Right]}" forward-word
|
||||
|
||||
# or manually recreate what i care about...
|
||||
# key[Left]=''${terminfo[kcub1]}
|
||||
# key[Right]=''${terminfo[kcuf1]}
|
||||
|
@@ -13,7 +13,6 @@
|
||||
];
|
||||
group = "users";
|
||||
extraGroups = [
|
||||
"clightning" # servo, for clightning-cli
|
||||
"dialout" # required for modem access (moby)
|
||||
"export" # to read filesystem exports (servo)
|
||||
"feedbackd" # moby, so `fbcli` can control vibrator and LEDs
|
||||
|
@@ -8,41 +8,21 @@
|
||||
# - copy the Address, PublicKey, Endpoint from OVPN's config
|
||||
# N.B.: maximum interface name in Linux is 15 characters.
|
||||
let
|
||||
def-wg-vpn = name: { endpoint, publicKey, address, dns, privateKeyFile }: {
|
||||
# networking.wg-quick.interfaces."${name}" = {
|
||||
# inherit address privateKeyFile dns;
|
||||
# peers = [
|
||||
# {
|
||||
# allowedIPs = [
|
||||
# "0.0.0.0/0"
|
||||
# "::/0"
|
||||
# ];
|
||||
# inherit endpoint publicKey;
|
||||
# }
|
||||
# ];
|
||||
# # to start: `systemctl start wg-quick-${name}`
|
||||
# autostart = false;
|
||||
# };
|
||||
systemd.network.netdevs."${name}" = {
|
||||
# see: `man 5 systemd.netdev`
|
||||
wireguardConfig = {
|
||||
PrivateKeyFile = privateKeyFile;
|
||||
};
|
||||
wireguardPeers = [{
|
||||
AllowedIPs = [
|
||||
def-wg-vpn = name: { endpoint, publicKey, address, dns, privateKeyFile, extraOptions ? {} }: {
|
||||
networking.wg-quick.interfaces."${name}" = {
|
||||
inherit address privateKeyFile dns;
|
||||
peers = [
|
||||
{
|
||||
allowedIPs = [
|
||||
"0.0.0.0/0"
|
||||
"::/0"
|
||||
];
|
||||
Endpoint = endpoint;
|
||||
PublicKey = publicKey;
|
||||
}];
|
||||
};
|
||||
systemd.network.networks."${name}" = {
|
||||
# see: `man 5 systemd.network`
|
||||
matchConfig.Name = name;
|
||||
networkConfig.Address = address;
|
||||
networkConfig.DNS = dns;
|
||||
};
|
||||
inherit endpoint publicKey;
|
||||
}
|
||||
];
|
||||
# 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;
|
@@ -57,12 +57,11 @@ in
|
||||
sane.programs.guiBaseApps = declPackageSet [
|
||||
"abaddon" # discord client
|
||||
"alacritty" # terminal emulator
|
||||
"delfin" # Jellyfin client
|
||||
"dialect" # language translation
|
||||
"dino" # XMPP client
|
||||
# "emote"
|
||||
"evince" # works on phosh
|
||||
"flare-signal" # gtk4 signal client
|
||||
# "flare-signal" # gtk4 signal client
|
||||
# "foliate" # e-book reader
|
||||
"fractal" # matrix client
|
||||
"g4music" # local music player
|
||||
@@ -78,23 +77,17 @@ in
|
||||
# "gnome.gnome-system-monitor"
|
||||
# "gnome.gnome-terminal" # works on phosh
|
||||
"gnome.gnome-weather"
|
||||
"gnome.seahorse" # keyring/secret manager
|
||||
"gnome-frog" # OCR/QR decoder
|
||||
"gpodder"
|
||||
# "gthumb"
|
||||
"gtkcord4" # Discord client. 2023/11/21: disabled because v0.0.12 leaks memory
|
||||
"gthumb"
|
||||
# "gtkcord4" # Discord client. 2023/11/21: disabled because it leaks memory
|
||||
"lemoa" # lemmy app
|
||||
"libnotify" # for notify-send; debugging
|
||||
# "lollypop"
|
||||
"loupe" # image viewer
|
||||
"mate.engrampa" # archive manager
|
||||
"mepo" # maps viewer
|
||||
"mpv"
|
||||
"networkmanagerapplet" # for nm-connection-editor: it's better than not having any gui!
|
||||
"ntfy-sh" # notification service
|
||||
# "newsflash" # RSS viewer
|
||||
# "newsflash"
|
||||
"pavucontrol"
|
||||
"pwvucontrol" # pipewire version of pavu
|
||||
# "picard" # music tagging
|
||||
# "libsForQt5.plasmatube" # Youtube player
|
||||
"signal-desktop"
|
||||
@@ -103,7 +96,6 @@ in
|
||||
# "tdesktop" # broken on phosh
|
||||
# "tokodon"
|
||||
"tuba" # mastodon/pleroma client (stores pw in keyring)
|
||||
"vulkan-tools" # vulkaninfo
|
||||
# "whalebird" # pleroma client (Electron). input is broken on phosh.
|
||||
"xdg-terminal-exec"
|
||||
"xterm" # broken on phosh
|
||||
@@ -112,15 +104,13 @@ in
|
||||
sane.programs.handheldGuiApps = declPackageSet [
|
||||
"calls" # gnome calls (dialer/handler)
|
||||
# "celluloid" # mpv frontend
|
||||
# "chatty" # matrix/xmpp/irc client (2023/12/29: disabled because broken cross build)
|
||||
"chatty" # matrix/xmpp/irc client
|
||||
"cozy" # audiobook player
|
||||
"epiphany" # gnome's web browser
|
||||
# "iotas" # note taking app
|
||||
"gpodder"
|
||||
"komikku"
|
||||
"koreader"
|
||||
"megapixels" # camera app
|
||||
"notejot" # note taking, e.g. shopping list
|
||||
"planify" # todo-tracker/planner
|
||||
"portfolio-filemanager"
|
||||
"tangram" # web browser
|
||||
"wike" # Wikipedia Reader
|
||||
@@ -163,9 +153,8 @@ in
|
||||
"mumble"
|
||||
# "nheko" # Matrix chat client
|
||||
# "obsidian"
|
||||
"openscad" # 3d modeling
|
||||
# "rhythmbox" # local music player
|
||||
# "slic3r"
|
||||
"slic3r"
|
||||
"soundconverter"
|
||||
"spotify" # x86-only
|
||||
"steam"
|
||||
|
@@ -21,6 +21,7 @@ in
|
||||
services.xserver.displayManager.gdm.enable = true;
|
||||
|
||||
# gnome does networking stuff with networkmanager
|
||||
networking.useDHCP = false;
|
||||
networking.networkmanager.enable = true;
|
||||
networking.wireless.enable = lib.mkForce false;
|
||||
};
|
||||
|
@@ -74,6 +74,7 @@ in
|
||||
services.gnome.gnome-online-miners.enable = mkForce false;
|
||||
|
||||
# XXX: phosh enables networkmanager by default; can probably disable these lines
|
||||
networking.useDHCP = false;
|
||||
networking.networkmanager.enable = true;
|
||||
networking.wireless.enable = lib.mkForce false;
|
||||
|
||||
|
@@ -4,8 +4,8 @@
|
||||
# sway-config docs: `man 5 sway`
|
||||
let
|
||||
cfg = config.sane.gui.sway;
|
||||
wrapSway = sway': swayOverrideArgs: let
|
||||
# `wrapSway` exists to create a `sway.desktop` file
|
||||
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;
|
||||
@@ -14,36 +14,31 @@ let
|
||||
echo "launching sway-session (sway.desktop)..." | ${systemd-cat} --identifier=sway-session
|
||||
sway 2>&1 | ${systemd-cat} --identifier=sway-session
|
||||
'';
|
||||
origSway = pkgs.sway.override {
|
||||
# this override is what `programs.nixos` would do internally if we left `package` unset.
|
||||
configuredSway = sway'.override swayOverrideArgs;
|
||||
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 ${configuredSway}/share/wayland-sessions/sway.desktop $out/share/wayland-sessions/sway.desktop \
|
||||
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
|
||||
'';
|
||||
in pkgs.symlinkJoin {
|
||||
inherit (configuredSway) name meta;
|
||||
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 configuredSway provides
|
||||
paths = [ desktop-file configuredSway ];
|
||||
# overriding whatever the origSway provides
|
||||
paths = [ desktop-file origSway ];
|
||||
passthru = {
|
||||
inherit (configuredSway.passthru) providedSessions;
|
||||
# nixos/modules/programs/wayland/sway.nix will call `.override` on the package we provide it
|
||||
override = wrapSway sway';
|
||||
inherit (origSway.passthru) providedSessions;
|
||||
};
|
||||
};
|
||||
defaultPackage = wrapSway pkgs.sway {
|
||||
# this is technically optional, in that the nixos sway module will call `override` with these args anyway.
|
||||
# but that wasn't always the case; it may change again; so don't rely on it.
|
||||
inherit (config.programs.sway)
|
||||
extraSessionCommands extraOptions;
|
||||
withBaseWrapper = config.programs.sway.wrapperFeatures.base;
|
||||
withGtkWrapper = config.programs.sway.wrapperFeatures.gtk;
|
||||
isNixOS = true;
|
||||
# TODO: `enableXWayland = ...`?
|
||||
};
|
||||
in
|
||||
{
|
||||
options = with lib; {
|
||||
@@ -181,7 +176,7 @@ in
|
||||
"playerctl" # for waybar & particularly to have playerctld running
|
||||
# "mako" # notification daemon
|
||||
"swaynotificationcenter" # notification daemon
|
||||
"wob" # render volume changes on-screen
|
||||
# # "pavucontrol"
|
||||
# "gnome.gnome-bluetooth" # XXX(2023/05/14): broken
|
||||
# "gnome.gnome-control-center" # XXX(2023/06/28): depends on webkitgtk4_1
|
||||
"sway-contrib.grimshot"
|
||||
@@ -202,7 +197,7 @@ in
|
||||
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 "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
|
||||
@@ -235,7 +230,6 @@ in
|
||||
# emulate pulseaudio for legacy apps (e.g. sxmo-utils)
|
||||
pulse.enable = true;
|
||||
};
|
||||
services.gvfs.enable = true; # allow nautilus to mount remote filesystems (e.g. ftp://...)
|
||||
# rtkit/RealtimeKit: allow applications which want realtime audio (e.g. Dino? Pulseaudio server?) to request it.
|
||||
# this might require more configuration (e.g. polkit-related) to work exactly as desired.
|
||||
# - readme outlines requirements: <https://github.com/heftig/rtkit>
|
||||
@@ -260,6 +254,7 @@ in
|
||||
# };
|
||||
# sane.persist.sys.byStore.plaintext = [ "/var/lib/alsa" ];
|
||||
|
||||
networking.useDHCP = false;
|
||||
networking.networkmanager.enable = true;
|
||||
networking.wireless.enable = lib.mkForce false;
|
||||
|
||||
@@ -282,7 +277,6 @@ in
|
||||
# - org.freedesktop.impl.portal.ScreenCast
|
||||
# - org.freedesktop.impl.portal.Screenshot
|
||||
enable = true;
|
||||
package = cfg.package;
|
||||
extraPackages = []; # nixos adds swaylock, swayidle, foot, dmenu by default
|
||||
# extraOptions = [ "--debug" ];
|
||||
# "wrapGAppsHook wrapper to execute sway with required environment variables for GTK applications."
|
||||
@@ -293,6 +287,7 @@ in
|
||||
# this sets XDG_CURRENT_DESKTOP=sway
|
||||
# and makes sure that sway is launched dbus-run-session.
|
||||
wrapperFeatures.base = true;
|
||||
package = cfg.package;
|
||||
};
|
||||
programs.xwayland.enable = cfg.config.xwayland;
|
||||
# provide portals for:
|
||||
|
@@ -1,6 +1,3 @@
|
||||
# docs:
|
||||
# - `man 5 sway`
|
||||
#
|
||||
# xwayland enable|disable|force
|
||||
# - enable: lazily launch xwayland on first client connection
|
||||
# - disable: never launch xwayland
|
||||
@@ -151,17 +148,10 @@ for_window [title="megapixels"] inhibit_idle open
|
||||
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
|
||||
for_window [app_id="geary"] move container to workspace number 1
|
||||
for_window [app_id="signal"] move container to workspace number 1
|
||||
# class=Signal for when it's running with Xwayland
|
||||
for_window [class="Signal"] move container to workspace number 1
|
||||
for_window [app_id="so.libdb.gtkcord4"] move container to workspace number 1
|
||||
for_window [app_id="xyz.diamondb.gtkcord4"] move container to workspace number 1
|
||||
for_window [app_id="abaddon"] move container to workspace number 1
|
||||
|
||||
# window display settings
|
||||
# force KOReader to always display a titlebar, even when the only window being rendered.
|
||||
# desirable primarily to avoid slooow reflows when another app is opened. but also nice to have the book title rendered.
|
||||
for_window [app_id="KOReader"] border normal
|
||||
|
||||
### displays
|
||||
## DESKTOP
|
||||
output "Goldstar Company Ltd LG ULTRAWIDE 0x00004E94" {
|
||||
|
@@ -59,8 +59,6 @@ window#waybar {
|
||||
#cpu,
|
||||
#custom-media,
|
||||
#custom-swaync,
|
||||
#custom-sxmo,
|
||||
#custom-sxmo-sane,
|
||||
#disk,
|
||||
#idle_inhibitor,
|
||||
#memory,
|
||||
|
@@ -32,8 +32,6 @@ in
|
||||
# - 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>
|
||||
# - alternative: <https://www.reddit.com/r/swaywm/comments/g6nw46/comment/fob604s/>
|
||||
# - this one shades the background based on how far through the song you are
|
||||
#
|
||||
# N.B.: for this to behave well with multiple MPRIS clients,
|
||||
# `playerctld` must be enabled. see: <https://github.com/altdesktop/playerctl/issues/161>
|
||||
|
@@ -68,11 +68,6 @@ let
|
||||
'';
|
||||
|
||||
hookPkgs = {
|
||||
block_suspend = pkgs.static-nix-shell.mkBash {
|
||||
pname = "sxmo_hook_block_suspend.sh";
|
||||
pkgs = [ "procps" ];
|
||||
src = ./hooks;
|
||||
};
|
||||
inputhandler = pkgs.static-nix-shell.mkBash {
|
||||
pname = "sxmo_hook_inputhandler.sh";
|
||||
pkgs = [ "coreutils" "playerctl" "pulseaudio" ];
|
||||
@@ -88,6 +83,11 @@ let
|
||||
pkgs = [ "sway" ];
|
||||
src = ./hooks;
|
||||
};
|
||||
screenoff = pkgs.static-nix-shell.mkBash {
|
||||
pname = "sxmo_hook_screenoff.sh";
|
||||
pkgs = [ "sway" ];
|
||||
src = ./hooks;
|
||||
};
|
||||
start = pkgs.static-nix-shell.mkBash {
|
||||
pname = "sxmo_hook_start.sh";
|
||||
pkgs = [ "systemd" "xdg-user-dirs" ];
|
||||
@@ -150,7 +150,7 @@ in
|
||||
# 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_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";
|
||||
@@ -176,7 +176,6 @@ in
|
||||
"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_screenoff.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_screenoff.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";
|
||||
@@ -187,10 +186,10 @@ in
|
||||
"sxmo_hook_tailtextlog.sh" = "${package}/share/sxmo/default_hooks/sxmo_hook_tailtextlog.sh";
|
||||
} // {
|
||||
# default hooks for this nix module, not upstreamable
|
||||
"sxmo_hook_block_suspend.sh" = "${hookPkgs.block_suspend}/bin/sxmo_hook_block_suspend.sh";
|
||||
"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_screenoff.sh" = "${hookPkgs.screenoff}/bin/sxmo_hook_screenoff.sh";
|
||||
"sxmo_hook_start.sh" = "${hookPkgs.start}/bin/sxmo_hook_start.sh";
|
||||
"sxmo_suspend.sh" = "${hookPkgs.suspend}/bin/sxmo_suspend.sh";
|
||||
};
|
||||
@@ -235,10 +234,9 @@ in
|
||||
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";
|
||||
SXMO_NO_AUDIO = mkSettingsOpt "" "don't start pipewire/pulseaudio in sxmo_hook_start.sh, don't show audio in statusbar, disable audio menu";
|
||||
SXMO_NO_AUDIO = mkSettingsOpt "1" "don't start pipewire/pulseaudio in sxmo_hook_start.sh";
|
||||
SXMO_STATES = mkSettingsOpt "unlock screenoff" "list of states the device should support (unlock, lock, screenoff)";
|
||||
SXMO_SWAY_SCALE = mkSettingsOpt "1" "sway output scale";
|
||||
SXMO_WOB_DISABLE = mkSettingsOpt "" "disable the on-screen volume display";
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
@@ -295,7 +293,7 @@ in
|
||||
enable = true;
|
||||
# we manage the greeter ourselves (TODO: merge this into sway config as well)
|
||||
useGreeter = false;
|
||||
waybar.top = import ./waybar-top.nix { inherit pkgs; };
|
||||
waybar.top = import ./waybar-top.nix;
|
||||
# reset extra waybar style
|
||||
waybar.extra_style = "";
|
||||
config = {
|
||||
@@ -309,12 +307,7 @@ in
|
||||
# these could be added, but i don't see much benefit.
|
||||
font = "pango:monospace 10";
|
||||
mod = "Mod1"; # prefer Alt
|
||||
# about xwayland:
|
||||
# - required by many electron apps, though some electron apps support NIXOS_OZONE_WL=1 for native wayland.
|
||||
# - when xwayland is enabled, KOreader incorrectly chooses the X11 backend
|
||||
# -> slower; blurrier
|
||||
# - xwayland uses a small amount of memory (like 30MiB, IIRC?)
|
||||
xwayland = false;
|
||||
# xwayland = false; # disable to reduce RAM usage. N.B.: xwayland is needed for electron apps!
|
||||
workspace_layout = "tabbed";
|
||||
|
||||
brightness_down_cmd = "sxmo_brightness.sh down";
|
||||
@@ -385,12 +378,7 @@ in
|
||||
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'
|
||||
|
||||
# XXX(2023/12/04): this shouldn't be necessary, but without this Komikku fails to launch because XDG_SESSION_TYPE is unset
|
||||
|
||||
exec dbus-update-activation-environment --systemd XDG_SESSION_TYPE
|
||||
exec_always ${sxmo_init}
|
||||
'';
|
||||
};
|
||||
@@ -449,76 +437,57 @@ in
|
||||
event_name = eventName;
|
||||
inherit transitions;
|
||||
};
|
||||
friendlyToBonsai = { trigger ? null, terminal ? false, timeout ? {}, power_pressed ? {}, power_released ? {}, voldown_pressed ? {}, voldown_released ? {}, volup_pressed ? {}, volup_released ? {} }@args:
|
||||
friendlyToBonsai = { timeout ? null, trigger ? null, power_pressed ? {}, power_released ? {}, voldown_pressed ? {}, voldown_released ? {}, volup_pressed ? {}, volup_released ? {} }@args:
|
||||
if trigger != null then [
|
||||
(doExec trigger (friendlyToBonsai (builtins.removeAttrs args ["trigger"])))
|
||||
] else let
|
||||
events = [ ]
|
||||
++ (lib.optional (timeout != {}) (onDelay (timeout.ms or 400) (friendlyToBonsai (builtins.removeAttrs timeout ["ms"]))))
|
||||
++ (lib.optional (power_pressed != {}) (onEvent "power_pressed" (friendlyToBonsai power_pressed)))
|
||||
++ (lib.optional (power_released != {}) (onEvent "power_released" (friendlyToBonsai power_released)))
|
||||
++ (lib.optional (voldown_pressed != {}) (onEvent "voldown_pressed" (friendlyToBonsai voldown_pressed)))
|
||||
++ (lib.optional (voldown_released != {}) (onEvent "voldown_released" (friendlyToBonsai voldown_released)))
|
||||
++ (lib.optional (volup_pressed != {}) (onEvent "volup_pressed" (friendlyToBonsai volup_pressed)))
|
||||
++ (lib.optional (volup_released != {}) (onEvent "volup_released" (friendlyToBonsai volup_released)))
|
||||
;
|
||||
in assert terminal -> events == []; events;
|
||||
] else [
|
||||
(lib.mkIf (timeout != null) (onDelay (timeout.ms or 400) (friendlyToBonsai (builtins.removeAttrs timeout ["ms"]))))
|
||||
(lib.mkIf (power_pressed != {}) (onEvent "power_pressed" (friendlyToBonsai power_pressed)))
|
||||
(lib.mkIf (power_released != {}) (onEvent "power_released" (friendlyToBonsai power_released)))
|
||||
(lib.mkIf (voldown_pressed != {}) (onEvent "voldown_pressed" (friendlyToBonsai voldown_pressed)))
|
||||
(lib.mkIf (voldown_released != {}) (onEvent "voldown_released" (friendlyToBonsai voldown_released)))
|
||||
(lib.mkIf (volup_pressed != {}) (onEvent "volup_pressed" (friendlyToBonsai volup_pressed)))
|
||||
(lib.mkIf (volup_released != {}) (onEvent "volup_released" (friendlyToBonsai volup_released)))
|
||||
];
|
||||
recurseVolUpDown = ttl: if ttl == 0 then {
|
||||
} else {
|
||||
voldown_pressed = {
|
||||
trigger = "powerhold_voldown";
|
||||
timeout.ms = 1000;
|
||||
power_released = {};
|
||||
} // recurseVolUpDown (ttl - 1);
|
||||
|
||||
# trigger ${button}_hold_N every `holdTime` ms until ${button} is released
|
||||
recurseHold = button: { count ? 1, maxHolds ? 5, prefix ? "", holdTime ? 600, ... }@opts: lib.optionalAttrs (count <= maxHolds) {
|
||||
"${button}_released".terminal = true; # end the hold -> back to root state
|
||||
timeout = {
|
||||
ms = holdTime;
|
||||
trigger = "${prefix}${button}_hold_${builtins.toString count}";
|
||||
} // (recurseHold button (opts // { count = count+1; }));
|
||||
};
|
||||
|
||||
# trigger volup_tap_N or voldown_tap_N on every tap.
|
||||
# if a volume button is held, then switch into `recurseHold`'s handling instead
|
||||
volumeActions = { count ? 1, maxTaps ? 5, prefix ? "", timeout ? 600, ... }@opts: lib.optionalAttrs (count != maxTaps) {
|
||||
volup_pressed = (recurseHold "volup" opts) // {
|
||||
volup_released = {
|
||||
trigger = "${prefix}volup_tap_${builtins.toString count}";
|
||||
timeout.ms = timeout;
|
||||
} // (volumeActions (opts // { count = count+1; }));
|
||||
};
|
||||
voldown_pressed = (recurseHold "voldown" opts) // {
|
||||
voldown_released = {
|
||||
trigger = "${prefix}voldown_tap_${builtins.toString count}";
|
||||
timeout.ms = timeout;
|
||||
} // (volumeActions (opts // { count = count+1; }));
|
||||
};
|
||||
volup_pressed = {
|
||||
trigger = "powerhold_volup";
|
||||
timeout.ms = 1000;
|
||||
power_released = {};
|
||||
} // recurseVolUpDown (ttl - 1);
|
||||
};
|
||||
in friendlyToBonsai {
|
||||
# map sequences of "events" to an argument to pass to sxmo_hook_inputhandler.sh
|
||||
|
||||
# map: power (short), power (short) x2, power (long)
|
||||
power_pressed.timeout.ms = 900; # press w/o release. this is a long timeout because it's tied to the "kill window" action.
|
||||
# tap the power button N times to trigger N different actions
|
||||
power_pressed.timeout.ms = 1200; # press w/o release. this is a long timeout because it's tied to the "kill window" action.
|
||||
power_pressed.timeout.trigger = "powerhold";
|
||||
power_pressed.power_released.timeout.trigger = "powerbutton_one";
|
||||
power_pressed.power_released.timeout.ms = 300;
|
||||
power_pressed.power_released.timeout.ms = 600; # long timeout to make `powertoggle_*` easier
|
||||
power_pressed.power_released.power_pressed.trigger = "powerbutton_two";
|
||||
|
||||
# map: volume taps and holds
|
||||
volup_pressed = (recurseHold "volup" {}) // {
|
||||
# this either becomes volup_hold_* (via recurseHold, above) or:
|
||||
# - a short volup_tap_1 followed by:
|
||||
# - a *finalized* volup_1 (i.e. end of action)
|
||||
# - more taps/holds, in which case we prefix it with `modal_<action>`
|
||||
# to denote that we very explicitly entered this state.
|
||||
#
|
||||
# it's clunky: i do it this way so that voldown can map to keyboard/terminal in unlock mode
|
||||
# but trigger media controls in screenoff
|
||||
# in a way which *still* allows media controls if explicitly entered into via a tap on volup first
|
||||
volup_released = (volumeActions { prefix = "modal_"; }) // {
|
||||
trigger = "volup_tap_1";
|
||||
timeout.ms = 300;
|
||||
timeout.trigger = "volup_1";
|
||||
};
|
||||
};
|
||||
voldown_pressed = (volumeActions {}).voldown_pressed // {
|
||||
trigger = "voldown_start";
|
||||
};
|
||||
# power_pressed.power_released.power_released.timeout.trigger = "powerbutton_two";
|
||||
# power_pressed.power_released.power_released.power_released.trigger = "powerbutton_three";
|
||||
|
||||
# tap power, then tap up/down after releasing it
|
||||
power_pressed.power_released.voldown_pressed.trigger = "powertoggle_voldown";
|
||||
power_pressed.power_released.volup_pressed.trigger = "powertoggle_volup";
|
||||
|
||||
# chording: hold power and then tap vol-up N times to adjust the volume by N increments.
|
||||
# XXX: HOLDING POWER LIKE THIS IS RISKY. but the default hard-power-off is like 10s, so... i guess this works until it becomes a problem...?
|
||||
power_pressed.voldown_pressed = (recurseVolUpDown 5).voldown_pressed;
|
||||
power_pressed.volup_pressed = (recurseVolUpDown 5).volup_pressed;
|
||||
|
||||
# tap just one of the volume buttons.
|
||||
voldown_pressed.trigger = "voldown_one";
|
||||
volup_pressed.trigger = "volup_one";
|
||||
};
|
||||
|
||||
# sxmo puts in /share/sxmo:
|
||||
@@ -528,6 +497,16 @@ in
|
||||
# - 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 = "${package}/bin/sxmo_setpermissions.sh";
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
# if superd fails to start a service within 100ms, it'll try to start again
|
||||
# the fallout of this is that during intense lag (e.g. OOM or swapping) it can
|
||||
# start the service many times.
|
||||
@@ -604,7 +583,11 @@ in
|
||||
];
|
||||
|
||||
sane.user.services = let
|
||||
sxmoPath = [ package ] ++ package.runtimeDeps;
|
||||
sxmoPath = [
|
||||
"/etc/profiles/per-user/colin" # so as to launch user-enabled applications (like g4music, etc)
|
||||
"/run/wrappers" # for doas, and anything else suid
|
||||
"/run/current-system/sw" # for things installed system-wide, especially flock
|
||||
] ++ [ package ] ++ package.runtimeDeps;
|
||||
sxmoEnvSetup = ''
|
||||
# mimic my sxmo_init.sh a bit. refer to the actual sxmo_init.sh above for details.
|
||||
# the specific ordering, and the duplicated profile sourcing, matters.
|
||||
@@ -627,19 +610,16 @@ in
|
||||
serviceConfig.RestartSec = "20s";
|
||||
};
|
||||
in {
|
||||
# these are defined here, and started mostly in sxmo_hook_start.sh.
|
||||
# the ones commented our here are the ones i explicitly no longer use.
|
||||
# uncommenting them here *won't* cause them to be auto-started.
|
||||
sxmo_autosuspend = sxmoService "autosuspend";
|
||||
# sxmo_battery_monitor = sxmoService "battery_monitor";
|
||||
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_networkmonitor = sxmoService "networkmonitor";
|
||||
sxmo_notificationmonitor = sxmoService "notificationmonitor";
|
||||
# sxmo_soundmonitor = sxmoService "soundmonitor";
|
||||
# sxmo_wob = sxmoService "wob";
|
||||
sxmo_soundmonitor = sxmoService "soundmonitor";
|
||||
sxmo_wob = sxmoService "wob";
|
||||
sxmo-x11-status = sxmoService "status_xsetroot";
|
||||
|
||||
bonsaid.path = sxmoPath;
|
||||
|
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p procps
|
||||
|
||||
# Basic exponential backoff, this should save some resources if we're blocked by
|
||||
# the same thing for a while
|
||||
delay() {
|
||||
sleep "$delay_time"
|
||||
delay_time="$((delay_time*2))"
|
||||
if [ "$delay_time" -gt 45 ]; then
|
||||
delay_time=45
|
||||
fi
|
||||
}
|
||||
|
||||
wait_item() {
|
||||
delay_time=1
|
||||
while $1 > /dev/null 2>&1; do
|
||||
echo "Blocking suspend for $1"
|
||||
waited=1
|
||||
delay
|
||||
done
|
||||
}
|
||||
|
||||
##################################### below is original, not shared with upstream sxmo_hook_block_suspend.sh
|
||||
|
||||
casting_go2tv() {
|
||||
pgrep -f go2tv
|
||||
}
|
||||
|
||||
# forward to the next block_suspend.sh script (if any).
|
||||
# have to handle the case where this script is on PATH, and also when it was called without being on PATH.
|
||||
# the implementation here causes us to call ourselves exactly once, at most (or twice, if there are duplicate PATH entries)
|
||||
SXMO_HOOK_BLOCK_SUSPEND_DEPTH=$((${SXMO_HOOK_BLOCK_SUSPEND_DEPTH:-0} + 1))
|
||||
echo "recurse counter: $SXMO_HOOK_BLOCK_SUSPEND_DEPTH"
|
||||
|
||||
block_suspend_next=true
|
||||
FWD_COUNT=0
|
||||
IFS=:
|
||||
for p in $PATH ; do
|
||||
echo "testing: $p/sxmo_hook_block_suspend.sh"
|
||||
if $(test -x "$p/sxmo_hook_block_suspend.sh"); then
|
||||
FWD_COUNT=$(($FWD_COUNT + 1))
|
||||
fi
|
||||
if [ "$FWD_COUNT" -eq "$SXMO_HOOK_BLOCK_SUSPEND_DEPTH" ]; then
|
||||
block_suspend_next="$p/sxmo_hook_block_suspend.sh"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
while [ "$waited" != "0" ]; do
|
||||
waited=0
|
||||
|
||||
echo "forwarding to: $block_suspend_next"
|
||||
SXMO_HOOK_BLOCK_SUSPEND_DEPTH="$SXMO_HOOK_BLOCK_SUSPEND_DEPTH" "$block_suspend_next"
|
||||
|
||||
# wait for my own items last. else, we could wait for go2tv, then wait for internals, and in the meantime go2tv was re-spawned.
|
||||
wait_item casting_go2tv
|
||||
done
|
@@ -10,41 +10,40 @@
|
||||
# - 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
|
||||
#
|
||||
# example of a design which considers these things:
|
||||
# proposed future design:
|
||||
# - when unlocked:
|
||||
# - volup toggle -> app menu
|
||||
# - voldown press -> keyboard
|
||||
# - voldown hold -> terminal
|
||||
# - power x2 -> screenoff
|
||||
# - hold power -> kill app
|
||||
# - when locked:
|
||||
# - volup tap -> volume up
|
||||
# - volup hold -> media seek forward
|
||||
# - voldown tap -> volume down
|
||||
# - voldown hold -> media seek backward
|
||||
# - power x1 -> screen on
|
||||
# - power x2 -> play/pause media
|
||||
# some trickiness allows for media controls in unlocked mode:
|
||||
# - volup tap -> enter media mode
|
||||
# - i.e. in this state, vol tap/hold is mapped to volume/seek
|
||||
# - if, after entering media mode, no more taps occur, then we trigger the default app-menu action
|
||||
# limitations/downsides:
|
||||
# - power mappings means phone is artificially slow to unlock.
|
||||
# - media controls when unlocked have quirks:
|
||||
# - mashing voldown to decrease the volume will leave you with a toggled keyboard.
|
||||
# - seeking backward isn't possible except by first tapping volup.
|
||||
|
||||
# - volup-release -> app menu
|
||||
# - volup-hold -> WM menu
|
||||
# - voldown-release -> toggle keyboard
|
||||
# - voldown-hold -> terminal
|
||||
# - pow-volup xN -> volume up
|
||||
# - pow-voldown xN -> volume down
|
||||
# - pow-x2 -> screen off
|
||||
# - pow-hold -> kill app
|
||||
# - when screenoff:
|
||||
# - volup -> volume up
|
||||
# - voldown -> volume down
|
||||
# - pow-x1 -> screen on
|
||||
# - pow-x2 -> toggle player
|
||||
# - pow-volup -> seek +30s
|
||||
# - pow-voldown -> seek -10s
|
||||
# benefits:
|
||||
# - volup and voldown are able to be far more responsive
|
||||
# - which means faster vkbd, menus, volume adjustment (when locked)
|
||||
# - less mental load than the chording-based approach (where i hold power to adjust volume)
|
||||
# - less risk due to not chording the power button
|
||||
# drawbacks:
|
||||
# - volup/down actions are triggered by the release instead of the press; slight additional latency for pulling open the keyboard
|
||||
# - moving the WM menu into the top-level menu could allow keeping voldown free of complication
|
||||
|
||||
# increments to use for volume adjustment
|
||||
VOL_INCR=5
|
||||
VOL_INCR_1=5
|
||||
VOL_INCR_2=10
|
||||
|
||||
# replicating the naming from upstream sxmo_hook_inputhandler.sh...
|
||||
ACTION="$1"
|
||||
STATE=$(cat "$SXMO_STATE")
|
||||
|
||||
noop() {
|
||||
true
|
||||
}
|
||||
|
||||
handle_with() {
|
||||
echo "sxmo_hook_inputhandler.sh: STATE=$STATE ACTION=$ACTION: handle_with: $@"
|
||||
@@ -52,6 +51,18 @@ 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.sh set screenoff
|
||||
# *)
|
||||
# respond_with sxmo_state.sh set unlock
|
||||
# esac
|
||||
# }
|
||||
|
||||
# state is one of:
|
||||
# - "unlock" => normal operation; display on and touchscreen on
|
||||
@@ -68,35 +79,24 @@ if [ "$STATE" = "unlock" ]; then
|
||||
handle_with sxmo_killwindow.sh
|
||||
;;
|
||||
|
||||
"volup_tap_1")
|
||||
# swallow: this could be the start to a media control (multi taps / holds),
|
||||
# or it could be just a single tap -> release, handled next/below
|
||||
handle_with noop
|
||||
;;
|
||||
"volup_1")
|
||||
"volup_one")
|
||||
# volume up once: app-specific menu w/ fallback to SXMO system menu
|
||||
handle_with sxmo_appmenu.sh
|
||||
;;
|
||||
|
||||
"voldown_start")
|
||||
"voldown_one")
|
||||
# volume down once: toggle keyboard
|
||||
handle_with sxmo_keyboard.sh toggle
|
||||
;;
|
||||
"voldown_hold_2")
|
||||
# hold voldown to launch terminal
|
||||
# note we already triggered the keyboard; that's fine: usually keyboard + terminal go together :)
|
||||
# voldown_hold_1 frequently triggers during short taps meant only to reveal the keyboard,
|
||||
# so prefer a longer hold duration
|
||||
|
||||
"powertoggle_volup")
|
||||
# power -> volume up: DE menu
|
||||
handle_with sxmo_wmmenu.sh
|
||||
;;
|
||||
"powertoggle_voldown")
|
||||
# power -> volume down: launch terminal
|
||||
handle_with sxmo_terminal.sh
|
||||
;;
|
||||
"voldown_tap_1")
|
||||
# swallow, to prevent keyboard from also triggering media controls
|
||||
handle_with noop
|
||||
;;
|
||||
voldown_hold_*)
|
||||
# swallow, to prevent terminal from also triggering media controls
|
||||
handle_with noop
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -110,6 +110,14 @@ if [ "$STATE" = "screenoff" ]; then
|
||||
# power toggle during deep sleep often gets misread as power hold, so treat same
|
||||
handle_with sxmo_state.sh set unlock
|
||||
;;
|
||||
"powertoggle_volup"|"powerhold_volup")
|
||||
# power -> volume up: seek forward
|
||||
handle_with playerctl position 30+
|
||||
;;
|
||||
"powertoggle_voldown"|"powerhold_voldown")
|
||||
# power -> volume down: seek backward
|
||||
handle_with playerctl position 10-
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -125,20 +133,21 @@ case "$ACTION" in
|
||||
;;
|
||||
# powerbutton_three: intentional no-op because overloading the kill-window handler is risky
|
||||
|
||||
volup_tap*|modal_volup_tap*)
|
||||
handle_with pactl set-sink-volume @DEFAULT_SINK@ +"$VOL_INCR%"
|
||||
"volup_one")
|
||||
handle_with pactl set-sink-volume @DEFAULT_SINK@ +"$VOL_INCR_1%"
|
||||
;;
|
||||
voldown_tap*|modal_voldown_tap*)
|
||||
handle_with pactl set-sink-volume @DEFAULT_SINK@ -"$VOL_INCR%"
|
||||
"voldown_one")
|
||||
handle_with pactl set-sink-volume @DEFAULT_SINK@ -"$VOL_INCR_1%"
|
||||
;;
|
||||
|
||||
volup_hold*|modal_volup_hold*)
|
||||
handle_with playerctl position 30+
|
||||
# HOLD power button and tap volup/down to adjust volume
|
||||
"powerhold_volup")
|
||||
handle_with pactl set-sink-volume @DEFAULT_SINK@ +"$VOL_INCR_1%"
|
||||
;;
|
||||
voldown_hold*|modal_voldown_hold*)
|
||||
handle_with playerctl position 10-
|
||||
"powerhold_voldown")
|
||||
handle_with pactl set-sink-volume @DEFAULT_SINK@ -"$VOL_INCR_1%"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
handle_with noop
|
||||
handle_with echo "no-op"
|
||||
|
22
hosts/modules/gui/sxmo/hooks/sxmo_hook_screenoff.sh
Executable file
22
hosts/modules/gui/sxmo/hooks/sxmo_hook_screenoff.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p sway
|
||||
|
||||
# this hook is mostly identical to default sxmo_hook_screenoff.sh except:
|
||||
# - the LED frequency is adjusted from its default of "blink every 2s"
|
||||
# - dwm-specific bits are removed
|
||||
|
||||
BLINK_FREQ=8
|
||||
|
||||
swaymsg mode default
|
||||
sxmo_wm.sh dpms on
|
||||
sxmo_wm.sh inputevent touchscreen off
|
||||
|
||||
sxmo_jobs.sh start periodic_blink sxmo_run_periodically.sh "$BLINK_FREQ" sxmo_led.sh blink red blue
|
||||
|
||||
wait
|
||||
|
||||
# avoid immediate suspension. particularly, ensure that we get at least one blink in
|
||||
sxmo_wakelock.sh lock sxmo_hold_a_bit "$BLINK_FREQ"s
|
||||
sxmo_wakelock.sh unlock sxmo_not_screenoff
|
||||
|
||||
|
@@ -10,15 +10,14 @@ xdg-user-dirs-update
|
||||
sxmo_jobs.sh start daemon_manager
|
||||
|
||||
# Periodically update some status bar components
|
||||
# don't: statusbar is managed by waybar
|
||||
# sxmo_hook_statusbar.sh all
|
||||
# sxmo_jobs.sh start statusbar_periodics sxmo_run_aligned.sh 60 \
|
||||
# sxmo_hook_statusbar.sh periodics
|
||||
sxmo_hook_statusbar.sh all
|
||||
sxmo_jobs.sh start statusbar_periodics sxmo_run_aligned.sh 60 \
|
||||
sxmo_hook_statusbar.sh periodics
|
||||
|
||||
# TODO: start these externally, via `wantedBy` in nix
|
||||
# don't: i don't use mako
|
||||
# don't: mako is managed externally
|
||||
# superctl start mako
|
||||
# systemctl --user start sxmo_wob
|
||||
systemctl --user start sxmo_wob
|
||||
systemctl --user start sxmo_menumode_toggler
|
||||
systemctl --user start bonsaid
|
||||
# don't: sway background is managed externally
|
||||
@@ -50,22 +49,16 @@ fi
|
||||
# superctl start sxmo_conky
|
||||
|
||||
# Monitor the battery
|
||||
# don't: this is *exclusively* for sxmo_hook_statusbar.sh, which i don't use.
|
||||
# systemctl --user start sxmo_battery_monitor
|
||||
systemctl --user start sxmo_battery_monitor
|
||||
|
||||
# It watch network changes and update the status bar icon by example
|
||||
# don't: this is for sxmo_hook_statusbar.sh, which i don't use.
|
||||
# this means we never call sxmo_hook_network_{up,down,...}, but the defaults are no-op anyway
|
||||
# systemctl --user start sxmo_networkmonitor
|
||||
systemctl --user start sxmo_networkmonitor
|
||||
|
||||
# The daemon that display notifications popup messages
|
||||
# more importantly: it lights the led green when a notification arrives
|
||||
systemctl --user start sxmo_notificationmonitor
|
||||
|
||||
# monitor for headphone for statusbar
|
||||
# this also invokes `wob` whenever the volume is changed
|
||||
# don't: my volume monitoring is handled by sway
|
||||
# systemctl --user start sxmo_soundmonitor
|
||||
systemctl --user start sxmo_soundmonitor
|
||||
|
||||
# rotate UI based on physical display angle by default
|
||||
if [ -n "$SXMO_AUTOROTATE" ]; then
|
||||
|
@@ -30,22 +30,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
NTFY_HOST = 'uninsane.org'
|
||||
NTFY_PORT_BASE = 5550
|
||||
|
||||
# duration in seconds to sleep for
|
||||
SUSPEND_TIME = 300
|
||||
SUSPEND_TIME=300
|
||||
# take care that WOWLAN_DELAY might include more than you think (e.g. time spent configuring wowlan pattern rules)
|
||||
WOWLAN_DELAY = 6
|
||||
|
||||
# SXMO LED blink frequency to set on resume
|
||||
BLINK_FREQ = 5
|
||||
WOWLAN_DELAY=6
|
||||
|
||||
class Executor:
|
||||
def __init__(self, dry_run: bool = False):
|
||||
self.dry_run = dry_run
|
||||
|
||||
def exec(self, cmd: list[str], sudo: bool = False, check: bool = True, wait: bool = True):
|
||||
if check: assert wait, "can't check_output without first waiting for process completion"
|
||||
|
||||
def exec(self, cmd: list[str], sudo: bool = False, check: bool = True):
|
||||
if sudo:
|
||||
cmd = [ 'doas' ] + cmd
|
||||
|
||||
@@ -53,7 +46,6 @@ class Executor:
|
||||
if self.dry_run:
|
||||
return
|
||||
|
||||
if wait:
|
||||
try:
|
||||
res = subprocess.run(cmd, capture_output=True)
|
||||
except Exception as e:
|
||||
@@ -67,11 +59,6 @@ class Executor:
|
||||
if check:
|
||||
res.check_returncode()
|
||||
|
||||
else:
|
||||
res = subprocess.Popen(cmd)
|
||||
|
||||
return res
|
||||
|
||||
def try_connect(self, dest, delay):
|
||||
logger.debug(f"opening socket to {dest} with timeout {delay}")
|
||||
if self.dry_run:
|
||||
@@ -137,33 +124,13 @@ class Suspender:
|
||||
def suspend(self, duration: int, mode: str):
|
||||
logger.info(f"calling suspend for duration: {duration}")
|
||||
if mode == 'rtcwake':
|
||||
self.executor.exec(['rtcwake', '-m', 'mem', '-s', str(duration)], sudo=True, check=False)
|
||||
self.executor.exec(['rtcwake', '-m', 'mem', '-s', str(duration)], check=False)
|
||||
elif mode == 'sleep':
|
||||
time.sleep(duration)
|
||||
else:
|
||||
assert False, f"unknown suspend mode: {mode}"
|
||||
|
||||
|
||||
class SxmoApi:
|
||||
def __init__(self, executor: Executor):
|
||||
self.executor = executor
|
||||
|
||||
def halt_services(self) -> None:
|
||||
res = self.executor.exec(['sxmo_jobs.sh', 'running', 'periodic_blink'], check=False)
|
||||
self.was_blinking = res and res.returncode == 0
|
||||
if self.was_blinking:
|
||||
self.executor.exec(['sxmo_jobs.sh', 'stop', 'periodic_blink'], check=False)
|
||||
|
||||
def resume_services(self) -> None:
|
||||
if self.was_blinking:
|
||||
# XXX: sxmo_jobs.sh is supposed to run the job in the background, but somehow it fails (blocks), only when invoked from Python.
|
||||
# oh well, just call it asynchronously (wait=False)
|
||||
self.executor.exec(['sxmo_jobs.sh', 'start', 'periodic_blink', 'sxmo_run_periodically.sh', str(BLINK_FREQ), 'sxmo_led.sh', 'blink', 'red', 'blue'], check=False, wait=False)
|
||||
|
||||
def call_postwake_hook(self) -> None:
|
||||
self.executor.exec(['sxmo_hook_postwake.sh'], check=False)
|
||||
|
||||
|
||||
def main():
|
||||
logging.basicConfig()
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
@@ -185,9 +152,7 @@ def main():
|
||||
suspend_mode = args.suspend_mode
|
||||
executor = Executor(dry_run=args.dry_run)
|
||||
suspender = Suspender(executor, wowlan_delay=wowlan_delay)
|
||||
sxmo_api = SxmoApi(executor)
|
||||
|
||||
sxmo_api.halt_services()
|
||||
suspender.open_ntfy_stream()
|
||||
suspender.configure_wowlan()
|
||||
|
||||
@@ -200,8 +165,7 @@ def main():
|
||||
logger.info(f"suspended for {time_spent:.0f} seconds")
|
||||
|
||||
suspender.close_ntfy_stream()
|
||||
sxmo_api.resume_services()
|
||||
sxmo_api.call_postwake_hook()
|
||||
executor.exec(['sxmo_hook_postwake.sh'], check=False)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@@ -1,122 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p sxmo-utils -p sxmo-utils.runtimeDeps
|
||||
#
|
||||
# usage:
|
||||
# waybar-sxmo-status widget1 [ widget2 [...]]
|
||||
#
|
||||
# where each widget is one of:
|
||||
# - modem-state
|
||||
# - modem-tech
|
||||
# - modem-signal
|
||||
# - wifi-status
|
||||
# - volume
|
||||
|
||||
# sxmo_hook_statusbar.sh assumes:
|
||||
# - mmcli, jq on PATH
|
||||
# - sxmo_hook_icons.sh and sxmo_common.sh are sourcable
|
||||
# - from sxmo_common, it only uses sxmobar (and aliases jq=gojq)
|
||||
|
||||
# setup environment so that the hooks will be on PATH:
|
||||
# - sxmo_hook_statusbar.sh
|
||||
# - sxmo_hook_icons.sh
|
||||
export HOME="${HOME:-/home/colin}"
|
||||
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
export PATH="$XDG_CONFIG_HOME/sxmo/hooks:$PATH"
|
||||
|
||||
# ensure that sxmo_audio.sh tells us the volume instead of early-returning
|
||||
export SXMO_NO_AUDIO=
|
||||
|
||||
# clunky interaction between us and sxmo_hook_statusbar.sh:
|
||||
# - we export `sxmobar` to it, but within that function cannot modify the environment
|
||||
# of *this* script, because it gets run in a different process.
|
||||
# - so, `sxmobar` prints info to stdout, and then this script re-interprets that info.
|
||||
# - practically, `sxmobar` prints shell commands, and then this script `eval`s them, to achieve that IPC.
|
||||
sxmobar() {
|
||||
action="$1"
|
||||
shift
|
||||
if [ "$action" = "-a" ]; then
|
||||
while [ -n "$*" ]; do
|
||||
arg="$1"
|
||||
case "$arg" in
|
||||
"-f"|"-b"|"-t"|"-e")
|
||||
# foreground/background/text/emphasis: ignore it
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
# begin arguments
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "setitem $@"
|
||||
fi
|
||||
}
|
||||
export -f sxmobar
|
||||
|
||||
setitem() {
|
||||
id="$1"
|
||||
priority="$2"
|
||||
value="$3"
|
||||
case "$id" in
|
||||
modem-state)
|
||||
modem_state="$value"
|
||||
;;
|
||||
modem-tech)
|
||||
modem_tech="$value"
|
||||
;;
|
||||
modem-signal)
|
||||
modem_signal="$value"
|
||||
;;
|
||||
wifi-status)
|
||||
wifi_status="$value"
|
||||
;;
|
||||
volume)
|
||||
volume="$value"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
while [ -n "$*" ]; do
|
||||
variable="$1"
|
||||
shift
|
||||
case "$variable" in
|
||||
"--verbose")
|
||||
set -x
|
||||
;;
|
||||
"modem-state")
|
||||
if [ -z "$modem_state" ]; then
|
||||
eval "$(sxmo_hook_statusbar.sh modem)"
|
||||
fi
|
||||
echo -n "$modem_state"
|
||||
;;
|
||||
"modem-tech")
|
||||
if [ -z "$modem_tech" ]; then
|
||||
eval "$(sxmo_hook_statusbar.sh modem)"
|
||||
fi
|
||||
echo -n "$modem_tech"
|
||||
;;
|
||||
"modem-signal")
|
||||
if [ -z "$modem_signal" ]; then
|
||||
eval "$(sxmo_hook_statusbar.sh modem)"
|
||||
fi
|
||||
echo -n "$modem_signal"
|
||||
;;
|
||||
"wifi-status")
|
||||
if [ -z "$wifi_status" ]; then
|
||||
eval "$(sxmo_hook_statusbar.sh network wifi wlan0)"
|
||||
fi
|
||||
echo -n "$wifi_status"
|
||||
;;
|
||||
"volume")
|
||||
if [ -z "$volume" ]; then
|
||||
eval "$(sxmo_hook_statusbar.sh volume)"
|
||||
fi
|
||||
echo -n "$volume"
|
||||
;;
|
||||
*)
|
||||
echo -n "UNK: $variable"
|
||||
;;
|
||||
esac
|
||||
done
|
@@ -1,30 +1,12 @@
|
||||
# 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
|
||||
{ pkgs }:
|
||||
let
|
||||
waybar-sxmo-status = pkgs.static-nix-shell.mkBash {
|
||||
pname = "waybar-sxmo-status";
|
||||
src = ./.;
|
||||
pkgs = [ "sxmo-utils" "sxmo-utils.runtimeDeps" ];
|
||||
};
|
||||
in
|
||||
{
|
||||
height = 26;
|
||||
|
||||
modules-left = [ "sway/workspaces" ];
|
||||
modules-center = [ ];
|
||||
modules-right = [
|
||||
"custom/swaync"
|
||||
"clock"
|
||||
"battery"
|
||||
"custom/sxmo-sane"
|
||||
# "custom/sxmo"
|
||||
# "custom/sxmo/modem-state"
|
||||
# "custom/sxmo/modem-tech"
|
||||
# "custom/sxmo/modem-signal"
|
||||
# "custom/sxmo/wifi"
|
||||
];
|
||||
modules-right = [ "custom/swaync" "custom/sxmo" ];
|
||||
|
||||
"sway/workspaces" = {
|
||||
all-outputs = true;
|
||||
@@ -38,56 +20,12 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
"custom/sxmo-sane" = {
|
||||
# this calls all the SXMO indicators, inline.
|
||||
# so it works even without the "statusbar periodics" sxmo service running.
|
||||
interval = 2;
|
||||
format = "{}";
|
||||
exec = "${waybar-sxmo-status}/bin/waybar-sxmo-status modem-state modem-tech modem-signal wifi-status volume";
|
||||
};
|
||||
|
||||
"custom/sxmo" = {
|
||||
# this gives wifi state, battery, mic/speaker, lockstate, time all as one widget.
|
||||
# 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.
|
||||
# the values for this bar are computed in sxmo:configs/default_hooks/sxmo_hook_statusbar.sh
|
||||
# e.g. distinct vol-up & vol-down buttons next to the speaker?
|
||||
exec = "sxmo_status.sh";
|
||||
interval = 1;
|
||||
format = "{}";
|
||||
};
|
||||
# not ported: battery, ethernet
|
||||
"custom/sxmo/modem-state" = {
|
||||
exec = "cat /run/user/1000/sxmo_status/default/10-modem-state";
|
||||
interval = 2;
|
||||
format = "{}";
|
||||
};
|
||||
"custom/sxmo/modem-tech" = {
|
||||
exec = "cat /run/user/1000/sxmo_status/default/11-modem-tech";
|
||||
interval = 2;
|
||||
format = "{}";
|
||||
};
|
||||
"custom/sxmo/modem-signal" = {
|
||||
exec = "cat /run/user/1000/sxmo_status/default/12-modem-signal";
|
||||
interval = 2;
|
||||
format = "{}";
|
||||
};
|
||||
"custom/sxmo/wifi" = {
|
||||
exec = "cat /run/user/1000/sxmo_status/default/30-wifi-status";
|
||||
interval = 2;
|
||||
format = "{}";
|
||||
};
|
||||
"custom/sxmo/volume" = {
|
||||
exec = "cat /run/user/1000/sxmo_status/default/50-volume";
|
||||
interval = 2;
|
||||
format = "{}";
|
||||
};
|
||||
"custom/sxmo/state" = {
|
||||
exec = "cat /run/user/1000/sxmo_status/default/90-state";
|
||||
interval = 2;
|
||||
format = "{}";
|
||||
};
|
||||
"custom/sxmo/time" = {
|
||||
exec = "cat /run/user/1000/sxmo_status/default/99-time";
|
||||
interval = 2;
|
||||
format = "{}";
|
||||
};
|
||||
}
|
||||
|
@@ -94,7 +94,6 @@ in
|
||||
speedFactor = 2;
|
||||
supportedFeatures = [
|
||||
# "big-parallel" # it can't reliably build webkitgtk
|
||||
"no-binfmt"
|
||||
];
|
||||
mandatoryFeatures = [ ];
|
||||
sshUser = "nixremote";
|
||||
|
@@ -13,7 +13,7 @@ in
|
||||
};
|
||||
emulation = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
default = true;
|
||||
};
|
||||
ccache = mkOption {
|
||||
type = types.bool;
|
||||
@@ -35,8 +35,6 @@ in
|
||||
# it's nice to not be limited in that way, so increase this a bit.
|
||||
nix.nrBuildUsers = 64;
|
||||
|
||||
nix.settings.system-features = [ "big-parallel" ];
|
||||
|
||||
# enable cross compilation
|
||||
# TODO: do this via stdenv injection, linking into /run/binfmt the stuff in <nixpkgs:nixos/modules/system/boot/binfmt.nix>
|
||||
boot.binfmt.emulatedSystems = lib.optionals cfg.emulation [
|
||||
|
@@ -10,7 +10,6 @@
|
||||
|
||||
config = lib.mkIf config.sane.roles.handheld {
|
||||
sane.programs.guiApps.suggestedPrograms = [
|
||||
"consoleMediaUtils" # overbroad, but handy on very rare occasion
|
||||
"handheldGuiApps"
|
||||
];
|
||||
};
|
||||
|
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "A podcast for potential-pursuing optimists, for doers who want to make a dent in the universe, for those interested in crypto, longevity, city-building, etc., and for anyone who loves learning the stories of extraordinary entrepreneurs.",
|
||||
"is_podcast": true,
|
||||
"site_name": "",
|
||||
"site_url": "",
|
||||
"title": "POD OF JAKE",
|
||||
"url": "https://anchor.fm/s/2da69154/podcast/rss",
|
||||
"velocity": 0.124
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "About the modern financial infrastructure that the world sits atop of.",
|
||||
"is_podcast": false,
|
||||
"site_name": "Bits about Money",
|
||||
"site_url": "https://www.bitsaboutmoney.com",
|
||||
"title": "Bits about Money",
|
||||
"url": "https://www.bitsaboutmoney.com/archive/rss/",
|
||||
"velocity": 0.041
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "Recent content on The KosmosGhost Site",
|
||||
"is_podcast": false,
|
||||
"site_name": "The KosmosGhost Site",
|
||||
"site_url": "https://kosmosghost.github.io",
|
||||
"title": "The KosmosGhost Site",
|
||||
"url": "https://kosmosghost.github.io/index.xml",
|
||||
"velocity": 0.0
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "LINMOB.net is a blog about LINux on MOBile devices. With the PinePhone (Pro) and Librem 5 shipping it is back to report on GNU+Linux on mobile devices.",
|
||||
"is_podcast": false,
|
||||
"site_name": "LINux on MOBile",
|
||||
"site_url": "https://linmob.net",
|
||||
"title": "LINux on MOBile",
|
||||
"url": "https://linmob.net/feed.xml",
|
||||
"velocity": 0.176
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "<p>Comedians Dave Anthony and Gareth Reynolds picks a subject from history and examine it.</p>",
|
||||
"is_podcast": true,
|
||||
"site_name": "",
|
||||
"site_url": "",
|
||||
"title": "The Dollop with Dave Anthony and Gareth Reynolds",
|
||||
"url": "https://www.omnycontent.com/d/playlist/885ace83-027a-47ad-ad67-aca7002f1df8/22b063ac-654d-428f-bd69-ae2400349cde/65ff0206-b585-4e2a-9872-ae240034c9c9/podcast.rss",
|
||||
"velocity": 0.188
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user