Compare commits

...

73 Commits

Author SHA1 Message Date
20bdad9132 add missing dns.nix module 2023-06-07 23:36:15 +00:00
7aaab9eb30 WIP: DNS: split the zone generation out of trust-dns
this is in preparation for upstreaming parts of this into nixpkgs
2023-06-07 23:34:00 +00:00
481110fefb add a todo: split out trust-dns 2023-06-07 08:08:23 +00:00
c44f69a01f modules/services/dyn-dns: specifc sane-ip-check* more irectly 2023-06-07 08:00:43 +00:00
adbc2a76c3 modules/ports.nix: specify sane-ip-port-forward more directly 2023-06-07 08:00:43 +00:00
34ed201aff browserpass: specify sane-secrets-unlock more directly 2023-06-07 08:00:39 +00:00
4d63b81b05 zsh: refer to sane-deadlines more directly 2023-06-07 07:44:46 +00:00
e1a18cdae1 sane-scripts: port sane-wipe-browser to nix-shell & remove dead resholve code 2023-06-07 07:30:11 +00:00
2a1d87650b sane-scripts: port sane-which to nix-shell 2023-06-07 07:25:43 +00:00
4a18dfeef3 sane-scripts: port sane-vpn-up to nix-shell 2023-06-07 07:24:49 +00:00
ff1aece1ed sane-scripts: port sane-vpn-down to nix-shell 2023-06-07 07:24:32 +00:00
05cf5e376a sane-scripts: port sane-sync-from-servo to nix-shell 2023-06-07 07:21:36 +00:00
855a66499f sane-scripts: port sane-sync-from-iphone to nix-shell 2023-06-07 07:20:27 +00:00
b9cc581736 sane-scripts: port sane-sudo-redirect to nix-shell 2023-06-07 07:17:26 +00:00
0a8eee8af0 sane-scripts: port sane-stop-all-servo to nix-shell 2023-06-07 07:16:27 +00:00
a40fc7e112 sane-scripts: port sane-ssl-dump to nix-shell 2023-06-07 07:12:42 +00:00
6bbb5669a6 sane-scripts: port sane-shutdown to nix-shell 2023-06-07 07:11:41 +00:00
c8d5411462 sane-scripts: port sane-secrets-* to nix-shell 2023-06-07 07:07:07 +00:00
af4cfc29b1 sane-scripts: port sane-reclaim-disk-space to nix-shell 2023-06-07 07:00:07 +00:00
9942025a2f sane-scripts: port sane-reboot to nix-shell 2023-06-07 06:58:11 +00:00
04f7287781 sane-scripts: port sane-rcp to nix-shell 2023-06-07 06:57:07 +00:00
14ae501433 sane-scripts: sane-private-*: port to nix-shell 2023-06-07 06:53:45 +00:00
46edc56a32 sane-scripts: remove sane-test 2023-06-06 09:22:01 +00:00
7907623887 sane-scripts: lift sane-mount-servo out of resholve 2023-06-06 08:24:32 +00:00
c542e120ef refactor: sane-scripts: order the non-resholve scripts, rename py-scripts 2023-06-06 08:15:50 +00:00
7fcff0b6a2 sane-scripts: lift sane-ip-check out of resholve 2023-06-06 08:14:42 +00:00
32671201a4 sane-scripts: lift sane-git-init out of resholve 2023-06-06 08:10:31 +00:00
4d2268b5f1 sane-scripts: lift sane-find-dotfiles out of resholve 2023-06-06 08:09:37 +00:00
e5fe7c093a sane-scripts: lift sane-dev-cargo-loop out of resholve 2023-06-06 08:08:20 +00:00
162f3a291c sane-scripts: lift deadlines out of resholve 2023-06-06 08:05:10 +00:00
31740befbf programs: split jellyfin-media-player into own nix module 2023-06-06 07:54:08 +00:00
0c610c8f1c jellyfin-media-player: working qt6 build
haven't checked cross compilation
2023-06-06 07:54:08 +00:00
e9dc22c1f2 sxmo-utils: sxmo_hook_start.sh: don't start pulse/pipewire audio daemons at start; don't warn on 'first' use 2023-06-06 07:54:08 +00:00
75e6393680 sxmo-utils: move the upstrea sxmo_hook_start.sh inline
this can let me customize it aggressively here

but see track how it originally looked
2023-06-06 07:54:08 +00:00
9ca6857f4d sxmo-utils: refactor a bit to allow easier customizing 2023-06-06 07:54:08 +00:00
8c30b87a94 sane-find-dotfiles: include ~/.local/state 2023-06-06 07:54:08 +00:00
6ffd6693cb sane-scripts: remove sane-date-math
why did i even make this...
2023-06-06 07:54:08 +00:00
e11fe929f4 alsa-ucm-conf-sane: move from patched/ to additional/
the way i'm using this lately calls into question the naming scheme...
2023-06-06 07:54:08 +00:00
3dcd5629a7 moby: set ALSA_CONFIG_UCM2 in all the places it's needed 2023-06-06 07:54:08 +00:00
4cf4c38da3 WIP: jellyfin-media-player: support qt6
the hope is that achieving this would allo much faster mobile deployments

as qt6 can generally compile w/o emulation
2023-06-06 07:54:08 +00:00
e0e3c36d1b fix NIX_PATH overlay interaction that was crashing nix-shell 2023-06-06 07:49:52 +00:00
108c1d9d60 moby: don't set ALSA_CONFIG_UCM2 var within pulseaudio service 2023-06-01 09:38:51 +00:00
c6e16ebc13 alsa-ucm-conf: patch custom PinePhone conf into the upstream package rather than shipping *only* the PinePhone configs
this is more to faciliate a goal of eventually not shipping any custom audio profiles

i.e. stay close to how upstream does things until we reach that goal

-mnote that this doesn't actually override the alsa-ucm-conf nix package (yet).

doing so is costly
2023-06-01 09:19:45 +00:00
aa60838551 gpodder-configured: don't bail if we fail to realize the feeds 2023-06-01 00:10:36 +00:00
d6bde02dfe feeds: update URL for Acquired podcast 2023-06-01 00:04:54 +00:00
d07bb03936 feeds: update URL/title for _ACQ2_ 2023-05-31 23:57:08 +00:00
1ab2f42ff4 feeds: update URL for _The Portal_ 2023-05-31 23:54:46 +00:00
e0d20cb62a cross: fix phosh cross compilation 2023-05-31 09:16:04 +00:00
f8944c8379 programs: ship alsaUtils 2023-05-31 08:15:32 +00:00
ca38bb4aec refactor: remove deprecated types.string uses 2023-05-31 04:27:27 +00:00
287817056f refactor: sane.services.wan-ports -> sane.ports 2023-05-31 04:25:39 +00:00
5cc7ced859 dns: rework so that we branch to the LAN v.s. WAN results based on source IP of the query -- not interface.
this simplifies the UPnP forwards and the OVPN routing
2023-05-31 00:56:52 +00:00
4dc5378b3e dns: give different results based on which port the request arrives from
WAN and VPN requests are served by local port 1053 and `wan.uninsane.org`.

LAN requests are served by port 53 and `servo.lan.uninsane.org`.

i'm not *super* fond of this. a recursive resolver of uninsane.org via the VPN will only ever get WAN addresses (broken).

we may prefer to do IP-based responses, maybe via the same Linux firewall rules that forward from VPN namespace to root namespace
2023-05-30 12:00:30 +00:00
fe7e440997 git: remove __pycache__ 2023-05-28 21:49:29 +00:00
e4262cb0bc ssh: integrate with sane.services.wan-ports 2023-05-28 20:39:18 +00:00
35c9f2bf60 servo: enable UPnP port forwarding timer 2023-05-28 20:38:24 +00:00
13794e9eaa sane-scripts: build sane-ip-port-forward with inetutils (required for hostname command) 2023-05-27 23:27:36 +00:00
a33950da62 sane-scripts: UPnP retrieves LAN IP from the gateway 2023-05-27 23:26:57 +00:00
37995e23c2 sane-scripts: make the UPnP/ssdp code more resilient to errors 2023-05-27 23:17:47 +00:00
66156829d9 flake/nixpkgs: 2023-05-22 -> 2023-05-24
```
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/7084250df3d7f9735087d3234407f3c1fc2400e3' (2023-05-22)
  → 'github:nixos/nixpkgs/f91ee3065de91a3531329a674a45ddcb3467a650' (2023-05-24)
```
2023-05-27 21:02:28 +00:00
3c40fa6982 sane-script to forward a list of ports via UPnP 2023-05-27 09:57:41 +00:00
c1ddddddc0 ports: hide behind services.sane.wan-ports
later i will use this to enable UPnP on relevant ports
2023-05-26 23:28:30 +00:00
aae118b476 net: open UDP ports required for UPnP 2023-05-26 22:45:41 +00:00
7e402ce974 dyn-dns: obtain IP address via UPnP 2023-05-26 22:40:50 +00:00
5b80308074 servo: disable broken mx-discord-puppet 2023-05-26 21:04:54 +00:00
e5c94b410f lemmy-ui: update nodejs version 2023-05-26 21:04:34 +00:00
209c18cb38 flake/nixpkgs: 2023-05-18 -> 2023-05-22
```
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/48a0fb7aab511df92a17cf239c37f2bd2ec9ae3a' (2023-05-18)
  → 'github:nixos/nixpkgs/7084250df3d7f9735087d3234407f3c1fc2400e3' (2023-05-22)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/a376127bb5277cd2c337a9458744f370aaf2e08d' (2023-05-14)
  → 'github:Mic92/sops-nix/4ccdfb573f323a108a44c13bb7730e42baf962a9' (2023-05-21)
• Updated input 'sops-nix/nixpkgs-stable':
    'github:NixOS/nixpkgs/c6d2f3dc0d3efd4285eebe4f8a36a47ba438138e' (2023-05-14)
  → 'github:NixOS/nixpkgs/d0dade110dc7072d67ce27826cfe9ab2ab0cf247' (2023-05-21)
```
2023-05-26 05:58:22 +00:00
616a2dd19f add todo: debug wireguard flakiness 2023-05-26 05:58:08 +00:00
5b0f898c62 roles/ac: disable unused p2p services
i2p in particular binds to port 1900, which is partially in conflict with UPnP
2023-05-26 04:53:35 +00:00
a541e866a1 servo: remove the extraneous firewall enable statement. FW is enabled by default 2023-05-26 04:52:52 +00:00
d3eb0bee26 enable some net debugging tools 2023-05-25 09:48:42 +00:00
2ca0f6ea62 cross compilation: fix wrapFirefox
now the web browser extensions should work on moby?? :o :o
2023-05-25 06:07:05 +00:00
66be38bfbf librewolf: enable some more policies which might or might not actually improve things :-(
i really hate firefox, but there's not much alternative.
2023-05-25 01:01:34 +00:00
95 changed files with 1481 additions and 804 deletions

View File

@@ -1,3 +1,7 @@
## BUGS
- why i need to manually restart `wireguard-wg-ovpns` on servo periodically
- else DNS fails
## REFACTORING:
### sops/secrets
- attach secrets to the thing they're used by (sane.programs)
@@ -9,6 +13,9 @@
- will make it easier to test new services?
### upstreaming
- split out a trust-dns module
- see: <https://github.com/NixOS/nixpkgs/pull/205866#issuecomment-1575753054>
- bump nodejs version in lemmy-ui
- add updateScripts to all my packages in nixpkgs
- fix lightdm-mobile-greeter for newer libhandy
- port zecwallet-lite to a from-source build

18
flake.lock generated
View File

@@ -66,11 +66,11 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1684025543,
"narHash": "sha256-hGe7S+i5je+8E/b2mOXVI9nmr038Dw+bV8e1P8xHSe0=",
"lastModified": 1684632198,
"narHash": "sha256-SdxMPd0WmU9MnDBuuy7ouR++GftrThmSGL7PCQj/uVI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c6d2f3dc0d3efd4285eebe4f8a36a47ba438138e",
"rev": "d0dade110dc7072d67ce27826cfe9ab2ab0cf247",
"type": "github"
},
"original": {
@@ -82,11 +82,11 @@
},
"nixpkgs-unpatched": {
"locked": {
"lastModified": 1684385584,
"narHash": "sha256-O7y0gK8OLIDqz+LaHJJyeu09IGiXlZIS3+JgEzGmmJA=",
"lastModified": 1684935479,
"narHash": "sha256-6QMMsXMr2nhmOPHdti2j3KRHt+bai2zw+LJfdCl97Mk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "48a0fb7aab511df92a17cf239c37f2bd2ec9ae3a",
"rev": "f91ee3065de91a3531329a674a45ddcb3467a650",
"type": "github"
},
"original": {
@@ -113,11 +113,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1684032930,
"narHash": "sha256-ueeSYDii2e5bkKrsSdP12JhkW9sqgYrUghLC8aDfYGQ=",
"lastModified": 1684637723,
"narHash": "sha256-0vAxL7MVMhGbTkAyvzLvleELHjVsaS43p+PR1h9gzNQ=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "a376127bb5277cd2c337a9458744f370aaf2e08d",
"rev": "4ccdfb573f323a108a44c13bb7730e42baf962a9",
"type": "github"
},
"original": {

View File

@@ -77,14 +77,30 @@
# enable rotation sensor
hardware.sensor.iio.enable = true;
# from https://gitlab.manjaro.org/manjaro-arm/packages/community/phosh/alsa-ucm-pinephone
# mobile-nixos does this same thing, with *slightly different settings*.
# i trust manjaro more because the guy maintaining that is actively trying to upstream into alsa-ucm-conf.
# an alternative may be to build a custom alsa with the PinePhone config patch applied:
# - <https://github.com/alsa-project/alsa-ucm-conf/pull/134>
# that would make this be not device-specific
environment.variables.ALSA_CONFIG_UCM2 = "${./ucm2}";
systemd.services.pulseaudio.environment.ALSA_CONFIG_UCM2 = "${./ucm2}";
# inject specialized alsa configs via the environment.
# specifically, this gets the pinephone headphones & internal earpiece working.
# see pkgs/patched/alsa-ucm-conf for more info.
environment.variables.ALSA_CONFIG_UCM2 = "/run/current-system/sw/share/alsa/ucm2";
environment.pathsToLink = [ "/share/alsa/ucm2" ];
environment.systemPackages = [ pkgs.alsa-ucm-conf-sane ];
systemd =
let ucm-env = config.environment.variables.ALSA_CONFIG_UCM2;
in {
# cribbed from <repo:nixos/mobile-nixos:modules/quirks/audio.nix>
# pulseaudio
user.services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
# pipewire
user.services.pipewire.environment.ALSA_CONFIG_UCM2 = ucm-env;
user.services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env;
user.services.wireplumber.environment.ALSA_CONFIG_UCM2 = ucm-env;
services.pipewire.environment.ALSA_CONFIG_UCM2 = ucm-env;
services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env;
services.wireplumber.environment.ALSA_CONFIG_UCM2 = ucm-env;
};
hardware.opengl.driSupport = true;
}

View File

@@ -20,6 +20,7 @@
sane.zsh.showDeadlines = false; # ~/knowledge doesn't always exist
sane.services.dyn-dns.enable = true;
sane.services.wg-home.enable = true;
sane.services.wg-home.enableWan = true;
sane.services.wg-home.ip = config.sane.hosts.by-name."servo".wg-home.ip;
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade

View File

@@ -3,6 +3,12 @@
{
networking.domain = "uninsane.org";
sane.ports.openFirewall = true;
sane.ports.openUpnp = true;
# view refused packets with: `sudo journalctl -k`
# networking.firewall.logRefusedPackets = true;
# The global useDHCP flag is deprecated, therefore explicitly set to false here.
# Per-interface useDHCP will be mandatory in the future, so this generated config
# replicates the default behaviour.
@@ -11,9 +17,6 @@
# XXX colin: probably don't need this. wlan0 won't be populated unless i touch a value in networking.interfaces.wlan0
networking.wireless.enable = false;
# networking.firewall.enable = false;
networking.firewall.enable = true;
# this is needed to forward packets from the VPN to the host
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
@@ -153,9 +156,9 @@
# we also bridge DNS traffic
${in-ns} ${iptables} -A PREROUTING -t nat -p udp --dport 53 -m iprange --dst-range ${vpn-ip} \
-j DNAT --to-destination ${veth-host-ip}:53
-j DNAT --to-destination ${veth-host-ip}
${in-ns} ${iptables} -A PREROUTING -t nat -p tcp --dport 53 -m iprange --dst-range ${vpn-ip} \
-j DNAT --to-destination ${veth-host-ip}:53
-j DNAT --to-destination ${veth-host-ip}
# in order to access DNS in this netns, we need to route it to the VPN's nameservers
# - alternatively, we could fix DNS servers like 1.1.1.1.

View File

@@ -22,20 +22,60 @@
sane.persist.sys.plaintext = [
{ user = "ejabberd"; group = "ejabberd"; directory = "/var/lib/ejabberd"; }
];
networking.firewall.allowedTCPPorts = [
3478 # STUN/TURN
5222 # XMPP client -> server
5223 # XMPPS client -> server (XMPP over TLS)
5269 # XMPP server -> server
5270 # XMPPS server -> server (XMPP over TLS)
5280 # bosh
5281 # bosh (https) ??
5349 # STUN/TURN (TLS)
5443 # web services (file uploads, websockets, admin)
];
networking.firewall.allowedUDPPorts = [
3478 # STUN/TURN
];
sane.ports.ports."3478" = {
protocol = [ "tcp" "udp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-stun-turn";
};
sane.ports.ports."5222" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-client-to-server";
};
sane.ports.ports."5223" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpps-client-to-server"; # XMPP over TLS
};
sane.ports.ports."5269" = {
protocol = [ "tcp" ];
visibleTo.wan = true;
description = "colin-xmpp-server-to-server";
};
sane.ports.ports."5270" = {
protocol = [ "tcp" ];
visibleTo.wan = true;
description = "colin-xmpps-server-to-server"; # XMPP over TLS
};
sane.ports.ports."5280" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-bosh";
};
sane.ports.ports."5281" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-bosh-https";
};
sane.ports.ports."5349" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-stun-turn-over-tls";
};
sane.ports.ports."5443" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-web-services"; # file uploads, websockets, admin
};
# TODO: forward these TURN ports!
networking.firewall.allowedTCPPortRanges = [{
from = 49152; # TURN
to = 49408;
@@ -77,7 +117,7 @@
sane.services.trust-dns.zones."uninsane.org".inet = {
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
A."xmpp" = "%NATIVE%";
A."xmpp" = "%ANATIVE%";
CNAME."muc.xmpp" = "xmpp";
CNAME."pubsub.xmpp" = "xmpp";
CNAME."upload.xmpp" = "xmpp";
@@ -234,7 +274,7 @@
use_turn: true
turn_min_port: 49152
turn_max_port: 65535
turn_ipv4_address: %NATIVE%
turn_ipv4_address: %ANATIVE%
-
# STUN+TURN UDP
port: 3478
@@ -243,7 +283,7 @@
use_turn: true
turn_min_port: 49152
turn_max_port: 65535
turn_ipv4_address: %NATIVE%
turn_ipv4_address: %ANATIVE%
-
# STUN+TURN TLS over TCP
port: 5349
@@ -254,7 +294,7 @@
use_turn: true
turn_min_port: 49152
turn_max_port: 65535
turn_ipv4_address: %NATIVE%
turn_ipv4_address: %ANATIVE%
# TODO: enable mod_fail2ban
# TODO(low): look into mod_http_fileserver for serving macros?
@@ -387,7 +427,7 @@
# config is 444 (not 644), so we want to write out-of-place and then atomically move
# TODO: factor this out into `sane-woop` helper?
rm -f /var/lib/ejabberd/ejabberd.yaml.new
${sed} "s/%NATIVE%/$ip/" ${config-in} > /var/lib/ejabberd/ejabberd.yaml.new
${sed} "s/%ANATIVE%/$ip/" ${config-in} > /var/lib/ejabberd/ejabberd.yaml.new
mv /var/lib/ejabberd/ejabberd.yaml{.new,}
'';

View File

@@ -6,11 +6,18 @@
{ config, lib, pkgs, ... }:
{
networking.firewall.allowedTCPPorts = [
# exposed over non-vpn imap.uninsane.org
143 # IMAP
993 # IMAPS
];
sane.ports.ports."143" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-imap-imap.uninsane.org";
};
sane.ports.ports."993" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-imaps-imap.uninsane.org";
};
# exists only to manage certs for dovecot
services.nginx.virtualHosts."imap.uninsane.org" = {

View File

@@ -28,12 +28,21 @@ in
# "/var/lib/dovecot"
];
networking.firewall.allowedTCPPorts = [
# exposed over vpn mx.uninsane.org
25 # SMTP
465 # SMTPS
587 # SMTPS/submission
];
sane.ports.ports."25" = {
protocol = [ "tcp" ];
visibleTo.ovpn = true;
description = "colin-smtp-mx.uninsane.org";
};
sane.ports.ports."465" = {
protocol = [ "tcp" ];
visibleTo.ovpn = true;
description = "colin-smtps-mx.uninsane.org";
};
sane.ports.ports."587" = {
protocol = [ "tcp" ];
visibleTo.ovpn = true;
description = "colin-smtps-submission-mx.uninsane.org";
};
# exists only to manage certs for Postfix
services.nginx.virtualHosts."mx.uninsane.org" = {

View File

@@ -99,4 +99,11 @@
};
sane.services.trust-dns.zones."uninsane.org".inet.CNAME."git" = "native";
sane.ports.ports."22" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-git@git.uninsane.org";
};
}

View File

@@ -16,17 +16,30 @@
{ config, lib, ... }:
{
# identical to:
# services.jellyfin.openFirewall = true;
networking.firewall.allowedUDPPorts = [
# https://jellyfin.org/docs/general/networking/index.html
1900 # UPnP service discovery
7359 # Jellyfin-specific (?) client discovery
];
networking.firewall.allowedTCPPorts = [
8096 # HTTP (for the LAN)
8920 # HTTPS (for the LAN)
];
# https://jellyfin.org/docs/general/networking/index.html
sane.ports.ports."1900" = {
protocol = [ "udp" ];
visibleTo.lan = true;
description = "colin-upnp-for-jellyfin";
};
sane.ports.ports."7359" = {
protocol = [ "udp" ];
visibleTo.lan = true;
description = "colin-jellyfin-specific-client-discovery";
# ^ not sure if this is necessary: copied this port from nixos jellyfin.openFirewall
};
# not sure if 8096/8920 get used either:
sane.ports.ports."8096" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
description = "colin-jellyfin-http-lan";
};
sane.ports.ports."8920" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
description = "colin-jellyfin-https-lan";
};
sane.persist.sys.plaintext = [
{ user = "jellyfin"; group = "jellyfin"; mode = "0700"; directory = "/var/lib/jellyfin"; }
];

View File

@@ -1,4 +1,9 @@
{ lib, ... }:
# XXX mx-discord-puppet uses nodejs_14 which is EOL
# - mx-discord-puppet is abandoned upstream _and_ in nixpkgs
# - recommended to use mautrix-discord: <https://github.com/NixOS/nixpkgs/pull/200462>
lib.mkIf false
{
sane.persist.sys.plaintext = [
{ user = "matrix-synapse"; group = "matrix-synapse"; directory = "/var/lib/mx-puppet-discord"; }

View File

@@ -13,7 +13,19 @@ let
in
{
networking.firewall.allowedTCPPorts = [ 80 443 ];
sane.ports.ports."80" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
visibleTo.ovpn = true; # so that letsencrypt can procure a cert for the mx record
description = "colin-http-uninsane.org";
};
sane.ports.ports."443" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-https-uninsane.org";
};
services.nginx.enable = true;
services.nginx.appendConfig = ''

View File

@@ -12,12 +12,29 @@ lib.mkIf false
sane.persist.sys.plaintext = [
{ user = "prosody"; group = "prosody"; directory = "/var/lib/prosody"; }
];
networking.firewall.allowedTCPPorts = [
5222 # XMPP client -> server
5269 # XMPP server -> server
5280 # bosh
5281 # Prosody HTTPS port (necessary?)
];
sane.ports.ports."5222" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-client-to-server";
};
sane.ports.ports."5269" = {
protocol = [ "tcp" ];
visibleTo.wan = true;
description = "colin-xmpp-server-to-server";
};
sane.ports.ports."5280" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-bosh";
};
sane.ports.ports."5281" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-xmpp-prosody-https"; # necessary?
};
# provide access to certs
users.users.prosody.extraGroups = [ "nginx" ];

View File

@@ -30,17 +30,20 @@
7d ; Expire
5m) ; Negative response TTL
'';
TXT."rev" = "2022122101";
TXT."rev" = "2023052901";
CNAME."native" = "%CNAMENATIVE%";
A."@" = "%ANATIVE%";
A."wan" = "%AWAN%";
A."servo.lan" = config.sane.hosts.by-name."servo".lan-ip;
# XXX NS records must also not be CNAME
# it's best that we keep this identical, or a superset of, what org. lists as our NS.
# so, org. can specify ns2/ns3 as being to the VPN, with no mention of ns1. we provide ns1 here.
A."ns1" = "%NATIVE%";
A."ns1" = "%ANATIVE%";
A."ns2" = "185.157.162.178";
A."ns3" = "185.157.162.178";
A."ovpns" = "185.157.162.178";
A."native" = "%NATIVE%";
A."@" = "%NATIVE%";
NS."@" = [
"ns1.uninsane.org."
"ns2.uninsane.org."
@@ -48,20 +51,68 @@
];
};
sane.services.trust-dns.zones."uninsane.org".file =
"/var/lib/trust-dns/uninsane.org.zone";
sane.services.trust-dns.zones."uninsane.org".file = "uninsane.org.zone";
sane.services.trust-dns.zonedir = null;
systemd.services.trust-dns.preStart = let
sed = "${pkgs.gnused}/bin/sed";
zone-dir = "/var/lib/trust-dns";
zone-out = "${zone-dir}/uninsane.org.zone";
zone-template = pkgs.writeText "uninsane.org.zone.in" config.sane.services.trust-dns.generatedZones."uninsane.org";
in ''
# make WAN records available to trust-dns
mkdir -p ${zone-dir}
ip=$(cat '${config.sane.services.dyn-dns.ipPath}')
${sed} s/%NATIVE%/$ip/ ${zone-template} > ${zone-out}
'';
sane.services.trust-dns.package =
let
sed = "${pkgs.gnused}/bin/sed";
zone-dir = "/var/lib/trust-dns";
zone-wan = "${zone-dir}/wan/uninsane.org.zone";
zone-lan = "${zone-dir}/lan/uninsane.org.zone";
zone-template = config.sane.services.trust-dns.zones."uninsane.org".file;
in pkgs.writeShellScriptBin "named" ''
# compute wan/lan values
mkdir -p ${zone-dir}/{ovpn,wan,lan}
wan=$(cat '${config.sane.services.dyn-dns.ipPath}')
lan=${config.sane.hosts.by-name."servo".lan-ip}
# create specializations that resolve native.uninsane.org to different CNAMEs
${sed} s/%AWAN%/$wan/ ${zone-template} \
| ${sed} s/%CNAMENATIVE%/wan/ \
| ${sed} s/%ANATIVE%/$wan/ \
> ${zone-wan}
${sed} s/%AWAN%/$wan/ ${zone-template} \
| ${sed} s/%CNAMENATIVE%/servo.lan/ \
| ${sed} s/%ANATIVE%/$lan/ \
> ${zone-lan}
# launch the different interfaces, separately
${pkgs.trust-dns}/bin/named --port 53 --zonedir ${zone-dir}/wan/ $@ &
WANPID=$!
${pkgs.trust-dns}/bin/named --port 1053 --zonedir ${zone-dir}/lan/ $@ &
LANPID=$!
# wait until any of the processes exits, then kill them all and exit error
while kill -0 $WANPID $LANPID ; do
sleep 5
done
kill $WANPID $LANPID
exit 1
'';
sane.services.dyn-dns.restartOnChange = [ "trust-dns.service" ];
networking.nat.enable = true;
networking.nat.extraCommands = ''
# redirect incoming DNS requests from LAN addresses
# to the LAN-specialized DNS service
# N.B.: use the `nixos-*` chains instead of e.g. PREROUTING
# because they get cleanly reset across activations or `systemctl restart firewall`
# instead of accumulating cruft
iptables -t nat -A nixos-nat-pre -p udp --dport 53 \
-m iprange --src-range 10.78.76.0-10.78.79.255 \
-j DNAT --to-destination :1053
iptables -t nat -A nixos-nat-pre -p tcp --dport 53 \
-m iprange --src-range 10.78.76.0-10.78.79.255 \
-j DNAT --to-destination :1053
'';
sane.ports.ports."1053" = {
# because the NAT above redirects in nixos-nat-pre, LAN requests behave as though they arrived on the external interface at the redirected port.
# TODO: try nixos-nat-post instead?
protocol = [ "udp" "tcp" ];
visibleTo.lan = true;
description = "colin-redirected-dns-for-lan-namespace";
};
}

View File

@@ -8,6 +8,7 @@
./ids.nix
./machine-id.nix
./net.nix
./nix-path
./persist.nix
./programs
./secrets.nix
@@ -36,11 +37,6 @@
nix.extraOptions = ''
experimental-features = nix-command flakes
'';
# allow `nix-shell` (and probably nix-index?) to locate our patched and custom packages
nix.nixPath = [
"nixpkgs=${pkgs.path}"
"nixpkgs-overlays=${../..}/overlays"
];
# hardlinks identical files in the nix store to save 25-35% disk space.
# unclear _when_ this occurs. it's not a service.
# does the daemon continually scan the nix store?

View File

@@ -76,7 +76,7 @@ let
## Multidisciplinary Association for Psychedelic Studies
(fromDb "mapspodcast.libsyn.com" // uncat)
(fromDb "allinchamathjason.libsyn.com" // pol)
(fromDb "acquired.libsyn.com" // tech)
(fromDb "feeds.transistor.fm/acquired" // tech)
## ACQ2 - more "Acquired" episodes
(fromDb "acquiredlpbonussecretsecret.libsyn.com" // tech)
# The Intercept - Deconstructed; also available: <rss.acast.com/deconstructed>

View File

@@ -1,4 +1,4 @@
{ config, lib, pkgs, ... }:
{ lib, ... }:
{
# the default backend is "wpa_supplicant".
@@ -20,4 +20,8 @@
General.RoamThreshold = "-52"; # default -70
General.RoamThreshold5G = "-52"; # default -76
};
networking.firewall.allowedUDPPorts = [
1900 # to received UPnP advertisements. required by sane-ip-check-upnp
];
}

View File

@@ -0,0 +1,10 @@
{ pkgs, ... }:
{
# allow `nix-shell` (and probably nix-index?) to locate our patched and custom packages
nix.nixPath = [
"nixpkgs=${pkgs.path}"
# 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"
];
}

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

@@ -42,6 +42,7 @@ let
jq
killall
lsof
miniupnpc
nano
netcat
nethogs
@@ -61,6 +62,7 @@ let
tree
usbutils
wget
wirelesstools # iwlist
;
};
sysadminExtraPkgs = {
@@ -93,6 +95,7 @@ let
# - transcoders (ffmpeg, imagemagick) only wanted on desko/lappy ("powerutils"?)
consolePkgs = {
inherit (pkgs)
alsaUtils # for aplay, speaker-test
cdrtools
dmidecode
efivar
@@ -110,7 +113,7 @@ let
lm_sensors # for sensors-detect
lshw
ffmpeg
memtester
# memtester
neovim
# nettools
# networkmanager
@@ -261,6 +264,7 @@ in
./git.nix
./gnome-feeds.nix
./gpodder.nix
./jellyfin-media-player.nix
./kitty
./libreoffice.nix
./mpv.nix
@@ -355,11 +359,6 @@ in
suggestedPrograms = [ "ghostscript" ];
};
# jellyfin stores things in a bunch of directories: this one persists auth info.
# it *might* be possible to populate this externally (it's Qt stuff), but likely to
# be fragile and take an hour+ to figure out.
jellyfin-media-player.persist.plaintext = [ ".local/share/Jellyfin Media Player" ];
# actual monero blockchain (not wallet/etc; safe to delete, just slow to regenerate)
# XXX: is it really safe to persist this? it doesn't have info that could de-anonymize if captured?
monero-gui.persist.plaintext = [ ".bitmonero" ];

View File

@@ -0,0 +1,13 @@
{ pkgs, ... }:
{
sane.programs.jellyfin-media-player = {
package = pkgs.jellyfin-media-player;
# package = pkgs.jellyfin-media-player-qt6;
# jellyfin stores things in a bunch of directories: this one persists auth info.
# it *might* be possible to populate this externally (it's Qt stuff), but likely to
# be fragile and take an hour+ to figure out.
persist.plaintext = [ ".local/share/Jellyfin Media Player" ];
};
}

View File

@@ -56,10 +56,26 @@ let
nixExtensions = concatMap (ext: optional ext.enable ext.package) (attrValues cfg.addons);
extraPolicies = {
FirefoxHome = {
Search = true;
Pocket = false;
Snippets = false;
TopSites = false;
Highlights = false;
};
NoDefaultBookmarks = true;
OfferToSaveLogins = false;
OfferToSaveLoginsDefault = false;
PasswordManagerEnabled = false;
SearchEngines = {
Default = "DuckDuckGo";
};
UserMessaging = {
ExtensionRecommendations = false;
SkipOnboarding = true;
};
# these were taken from Librewolf
AppUpdateURL = "https://localhost";
DisableAppUpdate = true;
OverrideFirstRunPage = "";
@@ -88,6 +104,7 @@ let
# };
# NewTabPage = true;
};
# extraPrefs = ...
};
addonOpts = types.submodule {

View File

@@ -138,7 +138,7 @@ in
}
''
+ lib.optionalString cfg.showDeadlines ''
${pkgs.sane-scripts}/bin/sane-deadlines
${pkgs.sane-scripts.deadlines}/bin/sane-deadlines
''
+ ''
# auto-cd into any of these dirs by typing them and pressing 'enter':

View File

@@ -30,4 +30,15 @@ in
})
(globalKeys ++ domainKeys)
);
services.openssh = {
enable = true;
settings.PermitRootLogin = "no";
settings.PasswordAuthentication = false;
};
sane.ports.ports."22" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
description = lib.mkDefault "colin-ssh";
};
}

View File

@@ -129,11 +129,5 @@ in
enable = true;
wheelNeedsPassword = false;
};
services.openssh = {
enable = true;
settings.PermitRootLogin = "no";
settings.PasswordAuthentication = false;
};
};
}

View File

@@ -90,7 +90,7 @@ in
};
sane.gui.sxmo.terminal = mkOption {
# type = types.nullOr (types.enum [ "foot" "st" "vte" ]);
type = types.nullOr types.string;
type = types.nullOr types.str;
default = "foot";
description = ''
name of terminal to use for sxmo_terminal.sh.
@@ -99,7 +99,7 @@ in
};
sane.gui.sxmo.keyboard = mkOption {
# type = types.nullOr (types.enum ["wvkbd"])
type = types.nullOr types.string;
type = types.nullOr types.str;
default = "wvkbd";
description = ''
name of on-screen-keyboard to use for sxmo_keyboard.sh.
@@ -108,7 +108,7 @@ in
'';
};
sane.gui.sxmo.settings = mkOption {
type = types.attrsOf types.string;
type = types.attrsOf types.str;
default = {};
description = ''
environment variables used to configure sxmo.

View File

@@ -26,7 +26,7 @@ in
type = types.bool;
};
sane.nixcache.substituters = mkOption {
type = types.listOf types.string;
type = types.listOf types.str;
default =
# TODO: make these blacklisted entries injectable
(lib.optional (hostName != "servo") "https://nixcache.uninsane.org")

View File

@@ -10,7 +10,7 @@
};
config = lib.mkIf config.sane.roles.ac {
sane.yggdrasil.enable = true;
services.i2p.enable = true;
# sane.yggdrasil.enable = true;
# services.i2p.enable = true;
};
}

View File

@@ -33,6 +33,11 @@ in
type = types.bool;
default = false;
};
sane.services.wg-home.enableWan = mkOption {
type = types.bool;
default = false;
description = "whether to make this port visible on the WAN";
};
sane.services.wg-home.ip = mkOption {
type = types.str;
};
@@ -50,7 +55,12 @@ in
# this config defines both the endpoint (server) and client configs
# for convenience, have both the server and client use the same port for their wireguard connections.
networking.firewall.allowedUDPPorts = [ 51820 ];
sane.ports.ports."51820" = {
protocol = [ "udp" ];
visibleTo.lan = true;
visibleTo.wan = cfg.enableWan;
description = "colin-wireguard";
};
networking.wireguard.interfaces.wg-home = {
listenPort = 51820;
privateKeyFile = "/run/wg-home.priv";

View File

@@ -1,21 +0,0 @@
{
"bozo": 0,
"content_length": 1369733,
"content_type": "application/rss+xml; charset=utf-8",
"description": "Every company has a story. Learn the playbooks that built the worlds greatest companies — and how you can apply them as a founder, operator, or investor.",
"favicon": null,
"hubs": [],
"is_podcast": true,
"is_push": false,
"item_count": 173,
"last_seen": "2023-01-11T15:26:37.515527+00:00",
"last_updated": "2022-12-19T07:22:28+00:00",
"score": 18,
"self_url": "https://acquired.libsyn.com/rss",
"site_name": null,
"site_url": null,
"title": "Acquired",
"url": "https://acquired.libsyn.com/rss",
"velocity": 0.066,
"version": "rss20"
}

View File

@@ -1,21 +1,23 @@
{
"bozo": 0,
"content_length": 443732,
"content_type": "application/rss+xml; charset=utf-8",
"description": "Ben and David are joined by expert founders and investors \u2014 writing the next generation of great company stories in real-time.\n\nWe go behind the scenes on their journeys and bring back emerging insights and lessons that are useful for anyone in the tech and investing ecosystems.\n\nAcquired covers yesterday. ACQ2 covers tomorrow.",
"content_length": 567579,
"content_type": "text/xml; charset=utf-8",
"description": "ACQ2 is Ben and David's conversations with expert founders and investors.",
"favicon": "",
"favicon_data_uri": "",
"hubs": [],
"hubs": [
"https://pubsubhubbub.appspot.com/"
],
"is_podcast": true,
"is_push": false,
"item_count": 92,
"last_updated": "2023-03-02T17:03:15+00:00",
"score": 10,
"self_url": "https://acquiredlpbonussecretsecret.libsyn.com/",
"site_name": "ACQ2 by Acquired",
"site_url": "https://acquiredlpbonussecretsecret.libsyn.com",
"title": "ACQ2 by Acquired",
"url": "https://acquiredlpbonussecretsecret.libsyn.com",
"velocity": 0.057,
"is_push": true,
"item_count": 91,
"last_updated": "2023-05-09T06:51:48+00:00",
"score": 24,
"self_url": "https://feeds.transistor.fm/acq2",
"site_name": "ACQ2: The Acquired Interviews",
"site_url": "https://feeds.transistor.fm",
"title": "ACQ2: The Acquired Interviews",
"url": "https://feeds.transistor.fm/acq2",
"velocity": 0.054,
"version": "rss20"
}

View File

@@ -0,0 +1,23 @@
{
"bozo": 0,
"content_length": 1579416,
"content_type": "text/xml; charset=utf-8",
"description": "Every company has a story.",
"favicon": "",
"favicon_data_uri": "",
"hubs": [
"https://pubsubhubbub.appspot.com/"
],
"is_podcast": true,
"is_push": true,
"item_count": 178,
"last_updated": "2023-05-30T05:02:40+00:00",
"score": 24,
"self_url": "https://feeds.transistor.fm/acquired",
"site_name": "",
"site_url": "https://feeds.transistor.fm",
"title": "Acquired",
"url": "https://feeds.transistor.fm/acquired",
"velocity": 0.064,
"version": "rss20"
}

View File

@@ -1,21 +1,21 @@
{
"bozo": 0,
"content_length": 235911,
"content_length": 145311,
"content_type": "application/xml; charset=utf-8",
"description": "<p>The Portal is an exploration into discovery, including conversations with thought leaders. Host Eric Weinstein, Managing Director of Thiel Capital, brings his unique expertise and diverse roster of guests for a wide range of discussions, including science, culture, business, and capitalism. The show will feature people whose lives demonstrate that portals into what we would normally consider impossible, are indeed possible.&nbsp;&nbsp;Guests include presidential candidate Andrew Yang, NY Times bestselling author Sam Harris, and retired Navy Seal and creator of the hit business podcast Jocko Willink.</p>",
"favicon": null,
"favicon": "",
"favicon_data_uri": "",
"hubs": [],
"is_podcast": true,
"is_push": false,
"item_count": 44,
"last_seen": "2023-01-11T14:47:44.995855+00:00",
"last_updated": "2020-12-02T07:50:55+00:00",
"score": -12,
"self_url": "https://www.omnycontent.com/d/playlist/9b7dacdf-a925-4f95-84dc-ac46003451ff/1713c520-edb6-43a3-b1b9-acb8002fdae7/58e33a0c-f86b-41c5-a11c-acb8002fdaf5/podcast.rss",
"site_name": null,
"site_url": null,
"score": 8,
"self_url": "",
"site_name": "",
"site_url": "",
"title": "The Portal",
"url": "https://www.omnycontent.com/d/playlist/9b7dacdf-a925-4f95-84dc-ac46003451ff/1713c520-edb6-43a3-b1b9-acb8002fdae7/58e33a0c-f86b-41c5-a11c-acb8002fdaf5/podcast.rss",
"url": "https://feed.cdnstream1.com/zjb/feed/download/d9/8a/71/d98a71ac-d1a3-4d92-ab64-64b4ff3192d1.xml",
"velocity": 0.082,
"version": "rss20"
}
}

View File

@@ -2,12 +2,14 @@
{
imports = [
./dns.nix
./feeds.nix
./fs
./ids.nix
./programs.nix
./image.nix
./persist
./ports.nix
./services
./sops.nix
./ssh.nix

145
modules/dns.nix Normal file
View File

@@ -0,0 +1,145 @@
{ config, lib, pkgs, ... }:
let
cfg = config.sane.dns;
toml = pkgs.formats.toml { };
recordFormatters = {
# quote rules for zone files:
# - any character may be encoded by `\DDD`, where `DDD` represents its ascii value in base 8.
# - any non-digit `X` may be encoded by `\X`.
# - stated in: <https://www.ietf.org/rfc/rfc1035.txt>: 5.1 Format
# - visible in <trust-dns:crates/proto/src/serialize/txt/zone_lex.rs:escape_seq>
# for us, we can just replace `\` => `\\ and `"` -> `\"`
TXT = value: "\"" + (lib.escape [ "\\" "\"" ] value) + "\"";
};
# proto: "INET", etc
# rrtype: "TXT", "A", "CNAME", etc
fmtRecord = proto: rrtype: name: value:
let
formatter = recordFormatters."${rrtype}" or lib.id;
in
"${name}\t${proto}\t${rrtype}\t${formatter value}";
fmtRecordList = proto: rrtype: name: values: concatStringsSep
"\n"
(map (fmtRecord proto rrtype name) values)
;
fmtRecordAttrs = proto: rrtype: rrAttrs:
concatStringsSep
"\n"
(
attrValues (
mapAttrs
(name: fmtRecordList proto rrtype name)
rrAttrs
)
);
# format other .zone files to include into this one
fmtIncludes = paths: concatStringsSep
"\n"
(map (path: "$INCLUDE ${path}") paths);
genZone = zcfg: ''
$TTL ${toString zcfg.TTL}
${fmtRecordAttrs "IN" "SOA" zcfg.inet.SOA}
${fmtRecordAttrs "IN" "A" zcfg.inet.A}
${fmtRecordAttrs "IN" "CNAME" zcfg.inet.CNAME}
${fmtRecordAttrs "IN" "MX" zcfg.inet.MX}
${fmtRecordAttrs "IN" "NS" zcfg.inet.NS}
${fmtRecordAttrs "IN" "SRV" zcfg.inet.SRV}
${fmtRecordAttrs "IN" "TXT" zcfg.inet.TXT}
${fmtIncludes zcfg.include}
${zcfg.extraConfig}
'';
# (listOf ty) type which also accepts single-assignment of `ty`.
# it's used to allow the user to write:
# CNAME."foo" = "bar";
# as shorthand for
# CNAME."foo" = [ "bar" ];
listOrUnit = ty: types.coercedTo ty (elem: [ elem ]) (types.listOf ty);
in
{
options = {
sane.dns = {
zones = mkOption {
type = types.attrsOf (types.submodule {
options = {
name = mkOption {
type = types.nullOr types.str;
description = "zone name. defaults to the attribute name in zones";
default = null;
};
TTL = mkOption {
type = types.int;
description = "default TTL";
default = 3600;
};
include = mkOption {
type = types.listOf types.str;
description = "paths of other zone files to $INCLUDE into this one";
default = [];
};
extraConfig = mkOption {
type = types.lines;
description = "extra lines to append to the zone file";
default = "";
};
inet = {
SOA = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "Start of Authority record(s)";
default = {};
};
A = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "IPv4 address record(s)";
default = {};
};
CNAME = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "canonical name record(s)";
default = {};
};
MX = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "mail exchanger record(s)";
default = {};
};
NS = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "name server record(s)";
default = {};
};
SRV = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "service record(s)";
default = {};
};
TXT = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "text record(s)";
default = {};
};
};
file = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
instead of using the generated zone file, use the specified path (user should populate the file specified here).
'';
};
};
});
default = {};
description = "Declarative zone config";
};
};
};
config = {
sane.services.trust-dns.zones = builtins.mapAttrs (_name: zcfg: {
text = genZone zcfg;
}) cfg.zones;
};
}

113
modules/ports.nix Normal file
View File

@@ -0,0 +1,113 @@
{ config, lib, pkgs, ... }:
let
cfg = config.sane.ports;
portOpts = with lib; types.submodule {
options = {
protocol = mkOption {
type = types.listOf (types.enum [ "udp" "tcp" ]);
};
visibleTo.lan = mkOption {
type = types.bool;
default = false;
# XXX: if a service is visible to the WAN, it ends up visible to the LAN as well.
# technically solvable (explicitly drop packets delivered from LAN IPs) but doesn't make much sense.
};
visibleTo.wan = mkOption {
type = types.bool;
default = false;
};
visibleTo.ovpn = mkOption {
type = types.bool;
default = false;
# XXX: behaves more or less the same as `lan` visibility.
# OVPN passes everything by default.
# TODO: have *this* drive what we forward from wireguard namespace to main namespace
};
description = mkOption {
type = types.str;
default = "colin-${config.net.hostName}";
description = ''
short description of why this port is open.
this is shown, for example, in an upstream's UPnP status page.
'';
};
};
};
# gives networking.firewall value for a given "${port}" = portCfg.
firewallConfigForPort = port: portCfg:
# any form of visibility means we need to open the firewall
lib.mkIf (portCfg.visibleTo.lan || portCfg.visibleTo.wan || portCfg.visibleTo.ovpn) {
allowedTCPPorts = lib.optional (lib.elem "tcp" portCfg.protocol) (lib.toInt port);
allowedUDPPorts = lib.optional (lib.elem "udp" portCfg.protocol) (lib.toInt port);
};
in
{
options = with lib; {
sane.ports = {
openFirewall = mkOption {
default = false;
type = types.bool;
};
openUpnp = mkOption {
default = false;
type = types.bool;
};
upnpRenewInterval = mkOption {
default = "1hr";
type = types.str;
description = "how frequently to renew UPnP leases";
};
upnpLeaseDuration = mkOption {
default = 86400;
type = types.int;
description = "how long to lease UPnP ports for";
};
ports = mkOption {
type = types.attrsOf portOpts;
default = {};
};
};
};
config = lib.mkMerge [
(lib.mkIf cfg.openFirewall {
networking.firewall = lib.mkMerge (lib.mapAttrsToList firewallConfigForPort cfg.ports);
})
(lib.mkIf cfg.openUpnp {
systemd.services.upnp-forwards = {
description = "forward ports from upstream gateway to this host";
serviceConfig.Type = "oneshot";
restartTriggers = [(builtins.toJSON cfg)];
after = [ "network.target" ];
script =
let
portFwd = "${pkgs.sane-scripts.ip-port-forward}/bin/sane-ip-port-forward";
forwardsPerCfg = lib.mapAttrsToList
(port: portCfg: lib.optionals portCfg.visibleTo.wan
(
lib.optional (lib.elem "udp" portCfg.protocol) "udp:${port}:${portCfg.description}"
++ lib.optional (lib.elem "tcp" portCfg.protocol) "tcp:${port}:${portCfg.description}"
)
)
cfg.ports;
forwards = lib.flatten forwardsPerCfg;
in ''
${portFwd} -v -d ${builtins.toString cfg.upnpLeaseDuration} \
${lib.escapeShellArgs forwards}
'';
};
systemd.timers.upnp-forwards = {
wantedBy = [ "network.target" ];
timerConfig = {
OnStartupSec = "1min";
OnUnitActiveSec = cfg.upnpRenewInterval;
};
};
})
];
}

View File

@@ -5,8 +5,9 @@ let
cfg = config.sane.services.dyn-dns;
getIp = pkgs.writeShellScript "dyn-dns-query-wan" ''
# preferred method and fallback
${pkgs.sane-scripts}/bin/sane-ip-check-router-wan || \
${pkgs.sane-scripts}/bin/sane-ip-check
# OPNsense router broadcasts its UPnP endpoint every 30s
timeout 60 ${pkgs.sane-scripts.ip-check-upnp}/bin/sane-ip-check-upnp || \
${pkgs.sane-scripts.ip-check}/bin/sane-ip-check
'';
in
{

View File

@@ -7,50 +7,6 @@ with lib;
let
cfg = config.sane.services.trust-dns;
toml = pkgs.formats.toml { };
recordFormatters = {
# quote rules for zone files:
# - any character may be encoded by `\DDD`, where `DDD` represents its ascii value in base 8.
# - any non-digit `X` may be encoded by `\X`.
# - stated in: <https://www.ietf.org/rfc/rfc1035.txt>: 5.1 Format
# - visible in <trust-dns:crates/proto/src/serialize/txt/zone_lex.rs:escape_seq>
# for us, we can just replace `\` => `\\ and `"` -> `\"`
TXT = value: "\"" + (lib.escape [ "\\" "\"" ] value) + "\"";
};
fmtRecord = proto: rrtype: name: value:
let
formatter = recordFormatters."${rrtype}" or lib.id;
in
"${name}\t${proto}\t${rrtype}\t${formatter value}";
fmtRecordList = proto: rrtype: name: values: concatStringsSep
"\n"
(map (fmtRecord proto rrtype name) values)
;
fmtRecordAttrs = proto: rrtype: rrAttrs:
concatStringsSep
"\n"
(
attrValues (
mapAttrs
(name: fmtRecordList proto rrtype name)
rrAttrs
)
);
fmtIncludes = paths: concatStringsSep
"\n"
(map (path: "$INCLUDE ${path}") paths);
genZone = zcfg: ''
$TTL ${toString zcfg.TTL}
${fmtRecordAttrs "IN" "SOA" zcfg.inet.SOA}
${fmtRecordAttrs "IN" "A" zcfg.inet.A}
${fmtRecordAttrs "IN" "CNAME" zcfg.inet.CNAME}
${fmtRecordAttrs "IN" "MX" zcfg.inet.MX}
${fmtRecordAttrs "IN" "NS" zcfg.inet.NS}
${fmtRecordAttrs "IN" "SRV" zcfg.inet.SRV}
${fmtRecordAttrs "IN" "TXT" zcfg.inet.TXT}
${fmtIncludes zcfg.include}
${zcfg.extraConfig}
'';
configFile = toml.generate "trust-dns.toml" {
listen_addrs_ipv4 = cfg.listenAddrsIPv4;
@@ -58,20 +14,10 @@ let
mapAttrs (zname: zcfg: rec {
zone = if zcfg.name == null then zname else zcfg.name;
zone_type = "Primary";
file = if zcfg.file == null then
pkgs.writeText "${zone}.zone" (genZone zcfg)
else
zcfg.file;
file = zcfg.file;
}) cfg.zones
);
};
# (listOf ty) type which also accepts single-assignment of `ty`.
# it's used to allow the user to write:
# CNAME."foo" = "bar";
# as shorthand for
# CNAME."foo" = [ "bar" ];
listOrUnit = ty: types.coercedTo ty (elem: [ elem ]) (types.listOf ty);
in
{
options = {
@@ -80,6 +26,14 @@ in
default = false;
type = types.bool;
};
package = mkOption {
type = types.package;
default = pkgs.trust-dns;
description = ''
trust-dns package to use.
should provide bin/named, which will be invoked with --config x and --zonedir d and maybe -q.
'';
};
listenAddrsIPv4 = mkOption {
type = types.listOf types.str;
default = [];
@@ -89,101 +43,65 @@ in
type = types.bool;
default = false;
};
zonedir = mkOption {
type = types.nullOr types.str;
default = "/";
description = ''
where the `file` option in zones.* is relative to.
'';
};
# reference <nixpkgs:nixos/modules/services/web-servers/nginx/vhost-options.nix>
zones = mkOption {
type = types.attrsOf (types.submodule {
type = types.attrsOf (types.submodule ({ config, name, ... }: {
options = {
name = mkOption {
type = types.nullOr types.str;
description = "zone name. defaults to the attribute name in zones";
default = name;
};
text = mkOption {
type = types.nullOr types.str; # TODO: types.lines?
default = null;
};
TTL = mkOption {
type = types.int;
description = "default TTL";
default = 3600;
};
include = mkOption {
type = types.listOf types.str;
description = "paths of other zone files to $INCLUDE into this one";
default = [];
};
extraConfig = mkOption {
type = types.lines;
description = "extra lines to append to the zone file";
default = "";
};
inet = {
SOA = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "Start of Authority record(s)";
default = {};
};
A = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "IPv4 address record(s)";
default = {};
};
CNAME = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "canonical name record(s)";
default = {};
};
MX = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "mail exchanger record(s)";
default = {};
};
NS = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "name server record(s)";
default = {};
};
SRV = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "service record(s)";
default = {};
};
TXT = mkOption {
type = types.attrsOf (listOrUnit types.str);
description = "text record(s)";
default = {};
};
};
file = mkOption {
type = types.nullOr types.str;
default = null;
description = "instead of using the generated zone file, use the specified path";
type = types.nullOr types.str; # TODO: types.path?
description = ''
runtime path to a .zone file.
if omitted, will be generated from the `text` option.
'';
};
};
});
config = {
file = lib.mkIf (config.text != null) (pkgs.writeText "${config.name}.zone" config.text);
};
}));
default = {};
description = "Declarative zone config";
};
generatedZones = mkOption {
type = types.attrsOf types.str;
description = "generated zone text for each zone";
};
};
};
config = mkIf cfg.enable {
sane.services.trust-dns.generatedZones = mapAttrs (zone: zcfg: genZone zcfg) cfg.zones;
networking.firewall.allowedTCPPorts = [ 53 ];
networking.firewall.allowedUDPPorts = [ 53 ];
sane.ports.ports."53" = {
protocol = [ "udp" "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-dns-hosting";
};
systemd.services.trust-dns = {
description = "trust-dns DNS server";
serviceConfig = {
ExecStart =
let
flags = lib.optionalString cfg.quiet "-q";
flags = lib.optional cfg.quiet "-q" ++
lib.optionals (cfg.zonedir != null) [ "--zonedir" cfg.zonedir ];
flagsStr = builtins.concatStringsSep " " flags;
in ''
${pkgs.trust-dns}/bin/named \
${cfg.package}/bin/named \
--config ${configFile} \
--zonedir / ${flags}
${flagsStr}
'';
Type = "simple";
Restart = "on-failure";

View File

@@ -0,0 +1,14 @@
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index d188ecdda55..69174ba7dc7 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -26607,7 +26607,8 @@ with pkgs;
tinyalsa = callPackage ../os-specific/linux/tinyalsa { };
- inherit (callPackage ../os-specific/linux/alsa-project { })
+ alsa-project = callPackage ../os-specific/linux/alsa-project { };
+ inherit (alsa-project)
alsa-firmware
alsa-lib
alsa-oss

View File

@@ -129,14 +129,6 @@ in [
hash = "sha256-+g3XhmBt/udhbBDiVyfWnfXKvZTvDurlvPblQ9HYp3s=";
})
(fetchpatch' {
# 2023/05/24: merged upstream
# hare: unstable-2023-03-15 -> unstable-2023-04-23
# + harec: unstable-2023-02-18 -> unstable-2023-04-25
prUrl = "https://github.com/NixOS/nixpkgs/pull/233732";
hash = "sha256-SGDKvsMiK3Pq57JEj/MamDBX5jBXwV/E5jclKO2NAUs=";
})
# (fetchpatch' {
# title = "hare-json: init at unstable-2023-01-31";
# saneCommit = "260f9c6ac4e3564acbceb46aa4b65fbb652f8e23";
@@ -158,6 +150,9 @@ in [
hash = "sha256-9XKPNg7TewicfbMgiASpYysTs5aduIVP+4onz+noc/0=";
})
# make alsa-project members overridable
./2023-05-31-toplevel-alsa.patch
# for raspberry pi: allow building u-boot for rpi 4{,00}
# TODO: remove after upstreamed: https://github.com/NixOS/nixpkgs/pull/176018
# (it's a dupe of https://github.com/NixOS/nixpkgs/pull/112677 )

View File

@@ -378,8 +378,17 @@ in {
# };
# fixes: "src/meson.build:106:0: ERROR: Program 'glib-compile-resources' not found or not executable"
file-roller = mvToNativeInputs [ final.glib ] super.file-roller;
gnome-bluetooth = super.gnome-bluetooth.override {
# fixes -msse2, -mfpmath=sse flags
wrapGAppsHook4 = final.wrapGAppsHook;
};
# fixes: "meson.build:75:6: ERROR: Program 'gtk-update-icon-cache' not found or not executable"
gnome-clocks = addNativeInputs [ final.gtk4 ] super.gnome-clocks;
gnome-clocks = (
addNativeInputs [ final.gtk4 ] super.gnome-clocks
).override {
# fixes -msse2, -mfpmath=sse flags
wrapGAppsHook4 = final.wrapGAppsHook;
};
# fixes: "src/meson.build:3:0: ERROR: Program 'glib-compile-resources' not found or not executable"
gnome-color-manager = mvToNativeInputs [ final.glib ] super.gnome-color-manager;
# fixes "subprojects/gvc/meson.build:30:0: ERROR: Program 'glib-mkenums mkenums' not found or not executable"
@@ -470,7 +479,7 @@ in {
gnome-user-share = addNativeInputs [ final.glib ] super.gnome-user-share;
# fixes: "FileNotFoundError: [Errno 2] No such file or directory: 'gtk4-update-icon-cache'"
gnome-weather = addNativeInputs [ final.gtk4 ] super.gnome-weather;
mutter = super.mutter.overrideAttrs (orig: {
mutter = (super.mutter.overrideAttrs (orig: {
nativeBuildInputs = orig.nativeBuildInputs ++ [
final.glib # fixes "clutter/clutter/meson.build:281:0: ERROR: Program 'glib-mkenums mkenums' not found or not executable"
final.buildPackages.gobject-introspection # allows to build without forcing `introspection=false` (which would break gnome-shell)
@@ -481,7 +490,10 @@ in {
];
mesonFlags = lib.remove "-Ddocs=true" orig.mesonFlags;
outputs = lib.remove "devdoc" orig.outputs;
});
})).override {
# fixes -msse2, -mfpmath=sse flags
wrapGAppsHook4 = final.wrapGAppsHook;
};
# nautilus = super.nautilus.override {
# # fixes: "meson.build:123:0: ERROR: Dependency "libxml-2.0" not found, tried pkgconfig"
# # new failure mode: "/nix/store/grqh2wygy9f9wp5bgvqn4im76v82zmcx-binutils-2.39/bin/ld: /nix/store/f7yr5z123d162p5457jh3wzkqm7x8yah-glib-2.74.3/lib/libglib-2.0.so: error adding symbols: file in wrong format"
@@ -1134,7 +1146,14 @@ in {
addNativeInputs [ final.wayland-scanner ] (
mvToNativeInputs [ final.gettext final.glib ] prev.xdg-desktop-portal-gnome
)
);
).override {
# fixes -msse2, -mfpmath=sse flags
wrapGAppsHook4 = final.wrapGAppsHook;
};
# "fatal error: urcu.h: No such file or directory"
# xfsprogs wants to compile things for the build target (BUILD_CC)
# xfsprogs = useEmulatedStdenv prev.xfsprogs;
xfsprogs = addNativeInputs [ final.liburcu ] prev.xfsprogs;
# webkitgtk = prev.webkitgtk.override { stdenv = final.ccacheStdenv; };
# webp-pixbuf-loader = prev.webp-pixbuf-loader.override {
# # fixes "Builder called die: Cannot wrap '/nix/store/kpp8qhzdjqgvw73llka5gpnsj0l4jlg8-gdk-pixbuf-aarch64-unknown-linux-gnu-2.42.10/bin/gdk-pixbuf-thumbnailer' because it is not an executable file"
@@ -1150,6 +1169,10 @@ in {
});
# XXX: aarch64 webp-pixbuf-loader wanted by gdk-pixbuf-loaders.cache.drv, wanted by aarch64 gnome-control-center
# "extract-binary-wrapper-cmd: line 2: strings: command not found"
# XXX: technically this belongs in pkgs/build-support/setup-hooks/make-binary-wrapper/default.nix ?
wrapFirefox = browser: args: addNativeInputs [ final.bintools-unwrapped ] (prev.wrapFirefox browser args);
wvkbd = (
# "wayland-scanner: no such program"
mvToNativeInputs [ final.wayland-scanner ] prev.wvkbd

View File

@@ -0,0 +1,24 @@
{ alsa-ucm-conf }:
alsa-ucm-conf.overrideAttrs (upstream: {
# upstream alsa ships with PinePhone audio configs, but they don't actually produce sound.
# see: <https://github.com/alsa-project/alsa-ucm-conf/pull/134>
# these audio files come from some revision of:
# - <https://gitlab.manjaro.org/manjaro-arm/packages/community/phosh/alsa-ucm-pinephone>
#
# alternative to patching is to plumb `ALSA_CONFIG_UCM2 = "${./ucm2}"` environment variable into the relevant places
# e.g. `systemd.services.pulseaudio.environment`.
# that leaves more opportunity for gaps (i.e. missing a service),
# on the other hand this method causes about 500 packages to be rebuilt (including qt5 and webkitgtk).
#
# note that with these files, the following audio device support:
# - headphones work.
# - "internal earpiece" works.
# - "internal speaker" doesn't work.
# - "analog output" doesn't work.
postPatch = upstream.postPatch or "" + ''
cp ${./ucm2/PinePhone}/* ucm2/Allwinner/A64/PinePhone/
# fix the self-contained ucm files i source from to have correct path within the alsa-ucm-conf source tree
sed -i 's:"HiFi.conf":"/Allwinner/A64/PinePhone/HiFi.conf":' ucm2/Allwinner/A64/PinePhone/PinePhone.conf
sed -i 's:"VoiceCall.conf":"/Allwinner/A64/PinePhone/VoiceCall.conf":' ucm2/Allwinner/A64/PinePhone/PinePhone.conf
'';
})

View File

@@ -31,8 +31,8 @@ in
# repeat imports are deduplicated by url, even when offline.
postBuild = ''
makeWrapper $out/bin/gpodder $out/bin/gpodder-configured \
--run "$out/bin/gpodder-remove-extra ~/.config/gpodderFeeds.opml" \
--run "$out/bin/gpo import ~/.config/gpodderFeeds.opml" \
--run "$out/bin/gpodder-remove-extra ~/.config/gpodderFeeds.opml || true" \
--run "$out/bin/gpo import ~/.config/gpodderFeeds.opml || true" \
# fix up the .desktop file to invoke our wrapped application
orig_desktop=$(readlink $out/share/applications/gpodder.desktop)

View File

@@ -0,0 +1,24 @@
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bcebe43..a15b0ef 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -107,8 +107,8 @@ endif()
set(RESOURCE_ROOT .)
if(APPLE)
set(RESOURCE_ROOT Resources)
- add_resources(TARGET ${MAIN_TARGET} SOURCES ${CMAKE_CURRENT_BINARY_DIR}/../dist/ DEST ${RESOURCE_ROOT}/web-client/desktop)
- add_resources(TARGET ${MAIN_TARGET} SOURCES ${CMAKE_SOURCE_DIR}/native/ DEST ${RESOURCE_ROOT}/web-client/extension)
+ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../../jellyfin-web/ DESTINATION ${RESOURCE_ROOT}/web-client/desktop)
+ install(DIRECTORY ${CMAKE_SOURCE_DIR}/native/ DESTINATION ${RESOURCE_ROOT}/web-client/extension)
endif()
if(NOT APPLE)
@@ -121,7 +121,7 @@ if(NOT APPLE)
install(FILES ${loc}/qtwebengine_devtools_resources.pak DESTINATION resources)
endif()
endforeach()
- install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../dist/ DESTINATION ${INSTALL_RESOURCE_DIR}/web-client/desktop)
+ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../../jellyfin-web/ DESTINATION ${INSTALL_RESOURCE_DIR}/web-client/desktop)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/native/ DESTINATION ${INSTALL_RESOURCE_DIR}/web-client/extension)
endif()

View File

@@ -0,0 +1,13 @@
diff --git a/src/input/InputComponent.cpp b/src/input/InputComponent.cpp
index 0f5f129..94596b6 100644
--- a/src/input/InputComponent.cpp
+++ b/src/input/InputComponent.cpp
@@ -132,7 +132,7 @@ void InputComponent::handleAction(const QString& action)
else
{
qDebug() << "Invoking slot" << qPrintable(recvSlot->m_slot.data());
- QGenericArgument arg0 = QGenericArgument();
+ QMetaMethodArgument arg0;
if (recvSlot->m_hasArguments)
arg0 = Q_ARG(const QString&, hostArguments);

View File

@@ -0,0 +1,66 @@
{ lib
, cmake
, fetchFromGitHub
, jellyfin-media-player
, libGL
, libX11
, libXrandr
, libvdpau
, mpv
, ninja
, pkg-config
, python3
, qt6
, SDL2
, stdenv
}:
(jellyfin-media-player.overrideAttrs (upstream: {
src = fetchFromGitHub {
owner = "jellyfin";
repo = "jellyfin-media-player";
rev = "qt6";
hash = "sha256-saR/P2daqjF0G8N7BX6Rtsb1dWGjdf5MPDx1lhoioEw=";
};
# nixos ships two patches:
# - the first fixes "web paths" and has *mostly* been upstreamed (so skip and manually tweak a bit)
# - the second disables auto-update notifications (keep)
patches = (builtins.tail upstream.patches) ++ [
./0001-fix-web-path.patch
./0002-qt6-build-fixes.patch
];
buildInputs = [
SDL2
libGL
libX11
libXrandr
libvdpau
mpv
qt6.qtbase
qt6.qtwebchannel
qt6.qtwebengine
# qtx11extras
] ++ lib.optionals stdenv.isLinux [
qt6.qtwayland
];
nativeBuildInputs = [
cmake
ninja
pkg-config
python3
# new packages which weren't needed before
qt6.wrapQtAppsHook # replaces the implicit qt5 version
qt6.qt5compat
];
cmakeFlags = [
"-DCMAKE_BUILD_TYPE=Release"
"-DQTROOT=${qt6.qtbase}"
"-GNinja"
];
meta = upstream.meta // {
platforms = upstream.meta.platforms ++ [ "aarch64-linux" ];
};
}))

View File

@@ -1,109 +1,13 @@
{ lib
, pkgs
, resholve
, static-nix-shell
, symlinkJoin
}:
let
shell-scripts = resholve.mkDerivation {
# resholve documentation:
# - nix: https://github.com/nixos/nixpkgs/blob/master/pkgs/development/misc/resholve/README.md
# - generic: https://github.com/abathur/resholve
pname = "sane-scripts";
version = "0.1.0";
src = ./src;
solutions = {
default = {
# note: `scripts` refers to the store path here
scripts = [ "bin/*" ];
interpreter = "${pkgs.bash}/bin/bash";
inputs = with pkgs; [
# string is interpreted as relative path from @OUT@.
# this lets our scripts reference eachother.
# see: <https://github.com/abathur/resholve/issues/26>
"bin"
coreutils-full
curl
file
findutils
git
gnugrep
gnused
gocryptfs
ifuse
inetutils
inotify-tools
iwd
jq
ncurses
oath-toolkit
openssh
openssl
rmlint
rsync
ssh-to-age
sops
sudo
systemd
util-linux
which
];
keep = {
# we write here: keep it
"/tmp/rmlint.sh" = true;
# intentionally escapes (into user code)
"$external_cmd" = true;
"$maybe_sudo" = true;
};
fake = {
external = [
# https://github.com/abathur/resholve/issues/29
# "umount"
# "/run/wrappers/bin/sudo"
"sudo"
];
};
fix = {
# this replaces umount with the non-setuid-wrapper umount.
# not sure if/where that lack of suid causes problems.
umount = true;
};
prologue = "${./resholve-prologue}";
# list of programs which *can* or *cannot* exec their arguments
execer = with pkgs; [
"cannot:${git}/bin/git"
"cannot:${gocryptfs}/bin/gocryptfs"
"cannot:${ifuse}/bin/ifuse"
"cannot:${iwd}/bin/iwctl"
"cannot:${oath-toolkit}/bin/oathtool"
"cannot:${openssh}/bin/ssh-keygen"
"cannot:${rmlint}/bin/rmlint"
"cannot:${rsync}/bin/rsync"
"cannot:${sops}/bin/sops"
"cannot:${ssh-to-age}/bin/ssh-to-age"
"cannot:${systemd}/bin/systemctl"
];
};
};
# remove python scripts (we package them further below)
patchPhase = builtins.concatStringsSep
"\n"
(lib.mapAttrsToList (name: pkg: "rm ${pkg.pname}") py-scripts)
;
installPhase = ''
mkdir -p $out/bin
cp -R * $out/bin/
'';
};
py-scripts = {
nix-shell-scripts = {
# anything added to this attrset gets symlink-joined into `sane-scripts`
# and is made available through `sane-scripts.passthru`
backup-ls = static-nix-shell.mkBash {
pname = "sane-backup-ls";
src = ./src;
@@ -134,25 +38,178 @@ let
src = ./src;
pkgs = [ "transmission" ];
};
date-math = static-nix-shell.mkPython3Bin {
pname = "sane-date-math";
deadlines = static-nix-shell.mkBash {
pname = "sane-deadlines";
src = ./src;
pkgs = [ "coreutils-full" ];
};
dev-cargo-loop = static-nix-shell.mkBash {
pname = "sane-dev-cargo-loop";
src = ./src;
pkgs = [ "inotify-tools" "ncurses" ];
};
find-dotfiles = static-nix-shell.mkBash {
pname = "sane-find-dotfiles";
src = ./src;
pkgs = [ "findutils" ];
};
git-init = static-nix-shell.mkBash {
pname = "sane-git-init";
src = ./src;
pkgs = [ "git" ];
};
ip-check = static-nix-shell.mkBash {
pname = "sane-ip-check";
src = ./src;
pkgs = [ "curl" "gnugrep" ];
};
ip-check-upnp = static-nix-shell.mkPython3Bin {
pname = "sane-ip-check-upnp";
src = ./src;
pkgs = [ "miniupnpc" ];
postInstall = ''
mkdir -p $out/bin/lib
cp -R lib/* $out/bin/lib/
'';
};
ip-port-forward = static-nix-shell.mkPython3Bin {
pname = "sane-ip-port-forward";
src = ./src;
pkgs = [ "inetutils" "miniupnpc" ];
postInstall = ''
mkdir -p $out/bin/lib
cp -R lib/* $out/bin/lib/
'';
};
ip-reconnect = static-nix-shell.mkPython3Bin {
pname = "sane-ip-reconnect";
src = ./src;
};
mount-servo = static-nix-shell.mkBash {
pname = "sane-mount-servo";
src = ./src;
pkgs = [ "coreutils-full" ];
};
mount-servo-root = static-nix-shell.mkBash {
pname = "sane-mount-servo-root";
src = ./src;
pkgs = [ "coreutils-full" ];
};
private-change-passwd = static-nix-shell.mkBash {
pname = "sane-private-change-passwd";
src = ./src;
pkgs = [ "gocryptfs" "rsync" "sane-scripts.private-unlock" ];
};
private-do = static-nix-shell.mkBash {
pname = "sane-private-do";
src = ./src;
pkgs = [ "sane-scripts.private-unlock" ];
};
private-init = static-nix-shell.mkBash {
pname = "sane-private-init";
src = ./src;
pkgs = [ "gocryptfs" ];
};
private-lock = static-nix-shell.mkBash {
pname = "sane-private-lock";
src = ./src;
};
private-unlock = static-nix-shell.mkBash {
pname = "sane-private-unlock";
src = ./src;
pkgs = [ "gocryptfs" ];
};
rcp = static-nix-shell.mkBash {
pname = "sane-rcp";
src = ./src;
pkgs = [ "rsync" ];
};
reboot = static-nix-shell.mkBash {
pname = "sane-reboot";
src = ./src;
pkgs = [ "systemd" ];
};
reclaim-boot-space = static-nix-shell.mkPython3Bin {
pname = "sane-reclaim-boot-space";
src = ./src;
};
ip-reconnect = static-nix-shell.mkPython3Bin {
pname = "sane-ip-reconnect";
reclaim-disk-space = static-nix-shell.mkBash {
pname = "sane-reclaim-disk-space";
src = ./src;
pkgs = [ "nix" "rmlint" "util-linux" ];
};
secrets-dump = static-nix-shell.mkBash {
pname = "sane-secrets-dump";
src = ./src;
pkgs = [ "gnugrep" "sops" "oath-toolkit" ];
};
secrets-unlock = static-nix-shell.mkBash {
pname = "sane-secrets-unlock";
src = ./src;
pkgs = [ "coreutils-full" "openssh" "ssh-to-age" ];
};
secrets-update-keys = static-nix-shell.mkBash {
pname = "sane-secrets-update-keys";
src = ./src;
pkgs = [ "coreutils-full" "findutils" "sops" ];
};
shutdown = static-nix-shell.mkBash {
pname = "sane-shutdown";
src = ./src;
pkgs = [ "inetutils" "systemd" ];
};
ssl-dump = static-nix-shell.mkBash {
pname = "sane-ssl-dump";
src = ./src;
pkgs = [ "openssl" ];
};
stop-all-servo = static-nix-shell.mkBash {
pname = "sane-stop-all-servo";
src = ./src;
pkgs = [ "systemd" ];
};
sudo-redirect = static-nix-shell.mkBash {
pname = "sane-sudo-redirect";
src = ./src;
pkgs = [ "coreutils-full" ];
};
sync-from-iphone = static-nix-shell.mkZsh {
pname = "sane-sync-from-iphone";
src = ./src;
pkgs = [ "coreutils-full" "ifuse" "rsync" ];
};
sync-from-servo = static-nix-shell.mkBash {
pname = "sane-sync-from-servo";
src = ./src;
pkgs = [ "rsync" "sane-scripts.mount-servo" ];
};
vpn-down = static-nix-shell.mkBash {
pname = "sane-vpn-down";
src = ./src;
pkgs = [ "coreutils-full" "gnugrep" "gnused" "sane-scripts.ip-check" "systemd" ];
};
vpn-up = static-nix-shell.mkBash {
pname = "sane-vpn-up";
src = ./src;
pkgs = [ "coreutils-full" "gnugrep" "gnused" "sane-scripts.ip-check" "systemd" ];
};
which = static-nix-shell.mkBash {
pname = "sane-which";
src = ./src;
pkgs = [ "coreutils-full" "file" ];
};
wipe-browser = static-nix-shell.mkBash {
pname = "sane-wipe-browser";
src = ./src;
};
};
in
symlinkJoin {
name = "sane-scripts";
paths = [ shell-scripts ] ++ lib.attrValues py-scripts;
paths = lib.attrValues nix-shell-scripts;
passthru = nix-shell-scripts;
meta = {
description = "collection of scripts associated with uninsane systems";
description = "collection of scripts associated with sane systems";
homepage = "https://git.uninsane.org";
platforms = lib.platforms.all;
};

View File

@@ -0,0 +1,116 @@
# based on this minimal SSDP client: <https://gist.github.com/schlamar/2428250>
import logging
import socket
import struct
import subprocess
logger = logging.getLogger(__name__)
MCAST_GRP = "239.255.255.250"
class SsdpResponse:
def __init__(self, headers: "Dict[str, str]"):
self.headers = headers
@staticmethod
def parse(msg: str) -> "Self":
headers = {}
for line in [m.strip() for m in msg.split("\r\n") if m.strip()]:
if ":" not in line: continue
sep_idx = line.find(":")
header, content = line[:sep_idx].strip(), line[sep_idx+1:].strip()
headers[header.upper()] = content
if headers:
return SsdpResponse(headers)
def is_rootdevice(self) -> bool:
return self.headers.get("NT", "").lower() == "upnp:rootdevice"
def location(self) -> str:
return self.headers.get("LOCATION")
def get_root_devices():
listener = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
listener.bind(("", 1900))
logger.info("bound")
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
listener.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
root_descs = set()
while True:
packet, (host, src_port) = listener.recvfrom(2048)
logger.info(f"message from {host}")
# if host.endswith(".1"): # router
try:
msg = packet.decode("utf-8")
except:
logger.debug("failed to decode packet to string")
else:
logger.debug(msg)
resp = SsdpResponse.parse(msg)
if resp and resp.is_rootdevice():
root_desc = resp.location()
if root_desc and root_desc not in root_descs:
root_descs.add(root_desc)
logger.info(f"root desc: {root_desc}")
yield root_desc
def get_ips_from_location(location: str):
"""
location = URI from the Location header, e.g. http://10.78.79.1:2189/rootDesc.xml
returns (lan, wan)
"""
# get connection [s]tatus
cmd = ["upnpc", "-u", location, "-s"]
res = subprocess.run(cmd, capture_output=True)
if res.returncode != 0:
logger.info(f"get_wan_from_location failed: {cmd!r}\n{res.stderr}")
return None
status = res.stdout.decode("utf-8")
logger.debug(f"got status: {status}")
lan = None
wan = None
for line in [l.strip() for l in status.split("\n")]:
wan_sentinel = "ExternalIPAddress ="
lan_sentinel = "Local LAN ip address :"
if line.startswith(wan_sentinel):
wan = line[len(wan_sentinel):].strip()
logger.info(f"got WAN = {wan} from {location}")
if line.startswith(lan_sentinel):
lan = line[len(lan_sentinel):].strip()
logger.info(f"got LAN = {lan} from {location}")
return lan, wan
def get_any_wan():
""" return (location, LAN IP, WAN IP) for the first device seen which has a WAN IP """
for location in get_root_devices():
lan, wan = get_ips_from_location(location)
if lan and wan:
return location, lan, wan
def forward_port(root_device: str, proto: str, port: int, lan_ip: str, reason: str = "", duration: int = 86400):
args = [
"upnpc",
"-u", root_device,
"-e", reason,
"-a", lan_ip,
str(port),
str(port),
proto,
str(duration),
]
logger.debug(f"running: {args!r}")
stdout = subprocess.check_output(args).decode("utf-8")
logger.info(stdout)

View File

@@ -1,348 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])"
# i just went overboard playing around with parsers, is all.
# use this like `./sane-date-math 'today - 5d'`
# of course, it handles parentheses and operator precedence/associativity, so you can do sillier things like
# `./sane-date-math ' today - (1+3 *4 - ((0)) ) *7d '`
import abc
from datetime import datetime, timedelta
import sys
class Token:
def __init__(self, c: str):
self.c = c
def __repr__(self) -> str:
return f"{self.c!r}"
def __str__(self) -> str:
return self.c
def __eq__(self, other: 'Token') -> bool:
return self.c == other.c
PLUS = Token('+')
MINUS = Token('-')
ASTERISK = Token('*')
SPACE = Token(' ')
OPEN_PAREN = Token('(')
CLOSE_PAREN = Token(')')
UNDERSCORE = Token('_')
DIGITS = [Token(c) for c in '0123456789']
ALPHA_LOWER = [Token(c) for c in 'abcdefghijklmnopqrstuvwxyz']
ALPHA_UPPER = [Token(t.c.upper()) for t in ALPHA_LOWER]
ALPHA = ALPHA_LOWER + ALPHA_UPPER
ALPHA_UNDER = ALPHA + [UNDERSCORE]
ALPHA_NUM_UNDER = ALPHA_UNDER + DIGITS
class ParserContext:
def feed(self, token: Token) -> 'ParserContext':
return None # can't ingest the token
def upgrade(self) -> 'ParserContext':
return None # no upgrade path
class Parser:
"""
LR parser.
keeps exactly one root item, and for each input token
feeds it to the root, possibly "upgrading" the root N times
before it's able to be fed.
"""
def __init__(self, root: ParserContext):
self.root = root
def feed(self, token: Token) -> bool:
new_root = self.root.feed(token)
if new_root is not None:
self.root = new_root
return True
else:
# root can't directly accept this item.
# "upgrade" it and try again.
new_root = self.root.upgrade()
if new_root is None: return False
self.root = new_root
return self.feed(token)
def complete(self) -> ParserContext:
# upgrade the root as far as possible before returning
root = None
new_root = self.root
while new_root is not None:
root = new_root
new_root = root.upgrade()
return root
class ReprParserContext(ParserContext):
""" helper that gives a good default repr to most contexts """
def __init__(self, items: list = None):
self.items = items if items is not None else []
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.items!r})'
class BaseContext(ReprParserContext):
""" empty context; initial state of the parser """
def feed(self, token: Token) -> ParserContext:
if token == SPACE:
return self
if token == OPEN_PAREN:
return ParenContext(BaseContext())
if token in DIGITS:
return IntegerContext([token])
if token in ALPHA_UNDER:
return IdentifierContext([token])
class IdentifierContext(ReprParserContext):
""" context is an identifier like `today` """
def __init__(self, tokens: list):
super().__init__(tokens)
self.tokens = tokens
def feed(self, token: Token) -> ParserContext:
if token in ALPHA_NUM_UNDER:
return IdentifierContext(self.tokens + [token])
def upgrade(self) -> ParserContext:
return StrongValueContext(self)
class IntegerContext(ReprParserContext):
""" context is an integer like `45` """
def __init__(self, tokens: list):
super().__init__(tokens)
self.tokens = tokens
def feed(self, token: Token) -> ParserContext:
if token in DIGITS:
return IntegerContext(self.tokens + [token])
if token == Token('d'):
return DurationContext(self)
def upgrade(self) -> ParserContext:
# can't continue the integer; it becomes a value
return StrongValueContext(self)
class DurationContext(ReprParserContext):
""" context is a duration like `14d` """
def __init__(self, value: IntegerContext):
super().__init__([value])
self.value = value
def upgrade(self) -> ParserContext:
return StrongValueContext(self)
class BaseValueContext(ReprParserContext):
""" abstract base for types that can be used in compound expressions """
def __init__(self, value: ParserContext):
super().__init__([value])
self.value = value
def feed(self, token: Token) -> ParserContext:
if token == SPACE:
return self
class StrongValueContext(BaseValueContext):
"""
in the context of operators, a strong value is something which prefers
to not be grabbed by a lhs value.
so for example, strong values have the opportunity to initiate a multiply operation before the lhs closes an addition operation that this strong value is a part of
"""
def feed(self, token: Token) -> ParserContext:
if token == ASTERISK:
return BinaryOpContext(self, token, BaseContext())
return super().feed(token)
def upgrade(self) -> ParserContext:
return WeakValueContext(self.value)
class WeakValueContext(BaseValueContext):
def feed(self, token: Token) -> ParserContext:
if token == PLUS:
return BinaryOpContext(self, token, BaseContext())
if token == MINUS:
return BinaryOpContext(self, token, BaseContext())
return super().feed(token)
class BinaryOpContext(ReprParserContext):
""" context for a binary operation. the LHS and operator are parsed, but the rhs may not yet contain a value """
def __init__(self, lhs: BaseValueContext, oper: Token, rhs: ParserContext):
super().__init__([lhs, oper, rhs])
self.lhs = lhs
self.oper = oper
self.rhs = rhs
@property
def precedence_class(self) -> type:
if self.oper in [PLUS, MINUS]:
return WeakValueContext
if self.oper == ASTERISK:
return StrongValueContext
def feed(self, token: Token) -> ParserContext:
new_rhs = self.rhs.feed(token)
if new_rhs is not None:
return BinaryOpContext(self.lhs, self.oper, new_rhs)
def upgrade(self) -> ParserContext:
new_rhs = self.rhs.upgrade()
if new_rhs is None: return None
# upgrade self once the rhs has reach the required precedence compatible with this operator
new_self = BinaryOpContext(self.lhs, self.oper, new_rhs)
if isinstance(new_rhs, self.precedence_class):
return StrongValueContext(self) # close the operation
return new_self
class ParenContext(ReprParserContext):
""" context for a value contained within parentheses """
def __init__(self, inner: ParserContext):
super().__init__([inner])
self.inner = inner
def feed(self, token: Token) -> ParserContext:
new_inner = self.inner.feed(token)
if new_inner is not None:
return ParenContext(new_inner)
if token == CLOSE_PAREN and isinstance(self.inner, WeakValueContext):
return StrongValueContext(self)
def upgrade(self) -> ParserContext:
new_inner = self.inner.upgrade()
if new_inner is not None:
return ParenContext(new_inner)
## AstItems are produced from a ParserContext input
## ParserContext parse outputs are translated into `AstItem`s before evaluation
## so that we can operate on a higher-level tree that directly encodes native values like integers
class AstItem(metaclass=abc.ABCMeta):
@abc.abstractmethod
def eval(self, context: dict):
pass
@staticmethod
def decode_item(p: ParserContext) -> 'AstItem':
if isinstance(p, IntegerContext):
return Literal(AstItem.decode_integer(p))
if isinstance(p, DurationContext):
return Literal(timedelta(AstItem.decode_integer(p.value)))
if isinstance(p, IdentifierContext):
return Variable(AstItem.decode_identifier(p))
if isinstance(p, BaseValueContext):
return AstItem.decode_item(p.value)
if isinstance(p, BinaryOpContext):
return AstItem.decode_bin_op(
p.oper.c,
AstItem.decode_item(p.lhs),
AstItem.decode_item(p.rhs)
)
if isinstance(p, ParenContext):
return AstItem.decode_item(p.inner)
@staticmethod
def decode_integer(p: IntegerContext) -> int:
return int(''.join(t.c for t in p.tokens))
@staticmethod
def decode_identifier(p: IdentifierContext) -> str:
return ''.join(t.c for t in p.tokens)
@staticmethod
def decode_bin_op(ty: str, lhs: 'AstItem', rhs: 'AstItem') -> 'BinaryOp':
if ty == '+':
return AddOp(lhs, rhs)
if ty == '-':
return SubOp(lhs, rhs)
if ty == '*':
return MulOp(lhs, rhs)
class Literal(AstItem):
def __init__(self, v):
self.v = v
def __str__(self) -> str:
return str(self.v)
def eval(self, context: dict):
return self.v
class Variable(AstItem):
def __init__(self, name: str):
self.name = name
def __str__(self) -> str:
return self.name
def eval(self, context: dict):
return context[self.name]
class BinaryOp(AstItem):
def __init__(self, lhs, rhs):
self.lhs = lhs
self.rhs = rhs
class AddOp(BinaryOp):
def __str__(self):
return f"({self.lhs} + {self.rhs})"
def eval(self, context: dict):
return self.lhs.eval(context) + self.rhs.eval(context)
class SubOp(BinaryOp):
def __str__(self):
return f"({self.lhs} - {self.rhs})"
def eval(self, context: dict):
return self.lhs.eval(context) - self.rhs.eval(context)
class MulOp(BinaryOp):
def __str__(self):
return f"({self.lhs} * {self.rhs})"
def eval(self, context: dict):
return self.lhs.eval(context) * self.rhs.eval(context)
## toplevel routine. tokenize -> parse -> decode to AST -> evaluate
def tokenize(stream: str) -> list:
return [Token(char) for char in stream]
def parse(tokens: list) -> ParserContext:
parser = Parser(BaseContext())
for i, t in enumerate(tokens):
result = parser.feed(t)
# print(f"i={i}; t={t}; state: {ctx!r}")
assert result, f"unexpected token '{t}' at {i}; state: {parser.complete()!r}"
return parser.complete()
def evaluate(expr: str) -> object:
tok = tokenize(expr)
parse_tree = parse(tok)
print(parse_tree)
ast = AstItem.decode_item(parse_tree)
print(ast)
env = dict(
today=datetime.now()
)
return ast.eval(env)
if __name__ == '__main__':
expr = " ".join(sys.argv[1:])
print(evaluate(expr))

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils-full
# processes a tab-separated "deadlines" file and alerts for any upcoming events.
#

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p inotify-tools -p ncurses
# watches PWD for any changes underneath it and re-runs `cargo build --a>
# optionally, provide your own build command as the first argument

View File

@@ -1,9 +1,11 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p findutils
# find where a package stores its dotfiles/dotdir
# e.g. `sane-find-dotfiles foo` might print `/home/colin/.foo`, `/home/colin/.local/share/foo`, etc.
find ~/ -maxdepth 1 -iname "*$1*" -print
find ~/.local/share/*/ -maxdepth 1 -iname "*$1*" -print
find ~/.local/state/*/ -maxdepth 1 -iname "*$1*" -print
find ~/.config/*/ -maxdepth 1 -iname "*$1*" -print
find ~/.cache/*/ -maxdepth 1 -iname "*$1*" -print

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p git
set -x

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p curl -p gnugrep
ip=$(curl --silent https://ipinfo.io/ip)
echo "$ip" | grep -P " *^\d+\.\d+\.\d+\.\d+ *$"
exit $?

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env bash
# query the WAN IP address OF MY ROUTER
# requires creds
passwd=$(sudo cat /run/secrets/router_passwd)
cookie=$(mktemp)
curlflags="curl --silent --insecure --cookie-jar $cookie --connect-timeout 5"
# authenticate
curl $curlflags \
--data "username=admin&password=$passwd" \
https://192.168.0.1
# query the WAN IP
ip=$(curl $curlflags \
-H "X-Requested-With: XMLHttpRequest" \
"https://192.168.0.1/cgi/cgi_action?Action=GetConnectionStatus" \
| jq -r .wan_status.ipaddr)
echo "$ip" | grep -P " *^\d+\.\d+\.\d+\.\d+ *$"
exit $?

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p miniupnpc
# best to run this with an external timeout. e.g.
# - `timeout 60 sane-ip-check-upnp`
import logging
import os
import sys
d = os.path.dirname(__file__)
sys.path.insert(0, d)
from lib.sane_ssdp import get_any_wan
if __name__ == '__main__':
logging.basicConfig()
for arg in sys.argv[1:]:
if arg == "-v":
logging.getLogger().setLevel(logging.INFO)
elif arg == "-vv":
logging.getLogger().setLevel(logging.DEBUG)
else:
raise RuntimeError(f"invalid CLI argument {arg!r}")
_rootdev, _lan_ip, wan_ip = get_any_wan()
print(wan_ip)

View File

@@ -0,0 +1,106 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p inetutils -p miniupnpc
'''
USAGE: sane-ip-port-forward [options] [proto:port[:desc]]*
options:
-v: verbose (show info messages)
-vv: more verbose (show debug messages)
-h: show this help messages
-d <int>: lease for the given duration in seconds (default: {DEFAULT_LEASE_SEC})
proto:port[:desc]:
proto is `udp` or `tcp` (case insensitive)
port is any integer 1-65535 inclusive
desc is some public description of the port forward (for humans trying to understand a network config)
'''
DEFAULT_LEASE_SEC = 86400
import logging
import subprocess
import sys
sys.path.insert(0, ".")
from lib.sane_ssdp import get_any_wan, forward_port
class BadCliArgs(Exception):
def __init__(self, msg: str = None):
helpstr = __doc__.format(DEFAULT_LEASE_SEC=DEFAULT_LEASE_SEC).strip()
if msg:
super().__init__(f"{msg}\n\n{helpstr}")
else:
super().__init__(helpstr)
def try_parse_port(s: str):
"""
`udp:53` -> ["udp", 53, ""]
`tcp:65535:my description` -> ["tcp", 65535, "my description"]
"""
s = s.strip()
def split_at_colon(s_: str):
next_colon = s_.find(":")
return s_[:next_colon], s_[next_colon+1:]
if ":" not in s: return # invalid format
proto, rest = split_at_colon(s)
if ":" in rest:
portstr, desc = split_at_colon(rest)
else:
portstr = rest
desc = ""
try:
proto, port = proto.lower(), int(portstr)
assert proto in ["tcp", "udp"]
assert 0 < port < 65536
return proto, port, desc
except Exception:
pass
def parse_args(argv: "List[str]") -> "List[('udp'|'tcp', port: int, description: str)]":
"""
returns (list of forwards, lease duration)
where:
- list of forwards is [('udp'|'tcp', port: int, description: str)]
- lease duration is seconds: int
"""
forwards = []
duration = DEFAULT_LEASE_SEC
unparsed = sys.argv[1:][::-1]
while unparsed:
arg = unparsed.pop()
if arg == "-h":
raise BadCliArgs()
if arg == "-v":
logging.getLogger().setLevel(logging.INFO)
elif arg == "-vv":
logging.getLogger().setLevel(logging.DEBUG)
elif arg == "-d" and unparsed:
d = unparsed.pop()
try:
duration = int(d)
except Exception:
raise BadCliArgs(f"invalid CLI argument: -d {d!r}")
elif try_parse_port(arg):
forwards.append(try_parse_port(arg))
else:
raise BadCliArgs(f"invalid CLI argument: {arg!r}")
return forwards, duration
if __name__ == '__main__':
logging.basicConfig()
try:
forwards, duration = parse_args(sys.argv)
except BadCliArgs as e:
print(e)
sys.exit(1)
root_device, lan, _wan = get_any_wan()
for (proto, port, reason) in forwards:
forward_port(root_device, proto, port, lan_ip=lan, reason=reason, duration=duration)

View File

@@ -1,4 +1,7 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils-full
#^ requires `sudo` and `mount` to be installed system-wide (suid)
set -ex
# if lan not mounted, then try to mount it

View File

@@ -1,4 +1,7 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils-full
#^ requires `sudo` and `mount` to be installed system-wide (suid)
#
set -ex
# if lan not mounted, then try to mount it

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p gocryptfs -p rsync -p sane-scripts.private-unlock
set -ex

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p sane-scripts.private-unlock
# unlock the ~/private store, run some command, and then re-lock the store

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p gocryptfs
set -ex

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash
sudo umount /home/colin/private

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p gocryptfs
set -ex

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env sh
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p rsync
# rsync, with sane defaults
# + verbosity
# + default to cwd as destination if none is provided

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p systemd
target="$1"
host="$(hostname)"

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p nix -p rmlint -p util-linux
# script to reclaim some hard drive space
# some of this is documented here:
# - <https://nixos.wiki/wiki/Storage_optimization>

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p gnugrep -p oath-toolkit -p sops
# use: `sane-dump-secret /path/to/accounts/website.yaml`
# dumps relevant information about the account, include a OTP code if present
secrets=$(sops -d --output-type dotenv $1)

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils-full -p openssh -p ssh-to-age
# unlocks the SOPS store (i.e. populate a SOPS key from the user's SSH key)
set -ex

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils-full -p findutils -p sops
# after modifying .sops.yaml, run this to re-encode all secrets to the new keys
# pass the base directory (under which *everything* is a secret) as argument
for i in $(find "$1" -print)

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p inetutils -p systemd
target="$1"
host="$(hostname)"

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p openssl
# dump info about the provided SSL certificate
cert="$1"

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p systemd
sudo systemctl stop matrix-appservice-irc mx-puppet-discord
sudo systemctl stop pleroma gitea matrix-synapse jellyfin transmission jackett
sudo systemctl stop ejabberd goaccess i2p kiwix-serve navidrome

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils-full
# redirects to $1, when writing to $1 requires sudo permissions.
# i.e. convert a failing command:

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env zsh
#!/usr/bin/env nix-shell
#!nix-shell -i zsh -p coreutils-full -p ifuse -p rsync
# this really does need zsh because bash `test -e` behaves differently
set -ex

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p rsync -p sane-scripts.mount-servo
set -ex
sane-mount-servo

View File

@@ -1,2 +0,0 @@
#!/usr/bin/env bash
echo success

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils-full -p gnugrep -p gnused -p sane-scripts.ip-check -p systemd
# first arg should be the region, e.g. `us` or `ukr`

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils-full -p gnugrep -p gnused -p sane-scripts.ip-check -p systemd
# first arg should be the region, e.g. `us` or `ukr`

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils-full -p file
# traces a PATH lookup by printing the source, resolution, and any symlinks traversed
# finally, prints the content of the file
echo $1

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash
# remove firefox/librewolf/chromium artifacts
rm -rf \
~/.librewolf/default/* \

View File

@@ -13,9 +13,10 @@ let
# <name> = expected string in the nix-shell invocation
# <value> = package to provide
pkgsToAttrs = prefix: pkgSet: expr: ({
# branch based on the type of `expr`
"lambda" = expr: pkgsToAttrs prefix pkgSet (expr pkgSet);
"list" = expr: foldl' (acc: pname: acc // {
"${prefix + pname}" = pkgSet."${pname}";
"${prefix + pname}" = lib.getAttrFromPath (lib.splitString "." pname) pkgSet;
}) {} expr;
"set" = expr: expr;
})."${typeOf expr}" expr;
@@ -53,6 +54,7 @@ in rec {
'';
nativeBuildInputs = [ makeWrapper ];
installPhase = ''
runHook preInstall
mkdir -p $out/bin
mv ${srcPath} $out/bin/${srcPath}
@@ -62,6 +64,8 @@ in rec {
# add runtime dependencies to PATH
wrapProgram $out/bin/${srcPath} \
--suffix PATH : ${lib.makeBinPath pkgsEnv }
runHook postInstall
'';
} // (removeAttrs attrs [ "interpreter" "interpreterName" "pkgsEnv" "pkgExprs" "srcPath" ])
);
@@ -78,6 +82,18 @@ in rec {
} // (removeAttrs attrs [ "pkgs" ])
);
# `mkShell` specialization for `nix-shell -i zsh` scripts.
mkZsh = { pname, pkgs ? {}, srcPath ? pname, ...}@attrs:
let
pkgsAsAttrs = pkgsToAttrs "" pkgs' pkgs;
pkgsEnv = attrValues pkgsAsAttrs;
pkgExprs = attrNames pkgsAsAttrs;
in mkShell ({
inherit pkgsEnv pkgExprs;
interpreter = "${pkgs'.zsh}/bin/zsh";
} // (removeAttrs attrs [ "pkgs" ])
);
# `mkShell` specialization for invocations of `nix-shell -p "python3.withPackages (...)"`
# pyPkgs argument is parsed the same as pkgs, except that names are assumed to be relative to `"ps"` if specified in list form.
mkPython3Bin = { pname, pkgs ? {}, pyPkgs ? {}, srcPath ? pname, ... }@attrs:

View File

@@ -0,0 +1,24 @@
diff --git a/setup_config_version.sh b/setup_config_version.sh
index fde40c1..6386014 100755
--- a/setup_config_version.sh
+++ b/setup_config_version.sh
@@ -8,7 +8,7 @@ case "$1" in
exit
esac
-case "$(busybox head -n1 "$1")" in
+case "$(head -n1 "$1")" in
"#"*)
comment="#"
;;
@@ -23,7 +23,7 @@ case "$(busybox head -n1 "$1")" in
;;
esac
-busybox md5sum "$1" | \
- busybox cut -d" " -f1 | \
- busybox xargs -I{} busybox sed -i "2i$comment configversion: {}" \
+md5sum "$1" | \
+ cut -d" " -f1 | \
+ xargs -I{} sed -i "2i$comment configversion: {}" \
"$1"

View File

@@ -0,0 +1,54 @@
#!/bin/sh
# include common definitions
# shellcheck source=scripts/core/sxmo_common.sh
. sxmo_common.sh
# Create xdg user directories, such as ~/Pictures
xdg-user-dirs-update
sxmo_daemons.sh start daemon_manager superd -v
# let time to superd to start correctly
while ! superctl status > /dev/null 2>&1; do
sleep 0.5
done
# Periodically update some status bar components
sxmo_hook_statusbar.sh all
sxmo_daemons.sh start statusbar_periodics sxmo_run_aligned.sh 60 \
sxmo_hook_statusbar.sh periodics
# mako/dunst are required for warnings.
# load some other little things here too.
superctl start mako
superctl start sxmo_wob
superctl start sxmo_menumode_toggler
superctl start bonsaid
swaymsg output '*' bg "$SXMO_BG_IMG" fill
# To setup initial lock state
sxmo_hook_unlock.sh
# Turn on auto-suspend
if [ -w "/sys/power/wakeup_count" ] && [ -f "/sys/power/wake_lock" ]; then
superctl start sxmo_autosuspend
fi
# Turn on lisgd
superctl start sxmo_hook_lisgd
# Start the desktop widget (e.g. clock)
superctl start sxmo_conky
# Monitor the battery
superctl start sxmo_battery_monitor
# It watch network changes and update the status bar icon by example
superctl start sxmo_networkmonitor
# The daemon that display notifications popup messages
superctl start sxmo_notificationmonitor
# monitor for headphone for statusbar
superctl start sxmo_soundmonitor

View File

@@ -2,6 +2,7 @@
, fetchgit
, gitUpdater
, lib
, rsync
}:
stdenv.mkDerivation rec {
@@ -19,9 +20,10 @@ stdenv.mkDerivation rec {
./0001-group-differs-from-user.patch
./0002-ensure-log-dir.patch
./0003-fix-xkb-paths.patch
./0004-no-busybox.patch
# personal preferences:
./0004-full-auto-rotate.patch
./0104-full-auto-rotate.patch
];
postPatch = ''
@@ -30,8 +32,11 @@ stdenv.mkDerivation rec {
sed -i "s@/usr/bin/@@g" scripts/core/sxmo_version.sh
sed -i 's:ExecStart=/usr/bin/:ExecStart=/usr/bin/env :' configs/superd/services/*.service
# on devices where volume is part of the primary keyboard, we want to avoid overwriting the default map
cp ${./en_us_105.xkb} configs/xkb/xkb_mobile_normal_buttons
# apply customizations
# - xkb_mobile_normal_buttons:
# - on devices where volume is part of the primary keyboard (e.g. thinkpad), we want to avoid overwriting the default map
# - this provided map is the en_US 105 key map
${rsync}/bin/rsync -rlv ${./customization}/ ./
'';
installFlags = [

View File

@@ -19,11 +19,13 @@ let
sane-lib = import ../modules/lib final';
### ADDITIONAL PACKAGES
alsa-ucm-conf-sane = callPackage ./additional/alsa-ucm-conf-sane { };
bonsai = unpatched.bonsai or (callPackage ./additional/bonsai { });
bootpart-uefi-x86_64 = callPackage ./additional/bootpart-uefi-x86_64 { };
browserpass-extension = callPackage ./additional/browserpass-extension { };
cargoDocsetHook = callPackage ./additional/cargo-docset/hook.nix { };
feeds = lib.recurseIntoAttrs (callPackage ./additional/feeds { });
jellyfin-media-player-qt6 = callPackage ./additional/jellyfin-media-player-qt6 { };
gopass-native-messaging-host = callPackage ./additional/gopass-native-messaging-host { };
gpodder-configured = callPackage ./additional/gpodder-configured { };
hare-ev = unpatched.hare-ev or (callPackage ./additional/hare-ev { });
@@ -63,6 +65,13 @@ let
# - pkgs.callPackage draws from the _final_ package set.
# - unpatched.XYZ draws (selectively) from the _unpatched_ package set.
# see <overlays/pkgs.nix>
# XXX patching this is... really costly.
# prefer to set ALSA_CONFIG_UCM2 = "${pkgs.alsa-ucm-conf-sane}/share/alsa/ucm2" if possible instead.
# alsa-project = unpatched.alsa-project.overrideScope' (sself: ssuper: {
# alsa-ucm-conf = sself.callPackage ./additional/alsa-ucm-conf-sane { inherit (ssuper) alsa-ucm-conf; };
# });
browserpass = callPackage ./patched/browserpass { inherit (unpatched) browserpass; };
# mozilla keeps nerfing itself and removing configuration options
@@ -80,6 +89,7 @@ let
jackett = callPackage ./patched/jackett { inherit (unpatched) jackett; };
lemmy-server = callPackage ./patched/lemmy-server { inherit (unpatched) lemmy-server; };
lemmy-ui = callPackage ./patched/lemmy-ui { inherit (unpatched) lemmy-ui; };
phoc = callPackage ./patched/phoc { inherit (unpatched) phoc; };

View File

@@ -16,7 +16,7 @@ let
src = ./.;
inherit bash gnused sops;
sane_scripts = sane-scripts;
sane_secrets_unlock = sane-scripts.secrets-unlock; # XXX: must be snake_case
installPhase = ''
mkdir -p $out/bin
substituteAll ${./sops-gpg-adapter} $out/bin/gpg
@@ -45,4 +45,8 @@ in
mkdir -p $out/lib/mozilla/native-messaging-hosts
ln -s $out/lib/browserpass/hosts/firefox/*.json $out/lib/mozilla/native-messaging-hosts
'';
passthru = (upstream.passthru or {}) // {
inherit sane-browserpass-gpg;
};
})

View File

@@ -8,7 +8,7 @@ then
fi
# ensure the secret store is unlocked
@sane_scripts@/bin/sane-secrets-unlock
@sane_secrets_unlock@/bin/sane-secrets-unlock
# using exec here forwards our stdin
# browserpass parses the response in

View File

@@ -0,0 +1,5 @@
{ lemmy-ui, nodejs }:
lemmy-ui.override {
# build w/ latest nodejs; not 14.x
inherit nodejs;
}