rots 0.4.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.
- rots-0.4.0/.git +1 -0
- rots-0.4.0/.github/workflows/ci.yml +116 -0
- rots-0.4.0/.github/workflows/claude-code-review.yml +100 -0
- rots-0.4.0/.github/workflows/claude.yml +77 -0
- rots-0.4.0/.github/workflows/release.yml +34 -0
- rots-0.4.0/.gitignore +30 -0
- rots-0.4.0/.pre-commit-config.yaml +113 -0
- rots-0.4.0/CHANGELOG.rst +13 -0
- rots-0.4.0/CLAUDE.md +96 -0
- rots-0.4.0/LICENSE +21 -0
- rots-0.4.0/PKG-INFO +287 -0
- rots-0.4.0/README.md +251 -0
- rots-0.4.0/changelog.d/20260224_operational_improvements.rst +25 -0
- rots-0.4.0/changelog.d/20260225_image_ref_security.rst +28 -0
- rots-0.4.0/changelog.d/20260226_rename_to_rots.rst +11 -0
- rots-0.4.0/changelog.d/README.md +46 -0
- rots-0.4.0/changelog.d/scriv.ini +5 -0
- rots-0.4.0/docs/architecture/decision-records/README.md +41 -0
- rots-0.4.0/docs/architecture/decision-records/adr-000.md +32 -0
- rots-0.4.0/docs/architecture/decision-records/adr-001.md +84 -0
- rots-0.4.0/docs/architecture/decision-records/adr-002.md +84 -0
- rots-0.4.0/docs/networking.md +137 -0
- rots-0.4.0/docs/remote-execution.md +145 -0
- rots-0.4.0/docs/testing.md +174 -0
- rots-0.4.0/docs/wips/.gitignore +1 -0
- rots-0.4.0/docs/wips/0205-00-environment-configuration-skeleton.md +340 -0
- rots-0.4.0/docs/wips/0205-01-the-vision-of-the-tooling.md +306 -0
- rots-0.4.0/docs/wips/0205-02-four-layer-conceptual-model-draft.md +36 -0
- rots-0.4.0/docs/wips/0205-02-four-layer-model-draft2.md +108 -0
- rots-0.4.0/docs/wips/0205-02-layer4-the-gateway-bastion.md +38 -0
- rots-0.4.0/docs/wips/0205-03-layer0-dnsmasq.md +204 -0
- rots-0.4.0/docs/wips/0205-04-layer0-dnsmasq-draft2.md +119 -0
- rots-0.4.0/docs/wips/0205-04-layer1-sshtmux.md +6 -0
- rots-0.4.0/docs/wips/0205-04-ots-host-config-push-HANDOFF.md +115 -0
- rots-0.4.0/docs/wips/0205-04-the-vision-of-the-tooling-draft2.md +252 -0
- rots-0.4.0/docs/wips/0205-04-three-layer-model.md +188 -0
- rots-0.4.0/docs/wips/0206-MANUAL-OPERATIONS.md +712 -0
- rots-0.4.0/packages/ots-shared/pyproject.toml +52 -0
- rots-0.4.0/packages/ots-shared/src/ots_shared/__init__.py +1 -0
- rots-0.4.0/packages/ots-shared/src/ots_shared/cli.py +70 -0
- rots-0.4.0/packages/ots-shared/src/ots_shared/exit_codes.py +29 -0
- rots-0.4.0/packages/ots-shared/src/ots_shared/ssh/__init__.py +51 -0
- rots-0.4.0/packages/ots-shared/src/ots_shared/ssh/_pty.py +172 -0
- rots-0.4.0/packages/ots-shared/src/ots_shared/ssh/connection.py +152 -0
- rots-0.4.0/packages/ots-shared/src/ots_shared/ssh/env.py +191 -0
- rots-0.4.0/packages/ots-shared/src/ots_shared/ssh/executor.py +483 -0
- rots-0.4.0/packages/ots-shared/src/ots_shared/taxonomy.py +100 -0
- rots-0.4.0/packages/ots-shared/tests/__init__.py +0 -0
- rots-0.4.0/packages/ots-shared/tests/conftest.py +101 -0
- rots-0.4.0/packages/ots-shared/tests/ssh/__init__.py +0 -0
- rots-0.4.0/packages/ots-shared/tests/ssh/test_connection.py +150 -0
- rots-0.4.0/packages/ots-shared/tests/ssh/test_env.py +349 -0
- rots-0.4.0/packages/ots-shared/tests/ssh/test_executor.py +913 -0
- rots-0.4.0/packages/ots-shared/tests/ssh/test_pty.py +173 -0
- rots-0.4.0/packages/ots-shared/tests/test_cli.py +129 -0
- rots-0.4.0/packages/ots-shared/tests/test_exit_codes.py +43 -0
- rots-0.4.0/packages/ots-shared/tests/test_taxonomy.py +56 -0
- rots-0.4.0/pyproject.toml +89 -0
- rots-0.4.0/scripts/rename_to_rots.sh +58 -0
- rots-0.4.0/src/rots/__init__.py +8 -0
- rots-0.4.0/src/rots/__main__.py +7 -0
- rots-0.4.0/src/rots/assets.py +96 -0
- rots-0.4.0/src/rots/cli.py +333 -0
- rots-0.4.0/src/rots/commands/README.md +128 -0
- rots-0.4.0/src/rots/commands/__init__.py +13 -0
- rots-0.4.0/src/rots/commands/assets.py +31 -0
- rots-0.4.0/src/rots/commands/cloudinit/__init__.py +6 -0
- rots-0.4.0/src/rots/commands/cloudinit/app.py +192 -0
- rots-0.4.0/src/rots/commands/cloudinit/templates.py +376 -0
- rots-0.4.0/src/rots/commands/common.py +122 -0
- rots-0.4.0/src/rots/commands/db.py +467 -0
- rots-0.4.0/src/rots/commands/env/__init__.py +6 -0
- rots-0.4.0/src/rots/commands/env/app.py +495 -0
- rots-0.4.0/src/rots/commands/host/__init__.py +6 -0
- rots-0.4.0/src/rots/commands/host/_manifest.py +82 -0
- rots-0.4.0/src/rots/commands/host/_rsync.py +179 -0
- rots-0.4.0/src/rots/commands/host/app.py +451 -0
- rots-0.4.0/src/rots/commands/image/__init__.py +6 -0
- rots-0.4.0/src/rots/commands/image/app.py +1691 -0
- rots-0.4.0/src/rots/commands/init.py +428 -0
- rots-0.4.0/src/rots/commands/instance/__init__.py +69 -0
- rots-0.4.0/src/rots/commands/instance/_helpers.py +462 -0
- rots-0.4.0/src/rots/commands/instance/annotations.py +112 -0
- rots-0.4.0/src/rots/commands/instance/app.py +2441 -0
- rots-0.4.0/src/rots/commands/proxy/__init__.py +11 -0
- rots-0.4.0/src/rots/commands/proxy/_helpers.py +168 -0
- rots-0.4.0/src/rots/commands/proxy/app.py +266 -0
- rots-0.4.0/src/rots/commands/service/__init__.py +6 -0
- rots-0.4.0/src/rots/commands/service/_helpers.py +433 -0
- rots-0.4.0/src/rots/commands/service/app.py +608 -0
- rots-0.4.0/src/rots/commands/service/packages.py +218 -0
- rots-0.4.0/src/rots/config.py +539 -0
- rots-0.4.0/src/rots/context.py +13 -0
- rots-0.4.0/src/rots/db.py +865 -0
- rots-0.4.0/src/rots/environment_file.py +526 -0
- rots-0.4.0/src/rots/podman.py +117 -0
- rots-0.4.0/src/rots/quadlet.py +686 -0
- rots-0.4.0/src/rots/systemd.py +666 -0
- rots-0.4.0/tests/__init__.py +1 -0
- rots-0.4.0/tests/commands/__init__.py +1 -0
- rots-0.4.0/tests/commands/cloudinit/__init__.py +2 -0
- rots-0.4.0/tests/commands/cloudinit/test_app.py +265 -0
- rots-0.4.0/tests/commands/cloudinit/test_templates.py +463 -0
- rots-0.4.0/tests/commands/host/__init__.py +0 -0
- rots-0.4.0/tests/commands/host/test_app.py +422 -0
- rots-0.4.0/tests/commands/host/test_manifest.py +111 -0
- rots-0.4.0/tests/commands/host/test_rsync.py +154 -0
- rots-0.4.0/tests/commands/image/__init__.py +2 -0
- rots-0.4.0/tests/commands/image/test_app.py +1698 -0
- rots-0.4.0/tests/commands/image/test_build.py +1274 -0
- rots-0.4.0/tests/commands/image/test_image_ref_parsing.py +225 -0
- rots-0.4.0/tests/commands/instance/__init__.py +2 -0
- rots-0.4.0/tests/commands/instance/test_app.py +3193 -0
- rots-0.4.0/tests/commands/instance/test_config_transform.py +1005 -0
- rots-0.4.0/tests/commands/instance/test_deploy_image_ref.py +282 -0
- rots-0.4.0/tests/commands/instance/test_health_and_ps.py +402 -0
- rots-0.4.0/tests/commands/instance/test_helpers.py +685 -0
- rots-0.4.0/tests/commands/instance/test_run_image_ref.py +248 -0
- rots-0.4.0/tests/commands/instance/test_shell.py +476 -0
- rots-0.4.0/tests/commands/proxy/__init__.py +1 -0
- rots-0.4.0/tests/commands/proxy/test_app.py +423 -0
- rots-0.4.0/tests/commands/proxy/test_helpers.py +344 -0
- rots-0.4.0/tests/commands/service/__init__.py +2 -0
- rots-0.4.0/tests/commands/service/test_app.py +1043 -0
- rots-0.4.0/tests/commands/service/test_helpers.py +376 -0
- rots-0.4.0/tests/commands/service/test_packages.py +168 -0
- rots-0.4.0/tests/commands/test_assets.py +115 -0
- rots-0.4.0/tests/commands/test_db_commands.py +590 -0
- rots-0.4.0/tests/commands/test_env.py +628 -0
- rots-0.4.0/tests/commands/test_init.py +747 -0
- rots-0.4.0/tests/conftest.py +26 -0
- rots-0.4.0/tests/integration/__init__.py +1 -0
- rots-0.4.0/tests/integration/conftest.py +371 -0
- rots-0.4.0/tests/integration/test_smoke.py +206 -0
- rots-0.4.0/tests/integration/test_ssh_remote.py +368 -0
- rots-0.4.0/tests/test_assets.py +411 -0
- rots-0.4.0/tests/test_cli.py +588 -0
- rots-0.4.0/tests/test_config.py +1612 -0
- rots-0.4.0/tests/test_db.py +2097 -0
- rots-0.4.0/tests/test_environment_file.py +685 -0
- rots-0.4.0/tests/test_image_build.py +335 -0
- rots-0.4.0/tests/test_image_reference_precedence.py +324 -0
- rots-0.4.0/tests/test_image_reference_security.py +332 -0
- rots-0.4.0/tests/test_main.py +46 -0
- rots-0.4.0/tests/test_parse_image_reference.py +131 -0
- rots-0.4.0/tests/test_podman.py +329 -0
- rots-0.4.0/tests/test_quadlet.py +1524 -0
- rots-0.4.0/tests/test_quadlet_render.py +288 -0
- rots-0.4.0/tests/test_systemd.py +2051 -0
- rots-0.4.0/uv.lock +994 -0
rots-0.4.0/.git
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gitdir: ../../.git/modules/deployments/containers
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ['3.11', '3.12', '3.13']
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v5
|
|
21
|
+
with:
|
|
22
|
+
enable-cache: true
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: uv sync --frozen --extra test
|
|
31
|
+
|
|
32
|
+
- name: Run tests with coverage
|
|
33
|
+
run: |
|
|
34
|
+
uv run pytest tests/ \
|
|
35
|
+
--ignore=tests/integration \
|
|
36
|
+
--cov=rots \
|
|
37
|
+
--cov-report=term-missing \
|
|
38
|
+
--cov-report=xml \
|
|
39
|
+
--cov-fail-under=70
|
|
40
|
+
|
|
41
|
+
- name: Check per-file coverage minimums
|
|
42
|
+
run: |
|
|
43
|
+
# cli.py: baseline 26% (target: 80% — add tests for subcommand registration)
|
|
44
|
+
uv run python -m coverage report \
|
|
45
|
+
--include="*/rots/cli.py" \
|
|
46
|
+
--fail-under=26
|
|
47
|
+
# commands/db.py: baseline 29% (target: 70% — add tests for deploy history queries)
|
|
48
|
+
uv run python -m coverage report \
|
|
49
|
+
--include="*/rots/commands/db.py" \
|
|
50
|
+
--fail-under=29
|
|
51
|
+
|
|
52
|
+
- name: Upload coverage to Codecov
|
|
53
|
+
if: matrix.python-version == '3.11'
|
|
54
|
+
uses: codecov/codecov-action@v4
|
|
55
|
+
with:
|
|
56
|
+
files: ./coverage.xml
|
|
57
|
+
fail_ci_if_error: false
|
|
58
|
+
verbose: true
|
|
59
|
+
|
|
60
|
+
integration:
|
|
61
|
+
# Integration smoke tests run on ubuntu-latest where systemd and podman
|
|
62
|
+
# are available. Tests skip gracefully when prerequisites are absent.
|
|
63
|
+
# This job is allowed to fail without blocking the PR (continue-on-error).
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
needs: test
|
|
66
|
+
continue-on-error: true
|
|
67
|
+
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
|
|
71
|
+
- name: Install uv
|
|
72
|
+
uses: astral-sh/setup-uv@v5
|
|
73
|
+
with:
|
|
74
|
+
enable-cache: true
|
|
75
|
+
|
|
76
|
+
- name: Set up Python 3.11
|
|
77
|
+
uses: actions/setup-python@v5
|
|
78
|
+
with:
|
|
79
|
+
python-version: '3.11'
|
|
80
|
+
|
|
81
|
+
- name: Install dependencies
|
|
82
|
+
run: uv sync --frozen --extra test
|
|
83
|
+
|
|
84
|
+
- name: Run integration smoke tests
|
|
85
|
+
run: |
|
|
86
|
+
uv run pytest tests/integration/ -v \
|
|
87
|
+
--tb=short \
|
|
88
|
+
--no-header
|
|
89
|
+
|
|
90
|
+
lint:
|
|
91
|
+
runs-on: ubuntu-latest
|
|
92
|
+
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@v4
|
|
95
|
+
|
|
96
|
+
- name: Install uv
|
|
97
|
+
uses: astral-sh/setup-uv@v5
|
|
98
|
+
with:
|
|
99
|
+
enable-cache: true
|
|
100
|
+
|
|
101
|
+
- name: Set up Python
|
|
102
|
+
uses: actions/setup-python@v5
|
|
103
|
+
with:
|
|
104
|
+
python-version: '3.11'
|
|
105
|
+
|
|
106
|
+
- name: Install dependencies
|
|
107
|
+
run: uv sync --frozen --extra dev
|
|
108
|
+
|
|
109
|
+
- name: Run ruff linter
|
|
110
|
+
run: uv run ruff check src/
|
|
111
|
+
|
|
112
|
+
- name: Run ruff formatter check
|
|
113
|
+
run: uv run ruff format --check src/
|
|
114
|
+
|
|
115
|
+
- name: Run pyright
|
|
116
|
+
run: uv run pyright src/
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# .github/workflows/claude-code-review.yml
|
|
2
|
+
|
|
3
|
+
name: Claude Code Review
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
pull_request:
|
|
7
|
+
branches:
|
|
8
|
+
- 'feature/*'
|
|
9
|
+
- 'fix/*'
|
|
10
|
+
- 'wip/*'
|
|
11
|
+
- 'rel/*'
|
|
12
|
+
- 'develop'
|
|
13
|
+
- 'main'
|
|
14
|
+
types: [opened, synchronize, labeled]
|
|
15
|
+
workflow_dispatch:
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
claude-review:
|
|
19
|
+
# Optional: Filter by PR author
|
|
20
|
+
# if: |
|
|
21
|
+
# github.event.pull_request.user.login == 'external-contributor' ||
|
|
22
|
+
# github.event.pull_request.user.login == 'new-developer' ||
|
|
23
|
+
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
|
24
|
+
# if: ${{ !startsWith(github.head_ref, 'deps/') }}
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
# Skip bot-created PRs (renovate, dependabot, etc.) - they show as 'name[bot]' in github.actor
|
|
27
|
+
if: ${{
|
|
28
|
+
!endsWith(github.actor, '[bot]') &&
|
|
29
|
+
(
|
|
30
|
+
(github.event.action == 'opened') ||
|
|
31
|
+
(github.event.action == 'labeled' && github.event.label.name == 'claude-review') ||
|
|
32
|
+
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'claude-review'))
|
|
33
|
+
)
|
|
34
|
+
}}
|
|
35
|
+
|
|
36
|
+
permissions:
|
|
37
|
+
contents: read
|
|
38
|
+
pull-requests: read
|
|
39
|
+
issues: read
|
|
40
|
+
id-token: write
|
|
41
|
+
|
|
42
|
+
steps:
|
|
43
|
+
- name: Checkout repository
|
|
44
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
45
|
+
with:
|
|
46
|
+
fetch-depth: 1
|
|
47
|
+
|
|
48
|
+
- name: Run Claude Code Review
|
|
49
|
+
id: claude-review
|
|
50
|
+
uses: anthropics/claude-code-action@beta
|
|
51
|
+
with:
|
|
52
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
53
|
+
|
|
54
|
+
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
|
|
55
|
+
# model: "claude-opus-4-20250514"
|
|
56
|
+
|
|
57
|
+
# Direct prompt for automated review (no @claude mention needed)
|
|
58
|
+
direct_prompt: |
|
|
59
|
+
Please review this pull request and provide feedback on:
|
|
60
|
+
- Code quality and best practices
|
|
61
|
+
- Potential bugs or issues
|
|
62
|
+
- Performance considerations
|
|
63
|
+
- Security concerns
|
|
64
|
+
- Test coverage
|
|
65
|
+
|
|
66
|
+
Be constructive and helpful in your feedback.
|
|
67
|
+
|
|
68
|
+
# Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
|
|
69
|
+
use_sticky_comment: true
|
|
70
|
+
|
|
71
|
+
# Optional: Different prompts for different authors
|
|
72
|
+
# direct_prompt: |
|
|
73
|
+
# ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
|
|
74
|
+
# 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
|
|
75
|
+
# 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
|
|
76
|
+
|
|
77
|
+
# Optional: Add specific tools for running tests or linting
|
|
78
|
+
# allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)"
|
|
79
|
+
|
|
80
|
+
# Optional: Skip review for certain conditions
|
|
81
|
+
# if: |
|
|
82
|
+
# !contains(github.event.pull_request.title, '[skip-review]') &&
|
|
83
|
+
# !contains(github.event.pull_request.title, '[WIP]')
|
|
84
|
+
|
|
85
|
+
- name: Remove claude-review label
|
|
86
|
+
# Remove label whether success or failure - prevents getting stuck
|
|
87
|
+
if: always() && github.event.action != 'opened'
|
|
88
|
+
uses: actions/github-script@v7
|
|
89
|
+
with:
|
|
90
|
+
script: |
|
|
91
|
+
try {
|
|
92
|
+
await github.rest.issues.removeLabel({
|
|
93
|
+
owner: context.repo.owner,
|
|
94
|
+
repo: context.repo.repo,
|
|
95
|
+
issue_number: context.issue.number,
|
|
96
|
+
name: 'claude-review'
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.log('Label not found or already removed');
|
|
100
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# .github/workflows/claude.yml
|
|
2
|
+
|
|
3
|
+
name: Claude Code
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
pull_request:
|
|
7
|
+
branches:
|
|
8
|
+
- 'feature/*'
|
|
9
|
+
- 'fix/*'
|
|
10
|
+
- 'wip/*'
|
|
11
|
+
- 'rel/*'
|
|
12
|
+
- 'develop'
|
|
13
|
+
- 'main'
|
|
14
|
+
issue_comment:
|
|
15
|
+
types: [created]
|
|
16
|
+
pull_request_review_comment:
|
|
17
|
+
types: [created]
|
|
18
|
+
issues:
|
|
19
|
+
types: [opened, assigned]
|
|
20
|
+
pull_request_review:
|
|
21
|
+
types: [submitted]
|
|
22
|
+
workflow_dispatch:
|
|
23
|
+
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
claude:
|
|
29
|
+
if: |
|
|
30
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
31
|
+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
32
|
+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
|
33
|
+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
permissions:
|
|
36
|
+
contents: read
|
|
37
|
+
pull-requests: read
|
|
38
|
+
issues: read
|
|
39
|
+
id-token: write
|
|
40
|
+
actions: read # Required for Claude to read CI results on PRs
|
|
41
|
+
steps:
|
|
42
|
+
- name: Checkout repository
|
|
43
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
44
|
+
with:
|
|
45
|
+
fetch-depth: 1
|
|
46
|
+
|
|
47
|
+
- name: Run Claude Code
|
|
48
|
+
id: claude
|
|
49
|
+
uses: anthropics/claude-code-action@beta
|
|
50
|
+
with:
|
|
51
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
52
|
+
|
|
53
|
+
# This is an optional setting that allows Claude to read CI results on PRs
|
|
54
|
+
additional_permissions: |
|
|
55
|
+
actions: read
|
|
56
|
+
|
|
57
|
+
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
|
|
58
|
+
# model: "claude-opus-4-20250514"
|
|
59
|
+
|
|
60
|
+
# Optional: Customize the trigger phrase (default: @claude)
|
|
61
|
+
# trigger_phrase: "/claude"
|
|
62
|
+
|
|
63
|
+
# Optional: Trigger when specific user is assigned to an issue
|
|
64
|
+
# assignee_trigger: "claude-bot"
|
|
65
|
+
|
|
66
|
+
# Optional: Allow Claude to run specific commands
|
|
67
|
+
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
|
|
68
|
+
|
|
69
|
+
# Optional: Add custom instructions for Claude to customize its behavior for your project
|
|
70
|
+
# custom_instructions: |
|
|
71
|
+
# Follow our coding standards
|
|
72
|
+
# Ensure all new code has tests
|
|
73
|
+
# Use TypeScript for new files
|
|
74
|
+
|
|
75
|
+
# Optional: Custom environment variables for Claude
|
|
76
|
+
# claude_env: |
|
|
77
|
+
# NODE_ENV: test
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
enable-cache: true
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: '3.11'
|
|
26
|
+
|
|
27
|
+
- name: Build package
|
|
28
|
+
run: uv build
|
|
29
|
+
|
|
30
|
+
- name: Create GitHub Release
|
|
31
|
+
uses: softprops/action-gh-release@v1
|
|
32
|
+
with:
|
|
33
|
+
files: dist/*
|
|
34
|
+
generate_release_notes: true
|
rots-0.4.0/.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
ENV/
|
|
16
|
+
|
|
17
|
+
# IDE
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/
|
|
20
|
+
*.swp
|
|
21
|
+
*.swo
|
|
22
|
+
*.txt
|
|
23
|
+
|
|
24
|
+
# OS
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
27
|
+
|
|
28
|
+
.claude
|
|
29
|
+
.serena
|
|
30
|
+
.coverage
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Pre-Commit Hooks Configuration
|
|
3
|
+
#
|
|
4
|
+
# Fast, lightweight code quality checks that run before each commit
|
|
5
|
+
#
|
|
6
|
+
# Setup:
|
|
7
|
+
# 1. Install pre-commit:
|
|
8
|
+
# $ pip install pre-commit
|
|
9
|
+
#
|
|
10
|
+
# 2. Install git hooks:
|
|
11
|
+
# $ pre-commit install
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# Hooks run automatically on 'git commit'
|
|
15
|
+
#
|
|
16
|
+
# Manual commands:
|
|
17
|
+
# - Check all files:
|
|
18
|
+
# $ pre-commit run --all-files
|
|
19
|
+
#
|
|
20
|
+
# - Update hooks:
|
|
21
|
+
# $ pre-commit autoupdate
|
|
22
|
+
#
|
|
23
|
+
# - Reinstall after config changes:
|
|
24
|
+
# $ pre-commit install
|
|
25
|
+
#
|
|
26
|
+
# Best Practices:
|
|
27
|
+
# - Reinstall hooks after modifying this config
|
|
28
|
+
# - Commit config changes in isolation
|
|
29
|
+
# - Keep checks fast to maintain workflow
|
|
30
|
+
#
|
|
31
|
+
# Resources:
|
|
32
|
+
# - Docs: https://pre-commit.com
|
|
33
|
+
# - Available hooks: https://pre-commit.com/hooks.html
|
|
34
|
+
|
|
35
|
+
# Hook installation configuration
|
|
36
|
+
default_install_hook_types:
|
|
37
|
+
- pre-commit # Primary code quality checks
|
|
38
|
+
- prepare-commit-msg # Commit message preprocessing
|
|
39
|
+
- post-commit # Actions after successful commit
|
|
40
|
+
- post-checkout # Triggered after git checkout
|
|
41
|
+
- post-merge # Triggered after git merge
|
|
42
|
+
|
|
43
|
+
# Default execution stage
|
|
44
|
+
default_stages: [pre-commit]
|
|
45
|
+
|
|
46
|
+
# Allow all hooks to run even if one (or more fails). This avoids
|
|
47
|
+
# the commit->fail->fix->commit->fail->fix->commit routine.
|
|
48
|
+
fail_fast: false
|
|
49
|
+
|
|
50
|
+
repos:
|
|
51
|
+
# Meta hooks: basic checks for pre-commit config itself
|
|
52
|
+
- repo: meta
|
|
53
|
+
hooks:
|
|
54
|
+
- id: check-hooks-apply
|
|
55
|
+
- id: check-useless-excludes
|
|
56
|
+
|
|
57
|
+
# Standard pre-commit hooks: lightweight, universal checks
|
|
58
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
59
|
+
rev: v5.0.0
|
|
60
|
+
hooks:
|
|
61
|
+
# Formatting and basic sanitization
|
|
62
|
+
- id: trailing-whitespace # Remove trailing whitespaces
|
|
63
|
+
- id: end-of-file-fixer # Ensure files end with newline
|
|
64
|
+
- id: check-merge-conflict # Detect unresolved merge conflicts
|
|
65
|
+
- id: detect-private-key # Warn about committing private keys
|
|
66
|
+
- id: check-added-large-files # Prevent committing oversized files
|
|
67
|
+
args: ["--maxkb=2500"] # 2.5MB file size threshold
|
|
68
|
+
- id: no-commit-to-branch # Prevent direct commits to critical branches
|
|
69
|
+
args: ["--branch", "main"]
|
|
70
|
+
|
|
71
|
+
# Commit message issue tracking integration
|
|
72
|
+
- repo: https://github.com/delano/add-msg-issue-prefix-hook
|
|
73
|
+
rev: v0.0.14-fork
|
|
74
|
+
hooks:
|
|
75
|
+
- id: add-msg-issue-prefix
|
|
76
|
+
stages: [prepare-commit-msg]
|
|
77
|
+
description: Automatically prefix commits with issue numbers
|
|
78
|
+
args:
|
|
79
|
+
- "--default="
|
|
80
|
+
- "--exclude-pattern=^(dependabot|renovate)/"
|
|
81
|
+
- "--pattern=(i18n(?=/)|([a-zA-Z0-9]{0,10}-?[0-9]{1,5}))"
|
|
82
|
+
- "--template=[#{}]"
|
|
83
|
+
|
|
84
|
+
# Pyright - static type checking (matches IDE)
|
|
85
|
+
# Only checks changed files rather than all of src/ for speed
|
|
86
|
+
- repo: https://github.com/RobertCraigie/pyright-python
|
|
87
|
+
rev: v1.1.390
|
|
88
|
+
hooks:
|
|
89
|
+
- id: pyright
|
|
90
|
+
files: ^src/
|
|
91
|
+
additional_dependencies:
|
|
92
|
+
- cyclopts>=3.9
|
|
93
|
+
- pytest>=8.0
|
|
94
|
+
|
|
95
|
+
# Ruff - fast linting and formatting
|
|
96
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
97
|
+
rev: v0.9.2
|
|
98
|
+
hooks:
|
|
99
|
+
- id: ruff
|
|
100
|
+
args: [--fix]
|
|
101
|
+
- id: ruff-format
|
|
102
|
+
|
|
103
|
+
# pytest - run before push with coverage and last-failed optimization
|
|
104
|
+
# Skipped on commit for speed; full suite gates push instead
|
|
105
|
+
- repo: local
|
|
106
|
+
hooks:
|
|
107
|
+
- id: pytest-cov
|
|
108
|
+
name: pytest-cov
|
|
109
|
+
entry: .venv/bin/pytest tests/ -x -q --ff --cov=rots --cov-report=term-missing --cov-fail-under=70
|
|
110
|
+
language: system
|
|
111
|
+
pass_filenames: false
|
|
112
|
+
files: ^(src/|tests/).*\.py$
|
|
113
|
+
stages: [pre-push]
|
rots-0.4.0/CHANGELOG.rst
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
CHANGELOG
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
All notable changes to rots are documented here.
|
|
5
|
+
|
|
6
|
+
The format is based on `Keep a
|
|
7
|
+
Changelog <https://keepachangelog.com/en/1.1.0/>`__, and this project
|
|
8
|
+
adheres to `Semantic
|
|
9
|
+
Versioning <https://semver.org/spec/v2.0.0.html>`__.
|
|
10
|
+
|
|
11
|
+
.. raw:: html
|
|
12
|
+
|
|
13
|
+
<!--scriv-insert-here-->
|
rots-0.4.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Build & Test Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install for development (editable)
|
|
9
|
+
pip install -e ".[dev,test]"
|
|
10
|
+
|
|
11
|
+
# Run all tests
|
|
12
|
+
pytest tests/
|
|
13
|
+
|
|
14
|
+
# Run single test file
|
|
15
|
+
pytest tests/test_quadlet.py
|
|
16
|
+
|
|
17
|
+
# Run single test by name
|
|
18
|
+
pytest tests/test_quadlet.py -k "test_template"
|
|
19
|
+
|
|
20
|
+
# Run service management tests
|
|
21
|
+
pytest tests/test_service.py
|
|
22
|
+
|
|
23
|
+
# Run cloud-init tests
|
|
24
|
+
pytest tests/commands/cloudinit/
|
|
25
|
+
|
|
26
|
+
# Run tests with coverage (CI threshold: 70%)
|
|
27
|
+
pytest tests/ --cov=rots --cov-report=term-missing --cov-fail-under=70
|
|
28
|
+
|
|
29
|
+
# IMPORTANT: See docs/TESTING.md for testing patterns
|
|
30
|
+
# Key rule: mock responses must use tmp_path, not real system paths
|
|
31
|
+
|
|
32
|
+
# Lint and format
|
|
33
|
+
ruff check src/
|
|
34
|
+
ruff format src/
|
|
35
|
+
ruff check src/ --fix # auto-fix
|
|
36
|
+
|
|
37
|
+
# Type checking
|
|
38
|
+
pyright src/
|
|
39
|
+
|
|
40
|
+
# Pre-commit hooks (auto-installed)
|
|
41
|
+
pre-commit run --all-files
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Git Notes
|
|
45
|
+
|
|
46
|
+
When running git commands with long output, use `git --no-pager diff` etc.
|
|
47
|
+
|
|
48
|
+
## Architecture
|
|
49
|
+
|
|
50
|
+
This is a dual-purpose service orchestration tool:
|
|
51
|
+
|
|
52
|
+
1. **Container management**: OneTimeSecret containers via Podman Quadlets (systemd integration)
|
|
53
|
+
2. **Service management**: Native systemd services for dependencies (Valkey, Redis)
|
|
54
|
+
|
|
55
|
+
### Core Modules (`src/rots/`)
|
|
56
|
+
|
|
57
|
+
- **cli.py** - Entry point (`app`), registers subcommand groups
|
|
58
|
+
- **config.py** - `Config` dataclass: image, tag, paths. Reads from env vars (IMAGE, TAG, etc.)
|
|
59
|
+
- **quadlet.py** - Writes systemd Quadlet template to `/etc/containers/systemd/onetime@.container`
|
|
60
|
+
- **systemd.py** - Wrappers around `systemctl`: start/stop/restart/status, `discover_instances()` for auto-detection
|
|
61
|
+
- **podman.py** - `Podman` class: chainable interface to podman CLI (e.g., `podman.container.ls()`)
|
|
62
|
+
- **assets.py** - Extracts `/app/public` from container image to shared volume
|
|
63
|
+
|
|
64
|
+
### Commands (`src/rots/commands/`)
|
|
65
|
+
|
|
66
|
+
#### Container Commands
|
|
67
|
+
|
|
68
|
+
- **instance.py** - Main operations: `deploy`, `redeploy`, `undeploy`, `start`, `stop`, `restart`, `status`, `logs`, `list`
|
|
69
|
+
- **assets.py** - `sync` command for static asset updates
|
|
70
|
+
- **image.py** - Container image management
|
|
71
|
+
- **proxy.py** - Caddy reverse proxy configuration
|
|
72
|
+
|
|
73
|
+
#### Service Commands (`service/`)
|
|
74
|
+
|
|
75
|
+
- **app.py** - Service lifecycle: `init`, `start`, `stop`, `restart`, `status`, `logs`, `enable`, `disable`, `list_instances`
|
|
76
|
+
- **packages.py** - Service package definitions: `VALKEY`, `REDIS` with config paths, secrets handling, systemd templates
|
|
77
|
+
- **\_helpers.py** - Shared utilities: config file management, secrets creation, systemctl wrappers
|
|
78
|
+
|
|
79
|
+
#### Cloud-Init Commands (`cloudinit/`)
|
|
80
|
+
|
|
81
|
+
- **app.py** - Cloud-init generation: `generate`, `validate`
|
|
82
|
+
- **templates.py** - DEB822-style apt sources templates for Debian 13 (Trixie), PostgreSQL, and Valkey repositories
|
|
83
|
+
|
|
84
|
+
### Key Patterns
|
|
85
|
+
|
|
86
|
+
- Uses **cyclopts** for CLI framework (decorators like `@app.command()`)
|
|
87
|
+
- **Port-based instance identification**: Each instance runs on a specific port (e.g., 7043 for containers, 6379 for services)
|
|
88
|
+
- **Container auto-discovery**: `systemd.discover_instances()` finds running `onetime@*` services
|
|
89
|
+
- **Container env templating**: `/etc/onetimesecret/.env` → `/var/lib/onetimesecret/.env-{port}` (FHS-compliant)
|
|
90
|
+
- **Service config copy-on-write**: Package defaults (`/etc/valkey/valkey.conf`) → instance configs (`/etc/valkey/instances/6379.conf`)
|
|
91
|
+
- **Service secrets separation**: Sensitive data in separate files with restricted permissions (mode 0640, owned by service user)
|
|
92
|
+
- **Package-provided templates**: Uses existing systemd templates (`valkey-server@.service`) rather than creating custom units
|
|
93
|
+
- ## Verification
|
|
94
|
+
Don't invent technical rationales. When working with runtime behavior, get or
|
|
95
|
+
ask for actual output (podman ps, systemctl status, etc.) before changing code.
|
|
96
|
+
Documentation and memories are not substitutes for verified information.
|
rots-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Onetime Secret
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|