# 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 whole project via FTP/SFTP. Two layouts work: **Preferred — document root points at `public/`:** `app/`, `config/` and `data/` are outside the web root and unreachable by design. Most shared hosters let you set the document root per domain. **Fallback — document root is the project root:** The included root `.htaccess` rewrites all requests into `public/`, and `app/.htaccess`, `config/.htaccess`, `data/.htaccess` each deny direct access. Verify after deploying: `https://your-domain.com/config/config.php` must return *403 Forbidden*. ### Writable directories The PHP process must be able to write to: - `data/` (and `data/galleries/`) — flat-file content - `public/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 -t public ``` Everything except real S3 traffic works without credentials; gallery pages render presigned URLs that simply won't resolve until real keys are configured.