From 9d4444f4ff4b404704b80acd2b9f5a7df5aef282 Mon Sep 17 00:00:00 2001 From: thibaud-leclere Date: Tue, 5 May 2026 09:11:59 +0200 Subject: [PATCH] docs: add installer bootstrap design --- ...26-05-05-get-installer-bootstrap-design.md | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-05-get-installer-bootstrap-design.md diff --git a/docs/superpowers/specs/2026-05-05-get-installer-bootstrap-design.md b/docs/superpowers/specs/2026-05-05-get-installer-bootstrap-design.md new file mode 100644 index 0000000..36fb2a3 --- /dev/null +++ b/docs/superpowers/specs/2026-05-05-get-installer-bootstrap-design.md @@ -0,0 +1,215 @@ +# Get Installer Bootstrap Design + +## Goal + +Build a small web application that serves cached `.sh` installer scripts from +Git repositories through stable public paths. The primary use case is +bootstrapping installation scripts for projects through URLs such as +`/mcp/graylog/install.sh`, while keeping repository details and access tokens +managed from an admin interface. + +## Scope + +The first version includes: + +- A public endpoint that serves cached shell scripts for configured paths. +- An `/admin` interface with a login screen. +- Admin account storage in SQLite with hashed passwords. +- CRUD for script mappings. +- Optional per-mapping Git access token support for private repositories. +- Manual synchronization from Git into a local cache. +- Docker support for production deployment through Coolify. +- Docker Compose support for local development with the source directory mounted + into the PHP container. + +The first version does not include: + +- Multi-tenant access control. +- Scheduled background synchronization. +- Webhook-triggered synchronization. +- Multiple active application replicas sharing the same SQLite database. +- Public directory browsing or repository browsing. + +## Technology + +Use PHP 8.3 with a minimal Symfony application. Symfony provides routing, +forms, CSRF protection, password hashing, Doctrine integration, migrations, and +test support without needing to reimplement those concerns. + +Use SQLite by default through `DATABASE_URL`. This is enough for one deployed +instance and keeps Coolify deployment simple. The application should keep the +database configuration compatible with PostgreSQL so that a future deployment +can switch to PostgreSQL by changing `DATABASE_URL` and adding a database +service. + +Use Nginx in front of PHP-FPM. The production image copies application sources +into the image. The development Compose file mounts the local source tree into +the container so code changes can be tested without rebuilding. + +## Main Components + +### Public Script Serving + +The public controller receives all non-admin paths and resolves them against an +active mapping. A configured public path such as `mcp/graylog/install.sh` +matches the request path `/mcp/graylog/install.sh`. + +The public endpoint never contacts Git directly. It serves only the current +cached file for the mapping. If no active mapping exists, or if the mapping has +never synchronized successfully, the endpoint returns `404`. + +Served files should use a shell-friendly content type such as +`text/x-shellscript; charset=UTF-8`, with `Content-Disposition: inline`. + +### Admin Interface + +`/admin` displays a login screen when the user is not authenticated. Admin +users are stored in the application database and passwords are hashed with +Symfony's password hasher. + +After login, the admin can: + +- List mappings. +- Create a mapping. +- Edit a mapping. +- Delete a mapping. +- Trigger synchronization for one mapping. +- See the last synchronization status, last synchronization timestamp, and last + synchronization error. + +All admin forms use CSRF protection. + +### Mapping Model + +Each mapping stores: + +- Public path, for example `mcp/graylog/install.sh`. +- Git repository URL, for example + `https://forge.lclr.dev/AI/graylog-mcp.git`. +- Git ref, for example `main`. +- File path inside the repository, for example `install.sh`. +- Optional access token. +- Active flag. +- Last synchronization status. +- Last successful synchronization timestamp. +- Last synchronization error. +- Cached file path or cache key. + +Public paths and repository file paths are normalized before storage and before +use. They must be relative paths, must not contain `..`, and public paths must +end in `.sh`. + +### Git Synchronization + +Synchronization is manual from the admin interface. + +For each mapping, the synchronizer: + +1. Creates or reuses a per-mapping working directory under a cache volume. +2. Clones the repository when the local working directory does not exist. +3. Fetches updates when it already exists. +4. Checks out the configured ref. +5. Validates that the configured repository file path resolves inside the + checkout. +6. Validates that the target is a regular file. +7. Copies the file into a stable served cache location for the mapping. +8. Updates synchronization metadata in the database. + +If synchronization fails, the previous served cache remains in place. The +mapping records the error so the admin can diagnose the issue without breaking +already working installer URLs. + +For private HTTPS repositories, the synchronizer uses the configured access +token only during Git operations. The token is never exposed in public responses +or admin list views. Forms may allow replacing or clearing a token without +displaying the stored value. + +### Storage Layout + +The application uses two persistent locations: + +- Database volume: stores SQLite database files. +- Cache volume: stores Git working directories and served script copies. + +The application never serves files directly from arbitrary disk paths. Public +responses always go through the mapping lookup and cache resolver. + +## Docker And Deployment + +Production deployment uses Docker Compose with at least: + +- An app/PHP-FPM container built from the repository. +- An Nginx container using a checked-in Nginx config. +- Volumes for the SQLite database and Git/script cache. + +The production Dockerfile copies source files into the image and installs PHP +dependencies during the image build. + +Development uses a Compose override or a dedicated development Compose file +that mounts the local repository into the app container. This allows editing +Symfony files locally and testing immediately without rebuilding the image. + +## Security + +Admin authentication is required for every `/admin` route except login. + +Tokens are sensitive data. They must not be logged, displayed in list pages, or +returned by public endpoints. Error messages should identify which mapping +failed and why, without printing credentials or credential-bearing Git URLs. + +Path handling is strict: + +- Public paths are relative. +- Public paths cannot contain empty segments, `.` segments, or `..` segments. +- Public paths must end with `.sh`. +- Repository file paths are relative. +- Repository file paths cannot escape the checkout directory. +- Public serving cannot fall back to arbitrary files on disk. + +The admin interface uses CSRF protection for state-changing actions. + +## Error Handling + +Public endpoint behavior: + +- Unknown path: `404`. +- Known mapping without successful cache: `404`. +- Inactive mapping: `404`. +- Unexpected read failure: `500`, with details in server logs only. + +Admin synchronization behavior: + +- Git failures are stored on the mapping as the last synchronization error. +- The previous cached script remains available when present. +- The admin screen shows success or failure after synchronization. + +Production logs should identify the mapping, public path, repository host/path +when useful, and the operation that failed. Logs must not include access tokens. + +## Testing + +The implementation should include focused tests for: + +- Public path normalization and rejection of dangerous paths. +- Repository file path normalization and checkout escape prevention. +- Public endpoint returns cached script content for a valid synchronized + mapping. +- Public endpoint returns `404` for unknown, inactive, or unsynchronized + mappings. +- Admin login protects `/admin`. +- Mapping creation validates required fields and `.sh` public paths. +- Synchronization preserves the previous cached file when Git update fails. + +Git synchronization can be tested against local fixture repositories to avoid +network dependency. + +## Regression Risk + +The highest risk areas are path handling, token handling, and cache update +atomicity. The implementation should keep these responsibilities isolated in +small services so the public controller, admin forms, and synchronizer do not +duplicate security-sensitive logic. + +SQLite is acceptable for the first deployment because the app is intended to run +as a single instance. If the deployment later needs multiple active replicas, +PostgreSQL should replace SQLite before scaling horizontally.