Compare commits

..

3 Commits

Author SHA1 Message Date
e8f2ecd2ea fix(ci): use script file instead of inline Python in playbook-schema
Some checks failed
PR Checklist / pr-checklist (pull_request) Successful in 1m8s
Validate Config / YAML Lint (pull_request) Failing after 7s
Validate Config / JSON Validate (pull_request) Successful in 7s
Validate Config / Python Syntax & Import Check (pull_request) Failing after 7s
Validate Config / Shell Script Lint (pull_request) Successful in 20s
Validate Config / Cron Syntax Check (pull_request) Successful in 5s
Validate Config / Deploy Script Dry Run (pull_request) Successful in 5s
Validate Config / Playbook Schema Validation (pull_request) Successful in 9s
Architecture Lint / Lint Repository (pull_request) Failing after 7s
Architecture Lint / Linter Tests (pull_request) Successful in 9s
Inline Python code with f-strings and set literals caused YAML parsing
errors. Replaced with reference to scripts/validate_playbook_schema.py.

Refs: #461
2026-04-11 00:21:09 +00:00
941cb25cbe fix(ci): extract inline Python to script for validate-config workflow
The inline Python in playbook-schema job had YAML parsing issues.
Extracted to scripts/validate_playbook_schema.py.

Refs: #461
2026-04-11 00:21:03 +00:00
05e9c1bf51 security: .gitignore secret cleanup
Some checks failed
Architecture Lint / Linter Tests (push) Successful in 9s
Architecture Lint / Lint Repository (push) Failing after 9s
2026-04-10 23:38:39 +00:00
5 changed files with 59 additions and 92 deletions

View File

@@ -112,23 +112,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install PyYAML
run: pip install pyyaml
- name: Validate playbook structure
run: |
python3 -c "
import yaml, sys, glob
required_keys = {'name', 'description'}
for f in glob.glob('playbooks/*.yaml'):
with open(f) as fh:
try:
data = yaml.safe_load(fh)
if not isinstance(data, dict):
print(f'ERROR: {f} is not a YAML mapping')
sys.exit(1)
missing = required_keys - set(data.keys())
if missing:
print(f'WARNING: {f} missing keys: {missing}')
print(f'OK: {f}')
except yaml.YAMLError as e:
print(f'ERROR: {f}: {e}')
sys.exit(1)
"
run: python3 scripts/validate_playbook_schema.py

24
.gitignore vendored
View File

@@ -10,3 +10,27 @@ __pycache__/
# Generated audit reports
reports/
# Secrets and credentials
.bash_history
.git-credentials
.gitea_token
.ssh/id_*
.ssh/known_hosts
.viminfo
.wget-hsts
.profile
.bashrc
.bash_logout
.python_history
.lesshst
.selected_editor
.sudo_as_admin_successful
.config/telegram/
.hermes/.env
.hermes/auth.json
*.pem
*.key
.env
.env.*
!.env.example

View File

@@ -14,7 +14,6 @@ import subprocess
import argparse
import requests
import datetime
from typing import Sequence
# --- CONFIGURATION ---
FLEET = {
@@ -193,10 +192,10 @@ EXAMPLES:
"""
print(help_text)
def build_parser() -> argparse.ArgumentParser:
def main():
parser = argparse.ArgumentParser(
description="Self-healing infrastructure script (safe-by-default).",
add_help=False,
add_help=False # We'll handle --help ourselves
)
parser.add_argument("--dry-run", action="store_true", default=False,
help="Run in dry-run mode (default behavior).")
@@ -210,28 +209,25 @@ def build_parser() -> argparse.ArgumentParser:
help="Show detailed help about safety features.")
parser.add_argument("--help", "-h", action="store_true", default=False,
help="Show standard help.")
return parser
def main(argv: Sequence[str] | None = None):
parser = build_parser()
args = parser.parse_args(list(argv) if argv is not None else None)
args = parser.parse_args()
if args.help_safe:
print_help_safe()
raise SystemExit(0)
sys.exit(0)
if args.help:
parser.print_help()
raise SystemExit(0)
sys.exit(0)
# Determine mode: if --execute is given, disable dry-run
dry_run = not args.execute
# If --dry-run is explicitly given, ensure dry-run (redundant but clear)
if args.dry_run:
dry_run = True
healer = SelfHealer(dry_run=dry_run, confirm_kill=args.confirm_kill, yes=args.yes)
healer.run()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3
"""Validate playbook YAML files have required keys."""
import yaml
import sys
import glob
required_keys = {'name', 'description'}
for f in glob.glob('playbooks/*.yaml'):
with open(f) as fh:
try:
data = yaml.safe_load(fh)
if not isinstance(data, dict):
print(f'ERROR: {f} is not a YAML mapping')
sys.exit(1)
missing = required_keys - set(data.keys())
if missing:
print(f'WARNING: {f} missing keys: {missing}')
print(f'OK: {f}')
except yaml.YAMLError as e:
print(f'ERROR: {f}: {e}')
sys.exit(1)

View File

@@ -1,62 +0,0 @@
"""Tests for scripts/self_healing.py safe CLI behavior."""
from __future__ import annotations
import importlib.util
from pathlib import Path
from unittest.mock import MagicMock
import pytest
REPO_ROOT = Path(__file__).parent.parent
spec = importlib.util.spec_from_file_location("self_healing", REPO_ROOT / "scripts" / "self_healing.py")
sh = importlib.util.module_from_spec(spec)
spec.loader.exec_module(sh)
class TestMainCli:
def test_help_exits_without_running_healer(self, monkeypatch, capsys):
healer_cls = MagicMock()
monkeypatch.setattr(sh, "SelfHealer", healer_cls)
with pytest.raises(SystemExit) as excinfo:
sh.main(["--help"])
assert excinfo.value.code == 0
healer_cls.assert_not_called()
out = capsys.readouterr().out
assert "--execute" in out
assert "--help-safe" in out
def test_help_safe_exits_without_running_healer(self, monkeypatch, capsys):
healer_cls = MagicMock()
monkeypatch.setattr(sh, "SelfHealer", healer_cls)
with pytest.raises(SystemExit) as excinfo:
sh.main(["--help-safe"])
assert excinfo.value.code == 0
healer_cls.assert_not_called()
out = capsys.readouterr().out
assert "DRY-RUN" in out
assert "--confirm-kill" in out
def test_default_invocation_runs_in_dry_run_mode(self, monkeypatch):
healer = MagicMock()
healer_cls = MagicMock(return_value=healer)
monkeypatch.setattr(sh, "SelfHealer", healer_cls)
sh.main([])
healer_cls.assert_called_once_with(dry_run=True, confirm_kill=False, yes=False)
healer.run.assert_called_once_with()
def test_execute_flag_disables_dry_run(self, monkeypatch):
healer = MagicMock()
healer_cls = MagicMock(return_value=healer)
monkeypatch.setattr(sh, "SelfHealer", healer_cls)
sh.main(["--execute", "--yes", "--confirm-kill"])
healer_cls.assert_called_once_with(dry_run=False, confirm_kill=True, yes=True)
healer.run.assert_called_once_with()