## GeoLynx App Update Guide

### Updating an existing server to the latest `main`

The short version of `app_deploy_guide.md` — that one builds a server from nothing, this one
brings an already-working server up to date.

**Starting point assumed:** the repo is merged to `main` and ready to deploy, and you're
logged in to the server over SSH. Paths below are the standard ones from the deploy guide
(`/var/www/geolynx-app`); adjust if this box differs.

Everything runs with `sudo`, because the tree is owned by `www-data` and `/var/www` is
root-owned — same reasoning as deploy guide 4.2.

---

## Step 0: One-time per server

Git refuses to operate on a repo owned by another user unless told it's trusted. Once per
server, never again:

```bash
sudo git config --global --add safe.directory /var/www/geolynx-app
```

---

## Step 1: See what's coming (and note the way back)

```bash
sudo git -C /var/www/geolynx-app rev-parse --short HEAD    # write this down — it's your rollback point
sudo git -C /var/www/geolynx-app status --short            # any local edits on the server?
sudo git -C /var/www/geolynx-app fetch origin
sudo git -C /var/www/geolynx-app log --oneline HEAD..origin/main
sudo git -C /var/www/geolynx-app diff --name-only HEAD origin/main -- db/
```

The last line is the one that decides how much work this update is: anything under `db/`
means **new migrations to apply** (Step 4). Nothing else in the repo needs a step of its own
— but the QGIS export scripts aren't *in* the repo, so if this release changed them, that's
a manual copy (see "What a pull doesn't update" at the end).

`status --short` should normally come back empty. If it doesn't, read Step 2's second half
before going any further.

---

## Step 2: Pull

**Normal case** — nothing has been edited on the server:

```bash
sudo git -C /var/www/geolynx-app pull --ff-only
```

`--ff-only` makes git refuse rather than invent a merge commit on a production box. If it
refuses, something is out of shape — don't force past it without knowing why.

**If files were patched directly on the server** (the urgent-hotfix case — git will refuse
the pull, or `status --short` showed modified files):

First establish whether that patch is already in `main`. `git diff` shows what's on disk
that isn't committed; if the same change arrived properly through the repo, the reset below
is safe and discards nothing real:

```bash
sudo git -C /var/www/geolynx-app diff                      # what the hotfix actually changed
sudo cp /var/www/geolynx-app/www/fn/thefile.php /root/hotfix-backup-thefile.php   # only if it is NOT in main
```

Then discard the server-side edits and take `main` exactly:

```bash
sudo git -C /var/www/geolynx-app fetch origin
sudo git -C /var/www/geolynx-app reset --hard origin/main
```

> `reset --hard` throws away **tracked** file changes only. Gitignored files are untouched —
> `www/fn/db.php` and `www/fn/mail_config.php` survive, as does `/var/www/netplanner-files`,
> which sits outside the tree entirely. Untracked files you added on the server also stay
> put; `reset --hard` doesn't remove them.

---

## Step 3: Re-set ownership

The pull ran as root, so any file it created or rewrote is now root-owned and Apache can't
necessarily read it. Hand the tree back:

```bash
sudo chown -R www-data:www-data /var/www/geolynx-app
```

---

## Step 4: Run migrations

Only if Step 1 showed new files under `db/`. Dry run first — it prints the target database
and the pending list, and changes nothing:

```bash
sudo php /var/www/geolynx-app/db/migrate.php
sudo php /var/www/geolynx-app/db/migrate.php --apply
```

The runner reads `www/fn/db.php`, so it targets whatever database this server's app targets,
and each database tracks its own applied set in `public.schema_migrations` — it applies
exactly what this box is missing, whether that's one migration or ten. Each file runs in its
own transaction: a failure rolls back completely and isn't recorded, so fix and rerun.
Full detail in `docs/database-migrations.md`.

Run these back-to-back with the pull. Between the two, the new code is live against the old
schema.

---

## Step 5: Check it worked

```bash
sudo git -C /var/www/geolynx-app log --oneline -1          # matches the top of origin/main?
sudo tail -50 /var/log/apache2/geolynx_error.log
```

- [ ] Load the site and log in
- [ ] Open the map — layers render
- [ ] Open whatever the update actually changed, and exercise it
- [ ] Anything the migrations touched — new column, new view, new map layer — shows up

**If a code change doesn't appear to have taken effect**, give it a moment before assuming
the pull failed: PHP's realpath cache can hold stale stat results for up to 120s, and if
this server has `opcache.validate_timestamps=0` a PHP edit needs a reload before it's seen
at all:

```bash
sudo systemctl reload apache2
```

---

## Rollback

Code rolls back cleanly — reset to the SHA from Step 1 and fix ownership again:

```bash
sudo git -C /var/www/geolynx-app reset --hard <sha-from-step-1>
sudo chown -R www-data:www-data /var/www/geolynx-app
```

Migrations do **not** roll back. They're immutable once applied, and the runner has no
`--down`. If a migration turns out to be wrong, the fix is a new migration correcting it —
never an edit to the applied file. So if you've already run Step 4, reverting the code puts
old code against a newer schema; that's usually harmless (added columns and views the old
code ignores), but check what the migration actually did before relying on it.

---

## What a pull doesn't update

Everything below lives outside the repo, so it stays exactly as it was — which is the point,
but it also means a change to any of them is a manual step you have to spot yourself:

- **`www/fn/db.php` and `www/fn/mail_config.php`** — gitignored, this server's own credentials.
- **The QGIS export scripts and `.qgz` files** — not in the repo at all. They live at the
  path referenced in `project_export_pdf.php` / `opportunity_export_pdf.php`, alongside
  `db_config.py`. A pull can never bring a change to them, so if this release depends on an
  edited script, copy it across by hand (deploy guide 4.7) — and leave `db_config.py` alone,
  it points at *this* server's DB, not dev's.
- **`/etc/geolynx/pg_service.conf`** — the named DB connection the `.qgz` layers resolve
  (deploy guide 3.3).
- **Uploaded files in `/var/www/netplanner-files/`** — outside the web root and outside the
  tree, untouched by both the pull and the `chown` in Step 3.
- **PHP limits (`/etc/php/*/apache2/php.ini`), the Apache vhost, UFW, certbot** — server
  config, not app code.
- **GeoServer layers and styles** — configured in GeoServer, not in the repo. Note that map
  layers served through the app are configured in the `public.map_layers` **table**, so
  those *do* arrive, via a migration.
