Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
xstar97 committed Mar 6, 2024
1 parent f14fb17 commit 592759b
Show file tree
Hide file tree
Showing 12 changed files with 457 additions and 136 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dev.sh
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ WORKDIR /build
# Copy all files from the cmd directory
COPY go.mod ./go.mod
COPY go.sum ./go.sum
COPY internal/server ./internal/server
COPY internal/utils ./internal/utils
COPY internal/routes ./internal/routes
COPY internal/config ./internal/config
COPY cmd/main.go ./main.go

Expand Down
96 changes: 95 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,97 @@
# palworld-query-api

query palworld with rcon!
Streamlined web API for effortlessly managing and querying Palworld game servers using the RCON protocol.

## Features

- Supports querying multiple Palworld game servers.
- Dynamic routing for retrieving server data by name.

## Installation

To install and run *palworld-query-api*, follow these steps:

1. Clone the repository: `git clone https://github.com/xstar97/palworld-query-api.git`
2. Navigate to the project directory: `cd palworld-query-api`
3. Build the project: `go build cmd/main.go`
4. Run the compiled binary: `./palworld-query-api`

Make sure you have Go installed and properly configured on your system before proceeding.

### Command-Line Installation

To install and run *palworld-query-api* from the command line, follow these steps:

1. Clone the repository: `git clone https://github.com/xstar97/palworld-query-api.git`
2. Navigate to the project directory: `cd palworld-query-api`
3. Build the project: `go build`
4. Run the compiled binary: `./palworld-query-api`

Make sure you have Go installed and properly configured on your system before proceeding.

#### Command-Line Flags

You can customize the behavior of *palworld-query-api* using the following command-line flags:

| Flag | Description | Default Value |
|--------------------|---------------------------------------|--------------------|
| `-port` | Web port | `3000` |
| `-cli-root` | Root path to rcon file | `/app/rcon/rcon` |
| `-cli-config` | Root path to rcon.yaml | `/config/rcon.yaml`|
| `-logs-path` | Logs path | `/logs` |

Replace the default values as needed when running the binary.

### Docker Installation

Alternatively, you can use the Docker image hosted on GitHub. Use the following `docker-compose.yml` file:

```yaml
version: '3.8'

services:
palworld-query-api:
image: ghcr.io/xstar97/palworld-query-api:latest
environment:
- PORT=3000
# default values; really dont need to be changed!
# - CLI_ROOT=/app/rcon/rcon
# - CLI_CONFIG=/config/rcon.yaml
# - LOGS_PATH=/logs
# generates the yaml from this json array (optional, but recommended)
# - CONFIG_JSON='{"servers":[{"name":"default","address":"localhost:25575","password":"1234567890","type":"rcon","timeout":"10s"}]}'
ports:
- "3000:3000"
volumes:
- ./config:/config
- ./logs:/logs
```
an env variable `CONFIG_JSON` can be set to automatically create the rcon.yaml file needed for the rcon-cli tool.

```json
{
"servers": [
{
"name": "default",
"address": "localhost:25575",
"password": "1234567890",
"type": "rcon",
"timeout": "60s"
}
]
}
```

### License

This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.

## Contributing

Contributions are welcome! Please see the [CONTRIBUTING.md](./CONTRIBUTING.md) file for more details.

## Acknowledgements

- [rcon-cli](https://github.com/gorcon/rcon-cli) - The underlying CLI tool for RCON communication.
- [palworld](https://palworld.gg/) - The game server platform supported by this tool.
41 changes: 13 additions & 28 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
// main.go
package main

import (
"encoding/json"
"fmt"
"log"
"net/http"
"palworld-query-api/internal/config"
"palworld-query-api/internal/server"
"fmt"
"log"
"net/http"
"palworld-query-api/internal/config"
"palworld-query-api/internal/routes"
)

func main() {
cfg := config.ParseFlags()
port := fmt.Sprintf(":%s", config.CONFIG.PORT)

http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
// Respond with 200 OK status
w.WriteHeader(http.StatusOK)
})
// Register healthz route
http.HandleFunc(config.ROUTES.HEALTH, routes.HealthzHandler)

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println("Received API request")
serverData, err := server.GetServerData(cfg)
if err != nil {
log.Printf("Error getting server data: %v\n", err)
http.Error(w, "Error getting server data", http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(serverData)
log.Println("Sent server data to client")
})

log.Printf("server listening on port %d\n", cfg.Port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", cfg.Port), nil))
// Register servers route
http.HandleFunc(config.ROUTES.SERVERS, routes.IndexHandler)

log.Printf("server listening on port %s\n", port)
log.Fatal(http.ListenAndServe(port, nil))
}
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ module palworld-query-api

go 1.22

require gopkg.in/yaml.v2 v2.4.0 // indirect
require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
golang.org/x/sys v0.4.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
123 changes: 88 additions & 35 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,58 +1,111 @@
// internal/config/config.go
package config

import (
"flag"
"log"
"os"
"palworld-query-api/internal/utils"
"strconv"
)

type Config struct {
RconCLIPath string
RconCLIConfig string
Port int
LogsPath string // New field for logs directory
type ConfigServer struct {
Address string `json:"address"`
Password string `json:"password"`
Type string `json:"type"`
Timeout string `json:"timeout"`
}

func ParseFlags() *Config {
var config Config
type JsonServerConfig struct {
Name string `json:"name"`
Address string `json:"address"`
Password string `json:"password"`
Type string `json:"type"`
Timeout string `json:"timeout"`
}

// Check environment variables
config.RconCLIPath = os.Getenv("RCON_CLI_PATH")
config.RconCLIConfig = os.Getenv("RCON_CLI_CONFIG")
config.LogsPath = os.Getenv("LOGS_PATH") // Read the LOGS_PATH environment variable
portEnv := os.Getenv("PORT")
if portEnv != "" {
p, err := strconv.Atoi(portEnv)
if err != nil {
log.Fatalf("Invalid value for PORT environment variable: %v", err)
}
config.Port = p
}
type JsonConfigData struct {
Servers []JsonServerConfig `json:"servers"`
}

// Parse flags if environment variables not set
flag.StringVar(&config.RconCLIPath, "rcon-cli-path", "/app/rcon/rcon", "Path to the rcon-cli executable")
flag.StringVar(&config.RconCLIConfig, "rcon-cli-config", "/config/rcon.yaml", "Path to the rcon-cli config file")
flag.IntVar(&config.Port, "port", 3000, "server port")
flag.StringVar(&config.LogsPath, "logs-path", "/logs", "Path to the directory for log files") // Add logs-path flag
flag.Parse()
// Constants for routes
var ROUTES = struct {
SERVERS string
HEALTH string
}{
SERVERS: "/servers/",
HEALTH: "/healthz",
}

// Check if RconCLIPath exists
if _, err := os.Stat(config.RconCLIPath); os.IsNotExist(err) {
log.Fatalf("RconCLIPath '%s' does not exist", config.RconCLIPath)
}
// Configuration constants
var CONFIG = struct {
// Web port
PORT string
// Root path to rcon file
CLI_ROOT string
// Root path to rcon.yaml
CLI_CONFIG string
// Default rcon env
CLI_DEFAULT_SERVER string
// Default rcon env
LOGS_PATH string
}{}

// Constants for commands
var COMMANDS = struct {
ENV string
CONFIG string
}{
ENV: "--env",
CONFIG: "--config",
}

// Constants for commands
var PALWORLD_RCON_COMMANDS = struct {
INFO string
SHOW_PLAYERS string
}{
INFO: "info",
SHOW_PLAYERS: "showplayers",
}

// Function to set configuration from environment variables
func setConfigFromEnv() {
setIfNotEmpty := func(key string, value *string) {
if env := os.Getenv(key); env != "" {
*value = env
}
}

setIfNotEmpty("PORT", &CONFIG.PORT)
setIfNotEmpty("CLI_ROOT", &CONFIG.CLI_ROOT)
setIfNotEmpty("CLI_CONFIG", &CONFIG.CLI_CONFIG)
setIfNotEmpty("CLI_DEFAULT_SERVER", &CONFIG.CLI_DEFAULT_SERVER)
setIfNotEmpty("LOGS_PATH", &CONFIG.LOGS_PATH)
}

// Parse flags
func init() {
// Set configuration from environment variables
setConfigFromEnv()

flag.StringVar(&CONFIG.PORT, "port", "3000", "Server port")
flag.StringVar(&CONFIG.CLI_ROOT, "cli-root", "/app/rcon/rcon", "Root path to rcon file")
flag.StringVar(&CONFIG.CLI_CONFIG, "cli-config", "/config/rcon.yaml", "Root path to rcon.yaml")
flag.StringVar(&CONFIG.CLI_DEFAULT_SERVER, "cli-def-server", "default", "Default rcon env")
flag.StringVar(&CONFIG.LOGS_PATH, "logs-path", "/logs", "Logs path")
flag.Parse()

// Check if CONFIG_JSON is set
configJSON := os.Getenv("CONFIG_JSON")
if configJSON != "" {
// Update the existing config file if it exists, otherwise create a new one
err := utils.GenerateConfigFromJSON(configJSON, config.RconCLIConfig, config.LogsPath)
err := GenerateConfigFromJSON(configJSON, CONFIG.CLI_CONFIG, CONFIG.LOGS_PATH)
if err != nil {
log.Fatalf("Error generating config from JSON: %v", err)
}
}

return &config
// Log the set flags
log.Printf("Server port: %s", CONFIG.PORT)
log.Printf("Root path to rcon file: %s", CONFIG.CLI_ROOT)
log.Printf("Root path to rcon.yaml: %s", CONFIG.CLI_CONFIG)
log.Printf("Default rcon env: %s", CONFIG.CLI_DEFAULT_SERVER)
log.Printf("Logs path: %s", CONFIG.LOGS_PATH)
}
28 changes: 13 additions & 15 deletions internal/server/server.go → internal/config/server.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// internal/server/server.go
package server
package config

import (
"fmt"
"os/exec"
"strings"
"palworld-query-api/internal/config"
)

type ServerInfo struct {
Expand All @@ -19,16 +16,16 @@ type Players struct {
List []string `json:"list"`
}

func GetServerData(config *config.Config) (*ServerInfo, error) {
func GetServerData(serverName string) (*ServerInfo, error) {
serverInfo := &ServerInfo{}

infoOutput, err := runRCONCommand(config, "info")
infoOutput, err := runRCONCommand(serverName, "info")
if err != nil {
return nil, fmt.Errorf("error running 'rcon-cli info': %v", err)
}
parseServerInfo(infoOutput, serverInfo)

playersOutput, err := runRCONCommand(config, "showplayers")
playersOutput, err := runRCONCommand(serverName, "showplayers")
if err != nil {
return nil, fmt.Errorf("error running 'rcon-cli showplayers': %v", err)
}
Expand All @@ -37,13 +34,12 @@ func GetServerData(config *config.Config) (*ServerInfo, error) {
return serverInfo, nil
}

func runRCONCommand(config *config.Config, command string) (string, error) {
cmd := exec.Command(config.RconCLIPath, "-config", config.RconCLIConfig, command)
output, err := cmd.Output()
if err != nil {
return "", err
}
return string(output), nil
func runRCONCommand(serverName string, command string) (string, error) {
output, err := ExecuteShellCommand(CONFIG.CLI_ROOT, COMMANDS.CONFIG, CONFIG.CLI_CONFIG, COMMANDS.ENV, serverName, command)
if err != nil {
return "", fmt.Errorf("failed to run rcon-cli: %v", err)
}
return string(output), nil // Convert output to string before returning
}

func parseServerInfo(output string, serverInfo *ServerInfo) {
Expand All @@ -62,7 +58,9 @@ func parseServerInfo(output string, serverInfo *ServerInfo) {

func parsePlayerList(output string, serverInfo *ServerInfo) {
lines := strings.Split(output, "\n")
players := Players{}
players := Players{
List: make([]string, 0), // Initialize the list with an empty slice
}
for _, line := range lines {
if !strings.HasPrefix(line, "name,playeruid,steamid") && line != "" {
playerData := strings.Split(line, ",")
Expand Down
Loading

0 comments on commit 592759b

Please sign in to comment.