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.
Files changed (150) hide show
  1. rots-0.4.0/.git +1 -0
  2. rots-0.4.0/.github/workflows/ci.yml +116 -0
  3. rots-0.4.0/.github/workflows/claude-code-review.yml +100 -0
  4. rots-0.4.0/.github/workflows/claude.yml +77 -0
  5. rots-0.4.0/.github/workflows/release.yml +34 -0
  6. rots-0.4.0/.gitignore +30 -0
  7. rots-0.4.0/.pre-commit-config.yaml +113 -0
  8. rots-0.4.0/CHANGELOG.rst +13 -0
  9. rots-0.4.0/CLAUDE.md +96 -0
  10. rots-0.4.0/LICENSE +21 -0
  11. rots-0.4.0/PKG-INFO +287 -0
  12. rots-0.4.0/README.md +251 -0
  13. rots-0.4.0/changelog.d/20260224_operational_improvements.rst +25 -0
  14. rots-0.4.0/changelog.d/20260225_image_ref_security.rst +28 -0
  15. rots-0.4.0/changelog.d/20260226_rename_to_rots.rst +11 -0
  16. rots-0.4.0/changelog.d/README.md +46 -0
  17. rots-0.4.0/changelog.d/scriv.ini +5 -0
  18. rots-0.4.0/docs/architecture/decision-records/README.md +41 -0
  19. rots-0.4.0/docs/architecture/decision-records/adr-000.md +32 -0
  20. rots-0.4.0/docs/architecture/decision-records/adr-001.md +84 -0
  21. rots-0.4.0/docs/architecture/decision-records/adr-002.md +84 -0
  22. rots-0.4.0/docs/networking.md +137 -0
  23. rots-0.4.0/docs/remote-execution.md +145 -0
  24. rots-0.4.0/docs/testing.md +174 -0
  25. rots-0.4.0/docs/wips/.gitignore +1 -0
  26. rots-0.4.0/docs/wips/0205-00-environment-configuration-skeleton.md +340 -0
  27. rots-0.4.0/docs/wips/0205-01-the-vision-of-the-tooling.md +306 -0
  28. rots-0.4.0/docs/wips/0205-02-four-layer-conceptual-model-draft.md +36 -0
  29. rots-0.4.0/docs/wips/0205-02-four-layer-model-draft2.md +108 -0
  30. rots-0.4.0/docs/wips/0205-02-layer4-the-gateway-bastion.md +38 -0
  31. rots-0.4.0/docs/wips/0205-03-layer0-dnsmasq.md +204 -0
  32. rots-0.4.0/docs/wips/0205-04-layer0-dnsmasq-draft2.md +119 -0
  33. rots-0.4.0/docs/wips/0205-04-layer1-sshtmux.md +6 -0
  34. rots-0.4.0/docs/wips/0205-04-ots-host-config-push-HANDOFF.md +115 -0
  35. rots-0.4.0/docs/wips/0205-04-the-vision-of-the-tooling-draft2.md +252 -0
  36. rots-0.4.0/docs/wips/0205-04-three-layer-model.md +188 -0
  37. rots-0.4.0/docs/wips/0206-MANUAL-OPERATIONS.md +712 -0
  38. rots-0.4.0/packages/ots-shared/pyproject.toml +52 -0
  39. rots-0.4.0/packages/ots-shared/src/ots_shared/__init__.py +1 -0
  40. rots-0.4.0/packages/ots-shared/src/ots_shared/cli.py +70 -0
  41. rots-0.4.0/packages/ots-shared/src/ots_shared/exit_codes.py +29 -0
  42. rots-0.4.0/packages/ots-shared/src/ots_shared/ssh/__init__.py +51 -0
  43. rots-0.4.0/packages/ots-shared/src/ots_shared/ssh/_pty.py +172 -0
  44. rots-0.4.0/packages/ots-shared/src/ots_shared/ssh/connection.py +152 -0
  45. rots-0.4.0/packages/ots-shared/src/ots_shared/ssh/env.py +191 -0
  46. rots-0.4.0/packages/ots-shared/src/ots_shared/ssh/executor.py +483 -0
  47. rots-0.4.0/packages/ots-shared/src/ots_shared/taxonomy.py +100 -0
  48. rots-0.4.0/packages/ots-shared/tests/__init__.py +0 -0
  49. rots-0.4.0/packages/ots-shared/tests/conftest.py +101 -0
  50. rots-0.4.0/packages/ots-shared/tests/ssh/__init__.py +0 -0
  51. rots-0.4.0/packages/ots-shared/tests/ssh/test_connection.py +150 -0
  52. rots-0.4.0/packages/ots-shared/tests/ssh/test_env.py +349 -0
  53. rots-0.4.0/packages/ots-shared/tests/ssh/test_executor.py +913 -0
  54. rots-0.4.0/packages/ots-shared/tests/ssh/test_pty.py +173 -0
  55. rots-0.4.0/packages/ots-shared/tests/test_cli.py +129 -0
  56. rots-0.4.0/packages/ots-shared/tests/test_exit_codes.py +43 -0
  57. rots-0.4.0/packages/ots-shared/tests/test_taxonomy.py +56 -0
  58. rots-0.4.0/pyproject.toml +89 -0
  59. rots-0.4.0/scripts/rename_to_rots.sh +58 -0
  60. rots-0.4.0/src/rots/__init__.py +8 -0
  61. rots-0.4.0/src/rots/__main__.py +7 -0
  62. rots-0.4.0/src/rots/assets.py +96 -0
  63. rots-0.4.0/src/rots/cli.py +333 -0
  64. rots-0.4.0/src/rots/commands/README.md +128 -0
  65. rots-0.4.0/src/rots/commands/__init__.py +13 -0
  66. rots-0.4.0/src/rots/commands/assets.py +31 -0
  67. rots-0.4.0/src/rots/commands/cloudinit/__init__.py +6 -0
  68. rots-0.4.0/src/rots/commands/cloudinit/app.py +192 -0
  69. rots-0.4.0/src/rots/commands/cloudinit/templates.py +376 -0
  70. rots-0.4.0/src/rots/commands/common.py +122 -0
  71. rots-0.4.0/src/rots/commands/db.py +467 -0
  72. rots-0.4.0/src/rots/commands/env/__init__.py +6 -0
  73. rots-0.4.0/src/rots/commands/env/app.py +495 -0
  74. rots-0.4.0/src/rots/commands/host/__init__.py +6 -0
  75. rots-0.4.0/src/rots/commands/host/_manifest.py +82 -0
  76. rots-0.4.0/src/rots/commands/host/_rsync.py +179 -0
  77. rots-0.4.0/src/rots/commands/host/app.py +451 -0
  78. rots-0.4.0/src/rots/commands/image/__init__.py +6 -0
  79. rots-0.4.0/src/rots/commands/image/app.py +1691 -0
  80. rots-0.4.0/src/rots/commands/init.py +428 -0
  81. rots-0.4.0/src/rots/commands/instance/__init__.py +69 -0
  82. rots-0.4.0/src/rots/commands/instance/_helpers.py +462 -0
  83. rots-0.4.0/src/rots/commands/instance/annotations.py +112 -0
  84. rots-0.4.0/src/rots/commands/instance/app.py +2441 -0
  85. rots-0.4.0/src/rots/commands/proxy/__init__.py +11 -0
  86. rots-0.4.0/src/rots/commands/proxy/_helpers.py +168 -0
  87. rots-0.4.0/src/rots/commands/proxy/app.py +266 -0
  88. rots-0.4.0/src/rots/commands/service/__init__.py +6 -0
  89. rots-0.4.0/src/rots/commands/service/_helpers.py +433 -0
  90. rots-0.4.0/src/rots/commands/service/app.py +608 -0
  91. rots-0.4.0/src/rots/commands/service/packages.py +218 -0
  92. rots-0.4.0/src/rots/config.py +539 -0
  93. rots-0.4.0/src/rots/context.py +13 -0
  94. rots-0.4.0/src/rots/db.py +865 -0
  95. rots-0.4.0/src/rots/environment_file.py +526 -0
  96. rots-0.4.0/src/rots/podman.py +117 -0
  97. rots-0.4.0/src/rots/quadlet.py +686 -0
  98. rots-0.4.0/src/rots/systemd.py +666 -0
  99. rots-0.4.0/tests/__init__.py +1 -0
  100. rots-0.4.0/tests/commands/__init__.py +1 -0
  101. rots-0.4.0/tests/commands/cloudinit/__init__.py +2 -0
  102. rots-0.4.0/tests/commands/cloudinit/test_app.py +265 -0
  103. rots-0.4.0/tests/commands/cloudinit/test_templates.py +463 -0
  104. rots-0.4.0/tests/commands/host/__init__.py +0 -0
  105. rots-0.4.0/tests/commands/host/test_app.py +422 -0
  106. rots-0.4.0/tests/commands/host/test_manifest.py +111 -0
  107. rots-0.4.0/tests/commands/host/test_rsync.py +154 -0
  108. rots-0.4.0/tests/commands/image/__init__.py +2 -0
  109. rots-0.4.0/tests/commands/image/test_app.py +1698 -0
  110. rots-0.4.0/tests/commands/image/test_build.py +1274 -0
  111. rots-0.4.0/tests/commands/image/test_image_ref_parsing.py +225 -0
  112. rots-0.4.0/tests/commands/instance/__init__.py +2 -0
  113. rots-0.4.0/tests/commands/instance/test_app.py +3193 -0
  114. rots-0.4.0/tests/commands/instance/test_config_transform.py +1005 -0
  115. rots-0.4.0/tests/commands/instance/test_deploy_image_ref.py +282 -0
  116. rots-0.4.0/tests/commands/instance/test_health_and_ps.py +402 -0
  117. rots-0.4.0/tests/commands/instance/test_helpers.py +685 -0
  118. rots-0.4.0/tests/commands/instance/test_run_image_ref.py +248 -0
  119. rots-0.4.0/tests/commands/instance/test_shell.py +476 -0
  120. rots-0.4.0/tests/commands/proxy/__init__.py +1 -0
  121. rots-0.4.0/tests/commands/proxy/test_app.py +423 -0
  122. rots-0.4.0/tests/commands/proxy/test_helpers.py +344 -0
  123. rots-0.4.0/tests/commands/service/__init__.py +2 -0
  124. rots-0.4.0/tests/commands/service/test_app.py +1043 -0
  125. rots-0.4.0/tests/commands/service/test_helpers.py +376 -0
  126. rots-0.4.0/tests/commands/service/test_packages.py +168 -0
  127. rots-0.4.0/tests/commands/test_assets.py +115 -0
  128. rots-0.4.0/tests/commands/test_db_commands.py +590 -0
  129. rots-0.4.0/tests/commands/test_env.py +628 -0
  130. rots-0.4.0/tests/commands/test_init.py +747 -0
  131. rots-0.4.0/tests/conftest.py +26 -0
  132. rots-0.4.0/tests/integration/__init__.py +1 -0
  133. rots-0.4.0/tests/integration/conftest.py +371 -0
  134. rots-0.4.0/tests/integration/test_smoke.py +206 -0
  135. rots-0.4.0/tests/integration/test_ssh_remote.py +368 -0
  136. rots-0.4.0/tests/test_assets.py +411 -0
  137. rots-0.4.0/tests/test_cli.py +588 -0
  138. rots-0.4.0/tests/test_config.py +1612 -0
  139. rots-0.4.0/tests/test_db.py +2097 -0
  140. rots-0.4.0/tests/test_environment_file.py +685 -0
  141. rots-0.4.0/tests/test_image_build.py +335 -0
  142. rots-0.4.0/tests/test_image_reference_precedence.py +324 -0
  143. rots-0.4.0/tests/test_image_reference_security.py +332 -0
  144. rots-0.4.0/tests/test_main.py +46 -0
  145. rots-0.4.0/tests/test_parse_image_reference.py +131 -0
  146. rots-0.4.0/tests/test_podman.py +329 -0
  147. rots-0.4.0/tests/test_quadlet.py +1524 -0
  148. rots-0.4.0/tests/test_quadlet_render.py +288 -0
  149. rots-0.4.0/tests/test_systemd.py +2051 -0
  150. 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]
@@ -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.