# Runbook: Nextcloud Operations

## Current Deployment

- VM hostname: `nextcloud`
- VM IP: `192.168.0.110`
- OS: Ubuntu 25.04
- Deployment path: `/opt/nextcloud`
- LAN access URL for now: `http://192.168.0.110:8080`
- Tailscale access URL for now: `http://100.76.27.77:8080`
- Intended hostname later: `nc.dropcutstud.io`
- Docker Compose services:
  - `nextcloud-app`
  - `nextcloud-db`
  - `nextcloud-redis`

## Accounts

Created accounts:

- `ncadmin` — initial admin account
- `deeso` — user account
- `piagent` — Pi assistant account

Passwords are not stored in this repo. They are stored on the VM in:

```bash
/root/nextcloud-credentials.env
```

To view them from the VM:

```bash
sudo cat /root/nextcloud-credentials.env
```

## Common Commands

SSH to VM:

```bash
ssh -i .ssh/piagent_homelab piagent@192.168.0.110
```

Check containers:

```bash
cd /opt/nextcloud
sudo docker compose ps
```

View logs:

```bash
cd /opt/nextcloud
sudo docker compose logs -f --tail=100
```

Run Nextcloud OCC:

```bash
sudo docker exec -u www-data nextcloud-app php occ status
```

Update containers:

```bash
cd /opt/nextcloud
sudo docker compose pull
sudo docker compose up -d
```

## Enabled Apps

- Calendar
- Tasks
- Talk / Spreed
- Contacts
- Notes

## Background Jobs

Nextcloud background jobs are set to cron mode.

Host systemd timer:

```bash
systemctl status nextcloud-cron.timer
```

## Tailscale

Tailscale is installed and authenticated.

- Tailscale hostname: `nextcloud`
- Tailscale IP: `100.76.27.77`
- Direct Tailscale URL: `http://100.76.27.77:8080`

Check status:

```bash
sudo tailscale status
```

## DNS Notes

`nc.dropcutstud.io` currently resolves publicly to the user's public IP, not directly to the VM.

For LAN-only access, configure an OPNsense Unbound host override:

- Host: `nc`
- Domain: `dropcutstud.io`
- IP: either the Caddy LXC IP if Caddy reverse proxies to Nextcloud, or `192.168.0.110` if connecting directly to the VM.

Caddy / reverse proxy is currently on the back burner due to configuration errors.

Current working design:

```text
LAN clients -> http://192.168.0.110:8080
Tailscale clients -> http://100.76.27.77:8080
```

Possible later design:

```text
LAN/Tailscale clients -> nc.dropcutstud.io -> Caddy LXC -> Nextcloud VM:8080
```

Caddy can then provide HTTPS while Nextcloud itself remains private.

## Backup and Restore

### Backup Class: Critical (bootstrap: standard)

Nextcloud data (tasks, calendar, files) is critical if real user data is stored. During bootstrap, backups are implemented at standard class level: config + database, age-encrypted, off-guest to Nimrod LXC. User data volume (`nextcloud_data`) backup will be added when real files are uploaded.

### Backup Scope (bootstrap phase)

Currently backed up:
- PostgreSQL database dump (`pg_dump --clean --if-exists`)
- Nextcloud config volume (apps, config, themes) – excludes user `data/` directory
- Docker Compose definitions and `.env`

Not yet backed up (add when real user files are stored):
- User data volume (`nextcloud_data`) from `/var/lib/docker/volumes/nextcloud_data/_data`

### Encryption

Backups are encrypted with `age` using the Nextcloud-backup recipient key.
- Public key on VM: `/etc/nextcloud-backup/backup-recipient.txt`
- Private key: User-owned and stored outside this VM (saved during initial setup)
- **Losing the private key = losing access to all encrypted backups**

### Backup Script

Location: `/usr/local/sbin/nextcloud-backup`

Manual run:

```bash
/usr/local/sbin/nextcloud-backup
```

What it does:
1. Dumps PostgreSQL database with `pg_dump --clean --if-exists`
2. Archives config volume (excludes user data)
3. Archives compose files (docker-compose.yml, .env)
4. Combines into a single tar and encrypts with age
5. Verifies SHA256 checksum
6. Copies encrypted backup and manifest to Nimrod LXC 104 (`piagent@192.168.0.222`)
7. Verifies off-guest copy checksum
8. Prunes local backups older than 14 days

### Schedule

Daily at 3:00 AM via `/etc/cron.d/nextcloud-backup`:

```
0 3 * * * piagent /usr/local/sbin/nextcloud-backup >/var/log/nextcloud-backup.log 2>&1
```

### Backup Locations

| Location | Path | Format |
|---|---|---|
| Local (VM) | `/var/backups/nextcloud/nextcloud-backup-*.tar.age` | Age-encrypted tar |
| Off-guest (Nimrod LXC 104) | `/home/piagent/backups/nextcloud/` | Age-encrypted tar copy |

### Restore Script

Location: `/usr/local/sbin/nextcloud-restore`

**Prerequisites:** The `AGE_PRIVATE_KEY` environment variable must be set to the Nextcloud backup private key.

Verify backup integrity (read-only, no changes):

```bash
AGE_PRIVATE_KEY=/path/to/nextcloud-backup-key /usr/local/sbin/nextcloud-restore /path/to/backup.tar.age --verify-only
```

Dry-run restore (shows steps, no changes):

```bash
AGE_PRIVATE_KEY=/path/to/nextcloud-backup-key /usr/local/sbin/nextcloud-restore /path/to/backup.tar.age --dry-run
```

Full restore (requires the stack to be stopped):

```bash
AGE_PRIVATE_KEY=/path/to/nextcloud-backup-key sudo /usr/local/sbin/nextcloud-restore /var/backups/nextcloud/nextcloud-backup-LATEST.tar.age
```

### Restore Verification (minimal check)

To verify a backup is decryptable and contains expected components, SSH to the VM and run:

```bash
AGE_PRIVATE_KEY=/path/to/nextcloud-backup-key /usr/local/sbin/nextcloud-restore /path/to/backup.tar.age --verify-only
```

This decrypts the backup, lists its contents, and cleans up without modifying any files.

### Retention

- Local (VM): 14 days (automatic pruning in backup script)
- Off-guest (Nimrod): Manual cleanup until scheduled pruning is added
- Offline/user-controlled: Recommended before storing important data

### Data Volume Backup (future)

When real user files are stored in Nextcloud, add the `nextcloud_data` volume to the backup scope. The volume is at `/var/lib/docker/volumes/nextcloud_data/_data`. Update the backup script to include:

```bash
# Add to nextcloud-backup.sh:
docker exec nextcloud-app tar -c -C /var/www/html/data . > "nextcloud-data-${TIMESTAMP}.tar"
```

Then re-verify backup/restore with realistic data.

### Constraint

No **important** data should be stored in Nextcloud until:
1. A restore test has been successfully performed
2. The user confirms they have a copy of the age private key in safe offline storage
