fix(filebrowser): run the file manager inside the pod of the agent on 2.0.0 #398

Merged
grachevko merged 3 commits from fix/filebrowser-v2 into main 2026-10-12 16:04:23 +00:00
Collaborator

What was wrong

Two things at once, one of them the reason the other had to be done here rather than in place.

  1. The file manager does not start. The release has been on the 2.0.0-stable image since the Renovate major bump, and the application refuses the configuration of 1.x:
[ERROR] unable to load config, waiting 5 seconds before exiting...
[FATAL] error unmarshaling YAML data: [19:3] unknown field "port"
   17 | server:
   18 |   cacheDir: /home/filebrowser/data/cache
>  19 |   port: 8080
  1. The file manager is a second pod on the claim of the agent, and that costs a manual step on every eviction. The claim is ReadWriteOnce: it is held by the pods of ONE node at a time, while the scheduler does not see the attachment (the PV of this storage class carries only a zone affinity, and the zone is the same for all three nodes). The agent can therefore be rescheduled onto another node while the file manager stays on the old one, and the claim cannot be attached to the new node until that pod is deleted by hand.

What this fixes

  • kubernetes/apps/grachevko/filebrowser/app/configmap.yaml — the configuration in the shape of 2.0.0: http.port and the single http.trustProxyHeaders in place of the trusted-headers list; server.database on SQLite with migrateFrom; server.sources[].config.defaultPermissions in place of the account permissions (with view, a right of its own in 2.0.0); userDefaults.account.permissions narrowed to what stays global. What changed is where each of our settings lives, not what it is: the port stays 8080 with no capability to bind a privileged port, the login stays pocket-id only, the session stays 24 hours, the source stays the whole volume of the agent.
  • kubernetes/apps/grachevko/hermes/app/helmrelease.yaml — the file manager becomes a container of the agent's pod (its image, limits, probes and login read from the ConfigMap and the Secret of the filebrowser directory), and an init container renames the BoltDB file of 1.x once (database.db → database.db.old, with a copy outside the working directory on the claim of the agent), which is what the import of 2.0.0 waits for. The volumes of the file manager are mounted for its own containers (advancedMounts), the claim of the agent only as before. Its Service is hermes-filebrowser, as app-template names one after the release and the key of the service map.
  • kubernetes/apps/grachevko/hermes/app/filebrowser-httproute.yaml — the route of hermes-files.<domain> moves here from the filebrowser directory, its backendRef naming that Service.
  • kubernetes/apps/grachevko/filebrowser/ — the HelmRelease, its kustomization and the Kustomization of the application are gone; the directory keeps what the pod of the agent reads: the configuration from git, the claim of the file manager itself and its own pocket-id login (config/, secrets/). The claim keeps the prune: disabled annotation, so nothing about this change can take the data with it.
  • kubernetes/apps/grachevko/hermes/ks.yaml — the Kustomization of the agent depends on those three, so the file, the claim and the client exist before the container is created.

What it costs: the readiness of the file manager is now the readiness of the pod, so a broken file manager takes the agent's own routes out of their Services. The memory of the node is not part of the price — the limit is the container's own, and the pod keeps the quality-of-service class it had (its requests are below its limit either way).

Verified

  • flate test all -p ./kubernetes/flux/cluster — 307 passed; flate build hr hermes -n grachevko renders the pod with app and filebrowser, the two init containers, /etc/filebrowser and /home/filebrowser/data mounted for the file manager's containers only, and the Service hermes-filebrowser on port 8080; flate build ks filebrowser -n grachevko renders the ConfigMap and the claim.
  • every key of the new configuration is taken from the image's own sources rather than from 1.x: http.port, http.trustProxyHeaders, server.database.path|migrateFrom, server.cacheDir, server.sources[].config.defaultEnabled|defaultPermissions, auth.tokenExpirationHours (the field is Auth.TokenExpirationHours, tag tokenExpirationHours, in backend/pkg/settings/auth.go), auth.methods.password|oidc, userDefaults.account.permissions (backend/pkg/settings/validConfig.yaml). The file parses to server.database.path and server.sources[0].config.defaultPermissions, and no key of 1.x is left in it.
  • the port name of the file manager is files, not http: the name http in this pod belongs to the agent's own port, and port names are unique within a pod.

After the merge

  1. Watch the first start: the log of the filebrowser container shows Starting migration from BoltDB to SQLite … Migration completed successfully!, and the UI must show the users, the shares, the API tokens and the index.
  2. When the migration is confirmed, migrateFrom can be dropped from the ConfigMap (phase 3 of the upstream guide), and both copies of the database — database.db.old on the claim of the file manager and .filebrowser-database.db.backup on the volume of the agent — can be deleted.
  3. The name hermes-files.<domain> keeps answering through the same route; its backend is now the hermes-filebrowser Service of the agent's release, and the old filebrowser Service disappears with the old release.

Rollback

git revert of the merge commit restores the second pod and the configuration of 1.x, which the 2.0.0 image refuses: the way back to a working file manager is the 1.5.8-stable image with the reverted configuration, plus database.db.old renamed back to database.db. The volumes are untouched by the migration itself.

## What was wrong Two things at once, one of them the reason the other had to be done here rather than in place. 1. **The file manager does not start.** The release has been on the `2.0.0-stable` image since the Renovate major bump, and the application refuses the configuration of 1.x: ``` [ERROR] unable to load config, waiting 5 seconds before exiting... [FATAL] error unmarshaling YAML data: [19:3] unknown field "port" 17 | server: 18 | cacheDir: /home/filebrowser/data/cache > 19 | port: 8080 ``` 2. **The file manager is a second pod on the claim of the agent, and that costs a manual step on every eviction.** The claim is ReadWriteOnce: it is held by the pods of ONE node at a time, while the scheduler does not see the attachment (the PV of this storage class carries only a zone affinity, and the zone is the same for all three nodes). The agent can therefore be rescheduled onto another node while the file manager stays on the old one, and the claim cannot be attached to the new node until that pod is deleted by hand. ## What this fixes - `kubernetes/apps/grachevko/filebrowser/app/configmap.yaml` — the configuration in the shape of 2.0.0: `http.port` and the single `http.trustProxyHeaders` in place of the trusted-headers list; `server.database` on SQLite with `migrateFrom`; `server.sources[].config.defaultPermissions` in place of the account permissions (with `view`, a right of its own in 2.0.0); `userDefaults.account.permissions` narrowed to what stays global. What changed is where each of our settings lives, not what it is: the port stays 8080 with no capability to bind a privileged port, the login stays pocket-id only, the session stays 24 hours, the source stays the whole volume of the agent. - `kubernetes/apps/grachevko/hermes/app/helmrelease.yaml` — the file manager becomes a container of the agent's pod (its image, limits, probes and login read from the ConfigMap and the Secret of the filebrowser directory), and an init container renames the BoltDB file of 1.x once (`database.db` → `database.db.old`, with a copy outside the working directory on the claim of the agent), which is what the import of 2.0.0 waits for. The volumes of the file manager are mounted for its own containers (`advancedMounts`), the claim of the agent only as before. Its Service is `hermes-filebrowser`, as app-template names one after the release and the key of the service map. - `kubernetes/apps/grachevko/hermes/app/filebrowser-httproute.yaml` — the route of `hermes-files.<domain>` moves here from the filebrowser directory, its `backendRef` naming that Service. - `kubernetes/apps/grachevko/filebrowser/` — the HelmRelease, its kustomization and the Kustomization of the application are gone; the directory keeps what the pod of the agent reads: the configuration from git, the claim of the file manager itself and its own pocket-id login (`config/`, `secrets/`). The claim keeps the `prune: disabled` annotation, so nothing about this change can take the data with it. - `kubernetes/apps/grachevko/hermes/ks.yaml` — the Kustomization of the agent depends on those three, so the file, the claim and the client exist before the container is created. What it costs: the readiness of the file manager is now the readiness of the pod, so a broken file manager takes the agent's own routes out of their Services. The memory of the node is not part of the price — the limit is the container's own, and the pod keeps the quality-of-service class it had (its requests are below its limit either way). ## Verified - `flate test all -p ./kubernetes/flux/cluster` — 307 passed; `flate build hr hermes -n grachevko` renders the pod with `app` and `filebrowser`, the two init containers, `/etc/filebrowser` and `/home/filebrowser/data` mounted for the file manager's containers only, and the Service `hermes-filebrowser` on port 8080; `flate build ks filebrowser -n grachevko` renders the ConfigMap and the claim. - every key of the new configuration is taken from the image's own sources rather than from 1.x: `http.port`, `http.trustProxyHeaders`, `server.database.path|migrateFrom`, `server.cacheDir`, `server.sources[].config.defaultEnabled|defaultPermissions`, `auth.tokenExpirationHours` (the field is `Auth.TokenExpirationHours`, tag `tokenExpirationHours`, in `backend/pkg/settings/auth.go`), `auth.methods.password|oidc`, `userDefaults.account.permissions` (`backend/pkg/settings/validConfig.yaml`). The file parses to `server.database.path` and `server.sources[0].config.defaultPermissions`, and no key of 1.x is left in it. - the port name of the file manager is `files`, not `http`: the name `http` in this pod belongs to the agent's own port, and port names are unique within a pod. ## After the merge 1. Watch the first start: the log of the `filebrowser` container shows `Starting migration from BoltDB to SQLite` … `Migration completed successfully!`, and the UI must show the users, the shares, the API tokens and the index. 2. When the migration is confirmed, `migrateFrom` can be dropped from the ConfigMap (phase 3 of the upstream guide), and both copies of the database — `database.db.old` on the claim of the file manager and `.filebrowser-database.db.backup` on the volume of the agent — can be deleted. 3. The name `hermes-files.<domain>` keeps answering through the same route; its backend is now the `hermes-filebrowser` Service of the agent's release, and the old `filebrowser` Service disappears with the old release. ## Rollback `git revert` of the merge commit restores the second pod and the configuration of 1.x, which the 2.0.0 image refuses: the way back to a working file manager is the `1.5.8-stable` image with the reverted configuration, plus `database.db.old` renamed back to `database.db`. The volumes are untouched by the migration itself.
fix(filebrowser): migrate the configuration and the database to 2.0.0
Some checks failed
Flate / Flate - Filter (pull_request) Successful in 3s
Flux Local / Flux Local - Test (pull_request) Successful in 1m5s
Flux Local / Flux Local - Diff (pull_request) Successful in 1m37s
Flate / Flate (pull_request) Successful in 25s
Flux Local / Flux Local - Success (pull_request) Successful in 3s
Flate / Flate - Success (pull_request) Successful in 3s
LLM Review / LLM Review (pull_request) Failing after 6m59s
LLM Review / LLM Review - Success (pull_request) Failing after 3s
ffc557c56a
The release has been on the 2.0.0 image since the Renovate major bump, and the application refuses the
configuration of 1.x:

  [FATAL] error unmarshaling YAML data: [19:3] unknown field "port"

2.0.0 moved the HTTP options out of `server` into their own `http` block, replaced the BoltDB database
with SQLite, and moved the file operations of a user (view, download, modify, create, delete) from
`userDefaults` to the source. The keys of the new file are taken from the image itself
(`backend/pkg/settings/auth.go`, `backend/pkg/settings/validConfig.yaml`) and from the v2 migration
guide: what changed is where each of our settings lives, not what it is.

- kubernetes/apps/grachevko/filebrowser/app/configmap.yaml: the configuration in the shape of 2.0.0 -
  http.port and http.trustProxyHeaders in place of the trusted-headers list, server.database on SQLite
  with migrateFrom pointing at the renamed file of 1.x, server.sources[].config.defaultPermissions in
  place of the account permissions, and userDefaults.account.permissions narrowed to what 2.0.0 keeps
  there (share, admin).
- kubernetes/apps/grachevko/filebrowser/app/helmrelease.yaml: an init container renames the BoltDB file
  once (database.db -> database.db.old) and keeps a copy outside the working directory on the claim of
  the agent, because 2.0.0 refuses to start while an unrenamed database.db lies in the working
  directory. Idempotent: on a fresh volume there is nothing to rename, and the import itself runs
  inside the application on the first start.

Records of the checks and the follow-up steps are in the body of the pull request.
Collaborator

Failed to update PR description

Failed to update PR description

Rendered diff of the HelmReleases in this pull request:

--- HelmRelease: grachevko/hermes Deployment: grachevko/hermes

+++ HelmRelease: grachevko/hermes Deployment: grachevko/hermes

@@ -55,12 +55,37 @@

           runAsGroup: 0
           runAsNonRoot: false
           runAsUser: 0
         volumeMounts:
         - mountPath: /opt/data
           name: data
+        - mountPath: /etc/hermes/.env
+          name: managed-env
+          readOnly: true
+          subPath: .env
+        - mountPath: /run
+          name: run
+        - mountPath: /tmp
+          name: tmp
+      - command:
+        - sh
+        - -c
+        - |
+          set -eu
+          cd /home/filebrowser/data
+          if [ -f database.db ] && [ ! -f database.db.old ]; then
+            cp -a database.db /opt/data/.filebrowser-database.db.backup
+            mv database.db database.db.old
+          fi
+        image: docker.io/library/busybox:1.38.0@sha256:fd7dc98638c8e305f4dc34e979f1c0fdfdcaeb0fbf8fcff77ae834b6da3d7e6e
+        name: migrate-state
+        volumeMounts:
+        - mountPath: /opt/data
+          name: data
+        - mountPath: /home/filebrowser/data
+          name: filebrowser-state
         - mountPath: /etc/hermes/.env
           name: managed-env
           readOnly: true
           subPath: .env
         - mountPath: /run
           name: run
@@ -132,16 +157,85 @@

           readOnly: true
           subPath: .env
         - mountPath: /run
           name: run
         - mountPath: /tmp
           name: tmp
+      - env:
+        - name: FILEBROWSER_CONFIG
+          value: /etc/filebrowser/config.yaml
+        - name: FILEBROWSER_OIDC_CLIENT_ID
+          valueFrom:
+            secretKeyRef:
+              key: client_id
+              name: filebrowser-oidc
+        - name: FILEBROWSER_OIDC_CLIENT_SECRET
+          valueFrom:
+            secretKeyRef:
+              key: client_secret
+              name: filebrowser-oidc
+        - name: SSL_CERT_FILE
+          value: /etc/ssl/certs/internal-ca/ca.crt
+        - name: TZ
+          value: Europe/Moscow
+        image: gtstef/filebrowser:2.0.0-stable@sha256:9d42f635e32913d7daf05259428b6c70061cfdca3affc9602b4c31717e0a4bae
+        name: filebrowser
+        ports:
+        - containerPort: 8080
+          name: files
+          protocol: TCP
+        readinessProbe:
+          httpGet:
+            path: /health
+            port: files
+          periodSeconds: 10
+          timeoutSeconds: 5
+        resources:
+          limits:
+            memory: 1Gi
+          requests:
+            cpu: 25m
+            memory: 128Mi
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+          readOnlyRootFilesystem: true
+        startupProbe:
+          failureThreshold: 30
+          httpGet:
+            path: /health
+            port: files
+          periodSeconds: 10
+        volumeMounts:
+        - mountPath: /opt/data
+          name: data
+        - mountPath: /etc/filebrowser
+          name: filebrowser-config
+          readOnly: true
+        - mountPath: /home/filebrowser/data
+          name: filebrowser-state
+        - mountPath: /etc/hermes/.env
+          name: managed-env
+          readOnly: true
+          subPath: .env
+        - mountPath: /run
+          name: run
+        - mountPath: /tmp
+          name: tmp
       volumes:
       - name: data
         persistentVolumeClaim:
           claimName: hermes
+      - configMap:
+          name: filebrowser
+        name: filebrowser-config
+      - name: filebrowser-state
+        persistentVolumeClaim:
+          claimName: filebrowser
       - name: managed-env
         secret:
           secretName: hermes-managed-env
       - emptyDir: {}
         name: run
       - emptyDir: {}
--- HelmRelease: grachevko/hermes Service: grachevko/hermes-filebrowser

+++ HelmRelease: grachevko/hermes Service: grachevko/hermes-filebrowser

@@ -0,0 +1,23 @@

+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: hermes-filebrowser
+  labels:
+    app.kubernetes.io/instance: hermes
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: hermes
+    app.kubernetes.io/service: hermes-filebrowser
+  namespace: grachevko
+spec:
+  type: ClusterIP
+  ports:
+  - port: 8080
+    targetPort: 8080
+    protocol: TCP
+    name: files
+  selector:
+    app.kubernetes.io/controller: hermes
+    app.kubernetes.io/instance: hermes
+    app.kubernetes.io/name: hermes
+
--- HelmRelease: grachevko/filebrowser ServiceAccount: grachevko/filebrowser

+++ HelmRelease: grachevko/filebrowser ServiceAccount: grachevko/filebrowser

@@ -1,11 +0,0 @@

----
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  name: filebrowser
-  labels:
-    app.kubernetes.io/instance: filebrowser
-    app.kubernetes.io/managed-by: Helm
-    app.kubernetes.io/name: filebrowser
-  namespace: grachevko
-
--- HelmRelease: grachevko/filebrowser Service: grachevko/filebrowser

+++ HelmRelease: grachevko/filebrowser Service: grachevko/filebrowser

@@ -1,23 +0,0 @@

----
-apiVersion: v1
-kind: Service
-metadata:
-  name: filebrowser
-  labels:
-    app.kubernetes.io/instance: filebrowser
-    app.kubernetes.io/managed-by: Helm
-    app.kubernetes.io/name: filebrowser
-    app.kubernetes.io/service: filebrowser
-  namespace: grachevko
-spec:
-  type: ClusterIP
-  ports:
-  - port: 8080
-    targetPort: 8080
-    protocol: TCP
-    name: http
-  selector:
-    app.kubernetes.io/controller: filebrowser
-    app.kubernetes.io/instance: filebrowser
-    app.kubernetes.io/name: filebrowser
-
--- HelmRelease: grachevko/filebrowser Deployment: grachevko/filebrowser

+++ HelmRelease: grachevko/filebrowser Deployment: grachevko/filebrowser

@@ -1,130 +0,0 @@

----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: filebrowser
-  labels:
-    app.kubernetes.io/controller: filebrowser
-    app.kubernetes.io/instance: filebrowser
-    app.kubernetes.io/managed-by: Helm
-    app.kubernetes.io/name: filebrowser
-  annotations:
-    reloader.stakater.com/auto: 'true'
-  namespace: grachevko
-spec:
-  revisionHistoryLimit: 3
-  replicas: 1
-  strategy:
-    type: Recreate
-  selector:
-    matchLabels:
-      app.kubernetes.io/controller: filebrowser
-      app.kubernetes.io/instance: filebrowser
-      app.kubernetes.io/name: filebrowser
-  template:
-    metadata:
-      labels:
-        app.kubernetes.io/controller: filebrowser
-        app.kubernetes.io/instance: filebrowser
-        app.kubernetes.io/name: filebrowser
-    spec:
-      enableServiceLinks: false
-      serviceAccountName: filebrowser
-      automountServiceAccountToken: false
-      securityContext:
-        fsGroup: 10000
-        fsGroupChangePolicy: OnRootMismatch
-        runAsGroup: 10000
-        runAsNonRoot: true
-        runAsUser: 10000
-        seccompProfile:
-          type: RuntimeDefault
-      hostIPC: false
-      hostNetwork: false
-      hostPID: false
-      dnsPolicy: ClusterFirst
-      affinity:
-        podAffinity:
-          requiredDuringSchedulingIgnoredDuringExecution:
-          - labelSelector:
-              matchLabels:
-                app.kubernetes.io/instance: hermes
-                app.kubernetes.io/name: hermes
-            topologyKey: kubernetes.io/hostname
-      containers:
-      - env:
-        - name: FILEBROWSER_CONFIG
-          value: /etc/filebrowser/config.yaml
-        - name: FILEBROWSER_OIDC_CLIENT_ID
-          valueFrom:
-            secretKeyRef:
-              key: client_id
-              name: filebrowser-oidc
-        - name: FILEBROWSER_OIDC_CLIENT_SECRET
-          valueFrom:
-            secretKeyRef:
-              key: client_secret
-              name: filebrowser-oidc
-        - name: SSL_CERT_FILE
-          value: /etc/ssl/certs/internal-ca/ca.crt
-        - name: TZ
-          value: Europe/Moscow
-        image: gtstef/filebrowser:2.0.0-stable@sha256:9d42f635e32913d7daf05259428b6c70061cfdca3affc9602b4c31717e0a4bae
-        name: app
-        ports:
-        - containerPort: 8080
-          name: http
-          protocol: TCP
-        readinessProbe:
-          httpGet:
-            path: /health
-            port: http
-          periodSeconds: 10
-          timeoutSeconds: 5
-        resources:
-          limits:
-            memory: 1Gi
-          requests:
-            cpu: 25m
-            memory: 128Mi
-        securityContext:
-          allowPrivilegeEscalation: false
-          capabilities:
-            drop:
-            - ALL
-          readOnlyRootFilesystem: true
-        startupProbe:
-          failureThreshold: 30
-          httpGet:
-            path: /health
-            port: http
-          periodSeconds: 10
-        volumeMounts:
-        - mountPath: /opt/data
-          name: agent
-        - mountPath: /etc/filebrowser
-          name: config
-          readOnly: true
-        - mountPath: /etc/ssl/certs/internal-ca
-          name: internal-ca
-          readOnly: true
-        - mountPath: /home/filebrowser/data
-          name: state
-        - mountPath: /tmp
-          name: tmp
-      volumes:
-      - name: agent
-        persistentVolumeClaim:
-          claimName: hermes
-      - configMap:
-          name: filebrowser
-        name: config
-      - configMap:
-          name: internal-ca
-        name: internal-ca
-      - name: state
-        persistentVolumeClaim:
-          claimName: filebrowser
-      - emptyDir: {}
-        name: tmp
-
Rendered diff of the HelmReleases in this pull request: ```diff --- HelmRelease: grachevko/hermes Deployment: grachevko/hermes +++ HelmRelease: grachevko/hermes Deployment: grachevko/hermes @@ -55,12 +55,37 @@ runAsGroup: 0 runAsNonRoot: false runAsUser: 0 volumeMounts: - mountPath: /opt/data name: data + - mountPath: /etc/hermes/.env + name: managed-env + readOnly: true + subPath: .env + - mountPath: /run + name: run + - mountPath: /tmp + name: tmp + - command: + - sh + - -c + - | + set -eu + cd /home/filebrowser/data + if [ -f database.db ] && [ ! -f database.db.old ]; then + cp -a database.db /opt/data/.filebrowser-database.db.backup + mv database.db database.db.old + fi + image: docker.io/library/busybox:1.38.0@sha256:fd7dc98638c8e305f4dc34e979f1c0fdfdcaeb0fbf8fcff77ae834b6da3d7e6e + name: migrate-state + volumeMounts: + - mountPath: /opt/data + name: data + - mountPath: /home/filebrowser/data + name: filebrowser-state - mountPath: /etc/hermes/.env name: managed-env readOnly: true subPath: .env - mountPath: /run name: run @@ -132,16 +157,85 @@ readOnly: true subPath: .env - mountPath: /run name: run - mountPath: /tmp name: tmp + - env: + - name: FILEBROWSER_CONFIG + value: /etc/filebrowser/config.yaml + - name: FILEBROWSER_OIDC_CLIENT_ID + valueFrom: + secretKeyRef: + key: client_id + name: filebrowser-oidc + - name: FILEBROWSER_OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + key: client_secret + name: filebrowser-oidc + - name: SSL_CERT_FILE + value: /etc/ssl/certs/internal-ca/ca.crt + - name: TZ + value: Europe/Moscow + image: gtstef/filebrowser:2.0.0-stable@sha256:9d42f635e32913d7daf05259428b6c70061cfdca3affc9602b4c31717e0a4bae + name: filebrowser + ports: + - containerPort: 8080 + name: files + protocol: TCP + readinessProbe: + httpGet: + path: /health + port: files + periodSeconds: 10 + timeoutSeconds: 5 + resources: + limits: + memory: 1Gi + requests: + cpu: 25m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + startupProbe: + failureThreshold: 30 + httpGet: + path: /health + port: files + periodSeconds: 10 + volumeMounts: + - mountPath: /opt/data + name: data + - mountPath: /etc/filebrowser + name: filebrowser-config + readOnly: true + - mountPath: /home/filebrowser/data + name: filebrowser-state + - mountPath: /etc/hermes/.env + name: managed-env + readOnly: true + subPath: .env + - mountPath: /run + name: run + - mountPath: /tmp + name: tmp volumes: - name: data persistentVolumeClaim: claimName: hermes + - configMap: + name: filebrowser + name: filebrowser-config + - name: filebrowser-state + persistentVolumeClaim: + claimName: filebrowser - name: managed-env secret: secretName: hermes-managed-env - emptyDir: {} name: run - emptyDir: {} --- HelmRelease: grachevko/hermes Service: grachevko/hermes-filebrowser +++ HelmRelease: grachevko/hermes Service: grachevko/hermes-filebrowser @@ -0,0 +1,23 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: hermes-filebrowser + labels: + app.kubernetes.io/instance: hermes + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: hermes + app.kubernetes.io/service: hermes-filebrowser + namespace: grachevko +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: files + selector: + app.kubernetes.io/controller: hermes + app.kubernetes.io/instance: hermes + app.kubernetes.io/name: hermes + --- HelmRelease: grachevko/filebrowser ServiceAccount: grachevko/filebrowser +++ HelmRelease: grachevko/filebrowser ServiceAccount: grachevko/filebrowser @@ -1,11 +0,0 @@ ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: filebrowser - labels: - app.kubernetes.io/instance: filebrowser - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/name: filebrowser - namespace: grachevko - --- HelmRelease: grachevko/filebrowser Service: grachevko/filebrowser +++ HelmRelease: grachevko/filebrowser Service: grachevko/filebrowser @@ -1,23 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: filebrowser - labels: - app.kubernetes.io/instance: filebrowser - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/name: filebrowser - app.kubernetes.io/service: filebrowser - namespace: grachevko -spec: - type: ClusterIP - ports: - - port: 8080 - targetPort: 8080 - protocol: TCP - name: http - selector: - app.kubernetes.io/controller: filebrowser - app.kubernetes.io/instance: filebrowser - app.kubernetes.io/name: filebrowser - --- HelmRelease: grachevko/filebrowser Deployment: grachevko/filebrowser +++ HelmRelease: grachevko/filebrowser Deployment: grachevko/filebrowser @@ -1,130 +0,0 @@ ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: filebrowser - labels: - app.kubernetes.io/controller: filebrowser - app.kubernetes.io/instance: filebrowser - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/name: filebrowser - annotations: - reloader.stakater.com/auto: 'true' - namespace: grachevko -spec: - revisionHistoryLimit: 3 - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - app.kubernetes.io/controller: filebrowser - app.kubernetes.io/instance: filebrowser - app.kubernetes.io/name: filebrowser - template: - metadata: - labels: - app.kubernetes.io/controller: filebrowser - app.kubernetes.io/instance: filebrowser - app.kubernetes.io/name: filebrowser - spec: - enableServiceLinks: false - serviceAccountName: filebrowser - automountServiceAccountToken: false - securityContext: - fsGroup: 10000 - fsGroupChangePolicy: OnRootMismatch - runAsGroup: 10000 - runAsNonRoot: true - runAsUser: 10000 - seccompProfile: - type: RuntimeDefault - hostIPC: false - hostNetwork: false - hostPID: false - dnsPolicy: ClusterFirst - affinity: - podAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchLabels: - app.kubernetes.io/instance: hermes - app.kubernetes.io/name: hermes - topologyKey: kubernetes.io/hostname - containers: - - env: - - name: FILEBROWSER_CONFIG - value: /etc/filebrowser/config.yaml - - name: FILEBROWSER_OIDC_CLIENT_ID - valueFrom: - secretKeyRef: - key: client_id - name: filebrowser-oidc - - name: FILEBROWSER_OIDC_CLIENT_SECRET - valueFrom: - secretKeyRef: - key: client_secret - name: filebrowser-oidc - - name: SSL_CERT_FILE - value: /etc/ssl/certs/internal-ca/ca.crt - - name: TZ - value: Europe/Moscow - image: gtstef/filebrowser:2.0.0-stable@sha256:9d42f635e32913d7daf05259428b6c70061cfdca3affc9602b4c31717e0a4bae - name: app - ports: - - containerPort: 8080 - name: http - protocol: TCP - readinessProbe: - httpGet: - path: /health - port: http - periodSeconds: 10 - timeoutSeconds: 5 - resources: - limits: - memory: 1Gi - requests: - cpu: 25m - memory: 128Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - startupProbe: - failureThreshold: 30 - httpGet: - path: /health - port: http - periodSeconds: 10 - volumeMounts: - - mountPath: /opt/data - name: agent - - mountPath: /etc/filebrowser - name: config - readOnly: true - - mountPath: /etc/ssl/certs/internal-ca - name: internal-ca - readOnly: true - - mountPath: /home/filebrowser/data - name: state - - mountPath: /tmp - name: tmp - volumes: - - name: agent - persistentVolumeClaim: - claimName: hermes - - configMap: - name: filebrowser - name: config - - configMap: - name: internal-ca - name: internal-ca - - name: state - persistentVolumeClaim: - claimName: filebrowser - - emptyDir: {} - name: tmp - ```
Collaborator

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ No major issues detected
## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr> <tr><td>🧪&nbsp;<strong>No relevant tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> <tr><td>⚡&nbsp;<strong>No major issues detected</strong></td></tr> </table>
Collaborator

PR Code Suggestions ✨

No code suggestions found for the PR.

## PR Code Suggestions ✨ <!-- pr-agent:improve:no-suggestions --> No code suggestions found for the PR.
fix(filebrowser): run the file manager inside the pod of the agent
Some checks failed
Flate / Flate - Filter (pull_request) Successful in 3s
Flate / Flate (pull_request) Successful in 36s
Flux Local / Flux Local - Success (pull_request) Successful in 3s
Flux Local / Flux Local - Test (pull_request) Successful in 1m9s
Flux Local / Flux Local - Diff (pull_request) Successful in 1m46s
Flate / Flate - Success (pull_request) Successful in 3s
LLM Review / LLM Review (pull_request) Failing after 5m15s
LLM Review / LLM Review - Success (pull_request) Failing after 3s
e4a68675c9
The file manager is a second pod on the same ReadWriteOnce claim, and that costs a manual step on
every eviction: the claim is held by the pods of ONE node at a time, the scheduler does not see the
attachment (the PV of this storage class carries only a zone affinity, and the zone is the same for
every node of the cluster), so the agent can be rescheduled onto another node while the file manager
stays on the old one - and the claim cannot be attached to the new node until that pod is deleted by
hand. Beside the agent the file manager moves with it by construction.

- kubernetes/apps/grachevko/hermes/app/helmrelease.yaml: the filebrowser container (the image of the
  release, its configuration and its login read from the ConfigMap and the Secret of the filebrowser
  directory, its own limits and probes) and the init container that renames the BoltDB file of 1.x.
  The volumes of the file manager are mounted for its own containers only (advancedMounts), the
  claim of the agent as before; the Service of the file manager is hermes-filebrowser, as
  app-template names a Service after the release and the key of the service map.
- kubernetes/apps/grachevko/hermes/app/filebrowser-httproute.yaml: the route of hermes-files.<domain>
  moves here from the filebrowser directory, its backendRef naming that Service.
- kubernetes/apps/grachevko/filebrowser/: the HelmRelease, its kustomization and the Kustomization of
  the application are gone; the directory keeps what the pod of the agent reads - the configuration
  from git, the claim of the file manager itself and its own pocket-id login (config/ and secrets/).
- kubernetes/apps/grachevko/hermes/ks.yaml: the Kustomization of the agent depends on the three of
  them, so the file, the claim and the client exist before the container is created.

What it costs: the readiness of the file manager is the readiness of the pod, so a broken file
manager takes the agent's own routes out of their Services. The memory of the node is not part of the
price: the limit is the container's own, and the pod keeps the class it had.
hermes changed title from fix(filebrowser): migrate the configuration and the database to 2.0.0 to fix(filebrowser): run the file manager inside the pod of the agent on 2.0.0 2026-10-12 15:37:07 +00:00
fix(filebrowser): restore the substitution and make the state paths absolute
All checks were successful
Flux Local / Flux Local - Test (pull_request) Successful in 1m9s
Flate / Flate (pull_request) Successful in 41s
Flux Local / Flux Local - Success (pull_request) Successful in 2s
Flate / Flate - Filter (pull_request) Successful in 3s
Flux Local / Flux Local - Diff (pull_request) Successful in 1m36s
Flate / Flate - Success (pull_request) Successful in 3s
LLM Review / LLM Review (pull_request) Successful in 6m53s
LLM Review / LLM Review - Success (pull_request) Successful in 3s
5fd310def8
The review of the first commit found a real defect: rewriting the Kustomization of the filebrowser
directory dropped its postBuild, while the issuer of the login inside the mounted config.yaml still
carries ${SECRET_DOMAIN} - the placeholder would reach the API server and the operator as a literal,
and the OIDC login of the file manager would break. The substitution is back, on that
Kustomization, with the reason spelled out.

The second finding of the same review - `cacheDir` read as a child of `server.database` - does not
reproduce: the config parses to server keys [database, cacheDir, sources], and `cacheDir` is a
direct child of `server`, which is where 2.0.0 keeps it (`backend/pkg/settings/validConfig.yaml`).
No edit follows from it.

On the same line, the two state paths become absolute: the working directory of the image is not a
contract, and a relative path would land the database outside the claim that exists for it.
grachevko deleted branch fix/filebrowser-v2 2026-10-12 16:04:23 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
grachevko/home-ops!398
No description provided.