8. Scheduled jobs
Four jobs keep a v1 site current. They are independent of each other, and each can be run by hand at any time.
| Time | Job | Unit / runner | Log |
|---|---|---|---|
| 01:30 | Database backup | eregister-db-backup / /usr/local/bin/eregister-db-backup.sh |
/var/log/eregister-db-backup.log |
| 02:30 | Repo auto-pull | eregister-autopull / /usr/local/bin/eregister-autopull.sh |
/var/log/eregister-autopull.log |
| 03:30 | Clinical form import | eregister-form-import / /usr/local/bin/eregister-form-import.sh |
/var/log/eregister-form-import.log |
| 04:30 | Concept dictionary import | eregister-concept-import / /usr/local/bin/eregister-concept-import.sh |
/var/log/eregister-concept-import.log |
The order is deliberate:
- The backup runs first, so the night's dump predates whatever the other three do.
- The auto-pull runs before the two imports, so each import sees the repos that were pulled the same night.
8.1 systemd or cron
Where systemd is present (Ubuntu's default), each job is a .service +
.timer pair in /etc/systemd/system/. Where it is not, the job is an
/etc/cron.d/ entry.
Both run the same standalone runner script, so behaviour does not change with the scheduler.
A host with neither gets the exact cron line to add by hand:
[⚠] Neither systemd nor /etc/cron.d is available. The runner was installed at
[⚠] /usr/local/bin/eregister-form-import.sh but NOT scheduled — add your own entry:
[⚠] 30 3 * * * root /usr/local/bin/eregister-form-import.sh
8.2 The auto-pull job
Keeps the asset and config repos in sync with their remotes — fetch --depth 1
plus a fast-forward reset --hard onto the tracked branch, which mirrors what
the installer does and keeps the checkout shallow.
Covered repos:
standard-config-ls
implementer-interface-release
openmrs-v1-modules
clinical-obs-forms
dhisconnector_mappings_v1
eregister_concepts_release_v1
openmrs_reporting_release
Deliberately excluded:
bahmni-docker-ls— the stack itself, pinned to the deployed release. It must not drift underneath a running instance.bahmni_config(0.92) — historical, restore-only.
A dirty working tree is left untouched. Never clobber uncommitted local work.
Info
Pulling a repo changes files on disk. It does not deploy anything. The
forms still need an import, the dictionary still needs a load, and the stack
still needs docker compose up -d. That is what the nightly imports and
catch-up.sh are for.
8.3 What catch-up checks
For each job, one row:
✔ OK cron eregister-db-backup systemd timer active (next: Mon 2026-09-22 01:30:00)
⟳ FIXED cron eregister-form-import was missing — installed (systemd timer)
— SKIP cron eregister-concept-import disabled (--no-concepts)
✘ GAP cron eregister-autopull not scheduled (declined)
Note the shape of the form-importer rows in particular: neither --no-forms nor
a failed importer install may skip the credentials row. A site that asked to
leave its forms alone still needs the script and env file in place for a later
manual run.
8.4 Inspecting a job
systemd
# is the timer active, and when does it next fire?
systemctl status eregister-form-import.timer
systemctl list-timers 'eregister-*'
# what happened on the last run?
journalctl -u eregister-form-import.service -n 50
# run it now, out of schedule
sudo systemctl start eregister-form-import.service
cron
cat /etc/cron.d/eregister-form-import
sudo tail -n 50 /var/log/eregister-form-import.log
Either way
Every runner is safe to invoke directly:
sudo /usr/local/bin/eregister-db-backup.sh
sudo /usr/local/bin/eregister-autopull.sh
sudo /usr/local/bin/eregister-form-import.sh
sudo /usr/local/bin/eregister-concept-import.sh
8.5 Changing a schedule
Set the environment variable and re-run catch-up.sh, which rewrites the unit:
# form import at 05:15 instead of 03:30
sudo EREGISTER_FORM_IMPORT_ONCALENDAR='*-*-* 05:15:00' \
EREGISTER_FORM_IMPORT_CRON='15 5 * * *' \
./catch-up.sh
Set both variables. The systemd one is used where systemd is present, the cron one where it is not, and a host can change scheduler between runs.
8.6 Disabling a job
| Job | Disable with |
|---|---|
| Database backup | --no-db-backup / EREGISTER_DB_BACKUP=0 |
| Auto-pull | EREGISTER_AUTO_PULL=0 |
| Form import | --no-forms / EREGISTER_IMPORT_FORMS=0 |
| Concept import | --no-concepts / EREGISTER_CONCEPT_IMPORT=0 |
Disabling silences the corresponding report rows too, so the exit code does not fail for a state you chose.
8.7 The form-import runner in detail
The wrapper eregister-form-import.sh is generated, and re-generated by every
catch-up.sh run. One run:
- Sources
/etc/eregister/form-import.envfor the credentials - If
EREGISTER_FORM_IMPORT_SELF_PULL=1(the default), refreshes theclinical-obs-formsclone itself — so the daily import still picks up new forms on a host where auto-pull was declined cds into/var/lib/v1/form-importsoimportErrors.txtlands somewhere writable rather than wherever cron happened to start- Runs the importer over the whole folder with
-k -r
A detached HEAD, or a modification to a tracked file, skips the refresh and imports what is on disk:
SKIP refresh (/var/lib/v1/clinical-obs-forms has uncommitted changes to tracked files)
Untracked files do not block it. git reset --hard cannot delete an
untracked file, so one is not local work a refresh could destroy — and treating
it as "dirty" is what froze sites whose EMR drops <form-uuid>.json into the
clone: permanently dirty tree, refresh skipped every night, new forms never
arriving.