mirror of
https://github.com/renorris/openfsd
synced 2026-03-22 06:25:35 +08:00
add data API calls
- /api/v1/data/status.txt - /api/v1/data/servers.json - /api/v1/data/servers.txt
This commit is contained in:
31
README.md
31
README.md
@@ -37,19 +37,21 @@ Persistent storage utilizes MySQL. You will need a MySQL server to point openfsd
|
||||
|
||||
Use the following environment variables to configure the server:
|
||||
|
||||
| Variable Name | Default Value | Description |
|
||||
|-----------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `FSD_ADDR` | 0.0.0.0:6809 | FSD listen address |
|
||||
| `HTTP_ADDR` | 0.0.0.0:8080 | HTTP listen address |
|
||||
| `TLS_CERT_FILE` | | TLS certificate file path (setting this enables HTTPS. otherwise, plaintext HTTP will be used.) |
|
||||
| `TLS_KEY_FILE` | | TLS key file path |
|
||||
| `IN_MEMORY_DB` | false | Enables an ephemeral in-memory database in place of a real MySQL server.<br>This should only be used for testing. |
|
||||
| `MYSQL_USER` | | MySQL username |
|
||||
| `MYSQL_PASS` | | MySQL password |
|
||||
| `MYSQL_NET` | | MySQL network protocol e.g. `tcp` |
|
||||
| `MYSQL_ADDR` | | MySQL network address e.g. `127.0.0.1:3306` |
|
||||
| `MYSQL_DBNAME` | | MySQL database name |
|
||||
| `MOTD` | openfsd | "Message of the Day." This text is sent as a chat message to each client upon successful login to FSD. |
|
||||
| Variable Name | Default Value | Description |
|
||||
|-----------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `FSD_ADDR` | 0.0.0.0:6809 | FSD listen address |
|
||||
| `HTTP_ADDR` | 0.0.0.0:8080 | HTTP listen address |
|
||||
| `DOMAIN_NAME` | | Server domain name. This is required to properly set status.txt content.<br>e.g. `myopenfsdserver.com`<br>Optionally use `HTTP_DOMAIN_NAME` and `FSD_DOMAIN_NAME` for more granularity if required. |
|
||||
| `TLS_ENABLED` | false | Whether to **flag** that TLS is enabled somewhere between openfsd and the client, so the status.txt API will format properly. This will **not** enable TLS for the internal HTTP server. Use TLS_CERT_FILE and TLS_KEY_FILE for that. |
|
||||
| `TLS_CERT_FILE` | | TLS certificate file path (setting this enables HTTPS. otherwise, plaintext HTTP will be used.) |
|
||||
| `TLS_KEY_FILE` | | TLS key file path |
|
||||
| `IN_MEMORY_DB` | false | Enables an ephemeral in-memory database in place of a real MySQL server.<br>This should only be used for testing. |
|
||||
| `MYSQL_USER` | | MySQL username |
|
||||
| `MYSQL_PASS` | | MySQL password |
|
||||
| `MYSQL_NET` | | MySQL network protocol e.g. `tcp` |
|
||||
| `MYSQL_ADDR` | | MySQL network address e.g. `127.0.0.1:3306` |
|
||||
| `MYSQL_DBNAME` | | MySQL database name |
|
||||
| `MOTD` | openfsd | "Message of the Day." This text is sent as a chat message to each client upon successful login to FSD. |
|
||||
|
||||
For 99.9% of use cases, it is also recommended to set:
|
||||
```
|
||||
@@ -97,6 +99,9 @@ Administrators and supervisors can create/mutate user records via the administra
|
||||
|
||||
- `/api/v1/users` (See [documentation](https://github.com/renorris/openfsd/tree/main/web))
|
||||
- `/api/v1/data/openfsd-data.json` VATSIM-esque [data feed](https://github.com/renorris/openfsd/blob/main/web/DATAFEED.md)
|
||||
- `/api/v1/data/status.txt` VATSIM-esque [status.txt](https://status.vatsim.net)
|
||||
- `/api/v1/data/servers.txt` VATSIM-esque [servers.txt](https://data.vatsim.net/vatsim-servers.txt)
|
||||
- `/api/v1/data/servers.json` VATSIM-esque [servers.json](https://data.vatsim.net/v3/vatsim-servers.json)
|
||||
- `/login ... etc` front-end interface
|
||||
|
||||
## Connecting
|
||||
|
||||
@@ -51,6 +51,11 @@ func (s *HTTPService) boot(ctx context.Context, listener net.Listener) (err erro
|
||||
// data feed
|
||||
mux.HandleFunc("GET /api/v1/data/openfsd-data.json", web.DataFeedHandler)
|
||||
|
||||
// status.txt, servers.txt, servers.json
|
||||
mux.HandleFunc("GET /api/v1/data/status.txt", web.StatusTxtHandler)
|
||||
mux.HandleFunc("GET /api/v1/data/servers.txt", web.ServerListTxtHandler)
|
||||
mux.HandleFunc("GET /api/v1/data/servers.json", web.ServerListJsonHandler)
|
||||
|
||||
// favicon
|
||||
mux.HandleFunc("/favicon.ico", web.FaviconHandler)
|
||||
|
||||
|
||||
@@ -54,17 +54,21 @@ func DataFeed() *datafeed.DataFeed {
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
FSDListenAddress string `env:"FSD_ADDR, default=0.0.0.0:6809"` // FSD network frontend/port
|
||||
HTTPListenAddress string `env:"HTTP_ADDR, default=0.0.0.0:8080"` // HTTP network frontend/port
|
||||
TLSCertFile string `env:"TLS_CERT_FILE"` // TLS certificate file path
|
||||
TLSKeyFile string `env:"TLS_KEY_FILE"` // TLS key file path
|
||||
MySQLUser string `env:"MYSQL_USER"` // MySQL username
|
||||
MySQLPass string `env:"MYSQL_PASS"` // MySQL password
|
||||
MySQLNet string `env:"MYSQL_NET"` // MySQL network protocol e.g. tcp, unix, etc
|
||||
MySQLAddr string `env:"MYSQL_ADDR"` // MySQL network address e.g. 127.0.0.1:3306
|
||||
MySQLDBName string `env:"MYSQL_DBNAME"` // MySQL database name
|
||||
InMemoryDB bool `env:"IN_MEMORY_DB, default=false"` // Whether to use an ephemeral in-memory DB instead of a real MySQL server
|
||||
MOTD string `env:"MOTD, default=openfsd"` // Server "Message of the Day"
|
||||
FSDListenAddress string `env:"FSD_ADDR, default=0.0.0.0:6809"` // FSD network frontend/port
|
||||
HTTPListenAddress string `env:"HTTP_ADDR, default=0.0.0.0:8080"` // HTTP network frontend/port
|
||||
TLSEnabled bool `env:"TLS_ENABLED"` // Whether to **flag** that TLS is enabled somewhere between openfsd and the client
|
||||
TLSCertFile string `env:"TLS_CERT_FILE"` // TLS certificate file path
|
||||
TLSKeyFile string `env:"TLS_KEY_FILE"` // TLS key file path
|
||||
DomainName string `env:"DOMAIN_NAME, default=INCORRECT_DOMAIN_NAME_CONFIGURATION"` // Server domain name e.g. myserver.com
|
||||
HTTPDomainName string `env:"HTTP_DOMAIN_NAME"` // HTTP domain name e.g. web.myserver.com (overrides DOMAIN_NAME for HTTP services if set)
|
||||
FSDDomainName string `env:"FSD_DOMAIN_NAME"` // FSD domain name e.g. fsd.myserver.com (overrides DOMAIN_NAME for FSD services if set)
|
||||
MySQLUser string `env:"MYSQL_USER"` // MySQL username
|
||||
MySQLPass string `env:"MYSQL_PASS"` // MySQL password
|
||||
MySQLNet string `env:"MYSQL_NET"` // MySQL network protocol e.g. tcp, unix, etc
|
||||
MySQLAddr string `env:"MYSQL_ADDR"` // MySQL network address e.g. 127.0.0.1:3306
|
||||
MySQLDBName string `env:"MYSQL_DBNAME"` // MySQL database name
|
||||
InMemoryDB bool `env:"IN_MEMORY_DB, default=false"` // Whether to use an ephemeral in-memory DB instead of a real MySQL server
|
||||
MOTD string `env:"MOTD, default=openfsd"` // Server "Message of the Day"
|
||||
}
|
||||
|
||||
type ServerContext struct {
|
||||
@@ -101,6 +105,11 @@ func New() *ServerContext {
|
||||
server.config.MySQLPass = ""
|
||||
}
|
||||
|
||||
// Ensure TLSEnabled is set if TLS for the internal HTTP server is enabled
|
||||
if server.config.TLSCertFile != "" {
|
||||
server.config.TLSEnabled = true
|
||||
}
|
||||
|
||||
// Load the JWT private key
|
||||
server.jwtKey = loadOrCreateJWTKey(privateKeyFile)
|
||||
|
||||
|
||||
128
web/serverlist_handler.go
Normal file
128
web/serverlist_handler.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/renorris/openfsd/servercontext"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TXT
|
||||
|
||||
const serverlistTextFormat = `!GENERAL:
|
||||
VERSION = 8
|
||||
RELOAD = 2
|
||||
UPDATE = 20220401021210
|
||||
ATIS ALLOW MIN = 5
|
||||
CONNECTED CLIENTS = 1
|
||||
;
|
||||
;
|
||||
!SERVERS:
|
||||
{SERVERS_LIST}
|
||||
;
|
||||
; END`
|
||||
|
||||
var formattedServerListTxt string
|
||||
|
||||
func formatServerListTxt() string {
|
||||
|
||||
var domainName string
|
||||
if servercontext.Config().FSDDomainName != "" {
|
||||
domainName = servercontext.Config().FSDDomainName
|
||||
} else {
|
||||
domainName = servercontext.Config().DomainName
|
||||
}
|
||||
|
||||
serversList := fmt.Sprintf("OPENFSD:%s:Everywhere:OPENFSD:1:", domainName)
|
||||
return strings.Replace(serverlistTextFormat, "{SERVERS_LIST}", serversList, -1)
|
||||
}
|
||||
|
||||
var formattedServerListTxtEtag string
|
||||
|
||||
// JSON
|
||||
|
||||
type serverListEntry struct {
|
||||
Ident string `json:"ident"`
|
||||
HostnameOrIP string `json:"hostname_or_ip"`
|
||||
Location string `json:"location"`
|
||||
Name string `json:"name"`
|
||||
ClientsConnectionAllowed int `json:"clients_connection_allowed"`
|
||||
ClientConnectionAllowed bool `json:"client_connections_allowed"`
|
||||
IsSweatbox bool `json:"is_sweatbox"`
|
||||
}
|
||||
|
||||
var formattedServerListJson = ""
|
||||
|
||||
func formatServerListJson() (str string, err error) {
|
||||
var domainName string
|
||||
if servercontext.Config().FSDDomainName != "" {
|
||||
domainName = servercontext.Config().FSDDomainName
|
||||
} else {
|
||||
domainName = servercontext.Config().DomainName
|
||||
}
|
||||
|
||||
serverList := []serverListEntry{{
|
||||
Ident: "OPENFSD",
|
||||
HostnameOrIP: domainName,
|
||||
Location: "Everywhere",
|
||||
Name: "OPENFSD",
|
||||
ClientsConnectionAllowed: 1,
|
||||
ClientConnectionAllowed: true,
|
||||
IsSweatbox: false,
|
||||
}}
|
||||
|
||||
var serverListBytes []byte
|
||||
if serverListBytes, err = json.Marshal(&serverList); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
str = string(serverListBytes)
|
||||
return
|
||||
}
|
||||
|
||||
var formattedServerListJSONEtag = ""
|
||||
|
||||
// ServerListJsonHandler handles json server list calls
|
||||
func ServerListJsonHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if formattedServerListJson == "" {
|
||||
var err error
|
||||
if formattedServerListJson, err = formatServerListJson(); err != nil {
|
||||
http.Error(w, "internal server error: error marshalling server list JSON", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if formattedServerListJSONEtag == "" {
|
||||
formattedServerListJSONEtag = getEtag(formattedServerListJson)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("ETag", formattedServerListJSONEtag)
|
||||
w.Header().Set("Cache-Control", "max-age=60")
|
||||
|
||||
w.WriteHeader(200)
|
||||
|
||||
io.Copy(w, bytes.NewReader([]byte(formattedServerListJson)))
|
||||
}
|
||||
|
||||
// ServerListTxtHandler handles text/plain server list calls
|
||||
func ServerListTxtHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if formattedServerListTxt == "" {
|
||||
formattedServerListTxt = formatServerListTxt()
|
||||
}
|
||||
|
||||
if formattedServerListTxtEtag == "" {
|
||||
formattedServerListTxtEtag = getEtag(formattedServerListTxt)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("ETag", formattedServerListTxtEtag)
|
||||
w.Header().Set("Cache-Control", "max-age=60")
|
||||
|
||||
w.WriteHeader(200)
|
||||
|
||||
io.Copy(w, bytes.NewReader([]byte(formattedServerListTxt)))
|
||||
}
|
||||
61
web/statustxt_handler.go
Normal file
61
web/statustxt_handler.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/renorris/openfsd/servercontext"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const statusFormat = `; IMPORTANT NOTE: This file can change as data sources change. Please check at regular intervals.
|
||||
120218:NOTCP
|
||||
;
|
||||
json3={OPENFSD_ADDRESS}/api/v1/data/openfsd-data.json
|
||||
;
|
||||
url1={OPENFSD_ADDRESS}/api/v1/data/servers.txt
|
||||
;
|
||||
servers.live={OPENFSD_ADDRESS}/api/v1/data/servers.txt
|
||||
;
|
||||
voice0=afv
|
||||
;
|
||||
; END`
|
||||
|
||||
var formattedStatusTxt string
|
||||
|
||||
func formatStatusTxt() string {
|
||||
openfsdAddress := ""
|
||||
if servercontext.Config().TLSEnabled {
|
||||
openfsdAddress += "https://"
|
||||
} else {
|
||||
openfsdAddress += "http://"
|
||||
}
|
||||
|
||||
if servercontext.Config().HTTPDomainName != "" {
|
||||
openfsdAddress += servercontext.Config().HTTPDomainName
|
||||
} else {
|
||||
openfsdAddress += servercontext.Config().DomainName
|
||||
}
|
||||
|
||||
return strings.Replace(statusFormat, "{OPENFSD_ADDRESS}", openfsdAddress, -1)
|
||||
}
|
||||
|
||||
var formattedStatusTxtEtag string
|
||||
|
||||
func StatusTxtHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if formattedStatusTxt == "" {
|
||||
formattedStatusTxt = formatStatusTxt()
|
||||
}
|
||||
|
||||
if formattedStatusTxtEtag == "" {
|
||||
formattedStatusTxtEtag = getEtag(formattedStatusTxt)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("ETag", formattedStatusTxtEtag)
|
||||
w.Header().Set("Cache-Control", "max-age=60")
|
||||
|
||||
w.WriteHeader(200)
|
||||
|
||||
io.Copy(w, bytes.NewReader([]byte(formattedStatusTxt)))
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package web
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
@@ -24,3 +25,9 @@ func generateRandomPassword() (string, error) {
|
||||
|
||||
return hex.EncodeToString(randBytes), nil
|
||||
}
|
||||
|
||||
func getEtag(str string) string {
|
||||
sum := sha1.Sum([]byte(str))
|
||||
sumSlice := sum[:]
|
||||
return hex.EncodeToString(sumSlice)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user