Compare commits

..

2 Commits

302 changed files with 2518 additions and 5822 deletions

View File

@@ -2,8 +2,6 @@
# .❄≡We|_c0m3 7o m`/ f14k≡❄. # .❄≡We|_c0m3 7o m`/ f14k≡❄.
(er, it's not a flake anymore. welcome to my nix files.)
## What's Here ## What's Here
this is the top-level repo from which i configure/deploy all my NixOS machines: this is the top-level repo from which i configure/deploy all my NixOS machines:
@@ -31,7 +29,11 @@ you might specifically be interested in these files (elaborated further in #key-
## Using This Repo In Your Own Config ## Using This Repo In Your Own Config
follow the instructions [here][NUR] to access my packages through the Nix User Repositories. this should be a pretty "standard" flake. just reference it, and import either
- `nixosModules.sane` (for the modules)
- `overlays.pkgs` (for the packages)
or follow the instructions [here][NUR] to use it via the Nix User Repositories.
[NUR]: https://nur.nix-community.org/ [NUR]: https://nur.nix-community.org/
@@ -39,15 +41,19 @@ follow the instructions [here][NUR] to access my packages through the Nix User R
- `doc/` - `doc/`
- instructions for tasks i find myself doing semi-occasionally in this repo. - instructions for tasks i find myself doing semi-occasionally in this repo.
- `hosts/` - `hosts/`
- configs which aren't factored with external use in mind. - the bulk of config which isn't factored with external use in mind.
- that is, if you were to add this repo to a flake.nix for your own use, - that is, if you were to add this repo to a flake.nix for your own use,
you won't likely be depending on anything in this directory. you won't likely be depending on anything in this directory.
- `integrations/` - `integrations/`
- code intended for consumption by external tools (e.g. the Nix User Repos). - code intended for consumption by external tools (e.g. the Nix User Repos)
- `modules/` - `modules/`
- config which is gated behind `enable` flags, in similar style to nixpkgs' `nixos/` directory. - config which is gated behind `enable` flags, in similar style to nixpkgs'
- if you depend on this repo for anything besides packages, it's most likely for something in this directory. `nixos/` directory.
- if you depend on this repo, it's most likely for something in this directory.
- `nixpatches/`
- literally, diffs i apply atop upstream nixpkgs before performing further eval.
- `overlays/` - `overlays/`
- exposed via the `overlays` output in `flake.nix`.
- predominantly a list of `callPackage` directives. - predominantly a list of `callPackage` directives.
- `pkgs/` - `pkgs/`
- derivations for things not yet packaged in nixpkgs. - derivations for things not yet packaged in nixpkgs.
@@ -55,12 +61,13 @@ follow the instructions [here][NUR] to access my packages through the Nix User R
- inline code for wholly custom packages (e.g. `pkgs/additional/sane-scripts/` for CLI tools - inline code for wholly custom packages (e.g. `pkgs/additional/sane-scripts/` for CLI tools
that are highly specific to my setup). that are highly specific to my setup).
- `scripts/` - `scripts/`
- scripts which aren't reachable on a deployed system, but may aid manual deployments. - scripts which aren't reachable on a deployed system, but may aid manual deployments
- `secrets/` - `secrets/`
- encrypted keys, API tokens, anything which one or more of my machines needs - encrypted keys, API tokens, anything which one or more of my machines needs
read access to but shouldn't be world-readable. read access to but shouldn't be world-readable.
- not much to see here. - not much to see here
- `templates/` - `templates/`
- exposed via the `templates` output in `flake.nix`.
- used to instantiate short-lived environments. - used to instantiate short-lived environments.
- used to auto-fill the boiler-plate portions of new packages. - used to auto-fill the boiler-plate portions of new packages.

43
TODO.md
View File

@@ -2,18 +2,21 @@
- `rmDbusServices` may break sandboxing - `rmDbusServices` may break sandboxing
- e.g. if the package ships a systemd unit which references $out, then make-sandboxed won't properly update that unit. - e.g. if the package ships a systemd unit which references $out, then make-sandboxed won't properly update that unit.
- `rmDbusServicesInPlace` is not affected - `rmDbusServicesInPlace` is not affected
- moby: touchscreen input is still enabled when screen is off
- when moby wlan is explicitly set down (via ip link set wlan0 down), /var/lib/trust-dns/dhcp-configs doesn't get reset - when moby wlan is explicitly set down (via ip link set wlan0 down), /var/lib/trust-dns/dhcp-configs doesn't get reset
- `ip monitor` can detect those manual link state changes (NM-dispatcher it seems cannot) - `ip monitor` can detect those manual link state changes (NM-dispatcher it seems cannot)
- or try dnsmasq? - or try dnsmasq?
- trust-dns: can't recursively resolve api.mangadex.org - trust-dns: can't recursively resolve api.mangadex.org
- nor `m.wikipedia.org` (`dyna.wikipedia.org`)
- and *sometimes* apple.com fails - and *sometimes* apple.com fails
- sandbox: link cache means that if i update ~/.config/... files inline, sandboxed programs still see the old version - sandbox: link cache means that if i update ~/.config/... files inline, sandboxed programs still see the old version
- mpv: audiocast has mpv sending its output to the builtin speakers unless manually changed - mpv: audiocast has mpv sending its output to the builtin speakers unless manually changed
- mpv: no way to exit fullscreen video on moby - mpv: no way to exit fullscreen video on moby
- uosc hides controls on FS, and touch doesn't support unhiding - uosc hides controls on FS, and touch doesn't support unhiding
- Signal restart loop drains battery
- decrease s6 restart time?
- `ssh` access doesn't grant same linux capabilities as login - `ssh` access doesn't grant same linux capabilities as login
- syshud (volume overlay): when casting with `blast`, syshud doesn't react to volume changes - ringer (i.e. dino incoming call) doesn't prevent moby from sleeping
- sysvol (volume overlay): when casting with `blast`, sysvol doesn't react to volume changes
- moby: kaslr is effectively disabled - moby: kaslr is effectively disabled
- `dmesg | grep "KASLR disabled due to lack of seed"` - `dmesg | grep "KASLR disabled due to lack of seed"`
- fix by adding `kaslrseed` to uboot script before `booti` - fix by adding `kaslrseed` to uboot script before `booti`
@@ -22,12 +25,6 @@
- moby: bpf is effectively disabled? - moby: bpf is effectively disabled?
- `dmesg | grep 'systemd[1]: bpf-lsm: Failed to load BPF object: No such process'` - `dmesg | grep 'systemd[1]: bpf-lsm: Failed to load BPF object: No such process'`
- `dmesg | grep 'hid_bpf: error while preloading HID BPF dispatcher: -22'` - `dmesg | grep 'hid_bpf: error while preloading HID BPF dispatcher: -22'`
- `s6` is not re-entrant
- so if the desktop crashes, the login process from `unl0kr` fails to re-launch the GUI
- nwg-panel will sometimes create nested bars (happens maybe when i turn an external display off, then on?)
- wg-home is unreachable for a couple minutes when switching between LAN/WAN/3G
- because the endpoint DNS changes.
- causes calls to not transfer from WiFi -> cellular.
## REFACTORING: ## REFACTORING:
- add import checks to my Python nix-shell scripts - add import checks to my Python nix-shell scripts
@@ -67,8 +64,6 @@
- /mnt/desko/home, etc, shouldn't include secrets (~/private) - /mnt/desko/home, etc, shouldn't include secrets (~/private)
- 95% of its use is for remote media access and stuff which isn't in VCS (~/records) - 95% of its use is for remote media access and stuff which isn't in VCS (~/records)
- port all sane.programs to be sandboxed - port all sane.programs to be sandboxed
- sandbox `curlftpfs`
- sandbox `sshfs-fuse`
- enforce that all `environment.packages` has a sandbox profile (or explicitly opts out) - enforce that all `environment.packages` has a sandbox profile (or explicitly opts out)
- revisit "non-sandboxable" apps and check that i'm not actually just missing mountpoints - revisit "non-sandboxable" apps and check that i'm not actually just missing mountpoints
- LL_FS_RW=/ isn't enough -- need all mount points like `=/:/proc:/sys:...`. - LL_FS_RW=/ isn't enough -- need all mount points like `=/:/proc:/sys:...`.
@@ -83,16 +78,23 @@
- it adds like 50-70ms launch time _on my laptop_. i'd hate to know how much that is on the pinephone. - it adds like 50-70ms launch time _on my laptop_. i'd hate to know how much that is on the pinephone.
- make dconf stuff less monolithic - make dconf stuff less monolithic
- i.e. per-app dconf profiles for those which need it. possible static config. - i.e. per-app dconf profiles for those which need it. possible static config.
- flatpak/spectrum has some stuff to proxy dconf per-app - canaries for important services
- e.g. daily email checks; daily backup checks
- integrate `nix check` into Gitea actions?
#### sudo-free world
- `systemctl restart FOO`: needs `sudo`
- `systemctl daemon-reload`: needs sudo
- `watch ifconfig`: needs `SANEBOX_DISABLE=1`
### user experience ### user experience
- rofi: sort items case-insensitively - rofi: sort items case-insensitively
- xdg-desktop-portal shouldn't kill children on exit
- *maybe* a job for `setsid -f`?
- replace starship prompt with something more efficient - replace starship prompt with something more efficient
- watch `forkstat`: it does way too much - watch `forkstat`: it does way too much
- cleanup waybar/nwg-panel so that it's not invoking playerctl every 2 seconds - cleanup waybar so that it's not invoking playerctl every 2 seconds
- nwg-panel: doesn't know that virtual-desktop 10/TV exists
- install apps: - install apps:
- compass viewer (moby)
- display QR codes for WiFi endpoints: <https://linuxphoneapps.org/apps/noappid.wisperwind.wifi2qr/> - display QR codes for WiFi endpoints: <https://linuxphoneapps.org/apps/noappid.wisperwind.wifi2qr/>
- shopping list (not in nixpkgs): <https://linuxphoneapps.org/apps/ro.hume.cosmin.shoppinglist/> - shopping list (not in nixpkgs): <https://linuxphoneapps.org/apps/ro.hume.cosmin.shoppinglist/>
- offline Wikipedia (or, add to `wike`) - offline Wikipedia (or, add to `wike`)
@@ -101,7 +103,6 @@
- Gnome Highscore (retro games)?: <https://gitlab.gnome.org/World/highscore> - Gnome Highscore (retro games)?: <https://gitlab.gnome.org/World/highscore>
- better maps for mobile (Osmin (QtQuick)? Pure Maps (Qt/Kirigami)? - better maps for mobile (Osmin (QtQuick)? Pure Maps (Qt/Kirigami)?
- note-taking app: <https://linuxphoneapps.org/categories/note-taking/> - note-taking app: <https://linuxphoneapps.org/categories/note-taking/>
- Folio is nice, uses standard markdown, though it only supports flat repos
- OSK overlay specifically for mobile gaming - OSK overlay specifically for mobile gaming
- i.e. mock joysticks, for use with SuperTux and SuperTuxKart - i.e. mock joysticks, for use with SuperTux and SuperTuxKart
- install mobile-friendly games: - install mobile-friendly games:
@@ -122,11 +123,13 @@
- don't show MPRIS if no players detected - don't show MPRIS if no players detected
- this is a problem of playerctld, i guess - this is a problem of playerctld, i guess
- add option to change audio output - add option to change audio output
- fix colors (red alert) to match overall theme
- moby: tune GPS - moby: tune GPS
- fix iio-sensor-proxy magnetometer scaling - run only geoclue, and not gpsd, to save power?
- tune QGPS setting in eg25-control, for less jitter? - tune QGPS setting in eg25-control, for less jitter?
- direct mepo to prefer gpsd, with fallback to geoclue, for better accuracy?
- configure geoclue to do some smoothing? - configure geoclue to do some smoothing?
- manually do smoothing, as some layer between mepo and geoclue? - manually do smoothing, as some layer between mepo and geoclue/gpsd?
- moby: port `freshen-agps` timer service to s6 (maybe i want some `s6-cron` or something) - moby: port `freshen-agps` timer service to s6 (maybe i want some `s6-cron` or something)
- moby: show battery state on ssh login - moby: show battery state on ssh login
- moby: improve gPodder launch time - moby: improve gPodder launch time
@@ -153,9 +156,13 @@
- email: fix so that local mail doesn't go to junk - email: fix so that local mail doesn't go to junk
- git sendmail flow adds the DKIM signatures, but gets delivered locally w/o having the sig checked, so goes into Junk - git sendmail flow adds the DKIM signatures, but gets delivered locally w/o having the sig checked, so goes into Junk
- could change junk filter from "no DKIM success" to explicit "DKIM failed" - could change junk filter from "no DKIM success" to explicit "DKIM failed"
- add an auto-reply address (e.g. `reply-test@uninsane.org`) which reflects all incoming mail; use this (or a friend running this) for liveness checks
### perf ### perf
- debug nixos-rebuild times
- use `systemctl list-jobs` to show what's being waited on
- i think it's `systemd-networkd-wait-online.service` that's blocking this?
- i wonder what interface it's waiting for. i should use `--ignore=...` to ignore interfaces i don't care about.
- also `wireguard-wg-home.target` when net is offline
- add `pkgs.impure-cached.<foo>` package set to build things with ccache enabled - add `pkgs.impure-cached.<foo>` package set to build things with ccache enabled
- every package here can be auto-generated, and marked with some env var so that it doesn't pollute the pure package set - every package here can be auto-generated, and marked with some env var so that it doesn't pollute the pure package set
- would be super handy for package prototyping! - would be super handy for package prototyping!

View File

@@ -1,5 +1,9 @@
{ ... }@args: # limited, non-flake interface to this repo.
let # this file exposes the same view into `pkgs` which the flake would see when evaluated.
sane-nix-files = import ./pkgs/additional/sane-nix-files { }; #
in # the primary purpose of this file is so i can run `updateScript`s which expect
import "${sane-nix-files}/impure.nix" args # the root to be `default.nix`
{ pkgs ? import <nixpkgs> {} }:
pkgs.appendOverlays [
(import ./overlays/all.nix)
]

7
flake.lock generated Normal file
View File

@@ -0,0 +1,7 @@
{
"nodes": {
"root": {}
},
"root": "root",
"version": 7
}

603
flake.nix Normal file
View File

@@ -0,0 +1,603 @@
# FLAKE FEEDBACK:
# - if flake inputs are meant to be human-readable, a human should be able to easily track them down given the URL.
# - this is not the case with registry URLs, like `nixpkgs/nixos-22.11`.
# - this is marginally the case with schemes like `github:nixos/nixpkgs`.
# - given the *existing* `git+https://` scheme, i propose expressing github URLs similarly:
# - `github+https://github.com/nixos/nixpkgs/tree/nixos-22.11`
# - this would allow for the same optimizations as today's `github:nixos/nixpkgs`, but without obscuring the source.
# a code reader could view the source being referenced simply by clicking the https:// portion of that URI.
# - need some way to apply local patches to inputs.
#
#
# DEVELOPMENT DOCS:
# - Flake docs: <https://nixos.wiki/wiki/Flakes>
# - Flake RFC: <https://github.com/tweag/rfcs/blob/flakes/rfcs/0049-flakes.md>
# - Discussion: <https://github.com/NixOS/rfcs/pull/49>
# - <https://serokell.io/blog/practical-nix-flakes>
#
#
# COMMON OPERATIONS:
# - update a specific flake input:
# - `nix flake lock --update-input nixpkgs`
{
outputs = {
self,
}@inputs:
let
inherit (builtins) attrNames elem listToAttrs map mapAttrs;
# redefine some nixpkgs `lib` functions to avoid the infinite recursion
# of if we tried to use patched `nixpkgs.lib` as part of the patching process.
mapAttrs' = f: set:
listToAttrs (map (attr: f attr set.${attr}) (attrNames set));
optionalAttrs = cond: attrs: if cond then attrs else {};
# mapAttrs but without the `name` argument
mapAttrValues = f: mapAttrs (_: f);
nixpkgs' = import ./pkgs/additional/nixpkgs;
nixpkgsUnpatched = nixpkgs' { doPatch = false; localSystem = "x86_64-linux"; };
nixpkgsCompiledBy = { system, variant ? "master" }:
(nixpkgs' { inherit variant system; }).legacyPackages."${system}";
evalHost = { name, local, target, variant ? null, nixpkgs ? nixpkgs' { localSystem = local; system = target;} }: nixpkgs.nixos (
[
(optionalAttrs (variant == "light") {
sane.maxBuildCost = 2;
})
(optionalAttrs (variant == "min") {
sane.maxBuildCost = 0;
})
(import ./hosts/instantiate.nix { hostName = name; })
(import ./modules)
(nixpkgs.appendOverlays [ self.overlays.pkgs ]).sops-nix.nixosModules.sops
{
nixpkgs.overlays = [
self.overlays.sane-all
];
}
]
);
in {
nixosConfigurations = let
hosts = {
servo = { name = "servo"; local = "x86_64-linux"; target = "x86_64-linux"; };
desko = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; };
desko-light = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; variant = "light"; };
lappy = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; };
lappy-light = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; variant = "light"; };
lappy-min = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; variant = "min"; };
moby = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; };
moby-light = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; variant = "light"; };
moby-min = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; variant = "min"; };
# crappy is technically armv7a, and armv7l uses only a _subset_ of the available ISA.
# but it's not as widely cached.
crappy = { name = "crappy"; local = "x86_64-linux"; target = "armv7l-linux"; };
crappy-min = { name = "crappy"; local = "x86_64-linux"; target = "armv7l-linux"; variant = "min"; };
crappy-7a = { name = "crappy"; local = "x86_64-linux"; target = "armv7a-linux"; variant = "min"; };
rescue = { name = "rescue"; local = "x86_64-linux"; target = "x86_64-linux"; };
};
hostsNext = mapAttrs' (h: v: {
name = "${h}-next";
value = v // {
nixpkgs = nixpkgs' {
localSystem = v.local;
system = v.target;
variant = "staging-next";
};
};
}) hosts;
hostsStaging = mapAttrs' (h: v: {
name = "${h}-staging";
value = v // {
nixpkgs = nixpkgs' {
localSystem = v.local;
system = v.target;
variant = "staging";
};
};
}) hosts;
in mapAttrValues evalHost (
hosts // hostsNext // hostsStaging
);
# unofficial output
# this produces a EFI-bootable .img file (GPT with a /boot partition and a system (/ or /nix) partition).
# after building this:
# - flash it to a bootable medium (SD card, flash drive, HDD)
# - resize the root partition (use cfdisk)
# - mount the part
# - chown root:nixbld <part>/nix/store
# - chown root:root -R <part>/nix/store/*
# - chown root:root -R <part>/persist # if using impermanence
# - populate any important things (persist/, home/colin/.ssh, etc)
# - boot
# - if fs wasn't resized automatically, then `sudo btrfs filesystem resize max /`
# - checkout this flake into /etc/nixos AND UPDATE THE FS UUIDS.
# - `nixos-rebuild --flake './#<host>' switch`
imgs = mapAttrValues (host: host.config.system.build.img) self.nixosConfigurations;
# 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;
overlays = {
# N.B.: `nix flake check` requires every overlay to take `final: prev:` at defn site,
# hence the weird redundancy.
default = final: prev: self.overlays.pkgs final prev;
sane-all = final: prev: import ./overlays/all.nix final prev;
pkgs = final: prev: import ./overlays/pkgs.nix final prev;
preferences = final: prev: import ./overlays/preferences.nix final prev;
};
# this includes both our native packages and all the nixpkgs packages.
legacyPackages =
let
allPkgsFor = variant: additionalOverlays: system:
(nixpkgs' { inherit system variant; localSystem = "x86_64-linux"; })
.appendOverlays (
[
self.overlays.pkgs
] ++ additionalOverlays
);
allPkgsFor' = system: allPkgsFor
"master"
[(self: super: {
# build `pkgsNext.FOO` to build the package FOO from nixpkgs staging-next branch
pkgsNext = allPkgsFor "staging-next" [] system;
pkgsStaging = allPkgsFor "staging" [] system;
})]
system
;
in {
x86_64-linux = allPkgsFor' "x86_64-linux";
aarch64-linux = allPkgsFor' "aarch64-linux";
};
# extract only our own packages from the full set.
# because of `nix flake check`, we flatten the package set and only surface x86_64-linux packages.
packages = mapAttrs
(system: passthruPkgs: passthruPkgs.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)
)
(
import ./pkgs { pkgs = passthruPkgs; }
)
)
# self.legacyPackages;
{
x86_64-linux = nixpkgs' { localSystem = "x86_64-linux"; };
}
;
apps."x86_64-linux" =
let
pkgs = self.legacyPackages."x86_64-linux";
sanePkgs = import ./pkgs { inherit pkgs; };
deployScript = host: addr: action: pkgs.writeShellScript "deploy-${host}" ''
set -e
host="${host}"
addr="${addr}"
action="${if action != null then action else ""}"
runOnTarget() {
# run the command ($@) on the machine we're deploying to.
# if that's a remote machine, then do it via ssh, else local shell.
if [ -n "$addr" ]; then
ssh "$addr" "$@"
else
"$@"
fi
}
nix build ".#nixosConfigurations.$host.config.system.build.toplevel" --out-link "./build/result-$host" "$@"
storePath="$(readlink ./build/result-$host)"
# mimic `nixos-rebuild --target-host`, in effect:
# - nix-copy-closure ...
# - nix-env --set ...
# - switch-to-configuration <boot|dry-activate|switch|test|>
# avoid the actual `nixos-rebuild` for a few reasons:
# - fewer nix evals
# - more introspectability and debuggability
# - sandbox friendliness (especially: `git` doesn't have to be run as root)
if [ -n "$addr" ]; then
sudo nix store sign -r -k /run/secrets/nix_signing_key "$storePath"
# add more `-v` for more verbosity (up to 5).
# builders-use-substitutes false: optimizes so that the remote machine doesn't try to get paths from its substituters.
# we already have all paths here, and the remote substitution is slow to check and SERIOUSLY flaky on moby in particular.
nix copy -vv --option builders-use-substitutes false --to "ssh-ng://$addr" "$storePath"
fi
if [ -n "$action" ]; then
runOnTarget sudo nix-env -p /nix/var/nix/profiles/system --set "$storePath"
runOnTarget sudo "$storePath/bin/switch-to-configuration" "$action"
fi
'';
deployApp = host: addr: action: {
type = "app";
program = ''${deployScript host addr action}'';
};
# pkg updating.
# a cleaner alternative lives here: <https://discourse.nixos.org/t/how-can-i-run-the-updatescript-of-personal-packages/25274/2>
# mkUpdater :: [ String ] -> { type = "app"; program = path; }
mkUpdater = attrPath: {
type = "app";
program = let
pkg = pkgs.lib.getAttrFromPath attrPath sanePkgs;
strAttrPath = pkgs.lib.concatStringsSep "." attrPath;
commandArgv = pkg.updateScript.command or pkg.updateScript;
command = pkgs.lib.escapeShellArgs commandArgv;
in builtins.toString (pkgs.writeShellScript "update-${strAttrPath}" ''
set -x
env UPDATE_NIX_NAME=${pkg.name} UPDATE_NIX_PNAME=${pkg.pname} UPDATE_NIX_OLD_VERSION=${pkg.version} UPDATE_NIX_ATTR_PATH=${strAttrPath} ${command}
'');
};
mkUpdatersNoAliases = opts: basePath: pkgs.lib.concatMapAttrs
(name: pkg:
if pkg.recurseForDerivations or false then {
"${name}" = mkUpdaters opts (basePath ++ [ name ]);
} else if pkg.updateScript or null != null then {
"${name}" = mkUpdater (basePath ++ [ name ]);
} else {}
)
(pkgs.lib.getAttrFromPath basePath sanePkgs);
mkUpdaters = { ignore ? [], flakePrefix ? [] }@opts: basePath:
let
updaters = mkUpdatersNoAliases opts basePath;
invokeUpdater = name: pkg:
let
fullPath = basePath ++ [ name ];
doUpdateByDefault = !builtins.elem fullPath ignore;
# in case `name` has a `.` in it, we have to quote it
escapedPath = builtins.map (p: ''"${p}"'') fullPath;
updatePath = builtins.concatStringsSep "." (flakePrefix ++ escapedPath);
in pkgs.lib.optionalString doUpdateByDefault (
pkgs.lib.escapeShellArgs [
"nix" "run" ".#${updatePath}"
]
);
in {
type = "app";
# top-level app just invokes the updater of everything one layer below it
program = builtins.toString (pkgs.writeShellScript
(builtins.concatStringsSep "-" (flakePrefix ++ basePath))
(builtins.concatStringsSep
"\n"
(pkgs.lib.mapAttrsToList invokeUpdater updaters)
)
);
} // updaters;
in {
help = {
type = "app";
program = let
helpMsg = builtins.toFile "nixos-config-help-message" ''
commands:
- `nix run '.#help'`
- show this message
- `nix run '.#update.pkgs'`
- updates every package
- `nix run '.#update.feeds'`
- updates metadata for all feeds
- `nix run '.#init-feed' <url>`
- `nix run '.#deploy.{desko,lappy,moby,servo}[-light|-test]' [nix args ...]`
- build and deploy the host
- `nix run '.#preDeploy.{desko,lappy,moby,servo}[-light]' [nix args ...]`
- copy closures to a host, but don't activate it
- or `nix run '.#preDeploy'` to target all hosts
- `nix run '.#check'`
- make sure all systems build; NUR evaluates
- `nix run '.#bench'`
- benchmark the eval time of common targets this flake provides
specific build targets of interest:
- `nix build '.#imgs.rescue'`
'';
in builtins.toString (pkgs.writeShellScript "nixos-config-help" ''
cat ${helpMsg}
echo ""
echo "complete flake structure:"
nix flake show --option allow-import-from-derivation true
'');
};
# wrangle some names to get package updaters which refer back into the flake, but also conditionally ignore certain paths (e.g. sane.feeds).
# TODO: better design
update = rec {
_impl.pkgs.sane = mkUpdaters { flakePrefix = [ "update" "_impl" "pkgs" ]; ignore = [ [ "sane" "feeds" ] ]; } [ "sane" ];
pkgs = _impl.pkgs.sane;
_impl.feeds.sane.feeds = mkUpdaters { flakePrefix = [ "update" "_impl" "feeds" ]; } [ "sane" "feeds" ];
feeds = _impl.feeds.sane.feeds;
};
init-feed = {
type = "app";
program = "${pkgs.feeds.init-feed}";
};
deploy = {
crappy = deployApp "crappy" "crappy" "switch";
crappy-light = deployApp "crappy-light" "crappy" "switch";
crappy-min = deployApp "crappy-min" "crappy" "switch";
desko = deployApp "desko" "desko" "switch";
desko-light = deployApp "desko-light" "desko" "switch";
lappy = deployApp "lappy" "lappy" "switch";
lappy-light = deployApp "lappy-light" "lappy" "switch";
lappy-min = deployApp "lappy-min" "lappy" "switch";
moby = deployApp "moby" "moby" "switch";
moby-light = deployApp "moby-light" "moby" "switch";
moby-min = deployApp "moby-min" "moby" "switch";
moby-test = deployApp "moby" "moby" "test";
servo = deployApp "servo" "servo" "switch";
# like `nixos-rebuild --flake . switch`
self = deployApp "$(hostname)" "" "switch";
self-light = deployApp "$(hostname)-light" "" "switch";
self-min = deployApp "$(hostname)-min" "" "switch";
type = "app";
program = builtins.toString (pkgs.writeShellScript "deploy-all" ''
nix run '.#deploy.lappy'
nix run '.#deploy.moby'
nix run '.#deploy.desko'
nix run '.#deploy.servo'
nix run '.#deploy.crappy'
'');
};
preDeploy = {
# build the host and copy the runtime closure to that host, but don't activate it.
crappy = deployApp "crappy" "crappy" null;
crappy-light = deployApp "crappy-light" "crappy" null;
crappy-min = deployApp "crappy-min" "crappy" null;
desko = deployApp "desko" "desko" null;
desko-light = deployApp "desko-light" "desko" null;
lappy = deployApp "lappy" "lappy" null;
lappy-light = deployApp "lappy-light" "lappy" null;
lappy-min = deployApp "lappy-min" "lappy" null;
moby = deployApp "moby" "moby" null;
moby-light = deployApp "moby-light" "moby" null;
moby-min = deployApp "moby-min" "moby" null;
servo = deployApp "servo" "servo" null;
type = "app";
program = builtins.toString (pkgs.writeShellScript "predeploy-all" ''
# copy the -min/-light variants first; this might be run while waiting on a full build. or the full build failed.
nix run '.#preDeploy.moby-min' -- "$@"
nix run '.#preDeploy.lappy-min' -- "$@"
nix run '.#preDeploy.crappy-min' -- "$@"
nix run '.#preDeploy.moby-light' -- "$@"
nix run '.#preDeploy.lappy-light' -- "$@"
nix run '.#preDeploy.desko-light' -- "$@"
nix run '.#preDeploy.crappy-light' -- "$@"
nix run '.#preDeploy.lappy' -- "$@"
nix run '.#preDeploy.servo' -- "$@"
nix run '.#preDeploy.moby' -- "$@"
nix run '.#preDeploy.desko' -- "$@"
nix run '.#preDeploy.crappy' -- "$@"
'');
};
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
type = "app";
program = builtins.toString (pkgs.writeShellScript "sync-to-moby" ''
sudo mount /mnt/moby/home
sudo mount /mnt/desko/home
sudo mount /mnt/servo/media/Books
# copy photos/screenshots from moby to desko:
${pkgs.rsync}/bin/rsync -arv --exclude servo-macros /mnt/moby/home/Pictures/ /mnt/desko/home/Pictures/moby/
# copy books from servo to moby; delete old/untracked ones, but keep KOreader state files (sdr)
${pkgs.rsync}/bin/rsync -arv --delete --exclude unprocessed --exclude '*.sdr' /mnt/servo/media/Books/ /mnt/moby/home/Books/local/servo/
# 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 "$@"
'');
};
check = {
type = "app";
program = builtins.toString (pkgs.writeShellScript "check-all" ''
nix run '.#check.nur'
RC0=$?
nix run '.#check.hostConfigs'
RC1=$?
nix run '.#check.rescue'
RC2=$?
echo "nur: $RC0"
echo "hostConfigs: $RC1"
echo "rescue: $RC2"
exit $(($RC0 | $RC1 | $RC2))
'');
};
check.nur = {
# `nix run '.#check-nur'`
# validates that my repo can be included in the Nix User Repository
type = "app";
program = builtins.toString (pkgs.writeShellScript "check-nur" ''
cd ${./.}/integrations/nur
NIX_PATH= NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1 nix-env -f . -qa \* --meta --xml \
--allowed-uris https://static.rust-lang.org \
--option restrict-eval true \
--option allow-import-from-derivation true \
--drv-path --show-trace \
-I nixpkgs=${nixpkgsUnpatched} \
-I nixpkgs-overlays=${./.}/hosts/common/nix/overlay \
-I ../../ \
| tee # tee to prevent interactive mode
'');
};
check.hostConfigs = {
type = "app";
program = let
checkHost = host: let
shellHost = pkgs.lib.replaceStrings [ "-" ] [ "_" ] host;
in ''
nix build -v '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./build/result-${host} -j2 "$@"
RC_${shellHost}=$?
'';
in builtins.toString (pkgs.writeShellScript
"check-host-configs"
''
# build minimally-usable hosts first, then their full image.
# this gives me a minimal image i can deploy or copy over, early.
${checkHost "lappy-min"}
${checkHost "moby-min"}
${checkHost "crappy-min"}
${checkHost "desko-light"}
${checkHost "moby-light"}
${checkHost "lappy-light"}
${checkHost "crappy-light"}
${checkHost "desko"}
${checkHost "lappy"}
${checkHost "servo"}
${checkHost "moby"}
${checkHost "crappy"}
${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 "crappy-light-next"}
${checkHost "desko-next"}
${checkHost "lappy-next"}
${checkHost "servo-next"}
${checkHost "moby-next"}
${checkHost "crappy-next"}
${checkHost "rescue-next"}
echo "desko: $RC_desko"
echo "lappy: $RC_lappy"
echo "servo: $RC_servo"
echo "moby: $RC_moby"
echo "crappy: $RC_crappy"
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 "crappy-next: $RC_crappy_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_crappy | $RC_rescue))
''
);
};
check.rescue = {
type = "app";
program = builtins.toString (pkgs.writeShellScript "check-rescue" ''
nix build -v '.#imgs.rescue' --out-link ./build/result-rescue-img -j2
'');
};
bench = {
type = "app";
program = builtins.toString (pkgs.writeShellScript "bench" ''
doBench() {
attrPath="$1"
shift
echo -n "benchmarking eval of '$attrPath'... "
/run/current-system/sw/bin/time -f "%e sec" -o /dev/stdout \
nix eval --no-eval-cache --quiet --raw ".#$attrPath" --apply 'result: if result != null then "" else "unexpected null"' $@ 2> /dev/null
}
if [ -n "$1" ]; then
doBench "$@"
else
doBench hostConfigs
doBench hostConfigs.lappy
doBench hostConfigs.lappy.sane.programs
doBench hostConfigs.lappy.sane.users.colin
doBench hostConfigs.lappy.sane.fs
doBench hostConfigs.lappy.environment.systemPackages
fi
'');
};
};
templates = {
env.python-data = {
# initialize with:
# - `nix flake init -t '/home/colin/dev/nixos/#env.python-data'`
# then enter with:
# - `nix develop`
path = ./templates/env/python-data;
description = "python environment for data processing";
};
pkgs.make = {
# initialize with:
# - `nix flake init -t '/home/colin/dev/nixos/#pkgs.make'`
path = ./templates/pkgs/make;
description = "default Makefile-based derivation";
};
pkgs.python = {
# initialize with:
# - `nix flake init -t '/home/colin/dev/nixos/#pkgs.python'`
path = ./templates/pkgs/python;
description = "python package";
};
pkgs.rust-inline = {
# initialize with:
# - `nix flake init -t '/home/colin/dev/nixos/#pkgs.rust-inline'`
path = ./templates/pkgs/rust-inline;
description = "rust package and development environment (inline rust sources)";
};
pkgs.rust = {
# initialize with:
# - `nix flake init -t '/home/colin/dev/nixos/#pkgs.rust'`
path = ./templates/pkgs/rust;
description = "rust package fit to ship in nixpkgs";
};
};
};
}

View File

@@ -12,23 +12,15 @@
users.users.colin.initialPassword = "147147"; users.users.colin.initialPassword = "147147";
sane.programs.sway.enableFor.user.colin = true; sane.programs.sway.enableFor.user.colin = true;
sane.programs.calls.enableFor.user.colin = false; sane.programs.calls.enableFor.user.colin = false;
sane.programs.consoleMediaUtils.enableFor.user.colin = true; sane.programs.consoleMediaUtils.enableFor.user.colin = true;
sane.programs.epiphany.enableFor.user.colin = true; sane.programs.epiphany.enableFor.user.colin = true;
sane.programs.geary.enableFor.user.colin = false; sane.programs."gnome.geary".enableFor.user.colin = false;
# sane.programs.firefox.enableFor.user.colin = true; # sane.programs.firefox.enableFor.user.colin = true;
sane.programs.portfolio-filemanager.enableFor.user.colin = true; sane.programs.portfolio-filemanager.enableFor.user.colin = true;
sane.programs.signal-desktop.enableFor.user.colin = false; sane.programs.signal-desktop.enableFor.user.colin = false;
sane.programs.wike.enableFor.user.colin = true; sane.programs.wike.enableFor.user.colin = true;
sane.programs.dino.config.autostart = false;
sane.programs.dissent.config.autostart = false;
sane.programs.fractal.config.autostart = false;
sane.programs.sway.config.mod = "Mod1"; #< alt key instead of Super
# sane.programs.guiApps.enableFor.user.colin = false;
# sane.programs.pcGuiApps.enableFor.user.colin = false; #< errors! # sane.programs.pcGuiApps.enableFor.user.colin = false; #< errors!
sane.programs.blueberry.enableFor.user.colin = false; # bluetooth manager: doesn't cross compile! sane.programs.blueberry.enableFor.user.colin = false; # bluetooth manager: doesn't cross compile!

View File

@@ -32,14 +32,9 @@
sane.programs.iphoneUtils.enableFor.user.colin = true; sane.programs.iphoneUtils.enableFor.user.colin = true;
sane.programs.steam.enableFor.user.colin = true; sane.programs.steam.enableFor.user.colin = true;
sane.programs.geary.config.autostart = true; sane.programs."gnome.geary".config.autostart = true;
sane.programs.signal-desktop.config.autostart = true; sane.programs.signal-desktop.config.autostart = true;
sane.programs.nwg-panel.config = {
battery = false;
brightness = false;
};
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ]; sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
# needed to use libimobiledevice/ifuse, for iphone sync # needed to use libimobiledevice/ifuse, for iphone sync

View File

@@ -18,7 +18,7 @@
sane.programs.stepmania.enableFor.user.colin = true; sane.programs.stepmania.enableFor.user.colin = true;
sane.programs.sway.enableFor.user.colin = true; sane.programs.sway.enableFor.user.colin = true;
sane.programs.geary.config.autostart = true; sane.programs."gnome.geary".config.autostart = true;
sane.programs.signal-desktop.config.autostart = true; sane.programs.signal-desktop.config.autostart = true;
sops.secrets.colin-passwd.neededForUsers = true; sops.secrets.colin-passwd.neededForUsers = true;

View File

@@ -10,6 +10,7 @@
{ {
imports = [ imports = [
./fs.nix ./fs.nix
./gps.nix
]; ];
sane.hal.pine64.enable = true; sane.hal.pine64.enable = true;
@@ -28,7 +29,6 @@
sops.secrets.colin-passwd.neededForUsers = true; sops.secrets.colin-passwd.neededForUsers = true;
sane.programs.sway.enableFor.user.colin = true; sane.programs.sway.enableFor.user.colin = true;
sane.programs.sway.config.mod = "Mod1"; #< alt key instead of Super
sane.programs.blueberry.enableFor.user.colin = false; # bluetooth manager: doesn't cross compile! sane.programs.blueberry.enableFor.user.colin = false; # bluetooth manager: doesn't cross compile!
sane.programs.fcitx5.enableFor.user.colin = false; # does not cross compile sane.programs.fcitx5.enableFor.user.colin = false; # does not cross compile
sane.programs.mercurial.enableFor.user.colin = false; # does not cross compile sane.programs.mercurial.enableFor.user.colin = false; # does not cross compile
@@ -36,12 +36,12 @@
# enabled for easier debugging # enabled for easier debugging
sane.programs.eg25-control.enableFor.user.colin = true; sane.programs.eg25-control.enableFor.user.colin = true;
# sane.programs.rtl8723cs-wowlan.enableFor.user.colin = true; sane.programs.rtl8723cs-wowlan.enableFor.user.colin = true;
# sane.programs.ntfy-sh.config.autostart = true; # sane.programs.ntfy-sh.config.autostart = true;
sane.programs.dino.config.autostart = true; sane.programs.dino.config.autostart = true;
sane.programs.signal-desktop.config.autostart = true; # sane.programs.signal-desktop.config.autostart = true; # TODO: enable once electron stops derping.
# sane.programs.geary.config.autostart = true; # sane.programs."gnome.geary".config.autostart = true;
# sane.programs.calls.config.autostart = true; # sane.programs.calls.config.autostart = true;
sane.programs.pipewire.config = { sane.programs.pipewire.config = {

View File

@@ -0,0 +1,68 @@
# pinephone GPS happens in EG25 modem
# serial control interface to modem is /dev/ttyUSB2
# after enabling GPS, readout is /dev/ttyUSB1
#
# minimal process to enable modem and GPS:
# - `echo 1 > /sys/class/modem-power/modem-power/device/powered`
# - `screen /dev/ttyUSB2 115200`
# - `AT+QGPSCFG="nmeasrc",1`
# - `AT+QGPS=1`
# this process is automated by my `eg25-control` program and services (`eg25-control-powered`, `eg25-control-gps`)
# - see the `modules/` directory further up this repository.
#
# now, something like `gpsd` can directly read from /dev/ttyUSB1,
# or geoclue can query the GPS directly through modem-manager
#
# initial GPS fix can take 15+ minutes.
# meanwhile, services like eg25-manager or eg25-control-freshen-agps can speed this up by uploading assisted GPS data to the modem.
#
# support/help:
# - geoclue, gnome-maps
# - irc: #gnome-maps on irc.gimp.org
# - Matrix: #gnome-maps:gnome.org (unclear if bridged to IRC)
#
# programs to pair this with:
# - `satellite-gtk`: <https://codeberg.org/tpikonen/satellite>
# - shows/tracks which satellites the GPS is connected to; useful to understand fix characteristics
# - `gnome-maps`: uses geoclue, has route planning
# - `mepo`: uses gpsd, minimalist, flaky, and buttons are kinda hard to activate on mobile
# - puremaps?
# - osmin?
#
# known/outstanding bugs:
# - `systemctl start eg25-control-gps` can the hang the whole system (2023/10/06)
# - i think it's actually `eg25-control-powered` which does this (started by the gps)
# - best guess is modem draws so much power at launch that other parts of the system see undervoltage
# - workaround is to hard power-cycle the system. the modem may not bring up after reboot: leave unpowered for 60s and boot again.
#
# future work:
# - integrate with [wigle](https://www.wigle.net/) for offline equivalent to Mozilla Location Services
{ config, lib, ... }:
{
# test gpsd with `gpspipe -w -n 10 2> /dev/null | grep -m 1 TPV | jq '.lat, .lon' | tr '\n' ' '`
# ^ should return <lat> <long>
services.gpsd.enable = true;
services.gpsd.devices = [ "/dev/ttyUSB1" ];
# test geoclue2 by building `geoclue2-with-demo-agent`
# and running "${geoclue2-with-demo-agent}/libexec/geoclue-2.0/demos/where-am-i"
# note that geoclue is dbus-activated, and auto-stops after 60s with no caller
services.geoclue2.enable = true;
services.geoclue2.appConfig.where-am-i = {
# this is the default "agent", shipped by geoclue package: allow it to use location
isAllowed = true;
isSystem = false;
# XXX: setting users != [] might be causing `where-am-i` to time out
users = [
# restrict to only one set of users. empty array (default) means "allow any user to access geolocation".
(builtins.toString config.users.users.colin.uid)
];
};
systemd.services.geoclue.after = lib.mkForce []; #< defaults to network-online, but not all my sources require network
users.users.geoclue.extraGroups = [
"dialout" # TODO: figure out if dialout is required. that's for /dev/ttyUSB1, but geoclue probably doesn't read that?
];
sane.programs.where-am-i.enableFor.user.colin = true;
}

View File

@@ -7,11 +7,12 @@
./services ./services
]; ];
sane.programs = {
# for administering services # for administering services
sane.programs.clightning-sane.enableFor.user.colin = true; freshrss.enableFor.user.colin = true;
# sane.programs.freshrss.enableFor.user.colin = true; matrix-synapse.enableFor.user.colin = true;
# sane.programs.signaldctl.enableFor.user.colin = true; signaldctl.enableFor.user.colin = true;
# sane.programs.matrix-synapse.enableFor.user.colin = true; };
sane.roles.build-machine.enable = true; sane.roles.build-machine.enable = true;
sane.programs.zsh.config.showDeadlines = false; # ~/knowledge doesn't always exist sane.programs.zsh.config.showDeadlines = false; # ~/knowledge doesn't always exist

View File

@@ -54,7 +54,7 @@
options = [ "acl" ]; #< not sure if this `acl` flag is actually necessary. it mounts without it. options = [ "acl" ]; #< not sure if this `acl` flag is actually necessary. it mounts without it.
}; };
# services.zfs.zed = ... # TODO: zfs can send me emails when disks fail # services.zfs.zed = ... # TODO: zfs can send me emails when disks fail
sane.programs.sysadminUtils.suggestedPrograms = [ "zfs-tools" ]; sane.programs.sysadminUtils.suggestedPrograms = [ "zfs" ];
sane.persist.stores."ext" = { sane.persist.stores."ext" = {
origin = "/mnt/pool/persist"; origin = "/mnt/pool/persist";

View File

@@ -3,19 +3,9 @@
let let
portOpts = with lib; types.submodule { portOpts = with lib; types.submodule {
options = { options = {
visibleTo.ovpns = mkOption { visibleTo.ovpn = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
description = ''
whether to forward inbound traffic on the OVPN vpn port to the corresponding localhost port.
'';
};
visibleTo.doof = mkOption {
type = types.bool;
default = false;
description = ''
whether to forward inbound traffic on the doofnet vpn port to the corresponding localhost port.
'';
}; };
}; };
}; };
@@ -23,7 +13,7 @@ in
{ {
options = with lib; { options = with lib; {
sane.ports.ports = mkOption { sane.ports.ports = mkOption {
# add the `visibleTo.{doof,ovpns}` options # add the `visibleTo.ovpn` option
type = types.attrsOf portOpts; type = types.attrsOf portOpts;
}; };
}; };
@@ -50,16 +40,18 @@ in
# tun-sea config # tun-sea config
sane.dns.zones."uninsane.org".inet.A."doof.tunnel" = "205.201.63.12"; sane.dns.zones."uninsane.org".inet.A."doof.tunnel" = "205.201.63.12";
# sane.dns.zones."uninsane.org".inet.AAAA."doof.tunnel" = "2602:fce8:106::51"; #< TODO: enable IPv6 sane.dns.zones."uninsane.org".inet.AAAA."doof.tunnel" = "2602:fce8:106::51";
networking.wireguard.interfaces.wg-doof = { networking.wireguard.interfaces.wg-doof = let
ip = "${pkgs.iproute2}/bin/ip";
in {
privateKeyFile = config.sops.secrets.wg_doof_privkey.path; privateKeyFile = config.sops.secrets.wg_doof_privkey.path;
# wg is active only in this namespace. # wg is active only in this namespace.
# run e.g. ip netns exec doof <some command like ping/curl/etc, it'll go through wg> # run e.g. ip netns exec doof <some command like ping/curl/etc, it'll go through wg>
# sudo ip netns exec doof ping www.google.com # sudo ip netns exec doof ping www.google.com
interfaceNamespace = "doof"; interfaceNamespace = "doof";
ips = [ ips = [
"205.201.63.12" "205.201.63.12/32"
# "2602:fce8:106::51/128" #< TODO: enable IPv6 "2602:fce8:106::51/128"
]; ];
peers = [ peers = [
{ {
@@ -71,24 +63,45 @@ in
persistentKeepalive = 25; #< keep the NAT alive persistentKeepalive = 25; #< keep the NAT alive
} }
]; ];
preSetup = ''
${ip} netns add doof || (test -e /run/netns/doof && echo "doof already exists")
'';
postShutdown = ''
${ip} netns delete doof || echo "couldn't delete doof"
'';
}; };
sane.netns.doof.hostVethIpv4 = "10.0.2.5";
sane.netns.doof.netnsVethIpv4 = "10.0.2.6";
sane.netns.doof.netnsPubIpv4 = "205.201.63.12";
sane.netns.doof.routeTable = 12;
# OVPN CONFIG (https://www.ovpn.com): # OVPN CONFIG (https://www.ovpn.com):
# DOCS: https://nixos.wiki/wiki/WireGuard # DOCS: https://nixos.wiki/wiki/WireGuard
# if you `systemctl restart wireguard-wg-ovpns`, make sure to also restart any other services in `NetworkNamespacePath = .../ovpns`. # if you `systemctl restart wireguard-wg-ovpns`, make sure to also restart any other services in `NetworkNamespacePath = .../ovpns`.
# TODO: why not create the namespace as a seperate operation (nix config for that?) # TODO: why not create the namespace as a seperate operation (nix config for that?)
networking.wireguard.enable = true; networking.wireguard.enable = true;
networking.wireguard.interfaces.wg-ovpns = { networking.wireguard.interfaces.wg-ovpns = let
ip = "${pkgs.iproute2}/bin/ip";
in-ns = "${ip} netns exec ovpns";
iptables = "${pkgs.iptables}/bin/iptables";
veth-host-ip = "10.0.1.5";
veth-local-ip = "10.0.1.6";
vpn-ip = "185.157.162.178";
# DNS = 46.227.67.134, 192.165.9.158, 2a07:a880:4601:10f0:cd45::1, 2001:67c:750:1:cafe:cd45::1
vpn-dns = "46.227.67.134";
bridgePort = port: proto: ''
${in-ns} ${iptables} -A PREROUTING -t nat -p ${proto} --dport ${port} -m iprange --dst-range ${vpn-ip} \
-j DNAT --to-destination ${veth-host-ip}
'';
bridgeStatements = lib.foldlAttrs
(acc: port: portCfg: acc ++ (builtins.map (bridgePort port) portCfg.protocol))
[]
config.sane.ports.ports;
in {
privateKeyFile = config.sops.secrets.wg_ovpns_privkey.path; privateKeyFile = config.sops.secrets.wg_ovpns_privkey.path;
# wg is active only in this namespace. # wg is active only in this namespace.
# run e.g. ip netns exec ovpns <some command like ping/curl/etc, it'll go through wg> # run e.g. ip netns exec ovpns <some command like ping/curl/etc, it'll go through wg>
# sudo ip netns exec ovpns ping www.google.com # sudo ip netns exec ovpns ping www.google.com
interfaceNamespace = "ovpns"; interfaceNamespace = "ovpns";
ips = [ "185.157.162.178" ]; ips = [
"185.157.162.178/32"
];
peers = [ peers = [
{ {
publicKey = "SkkEZDCBde22KTs/Hc7FWvDBfdOCQA4YtBEuC3n5KGs="; publicKey = "SkkEZDCBde22KTs/Hc7FWvDBfdOCQA4YtBEuC3n5KGs=";
@@ -106,11 +119,99 @@ in
# dynamicEndpointRefreshRestartSeconds = 5; # dynamicEndpointRefreshRestartSeconds = 5;
} }
]; ];
preSetup = ''
${ip} netns add ovpns || (test -e /run/netns/ovpns && echo "ovpns already exists")
'';
postShutdown = ''
${in-ns} ip link del ovpns-veth-b || echo "couldn't delete ovpns-veth-b"
${ip} link del ovpns-veth-a || echo "couldn't delete ovpns-veth-a"
${ip} netns delete ovpns || echo "couldn't delete ovpns"
# restore rules/routes
${ip} rule del from ${veth-host-ip} lookup ovpns pref 50 || echo "couldn't delete init -> ovpns rule"
${ip} route del default via ${veth-local-ip} dev ovpns-veth-a proto kernel src ${veth-host-ip} metric 1002 table ovpns || echo "couldn't delete init -> ovpns route"
${ip} rule add from all lookup local pref 0
${ip} rule del from all lookup local pref 100
'';
postSetup = ''
# DOCS:
# - some of this approach is described here: <https://josephmuia.ca/2018-05-16-net-namespaces-veth-nat/>
# - iptables primer: <https://danielmiessler.com/study/iptables/>
# create veth pair
${ip} link add ovpns-veth-a type veth peer name ovpns-veth-b
${ip} addr add ${veth-host-ip}/24 dev ovpns-veth-a
${ip} link set ovpns-veth-a up
# mv veth-b into the ovpns namespace
${ip} link set ovpns-veth-b netns ovpns
${in-ns} ip addr add ${veth-local-ip}/24 dev ovpns-veth-b
${in-ns} ip link set ovpns-veth-b up
# make it so traffic originating from the host side of the veth
# is sent over the veth no matter its destination.
${ip} rule add from ${veth-host-ip} lookup ovpns pref 50
# for traffic originating at the host veth to the WAN, use the veth as our gateway
# not sure if the metric 1002 matters.
${ip} route add default via ${veth-local-ip} dev ovpns-veth-a proto kernel src ${veth-host-ip} metric 1002 table ovpns
# give the default route lower priority
${ip} rule add from all lookup local pref 100
${ip} rule del from all lookup local pref 0
# in order to access DNS in this netns, we need to route it to the VPN's nameservers
# - alternatively, we could fix DNS servers like 1.1.1.1.
${in-ns} ${iptables} -A OUTPUT -t nat -p udp --dport 53 -m iprange --dst-range 127.0.0.53 \
-j DNAT --to-destination ${vpn-dns}:53
'' + (lib.concatStringsSep "\n" bridgeStatements);
}; };
sane.netns.ovpns.hostVethIpv4 = "10.0.1.5";
sane.netns.ovpns.netnsVethIpv4 = "10.0.1.6"; # create a new routing table that we can use to proxy traffic out of the root namespace
sane.netns.ovpns.netnsPubIpv4 = "185.157.162.178"; # through the ovpns namespace, and to the WAN via VPN.
sane.netns.ovpns.routeTable = 11; networking.iproute2.rttablesExtraConfig = ''
sane.netns.ovpns.dns = "46.227.67.134"; #< DNS requests inside the namespace are forwarded here 5 ovpns
'';
networking.iproute2.enable = true;
# HURRICANE ELECTRIC CONFIG:
# networking.sits = {
# hurricane = {
# remote = "216.218.226.238";
# local = "192.168.0.5";
# # local = "10.0.0.5";
# # remote = "10.0.0.1";
# # local = "10.0.0.22";
# dev = "eth0";
# ttl = 255;
# };
# };
# networking.interfaces."hurricane".ipv6 = {
# addresses = [
# # mx.uninsane.org (publically routed /64)
# {
# address = "2001:470:b:465::1";
# prefixLength = 128;
# }
# # client addr
# # {
# # address = "2001:470:a:466::2";
# # prefixLength = 64;
# # }
# ];
# routes = [
# {
# address = "::";
# prefixLength = 0;
# # via = "2001:470:a:466::1";
# }
# ];
# };
# # after configuration, we want the hurricane device to look like this:
# # hurricane: flags=209<UP,POINTOPOINT,RUNNING,NOARP> mtu 1480
# # inet6 2001:470:a:450::2 prefixlen 64 scopeid 0x0<global>
# # inet6 fe80::c0a8:16 prefixlen 64 scopeid 0x20<link>
# # sit txqueuelen 1000 (IPv6-in-IPv4)
# # test with:
# # curl --interface hurricane http://[2607:f8b0:400a:80b::2004]
# # ping 2607:f8b0:400a:80b::2004
}; };
} }

View File

@@ -0,0 +1,34 @@
{ config, lib, ... }:
let
cweb-cfg = config.services.calibre-web;
inherit (cweb-cfg) user group;
inherit (cweb-cfg.listen) ip port;
svc-dir = "/var/lib/${cweb-cfg.dataDir}";
in
# XXX: disabled because of runtime errors like:
# > File "/nix/store/c7jqvx980nlg9xhxi065cba61r2ain9y-calibre-web-0.6.19/lib/python3.10/site-packages/calibreweb/cps/db.py", line 926, in speaking_language
# > languages = self.session.query(Languages) \
# > AttributeError: 'NoneType' object has no attribute 'query'
lib.mkIf false
{
sane.persist.sys.byStore.plaintext = [
{ inherit user group; mode = "0700"; path = svc-dir; method = "bind"; }
];
services.calibre-web.enable = true;
services.calibre-web.listen.ip = "127.0.0.1";
# XXX: externally populate `${svc-dir}/metadata.db` (once) from
# <https://github.com/janeczku/calibre-web/blob/master/library/metadata.db>
# i don't know why you have to do this??
# services.calibre-web.options.calibreLibrary = svc-dir;
services.nginx.virtualHosts."calibre.uninsane.org" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://${ip}:${builtins.toString port}";
};
};
sane.dns.zones."uninsane.org".inet.CNAME."calibre" = "native";
}

View File

@@ -36,8 +36,7 @@
# - rb = received bytes # - rb = received bytes
# - sp = sent packets # - sp = sent packets
# - sb = sent bytes # - sb = sent bytes
{ lib, ... }:
{ config, lib, ... }:
let let
# TURN port range (inclusive). # TURN port range (inclusive).
# default coturn behavior is to use the upper quarter of all ports. i.e. 49152 - 65535. # default coturn behavior is to use the upper quarter of all ports. i.e. 49152 - 65535.
@@ -56,7 +55,7 @@ in
# protocol = [ "tcp" "udp" ]; # protocol = [ "tcp" "udp" ];
# # visibleTo.lan = true; # # visibleTo.lan = true;
# # visibleTo.wan = true; # # visibleTo.wan = true;
# visibleTo.ovpns = true; # forward traffic from the VPN to the root NS # visibleTo.ovpn = true; # forward traffic from the VPN to the root NS
# description = "colin-stun-turn"; # description = "colin-stun-turn";
# }; # };
# "5349" = { # "5349" = {
@@ -64,7 +63,7 @@ in
# protocol = [ "tcp" ]; # protocol = [ "tcp" ];
# # visibleTo.lan = true; # # visibleTo.lan = true;
# # visibleTo.wan = true; # # visibleTo.wan = true;
# visibleTo.ovpns = true; # visibleTo.ovpn = true;
# description = "colin-stun-turn-over-tls"; # description = "colin-stun-turn-over-tls";
# }; # };
# } # }
@@ -77,7 +76,7 @@ in
# protocol = [ "tcp" "udp" ]; # protocol = [ "tcp" "udp" ];
# # visibleTo.lan = true; # # visibleTo.lan = true;
# # visibleTo.wan = true; # # visibleTo.wan = true;
# visibleTo.ovpns = true; # visibleTo.ovpn = true;
# description = "colin-turn-${builtins.toString count}-of-${builtins.toString numPorts}"; # description = "colin-turn-${builtins.toString count}-of-${builtins.toString numPorts}";
# }; # };
# }) # })
@@ -131,11 +130,11 @@ in
"verbose" "verbose"
# "Verbose" #< even MORE verbosity than "verbose" (it's TOO MUCH verbosity really) # "Verbose" #< even MORE verbosity than "verbose" (it's TOO MUCH verbosity really)
"no-multicast-peers" # disables sending to IPv4 broadcast addresses (e.g. 224.0.0.0/3) "no-multicast-peers" # disables sending to IPv4 broadcast addresses (e.g. 224.0.0.0/3)
# "listening-ip=${config.sane.netns.ovpns.hostVethIpv4}" "external-ip=${config.sane.netns.ovpns.netnsPubIpv4}" #< 2024/04/25: works, if running in root namespace # "listening-ip=10.0.1.5" "external-ip=185.157.162.178" #< 2024/04/25: works, if running in root namespace
"listening-ip=${config.sane.netns.ovpns.netnsPubIpv4}" "external-ip=${config.sane.netns.ovpns.netnsPubIpv4}" "listening-ip=185.157.162.178" "external-ip=185.157.162.178"
# old attempts: # old attempts:
# "external-ip=${config.sane.netns.ovpns.netnsPubIpv4}/${config.sane.netns.ovpns.hostVethIpv4}" # "external-ip=185.157.162.178/10.0.1.5"
# "listening-ip=10.78.79.51" # can be specified multiple times; omit for * # "listening-ip=10.78.79.51" # can be specified multiple times; omit for *
# "external-ip=97.113.128.229/10.78.79.51" # "external-ip=97.113.128.229/10.78.79.51"
# "external-ip=97.113.128.229" # "external-ip=97.113.128.229"

View File

@@ -16,16 +16,14 @@
# - validate with `bitcoin-cli -netinfo` # - validate with `bitcoin-cli -netinfo`
{ config, lib, pkgs, sane-lib, ... }: { config, lib, pkgs, sane-lib, ... }:
let let
# bitcoind = config.sane.programs.bitcoind.packageUnwrapped;
bitcoind = pkgs.bitcoind;
# wrapper to run bitcoind with the tor onion address as externalip (computed at runtime) # wrapper to run bitcoind with the tor onion address as externalip (computed at runtime)
_bitcoindWithExternalIp = pkgs.writeShellScriptBin "bitcoind" '' _bitcoindWithExternalIp = with pkgs; writeShellScriptBin "bitcoind" ''
externalip="$(cat /var/lib/tor/onion/bitcoind/hostname)" externalip="$(cat /var/lib/tor/onion/bitcoind/hostname)"
exec ${bitcoind}/bin/bitcoind "-externalip=$externalip" "$@" 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. # 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: # therefore, even though services.bitcoind only needs `bitcoind` binary, provide all the other bitcoin-related binaries (notably `bitcoin-cli`) as well:
bitcoindWithExternalIp = pkgs.symlinkJoin { bitcoindWithExternalIp = with pkgs; symlinkJoin {
name = "bitcoind-with-external-ip"; name = "bitcoind-with-external-ip";
paths = [ _bitcoindWithExternalIp bitcoind ]; paths = [ _bitcoindWithExternalIp bitcoind ];
}; };

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
#!nix-shell -i python3 -p pyln-client -p python3 #!nix-shell -i python3 -p "python3.withPackages (ps: [ ps.pyln-client ])"
""" """
clightning-sane: helper to perform common Lightning node admin operations: clightning-sane: helper to perform common Lightning node admin operations:

View File

@@ -116,10 +116,7 @@
# - fee-per-satoshi=<ppm> # - fee-per-satoshi=<ppm>
# - feature configs (i.e. experimental-xyz options) # - feature configs (i.e. experimental-xyz options)
sane.services.clightning.extraConfig = '' sane.services.clightning.extraConfig = ''
# log levels: "io", "debug", "info", "unusual", "broken" log-level=debug:lightningd
log-level=info:lightningd
# log-level=debug:lightningd
# peerswap: # peerswap:
# - config example: <https://github.com/fort-nix/nix-bitcoin/pull/462/files#diff-b357d832705b8ce8df1f41934d613f79adb77c4cd5cd9e9eb12a163fca3e16c6> # - config example: <https://github.com/fort-nix/nix-bitcoin/pull/462/files#diff-b357d832705b8ce8df1f41934d613f79adb77c4cd5cd9e9eb12a163fca3e16c6>
# XXX: peerswap crashes clightning on launch. stacktrace is useless. # XXX: peerswap crashes clightning on launch. stacktrace is useless.
@@ -135,5 +132,4 @@
}; };
sane.programs.clightning.enableFor.user.colin = true; # for debugging/admin: `lightning-cli` sane.programs.clightning.enableFor.user.colin = true; # for debugging/admin: `lightning-cli`
sane.programs.clightning.packageUnwrapped = config.sane.services.clightning.package;
} }

View File

@@ -1,6 +1,7 @@
{ ... }: { ... }:
{ {
imports = [ imports = [
./calibre.nix
./coturn.nix ./coturn.nix
./cryptocurrencies ./cryptocurrencies
./email ./email
@@ -25,7 +26,7 @@
./postgres.nix ./postgres.nix
./prosody ./prosody
./slskd.nix ./slskd.nix
./transmission ./transmission.nix
./trust-dns.nix ./trust-dns.nix
./wikipedia.nix ./wikipedia.nix
]; ];

View File

@@ -51,54 +51,54 @@ lib.mkIf false
{ {
"3478" = { "3478" = {
protocol = [ "tcp" "udp" ]; protocol = [ "tcp" "udp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-stun-turn"; description = "colin-xmpp-stun-turn";
}; };
"5222" = { "5222" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-client-to-server"; description = "colin-xmpp-client-to-server";
}; };
"5223" = { "5223" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpps-client-to-server"; # XMPP over TLS description = "colin-xmpps-client-to-server"; # XMPP over TLS
}; };
"5269" = { "5269" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true; visibleTo.wan = true;
description = "colin-xmpp-server-to-server"; description = "colin-xmpp-server-to-server";
}; };
"5270" = { "5270" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true; visibleTo.wan = true;
description = "colin-xmpps-server-to-server"; # XMPP over TLS description = "colin-xmpps-server-to-server"; # XMPP over TLS
}; };
"5280" = { "5280" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-bosh"; description = "colin-xmpp-bosh";
}; };
"5281" = { "5281" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-bosh-https"; description = "colin-xmpp-bosh-https";
}; };
"5349" = { "5349" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-stun-turn-over-tls"; description = "colin-xmpp-stun-turn-over-tls";
}; };
"5443" = { "5443" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-web-services"; # file uploads, websockets, admin description = "colin-xmpp-web-services"; # file uploads, websockets, admin
}; };
} }
@@ -109,8 +109,8 @@ lib.mkIf false
numPorts = turnPortHigh - turnPortLow + 1; numPorts = turnPortHigh - turnPortLow + 1;
in { in {
protocol = [ "tcp" "udp" ]; protocol = [ "tcp" "udp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-turn-${builtins.toString count}-of-${builtins.toString numPorts}"; description = "colin-xmpp-turn-${builtins.toString count}-of-${builtins.toString numPorts}";
}; };
}) })

View File

@@ -8,14 +8,14 @@
{ {
sane.ports.ports."143" = { sane.ports.ports."143" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-imap-imap.uninsane.org"; description = "colin-imap-imap.uninsane.org";
}; };
sane.ports.ports."993" = { sane.ports.ports."993" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-imaps-imap.uninsane.org"; description = "colin-imaps-imap.uninsane.org";
}; };

View File

@@ -1,6 +1,6 @@
# postfix config options: <https://www.postfix.org/postconf.5.html> # postfix config options: <https://www.postfix.org/postconf.5.html>
{ config, lib, pkgs, ... }: { lib, pkgs, ... }:
let let
submissionOptions = { submissionOptions = {
@@ -56,7 +56,8 @@ in
sane.dns.zones."uninsane.org".inet = { sane.dns.zones."uninsane.org".inet = {
MX."@" = "10 mx.uninsane.org."; MX."@" = "10 mx.uninsane.org.";
A."mx" = "%AOVPNS%"; #< XXX: RFC's specify that the MX record CANNOT BE A CNAME. TODO: use "%AOVPNS%? # XXX: RFC's specify that the MX record CANNOT BE A CNAME
A."mx" = "185.157.162.178";
# Sender Policy Framework: # Sender Policy Framework:
# +mx => mail passes if it originated from the MX # +mx => mail passes if it originated from the MX

View File

@@ -12,10 +12,6 @@
device = "/var/media"; device = "/var/media";
options = [ "rbind" ]; options = [ "rbind" ];
}; };
fileSystems."/var/export/pub" = {
device = "/var/www/sites/uninsane.org/share";
options = [ "rbind" ];
};
# fileSystems."/var/export/playground" = { # fileSystems."/var/export/playground" = {
# device = config.fileSystems."/mnt/persist/ext".device; # device = config.fileSystems."/mnt/persist/ext".device;
# fsType = "btrfs"; # fsType = "btrfs";
@@ -41,8 +37,7 @@
wantedBy = [ "nfs.service" "sftpgo.service" ]; wantedBy = [ "nfs.service" "sftpgo.service" ];
file.text = '' file.text = ''
- media/ read-only: Videos, Music, Books, etc - media/ read-only: Videos, Music, Books, etc
- playground/ read-write: use it to share files with other users of this server, inaccessible from the www - playground/ read-write: use it to share files with other users of this server
- pub/ read-only: content made to be shared with the www
''; '';
}; };

View File

@@ -9,10 +9,10 @@
{ config, lib, pkgs, sane-lib, ... }: { config, lib, pkgs, sane-lib, ... }:
let let
external_auth_hook = pkgs.static-nix-shell.mkPython3 { external_auth_hook = pkgs.static-nix-shell.mkPython3Bin {
pname = "external_auth_hook"; pname = "external_auth_hook";
srcRoot = ./.; srcRoot = ./.;
pkgs = [ "python3.pkgs.passlib" ]; pyPkgs = [ "passlib" ];
}; };
# Client initiates a FTP "control connection" on port 21. # Client initiates a FTP "control connection" on port 21.
# - this handles the client -> server commands, and the server -> client status, but not the actual data # - this handles the client -> server commands, and the server -> client status, but not the actual data
@@ -27,12 +27,13 @@ in
"21" = { "21" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.lan = true; visibleTo.lan = true;
# visibleTo.wan = true;
description = "colin-FTP server"; description = "colin-FTP server";
}; };
"990" = { "990" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-FTPS server"; description = "colin-FTPS server";
}; };
} // (sane-lib.mapToAttrs } // (sane-lib.mapToAttrs
@@ -40,8 +41,8 @@ in
name = builtins.toString port; name = builtins.toString port;
value = { value = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-FTP server data port range"; description = "colin-FTP server data port range";
}; };
}) })
@@ -100,13 +101,6 @@ in
debug = true; debug = true;
tls_mode = 2; # 2 = "implicit FTPS": client negotiates TLS before any FTP command. tls_mode = 2; # 2 = "implicit FTPS": client negotiates TLS before any FTP command.
} }
{
# binding this means any doof client can connect (TLS only)
address = config.sane.netns.doof.hostVethIpv4;
port = 990;
debug = true;
tls_mode = 2; # 2 = "implicit FTPS": client negotiates TLS before any FTP command.
}
]; ];
# active mode is susceptible to "bounce attacks", without much benefit over passive mode # active mode is susceptible to "bounce attacks", without much benefit over passive mode
@@ -123,7 +117,7 @@ in
banner = '' banner = ''
Welcome, friends, to Colin's FTP server! Also available via NFS on the same host, but LAN-only. Welcome, friends, to Colin's FTP server! Also available via NFS on the same host, but LAN-only.
Read-only access (LAN clients see everything; WAN clients can only see /pub): Read-only access (LAN-restricted):
Username: "anonymous" Username: "anonymous"
Password: "anonymous" Password: "anonymous"

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
#!nix-shell -i python3 -p python3 -p python3.pkgs.passlib #!nix-shell -i python3 -p "python3.withPackages (ps: [ ps.passlib ])"
# vim: set filetype=python : # vim: set filetype=python :
# #
# available environment variables: # available environment variables:
@@ -45,8 +45,6 @@ from hmac import compare_digest
authFail = dict(username="") authFail = dict(username="")
PERM_DENY = []
PERM_LIST = [ "list" ]
PERM_RO = [ "list", "download" ] PERM_RO = [ "list", "download" ]
PERM_RW = [ PERM_RW = [
# read-only: # read-only:
@@ -129,14 +127,12 @@ def getAuthResponse(ip: str, username: str, password: str) -> dict:
return mkAuthOk(username, permissions = { return mkAuthOk(username, permissions = {
"/": PERM_RW, "/": PERM_RW,
"/playground": PERM_RW, "/playground": PERM_RW,
"/pub": PERM_RO,
}) })
if isWireguard(ip): if isWireguard(ip):
# allow any user from wireguard # allow any user from wireguard
return mkAuthOk(username, permissions = { return mkAuthOk(username, permissions = {
"/": PERM_RW, "/": PERM_RW,
"/playground": PERM_RW, "/playground": PERM_RW,
"/pub": PERM_RO,
}) })
if isLan(ip): if isLan(ip):
if username == "anonymous": if username == "anonymous":
@@ -144,18 +140,6 @@ def getAuthResponse(ip: str, username: str, password: str) -> dict:
return mkAuthOk("anonymous", permissions = { return mkAuthOk("anonymous", permissions = {
"/": PERM_RO, "/": PERM_RO,
"/playground": PERM_RW, "/playground": PERM_RW,
"/pub": PERM_RO,
})
if username == "anonymous":
# anonymous users from the www can have even more limited access.
# mostly because i need an easy way to test WAN connectivity :-)
return mkAuthOk("anonymous", permissions = {
# "/": PERM_DENY,
"/": PERM_LIST, #< REQUIRED, even for lftp to list a subdir
"/media": PERM_DENY,
"/playground": PERM_DENY,
"/pub": PERM_RO,
# "/README.md": PERM_RO, #< does not work
}) })
return authFail return authFail

View File

@@ -10,7 +10,6 @@
# ``` # ```
{ config, lib, pkgs, sane-lib, ... }: { config, lib, pkgs, sane-lib, ... }:
lib.mkIf false #< 2024/07/04: i haven't actively used this for months
{ {
sops.secrets."freshrss_passwd" = { sops.secrets."freshrss_passwd" = {
owner = config.users.users.freshrss.name; owner = config.users.users.freshrss.name;

View File

@@ -38,23 +38,19 @@
ROOT_URL = "https://git.uninsane.org/"; ROOT_URL = "https://git.uninsane.org/";
}; };
service = { service = {
# timeout for email approval. 5760 = 4 days. 10080 = 7 days # timeout for email approval. 5760 = 4 days
ACTIVE_CODE_LIVE_MINUTES = 10080; ACTIVE_CODE_LIVE_MINUTES = 5760;
# REGISTER_EMAIL_CONFIRM = false; # REGISTER_EMAIL_CONFIRM = false;
# REGISTER_MANUAL_CONFIRM = true; # REGISTER_MANUAL_CONFIRM = true;
REGISTER_EMAIL_CONFIRM = true; REGISTER_EMAIL_CONFIRM = true;
# not sure what this notifies *on*... # not sure what this notified on?
ENABLE_NOTIFY_MAIL = true; ENABLE_NOTIFY_MAIL = true;
# defaults to image-based captcha. # defaults to image-based captcha.
# also supports recaptcha (with custom URLs) or hCaptcha. # also supports recaptcha (with custom URLs) or hCaptcha.
ENABLE_CAPTCHA = true; ENABLE_CAPTCHA = true;
NOREPLY_ADDRESS = "noreply.anonymous.git@uninsane.org"; NOREPLY_ADDRESS = "noreply.anonymous.git@uninsane.org";
}; };
session = { session.COOKIE_SECURE = true;
COOKIE_SECURE = true;
# keep me logged in for 30 days
SESSION_LIFE_TIME = 60 * 60 * 24 * 30;
};
repository = { repository = {
DEFAULT_BRANCH = "master"; DEFAULT_BRANCH = "master";
ENABLE_PUSH_CREATE_USER = true; ENABLE_PUSH_CREATE_USER = true;
@@ -64,8 +60,8 @@
SHOW_FOOTER_TEMPLATE_LOAD_TIME = false; SHOW_FOOTER_TEMPLATE_LOAD_TIME = false;
}; };
ui = { ui = {
# options: "gitea-auto" (adapt to system theme), "gitea-dark", "gitea-light" # options: "auto", "gitea", "arc-green"
# DEFAULT_THEME = "gitea-auto"; DEFAULT_THEME = "arc-green";
# cache frontend assets if true # cache frontend assets if true
# USE_SERVICE_WORKER = true; # USE_SERVICE_WORKER = true;
}; };
@@ -74,10 +70,9 @@
# alternative is to use nixos-level config: # alternative is to use nixos-level config:
# services.gitea.mailerPasswordFile = ... # services.gitea.mailerPasswordFile = ...
ENABLED = true; ENABLED = true;
MAILER_TYPE = "sendmail";
FROM = "notify.git@uninsane.org"; FROM = "notify.git@uninsane.org";
PROTOCOL = "sendmail";
SENDMAIL_PATH = "${pkgs.postfix}/bin/sendmail"; SENDMAIL_PATH = "${pkgs.postfix}/bin/sendmail";
SENDMAIL_ARGS = "--"; # most "sendmail" programs take options, "--" will prevent an email address being interpreted as an option.
}; };
time = { time = {
# options: ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, RFC3339, RFC3339Nano, Kitchen, Stamp, StampMilli, StampMicro, StampNano # options: ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, RFC3339, RFC3339Nano, Kitchen, Stamp, StampMilli, StampMicro, StampNano
@@ -134,7 +129,7 @@
sane.ports.ports."22" = { sane.ports.ports."22" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.doof = true; visibleTo.wan = true;
description = "colin-git@git.uninsane.org"; description = "colin-git@git.uninsane.org";
}; };
} }

View File

@@ -1,5 +1,6 @@
{ config, lib, pkgs, ... }: { lib, pkgs, ... }:
lib.mkIf false #< TODO: re-enable once confident of sandboxing
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.byStore.plaintext = [
# TODO: mode? we only need this to save Indexer creds ==> migrate to config? # TODO: mode? we only need this to save Indexer creds ==> migrate to config?
@@ -12,7 +13,7 @@
systemd.services.jackett.serviceConfig = { systemd.services.jackett.serviceConfig = {
# run this behind the OVPN static VPN # run this behind the OVPN static VPN
NetworkNamespacePath = "/run/netns/ovpns"; NetworkNamespacePath = "/run/netns/ovpns";
ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect ${config.sane.netns.ovpns.netnsPubIpv4}" ]; # abort if public IP is not as expected ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect 185.157.162.178" ]; # abort if public IP is not as expected
# patch jackett to listen on the public interfaces # patch jackett to listen on the public interfaces
# ExecStart = lib.mkForce "${pkgs.jackett}/bin/Jackett --NoUpdates --DataFolder /var/lib/jackett/.config/Jackett --ListenPublic"; # ExecStart = lib.mkForce "${pkgs.jackett}/bin/Jackett --NoUpdates --DataFolder /var/lib/jackett/.config/Jackett --ListenPublic";
@@ -24,7 +25,8 @@
enableACME = true; enableACME = true;
# inherit kTLS; # inherit kTLS;
locations."/" = { locations."/" = {
proxyPass = "http://${config.sane.netns.ovpns.netnsVethIpv4}:9117"; # proxyPass = "http://ovpns.uninsane.org:9117";
proxyPass = "http://10.0.1.6:9117";
recommendedProxySettings = true; recommendedProxySettings = true;
}; };
}; };

View File

@@ -46,7 +46,6 @@ in {
}; };
systemd.services.lemmy.environment = { systemd.services.lemmy.environment = {
RUST_BACKTRACE = "full"; RUST_BACKTRACE = "full";
RUST_LOG = "warn";
# RUST_LOG = "debug"; # RUST_LOG = "debug";
# RUST_LOG = "trace"; # RUST_LOG = "trace";
# upstream defaults LEMMY_DATABASE_URL = "postgres:///lemmy?host=/run/postgresql"; # upstream defaults LEMMY_DATABASE_URL = "postgres:///lemmy?host=/run/postgresql";

View File

@@ -1,6 +1,6 @@
# docs: <https://nixos.wiki/wiki/Matrix> # docs: <https://nixos.wiki/wiki/Matrix>
# docs: <https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-synapse> # docs: <https://nixos.org/manual/nixos/stable/index.html#module-services-matrix-synapse>
# example config: <https://github.com/element-hq/synapse/blob/develop/docs/sample_config.yaml> # example config: <https://github.com/matrix-org/synapse/blob/develop/docs/sample_config.yaml>
# #
# ENABLING PUSH NOTIFICATIONS (with UnifiedPush/ntfy): # ENABLING PUSH NOTIFICATIONS (with UnifiedPush/ntfy):
# - Matrix "pushers" API spec: <https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3pushersset> # - Matrix "pushers" API spec: <https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3pushersset>
@@ -24,8 +24,10 @@
{ user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/matrix-synapse"; method = "bind"; } { user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/matrix-synapse"; method = "bind"; }
]; ];
services.matrix-synapse.enable = true; services.matrix-synapse.enable = true;
services.matrix-synapse.log.root.level = "WARNING"; # accepts "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL" (?)
services.matrix-synapse.settings = { services.matrix-synapse.settings = {
# this changes the default log level from INFO to WARN.
# maybe there's an easier way?
log_config = ./synapse-log_level.yaml;
server_name = "uninsane.org"; server_name = "uninsane.org";
# services.matrix-synapse.enable_registration_captcha = true; # services.matrix-synapse.enable_registration_captcha = true;

View File

@@ -1,13 +1,15 @@
# config docs: # config docs:
# - <https://github.com/matrix-org/matrix-appservice-irc/blob/develop/config.sample.yaml> # - <https://github.com/matrix-org/matrix-appservice-irc/blob/develop/config.sample.yaml>
# probably want to remove that.
{ config, lib, ... }: { config, lib, ... }:
let let
ircServer = { name, additionalAddresses ? [], ssl ? true, sasl ? true, port ? if ssl then 6697 else 6667 }: let ircServer = { name, additionalAddresses ? [], sasl ? true, port ? 6697 }: let
lowerName = lib.toLower name; lowerName = lib.toLower name;
in { in {
# XXX sasl: appservice doesn't support NickServ identification (only SASL, or PASS if sasl = false) # XXX sasl: appservice doesn't support NickServ identification (only SASL, or PASS if sasl = false)
inherit additionalAddresses name port sasl ssl; inherit name additionalAddresses sasl port;
ssl = true;
botConfig = { botConfig = {
# bot has no presence in IRC channel; only real Matrix users # bot has no presence in IRC channel; only real Matrix users
enabled = false; enabled = false;
@@ -127,7 +129,6 @@ in
}; };
ircService = { ircService = {
logging.level = "warn"; # "error", "warn", "info", "debug"
servers = { servers = {
"irc.esper.net" = ircServer { "irc.esper.net" = ircServer {
name = "esper"; name = "esper";
@@ -155,10 +156,6 @@ in
# - #sxmo-offtopic # - #sxmo-offtopic
}; };
"irc.rizon.net" = ircServer { name = "Rizon"; }; "irc.rizon.net" = ircServer { name = "Rizon"; };
"wigle.net" = ircServer {
name = "WiGLE";
ssl = false;
};
}; };
}; };
}; };

View File

@@ -0,0 +1,27 @@
version: 1
# In systemd's journal, loglevel is implicitly stored, so let's omit it
# from the message text.
formatters:
journal_fmt:
format: '%(name)s: [%(request)s] %(message)s'
filters:
context:
(): synapse.util.logcontext.LoggingContextFilter
request: ""
handlers:
journal:
class: systemd.journal.JournalHandler
formatter: journal_fmt
filters: [context]
SYSLOG_IDENTIFIER: synapse
# default log level: INFO
root:
level: WARN
handlers: [journal]
disable_existing_loggers: False

View File

@@ -17,14 +17,14 @@ in
sane.ports.ports."80" = { sane.ports.ports."80" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.ovpns = true; # so that letsencrypt can procure a cert for the mx record visibleTo.wan = true;
visibleTo.doof = true; visibleTo.ovpn = true; # so that letsencrypt can procure a cert for the mx record
description = "colin-http-uninsane.org"; description = "colin-http-uninsane.org";
}; };
sane.ports.ports."443" = { sane.ports.ports."443" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.doof = true; visibleTo.wan = true;
description = "colin-https-uninsane.org"; description = "colin-https-uninsane.org";
}; };

View File

@@ -86,7 +86,7 @@ in
sane.ports.ports."${builtins.toString altPort}" = { sane.ports.ports."${builtins.toString altPort}" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.doof = true; visibleTo.wan = true;
description = "colin-ntfy.uninsane.org"; description = "colin-ntfy.uninsane.org";
}; };
} }

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
#!nix-shell -i python3 -p ntfy-sh -p python3 #!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p ntfy-sh
import argparse import argparse
import logging import logging

View File

@@ -47,7 +47,7 @@ in
}; };
sane.ntfy-waiter.package = mkOption { sane.ntfy-waiter.package = mkOption {
type = types.package; type = types.package;
default = pkgs.static-nix-shell.mkPython3 { default = pkgs.static-nix-shell.mkPython3Bin {
pname = "ntfy-waiter"; pname = "ntfy-waiter";
srcRoot = ./.; srcRoot = ./.;
pkgs = [ "ntfy-sh" ]; pkgs = [ "ntfy-sh" ];
@@ -62,8 +62,8 @@ in
sane.ports.ports = lib.mkMerge (lib.forEach portRange (port: { sane.ports.ports = lib.mkMerge (lib.forEach portRange (port: {
"${builtins.toString port}" = { "${builtins.toString port}" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; 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 - portLow + 1)}-of-${builtins.toString numPorts}";
}; };
})); }));

View File

@@ -61,42 +61,42 @@ in
]; ];
sane.ports.ports."5000" = { sane.ports.ports."5000" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-prosody-fileshare-proxy65"; description = "colin-xmpp-prosody-fileshare-proxy65";
}; };
sane.ports.ports."5222" = { sane.ports.ports."5222" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-client-to-server"; description = "colin-xmpp-client-to-server";
}; };
sane.ports.ports."5223" = { sane.ports.ports."5223" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpps-client-to-server"; # XMPP over TLS description = "colin-xmpps-client-to-server"; # XMPP over TLS
}; };
sane.ports.ports."5269" = { sane.ports.ports."5269" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true; visibleTo.wan = true;
description = "colin-xmpp-server-to-server"; description = "colin-xmpp-server-to-server";
}; };
sane.ports.ports."5270" = { sane.ports.ports."5270" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true; visibleTo.wan = true;
description = "colin-xmpps-server-to-server"; # XMPP over TLS description = "colin-xmpps-server-to-server"; # XMPP over TLS
}; };
sane.ports.ports."5280" = { sane.ports.ports."5280" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-bosh"; description = "colin-xmpp-bosh";
}; };
sane.ports.ports."5281" = { sane.ports.ports."5281" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
visibleTo.doof = true;
visibleTo.lan = true; visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-prosody-https"; # necessary? description = "colin-xmpp-prosody-https"; # necessary?
}; };

View File

@@ -22,7 +22,8 @@
sane.ports.ports."50300" = { sane.ports.ports."50300" = {
protocol = [ "tcp" ]; protocol = [ "tcp" ];
# visibleTo.ovpns = true; #< not needed: it runs in the ovpns namespace # not visible to WAN: i run this in a separate netns
visibleTo.ovpn = true;
description = "colin-soulseek"; description = "colin-soulseek";
}; };
@@ -32,7 +33,7 @@
forceSSL = true; forceSSL = true;
enableACME = true; enableACME = true;
locations."/" = { locations."/" = {
proxyPass = "http://${config.sane.netns.ovpns.netnsVethIpv4}:5030"; proxyPass = "http://10.0.1.6:5030";
proxyWebsockets = true; proxyWebsockets = true;
}; };
}; };
@@ -71,7 +72,7 @@
systemd.services.slskd.serviceConfig = { systemd.services.slskd.serviceConfig = {
# run this behind the OVPN static VPN # run this behind the OVPN static VPN
NetworkNamespacePath = "/run/netns/ovpns"; NetworkNamespacePath = "/run/netns/ovpns";
ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect ${config.sane.netns.ovpns.netnsPubIpv4}" ]; # abort if public IP is not as expected ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect 185.157.162.178" ]; # abort if public IP is not as expected
Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
RestartSec = "60s"; RestartSec = "60s";

View File

@@ -22,19 +22,67 @@ let
--replace-fail 'set(TR_USER_AGENT_PREFIX "''${TR_SEMVER}")' 'set(TR_USER_AGENT_PREFIX "3.00")' --replace-fail 'set(TR_USER_AGENT_PREFIX "''${TR_SEMVER}")' 'set(TR_USER_AGENT_PREFIX "3.00")'
''; '';
}); });
download-dir = "/var/media/torrents"; #< keep in sync with consts embedded in `torrent-done` download-dir = "/var/media/torrents";
torrent-done = pkgs.static-nix-shell.mkBash { torrent-done = pkgs.writeShellApplication {
pname = "torrent-done"; name = "torrent-done";
srcRoot = ./.; runtimeInputs = with pkgs; [
pkgs = [ acl
"acl" coreutils
"coreutils" findutils
"findutils" rsync
"rsync" util-linux
"util-linux"
]; ];
text = ''
destructive() {
if [ -n "''${TR_DRY_RUN-}" ]; then
echo "$*"
else
"$@"
fi
}
if [[ "$TR_TORRENT_DIR" =~ ^.*freeleech.*$ ]]; then
# freeleech torrents have no place in my permanent library
echo "freeleech: nothing to do"
exit 0
fi
if ! [[ "$TR_TORRENT_DIR" =~ ^${download-dir}/.*$ ]]; then
echo "unexpected torrent dir, aborting: $TR_TORRENT_DIR"
exit 0
fi
REL_DIR="''${TR_TORRENT_DIR#${download-dir}/}"
MEDIA_DIR="/var/media/$REL_DIR"
destructive mkdir -p "$(dirname "$MEDIA_DIR")"
destructive rsync -arv "$TR_TORRENT_DIR/" "$MEDIA_DIR/"
# make the media rwx by anyone in the group
destructive find "$MEDIA_DIR" -type d -exec setfacl --recursive --modify d:g::rwx,o::rx {} \;
destructive find "$MEDIA_DIR" -type d -exec chmod g+rw,a+rx {} \;
# if there's a single directory inside the media dir, then inline that
subdirs=("$MEDIA_DIR"/*)
if [ ''${#subdirs} -eq 1 ]; then
dirname="''${subdirs[0]}"
if [ -d "$dirname" ]; then
mv "$dirname"/* "$MEDIA_DIR/" && rmdir "$dirname"
fi
fi
# remove noisy files:
find "$MEDIA_DIR/" -type f \(\
-iname 'www.YTS.*.jpg' \
-o -iname 'WWW.YIFY*.COM.jpg' \
-o -iname 'YIFY*.com.txt' \
-o -iname 'YTS*.com.txt' \
\) -exec rm {} \;
# dedupe the whole media library.
# yeah, a bit excessive: move this to a cron job if that's problematic.
destructive hardlink /var/media --reflink=always --ignore-time --verbose
'';
}; };
in in
lib.mkIf false #< TODO: re-enable once confident of sandboxing
{ {
sane.persist.sys.byStore.plaintext = [ sane.persist.sys.byStore.plaintext = [
# TODO: mode? we need this specifically for the stats tracking in .config/ # TODO: mode? we need this specifically for the stats tracking in .config/
@@ -58,8 +106,8 @@ in
# DOCUMENTATION/options list: <https://github.com/transmission/transmission/blob/main/docs/Editing-Configuration-Files.md#options> # DOCUMENTATION/options list: <https://github.com/transmission/transmission/blob/main/docs/Editing-Configuration-Files.md#options>
# message-level = 3; #< enable for debug logging. 0-3, default is 2. # message-level = 3; #< enable for debug logging. 0-3, default is 2.
# ovpns.netnsVethIpv4 => allow rpc only from the root servo ns. it'll tunnel things to the net, if need be. # 10.0.1.6 => allow rpc only from the root servo ns. it'll tunnel things to the net, if need be.
rpc-bind-address = config.sane.netns.ovpns.netnsVethIpv4; rpc-bind-address = "10.0.1.6";
#rpc-host-whitelist = "bt.uninsane.org"; #rpc-host-whitelist = "bt.uninsane.org";
#rpc-whitelist = "*.*.*.*"; #rpc-whitelist = "*.*.*.*";
rpc-authentication-required = true; rpc-authentication-required = true;
@@ -70,7 +118,7 @@ in
rpc-whitelist-enabled = false; rpc-whitelist-enabled = false;
# force behind ovpns in case the NetworkNamespace fails somehow # force behind ovpns in case the NetworkNamespace fails somehow
bind-address-ipv4 = config.sane.netns.ovpns.netnsPubIpv4; bind-address-ipv4 = "185.157.162.178";
port-forwarding-enabled = false; port-forwarding-enabled = false;
# hopefully, make the downloads world-readable # hopefully, make the downloads world-readable
@@ -112,7 +160,7 @@ in
systemd.services.transmission.serviceConfig = { systemd.services.transmission.serviceConfig = {
# run this behind the OVPN static VPN # run this behind the OVPN static VPN
NetworkNamespacePath = "/run/netns/ovpns"; NetworkNamespacePath = "/run/netns/ovpns";
ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect ${config.sane.netns.ovpns.netnsPubIpv4}" ]; # abort if public IP is not as expected ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect 185.157.162.178" ]; # abort if public IP is not as expected
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "30s"; RestartSec = "30s";
@@ -142,14 +190,14 @@ in
# inherit kTLS; # inherit kTLS;
locations."/" = { locations."/" = {
# proxyPass = "http://ovpns.uninsane.org:9091"; # proxyPass = "http://ovpns.uninsane.org:9091";
proxyPass = "http://${config.sane.netns.ovpns.netnsVethIpv4}:9091"; proxyPass = "http://10.0.1.6:9091";
}; };
}; };
sane.dns.zones."uninsane.org".inet.CNAME."bt" = "native"; sane.dns.zones."uninsane.org".inet.CNAME."bt" = "native";
sane.ports.ports."51413" = { sane.ports.ports."51413" = {
protocol = [ "tcp" "udp" ]; protocol = [ "tcp" "udp" ];
# visibleTo.ovpns = true; #< not needed: it runs in the ovpns namespace visibleTo.ovpn = true;
description = "colin-bittorrent"; description = "colin-bittorrent";
}; };
} }

View File

@@ -1,76 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p acl -p bash -p coreutils -p findutils -p rsync -p util-linux
# transmission invokes this with no args, and the following env vars:
# - TR_TORRENT_DIR: full path to the folder i told transmission to download it to.
# e.g. /var/media/torrents/Videos/Film/Jason.Bourne-2016
# optionally:
# - TR_DRY_RUN=1
# - TR_DEBUG=1
# - TR_NO_HARDLINK=1
DOWNLOAD_DIR=/var/media/torrents
destructive() {
if [ -n "${TR_DRY_RUN-}" ]; then
echo "[dry-run] $*"
else
debug "$@"
"$@"
fi
}
debug() {
if [ -n "${TR_DEBUG-}" ]; then
echo "$@"
fi
}
echo "TR_TORRENT_DIR=$TR_TORRENT_DIR torrent-done $*"
if [[ "$TR_TORRENT_DIR" =~ ^.*freeleech.*$ ]]; then
# freeleech torrents have no place in my permanent library
echo "freeleech: nothing to do"
exit 0
fi
if ! [[ "$TR_TORRENT_DIR" =~ ^$DOWNLOAD_DIR/.*$ ]]; then
echo "unexpected torrent dir, aborting: $TR_TORRENT_DIR"
exit 0
fi
REL_DIR="${TR_TORRENT_DIR#$DOWNLOAD_DIR/}"
MEDIA_DIR="/var/media/$REL_DIR"
destructive mkdir -p "$(dirname "$MEDIA_DIR")"
destructive rsync -arv "$TR_TORRENT_DIR/" "$MEDIA_DIR/"
# make the media rwx by anyone in the group
destructive find "$MEDIA_DIR" -type d -exec setfacl --recursive --modify d:g::rwx,o::rx {} \;
destructive find "$MEDIA_DIR" -type d -exec chmod g+rw,a+rx {} \;
# if there's a single directory inside the media dir, then inline that
subdirs=("$MEDIA_DIR"/*)
debug "top-level items in torrent dir:" "${subdirs[@]}"
if [ ${#subdirs[@]} -eq 1 ]; then
dirname="${subdirs[0]}"
debug "exactly one top-level item, checking if directory: $dirname"
if [ -d "$dirname" ]; then
destructive mv "$dirname"/* "$MEDIA_DIR/" && destructive rmdir "$dirname"
fi
fi
# remove noisy files:
destructive find "$MEDIA_DIR/" -type f \(\
-iname '.*downloaded.?from.*' \
-o -iname 'source.txt' \
-o -iname 'upcoming.?releases.*' \
-o -iname 'www.YTS.*.jpg' \
-o -iname 'WWW.YIFY*.COM.jpg' \
-o -iname 'YIFY*.com.txt' \
-o -iname 'YTS*.com.txt' \
\) -exec rm {} \;
if ! [ -n "${TR_NO_HARDLINK}" ]; then
# dedupe the whole media library.
# yeah, a bit excessive: move this to a cron job if that's problematic
# or make it run with only 1/N probability, etc.
destructive hardlink /var/media --reflink=always --ignore-time --verbose
fi

View File

@@ -4,14 +4,14 @@
let let
dyn-dns = config.sane.services.dyn-dns; dyn-dns = config.sane.services.dyn-dns;
nativeAddrs = lib.mapAttrs (_name: builtins.head) config.sane.dns.zones."uninsane.org".inet.A; nativeAddrs = lib.mapAttrs (_name: builtins.head) config.sane.dns.zones."uninsane.org".inet.A;
bindOvpn = "10.0.1.5";
in in
{ {
sane.ports.ports."53" = { sane.ports.ports."53" = {
protocol = [ "udp" "tcp" ]; protocol = [ "udp" "tcp" ];
visibleTo.lan = true; visibleTo.lan = true;
# visibleTo.wan = true; visibleTo.wan = true;
visibleTo.ovpns = true; visibleTo.ovpn = true;
visibleTo.doof = true;
description = "colin-dns-hosting"; description = "colin-dns-hosting";
}; };
@@ -39,7 +39,6 @@ in
CNAME."native" = "%CNAMENATIVE%"; CNAME."native" = "%CNAMENATIVE%";
A."@" = "%ANATIVE%"; A."@" = "%ANATIVE%";
A."servo.wan" = "%AWAN%"; A."servo.wan" = "%AWAN%";
A."servo.doof" = "%ADOOF%";
A."servo.lan" = config.sane.hosts.by-name."servo".lan-ip; A."servo.lan" = config.sane.hosts.by-name."servo".lan-ip;
A."servo.hn" = config.sane.hosts.by-name."servo".wg-home.ip; A."servo.hn" = config.sane.hosts.by-name."servo".wg-home.ip;
@@ -47,9 +46,9 @@ in
# it's best that we keep this identical, or a superset of, what org. lists as our NS. # it's best that we keep this identical, or a superset of, what org. lists as our NS.
# so, org. can specify ns2/ns3 as being to the VPN, with no mention of ns1. we provide ns1 here. # so, org. can specify ns2/ns3 as being to the VPN, with no mention of ns1. we provide ns1 here.
A."ns1" = "%ANATIVE%"; A."ns1" = "%ANATIVE%";
A."ns2" = "%ADOOF%"; A."ns2" = "185.157.162.178";
A."ns3" = "%AOVPNS%"; A."ns3" = "185.157.162.178";
A."ovpns" = "%AOVPNS%"; A."ovpns" = "185.157.162.178";
NS."@" = [ NS."@" = [
"ns1.uninsane.org." "ns1.uninsane.org."
"ns2.uninsane.org." "ns2.uninsane.org."
@@ -60,58 +59,79 @@ in
services.trust-dns.settings.zones = [ "uninsane.org" ]; services.trust-dns.settings.zones = [ "uninsane.org" ];
networking.nat.enable = true; #< TODO: try removing this? networking.nat.enable = true;
# networking.nat.extraCommands = '' networking.nat.extraCommands = ''
# # redirect incoming DNS requests from LAN addresses # redirect incoming DNS requests from LAN addresses
# # to the LAN-specialized DNS service # to the LAN-specialized DNS service
# # N.B.: use the `nixos-*` chains instead of e.g. PREROUTING # N.B.: use the `nixos-*` chains instead of e.g. PREROUTING
# # because they get cleanly reset across activations or `systemctl restart firewall` # because they get cleanly reset across activations or `systemctl restart firewall`
# # instead of accumulating cruft # instead of accumulating cruft
# iptables -t nat -A nixos-nat-pre -p udp --dport 53 \ iptables -t nat -A nixos-nat-pre -p udp --dport 53 \
# -m iprange --src-range 10.78.76.0-10.78.79.255 \ -m iprange --src-range 10.78.76.0-10.78.79.255 \
# -j DNAT --to-destination :1053 -j DNAT --to-destination :1053
# iptables -t nat -A nixos-nat-pre -p tcp --dport 53 \ iptables -t nat -A nixos-nat-pre -p tcp --dport 53 \
# -m iprange --src-range 10.78.76.0-10.78.79.255 \ -m iprange --src-range 10.78.76.0-10.78.79.255 \
# -j DNAT --to-destination :1053 -j DNAT --to-destination :1053
# ''; '';
# sane.ports.ports."1053" = { sane.ports.ports."1053" = {
# # because the NAT above redirects in nixos-nat-pre, LAN requests behave as though they arrived on the external interface at the redirected port. # because the NAT above redirects in nixos-nat-pre, LAN requests behave as though they arrived on the external interface at the redirected port.
# # TODO: try nixos-nat-post instead? # TODO: try nixos-nat-post instead?
# # TODO: or, don't NAT from port 53 -> port 1053, but rather nat from LAN addr to a loopback addr. # TODO: or, don't NAT from port 53 -> port 1053, but rather nat from LAN addr to a loopback addr.
# # - this is complicated in that loopback is a different interface than eth0, so rewriting the destination address would cause the packets to just be dropped by the interface # - this is complicated in that loopback is a different interface than eth0, so rewriting the destination address would cause the packets to just be dropped by the interface
# protocol = [ "udp" "tcp" ]; protocol = [ "udp" "tcp" ];
# visibleTo.lan = true; visibleTo.lan = true;
# description = "colin-redirected-dns-for-lan-namespace"; description = "colin-redirected-dns-for-lan-namespace";
# }; };
sane.services.trust-dns.enable = true; sane.services.trust-dns.enable = true;
sane.services.trust-dns.instances = let sane.services.trust-dns.instances = let
mkSubstitutions = flavor: { mkSubstitutions = flavor: {
"%ADOOF%" = config.sane.netns.doof.netnsPubIpv4;
"%ANATIVE%" = nativeAddrs."servo.${flavor}";
"%AOVPNS%" = config.sane.netns.ovpns.netnsPubIpv4;
"%AWAN%" = "$(cat '${dyn-dns.ipPath}')"; "%AWAN%" = "$(cat '${dyn-dns.ipPath}')";
"%CNAMENATIVE%" = "servo.${flavor}"; "%CNAMENATIVE%" = "servo.${flavor}";
"%ANATIVE%" = nativeAddrs."servo.${flavor}";
"%AOVPNS%" = "185.157.162.178";
}; };
in in
{ {
doof = { wan = {
substitutions = mkSubstitutions "doof"; substitutions = mkSubstitutions "wan";
listenAddrsIpv4 = [ listenAddrsIpv4 = [
config.sane.netns.doof.hostVethIpv4 nativeAddrs."servo.lan"
config.sane.netns.ovpns.hostVethIpv4 bindOvpn
]; ];
}; };
lan = {
substitutions = mkSubstitutions "lan";
listenAddrsIpv4 = [ nativeAddrs."servo.lan" ];
port = 1053;
};
hn = { hn = {
substitutions = mkSubstitutions "hn"; substitutions = mkSubstitutions "hn";
listenAddrsIpv4 = [ nativeAddrs."servo.hn" ]; listenAddrsIpv4 = [ nativeAddrs."servo.hn" ];
enableRecursiveResolver = true; #< allow wireguard clients to use this as their DNS resolver port = 1053;
};
# hn-resolver = {
# # don't need %AWAN% here because we forward to the hn instance.
# listenAddrsIpv4 = [ nativeAddrs."servo.hn" ];
# extraConfig = { # extraConfig = {
# zones = [ # zones = [
# { # {
# zone = "uninsane.org";
# zone_type = "Forward";
# stores = {
# type = "forward";
# name_servers = [
# {
# socket_addr = "${nativeAddrs."servo.hn"}:1053";
# protocol = "udp";
# trust_nx_responses = true;
# }
# ];
# };
# }
# {
# # forward the root zone to the local DNS resolver # # forward the root zone to the local DNS resolver
# # to allow wireguard clients to use this as their DNS resolver
# zone = "."; # zone = ".";
# zone_type = "Forward"; # zone_type = "Forward";
# stores = { # stores = {
@@ -127,24 +147,13 @@ in
# } # }
# ]; # ];
# }; # };
};
lan = {
substitutions = mkSubstitutions "lan";
listenAddrsIpv4 = [ nativeAddrs."servo.lan" ];
# port = 1053;
};
# wan = {
# substitutions = mkSubstitutions "wan";
# listenAddrsIpv4 = [
# nativeAddrs."servo.lan"
# ];
# }; # };
}; };
sane.services.dyn-dns.restartOnChange = [ sane.services.dyn-dns.restartOnChange = [
"trust-dns-doof.service" "trust-dns-wan.service"
"trust-dns-hn.service"
"trust-dns-lan.service" "trust-dns-lan.service"
# "trust-dns-wan.service" "trust-dns-hn.service"
# "trust-dns-hn-resolver.service" # doesn't need restart because it doesn't know about WAN IP
]; ];
} }

View File

@@ -9,7 +9,7 @@
./ids.nix ./ids.nix
./machine-id.nix ./machine-id.nix
./net ./net
./nix.nix ./nix
./persist.nix ./persist.nix
./polyunfill.nix ./polyunfill.nix
./programs ./programs

View File

@@ -1,9 +1,14 @@
# where to find good stuff? # where to find good stuff?
# - universal search/directory: <https://podcastindex.org> # - universal search/directory: <https://podcastindex.org>
# - list of lists: <https://en.wikipedia.org/wiki/Category:Lists_of_podcasts>
# - podcasts w/ a community: <https://lemmyverse.net/communities?query=podcast> # - podcasts w/ a community: <https://lemmyverse.net/communities?query=podcast>
# - podcast rec thread: <https://lemmy.ml/post/1565858> # - podcast rec thread: <https://lemmy.ml/post/1565858>
# #
# candidates:
# - The Nonlinear Library (podcast): <https://forum.effectivealtruism.org/posts/JTZTBienqWEAjGDRv/listen-to-more-ea-content-with-the-nonlinear-library>
# - has ~10 posts per day, text-to-speech; i would need better tagging before adding this
# - <https://www.metaculus.com/questions/11102/introducing-the-metaculus-journal-podcast/>
# - dead since 2022/10 - 2023/03
{ lib, sane-data, ... }: { lib, sane-data, ... }:
let let
hourly = { freq = "hourly"; }; hourly = { freq = "hourly"; };
@@ -75,7 +80,6 @@ let
(fromDb "feeds.simplecast.com/wgl4xEgL" // rat) # Econ Talk (fromDb "feeds.simplecast.com/wgl4xEgL" // rat) # Econ Talk
(fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura (fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura
(fromDb "feeds.transistor.fm/acquired" // tech) (fromDb "feeds.transistor.fm/acquired" // tech)
(fromDb "feeds.twit.tv/floss.xml" // tech)
(fromDb "fulltimenix.com" // tech) (fromDb "fulltimenix.com" // tech)
(fromDb "futureofcoding.org/episodes" // tech) (fromDb "futureofcoding.org/episodes" // tech)
(fromDb "hackerpublicradio.org" // tech) (fromDb "hackerpublicradio.org" // tech)
@@ -99,7 +103,6 @@ let
(fromDb "seattlenice.buzzsprout.com" // pol) (fromDb "seattlenice.buzzsprout.com" // pol)
(fromDb "srslywrong.com" // pol) (fromDb "srslywrong.com" // pol)
(fromDb "sharkbytes.transistor.fm" // tech) # Wireshark Podcast o_0 (fromDb "sharkbytes.transistor.fm" // tech) # Wireshark Podcast o_0
(fromDb "sharptech.fm/feed/podcast" // tech)
(fromDb "sscpodcast.libsyn.com" // rat) # Astral Codex Ten (fromDb "sscpodcast.libsyn.com" // rat) # Astral Codex Ten
(fromDb "talesfromthebridge.buzzsprout.com" // tech) # Sci-Fi? has Peter Watts; author of No Moods, Ads or Cutesy Fucking Icons (rifters.com) (fromDb "talesfromthebridge.buzzsprout.com" // tech) # Sci-Fi? has Peter Watts; author of No Moods, Ads or Cutesy Fucking Icons (rifters.com)
(fromDb "theamphour.com" // tech) (fromDb "theamphour.com" // tech)
@@ -132,7 +135,6 @@ let
(fromDb "artemis.sh" // tech) (fromDb "artemis.sh" // tech)
(fromDb "ascii.textfiles.com" // tech) # Jason Scott (fromDb "ascii.textfiles.com" // tech) # Jason Scott
(fromDb "austinvernon.site" // tech) (fromDb "austinvernon.site" // tech)
(fromDb "buttondown.email" // tech)
(fromDb "ben-evans.com/benedictevans" // pol) (fromDb "ben-evans.com/benedictevans" // pol)
(fromDb "bitbashing.io" // tech) (fromDb "bitbashing.io" // tech)
(fromDb "bitsaboutmoney.com" // uncat) (fromDb "bitsaboutmoney.com" // uncat)
@@ -193,8 +195,6 @@ let
(fromDb "weekinethereumnews.com" // tech) (fromDb "weekinethereumnews.com" // tech)
(fromDb "willow.phantoma.online") # wizard@xyzzy.link (fromDb "willow.phantoma.online") # wizard@xyzzy.link
(fromDb "xn--gckvb8fzb.com" // tech) (fromDb "xn--gckvb8fzb.com" // tech)
(fromDb "xorvoid.com" // tech)
(fromDb "www.thebignewsletter.com" // pol)
(mkSubstack "astralcodexten" // rat // daily) # Scott Alexander (mkSubstack "astralcodexten" // rat // daily) # Scott Alexander
(mkSubstack "eliqian" // rat // weekly) (mkSubstack "eliqian" // rat // weekly)
(mkSubstack "oversharing" // pol // daily) (mkSubstack "oversharing" // pol // daily)

View File

@@ -26,6 +26,10 @@ let
# lazyMount: defer mounting until first access from userspace. # lazyMount: defer mounting until first access from userspace.
# see: `man systemd.automount`, `man automount`, `man autofs` # see: `man systemd.automount`, `man automount`, `man autofs`
lazyMount = noauto ++ automount; lazyMount = noauto ++ automount;
wg = [
"x-systemd.requires=wireguard-wg-home.service"
"x-systemd.after=wireguard-wg-home.service"
];
fuse = [ fuse = [
"allow_other" # allow users other than the one who mounts it to access it. needed, if systemd is the one mounting this fs (as root) "allow_other" # allow users other than the one who mounts it to access it. needed, if systemd is the one mounting this fs (as root)
@@ -132,9 +136,9 @@ let
device = "ftp://servo-hn:/${subdir}"; device = "ftp://servo-hn:/${subdir}";
noCheck = true; noCheck = true;
fsType = "fuse.curlftpfs"; fsType = "fuse.curlftpfs";
options = fsOpts.ftp ++ fsOpts.noauto; options = fsOpts.ftp ++ fsOpts.noauto ++ fsOpts.wg;
# fsType = "nfs"; # fsType = "nfs";
# options = fsOpts.nfs ++ fsOpts.lazyMount; # options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg;
}; };
systemd.services."automount-servo-${utils.escapeSystemdPath subdir}" = let systemd.services."automount-servo-${utils.escapeSystemdPath subdir}" = let
fs = config.fileSystems."/mnt/servo/${subdir}"; fs = config.fileSystems."/mnt/servo/${subdir}";

View File

@@ -62,7 +62,6 @@
sane.ids.clightning.gid = 2419; sane.ids.clightning.gid = 2419;
sane.ids.nix-serve.uid = 2420; sane.ids.nix-serve.uid = 2420;
sane.ids.nix-serve.gid = 2420; sane.ids.nix-serve.gid = 2420;
sane.ids.plugdev.gid = 2421;
sane.ids.colin.uid = 1000; sane.ids.colin.uid = 1000;
sane.ids.guest.uid = 1100; sane.ids.guest.uid = 1100;

View File

@@ -20,7 +20,7 @@
# - each namespace may use a different /etc/resolv.conf to specify different DNS servers # - each namespace may use a different /etc/resolv.conf to specify different DNS servers
# - nscd breaks namespacing: the host nscd is unaware of the guest's /etc/resolv.conf, and so directs the guest's DNS requests to the host's servers. # - nscd breaks namespacing: the host nscd is unaware of the guest's /etc/resolv.conf, and so directs the guest's DNS requests to the host's servers.
# - this is fixed by either removing `/var/run/nscd/socket` from the namespace, or disabling nscd altogether. # - this is fixed by either removing `/var/run/nscd/socket` from the namespace, or disabling nscd altogether.
{ config, lib, pkgs, ... }: { config, lib, ... }:
lib.mkMerge [ lib.mkMerge [
{ {
sane.services.trust-dns.enable = lib.mkDefault config.sane.services.trust-dns.asSystemResolver; sane.services.trust-dns.enable = lib.mkDefault config.sane.services.trust-dns.asSystemResolver;
@@ -59,35 +59,15 @@ lib.mkMerge [
# in the netns and we query upstream DNS more often than needed. hm. # in the netns and we query upstream DNS more often than needed. hm.
# services.nscd.enableNsncd = true; # services.nscd.enableNsncd = true;
# disabling nscd LOSES US SOME FUNCTIONALITY. in particular, only the glibc-builtin modules are accessible via /etc/resolv.conf (er, did i mean /etc/nsswitch.conf?). # disabling nscd LOSES US SOME FUNCTIONALITY. in particular, only the glibc-builtin modules are accessible via /etc/resolv.conf.
# - dns: glibc-bultin # - dns: glibc-bultin
# - files: glibc-builtin # - files: glibc-builtin
# - myhostname: systemd # - myhostname: systemd
# - mymachines: systemd # - mymachines: systemd
# - resolve: systemd # - resolve: systemd
# in practice, i see no difference with nscd disabled. # in practice, i see no difference with nscd disabled.
# - the exception is when the system dns resolver doesn't do everything.
# for example, systemd-resolved does mDNS. hickory-dns does not. a hickory-dns system won't be mDNS-capable.
# disabling nscd VASTLY simplifies netns and process isolation. see explainer at top of file. # disabling nscd VASTLY simplifies netns and process isolation. see explainer at top of file.
services.nscd.enable = false; services.nscd.enable = false;
# system.nssModules = lib.mkForce []; system.nssModules = lib.mkForce [];
sane.silencedAssertions = [''.*Loading NSS modules from system.nssModules.*requires services.nscd.enable being set to true.*''];
# add NSS modules into their own subdirectory.
# then i can add just the NSS modules library path to the global LD_LIBRARY_PATH, rather than ALL of /run/current-system/sw/lib.
# TODO: i'm doing this so as to achieve mdns DNS resolution (avahi). it would be better to just have trust-dns delegate .local to avahi
# (except avahi doesn't act as a local resolver over DNS protocol -- only dbus).
environment.systemPackages = [(pkgs.symlinkJoin {
name = "nss-modules";
paths = config.system.nssModules.list;
postBuild = ''
mkdir nss
mv $out/lib/libnss_* nss
rm -rf $out
mkdir -p $out/lib
mv nss $out/lib
'';
})];
environment.variables.LD_LIBRARY_PATH = [ "/run/current-system/sw/lib/nss" ];
systemd.globalEnvironment.LD_LIBRARY_PATH = "/run/current-system/sw/lib/nss"; #< specifically for `geoclue.service`
} }
] ]

View File

@@ -14,7 +14,7 @@
# after = [ "polkit.service" ]; # after = [ "polkit.service" ];
# requires = [ "polkit.service" ]; # requires = [ "polkit.service" ];
wantedBy = [ "network.target" ]; #< default is `multi-user.target`, somehow it doesn't auto-start with that... wantedBy = [ "network.target" ]; #< default is `multi-user.target`, somehow it doesn't auto-start with that...
path = [ "/run/current-system/sw" ]; #< so it can find `sanebox` # path = [ "/run/current-system/sw" ]; #< so it can find `sanebox`
# serviceConfig.Type = "dbus"; # serviceConfig.Type = "dbus";
# serviceConfig.BusName = "org.freedesktop.ModemManager1"; # serviceConfig.BusName = "org.freedesktop.ModemManager1";

View File

@@ -59,18 +59,14 @@
# note the import starts at repo root: this allows `./overlay/default.nix` to access the stuff at the root # note the import starts at repo root: this allows `./overlay/default.nix` to access the stuff at the root
# "nixpkgs-overlays=${../../..}/hosts/common/nix-path/overlay" # "nixpkgs-overlays=${../../..}/hosts/common/nix-path/overlay"
# as long as my system itself doesn't rely on NIXPKGS at runtime, we can point the overlays to git # as long as my system itself doesn't rely on NIXPKGS at runtime, we can point the overlays to git
# to avoid `switch`ing so much during development. # to avoid switching so much during development
# TODO: it would be nice to remove this someday! "nixpkgs-overlays=/home/colin/dev/nixos/hosts/common/nix/overlay"
# it's an impurity that touches way more than i need and tends to cause hard-to-debug eval issues
# when it goes wrong. should i port my `nix-shell` scripts to something more tailored to my uses
# and then delete `nixpkgs-overlays`?
"nixpkgs-overlays=/home/colin/dev/nixos/integrations/nixpkgs/nixpkgs-overlays.nix"
]; ];
# ensure new deployments have a source of this repo with which they can bootstrap. # ensure new deployments have a source of this repo with which they can bootstrap.
# this however changes on every commit and can be slow to copy for e.g. `moby`. # this however changes on every commit and can be slow to copy for e.g. `moby`.
environment.etc."nixos" = lib.mkIf (config.sane.maxBuildCost >= 3) { environment.etc."nixos" = lib.mkIf (config.sane.maxBuildCost >= 3) {
source = pkgs.sane-nix-files; source = ../../..;
}; };
environment.etc."nix/registry.json" = lib.mkIf (config.sane.maxBuildCost < 3) { environment.etc."nix/registry.json" = lib.mkIf (config.sane.maxBuildCost < 3) {
enable = false; enable = false;

View File

@@ -0,0 +1,4 @@
# XXX: NIX_PATH=...:nixpkgs-overlays=... will import every overlay in the directory
# so we prefer to give it a directory with just this *one* overlay, otherwise it imports conflicting overlays
# and gets stuck in a loop until it OOMs
import ../../../../overlays/all.nix

View File

@@ -50,14 +50,12 @@ in
"fd" "fd"
"file" "file"
"forkstat" # monitor every spawned/forked process "forkstat" # monitor every spawned/forked process
"free"
# "fwupd" # "fwupd"
"gawk" "gawk"
"gdb" # to debug segfaults "gdb" # to debug segfaults
"git" "git"
"gptfdisk" # gdisk "gptfdisk" # gdisk
"hdparm" "hdparm"
"hping"
"htop" "htop"
"iftop" "iftop"
"inetutils" # for telnet "inetutils" # for telnet
@@ -86,7 +84,6 @@ in
"parted" "parted"
"pciutils" "pciutils"
"powertop" "powertop"
"ps"
"pstree" "pstree"
"ripgrep" "ripgrep"
"s6-rc" # service manager "s6-rc" # service manager
@@ -100,7 +97,6 @@ in
"usbutils" # lsusb "usbutils" # lsusb
"util-linux" # lsblk, lscpu, etc "util-linux" # lsblk, lscpu, etc
"valgrind" "valgrind"
"watch"
"wget" "wget"
"wirelesstools" # iwlist "wirelesstools" # iwlist
# "xq" # jq for XML # "xq" # jq for XML
@@ -152,7 +148,7 @@ in
# "ponymix" # "ponymix"
"pulsemixer" "pulsemixer"
"python3-repl" "python3-repl"
# "python3.pkgs.eyeD3" # music tagging # "python3Packages.eyeD3" # music tagging
"ripgrep" # needed as a user package so that its user-level config file can be installed "ripgrep" # needed as a user package so that its user-level config file can be installed
"rsync" "rsync"
"sane-scripts.bittorrent" "sane-scripts.bittorrent"
@@ -176,12 +172,8 @@ in
# "gh" # MS GitHub cli # "gh" # MS GitHub cli
"nix-index" "nix-index"
"nixpkgs-review" "nixpkgs-review"
"qmk-udev-rules"
"sane-scripts.dev" "sane-scripts.dev"
"sequoia" "sequoia"
# "via"
"wally-cli"
# "zsa-udev-rules"
]; ];
consoleMediaUtils = declPackageSet [ consoleMediaUtils = declPackageSet [
@@ -281,28 +273,27 @@ in
# "gnome.cheese" # "gnome.cheese"
# "gnome-feeds" # RSS reader (with claimed mobile support) # "gnome-feeds" # RSS reader (with claimed mobile support)
# "gnome.file-roller" # "gnome.file-roller"
"geary" # adaptive e-mail client; uses webkitgtk 4.1 "gnome.geary" # adaptive e-mail client; uses webkitgtk 4.1
"gnome-calculator" "gnome.gnome-calculator"
"gnome-calendar" "gnome.gnome-calendar"
"gnome.gnome-clocks" "gnome.gnome-clocks"
"gnome.gnome-maps" "gnome.gnome-maps"
# "gnome-podcasts" # "gnome-podcasts"
# "gnome.gnome-system-monitor" # "gnome.gnome-system-monitor"
# "gnome.gnome-terminal" # works on phosh # "gnome.gnome-terminal" # works on phosh
"gnome.gnome-weather" "gnome.gnome-weather"
# "seahorse" # keyring/secret manager # "gnome.seahorse" # keyring/secret manager
"gnome-frog" # OCR/QR decoder "gnome-frog" # OCR/QR decoder
"gpodder" "gpodder"
# "gst-device-monitor" # for debugging audio/video "gst-device-monitor" # for debugging audio/video
# "gthumb" # "gthumb"
# "lemoa" # lemmy app # "lemoa" # lemmy app
# "libcamera" # for `cam` binary (useful for debugging cameras) "libcamera" # for `cam` binary (useful for debugging cameras)
"libnotify" # for notify-send; debugging "libnotify" # for notify-send; debugging
# "lollypop" # "lollypop"
"loupe" # image viewer "loupe" # image viewer
"mate.engrampa" # archive manager "mate.engrampa" # archive manager
"mepo" # maps viewer "mepo" # maps viewer
# "mesa-demos" # for eglinfo, glxinfo & other testing tools
"mpv" "mpv"
"networkmanagerapplet" # for nm-connection-editor: it's better than not having any gui! "networkmanagerapplet" # for nm-connection-editor: it's better than not having any gui!
"ntfy-sh" # notification service "ntfy-sh" # notification service
@@ -312,7 +303,7 @@ in
# "picard" # music tagging # "picard" # music tagging
# "libsForQt5.plasmatube" # Youtube player # "libsForQt5.plasmatube" # Youtube player
"signal-desktop" "signal-desktop"
# "snapshot" # camera app "snapshot" # camera app
"spot" # Gnome Spotify client "spot" # Gnome Spotify client
# "sublime-music" # "sublime-music"
# "tdesktop" # broken on phosh # "tdesktop" # broken on phosh
@@ -327,7 +318,7 @@ in
handheldGuiApps = declPackageSet [ handheldGuiApps = declPackageSet [
# "celluloid" # mpv frontend # "celluloid" # mpv frontend
# "chatty" # matrix/xmpp/irc client (2023/12/29: disabled because broken cross build) # "chatty" # matrix/xmpp/irc client (2023/12/29: disabled because broken cross build)
# "cozy" # audiobook player "cozy" # audiobook player
"epiphany" # gnome's web browser "epiphany" # gnome's web browser
# "iotas" # note taking app # "iotas" # note taking app
"komikku" "komikku"
@@ -353,7 +344,7 @@ in
# "chromium" # chromium takes hours to build. brave is chromium-based, distributed in binary form, so prefer it. # "chromium" # chromium takes hours to build. brave is chromium-based, distributed in binary form, so prefer it.
# "cups" # "cups"
"discord" # x86-only "discord" # x86-only
# "electrum" "electrum"
"element-desktop" "element-desktop"
"firefox" "firefox"
"font-manager" "font-manager"
@@ -361,14 +352,13 @@ in
"gimp" # broken on phosh "gimp" # broken on phosh
# "gnome.dconf-editor" # "gnome.dconf-editor"
# "gnome.file-roller" # "gnome.file-roller"
"gnome-disk-utility" "gnome.gnome-disk-utility"
"nautilus" # file browser "gnome.nautilus" # file browser
# "gnome.totem" # video player, supposedly supports UPnP # "gnome.totem" # video player, supposedly supports UPnP
# "handbrake" #< TODO: fix build "handbrake"
"inkscape" "inkscape"
# "jellyfin-media-player" # "jellyfin-media-player"
"kdenlive" "kdenlive"
# "keymapp"
# "kid3" # audio tagging # "kid3" # audio tagging
"krita" "krita"
"libreoffice" # TODO: replace with an office suite that uses saner packaging? "libreoffice" # TODO: replace with an office suite that uses saner packaging?
@@ -400,12 +390,6 @@ in
backblaze-b2 = {}; backblaze-b2 = {};
bitcoind.sandbox.method = "bwrap";
bitcoind.sandbox.extraHomePaths = [
".config/bitcoin/bitcoin.conf"
];
bitcoind.sandbox.net = "all"; # actually needs only localhost
blanket.buildCost = 1; blanket.buildCost = 1;
blanket.sandbox.method = "bwrap"; blanket.sandbox.method = "bwrap";
blanket.sandbox.whitelistAudio = true; blanket.sandbox.whitelistAudio = true;
@@ -434,16 +418,6 @@ in
clang = {}; clang = {};
clightning.sandbox.method = "bwrap";
clightning.sandbox.extraHomePaths = [
".lightning/bitcoin/lightning-rpc"
];
clightning-sane.sandbox.method = "bwrap";
clightning-sane.sandbox.extraPaths = [
"/var/lib/clightning/bitcoin/lightning-rpc"
];
# cryptsetup: typical use is `cryptsetup open /dev/loopxyz mappedName`, and creates `/dev/mapper/mappedName` # cryptsetup: typical use is `cryptsetup open /dev/loopxyz mappedName`, and creates `/dev/mapper/mappedName`
cryptsetup.sandbox.method = "landlock"; cryptsetup.sandbox.method = "landlock";
cryptsetup.sandbox.extraPaths = [ cryptsetup.sandbox.extraPaths = [
@@ -596,6 +570,10 @@ in
gawk.sandbox.wrapperType = "inplace"; # /share/gawk libraries refer to /libexec gawk.sandbox.wrapperType = "inplace"; # /share/gawk libraries refer to /libexec
gawk.sandbox.autodetectCliPaths = "existingFile"; gawk.sandbox.autodetectCliPaths = "existingFile";
gdb.sandbox.enable = false; # gdb doesn't sandbox well. i don't know how you could.
# gdb.sandbox.method = "landlock"; # permission denied when trying to attach, even as root
gdb.sandbox.autodetectCliPaths = true;
geoclue2-with-demo-agent = {}; geoclue2-with-demo-agent = {};
# MS GitHub stores auth token in .config # MS GitHub stores auth token in .config
@@ -622,37 +600,32 @@ in
"/tmp" # "Cannot open display:" if it can't mount /tmp 👀 "/tmp" # "Cannot open display:" if it can't mount /tmp 👀
]; ];
gnome-calculator.buildCost = 1; "gnome.gnome-calculator".buildCost = 1;
gnome-calculator.sandbox.method = "bwrap"; "gnome.gnome-calculator".sandbox.method = "bwrap";
gnome-calculator.sandbox.whitelistWayland = true; "gnome.gnome-calculator".sandbox.whitelistWayland = true;
gnome-calendar.buildCost = 1; "gnome.gnome-calendar".buildCost = 1;
# gnome-calendar surely has data to persist, but i use it strictly to do date math, not track events. # gnome-calendar surely has data to persist, but i use it strictly to do date math, not track events.
gnome-calendar.sandbox.method = "bwrap"; "gnome.gnome-calendar".sandbox.method = "bwrap";
gnome-calendar.sandbox.whitelistWayland = true; "gnome.gnome-calendar".sandbox.whitelistWayland = true;
# gnome-disks # gnome-disks
gnome-disk-utility.buildCost = 1; "gnome.gnome-disk-utility".buildCost = 1;
gnome-disk-utility.sandbox.method = "bwrap"; "gnome.gnome-disk-utility".sandbox.method = "bwrap";
gnome-disk-utility.sandbox.whitelistDbus = [ "system" ]; "gnome.gnome-disk-utility".sandbox.whitelistDbus = [ "system" ];
gnome-disk-utility.sandbox.whitelistWayland = true; "gnome.gnome-disk-utility".sandbox.whitelistWayland = true;
gnome-disk-utility.sandbox.extraHomePaths = [ "gnome.gnome-disk-utility".sandbox.extraHomePaths = [
"tmp" "tmp"
"use/iso" "use/iso"
# TODO: probably need /dev and such # TODO: probably need /dev and such
]; ];
hping.sandbox.method = "landlock";
hping.sandbox.net = "all";
hping.sandbox.capabilities = [ "net_raw" ];
hping.sandbox.autodetectCliPaths = "existingFile"; # for sending packet data from file
# seahorse: dump gnome-keyring secrets. # seahorse: dump gnome-keyring secrets.
seahorse.buildCost = 1; "gnome.seahorse".buildCost = 1;
# N.B. it can lso manage ~/.ssh keys, but i explicitly don't add those to the sandbox for now. # N.B.: it can also manage ~/.ssh keys, but i explicitly don't add those to the sandbox for now.
seahorse.sandbox.method = "bwrap"; "gnome.seahorse".sandbox.method = "bwrap";
seahorse.sandbox.whitelistDbus = [ "user" ]; "gnome.seahorse".sandbox.whitelistDbus = [ "user" ];
seahorse.sandbox.whitelistWayland = true; "gnome.seahorse".sandbox.whitelistWayland = true;
gnome-2048.buildCost = 1; gnome-2048.buildCost = 1;
gnome-2048.sandbox.method = "bwrap"; gnome-2048.sandbox.method = "bwrap";
@@ -842,11 +815,6 @@ in
mercurial.sandbox.net = "clearnet"; mercurial.sandbox.net = "clearnet";
mercurial.sandbox.whitelistPwd = true; mercurial.sandbox.whitelistPwd = true;
mesa-demos.sandbox.method = "bwrap";
mesa-demos.sandbox.whitelistDri = true;
mesa-demos.sandbox.whitelistWayland = true;
mesa-demos.sandbox.whitelistX = true;
# actual monero blockchain (not wallet/etc; safe to delete, just slow to regenerate) # actual monero blockchain (not wallet/etc; safe to delete, just slow to regenerate)
monero-gui.buildCost = 1; monero-gui.buildCost = 1;
# XXX: is it really safe to persist this? it doesn't have info that could de-anonymize if captured? # XXX: is it really safe to persist this? it doesn't have info that could de-anonymize if captured?
@@ -975,9 +943,7 @@ in
python3-repl.packageUnwrapped = pkgs.python3.withPackages (ps: with ps; [ python3-repl.packageUnwrapped = pkgs.python3.withPackages (ps: with ps; [
psutil psutil
pykakasi
requests requests
unidecode
]); ]);
python3-repl.sandbox.method = "bwrap"; python3-repl.sandbox.method = "bwrap";
python3-repl.sandbox.net = "clearnet"; python3-repl.sandbox.net = "clearnet";
@@ -1051,9 +1017,7 @@ in
"Music" "Music"
"tmp" "tmp"
"use" "use"
".config/dconf"
]; ];
soundconverter.sandbox.whitelistDbus = [ "user" ]; # for dconf
soundconverter.sandbox.extraPaths = [ soundconverter.sandbox.extraPaths = [
"/mnt/servo/media/Music" "/mnt/servo/media/Music"
"/mnt/servo/media/games" "/mnt/servo/media/games"
@@ -1133,6 +1097,9 @@ in
valgrind.buildCost = 1; valgrind.buildCost = 1;
valgrind.sandbox.enable = false; #< it's a launcher: can't sandbox valgrind.sandbox.enable = false; #< it's a launcher: can't sandbox
visidata.sandbox.method = "bwrap"; # TODO:sandbox: untested
visidata.sandbox.autodetectCliPaths = true;
# `vulkaninfo`, `vkcube` # `vulkaninfo`, `vkcube`
vulkan-tools.sandbox.method = "landlock"; vulkan-tools.sandbox.method = "landlock";
@@ -1150,8 +1117,6 @@ in
"tmp" "tmp"
]; ];
watch.sandbox.enable = false; #< it executes the command it's given
wdisplays.sandbox.method = "bwrap"; wdisplays.sandbox.method = "bwrap";
wdisplays.sandbox.whitelistWayland = true; wdisplays.sandbox.whitelistWayland = true;
@@ -1190,6 +1155,8 @@ in
yt-dlp.sandbox.method = "bwrap"; # TODO:sandbox: untested yt-dlp.sandbox.method = "bwrap"; # TODO:sandbox: untested
yt-dlp.sandbox.net = "all"; yt-dlp.sandbox.net = "all";
yt-dlp.sandbox.whitelistPwd = true; # saves to pwd by default yt-dlp.sandbox.whitelistPwd = true; # saves to pwd by default
zfs = {};
}; };
sane.persist.sys.byStore.plaintext = lib.mkIf config.sane.programs.guiApps.enabled [ sane.persist.sys.byStore.plaintext = lib.mkIf config.sane.programs.guiApps.enabled [
@@ -1206,12 +1173,13 @@ in
]; ];
}; };
hardware.graphics = lib.mkIf config.sane.programs.guiApps.enabled ({ hardware.opengl = lib.mkIf config.sane.programs.guiApps.enabled ({
enable = true; enable = true;
driSupport = lib.mkDefault true;
} // (lib.optionalAttrs pkgs.stdenv.isx86_64 { } // (lib.optionalAttrs pkgs.stdenv.isx86_64 {
# for 32 bit applications # for 32 bit applications
# upstream nixpkgs forbids setting enable32Bit unless specifically x86_64 (so aarch64 isn't allowed) # upstream nixpkgs forbids setting driSupport32Bit unless specifically x86_64 (so aarch64 isn't allowed)
enable32Bit = lib.mkDefault true; driSupport32Bit = lib.mkDefault true;
})); }));
system.activationScripts.notifyActive = lib.mkIf config.sane.programs.guiApps.enabled { system.activationScripts.notifyActive = lib.mkIf config.sane.programs.guiApps.enabled {

View File

@@ -1,42 +0,0 @@
# Avahi zeroconf (mDNS) implementation.
# runs as systemd `avahi-daemon.service`
#
# - <https://avahi.org/>
# - code: <https://github.com/avahi/avahi>
# - IRC: #avahi on irc.libera.chat
#
# - `avahi-browse --help` for usage
# - `man avahi-daemon.conf`
# - `LD_LIBRARY_PATH=/nix/store/ngwj3jqmxh8k4qji2z0lj7y1f8vzqrn2-nss-mdns-0.15.1/lib getent hosts desko.local`
# nss-mdns goes through avahi-daemon, so there IS caching here
#
{ config, lib, ... }:
{
sane.programs.avahi = {
sandbox.method = "bwrap";
sandbox.whitelistDbus = [ "system" ];
sandbox.net = "all"; #< otherwise it will show 'null' in place of each interface name.
sandbox.extraPaths = [
"/" #< else the daemon exits immediately. TODO: decrease this scope.
];
};
services.avahi = lib.mkIf config.sane.programs.avahi.enabled {
enable = true;
package = config.sane.programs.avahi.package;
publish.enable = true;
publish.userServices = true;
nssmdns4 = true;
nssmdns6 = true;
# reflector = true;
allowInterfaces = [
# particularly, the default config disallows loopback, which is kinda fucking retarded, right?
"ens1" #< servo
"enp5s0" #< desko
"lo"
"wg-home"
"wlan0" #< moby
"wlp3s0" #< lappy
"wlp4s0" #< desko
];
};
}

View File

@@ -95,7 +95,7 @@ in
packageUnwrapped = pkgs.bemenu.overrideAttrs (upstream: { packageUnwrapped = pkgs.bemenu.overrideAttrs (upstream: {
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ [ nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ [
pkgs.makeBinaryWrapper pkgs.makeWrapper
]; ];
# can alternatively be specified as CLI flags # can alternatively be specified as CLI flags
postInstall = (upstream.postInstall or "") + '' postInstall = (upstream.postInstall or "") + ''

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
#!nix-shell -i python3 -p blast-ugjka -p python3 #!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p blast-ugjka
# vim: set filetype=python : # vim: set filetype=python :
import logging import logging

View File

@@ -31,7 +31,7 @@ in
sane.programs.blast-to-default = { sane.programs.blast-to-default = {
# helper to deal with blast's interactive CLI # helper to deal with blast's interactive CLI
packageUnwrapped = pkgs.static-nix-shell.mkPython3 { packageUnwrapped = pkgs.static-nix-shell.mkPython3Bin {
pname = "blast-to-default"; pname = "blast-to-default";
pkgs = [ "blast-ugjka" ]; pkgs = [ "blast-ugjka" ];
srcRoot = ./.; srcRoot = ./.;

View File

@@ -111,7 +111,7 @@ in
''; '';
}); });
fs.".config/bonsai/bonsai_tree.json".symlink.target = pkgs.writers.writeJSON "bonsai_tree.json" cfg.config.transitions; fs.".config/bonsai/bonsai_tree.json".symlink.text = builtins.toJSON cfg.config.transitions;
sandbox.method = "bwrap"; sandbox.method = "bwrap";
sandbox.extraRuntimePaths = [ sandbox.extraRuntimePaths = [

View File

@@ -13,7 +13,7 @@
sane.programs.callaudiod = { sane.programs.callaudiod = {
packageUnwrapped = pkgs.rmDbusServices pkgs.callaudiod; packageUnwrapped = pkgs.rmDbusServices pkgs.callaudiod;
sandbox.method = "bwrap"; # probably more needed once i enable proper sandboxing, but for now this ensures the service isn't started too early!
sandbox.whitelistAudio = true; sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; sandbox.whitelistDbus = [ "user" ];

View File

@@ -24,7 +24,7 @@ in
}; };
}; };
packageUnwrapped = pkgs.rmDbusServicesInPlace (pkgs.calls.overrideAttrs (upstream: { packageUnwrapped = pkgs.calls.overrideAttrs (upstream: {
patches = (upstream.patches or []) ++ [ patches = (upstream.patches or []) ++ [
(pkgs.fetchpatch { (pkgs.fetchpatch {
# usability improvement... if the UI is visible, then i can receive calls. otherwise, i can't! # usability improvement... if the UI is visible, then i can receive calls. otherwise, i can't!
@@ -33,10 +33,10 @@ in
hash = "sha256-NoVQV2TlkCcsBt0uwSyK82hBKySUW4pADrJVfLFvWgU="; hash = "sha256-NoVQV2TlkCcsBt0uwSyK82hBKySUW4pADrJVfLFvWgU=";
}) })
]; ];
})); });
sandbox.method = "bwrap"; sandbox.method = "bwrap";
sandbox.net = "vpn.wg-home"; #< XXX(2024/07/05): my cell carrier seems to block RTP, so tunnel it. sandbox.net = "clearnet";
sandbox.whitelistAudio = true; sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; # necessary for secrets, at the minimum sandbox.whitelistDbus = [ "user" ]; # necessary for secrets, at the minimum
sandbox.whitelistWayland = true; sandbox.whitelistWayland = true;

View File

@@ -66,7 +66,6 @@ end
if vars.percent ~= nil then if vars.percent ~= nil then
bat_args = bat_args .. " --percent-suffix '" .. vars.percent .. "'" bat_args = bat_args .. " --percent-suffix '" .. vars.percent .. "'"
end end
bat_args = bat_args .. " {bat}"
-- N.B.: `[[ <text> ]]` is Lua's multiline string literal -- N.B.: `[[ <text> ]]` is Lua's multiline string literal
conky.text = [[ conky.text = [[
@@ -74,8 +73,8 @@ ${color1}${shadecolor 707070}${font sans-serif:size=50:style=Bold}${alignc}${exe
${color2}${shadecolor a4d7d0}${font sans-serif:size=20}${alignc}${exec date +"%a %d %b"}${font} ${color2}${shadecolor a4d7d0}${font sans-serif:size=20}${alignc}${exec date +"%a %d %b"}${font}
${color1}${shadecolor}${font sans-serif:size=22:style=Bold}${alignc}${execp sane-sysload ]] .. bat_args .. [[ }${font} ${color1}${shadecolor}${font sans-serif:size=22:style=Bold}${alignc}${execp @bat@ ]] .. bat_args .. [[ }${font}
${color1}${shadecolor}${font sans-serif:size=20:style=Bold}${alignc}${texeci 600 timeout 20 sane-weather }${font} ${color1}${shadecolor}${font sans-serif:size=20:style=Bold}${alignc}${texeci 600 @weather@ }${font}
${color2}${shadecolor a4d7d0}${font sans-serif:size=16}${alignc}⇅ ${downspeedf wlan0}]] .. vars.kBps .. [[${font} ${color2}${shadecolor a4d7d0}${font sans-serif:size=16}${alignc}⇅ ${downspeedf wlan0}]] .. vars.kBps .. [[${font}

View File

@@ -1,22 +1,38 @@
{ pkgs, ... }: { pkgs, ... }:
{ {
sane.programs.sane-battery-estimate = {
packageUnwrapped = pkgs.static-nix-shell.mkBash {
pname = "sane-battery-estimate";
srcRoot = ./.;
};
sandbox.method = "bwrap";
sandbox.extraPaths = [
"/sys/class/power_supply"
"/sys/devices"
];
};
sane.programs.conky = { sane.programs.conky = {
sandbox.method = "bwrap"; sandbox.method = "bwrap";
sandbox.net = "clearnet"; #< for the scripts it calls (weather) sandbox.net = "clearnet"; #< for the scripts it calls (weather)
sandbox.extraPaths = [ sandbox.extraPaths = [
"/sys/class/power_supply" "/sys/class/power_supply"
"/sys/devices" # needed by sane-sysload "/sys/devices" # needed by battery_estimate
# "/sys/devices/cpu" # "/sys/devices/cpu"
# "/sys/devices/system" # "/sys/devices/system"
]; ];
sandbox.whitelistWayland = true; sandbox.whitelistWayland = true;
suggestedPrograms = [ suggestedPrograms = [
"sane-sysload" "sane-battery-estimate"
"sane-weather" "sane-weather"
]; ];
fs.".config/conky/conky.conf".symlink.target = ./conky.conf; fs.".config/conky/conky.conf".symlink.target = pkgs.substituteAll {
src = ./conky.conf;
bat = "sane-battery-estimate";
weather = "timeout 20 sane-weather";
};
services.conky = { services.conky = {
description = "conky dynamic desktop background"; description = "conky dynamic desktop background";

View File

@@ -0,0 +1,183 @@
#!/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 may 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: 208⧗
symbol_hr=""
symbol_min="⧗"
# variants:
# symbol_hr=":"
# symbol_min="⧖"
# symbol_min="⌛"
# render time like: 2'08"
# symbol_hr="'"
# symbol_min='"'
log() {
if [ "$BATTERY_ESTIMATE_DEBUG" = "1" ]; then
printf "$@" >&2
echo >&2
fi
}
render_icon() {
# args:
# 1: "chg" or "dis"
# 2: current battery percentage
level=$(($2 / 25))
level=$(($level > 3 ? 3 : $level))
level=$(($level < 0 ? 0 : $level))
log "icon: %s %d" "$1" "$level"
if [ "$1" = "dis" ]; then
printf "%s" "${icon_bat_dis[$level]}"
elif [ "$1" = "chg" ]; then
printf "%s" "${icon_bat_chg[$level]}"
fi
}
try_path() {
# assigns output variables:
# - perc, perc_from_full (0-100)
# - full, rate (pos means charging)
if [ -f "$1/capacity" ]; then
log "perc, perc_from_full from %s" "$1/capacity"
perc=$(cat "$1/capacity")
perc_from_full=$((100 - $perc))
fi
if [ -f "$1/charge_full_design" ] && [ -f "$1/current_now" ]; then
log "full, rate from %s and %s" "$1/charge_full_design" "$1/current_now"
# current is positive when charging
full=$(cat "$1/charge_full_design")
rate=$(cat "$1/current_now")
elif [ -f "$1/energy_full" ] && [ -f "$1/power_now" ]; then
log "full, rate from %s and %s" "$1/energy_full" "$1/power_now"
# power_now is positive when discharging
full=$(cat "$1/energy_full")
rate=-$(cat "$1/power_now")
elif [ -f "$1/energy_full" ] && [ -f "$1/energy_now" ]; then
log "full, rate from %s and %s" "$1/energy_full" "$1/energy_now"
log " this is a compatibility path for legacy Thinkpad batteries which do not populate the 'power_now' field, and incorrectly populate 'energy_now' with power info"
# energy_now is positive when discharging
full=$(cat "$1/energy_full")
rate=-$(cat "$1/energy_now")
fi
}
try_all_paths() {
try_path "/sys/class/power_supply/axp20x-battery" # Pinephone
try_path "/sys/class/power_supply/BAT0" # Thinkpad
log "perc: %d, perc_from_full: %d" "$perc" "$perc_from_full"
log "full: %f, rate: %f" "$full" "$rate"
log " rate > 0 means charging, else discharging"
}
fmt_minutes() {
# 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
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
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

View File

@@ -10,7 +10,6 @@
./assorted.nix ./assorted.nix
./audacity.nix ./audacity.nix
./ausyscall.nix ./ausyscall.nix
./avahi.nix
./bemenu.nix ./bemenu.nix
./blast-ugjka ./blast-ugjka
./bonsai.nix ./bonsai.nix
@@ -46,16 +45,12 @@
./flare-signal.nix ./flare-signal.nix
./fontconfig.nix ./fontconfig.nix
./fractal.nix ./fractal.nix
./free.nix
./frozen-bubble.nix ./frozen-bubble.nix
./fwupd.nix ./fwupd.nix
./g4music.nix ./g4music.nix
./gajim.nix ./gajim.nix
./gdb.nix
./gdbus.nix ./gdbus.nix
./geary.nix ./geary.nix
./geoclue-demo-agent.nix
./geoclue2.nix
./git.nix ./git.nix
./gnome-clocks.nix ./gnome-clocks.nix
./gnome-feeds.nix ./gnome-feeds.nix
@@ -64,8 +59,6 @@
./gnome-weather.nix ./gnome-weather.nix
./go2tv.nix ./go2tv.nix
./gpodder.nix ./gpodder.nix
./gpsd.nix
./gps-share.nix
./grimshot.nix ./grimshot.nix
./gst-device-monitor.nix ./gst-device-monitor.nix
./gthumb.nix ./gthumb.nix
@@ -73,11 +66,9 @@
./handbrake.nix ./handbrake.nix
./helix.nix ./helix.nix
./htop ./htop
./iio-sensor-proxy.nix
./imagemagick.nix ./imagemagick.nix
./jellyfin-media-player.nix ./jellyfin-media-player.nix
./kdenlive.nix ./kdenlive.nix
./keymapp.nix
./komikku.nix ./komikku.nix
./koreader ./koreader
./less.nix ./less.nix
@@ -95,7 +86,6 @@
./msmtp.nix ./msmtp.nix
./nautilus.nix ./nautilus.nix
./neovim.nix ./neovim.nix
./networkmanager_dmenu
./newsflash.nix ./newsflash.nix
./nheko.nix ./nheko.nix
./nicotine-plus.nix ./nicotine-plus.nix
@@ -103,21 +93,14 @@
./nmcli.nix ./nmcli.nix
./notejot.nix ./notejot.nix
./ntfy-sh.nix ./ntfy-sh.nix
./nwg-panel
./objdump.nix ./objdump.nix
./obsidian.nix ./obsidian.nix
./offlineimap.nix ./offlineimap.nix
./ols.nix
./open-in-mpv.nix ./open-in-mpv.nix
./pactl.nix
./pidof.nix
./pipewire.nix ./pipewire.nix
./pkill.nix
./planify.nix ./planify.nix
./portfolio-filemanager.nix ./portfolio-filemanager.nix
./playerctl.nix ./playerctl.nix
./ps.nix
./qmk-udev-rules.nix
./rhythmbox.nix ./rhythmbox.nix
./ripgrep.nix ./ripgrep.nix
./rofi ./rofi
@@ -127,10 +110,8 @@
./sane-open.nix ./sane-open.nix
./sane-screenshot.nix ./sane-screenshot.nix
./sane-scripts.nix ./sane-scripts.nix
./sane-sysload.nix
./sane-theme.nix ./sane-theme.nix
./sanebox.nix ./sanebox.nix
./satellite.nix
./schlock.nix ./schlock.nix
./seatd.nix ./seatd.nix
./sfeed.nix ./sfeed.nix
@@ -149,19 +130,14 @@
./swayidle.nix ./swayidle.nix
./swaylock.nix ./swaylock.nix
./swaynotificationcenter ./swaynotificationcenter
./switchboard.nix ./sysvol.nix
./syshud.nix
./tangram.nix ./tangram.nix
./tor-browser.nix ./tor-browser.nix
./tuba.nix ./tuba.nix
./unl0kr ./unl0kr
./via.nix
./visidata.nix
./vlc.nix ./vlc.nix
./wally-cli.nix
./waybar ./waybar
./waylock.nix ./waylock.nix
./where-am-i.nix
./wike.nix ./wike.nix
./wine.nix ./wine.nix
./wireplumber.nix ./wireplumber.nix
@@ -177,8 +153,6 @@
./zeal.nix ./zeal.nix
./zecwallet-lite.nix ./zecwallet-lite.nix
./zulip.nix ./zulip.nix
./zsa-udev-rules.nix
./zfs-tools.nix
./zsh ./zsh
]; ];

View File

@@ -50,13 +50,32 @@ in
}; };
}; };
packageUnwrapped = pkgs.dino.override { packageUnwrapped = (pkgs.dino.override {
# XXX(2024/04/24): build without echo cancelation (i.e. force WITH_VOICE_PROCESSOR to be undefined). # XXX(2024/04/24): build without echo cancelation (i.e. force WITH_VOICE_PROCESSOR to be undefined).
# this means that if the other end of the call is on speaker phone, i'm liable to hear my own voice # this means that if the other end of the call is on speaker phone, i'm liable to hear my own voice
# leave their speaker, enter their mic, and then return to me. # leave their speaker, enter their mic, and then return to me.
# the benefit is a >50% reduction in CPU use. insignificant on any modern PC; make-or-break on a low-power Pinephone. # the benefit is a >50% reduction in CPU use. insignificant on any modern PC; make-or-break on a low-power Pinephone.
webrtc-audio-processing = null; webrtc-audio-processing = null;
}).overrideAttrs (upstream: {
# i'm updating experimentally to see if it improves call performance.
# i don't *think* this is actually necessary; i don't notice any difference.
version = "0.4.3-unstable-2024-04-28";
src = lib.warnIf (lib.versionOlder "0.4.3" upstream.version) "dino update: safe to remove sane patches" pkgs.fetchFromGitHub {
owner = "dino";
repo = "dino";
rev = "657502955567dd538e56f300e075c7db52e25d74";
hash = "sha256-SApJy9FgxxLOB5A/zGtpdFZtSqSiS03vggRrCte1tFE=";
}; };
# avoid double-application of upstreamed patches
# https://github.com/NixOS/nixpkgs/pull/309265
patches = [];
checkPhase = ''
runHook preCheck
./xmpp-vala-test
# ./signal-protocol-vala-test # doesn't exist anymore
runHook postCheck
'';
});
sandbox.method = "bwrap"; sandbox.method = "bwrap";
sandbox.net = "clearnet"; sandbox.net = "clearnet";

View File

@@ -6,17 +6,6 @@ in
sane.programs.eg25-control = { sane.programs.eg25-control = {
suggestedPrograms = [ "mmcli" ]; suggestedPrograms = [ "mmcli" ];
sandbox.method = "bwrap";
sandbox.extraPaths = [
"/sys/class/modem-power"
"/sys/devices"
# "/var/lib/eg25-control"
];
sandbox.net = "all"; #< for downloading the almanac
sandbox.whitelistDbus = [
"system" #< used by `mmcli`
];
services.eg25-control-powered = { services.eg25-control-powered = {
description = "eg25-control-powered: power to the Qualcomm eg25 modem used by PinePhone"; description = "eg25-control-powered: power to the Qualcomm eg25 modem used by PinePhone";
startCommand = "eg25-control --power-on --verbose"; startCommand = "eg25-control --power-on --verbose";

View File

@@ -55,7 +55,7 @@ in
# - theme-demo # - theme-demo
# - timeout-completed # - timeout-completed
# - window-close # - window-close
fs.".config/feedbackd/themes/proxied.json".symlink.target = pkgs.writers.writeJSON "proxied.json" { fs.".config/feedbackd/themes/proxied.json".symlink.text = builtins.toJSON {
name = "proxied"; name = "proxied";
parent-theme = "default"; parent-theme = "default";
profiles = [ profiles = [

View File

@@ -1,9 +0,0 @@
{ pkgs, ... }:
{
sane.programs.free = {
packageUnwrapped = pkgs.linkIntoOwnPackage pkgs.procps "bin/free";
sandbox.method = "bwrap";
sandbox.isolatePids = false;
};
}

View File

@@ -1,13 +0,0 @@
{ pkgs, ... }:
{
sane.programs.gdb = {
sandbox.enable = false; # gdb doesn't sandbox well. i don't know how you could.
# sandbox.method = "landlock"; # permission denied when trying to attach, even as root
sandbox.autodetectCliPaths = true;
fs.".config/gdb/gdbinit".symlink.text = ''
# enable commands like `py-bt`, `py-list`, etc.
# for usage, see: <https://wiki.python.org/moin/DebuggingWithGdb>
source ${pkgs.python3}/share/gdb/libpython.py
'';
};
}

View File

@@ -5,10 +5,10 @@
# <https://gitlab.gnome.org/GNOME/geary/-/issues/1212> # <https://gitlab.gnome.org/GNOME/geary/-/issues/1212>
{ config, lib, ... }: { config, lib, ... }:
let let
cfg = config.sane.programs."geary"; cfg = config.sane.programs."gnome.geary";
in in
{ {
sane.programs."geary" = { sane.programs."gnome.geary" = {
configOption = with lib; mkOption { configOption = with lib; mkOption {
default = {}; default = {};
type = types.submodule { type = types.submodule {

View File

@@ -1,23 +0,0 @@
{ config, pkgs, ... }:
{
sane.programs.geoclue-demo-agent = {
packageUnwrapped = pkgs.linkFarm "geoclue-demo-agent" [{
# bring the demo agent into a `bin/` directory so it can be invokable via PATH
name = "bin/geoclue-demo-agent";
path = "${config.sane.programs.geoclue2.packageUnwrapped}/libexec/geoclue-2.0/demos/agent";
}];
sandbox.method = "bwrap";
sandbox.whitelistDbus = [
"system"
];
services.geoclue-agent = {
description = "geoclue 'demo' agent";
# XXX: i don't actually understand how this works: upstream dbus rules would appear to restrict
# the dbus owner to just root/geoclue, but we're neither and this still works (and breaks if i remove the agent service!)
command = "geoclue-demo-agent";
partOf = [ "graphical-session" ];
};
};
}

View File

@@ -1,76 +0,0 @@
# geoclue location services daemon.
#
# SUPPORT:
# - irc: #gnome-maps on irc.gimp.org
# - Matrix: #gnome-maps:gnome.org (unclear if bridged to IRC)
# - forums: <https://discourse.gnome.org/c/platform>
# - git: <https://gitlab.freedesktop.org/geoclue/geoclue/>
# - D-Bus API docs: <https://www.freedesktop.org/software/geoclue/docs/>
#
# HOW TO TEST:
# - just invoke `where-am-i`: it should output the current latitude/longitude.
## more manual testing:
# - build `geoclue2-with-demo-agent`
# - run the service: `systemctl start geoclue` or "${geoclue2-with-demo-agent}/libexec/geoclue"
# - run "${geoclue2-with-demo-agent}/libexec/geoclue-2.0/demos/agent"
# - keep this running in the background
# - run "${geoclue2-with-demo-agent}/libexec/geoclue-2.0/demos/where-am-i"
#
# DATA FLOW:
# - geoclue2 does http calls into local `ols`, which either hits the local disk or queries https://wigle.net.
# - geoclue users like gnome-maps somehow depend on an "agent",
# a user service which launches the geoclue system service on-demand (via dbus activation).
#
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.geoclue2;
in
{
sane.programs.geoclue2 = {
# packageUnwrapped = pkgs.rmDbusServices pkgs.geoclue2;
# packageUnwrapped = pkgs.geoclue2.override { withDemoAgent = true; };
packageUnwrapped = pkgs.geoclue2-with-demo-agent;
suggestedPrograms = [
"avahi" #< to discover LAN gps devices
"geoclue-demo-agent"
# "gps-share"
"iio-sensor-proxy"
"ols" #< WiFi SSID -> lat/long lookups
"satellite" #< graphical view into GPS fix data
"where-am-i" #< handy debugging/testing tool
];
# XXX(2024/07/05): no way to plumb my sandboxed geoclue into `services.geoclue2`.
# then, the package doesn't get used directly anywhere. but other programs reference `packageUnwrapped`,
# so keep that part still.
sandbox.enable = false;
package = lib.mkForce null;
# experimental sandboxing (2024/07/05)
# sandbox.method = "bwrap";
# sandbox.whitelistDbus = [
# "system"
# ];
# sandbox.net = "all";
};
# sane.programs.geoclue2.enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
services.geoclue2 = lib.mkIf cfg.enabled {
enable = true;
geoProviderUrl = "http://127.0.0.1:8088/v1/geolocate"; #< ols
# XXX(2024/06/25): when Geoclue uses ModemManager's GPS API, it wants to enable GPS
# tracking at the start, and disable it at the end. that causes tracking to be lost, regularly.
# this is not optional behavior: if Geoclue fails to control modem manager (because of a polkit policy, say),
# then it won't even try to read the data from modem manager.
# SOLUTION: tell Geoclue to get GPS from gps-share ("enableNmea", i.e. `network-nmea.enable`)
# and NOT from modem manager.
enableModemGPS = false;
enableNmea = true;
};
systemd.user.services = lib.mkIf cfg.enabled {
# nixos services.geoclue2 runs the agent as a user service by default, but i don't use systemd so that doesn't work.
# i manage the agent myself, in sane.programs.geoclue-demo-agent.
geoclue-agent.enable = false;
};
}

View File

@@ -40,7 +40,6 @@ in
alias.amend = "commit --amend --no-edit"; alias.amend = "commit --amend --no-edit";
alias.br = "branch"; alias.br = "branch";
alias.co = "checkout"; alias.co = "checkout";
alias.com = "commit";
alias.cp = "cherry-pick"; alias.cp = "cherry-pick";
alias.d = "difftool"; alias.d = "difftool";
alias.dif = "diff"; # common typo alias.dif = "diff"; # common typo

View File

@@ -1,12 +1,12 @@
# gnome feeds RSS viewer # gnome feeds RSS viewer
{ config, lib, pkgs, sane-lib, ... }: { config, lib, sane-lib, ... }:
let let
feeds = sane-lib.feeds; feeds = sane-lib.feeds;
all-feeds = config.sane.feeds; all-feeds = config.sane.feeds;
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds; wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
in { in {
sane.programs.gnome-feeds.fs.".config/org.gabmus.gfeeds.json".symlink.target = pkgs.writers.writeJSON "org.gabmus.gfeeds.json" { sane.programs.gnome-feeds.fs.".config/org.gabmus.gfeeds.json".symlink.text = builtins.toJSON {
# feed format is a map from URL to a dict, # feed format is a map from URL to a dict,
# with dict["tags"] a list of string tags. # with dict["tags"] a list of string tags.
feeds = sane-lib.mapToAttrs (feed: { feeds = sane-lib.mapToAttrs (feed: {

View File

@@ -1,7 +1,7 @@
{ lib, pkgs, ... }: { lib, pkgs, ... }:
{ {
sane.programs.gnome-keyring = { sane.programs.gnome-keyring = {
packageUnwrapped = pkgs.rmDbusServices pkgs.gnome-keyring; packageUnwrapped = pkgs.rmDbusServices pkgs.gnome.gnome-keyring;
sandbox.method = "bwrap"; sandbox.method = "bwrap";
sandbox.whitelistDbus = [ "user" ]; sandbox.whitelistDbus = [ "user" ];
sandbox.extraRuntimePaths = [ sandbox.extraRuntimePaths = [

View File

@@ -1,30 +1,7 @@
# SUPPORT:
# - irc: #gnome-maps on irc.gimp.org
# - Matrix: #gnome-maps:gnome.org (unclear if bridged to IRC)
#
# INTEGRATIONS:
# - uses https://graphhopper.com for routing
# - <https://github.com/graphhopper/graphhopper> (not packaged for Nix)
# - uses https://tile.openstreetmap.org for tiles
# - uses https://overpass-api.de for ... ?
# TIPS:
# - use "Northwest" instead of "NW", and "Street" instead of "St", etc.
# otherwise, it might not find your destination!
{ pkgs, ... }: { pkgs, ... }:
{ {
sane.programs."gnome.gnome-maps" = { sane.programs."gnome.gnome-maps" = {
packageUnwrapped = pkgs.rmDbusServicesInPlace (pkgs.gnome.gnome-maps.overrideAttrs (base: { packageUnwrapped = pkgs.rmDbusServices pkgs.gnome.gnome-maps;
# default .desktop file is trying to do some dbus launch (?) which fails even *if* i install `gapplication` (glib.bin)
postPatch = (base.postPatch or "") + ''
substituteInPlace data/org.gnome.Maps.desktop.in.in \
--replace-fail 'Exec=gapplication launch @app-id@ %U' 'Exec=gnome-maps %U'
'';
}));
suggestedPrograms = [
"geoclue2"
];
sandbox.wrapperType = "inplace"; #< /share directory contains Gir info which references libgnome-maps.so by path
sandbox.method = "bwrap"; sandbox.method = "bwrap";
sandbox.whitelistDri = true; # for perf sandbox.whitelistDri = true; # for perf
sandbox.whitelistDbus = [ sandbox.whitelistDbus = [

View File

@@ -1,56 +0,0 @@
# gps-share: <https://github.com/zeenix/gps-share>
# takes a local GPS device (e.g. /dev/ttyUSB1) and makes it available over TCP/Avahi (multicast DNS).
#
# common usecases:
# 1. make positioning available to any device on a network, even if that device has no local GPS
# - e.g. my desktop can use my phone's GPS device, if on the same network.
# 2. allow multiple clients to share a GPS device.
# GPS devices are serial devices, and so only one process can consume the data at a time.
# gps-share can camp the serial device, and then allow *multiple* subscribers
# 3. provide a *read-only* API to clients like Geoclue.
# that is, expose the GPS device *output* to a client, but don't let the client write to the device (e.g. enable/disable the GPS).
# this is the primary function i derive from gps-share
#
# HOW TO TEST:
# - `nc localhost 10110`
# should stream GPS NMEA output to the console
# - `avahi-browse --resolve _nmea-0183._tcp`: should show hosts on the local network which provide GPS info
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.gps-share;
in
{
sane.programs.gps-share = {
suggestedPrograms = [
"jq"
# and systemd, for udevadm
];
services.gps-share = {
description = "gps-share: make local GPS serial readings available over Avahi";
# usage:
# gps-share --no-announce # to disable Avahi
# gps-share --no-tcp # only makes sense if using --socket-path
# gps-share --network-interface lo # defaults to all interfaces, but firewalling means actually more restrictive
# gps-share --socket-path $XDG_RUNTIME_DIR/gps-share/gps-share.sock # share over a unix socket
command = pkgs.writeShellScript "gps-share" ''
dev=$(udevadm info --property-match=ID_MM_PORT_TYPE_GPS=1 --json=pretty --export-db | jq -r .DEVNAME)
if [ -z "$dev" ]; then
echo "no GPS device found"
exit 1
fi
echo "using $dev for GPS NMEA"
gps-share "$dev"
'';
# TODO: this should be `partOf = [ "gps" ]`:
# it fails to launch if the NMEA device doesn't yet exist, and so restart loop when modem is not booted
dependencyOf = [ "geoclue-agent" ];
};
sandbox.method = "bwrap";
sandbox.net = "all";
sandbox.autodetectCliPaths = "existingFile";
};
# TODO: restrict this to just LAN devices!!
networking.firewall.allowedTCPPorts = lib.mkIf cfg.enabled [ 10110 ];
}

View File

@@ -1,33 +0,0 @@
# test gpsd with `gpspipe -w -n 10 2> /dev/null | grep -m 1 TPV | jq '.lat, .lon' | tr '\n' ' '`
# ^ should return <lat> <long>
#
# TODO(2024/06/19): nixpkgs' gpsd service isn't sandboxed at ALL. i should sandbox that, or remove this integration.
#
# pinephone GPS happens in EG25 modem
# serial control interface to modem is /dev/ttyUSB2
# after enabling GPS, readout is /dev/ttyUSB1
#
# minimal process to enable modem and GPS:
# - `echo 1 > /sys/class/modem-power/modem-power/device/powered`
# - `screen /dev/ttyUSB2 115200`
# - `AT+QGPSCFG="nmeasrc",1`
# - `AT+QGPS=1`
# this process is automated by my `eg25-control` program and services (`eg25-control-powered`, `eg25-control-gps`)
# - see the `modules/` directory further up this repository.
#
# now, something like `gpsd` can directly read from /dev/ttyUSB1,
# or geoclue can query the GPS directly through modem-manager
#
# initial GPS fix can take 15+ minutes.
# meanwhile, services like eg25-manager or eg25-control-freshen-agps can speed this up by uploading assisted GPS data to the modem.
{ config, lib, ... }:
let
cfg = config.sane.programs.gpsd;
in
{
sane.programs.gpsd = {};
services.gpsd = lib.mkIf cfg.enabled {
enable = true;
devices = [ "/dev/ttyUSB1" ];
};
}

View File

@@ -20,14 +20,5 @@
pkgs.pipewire #< required for Video/Source (video4linux) pkgs.pipewire #< required for Video/Source (video4linux)
]; ];
}); });
sandbox.method = "bwrap";
sandbox.whitelistAudio = true;
sandbox.extraPaths = [
"/dev" # tried, but failed to narrow this down (moby)
"/run/udev/data"
"/sys/class/video4linux"
"/sys/devices"
];
}; };
} }

View File

@@ -1,55 +0,0 @@
# chat (Matrix): #iio-sensor-proxy:dylanvanassche.be
# src: <https://gitlab.freedesktop.org/hadess/iio-sensor-proxy>
# IIO = "Industrial I/O": <https://www.kernel.org/doc/html/v4.12/driver-api/iio/index.html>
# iio-sensor-proxy reads IIO data reported by the kernel at /sys/bus/iio/* and makes it available to dbus applications.
# this includes:
# - ambient light sensor
# - compass/magnetometer (LIMITED)
# - accelerometer (rotation)
#
# use:
# - show available sensors: `gdbus introspect --system --dest net.hadess.SensorProxy --object-path /net/hadess/SensorProxy`
# - read sensors: `sudo -u geoclue monitor-sensor --compass`
# - default dbus policy only allows geoclue to use the compass
# - `sudo monitor-sensor` for light/rotation
#
# HARDWARE SUPPORT: PINEPHONE (2024/07/01)
# - accelerometer and light sensor seem to work
# - magnetometer (af8133j, different but similar to lis3mdl) IS NOT SUPPORTED
# - <https://gitlab.freedesktop.org/hadess/iio-sensor-proxy/-/issues/310>
# - exists in sysfs and can be viewed with
# `cat /sys/devices/platform/soc/1c2b000.i2c/i2c-1/1-001c/iio:device2/in_magn_x_raw`
# - nothing in iio-sensor-proxy reads anything related to "magn".
# - WIP PR to support magnetometers: <https://gitlab.freedesktop.org/hadess/iio-sensor-proxy/-/merge_requests/316>
# - after rebase, it *functions*, but does not scale the readings correctly
# heading changes only over the range of 50 - 70 deg.
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.iio-sensor-proxy;
in
{
sane.programs.iio-sensor-proxy = {
packageUnwrapped = pkgs.iio-sensor-proxy.overrideAttrs (upstream: {
patches = (upstream.patches or []) ++ [
(pkgs.fetchpatch {
name = "WIP:compass: Add support for polling uncalibrated devices";
# url = "https://gitlab.freedesktop.org/hadess/iio-sensor-proxy/-/merge_requests/316.diff";
url = "https://git.uninsane.org/colin/iio-sensor-proxy/commit/fd21f1f4bf1eadd603b1f24f628b979691d9cf3b.diff";
hash = "sha256-+GoEPby6q+uSkQlZWFWr5ghx3BKBMGk7uv/DDhGnxDk=";
})
];
});
enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true; #< for dbus/polkit policies
sandbox.method = "bwrap";
sandbox.whitelistDbus = [ "system" ];
sandbox.extraPaths = [
"/run/udev/data"
"/sys/bus"
"/sys/devices"
];
};
services.udev.packages = lib.mkIf cfg.enabled [ cfg.package ];
# services.dbus.packages = lib.mkIf cfg.enabled [ cfg.package ]; #< for bus ownership policy
systemd.packages = lib.mkIf cfg.enabled [ cfg.package ]; #< for iio-sensor-proxy.service
}

View File

@@ -1,8 +0,0 @@
# ZSA keyboard (Ergodox, Moonlander, ...) firmware flasher and keymap viewer.
# video: <https://www.zsa.io/flash>
# displays on launch:
# - "Error connecting to the keyboard, make sure the layout flashed on your keyboard was recently compiled with Oryx and that the [Live training] option is toggled on in the advanced settings."
{ ... }:
{
sane.programs.keymapp = {};
}

View File

@@ -17,7 +17,7 @@
# - these are stored in `~/.config/koreader/data/dict` # - these are stored in `~/.config/koreader/data/dict`
# - configure defaults: # - configure defaults:
# - edit keys in ~/.config/koreader/settings.reader.lua # - edit keys in ~/.config/koreader/settings.reader.lua
# - default font size: `["copt_font_size"] = 30,` # - default font size: `["copt_font_size"] = 28,`
# - home dir: `["home_dir"] = "/home/colin/Books",` # - home dir: `["home_dir"] = "/home/colin/Books",`
{ config, lib, pkgs, sane-lib, ... }: { config, lib, pkgs, sane-lib, ... }:

View File

@@ -1,18 +1,14 @@
# docs: <https://git.sr.ht/~mil/mepo> # docs: <https://git.sr.ht/~mil/mepo>
# irc #mepo:irc.oftc.net # irc #mepo:irc.oftc.net
#
{ config, lib, ... }: { config, lib, ... }:
{ {
sane.programs.mepo = { sane.programs.mepo = {
sandbox.method = "bwrap"; sandbox.method = "bwrap";
sandbox.net = "all"; # for tiles *and* for localhost comm to gpsd sandbox.net = "all"; # for tiles *and* for localhost comm to gpsd
sandbox.whitelistDri = true; sandbox.whitelistDri = true;
sandbox.whitelistWayland = true; sandbox.whitelistWayland = true;
sandbox.whitelistDbus = [ sandbox.whitelistDbus = [ "user" ]; # for geoclue
"system" # system is required for non-portal location services
"user" #< not sure if "user" is necessary?
];
sandbox.usePortal = false; # TODO: set up portal-based location services
persist.byStore.plaintext = [ ".cache/mepo/tiles" ]; persist.byStore.plaintext = [ ".cache/mepo/tiles" ];
# ~/.cache/mepo/savestate has precise coordinates and pins: keep those private # ~/.cache/mepo/savestate has precise coordinates and pins: keep those private
@@ -20,11 +16,11 @@
{ type = "file"; path = ".cache/mepo/savestate"; } { type = "file"; path = ".cache/mepo/savestate"; }
]; ];
# enable geoclue2 and gpsd for location data. # give mepo access to gpsd for location data, if that's enabled.
suggestedPrograms = [ # same with geoclue2.
"geoclue2" suggestedPrograms = lib.optional config.services.gpsd.enable "gpsd"
# "gpsd" #< not required, and mepo only uses it if geoclue is unavailable ++ lib.optional config.services.geoclue2.enable "geoclue2-with-demo-agent"
]; ;
}; };
# programs.mepo = lib.mkIf config.sane.programs.mepo.enabled { # programs.mepo = lib.mkIf config.sane.programs.mepo.enabled {

View File

@@ -5,7 +5,7 @@
# alternative to mimeo is jaro: <https://github.com/isamert/jaro> # alternative to mimeo is jaro: <https://github.com/isamert/jaro>
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
mimeo-open-desktop = pkgs.static-nix-shell.mkPython3 { mimeo-open-desktop = pkgs.static-nix-shell.mkPython3Bin {
pname = "mimeo-open-desktop"; pname = "mimeo-open-desktop";
srcRoot = ./.; srcRoot = ./.;
pkgs = [ "mimeo" ]; pkgs = [ "mimeo" ];

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
#!nix-shell -i python3 -p mimeo -p python3 #!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p mimeo
# TODO: migrate nixpkgs mimeo to be `buildPythonPackage` to make it importable here. # TODO: migrate nixpkgs mimeo to be `buildPythonPackage` to make it importable here.
# see <doc/languages-frameworks/python.section.md> # see <doc/languages-frameworks/python.section.md>

View File

@@ -1,19 +1,3 @@
# GPS:
# - enable: `mmcli --modem any --location-enable-gps-unmanaged`
# - or `mmcli -m any --location-enable-gps-nmea`
# - or use `s6-rc start eg25-control-gps`
# - verify GPS is enabled: `mmcli --modem any --location-status`
# - query GPS coordinates: `mmcli -m any --location-get`
# - monitor constellation info: `mmcli -m any --location-monitor`
# - i.e. which satellites are in view
# - or just `cat /dev/ttyUSB1`
#
# interactions, warnings:
# - Geoclue (`where-am-i`) toggles mmcli GPS on/off every 60s, often resetting it to the "off" state
# - see: <https://gitlab.freedesktop.org/geoclue/geoclue/-/issues/180>
# - the effect is that GPS data is effectively useless inside apps like gnome-maps
# i think the trick is to get "--location-enable-gps-unmanaged" gps working again
# or use gnss-share/gpsd (this may be what "unmanaged" means).
{ pkgs, ... }: { pkgs, ... }:
{ {
sane.programs.mmcli = { sane.programs.mmcli = {
@@ -22,11 +6,7 @@
mainProgram = "mmcli"; mainProgram = "mmcli";
}; };
}); });
# TODO: sandbox
sandbox.method = "bwrap";
sandbox.whitelistDbus = [
"system"
];
}; };
} }

View File

@@ -241,10 +241,9 @@ in
mime.associations."video/webm" = "mpv.desktop"; mime.associations."video/webm" = "mpv.desktop";
mime.associations."video/x-flv" = "mpv.desktop"; mime.associations."video/x-flv" = "mpv.desktop";
mime.associations."video/x-matroska" = "mpv.desktop"; mime.associations."video/x-matroska" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtu.be/.+" = "mpv.desktop"; mime.urlAssociations."^https?://(www.)?youtube.com/watch\?.*v=" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/shorts/.+" = "mpv.desktop"; mime.urlAssociations."^https?://(www.)?youtube.com/v/" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/v/" = "mpv.desktop"; mime.urlAssociations."^https?://(www.)?youtu.be/.+" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/watch\?.*v=" = "mpv.desktop";
}; };
} }

View File

@@ -1,8 +1,8 @@
{ pkgs, ... }: { pkgs, ... }:
{ {
sane.programs."nautilus" = { sane.programs."gnome.nautilus" = {
# some of its dbus services don't even refer to real paths # some of its dbus services don't even refer to real paths
packageUnwrapped = pkgs.rmDbusServicesInPlace (pkgs.nautilus.overrideAttrs (orig: { packageUnwrapped = pkgs.rmDbusServicesInPlace (pkgs.gnome.nautilus.overrideAttrs (orig: {
# enable the "Audio and Video Properties" pane. see: <https://nixos.wiki/wiki/Nautilus> # enable the "Audio and Video Properties" pane. see: <https://nixos.wiki/wiki/Nautilus>
buildInputs = orig.buildInputs ++ (with pkgs.gst_all_1; [ buildInputs = orig.buildInputs ++ (with pkgs.gst_all_1; [
gst-plugins-good gst-plugins-good

View File

@@ -1,44 +0,0 @@
[dmenu]
dmenu_command = rofi -dmenu
# # Note that dmenu_command can contain arguments as well like:
# # `dmenu_command = rofi -dmenu -i -theme nmdm`
# # `dmenu_command = rofi -dmenu -width 30 -i`
# # `dmenu_command = dmenu -i -l 25 -b -nb #909090 -nf #303030`
# # `dmenu_command = fuzzel --dmenu`
rofi_highlight = True
compact = True
# pinentry = <Pinentry command> # (Default: None) e.g. `pinentry-gtk`
# wifi_chars = <string of 4 unicode characters representing 1-4 bars strength>
wifi_chars = ▂▄▆█
# wifi_icons = <characters representing signal strength as an icon>
wifi_icons = 󰤯󰤟󰤢󰤥󰤨
# format = <Python style format string for the access point entries>
# # TODO: replace `{sec}` with a locked/unlocked icon
format = {icon} {name} [{signal}%%] [{sec}]
# # Available variables are:
# # * {name} - Access point name
# # * {sec} - Security type
# # * {signal} - Signal strength on a scale of 0-100
# # * {bars} - Bar-based display of signal strength (see wifi_chars)
# # * {icon} - Icon-based display of signal strength (see wifi_icons)
# # * {max_len_name} and {max_len_sec} are the maximum lengths of {name} / {sec}
# # respectively and may be useful for formatting.
# list_saved = <True or False> # (Default: False) list saved connections
[dmenu_passphrase]
# # Uses the -password flag for Rofi, -x for bemenu. For dmenu, sets -nb and
# # -nf to the same color or uses -P if the dmenu password patch is applied
# # https://tools.suckless.org/dmenu/patches/password/
# obscure = True
# obscure_color = #222222
[pinentry]
# description = <Pinentry description> (Default: Get network password)
# prompt = <Pinentry prompt> (Default: Password:)
[editor]
# terminal = <name of terminal program>
# gui_if_available = <True or False> (Default: True)
[nmdm]
# rescan_delay = <seconds> # (seconds to wait after a wifi rescan before redisplaying the results)

View File

@@ -1,21 +0,0 @@
# source: <https://github.com/firecat53/networkmanager-dmenu>
{ ... }:
{
sane.programs.networkmanager_dmenu = {
sandbox.method = "bwrap";
sandbox.isolatePids = false; #< so it can know that NetworkManager really is running... (?)
sandbox.whitelistDbus = [
"system"
];
sandbox.whitelistWayland = true;
sandbox.extraHomePaths = [
".cache/rofi"
".config/rofi"
];
suggestedPrograms = [
"pidof"
];
fs.".config/networkmanager-dmenu/config.ini".symlink.target = ./config.ini;
};
}

View File

@@ -2,9 +2,6 @@
{ {
sane.programs.nmcli = { sane.programs.nmcli = {
packageUnwrapped = pkgs.networkmanager-split.nmcli; packageUnwrapped = pkgs.networkmanager-split.nmcli;
sandbox.method = "bwrap"; # TODO: sandbox
sandbox.whitelistDbus = [
"system"
];
}; };
} }

View File

@@ -1,7 +0,0 @@
{
"restart-on-display": true,
"restart-delay": 500,
"run-through-compositor": false,
"processes-background-only": true,
"processes-own-only": true
}

View File

@@ -1,191 +0,0 @@
# TODO:
# - try this PR to get custom workspace names to work:
# - <https://github.com/nwg-piotr/nwg-panel/pull/191>
# - add network/bluetooth indicator
# - <https://github.com/nwg-piotr/nwg-panel/issues/269>
# - add CPU/meminfo executor
# - use sane-sysload
{
controlsSettingsComponents,
controlsSettingsCustomItems,
height,
locker,
modulesRight,
playerctlChars,
mediaPrevNext,
windowIcon,
windowTitle,
workspaceHideEmpty,
workspaceNumbers,
}:
[
{
controls = "right";
css-name = "panel-top";
exclusive-zone = true;
height = height;
homogeneous = false; #< homogenous=false means to not force modules-{left,center,right} to an inflexible 33%/33%/33% real-estate split.
icons = "light";
items-padding = 0;
layer = "bottom";
margin-bottom = 0;
margin-top = 0;
menu-start = "off";
name = "panel-top";
# output = "All" => display the bar on every output.
# - documented: <https://github.com/nwg-piotr/nwg-panel/issues/48>
# alternatively, i could declare one bar per display,
# and then customize it so that the external display(s) render a less noisy bar.
# this will be easier once this is addressed: <https://github.com/nwg-piotr/nwg-panel/issues/215>
output = "All";
padding-horizontal = 0;
padding-vertical = 0;
position = "top";
sigrt = 64;
spacing = 0;
start-hidden = false;
use-sigrt = false;
width = "auto";
modules-left = [
"sway-workspaces"
];
modules-center = [
"clock"
];
modules-right = modulesRight;
clock = {
angle = 0.0;
calendar-css-name = "calendar-window";
calendar-icon-size = 24;
calendar-interval = 60;
calendar-margin-horizontal = 0;
calendar-margin-vertical = 0;
calendar-on = true;
calendar-path = "";
calendar-placement = "top";
css-name = "clock";
format = "%H:%M";
interval = 30;
on-left-click = "";
on-middle-click = "";
on-right-click = "";
on-scroll-down = "";
on-scroll-up = "";
root-css-name = "root-clock";
tooltip-date-format = true;
tooltip-text = "%a; %d %b %H:%M:%S";
};
controls-settings = {
battery-low-interval = 4; #< notify every N minutes when battery continues to remain low
battery-low-level = 15; #< notify if battery is lower than this percent
# commands.battery = ""; #< optional action to perform when battery icon is clicked in the drop-down menu
components = controlsSettingsComponents;
click-closes = false;
custom-items = controlsSettingsCustomItems;
css-name = "controls-window";
hover-opens = false;
icon-size = 16;
interval = 1;
leave-closes = false;
menu.icon = "system-shutdown-symbolic";
menu.items = [
{
name = "Lock";
cmd = "s6-rc start ${locker}";
}
# {
# name = "Logout";
# cmd = "swaymsg exit";
# }
{
name = "Reboot";
cmd = "reboot";
}
{
name = "Shutdown";
cmd = "shutdown now";
}
];
menu.name = "Exit";
output-switcher = true; #< allow changing the default audio sink
#v `show-<x>` means "show the NUMERICAL VALUE corresponding to <x>"
# e.g. show-battery means "show the battery _percentage_ next to its icon".
show-battery = true;
show-brightness = false;
show-values = false;
show-volume = false;
# window-width: should be 360 for moby, but because of weird `margin` tweaks in style.css
# we have to add 20px to both sides
window-width = 400;
};
playerctl = {
button-css-name = "playerctl-button";
buttons-position = "left";
chars = playerctlChars;
icon-size = 16;
interval = 2;
label-css-name = "playerctl-label";
scroll = false;
show-cover = false; #< don't show the little music-note icon
show-previous = mediaPrevNext;
show-next = mediaPrevNext;
show-name = mediaPrevNext;
};
swaync = {
css-name = "swaync-label";
# interval = 1;
# icon-placement = "left";
# icon-size = 18;
# tooltip-text = "";
# on-left-click = "swaync-client -t";
# on-right-click = "";
# on-middle-click = "";
# on-scroll-up = "";
# on-scroll-down = "";
# always-show-icon = true;
};
sway-workspaces = {
angle = 0.0;
custom-labels = [];
focused-labels = [];
hide-empty = workspaceHideEmpty;
image-size = 16;
mark-autotiling = true;
mark-content = false;
name-length = 40;
numbers = workspaceNumbers;
show-icon = windowIcon;
show-layout = false;
show-name = windowTitle;
};
executor-sysload = {
script = "sane-sysload {mem} {cpu}";
interval = 10;
css-name = "";
on-right-click = "";
icon-size = 16;
show-icon = false;
tooltip-text = "";
on-left-click = "";
on-middle-click = "";
on-scroll-up = "";
on-scroll-down = "";
};
# unused modules:
brightness-slider = {};
dwl-tags = {};
hyprland-taskbar = {};
hyprland-workspaces = {};
keyboard-layout = {};
openweather = {};
scratchpad = {};
sway-mode = {};
sway-taskbar = {}; #< windows-style taskbar, usually placed at the bottom of the screen, to show open windows & tab to them on click
tray = {};
}
]

View File

@@ -1,220 +0,0 @@
# nwg-panel: a wayland status bar (like waybar, etc)
# documentation is in the GitHub Wiki:
# - <https://github.com/nwg-piotr/nwg-panel/wiki/Configuration>
#
# interactively configure with: `nwg-panel-config`
# ^ note that this may interfere with the `nwg-panel` service
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.nwg-panel;
mkEnableOption' = default: description: lib.mkOption {
type = lib.types.bool;
inherit default description;
};
in
{
sane.programs.torch-toggle = {
packageUnwrapped = pkgs.static-nix-shell.mkBash {
pname = "torch-toggle";
pkgs = [ "brightnessctl" ];
srcRoot = ./.;
};
suggestedPrograms = [
"brightnessctl"
];
sandbox.enable = false; # trivial, and all deps are sandboxed
};
sane.programs.nwg-panel = {
configOption = with lib; mkOption {
default = {};
type = types.submodule {
options = {
clockFontSize = mkOption {
type = types.int;
# what looks good:
# - 15px on moby
# - 24px on lappy
# there's about 10px padding total around this (above + below)
default = lib.min 24 (cfg.config.height - 11);
};
fontSize = mkOption {
type = types.int;
default = 16;
};
height = mkOption {
type = types.int;
default = 40;
description = ''
height of the top bar in px.
'';
};
locker = mkOption {
type = types.str;
default = config.sane.programs.swayidle.config.actions.lock.service;
description = ''
s6 service to start which can lock the screen
'';
};
torch = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
device name for the torch (flashlight), if any.
find with `brightnessctl -l`
'';
example = "white:flash";
};
battery = mkEnableOption' true "display battery status";
brightness = mkEnableOption' true "display backlight level and slider";
mediaTitle = mkEnableOption' true "display title of current song/media";
mediaPrevNext = mkEnableOption' true "display prev/next button in media";
sysload = mkEnableOption' true "display system load info (cpu/memory)";
windowIcon = mkEnableOption' true "display icon of active window";
windowTitle = mkEnableOption' true "display title of active window";
workspaceNumbers = mkOption {
type = types.listOf types.str;
default = [
# TODO: workspace 10 should be rendered as "TV"
"1" "2" "3" "4" "5" "6" "7" "8" "9" "10"
];
description = ''
workspaces to monitor
'';
};
workspaceHideEmpty = mkOption {
type = types.bool;
default = true;
};
};
};
};
packageUnwrapped = (pkgs.nwg-panel.override {
# XXX(2024/06/13): hyprland does not cross compile
hyprland = null;
# XXX(2024/06/13): wlr-randr does not cross compile
wlr-randr = null; #< only used if not on sway/hyprland; or if using dwl
python3Packages = pkgs.python3Packages // {
dasbus = pkgs.python3Packages.dasbus.overridePythonAttrs (_: {
# XXX(2024/07/07): python3.12 update broke tests
doCheck = false;
});
};
}).overrideAttrs (base: {
# patches = (base.patches or []) ++ lib.optionals (!cfg.config.mediaPrevNext) [
# ./playerctl-no-prev-next.diff
# ];
patches = (base.patches or []) ++ [
(pkgs.fetchpatch {
# upstreaming: <https://github.com/nwg-piotr/nwg-panel/pull/309>
url = "https://git.uninsane.org/colin/nwg-panel/commit/a714e4100c409feb02c454874d030d192bfb0ae5.patch";
name = "playerctl: add settings to control which elements are displayed";
hash = "sha256-OofS46wAI3EDE3JbYs/Nn+Vkw9TP1mwSFvk+vBERg2s=";
})
];
# - disable the drop-down chevron by the controls.
# it's precious space on moby, doesn't do much to help on lappy either.
# - disable brightness indicator for same reason.
# - *leave* the volume indicator: one *could* remove it, however on desko that would leave the controls pane empty
# making the dropdown inaccessible
# also, remove padding from the items. i can manage that in css and the python padding prevents that.
postPatch = (base.postPatch or "") + ''
substituteInPlace nwg_panel/modules/controls.py --replace-fail \
'self.box.pack_start(box, False, False, 6)' \
'self.box.pack_start(box, False, False, 0)'
substituteInPlace nwg_panel/modules/controls.py --replace-fail \
'box.pack_start(self.pan_image, False, False, 4)' \
'# box.pack_start(self.pan_image, False, False, 0)'
substituteInPlace nwg_panel/modules/controls.py --replace-fail \
'box.pack_start(self.bri_image, False, False, 4)' \
'# box.pack_start(self.bri_image, False, False, 0)'
substituteInPlace nwg_panel/modules/controls.py --replace-fail \
'box.pack_start(self.vol_image, False, False, 4)' \
'box.pack_start(self.vol_image, False, False, 0)'
'';
# XXX(2024/06/13) the bluetooth stuff doesn't cross compile, so disable it
propagatedBuildInputs = lib.filter (p: p.pname != "pybluez") base.propagatedBuildInputs;
strictDeps = true;
});
suggestedPrograms = [
"brightnessctl"
"pactl" # pactl required by `per-app-volume` component.
"sane-die-with-parent"
] ++ lib.optionals (cfg.config.torch != null) [
"torch-toggle"
];
fs.".config/nwg-panel/style.css".symlink.target = pkgs.substituteAll {
src = ./style.css;
inherit (cfg.config) fontSize clockFontSize;
};
fs.".config/nwg-panel/common-settings.json".symlink.target = ./common-settings.json;
fs.".config/nwg-panel/config".symlink.target = pkgs.writers.writeJSON "config" (import ./config.nix {
inherit (cfg.config) locker height mediaPrevNext windowIcon windowTitle workspaceHideEmpty workspaceNumbers;
# component order matters, mostly for the drop-down.
# default for most tools (e.g. swaync) is brightness control above volume.
controlsSettingsComponents =
lib.optionals cfg.config.brightness [
"brightness"
] ++ [
"volume"
"per-app-volume"
] ++ lib.optionals cfg.config.battery [
"battery"
]
;
controlsSettingsCustomItems = lib.optionals (cfg.config.torch != null) [
{
name = "Torch";
# icons: find them in /etc/profiles/per-user/colin/share/icons
# display-brightness-symbolic, keyboard-brightness-symbolic, night-light-symbolic
icon = "display-brightness-symbolic";
cmd = "torch-toggle ${cfg.config.torch}";
}
];
modulesRight = [
"playerctl"
] ++ lib.optionals cfg.config.sysload [
"executor-sysload"
];
playerctlChars = if cfg.config.mediaTitle then 60 else 0;
});
sandbox.method = "bwrap";
sandbox.whitelistAudio = true;
sandbox.whitelistDri = true;
sandbox.whitelistS6 = true;
sandbox.whitelistWayland = true;
sandbox.whitelistDbus = [
"user" # playerctl, swaync, ...
"system" # for "shutdown" option to speak to systemd
];
sandbox.extraPaths = [
"/run/systemd" #< for "shutdown" option
"/sys/class/backlight"
"/sys/class/leds" #< for torch/flashlight on moby
"/sys/class/power_supply" #< for the battery indicator
"/sys/devices"
];
sandbox.extraRuntimePaths = [ "sway" ];
services.nwg-panel = {
description = "nwg-panel status/topbar for wayland";
partOf = [ "graphical-session" ];
# to debug styling, run with GTK_DEBUG=interactive
# N.B.: G_MESSAGES_DEBUG=all causes the swaync icon to not render
# command = "env G_MESSAGES_DEBUG=all nwg-panel";
# XXX: try `nwg-panel & ; kill $$`. the inner nwg-panel doesn't die (without sane-die-with-parent), and hence the service would be prone to maintaining _multiple_ bars.
command = "sane-die-with-parent --descendants --signal SIGKILL nwg-panel";
};
};
}

View File

@@ -1,59 +0,0 @@
commit 7aa759990b38b09abf9010dfe58e4cbdc1493282 (HEAD -> dev-sane)
Author: Colin <colin@uninsane.org>
Date: 2024-06-15 21:41:46 +0000
playerctl: remove backward/forward/music-note icons
these aren't worth the space cost on narrow devices (moby)
diff --git a/nwg_panel/modules/playerctl.py b/nwg_panel/modules/playerctl.py
index ff48d4c..43ae221 100644
--- a/nwg_panel/modules/playerctl.py
+++ b/nwg_panel/modules/playerctl.py
@@ -211,15 +211,6 @@ class Playerctl(Gtk.EventBox):
if self.settings["angle"] != 0.0:
button_box.set_orientation(Gtk.Orientation.VERTICAL)
- img = Gtk.Image()
- update_image(img, "media-skip-backward-symbolic", self.settings["icon-size"], icons_path=self.icons_path)
- btn = Gtk.Button()
- btn.set_image(img)
- if self.settings["button-css-name"]:
- btn.set_property("name", self.settings["button-css-name"])
- btn.connect("clicked", self.launch, self.PlayerOps.PREVIOUS)
- button_box.pack_start(btn, False, False, 1)
-
self.play_pause_btn = Gtk.Button()
if self.settings["button-css-name"]:
self.play_pause_btn.set_property("name", self.settings["button-css-name"])
@@ -229,15 +220,6 @@ class Playerctl(Gtk.EventBox):
self.play_pause_btn.connect("clicked", self.launch, self.PlayerOps.PLAY_PAUSE)
button_box.pack_start(self.play_pause_btn, False, False, 1)
- img = Gtk.Image()
- update_image(img, "media-skip-forward-symbolic", self.settings["icon-size"], icons_path=self.icons_path)
- btn = Gtk.Button()
- btn.set_image(img)
- if self.settings["button-css-name"]:
- btn.set_property("name", self.settings["button-css-name"])
- btn.connect("clicked", self.launch, self.PlayerOps.NEXT)
- button_box.pack_start(btn, False, False, 1)
-
self.num_players_lbl = Gtk.Label.new("")
if self.settings["label-css-name"]:
self.num_players_lbl.set_property("name", self.settings["label-css-name"])
@@ -257,13 +239,9 @@ class Playerctl(Gtk.EventBox):
self.box.pack_start(button_box, False, False, 2)
if self.settings["show-cover"]:
self.box.pack_start(self.cover_img, False, False, 0)
- self.box.pack_start(self.num_players_lbl, False, False, 0)
- self.box.pack_start(self.label, False, False, 5)
else:
if self.settings["show-cover"]:
self.box.pack_start(self.cover_img, False, False, 2)
- self.box.pack_start(self.num_players_lbl, False, False, 0)
- self.box.pack_start(self.label, False, False, 2)
self.box.pack_start(button_box, False, False, 10)
def launch(self, button, op):

View File

@@ -1,241 +0,0 @@
/* foreground (text)/background */
@define-color fg0 #d8d8d8;
@define-color fg1 #ffffff;
@define-color bg0 #130c0c;
@define-color bg1 #1c1716;
/* green accents */
@define-color accent-g0 #1f5e54;
@define-color accent-g1 #418379;
@define-color accent-g2 #63a89c;
/* red accents */
@define-color accent-r0 #c96262;
@define-color accent-r1 #d27871;
@define-color accent-r2 #ff968b;
/* light (teal-white) accents */
@define-color accent-l0 #e1f0ef;
@define-color accent-l1 #f9fffc;
* {
font-size: @fontSize@px;
}
#task-box-focused {
background-color: @accent-g2;
}
#playerctl-button {
background-color: rgba(0, 0, 0, 0.08);
background-image: none;
border: none;
box-shadow: none;
/* remove the 1px gap between buttons, since that causes color stripes if the background is a different color */
margin-left: -1;
margin-right: -1;
outline: none;
/* prevent the buttons from pushing the whole bar down */
padding-top: 0px;
padding-bottom: 0px;
margin-top: 0px;
margin-bottom: 0px;
}
#panel-top {
background: @accent-g1;
color: @fg1;
}
/* fix up the top bar sections so that the clock can be centered, even without forcing it to take 1/3rd of the bar */
/* pair with `homogenous = false` in config. on overflow, the clock may be rendered on top of the left portion of the bar */
/* and the right portion of the bar will render on top of all */
#panel-top > box > box > box > #left-box {
margin-left: 0px;
margin-right: -16384px;
}
#panel-top > box > box > box > #center-box {
margin-left: 0px;
margin-right: 0px;
}
#panel-top > box > box > box > box {
/* this is the *parent* of #right-box, which is uniquely under an unnamed "helper box" */
/* i have to address this parent, because otherwise only the controls are visible and the executors (including playerctl) */
/* are packed in a fill mode that pushes them off the visible section of the bar */
margin-left: -16384px;
}
#right-box > widget > * {
/* TODO: tune this for moby */
padding-right: 3px;
padding-left: 2px;
}
#swaync-label {
/* move the notification count closer to the bell icon */
margin-left: -3px;
/* TODO: this should be main font size -1 */
font-size: 14px;
color: @accent-r2;
}
/* increase the size of each workspace icon */
#sway-workspaces-item > label {
padding-left: 1px;
padding-right: 1px;
}
/* default config highlights hovered workspace with a gray border */
#sway-workspaces > widget:selected {
box-shadow: none;
}
/* the CSS nodes are difficult to determine.
* reference: <https://github.com/numixproject/numix-gtk-theme/blob/master/src/gtk-3.20/scss/widgets/_calendar.scss>
*/
#calendar-window {
background-color: @accent-l0;
}
calendar {
background-color: @accent-l0;
}
calendar :selected {
background-color: @accent-r1;
}
#controls-window {
border-radius: 15px;
background: @bg1;
color: @fg1;
}
/* default config highlights selected items with a green border */
widget:selected {
box-shadow: none;
background-color: @accent-g0;
}
#controls-window widget:selected {
box-shadow: none;
background-color: @accent-r0;
}
/* default config puts a *ridiculous* amount of padding around the whole controls window */
#controls-window > widget > .vertical {
margin-left: -20px;
margin-right: -20px;
}
#controls-window > widget > .vertical > .horizontal {
margin-top: -14px; /* full reset would be -20px */
margin-bottom: -20px;
}
/* add back in a little bit of padding, but in a way such that my highlights apply to it */
#controls-window .horizontal widget > box,
#controls-window > widget > .vertical > .horizontal > .vertical > .horizontal
{
padding-left: 12px;
padding-right: 12px;
}
#controls-window > widget > .vertical > .horizontal > .vertical > widget > box
{
padding-top: 6px;
padding-bottom: 6px;
}
#controls-window > widget > .vertical > .horizontal > .vertical > box > widget > box
{
padding-top: 3px;
padding-bottom: 3px;
}
/* hierarchy is .horizontal > {image, scale > { value, contents > trough > { slider, highlight } } } */
scale {
padding-right: 0px;
padding-top: 0px;
padding-bottom: 0px;
}
scale trough {
padding-left: 9px;
padding-right: 9px;
border-radius: 9px;
border-color: rgba(0, 0, 0, 0);
background: @bg0;
}
scale highlight {
border-radius: 9px;
border-color: rgba(0, 0, 0, 0);
margin: 0px;
margin-left: -9px;
background: @accent-r1;
}
scale slider {
margin-top: -3px;
margin-bottom: -3px;
background: @accent-l1;
min-height: 25px;
min-width: 25px;
}
#clock {
font-family: monospace;
font-size: @clockFontSize@px;
}
/* UNUSED IN MY CURRENT CONFIG: COPIED FROM SAMPLE CONFIG */
/* Controls window in sample config uses this name */
/* Brightness slider popup window in sample config uses this name */
#brightness-popup {
border-radius: 15px;
background: @bg1;
color: @fg1;
}
#brightness-popup box {
padding: 15px;
}
/* Executors usually behave better in monospace fonts */
#executor-label {
font-family: monospace;
}
/* Bottom panel in sample config uses this name */
#panel-bottom {
background: #101010;
color: #eeeeee;
}
/* Sample executor-weather uses "css-name": "weather" */
#weather {
font-size: 16px;
}
/* dwl-tags module */
#dwl-tag-box {
padding-top: 4px;
padding-bottom: 4px;
}
#dwl-tag-occupied {
font-family: monospace;
color: #eee;
background-color: #006699;
padding-left: 3px;
padding-right: 3px;
}
#dwl-tag-free {
font-family: monospace;
color: #eee;
background-color: rgba (32, 50, 90, 1.0);
padding-left: 3px;
padding-right: 3px;
}
#dwl-tag-urgent {
font-family: monospace;
color: #eee;
background-color: #ee6600;
padding-left: 3px;
padding-right: 3px;
}
#dwl-tag-selected {
border: solid 2px;
border-color: #81a1c1;
}

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash -p brightnessctl
dev="$1"
if [ -z "$dev" ]; then
dev="white:flash"
fi
if [ "$(brightnessctl -d "$dev" get)" -gt 0 ]; then
brightnessctl -q -d "$dev" set "0%"
else
brightnessctl -q -d "$dev" set "100%"
fi

View File

@@ -4,7 +4,5 @@
# binutils-unwrapped is like 80 MiB, just for this one binary; # binutils-unwrapped is like 80 MiB, just for this one binary;
# dynamic linking means copying the binary doesn't reduce the closure much at all compared to just symlinking it. # dynamic linking means copying the binary doesn't reduce the closure much at all compared to just symlinking it.
packageUnwrapped = pkgs.linkIntoOwnPackage pkgs.binutils-unwrapped "bin/objdump"; packageUnwrapped = pkgs.linkIntoOwnPackage pkgs.binutils-unwrapped "bin/objdump";
sandbox.method = "bwrap";
sandbox.autodetectCliPaths = "existingFile";
}; };
} }

View File

@@ -1,48 +0,0 @@
# OLS: Offline Location Service: <https://codeberg.org/tpikonen/ols>
# fields {wifi SSID,cell tower} -> lat/long queries from geoclue
# satisfies queries via https://wigle.net, by learning about map tiles
# and caching those on-disk so that repeat queries may be serviced offline.
#
# it listens on localhost:8088, and one can validate its operation with a query like (substitute macAddresses for something real):
# - WiFi: curl -d '{"wifiAccessPoints":[{"macAddress":"01:23:45:67:89:ab","signalStrength":-78},{"macAddress":"cd:ef:01:23:45:56","signalStrength":-76}]}' http://127.0.0.1:8088/v1/geolocate
# - Cell: curl -d '{"cellTowers":[{ "radioType": "lte", "mobileCountryCode": 310, "mobileNetworkCode": 260, "locationAreaCode": NNNNN, "cellId": MMMMMMMM }]}' http://127.0.0.1:8088/v1/geolocate
#
## wigle docs:
# - IRC: #wigle on WiGLE.net:6667
# - API: <https://api.wigle.net/swagger>
# API return codes:
# - 429: "too many queries today."
#
# rate limiting:
# - as a new user you'll be limited to something ridiculous like 5 queries per day.
# supposedly this improves "based on history and participation".
# - source: <https://api.wigle.net/swagger#/Network%20search%20and%20information%20tools/search_2>
# - "API for some functions is limited on a daily basis for all users for the time being, but if you'd like increased access, please email us (include your username and usecase) at WiGLE-admin@wigle.net."
# - source: <https://wigle.net/account>
{ pkgs, ... }:
{
sane.programs.ols = {
packageUnwrapped = pkgs.geoclue-ols;
fs.".config/ols/cell.db".symlink.target = pkgs.runCommandLocal "cell.db" {
nativeBuildInputs = [ pkgs.geoclue-ols ];
} ''
cellid-ols-import -o "$out" "${pkgs.opencellid}"
'';
persist.byStore.private = [
".local/share/ols"
];
secrets.".config/ols/ols.toml" = ../../../secrets/common/ols.toml.bin;
sandbox.method = "bwrap";
sandbox.net = "all";
services.ols = {
description = "ols: Offline Location Service";
command = "ols 2>&1"; # XXX: it logs to stderr, and my s6 infrastructure apparently doesn't handle that
partOf = [ "graphical-session" ];
};
};
}

View File

@@ -1,8 +0,0 @@
{ pkgs, ... }:
{
sane.programs.pactl = {
packageUnwrapped = pkgs.linkIntoOwnPackage pkgs.pulseaudio "bin/pactl";
sandbox.method = "bwrap";
sandbox.whitelistAudio = true;
};
}

View File

@@ -1,8 +0,0 @@
{ pkgs, ... }:
{
sane.programs.pidof = {
packageUnwrapped = pkgs.linkIntoOwnPackage pkgs.procps "bin/pidof";
sandbox.method = "bwrap";
sandbox.isolatePids = false;
};
}

View File

@@ -1,8 +0,0 @@
{ pkgs, ... }:
{
sane.programs.pkill = {
packageUnwrapped = pkgs.linkIntoOwnPackage pkgs.procps "bin/pkill";
sandbox.method = "bwrap";
sandbox.isolatePids = false;
};
}

View File

@@ -1,8 +0,0 @@
{ pkgs, ... }:
{
sane.programs.ps = {
packageUnwrapped = pkgs.linkIntoOwnPackage pkgs.procps "bin/ps";
sandbox.method = "bwrap";
sandbox.isolatePids = false;
};
}

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