This directory contains GitHub Actions workflows for automated building, testing, and publishing of NetDriver packages.
Trigger: Pull requests and pushes to master/main branch
Purpose: Validates that packages can be built successfully
What it does:
- Builds both
netdriver-agentandnetdriver-simunetpackages - Verifies package metadata using
twine check - Uploads build artifacts for inspection
- Runs on Python 3.12
Usage: Automatically runs on PR creation and commits
Trigger:
- Automatically when a GitHub release is published
- Manually via workflow dispatch
Purpose: Publishes packages to PyPI or TestPyPI
What it does:
- Uses pre-built Docker container with uv installed
- Builds wheel packages for selected projects
- Publishes to PyPI or TestPyPI
- Uploads build artifacts
Manual Usage:
- Go to Actions → "Publish to PyPI"
- Click "Run workflow"
- Select:
- Environment:
testpypiorpypi - Projects:
all,agent,simunet, oragent,simunet
- Environment:
- Click "Run workflow"
Note: Requires the CI Docker image to be built first (see section 5 below)
The project supports independent release workflows for agent and simunet:
Trigger: When an agent-prefixed tag is pushed (e.g., agent-1.0.0)
Purpose: Creates GitHub release and publishes ONLY agent package to PyPI
What it does:
- Creates a GitHub release for agent
- Builds only agent package
- Publishes to PyPI
- Builds and pushes agent Docker image to GHCR
- Attaches wheel file to the release
Usage:
# Update agent version in pyproject.toml (optional, will be updated by workflow)
sed -i 's/^version = ".*"/version = "1.0.0"/' packages/agent/pyproject.toml
# Commit version changes (optional)
git add packages/agent/pyproject.toml
git commit -m "chore: bump agent version to 1.0.0"
# Create and push agent tag
git tag agent-1.0.0
git push origin master
git push origin agent-1.0.0Trigger: When a simunet-prefixed tag is pushed (e.g., simunet-2.5.0)
Purpose: Creates GitHub release and publishes ONLY simunet package to PyPI
What it does:
- Creates a GitHub release for simunet
- Builds only simunet package
- Publishes to PyPI
- Builds and pushes simunet Docker image to GHCR
- Attaches wheel file to the release
Usage:
# Update simunet version in pyproject.toml (optional, will be updated by workflow)
sed -i 's/^version = ".*"/version = "2.5.0"/' packages/simunet/pyproject.toml
# Commit version changes (optional)
git add packages/simunet/pyproject.toml
git commit -m "chore: bump simunet version to 2.5.0"
# Create and push simunet tag
git tag simunet-2.5.0
git push origin master
git push origin simunet-2.5.0Add the following secrets to your GitHub repository: Settings → Secrets and variables → Actions → New repository secret
| Secret Name | Description | Get From |
|---|---|---|
PYPI_API_TOKEN |
PyPI API token | https://pypi.org/manage/account/token/ |
TEST_PYPI_API_TOKEN |
TestPyPI API token | https://test.pypi.org/manage/account/token/ |
Creating PyPI Tokens:
-
For PyPI:
- Visit https://pypi.org/manage/account/token/
- Click "Add API token"
- Token name:
GitHub Actions - NetDriver - Scope:
Entire account(or specific project after first upload) - Copy the token (starts with
pypi-...)
-
For TestPyPI:
- Visit https://test.pypi.org/manage/account/token/
- Follow same steps as above
-
Add to GitHub:
- Go to your repository → Settings → Secrets and variables → Actions
- Click "New repository secret"
- Name:
PYPI_API_TOKEN - Secret: Paste your token
- Repeat for
TEST_PYPI_API_TOKEN
GitHub Actions supports PyPI's trusted publishing (no token needed):
-
Go to PyPI → Your account → Publishing
-
Add publisher for each package:
- Owner: Your GitHub username/organization
- Repository:
netdriver - Workflow:
release-agent.yml(for netdriver-agent) - Workflow:
release-simunet.yml(for netdriver-simunet) - Environment:
pypi
-
Both workflows are already configured with
id-token: writefor trusted publishing
Use this when you only need to release the agent:
-
Update version number (optional):
sed -i 's/^version = ".*"/version = "1.0.0"/' packages/agent/pyproject.toml -
Commit changes (optional):
git add packages/agent/pyproject.toml git commit -m "chore: bump agent version to 1.0.0" git push origin master -
Create and push tag:
git tag agent-1.0.0 git push origin agent-1.0.0
-
Workflow will automatically:
- Update agent version to 1.0.0
- Create GitHub release for agent
- Build agent package
- Publish to PyPI
- Build and push agent Docker image to GHCR
Use this when you need to release simunet:
-
Update version number (optional):
sed -i 's/^version = ".*"/version = "2.5.0"/' packages/simunet/pyproject.toml -
Commit changes (optional):
git add packages/simunet/pyproject.toml git commit -m "chore: bump simunet version to 2.5.0" git push origin master -
Create and push tag:
git tag simunet-2.5.0 git push origin simunet-2.5.0
-
Workflow will automatically:
- Update simunet version to 2.5.0
- Create GitHub release for simunet
- Build simunet package
- Publish to PyPI
- Build and push simunet Docker image to GHCR
To test publishing before official release:
-
Manual workflow dispatch:
- Go to Actions → "Publish to PyPI"
- Run workflow with:
- Environment:
testpypi - Projects:
all
- Environment:
-
Or use CLI:
uv publish --directory packages/agent --publish-url https://test.pypi.org/legacy/ --token $TESTPYPI_TOKEN uv publish --directory packages/simunet --publish-url https://test.pypi.org/legacy/ --token $TESTPYPI_TOKEN
-
Verify on TestPyPI:
-
Test installation:
pip install --index-url https://test.pypi.org/simple/ netdriver-agent
This is expected in Polylith architecture and can be ignored. The build will still succeed.
This means the version already exists on PyPI. Solutions:
- Bump the version number
- Use
--skip-existingflag (already in workflows)
Check that:
- GitHub secrets are correctly configured
- Tokens haven't expired
- Token has correct permissions
- PyPI can take a few minutes to update indexes
- Check package name is correct (use underscore vs hyphen)
- Verify on PyPI website first
All release workflows automatically build and publish Docker images to GitHub Container Registry (GHCR).
Each release creates multiple tags for flexibility:
latest- Always points to the most recent release<version>- Specific version (e.g.,1.0.0)<major>.<minor>- Minor version (e.g.,1.0)<major>- Major version (e.g.,1)
Docker images are built for multiple architectures:
linux/amd64(x86_64)linux/arm64(ARM64/Apple Silicon)
| Package | Registry | Image Name |
|---|---|---|
| Agent | GHCR | ghcr.io/opensecflow/netdriver/netdriver-agent |
| Simunet | GHCR | ghcr.io/opensecflow/netdriver/netdriver-simunet |
Before running either agent or simunet, prepare the directories and configuration:
Agent:
# Create directories
mkdir -p config/agent logs
# Download default configuration
curl -o config/agent/agent.yml https://raw.githubusercontent.com/opensecflow/netdriver/master/config/agent/agent.ymlSimunet:
# Create directories
mkdir -p config/simunet logs
# Download default configuration
curl -o config/simunet/simunet.yml https://raw.githubusercontent.com/opensecflow/netdriver/master/config/simunet/simunet.ymlAgent:
# Pull latest
docker pull ghcr.io/opensecflow/netdriver/netdriver-agent:latest
# Or pull specific version
docker pull ghcr.io/opensecflow/netdriver/netdriver-agent:1.0.0
# Run agent
docker run -d -p 8000:8000 \
-v $(pwd)/config:/app/config \
-v $(pwd)/logs:/app/logs \
ghcr.io/opensecflow/netdriver/netdriver-agent:latestSimunet:
# Pull latest
docker pull ghcr.io/opensecflow/netdriver/netdriver-simunet:latest
# Or pull specific version
docker pull ghcr.io/opensecflow/netdriver/netdriver-simunet:2.5.0
# Run simunet with host network mode (SSH ports bind directly to host)
docker run -d --network host \
-v $(pwd)/config:/app/config \
-v $(pwd)/logs:/app/logs \
ghcr.io/opensecflow/netdriver/netdriver-simunet:latestNote: Simunet uses host network mode (--network host) to bind SSH ports (default 2201-2220) directly to the host. This is required for proper SSH server functionality.
Docker images are public and can be pulled without authentication. For private repositories, authenticate first:
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdinThe project uses prefixed tag patterns for independent releases:
| Tag Pattern | Workflow | Releases | Artifacts |
|---|---|---|---|
agent-1.0.0 |
release-agent.yml |
Agent only | PyPI package + Docker image |
simunet-2.5.0 |
release-simunet.yml |
Simunet only | PyPI package + Docker image |
Purpose: Creates a Docker image with uv and Python pre-installed for faster CI/CD
What it includes:
- Python 3.12
- uv package manager
- Git and essential build tools
Building the image:
# Build locally
docker build -t netdriver-ci -f .github/Dockerfile.ci .
# Or trigger GitHub workflow to build and push to GHCR
# Go to Actions → "Build CI Image" → Run workflowUsing the custom image in workflows:
The publish-pypi.yml workflow uses this approach:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv python install
- run: uv sync
- run: uv build --directory packages/agentBenefits of using Docker image:
- ⚡ Faster: No need to install uv on every run
- 🔒 Consistent: Same environment across all workflows
- 💾 Cacheable: Image layers are cached by Docker
- 🎯 Reproducible: Exact same versions every time
Image locations:
- GitHub Container Registry:
ghcr.io/opensecflow/netdriver/python-uv:3.12 - Available tags:
latest,master,<branch>-<sha>
Benefits:
- ⚡ Faster: Setup in ~30 seconds vs ~2-3 minutes
- 🔒 Consistent: Same environment across all workflows
- 💾 Cacheable: Docker layer caching
- 🎯 Reproducible: Exact versions every time
netdriver/
├── .github/
│ ├── Dockerfile.ci # CI/CD Docker image
│ └── workflows/
│ ├── build-ci-image.yml # Build Docker image
│ ├── build.yml # PR/push build validation
│ ├── publish-pypi.yml # Manual PyPI publishing
│ ├── release-agent.yml # Agent release workflow
│ └── release-simunet.yml # Simunet release workflow
├── bases/
│ └── netdriver/
│ ├── agent/ # REST API service
│ └── simunet/ # Simulation network
├── components/ # Shared components
└── packages/
├── agent/
│ └── pyproject.toml
└── simunet/
└── pyproject.toml
If you want to use your own Docker image:
# Build
docker build -t your-registry/netdriver-ci:latest -f .github/Dockerfile.ci .
# Push to your registry
docker push your-registry/netdriver-ci:latestEdit publish-pypi.yml:
container:
image: your-registry/netdriver-ci:latest
credentials:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}uv is pre-installed, so you can use it directly:
- name: Verify uv installation
run: |
uv --version