devstuff 2.3.0__tar.gz → 2.5.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.
- devstuff-2.5.0/.github/workflows/bump.yml +180 -0
- devstuff-2.5.0/.github/workflows/publish.yml +96 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/.github/workflows/test-installs.yml +1 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/CHANGELOG.md +12 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/PKG-INFO +42 -2
- {devstuff-2.3.0 → devstuff-2.5.0}/README.md +41 -1
- {devstuff-2.3.0 → devstuff-2.5.0}/pyproject.toml +1 -1
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/__init__.py +6 -0
- devstuff-2.5.0/src/dev_setup/configure/reptyr/__init__.py +1 -0
- devstuff-2.5.0/src/dev_setup/configure/reptyr/wizard.py +136 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/tools.yaml +10 -0
- devstuff-2.5.0/tests/test_configure_reptyr.py +150 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/uv.lock +1 -1
- devstuff-2.3.0/.github/workflows/bump.yml +0 -95
- devstuff-2.3.0/.github/workflows/publish.yml +0 -44
- {devstuff-2.3.0 → devstuff-2.5.0}/.claude/skills/run-stuff/SKILL.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/.claude/skills/run-stuff/smoke.sh +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/.github/workflows/ci.yml +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/.gitignore +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/CLAUDE.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/dev/Dockerfile +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/dev/Dockerfile.ci +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/dev/Makefile +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/dev/docker-compose.yml +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/devstuff +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/README.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/agent/development-plan.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/agent/specifications.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/agent/stack-decisions.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/ansible-config/development-plan.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/ansible-config/specifications.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/ansible-config/stack-decisions.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/bat-config/development-plan.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/bat-config/specifications.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/bat-config/stack-decisions.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/commitizen-config/development-plan.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/commitizen-config/specifications.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/commitizen-config/stack-decisions.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/docker-config/development-plan.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/docker-config/specifications.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/docker-config/stack-decisions.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/lazygit-config/development-plan.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/lazygit-config/specifications.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/lazygit-config/stack-decisions.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/precommit-config/development-plan.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/precommit-config/specifications.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/precommit-config/stack-decisions.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/starship-config/development-plan.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/starship-config/specifications.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/starship-config/stack-decisions.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/uv-backbone/development-plan.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/uv-backbone/specifications.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/uv-backbone/stack-decisions.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/verbose-mode/development-plan.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/verbose-mode/specifications.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/docs/specs/verbose-mode/stack-decisions.md +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/install.sh +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/__main__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/approval.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/bridges.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/catalog.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/completion.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/config.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/loop.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/ollama.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/preflight.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/primitives.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/registry.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/sandbox.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/session.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/transcript.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent/wizard.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent_tools.schema.json +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/agent_tools.yaml +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/base.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/catalog.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/cli.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/add_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/agent_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/catalog_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/configure_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/delete_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/docs_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/doctor_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/functions_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/help_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/install_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/list_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/remove_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/run_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/skills_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/commands/update_cmd.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/detect.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/model.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/render.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/validate.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/wizard.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/detect.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/model.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/preview.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/render.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/wizard.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/detect.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/model.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/render.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/validate.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/wizard.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/detect.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/model.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/render.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/validate.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/wizard.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/detect.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/model.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/render.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/validate.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/wizard.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/detect.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/model.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/render.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/validate.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/wizard.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/fonts.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/model.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/preview.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/render.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/wizard.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/doctor.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/function_runner.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/functions.schema.json +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/functions.yaml +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/functions_catalog.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/functions_registry.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/generic.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/registry.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/ui.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/src/dev_setup/verbose.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/integration/__init__.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/integration/conftest.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/integration/test_agent_smoke.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/integration/test_agent_tui.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/integration/test_commitizen_config.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/integration/test_tools.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_agent.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_agent_completion.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_agent_loop.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_agent_sandbox.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_agent_tools.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_agent_wizard.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_catalog.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_configure_ansible.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_configure_bat.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_configure_commitizen.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_configure_docker.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_configure_lazygit.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_configure_precommit.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_configure_starship.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_doctor.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_functions.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_generic.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_registry.py +0 -0
- {devstuff-2.3.0 → devstuff-2.5.0}/tests/test_verbose.py +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
name: Bump version
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [closed]
|
|
6
|
+
branches:
|
|
7
|
+
- master
|
|
8
|
+
|
|
9
|
+
# Serialize bump runs against master regardless of which PR triggered them.
|
|
10
|
+
concurrency:
|
|
11
|
+
group: bump-master
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
bump:
|
|
16
|
+
name: cz bump
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
# Only run for PRs that were actually merged, not closed unmerged.
|
|
19
|
+
# A raw `git push` — which is how this job publishes its own commit —
|
|
20
|
+
# never fires pull_request events, so there's no infinite-loop risk
|
|
21
|
+
# from this job triggering itself (unlike the old push-to-master
|
|
22
|
+
# trigger, which needed an explicit "bump:" message guard for that).
|
|
23
|
+
if: github.event.pull_request.merged == true
|
|
24
|
+
permissions:
|
|
25
|
+
contents: write
|
|
26
|
+
env:
|
|
27
|
+
# secrets can't be referenced directly in step `if:` conditions, so
|
|
28
|
+
# surface presence via an env var instead.
|
|
29
|
+
HAS_GH_PAT: ${{ secrets.GH_PAT != '' }}
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- name: Require GH_PAT
|
|
33
|
+
# GH_PAT (a Personal Access Token with repo write scope) is required
|
|
34
|
+
# so that the tag push re-triggers publish.yml — GitHub Actions does
|
|
35
|
+
# not re-process events from pushes made with GITHUB_TOKEN. Fail loudly
|
|
36
|
+
# instead of silently pushing a tag that will never publish.
|
|
37
|
+
if: env.HAS_GH_PAT != 'true'
|
|
38
|
+
run: |
|
|
39
|
+
echo "::error::GH_PAT secret is not set. It is required so the tag push triggers publish.yml (GITHUB_TOKEN pushes are not re-processed by Actions)."
|
|
40
|
+
exit 1
|
|
41
|
+
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
with:
|
|
44
|
+
ref: master # the merge already happened server-side by the time this runs
|
|
45
|
+
fetch-depth: 0 # commitizen needs full history to determine bump type
|
|
46
|
+
token: ${{ secrets.GH_PAT }}
|
|
47
|
+
|
|
48
|
+
- name: Install uv
|
|
49
|
+
uses: astral-sh/setup-uv@v5
|
|
50
|
+
|
|
51
|
+
- name: Configure git
|
|
52
|
+
run: |
|
|
53
|
+
git config user.name "github-actions[bot]"
|
|
54
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
55
|
+
|
|
56
|
+
- name: Run cz bump
|
|
57
|
+
id: bump
|
|
58
|
+
run: |
|
|
59
|
+
if uv run --group dev cz bump --yes; then
|
|
60
|
+
echo "bumped=true" >> "$GITHUB_OUTPUT"
|
|
61
|
+
else
|
|
62
|
+
EXIT=$?
|
|
63
|
+
if [ "$EXIT" -eq 21 ]; then
|
|
64
|
+
echo "No conventional commits found since last tag — nothing to bump."
|
|
65
|
+
echo "bumped=false" >> "$GITHUB_OUTPUT"
|
|
66
|
+
else
|
|
67
|
+
exit "$EXIT"
|
|
68
|
+
fi
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# Build-only PRs: `cz bump` exits 21 because `build` is deliberately not
|
|
72
|
+
# in bump_map (we don't want it consuming a PATCH slot from the main
|
|
73
|
+
# release line). Instead, ship a PEP 440 post-release — vX.Y.Z.postN —
|
|
74
|
+
# which is publishable to PyPI, sorts after vX.Y.Z but before the next
|
|
75
|
+
# vX.(Y+1).0, and is still recognised by `cz bump` as the current
|
|
76
|
+
# version tag (tag_format is `v$version` and `X.Y.Z.postN` is valid
|
|
77
|
+
# PEP 440), so the next regular bump stays coherent. A PR mixing `build`
|
|
78
|
+
# with feat/fix/etc. is handled by the `cz bump` path above — the build
|
|
79
|
+
# commits just fold into that release's changelog — so this step only
|
|
80
|
+
# fires when there was nothing else to release.
|
|
81
|
+
- name: Build-patch post-release
|
|
82
|
+
id: buildpatch
|
|
83
|
+
if: steps.bump.outputs.bumped == 'false'
|
|
84
|
+
run: |
|
|
85
|
+
set -euo pipefail
|
|
86
|
+
|
|
87
|
+
last_tag="$(git describe --tags --abbrev=0)"
|
|
88
|
+
commits="$(git log "${last_tag}..HEAD" --pretty=format:%s)"
|
|
89
|
+
|
|
90
|
+
# No build commits -> genuinely nothing to release.
|
|
91
|
+
if ! printf '%s\n' "$commits" | grep -qE '^build(\(.+\))?!?:'; then
|
|
92
|
+
echo "No build commits since ${last_tag} — nothing to release."
|
|
93
|
+
echo "buildpatch=false" >> "$GITHUB_OUTPUT"
|
|
94
|
+
exit 0
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Safety net: if a higher-precedence release type slipped through,
|
|
98
|
+
# bail rather than ship a competing post-release. (cz bump exiting 21
|
|
99
|
+
# already implies this, but guard against future bump_map changes.)
|
|
100
|
+
if printf '%s\n' "$commits" | grep -qE '^(feat|fix|refactor|perf|deps)(\(.+\))?!?:'; then
|
|
101
|
+
echo "Higher-precedence release commits present alongside build — deferring to the main release track."
|
|
102
|
+
echo "buildpatch=false" >> "$GITHUB_OUTPUT"
|
|
103
|
+
exit 0
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# Current version -> release segment (strip any existing post/dev).
|
|
107
|
+
current_version="$(grep -E '^version = ' pyproject.toml | head -1 | sed -E 's/^version = "(.*)"$/\1/')"
|
|
108
|
+
base="$(printf '%s' "$current_version" | sed -E 's/(\.post[0-9]+|\.dev[0-9]+).*$//')"
|
|
109
|
+
|
|
110
|
+
# Next post number for this base (vX.Y.Z.post1, .post2, ...).
|
|
111
|
+
prev_n="$(git tag --list "v${base}.post*" | sed -E 's/^v.*\.post([0-9]+)$/\1/' | sort -n | tail -1 || true)"
|
|
112
|
+
next_n=$(( ${prev_n:-0} + 1 ))
|
|
113
|
+
new_version="${base}.post${next_n}"
|
|
114
|
+
new_tag="v${new_version}"
|
|
115
|
+
|
|
116
|
+
echo "Bumping build patch: ${current_version} -> ${new_version} (${new_tag})"
|
|
117
|
+
|
|
118
|
+
# Bump version in pyproject.toml.
|
|
119
|
+
sed -i -E "s/^version = \".*\"/version = \"${new_version}\"/" pyproject.toml
|
|
120
|
+
|
|
121
|
+
# Prepend a Build section to the changelog (matches cz's heading style).
|
|
122
|
+
date="$(date +%Y-%m-%d)"
|
|
123
|
+
build_entries="$(
|
|
124
|
+
printf '%s\n' "$commits" | grep -E '^build(\(.+\))?!?:' | while IFS= read -r subject; do
|
|
125
|
+
msg="$(printf '%s' "$subject" | sed -E 's/^build(\([^)]+\))?(!)?: //')"
|
|
126
|
+
scope="$(printf '%s' "$subject" | sed -nE 's/^build\(([^)]+)\).*/\1/p')"
|
|
127
|
+
if [ -n "$scope" ]; then
|
|
128
|
+
printf -- '- **%s**: %s\n' "$scope" "$msg"
|
|
129
|
+
else
|
|
130
|
+
printf -- '- %s\n' "$msg"
|
|
131
|
+
fi
|
|
132
|
+
done
|
|
133
|
+
)"
|
|
134
|
+
{
|
|
135
|
+
printf '## %s (%s)\n\n### Build\n\n%s\n' "$new_tag" "$date" "$build_entries"
|
|
136
|
+
cat CHANGELOG.md
|
|
137
|
+
} > CHANGELOG.md.new
|
|
138
|
+
mv CHANGELOG.md.new CHANGELOG.md
|
|
139
|
+
|
|
140
|
+
# Sync the lockfile against the new version before committing so the
|
|
141
|
+
# tag points at a single self-contained commit (no amend/retag needed).
|
|
142
|
+
uv lock
|
|
143
|
+
git add pyproject.toml CHANGELOG.md uv.lock
|
|
144
|
+
git commit -m "bump: version ${current_version} → ${new_version} (build patch)"
|
|
145
|
+
git tag -a "$new_tag" -m "$new_tag" HEAD
|
|
146
|
+
|
|
147
|
+
git push --follow-tags
|
|
148
|
+
if ! git ls-remote --exit-code --tags origin "refs/tags/${new_tag}" >/dev/null; then
|
|
149
|
+
echo "::error::${new_tag} was not found on origin after push --follow-tags (publish.yml build_patch job will not trigger)."
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
echo "buildpatch=true" >> "$GITHUB_OUTPUT"
|
|
154
|
+
echo "new_tag=${new_tag}" >> "$GITHUB_OUTPUT"
|
|
155
|
+
|
|
156
|
+
- name: Sync lockfile
|
|
157
|
+
if: steps.bump.outputs.bumped == 'true'
|
|
158
|
+
run: |
|
|
159
|
+
uv lock
|
|
160
|
+
git add uv.lock
|
|
161
|
+
if ! git diff --staged --quiet; then
|
|
162
|
+
# cz bump already tagged the pre-lock commit; amending moves HEAD
|
|
163
|
+
# to a new SHA, so the tag must be recreated to point at it.
|
|
164
|
+
# Must stay annotated (-a) — `git push --follow-tags` only
|
|
165
|
+
# pushes annotated tags, so a lightweight retag here would
|
|
166
|
+
# silently never reach the remote.
|
|
167
|
+
tag="$(git describe --tags --exact-match HEAD)"
|
|
168
|
+
git commit --amend --no-edit
|
|
169
|
+
git tag -f -a "$tag" -m "$tag" HEAD
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
- name: Push commit and tag
|
|
173
|
+
if: steps.bump.outputs.bumped == 'true'
|
|
174
|
+
run: |
|
|
175
|
+
git push --follow-tags
|
|
176
|
+
tag="$(git describe --tags --exact-match HEAD)"
|
|
177
|
+
if ! git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null; then
|
|
178
|
+
echo "::error::${tag} was not found on origin after push --follow-tags (publish.yml will not trigger)."
|
|
179
|
+
exit 1
|
|
180
|
+
fi
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# A `v*` tag push triggers this workflow. Two release tracks share that
|
|
4
|
+
# prefix, so they're routed by job-level `if` conditions on the tag name
|
|
5
|
+
# rather than by disjoint trigger globs (GitHub Actions globs can't negate,
|
|
6
|
+
# so `v*` would match both and a glob-only split would double-publish):
|
|
7
|
+
#
|
|
8
|
+
# - Regular releases vX.Y.Z -> build / publish
|
|
9
|
+
# - Build-patch post vX.Y.Z.postN -> build_patch / publish_build_patch
|
|
10
|
+
#
|
|
11
|
+
# The post-release track exists so a PR that contains only `build`-type
|
|
12
|
+
# commits still ships a publishable, PEP 440-distinct version. bump.yml
|
|
13
|
+
# decides which tag to create; this file only reacts to whichever one lands.
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
tags:
|
|
17
|
+
- "v*"
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
build:
|
|
21
|
+
name: Build distribution
|
|
22
|
+
if: ${{ !contains(github.ref, '.post') }}
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@v5
|
|
29
|
+
|
|
30
|
+
- name: Build wheel and source distribution
|
|
31
|
+
run: uv build
|
|
32
|
+
|
|
33
|
+
- name: Upload dist artifacts
|
|
34
|
+
uses: actions/upload-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: dist
|
|
37
|
+
path: dist/
|
|
38
|
+
|
|
39
|
+
publish:
|
|
40
|
+
name: Publish to PyPI
|
|
41
|
+
if: ${{ !contains(github.ref, '.post') }}
|
|
42
|
+
needs: build
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
environment:
|
|
45
|
+
name: pypi
|
|
46
|
+
url: https://pypi.org/p/devstuff
|
|
47
|
+
permissions:
|
|
48
|
+
id-token: write # required for OIDC trusted publishing
|
|
49
|
+
steps:
|
|
50
|
+
- name: Download dist artifacts
|
|
51
|
+
uses: actions/download-artifact@v4
|
|
52
|
+
with:
|
|
53
|
+
name: dist
|
|
54
|
+
path: dist/
|
|
55
|
+
|
|
56
|
+
- name: Publish to PyPI
|
|
57
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
58
|
+
|
|
59
|
+
build_patch:
|
|
60
|
+
name: Build build-patch distribution
|
|
61
|
+
if: ${{ contains(github.ref, '.post') }}
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
|
|
66
|
+
- name: Install uv
|
|
67
|
+
uses: astral-sh/setup-uv@v5
|
|
68
|
+
|
|
69
|
+
- name: Build wheel and source distribution
|
|
70
|
+
run: uv build
|
|
71
|
+
|
|
72
|
+
- name: Upload dist artifacts
|
|
73
|
+
uses: actions/upload-artifact@v4
|
|
74
|
+
with:
|
|
75
|
+
name: dist-build-patch
|
|
76
|
+
path: dist/
|
|
77
|
+
|
|
78
|
+
publish_build_patch:
|
|
79
|
+
name: Publish build-patch to PyPI
|
|
80
|
+
if: ${{ contains(github.ref, '.post') }}
|
|
81
|
+
needs: build_patch
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
environment:
|
|
84
|
+
name: pypi
|
|
85
|
+
url: https://pypi.org/p/devstuff
|
|
86
|
+
permissions:
|
|
87
|
+
id-token: write # required for OIDC trusted publishing
|
|
88
|
+
steps:
|
|
89
|
+
- name: Download dist artifacts
|
|
90
|
+
uses: actions/download-artifact@v4
|
|
91
|
+
with:
|
|
92
|
+
name: dist-build-patch
|
|
93
|
+
path: dist/
|
|
94
|
+
|
|
95
|
+
- name: Publish to PyPI
|
|
96
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## v2.5.0 (2026-09-09)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- **reptyr**: add tool and ptrace permission wizard
|
|
6
|
+
|
|
7
|
+
## v2.4.0 (2026-08-20)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **ci**: publish build-patch updates as PEP 440 post-releases
|
|
12
|
+
|
|
1
13
|
## v2.3.0 (2026-08-13)
|
|
2
14
|
|
|
3
15
|
### Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: devstuff
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Development environment setup CLI for Linux
|
|
5
5
|
Project-URL: Repository, https://github.com/thesawdawg/dev-setup-py
|
|
6
6
|
Author-email: Sawyer <sawyerksu@gmail.com>
|
|
@@ -324,6 +324,7 @@ devstuff configure starship --output /tmp/try.toml # write elsewhere, leave th
|
|
|
324
324
|
|
|
325
325
|
| Tool | What it configures |
|
|
326
326
|
|------|-------------------|
|
|
327
|
+
| `reptyr` | Temporary or persistent Linux ptrace permissions; command-line usage guidance |
|
|
327
328
|
| `ansible` | `ansible.cfg` — inventory paths, forks, pipelining, become and vault |
|
|
328
329
|
| `bat` | Theme, decorations, paging, and the man-page/`cat` shell integration |
|
|
329
330
|
| `commitizen` | Commit types, what each one bumps, git tags, and changelog sections |
|
|
@@ -332,7 +333,8 @@ devstuff configure starship --output /tmp/try.toml # write elsewhere, leave th
|
|
|
332
333
|
| `pre-commit` | Which git hooks run, when they run, and what they're allowed to rewrite |
|
|
333
334
|
| `starship` | Prompt style, colour palette, which sections appear, and layout |
|
|
334
335
|
|
|
335
|
-
|
|
336
|
+
The reptyr wizard verifies the live kernel setting after applying it. Other wizards check
|
|
337
|
+
their result against the real tool before saving. What that check can prove
|
|
336
338
|
varies a lot, and the wizards say so rather than implying more than they know:
|
|
337
339
|
|
|
338
340
|
| Tool | What the tool itself checks | What the wizard has to check instead |
|
|
@@ -739,6 +741,43 @@ characters, and a quoted boolean is read as `False`. A retired section in an exi
|
|
|
739
741
|
*preserved and reported*, never silently migrated — the wizard can't know whether an older
|
|
740
742
|
ansible elsewhere is still reading it.
|
|
741
743
|
|
|
744
|
+
#### reptyr wizard
|
|
745
|
+
|
|
746
|
+
```bash
|
|
747
|
+
devstuff install reptyr # apt package; offers setup after installation
|
|
748
|
+
devstuff configure reptyr # also works for an already-installed package
|
|
749
|
+
devstuff configure reptyr --path # /etc/sysctl.d/99-devstuff-reptyr.conf
|
|
750
|
+
devstuff configure reptyr --output /tmp/reptyr.conf # export only, no live changes
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
The wizard shows the current `kernel.yama.ptrace_scope` and offers keeping it unchanged,
|
|
754
|
+
enabling same-user attach (`0`), restricted attach (`1`), or admin-only attach (`2`).
|
|
755
|
+
Choose a change for this boot only, or save and apply a persistent sysctl drop-in. The
|
|
756
|
+
preview and confirmation come before any write or sudo command. Persistent changes use
|
|
757
|
+
`sudo install --backup=numbered` to preserve an existing drop-in, then load only that file
|
|
758
|
+
with `sudo sysctl --load`. The wizard reads the live value back to verify success.
|
|
759
|
+
|
|
760
|
+
Setting `0` relaxes protection **system-wide**, allowing same-user processes to inspect
|
|
761
|
+
and control other dumpable processes. It does not grant cross-user access or bypass
|
|
762
|
+
container/security policies. Mode `3` cannot be changed until reboot, so the wizard
|
|
763
|
+
refuses live changes in that state. If Yama is unavailable or unreadable, it offers only
|
|
764
|
+
guidance; offline `--output` export still works. Other sysctl files may override the saved
|
|
765
|
+
value at boot. Temporary changes leave all boot configuration untouched.
|
|
766
|
+
|
|
767
|
+
To return to restricted attach, select `1` and the persistent option. This sets a known
|
|
768
|
+
policy rather than claiming to restore an unknown original value. Removing the reptyr
|
|
769
|
+
package does not remove this system policy; rerun the wizard before uninstalling if needed.
|
|
770
|
+
For unattended installs, run `devstuff configure reptyr` later in a terminal.
|
|
771
|
+
|
|
772
|
+
reptyr has no rc file: its remaining options are per invocation, and the wizard explains
|
|
773
|
+
them rather than inventing persistent defaults. Use `reptyr PID` inside the destination
|
|
774
|
+
terminal; `-T` steals the whole TTY (SSH children require root); `-s` redirects standard
|
|
775
|
+
streams even when they weren't attached to a terminal; `-l` creates a PTY, optionally
|
|
776
|
+
running a command with `REPTYR_PTY` set; `-L` also attaches that command's streams and
|
|
777
|
+
session; `-V` enables debug output, `-v` prints the version, and `-h` shows help.
|
|
778
|
+
See the [upstream manual](https://github.com/nelhage/reptyr/blob/master/reptyr.1) and
|
|
779
|
+
[Linux Yama documentation](https://docs.kernel.org/admin-guide/LSM/Yama.html).
|
|
780
|
+
|
|
742
781
|
#### lazygit wizard
|
|
743
782
|
|
|
744
783
|
lazygit refuses to start on a value of the wrong **type**, and silently ignores an unknown
|
|
@@ -1230,6 +1269,7 @@ Optional utilities you may want on some machines.
|
|
|
1230
1269
|
| `homebrew` | Homebrew | Package manager for Linux (and macOS) | `brew --help` |
|
|
1231
1270
|
| `ipython` | IPython | Enhanced interactive Python shell with rich tab completion and magic commands | `ipython --help` |
|
|
1232
1271
|
| `lazygit` | lazygit | TUI git client for fast, keyboard-driven git workflows (`devstuff configure lazygit`) | `lazygit --help` |
|
|
1272
|
+
| `reptyr` | reptyr | Move a running process to another terminal (`devstuff configure reptyr` for permissions) | `reptyr -h` |
|
|
1233
1273
|
| `llm-checker` | llm-checker | Check LLM compatibility with your hardware | `llm-checker --help` |
|
|
1234
1274
|
| `lmstudio` | LM Studio | Run large language models locally with a GUI | `lms --help` |
|
|
1235
1275
|
| `mkcert` | mkcert | Zero-config local HTTPS certificates | `mkcert --help` |
|
|
@@ -297,6 +297,7 @@ devstuff configure starship --output /tmp/try.toml # write elsewhere, leave th
|
|
|
297
297
|
|
|
298
298
|
| Tool | What it configures |
|
|
299
299
|
|------|-------------------|
|
|
300
|
+
| `reptyr` | Temporary or persistent Linux ptrace permissions; command-line usage guidance |
|
|
300
301
|
| `ansible` | `ansible.cfg` — inventory paths, forks, pipelining, become and vault |
|
|
301
302
|
| `bat` | Theme, decorations, paging, and the man-page/`cat` shell integration |
|
|
302
303
|
| `commitizen` | Commit types, what each one bumps, git tags, and changelog sections |
|
|
@@ -305,7 +306,8 @@ devstuff configure starship --output /tmp/try.toml # write elsewhere, leave th
|
|
|
305
306
|
| `pre-commit` | Which git hooks run, when they run, and what they're allowed to rewrite |
|
|
306
307
|
| `starship` | Prompt style, colour palette, which sections appear, and layout |
|
|
307
308
|
|
|
308
|
-
|
|
309
|
+
The reptyr wizard verifies the live kernel setting after applying it. Other wizards check
|
|
310
|
+
their result against the real tool before saving. What that check can prove
|
|
309
311
|
varies a lot, and the wizards say so rather than implying more than they know:
|
|
310
312
|
|
|
311
313
|
| Tool | What the tool itself checks | What the wizard has to check instead |
|
|
@@ -712,6 +714,43 @@ characters, and a quoted boolean is read as `False`. A retired section in an exi
|
|
|
712
714
|
*preserved and reported*, never silently migrated — the wizard can't know whether an older
|
|
713
715
|
ansible elsewhere is still reading it.
|
|
714
716
|
|
|
717
|
+
#### reptyr wizard
|
|
718
|
+
|
|
719
|
+
```bash
|
|
720
|
+
devstuff install reptyr # apt package; offers setup after installation
|
|
721
|
+
devstuff configure reptyr # also works for an already-installed package
|
|
722
|
+
devstuff configure reptyr --path # /etc/sysctl.d/99-devstuff-reptyr.conf
|
|
723
|
+
devstuff configure reptyr --output /tmp/reptyr.conf # export only, no live changes
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
The wizard shows the current `kernel.yama.ptrace_scope` and offers keeping it unchanged,
|
|
727
|
+
enabling same-user attach (`0`), restricted attach (`1`), or admin-only attach (`2`).
|
|
728
|
+
Choose a change for this boot only, or save and apply a persistent sysctl drop-in. The
|
|
729
|
+
preview and confirmation come before any write or sudo command. Persistent changes use
|
|
730
|
+
`sudo install --backup=numbered` to preserve an existing drop-in, then load only that file
|
|
731
|
+
with `sudo sysctl --load`. The wizard reads the live value back to verify success.
|
|
732
|
+
|
|
733
|
+
Setting `0` relaxes protection **system-wide**, allowing same-user processes to inspect
|
|
734
|
+
and control other dumpable processes. It does not grant cross-user access or bypass
|
|
735
|
+
container/security policies. Mode `3` cannot be changed until reboot, so the wizard
|
|
736
|
+
refuses live changes in that state. If Yama is unavailable or unreadable, it offers only
|
|
737
|
+
guidance; offline `--output` export still works. Other sysctl files may override the saved
|
|
738
|
+
value at boot. Temporary changes leave all boot configuration untouched.
|
|
739
|
+
|
|
740
|
+
To return to restricted attach, select `1` and the persistent option. This sets a known
|
|
741
|
+
policy rather than claiming to restore an unknown original value. Removing the reptyr
|
|
742
|
+
package does not remove this system policy; rerun the wizard before uninstalling if needed.
|
|
743
|
+
For unattended installs, run `devstuff configure reptyr` later in a terminal.
|
|
744
|
+
|
|
745
|
+
reptyr has no rc file: its remaining options are per invocation, and the wizard explains
|
|
746
|
+
them rather than inventing persistent defaults. Use `reptyr PID` inside the destination
|
|
747
|
+
terminal; `-T` steals the whole TTY (SSH children require root); `-s` redirects standard
|
|
748
|
+
streams even when they weren't attached to a terminal; `-l` creates a PTY, optionally
|
|
749
|
+
running a command with `REPTYR_PTY` set; `-L` also attaches that command's streams and
|
|
750
|
+
session; `-V` enables debug output, `-v` prints the version, and `-h` shows help.
|
|
751
|
+
See the [upstream manual](https://github.com/nelhage/reptyr/blob/master/reptyr.1) and
|
|
752
|
+
[Linux Yama documentation](https://docs.kernel.org/admin-guide/LSM/Yama.html).
|
|
753
|
+
|
|
715
754
|
#### lazygit wizard
|
|
716
755
|
|
|
717
756
|
lazygit refuses to start on a value of the wrong **type**, and silently ignores an unknown
|
|
@@ -1203,6 +1242,7 @@ Optional utilities you may want on some machines.
|
|
|
1203
1242
|
| `homebrew` | Homebrew | Package manager for Linux (and macOS) | `brew --help` |
|
|
1204
1243
|
| `ipython` | IPython | Enhanced interactive Python shell with rich tab completion and magic commands | `ipython --help` |
|
|
1205
1244
|
| `lazygit` | lazygit | TUI git client for fast, keyboard-driven git workflows (`devstuff configure lazygit`) | `lazygit --help` |
|
|
1245
|
+
| `reptyr` | reptyr | Move a running process to another terminal (`devstuff configure reptyr` for permissions) | `reptyr -h` |
|
|
1206
1246
|
| `llm-checker` | llm-checker | Check LLM compatibility with your hardware | `llm-checker --help` |
|
|
1207
1247
|
| `lmstudio` | LM Studio | Run large language models locally with a GUI | `lms --help` |
|
|
1208
1248
|
| `mkcert` | mkcert | Zero-config local HTTPS certificates | `mkcert --help` |
|
|
@@ -39,6 +39,12 @@ class Configurator:
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
CONFIGURATORS: dict[str, Configurator] = {
|
|
42
|
+
"reptyr": Configurator(
|
|
43
|
+
key="reptyr",
|
|
44
|
+
label="reptyr",
|
|
45
|
+
description="Temporary or persistent ptrace permissions and command-line usage",
|
|
46
|
+
module="dev_setup.configure.reptyr.wizard",
|
|
47
|
+
),
|
|
42
48
|
"ansible": Configurator(
|
|
43
49
|
key="ansible",
|
|
44
50
|
label="Ansible",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""reptyr permission setup; reptyr itself has no configuration file."""
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Configure Linux Yama permissions without inventing a reptyr rc file.
|
|
2
|
+
|
|
3
|
+
References: upstream reptyr.1 and docs.kernel.org/admin-guide/LSM/Yama.html.
|
|
4
|
+
Only sysctl modes 0-2 are offered: mode 3 cannot be changed until reboot.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import tempfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import questionary
|
|
13
|
+
|
|
14
|
+
from dev_setup import ui
|
|
15
|
+
from dev_setup.generic import _run
|
|
16
|
+
|
|
17
|
+
SCOPE_PATH = Path("/proc/sys/kernel/yama/ptrace_scope")
|
|
18
|
+
SYSTEM_PATH = Path("/etc/sysctl.d/99-devstuff-reptyr.conf")
|
|
19
|
+
SETTING = "kernel.yama.ptrace_scope"
|
|
20
|
+
MODES = {
|
|
21
|
+
0: "Enable same-user attach (0) — permits ordinary reptyr PID",
|
|
22
|
+
1: "Restrict attach (1) — descendants or explicitly permitted processes",
|
|
23
|
+
2: "Admin-only attach (2) — requires CAP_SYS_PTRACE",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def config_path() -> Path:
|
|
28
|
+
return SYSTEM_PATH
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def read_scope() -> int | None:
|
|
32
|
+
try:
|
|
33
|
+
value = int(SCOPE_PATH.read_text().strip())
|
|
34
|
+
except (OSError, ValueError):
|
|
35
|
+
return None
|
|
36
|
+
return value if value in (0, 1, 2, 3) else None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def render(scope: int) -> str:
|
|
40
|
+
if scope not in MODES:
|
|
41
|
+
raise ValueError("Only ptrace_scope values 0, 1 and 2 are supported")
|
|
42
|
+
return (
|
|
43
|
+
"# Managed by devstuff configure reptyr; affects all processes on this host.\n"
|
|
44
|
+
"# 0: same-user attach; 1: restricted attach; 2: admin-only attach.\n"
|
|
45
|
+
f"{SETTING} = {scope}\n"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _usage() -> None:
|
|
50
|
+
ui.info("reptyr has no rc file or persistent command-line defaults.")
|
|
51
|
+
for line in (
|
|
52
|
+
"reptyr PID Move a process into this terminal (for example, inside tmux).",
|
|
53
|
+
"reptyr -T PID Steal the whole TTY without ptracing the target; SSH children need root.",
|
|
54
|
+
"reptyr -s PID Redirect stdin/out/err even when they were not attached to a terminal.",
|
|
55
|
+
"reptyr -l Create a PTY; optional COMMAND receives REPTYR_PTY in its environment.",
|
|
56
|
+
"reptyr -L COMMAND Also attach the command's standard streams and session to the PTY.",
|
|
57
|
+
"reptyr -V PID Debug logging; -v prints the version; -h shows installed-version help.",
|
|
58
|
+
):
|
|
59
|
+
ui.dim(line)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def run(*, target: Path | None = None) -> int | None:
|
|
63
|
+
ui.section("reptyr permissions")
|
|
64
|
+
_usage()
|
|
65
|
+
current = read_scope()
|
|
66
|
+
ui.info(f"Current {SETTING}: {current if current is not None else 'unavailable'}")
|
|
67
|
+
# --output is always an offline export, even on a host without Yama.
|
|
68
|
+
if target is None and current is None:
|
|
69
|
+
ui.warn("Yama is absent or unreadable; no permission change can be applied here.")
|
|
70
|
+
ui.dim("Other ptrace restrictions may still apply. Use --output to export a sysctl config.")
|
|
71
|
+
return None
|
|
72
|
+
if target is None and current == 3:
|
|
73
|
+
ui.warn("ptrace_scope=3 is locked until reboot, even for root. No changes made.")
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
choices = [questionary.Choice("Keep current permissions / cancel", value="keep")]
|
|
77
|
+
choices.extend(questionary.Choice(label, value=str(value)) for value, label in MODES.items())
|
|
78
|
+
selected = ui.select("Permission policy:", choices)
|
|
79
|
+
if not selected or selected == "keep":
|
|
80
|
+
return None
|
|
81
|
+
scope = int(selected)
|
|
82
|
+
content = render(scope)
|
|
83
|
+
if scope == 0:
|
|
84
|
+
ui.warn("This relaxes ptrace protection system-wide: same-user processes can inspect or control")
|
|
85
|
+
ui.warn("other dumpable processes. Other users' permissions and container policies still apply.")
|
|
86
|
+
|
|
87
|
+
lifetime = "persistent"
|
|
88
|
+
if target is None:
|
|
89
|
+
lifetime = ui.select("Apply for:", [
|
|
90
|
+
questionary.Choice("This boot only (no files changed)", value="temporary"),
|
|
91
|
+
questionary.Choice("Now and at boot (save a sysctl drop-in)", value="persistent"),
|
|
92
|
+
questionary.Choice("Cancel", value="cancel"),
|
|
93
|
+
])
|
|
94
|
+
if lifetime not in ("temporary", "persistent"):
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
if target is not None:
|
|
98
|
+
ui.info(f"Export only: {target}; existing content will be replaced. No sudo or live changes.")
|
|
99
|
+
elif lifetime == "temporary":
|
|
100
|
+
ui.info(f"Will run: sudo sysctl -w {SETTING}={scope}")
|
|
101
|
+
ui.dim("Existing boot configuration stays in place and may restore a different value.")
|
|
102
|
+
else:
|
|
103
|
+
ui.info(f"Will save {SYSTEM_PATH} with sudo install (numbered backup if it exists).")
|
|
104
|
+
ui.info(f"Then run: sudo sysctl --load {SYSTEM_PATH}")
|
|
105
|
+
ui.dim("Other sysctl files can override this at boot; only this drop-in is loaded now.")
|
|
106
|
+
ui.console.print(content, markup=False)
|
|
107
|
+
if not ui.confirm("Apply this configuration?", default=False):
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
if target is not None:
|
|
111
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
target.write_text(content, encoding="utf-8")
|
|
113
|
+
ui.success(f"Exported to {target}; live permissions unchanged.")
|
|
114
|
+
return scope
|
|
115
|
+
|
|
116
|
+
saved = False
|
|
117
|
+
try:
|
|
118
|
+
if lifetime == "persistent":
|
|
119
|
+
with tempfile.TemporaryDirectory(prefix="devstuff-reptyr-") as directory:
|
|
120
|
+
source = Path(directory) / "reptyr.conf"
|
|
121
|
+
source.write_text(content, encoding="utf-8")
|
|
122
|
+
_run(["sudo", "install", "--backup=numbered", "-m", "0644", str(source), str(SYSTEM_PATH)])
|
|
123
|
+
saved = True
|
|
124
|
+
_run(["sudo", "sysctl", "--load", str(SYSTEM_PATH)])
|
|
125
|
+
else:
|
|
126
|
+
_run(["sudo", "sysctl", "-w", f"{SETTING}={scope}"])
|
|
127
|
+
except (RuntimeError, OSError) as exc:
|
|
128
|
+
ui.error(f"Could not apply reptyr permissions: {exc}")
|
|
129
|
+
if saved:
|
|
130
|
+
ui.warn(f"{SYSTEM_PATH} was saved, but the live change failed; review it before reboot.")
|
|
131
|
+
return None
|
|
132
|
+
if read_scope() != scope:
|
|
133
|
+
ui.error("The live ptrace_scope does not match the requested value; permissions are not verified.")
|
|
134
|
+
return None
|
|
135
|
+
ui.success(f"Verified {SETTING}={scope}.")
|
|
136
|
+
return scope
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
version: 1
|
|
2
2
|
tools:
|
|
3
|
+
reptyr:
|
|
4
|
+
name: reptyr
|
|
5
|
+
description: Move a running process to another terminal (permission wizard available)
|
|
6
|
+
category: tools
|
|
7
|
+
type: apt
|
|
8
|
+
apt_packages: reptyr
|
|
9
|
+
check_cmd: reptyr
|
|
10
|
+
help_cmd: reptyr -h
|
|
11
|
+
docs_url: https://github.com/nelhage/reptyr
|
|
12
|
+
|
|
3
13
|
ansible:
|
|
4
14
|
name: Ansible
|
|
5
15
|
description: Automation engine for configuration management and app deployment
|