Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 99 additions & 5 deletions .devcontainer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,104 @@ This directory contains the configuration for [GitHub Codespaces](https://github
## Contents

- `devcontainer.json`: The primary configuration file defining the development environment.
- `bootstrap.sh`: A script that runs after the container is created to initialize the environment (installing dependencies, setting up tools).
- `bootstrap.sh`: Post-create script that initializes the environment (installing dependencies, setting up tools).

## Features
## Python Version

- **Python Environment**: Pre-configured with Python 3.12.
- **Tools**: Includes `uv` for fast package management and `make` for project tasks.
- **Extensions**: Recommended VS Code extensions for Python development, including Ruff and Marimo.
The Python version is controlled by the `.python-version` file in the repository root (single source of truth).

**How it works:**
1. The devcontainer uses a base Python image (3.12)
2. `bootstrap.sh` reads `.python-version` and exports `PYTHON_VERSION`
3. `make install` uses UV to create a venv with the exact Python version specified
4. UV automatically downloads the correct Python version if needed

No manual setup required - UV handles Python version management!

## What's Configured

The `.devcontainer` setup provides:

- 🐍 **Python** runtime environment
- 🔧 **UV Package Manager** - Fast Python package installer and resolver
- ⚡ **Makefile** - For running project workflows
- 🧪 **Pre-commit Hooks** - Automated code quality checks
- 📊 **Marimo Integration** - Interactive notebook support with VS Code extension
- 🔍 **Python Development Tools** - Pylance, Python extension, and optimized settings
- 🚀 **Port Forwarding** - Port 8080 for development servers
- 🔐 **SSH Agent Forwarding** - Full Git functionality with your host SSH keys

## Usage

### In VS Code

1. Install the "Dev Containers" extension
2. Open the repository in VS Code
3. Click "Reopen in Container" when prompted
4. The environment will automatically set up with all dependencies

### In GitHub Codespaces

1. Navigate to the repository on GitHub
2. Click the green "Code" button
3. Select "Codespaces" tab
4. Click "Create codespace on main" (or your branch)
5. Your development environment will be ready in minutes

The dev container automatically runs the initialization script that:

- Installs UV package manager
- Configures the Python virtual environment
- Installs project dependencies
- Sets up pre-commit hooks

## Publishing Devcontainer Images

The repository includes workflows for building and publishing devcontainer images:

### CI Validation

The **DEVCONTAINER** workflow automatically validates that your devcontainer builds successfully:
- Triggers on changes to `.devcontainer/**` files or the workflow itself
- Builds the image without publishing (`push: never`)
- Works on pushes to any branch and pull requests
- Gracefully skips if no `.devcontainer/devcontainer.json` exists

## VS Code Dev Container SSH Agent Forwarding

Dev containers launched locally via VS code
are configured with SSH agent forwarding
to enable seamless Git operations:

- **Mounts your SSH directory** - Your `~/.ssh` folder is mounted into the container
- **Forwards SSH agent** - Your host's SSH agent is available inside the container
- **Enables Git operations** - Push, pull, and clone using your existing SSH keys
- **Works transparently** - No additional setup required in VS Code dev containers

## Troubleshooting

Common issues and solutions when using this configuration template.

---

### SSH authentication fails on macOS when using devcontainer

**Symptom**: When building or using the devcontainer on macOS, Git operations (pull, push, clone) fail with SSH authentication errors, even though your SSH keys work fine on the host.

**Cause**: macOS SSH config often includes `UseKeychain yes`, which is a macOS-specific directive. When the devcontainer mounts your `~/.ssh` directory, other platforms (Linux containers) don't recognize this directive and fail to parse the SSH config.

**Solution**: Add `IgnoreUnknown UseKeychain` to the top of your `~/.ssh/config` file on your Mac:

```ssh-config
# At the top of ~/.ssh/config
IgnoreUnknown UseKeychain

Host *
AddKeysToAgent yes
UseKeychain yes
IdentityFile ~/.ssh/id_rsa
```

This tells SSH clients on non-macOS platforms to ignore the `UseKeychain` directive instead of failing.

**Reference**: [Stack Overflow solution](https://stackoverflow.com/questions/75613632/trying-to-ssh-to-my-server-from-the-terminal-ends-with-error-line-x-bad-configu/75616369#75616369)
20 changes: 13 additions & 7 deletions .devcontainer/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@
set -euo pipefail
IFS=$'\n\t'

# Use UV_INSTALL_DIR from environment or default to local bin
# Read Python version from .python-version (single source of truth)
if [ -f ".python-version" ]; then
export PYTHON_VERSION=$(cat .python-version | tr -d '[:space:]')
echo "Using Python version from .python-version: $PYTHON_VERSION"
fi

# Use INSTALL_DIR from environment or default to local bin
# In devcontainer, this is set to /home/vscode/.local/bin to avoid conflict with host
export UV_INSTALL_DIR="${UV_INSTALL_DIR:-./bin}"
export UV_BIN="${UV_INSTALL_DIR}/uv"
export UVX_BIN="${UV_INSTALL_DIR}/uvx"
export INSTALL_DIR="${INSTALL_DIR:-./bin}"
export UV_BIN="${INSTALL_DIR}/uv"
export UVX_BIN="${INSTALL_DIR}/uvx"

# Only remove existing binaries if we are installing to the default ./bin location
# and we want to force re-installation (e.g. if OS changed)
if [ "$UV_INSTALL_DIR" = "./bin" ]; then
if [ "$INSTALL_DIR" = "./bin" ]; then
rm -f "$UV_BIN" "$UVX_BIN"
fi

Expand All @@ -21,10 +27,10 @@ export UV_LINK_MODE=copy
# Make UV environment variables persistent for all sessions
echo "export UV_LINK_MODE=copy" >> ~/.bashrc
echo "export UV_VENV_CLEAR=1" >> ~/.bashrc
echo "export PATH=\"$UV_INSTALL_DIR:\$PATH\"" >> ~/.bashrc
echo "export PATH=\"$INSTALL_DIR:\$PATH\"" >> ~/.bashrc

# Add to current PATH so subsequent commands can find uv
export PATH="$UV_INSTALL_DIR:$PATH"
export PATH="$INSTALL_DIR:$PATH"

make install

Expand Down
8 changes: 5 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
{
"name": "Python 3.14 (Rhiza)",
"name": "Python Dev (Rhiza)",
"image": "mcr.microsoft.com/devcontainers/python:3.14",
"hostRequirements": {
"cpus": 4
},
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {},
"ghcr.io/devcontainers/features/git:1": {}
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/copilot-cli:1": {}
},
"mounts": [
"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached"
],
"containerEnv": {
"SSH_AUTH_SOCK": "${localEnv:SSH_AUTH_SOCK}",
"UV_INSTALL_DIR": "/home/vscode/.local/bin"
"INSTALL_DIR": "/home/vscode/.local/bin"
},
"forwardPorts": [8080],
"customizations": {
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/rhiza_book.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.9.21"
version: "0.9.24"

- name: "Sync the virtual environment for ${{ github.repository }}"
shell: bash
Expand All @@ -52,9 +52,6 @@ jobs:
# will just use .python-version?
uv sync --all-extras --all-groups --frozen

- name: Install dev dependencies
run: bash .rhiza/scripts/install-dev-deps.sh

- name: "Make the book"
run: |
make book
Expand Down
18 changes: 5 additions & 13 deletions .github/workflows/rhiza_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.9.21"
version: "0.9.24"

- id: versions
run: |
Expand Down Expand Up @@ -58,17 +58,9 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.9.21"

- name: "Sync the virtual environment for ${{ github.repository }}"
shell: bash
run: |
export UV_EXTRA_INDEX_URL="${{ secrets.uv-extra-index-url }}"
uv venv --python ${{ matrix.python-version }}
uv sync --all-extras --all-groups --frozen

- name: Install dev dependencies
run: bash .rhiza/scripts/install-dev-deps.sh
version: "0.9.24"
python-version: ${{ matrix.python-version }}

- name: Run tests
run: uv run pytest tests
run: |
make test
2 changes: 1 addition & 1 deletion .github/workflows/rhiza_deptry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
name: Check dependencies with deptry
runs-on: ubuntu-latest
container:
image: ghcr.io/astral-sh/uv:0.9.21-python3.12-trixie
image: ghcr.io/astral-sh/uv:0.9.24-python3.12-trixie

steps:
- uses: actions/checkout@v6
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/rhiza_docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ jobs:
if: ${{ steps.check_dockerfile.outputs.docker_present == 'true' }}
uses: docker/setup-buildx-action@v3

- name: Read Python version from .python-version
if: ${{ steps.check_dockerfile.outputs.docker_present == 'true' }}
id: python_version
run: |
if [ -f .python-version ]; then
PYTHON_VERSION=$(cat .python-version | tr -d '[:space:]')
echo "version=$PYTHON_VERSION" >> $GITHUB_OUTPUT
echo "Using Python version: $PYTHON_VERSION"
else
echo "version=3.12" >> $GITHUB_OUTPUT
echo "::warning::.python-version not found, using default 3.12"
fi

- name: Build container image with Docker Buildx (validation only)
if: ${{ steps.check_dockerfile.outputs.docker_present == 'true' }}
run: |
Expand All @@ -59,6 +72,7 @@ jobs:
REPO_NAME="${REPO_NAME//[._]/}"
docker buildx build \
--file docker/Dockerfile \
--build-arg PYTHON_VERSION=${{ steps.python_version.outputs.version }} \
--tag "${REPO_NAME}:ci" \
--load \
.
2 changes: 1 addition & 1 deletion .github/workflows/rhiza_marimo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.9.21"
version: "0.9.24"

# Execute the notebook with the appropriate runner based on its content
- name: Run notebook
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/rhiza_pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,7 @@ jobs:
steps:
- uses: actions/checkout@v6

- uses: pre-commit/action@v3.0.1

# Run pre-commit
- name: Run pre-commit
run: |
make fmt
4 changes: 2 additions & 2 deletions .github/workflows/rhiza_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.9.21"
version: "0.9.24"

- name: "Sync the virtual environment for ${{ github.repository }}"
shell: bash
Expand Down Expand Up @@ -322,7 +322,7 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.9.21"
version: "0.9.24"

- name: "Sync the virtual environment for ${{ github.repository }}"
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rhiza_validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
# don't run this in rhiza itself. Rhiza has no template.yml file.
if: ${{ github.repository != 'jebel-quant/rhiza' }}
container:
image: ghcr.io/astral-sh/uv:0.9.21-python3.12-trixie
image: ghcr.io/astral-sh/uv:0.9.24-python3.12-trixie

steps:
- name: Checkout repository
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

### Don't expose API keys, etc.
.env
!.rhiza/.env

__marimo__

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repos:
- id: check-yaml

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.14.10'
rev: 'v0.14.11'
hooks:
- id: ruff
args: [ --fix, --exit-non-zero-on-fix, --unsafe-fixes ]
Expand Down
7 changes: 6 additions & 1 deletion .rhiza/.cfg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ sign_tags = false
tag_name = "v{new_version}"
tag_message = "Bump version: {current_version} → {new_version}"
allow_dirty = false
commit = false
commit = true
message = "Chore: bump version {current_version} → {new_version}"
commit_args = ""

Expand All @@ -28,4 +28,9 @@ values = [
[[tool.bumpversion.files]]
filename = "pyproject.toml"
search = 'version = "{current_version}"'
replace = 'version = "{new_version}"'

[[tool.bumpversion.files]]
filename = "uv.lock"
search = 'version = "{current_version}"'
replace = 'version = "{new_version}"'
14 changes: 14 additions & 0 deletions .rhiza/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
MARIMO_FOLDER=book/marimo
SOURCE_FOLDER=src
SCRIPTS_FOLDER=.rhiza/scripts
CUSTOM_SCRIPTS_FOLDER=.rhiza/customisations/scripts

# Book-specific variables
BOOK_TITLE=Project Documentation
BOOK_SUBTITLE=Generated by minibook
PDOC_TEMPLATE_DIR=book/pdoc-templates
BOOK_TEMPLATE=book/minibook-templates/custom.html.jinja2
DOCFORMAT=google

# Agentic-specific variables
DEFAULT_AI_MODEL=gpt-4.1
38 changes: 38 additions & 0 deletions .rhiza/Makefile.rhiza
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## Makefile.rhiza - rhiza actions
# This file is included by the main Makefile

# RHIZA_LOGO definition
define RHIZA_LOGO
____ _ _
| _ \| |__ (_)______ _
| |_) | '_ \| |_ / _\`|
| _ <| | | | |/ / (_| |
|_| \_\_| |_|_/___\__,_|

endef
export RHIZA_LOGO

# Declare phony targets
.PHONY: print-logo sync validate

##@ Rhiza Workflows

print-logo:
@printf "${BLUE}$$RHIZA_LOGO${RESET}\n"


sync: ## sync with template repository as defined in .rhiza/template.yml
@if git remote get-url origin 2>/dev/null | grep -iqE 'jebel-quant/rhiza(\.git)?$$'; then \
printf "${BLUE}[INFO] Skipping sync in rhiza repository (no template.yml by design)${RESET}\n"; \
else \
$(MAKE) install-uv; \
${UVX_BIN} "rhiza>=0.7.1" materialize --force .; \
fi

validate: ## validate project structure against template repository as defined in .rhiza/template.yml
@if git remote get-url origin 2>/dev/null | grep -iqE 'jebel-quant/rhiza(\.git)?$$'; then \
printf "${BLUE}[INFO] Skipping validate in rhiza repository (no template.yml by design)${RESET}\n"; \
else \
$(MAKE) install-uv; \
${UVX_BIN} "rhiza>=0.7.1" validate .; \
fi
Loading