Compare commits

..
Author SHA1 Message Date
Ruben Hensen f7396b3cb7 immich: share the group account's people with everyone 2026-10-28 15:10:06 +01:00
20 changed files with 79 additions and 79 deletions

No files matched your search

-4
View File
@@ -103,8 +103,6 @@ spec:
requests:
cpu: 70m
memory: 560Mi
limits:
memory: 1536Mi
volumes:
- name: patched-ca-bundle
configMap:
@@ -194,8 +192,6 @@ spec:
requests:
cpu: 100m
memory: 336Mi
limits:
memory: 1Gi
volumes:
- name: patched-ca-bundle
configMap:
-6
View File
@@ -38,22 +38,16 @@ spec:
requests:
cpu: 10m
memory: 48Mi
limits:
memory: 128Mi
cainjector:
resources:
requests:
cpu: 10m
memory: 80Mi
limits:
memory: 256Mi
webhook:
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
syncPolicy:
syncOptions:
- CreateNamespace=true
-2
View File
@@ -28,8 +28,6 @@ spec:
requests:
cpu: 10m
memory: 48Mi
limits:
memory: 128Mi
destination:
server: https://kubernetes.default.svc
namespace: cnpg-system
@@ -24,22 +24,16 @@ spec:
requests:
cpu: 20m
memory: 64Mi
limits:
memory: 128Mi
certController:
resources:
requests:
cpu: 10m
memory: 64Mi
limits:
memory: 128Mi
webhook:
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
destination:
server: https://kubernetes.default.svc
namespace: external-secrets
@@ -21,8 +21,6 @@ spec:
requests:
cpu: 10m
memory: 64Mi
limits:
memory: 128Mi
destination:
server: https://kubernetes.default.svc
namespace: cnpg-system
+2 -16
View File
@@ -1,6 +1,6 @@
# Requests from what the components used on 2026-10-25 (kubectl top every
# 5 minutes; highest value plus 10%). Memory limits at about twice the
# highest value seen then or on 2026-10-27, rounded up; no CPU limits.
# 5 minutes; highest value plus 10%). No limits until there are daytime
# numbers, see docs/roadmap.md.
apiVersion: apps/v1
kind: StatefulSet
metadata:
@@ -14,8 +14,6 @@ spec:
requests:
cpu: 150m
memory: 704Mi
limits:
memory: 2560Mi
---
apiVersion: apps/v1
kind: Deployment
@@ -30,8 +28,6 @@ spec:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 512Mi
---
apiVersion: apps/v1
kind: Deployment
@@ -46,8 +42,6 @@ spec:
requests:
cpu: 10m
memory: 64Mi
limits:
memory: 256Mi
---
apiVersion: apps/v1
kind: Deployment
@@ -62,8 +56,6 @@ spec:
requests:
cpu: 20m
memory: 96Mi
limits:
memory: 256Mi
---
apiVersion: apps/v1
kind: Deployment
@@ -78,8 +70,6 @@ spec:
requests:
cpu: 10m
memory: 48Mi
limits:
memory: 128Mi
---
apiVersion: apps/v1
kind: Deployment
@@ -94,8 +84,6 @@ spec:
requests:
cpu: 10m
memory: 48Mi
limits:
memory: 128Mi
---
apiVersion: apps/v1
kind: Deployment
@@ -110,5 +98,3 @@ spec:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
-2
View File
@@ -67,8 +67,6 @@ spec:
requests:
cpu: 10m
memory: 64Mi
limits:
memory: 512Mi
env:
# Newer boto3 adds checksums that S3-compatible stores can reject;
# the plugin's docs suggest sending them only when required.
-2
View File
@@ -18,8 +18,6 @@ spec:
requests:
cpu: 50m
memory: 256Mi
limits:
memory: 1Gi
bootstrap:
initdb:
database: authentik
+7 -2
View File
@@ -27,7 +27,9 @@ view, and everything is shared with everyone automatically.
- **immich has no user groups**; albums are shared per user, as viewer or
editor. The owner and editors of an album may add users and change
their roles. Search only covers your own photos and those of partners
you show in your timeline.
you show in your timeline. People (face recognition) are per user too,
and a partner's library doesn't bring its people along; since 3.3 a
user can share people with the users in their cluster group.
## Design
@@ -46,7 +48,10 @@ view, and everything is shared with everyone automatically.
every user into every album of the group account (`fotoupload@`), as
editor or viewer by quota; the group account's library as a partner of
everyone, shown in the timeline; album invitations for albums older than
a week hidden; sessions of OAuth users older than 30 days ended. Albums
a week hidden; sessions of OAuth users older than 30 days ended;
everyone in the group account's cluster group with its people shared
(names editable by those who may upload, added 2026-10-28, when nobody
but the group account could search by face). Albums
that kaderleden create get the group account as editor first (in the
database, since the API only lets an album's owner or editors share
it), so they go to everyone too; names starting with "Privé"/"Prive"
+2 -7
View File
@@ -341,13 +341,8 @@ Counted 2026-10-25 over the 73 running containers:
requested. Still without requests: Longhorn (22 containers, its chart),
the Vault servers, the LDAP outpost, immich's PG14 cluster and three in
kube-system
- [x] Memory limits: PR #58, for everything that got requests in #33.
About twice the highest memory from `kubectl top` every 5 minutes from
2026-10-26 22:29 to 2026-10-27 21:55 UTC or the night of #33, rounded
up; no CPU limits. No OOM kills that day. immich's machine learning
sat idle all day (240Mi), so on 2026-10-28 a test image went through
CLIP, face recognition and OCR inside the container: 884Mi with all
three loaded, limit 2Gi
- [ ] Memory limits, once there are daytime numbers: the night ones miss
immich's machine learning at work (it idles at 234Mi)
- [x] WordPress (scoutingmbg.nl) was OOMKilled at 08:27:59 UTC on
2026-10-25, its third restart: a crawler asked for 88 pages and assets
within a few seconds (normally ~6 a minute), Apache started a PHP worker
-2
View File
@@ -83,8 +83,6 @@ spec:
requests:
cpu: 50m
memory: 176Mi
limits:
memory: 512Mi
# TCP, because mod_auth_openidc answers 401 to anything that isn't a
# browser.
readinessProbe:
-2
View File
@@ -54,8 +54,6 @@ spec:
requests:
cpu: 20m
memory: 336Mi
limits:
memory: 1Gi
# /api/healthz checks the database and the cache.
readinessProbe:
httpGet:
+2 -8
View File
@@ -67,8 +67,6 @@ spec:
requests:
cpu: 10m
memory: 64Mi
limits:
memory: 256Mi
image:
repository: docker.io/valkey/valkey
tag: 9.1.2-alpine@sha256:48332870af354a799964c0012ae1194a0bf2bf894eb508f945810596dc2d8d11
@@ -95,8 +93,6 @@ spec:
requests:
cpu: 100m
memory: 1280Mi
limits:
memory: 3Gi
# Node trusts its built-in roots, which still include the
# ones above the compromised eMudhra intermediates. Use
# patched-ca-bundle through OpenSSL until Node ships a fix.
@@ -121,14 +117,12 @@ spec:
image:
repository: ghcr.io/immich-app/immich-machine-learning
pullPolicy: IfNotPresent
# Idle at 240Mi; 884Mi with CLIP, face recognition and
# OCR loaded (2026-10-28, a test image through each).
# Idle at night (234Mi); the models take over a GiB when
# it works.
resources:
requests:
cpu: 50m
memory: 1Gi
limits:
memory: 2Gi
env:
TRANSFORMERS_CACHE: /cache
HF_XET_CACHE: /cache/huggingface-xet
-2
View File
@@ -71,8 +71,6 @@ spec:
requests:
cpu: 10m
memory: 64Mi
limits:
memory: 512Mi
env:
# Newer boto3 adds checksums that S3-compatible stores can reject;
# the plugin's docs suggest sending them only when required.
-2
View File
@@ -13,8 +13,6 @@ spec:
requests:
cpu: 20m
memory: 320Mi
limits:
memory: 1Gi
postgresql:
shared_preload_libraries:
+66 -2
View File
@@ -8,6 +8,10 @@
# - the group account's library is shared as a partner and shown in
# everyone's timeline, so search covers it (immich only searches your own
# and your timeline partners' photos);
# - everyone is put in the group account's cluster group and gets its
# people (person sharing, immich 3.3): people are per user, and a partner
# library doesn't bring its people along. Those who may upload can name
# them, for everyone;
# - the invitation notifications for albums older than a week are hidden,
# or a new member would get one per album;
# - sessions of OAuth users older than SESSION_MAX_AGE_DAYS are ended, so
@@ -15,8 +19,11 @@
# Online.
# Albums and partners go through the API. Joining other people's albums
# (the API only lets an album's owner or editors share it), the timeline
# flag (which only the receiving user can set), the notifications and the
# sessions go straight to the database.
# flag and the cluster group (which only the receiving user can set or
# accept), sharing the people (the API wants person.update, which the
# group's key lacks, and with password login off nobody can log in as the
# group to add it), the notifications and the sessions go straight to the
# database.
apiVersion: v1
kind: ConfigMap
metadata:
@@ -24,6 +31,7 @@ metadata:
namespace: immich
data:
sync.py: |
import collections
import json
import os
import subprocess
@@ -129,6 +137,62 @@ data:
'UPDATE partner SET "inTimeline" = true WHERE "sharedById" = :\'group\' AND NOT "inTimeline"',
group=group_id,
)
# What accepting an invitation to the group account's cluster group does
# (ClusterGroupService.acceptRequest): the person groups only the member
# has people in move along. Members with people in groups that others
# also have people in are left alone; that only happens after sharing.
for (email,) in sql(
"""
BEGIN;
CREATE TEMP TABLE moving ON COMMIT DROP AS
SELECT u.id FROM "user" u, "user" g
WHERE g.id = :'group' AND u.id = ANY (:'members'::uuid[])
AND u."clusterGroupId" <> g."clusterGroupId"
AND NOT EXISTS (
SELECT 1 FROM person p
JOIN person o ON o."personGroupId" = p."personGroupId" AND o."ownerId" <> p."ownerId"
WHERE p."ownerId" = u.id
);
UPDATE person_group SET "clusterGroupId" = (SELECT "clusterGroupId" FROM "user" WHERE id = :'group')
WHERE id IN (SELECT p."personGroupId" FROM person p JOIN moving m ON m.id = p."ownerId");
DELETE FROM cluster_group_request
WHERE "userId" IN (SELECT id FROM moving)
AND "clusterGroupId" = (SELECT "clusterGroupId" FROM "user" WHERE id = :'group');
UPDATE "user" SET "clusterGroupId" = (SELECT "clusterGroupId" FROM "user" WHERE id = :'group')
WHERE id IN (SELECT id FROM moving)
RETURNING email;
COMMIT;
""",
group=group_id,
members="{" + ",".join(members) + "}",
):
print(f"put {email} in the group's cluster group", flush=True)
# What PUT /people/users does for the group account: its people, with
# their names, in every member's people list and search filters. Write
# access (naming people, for everyone) for those who may upload.
shared = sql(
"""
INSERT INTO person_user ("personGroupId", "sharedById", "sharedWithId", role)
SELECT p."personGroupId", p."ownerId", m.id, m.role::person_user_role_enum
FROM person p
JOIN "user" g ON g.id = p."ownerId"
CROSS JOIN unnest(:'ids'::uuid[], :'roles'::text[]) AS m(id, role)
JOIN "user" u ON u.id = m.id AND u."clusterGroupId" = g."clusterGroupId"
WHERE p."ownerId" = :'group' AND NOT p."isHidden"
AND NOT EXISTS (
SELECT 1 FROM person_user pu
WHERE pu."personGroupId" = p."personGroupId" AND pu."sharedById" = p."ownerId"
AND pu."sharedWithId" = m.id AND pu.role = m.role::person_user_role_enum
)
ON CONFLICT ("personGroupId", "sharedById", "sharedWithId") DO UPDATE SET role = EXCLUDED.role
RETURNING "sharedWithId", role
""",
group=group_id,
ids="{" + ",".join(wanted) + "}",
roles="{" + ",".join("read" if role == "viewer" else "write" for role in wanted.values()) + "}",
)
for (uid, role), count in sorted(collections.Counter(map(tuple, shared)).items()):
print(f"share {count} people with {members[uid]} ({role})", flush=True)
sql(
"""
UPDATE notification SET "deletedAt" = now()
-6
View File
@@ -20,8 +20,6 @@ spec:
requests:
cpu: 10m
memory: 48Mi
limits:
memory: 128Mi
metrics:
enabled: true
serviceMonitor:
@@ -31,8 +29,6 @@ spec:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
serviceMonitor:
enabled: false
certController:
@@ -40,8 +36,6 @@ spec:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
serviceMonitor:
enabled: false
syncPolicy:
-2
View File
@@ -60,8 +60,6 @@ spec:
requests:
cpu: 10m
memory: 160Mi
limits:
memory: 512Mi
readinessProbe:
httpGet:
path: /login
-2
View File
@@ -7,8 +7,6 @@ injector:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
# The chart's default anti-affinity forbids two injector pods on one
# node, so on this single node a rolling update can never start the new
# pod while the old one runs. Recreate removes the old pod first, which
-2
View File
@@ -71,8 +71,6 @@ spec:
requests:
cpu: 10m
memory: 64Mi
limits:
memory: 128Mi
# 512Mi got the server OOMKilled 314 times, including halfway
# through a backup of ~14.7k items.
resources: