Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

immich-public-proxy: init at 1.5.4, nixos/immich-public-proxy: init module #362907

Merged
merged 3 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions maintainers/maintainer-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -10110,6 +10110,12 @@
githubId = 45084216;
keys = [ { fingerprint = "1BF9 8D10 E0D0 0B41 5723 5836 4C13 3A84 E646 9228"; } ];
};
jaculabilis = {
name = "Tim Van Baak";
email = "[email protected]";
github = "Jaculabilis";
githubId = 10787844;
};
jaduff = {
email = "[email protected]";
github = "jaduff";
Expand Down
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2505.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@

- [Actual Budget](https://actualbudget.org/), a local-first personal finance app. Available as [services.actual](#opt-services.actual.enable).

- [immich-public-proxy](https://github.com/alangrainger/immich-public-proxy), a proxy for sharing Immich albums without exposing the Immich API. Available as [services.immich-public-proxy](#opt-services.immich-public-proxy.enable).

- [mqtt-exporter](https://github.com/kpetremann/mqtt-exporter/), a Prometheus exporter for exposing messages from MQTT. Available as [services.prometheus.exporters.mqtt](#opt-services.prometheus.exporters.mqtt.enable).

- [nvidia-gpu](https://github.com/utkuozdemir/nvidia_gpu_exporter), a Prometheus exporter that scrapes `nvidia-smi` for GPU metrics. Available as [services.prometheus.exporters.nvidia-gpu](#opt-services.prometheus.exporters.nvidia-gpu.enable).
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1484,6 +1484,7 @@
./services/web-apps/icingaweb2/module-monitoring.nix
./services/web-apps/ifm.nix
./services/web-apps/immich.nix
./services/web-apps/immich-public-proxy.nix
./services/web-apps/invidious.nix
./services/web-apps/invoiceplane.nix
./services/web-apps/isso.nix
Expand Down
98 changes: 98 additions & 0 deletions nixos/modules/services/web-apps/immich-public-proxy.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.immich-public-proxy;
format = pkgs.formats.json { };
inherit (lib)
types
mkIf
mkOption
mkEnableOption
;
in
{
options.services.immich-public-proxy = {
enable = mkEnableOption "Immich Public Proxy";
package = lib.mkPackageOption pkgs "immich-public-proxy" { };

immichUrl = mkOption {
type = types.str;
description = "URL of the Immich instance";
};

port = mkOption {
type = types.port;
default = 3000;
description = "The port that IPP will listen on.";
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = "Whether to open the IPP port in the firewall";
};

settings = mkOption {
type = types.submodule {
freeformType = format.type;
};
default = { };
description = ''
Configuration for IPP. See <https://github.com/alangrainger/immich-public-proxy/blob/main/README.md#additional-configuration> for options and defaults.
'';
};
};

config = mkIf cfg.enable {
systemd.services.immich-public-proxy = {
description = "Immich public proxy for sharing albums publicly without exposing your Immich instance";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment = {
IMMICH_URL = cfg.immichUrl;
IPP_PORT = builtins.toString cfg.port;
IPP_CONFIG = "${format.generate "config.json" cfg.settings}";
};
serviceConfig = {
ExecStart = lib.getExe cfg.package;
SyslogIdentifier = "ipp";
User = "ipp";
Group = "ipp";
DynamicUser = true;
Type = "simple";
Restart = "on-failure";
RestartSec = 3;

# Hardening
CapabilityBoundingSet = "";
NoNewPrivileges = true;
PrivateUsers = true;
PrivateTmp = true;
PrivateDevices = true;
PrivateMounts = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
};
};

networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];

meta.maintainers = with lib.maintainers; [ jaculabilis ];
};
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ in {
ifm = handleTest ./ifm.nix {};
iftop = handleTest ./iftop.nix {};
immich = handleTest ./web-apps/immich.nix {};
immich-public-proxy = handleTest ./web-apps/immich-public-proxy.nix {};
incron = handleTest ./incron.nix {};
incus = pkgs.recurseIntoAttrs (handleTest ./incus { lts = false; inherit system pkgs; });
incus-lts = pkgs.recurseIntoAttrs (handleTest ./incus { inherit system pkgs; });
Expand Down
105 changes: 105 additions & 0 deletions nixos/tests/web-apps/immich-public-proxy.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import ../make-test-python.nix (
{ pkgs, lib, ... }:
{
name = "immich-public-proxy";

nodes.machine =
{ pkgs, ... }@args:
{
environment.systemPackages = [
pkgs.imagemagick
pkgs.immich-cli
];
services.immich = {
enable = true;
port = 2283;
# disable a lot of features that aren't needed for this test
machine-learning.enable = false;
settings = {
backup.database.enabled = false;
machineLearning.enabled = false;
map.enabled = false;
reverseGeocoding.enabled = false;
metadata.faces.import = false;
newVersionCheck.enabled = false;
notifications.smtp.enabled = false;
};
};
services.immich-public-proxy = {
enable = true;
immichUrl = "http://localhost:2283";
port = 8002;
settings.ipp.responseHeaders."X-NixOS" = "Rules";
};
};

testScript = ''
import json

machine.wait_for_unit("immich-server.service")
machine.wait_for_unit("immich-public-proxy.service")
machine.wait_for_open_port(2283)
machine.wait_for_open_port(8002)

# The proxy should be up
machine.succeed("curl -sf http://localhost:8002")

# Verify the static assets are served
machine.succeed("curl -sf http://localhost:8002/robots.txt")
machine.succeed("curl -sf http://localhost:8002/share/static/style.css")

# Check that the response header in the settings is sent
res = machine.succeed("""
curl -sD - http://localhost:8002 -o /dev/null
""")
assert "x-nixos: rules" in res.lower(), res

# Log in to Immich and create an access key
machine.succeed("""
curl -sf --json '{ "email": "[email protected]", "name": "Admin", "password": "admin" }' http://localhost:2283/api/auth/admin-sign-up
""")
res = machine.succeed("""
curl -sf --json '{ "email": "[email protected]", "password": "admin" }' http://localhost:2283/api/auth/login
""")
token = json.loads(res)['accessToken']
res = machine.succeed("""
curl -sf -H 'Cookie: immich_access_token=%s' --json '{ "name": "API Key", "permissions": ["all"] }' http://localhost:2283/api/api-keys
""" % token)
key = json.loads(res)['secret']
machine.succeed(f"immich login http://localhost:2283/api {key}")
res = machine.succeed("immich server-info")
print(res)

# Upload some blank images to a new album
# If there's only one image, the proxy serves the image directly
machine.succeed("magick -size 800x600 canvas:white /tmp/white.png")
machine.succeed("immich upload -A '✨ Reproducible Moments ✨' /tmp/white.png")
machine.succeed("magick -size 800x600 canvas:black /tmp/black.png")
machine.succeed("immich upload -A '✨ Reproducible Moments ✨' /tmp/black.png")
res = machine.succeed("immich server-info")
print(res)

# Get the new album id
res = machine.succeed("""
curl -sf -H 'Cookie: immich_access_token=%s' http://localhost:2283/api/albums
""" % token)
album_id = json.loads(res)[0]['id']

# Create a shared link
res = machine.succeed("""
curl -sf -H 'Cookie: immich_access_token=%s' --json '{ "albumId": "%s", "type": "ALBUM" }' http://localhost:2283/api/shared-links
""" % (token, album_id))
share_key = json.loads(res)['key']

# Access the share
machine.succeed("""
curl -sf http://localhost:2283/share/%s
""" % share_key)

# Access the share through the proxy
machine.succeed("""
curl -sf http://localhost:8002/share/%s
""" % share_key)
'';
}
)
50 changes: 50 additions & 0 deletions pkgs/by-name/im/immich-public-proxy/package.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
lib,
buildNpmPackage,
fetchFromGitHub,
nix-update-script,
nixosTests,
nodejs,
}:
buildNpmPackage rec {
pname = "immich-public-proxy";
version = "1.5.4";
src = fetchFromGitHub {
owner = "alangrainger";
repo = "immich-public-proxy";
rev = "v${version}";
hash = "sha256-GoAUR8s2tRHpXD/yk42u6DDvkI97XAUlF9Zsq8pb/1M=";
};

sourceRoot = "${src.name}/app";

npmDepsHash = "sha256-BN7g+31ijH8r9rsv5zzjnE8PT7ozAswoyZNJ0XqXGyw=";

# patch in absolute nix store paths so the process doesn't need to cwd in $out
postPatch = ''
substituteInPlace src/index.ts --replace-fail \
"const app = express()" \
"const app = express()
// Set the views path to the nix output
app.set('views', '$out/lib/node_modules/immich-public-proxy/views')" \
--replace-fail \
"static('public'" \
"static('$out/lib/node_modules/immich-public-proxy/public'"
'';

passthru = {
tests = {
inherit (nixosTests) immich-public-proxy;
};
updateScript = nix-update-script { };
};

meta = {
description = "Share your Immich photos and albums in a safe way without exposing your Immich instance to the public";
homepage = "https://github.com/alangrainger/immich-public-proxy";
license = lib.licenses.agpl3Only;
maintainers = with lib.maintainers; [ jaculabilis ];
inherit (nodejs.meta) platforms;
mainProgram = "immich-public-proxy";
};
}
Loading