chore: import upstream snapshot with attribution
Check engine pin consistency / Dockerfile / CI pin consistency (push) Successful in 8s
Sirius CI/CD Pipeline / Detect Changes (push) Successful in 23s
Validate Docker Configuration / Validate Docker Compose Configuration (push) Successful in 47s
Sirius CI/CD Pipeline / Build API (${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Build UI (${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Engine Manifest (push) Has been cancelled
Sirius CI/CD Pipeline / Merge API Manifest (push) Has been cancelled
Sirius CI/CD Pipeline / Merge UI Manifest (push) Has been cancelled
Sirius CI/CD Pipeline / Build Engine (${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Build Infra (${{ matrix.service }}, ${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Infra Manifest (sirius-postgres) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Infra Manifest (sirius-rabbitmq) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Infra Manifest (sirius-valkey) (push) Has been cancelled
Sirius CI/CD Pipeline / Integration Test (push) Has been cancelled
Sirius CI/CD Pipeline / Public Stack Contract (push) Has been cancelled
Sirius CI/CD Pipeline / Dispatch Demo Deployment (sirius-demo branch) (push) Has been cancelled
Sirius CI/CD Pipeline / Dispatch Demo Canary (main branch) (push) Has been cancelled
Sirius CI/CD Pipeline / Guard Registry Namespace (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:32:25 +08:00
commit 161ef94b4f
708 changed files with 189571 additions and 0 deletions
+172
View File
@@ -0,0 +1,172 @@
name: ChatOps Runner
on:
repository_dispatch:
types: [chatops-command]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
run-command:
name: Execute triage/test command
runs-on: ubuntu-latest
env:
COMMAND: ${{ github.event.client_payload.command }}
ISSUE_NUMBER: ${{ github.event.client_payload.issue_number }}
IS_PR: ${{ github.event.client_payload.is_pr }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Parse command
id: parse
shell: bash
run: |
set -euo pipefail
cmd="${COMMAND}"
echo "raw=$cmd" >> "$GITHUB_OUTPUT"
if [[ "$cmd" == /triage* ]]; then
state="$(echo "$cmd" | awk '{print $2}')"
echo "kind=triage" >> "$GITHUB_OUTPUT"
echo "state=${state}" >> "$GITHUB_OUTPUT"
exit 0
fi
if [[ "$cmd" == /test* ]]; then
sub="$(echo "$cmd" | awk '{print $2}')"
suite="$(echo "$cmd" | awk '{print $3}')"
echo "kind=test" >> "$GITHUB_OUTPUT"
echo "sub=${sub}" >> "$GITHUB_OUTPUT"
echo "suite=${suite}" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "kind=unknown" >> "$GITHUB_OUTPUT"
- name: Handle triage status transition
if: steps.parse.outputs.kind == 'triage'
uses: actions/github-script@v7
with:
script: |
const target = "${{ steps.parse.outputs.state }}";
const allowed = new Set(["needs-info", "repro-ready", "confirmed"]);
if (!allowed.has(target)) {
core.setOutput("triage_message", `Unsupported triage state: ${target}`);
return;
}
const statusLabels = [
"status:needs-triage",
"status:needs-info",
"status:repro-ready",
"status:confirmed",
"status:in-progress",
"status:blocked",
"status:ready-to-merge",
"status:done",
];
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue_number = Number(process.env.ISSUE_NUMBER);
const issue = await github.rest.issues.get({ owner, repo, issue_number });
const existingLabels = issue.data.labels.map((l) => l.name);
for (const label of existingLabels) {
if (statusLabels.includes(label)) {
try {
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: label });
} catch (error) {
core.warning(`Could not remove ${label}: ${error.message}`);
}
}
}
const newLabel = `status:${target}`;
await github.rest.issues.addLabels({
owner,
repo,
issue_number,
labels: [newLabel],
});
core.setOutput("triage_message", `Applied \`${newLabel}\`.`);
- name: Run requested test suite
id: test
if: steps.parse.outputs.kind == 'test'
shell: bash
run: |
set -uo pipefail
sub="${{ steps.parse.outputs.sub }}"
suite="${{ steps.parse.outputs.suite }}"
mkdir -p .chatops
log_file=".chatops/output.log"
exit_code=0
if [[ "$sub" == "health" ]]; then
./testing/container-testing/test-health.sh > "$log_file" 2>&1 || exit_code=$?
elif [[ "$sub" == "integration" ]]; then
./testing/container-testing/test-integration.sh > "$log_file" 2>&1 || exit_code=$?
elif [[ "$sub" == "security" ]]; then
if [[ -z "$suite" ]]; then
echo "Missing suite. Use: /test security <suite>" > "$log_file"
exit_code=2
else
cd testing/security
go run . --suite "$suite" > "../../$log_file" 2>&1 || exit_code=$?
fi
else
echo "Unsupported test command: /test $sub" > "$log_file"
exit_code=2
fi
echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"
{
echo "excerpt<<'EOF'"
tail -n 40 "$log_file"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Publish result comment
uses: actions/github-script@v7
env:
TRIAGE_MESSAGE: ${{ steps.handle-triage-status-transition.outputs.triage_message }}
TEST_EXIT: ${{ steps.test.outputs.exit_code }}
TEST_EXCERPT: ${{ steps.test.outputs.excerpt }}
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue_number = Number(process.env.ISSUE_NUMBER);
const command = process.env.COMMAND;
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;
let body = `<!-- sirius-chatops-result -->\n## ChatOps Result\n\n- Command: \`${command}\`\n`;
if ("${{ steps.parse.outputs.kind }}" === "triage") {
body += `- Outcome: ${process.env.TRIAGE_MESSAGE || "No change was applied."}\n`;
} else if ("${{ steps.parse.outputs.kind }}" === "test") {
const code = Number(process.env.TEST_EXIT || "1");
body += `- Outcome: ${code === 0 ? "PASS" : "FAIL"} (exit ${code})\n`;
body += `- Run logs: ${runUrl}\n\n`;
body += "### Error/Result Excerpt\n";
body += "```text\n";
body += `${process.env.TEST_EXCERPT || "No excerpt captured."}\n`;
body += "```\n";
} else {
body += "- Outcome: Unsupported command.\n";
}
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body,
});
+108
View File
@@ -0,0 +1,108 @@
name: Check engine pin consistency
# Fails any PR that lets the sirius-engine submodule pins drift between
# Dockerfile defaults and the CI fallback build-args, or that introduces
# a floating ref (main / master / branch name) instead of a full SHA or
# version tag.
#
# See:
# - sirius-engine/Dockerfile (ARG ..._COMMIT_SHA defaults)
# - .github/workflows/ci.yml (build-engine / build-api build-args)
# - documentation/dev/architecture/README.engine-component-pinning.md
on:
pull_request:
paths:
- "sirius-engine/Dockerfile"
- ".github/workflows/ci.yml"
- ".github/workflows/check-pin-consistency.yml"
push:
branches: [main]
paths:
- "sirius-engine/Dockerfile"
- ".github/workflows/ci.yml"
- ".github/workflows/check-pin-consistency.yml"
workflow_dispatch:
jobs:
check-pins:
name: Dockerfile / CI pin consistency
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Verify pins agree and are not floating
shell: bash
run: |
set -euo pipefail
DOCKERFILE=sirius-engine/Dockerfile
CI=.github/workflows/ci.yml
# Components managed by the engine pin policy. Each entry is the
# ARG/env-var prefix as it appears in both files.
PINS=(GO_API APP_SCANNER APP_TERMINAL SIRIUS_NSE APP_AGENT PINGPP)
# Acceptable pin shape: a full 40-char SHA, OR a vX.Y.Z[-suffix] tag.
# Floating refs (main, master, HEAD, branch names) are rejected.
shape_ok() {
local v="$1"
if [[ "$v" =~ ^[0-9a-f]{40}$ ]]; then return 0; fi
if [[ "$v" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([-.][A-Za-z0-9.-]+)?$ ]]; then return 0; fi
return 1
}
fail=0
for prefix in "${PINS[@]}"; do
arg_name="${prefix}_COMMIT_SHA"
dockerfile_value="$(grep -E "^ARG[[:space:]]+${arg_name}=" "$DOCKERFILE" | head -1 | sed -E "s/^ARG[[:space:]]+${arg_name}=//")"
# CI fallback lives inside ${{ env.X || 'literal' }}. Pull the literal.
ci_value="$(grep -E "${arg_name}=\\\$\\{\\{[[:space:]]*env\\.${arg_name}" "$CI" | head -1 | sed -E "s/.*\\|\\|[[:space:]]*'([^']+)'.*/\\1/")"
if [ -z "$dockerfile_value" ]; then
echo "::error file=${DOCKERFILE}::${arg_name} has no ARG default"
fail=1
continue
fi
if [ -z "$ci_value" ]; then
echo "::error file=${CI}::${arg_name} has no CI build-args fallback"
fail=1
continue
fi
if ! shape_ok "$dockerfile_value"; then
echo "::error file=${DOCKERFILE}::${arg_name}=${dockerfile_value} is a floating ref or malformed pin (require full SHA or vX.Y.Z tag)"
fail=1
fi
if ! shape_ok "$ci_value"; then
echo "::error file=${CI}::${arg_name}=${ci_value} is a floating ref or malformed pin (require full SHA or vX.Y.Z tag)"
fail=1
fi
if [ "$dockerfile_value" != "$ci_value" ]; then
echo "::error::${arg_name} drift: Dockerfile=${dockerfile_value} CI=${ci_value}"
fail=1
else
echo "OK ${arg_name}=${dockerfile_value}"
fi
done
# Bonus: forbid `sed -i` blocks against minor-project source in
# the engine Dockerfile. Patches must be upstreamed.
if grep -nE "^[[:space:]]*sed[[:space:]]+-i" "$DOCKERFILE"; then
echo "::error file=${DOCKERFILE}::Inline sed patches against submodule source are forbidden. Upstream the change to the relevant minor-project and bump the pin instead."
fail=1
fi
if [ "$fail" -ne 0 ]; then
echo ""
echo "Pin consistency check failed. See documentation/dev/architecture/README.engine-component-pinning.md for the policy."
exit 1
fi
echo ""
echo "All engine pins are consistent and well-formed."
+409
View File
@@ -0,0 +1,409 @@
name: Sirius CI/CD Pipeline (Reference Only)
# Triggers disabled — ci.yml is the canonical CI pipeline.
# This file is retained for reference; it never executes automatically.
on:
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAMESPACE: siriusscan
jobs:
detect-changes:
name: Detect Changes
runs-on: ubuntu-latest
outputs:
sirius_ui_changes: ${{ steps.changes.outputs.sirius_ui_changes }}
sirius_api_changes: ${{ steps.changes.outputs.sirius_api_changes }}
sirius_engine_changes: ${{ steps.changes.outputs.sirius_engine_changes }}
documentation_changes: ${{ steps.changes.outputs.documentation_changes }}
docker_changes: ${{ steps.changes.outputs.docker_changes }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine Changed Files
id: changes
run: |
# For pull requests
if [ "${{ github.event_name }}" == "pull_request" ]; then
BASE_SHA=${{ github.event.pull_request.base.sha }}
HEAD_SHA=${{ github.event.pull_request.head.sha }}
else
# For push events
BASE_SHA=${{ github.event.before }}
HEAD_SHA=${{ github.event.after }}
fi
# Get changed files
git diff --name-only $BASE_SHA $HEAD_SHA > changed_files.txt
echo "Changed files:"
cat changed_files.txt
# Check for service-specific changes
if grep -q "sirius-ui/" changed_files.txt; then
echo "UI changes detected"
echo "sirius_ui_changes=true" >> $GITHUB_OUTPUT
fi
if grep -q "sirius-api/" changed_files.txt; then
echo "API changes detected"
echo "sirius_api_changes=true" >> $GITHUB_OUTPUT
fi
if grep -q "sirius-engine/" changed_files.txt; then
echo "Engine changes detected"
echo "sirius_engine_changes=true" >> $GITHUB_OUTPUT
fi
# Check for documentation changes
if grep -q "documentation/" changed_files.txt; then
echo "Documentation changes detected"
echo "documentation_changes=true" >> $GITHUB_OUTPUT
fi
# Check for Docker/CI changes
if grep -q -E "(Dockerfile|\.github/|docker-compose|scripts/)" changed_files.txt; then
echo "Docker or CI changes detected"
echo "docker_changes=true" >> $GITHUB_OUTPUT
# Docker changes affect all services
echo "sirius_ui_changes=true" >> $GITHUB_OUTPUT
echo "sirius_api_changes=true" >> $GITHUB_OUTPUT
echo "sirius_engine_changes=true" >> $GITHUB_OUTPUT
fi
# If no specific changes detected but we have changes, rebuild everything
if [ ! -s changed_files.txt ]; then
echo "General changes detected, rebuilding everything"
echo "sirius_ui_changes=true" >> $GITHUB_OUTPUT
echo "sirius_api_changes=true" >> $GITHUB_OUTPUT
echo "sirius_engine_changes=true" >> $GITHUB_OUTPUT
fi
quick-checks:
name: Quick Validation
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Validate Docker Compose Configurations
env:
SIRIUS_API_KEY: ci-placeholder-api-key
POSTGRES_PASSWORD: ci-postgres-password
NEXTAUTH_SECRET: ci-nextauth-secret
INITIAL_ADMIN_PASSWORD: ci-admin-password
run: |
echo "🔍 Validating Docker Compose configurations..."
docker compose config --quiet
docker compose -f docker-compose.yaml -f docker-compose.dev.yaml config --quiet
docker compose -f docker-compose.yaml -f docker-compose.prod.yaml config --quiet
echo "✅ All Docker Compose configurations are valid"
- name: Validate Documentation
run: |
echo "📚 Validating documentation..."
cd testing/container-testing
make lint-docs-quick
make lint-index
echo "✅ Documentation validation passed"
build-and-push:
name: Build & Push Images
needs: [detect-changes, quick-checks]
runs-on: ubuntu-latest
if: github.event_name == 'push' && (needs.detect-changes.outputs.sirius_ui_changes == 'true' || needs.detect-changes.outputs.sirius_api_changes == 'true' || needs.detect-changes.outputs.sirius_engine_changes == 'true')
outputs:
image_tag: ${{ steps.meta.outputs.image_tag }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.GHCR_PUSH_USER }}
password: ${{ secrets.GHCR_PUSH_TOKEN }}
- name: Generate metadata
id: meta
run: |
# Set image tags based on event type
if [ "${{ github.event_name }}" == "pull_request" ]; then
TAG="pr-${{ github.event.number }}"
elif [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == "refs/heads/main" ]; then
TAG="latest"
echo "also_tag_beta=true" >> $GITHUB_OUTPUT
else
TAG="dev"
fi
echo "image_tag=$TAG" >> $GITHUB_OUTPUT
echo "Generated image tag: $TAG"
- name: Build and push sirius-ui
if: needs.detect-changes.outputs.sirius_ui_changes == 'true'
uses: docker/build-push-action@v6
with:
context: ./sirius-ui
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-ui:${{ steps.meta.outputs.image_tag }}
${{ steps.meta.outputs.also_tag_beta == 'true' && format('{0}/{1}/sirius-ui:beta', env.REGISTRY, env.IMAGE_NAMESPACE) || '' }}
- name: Build and push sirius-api
if: needs.detect-changes.outputs.sirius_api_changes == 'true'
uses: docker/build-push-action@v6
with:
context: ./sirius-api
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-api:${{ steps.meta.outputs.image_tag }}
${{ steps.meta.outputs.also_tag_beta == 'true' && format('{0}/{1}/sirius-api:beta', env.REGISTRY, env.IMAGE_NAMESPACE) || '' }}
- name: Build and push sirius-engine
if: needs.detect-changes.outputs.sirius_engine_changes == 'true'
uses: docker/build-push-action@v6
with:
context: ./sirius-engine
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-engine:${{ steps.meta.outputs.image_tag }}
${{ steps.meta.outputs.also_tag_beta == 'true' && format('{0}/{1}/sirius-engine:beta', env.REGISTRY, env.IMAGE_NAMESPACE) || '' }}
integration-test:
name: Integration Testing
needs: [detect-changes, build-and-push]
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.sirius_ui_changes == 'true' || needs.detect-changes.outputs.sirius_api_changes == 'true' || needs.detect-changes.outputs.sirius_engine_changes == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.GHCR_PUSH_USER }}
password: ${{ secrets.GHCR_PUSH_TOKEN }}
- name: Create CI test environment
run: |
# Create test docker-compose configuration
cat > docker-compose.ci.yml << EOF
name: sirius-ci-test
services:
sirius-postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: sirius_test
tmpfs:
- /var/lib/postgresql/data # Use RAM for CI testing
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
sirius-rabbitmq:
image: rabbitmq:3-management
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 5s
timeout: 5s
retries: 5
sirius-valkey:
image: valkey/valkey:latest
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
sirius-ui:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-ui:${{ needs.build-and-push.outputs.image_tag }}
environment:
- NODE_ENV=production
- SKIP_ENV_VALIDATION=1
- DATABASE_URL=postgresql://postgres:postgres@sirius-postgres:5432/sirius_test
- NEXTAUTH_SECRET=test-secret-key
- INITIAL_ADMIN_PASSWORD=test-admin-password
- NEXTAUTH_URL=http://localhost:3000
- SIRIUS_API_URL=http://sirius-api:9001
- NEXT_PUBLIC_SIRIUS_API_URL=http://localhost:9001
- SIRIUS_API_KEY=ci-placeholder-api-key
depends_on:
sirius-postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 10s
timeout: 5s
retries: 3
sirius-api:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-api:${{ needs.build-and-push.outputs.image_tag }}
environment:
- GO_ENV=production
- API_PORT=9001
- POSTGRES_HOST=sirius-postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=sirius_test
- POSTGRES_PORT=5432
- VALKEY_HOST=sirius-valkey
- VALKEY_PORT=6379
- RABBITMQ_URL=amqp://guest:guest@sirius-rabbitmq:5672/
- LOG_LEVEL=info
- SIRIUS_API_KEY=ci-placeholder-api-key
depends_on:
sirius-postgres:
condition: service_healthy
sirius-rabbitmq:
condition: service_healthy
sirius-valkey:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9001/api/v1/health"]
interval: 10s
timeout: 5s
retries: 3
sirius-engine:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-engine:${{ needs.build-and-push.outputs.image_tag }}
environment:
- GO_ENV=production
- ENGINE_MAIN_PORT=5174
- GRPC_AGENT_PORT=50051
- POSTGRES_HOST=sirius-postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=sirius_test
- POSTGRES_PORT=5432
- VALKEY_HOST=sirius-valkey
- VALKEY_PORT=6379
- RABBITMQ_URL=amqp://guest:guest@sirius-rabbitmq:5672/
- LOG_LEVEL=info
- SIRIUS_API_KEY=ci-placeholder-api-key
depends_on:
sirius-postgres:
condition: service_healthy
sirius-rabbitmq:
condition: service_healthy
sirius-valkey:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5174/health"]
interval: 10s
timeout: 5s
retries: 3
EOF
- name: Run integration tests
run: |
echo "🧪 Starting integration tests..."
# Start infrastructure services
docker compose -f docker-compose.ci.yml up -d sirius-postgres sirius-rabbitmq sirius-valkey
# Wait for infrastructure services to all be healthy
echo "⏳ Waiting for infrastructure services to be healthy..."
timeout 90 bash -c 'until docker compose -f docker-compose.ci.yml ps | grep -q "sirius-postgres.*(healthy)" && docker compose -f docker-compose.ci.yml ps | grep -q "sirius-rabbitmq.*(healthy)" && docker compose -f docker-compose.ci.yml ps | grep -q "sirius-valkey.*(healthy)"; do sleep 2; done'
# Start application services based on what was built
SERVICES_TO_TEST=""
if [ "${{ needs.detect-changes.outputs.sirius_api_changes }}" == "true" ]; then
SERVICES_TO_TEST="$SERVICES_TO_TEST sirius-api"
fi
if [ "${{ needs.detect-changes.outputs.sirius_engine_changes }}" == "true" ]; then
SERVICES_TO_TEST="$SERVICES_TO_TEST sirius-engine"
fi
if [ "${{ needs.detect-changes.outputs.sirius_ui_changes }}" == "true" ]; then
SERVICES_TO_TEST="$SERVICES_TO_TEST sirius-ui"
fi
if [ -n "$SERVICES_TO_TEST" ]; then
echo "🚀 Starting application services: $SERVICES_TO_TEST"
docker compose -f docker-compose.ci.yml up -d $SERVICES_TO_TEST
# Wait only for the services started in this run by checking their
# health endpoints directly instead of container health state labels.
echo "⏳ Waiting for application services to become reachable..."
if echo "$SERVICES_TO_TEST" | grep -q "sirius-api"; then
timeout 180 bash -c 'until docker compose -f docker-compose.ci.yml exec -T sirius-api wget --no-verbose --tries=1 --spider http://localhost:9001/api/v1/health; do sleep 5; done'
fi
if echo "$SERVICES_TO_TEST" | grep -q "sirius-engine"; then
timeout 180 bash -c 'until docker compose -f docker-compose.ci.yml exec -T sirius-engine wget --no-verbose --tries=1 --spider http://localhost:5174/health; do sleep 5; done'
fi
if echo "$SERVICES_TO_TEST" | grep -q "sirius-ui"; then
timeout 180 bash -c 'until docker compose -f docker-compose.ci.yml exec -T sirius-ui wget --no-verbose --tries=1 --spider http://localhost:3000/api/health; do sleep 5; done'
fi
# Check service status
echo "📊 Service status:"
docker compose -f docker-compose.ci.yml ps
# Run health checks
echo "🏥 Running health checks..."
if echo "$SERVICES_TO_TEST" | grep -q "sirius-api"; then
echo "Testing sirius-api health..."
docker compose -f docker-compose.ci.yml exec -T sirius-api wget --no-verbose --tries=1 --spider http://localhost:9001/api/v1/health || exit 1
fi
if echo "$SERVICES_TO_TEST" | grep -q "sirius-engine"; then
echo "Testing sirius-engine health..."
docker compose -f docker-compose.ci.yml exec -T sirius-engine wget --no-verbose --tries=1 --spider http://localhost:5174/health || exit 1
fi
if echo "$SERVICES_TO_TEST" | grep -q "sirius-ui"; then
echo "Testing sirius-ui health..."
docker compose -f docker-compose.ci.yml exec -T sirius-ui wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
fi
echo "✅ All health checks passed!"
else
echo "️ No application services to test"
fi
# Cleanup
echo "🧹 Cleaning up test environment..."
docker compose -f docker-compose.ci.yml down
deployment:
name: Production Deployment
needs: [detect-changes, build-and-push, integration-test]
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy to Production
run: |
echo "🚀 Deploying to production..."
echo "Image tag: ${{ needs.build-and-push.outputs.image_tag }}"
echo "✅ Deployment completed (placeholder)"
File diff suppressed because it is too large Load Diff
+183
View File
@@ -0,0 +1,183 @@
name: Deploy to Environment
# Deployment-only workflow. Images are built and published by ci.yml.
# This workflow handles environment-specific deployment configuration
# and manual promotion between environments.
#
# The push trigger has been removed. All image builds happen in ci.yml.
# Use workflow_dispatch to deploy a specific image tag to staging or production.
on:
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
default: "staging"
type: choice
options:
- staging
- production
image_tag:
description: "Image tag to deploy (e.g. latest, beta, v1.2.3)"
required: true
default: "latest"
force_deploy:
description: "Force deployment even if already at this tag"
required: false
default: false
type: boolean
env:
REGISTRY: ghcr.io
IMAGE_REGISTRY: ghcr.io/siriusscan
jobs:
# ─────────────────────────────────────────────────────────────────────────────
# Manual deployment to staging or production
# ─────────────────────────────────────────────────────────────────────────────
deploy-manual:
name: "Deploy to ${{ github.event.inputs.environment }}"
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Validate deployment inputs
run: |
echo "Environment: ${{ github.event.inputs.environment }}"
echo "Image Tag: ${{ github.event.inputs.image_tag }}"
echo "Force Deploy: ${{ github.event.inputs.force_deploy }}"
if [[ ! "${{ github.event.inputs.image_tag }}" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "Invalid image tag format"
exit 1
fi
echo "Deployment inputs validated"
- name: Generate environment configuration
run: |
ENV="${{ github.event.inputs.environment }}"
TAG="${{ github.event.inputs.image_tag }}"
mkdir -p environments
case "$ENV" in
staging)
cat > environments/.env.staging << EOF
ENVIRONMENT=staging
NODE_ENV=staging
GO_ENV=staging
IMAGE_TAG=${TAG}
IMAGE_REGISTRY=${{ env.IMAGE_REGISTRY }}
POSTGRES_USER=sirius_staging
POSTGRES_PASSWORD=${{ secrets.STAGING_POSTGRES_PASSWORD }}
POSTGRES_DB=sirius_staging
POSTGRES_HOST=staging-postgres.internal
SIRIUS_API_URL=https://staging-api.sirius.company.com
NEXT_PUBLIC_SIRIUS_API_URL=https://staging-api.sirius.company.com
NEXTAUTH_SECRET=${{ secrets.STAGING_NEXTAUTH_SECRET }}
NEXTAUTH_URL=https://staging.sirius.company.com
LOG_LEVEL=debug
LOG_FORMAT=json
EOF
;;
production)
cat > environments/.env.production << EOF
ENVIRONMENT=production
NODE_ENV=production
GO_ENV=production
IMAGE_TAG=${TAG}
IMAGE_REGISTRY=${{ env.IMAGE_REGISTRY }}
POSTGRES_USER=sirius_prod
POSTGRES_PASSWORD=${{ secrets.PRODUCTION_POSTGRES_PASSWORD }}
POSTGRES_DB=sirius_production
POSTGRES_HOST=prod-postgres.internal
SIRIUS_API_URL=https://api.sirius.company.com
NEXT_PUBLIC_SIRIUS_API_URL=https://api.sirius.company.com
NEXTAUTH_SECRET=${{ secrets.PRODUCTION_NEXTAUTH_SECRET }}
NEXTAUTH_URL=https://sirius.company.com
LOG_LEVEL=info
LOG_FORMAT=json
TLS_ENABLED=true
EOF
;;
esac
echo "Environment configuration generated for $ENV"
- name: Deploy to ${{ github.event.inputs.environment }}
run: |
ENV="${{ github.event.inputs.environment }}"
TAG="${{ github.event.inputs.image_tag }}"
echo "Deploying to $ENV with image tag: $TAG"
if [[ -f "docker-compose.$ENV.yaml" ]]; then
echo "Found docker-compose.$ENV.yaml"
else
echo "Missing docker-compose.$ENV.yaml"
exit 1
fi
if [[ -f "environments/.env.$ENV" ]]; then
echo "Found environments/.env.$ENV"
else
echo "Missing environments/.env.$ENV"
exit 1
fi
echo "Deployment validation completed"
echo "$ENV deployment successful with tag: $TAG"
- name: Post-deployment notification
if: success()
run: |
echo "Deployment to ${{ github.event.inputs.environment }} completed successfully"
echo "Image tag: ${{ github.event.inputs.image_tag }}"
# ─────────────────────────────────────────────────────────────────────────────
# Security scan runs only for production deployments
# ─────────────────────────────────────────────────────────────────────────────
security-scan:
name: Security Scan
runs-on: ubuntu-latest
if: github.event.inputs.environment == 'production'
needs: deploy-manual
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run security scan
run: |
echo "Running security scan for production deployment..."
echo "Security scan completed - no critical vulnerabilities found"
# ─────────────────────────────────────────────────────────────────────────────
# Rollback on deployment failure
# ─────────────────────────────────────────────────────────────────────────────
rollback:
name: Rollback
runs-on: ubuntu-latest
if: failure()
needs: [deploy-manual]
environment: ${{ github.event.inputs.environment }}
steps:
- name: Rollback deployment
run: |
echo "Rolling back deployment to ${{ github.event.inputs.environment }}"
echo "Rollback completed"
+197
View File
@@ -0,0 +1,197 @@
name: Issue Triage
on:
issues:
types: [opened, edited]
permissions:
contents: read
issues: write
jobs:
triage:
name: Deterministic issue triage
runs-on: ubuntu-latest
steps:
- name: Apply labels and publish triage card
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const issueNumber = issue.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const text = `${issue.title}\n${issue.body || ""}`.toLowerCase();
const statusLabels = new Set([
"status:needs-triage",
"status:needs-info",
"status:repro-ready",
"status:confirmed",
"status:in-progress",
"status:blocked",
"status:ready-to-merge",
"status:done",
]);
const nextLabels = new Set();
const reasons = [];
// Type classification
if (/(security|vulnerability|cve|hardening|auth bypass|cors)/.test(text)) {
nextLabels.add("type:security");
reasons.push("Detected security-oriented keywords.");
} else if (/(question|how do i|help|clarify)/.test(text)) {
nextLabels.add("type:question");
reasons.push("Detected question/support language.");
} else if (/(docs|documentation|readme|guide)/.test(text)) {
nextLabels.add("type:docs");
reasons.push("Detected documentation-related language.");
} else if (/(feature|enhancement|improve|request)/.test(text)) {
nextLabels.add("type:enhancement");
reasons.push("Detected enhancement/request language.");
} else {
nextLabels.add("type:bug");
reasons.push("Defaulted to bug classification.");
}
// Area classification
if (/(sirius_api_key|x-api-key|401|auth|nextauth|jwt|session)/.test(text)) {
nextLabels.add("area:auth");
reasons.push("Matched auth/API-key indicators.");
}
if (/(installer|startup|secret|nextauth_secret|initial_admin_password|postgres_password)/.test(text)) {
nextLabels.add("area:installer-secrets");
reasons.push("Matched installer/startup secret indicators.");
}
if (/(docker-compose\.dev|dev overlay|volume mount|mount shadow|prisma)/.test(text)) {
nextLabels.add("area:compose-dev");
reasons.push("Matched dev-overlay/compose indicators.");
}
if (/(docker-compose\.prod|production overlay|hardened production)/.test(text)) {
nextLabels.add("area:compose-prod");
reasons.push("Matched production overlay indicators.");
}
if (/(ui|frontend|next\.js|trpc|browser)/.test(text)) nextLabels.add("area:ui");
if (/(api|fiber|endpoint|rest)/.test(text)) nextLabels.add("area:api");
if (/(engine|scanner|grpc|agent)/.test(text)) nextLabels.add("area:engine");
if (/(rabbitmq|queue|amqp)/.test(text)) nextLabels.add("area:rabbitmq");
if (/(postgres|database|migration|prisma schema)/.test(text)) nextLabels.add("area:postgres");
if (/(valkey|redis|cache)/.test(text)) nextLabels.add("area:valkey");
if (/(windows|wsl|crlf)/.test(text)) nextLabels.add("platform:windows");
// Severity hint
if (/(critical|auth bypass|remote code execution|data loss|privilege escalation)/.test(text)) {
nextLabels.add("sev:critical");
} else if (/(stack won't boot|cannot start|service down|outage|unusable|401 everywhere)/.test(text)) {
nextLabels.add("sev:high");
} else if (/(intermittent|major|fails often)/.test(text)) {
nextLabels.add("sev:medium");
} else {
nextLabels.add("sev:low");
}
// Missing info checks for fast mobile triage
const missing = [];
if (!/(repro|steps to reproduce|reproduction)/.test(text)) {
missing.push("Reproduction steps");
}
if (!/(docker compose logs|logs --no-color|stack trace|error:)/.test(text)) {
missing.push("Relevant service logs");
}
if (!/(docker compose config --quiet|installer|sirius-installer)/.test(text)) {
missing.push("Installer/config-render validation evidence");
}
if (missing.length > 0) {
nextLabels.add("status:needs-info");
reasons.push("Missing diagnostics were detected.");
} else {
nextLabels.add("status:repro-ready");
reasons.push("Sufficient diagnostics detected for reproduction.");
}
// Remove existing status labels to keep lifecycle single-valued
for (const label of issue.labels.map((l) => l.name)) {
if (statusLabels.has(label) && !nextLabels.has(label)) {
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: issueNumber,
name: label,
});
} catch (error) {
core.warning(`Could not remove label ${label}: ${error.message}`);
}
}
}
// Add computed labels
const labelsToAdd = [...nextLabels];
if (labelsToAdd.length > 0) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: issueNumber,
labels: labelsToAdd,
});
}
const triageMarker = "<!-- sirius-triage-card -->";
const missingBlock = missing.length
? missing.map((m) => `- [ ] ${m}`).join("\n")
: "- [x] Issue includes baseline diagnostics.";
const body = `${triageMarker}
## Triage Card
**Summary**
- Type: ${[...nextLabels].find((l) => l.startsWith("type:")) || "not-set"}
- Areas: ${[...nextLabels].filter((l) => l.startsWith("area:") || l.startsWith("platform:")).join(", ") || "not-set"}
- Severity hint: ${[...nextLabels].find((l) => l.startsWith("sev:")) || "not-set"}
- Status: ${[...nextLabels].find((l) => l.startsWith("status:")) || "not-set"}
**Why this classification**
${reasons.map((r) => `- ${r}`).join("\n")}
**Missing info checklist**
${missingBlock}
**Relevant runbooks**
- API Key Operations: \`documentation/dev/operations/README.api-key-operations.md\`
- Startup/Secrets ADR: \`documentation/dev/architecture/ADR.startup-secrets-model.md\`
- Auth Surface Matrix: \`documentation/dev/architecture/README.auth-surface-matrix.md\`
**Maintainer mobile commands**
- \`/triage needs-info\`
- \`/triage repro-ready\`
- \`/triage confirmed\`
- \`/test health\`
- \`/test integration\`
- \`/test security auth-surface\`
`;
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: issueNumber,
per_page: 100,
});
const existing = comments.find((c) => c.body && c.body.includes(triageMarker));
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body,
});
}
+19
View File
@@ -0,0 +1,19 @@
name: PR Labeler
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
permissions:
contents: read
pull-requests: write
jobs:
label:
name: Apply path-based labels
runs-on: ubuntu-latest
steps:
- name: Label PR by changed files
uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
+123
View File
@@ -0,0 +1,123 @@
name: PR Review Card
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
review-card:
name: Publish review card
runs-on: ubuntu-latest
steps:
- name: Build and post review card
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pull_number = context.payload.pull_request.number;
const marker = "<!-- sirius-pr-review-card -->";
const files = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number,
per_page: 100,
});
const paths = files.map((f) => f.filename);
const changed = new Set();
const risks = [];
const requiredTests = new Set();
const has = (pattern) => paths.some((p) => pattern.test(p));
if (has(/^sirius-ui\//)) {
changed.add("area:ui");
requiredTests.add("Frontend checklist (visual + functional + container checks)");
}
if (has(/^sirius-api\//)) {
changed.add("area:api");
requiredTests.add("Backend API checklist (endpoint, validation, auth checks)");
}
if (has(/^sirius-engine\//)) {
changed.add("area:engine");
requiredTests.add("Docker/container + integration checklist");
}
if (has(/^documentation\//) || has(/^README\.md$/)) {
changed.add("type:docs");
requiredTests.add("Documentation checklist (`lint-docs`, `lint-index`)");
}
if (has(/docker-compose|Dockerfile|\.github\/workflows/)) {
changed.add("area:compose-prod");
requiredTests.add("Container/Docker checklist (`test-build`, `test-health`, `test-integration`)");
risks.push("Runtime/startup contract changed (compose/workflow/dockerfile touched).");
}
if (has(/auth|apikey|nextauth|README\.auth-surface-matrix\.md|README\.api-key-operations\.md/i)) {
changed.add("area:auth");
requiredTests.add("Authentication checklist + `security auth-surface` suite");
risks.push("Auth surface touched; require explicit auth regression evidence.");
}
if (has(/prisma|migration|sirius-postgres/i)) {
changed.add("area:postgres");
requiredTests.add("Database schema/migration checklist");
risks.push("Persistence layer changed; validate migrations and rollback safety.");
}
if (has(/rabbitmq|queue|amqp/i)) {
changed.add("area:rabbitmq");
requiredTests.add("Integration + queue behavior validation");
}
if (risks.length === 0) {
risks.push("No high-risk patterns detected by automation.");
}
const body = `${marker}
## PR Review Card
**Changed surfaces**
${[...changed].length ? [...changed].map((c) => `- ${c}`).join("\n") : "- none detected"}
**Risk flags**
${risks.map((r) => `- ${r}`).join("\n")}
**Required testing evidence**
${[...requiredTests].length ? [...requiredTests].map((t) => `- [ ] ${t}`).join("\n") : "- [ ] General smoke + health evidence"}
**Reference checklist**
- \`documentation/dev/test/CHECKLIST.testing-by-type.md\`
**Maintainer commands**
- \`/test health\`
- \`/test integration\`
- \`/test security auth-surface\`
`;
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: pull_number,
per_page: 100,
});
const existing = comments.find((c) => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number: pull_number,
body,
});
}
+28
View File
@@ -0,0 +1,28 @@
name: PR Size Labeler
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
permissions:
contents: read
pull-requests: write
jobs:
size:
name: Apply size label
runs-on: ubuntu-latest
steps:
- name: Apply PR size label
uses: pascalgn/size-label-action@v0.5.5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
XS_MAX_SIZE: "25"
S_MAX_SIZE: "99"
M_MAX_SIZE: "299"
L_MAX_SIZE: "699"
XL_MAX_SIZE: "1499"
FAIL_IF_XL: "false"
IGNORE_FILE_PATTERNS: |
*.lock
*.snap
@@ -0,0 +1,150 @@
name: Publish Release Image Tags
on:
workflow_dispatch:
inputs:
source_tag:
description: "Existing source image tag (e.g. latest)"
required: true
default: "latest"
target_tag:
description: "Release image tag to publish (e.g. v1.0.0)"
required: true
default: "v1.0.0"
env:
GHCR_OWNER: SiriusScan
REGISTRY: ghcr.io
IMAGE_NAMESPACE: siriusscan
jobs:
retag-and-publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.GHCR_PUSH_USER }}
password: ${{ secrets.GHCR_PUSH_TOKEN }}
- name: Verify source manifests exist for all components
run: |
set -euo pipefail
verify_source_manifest() {
local component="$1"
local source_ref="${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${component}:${{ github.event.inputs.source_tag }}"
echo "Checking source manifest: ${source_ref}"
docker buildx imagetools inspect "${source_ref}" > /dev/null
}
verify_source_manifest sirius-ui
verify_source_manifest sirius-api
verify_source_manifest sirius-engine
verify_source_manifest sirius-postgres
verify_source_manifest sirius-rabbitmq
verify_source_manifest sirius-valkey
- name: Publish sirius-ui release tag
run: |
docker buildx imagetools create \
-t "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-ui:${{ github.event.inputs.target_tag }}" \
"${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-ui:${{ github.event.inputs.source_tag }}"
- name: Publish sirius-api release tag
run: |
docker buildx imagetools create \
-t "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-api:${{ github.event.inputs.target_tag }}" \
"${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-api:${{ github.event.inputs.source_tag }}"
- name: Publish sirius-engine release tag
run: |
docker buildx imagetools create \
-t "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-engine:${{ github.event.inputs.target_tag }}" \
"${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-engine:${{ github.event.inputs.source_tag }}"
- name: Publish sirius-postgres release tag
run: |
docker buildx imagetools create \
-t "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-postgres:${{ github.event.inputs.target_tag }}" \
"${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-postgres:${{ github.event.inputs.source_tag }}"
- name: Publish sirius-rabbitmq release tag
run: |
docker buildx imagetools create \
-t "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-rabbitmq:${{ github.event.inputs.target_tag }}" \
"${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-rabbitmq:${{ github.event.inputs.source_tag }}"
- name: Publish sirius-valkey release tag
run: |
docker buildx imagetools create \
-t "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-valkey:${{ github.event.inputs.target_tag }}" \
"${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-valkey:${{ github.event.inputs.source_tag }}"
- name: Verify published manifests
run: |
docker manifest inspect "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-ui:${{ github.event.inputs.target_tag }}" > /dev/null
docker manifest inspect "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-api:${{ github.event.inputs.target_tag }}" > /dev/null
docker manifest inspect "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-engine:${{ github.event.inputs.target_tag }}" > /dev/null
docker manifest inspect "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-postgres:${{ github.event.inputs.target_tag }}" > /dev/null
docker manifest inspect "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-rabbitmq:${{ github.event.inputs.target_tag }}" > /dev/null
docker manifest inspect "${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/sirius-valkey:${{ github.event.inputs.target_tag }}" > /dev/null
echo "Published release image tags: ${{ github.event.inputs.target_tag }}"
- name: Verify source and release digest parity
run: |
set -euo pipefail
verify_digest() {
local component="$1"
local source_ref="${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${component}:${{ github.event.inputs.source_tag }}"
local target_ref="${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${component}:${{ github.event.inputs.target_tag }}"
local source_digest
local target_digest
# Human-readable output no longer has a stable "^Digest:" line; use JSON index digest.
source_digest=$(docker buildx imagetools inspect "$source_ref" --format '{{json .}}' | jq -r '.manifest.digest')
target_digest=$(docker buildx imagetools inspect "$target_ref" --format '{{json .}}' | jq -r '.manifest.digest')
echo "${component} source digest: ${source_digest}"
echo "${component} target digest: ${target_digest}"
if [ -z "$source_digest" ] || [ -z "$target_digest" ] || [ "$source_digest" != "$target_digest" ]; then
echo "Digest verification failed for ${component}" >&2
exit 1
fi
}
verify_digest sirius-ui
verify_digest sirius-api
verify_digest sirius-engine
verify_digest sirius-postgres
verify_digest sirius-rabbitmq
verify_digest sirius-valkey
- name: Verify anonymous access for release tag
run: |
bash scripts/verify-ghcr-public-access.sh "${{ github.event.inputs.target_tag }}"
validate-public-release-stack:
needs: retag-and-publish
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Smoke-test published public compose path
run: |
bash scripts/validate-public-compose-path.sh "${{ github.event.inputs.target_tag }}"
@@ -0,0 +1,65 @@
name: Slash Command Dispatch
on:
issue_comment:
types: [created]
permissions:
contents: write
issues: read
pull-requests: read
jobs:
dispatch:
name: Dispatch chatops command
runs-on: ubuntu-latest
if: >
github.event.issue.pull_request || !github.event.issue.pull_request
steps:
- name: Validate and dispatch
uses: actions/github-script@v7
with:
script: |
const commentBody = (context.payload.comment.body || "").trim();
const assoc = context.payload.comment.author_association || "";
const allowedAssociations = new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
if (!commentBody.startsWith("/")) {
core.info("Not a slash command, skipping.");
return;
}
if (!allowedAssociations.has(assoc)) {
core.info(`Ignoring slash command from association: ${assoc}`);
return;
}
const isSupported =
commentBody.startsWith("/triage ") ||
commentBody === "/triage" ||
commentBody.startsWith("/test ");
if (!isSupported) {
core.info(`Unsupported slash command: ${commentBody}`);
return;
}
const issueNumber = context.payload.issue.number;
const isPr = !!context.payload.issue.pull_request;
const prNumber = isPr ? issueNumber : null;
await github.request("POST /repos/{owner}/{repo}/dispatches", {
owner: context.repo.owner,
repo: context.repo.repo,
event_type: "chatops-command",
client_payload: {
command: commentBody,
issue_number: issueNumber,
pr_number: prNumber,
is_pr: isPr,
comment_id: context.payload.comment.id,
actor: context.actor,
},
});
core.info(`Dispatched chatops command: ${commentBody}`);
+43
View File
@@ -0,0 +1,43 @@
name: Stale Issue and PR Hygiene
on:
schedule:
- cron: "30 4 * * *"
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
name: Mark stale issues and PRs
runs-on: ubuntu-latest
steps:
- name: Process stale issues and PRs
uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: >
This issue has had no activity for 60 days and is now marked stale.
If this is still relevant, add an update and we will keep it open.
close-issue-message: >
Closing this issue due to inactivity.
Comment with updated context to reopen if work is still needed.
stale-pr-message: >
This pull request has had no activity for 60 days and is now marked stale.
Please push updates or comment if it should remain open.
close-pr-message: >
Closing this pull request due to inactivity.
Reopen or open a new PR when updates are ready.
days-before-issue-stale: 60
days-before-issue-close: 14
days-before-pr-stale: 60
days-before-pr-close: 14
exempt-issue-labels: "type:security,sev:critical,status:blocked"
exempt-pr-labels: "type:security,sev:critical,status:blocked"
exempt-all-milestones: true
stale-issue-label: "status:needs-info"
stale-pr-label: "status:needs-info"
close-issue-label: "status:done"
close-pr-label: "status:done"
@@ -0,0 +1,93 @@
name: Validate Docker Configuration
on:
pull_request:
paths:
- "docker-compose*.yaml"
- ".github/workflows/validate-docker-config.yml"
push:
branches: [main, develop]
paths:
- "docker-compose*.yaml"
- ".github/workflows/validate-docker-config.yml"
jobs:
validate-docker-config:
name: Validate Docker Compose Configuration
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Validate docker-compose.override.yaml
run: |
echo "🔍 Validating docker-compose.override.yaml configuration..."
# Check if volume mounts in sirius-engine are commented out
if grep -E "^\s*-\s+\.\./minor-projects/" docker-compose.override.yaml; then
echo "❌ FAIL: Volume mounts are uncommented in docker-compose.override.yaml"
echo "Found uncommented volume mounts:"
grep -E "^\s*-\s+\.\./minor-projects/" docker-compose.override.yaml
echo ""
echo "💡 These should be commented out with '#' to prevent accidental commits"
echo "💡 Use docker-compose.local.yaml for local development overrides"
exit 1
fi
# Check that the commented examples exist
if ! grep -E "^\s*#\s*-\s+\.\./minor-projects/" docker-compose.override.yaml; then
echo "⚠️ WARNING: No commented volume mount examples found"
echo "💡 Consider adding commented examples for developer reference"
fi
echo "✅ docker-compose.override.yaml validation passed"
- name: Validate no local files committed
run: |
echo "🔍 Checking for accidentally committed local files..."
# Check if any local override files were committed
if [ -f "docker-compose.local.yaml" ]; then
echo "❌ FAIL: docker-compose.local.yaml should not be committed"
echo "💡 This file is for local development only and should be git-ignored"
exit 1
fi
# Check for any other local override patterns
if ls docker-compose.*.local.yaml 2>/dev/null; then
echo "❌ FAIL: Local docker-compose files found:"
ls docker-compose.*.local.yaml
echo "💡 These files should be git-ignored"
exit 1
fi
echo "✅ No local override files found in repository"
- name: Validate no outdated template files
run: |
echo "🔍 Checking for outdated template files..."
# Check for old template files that should not exist
if [ -f "docker-compose.local.example.yaml" ]; then
echo "❌ FAIL: docker-compose.local.example.yaml is an outdated template"
echo "💡 This file is from an old implementation and should be removed"
exit 1
fi
echo "✅ No outdated template files found"
- name: Summary
if: success()
run: |
echo "🎉 All Docker configuration validations passed!"
echo ""
echo "📋 What was validated:"
echo " ✅ Volume mounts are properly commented in docker-compose.override.yaml"
echo " ✅ No local override files accidentally committed"
echo " ✅ No outdated template files found"
echo ""
echo "💡 For local development, developers should:"
echo " 1. Run './scripts/dev-setup.sh init' to create local overrides"
echo " 2. Edit docker-compose.local.yaml (git-ignored) for their needs"
echo " 3. Use './scripts/dev-setup.sh start-extended' for extended development"
@@ -0,0 +1,56 @@
# Verifies that the GitHub Release tag has matching, anonymously pullable images
# for all six GHCR packages used by docker-compose.yaml. Complements ci.yml, which
# only validates the public compose path for `latest` on main pushes.
name: Verify GHCR Release Tag
on:
release:
types: [published]
workflow_dispatch:
inputs:
image_tag:
description: "Image tag to verify (e.g. v1.0.0). Leave empty to resolve the latest GitHub release tag."
required: false
default: ""
schedule:
# Weekly: catch drift between GitHub Releases and GHCR semver tags
- cron: "0 12 * * 1"
permissions:
contents: read
jobs:
verify-anonymous-ghcr:
name: Anonymous GHCR manifest check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Resolve image tag
id: tag
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
MANUAL="${{ inputs.image_tag }}"
if [ -n "${MANUAL}" ]; then
echo "tag=${MANUAL}" >> "$GITHUB_OUTPUT"
echo "Resolved tag (manual): ${MANUAL}"
exit 0
fi
fi
if [ "${{ github.event_name }}" = "release" ]; then
TAG="${{ github.event.release.tag_name }}"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "Resolved tag (release event): ${TAG}"
exit 0
fi
TAG=$(gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq '.tag_name')
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "Resolved tag (latest GitHub release): ${TAG}"
- name: Verify anonymous GHCR access (compose-rendered refs)
run: |
bash scripts/verify-ghcr-public-access.sh "${{ steps.tag.outputs.tag }}"
+29
View File
@@ -0,0 +1,29 @@
name: Welcome First-Time Contributors
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
permissions:
issues: write
pull-requests: write
jobs:
welcome:
name: Welcome contributors
runs-on: ubuntu-latest
steps:
- name: Post welcome message
uses: actions/first-interaction@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: |
Thanks for opening your first issue in Sirius Scan.
Please include as much diagnostic detail as possible so maintainers can triage quickly.
You can also use Discussions for questions and design proposals.
pr-message: |
Thanks for your first pull request to Sirius Scan.
Please complete the PR template checklist and include validation evidence.
A maintainer will review as soon as possible.