From f426595013c49011237d0c4b3ef9c9e78785948e Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Fri, 28 Nov 2025 15:18:44 -0500 Subject: [PATCH 01/11] fix: update README to reflect project name change and improve clarity --- README.md | 73 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 22de5ee7..35f1e938 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@

- Caddy Proxy Manager+ + Charon

-

Caddy Proxy Manager+

+

Charon

The friendly way to manage your reverse proxy
@@ -11,8 +11,8 @@

License: MIT - Release - Build Status + Release + Build Status

--- @@ -23,7 +23,7 @@ |---------|-------------| | 🎨 **Beautiful Dark UI** | Modern interface that's easy on the eyes, works on any device | | πŸ” **Automatic HTTPS** | Free SSL certificates from Let's Encrypt, auto-renewed | -| πŸ›‘οΈ **Built-in Security** | CrowdSec integration, geo-blocking, IP access lists | +| πŸ›‘οΈ **Built-in Security** | CrowdSec integration, geo-blocking, IP access lists (optional, powered by Cerberus) | | πŸ“Š **Uptime Monitoring** | Know when your services go down with smart notifications | | 🐳 **Docker Discovery** | Auto-detect containers on local and remote Docker hosts | | πŸ“₯ **Easy Import** | Bring your existing Caddy or NPM configs with one click | @@ -32,29 +32,69 @@ | 🌐 **WebSocket Support** | Perfect for real-time apps and chat services | | ⚑ **Zero Downtime** | Hot-reload configuration without restarts | -**[See all features β†’](https://wikid82.github.io/cpmp/docs/features.html)** +**[See all features β†’](https://wikid82.github.io/charon/features)** --- ## πŸš€ Quick Start ```bash -# Clone and start -git clone https://github.com/Wikid82/cpmp.git -cd cpmp -docker compose up -d +services: + charon: + image: ghcr.io/wikid82/charon:latest + container_name: charon + restart: unless-stopped + ports: + - "80:80" # HTTP (Caddy proxy) + - "443:443" # HTTPS (Caddy proxy) + - "443:443/udp" # HTTP/3 (Caddy proxy) + - "8080:8080" # Management UI (Charon) + environment: + - CHARON_ENV=production # New env var prefix (CHARON_). CPM_ values still supported. + - TZ=UTC # Set timezone (e.g., America/New_York) + - CHARON_HTTP_PORT=8080 + - CHARON_DB_PATH=/app/data/charon.db + - CHARON_FRONTEND_DIR=/app/frontend/dist + - CHARON_CADDY_ADMIN_API=http://localhost:2019 + - CHARON_CADDY_CONFIG_DIR=/app/data/caddy + - CHARON_CADDY_BINARY=caddy + - CHARON_IMPORT_CADDYFILE=/import/Caddyfile + - CHARON_IMPORT_DIR=/app/data/imports + # Security Services (Optional) + #- CERBERUS_SECURITY_CROWDSEC_MODE=disabled # disabled, local, external + #- CERBERUS_SECURITY_CROWDSEC_API_URL= # Required if mode is external + #- CERBERUS_SECURITY_CROWDSEC_API_KEY= # Required if mode is external + #- CERBERUS_SECURITY_WAF_MODE=disabled # disabled, enabled + #- CERBERUS_SECURITY_RATELIMIT_ENABLED=false + #- CERBERUS_SECURITY_ACL_ENABLED=false + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - :/app/data + - :/data + - :/config + - /var/run/docker.sock:/var/run/docker.sock:ro # For local container discovery + # Mount your existing Caddyfile for automatic import (optional) + # - ./my-existing-Caddyfile:/import/Caddyfile:ro + # - ./sites:/import/sites:ro # If your Caddyfile imports other files + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/v1/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s ``` Open **http://localhost:8080** β€” that's it! πŸŽ‰ -**[Full documentation β†’](https://wikid82.github.io/cpmp/docs/index.html)** +**[Full documentation β†’](https://wikid82.github.io/charon/)** --- ## πŸ’¬ Community -- πŸ› **Found a bug?** [Open an issue](https://github.com/Wikid82/cpmp/issues) -- πŸ’‘ **Have an idea?** [Start a discussion](https://github.com/Wikid82/cpmp/discussions) +- πŸ› **Found a bug?** [Open an issue](https://github.com/Wikid82/charon/issues) +- πŸ’‘ **Have an idea?** [Start a discussion](https://github.com/Wikid82/charon/discussions) - πŸ“‹ **Roadmap** [View the project board](https://github.com/users/Wikid82/projects/7) ## 🀝 Contributing @@ -65,11 +105,12 @@ We welcome contributions! See our [Contributing Guide](CONTRIBUTING.md) to get s

MIT License Β· - Documentation Β· - Releases + Documentation Β· + Releases

Built with ❀️ by @Wikid82
- Powered by Caddy Server Β· Inspired by Nginx Proxy Manager + Security Suite: Charon is powered by Cerberus β€” an optional security engine bundling WAF (Coraza), CrowdSec integrations, ACLs and rate-limiting. + Powered by Caddy Server Β· Inspired by Nginx Proxy Manager & Pangolin

From 19f884f88ca8dbd677dd84896c68af3712ba74e6 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Fri, 28 Nov 2025 15:37:49 -0500 Subject: [PATCH 02/11] fix: update README to enhance clarity and detail about Charon and Cerberus --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 35f1e938..933de2bf 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,17 @@

Charon

-

- The friendly way to manage your reverse proxy
- Point, click, done. No config files needed. -

+

The Gateway to Effortless Connectivity. + + +Charon bridges the gap between the complex internet and your private services. Enjoy a simplified, visual management experience built specifically for the home server enthusiast. No code requiredβ€”just safe passage.

+ +

Cerberus

+ +

The Guardian at the Gate. + + +Ensure nothing passes without permission. Cerberus is a robust security suite featuring the Coraza WAF, deep CrowdSec integration, and granular rate-limiting. Always watching, always protecting.

License: MIT @@ -111,6 +118,5 @@ We welcome contributions! See our [Contributing Guide](CONTRIBUTING.md) to get s

Built with ❀️ by @Wikid82
- Security Suite: Charon is powered by Cerberus β€” an optional security engine bundling WAF (Coraza), CrowdSec integrations, ACLs and rate-limiting. Powered by Caddy Server Β· Inspired by Nginx Proxy Manager & Pangolin

From 1a2568ff4d9cac0b43dad94c659dd533bbd0deea Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Fri, 28 Nov 2025 15:42:25 -0500 Subject: [PATCH 03/11] fix: correct header level for Cerberus section in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 933de2bf..b7b12cf0 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Charon bridges the gap between the complex internet and your private services. Enjoy a simplified, visual management experience built specifically for the home server enthusiast. No code requiredβ€”just safe passage.

-

Cerberus

+

Cerberus

The Guardian at the Gate. From fc1bf92bd675e009e457ce51e76916a4ceed8852 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Fri, 28 Nov 2025 15:43:46 -0500 Subject: [PATCH 04/11] fix: add spacing before license and release badges in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b7b12cf0..4a92f278 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Charon bridges the gap between the complex internet and your private services. E Ensure nothing passes without permission. Cerberus is a robust security suite featuring the Coraza WAF, deep CrowdSec integration, and granular rate-limiting. Always watching, always protecting.

- +

License: MIT Release From 62bbd6693ea9ce45a3a4b6a15e7f58c005ab2b10 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Fri, 28 Nov 2025 15:50:12 -0500 Subject: [PATCH 05/11] fix: reorder feature list in README for improved clarity --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4a92f278..bf746875 100644 --- a/README.md +++ b/README.md @@ -28,16 +28,16 @@ Ensure nothing passes without permission. Cerberus is a robust security suite fe | Feature | Description | |---------|-------------| -| 🎨 **Beautiful Dark UI** | Modern interface that's easy on the eyes, works on any device | | πŸ” **Automatic HTTPS** | Free SSL certificates from Let's Encrypt, auto-renewed | | πŸ›‘οΈ **Built-in Security** | CrowdSec integration, geo-blocking, IP access lists (optional, powered by Cerberus) | -| πŸ“Š **Uptime Monitoring** | Know when your services go down with smart notifications | -| 🐳 **Docker Discovery** | Auto-detect containers on local and remote Docker hosts | -| πŸ“₯ **Easy Import** | Bring your existing Caddy or NPM configs with one click | -| πŸ’Ύ **Backup & Restore** | Never lose your settings, export anytime | -| πŸ” **Health Checks** | Test connections before saving | -| 🌐 **WebSocket Support** | Perfect for real-time apps and chat services | | ⚑ **Zero Downtime** | Hot-reload configuration without restarts | +| 🐳 **Docker Discovery** | Auto-detect containers on local and remote Docker hosts | +| πŸ“Š **Uptime Monitoring** | Know when your services go down with smart notifications | +| πŸ” **Health Checks** | Test connections before saving | +| πŸ“₯ **Easy Import** | Bring your existing Caddy configs with one click | +| πŸ’Ύ **Backup & Restore** | Never lose your settings, export anytime | +| 🌐 **WebSocket Support** | Perfect for real-time apps and chat services | +| 🎨 **Beautiful Dark UI** | Modern interface that's easy on the eyes, works on any device | **[See all features β†’](https://wikid82.github.io/charon/features)** From 78f7f7ecadc11ab5eeaf2df030734cfe68a18489 Mon Sep 17 00:00:00 2001 From: CI Date: Sat, 29 Nov 2025 17:11:05 +0000 Subject: [PATCH 06/11] chore(ci): CodeQL setup + seed + integration test (cherry-pick) (cherry picked from commit 35957bf196c6aee928d66e723f3074c2b2935d68) --- .github/workflows/codeql.yml | 6 +-- backend/cmd/seed/main.go | 6 +-- scripts/integration-test.sh | 77 ++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 6 deletions(-) create mode 100755 scripts/integration-test.sh diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6c609b5e..d17a521a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,14 +34,14 @@ jobs: uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6 - name: Initialize CodeQL - uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4 + uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4 + uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4 + uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4 with: category: "/language:${{ matrix.language }}" diff --git a/backend/cmd/seed/main.go b/backend/cmd/seed/main.go index 5b12b34c..68a16688 100644 --- a/backend/cmd/seed/main.go +++ b/backend/cmd/seed/main.go @@ -8,12 +8,12 @@ import ( "gorm.io/driver/sqlite" "gorm.io/gorm" - "github.com/Wikid82/CaddyProxyManagerPlus/backend/internal/models" + "github.com/Wikid82/charon/backend/internal/models" ) func main() { // Connect to database - db, err := gorm.Open(sqlite.Open("./data/cpm.db"), &gorm.Config{}) + db, err := gorm.Open(sqlite.Open("./data/charon.db"), &gorm.Config{}) if err != nil { log.Fatal("Failed to connect to database:", err) } @@ -152,7 +152,7 @@ func main() { settings := []models.Setting{ { Key: "app_name", - Value: "Caddy Proxy Manager+", + Value: "Charon", Type: "string", Category: "general", }, diff --git a/scripts/integration-test.sh b/scripts/integration-test.sh new file mode 100755 index 00000000..2ff8c7da --- /dev/null +++ b/scripts/integration-test.sh @@ -0,0 +1,77 @@ +#!/bin/bash +set -e + +# Configuration +API_URL="http://localhost:8080/api/v1" +ADMIN_EMAIL="admin@example.com" +ADMIN_PASSWORD="changeme" + +echo "Waiting for Charon to be ready..." +for i in $(seq 1 30); do + code=$(curl -s -o /dev/null -w "%{http_code}" $API_URL/health || echo "000") + if [ "$code" = "200" ]; then + echo "βœ… Charon is ready!" + break + fi + echo "Attempt $i/30: health not ready (code=$code); waiting..." + sleep 2 +done + +if [ "$code" != "200" ]; then + echo "❌ Charon failed to start" + exit 1 +fi + +echo "Logging in..." +TOKEN=$(curl -s -X POST $API_URL/auth/login \ + -H "Content-Type: application/json" \ + -d "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\"}" | jq -r .token) + +if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then + echo "❌ Login failed" + exit 1 +fi +echo "βœ… Login successful" + +echo "Creating Proxy Host..." +# We use 'whoami' as the forward host because they are on the same docker network +RESPONSE=$(curl -s -X POST $API_URL/proxy-hosts \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "domain_names": ["test.localhost"], + "forward_scheme": "http", + "forward_host": "whoami", + "forward_port": 80, + "access_list_id": "", + "certificate_id": "", + "ssl_forced": false, + "caching_enabled": false, + "block_exploits": false, + "allow_websocket_upgrade": true, + "http2_support": true, + "hsts_enabled": false, + "hsts_subdomains": false, + "locations": [] + }') + +ID=$(echo $RESPONSE | jq -r .uuid) +if [ -z "$ID" ] || [ "$ID" = "null" ]; then + echo "❌ Failed to create proxy host: $RESPONSE" + exit 1 +fi +echo "βœ… Proxy Host created (ID: $ID)" + +echo "Testing Proxy..." +# We use Host header to route to the correct proxy host +# We hit localhost:80 (Caddy) which should route to whoami +HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: test.localhost" http://localhost:80) +CONTENT=$(curl -s -H "Host: test.localhost" http://localhost:80) + +if [ "$HTTP_CODE" = "200" ] && echo "$CONTENT" | grep -q "Hostname:"; then + echo "βœ… Proxy test passed! Content received from whoami." +else + echo "❌ Proxy test failed (Code: $HTTP_CODE)" + echo "Content: $CONTENT" + exit 1 +fi From a60be34f6061abf3bfa43f166ec09edd25836ee0 Mon Sep 17 00:00:00 2001 From: CI Date: Sat, 29 Nov 2025 21:23:54 +0000 Subject: [PATCH 07/11] chore(ci): add PR-only Trivy app-only scan and pin Caddy v2.10.2 --- .github/workflows/docker-publish.yml | 117 ++++++++++++++++++--------- Dockerfile | 100 ++++++++++++++++------- 2 files changed, 154 insertions(+), 63 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 7079c13d..55e0c7df 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -6,8 +6,7 @@ on: - main - development - feature/beta-release - tags: - - 'v*.*.*' + # Note: Tags are handled by release-goreleaser.yml to avoid duplicate builds pull_request: branches: - main @@ -18,7 +17,7 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository_owner }}/cpmp + IMAGE_NAME: ${{ github.repository_owner }}/charon jobs: build-and-push: @@ -35,7 +34,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Normalize image name run: | @@ -70,11 +69,11 @@ jobs: - name: Set up QEMU if: steps.skip.outputs.skip_build != 'true' - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 - name: Set up Docker Buildx if: steps.skip.outputs.skip_build != 'true' - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Resolve Caddy base digest if: steps.skip.outputs.skip_build != 'true' @@ -84,34 +83,42 @@ jobs: DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' caddy:2-alpine) echo "image=$DIGEST" >> $GITHUB_OUTPUT + - name: Choose Registry Token + if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' + run: | + if [ -n "${{ secrets.CHARON_TOKEN }}" ]; then + echo "Using CHARON_TOKEN" >&2 + echo "REGISTRY_PASSWORD=${{ secrets.CHARON_TOKEN }}" >> $GITHUB_ENV + else + echo "Using CPMP_TOKEN fallback" >&2 + echo "REGISTRY_PASSWORD=${{ secrets.CPMP_TOKEN }}" >> $GITHUB_ENV + fi + - name: Log in to Container Registry if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} - password: ${{ secrets.CPMP_TOKEN }} + password: ${{ env.REGISTRY_PASSWORD }} - name: Extract metadata (tags, labels) if: steps.skip.outputs.skip_build != 'true' id: meta - uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 + uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=raw,value=latest,enable={{is_default_branch}} type=raw,value=dev,enable=${{ github.ref == 'refs/heads/development' }} type=raw,value=beta,enable=${{ github.ref == 'refs/heads/feature/beta-release' }} - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} type=raw,value=pr-${{ github.ref_name }},enable=${{ github.event_name == 'pull_request' }} type=sha,format=short,enable=${{ github.event_name != 'pull_request' }} - name: Build and push Docker image if: steps.skip.outputs.skip_build != 'true' id: build-and-push - uses: docker/build-push-action@v6 # v6.9.0 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 with: context: . platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }} @@ -128,7 +135,7 @@ jobs: - name: Run Trivy scan (table output) if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' - uses: aquasecurity/trivy-action@0.28.0 # 0.28.0 + uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1 with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} format: 'table' @@ -139,7 +146,7 @@ jobs: - name: Run Trivy vulnerability scanner (SARIF) if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' id: trivy - uses: aquasecurity/trivy-action@0.28.0 # 0.28.0 + uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1 with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} format: 'sarif' @@ -159,7 +166,7 @@ jobs: - name: Upload Trivy results if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.trivy-check.outputs.exists == 'true' - uses: github/codeql-action/upload-sarif@f079b8493333aace61c81488f8bd40919487bd9f # v3.26.13 + uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 with: sarif_file: 'trivy-results.sarif' token: ${{ secrets.GITHUB_TOKEN }} @@ -184,6 +191,9 @@ jobs: if: needs.build-and-push.outputs.skip_build != 'true' && github.event_name != 'pull_request' steps: + - name: Checkout repository + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - name: Normalize image name run: | raw="${{ github.repository_owner }}/${{ github.event.repository.name }}" @@ -202,38 +212,47 @@ jobs: echo "tag=sha-$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT fi + - name: Choose Registry Token + run: | + if [ -n "${{ secrets.CHARON_TOKEN }}" ]; then + echo "Using CHARON_TOKEN" >&2 + echo "REGISTRY_PASSWORD=${{ secrets.CHARON_TOKEN }}" >> $GITHUB_ENV + else + echo "Using CPMP_TOKEN fallback" >&2 + echo "REGISTRY_PASSWORD=${{ secrets.CPMP_TOKEN }}" >> $GITHUB_ENV + fi + - name: Log in to GitHub Container Registry - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.actor }} - password: ${{ secrets.CPMP_TOKEN }} + password: ${{ env.REGISTRY_PASSWORD }} - name: Pull Docker image run: docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} - - name: Run container + - name: Create Docker Network + run: docker network create charon-test-net + + - name: Run Upstream Service (whoami) + run: | + docker run -d \ + --name whoami \ + --network charon-test-net \ + traefik/whoami + + - name: Run Charon Container run: | docker run -d \ --name test-container \ + --network charon-test-net \ -p 8080:8080 \ + -p 80:80 \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} - - name: Test health endpoint (retries) - run: | - set +e - for i in $(seq 1 30); do - code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/v1/health || echo "000") - if [ "$code" = "200" ]; then - echo "βœ… Health check passed on attempt $i" - exit 0 - fi - echo "Attempt $i/30: health not ready (code=$code); waiting..." - sleep 2 - done - echo "❌ Health check failed after retries" - docker logs test-container || true - exit 1 + - name: Run Integration Test + run: ./scripts/integration-test.sh - name: Check container logs if: always() @@ -241,7 +260,10 @@ jobs: - name: Stop container if: always() - run: docker stop test-container && docker rm test-container + run: | + docker stop test-container whoami || true + docker rm test-container whoami || true + docker network rm charon-test-net || true - name: Create test summary if: always() @@ -249,4 +271,27 @@ jobs: echo "## πŸ§ͺ Docker Image Test Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- **Image**: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY - echo "- **Health Check**: ${{ job.status == 'success' && 'βœ… Passed' || '❌ Failed' }}" >> $GITHUB_STEP_SUMMARY + echo "- **Integration Test**: ${{ job.status == 'success' && 'βœ… Passed' || '❌ Failed' }}" >> $GITHUB_STEP_SUMMARY + + trivy-pr-app-only: + name: Trivy (PR) - App-only + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build image locally for PR + run: | + docker build -t charon:pr-${{ github.sha }} . + + - name: Extract `charon` binary from image + run: | + CONTAINER=$(docker create charon:pr-${{ github.sha }}) + docker cp ${CONTAINER}:/app/charon ./charon_binary || true + docker rm ${CONTAINER} || true + + - name: Run Trivy filesystem scan on `charon` (fail PR on HIGH/CRITICAL) + run: | + docker run --rm -v $HOME/.cache/trivy:/root/.cache/trivy -v $PWD:/workdir aquasec/trivy:latest fs --exit-code 1 --severity CRITICAL,HIGH /workdir/charon_binary + shell: bash diff --git a/Dockerfile b/Dockerfile index 675ddce6..03d04a4b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# Multi-stage Dockerfile for CaddyProxyManager+ with integrated Caddy +# Multi-stage Dockerfile for Charon with integrated Caddy # Single container deployment for simplified home user setup # Build arguments for versioning @@ -6,9 +6,19 @@ ARG VERSION=dev ARG BUILD_DATE ARG VCS_REF -# Allow pinning Caddy base image by digest via build-arg -# Using caddy:2.9.1-alpine to fix CVE-2025-59530 and stdlib vulnerabilities -ARG CADDY_IMAGE=caddy:2.9.1-alpine +# Allow pinning Caddy version - Renovate will update this +# Build the most recent Caddy 2.x release (keeps major pinned under v3). +# Setting this to '2' tells xcaddy to resolve the latest v2.x tag so we +# avoid accidentally pulling a v3 major release. Renovate can still update +# this ARG to a specific v2.x tag when desired. +## Try to build the requested Caddy v2.x tag (Renovate can update this ARG). +## If the requested tag isn't available, fall back to a known-good v2.10.2 build. +ARG CADDY_VERSION=2.10.2 +## When an official caddy image tag isn't available on the host, use a +## plain Alpine base image and overwrite its caddy binary with our +## xcaddy-built binary in the later COPY step. This avoids relying on +## upstream caddy image tags while still shipping a pinned caddy binary. +ARG CADDY_IMAGE=alpine:3.18 # ---- Cross-Compilation Helpers ---- FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.8.0 AS xx @@ -42,12 +52,15 @@ WORKDIR /app/backend # Install build dependencies # xx-apk installs packages for the TARGET architecture ARG TARGETPLATFORM +# hadolint ignore=DL3018 RUN apk add --no-cache clang lld +# hadolint ignore=DL3018,DL3059 RUN xx-apk add --no-cache gcc musl-dev sqlite-dev # Install Delve (cross-compile for target) # Note: xx-go install puts binaries in /go/bin/TARGETOS_TARGETARCH/dlv if cross-compiling. # We find it and move it to /go/bin/dlv so it's in a consistent location for the next stage. +# hadolint ignore=DL3059,DL4006 RUN CGO_ENABLED=0 xx-go install github.com/go-delve/delve/cmd/dlv@latest && \ DLV_PATH=$(find /go/bin -name dlv -type f | head -n 1) && \ if [ -n "$DLV_PATH" ] && [ "$DLV_PATH" != "/go/bin/dlv" ]; then \ @@ -68,17 +81,14 @@ ARG VCS_REF=unknown ARG BUILD_DATE=unknown # Build the Go binary with version information injected via ldflags -# -gcflags "all=-N -l" disables optimizations and inlining for better debugging # xx-go handles CGO and cross-compilation flags automatically RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg/mod \ CGO_ENABLED=1 xx-go build \ - -gcflags "all=-N -l" \ - -a -installsuffix cgo \ - -ldflags "-X github.com/Wikid82/CaddyProxyManagerPlus/backend/internal/version.Version=${VERSION} \ - -X github.com/Wikid82/CaddyProxyManagerPlus/backend/internal/version.GitCommit=${VCS_REF} \ - -X github.com/Wikid82/CaddyProxyManagerPlus/backend/internal/version.BuildTime=${BUILD_DATE}" \ - -o cpmp ./cmd/api + -ldflags "-s -w -X github.com/Wikid82/charon/backend/internal/version.Version=${VERSION} \ + -X github.com/Wikid82/charon/backend/internal/version.GitCommit=${VCS_REF} \ + -X github.com/Wikid82/charon/backend/internal/version.BuildTime=${BUILD_DATE}" \ + -o charon ./cmd/api # ---- Caddy Builder ---- # Build Caddy from source to ensure we use the latest Go version and dependencies @@ -86,32 +96,60 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ FROM --platform=$BUILDPLATFORM golang:alpine AS caddy-builder ARG TARGETOS ARG TARGETARCH +ARG CADDY_VERSION +# hadolint ignore=DL3018 RUN apk add --no-cache git +# hadolint ignore=DL3062 RUN --mount=type=cache,target=/go/pkg/mod \ go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest -# Build Caddy for the target architecture +# Pre-fetch/override vulnerable module versions in the module cache so xcaddy +# will pick them up during the build. These `go get` calls attempt to pin +# fixed versions of dependencies known to cause Trivy findings (expr, quic-go). +RUN --mount=type=cache,target=/go/pkg/mod \ + go get github.com/expr-lang/expr@v1.17.0 github.com/quic-go/quic-go@v0.54.1 || true + +# Build Caddy for the target architecture with security plugins. +# Try the requested v${CADDY_VERSION} tag first; if it fails (unknown tag), +# fall back to a known-good v2.10.2 build to keep the build resilient. RUN --mount=type=cache,target=/root/.cache/go-build \ - --mount=type=cache,target=/go/pkg/mod \ - GOOS=$TARGETOS GOARCH=$TARGETARCH xcaddy build v2.9.1 \ - --replace github.com/quic-go/quic-go=github.com/quic-go/quic-go@v0.49.1 \ - --replace golang.org/x/crypto=golang.org/x/crypto@v0.35.0 \ - --output /usr/bin/caddy + --mount=type=cache,target=/go/pkg/mod \ + sh -c "GOOS=$TARGETOS GOARCH=$TARGETARCH xcaddy build v${CADDY_VERSION} \ + --with github.com/greenpau/caddy-security \ + --with github.com/corazawaf/coraza-caddy/v2 \ + --with github.com/hslatman/caddy-crowdsec-bouncer \ + --with github.com/zhangjiayin/caddy-geoip2 \ + --output /usr/bin/caddy || \ + (echo 'Requested Caddy tag v${CADDY_VERSION} failed; falling back to v2.10.2' && \ + GOOS=$TARGETOS GOARCH=$TARGETARCH xcaddy build v2.10.2 \ + --with github.com/greenpau/caddy-security \ + --with github.com/corazawaf/coraza-caddy/v2 \ + --with github.com/hslatman/caddy-crowdsec-bouncer \ + --with github.com/zhangjiayin/caddy-geoip2 --output /usr/bin/caddy)" # ---- Final Runtime with Caddy ---- FROM ${CADDY_IMAGE} WORKDIR /app -# Install runtime dependencies for CPM+ (no bash needed) -RUN apk --no-cache add ca-certificates sqlite-libs \ +# Install runtime dependencies for Charon (no bash needed) +# hadolint ignore=DL3018 +RUN apk --no-cache add ca-certificates sqlite-libs tzdata curl \ && apk --no-cache upgrade +# Download MaxMind GeoLite2 Country database +# Note: In production, users should provide their own MaxMind license key +# This uses the publicly available GeoLite2 database +RUN mkdir -p /app/data/geoip && \ + curl -L "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb" \ + -o /app/data/geoip/GeoLite2-Country.mmdb + # Copy Caddy binary from caddy-builder (overwriting the one from base image) COPY --from=caddy-builder /usr/bin/caddy /usr/bin/caddy # Copy Go binary from backend builder -COPY --from=backend-builder /app/backend/cpmp /app/cpmp +COPY --from=backend-builder /app/backend/charon /app/charon +RUN ln -s /app/charon /app/cpmp || true # Copy Delve debugger (xx-go install places it in /go/bin) COPY --from=backend-builder /go/bin/dlv /usr/local/bin/dlv @@ -123,12 +161,20 @@ COPY docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh # Set default environment variables -ENV CPM_ENV=production \ +ENV CHARON_ENV=production \ + CHARON_HTTP_PORT=8080 \ + CHARON_DB_PATH=/app/data/charon.db \ + CHARON_FRONTEND_DIR=/app/frontend/dist \ + CHARON_CADDY_ADMIN_API=http://localhost:2019 \ + CHARON_CADDY_CONFIG_DIR=/app/data/caddy \ + CHARON_GEOIP_DB_PATH=/app/data/geoip/GeoLite2-Country.mmdb \ + CPM_ENV=production \ CPM_HTTP_PORT=8080 \ CPM_DB_PATH=/app/data/cpm.db \ CPM_FRONTEND_DIR=/app/frontend/dist \ CPM_CADDY_ADMIN_API=http://localhost:2019 \ - CPM_CADDY_CONFIG_DIR=/app/data/caddy + CPM_CADDY_CONFIG_DIR=/app/data/caddy \ + CPM_GEOIP_DB_PATH=/app/data/geoip/GeoLite2-Country.mmdb # Create necessary directories RUN mkdir -p /app/data /app/data/caddy /config @@ -139,18 +185,18 @@ ARG BUILD_DATE ARG VCS_REF # OCI image labels for version metadata -LABEL org.opencontainers.image.title="CaddyProxyManager+ (CPMP)" \ +LABEL org.opencontainers.image.title="Charon (CPMP legacy)" \ org.opencontainers.image.description="Web UI for managing Caddy reverse proxy configurations" \ org.opencontainers.image.version="${VERSION}" \ org.opencontainers.image.created="${BUILD_DATE}" \ org.opencontainers.image.revision="${VCS_REF}" \ - org.opencontainers.image.source="https://github.com/Wikid82/CaddyProxyManagerPlus" \ - org.opencontainers.image.url="https://github.com/Wikid82/CaddyProxyManagerPlus" \ - org.opencontainers.image.vendor="CaddyProxyManagerPlus" \ + org.opencontainers.image.source="https://github.com/Wikid82/charon" \ + org.opencontainers.image.url="https://github.com/Wikid82/charon" \ + org.opencontainers.image.vendor="charon" \ org.opencontainers.image.licenses="MIT" # Expose ports EXPOSE 80 443 443/udp 8080 2019 -# Use custom entrypoint to start both Caddy and CPM+ +# Use custom entrypoint to start both Caddy and Charon ENTRYPOINT ["/docker-entrypoint.sh"] From 4595fd4dd0bcfcecde2856d81777ec09e4b88087 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sat, 29 Nov 2025 17:53:54 -0500 Subject: [PATCH 08/11] Update .goreleaser.yaml --- .goreleaser.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b16bbf30..85171bf1 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,4 +1,4 @@ -version: 2 +version: 1 project_name: charon From 7b4145606fc194addb167950ae11030545dd1af1 Mon Sep 17 00:00:00 2001 From: CI Date: Sat, 29 Nov 2025 23:48:33 +0000 Subject: [PATCH 09/11] ci: set git identity for auto-versioning and normalize tags; update .gitignore --- .github/workflows/auto-versioning.yml | 12 +++++++++++- .gitignore | 1 - 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-versioning.yml b/.github/workflows/auto-versioning.yml index 781e4640..28a05a23 100644 --- a/.github/workflows/auto-versioning.yml +++ b/.github/workflows/auto-versioning.yml @@ -37,7 +37,17 @@ jobs: - name: Create annotated tag and push if: ${{ steps.semver.outputs.changed }} run: | - git tag -a v${{ steps.semver.outputs.version }} -m "Release v${{ steps.semver.outputs.version }}" + # Ensure a committer identity is configured in the runner so git tag works + git config --global user.email "actions@github.com" + git config --global user.name "GitHub Actions" + + # Normalize the version: remove any leading 'v' so we don't end up with 'vvX.Y.Z' + RAW="${{ steps.semver.outputs.version }}" + VERSION_NO_V="${RAW#v}" + + TAG="v${VERSION_NO_V}" + echo "Creating tag: ${TAG}" + git tag -a "${TAG}" -m "Release ${TAG}" git push origin --tags env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index acdcb2a4..2ea3fb99 100644 --- a/.gitignore +++ b/.gitignore @@ -118,4 +118,3 @@ PROJECT_PLANNING.md SECURITY_IMPLEMENTATION_PLAN.md VERSIONING_IMPLEMENTATION.md backend/internal/api/handlers/import_handler.go.bak -docker-compose.local.yml From eacf80ea2a8b6ee9f835ca04f189a74fc16560a1 Mon Sep 17 00:00:00 2001 From: CI Date: Sat, 29 Nov 2025 23:48:57 +0000 Subject: [PATCH 10/11] feat: add docker-compose configuration for local development environment --- docker-compose.local.yml | 58 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 docker-compose.local.yml diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 00000000..e9f6ecf3 --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,58 @@ +services: + app: + image: cpmp:local + container_name: cpmp-debug + restart: unless-stopped + ports: + - "80:80" # HTTP (Caddy proxy) + - "443:443" # HTTPS (Caddy proxy) + - "443:443/udp" # HTTP/3 (Caddy proxy) + - "8080:8080" # Management UI (CPM+) + - "2345:2345" # Delve Debugger + environment: + - CPM_ENV=development + - CPMP_DEBUG=1 + - TZ=America/New_York + - CPM_HTTP_PORT=8080 + - CPM_DB_PATH=/app/data/cpm.db + - CPM_FRONTEND_DIR=/app/frontend/dist + - CPM_CADDY_ADMIN_API=http://localhost:2019 + - CPM_CADDY_CONFIG_DIR=/app/data/caddy + - CPM_CADDY_BINARY=caddy + - CPM_IMPORT_CADDYFILE=/import/Caddyfile + - CPM_IMPORT_DIR=/app/data/imports + - CPM_ACME_STAGING=false + extra_hosts: + - "host.docker.internal:host-gateway" + cap_add: + - SYS_PTRACE + security_opt: + - seccomp:unconfined + volumes: + - cpm_data_local:/app/data + - caddy_data_local:/data + - caddy_config_local:/config + - /var/run/docker.sock:/var/run/docker.sock:ro # For local container discovery + - ./backend:/app/backend:ro # Mount source for debugging + # Mount your existing Caddyfile for automatic import (optional) +# - :/import/Caddyfile:ro +# - :/import/sites:ro # If your Caddyfile imports other files + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/v1/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + cpm_data_local: + driver: local + caddy_data_local: + driver: local + caddy_config_local: + driver: local + +networks: + default: + name: containers_default + external: true From 7abdfe29d666377dffeedc9966fa246408439577 Mon Sep 17 00:00:00 2001 From: CI Date: Sat, 29 Nov 2025 23:50:38 +0000 Subject: [PATCH 11/11] ci: skip existing tag creation and use normalized TAG for release --- .github/workflows/auto-versioning.yml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/auto-versioning.yml b/.github/workflows/auto-versioning.yml index 28a05a23..030b62c1 100644 --- a/.github/workflows/auto-versioning.yml +++ b/.github/workflows/auto-versioning.yml @@ -34,7 +34,8 @@ jobs: run: | echo "Next version: ${{ steps.semver.outputs.version }}" - - name: Create annotated tag and push + - id: create_tag + name: Create annotated tag and push if: ${{ steps.semver.outputs.changed }} run: | # Ensure a committer identity is configured in the runner so git tag works @@ -46,9 +47,18 @@ jobs: VERSION_NO_V="${RAW#v}" TAG="v${VERSION_NO_V}" - echo "Creating tag: ${TAG}" - git tag -a "${TAG}" -m "Release ${TAG}" - git push origin --tags + echo "TAG=${TAG}" + + # If tag already exists, skip creation to avoid failure + if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then + echo "Tag ${TAG} already exists; skipping tag creation" + else + git tag -a "${TAG}" -m "Release ${TAG}" + git push origin "${TAG}" + fi + + # Export the tag for downstream steps + echo "tag=${TAG}" >> $GITHUB_OUTPUT env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -56,8 +66,8 @@ jobs: if: ${{ steps.semver.outputs.changed }} uses: softprops/action-gh-release@v1 with: - tag_name: ${{ steps.semver.outputs.version }} - name: Release ${{ steps.semver.outputs.version }} + tag_name: ${{ steps.create_tag.outputs.tag }} + name: Release ${{ steps.create_tag.outputs.tag }} body: ${{ steps.semver.outputs.release_notes }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}