Skip to content

Commit c27363b

Browse files
committed
Deprecate config.json, add config.toml support
1 parent 172b5cd commit c27363b

File tree

8 files changed

+157
-54
lines changed

8 files changed

+157
-54
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# configuration files
2+
*config.toml
23
*config.json
34
users.json
45
logs/

API.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,19 +107,25 @@ Get Octyne's configuration file contents. Added in v1.1.0.
107107

108108
**Response:**
109109

110-
HTTP 200 response with the configuration file contents in the body. The configuration file format is documented in the [README](https://github.com/retrixe/octyne/blob/main/README.md).
110+
HTTP 200 response with the configuration file contents in the body. The configuration file format is documented in the [README](https://github.com/retrixe/octyne/blob/main/README.md) and is formatted with either [TOML (supported since v1.4.0)](https://toml.io) or [HuJSON (⚠️ deprecated)](https://github.com/tailscale/hujson).
111111

112-
⚠️ *Warning:* The configuration file uses [HuJSON](https://github.com/tailscale/hujson) instead of JSON! This means that comments and trailing commas are allowed. Don't parse the body assuming that it's JSON.
112+
The `Content-Type` header will be set to `application/toml` if the config is using [TOML](https://toml.io) or `application/json` if the config is using [HuJSON](https://github.com/tailscale/hujson).
113+
114+
⚠️ *Note:* `Content-Type` was added to the response headers in v1.4.0. If absent, HuJSON can be assumed, since prior versions only support HuJSON.
113115

114116
---
115117

116118
### PATCH /config
117119

118120
Modify Octyne's configuration. Added in v1.1.0.
119121

122+
**Request Headers:**
123+
124+
- `Content-Type` - The content type of the request body (either `application/toml` or `application/json`). If not provided, it will be inferred from the request body. Added in v1.4.0.
125+
120126
**Request Body:**
121127

122-
New configuration file contents in the body. The configuration file format is documented in the [README](https://github.com/retrixe/octyne/blob/main/README.md) and is formatted with [HuJSON](https://github.com/tailscale/hujson).
128+
New configuration file contents in the body. The configuration file format is documented in the [README](https://github.com/retrixe/octyne/blob/main/README.md) and is formatted with either [TOML (supported since v1.4.0)](https://toml.io) or [HuJSON (⚠️ deprecated)](https://github.com/tailscale/hujson).
123129

124130
**Response:**
125131

README.md

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ A process manager with an HTTP API for remote console and file access.
44

55
Octyne allows running multiple apps on a remote server and provides an HTTP API to manage them. This allows for hosting web servers, game servers, bots and so on on remote servers without having to mess with SSH, using `screen` and `systemd` whenever you want to make any change, in a highly manageable and secure way.
66

7-
It incorporates the ability to manage files and access the terminal output and input over HTTP remotely. For further security, it is recommended to use HTTPS (see [config.json](#configjson)) to ensure end-to-end secure transmission.
7+
It incorporates the ability to manage files and access the terminal output and input over HTTP remotely. For further security, it is recommended to use HTTPS (see [config.toml](#configtoml)) to ensure end-to-end secure transmission.
88

99
[retrixe/ecthelion](https://github.com/retrixe/ecthelion) complements octyne by providing a web interface to control apps on octyne remotely.
1010

1111
## Quick Start
1212

1313
- [Download the latest version of Octyne from GitHub Releases for your OS and CPU.](https://github.com/retrixe/octyne/releases/latest) Alternatively, you can get the latest bleeding edge version of Octyne from [GitHub Actions](https://github.com/retrixe/octyne/actions?query=branch%3Amain), or by compiling it yourself.
1414
- Place octyne in a folder (on Linux/macOS/\*nix, mark as executable with `chmod +x <octyne file name>`).
15-
- Create a `config.json` next to Octyne (see [here](https://github.com/retrixe/octyne#configuration) for details).
15+
- Create a `config.toml` next to Octyne (see [here](https://github.com/retrixe/octyne#configuration) for details).
1616
- Run `./<octyne file name>` in a terminal in the folder to start Octyne. An `admin` user will be generated for you.
1717
- You may want to get [Ecthelion](https://github.com/retrixe/ecthelion) to manage Octyne over the internet, and [octynectl](https://github.com/retrixe/octynectl) as a CLI tool to manage Octyne locally on your machine. [Additionally, make sure to follow the security practices here to prevent attacks against your setup!](https://github.com/retrixe/octyne#security-practices-and-reverse-proxying)
1818
- You might want to manage Octyne using systemd on Linux systems, which can start/stop Octyne, start it on boot, store its logs and restart it on crash. [This article should help you out.](https://medium.com/@benmorel/creating-a-linux-service-with-systemd-611b5c8b91d6)
@@ -27,10 +27,61 @@ Octyne depends on two files in the current working directory to get configuratio
2727

2828
The path to these files can be customised using the `--config=/path/to/config.json` and `--users=/path/to/users.json` CLI flags (if relative, resolved relative to the working directory).
2929

30-
### config.json
30+
### config.toml
3131

3232
Used to configure the apps Octyne should start, Redis-based authentication for allowing more than a single node, Unix socket API, and HTTPS support. A reverse proxy can also be used for HTTPS if it supports WSS.
3333

34+
```toml
35+
port = 42069 # optional, default is 42069
36+
37+
[unixSocket]
38+
# enables Unix socket API for auth-less actions by locally running apps e.g. octynectl
39+
enabled = true
40+
# optional, if absent, default is TMP/octyne.sock.PORT (see API.md for details)
41+
# location = ""
42+
# optional, sets the socket's group owner, if absent, default is current user's primary group
43+
# group = ""
44+
45+
[redis]
46+
# whether the authentication tokens should sync to Redis for more than 1 node
47+
enabled = false
48+
# link to Redis server
49+
url = "redis://localhost"
50+
51+
[https]
52+
# whether Octyne should listen using HTTP or HTTPS
53+
enabled = false
54+
# path to HTTPS certificate
55+
cert = "/path/to/cert.pem"
56+
# path to HTTPS private key
57+
key = "/path/to/key.pem"
58+
59+
[logging]
60+
# whether Octyne should log actions
61+
enabled = true
62+
# path to log files, can be relative or absolute
63+
path = "logs"
64+
65+
[logging.actions]
66+
# optional, disable logging for specific actions, more info below
67+
68+
# each key has the name of the server
69+
[servers.test1]
70+
# optional, default true, Octyne won't auto-start when false
71+
# enabled = true
72+
# the directory in which the server is located
73+
directory = "/home/test/server"
74+
# the command to run to start the server
75+
command = "java -jar spigot-1.12.2.jar"
76+
```
77+
78+
### config.json
79+
80+
In Octyne v1.4.0, JSON was replaced with TOML for configuration. However, JSON still works for backwards compatibility, so the documentation below has been retained. Note that JSON is deprecated and will be removed with v2.0. Migrate to v1.4.0 or later to use TOML.
81+
82+
<details>
83+
<summary>config.json example</summary>
84+
3485
*NOTE: Octyne supports comments and trailing commas in the config.json file, they don't need to be removed.*
3586

3687
```jsonc
@@ -65,6 +116,8 @@ Used to configure the apps Octyne should start, Redis-based authentication for a
65116
}
66117
```
67118

119+
</details>
120+
68121
### users.json
69122

70123
Contains users who can log into Octyne. This file is automatically generated on first start with an `admin` user and a generated secure password which is logged to terminal. You can perform account management via Ecthelion, octynectl or other such tools.
@@ -81,7 +134,15 @@ Note: actions performed locally using the Unix socket API by apps like [octynect
81134

82135
**Note: Fine-grained control over logging is currently *experimental*. Therefore, action names may change in any version, not just major versions. However, we will generally try to avoid this in the interest of stability.**
83136

84-
By default, Octyne will log all actions performed by users. You can enable/disable logging for specific actions by setting the action to `true` or `false` in the `logging.actions` object in `config.json`. For example, to disable logging for `auth.login` and `auth.logout`, your `actions` object would be:
137+
By default, Octyne will log all actions performed by users. You can enable/disable logging for specific actions by setting the action to `true` or `false` in the `logging.actions` section in `config.toml`. For example, to disable logging for `auth.login` and `auth.logout`:
138+
139+
```toml
140+
[logging.actions]
141+
"auth.login" = false
142+
"auth.logout" = false
143+
```
144+
145+
Or, with the older `config.json` format:
85146

86147
```json
87148
"actions": {

config.go

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package main
33
import (
44
"encoding/json"
55
"os"
6+
"strings"
67

8+
"github.com/pelletier/go-toml/v2"
79
"github.com/tailscale/hujson"
810
)
911

@@ -19,58 +21,69 @@ var defaultConfig = Config{
1921
Servers: map[string]ServerConfig{},
2022
}
2123

24+
func isJSON(data []byte) bool {
25+
return strings.HasPrefix(strings.TrimSpace(string(data)), "{")
26+
}
27+
2228
func ReadConfig() (Config, error) {
2329
config := defaultConfig
24-
contents, err := os.ReadFile(ConfigJsonPath)
30+
contents, err := os.ReadFile(ConfigFilePath)
2531
if err != nil {
2632
return config, err
2733
}
28-
contents, err = hujson.Standardize(contents)
29-
if err != nil {
30-
return config, err
31-
}
32-
err = json.Unmarshal(contents, &config)
33-
if err != nil {
34-
return config, err
34+
if isJSON(contents) {
35+
contents, err = hujson.Standardize(contents)
36+
if err != nil {
37+
return config, err
38+
}
39+
err = json.Unmarshal(contents, &config)
40+
if err != nil {
41+
return config, err
42+
}
43+
} else {
44+
err = toml.Unmarshal(contents, &config)
45+
if err != nil {
46+
return config, err
47+
}
3548
}
3649
return config, nil
3750
}
3851

3952
// Config is the main config for Octyne.
4053
type Config struct {
41-
Port uint16 `json:"port"`
42-
UnixSocket UnixSocketConfig `json:"unixSocket"`
43-
HTTPS HTTPSConfig `json:"https"`
44-
Redis RedisConfig `json:"redis"`
45-
Logging LoggingConfig `json:"logging"`
46-
Servers map[string]ServerConfig `json:"servers"`
54+
Port uint16 `json:"port" toml:"port"`
55+
UnixSocket UnixSocketConfig `json:"unixSocket" toml:"unixSocket"`
56+
HTTPS HTTPSConfig `json:"https" toml:"https"`
57+
Redis RedisConfig `json:"redis" toml:"redis"`
58+
Logging LoggingConfig `json:"logging" toml:"logging"`
59+
Servers map[string]ServerConfig `json:"servers" toml:"servers"`
4760
}
4861

4962
// RedisConfig contains whether or not Redis is enabled, and if so, how to connect.
5063
type RedisConfig struct {
51-
Enabled bool `json:"enabled"`
52-
URL string `json:"url"`
64+
Enabled bool `json:"enabled" toml:"enabled"`
65+
URL string `json:"url" toml:"url"`
5366
}
5467

5568
// HTTPSConfig contains whether or not HTTPS is enabled, and if so, path to cert and key.
5669
type HTTPSConfig struct {
57-
Enabled bool `json:"enabled"`
58-
Cert string `json:"cert"`
59-
Key string `json:"key"`
70+
Enabled bool `json:"enabled" toml:"enabled"`
71+
Cert string `json:"cert" toml:"cert"`
72+
Key string `json:"key" toml:"key"`
6073
}
6174

6275
// UnixSocketConfig contains whether or not Unix socket is enabled, and if so, path to socket.
6376
type UnixSocketConfig struct {
64-
Enabled bool `json:"enabled"`
65-
Location string `json:"location"`
66-
Group string `json:"group"`
77+
Enabled bool `json:"enabled" toml:"enabled"`
78+
Location string `json:"location" toml:"location"`
79+
Group string `json:"group" toml:"group"`
6780
}
6881

6982
// ServerConfig is the config for individual servers.
7083
type ServerConfig struct {
71-
Enabled bool `json:"enabled"`
72-
Directory string `json:"directory"`
73-
Command string `json:"command"`
84+
Enabled bool `json:"enabled" toml:"enabled"`
85+
Directory string `json:"directory" toml:"directory"`
86+
Command string `json:"command" toml:"command"`
7487
}
7588

7689
// UnmarshalJSON unmarshals ServerConfig and sets default values.
@@ -84,9 +97,9 @@ func (c *ServerConfig) UnmarshalJSON(data []byte) error {
8497

8598
// LoggingConfig is the config for action logging.
8699
type LoggingConfig struct {
87-
Enabled bool `json:"enabled"`
88-
Path string `json:"path"`
89-
Actions map[string]bool `json:"actions"`
100+
Enabled bool `json:"enabled" toml:"enabled"`
101+
Path string `json:"path" toml:"path"`
102+
Actions map[string]bool `json:"actions" toml:"actions"`
90103
}
91104

92105
// ShouldLog returns whether or not a particular action should be logged.

endpoints_misc.go

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
"github.com/gorilla/websocket"
14+
"github.com/pelletier/go-toml/v2"
1415
"github.com/retrixe/octyne/auth"
1516
"github.com/retrixe/octyne/system"
1617
"github.com/tailscale/hujson"
@@ -32,12 +33,17 @@ func configEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request
3233
return
3334
}
3435
if r.Method == "GET" {
35-
contents, err := os.ReadFile(ConfigJsonPath)
36+
contents, err := os.ReadFile(ConfigFilePath)
3637
if err != nil {
37-
log.Println("Error reading "+ConfigJsonPath+" when user accessed /config!", err)
38+
log.Println("Error reading "+ConfigFilePath+" when user accessed /config!", err)
3839
httpError(w, "Internal Server Error!", http.StatusInternalServerError)
3940
return
4041
}
42+
if isJSON(contents) {
43+
w.Header().Set("content-type", "application/json")
44+
} else {
45+
w.Header().Set("content-type", "application/toml")
46+
}
4147
connector.Info("config.view", "ip", GetIP(r), "user", user)
4248
writeJsonStringRes(w, string(contents))
4349
} else if r.Method == "PATCH" {
@@ -49,25 +55,34 @@ func configEndpoint(connector *Connector, w http.ResponseWriter, r *http.Request
4955
}
5056
var origJson = buffer.String()
5157
var config Config
52-
contents, err := hujson.Standardize(buffer.Bytes())
53-
if err != nil {
54-
httpError(w, "Invalid JSON body!", http.StatusBadRequest)
55-
return
56-
}
57-
err = json.Unmarshal(contents, &config)
58-
if err != nil {
59-
httpError(w, "Invalid JSON body!", http.StatusBadRequest)
60-
return
58+
contentType := r.Header.Get("content-type")
59+
if contentType == "application/json" || (contentType == "" && isJSON(buffer.Bytes())) {
60+
contents, err := hujson.Standardize(buffer.Bytes())
61+
if err != nil {
62+
httpError(w, "Invalid JSON body!", http.StatusBadRequest)
63+
return
64+
}
65+
err = json.Unmarshal(contents, &config)
66+
if err != nil {
67+
httpError(w, "Invalid JSON body!", http.StatusBadRequest)
68+
return
69+
}
70+
} else {
71+
err = toml.Unmarshal(buffer.Bytes(), &config)
72+
if err != nil {
73+
httpError(w, "Invalid TOML body!", http.StatusBadRequest)
74+
return
75+
}
6176
}
62-
err = os.WriteFile(ConfigJsonPath+"~", []byte(strings.TrimSpace(origJson)+"\n"), 0666)
77+
err = os.WriteFile(ConfigFilePath+"~", []byte(strings.TrimSpace(origJson)+"\n"), 0666)
6378
if err != nil {
64-
log.Println("Error writing to " + ConfigJsonPath + " when user modified config!")
79+
log.Println("Error writing to " + ConfigFilePath + " when user modified config!")
6580
httpError(w, "Internal Server Error!", http.StatusInternalServerError)
6681
return
6782
}
68-
err = os.Rename(ConfigJsonPath+"~", ConfigJsonPath)
83+
err = os.Rename(ConfigFilePath+"~", ConfigFilePath)
6984
if err != nil {
70-
log.Println("Error writing to " + ConfigJsonPath + " when user modified config!")
85+
log.Println("Error writing to " + ConfigFilePath + " when user modified config!")
7186
httpError(w, "Internal Server Error!", http.StatusInternalServerError)
7287
return
7388
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/gomodule/redigo v2.0.0+incompatible
77
github.com/gorilla/handlers v1.4.2
88
github.com/gorilla/websocket v1.4.2
9+
github.com/pelletier/go-toml/v2 v2.2.3
910
github.com/puzpuzpuz/xsync/v3 v3.0.2
1011
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
1112
go.uber.org/zap v1.24.0

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YAR
1111
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
1212
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
1313
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
14+
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
15+
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
1416
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
1517
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
1618
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -19,8 +21,8 @@ github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbA
1921
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
2022
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
2123
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
22-
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
23-
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
24+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
25+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
2426
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
2527
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
2628
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=

main.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,22 @@ func getPort(config *Config) string {
2828
}
2929

3030
var info *log.Logger
31-
var ConfigJsonPath = "config.json"
31+
var ConfigFilePath = "config.toml"
3232
var UsersJsonPath = "users.json"
3333

3434
func main() {
35+
if _, err := os.Stat(ConfigFilePath); os.IsNotExist(err) {
36+
ConfigFilePath = "config.json" // Fallback to JSON if TOML not available.
37+
}
38+
3539
for _, arg := range os.Args {
3640
if arg == "--help" || arg == "-h" {
3741
println("Usage: " + os.Args[0] + " [--version] [--config=<path>] [--users=<path>]")
3842
return
3943
} else if strings.HasPrefix(arg, "--users=") {
4044
UsersJsonPath = arg[8:]
4145
} else if strings.HasPrefix(arg, "--config=") {
42-
ConfigJsonPath = arg[9:]
46+
ConfigFilePath = arg[9:]
4347
} else if arg == "--version" || arg == "-v" {
4448
println("octyne version " + OctyneVersion)
4549
return

0 commit comments

Comments
 (0)