Skip to content

PerishCode/runseal

Repository files navigation

runseal

Run a command inside a small, explicit profile.

Use runseal when a repository needs explicit local resources, named wrappers, and env/argv/symlink setup without becoming a task runner, secret manager, or deployment orchestrator.

It is designed for repos that carry private local paths such as .local, kubeconfig files, SSH config, tool directories, or wrapper scripts, and need one command to expose those paths to child tools predictably.

30-second Example

Declare profile-local resources:

[resources]
root = ".local"

[[injections]]
type = "env"

[injections.vars]
APP_SSH_CONFIG = "resource://ssh/config"
APP_SECRET_DIR = "resource://secrets"

Inspect what runseal resolved:

runseal @profile
runseal @resources
runseal @resolve resource://ssh/config

Run an external command or a named wrapper inside the profile:

runseal bash -lc 'echo "$APP_SSH_CONFIG"'
runseal :ssh host --run ./probe.sh

Inspect What Runseal Sees

Runseal inspection commands are read-only and do not run profile injections:

runseal @profile
runseal @resources
runseal @resolve resource:// resource://ssh/config
runseal @transpile --input-lang=seal --output-lang=bash ./operator.seal
runseal @tool json get '{"releaseVersion":"v0.6.0"}' '.releaseVersion'
runseal @wrappers
runseal @which :ssh

These commands answer the first debugging questions: which profile was selected, where resources resolve, which wrappers are visible, and which wrapper file a :name command will execute.

Command Routing

Command routing is based on the first command token:

  • runseal <cmd> runs an external command inside the profile.
  • runseal :<cmd> runs a profile wrapper.
  • runseal @<cmd> runs a runseal-owned command.

For example:

runseal --profile ./runseal.toml bash -lc 'echo "$RUNSEAL_PROFILE_PATH"'

Use runseal profile without @ to run an external command named profile.

Fit

Fits well:

  • repo-local private resources
  • explicit one-command execution environments
  • named wrappers with discoverability
  • passing profile-scoped paths to tools like ssh, kubectl, uv, or terraform

Not trying to be:

  • a task dependency graph
  • a secret lifecycle manager
  • a deployment orchestrator
  • a shell auto-activation tool

Capabilities

runseal currently supports three profile capabilities plus explicit wrapper and internal command namespaces:

  • env: export environment variables and ordered env operations.
  • symlink: create symlinks for the command lifecycle, then clean them up.
  • argv: inject fixed arguments after a matching child command token.
  • resource://...: resolve profile-local resource paths inside env values.

Install

Unix:

curl -fsSL https://runseal.perish.uk/manage.sh | sh

Windows:

irm https://runseal.perish.uk/manage.ps1 | pwsh

Install a beta or one explicit version:

curl -fsSL https://runseal.perish.uk/manage.sh | sh -s -- install --channel beta
curl -fsSL https://runseal.perish.uk/manage.sh | sh -s -- install --version v0.1.0-beta.10

Uninstall:

curl -fsSL https://runseal.perish.uk/manage.sh | sh -s -- uninstall

If --profile is omitted, profile discovery walks from the current directory to filesystem root. At each directory, format priority is:

  1. runseal.toml
  2. runseal.yaml
  3. runseal.yml
  4. runseal.json

If no ancestor profile is found, discovery falls back to:

  1. $RUNSEAL_PROFILE_HOME/default.toml
  2. $RUNSEAL_PROFILE_HOME/default.yaml
  3. $RUNSEAL_PROFILE_HOME/default.yml
  4. $RUNSEAL_PROFILE_HOME/default.json

RUNSEAL_HOME is the runseal configuration root. When unset it defaults to ~/.runseal.

RUNSEAL_PROFILE_HOME is the profile directory. When unset it defaults to $RUNSEAL_HOME/profiles.

Each child command receives:

  • RUNSEAL_HOME
  • RUNSEAL_PROFILE_HOME
  • RUNSEAL_PROFILE_PATH
  • RUNSEAL_WRAPPER_PATH

Profile

[resources]
root = ".local"

[[injections]]
type = "env"

[injections.vars]
RUNSEAL_ENV = "dev"
LOCAL_ROOT = "resource://"
SSH_CONFIG = "resource://ssh/config"

[[injections]]
type = "env"

[[injections.ops]]
op = "prepend"
key = "PATH"
value = "./bin"
separator = "os"
dedup = true

[[injections]]
type = "symlink"
source = "./tool"
target = "./.runseal-bin/tool"
on_exist = "replace"
cleanup = true

[[injections]]
type = "argv"
command = "ssh"
args = ["-F", ".local/ssh/config"]

Lifecycle symlink targets are single-owner. Do not run concurrent runseal invocations that manage the same target with cleanup = true; one process can replace or remove the link while another still expects to own it. Use distinct targets when commands need to run in parallel under the same profile.

resource://path/to/file is a profile-only path literal. A profile that uses resource URIs must declare:

[resources]
root = ".local"

The resource root may be relative to the profile directory, absolute, or ~ expanded. In env injection values, runseal rewrites resource URIs to absolute paths under that configured root. For example, with root = ".local", resource://ssh/config resolves to <profile-dir>/.local/ssh/config. resource:// and resource://. resolve to the resource root itself.

Child commands receive only the resolved absolute path. They do not receive or need to understand resource://.

Resource URIs are resolved only when the env value is exactly the URI. runseal does not perform partial string interpolation inside env values.

Resource paths must be relative URI-style paths. Empty paths, empty path segments, ., .., backslash separators, and : inside path segments are rejected. Resource paths are resolved without checking whether the file exists.

Wrappers

If the command token starts with :, runseal resolves it as a wrapper executable instead of a literal program name:

runseal :ssh host --run ./probe.sh -- arg

Wrapper lookup order is:

  1. <profile-dir>/.runseal/wrappers/<name>.seal
  2. <profile-dir>/.runseal/wrappers/<name>.sh
  3. $RUNSEAL_HOME/wrappers/<name>.seal
  4. $RUNSEAL_HOME/wrappers/<name>.sh

The profile directory is the directory containing RUNSEAL_PROFILE_PATH. Successful profile and wrapper paths are normalized absolute paths. The child working directory is not changed. A resolved wrapper receives:

  • RUNSEAL_WRAPPER_NAME
  • RUNSEAL_WRAPPER_FILE

Seal wrappers use the .seal suffix and are interpreted directly by runseal. On Unix, shell wrappers use the .sh suffix and must be executable. On Windows, runseal also checks .exe, .cmd, and .bat when the wrapper name has no extension.

Seal wrappers

.seal files are bash-runnable wrapper glue. They are meant for cross-platform repository operations where the bash/PowerShell shared shape is clear. The boundary is syntax shape, not script size:

  • ordinary command execution, assignment, functions, if, while, case, shift, "$@", command success predicates such as if git checkout "$branch"; then, and command-scoped env overlays such as KUBECONFIG="$kubeconfig" kubectl "$@"
  • bash [ ... ] tests for ordinary predicates
  • explicit runseal @tool ... calls for atomic glue where bash and PowerShell do not share a clean expression

Use .seal as the profile integration layer: it should pass caller-specific paths, env names, and defaults explicitly. Keep reusable domain atoms in @tool, such as SSH config inspection, stdin script execution, path-list joining, branch slugging, Gitee PR API calls, and encrypted local archive round trips. For example, a wrapper can expose :ssh <host> --run <script> while runseal @tool ssh script run owns the stdin, argv forwarding, and host config details.

For example:

fail() {
  printf '%s\n' "$1" >&2
  exit 1
}

if [ -z "$channel" ]; then
  fail "channel missing"
fi

raw=$(gh run list --json databaseId)
run_id=$(runseal @tool json get "$raw" '.[0].databaseId')

Runseal interprets .seal wrappers directly when called as runseal :name. Use runseal @transpile --input-lang=seal --output-lang=bash <file> or --output-lang=powershell to inspect generated targets.

Seal is not intended to become a general scripting language. If a workflow wants richer parsing, data structures, or platform-specific behavior, move that part to Python, Ruby, JavaScript, etc. and call it from the wrapper.

Internal Commands

If the command token starts with @, runseal resolves it as a runseal internal command instead of a literal program name:

runseal @profile
runseal @resources
runseal @resolve resource:// resource://ssh/config
runseal @transpile --input-lang=seal --output-lang=sealir ./operator.seal
runseal @wrappers
runseal @which :ssh

Runseal-owned commands do not run profile injections. Inspection commands are read-only; @tool is the explicit atomic tool runtime.

  • @profile prints the resolved runseal runtime paths. If resources are configured, it also prints RUNSEAL_RESOURCE_ROOT.
  • @resources prints the resolved resource root.
  • @resolve resource://... prints resolved absolute resource paths, one per argument.
  • @transpile --input-lang=<lang> --output-lang=<lang> <source> transpiles explicit glue languages and prints the generated output. Cold start supports bash, seal, powershell, and sealir inputs and outputs for the currently recognized intersection.
  • @tool <namespace> <command> ... runs an atomic runseal tool command. Cold start supports JSON, string, regex, integer, process, filesystem, archive, SSH config, GitHub, Gitee, and Cloudflare helpers. Run runseal @tool --help for the complete tool index. Tools are reusable atoms: they may read generic defaults such as service tokens, but profile-specific paths and env names should be supplied by the calling wrapper.
  • @wrappers lists the effective wrappers visible to the current profile.
  • @which :<name> prints the wrapper file that :<name> resolves to.

YAML and JSON profiles use the same structure:

resources:
  root: .local
injections:
  - type: env
    vars:
      LOCAL_ROOT: resource://
      SSH_CONFIG: resource://ssh/config
{
  "resources": {
    "root": ".local"
  },
  "injections": [
    {
      "type": "env",
      "vars": {
        "LOCAL_ROOT": "resource://",
        "SSH_CONFIG": "resource://ssh/config"
      }
    }
  ]
}

Validation

Initialize local development hooks:

runseal :init
cargo fmt --check
cargo test

Repo-local operator commands use runseal itself:

runseal :cloudflare manage-inspect
runseal :pr --dry-run
runseal :release --channel beta --dry-run

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors