Skip to content

Commit

Permalink
🐬 all: update shared code
Browse files Browse the repository at this point in the history
Some of the infrastructure code has evolved in other projects. Apply the updates to this project.
  • Loading branch information
database64128 committed Jul 22, 2024
1 parent 021d420 commit 0c1d7dc
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 51 deletions.
94 changes: 67 additions & 27 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package api
import (
"context"
"errors"
"fmt"

v1 "github.com/database64128/shadowsocks-go/api/v1"
"github.com/database64128/shadowsocks-go/api/ssm"
"github.com/database64128/shadowsocks-go/jsonhelper"
"github.com/gofiber/contrib/fiberzap/v2"
"github.com/gofiber/fiber/v2"
Expand All @@ -16,29 +17,53 @@ import (

// Config stores the configuration for the RESTful API.
type Config struct {
// Enabled controls whether the API server is enabled.
Enabled bool `json:"enabled"`

// Debug
// DebugPprof enables pprof endpoints for debugging and profiling.
DebugPprof bool `json:"debugPprof"`

// Reverse proxy
EnableTrustedProxyCheck bool `json:"enableTrustedProxyCheck"`
TrustedProxies []string `json:"trustedProxies"`
ProxyHeader string `json:"proxyHeader"`
// EnableTrustedProxyCheck enables trusted proxy checks.
EnableTrustedProxyCheck bool `json:"enableTrustedProxyCheck"`

// Listen
Listen string `json:"listen"`
CertFile string `json:"certFile"`
KeyFile string `json:"keyFile"`
// TrustedProxies is the list of trusted proxies.
// This only takes effect if EnableTrustedProxyCheck is true.
TrustedProxies []string `json:"trustedProxies"`

// ProxyHeader is the header used to determine the client's IP address.
// If empty, the remote peer's address is used.
ProxyHeader string `json:"proxyHeader"`

// ListenAddress is the address to listen on.
ListenAddress string `json:"listen"`

// CertFile is the path to the certificate file.
// If empty, TLS is disabled.
CertFile string `json:"certFile"`

// KeyFile is the path to the key file.
// This is required if CertFile is set.
KeyFile string `json:"keyFile"`

// ClientCertFile is the path to the client certificate file.
// If empty, client certificate authentication is disabled.
ClientCertFile string `json:"clientCertFile"`

// Misc
SecretPath string `json:"secretPath"`
// StaticPath is the path where static files are served from.
// If empty, static file serving is disabled.
StaticPath string `json:"staticPath"`

// SecretPath adds a secret path prefix to all routes.
// If empty, no secret path is added.
SecretPath string `json:"secretPath"`

// FiberConfigPath overrides the [fiber.Config] settings we use.
// If empty, no overrides are applied.
FiberConfigPath string `json:"fiberConfigPath"`
}

// Server returns a new API server from the config.
func (c *Config) Server(logger *zap.Logger) (*Server, *v1.ServerManager, error) {
func (c *Config) Server(logger *zap.Logger) (*Server, *ssm.ServerManager, error) {
if !c.Enabled {
return nil, nil, nil
}
Expand All @@ -56,8 +81,8 @@ func (c *Config) Server(logger *zap.Logger) (*Server, *v1.ServerManager, error)
}

if c.FiberConfigPath != "" {
if err := jsonhelper.LoadAndDecodeDisallowUnknownFields(c.FiberConfigPath, &fc); err != nil {
return nil, nil, err
if err := jsonhelper.OpenAndDecodeDisallowUnknownFields(c.FiberConfigPath, &fc); err != nil {
return nil, nil, fmt.Errorf("failed to load fiber config: %w", err)
}
}

Expand All @@ -67,24 +92,38 @@ func (c *Config) Server(logger *zap.Logger) (*Server, *v1.ServerManager, error)

app.Use(fiberzap.New(fiberzap.Config{
Logger: logger,
Fields: []string{"latency", "status", "method", "url", "ip"},
}))

if c.DebugPprof {
app.Use(pprof.New())
}

var router fiber.Router = app
if c.SecretPath != "" {
if c.SecretPath[0] != '/' {
c.SecretPath = "/" + c.SecretPath
}
router = app.Group(c.SecretPath)
}

sm := v1.Routes(router)
if c.DebugPprof {
app.Use(pprof.New(pprof.Config{
Prefix: c.SecretPath,
}))
}

api := router.Group("/api")

// /api/ssm/v1
sm := ssm.NewServerManager()
sm.RegisterRoutes(api.Group("/ssm/v1"))

if c.StaticPath != "" {
router.Static("/", c.StaticPath, fiber.Static{
ByteRange: true,
})
}

return &Server{
logger: logger,
app: app,
listen: c.Listen,
listenAddress: c.ListenAddress,
certFile: c.CertFile,
keyFile: c.KeyFile,
clientCertFile: c.ClientCertFile,
Expand All @@ -95,30 +134,31 @@ func (c *Config) Server(logger *zap.Logger) (*Server, *v1.ServerManager, error)
type Server struct {
logger *zap.Logger
app *fiber.App
listen string
listenAddress string
certFile string
keyFile string
clientCertFile string
ctx context.Context
}

// String implements the service.Service String method.
// String implements [service.Service.String].
func (s *Server) String() string {
return "API server"
}

// Start starts the API server.
func (s *Server) Start(ctx context.Context) error {
s.logger.Info("Starting API server", zap.String("listenAddress", s.listenAddress))
s.ctx = ctx
go func() {
var err error
switch {
case s.clientCertFile != "":
err = s.app.ListenMutualTLS(s.listen, s.certFile, s.keyFile, s.clientCertFile)
err = s.app.ListenMutualTLS(s.listenAddress, s.certFile, s.keyFile, s.clientCertFile)
case s.certFile != "":
err = s.app.ListenTLS(s.listen, s.certFile, s.keyFile)
err = s.app.ListenTLS(s.listenAddress, s.certFile, s.keyFile)
default:
err = s.app.Listen(s.listen)
err = s.app.Listen(s.listenAddress)
}
if err != nil {
s.logger.Fatal("Failed to start API server", zap.Error(err))
Expand Down
12 changes: 9 additions & 3 deletions api/v1/servers.go → api/ssm/ssm.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package v1
// Package ssm implements the Shadowsocks Server Management API v1.
package ssm

import (
"errors"
Expand All @@ -8,6 +9,11 @@ import (
"github.com/gofiber/fiber/v2"
)

// StandardError is the standard error response.
type StandardError struct {
Message string `json:"error"`
}

// ServerInfo contains information about the API server.
type ServerInfo struct {
Name string `json:"server"`
Expand Down Expand Up @@ -51,8 +57,8 @@ func (sm *ServerManager) AddServer(name string, cms *cred.ManagedServer, sc stat
sm.managedServerNames = append(sm.managedServerNames, name)
}

// Routes sets up routes for the /v1/servers endpoint.
func (sm *ServerManager) Routes(v1 fiber.Router) {
// RegisterRoutes sets up routes for the /servers endpoint.
func (sm *ServerManager) RegisterRoutes(v1 fiber.Router) {
v1.Get("/servers", sm.ListServers)

server := v1.Group("/servers/:server", sm.ContextManagedServer)
Expand Down
6 changes: 0 additions & 6 deletions api/v1/errors.go

This file was deleted.

10 changes: 0 additions & 10 deletions api/v1/v1.go

This file was deleted.

2 changes: 1 addition & 1 deletion cmd/shadowsocks-go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func main() {
defer logger.Sync()

var sc service.Config
if err = jsonhelper.LoadAndDecodeDisallowUnknownFields(confPath, &sc); err != nil {
if err = jsonhelper.OpenAndDecodeDisallowUnknownFields(confPath, &sc); err != nil {
logger.Fatal("Failed to load config",
zap.String("confPath", confPath),
zap.Error(err),
Expand Down
3 changes: 2 additions & 1 deletion jsonhelper/jsonhelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import (
"os"
)

func LoadAndDecodeDisallowUnknownFields(path string, v any) error {
// OpenAndDecodeDisallowUnknownFields opens the file at path and decodes it into v, disallowing unknown fields.
func OpenAndDecodeDisallowUnknownFields(path string, v any) error {
f, err := os.Open(path)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion logging/zap.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func NewZapLogger(preset string, level zapcore.Level) (*zap.Logger, error) {
case "development":
cfg = zap.NewDevelopmentConfig()
default:
if err := jsonhelper.LoadAndDecodeDisallowUnknownFields(preset, &cfg); err != nil {
if err := jsonhelper.OpenAndDecodeDisallowUnknownFields(preset, &cfg); err != nil {
return nil, fmt.Errorf("failed to load zap logger config from file %q: %w", preset, err)
}
}
Expand Down
4 changes: 2 additions & 2 deletions service/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"net/netip"
"time"

v1 "github.com/database64128/shadowsocks-go/api/v1"
"github.com/database64128/shadowsocks-go/api/ssm"
"github.com/database64128/shadowsocks-go/conn"
"github.com/database64128/shadowsocks-go/cred"
"github.com/database64128/shadowsocks-go/direct"
Expand Down Expand Up @@ -535,7 +535,7 @@ func (sc *ServerConfig) UDPRelay(maxClientPackerHeadroom zerocopy.Headroom) (Rel
}

// PostInit performs post-initialization tasks.
func (sc *ServerConfig) PostInit(credman *cred.Manager, apiSM *v1.ServerManager) error {
func (sc *ServerConfig) PostInit(credman *cred.Manager, apiSM *ssm.ServerManager) error {
var cms *cred.ManagedServer

switch sc.Protocol {
Expand Down

0 comments on commit 0c1d7dc

Please sign in to comment.