| name | add-service |
| description | How to add a new managed dev service to devctl — writing the ServiceDefinition, registering it in config/defaults.go, and wiring up the optional installer |
| license | MIT |
| compatibility | opencode |
| metadata | {"layer":"backend","concerns":"services, config"} |
Overview
Service definitions are static Go code in config/defaults.go. There is no services.yaml at runtime — it was removed. services.NewRegistry(config.DefaultServices()) converts the slice to an in-memory *Registry at startup. To add a new built-in service, add an entry to DefaultServices().
The Definition struct
services/definition.go:
type Definition struct {
ID string
Label string
Start string
Stop string
Restart string
Status string
StatusRegex string
Version string
VersionRegex string
Log string
Installable bool
Managed bool
ManagedCmd string
ManagedArgs string
}
| Field | Notes |
|---|
ID | Lowercase, unique, used as the API path param (e.g. "redis") |
Label | Human-readable display name (e.g. "Redis") |
Start/Stop/Restart | Shell commands run via sh -c (only for non-Managed services) |
Status | Command whose stdout is matched against StatusRegex |
StatusRegex | Named capture `(?Pactive |
Version | Command whose stdout is matched against VersionRegex |
VersionRegex | Named capture (?P<version>[\d.]+) |
Log | Absolute path to a log file (tailed for the log SSE endpoint) |
Installable | true if an Installer is registered for this ID via install.NewRegistry(...) |
Managed | true if devctl runs this as a supervised child process (not systemctl) |
ManagedCmd | Executable for supervised services (e.g. "php") |
ManagedArgs | Args string for supervised services (e.g. "artisan reverb:start ...") |
Example — standard systemctl service
In config/defaults.go, append to the slice returned by DefaultServices():
{
ID: "my-service",
Label: "My Service",
Start: "systemctl start my-service",
Stop: "systemctl stop my-service",
Restart: "systemctl restart my-service",
Status: "systemctl is-active my-service",
StatusRegex: `(?P<status>active|inactive|failed)`,
Version: "my-service --version",
VersionRegex: `(?P<version>[\d.]+)`,
Log: "/var/log/my-service/my-service.log",
Installable: true,
},
Example — supervised child process (Managed)
For services that run as child processes of devctl (like Laravel Reverb):
{
ID: "reverb",
Label: "Laravel Reverb",
Managed: true,
ManagedCmd: "php",
ManagedArgs: "artisan reverb:start --host=127.0.0.1 --port=7383",
Log: "",
Installable: true,
},
- Leave
Start, Stop, Restart, Status, StatusRegex empty — the supervisor handles these.
- Working dir is
$HOME/sites/<id> by convention (set automatically by the supervisor).
- The supervisor auto-starts installed managed services on devctl startup.
PHP-FPM services (auto-generated)
PHP-FPM services are not in defaults.go. They are auto-generated when a PHP version is installed, keyed as php8.3-fpm, php8.2-fpm, etc. See php/installer.go for that logic — don't add PHP-FPM entries manually.
Checklist when adding a new service