Add jirorian Nostr image upload service

Adds the jirorian NixOS module (systemd service, sops secrets, Caddy
fragment), wires it into the flake as a Codeberg input, fixes the Caddy
glob import path, and encrypts all five R2/Nostr secrets into bucur.yaml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
randogoth 2026-06-08 10:09:30 +03:00
parent 3ae266293f
commit e8dcc49320
6 changed files with 88 additions and 51 deletions

3
.gitignore vendored
View file

@ -7,4 +7,5 @@
/result /result
/result/ /result/
/*.qcow2 /*.qcow2
/server.md /server.md
r2-upload-api.md

View file

@ -1,47 +0,0 @@
Yes — but you need **NixOS**, not just “minimal Nix.” Heres the pragmatic path based on your flake:
**Whats missing right now**
- There is **no hardware config** in the repo. You must generate one on the server and include it.
- `sops-nix` expects the **age key file** `secrets/age/keys.txt` to exist **in your local checkout at build time** (its ignored, so you must place it manually).
**Recommended install flow (fresh NixOS install)**
1. Boot the NixOS minimal ISO on the server and partition/mount as usual.
2. Generate hardware config:
```bash
nixos-generate-config --root /mnt
```
3. Clone this repo into `/mnt/etc/nixos` (or `/mnt/etc/nixos/bucur`).
4. Move the generated hardware file into the repo:
```bash
mv /mnt/etc/nixos/hardware-configuration.nix /mnt/etc/nixos/hosts/bucur-hardware.nix
```
5. Add it to the flake modules list and ignore it:
- Add `./hosts/bucur-hardware.nix` to `modules` in `flake.nix`.
- Add `/hosts/bucur-hardware.nix` to `.gitignore`.
6. Place your age key in the repo (localonly):
```bash
install -d -m 700 /mnt/etc/nixos/secrets/age
install -m 600 /path/to/keys.txt /mnt/etc/nixos/secrets/age/keys.txt
```
7. Install using the flake:
```bash
nixos-install --flake /mnt/etc/nixos#bucur
```
8. Reboot, then SSH in as `tobias` using the key in `modules/users/tobias.nix`.
**Alternative (remote rebuild on existing NixOS)**
- If the server is already running NixOS and you can SSH:
```bash
nixos-rebuild switch --flake /path/to/repo#bucur
```
**Important gotchas**
- `services.openssh` disables passwords, so you must have the correct SSH key in `modules/users/tobias.nix`.
- `sops-nix` will fail unless `secrets/age/keys.txt` exists in the checkout used to build.
- `server.md` is ignored and no longer part of the repo/history.
If you want, I can:
1. Add a `hosts/bucur-hardware.nix` placeholder + `.gitignore` entry now.
2. Give you a copypaste install script tailored to your disk layout.
Tell me if the server is **fresh** or already running NixOS, and how you want to deploy (local install vs remote rebuild).

View file

@ -29,6 +29,9 @@
zonetoast.url = "git+ssh://git@codeberg.org/randogoth/zonetoast.git"; zonetoast.url = "git+ssh://git@codeberg.org/randogoth/zonetoast.git";
zonetoast.flake = false; zonetoast.flake = false;
jirorian.url = "git+ssh://git@codeberg.org/randogoth/jirorian.git";
jirorian.inputs.nixpkgs.follows = "nixpkgs";
}; };
outputs = { self, nixpkgs, sops-nix, ... }@inputs: outputs = { self, nixpkgs, sops-nix, ... }@inputs:
@ -52,6 +55,7 @@
./modules/services/podman.nix ./modules/services/podman.nix
./modules/services/static-sites ./modules/services/static-sites
./modules/services/webhook-deploy.nix ./modules/services/webhook-deploy.nix
./modules/services/jirorian.nix
./modules/users/tobias.nix ./modules/users/tobias.nix
]; ];
}; };

View file

@ -72,6 +72,6 @@ relay.otherwhere.app {
reverse_proxy localhost:8880 reverse_proxy localhost:8880
} }
import Caddyfile.d/*.caddyfile import /etc/caddy/Caddyfile.d/*.caddyfile
''; '';
} }

View file

@ -0,0 +1,74 @@
{ pkgs, inputs, config, ... }:
let
# Public domain for this service — set before deploying.
domain = "upload.otherwhere.app";
# Non-secret runtime configuration.
port = 8390;
relayUrl = "wss://relay.otherwhere.app/";
cdnUrl = "https://cdn.otherwhere.app";
bucketName = "media";
pkg = inputs.jirorian.packages.${pkgs.system}.default;
# Writes an EnvironmentFile from sops-managed secret files at service start.
makeEnv = pkgs.writeShellScript "jirorian-make-env" ''
set -euo pipefail
install -m 0600 /dev/null /run/jirorian/env
{
echo "R2_ACCESS_KEY_ID=$(cat ${config.sops.secrets.jirorian_r2_access_key_id.path})"
echo "R2_SECRET_ACCESS_KEY=$(cat ${config.sops.secrets.jirorian_r2_secret_access_key.path})"
echo "R2_ENDPOINT=$(cat ${config.sops.secrets.jirorian_r2_endpoint.path})"
echo "SERVICE_NOSTR_PRIVKEY=$(cat ${config.sops.secrets.jirorian_nostr_privkey.path})"
echo "JIRORIAN_APP_SECRETS=$(cat ${config.sops.secrets.jirorian_app_secrets.path})"
} >> /run/jirorian/env
'';
in
{
# Secrets — add corresponding keys to secrets/bucur.yaml via sops.
sops.secrets.jirorian_r2_access_key_id = { owner = "jirorian"; };
sops.secrets.jirorian_r2_secret_access_key = { owner = "jirorian"; };
sops.secrets.jirorian_r2_endpoint = { owner = "jirorian"; };
sops.secrets.jirorian_nostr_privkey = { owner = "jirorian"; };
sops.secrets.jirorian_app_secrets = { owner = "jirorian"; };
users.users.jirorian = {
isSystemUser = true;
group = "jirorian";
home = "/var/lib/jirorian";
};
users.groups.jirorian = {};
systemd.services.jirorian = {
description = "Jirorian Nostr-native R2 image upload API";
after = [ "network.target" "sops-nix.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = "jirorian";
Group = "jirorian";
StateDirectory = "jirorian";
RuntimeDirectory = "jirorian";
ExecStartPre = "+${makeEnv}";
ExecStart = "${pkg}/bin/jirorian";
EnvironmentFile = "/run/jirorian/env";
Environment = [
"JIRORIAN_BASE_URL=https://${domain}"
"JIRORIAN_PORT=${toString port}"
"JIRORIAN_DB=/var/lib/jirorian/jirorian.db"
"R2_BUCKET_NAME=${bucketName}"
"R2_CDN_URL=${cdnUrl}"
"SERVICE_RELAY_URL=${relayUrl}"
];
Restart = "on-failure";
RestartSec = 5;
};
};
# Caddy reverse proxy fragment — picked up via the glob import in caddy.nix.
environment.etc."caddy/Caddyfile.d/jirorian.caddyfile".text = ''
${domain} {
reverse_proxy localhost:${toString port}
}
'';
}

View file

@ -1,6 +1,11 @@
mtproto_secret: ENC[AES256_GCM,data:MyzyUCH8cLOG+GMtmV03daJ9sqwOw/ozN2yi/14SMlY=,iv:LLf2LIvX+jNjRdMHclHpsmTZa7wmVM3gD6RbqeCGmik=,tag:i36lVESyKJafsn14kSzh3g==,type:str] mtproto_secret: ENC[AES256_GCM,data:MyzyUCH8cLOG+GMtmV03daJ9sqwOw/ozN2yi/14SMlY=,iv:LLf2LIvX+jNjRdMHclHpsmTZa7wmVM3gD6RbqeCGmik=,tag:i36lVESyKJafsn14kSzh3g==,type:str]
codeberg_id_ed25519: ENC[AES256_GCM,data:AZbFWfc80HVskenEVLYL9MOYrGhW7Cwzdqv8gS4+1JGfk+nC801SmMgElUdISo+3m/O54b8RbJtdVz2u99gL8WG6wgbK7xqf40q4HNvV5cFXp7L/ozUkT41uXjWsDIdnMwumOqL/vjXowmAay30kMOiD5M4DL0t6EmuOo1Rcdffe/HwCVd2Pz7ZpU+BSTQV/pDTK1X3taUCjCY3Ui0XrcSCY8G2tuX5pFQ6t+vLrdqy3UGhofYHQ+nZuoyEKQHCcvEhYvEgDPJtyy+gk96vZCDYyXb9am/5k0I+MoIsArpavp4P0r5dNDcTnuCHH+fVPTG4tOsG/Ztf1rFBCsqPiM0azNElI8FERSKLYipuDDD8GI46p43OalHw4pxmAHXNOs70PfIH25iFJ5AUb32v2A71LScJMNP/TFSSezuq6g5eTd/JrtpQnWwToH1eVrwcN/8YX3xflLZ2JO1q4fjCXrkYHyw004dV/HAq3u1XQDmfMviZiH80+hiVIgZLfMWv5IyzfuomqSnfOj6oJ6D+j,iv:37HiX60OnO/zZFs1v4VWQLxcaoC1byZ3N2ueYFh4BVQ=,tag:5+z2YlnA8+MEcQTTrYhPJA==,type:str] codeberg_id_ed25519: ENC[AES256_GCM,data:AZbFWfc80HVskenEVLYL9MOYrGhW7Cwzdqv8gS4+1JGfk+nC801SmMgElUdISo+3m/O54b8RbJtdVz2u99gL8WG6wgbK7xqf40q4HNvV5cFXp7L/ozUkT41uXjWsDIdnMwumOqL/vjXowmAay30kMOiD5M4DL0t6EmuOo1Rcdffe/HwCVd2Pz7ZpU+BSTQV/pDTK1X3taUCjCY3Ui0XrcSCY8G2tuX5pFQ6t+vLrdqy3UGhofYHQ+nZuoyEKQHCcvEhYvEgDPJtyy+gk96vZCDYyXb9am/5k0I+MoIsArpavp4P0r5dNDcTnuCHH+fVPTG4tOsG/Ztf1rFBCsqPiM0azNElI8FERSKLYipuDDD8GI46p43OalHw4pxmAHXNOs70PfIH25iFJ5AUb32v2A71LScJMNP/TFSSezuq6g5eTd/JrtpQnWwToH1eVrwcN/8YX3xflLZ2JO1q4fjCXrkYHyw004dV/HAq3u1XQDmfMviZiH80+hiVIgZLfMWv5IyzfuomqSnfOj6oJ6D+j,iv:37HiX60OnO/zZFs1v4VWQLxcaoC1byZ3N2ueYFh4BVQ=,tag:5+z2YlnA8+MEcQTTrYhPJA==,type:str]
webhook_secret: ENC[AES256_GCM,data:iLDVsnRCifIXdZzuTTDRT/XHQoW4O/C2Wc33cxn3QoiZNgVXJef50In8+mKPy6SXV05iJqe/0YdOXF3IsHbelA==,iv:Ve/jQx6xPeS3O5h67cideBpGEhJSCVgtDfIypQSmIMc=,tag:Lcp+buNI23SSXVW9Vsn1RQ==,type:str] webhook_secret: ENC[AES256_GCM,data:iLDVsnRCifIXdZzuTTDRT/XHQoW4O/C2Wc33cxn3QoiZNgVXJef50In8+mKPy6SXV05iJqe/0YdOXF3IsHbelA==,iv:Ve/jQx6xPeS3O5h67cideBpGEhJSCVgtDfIypQSmIMc=,tag:Lcp+buNI23SSXVW9Vsn1RQ==,type:str]
jirorian_nostr_privkey: ENC[AES256_GCM,data:fn19816F7BPzdUpRwI8uv6wHFaAh1ipQ7hsHb6iXecz1rQufPW1M1QHW2ee7Zc073XgutQK+tSlpbYlwPSwwVQ==,iv:Ap8ykEyV/7QkAf4FEBPvdWNjIIhQOOyM/pO0r5bEMBg=,tag:O0BqmzqF3MP2xfqX46FDug==,type:str]
jirorian_r2_access_key_id: ENC[AES256_GCM,data:e20DrP0pvsKiA8Ir5i0Na74TTGwRc8P7xc1928OU9eg=,iv:F5yLO2Vl0kP18ntU98HsOq/PBXOXLV/qTq6s9p+1tOE=,tag:dVZzJfKqaw5j5GADOJhWpg==,type:str]
jirorian_r2_secret_access_key: ENC[AES256_GCM,data:ix+y6sdDa+Ze40ty1hQufoi6NCmWL0OrAD2Sk+ljs+cMCTDMdNyLh8YaNfQkUK1I73+csxcyojTMJEA2XSG52A==,iv:sGSPVRq0dMlXX8NnOgirgplprtNgktaxwY6p7ZmIzKw=,tag:E8PAT9t9d1LRoaJw+OTisg==,type:str]
jirorian_r2_endpoint: ENC[AES256_GCM,data:9mokSsGMS2dU72sMEpQNbsjysalOlMnHdd/VS2ebaG7ah80e8p7zG9IJxsXx/6jHy9XPPO+1ItsyhyNhyovfGs0=,iv:inCCSHuHI4X/B0jFilT3j/c4CNntOsti4pdsj1Z7xhE=,tag:OJWu/xmT1Y/XxbWG2mqCrA==,type:str]
jirorian_app_secrets: ENC[AES256_GCM,data:soUEk9GBrpN03+5rqMd2s9qT6sFGAswZUWjKjAHz0k058AJaNbOVlTW56CUjMG25/noIQzhwgqO7IVOzP1+UcdSOqB/67y7Q2rWG0ua8Nxg+qALV5e4Ub6rKGpgYHUfnwYW2wYi7UyxZQe1/mFaG6zCbahp956q/GHYUbXAPfXJcsFjZb0bkenUbrpxkKGdAadPfjwbdSD8ZXadGdwA=,iv:DXdxNIphpTkfcWLfWQrIG2GPsFp1WDLMp16M1Dbv4T8=,tag:ZuBf0XEPFKYCqvFe5FVvow==,type:str]
sops: sops:
age: age:
- recipient: age1647c5f46njylggj33h26t6twhq57n5qeaza04l845uy0fksv0ucsq82c3d - recipient: age1647c5f46njylggj33h26t6twhq57n5qeaza04l845uy0fksv0ucsq82c3d
@ -12,7 +17,7 @@ sops:
U0x1ZmtzLytSL1Y3cVo1ZVJ2Ymtob1EKTpgjSymQx14wLSuUhh5Txq++3DrLS4Bs U0x1ZmtzLytSL1Y3cVo1ZVJ2Ymtob1EKTpgjSymQx14wLSuUhh5Txq++3DrLS4Bs
x8vVtQgcXZgv1q0OMGup6DT4bkY6UiNbn9bAt4rHCaRPeb15x4ZKNw== x8vVtQgcXZgv1q0OMGup6DT4bkY6UiNbn9bAt4rHCaRPeb15x4ZKNw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2026-05-14T10:35:18Z" lastmodified: "2026-06-08T07:05:00Z"
mac: ENC[AES256_GCM,data:aTYOA9JxUYYhlqN9Cgocxl50qiSvZ5wCVCgNf15yUNmxXhuaQW8FV1TNXBrJ++k6AQ7+Yerh6cDEu3wjXg38oNdVF60pU4xlMFsQZy337yUXIi0IwStuy7hwshDj6prnROg2a0s77MpSJQLPHF2yQpFv1pHW3aTC3HAiCXcKrL0=,iv:PYOYeHCa4IHE9Kx87ECyMNRtfgmaZ4QUg4rsvjiSgSI=,tag:dGBhf8RW/PRGbA6XziaSAQ==,type:str] mac: ENC[AES256_GCM,data:BomXN9yuhCDbxcWamyScXDDtqUDvG8JuQlkrQzCm6iyvAkXR6QrFbzAP2REoQgEEMKGeAUc0SBZHu3/qgyA9FTHn65Z1zce7sbMu2tRkmb3KKeD/us6gCVRxyEgRB4WLXDaVNAt2aWUC35ek3COt8qQhKEFc05V5WRd6qpaGa6Y=,iv:65lpzgwG+7aNefHc1m3cGKKauE8TMYzAXiTBzUNZJ9I=,tag:cjDB+eO/34YfLd8hJmL/yw==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.11.0 version: 3.11.0