No description
  • Go 70%
  • Vue 18.3%
  • TypeScript 10.2%
  • Shell 0.9%
  • Makefile 0.2%
  • Other 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Tronax e5a0427a30
Sicherheitsfixes, i18n (DE/EN/ES) & Produkt-Admin mit Treiber-Auswahl
Sicherheit/Bugs (Backend):
- Passwortwechsel widerruft jetzt alle Refresh-Sessions des Kontos
- Refresh-Flow prüft Kontosperre (gesperrte Accounts behalten keinen Zugang)
- Rate-Limiting für /auth (Login 10/min, Register 5/min, Refresh 60/min)
- CORS: ohne konfigurierte Origins kein Allow-All mehr (fail-closed)
- Katalogfilter ?type= wird jetzt in der DB angewandt (war wirkungslos)
- PATCH /me überschreibt nicht mehr ungesendete Felder mit Leerstrings
- Admin-Guthabenkorrektur schreibt adjustment-Transaktion ins Ledger
- Admins können sich nicht selbst degradieren (Lockout-Schutz)
- PDF-Download: Pfad-Check gegen konfiguriertes Verzeichnis, Quantifier-Fix
- Pagination geclampt (Overflow → negativer OFFSET vermieden)

Frontend:
- Doppeltes Unwrapping des {data,meta}-Envelops behoben: Listenansichten
  (Produkte, Orders, Tickets, ...) waren dadurch immer leer
- vue-i18n mit DE/EN/ES, Sprachumschalter in Header & Sidebar,
  Persistenz via localStorage, Auto-Erkennung der Browsersprache
- Produktformular: Typ-Auswahl (Gameserver/VPS/App), Driver-Dropdown aus
  den konfigurierten Treibern (/meta), Zyklus-Checkboxen, Starter-
  Templates pro Treiber für das JSON-Schema, Stock/Sort/Active-Felder
- Passwortwechsel meldet sich sauber ab (Session wird serverseitig beendet)
2026-09-13 14:45:36 +02:00
backend Sicherheitsfixes, i18n (DE/EN/ES) & Produkt-Admin mit Treiber-Auswahl 2026-09-13 14:45:36 +02:00
frontend Sicherheitsfixes, i18n (DE/EN/ES) & Produkt-Admin mit Treiber-Auswahl 2026-09-13 14:45:36 +02:00
scripts Mock-Treiber, JSONB-Mapper & Deployment-Fixes 2026-09-13 14:45:12 +02:00
.env.example Mock-Treiber, JSONB-Mapper & Deployment-Fixes 2026-09-13 14:45:12 +02:00
.gitignore Initialer Commit: Modulare Open-Source-Hosting-Plattform (Go + Vue 3) 2026-09-12 13:07:19 +02:00
docker-compose.yml Mock-Treiber, JSONB-Mapper & Deployment-Fixes 2026-09-13 14:45:12 +02:00
Makefile Initialer Commit: Modulare Open-Source-Hosting-Plattform (Go + Vue 3) 2026-09-12 13:07:19 +02:00
README.md Sicherheitsfixes, i18n (DE/EN/ES) & Produkt-Admin mit Treiber-Auswahl 2026-09-13 14:45:36 +02:00

Hostplate logo

Hostplate

Open-source modular hosting platform — game servers, VPS & managed apps.


Hostplate is a modular, highly configurable hosting platform for game servers, VPS and managed apps — Go backend + Vue 3 frontend. Every external system (virtualization, panel, payments, mail) is a swappable driver behind an interface: the core has zero hardcoded providers or billing logic.

Module path: The Go module lives at git.dietzlabs.net/Tronax/hostplate/backend (private Forgejo). Forking or self-hosting? Adapt it with a single search-and-replace of that path across backend/.

Private module usage

Because the module is private, tell the Go toolchain to skip the public proxy and checksum database, and authenticate against Forgejo:

go env -w GOPRIVATE=git.dietzlabs.net/*

# HTTPS (credentials via ~/.netrc):
#   machine git.dietzlabs.net login <user> password <token>
# SSH (recommended):
git config --global url."git@git.dietzlabs.net:".insteadOf "https://git.dietzlabs.net/"

Architecture

backend/
├── cmd/
│   ├── server/            HTTP API entrypoint (Gin)
│   └── worker/            asynq worker + periodic billing scheduler
├── internal/
│   ├── config/            viper-based config (YAML + HP_* env overrides)
│   ├── core/
│   │   ├── models/        typed domain entities (GORM mapping only)
│   │   ├── ports/         repository + queue interfaces (hexagonal)
│   │   └── apperr/        typed domain errors → structured JSON
│   ├── repository/        GORM adapters implementing the ports
│   ├── services/          business logic (auth, catalog, orders, billing, …)
│   ├── handlers/          HTTP layer (validation, structured responses)
│   ├── middleware/        JWT auth, RBAC, error handler, request log
│   ├── drivers/           concrete drivers:
│   │   ├── pterodactyl/   game servers (Application + Client API)
│   │   ├── proxmox/       KVM clones from templates, Cloud-Init
│   │   ├── hetzner/       Hetzner Cloud (hcloud-go)
│   │   ├── dockerportainer/ managed app stacks (Portainer/Docker)
│   │   ├── stripe/        hosted Checkout + signed webhooks
│   │   ├── paypal/        Orders v2 + webhook signature verification
│   │   └── wallet/        integrated prepaid balance gateway
│   ├── tasks/             asynq queue, worker handlers, cron scheduler
│   ├── bootstrap/         the only place that wires concrete drivers
│   └── database/          GORM + embedded golang-migrate migrations
└── pkg/
    ├── provisioner/       THE lifecycle interface + driver registry
    ├── payment/           THE gateway interface + registry
    ├── invoice/           maroto/v2 PDF rendering
    ├── mail/              smtp/log mailer drivers
    ├── httpx/             shared JSON REST client for drivers
    └── schema/            JSON-Schema subset validator (configurator)

frontend/                   Vue 3 + TypeScript + Pinia + Tailwind CSS 4
├── storefront              catalog, dynamic configurator, checkout
├── customer dashboard      services, power control, billing, tickets
└── admin center            products, customers, orders, system logs

Core interfaces

Provisioning (pkg/provisioner/provisioner.go) — full lifecycle: Create / Start / Stop / Restart / Suspend / Unsuspend / Terminate / GetStatus / GetMetrics. Products name their driver in products.provisioner_driver; static driver parameters live in the product's config_schema under the reserved key "driver", customer selections in services.config_data.

Payments (pkg/payment/gateway.go): GetID / CreatePaymentIntent / HandleWebhook / Refund. Webhooks are stored with a unique (gateway, event_id) pair before processing → duplicate deliveries are no-ops (idempotent at-least-once).

Billing model

Dual mode, toggled per product and platform-wide via features.*:

  • Prepaid wallet — deposits via Stripe/PayPal credit the balance; orders/renewals charge it atomically (users.wallet_balance guarded against overdraft, every movement recorded in transactions).
  • Subscription — gateway recurring payments extend service expiry via subscription.paid webhooks.

A periodic billing run (asynq cron) renews prepaid services, suspends expired ones after the grace period and terminates long-suspended ones.

Tax / invoicing

Configuration-driven, three modes:

  1. billing.small_business.enabled → no tax line, prints the configurable § 19 UStG notice (Kleinunternehmerregelung).
  2. EU reverse charge for B2B customers with VAT ID and foreign country.
  3. Regular taxation with billing.default_tax_rate.

Every paid order/renewal automatically produces a numbered PDF invoice (maroto/v2) stored under invoice.pdf_dir.

Internationalization

The web interface ships in English, German and Spanish. The language switcher lives in the public header and the dashboard sidebar; the choice is persisted in localStorage (hp_locale) and first-time visitors get their browser language when available. Translations live in frontend/src/i18n/locales/ — adding a language means adding one catalog file and registering it in frontend/src/i18n/index.ts.

Quick start (Docker)

cp .env.example .env            # set HP_AUTH_JWT_SECRET (openssl rand -hex 32)
docker compose up -d --build
# → storefront http://localhost:5173  (proxies /api to :8080)
# → login with auth.initial_admin credentials

Local development

# infrastructure only
docker compose up -d postgres redis

# backend (needs config: cp backend/config.example.yaml backend/config.yaml)
make api      # HTTP API on :8080
make worker   # job worker + billing scheduler

# frontend
cd frontend && pnpm install && pnpm dev   # Vite on :5173 (proxies /api)

Migrations run automatically on startup (database.automigrate); the SQL lives in backend/internal/database/sql/ (golang-migrate format).

Product configuration example

A game server product's config_schema shows how driver params and the storefront configurator combine:

{
  "type": "object",
  "required": ["location"],
  "properties": {
    "location": {
      "type": "string",
      "title": "Location",
      "enum": ["fsn1", "nbg1", "hel1"]
    },
    "memory": {
      "type": "integer",
      "title": "Memory (MB)",
      "minimum": 1024, "maximum": 16384, "default": 2048
    },
    "driver": {
      "egg": 5,
      "docker_image": "ghcr.io/pterodactyl/yolks:java_21",
      "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{.SERVER_JARFILE}}",
      "environment": { "SERVER_JARFILE": "server.jar" }
    }
  }
}

Customer-visible fields are validated server-side (pkg/schema) at order time; the driver object is passed straight to the provisioner.

API

REST under /api/v1 — all responses are {"data": …} or {"error": {"code", "message"}}. Highlights:

Area Endpoints
Auth POST /auth/register·login·refresh·logout, JWT access (15 min) + rotating refresh tokens with reuse detection
Catalog GET /products, GET /products/:slug
Orders POST /orders, POST /orders/:id/pay, GET /orders
Services GET /services, POST /services/:id/power, GET /services/:id/refresh
Wallet GET /wallet, POST /wallet/deposits, GET /wallet/transactions
Invoices GET /invoices, GET /invoices/:id/download (PDF)
Tickets POST/GET /tickets, POST /tickets/:id/messages
Webhooks POST /webhooks/payments/:gateway
Admin /admin/{stats,users,products,orders,services,logs} (RBAC: admin/support)

Status & roadmap

Implemented: all five mission phases — core scaffolding, auth/RBAC, driver interfaces, all seven drivers, dual billing with idempotent webhooks, invoicing with tax toggles, Vue 3 storefront/dashboard/admin.

Potential next steps:

  • WebSocket live console per driver (Pterodactyl client WS proxy)
  • 2FA login flow (secret column already in schema)
  • Stripe Billing native subscriptions for gateway-side recurring charges
  • Per-node resource scheduling / cluster status page