Brings 53 develop commits (i18n, Diva, campaign, guild invites, save
transfer, return/rookie guilds, hunting tournament, JSON quest/scenario
loaders, Ghidra-derived user binary parsing, and misc fixes) onto main
now that 9.3.2 has been tagged and released.
Resolves two overlap zones:
1. Migration number collision. Main shipped 0010_fix_zero_rasta_id and
0011_fix_stale_boost_time in 9.3.2; develop had independently
numbered 0010_campaign..0015_tournament. The migration runner keys
applied versions by integer, so coexisting files with the same
numeric prefix would silently skip each other. Develop's files have
been renumbered to 0016..0021, leaving main's 0010/0011 intact. A
schema_version rename script is required on any server that had
already applied the old develop numbers (only frontier.mogapedia.fr
at the time of this merge).
2. CHANGELOG.md. Develop's in-progress feature entries move into
[Unreleased] with updated migration references; the [9.3.2] section
is preserved verbatim.
main.go version string bumped to 9.4.0-dev to mark the new cycle.
Full test suite (go test -race ./...) passes.
Resolves CHANGELOG.md conflict: preserve develop's [Unreleased] block,
insert the [9.3.1] section from main, remove the duplicate
DisableSaveIntegrityCheck entry that had been in [Unreleased].
Add ShutdownAndDrain to the channel server (issue #179 non-breaking
subset): on SIGTERM/SIGINT, force-close all active sessions so that
logoutPlayer runs for each one (saves character data, cleans up stages
and semaphores), then poll until the session map empties or a 30-second
context deadline passes. Existing Shutdown() is unchanged.
Add ShutdownCountdownSeconds int config field (default 10) alongside
DisableSoftCrash so operators can tune the broadcast countdown without
patching code. A zero value falls back to 10 for safety.
Fix pre-existing test failures: MsgMhfAddRewardSongCount has a complete
Parse() implementation so it no longer belongs in the "NOT IMPLEMENTED"
parse test list; its handler test is updated to pass a real packet and
assert an ACK response instead of calling with nil.
A second Ctrl+C/SIGINT while the 10-second shutdown countdown is
running now exits immediately instead of being silently dropped.
The signal channel is buffered to 2 so the second signal is captured,
and the countdown select exits via os.Exit(1) on receipt.
The catch-up migration now creates the servers table if missing, like
sign_sessions. Startup cleanup queries in main.go use Exec instead of
MustExec so missing tables log warnings rather than panicking.
Instead of a generic "Failed to ping" message, detect the actual
cause and show a targeted fix:
- net error → PostgreSQL not reachable, check if it's running
- pq 28P01 → wrong password, update config.json
- pq 3D000 → database doesn't exist, create it
An MMO server without multiplayer defeats the purpose. PostgreSQL
is the right choice and Docker Compose already solves the setup
pain. This reverts the common/db wrapper, SQLite schema, config
Driver field, modernc.org/sqlite dependency, and all repo type
changes while keeping the dashboard, wizard, and CI improvements
from the previous commit.
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
Extract a Session interface from *discordgo.Session so DiscordBot methods
can be tested with a mock — no live Discord connection required. Add
AddHandler, RegisterCommands, and UserID methods to DiscordBot so
external callers (main.go, sys_channel_server.go) no longer reach
through bot.Session directly. Rewrite tests with a mockSession,
raising discordbot coverage from 12.5% to 66.7%.
- Add Language default ("jp") so missing field no longer produces empty
string, and include language selector in setup wizard
- Add --setup flag to re-run wizard even when config.json exists,
providing a recovery path for corrupted configs
- Auto-apply seed data on fresh databases so users who skip the wizard
still get shops, events, and gacha
- Fix stale docs referencing non-existent init/setup.sh and
schemas/patch-schema/ in docker/README, CONTRIBUTING, and README
Replace 4 independent schema management code paths (Docker shell
script, setup wizard pg_restore, test helpers, manual psql) with a
single migration runner embedded in the server binary.
The new server/migrations/ package uses Go embed to bundle all SQL
schemas. On startup, Migrate() creates a schema_version tracking
table, detects existing databases (auto-marks baseline as applied),
and runs pending migrations in transactions.
Key changes:
- Consolidated init.sql + 9.2-update + 33 patches into 0001_init.sql
- Setup wizard simplified to single "Apply schema" checkbox
- Test helpers use migrations.Migrate() instead of pg_restore
- Docker no longer needs schema volume mounts or init script
- Seed data (shops, events, gacha) embedded and applied via API
- Future migrations just add 0002_*.sql files — no manual steps
When config.json is missing, Erupe now launches a temporary HTTP server
on port 8080 serving a guided setup wizard instead of exiting with a
cryptic error. The wizard walks users through database connection,
schema initialization (pg_restore + SQL migrations), and server settings,
then writes config.json and continues normal startup without restart.
- Warn at startup when quest files are missing (clients crash without
them) and point users to the download link
- Fix Host config description: it's the advertised IP, not a bind
address — 0.0.0.0 was wrong advice
- Load bundled schemas (shops, events, gacha) in Docker init so new
users get working demo data out of the box
- Renumber duplicate patch schema 28 → 32 to resolve numbering
collision
- Fix patch schema example filename to use hyphens matching actual
files
main.go always sets both Channels and Registry together, making the
Channels fallback paths dead code. This removes:
- Server.Channels field from the Server struct
- 3 if/else fallback blocks in handlers_session.go (replaced with
Registry.FindChannelForStage, SearchSessions, SearchStages)
- 1 if/else fallback block in handlers_guild_ops.go (replaced with
Registry.NotifyMailToCharID)
- 3 method fallbacks in sys_channel_server.go (WorldcastMHF,
FindSessionByCharID, DisconnectUser now delegate directly)
Updates anti-patterns.md #6 to "accepted design" — Session struct is
appropriate for this game server's handler pattern, and cross-channel
coupling is now fully routed through the ChannelRegistry interface.
The config package used `package _config` with a leading underscore,
which is unconventional in Go. Rename to `package config` (matching the
directory name) and use `cfg` as the standard import alias across all
93 importing files.
sqlx.Open was called with no pool configuration, risking PostgreSQL
connection exhaustion under load. Set max open/idle conns and lifetimes.
CreatePost INSERT + soft-delete UPDATE were two separate queries with
no transaction, risking inconsistent state on partial failure.
CollectAdventure used SELECT then UPDATE without a lock, allowing
concurrent guild members to double-collect. Now uses SELECT FOR UPDATE
within a transaction.
Replace the mutable global `_config.ErupeConfig` with dependency
injection across 79 files. Config is now threaded through existing
paths: `ClientContext.RealClientMode` for packet encoding, `s.server.
erupeConfig` for channel handlers, and explicit parameters for utility
functions. This removes hidden coupling, enables test parallelism
without global save/restore, and prevents low-level packages from
reaching up to the config layer.
Key changes:
- Enrich ClientContext with RealClientMode for packet files
- Add mode parameter to CryptConn, mhfitem, mhfcourse functions
- Convert handlers_commands init() to lazy sync.Once initialization
- Delete global var, init(), and helper functions from config.go
- Update all tests to pass config explicitly
Enable multiple Erupe instances to share a single PostgreSQL database
without destroying each other's state, fix existing data races in
cross-channel access, and lay groundwork for future distributed
channel server deployments.
Phase 1 — DB safety:
- Scope DELETE FROM servers/sign_sessions to this instance's server IDs
- Fix ci++ bug where failed channel start shifted subsequent IDs
Phase 2 — Fix data races in cross-channel access:
- Lock sessions map in FindSessionByCharID and DisconnectUser
- Lock stagesLock in handleMsgSysLockGlobalSema
- Snapshot sessions/stages under lock in TransitMessage types 1-4
- Lock channel when finding mail notification targets
Phase 3 — ChannelRegistry interface:
- Define ChannelRegistry interface with 7 cross-channel operations
- Implement LocalChannelRegistry with proper locking
- Add SessionSnapshot/StageSnapshot immutable copy types
- Delegate WorldcastMHF, FindSessionByCharID, DisconnectUser to Registry
- Migrate LockGlobalSema and guild mail handlers to use Registry
- Add comprehensive tests including concurrent access
Phase 4 — Per-channel enable/disable:
- Add Enabled *bool to EntranceChannelInfo (nil defaults to true)
- Skip disabled channels in startup loop, preserving ID stability
- Add IsEnabled() helper with backward-compatible default
- Update config.example.json with Enabled field
Re-enable the golangci-lint job in CI (disabled Oct 2025), update to
Go 1.25 and golangci-lint-action v7. Fix errcheck, gosimple S1009,
staticcheck SA4031 and SA2001 errors across 54 files. Remaining ~39
lint errors will be addressed in follow-up commits.
Move time utilities (TimeAdjusted, TimeMidnight, TimeWeekStart, TimeWeekNext,
TimeGameAbsolute) from channelserver into common/gametime to break the
inappropriate dependency where signserver, entranceserver, and api imported
the 38K-line channelserver package just for time functions.
Replace all fmt.Printf debug logging in sys_session.go and handlers_object.go
with structured zap logging for consistent observability.