Skip to content

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:

docker exec calserver-api-v2 php artisan migrate --force

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:

docker exec calserver-api-v2 grep <run-id> storage/logs/laravel.log

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=false turns 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 client calserver-sync and are read nowhere in calServer V2. They remain in the property table only.
  • Session timeout is container configuration in V2 (SANCTUM_EXPIRATION), not a tenant setting.
  • Updates have their own page: Update guide.