Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Deploy Docs to GitHub Pages

on:
push:
tags:
- 'docs*'
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Generate proto code
run: |
docker run --rm \
--volume "${{ github.workspace }}:/workspace" \
--workdir /workspace \
bufbuild/buf:latest generate

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install dependencies
working-directory: docs/discopanel
run: npm ci

- name: Build docs
working-directory: docs/discopanel
run: npm run build

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/discopanel/dist

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
uses: actions/download-artifact@v4
with:
name: api-schema-yaml
path: web/discopanel/static/schemav1.yaml
path: web/discopanel/static

- name: Set up Node.js
uses: actions/setup-node@v4
Expand Down Expand Up @@ -161,7 +161,7 @@ jobs:
CC: ${{ matrix.cc || '' }}
CXX: ${{ matrix.cxx || '' }}
run: |
go build -tags embed -o discopanel-${{ matrix.suffix }} ./cmd/discopanel
go build -o discopanel-${{ matrix.suffix }} ./cmd/discopanel

- name: Compress binary (non-Windows)
if: matrix.goos != 'windows'
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ sandbox/
build/
discopanel
!proto/discopanel
!docs/discopanel
web/discopanel/src/lib/proto/gnostic/
!cmd/discopanel
!web/discopanel
data/
Expand Down
27 changes: 19 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.PHONY: dev prod clean build build-frontend run deps test fmt lint check help kill-dev image dev-docker modules proto proto-clean proto-lint proto-format proto-breaking gen
.PHONY: dev prod clean build build-frontend run deps test fmt lint check help kill-dev image dev-docker dev-auth modules proto proto-clean proto-lint proto-format proto-breaking gen dev-docs

DATA_DIR := ./data
DOCKER_DATA_DIR := /tmp/discopanel
DB_FILE := $(DATA_DIR)/discopanel.db
FRONTEND_DIR := web/discopanel
DISCOPANEL_BIN := build/discopanel
Expand Down Expand Up @@ -28,17 +29,22 @@ run:

dev: clean run

# Build and run docker container for local dev
dev-docker:
@echo "Building and running Docker container for development..."
docker compose -f docker-compose.dev.yaml build --no-cache
docker compose -f docker-compose.dev.yaml up

# Build and run with OIDC provider (Keycloak)
dev-auth-%: clean
docker compose -f oidc/$*/docker-compose.yaml down -v --remove-orphans
@docker run --rm -v /tmp:/tmp alpine rm -rf /tmp/discopanel
@echo "Building and running with OIDC provider..."
docker compose -f oidc/$*/docker-compose.yaml build --no-cache
docker compose -f oidc/$*/docker-compose.yaml up

dev-docs:
cd docs/discopanel && npm run dev

# Production build and run
prod: build-frontend
@echo "Building for production..."
@mkdir -p $(DATA_DIR)
go build -tags embed -o $(DISCOPANEL_BIN) cmd/discopanel/main.go
go build -o $(DISCOPANEL_BIN) cmd/discopanel/main.go

# Build frontend for production
build-frontend:
Expand Down Expand Up @@ -90,6 +96,10 @@ clean:
echo "Removing data directory..."; \
rm -rf $(DATA_DIR); \
fi
@if [ -d "$(DOCKER_DATA_DIR)" ]; then \
echo "Removing docker data directory..."; \
docker run --rm -v $(DOCKER_DATA_DIR):/tmp alpine sh -c 'rm -rf /tmp/*'; \
fi
@if [ -f "$(DISCOPANEL_BIN)" ]; then \
echo "Removing backend binary..."; \
rm -f $(DISCOPANEL_BIN); \
Expand Down Expand Up @@ -179,6 +189,7 @@ help:
@echo " make prod - Build and run in production mode"
@echo " make image - Build and push Docker image to :dev tag"
@echo " make dev-docker - Build and run Docker container locally (no cache)"
@echo " make dev-auth - Build and run with OIDC provider (Keycloak)"
@echo " make modules - Build and push all module Docker images"
@echo " make clean - Remove data directory and build artifacts"
@echo " make kill-dev - Kill any orphaned dev processes"
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,22 @@ make gen
docker run --rm -v "$(pwd):/workspace" -w /workspace bufbuild/buf:latest generate
```

## Docs

The doc site lives in `docs/discopanel/` and is built with [Astro](https://astro.build) + [Starlight](https://starlight.astro.build). Deployed to [docs.discopanel.app](https://docs.discopanel.app).

### Contributing, building, & viewing locally

```bash
make dev-docs
```

Open `http://localhost:4321`.

### Reporting doc issues

If something is wrong or missing, open an issue on [GitHub](https://github.com/nickheyer/discopanel/issues) or mention it in [Discord](https://discord.gg/6Z9yKTbsrP).

## License

MIT. Do whatever you want with it, just don't blame me when it breaks.
Expand Down
8 changes: 7 additions & 1 deletion buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ plugins:
opt:
- path=schemav1.yaml

- remote: buf.build/community/sudorandom-connect-openapi:v0.21.3
out: docs/discopanel/public
opt:
- path=schemav1.yaml

# Generate Go code with protoc-gen-go
- remote: buf.build/protocolbuffers/go
out: pkg/proto
Expand All @@ -22,4 +27,5 @@ plugins:
out: web/discopanel/src/lib/proto
opt:
- target=ts
- import_extension=none
- import_extension=none
include_imports: true
7 changes: 7 additions & 0 deletions buf.lock
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
# Generated by buf. DO NOT EDIT.
version: v2
deps:
- name: buf.build/gnostic/gnostic
commit: 087bc8072ce44e339f213209e4d57bf0
digest: b5:c4eebcd04bc2fdd5dd0b8d695eb419682a650b600cdb56ff2ed61208a24603e0eb1b8ae0d467925c69a24bde6d322f3c4112bd2b8efdd682d8c3128384cdac9a
- name: buf.build/googleapis/googleapis
commit: 004180b77378443887d3b55cabc00384
digest: b5:e8f475fe3330f31f5fd86ac689093bcd274e19611a09db91f41d637cb9197881ce89882b94d13a58738e53c91c6e4bae7dc1feba85f590164c975a89e25115dc
3 changes: 2 additions & 1 deletion buf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ breaking:
- EXTENSION_NO_DELETE
- FIELD_SAME_DEFAULT
deps:
- buf.build/googleapis/googleapis
- buf.build/googleapis/googleapis
- buf.build/gnostic/gnostic
26 changes: 2 additions & 24 deletions cmd/discopanel/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,36 +56,14 @@ func main() {
}
}

// Initialize storage with connection pooling
store, err := storage.NewSQLiteStore(cfg.Database.Path, storage.DBConfig{
MaxOpenConns: cfg.Database.MaxConnections,
MaxIdleConns: cfg.Database.MaxIdleConns,
ConnMaxLifetime: time.Duration(cfg.Database.ConnMaxLifetime) * time.Second,
})
// Initialize storage w/ migrations and seeding
store, err := storage.NewSQLiteStore(cfg)
if err != nil {
log.Fatal("Failed to initialize storage: %v", err)
}
defer store.Close()

// Initialize global settings with config defaults if they don't exist
ctx := context.Background()
_, isNew, err := store.GetGlobalSettings(ctx)
if err != nil {
log.Fatal("Failed to get global settings: %v", err)
}

// Check if global settings are empty (just created) and populate with config defaults
if isNew || cfg.Minecraft.ResetGlobal {
// Copy the config defaults to global settings
globalConfig := config.LoadGlobalServerConfig(cfg)
globalConfig.ID = storage.GlobalSettingsID
globalConfig.ServerID = storage.GlobalSettingsID

if err := store.UpdateGlobalSettings(ctx, &globalConfig); err != nil {
log.Fatal("Failed to initialize global settings: %v", err)
}
log.Info("Initialized global settings from config file")
}

// Initialize Docker client with configuration
dockerClient, err := docker.NewClient(cfg.Docker.Host, log, docker.ClientConfig{
Expand Down
46 changes: 46 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# DiscoPanel Configuration File
# This file contains all configuration options for DiscoPanel
#
# These can all be set via env, like we do in our docker compose example(s). For example:
#
# server:
# port: "8080"
#
# - is the same as -
#
# DISCOPANEL_SERVER_PORT="8080"

# Server configuration for the DiscoPanel application itself
server:
Expand All @@ -8,6 +17,7 @@ server:
read_timeout: 15
write_timeout: 15
idle_timeout: 60
user_agent: "DiscoPanel/1.0 (github.com/nickheyer/discopanel)"

# Database configuration
database:
Expand All @@ -31,6 +41,42 @@ storage:
temp_dir: "./tmp"
max_upload_size: 524288000 # 500MB in bytes

# Authentication configuration
auth:
session_timeout: 86400 # default: 24 hours
anonymous_access: false # Allow unauthenticated access
jwt_secret: "" # Leave empty to auto-generate

# Local authentication (login via discopanel web ui)
local:
enabled: true # Enable local
allow_registration: false # Allow new users to register themselves via login

# OIDC authentication (login via OIDC-compliant provider, ie: keycloak, authelia, authentik, etc.)
oidc:
enabled: false
issuer_uri: "" # OIDC Provider url (ie: http://authelia.local:9091, http://localhost:8180/realms/discopanel, etc.)
client_id: "" # Client ID registered with your OIDC (like "discopanel")
client_secret: "" # Client secret registered with your OIDC
redirect_url: "" # Where OIDC sends users after login (ie: "http://localhost:8080/api/v1/auth/oidc/callback")
scopes: ["openid", "profile", "email"]
role_claim: "groups" # The token claim that contains the user's groups (usually "groups")
role_mapping: {} # Mapping to groups if they arent the same name as discopanels (ie: {"my-admins": "admin", "my-users": "user"})
skip_tls_verify: false # Skip TLS certificate verification (for self-signed certs)

# Upload configuration
upload:
session_ttl: 240 # Upload session time-to-live in minutes (default: 4 hours)
default_chunk_size: 5242880 # 5MB default chunk size (client can override)
max_chunk_size: 10485760 # 10MB max chunk size (server enforced)
max_upload_size: 0 # Max total upload size in bytes (0 = unlimited)

# Module configuration
module:
enabled: true
port_range_min: 8100
port_range_max: 8199

# Proxy configuration - ENABLE THIS FOR MINECRAFT ROUTING
proxy:
enabled: true
Expand Down
18 changes: 0 additions & 18 deletions docker-compose.dev.yaml

This file was deleted.

21 changes: 15 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,31 @@ services:
# You can set this to any path you'd like, but the path must exist AND you must use the same
# absolute paths below for the below env vars (in the environment section at the bottom). Example:
# DISCOPANEL_DATA_DIR=/app/data
# DISCOPANEL_HOST_DATA_PATH=/home/user/data
# DISCOPANEL_HOST_DATA_PATH=/opt/discopanel/data
# (See environment)
- /home/user/data:/app/data

- ./backups:/app/backups
- ./tmp:/app/tmp
- /opt/discopanel/data:/app/data
- /opt/discopanel/backups:/app/backups
- /tmp/discopanel:/app/tmp

# Configuration file, uncomment if you are using a config file (optional, see config.example.yaml for all available options).
#- ./config.yaml:/app/config.yaml:ro
environment:
- DISCOPANEL_DATA_DIR=/app/data

# IMPORTANT: THIS MUST BE SET TO THE SAME PATH AS THE SERVER DATA PATH IN "volumes" above
- DISCOPANEL_HOST_DATA_PATH=/home/user/data
- DISCOPANEL_HOST_DATA_PATH=/opt/discopanel/data
- TZ=UTC

# ── Authentication ──────────────────────────
# Local auth (username/password) is on by default.
# You create your first admin account on first login.
#
# Want to let new users sign up on their own?
# - DISCOPANEL_AUTH_LOCAL_ALLOW_REGISTRATION=true
#
# Want single sign-on (OIDC) with Keycloak, Authelia, etc?
# See the oidc/ folder for examples.

# DONT FORGET THIS
extra_hosts:
- "host.docker.internal:host-gateway"
Expand Down
6 changes: 3 additions & 3 deletions docker/Dockerfile.discopanel
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ ARG APP_VERSION=dev
# Generate protobuf code using buf
FROM bufbuild/buf:latest AS proto-builder
WORKDIR /app
COPY buf.yaml buf.gen.yaml ./
COPY buf.yaml buf.gen.yaml buf.lock ./
COPY proto/ ./proto/
RUN mkdir -p web/discopanel/static pkg/proto web/discopanel/src/lib/proto && buf generate

FROM node:22-alpine AS frontend-builder
FROM node:alpine AS frontend-builder

ARG APP_VERSION
ENV APP_VERSION=${APP_VERSION}
Expand All @@ -23,7 +23,7 @@ COPY --from=proto-builder /app/web/discopanel/static/schemav1.yaml ./static/

RUN npm run build

FROM golang:1.24.5-alpine AS backend-builder
FROM golang:alpine AS backend-builder

ARG APP_VERSION
ENV APP_VERSION=${APP_VERSION}
Expand Down
Loading