Skip to content
hagix9Public

About

Lightweight, agentless configuration management in Rust

Topics

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

155 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sinter

English | 日本語

Documentation: https://sinter.fulltrust.co.jp/ (日本語)

Small enough to understand, strong enough to trust.

Release CI License: MIT OR Apache-2.0

Open in GitHub Codespaces

Try Sinter v1.3.0 in a disposable Ubuntu container in your browser — nothing to install locally, no SSH, no servers. See the start guide.

Sinter is a lightweight, agentless configuration-management tool inspired by Itamae. It describes and applies operating-system configuration from a single Rust binary without requiring an agent, Ruby, Python, or a Sinter runtime on the managed host.

Sinter terminal demo: plan, apply, audit

Why Sinter?

  • Agentless, single-binary controller Managed hosts do not need a Sinter agent or runtime, nor a Ruby or Python runtime.

  • plan means observation only sinter plan observes target state without uploading staging data, changing permissions or ownership, installing or removing packages, changing services, or executing command resources.

  • apply re-observes before mutation A previous plan is never treated as authoritative current state. Stateful resources are observed again immediately before Sinter decides whether to mutate them.

  • Fail closed rather than guessing Unsafe parent paths, unexpected symlinks, unknown or changed SSH host keys, failed verification, and indeterminate state do not silently continue as success.

  • Truthful result reporting Sinter keeps execution, change, verification, and disposition distinct where necessary, preserving states such as changed, failed, indeterminate, possible, verified, and blocked instead of flattening everything into a boolean result.

  • Strict SSH identity checking The selected known_hosts file is authoritative. Sinter does not automatically enroll unknown hosts or fall back to insecure verification. Non-default SSH ports require an explicit [host]:port identity.

  • Idempotent by design When a stateful resource already matches the desired state, applying the same recipe again performs zero mutations for that resource.

Quick example

version: 1

resources:
  - id: tree
    type: package
    with:
      name: tree
      state: present
sinter validate recipe.yaml
sinter plan --host server.example.com recipe.yaml
sinter apply --host server.example.com --sudo recipe.yaml
sinter audit --host server.example.com --sudo recipe.yaml

This repository implements Sinter v0.2 as specified by GOALS.md and DESIGN.md, which are the authoritative specification; v0.2 extends the v0.1 contract with RHEL-family platform support (Rocky Linux, RHEL, AlmaLinux — dnf). The implementation still adds no roles, plugins, orchestration, or embedded scripting. Multiple hosts are handled by a deliberately small, fail-closed inventory (hosts, flat groups, explicit per-recipe targets) — see Multiple hosts.

Install

Sinter v1.3.0 ships one sinter-v1.3.0-linux-x86_64.tar.gz artifact covering every supported Linux x86_64 platform line: Ubuntu, Rocky Linux, RHEL 9 / 10, and AlmaLinux 9 / 10.

curl -fsSL https://sinter.fulltrust.co.jp/install.sh | sh
$HOME/.local/bin/sinter --version

Acceptance-tested point releases (v1.3.0 release artifact): Ubuntu 24.04.5 LTS, Ubuntu 26.04.1 LTS, Rocky Linux 9.8, Rocky Linux 10.2, RHEL 9.8, RHEL 10.2, AlmaLinux 9.8 and AlmaLinux 10.2, all x86_64. Other point releases have not each been independently accepted.

The installer selects the latest stable official GitHub release, verifies SHA256SUMS before extraction, and installs without sudo into $HOME/.local/bin. If needed, add that directory to PATH yourself; shell profiles are not edited. For inspect-before-run and manual downloads, see Installation.

Build

cargo build --release
# binary: target/release/sinter

Workflow

sinter validate recipe.yaml
sinter plan --host host.example recipe.yaml
sinter apply --host host.example recipe.yaml
sinter audit --host host.example recipe.yaml
  • validate checks recipe structure and semantics without connecting to a target.
  • plan performs observation only and produces a non-authoritative preview of what apply would change. It never writes files, uploads staging data, changes permissions/ownership, changes packages or services, or executes command resources.
  • apply re-observes every stateful resource immediately before deciding whether to mutate it.
  • audit is also read-only, but answers a different question: whether the target currently matches the recipe. It reports PASS/DRIFT/ NOT_AUDITABLE/NOT_APPLICABLE/ERROR per resource — command resources are always NOT_AUDITABLE and never executed — and exits 7 on drift or 6 on observation errors.

Local targets are used when --host is omitted. SSH and passwordless sudo -n are supported with --host … --sudo. From v1.1.0, if ssh <host> works, --host <host> uses the same ~/.ssh/config settings (HostName, User, Port, IdentityFile, ssh-agent); host keys must still already be in known_hosts.

From v1.1.0, validate checks only the recipe: --host, --inventory and SSH options are accepted on every command and ignored by validate, so one command line works for every phase. Status words are colored on a terminal only (never in pipes, with NO_COLOR, or in --format json).

Available since v1.3.0.

While plan, apply and audit run, an interactive terminal shows a transient progress line on stderr that disappears when the run ends. SINTER_PROGRESS=plain writes persistent progress: lines to stderr instead, for logs and CI. There is no progress in --format json, for recipes that reference encrypted secrets, or by default when stderr is not a terminal. Details: Progress output; design record: ADR 0001.

Multiple hosts, bundles and backups

Available from Sinter v1.1.0. Existing single-host recipes and --host command lines work unchanged.

# hosts.yaml — which hosts exist
hosts:
  web01: { address: 10.0.0.11, user: ubuntu }
  db01:  { address: 10.0.0.21, user: rocky }
groups:
  web: { hosts: [web01] }
# nginx.yaml — where this recipe may run, and what to save first
version: 1
targets:
  groups: [web]
backup:
  paths: [/etc/nginx/nginx.conf]
resources:
  - id: nginx
    type: package
    with:
      name: nginx
      state: present
sinter plan  nginx.yaml --inventory hosts.yaml   # shows MATCH/SKIP per host
sinter apply nginx.yaml --inventory hosts.yaml
  • A host in the inventory is never a target by itself: with --inventory, a recipe without targets is an error, and hosts no recipe selects are never contacted.
  • A bundle (version: 1 + recipes: [common.yaml, nginx.yaml]) runs several recipes; each is resolved against its own targets.
  • apply stops at the first execution that exits non-zero (for any reason) and reports the rest not_run; plan/audit visit every selected host; a partial failure never exits 0.
  • backup.paths are copied on each selected host before apply changes anything (under ~/.sinter/backups/<run-id>/, or /var/lib/sinter/backups/<run-id>/ with --sudo); a failed backup stops the apply. Backups are not a rollback.

Details: CLI reference.

CLI exit codes

Code Meaning
0 invocation completed successfully (plan differences still exit 0; audit: no DRIFT and no ERROR — NOT_AUDITABLE/NOT_APPLICABLE resources may still be present)
2 validation/schema error
3 target connection/capability/security error
4 plan could not be completed safely
5 apply failed
6 apply became indeterminate; audit recorded one or more ERROR results (errors dominate DRIFT)
7 audit detected DRIFT with no ERROR results

Recipe model

YAML and TOML are frontends for one common semantic IR. Equivalent recipes in either format produce equivalent typed values, resource identities, ordering, desired state, ChangeSets, and execution behavior.

Top-level fields: version, vars, include, resources, handlers.

Resource types in v0.2: file, directory, template, link, command, package, service. Handlers are delayed restart/reload service actions. Since v1.2.0 Sinter also has two local account types, group and user: explicit depends_on, local database only, no renumbering, additive supplementary groups, absent without -r/-f. See the resource reference.

Also since v1.2.0: sinter secrets encrypt | decrypt | list encrypts secret files in the standard age format (recipients or a terminal-only passphrase; identities outside the repository by default). A file resource can take its content from such a secret with content: { secret: <path> }, and a user can take its password hash from one with password_hash: { secret: <path> } (validate never decrypts; plan, apply and audit do; the hash needs --sudo). sinter secrets list --recipe FILE shows which recipes' resources use each secret and which listed secrets none of those recipes reference. See the sinter secrets reference.

A minimal recipe:

version: 1
vars:
  greeting:
    value: hello
    sensitive: false
resources:
  - id: motd
    type: template
    with:
      path: /etc/motd
      source: templates/motd
      mode: "0644"
    notify:
      - restart_motd
handlers:
  - id: restart_motd
    service: motd
    action: restart

Security and safety properties

  • Plan performs observation only and cannot mutate state.
  • Apply re-observes state immediately before every mutation decision; a plan is never reused as current state.
  • Stateful resources are idempotent: a second apply performs zero mutations.
  • SSH accepts only hosts already present in the selected known_hosts file. Unknown or changed keys are connection failures; there is no insecure fallback or automatic enrollment.
  • Remote commands preserve exact argv with no unintended shell evaluation. NUL bytes are rejected.
  • --sudo runs every target-side operation with effective UID 0 via non-interactive sudo -n. Without it, everything runs as the target user. Sinter never retries a permission failure with sudo.
  • Command resources use a fixed baseline environment (PATH, LANG, LC_ALL, HOME); controller, SSH-session, sudo, and login-shell environment variables are not inherited. Reserved names cannot be overridden by recipes.
  • Filesystem mutations enforce a parent-path trust boundary, reject unexpected symlinks, preserve existing metadata when omitted, refuse to discard unsupported security metadata, and publish content atomically by rename.
  • Indeterminate mutations (timeout after dispatch, lost response, signal uncertainty) are never retried automatically.
  • Fail-fast: the first failed or indeterminate resource stops further execution and reports remaining resources as blocked.
  • Sensitive values never appear in normal, verbose, diff, registered-result, diagnostics, or structured output. For sensitive content, hashes and sizes are hidden.
  • Failed, indeterminate, verification-failure, and possible-change outcomes are reported truthfully.

Supported platforms

Managed targets:

Platform Architecture Package backend Status
Ubuntu 24.04 LTS amd64 apt Supported, acceptance-tested
Ubuntu 26.04 LTS amd64 apt Supported, acceptance-tested
Rocky Linux 9 x86_64 dnf Supported, acceptance-tested
Rocky Linux 10 x86_64 dnf Supported, acceptance-tested
RHEL 9 x86_64 dnf Supported, acceptance-tested
RHEL 10 x86_64 dnf Supported, acceptance-tested
AlmaLinux 9 x86_64 dnf Supported, acceptance-tested
AlmaLinux 10 x86_64 dnf Supported, acceptance-tested
Oracle Linux x86_64 dnf Expected compatible — not acceptance-tested

Package recipes are platform-neutral: the same type: package / state: present resource is handled by apt on Ubuntu and dnf on RHEL-family targets, selected from the detected /etc/os-release identity.

Oracle Linux is recognized as a Red Hat-family platform and uses Sinter's DNF backend. It is expected to be compatible with the corresponding Red Hat-family implementation, but it is not currently part of Sinter's real-host acceptance matrix.

Each release from v1.0.0 on is acceptance-tested as its exact release artifact on the eight supported targets before it is published, and its acceptance evidence (manifest, raw logs, checksums) is published with the release. The current release, Sinter v1.3.0, passed the Linux x86_64 validation gate and was then acceptance-tested as its exact release artifact (sinter-v1.3.0-linux-x86_64.tar.gz) on eight real x86_64 Linux hosts — the exact point releases listed under Install. On every host the tarball and the extracted binary were verified byte-identical (SHA-256) and ran the established acceptance scenario plus artifact-identity and MCP checks, a real-host group → user → directory → file lifecycle, and a real-host suite for the v1.3.0 features (progress output, plan/apply agreement, template tag errors, rpm ~/^ versions): 3,980 checks passed, 0 failed (see the v1.3.0 acceptance evidence). Sinter v1.2.0 passed the earlier eight-host acceptance (1240 checks, 0 failed; v1.2.0 evidence). Sinter v1.1.3, v1.1.2, v1.1.1, v1.1.0 and v1.0.0 each passed the earlier eight-host acceptance (408/408), and v0.5.1 and v0.4.1 each passed the earlier one (344/344); those records and earlier VM and release evidence remain historical.

All managed targets require systemd, an OpenSSH server, /bin/sh, the attr package (/usr/bin/getfattr, used to inspect extended attributes and POSIX ACLs before any write — check test -x /usr/bin/getfattr on each target; install attr with apt or dnf if missing), and passwordless sudo -n when privilege escalation is required. The controller reference environments are macOS, Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Rocky Linux 9, Rocky Linux 10, RHEL 9, RHEL 10, AlmaLinux 9, AlmaLinux 10, and other x86_64 Linux environments where the binary builds.

Testing

The test suite is split into unit tests (in src/) and integration/acceptance tests (in tests/):

Suite Scope
lib unit tests value model, frontends, expressions/Unknown, paths, argv quoting, package states
frontends YAML/TOML equivalence and IR fixtures, schema rejection, includes
engine file/directory/link/template, plan safety, idempotency, fail-fast, static identifiers
commands guards, registers, changed_when, environment baseline, exit codes
handlers delayed handlers, dedup, fail-fast, verification gating
package_service apt install/remove/idempotency, systemd state/enabled combinations
file_safety trust boundary, symlink rejection, metadata preservation, atomic publication, failure injection
truthfulness result-dimension matrix, ordering, dependency blocks
cli exit codes, JSON output, sensitive-output redaction
ssh real SSH integration (known_hosts, sudo, argv exactness, timeouts, signals)

Run the full suite on the reference target:

cargo test

SSH integration tests are enabled by environment variables pointing at a disposable Ubuntu target:

export SINTER_TEST_SSH_HOST=127.0.0.1
export SINTER_TEST_SSH_PORT=22
export SINTER_TEST_SSH_USER=ubuntu
export SINTER_TEST_SSH_KNOWN_HOSTS=/path/to/known_hosts
export SINTER_TEST_SSH_IDENTITY=/path/to/test_key
cargo test --test ssh

External observation and an instrumented command log are both used to verify plan performs no mutation and that idempotent second applies issue no mutation operations.

Repository layout

src/
  value.rs         common semantic value model
  yaml.rs          YAML frontend (rejects aliases/anchors/merge/dupes/non-finite)
  toml_front.rs    TOML frontend (rejects datetimes/non-finite)
  document.rs      schema validation of parsed documents
  ir.rs            intermediate representation constants
  model.rs         include expansion, loops, static identifiers, graph validation
  expressions.rs   expression language, interpolation, Unknown semantics
  facts.rs         target fact model
  executor.rs      local/SSH execution, known_hosts, sudo, exact argv
  targetfs.rs      target filesystem trust checks and atomic publication
  resources.rs     resource implementations
  engine.rs        plan/apply engine, ordering, dependencies, handlers, fail-fast
  audit.rs         read-only audit engine (per-resource compliance/drift)
  result.rs        result dimensions (execution/change/verification/disposition)
  diff.rs          truthful, sanitized diff rendering
  output.rs        human and JSON rendering with sensitive redaction
  error.rs         error kinds and exit codes
  progress.rs      internal progress events (stage, count, item kind and id only)
  progress_session.rs  progress mode decision, session worker, bounded teardown
  progress_tty.rs  transient terminal progress line (stderr)
  progress_plain.rs  opt-in persistent progress lines (SINTER_PROGRESS=plain)
  mcp.rs           read-only MCP stdio adapter
  targets.rs       administrator-owned named SSH target profiles for MCP
  main.rs          CLI
tests/             acceptance and integration test suites

MCP interface

Status: released in v0.5.0.

sinter mcp serves a minimal, strictly read-only MCP (Model Context Protocol) endpoint over stdio (newline-delimited JSON-RPC 2.0). It is a thin adapter over the authoritative core — no validation, platform, or planning rule is reimplemented.

Tools (all read-only; there is intentionally no apply/execute/install tool):

Tool Purpose
sinter_get_version Crate version and read-only capability statement.
sinter_classify_platform Classify a target from /etc/os-release content (family, package backend) via the real platform model.
sinter_validate_manifest Validate recipe text with the real load_model parser; structured diagnostics.
sinter_inspect_manifest Structural recipe summary: resource identities, types, dependencies, sensitivity flags. Values are never returned.
sinter_plan Plan a recipe against a supplied-facts target snapshot (ubuntu2404, ubuntu2604, rocky9, rocky10) using the in-process scripted target — production planning code, no SSH, no real host, Mode::Plan only.
sinter_list_targets List the opaque names of administrator-configured SSH target profiles (names only — never connection details).
sinter_plan_host Plan a recipe against a named SSH target profile: real-host read-only observation via the production Mode::Plan path.
sinter_audit_host Audit whether a named SSH target satisfies a recipe via the production run_audit path. Read-only.

Not available: apply, arbitrary command execution, or any mutation. Remote access is possible only through administrator-configured named targets — see below.

MCP manifests accept inline content only: include: and source: are rejected on the parsed structure before loading in every manifest-consuming tool, so a manifest never grants controller-local filesystem read authority. This restriction is MCP-specific — ordinary CLI recipes keep full include:/source: support.

Client configuration example (stdio servers):

{ "mcpServers": { "sinter": { "command": "sinter", "args": ["mcp"] } } }

Named targets (--targets-file)

sinter mcp --targets-file targets.toml enables real-host read-only observation through an immutable, startup-loaded registry of named SSH profiles. The MCP client may reference a target only by its opaque name — it cannot supply host, port, user, known_hosts, identity files, sudo, or any other connection parameter. Those are exclusively administrator-owned profile policy.

[targets.web01]
host = "web01.example.com"
port = 22                    # optional, default 22
user = "deploy"
known_hosts = "/secure/path/known_hosts"
identity_files = ["/secure/path/id_ed25519"]  # optional
sudo = false                 # optional: profile-owned privilege policy

[targets.db01]
host = "10.0.0.20"
user = "ops"
known_hosts = "/secure/path/known_hosts"
sudo = true
  • Profile names: [A-Za-z0-9_-], start alphanumeric, max 64 chars.
  • The file is parsed once at startup; a missing, unreadable, malformed, or structurally invalid file aborts sinter mcp with an error. No implicit default locations, no environment-variable discovery.
  • Without --targets-file, the host tools are registered but fail closed: sinter_list_targets returns an empty list and plan/audit calls report unknown target.
  • Host tools reuse the production Plan/Audit paths: Mode::Plan on a read-only TargetFs (mutation permits are unobtainable), command resources never execute, and run_audit additionally refuses any engine that could produce a permit.
  • sinter_list_targets returns names only; underlying diagnostics are sanitized so profile internals (host, user, key paths) do not reach MCP output.
  • An omitted or empty identity_files follows the existing Sinter SSH authentication behavior and may use default identity resolution; it does not disable authentication.
  • Host plan output never returns file or template bodies: content diffs are redacted at the MCP boundary regardless of the manifest's sensitive flags.

This is unrelated to the documentation site's WebMCP surface (browser-side, documentation lookup only); Core MCP exposes Sinter's own operations.

ChatGPT Plugin (paused)

Paused. The public ChatGPT plugin / remote MCP publication path is currently paused while the project focuses on the core Sinter experience, and access requests are on hold. This does not affect the Sinter CLI, sinter mcp, or WebMCP. The section below describes the architecture as built.

The Sinter ChatGPT plugin lets ChatGPT call the read-only sinter mcp tools on your own Sinter installation:

ChatGPT → Sinter plugin → public Gateway (https://gateway.fulltrust.co.jp/mcp)
        → your account's controller → your sinter-bridge → your local `sinter mcp`
  • The public Gateway (gateway/ crate) authenticates ChatGPT with OAuth and relays MCP requests; it is not your Sinter host and never connects to your servers.
  • sinter-bridge runs on your machine (outbound HTTPS only) and forwards each request to a local sinter mcp child. All tools are read-only and annotated readOnlyHint: true, destructiveHint: false, openWorldHint: false.
  • Sinter for ChatGPT is currently available by invitation. To request access, contact us through the Fulltrust contact form and mention "Sinter" in your inquiry (do not include server details, credentials, or tokens). The Gateway operator then sets up your sign-in account and issues a one-time registration token for your bridge.

Setup, troubleshooting, and security/privacy details: ChatGPT Plugin guide.

Reporting bugs and vulnerabilities

License

Sinter is licensed under either of:

  • Apache License, Version 2.0 (LICENSE-APACHE)
  • MIT License (LICENSE-MIT)

at your option.

About

Lightweight, agentless configuration management in Rust

Topics

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages