Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions projects/start-os/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ file tracks notable changes since the move to the monorepo.
image would be installed without complaint. It now verifies whenever
`CHECKSUM` is set.

- **A service whose startup routine throws now reports the failure in its own
logs, and StartOS names the failure for what it is.** The container runtime
handed the exception back to StartOS over its socket without also printing
it, so a service that failed to start went quiet in `Logs` at the moment it
needed to speak, while restarting every ten seconds. The exception now
appears in the service's own log next to the procedure that raised it, and
StartOS labels a failure that came from the runtime `Service Runtime Error`
rather than `Unknown Error`.

- **The login banner reports system status for every user, not just the first
one to log in.** It staged its database snapshot at a fixed path in `/tmp`,
which `pam_motd` created as root at login — so any subsequent non-root run
Expand Down
30 changes: 30 additions & 0 deletions projects/start-os/container-runtime/RPCSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,33 @@ The `execute` and `sandbox` methods route to procedures based on the `procedure`
| `/backup/create` | Create a backup |
| `/actions/{name}/getInput` | Get input spec for an action |
| `/actions/{name}/run` | Run an action with input |

## Errors

A failed call answers with a JSON-RPC error object:

```ts
{
code: number,
message: string, // fixed label for the code
data: {
details: string, // what went wrong
debug?: string, // stack trace, when there is one
},
}
```

`code` selects the error; `message` is that code's fixed label and carries no
per-call detail. StartOS reads `code` and renders its own translated label from
it, so anything written into `message` here reaches nobody — the text an
operator reads is `data.details`.

Codes are either a standard JSON-RPC code or a `start-core` `ErrorKind`
discriminant (`shared-libs/crates/start-core/src/error.rs`):

| code | message | raised when |
| -------- | ----------------------- | ---------------------------------------- |
| `-32602` | `invalid params` | the request carries no `method` |
| `-32601` | `Method not found` | the `method` is not one of the above |
| `38` | `Invalid Request` | the line is malformed, or dispatch fails |
| `59` | `Service Runtime Error` | a method or procedure threw |
59 changes: 35 additions & 24 deletions projects/start-os/container-runtime/src/Adapters/RpcListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,13 @@ const isDevBuild = (process.env.STARTOS_ENVIRONMENT ?? '')

const jsonParse = (x: string) => JSON.parse(x)

const handleRpc = (id: IdType, result: Promise<RpcResult>) =>
// codes are start-core ErrorKind discriminants; the OS localizes the message from the code
const errorKind = {
invalidRequest: { code: 38, message: 'Invalid Request' },
serviceRuntime: { code: 59, message: 'Service Runtime Error' },
} as const

const handleRpc = (id: IdType, method: string, result: Promise<RpcResult>) =>
result
.then(result => {
return {
Expand All @@ -129,15 +135,17 @@ const handleRpc = (id: IdType, result: Promise<RpcResult>) =>
(x as any).result = null
return x
})
.catch(error => ({
jsonrpc,
id,
error: {
code: 0,
message: typeof error,
data: { details: '' + error, debug: error?.stack },
},
}))
.catch(error => {
console.error(`${method} failed`, utils.asError(error))
return {
jsonrpc,
id,
error: {
...errorKind.serviceRuntime,
data: { details: '' + error, debug: error?.stack },
},
}
})

const hasIdSchema = z.object({ id: idType })
const hasId = (v: unknown): v is z.infer<typeof hasIdSchema> =>
Expand Down Expand Up @@ -181,12 +189,11 @@ export class RpcListener {
jsonrpc,
id,
error: {
message: typeof error,
...errorKind.invalidRequest,
data: {
details: error?.message ?? String(error),
debug: error?.stack,
},
code: 1,
},
})
const writeDataToSocket = (x: SocketResponse) => {
Expand Down Expand Up @@ -270,7 +277,7 @@ export class RpcListener {
const { input: inp, timeout, id: eventId } = params
const result = this.getResult(procedure, system, eventId, timeout, inp)

return handleRpc(id, result)
return handleRpc(id, 'execute', result)
}
case 'sandbox': {
const { id, params } = sandboxRunType.parse(input)
Expand All @@ -279,7 +286,7 @@ export class RpcListener {
const { input: inp, timeout, id: eventId } = params
const result = this.getResult(procedure, system, eventId, timeout, inp)

return handleRpc(id, result)
return handleRpc(id, 'sandbox', result)
}
case 'callback': {
const {
Expand All @@ -298,6 +305,7 @@ export class RpcListener {
})
return handleRpc(
id,
'start',
this.system.start(effects).then(result => ({ result })),
)
}
Expand All @@ -306,13 +314,15 @@ export class RpcListener {
this.callbacks?.removeChild('main')
return handleRpc(
id,
'stop',
this.system.stop().then(result => ({ result })),
)
}
case 'exit': {
const { id, params } = exitType.parse(input)
return handleRpc(
id,
'exit',
(async () => {
if (this._system) {
let target = null
Expand All @@ -337,6 +347,7 @@ export class RpcListener {
const { id, params } = initType.parse(input)
return handleRpc(
id,
'init',
(async () => {
if (!this._system) {
const system = await this.getDependencies.system()
Expand Down Expand Up @@ -364,6 +375,7 @@ export class RpcListener {
const { id, params } = evalType.parse(input)
return handleRpc(
id,
'eval',
(async () => {
const result = await new Function(
`return (async () => { return (${params.script}) }).call(this)`,
Expand Down Expand Up @@ -448,17 +460,16 @@ export class RpcListener {
}
}
})().then(ensureResultTypeShape, error => {
const errorSchema = z.object({
error: z.string(),
code: z.number().default(0),
})
const parsed = errorSchema.safeParse(error)
if (parsed.success) {
return {
error: { code: parsed.data.code, message: parsed.data.error },
}
const legacy = z.object({ error: z.string() }).safeParse(error)
return {
error: {
...errorKind.serviceRuntime,
data: {
details: legacy.success ? legacy.data.error : String(error),
debug: error?.stack,
},
},
}
return { error: { code: 0, message: String(error) } }
})
}
}
12 changes: 6 additions & 6 deletions shared-libs/crates/start-core/locales/i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -803,12 +803,12 @@ error.lan-port-conflict:
fr_FR: "Configuration de Port LAN Incompatible"
pl_PL: "Niekompatybilna Konfiguracja Portu LAN"

error.javascript:
en_US: "Javascript Engine Error"
de_DE: "JavaScript-Engine-Fehler"
es_ES: "Error del Motor JavaScript"
fr_FR: "Erreur du Moteur JavaScript"
pl_PL: "Błąd Silnika JavaScript"
error.service-runtime:
en_US: "Service Runtime Error"
de_DE: "Dienstlaufzeitfehler"
es_ES: "Error del Entorno de Ejecución del Servicio"
fr_FR: "Erreur du Runtime de Service"
pl_PL: "Błąd Środowiska Wykonawczego Usługi"

error.pem:
en_US: "PEM Encoding Error"
Expand Down
4 changes: 2 additions & 2 deletions shared-libs/crates/start-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub enum ErrorKind {
InvalidBackupTargetId = 56,
ProductKeyMismatch = 57,
LanPortConflict = 58,
Javascript = 59,
ServiceRuntime = 59,
Pem = 60,
TLSInit = 61,
Ascii = 62,
Expand Down Expand Up @@ -166,7 +166,7 @@ impl ErrorKind {
InvalidBackupTargetId => t!("error.invalid-backup-target-id"),
ProductKeyMismatch => t!("error.product-key-mismatch"),
LanPortConflict => t!("error.lan-port-conflict"),
Javascript => t!("error.javascript"),
ServiceRuntime => t!("error.service-runtime"),
Pem => t!("error.pem"),
TLSInit => t!("error.tls-init"),
Ascii => t!("error.ascii"),
Expand Down
4 changes: 2 additions & 2 deletions shared-libs/crates/start-core/src/s9pk/v2/pack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ pub async fn pack(ctx: CliContext, params: PackParams) -> Result<(), Error> {
"console.log(JSON.stringify(require('{}/index.js').manifest))",
js_dir.display()
))
.invoke(ErrorKind::Javascript)
.invoke(ErrorKind::ServiceRuntime)
.await?
.into();
files.insert(
Expand Down Expand Up @@ -893,7 +893,7 @@ pub async fn list_ingredients(_: CliContext, params: PackParams) -> Result<Vec<P
"console.log(JSON.stringify(require('{}').manifest))",
js_path.display()
))
.invoke(ErrorKind::Javascript)
.invoke(ErrorKind::ServiceRuntime)
.await?,
)
.with_kind(ErrorKind::Deserialization)
Expand Down