feat: add SQLite support, setup wizard enhancements, and live dashboard

Add zero-dependency SQLite mode so users can run Erupe without
PostgreSQL. A transparent db.DB wrapper auto-translates PostgreSQL
SQL ($N placeholders, now(), ::casts, ILIKE, public. prefix,
TRUNCATE) for SQLite at runtime — all 28 repo files use the wrapper
with no per-query changes needed.

Setup wizard gains two new steps: quest file detection with download
link, and gameplay presets (solo/small/community/rebalanced). The API
server gets a /dashboard endpoint with auto-refreshing stats.

CI release workflow now builds and pushes Docker images to GHCR
alongside binary artifacts on tag push.

Key changes:
- common/db: DB/Tx wrapper with 6 SQL translation rules
- server/migrations/sqlite: full SQLite schema (0001-0005)
- config: Database.Driver field ("postgres" or "sqlite")
- main.go: SQLite connection with WAL mode, single writer
- server/setup: quest check + preset selection steps
- server/api: /dashboard with live stats
- .github/workflows: Docker in release, deduplicate docker.yml
This commit is contained in:
Houmgaor
2026-03-05 18:00:30 +01:00
parent 03adb21e99
commit ecfe58ffb4
86 changed files with 2326 additions and 356 deletions

54
main.go
View File

@@ -25,6 +25,7 @@ import (
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
"go.uber.org/zap"
_ "modernc.org/sqlite"
)
// Temporary DB auto clean on startup for quick development & testing.
@@ -108,7 +109,7 @@ func main() {
logger.Info(fmt.Sprintf("Starting Erupe (9.3b-%s)", Commit()))
logger.Info(fmt.Sprintf("Client Mode: %s (%d)", config.ClientMode, config.RealClientMode))
if config.Database.Password == "" {
if config.Database.Driver != "sqlite" && config.Database.Password == "" {
preventClose(config, "Database password is blank")
}
@@ -136,19 +137,38 @@ func main() {
logger.Info("Discord: Disabled")
}
// Create the postgres DB pool.
connectString := fmt.Sprintf(
"host='%s' port='%d' user='%s' password='%s' dbname='%s' sslmode=disable",
config.Database.Host,
config.Database.Port,
config.Database.User,
config.Database.Password,
config.Database.Database,
)
db, err := sqlx.Open("postgres", connectString)
if err != nil {
preventClose(config, fmt.Sprintf("Database: Failed to open, %s", err.Error()))
// Create the DB pool.
var db *sqlx.DB
if config.Database.Driver == "sqlite" {
dbPath := config.Database.Database
if dbPath == "" || dbPath == "erupe" {
dbPath = "erupe.db"
}
db, err = sqlx.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)")
if err != nil {
preventClose(config, fmt.Sprintf("Database: Failed to open SQLite %s, %s", dbPath, err.Error()))
}
// SQLite only supports one writer at a time.
db.SetMaxOpenConns(1)
logger.Info(fmt.Sprintf("Database: SQLite opened (%s)", dbPath))
} else {
connectString := fmt.Sprintf(
"host='%s' port='%d' user='%s' password='%s' dbname='%s' sslmode=disable",
config.Database.Host,
config.Database.Port,
config.Database.User,
config.Database.Password,
config.Database.Database,
)
db, err = sqlx.Open("postgres", connectString)
if err != nil {
preventClose(config, fmt.Sprintf("Database: Failed to open, %s", err.Error()))
}
// Configure connection pool to avoid exhausting PostgreSQL under load.
db.SetMaxOpenConns(50)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(2 * time.Minute)
}
// Test the DB connection.
@@ -157,12 +177,6 @@ func main() {
preventClose(config, fmt.Sprintf("Database: Failed to ping, %s", err.Error()))
}
// Configure connection pool to avoid exhausting PostgreSQL under load.
db.SetMaxOpenConns(50)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(2 * time.Minute)
logger.Info("Database: Started successfully")
// Run database migrations