CI/CD Standardization Guide
CI/CD Standardization Guide for z-tools Projects
Overview
This guide documents unified CI/CD patterns for GitHub Actions workflows across the z-tools ecosystem. The goal is to standardize:
- Continuous Integration (CI) - Testing, linting, security scanning on every commit
- Release Automation - Automated packaging and publishing on version tags
- Multi-Architecture Support - Building for both amd64 and ARM64
- Security Scanning - Integrated security checks in CI/CD pipeline
Project Matrix
| Project | Type | Package Manager | CI Workflow | Release Workflow | Architecture |
|---|---|---|---|---|---|
| z-edit | Python | uv + CMake | ci.yml | release.yml | multiarch (any) |
| z-open | Python | uv + CMake | build-and-release.yml | build-and-release.yml | multiarch (any) |
| z-kitty-launcher | Rust | Cargo | ci.yml | release.yml | Pending |
| z-rclone-mount-applete | Rust | Cargo | ci.yml | release.yml | Pending |
Standard CI Workflow (ci.yml)
Purpose
Runs on every push and pull request to ensure code quality and compatibility.
Trigger
1
2
3
4
5
6
7
on:
push:
branches:
- master
- main
- develop
pull_request:
Standard Jobs
1. Test Job (All Projects)
Python Projects:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: $
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Run tests
run: pytest -v --cov=. --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
Rust Projects:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
test:
runs-on: ubuntu-latest
strategy:
matrix:
rust-version: [stable, beta]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: $
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
- name: Run tests
run: cargo test --verbose
- name: Run doctests
run: cargo test --doc
2. Linting Job
Python Projects - Ruff:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: 'pip'
- name: Install ruff
run: pip install ruff
- name: Lint with ruff
run: ruff check .
- name: Format check with ruff
run: ruff format . --check
Rust Projects - Clippy:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
components: clippy
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
- name: Run Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Check formatting
run: cargo fmt --all -- --check
3. Security Scanning Job
Python Projects - Bandit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
security:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install bandit
run: pip install bandit
- name: Run security scan
run: bandit -r . -f json -o bandit-report.json || true
- name: Check for critical issues
run: |
bandit -r . -ll || {
echo "Security issues found!"
exit 1
}
Rust Projects - Cargo-Audit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
security:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Run security audit
run: cargo audit --deny warnings
- name: Check for vulnerabilities
continue-on-error: true
run: cargo audit
Standard Release Workflow
Purpose
Runs on version tag pushes (e.g., v1.2.3) to build and publish release artifacts.
Trigger
1
2
3
4
on:
push:
tags:
- 'v*'
Python Projects - Standard Release Workflow
Location: .github/workflows/release.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: $
steps:
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Validate version format
run: |
VERSION=$
if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "Invalid version format: $VERSION"
exit 1
fi
echo "Version validated: $VERSION"
build-source-package:
needs: prepare
runs-on: ubuntu-latest
outputs:
source-archive: $
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build source archive
id: build
env:
VERSION: $
run: |
git archive --format=tar.gz \
--prefix="PROJECT-${VERSION}-source/" \
-o "PROJECT-${VERSION}-source.tar.gz" \
HEAD
echo "source-archive=PROJECT-${VERSION}-source.tar.gz" >> "$GITHUB_OUTPUT"
- name: Upload source package as artifact
uses: actions/upload-artifact@v4
with:
name: source-package
path: PROJECT-*.tar.gz
retention-days: 7
build-debian-multiarch:
needs: prepare
runs-on: ubuntu-latest
outputs:
deb-package: $
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install build dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y cmake python3-magic dpkg-dev debhelper dh-python
- name: Configure CMake
run: cmake -B build -DCMAKE_INSTALL_PREFIX=/opt/PROJECT -DPROJECT_BUILD_WHEEL=OFF
- name: Build Debian package (multiarch)
id: build
env:
VERSION: $
run: |
cmake --build build --target deb
DEB_FILE=$(ls build/PROJECT-*.deb | head -1)
if [ -z "$DEB_FILE" ]; then
echo "Debian package creation failed"
exit 1
fi
# Verify Architecture: any
ARCH=$(dpkg -I "$DEB_FILE" | grep "Architecture:" | awk '{print $2}')
if [ "$ARCH" != "any" ]; then
echo "ERROR: Expected Architecture: any, got $ARCH"
exit 1
fi
# Rename to indicate multiarch
DEB_RENAMED="${DEB_FILE%.*}-multiarch.deb"
mv "$DEB_FILE" "$DEB_RENAMED"
echo "deb-package=$(basename $DEB_RENAMED)" >> "$GITHUB_OUTPUT"
dpkg -I "$DEB_RENAMED"
- name: Upload Debian package as artifact
uses: actions/upload-artifact@v4
with:
name: debian-multiarch
path: build/PROJECT-*-multiarch.deb
retention-days: 7
create-release:
needs: [prepare, build-source-package, build-debian-multiarch]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: ./release-assets/
- name: Verify release assets
run: |
echo "Release assets:"
find release-assets -type f -exec ls -lh {} \;
- name: Generate checksums
run: |
cd release-assets
find . -type f | sort | xargs sha256sum > SHA256SUMS
cat SHA256SUMS
cd ..
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: "PROJECT $"
generate_release_notes: true
draft: false
prerelease: false
files: release-assets/**/*
env:
GITHUB_TOKEN: $
Rust Projects - Standard Release Workflow
Location: .github/workflows/release.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: $
steps:
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Validate version format
run: |
VERSION=$
if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "Invalid version format: $VERSION"
exit 1
fi
build-source-archive:
needs: prepare
runs-on: ubuntu-latest
outputs:
source-archive: $
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build source archive
id: build
env:
VERSION: $
run: |
git archive --format=tar.gz \
--prefix="PROJECT-${VERSION}/" \
-o "PROJECT-${VERSION}.tar.gz" \
HEAD
echo "source-archive=PROJECT-${VERSION}.tar.gz" >> "$GITHUB_OUTPUT"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: source-archive
path: PROJECT-*.tar.gz
retention-days: 7
build-binary-amd64:
needs: prepare
runs-on: ubuntu-latest
outputs:
binary: $
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
- name: Build release binary
env:
VERSION: $
run: |
cargo build --release
BINARY=$(ls target/release/PROJECT | head -1)
RENAMED="${BINARY}-amd64"
cp "$BINARY" "$RENAMED"
echo "binary=$RENAMED" >> "$GITHUB_OUTPUT"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-amd64
path: PROJECT-amd64
retention-days: 7
create-release:
needs: [prepare, build-source-archive, build-binary-amd64]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: ./release-assets/
- name: Generate checksums
run: |
cd release-assets
find . -type f | sort | xargs sha256sum > SHA256SUMS
cat SHA256SUMS
cd ..
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: "PROJECT $"
generate_release_notes: true
draft: false
prerelease: false
files: release-assets/**/*
env:
GITHUB_TOKEN: $
Multi-Architecture Support
Strategy for Python Projects
Pure Python projects use multiarch (Architecture: any) Debian packages:
- Set
Architecture: anyindebian/control - Set
CPACK_DEBIAN_PACKAGE_ARCHITECTURE "any"in CMakeLists.txt - Build once; package works on all architectures
- No QEMU or cross-compilation needed
Advantages:
- Fast builds (single architecture)
- No complexity overhead
- Works on amd64, ARM64, ARM32, etc.
- Industry standard for pure Python packages
Strategy for Rust Projects
Rust projects use separate architecture binaries:
Approach A: Single-Architecture (Simple)
- Build for amd64 only initially
- Release includes source tarball + amd64 binary
- Future: Expand to ARM64 when needed
Approach B: Multi-Architecture Matrix (Advanced)
1
2
3
4
5
6
7
8
9
10
11
12
13
build:
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
suffix: amd64
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
suffix: arm64
runs-on: $
steps:
# Standard build steps with matrix.target and matrix.suffix
Security Scanning Integration
Python Security Scanning
Tools:
bandit- Security linter for Pythonruff- Fast Python linter (includes some security checks)- Optional:
pip-auditfor dependency vulnerabilities
CI Integration:
1
2
3
4
5
6
7
8
9
10
11
12
security:
runs-on: ubuntu-latest
steps:
- name: Bandit - Security linter
run: |
pip install bandit
bandit -r . --severity-level medium || true
- name: Pip-audit - Dependency check
run: |
pip install pip-audit
pip-audit --desc || true
Rust Security Scanning
Tools:
cargo-audit- Check for known vulnerabilitiescargo-deny- Comprehensive dependency checkingclippy- Linter with security warnings
CI Integration:
1
2
3
4
5
6
7
8
9
10
11
12
security:
runs-on: ubuntu-latest
steps:
- name: Cargo-audit - Vulnerability check
run: |
cargo install cargo-audit
cargo audit --deny warnings || true
- name: Cargo-deny - Dependency policy
run: |
cargo install cargo-deny
cargo deny check || true
Job Dependencies and Ordering
CI Workflow Order
1
2
3
4
5
6
7
8
9
┌─────────────────┐
│ Checkout │
└────────┬────────┘
│
┌────┴────┐
│ │
┌───▼───┐ ┌──▼────┐ ┌─────────┐
│ Test │ │ Lint │ │Security │
└───────┘ └───────┘ └─────────┘
All jobs run in parallel (no dependencies).
Release Workflow Order
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──────────┐
│ prepare │
└────┬─────┘
│
┌──┴──────────────────┐
│ │
┌─▼──────────┐ ┌──────▼──────┐
│build-source│ │build-debian │
└─┬──────────┘ └──────┬───────┘
│ │
└──────────┬─────────┘
│
┌────▼──────────┐
│create-release │
└───────────────┘
Sequential: prepare → build-* (parallel) → create-release.
Testing Workflow Changes
When updating CI/CD workflows:
- Test locally (if possible):
1
act -j test # Run GitHub Actions locally
- Create test branch and workflow file:
1 2 3
git checkout -b test/ci-changes # Make workflow changes git push origin test/ci-changes
-
Monitor test run in GitHub Actions
- Merge after verification
Migration Path
For Python Projects (Already Complete)
- ✅ z-edit: Multiarch CI/CD implemented
- ✅ z-open: Multiarch CI/CD implemented
For Rust Projects (Next Phase)
- ⏳ z-kitty-launcher: Adopt standard Rust CI/CD template
- ⏳ z-rclone-mount-applete: Adopt standard Rust CI/CD template
Implementation checklist:
- Create unified
ci.ymlwith test, lint, security jobs - Create unified
release.ymlfor binary release - Add
cargo-auditto security scanning - Test on fresh repository
- Document in project AGENTS.md
Configuration Checklist
Python Projects
- CI triggers on push and PR
- Test matrix includes Python 3.11, 3.12, 3.13
- Ruff linting configured
- Bandit security scanning enabled
- Release triggers on
v*tags - Debian package architecture set to
any - Source archive included in release
- Checksums generated and included
Rust Projects
- CI triggers on push and PR
- Test matrix includes stable and beta
- Clippy linting enabled with -D warnings
- Cargo-audit security scanning enabled
- Release triggers on
v*tags - Source archive created
- Binary built for target architecture
- Checksums generated and included
Troubleshooting
Workflow Not Triggering
Problem: Release workflow doesn’t run on tag push Solution:
- Verify tag format matches
v*pattern - Check branch protection rules allow tags
- Ensure workflow file is on
masterbranch
Security Scan Failures
Problem: Bandit/cargo-audit finds issues and blocks release Solution:
- Review findings:
bandit -r . -v - Fix security issues before release
- For false positives: use tool configuration to skip
Artifact Download Order
Problem: create-release can’t find artifacts
Solution:
- Use
actions/download-artifact@v4withpath: ./release-assets/ - Artifact names must match exactly
- Use
find release-assets -type fto debug
Future Enhancements
- Automated Changelog Generation
- Parse conventional commits
- Generate release notes automatically
- Multi-Architecture Rust Builds
- Matrix jobs for amd64, ARM64, ARM32
- Docker-based cross-compilation
- Automated Security Updates
- Dependabot for Python/Rust deps
- Automated PR creation for updates
- Performance Dashboards
- Track CI/CD build times
- Alert on regressions
- Artifact Retention Policies
- Automatic cleanup of old artifacts
- S3-based long-term storage
References
- GitHub Actions: https://docs.github.com/en/actions
- Bandit: https://bandit.readthedocs.io/
- Clippy: https://doc.rust-lang.org/clippy/
- Cargo-audit: https://rustsec.org/
- Multiarch DEB: https://wiki.debian.org/Multiarch
Summary
This standardization guide provides:
✅ Unified CI patterns for all projects
✅ Consistent release workflows across Python and Rust
✅ Security scanning integration in all pipelines
✅ Multi-architecture support (multiarch for Python, matrix for Rust)
✅ Troubleshooting guidance for common issues
✅ Clear migration path for remaining projects
All patterns follow GitHub Actions best practices and industry standards.