Files
timebank-cc-public/references/MULTI_PLATFORM_INSTANCES.md
T

494 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Multiple Platforms, Servers and Instances: White-Label Guide
How to set up and maintain a branded platform instance (Timebank.cc, Uuro or any other) running this
application. One instance runs per server; every instance runs the same code from git and differs only in
its `.env`, its platform folder `platform/` (config, theme, logos, favicons, text and template overrides; outside
git) and its database content.
The platform folder is the recommended place for everything platform-specific. The older way, gitignored files in
`config/` and uploaded branding files in `storage/`, keeps working; the platform folder wins where both exist.
| Instance | Server | Platform files (2026-10-19) |
|---|---|---|
| Timebank.cc | production | the older way: `config/timebank_cc.php`, `config/themes.php`; its logos and favicons are the defaults in the code. Moving it into `platform/` is planned (`php artisan platform:init --from-current`). |
| Uuro | staging | its own branch `uuro` until its Laravel 11 upgrade; then everything in `platform/` (runbook: [RUNBOOK_LARAVEL_10_TO_11.md](RUNBOOK_LARAVEL_10_TO_11.md)). |
Last checked against the code: 2026-10-19 (branch `laravel-11`, cd982cdd).
---
## Contents
1. [Checklist for a new platform instance](#1-checklist-for-a-new-platform-instance)
2. [What makes an instance different](#2-what-makes-an-instance-different)
3. [The platform folder](#3-the-platform-folder)
4. [Platform configuration](#4-platform-configuration)
5. [Theme](#5-theme)
6. [Logos and favicons](#6-logos-and-favicons)
7. [Platform name in texts and translations](#7-platform-name-in-texts-and-translations)
8. [Platform-specific pages and switches](#8-platform-specific-pages-and-switches)
9. [Footer](#9-footer)
10. [Custom content pages](#10-custom-content-pages)
11. [Keeping an instance up to date](#11-keeping-an-instance-up-to-date)
12. [Troubleshooting](#12-troubleshooting)
13. [Quick reference](#13-quick-reference)
14. [Related documentation](#14-related-documentation)
---
## 1. Checklist for a new platform instance
The server itself (PHP, MySQL, Redis, Elasticsearch, Reverb, mail) is set up with
[SETUP_GUIDE.md](SETUP_GUIDE.md) and [PRODUCTION_DEPLOYMENT.md](PRODUCTION_DEPLOYMENT.md). Then, for the
platform:
1. **`.env`**: set at least
- `APP_NAME`, `APP_URL`
- `TIMEBANK_CONFIG=<platform>`: the platform's name, e.g. `myorg`. `timebank_config()` reads the platform
config under this name; see [section 4](#4-platform-configuration).
- `TIMEBANK_THEME=<theme>`: `timebank_cc`, `uuro`, `vegetable` or `yellow`; not needed when the platform folder
has a `theme.php` of its own
- `DEPLOY_ENVIRONMENT=server`, `DEPLOY_APP_DIR`, `DEPLOY_SERVER_TYPE`, `DEPLOY_WS_URL`
- `DB_DEPLOY_USERNAME` / `DB_DEPLOY_PASSWORD`: a MySQL user that may alter and restore the database
- `MAIL_*`, `MAIL_SUPPORT_ADDRESS`, `MAIL_BOUNCE_ADDRESS`
2. **Platform folder** ([section 3](#3-the-platform-folder)), in the application root:
```bash
php artisan platform:init # platform/ from platform.example/
cp config/timebank-default.php.example platform/config.php # the platform config, from the template
```
3. **Platform config** `platform/config.php`: names, currency and terms per language, mail addresses,
account limits, the switches in [section 8](#8-platform-specific-pages-and-switches), and the footer. The
template uses `Example Timebank` and `@example.org` as placeholders: replace all of them.
4. **Theme**: pick a built-in one with `TIMEBANK_THEME`, or make your own in `platform/theme.php`
([section 5](#5-theme)).
5. **Branding files** in `platform/public/`: `mail-logo.png`, `mail-logo-inverted.png`, the favicons and
`og-image.png` ([section 6](#6-logos-and-favicons)); a logo of your own in `platform/views/logos/<theme>.blade.php`.
Without them, emails get a generated text logo with the platform name, and the site uses the default favicons.
6. **First deploy**: `./deploy.sh -e server`. It creates the config files the code still needs from their
templates (`config/themes.php` and the two template configs; not `config/<platform>.php` when
`platform/config.php` exists), merges new keys into `platform/config.php` and links `platform/public` at
`/platform`.
7. **Privacy policy**: put your policy in `references/gdpr/<platform>/<YYYY-MM-DD>/privacy-policy-FULL-<locale>.md`
(the download link on the privacy page reads the newest dated folder). Without it the download returns 404.
8. **Content**: static pages (FAQ, principles, getting started and so on) are posts in the database; create
them in the admin area ([section 10](#10-custom-content-pages)).
9. **Background services**: queue workers with all queues, Reverb and the scheduler
([QUEUE_WORKERS_SETUP.md](QUEUE_WORKERS_SETUP.md), [WEBSOCKET_SETUP.md](WEBSOCKET_SETUP.md)), search
([ELASTICSEARCH_SETUP.md](ELASTICSEARCH_SETUP.md)), bounce handling ([BOUNCE_SETUP.md](BOUNCE_SETUP.md)).
10. **Check**: open the site with your theme, log in, switch profiles, send a test mail
([EMAIL-TESTING-GUIDE.md](EMAIL-TESTING-GUIDE.md)), and look for the old brand: no "Timebank.cc",
no `example.org` addresses, no city pages you do not want. `php artisan platform:check` reports which config is
used and whether `/platform` is linked.
11. **Off-site copy**: put `platform/` in a private git repository of its own (no secrets in it: those stay in
`.env`); the server's backups stay on the server.
---
## 2. What makes an instance different
| Layer | In the platform folder (recommended) | The older way (still works) | In git |
|---|---|---|---|
| Environment, passwords | `.env` | `.env` | no |
| Platform behaviour, names, mail, limits, footer, switches | `platform/config.php` | `config/<TIMEBANK_CONFIG>.php` | no; templates `config/*.php.example` are |
| Theme colours, fonts, logo references | `platform/theme.php` | an entry in `config/themes.php`, chosen with `TIMEBANK_THEME` | no; template `config/themes.php.example` is |
| Website logo (SVG) | `platform/views/logos/<theme>.blade.php` | `resources/views/logos/<theme>.blade.php` | the older way is |
| Email/PDF logos, favicons, preview image | `platform/public/` | `storage/app/public/app-images/`, `public/<favicon_dir>`, `public/<og_image>` | no |
| Texts and templates | `platform/lang/`, `platform/views/` (overrides) | none: shared by all instances | no |
| Static page content, posts, categories | database | database | no |
| Privacy policy | `references/gdpr/<platform>/` | the same | yes |
Everything else, the application code and the translations, is shared by all instances; a platform changes
texts and templates through overrides in its platform folder, never in the shared files.
**Config protection.** The platform folder and the active config files are gitignored, so a `git pull` never
overwrites them.
`deploy.sh` creates a missing one from its `.example` template, and `php artisan config:merge` copies keys
that are new in the template into your file without changing your values. Details:
[CONFIG_MANAGEMENT.md](CONFIG_MANAGEMENT.md).
---
## 3. The platform folder
Everything that makes an installation one platform can live in one folder, `platform/` in the project root
(`PLATFORM_PATH` in `.env` moves it). It is outside git (`.gitignore`), so deploys, upgrades and rollbacks never
touch it, and the same code from `main` runs on every server. Every part is optional: without the folder, or
without one of its parts, the installation runs on `config/` and the files in the code as described in the
sections below. Loaded by `App\Providers\PlatformServiceProvider`.
| Path | What | Behaviour |
|---|---|---|
| `config.php` | The platform config | Replaces `config/<TIMEBANK_CONFIG>.php`; `config:merge` adds new keys to it |
| `theme.php` | One theme, with its `id` | Becomes the active theme (instead of `TIMEBANK_THEME`) |
| `lang/<locale>.json`, `lang/<locale>/<group>.php`, `lang/vendor/<package>/...` | Changed texts, only the keys you change | Merged key by key over the code's texts; placeholders keep working |
| `views/...`, `views/mail/...`, `views/vendor/<package>/...` | Changed templates: pages, emails, the email layout, the logo, package views | Replace the whole template |
| `public/` | Favicons, `mail-logo.png`, `mail-logo-inverted.png` | Served at `/platform/` after `php artisan platform:link` (deploy.sh runs it); used before the theme's files |
The tracked skeleton `platform.example/` (with a README per path) is the starting point.
```bash
php artisan platform:init # create platform/ from platform.example/
php artisan platform:init --from-current # also copy config/<name>.php and the active theme into it
php artisan platform:override emails.layouts.html # copy a template to change it (also mail::html.header,
# wirechat::components.divider)
php artisan platform:link # serve platform/public at /platform
php artisan platform:check # read-only: which config is used, stale overrides
```
**Texts or templates?** 31 of the 34 email templates take their wording from translation strings, so a text
override changes almost every email. Override a template only for another structure or layout.
**Overrides go stale.** A translation key is the English source sentence: when the code rewords it, an override
of the old sentence is no longer used. A copied template misses later fixes to its original.
`php artisan platform:check` reports both: keys the code no longer has (keys that the platform's own templates
use count as its own texts) and templates whose original changed since `platform:override` copied it (the first
line holds the original's fingerprint). Run it after every update.
**Moving an existing installation into the folder.** `php artisan platform:init --from-current`, copy the brand
files it lists into `platform/public/`, `php artisan platform:link`, check the site. `config/<name>.php` stays as a
fallback until you remove it; `platform:check` warns while both exist.
**Good practice.**
- No secrets in the folder: passwords and keys stay in `.env`. Then the folder can be a private git repository of
its own, which gives history and an off-site copy (the server backups stay on the server). The storage backup
(`scripts/backup-storage.sh`) also archives it, in `backups/platform/`.
- The web server only reads the folder; normal file permissions are enough.
- After a change: `php artisan view:clear` for templates; restart the queue workers and Reverb after a config
change (they read the config when they start).
- Tests never read a developer's own platform folder (`PLATFORM_PATH` in `phpunit.xml` points elsewhere): they run
on `config/<TIMEBANK_CONFIG>.php`. A developer whose config moved into `platform/` therefore keeps that file too
(92 tests failed on the template alone, 2026-10-19: they depend on the developer's config values).
---
## 4. Platform configuration
**File**: `platform/config.php`; without it, `config/<name>.php`, where `<name>` is `TIMEBANK_CONFIG` in `.env`.
Either way the config is read under that name, so code uses `timebank_config('key')`. Any name works:
`timebank_cc` and `timebank-default` exist as templates, and a platform can use its own (`uuro`, `myorg`):
- Start `platform/config.php` from `config/timebank-default.php.example`. Without a platform folder, `deploy.sh`
creates `config/<name>.php` from that template when it is missing, and adds it to `.git/info/exclude` on that
server, so deploys do not report it as an uncommitted change.
- A development machine keeps `config/<name>.php` even with a platform folder: the tests read that file and never the
platform folder.
- `php artisan config:merge <name>` (and `--all`) adds new keys to the platform config (to `platform/config.php`
when it exists) from `config/<name>.php.example` if that exists, otherwise from
`config/timebank-default.php.example`. Lists (footer links, payment types, rules) are left as they are.
Do not copy the active `config/timebank-default.php` of another server: it may be outdated or hold that
server's settings. Start from the template.
**Main sections** (see the comments in the template for every key):
| Key | What |
|---|---|
| `platform_translations.<locale>.*` | `platform_name`, `platform_name_legal`, `platform_name_short`, `platform_slogan`, currency name, plural, symbol and position, thousand separator, `platform_user(s)`, `platform_principles`, for each language (en, nl, de, es, fr) |
| `mail.*` | `system_admin`, `content_admin`, `payments`, `chat_messenger`, `support` addresses and names; `platform_sign_off_signature` per language |
| `accounts.<type>` and `profiles.<type>` | Account limits (`limit_min`, `limit_max` in minutes) and the transaction types an account may receive, per profile type |
| `permissions.<type>` | Which transaction types each profile type may make |
| `account_info.<type>.*` | Which balances and totals are shown publicly (`*_public`), to the owner (`*_self`) and to privileged viewers (`*_privileged`, see `privileged_viewer_types`) |
| `local_pages`, `android_app` | Platform-specific pages, see [section 8](#8-platform-specific-pages-and-switches) |
| `footer` | Footer sections and links, see [section 9](#9-footer) |
| `seo` | `allow_indexing_guest`, `allow_indexing_auth` |
| `profile_inactive`, `profile_incomplete`, `delete_profile` | See [PROFILE_INACTIVE_CONFIG.md](PROFILE_INACTIVE_CONFIG.md), [PROFILE_INCOMPLETE_CONFIG.md](PROFILE_INCOMPLETE_CONFIG.md), [PROFILE_AUTO_DELETE.md](PROFILE_AUTO_DELETE.md) |
| `main_search_bar`, `search_optimization` | Search behaviour and boosts, see [SEARCH_REFERENCE.md](SEARCH_REFERENCE.md) |
| `cert_monitor`, `health_monitor` | Certificate expiry warnings and the health check, mostly set through `.env` |
**Reading values in code**:
```php
timebank_config('accounts.user.limit_max'); // the active platform's value
timebank_config('local_pages.the-hague', true); // with a default for a missing key
config('app.timebank_config'); // the platform config name itself
platform_name(); platform_currency_symbol(); // wording helpers, current locale
```
---
## 5. Theme
**File**: `platform/theme.php`, one theme with its own `id`, which becomes the active theme. Without it: the theme
named by `TIMEBANK_THEME` in `config/themes.php` (template `config/themes.php.example`).
Themes are applied at runtime through CSS variables, so **one asset build serves every theme**: switching
`TIMEBANK_THEME` needs no `npm run build`. Only `VITE_*` values in `.env` (such as the Reverb host) are
compiled into the build.
**A theme of your own** belongs in `platform/theme.php`: copy a whole theme block from `config/themes.php.example`
(every colour key is needed: a missing one leaves its CSS variable empty), give it an `id`, and change the name,
colours, typography and logos. `php artisan platform:init --from-current` writes the active theme there for you.
Its logo view may live in the platform folder too (`platform/views/logos/mybrand.blade.php`): nothing of the theme
needs to be in the code.
```php
<?php
return [
'id' => 'mybrand',
'name' => 'My Brand',
'description' => '...',
'logos' => [
'svg_inline' => 'logos.mybrand', // platform/views/logos/mybrand.blade.php
'email_logo' => null, // platform/public/mail-logo.png is used first
'email_logo_inverted' => null, // platform/public/mail-logo-inverted.png
'favicon_dir' => null, // platform/public/ is used first
'og_image' => null, // platform/public/og-image.png is used first
],
'colors' => [ 'brand' => '#...', 'logo' => '#...', 'primary' => [50 => '#...', /* ... */ 900 => '#...'], /* ... */ ],
'typography' => [ 'font_family_body' => '...', 'font_family_heading' => '...', /* ... */ ],
];
```
The older way still works: the same block under a new key in `config/themes.php`, selected with `TIMEBANK_THEME`.
A new theme that should be available to every installation belongs in `config/themes.php.example` plus a
logo view in `resources/views/logos/`, committed to git.
**In views and code**, always use theme classes and helpers, never fixed colours: `bg-theme-brand`,
`text-theme-primary`, `border-theme-border`, `theme_color('primary.500')`, `theme_font('font_family_body')`.
UI patterns: [STYLE_GUIDE.md](STYLE_GUIDE.md), [THEME_CLASS_CONVERSION_GUIDE.md](THEME_CLASS_CONVERSION_GUIDE.md).
Technical details: [THEME_IMPLEMENTATION.md](THEME_IMPLEMENTATION.md).
---
## 6. Logos and favicons
Each asset is looked up in this order: the platform folder, the theme setting, the fallback.
| Asset | Platform folder | Theme setting (the older way) | Fallback when missing |
|---|---|---|---|
| Website logo (inline SVG) | `platform/views/logos/<theme>.blade.php` | `resources/views/logos/<theme>.blade.php` (in git; see `resources/views/logos/README.md`) | none: required for a theme |
| Email and PDF logo | `platform/public/mail-logo.png` | `storage/app/public/<logos.email_logo>`, e.g. `app-images/uuro_mail_logo.png` | a generated PNG with the platform name in the theme's logo colour; the `timebank_cc` theme falls back to `app_mail_logo.png` |
| Email logo for dark headers (newsletter) | `platform/public/mail-logo-inverted.png` | `storage/app/public/<logos.email_logo_inverted>` | the generated logo in white; `timebank_cc`: `app_mail_logo_inverted.png` |
| Favicons: `favicon.ico`, `favicon-32x32.png`, `favicon-16x16.png`, `apple-touch-icon.png` | `platform/public/` | `public/<logos.favicon_dir>/` | the files in `public/` |
| Optional icons: `favicon.svg`, `favicon-96x96.png`, `site.webmanifest` | `platform/public/` | `public/<logos.favicon_dir>/` | the file in `public/`, else not linked |
| Preview image for shared links (`og:image`, about 1200×630) | `platform/public/og-image.png` | `public/<logos.og_image>` | no preview image; the other Open Graph tags (title, site name, URL, locale) are always there |
Files in `platform/public/` are served at `/platform/...` once `php artisan platform:link` has run (`deploy.sh`
runs it). The layouts include one partial for the title, the icons and the Open Graph tags:
`resources/views/layouts/partials/brand-head.blade.php`.
**Email logo**: a PNG of about 900×700 pixels with a transparent background works best (emails show it about
120 pixels wide). Put it in `platform/public/mail-logo.png` (or, the older way, upload it to
`storage/app/public/app-images/` and name it in the theme). Code reads it
through `theme_email_logo_url()` (emails) and `theme_email_logo_file()` (PDFs), with `true` for the inverted logo; the generated fallback is
`App\Services\GeneratedLogo`, cached in `storage/app/public/app-images/generated/`.
**Favicons**: put the files in `platform/public/` (or, the older way, in
`storage/app/public/app-images/favicons/<theme>/` with `'favicon_dir' => 'storage/app-images/favicons/<theme>'` in
the theme). Layouts read them through `theme_favicon('favicon.ico')` and, for the optional ones,
`theme_favicon_optional()`; the preview image through `theme_og_image()`.
The platform folder, uploaded logos (`*_mail_logo*.png`) and favicons are gitignored, so they do not appear as
changes in `git status` and survive every deploy.
---
## 7. Platform name in texts and translations
Translations are in `resources/lang/<locale>.json` (and `resources/lang/<locale>/*.php`), shared by all
platforms. Strings that mention the platform use placeholders, replaced from `platform_translations`:
| Placeholder | Replaced by |
|---|---|
| `@PLATFORM_NAME@`, `@PLATFORM_NAME_SHORT@`, `@PLATFORM_NAME_LEGAL@`, `@PLATFORM_SLOGAN@` | names |
| `@PLATFORM_USER@`, `@PLATFORM_USERS@`, `@PLATFORM_PRINCIPLES@` | terms |
| `@PLATFORM_CURRENCY_NAME@`, `@PLATFORM_CURRENCY_NAME_PLURAL@`, `@PLATFORM_CURRENCY_SYMBOL@` | currency |
In code, render such a string with `trans_with_platform('Why @PLATFORM_NAME_SHORT@?')`, not with `__()`
(which leaves the placeholder in place). Never write a brand name into a translation key or a view. The
wording helpers `platform_name()`, `platform_users()`, `platform_currency_symbol()` and so on return the
values directly. Translation workflow: `references/translations/`.
A platform that wants other wording overrides single texts in its platform folder (`platform/lang/`,
[section 3](#3-the-platform-folder)) instead of changing the shared files.
---
## 8. Platform-specific pages and switches
Some pages only make sense for one platform. They are switched in the platform config:
```php
'local_pages' => [ // static pages about Timebank.cc's local groups
'the-hague' => false,
'lekkernassuh' => false,
'amst-brus-lisb' => false,
],
'android_app' => [
'enabled' => false, // the Android app page and the app's update check (/app/version)
'apk_file' => 'app-{version}.apk', // in storage/app/public/apk/; {version} is the version name
],
```
A page that is off has no route (404), and footer links to it are left out automatically. The default
template has all of them off; the Timebank.cc template has them on. A key missing from an older config keeps
the old behaviour (on) until `config:merge` adds it.
For an Android app of your own, see [ANDROID_APP_UPDATE_FLOW.md](ANDROID_APP_UPDATE_FLOW.md);
`scripts/deploy-apk.sh` uploads an APK to another server with `APK_REMOTE_HOST=... APK_REMOTE_APP_DIR=...`.
---
## 9. Footer
The footer is built from `footer` in the platform config:
```php
'footer' => [
'sections' => [
[
'title' => 'Who we are', // translation key
'order' => 1, // left to right
'visible' => true,
'links' => [
['route' => 'static-faq', 'title' => 'FAQ', 'order' => 1, 'visible' => true],
['url' => 'mailto:info@example.org', 'title' => 'Email us', 'order' => 2, 'visible' => true],
['route' => 'static-messenger', 'title' => 'Chat messenger', 'order' => 3, 'visible' => true,
'auth_required' => true], // only for logged-in users
],
],
],
'tagline' => 'Your time is currency', // translation key
],
```
- `route` is a route name from `routes/web.php`; a route that does not exist on this platform (e.g. a
switched-off local page) is skipped instead of causing an error.
- `url` is for external or `mailto:` links.
- `title` values are translation keys: add them to `resources/lang/*.json` for every language.
---
## 10. Custom content pages
Static pages show posts from the database: each page view (e.g. `resources/views/static/faq.blade.php`)
embeds `@livewire('static-post', ['type' => 'SiteContents\Static\Faq', ...])`, and admins write the content
as posts in that category. Limits per page are in `posts.static.<page>` of the platform config.
**A new page** (a code change, so for every platform):
1. Category type in `database/seeders/ExtendCategoriesSeeder.php`, e.g. `SiteContents\Static\MyPage`, and
its names and slugs in all 5 languages in `database/seeders/ExtendCategoryTranslationsSeeder.php`.
2. A view `resources/views/static/my-page.blade.php`, modelled on an existing static page.
3. A route in `routes/web.php`, in the localized group:
`Route::view(LaravelLocalization::transRoute('routes.static.my-page'), 'static.my-page')->name('static-my-page');`
with the URL slug per language in `resources/lang/<locale>/routes.php`.
4. Optional `posts.static.my-page` limits in both config templates, and a footer link.
5. `deploy.sh` runs both seeders; locally: `php artisan db:seed --class=ExtendCategoriesSeeder` and
`--class=ExtendCategoryTranslationsSeeder`.
To change seeded content on every installation (rename a category type or slug), add a step to
`app/Console/Commands/DatabaseUpdate.php` (`php artisan database:update`, run by `deploy.sh`).
---
## 11. Keeping an instance up to date
- **Routine deploys**: `./deploy.sh -e server`. It pulls `main`, offers to merge new config keys, installs
packages, migrates, re-indexes search and builds assets. Answer `y` and then `yes` per file to the config
merge; it adds new keys and keeps your values (backups in `storage/config-backups/`).
- **Check for new settings at any time**: `php artisan config:merge --all --dry-run`. Lists (footer
sections and links, payment types, validation rules) are the platform's own choice: a list the platform config
has is never changed by a merge; a missing one is added whole.
- **Stale overrides**: `php artisan platform:check` after every update.
- **Major upgrades** (e.g. Laravel 11 to 12) go through `scripts/upgrade.sh` (backup, maintenance window with tests,
rollback), never through a routine deploy: `deploy.sh` stops a server deploy that would raise the Laravel major
version. The Laravel 10 to 11 upgrade is described in [UPGRADE_LARAVEL_10_TO_11.md](UPGRADE_LARAVEL_10_TO_11.md);
its runbook is Uuro's, the last installation on Laravel 10, which leaves its own branch with
`upgrade.sh --leave-branch`.
After a merge, review new keys in your platform config: new switches default to the template's value
(off in the default template), and names or addresses from the template are placeholders.
---
## 12. Troubleshooting
| Problem | Check |
|---|---|
| Theme not applied | `platform/theme.php` has an `id` and wins over `TIMEBANK_THEME`; without it, `TIMEBANK_THEME` matches a key in `config/themes.php`. The theme has all colour keys (compare with `themes.php.example`). |
| Wrong or default settings | `php artisan platform:check` shows which config is used (`platform/config.php` wins over `config/<name>.php`); without a platform folder, `TIMEBANK_CONFIG` matches the file name without `.php`. Run `php artisan config:merge <name> --dry-run` for missing keys. Restart the queue workers and Reverb after a config change. |
| A changed text or template does not show | `php artisan platform:check`: the code may have reworded the sentence (the override's key no longer exists) or changed the original template. After changing a template: `php artisan view:clear`. |
| Logo, favicon or preview image from `platform/public/` not used | `public/platform` is not linked: `php artisan platform:link`. |
| Brand name of another platform in a text | Look for a literal name in the view or translation key and switch it to a placeholder with `trans_with_platform()` ([section 7](#7-platform-name-in-texts-and-translations)). Texts in posts are content: edit them in the admin area. |
| Email shows a text logo instead of yours | No `platform/public/mail-logo.png` (or it is not linked), and the file named in `logos.email_logo` is missing in `storage/app/public/`. |
| Footer link missing | `visible` is true, the route exists on this platform, the section is visible, and `auth_required` matches. |
| Footer link causes an error | Should not happen any more (unknown routes are skipped); check the `url`/`route` key names. |
| City or Android pages appear on another platform | `local_pages` / `android_app` are missing from its config: run `config:merge`, then set them to `false`. |
| Privacy policy download gives 404 | No `references/gdpr/<TIMEBANK_CONFIG>/<date>/privacy-policy-FULL-<locale>.md`. |
| `deploy.sh` asks about uncommitted changes | Run `git status`: files changed on the server. The platform folder, uploaded logos, favicons and your platform config are excluded automatically; anything else was edited by hand. |
| `config:merge` reports `not merged` or `Merged config is invalid` | The file was left unchanged; see [CONFIG_MANAGEMENT.md](CONFIG_MANAGEMENT.md). |
---
## 13. Quick reference
```bash
# .env
TIMEBANK_CONFIG=<platform> # config/<platform>.php
TIMEBANK_THEME=<theme> # a key in config/themes.php (platform/theme.php wins)
PLATFORM_PATH=platform # the platform folder (default)
# Commands
./deploy.sh -e server # deploy (creates missing configs, merges new keys)
php artisan config:merge --all --dry-run # new keys waiting
php artisan config:merge <name> # merge into one config
php artisan config:merge --restore # restore a config from its backup
php artisan database:update # data updates of seeded content
php artisan platform:init [--from-current] # create the platform folder
php artisan platform:override <view> # copy a template into it
php artisan platform:link # serve platform/public at /platform
php artisan platform:check # stale overrides, config in use
```
```php
timebank_config('key.path', $default) // platform setting
config('app.timebank_config') // platform config name
platform_name() trans_with_platform('...@PLATFORM_NAME@...')
theme_color('brand') theme_font('font_family_body') theme_logo('svg_inline')
theme_email_logo_url() theme_email_logo_file() theme_favicon('favicon.ico') theme_og_image()
platform_path('lang') // inside the platform folder
```
---
## 14. Related documentation
**Setting up a server and instance**
- [SETUP_GUIDE.md](SETUP_GUIDE.md): complete Debian server setup (PHP, MySQL, Redis, Elasticsearch, web server)
- [PRODUCTION_DEPLOYMENT.md](PRODUCTION_DEPLOYMENT.md): production deployment
- [EXTERNAL_SERVICES_REQUIREMENTS.md](EXTERNAL_SERVICES_REQUIREMENTS.md): external services an instance needs
- [QUEUE_WORKERS_SETUP.md](QUEUE_WORKERS_SETUP.md): queue workers (all queues)
- [WEBSOCKET_SETUP.md](WEBSOCKET_SETUP.md), [WEBSOCKET_SEPARATE_DOMAIN.md](WEBSOCKET_SEPARATE_DOMAIN.md): Reverb
- [ELASTICSEARCH_SETUP.md](ELASTICSEARCH_SETUP.md): search
- [SSL_CERTIFICATE_RENEWAL.md](SSL_CERTIFICATE_RENEWAL.md): certificates
- [DEPLOYMENT_HTMLPURIFIER_FIX.md](DEPLOYMENT_HTMLPURIFIER_FIX.md): HTMLPurifier cache directory
**Configuration and updates**
- [CONFIG_MANAGEMENT.md](CONFIG_MANAGEMENT.md): config templates, `config:merge`, backups, platforms with their own config name
- [UPGRADE_LARAVEL_10_TO_11.md](UPGRADE_LARAVEL_10_TO_11.md): the Laravel 10 to 11 upgrade; [RUNBOOK_LARAVEL_10_TO_11.md](RUNBOOK_LARAVEL_10_TO_11.md): its checklist for the Uuro server
- `platform.example/README.md`: what goes where in a platform folder
- [PROFILE_INACTIVE_CONFIG.md](PROFILE_INACTIVE_CONFIG.md), [PROFILE_INCOMPLETE_CONFIG.md](PROFILE_INCOMPLETE_CONFIG.md), [PROFILE_AUTO_DELETE.md](PROFILE_AUTO_DELETE.md), [PROFILE_DELETION_GRACE_PERIOD.md](PROFILE_DELETION_GRACE_PERIOD.md): profile lifecycle settings
- [SEARCH_REFERENCE.md](SEARCH_REFERENCE.md), [SEARCH_REFERENCE_SIMPLE.md](SEARCH_REFERENCE_SIMPLE.md): search settings and behaviour
**Look and feel**
- [THEME_IMPLEMENTATION.md](THEME_IMPLEMENTATION.md): how themes work
- [STYLE_GUIDE.md](STYLE_GUIDE.md): UI patterns for every view
- [THEME_CLASS_CONVERSION_GUIDE.md](THEME_CLASS_CONVERSION_GUIDE.md): converting fixed colours to theme classes
- `resources/views/logos/README.md`: website (SVG) logos
**Mail**
- [EMAIL-TESTING-GUIDE.md](EMAIL-TESTING-GUIDE.md), [ARTISAN-EMAIL-COMMAND-SUMMARY.md](ARTISAN-EMAIL-COMMAND-SUMMARY.md): testing emails per theme
- [BOUNCE_SETUP.md](BOUNCE_SETUP.md), [MAIL_BOUNCE_SYSTEM.md](MAIL_BOUNCE_SYSTEM.md): bounce handling
**Platform-specific features**
- [ANDROID_APP_UPDATE_FLOW.md](ANDROID_APP_UPDATE_FLOW.md): Android app and its update check
- `references/gdpr/<platform>/`: privacy policy per platform
- `references/translations/`: translation workflow
**For developers and AI agents**: `AGENTS.md` in the repository root.