Operate

Jobs and scheduler operation

Run, inspect and maintain scheduled work.

Scheduler and jobs

For manual VPS deployments, install the scheduler orchestrator once per server. Register every deployed project; scheduling is required even without application-specific tasks:

sudo php /var/www/site/core/cli/nimbly.php scheduler:install
sudo php /var/www/site/core/cli/nimbly.php scheduler:project:add site /var/www/site
sudo php /var/www/site/core/cli/nimbly.php scheduler:cron:install --user=www-data

This creates /etc/cron.d/nimbly-scheduler, which runs /usr/local/bin/nimbly-scheduler-orchestrator every minute as www-data, and /etc/logrotate.d/nimbly-scheduler, which rotates the scheduler log weekly. The orchestrator then dispatches registered project schedulers independently. In Docker containers the scheduler runs automatically, so no host cron setup is required.

Run schedule:init once to create project schedule files in ext/cli/:

./nimbly schedule:init

This generates three files:

File Used when
ext/cli/schedule.inc APP_ENV=dev or no env-specific file found
ext/cli/schedule.prod.inc APP_ENV=prod
ext/cli/schedule.stage.inc APP_ENV=stage

The prod and stage schedules include ext:sync, which commits and pushes data changes in ext/ back to the repository every hour. The dev schedule does not. Customize each file independently for environment-specific tasks.

When migrating an existing VPS, first install the orchestrator, register the projects, run scheduler:run --dry-run, run the wrapper once manually as www-data, and confirm /var/log/nimbly-scheduler.log. Back up the old www-data crontab under /etc/nimbly/backups/, then remove only migrated per-project schedule:run lines. Do not remove unrelated cron jobs.

Admin git pull remains available under Settings as a simple self-managed update option. For release-managed sites, prefer a repeatable deploy path: pull the intended git revision, run setup/build/lint, and make sure the scheduler is active.


Mandatory maintenance and session expiry

Every schedule:run invocation includes the Core task definitions before it loads the application's schedule. Application schedules add tasks; they cannot remove or reschedule these mandatory invocations:

Task ID Command Interval
sessions-prune sessions:prune 30 minutes
jobs-run jobs:run 10 1 minute
jobs-prune jobs:prune --days=30 24 hours
history-prune history:prune --days=90 24 hours
agent-prune agent:prune --days=30 24 hours
stats-rollup stats:rollup 1 hour

Existing application entries with these IDs or commands are deduplicated. Override an implementation, not its schedule: define the same command in ext/cli/commands.php, using the normal command registry contract. Core trusts the implementation's exit status. No inspection of its internal work occurs. The host registry lists projects, not task choices; legacy enabled flags no longer suppress registered projects. Remove retired deployments from the registry. Before registering an older unscheduled project, review its queued jobs and application schedule: enabling processing may deliver pending email or execute other queued work.

./nimbly schedule:status reports successful mandatory task completion and exits nonzero when a task failed, never succeeded, or is overdue by more than two intervals. A successful run with an empty queue is healthy. The admin Needs attention band and host audit independently read this state, so they also detect a stopped scheduler. Check this command after every deployment; installing cron alone is not proof of working maintenance. The installed host audit ships a root-owned nimbly-maintenance.php helper beside its collector.

For VPS installations, use the existing host orchestrator and register all active projects. Container images already invoke the project scheduler every minute. Other cloud platforms must invoke the same CLI every minute using a single scheduler with access to the application's persistent ext/data and runtime configuration. Monitor schedule:status independently of that runner. No extra session-cleanup daemon or per-project cleanup registration is needed.

Session functions, including file inspection and cleanup, live in core/lib/session.php. Defaults are rolling inactivity limits: anonymous 4 hours, authenticated users 3 days, admins 7 days. There is no absolute login-age limit: regular activity keeps an authenticated session alive. Session ID rotation does not reset its identity or sign the user out. Anonymous reads and permission checks do not create sessions; forms and other stateful features may do so. Authenticated cookies persist across browser restarts; anonymous cookies remain browser-session cookies.

Every cookie core sets has the same options (session_cookie_options()): the site's base path, HttpOnly, SameSite=Lax, and Secure on https. That goes for the session cookie, the form key of a visitor without a session (key) and a variable kept with [#set session ...#] when there is no session; the last two last 30 days (persistent_cookie_options()). Scripts in the page cannot read them.

Projects can configure durations in seconds under session in .config/site:

{
  "session": {
    "anonymous": 14400,
    "authenticated": 259200,
    "roles": { "admin": 604800, "editor": 259200 }
  }
}

The longest matching assigned-role override wins; otherwise the authenticated default applies. Temporary role switching does not change the assigned-role snapshot. Legacy sessions retain their original 24-hour validity until their first valid live request upgrades them; an already-expired session is never revived. Login, cookies, and cleanup use the same policy. Core disables PHP's request-triggered garbage collection for its private session directory.

./nimbly sessions:prune --dry-run reports aggregate counts without changing sessions. Normal execution streams files, skips locked files and symlinks, and removes expired sessions only. Malformed, unsupported serialized objects or references, and files larger than 1 MiB are reported as failures rather than deleted. The reader accepts scalar/array php and php_serialize encodings. The separate Sign out everyone action intentionally removes every session. Remember-me UI and its longer timeout are deferred.

Jobs and Site settings

/nb-admin/jobs (core/modules/admin/lib/jobs-panel/) is a diagnostic view over the job queue and scheduler - queued/running/done/failed counts, a table of jobs with their last error, and "Run due jobs now" / "Prune old jobs" actions, gated on view-.jobs / manage-.jobs. "Prune old jobs" removes both done jobs (aged off completed_at) and terminally failed jobs with no retries left (aged off _modified) older than 30 days - the same rule the scheduled jobs:prune --days=30 task applies, so a fixed-and-forgotten failure eventually drops off the Needs attention band on its own. Each done/failed row also has its own Delete button (gated on manage-.jobs) for clearing an individual job immediately regardless of age - queued/running rows show it disabled, since deleting an in-flight job could orphan a lock or a running process.

/nb-admin/settings (core/modules/admin/lib/site-settings/) is one flat page gated on edit-.config. It edits the site-wide .config/site record: name, description, languages, text direction (direction, ltr/rtl, rendered as <html dir>) and admin sidebar position (nimblybar.side). Name, description, direction and side are per-language values, and each field has its own language switch. On save, direction and side are filled in for every site language: Arabic, Persian and Hebrew start rtl with the sidebar on the right, and other languages start ltr and left. For users with manage-system, it also holds the custom pages switch with its page types, and the navigation editing switch (.config/managed_pages). Each switch shows only when there is a pages resource or navigation slots to switch on. Pages appear under Resources in the Nimbly bar; the Navigation admin tab is hidden for everyone while its switch is off. This is distinct from the per-page title editor behind the nimblybar gear icon, which edits page_settings.page_title for the current page only.

View this page as Markdown