devstuff 2.4.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.4.0 → devstuff-2.5.0}/.github/workflows/test-installs.yml +1 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/CHANGELOG.md +6 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/PKG-INFO +42 -2
- {devstuff-2.4.0 → devstuff-2.5.0}/README.md +41 -1
- {devstuff-2.4.0 → devstuff-2.5.0}/pyproject.toml +1 -1
- {devstuff-2.4.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.4.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.4.0 → devstuff-2.5.0}/uv.lock +1 -1
- {devstuff-2.4.0 → devstuff-2.5.0}/.claude/skills/run-stuff/SKILL.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/.claude/skills/run-stuff/smoke.sh +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/.github/workflows/bump.yml +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/.github/workflows/ci.yml +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/.github/workflows/publish.yml +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/.gitignore +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/CLAUDE.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/dev/Dockerfile +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/dev/Dockerfile.ci +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/dev/Makefile +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/dev/docker-compose.yml +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/devstuff +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/README.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/agent/development-plan.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/agent/specifications.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/agent/stack-decisions.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/ansible-config/development-plan.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/ansible-config/specifications.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/ansible-config/stack-decisions.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/bat-config/development-plan.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/bat-config/specifications.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/bat-config/stack-decisions.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/commitizen-config/development-plan.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/commitizen-config/specifications.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/commitizen-config/stack-decisions.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/docker-config/development-plan.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/docker-config/specifications.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/docker-config/stack-decisions.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/lazygit-config/development-plan.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/lazygit-config/specifications.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/lazygit-config/stack-decisions.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/precommit-config/development-plan.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/precommit-config/specifications.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/precommit-config/stack-decisions.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/starship-config/development-plan.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/starship-config/specifications.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/starship-config/stack-decisions.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/uv-backbone/development-plan.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/uv-backbone/specifications.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/uv-backbone/stack-decisions.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/verbose-mode/development-plan.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/verbose-mode/specifications.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/docs/specs/verbose-mode/stack-decisions.md +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/install.sh +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/__main__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/approval.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/bridges.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/catalog.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/completion.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/config.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/loop.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/ollama.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/preflight.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/primitives.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/registry.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/sandbox.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/session.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/transcript.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent/wizard.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent_tools.schema.json +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/agent_tools.yaml +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/base.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/catalog.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/cli.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/add_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/agent_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/catalog_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/configure_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/delete_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/docs_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/doctor_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/functions_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/help_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/install_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/list_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/remove_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/run_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/skills_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/commands/update_cmd.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/detect.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/model.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/render.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/validate.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/ansible/wizard.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/detect.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/model.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/preview.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/render.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/bat/wizard.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/detect.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/model.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/render.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/validate.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/commitizen/wizard.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/detect.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/model.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/render.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/validate.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/docker/wizard.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/detect.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/model.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/render.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/validate.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/lazygit/wizard.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/detect.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/model.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/render.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/validate.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/precommit/wizard.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/fonts.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/model.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/preview.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/render.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/configure/starship/wizard.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/doctor.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/function_runner.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/functions.schema.json +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/functions.yaml +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/functions_catalog.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/functions_registry.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/generic.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/registry.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/ui.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/src/dev_setup/verbose.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/integration/__init__.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/integration/conftest.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/integration/test_agent_smoke.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/integration/test_agent_tui.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/integration/test_commitizen_config.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/integration/test_tools.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_agent.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_agent_completion.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_agent_loop.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_agent_sandbox.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_agent_tools.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_agent_wizard.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_catalog.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_configure_ansible.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_configure_bat.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_configure_commitizen.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_configure_docker.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_configure_lazygit.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_configure_precommit.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_configure_starship.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_doctor.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_functions.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_generic.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_registry.py +0 -0
- {devstuff-2.4.0 → devstuff-2.5.0}/tests/test_verbose.py +0 -0
|
@@ -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
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from unittest.mock import Mock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from click.testing import CliRunner
|
|
5
|
+
|
|
6
|
+
from dev_setup import configure
|
|
7
|
+
from dev_setup.commands.configure_cmd import configure_cmd, offer_after_install
|
|
8
|
+
from dev_setup.configure.reptyr import wizard
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def flow(monkeypatch, tmp_path):
|
|
13
|
+
scope = tmp_path / "ptrace_scope"
|
|
14
|
+
scope.write_text("1\n")
|
|
15
|
+
monkeypatch.setattr(wizard, "SCOPE_PATH", scope)
|
|
16
|
+
monkeypatch.setattr(wizard, "SYSTEM_PATH", tmp_path / "99-devstuff-reptyr.conf")
|
|
17
|
+
runner = Mock()
|
|
18
|
+
monkeypatch.setattr(wizard, "_run", runner)
|
|
19
|
+
monkeypatch.setattr(wizard.ui, "confirm", lambda *a, **kw: True)
|
|
20
|
+
return scope, runner
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def answers(monkeypatch, *values):
|
|
24
|
+
iterator = iter(values)
|
|
25
|
+
monkeypatch.setattr(wizard.ui, "select", lambda *a, **kw: next(iterator))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_registered_and_path(flow):
|
|
29
|
+
assert configure.get("reptyr").load() is wizard
|
|
30
|
+
result = CliRunner().invoke(configure_cmd, ["reptyr", "--path"])
|
|
31
|
+
assert result.exit_code == 0
|
|
32
|
+
assert str(wizard.SYSTEM_PATH) in result.output
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.mark.parametrize("value", ["0", "1", "2", "3", "broken", "4"])
|
|
36
|
+
def test_detection(flow, value):
|
|
37
|
+
path, _ = flow
|
|
38
|
+
path.write_text(value)
|
|
39
|
+
assert wizard.read_scope() == (int(value) if value in "0123" else None)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.parametrize("state", ["missing", "3"])
|
|
43
|
+
def test_unavailable_or_locked_cannot_mutate(flow, monkeypatch, state):
|
|
44
|
+
path, runner = flow
|
|
45
|
+
if state == "missing":
|
|
46
|
+
path.unlink()
|
|
47
|
+
else:
|
|
48
|
+
path.write_text(state)
|
|
49
|
+
answers(monkeypatch) # no prompts should be needed
|
|
50
|
+
assert wizard.run() is None
|
|
51
|
+
runner.assert_not_called()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.mark.parametrize("selection", [None, "keep"])
|
|
55
|
+
def test_cancel_policy(flow, monkeypatch, selection):
|
|
56
|
+
answers(monkeypatch, selection)
|
|
57
|
+
assert wizard.run() is None
|
|
58
|
+
flow[1].assert_not_called()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_declined_confirmation_writes_nothing(flow, monkeypatch, tmp_path):
|
|
62
|
+
answers(monkeypatch, "0")
|
|
63
|
+
monkeypatch.setattr(wizard.ui, "confirm", lambda *a, **kw: False)
|
|
64
|
+
target = tmp_path / "export.conf"
|
|
65
|
+
assert wizard.run(target=target) is None
|
|
66
|
+
assert not target.exists()
|
|
67
|
+
flow[1].assert_not_called()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.parametrize("value", [0, 1, 2])
|
|
71
|
+
def test_offline_export_never_uses_sudo(flow, monkeypatch, tmp_path, value):
|
|
72
|
+
flow[0].unlink()
|
|
73
|
+
answers(monkeypatch, str(value))
|
|
74
|
+
target = tmp_path / "export.conf"
|
|
75
|
+
assert wizard.run(target=target) == value
|
|
76
|
+
assert f"kernel.yama.ptrace_scope = {value}\n" in target.read_text()
|
|
77
|
+
flow[1].assert_not_called()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_temporary_change_verified_without_file(flow, monkeypatch):
|
|
81
|
+
path, runner = flow
|
|
82
|
+
runner.side_effect = lambda cmd: path.write_text("0")
|
|
83
|
+
answers(monkeypatch, "0", "temporary")
|
|
84
|
+
assert wizard.run() == 0
|
|
85
|
+
runner.assert_called_once_with(["sudo", "sysctl", "-w", "kernel.yama.ptrace_scope=0"])
|
|
86
|
+
assert not wizard.SYSTEM_PATH.exists()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_persistent_backup_and_load_only_own_file(flow, monkeypatch):
|
|
90
|
+
path, runner = flow
|
|
91
|
+
|
|
92
|
+
def execute(cmd):
|
|
93
|
+
if cmd[1] == "install":
|
|
94
|
+
from pathlib import Path
|
|
95
|
+
assert "--backup=numbered" in cmd
|
|
96
|
+
assert "kernel.yama.ptrace_scope = 0" in Path(cmd[-2]).read_text()
|
|
97
|
+
else:
|
|
98
|
+
assert cmd == ["sudo", "sysctl", "--load", str(wizard.SYSTEM_PATH)]
|
|
99
|
+
path.write_text("0")
|
|
100
|
+
|
|
101
|
+
runner.side_effect = execute
|
|
102
|
+
answers(monkeypatch, "0", "persistent")
|
|
103
|
+
assert wizard.run() == 0
|
|
104
|
+
assert runner.call_count == 2
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@pytest.mark.parametrize("failure", [RuntimeError("denied"), FileNotFoundError("sudo")])
|
|
108
|
+
def test_failed_apply_never_reports_success(flow, monkeypatch, failure):
|
|
109
|
+
flow[1].side_effect = failure
|
|
110
|
+
success = Mock()
|
|
111
|
+
monkeypatch.setattr(wizard.ui, "success", success)
|
|
112
|
+
answers(monkeypatch, "0", "temporary")
|
|
113
|
+
assert wizard.run() is None
|
|
114
|
+
success.assert_not_called()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_saved_but_not_applied_is_reported(flow, monkeypatch):
|
|
118
|
+
flow[1].side_effect = [None, RuntimeError("read-only sysctl")]
|
|
119
|
+
warning = Mock()
|
|
120
|
+
monkeypatch.setattr(wizard.ui, "warn", warning)
|
|
121
|
+
answers(monkeypatch, "0", "persistent")
|
|
122
|
+
assert wizard.run() is None
|
|
123
|
+
assert "was saved, but the live change failed" in warning.call_args.args[0]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_readback_mismatch_is_failure(flow, monkeypatch):
|
|
127
|
+
answers(monkeypatch, "0", "temporary")
|
|
128
|
+
assert wizard.run() is None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_mode_three_cannot_be_generated():
|
|
132
|
+
with pytest.raises(ValueError):
|
|
133
|
+
wizard.render(3)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_post_install_offer_runs_wizard(monkeypatch):
|
|
137
|
+
monkeypatch.setattr("sys.stdin.isatty", lambda: True)
|
|
138
|
+
monkeypatch.setattr(wizard.ui, "confirm", lambda *a, **kw: True)
|
|
139
|
+
run = Mock()
|
|
140
|
+
monkeypatch.setattr(wizard, "run", run)
|
|
141
|
+
offer_after_install("reptyr")
|
|
142
|
+
run.assert_called_once_with()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_noninteractive_install_does_not_change_permissions(monkeypatch):
|
|
146
|
+
monkeypatch.setattr("sys.stdin.isatty", lambda: False)
|
|
147
|
+
run = Mock()
|
|
148
|
+
monkeypatch.setattr(wizard, "run", run)
|
|
149
|
+
offer_after_install("reptyr")
|
|
150
|
+
run.assert_not_called()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|