Compare commits
9 Commits
rapp/creat
...
feature/ve
| Author | SHA1 | Date | |
|---|---|---|---|
| 9650bd8b3c | |||
| 33112f6142 | |||
| fc00c3f627 | |||
| 436a81e2a8 | |||
| fa6780d032 | |||
| 19d41f3041 | |||
| a32e2da6c3 | |||
| 062b30e379 | |||
| b186c22bf2 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -4,3 +4,7 @@ apps/proxy
|
|||||||
apps/administration/*
|
apps/administration/*
|
||||||
apps/tools/app/*
|
apps/tools/app/*
|
||||||
env/secrets.env
|
env/secrets.env
|
||||||
|
infra/core/traefik/data/acme.json
|
||||||
|
infra/**/.env
|
||||||
|
infra/**/*.env.local
|
||||||
|
infra/secrets/*
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -7,3 +7,6 @@
|
|||||||
[submodule "apps/tools/invoiceninja/dockerfiles"]
|
[submodule "apps/tools/invoiceninja/dockerfiles"]
|
||||||
path = apps/tools/invoiceninja/dockerfiles
|
path = apps/tools/invoiceninja/dockerfiles
|
||||||
url = https://github.com/invoiceninja/dockerfiles.git
|
url = https://github.com/invoiceninja/dockerfiles.git
|
||||||
|
[submodule "apps/security/Eduroam Analyzer/asn-updater"]
|
||||||
|
path = apps/security/Eduroam Analyzer/asn-updater
|
||||||
|
url = https://gitea.mindboost.team/mindboost/education-flagger.git
|
||||||
|
|||||||
42
Makefile
Normal file
42
Makefile
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
SHELL := /bin/bash
|
||||||
|
|
||||||
|
# Environment selection
|
||||||
|
ENV ?= development
|
||||||
|
COMMON_ENV := infra/env/$(ENV)/common.env
|
||||||
|
|
||||||
|
# Helper to pass env files if present
|
||||||
|
define with_env
|
||||||
|
$(foreach f,$(1),$(if $(wildcard $(f)),--env-file $(f),))
|
||||||
|
endef
|
||||||
|
|
||||||
|
.PHONY: bootstrap proxy-up proxy-down proxy-logs app-up app-down app-logs ps
|
||||||
|
|
||||||
|
bootstrap:
|
||||||
|
@bash scripts/infra/bootstrap.sh
|
||||||
|
|
||||||
|
proxy-up:
|
||||||
|
@docker compose -f infra/core/traefik/docker-compose.yml $(call with_env,$(COMMON_ENV) infra/apps/traefik/.env) up -d
|
||||||
|
|
||||||
|
proxy-down:
|
||||||
|
@docker compose -f infra/core/traefik/docker-compose.yml $(call with_env,$(COMMON_ENV) infra/apps/traefik/.env) down
|
||||||
|
|
||||||
|
proxy-logs:
|
||||||
|
@docker compose -f infra/core/traefik/docker-compose.yml $(call with_env,$(COMMON_ENV) infra/apps/traefik/.env) logs -f
|
||||||
|
|
||||||
|
# Usage: make app-up APP=nextcloud
|
||||||
|
APP ?=
|
||||||
|
app-up:
|
||||||
|
@test -n "$(APP)" || (echo "APP not set. Example: make app-up APP=nextcloud" && exit 1)
|
||||||
|
@docker compose -f infra/apps/$(APP)/docker-compose.yml $(call with_env,$(COMMON_ENV) infra/apps/$(APP)/.env) up -d
|
||||||
|
|
||||||
|
app-down:
|
||||||
|
@test -n "$(APP)" || (echo "APP not set. Example: make app-down APP=nextcloud" && exit 1)
|
||||||
|
@docker compose -f infra/apps/$(APP)/docker-compose.yml $(call with_env,$(COMMON_ENV) infra/apps/$(APP)/.env) down
|
||||||
|
|
||||||
|
app-logs:
|
||||||
|
@test -n "$(APP)" || (echo "APP not set. Example: make app-logs APP=nextcloud" && exit 1)
|
||||||
|
@docker compose -f infra/apps/$(APP)/docker-compose.yml $(call with_env,$(COMMON_ENV) infra/apps/$(APP)/.env) logs -f
|
||||||
|
|
||||||
|
ps:
|
||||||
|
@docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Networks}}'
|
||||||
|
|
||||||
24
README.md
24
README.md
@@ -2,6 +2,30 @@
|
|||||||
|
|
||||||
All the software used and hosted by mindboost organized in containers.
|
All the software used and hosted by mindboost organized in containers.
|
||||||
|
|
||||||
|
## New Infra (v2) Overview
|
||||||
|
|
||||||
|
This repo now includes a modular, best‑practice infrastructure under `infra/` to make replication and selective deployment easy. It is centered on Traefik as the reverse proxy with automatic TLS via Let's Encrypt, environment layering, and pick‑what‑you‑need application stacks.
|
||||||
|
|
||||||
|
- Core: `infra/core/traefik` — Traefik with HTTPS (ACME), dashboard, and sane defaults
|
||||||
|
- Apps: `infra/apps/<service>` — self‑contained stacks (e.g., `nextcloud`)
|
||||||
|
- Env: `infra/env/<environment>/common.env` — environment defaults (dev/prod)
|
||||||
|
- Secrets: `infra/secrets/` — local secret storage (ignored by git)
|
||||||
|
- Make targets: top‑level `Makefile` to bootstrap, start proxy, and start apps
|
||||||
|
|
||||||
|
Quickstart
|
||||||
|
|
||||||
|
- Copy `infra/env/development/common.env` and adjust domains and ACME email.
|
||||||
|
- Create the shared proxy network and ACME storage: `make bootstrap`
|
||||||
|
- Start Traefik: `make proxy-up`
|
||||||
|
- Start a service, e.g. Nextcloud: `make app-up APP=nextcloud`
|
||||||
|
|
||||||
|
Notes
|
||||||
|
|
||||||
|
- Traefik dashboard is exposed at `TRAEFIK_DASHBOARD_DOMAIN` with optional basic auth.
|
||||||
|
- Services connect to an external `proxy` network for routing, plus their own internal network.
|
||||||
|
- Each app has its own `.env.example`; copy to `.env` and adjust.
|
||||||
|
- The legacy `apps/` structure remains as-is; new infra is additive and can coexist.
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
./apps/
|
./apps/
|
||||||
|
|||||||
11
apps/security/Eduroam Analyzer/.env.example
Normal file
11
apps/security/Eduroam Analyzer/.env.example
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# MaxMind (create a free GeoLite2 license key in your MaxMind account)
|
||||||
|
MAXMIND_LICENSE_KEY=your_maxmind_license_key
|
||||||
|
|
||||||
|
# PeeringDB (optional; reduces rate limits)
|
||||||
|
PDB_API_KEY=your_peeringdb_api_key
|
||||||
|
|
||||||
|
# existing Traefik/proxy network name (must already exist)
|
||||||
|
PROXY_NETWORK=proxy
|
||||||
|
|
||||||
|
# update interval in seconds (30 days)
|
||||||
|
UPDATE_INTERVAL_SECONDS=2592000
|
||||||
1
apps/security/Eduroam Analyzer/.gitignore
vendored
Normal file
1
apps/security/Eduroam Analyzer/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
16
apps/security/Eduroam Analyzer/Dockerfile
Normal file
16
apps/security/Eduroam Analyzer/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM golang:1.22-alpine AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY go.mod ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY main.go ./
|
||||||
|
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/asn-header-service
|
||||||
|
|
||||||
|
FROM alpine:3.20
|
||||||
|
RUN adduser -D -H -u 10001 app
|
||||||
|
USER 10001
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /out/asn-header-service /app/asn-header-service
|
||||||
|
EXPOSE 8080
|
||||||
|
ENV ADDR=:8080
|
||||||
|
ENTRYPOINT ["/app/asn-header-service"]
|
||||||
|
|
||||||
89
apps/security/Eduroam Analyzer/README.md
Normal file
89
apps/security/Eduroam Analyzer/README.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# NREN / ASN Detection Service
|
||||||
|
|
||||||
|
Dieses Projekt stellt einen **minimalen Microservice** bereit, um **Hochschul- und Forschungsnetzwerke (NRENs)** anhand der **Autonomous System Number (ASN)** zu erkennen.
|
||||||
|
|
||||||
|
Der Zweck ist es, **Anfragen aus Hochschulnetzen (z. B. eduroam)** zu identifizieren, um **Research-bezogene Services kostenlos oder bevorzugt bereitzustellen**.
|
||||||
|
|
||||||
|
Das System dient ausschließlich der **Netzwerk-Klassifikation** und **ersetzt keine Authentifizierung**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
- Erkennen, ob eine Anfrage aus einem **Hochschul- oder Forschungsnetz** stammt
|
||||||
|
- Bereitstellung eines **Header-Hinweises** für nachgelagerte Services
|
||||||
|
- Grundlage für Entscheidungen wie:
|
||||||
|
- kostenfreie Research-Features
|
||||||
|
- angepasste UI-Hinweise
|
||||||
|
- alternative Rate-Limits
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Funktionsweise (Kurzfassung)
|
||||||
|
|
||||||
|
```
|
||||||
|
Client
|
||||||
|
→ Traefik
|
||||||
|
→ ForwardAuth
|
||||||
|
→ ASN Detection Service
|
||||||
|
→ Header wird ergänzt
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Die Client-IP wird ermittelt
|
||||||
|
2. Die zugehörige ASN wird lokal nachgeschlagen
|
||||||
|
3. Die ASN wird mit einer NREN-ASN-Liste verglichen
|
||||||
|
4. Das Ergebnis wird als HTTP-Header zurückgegeben
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Datenquellen
|
||||||
|
|
||||||
|
- **GeoLite2 ASN (MaxMind)**
|
||||||
|
- kostenlos
|
||||||
|
- lokal
|
||||||
|
- monatliche Aktualisierung
|
||||||
|
|
||||||
|
- **NREN-ASN-Liste**
|
||||||
|
- abgeleitet aus PeeringDB
|
||||||
|
- Kategorie: `Research and Education`
|
||||||
|
- monatliche Aktualisierung
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bereitgestellte Header
|
||||||
|
|
||||||
|
| Header | Beschreibung |
|
||||||
|
|------|-------------|
|
||||||
|
| `X-ASN` | ASN der Client-IP |
|
||||||
|
| `X-ASN-ORG` | Organisation (optional) |
|
||||||
|
| `X-NREN` | `1` wenn ASN zu einem Hochschul-/Forschungsnetz gehört, sonst `0` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
Der Service wird als **Traefik ForwardAuth Middleware** eingebunden.
|
||||||
|
Die Header werden über `authResponseHeaders` an die eigentliche Anwendung weitergereicht.
|
||||||
|
|
||||||
|
Der Service ist **nicht öffentlich exponiert** und kommuniziert ausschließlich über das interne Docker-Netzwerk.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update-Strategie
|
||||||
|
|
||||||
|
- monatliche Aktualisierung der ASN-Daten
|
||||||
|
- keine externen Requests während der Anfrageverarbeitung
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Einschränkungen
|
||||||
|
|
||||||
|
- Die Erkennung ist **heuristisch**
|
||||||
|
- Es gibt **keine Garantie**, dass jede Anfrage aus einem Hochschulnetz erkannt wird
|
||||||
|
- Die Information darf **nicht als Authentifizierungsmerkmal** verwendet werden
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
|
||||||
|
Dieses Projekt ermöglicht eine **performante, datenschutzfreundliche Erkennung von Hochschulnetzen**, um **Research-Angebote kontextabhängig bereitzustellen**, ohne Nutzer zu identifizieren oder externe Dienste zur Laufzeit zu kontaktieren.
|
||||||
89
apps/security/Eduroam Analyzer/README_technical.md
Normal file
89
apps/security/Eduroam Analyzer/README_technical.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# NREN / ASN Detection Service
|
||||||
|
|
||||||
|
Dieses Projekt stellt einen **minimalen Microservice** bereit, um **Hochschul- und Forschungsnetzwerke (NRENs)** anhand der **Autonomous System Number (ASN)** zu erkennen.
|
||||||
|
|
||||||
|
Der Zweck ist es, **Anfragen aus Hochschulnetzen (z. B. eduroam)** zu identifizieren, um **Research-bezogene Services kostenlos oder bevorzugt bereitzustellen**.
|
||||||
|
|
||||||
|
Das System dient ausschließlich der **Netzwerk-Klassifikation** und **ersetzt keine Authentifizierung**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
- Erkennen, ob eine Anfrage aus einem **Hochschul- oder Forschungsnetz** stammt
|
||||||
|
- Bereitstellung eines **Header-Hinweises** für nachgelagerte Services
|
||||||
|
- Grundlage für Entscheidungen wie:
|
||||||
|
- kostenfreie Research-Features
|
||||||
|
- angepasste UI-Hinweise
|
||||||
|
- alternative Rate-Limits
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Funktionsweise (Kurzfassung)
|
||||||
|
|
||||||
|
```
|
||||||
|
Client
|
||||||
|
→ Traefik
|
||||||
|
→ ForwardAuth
|
||||||
|
→ ASN Detection Service
|
||||||
|
→ Header wird ergänzt
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Die Client-IP wird ermittelt
|
||||||
|
2. Die zugehörige ASN wird lokal nachgeschlagen
|
||||||
|
3. Die ASN wird mit einer NREN-ASN-Liste verglichen
|
||||||
|
4. Das Ergebnis wird als HTTP-Header zurückgegeben
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Datenquellen
|
||||||
|
|
||||||
|
- **GeoLite2 ASN (MaxMind)**
|
||||||
|
- kostenlos
|
||||||
|
- lokal
|
||||||
|
- monatliche Aktualisierung
|
||||||
|
|
||||||
|
- **NREN-ASN-Liste**
|
||||||
|
- abgeleitet aus PeeringDB
|
||||||
|
- Kategorie: `Research and Education`
|
||||||
|
- monatliche Aktualisierung
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bereitgestellte Header
|
||||||
|
|
||||||
|
| Header | Beschreibung |
|
||||||
|
|------|-------------|
|
||||||
|
| `X-ASN` | ASN der Client-IP |
|
||||||
|
| `X-ASN-ORG` | Organisation (optional) |
|
||||||
|
| `X-NREN` | `1` wenn ASN zu einem Hochschul-/Forschungsnetz gehört, sonst `0` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
Der Service wird als **Traefik ForwardAuth Middleware** eingebunden.
|
||||||
|
Die Header werden über `authResponseHeaders` an die eigentliche Anwendung weitergereicht.
|
||||||
|
|
||||||
|
Der Service ist **nicht öffentlich exponiert** und kommuniziert ausschließlich über das interne Docker-Netzwerk.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update-Strategie
|
||||||
|
|
||||||
|
- monatliche Aktualisierung der ASN-Daten
|
||||||
|
- keine externen Requests während der Anfrageverarbeitung
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Einschränkungen
|
||||||
|
|
||||||
|
- Die Erkennung ist **heuristisch**
|
||||||
|
- Es gibt **keine Garantie**, dass jede Anfrage aus einem Hochschulnetz erkannt wird
|
||||||
|
- Die Information darf **nicht als Authentifizierungsmerkmal** verwendet werden
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
|
||||||
|
Dieses Projekt ermöglicht eine **performante, datenschutzfreundliche Erkennung von Hochschulnetzen**, um **Research-Angebote kontextabhängig bereitzustellen**, ohne Nutzer zu identifizieren oder externe Dienste zur Laufzeit zu kontaktieren.
|
||||||
1
apps/security/Eduroam Analyzer/asn-updater
Submodule
1
apps/security/Eduroam Analyzer/asn-updater
Submodule
Submodule apps/security/Eduroam Analyzer/asn-updater added at d36a1e7655
36
apps/security/Eduroam Analyzer/docker-compose.yml
Normal file
36
apps/security/Eduroam Analyzer/docker-compose.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
services:
|
||||||
|
asn-header:
|
||||||
|
build: .
|
||||||
|
container_name: asn-header
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
MMDB_PATH: /data/GeoLite2-ASN.mmdb
|
||||||
|
ASN_LIST_PATH: /data/nren_asns.txt
|
||||||
|
ADDR: ":8080"
|
||||||
|
volumes:
|
||||||
|
- asn_data:/data:ro
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
|
||||||
|
asn-updater:
|
||||||
|
build: ./asn-updater
|
||||||
|
container_name: asn-updater
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
OUT_DIR: /data
|
||||||
|
PDB_INFO_TYPE: "Research and Education"
|
||||||
|
INTERVAL_SECONDS: "${UPDATE_INTERVAL_SECONDS}"
|
||||||
|
volumes:
|
||||||
|
- asn_data:/data
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
name: ${PROXY_NETWORK}
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
asn_data:
|
||||||
6
apps/security/Eduroam Analyzer/go.mod
Normal file
6
apps/security/Eduroam Analyzer/go.mod
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module asn-header-service
|
||||||
|
|
||||||
|
go 1.22
|
||||||
|
|
||||||
|
require github.com/oschwald/maxminddb-golang v1.13.1
|
||||||
|
|
||||||
158
apps/security/Eduroam Analyzer/main.go
Normal file
158
apps/security/Eduroam Analyzer/main.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/oschwald/maxminddb-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
type asnRecord struct {
|
||||||
|
ASN uint `maxminddb:"autonomous_system_number"`
|
||||||
|
Org string `maxminddb:"autonomous_system_organization"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type server struct {
|
||||||
|
db *maxminddb.Reader
|
||||||
|
nrenASNs map[uint]struct{}
|
||||||
|
ready atomic.Bool
|
||||||
|
versionTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadASNSet(path string) (map[uint]struct{}, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
set := make(map[uint]struct{}, 4096)
|
||||||
|
sc := bufio.NewScanner(f)
|
||||||
|
for sc.Scan() {
|
||||||
|
line := strings.TrimSpace(sc.Text())
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v, err := strconv.ParseUint(line, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
set[uint(v)] = struct{}{}
|
||||||
|
}
|
||||||
|
return set, sc.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstForwardedFor(r *http.Request) string {
|
||||||
|
xff := r.Header.Get("X-Forwarded-For")
|
||||||
|
if xff == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
parts := strings.Split(xff, ",")
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(parts[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func remoteIP(r *http.Request) string {
|
||||||
|
// Prefer XFF (because Traefik is proxy)
|
||||||
|
ip := firstForwardedFor(r)
|
||||||
|
if ip != "" {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||||
|
if err == nil {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
return r.RemoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) authHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !s.ready.Load() {
|
||||||
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ipStr := remoteIP(r)
|
||||||
|
parsed := net.ParseIP(ipStr)
|
||||||
|
if parsed == nil {
|
||||||
|
// Always 200: we enrich, not block
|
||||||
|
w.Header().Set("X-NREN", "0")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rec asnRecord
|
||||||
|
if err := s.db.Lookup(parsed, &rec); err != nil || rec.ASN == 0 {
|
||||||
|
w.Header().Set("X-NREN", "0")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("X-ASN", strconv.FormatUint(uint64(rec.ASN), 10))
|
||||||
|
if rec.Org != "" {
|
||||||
|
// optional: keep it short; some org strings can be long
|
||||||
|
w.Header().Set("X-ASN-ORG", rec.Org)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := s.nrenASNs[rec.ASN]
|
||||||
|
if ok {
|
||||||
|
w.Header().Set("X-NREN", "1")
|
||||||
|
} else {
|
||||||
|
w.Header().Set("X-NREN", "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
w.Header().Set("X-Service", s.versionTag)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mmdbPath := getenv("MMDB_PATH", "/data/GeoLite2-ASN.mmdb")
|
||||||
|
asnListPath := getenv("ASN_LIST_PATH", "/data/nren_asns.txt")
|
||||||
|
addr := getenv("ADDR", ":8080")
|
||||||
|
version := getenv("VERSION_TAG", "asn-header-service")
|
||||||
|
|
||||||
|
db, err := maxminddb.Open(mmdbPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to open mmdb: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
set, err := loadASNSet(asnListPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to load asn list: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &server{db: db, nrenASNs: set, versionTag: version}
|
||||||
|
s.ready.Store(true)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/auth", s.authHandler)
|
||||||
|
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: mux,
|
||||||
|
ReadHeaderTimeout: 2 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("listening on %s (asn_count=%d)", addr, len(set))
|
||||||
|
log.Fatal(srv.ListenAndServe())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getenv(k, def string) string {
|
||||||
|
v := strings.TrimSpace(os.Getenv(k))
|
||||||
|
if v == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
114
dev-fpm.docker-compose.yml
Normal file
114
dev-fpm.docker-compose.yml
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
mariadb_webapp_dev:
|
||||||
|
image: docker.io/bitnami/mariadb:11.1
|
||||||
|
container_name: ${DEV_COMPOSE_PREFIX:-dev}-mariadb
|
||||||
|
hostname: ${DEV_DB_HOST:-mariadb-webapp-dev}
|
||||||
|
environment:
|
||||||
|
MARIADB_USER: ${MARIADB_USER}
|
||||||
|
MARIADB_DATABASE: ${MARIADB_DATABASE}
|
||||||
|
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
|
||||||
|
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
|
||||||
|
networks:
|
||||||
|
- dev_backend
|
||||||
|
volumes:
|
||||||
|
- mindboost_mariadb_data_dev:/var/lib/mysql
|
||||||
|
|
||||||
|
laravel-redis-dev:
|
||||||
|
image: redis:alpine
|
||||||
|
container_name: ${DEV_COMPOSE_PREFIX:-dev}-redis
|
||||||
|
hostname: ${DEV_REDIS_HOST:-laravel-redis-dev}
|
||||||
|
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
|
||||||
|
networks:
|
||||||
|
- dev_backend
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./data/redis-dev:/data
|
||||||
|
|
||||||
|
laravel_backend_dev:
|
||||||
|
image: ${BACKEND_IMAGE}
|
||||||
|
container_name: ${DEV_COMPOSE_PREFIX:-dev}-backend
|
||||||
|
environment:
|
||||||
|
APP_ENV: ${APP_ENV:-production}
|
||||||
|
APP_NAME: ${APP_NAME:-Mindboost Backend Dev}
|
||||||
|
APP_URL: https://${DEV_BACKEND_DOMAIN}
|
||||||
|
FRONTEND_URL: https://${DEV_FRONTEND_DOMAIN}
|
||||||
|
DB_CONNECTION: mysql
|
||||||
|
DB_HOST: ${DEV_DB_HOST:-mariadb-webapp-dev}
|
||||||
|
DB_PORT: ${DB_PORT:-3306}
|
||||||
|
DB_DATABASE: ${MARIADB_DATABASE}
|
||||||
|
DB_USERNAME: ${MARIADB_USER}
|
||||||
|
DB_PASSWORD: ${MARIADB_PASSWORD}
|
||||||
|
REDIS_HOST: ${DEV_REDIS_HOST:-laravel-redis-dev}
|
||||||
|
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||||
|
REDIS_PORT: ${REDIS_PORT:-6379}
|
||||||
|
CACHE_DRIVER: redis
|
||||||
|
QUEUE_CONNECTION: redis
|
||||||
|
SESSION_DRIVER: redis
|
||||||
|
volumes:
|
||||||
|
- ${BACKEND_CODE_PATH:-./apps/backend/src}:/app
|
||||||
|
- ${BACKEND_PUBLIC_PATH:-./apps/backend/src/public}:/var/www/public
|
||||||
|
- ${BACKEND_ENV_FILE:-./env/development/.env.backend}:/var/www/.env
|
||||||
|
- ./logs/backend-dev:/var/www/storage/logs
|
||||||
|
depends_on:
|
||||||
|
- mariadb_webapp_dev
|
||||||
|
- laravel-redis-dev
|
||||||
|
networks:
|
||||||
|
- dev_backend
|
||||||
|
|
||||||
|
laravel-nginx-dev:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: ${DEV_COMPOSE_PREFIX:-dev}-nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx:/etc/nginx/conf.d:ro
|
||||||
|
- ${BACKEND_PUBLIC_PATH:-./apps/backend/src/public}:/var/www/public:ro
|
||||||
|
depends_on:
|
||||||
|
- laravel_backend_dev
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.dev_backend_http.entrypoints=web"
|
||||||
|
- "traefik.http.routers.dev_backend_http.rule=Host(`${DEV_BACKEND_DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.dev_backend_http.middlewares=traefik-https-redirect"
|
||||||
|
- "traefik.http.routers.dev_backend_https.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.dev_backend_https.rule=Host(`${DEV_BACKEND_DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.dev_backend_https.tls=true"
|
||||||
|
- "traefik.http.routers.dev_backend_https.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
|
||||||
|
- "traefik.http.routers.dev_backend_https.service=dev_backend_service"
|
||||||
|
- "traefik.http.services.dev_backend_service.loadbalancer.server.port=80"
|
||||||
|
- "traefik.docker.network=${TRAEFIK_NETWORK}"
|
||||||
|
networks:
|
||||||
|
- dev_backend
|
||||||
|
- proxy
|
||||||
|
|
||||||
|
nuxt_frontend_dev:
|
||||||
|
image: ${NUXT_IMAGE}
|
||||||
|
container_name: ${DEV_COMPOSE_PREFIX:-dev}-frontend
|
||||||
|
environment:
|
||||||
|
VUE_APP_BACKEND_HOST_ADDRESS: https://${DEV_BACKEND_DOMAIN}
|
||||||
|
NUXT_PUBLIC_BACKEND_URL: https://${DEV_BACKEND_DOMAIN}
|
||||||
|
networks:
|
||||||
|
- dev_backend
|
||||||
|
- proxy
|
||||||
|
depends_on:
|
||||||
|
- laravel_backend_dev
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.dev_frontend_http.entrypoints=web"
|
||||||
|
- "traefik.http.routers.dev_frontend_http.rule=Host(`${DEV_FRONTEND_DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.dev_frontend_http.middlewares=traefik-https-redirect"
|
||||||
|
- "traefik.http.routers.dev_frontend_https.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.dev_frontend_https.rule=Host(`${DEV_FRONTEND_DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.dev_frontend_https.tls=true"
|
||||||
|
- "traefik.http.routers.dev_frontend_https.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
|
||||||
|
- "traefik.http.services.dev_frontend_https.loadbalancer.server.port=${VUE_INTERNAL_PORT}"
|
||||||
|
- "traefik.docker.network=${TRAEFIK_NETWORK}"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dev_backend:
|
||||||
|
driver: bridge
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mindboost_mariadb_data_dev:
|
||||||
29
docs/infra.md
Normal file
29
docs/infra.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
Infrastructure v2
|
||||||
|
|
||||||
|
Goals
|
||||||
|
|
||||||
|
- Modular stacks you can pick individually (Nextcloud, etc.)
|
||||||
|
- Unified reverse proxy (Traefik) with automatic TLS
|
||||||
|
- Clear env layering and git‑ignored secrets
|
||||||
|
- Simple Make targets for a smooth DX
|
||||||
|
|
||||||
|
Layout
|
||||||
|
|
||||||
|
- infra/core/traefik: Traefik compose + static/dynamic config
|
||||||
|
- infra/apps/<service>: Self‑contained compose stacks and .env.example
|
||||||
|
- infra/env/<env>/common.env: Shared environment defaults per environment
|
||||||
|
- infra/secrets: Local secret files (ignored)
|
||||||
|
- scripts/infra/bootstrap.sh: Creates proxy network and ACME storage
|
||||||
|
|
||||||
|
Usage
|
||||||
|
|
||||||
|
1. cp infra/env/development/common.env infra/env/development/common.env (adjust values)
|
||||||
|
2. make bootstrap
|
||||||
|
3. make proxy-up
|
||||||
|
4. make app-up APP=nextcloud
|
||||||
|
|
||||||
|
Security
|
||||||
|
|
||||||
|
- Do not commit real secrets. Place them in local `.env` files or secret managers.
|
||||||
|
- Optionally protect Traefik dashboard with basic auth via `TRAEFIK_BASIC_AUTH_USERS`.
|
||||||
|
|
||||||
14
infra/apps/nextcloud/.env.example
Normal file
14
infra/apps/nextcloud/.env.example
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Nextcloud stack configuration
|
||||||
|
|
||||||
|
NEXTCLOUD_DOMAIN=cloud.example.com
|
||||||
|
|
||||||
|
# Database
|
||||||
|
NEXTCLOUD_DB_NAME=nextcloud
|
||||||
|
NEXTCLOUD_DB_USER=nextcloud
|
||||||
|
NEXTCLOUD_DB_PASSWORD=changeMe
|
||||||
|
NEXTCLOUD_DB_ROOT_PASSWORD=changeMeRoot
|
||||||
|
|
||||||
|
# PHP tuning
|
||||||
|
NEXTCLOUD_PHP_MEMORY_LIMIT=512M
|
||||||
|
NEXTCLOUD_PHP_UPLOAD_LIMIT=1024M
|
||||||
|
|
||||||
13
infra/apps/nextcloud/README.md
Normal file
13
infra/apps/nextcloud/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Nextcloud Stack
|
||||||
|
|
||||||
|
Env vars required (copy .env.example to .env and adjust):
|
||||||
|
|
||||||
|
- NEXTCLOUD_DOMAIN: public domain for Nextcloud
|
||||||
|
- NEXTCLOUD_DB_NAME, NEXTCLOUD_DB_USER, NEXTCLOUD_DB_PASSWORD, NEXTCLOUD_DB_ROOT_PASSWORD
|
||||||
|
- Optional: NEXTCLOUD_PHP_MEMORY_LIMIT, NEXTCLOUD_PHP_UPLOAD_LIMIT
|
||||||
|
|
||||||
|
Usage
|
||||||
|
|
||||||
|
- Ensure the Traefik proxy stack is up and the external `${TRAEFIK_NETWORK}` network exists.
|
||||||
|
- Run: `docker compose --env-file ../../env/${ENV}/common.env --env-file ./.env -f docker-compose.yml up -d`
|
||||||
|
|
||||||
70
infra/apps/nextcloud/docker-compose.yml
Normal file
70
infra/apps/nextcloud/docker-compose.yml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
services:
|
||||||
|
nextcloud:
|
||||||
|
image: nextcloud:28-apache
|
||||||
|
container_name: ${INFRASTRUCTURE_LABEL:-stack}-nextcloud
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
environment:
|
||||||
|
- NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_DOMAIN}
|
||||||
|
- OVERWRITEHOST=${NEXTCLOUD_DOMAIN}
|
||||||
|
- OVERWRITEPROTOCOL=https
|
||||||
|
- OVERWRITECLIURL=https://${NEXTCLOUD_DOMAIN}
|
||||||
|
- REDIS_HOST=redis
|
||||||
|
- MYSQL_HOST=db
|
||||||
|
- MYSQL_DATABASE=${NEXTCLOUD_DB_NAME:-nextcloud}
|
||||||
|
- MYSQL_USER=${NEXTCLOUD_DB_USER:-nextcloud}
|
||||||
|
- MYSQL_PASSWORD=${NEXTCLOUD_DB_PASSWORD}
|
||||||
|
- PHP_MEMORY_LIMIT=${NEXTCLOUD_PHP_MEMORY_LIMIT:-512M}
|
||||||
|
- PHP_UPLOAD_LIMIT=${NEXTCLOUD_PHP_UPLOAD_LIMIT:-1024M}
|
||||||
|
volumes:
|
||||||
|
- nextcloud_data:/var/www/html
|
||||||
|
networks:
|
||||||
|
- nextcloud
|
||||||
|
- proxy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.nextcloud.rule=Host(`${NEXTCLOUD_DOMAIN}`)
|
||||||
|
- traefik.http.routers.nextcloud.entrypoints=websecure
|
||||||
|
- traefik.http.routers.nextcloud.tls=true
|
||||||
|
- traefik.http.routers.nextcloud.tls.certresolver=letsencrypt
|
||||||
|
- traefik.http.services.nextcloud.loadbalancer.server.port=80
|
||||||
|
- traefik.http.routers.nextcloud.middlewares=security-headers@file
|
||||||
|
- traefik.docker.network=${TRAEFIK_NETWORK:-proxy}
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: mariadb:11
|
||||||
|
container_name: ${INFRASTRUCTURE_LABEL:-stack}-nextcloud-db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- MYSQL_ROOT_PASSWORD=${NEXTCLOUD_DB_ROOT_PASSWORD}
|
||||||
|
- MYSQL_DATABASE=${NEXTCLOUD_DB_NAME:-nextcloud}
|
||||||
|
- MYSQL_USER=${NEXTCLOUD_DB_USER:-nextcloud}
|
||||||
|
- MYSQL_PASSWORD=${NEXTCLOUD_DB_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
- nextcloud
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: ${INFRASTRUCTURE_LABEL:-stack}-nextcloud-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
command: redis-server --appendonly yes
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- nextcloud
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
nextcloud_data:
|
||||||
|
db_data:
|
||||||
|
redis_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
nextcloud:
|
||||||
|
name: ${INFRASTRUCTURE_LABEL:-stack}-nextcloud
|
||||||
|
|
||||||
47
infra/core/traefik/docker-compose.yml
Normal file
47
infra/core/traefik/docker-compose.yml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
image: traefik:v2.11
|
||||||
|
container_name: ${INFRASTRUCTURE_LABEL:-stack}-traefik
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
- --providers.file.directory=/etc/traefik/dynamic
|
||||||
|
- --providers.file.watch=true
|
||||||
|
- --providers.docker=true
|
||||||
|
- --providers.docker.exposedbydefault=false
|
||||||
|
- --providers.docker.network=${TRAEFIK_NETWORK:-proxy}
|
||||||
|
- --entrypoints.web.address=:80
|
||||||
|
- --entrypoints.websecure.address=:443
|
||||||
|
- --certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}
|
||||||
|
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
|
||||||
|
- --certificatesresolvers.letsencrypt.acme.httpchallenge=true
|
||||||
|
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
|
||||||
|
- --api.dashboard=true
|
||||||
|
- --log.level=${TRAEFIK_LOG_LEVEL:-INFO}
|
||||||
|
- --accesslog=true
|
||||||
|
ports:
|
||||||
|
- ${TRAEFIK_HTTP_PORT:-80}:80
|
||||||
|
- ${TRAEFIK_HTTPS_PORT:-443}:443
|
||||||
|
environment:
|
||||||
|
- TZ=${TZ:-UTC}
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
- ./traefik.yml:/etc/traefik/traefik.yml:ro
|
||||||
|
- ./dynamic:/etc/traefik/dynamic:ro
|
||||||
|
- ./data:/letsencrypt
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DASHBOARD_DOMAIN}`)
|
||||||
|
- traefik.http.routers.traefik.entrypoints=websecure
|
||||||
|
- traefik.http.routers.traefik.tls=true
|
||||||
|
- traefik.http.routers.traefik.tls.certresolver=letsencrypt
|
||||||
|
- traefik.http.routers.traefik.service=api@internal
|
||||||
|
- traefik.docker.network=${TRAEFIK_NETWORK:-proxy}
|
||||||
|
# Optional: protect dashboard with basic auth if TRAEFIK_BASIC_AUTH_USERS is set
|
||||||
|
- traefik.http.routers.traefik.middlewares=dashboard-basicauth@file
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
|
||||||
25
infra/core/traefik/dynamic/middlewares.yml
Normal file
25
infra/core/traefik/dynamic/middlewares.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
redirect-to-https:
|
||||||
|
redirectScheme:
|
||||||
|
scheme: https
|
||||||
|
permanent: true
|
||||||
|
|
||||||
|
security-headers:
|
||||||
|
headers:
|
||||||
|
frameDeny: true
|
||||||
|
contentTypeNosniff: true
|
||||||
|
browserXssFilter: true
|
||||||
|
referrerPolicy: no-referrer-when-downgrade
|
||||||
|
stsSeconds: 31536000
|
||||||
|
stsIncludeSubdomains: true
|
||||||
|
stsPreload: true
|
||||||
|
|
||||||
|
dashboard-basicauth:
|
||||||
|
basicAuth:
|
||||||
|
users:
|
||||||
|
# Provide users via env TRAEFIK_BASIC_AUTH_USERS, format: user:hashedpassword
|
||||||
|
# Example to generate: htpasswd -nbB admin 'yourpassword'
|
||||||
|
# If env is empty, you can comment this middleware out from labels
|
||||||
|
- ${TRAEFIK_BASIC_AUTH_USERS:-}
|
||||||
|
|
||||||
6
infra/core/traefik/dynamic/tls.yml
Normal file
6
infra/core/traefik/dynamic/tls.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
tls:
|
||||||
|
options:
|
||||||
|
default:
|
||||||
|
minVersion: VersionTLS12
|
||||||
|
sniStrict: true
|
||||||
|
|
||||||
30
infra/core/traefik/traefik.yml
Normal file
30
infra/core/traefik/traefik.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
api:
|
||||||
|
dashboard: true
|
||||||
|
|
||||||
|
providers:
|
||||||
|
docker:
|
||||||
|
exposedByDefault: false
|
||||||
|
network: ${TRAEFIK_NETWORK:-proxy}
|
||||||
|
file:
|
||||||
|
directory: /etc/traefik/dynamic
|
||||||
|
watch: true
|
||||||
|
|
||||||
|
entryPoints:
|
||||||
|
web:
|
||||||
|
address: ":80"
|
||||||
|
websecure:
|
||||||
|
address: ":443"
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: ${TRAEFIK_LOG_LEVEL:-INFO}
|
||||||
|
|
||||||
|
accessLog: {}
|
||||||
|
|
||||||
|
certificatesResolvers:
|
||||||
|
letsencrypt:
|
||||||
|
acme:
|
||||||
|
email: ${ACME_EMAIL}
|
||||||
|
storage: /letsencrypt/acme.json
|
||||||
|
httpChallenge:
|
||||||
|
entryPoint: web
|
||||||
|
|
||||||
14
infra/env/common.env.example
vendored
Normal file
14
infra/env/common.env.example
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Global/defaults
|
||||||
|
INFRASTRUCTURE_LABEL=mindboost
|
||||||
|
TZ=UTC
|
||||||
|
|
||||||
|
# Traefik / proxy
|
||||||
|
TRAEFIK_NETWORK=proxy
|
||||||
|
TRAEFIK_HTTP_PORT=80
|
||||||
|
TRAEFIK_HTTPS_PORT=443
|
||||||
|
TRAEFIK_LOG_LEVEL=INFO
|
||||||
|
ACME_EMAIL=you@example.com
|
||||||
|
TRAEFIK_DASHBOARD_DOMAIN=traefik.example.com
|
||||||
|
# Optional basic auth users for dashboard (format: user:hashed)
|
||||||
|
#TRAEFIK_BASIC_AUTH_USERS=admin:$2y$05$...
|
||||||
|
|
||||||
11
infra/env/development/common.env
vendored
Normal file
11
infra/env/development/common.env
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Development defaults (copy to production and adjust as needed)
|
||||||
|
INFRASTRUCTURE_LABEL=dev
|
||||||
|
TZ=UTC
|
||||||
|
|
||||||
|
TRAEFIK_NETWORK=proxy
|
||||||
|
TRAEFIK_HTTP_PORT=80
|
||||||
|
TRAEFIK_HTTPS_PORT=443
|
||||||
|
TRAEFIK_LOG_LEVEL=INFO
|
||||||
|
ACME_EMAIL=dev@example.com
|
||||||
|
TRAEFIK_DASHBOARD_DOMAIN=traefik.local
|
||||||
|
|
||||||
27
nginx/dev-backend.conf
Normal file
27
nginx/dev-backend.conf
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /var/www/public;
|
||||||
|
index index.php index.html;
|
||||||
|
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
add_header X-Frame-Options SAMEORIGIN;
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
fastcgi_pass laravel_backend_dev:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.(?!well-known).* {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
scripts/infra/bootstrap.sh
Normal file
24
scripts/infra/bootstrap.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Create external proxy network if it doesn't exist and prepare Traefik state
|
||||||
|
|
||||||
|
NETWORK_NAME=${TRAEFIK_NETWORK:-proxy}
|
||||||
|
ACME_FILE="infra/core/traefik/data/acme.json"
|
||||||
|
|
||||||
|
echo "[bootstrap] Ensuring external network '${NETWORK_NAME}' exists..."
|
||||||
|
if ! docker network ls --format '{{.Name}}' | grep -qx "${NETWORK_NAME}"; then
|
||||||
|
docker network create "${NETWORK_NAME}"
|
||||||
|
echo "[bootstrap] Created network '${NETWORK_NAME}'."
|
||||||
|
else
|
||||||
|
echo "[bootstrap] Network '${NETWORK_NAME}' already exists."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[bootstrap] Ensuring ACME storage exists with correct permissions..."
|
||||||
|
mkdir -p "$(dirname "${ACME_FILE}")"
|
||||||
|
touch "${ACME_FILE}"
|
||||||
|
chmod 600 "${ACME_FILE}"
|
||||||
|
echo "[bootstrap] ACME storage ready at ${ACME_FILE}."
|
||||||
|
|
||||||
|
echo "[bootstrap] Done."
|
||||||
|
|
||||||
Reference in New Issue
Block a user