opsscript-gate 0.4.1__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/.github/workflows/test.yml +15 -2
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/CHANGELOG.md +23 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/PKG-INFO +111 -13
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/README.md +110 -12
- opsscript_gate-0.5.0/README.zh-CN.md +67 -0
- opsscript_gate-0.5.0/action.yml +130 -0
- opsscript_gate-0.5.0/docs/configuration.md +115 -0
- opsscript_gate-0.5.0/docs/troubleshooting.md +67 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/pyproject.toml +1 -1
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/src/opsscript_gate/__init__.py +1 -1
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/src/opsscript_gate/cli.py +89 -4
- opsscript_gate-0.5.0/src/opsscript_gate/config.py +78 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/src/opsscript_gate/discovery.py +16 -3
- opsscript_gate-0.5.0/tests/test_action_contract.py +22 -0
- opsscript_gate-0.5.0/tests/test_onboarding.py +133 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/tests/test_runner.py +2 -1
- opsscript_gate-0.4.1/action.yml +0 -90
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/.gitattributes +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/.github/ISSUE_TEMPLATE/distro_request.yml +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/.github/assets/social-preview.png +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/.github/pull_request_template.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/.github/workflows/demo.yml +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/.github/workflows/release.yml +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/.gitignore +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/CONTRIBUTING.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/LICENSE +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/ROADMAP.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/SECURITY.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/docs/promotion/awesome-lists-pr.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/docs/promotion/juejin-zhihu-article.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/docs/promotion/reddit-devops.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/docs/promotion/show-hn.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/docs/promotion/v2ex-post.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/examples/alpine-incompatibility/README.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/examples/alpine-incompatibility/apt_dependency.sh +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/examples/basic/README.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/examples/basic/clean_setup.sh +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/examples/github-actions/README.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/examples/github-actions/workflow.yml +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/examples/interactive-hang/README.md +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/examples/interactive-hang/prompt_confirm.sh +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/src/opsscript_gate/models.py +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/src/opsscript_gate/remediation.py +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/src/opsscript_gate/reporter.py +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/src/opsscript_gate/runner.py +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/tests/fixtures/fail_deps.sh +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/tests/fixtures/fail_hang.sh +0 -0
- {opsscript_gate-0.4.1 → opsscript_gate-0.5.0}/tests/fixtures/pass_basic.sh +0 -0
|
@@ -8,7 +8,11 @@ on:
|
|
|
8
8
|
|
|
9
9
|
jobs:
|
|
10
10
|
test:
|
|
11
|
-
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, windows-latest]
|
|
14
|
+
python: ['3.10', '3.12']
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
12
16
|
steps:
|
|
13
17
|
- name: Checkout Code
|
|
14
18
|
uses: actions/checkout@v7
|
|
@@ -16,7 +20,7 @@ jobs:
|
|
|
16
20
|
- name: Set up Python
|
|
17
21
|
uses: actions/setup-python@v7
|
|
18
22
|
with:
|
|
19
|
-
python-version:
|
|
23
|
+
python-version: ${{ matrix.python }}
|
|
20
24
|
|
|
21
25
|
- name: Install Dependencies
|
|
22
26
|
run: |
|
|
@@ -50,3 +54,12 @@ jobs:
|
|
|
50
54
|
- name: Run Docker Integration Tests
|
|
51
55
|
run: |
|
|
52
56
|
pytest -v -m integration
|
|
57
|
+
|
|
58
|
+
- name: Exercise the local composite Action
|
|
59
|
+
uses: ./
|
|
60
|
+
with:
|
|
61
|
+
script-path: tests/fixtures/pass_basic.sh
|
|
62
|
+
preset: minimal
|
|
63
|
+
network: none
|
|
64
|
+
output: action-report.json
|
|
65
|
+
format: json
|
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2026-09-24
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `init`: non-overwriting project configuration and GitHub workflow generation.
|
|
7
|
+
- Shared, strictly validated `.opsscript-gate.json` settings; explicit CLI/Action inputs win.
|
|
8
|
+
- Docker-free `--dry-run` plans in text or JSON, including execution counts.
|
|
9
|
+
- `minimal` and `ubuntu` distribution presets, repeatable exclusion globs and configurable discovery limit.
|
|
10
|
+
- `--output` report artifacts, including failure reports; source/config overwrite protection.
|
|
11
|
+
- Action inputs for configuration, presets, exclusions, preview and saved reports.
|
|
12
|
+
- Chinese quickstart, configuration guide and Ubuntu/Alpine troubleshooting guide.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Discovery now fails on overflow instead of silently testing only the first 20 scripts.
|
|
16
|
+
- Discovery skips symlinks and non-files and rejects shebang prefix collisions.
|
|
17
|
+
- CLI rejects non-positive limits and unsupported network modes before execution.
|
|
18
|
+
- Default Action inputs no longer override a user's project configuration.
|
|
19
|
+
|
|
20
|
+
### Migration
|
|
21
|
+
- Repositories with more than 20 discovered scripts must exclude unwanted scripts or raise `max_scripts`.
|
|
22
|
+
- Auto-discovery no longer follows file symlinks; explicit script paths remain supported.
|
|
23
|
+
- Default settings without a configuration file remain compatible with v0.4.1 and earlier.
|
|
24
|
+
- Generated workflows use the v0.5.0 release tag.
|
|
25
|
+
|
|
3
26
|
All notable changes to this project will be documented in this file.
|
|
4
27
|
|
|
5
28
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: opsscript-gate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Run Linux shell scripts across Debian, Ubuntu and Alpine before release to catch runtime compatibility failures
|
|
5
5
|
Project-URL: Homepage, https://github.com/Mresyzz/opsscript-gate
|
|
6
6
|
Project-URL: Repository, https://github.com/Mresyzz/opsscript-gate
|
|
@@ -26,7 +26,9 @@ Provides-Extra: test
|
|
|
26
26
|
Requires-Dist: pytest>=8.0.0; extra == 'test'
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
|
-
# OpsScript Gate
|
|
29
|
+
# OpsScript Gate — shell script compatibility testing
|
|
30
|
+
|
|
31
|
+
[简体中文](README.zh-CN.md) · [Configuration](docs/configuration.md) · [Troubleshooting](docs/troubleshooting.md)
|
|
30
32
|
|
|
31
33
|
> **ShellCheck tells you if your script looks portable. OpsScript Gate checks if it actually runs there.**
|
|
32
34
|
|
|
@@ -41,8 +43,32 @@ Description-Content-Type: text/markdown
|
|
|
41
43
|
|
|
42
44
|
**OpsScript Gate** is a drop-in runtime compatibility gate for Linux shell scripts. It executes your scripts inside unprivileged Debian, Ubuntu, and Alpine containers before merge, catching environment-specific runtime failures, missing interpreters, and package-manager assumptions that static analysis cannot detect.
|
|
43
45
|
|
|
46
|
+
It is designed for shell testing, portable shell validation, Bash/POSIX compatibility checks, and cross-distro CI where syntax-only tooling is not enough.
|
|
47
|
+
|
|
44
48
|
---
|
|
45
49
|
|
|
50
|
+
## New in v0.5.0
|
|
51
|
+
|
|
52
|
+
Preview exactly what will run, keep local and CI settings together, and exclude test
|
|
53
|
+
fixtures before executing scripts. These controls are included in v0.5.0. Projects on
|
|
54
|
+
v0.4.1 and earlier do not include them.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
opsscript-gate init
|
|
58
|
+
opsscript-gate run --dry-run
|
|
59
|
+
opsscript-gate run --format json --output reports/compatibility.json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`init` creates `.opsscript-gate.json` and a GitHub Actions workflow without replacing
|
|
63
|
+
existing files. Review its exclusions (`tests/*`, `examples/*`) and offline network
|
|
64
|
+
default before running. No Docker is required for `init` or `--dry-run`.
|
|
65
|
+
|
|
66
|
+
Useful for standalone installers, container entrypoints and release scripts that
|
|
67
|
+
must work on both GNU/Linux and Alpine/BusyBox. Each script runs independently;
|
|
68
|
+
repository files, sibling scripts and project dependencies are **not** mounted.
|
|
69
|
+
See [configuration and migration](docs/configuration.md) and
|
|
70
|
+
[why a shell script works on Ubuntu but fails on Alpine](docs/troubleshooting.md).
|
|
71
|
+
|
|
46
72
|
## ⚡ 30-Second Quickstart
|
|
47
73
|
|
|
48
74
|
### In GitHub Actions (Zero Config)
|
|
@@ -58,15 +84,15 @@ jobs:
|
|
|
58
84
|
runs-on: ubuntu-latest
|
|
59
85
|
steps:
|
|
60
86
|
- uses: actions/checkout@v7
|
|
61
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
87
|
+
- uses: Mresyzz/opsscript-gate@v0.5.0
|
|
62
88
|
```
|
|
63
89
|
|
|
64
|
-
> **Zero Config**: If `script-path` is omitted, OpsScript Gate automatically discovers shell scripts in your repository
|
|
90
|
+
> **Zero Config**: If `script-path` is omitted, OpsScript Gate automatically discovers shell scripts in your repository. Scripts run sequentially, with concurrent distribution checks for each script. Preview discovery before running unfamiliar repositories.
|
|
65
91
|
|
|
66
92
|
Or test a specific script with custom execution modes:
|
|
67
93
|
|
|
68
94
|
```yaml
|
|
69
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
95
|
+
- uses: Mresyzz/opsscript-gate@v0.5.0
|
|
70
96
|
with:
|
|
71
97
|
script-path: scripts/install.sh
|
|
72
98
|
shell: auto
|
|
@@ -88,6 +114,35 @@ opsscript-gate run ./scripts/install.sh
|
|
|
88
114
|
opsscript-gate run
|
|
89
115
|
```
|
|
90
116
|
|
|
117
|
+
### Try a Real Failure in 30 Seconds
|
|
118
|
+
|
|
119
|
+
Create `install.sh`:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
#!/bin/sh
|
|
123
|
+
set -e
|
|
124
|
+
apt-get --version
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Then run:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
opsscript-gate run ./install.sh
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A typical result across the default matrix looks like this:
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
Debian 12 PASS
|
|
137
|
+
Ubuntu 22.04 PASS
|
|
138
|
+
Ubuntu 24.04 PASS
|
|
139
|
+
Alpine 3.20 FAIL
|
|
140
|
+
|
|
141
|
+
apt-get: not found
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The script is valid shell, but the runtime environment is incompatible. That is exactly the class of failure OpsScript Gate is designed to catch.
|
|
145
|
+
|
|
91
146
|
---
|
|
92
147
|
|
|
93
148
|
## 🎯 Reviewer-First Experience: What It Produces
|
|
@@ -190,7 +245,10 @@ sh: line 4: apt-get: not found
|
|
|
190
245
|
|
|
191
246
|
## 🔒 Security Boundaries & Hardened Isolation
|
|
192
247
|
|
|
193
|
-
OpsScript Gate is not a security sandbox for untrusted code
|
|
248
|
+
OpsScript Gate is a runtime compatibility testing tool, **not a security sandbox for hostile or fully untrusted code**. Containers still share the host kernel, so target scripts should be treated accordingly.
|
|
249
|
+
|
|
250
|
+
<details>
|
|
251
|
+
<summary><strong>View security boundaries and runtime hardening details</strong></summary>
|
|
194
252
|
|
|
195
253
|
OpsScript Gate applies conservative, restricted container defaults when running scripts:
|
|
196
254
|
|
|
@@ -208,22 +266,38 @@ OpsScript Gate applies conservative, restricted container defaults when running
|
|
|
208
266
|
4. **Anti-Hang Deadlock Defense**:
|
|
209
267
|
- Disables TTY and stdin (`stdin_open=False`, `tty=False`).
|
|
210
268
|
- Disconnects standard input: `/bin/sh -c "... /tmp/target_script.sh </dev/null"`.
|
|
211
|
-
- Injects `DEBIAN_FRONTEND=noninteractive` and `CI=true`. Interactive prompts (`read -p`) fail
|
|
269
|
+
- Injects `DEBIAN_FRONTEND=noninteractive` and `CI=true`. Interactive prompts (`read -p`) fail instead of hanging CI runners.
|
|
212
270
|
5. **Hard Timeout & Container Cleanup**:
|
|
213
271
|
- Enforces configurable timeout (default: 60s). Timed-out containers are sent `SIGKILL` and marked `TIMED_OUT`.
|
|
214
272
|
- Container removal is attempted from a `finally` block in normal, failure, and timeout execution paths.
|
|
215
273
|
6. **Bounded Output & Memory Protection**:
|
|
216
|
-
- Captures container logs using
|
|
274
|
+
- Captures container logs using a rolling byte buffer capped at 256 KiB (`MAX_CAPTURED_LOG_BYTES`) and a 500-line tail limit (`MAX_LOG_TAIL_LINES`) to reduce memory-exhaustion risk from runaway output.
|
|
217
275
|
7. **Untrusted Log Neutralization & Terminal Defense**:
|
|
218
276
|
- Neutralizes line-leading workflow commands (`[container] ::`) to prevent forged GitHub Actions annotations in CI runners.
|
|
219
|
-
- Strips ANSI escape sequences and dangerous C0 control characters, and normalizes carriage returns (`\r`) to defeat terminal line
|
|
220
|
-
- Employs context-sensitive escaping (`escape_inline_code`, `escape_markdown_text`, `escape_markdown_table_cell`, `escape_html_text`, `format_safe_code_fence`) to
|
|
277
|
+
- Strips ANSI escape sequences and dangerous C0 control characters, and normalizes carriage returns (`\r`) to defeat terminal line-overwrite spoofing.
|
|
278
|
+
- Employs context-sensitive escaping (`escape_inline_code`, `escape_markdown_text`, `escape_markdown_table_cell`, `escape_html_text`, `format_safe_code_fence`) to protect Step Summary output contexts.
|
|
221
279
|
8. **Command Injection Defense**:
|
|
222
|
-
- OpsScript Gate's own annotations (`::error`) apply strict percent-encoding for
|
|
280
|
+
- OpsScript Gate's own annotations (`::error`) apply strict percent-encoding for workflow-command fields and message bodies.
|
|
223
281
|
9. **Bounded Streaming CRLF & Shebang Defense**:
|
|
224
|
-
- Stream-normalizes
|
|
282
|
+
- Stream-normalizes CRLF in 64 KiB chunks and bounds shebang parsing to 4096 bytes without whole-file memory allocation.
|
|
225
283
|
- Pre-normalizes scripts once before parallel matrix runs, sharing a read-only prepared path across worker threads.
|
|
226
284
|
|
|
285
|
+
For the complete supported-version policy and vulnerability reporting guidance, see [SECURITY.md](SECURITY.md).
|
|
286
|
+
|
|
287
|
+
</details>
|
|
288
|
+
|
|
289
|
+
### When NOT to use OpsScript Gate
|
|
290
|
+
|
|
291
|
+
OpsScript Gate is intentionally focused on Linux shell runtime compatibility. It is not designed for:
|
|
292
|
+
|
|
293
|
+
- executing hostile or fully untrusted third-party scripts
|
|
294
|
+
- kernel-level or privileged behavior testing
|
|
295
|
+
- replacing full integration or end-to-end test suites
|
|
296
|
+
- validating macOS or Windows behavior
|
|
297
|
+
- proving that a script is secure
|
|
298
|
+
|
|
299
|
+
Use it when you want to know whether a shell script actually runs across the supported Linux distributions.
|
|
300
|
+
|
|
227
301
|
---
|
|
228
302
|
|
|
229
303
|
## 📦 Default Test Matrix
|
|
@@ -276,7 +350,23 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
276
350
|
|
|
277
351
|
---
|
|
278
352
|
|
|
279
|
-
|
|
353
|
+
### v0.5.0 project controls
|
|
354
|
+
|
|
355
|
+
| Option | Purpose |
|
|
356
|
+
| :--- | :--- |
|
|
357
|
+
| `init` | Generate configuration and a GitHub workflow without overwriting files |
|
|
358
|
+
| `--dry-run` | Preview selected scripts, images and total executions without Docker |
|
|
359
|
+
| `--config PATH` | Load a specific JSON configuration file |
|
|
360
|
+
| `--preset minimal` | Debian + Alpine (two container runs per script) |
|
|
361
|
+
| `--preset ubuntu` | Ubuntu 22.04 + 24.04 |
|
|
362
|
+
| `--exclude 'tests/*'` | Exclude a repository-relative glob; repeatable |
|
|
363
|
+
| `--max-scripts 50` | Raise the default discovery limit of 20; overflow is an error |
|
|
364
|
+
| `--output report.json` | Save the selected output format, including failed test reports |
|
|
365
|
+
|
|
366
|
+
Explicit CLI options override project settings. Explicit script paths bypass discovery
|
|
367
|
+
and its exclusions. See [complete configuration semantics](docs/configuration.md).
|
|
368
|
+
|
|
369
|
+
## 🌍 Real-World Usage
|
|
280
370
|
|
|
281
371
|
OpsScript Gate is actively used in [`Mresyzz/linux-dev-bootstrap`](https://github.com/Mresyzz/linux-dev-bootstrap) to validate `install.sh` across the default Debian, Ubuntu, and Alpine matrix in GitHub Actions.
|
|
282
372
|
|
|
@@ -303,6 +393,14 @@ pytest -v
|
|
|
303
393
|
|
|
304
394
|
---
|
|
305
395
|
|
|
396
|
+
## 💛 If OpsScript Gate Helps
|
|
397
|
+
|
|
398
|
+
If OpsScript Gate catches a compatibility problem in one of your scripts, consider starring the repository so other shell and DevOps maintainers can find it too.
|
|
399
|
+
|
|
400
|
+
Bug reports, real-world compatibility cases, and pull requests are especially welcome.
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
306
404
|
## 📄 License
|
|
307
405
|
|
|
308
406
|
OpsScript Gate is licensed under the [MIT License](LICENSE).
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
# OpsScript Gate
|
|
1
|
+
# OpsScript Gate — shell script compatibility testing
|
|
2
|
+
|
|
3
|
+
[简体中文](README.zh-CN.md) · [Configuration](docs/configuration.md) · [Troubleshooting](docs/troubleshooting.md)
|
|
2
4
|
|
|
3
5
|
> **ShellCheck tells you if your script looks portable. OpsScript Gate checks if it actually runs there.**
|
|
4
6
|
|
|
@@ -13,8 +15,32 @@
|
|
|
13
15
|
|
|
14
16
|
**OpsScript Gate** is a drop-in runtime compatibility gate for Linux shell scripts. It executes your scripts inside unprivileged Debian, Ubuntu, and Alpine containers before merge, catching environment-specific runtime failures, missing interpreters, and package-manager assumptions that static analysis cannot detect.
|
|
15
17
|
|
|
18
|
+
It is designed for shell testing, portable shell validation, Bash/POSIX compatibility checks, and cross-distro CI where syntax-only tooling is not enough.
|
|
19
|
+
|
|
16
20
|
---
|
|
17
21
|
|
|
22
|
+
## New in v0.5.0
|
|
23
|
+
|
|
24
|
+
Preview exactly what will run, keep local and CI settings together, and exclude test
|
|
25
|
+
fixtures before executing scripts. These controls are included in v0.5.0. Projects on
|
|
26
|
+
v0.4.1 and earlier do not include them.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
opsscript-gate init
|
|
30
|
+
opsscript-gate run --dry-run
|
|
31
|
+
opsscript-gate run --format json --output reports/compatibility.json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`init` creates `.opsscript-gate.json` and a GitHub Actions workflow without replacing
|
|
35
|
+
existing files. Review its exclusions (`tests/*`, `examples/*`) and offline network
|
|
36
|
+
default before running. No Docker is required for `init` or `--dry-run`.
|
|
37
|
+
|
|
38
|
+
Useful for standalone installers, container entrypoints and release scripts that
|
|
39
|
+
must work on both GNU/Linux and Alpine/BusyBox. Each script runs independently;
|
|
40
|
+
repository files, sibling scripts and project dependencies are **not** mounted.
|
|
41
|
+
See [configuration and migration](docs/configuration.md) and
|
|
42
|
+
[why a shell script works on Ubuntu but fails on Alpine](docs/troubleshooting.md).
|
|
43
|
+
|
|
18
44
|
## ⚡ 30-Second Quickstart
|
|
19
45
|
|
|
20
46
|
### In GitHub Actions (Zero Config)
|
|
@@ -30,15 +56,15 @@ jobs:
|
|
|
30
56
|
runs-on: ubuntu-latest
|
|
31
57
|
steps:
|
|
32
58
|
- uses: actions/checkout@v7
|
|
33
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
59
|
+
- uses: Mresyzz/opsscript-gate@v0.5.0
|
|
34
60
|
```
|
|
35
61
|
|
|
36
|
-
> **Zero Config**: If `script-path` is omitted, OpsScript Gate automatically discovers shell scripts in your repository
|
|
62
|
+
> **Zero Config**: If `script-path` is omitted, OpsScript Gate automatically discovers shell scripts in your repository. Scripts run sequentially, with concurrent distribution checks for each script. Preview discovery before running unfamiliar repositories.
|
|
37
63
|
|
|
38
64
|
Or test a specific script with custom execution modes:
|
|
39
65
|
|
|
40
66
|
```yaml
|
|
41
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
67
|
+
- uses: Mresyzz/opsscript-gate@v0.5.0
|
|
42
68
|
with:
|
|
43
69
|
script-path: scripts/install.sh
|
|
44
70
|
shell: auto
|
|
@@ -60,6 +86,35 @@ opsscript-gate run ./scripts/install.sh
|
|
|
60
86
|
opsscript-gate run
|
|
61
87
|
```
|
|
62
88
|
|
|
89
|
+
### Try a Real Failure in 30 Seconds
|
|
90
|
+
|
|
91
|
+
Create `install.sh`:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
#!/bin/sh
|
|
95
|
+
set -e
|
|
96
|
+
apt-get --version
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Then run:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
opsscript-gate run ./install.sh
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
A typical result across the default matrix looks like this:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
Debian 12 PASS
|
|
109
|
+
Ubuntu 22.04 PASS
|
|
110
|
+
Ubuntu 24.04 PASS
|
|
111
|
+
Alpine 3.20 FAIL
|
|
112
|
+
|
|
113
|
+
apt-get: not found
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The script is valid shell, but the runtime environment is incompatible. That is exactly the class of failure OpsScript Gate is designed to catch.
|
|
117
|
+
|
|
63
118
|
---
|
|
64
119
|
|
|
65
120
|
## 🎯 Reviewer-First Experience: What It Produces
|
|
@@ -162,7 +217,10 @@ sh: line 4: apt-get: not found
|
|
|
162
217
|
|
|
163
218
|
## 🔒 Security Boundaries & Hardened Isolation
|
|
164
219
|
|
|
165
|
-
OpsScript Gate is not a security sandbox for untrusted code
|
|
220
|
+
OpsScript Gate is a runtime compatibility testing tool, **not a security sandbox for hostile or fully untrusted code**. Containers still share the host kernel, so target scripts should be treated accordingly.
|
|
221
|
+
|
|
222
|
+
<details>
|
|
223
|
+
<summary><strong>View security boundaries and runtime hardening details</strong></summary>
|
|
166
224
|
|
|
167
225
|
OpsScript Gate applies conservative, restricted container defaults when running scripts:
|
|
168
226
|
|
|
@@ -180,22 +238,38 @@ OpsScript Gate applies conservative, restricted container defaults when running
|
|
|
180
238
|
4. **Anti-Hang Deadlock Defense**:
|
|
181
239
|
- Disables TTY and stdin (`stdin_open=False`, `tty=False`).
|
|
182
240
|
- Disconnects standard input: `/bin/sh -c "... /tmp/target_script.sh </dev/null"`.
|
|
183
|
-
- Injects `DEBIAN_FRONTEND=noninteractive` and `CI=true`. Interactive prompts (`read -p`) fail
|
|
241
|
+
- Injects `DEBIAN_FRONTEND=noninteractive` and `CI=true`. Interactive prompts (`read -p`) fail instead of hanging CI runners.
|
|
184
242
|
5. **Hard Timeout & Container Cleanup**:
|
|
185
243
|
- Enforces configurable timeout (default: 60s). Timed-out containers are sent `SIGKILL` and marked `TIMED_OUT`.
|
|
186
244
|
- Container removal is attempted from a `finally` block in normal, failure, and timeout execution paths.
|
|
187
245
|
6. **Bounded Output & Memory Protection**:
|
|
188
|
-
- Captures container logs using
|
|
246
|
+
- Captures container logs using a rolling byte buffer capped at 256 KiB (`MAX_CAPTURED_LOG_BYTES`) and a 500-line tail limit (`MAX_LOG_TAIL_LINES`) to reduce memory-exhaustion risk from runaway output.
|
|
189
247
|
7. **Untrusted Log Neutralization & Terminal Defense**:
|
|
190
248
|
- Neutralizes line-leading workflow commands (`[container] ::`) to prevent forged GitHub Actions annotations in CI runners.
|
|
191
|
-
- Strips ANSI escape sequences and dangerous C0 control characters, and normalizes carriage returns (`\r`) to defeat terminal line
|
|
192
|
-
- Employs context-sensitive escaping (`escape_inline_code`, `escape_markdown_text`, `escape_markdown_table_cell`, `escape_html_text`, `format_safe_code_fence`) to
|
|
249
|
+
- Strips ANSI escape sequences and dangerous C0 control characters, and normalizes carriage returns (`\r`) to defeat terminal line-overwrite spoofing.
|
|
250
|
+
- Employs context-sensitive escaping (`escape_inline_code`, `escape_markdown_text`, `escape_markdown_table_cell`, `escape_html_text`, `format_safe_code_fence`) to protect Step Summary output contexts.
|
|
193
251
|
8. **Command Injection Defense**:
|
|
194
|
-
- OpsScript Gate's own annotations (`::error`) apply strict percent-encoding for
|
|
252
|
+
- OpsScript Gate's own annotations (`::error`) apply strict percent-encoding for workflow-command fields and message bodies.
|
|
195
253
|
9. **Bounded Streaming CRLF & Shebang Defense**:
|
|
196
|
-
- Stream-normalizes
|
|
254
|
+
- Stream-normalizes CRLF in 64 KiB chunks and bounds shebang parsing to 4096 bytes without whole-file memory allocation.
|
|
197
255
|
- Pre-normalizes scripts once before parallel matrix runs, sharing a read-only prepared path across worker threads.
|
|
198
256
|
|
|
257
|
+
For the complete supported-version policy and vulnerability reporting guidance, see [SECURITY.md](SECURITY.md).
|
|
258
|
+
|
|
259
|
+
</details>
|
|
260
|
+
|
|
261
|
+
### When NOT to use OpsScript Gate
|
|
262
|
+
|
|
263
|
+
OpsScript Gate is intentionally focused on Linux shell runtime compatibility. It is not designed for:
|
|
264
|
+
|
|
265
|
+
- executing hostile or fully untrusted third-party scripts
|
|
266
|
+
- kernel-level or privileged behavior testing
|
|
267
|
+
- replacing full integration or end-to-end test suites
|
|
268
|
+
- validating macOS or Windows behavior
|
|
269
|
+
- proving that a script is secure
|
|
270
|
+
|
|
271
|
+
Use it when you want to know whether a shell script actually runs across the supported Linux distributions.
|
|
272
|
+
|
|
199
273
|
---
|
|
200
274
|
|
|
201
275
|
## 📦 Default Test Matrix
|
|
@@ -248,7 +322,23 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
248
322
|
|
|
249
323
|
---
|
|
250
324
|
|
|
251
|
-
|
|
325
|
+
### v0.5.0 project controls
|
|
326
|
+
|
|
327
|
+
| Option | Purpose |
|
|
328
|
+
| :--- | :--- |
|
|
329
|
+
| `init` | Generate configuration and a GitHub workflow without overwriting files |
|
|
330
|
+
| `--dry-run` | Preview selected scripts, images and total executions without Docker |
|
|
331
|
+
| `--config PATH` | Load a specific JSON configuration file |
|
|
332
|
+
| `--preset minimal` | Debian + Alpine (two container runs per script) |
|
|
333
|
+
| `--preset ubuntu` | Ubuntu 22.04 + 24.04 |
|
|
334
|
+
| `--exclude 'tests/*'` | Exclude a repository-relative glob; repeatable |
|
|
335
|
+
| `--max-scripts 50` | Raise the default discovery limit of 20; overflow is an error |
|
|
336
|
+
| `--output report.json` | Save the selected output format, including failed test reports |
|
|
337
|
+
|
|
338
|
+
Explicit CLI options override project settings. Explicit script paths bypass discovery
|
|
339
|
+
and its exclusions. See [complete configuration semantics](docs/configuration.md).
|
|
340
|
+
|
|
341
|
+
## 🌍 Real-World Usage
|
|
252
342
|
|
|
253
343
|
OpsScript Gate is actively used in [`Mresyzz/linux-dev-bootstrap`](https://github.com/Mresyzz/linux-dev-bootstrap) to validate `install.sh` across the default Debian, Ubuntu, and Alpine matrix in GitHub Actions.
|
|
254
344
|
|
|
@@ -275,6 +365,14 @@ pytest -v
|
|
|
275
365
|
|
|
276
366
|
---
|
|
277
367
|
|
|
368
|
+
## 💛 If OpsScript Gate Helps
|
|
369
|
+
|
|
370
|
+
If OpsScript Gate catches a compatibility problem in one of your scripts, consider starring the repository so other shell and DevOps maintainers can find it too.
|
|
371
|
+
|
|
372
|
+
Bug reports, real-world compatibility cases, and pull requests are especially welcome.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
278
376
|
## 📄 License
|
|
279
377
|
|
|
280
378
|
OpsScript Gate is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# OpsScript Gate:Shell 脚本跨发行版运行检查
|
|
2
|
+
|
|
3
|
+
[English](README.md) · [配置说明](docs/configuration.md) · [常见问题](docs/troubleshooting.md)
|
|
4
|
+
|
|
5
|
+
在 Debian、Ubuntu、Alpine 容器里实际运行 Shell 脚本,提前发现缺失命令、
|
|
6
|
+
Bash 依赖、包管理器假设和交互式阻塞。它与 ShellCheck 互补。
|
|
7
|
+
|
|
8
|
+
适合独立安装脚本、容器入口脚本和发布脚本。每次只挂载目标脚本,
|
|
9
|
+
不挂载整个仓库;依赖其他仓库文件的脚本需要现有项目测试配合。
|
|
10
|
+
|
|
11
|
+
## 试用本次 v0.5.0 更新
|
|
12
|
+
|
|
13
|
+
v0.5.0 已作为正式版本发布;如果你要从源码验证当前分支,也可以执行:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install -e .
|
|
17
|
+
opsscript-gate run examples/basic/clean_setup.sh --dry-run
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
在你希望接入检查的仓库根目录执行:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
opsscript-gate init
|
|
24
|
+
opsscript-gate run --dry-run
|
|
25
|
+
opsscript-gate run --format json --output reports/compatibility.json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`init` 生成配置和 GitHub 工作流,不覆盖已有文件。工作流引用 `v0.5.0`。
|
|
29
|
+
预览不需要 Docker;真实运行需要 Python 3.10+
|
|
30
|
+
和可访问的 Linux Docker 引擎。
|
|
31
|
+
|
|
32
|
+
生成的配置默认排除 `tests/*`、`examples/*`,使用 Debian + Alpine,尊重
|
|
33
|
+
脚本 shebang,并关闭网络。请根据实际脚本审核配置;需要下载文件时启用
|
|
34
|
+
`--network bridge`。保留现有无配置行为:四个发行版、POSIX shell、bridge 网络。
|
|
35
|
+
|
|
36
|
+
## 本轮改进
|
|
37
|
+
|
|
38
|
+
- 运行前展示脚本、镜像和总容器执行次数。
|
|
39
|
+
- 本地与 GitHub Actions 共用 `.opsscript-gate.json`。
|
|
40
|
+
- 排除规则、发行版预设、扫描数量限制。
|
|
41
|
+
- 超过扫描上限明确报错,避免只测前 20 个却误以为全部通过。
|
|
42
|
+
- 报告保存为 JSON、Markdown 或文本,失败时同样保留结果。
|
|
43
|
+
- 自动发现跳过符号链接,并严格校验配置与执行参数。
|
|
44
|
+
|
|
45
|
+
返回码 `0` 表示全部通过,`1` 表示检查失败或发生错误。`--dry-run` 成功只表示
|
|
46
|
+
执行计划生成成功。容器不是不可信代码的安全沙箱。
|
|
47
|
+
|
|
48
|
+
## 示例:为什么 Alpine 上会失败
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
#!/bin/sh
|
|
52
|
+
set -e
|
|
53
|
+
apt-get --version
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
这个脚本可以符合 POSIX 语法,但 Alpine 使用 `apk`,实际执行会暴露命令缺失。
|
|
57
|
+
查看 [Ubuntu/Alpine 排错说明](docs/troubleshooting.md) 获取常见原因与修复方向。
|
|
58
|
+
|
|
59
|
+
## 测试
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install -e '.[test]'
|
|
63
|
+
pytest -q -m 'not integration'
|
|
64
|
+
pytest -q -m integration
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
第二组需要 Docker。跳过容器测试不能算作完整集成验证。
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
name: 'OpsScript Gate'
|
|
2
|
+
description: 'Run Linux shell scripts across Debian, Ubuntu and Alpine before release to catch runtime compatibility failures'
|
|
3
|
+
author: 'Mresyzz'
|
|
4
|
+
branding:
|
|
5
|
+
icon: 'shield'
|
|
6
|
+
color: 'blue'
|
|
7
|
+
|
|
8
|
+
inputs:
|
|
9
|
+
script-path:
|
|
10
|
+
description: 'Path to the target shell script to test (auto-discovers repository scripts if omitted)'
|
|
11
|
+
required: false
|
|
12
|
+
default: ''
|
|
13
|
+
matrix:
|
|
14
|
+
description: 'Comma-separated container images to test (e.g. debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20)'
|
|
15
|
+
required: false
|
|
16
|
+
default: ''
|
|
17
|
+
jobs:
|
|
18
|
+
description: 'Number of parallel container execution jobs (default: min(2, matrix_size))'
|
|
19
|
+
required: false
|
|
20
|
+
default: ''
|
|
21
|
+
timeout:
|
|
22
|
+
description: 'Hard timeout in seconds per container (default: 60)'
|
|
23
|
+
required: false
|
|
24
|
+
default: ''
|
|
25
|
+
format:
|
|
26
|
+
description: 'Output report format: table, markdown, or json'
|
|
27
|
+
required: false
|
|
28
|
+
default: ''
|
|
29
|
+
shell:
|
|
30
|
+
description: "Shell execution mode: 'posix' (default), 'shebang', or 'auto'"
|
|
31
|
+
required: false
|
|
32
|
+
default: ''
|
|
33
|
+
mem-limit:
|
|
34
|
+
description: 'Memory limit for containers (default: 256m)'
|
|
35
|
+
required: false
|
|
36
|
+
default: ''
|
|
37
|
+
pids-limit:
|
|
38
|
+
description: 'Maximum number of processes per container (default: 128)'
|
|
39
|
+
required: false
|
|
40
|
+
default: ''
|
|
41
|
+
network:
|
|
42
|
+
description: "Container network mode: 'bridge' or 'none' (default: bridge)"
|
|
43
|
+
required: false
|
|
44
|
+
default: ''
|
|
45
|
+
|
|
46
|
+
config:
|
|
47
|
+
description: 'Path to project JSON configuration'
|
|
48
|
+
required: false
|
|
49
|
+
default: ''
|
|
50
|
+
preset:
|
|
51
|
+
description: 'Distribution preset: minimal or ubuntu'
|
|
52
|
+
required: false
|
|
53
|
+
default: ''
|
|
54
|
+
exclude:
|
|
55
|
+
description: 'Newline-separated repository-relative exclusion globs'
|
|
56
|
+
required: false
|
|
57
|
+
default: ''
|
|
58
|
+
max-scripts:
|
|
59
|
+
description: 'Maximum discovered scripts; exceeding the limit fails'
|
|
60
|
+
required: false
|
|
61
|
+
default: ''
|
|
62
|
+
dry-run:
|
|
63
|
+
description: 'Preview the execution plan without Docker'
|
|
64
|
+
required: false
|
|
65
|
+
default: 'false'
|
|
66
|
+
output:
|
|
67
|
+
description: 'Save a report file for upload-artifact or other tooling'
|
|
68
|
+
required: false
|
|
69
|
+
default: ''
|
|
70
|
+
|
|
71
|
+
runs:
|
|
72
|
+
using: 'composite'
|
|
73
|
+
steps:
|
|
74
|
+
- name: Set up Python
|
|
75
|
+
uses: actions/setup-python@v7
|
|
76
|
+
with:
|
|
77
|
+
python-version: '3.12'
|
|
78
|
+
|
|
79
|
+
- name: Install opsscript-gate
|
|
80
|
+
shell: bash
|
|
81
|
+
run: |
|
|
82
|
+
pip install "${{ github.action_path }}"
|
|
83
|
+
|
|
84
|
+
- name: Run compatibility gate
|
|
85
|
+
shell: bash
|
|
86
|
+
env:
|
|
87
|
+
INPUT_SCRIPT_PATH: ${{ inputs.script-path }}
|
|
88
|
+
INPUT_MATRIX: ${{ inputs.matrix }}
|
|
89
|
+
INPUT_JOBS: ${{ inputs.jobs }}
|
|
90
|
+
INPUT_TIMEOUT: ${{ inputs.timeout }}
|
|
91
|
+
INPUT_FORMAT: ${{ inputs.format }}
|
|
92
|
+
INPUT_SHELL: ${{ inputs.shell }}
|
|
93
|
+
INPUT_MEM_LIMIT: ${{ inputs.mem-limit }}
|
|
94
|
+
INPUT_PIDS_LIMIT: ${{ inputs.pids-limit }}
|
|
95
|
+
INPUT_NETWORK: ${{ inputs.network }}
|
|
96
|
+
INPUT_CONFIG: ${{ inputs.config }}
|
|
97
|
+
INPUT_PRESET: ${{ inputs.preset }}
|
|
98
|
+
INPUT_EXCLUDE: ${{ inputs.exclude }}
|
|
99
|
+
INPUT_MAX_SCRIPTS: ${{ inputs.max-scripts }}
|
|
100
|
+
INPUT_DRY_RUN: ${{ inputs.dry-run }}
|
|
101
|
+
INPUT_OUTPUT: ${{ inputs.output }}
|
|
102
|
+
run: |
|
|
103
|
+
ARGS=("run")
|
|
104
|
+
if [ -n "$INPUT_SCRIPT_PATH" ]; then
|
|
105
|
+
ARGS+=("$INPUT_SCRIPT_PATH")
|
|
106
|
+
fi
|
|
107
|
+
# Only explicit inputs override project configuration.
|
|
108
|
+
[ -z "$INPUT_TIMEOUT" ] || ARGS+=(--timeout "$INPUT_TIMEOUT")
|
|
109
|
+
[ -z "$INPUT_FORMAT" ] || ARGS+=(--format "$INPUT_FORMAT")
|
|
110
|
+
[ -z "$INPUT_SHELL" ] || ARGS+=(--shell "$INPUT_SHELL")
|
|
111
|
+
[ -z "$INPUT_MEM_LIMIT" ] || ARGS+=(--mem-limit "$INPUT_MEM_LIMIT")
|
|
112
|
+
[ -z "$INPUT_PIDS_LIMIT" ] || ARGS+=(--pids-limit "$INPUT_PIDS_LIMIT")
|
|
113
|
+
[ -z "$INPUT_NETWORK" ] || ARGS+=(--network "$INPUT_NETWORK")
|
|
114
|
+
[ -z "$INPUT_MATRIX" ] || ARGS+=(--matrix "$INPUT_MATRIX")
|
|
115
|
+
[ -z "$INPUT_JOBS" ] || ARGS+=(--jobs "$INPUT_JOBS")
|
|
116
|
+
[ -z "$INPUT_CONFIG" ] || ARGS+=(--config "$INPUT_CONFIG")
|
|
117
|
+
[ -z "$INPUT_PRESET" ] || ARGS+=(--preset "$INPUT_PRESET")
|
|
118
|
+
[ -z "$INPUT_MAX_SCRIPTS" ] || ARGS+=(--max-scripts "$INPUT_MAX_SCRIPTS")
|
|
119
|
+
[ -z "$INPUT_OUTPUT" ] || ARGS+=(--output "$INPUT_OUTPUT")
|
|
120
|
+
while IFS= read -r pattern; do
|
|
121
|
+
pattern="${pattern%$'\r'}"
|
|
122
|
+
[ -z "$pattern" ] || ARGS+=("--exclude" "$pattern")
|
|
123
|
+
done <<< "$INPUT_EXCLUDE"
|
|
124
|
+
if [ "$INPUT_DRY_RUN" = "true" ]; then
|
|
125
|
+
ARGS+=("--dry-run")
|
|
126
|
+
elif [ "$INPUT_DRY_RUN" != "false" ]; then
|
|
127
|
+
echo "dry-run must be true or false" >&2
|
|
128
|
+
exit 1
|
|
129
|
+
fi
|
|
130
|
+
python -m opsscript_gate.cli "${ARGS[@]}"
|