- Introduced a new package `run` with a `Relay` struct to manage the lifecycle of a relay instance. - Implemented `Start` and `Stop` methods for initializing and gracefully shutting down the relay, including options for log capturing and data directory cleanup. - Added methods to retrieve captured stdout and stderr logs. - Enhanced configuration handling for data directory and logging based on user-defined options.
201 lines
4.9 KiB
Go
201 lines
4.9 KiB
Go
package run
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/adrg/xdg"
|
|
"lol.mleku.dev/chk"
|
|
lol "lol.mleku.dev"
|
|
"next.orly.dev/app"
|
|
"next.orly.dev/app/config"
|
|
"next.orly.dev/pkg/acl"
|
|
"next.orly.dev/pkg/database"
|
|
)
|
|
|
|
// Options configures relay startup behavior.
|
|
type Options struct {
|
|
// CleanupDataDir controls whether the data directory is deleted on Stop().
|
|
// Defaults to true. Set to false to preserve the data directory.
|
|
CleanupDataDir *bool
|
|
|
|
// StdoutWriter is an optional writer to receive stdout logs.
|
|
// If nil, stdout will be captured to a buffer accessible via Relay.Stdout().
|
|
StdoutWriter io.Writer
|
|
|
|
// StderrWriter is an optional writer to receive stderr logs.
|
|
// If nil, stderr will be captured to a buffer accessible via Relay.Stderr().
|
|
StderrWriter io.Writer
|
|
}
|
|
|
|
// Relay represents a running relay instance that can be started and stopped.
|
|
type Relay struct {
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
db *database.D
|
|
quit chan struct{}
|
|
dataDir string
|
|
cleanupDataDir bool
|
|
|
|
// Log capture
|
|
stdoutBuf *bytes.Buffer
|
|
stderrBuf *bytes.Buffer
|
|
stdoutWriter io.Writer
|
|
stderrWriter io.Writer
|
|
logMu sync.RWMutex
|
|
}
|
|
|
|
// Start initializes and starts a relay with the given configuration.
|
|
// It bypasses the configuration loading step and uses the provided config directly.
|
|
//
|
|
// Parameters:
|
|
// - cfg: The configuration to use for the relay
|
|
// - opts: Optional configuration for relay behavior. If nil, defaults are used.
|
|
//
|
|
// Returns:
|
|
// - relay: A Relay instance that can be used to stop the relay
|
|
// - err: An error if initialization or startup fails
|
|
func Start(cfg *config.C, opts *Options) (relay *Relay, err error) {
|
|
relay = &Relay{
|
|
cleanupDataDir: true,
|
|
}
|
|
|
|
// Apply options
|
|
var userStdoutWriter, userStderrWriter io.Writer
|
|
if opts != nil {
|
|
if opts.CleanupDataDir != nil {
|
|
relay.cleanupDataDir = *opts.CleanupDataDir
|
|
}
|
|
userStdoutWriter = opts.StdoutWriter
|
|
userStderrWriter = opts.StderrWriter
|
|
}
|
|
|
|
// Set up log capture buffers
|
|
relay.stdoutBuf = &bytes.Buffer{}
|
|
relay.stderrBuf = &bytes.Buffer{}
|
|
|
|
// Build writers list for stdout
|
|
stdoutWriters := []io.Writer{relay.stdoutBuf}
|
|
if userStdoutWriter != nil {
|
|
stdoutWriters = append(stdoutWriters, userStdoutWriter)
|
|
}
|
|
stdoutWriters = append(stdoutWriters, os.Stdout)
|
|
relay.stdoutWriter = io.MultiWriter(stdoutWriters...)
|
|
|
|
// Build writers list for stderr
|
|
stderrWriters := []io.Writer{relay.stderrBuf}
|
|
if userStderrWriter != nil {
|
|
stderrWriters = append(stderrWriters, userStderrWriter)
|
|
}
|
|
stderrWriters = append(stderrWriters, os.Stderr)
|
|
relay.stderrWriter = io.MultiWriter(stderrWriters...)
|
|
|
|
// Set up logging - write to appropriate destination and capture
|
|
if cfg.LogToStdout {
|
|
lol.Writer = relay.stdoutWriter
|
|
} else {
|
|
lol.Writer = relay.stderrWriter
|
|
}
|
|
lol.SetLogLevel(cfg.LogLevel)
|
|
|
|
// Expand DataDir if needed
|
|
if cfg.DataDir == "" || strings.Contains(cfg.DataDir, "~") {
|
|
cfg.DataDir = filepath.Join(xdg.DataHome, cfg.AppName)
|
|
}
|
|
relay.dataDir = cfg.DataDir
|
|
|
|
// Create context
|
|
relay.ctx, relay.cancel = context.WithCancel(context.Background())
|
|
|
|
// Initialize database
|
|
if relay.db, err = database.New(
|
|
relay.ctx, relay.cancel, cfg.DataDir, cfg.DBLogLevel,
|
|
); chk.E(err) {
|
|
return
|
|
}
|
|
|
|
// Configure ACL
|
|
acl.Registry.Active.Store(cfg.ACLMode)
|
|
if err = acl.Registry.Configure(cfg, relay.db, relay.ctx); chk.E(err) {
|
|
return
|
|
}
|
|
acl.Registry.Syncer()
|
|
|
|
// Start the relay
|
|
relay.quit = app.Run(relay.ctx, cfg, relay.db)
|
|
|
|
return
|
|
}
|
|
|
|
// Stop gracefully stops the relay by canceling the context and closing the database.
|
|
// If CleanupDataDir is enabled (default), it also removes the data directory.
|
|
//
|
|
// Returns:
|
|
// - err: An error if shutdown fails
|
|
func (r *Relay) Stop() (err error) {
|
|
if r.cancel != nil {
|
|
r.cancel()
|
|
}
|
|
if r.quit != nil {
|
|
<-r.quit
|
|
}
|
|
if r.db != nil {
|
|
err = r.db.Close()
|
|
}
|
|
// Clean up data directory if enabled
|
|
if r.cleanupDataDir && r.dataDir != "" {
|
|
if rmErr := os.RemoveAll(r.dataDir); rmErr != nil {
|
|
if err == nil {
|
|
err = rmErr
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Stdout returns the complete stdout log buffer contents.
|
|
func (r *Relay) Stdout() string {
|
|
r.logMu.RLock()
|
|
defer r.logMu.RUnlock()
|
|
if r.stdoutBuf == nil {
|
|
return ""
|
|
}
|
|
return r.stdoutBuf.String()
|
|
}
|
|
|
|
// Stderr returns the complete stderr log buffer contents.
|
|
func (r *Relay) Stderr() string {
|
|
r.logMu.RLock()
|
|
defer r.logMu.RUnlock()
|
|
if r.stderrBuf == nil {
|
|
return ""
|
|
}
|
|
return r.stderrBuf.String()
|
|
}
|
|
|
|
// StdoutBytes returns the complete stdout log buffer as bytes.
|
|
func (r *Relay) StdoutBytes() []byte {
|
|
r.logMu.RLock()
|
|
defer r.logMu.RUnlock()
|
|
if r.stdoutBuf == nil {
|
|
return nil
|
|
}
|
|
return r.stdoutBuf.Bytes()
|
|
}
|
|
|
|
// StderrBytes returns the complete stderr log buffer as bytes.
|
|
func (r *Relay) StderrBytes() []byte {
|
|
r.logMu.RLock()
|
|
defer r.logMu.RUnlock()
|
|
if r.stderrBuf == nil {
|
|
return nil
|
|
}
|
|
return r.stderrBuf.Bytes()
|
|
}
|
|
|