# Setting Up the Server ## Overview Installation and operation of the Manage server. Requirements: PHP 8.0 or newer and a web server. No database server, no Composer, no build step. ## Installation 1. Put the repository into a directory of the web server, for example `/var/www/manage`. 2. Create the configuration: ```bash cp config.sample.php config.php ``` 3. Generate a password hash and enter it: ```bash php -r 'echo password_hash("a-long-password", PASSWORD_DEFAULT), PHP_EOL;' ``` ```php define("MANAGE_ADMIN_PASSWORD_HASH", '$2y$12$…'); ``` Use single quotes — a bcrypt hash contains `$`. 4. Set the public URL. Without this value, no client can download a package: ```php define("MANAGE_PUBLIC_URL", "https://manage.example.org"); ``` Absolute, without a trailing slash. If the installation lives in a subdirectory, that belongs too: `https://example.org/manage`. 5. Name the product. `MANAGE_PACKAGE_PREFIX` determines the filename of stored packages and should match the project's build script: ```php define("MANAGE_PRODUCT_NAME", "Example Orderform"); define("MANAGE_PACKAGE_PREFIX", "example-orderform"); ``` 6. Make sure `storage/` is writable. The directory is created on demand if needed: ```bash mkdir -p storage && chown www-data:www-data storage && chmod 2775 storage ``` 7. Open the UI: `https://manage.example.org/admin/login.php`. Under **Settings → Diagnostics** you can then see whether everything essential checks out: public URL, write access, password, upload limits. ## Web server ### Apache The bundled `.htaccess` locks `storage/`, `includes/`, `client-package/` and `config.php`, and sets security headers. It only works when `AllowOverride All` is set for the directory. ### nginx `.htaccess` has no effect under nginx. The locks must be set by hand: ```nginx location ^~ /storage/ { deny all; return 404; } location ^~ /includes/ { deny all; return 404; } location ^~ /client-package/ { deny all; return 404; } location = /config.php { deny all; return 404; } location ~ /\. { deny all; return 404; } ``` Verify the locks are effective: ```bash curl -s -o /dev/null -w "%{http_code}\n" https://manage.example.org/config.php curl -s -o /dev/null -w "%{http_code}\n" https://manage.example.org/storage/instances.json ``` Both must return `403` or `404`. ## Upload limits Backups and release packages are uploaded over HTTP. Both PHP limits need to be large enough, with `post_max_size` at least as large as `upload_max_filesize`: ```ini upload_max_filesize = 256M post_max_size = 256M max_execution_time = 300 memory_limit = 256M ``` The current values are shown on the diagnostics page. If a value is too small, the client reports an error message that names the cause explicitly. `memory_limit` becomes relevant when the S3 archive is active: an upload to S3 holds the file in memory in full. ## Retention Configurable under **Settings**, stored in `storage/settings.json`. The values there take precedence over the constants in `config.php`. - **Local backups per instance** – default 30, minimum 1 - **S3 backups per instance** – default 365, only with the S3 archive active Changes apply immediately, not only on the next upload. ## S3 archive (optional) Without S3, all backups sit on the local disk. With S3, every received backup is additionally pushed to an S3-compatible object storage system; locally, only the newest copies remain. ```php define("MANAGE_S3_ENABLED", true); define("MANAGE_S3_ENDPOINT", "https://fsn1.your-objectstorage.com"); define("MANAGE_S3_REGION", "fsn1"); define("MANAGE_S3_BUCKET", "my-backup-bucket"); define("MANAGE_S3_PREFIX", "manage-backups"); define("MANAGE_S3_ACCESS_KEY", "…"); define("MANAGE_S3_SECRET_KEY", "…"); ``` Objects live under `//`. Addressing: the default is virtual-hosted (`https://./`), which Hetzner and most providers expect. If the provider requires path-style, set `MANAGE_S3_PATH_STYLE` to `true`. Behavior: - S3 errors never fail a client upload. - A local copy is only deleted once it has fallen out of local retention **and** the S3 copy is confirmed. - Failed uploads are retried on the next upload from the same instance, or via "Catch up S3 uploads now". - The bucket can and should stay private: downloads go through the UI. - When enabling this on an existing installation, press "Catch up S3 uploads now" once so the archive catches up. Troubleshooting via `storage/logs/s3.log` and the excerpt under **Settings**: `AccessDenied` or a redirect in the status chain almost always points to the wrong addressing style; `SignatureDoesNotMatch` to a wrong region or secret key. Redirects are deliberately not followed, so a misconfiguration stays visible. ## Backing up the server The Manage server holds release packages and every instance's backups — it is itself worth backing up. To back up: - `storage/` – instances, manifest, packages, backups, settings - `config.php` – credentials With the S3 archive active, backups additionally live in the bucket; but `storage/instances.json` and `storage/releases/` do not. ## Operation - Login is rate-limited per IP (default 10 attempts per 15 minutes). - Rate limiting also applies to failed API authentications. - Logs live under `storage/logs/` and rotate automatically. - A password change happens in `config.php`; logged-in sessions remain valid until they expire. To end them immediately, clear `session.save_path` or change `session_name` in `includes/auth.php`. ## Next - [INSTANCE_MANAGEMENT](INSTANCE_MANAGEMENT.md) – creating instances and issuing tokens - [RELEASING](RELEASING.md) – building and publishing releases - [CONFIG_REFERENCE](CONFIG_REFERENCE.md) – every server constant