# ServerManager — AGENTS.md Repo-specific guidance for OpenCode sessions. Verified against code. ## Stack - PHP 8.3+, custom MVC (not Laravel), MySQL/MariaDB, phpseclib 3.0, phpdotenv - Autoload: PSR-4 `ServerManager\` → `src/`; `src/Helpers/functions.php` auto-loaded - Android companion app at `android/` (Jetpack Compose, Material3, Retrofit, Kotlinx Serialization) ## Entry point `public/index.php` → `App::boot()` → `App::run()` → `Router::dispatch()` ## Commands - `composer install` — install deps (no dev deps in `composer.json`) - `php bin/generate-key.php` — generate master key at `/etc/servermanager/master.key` - `php bin/monitor.php` — collect server metrics (runs via cron every minute) - `sudo bash install.sh` — full Ubuntu 22.04+/24.04+ install (needs sudo) - `mysql servermanager < database/migrations/001_initial_schema.sql` — db setup - `export ANDROID_HOME=/opt/android-sdk && cd android && ./gradlew assembleDebug` — build APK → `public/sysadmin.apk` ## Non-obvious conventions - Route params use `:id` syntax (`/servers/:id`), not `{id}`. Patterns: `:id` → `(\d+)`, `:slug` → `([a-zA-Z0-9\-]+)` - Router supports method spoofing via `_method` POST field - Session name is `SM_SESSION`; sessions stored server-side in `sessions` table - View engine: custom PHP templates (`extract()` + `include`), not Twig/Blade - SSH credentials encrypted with AES-256-CBC; master key at `/etc/servermanager/master.key` - `vendor/` is properly gitignored now (`/vendor/` in `.gitignore`, no leading space) - Android build outputs ignored: `android/.gradle/`, `android/build/`, `android/app/build/`, `android/local.properties` - Bump `versionCode` and `versionName` in `android/app/build.gradle.kts` on every code change to the Android app ## Backend gotchas - `View::json()` has `: never` return type — calls `exit` immediately. Do not put code after it expecting to run. - `AuditService::log(string $action, string $entityType, ?int $entityId, ?array $details = null, ?array $metadata = null)` — requires **3** positional args minimum. Calling with 2 args causes Error 500. - `RoleMiddleware` defaults to `'admin'` required level. Hierarchy: `super_admin` (3) > `admin` (2) > `operator` (1). - Role helper functions in views: `is_super_admin()`, `is_admin()`, `is_operator()` - CSRF: middleware checks `$_POST['_csrf_token']` then `$_SERVER['HTTP_X_CSRF_TOKEN']`. Token expiry 7200s default. - API routes have **no middleware** (auth is manual via `$this->authenticateRequest()` in controller). Web routes use `AuthMiddleware` / `RoleMiddleware` / `CSRFMiddleware`. - Admin group routes (`/admin/*`) already include `AuthMiddleware` + `RoleMiddleware`; individual routes add `CSRFMiddleware`. - Rate limiting on `POST /login` only (via `RateLimitMiddleware`) - Every credential access must be logged at `warning` level via `AuditService` - Notification read receipts tracked in `notification_reads` table: `(notification_id, user_id)` unique pair, with `read_at` timestamp ## Permission validation on server create/edit - When creating or editing a server, required SSH permissions can be specified and are validated via SSH before saving. - `PermissionValidator::validate(array $serverConfig, array $permissions)` connects to the remote server and runs check commands for each permission. - Permission types: `sudo` (checks passwordless sudo), `command` (checks `command -v`), `file_read` (`test -r`), `file_write` (`test -w`), `custom` (arbitrary command, exit code 0 = pass). - `ServerPermission` model handles CRUD for the `server_permissions` table. - Both web (`ServerController::store/update`) and API (`ApiController::serverAdd/serverUpdate`) flows include permission validation. - On validation failure, the web flow preserves form input via `$_SESSION['_form_input']` and redirects back with error details. ## Database - Migrations run manually: `mysql servermanager < database/migrations/NNN_name.sql` - 8 migrations: `001_initial_schema`, `002_server_access`, `003_teams`, `004_soft_deletes`, `005_app_config_and_notifications`, `006_agent_tokens`, `007_server_permission_checks`, `008_notification_reads` - Users table has `role` ENUM: `super_admin`, `admin`, `operator` - `api_token` column on users for Bearer token auth - `server_permissions` table stores required SSH permissions per server (FK to `servers.id` CASCADE) ## Testing / CI - **No tests, no CI, no linter, no formatter, no typechecker** exist in this repo ## Notable missing files - `.env.example` does NOT exist (referenced in `composer.json` post-install-cmd but missing from repo) - No Makefile, Dockerfile, or CI workflows