commit acca19d3dd8249fe8648de22f3f0263fadf1db8a Author: Jet Date: Wed Mar 18 16:10:01 2026 -0700 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65b07fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +photos/ +flickr/ +*.log +gallery-dl.conf diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..9d6662e --- /dev/null +++ b/SPEC.md @@ -0,0 +1,72 @@ +# Noisepics - Noisebridge Community Photo Gallery + +## What +Self-hosted photo gallery for Noisebridge using Piwigo, served at `noisepics.extremist.software`, with 1TB Hetzner Storage Box for photo storage. + +## Architecture + +``` + ┌─────────────────────────────┐ + │ extremist-software VPS │ +Internet ──► Caddy ─┤ │ + │ Piwigo (OCI container :2284) │ + │ MariaDB │ + │ │ │ + └────────┼──────────────────────┘ + │ CIFS mount + ┌────────▼──────────────────────┐ + │ Hetzner Storage Box BX11 │ + │ 1TB - €3.80/mo │ + │ /galleries/ │ + └───────────────────────────────┘ +``` + +## TODO + +### 1. Get storage +- [x] Order Hetzner Storage Box BX11 (1TB, €3.80/mo) +- [x] Note the address: `u563838.your-storagebox.de` +- [x] Enable Samba/CIFS + external reachability +- [ ] Create agenix secret with credentials (`username=u563838\npassword=...`) + +### 2. Deploy the module +- [ ] Add `noisepics` flake input to `extremist-software/flake.nix` +- [ ] Add `inputs.noisepics.nixosModules.default` to the modules list +- [ ] Create `modules/noisepics.nix` in extremist-software: + ```nix + { config, inputs, ... }: + { + services.noisepics = { + enable = true; + domain = "noisepics.extremist.software"; + storagebox = { + enable = true; + address = "u563838.your-storagebox.de"; + credentialsFile = config.age.secrets.noisepics-storagebox.path; + }; + }; + + age.secrets.noisepics-storagebox = { + file = ../secrets/noisepics-storagebox.age; + mode = "0400"; + }; + } + ``` +- [ ] Add DNS record: `noisepics.extremist.software` → VPS IP +- [ ] Deploy with `nhs` + +### 3. Import existing photos +- [ ] Mount storage box locally via Samba and copy photos in +- [ ] Use Piwigo's admin sync to pick them up +- [ ] Import `photos/metadata.json` tags/descriptions via Piwigo API + +### 4. Configure Piwigo +- [ ] Complete web setup wizard (DB: `piwigo` on localhost socket) +- [ ] Enable community uploads (Community plugin) +- [ ] Set up guest browsing (no login to view) +- [ ] Set Creative Commons licensing in gallery config + +## Cost +- Storage Box BX11: **€3.80/mo** +- VPS: already running (extremist-software) +- Total new cost: **€3.80/mo** diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..57eb3cb --- /dev/null +++ b/flake.nix @@ -0,0 +1,11 @@ +{ + description = "Noisepics - Noisebridge community photo gallery"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + }; + + outputs = { self, nixpkgs }: { + nixosModules.default = import ./module.nix; + }; +} diff --git a/module.nix b/module.nix new file mode 100644 index 0000000..70a229d --- /dev/null +++ b/module.nix @@ -0,0 +1,118 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.noisepics; +in { + options.services.noisepics = { + enable = lib.mkEnableOption "Noisepics community photo gallery"; + + domain = lib.mkOption { + type = lib.types.str; + default = "noisepics.extremist.software"; + description = "Domain name for the gallery"; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 2284; + description = "Internal port for Piwigo"; + }; + + dataDir = lib.mkOption { + type = lib.types.str; + default = "/var/lib/noisepics"; + description = "Directory for Piwigo data (local DB, config, cache)"; + }; + + storageDir = lib.mkOption { + type = lib.types.str; + default = "/mnt/noisepics-storage"; + description = "Mount point for Hetzner Storage Box (photos live here)"; + }; + + storagebox = { + enable = lib.mkEnableOption "Hetzner Storage Box mount"; + + address = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Hetzner Storage Box address (e.g. uXXXXXX.your-storagebox.de)"; + }; + + credentialsFile = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Path to a file containing username=... and password=... for the storage box"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + # Piwigo runs via its NixOS service (PHP + nginx/caddy upstream) + # Using the OCI container approach since Piwigo isn't packaged in nixpkgs + virtualisation.oci-containers.containers.noisepics = { + image = "lscr.io/linuxserver/piwigo:latest"; + ports = [ "127.0.0.1:${toString cfg.port}:80" ]; + volumes = [ + "${cfg.dataDir}/config:/config" + "${cfg.storageDir}/galleries:/gallery" + ]; + environment = { + PUID = "1000"; + PGID = "1000"; + TZ = "America/Los_Angeles"; + }; + }; + + # MariaDB for Piwigo + services.mysql = { + enable = true; + package = pkgs.mariadb; + ensureDatabases = [ "piwigo" ]; + ensureUsers = [ + { + name = "piwigo"; + ensurePermissions = { "piwigo.*" = "ALL PRIVILEGES"; }; + } + ]; + }; + + # Caddy virtualhost + services.caddy.virtualHosts.${cfg.domain} = { + extraConfig = '' + rate_limit { + zone noisepics_per_ip { + key {remote.ip} + events 120 + window 1m + } + } + reverse_proxy localhost:${toString cfg.port} + ''; + }; + + # Data directories + systemd.tmpfiles.rules = [ + "d ${cfg.dataDir} 0755 1000 1000 -" + "d ${cfg.dataDir}/config 0755 1000 1000 -" + "d ${cfg.storageDir} 0755 1000 1000 -" + "d ${cfg.storageDir}/galleries 0755 1000 1000 -" + ]; + + # Hetzner Storage Box mount + fileSystems.${cfg.storageDir} = lib.mkIf cfg.storagebox.enable { + device = "//${cfg.storagebox.address}/backup"; + fsType = "cifs"; + options = [ + "credentials=${cfg.storagebox.credentialsFile}" + "uid=1000" + "gid=1000" + "iocharset=utf8" + "nofail" + "_netdev" + "x-systemd.automount" + "x-systemd.idle-timeout=60" + ]; + }; + }; +} diff --git a/secrets.nix b/secrets.nix new file mode 100644 index 0000000..cfb9493 --- /dev/null +++ b/secrets.nix @@ -0,0 +1,6 @@ +let + server = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAING219cDKTDLaZefmqvOHfXvYloA/ErsCGE0pM022vlB"; + jet = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu"; +in { + "secrets/noisepics-storagebox.age".publicKeys = [ server jet ]; +} diff --git a/secrets/noisepics-storagebox.age b/secrets/noisepics-storagebox.age new file mode 100644 index 0000000..b9b54e7 Binary files /dev/null and b/secrets/noisepics-storagebox.age differ