Skip to content

Commit 350e328

Browse files
author
yosebyte
authored
Refactor master package for improved clarity and maintainability
- Enhanced documentation throughout the master package, including function descriptions and struct field comments for better understanding. - Updated `startPeriodicTasks` to use `reloadInterval` for maintenance tasks, ensuring state backups and instance restarts are performed at appropriate intervals. - Introduced `saveState` and `saveStateAsync` methods for better state management, allowing for non-blocking state persistence. - Improved instance handling in REST API, including better validation and error handling in `handleInstances`, `handlePatchInstance`, and `handlePutInstance`. - Added support for CORS headers in API responses to facilitate cross-origin requests from local clients. - Enhanced logging and error handling across various methods to improve observability and debugging capabilities. - Updated `linuxSysInfo` to collect lightweight metrics directly from `/proc`, ensuring compatibility across platforms. - Improved instance lifecycle management, ensuring that state transitions are handled safely and consistently.
1 parent 0138706 commit 350e328

13 files changed

Lines changed: 708 additions & 257 deletions

File tree

README.md

Lines changed: 60 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,40 @@
1-
<p align="center">
2-
<img src="assets/openctrl.png" alt="OpenCtrl banner" width="50%">
3-
</p>
4-
5-
<p align="center">
6-
<a href="docs/master.md"><img alt="API v2" src="https://img.shields.io/badge/API-v2-14b8a6?style=flat-square"></a>
7-
<a href="docs/master.md"><img alt="Events SSE" src="https://img.shields.io/badge/events-SSE-f59e0b?style=flat-square"></a>
8-
<img alt="Go 1.26.4" src="https://img.shields.io/badge/Go-1.26.4-00ADD8?style=flat-square&amp;logo=go&amp;logoColor=white">
9-
<a href="LICENSE"><img alt="License BSD 3-Clause" src="https://img.shields.io/badge/license-BSD--3--Clause-0f172a?style=flat-square"></a>
10-
</p>
11-
12-
OpenCtrl is a focused control-plane master for supervising URL-defined
13-
instances. It stores instance definitions, launches a compatible runtime binary
14-
for each managed instance, exposes a versioned REST API, and streams live
15-
lifecycle, log, and metric updates over Server-Sent Events.
16-
17-
It is intentionally narrow: the master owns orchestration, persistence,
18-
authentication, and observability; runtime binaries own protocol-specific work.
19-
20-
## Contents
21-
22-
- [Highlights](#highlights)
23-
- [Architecture](#architecture)
24-
- [Quick Start](#quick-start)
25-
- [Master URL](#master-url)
26-
- [API Surface](#api-surface)
27-
- [Runtime Contract](#runtime-contract)
28-
- [State and Security](#state-and-security)
29-
- [Release](#release)
30-
- [Documentation](#documentation)
31-
32-
## Highlights
33-
34-
| Capability | What it provides |
35-
| --- | --- |
36-
| URL-defined instances | Every managed workload is represented by one URL and one generated instance ID. |
37-
| Process supervision | The master starts, stops, restarts, and deletes child processes through a stable runtime contract. |
38-
| Versioned control API | REST endpoints are served under `/api/v2` or a custom versioned prefix. |
39-
| Live event stream | SSE publishes initial state, lifecycle changes, child logs, metric checkpoints, deletions, and shutdown events. |
40-
| Durable local state | Instance definitions and the API key are persisted beside the executable with a backup file. |
41-
| Runtime neutrality | The master does not special-case child protocols; compatible binaries validate and execute their own URLs. |
42-
| TLS-ready operation | HTTP, self-signed TLS, and file-backed TLS certificate modes are selected from the master URL. |
43-
44-
## Architecture
45-
46-
```mermaid
47-
flowchart LR
48-
client["API clients"]
49-
events["SSE subscribers"]
50-
master["OpenCtrl master"]
51-
state["gob state"]
52-
runtime["runtime binary"]
53-
instA["instance URL"]
54-
instB["instance URL"]
55-
56-
client -->|"REST + X-API-Key"| master
57-
master -->|"event: instance"| events
58-
master <-->|"load / save"| state
59-
master -->|"spawn <runtime> <url>"| runtime
60-
runtime --> instA
61-
runtime --> instB
62-
```
1+
# OpenCtrl
2+
3+
OpenCtrl is an advanced control-plane master for URL-defined runtime instances.
634

64-
The master process provides the control plane. Child processes stay in the
65-
foreground, emit logs and optional checkpoints, and exit when the master stops
66-
them.
5+
It stores instance definitions, supervises child processes, exposes a versioned
6+
HTTP API, and streams lifecycle, log, and metric updates over Server-Sent
7+
Events. The master stays generic: orchestration belongs here; protocol behavior
8+
belongs to the runtime binary.
679

68-
## Quick Start
10+
## Design
6911

70-
Build the master:
12+
- URL-first instance configuration
13+
- Runtime-neutral process supervision
14+
- Versioned REST API under `/api/v2`
15+
- Server-Sent Events for live state
16+
- Local durable state with backup
17+
- HTTP, self-signed TLS, or certificate-backed TLS
18+
19+
## Build
7120

7221
```sh
7322
go build -trimpath -o ./bin/openctrl ./cmd/openctrl
7423
```
7524

76-
Start a local control plane:
25+
## Run
7726

7827
```sh
7928
./bin/openctrl 'master://127.0.0.1:8080'
8029
```
8130

82-
The master prints an API key on startup:
31+
On first start, OpenCtrl prints an API key:
8332

8433
```text
8534
Master.run: API key created: <api-key>
8635
```
8736

88-
Use the key with the versioned API:
37+
Use it with the API:
8938

9039
```sh
9140
BASE='http://127.0.0.1:8080/api/v2'
@@ -95,7 +44,8 @@ curl -H "X-API-Key: ${API_KEY}" "${BASE}/info"
9544
curl -H "X-API-Key: ${API_KEY}" "${BASE}/instances"
9645
```
9746

98-
Run managed instances through a compatible runtime binary:
47+
To supervise non-`master` instance schemes, point the master at a compatible
48+
runtime binary:
9949

10050
```sh
10151
./bin/openctrl 'master://127.0.0.1:8080?bin=/opt/openctrl/runtime'
@@ -116,109 +66,79 @@ curl -X POST "${BASE}/instances" \
11666
master://<host>:<port>[/prefix][?<query>]
11767
```
11868

119-
The host and port become the listen address. The optional path becomes the API
120-
prefix, and the API version is appended automatically.
69+
The host and port become the listen address. The path becomes the API prefix,
70+
with the API version appended automatically.
12171

122-
| Master URL | API base |
72+
| URL | API base |
12373
| --- | --- |
12474
| `master://127.0.0.1:8080` | `http://127.0.0.1:8080/api/v2` |
12575
| `master://127.0.0.1:8080/control` | `http://127.0.0.1:8080/control/v2` |
12676
| `master://127.0.0.1:8443?tls=1` | `https://127.0.0.1:8443/api/v2` |
12777

128-
| Parameter | Values | Default | Purpose |
129-
| --- | --- | --- | --- |
130-
| `tls` | `0`, `1`, `2` | `0` | Select HTTP, self-signed TLS, or certificate-backed TLS. |
131-
| `crt` | file path | empty | Certificate file used with `tls=2`. |
132-
| `key` | file path | empty | Private key file used with `tls=2`. |
133-
| `bin` | file path | current executable | Runtime binary used to launch managed instances. |
78+
| Query | Description |
79+
| --- | --- |
80+
| `tls=0` | Plain HTTP. |
81+
| `tls=1` | Self-signed TLS. |
82+
| `tls=2&crt=<path>&key=<path>` | Certificate-backed TLS. |
83+
| `bin=<path>` | Runtime binary used for managed instances. |
13484

135-
## API Surface
85+
## API
13686

137-
All protected requests must include:
87+
All protected requests require:
13888

13989
```text
14090
X-API-Key: <api-key>
14191
```
14292

143-
Core endpoints:
144-
145-
| Method | Path | Description |
93+
| Method | Path | Purpose |
14694
| --- | --- | --- |
147-
| `GET` | `/info` | Return master identity, runtime configuration, uptime, and system metrics. |
95+
| `GET` | `/info` | Read master identity and runtime information. |
14896
| `POST` | `/info` | Set the master alias. |
149-
| `GET` | `/instances` | Return all instances, including the internal API-key instance. |
150-
| `POST` | `/instances` | Create and asynchronously start an instance. |
151-
| `GET` | `/instances/{id}` | Return one instance. |
152-
| `PATCH` | `/instances/{id}` | Update metadata, restart policy, or lifecycle action. |
153-
| `PUT` | `/instances/{id}` | Replace the instance URL and restart the instance. |
154-
| `DELETE` | `/instances/{id}` | Stop and delete the instance. |
155-
| `GET` | `/events` | Open the Server-Sent Events stream. |
156-
| `GET` | `/tcping?target=host:port` | Test TCP reachability from the master process. |
157-
158-
Lifecycle actions accepted by `PATCH /instances/{id}`:
159-
160-
| Action | Effect |
161-
| --- | --- |
162-
| `start` | Start a stopped child process. |
163-
| `stop` | Stop an active child process. |
164-
| `restart` | Stop and start the child process. |
165-
| `reset` | Reset runtime byte counters. |
97+
| `GET` | `/instances` | List instances. |
98+
| `POST` | `/instances` | Create and start an instance. |
99+
| `GET` | `/instances/{id}` | Read one instance. |
100+
| `PATCH` | `/instances/{id}` | Update metadata, policy, or lifecycle action. |
101+
| `PUT` | `/instances/{id}` | Replace the instance URL. |
102+
| `DELETE` | `/instances/{id}` | Stop and delete an instance. |
103+
| `GET` | `/events` | Subscribe to instance events. |
104+
| `GET` | `/tcping?target=host:port` | Test TCP reachability. |
105+
106+
`PATCH /instances/{id}` accepts `start`, `stop`, `restart`, and `reset`.
166107

167-
SSE frames use the event name `instance`:
108+
SSE messages use the event name `instance`:
168109

169110
```text
170111
event: instance
171112
data: {"type":"update","time":"2026-06-08T12:00:00Z","instance":{...},"logs":""}
172113
```
173114

174-
Event payload types are `initial`, `create`, `update`, `delete`, `log`, and
115+
Event types are `initial`, `create`, `update`, `delete`, `log`, and
175116
`shutdown`.
176117

177118
## Runtime Contract
178119

179-
OpenCtrl supervises runtime binaries with a small process contract:
120+
OpenCtrl launches managed instances as:
180121

181122
```text
182123
<runtime-binary> <instance-url>
183124
```
184125

185-
For non-`master` instance schemes, point `bin` at a binary that understands
186-
those schemes. The master treats child URLs as opaque runtime configuration.
187-
188-
Runtime binaries MUST:
189-
190-
- accept the full instance URL as the first positional argument;
191-
- reject invalid configuration before starting long-running work;
192-
- stay in the foreground while the instance is active;
193-
- handle termination and exit within the master's 5-second grace period when
194-
possible;
195-
- return a non-zero exit code for failures that should place the instance in
196-
`error`;
197-
- avoid printing secrets from URLs, headers, keys, or tokens.
198-
199-
Runtime binaries SHOULD:
126+
Runtime binaries must accept the full instance URL as the first argument, stay
127+
in the foreground while active, reject invalid configuration before long-running
128+
work starts, handle termination promptly, and return a non-zero exit code for
129+
failures that should move the instance to `error`.
200130

201-
- emit newline-delimited logs to stdout or stderr;
202-
- emit checkpoints after the service is ready when metrics are available;
203-
- keep byte counters monotonic for the life of the process.
204-
205-
Runtime binaries MAY:
206-
207-
- define their own URL schemes and query parameters;
208-
- omit checkpoints when metrics are not meaningful;
209-
- print ordinary operational logs that should be visible to API clients.
210-
211-
Checkpoint format:
131+
Runtime binaries should write newline-delimited logs to stdout or stderr. They
132+
may emit checkpoints for metrics:
212133

213134
```text
214135
CHECK_POINT|MODE=<n>|PING=<n>ms|POOL=<n>|TCPS=<n>|UDPS=<n>|TCPRX=<bytes>|TCPTX=<bytes>|UDPRX=<bytes>|UDPTX=<bytes>
215136
```
216137

217-
Non-checkpoint child output is forwarded to stdout and published as `log`
218-
events. If a line contains the substring `ERROR`, the instance is marked
219-
`error`.
138+
Non-checkpoint output is forwarded to stdout and published as `log` events. A
139+
line containing `ERROR` marks the instance as `error`.
220140

221-
## State and Security
141+
## State
222142

223143
State is stored beside the executable:
224144

@@ -227,37 +147,23 @@ State is stored beside the executable:
227147
<executable-directory>/gob/openctrl.gob.backup
228148
```
229149

230-
Operational notes:
231-
232-
- protect the API key printed by the master;
233-
- protect the state directory because `openctrl.gob` stores the API key;
234-
- use `tls=2` with a trusted certificate outside local-only deployments;
235-
- use a trusted `bin` path because managed instances execute that binary;
236-
- treat instance URLs as secrets when they contain credentials or tokens;
237-
- account for permissive CORS headers when exposing the API across origins.
150+
Protect the API key, state directory, runtime binary path, and instance URLs.
151+
Use `tls=2` for networked deployments.
238152

239153
## Release
240154

241-
Build a local binary:
242-
243-
```sh
244-
go build -trimpath -o ./bin/openctrl ./cmd/openctrl
245-
```
246-
247-
Embed a version string:
248-
249155
```sh
250156
go build -trimpath \
251157
-ldflags="-s -w -X main.version=$VERSION" \
252158
-o ./bin/openctrl ./cmd/openctrl
253159
```
254160

255-
Tagged releases are built by GoReleaser for Darwin, FreeBSD, Linux, and
161+
Tagged releases are built with GoReleaser for Darwin, FreeBSD, Linux, and
256162
Windows on `amd64` and `arm64`.
257163

258164
## Documentation
259165

260-
- [Master API and runtime contract](docs/master.md)
166+
See [docs/master.md](docs/master.md) for the full API and runtime contract.
261167

262168
## License
263169

assets/openctrl.png

-1.1 MB
Binary file not shown.

cmd/openctrl/main.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// Command openctrl starts an OpenCtrl core from a URL-shaped configuration.
2+
//
3+
// The binary uses the URL scheme as the core selector. Today only master:// is
4+
// handled here; child protocol URLs are passed to managed child binaries by the
5+
// master package and are not parsed by the CLI entrypoint.
16
package main
27

38
import (
@@ -9,6 +14,10 @@ import (
914
"openctrl/v2/internal/master"
1015
)
1116

17+
// version is replaced by release builds and remains "dev" for local builds.
18+
//
19+
// Keep this variable package-level so linker flags can override it without
20+
// changing code.
1221
var version = "dev"
1322

1423
func main() {
@@ -17,6 +26,11 @@ func main() {
1726
}
1827
}
1928

29+
// start validates the CLI contract, parses the configuration URL, and runs the
30+
// selected core until it exits.
31+
//
32+
// The function is kept separate from main so tests can exercise argument
33+
// handling without terminating the process through os.Exit.
2034
func start(args []string) error {
2135
if len(args) < 2 {
2236
return fmt.Errorf("Main.start: missing configuration URL argument")
@@ -45,6 +59,11 @@ func start(args []string) error {
4559
return nil
4660
}
4761

62+
// exit prints the final runtime envelope before terminating with a non-zero
63+
// status code.
64+
//
65+
// Startup failures are intentionally one-line and machine-readable enough for
66+
// service managers while still being readable in a terminal.
4867
func exit(err error) {
4968
errMsg := "none"
5069
if err != nil {
@@ -57,6 +76,11 @@ func exit(err error) {
5776
os.Exit(1)
5877
}
5978

79+
// createCore maps the configuration scheme to the matching OpenCtrl core.
80+
//
81+
// This is the top-level dispatch boundary. Adding another core should happen
82+
// here, while adding another managed child protocol should generally not; child
83+
// protocols belong behind the master binary launch contract.
6084
func createCore(parsedURL *url.URL) (interface{ Run() }, error) {
6185
switch parsedURL.Scheme {
6286
case "master":

0 commit comments

Comments
 (0)