# Setup & Deployment ## 1. Requirements - Shared webhosting with PHP **8.1 or newer**, `curl` and `openssl` extensions (both are standard), and Apache `.htaccess` support. - A **Hetzner Object Storage** bucket (any S3-compatible storage works). - No database, no Composer, no build step. ## 2. Hetzner Object Storage 1. In the [Hetzner Cloud Console](https://console.hetzner.cloud/) create a Bucket (e.g. location `fsn1`). Set visibility to **private** — visitors get access only through short-lived presigned URLs. 2. Create S3 credentials (Security → S3 credentials) and note the *access key* and *secret key*. No bucket **CORS** rule is needed: uploads are proxied through the webhost (same-origin) and gallery images load via `` presigned GET URLs, which browsers don't subject to CORS. Keep the bucket **private**. ## 2a. PHP upload limits Uploads stream through PHP one image per request, so the *total* gallery size is irrelevant — but each single image must fit the host's limits. Ensure `upload_max_filesize` and `post_max_size` are at least as large as your biggest original (e.g. 200M for RAW files); on shared hosting set these in `.user.ini` or `php.ini`: ```ini upload_max_filesize = 200M post_max_size = 200M ``` `max_execution_time` is lifted per upload request in code, but if your host caps it at the web-server level (e.g. Apache/FPM request timeout), raise that too for large files. ## 3. Configuration ```bash cp config/config.sample.php config/config.php cp config/credentials.sample.php config/credentials.php ``` Edit `config/config.php`: | Key | Meaning | | --- | --- | | `site.name` | Fallback site title | | `site.base_url` | Public base URL, used for gallery share links | | `site.timezone` | Timezone for expiry checks, e.g. `Europe/Berlin` | | `s3.endpoint` | `https://.your-objectstorage.com` | | `s3.region` | The location, e.g. `fsn1` | | `s3.bucket` | Bucket name | | `s3.access_key` / `s3.secret_key` | S3 credentials | | `s3.url_ttl` | Lifetime of presigned view URLs in seconds | | `uploads.thumb_size` | Longest edge of grid thumbnails (browser-generated) | The default admin login is `admin` / `changeme` — **change it in the admin Settings page immediately after the first login.** ## 4. Uploading to the webhost Upload the **contents of this folder** into your account's document root (usually `public_html/`, `htdocs/` or `www/`) via FTP/SFTP. The site's home page, `index.php`, sits directly in the document root — there is no separate web-root subfolder to configure. The application internals (`app/`, `config/`, `data/`, `docs/`) live inside the document root but are blocked from the web by the root `.htaccess` (plus a deny-all `.htaccess` inside each of `app/`, `config/`, `data/` as a fallback). Verify after deploying — each of these must return **403 Forbidden**, never their contents: - `https://your-domain.com/config/config.php` - `https://your-domain.com/config/credentials.php` - `https://your-domain.com/data/site.json` If they don't, your host ignores `.htaccess` — move `app/`, `config/` and `data/` above the document root and adjust the paths, or contact support. ### Writable directories The PHP process must be able to write to: - `data/` (and `data/galleries/`) — flat-file content - `media/` — hero + showreel images - `config/` — only for the online password change On typical shared hosting (suEXEC/FPM running as your user) this already works; otherwise `chmod 755` the directories (or `775`/`777` as a last resort). ## 5. First-deploy smoke test 1. Open `/admin/`, log in, change the password (Settings). 2. Front page: set your name and intro text, upload a hero image → check the landing page. 3. Showreel: upload 2–3 images → check `/showreel.php`, scroll behavior, and that navigation hides when scrolling down. 4. Galleries: create a test gallery **with password and an expiry date of today**, upload a handful of images. Then: - the upload list shows *done* for each file (webhost → S3 working), - the gallery page asks for the password and then shows images (presigned GETs working), - images load from `your-objectstorage.com`, not from your domain, - tomorrow the gallery shows "not available" (expiry working). 5. Delete the test gallery — the S3 objects are removed as well. ## 6. Local development ```bash php -S localhost:8080 router.php ``` `router.php` reproduces the `.htaccess` protection for the PHP built-in server (which does not read `.htaccess`); it is only used locally. Everything except real S3 traffic works without credentials; gallery pages render presigned URLs that simply won't resolve until real keys are configured.