This guide covers building Valhalla from source for development work.
- Go 1.25 or later - Download Go
- Git - For cloning the repository
- Data.nx file - See Installation Guide for conversion
- MySQL 5.7+ or MariaDB - For database
- Download installer from golang.org/dl/
- Run installer
- Verify installation:
go version
# Ubuntu/Debian
sudo apt update
sudo apt install golang-go
# Or download latest from golang.org
wget https://go.dev/dl/go1.25.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.25.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc
# Verify
go version# Using Homebrew
brew install go
# Or download from golang.org
# Verify
go versiongit clone https://github.com/Hucaru/Valhalla.git
cd Valhallago mod downloadThis downloads all required Go modules specified in go.mod.
go build -v .This creates an executable:
- Windows:
Valhalla.exe - Linux/macOS:
Valhalla
The -v flag shows verbose output to see which packages are being compiled.
Follow the Local Setup Guide to configure and run the built executable.
For faster iteration during development:
# Build and run immediately
go run . -type login -config config_login.toml
# Build with race detector (slower but catches concurrency bugs)
go build -race -v .Valhalla/
├── main.go # Entry point
├── server_login.go # Login server implementation
├── server_world.go # World server implementation
├── server_channel.go # Channel server implementation
├── server_cashshop.go # Cash shop server implementation
├── server_config.go # Configuration loading
├── common/ # Shared utilities
├── channel/ # Channel server logic
├── login/ # Login server logic
├── world/ # World server logic
├── cashshop/ # Cash shop logic
├── mnet/ # Network layer
├── mpacket/ # Packet handling
├── nx/ # NX file reader
├── constant/ # Game constants
├── internal/ # Internal packages
├── scripts/ # NPC scripts (JavaScript)
├── drops.json # Drop data
├── reactors.json # Reactor data
└── reactor_drops.json # Reactor drop data
# Run all tests
go test ./...
# Run tests with coverage
go test -cover ./...
# Run tests with verbose output
go test -v ./...
# Run specific package tests
go test ./channel/...
# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.outFormat code using Go's standard formatter:
# Format all files
go fmt ./...
# Check formatting without applying changes
go fmt -n ./...Use golangci-lint for comprehensive linting:
# Install golangci-lint
# See https://golangci-lint.run/usage/install/
# Run linter
golangci-lint run
# Run with auto-fix
golangci-lint run --fix# Run go vet (included in go test)
go vet ./...
# Check for common mistakes
go vet -composites=false ./...Go supports cross-compilation for different operating systems and architectures.
# Linux AMD64
GOOS=linux GOARCH=amd64 go build -o Valhalla-linux-amd64 .
# Linux ARM64 (for ARM servers/Raspberry Pi)
GOOS=linux GOARCH=arm64 go build -o Valhalla-linux-arm64 .GOOS=windows GOARCH=amd64 go build -o Valhalla-windows-amd64.exe .# Intel Macs
GOOS=darwin GOARCH=amd64 go build -o Valhalla-darwin-amd64 .
# Apple Silicon (M1/M2)
GOOS=darwin GOARCH=arm64 go build -o Valhalla-darwin-arm64 .Create a build script for all platforms:
#!/bin/bash
# build-all.sh
platforms=(
"linux/amd64"
"linux/arm64"
"windows/amd64"
"darwin/amd64"
"darwin/arm64"
)
for platform in "${platforms[@]}"; do
IFS='/' read -r -a parts <<< "$platform"
GOOS="${parts[0]}"
GOARCH="${parts[1]}"
output="Valhalla-${GOOS}-${GOARCH}"
if [ "$GOOS" = "windows" ]; then
output="${output}.exe"
fi
echo "Building for $GOOS/$GOARCH..."
GOOS=$GOOS GOARCH=$GOARCH go build -o "build/$output" .
done
echo "Build complete! Binaries in build/"Run it:
chmod +x build-all.sh
./build-all.sh# Reduce binary size by stripping debug info
go build -ldflags="-s -w" .
# Build with static linking (useful for Docker alpine images)
CGO_ENABLED=0 go build -ldflags="-s -w" .# Build with race detector
go build -race .
# Build with optimizations disabled (easier debugging)
go build -gcflags="all=-N -l" .Embed version information at build time:
# In main.go, add variables:
# var (
# Version = "dev"
# BuildTime = "unknown"
# GitCommit = "unknown"
# )
# Build with version info
go build -ldflags="-X main.Version=1.0.0 -X main.BuildTime=$(date -u +%Y%m%d%H%M%S) -X main.GitCommit=$(git rev-parse HEAD)" .Build the Docker image locally:
# Build image
docker build -t valhalla:dev .
# Build with specific platform
docker build --platform linux/amd64 -t valhalla:dev .
# Build with build arguments
docker build --build-arg GO_VERSION=1.25 -t valhalla:dev .# List all dependencies
go list -m all
# View dependency graph
go mod graph
# Check for available updates
go list -u -m all# Update all dependencies to latest minor/patch versions
go get -u ./...
# Update specific dependency
go get github.com/spf13/viper@latest
# Tidy up (remove unused dependencies)
go mod tidyCreate a vendor/ directory with all dependencies:
# Create vendor directory
go mod vendor
# Build using vendor directory
go build -mod=vendor .This ensures builds are reproducible even if dependency sources change.
- Install Go extension
- Configure settings (
.vscode/settings.json):{ "go.useLanguageServer": true, "go.lintTool": "golangci-lint", "go.lintOnSave": "package", "go.formatTool": "goimports", "go.buildOnSave": "off" }
- Install Go plugin
- Open project
- GoLand will automatically detect
go.modand configure the project
Use vim-go:
" In .vimrc
Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }Install Delve:
go install github.com/go-delve/delve/cmd/dlv@latestDebug a server:
# Start login server with debugger
dlv debug . -- -type login -config config_login.toml
# In debugger:
(dlv) break main.main
(dlv) continue
(dlv) nextCreate .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Login Server",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}",
"args": ["-type", "login", "-config", "config_login.toml"]
},
{
"name": "Launch Channel Server",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}",
"args": ["-type", "channel", "-config", "config_channel_1.toml"]
}
]
}# Build with profiling
go build -o Valhalla .
# Run with CPU profiling
./Valhalla -type channel -config config_channel_1.toml -cpuprofile=cpu.prof
# Analyze profile
go tool pprof cpu.prof# Run with memory profiling
./Valhalla -type channel -config config_channel_1.toml -memprofile=mem.prof
# Analyze
go tool pprof mem.profAdd to your code:
import _ "net/http/pprof"
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()Access profiles at:
- http://localhost:6060/debug/pprof/
- http://localhost:6060/debug/pprof/heap
- http://localhost:6060/debug/pprof/goroutine
The project uses GitHub Actions for automated builds and releases.
The project uses GoReleaser for creating releases:
# Install GoReleaser
go install github.com/goreleaser/goreleaser@latest
# Test release locally (no publish)
goreleaser release --snapshot --clean
# Create actual release (requires git tag)
git tag v1.0.0
goreleaser release --clean# Clean module cache
go clean -modcache
# Re-download dependencies
go mod download# Ensure binary has execute permissions (Linux/macOS)
chmod +x Valhalla
# Add to PATH or run with ./
./Valhalla -type loginGo doesn't allow circular imports. Restructure code to break the cycle, often by:
- Creating a separate package for shared interfaces
- Moving common types to a
typespackage - Using dependency injection
If you encounter CGO errors and don't need CGO:
CGO_ENABLED=0 go build .When contributing code:
- Format your code:
go fmt ./... - Run tests:
go test ./... - Run linter:
golangci-lint run - Write tests for new features
- Update documentation as needed
- Follow Go conventions: Use effective Go practices
- Set up local environment: Local.md
- Configure servers: Configuration.md
- Deploy with Docker: Docker.md
- Deploy to Kubernetes: Kubernetes.md
- Effective Go
- Go by Example
- Go Wiki
- Awesome Go - Curated list of Go libraries