opsscript-gate 0.9.0__tar.gz → 0.9.2__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.
Files changed (60) hide show
  1. opsscript_gate-0.9.2/.ai/v0.9.2_issues_report.md +90 -0
  2. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.github/workflow-templates/opsscript-gate.yml +1 -1
  3. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.github/workflows/release.yml +3 -8
  4. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/CHANGELOG.md +30 -0
  5. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/CITATION.cff +1 -2
  6. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/PKG-INFO +16 -13
  7. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/README.md +12 -10
  8. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/README.zh-CN.md +4 -4
  9. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/action.yml +7 -6
  10. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/demo.html +4 -4
  11. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/docs/command-not-found.html +70 -1
  12. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/docs/command-not-found.md +25 -1
  13. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/docs/configuration.md +7 -4
  14. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/docs/gpt-oss.md +1 -1
  15. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/docs/index.html +20 -4
  16. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/docs/llms.txt +8 -1
  17. opsscript_gate-0.9.2/docs/robots.txt +3 -0
  18. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/examples/github-actions/README.md +1 -1
  19. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/examples/github-actions/workflow.yml +1 -1
  20. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/index.html +20 -4
  21. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/llms.txt +8 -1
  22. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/pyproject.toml +6 -2
  23. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/src/opsscript_gate/__init__.py +1 -1
  24. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/src/opsscript_gate/cli.py +74 -15
  25. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/src/opsscript_gate/config.py +39 -3
  26. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/src/opsscript_gate/discovery.py +37 -3
  27. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/src/opsscript_gate/models.py +20 -8
  28. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/src/opsscript_gate/remediation.py +59 -2
  29. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/src/opsscript_gate/reporter.py +98 -51
  30. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/src/opsscript_gate/runner.py +270 -55
  31. opsscript_gate-0.9.2/tests/test_action_contract.py +45 -0
  32. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/tests/test_onboarding.py +91 -1
  33. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/tests/test_runner.py +436 -6
  34. opsscript_gate-0.9.0/tests/test_action_contract.py +0 -24
  35. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.gitattributes +0 -0
  36. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  37. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.github/ISSUE_TEMPLATE/distro_request.yml +0 -0
  38. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  39. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.github/assets/social-preview.png +0 -0
  40. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.github/pull_request_template.md +0 -0
  41. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.github/workflow-templates/opsscript-gate.properties.json +0 -0
  42. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.github/workflows/demo.yml +0 -0
  43. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.github/workflows/deploy-pages.yml +0 -0
  44. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.github/workflows/test.yml +0 -0
  45. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/.gitignore +0 -0
  46. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/CONTRIBUTING.md +0 -0
  47. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/LICENSE +0 -0
  48. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/ROADMAP.md +0 -0
  49. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/SECURITY.md +0 -0
  50. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/docs/sitemap.xml +0 -0
  51. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/docs/troubleshooting.md +0 -0
  52. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/examples/alpine-incompatibility/README.md +0 -0
  53. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/examples/alpine-incompatibility/apt_dependency.sh +0 -0
  54. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/examples/basic/README.md +0 -0
  55. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/examples/basic/clean_setup.sh +0 -0
  56. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/examples/interactive-hang/README.md +0 -0
  57. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/examples/interactive-hang/prompt_confirm.sh +0 -0
  58. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/tests/fixtures/fail_deps.sh +0 -0
  59. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/tests/fixtures/fail_hang.sh +0 -0
  60. {opsscript_gate-0.9.0 → opsscript_gate-0.9.2}/tests/fixtures/pass_basic.sh +0 -0
@@ -0,0 +1,90 @@
1
+ # OpsScript Gate 本地 v0.9.2 代码审查、缺陷排查与修复报告
2
+
3
+ **审查与修复时间**:2026-10-05
4
+ **基准分支**:`codex/v0.9.2-hardening`
5
+ **测试套件状态**:`pytest` 177 项测试全量通过(165 passed, 12 skipped, 0 failures)
6
+
7
+ ---
8
+
9
+ ## 概述
10
+
11
+ 针对本地 `0.9.2` 版本在近期提交(`6260a74` 到 `cae3fbe`)及后续排查中发现的所有缺陷,进行了地毯式分析与代码修复。所有发现的问题均已完成第一性原理剖析、修复并配齐自动化回归测试。
12
+
13
+ ---
14
+
15
+ ## 本轮新发现的问题与修复记录
16
+
17
+ ### 1. `discovery.py` 中 `_is_ignored_path` 误将前导 `./` 路径判定为忽略项
18
+ * **文件位置**:`src/opsscript_gate/discovery.py`
19
+ * **问题成因**:
20
+ `_is_ignored_path` 将路径按 `/` 切分后遍历每个 `part`,直接使用 `part.startswith(".")`。当路径是以 `./` 开头(例如 `./scripts/install.sh` 或 `./run.sh`)时,首个 `part` 为单个点 `.`,`'.'.startswith(".")` 恒为 `True`,导致合法脚本被错误判定为忽略项。
21
+ * **修复方案**:
22
+ 在切分前先剥离前导 `./`,并将 `part in (".", "..")` 显式排除在隐藏目录判断之外。
23
+ * **测试用例**:
24
+ 新增 `tests/test_runner.py::test_is_ignored_path_dot_slash_and_hidden`。
25
+
26
+ ---
27
+
28
+ ### 2. `runner.py` 中 `_is_wait_timeout_error` 无法识别嵌套异常原因链
29
+ * **文件位置**:`src/opsscript_gate/runner.py`
30
+ * **问题成因**:
31
+ Docker SDK (urllib3 / requests) 发生超时异常时,常将底层的 `ReadTimeout` 或 `SocketTimeout` 包装在 `requests.exceptions.ConnectionError` 或顶层异常中。原函数只检查了外层异常类名,导致被包装的真实超时被误报为通信崩溃(`DistroStatus.ERROR`),且未能触发超时强杀逻辑。
32
+ * **修复方案**:
33
+ 在 `_is_wait_timeout_error` 中遍历 `__cause__` 与 `__context__` 异常链,全面识别深层超时错误。
34
+ * **测试用例**:
35
+ 新增 `tests/test_runner.py::test_is_wait_timeout_error_cause_chain`。
36
+
37
+ ---
38
+
39
+ ### 3. `cli.py` 中 `--output` 指定已有目录时缺乏前置拦截
40
+ * **文件位置**:`src/opsscript_gate/cli.py`
41
+ * **问题成因**:
42
+ 若用户误将已存在的目录传给 `--output`(例如 `--output reports/` 或 `--output .`),原逻辑在跑完所有耗时的容器矩阵测试后,才在 `publish_output` 中因 `write_text` 抛出 `IsADirectoryError` 或 `PermissionError`,严重浪费时间。
43
+ * **修复方案**:
44
+ 在执行矩阵前增加 `if output_path.is_dir(): sys.stderr.write("Error: output must name a file, not an existing directory\n"); return 1` 提前拦截。
45
+ * **测试用例**:
46
+ 新增 `tests/test_runner.py::test_cli_output_rejects_existing_directory_early`。
47
+
48
+ ---
49
+
50
+ ### 4. `remediation.py` 扩充跨发行版包管理器互踩诊断规则
51
+ * **文件位置**:`src/opsscript_gate/remediation.py`
52
+ * **问题成因**:
53
+ 此前仅包含 Debian/Ubuntu(apt)与 Alpine(apk)的双向匹配。当脚本在 Debian/Ubuntu/Alpine 上尝试调用 `yum` 或 `dnf`,或者在 Fedora/CentOS/RHEL 系列镜像上调用 `apt`/`apk` 时,系统只能报通用缺失错误,缺乏精准指引。
54
+ * **修复方案**:
55
+ 新增 `_match_debian_ubuntu_yum`、`_match_alpine_yum`、`_match_rhel_fedora_apt` 匹配器及专属修复建议。
56
+ * **测试用例**:
57
+ 新增 `tests/test_runner.py::test_remediation_enterprise_distro_package_rules`。
58
+
59
+ ---
60
+
61
+ ### 5. `runner.py` 中保护 `container.reload()` 避免网络抖动覆盖已取得的状态码
62
+ * **文件位置**:`src/opsscript_gate/runner.py`
63
+ * **问题成因**:
64
+ 在容器执行完毕后,`container.wait()` 已返回了包含 `StatusCode` 的字典,但随后的 `container.reload()` 调用缺乏异常防护。一旦容器刚好在退出瞬间发生通信抖动,会导致原本已成功完成的测试异常崩溃。
65
+ * **修复方案**:
66
+ 为 `container.reload()` 增加 `try-except` 防护,优先并安全地使用已拿到的有效退出码。
67
+
68
+ ---
69
+
70
+ ### 6. `remediation.py` 中 `curl`/`wget` 绝对路径匹配与最小容器缺失 `sudo` 修复规则
71
+ * **文件位置**:`src/opsscript_gate/remediation.py`
72
+ * **问题成因**:
73
+ 1. `_match_curl` 与 `_match_wget` 仅检查了命令名完全等于 `curl` / `wget`,当脚本使用 `/usr/bin/curl`、`/bin/curl` 等绝对路径调用时,无法匹配到建议规则。
74
+ 2. 最小化 Linux 容器镜像(Debian slim、Ubuntu、Alpine 等)默认不带 `sudo`,且容器通常本身即为 root 权限。当运维脚本带有 `sudo` 时会报 `sudo: not found`,此前缺乏专属修复建议。
75
+ * **修复方案**:
76
+ 提取命令路径中的 basename 统一进行匹配,并新增 `_match_sudo` 规则与针对性修复建议。
77
+ * **测试用例**:
78
+ 新增 `tests/test_runner.py::test_remediation_curl_wget_path_and_sudo`。
79
+
80
+ ---
81
+
82
+ ## 历史排查问题的闭环确认(已在近期提交中合入)
83
+
84
+ 1. **`reporter.py` 中 `_sarif_path` 绝对路径转换**:已在 Commit `6a830e4` 中引入 `os.path.relpath` 与防止 `../` 逃逸的处理。
85
+ 2. **`discovery.py` 中 `discover_scripts` 过滤隐藏文件**:已在 Commit `9618874` 中过滤了 `filename.startswith(".")`。
86
+ 3. **`action.yml` 中 `dry-run` 大小写敏感**:已在 Commit `03b30ab` 中使用 `tr '[:upper:]' '[:lower:]'` 规整。
87
+ 4. **`cli.py` 中 `publish_output` 写文件失败吞没 stdout**:已在 Commit `03b30ab` 中调整为先打印控制台输出再写文件。
88
+ 5. **`runner.py` 中多轮执行代表性结果选择**:已在 Commit `6a830e4` 中优化为优先保留具备 `diagnostic` 的结果。
89
+ 6. **Docker 引擎模式检查**:已在 Commit `0b8a202` 中通过 `client.info()` 校验 `OSType == "linux"`。
90
+ 7. **资源上限守卫**:已在 Commit `cae3fbe`, `13f0730`, `373d5a0` 中为 `matrix`(最多32个)、`timeout`(最多3600秒)、`repeat`(最多20次)增加上限校验。
@@ -25,7 +25,7 @@ jobs:
25
25
  persist-credentials: false
26
26
 
27
27
  - name: Run OpsScript Gate
28
- uses: Mresyzz/opsscript-gate@v0.8.3
28
+ uses: Mresyzz/opsscript-gate@v0.9.2
29
29
  with:
30
30
  shell: auto
31
31
  network: none
@@ -123,17 +123,12 @@ jobs:
123
123
  TAG_SHA=$(git rev-list -n 1 "$TARGET_TAG")
124
124
  echo "Tag $TARGET_TAG already exists, pointing to commit: $TAG_SHA"
125
125
  if [ "$TAG_SHA" = "${{ github.sha }}" ]; then
126
- echo "Tag $TARGET_TAG points to current commit (${{ github.sha }}). Treating as retry/resume of release."
127
- IS_NEW_RELEASE="true"
128
- SHOULD_TAG="false"
126
+ echo "Tag $TARGET_TAG already points to the current commit. The tag workflow owns release publication."
129
127
  else
130
128
  echo "Tag $TARGET_TAG was released from earlier commit ($TAG_SHA != ${{ github.sha }}). Skipping release."
131
- IS_NEW_RELEASE="false"
132
- SHOULD_TAG="false"
133
129
  fi
134
130
  else
135
- echo "Target tag $TARGET_TAG does not exist. Scheduling new release for commit ${{ github.sha }}."
136
- IS_NEW_RELEASE="true"
131
+ echo "Target tag $TARGET_TAG does not exist. Creating it and handing publication to the tag workflow."
137
132
  SHOULD_TAG="true"
138
133
  fi
139
134
  else
@@ -171,7 +166,7 @@ jobs:
171
166
  retention-days: 7
172
167
 
173
168
  - name: Create and push Git tag
174
- if: steps.release-check.outputs.is_new_release == 'true' && steps.release-check.outputs.should_tag == 'true'
169
+ if: steps.release-check.outputs.should_tag == 'true'
175
170
  shell: bash
176
171
  run: |
177
172
  TARGET_TAG="${{ steps.release-check.outputs.release_tag }}"
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.2] - Unreleased
4
+
5
+ ### Fixed
6
+ - Hardened SARIF, Markdown, terminal, and machine-readable output handling.
7
+ - Prevented false package-setup errors, stale Docker wait results, hard-link overwrites, and unbounded log copies.
8
+ - Added BOM-aware discovery, Windows exclusion normalization, bounded concurrency, and runtime option validation.
9
+ - Added a one-CPU container limit and bounded Docker log rotation, while keeping disposable container filesystems writable for installers.
10
+ - Normalized SARIF paths, preserved actionable repeat-run diagnostics, skipped hidden auto-discovered files, and kept terminal reports visible when file output fails.
11
+ - Fixed release workflow tag handoff so a `main` push cannot publish the same version twice.
12
+ - Made `doctor` reject Docker's Windows-container mode before Linux image checks begin.
13
+ - Added problem-first FAQ content, structured data, canonical URLs, and AI-readable answers for runtime compatibility searches.
14
+ - Bounded repeat runs to 20 attempts per distribution to prevent accidental unbounded container work and result retention.
15
+ - Bounded per-container timeouts to one hour so fallback polling cannot wait indefinitely on an extreme configuration.
16
+ - Bounded custom matrices to 32 images so a single configuration cannot create unbounded container work.
17
+ - Fixed `_is_ignored_path` in script discovery to avoid misclassifying paths with leading `./` as ignored.
18
+ - Traversed exception cause chains in `_is_wait_timeout_error` to reliably identify nested Docker wait timeouts.
19
+ - Protected container reload against network jitter to preserve valid exit codes.
20
+ - Added early validation for CLI `--output` to reject existing directories before executing test matrices.
21
+ - Expanded cross-distro package manager diagnostics and added remediation for path-qualified curl/wget and missing sudo.
22
+ - Refreshed active workflow templates, examples, and documentation to `v0.9.2`.
23
+
24
+ ## [0.9.1] - 2026-10-03
25
+
26
+ ### Fixed
27
+ - Normalized discovery exclusions so top-level paths match with or without `./`.
28
+ - Made SARIF output conform to the 2.1.0 result schema.
29
+ - Failed closed when Docker omitted an exit code and separated package setup errors from script failures.
30
+ - Validated project configuration in `doctor` and handled UTF-8 BOM scripts.
31
+ - Distinguished Docker wait API errors from genuine execution timeouts.
32
+
3
33
  ## [0.9.0] - 2026-10-03
4
34
 
5
35
  ### Added
@@ -5,8 +5,7 @@ type: software
5
5
  authors:
6
6
  - family-names: Mresyzz
7
7
  given-names: Mresy
8
- version: 0.9.0
9
- date-released: 2026-10-02
8
+ version: 0.9.2
10
9
  repository-code: https://github.com/Mresyzz/opsscript-gate
11
10
  url: https://github.com/Mresyzz/opsscript-gate
12
11
  license: MIT
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: opsscript-gate
3
- Version: 0.9.0
4
- Summary: Cross-distro shell script compatibility checker for runtime testing in Debian, Ubuntu and Alpine
3
+ Version: 0.9.2
4
+ Summary: Cross-distro shell script compatibility and portability checker for Debian, Ubuntu, and Alpine. Runs real containers to diagnose command not found.
5
5
  Project-URL: Homepage, https://mresyzz.github.io/opsscript-gate/
6
6
  Project-URL: Demo, https://mresyzz.github.io/opsscript-gate/
7
7
  Project-URL: Repository, https://github.com/Mresyzz/opsscript-gate
@@ -12,7 +12,7 @@ Project-URL: Troubleshooting, https://github.com/Mresyzz/opsscript-gate/blob/mai
12
12
  Author: Mresyzz
13
13
  License: MIT
14
14
  License-File: LICENSE
15
- Keywords: alpine,apk,apt-get,bashisms,bootstrap,busybox,ci,command-not-found,compatibility,cross-distro,debian,devops,docker,github-actions,installer,installer-testing,linux,portable-shell,posix,runtime-compatibility,shell,shell-portability,shell-script-testing,shellcheck,shellcheck-alternative,testing,ubuntu
15
+ Keywords: alpine,apk,apt-get,bashisms,bootstrap,busybox,ci,command-not-found,compatibility,cross-distro,debian,devops,distribution-compatibility,docker,github-actions,installer,installer-testing,linux,portable-shell,posix,real-container-testing,runtime-compatibility,shell,shell-portability,shell-runtime,shell-script-testing,shellcheck,shellcheck-alternative,testing,ubuntu
16
16
  Classifier: Development Status :: 4 - Beta
17
17
  Classifier: Intended Audience :: Developers
18
18
  Classifier: Intended Audience :: System Administrators
@@ -26,6 +26,7 @@ Classifier: Topic :: System :: Systems Administration
26
26
  Requires-Python: >=3.10
27
27
  Requires-Dist: docker>=7.0.0
28
28
  Provides-Extra: test
29
+ Requires-Dist: markdown-it-py>=3.0.0; extra == 'test'
29
30
  Requires-Dist: pytest>=8.0.0; extra == 'test'
30
31
  Description-Content-Type: text/markdown
31
32
 
@@ -79,7 +80,7 @@ apt-get --version
79
80
  Run it locally with `opsscript-gate run ./install.sh`, or add the following step to CI:
80
81
 
81
82
  ```yaml
82
- - uses: Mresyzz/opsscript-gate@v0.9.0
83
+ - uses: Mresyzz/opsscript-gate@v0.9.2
83
84
  with:
84
85
  script-path: install.sh
85
86
  ```
@@ -88,7 +89,7 @@ The result identifies the failing distribution, exit code, line, missing command
88
89
 
89
90
  ---
90
91
 
91
- ## Current release: v0.9.0
92
+ ## Current version: v0.9.2 (local candidate)
92
93
 
93
94
  The current release includes a project config file, a dry-run plan, presets, exclusions,
94
95
  saved reports, changed-script selection for pull requests, and repeat runs for detecting
@@ -142,7 +143,7 @@ jobs:
142
143
  - uses: actions/checkout@v7
143
144
  with:
144
145
  persist-credentials: false
145
- - uses: Mresyzz/opsscript-gate@v0.9.0
146
+ - uses: Mresyzz/opsscript-gate@v0.9.2
146
147
  ```
147
148
 
148
149
  This zero-configuration form discovers shell scripts in the repository. Use
@@ -153,7 +154,7 @@ repository has a single installer. For self-hosted model installers, see the
153
154
  Or test a specific script with custom execution modes:
154
155
 
155
156
  ```yaml
156
- - uses: Mresyzz/opsscript-gate@v0.9.0
157
+ - uses: Mresyzz/opsscript-gate@v0.9.2
157
158
  with:
158
159
  script-path: scripts/install.sh
159
160
  shell: auto
@@ -187,7 +188,7 @@ unrelated scripts:
187
188
  with:
188
189
  persist-credentials: false
189
190
  fetch-depth: 0
190
- - uses: Mresyzz/opsscript-gate@v0.9.0
191
+ - uses: Mresyzz/opsscript-gate@v0.9.2
191
192
  with:
192
193
  changed-since: ${{ github.event.pull_request.base.sha }}
193
194
  preset: minimal
@@ -199,8 +200,8 @@ container. The same selection can be previewed locally with
199
200
 
200
201
  ### Detect intermittent runtime failures
201
202
 
202
- Use `--repeat N` when a script sometimes passes and sometimes fails in CI. OpsScript
203
- Gate runs each distribution N times and reports `FLAKY` when the same distribution has
203
+ Use `--repeat N` when a script sometimes passes and sometimes fails in CI. Values from
204
+ 1 through 20 are accepted. OpsScript Gate runs each distribution N times and reports `FLAKY` when the same distribution has
204
205
  both passing and failing attempts. The JSON, Markdown, SARIF, annotations, and step
205
206
  summary include the attempt counts so a retry cannot silently turn an unstable script
206
207
  green.
@@ -278,7 +279,7 @@ Use `sarif` when the result should appear in GitHub Code Scanning or another
278
279
  SARIF-compatible viewer. Upload it explicitly with the official upload action:
279
280
 
280
281
  ```yaml
281
- - uses: Mresyzz/opsscript-gate@v0.9.0
282
+ - uses: Mresyzz/opsscript-gate@v0.9.2
282
283
  with:
283
284
  script-path: scripts/install.sh
284
285
  format: sarif
@@ -373,9 +374,11 @@ The runner uses restricted container defaults:
373
374
  2. **Resource Constraints**:
374
375
  - Memory limits enforced per container (`--mem-limit`, default: `256m`).
375
376
  - Process caps enforced to prevent fork bombs (`--pids-limit`, default: `128`).
377
+ - CPU is capped at one host CPU per container and Docker's JSON log driver rotates logs.
376
378
  - Network isolation configurable (`--network bridge` or `--network none`).
377
379
  3. **Read-Only Target Mount**:
378
380
  - The tested script is mounted read-only (`:ro`) at `/tmp/target_script.sh`.
381
+ - The disposable container root remains writable so installers can create files during a real run.
379
382
  - No host directories or sensitive sockets are mounted into test containers.
380
383
  4. **Anti-Hang Deadlock Defense**:
381
384
  - Disables TTY and stdin (`stdin_open=False`, `tty=False`).
@@ -448,8 +451,8 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
448
451
  | :--- | :--- | :--- | :--- |
449
452
  | `script_path` | Positional | *Optional* | Path to target shell script (auto-discovers if omitted) |
450
453
  | `-j, --jobs` | Integer | `min(2, size)` | Number of concurrent container jobs |
451
- | `--matrix` | String | `debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20` | Comma-separated list of Docker images |
452
- | `--timeout` | Integer | `60` | Hard timeout per container in seconds |
454
+ | `--matrix` | String | `debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20` | Comma-separated list of Docker images (maximum `32`) |
455
+ | `--timeout` | Integer | `60` | Hard timeout per container in seconds (maximum `3600`) |
453
456
  | `--format` | Choice | `table` | Output format: `table`, `markdown`, `json`, or `sarif` |
454
457
  | `--shell` | Choice | `posix` | Execution mode: `posix` (default), `shebang`, or `auto` |
455
458
  | `--mem-limit` | String | `256m` | Memory limit per container (e.g. `256m`, `512m`) |
@@ -48,7 +48,7 @@ apt-get --version
48
48
  Run it locally with `opsscript-gate run ./install.sh`, or add the following step to CI:
49
49
 
50
50
  ```yaml
51
- - uses: Mresyzz/opsscript-gate@v0.9.0
51
+ - uses: Mresyzz/opsscript-gate@v0.9.2
52
52
  with:
53
53
  script-path: install.sh
54
54
  ```
@@ -57,7 +57,7 @@ The result identifies the failing distribution, exit code, line, missing command
57
57
 
58
58
  ---
59
59
 
60
- ## Current release: v0.9.0
60
+ ## Current version: v0.9.2 (local candidate)
61
61
 
62
62
  The current release includes a project config file, a dry-run plan, presets, exclusions,
63
63
  saved reports, changed-script selection for pull requests, and repeat runs for detecting
@@ -111,7 +111,7 @@ jobs:
111
111
  - uses: actions/checkout@v7
112
112
  with:
113
113
  persist-credentials: false
114
- - uses: Mresyzz/opsscript-gate@v0.9.0
114
+ - uses: Mresyzz/opsscript-gate@v0.9.2
115
115
  ```
116
116
 
117
117
  This zero-configuration form discovers shell scripts in the repository. Use
@@ -122,7 +122,7 @@ repository has a single installer. For self-hosted model installers, see the
122
122
  Or test a specific script with custom execution modes:
123
123
 
124
124
  ```yaml
125
- - uses: Mresyzz/opsscript-gate@v0.9.0
125
+ - uses: Mresyzz/opsscript-gate@v0.9.2
126
126
  with:
127
127
  script-path: scripts/install.sh
128
128
  shell: auto
@@ -156,7 +156,7 @@ unrelated scripts:
156
156
  with:
157
157
  persist-credentials: false
158
158
  fetch-depth: 0
159
- - uses: Mresyzz/opsscript-gate@v0.9.0
159
+ - uses: Mresyzz/opsscript-gate@v0.9.2
160
160
  with:
161
161
  changed-since: ${{ github.event.pull_request.base.sha }}
162
162
  preset: minimal
@@ -168,8 +168,8 @@ container. The same selection can be previewed locally with
168
168
 
169
169
  ### Detect intermittent runtime failures
170
170
 
171
- Use `--repeat N` when a script sometimes passes and sometimes fails in CI. OpsScript
172
- Gate runs each distribution N times and reports `FLAKY` when the same distribution has
171
+ Use `--repeat N` when a script sometimes passes and sometimes fails in CI. Values from
172
+ 1 through 20 are accepted. OpsScript Gate runs each distribution N times and reports `FLAKY` when the same distribution has
173
173
  both passing and failing attempts. The JSON, Markdown, SARIF, annotations, and step
174
174
  summary include the attempt counts so a retry cannot silently turn an unstable script
175
175
  green.
@@ -247,7 +247,7 @@ Use `sarif` when the result should appear in GitHub Code Scanning or another
247
247
  SARIF-compatible viewer. Upload it explicitly with the official upload action:
248
248
 
249
249
  ```yaml
250
- - uses: Mresyzz/opsscript-gate@v0.9.0
250
+ - uses: Mresyzz/opsscript-gate@v0.9.2
251
251
  with:
252
252
  script-path: scripts/install.sh
253
253
  format: sarif
@@ -342,9 +342,11 @@ The runner uses restricted container defaults:
342
342
  2. **Resource Constraints**:
343
343
  - Memory limits enforced per container (`--mem-limit`, default: `256m`).
344
344
  - Process caps enforced to prevent fork bombs (`--pids-limit`, default: `128`).
345
+ - CPU is capped at one host CPU per container and Docker's JSON log driver rotates logs.
345
346
  - Network isolation configurable (`--network bridge` or `--network none`).
346
347
  3. **Read-Only Target Mount**:
347
348
  - The tested script is mounted read-only (`:ro`) at `/tmp/target_script.sh`.
349
+ - The disposable container root remains writable so installers can create files during a real run.
348
350
  - No host directories or sensitive sockets are mounted into test containers.
349
351
  4. **Anti-Hang Deadlock Defense**:
350
352
  - Disables TTY and stdin (`stdin_open=False`, `tty=False`).
@@ -417,8 +419,8 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
417
419
  | :--- | :--- | :--- | :--- |
418
420
  | `script_path` | Positional | *Optional* | Path to target shell script (auto-discovers if omitted) |
419
421
  | `-j, --jobs` | Integer | `min(2, size)` | Number of concurrent container jobs |
420
- | `--matrix` | String | `debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20` | Comma-separated list of Docker images |
421
- | `--timeout` | Integer | `60` | Hard timeout per container in seconds |
422
+ | `--matrix` | String | `debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20` | Comma-separated list of Docker images (maximum `32`) |
423
+ | `--timeout` | Integer | `60` | Hard timeout per container in seconds (maximum `3600`) |
422
424
  | `--format` | Choice | `table` | Output format: `table`, `markdown`, `json`, or `sarif` |
423
425
  | `--shell` | Choice | `posix` | Execution mode: `posix` (default), `shebang`, or `auto` |
424
426
  | `--mem-limit` | String | `256m` | Memory limit per container (e.g. `256m`, `512m`) |
@@ -17,9 +17,9 @@ Bash 依赖、包管理器假设和交互式阻塞。它与 ShellCheck 互补。
17
17
 
18
18
  也可以直接打开 [OpsScript Gate 在线演示](https://mresyzz.github.io/opsscript-gate/),先查看 ShellCheck 与运行时验证的差异,再复制 workflow 到自己的仓库。
19
19
 
20
- ## 当前版本 v0.9.0
20
+ ## 当前版本 v0.9.2
21
21
 
22
- v0.9.0 已作为正式版本发布;如果你要从源码验证当前分支,也可以执行:
22
+ v0.9.2 是当前本地候选版本;如果你要从源码验证当前分支,也可以执行:
23
23
 
24
24
  ```bash
25
25
  pip install -e .
@@ -35,7 +35,7 @@ opsscript-gate run --dry-run
35
35
  opsscript-gate run --format json --output reports/compatibility.json
36
36
  ```
37
37
 
38
- `init` 生成配置和 GitHub 工作流,不覆盖已有文件。工作流引用 `v0.9.0`。
38
+ `init` 生成配置和 GitHub 工作流,不覆盖已有文件。工作流引用 `v0.9.2`。
39
39
  预览不需要 Docker;真实运行需要 Python 3.10+
40
40
  和可访问的 Linux Docker 引擎。
41
41
  `opsscript-gate doctor` 可以在真实运行前检查 Python 和 Docker。
@@ -74,7 +74,7 @@ Docker daemon 和可选的项目配置。
74
74
  - uses: actions/checkout@v7
75
75
  with:
76
76
  fetch-depth: 0
77
- - uses: Mresyzz/opsscript-gate@v0.9.0
77
+ - uses: Mresyzz/opsscript-gate@v0.9.2
78
78
  with:
79
79
  changed-since: ${{ github.event.pull_request.base.sha }}
80
80
  preset: minimal
@@ -1,5 +1,5 @@
1
1
  name: 'OpsScript Gate'
2
- description: 'Cross-distro shell script compatibility checker for runtime testing in Debian, Ubuntu, and Alpine. Diagnoses command not found.'
2
+ description: 'Cross-distro shell script compatibility and portability checker for Debian, Ubuntu, and Alpine. Runs real containers to diagnose command not found.'
3
3
  author: 'Mresyzz'
4
4
  branding:
5
5
  icon: 'shield'
@@ -11,7 +11,7 @@ inputs:
11
11
  required: false
12
12
  default: ''
13
13
  matrix:
14
- description: 'Comma-separated container images to test (e.g. debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20)'
14
+ description: 'Comma-separated container images to test (maximum: 32; e.g. debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20)'
15
15
  required: false
16
16
  default: ''
17
17
  jobs:
@@ -19,11 +19,11 @@ inputs:
19
19
  required: false
20
20
  default: ''
21
21
  timeout:
22
- description: 'Hard timeout in seconds per container (default: 60)'
22
+ description: 'Hard timeout in seconds per container (default: 60, maximum: 3600)'
23
23
  required: false
24
24
  default: ''
25
25
  repeat:
26
- description: 'Repeat each distribution run to detect flaky runtime behavior (default: 1)'
26
+ description: 'Repeat each distribution run to detect flaky runtime behavior (default: 1, maximum: 20)'
27
27
  required: false
28
28
  default: ''
29
29
  format:
@@ -139,9 +139,10 @@ runs:
139
139
  pattern="${pattern%$'\r'}"
140
140
  [ -z "$pattern" ] || ARGS+=("--exclude" "$pattern")
141
141
  done <<< "$INPUT_EXCLUDE"
142
- if [ "$INPUT_DRY_RUN" = "true" ]; then
142
+ DRY_RUN_VALUE="$(printf '%s' "$INPUT_DRY_RUN" | tr '[:upper:]' '[:lower:]')"
143
+ if [ "$DRY_RUN_VALUE" = "true" ]; then
143
144
  ARGS+=("--dry-run")
144
- elif [ "$INPUT_DRY_RUN" != "false" ]; then
145
+ elif [ "$DRY_RUN_VALUE" != "false" ]; then
145
146
  echo "dry-run must be true or false" >&2
146
147
  exit 1
147
148
  fi
@@ -232,7 +232,7 @@
232
232
  OpsScript Gate
233
233
  </span>
234
234
  <span class="font-mono text-[11px] px-1.5 py-0.5 rounded bg-[var(--surface-muted)] text-[var(--text-muted)] border border-[var(--border-light)]">
235
- v0.9.0
235
+ v0.9.2
236
236
  </span>
237
237
  </div>
238
238
  </div>
@@ -367,7 +367,7 @@
367
367
  <div class="panel-card p-4 flex flex-col justify-between space-y-3">
368
368
  <div class="space-y-2.5">
369
369
  <div class="flex items-center justify-between border-b border-[var(--border-light)] pb-2">
370
- <span class="font-mono text-xs font-medium text-[var(--text-muted)]">OpsScript Gate Runtime (v0.9.0)</span>
370
+ <span class="font-mono text-xs font-medium text-[var(--text-muted)]">OpsScript Gate Runtime (v0.9.2)</span>
371
371
  <span id="ops-status" class="font-mono text-xs font-semibold px-2 py-0.5 rounded border">
372
372
  1 DISTRO FAILED
373
373
  </span>
@@ -536,7 +536,7 @@
536
536
  <span class="font-semibold text-[var(--text-hero)]">steps:</span>
537
537
  - <span class="font-semibold text-[var(--text-hero)]">uses:</span> actions/checkout@v4
538
538
  - <span class="font-semibold text-[var(--text-hero)]">name:</span> Validate scripts across distributions
539
- <span class="font-semibold text-[var(--brand)]">uses:</span> Mresyzz/opsscript-gate@v0.9.0
539
+ <span class="font-semibold text-[var(--brand)]">uses:</span> Mresyzz/opsscript-gate@v0.9.2
540
540
  <span class="font-semibold text-[var(--text-hero)]">with:</span>
541
541
  <span class="text-[var(--text-body)]">script-path:</span> ./install.sh
542
542
  <span class="text-[var(--text-body)]">shell:</span> auto
@@ -604,7 +604,7 @@ jobs:
604
604
  steps:
605
605
  - uses: actions/checkout@v4
606
606
  - name: Validate scripts across distributions
607
- uses: Mresyzz/opsscript-gate@v0.9.0
607
+ uses: Mresyzz/opsscript-gate@v0.9.2
608
608
  with:
609
609
  script-path: ./install.sh
610
610
  shell: auto
@@ -5,6 +5,61 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Test shell scripts in Debian, Ubuntu and Alpine: command not found | OpsScript Gate</title>
7
7
  <meta name="description" content="Test a shell installer in real Debian, Ubuntu and Alpine containers. Diagnose command not found, exit code 127 and missing Bash with OpsScript Gate, a GitHub Action and CLI.">
8
+ <script type="application/ld+json">
9
+ {
10
+ "@context": "https://schema.org",
11
+ "@type": "SoftwareApplication",
12
+ "name": "OpsScript Gate",
13
+ "applicationCategory": "DeveloperApplication",
14
+ "operatingSystem": "Linux",
15
+ "description": "Run shell scripts in real Debian, Ubuntu, and Alpine containers to diagnose command not found and cross-distro runtime failures.",
16
+ "url": "https://mresyzz.github.io/opsscript-gate/command-not-found.html",
17
+ "codeRepository": "https://github.com/Mresyzz/opsscript-gate",
18
+ "downloadUrl": "https://pypi.org/project/opsscript-gate/",
19
+ "isAccessibleForFree": true,
20
+ "license": "https://opensource.org/licenses/MIT"
21
+ }
22
+ </script>
23
+ <script type="application/ld+json">
24
+ {
25
+ "@context": "https://schema.org",
26
+ "@type": "FAQPage",
27
+ "mainEntity": [
28
+ {
29
+ "@type": "Question",
30
+ "name": "How do I test a shell script on Debian, Ubuntu, and Alpine?",
31
+ "acceptedAnswer": {
32
+ "@type": "Answer",
33
+ "text": "Add the OpsScript Gate GitHub Action or install the Python CLI, then run the script in real Debian, Ubuntu, and Alpine containers. The report shows the failing image, exit code, line, and missing command."
34
+ }
35
+ },
36
+ {
37
+ "@type": "Question",
38
+ "name": "Why can ShellCheck pass while a shell installer still fails with command not found?",
39
+ "acceptedAnswer": {
40
+ "@type": "Answer",
41
+ "text": "ShellCheck analyzes shell source and syntax. It does not know which binaries are installed in the runtime image. OpsScript Gate executes the script so missing commands and package-manager assumptions appear in the target distribution."
42
+ }
43
+ },
44
+ {
45
+ "@type": "Question",
46
+ "name": "How do I diagnose apt-get not found on Alpine?",
47
+ "acceptedAnswer": {
48
+ "@type": "Answer",
49
+ "text": "Alpine normally uses apk rather than apt-get. Run the installer across the matrix and branch on the package manager or document a distribution-specific prerequisite."
50
+ }
51
+ },
52
+ {
53
+ "@type": "Question",
54
+ "name": "Can I run the cross-distro shell check without GitHub Actions?",
55
+ "acceptedAnswer": {
56
+ "@type": "Answer",
57
+ "text": "Yes. Install opsscript-gate from PyPI, ensure a Docker engine running Linux containers is reachable, and run opsscript-gate run ./install.sh."
58
+ }
59
+ }
60
+ ]
61
+ }
62
+ </script>
8
63
  <link rel="canonical" href="https://mresyzz.github.io/opsscript-gate/command-not-found.html">
9
64
  <meta property="og:title" content="ShellCheck passed. Does your installer run on Alpine?">
10
65
  <meta property="og:description" content="A reproducible shell runtime compatibility check for Debian, Ubuntu and Alpine, with a copyable GitHub Actions workflow.">
@@ -79,7 +134,7 @@ jobs:
79
134
  - uses: actions/checkout@v7
80
135
  with:
81
136
  persist-credentials: false
82
- - uses: Mresyzz/opsscript-gate@v0.9.0
137
+ - uses: Mresyzz/opsscript-gate@v0.9.2
83
138
  with:
84
139
  script-path: install.sh
85
140
  shell: auto
@@ -113,6 +168,20 @@ opsscript-gate run ./install.sh</code></pre>
113
168
  <p>OpsScript Gate mounts each script independently. Sibling files and repository dependencies are not mounted.
114
169
  These are distribution user-space tests in containers, not separate Linux kernels or full-machine tests.
115
170
  Passing checks do not prove security or replace project integration tests.</p>
171
+
172
+ <h2 id="faq">Frequently asked questions</h2>
173
+ <h3>How do I test a shell script on Debian, Ubuntu, and Alpine?</h3>
174
+ <p>Add the GitHub Action or install the CLI, then run the script in real containers for each distribution.
175
+ The report identifies the failing image, exit code, line, and missing command.</p>
176
+ <h3>Why can ShellCheck pass while the installer still fails?</h3>
177
+ <p>ShellCheck analyzes source and syntax. It cannot know which binaries are installed in the image that
178
+ executes the script. Runtime execution exposes missing commands and package-manager assumptions.</p>
179
+ <h3>How do I diagnose <code>apt-get: not found</code> on Alpine?</h3>
180
+ <p>Alpine normally uses <code>apk</code>. Detect the available package manager or document a distribution-specific
181
+ prerequisite instead of assuming Debian's APT exists everywhere.</p>
182
+ <h3>Can I run the check without GitHub Actions?</h3>
183
+ <p>Yes. Install <code>opsscript-gate</code> from PyPI, make sure a Docker engine running Linux containers is
184
+ reachable, and run <code>opsscript-gate run ./install.sh</code>.</p>
116
185
  <footer>Read the <a href="https://github.com/Mresyzz/opsscript-gate/blob/main/docs/configuration.md">configuration guide</a>,
117
186
  <a href="https://github.com/Mresyzz/opsscript-gate/blob/main/docs/troubleshooting.md">troubleshooting</a>,
118
187
  or <a href="https://github.com/Mresyzz/opsscript-gate/blob/main/SECURITY.md">security boundaries</a>.
@@ -25,7 +25,7 @@ jobs:
25
25
  - uses: actions/checkout@v7
26
26
  with:
27
27
  persist-credentials: false
28
- - uses: Mresyzz/opsscript-gate@v0.9.0
28
+ - uses: Mresyzz/opsscript-gate@v0.9.2
29
29
  with:
30
30
  script-path: scripts/install.sh
31
31
  shell: auto
@@ -117,3 +117,27 @@ run in the Linux environments we support?**
117
117
 
118
118
  See the [Alpine incompatibility example](../examples/alpine-incompatibility/README.md),
119
119
  [troubleshooting guide](troubleshooting.md), or the [interactive demo](https://mresyzz.github.io/opsscript-gate/).
120
+
121
+ ## Frequently asked questions
122
+
123
+ ### How do I test a shell script on Debian, Ubuntu, and Alpine?
124
+
125
+ Add the OpsScript Gate GitHub Action or install the CLI, then run the script in real
126
+ containers for each distribution. The report identifies the failing image, exit code,
127
+ line, and missing command.
128
+
129
+ ### Why can ShellCheck pass while the installer still fails?
130
+
131
+ ShellCheck analyzes source and syntax. It cannot know which binaries are installed in
132
+ the image that executes the script. Runtime execution exposes missing commands and
133
+ package-manager assumptions.
134
+
135
+ ### How do I diagnose `apt-get: not found` on Alpine?
136
+
137
+ Alpine normally uses `apk`. Detect the available package manager or document a
138
+ distribution-specific prerequisite instead of assuming Debian's APT exists everywhere.
139
+
140
+ ### Can I run the check without GitHub Actions?
141
+
142
+ Yes. Install `opsscript-gate` from PyPI, make sure a Docker engine running Linux
143
+ containers is reachable, and run `opsscript-gate run ./install.sh`.