# Caresoft Imaging — Installation

Two machines, or two roles on one machine:

- **Application server** — PHP + MySQL. This is where the code in this
  repository runs. Windows with IIS is fine here; it is the environment your
  team already supports.
- **Archive server** — Orthanc. **Use Ubuntu LTS.** Reasons are in §4.

For a small clinic both can live on one box. For a hospital running CT and
MR, keep them separate.

---

## 1. Application server

### Requirements

- PHP 8.1 or later (8.3 recommended) with `pdo_mysql`, `curl`, `mbstring`,
  `openssl`, `json`
- MySQL 8.0 or MariaDB 10.11+
- Apache with `mod_rewrite`, or IIS with URL Rewrite installed
- TLS certificate. Do not run this over plain HTTP.

### Steps

Upgrading an existing installation? Run the migrations in `db/` in number
order — `migration-002-quality.sql` adds the repeat/reject table and the
contrast fields; `migration-003-delivery.sql` adds verification codes and
delivery tracking; `migration-004-critical.sql` adds the escalation chain and
notification log; `migration-005-equipment.sql` adds the equipment event log
and regulatory dates; `migration-006-storage.sql` adds retention rules, legal
hold and export jobs; `migration-007-analytics.sql` adds reporting indexes;
`migration-008-telerad.sql` adds teleradiology partners, rates and invoices;
`migration-009-abdm-dpdp.sql` adds ABDM linkage and DPDP controls;
`migration-010-dictation-ai.sql` adds macros, hanging protocols and AI results. A fresh `schema.sql` already includes them.

```bash
# 1. Put the code somewhere outside the web root, and point the site
#    document root at the public/ folder ONLY.
#    /var/www/caresoft-imaging/public   <- document root
#    /var/www/caresoft-imaging/config   <- must NOT be web-reachable

# 2. Database
mysql -u root -p < db/schema.sql
mysql -u root -p < db/seed.sql          # optional: demo content

# 3. Database user (do not use root)
mysql -u root -p -e "
  CREATE USER 'caresoft_img'@'127.0.0.1' IDENTIFIED BY 'a-strong-password';
  GRANT SELECT, INSERT, UPDATE, DELETE ON caresoft_imaging.* TO 'caresoft_img'@'127.0.0.1';
  FLUSH PRIVILEGES;"

# 4. Configuration
cp config/config.sample.php config/config.php
openssl rand -base64 32          # paste the result into APP_KEY
# then edit APP_URL, DB_*, MAIL_* in config/config.php

# 5. Writable folder for logs
mkdir -p storage/logs && chown www-data:www-data storage/logs && chmod 750 storage/logs
```

On IIS, `public/web.config` is already in place and mirrors the `.htaccess`
rules. Give the application pool identity write access to `storage/logs`
and read access everywhere else.

### Immediately after install

The seed file creates eight accounts, all with the password
`Caresoft@2026` and all flagged to change it at first sign-in.

**Before the server is reachable from outside**, sign in as `csadmin`,
change that password, then disable or delete every seeded account you are
not going to use. The demo tenant `demo` should be deleted on a real
installation.

---

## 2. Archive server — Ubuntu

```bash
sudo apt update
sudo apt install orthanc orthanc-postgresql orthanc-dicomweb \
                 orthanc-webviewer postgresql php8.3-cli php8.3-curl

sudo -u postgres createuser orthanc --pwprompt
sudo -u postgres createdb orthanc -O orthanc

sudo cp orthanc/orthanc.sample.json /etc/orthanc/caresoft.json
sudo nano /etc/orthanc/caresoft.json
```

Edit at minimum:

- `RegisteredUsers` — a strong password for the `caresoft` account
- `DicomModalities` — one entry per machine: `"AETITLE": ["AETITLE", "ip", port]`
- `PostgreSQL.Password`
- `Worklists.Database` — the folder the application writes worklist files to
- Remove the `OrthancPeers` block unless you are forwarding to a central node

```bash
sudo mkdir -p /var/lib/orthanc/worklists
sudo chown orthanc:orthanc /var/lib/orthanc/worklists
sudo systemctl restart orthanc
curl -u caresoft:yourpassword http://127.0.0.1:8042/system
```

### Vet the plugins before a paid install

Orthanc core is GPLv3. Some plugins are AGPL, which is a different risk
profile for a hosted service. Check each plugin you enable and get the
written legal opinion referenced in the scope document.

---

## 3. Connecting the two

### In the platform console

Hospitals → the hospital → **Image archive**:

- Base URL, for example `http://10.0.10.5:8042`
- Username and password for the `caresoft` account
- Save. The webhook secret shown on the page is what the poller will use.

Then Machines → add each modality with its AE title, IP and port, and press
**Echo**. A green result means the archive can reach the machine. Also
configure the archive's AE title, IP and port on the machine's own console —
the echo tests one direction, the first real study tests the other.

### The ingest poller

Runs on the archive server. It watches Orthanc's change feed and posts a
signed notification when a study is complete.

```bash
sudo cp tools/caresoft-notify.php /usr/local/bin/caresoft-notify.php
sudo cp tools/caresoft-notify.sample.ini /etc/orthanc/caresoft-notify.ini
sudo chmod 600 /etc/orthanc/caresoft-notify.ini    # it holds the secret
sudo nano /etc/orthanc/caresoft-notify.ini
```

Then a systemd timer:

```ini
# /etc/systemd/system/caresoft-notify.service
[Unit]
Description=Caresoft Imaging study ingest notifier
After=orthanc.service

[Service]
Type=oneshot
User=orthanc
ExecStart=/usr/bin/php /usr/local/bin/caresoft-notify.php /etc/orthanc/caresoft-notify.ini
```

```ini
# /etc/systemd/system/caresoft-notify.timer
[Unit]
Description=Run the Caresoft ingest notifier every minute

[Timer]
OnBootSec=60
OnUnitActiveSec=60

[Install]
WantedBy=timers.target
```

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now caresoft-notify.timer
sudo journalctl -u caresoft-notify -f
```

On Windows, run the same script as a scheduled task every minute, or wrap it
with NSSM as a service.

### The escalation worker

This one runs on the **application** server, not the archive, and it is not
optional: without it, "escalates automatically" is a promise on a screen.

```bash
sudo tee /etc/systemd/system/caresoft-escalate.service >/dev/null <<'EOF'
[Unit]
Description=Caresoft Imaging critical finding escalation
[Service]
Type=oneshot
User=www-data
ExecStart=/usr/bin/php /var/www/caresoft-imaging/tools/caresoft-escalate.php
EOF

sudo tee /etc/systemd/system/caresoft-escalate.timer >/dev/null <<'EOF'
[Unit]
Description=Run the escalation worker every minute
[Timer]
OnBootSec=60
OnUnitActiveSec=60
[Install]
WantedBy=timers.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now caresoft-escalate.timer
php tools/caresoft-escalate.php --dry-run     # safe: shows what it would do
```

Route the unit's output to somebody's inbox. When the escalation chain is
exhausted and nobody could be reached, that warning is the last line of
defence.

### The equipment monitor

Same pattern, also on the application server, but every five minutes rather
than every minute — an echo to every machine on every hospital is more
traffic than a database query.

```bash
# caresoft-monitor.service: ExecStart=/usr/bin/php /var/www/caresoft-imaging/tools/caresoft-monitor.php
# caresoft-monitor.timer:   OnUnitActiveSec=300
php tools/caresoft-monitor.php --dry-run      # safe: reports, changes nothing
```

It opens a breakdown after three consecutive failed echoes. On a site with a
flaky network, raise that threshold in the script rather than turning the
monitor off — a machine that is genuinely unreachable is worth a false alarm.

### The storage worker

Nightly, on the application server. Plans retention and moves studies to cold.
It never deletes anything on a schedule.

```bash
# caresoft-storage.service: ExecStart=/usr/bin/php /var/www/caresoft-imaging/tools/caresoft-storage.php --tier
# caresoft-storage.timer:   OnCalendar=*-*-* 02:30:00
```

**Purging is deliberately not scheduled.** When the storage screen shows
studies past their retention date, an administrator reviews them and runs:

```bash
php tools/caresoft-storage.php --purge                    # report only, deletes nothing
php tools/caresoft-storage.php --purge --confirm --max=50 # actually delete, capped
```

Do not put `--confirm` in a timer. The review is the control.

### The teleradiology worker

Every minute, on the application server. Allocates acquired studies against
the rules, forwards queued studies to the central node, closes allocations
whose report has been signed, and flags SLA breaches.

```bash
# caresoft-telerad.service: ExecStart=/usr/bin/php /var/www/caresoft-imaging/tools/caresoft-telerad.php
# caresoft-telerad.timer:   OnUnitActiveSec=60
php tools/caresoft-telerad.php --dry-run     # shows which rule each study would match
```

The dry run is the quickest way to check a new rule does what you think
before it starts sending studies to a partner.

### Worklist folder

The application writes worklist files as JSON into the folder configured at
`Worklists.Database`. The web server must be able to write there. On a split
deployment that means an SMB or NFS share, and the tenant setting
`worklist_dir` must point at the path as the **web server** sees it.

If the two servers cannot share a folder, run the application's worklist
writer on the archive host instead — raise it with Caresoft engineering
rather than opening the share to the internet.

---

## 4. Why Ubuntu for the archive

Orthanc's primary development and packaging target is Linux. The plugins you
will actually use — PostgreSQL index, DICOMweb, worklists, object storage —
are better tested there, upgrades are cleaner, and the cloud instance costs
roughly half with no Windows Server licence.

On the index database: Orthanc's **PostgreSQL** plugin is more battle-tested
at high study volumes than its MySQL plugin. That database is Orthanc's own
and your team never queries it directly, so take the better-supported option.
The Caresoft application database stays MySQL either way.

Run Windows for the archive only where a hospital's IT policy forbids Linux
on their network.

### Sizing

| Modality | Average study | Note |
|---|---|---|
| X-ray (CR/DX) | 10–30 MB | High volume, small |
| Ultrasound | 20–80 MB | Larger with cine loops |
| CT | 100 MB – 1 GB | Thin slices dominate growth |
| MRI | 50–300 MB | |
| Mammography | 100–250 MB | Longest retention |

Budget **1.5–3 TB per year** for a hospital running CT, MR and routine
radiography. Multiply by the retention requirement, add 30 percent headroom,
and plan backup at roughly twice the primary.

---

## 5. Hardening before go-live

- [ ] `config/config.php` is outside the document root and not world-readable
- [ ] `APP_DEBUG` is `false`
- [ ] Every seeded account has a changed password, or is deleted
- [ ] The demo tenant is deleted
- [ ] TLS on the application, and on the archive if it crosses any network
- [ ] The archive is **not** exposed to the internet — bind it to the LAN or
      put it behind the reverse proxy with authentication
- [ ] Database user has no `DROP` or `CREATE` rights
- [ ] `storage/logs` is writable by the web server and readable by nobody else
- [ ] `caresoft-notify.ini` is mode 600
- [ ] Backups configured for both the MySQL database and the Orthanc storage
      directory, and a restore actually tested
- [ ] Retention and tiering values set per hospital under Settings
- [ ] Escalation chain configured under Critical findings — at minimum a duty
      officer at level 1, or findings will sit open with nobody to call
- [ ] The escalation timer is enabled and its output goes somewhere a person reads
- [ ] Escalation windows set under Settings (15 minutes critical, 60 urgent
      are reasonable starting points)
- [ ] The equipment monitor timer is enabled
- [ ] AMC, warranty, AERB and calibration dates entered for every machine, or
      the due-date warnings have nothing to work from
- [ ] Quiet-period hours set on machines that run daily, so a silent console
      is noticed
- [ ] Retention rules reviewed and signed off by the hospital against its own
      state's requirement — the seeded defaults are a starting point, not advice
- [ ] Storage quota set on the hospital record, or the exhaustion projection
      has nothing to work from
- [ ] The storage timer runs `--tier` only; `--confirm` is never scheduled
- [ ] A restore has actually been tested and recorded on the storage screen
- [ ] At least one allocation rule exists, with a catch-all last, or acquired
      studies will sit with nobody assigned
- [ ] Every external reader account has a partner set on the Users screen —
      an external account without one sees the whole department
- [ ] Rate cards cover the modality and priority combinations you actually
      send out, or those studies bill at no rate
- [ ] The central Orthanc peer is configured on the site node under the name
      set in `central_peer`, or forwarding will fail every time
- [ ] ABDM stays in sandbox until the facility has passed milestone
      certification — the software refuses production without a recorded
      certification date, and that check should not be worked around
- [ ] The DPDP notice has been written and its version recorded, in every
      language the hospital actually serves
- [ ] The record of processing has been reviewed by the hospital's counsel —
      the seeded draft describes how the software works, not what the law
      requires of this hospital
- [ ] Someone is named to receive data principal requests, and the response
      commitment in Settings matches what the notice promises
- [ ] Radiologists reporting by voice are on Chrome or Edge; Firefox has no
      speech engine and the dictation button will be disabled
- [ ] Any AI service has its regulatory reference recorded before it is
      switched on, and somebody owns reviewing the agreement rate monthly
- [ ] `caresoft-ai.php` is never left running with `--mock` on a live site —
      mock results are labelled, but they should not be there at all
- [ ] `report_link_days` set to whatever the hospital is comfortable with
- [ ] Letterhead and address lines set under Settings, and a test report
      printed and checked against the hospital's existing report format
- [ ] WhatsApp provider credentials set, or the WhatsApp buttons will fail
      cleanly and staff will have to copy links by hand

---

## 5b. Check the archive before trusting the DICOM code

Run this on the archive host as soon as Orthanc is up, before connecting a
modality:

```bash
php tools/caresoft-orthanc-check.php \
    --url=http://127.0.0.1:8042 --user=caresoft --pass=SECRET \
    --aet=CT1 --worklist=/var/lib/orthanc/worklists
```

It exercises every endpoint and tag name the application reads and prints
what actually came back. Run it again after the first study arrives — the
ingest checks only mean something once there is a study to inspect.

## 5c. Demonstration data

For a sales demonstration or a training environment, months of synthetic
history can be generated so the management screens are not empty:

```bash
php tools/caresoft-demo-data.php --tenant=demo --months=6
php tools/caresoft-demo-data.php --tenant=demo --wipe     # remove it again
```

Never run this against a hospital that holds real studies. It refuses by
default, but do not rely on that.

## 6. First study, end to end

Do this on day one with a real machine before promising anything:

1. Raise an order through `POST /api/his-order.php` (or insert one manually).
2. Confirm it appears in the modality worklist on the machine console.
3. Acquire, and send.
4. Watch `journalctl -u caresoft-notify` and the application log for the ingest.
5. Confirm the study appears on the radiologist worklist with the SLA clock running.
6. Report it, verify it, and check the audit trail.
7. Sign the report, print it, and scan the QR with a phone — it should open
   the verification page and show the signer and date.
8. Send the patient link to your own number, open it, and confirm the read
   shows up on Report delivery in the hospital admin console.
9. Flag a critical finding on a test study, open the acknowledgement link on
   a phone, and confirm it. Then check that the register shows who confirmed
   and how long it took. Leave one unacknowledged for twenty minutes and
   check that the duty officer was contacted.
10. As a technician, walk the acquisition console: mark arrived, start, log a
   repeat with a reason code, and complete. Then open Quality & repeats on
   the hospital admin console and confirm the repeat appears in the breakdown.

If step 2 fails it is almost always the AE title or the worklist folder
permissions. If step 4 fails it is almost always the webhook secret.
