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
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:
@@ -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,
|
||||
});
|
||||
@@ -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."
|
||||
@@ -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
@@ -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"
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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 }}"
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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}`);
|
||||
@@ -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 }}"
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user