68 lines
5.0 KiB
Markdown
68 lines
5.0 KiB
Markdown
# 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`
|
|
- `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)
|
|
|
|
## PR creation workflow
|
|
- If the current branch **exists on remote** (`git ls-remote --heads origin <branch>`), create a new PR from it via API: `POST /api/v1/repos/devlab/server-manager/pulls` with `head=<branch>`, `base=master`.
|
|
- If the current branch **does not exist on remote**, create a new one from master first: `git checkout -b <name> master`, push it, then create the PR.
|
|
- The API token for `git.devlab.lat` is embedded in the remote URL (`agent:<token>@git.devlab.lat`). Use `-u "agent:<token>"` for basic auth. The token is accessible via `git remote get-url origin`.
|
|
|
|
## 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
|