mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
mok ux, autofill device and pf forms, docss for st, docs for start-sdk
This commit is contained in:
422
sdk/ARCHITECTURE.md
Normal file
422
sdk/ARCHITECTURE.md
Normal file
@@ -0,0 +1,422 @@
|
||||
# SDK Architecture
|
||||
|
||||
The Start SDK is split into two npm packages that form a layered architecture: **base** provides the foundational types, ABI contract, and effects interface; **package** builds on base to provide the developer-facing SDK facade.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ package/ (@start9labs/start-sdk) │
|
||||
│ Developer-facing facade, daemon management, health checks, │
|
||||
│ backup system, file helpers, triggers, subcontainers │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ base/ (@start9labs/start-sdk-base) │ │
|
||||
│ │ ABI, Effects, OS bindings, actions/input builders, │ │
|
||||
│ │ ExVer parser, interfaces, dependencies, S9pk, utils │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│ ▲
|
||||
│ Effects calls (RPC) │ Callbacks
|
||||
▼ │
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ StartOS Runtime (Rust supervisor) │
|
||||
│ Executes effects, manages containers, networking, storage │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
The SDK follows [Semantic Versioning](https://semver.org/) within the `0.4.0-beta.*` pre-release series. The SDK version tracks independently from the StartOS release versions.
|
||||
|
||||
## Base Package (`base/`)
|
||||
|
||||
The base package is a self-contained library of types, interfaces, and low-level builders. It has no dependency on the package layer and can be used independently when only type definitions or validation are needed.
|
||||
|
||||
### OS Bindings (`base/lib/osBindings/`)
|
||||
|
||||
~325 auto-generated TypeScript files defining every type exchanged between the SDK and the StartOS runtime. These cover the full surface area of the system: manifests, actions, health checks, service interfaces, bind parameters, dependency requirements, alerts, SSL, domains, SMTP, networking, images, and more.
|
||||
|
||||
All bindings are re-exported through `base/lib/osBindings/index.ts`.
|
||||
|
||||
Key types include:
|
||||
- `Manifest` — The full service package manifest as understood by the OS
|
||||
- `ActionMetadata` — Describes an action's name, description, visibility, and availability
|
||||
- `BindParams` — Port binding configuration (protocol, hostId, internal port)
|
||||
- `ServiceInterface` — A network endpoint exported to users
|
||||
- `DependencyRequirement` — Version range and health check requirements for a dependency
|
||||
- `SetHealth` — Health check result reporting
|
||||
- `HostnameInfo` / `Host` — Hostname and host metadata
|
||||
|
||||
### ABI and Core Types (`base/lib/types.ts`)
|
||||
|
||||
Defines the Application Binary Interface — the contract every service package must fulfill:
|
||||
|
||||
```typescript
|
||||
namespace ExpectedExports {
|
||||
main // Start the service daemon(s)
|
||||
init // Initialize on install/update/restore
|
||||
uninit // Clean up on uninstall/update/shutdown
|
||||
manifest // Service metadata
|
||||
actions // User-invocable operations
|
||||
createBackup // Export service data
|
||||
}
|
||||
```
|
||||
|
||||
Also defines foundational types used throughout the SDK:
|
||||
- `Daemon` / `DaemonReturned` — Running process handles with `wait()` and `term()`
|
||||
- `CommandType` — Shell string, argv array, or `UseEntrypoint`
|
||||
- `ServiceInterfaceType` — `'ui' | 'api' | 'p2p'`
|
||||
- `SmtpValue` — SMTP server configuration
|
||||
- `KnownError` — Structured user-facing errors
|
||||
- `DependsOn` — Package-to-health-check dependency mapping
|
||||
- `PathMaker`, `MaybePromise`, `DeepPartial`, `DeepReadonly` — Utility types
|
||||
|
||||
### Effects Interface (`base/lib/Effects.ts`)
|
||||
|
||||
The bridge between TypeScript service code and the StartOS runtime. Every runtime capability is accessed through an `Effects` object passed to lifecycle hooks.
|
||||
|
||||
Effects are organized by subsystem:
|
||||
|
||||
| Subsystem | Methods | Purpose |
|
||||
|-----------|---------|---------|
|
||||
| **Action** | `export`, `clear`, `getInput`, `run`, `createTask`, `clearTasks` | Register and invoke user actions |
|
||||
| **Control** | `restart`, `shutdown`, `getStatus`, `setMainStatus` | Service lifecycle control |
|
||||
| **Dependency** | `setDependencies`, `getDependencies`, `checkDependencies`, `mount`, `getInstalledPackages`, `getServiceManifest` | Inter-service dependency management |
|
||||
| **Health** | `setHealth` | Report health check results |
|
||||
| **Subcontainer** | `createFs`, `destroyFs` | Container filesystem management |
|
||||
| **Networking** | `bind`, `getServicePortForward`, `clearBindings`, `getHostInfo`, `getContainerIp`, `getOsIp`, `getOutboundGateway` | Port binding and network info |
|
||||
| **Interfaces** | `exportServiceInterface`, `getServiceInterface`, `listServiceInterfaces`, `clearServiceInterfaces` | Service endpoint management |
|
||||
| **Plugin** | `plugin.url.register`, `plugin.url.exportUrl`, `plugin.url.clearUrls` | Plugin system hooks |
|
||||
| **SSL** | `getSslCertificate`, `getSslKey` | TLS certificate management |
|
||||
| **System** | `getSystemSmtp`, `setDataVersion`, `getDataVersion` | System-wide configuration |
|
||||
|
||||
Effects also support reactive callbacks: many methods accept an optional `callback` parameter that the runtime invokes when the underlying value changes, enabling the reactive subscription patterns (`const()`, `watch()`, etc.).
|
||||
|
||||
### Action and Input System (`base/lib/actions/`)
|
||||
|
||||
#### Actions (`setupActions.ts`)
|
||||
|
||||
The `Action` class defines user-invocable operations. Two factory methods:
|
||||
- `Action.withInput(id, metadata, inputSpec, prefill, execute)` — Action with a validated form
|
||||
- `Action.withoutInput(id, metadata, execute)` — Action without user input
|
||||
|
||||
`Actions` is a typed map accumulated via `.addAction()` chaining.
|
||||
|
||||
#### Input Specification (`actions/input/`)
|
||||
|
||||
A builder-pattern system for declaring validated form inputs:
|
||||
|
||||
```
|
||||
inputSpec/
|
||||
├── builder/
|
||||
│ ├── inputSpec.ts — InputSpec.of() entry point
|
||||
│ ├── value.ts — Value class (individual form fields)
|
||||
│ ├── list.ts — List builder (arrays of values)
|
||||
│ └── variants.ts — Variants/Union builder (conditional fields)
|
||||
├── inputSpecTypes.ts — Type definitions for all field types
|
||||
└── inputSpecConstants.ts — Pre-built specs (SMTP, etc.)
|
||||
```
|
||||
|
||||
Supported field types via `Value`:
|
||||
- `text`, `textarea`, `number` — Text and numeric input
|
||||
- `toggle` — Boolean switch
|
||||
- `select`, `multiselect` — Single/multi-choice dropdown
|
||||
- `list` — Repeatable array of sub-values
|
||||
- `color`, `datetime` — Specialized pickers
|
||||
- `object` — Nested sub-form
|
||||
- `union` / `dynamicUnion` — Conditional fields based on a discriminator
|
||||
|
||||
### Dependencies (`base/lib/dependencies/`)
|
||||
|
||||
- `setupDependencies.ts` — Declare what the service depends on (package IDs, version ranges, health checks)
|
||||
- `dependencies.ts` — Runtime dependency checking via `checkDependencies()`
|
||||
|
||||
### Interfaces (`base/lib/interfaces/`)
|
||||
|
||||
Network interface declaration and port binding:
|
||||
|
||||
- `setupInterfaces.ts` — Top-level `setupServiceInterfaces()` function
|
||||
- `Host.ts` — `MultiHost` class for binding ports and exporting interfaces. A single MultiHost can bind a port and export multiple interfaces (e.g. a primary UI and admin UI on the same port with different paths)
|
||||
- `ServiceInterfaceBuilder.ts` — Builder for constructing `ServiceInterface` objects with name, type, description, scheme overrides, username, path, and query params
|
||||
- `setupExportedUrls.ts` — URL plugin support for exporting URLs to other services
|
||||
|
||||
### Initialization (`base/lib/inits/`)
|
||||
|
||||
- `setupInit.ts` — Compose init scripts that run on install, update, restore, or boot
|
||||
- `setupUninit.ts` — Compose uninit scripts that run on uninstall, update, or shutdown
|
||||
- `setupOnInit` / `setupOnUninit` — Register callbacks for specific init/uninit events
|
||||
|
||||
Init scripts receive a `kind` parameter (`'install' | 'update' | 'restore' | null`) so they can branch logic based on the initialization context.
|
||||
|
||||
### Extended Versioning (`base/lib/exver/`)
|
||||
|
||||
A PEG parser-based versioning system that extends semver:
|
||||
|
||||
- **`Version`** — Standard semantic version (`1.2.3-beta.1`)
|
||||
- **`ExtendedVersion` (ExVer)** — Adds an optional flavor prefix and a downstream version: `#flavor:upstream:downstream`
|
||||
- **`VersionRange`** — Boolean expressions over version comparisons (`>=1.0.0 && <2.0.0 || =3.0.0`)
|
||||
|
||||
The parser is generated from `exver.pegjs` via Peggy and emitted as `exver.ts`.
|
||||
|
||||
ExVer separates upstream project versions from StartOS wrapper versions, allowing the package maintainer's versioning to evolve independently from the upstream software.
|
||||
|
||||
### S9pk Format (`base/lib/s9pk/`)
|
||||
|
||||
Parser and verifier for `.s9pk` service package archives:
|
||||
|
||||
- `S9pk` class — Deserialize and inspect package contents
|
||||
- Merkle archive support for cryptographic verification of package integrity
|
||||
- Methods: `deserialize()`, `icon()`, `license()`, etc.
|
||||
|
||||
### Utilities (`base/lib/util/`)
|
||||
|
||||
~28 utility modules including:
|
||||
|
||||
**Reactive subscription wrappers** — Each wraps an Effects callback-based method into a consistent reactive API:
|
||||
- `Watchable` — Base class providing `const()`, `once()`, `watch()`, `onChange()`, `waitFor()`
|
||||
- `GetContainerIp`, `GetStatus`, `GetSystemSmtp`, `GetOutboundGateway`, `GetSslCertificate`, `GetHostInfo`, `GetServiceManifest` — Typed wrappers for specific Effects methods
|
||||
|
||||
**General utilities:**
|
||||
- `deepEqual` / `deepMerge` — Deep object comparison and merging
|
||||
- `patterns` — Hostname regex, port validators
|
||||
- `splitCommand` — Parse shell command strings into argv arrays
|
||||
- `Drop` — RAII-style cleanup utility
|
||||
- `graph` — Dependency graph utilities
|
||||
|
||||
## Package Layer (`package/`)
|
||||
|
||||
The package layer provides the developer-facing API. It re-exports everything from base and adds higher-level abstractions.
|
||||
|
||||
### StartSdk Facade (`package/lib/StartSdk.ts`)
|
||||
|
||||
The primary entry point for service developers. Constructed via a builder chain:
|
||||
|
||||
```typescript
|
||||
const sdk = StartSdk.of()
|
||||
.withManifest(manifest)
|
||||
.build(true)
|
||||
```
|
||||
|
||||
The `.build()` method returns an object containing the entire SDK surface area, organized by concern:
|
||||
|
||||
| Category | Members | Purpose |
|
||||
|----------|---------|---------|
|
||||
| **Manifest** | `manifest`, `volumes` | Access manifest data and volume paths |
|
||||
| **Actions** | `Action.withInput`, `Action.withoutInput`, `Actions`, `action.run`, `action.createTask`, `action.createOwnTask`, `action.clearTask` | Define and manage user actions |
|
||||
| **Daemons** | `Daemons.of`, `Daemon.of`, `setupMain` | Configure service processes |
|
||||
| **Health** | `healthCheck.checkPortListening`, `.checkWebUrl`, `.runHealthScript` | Built-in health checks |
|
||||
| **Interfaces** | `createInterface`, `MultiHost.of`, `setupInterfaces`, `serviceInterface.*` | Network endpoint management |
|
||||
| **Backups** | `setupBackups`, `Backups.ofVolumes`, `Backups.ofSyncs`, `Backups.withOptions` | Backup configuration |
|
||||
| **Dependencies** | `setupDependencies`, `checkDependencies` | Dependency declaration and verification |
|
||||
| **Init/Uninit** | `setupInit`, `setupUninit`, `setupOnInit`, `setupOnUninit` | Lifecycle hooks |
|
||||
| **Containers** | `SubContainer.of`, `SubContainer.withTemp`, `Mounts.of` | Container execution with mounts |
|
||||
| **Forms** | `InputSpec.of`, `Value`, `Variants`, `List` | Form input builders |
|
||||
| **Triggers** | `trigger.defaultTrigger`, `.cooldownTrigger`, `.changeOnFirstSuccess`, `.successFailure` | Health check polling strategies |
|
||||
| **Reactive** | `getContainerIp`, `getStatus`, `getSystemSmtp`, `getOutboundGateway`, `getSslCertificate`, `getServiceManifest` | Subscription-based data access |
|
||||
| **Plugins** | `plugin.url.register`, `plugin.url.exportUrl` | Plugin system (gated by manifest `plugins` field) |
|
||||
| **Effects** | `restart`, `shutdown`, `setHealth`, `mount`, `clearBindings`, ... | Direct effect wrappers |
|
||||
| **Utilities** | `nullIfEmpty`, `useEntrypoint`, `patterns`, `setDataVersion`, `getDataVersion` | Misc helpers |
|
||||
|
||||
### Daemon Management (`package/lib/mainFn/`)
|
||||
|
||||
The daemon subsystem manages long-running processes:
|
||||
|
||||
```
|
||||
mainFn/
|
||||
├── Daemons.ts — Multi-daemon topology builder
|
||||
├── Daemon.ts — Single daemon wrapper
|
||||
├── HealthDaemon.ts — Health check executor
|
||||
├── CommandController.ts — Command execution controller
|
||||
├── Mounts.ts — Volume/asset/dependency mount builder
|
||||
├── Oneshot.ts — One-time startup commands
|
||||
└── index.ts — setupMain() entry point
|
||||
```
|
||||
|
||||
**Daemons** is a builder that accumulates process definitions:
|
||||
```typescript
|
||||
sdk.Daemons.of(effects)
|
||||
.addDaemon('db', { /* command, ready probe, mounts */ })
|
||||
.addDaemon('app', { requires: ['db'], /* ... */ })
|
||||
.addHealthCheck('primary', { /* ... */ })
|
||||
```
|
||||
|
||||
Features:
|
||||
- Startup ordering via `requires` (dependency graph between daemons)
|
||||
- Ready probes (wait for a daemon to be ready before starting dependents)
|
||||
- Graceful shutdown with configurable signals and timeouts
|
||||
- One-shot commands that run before daemons start
|
||||
|
||||
**Mounts** declares what to attach to a container:
|
||||
```typescript
|
||||
sdk.Mounts.of()
|
||||
.mountVolume('main', '/data')
|
||||
.mountAssets('scripts', '/scripts')
|
||||
.mountDependency('bitcoind', 'main', '/bitcoin-data', { readonly: true })
|
||||
.mountBackup('/backup')
|
||||
```
|
||||
|
||||
### Health Checks (`package/lib/health/`)
|
||||
|
||||
```
|
||||
health/
|
||||
├── HealthCheck.ts — Periodic probe with startup grace period
|
||||
└── checkFns/
|
||||
├── checkPortListening.ts — TCP port connectivity check
|
||||
├── checkWebUrl.ts — HTTP(S) status code check
|
||||
└── runHealthScript.ts — Script exit code check
|
||||
```
|
||||
|
||||
Health checks are paired with **triggers** that control polling behavior:
|
||||
- `defaultTrigger` — Fixed interval (e.g. every 30s)
|
||||
- `cooldownTrigger` — Wait longer after failures
|
||||
- `changeOnFirstSuccess` — Rapid polling until first success, then slow down
|
||||
- `successFailure` — Different intervals for healthy vs unhealthy states
|
||||
|
||||
### Backup System (`package/lib/backup/`)
|
||||
|
||||
```
|
||||
backup/
|
||||
├── setupBackups.ts — Top-level setup function
|
||||
└── Backups.ts — Volume selection and rsync options
|
||||
```
|
||||
|
||||
Three builder patterns:
|
||||
- `Backups.ofVolumes('main', 'data')` — Back up entire volumes
|
||||
- `Backups.ofSyncs([{ dataPath, backupPath }])` — Custom sync pairs
|
||||
- `Backups.withOptions({ exclude: ['cache/'] })` — Rsync options
|
||||
|
||||
### File Helpers (`package/lib/util/fileHelper.ts`)
|
||||
|
||||
Type-safe configuration file management:
|
||||
|
||||
```typescript
|
||||
const configFile = FileHelper.yaml(effects, sdk.volumes.main.path('config.yml'), {
|
||||
port: 8080,
|
||||
debug: false,
|
||||
})
|
||||
|
||||
// Reactive reading
|
||||
const config = await configFile.read.const(effects)
|
||||
|
||||
// Partial merge
|
||||
await configFile.merge({ debug: true })
|
||||
|
||||
// Full write
|
||||
await configFile.write({ port: 9090, debug: true })
|
||||
```
|
||||
|
||||
Supported formats: JSON, YAML, TOML, INI, ENV, and custom parsers.
|
||||
|
||||
### Subcontainers (`package/lib/util/SubContainer.ts`)
|
||||
|
||||
Execute commands in isolated container environments:
|
||||
|
||||
```typescript
|
||||
// Long-lived subcontainer
|
||||
const container = await sdk.SubContainer.of(effects, { imageId: 'main' }, mounts, 'app')
|
||||
|
||||
// One-shot execution
|
||||
await sdk.SubContainer.withTemp(effects, { imageId: 'main' }, mounts, 'migrate', async (c) => {
|
||||
await c.exec(['run-migrations'])
|
||||
})
|
||||
```
|
||||
|
||||
### Manifest Building (`package/lib/manifest/`)
|
||||
|
||||
```typescript
|
||||
const manifest = setupManifest({
|
||||
id: 'my-service',
|
||||
title: 'My Service',
|
||||
license: 'MIT',
|
||||
description: { short: '...', long: '...' },
|
||||
images: { main: { source: { dockerTag: 'myimage:1.0' } } },
|
||||
volumes: { main: {} },
|
||||
dependencies: {},
|
||||
// ...
|
||||
})
|
||||
|
||||
export default buildManifest(manifest)
|
||||
```
|
||||
|
||||
`buildManifest()` finalizes the manifest with the current SDK version, OS version compatibility, and migration version ranges.
|
||||
|
||||
### Versioning (`package/lib/version/`)
|
||||
|
||||
Helpers for data version management during migrations:
|
||||
|
||||
```typescript
|
||||
await sdk.setDataVersion(effects, '1.2.0:0')
|
||||
const version = await sdk.getDataVersion(effects)
|
||||
```
|
||||
|
||||
Used in init scripts to track which migration version the service's data has been brought to.
|
||||
|
||||
### Internationalization (`package/lib/i18n/`)
|
||||
|
||||
```typescript
|
||||
const t = setupI18n({ en_US: enStrings, es_ES: esStrings })
|
||||
const greeting = t('hello', { name: 'World' }) // "Hello, World!" or "Hola, World!"
|
||||
```
|
||||
|
||||
Supports locale fallback and Intl-based formatting.
|
||||
|
||||
### Triggers (`package/lib/trigger/`)
|
||||
|
||||
Polling strategy functions that determine when health checks run:
|
||||
|
||||
```typescript
|
||||
sdk.trigger.defaultTrigger({ timeout: 30_000 })
|
||||
sdk.trigger.cooldownTrigger({ timeout: 30_000, cooldown: 60_000 })
|
||||
sdk.trigger.changeOnFirstSuccess({ first: 5_000, then: 30_000 })
|
||||
sdk.trigger.successFailure({ success: 60_000, failure: 10_000 })
|
||||
```
|
||||
|
||||
## Build Pipeline
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed build instructions, make targets, and development workflow.
|
||||
|
||||
At a high level: Peggy generates the ExVer parser, TypeScript compiles both packages in strict mode (base to `baseDist/`, package to `dist/`), hand-written `.js`/`.d.ts` pairs are copied into the output, and `node_modules` are bundled for self-contained distribution.
|
||||
|
||||
## Data Flow
|
||||
|
||||
A typical service package lifecycle:
|
||||
|
||||
```
|
||||
1. INSTALL / UPDATE / RESTORE
|
||||
├── init({ effects, kind })
|
||||
│ ├── Version migrations (if update)
|
||||
│ ├── setupDependencies()
|
||||
│ ├── setupInterfaces() → bind ports, export interfaces
|
||||
│ └── Actions registration → export actions to OS
|
||||
│
|
||||
2. MAIN
|
||||
│ setupMain() → Daemons.of(effects)
|
||||
│ ├── Oneshots run first
|
||||
│ ├── Daemons start in dependency order
|
||||
│ ├── Health checks begin polling
|
||||
│ └── Service runs until shutdown/restart
|
||||
│
|
||||
3. SHUTDOWN / UNINSTALL / UPDATE
|
||||
│ uninit({ effects, target })
|
||||
│ └── Version down-migrations (if needed)
|
||||
│
|
||||
4. BACKUP (user-triggered)
|
||||
createBackup({ effects })
|
||||
└── rsync volumes to backup location
|
||||
```
|
||||
|
||||
## Key Design Patterns
|
||||
|
||||
### Builder Pattern
|
||||
Most SDK APIs use immutable builder chains: `Daemons.of().addDaemon().addHealthCheck()`, `Mounts.of().mountVolume().mountAssets()`, `Actions.of().addAction()`. This provides type accumulation — each chained call narrows the type to reflect what has been configured.
|
||||
|
||||
### Effects as Capability System
|
||||
All runtime interactions go through the `Effects` object rather than direct system calls. This makes the runtime boundary explicit, enables the OS to mediate all side effects, and makes service code testable by providing mock effects.
|
||||
|
||||
### Reactive Subscriptions
|
||||
The `Watchable` base class provides a consistent API for values that can change over time:
|
||||
- `const(effects)` — Read once; if the value changes, triggers a retry of the enclosing context
|
||||
- `once()` — Read once without reactivity
|
||||
- `watch()` — Async generator yielding on each change
|
||||
- `onChange(callback)` — Invoke callback on each change
|
||||
- `waitFor(predicate)` — Block until a condition is met
|
||||
|
||||
### Type-safe Manifest Threading
|
||||
The manifest type flows through the entire SDK via generics. When you call `StartSdk.of().withManifest(manifest)`, the manifest's volume names, image IDs, dependency IDs, and plugin list become available as type constraints throughout all subsequent API calls. For example, `Mounts.of().mountVolume()` only accepts volume names declared in the manifest.
|
||||
109
sdk/CHANGELOG.md
Normal file
109
sdk/CHANGELOG.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Changelog
|
||||
|
||||
## 0.4.0-beta.59 — StartOS v0.4.0-alpha.20 (2026-03-06)
|
||||
|
||||
### Added
|
||||
|
||||
- Support for preferred external ports besides 443
|
||||
- Bridge filter kind on service interfaces
|
||||
|
||||
### Fixed
|
||||
|
||||
- Merge version ranges when adding existing package signer
|
||||
- Task fix for action task system
|
||||
|
||||
## 0.4.0-beta.56 — StartOS v0.4.0-alpha.19 (2026-02-02)
|
||||
|
||||
### Added
|
||||
|
||||
- `getOutboundGateway` effect and SDK wrapper
|
||||
- Improved service version migration and data version handling
|
||||
- `zod-deep-partial` integration with `partialValidator` on `InputSpec`
|
||||
- SMTP rework with improved provider variants and system SMTP spec
|
||||
|
||||
### Changed
|
||||
|
||||
- Migrated from `ts-matches` to `zod` across all TypeScript packages
|
||||
- Builder-style `InputSpec` API with prefill plumbing
|
||||
- Split `row_actions` into `remove_action` and `overflow_actions` for URL plugins
|
||||
|
||||
### Fixed
|
||||
|
||||
- Scoped public domain to single binding and return single port check
|
||||
- Preserved `z` namespace types for SDK consumers
|
||||
- `--arch` flag falls back to emulation when native image unavailable
|
||||
|
||||
## 0.4.0-beta.54 — StartOS v0.4.0-alpha.18 (2026-01-27)
|
||||
|
||||
### Added
|
||||
|
||||
- Device info RPC
|
||||
- Hardware acceleration and NVIDIA card support on nonfree images
|
||||
|
||||
### Changed
|
||||
|
||||
- Consolidated setup flow
|
||||
- Improved SDK abort handling and `InputSpec` filtering
|
||||
|
||||
## 0.4.0-beta.49 — StartOS v0.4.0-alpha.17 (2026-01-10)
|
||||
|
||||
### Added
|
||||
|
||||
- JSDoc comments on all consumer-facing APIs
|
||||
- StartTunnel random subnet support
|
||||
- Port 80 to 5443 tunnel mapping
|
||||
|
||||
### Fixed
|
||||
|
||||
- `EffectCreator` type corrections
|
||||
- Allow multiple equal signs in ENV `FileHelper` values
|
||||
- Miscellaneous alpha.16 follow-up fixes
|
||||
|
||||
## 0.4.0-beta.45 — StartOS v0.4.0-alpha.16 (2025-12-18)
|
||||
|
||||
### Added
|
||||
|
||||
- `map` and `eq` on `getServiceInterface` watcher
|
||||
- Flavor-aware version range handling
|
||||
|
||||
### Changed
|
||||
|
||||
- Refactored `StatusInfo` types
|
||||
- Improved shutdown ordering for daemons
|
||||
- Improved StartTunnel validation and garbage collection
|
||||
|
||||
## 0.4.0-beta.43 — StartOS v0.4.0-alpha.15 (2025-11-26)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Minor bugfixes for alpha.14
|
||||
|
||||
## 0.4.0-beta.42 — StartOS v0.4.0-alpha.14 (2025-11-20)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bugfixes for alpha.13
|
||||
|
||||
## 0.4.0-beta.41 — StartOS v0.4.0-alpha.13 (2025-11-15)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bugfixes for alpha.12
|
||||
|
||||
## 0.4.0-beta.40 — StartOS v0.4.0-alpha.12 (2025-11-07)
|
||||
|
||||
### Added
|
||||
|
||||
- StartTunnel integration
|
||||
- Configurable `textarea` rows in `InputSpec`
|
||||
|
||||
## 0.4.0-beta.39 — StartOS v0.4.0-alpha.11 (2025-09-24)
|
||||
|
||||
### Added
|
||||
|
||||
- Gateway limiting for StartTunnel
|
||||
- Improved copy UX around Tor SSL
|
||||
|
||||
### Changed
|
||||
|
||||
- SDK type updates and internal improvements
|
||||
@@ -6,3 +6,10 @@ TypeScript SDK for packaging services for StartOS (`@start9labs/start-sdk`).
|
||||
|
||||
- `base/` — Core types, ABI definitions, effects interface (`@start9labs/start-sdk-base`)
|
||||
- `package/` — Full SDK for package developers, re-exports base
|
||||
|
||||
## Releasing
|
||||
|
||||
When bumping the SDK version (in `package/package.json`), always update `CHANGELOG.md`:
|
||||
1. Add a new version heading at the top of the file
|
||||
2. Use the format: `## <sdk-version> — StartOS <os-version> (<date>)`
|
||||
3. Categorize entries under `### Added`, `### Changed`, `### Fixed`, or `### Removed`
|
||||
|
||||
209
sdk/CONTRIBUTING.md
Normal file
209
sdk/CONTRIBUTING.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Contributing to Start SDK
|
||||
|
||||
This guide covers developing the SDK itself. If you're building a service package *using* the SDK, see the [packaging docs](https://docs.start9.com/packaging).
|
||||
|
||||
For contributing to the broader StartOS project, see the root [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js v22+** (via [nvm](https://github.com/nvm-sh/nvm) recommended)
|
||||
- **npm** (ships with Node.js)
|
||||
- **GNU Make**
|
||||
|
||||
Verify your setup:
|
||||
|
||||
```bash
|
||||
node --version # v22.x or higher
|
||||
npm --version
|
||||
make --version
|
||||
```
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```
|
||||
sdk/
|
||||
├── base/ # @start9labs/start-sdk-base (core types, ABI, effects)
|
||||
│ ├── lib/ # TypeScript source
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.json
|
||||
│ └── jest.config.js
|
||||
├── package/ # @start9labs/start-sdk (full developer-facing SDK)
|
||||
│ ├── lib/ # TypeScript source
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.json
|
||||
│ └── jest.config.js
|
||||
├── baseDist/ # Build output for base (generated)
|
||||
├── dist/ # Build output for package (generated, published to npm)
|
||||
├── Makefile # Build orchestration
|
||||
├── README.md
|
||||
├── ARCHITECTURE.md
|
||||
└── CLAUDE.md
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
Install dependencies for both sub-packages:
|
||||
|
||||
```bash
|
||||
cd sdk
|
||||
make node_modules
|
||||
```
|
||||
|
||||
This runs `npm ci` in both `base/` and `package/`.
|
||||
|
||||
## Building
|
||||
|
||||
### Full Build
|
||||
|
||||
```bash
|
||||
make bundle
|
||||
```
|
||||
|
||||
This runs the complete pipeline: TypeScript compilation, hand-written pair copying, node_modules bundling, formatting, and tests. Outputs land in `baseDist/` (base) and `dist/` (package).
|
||||
|
||||
### Individual Targets
|
||||
|
||||
| Target | Description |
|
||||
|--------|-------------|
|
||||
| `make bundle` | Full build: compile + format + test |
|
||||
| `make baseDist` | Compile base package only |
|
||||
| `make dist` | Compile full package (depends on base) |
|
||||
| `make fmt` | Run Prettier on all `.ts` files |
|
||||
| `make check` | Type-check without emitting (both packages) |
|
||||
| `make clean` | Remove all build artifacts and node_modules |
|
||||
|
||||
### What the Build Does
|
||||
|
||||
1. **Peggy parser generation** — `base/lib/exver/exver.pegjs` is compiled to `exver.ts` (the ExVer version parser)
|
||||
2. **TypeScript compilation** — Strict mode, CommonJS output, declaration files
|
||||
- `base/` compiles to `baseDist/`
|
||||
- `package/` compiles to `dist/`
|
||||
3. **Hand-written pair copying** — `.js`/`.d.ts` files without a corresponding `.ts` source are copied into the output directories. These are manually maintained JavaScript files with hand-written type declarations.
|
||||
4. **Dependency bundling** — `node_modules/` is rsynced into both output directories so the published package is self-contained
|
||||
5. **Formatting** — Prettier formats all TypeScript source
|
||||
6. **Testing** — Jest runs both test suites
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run all tests (base + package)
|
||||
make test
|
||||
|
||||
# Run base tests only
|
||||
make base/test
|
||||
|
||||
# Run package tests only
|
||||
make package/test
|
||||
|
||||
# Run a specific test file directly
|
||||
cd base && npx jest --testPathPattern=exver
|
||||
cd package && npx jest --testPathPattern=host
|
||||
```
|
||||
|
||||
Tests use [Jest](https://jestjs.io/) with [ts-jest](https://kulshekhar.github.io/ts-jest/) for TypeScript support. Configuration is in each sub-package's `jest.config.js`.
|
||||
|
||||
### Test Files
|
||||
|
||||
Tests live alongside their subjects or in dedicated `test/` directories:
|
||||
|
||||
- `base/lib/test/` — ExVer parsing, input spec types, deep merge, graph utilities, type validation
|
||||
- `base/lib/util/inMs.test.ts` — Time conversion utility
|
||||
- `package/lib/test/` — Health checks, host binding, input spec builder
|
||||
|
||||
Test files use the `.test.ts` extension and are excluded from compilation via `tsconfig.json`.
|
||||
|
||||
## Formatting
|
||||
|
||||
```bash
|
||||
make fmt
|
||||
```
|
||||
|
||||
Runs Prettier with the project config (single quotes, no semicolons, trailing commas, 2-space indent). The Prettier config lives in each sub-package's `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
```
|
||||
|
||||
## Type Checking
|
||||
|
||||
To check types without building:
|
||||
|
||||
```bash
|
||||
make check
|
||||
```
|
||||
|
||||
Or directly per package:
|
||||
|
||||
```bash
|
||||
cd base && npm run check
|
||||
cd package && npm run check
|
||||
```
|
||||
|
||||
Both packages use strict TypeScript (`"strict": true`) targeting ES2021 with CommonJS module output.
|
||||
|
||||
## Local Development with a Service Package
|
||||
|
||||
To test SDK changes against a local service package without publishing to npm:
|
||||
|
||||
```bash
|
||||
# Build and create a local npm link
|
||||
make link
|
||||
|
||||
# In your service package directory
|
||||
npm link @start9labs/start-sdk
|
||||
```
|
||||
|
||||
This symlinks the built `dist/` into your global node_modules so your service package picks up local changes.
|
||||
|
||||
## Publishing
|
||||
|
||||
```bash
|
||||
make publish
|
||||
```
|
||||
|
||||
This builds the full bundle, then runs `npm publish --access=public --tag=latest` from `dist/`. The published package is `@start9labs/start-sdk`.
|
||||
|
||||
Only the `dist/` directory is published — it contains the compiled JavaScript, declaration files, bundled dependencies, and package metadata.
|
||||
|
||||
## Adding New Features
|
||||
|
||||
### Base vs Package
|
||||
|
||||
Decide where new code belongs:
|
||||
|
||||
- **`base/`** — Types, interfaces, ABI contracts, OS bindings, and low-level builders that have no dependency on the package layer. Code here should be usable independently.
|
||||
- **`package/`** — Developer-facing API, convenience wrappers, runtime helpers (daemons, health checks, backups, file helpers, subcontainers). Code here imports from base and adds higher-level abstractions.
|
||||
|
||||
### Key Conventions
|
||||
|
||||
- **Builder pattern** — Most APIs use immutable builder chains (`.addDaemon()`, `.mountVolume()`, `.addAction()`). Each call returns a new type that accumulates configuration.
|
||||
- **Effects boundary** — All runtime interactions go through the `Effects` interface. Never call system APIs directly.
|
||||
- **Manifest type threading** — The manifest type flows through generics so that volume names, image IDs, and dependency IDs are type-constrained.
|
||||
- **Re-export from package** — If you add a new export to base, also re-export it from `package/lib/index.ts` (or expose it through `StartSdk.build()`).
|
||||
|
||||
### Adding OS Bindings
|
||||
|
||||
Types in `base/lib/osBindings/` mirror Rust types from the StartOS core. When Rust types change, the corresponding TypeScript bindings need updating. These are re-exported through `base/lib/osBindings/index.ts`.
|
||||
|
||||
### Writing Tests
|
||||
|
||||
- Place test files next to the code they test, or in the `test/` directory
|
||||
- Use the `.test.ts` extension
|
||||
- Tests run in Node.js with ts-jest — no browser environment
|
||||
|
||||
## Commit Messages
|
||||
|
||||
Follow [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
feat(sdk): add WebSocket health check
|
||||
fix(sdk): correct ExVer range parsing for pre-release versions
|
||||
test(sdk): add coverage for MultiHost port binding
|
||||
```
|
||||
|
||||
See the root [CONTRIBUTING.md](../CONTRIBUTING.md#commit-messages) for the full convention.
|
||||
13
sdk/Makefile
13
sdk/Makefile
@@ -27,23 +27,24 @@ bundle: baseDist dist | test fmt
|
||||
base/lib/exver/exver.ts: base/node_modules base/lib/exver/exver.pegjs
|
||||
cd base && npm run peggy
|
||||
|
||||
baseDist: $(PACKAGE_TS_FILES) $(BASE_TS_FILES) base/package.json base/node_modules base/README.md base/LICENSE
|
||||
baseDist: $(PACKAGE_TS_FILES) $(BASE_TS_FILES) base/package.json base/node_modules base/README.md LICENSE
|
||||
(cd base && npm run tsc)
|
||||
rsync -ac --include='*.js' --include='*.d.ts' --include='*/' --exclude='*' base/lib/ baseDist/
|
||||
rsync -ac base/node_modules baseDist/
|
||||
cp base/package.json baseDist/package.json
|
||||
cp base/README.md baseDist/README.md
|
||||
cp base/LICENSE baseDist/LICENSE
|
||||
cp LICENSE baseDist/LICENSE
|
||||
touch baseDist
|
||||
|
||||
dist: $(PACKAGE_TS_FILES) $(BASE_TS_FILES) package/package.json package/.npmignore package/node_modules package/README.md package/LICENSE
|
||||
dist: $(PACKAGE_TS_FILES) $(BASE_TS_FILES) package/package.json package/.npmignore package/node_modules README.md LICENSE CHANGELOG.md
|
||||
(cd package && npm run tsc)
|
||||
rsync -ac --include='*.js' --include='*.d.ts' --include='*/' --exclude='*' base/lib/ dist/base/lib/
|
||||
rsync -ac package/node_modules dist/
|
||||
cp package/.npmignore dist/.npmignore
|
||||
cp package/package.json dist/package.json
|
||||
cp package/README.md dist/README.md
|
||||
cp package/LICENSE dist/LICENSE
|
||||
cp README.md dist/README.md
|
||||
cp LICENSE dist/LICENSE
|
||||
cp CHANGELOG.md dist/CHANGELOG.md
|
||||
touch dist
|
||||
|
||||
full-bundle: bundle
|
||||
@@ -71,7 +72,7 @@ base/node_modules: base/package-lock.json
|
||||
|
||||
node_modules: package/node_modules base/node_modules
|
||||
|
||||
publish: bundle package/package.json package/README.md package/LICENSE
|
||||
publish: bundle package/package.json README.md LICENSE CHANGELOG.md
|
||||
cd dist && npm publish --access=public --tag=latest
|
||||
|
||||
link: bundle
|
||||
|
||||
103
sdk/README.md
Normal file
103
sdk/README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Start SDK
|
||||
|
||||
The Start SDK (`@start9labs/start-sdk`) is a TypeScript framework for packaging services to run on [StartOS](https://github.com/Start9Labs/start-os). It provides a strongly-typed, builder-pattern API for defining every aspect of a service package: manifests, daemons, health checks, networking interfaces, actions, backups, dependencies, configuration, and more.
|
||||
|
||||
## Features
|
||||
|
||||
- **Type-safe manifest definitions** - Declare your service's identity, metadata, images, volumes, and dependencies with full TypeScript inference.
|
||||
- **Daemon management** - Define multi-process topologies with startup ordering, ready probes, and graceful shutdown via `Daemons.of().addDaemon()`.
|
||||
- **Health checks** - Built-in checks for TCP port listening, HTTP(S) endpoints, and custom scripts, with configurable polling strategies (fixed interval, cooldown, adaptive).
|
||||
- **Network interfaces** - Bind ports, export UI/API/P2P interfaces, and manage hostnames with MultiHost and ServiceInterfaceBuilder.
|
||||
- **User actions** - Create interactive operations with validated form inputs (text, select, toggle, list, union/variants, and more) that users can trigger from the StartOS UI.
|
||||
- **Backup and restore** - Rsync-based volume backups with exclude patterns and custom sync paths.
|
||||
- **Dependency management** - Declare inter-service dependencies with version ranges, health check requirements, and volume mounts.
|
||||
- **Configuration file helpers** - Read, write, and merge JSON, YAML, TOML, INI, and ENV files with type-safe `FileHelper`.
|
||||
- **Reactive subscriptions** - Watch for changes to container IPs, SSL certificates, SMTP config, service status, and more with `const()`, `once()`, `watch()`, `onChange()`, and `waitFor()` patterns.
|
||||
- **Extended versioning (ExVer)** - Flavor-aware semantic versioning with range matching, supporting independent upstream and downstream version tracking.
|
||||
- **Internationalization** - Built-in i18n support with locale fallback and parameter substitution.
|
||||
- **Container execution** - Run commands in subcontainers with volume mounts, environment variables, and entrypoint overrides.
|
||||
- **Plugin system** - Extensible plugin architecture (e.g. `url-v0` for URL management).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { setupManifest, buildManifest } from '@start9labs/start-sdk'
|
||||
|
||||
const manifest = setupManifest({
|
||||
id: 'my-service',
|
||||
title: 'My Service',
|
||||
license: 'MIT',
|
||||
// ...
|
||||
})
|
||||
|
||||
export default buildManifest(manifest)
|
||||
```
|
||||
|
||||
The primary entry point is the `StartSdk` facade:
|
||||
|
||||
```typescript
|
||||
import { StartSdk } from '@start9labs/start-sdk'
|
||||
import { manifest } from './manifest'
|
||||
|
||||
export const sdk = StartSdk.of().withManifest(manifest).build(true)
|
||||
```
|
||||
|
||||
From there, `sdk` exposes the full toolkit:
|
||||
|
||||
```typescript
|
||||
// Define daemons
|
||||
export const main = sdk.setupMain(async ({ effects }) =>
|
||||
sdk.Daemons.of(effects)
|
||||
.addDaemon('primary', { /* ... */ })
|
||||
)
|
||||
|
||||
// Define actions
|
||||
export const setName = sdk.Action.withInput('set-name', /* ... */)
|
||||
|
||||
// Define interfaces
|
||||
export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
|
||||
const multi = sdk.MultiHost.of(effects, 'web')
|
||||
const origin = await multi.bindPort(80, { protocol: 'http' })
|
||||
const ui = sdk.createInterface(effects, { name: 'Web UI', id: 'ui', /* ... */ })
|
||||
return [await origin.export([ui])]
|
||||
})
|
||||
|
||||
// Define backups
|
||||
export const { createBackup, restoreBackup } = sdk.setupBackups(
|
||||
async () => sdk.Backups.ofVolumes('main')
|
||||
)
|
||||
```
|
||||
|
||||
## Packages
|
||||
|
||||
| Package | npm | Description |
|
||||
|---------|-----|-------------|
|
||||
| `package/` | `@start9labs/start-sdk` | Full SDK for service developers |
|
||||
| `base/` | `@start9labs/start-sdk-base` | Core types, ABI definitions, and effects interface |
|
||||
|
||||
## Documentation
|
||||
|
||||
For comprehensive packaging guides, tutorials, and API reference:
|
||||
|
||||
**[docs.start9.com/packaging](https://docs.start9.com/packaging)**
|
||||
|
||||
The packaging docs cover:
|
||||
- Environment setup and prerequisites
|
||||
- Project structure and conventions
|
||||
- Manifest, main, interfaces, actions, and all other service modules
|
||||
- File models and configuration management
|
||||
- Versioning, migrations, and initialization
|
||||
- Dependencies and cross-service communication
|
||||
- Building and installing `.s9pk` packages
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for environment setup, building from source, testing, and development workflow.
|
||||
|
||||
## Architecture
|
||||
|
||||
See [ARCHITECTURE.md](ARCHITECTURE.md) for a detailed overview of the SDK's internal structure, module responsibilities, and data flow.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Start9 Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,18 +0,0 @@
|
||||
# Start SDK
|
||||
|
||||
## Config Conversion
|
||||
|
||||
- Copy the old config json (from the getConfig.ts)
|
||||
- Install the start-sdk with `npm i`
|
||||
- paste the config into makeOutput.ts::oldSpecToBuilder (second param)
|
||||
- Make the third param
|
||||
|
||||
```ts
|
||||
{
|
||||
StartSdk: "start-sdk/lib",
|
||||
}
|
||||
```
|
||||
|
||||
- run the script `npm run buildOutput` to make the output.ts
|
||||
- Copy this whole file into startos/procedures/config/spec.ts
|
||||
- Fix all the TODO
|
||||
Reference in New Issue
Block a user