Skip to content

Start scripts that run, log and time out; the message bus socket leaves /tmp - #2

Merged
inkly merged 4 commits into
mainfrom
feat/v0.4.27
Sep 22, 2026
Merged

inkly merged 4 commits into
mainfrom
feat/v0.4.27

Conversation

@inkly

@inkly inkly commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator
  • ExecuteScripts read the shebang after closing the file (every script ran with /bin/sh), threw each script's output away, stopped at the first failure and had no time limit, so a hung script held the core's start until systemd restarted it in a loop. Scripts now run with their shebang (read like the kernel does), through os/exec, for at most 60 seconds (the whole process group is killed on Linux), each run is logged with its output, and a failure does not stop the next ones.
  • The message bus socket moves from /tmp, where any local user could squat the path, to the runtime path: MessageBusSocketPath(runtimePath). PublishEventInSocket takes the runtime path (breaking; its only caller is AppManagement).
  • InternalAuthorization(runtimePath) gives the internal credential for websocket handshakes.

… logs

ExecuteScripts read the shebang after closing the file, so every script ran with /bin/sh, and it passed the whole shebang as the program name, so '#!/usr/bin/env bash' could not have worked either. It threw the output away, stopped at the first failing script and had no time limit, so a hung script held the start of CasaOS until systemd killed it, again at every restart.

Now each regular file of the directory runs in name order with the program and optional argument of its shebang line, for at most ScriptTimeout (60 seconds). On Linux the script gets its own process group and a timeout kills the whole group; elsewhere only the script's process is killed and WaitDelay stops waiting for pipes its children keep open. Each run is logged with its name, interpreter, exit code, duration and the last 4 KB of its output, at Info or Error. A failure no longer stops the next scripts; the failures come back joined. A missing directory is logged and is not an error.
PublishEventInSocket posted to /tmp/message-bus.sock. /tmp is world-writable, so any local user could create that path first and keep the message bus from listening, or answer in its place. The socket now lives in the runtime path, which only root can write: MessageBusSocketPath(runtimePath) returns it, and PublishEventInSocket takes the runtime path as its second argument. The message bus must listen on the same path.

The dial and the request now follow the caller's context, and keep-alive is off: each call builds its own transport, and the idle connection it kept was never reused nor closed.
A websocket handshake from x/net/websocket takes its headers from Config.Header, where a request editor does not fit. InternalAuthorization(runtimePath) returns "Internal <secret>" when the secret file is readable and an empty string otherwise, so such a caller can set the header itself. InternalRequestEditor and the HTTP helpers now go through it; their behaviour does not change.
…, and warn about kept pipes

utils/exec's injection check refused shebang arguments with a space and
script names with one, though no shell ever sees them: scripts now go
straight to os/exec. The first line is read the way the kernel reads it, at
most 256 bytes, so a long first line no longer fails the script. A start.d
entry that cannot be stat'ed is logged instead of skipped silently, and a
script whose daemon kept its output open is logged as a warning: that daemon
dies of SIGPIPE on its next write. A #!/bin/cat case proves the shebang's
program is the one that runs.
@inkly
inkly merged commit af9a6b6 into main Sep 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant