# Deployment Guide

## Requirements

- Docker 24.0+
- Docker Compose 2.20+
- A reverse proxy that terminates TLS for the public hostname
- A writable checkout directory for `./data` and `./content`

## Fresh Deployment

```bash
git clone https://github.com/your-org/cairnquire.git
cd cairnquire
cp .env.example .env
```

Edit `.env` and set the HTTPS origin that users will open in their browser:

```dotenv
CAIRNQUIRE_PUBLIC_ORIGIN=https://cairnquire.example.com
```

Start the application and verify the local container health endpoint:

```bash
docker compose up -d --build
docker compose ps
curl http://127.0.0.1:8080/health
```

Then open `https://cairnquire.example.com/setup`. On an empty database the
first-run wizard:

1. Creates the initial administrator account.
2. Sets its password.
3. Records whether visitors may create their own accounts.
4. Signs the administrator in.

The setup endpoint is only available until the first administrator has been
created. The signup setting can be changed later from the administrator's
account page.

## Environment Variables

The checked-in Compose file sets the container paths. For a normal single-host
deployment, only `CAIRNQUIRE_PUBLIC_ORIGIN` must be supplied in `.env`.

| Variable | Required | Compose default | Description |
|----------|----------|-----------------|-------------|
| `CAIRNQUIRE_PUBLIC_ORIGIN` | Yes | None | Exact external HTTPS origin. Passkeys and device-flow URLs depend on this value. |
| `CAIRNQUIRE_EMAIL_SMTP_HOST` | No | `mailpit` | SMTP server used for notifications. |
| `CAIRNQUIRE_EMAIL_SMTP_PORT` | No | `1025` | SMTP server port. |
| `CAIRNQUIRE_EMAIL_FROM` | No | `notifications@cairnquire.local` | Notification sender address. |
| `CAIRNQUIRE_LOG_LEVEL` | No | `INFO` | Application log level. |

The server also accepts these lower-level overrides when it is run outside the
checked-in Compose deployment:

| Variable | Default | Description |
|----------|---------|-------------|
| `CAIRNQUIRE_SERVER_ADDR` | `:8080` | Listen address. |
| `CAIRNQUIRE_DATABASE_PATH` | `../../data/db.sqlite` | Local SQLite/libsql database path. |
| `CAIRNQUIRE_DATABASE_PRIMARY_URL` | Empty | Optional remote libsql primary URL for embedded-replica mode. |
| `CAIRNQUIRE_DATABASE_AUTH_TOKEN` | Empty | Optional auth token for the remote libsql primary. |
| `CAIRNQUIRE_CONTENT_SOURCE_DIR` | `../../content` | Writable Markdown source directory. |
| `CAIRNQUIRE_CONTENT_STORE_DIR` | `../../data/files` | Content-addressed attachment store. |
| `CAIRNQUIRE_CONFIG` | Empty | Optional JSON configuration file. Environment variables override it. |
| `CAIRNQUIRE_DEV_MODE` | `false` | Local-only auth shortcut. Never enable this on a deployed server. |

## Persistent Data

Compose mounts:

- `./data:/workspace/data` for the database and attachments
- `./content:/workspace/content` for editable Markdown source

Both mounts must remain writable because uploads, browser edits, and sync
writebacks modify them.

## Reverse Proxy

Expose container port `8080` through a TLS-terminating reverse proxy. Preserve
the original hostname and forwarded client address headers. The configured
`CAIRNQUIRE_PUBLIC_ORIGIN` must exactly match the external origin, including
`https://`.

## Backup And Restore

Stop writes or stop the app container before copying a fully consistent local
database backup:

```bash
docker compose stop app
tar czf cairnquire-backup.tgz data content
docker compose up -d app
```

Restore by stopping the app, replacing `data` and `content` from the archive,
and starting the app again.

## Upgrade

```bash
docker compose stop app
tar czf cairnquire-backup-before-upgrade.tgz data content
git pull
docker compose up -d --build
curl http://127.0.0.1:8080/health
```

Database migrations run automatically on startup.

## Troubleshooting

```bash
docker compose logs -f app
docker compose ps
curl http://127.0.0.1:8080/health
```

If passkey creation fails after deployment, confirm that the browser URL and
`CAIRNQUIRE_PUBLIC_ORIGIN` are the same HTTPS origin.
