Skip to main content

OpsScript Gate

ShellCheck tells you if your script looks portable. OpsScript Gate checks if it actually runs there.

CI Demo PyPI Python Versions GitHub Marketplace Release License: MIT Supported Distros

OpsScript Gate is a drop-in runtime compatibility gate for Linux shell scripts. It executes your shell scripts inside isolated Debian, Ubuntu, and Alpine containers before release, catching environment-specific runtime failures that static analysis cannot detect.

OpsScript Gate Terminal Preview


Quickstart

In GitHub Actions

Add one step to your pull request workflow (.github/workflows/gate.yml):

- name: Verify Shell Script Portability
  uses: Mresyzz/opsscript-gate@v0.2.1
  with:
    script-path: scripts/setup.sh
    # shell: posix  # optional: 'posix' (default), 'shebang', or 'auto'

In Local Terminal (CLI)

Requires Python 3.10+ and a local Docker engine:

# Install from PyPI
pip install opsscript-gate

# Latest development version
pip install git+https://github.com/Mresyzz/opsscript-gate.git

# Run compatibility gate against your script
opsscript-gate run ./scripts/setup.sh

Example: What Static Analysis Misses

Consider this deployment script:

#!/bin/sh
set -e
echo "Fetching package information..."
apt-get --version

Running shellcheck reports 0 errors, 0 warnings because the syntax is valid POSIX shell.

However, when verified with OpsScript Gate:

+--------------------+----------+-----------+----------------------------------------------------+
| Distro             | Status   | Exit Code | Details                                            |
+--------------------+----------+-----------+----------------------------------------------------+
| debian:12-slim     | PASS     | 0         | OK                                                 |
| ubuntu:22.04       | PASS     | 0         | OK                                                 |
| ubuntu:24.04       | PASS     | 0         | OK                                                 |
| alpine:3.20        | FAIL     | 127       | Script failed with non-zero exit code: 127         |
+--------------------+----------+-----------+----------------------------------------------------+
Result: FAILED

============================================================
Failed Distributions - Output Snippets (last 15 lines):
============================================================

--- [alpine:3.20] (FAIL) ---
/tmp/target_script.sh: line 4: apt-get: not found

Example output; timing values omitted because they vary by host and image cache state.

Why it failed: Alpine Linux is musl/BusyBox-based and uses apk, not apt-get. OpsScript Gate catches the missing utility (exit code 127) during test execution, before the script is deployed.


Why OpsScript Gate?

OpsScript Gate vs ShellCheck vs Custom CI Matrix

Capability OpsScript Gate ShellCheck Handwritten CI Matrix
Runtime execution Yes No (Static AST only) Yes
Real distro environments Yes (Debian, Ubuntu, Alpine) No Yes
Preconfigured defaults Yes Yes Requires custom workflow configuration
Safe container defaults Built-in (ro, cap_drop, kill) N/A User-defined
Anti-hang stdin protection Built-in (</dev/null, noninteractive) No User-defined
Unified summary & diagnostics Built-in (ASCII + Step Summary) Static warnings User-defined
  • ShellCheck is indispensable for static analysis (syntax, quoting, SC warnings). OpsScript Gate complements it by testing actual execution behavior in real distributions.
  • Handwritten CI Matrix requires maintaining complex Docker configurations, volume mounts, timeout guards, and log parsers across every project. OpsScript Gate packages this into a single check.

Security Boundaries

OpsScript Gate uses conservative container defaults when running scripts:

  1. Unprivileged by Design:
    • Containers run with privileged=False.
    • All Linux capabilities are dropped: cap_drop=["ALL"].
    • Privilege escalation is disabled: security_opt=["no-new-privileges:true"].
  2. Read-Only Target Mount:
    • The tested script is mounted read-only (:ro) at /tmp/target_script.sh.
    • OpsScript Gate does not mount additional host filesystem paths into the test container.
  3. Anti-Hang Deadlock Defense:
    • Disables TTY and stdin (stdin_open=False, tty=False).
    • Redirects execution: /bin/sh -c "/bin/sh /tmp/target_script.sh </dev/null".
    • Injects DEBIAN_FRONTEND=noninteractive and CI=true.
    • Any script prompting for user input (read -p) fails immediately instead of blocking the CI runner.
  4. Timeout & Container Cleanup:
    • Enforces a configurable timeout (default: 60s). Timed-out containers are sent SIGKILL and marked TIMED_OUT.
    • Container removal is attempted from a finally block during normal Python execution paths, including failures and timeouts.
  5. Windows CRLF Defense:
    • Automatically detects and normalizes carriage returns (\r\n -> \n) before container execution, preventing false \r: command not found errors.
  6. POSIX-oriented /bin/sh Baseline:
    • Containers invoke /bin/sh directly, catching undeclared Bashism syntax (e.g. bash arrays, [[ ... ]]) that break in lightweight Alpine environments.

Default Test Matrix

Image Distribution Focus
debian:12-slim Debian 12 (Bookworm) Minimal glibc + APT base
ubuntu:22.04 Ubuntu 22.04 LTS (Jammy) Enterprise long-term support baseline
ubuntu:24.04 Ubuntu 24.04 LTS (Noble) Modern glibc, updated coreutils & defaults
alpine:3.20 Alpine Linux 3.20 Minimal musl libc + BusyBox (strict POSIX test)

You can customize the matrix at any time via --matrix or Action input matrix.


CLI Reference

usage: opsscript-gate run [-h] [--matrix MATRIX] [--timeout TIMEOUT]
                          [--format {table,markdown,json}]
                          [--shell {posix,shebang,auto}]
                          script_path
Parameter Type Default Description
script_path Positional Required Path to target shell script
--matrix String debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20 Comma-separated list of Docker images
--timeout Integer 60 Hard timeout per container in seconds
--format Choice table Output format: table, markdown, or json
--shell Choice posix Execution mode: posix (default), shebang, or auto
--version Flag - Show version number
-h, --help Flag - Show argument help

Shell Execution Modes (--shell)

  • posix (default): Strictly executes with /bin/sh, ignoring any script shebang. Ideal for verifying that your script runs in minimal POSIX-compliant environments (e.g. Alpine BusyBox).
  • shebang: Strictly honors the interpreter specified in the script's shebang (#!/bin/sh, #!/bin/bash, #!/usr/bin/sh, #!/usr/bin/bash, #!/usr/bin/env sh, #!/usr/bin/env bash). If the shebang is missing, malformed, or specifies an unsupported interpreter/flag, the check fails immediately with an error before running containers.
  • auto: Honors recognized shebangs if present; falls back to /bin/sh if no shebang is declared. Scripts with explicit unsupported or malformed shebangs fail immediately with an error (does not silently execute as POSIX).

Exit Code Convention

  • 0: All distributions passed (PASS).
  • 1: At least one distribution failed (FAIL), timed out (TIMED_OUT), or errored (ERROR).

Examples

Check out the examples/ directory for self-contained, runnable scenarios:


Dogfooding

OpsScript Gate is used in Mresyzz/linux-dev-bootstrap to validate its install.sh script across the default Debian, Ubuntu, and Alpine matrix in GitHub Actions. That workflow pins Mresyzz/opsscript-gate@v0.2.0 with shell: auto.

See the downstream workflow: .github/workflows/test.yml.


Development & Testing

The test suite uses Docker SDK mocking to ensure fast unit tests without needing a local daemon:

# Clone and install with test dependencies
git clone https://github.com/Mresyzz/opsscript-gate.git
cd opsscript-gate
pip install -e .[test]

# Run unit tests (mocked)
pytest -v -m "not integration"

# Run integration tests (Requires Docker daemon)
pytest -v

Current limitations

  • Requires access to a Docker daemon.
  • Scripts are executed with /bin/sh by default; use --shell shebang or --shell auto for shebang-aware execution.
  • Distribution runs are currently sequential.
  • Containers use bridge networking by default.
  • Failure reports currently include only a tail of captured output.
  • OpsScript Gate checks runtime execution and exit status; it does not validate application-specific outcomes.

Roadmap

See ROADMAP.md for planned capabilities, including:

  • Container resource limits (--mem-limit, --pids-limit)
  • Configurable network isolation (--network none|bridge)
  • Parallel matrix execution

Contributing & Security

  • Contributing: Please review CONTRIBUTING.md for pull request guidelines and security boundaries.
  • Security Policy: Read SECURITY.md to report vulnerabilities responsibly.
  • Changelog: See CHANGELOG.md for release history.

License

OpsScript Gate is licensed under the MIT License.

Release files for opsscript-gate 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for opsscript-gate 0.2.1
File Size Uploaded
opsscript_gate-0.2.1.tar.gz 83.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for opsscript-gate 0.2.1
File Interpreter ABI Platform
opsscript_gate-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 100.0 kB

Release files / opsscript_gate-0.2.1.tar.gz

Download URL opsscript_gate-0.2.1.tar.gz
Size 83.5 kB
Tags Source
SHA-256 checksum
How to use checksums
e0a09045121b450e902b4f7208e3a48d35f506fbdeb95cfb2006dae7bb4853bf
BLAKE2b-256 checksum
How to use checksums
06e8a85c13f181e9de9f0e031c65c0a350ef05ae4a54ca71dbf8967f2a57bb9f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release files / opsscript_gate-0.2.1-py3-none-any.whl

Download URL opsscript_gate-0.2.1-py3-none-any.whl
Size 16.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7deb1b87a97814ad65c46a5d30cca1d8864557dfcae7182888815a719f0c9385
BLAKE2b-256 checksum
How to use checksums
200310783d6417cb2502180a5bb765a851cadb6032e96c6e844c00ae4bbaf7b8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page