Skip to content

Commit fdd6159

Browse files
committed
chore: bootstrap clean-room Go server repository
0 parents  commit fdd6159

23 files changed

Lines changed: 668 additions & 0 deletions

File tree

.dockerignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.git
2+
.github
3+
bin
4+
dist
5+
coverage.out
6+
*.test
7+
*.out
8+
.vscode
9+
.idea

.editorconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_style = tab
7+
indent_size = 4
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.md]
12+
indent_style = space
13+
indent_size = 2
14+
trim_trailing_whitespace = false

.github/workflows/ci.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
test-and-build:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Go
18+
uses: actions/setup-go@v5
19+
with:
20+
go-version: '1.26'
21+
cache: true
22+
23+
- name: Show Go version
24+
run: go version
25+
26+
- name: Run tests
27+
run: go test ./...
28+
29+
- name: Build daemons
30+
run: |
31+
go build ./cmd/authd
32+
go build ./cmd/gamed
33+
34+
- name: Validate Docker runtime image
35+
run: docker build --target runtime -t go-metin2-server:ci .
36+
37+
- name: Validate Docker debug image
38+
run: docker build --target runtime-debug -t go-metin2-server:debug-ci .

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Binaries and build artifacts
2+
bin/
3+
dist/
4+
coverage.out
5+
*.test
6+
*.out
7+
8+
# IDEs / editors
9+
.vscode/
10+
.idea/
11+
*.swp
12+
.DS_Store
13+
14+
# Local runtime data
15+
.env
16+
.env.local
17+
.pprof/

Dockerfile

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# syntax=docker/dockerfile:1.7
2+
3+
FROM golang:1.26-bookworm AS build
4+
WORKDIR /src
5+
6+
COPY go.mod ./
7+
RUN --mount=type=cache,target=/go/pkg/mod \
8+
go mod download
9+
10+
COPY . .
11+
12+
ARG TARGETOS=linux
13+
ARG TARGETARCH=amd64
14+
ENV CGO_ENABLED=0
15+
16+
RUN --mount=type=cache,target=/root/.cache/go-build \
17+
GOOS=$TARGETOS GOARCH=$TARGETARCH go build -trimpath=false -buildvcs=true -o /out/authd ./cmd/authd && \
18+
GOOS=$TARGETOS GOARCH=$TARGETARCH go build -trimpath=false -buildvcs=true -o /out/gamed ./cmd/gamed
19+
20+
FROM gcr.io/distroless/static-debian12:nonroot AS runtime
21+
WORKDIR /app
22+
COPY --from=build /out/authd /app/authd
23+
COPY --from=build /out/gamed /app/gamed
24+
ENV METIN2_PPROF_ADDR=:6060
25+
EXPOSE 6060 6061
26+
USER nonroot:nonroot
27+
ENTRYPOINT ["/app/gamed"]
28+
29+
FROM gcr.io/distroless/static-debian12:debug-nonroot AS runtime-debug
30+
WORKDIR /app
31+
COPY --from=build /out/authd /app/authd
32+
COPY --from=build /out/gamed /app/gamed
33+
ENV METIN2_PPROF_ADDR=:6060
34+
EXPOSE 6060 6061
35+
USER nonroot:nonroot
36+
ENTRYPOINT ["/app/gamed"]

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Mikel Calvo
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
GO ?= go
2+
IMAGE ?= go-metin2-server
3+
4+
.PHONY: fmt test build build-authd build-gamed docker-build docker-build-debug
5+
6+
fmt:
7+
$(GO) fmt ./...
8+
9+
test:
10+
$(GO) test ./...
11+
12+
build: build-authd build-gamed
13+
14+
build-authd:
15+
mkdir -p bin
16+
$(GO) build -o bin/authd ./cmd/authd
17+
18+
build-gamed:
19+
mkdir -p bin
20+
$(GO) build -o bin/gamed ./cmd/gamed
21+
22+
docker-build:
23+
docker build --target runtime -t $(IMAGE):latest .
24+
25+
docker-build-debug:
26+
docker build --target runtime-debug -t $(IMAGE):debug .

README.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# go-metin2-server
2+
3+
Clean-room Metin2 server emulator in Go, targeting TMP4-era client compatibility.
4+
5+
## Status
6+
7+
This repository is the public bootstrap for a fresh, protocol-first rewrite.
8+
9+
Current scope of the project:
10+
- Go 1.26 baseline.
11+
- Clean-room process: no legacy server/client code is vendored into this repository.
12+
- Separate `authd` and `gamed` binaries from day zero.
13+
- A dedicated pprof/ops HTTP server for profiling goroutines, heap, allocs, mutex contention and blocking.
14+
- Multi-stage Docker build with a lightweight runtime image that keeps Go debug information intact by avoiding stripped builds.
15+
16+
## Near-term goal
17+
18+
The first real milestone is not “full gameplay”.
19+
It is a minimal but complete TMP4-compatible boot path:
20+
- handshake,
21+
- login/auth,
22+
- character list,
23+
- create character,
24+
- select character,
25+
- enter game,
26+
- spawn in a minimal world,
27+
- basic movement.
28+
29+
## Project layout
30+
31+
- `cmd/authd` — auth daemon entrypoint
32+
- `cmd/gamed` — game daemon entrypoint
33+
- `internal/config` — runtime config loading
34+
- `internal/ops` — health and pprof endpoints
35+
- `internal/service` — shared service bootstrap / shutdown helpers
36+
- `docs/` — engineering and clean-room documentation
37+
- `spec/protocol` — protocol notes and packet inventory
38+
39+
## pprof
40+
41+
Both binaries expose an ops server with:
42+
- `/healthz`
43+
- `/debug/pprof/`
44+
- `/debug/pprof/goroutine`
45+
- `/debug/pprof/heap`
46+
- `/debug/pprof/profile`
47+
- `/debug/pprof/allocs`
48+
- `/debug/pprof/block`
49+
- `/debug/pprof/mutex`
50+
- `/debug/pprof/trace`
51+
52+
Default addresses:
53+
- `gamed`: `:6060`
54+
- `authd`: `:6061`
55+
56+
Global override:
57+
- `METIN2_PPROF_ADDR`
58+
59+
Per-service overrides:
60+
- `METIN2_GAMED_PPROF_ADDR`
61+
- `METIN2_AUTHD_PPROF_ADDR`
62+
63+
Examples:
64+
65+
```bash
66+
go tool pprof http://127.0.0.1:6060/debug/pprof/heap
67+
go tool pprof http://127.0.0.1:6060/debug/pprof/goroutine
68+
go tool pprof http://127.0.0.1:6060/debug/pprof/profile?seconds=30
69+
curl http://127.0.0.1:6060/debug/pprof/goroutine?debug=1
70+
```
71+
72+
Do not expose pprof directly to the public internet.
73+
74+
## Development
75+
76+
### Local
77+
78+
```bash
79+
make test
80+
```
81+
82+
Run the daemons locally:
83+
84+
```bash
85+
go run ./cmd/authd
86+
go run ./cmd/gamed
87+
```
88+
89+
### Docker
90+
91+
Build the default lightweight runtime image:
92+
93+
```bash
94+
docker build --target runtime -t go-metin2-server:latest .
95+
```
96+
97+
Build the debug-flavoured runtime image:
98+
99+
```bash
100+
docker build --target runtime-debug -t go-metin2-server:debug .
101+
```
102+
103+
Why this Dockerfile keeps debug information:
104+
- it uses a multi-stage build,
105+
- it keeps the final image small with Distroless,
106+
- it deliberately does not pass `-ldflags="-s -w"`, so DWARF/debug symbols remain available.
107+
108+
## Clean-room rule
109+
110+
This repository must only contain code, documentation and fixtures produced for this project.
111+
Do not copy legacy Metin2 server/client source into this repository.
112+
113+
See:
114+
- `docs/clean-room-policy.md`
115+
- `docs/development.md`
116+
- `docs/debugging-and-profiling.md`
117+
- `spec/protocol/README.md`

cmd/authd/main.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log/slog"
7+
"os"
8+
"os/signal"
9+
"syscall"
10+
11+
"github.com/MikelCalvo/go-metin2-server/internal/buildinfo"
12+
"github.com/MikelCalvo/go-metin2-server/internal/config"
13+
"github.com/MikelCalvo/go-metin2-server/internal/service"
14+
)
15+
16+
func main() {
17+
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)).With(
18+
"service", "authd",
19+
"version", buildinfo.Version,
20+
"commit", buildinfo.Commit,
21+
"build_date", buildinfo.BuildDate,
22+
)
23+
24+
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
25+
defer stop()
26+
27+
cfg := config.LoadService("authd", ":6061")
28+
if err := service.Run(ctx, cfg, logger); err != nil {
29+
logger.Error("service stopped with error", "err", err)
30+
fmt.Fprintln(os.Stderr, err)
31+
os.Exit(1)
32+
}
33+
}

cmd/gamed/main.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log/slog"
7+
"os"
8+
"os/signal"
9+
"syscall"
10+
11+
"github.com/MikelCalvo/go-metin2-server/internal/buildinfo"
12+
"github.com/MikelCalvo/go-metin2-server/internal/config"
13+
"github.com/MikelCalvo/go-metin2-server/internal/service"
14+
)
15+
16+
func main() {
17+
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)).With(
18+
"service", "gamed",
19+
"version", buildinfo.Version,
20+
"commit", buildinfo.Commit,
21+
"build_date", buildinfo.BuildDate,
22+
)
23+
24+
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
25+
defer stop()
26+
27+
cfg := config.LoadService("gamed", ":6060")
28+
if err := service.Run(ctx, cfg, logger); err != nil {
29+
logger.Error("service stopped with error", "err", err)
30+
fmt.Fprintln(os.Stderr, err)
31+
os.Exit(1)
32+
}
33+
}

0 commit comments

Comments
 (0)