ots-containers 0.3.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 (90) hide show
  1. ots_containers-0.3.0/.claude/settings.local.json +42 -0
  2. ots_containers-0.3.0/.github/workflows/ci.yml +69 -0
  3. ots_containers-0.3.0/.github/workflows/claude-code-review.yml +95 -0
  4. ots_containers-0.3.0/.github/workflows/claude.yml +77 -0
  5. ots_containers-0.3.0/.github/workflows/release.yml +32 -0
  6. ots_containers-0.3.0/.gitignore +28 -0
  7. ots_containers-0.3.0/.pre-commit-config.yaml +122 -0
  8. ots_containers-0.3.0/.serena/.gitignore +2 -0
  9. ots_containers-0.3.0/.serena/cache/python/document_symbols.pkl +0 -0
  10. ots_containers-0.3.0/.serena/cache/python/raw_document_symbols.pkl +0 -0
  11. ots_containers-0.3.0/.serena/memories/.keep +0 -0
  12. ots_containers-0.3.0/.serena/memories/1215-dbus-conversion-technical-notes.md +81 -0
  13. ots_containers-0.3.0/.serena/memories/1216-ots-service-command-design.md +107 -0
  14. ots_containers-0.3.0/.serena/memories/fhs-directory-standards.md +371 -0
  15. ots_containers-0.3.0/.serena/memories/next-dev-handoff.md +65 -0
  16. ots_containers-0.3.0/.serena/memories/ots-containers-conformance-status.md +67 -0
  17. ots_containers-0.3.0/.serena/memories/podman-quadlet-production-standards.md +99 -0
  18. ots_containers-0.3.0/.serena/project.yml +84 -0
  19. ots_containers-0.3.0/CLAUDE.md +92 -0
  20. ots_containers-0.3.0/LICENSE +21 -0
  21. ots_containers-0.3.0/NETWORKING.md +136 -0
  22. ots_containers-0.3.0/PKG-INFO +284 -0
  23. ots_containers-0.3.0/README.md +250 -0
  24. ots_containers-0.3.0/docs/MANUAL-OPERATIONS.md +700 -0
  25. ots_containers-0.3.0/docs/TESTING.md +174 -0
  26. ots_containers-0.3.0/docs/architecture/decision-records/README.md +41 -0
  27. ots_containers-0.3.0/docs/architecture/decision-records/adr-000.md +32 -0
  28. ots_containers-0.3.0/docs/architecture/decision-records/adr-001.md +84 -0
  29. ots_containers-0.3.0/pyproject.toml +67 -0
  30. ots_containers-0.3.0/src/ots_containers/__init__.py +3 -0
  31. ots_containers-0.3.0/src/ots_containers/assets.py +32 -0
  32. ots_containers-0.3.0/src/ots_containers/cli.py +86 -0
  33. ots_containers-0.3.0/src/ots_containers/commands/README.md +124 -0
  34. ots_containers-0.3.0/src/ots_containers/commands/__init__.py +9 -0
  35. ots_containers-0.3.0/src/ots_containers/commands/assets.py +29 -0
  36. ots_containers-0.3.0/src/ots_containers/commands/cloudinit/__init__.py +6 -0
  37. ots_containers-0.3.0/src/ots_containers/commands/cloudinit/app.py +158 -0
  38. ots_containers-0.3.0/src/ots_containers/commands/cloudinit/templates.py +127 -0
  39. ots_containers-0.3.0/src/ots_containers/commands/common.py +71 -0
  40. ots_containers-0.3.0/src/ots_containers/commands/env/__init__.py +6 -0
  41. ots_containers-0.3.0/src/ots_containers/commands/env/app.py +348 -0
  42. ots_containers-0.3.0/src/ots_containers/commands/image/__init__.py +6 -0
  43. ots_containers-0.3.0/src/ots_containers/commands/image/app.py +1137 -0
  44. ots_containers-0.3.0/src/ots_containers/commands/init.py +253 -0
  45. ots_containers-0.3.0/src/ots_containers/commands/instance/__init__.py +57 -0
  46. ots_containers-0.3.0/src/ots_containers/commands/instance/_helpers.py +161 -0
  47. ots_containers-0.3.0/src/ots_containers/commands/instance/annotations.py +111 -0
  48. ots_containers-0.3.0/src/ots_containers/commands/instance/app.py +935 -0
  49. ots_containers-0.3.0/src/ots_containers/commands/proxy/__init__.py +11 -0
  50. ots_containers-0.3.0/src/ots_containers/commands/proxy/_helpers.py +95 -0
  51. ots_containers-0.3.0/src/ots_containers/commands/proxy/app.py +160 -0
  52. ots_containers-0.3.0/src/ots_containers/commands/service/__init__.py +6 -0
  53. ots_containers-0.3.0/src/ots_containers/commands/service/_helpers.py +294 -0
  54. ots_containers-0.3.0/src/ots_containers/commands/service/app.py +498 -0
  55. ots_containers-0.3.0/src/ots_containers/commands/service/packages.py +217 -0
  56. ots_containers-0.3.0/src/ots_containers/config.py +158 -0
  57. ots_containers-0.3.0/src/ots_containers/db.py +582 -0
  58. ots_containers-0.3.0/src/ots_containers/environment_file.py +491 -0
  59. ots_containers-0.3.0/src/ots_containers/podman.py +60 -0
  60. ots_containers-0.3.0/src/ots_containers/quadlet.py +341 -0
  61. ots_containers-0.3.0/src/ots_containers/systemd.py +217 -0
  62. ots_containers-0.3.0/tests/__init__.py +1 -0
  63. ots_containers-0.3.0/tests/commands/__init__.py +1 -0
  64. ots_containers-0.3.0/tests/commands/cloudinit/__init__.py +2 -0
  65. ots_containers-0.3.0/tests/commands/cloudinit/test_app.py +181 -0
  66. ots_containers-0.3.0/tests/commands/cloudinit/test_templates.py +163 -0
  67. ots_containers-0.3.0/tests/commands/image/__init__.py +2 -0
  68. ots_containers-0.3.0/tests/commands/image/test_app.py +255 -0
  69. ots_containers-0.3.0/tests/commands/image/test_build.py +711 -0
  70. ots_containers-0.3.0/tests/commands/instance/__init__.py +2 -0
  71. ots_containers-0.3.0/tests/commands/instance/test_app.py +929 -0
  72. ots_containers-0.3.0/tests/commands/instance/test_helpers.py +276 -0
  73. ots_containers-0.3.0/tests/commands/proxy/__init__.py +1 -0
  74. ots_containers-0.3.0/tests/commands/proxy/test_app.py +303 -0
  75. ots_containers-0.3.0/tests/commands/proxy/test_helpers.py +167 -0
  76. ots_containers-0.3.0/tests/commands/service/__init__.py +2 -0
  77. ots_containers-0.3.0/tests/commands/service/test_app.py +388 -0
  78. ots_containers-0.3.0/tests/commands/service/test_helpers.py +376 -0
  79. ots_containers-0.3.0/tests/commands/service/test_packages.py +160 -0
  80. ots_containers-0.3.0/tests/commands/test_assets.py +77 -0
  81. ots_containers-0.3.0/tests/commands/test_init.py +400 -0
  82. ots_containers-0.3.0/tests/test_assets.py +182 -0
  83. ots_containers-0.3.0/tests/test_cli.py +97 -0
  84. ots_containers-0.3.0/tests/test_config.py +143 -0
  85. ots_containers-0.3.0/tests/test_db.py +257 -0
  86. ots_containers-0.3.0/tests/test_environment_file.py +420 -0
  87. ots_containers-0.3.0/tests/test_image_build.py +331 -0
  88. ots_containers-0.3.0/tests/test_podman.py +200 -0
  89. ots_containers-0.3.0/tests/test_quadlet.py +719 -0
  90. ots_containers-0.3.0/tests/test_systemd.py +728 -0
@@ -0,0 +1,42 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(pytest:*)",
5
+ "Bash(python -m pytest:*)",
6
+ "Bash(git --no-pager log --oneline -4)",
7
+ "Bash(git --no-pager log --oneline -20 /Users/d/Projects/opensource/onetime/ots-containers)",
8
+ "Bash(python -m ots_containers.cli:*)",
9
+ "Bash(python:*)",
10
+ "Bash(pyright:*)",
11
+ "Bash(ruff check:*)",
12
+ "Bash(git --no-pager log --oneline -3)",
13
+ "Bash(pip install:*)",
14
+ "Bash(ots-containers cloudinit generate:*)",
15
+ "Bash(ots-containers cloudinit:*)",
16
+ "Bash(ruff format:*)",
17
+ "Bash(SKIP=pytest git commit -m \"Fix Valkey template service config path for Debian convention\n\n- Update ServicePackage to support both subdirectory and flat config layouts\n- Add use_instances_subdir flag to control directory structure\n- Change Valkey config pattern to valkey-{instance}.conf \\(Debian template expects /etc/valkey/valkey-%i.conf\\)\n- Update secrets file pattern to match: valkey-{instance}.secrets\n- Fix ensure_instances_dir\\(\\) to handle both directory structures\n- Update list_instances to correctly parse instance names from both patterns\n- Add check_default_service_conflict\\(\\) to warn about running default service\n- Integrate default service check into init command\n\nResolves issue where valkey-server@6380.service failed with:\n ''Fatal error, can''t open config file /etc/valkey/valkey-6380.conf''\n\nThe Debian systemd template expects configs at /etc/valkey/valkey-{port}.conf,\nnot /etc/valkey/instances/{port}.conf as our code was creating.\")",
18
+ "Bash(SKIP=pytest git commit --no-gpg-sign -m \"Fix Valkey template service config path for Debian convention\n\n- Update ServicePackage to support both subdirectory and flat config layouts\n- Add use_instances_subdir flag to control directory structure\n- Change Valkey config pattern to valkey-{instance}.conf \\(Debian template expects /etc/valkey/valkey-%i.conf\\)\n- Update secrets file pattern to match: valkey-{instance}.secrets\n- Fix ensure_instances_dir\\(\\) to handle both directory structures\n- Update list_instances to correctly parse instance names from both patterns\n- Add check_default_service_conflict\\(\\) to warn about running default service\n- Integrate default service check into init command\n\nResolves issue where valkey-server@6380.service failed with:\n ''Fatal error, can''t open config file /etc/valkey/valkey-6380.conf''\n\nThe Debian systemd template expects configs at /etc/valkey/valkey-{port}.conf,\nnot /etc/valkey/instances/{port}.conf as our code was creating.\")",
19
+ "Bash(SKIP=pytest-cov git push)",
20
+ "Bash(.venv/bin/python:*)",
21
+ "Bash(.venv/bin/pip install:*)",
22
+ "Bash(.venv/bin/pytest tests/ -x -q)",
23
+ "Bash(.venv/bin/pytest tests/test_quadlet.py -x -q)",
24
+ "Bash(.venv/bin/pytest tests/ -q)",
25
+ "Bash(git --no-pager log --oneline -10)",
26
+ "Bash(caddy version:*)",
27
+ "Bash(caddy list-modules:*)",
28
+ "Bash(podman image inspect:*)",
29
+ "Bash(git --no-pager log main..HEAD --oneline)",
30
+ "Bash(git --no-pager branch:*)",
31
+ "Bash(git --no-pager log jan20..HEAD --oneline)",
32
+ "Bash(git --no-pager log delano/dec16..HEAD --oneline)"
33
+ ],
34
+ "enableAllProjectMcpServers": true,
35
+ "enabledMcpjsonServers": [
36
+ "kagi",
37
+ "serena",
38
+ "context7"
39
+ ],
40
+ "includeCoAuthoredBy": false
41
+ }
42
+ }
@@ -0,0 +1,69 @@
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: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ pip install -e ".[test]"
28
+
29
+ - name: Run tests with coverage
30
+ run: |
31
+ pytest tests/ \
32
+ --cov=ots_containers \
33
+ --cov-report=term-missing \
34
+ --cov-report=xml \
35
+ --cov-fail-under=70
36
+
37
+ - name: Upload coverage to Codecov
38
+ if: matrix.python-version == '3.11'
39
+ uses: codecov/codecov-action@v4
40
+ with:
41
+ files: ./coverage.xml
42
+ fail_ci_if_error: false
43
+ verbose: true
44
+
45
+ lint:
46
+ runs-on: ubuntu-latest
47
+
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+
51
+ - name: Set up Python
52
+ uses: actions/setup-python@v5
53
+ with:
54
+ python-version: '3.11'
55
+
56
+ - name: Install dependencies
57
+ run: |
58
+ python -m pip install --upgrade pip
59
+ pip install ruff pyright
60
+ pip install -e ".[dev]"
61
+
62
+ - name: Run ruff linter
63
+ run: ruff check src/
64
+
65
+ - name: Run ruff formatter check
66
+ run: ruff format --check src/
67
+
68
+ - name: Run pyright
69
+ run: pyright src/
@@ -0,0 +1,95 @@
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
+ if: |
27
+ (github.event.action == 'opened') ||
28
+ (github.event.action == 'labeled' && github.event.label.name == 'claude-review') ||
29
+ (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'claude-review'))
30
+
31
+ permissions:
32
+ contents: read
33
+ pull-requests: read
34
+ issues: read
35
+ id-token: write
36
+
37
+ steps:
38
+ - name: Checkout repository
39
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
40
+ with:
41
+ fetch-depth: 1
42
+
43
+ - name: Run Claude Code Review
44
+ id: claude-review
45
+ uses: anthropics/claude-code-action@beta
46
+ with:
47
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
48
+
49
+ # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
50
+ # model: "claude-opus-4-20250514"
51
+
52
+ # Direct prompt for automated review (no @claude mention needed)
53
+ direct_prompt: |
54
+ Please review this pull request and provide feedback on:
55
+ - Code quality and best practices
56
+ - Potential bugs or issues
57
+ - Performance considerations
58
+ - Security concerns
59
+ - Test coverage
60
+
61
+ Be constructive and helpful in your feedback.
62
+
63
+ # Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
64
+ use_sticky_comment: true
65
+
66
+ # Optional: Different prompts for different authors
67
+ # direct_prompt: |
68
+ # ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
69
+ # 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
70
+ # 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
71
+
72
+ # Optional: Add specific tools for running tests or linting
73
+ # allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)"
74
+
75
+ # Optional: Skip review for certain conditions
76
+ # if: |
77
+ # !contains(github.event.pull_request.title, '[skip-review]') &&
78
+ # !contains(github.event.pull_request.title, '[WIP]')
79
+
80
+ - name: Remove claude-review label
81
+ # Remove label whether success or failure - prevents getting stuck
82
+ if: always() && github.event.action != 'opened'
83
+ uses: actions/github-script@v7
84
+ with:
85
+ script: |
86
+ try {
87
+ await github.rest.issues.removeLabel({
88
+ owner: context.repo.owner,
89
+ repo: context.repo.repo,
90
+ issue_number: context.issue.number,
91
+ name: 'claude-review'
92
+ });
93
+ } catch (error) {
94
+ console.log('Label not found or already removed');
95
+ }
@@ -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,32 @@
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: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: '3.11'
21
+
22
+ - name: Install build tools
23
+ run: pip install build
24
+
25
+ - name: Build package
26
+ run: python -m build
27
+
28
+ - name: Create GitHub Release
29
+ uses: softprops/action-gh-release@v1
30
+ with:
31
+ files: dist/*
32
+ generate_release_notes: true
@@ -0,0 +1,28 @@
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
+ .coverage
@@ -0,0 +1,122 @@
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
+ - repo: https://github.com/RobertCraigie/pyright-python
86
+ rev: v1.1.390
87
+ hooks:
88
+ - id: pyright
89
+ args: [src/]
90
+ additional_dependencies:
91
+ - cyclopts>=3.9
92
+ - pytest>=8.0
93
+
94
+ # Ruff - fast linting and formatting
95
+ - repo: https://github.com/astral-sh/ruff-pre-commit
96
+ rev: v0.8.3
97
+ hooks:
98
+ - id: ruff
99
+ args: [--fix]
100
+ - id: ruff-format
101
+
102
+ # pytest - run tests before commit (fast, no coverage)
103
+ # Only runs when Python files in src/ or tests/ are modified
104
+ - repo: local
105
+ hooks:
106
+ - id: pytest
107
+ name: pytest
108
+ entry: .venv/bin/pytest tests/ -x -q
109
+ language: system
110
+ pass_filenames: false
111
+ files: ^(src/|tests/).*\.py$
112
+ stages: [pre-commit]
113
+
114
+ # pytest with coverage - run before push
115
+ # Only runs when Python files in src/ or tests/ are modified
116
+ - id: pytest-cov
117
+ name: pytest-cov
118
+ entry: .venv/bin/pytest tests/ -x -q --cov=ots_containers --cov-report=term-missing --cov-fail-under=70
119
+ language: system
120
+ pass_filenames: false
121
+ files: ^(src/|tests/).*\.py$
122
+ stages: [pre-push]
@@ -0,0 +1,2 @@
1
+ /cache
2
+ *
File without changes
@@ -0,0 +1,81 @@
1
+ # D-Bus Conversion Technical Notes (2025-12-15)
2
+
3
+ ## Issue Reference
4
+ https://github.com/onetimesecret/ots-containers/issues/1
5
+
6
+ ## Problem Context
7
+
8
+ The `systemd.py` module parses `systemctl` CLI output, which caused a production bug:
9
+ - `discover_instances()` returned failed units as "running"
10
+ - Root cause: didn't parse ACTIVE/SUB state columns, only matched unit names
11
+ - Fixed in commit 614099e but parsing remains fragile
12
+
13
+ ## D-Bus vs CLI
14
+
15
+ systemd's official API is D-Bus. CLI tools are wrappers. Key advantages:
16
+ - Structured data (properties, not text)
17
+ - Stable API across versions
18
+ - Better testability (mock D-Bus interface)
19
+ - Proper async job handling for start/stop operations
20
+
21
+ ## Implementation Notes
22
+
23
+ ### Library Choice
24
+
25
+ **Recommend pystemd**:
26
+ - Facebook/Meta maintained
27
+ - Direct systemd bindings
28
+ - Used in production at scale
29
+ - `pip install pystemd` (requires libsystemd-dev)
30
+
31
+ ### Key D-Bus Concepts
32
+
33
+ Manager object: `/org/freedesktop/systemd1`
34
+ - `ListUnits()` - Returns array of unit info tuples
35
+ - `StartUnit(name, mode)` - Returns job path
36
+ - `StopUnit(name, mode)` - Returns job path
37
+ - `Reload()` - Daemon reload
38
+
39
+ Unit properties (org.freedesktop.systemd1.Unit):
40
+ - `ActiveState`: "active" | "inactive" | "failed" | "activating" | "deactivating"
41
+ - `SubState`: "running" | "dead" | "failed" | "exited" | etc.
42
+ - `LoadState`: "loaded" | "not-found" | "masked"
43
+
44
+ ### Migration Strategy
45
+
46
+ 1. Add pystemd dependency
47
+ 2. Create `_dbus.py` module with D-Bus implementation
48
+ 3. Update `systemd.py` to use D-Bus by default
49
+ 4. Keep CLI fallback for environments without D-Bus access
50
+ 5. Update tests to mock D-Bus interface
51
+
52
+ ### Test Considerations
53
+
54
+ pystemd can be mocked:
55
+ ```python
56
+ @pytest.fixture
57
+ def mock_manager(mocker):
58
+ manager = mocker.Mock()
59
+ manager.list_units.return_value = [
60
+ (b"onetime@7043.service", b"", b"loaded", b"active", b"running", ...),
61
+ ]
62
+ mocker.patch("pystemd.systemd1.Manager", return_value=manager)
63
+ return manager
64
+ ```
65
+
66
+ ### Async Operations
67
+
68
+ Start/Stop/Restart are async in systemd. Current code uses `check=True` which waits.
69
+ D-Bus returns a job path; need to either:
70
+ - Poll job status until complete
71
+ - Use synchronous convenience methods if available
72
+ - Accept fire-and-forget for some operations
73
+
74
+ ## Related Commits
75
+
76
+ - 614099e: Filter discover_instances to only return running units (the parsing fix)
77
+ - 5078405: Fix container naming (another verification failure)
78
+
79
+ ## Branch Context
80
+
81
+ Work on `delano/next` branch. Issue #1 created for tracking.
@@ -0,0 +1,107 @@
1
+ # ots service Command Design - December 2024
2
+
3
+ ## Overview
4
+
5
+ Design for managing systemd template services (e.g., `valkey-server@.service`) on Debian 13 systems. Uses package-provided templates rather than custom unit files.
6
+
7
+ ## Command Structure
8
+
9
+ ```bash
10
+ ots service init valkey 6379 # Full setup wizard
11
+ ots service enable valkey 6380 # Add another instance
12
+ ots service disable valkey 6379 # Stop/disable instance
13
+ ots service status valkey 6379 # Show systemd status
14
+ ots service logs valkey 6379 # journalctl wrapper
15
+ ots service list valkey # Auto-discover instances
16
+ ots service doctor valkey 6379 # Comprehensive health check
17
+ ots service reconcile valkey # Detect DB vs systemctl drift
18
+ ots service adopt valkey 6379 # Bring manual units under management
19
+ ots service forget valkey 6379 # Remove phantom DB records
20
+ ots service secret-set valkey 6379 requirepass
21
+ ots service secret-rotate valkey 6379 requirepass
22
+ ```
23
+
24
+ ## Key Design Decisions
25
+
26
+ ### 1. Config File Management
27
+ - **Strategy**: Copy-on-write with `/etc/<pkg>/instances/` subdirectory
28
+ - Avoids dpkg conffile conflicts on package upgrades
29
+ - Package base config (`/etc/valkey/valkey.conf`) treated as readonly reference
30
+
31
+ ### 2. Source of Truth
32
+ - **systemctl** = runtime state (is-active, is-enabled, list-units)
33
+ - **SQLite database** = metadata (config paths, ports, timestamps, audit trail)
34
+ - Use `systemctl --output=json` on Debian 13 (systemd 255+) for robustness
35
+
36
+ ### 3. Secret Handling (Three Tiers)
37
+ - **inline**: Secrets in main config (mode 0600)
38
+ - **separate** (default): `.conf` + `.secrets` files
39
+ - **credential**: systemd LoadCredential= directive
40
+
41
+ ### 4. FHS Compliance
42
+ - Config: `/etc/<pkg>/instances/{instance}.conf`
43
+ - Secrets: `/etc/<pkg>/instances/{instance}.secrets` (mode 0600)
44
+ - Data: `/var/lib/<pkg>/{instance}/`
45
+
46
+ ## Database Schema
47
+
48
+ ```sql
49
+ service_instances (package, instance, config_file, data_dir, port, created_at)
50
+ service_actions (timestamp, package, instance, action, success, notes)
51
+ ```
52
+
53
+ ## ServicePackage Registry Pattern
54
+
55
+ ```python
56
+ @dataclass
57
+ class SecretConfig:
58
+ secret_keys: list[str]
59
+ secrets_file_pattern: str | None
60
+ include_directive: str | None
61
+ config_with_secrets_mode: int = 0o600
62
+ secrets_file_mode: int = 0o600
63
+ secrets_owned_by_service: bool = True
64
+
65
+ @dataclass
66
+ class ServicePackage:
67
+ name: str
68
+ template: str # e.g., "valkey-server@"
69
+ config_dir: Path # /etc/valkey
70
+ data_dir: Path # /var/lib/valkey
71
+ config_file_pattern: str # "{instance}.conf"
72
+ default_service: str | None # "valkey-server.service"
73
+ default_config: Path | None # /etc/valkey/valkey.conf
74
+ secrets: SecretConfig | None
75
+ # ... additional fields
76
+ ```
77
+
78
+ ## Doctor Command Checks
79
+
80
+ 1. `systemd_syntax` - systemd-analyze verify
81
+ 2. `security_score` - systemd-analyze security
82
+ 3. `config_file` - exists, correct permissions
83
+ 4. `secrets_file` - 0600, correct ownership
84
+ 5. `data_directory` - exists, correct ownership
85
+ 6. `runtime_status` - active + enabled
86
+ 7. `db_sync` - DB matches systemctl
87
+
88
+ ## File Structure
89
+
90
+ ```
91
+ src/ots_containers/commands/service/
92
+ __init__.py
93
+ app.py # Main commands
94
+ packages.py # ServicePackage registry
95
+ _helpers.py # Config/secret helpers
96
+ db.py # Database operations (extend existing)
97
+ ```
98
+
99
+ ## Ops Standards Review Result
100
+
101
+ - ✅ FHS Compliance: 10/10
102
+ - ✅ systemd Integration: 9/10
103
+ - ⚠️ Security: Addressed with secret handling
104
+ - ✅ Maintainability: 9/10 (reconcile/adopt pattern)
105
+
106
+ ## Related Memories
107
+ - fhs-directory-standards (comprehensive FHS research)