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.
- ots_containers-0.3.0/.claude/settings.local.json +42 -0
- ots_containers-0.3.0/.github/workflows/ci.yml +69 -0
- ots_containers-0.3.0/.github/workflows/claude-code-review.yml +95 -0
- ots_containers-0.3.0/.github/workflows/claude.yml +77 -0
- ots_containers-0.3.0/.github/workflows/release.yml +32 -0
- ots_containers-0.3.0/.gitignore +28 -0
- ots_containers-0.3.0/.pre-commit-config.yaml +122 -0
- ots_containers-0.3.0/.serena/.gitignore +2 -0
- ots_containers-0.3.0/.serena/cache/python/document_symbols.pkl +0 -0
- ots_containers-0.3.0/.serena/cache/python/raw_document_symbols.pkl +0 -0
- ots_containers-0.3.0/.serena/memories/.keep +0 -0
- ots_containers-0.3.0/.serena/memories/1215-dbus-conversion-technical-notes.md +81 -0
- ots_containers-0.3.0/.serena/memories/1216-ots-service-command-design.md +107 -0
- ots_containers-0.3.0/.serena/memories/fhs-directory-standards.md +371 -0
- ots_containers-0.3.0/.serena/memories/next-dev-handoff.md +65 -0
- ots_containers-0.3.0/.serena/memories/ots-containers-conformance-status.md +67 -0
- ots_containers-0.3.0/.serena/memories/podman-quadlet-production-standards.md +99 -0
- ots_containers-0.3.0/.serena/project.yml +84 -0
- ots_containers-0.3.0/CLAUDE.md +92 -0
- ots_containers-0.3.0/LICENSE +21 -0
- ots_containers-0.3.0/NETWORKING.md +136 -0
- ots_containers-0.3.0/PKG-INFO +284 -0
- ots_containers-0.3.0/README.md +250 -0
- ots_containers-0.3.0/docs/MANUAL-OPERATIONS.md +700 -0
- ots_containers-0.3.0/docs/TESTING.md +174 -0
- ots_containers-0.3.0/docs/architecture/decision-records/README.md +41 -0
- ots_containers-0.3.0/docs/architecture/decision-records/adr-000.md +32 -0
- ots_containers-0.3.0/docs/architecture/decision-records/adr-001.md +84 -0
- ots_containers-0.3.0/pyproject.toml +67 -0
- ots_containers-0.3.0/src/ots_containers/__init__.py +3 -0
- ots_containers-0.3.0/src/ots_containers/assets.py +32 -0
- ots_containers-0.3.0/src/ots_containers/cli.py +86 -0
- ots_containers-0.3.0/src/ots_containers/commands/README.md +124 -0
- ots_containers-0.3.0/src/ots_containers/commands/__init__.py +9 -0
- ots_containers-0.3.0/src/ots_containers/commands/assets.py +29 -0
- ots_containers-0.3.0/src/ots_containers/commands/cloudinit/__init__.py +6 -0
- ots_containers-0.3.0/src/ots_containers/commands/cloudinit/app.py +158 -0
- ots_containers-0.3.0/src/ots_containers/commands/cloudinit/templates.py +127 -0
- ots_containers-0.3.0/src/ots_containers/commands/common.py +71 -0
- ots_containers-0.3.0/src/ots_containers/commands/env/__init__.py +6 -0
- ots_containers-0.3.0/src/ots_containers/commands/env/app.py +348 -0
- ots_containers-0.3.0/src/ots_containers/commands/image/__init__.py +6 -0
- ots_containers-0.3.0/src/ots_containers/commands/image/app.py +1137 -0
- ots_containers-0.3.0/src/ots_containers/commands/init.py +253 -0
- ots_containers-0.3.0/src/ots_containers/commands/instance/__init__.py +57 -0
- ots_containers-0.3.0/src/ots_containers/commands/instance/_helpers.py +161 -0
- ots_containers-0.3.0/src/ots_containers/commands/instance/annotations.py +111 -0
- ots_containers-0.3.0/src/ots_containers/commands/instance/app.py +935 -0
- ots_containers-0.3.0/src/ots_containers/commands/proxy/__init__.py +11 -0
- ots_containers-0.3.0/src/ots_containers/commands/proxy/_helpers.py +95 -0
- ots_containers-0.3.0/src/ots_containers/commands/proxy/app.py +160 -0
- ots_containers-0.3.0/src/ots_containers/commands/service/__init__.py +6 -0
- ots_containers-0.3.0/src/ots_containers/commands/service/_helpers.py +294 -0
- ots_containers-0.3.0/src/ots_containers/commands/service/app.py +498 -0
- ots_containers-0.3.0/src/ots_containers/commands/service/packages.py +217 -0
- ots_containers-0.3.0/src/ots_containers/config.py +158 -0
- ots_containers-0.3.0/src/ots_containers/db.py +582 -0
- ots_containers-0.3.0/src/ots_containers/environment_file.py +491 -0
- ots_containers-0.3.0/src/ots_containers/podman.py +60 -0
- ots_containers-0.3.0/src/ots_containers/quadlet.py +341 -0
- ots_containers-0.3.0/src/ots_containers/systemd.py +217 -0
- ots_containers-0.3.0/tests/__init__.py +1 -0
- ots_containers-0.3.0/tests/commands/__init__.py +1 -0
- ots_containers-0.3.0/tests/commands/cloudinit/__init__.py +2 -0
- ots_containers-0.3.0/tests/commands/cloudinit/test_app.py +181 -0
- ots_containers-0.3.0/tests/commands/cloudinit/test_templates.py +163 -0
- ots_containers-0.3.0/tests/commands/image/__init__.py +2 -0
- ots_containers-0.3.0/tests/commands/image/test_app.py +255 -0
- ots_containers-0.3.0/tests/commands/image/test_build.py +711 -0
- ots_containers-0.3.0/tests/commands/instance/__init__.py +2 -0
- ots_containers-0.3.0/tests/commands/instance/test_app.py +929 -0
- ots_containers-0.3.0/tests/commands/instance/test_helpers.py +276 -0
- ots_containers-0.3.0/tests/commands/proxy/__init__.py +1 -0
- ots_containers-0.3.0/tests/commands/proxy/test_app.py +303 -0
- ots_containers-0.3.0/tests/commands/proxy/test_helpers.py +167 -0
- ots_containers-0.3.0/tests/commands/service/__init__.py +2 -0
- ots_containers-0.3.0/tests/commands/service/test_app.py +388 -0
- ots_containers-0.3.0/tests/commands/service/test_helpers.py +376 -0
- ots_containers-0.3.0/tests/commands/service/test_packages.py +160 -0
- ots_containers-0.3.0/tests/commands/test_assets.py +77 -0
- ots_containers-0.3.0/tests/commands/test_init.py +400 -0
- ots_containers-0.3.0/tests/test_assets.py +182 -0
- ots_containers-0.3.0/tests/test_cli.py +97 -0
- ots_containers-0.3.0/tests/test_config.py +143 -0
- ots_containers-0.3.0/tests/test_db.py +257 -0
- ots_containers-0.3.0/tests/test_environment_file.py +420 -0
- ots_containers-0.3.0/tests/test_image_build.py +331 -0
- ots_containers-0.3.0/tests/test_podman.py +200 -0
- ots_containers-0.3.0/tests/test_quadlet.py +719 -0
- 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]
|
|
Binary file
|
|
Binary file
|
|
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)
|