# 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`

## Database
- Migrations run manually: `mysql servermanager < database/migrations/NNN_name.sql`
- 5 migrations: `001_initial_schema`, `002_server_access`, `003_teams`, `004_soft_deletes`, `005_app_config_and_notifications`
- Users table has `role` ENUM: `super_admin`, `admin`, `operator`
- `api_token` column on users for Bearer token auth

## 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
