aboutsummaryrefslogtreecommitdiffstats
path: root/modules/system/services
diff options
context:
space:
mode:
Diffstat (limited to 'modules/system/services')
-rw-r--r--modules/system/services/adb/default.nix22
-rw-r--r--modules/system/services/backup/default.nix49
-rw-r--r--modules/system/services/dconf/default.nix7
-rw-r--r--modules/system/services/default.nix19
-rw-r--r--modules/system/services/fwupd/default.nix14
-rw-r--r--modules/system/services/issue_file/default.nix38
-rw-r--r--modules/system/services/nix/default.nix53
-rw-r--r--modules/system/services/openssh/default.nix15
-rw-r--r--modules/system/services/postgresql/default.nix17
-rw-r--r--modules/system/services/printing/default.nix45
-rw-r--r--modules/system/services/scanning/default.nix25
-rw-r--r--modules/system/services/serverphone/certificates/ca.crt10
-rw-r--r--modules/system/services/serverphone/certificates/server.crt10
-rw-r--r--modules/system/services/serverphone/default.nix49
l---------modules/system/services/serverphone/keys/key_11
l---------modules/system/services/serverphone/keys/key_21
-rw-r--r--modules/system/services/snapper/default.nix53
-rw-r--r--modules/system/services/steam/default.nix23
-rw-r--r--modules/system/services/swaylock/default.nix4
-rw-r--r--modules/system/services/xdg/default.nix58
-rwxr-xr-xmodules/system/services/xdg/scripts/lf_wrapper.sh79
-rwxr-xr-xmodules/system/services/xdg/scripts/ranger_wrapper.sh68
22 files changed, 660 insertions, 0 deletions
diff --git a/modules/system/services/adb/default.nix b/modules/system/services/adb/default.nix
new file mode 100644
index 00000000..4055dbb1
--- /dev/null
+++ b/modules/system/services/adb/default.nix
@@ -0,0 +1,22 @@
+{
+ lib,
+ config,
+ ...
+}: let
+ cfg = config.soispha.adb;
+in {
+ options.soispha.adb = {
+ enable = lib.mkEnableOption "Android adb bridge";
+ user = lib.mkOption {
+ type = lib.types.str;
+ example = "soispha";
+ default = "soispha";
+ description = "Username to grant access to adb bridge";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ programs.adb.enable = true;
+ users.users."${cfg.user}".extraGroups = ["adbusers"];
+ };
+}
diff --git a/modules/system/services/backup/default.nix b/modules/system/services/backup/default.nix
new file mode 100644
index 00000000..705dcf23
--- /dev/null
+++ b/modules/system/services/backup/default.nix
@@ -0,0 +1,49 @@
+{
+ lib,
+ pkgs,
+ config,
+ ...
+}: let
+ backup-script = pkgs.writeShellScriptBin "backsnap" ''
+ ${pkgs.util-linux}/bin/mount --mkdir "/dev/disk/by-uuid/${cfg.backupDiskUuid}" "/run/media/${cfg.backupDiskUuid}";
+ ${pkgs.snap-sync-forked}/bin/snap-sync-forked --UUID "${cfg.backupDiskUuid}" --noconfirm;
+ ${pkgs.util-linux}/bin/umount "/run/media/${cfg.backupDiskUuid}";
+ '';
+
+ cfg = config.soispha.backup;
+in {
+ options.soispha.backup = {
+ enable = lib.mkEnableOption "backups with my forked snap-sync";
+ backupDiskUuid = lib.mkOption {
+ type = lib.types.str;
+ example = lib.literalExpression "d1d20ae7-3d8a-44da-86da-677dbbb10c89";
+ description = "The UUID of the backup disk";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ systemd = {
+ services.backup = {
+ wantedBy = lib.mkForce [];
+ unitConfig = {
+ Description = "Backup the last snapshots of the persitent-storage subvolume.";
+ };
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${backup-script}/bin/backsnap";
+ };
+ };
+
+ timers.backup = {
+ wantedBy = ["timers.target"];
+ unitConfig = {
+ Description = "Backup 15min after boot and every 8 hours";
+ };
+ timerConfig = {
+ OnBootSec = "15min";
+ OnUnitActiveSec = "8h";
+ };
+ };
+ };
+ };
+}
diff --git a/modules/system/services/dconf/default.nix b/modules/system/services/dconf/default.nix
new file mode 100644
index 00000000..f6598a9b
--- /dev/null
+++ b/modules/system/services/dconf/default.nix
@@ -0,0 +1,7 @@
+{...}: {
+ # needed to make home-manager play nice with some apps. See:
+ # https://nix-community.github.io/home-manager/index.xhtml#_why_do_i_get_an_error_message_about_literal_ca_desrt_dconf_literal_or_literal_dconf_service_literal
+ programs.dconf.enable = true;
+ # FIXME: This should also be parameterized. <2024-05-16>
+}
+# vim: nolinebreak nowrap textwidth=0
diff --git a/modules/system/services/default.nix b/modules/system/services/default.nix
new file mode 100644
index 00000000..76ef26e2
--- /dev/null
+++ b/modules/system/services/default.nix
@@ -0,0 +1,19 @@
+{...}: {
+ imports = [
+ #./serverphone
+ ./adb
+ ./backup
+ ./dconf
+ ./fwupd
+ ./issue_file
+ ./nix
+ ./openssh
+ ./postgresql
+ ./printing
+ ./scanning
+ ./snapper
+ ./steam
+ ./swaylock
+ ./xdg
+ ];
+}
diff --git a/modules/system/services/fwupd/default.nix b/modules/system/services/fwupd/default.nix
new file mode 100644
index 00000000..5ad4f467
--- /dev/null
+++ b/modules/system/services/fwupd/default.nix
@@ -0,0 +1,14 @@
+{
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.soispha.services.fwupd;
+in {
+ options.soispha.services.fwupd = {
+ enable = lib.mkEnableOption "fwupd";
+ };
+ config = lib.mkIf cfg.enable {
+ services.fwupd.enable = true;
+ };
+}
diff --git a/modules/system/services/issue_file/default.nix b/modules/system/services/issue_file/default.nix
new file mode 100644
index 00000000..930be1d9
--- /dev/null
+++ b/modules/system/services/issue_file/default.nix
@@ -0,0 +1,38 @@
+{config, ...}: {
+ environment.etc.issue = {
+ # Friendly greeting on the virtual consoles.
+ text = ''
+ [?25l[?7l 
+  ▗▄▄▄ ▗▄▄▄▄ ▄▄▄▖ 
+  ▜███▙ ▜███▙ ▟███▛ 
+  ▜███▙ ▜███▙▟███▛ 
+  ▜███▙ ▜██████▛ 
+  ▟█████████████████▙ ▜████▛ ▟▙ 
+  ▟███████████████████▙ ▜███▙ ▟██▙ 
+  ▄▄▄▄▖ ▜███▙ ▟███▛ 
+  ▟███▛ ▜██▛ ▟███▛ 
+  ▟███▛ ▜▛ ▟███▛ 
+ ▟███████████▛ ▟██████████▙
+ ▜██████████▛ ▟███████████▛
+  ▟███▛ ▟▙ ▟███▛ 
+  ▟███▛ ▟██▙ ▟███▛ 
+  ▟███▛ ▜███▙ ▝▀▀▀▀ 
+  ▜██▛ ▜███▙ ▜██████████████████▛ 
+  ▜▛ ▟████▙ ▜████████████████▛ 
+  ▟██████▙ ▜███▙ 
+  ▟███▛▜███▙ ▜███▙ 
+  ▟███▛ ▜███▙ ▜███▙ 
+  ▝▀▀▀ ▀▀▀▀▘ ▀▀▀▘ 
+  
+  NixOS ${config.system.nixos.label} 
+  --------------
+ 
+   date: \d
+   time: \t
+   ipv4: \4
+   ipv6: \6
+   tty: \l
+ 
+ '';
+ };
+}
diff --git a/modules/system/services/nix/default.nix b/modules/system/services/nix/default.nix
new file mode 100644
index 00000000..65fc7273
--- /dev/null
+++ b/modules/system/services/nix/default.nix
@@ -0,0 +1,53 @@
+{
+ pkgs,
+
+ # flakes
+ nixpkgs_as_input,
+ templates,
+ self,
+ ...
+}: {
+ nix = {
+ package = pkgs.nixVersions.latest;
+
+ # Disable nix channels (this is a remnant of old days)
+ channel.enable = false;
+
+ registry = {
+ nixpkgs.flake = nixpkgs_as_input;
+ n.flake = self; # Otherwise the nixpkgs config is not available
+
+ t.flake = templates;
+
+ my_flake.flake = self;
+ m.flake = self;
+ };
+
+ gc = {
+ automatic = true;
+ dates = "weekly";
+ options = "--delete-older-than 7d";
+ };
+
+ settings = {
+ auto-optimise-store = true;
+ experimental-features = [
+ "nix-command"
+ "flakes"
+ #"ca-derivations"
+ ];
+
+ use-xdg-base-directories = true;
+
+ #substituters = ["https://cache.ngi0.nixos.org/"];
+ #trusted-public-keys = ["cache.ngi0.nixos.org-1:KqH5CBLNSyX184S9BKZJo1LxrxJ9ltnY2uAs5c/f1MA="];
+
+ fallback = true; # Build from source, if binary can't be substituted
+
+ keep-failed = true; # keep failed tmp build dirs
+ pure-eval = true; # restrict file system and network access to hash
+
+ sandbox-fallback = false; # Don't disable the sandbox, if the kernel doesn't support it
+ };
+ };
+}
diff --git a/modules/system/services/openssh/default.nix b/modules/system/services/openssh/default.nix
new file mode 100644
index 00000000..b733dbe7
--- /dev/null
+++ b/modules/system/services/openssh/default.nix
@@ -0,0 +1,15 @@
+{...}: {
+ services.openssh = {
+ enable = true;
+ hostKeys = [
+ {
+ path = "/srv/sshd/ssh_host_ed25519_key";
+ rounds = 1000;
+ type = "ed25519";
+ }
+ ];
+ settings = {
+ PasswordAuthentication = false;
+ };
+ };
+}
diff --git a/modules/system/services/postgresql/default.nix b/modules/system/services/postgresql/default.nix
new file mode 100644
index 00000000..c47a235c
--- /dev/null
+++ b/modules/system/services/postgresql/default.nix
@@ -0,0 +1,17 @@
+{
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.soispha.services.postgresql;
+in {
+ options.soispha.services.postgresql = {
+ enable = lib.mkEnableOption "postgresql";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.postgresql = {
+ enable = true;
+ };
+ };
+}
diff --git a/modules/system/services/printing/default.nix b/modules/system/services/printing/default.nix
new file mode 100644
index 00000000..85d15b16
--- /dev/null
+++ b/modules/system/services/printing/default.nix
@@ -0,0 +1,45 @@
+{
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.soispha.services.printing;
+in {
+ options.soispha.services.printing = {
+ enable = lib.mkEnableOption "default printing configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.avahi = {
+ enable = true;
+ nssmdns4 = true;
+ nssmdns6 = true;
+ openFirewall = true;
+ };
+
+ services.printing = {
+ enable = true;
+ startWhenNeeded = true;
+ webInterface = true;
+
+ # deletes `/var/cache/cups`, `/var/lib/cups` and `/var/spool/cups` on cups startup
+ stateless = true;
+
+ drivers = [];
+ };
+
+ hardware = {
+ printers = {
+ ensurePrinters = [
+ {
+ name = "Brother";
+ description = "Brother DCP-9022CDW";
+ model = "everywhere";
+ deviceUri = "dnssd://Brother%20DCP-9022CDW._ipp._tcp.local/?uuid=e3248000-80ce-11db-8000-30055c773bcf";
+ }
+ ];
+ ensureDefaultPrinter = "Brother";
+ };
+ };
+ };
+}
diff --git a/modules/system/services/scanning/default.nix b/modules/system/services/scanning/default.nix
new file mode 100644
index 00000000..dda507fa
--- /dev/null
+++ b/modules/system/services/scanning/default.nix
@@ -0,0 +1,25 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}: let
+ cfg = config.soispha.services.scanning;
+in {
+ options.soispha.services.scanning = {
+ enable = lib.mkEnableOption "default scanning configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ hardware = {
+ sane = {
+ enable = true;
+ extraBackends = [pkgs.sane-airscan];
+ };
+ };
+
+ users.users.soispha.extraGroups = [
+ "scanner" # for permission to access the scanner.
+ ];
+ };
+}
diff --git a/modules/system/services/serverphone/certificates/ca.crt b/modules/system/services/serverphone/certificates/ca.crt
new file mode 100644
index 00000000..7a4ae6f9
--- /dev/null
+++ b/modules/system/services/serverphone/certificates/ca.crt
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBXDCCAQOgAwIBAgIIRQ2wXiaD5pMwCgYIKoZIzj0EAwIwGTEXMBUGA1UEAwwO
+U2VydmVycGhvbmUgQ0EwHhcNMjMwNjA2MTIzNzM3WhcNMzMwNjAzMTIzNzM3WjAZ
+MRcwFQYDVQQDDA5TZXJ2ZXJwaG9uZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH
+A0IABDZMtz3liWniBedisStXDO2sxFCKBH239ezH7uADu8g5peGssmNu1rXEDrg1
+sFwVUjQeJAocYYNoUeHiVpODf1ejNTAzMB0GA1UdDgQWBBST5oMmXrANRbCLIQpN
+W7e5uSCL3DASBgNVHRMBAf8ECDAGAQH/AgEBMAoGCCqGSM49BAMCA0cAMEQCIFig
+xA3MvRNP4uXaUEWwdP1pYL/R8N46G4NZrPEfiNV4AiA+NJSTFRCOUqEsvSb7PTFx
+YuMuJF4XxWnmStz3ym7xXA==
+-----END CERTIFICATE-----
diff --git a/modules/system/services/serverphone/certificates/server.crt b/modules/system/services/serverphone/certificates/server.crt
new file mode 100644
index 00000000..f994cdc8
--- /dev/null
+++ b/modules/system/services/serverphone/certificates/server.crt
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBTjCB9KADAgECAgkAhKrdjsoiOrkwCgYIKoZIzj0EAwIwGTEXMBUGA1UEAwwO
+U2VydmVycGhvbmUgQ0EwHhcNMjMwNjA2MTIzOTIwWhcNMjQwNjA1MTIzOTIwWjAm
+MSQwIgYDVQQDDBtDbGllbnQgcnVubmluZyBvbiBsb2NhbGhvc3QwWTATBgcqhkjO
+PQIBBggqhkjOPQMBBwNCAAS1ILQo8ae8ydqFlt5RncUT7joQiozk6Omunb0vxVz5
+toJRDmVqc1s6KhpCTipUV5coTcaK1TBz0+fft+9VH7cwoxgwFjAUBgNVHREEDTAL
+gglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDSQAwRgIhAN7ohtsBLrjlgmSe9ngovxZM
+z61n0+/7w2mtX/OrLMWIAiEAu+D2S2o0s7E9pp2Rkug8cT5T4GCWgFgEHk5x2L/E
+RVI=
+-----END CERTIFICATE-----
diff --git a/modules/system/services/serverphone/default.nix b/modules/system/services/serverphone/default.nix
new file mode 100644
index 00000000..20125a75
--- /dev/null
+++ b/modules/system/services/serverphone/default.nix
@@ -0,0 +1,49 @@
+{
+ config,
+ serverphone,
+ system,
+ lib,
+ ...
+}: {
+ config = lib.mkIf config.soispha.secrets.enable {
+ services.serverphone = {
+ package = "${serverphone.packages.${system}.default}";
+ enable = true;
+ domain = "localhost";
+ configureDoas = true;
+ acceptedSshKeys = [
+ "AAAAC3NzaC1lZDI1NTE5AAAAIGBFuTNNn71Rhfnop2cdz3r/RhWWlCePnSBOhTBbu2ME"
+ ];
+ authorized = {
+ acceptedGpgKeys = [
+ {
+ source = ./keys/key_1;
+ trust = "ultimate";
+ }
+ {
+ source = ./keys/key_2;
+ trust = "ultimate";
+ }
+ ];
+ };
+ caCertificate = "${./certificates/ca.crt}";
+ certificate = "${./certificates/server.crt}";
+ privateKey = config.age.secrets.serverphoneServer.path;
+ certificateRequest = {
+ acceptedUsers = [
+ "soispha $argon2id$v=19$m=19456,t=2,p=1$EvhPENIBqL5b1RO5waNMWA$pJ8vDrCNJKDlqwB5bVDLjHVPEXm9McQhtt9OXSD8Zkc"
+ ];
+ caPrivateKey = config.age.secrets.serverphoneCa.path;
+ };
+ };
+
+ users.users.serverphone = {
+ group = "serverphone";
+ isSystemUser = true;
+ home = "/run/serverphone";
+ };
+ users.groups.serverphone = {
+ members = ["serverphone"];
+ };
+ };
+}
diff --git a/modules/system/services/serverphone/keys/key_1 b/modules/system/services/serverphone/keys/key_1
new file mode 120000
index 00000000..67720882
--- /dev/null
+++ b/modules/system/services/serverphone/keys/key_1
@@ -0,0 +1 @@
+../../../../home-manager/soispha/config/gpg/keys/key_1 \ No newline at end of file
diff --git a/modules/system/services/serverphone/keys/key_2 b/modules/system/services/serverphone/keys/key_2
new file mode 120000
index 00000000..24df7207
--- /dev/null
+++ b/modules/system/services/serverphone/keys/key_2
@@ -0,0 +1 @@
+../../../../home-manager/soispha/config/gpg/keys/key_2 \ No newline at end of file
diff --git a/modules/system/services/snapper/default.nix b/modules/system/services/snapper/default.nix
new file mode 100644
index 00000000..bf8201a4
--- /dev/null
+++ b/modules/system/services/snapper/default.nix
@@ -0,0 +1,53 @@
+{
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.soispha.services.snapper;
+in {
+ options.soispha.services.snapper = {
+ enable = lib.mkEnableOption "snapper config";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.snapper = {
+ configs = {
+ srv = {
+ SUBVOLUME = "/srv";
+ FSTYPE = "btrfs";
+ # users and groups allowed to work with config
+ ALLOW_GROUPS = ["wheel"];
+
+ # sync users and groups from ALLOW_USERS and ALLOW_GROUPS to .snapshots
+ # directory
+ SYNC_ACL = true;
+
+ # run daily number cleanup
+ NUMBER_CLEANUP = false;
+
+ # limit for number cleanup
+ NUMBER_MIN_AGE = 1800;
+ NUMBER_LIMIT = 50;
+ NUMBER_LIMIT_IMPORTANT = 10;
+
+ # create hourly snapshots
+ TIMELINE_CREATE = true;
+
+ # cleanup hourly snapshots after some time
+ TIMELINE_CLEANUP = true;
+
+ # limits for timeline cleanup
+ TIMELINE_MIN_AGE = 1800;
+ TIMELINE_LIMIT_HOURLY = 7;
+ TIMELINE_LIMIT_DAILY = 3;
+ TIMELINE_LIMIT_WEEKLY = 2;
+ TIMELINE_LIMIT_MONTHLY = 0;
+ TIMELINE_LIMIT_YEARLY = 2;
+
+ # cleanup empty pre-post-pairs
+ EMPTY_PRE_POST_CLEANUP = true;
+ };
+ };
+ };
+ };
+}
diff --git a/modules/system/services/steam/default.nix b/modules/system/services/steam/default.nix
new file mode 100644
index 00000000..6e507fd9
--- /dev/null
+++ b/modules/system/services/steam/default.nix
@@ -0,0 +1,23 @@
+{
+ lib,
+ config,
+ pkgs,
+ ...
+}: let
+ cfg = config.soispha.services.steam;
+in {
+ options.soispha.services.steam = {
+ enable = lib.mkEnableOption "Stream";
+ };
+
+ config = lib.mkIf cfg.enable {
+ programs.steam = {
+ enable = true;
+ };
+
+ environment.systemPackages = [
+ # TODO: Why is this package needed? <2024-05-16>
+ pkgs.wineWowPackages.waylandFull
+ ];
+ };
+}
diff --git a/modules/system/services/swaylock/default.nix b/modules/system/services/swaylock/default.nix
new file mode 100644
index 00000000..6cbcef28
--- /dev/null
+++ b/modules/system/services/swaylock/default.nix
@@ -0,0 +1,4 @@
+{...}: {
+ # otherwise swaylock can't access the user password.
+ security.pam.services.swaylock = {};
+}
diff --git a/modules/system/services/xdg/default.nix b/modules/system/services/xdg/default.nix
new file mode 100644
index 00000000..5140a832
--- /dev/null
+++ b/modules/system/services/xdg/default.nix
@@ -0,0 +1,58 @@
+{
+ pkgs,
+ nixpkgs_open_prs,
+ sysLib,
+ system,
+ ...
+}: let
+ pkgs_tfc = nixpkgs_open_prs.nixpkgs-tfc.legacyPackages."${system}";
+in {
+ services.dbus.enable = true;
+ xdg = {
+ portal = {
+ enable = true;
+ termfilechooser = {
+ enable = true;
+ logLevel = "TRACE";
+ package = pkgs_tfc.xdg-desktop-portal-termfilechooser;
+ settings = {
+ filechooser = {
+ default_dir = "/tmp";
+ cmd = "${sysLib.writeShellScript {
+ src = ./scripts/lf_wrapper.sh;
+ name = "lf_wrapper";
+ keepPath = true;
+ dependencies = with pkgs; [
+ lf
+ alacritty
+ bash
+ ];
+ }}/bin/lf_wrapper";
+ };
+ };
+ };
+ wlr = {
+ enable = true;
+ };
+ config = {
+ common = {
+ # NOTE: The next entry is supposedly needed for gtk based apps <2023-08-31>
+ default = ["wlr" "gtk"];
+ "org.freedesktop.impl.portal.FileChooser" = ["termfilechooser"];
+ };
+
+ # TODO: Also activate, when on another wlr-based compositor <2023-11-25>
+ river = {
+ default = ["wlr" "gtk"];
+ "org.freedesktop.impl.portal.FileChooser" = ["termfilechooser"];
+ };
+ };
+ extraPortals = [
+ pkgs.xdg-desktop-portal-gtk
+ pkgs.xdg-desktop-portal-wlr
+ pkgs_tfc.xdg-desktop-portal-termfilechooser
+ ];
+ };
+ };
+ # TODO: mime = {};
+}
diff --git a/modules/system/services/xdg/scripts/lf_wrapper.sh b/modules/system/services/xdg/scripts/lf_wrapper.sh
new file mode 100755
index 00000000..16603fe4
--- /dev/null
+++ b/modules/system/services/xdg/scripts/lf_wrapper.sh
@@ -0,0 +1,79 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+# This wrapper script is invoked by xdg-desktop-portal-termfilechooser.
+#
+# Inputs:
+# 1. "1" if multiple files can be chosen, "0" otherwise.
+# 2. "1" if a directory should be chosen, "0" otherwise.
+# 3. "0" if opening files was requested, "1" if writing to a file was
+# requested. For example, when uploading files in Firefox, this will be "0".
+# When saving a web page in Firefox, this will be "1".
+# 4. If writing to a file, this is recommended path provided by the caller. For
+# example, when saving a web page in Firefox, this will be the recommended
+# path Firefox provided, such as "~/Downloads/webpage_title.html".
+# Note that if the path already exists, we keep appending "_" to it until we
+# get a path that does not exist.
+# 5. The output path, to which results should be written.
+#
+# Output:
+# The script should print the selected paths to the output path (argument #5),
+# one path per line.
+# If nothing is printed, then the operation is assumed to have been canceled.
+
+multiple="$1"
+directory="$2"
+save="$3"
+recommended_path="$4"
+out="$5"
+
+# echo > /tmp/stdout
+# echo > /tmp/stderr
+#
+# exec 1>> /tmp/stdout
+# exec 2>> /tmp/stderr
+
+cmd="$(command -v lf)"
+termcmd="${TERMINAL:-$(command -v alacritty)}"
+
+if [ "$save" = "1" ]; then
+ set -- -selection-path="$out" -command='set promptfmt "Select the file to write to %S \033[32;1m%u@%h\033[0m:\033[34;1m%d\033[0m\033[1m%f\033[0m"' "$recommended_path"
+ cat <<EOF >"$recommended_path"
+xdg-desktop-portal-termfilechooser saving files tutorial
+
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+!!! === WARNING! === !!!
+!!! The contents of *whatever* file you open last in !!!
+!!! lf will be *overwritten*! !!!
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+Instructions:
+1) Move this file wherever you want.
+2) Rename the file if needed.
+3) Confirm your selection by opening the file, for
+ example by pressing <Enter>.
+
+Notes:
+1) This file is provided for your convenience. You
+ could delete it and choose another file to overwrite
+ that, for example.
+2) If you quit lf without opening a file, this file
+ will be removed and the save operation aborted.
+EOF
+
+elif [ "$directory" = "1" ]; then
+ set -- -selection-path="$out" -command='set dironly' -command='set promptfmt "Select directory (quit in dir to select it) %S \033[32;1m%u@%h\033[0m:\033[34;1m%d\033[0m\033[1m%f\033[0m"'
+elif [ "$multiple" = "1" ]; then
+ set -- -selection-path="$out" -command='set promptfmt "Select file(s) (open file to select it; <Space> to select multiple) %S \033[32;1m%u@%h\033[0m:\033[34;1m%d\033[0m\033[1m%f\033[0m"'
+else
+ set -- -selection-path="$out" -command='set promptfmt "Select file (open file to select it) %S \033[32;1m%u@%h\033[0m:\033[34;1m%d\033[0m\033[1m%f\033[0m"'
+fi
+
+"$termcmd" --title 'floating please' -e "$cmd" "$@"
+
+if [ "$save" = "1" ] && [ ! -s "$out" ]; then
+ rm "$recommended_path"
+fi
+# vim: ft=sh
diff --git a/modules/system/services/xdg/scripts/ranger_wrapper.sh b/modules/system/services/xdg/scripts/ranger_wrapper.sh
new file mode 100755
index 00000000..e148bf19
--- /dev/null
+++ b/modules/system/services/xdg/scripts/ranger_wrapper.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+# This wrapper script is invoked by xdg-desktop-portal-termfilechooser.
+#
+# Inputs:
+# 1. "1" if multiple files can be chosen, "0" otherwise.
+# 2. "1" if a directory should be chosen, "0" otherwise.
+# 3. "0" if opening files was requested, "1" if writing to a file was
+# requested. For example, when uploading files in Firefox, this will be "0".
+# When saving a web page in Firefox, this will be "1".
+# 4. If writing to a file, this is recommended path provided by the caller. For
+# example, when saving a web page in Firefox, this will be the recommended
+# path Firefox provided, such as "~/Downloads/webpage_title.html".
+# Note that if the path already exists, we keep appending "_" to it until we
+# get a path that does not exist.
+# 5. The output path, to which results should be written.
+#
+# Output:
+# The script should print the selected paths to the output path (argument #5),
+# one path per line.
+# If nothing is printed, then the operation is assumed to have been canceled.
+
+multiple="$1"
+directory="$2"
+save="$3"
+path="$4"
+out="$5"
+
+cmd="$(command -v ranger)"
+termcmd="${TERMCMD:-$(command -v kitty)}"
+
+if [ "$save" = "1" ]; then
+ set -- --choosefile="$out" --cmd='echo Select save path (see tutorial in preview pane; try pressing zv or zp if no preview)' "$path"
+ printf '%s' 'xdg-desktop-portal-termfilechooser saving files tutorial
+
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+!!! === WARNING! === !!!
+!!! The contents of *whatever* file you open last in !!!
+!!! ranger will be *overwritten*! !!!
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+Instructions:
+1) Move this file wherever you want.
+2) Rename the file if needed.
+3) Confirm your selection by opening the file, for
+ example by pressing <Enter>.
+
+Notes:
+1) This file is provided for your convenience. You
+ could delete it and choose another file to overwrite
+ that, for example.
+2) If you quit ranger without opening a file, this file
+ will be removed and the save operation aborted.
+' >"$path"
+elif [ "$directory" = "1" ]; then
+ set -- --choosedir="$out" --show-only-dirs --cmd="echo Select directory (quit in dir to select it)"
+elif [ "$multiple" = "1" ]; then
+ set -- --choosefiles="$out" --cmd="echo Select file(s) (open file to select it; <Space> to select multiple)"
+else
+ set -- --choosefile="$out" --cmd="echo Select file (open file to select it)"
+fi
+
+"$termcmd" -- "$cmd" "$@"
+if [ "$save" = "1" ] && [ ! -s "$out" ]; then
+ rm "$path"
+fi