Autopulse¶
Media-automation app in the media namespace (bjw-s app-template HelmRelease).
Receives import/upgrade webhooks from Sonarr and Radarr and tells Jellyfin to
rescan just the changed paths.
Purpose¶
- Sonarr and Radarr trigger autopulse on import/upgrade; autopulse's one target
is Jellyfin (
http://jellyfin.media.svc.cluster.local:8096), scanned per path rather than whole-library. - Web UI reachable at
autopulse.${SECRET_DOMAIN}viaenvoy-internal(internal-only); SQLite database persisted on a VolSync-backed PVC. - Not in scope: a Plex target, or exposing the app externally.
Design decisions¶
- Jellyfin is the only target. Plex is deliberately absent. Sonarr and
Radarr both already carry a native "Plex Media Server" Connect notification
(
updateLibrary: true, on import and upgrade), so a Plex target here would double-scan. Jellyfin has no such native notifier in either app and, with the library mounted over NFS, receives no filesystem events. That is the gap autopulse closes. - No
rewrite:blocks. The Sonarr/Radarr root folders are/media/tvand/media/movies, and autopulse, Sonarr, Radarr and Jellyfin all mount the same export at/media, so the path a trigger sends is already the path Jellyfin expects.opts.check_path: truemakes autopulse stat the file before notifying, which is why the HelmRelease mounts the media export read-only. - Trigger auth is the app's basic auth. autopulse has no per-trigger token;
Sonarr/Radarr POST to
/triggers/sonarr//triggers/radarrwith the sameauth.username/auth.passwordas the UI. - SQLite, not Postgres. v2.0.0 changed the default
database_urlfrom PostgreSQL to a SQLite file (sqlite://data/autopulse.db, resolving to/app/data/autopulse.db). The minimal config embraces this: no database dependency, just a single file on a small PVC. - Single-file SQLite DB backed up with VolSync. The whole "database" is one file living on a PVC, so the standard VolSync component backs it up exactly like any other app's data dir, with no DB-specific dump/restore tooling needed:
- The
volsynccomponent (added inks.yamlspec.components, never also inapp/kustomization.yaml) auto-creates the PVCautopulse(ceph-block), the NFS + remoteReplicationSources, and the restoreReplicationDestination. VOLSYNC_CAPACITY: 1Gibecause a SQLite DB is tiny (the component default is 5Gi, overridden down here).- Backup credentials reuse the shared VolSync 1Password items; no new backup secrets are introduced.
- Writable PVC at
/app/data, root FS stays read-only. The HelmRelease keepsreadOnlyRootFilesystem: true; SQLite still works because the writable mount is the PVC at/app/data, not the root FS:
- Inline
config.yamlin the ExternalSecret. The earliertemplateFrom.configMappointed at a ConfigMap that never existed in Git or the cluster, so it was dropped. The config is rendered inline; only the auth credentials come from 1Password, while the non-secretdatabase_urllives in the template:
target:
name: autopulse-secret
template:
engineVersion: v2
data:
config.yaml: |
app:
database_url: sqlite://data/autopulse.db
auth:
username: "{{ .username }}"
password: "{{ .password }}"
dataFrom:
- extract:
key: autopulse
Deploy gotchas¶
- An unconfigured v2 pod cannot start. With
readOnlyRootFilesystem: trueand no writable mount at/app/data, v2 cannot create the SQLite file. The writabledataPVC (above) is mandatory, not optional. existingClaimmust match the VolSync PVC name. The HelmRelease usesexistingClaim: "{{ .Release.Name }}", which resolves toautopulse, the exact name the VolSync component derives fromAPP. PVC name, claim, andReplicationSourcesourcePVCmust all agree.- Default credentials must be changed. v2 ships
admin/passworddefaults. Create theautopulse1Password login item (usernameadmin+ a generated 32-char password) viaop item createin the operator's signed-in shell, never the UI, never committed. The field labelsusername/passwordmust match the{{ .username }}/{{ .password }}template vars. The Jellyfin API key (Dashboard → API Keys, one issued forautopulse) lives on the same item as a concealedJELLYFIN_TOKENfield. - The Sonarr/Radarr side is not in Git. Each app needs a Connect → Webhook
(Settings → Connect) pointing at
http://autopulse.media.svc.cluster.local:2875/triggers/<sonarr|radarr>, methodPOST, basic auth with the autopulse credentials, on Import and Upgrade (Rename/Delete too if wanted). Re-add them after any *arr restore. - First-deploy restore finds no snapshot. This is standard VolSync
bootstrap: the
ReplicationDestinationprovisions an empty PVC and the app initializes a fresh DB. No manual action.
Operational notes¶
- Enable/disable lives in the namespace
kustomization.yaml: the app entry- ./autopulse/ks.yamlis commented out when dormant. Keep it in alphabetical position when re-enabling. - Health endpoint
/health(port2875) is unauthenticated in v2, handy for a quick liveness check from inside the pod without credentials. - Verify a healthy deploy by checking, in order:
Kustomization autopulsereconcilesReady=True.ExternalSecret autopulse-secretisSecretSynced=Trueand the rendered Secret carries aconfig.yamlwith the auth block (aSecretSyncedErroralmost always means the 1Password item is in the wrong vault).- PVC
autopulseisBound(1Gi,ceph-block);ReplicationSources for NFS and remote both exist. - Pod is
1/1 Ready; logs show the SQLite DB created at/app/data/autopulse.dbwith no auth/config-load errors.