# 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*. 3. Configure **CORS** on the bucket so the admin browser may upload directly to S3 and so gallery images load on your domain. Hetzner supports the standard S3 CORS API; using `s3cmd`, `aws s3api`, or any S3 GUI client (e.g. Cyberduck), apply: ```json { "CORSRules": [ { "AllowedOrigins": ["https://www.your-domain.com"], "AllowedMethods": ["GET", "PUT"], "AllowedHeaders": ["*"], "MaxAgeSeconds": 3600 } ] } ``` With `aws` CLI: ```bash aws s3api put-bucket-cors \ --endpoint-url https://fsn1.your-objectstorage.com \ --bucket my-photo-galleries \ --cors-configuration file://cors.json ``` Replace the origin with your real domain (and add `http://localhost:8080` temporarily if you want to test uploads locally). ## 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 (S3 + CORS 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.