kanibako-cli 1.5.0.dev14__tar.gz → 1.6.0.dev23__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.
- {kanibako_cli-1.5.0.dev14/src/kanibako_cli.egg-info → kanibako_cli-1.6.0.dev23}/PKG-INFO +1 -1
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/README.md +14 -7
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/pyproject.toml +15 -2
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/__init__.py +1 -1
- kanibako_cli-1.6.0.dev23/src/kanibako/_atomic.py +62 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/auth_browser.py +7 -0
- kanibako_cli-1.6.0.dev23/src/kanibako/baseline.py +124 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/cli.py +3 -1
- kanibako_cli-1.6.0.dev23/src/kanibako/commands/baseline_cmd.py +235 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/box/__init__.py +8 -5
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/box/_duplicate.py +125 -27
- kanibako_cli-1.6.0.dev23/src/kanibako/commands/box/_lifecycle.py +1397 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/box/_parser.py +100 -165
- kanibako_cli-1.6.0.dev23/src/kanibako/commands/diagnose.py +497 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/image.py +59 -79
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/install.py +7 -8
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/start.py +582 -124
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/workset_cmd.py +332 -3
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/config.py +208 -52
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/config_interface.py +23 -12
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/config_io.py +10 -6
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/container.py +86 -90
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/containerfiles.py +1 -1
- kanibako_cli-1.6.0.dev23/src/kanibako/containers/tmux.conf +12 -0
- kanibako_cli-1.6.0.dev23/src/kanibako/data/image-baseline.yaml +29 -0
- kanibako_cli-1.6.0.dev23/src/kanibako/freshness.py +91 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/helper_listener.py +11 -1
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/paths.py +78 -2
- kanibako_cli-1.6.0.dev23/src/kanibako/py.typed +0 -0
- kanibako_cli-1.6.0.dev23/src/kanibako/registry.py +174 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/rig_meta.py +3 -2
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/rig_registry.py +3 -1
- kanibako_cli-1.6.0.dev23/src/kanibako/scripts/__init__.py +0 -0
- kanibako_cli-1.6.0.dev23/src/kanibako/shells.py +189 -0
- kanibako_cli-1.6.0.dev23/src/kanibako/targets/assembly.py +328 -0
- kanibako_cli-1.6.0.dev23/src/kanibako/targets/base.py +511 -0
- kanibako_cli-1.6.0.dev23/src/kanibako/targets/credsync.py +165 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/templates_image.py +3 -2
- kanibako_cli-1.6.0.dev23/src/kanibako/workset.py +672 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23/src/kanibako_cli.egg-info}/PKG-INFO +1 -1
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako_cli.egg-info/SOURCES.txt +14 -2
- kanibako_cli-1.6.0.dev23/tests/test_atomic.py +108 -0
- kanibako_cli-1.6.0.dev23/tests/test_baseline.py +135 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_cli.py +60 -25
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_config.py +292 -13
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_config_interface.py +70 -4
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_container.py +174 -22
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_container_extended.py +13 -32
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_containerfiles.py +27 -16
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_error_messages.py +22 -20
- kanibako_cli-1.6.0.dev23/tests/test_freshness.py +168 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_helper_listener.py +72 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_paths.py +104 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_registry.py +108 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_resource_scoping.py +73 -5
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_safety_invariants.py +15 -123
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_settings_seeds.py +58 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_settings_shares.py +64 -0
- kanibako_cli-1.6.0.dev23/tests/test_shells.py +267 -0
- kanibako_cli-1.6.0.dev23/tests/test_workset.py +686 -0
- kanibako_cli-1.5.0.dev14/src/kanibako/commands/box/_migrate.py +0 -574
- kanibako_cli-1.5.0.dev14/src/kanibako/commands/diagnose.py +0 -239
- kanibako_cli-1.5.0.dev14/src/kanibako/containers/Containerfile.kanibako +0 -99
- kanibako_cli-1.5.0.dev14/src/kanibako/freshness.py +0 -67
- kanibako_cli-1.5.0.dev14/src/kanibako/registry.py +0 -71
- kanibako_cli-1.5.0.dev14/src/kanibako/targets/base.py +0 -243
- kanibako_cli-1.5.0.dev14/src/kanibako/workset.py +0 -347
- kanibako_cli-1.5.0.dev14/tests/test_freshness.py +0 -101
- kanibako_cli-1.5.0.dev14/tests/test_workset.py +0 -369
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/LICENSE.md +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/setup.cfg +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/__main__.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/auth_parser.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/browser_sidecar.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/browser_state.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/bun_sea.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/__init__.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/archive.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/clean.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/crab_cmd.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/fork_cmd.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/helper_cmd.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/refresh_credentials.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/restore.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/setup_cmd.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/stop.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/system_cmd.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/upgrade.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/commands/vault_cmd.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/containers/Containerfile.template-android +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/containers/Containerfile.template-dotnet +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/containers/Containerfile.template-js +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/containers/Containerfile.template-jvm +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/containers/Containerfile.template-systems +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/containers/__init__.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/crabs.py +0 -0
- {kanibako_cli-1.5.0.dev14/src/kanibako/scripts → kanibako_cli-1.6.0.dev23/src/kanibako/data}/__init__.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/errors.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/git.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/helper_client.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/helpers.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/hygiene.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/image_sharing.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/instructions.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/log.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/names.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/plugins/__init__.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/rig_bundle.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/rig_resolve.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/rig_source.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/scripts/helper-init.sh +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/scripts/kanibako-entry +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/settings_resolve.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/settings_seeds.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/settings_shares.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/shellenv.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/snapshots.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/targets/__init__.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/targets/no_agent.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/templates.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/tweakcc.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/tweakcc_cache.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako/utils.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako_cli.egg-info/dependency_links.txt +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako_cli.egg-info/entry_points.txt +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako_cli.egg-info/requires.txt +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/src/kanibako_cli.egg-info/top_level.txt +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_archive_restore_integration.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_auth_browser.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_auth_parser.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_browser_sidecar.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_browser_state.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_bun_sea.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_cli_exitcodes.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_comms.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_container_integration.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_crabs.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_credentials.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_env_cmd.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_fork.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_git.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_helper_client.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_helper_cmd.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_helpers.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_hygiene.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_image_sharing.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_init_cmd.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_install_integration.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_instructions.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_lifecycle_integration.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_log.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_names.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_paths_extended.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_rig_bundle.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_rig_meta.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_rig_registry.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_rig_resolve.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_rig_source.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_settings_resolve.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_shellenv.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_snapshots.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_standalone_paths.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_start_integration.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_status.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_system_paths.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_templates.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_templates_image.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_tweakcc.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_utils.py +0 -0
- {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.6.0.dev23}/tests/test_workset_paths.py +0 -0
|
@@ -222,11 +222,13 @@ shortcuts for common operations:
|
|
|
222
222
|
| `box config` | View or modify project configuration |
|
|
223
223
|
| `box diagnose [project]` | Check project box health |
|
|
224
224
|
|
|
225
|
-
**Relocation:**
|
|
225
|
+
**Relocation & conversion:**
|
|
226
226
|
|
|
227
227
|
| Subcommand | Description |
|
|
228
228
|
|------------|-------------|
|
|
229
|
-
| `box
|
|
229
|
+
| `box remap <old> [<new>]` | Update kanibako's recorded path after you moved the folder yourself (records only, no file move; `<new>` defaults to `./`) |
|
|
230
|
+
| `box move <old> <new>` / `box mv` | Physically relocate the workspace (both paths required; a target flag also changes ownership) |
|
|
231
|
+
| `box convert [<old>] (--default \| --standalone \| --workset <ws>)` | Change ownership/mode (in-place by default; `--move [path]` relocates, bare `--move` moves into the target workset; `--name` renames) |
|
|
230
232
|
| `box duplicate <source> [dest]` | Copy project (`--name`, `--bare`, `--force`) |
|
|
231
233
|
| `box archive [project]` | Pack session data to .txz (`--all`, `--allow-uncommitted`, `--allow-unpushed`, `--force`) |
|
|
232
234
|
| `box extract <archive> [dest]` | Unpack from archive (`--name`, `--force`) |
|
|
@@ -248,7 +250,7 @@ shortcuts for common operations:
|
|
|
248
250
|
| `rig list` / `rig ls` | List available rigs (`-q`) |
|
|
249
251
|
| `rig info` / `rig inspect` | Rig details (source, size, recoverability) |
|
|
250
252
|
| `rig rm` / `rig delete` | Remove rig (`--force`) |
|
|
251
|
-
| `rig rebuild [rig]` | Rebuild
|
|
253
|
+
| `rig rebuild [rig]` | Rebuild a template, or pull a prefab/base from the registry (`--all`) |
|
|
252
254
|
| `rig diagnose` | Check rig (image) status |
|
|
253
255
|
|
|
254
256
|
### `workset` Subcommands
|
|
@@ -437,13 +439,17 @@ Container rigs are built on [Droste](https://github.com/doctorjei/droste), a
|
|
|
437
439
|
layered OCI image builder.
|
|
438
440
|
[Kento](https://github.com/doctorjei/kento) converts them to LXC/VM hosts.
|
|
439
441
|
|
|
440
|
-
|
|
441
|
-
Kanibako
|
|
442
|
+
Base rigs are pulled automatically from GHCR on first use; they are
|
|
443
|
+
**pull-only** (Kanibako does not build them locally). If a pull fails, Kanibako
|
|
444
|
+
reports an actionable error. To use a custom base, build it yourself from the
|
|
445
|
+
[kanibako-images](https://github.com/doctorjei/kanibako-images) repo and point
|
|
446
|
+
Kanibako at the resulting local image via `--image` / `box_image`. Toolchain
|
|
447
|
+
*templates* still build locally (they layer on a pulled base).
|
|
442
448
|
|
|
443
449
|
```bash
|
|
444
450
|
kanibako rig list # show local rigs
|
|
445
|
-
kanibako rig rebuild #
|
|
446
|
-
kanibako rig rebuild --all #
|
|
451
|
+
kanibako rig rebuild # build the current template / pull the current base
|
|
452
|
+
kanibako rig rebuild --all # update all known rigs
|
|
447
453
|
```
|
|
448
454
|
|
|
449
455
|
### Custom Rigs
|
|
@@ -802,6 +808,7 @@ All kanibako config files are YAML.
|
|
|
802
808
|
| `autonomous` | `true` | Enable autonomy override |
|
|
803
809
|
| `persistence` | `persistent` | Session type (persistent/ephemeral) |
|
|
804
810
|
| `box.image` | `kanibako-oci:latest` | Container rig |
|
|
811
|
+
| `box.shell` | `$KANIBAKO_SHELL` | Shell launched for a no-agent box (`kanibako start` with no agent, `kanibako shell`); resolved `box.shell` → `$KANIBAKO_SHELL` → the image's recorded login shell → `sh` |
|
|
805
812
|
| `box.crab` | (auto-detect) | Agent target plugin |
|
|
806
813
|
| `box.share_images` | | Share host images into the box |
|
|
807
814
|
| `group_auth` | `true` | Shared credentials across the group (`true`) vs. per-project (`false`) |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kanibako-cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.6.0.dev23"
|
|
8
8
|
description = "Run AI coding agents in rootless containers with per-project isolation"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
dependencies = ["argcomplete>=3.0", "PyYAML>=6.0"]
|
|
@@ -22,8 +22,10 @@ dev = ["pytest>=7.0", "pytest-cov", "mypy", "ruff", "bump2version"]
|
|
|
22
22
|
where = ["src"]
|
|
23
23
|
|
|
24
24
|
[tool.setuptools.package-data]
|
|
25
|
-
"kanibako
|
|
25
|
+
"kanibako" = ["py.typed"]
|
|
26
|
+
"kanibako.containers" = ["Containerfile.*", "tmux.conf"]
|
|
26
27
|
"kanibako.scripts" = ["*.sh", "kanibako-entry"]
|
|
28
|
+
"kanibako.data" = ["*.yaml"]
|
|
27
29
|
|
|
28
30
|
[tool.ruff]
|
|
29
31
|
target-version = "py311"
|
|
@@ -32,6 +34,17 @@ line-length = 99
|
|
|
32
34
|
[tool.ruff.lint.per-file-ignores]
|
|
33
35
|
"tests/conftest.py" = ["E402"]
|
|
34
36
|
|
|
37
|
+
[tool.mypy]
|
|
38
|
+
# kanibako is a namespace package split across distributions: the core ships
|
|
39
|
+
# kanibako/ (regular package) while each plugin contributes
|
|
40
|
+
# kanibako/plugins/<agent>/ with no __init__.py in kanibako/plugins/. To let
|
|
41
|
+
# `mypy packages/agent-<agent>/src` assign the full dotted module name (and
|
|
42
|
+
# thus resolve sibling imports like kanibako.plugins.claude.target), point
|
|
43
|
+
# mypy at every src base and enable explicit package bases.
|
|
44
|
+
namespace_packages = true
|
|
45
|
+
explicit_package_bases = true
|
|
46
|
+
mypy_path = "$MYPY_CONFIG_FILE_DIR/src:$MYPY_CONFIG_FILE_DIR/packages/agent-claude/src:$MYPY_CONFIG_FILE_DIR/packages/agent-goose/src:$MYPY_CONFIG_FILE_DIR/packages/agent-codex/src"
|
|
47
|
+
|
|
35
48
|
[[tool.mypy.overrides]]
|
|
36
49
|
module = ["yaml"]
|
|
37
50
|
ignore_missing_imports = true
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Atomic file writes for persistent state.
|
|
2
|
+
|
|
3
|
+
A crash mid-write with a plain ``path.write_text(...)`` leaves a torn/truncated
|
|
4
|
+
file that the next run fails to parse. Writing to a temp file in the *same
|
|
5
|
+
directory* and then ``os.replace``-ing it over the target is atomic on POSIX
|
|
6
|
+
(``rename(2)`` within one filesystem), so a reader sees either the old file or
|
|
7
|
+
the new one — never a half-written one.
|
|
8
|
+
|
|
9
|
+
These helpers are dependency-free (only the stdlib) so any module can route its
|
|
10
|
+
registry/state writes through them without risking an import cycle.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import tempfile
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def atomic_write_text(path: Path, data: str) -> None:
|
|
23
|
+
"""Write *data* to *path* atomically.
|
|
24
|
+
|
|
25
|
+
The bytes land in a temp file in ``path.parent`` (so ``os.replace`` stays on
|
|
26
|
+
one filesystem), are flushed and ``fsync``-ed, then renamed over *path*. On
|
|
27
|
+
any failure the temp file is removed and the original *path* is left intact.
|
|
28
|
+
Parent directories are created if needed.
|
|
29
|
+
"""
|
|
30
|
+
path = Path(path)
|
|
31
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
|
|
33
|
+
encoded = data.encode("utf-8")
|
|
34
|
+
# Create the temp file in the SAME directory as the target so os.replace is
|
|
35
|
+
# a same-filesystem rename (atomic). delete=False so we control the rename.
|
|
36
|
+
fd, tmp_name = tempfile.mkstemp(
|
|
37
|
+
prefix=f".{path.name}.", suffix=".tmp", dir=str(path.parent),
|
|
38
|
+
)
|
|
39
|
+
tmp_path = Path(tmp_name)
|
|
40
|
+
try:
|
|
41
|
+
with os.fdopen(fd, "wb") as fh:
|
|
42
|
+
fh.write(encoded)
|
|
43
|
+
fh.flush()
|
|
44
|
+
os.fsync(fh.fileno())
|
|
45
|
+
os.replace(tmp_path, path)
|
|
46
|
+
except BaseException:
|
|
47
|
+
# Leave the original file untouched; clean up the temp on any failure.
|
|
48
|
+
try:
|
|
49
|
+
tmp_path.unlink()
|
|
50
|
+
except OSError:
|
|
51
|
+
pass
|
|
52
|
+
raise
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def atomic_write_yaml(path: Path, data: object, **dump_kwargs: object) -> None:
|
|
56
|
+
"""Serialize *data* to YAML and write it to *path* atomically.
|
|
57
|
+
|
|
58
|
+
A thin convenience over :func:`atomic_write_text` for the common case of a
|
|
59
|
+
YAML document. Extra keyword arguments are forwarded to ``yaml.safe_dump``.
|
|
60
|
+
"""
|
|
61
|
+
text = yaml.safe_dump(data, **dump_kwargs)
|
|
62
|
+
atomic_write_text(path, text)
|
|
@@ -185,6 +185,7 @@ def auto_refresh_auth(
|
|
|
185
185
|
*,
|
|
186
186
|
headless: bool = True,
|
|
187
187
|
login_timeout: float = 60,
|
|
188
|
+
env: dict[str, str] | None = None,
|
|
188
189
|
) -> AuthResult:
|
|
189
190
|
"""Orchestrate fully automated OAuth: start login, parse URL, automate browser.
|
|
190
191
|
|
|
@@ -194,6 +195,11 @@ def auto_refresh_auth(
|
|
|
194
195
|
4. If the browser clicks "Authorize", the redirect completes the login
|
|
195
196
|
5. Wait for ``claude auth login`` to finish
|
|
196
197
|
|
|
198
|
+
*env*, when supplied, fully replaces the environment of the spawned
|
|
199
|
+
``claude auth login`` process. Callers pass it to inject
|
|
200
|
+
``DISABLE_AUTOUPDATER=1`` so this host exec does not wake Claude's async
|
|
201
|
+
background auto-updater mid-launch.
|
|
202
|
+
|
|
197
203
|
Returns :class:`AuthResult` indicating success or failure.
|
|
198
204
|
"""
|
|
199
205
|
import subprocess
|
|
@@ -215,6 +221,7 @@ def auto_refresh_auth(
|
|
|
215
221
|
stderr=subprocess.STDOUT,
|
|
216
222
|
stdin=subprocess.PIPE,
|
|
217
223
|
text=True,
|
|
224
|
+
env=env,
|
|
218
225
|
)
|
|
219
226
|
except (FileNotFoundError, OSError) as exc:
|
|
220
227
|
return AuthResult(success=False, error=f"Failed to start auth: {exc}")
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Image-baseline manifest: the universal in-box runtime tool contract.
|
|
2
|
+
|
|
3
|
+
The baseline is a mapping of ``apt-package-name -> [executables]``. The shipped
|
|
4
|
+
default lives in :mod:`kanibako.data` (``image-baseline.yaml``); site and user
|
|
5
|
+
overlays are merged on top **additively** (the scoped-shares spirit in
|
|
6
|
+
:mod:`kanibako.settings_shares`): later layers add new packages or override an
|
|
7
|
+
existing package's executable list. Precedence, least- to most-specific:
|
|
8
|
+
|
|
9
|
+
built-in default < /etc/kanibako/image-baseline.yaml < ~/.config/kanibako/image-baseline.yaml
|
|
10
|
+
|
|
11
|
+
The package NAME is what ``apt-get install`` consumes (auto-install assumes
|
|
12
|
+
apt/debian); the EXECUTABLE values are what ``command -v`` probes (verify is
|
|
13
|
+
package-manager-agnostic). They differ on purpose (e.g. ``ripgrep`` -> ``rg``).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import importlib.resources
|
|
19
|
+
import sys
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import yaml
|
|
24
|
+
|
|
25
|
+
from kanibako.paths import xdg
|
|
26
|
+
|
|
27
|
+
# Filename used both for the shipped default (in kanibako.data) and the overlays.
|
|
28
|
+
BASELINE_FILENAME = "image-baseline.yaml"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _read_doc(path: Path) -> dict[str, list[str]]:
|
|
32
|
+
"""Parse a baseline YAML file into ``{package: [executables]}``.
|
|
33
|
+
|
|
34
|
+
Tolerates a missing/empty file (returns ``{}``). Normalizes each value to
|
|
35
|
+
a list of strings; a bare string value becomes a single-element list.
|
|
36
|
+
"""
|
|
37
|
+
if not path.is_file():
|
|
38
|
+
return {}
|
|
39
|
+
raw = yaml.safe_load(path.read_text()) or {}
|
|
40
|
+
if not isinstance(raw, dict):
|
|
41
|
+
return {}
|
|
42
|
+
result: dict[str, list[str]] = {}
|
|
43
|
+
for pkg, exes in raw.items():
|
|
44
|
+
if exes is None:
|
|
45
|
+
result[str(pkg)] = []
|
|
46
|
+
elif isinstance(exes, str):
|
|
47
|
+
result[str(pkg)] = [exes]
|
|
48
|
+
else:
|
|
49
|
+
result[str(pkg)] = [str(e) for e in exes]
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _shipped_default() -> dict[str, list[str]]:
|
|
54
|
+
"""Read the bundled default baseline shipped as package data."""
|
|
55
|
+
ref = importlib.resources.files("kanibako.data").joinpath(BASELINE_FILENAME)
|
|
56
|
+
return _read_doc(Path(str(ref)))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _overlay_paths() -> list[Path]:
|
|
60
|
+
"""Overlay locations, in additive merge order (machine then user)."""
|
|
61
|
+
config_home = xdg("XDG_CONFIG_HOME", ".config")
|
|
62
|
+
return [
|
|
63
|
+
Path("/etc/kanibako") / BASELINE_FILENAME,
|
|
64
|
+
config_home / "kanibako" / BASELINE_FILENAME,
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def load_baseline() -> dict[str, list[str]]:
|
|
69
|
+
"""Return the merged baseline ``{package: [executables]}``.
|
|
70
|
+
|
|
71
|
+
Starts from the shipped default, then additively merges the machine
|
|
72
|
+
(``/etc``) and user (``~/.config``) overlays if present: a later layer adds
|
|
73
|
+
a new package or replaces an existing package's executable list. Missing
|
|
74
|
+
overlay files are treated as empty levels.
|
|
75
|
+
"""
|
|
76
|
+
merged = _shipped_default()
|
|
77
|
+
for path in _overlay_paths():
|
|
78
|
+
merged.update(_read_doc(path))
|
|
79
|
+
return merged
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def packages() -> list[str]:
|
|
83
|
+
"""Return the baseline package names in sorted, stable order."""
|
|
84
|
+
return sorted(load_baseline())
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def executables() -> list[tuple[str, str]]:
|
|
88
|
+
"""Return ``(package, executable)`` pairs, sorted by package then executable."""
|
|
89
|
+
pairs: list[tuple[str, str]] = []
|
|
90
|
+
baseline = load_baseline()
|
|
91
|
+
for pkg in sorted(baseline):
|
|
92
|
+
for exe in baseline[pkg]:
|
|
93
|
+
pairs.append((pkg, exe))
|
|
94
|
+
return pairs
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def verify(probe: Callable[[str], bool]) -> list[tuple[str, str]]:
|
|
98
|
+
"""Return the ``(package, executable)`` pairs whose executable is missing.
|
|
99
|
+
|
|
100
|
+
*probe* answers "is this executable present?" (typically a ``command -v``
|
|
101
|
+
check inside an image). An empty result means the baseline is satisfied.
|
|
102
|
+
"""
|
|
103
|
+
return [(pkg, exe) for pkg, exe in executables() if not probe(exe)]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def install_command(pkgs: list[str]) -> list[str]:
|
|
107
|
+
"""Build the apt-get install argv for *pkgs* (debian).
|
|
108
|
+
|
|
109
|
+
Returns the full argv (``apt-get install -y --no-install-recommends ...``).
|
|
110
|
+
The caller decides where it runs (host vs. in-box) and on non-debian
|
|
111
|
+
systems should skip it (see :func:`warn_non_debian`).
|
|
112
|
+
"""
|
|
113
|
+
return [
|
|
114
|
+
"apt-get", "install", "-y", "--no-install-recommends", *pkgs,
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def warn_non_debian() -> None:
|
|
119
|
+
"""Emit a stderr warning that auto-install only supports apt/debian."""
|
|
120
|
+
print(
|
|
121
|
+
"kanibako baseline: automatic install assumes apt/debian; "
|
|
122
|
+
"install the baseline packages with your distro's package manager.",
|
|
123
|
+
file=sys.stderr,
|
|
124
|
+
)
|
|
@@ -74,6 +74,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
74
74
|
from kanibako.commands.workset_cmd import add_parser as add_workset_parser
|
|
75
75
|
from kanibako.commands.crab_cmd import add_parser as add_crab_parser
|
|
76
76
|
from kanibako.commands.system_cmd import add_parser as add_system_parser
|
|
77
|
+
from kanibako.commands.baseline_cmd import add_parser as add_baseline_parser
|
|
77
78
|
|
|
78
79
|
# Setup wizard (before management commands, works pre-init).
|
|
79
80
|
from kanibako.commands.setup_cmd import run_setup
|
|
@@ -165,6 +166,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
165
166
|
add_workset_parser(subparsers)
|
|
166
167
|
add_crab_parser(subparsers)
|
|
167
168
|
add_system_parser(subparsers)
|
|
169
|
+
add_baseline_parser(subparsers)
|
|
168
170
|
|
|
169
171
|
return parser
|
|
170
172
|
|
|
@@ -179,7 +181,7 @@ _SUBCOMMANDS = {
|
|
|
179
181
|
# Top-level aliases (delegate to box subcommands).
|
|
180
182
|
"start", "stop", "shell", "ps", "list", "create", "rm",
|
|
181
183
|
# Management commands.
|
|
182
|
-
"box", "rig", "workset", "crab", "system",
|
|
184
|
+
"box", "rig", "workset", "crab", "system", "baseline",
|
|
183
185
|
# Command aliases (#62).
|
|
184
186
|
"agent", "image", "container",
|
|
185
187
|
# Setup wizard.
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""kanibako baseline: inspect, verify, and install the image baseline contract.
|
|
2
|
+
|
|
3
|
+
The baseline is the universal set of in-box runtime tools kanibako relies on
|
|
4
|
+
(see :mod:`kanibako.baseline`). ``list`` feeds a Containerfile (``apt-get
|
|
5
|
+
install $(kanibako baseline list)``), ``verify`` probes an image for the
|
|
6
|
+
required executables, and ``install`` runs the apt-get install of the package
|
|
7
|
+
names.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from kanibako import baseline as baseline_mod
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def add_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
20
|
+
p = subparsers.add_parser(
|
|
21
|
+
"baseline",
|
|
22
|
+
help="Inspect/verify the universal in-box tool baseline",
|
|
23
|
+
description=(
|
|
24
|
+
"Inspect, verify, or install the image baseline — the universal set "
|
|
25
|
+
"of in-box runtime tools kanibako relies on across all box variants."
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
sub = p.add_subparsers(dest="baseline_command", metavar="COMMAND")
|
|
29
|
+
|
|
30
|
+
# baseline list [--executables]
|
|
31
|
+
list_p = sub.add_parser(
|
|
32
|
+
"list",
|
|
33
|
+
help="Print baseline package names (or executables with --executables)",
|
|
34
|
+
)
|
|
35
|
+
list_p.add_argument(
|
|
36
|
+
"--executables", action="store_true",
|
|
37
|
+
help="Print 'package: exe1 exe2' per line instead of just package names",
|
|
38
|
+
)
|
|
39
|
+
list_p.set_defaults(func=run_list)
|
|
40
|
+
|
|
41
|
+
# baseline verify [IMAGE] [--all] [--only PKG ...] [--skip PKG ...]
|
|
42
|
+
verify_p = sub.add_parser(
|
|
43
|
+
"verify",
|
|
44
|
+
help="Probe an image for the baseline executables",
|
|
45
|
+
)
|
|
46
|
+
verify_p.add_argument(
|
|
47
|
+
"image", nargs="?", default=None,
|
|
48
|
+
help="Image to probe (default: the configured box_image)",
|
|
49
|
+
)
|
|
50
|
+
verify_p.add_argument(
|
|
51
|
+
"--all", action="store_true", dest="all_images",
|
|
52
|
+
help="Probe every local kanibako-* image",
|
|
53
|
+
)
|
|
54
|
+
verify_p.add_argument(
|
|
55
|
+
"--only", nargs="+", default=None, metavar="PKG",
|
|
56
|
+
help="Only check these baseline packages",
|
|
57
|
+
)
|
|
58
|
+
verify_p.add_argument(
|
|
59
|
+
"--skip", nargs="+", default=None, metavar="PKG",
|
|
60
|
+
help="Skip these baseline packages",
|
|
61
|
+
)
|
|
62
|
+
verify_p.set_defaults(func=run_verify)
|
|
63
|
+
|
|
64
|
+
# baseline install [--only PKG ...] [--skip PKG ...]
|
|
65
|
+
install_p = sub.add_parser(
|
|
66
|
+
"install",
|
|
67
|
+
help="apt-get install the baseline packages (debian; current environment)",
|
|
68
|
+
)
|
|
69
|
+
install_p.add_argument(
|
|
70
|
+
"--only", nargs="+", default=None, metavar="PKG",
|
|
71
|
+
help="Only install these baseline packages",
|
|
72
|
+
)
|
|
73
|
+
install_p.add_argument(
|
|
74
|
+
"--skip", nargs="+", default=None, metavar="PKG",
|
|
75
|
+
help="Skip these baseline packages",
|
|
76
|
+
)
|
|
77
|
+
install_p.add_argument(
|
|
78
|
+
"--dry-run", action="store_true",
|
|
79
|
+
help="Print the install command instead of running it",
|
|
80
|
+
)
|
|
81
|
+
install_p.set_defaults(func=run_install)
|
|
82
|
+
|
|
83
|
+
# Default to list when 'baseline' is run bare.
|
|
84
|
+
p.set_defaults(func=run_list)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _filter_packages(
|
|
88
|
+
pkgs: list[str], only: list[str] | None, skip: list[str] | None,
|
|
89
|
+
) -> list[str]:
|
|
90
|
+
"""Apply --only / --skip filters to a sorted package list."""
|
|
91
|
+
result = pkgs
|
|
92
|
+
if only:
|
|
93
|
+
only_set = set(only)
|
|
94
|
+
result = [p for p in result if p in only_set]
|
|
95
|
+
if skip:
|
|
96
|
+
skip_set = set(skip)
|
|
97
|
+
result = [p for p in result if p not in skip_set]
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def run_list(args: argparse.Namespace) -> int:
|
|
102
|
+
"""Print the baseline.
|
|
103
|
+
|
|
104
|
+
Default: package names, space-separated on one line — a Containerfile
|
|
105
|
+
consumes this verbatim via ``$(kanibako baseline list)``, so stdout stays
|
|
106
|
+
clean (no logging). With ``--executables``: ``package: exe1 exe2`` lines.
|
|
107
|
+
"""
|
|
108
|
+
if getattr(args, "executables", False):
|
|
109
|
+
baseline = baseline_mod.load_baseline()
|
|
110
|
+
for pkg in sorted(baseline):
|
|
111
|
+
exes = " ".join(baseline[pkg])
|
|
112
|
+
print(f"{pkg}: {exes}")
|
|
113
|
+
return 0
|
|
114
|
+
|
|
115
|
+
pkgs = baseline_mod.packages()
|
|
116
|
+
print(" ".join(pkgs))
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _make_probe(runtime, image: str):
|
|
121
|
+
"""Return a ``probe(exe) -> bool`` that runs an ephemeral container.
|
|
122
|
+
|
|
123
|
+
Each probe runs ``<runtime> run --rm <image> sh -lc 'command -v <exe>'``
|
|
124
|
+
and reports success on a zero exit code.
|
|
125
|
+
"""
|
|
126
|
+
def probe(exe: str) -> bool:
|
|
127
|
+
result = subprocess.run(
|
|
128
|
+
[
|
|
129
|
+
runtime.cmd, "run", "--rm", image,
|
|
130
|
+
"sh", "-lc", f"command -v {exe}",
|
|
131
|
+
],
|
|
132
|
+
capture_output=True,
|
|
133
|
+
text=True,
|
|
134
|
+
)
|
|
135
|
+
return result.returncode == 0
|
|
136
|
+
|
|
137
|
+
return probe
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _verify_image(runtime, image: str, pkgs: list[str]) -> list[tuple[str, str]]:
|
|
141
|
+
"""Probe *image* for the executables of *pkgs*. Return missing pairs."""
|
|
142
|
+
baseline = baseline_mod.load_baseline()
|
|
143
|
+
wanted = {pkg: baseline[pkg] for pkg in pkgs if pkg in baseline}
|
|
144
|
+
probe = _make_probe(runtime, image)
|
|
145
|
+
missing: list[tuple[str, str]] = []
|
|
146
|
+
for pkg in sorted(wanted):
|
|
147
|
+
for exe in wanted[pkg]:
|
|
148
|
+
if not probe(exe):
|
|
149
|
+
missing.append((pkg, exe))
|
|
150
|
+
return missing
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def run_verify(args: argparse.Namespace) -> int:
|
|
154
|
+
"""Probe one or all images for the baseline executables.
|
|
155
|
+
|
|
156
|
+
Exit code: 1 if any executable is missing in any probed image, else 0.
|
|
157
|
+
"""
|
|
158
|
+
from kanibako.config import config_file_path, load_merged_config
|
|
159
|
+
from kanibako.container import ContainerRuntime
|
|
160
|
+
from kanibako.errors import ContainerError
|
|
161
|
+
from kanibako.paths import xdg
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
runtime = ContainerRuntime()
|
|
165
|
+
except ContainerError as exc:
|
|
166
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
167
|
+
return 1
|
|
168
|
+
|
|
169
|
+
pkgs = _filter_packages(
|
|
170
|
+
baseline_mod.packages(),
|
|
171
|
+
getattr(args, "only", None),
|
|
172
|
+
getattr(args, "skip", None),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Determine the image(s) to probe.
|
|
176
|
+
if getattr(args, "all_images", False):
|
|
177
|
+
images = [repo for repo, _size in runtime.list_local_images()]
|
|
178
|
+
if not images:
|
|
179
|
+
print("No local kanibako images found.", file=sys.stderr)
|
|
180
|
+
return 1
|
|
181
|
+
else:
|
|
182
|
+
image = getattr(args, "image", None)
|
|
183
|
+
if not image:
|
|
184
|
+
config_home = xdg("XDG_CONFIG_HOME", ".config")
|
|
185
|
+
cf = config_file_path(config_home)
|
|
186
|
+
merged = load_merged_config(cf, None)
|
|
187
|
+
image = merged.box_image
|
|
188
|
+
images = [image]
|
|
189
|
+
|
|
190
|
+
any_missing = False
|
|
191
|
+
for image in images:
|
|
192
|
+
missing = _verify_image(runtime, image, pkgs)
|
|
193
|
+
print(f"Baseline verify: {image}")
|
|
194
|
+
if not missing:
|
|
195
|
+
print(" [ok] all baseline executables present")
|
|
196
|
+
else:
|
|
197
|
+
any_missing = True
|
|
198
|
+
for pkg, exe in missing:
|
|
199
|
+
print(f" [!!] {pkg}: missing '{exe}'")
|
|
200
|
+
print()
|
|
201
|
+
|
|
202
|
+
return 1 if any_missing else 0
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def run_install(args: argparse.Namespace) -> int:
|
|
206
|
+
"""apt-get install the baseline packages in the CURRENT environment.
|
|
207
|
+
|
|
208
|
+
This targets the environment the command runs in (e.g. inside a box, or a
|
|
209
|
+
host that uses apt) — it does NOT shell into a separate box. Use it from a
|
|
210
|
+
Containerfile/box where ``apt-get`` is available and you have privileges.
|
|
211
|
+
Non-debian systems are warned and skipped.
|
|
212
|
+
"""
|
|
213
|
+
import shutil
|
|
214
|
+
|
|
215
|
+
pkgs = _filter_packages(
|
|
216
|
+
baseline_mod.packages(),
|
|
217
|
+
getattr(args, "only", None),
|
|
218
|
+
getattr(args, "skip", None),
|
|
219
|
+
)
|
|
220
|
+
if not pkgs:
|
|
221
|
+
print("Nothing to install.", file=sys.stderr)
|
|
222
|
+
return 0
|
|
223
|
+
|
|
224
|
+
cmd = baseline_mod.install_command(pkgs)
|
|
225
|
+
|
|
226
|
+
if getattr(args, "dry_run", False):
|
|
227
|
+
print(" ".join(cmd))
|
|
228
|
+
return 0
|
|
229
|
+
|
|
230
|
+
if shutil.which("apt-get") is None:
|
|
231
|
+
baseline_mod.warn_non_debian()
|
|
232
|
+
return 1
|
|
233
|
+
|
|
234
|
+
result = subprocess.run(cmd)
|
|
235
|
+
return result.returncode
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
"""kanibako box: project lifecycle management (create, list, config,
|
|
1
|
+
"""kanibako box: project lifecycle management (create, list, config, remap, move, convert, duplicate, archive, extract, purge, vault)."""
|
|
2
2
|
|
|
3
3
|
from kanibako.commands.box._duplicate import run_duplicate
|
|
4
|
-
from kanibako.commands.box.
|
|
4
|
+
from kanibako.commands.box._lifecycle import (
|
|
5
|
+
run_convert,
|
|
6
|
+
run_move,
|
|
7
|
+
run_remap,
|
|
8
|
+
)
|
|
5
9
|
from kanibako.commands.box._parser import (
|
|
6
10
|
_check_container_running,
|
|
7
11
|
_format_credential_age,
|
|
@@ -10,13 +14,12 @@ from kanibako.commands.box._parser import (
|
|
|
10
14
|
run_create,
|
|
11
15
|
run_info,
|
|
12
16
|
run_list,
|
|
13
|
-
run_move,
|
|
14
17
|
run_ps,
|
|
15
18
|
run_rm,
|
|
16
19
|
)
|
|
17
20
|
|
|
18
21
|
__all__ = [
|
|
19
22
|
"_check_container_running", "_format_credential_age",
|
|
20
|
-
"add_parser", "run_config", "run_create", "run_duplicate",
|
|
21
|
-
"run_info", "run_list", "
|
|
23
|
+
"add_parser", "run_config", "run_convert", "run_create", "run_duplicate",
|
|
24
|
+
"run_info", "run_list", "run_move", "run_ps", "run_remap", "run_rm",
|
|
22
25
|
]
|