# TubePress — agent & automation guide

TubePress is a self-hosted video-site CMS (PHP + MySQL/MariaDB). It is built to be
**deployed autonomously by an AI agent or an automation script, end to end, with no
browser and no interactive prompts** — while remaining fully installable by hand
through the same web setup wizard a human would use.

This file is the contract for unattended deployment. Everything below has been
verified against a clean install.

---

## TL;DR — deploy in one command

```bash
# from the unzipped project root, with the database already reachable
php bin/install.php \
  --db-host=127.0.0.1 --db-name=tubepress --db-user=tubepress \
  --admin-email=admin@example.com --site-url=https://example.com \
  --json
```

Secrets are better passed as environment variables than as flags (flags are visible
in the process list and shell history):

```bash
export TUBEPRESS_DB_PASSWORD='…'        # the DB user's password
export TUBEPRESS_ADMIN_PASSWORD='…'     # omit to have a strong one generated for you
php bin/install.php --db-name=tubepress --db-user=tubepress \
  --admin-email=admin@example.com --site-url=https://example.com --json
```

The installer prints a JSON result. On success `"ok": true`; if you did not supply an
admin password, the generated one is in `"admin_password"` (shown only once — store it).

---

## Requirements

Run the gate first; it never changes anything and exits non-zero if a **required**
check fails:

```bash
php bin/install.php --check --json
```

**Required (block install):** PHP ≥ 8.2, extensions `pdo_mysql`, `mbstring`, `fileinfo`,
and writable `config/`, `storage/`, `public/uploads/`. A MySQL 5.7+/MariaDB 10.3+
database the DB user can access (it may be pre-created — see below).

**Recommended (warn only):** `curl` (catalogue + update checks), `zip` (install
plugins/themes from the admin), `gd` or `imagick` (thumbnails), `exec()` enabled (the
self-managed cron heartbeat). No `composer` step is needed — there are no Composer
dependencies.

---

## Autonomous deploy — full procedure

1. **Place the code.** Unzip the package to its final location, e.g.
   `/var/www/tubepress`. The web server's document root must be the `public/`
   subdirectory.

2. **Ensure writable dirs** (the installer also auto-creates these, but set ownership
   for your web user):
   ```bash
   mkdir -p config storage public/uploads
   chown -R www-data:www-data config storage public/uploads
   ```

3. **Check the environment:** `php bin/install.php --check --json` → require
   `"ok": true`.

4. **Provision the database.** Either let the installer create it (the DB user needs
   the global `CREATE` privilege), or pre-create it (shared hosting / least-privilege)
   and grant the user access to that database:
   ```sql
   CREATE DATABASE tubepress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
   CREATE USER 'tubepress'@'127.0.0.1' IDENTIFIED BY '…';
   GRANT ALL PRIVILEGES ON tubepress.* TO 'tubepress'@'127.0.0.1';
   ```

5. **Install:** run `php bin/install.php …` (see TL;DR). This connects, runs all
   migrations, writes `config/database.php`, creates the admin account, seeds the
   default settings/pages/SEO, and writes `config/installed.php`. Parse the JSON;
   stop on `"ok": false` and read `"message"`.

6. **Configure the web server** (see next section) and reload it.

7. **(Optional) Start background jobs** — see "Background jobs".

8. **Verify:** `curl -fsS https://example.com/ -o /dev/null` returns 200, and
   `https://example.com/admin/login` serves the admin login.

The installer is **idempotent**: on an already-installed site it exits with
`"error": "already_installed"` and changes nothing unless you pass `--force` (a
non-destructive re-run that never drops data or duplicates the admin/pages).

---

## Configuration reference

Resolution order for every value: **CLI flag > environment variable > `--config` JSON
file > built-in default.**

| Flag | Env var | Required | Default | Notes |
|------|---------|----------|---------|-------|
| `--db-host` | `TUBEPRESS_DB_HOST` | no | `127.0.0.1` | `host`, `host:port`, `:port`, `[ipv6]:port`, or `/path/to.sock` |
| `--db-name` | `TUBEPRESS_DB_NAME` | **yes** | — | letters, digits, `_`, `-` |
| `--db-user` | `TUBEPRESS_DB_USER` | **yes** | — | |
| `--db-pass` | `TUBEPRESS_DB_PASSWORD` | no | empty | prefer the env var |
| `--admin-user` | `TUBEPRESS_ADMIN_USER` | no | `admin` | 3–50 chars `[A-Za-z0-9_-]` |
| `--admin-email` | `TUBEPRESS_ADMIN_EMAIL` | **yes** | — | valid email |
| `--admin-pass` | `TUBEPRESS_ADMIN_PASSWORD` | no | *generated* | ≥ 8 chars; if omitted a strong one is generated and returned once |
| `--site-name` | `TUBEPRESS_SITE_NAME` | no | `My Video Site` | |
| `--site-description` | `TUBEPRESS_SITE_DESCRIPTION` | no | empty | |
| `--site-url` | `TUBEPRESS_SITE_URL` | no | empty | e.g. `https://example.com`; set it for correct links + cron |

Other flags: `--config=FILE` (JSON answer file, keys without the `--` prefix, e.g.
`{"db_name":"tubepress","db_user":"tubepress"}`), `--force`, `--check`, `--json`,
`--help`. Exit codes: `0` success / checks pass, `1` install or checks failed,
`2` usage error.

---

## Web server configuration

**Document root is `public/`.** Non-file requests route to `public/index.php`.

### nginx

```nginx
server {
    listen 80;
    server_name example.com;
    root /var/www/tubepress/public;
    index index.php;
    charset utf-8;
    client_max_body_size 2048M;           # large video uploads

    # Theme / admin / install static assets
    location /themes/ {
        alias /var/www/tubepress/themes/;
        location ~ \.(php|json)$ { deny all; }
        expires 30d; access_log off;
    }
    location = /templates/admin/style.css   { alias /var/www/tubepress/templates/admin/style.css;   expires 30d; }
    location = /templates/install/style.css { alias /var/www/tubepress/templates/install/style.css; expires 30d; }
    location /templates/admin/img/ {
        alias /var/www/tubepress/templates/admin/img/;
        location ~ \.php$ { deny all; }
        expires 30d; access_log off;
    }

    # Never serve code or data
    location ~ /\.                         { deny all; }
    location ~ ^/(config|src|sql|storage)  { deny all; }
    location ~* ^/(themes|uploads|plugins)/.*\.php$ { deny all; }

    location / { try_files $uri $uri/ /index.php?$query_string; }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;   # match your PHP-FPM socket
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 300;
    }
}
```

Add TLS (e.g. certbot) for production; over HTTPS the app sets secure cookies
automatically.

### Apache

Document root `public/`, `AllowOverride All`, `mod_rewrite` enabled. The shipped
`public/.htaccess` provides the rewrite to `index.php` and blocks `themes/**.php`,
`templates/**.php`. If `symlink()` is allowed, the installer links
`public/themes` and `public/templates`; if not, the app falls back to serving those
assets through PHP, so styling still works.

---

## Background jobs (cron) — no system crontab needed

TubePress runs its periodic work (update checks, sitemap, transcode dispatch, etc.)
two ways: opportunistically after web requests, and via a self-managed heartbeat that
keeps it ticking on a zero-traffic site. Start the heartbeat once (it re-spawns itself
on later web requests if it dies, when `exec()` is available):

```bash
php bin/heartbeat.php >/dev/null 2>&1 &     # or a systemd service
```

It requires a correct `site_url` (set `--site-url` at install). Disable at runtime
with the setting `heartbeat_enabled = 0`.

---

## Updates

Signed, checksummed core/plugin/theme updates are delivered from the vendor and applied
from **Admin → Updates** (or opt-in daily auto-update via the `auto_update_enabled`
setting). Updates preserve `config/`, the database, plugins and uploads. Do not
hand-patch core files — apply updates through the admin so signatures verify.

---

## Security notes

- The generated admin password is shown **once** in the install output — capture it.
- `config/database.php` holds DB credentials; the installer tightens it to `0640`
  where the OS allows. Keep `config/`, `src/`, `sql/`, `storage/` outside the web root
  (the docroot is `public/`) — the sample server configs already deny them.
- 2FA (TOTP), Argon2id password hashing (bcrypt fallback), CSRF, rate limiting and a
  spam guard are built in.

---

## Manual installation (the human path is unchanged)

Prefer a browser? Point the web server at `public/`, ensure the dirs above are
writable, and open `https://example.com/install` — the WordPress-style wizard walks
through requirements → database → admin → settings → done. The CLI installer above is
purely an automation-friendly front end over the **same** install logic; both produce
an identical site.

---

## Project layout (orientation)

```
public/          web root (index.php, .htaccess, uploads/, assets/)
config/          app.php, php-guard.php; database.php + installed.php written at install
src/             application code (Core, Install, Admin, Front, Models, …)
sql/             numbered migrations, applied in order at install/update
themes/          front-end themes (default: simply)
plugins/         optional plugins (empty by default; install from the marketplace)
bin/             CLI entry points — install.php, heartbeat.php, workers
templates/       admin + install + shared view templates
lang/            interface translations
```
