# Origin's config file tells you which node starts which service

> origin is an Apache-2.0 distributed game server engine written in Go, structured around three objects, node, service and module, with a JSON configuration that resolves per node overrides on top of a shared service block. The catch is that the documentation and the module path are still on v2 while the recent releases are v3 release candidates.

**duanhf2012/origin** — A  game server framework in Go (golang).

- Repository: https://github.com/duanhf2012/origin
- Stars: 1,702 · Forks: 198
- Language: Go
- License: Apache-2.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/duanhf2012-origin

## A whole server process is one Node

The minimal program starts a node and nothing else:

```go
package main

import (
    "github.com/duanhf2012/origin/v2/node"
)

func main() {
    node.Start()
}
```

Everything else comes from configuration. That is the central design claim: the engine is designed the way Go is designed, aiming to provide a simple and easy to use mode for fast development, and to let a server architecture be shaped quickly and flexibly according to business needs.

The three objects are worth learning before reading any config file.

A Node corresponds to one origin process. A Service is an independent service, thought of as a large functional module, and is a subset of a Node, created and installed into the Node. Services can support external RPC and other things. A Module is the smallest object unit, and the documentation strongly recommends splitting business logic into small Modules composed together, since the engine monitors the running state of every service and module, including slow handling and infinite loop functions. Modules can form tree relationships, and a Service is itself a type of Module.

That last point is the useful one. Monitoring is a property of the object model rather than something you instrument yourself.

Two companion repositories exist: `originserver_v2`, a skeleton server the documentation is written against, and `origingame`, which adds network modules for tcp, kcp and websocket, a character load and login flow, save files and config reading.

## Two discovery backends and the constraint each one puts on your config

The Discovery section chooses how nodes find each other, and the config offers etcd or origin's own built in discovery.

With etcd, the config lists endpoints, optional credentials and certificate paths, and the important part is the network naming. `TTLSecond` is the health check TTL expiry at 10 seconds, and `DialTimeoutMillisecond` is the connection timeout at 3000. The `EtcdList` can hold several entries, and the documentation flags a requirement: the list must contain an entry with a `LocalNetworkName`, which declares that all Nodes belong to a network name such as `network_Area1`. Services register into that name, and Nodes watching the network discover the services in it. A local network watches all services in that network by default.

`NeighborNetworkName` lists adjacent networks to watch, and `Endpoints` holds the etcd addresses. The certificate fields `Cert`, `CertKey` and `Ca` are there for a secured etcd.

With origin's own discovery, there is no etcd and no external process. `LocalMasterNodeId` names the master that local Nodes register with, and the documentation notes the constraint that the NodeId you put there must also appear in `MasterNodeList`. The master synchronizes to all watching Nodes, and a local Node watches all services in its local master by default. `MasterNodeList` entries carry `NodeId` and `ListenAddr`, and the documentation warns these must match the actual Node config.

Both forms have the same 10 second `TTLSecond`. The origin form means one fewer service to operate, and also means the master is a single point of discovery for its cluster.

## RpcMode decides whether nodes talk over TCP or through NATS

Two values are supported. The default is:

```json
{
  "RpcMode":{
      "Type": "Default"
  }
}
```

where nodes form the network by connecting to each other over TCP.

The alternative is Nats:

```json
{
  "RpcMode":{
      "Type": "Nats",
      "remark": "support Default or Nats",
      "Nats": {
          "NatsUrl":"127.0.0.1:4222",
          "NoRandomize": true
      }
  }
}
```

`NatsUrl` is the connection string and `NoRandomize` controls the connection order strategy in a multi connection cluster: false connects randomly, true connects in order.

The choice is not only about transport. In Default mode, node to node reachability is expressed by `ListenAddr` and by which NodeId you start, so topology is in your config. In Nats mode the message bus becomes the topology, and the addresses in `NodeList` matter for other reasons.

## NodeList ordering decides startup order and stop order

The NodeList entries carry the identity and the tuning for each process:

```json
{
    "NodeList":[
        {
          "NodeId": "node_1",
          "Private": false,
          "ListenAddr":"127.0.0.1:8001",
          "MaxRpcParamLen": 409600,
          "CompressBytesLen": 20480,
          "ServiceList": ["TestService1","TestService2","TestServiceCall","GateService","_TcpService","HttpService","WSService"]
        }
    ]
}
```

`NodeId` must be unique within one service discovery network. `Private` marks a node that other nodes will not discover, though it still runs on its own. `ListenAddr` is the address the RPC service listens on.

The two size settings are the ones with a stated default. `MaxRpcParamLen` may be omitted, in which case a single RPC call supports up to 4294967295 bytes. `CompressBytesLen` compresses RPC network data at or above that size, and may be omitted or set to 0 for no compression.

Then there is the rule that surprises people: origin installs and initialises services in the configured order, but stops them in the reverse order.

The underscore prefix in `_TcpService` means the service stays inside its own process and is not published to the subnet, which is how you run a private listener such as an admin or debug port alongside public ones. Note the two example nodes use it differently, one listing `_TcpService` and the other listing `TcpService` in the same position.

You select which node a process runs with `originserver -start nodeid="node_1"`, and `originserver -help` lists the rest.

## Service config resolves per node, then falls back to the shared block

The Service section configures the listeners. Three are documented.

HttpService takes `ListenAddr`, `ReadTimeout`, `WriteTimeout` and `ProcessTimeout` in milliseconds, a `ManualStart` flag which means you must call `StartListen()` yourself, and a `CAFile` array of `Certfile` and `Keyfile` pairs, which the documentation says you can ignore if a web server in front of you terminates https.

TcpService takes `ListenAddr`, `MaxConnNum`, `PendingWriteNum` for the send queue length, a `LittleEndian` flag, `MinMsgLen` and `MaxMsgLen`.

WSService takes `ListenAddr`, `MaxConnNum`, `PendingWriteNum` and `MaxMsgLen`, with no endianness or minimum length setting.

The resolution rule is the part to internalise. There is a shared `Service` block and a `NodeService` array keyed by `NodeId`. When starting, the engine looks up the NodeId, uses that entry's service config if found, and falls back to the common `Service` block when it is not. A typo in a NodeId, or a NodeId written as a number in one place and a string in another, produces a node running with the shared defaults rather than an error.

Global config is separate and reachable from any service:

```go
globalCfg := cluster.GetCluster().GetGlobalCfg()
mapGlobal, ok := globalCfg.(map[string]interface{})
if ok == false {
    return fmt.Errorf("Canot find Global from config.")
}
```

The example config puts `AreaId` in Global, which is how a shared value reaches every service without editing each one.

## v2 in the module path, v3 release candidates in the tags

The default branch is `v2` and the module is `github.com/duanhf2012/origin/v2`, with `go 1.22` and `toolchain go1.22.7` declared in `go.mod`. The import in the hello world carries the `/v2` segment, so files that import the module use that path.

The recent releases say something different: v3.2.0-rc.1 on 2026-08-15 and v3.1.0-rc.1 on 2026-08-10, both release candidates, preceded by v2.2.0 on 2025-12-15. The last push was on 2026-08-29.

So the tagged work is on a v3 line that has not had a final release, while the branch the documentation describes is v2. Anyone importing this today is importing `/v2` and should assume the v3 line will require an import path change and a configuration review.

The dependency list also shows what the engine actually uses. Kafka through `github.com/IBM/sarama`, NATS through `nats-io/nats.go`, etcd through the v3 client, KCP through `xtaci/kcp-go/v5`, WebSocket through `gorilla/websocket`, gin for HTTP, zap for logging, lz4 for compression, redis through redigo, MySQL and MongoDB drivers, gopsutil for process information, protobuf, and a rotate-based log writer. The repository layout follows the same split, with `cluster/`, `concurrent/`, `console/`, `event/`, `log/`, `network/`, `node/`, `profiler/`, `rpc/`, `service/`, `sysmodule/`, `sysservice/` and `util/`.

The project is Apache-2.0, with the licence text at the repository root.

## Conclusion

Adopt origin if you are building a Go game backend, including an H5 game server, and you want one binary whose services, listeners and discovery are declared in JSON rather than assembled in code. Do not adopt it on the assumption that the released version is the documented one: the module path and the README both say v2 while the newest tags are v3.2.0-rc.1 and v3.1.0-rc.1, both release candidates. Verify first that `originserver -help` on your build lists the flags the README expects, and read the Service section carefully because a NodeService override that fails to match a NodeId silently falls back to the shared Service block rather than failing the start.

## FAQ

### How do I start an origin game server?

Create a node and start it with `node.Start()`, importing `github.com/duanhf2012/origin/v2/node`. Services, listeners and discovery all come from the JSON configuration, and you select which node runs with `originserver -start nodeid="node_1"`.

### What are the three core objects in origin?

A Node is one origin process, a Service is a large functional module installed into a Node, and a Module is the smallest unit of business logic. The engine monitors the running state of services and modules, including slow handling and infinite loop functions.

### Which service discovery backends does origin support?

etcd and origin's own built in discovery. The etcd form requires one EtcdList entry with a `LocalNetworkName` declaring the network that Nodes belong to, and the Origin form requires the `LocalMasterNodeId` to also appear in `MasterNodeList`.

### How do per node service overrides work in origin?

The `NodeService` array is keyed by `NodeId`. On start the engine looks up the running node there and uses that service config if found, and falls back to the shared `Service` block when it is not.

### What version of origin is current?

The default branch and the module path are v2, and the most recent tags are v3.2.0-rc.1 and v3.1.0-rc.1, both release candidates from August 2026. v2.2.0 is the last final release listed.

## Sources

- [duanhf2012/origin on GitHub](https://github.com/duanhf2012/origin)
- [Issues](https://github.com/duanhf2012/origin/issues)
- [License: Apache-2.0](https://github.com/duanhf2012/origin/blob/v2/LICENSE)
- [README](https://github.com/duanhf2012/origin/blob/v2/README.md)
- [Releases](https://github.com/duanhf2012/origin/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/duanhf2012-origin
