diff --git a/.github/workflows/build-linux-packages.yaml b/.github/workflows/build-linux-packages.yaml new file mode 100644 index 0000000..74b1003 --- /dev/null +++ b/.github/workflows/build-linux-packages.yaml @@ -0,0 +1,140 @@ +name: build-linux-packages + +on: + release: + types: + - published + workflow_dispatch: + inputs: + version: + description: 'Package version (e.g., 3.2.1)' + required: false + default: '' + +jobs: + build-packages: + name: Build ${{ matrix.format }} for ${{ matrix.arch }} + runs-on: ubuntu-latest + strategy: + matrix: + arch: [amd64, arm64] + format: [deb, rpm] + + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.11' + + - name: Determine version + id: version + run: | + set -euo pipefail + + # Use workflow input if provided, otherwise extract from pyproject.toml + if [ -n "${{ github.event.inputs.version }}" ]; then + VERSION="${{ github.event.inputs.version }}" + elif [ "${GITHUB_EVENT_NAME}" = "release" ]; then + VERSION="${GITHUB_REF_NAME#v}" + else + VERSION=$(python -c "import tomllib; f = open('pyproject.toml', 'rb'); data = tomllib.load(f); f.close(); print(data['project']['version'])") + fi + + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "Package version: ${VERSION}" + + - name: Install nfpm + run: | + set -euo pipefail + echo "deb [trusted=yes] https://repo.goreleaser.com/apt/ /" | sudo tee /etc/apt/sources.list.d/goreleaser.list + sudo apt-get update + sudo apt-get install -y nfpm + + - name: Install Python build dependencies + run: | + python -m pip install --upgrade pip + pip install build virtualenv + + - name: Build Python wheel + run: | + python -m build --wheel + + - name: Create vendored virtual environment + run: | + set -euo pipefail + + # Create a fresh venv in dist/venv + mkdir -p dist + python -m virtualenv dist/venv + + # Install structkit and its dependencies into the venv + dist/venv/bin/pip install --upgrade pip + dist/venv/bin/pip install dist/*.whl + + # Clean up unnecessary files to reduce package size + find dist/venv -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true + find dist/venv -type f -name "*.pyc" -delete 2>/dev/null || true + find dist/venv -type f -name "*.pyo" -delete 2>/dev/null || true + rm -rf dist/venv/lib/python*/site-packages/pip* || true + rm -rf dist/venv/lib/python*/site-packages/setuptools* || true + + - name: Set architecture for nfpm + id: arch + run: | + if [ "${{ matrix.arch }}" = "amd64" ]; then + echo "nfpm_arch=amd64" >> $GITHUB_OUTPUT + elif [ "${{ matrix.arch }}" = "arm64" ]; then + echo "nfpm_arch=arm64" >> $GITHUB_OUTPUT + fi + + - name: Build ${{ matrix.format }} package + env: + VERSION: ${{ steps.version.outputs.version }} + ARCH: ${{ steps.arch.outputs.nfpm_arch }} + run: | + set -euo pipefail + + # Build the package using nfpm + if [ "${{ matrix.format }}" = "deb" ]; then + nfpm package \ + --packager deb \ + --config packaging/debian/nfpm.yaml \ + --target dist/ + elif [ "${{ matrix.format }}" = "rpm" ]; then + nfpm package \ + --packager rpm \ + --config packaging/rpm/nfpm.yaml \ + --target dist/ + fi + + # List generated packages + ls -lh dist/*.{deb,rpm} 2>/dev/null || true + + - name: Upload package artifacts + uses: actions/upload-artifact@v7 + with: + name: structkit-${{ steps.version.outputs.version }}-${{ matrix.arch }}-${{ matrix.format }} + path: | + dist/*.deb + dist/*.rpm + retention-days: 90 + + summary: + name: Build Summary + needs: build-packages + runs-on: ubuntu-latest + if: always() + + steps: + - name: Generate summary + run: | + echo "## Linux Package Build Complete" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Built packages for:" >> $GITHUB_STEP_SUMMARY + echo "- Debian/Ubuntu (.deb) - amd64, arm64" >> $GITHUB_STEP_SUMMARY + echo "- Fedora/RHEL (.rpm) - amd64, arm64" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Download artifacts from the Actions tab above." >> $GITHUB_STEP_SUMMARY diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 0000000..b5b766e --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,229 @@ +# Linux Distribution Packages + +This directory contains the configuration and tooling for building `.deb` (Debian/Ubuntu) and `.rpm` (Fedora/RHEL) packages for the structkit CLI. + +## Package Structure + +- `debian/` — Debian/Ubuntu package configuration +- `rpm/` — Fedora/RHEL package configuration +- `structkit-shim.sh` — Wrapper script installed at `/usr/bin/structkit` + +## Architecture + +The packages install structkit in a vendored Python virtual environment to handle dependencies that may not be available in distribution repositories: + +- **Vendored environment**: `/usr/lib/structkit/venv/` — Complete Python environment with all dependencies +- **CLI wrapper**: `/usr/bin/structkit` — Shim script that invokes the vendored structkit +- **Documentation**: `/usr/share/doc/structkit/` — README and LICENSE files + +This approach ensures: +- No conflicts with system Python packages +- Consistent dependency versions across distributions +- Simple installation without requiring pip or virtual environment management + +## Building Packages Locally + +### Prerequisites + +Install required tools: + +**Debian/Ubuntu:** +```bash +sudo apt-get update +sudo apt-get install -y python3 python3-pip python3-virtualenv +``` + +**Fedora/RHEL:** +```bash +sudo dnf install -y python3 python3-pip python3-virtualenv +``` + +**Install nfpm** (cross-platform package builder): +```bash +# Option 1: Using apt (Debian/Ubuntu) +echo "deb [trusted=yes] https://repo.goreleaser.com/apt/ /" | sudo tee /etc/apt/sources.list.d/goreleaser.list +sudo apt-get update +sudo apt-get install -y nfpm + +# Option 2: Using yum/dnf (Fedora/RHEL) +echo '[goreleaser] +name=GoReleaser +baseurl=https://repo.goreleaser.com/yum/ +enabled=1 +gpgcheck=0' | sudo tee /etc/yum.repos.d/goreleaser.repo +sudo dnf install -y nfpm + +# Option 3: Direct download +# See: https://nfpm.goreleaser.com/install/ +``` + +### Build Steps + +1. **Install Python build dependencies:** + ```bash + python3 -m pip install --upgrade pip build virtualenv + ``` + +2. **Build the Python wheel:** + ```bash + python3 -m build --wheel + ``` + +3. **Create vendored virtual environment:** + ```bash + mkdir -p dist + python3 -m virtualenv dist/venv + dist/venv/bin/pip install --upgrade pip + dist/venv/bin/pip install dist/*.whl + ``` + +4. **Build Debian package (amd64):** + ```bash + VERSION=$(python3 -c "import tomllib; f = open('pyproject.toml', 'rb'); data = tomllib.load(f); f.close(); print(data['project']['version'])") + ARCH=amd64 nfpm package --packager deb --config packaging/debian/nfpm.yaml --target dist/ + ``` + +5. **Build RPM package (amd64):** + ```bash + VERSION=$(python3 -c "import tomllib; f = open('pyproject.toml', 'rb'); data = tomllib.load(f); f.close(); print(data['project']['version'])") + ARCH=amd64 nfpm package --packager rpm --config packaging/rpm/nfpm.yaml --target dist/ + ``` + +The built packages will be in the `dist/` directory: +- `structkit_${VERSION}_amd64.deb` +- `structkit-${VERSION}-1.x86_64.rpm` + +## Installing Packages + +### Debian/Ubuntu + +```bash +# Download the .deb package, then: +sudo dpkg -i structkit_VERSION_amd64.deb + +# If there are dependency issues, resolve them with: +sudo apt-get install -f +``` + +### Fedora/RHEL + +```bash +# Download the .rpm package, then: +sudo dnf install ./structkit-VERSION-1.x86_64.rpm + +# Or using rpm directly: +sudo rpm -ivh structkit-VERSION-1.x86_64.rpm +``` + +### Verify Installation + +After installation, verify that structkit is working: + +```bash +structkit info +structkit --help +``` + +## CI/CD: GitHub Actions Workflow + +The `.github/workflows/build-linux-packages.yaml` workflow automatically builds packages when: + +1. **On release** — Triggered when a new GitHub release is published +2. **Manual dispatch** — Can be triggered manually from the Actions tab + +The workflow: +- Builds packages for both `amd64` and `arm64` architectures +- Creates both `.deb` and `.rpm` packages (4 total artifacts) +- Uploads packages as GitHub Actions artifacts (90-day retention) +- Can be triggered with a custom version via workflow dispatch + +### Triggering a Manual Build + +1. Go to **Actions** → **build-linux-packages** +2. Click **Run workflow** +3. Optionally specify a version (defaults to version in `pyproject.toml`) +4. Click **Run workflow** + +### Downloading Built Packages + +After the workflow completes: +1. Go to the workflow run page +2. Scroll to **Artifacts** section +3. Download the desired package(s): + - `structkit-VERSION-amd64-deb` + - `structkit-VERSION-arm64-deb` + - `structkit-VERSION-amd64-rpm` + - `structkit-VERSION-arm64-rpm` + +## Package Contents + +Each package includes: + +- `/usr/bin/structkit` — CLI entry point (wrapper script) +- `/usr/lib/structkit/venv/` — Vendored Python environment with: + - Python interpreter + - structkit and all dependencies + - Site packages (PyYAML, requests, openai, jinja2, etc.) +- `/usr/share/doc/structkit/README.md` — Project README +- `/usr/share/doc/structkit/LICENSE` — MIT License + +## Maintenance + +### Updating Package Metadata + +Package metadata is defined in: +- `packaging/debian/nfpm.yaml` — Debian package configuration +- `packaging/rpm/nfpm.yaml` — RPM package configuration + +Common fields to update: +- `maintainer` — Package maintainer contact +- `description` — Package description +- `depends` — Runtime dependencies +- `recommends` — Recommended packages + +### Version Synchronization + +The package version is automatically extracted from `pyproject.toml` during builds. Ensure the version in `pyproject.toml` is updated before building or releasing. + +## Troubleshooting + +### Package Installation Issues + +If you encounter dependency issues: + +**Debian/Ubuntu:** +```bash +sudo apt-get update +sudo apt-get install -f +``` + +**Fedora/RHEL:** +```bash +sudo dnf install ca-certificates +``` + +### CLI Not Working After Install + +Verify the installation: +```bash +which structkit # Should show /usr/bin/structkit +ls -la /usr/lib/structkit/venv/ # Should show Python environment +/usr/lib/structkit/venv/bin/structkit --help # Direct invocation +``` + +If the shim script fails, you can directly invoke: +```bash +/usr/lib/structkit/venv/bin/structkit [command] +``` + +## Future Enhancements + +This is Phase 1 of Linux distribution packaging. Not included in this PR: + +- Submission to Debian mentors or Fedora review +- PPA/COPR repository setup +- Official distribution repository inclusion +- Package signing/verification +- Marketplace/catalog integration + +These may be addressed in future phases based on community adoption and requirements. diff --git a/packaging/debian/nfpm.yaml b/packaging/debian/nfpm.yaml new file mode 100644 index 0000000..ebbae3d --- /dev/null +++ b/packaging/debian/nfpm.yaml @@ -0,0 +1,48 @@ +name: "structkit" +arch: "${ARCH}" +platform: "linux" +version: "${VERSION}" +section: "devel" +priority: "optional" +maintainer: "httpdss " +description: | + YAML-first project scaffolding tool with remote content fetching, + AI/MCP integration, and DevOps automation. + . + structkit is a powerful CLI tool for generating project structures + from YAML templates, with support for remote content fetching, + AI-powered generation, and Model Context Protocol integration. +vendor: "httpdss" +homepage: "https://structkit.app/" +license: "MIT" + +# Runtime dependencies (minimal - we vendor Python and dependencies) +depends: + - ca-certificates + +# Recommended packages +recommends: + - git + +contents: + # Vendored Python environment with all dependencies + - src: ./dist/venv + dst: /usr/lib/structkit/venv + + # CLI shim script + - src: ./packaging/structkit-shim.sh + dst: /usr/bin/structkit + file_info: + mode: 0755 + + # Documentation + - src: ./README.md + dst: /usr/share/doc/structkit/README.md + file_info: + mode: 0644 + + - src: ./LICENSE + dst: /usr/share/doc/structkit/LICENSE + file_info: + mode: 0644 + packager: deb diff --git a/packaging/rpm/nfpm.yaml b/packaging/rpm/nfpm.yaml new file mode 100644 index 0000000..a4276e2 --- /dev/null +++ b/packaging/rpm/nfpm.yaml @@ -0,0 +1,46 @@ +name: "structkit" +arch: "${ARCH}" +platform: "linux" +version: "${VERSION}" +release: "1" +maintainer: "httpdss " +description: | + YAML-first project scaffolding tool with remote content fetching, + AI/MCP integration, and DevOps automation. + + structkit is a powerful CLI tool for generating project structures + from YAML templates, with support for remote content fetching, + AI-powered generation, and Model Context Protocol integration. +vendor: "httpdss" +homepage: "https://structkit.app/" +license: "MIT" + +# Runtime dependencies (minimal - we vendor Python and dependencies) +depends: + - ca-certificates + +# Recommended packages +recommends: + - git + +contents: + # Vendored Python environment with all dependencies + - src: ./dist/venv + dst: /usr/lib/structkit/venv + + # CLI shim script + - src: ./packaging/structkit-shim.sh + dst: /usr/bin/structkit + file_info: + mode: 0755 + + # Documentation + - src: ./README.md + dst: /usr/share/doc/structkit/README.md + file_info: + mode: 0644 + + - src: ./LICENSE + dst: /usr/share/doc/structkit/LICENSE + file_info: + mode: 0644 diff --git a/packaging/structkit-shim.sh b/packaging/structkit-shim.sh new file mode 100644 index 0000000..073d858 --- /dev/null +++ b/packaging/structkit-shim.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Shim script for structkit CLI +# Executes structkit from the vendored Python environment + +set -e + +STRUCTKIT_HOME="/usr/lib/structkit" +PYTHON_BIN="${STRUCTKIT_HOME}/venv/bin/python" +STRUCTKIT_MODULE="${STRUCTKIT_HOME}/venv/bin/structkit" + +if [ ! -f "${PYTHON_BIN}" ]; then + echo "Error: structkit Python environment not found at ${STRUCTKIT_HOME}" >&2 + echo "Please reinstall structkit." >&2 + exit 1 +fi + +exec "${STRUCTKIT_MODULE}" "$@"