Merge NM-App-302-run-as-service into dev

This commit is contained in:
TrochtaOndrej
2026-03-20 01:04:35 +01:00
@@ -0,0 +1,91 @@
# ServiceLicence — Systemd Integration with AITM Dashboard
## What Was Done
Registered the `ServiceLicence` .NET license server as a systemd user service and surfaced it in the AITM dashboard (Test/Services tab) with start/stop/status controls.
## Files Changed
### Outside this repo — AITM project (`/home/zuf/dev/code/AITM/`)
| File | Change |
|------|--------|
| `src/shared-types.ts` | Added `managedBy?: string`, `systemdUnit?: string` to `TestAppConfig`; `rootOverride?: string` to `TestProjectConfig` |
| `src/types.ts` | Added `systemdUnit?: string` field; allowed `process: null` in `TestProcess` |
| `src/test-environment/TestEnvironmentService.ts` | Systemd-aware branches in `startTestApp`, `stopTestApp`, `getTestStatus`; `rootOverride` support in `testProjectDir()` |
| `src/socket/TestSocketHandlers.ts` | Skip dotnet build for systemd-managed apps in `test:build-and-start` and `test:start-all` |
| `config.json` | Added `service-licence` project entry with `rootOverride` and `managedBy: "systemd"` |
### System file
| File | Change |
|------|--------|
| `~/.config/systemd/user/servicelicence.service` | New systemd user unit for ServiceLicence; `NODE_ENV=development`, `DATABASE_URL` set; `Restart=on-failure` |
## Key Implementation Details
### Systemd Unit (`servicelicence.service`)
```ini
[Unit]
Description=ServiceLicence License Server
After=network.target
[Service]
WorkingDirectory=<path to LicenseServer project>
ExecStart=dotnet run --project <path>
Environment=NODE_ENV=development
Environment=DATABASE_URL=<connection string>
Restart=on-failure
[Install]
WantedBy=default.target
```
`NODE_ENV=development` was required (not `production`) so that the dev-secret default for `JWT_SECRET` is picked up — production mode requires an explicit `JWT_SECRET` env var.
### AITM Config Entry
```json
{
"id": "service-licence",
"name": "ServiceLicence",
"managedBy": "systemd",
"systemdUnit": "servicelicence",
"rootOverride": "/absolute/path/to/LicenseServer"
}
```
`rootOverride` is acceptable in `config.json` (config file, not source code). `managedBy: "systemd"` gates the new code branches.
### Backend Systemd-Aware Logic (`TestEnvironmentService.ts`)
Three functions were extended with a systemd branch:
- **`startTestApp(id)`** — runs `systemctl --user start <unit>` instead of spawning a child process
- **`stopTestApp(id)`** — runs `systemctl --user stop <unit>`
- **`getTestStatus(id)`** — parses `systemctl --user is-active <unit>` → maps `active``running`, anything else → `stopped`; survives AITM restarts (no in-memory process reference needed)
### Build Skip (`TestSocketHandlers.ts`)
`test:build-and-start` and `test:start-all` handlers skip `dotnet build` for entries with `managedBy: "systemd"` — the service manages its own lifecycle.
## Usage
```bash
# Check status
systemctl --user status servicelicence
# Restart
systemctl --user restart servicelicence
# Via AITM API
curl -s http://localhost:3333/api/test/status | jq '.[] | select(.id == "service-licence")'
```
## Acceptance Criteria Met
- `systemctl --user status servicelicence``active (running)`
- `curl -s http://localhost:3333/api/test/status | jq .` includes `service-licence` with valid status
- Start/Stop buttons in AITM Test tab work for ServiceLicence
- `npm run check:frontend` passes