server

package
v1.3.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 37 Imported by: 0

Documentation

Index

Constants

View Source
const HandshakeReplayLimit = 50

HandshakeReplayLimit is the scrollback window sent on every successful WebSocket handshake.

Variables

View Source
var (
	// ErrAdminSelfTarget is returned when an admin tries to kick or ban themselves.
	ErrAdminSelfTarget = errors.New("cannot kick or ban yourself")
	// ErrKickPermanentlyBanned is returned when KickUser targets a permanently banned user.
	ErrKickPermanentlyBanned = errors.New("cannot kick a permanently banned user")
	// ErrKickNotConnected is returned when KickUser targets a user with no active connection.
	ErrKickNotConnected = errors.New("user is not connected")
)

Sentinel errors for KickUser / BanUser so callers can map clear replies and claim success only when err == nil.

View Source
var (
	ServerLogger   = NewLogger("Server")
	ClientLogger   = NewLogger("Client")
	HubLogger      = NewLogger("Hub")
	AdminLogger    = NewLogger("Admin")
	PluginLogger   = NewLogger("Plugin")
	DatabaseLogger = NewLogger("Database")
	SecurityLogger = NewLogger("Security")
	FilterLogger   = NewLogger("Filter")
)

Global logger instances for different components

Functions

func BackupDatabase

func BackupDatabase(db *sql.DB, dbPath string) (string, error)

BackupDatabase creates a backup of the current database. In-process backup is supported for SQLite only; other dialects require native backup tools.

func ClearMessages

func ClearMessages(db *sql.DB) error

func ConvertPluginMessage

func ConvertPluginMessage(pluginMsg sdk.Message) shared.Message

ConvertPluginMessage converts a plugin message to a shared message

func CreateSchema

func CreateSchema(db *sql.DB)

CreateSchema applies schema migrations and terminates the process on failure. Tests and legacy callers use this wrapper; production startup should call MigrateSchema directly.

func DeleteMessage

func DeleteMessage(db *sql.DB, messageID int64, sender string, isAdmin bool) error

func EditMessage

func EditMessage(db *sql.DB, messageID int64, sender, newContent string, encrypted bool) error

func GetDatabaseStats

func GetDatabaseStats(db *sql.DB) (string, error)

GetDatabaseStats returns statistics about the database

func GetPinnedMessages

func GetPinnedMessages(db *sql.DB) []shared.Message

func GetRecentMessages

func GetRecentMessages(db *sql.DB) []shared.Message

GetRecentMessages returns the newest messages up to HandshakeReplayLimit (admin/utility path).

func GetRecentMessagesForUser

func GetRecentMessagesForUser(db *sql.DB, username string, limit int, banGapsHistory bool) []shared.Message

GetRecentMessagesForUser returns up to limit messages visible to username for handshake replay.

func GetRecentMessagesWithLimit added in v1.3.0

func GetRecentMessagesWithLimit(db *sql.DB, limit int) []shared.Message

func InitDB

func InitDB(conn string) (*sql.DB, error)

func InsertEncryptedMessage

func InsertEncryptedMessage(db *sql.DB, encryptedMsg *shared.EncryptedMessage) error

InsertEncryptedMessage stores an encrypted message in the database

func InsertMessage

func InsertMessage(db *sql.DB, msg shared.Message) (int64, error)

func LoadReactionsForMessages

func LoadReactionsForMessages(db *sql.DB, messageIDs []int64) []shared.Message

func LoadReadReceiptsForMessages

func LoadReadReceiptsForMessages(db *sql.DB, username string, messageIDs []int64) []shared.Message

func LoadUserChannel

func LoadUserChannel(db *sql.DB, username string) string

func LogToFile

func LogToFile(filename string) error

LogToFile enables logging to a file instead of stdout with rotation

func MigrateSchema added in v1.3.5

func MigrateSchema(db *sql.DB) error

MigrateSchema applies ordered schema migrations and verifies required tables exist. Existing databases without a schema_version row run the v1 baseline idempotently, then record version 1.

SQLite and PostgreSQL apply each versioned migration (DDL + version row) inside a single transaction so a mid-migration failure rolls back. MySQL DDL implicitly commits, so MySQL runs steps without a multi-statement transaction; each statement is still statement-atomic on InnoDB, and schema_version is recorded only after applyMigrationV1 returns nil.

func PersistReaction

func PersistReaction(db *sql.DB, msg shared.Message)

func PersistReadReceipt

func PersistReadReceipt(db *sql.DB, username string, messageID int64)

func PersistUserChannel

func PersistUserChannel(db *sql.DB, username, channel string)

func RunAdminPanel

func RunAdminPanel(hub *Hub, db *sql.DB, pluginManager *manager.PluginManager, liveConfig *config.Config) error

RunAdminPanel starts the admin panel TUI

func SearchMessages

func SearchMessages(db *sql.DB, query string, limit int) []shared.Message

func ServeWs

func ServeWs(hub *Hub, db *sql.DB, adminList []string, adminKey string, banGapsHistory bool, maxFileBytes int64, dbPath string) http.HandlerFunc

func SetLogLevel

func SetLogLevel(level LogLevel)

SetLogLevel sets the minimum log level (currently not implemented but ready for future use)

func TogglePinMessage

func TogglePinMessage(db *sql.DB, messageID int64) (bool, error)

Types

type AdminPanel

type AdminPanel struct {
	ServerLogger *Logger
	// contains filtered or unexported fields
}

AdminPanel represents the main admin panel state

func NewAdminPanel

func NewAdminPanel(hub *Hub, db *sql.DB, pluginManager *manager.PluginManager, liveConfig *config.Config) *AdminPanel

NewAdminPanel creates a new admin panel instance

func (*AdminPanel) Init

func (ap *AdminPanel) Init() tea.Cmd

Implement tea.Model interface

func (*AdminPanel) Update

func (ap *AdminPanel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (*AdminPanel) View

func (ap *AdminPanel) View() tea.View

type Client

type Client struct {
	// contains filtered or unexported fields
}

func (*Client) PingConn

func (c *Client) PingConn() error

PingConn sends a WebSocket ping control frame. Safe from goroutines other than writePump; serializes with writePump's pings and JSON writes.

type ComponentHealth

type ComponentHealth struct {
	Status    HealthStatus `json:"status"`
	Message   string       `json:"message,omitempty"`
	LastCheck time.Time    `json:"last_check"`
}

ComponentHealth represents the health of a specific component

type Config deprecated

type Config struct {
	Port     int      `json:"port"`
	Admins   []string `json:"admins"`
	AdminKey string   `json:"admin_key"`
}

Deprecated: Config is the legacy JSON-based server configuration. The main startup path uses config.Config from the config package instead. This type and its loaders are retained only for backward-compatible JSON file loading and may be removed in a future release.

func LoadConfig deprecated

func LoadConfig(path string) (Config, error)

Deprecated: LoadConfig loads configuration from a JSON file. Use config.LoadConfig from the config package for the primary startup path.

func LoadConfigFromDir deprecated

func LoadConfigFromDir(configDir string) (Config, error)

Deprecated: LoadConfigFromDir loads configuration from a directory, checking for JSON config files. Use config.LoadConfig from the config package for the primary startup path.

type DBDialect

type DBDialect string
const (
	DialectSQLite   DBDialect = "sqlite"
	DialectPostgres DBDialect = "postgres"
	DialectMySQL    DBDialect = "mysql"
)

type HealthCheck

type HealthCheck struct {
	Status     HealthStatus               `json:"status"`
	Timestamp  time.Time                  `json:"timestamp"`
	Version    string                     `json:"version"`
	Uptime     string                     `json:"uptime"`
	Components map[string]ComponentHealth `json:"components"`
	Metrics    SystemMetrics              `json:"metrics"`
}

HealthCheck represents a health check response

type HealthChecker

type HealthChecker struct {
	// contains filtered or unexported fields
}

HealthChecker manages health check functionality

func NewHealthChecker

func NewHealthChecker(hub *Hub, db *sql.DB, version string) *HealthChecker

NewHealthChecker creates a new health checker

func (*HealthChecker) CheckHealth

func (hc *HealthChecker) CheckHealth() *HealthCheck

CheckHealth performs a comprehensive health check

func (*HealthChecker) HealthCheckHandler

func (hc *HealthChecker) HealthCheckHandler(w http.ResponseWriter, r *http.Request)

HealthCheckHandler handles HTTP health check requests

func (*HealthChecker) SimpleHealthHandler

func (hc *HealthChecker) SimpleHealthHandler(w http.ResponseWriter, r *http.Request)

SimpleHealthHandler provides a simple health check endpoint

type HealthStatus

type HealthStatus string

HealthStatus represents the overall health status

const (
	HealthStatusHealthy   HealthStatus = "healthy"
	HealthStatusDegraded  HealthStatus = "degraded"
	HealthStatusUnhealthy HealthStatus = "unhealthy"
)

func (HealthStatus) String

func (hs HealthStatus) String() string

String returns the string representation of HealthStatus

type Hub

type Hub struct {
	// contains filtered or unexported fields
}

Hub coordinates WebSocket clients, channels, and moderation state.

Hub mutex and blocking-operation rules (not a strict total lock order):

  1. Never hold banMutex across disconnectClient or any potentially blocking send on client.send (BanUser / KickUser release banMutex first).
  2. clientsMutex then banMutex is OK only as separate critical sections (lookup under clientsMutex, unlock, then mutate bans under banMutex). Do not nest banMutex inside clientsMutex for long operations.
  3. Prefer clientsMutex before channelMutex when both are required. Do not hold banMutex together with channelMutex.
  4. metricsMutex may be taken briefly while already holding clientsMutex on register/unregister paths. Never take clientsMutex while holding metricsMutex.

func NewHub

func NewHub(pluginDir, dataDir, registryURL string, db *sql.DB) (*Hub, error)

func (*Hub) AllowUser

func (h *Hub) AllowUser(username string, adminUsername string) bool

AllowUser removes a user from temporary kick list (override early)

func (*Hub) BanUser

func (h *Hub) BanUser(username string, adminUsername string) error

BanUser adds a user to the permanent ban list. The ban state is recorded under banMutex, then the lock is released before kicking the connected client so that a blocked send channel cannot hold banMutex and stall all other ban/kick callers. Returns ErrAdminSelfTarget when username matches adminUsername (case-insensitive). Offline bans are still allowed when the target is a different user.

func (*Hub) CleanupExpiredBans

func (h *Hub) CleanupExpiredBans()

CleanupExpiredBans removes expired temporary kicks from the lists. Permanent bans have no expiry and are never cleared here.

func (*Hub) CleanupStaleConnections

func (h *Hub) CleanupStaleConnections()

CleanupStaleConnections removes clients with broken connections

func (*Hub) ForceDisconnectUser

func (h *Hub) ForceDisconnectUser(username string, adminUsername string) bool

ForceDisconnectUser forcibly removes a user from the clients map (admin command for stale connections)

func (*Hub) GetPluginManager

func (h *Hub) GetPluginManager() *manager.PluginManager

GetPluginManager returns the plugin manager reference

func (*Hub) GetTotalConnections

func (h *Hub) GetTotalConnections() int

GetTotalConnections returns the total number of connections since server start

func (*Hub) GetTotalDisconnects

func (h *Hub) GetTotalDisconnects() int

GetTotalDisconnects returns the total number of disconnections since server start

func (*Hub) IsUserBanned

func (h *Hub) IsUserBanned(username string) bool

IsUserBanned checks if a user is currently banned or kicked

func (*Hub) KickUser

func (h *Hub) KickUser(username string, adminUsername string) error

KickUser disconnects a connected user and temporarily bans them for 24 hours. The target must have an active WebSocket connection; offline users are not kicked or temp-banned (use BanUser for offline moderation). Like BanUser, banMutex is released before the disconnect to avoid holding the lock across a potentially blocking channel send. Returns ErrAdminSelfTarget when username matches adminUsername (case-insensitive), checked before any tempKicks write. Returns ErrKickNotConnected when the user is not connected. Returns ErrKickPermanentlyBanned when the target is already permanently banned.

func (*Hub) ReleaseUsername

func (h *Hub) ReleaseUsername(username string)

func (*Hub) Run

func (h *Hub) Run()

func (*Hub) TryReserveUsername

func (h *Hub) TryReserveUsername(username string) bool

func (*Hub) UnbanUser

func (h *Hub) UnbanUser(username string, adminUsername string) bool

UnbanUser removes a user from the ban list

type LogBuffer

type LogBuffer struct {
	// contains filtered or unexported fields
}

LogBuffer stores recent log entries in memory for admin panels

func GetLogBuffer

func GetLogBuffer() *LogBuffer

GetLogBuffer returns the global log buffer for admin panel access

func (*LogBuffer) AddEntry

func (lb *LogBuffer) AddEntry(entry LogEntry)

AddEntry adds a log entry to the buffer

func (*LogBuffer) GetEntries

func (lb *LogBuffer) GetEntries() []LogEntry

GetEntries returns a copy of all log entries (newest first)

func (*LogBuffer) GetRecentEntries

func (lb *LogBuffer) GetRecentEntries(count int) []LogEntry

GetRecentEntries returns the most recent N log entries (newest first)

type LogEntry

type LogEntry struct {
	Level     LogLevel               `json:"level"`
	Timestamp time.Time              `json:"timestamp"`
	Component string                 `json:"component"`
	UserID    string                 `json:"user_id,omitempty"`
	Message   string                 `json:"message"`
	Error     string                 `json:"error,omitempty"`
	Data      map[string]interface{} `json:"data,omitempty"`
}

LogEntry represents a structured log entry

type LogLevel

type LogLevel string

LogLevel represents the severity level of a log entry

const (
	LogLevelDebug LogLevel = "DEBUG"
	LogLevelInfo  LogLevel = "INFO"
	LogLevelWarn  LogLevel = "WARN"
	LogLevelError LogLevel = "ERROR"
)

type Logger

type Logger struct {
	// contains filtered or unexported fields
}

Logger provides structured logging functionality

func NewLogger

func NewLogger(component string) *Logger

NewLogger creates a new logger instance for a specific component

func (*Logger) Debug

func (l *Logger) Debug(message string, data ...map[string]interface{})

Debug logs a debug message

func (*Logger) Error

func (l *Logger) Error(message string, err error, data ...map[string]interface{})

Error logs an error message

func (*Logger) Info

func (l *Logger) Info(message string, data ...map[string]interface{})

Info logs an info message

func (*Logger) Warn

func (l *Logger) Warn(message string, data ...map[string]interface{})

Warn logs a warning message

func (*Logger) WithUser

func (l *Logger) WithUser(userID string) *Logger

WithUser creates a new logger instance with a specific user ID

type PluginCommandHandler

type PluginCommandHandler struct {
	// contains filtered or unexported fields
}

PluginCommandHandler handles plugin-related commands

func NewPluginCommandHandler

func NewPluginCommandHandler(pluginManager *manager.PluginManager) *PluginCommandHandler

NewPluginCommandHandler creates a new plugin command handler

func (*PluginCommandHandler) GetPluginMessageChannel

func (h *PluginCommandHandler) GetPluginMessageChannel() <-chan sdk.Message

GetPluginMessageChannel returns the channel for receiving messages from plugins

func (*PluginCommandHandler) HandlePluginCommand

func (h *PluginCommandHandler) HandlePluginCommand(cmd string, args []string, isAdmin bool) (string, error)

HandlePluginCommand handles plugin-related commands

func (*PluginCommandHandler) SendMessageToPlugins

func (h *PluginCommandHandler) SendMessageToPlugins(msg shared.Message)

SendMessageToPlugins sends a message to all enabled plugins

func (*PluginCommandHandler) UpdateUserListForPlugins

func (h *PluginCommandHandler) UpdateUserListForPlugins(users []string)

UpdateUserListForPlugins updates the user list for plugins

type ServerConfig

type ServerConfig struct {
	AdminKey      string
	AdminUsers    string
	Port          string
	SessionSecret string
}

func RunServerConfig

func RunServerConfig() (*ServerConfig, error)

RunServerConfig runs the interactive server configuration UI

type ServerConfigModel

type ServerConfigModel struct {
	// contains filtered or unexported fields
}

func NewServerConfigUI

func NewServerConfigUI() ServerConfigModel

func (ServerConfigModel) GetConfig

func (m ServerConfigModel) GetConfig() *ServerConfig

GetConfig returns the built configuration

func (ServerConfigModel) Init

func (m ServerConfigModel) Init() tea.Cmd

func (ServerConfigModel) IsCancelled

func (m ServerConfigModel) IsCancelled() bool

IsCancelled returns true if the user cancelled the configuration

func (ServerConfigModel) IsFinished

func (m ServerConfigModel) IsFinished() bool

IsFinished returns true if the user completed the configuration

func (ServerConfigModel) Update

func (m ServerConfigModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (ServerConfigModel) View

func (m ServerConfigModel) View() tea.View

type SystemMetrics

type SystemMetrics struct {
	MemoryUsage    float64 `json:"memory_usage_mb"`
	Goroutines     int     `json:"goroutines"`
	ActiveUsers    int     `json:"active_users"`
	TotalMessages  int     `json:"total_messages"`
	DatabaseStatus string  `json:"database_status"`
}

SystemMetrics represents system performance metrics

type UserList

type UserList struct {
	Users []string `json:"users"`
}

type WSMessage

type WSMessage struct {
	Type string          `json:"type"`
	Data json.RawMessage `json:"data"`
}

type WebAdminServer

type WebAdminServer struct {
	// contains filtered or unexported fields
}

func NewWebAdminServer

func NewWebAdminServer(hub *Hub, db *sql.DB, cfg *config.Config) *WebAdminServer

NewWebAdminServer creates a new web admin server with full functionality

func (*WebAdminServer) RegisterRoutes

func (w *WebAdminServer) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes attaches all web admin routes to mux

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL