aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--nixos/.gitignore1
-rw-r--r--nixos/README.md3
-rw-r--r--nixos/helpers.nix43
-rw-r--r--nixos/hosts/ev/default.nix48
-rw-r--r--nixos/hosts/ev/exporters.nix42
-rw-r--r--nixos/hosts/ev/hosehawk.nix28
-rw-r--r--nixos/hosts/ev/kodi.nix5
-rw-r--r--nixos/hosts/ev/metadata.toml1
-rw-r--r--nixos/hosts/ev/miniflux.nix27
-rw-r--r--nixos/hosts/ev/ports.nix22
-rw-r--r--nixos/hosts/ev/torrent.nix2
-rw-r--r--nixos/hosts/hamac/default.nix7
-rw-r--r--nixos/hosts/hamac/metadata.toml1
-rw-r--r--nixos/hosts/tente/default.nix89
-rw-r--r--nixos/hosts/tente/exporters.nix42
-rw-r--r--nixos/hosts/tente/git.nix15
-rw-r--r--nixos/hosts/tente/gotify.nix21
-rw-r--r--nixos/hosts/tente/grafana.nix75
-rw-r--r--nixos/hosts/tente/matrix.nix41
-rw-r--r--nixos/hosts/tente/metadata.toml1
-rw-r--r--nixos/hosts/tente/monitoring.nix317
-rw-r--r--nixos/hosts/tente/ports.nix19
-rw-r--r--nixos/hosts/tente/web-personal.nix2
-rw-r--r--nixos/npins/default.nix88
-rw-r--r--nixos/npins/sources.json102
-rw-r--r--nixos/profiles/common/basics.nix8
-rw-r--r--nixos/profiles/common/default.nix2
-rw-r--r--nixos/profiles/common/nixpkgs/overlays.nix25
-rw-r--r--nixos/profiles/common/sanix.nix3
-rw-r--r--nixos/profiles/server/default.nix5
-rw-r--r--nixos/profiles/workstation/create.nix6
-rw-r--r--nixos/profiles/workstation/default.nix18
-rw-r--r--nixos/profiles/workstation/dev.nix16
-rw-r--r--nixos/profiles/workstation/graphical.nix5
-rw-r--r--nixos/profiles/workstation/scripts/ed2
-rwxr-xr-xnixos/rebuild17
-rw-r--r--nixos/secrets/grafana-matrix-forwarder-env.agebin0 -> 449 bytes
-rw-r--r--nixos/secrets/miniflux-admin.age9
-rw-r--r--nixos/secrets/secrets.nix6
-rw-r--r--nixos/secrets/vpn-se-presharedKey.age14
-rw-r--r--nixos/secrets/vpn-se-privKey.age21
-rw-r--r--nixos/shared/alloy-nix-config/alloy_nix_config.go129
-rw-r--r--nixos/shared/alloy-nix-config/default.nix8
-rw-r--r--nixos/shared/alloy-nix-config/go.mod3
-rw-r--r--nixos/shared/grafana-matrix-forwarder/default.nix10
-rw-r--r--nixos/shared/grafana-matrix-forwarder/service.nix31
-rw-r--r--nixos/shared/monitoring.nix235
-rw-r--r--nixos/shared/postgres-collectors.nix22
-rw-r--r--nixos/shared/prometheus-sql-exporter/default.nix20
-rw-r--r--nixos/shared/prometheus-sql-exporter/service.nix13
-rw-r--r--nixos/shared/vpn.nix8
-rw-r--r--user/inkscape/pages.csv47
-rw-r--r--user/jj/config.toml8
-rw-r--r--user/sway/config11
-rw-r--r--user/vim/vimrc19
-rw-r--r--user/zed/settings.json22
-rw-r--r--user/zsh/.zshenv1
-rw-r--r--user/zsh/.zshrc1
-rw-r--r--user/zsh/zshrc-workstation.sh2
60 files changed, 1272 insertions, 521 deletions
diff --git a/README.md b/README.md
index 74f8cc5..fff89ab 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,10 @@
This repository contains my personal config files.
* `nixos/` -- contains my [NixOS](https://nixos.org/) configurations
+ * hosts
+ * ev -- my home server
+ * hamac -- my laptop
+ * tente -- my VPS
* `user/` -- contains my dotfiles
The idea behind this structure is that dotfiles like my
diff --git a/nixos/.gitignore b/nixos/.gitignore
new file mode 100644
index 0000000..c4a847d
--- /dev/null
+++ b/nixos/.gitignore
@@ -0,0 +1 @@
+/result
diff --git a/nixos/README.md b/nixos/README.md
index 67b6cf7..075f812 100644
--- a/nixos/README.md
+++ b/nixos/README.md
@@ -32,7 +32,8 @@ We're assuming that you just installed NixOS by going through the [official inst
nixos-generate-config --dir .
```
3. Rename the `configuration.nix` to `default.nix`.
- Add `# channel="..."` to the start of the `default.nix` file where `...` is the key of a channel pinned in `npins/sources.json`.
+ Create a `metadata.toml` file with a `channel` field
+ where the value is the key of a channel pinned in `npins/sources.json`.
A new channel can be pinned with:
```
diff --git a/nixos/helpers.nix b/nixos/helpers.nix
index c6d1ff8..a236c42 100644
--- a/nixos/helpers.nix
+++ b/nixos/helpers.nix
@@ -1,8 +1,5 @@
-{ config, ... }:
+{ config, lib, pkgs, ... }:
-let
- nixpkgs = import <nixpkgs> {};
-in
{
mkNginxConfig = name: ''
access_log /var/log/nginx/${name}.access.log json;
@@ -63,8 +60,32 @@ in
'}';
'';
+ serviceIndexHost = tailnetHostname: webPorts:
+ let
+ li = name: port: ''<li><a href="http://${tailnetHostname}:${toString port}">${name}</a></li>'';
+ html = "<ul>" + (lib.concatStringsSep "\n" (lib.mapAttrsToList li webPorts)) + "</ul>";
+ in
+ {
+ listenAddresses = [tailnetHostname];
+ locations."= /".extraConfig = ''
+ default_type text/html;
+ return 200 '${html}';
+ '';
+ };
+
+ writeAlloyConfig = cfg:
+ let
+ alloy-nix-config = "${pkgs.callPackage <top/shared/alloy-nix-config> {}}/bin/alloy-nix-config";
+ in
+ pkgs.runCommand "generated-config" {} ''
+ ${alloy-nix-config} ${pkgs.writeText "input.json" (builtins.toJSON cfg)} $out
+ # FUTURE: run alloy validate
+ '';
+
+ alloyConfigRef = s: { "$ref" = s; };
+
joinWgNamespace = ns: cfg:
- nixpkgs.lib.attrsets.recursiveUpdate cfg {
+ lib.attrsets.recursiveUpdate cfg {
bindsTo = ["netns@${ns}.service"];
after = ["wireguard-wg-${ns}.service"];
unitConfig.JoinsNamespaceOf = "netns@${ns}.service";
@@ -81,13 +102,9 @@ in
TimeoutStopSec = 300;
};
wantedBy = ["multi-user.target"];
- script =
- let
- pkgs = nixpkgs.pkgs;
- in
- ''
- ${pkgs.iproute2}/bin/ip netns exec ${ns} ${pkgs.iproute2}/bin/ip link set dev lo up
- ${pkgs.socat}/bin/socat tcp-listen:${toString port},fork,reuseaddr exec:'${pkgs.iproute2}/bin/ip netns exec ${ns} ${pkgs.socat}/bin/socat STDIO "tcp-connect:localhost:${toString port}"',nofork
- '';
+ script = ''
+ ${pkgs.iproute2}/bin/ip netns exec ${ns} ${pkgs.iproute2}/bin/ip link set dev lo up
+ ${pkgs.socat}/bin/socat tcp-listen:${toString port},fork,reuseaddr exec:'${pkgs.iproute2}/bin/ip netns exec ${ns} ${pkgs.socat}/bin/socat STDIO "tcp-connect:localhost:${toString port}"',nofork
+ '';
};
}
diff --git a/nixos/hosts/ev/default.nix b/nixos/hosts/ev/default.nix
index e59ec01..fbda40f 100644
--- a/nixos/hosts/ev/default.nix
+++ b/nixos/hosts/ev/default.nix
@@ -1,25 +1,60 @@
-# channel="nixos-small"
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running ‘nixos-help’).
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
+let
+ ports = import ./ports.nix;
+ helpers = import <top/helpers.nix> { inherit config lib pkgs; };
+in
{
imports = [
./hardware-configuration.nix
<top/profiles/server>
+ <top/shared/monitoring.nix>
+ <top/shared/postgresql.nix>
<top/shared/tailscale.nix>
<top/shared/basics-physical.nix>
<top/shared/vpn.nix>
+ ./exporters.nix
./home-automation.nix
./kodi.nix
+ ./miniflux.nix
./torrent.nix
+ ./hosehawk.nix
];
- home-automation.zigbee2mqttPort = 8080;
- torrent.qbittorrentWebUiPort = 7777;
+ # enable unlocking full disk encryption via SSH
+ boot.kernelParams = ["ip=dhcp"];
+ boot.initrd = {
+ availableKernelModules = ["r8169"]; # for Ethernet
+ network = {
+ enable = true;
+ ssh = {
+ enable = true;
+ port = 2222;
+ hostKeys = ["/etc/secrets/initrd/ssh_host_ed25519_key"];
+ authorizedKeys = [
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDo/Y7w3hQgUIOQi63e8+L7eTMsVWl1vqY+Bd4tvwShdAj8ECU6JnD6gkCVzqXfUNdpA0Csd9PZlGAbXU+0kxudryFV6mxbXvYf+z70vcF02L5lDJ1tzCV7t7SwXnoenSNBIra/M2zDFgGM4oUkl9iZ2wxn/X/mvFzopJsM3xe2YNtJhXzCyaQTakKRDdHMyj9E867Ko03H6ZD2PI+9G+S39tk5ZLIcG9qhLTfDPziiZj7AIeTYVoxQycajwSlvp8BLzxxCKH8Mq7qW86jfT4lYvUuL5ItQ1cdFbmvJNKpgGXBzgBU+6kWf5c7P2aajhE3otgpfBXWBZRA3hKk+E+xX martin@hamac"
+ ];
+ shell = "/bin/cryptsetup-askpass";
+ };
+ };
+ };
+ # unsure why this is necessary
+ networking.interfaces.enp3s0.useDHCP = true;
+
+ home-automation.zigbee2mqttPort = ports.zigbee2mqtt;
+ torrent.qbittorrentWebUiPort = ports.qbittorrent;
torrent.networkNamespace = "se";
+ monitoring.alloyUiPort = ports.grafanaAlloy;
+ monitoring.lokiPort = ports.grafanaLoki;
+ monitoring.prometheusPort = ports.prometheus;
+ monitoring.prometheusNodeExporterPort = ports.prometheusNodeExporter;
+ exporters.sqlExporterPort = ports.prometheusSqlExporter;
+ hosehawk.port = ports.hosehawk;
+ miniflux.port = ports.miniflux;
home-automation.zigbeeSerialPort = "/dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_e2fed465c59ded11962fd7a5a7669f5d-if00-port0";
@@ -60,6 +95,11 @@
];
networking.firewall.allowedUDPPorts = [];
+ services.nginx = {
+ enable = true;
+ virtualHosts."ev.tailnet" = helpers.serviceIndexHost "ev.tailnet" ports.webUis;
+ };
+
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
diff --git a/nixos/hosts/ev/exporters.nix b/nixos/hosts/ev/exporters.nix
new file mode 100644
index 0000000..1c03c35
--- /dev/null
+++ b/nixos/hosts/ev/exporters.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+let
+ helpers = import <top/shared/postgres-collectors.nix> { inherit config lib pkgs; };
+ cfg = config.exporters;
+in
+{
+ options.exporters = {
+ sqlExporterPort = lib.mkOption {
+ type = lib.types.int;
+ };
+ };
+
+ imports = [
+ <top/shared/prometheus-sql-exporter/service.nix>
+ ];
+
+ config = {
+ services.prometheus-sql-exporter = {
+ enable = true;
+ port = cfg.sqlExporterPort;
+ config = {
+ target = {
+ # This URL should be postgresql:///postgres?host=/run/postgresql
+ # but sql_exporter uses xo/dburl which isn't spec-compliant: https://github.com/xo/dburl/issues/46
+ data_source_name = "postgresql:/run/postgresql:/postgres";
+ collectors = helpers.collectorNames;
+ };
+ collectors = helpers.collectors;
+ };
+ };
+
+ monitoring.prometheusScrapeConfigs = [
+ {
+ job_name = "sql";
+ static_configs = [{
+ targets = [ "localhost:${toString cfg.sqlExporterPort}" ];
+ }];
+ }
+ ];
+ };
+}
diff --git a/nixos/hosts/ev/hosehawk.nix b/nixos/hosts/ev/hosehawk.nix
new file mode 100644
index 0000000..09637cd
--- /dev/null
+++ b/nixos/hosts/ev/hosehawk.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.hosehawk;
+in
+{
+ imports = [
+ /home/martin/repos/hosehawk/services/hosenest.nix
+ /home/martin/repos/hosehawk/services/hosehawk.nix
+ ];
+
+ options.hosehawk = {
+ port = lib.mkOption {
+ type = lib.types.int;
+ };
+ };
+
+ config = {
+ services.hosenest = {
+ enable = true;
+ port = cfg.port;
+ };
+ services.hosehawk = {
+ enable = true;
+ hosenestUrl = "http://localhost:${toString cfg.port}/";
+ };
+ };
+}
diff --git a/nixos/hosts/ev/kodi.nix b/nixos/hosts/ev/kodi.nix
index 2aea216..3862dfe 100644
--- a/nixos/hosts/ev/kodi.nix
+++ b/nixos/hosts/ev/kodi.nix
@@ -7,7 +7,10 @@ let
in
{
config = {
- users.extraUsers.kodi.isNormalUser = true;
+ users.users.kodi = {
+ isNormalUser = true;
+ extraGroups = ["audio"];
+ };
services = {
cage = {
diff --git a/nixos/hosts/ev/metadata.toml b/nixos/hosts/ev/metadata.toml
new file mode 100644
index 0000000..58f0301
--- /dev/null
+++ b/nixos/hosts/ev/metadata.toml
@@ -0,0 +1 @@
+channel = "nixos-small"
diff --git a/nixos/hosts/ev/miniflux.nix b/nixos/hosts/ev/miniflux.nix
new file mode 100644
index 0000000..5bf1ccd
--- /dev/null
+++ b/nixos/hosts/ev/miniflux.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.miniflux;
+in
+{
+ options.miniflux = {
+ port = lib.mkOption {
+ type = lib.types.int;
+ };
+ };
+
+ config = {
+ age.secrets.miniflux-admin.file = <top/secrets/miniflux-admin.age>;
+
+ services.miniflux = {
+ enable = true;
+ config = {
+ LISTEN_ADDR = "0.0.0.0:${toString cfg.port}";
+ DATABASE_URL = "user=miniflux host=/run/postgresql dbname=miniflux";
+ };
+ # miniflux does not support disabling its authentication. https://github.com/miniflux/v2/issues/408
+ adminCredentialsFile = config.age.secrets.miniflux-admin.path;
+ createDatabaseLocally = false;
+ };
+ };
+}
diff --git a/nixos/hosts/ev/ports.nix b/nixos/hosts/ev/ports.nix
new file mode 100644
index 0000000..502e190
--- /dev/null
+++ b/nixos/hosts/ev/ports.nix
@@ -0,0 +1,22 @@
+rec {
+ grafanaAlloy = 3001;
+ grafanaLoki = 3030;
+ hosehawk = 4000;
+ miniflux = 4001;
+ prometheus = 9090;
+ prometheusNodeExporter = 9002;
+ prometheusSqlExporter = 9003;
+ qbittorrent = 7777;
+ zigbee2mqtt = 8080;
+
+ webUis = {
+ inherit
+ grafanaAlloy
+ hosehawk
+ miniflux
+ prometheus
+ qbittorrent
+ zigbee2mqtt
+ ;
+ };
+}
diff --git a/nixos/hosts/ev/torrent.nix b/nixos/hosts/ev/torrent.nix
index ddc4dcb..a09c507 100644
--- a/nixos/hosts/ev/torrent.nix
+++ b/nixos/hosts/ev/torrent.nix
@@ -1,7 +1,7 @@
{ config, lib, pkgs, ... }:
let
- helpers = import <top/helpers.nix> { inherit config; };
+ helpers = import <top/helpers.nix> { inherit config lib pkgs; };
cfg = config.torrent;
in
{
diff --git a/nixos/hosts/hamac/default.nix b/nixos/hosts/hamac/default.nix
index 7085ed3..523b7d6 100644
--- a/nixos/hosts/hamac/default.nix
+++ b/nixos/hosts/hamac/default.nix
@@ -1,12 +1,11 @@
-# channel="nixos"
# See the configuration.nix(5) man page and the NixOS manual (accessible by running `nixos-help`).
+{ config, lib, pkgs, ... }:
+
let
- sources = import ../../npins;
+ sources = import <top/npins>;
pkgs-unstable = import sources.nixpkgs-unstable {};
in
-{ config, lib, pkgs, ... }:
-
{
_module.args = { inherit pkgs-unstable; };
imports = [
diff --git a/nixos/hosts/hamac/metadata.toml b/nixos/hosts/hamac/metadata.toml
new file mode 100644
index 0000000..fc34e27
--- /dev/null
+++ b/nixos/hosts/hamac/metadata.toml
@@ -0,0 +1 @@
+channel = "nixos"
diff --git a/nixos/hosts/tente/default.nix b/nixos/hosts/tente/default.nix
index 8904ba7..a54de6c 100644
--- a/nixos/hosts/tente/default.nix
+++ b/nixos/hosts/tente/default.nix
@@ -1,4 +1,3 @@
-# channel="nixos-small"
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
@@ -6,46 +5,73 @@
{ config, lib, pkgs, ... }:
let
- domain = "push-f.com";
+ baseDomain = "push-f.com";
acmeEmail = "martin@push-f.com";
- sources = import ../../npins;
- helpers = import <top/helpers.nix> { inherit config; };
+ ports = import ./ports.nix;
+ sources = import <top/npins>;
+ helpers = import <top/helpers.nix> { inherit config lib pkgs; };
+ pkgs-unstable = import sources.nixpkgs-unstable {};
in
{
+ _module.args = { inherit pkgs-unstable; };
imports = [
./hardware-configuration.nix
<top/profiles/server>
<top/shared/postgresql.nix>
<top/shared/tailscale.nix>
+ <top/shared/monitoring.nix>
./web-personal.nix
./git.nix
- ./gotify.nix
./headscale.nix
./matrix.nix
- ./monitoring.nix
+ ./exporters.nix
+ ./grafana.nix
+ "${sources.my-lex-surf}/service.nix"
"${sources.my-osm-proposals}/service.nix"
"${sources.my-geopos-link}/service.nix"
+ "${sources.my-rust-features}/service.nix"
"${sources.my-spec-pub}/service.nix"
];
- web-personal.domain = domain;
+ web-personal.domain = baseDomain;
web-personal.matrixApiDomain = config.matrix.apiDomain;
- git.webUiDomain = "git.${domain}";
- headscale.domain = "tailscale.${domain}";
- matrix.serverName = domain;
- matrix.apiDomain = "matrix.${domain}";
+ git.webUiDomain = "git.${baseDomain}";
+ headscale.domain = "tailscale.${baseDomain}";
+ matrix.serverName = baseDomain;
+ matrix.apiDomain = "matrix.${baseDomain}";
+
+ users.users.www-generator = {
+ isSystemUser = true;
+ group = "www-generator";
+ };
+ users.groups.www-generator = {};
+
+ services.lex-surf =
+ let
+ domain = "lex.surf";
+ in
+ {
+ enable = true;
+ domain = domain;
+ enableACME = true;
+ fetchUser = "www-generator";
+ nginx = {
+ forceSSL = true;
+ extraConfig = helpers.mkNginxConfig domain;
+ };
+ };
services.osm_proposals =
let
- subdomain = "osm-proposals.${domain}";
+ domain = "osm-proposals.${baseDomain}";
in
{
enable = true;
- virtualHost = subdomain;
+ virtualHost = domain;
nginx = {
enableACME = true;
forceSSL = true;
- extraConfig = helpers.mkNginxConfig subdomain;
+ extraConfig = helpers.mkNginxConfig domain;
};
};
@@ -63,6 +89,21 @@ in
};
};
+ services.rust-features =
+ let
+ domain = "rust-features.${baseDomain}";
+ in
+ {
+ enable = true;
+ user = "www-generator";
+ virtualHost = domain;
+ nginx = {
+ enableACME = true;
+ forceSSL = true;
+ extraConfig = helpers.mkNginxConfig domain;
+ };
+ };
+
services.spec-pub =
let
domain = "spec.pub";
@@ -77,14 +118,16 @@ in
};
};
- monitoring.grafanaUiPort = 3000;
- monitoring.alloyUiPort = 3001;
- monitoring.lokiPort = 3030;
- gotify.port = 4000;
- monitoring.prometheusNodeExporterPort = 9002;
- monitoring.prometheusSqlExporterPort = 9003;
- headscale.port = 8080;
- matrix.port = 8008;
+ grafana.port = ports.grafana;
+ grafana.matrixForwarderPort = ports.grafanaMatrixForwarder;
+ grafana.matrixServerUrl = "http://localhost:${toString ports.matrix}";
+ monitoring.alloyUiPort = ports.grafanaAlloy;
+ monitoring.lokiPort = ports.grafanaLoki;
+ monitoring.prometheusPort = ports.prometheus;
+ monitoring.prometheusNodeExporterPort = ports.prometheusNodeExporter;
+ exporters.sqlExporterPort = ports.prometheusSqlExporter;
+ headscale.port = ports.headscale;
+ matrix.port = ports.matrix;
# Use the GRUB 2 boot loader.
boot.loader.grub.enable = true;
@@ -160,6 +203,8 @@ in
enable = true;
group = "www-data";
+ virtualHosts."tente.tailnet" = helpers.serviceIndexHost "tente.tailnet" ports.webUis;
+
appendHttpConfig = ''
# Close the connection for unknown Host headers.
# If we don't do this nginx serves some random virtualhost.
diff --git a/nixos/hosts/tente/exporters.nix b/nixos/hosts/tente/exporters.nix
new file mode 100644
index 0000000..1c03c35
--- /dev/null
+++ b/nixos/hosts/tente/exporters.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+let
+ helpers = import <top/shared/postgres-collectors.nix> { inherit config lib pkgs; };
+ cfg = config.exporters;
+in
+{
+ options.exporters = {
+ sqlExporterPort = lib.mkOption {
+ type = lib.types.int;
+ };
+ };
+
+ imports = [
+ <top/shared/prometheus-sql-exporter/service.nix>
+ ];
+
+ config = {
+ services.prometheus-sql-exporter = {
+ enable = true;
+ port = cfg.sqlExporterPort;
+ config = {
+ target = {
+ # This URL should be postgresql:///postgres?host=/run/postgresql
+ # but sql_exporter uses xo/dburl which isn't spec-compliant: https://github.com/xo/dburl/issues/46
+ data_source_name = "postgresql:/run/postgresql:/postgres";
+ collectors = helpers.collectorNames;
+ };
+ collectors = helpers.collectors;
+ };
+ };
+
+ monitoring.prometheusScrapeConfigs = [
+ {
+ job_name = "sql";
+ static_configs = [{
+ targets = [ "localhost:${toString cfg.sqlExporterPort}" ];
+ }];
+ }
+ ];
+ };
+}
diff --git a/nixos/hosts/tente/git.nix b/nixos/hosts/tente/git.nix
index ee10a43..49ecff0 100644
--- a/nixos/hosts/tente/git.nix
+++ b/nixos/hosts/tente/git.nix
@@ -2,7 +2,7 @@
let
cfg = config.git;
- helpers = import <top/helpers.nix> { inherit config; };
+ helpers = import <top/helpers.nix> { inherit config lib pkgs; };
in
{
options.git = {
@@ -47,6 +47,19 @@ in
enable-git-config = 1;
root-title = "push-f.com repositories";
root-desc = "My various repositories.";
+ readme = ":README.md";
+ about-filter =
+ # about-formatting.sh from cgit uses python-markdown which doesn't
+ # follow CommonMark, so we're using the lowdown parser instead.
+ let
+ # The about-filter is invoked with stdin but also with the filename
+ # as argv[1] so we wrap lowdown in a script to ignore the argument.
+ formatScript = pkgs.writeScriptBin "about-format.sh" ''
+ #!/bin/sh
+ ${pkgs.lowdown}/bin/lowdown
+ '';
+ in
+ "${formatScript}/bin/about-format.sh";
enable-index-owner = 0;
source-filter = "${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py";
clone-prefix = "https://${cfg.webUiDomain}";
diff --git a/nixos/hosts/tente/gotify.nix b/nixos/hosts/tente/gotify.nix
deleted file mode 100644
index ba0fd02..0000000
--- a/nixos/hosts/tente/gotify.nix
+++ /dev/null
@@ -1,21 +0,0 @@
-{ config, lib, ... }:
-
-let
- cfg = config.gotify;
-in
-{
- options.gotify = {
- port = lib.mkOption {
- type = lib.types.int;
- };
- };
-
- config = {
- services.gotify = {
- enable = true;
- environment = {
- GOTIFY_SERVER_PORT = cfg.port;
- };
- };
- };
-}
diff --git a/nixos/hosts/tente/grafana.nix b/nixos/hosts/tente/grafana.nix
new file mode 100644
index 0000000..f7c8789
--- /dev/null
+++ b/nixos/hosts/tente/grafana.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.grafana;
+ evPorts = import <top/hosts/ev/ports.nix>;
+in
+{
+ options.grafana = {
+ port = lib.mkOption {
+ type = lib.types.int;
+ };
+ matrixForwarderPort = lib.mkOption {
+ type = lib.types.int;
+ };
+ prometheusSqlExporterPort = lib.mkOption {
+ type = lib.types.int;
+ };
+ matrixServerUrl = lib.mkOption {
+ type = lib.types.str;
+ };
+ };
+
+ imports = [
+ <top/shared/prometheus-sql-exporter/service.nix>
+ <top/shared/grafana-matrix-forwarder/service.nix>
+ ];
+
+ config = {
+ age.secrets.grafana-matrix-forwarder-env.file = <top/secrets/grafana-matrix-forwarder-env.age>;
+
+ services.grafana = {
+ enable = true;
+ settings = {
+ server = {
+ http_addr = "0.0.0.0";
+ http_port = cfg.port;
+ };
+ };
+
+ provision = {
+ enable = true;
+ datasources.settings.datasources = [
+ {
+ name = "Prometheus Tente";
+ type = "prometheus";
+ url = "http://${config.services.prometheus.listenAddress}:${toString config.monitoring.prometheusPort}";
+ }
+ {
+ name = "Loki Tente";
+ type = "loki";
+ access = "proxy";
+ url = "http://127.0.0.1:${toString config.monitoring.lokiPort}";
+ }
+ {
+ name = "Prometheus Ev";
+ type = "prometheus";
+ url = "http://ev.tailnet:${toString evPorts.prometheus}";
+ }
+ {
+ name = "Loki Ev";
+ type = "loki";
+ access = "proxy";
+ url = "http://ev.tailnet:${toString evPorts.grafanaLoki}";
+ }
+ ];
+ };
+ };
+ services.grafana-matrix-forwarder = {
+ enable = true;
+ port = cfg.matrixForwarderPort;
+ homeserver = cfg.matrixServerUrl;
+ environmentFile = config.age.secrets.grafana-matrix-forwarder-env.path;
+ };
+ };
+}
diff --git a/nixos/hosts/tente/matrix.nix b/nixos/hosts/tente/matrix.nix
index cf0f09a..89782fc 100644
--- a/nixos/hosts/tente/matrix.nix
+++ b/nixos/hosts/tente/matrix.nix
@@ -1,8 +1,8 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, pkgs-unstable, ... }:
let
cfg = config.matrix;
- helpers = import <top/helpers.nix> { inherit config; };
+ helpers = import <top/helpers.nix> { inherit config lib pkgs; };
in
{
options.matrix = {
@@ -19,32 +19,19 @@ in
config = {
services = {
- matrix-synapse = {
+ matrix-conduit = {
enable = true;
+ package = pkgs-unstable.matrix-conduit;
settings = {
- server_name = cfg.serverName;
- listeners = [{
- # This listener matches the default of NixOS 24.11 (replicated here to make the port configurable).
- bind_addresses = ["127.0.0.1"];
+ global = {
+ server_name = cfg.serverName;
port = cfg.port;
- resources = [
- {
- compress = true;
- names = ["client"];
- }
- {
- compress = false;
- names = ["federation"];
- }
- ];
- tls = false;
- type = "http";
- x_forwarded = true;
- }];
+ address = "127.0.0.1"; # this is the default of conduit but the nixos service defaults to ::1
+ database_backend = "rocksdb";
+ enable_lightning_bolt = false;
+ allow_registration = false;
+ };
};
-
- # The default is INFO which can easily spam the systemd journal with 500k messages a day.
- log.root.level = "WARNING";
};
nginx.virtualHosts.${cfg.apiDomain} = {
@@ -54,14 +41,12 @@ in
# TODO: add locations."/" with some message
- # Forward all Matrix API calls to the synapse Matrix homeserver. A trailing slash
+ # Forward all Matrix API calls to the Conduit Matrix homeserver. A trailing slash
# *must not* be used here.
locations."/_matrix".proxyPass = "http://127.0.0.1:${toString cfg.port}";
- # Forward requests for e.g. SSO and password-resets.
- locations."/_synapse/client".proxyPass = "http://127.0.0.1:${toString cfg.port}";
};
- # I don't really care about these nginx access logs. Synapse has its own
+ # I don't really care about these nginx access logs. Conduit has its own
# log anyway and with the default log rotation (weekly and delaycompress=true)
# the access logs from last week took up ~800MB.
logrotate.settings.matrix-nginx-access-log =
diff --git a/nixos/hosts/tente/metadata.toml b/nixos/hosts/tente/metadata.toml
new file mode 100644
index 0000000..58f0301
--- /dev/null
+++ b/nixos/hosts/tente/metadata.toml
@@ -0,0 +1 @@
+channel = "nixos-small"
diff --git a/nixos/hosts/tente/monitoring.nix b/nixos/hosts/tente/monitoring.nix
deleted file mode 100644
index 7e92eed..0000000
--- a/nixos/hosts/tente/monitoring.nix
+++ /dev/null
@@ -1,317 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
- cfg = config.monitoring;
-in
-{
- options.monitoring = {
- grafanaUiPort = lib.mkOption {
- type = lib.types.int;
- };
- lokiPort = lib.mkOption {
- type = lib.types.int;
- };
- alloyUiPort = lib.mkOption {
- type = lib.types.int;
- };
- prometheusNodeExporterPort = lib.mkOption {
- type = lib.types.int;
- };
- prometheusSqlExporterPort = lib.mkOption {
- type = lib.types.int;
- };
- };
-
- imports = [
- <top/shared/prometheus-sql-exporter/service.nix>
- ];
-
- config = {
- services.grafana = {
- enable = true;
- settings = {
- server = {
- http_addr = "0.0.0.0";
- http_port = cfg.grafanaUiPort;
- };
- };
-
- provision = {
- enable = true;
- datasources.settings.datasources = [
- {
- name = "Prometheus";
- type = "prometheus";
- url = "http://${config.services.prometheus.listenAddress}:${toString config.services.prometheus.port}";
- }
- {
- name = "Loki";
- type = "loki";
- access = "proxy";
- url = "http://127.0.0.1:${toString cfg.lokiPort}";
- }
- ];
- };
- };
-
- services.prometheus = {
- enable = true;
-
- scrapeConfigs = [
- {
- job_name = "node";
- static_configs = [{
- targets = [ "localhost:${toString cfg.prometheusNodeExporterPort}" ];
- }];
- }
- {
- job_name = "sql";
- static_configs = [{
- targets = [ "localhost:${toString cfg.prometheusSqlExporterPort}" ];
- }];
- }
- ];
-
- exporters.node = {
- enable = true;
- enabledCollectors = [ "systemd" ];
- port = cfg.prometheusNodeExporterPort;
- };
- };
-
- services.prometheus-sql-exporter = {
- enable = true;
- port = cfg.prometheusSqlExporterPort;
- config = {
- target = {
- # This URL should be postgresql:///postgres?host=/run/postgresql
- # but sql_exporter uses xo/dburl which isn't spec-compliant: https://github.com/xo/dburl/issues/46
- data_source_name = "postgresql:/run/postgresql:/postgres";
- collectors = ["db-sizes"];
- };
- collectors = [
- {
- collector_name = "db-sizes";
- metrics = [
- {
- metric_name = "pg_db_size_bytes";
- help = "disk space used by the database";
- type = "gauge";
- key_labels = ["database_name"];
- values = ["size"];
- query = "SELECT datname AS database_name, pg_database_size(datname) as size from pg_database";
- }
- ];
- }
- ];
- };
- };
-
- services.loki = {
- enable = true;
- configuration = {
- server.http_listen_port = cfg.lokiPort;
- auth_enabled = false;
-
- ingester = {
- lifecycler = {
- address = "127.0.0.1";
- ring = {
- kvstore.store = "inmemory";
- replication_factor = 1;
- };
- };
- };
-
- schema_config = {
- configs = [{
- store = "tsdb";
- object_store = "filesystem";
- schema = "v13";
- index = {
- prefix = "index_";
- period = "24h";
- };
- }];
- };
-
- storage_config = {
- tsdb_shipper = {
- active_index_directory = "/var/lib/loki/tsdb-active";
- cache_location = "/var/lib/loki/tsdb-cache";
- };
- };
-
- compactor = {
- working_directory = "/var/lib/loki";
- };
-
- limits_config = {
- allow_structured_metadata = true;
- };
- };
- };
-
- systemd.services.alloy = {
- serviceConfig = {
- SupplementaryGroups = [
- "systemd-journal"
- "www-data"
- ];
- };
- };
-
- services.alloy = {
- enable = true;
- extraFlags = ["--server.http.listen-addr=0.0.0.0:${toString cfg.alloyUiPort}"];
- # TODO: submit PR to nixpkgs so that the alloy config can be specified as a JSON expression
- configPath = pkgs.writeText "config.alloy" ''
- loki.source.journal "journal" {
- max_age = "12h0m0s"
- relabel_rules = discovery.relabel.journal.rules
- forward_to = [loki.process.journal.receiver]
- labels = {
- host = "tente",
- job = "systemd-journal",
- }
- }
-
- loki.process "journal" {
- forward_to = [loki.write.default.receiver]
-
- stage.match {
- // Select messages from systemd services that have LogExtraFields=log_format=logfmt.
- selector = "{__journal_log_format=\"logfmt\"}"
- stage.logfmt {
- mapping = { time = "", level = "" }
- }
- stage.timestamp {
- source = "time"
- format = "RFC3339"
- }
- stage.structured_metadata {
- values = { level = "" }
- }
- }
- }
-
- discovery.relabel "journal" {
- targets = []
-
- rule {
- source_labels = ["__journal__systemd_unit"]
- target_label = "unit"
- }
- }
-
- loki.source.file "nginx_access" {
- targets = local.file_match.nginx_access.targets
- forward_to = [loki.process.nginx_access.receiver]
- }
-
- local.file_match "nginx_access" {
- path_targets = [{
- __path__ = "/var/log/nginx/*.access.log",
- }]
- }
-
- loki.process "nginx_access" {
- forward_to = [loki.write.default.receiver]
-
- stage.static_labels {
- values = {
- job = "nginx",
- }
- }
-
- // Extracting the log file name as vhost because it's more convenient
- // to query for than the full filename. We could also use server_name
- // but there could be wildcard server_names and Loki labels should have
- // a low cardinality for performance reasons.
- stage.regex {
- source = "filename"
- expression = "(?P<vhost>[^/]+)\\.access\\.log$"
- }
-
- stage.labels {
- values = {
- vhost = "",
- }
- }
-
- stage.json {
- expressions = { "msec" = "", path = "" }
- }
-
- stage.timestamp {
- source = "msec"
- format = "Unix"
- }
-
- // Setting level=info to prevent Loki's log level detection from wrongly
- // detecting messages with paths containing "error" as errors.
- // Creating the filetype entry via stage.template because there's no
- // static_structured_metadata stage yet. (https://github.com/grafana/loki/issues/16703)
- stage.template {
- source = "level"
- template = "info"
- }
- stage.structured_metadata {
- values = { level = "" }
- }
-
- stage.labels {
- values = {
- // Temporarily adding path as a label so that we can use it in the match selectors.
- path = "",
- }
- }
-
- stage.match {
- selector = "{path=~\"/\\\\.well-known/.*\"}"
- // Creating the filetype entry via stage.template because there's no
- // static_structured_metadata stage yet. (https://github.com/grafana/loki/issues/16703)
- stage.template {
- source = "filetype"
- template = "well-known"
- }
- }
-
- stage.match {
- selector = "{path=\"/robots.txt\"}"
- stage.template {
- source = "filetype"
- template = "robots.txt"
- }
- }
-
- stage.match {
- selector = "{path=~\".*\\\\.atom$\"}"
- stage.template {
- source = "filetype"
- template = "feed"
- }
- }
-
- stage.structured_metadata {
- values = {
- filetype = "",
- }
- }
-
- // Dropping path again because it has a too high cardinality for a label.
- stage.label_drop {
- values = [ "path" ]
- }
- }
-
- loki.write "default" {
- endpoint {
- url = "http://127.0.0.1:${toString cfg.lokiPort}/loki/api/v1/push"
- }
- external_labels = {}
- }
- '';
- };
- };
-}
diff --git a/nixos/hosts/tente/ports.nix b/nixos/hosts/tente/ports.nix
new file mode 100644
index 0000000..133d0f7
--- /dev/null
+++ b/nixos/hosts/tente/ports.nix
@@ -0,0 +1,19 @@
+rec {
+ grafana = 3000;
+ grafanaAlloy = 3001;
+ grafanaLoki = 3030;
+ grafanaMatrixForwarder = 3002;
+ headscale = 8080;
+ matrix = 8008;
+ prometheus = 9090;
+ prometheusNodeExporter = 9002;
+ prometheusSqlExporter = 9003;
+
+ webUis = {
+ inherit
+ grafana
+ grafanaAlloy
+ prometheus
+ ;
+ };
+}
diff --git a/nixos/hosts/tente/web-personal.nix b/nixos/hosts/tente/web-personal.nix
index 109a87d..d706b9d 100644
--- a/nixos/hosts/tente/web-personal.nix
+++ b/nixos/hosts/tente/web-personal.nix
@@ -2,7 +2,7 @@
let
cfg = config.web-personal;
- helpers = import <top/helpers.nix> { inherit config; };
+ helpers = import <top/helpers.nix> { inherit config lib pkgs; };
in
{
options.web-personal = {
diff --git a/nixos/npins/default.nix b/nixos/npins/default.nix
index 5e7d086..6592476 100644
--- a/nixos/npins/default.nix
+++ b/nixos/npins/default.nix
@@ -1,10 +1,53 @@
+/*
+ This file is provided under the MIT licence:
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
# Generated by npins. Do not modify; will be overwritten regularly
let
data = builtins.fromJSON (builtins.readFile ./sources.json);
version = data.version;
+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
+ range =
+ first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1);
+
+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
+ stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
+
+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
+ stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
+ concatMapStrings = f: list: concatStrings (map f list);
+ concatStrings = builtins.concatStringsSep "";
+
+ # If the environment variable NPINS_OVERRIDE_${name} is set, then use
+ # the path directly as opposed to the fetched source.
+ # (Taken from Niv for compatibility)
+ mayOverride =
+ name: path:
+ let
+ envVarName = "NPINS_OVERRIDE_${saneName}";
+ saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name;
+ ersatz = builtins.getEnv envVarName;
+ in
+ if ersatz == "" then
+ path
+ else
+ # this turns the string into an actual Nix path (for both absolute and
+ # relative paths)
+ builtins.trace "Overriding path of \"${name}\" with \"${ersatz}\" due to set \"${envVarName}\"" (
+ if builtins.substring 0 1 ersatz == "/" then
+ /. + ersatz
+ else
+ /. + builtins.getEnv "PWD" + "/${ersatz}"
+ );
+
mkSource =
- spec:
+ name: spec:
assert spec ? type;
let
path =
@@ -16,16 +59,19 @@ let
mkPyPiSource spec
else if spec.type == "Channel" then
mkChannelSource spec
+ else if spec.type == "Tarball" then
+ mkTarballSource spec
else
builtins.throw "Unknown source type ${spec.type}";
in
- spec // { outPath = path; };
+ spec // { outPath = mayOverride name path; };
mkGitSource =
{
repository,
revision,
url ? null,
+ submodules,
hash,
branch ? null,
...
@@ -33,31 +79,39 @@ let
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
- if url != null then
- (builtins.fetchTarball {
+ if url != null && !submodules then
+ builtins.fetchTarball {
inherit url;
sha256 = hash; # FIXME: check nix version & use SRI hashes
- })
+ }
else
- assert repository.type == "Git";
let
+ url =
+ if repository.type == "Git" then
+ repository.url
+ else if repository.type == "GitHub" then
+ "https://github.com/${repository.owner}/${repository.repo}.git"
+ else if repository.type == "GitLab" then
+ "${repository.server}/${repository.repo_path}.git"
+ else
+ throw "Unrecognized repository type ${repository.type}";
urlToName =
url: rev:
let
- matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
+ matched = builtins.match "^.*/([^/]*)(\\.git)?$" url;
short = builtins.substring 0 7 rev;
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
in
"${if matched == null then "source" else builtins.head matched}${appendShort}";
- name = urlToName repository.url revision;
+ name = urlToName url revision;
in
builtins.fetchGit {
- url = repository.url;
rev = revision;
inherit name;
# hash = hash;
+ inherit url submodules;
};
mkPyPiSource =
@@ -73,8 +127,20 @@ let
inherit url;
sha256 = hash;
};
+
+ mkTarballSource =
+ {
+ url,
+ locked_url ? url,
+ hash,
+ ...
+ }:
+ builtins.fetchTarball {
+ url = locked_url;
+ sha256 = hash;
+ };
in
-if version == 3 then
- builtins.mapAttrs (_: mkSource) data.pins
+if version == 5 then
+ builtins.mapAttrs mkSource data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
diff --git a/nixos/npins/sources.json b/nixos/npins/sources.json
index 5d0d4d9..082a652 100644
--- a/nixos/npins/sources.json
+++ b/nixos/npins/sources.json
@@ -10,11 +10,27 @@
"pre_releases": false,
"version_upper_bound": null,
"release_prefix": null,
+ "submodules": false,
"version": "0.15.0",
"revision": "564595d0ad4be7277e07fa63b5a991b3c645655d",
"url": "https://api.github.com/repos/ryantm/agenix/tarball/0.15.0",
"hash": "01dhrghwa7zw93cybvx4gnrskqk97b004nfxgsys0736823956la"
},
+ "grafana-matrix-forwarder": {
+ "type": "GitRelease",
+ "repository": {
+ "type": "Git",
+ "url": "https://gitlab.com/hctrdev/grafana-matrix-forwarder.git"
+ },
+ "pre_releases": false,
+ "version_upper_bound": null,
+ "release_prefix": null,
+ "submodules": false,
+ "version": "v0.8.3",
+ "revision": "a77a360503ea12c8244646753a3bf271428ddd02",
+ "url": null,
+ "hash": "1mdgv6jfbjs3xsqk9dxli6w345jws8j5qgzqf6wljmxlvibbic6y"
+ },
"my-geopos-link": {
"type": "Git",
"repository": {
@@ -22,9 +38,22 @@
"url": "https://git.push-f.com/geopos.link"
},
"branch": "master",
- "revision": "ae67ef4f60656557d2408f3a4c2ba01959eef2c5",
+ "submodules": false,
+ "revision": "ab9198d989e0889816e510b66dad1548ce0cfb48",
+ "url": null,
+ "hash": "1xq5sl6rsxk8716ff8hghb3aihdp41ynfwaihllnzqghc6qpigjw"
+ },
+ "my-lex-surf": {
+ "type": "Git",
+ "repository": {
+ "type": "Git",
+ "url": "https://git.push-f.com/lex-surf"
+ },
+ "branch": "master",
+ "submodules": false,
+ "revision": "ef3318a78050ea8aa4f6a65dd4673958c0eadf85",
"url": null,
- "hash": "0r2m5q7zwxms9spbg12gq6q5pc5hz2x6i14qq5s2nxvvf4fsmnnv"
+ "hash": "0v1xmv3slsb965047wi968j3n58dzy227pc5y2g79g2zdppvx94d"
},
"my-osm-proposals": {
"type": "Git",
@@ -33,9 +62,22 @@
"url": "https://git.push-f.com/osm-proposals"
},
"branch": "master",
- "revision": "2ee6afdf3356dea2aebdd48d20dfe9eb07037ba3",
+ "submodules": false,
+ "revision": "f6e5ec8c75b434b74d9e2ed2abdb36e92f7255a1",
"url": null,
- "hash": "14j48alysvx4n06my3kspsbplx23sv07nvswrya1x3swmwn4mnnc"
+ "hash": "1ycdi1dp4d20rbxbr51n4hmskbgwwb4xz243gxky1jmy5lixww3d"
+ },
+ "my-rust-features": {
+ "type": "Git",
+ "repository": {
+ "type": "Git",
+ "url": "https://git.push-f.com/rust-features"
+ },
+ "branch": "master",
+ "submodules": false,
+ "revision": "60c929acab8bfcd3fdc355288f1a72c1ed303f11",
+ "url": null,
+ "hash": "1vnwhgqqi8ihic6fz6grzikc1dra6myl2b2aly2fgzrcbzrd5390"
},
"my-spec-pub": {
"type": "Git",
@@ -44,28 +86,56 @@
"url": "https://git.push-f.com/spec.pub"
},
"branch": "master",
- "revision": "65610a959cc75482001f14241eb521d7b18c8011",
+ "submodules": false,
+ "revision": "152c8021bef7310ce2501e23de12e892c3ed5032",
+ "url": null,
+ "hash": "1z83lhqzr7yx07qb3icz0pph64aij9a1z17rqxzqmyvjsyyn8nfd"
+ },
+ "my-vdf": {
+ "type": "Git",
+ "repository": {
+ "type": "Git",
+ "url": "https://git.push-f.com/vdf"
+ },
+ "branch": "master",
+ "submodules": false,
+ "revision": "ba6b72b4c7e257b9514ce21b7fbbc469e4c8a849",
"url": null,
- "hash": "0y9wz8kvjc3gv5qf8il5gh9wrx46vinaav1kq0yjiybibhpjskz7"
+ "hash": "0yrlxfzsi13zngr32zf6ca9fhh3z1lj3960rv2ipp51d09zvgwjq"
},
"nixos": {
"type": "Channel",
- "name": "nixos-24.11",
- "url": "https://releases.nixos.org/nixos/24.11/nixos-24.11.714826.04ef94c4c158/nixexprs.tar.xz",
- "hash": "1xfpmwi69c50dbsr00r04v3czh6scccvdai2f5qhr0vi1p1sgvgz"
+ "name": "nixos-25.05",
+ "url": "https://releases.nixos.org/nixos/25.05/nixos-25.05.808129.3385ca0cd7e1/nixexprs.tar.xz",
+ "hash": "0fm3h18h23nz1zyqjii1dfv4az7lmicx7h07klsig6zz4pp9by6s"
},
"nixos-small": {
"type": "Channel",
- "name": "nixos-24.11-small",
- "url": "https://releases.nixos.org/nixos/24.11-small/nixos-24.11.714830.060b03c5d950/nixexprs.tar.xz",
- "hash": "1lxm3y9jqkf8p63xrn6phyp5d763jfwcjxb3vpfqgc8iwq5z4cb9"
+ "name": "nixos-25.05-small",
+ "url": "https://releases.nixos.org/nixos/25.05-small/nixos-25.05.808273.5d35709e6ca2/nixexprs.tar.xz",
+ "hash": "1srzriwhnmhfgjwvb45ca2rk30kh68wfkh07s11idn8zyrr1qdly"
},
"nixpkgs-unstable": {
"type": "Channel",
"name": "nixpkgs-unstable",
- "url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.05pre763845.de0fe301211c/nixexprs.tar.xz",
- "hash": "1iq1a41c1c6i7lr8xpp2bwbm36v8qijisx1jv4126624swr9467l"
+ "url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre852399.4c202d26483c/nixexprs.tar.xz",
+ "hash": "0aaj59kn5q6n5cwq3i2a9sa89z0q71cy69kmpiy0kkn53hv3dxch"
+ },
+ "prometheus-sql-exporter": {
+ "type": "GitRelease",
+ "repository": {
+ "type": "Git",
+ "url": "https://github.com/burningalchemist/sql_exporter"
+ },
+ "pre_releases": false,
+ "version_upper_bound": null,
+ "release_prefix": null,
+ "submodules": false,
+ "version": "0.18.1",
+ "revision": "6e73ff71a8976939c28ce9a0ddbeefe0801d0a37",
+ "url": null,
+ "hash": "1a3j3rsjvw6wqyayml5x5c7x18bvn45ldr2p280yn9pqdzihk480"
}
},
- "version": 3
-} \ No newline at end of file
+ "version": 5
+}
diff --git a/nixos/profiles/common/basics.nix b/nixos/profiles/common/basics.nix
index aa6eff9..3f1859c 100644
--- a/nixos/profiles/common/basics.nix
+++ b/nixos/profiles/common/basics.nix
@@ -1,9 +1,10 @@
-{ config, pkgs, ... }:
+{ lib, config, pkgs, ... }:
{
environment.systemPackages = with pkgs; [
git
- vim
+ # lowPrio because we use vim-full in the workstation profile
+ (lowPrio vim)
file
htop
@@ -32,4 +33,7 @@
ZDOTDIR = "${config}/zsh";
};
+
+ # By default installs plugins that pull in GTK.
+ networking.networkmanager.plugins = lib.mkForce [];
}
diff --git a/nixos/profiles/common/default.nix b/nixos/profiles/common/default.nix
index 2b07724..a508a32 100644
--- a/nixos/profiles/common/default.nix
+++ b/nixos/profiles/common/default.nix
@@ -1,7 +1,7 @@
{ config, pkgs, ... }:
let
- sources = import ../../npins;
+ sources = import <top/npins>;
in
{
imports = [
diff --git a/nixos/profiles/common/nixpkgs/overlays.nix b/nixos/profiles/common/nixpkgs/overlays.nix
index 613338f..40f9041 100644
--- a/nixos/profiles/common/nixpkgs/overlays.nix
+++ b/nixos/profiles/common/nixpkgs/overlays.nix
@@ -1,4 +1,29 @@
# https://nixos.org/manual/nixpkgs/stable/#sec-overlays-definition
+{ pkgs, ... }:
[
+ # features
+ (final: prev: {
+ scc = prev.scc.overrideAttrs (old: {
+ # https://github.com/boyter/scc/pull/622
+ src = pkgs.fetchFromGitHub {
+ owner = "boyter";
+ repo = "scc";
+ rev = "b73ea06bdc5890821d03502a2cfc4224b19a9b67";
+ hash = "sha256-vcuoKrvludBE0KpXVLkKzB38n0mZJWVB8bYrgJDHKfY=";
+ };
+ });
+ })
+ # bug fixes
+ (final: prev: {
+ sway-unwrapped = prev.sway-unwrapped.overrideAttrs (old: {
+ patches = old.patches ++ [
+ # https://github.com/swaywm/sway/pull/8761
+ (pkgs.fetchpatch {
+ url = "https://github.com/swaywm/sway/commit/af7c6ec7b1daeeec67dd17e27fb75f1f1c347327.patch";
+ hash = "sha256-DxN/3IdswZ6q/ksBVr+wgwGe7ScJeg8gFHYQdQAueww=";
+ })
+ ];
+ });
+ })
]
diff --git a/nixos/profiles/common/sanix.nix b/nixos/profiles/common/sanix.nix
index a20db24..626aa73 100644
--- a/nixos/profiles/common/sanix.nix
+++ b/nixos/profiles/common/sanix.nix
@@ -2,14 +2,13 @@
# Use the Nixpkgs config and overlays from the local files for this NixOS build
nixpkgs = {
config = import ./nixpkgs/config.nix;
- overlays = import ./nixpkgs/overlays.nix;
+ overlays = import ./nixpkgs/overlays.nix { inherit pkgs; };
};
# Makes commands default to the same Nixpkgs, config, overlays and NixOS configuration
nix.nixPath = [
"nixpkgs=${pkgs.path}"
"nixos-config=${toString <nixos-config>}"
- "nixpkgs-overlays=${toString ./nixpkgs/overlays.nix}"
];
environment.variables.NIXPKGS_CONFIG = lib.mkForce (toString ./nixpkgs/config.nix);
diff --git a/nixos/profiles/server/default.nix b/nixos/profiles/server/default.nix
index ac24e20..07dcd4b 100644
--- a/nixos/profiles/server/default.nix
+++ b/nixos/profiles/server/default.nix
@@ -13,4 +13,9 @@
};
};
};
+
+ security.sudo.wheelNeedsPassword = false;
+
+ # For remote rebuilds with --target-host.
+ nix.settings.trusted-users = ["@wheel"];
}
diff --git a/nixos/profiles/workstation/create.nix b/nixos/profiles/workstation/create.nix
index 924717f..1c955a6 100644
--- a/nixos/profiles/workstation/create.nix
+++ b/nixos/profiles/workstation/create.nix
@@ -4,8 +4,14 @@
environment.systemPackages = with pkgs; [
graphviz-nox # for dot
+ hugo
typst
+ (inkscape-with-extensions.override {
+ inkscapeExtensions = [
+ inkscape-extensions.inkstitch
+ ];
+ })
gimp
krita
];
diff --git a/nixos/profiles/workstation/default.nix b/nixos/profiles/workstation/default.nix
index 0332200..9df5328 100644
--- a/nixos/profiles/workstation/default.nix
+++ b/nixos/profiles/workstation/default.nix
@@ -2,13 +2,7 @@
let
cfg = config.workstation;
- sources = import ../../npins;
- nixGit = import (pkgs.fetchFromGitHub {
- owner = "NixOS";
- repo = "nix";
- rev = "7a8a28629c61c75af010ff0a5a88c16c4ce536c7";
- sha256 = "sha256-oqG9AFPXBneKVmiWa9b9ai0hGZqHVKVFaFLdBZitSUA=";
- });
+ sources = import <top/npins>;
in
{
options.workstation = {
@@ -33,6 +27,7 @@ in
"wheel"
"networkmanager"
"dialout"
+ "wireshark"
];
};
@@ -44,6 +39,11 @@ in
environment.systemPackages = with pkgs; [
npins
(callPackage "${sources.agenix}/pkgs/agenix.nix" {})
+ (callPackage sources.my-vdf {})
+
+ vim-full
+ skim
+ (writeShellScriptBin "ed" (builtins.readFile ./scripts/ed))
unzip
@@ -60,9 +60,5 @@ in
# Age defaults to this anyway when openssh is enabled.
# We're setting this here for workstations where openssh is disabled.
age.identityPaths = ["/etc/ssh/ssh_host_ed25519_key"];
-
- # Using nix from git for --raw support in nix-instantiate --eval.
- # FUTURE: remove once upgrading to NixOS 25.05
- nix.package = nixGit.packages.${builtins.currentSystem}.default;
};
}
diff --git a/nixos/profiles/workstation/dev.nix b/nixos/profiles/workstation/dev.nix
index e550a4c..a40d574 100644
--- a/nixos/profiles/workstation/dev.nix
+++ b/nixos/profiles/workstation/dev.nix
@@ -4,16 +4,19 @@
environment.systemPackages = with pkgs; [
rustup
go
+ golangci-lint
python313
nodejs_22
# CLI tools
+ skim
docker-compose
gnumake
jq
+ pkgs-unstable.jujutsu
just
sqlite-interactive
- tokei
+ scc
fastmod
license-generator
@@ -22,12 +25,21 @@
gcc # rustc fails if cc linker isn't found
chromium
- pkgs-unstable.zed-editor
+ zed-editor
vscodium
# I'm installing extensions via my install-imperative script.
platformio
+
+ # language servers
+ # rust-analyzer is installed via rustup
+ nixd
];
+ programs.wireshark = {
+ enable = true;
+ package = pkgs.wireshark;
+ };
+
virtualisation.podman = {
enable = true;
dockerSocket.enable = true;
diff --git a/nixos/profiles/workstation/graphical.nix b/nixos/profiles/workstation/graphical.nix
index dc05f05..9e39092 100644
--- a/nixos/profiles/workstation/graphical.nix
+++ b/nixos/profiles/workstation/graphical.nix
@@ -32,7 +32,9 @@
# graphical tools
imv
- mpv
+ (mpv.override {
+ youtubeSupport = false; # disable yt-dlp dependency
+ })
zathura
# TODO: zathura doesn't adjust zoom on PDF open even though adjust-open="best-fit" is the default according to zathurarc(5)
element-desktop
@@ -67,6 +69,7 @@
pulse.enable = true;
};
security.rtkit.enable = true; # recommended for PipeWire
+ hardware.bluetooth.enable = true;
programs.thunderbird = {
enable = true;
diff --git a/nixos/profiles/workstation/scripts/ed b/nixos/profiles/workstation/scripts/ed
new file mode 100644
index 0000000..a3dd433
--- /dev/null
+++ b/nixos/profiles/workstation/scripts/ed
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+$VISUAL $(find . -not -path "*/.*" | sk --no-info "$@")
diff --git a/nixos/rebuild b/nixos/rebuild
index 80a52cd..1bc6c39 100755
--- a/nixos/rebuild
+++ b/nixos/rebuild
@@ -11,19 +11,16 @@ if [ ! -f $configPath ]; then
exit 1
fi
-firstLine=$(head -n1 "$configPath")
-
-if ! echo "$firstLine" | grep -E ^"# channel *=" > /dev/null; then
- echo "aborting: $configPath doesn't start with \`# channel=\"...\"\`, where ... is a pin from sources.json"
- exit 1
-fi
-
nix-eval() {
- nix-instantiate --eval --read-write-mode "$@" \
- | tr -d \" # nix-instantiate has no raw output yet (like the experimental nix eval)
+ nix-instantiate --eval --raw --read-write-mode "$@"
}
-channel=$(nix-eval --argstr line "$(echo $firstLine | tr -d \#)" --expr '{line}: (builtins.fromTOML line).channel')
+channel=$(nix-eval --argstr host "$HOSTNAME" --expr '{host}:
+let
+ toml = builtins.readFile ./hosts/${host}/metadata.toml;
+in
+ (builtins.fromTOML toml).channel
+')
nixpkgsPath=$(nix-eval --argstr channel "$channel" --expr '{channel}: (import ./npins).${channel}.outPath')
# nixos-rebuild always reads Nixpkgs from the NIX_PATH,
diff --git a/nixos/secrets/grafana-matrix-forwarder-env.age b/nixos/secrets/grafana-matrix-forwarder-env.age
new file mode 100644
index 0000000..84cab14
--- /dev/null
+++ b/nixos/secrets/grafana-matrix-forwarder-env.age
Binary files differ
diff --git a/nixos/secrets/miniflux-admin.age b/nixos/secrets/miniflux-admin.age
new file mode 100644
index 0000000..b656b07
--- /dev/null
+++ b/nixos/secrets/miniflux-admin.age
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 PMTW+A CIjI5374KCn9hZRM6vj4PTcUj4FzHan/buPBD5gQmw8
+6mXVcux6zZQ16U1y3bipsKD4y1QiMQ498IQAzpDJqt0
+-> ssh-ed25519 PHC5tQ IQiEl8meNDHIE0HI2SErDxFDVSEd/TCRH5PplnIC9H4
+mq/N57ex2NpbptzaMpAfYJ1Y/exIj1+J2X9CRW28aOg
+-> e#-grease TdfbQVa$ L X4("^:7K V&L1z
+cHZi1Yyb7tz7gw9zkPqbbhEZeGVIncPZfTpIvch0e/wDCIjMxY7e2Pb6nOVDxQ
+--- b32AUhCBWDVXlj4pUbDW91Y80nQ5Wp9zTy+rl6vnFHU
+A5'`eYZ%Fdk*&8IBe@Aa[/{eEu<)>ItMY};b \ No newline at end of file
diff --git a/nixos/secrets/secrets.nix b/nixos/secrets/secrets.nix
index db19967..f1c8ab8 100644
--- a/nixos/secrets/secrets.nix
+++ b/nixos/secrets/secrets.nix
@@ -1,10 +1,12 @@
let
- martin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICRBAAt77GXrDtIp6fSjeMHCV3e1ujCE0meetqX3YZpn";
+ martin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF+moAzcnDJsyUalRVdLeJS1D5wezwMDyHuM+Cyk1nQh";
hamac = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJmjbC0gk2s/qDQ+QR//GJH0ZPld99L0EtX7dPP5h2RN";
ev = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINCSypbTOnAYBO32vUUieOsb6ws32gCsDg8nB8JhuFuI";
+ tente = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGTyZdGXxkAjzRulRFB23AX0T/lVLZOaSaFJi+thsjgx";
in
{
"vpn-se-privKey.age".publicKeys = [ martin hamac ev ];
- "vpn-se-presharedKey.age".publicKeys = [ martin hamac ev ];
+ "grafana-matrix-forwarder-env.age".publicKeys = [ martin tente ];
+ "miniflux-admin.age".publicKeys = [ martin ev ];
}
diff --git a/nixos/secrets/vpn-se-presharedKey.age b/nixos/secrets/vpn-se-presharedKey.age
deleted file mode 100644
index 1f56d86..0000000
--- a/nixos/secrets/vpn-se-presharedKey.age
+++ /dev/null
@@ -1,14 +0,0 @@
-age-encryption.org/v1
--> ssh-ed25519 0iFcGg 33qVwdJ+x5d9ezpvYayjZqrwAZQDb7AxvOjQucyVYgQ
-z5/tRvXEE+xSJ4BKQORu4yI+UG2GNaKdTZe0FkN4VyY
--> ssh-ed25519 zNg/mg 9rug5AzVUH/fIDvtSVA0SZkQ0tR+T59VY2UYPrxZfFM
-g49jKdkf+tz/sl9g/RdfkRv/CKneO04rkXPprQYv9rw
--> ssh-ed25519 PHC5tQ H9j903SKpztlrUN/nP7Q8Io/iJLY8ka6aBlOc4d/+iA
-hfQIpLuvT2D8eo5T0MmydivcQE7DFbHhLO809YET0HI
--> W}8>=-grease
-hy4mn+TGe6QPbZud+cppGmgzyed8SgVcaYBumJdxIRy6NQXW8PH2itg6GTIR6Npb
-Nhj9zeSsQvAq8no7z+Q5DsXI7o6iVUDgvoQ1HcUan7WYGqR5MA0
---- WbaGYSFVrJ8+YgzYeLaLXfOsCDMMruUpmFtKvtym57E
-Ik"a#L!1\:
-=.+$@].Hnd]U/q-~ -T
- \ No newline at end of file
diff --git a/nixos/secrets/vpn-se-privKey.age b/nixos/secrets/vpn-se-privKey.age
index 93de475..ca2a8eb 100644
--- a/nixos/secrets/vpn-se-privKey.age
+++ b/nixos/secrets/vpn-se-privKey.age
@@ -1,11 +1,12 @@
age-encryption.org/v1
--> ssh-ed25519 0iFcGg KyKfFePFWpX9AOPw5Sy9UQkucPsQDwrEwRL66e3wdA4
-7DNSkNroD1HaRld0M5uMWtu7dojKUB7DPU9hdTPCXZ8
--> ssh-ed25519 zNg/mg Y0w7MCgwQKqb8FurFOyEshSmfCNoIKi0OnOJUsDeh14
-xILssj5y6XbZ10b39MqqhN42DRQt1AKIdh/Eidin8dA
--> ssh-ed25519 PHC5tQ nfW6lDN4vrv5EOCZGmfe9LEto5FDbU9Vh1LOvrnpvR0
-KrGBpZh7+DalPFoM0rW6ylehDnrmCz2JAOKqMEN4BoQ
--> k4]AeAV-grease pSemvkw @C;y
-vBW8ETA
---- 8Eel9tqXmZ3s7J1CqXlCMTOPAHWD/ftxB7t2DRtHi4A
-T›{vgm We 5Yxr嶘v} ]MCMbHAZ?\Hl'L, \ No newline at end of file
+-> ssh-ed25519 PMTW+A WNGr/0aVmRRmhZ6P6PaWxPML3VRBi59QTD5kQ2AuqXk
+6ivhC1Cm/DVWdCxx+48U0YzJqhj0rmZ195SmjEX+Y4k
+-> ssh-ed25519 zNg/mg I4MbADHlAgwced5uYaEWXNgKvzfyGxKAWiXtg7rgRCg
+ipQGQn7rCu59gHq5DjQIckvqGO5P/LJTtSP8yViEVdQ
+-> ssh-ed25519 PHC5tQ xsy2ivym+ymHacFJeTwll7aJyypoQTg8h/DyaVp98xs
+rwPb2fCAtHKzUQgZ/0SxKZ37MnVb8u74vesd0o5G7yc
+-> @n>>$-grease @J3N{1sN qN_Oh]I K(#
+dA
+--- P0ZI10bTAqn+OPH84hF1V+Qa/X7gqvMZHkYAZQg5zgo
+v:LՓQ "[YU
+KY?+$Aa?`o[k369U0Ȯ8PH[H*g`w \ No newline at end of file
diff --git a/nixos/shared/alloy-nix-config/alloy_nix_config.go b/nixos/shared/alloy-nix-config/alloy_nix_config.go
new file mode 100644
index 0000000..4b6eb63
--- /dev/null
+++ b/nixos/shared/alloy-nix-config/alloy_nix_config.go
@@ -0,0 +1,129 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "maps"
+ "os"
+ "slices"
+ "strconv"
+ "strings"
+)
+
+func main() {
+ if len(os.Args) != 3 {
+ fmt.Fprintf(os.Stderr, "usage: %s <json_path> <out_path>\n", os.Args[0])
+ os.Exit(1)
+ }
+
+ jsonPath := os.Args[1]
+ outPath := os.Args[2]
+
+ jsonData, err := os.ReadFile(jsonPath)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error reading file %s: %v\n", jsonPath, err)
+ os.Exit(1)
+ }
+
+ // It would be nice to preserve the order of blocks ... except that we can't
+ // because Nix already doesn't preserve the order of attribute sets.
+ var config map[string]any
+ if err := json.Unmarshal(jsonData, &config); err != nil {
+ fmt.Fprintf(os.Stderr, "error parsing JSON: %v\n", err)
+ os.Exit(1)
+ }
+
+ result := formatConfig(config)
+
+ if err := os.WriteFile(outPath, []byte(result), 0644); err != nil {
+ fmt.Fprintf(os.Stderr, "error writing file %s: %v\n", outPath, err)
+ os.Exit(1)
+ }
+}
+
+func formatConfig(config map[string]any) string {
+ var s strings.Builder
+
+ for _, blockName := range slices.Sorted(maps.Keys(config)) {
+ labels := config[blockName]
+
+ if labelsMap, ok := labels.(map[string]any); ok {
+ for label, block := range labelsMap {
+ if blockMap, ok := block.(map[string]any); ok {
+ s.WriteString(formatBlock(blockName, label, blockMap, 0))
+ }
+ }
+ }
+ }
+
+ return s.String()
+}
+
+func formatBlock(blockName string, label string, block map[string]any, indent int) string {
+ var s strings.Builder
+
+ s.WriteString(strings.Repeat(" ", indent))
+ s.WriteString(blockName)
+ if label != "" {
+ s.WriteString(fmt.Sprintf(` %s`, strconv.Quote(label)))
+ }
+ s.WriteString(" {\n")
+
+ var blocks []any
+ if blocksValue, exists := block["blocks"]; exists {
+ if blocksList, ok := blocksValue.([]any); ok {
+ blocks = blocksList
+ }
+ delete(block, "blocks")
+ }
+
+ for _, key := range slices.Sorted(maps.Keys(block)) {
+ s.WriteString(strings.Repeat(" ", indent+1))
+ s.WriteString(fmt.Sprintf("%s = %s\n", key, formatValue(block[key])))
+ }
+
+ for _, blockItem := range blocks {
+ if blockMap, ok := blockItem.(map[string]any); ok {
+ var name string
+ if nameValue, exists := blockMap["name"]; exists {
+ if nameStr, ok := nameValue.(string); ok {
+ name = nameStr
+ }
+ delete(blockMap, "name")
+ }
+
+ s.WriteString(formatBlock(name, "", blockMap, indent+1))
+ }
+ }
+
+ s.WriteString(strings.Repeat(" ", indent))
+ s.WriteString("}\n")
+
+ return s.String()
+}
+
+func formatValue(value any) string {
+ switch v := value.(type) {
+ case string:
+ return strconv.Quote(v)
+ case map[string]any:
+ if ref, exists := v["$ref"]; exists {
+ if refStr, ok := ref.(string); ok {
+ return refStr
+ }
+ }
+ var parts []string
+ for _, name := range slices.Sorted(maps.Keys(v)) {
+ parts = append(parts, fmt.Sprintf("%s=%s,", name, formatValue(v[name])))
+ }
+ return "{" + strings.Join(parts, " ") + "}"
+ case []any:
+ var parts []string
+ for _, item := range v {
+ parts = append(parts, formatValue(item))
+ }
+ return "[" + strings.Join(parts, ", ") + "]"
+ default:
+ return fmt.Sprintf("%v", v)
+ }
+}
diff --git a/nixos/shared/alloy-nix-config/default.nix b/nixos/shared/alloy-nix-config/default.nix
new file mode 100644
index 0000000..d4efe02
--- /dev/null
+++ b/nixos/shared/alloy-nix-config/default.nix
@@ -0,0 +1,8 @@
+{ pkgs ? import <nixpkgs> {} }:
+
+pkgs.buildGoModule {
+ pname = "alloy-nix-config";
+ version = "git";
+ src = ./.;
+ vendorHash = null;
+}
diff --git a/nixos/shared/alloy-nix-config/go.mod b/nixos/shared/alloy-nix-config/go.mod
new file mode 100644
index 0000000..2916089
--- /dev/null
+++ b/nixos/shared/alloy-nix-config/go.mod
@@ -0,0 +1,3 @@
+module push-f.com/alloy-nix-config
+
+go 1.24.5
diff --git a/nixos/shared/grafana-matrix-forwarder/default.nix b/nixos/shared/grafana-matrix-forwarder/default.nix
new file mode 100644
index 0000000..7a04dcb
--- /dev/null
+++ b/nixos/shared/grafana-matrix-forwarder/default.nix
@@ -0,0 +1,10 @@
+{ buildGoModule }:
+let
+ sources = import <top/npins>;
+in
+buildGoModule {
+ pname = "grafana-matrix-forwarder";
+ version = sources.grafana-matrix-forwarder.version;
+ src = sources.grafana-matrix-forwarder;
+ vendorHash = "sha256-ifkeakyRkIF2Y/4otUWhTvUzsPwRb1Wxx6gqN0806c4=";
+}
diff --git a/nixos/shared/grafana-matrix-forwarder/service.nix b/nixos/shared/grafana-matrix-forwarder/service.nix
new file mode 100644
index 0000000..5ad511c
--- /dev/null
+++ b/nixos/shared/grafana-matrix-forwarder/service.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+let
+ grafanaMatrixForwarder = pkgs.callPackage ./default.nix {};
+ cfg = config.services.grafana-matrix-forwarder;
+in
+{
+ options.services.grafana-matrix-forwarder = {
+ enable = lib.mkEnableOption "grafana-matrix-forwarder";
+ port = lib.mkOption {
+ type = lib.types.int;
+ };
+ homeserver = lib.mkOption {
+ type = lib.types.str;
+ };
+ environmentFile = lib.mkOption {
+ type = lib.types.path;
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ systemd.services.grafana-matrix-forwarder = {
+ serviceConfig = {
+ ExecStart = "${grafanaMatrixForwarder}/bin/grafana-matrix-forwarder --port=${toString cfg.port} --homeserver ${cfg.homeserver}";
+ EnvironmentFile = cfg.environmentFile;
+ DynamicUser = "true";
+ };
+ wantedBy = ["multi-user.target"];
+ };
+ };
+}
diff --git a/nixos/shared/monitoring.nix b/nixos/shared/monitoring.nix
new file mode 100644
index 0000000..eecad59
--- /dev/null
+++ b/nixos/shared/monitoring.nix
@@ -0,0 +1,235 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.monitoring;
+ helpers = import <top/helpers.nix> { inherit config lib pkgs; };
+in
+{
+ options.monitoring = {
+ lokiPort = lib.mkOption {
+ type = lib.types.int;
+ };
+ alloyUiPort = lib.mkOption {
+ type = lib.types.int;
+ };
+ prometheusPort = lib.mkOption {
+ type = lib.types.int;
+ };
+ prometheusNodeExporterPort = lib.mkOption {
+ type = lib.types.int;
+ };
+ prometheusScrapeConfigs = lib.mkOption {
+ type = lib.types.listOf lib.types.attrs;
+ default = [];
+ };
+ };
+ config = {
+ services.prometheus = {
+ enable = true;
+ port = cfg.prometheusPort;
+
+ retentionTime = "1y";
+
+ scrapeConfigs = [
+ {
+ job_name = "node";
+ static_configs = [{
+ targets = [ "localhost:${toString cfg.prometheusNodeExporterPort}" ];
+ }];
+ }
+ ] ++ cfg.prometheusScrapeConfigs;
+
+ exporters.node = {
+ enable = true;
+ enabledCollectors = [ "systemd" ];
+ port = cfg.prometheusNodeExporterPort;
+ };
+ };
+
+ services.loki = {
+ enable = true;
+ configuration = {
+ server.http_listen_port = cfg.lokiPort;
+ auth_enabled = false;
+
+ ingester = {
+ lifecycler = {
+ address = "127.0.0.1";
+ ring = {
+ kvstore.store = "inmemory";
+ replication_factor = 1;
+ };
+ };
+ };
+
+ schema_config = {
+ configs = [{
+ store = "tsdb";
+ object_store = "filesystem";
+ schema = "v13";
+ index = {
+ prefix = "index_";
+ period = "24h";
+ };
+ }];
+ };
+
+ storage_config = {
+ tsdb_shipper = {
+ active_index_directory = "/var/lib/loki/tsdb-active";
+ cache_location = "/var/lib/loki/tsdb-cache";
+ };
+ };
+
+ compactor = {
+ working_directory = "/var/lib/loki";
+ };
+
+ limits_config = {
+ allow_structured_metadata = true;
+ };
+ };
+ };
+
+ systemd.services.alloy = {
+ serviceConfig = {
+ SupplementaryGroups = [
+ "systemd-journal"
+ ] ++ lib.optional config.services.nginx.enable config.services.nginx.group;
+ };
+ };
+
+ services.alloy = {
+ enable = true;
+ extraFlags = ["--server.http.listen-addr=0.0.0.0:${toString cfg.alloyUiPort}"];
+ configPath =
+ let
+ ref = helpers.alloyConfigRef;
+ in
+ helpers.writeAlloyConfig {
+ "loki.source.journal".journal = {
+ max_age = "12h0m0s";
+ relabel_rules = ref "discovery.relabel.journal.rules";
+ forward_to = [(ref "loki.process.journal.receiver")];
+ labels = {
+ host = "tente";
+ job = "systemd-journal";
+ };
+ };
+ "loki.process".journal = {
+ forward_to = [(ref "loki.write.default.receiver")];
+ blocks = [
+ {
+ name = "stage.match";
+ # Select messages from systemd services that have LogExtraFields=LOG_FORMAT=logfmt.
+ selector = ''{__journal_LOG_FORMAT="logfmt"}'';
+ blocks = [
+ { name = "stage.logfmt"; mapping = { time = ""; level = ""; }; }
+ { name = "stage.timestamp"; source = "time"; format = "RFC3339"; }
+ {
+ # The slog package of the Go standard library prints levels as uppercase.
+ name = "stage.template";
+ source = "level";
+ template = "{{ ToLower .Value }}";
+ }
+ { name = "stage.structured_metadata"; values = { level = ""; }; }
+ ];
+ }
+ ];
+ };
+ "discovery.relabel".journal = {
+ targets = [];
+ blocks = [
+ {
+ name = "rule";
+ source_labels = ["__journal__systemd_unit"];
+ target_label = "unit";
+ }
+ ];
+ };
+
+ "loki.source.file".nginx_access = {
+ targets = ref "local.file_match.nginx_access.targets";
+ forward_to = [(ref "loki.process.nginx_access.receiver")];
+ };
+ "local.file_match".nginx_access = {
+ path_targets = [{
+ __path__ = "/var/log/nginx/*.access.log";
+ }];
+ };
+ "loki.process".nginx_access = {
+ forward_to = [(ref "loki.write.default.receiver")];
+ blocks = [
+ { name = "stage.static_labels"; values = { job = "nginx"; }; }
+
+ {
+ # Extracting the log file name as vhost because it's more convenient
+ # to query for than the full filename. We could also use server_name
+ # but there could be wildcard server_names and Loki labels should have
+ # a low cardinality for performance reasons.
+ name = "stage.regex";
+ source = "filename";
+ expression = "(?P<vhost>[^/]+)\\.access\\.log$";
+ }
+
+ { name = "stage.labels"; values = { vhost = ""; }; }
+ { name = "stage.json"; expressions = { msec = ""; path = ""; }; }
+ { name = "stage.timestamp"; source = "msec"; format = "Unix"; }
+ {
+ # Setting level=info to prevent Loki's log level detection from wrongly
+ # detecting messages with paths containing "error" as errors.
+ # Creating the filetype entry via stage.template because there's no
+ # static_structured_metadata stage yet. (https://github.com/grafana/loki/issues/16703)
+ name = "stage.template";
+ source = "level";
+ template = "info";
+ }
+ { name = "stage.structured_metadata"; values = { level = ""; }; }
+
+ # Temporarily adding path as a label so that we can use it in the match selectors.
+ { name = "stage.labels"; values = { path = ""; }; }
+ {
+ name = "stage.match";
+ selector = "{path=~\"/\\\\.well-known/.*\"}";
+ # Creating the filetype entry via stage.template because there's no
+ # static_structured_metadata stage yet. (https://github.com/grafana/loki/issues/16703)
+ blocks = [
+ { name = "stage.template"; source = "filetype"; template = "well-known"; }
+ ];
+ }
+ {
+ name = "stage.match";
+ selector = "{path=\"/robots.txt\"}";
+ blocks = [
+ { name = "stage.template"; source = "filetype"; template = "robots.txt"; }
+ ];
+ }
+ {
+ name = "stage.match";
+ selector = "{path=~\".*\\\\.atom$\"}";
+ blocks = [
+ { name = "stage.template"; source = "filetype"; template = "feed"; }
+ ];
+ }
+ {
+ name = "stage.structured_metadata";
+ values = { filetype = ""; };
+ }
+
+ # Dropping path again because it has a too high cardinality for a label.
+ { name = "stage.label_drop"; values = ["path"]; }
+ ];
+ };
+ "loki.write".default = {
+ blocks = [
+ {
+ name = "endpoint";
+ url = "http://127.0.0.1:${toString cfg.lokiPort}/loki/api/v1/push";
+ }
+ ];
+ external_labels = {};
+ };
+ };
+ };
+ };
+}
diff --git a/nixos/shared/postgres-collectors.nix b/nixos/shared/postgres-collectors.nix
new file mode 100644
index 0000000..fd44cb0
--- /dev/null
+++ b/nixos/shared/postgres-collectors.nix
@@ -0,0 +1,22 @@
+# collectors for the prometheus-sql-exporter
+{ config, lib, pkgs, ... }:
+
+{
+ collectorNames = ["db-sizes"];
+
+ collectors = [
+ {
+ collector_name = "db-sizes";
+ metrics = [
+ {
+ metric_name = "pg_db_size_bytes";
+ help = "disk space used by the database";
+ type = "gauge";
+ key_labels = ["database_name"];
+ values = ["size"];
+ query = "SELECT datname AS database_name, pg_database_size(datname) as size from pg_database";
+ }
+ ];
+ }
+ ];
+}
diff --git a/nixos/shared/prometheus-sql-exporter/default.nix b/nixos/shared/prometheus-sql-exporter/default.nix
index 81f1660..5d80a62 100644
--- a/nixos/shared/prometheus-sql-exporter/default.nix
+++ b/nixos/shared/prometheus-sql-exporter/default.nix
@@ -1,21 +1,15 @@
{
lib,
buildGoModule,
- fetchFromGitHub,
}:
-
-buildGoModule rec {
+let
+ sources = import <top/npins>;
+in
+buildGoModule {
pname = "sql_exporter";
- version = "0.17.1";
-
- src = fetchFromGitHub {
- owner = "burningalchemist";
- repo = pname;
- rev = version;
- sha256 = "sha256-AEPFXPplHtny1P3gMvB1gbMj10bpu9PXc6ywliF+dCc=";
- };
-
- vendorHash = "sha256-KFWDqbdbXvgEtz1nlasWrvIckpzasUdzbb+AKfXmYf8=";
+ version = sources.prometheus-sql-exporter.version;
+ src = sources.prometheus-sql-exporter;
+ vendorHash = "sha256-eZxxmqoiXPdjZs/lwbzvWco9mDFy0zmpGDcqTIyWbK4=";
meta = with lib; {
description = "Database-agnostic SQL exporter for Prometheus";
diff --git a/nixos/shared/prometheus-sql-exporter/service.nix b/nixos/shared/prometheus-sql-exporter/service.nix
index a887f91..a79528c 100644
--- a/nixos/shared/prometheus-sql-exporter/service.nix
+++ b/nixos/shared/prometheus-sql-exporter/service.nix
@@ -4,7 +4,16 @@
let
sqlExporter = pkgs.callPackage ./default.nix {};
cfg = config.services.prometheus-sql-exporter;
- configFile = builtins.toFile "config.yaml" (builtins.toJSON cfg.config);
+ configFile = builtins.toFile "config.yaml" (builtins.toJSON cfg.config);
+ validateConfig = file:
+ pkgs.runCommand "validate-config"
+ {
+ nativeBuildInputs = [sqlExporter];
+ }
+ ''
+ sql_exporter -config.check -config.file "${file}"
+ ln -s "${file}" "$out"
+ '';
in
{
options.services.prometheus-sql-exporter = {
@@ -20,7 +29,7 @@ in
config = lib.mkIf cfg.enable {
systemd.services.prometheus-sql-exporter = {
serviceConfig = {
- ExecStart = "${sqlExporter}/bin/sql_exporter -config.file ${configFile} -web.listen-address :${toString cfg.port}";
+ ExecStart = "${sqlExporter}/bin/sql_exporter -config.file ${validateConfig configFile} -web.listen-address :${toString cfg.port}";
DynamicUser = "true";
User = "prometheus-sql-exporter";
};
diff --git a/nixos/shared/vpn.nix b/nixos/shared/vpn.nix
index 59fb225..9cbcf45 100644
--- a/nixos/shared/vpn.nix
+++ b/nixos/shared/vpn.nix
@@ -2,7 +2,6 @@
{
age.secrets.vpn-se-privKey.file = ../secrets/vpn-se-privKey.age;
- age.secrets.vpn-se-presharedKey.file = ../secrets/vpn-se-presharedKey.age;
# We're creating the wireguard interfaces in network namespaces so that
# we can use them on demand:
@@ -14,15 +13,14 @@
interfaces.wg-se = {
interfaceNamespace = "se";
- ips = ["10.148.171.71/32"];
+ ips = ["10.128.241.130/32"];
privateKeyFile = config.age.secrets.vpn-se-privKey.path;
peers = [
{
- publicKey = "PyLCXAQT8KkM4T+dUsOQfn+Ub3pGxfGlxkIApuig+hk=";
- presharedKeyFile = config.age.secrets.vpn-se-presharedKey.path;
+ publicKey = "sb61ho9MhaxhJd6WSrryVmknq0r6oHEW7PP5i4lzAgM=";
allowedIPs = ["0.0.0.0/0"];
- endpoint = "se3.vpn.airdns.org:1637";
+ endpoint = "se.gw.xeovo.com:51820";
}
];
};
diff --git a/user/inkscape/pages.csv b/user/inkscape/pages.csv
new file mode 100644
index 0000000..75f8696
--- /dev/null
+++ b/user/inkscape/pages.csv
@@ -0,0 +1,47 @@
+#Inkscape page sizes
+#NAME, WIDTH, HEIGHT, UNIT
+Big embroidery hoop, 200, 280, mm
+Medium embroidery hoop, 200, 200, mm
+Small embroidery hoop, 140, 200, mm
+A4, 210, 297, mm
+A0, 841, 1189, mm
+A1, 594, 841, mm
+A2, 420, 594, mm
+A3, 297, 420, mm
+A5, 148, 210, mm
+A6, 105, 148, mm
+A7, 74, 105, mm
+A8, 52, 74, mm
+A9, 37, 52, mm
+A10, 26, 37, mm
+B0, 1000, 1414, mm
+B1, 707, 1000, mm
+B2, 500, 707, mm
+B3, 353, 500, mm
+B4, 250, 353, mm
+B5, 176, 250, mm
+B6, 125, 176, mm
+B7, 88, 125, mm
+B8, 62, 88, mm
+B9, 44, 62, mm
+B10, 31, 44, mm
+C0, 917, 1297, mm
+C1, 648, 917, mm
+C2, 458, 648, mm
+C3, 324, 458, mm
+C4, 229, 324, mm
+C5, 162, 229, mm
+C6, 114, 162, mm
+C7, 81, 114, mm
+C8, 57, 81, mm
+C9, 40, 57, mm
+C10, 28, 40, mm
+D1, 545, 771, mm
+D2, 385, 545, mm
+D3, 272, 385, mm
+D4, 192, 272, mm
+D5, 136, 192, mm
+D6, 96, 136, mm
+D7, 68, 96, mm
+ID Card (ISO 7810), 85.60, 53.98, mm
+Business Card (Europe), 85, 55, mm
diff --git a/user/jj/config.toml b/user/jj/config.toml
new file mode 100644
index 0000000..72e8c4e
--- /dev/null
+++ b/user/jj/config.toml
@@ -0,0 +1,8 @@
+"$schema" = "https://jj-vcs.github.io/jj/latest/config-schema.json"
+
+[user]
+name = "Martin Fischer"
+email = "martin@push-f.com"
+
+[ui]
+pager = ["less", "--quit-if-one-screen", "--RAW-CONTROL-CHARS"]
diff --git a/user/sway/config b/user/sway/config
index 2fafff8..3b65353 100644
--- a/user/sway/config
+++ b/user/sway/config
@@ -18,7 +18,7 @@ set $term foot
set $menu wmenu-run
### Output configuration
-exec_always <~/repos/pad/roadmap.md sed 's/(.\+)//' | set-wallpaper
+exec_always <~/repos/notes/roadmap.md sed 's/(.\+)//' | set-wallpaper
# Example configuration:
#
@@ -57,6 +57,13 @@ input "type:keyboard" {
xkb_file "~/config/colematik/colematik.xkb"
}
+input "10429:2395:UGTABLET_Artist_Pro_16_(Gen2)" {
+ map_to_output "DP-1"
+}
+input "10429:514:Hanvon_Ugee_Shortcut_Remote_Keyboard" {
+ xkb_layout "us"
+}
+
### Key bindings
#
# Basics:
@@ -82,6 +89,8 @@ input "type:keyboard" {
# Exit sway (logs you out of your Wayland session)
bindsym $mod+Shift+q exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -B 'Yes, exit sway' 'swaymsg exit'
+
+ bindsym $mod+m exec foot zsh -c 'cd ~/repos/notes && ed --min-query-length 1'
#
# Moving around:
#
diff --git a/user/vim/vimrc b/user/vim/vimrc
index b65deb0..952f863 100644
--- a/user/vim/vimrc
+++ b/user/vim/vimrc
@@ -1,4 +1,23 @@
syntax on
+" Don't break words in the middle.
+set linebreak
+
+" Enable easy copying between different vim instances.
+set clipboard=unnamed
+
+" Wayland clipboard workaround (see https://github.com/vim/vim/issues/5157).
+if !empty($WAYLAND_DISPLAY) && executable('wl-copy') && executable('wl-paste')
+ " enable focus reporting
+ let &t_fe = "\<Esc>[?1004h"
+ let &t_fd = "\<Esc>[?1004l"
+
+ augroup wl-clipboard
+ autocmd!
+ autocmd FocusLost * if @" != '' | call system('wl-copy --trim-newline', @") | endif
+ autocmd FocusGained * let @" = system('wl-paste -n')
+ augroup END
+endif
+
" Enable pasting with middle mouse click in insert mode.
set mouse=
diff --git a/user/zed/settings.json b/user/zed/settings.json
index 8614205..d2945a4 100644
--- a/user/zed/settings.json
+++ b/user/zed/settings.json
@@ -25,6 +25,24 @@
}
]
},
+ // By default Zed autoformats a lot of file formats (e.g. HTML, TOML, SQL) with
+ // formatters that aren't used by most projects. So I'm turning format_on_save
+ // off by default and then on for the programming languages that I use.
+ "format_on_save": "off",
+ "languages": {
+ "Go": {
+ "format_on_save": "on"
+ },
+ "Nix": {
+ "language_servers": ["nixd"]
+ },
+ "Rust": {
+ "format_on_save": "on"
+ },
+ "TypeScript": {
+ "format_on_save": "on"
+ }
+ },
"lsp": {
"rust-analyzer": {
"binary": {
@@ -52,7 +70,7 @@
"features": {
"edit_prediction_provider": "none"
},
- "assistant": {
+ "agent": {
"enabled": false,
"version": "2"
},
@@ -61,7 +79,7 @@
"button": false
},
"chat_panel": {
- "button": false
+ "button": "never"
},
"notification_panel": {
"button": false
diff --git a/user/zsh/.zshenv b/user/zsh/.zshenv
new file mode 100644
index 0000000..56a09b1
--- /dev/null
+++ b/user/zsh/.zshenv
@@ -0,0 +1 @@
+export VISUAL=vim
diff --git a/user/zsh/.zshrc b/user/zsh/.zshrc
index bc9fe0c..aa65059 100644
--- a/user/zsh/.zshrc
+++ b/user/zsh/.zshrc
@@ -48,7 +48,6 @@ SAVEHIST=1000000000
export XDG_CACHE_HOME=$HOME/.cache
export XDG_CONFIG_HOME=$HOME/.config
-export VISUAL=vim
export INPUTRC=$XDG_CONFIG_HOME/readline/inputrc
export PYTHON_BASIC_REPL=1 # use readline (Python >=3.13 defaults to its own REPL which has no vi keybindings)
diff --git a/user/zsh/zshrc-workstation.sh b/user/zsh/zshrc-workstation.sh
index e027208..93ac430 100644
--- a/user/zsh/zshrc-workstation.sh
+++ b/user/zsh/zshrc-workstation.sh
@@ -3,7 +3,7 @@
## Aliases
alias code='codium'
alias zed=zeditor
-alias tokei='tokei -s code -n commas'
+alias scc='scc --no-cocomo --no-size --no-gen --sort code'
## Environment variables
export CARGO_HOME="$HOME/.local/share/cargo"