Operations¶
Administration > Operations (/v2/admin/operations) manages the running installation: scheduled tasks, cache, database backups, TLS certificates and storage paths. In calServer V1 this was the "Administration" tab of the basic settings.
Three permissions on one screen
The tabs sit behind different operations because the endpoints behind them do. A tab whose operation the user does not hold is hidden — the screen does not run into an error.
| Tab | Operation |
|---|---|
| Scheduled tasks (incl. cache) | administration_management |
| Backups | database_management_admin |
| Certificates | basic_setting_admin |
| File storage | basic_setting_admin |
Database schema state¶
The first card in the Scheduled tasks tab. It is collapsed by default and speaks up only when something is wrong — and then first, because it names the cause whose consequences sit below it.
| Message | Meaning |
|---|---|
| The schema matches the release | Every shipped migration has run. The normal case. |
| n migrations are pending | The migration run at container start aborted. Everything after the failing migration was skipped — and stays skipped, because the next start fails at the same point. |
| This database was never migrated | There is no migrations table. The schema is not set up. |
| The schema state cannot be determined | The database did not answer the question. The reason is shown next to it. |
Why this shows up somewhere else entirely
An aborted migration run does not stop the container — the installation starts, the UI comes up. What the skipped migrations would have created is missing, though: one admin screen stays empty, another answers with HTTP 500. Anyone looking only at the symptoms searches inside the screen that shows nothing. This card names the cause instead.
What to do: the reason for the abort is in the container's startup log (docker logs calserver-api-v2, lines starting with ERROR:). Once it is fixed, a new run catches up:
Nothing is executed from the UI on purpose. A migration run against grown customer data does not belong behind a button.
Scheduled tasks¶
What calServer does regularly by itself: outbound mail, appointment reminders, time-based status rules, MET/TEAM sync, housekeeping.
Which tasks exist ships with calServer
This screen sets whether and when a task runs — not which ones there are. The catalogue comes with the release, so the list is complete on a fresh installation, and a task added in an update appears by itself.
Entering your own command is deliberately not possible: a field whose content the server executes would be a way to gain more rights through the UI than the role grants.
This replaces the Cron jobs of earlier releases. That table belonged to calServer V1; calServer V2 never executed it. It stays untouched while both releases run side by side.
The banner at the top¶
It answers the question that comes first during an incident: is the schedule working at all?
| Message | Meaning |
|---|---|
| The scheduler is working | Runs have been recorded. Normal operation. |
| The scheduler is paused | Someone stopped it with Pause. Resume lifts that, immediately and without a restart. The state lives in the database and survives a restart of the installation — paused stays paused. |
| The scheduler is paused from the console | php artisan schedule:pause was run. A separate state next to the switch; Resume lifts that one too. |
| The scheduler is not running | No run recorded for 26 hours. The installation then executes none of the tasks. Under Docker this should not happen. On Kubernetes it runs too, but is not started where several processes would meet a per-container cache store — see Scheduler. |
| The state cannot be assessed | The run history is not readable. Schedule and switches are still correct, but whether anything runs is recorded nowhere. Usually the table is missing because migrations are pending — see Database schema state above. |
Three tasks are switched off after an update
On an installation that already holds data, mail pipeline, appointment reminders and time-based status rules are off after the update. The reason: until then the schedule did not run at all, and those three would work off the whole accumulated backlog in one go — sending reminders for appointments months in the past, for instance.
They did not run before either, so nothing is lost. When you switch one on, calServer asks back and first shows what the initial run will work through — how many emails go out immediately, how many appointment reminders are due, and how many of those belong to appointments more than 90 days in the past. With no backlog the question is skipped.
What the preview does not answer: how many status changes the time-based rules will make. Only the dry run tells you that — a preview that calculates differently from the command itself would be worse than none.
Per task: one row¶
Above the list you see how many tasks there are and how many are switched on. If one has failed or a prerequisite is missing, that shows as a badge on the section header — even while the section is collapsed.
The row itself carries what the overview needs:
| Item | Meaning |
|---|---|
| Coloured dot on the left | State: green Ready, amber Running, red Failed (last run failed), grey Off or Prerequisite missing. Whatever needs attention — Failed, Running, Prerequisite missing — is also spelled out next to it; "Ready" and "Off" are already told by the switch on the right. |
| Pencil icon | This installation changed interval or switch against the default ("customised") |
| Interval in plain language | "Every 5 minutes", "Daily at 03:00" — from medium window width |
| Last and next run | "zuletzt vor 3 Min. · nächster in 2 Min." — from large window width; the exact timestamps are in the tooltip |
| Switch on the right | Turn the task on or off. Takes effect immediately. Switching on a parked task that has a backlog shows the preview first (see above). |
Three icon buttons before it: Run now, History and — only on customised tasks — Reset to default.
Run now triggers the task out of turn; it keeps running in the background even if the page is closed. Tasks that act across the whole dataset ask first.
History shows the last runs with result, duration, trigger and output. A failed run carries its exit code and error text; a run that left no error text points to the server log instead, with the run id as search term. This replaces the log files V1 wrote per cron job — no paths to maintain.
Reset to default restores the release's interval and switch.
Expanded¶
Clicking the name (or the arrow in front of it) expands the row. It holds what the overview does not need:
| Item | Meaning |
|---|---|
| Description | What the task does |
| Interval | Plain-language choice. For special cases Custom expression … with crontab syntax. |
| Repeat per minute | Only for tasks running more often than once a minute (MET/TEAM sync) |
| Last run | Result, duration and trigger (schedule or manual); below it the next run with date and time |
| Failure details | Only when the last run failed: a red box with the exit code and the run's error text (standard and error output, or the exception), plus the run id as search term for the server log |
| Command | The artisan command the task runs |
Several rows can be expanded at once — comparing two tasks does not close the first one.
From failure to log entry
The error text of a failed run sits right in the row — the red dot leads to expanding, expanding leads to the failure box. In addition, calServer logs every failure to the server log under its run id, next to whatever context the command itself logged:
Reaching for the log is only needed for runs that left no error text — history recorded before scheduled runs kept their output, or a process that died hard.
Why a row and not a card
Until August 2026 each task was a card with description, command and three form fields — some 200 pixels, seventeen times over. Four fitted on a screen. The question you almost always have on this page ("is everything running, or is one stuck?") could only be answered by scrolling. Operating still happens in the row: the switch and Run now are the reason you open this page, so they are not hidden behind the expander.
When a task reports a missing prerequisite
The task is not scheduled because a setting outside this screen is missing — for the document inbox, for instance, that it is enabled at all. The responsible configuration key is named in the expanded row. The row deliberately stays instead of disappearing.
Notifications by email¶
This holds the installation default: whether bell-menu notifications are also delivered as mail. Three values — no emails, immediately per event, once a day batched.
Shipped, this is no emails. An update that pushes the whole notification traffic into inboxes that never asked for it would be the wrong kind of surprise.
The default applies to everyone who has not chosen for themselves in their profile. Anyone who has keeps their choice, including when this is changed. The personal setting sits under My profile → Notifications by email (handbook).
Below it each area (orders, messages) can be given its own default — orders immediately and messages batched, for instance. Without a choice an area follows the default above it. The order is: whatever the user chose first (area, then general), then whatever is set here (area, then general). An area default here therefore does not override someone who switched delivery off for themselves.
Immediate delivery means one mail per event
On a busy installation that is a great many. "Once a day batched" instead lists in the evening what came in and is still unread — anyone who looks at the bell during the day gets less, and nothing at all with nothing open.
The daily digest hangs off the scheduled task Notification digest. With the schedule stopped it stays away; immediate delivery goes through the queue and is unaffected.
Queue¶
The scheduler triggers, the queue works off. Those are two processes and either can fail on its own: outbound mail, notifications, imports and backup test runs all go through the queue. If its worker is down, the UI keeps reporting "triggered" and none of it happens.
| Message | Meaning |
|---|---|
| Nothing is waiting | No job is pending. That does not mean a worker is running — that only shows once something is left behind. |
| n jobs in progress | Backlog within the normal range. The oldest entry is named with its age. |
| The queue is stalled | The oldest job has been waiting for more than 15 minutes. Nobody picks it up that long: the worker is probably down. |
| Jobs run without a queue | The driver is set to immediate execution (sync). Not meant for production — a backup run would hold open the request that triggered it. |
Why there is no \"the worker is running\"
A worker does not register anywhere. What is provable is the backlog: if something waits too long, nobody is taking it. The reverse does not hold — an empty queue only says nothing is pending right now. The display is worded accordingly instead of implying certainty.
Failed jobs¶
A job that fails for good lands in its own list and does not run again by itself. Until now that list was only visible on the console (php artisan queue:failed) — a silently lost outbound mail went unnoticed.
Each entry shows job type, queue, timestamp and the first line of the error. Two actions, individually or for all:
| Action | Effect |
|---|---|
| Retry | The job goes back on its original queue and runs as soon as a worker picks it up. |
| Discard | The job is deleted for good. What it was supposed to do will not happen. |
The 20 most recent are shown. "Retry all" and "Discard all" still apply to all of them.
When the list is nothing but the same error
Broadcast jobs (live grid updates) travel the same queue. If a broadcaster is configured that cannot work, one job fails per created record — the list then consists of a single, endlessly repeated entry.
calServer therefore does not select a broadcaster that cannot work: BROADCAST_CONNECTION=pusher only takes effect with complete credentials and the pusher/pusher-php-server package installed. With neither in place, live updates stay off and no job is created at all. When the credentials are set and only the package is missing, the reason is shown above this list. See Configuration.
Cache¶
Flush cache discards cached queries and configuration. Useful when the UI still shows stale values after a change. The next requests are slower until the cache refills; no data is lost.
Above it sits which store this is — more than a footnote, because the schedule depends on it:
| Message | Meaning |
|---|---|
| The cache is shared | Several processes reach the same store (database, redis, memcached, dynamodb). The schedule can coordinate through it. |
| The cache is local to this container | The store lives locally (file, array). Fine on a single instance. With several, each sees its own locks. |
| The cache does not answer | The store is unreachable. Locks fail, and the schedule coordinates through something that is not there. |
Why the cache decides the schedule
Scheduled tasks run with onOneServer(): before each run a lock is placed in the cache so that exactly one process works when several are running. If the store is container-local, every replica sees its own lock, every one believes it is the only one, and every task would run several times. The Helm chart therefore switches the scheduler off rather than letting it run wrongly — details under Scheduler.
So if no task runs on a replicated installation, the answer is here.
Entry count and size are shown as far as the driver gives them up without an expensive full scan: database and file answer that, Redis and Memcached would need a blocking key scan across the whole set — too much for a side note.
Backups¶
State¶
At the top sits the question that comes before any configuration: is there a backup at all? A properly configured action that has been failing for three weeks looked exactly like one that works.
| Message | Meaning |
|---|---|
| Backups are running | There is a current snapshot in the repository, and an active action repeats it. |
| Nothing is being backed up | A prerequisite is missing — repository key or storage target. The exact reason is shown with it. |
| The backup target does not answer | Configured, but access fails. That is a fault, not an open setup task. |
| There is no backup yet | No snapshot in the repository. A test run shows whether it works. |
| The last backup is stale | The newest snapshot is older than 48 hours. |
| Backups are not repeated | There is a backup, but no active action. Without one it stays at that single backup. |
Per action there are two timestamps side by side, deliberately:
| Value | Source |
|---|---|
| Last backup | From the restic repository. A snapshot exists or it does not — that is the truth about success. |
| Last run | From the run history. A run can have failed; the error message is shown below it. |
When they diverge — a run from today, a snapshot from last week — you see exactly the problem this is about. A failed run leaves no snapshot and would be invisible without the run history.
Scheduled runs are in the history now
Previously a nightly backup run only ended up in the application log. Whether it had been failing for weeks could not be answered without a container shell. Manual runs (test run button) and scheduled runs now write to the same history.
Actions¶
Scheduled database backups.
| Field | Meaning |
|---|---|
| Description | Name of the backup |
| Start / End | Window the backup may run in |
| Days of week / months / days of month | crontab fields for the repetition |
| Max. backups | How many backups are kept before the oldest is dropped |
| Active | Inactive entries do not run |
Each row has two extra actions: Clone creates a copy of the configuration (handy for a second, differing backup), Test run triggers a run outside the schedule.
Certificates¶
Whether this installation really runs over HTTPS. The banner at the top says it in one sentence; below it, per domain, which certificate file the reverse proxy would pick up and whether it is any good.
A missing file means that domain runs over HTTP — silently
Without a matching certificate the reverse proxy builds no HTTPS block for that name at all. Every container stays green, no log reports anything. That is exactly the case this tab makes visible.
calServer does not issue certificates. The ACME service next to the reverse
proxy does; this page reads what it serves. For every gap it names the
environment variable (CALSERVER_HOST, ADMINER_HOST, MCP_HOST) and the
deploy command to copy — nothing is executed. Same rule as for scheduled tasks:
a field whose content the server executes would be a way to obtain more
privilege through the screen than the role carries.
Two traps the tab calls out explicitly:
LETSENCRYPT_TEST=falseturns staging on. The switch only checks whether anything is in the variable, not what.- A file that is present need not carry the name. The reverse proxy selects by file name alone; the browser looks at the coverage.
Full version with every state, the ssl_certificate mirror and the Kubernetes
case: TLS certificates.
File storage¶
Usage¶
At the top sit the directories calServer V2 actually uses — read from the container configuration, not from the fields below. If DMS_PATH points at a mounted volume, that volume is what you see here.
Per directory: path, whether it exists, whether it is writable, size and file count. Below that, the free space of the filesystem storage/ lives on.
| Row marker | Meaning |
|---|---|
| Ready | Present and writable. |
| Read-only | Deliberate, not a fault — see the box below. |
| Not created | Does not exist (yet). The backup spool appears with the first run. |
| Not writable | Only for directories V2 writes to: whatever should go there is lost. |
| Not readable | The application cannot open the directory — its contents are therefore missing from the backup. |
| Banner message | Meaning |
|---|---|
| Plenty of space | Below 85 % used, all directories in order. |
| Space is getting tight | 85 % used or more. |
| Storage is nearly full | 95 % used or more. Uploads and backups will fail soon. |
| A directory is not writable | Outranks any fill level: whatever should go there is lost. |
| A directory is not readable | Its contents are missing from the backup without any run failing over it. |
| Free space cannot be determined | The environment gives no answer (hardened PHP configuration). |
Where documents live: two operating modes¶
calServer V1 stores its documents inside the file manager tree ({filemanager}/dms, inbox {filemanager}/inbox). calServer V2 supports both arrangements, and DMS_STORAGE_MODE in the container configuration decides which one applies:
| Mode | For | DMS store | File manager row |
|---|---|---|---|
own (default) |
An installation built alongside an existing V1 | storage/app/dms |
Read-only |
v1-tree |
A plain update of a running installation | {filemanager}/dms |
Ready (V2 writes into it) |
own leaves the V1 tree untouched. V2 only reads it to report usage and include it in backups, which is why the container mounts it read-only. Existing documents are brought over once:
docker exec calserver-api-v2 php artisan dms:import-v1 # report, writes nothing
docker exec calserver-api-v2 php artisan dms:import-v1 --apply --force
This is necessary because the data migration (v1:sync) transfers the document records, not the files. Without the import the documents would appear in every list and every download would come up empty. The command overwrites nothing and is repeatable; whatever it cannot find in the source it reports rather than passing over in silence.
v1-tree adopts the existing tree. Nothing is copied, the documents stay where they are, and V2 keeps writing there. The mount must then be writable — deploy.sh derives that from DMS_STORAGE_MODE, so there is only this one switch to set.
The mode is decided at setup, not later
Switching after the first upload leaves the documents already stored behind at the old location. If you really must switch, copy the store to the new location by hand first.
Why 'Read-only' is not a finding
In own mode the file manager tree belongs to calServer V1: that is where it is written, through the file administration and through WebDAV, and V2 has no writer for it. "Read-only" is a state then, not a fault. Removing the read-only flag without switching the mode puts a second writer on someone else's tree without V2 making any use of it.
In v1-tree mode it is the other way round: the row reads Ready, and a read-only mount is reported as Not writable — there it would be a genuine fault. The card derives this from the actual paths rather than from a second switch, so it cannot show one thing while the other applies.
Why the size measurement can stop early
A recursive walk over a DMS with a million files blocks the request for minutes. It therefore stops after 25,000 entries per directory, and the row says so — an honest partial answer beats an answer that never arrives. Measure sizes / Load without sizes turns the walk off entirely; existence, writability and free space cost nothing and stay.
The backup spool should be empty between two runs. If it is not, a run aborted without cleaning up.
Takeover from V1 (assistant)¶
Below the usage card sits Takeover from V1. It answers the one question of the 1:1 switch where the documents stay physically in place: does the configured storage root point at the files the database knows? It checks a sample of the most recent documents against the storage root and looks whether a DMS sits in the file manager tree ({filemanager}/dms).
| Finding | Meaning |
|---|---|
| Storage complete | Sample hits, root writable — nothing to do. |
| Documents sit in the V1 tree | The root points elsewhere. The card generates the v1-tree switch commands for this installation. |
| Storage read-only | Downloads work, uploads fail — the mount must become rw. |
| Files missing | The V1 tree does not explain it: check paths and volumes, details in the server log. |
| No documents | Nothing to take over; re-check after v1:sync. |
The generated commands come per target platform — Docker (.env lines plus deploy.sh, which derives the mount mode from the storage mode) and OpenShift (oc set env plus readOnly: false on the file manager volumeMount; there is no deploy.sh there, the mount mode has to go into the deployment explicitly). A measured DMS_PATH/DMS_INBOX_PATH override is named explicitly for removal — it would keep overriding the mode otherwise.
The card cannot execute the commands, on purpose: storage root and mount mode belong to the container level, and no web application should be able to repoint its own document store at runtime. After the restart, hit Re-check here — all checkmarks green (root exists, writable, sample complete) means the takeover is done. The full file reconciliation remains with the document management's integrity check.
Paths (V1)¶
Storage paths for documents, uploads, reports and backups.
V1 only
calServer V2 does not read these values. V2 derives its paths from the container configuration (DMS_PATH, FILE_MANAGER_PATH and the other environment variables, see Configuration). The properties apply to calServer V1 and stay editable while both versions run side by side — which is why every field carries the V1 only marker.
| Field | Meaning |
|---|---|
| Storage type | Local directory or remote storage |
| DMS directory | Where document management stores its files |
| Inbox directory | Watched folder the DMS inbox picks files up from |
| Upload directory | Target for uploaded files |
| Report directory | Where generated reports are stored |
| Backup directory | Where database backups are stored |
The switch for the DMS storage layout (flat ↔ sharded) is deliberately not here. It belongs to the dms:migrate-storage command, which moves the files along; as an editable field it would be a button that strands every document.
Not on this screen¶
- SymmetricDS paths (
settings/symmetric) belong to the standalone sync clientcalserver-syncand are read nowhere in calServer V2. They remain in thepropertytable only. - Session timeout is container configuration in V2 (
SANCTUM_EXPIRATION), not a tenant setting. - Updates have their own page: Update guide.