Browse Source
- 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.main
1 changed files with 200 additions and 0 deletions
@ -0,0 +1,200 @@ |
|||||||
|
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() |
||||||
|
} |
||||||
|
|
||||||
Loading…
Reference in new issue