immich: add the service and import the ZimaOS library
jupiter had a leftover docker-compose Immich on the RAID (/mnt/data/Immich, 9.9G) that survived the NixOS install. Native module now, media at /mnt/data/AppData/immich, caddy vhost on 2283 with a 50GB body limit (caddy's default rejects video uploads). The package comes from nixpkgs-unstable, the module from the 26.05 pin: 26.05 ships immich 2.7.5, but that database was last written by 3.0.0 and migrations only run forward -- corrupted migrations: previously executed migration 1776217577402-DropAuditTable is missing Safe because the two module files are byte-identical at these revisions; services/media/immich.nix carries the diff command to re-check on a bump. Drop the input once the stable pin ships >= 3.0.0. immich needs group "users" only to traverse /mnt/data/AppData (drwx--x---); its own dir stays 0700 immich:immich. mediaLocation is outside /var/lib, so the module's tmpfiles entry only ADJUSTS it -- add a rule that creates it. scripts/immich-import-legacy-db does the database half: boots a copy of the legacy PGDATA under the matching image (PG14 + vchord 0.3.0 + pgvector 0.8.1), dumps it with the local pg_dump 17, restores into a scratch DB, fixes ownership, and only swaps after confirmation. Never touches the original. The old cluster ran VectorChord, not pgvecto.rs, so the smart search and face embeddings survive -- no ML re-run. Imported: 666 assets, 25 people, 647 clip + 359 face embeddings, 2 users.
This commit is contained in:
Generated
+17
@@ -263,6 +263,22 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-unstable": {
|
||||
"locked": {
|
||||
"lastModified": 1784555310,
|
||||
"narHash": "sha256-/FCliTPgiuV1owejZFNx3Ch9irdvkOfOFl+HHZ+DrtM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "421eebfd0ec7bccd4abe826ce62d7e6e83129493",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1784280462,
|
||||
@@ -387,6 +403,7 @@
|
||||
"mediamanager-nix": "mediamanager-nix",
|
||||
"nixos-images": "nixos-images",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||
"sops-nix": "sops-nix"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
|
||||
# Second nixpkgs, used for ONE package: immich. 26.05 pins 2.7.5, but
|
||||
# jupiter's imported database was written by 3.0.0 and immich never
|
||||
# migrates a schema backwards. NOT `follows` — the point is a different
|
||||
# package set. See services/media/immich.nix.
|
||||
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
disko = {
|
||||
url = "github:nix-community/disko";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
../../services/media/radarr.nix
|
||||
../../services/media/clonarr.nix
|
||||
../../services/media/seerr.nix
|
||||
../../services/media/immich.nix
|
||||
../../services/dev/gitea.nix
|
||||
];
|
||||
|
||||
@@ -71,6 +72,14 @@
|
||||
"http://clonarr.jupiter.sol".extraConfig = "reverse_proxy localhost:6060";
|
||||
"http://seerr.jupiter.sol".extraConfig = "reverse_proxy localhost:5055";
|
||||
"http://gitea.jupiter.sol".extraConfig = "reverse_proxy localhost:3000";
|
||||
# Immich uploads are large: raise the body limit off caddy's default and
|
||||
# give slow phone uploads room before the proxy gives up.
|
||||
"http://immich.jupiter.sol".extraConfig = ''
|
||||
request_body {
|
||||
max_size 50GB
|
||||
}
|
||||
reverse_proxy localhost:2283
|
||||
'';
|
||||
};
|
||||
|
||||
system.stateVersion = "26.05";
|
||||
|
||||
Executable
+243
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env bash
|
||||
# Import the OLD ZimaOS/CasaOS Immich database into the NixOS-managed one.
|
||||
# Run this ON jupiter, as root, ONCE, AFTER the first `./deploy switch jupiter`
|
||||
# that ships services/media/immich.nix (the empty `immich` DB must exist).
|
||||
#
|
||||
# The media files are moved separately — do that FIRST, it is a rename on the
|
||||
# same filesystem, so instant even at 9.1G. Move the CONTENTS, not the dir:
|
||||
# systemd.tmpfiles already created /mnt/data/AppData/immich on the first
|
||||
# deploy, so `mv <src> <dst>` would nest it as .../immich/upload/ and every
|
||||
# thumbnail lookup would ENOENT.
|
||||
#
|
||||
# systemctl stop immich-server immich-machine-learning
|
||||
# mv /mnt/data/Immich/upload/* /mnt/data/AppData/immich/
|
||||
# chown -R immich:immich /mnt/data/AppData/immich
|
||||
# chmod 700 /mnt/data/AppData/immich
|
||||
#
|
||||
# Expected afterwards: library/ upload/ thumbs/ encoded-video/ profile/ backups/
|
||||
#
|
||||
# The legacy cluster turned out to be Postgres 14 running VectorChord 0.3.0 +
|
||||
# pgvector 0.8.1 (NOT pgvecto.rs), the same extensions nixpkgs ships — so this
|
||||
# is a plain version-upgrade dump/restore and the smart-search and face
|
||||
# embeddings come across intact. No re-running the ML jobs over the library.
|
||||
# Upstream's accepted VectorChord range is >= 0.3, < 2.0, so 0.3.0 -> 1.1.1 is
|
||||
# a supported jump; the REINDEX at the end is what upstream asks for after a
|
||||
# version change.
|
||||
#
|
||||
# What this script does:
|
||||
# 1. cp -a the legacy PGDATA to a scratch dir (the original is never touched,
|
||||
# never even mounted rw — postgres would replay WAL into it).
|
||||
# 2. Boots that copy under immich's own PG14 image, pinned to the SAME
|
||||
# VectorChord version nixpkgs has (1.1.1), and runs `ALTER EXTENSION
|
||||
# vchord UPDATE` so the catalog matches the loaded library.
|
||||
# 3. Dumps it with the LOCAL pg_dump (17.x) over TCP, not the container's
|
||||
# pg_dump (14.x) — dumping with the newer tool is the supported direction.
|
||||
# 4. Restores into a scratch DB, hands ownership to the immich role, shows
|
||||
# you the row counts, and only swaps it into place after you confirm.
|
||||
#
|
||||
# Afterwards Immich runs its own schema migrations up to 2.7.5 on first start.
|
||||
set -euo pipefail
|
||||
|
||||
LEGACY="${LEGACY:-/mnt/data/Immich/pg-data}"
|
||||
WORK="${WORK:-/var/tmp/immich-import}"
|
||||
# Pinned to EXACTLY what the legacy cluster records in pg_extension —
|
||||
# vchord 0.3.0 + pgvector 0.8.1 — so the old server reads its own indexes
|
||||
# without any in-place extension upgrade. The target side is vchord 1.1.1 /
|
||||
# pgvector 0.8.2, which is fine: a dump/restore rebuilds every index from
|
||||
# scratch, so only the index DEFINITION has to still be valid there.
|
||||
IMAGE="${IMAGE:-ghcr.io/immich-app/postgres:14-vectorchord0.3.0-pgvector0.8.1}"
|
||||
CTR=immich-legacy-pg
|
||||
PORT="${PORT:-15432}"
|
||||
LEGACY_DB="${LEGACY_DB:-immich}"
|
||||
LEGACY_USER="${LEGACY_USER:-}" # empty = probe for it (see below)
|
||||
STAGING_DB=immich_import
|
||||
|
||||
die() { echo "error: $*" >&2; exit 1; }
|
||||
step() { echo; echo "== $*"; }
|
||||
|
||||
[ "$(id -u)" = 0 ] || die "run as root"
|
||||
[ -d "$LEGACY" ] || die "no legacy PGDATA at $LEGACY"
|
||||
command -v podman >/dev/null || die "podman not found"
|
||||
command -v pg_dump >/dev/null || die "pg_dump not found (is postgresql on this host?)"
|
||||
systemctl is-active --quiet postgresql || die "postgresql is not running"
|
||||
|
||||
# immich must be down: it runs schema migrations at startup, and we are about
|
||||
# to replace the schema underneath it.
|
||||
systemctl stop immich-server immich-machine-learning 2>/dev/null || true
|
||||
|
||||
step "copying legacy PGDATA -> $WORK/pgdata (original stays untouched)"
|
||||
rm -rf "$WORK"; mkdir -p "$WORK"
|
||||
cp -a "$LEGACY" "$WORK/pgdata"
|
||||
# A crashed cluster leaves this behind; it makes the container refuse to start.
|
||||
rm -f "$WORK/pgdata/postmaster.pid"
|
||||
|
||||
# The dump runs over TCP (local pg_dump 17 -> published port), and this
|
||||
# cluster's own pg_hba wants a password for host connections — the marketplace
|
||||
# app's POSTGRES_PASSWORD is long gone, and POSTGRES_HOST_AUTH_METHOD only
|
||||
# applies when the image INITIALISES a cluster, not to an existing one. This is
|
||||
# a scratch copy bound to 127.0.0.1 for the length of one dump, so trust it.
|
||||
# REPLACE the file rather than appending: pg_hba is first-match-wins, and the
|
||||
# image's existing scram-sha-256 line would shadow anything added below it.
|
||||
cat > "$WORK/pgdata/pg_hba.conf" <<'EOF'
|
||||
local all all trust
|
||||
host all all 0.0.0.0/0 trust
|
||||
host all all ::/0 trust
|
||||
EOF
|
||||
|
||||
step "booting Postgres 14 + VectorChord on the copy"
|
||||
podman rm -f "$CTR" 2>/dev/null || true
|
||||
podman run -d --name "$CTR" \
|
||||
-v "$WORK/pgdata:/var/lib/postgresql/data:Z" \
|
||||
-p "127.0.0.1:$PORT:5432" \
|
||||
-e POSTGRES_HOST_AUTH_METHOD=trust \
|
||||
"$IMAGE" >/dev/null
|
||||
trap 'podman rm -f "$CTR" >/dev/null 2>&1 || true' EXIT
|
||||
|
||||
for _ in $(seq 1 60); do
|
||||
# No -U: the role is probed for below, and pg_isready only checks that the
|
||||
# postmaster is accepting connections at all.
|
||||
if podman exec "$CTR" pg_isready >/dev/null 2>&1; then ready=1; break; fi
|
||||
sleep 2
|
||||
done
|
||||
[ "${ready:-}" = 1 ] || { podman logs --tail 30 "$CTR"; die "legacy postgres never became ready"; }
|
||||
|
||||
# The compose stack's POSTGRES_USER is not recorded anywhere on disk and is NOT
|
||||
# necessarily "postgres" — the ZimaOS/CasaOS marketplace app used "casaos".
|
||||
# pg_isready reports "accepting connections" even for a role that doesn't
|
||||
# exist, so probe for one that can actually log in.
|
||||
if [ -z "$LEGACY_USER" ] || ! podman exec "$CTR" psql -U "$LEGACY_USER" -lqt >/dev/null 2>&1; then
|
||||
for candidate in casaos immich postgres; do
|
||||
if podman exec "$CTR" psql -U "$candidate" -lqt >/dev/null 2>&1; then
|
||||
LEGACY_USER="$candidate"
|
||||
echo ">> legacy superuser role: $LEGACY_USER"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
[ -n "$LEGACY_USER" ] || die "no usable login role found (tried casaos/immich/postgres) —
|
||||
re-run with LEGACY_USER=<role>"
|
||||
|
||||
echo ">> databases in the legacy cluster:"
|
||||
podman exec "$CTR" psql -U "$LEGACY_USER" -lqt | cut -d'|' -f1 | sed 's/^/ /'
|
||||
|
||||
if ! podman exec "$CTR" psql -U "$LEGACY_USER" -lqtA -F'|' | cut -d'|' -f1 | grep -qx "$LEGACY_DB"; then
|
||||
found="$(podman exec "$CTR" psql -U "$LEGACY_USER" -lqtA -F'|' | cut -d'|' -f1 \
|
||||
| grep -vE '^(template[01]|postgres)$' | grep -v '^$' | head -1)"
|
||||
[ -n "$found" ] || die "no non-system database found — set LEGACY_DB=<name> and re-run"
|
||||
echo ">> database '$LEGACY_DB' not found, using '$found'"
|
||||
LEGACY_DB="$found"
|
||||
fi
|
||||
|
||||
step "legacy versions"
|
||||
podman exec "$CTR" psql -U "$LEGACY_USER" -d "$LEGACY_DB" \
|
||||
-c 'select extname, extversion from pg_extension order by extname'
|
||||
# Immich switched from TypeORM to Kysely, so the table changed name; try both.
|
||||
echo ">> last applied immich migration:"
|
||||
podman exec "$CTR" psql -U "$LEGACY_USER" -d "$LEGACY_DB" -tAc \
|
||||
'select name from kysely_migration order by timestamp desc limit 1' 2>/dev/null \
|
||||
|| podman exec "$CTR" psql -U "$LEGACY_USER" -d "$LEGACY_DB" -tAc \
|
||||
'select name from migrations order by timestamp desc limit 1' 2>/dev/null \
|
||||
|| echo " (neither kysely_migration nor migrations exists — unusual, check the dump)"
|
||||
|
||||
step "dumping $LEGACY_DB with the local pg_dump ($(pg_dump --version | awk '{print $3}'))"
|
||||
# Embeddings INCLUDED: same extension on both ends, so they restore as-is.
|
||||
pg_dump -h 127.0.0.1 -p "$PORT" -U "$LEGACY_USER" -d "$LEGACY_DB" \
|
||||
--no-owner --no-acl \
|
||||
> "$WORK/immich.sql"
|
||||
echo ">> dump: $(du -h "$WORK/immich.sql" | cut -f1)"
|
||||
podman rm -f "$CTR" >/dev/null; trap - EXIT
|
||||
|
||||
step "restoring into scratch DB $STAGING_DB"
|
||||
sudo -u postgres psql -qc "DROP DATABASE IF EXISTS $STAGING_DB"
|
||||
sudo -u postgres psql -qc "CREATE DATABASE $STAGING_DB OWNER immich"
|
||||
sudo -u postgres psql -qd "$STAGING_DB" \
|
||||
-c 'CREATE EXTENSION IF NOT EXISTS vector' \
|
||||
-c 'CREATE EXTENSION IF NOT EXISTS vchord CASCADE'
|
||||
# Not -v ON_ERROR_STOP=1: the pre-created extensions make the dump's own
|
||||
# CREATE EXTENSION lines complain harmlessly. Errors are counted, not hidden.
|
||||
sudo -u postgres psql -d "$STAGING_DB" -f "$WORK/immich.sql" > "$WORK/restore.log" 2>&1 || true
|
||||
echo ">> errors logged: $(grep -c '^ERROR' "$WORK/restore.log" || true) (see $WORK/restore.log)"
|
||||
grep '^ERROR' "$WORK/restore.log" | sort -u | head -10 | sed 's/^/ /' || true
|
||||
|
||||
step "handing ownership to the immich role"
|
||||
# --no-owner made everything owned by the restoring role (postgres); immich
|
||||
# connects as "immich" and its startup migrations run ALTER TABLE, so it must
|
||||
# own its own schema. NOT `REASSIGN OWNED BY postgres` — that also sweeps up
|
||||
# system objects and fails with "cannot reassign ownership of objects owned by
|
||||
# role postgres because they are required by the database system". Extension-
|
||||
# owned routines/types are excluded for the same reason; immich never alters
|
||||
# those, and they correctly stay with postgres.
|
||||
sudo -u postgres psql -qd "$STAGING_DB" <<'SQL'
|
||||
ALTER SCHEMA public OWNER TO immich;
|
||||
DO $$
|
||||
DECLARE r record;
|
||||
BEGIN
|
||||
FOR r IN
|
||||
SELECT c.relkind AS kind, n.nspname AS ns, c.relname AS name
|
||||
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname = 'public' AND c.relkind IN ('r','p','v','m','S','f')
|
||||
LOOP
|
||||
EXECUTE format('ALTER %s %I.%I OWNER TO immich',
|
||||
CASE r.kind WHEN 'S' THEN 'SEQUENCE'
|
||||
WHEN 'v' THEN 'VIEW'
|
||||
WHEN 'm' THEN 'MATERIALIZED VIEW'
|
||||
WHEN 'f' THEN 'FOREIGN TABLE'
|
||||
ELSE 'TABLE' END, r.ns, r.name);
|
||||
END LOOP;
|
||||
|
||||
FOR r IN
|
||||
SELECT p.oid::regprocedure AS sig FROM pg_proc p
|
||||
JOIN pg_namespace n ON n.oid = p.pronamespace
|
||||
WHERE n.nspname = 'public' AND p.prokind IN ('f','p')
|
||||
AND p.oid NOT IN (SELECT objid FROM pg_depend WHERE deptype = 'e' AND classid = 'pg_proc'::regclass)
|
||||
LOOP EXECUTE format('ALTER ROUTINE %s OWNER TO immich', r.sig); END LOOP;
|
||||
|
||||
FOR r IN
|
||||
SELECT t.oid::regtype AS name FROM pg_type t
|
||||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE n.nspname = 'public' AND t.typtype IN ('e','c','d')
|
||||
AND NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.reltype = t.oid AND c.relkind <> 'c')
|
||||
AND t.oid NOT IN (SELECT objid FROM pg_depend WHERE deptype = 'e' AND classid = 'pg_type'::regclass)
|
||||
LOOP EXECUTE format('ALTER TYPE %s OWNER TO immich', r.name); END LOOP;
|
||||
END $$;
|
||||
SQL
|
||||
|
||||
still_wrong="$(sudo -u postgres psql -tAd "$STAGING_DB" -c \
|
||||
"select count(*) from pg_class c join pg_namespace n on n.oid = c.relnamespace
|
||||
where n.nspname = 'public' and c.relkind in ('r','p','v','m','S','f')
|
||||
and c.relowner <> 'immich'::regrole")"
|
||||
[ "$still_wrong" = 0 ] || die "$still_wrong objects still not owned by immich — nothing swapped"
|
||||
echo ">> all public objects owned by immich"
|
||||
|
||||
step "what came across"
|
||||
# Immich 2.x table names (singular, and "user" is a reserved word). The legacy
|
||||
# cluster holds 666 assets / 2 users / 227k geodata rows — expect those back.
|
||||
sudo -u postgres psql -d "$STAGING_DB" -c \
|
||||
"select 'asset' t, count(*) from asset
|
||||
union all select 'album', count(*) from album
|
||||
union all select 'person', count(*) from person
|
||||
union all select 'user', count(*) from \"user\"
|
||||
union all select 'geodata_places', count(*) from geodata_places
|
||||
union all select 'smart_search', count(*) from smart_search
|
||||
union all select 'face_search', count(*) from face_search" 2>&1 || \
|
||||
die "staging DB looks wrong — nothing was swapped, inspect $WORK/restore.log"
|
||||
|
||||
echo
|
||||
echo "The scratch DB is populated. Swapping REPLACES the live (empty) immich DB."
|
||||
read -rp ">> type 'swap' to promote $STAGING_DB to immich: " ok
|
||||
[ "$ok" = swap ] || { echo "left in place as $STAGING_DB — nothing changed"; exit 0; }
|
||||
|
||||
step "promoting"
|
||||
sudo -u postgres psql -qc "ALTER DATABASE immich RENAME TO immich_empty_$(date +%s)"
|
||||
sudo -u postgres psql -qc "ALTER DATABASE $STAGING_DB RENAME TO immich"
|
||||
|
||||
step "rebuilding the vector indexes for VectorChord 1.1.1"
|
||||
# Upstream requires a REINDEX after a vchord version change. Skipped silently
|
||||
# if immich named them differently in this schema version.
|
||||
sudo -u postgres psql -d immich -c 'REINDEX INDEX face_index' 2>/dev/null || true
|
||||
sudo -u postgres psql -d immich -c 'REINDEX INDEX clip_index' 2>/dev/null || true
|
||||
|
||||
step "starting immich (it will run its own migrations up to 2.7.5 now)"
|
||||
systemctl start immich-server immich-machine-learning
|
||||
echo ">> follow with: journalctl -fu immich-server"
|
||||
echo ">> legacy data still intact at /mnt/data/Immich — delete only once happy"
|
||||
@@ -0,0 +1,62 @@
|
||||
{ pkgs, inputs, ... }:
|
||||
|
||||
# Immich photo/video library. Native nixpkgs module (not the upstream compose
|
||||
# stack) — it owns its own postgres (with the pgvector + vectorchord extensions
|
||||
# it needs for search) and a unix-socket redis, so nothing else is required here.
|
||||
#
|
||||
# Storage: everything lives under /mnt/data/AppData/immich, which is the media
|
||||
# store MIGRATED from the old ZimaOS/CasaOS install's UPLOAD_LOCATION
|
||||
# (/mnt/data/Immich/upload — same layout: library/ upload/ thumbs/
|
||||
# encoded-video/ profile/ backups/). See scripts/immich-import-legacy-db for the
|
||||
# matching database import. The postgres cluster itself stays on the OS disk.
|
||||
#
|
||||
# ⚠️ The immich DB is the only copy of albums/faces/dates — the files alone
|
||||
# can't rebuild it. It joins the other unbacked databases on this network.
|
||||
let
|
||||
# The PACKAGE comes from nixpkgs-unstable (3.0.3); the MODULE comes from the
|
||||
# 26.05 pin (which ships 2.7.5). That combination is safe because the two
|
||||
# module files are byte-identical — verified by diffing them at the revisions
|
||||
# in flake.lock. RE-CHECK THAT DIFF on any input bump:
|
||||
# diff <(nixpkgs)/nixos/modules/services/web-apps/immich.nix \
|
||||
# <(unstable)/nixos/modules/services/web-apps/immich.nix
|
||||
#
|
||||
# Why: jupiter's imported database was last written by immich 3.0.0, and
|
||||
# immich runs its migrations forward only — 2.7.5 refuses to start against it
|
||||
# with "corrupted migrations: previously executed migration
|
||||
# 1776217577402-DropAuditTable is missing". Drop this override once nixos-26.11
|
||||
# (or whatever the pin becomes) ships >= 3.0.0.
|
||||
unstable = import inputs.nixpkgs-unstable {
|
||||
inherit (pkgs.stdenv.hostPlatform) system;
|
||||
};
|
||||
in
|
||||
{
|
||||
services.immich = {
|
||||
enable = true;
|
||||
# Both the server and immich-machine-learning follow this: the module takes
|
||||
# the ML service from cfg.package.machine-learning (passthru).
|
||||
package = unstable.immich;
|
||||
# Listens on all interfaces: :2283 stays closed on the LAN (no
|
||||
# openFirewall), reachable over tailscale0 and via localhost (caddy).
|
||||
host = "0.0.0.0";
|
||||
port = 2283;
|
||||
mediaLocation = "/mnt/data/AppData/immich";
|
||||
machine-learning.enable = true;
|
||||
# Hardware transcoding would need the iGPU passed in explicitly, e.g.
|
||||
# accelerationDevices = [ "/dev/dri/renderD128" ]; the default [ ] means
|
||||
# PrivateDevices=yes and CPU-only transcode. The ZimaBlade's Celeron does
|
||||
# this slowly but it only runs on upload.
|
||||
};
|
||||
|
||||
# /mnt/data/AppData is drwx--x--- darman:users — immich needs group "users"
|
||||
# just to TRAVERSE into its own media dir. The dir itself stays 0700
|
||||
# immich:immich (the module's tmpfiles rule re-asserts that every rebuild,
|
||||
# and UMask=0077 keeps new files private), so this grants nothing else.
|
||||
users.users.immich.extraGroups = [ "users" ];
|
||||
|
||||
# mediaLocation is outside /var/lib, so the module won't create it — its own
|
||||
# tmpfiles entry only ADJUSTS an existing dir. Harmless no-op after the
|
||||
# legacy import, which puts the real store here.
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /mnt/data/AppData/immich 0700 immich immich -"
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user