From ebf0409ab86de1b019749ee73f3919210f929062 Mon Sep 17 00:00:00 2001 From: lftobs Date: Tue, 16 Jun 2026 12:09:28 +0100 Subject: [PATCH 1/3] refactor(infra): improve monitoring and configuration management - Update docker-compose and install scripts to handle monitoring configs and Grafana dashboards more effectively - Strengthen pipeline workspace cleanup to prevent blocking deployments - Centralize and update system configurations in the API - Add default admin email and network filtering to monitoring stack --- .github/workflows/release.yml | 6 +- apps/api/src/orchestrator/pipeline.ts | 24 ++- apps/api/src/utils/config.ts | 137 +++++++++++++----- docker-compose.yml | 27 ++-- infra/caddy/Caddyfile | 2 +- infra/monitoring/grafana/datasources/loki.yml | 1 + .../grafana/datasources/prometheus.yml | 1 + infra/monitoring/promtail-config.yml | 3 + scripts/install.sh | 50 +++++-- 9 files changed, 179 insertions(+), 72 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 19c64a9..581fc02 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,11 +71,7 @@ jobs: cp docker-compose.yml "$TAR_DIR/" cp scripts/dequel "$TAR_DIR/dequel" cp infra/caddy/Caddyfile "$TAR_DIR/infra/caddy/" - cp infra/monitoring/prometheus.yml "$TAR_DIR/infra/monitoring/" - cp infra/monitoring/loki-config.yml "$TAR_DIR/infra/monitoring/" - cp infra/monitoring/promtail-config.yml "$TAR_DIR/infra/monitoring/" - cp infra/monitoring/grafana/datasources/loki.yml "$TAR_DIR/infra/monitoring/grafana/datasources/" - cp infra/monitoring/grafana/datasources/prometheus.yml "$TAR_DIR/infra/monitoring/grafana/datasources/" + cp -r infra/monitoring "$TAR_DIR/infra/" cd "$TAR_DIR" && tar -czf "../dequel-config-${VERSION}.tar.gz" . - name: Create GitHub Release diff --git a/apps/api/src/orchestrator/pipeline.ts b/apps/api/src/orchestrator/pipeline.ts index 34367ad..916b100 100644 --- a/apps/api/src/orchestrator/pipeline.ts +++ b/apps/api/src/orchestrator/pipeline.ts @@ -626,14 +626,22 @@ export class PipelineOrchestrator { return false; } finally { this.abortControllers.delete(deploymentId); - if (workspacePath) - await cleanupWorkspace( - workspacePath, - ); - if (uploadedArchivePath) - await rm(uploadedArchivePath, { - force: true, - }); + try { + if (workspacePath) + await cleanupWorkspace( + workspacePath, + ); + } catch { + // cleanup failures must never mask the deployment result + } + try { + if (uploadedArchivePath) + await rm(uploadedArchivePath, { + force: true, + }); + } catch { + // cleanup failures must never mask the deployment result + } } } diff --git a/apps/api/src/utils/config.ts b/apps/api/src/utils/config.ts index 8c6772e..ad4cdb9 100644 --- a/apps/api/src/utils/config.ts +++ b/apps/api/src/utils/config.ts @@ -1,43 +1,112 @@ -import { loadFileConfig, type FileConfig } from "./config-loader"; +import { loadFileConfig } from "./config-loader"; const fileConfig = loadFileConfig(); const withFile = ( - key: string, - envDefault: string, - transform?: (v: string) => T, + key: string, + envDefault: string, + transform?: (v: string) => T, ): T => { - const envVal = process.env[key]; - if (envVal !== undefined) { - return transform ? transform(envVal) : (envVal as unknown as T); - } - const fileVal = (fileConfig as Record)[key]; - if (fileVal !== undefined) return fileVal as T; - return transform ? transform(envDefault) : (envDefault as unknown as T); + const envVal = process.env[key]; + if (envVal !== undefined) { + return transform + ? transform(envVal) + : (envVal as unknown as T); + } + const fileVal = ( + fileConfig as Record + )[key]; + if (fileVal !== undefined) + return fileVal as T; + return transform + ? transform(envDefault) + : (envDefault as unknown as T); }; +const SYSTEM = { + databasePath: "/app/data/dequel.db", + workspaceRoot: "/app/workspace", + caddyRoutesDir: "/caddy/routes", + dockerNetwork: "dequel_net", + buildkitHost: "tcp://buildkit:1234", + redisUrl: "redis://redis:6379", +} as const; + export const config = { - port: withFile("PORT", "3001", Number), - databasePath: withFile("DATABASE_PATH", "/app/data/dequel.db"), - workspaceRoot: withFile("WORKSPACE_ROOT", "/app/workspace"), - caddyRoutesDir: withFile("CADDY_ROUTES_DIR", "/caddy/routes"), - caddyBaseDomain: withFile("CADDY_BASE_DOMAIN", "localhost"), - dockerNetwork: withFile("DOCKER_NETWORK", "dequel_net"), - appInternalPort: withFile("APP_INTERNAL_PORT", "3000", Number), - buildkitHost: withFile("BUILDKIT_HOST", "tcp://buildkit:1234"), - envEncryptionKey: withFile("ENV_ENCRYPTION_KEY", "dev-env-key-change-me"), - redisUrl: withFile("REDIS_URL", "redis://redis:6379"), - queueConcurrency: withFile("QUEUE_CONCURRENCY", "1", Number), - queueRetryMax: withFile("QUEUE_RETRY_MAX", "5", Number), - queueRetryBaseMs: withFile("QUEUE_RETRY_BASE_MS", "5000", Number), - smtpHost: withFile("SMTP_HOST", ""), - smtpPort: withFile("SMTP_PORT", "587", Number), - smtpUser: withFile("SMTP_USER", ""), - smtpPass: withFile("SMTP_PASS", ""), - smtpFrom: withFile("SMTP_FROM", "dequel@localhost"), - alertEvalIntervalMs: withFile("ALERT_EVAL_INTERVAL_MS", "60000", Number), - githubClientId: withFile("GITHUB_CLIENT_ID", ""), - githubClientSecret: withFile("GITHUB_CLIENT_SECRET", ""), - githubAppName: withFile("GITHUB_APP_NAME", "Dequel"), - githubWebhookSecret: withFile("GITHUB_WEBHOOK_SECRET", ""), + ...SYSTEM, + port: withFile( + "PORT", + "17474", + Number, + ), + caddyBaseDomain: withFile( + "CADDY_BASE_DOMAIN", + "localhost", + ), + appInternalPort: withFile( + "APP_INTERNAL_PORT", + "17476", + Number, + ), + envEncryptionKey: withFile( + "ENV_ENCRYPTION_KEY", + "dev-env-key-change-me", + ), + queueConcurrency: withFile( + "QUEUE_CONCURRENCY", + "3", + Number, + ), + queueRetryMax: withFile( + "QUEUE_RETRY_MAX", + "5", + Number, + ), + queueRetryBaseMs: withFile( + "QUEUE_RETRY_BASE_MS", + "5000", + Number, + ), + smtpHost: withFile( + "SMTP_HOST", + "", + ), + smtpPort: withFile( + "SMTP_PORT", + "587", + Number, + ), + smtpUser: withFile( + "SMTP_USER", + "", + ), + smtpPass: withFile( + "SMTP_PASS", + "", + ), + smtpFrom: withFile( + "SMTP_FROM", + "dequel@localhost", + ), + alertEvalIntervalMs: withFile( + "ALERT_EVAL_INTERVAL_MS", + "60000", + Number, + ), + githubClientId: withFile( + "GITHUB_CLIENT_ID", + "", + ), + githubClientSecret: withFile( + "GITHUB_CLIENT_SECRET", + "", + ), + githubAppName: withFile( + "GITHUB_APP_NAME", + "Dequel", + ), + githubWebhookSecret: withFile( + "GITHUB_WEBHOOK_SECRET", + "", + ), }; diff --git a/docker-compose.yml b/docker-compose.yml index df049a8..18a28c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,11 +18,8 @@ services: healthcheck: test: [ - "CMD", - "wget", - "--spider", - "-q", - "tcp://localhost:1234", + "CMD-SHELL", + "wget -q -T 2 -O /dev/null http://localhost:1234 >/dev/null 2>&1; [ $$? -ne 4 ]", ] interval: 5s timeout: 3s @@ -104,7 +101,7 @@ services: "--watch", ] environment: - CADDY_EMAIL: ${CADDY_EMAIL:-} + CADDY_EMAIL: ${CADDY_EMAIL:-admin@dequel.local} CADDY_BASE_DOMAIN: ${CADDY_BASE_DOMAIN:-localhost} ports: - "80:80" @@ -166,12 +163,19 @@ services: prometheus: image: prom/prometheus:latest + stop_grace_period: 60s command: - - --config.file=/etc/prometheus/prometheus.yml - - --storage.tsdb.path=/prometheus - - --web.console.libraries=/usr/share/prometheus/console_libraries - - --web.console.templates=/usr/share/prometheus/consoles - - --storage.tsdb.retention.time=30d + - sh + - -c + - | + promtool tsdb rebuild-index /prometheus 2>/dev/null || true + exec prometheus \ + --config.file=/etc/prometheus/prometheus.yml \ + --storage.tsdb.path=/prometheus \ + --web.console.libraries=/usr/share/prometheus/console_libraries \ + --web.console.templates=/usr/share/prometheus/consoles \ + --storage.tsdb.retention.time=30d \ + --storage.tsdb.wal-compression volumes: - ./infra/monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus-data:/prometheus @@ -245,6 +249,7 @@ services: GF_INSTALL_PLUGINS: "" volumes: - ./infra/monitoring/grafana/datasources:/etc/grafana/provisioning/datasources:ro + - ./infra/monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro - grafana-data:/var/lib/grafana depends_on: prometheus: diff --git a/infra/caddy/Caddyfile b/infra/caddy/Caddyfile index 6b34c41..0dcac11 100644 --- a/infra/caddy/Caddyfile +++ b/infra/caddy/Caddyfile @@ -1,5 +1,5 @@ { - email {$CADDY_EMAIL} + email {$CADDY_EMAIL:admin@dequel.local} } import /etc/caddy/routes/*.caddy diff --git a/infra/monitoring/grafana/datasources/loki.yml b/infra/monitoring/grafana/datasources/loki.yml index 67ce8b7..9d8ab1e 100644 --- a/infra/monitoring/grafana/datasources/loki.yml +++ b/infra/monitoring/grafana/datasources/loki.yml @@ -2,6 +2,7 @@ apiVersion: 1 datasources: - name: Loki + uid: loki type: loki access: proxy url: http://loki:3100 diff --git a/infra/monitoring/grafana/datasources/prometheus.yml b/infra/monitoring/grafana/datasources/prometheus.yml index bb009bb..00f9915 100644 --- a/infra/monitoring/grafana/datasources/prometheus.yml +++ b/infra/monitoring/grafana/datasources/prometheus.yml @@ -2,6 +2,7 @@ apiVersion: 1 datasources: - name: Prometheus + uid: prometheus type: prometheus access: proxy url: http://prometheus:9090 diff --git a/infra/monitoring/promtail-config.yml b/infra/monitoring/promtail-config.yml index 0bf72ff..0adcfd9 100644 --- a/infra/monitoring/promtail-config.yml +++ b/infra/monitoring/promtail-config.yml @@ -14,6 +14,9 @@ scrape_configs: - host: unix:///var/run/docker.sock refresh_interval: 15s relabel_configs: + - source_labels: ['__meta_docker_container_network_dequel_net'] + regex: 'true' + action: keep - source_labels: ['__meta_docker_container_name'] regex: '/(.*)' target_label: 'container' diff --git a/scripts/install.sh b/scripts/install.sh index 14dde29..f43e82b 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -54,7 +54,7 @@ check_prerequisites() { setup_directories() { header "Setting up installation directory" - mkdir -p "$INSTALL_DIR/data" "$INSTALL_DIR/workspace" "$INSTALL_DIR/infra/caddy/routes" "$INSTALL_DIR/infra/monitoring/grafana/datasources" + mkdir -p "$INSTALL_DIR/data" "$INSTALL_DIR/workspace" "$INSTALL_DIR/infra/caddy/routes" "$INSTALL_DIR/infra/monitoring/grafana/datasources" "$INSTALL_DIR/infra/monitoring/grafana/dashboards" info "Installing to: $INSTALL_DIR" } @@ -113,28 +113,52 @@ download_configs() { for f in loki.yml prometheus.yml; do download_if_missing "$BASE_URL/infra/monitoring/grafana/datasources/$f" "$INSTALL_DIR/infra/monitoring/grafana/datasources/$f" done + + for f in dashboards.yml deployed-apps.json; do + download_if_missing "$BASE_URL/infra/monitoring/grafana/dashboards/$f" "$INSTALL_DIR/infra/monitoring/grafana/dashboards/$f" + done } prompt_config() { header "Configuration" - if [ ! -t 0 ]; then - warn "Non-interactive mode: skipping configuration prompt" - warn "Set CADDY_EMAIL and CADDY_BASE_DOMAIN manually in $INSTALL_DIR/.env" + local ADMIN_EMAIL="" + local HOSTNAME="" + + if [ -t 0 ]; then + read -r -p " Admin email (for SSL notifications, optional): " ADMIN_EMAIL + read -r -p " Base domain (e.g. dequel.example.com, optional): " HOSTNAME + elif (: /dev/null; then + read -r -p " Admin email (for SSL notifications, optional): " ADMIN_EMAIL < /dev/tty + read -r -p " Base domain (e.g. dequel.example.com, optional): " HOSTNAME < /dev/tty + else + warn "No terminal — skipping configuration prompt" + warn "Set CADDY_EMAIL and CADDY_BASE_DOMAIN in $INSTALL_DIR/.env after install" return fi - read -r -p " Admin email (for SSL notifications, optional): " ADMIN_EMAIL - read -r -p " Hostname (e.g. dequel.example.com, optional): " HOSTNAME + mkdir -p "$INSTALL_DIR/data" - if [ -n "$ADMIN_EMAIL" ] || [ -n "$HOSTNAME" ]; then - cat > "$INSTALL_DIR/.env" </dev/null || dd if=/dev/urandom bs=32 count=1 status=none 2>/dev/null | od -A n -t x1 | tr -d ' \n' || echo "dev-env-key-change-me") + + cat > "$INSTALL_DIR/data/dequel.json" < "$INSTALL_DIR/.env" + chmod 600 "$INSTALL_DIR/.env" + success "Created $INSTALL_DIR/.env" } pull_images() { From ffdceb55071291ada138d97f627dab3eaf76f6d4 Mon Sep 17 00:00:00 2001 From: lftobs Date: Tue, 16 Jun 2026 12:09:57 +0100 Subject: [PATCH 2/3] chore: add issue templates, PR template, and security CI - Add GitHub issue templates for bug reports and feature requests - Add a pull request template for standardized contributions - Add a security workflow for scanning for forbidden patterns and vulnerabilities - Add a version bump utility script - Add Grafana dashboard provisioning for monitoring --- .github/ISSUE_TEMPLATE/bug_report.yml | 111 +++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 + .github/ISSUE_TEMPLATE/feature_request.yml | 88 ++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 43 +++++ .github/workflows/vuln-scan.yml | 42 +++++ CONTRIBUTING.md | 110 ++++++++++++ LICENSE | 21 +++ bump.sh | 1 + .../grafana/dashboards/dashboards.yml | 11 ++ .../grafana/dashboards/deployed-apps.json | 157 ++++++++++++++++++ scripts/workflow/bump.sh | 124 ++++++++++++++ scripts/workflow/forbidden-pattern-scan.sh | 20 +++ 12 files changed, 736 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/vuln-scan.yml create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 120000 bump.sh create mode 100644 infra/monitoring/grafana/dashboards/dashboards.yml create mode 100644 infra/monitoring/grafana/dashboards/deployed-apps.json create mode 100755 scripts/workflow/bump.sh create mode 100644 scripts/workflow/forbidden-pattern-scan.sh diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..7b48faf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,111 @@ +name: Bug Report +description: File a bug report to help us improve Dequel +title: "[Bug]: " +labels: ["bug", "triage"] +projects: [] +assignees: [] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report. Please provide as much detail as possible. + + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues and found nothing matching + required: true + + - type: textarea + attributes: + label: Describe the Bug + description: A clear and concise description of what the bug is. + placeholder: When I do X, Y happens instead of Z... + validations: + required: true + + - type: textarea + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error + validations: + required: true + + - type: textarea + attributes: + label: Expected Behavior + description: A clear and concise description of what you expected to happen. + placeholder: I expected X to happen... + validations: + required: true + + - type: textarea + attributes: + label: Screenshots / Logs + description: | + If applicable, add screenshots or relevant logs to help explain your problem. + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + placeholder: Drag and drop screenshots or paste log output here... + + - type: dropdown + attributes: + label: Affected Component + description: Which component of Dequel is affected? + multiple: true + options: + - API (Backend) + - Web Dashboard (Frontend) + - Docs + - CLI / Install Script + - Docker / Deployment + - Build System (Railpack / BuildKit) + - Caddy / Ingress + - Monitoring (Prometheus / Grafana / Loki) + - Other + validations: + required: true + + - type: input + attributes: + label: Dequel Version + description: What version of Dequel are you running? (Check `VERSION` or `scripts/dequel status`) + placeholder: e.g. v0.1.0 + validations: + required: true + + - type: textarea + attributes: + label: Environment + description: | + Examples: + - **OS**: Ubuntu 22.04 + - **Docker**: 24.0.7 + - **Bun**: 1.1.0 + - **Browser**: Chrome 125 + - **Deployment**: Docker Compose / Bare metal + value: | + - OS: + - Docker version: + - Bun version (if relevant): + - Browser (if relevant): + validations: + required: false + + - type: textarea + attributes: + label: Additional Context + description: Add any other context about the problem here (e.g. related issues, workarounds, research). + placeholder: Any additional context... + + - type: markdown + attributes: + value: | + --- + Before submitting, please ensure you've filled out all required fields marked with *. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..9d933a6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Dequel Documentation + url: https://dequel.intrep.xyz + about: Check the documentation before opening an issue. + - name: Discussions + url: https://github.com/Lftobs/dequel/discussions + about: Please ask questions and start discussions here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..12b1830 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,88 @@ +name: Feature Request +description: Suggest an idea for Dequel +title: "[Feature]: " +labels: ["enhancement"] +projects: [] +assignees: [] +body: + - type: markdown + attributes: + value: | + Thanks for suggesting a feature for Dequel. Please describe your idea clearly so we can understand the use case. + + - type: checkboxes + attributes: + label: Is there an existing feature request for this? + description: Please search to see if a similar feature request already exists. + options: + - label: I have searched the existing issues and found nothing matching + required: true + + - type: textarea + attributes: + label: Problem Statement + description: Is your feature request related to a problem? Please describe what you're trying to solve. + placeholder: I'm frustrated when [...] because [...] / I wish I could [...] + validations: + required: true + + - type: textarea + attributes: + label: Proposed Solution + description: A clear and concise description of what you want to happen. Be as specific as possible. + placeholder: The system should [...]. This could work by [...] + validations: + required: true + + - type: textarea + attributes: + label: Alternative Solutions + description: A clear and concise description of any alternative solutions or features you've considered. + placeholder: I also considered [...], but it doesn't work because [...] + validations: + required: false + + - type: dropdown + attributes: + label: Affected Component + description: Which component of Dequel would this affect? + multiple: true + options: + - API (Backend) + - Web Dashboard (Frontend) + - Docs + - CLI / Install Script + - Build System (Railpack / BuildKit) + - Caddy / Ingress + - Monitoring (Prometheus / Grafana / Loki) + - Other + validations: + required: true + + - type: textarea + attributes: + label: Mockups / Examples + description: | + If applicable, add mockups, diagrams, or examples from other projects that demonstrate the feature. + Tip: You can attach images by clicking this area and dragging files in. + placeholder: Drag and drop images or paste links to references... + + - type: textarea + attributes: + label: Additional Context + description: Add any other context or screenshots about the feature request here. + placeholder: Any additional information... + + - type: checkboxes + attributes: + label: Would you be willing to contribute this feature? + description: Let us know if you'd like to help implement this yourself. + options: + - label: Yes, I'd be happy to submit a PR for this feature + required: false + + - type: markdown + attributes: + value: | + --- + Before submitting, please ensure you've filled out all required fields marked with *. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c5bbfa3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,43 @@ +## Description + +Please provide a summary of the changes and the motivation behind them. What problem does this PR solve? + +Fixes #(issue) + +## Type of Change + +- [ ] Bug fix (non-breaking change that fixes an issue) +- [ ] New feature (non-breaking change that adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update +- [ ] Refactor (no functional changes) +- [ ] CI / Build / Tooling +- [ ] Other (please describe): + +## How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. + +- [ ] Existing tests pass (`bun test` in `apps/api/`) +- [ ] New tests added (if applicable) +- [ ] Manual testing performed (describe steps) + +## Checklist + +- [ ] My code follows the project's code style (no comments, named exports, functional components, etc.) +- [ ] I have read the [contributing guidelines](../CONTRIBUTING.md) +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] I have updated the documentation (if applicable) +- [ ] My changes generate no new warnings or lint errors +- [ ] I have run `bun test` in `apps/api/` and all tests pass +- [ ] I have synced the VERSION file if needed (`bun run sync-versions`) + +## Screenshots (if applicable) + +| Before | After | +|--------|-------| +| (insert here) | (insert here) | + +## Additional Context + +Add any other context about the PR here (e.g., migration notes, deployment considerations, rollback strategy). diff --git a/.github/workflows/vuln-scan.yml b/.github/workflows/vuln-scan.yml new file mode 100644 index 0000000..1de96cb --- /dev/null +++ b/.github/workflows/vuln-scan.yml @@ -0,0 +1,42 @@ +name: Security Scans + +on: + push: + pull_request: + workflow_dispatch: + +concurrency: + group: security-scans-${{ github.ref }} + cancel-in-progress: true + +jobs: + forbidden-pattern-scan: + name: Forbidden Pattern Scan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run forbidden pattern scan + run: bash scripts/workflow/forbidden-pattern-scan.sh "${{ github.workspace }}" + + lazarus-scanner: + name: Lazarus Scanner + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Download lazarus_scanner.py + run: | + curl -fsSL -o lazarus_scanner.py \ + https://raw.githubusercontent.com/hngprojects/lazarus-scanner/main/lazarus_scanner.py + [ -s lazarus_scanner.py ] || { echo "FAIL: lazarus_scanner.py is empty or missing"; exit 1; } + + - name: Run Lazarus scanner + run: python3 lazarus_scanner.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7ccfe82 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,110 @@ +# Contributing to Dequel + +Thank you for your interest in contributing! Here's how to get started. + +## Getting Started + +1. Fork and clone the repo +2. Install dependencies: `bun install` +3. Read [`AGENTS.md`](./AGENTS.md) for the full architecture and conventions + +## Reporting Bugs + +Open a [Bug Report](https://github.com/Lftobs/dequel/issues/new?template=bug_report.yml). Include: + +- Steps to reproduce +- Expected vs actual behavior +- Dequel version (`VERSION` file or `scripts/dequel status`) +- Environment details (OS, Docker version, browser if relevant) + +## Suggesting Features + +Open a [Feature Request](https://github.com/Lftobs/dequel/issues/new?template=feature_request.yml). Describe: + +- The problem you're solving +- Your proposed solution +- Any alternatives considered +- Whether you'd like to implement it yourself + +## Development + +### Running Locally + +```bash +# API (port 3001) +bun apps/api/src/index.ts + +# Web dashboard (port 3000) +bun apps/web/src/main.tsx + +# Docs (port 4321) +bun apps/docs/src/main.tsx +``` + +### Code Conventions + +- **No comments** in source code unless absolutely necessary +- **Named exports** over default exports +- **Functional components + hooks** in React +- **Tailwind CSS** for styling (web and docs) +- Max ~500 lines per file — split into feature-grouped directories +- `set -euo pipefail` in all bash scripts + +### Database Migrations + +```bash +# Generate migration from schema changes +bunx drizzle-kit generate + +# Push schema directly (dev only) +bunx drizzle-kit push +``` + +### Testing + +```bash +# API tests +bun test +``` + +Always run `bun test` in `apps/api/` before committing API changes. + +### Versioning + +```bash +# Bump version across the codebase +./bump.sh v0.2.0 +``` + +This updates `VERSION`, all `package.json` files, and optionally adds a changelog entry. + +## Pull Requests + +1. Create a PR from your fork using the [PR template](./.github/PULL_REQUEST_TEMPLATE.md) +2. Ensure all tests pass (`bun test`) +3. Keep changes focused — one feature/fix per PR +4. Update documentation if your change affects user-facing behavior +5. If changing API behavior, update the docs site content + +### PR Checklist + +- [ ] Tests pass (`bun test` in `apps/api/`) +- [ ] No new warnings or lint errors +- [ ] Documentation updated (if applicable) +- [ ] Version synced (`bun run sync-versions`) if `VERSION` changed +- [ ] Follows code conventions (no comments, named exports, etc.) + +## Release Process + +Maintainers cut releases by tagging: + +```bash +git tag vX.Y.Z +git push origin vX.Y.Z +``` + +CI builds Docker images to `ghcr.io/lftobs/dequel/{api,web}:X.Y.Z` and creates a GitHub Release. + +## Questions? + +Open a [Discussion](https://github.com/Lftobs/dequel/discussions) for questions and community support. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..925f81e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Lftobs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bump.sh b/bump.sh new file mode 120000 index 0000000..526dabc --- /dev/null +++ b/bump.sh @@ -0,0 +1 @@ +scripts/workflow/bump.sh \ No newline at end of file diff --git a/infra/monitoring/grafana/dashboards/dashboards.yml b/infra/monitoring/grafana/dashboards/dashboards.yml new file mode 100644 index 0000000..dd16c25 --- /dev/null +++ b/infra/monitoring/grafana/dashboards/dashboards.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: "Dequel" + orgId: 1 + folder: "" + type: file + disableDeletion: true + editable: false + options: + path: /etc/grafana/provisioning/dashboards diff --git a/infra/monitoring/grafana/dashboards/deployed-apps.json b/infra/monitoring/grafana/dashboards/deployed-apps.json new file mode 100644 index 0000000..dc9fc72 --- /dev/null +++ b/infra/monitoring/grafana/dashboards/deployed-apps.json @@ -0,0 +1,157 @@ +{ + "title": "Dequel — Deployed Apps", + "uid": "dequel-deployed-apps", + "tags": ["dequel"], + "schemaVersion": 39, + "version": 1, + "timezone": "browser", + "refresh": "30s", + "templating": { + "list": [ + { + "name": "container", + "type": "query", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "query": { + "query": "label_values(container_cpu_usage_seconds_total{job=\"cadvisor\"}, name)", + "refId": "container-var" + }, + "regex": "/([^/]+$)", + "sort": 1, + "multi": true, + "includeAll": true, + "allValue": ".*", + "refresh": 1, + "hide": 0, + "current": { + "text": "All", + "value": "$__all" + } + } + ] + }, + "panels": [ + { + "type": "row", + "title": "Resource Usage", + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 } + }, + { + "id": 1, + "type": "timeseries", + "title": "CPU Usage", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { "h": 9, "w": 12, "x": 0, "y": 1 }, + "fieldConfig": { + "defaults": { + "unit": "short", + "custom": { + "stacking": { "mode": "normal" }, + "fillOpacity": 30, + "lineWidth": 1 + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { "mode": "multi" } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "rate(container_cpu_usage_seconds_total{name=~\".*${container:regex}$\"}[$__rate_interval])", + "legendFormat": "{{name}}", + "refId": "A" + } + ] + }, + { + "id": 2, + "type": "timeseries", + "title": "Memory Usage", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { "h": 9, "w": 12, "x": 12, "y": 1 }, + "fieldConfig": { + "defaults": { + "unit": "bytes", + "custom": { + "stacking": { "mode": "normal" }, + "fillOpacity": 30, + "lineWidth": 1 + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { "mode": "multi" } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "container_memory_working_set_bytes{name=~\".*${container:regex}$\"}", + "legendFormat": "{{name}}", + "refId": "A" + } + ] + }, + { + "type": "row", + "title": "Logs", + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 10 } + }, + { + "id": 3, + "type": "logs", + "title": "Container Logs", + "datasource": { + "type": "loki", + "uid": "loki" + }, + "gridPos": { "h": 12, "w": 24, "x": 0, "y": 11 }, + "options": { + "showLabels": true, + "showTime": true, + "wrapLogMessage": true, + "enableLogDetails": true, + "dedupStrategy": "none" + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "{container=~\"${container:regex}\"}", + "refId": "A" + } + ] + } + ] +} diff --git a/scripts/workflow/bump.sh b/scripts/workflow/bump.sh new file mode 100755 index 0000000..fdb7335 --- /dev/null +++ b/scripts/workflow/bump.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +set -euo pipefail + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +VERSION_FILE="$ROOT_DIR/VERSION" + +validate_version() { + local version="$1" + if ! echo "$version" | grep -qP '^\d+\.\d+\.\d+$'; then + echo "Error: Version must be in format MAJOR.MINOR.PATCH (e.g. 0.1.1). Got: $version" + exit 1 + fi +} + +print_header() { + local current="$1" + local new="$2" + echo -e "${CYAN}Dequel Version Bump${NC}" + echo -e "${YELLOW}Current version:${NC} $current" + echo -e "${YELLOW}New version:${NC} $new" + echo "" +} + +confirm_bump() { + read -p "Proceed with bump? (y/N) " -n 1 -r + echo + [[ $REPLY =~ ^[Yy]$ ]] +} + +update_version_file() { + local version="$1" + echo "$version" > "$VERSION_FILE" + echo -e "${GREEN}✓${NC} Updated VERSION → $version" +} + +sync_package_jsons() { + (cd "$ROOT_DIR" && bun run sync-versions) + echo -e "${GREEN}✓${NC} Synced package.json files" +} + +add_changelog_entry() { + local version="$1" + echo "" + echo -e "${CYAN}Changelog entry${NC}" + echo -e "Format: ${YELLOW}# Changelog entry title (empty to skip)${NC}" + echo "Example: '### Added' or '### Fixed'" + read -p "Section header: " -r header + if [ -z "$header" ]; then + return 1 + fi + echo "Enter bullet points (one per line). Press Ctrl+D when done:" + bullets=$(cat) + if [ -z "$bullets" ]; then + return 1 + fi + local date + date=$(date +%Y-%m-%d) + local entry="\n## [$version] - $date\n\n$header\n" + while IFS= read -r line; do + if [ -n "$line" ]; then + entry="$entry\n- $line" + fi + done <<< "$bullets" + sed -i "2a\\$entry" "$ROOT_DIR/CHANGELOG.md" + echo -e "${GREEN}✓${NC} Added changelog entry" +} + +print_summary() { + local version="$1" + local changelog_updated="$2" + echo "" + echo -e "${CYAN}=== Summary ===${NC}" + echo -e " VERSION: ${GREEN}$version${NC}" + echo -e " package.json: ${GREEN}synced${NC}" + if [ "$changelog_updated" = true ]; then + echo -e " CHANGELOG: ${GREEN}updated${NC}" + else + echo -e " CHANGELOG: ${YELLOW}skipped${NC}" + fi + echo "" + echo -e "Next steps:" + echo -e " ${YELLOW}1. Review CHANGELOG.md${NC}" + echo -e " ${YELLOW}2. Commit: git add -A && git commit -m \"chore: bump to v$version\"${NC}" + echo -e " ${YELLOW}3. Tag: git tag v$version${NC}" + echo -e " ${YELLOW}4. Push: git push origin main --tags${NC}" +} + +main() { + if [ $# -ne 1 ]; then + echo "Usage: $0 " + echo "Example: $0 v0.1.1" + exit 1 + fi + + local version="${1#v}" + validate_version "$version" + + local current + current=$(cat "$VERSION_FILE") + + print_header "$current" "$version" + + if ! confirm_bump; then + echo "Aborted." + exit 0 + fi + + update_version_file "$version" + sync_package_jsons + + local changelog_updated=false + if add_changelog_entry "$version"; then + changelog_updated=true + fi + + print_summary "$version" "$changelog_updated" +} + +main "$@" diff --git a/scripts/workflow/forbidden-pattern-scan.sh b/scripts/workflow/forbidden-pattern-scan.sh new file mode 100644 index 0000000..9e623fd --- /dev/null +++ b/scripts/workflow/forbidden-pattern-scan.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="${1:-.}" +cd "$ROOT" + +FORBIDDEN=$'global[\'!\']' +EXCLUDE=${2:-":(exclude).github/workflows/forbidden-pattern-scan.yml"} + +matches=$(git grep -lF "$FORBIDDEN" -- . "$EXCLUDE" || true) +if [[ -n "$matches" ]]; then + echo "::error::Blocked literal pattern detected in repository files." >&2 + echo "Affected file(s):" >&2 + printf '%s\n' "$matches" >&2 + echo "" >&2 + git grep -nF "$FORBIDDEN" -- . "$EXCLUDE" >&2 || true + exit 1 +fi + +echo "OK: no files contain the forbidden pattern." From fb2e810dcc4a0c7cefb995910e4e601eb37da1b1 Mon Sep 17 00:00:00 2001 From: lftobs Date: Tue, 16 Jun 2026 12:17:18 +0100 Subject: [PATCH 3/3] chore: simplify issue templates Replace YAML issue forms with standard Markdown templates to reduce overhead. --- .github/ISSUE_TEMPLATE/bug_report.md | 53 ++++++++++ .github/ISSUE_TEMPLATE/bug_report.yml | 111 --------------------- .github/ISSUE_TEMPLATE/config.yml | 8 -- .github/ISSUE_TEMPLATE/feature_request.md | 41 ++++++++ .github/ISSUE_TEMPLATE/feature_request.yml | 88 ---------------- 5 files changed, 94 insertions(+), 207 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..cacf5e1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,53 @@ +--- +name: Bug Report +about: Report a bug to help us improve Dequel +title: "[Bug]: " +labels: bug, triage +assignees: "" +--- + +## Description + +A clear and concise description of what the bug is. + +## Steps to Reproduce + +1. Go to '...' +2. Click on '...' +3. Scroll down to '...' +4. See error + +## Expected Behavior + +What did you expect to happen? + +## Actual Behavior + +What actually happened? + +## Screenshots / Logs + +If applicable, add screenshots or relevant logs. + +## Environment + +- **Dequel Version**: (check `VERSION` or `scripts/dequel status`) +- **OS**: +- **Docker version**: +- **Bun version** (if relevant): +- **Browser** (if relevant): + +## Affected Component + +- [ ] API (Backend) +- [ ] Web Dashboard (Frontend) +- [ ] Docs +- [ ] CLI / Install Script +- [ ] Docker / Deployment +- [ ] Build System (Railpack / BuildKit) +- [ ] Caddy / Ingress +- [ ] Monitoring (Prometheus / Grafana / Loki) + +## Additional Context + +Add any other context, workarounds, or related issues. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 7b48faf..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,111 +0,0 @@ -name: Bug Report -description: File a bug report to help us improve Dequel -title: "[Bug]: " -labels: ["bug", "triage"] -projects: [] -assignees: [] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report. Please provide as much detail as possible. - - - type: checkboxes - attributes: - label: Is there an existing issue for this? - description: Please search to see if an issue already exists for the bug you encountered. - options: - - label: I have searched the existing issues and found nothing matching - required: true - - - type: textarea - attributes: - label: Describe the Bug - description: A clear and concise description of what the bug is. - placeholder: When I do X, Y happens instead of Z... - validations: - required: true - - - type: textarea - attributes: - label: Steps to Reproduce - description: Steps to reproduce the behavior. - placeholder: | - 1. Go to '...' - 2. Click on '...' - 3. Scroll down to '...' - 4. See error - validations: - required: true - - - type: textarea - attributes: - label: Expected Behavior - description: A clear and concise description of what you expected to happen. - placeholder: I expected X to happen... - validations: - required: true - - - type: textarea - attributes: - label: Screenshots / Logs - description: | - If applicable, add screenshots or relevant logs to help explain your problem. - Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. - placeholder: Drag and drop screenshots or paste log output here... - - - type: dropdown - attributes: - label: Affected Component - description: Which component of Dequel is affected? - multiple: true - options: - - API (Backend) - - Web Dashboard (Frontend) - - Docs - - CLI / Install Script - - Docker / Deployment - - Build System (Railpack / BuildKit) - - Caddy / Ingress - - Monitoring (Prometheus / Grafana / Loki) - - Other - validations: - required: true - - - type: input - attributes: - label: Dequel Version - description: What version of Dequel are you running? (Check `VERSION` or `scripts/dequel status`) - placeholder: e.g. v0.1.0 - validations: - required: true - - - type: textarea - attributes: - label: Environment - description: | - Examples: - - **OS**: Ubuntu 22.04 - - **Docker**: 24.0.7 - - **Bun**: 1.1.0 - - **Browser**: Chrome 125 - - **Deployment**: Docker Compose / Bare metal - value: | - - OS: - - Docker version: - - Bun version (if relevant): - - Browser (if relevant): - validations: - required: false - - - type: textarea - attributes: - label: Additional Context - description: Add any other context about the problem here (e.g. related issues, workarounds, research). - placeholder: Any additional context... - - - type: markdown - attributes: - value: | - --- - Before submitting, please ensure you've filled out all required fields marked with *. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 9d933a6..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Dequel Documentation - url: https://dequel.intrep.xyz - about: Check the documentation before opening an issue. - - name: Discussions - url: https://github.com/Lftobs/dequel/discussions - about: Please ask questions and start discussions here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..cd40805 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,41 @@ +--- +name: Feature Request +about: Suggest an idea for Dequel +title: "[Feature]: " +labels: enhancement +assignees: "" +--- + +## Problem Statement + +Is your feature request related to a problem? Please describe what you're trying to solve. + +## Proposed Solution + +A clear and concise description of what you want to happen. + +## Alternative Solutions + +Any alternative solutions or features you've considered. + +## Affected Component + +- [ ] API (Backend) +- [ ] Web Dashboard (Frontend) +- [ ] Docs +- [ ] CLI / Install Script +- [ ] Build System (Railpack / BuildKit) +- [ ] Caddy / Ingress +- [ ] Monitoring (Prometheus / Grafana / Loki) + +## Mockups / Examples + +If applicable, add mockups, diagrams, or examples from other projects. + +## Additional Context + +Add any other context or screenshots. + +## Would you like to implement this? + +- [ ] Yes, I'd be happy to submit a PR diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index 12b1830..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: Feature Request -description: Suggest an idea for Dequel -title: "[Feature]: " -labels: ["enhancement"] -projects: [] -assignees: [] -body: - - type: markdown - attributes: - value: | - Thanks for suggesting a feature for Dequel. Please describe your idea clearly so we can understand the use case. - - - type: checkboxes - attributes: - label: Is there an existing feature request for this? - description: Please search to see if a similar feature request already exists. - options: - - label: I have searched the existing issues and found nothing matching - required: true - - - type: textarea - attributes: - label: Problem Statement - description: Is your feature request related to a problem? Please describe what you're trying to solve. - placeholder: I'm frustrated when [...] because [...] / I wish I could [...] - validations: - required: true - - - type: textarea - attributes: - label: Proposed Solution - description: A clear and concise description of what you want to happen. Be as specific as possible. - placeholder: The system should [...]. This could work by [...] - validations: - required: true - - - type: textarea - attributes: - label: Alternative Solutions - description: A clear and concise description of any alternative solutions or features you've considered. - placeholder: I also considered [...], but it doesn't work because [...] - validations: - required: false - - - type: dropdown - attributes: - label: Affected Component - description: Which component of Dequel would this affect? - multiple: true - options: - - API (Backend) - - Web Dashboard (Frontend) - - Docs - - CLI / Install Script - - Build System (Railpack / BuildKit) - - Caddy / Ingress - - Monitoring (Prometheus / Grafana / Loki) - - Other - validations: - required: true - - - type: textarea - attributes: - label: Mockups / Examples - description: | - If applicable, add mockups, diagrams, or examples from other projects that demonstrate the feature. - Tip: You can attach images by clicking this area and dragging files in. - placeholder: Drag and drop images or paste links to references... - - - type: textarea - attributes: - label: Additional Context - description: Add any other context or screenshots about the feature request here. - placeholder: Any additional information... - - - type: checkboxes - attributes: - label: Would you be willing to contribute this feature? - description: Let us know if you'd like to help implement this yourself. - options: - - label: Yes, I'd be happy to submit a PR for this feature - required: false - - - type: markdown - attributes: - value: | - --- - Before submitting, please ensure you've filled out all required fields marked with *.