Sign inSign up

negrii/proxy-control

By negrii

Updated 29 days ago

Panel to govern Nginx Proxy Manager domains, reconciling public and private DNS.

Image
0

969

negrii/proxy-control repository overview

proxy-control

Web panel to govern the domains of Nginx Proxy Manager (NPM), distinguishing public and private domains and keeping them in sync with the system that resolves each type:

  • Public → registered first in Cloudflare (DNS), then in NPM.
  • Private → registered in the Mikrotik (RouterOS 7, static DNS), then in NPM.

The app reads the domains from NPM, shows them in a table with their sync state and, visually, whether they are active in Cloudflare (public) or in the Mikrotik (private), and offers reconciliation actions when there are divergences.

Design and detailed decisions: AGENTS.md (symlink: CLAUDE.md) and IMPLEMENTATION.md.

Stack

  • Astro 7 with SSR (@astrojs/node adapter in standalone mode).
  • Preact 10 for interactive components (islands).
  • Tailwind CSS 4 (via @tailwindcss/vite, configured with @theme).
  • Postgres with Drizzle ORM (versioned migrations).
  • Node 24+.
  • Observability: Prometheus metrics (prom-client) at /metrics, JSON logs to stdout collected by Grafana AlloyLoki, visualization in Grafana, and alerting via Alertmanager.

Architecture

Browser (Preact islands)
      │  fetch()
      ▼
Astro SSR endpoints (src/pages/api/*.ts)   ← all integration logic
      │
      ├─ Providers (src/server/providers/)
      │     ├─ npm.ts         → Nginx Proxy Manager API
      │     ├─ cloudflare.ts  → Cloudflare API (public domains)
      │     └─ mikrotik.ts    → RouterOS 7 REST API (private domains)
      │
      ├─ Reconciliation (src/server/…)  → compares desired vs actual state
      └─ Persistence (src/server/db/)   → Postgres (metadata and desired state)

Source of truth: NPM is the domain list; our DB stores the type (public/private), the desired state, and the result of the last reconciliation. Network/integration logic runs only on the server.

Getting started

The dev stack is self-contained: app with hot-reload + Postgres + full observability (Grafana, Prometheus, Loki, Alloy, Alertmanager).

npm run docker:dev
# same as: docker compose -f infra/dev/compose.yml up --build

Services exposed locally:

Local (without Docker)

Requires a reachable Postgres and the environment variables in a root .env.

npm install
npm run db:migrate     # apply Drizzle migrations
npm run dev            # dev server at http://localhost:4321

Environment variables

In dev they are read from a root .env (do not commit). The DNS provider secrets (Cloudflare/Mikrotik) are not passed via env: they are stored encrypted in the DB and edited from the panel (Settings → DNS Providers).

DATABASE_URL=postgresql://user:pass@host:5432/db
NPM_BASE_URL=http://npm.lan:81
NPM_EMAIL=...
NPM_PASSWORD=...
SESSION_SECRET=...     # signs the session cookie
SETTINGS_KEY=...       # REQUIRED: encrypts (AES-256-GCM) the provider secrets in the DB
AUTH_ENABLED=true      # false → disables login (LAN-only use)

Generate the access password hash with npm run auth:hash.

Commands

CommandAction
npm run devDev server at localhost:4321
npm run buildProduction build to ./dist/
npm run startRun the build (node ./dist/server/entry.mjs)
npm run typechecktsc --noEmit
npm run formatPrettier --write over the whole repo
npm run db:generateGenerate migrations from the schema (Drizzle Kit)
npm run db:migrateApply migrations
npm run db:studioOpen Drizzle Studio
npm run auth:hashGenerate the argon2 hash of the access password
npm run docker:devFull dev stack in Docker
npm run docker:prodProd stack (usually via pi-infra)

Observability

The app exposes /metrics (Prometheus) and /health. In the dev stack, the proxy-control · Overview Grafana dashboard shows app status, domains by state, reconciliations, and logs.

Note (dev only): the Astro dev server runs on Vite, which rejects requests with 403 when the Host header is not in allowedHosts. Since Prometheus scrapes /metrics by the Docker service name (proxy-control), that host is explicitly allowed in astro.config.mjs (vite.server.allowedHosts). Without that entry, up{job="proxy-control"} is 0 and the metrics panels stay empty. Not applicable in prod (standalone build, no Vite).

Deployment / infra

Everything deployment-related lives in infra/ (shared multi-stage Node 24 Dockerfile). Two environments: dev (self-contained) and prod (integrated with the pi-infra core, using its Postgres/pgbouncer and Grafana). See details in AGENTS.md.

Code standards

Prettier 3 (4 spaces, no ;, single quotes, trailingComma: all, printWidth 120) plus the structure conventions described in AGENTS.md. Run npm run format after any change.

Tag summary

Content type

Image

Digest

sha256:7ef04b9fc

Size

139 MB

Last updated

29 days ago

docker pull negrii/proxy-control