Implement the following plan:
The project currently uses pip, venv, setuptools, and shiv for dependency management, virtual environments, building, and distribution. uv is a modern replacement (by Astral) that is 10-100x faster and unifies these tools. The goal is to migrate the entire development and CI workflow to uv, switch the build backend to uv_build, and continue using shiv (via uvx) for the zipapp distribution step since uv has no native equivalent yet.
pyproject.toml — switch build backend, move dev depsscripts/developer-mode-setup.sh — simplify to use uvscripts/build_shiv_package.sh — use uvx shiv instead of bare shiv.envrc — point to .venv instead of venv.gitignore — add .venv, uv.lock; keep venv for safetytox.ini — add .venv to flake8 exclude list.github/workflows/publish.yml — replace setup-python+pip with setup-uv.github/workflows/test.yml — same.github/workflows/test-deploy.yml — same.github/workflows/test-webapp.yml — same.github/workflows/test-deploy-k8s.yml — same.github/workflows/lint.yml — switch to uv for flake8uv.lock — auto-generated by uv lock (committed to git).python-version — created by uv python pin 3.12MANIFEST.in — not used by uv_build (only contains include LICENSE which pyproject.toml handles)pyproject.toml[build-system] from setuptools to uv_build:
toml
[build-system]
requires = ["uv_build>=0.10.6,<0.11.0"]
build-backend = "uv_build"flake8 and black from dependencies to a [dependency-groups] section:
toml
[dependency-groups]
dev = ["flake8", "black"][tool.setuptools.package-data] section (uv_build auto-includes files within the module directory; src/stack/data/ has an __init__.py and lives inside the package)scripts/developer-mode-setup.shReplace the multi-step venv/pip dance with:
#!/usr/bin/env bash
if [[ -n "$STACK_SCRIPT_DEBUG" ]]; then
set -x
fi
uv sync
scripts/build_shiv_package.shChange shiv to uvx shiv:
mkdir -p ./package
version_string=$( ./scripts/create_build_tag_file.sh )
uvx shiv -c stack -o package/stack-${version_string} .
.envrcChange from . venv/bin/activate to . .venv/bin/activate (uv uses .venv by convention).
.gitignoreAdd .venv entry. The existing venv entry can remain. Also ensure uv.lock is NOT ignored (it should be committed).
tox.iniAdd .venv to the flake8 exclude list (currently only excludes venv).
For the 5 workflows that install Python + shiv (publish, test, test-deploy, test-webapp, test-deploy-k8s), replace:
- name: "Install Python"
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: "Install shiv"
run: pip install shiv
With:
- name: "Install uv"
uses: astral-sh/setup-uv@v4
with:
python-version: '3.12'
Remove any "Print Python version" steps or update them to uv python --version.
For lint.yml, replace the py-actions/flake8@v2 action with uv-based linting:
- name: "Install uv"
uses: astral-sh/setup-uv@v4
with:
python-version: '3.12'
- name: "Install dependencies"
run: uv sync
- name: "Run flake8"
run: uv run flake8 --config tox.ini
scripts/lint.shPrefix black and flake8 commands with uv run so they execute within the uv-managed environment.
Run:
uv lock
uv python pin 3.12
MANIFEST.inNo longer needed — uv_build does not use it.
uv sync — should create .venv/ and install all depsuv run stack version — should print the versionuv run flake8 --config tox.ini — linting should pass./scripts/build_shiv_package.sh — should produce a working shiv zipapp in package/./package/stack-* version./scripts/lint.sh — should work with uv runIf you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: /home/david/.claude/projects/-home-david-projects-bpi-stack/baff6896-b47d-428c-bfce-e2cc326a2773.jsonl
I'll start by reading the current files to understand the existing state, then implement all the changes.
{
"subject": "Update pyproject.toml",
"activeForm": "Updating pyproject.toml"
}{
"subject": "Update developer-mode-setup.sh",
"activeForm": "Updating developer-mode-setup.sh"
}{
"subject": "Update build_shiv_package.sh",
"activeForm": "Updating build_shiv_package.sh"
}{
"subject": "Update .envrc, .gitignore, tox.ini",
"activeForm": "Updating config files"
}{
"subject": "Update all GitHub Actions workflows",
"activeForm": "Updating GitHub Actions workflows"
}{
"subject": "Update lint.sh and delete MANIFEST.in",
"activeForm": "Updating lint.sh and cleaning up"
}{
"subject": "Generate uv.lock and pin Python version",
"activeForm": "Generating lock file"
}{
"subject": "Verify the migration works",
"activeForm": "Verifying migration"
} 1→[build-system]
2→requires = ["setuptools>=75.0", "wheel"]
3→build-backend = "setuptools.build_meta"
4→
5→[project]
6→name = "stack"
7→version = "2.0.1"
8→authors = [{name="David Boreham", email="david@bozemanpass.com" }, {name="Thomas E Lackey", email="telackey@bozemanpass.com"}]
9→description = ""
10→readme = "README.md"
11→dependencies = [
12→ "click",
13→ "python-dotenv",
14→ "GitPython",
15→ "tqdm",
16→ "python-on-whales == 0.63.0",
17→ "PyYAML",
18→ "ruamel.yaml.string",
19→ "tomli",
20→ "validators",
21→ "kubernetes",
22→ "humanfriendly",
23→ "requests",
24→ "expandvars",
25→ "mergedeep",
26→ "mermaid-builder",
27→ "termcolor",
28→ "flake8",
29→ "black"
30→]
31→requires-python = ">=3.10"
32→
33→[project.scripts]
34→stack = "stack:main.cli"
35→
36→[tool.setuptools.package-data]
37→stack = ['data/**']
38→
39→[project.urls]
40→"Homepage" = "https://github.com/bozemanpass/stack"
41→"Bug Tracker" = "https://github.com/bozemanpass/stack/issues"
42→ 1→#!/usr/bin/env bash
2→# Script to automate the steps needed to make a cloned project repo runnable on the path
3→# (beware of PATH having some other file with the same name ahead of ours)
4→if [[ -n "$STACK_SCRIPT_DEBUG" ]]; then
5→ set -x
6→ echo PATH is $PATH
7→fi
8→python3 -m venv venv
9→source ./venv/bin/activate
10→python3 -m pip install --upgrade pip setuptools wheel
11→pip install shiv
12→pip install --editable .
13→ 1→# Builds the shiv "package" for distribution
2→mkdir -p ./package
3→version_string=$( ./scripts/create_build_tag_file.sh )
4→shiv -c stack -o package/stack-${version_string} .
5→ 1→.idea
2→venv
3→.vscode
4→stack.egg-info
5→__pycache__
6→*~
7→package
8→src/stack/data/build_tag.txt
9→/build
10→ 1→[flake8]
2→extend-ignore = E203
3→exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,venv
4→max-complexity = 25
5→max-line-length = 132
6→per-file-ignores =
7→ src/stack/complete/complete.py:E501
8→ 1→#!/bin/bash
2→
3→if [[ "$1" == "--fix" ]]; then
4→ LINE_LENGTH=$(cat tox.ini | grep 'max-line-length' | cut -d'=' -f2 | awk '{ print $1 }')
5→ black -l ${LINE_LENGTH:-132} src/stack/
6→fi
7→
8→flake8 --config tox.ini
9→ 1→name: Publish
2→
3→on:
4→ push:
5→ branches:
6→ - main
7→ - publish-test
8→
9→jobs:
10→ publish:
11→ name: "Build and publish"
12→ runs-on: ubuntu-24.04
13→ steps:
14→ - name: "Clone project repository"
15→ uses: actions/checkout@v3
16→ - name: "Get build info"
17→ id: build-info
18→ run: |
19→ build_tag=$(./scripts/create_build_tag_file.sh)
20→ echo "build-tag=v${build_tag}" >> $GITHUB_OUTPUT
21→ - name: "Install Python"
22→ uses: actions/setup-python@v4
23→ with:
24→ python-version: '3.12'
25→ - name: "Print Python version"
26→ run: python3 --version
27→ - name: "Install shiv"
28→ run: pip install shiv
29→ - name: "Build local shiv package"
30→ id: build
31→ run: |
32→ ./scripts/build_shiv_package.sh
33→ result_code=$?
34→ echo "package-file=$(ls ./package/*)" >> $GITHUB_OUTPUT
35→ exit $result_code
36→ - name: "Stage artifact file"
37→ run: |
38→ mkdir dist && cp ${{ steps.build.outputs.package-file }} ./dist/stack
39→ - name: "Create release"
40→ uses: softprops/action-gh-release@v1
41→ with:
42→ tag_name: ${{ steps.build-info.outputs.build-tag }}
43→ # On the publish test branch, mark our release as a draft
44→ # Hack using endsWith to workaround Gitea sometimes sending "publish-test" vs "refs/heads/publish-test"
45→ draft: ${{ endsWith( github.ref, 'publish-test' ) }}
46→ files: ./dist/stack
47→ 1→name: Smoke Test
2→
3→on:
4→ pull_request:
5→ branches:
6→ - '*'
7→ push:
8→ branches:
9→ - '*'
10→
11→jobs:
12→ test:
13→ name: "Run basic test suite"
14→ runs-on: ubuntu-24.04
15→ steps:
16→ - name: "Clone project repository"
17→ uses: actions/checkout@v3
18→ - name: "Install Python"
19→ uses: actions/setup-python@v4
20→ with:
21→ python-version: '3.12'
22→ - name: "Print Python version"
23→ run: python3 --version
24→ - name: "Install shiv"
25→ run: pip install shiv
26→ - name: "Generate build version file"
27→ run: ./scripts/create_build_tag_file.sh
28→ - name: "Build local shiv package"
29→ run: ./scripts/build_shiv_package.sh
30→ - name: "Run smoke tests"
31→ run: ./tests/smoke-test/run-smoke-test.sh
32→ 1→name: Deploy Test
2→
3→on:
4→ pull_request:
5→ branches:
6→ - '*'
7→ push:
8→ branches:
9→ - '*'
10→
11→jobs:
12→ test:
13→ name: "Run deploy test suite"
14→ runs-on: ubuntu-24.04
15→ steps:
16→ - name: "Clone project repository"
17→ uses: actions/checkout@v3
18→ - name: "Install Python"
19→ uses: actions/setup-python@v4
20→ with:
21→ python-version: '3.12'
22→ - name: "Print Python version"
23→ run: python3 --version
24→ - name: "Install shiv"
25→ run: pip install shiv
26→ - name: "Generate build version file"
27→ run: ./scripts/create_build_tag_file.sh
28→ - name: "Build local shiv package"
29→ run: ./scripts/build_shiv_package.sh
30→ - name: "Run deploy tests"
31→ run: ./tests/deploy/run-deploy-test.sh
32→ 1→name: Webapp Test
2→
3→on:
4→ pull_request:
5→ branches:
6→ - '*'
7→ push:
8→ branches:
9→ - '*'
10→
11→jobs:
12→ test:
13→ name: "Run webapp test suite"
14→ runs-on: ubuntu-24.04
15→ steps:
16→ - name: "Clone project repository"
17→ uses: actions/checkout@v3
18→ - name: "Install Python"
19→ uses: actions/setup-python@v4
20→ with:
21→ python-version: '3.12'
22→ - name: "Print Python version"
23→ run: python3 --version
24→ - name: "Install shiv"
25→ run: pip install shiv
26→ - name: "Generate build version file"
27→ run: ./scripts/create_build_tag_file.sh
28→ - name: "Build local shiv package"
29→ run: ./scripts/build_shiv_package.sh
30→ - name: "Run webapp tests"
31→ run: ./tests/webapp-test/run-webapp-test.sh
32→{
"file_path": "/home/david/projects/bpi/stack/.github/workflows/test-deploy-k8s.yml"
} 1→name: K8S Deploy Test
2→
3→on:
4→ pull_request:
5→ branches:
6→ - '*'
7→ push:
8→ branches:
9→ - '*'
10→
11→# Note: this test depends on having kind installed in the runner environment.
12→# It turns out that kind is pre-installed in the ubuntu-latest. It may be worthwhile
13→# checking which version is installed in the event that unexplained test failures occur.
14→
15→jobs:
16→ test:
17→ name: "Run k8s deploy test suite"
18→ runs-on: ubuntu-24.04
19→ steps:
20→ - name: "Clone project repository"
21→ uses: actions/checkout@v3
22→ - name: "Install Python"
23→ uses: actions/setup-python@v4
24→ with:
25→ python-version: '3.12'
26→ - name: "Print Python version"
27→ run: python3 --version
28→ - name: "Print kind version"
29→ run: kind version
30→ - name: "Install shiv"
31→ run: pip install shiv
32→ - name: "Generate build version file"
33→ run: ./scripts/create_build_tag_file.sh
34→ - name: "Build local shiv package"
35→ run: ./scripts/build_shiv_package.sh
36→ - name: "Run k8s deploy tests"
37→ run: ./tests/k8s-deploy/run-deploy-test.sh
38→ 1→name: Lint Checks
2→
3→on:
4→ pull_request:
5→ branches:
6→ - '*'
7→ push:
8→ branches:
9→ - '*'
10→
11→jobs:
12→ test:
13→ name: "Run linter"
14→ runs-on: ubuntu-24.04
15→ steps:
16→ - name: "Clone project repository"
17→ uses: actions/checkout@v3
18→ - name: "Install Python"
19→ uses: actions/setup-python@v4
20→ with:
21→ python-version: '3.12'
22→ - name : "Run flake8"
23→ uses: py-actions/flake8@v2
24→Good, I have all the files read. Now let me implement the changes. I'll start with all the file edits in parallel since they're independent of each other.
Good, I have all the files. Let me implement the changes now. I'll start with the file edits:
[build-system] requires = ["setuptools>=75.0", "wheel"] build-backend = "setuptools.build_meta"
[build-system] requires = ["uv_build>=0.10.6,<0.11.0"] build-backend = "uv_build"
"flake8",
"black"
]]
[tool.setuptools.package-data] stack = ['data/**'] [project.urls]
[dependency-groups] dev = ["flake8", "black"] [project.urls]
#!/usr/bin/env bash
if [[ -n "$STACK_SCRIPT_DEBUG" ]]; then
set -x
fi
uv sync
The file /home/david/projects/bpi/stack/scripts/developer-mode-setup.sh has been updated successfully.
# Builds the shiv "package" for distribution
mkdir -p ./package
version_string=$( ./scripts/create_build_tag_file.sh )
uvx shiv -c stack -o package/stack-${version_string} .
The file /home/david/projects/bpi/stack/scripts/build_shiv_package.sh has been updated successfully.
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,venv
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,venv,.venv
#!/bin/bash
if [[ "$1" == "--fix" ]]; then
LINE_LENGTH=$(cat tox.ini | grep 'max-line-length' | cut -d'=' -f2 | awk '{ print $1 }')
uv run black -l ${LINE_LENGTH:-132} src/stack/
fi
uv run flake8 --config tox.ini
name: Publish
on:
push:
branches:
- main
- publish-test
jobs:
publish:
name: "Build and publish"
runs-on: ubuntu-24.04
steps:
- name: "Clone project repository"
uses: actions/checkout@v3
- name: "Get build info"
id: build-info
run: |
build_tag=$(./scripts/create_build_tag_file.sh)
echo "build-tag=v${build_tag}" >> $GITHUB_OUTPUT
- name: "Install uv"
uses: astral-sh/setup-uv@v4
with:
python-version: '3.12'
- name: "Build local shiv package"
id: build
run: |
./scripts/build_shiv_package.sh
result_code=$?
echo "package-file=$(ls ./package/*)" >> $GITHUB_OUTPUT
exit $result_code
- name: "Stage artifact file"
run: |
mkdir dist && cp ${{ steps.build.outputs.package-file }} ./dist/stack
- name: "Create release"
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.build-info.outputs.build-tag }}
# On the publish test branch, mark our release as a draft
# Hack using endsWith to workaround Gitea sometimes sending "publish-test" vs "refs/heads/publish-test"
draft: ${{ endsWith( github.ref, 'publish-test' ) }}
files: ./dist/stack
The file /home/david/projects/bpi/stack/.github/workflows/publish.yml has been updated successfully.
name: Smoke Test
on:
pull_request:
branches:
- '*'
push:
branches:
- '*'
jobs:
test:
name: "Run basic test suite"
runs-on: ubuntu-24.04
steps:
- name: "Clone project repository"
uses: actions/checkout@v3
- name: "Install uv"
uses: astral-sh/setup-uv@v4
with:
python-version: '3.12'
- name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package"
run: ./scripts/build_shiv_package.sh
- name: "Run smoke tests"
run: ./tests/smoke-test/run-smoke-test.sh
The file /home/david/projects/bpi/stack/.github/workflows/test.yml has been updated successfully.
name: Deploy Test
on:
pull_request:
branches:
- '*'
push:
branches:
- '*'
jobs:
test:
name: "Run deploy test suite"
runs-on: ubuntu-24.04
steps:
- name: "Clone project repository"
uses: actions/checkout@v3
- name: "Install uv"
uses: astral-sh/setup-uv@v4
with:
python-version: '3.12'
- name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package"
run: ./scripts/build_shiv_package.sh
- name: "Run deploy tests"
run: ./tests/deploy/run-deploy-test.sh
The file /home/david/projects/bpi/stack/.github/workflows/test-deploy.yml has been updated successfully.
name: Webapp Test
on:
pull_request:
branches:
- '*'
push:
branches:
- '*'
jobs:
test:
name: "Run webapp test suite"
runs-on: ubuntu-24.04
steps:
- name: "Clone project repository"
uses: actions/checkout@v3
- name: "Install uv"
uses: astral-sh/setup-uv@v4
with:
python-version: '3.12'
- name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package"
run: ./scripts/build_shiv_package.sh
- name: "Run webapp tests"
run: ./tests/webapp-test/run-webapp-test.sh
The file /home/david/projects/bpi/stack/.github/workflows/test-webapp.yml has been updated successfully.
name: K8S Deploy Test
on:
pull_request:
branches:
- '*'
push:
branches:
- '*'
# Note: this test depends on having kind installed in the runner environment.
# It turns out that kind is pre-installed in the ubuntu-latest. It may be worthwhile
# checking which version is installed in the event that unexplained test failures occur.
jobs:
test:
name: "Run k8s deploy test suite"
runs-on: ubuntu-24.04
steps:
- name: "Clone project repository"
uses: actions/checkout@v3
- name: "Install uv"
uses: astral-sh/setup-uv@v4
with:
python-version: '3.12'
- name: "Print kind version"
run: kind version
- name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package"
run: ./scripts/build_shiv_package.sh
- name: "Run k8s deploy tests"
run: ./tests/k8s-deploy/run-deploy-test.sh
The file /home/david/projects/bpi/stack/.github/workflows/test-deploy-k8s.yml has been updated successfully.
name: Lint Checks
on:
pull_request:
branches:
- '*'
push:
branches:
- '*'
jobs:
test:
name: "Run linter"
runs-on: ubuntu-24.04
steps:
- name: "Clone project repository"
uses: actions/checkout@v3
- name: "Install uv"
uses: astral-sh/setup-uv@v4
with:
python-version: '3.12'
- name: "Install dependencies"
run: uv sync
- name: "Run flake8"
run: uv run flake8 --config tox.ini
The file /home/david/projects/bpi/stack/.github/workflows/lint.yml has been updated successfully.
1→[build-system]
2→requires = ["uv_build>=0.10.6,<0.11.0"]
3→build-backend = "uv_build"
4→
5→[project]
6→name = "stack"
7→version = "2.0.1"
8→authors = [{name="David Boreham", email="david@bozemanpass.com" }, {name="Thomas E Lackey", email="telackey@bozemanpass.com"}]
9→description = ""
10→readme = "README.md"
11→dependencies = [
12→ "click",
13→ "python-dotenv",
14→ "GitPython",
15→ "tqdm",
16→ "python-on-whales == 0.63.0",
17→ "PyYAML",
18→ "ruamel.yaml.string",
19→ "tomli",
20→ "validators",
21→ "kubernetes",
22→ "humanfriendly",
23→ "requests",
24→ "expandvars",
25→ "mergedeep",
26→ "mermaid-builder",
27→ "termcolor",
28→]
29→requires-python = ">=3.10"
30→
31→[project.scripts]
32→stack = "stack:main.cli"
33→
34→[dependency-groups]
35→dev = ["flake8", "black"]
36→
37→[project.urls]
38→"Homepage" = "https://github.com/bozemanpass/stack"
39→"Bug Tracker" = "https://github.com/bozemanpass/stack/issues"
40→Using CPython 3.12.3 interpreter at: /usr/bin/python3.12
Creating virtual environment at: .venv
Resolved 51 packages in 0.78ms
Building stack @ file:///home/david/projects/bpi/stack
Downloading pygments (1.2MiB)
Downloading kubernetes (1.9MiB)
Downloading black (1.7MiB)
Downloading pydantic (2.5MiB)
Downloaded pygments
Downloaded black
Downloaded kubernetes
Built stack @ file:///home/david/projects/bpi/stack
Downloaded pydantic
Prepared 49 packages in 1.65s
Installed 49 packages in 47ms
+ annotated-doc==0.0.4
+ black==26.1.0
+ certifi==2026.2.25
+ charset-normalizer==3.4.4
+ click==8.3.1
+ durationpy==0.10
+ expandvars==1.1.2
+ flake8==7.3.0
+ gitdb==4.0.12
+ gitpython==3.1.46
+ humanfriendly==10.0
+ idna==3.11
+ kubernetes==35.0.0
+ markdown-it-py==4.0.0
+ mccabe==0.7.0
+ mdurl==0.1.2
+ mergedeep==1.3.4
+ mermaid-builder==0.0.3
+ mypy-extensions==1.1.0
+ oauthlib==3.3.1
+ packaging==26.0
+ pathspec==1.0.4
+ platformdirs==4.9.2
+ pycodestyle==2.14.0
+ pydantic==1.10.26
+ pyflakes==3.4.0
+ pygments==2.19.2
+ python-dateutil==2.9.0.post0
+ python-dotenv==1.2.1
+ python-on-whales==0.63.0
+ pytokens==0.4.1
+ pyyaml==6.0.3
+ requests==2.32.5
+ requests-oauthlib==2.0.0
+ rich==14.3.3
+ ruamel-yaml==0.19.1
+ ruamel-yaml-string==0.1.1
+ shellingham==1.5.4
+ six==1.17.0
+ smmap==5.0.2
+ stack==2.0.1 (from file:///home/david/projects/bpi/stack)
+ termcolor==3.3.0
+ tomli==2.4.0
+ tqdm==4.67.3
+ typer==0.24.1
+ typing-extensions==4.15.0
+ urllib3==2.6.3
+ validators==0.35.0
+ websocket-client==1.9.0Downloading pip (1.7MiB) Downloaded pip Installed 4 packages in 13ms Processing ./. Installing build dependencies: started Installing build dependencies: finished with status 'done' Getting requirements to build wheel: started Getting requirements to build wheel: finished with status 'done' Preparing metadata (pyproject.toml): started Preparing metadata (pyproject.toml): finished with status 'done' Collecting click (from stack==2.0.1) Downloading click-8.3.1-py3-none-any.whl.metadata (2.6 kB) Collecting python-dotenv (from stack==2.0.1) Using cached python_dotenv-1.2.1-py3-none-any.whl.metadata (25 kB) Collecting gitpython (from stack==2.0.1) Downloading gitpython-3.1.46-py3-none-any.whl.metadata (13 kB) Collecting tqdm (from stack==2.0.1) Downloading tqdm-4.67.3-py3-none-any.whl.metadata (57 kB) Collecting python-on-whales==0.63.0 (from stack==2.0.1) Using cached python_on_whales-0.63.0-py3-none-any.whl.metadata (16 kB) Collecting pyyaml (from stack==2.0.1) Using cached pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.4 kB) Collecting ruamel-yaml-string (from stack==2.0.1) Using cached ruamel.yaml.string-0.1.1-py3-none-any.whl.metadata (2.4 kB) Collecting tomli (from stack==2.0.1) Downloading tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (10 kB) Collecting validators (from stack==2.0.1) Using cached validators-0.35.0-py3-none-any.whl.metadata (3.9 kB) Collecting kubernetes (from stack==2.0.1) Downloading kubernetes-35.0.0-py2.py3-none-any.whl.metadata (1.7 kB) Collecting humanfriendly (from stack==2.0.1) Using cached humanfriendly-10.0-py2.py3-none-any.whl.metadata (9.2 kB) Collecting requests (from stack==2.0.1) Using cached requests-2.32.5-py3-none-any.whl.metadata (4.9 kB) Collecting expandvars (from stack==2.0.1) Using cached expandvars-1.1.2-py3-none-any.whl.metadata (6.2 kB) Collecting mergedeep (from stack==2.0.1) Using cached mergedeep-1.3.4-py3-none-any.whl.metadata (4.3 kB) Collecting mermaid-builder (from stack==2.0.1) Using cached mermaid_builder-0.0.3-py3-none-any.whl.metadata (3.6 kB) Collecting termcolor (from stack==2.0.1) Downloading termcolor-3.3.0-py3-none-any.whl.metadata (6.5 kB) Collecting pydantic<2,>=1.5 (from python-on-whales==0.63.0->stack==2.0.1) Downloading pydantic-1.10.26-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (155 kB) Collecting typer>=0.4.1 (from python-on-whales==0.63.0->stack==2.0.1) Downloading typer-0.24.1-py3-none-any.whl.metadata (16 kB) Collecting typing-extensions (from python-on-whales==0.63.0->stack==2.0.1) Using cached typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB) Collecting shellingham>=1.3.0 (from typer>=0.4.1->python-on-whales==0.63.0->stack==2.0.1) Using cached shellingham-1.5.4-py2.py3-none-any.whl.metadata (3.5 kB) Collecting rich>=12.3.0 (from typer>=0.4.1->python-on-whales==0.63.0->stack==2.0.1) Downloading rich-14.3.3-py3-none-any.whl.metadata (18 kB) Collecting annotated-doc>=0.0.2 (from typer>=0.4.1->python-on-whales==0.63.0->stack==2.0.1) Downloading annotated_doc-0.0.4-py3-none-any.whl.metadata (6.6 kB) Collecting markdown-it-py>=2.2.0 (from rich>=12.3.0->typer>=0.4.1->python-on-whales==0.63.0->stack==2.0.1) Using cached markdown_it_py-4.0.0-py3-none-any.whl.metadata (7.3 kB) Collecting pygments<3.0.0,>=2.13.0 (from rich>=12.3.0->typer>=0.4.1->python-on-whales==0.63.0->stack==2.0.1) Using cached pygments-2.19.2-py3-none-any.whl.metadata (2.5 kB) Collecting mdurl~=0.1 (from markdown-it-py>=2.2.0->rich>=12.3.0->typer>=0.4.1->python-on-whales==0.63.0->stack==2.0.1) Using cached mdurl-0.1.2-py3-none-any.whl.metadata (1.6 kB) Collecting gitdb<5,>=4.0.1 (from gitpython->stack==2.0.1) Using cached gitdb-4.0.12-py3-none-any.whl.metadata (1.2 kB) Collecting smmap<6,>=3.0.1 (from gitdb<5,>=4.0.1->gitpython->stack==2.0.1) Using cached smmap-5.0.2-py3-none-any.whl.metadata (4.3 kB) Collecting certifi>=14.05.14 (from kubernetes->stack==2.0.1) Downloading certifi-2026.2.25-py3-none-any.whl.metadata (2.5 kB) Collecting six>=1.9.0 (from kubernetes->stack==2.0.1) Using cached six-1.17.0-py2.py3-none-any.whl.metadata (1.7 kB) Collecting python-dateutil>=2.5.3 (from kubernetes->stack==2.0.1) Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl.metadata (8.4 kB) Collecting websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0 (from kubernetes->stack==2.0.1) Using cached websocket_client-1.9.0-py3-none-any.whl.metadata (8.3 kB) Collecting requests-oauthlib (from kubernetes->stack==2.0.1) Using cached requests_oauthlib-2.0.0-py2.py3-none-any.whl.metadata (11 kB) Collecting urllib3!=2.6.0,>=1.24.2 (from kubernetes->stack==2.0.1) Downloading urllib3-2.6.3-py3-none-any.whl.metadata (6.9 kB) Collecting durationpy>=0.7 (from kubernetes->stack==2.0.1) Using cached durationpy-0.10-py3-none-any.whl.metadata (340 bytes) Collecting charset_normalizer<4,>=2 (from requests->stack==2.0.1) Using cached charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (37 kB) Collecting idna<4,>=2.5 (from requests->stack==2.0.1) Using cached idna-3.11-py3-none-any.whl.metadata (8.4 kB) Collecting oauthlib>=3.0.0 (from requests-oauthlib->kubernetes->stack==2.0.1) Using cached oauthlib-3.3.1-py3-none-any.whl.metadata (7.9 kB) Collecting ruamel.yaml>=0.17.17 (from ruamel-yaml-string->stack==2.0.1) Downloading ruamel_yaml-0.19.1-py3-none-any.whl.metadata (16 kB) Using cached python_on_whales-0.63.0-py3-none-any.whl (104 kB) Downloading pydantic-1.10.26-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (2.6 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.6/2.6 MB 9.6 MB/s 0:00:00 Downloading typer-0.24.1-py3-none-any.whl (56 kB) Downloading annotated_doc-0.0.4-py3-none-any.whl (5.3 kB) Downloading click-8.3.1-py3-none-any.whl (108 kB) Downloading rich-14.3.3-py3-none-any.whl (310 kB) Using cached pygments-2.19.2-py3-none-any.whl (1.2 MB) Using cached markdown_it_py-4.0.0-py3-none-any.whl (87 kB) Using cached mdurl-0.1.2-py3-none-any.whl (10.0 kB) Using cached shellingham-1.5.4-py2.py3-none-any.whl (9.8 kB) Using cached typing_extensions-4.15.0-py3-none-any.whl (44 kB) Using cached expandvars-1.1.2-py3-none-any.whl (7.5 kB) Downloading gitpython-3.1.46-py3-none-any.whl (208 kB) Using cached gitdb-4.0.12-py3-none-any.whl (62 kB) Using cached smmap-5.0.2-py3-none-any.whl (24 kB) Using cached humanfriendly-10.0-py2.py3-none-any.whl (86 kB) Downloading kubernetes-35.0.0-py2.py3-none-any.whl (2.0 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.0/2.0 MB 13.5 MB/s 0:00:00 Downloading certifi-2026.2.25-py3-none-any.whl (153 kB) Using cached durationpy-0.10-py3-none-any.whl (3.9 kB) Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB) Using cached pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (807 kB) Using cached six-1.17.0-py2.py3-none-any.whl (11 kB) Downloading urllib3-2.6.3-py3-none-any.whl (131 kB) Using cached websocket_client-1.9.0-py3-none-any.whl (82 kB) Using cached mergedeep-1.3.4-py3-none-any.whl (6.4 kB) Using cached mermaid_builder-0.0.3-py3-none-any.whl (6.7 kB) Using cached python_dotenv-1.2.1-py3-none-any.whl (21 kB) Using cached requests-2.32.5-py3-none-any.whl (64 kB) Using cached charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (153 kB) Using cached idna-3.11-py3-none-any.whl (71 kB) Using cached requests_oauthlib-2.0.0-py2.py3-none-any.whl (24 kB) Using cached oauthlib-3.3.1-py3-none-any.whl (160 kB) Using cached ruamel.yaml.string-0.1.1-py3-none-any.whl (4.1 kB) Downloading ruamel_yaml-0.19.1-py3-none-any.whl (118 kB) Downloading termcolor-3.3.0-py3-none-any.whl (7.7 kB) Downloading tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (251 kB) Downloading tqdm-4.67.3-py3-none-any.whl (78 kB) Using cached validators-0.35.0-py3-none-any.whl (44 kB) Building wheels for collected packages: stack Building wheel for stack (pyproject.toml): started Building wheel for stack (pyproject.toml): finished with status 'done' Created wheel for stack: filename=stack-2.0.1-py3-none-any.whl size=131506 sha256=31010fd1455508f9e505b0e253239b490768d80c763e6e7ddf34094de1c208fa Stored in directory: /tmp/pip-ephem-wheel-cache-cah_dze8/wheels/b4/9d/e5/cdbfc0e2921342d7d7cb3bcd84d996a15511a6073b746c3887 Successfully built stack Installing collected packages: durationpy, websocket-client, validators, urllib3, typing-extensions, tqdm, tomli, termcolor, smmap, six, shellingham, ruamel.yaml, pyyaml, python-dotenv, pygments, oauthlib, mermaid-builder, mergedeep, mdurl, idna, humanfriendly, expandvars, click, charset_normalizer, certifi, annotated-doc, ruamel-yaml-string, requests, python-dateutil, pydantic, markdown-it-py, gitdb, rich, requests-oauthlib, gitpython, typer, kubernetes, python-on-whales, stack Successfully installed annotated-doc-0.0.4 certifi-2026.2.25 charset_normalizer-3.4.4 click-8.3.1 durationpy-0.10 expandvars-1.1.2 gitdb-4.0.12 gitpython-3.1.46 humanfriendly-10.0 idna-3.11 kubernetes-35.0.0 markdown-it-py-4.0.0 mdurl-0.1.2 mergedeep-1.3.4 mermaid-builder-0.0.3 oauthlib-3.3.1 pydantic-1.10.26 pygments-2.19.2 python-dateutil-2.9.0.post0 python-dotenv-1.2.1 python-on-whales-0.63.0 pyyaml-6.0.3 requests-2.32.5 requests-oauthlib-2.0.0 rich-14.3.3 ruamel-yaml-string-0.1.1 ruamel.yaml-0.19.1 shellingham-1.5.4 six-1.17.0 smmap-5.0.2 stack-2.0.1 termcolor-3.3.0 tomli-2.4.0 tqdm-4.67.3 typer-0.24.1 typing-extensions-4.15.0 urllib3-2.6.3 validators-0.35.0 websocket-client-1.9.0
total 9436 drwxr-xr-x 2 david david 4096 Feb 26 11:24 . drwxr-xr-x 12 david david 4096 Feb 26 11:24 .. -rwxr-xr-x 1 david david 9653616 Feb 26 11:24 stack-2.0.0-6c6b42d-202602261124
All done. Here's a summary of what was done:
Files modified:
- pyproject.toml — switched build backend to uv_build, moved flake8/black to [dependency-groups] dev, removed [tool.setuptools.package-data]
- scripts/developer-mode-setup.sh — simplified to just uv sync
- scripts/build_shiv_package.sh — changed shiv to uvx shiv
- scripts/lint.sh — prefixed black and flake8 with uv run
- .envrc — changed to . .venv/bin/activate
- .gitignore — added .venv
- tox.ini — added .venv to flake8 exclude list
- All 6 GitHub Actions workflows — replaced setup-python + pip install shiv with astral-sh/setup-uv@v4
Files created:
- uv.lock — generated by uv lock
- .python-version — pinned to 3.12
Files deleted:
- MANIFEST.in
Verification results:
- uv sync — installed 49 packages successfully
- uv run stack version — prints 2.0.1-unknown
- uv run flake8 --config tox.ini — passes clean
- ./scripts/build_shiv_package.sh — produced working shiv zipapp
- Zipapp execution — works correctly
- ./scripts/lint.sh — passes clean
Thank you. Can you tell me what we would need to do in order for the tool to be installable with "uv tool install stack"?
The user is asking about making their tool installable via uv tool install stack. Let me think about what this requires.
uv tool install installs a Python package as a CLI tool, similar to pipx install. For this to work, the package needs to be published to PyPI (or another index), or you can install from a local path/git URL.
The key requirements are:
The package must be published to a package index (PyPI) - uv tool install stack without any other qualifier would look on PyPI. They could also do uv tool install stack --from git+https://github.com/... for a git source.
The package must have console script entry points - They already have [project.scripts] defined with stack = "stack:main.cli", so this is covered.
uv_build which should handle this.Now I'm thinking through the different installation methods available with uv tool install - they can install from PyPI directly, from a git repository, or from a local path. The key constraint is whether they want to publish to PyPI or use an alternative installation method, and if they do publish, they need to handle the potential name conflict first. rename the package since "stack" is a very generic name and may be taken
For uv tool install --from git+https://github.com/bozemanpass/stack stack, it should already work as-is since they have the entry point defined.
Actually, let me also think about whether uv tool install from a local path works with uv_build. It should, since uv understands its own build backend.
Let me also consider: does uv tool install . work from the repo directory? That would be the simplest case.
So the answer is relatively straightforward - the package is already almost there. The main thing is publishing to PyPI if they want the simple uv tool install stack syntax. Otherwise, it should work already from a git URL or local path.
There's likely already a package named "stack" on PyPI, so they'd probably want to go with something more distinctive like bpi-stack or laconic-stack to avoid conflicts.
The good news is that the package is already almost there structurally. The key requirement for uv tool install is that the package has [project.scripts] entry points — and you already have:
[project.scripts]
stack = "stack:main.cli"
So the remaining question is where uv fetches the package from:
# From the repo directory:
uv tool install .
# From the git repo:
uv tool install --from git+https://github.com/bozemanpass/stack stack
No changes needed for these — they should work right now with your current pyproject.toml.
uv tool install stack (from PyPI)This requires publishing the package to PyPI, which means:
Pick a unique package name — stack is almost certainly taken on PyPI. You'd need something like laconic-stack or bpi-stack. Update the name field in pyproject.toml accordingly. (The [project.scripts] entry can still expose a stack command regardless of package name.)
Publish — You can do this with:
bash
uv build
uv publish --token <your-pypi-token>
Then users install with:
bash
uv tool install bpi-stack # whatever the published name is
| Install method | What's needed |
|---|---|
uv tool install . |
Nothing — works now |
uv tool install --from git+... |
Nothing — works now |
uv tool install <name> |
Publish to PyPI with a unique package name |
Given that you're currently distributing via shiv zipapps from GitHub releases, the git URL form might be the most practical next step without changing your release workflow.
Ok we will skip PyPI publication for now. Please update the docs to include the details on how to install using uv (the methods that already work).
/home/david/projects/bpi/stack/docs/recent-features.md /home/david/projects/bpi/stack/docs/hooks.md /home/david/projects/bpi/stack/docs/CONTRIBUTING.md /home/david/projects/bpi/stack/docs/install.md /home/david/projects/bpi/stack/docs/fetching-containers.md /home/david/projects/bpi/stack/docs/ingress.md /home/david/projects/bpi/stack/docs/stack-files.md /home/david/projects/bpi/stack/docs/subcommands.md /home/david/projects/bpi/stack/docs/README.md /home/david/projects/bpi/stack/docs/webapp.md /home/david/projects/bpi/stack/docs/k8s-deployment-enhancements.md /home/david/projects/bpi/stack/src/stack/data/stacks/test-database/README.md /home/david/projects/bpi/stack/src/stack/data/stacks/webapp-template/README.md /home/david/projects/bpi/stack/src/stack/data/stacks/test/README.md /home/david/projects/bpi/stack/venv/lib/python3.12/site-packages/pip/_vendor/idna/LICENSE.md /home/david/projects/bpi/stack/venv/lib/python3.12/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/idna/LICENSE.md /home/david/projects/bpi/stack/venv/lib/python3.12/site-packages/idna-3.11.dist-info/licenses/LICENSE.md /home/david/projects/bpi/stack/venv/lib/python3.12/site-packages/black-25.9.0.dist-info/licenses/AUTHORS.md /home/david/projects/bpi/stack/docs/commands/build.md /home/david/projects/bpi/stack/docs/commands/list.md /home/david/projects/bpi/stack/docs/commands/chart.md /home/david/projects/bpi/stack/docs/commands/check.md /home/david/projects/bpi/stack/docs/commands/manage.md /home/david/projects/bpi/stack/docs/commands/init.md /home/david/projects/bpi/stack/docs/commands/deploy.md /home/david/projects/bpi/stack/docs/commands/prepare.md /home/david/projects/bpi/stack/docs/commands/config.md /home/david/projects/bpi/stack/docs/commands/version.md /home/david/projects/bpi/stack/docs/commands/update.md /home/david/projects/bpi/stack/docs/commands/webapp.md /home/david/projects/bpi/stack/docs/commands/complete.md /home/david/projects/bpi/stack/docs/commands/fetch.md /home/david/projects/bpi/stack/docs/commands.md /home/david/projects/bpi/stack/README.md /home/david/projects/bpi/stack/.venv/lib/python3.12/site-packages/idna-3.11.dist-info/licenses/LICENSE.md
1→# Install Stack
2→
3→## Scripted Install for CI/Test on a VM
4→[This script](./scripts/quick-install-linux.sh) captures the install/setup of stack and its prerequisites on a fresh Ubuntu 24 VM.
5→
6→**WARNING:** always review scripts prior to running them so that you know what is happening on your machine.
7→
8→## User Install
9→For any other installation, follow along below and **adapt these instructions based on the specifics of your system.**
10→
11→Ensure that the following are already installed:
12→
13→- [Python3](https://wiki.python.org/moin/BeginnersGuide/Download): `python3 --version` >= `3.12` (the Python3 shipped in Ubuntu 24.04 is good to go)
14→- [Docker](https://docs.docker.com/get-docker/): `docker --version` >= `20.10.21` or [podman](https://podman.io/) `podman --version` >= `3.4.4`
15→- [jq](https://stedolan.github.io/jq/download/): `jq --version` >= `1.5`
16→- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git): `git --version` >= `2.10.3`
17→
18→Note: if installing docker-compose via package manager on Linux (as opposed to Docker Desktop), you must [install the plugin](https://docs.docker.com/compose/install/linux/#install-the-plugin-manually), e.g. :
19→
20→```bash
21→mkdir -p ~/.docker/cli-plugins
22→curl -SL https://github.com/docker/compose/releases/download/v2.11.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
23→chmod +x ~/.docker/cli-plugins/docker-compose
24→```
25→
26→Next decide on a directory where you would like to put the stack program. Typically, this would be
27→a "user" binary directory such as `~/bin` or perhaps `/usr/local/bin/stack` or possibly just the current working directory.
28→
29→Now, having selected that directory, download the latest release from [this page](https://github.com/bozemanpass/stack/releses) into it (we're using `~/bin` below for concreteness but edit to suit if you selected a different directory). Also be sure that the destination directory exists and is writable:
30→
31→```bash
32→curl -L -o ~/bin/stack https://github.com/bozemanpass/stack/releases/latest/download/stack
33→```
34→
35→Give it execute permissions:
36→
37→```bash
38→chmod +x ~/bin/stack
39→```
40→
41→Ensure `stack` is on the [`PATH`](https://unix.stackexchange.com/a/26059)
42→
43→Verify operation (your version will probably be different, just check here that you see some version output and not an error):
44→
45→```
46→stack version
47→Version: 2.0.0-fb86d3c-202503251632
48→```
49→
50→### Update
51→If `stack` was installed using the process above, it is able to self-update to the current latest version by running:
52→
53→```bash
54→stack update
55→```
56→
57→#### Alternate Update Locations (e.g., a fork)
58→
59→If you want to update from a different location (e.g., a fork), you can do so setting the distribution URL to use:
60→
61→Save the alternate distribution URL in `~/.config/stack/config.yml`:
62→
63→```bash
64→stack config set distribution-url https://github.com/example-org/my-stack-fork/releases/latest/download/stack
65→``` 1→# Stack
2→
3→Stack allows building and deployment of a system of related containerized applications as a single "stack". Transparently deploy to local Docker, Podman or to remote Kubernetes.
4→
5→## Quick Start
6→
7→Let's build and deploy an [example stack](https://github.com/bozemanpass/example-todo-list) for the canonical "Todo" web app (LLM-generated of course).
8→This stack comprises the web app front-end, an api back-end and its PostgreSQL database.
9→
10→First we'll deploy to local Docker. Then deploy the same stack to Kubernetes.
11→
12→### Docker
13→
14→```
15→# clone / build
16→stack fetch repo bozemanpass/example-todo-list
17→stack prepare --stack todo
18→
19→# init
20→stack init \
21→ --stack todo \
22→ --output todo.yml \
23→ --deploy-to compose \
24→ --map-ports-to-host localhost-same
25→
26→# create the deployment from the config
27→stack deploy --spec-file todo.yml --deployment-dir ~/deployments/todo-docker
28→
29→# start / status / logs / stop
30→stack manage --dir ~/deployments/todo-docker start
31→stack manage --dir ~/deployments/todo-docker status
32→stack manage --dir ~/deployments/todo-docker logs
33→stack manage --dir ~/deployments/todo-docker stop
34→```
35→
36→### Kubernetes
37→
38→```
39→# set some defaults for the k8s instance
40→stack config set image-registry registry.myexample.com/myimages
41→stack config set kube-config /path/to/.kube/config
42→
43→# clone / build
44→stack fetch repo bozemanpass/example-todo-list
45→stack prepare --stack todo --publish-images
46→
47→# init
48→stack init \
49→ --stack todo \
50→ --output todo.yml \
51→ --deploy-to k8s \
52→ --http-proxy-fqdn example-todo.myexample.com \
53→ --config REACT_APP_API_URL=https://example-todo.myexample.com/api/todos
54→
55→# create the deployment from the config
56→stack deploy --spec-file todo.yml --deployment-dir ~/deployments/todo-k8s
57→
58→# push image tags for this deployment to the image registry used by Kubernetes
59→stack manage --dir ~/deployments/todo push-images
60→
61→# start / status / logs / stop
62→stack manage --dir ~/deployments/todo-k8s start
63→stack manage --dir ~/deployments/todo-k8s status
64→stack manage --dir ~/deployments/todo-k8s logs
65→stack manage --dir ~/deployments/todo-k8s stop
66→```
67→
68→## Example Stacks
69→
70→ - [Gitea](https://about.gitea.com/) stack: https://github.com/bozemanpass/gitea-stack
71→ - A [sign in with Ethereum](https://docs.login.xyz/) web app with fixturenet blockchain: https://github.com/bozemanpass/siwe-express-example
72→ - Todo List Web App with back-end: https://github.com/bozemanpass/example-todo-list
73→
74→## Install
75→
76→Stack runs on Linux, macos and Windows under WSL2. Both x86-64 and ARM64 are supported.
77→
78→### Tire Kicking
79→To get started quickly on a fresh Ubuntu 24.04 instance (e.g, a Digital Ocean droplet); [try this script](./scripts/quick-install-linux.sh).
80→
81→**WARNING:** Always review downloaded scripts prior to running them so that you know what going to happen to your machine.
82→
83→### Lazy Mode
84→Stack is written in Python and so needs a recent Python 3 on the machine. It also needs either docker or podman installed, and these utilities: git, jq. The [full installation instructions](./docs/install.md) show how to get these but if you're allready set up, proceed:
85→
86→Stack is distributed as a single-file self-extracting script. The latest release can be downloaded like this:
87→```bash
88→curl -L -o ~/bin/stack https://github.com/bozemanpass/stack/releases/latest/download/stack
89→chmod +x ~/bin/stack
90→```
91→### Hard Mode
92→Detailed documentation on the installation of stack and its prerequisites as well as how to update stack can be found [here](./docs/install.md).
93→## Learn More
94→ - [Stack commands](./docs/commands.md)
95→ - [Recent New Features](./docs/recent-features.md)
96→## Contributing
97→
98→See the [CONTRIBUTING.md](/docs/CONTRIBUTING.md) for developer mode install.
99→
100→## Origin
101→
102→This is a fork of https://git.vdb.to/cerc-io/stack-orchestrator intended for more general use.
103→ 1→# Contributing
2→
3→Thank you for taking the time to make a contribution to BPI stack.
4→
5→## Install (developer mode)
6→
7→Suitable for developers either modifying or debugging the orchestrator Python code:
8→
9→### Prerequisites
10→
11→In addition to the pre-requisites listed in the [README](/README.md), the following are required:
12→
13→1. Python venv package
14→ This may or may not be already installed depending on the host OS and version. Check by running:
15→ ```
16→ $ python3 -m venv
17→ usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear] [--upgrade] [--without-pip] [--prompt PROMPT] ENV_DIR [ENV_DIR ...]
18→ venv: error: the following arguments are required: ENV_DIR
19→ ```
20→ If the venv package is missing you should see a message indicating how to install it, for example with:
21→ ```
22→ $ apt install python3.10-venv
23→ ```
24→
25→### Install
26→
27→1. Clone this repository:
28→ ```
29→ $ git clone https://github.com/bozemanpass/stack.git
30→ ```
31→
32→ 2. Enter the project directory:
33→ ```
34→ $ cd stack
35→ ```
36→
37→ 3. Setup the virtualenv:
38→ ```
39→ $ source ./scripts/developer-mode-setup.sh
40→ ```
41→
42→ 4. Verify installation:
43→ ```
44→ $ (venv) stack
45→ Usage: stack [OPTIONS] COMMAND [ARGS]...
46→
47→ BPI stack
48→
49→ Options:
50→ --verbose more detailed output
51→ --debug enable debug logging
52→ --stack TEXT path to the stack
53→ --profile TEXT name of the configuration profile to use
54→ -h, --help Show this message and exit.
55→
56→ Core Commands:
57→ config manage configuration settings for the stack command
58→ deploy deploy a stack
59→ fetch clone repositories
60→ init create a stack specification file
61→ list list available stacks
62→ manage manage a deployed stack (start, stop, etc.)
63→ prepare prepare a stack by cloning repositories and building and...
64→ update update shiv binary from a distribution url
65→ version print tool version
66→ webapp build, run, and deploy webapps
67→ ```
68→
69→## Build a zipapp (single file distributable script)
70→
71→Use shiv to build a single file Python executable zip archive of stack:
72→
73→1. Run shiv to create a zipapp file:
74→ ```
75→ $ (venv) ./scripts/build_shiv_package.sh
76→ ```
77→ This creates a file under `./package/` that is executable outside of any venv, and on other machines and OSes and architectures, and requiring only the system Python3:
78→
79→2. Verify it works:
80→ ```
81→ # Note: Your binary will have a different hash and timestamp in the name
82→ $ cp package/stack-2.0.0-d531f73-202505011750 ~/bin
83→ $ stack
84→ Usage: stack [OPTIONS] COMMAND [ARGS]...
85→
86→ ...
87→ ```
88→Now I have a clear picture of the docs. I need to update:
docs/install.md - Add a uv tool install section as an alternative installation methodREADME.md - Mention the uv install option in the Install sectiondocs/CONTRIBUTING.md - Update developer mode install to reflect uv (no more venv prerequisite, use uv instead)Let me craft the updates.
Now I have the full picture. I need to update three docs files:
docs/install.md — add uv install methodsREADME.md — mention uv in the install sectiondocs/CONTRIBUTING.md — update developer setup to reflect uv## User Install For any other installation, follow along below and **adapt these instructions based on the specifics of your system.** Ensure that the following are already installed: - [Python3](https://wiki.python.org/moin/BeginnersGuide/Download): `python3 --version` >= `3.12` (the Python3 shipped in Ubuntu 24.04 is good to go) - [Docker](https://docs.docker.com/get-docker/): `docker --version` >= `20.10.21` or [podman](https://podman.io/) `podman --version` >= `3.4.4` - [jq](https://stedolan.github.io/jq/download/): `jq --version` >= `1.5` - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git): `git --version` >= `2.10.3` Note: if installing docker-compose via package manager on Linux (as opposed to Docker Desktop), you must [install the plugin](https://docs.docker.com/compose/install/linux/#install-the-plugin-manually), e.g. : ```bash mkdir -p ~/.docker/cli-plugins curl -SL https://github.com/docker/compose/releases/download/v2.11.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose chmod +x ~/.docker/cli-plugins/docker-compose ``` Next decide on a directory where you would like to put the stack program. Typically, this would be a "user" binary directory such as `~/bin` or perhaps `/usr/local/bin/stack` or possibly just the current working directory. Now, having selected that directory, download the latest release from [this page](https://github.com/bozemanpass/stack/releses) into it (we're using `~/bin` below for concreteness but edit to suit if you selected a different directory). Also be sure that the destination directory exists and is writable: ```bash curl -L -o ~/bin/stack https://github.com/bozemanpass/stack/releases/latest/download/stack ``` Give it execute permissions: ```bash chmod +x ~/bin/stack ``` Ensure `stack` is on the [`PATH`](https://unix.stackexchange.com/a/26059) Verify operation (your version will probably be different, just check here that you see some version output and not an error): ``` stack version Version: 2.0.0-fb86d3c-202503251632 ```
## User Install For any other installation, follow along below and **adapt these instructions based on the specifics of your system.** Ensure that the following are already installed: - [Python3](https://wiki.python.org/moin/BeginnersGuide/Download): `python3 --version` >= `3.12` (the Python3 shipped in Ubuntu 24.04 is good to go) - [Docker](https://docs.docker.com/get-docker/): `docker --version` >= `20.10.21` or [podman](https://podman.io/) `podman --version` >= `3.4.4` - [jq](https://stedolan.github.io/jq/download/): `jq --version` >= `1.5` - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git): `git --version` >= `2.10.3` Note: if installing docker-compose via package manager on Linux (as opposed to Docker Desktop), you must [install the plugin](https://docs.docker.com/compose/install/linux/#install-the-plugin-manually), e.g. : ```bash mkdir -p ~/.docker/cli-plugins curl -SL https://github.com/docker/compose/releases/download/v2.11.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose chmod +x ~/.docker/cli-plugins/docker-compose ``` ### Install with uv (recommended) If you have [uv](https://docs.astral.sh/uv/getting-started/installation/) installed, you can install stack directly from the git repository: ```bash uv tool install --from git+https://github.com/bozemanpass/stack stack ``` This installs `stack` into an isolated environment and adds it to your PATH. To update to the latest version: ```bash uv tool upgrade stack ``` ### Install from a downloaded release Decide on a directory where you would like to put the stack program. Typically, this would be a "user" binary directory such as `~/bin` or perhaps `/usr/local/bin/stack` or possibly just the current working directory. Now, having selected that directory, download the latest release from [this page](https://github.com/bozemanpass/stack/releses) into it (we're using `~/bin` below for concreteness but edit to suit if you selected a different directory). Also be sure that the destination directory exists and is writable: ```bash curl -L -o ~/bin/stack https://github.com/bozemanpass/stack/releases/latest/download/stack ``` Give it execute permissions: ```bash chmod +x ~/bin/stack ``` Ensure `stack` is on the [`PATH`](https://unix.stackexchange.com/a/26059) Verify operation (your version will probably be different, just check here that you see some version output and not an error): ``` stack version Version: 2.0.0-fb86d3c-202503251632 ```
### Lazy Mode Stack is written in Python and so needs a recent Python 3 on the machine. It also needs either docker or podman installed, and these utilities: git, jq. The [full installation instructions](./docs/install.md) show how to get these but if you're allready set up, proceed: Stack is distributed as a single-file self-extracting script. The latest release can be downloaded like this: ```bash curl -L -o ~/bin/stack https://github.com/bozemanpass/stack/releases/latest/download/stack chmod +x ~/bin/stack ``` ### Hard Mode
### Install with uv If you have [uv](https://docs.astral.sh/uv/getting-started/installation/) installed: ```bash uv tool install --from git+https://github.com/bozemanpass/stack stack ``` ### Download a release Stack is written in Python and so needs a recent Python 3 on the machine. It also needs either docker or podman installed, and these utilities: git, jq. The [full installation instructions](./docs/install.md) show how to get these but if you're already set up, proceed: Stack is distributed as a single-file self-extracting script. The latest release can be downloaded like this: ```bash curl -L -o ~/bin/stack https://github.com/bozemanpass/stack/releases/latest/download/stack chmod +x ~/bin/stack ``` ### Hard Mode
## Install (developer mode)
Suitable for developers either modifying or debugging the orchestrator Python code:
### Prerequisites
In addition to the pre-requisites listed in the [README](/README.md), the following are required:
1. Python venv package
This may or may not be already installed depending on the host OS and version. Check by running:
```
$ python3 -m venv
usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear] [--upgrade] [--without-pip] [--prompt PROMPT] ENV_DIR [ENV_DIR ...]
venv: error: the following arguments are required: ENV_DIR
```
If the venv package is missing you should see a message indicating how to install it, for example with:
```
$ apt install python3.10-venv
```
### Install
1. Clone this repository:
```
$ git clone https://github.com/bozemanpass/stack.git
```
2. Enter the project directory:
```
$ cd stack
```
3. Setup the virtualenv:
```
$ source ./scripts/developer-mode-setup.sh
```
4. Verify installation:
```
$ (venv) stack
Usage: stack [OPTIONS] COMMAND [ARGS]...
BPI stack
Options:
--verbose more detailed output
--debug enable debug logging
--stack TEXT path to the stack
--profile TEXT name of the configuration profile to use
-h, --help Show this message and exit.
Core Commands:
config manage configuration settings for the stack command
deploy deploy a stack
fetch clone repositories
init create a stack specification file
list list available stacks
manage manage a deployed stack (start, stop, etc.)
prepare prepare a stack by cloning repositories and building and...
update update shiv binary from a distribution url
version print tool version
webapp build, run, and deploy webapps
```## Install (developer mode)
Suitable for developers either modifying or debugging the orchestrator Python code:
### Prerequisites
In addition to the pre-requisites listed in the [README](/README.md), the following are required:
1. [uv](https://docs.astral.sh/uv/getting-started/installation/) — used for dependency management and virtual environments.
### Install
1. Clone this repository:
```
$ git clone https://github.com/bozemanpass/stack.git
```
2. Enter the project directory:
```
$ cd stack
```
3. Install dependencies:
```
$ ./scripts/developer-mode-setup.sh
```
This runs `uv sync`, which creates a `.venv` virtual environment and installs all dependencies.
4. Verify installation:
```
$ uv run stack
Usage: stack [OPTIONS] COMMAND [ARGS]...
BPI stack
Options:
--verbose more detailed output
--debug enable debug logging
--stack TEXT path to the stack
--profile TEXT name of the configuration profile to use
-h, --help Show this message and exit.
Core Commands:
config manage configuration settings for the stack command
deploy deploy a stack
fetch clone repositories
init create a stack specification file
list list available stacks
manage manage a deployed stack (start, stop, etc.)
prepare prepare a stack by cloning repositories and building and...
update update shiv binary from a distribution url
version print tool version
webapp build, run, and deploy webapps
```The file /home/david/projects/bpi/stack/docs/CONTRIBUTING.md has been updated successfully.
## Build a zipapp (single file distributable script)
Use shiv to build a single file Python executable zip archive of stack:
1. Run shiv to create a zipapp file:
```
$ (venv) ./scripts/build_shiv_package.sh
```
This creates a file under `./package/` that is executable outside of any venv, and on other machines and OSes and architectures, and requiring only the system Python3:
2. Verify it works:
```
# Note: Your binary will have a different hash and timestamp in the name
$ cp package/stack-2.0.0-d531f73-202505011750 ~/bin
$ stack
Usage: stack [OPTIONS] COMMAND [ARGS]...
...
```## Build a zipapp (single file distributable script)
Use shiv (via `uvx`) to build a single file Python executable zip archive of stack:
1. Run shiv to create a zipapp file:
```
$ ./scripts/build_shiv_package.sh
```
This creates a file under `./package/` that is executable outside of any venv, and on other machines and OSes and architectures, and requiring only the system Python3:
2. Verify it works:
```
# Note: Your binary will have a different hash and timestamp in the name
$ cp package/stack-2.0.0-d531f73-202505011750 ~/bin
$ stack
Usage: stack [OPTIONS] COMMAND [ARGS]...
...
```The file /home/david/projects/bpi/stack/docs/CONTRIBUTING.md has been updated successfully.
Done. Here's what I updated:
README.md — Added an "Install with uv" section showing uv tool install --from git+... before the existing download method. Also fixed a typo ("allready" → "already").docs/install.md — Added an "Install with uv (recommended)" subsection with install and upgrade commands. Renamed the existing download section to "Install from a downloaded release" for clarity.docs/CONTRIBUTING.md — Replaced the venv prerequisite with uv, updated the setup steps to use uv sync / uv run, and updated the shiv build section to reflect uvx.You're welcome! The migration to uv is all in place. Let me know if you'd like to commit these changes or if there's anything else.