Skip to content

docs: add TeamPCP/CanisterWorm supply chain assessment to SECURITY.md #85

docs: add TeamPCP/CanisterWorm supply chain assessment to SECURITY.md

docs: add TeamPCP/CanisterWorm supply chain assessment to SECURITY.md #85

name: docker-publish
on:
workflow_dispatch:
inputs:
publish_latest:
description: 'Also publish the latest tag'
default: 'true'
required: true
type: choice
options:
- 'true'
- 'false'
release_tag:
description: 'Tag to build (e.g. webssh2-server-v2.3.5); leave blank to use the selected ref'
default: ''
required: false
type: string
release:
types:
- published
push:
branches:
- main
concurrency:
group: docker-publish-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-and-push:
runs-on: ubuntu-latest
# Skip push events if this commit has a release tag (release event will handle it)
if: |
github.event_name != 'push' ||
!startsWith(github.event.head_commit.message, 'chore(main): release webssh2-server')
permissions:
contents: write
packages: write
env:
DOCKERHUB_USER: ${{ secrets.DOCKER_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKER_TOKEN }}
steps:
- name: Check required secrets
if: ${{ env.DOCKERHUB_USER == '' || env.DOCKERHUB_TOKEN == '' }}
run: |
echo '::error::DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets are required' >&2
exit 1
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
fetch-tags: true
ref: ${{ inputs.release_tag != '' && inputs.release_tag || github.ref }}
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
with:
platforms: linux/amd64,linux/arm64
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: docker.io
username: ${{ env.DOCKERHUB_USER }}
password: ${{ env.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Normalize git ref context
run: |
ref="${GITHUB_REF:-}"
ref_name="${GITHUB_REF_NAME:-}"
release_tag="${RELEASE_TAG_NAME:-}"
# First, check if a release tag was explicitly provided (via event or workflow input)
if [ -n "$release_tag" ]; then
ref="refs/tags/$release_tag"
ref_name="$release_tag"
elif [ -z "$ref" ] && [ -n "$ref_name" ]; then
ref="refs/tags/$ref_name"
fi
# If we're on a push event (not an explicit release or tag), check if current commit has a release tag
# This handles the case where release-please creates a release AND pushes to main
if [ "$GITHUB_EVENT_NAME" = "push" ] && [[ "$ref_name" != webssh2-server-v* ]]; then
echo "Push event detected, checking for release tag on current commit..."
tag_at_head=$(git describe --tags --exact-match HEAD 2>/dev/null || echo "")
if [[ "$tag_at_head" =~ ^webssh2-server-v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Found release tag at HEAD: $tag_at_head"
ref="refs/tags/$tag_at_head"
ref_name="$tag_at_head"
fi
fi
echo "EFFECTIVE_REF=$ref" >> "$GITHUB_ENV"
echo "EFFECTIVE_REF_NAME=$ref_name" >> "$GITHUB_ENV"
echo "Effective ref: $ref"
echo "Effective ref_name: $ref_name"
env:
RELEASE_TAG_NAME: ${{ github.event.release.tag_name || inputs.release_tag || '' }}
- name: Derive release semver tags
id: release_meta
run: |
ref_name="${EFFECTIVE_REF_NAME:-}"
prefix='webssh2-server-v'
if [[ "$ref_name" =~ ^${prefix}([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
major="${BASH_REMATCH[1]}"
minor="${BASH_REMATCH[2]}"
patch="${BASH_REMATCH[3]}"
echo "has_release=true" >> "$GITHUB_OUTPUT"
echo "semver_full=${major}.${minor}.${patch}" >> "$GITHUB_OUTPUT"
echo "semver_minor=${major}.${minor}" >> "$GITHUB_OUTPUT"
echo "semver_major=${major}" >> "$GITHUB_OUTPUT"
else
echo "has_release=false" >> "$GITHUB_OUTPUT"
fi
- name: Derive GHCR image name
id: ghcr
run: |
owner=$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')
echo "image=ghcr.io/${owner}/webssh2" >> "${GITHUB_OUTPUT}"
- id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: |
docker.io/billchurch/webssh2
${{ steps.ghcr.outputs.image }}
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.action == 'published' }}
type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.publish_latest == 'true' }}
type=raw,value=main,enable=${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'release' && github.event.action == 'published') }}
type=raw,value=${{ steps.release_meta.outputs.semver_full }},enable=${{ steps.release_meta.outputs.has_release == 'true' }}
type=raw,value=${{ steps.release_meta.outputs.semver_minor }},enable=${{ steps.release_meta.outputs.has_release == 'true' }}
type=raw,value=${{ steps.release_meta.outputs.semver_major }},enable=${{ steps.release_meta.outputs.has_release == 'true' }}
type=ref,event=branch,enable=${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref != 'refs/heads/main' }}
type=sha
- name: Build and push docker image
id: build
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
timeout-minutes: 60
with:
context: .
file: ./Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# BuildKit cache configuration
# - Use GitHub Actions cache for faster rebuilds
# - mode=max caches all layers (not just final stage)
cache-from: type=gha
cache-to: type=gha,mode=max
# BuildKit features for optimization
build-args: |
BUILDKIT_INLINE_CACHE=1
- name: Test docker image
run: |
# Pull the image we just built (for current platform)
docker pull ${{ steps.ghcr.outputs.image }}:sha-${GITHUB_SHA::7}
# Start container and verify it runs
CONTAINER_ID=$(docker run -d --rm \
-e DEBUG=webssh2:* \
${{ steps.ghcr.outputs.image }}:sha-${GITHUB_SHA::7})
# Wait up to 30 seconds for startup
for i in {1..30}; do
if docker logs "$CONTAINER_ID" 2>&1 | grep -q "server started successfully"; then
echo "✓ Container started successfully"
docker stop "$CONTAINER_ID"
exit 0
fi
sleep 1
done
echo "✗ Container failed to start"
docker logs "$CONTAINER_ID"
docker stop "$CONTAINER_ID"
exit 1
- name: Update release notes with Docker images
if: ${{ steps.release_meta.outputs.has_release == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ env.EFFECTIVE_REF_NAME }}
SEMVER_FULL: ${{ steps.release_meta.outputs.semver_full }}
IMAGE_DIGEST: ${{ steps.build.outputs.digest }}
run: |
# Get current release body
CURRENT_BODY=$(gh release view "$RELEASE_TAG" --json body --jq '.body')
# Create Docker images section
DOCKER_SECTION="
## 🐳 Docker Images
This release is available as multi-platform Docker images (linux/amd64, linux/arm64):
### Docker Hub
\`\`\`bash
docker pull billchurch/webssh2:latest
docker pull billchurch/webssh2:${SEMVER_FULL}
docker pull billchurch/webssh2:${{ steps.release_meta.outputs.semver_minor }}
docker pull billchurch/webssh2:${{ steps.release_meta.outputs.semver_major }}
\`\`\`
### GitHub Container Registry
\`\`\`bash
docker pull ghcr.io/billchurch/webssh2:latest
docker pull ghcr.io/billchurch/webssh2:${SEMVER_FULL}
docker pull ghcr.io/billchurch/webssh2:${{ steps.release_meta.outputs.semver_minor }}
docker pull ghcr.io/billchurch/webssh2:${{ steps.release_meta.outputs.semver_major }}
\`\`\`
**Image Digest:** \`${IMAGE_DIGEST}\`
**Links:**
- [Docker Hub Repository](https://hub.docker.com/r/billchurch/webssh2)
- [GitHub Container Registry](https://github.com/billchurch/webssh2/pkgs/container/webssh2)
"
# Append Docker section to release notes
gh release edit "$RELEASE_TAG" --notes "${CURRENT_BODY}${DOCKER_SECTION}"