diff --git a/cmd/litestreamd/config.go b/cmd/litestream/config.go similarity index 88% rename from cmd/litestreamd/config.go rename to cmd/litestream/config.go index 40acc85..a473bc8 100644 --- a/cmd/litestreamd/config.go +++ b/cmd/litestream/config.go @@ -1,6 +1,7 @@ package main import ( + "flag" "fmt" "io/ioutil" "os" @@ -11,6 +12,11 @@ import ( "gopkg.in/yaml.v2" ) +// Default settings. +const ( + DefaultConfigPath = "~/litestream.yml" +) + // Config represents a configuration file for the litestream daemon. type Config struct { DBs []*DBConfig `yaml:"databases"` @@ -57,3 +63,7 @@ type ReplicaConfig struct { Name string `yaml:"name"` // name of replicator, optional. Path string `yaml:"path"` // used for file replicators } + +func registerConfigFlag(fs *flag.FlagSet, p *string) { + fs.StringVar(p, "config", DefaultConfigPath, "config path") +} diff --git a/cmd/litestream/main.go b/cmd/litestream/main.go new file mode 100644 index 0000000..c5f8b27 --- /dev/null +++ b/cmd/litestream/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "strings" +) + +// Build information. +var ( + Version = "(development build)" +) + +func main() { + log.SetFlags(0) + + m := NewMain() + if err := m.Run(context.Background(), os.Args[1:]); err == flag.ErrHelp { + os.Exit(1) + } else if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +type Main struct{} + +func NewMain() *Main { + return &Main{} +} + +func (m *Main) Run(ctx context.Context, args []string) (err error) { + var cmd string + if len(args) > 0 { + cmd, args = args[0], args[1:] + } + + switch cmd { + case "replicate": + return (&ReplicateCommand{}).Run(ctx, args) + case "version": + return (&VersionCommand{}).Run(ctx, args) + default: + if cmd == "" || cmd == "help" || strings.HasPrefix(cmd, "-") { + m.Usage() + return flag.ErrHelp + } + return fmt.Errorf("litestream %s: unknown command", cmd) + } +} + +func (m *Main) Usage() { + fmt.Println(` +litestream is a tool for replicating SQLite databases. + +Usage: + + litestream [arguments] + +The commands are: + + replicate runs a server to replicate databases + version prints the version +`[1:]) +} diff --git a/cmd/litestreamd/main.go b/cmd/litestream/replicate.go similarity index 50% rename from cmd/litestreamd/main.go rename to cmd/litestream/replicate.go index 5f7addb..76b3d60 100644 --- a/cmd/litestreamd/main.go +++ b/cmd/litestream/replicate.go @@ -5,64 +5,13 @@ import ( "errors" "flag" "fmt" - "log" "os" "os/signal" "github.com/benbjohnson/litestream" ) -// Build information. -var ( - Version = "(development build)" -) - -// Default settings. -const ( - DefaultConfigPath = "~/litestreamd.yml" -) - -func main() { - // Setup signal handler. - ctx, cancel := context.WithCancel(context.Background()) - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func() { <-c; cancel() }() - - // Initialize program and read flags/config. - m := NewMain() - if err := m.ParseFlags(ctx, os.Args[1:]); err == flag.ErrHelp { - os.Exit(1) - } else if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - - // Display version information. - fmt.Printf("litestreamd %s\n", Version) - - // Start monitoring databases. - if err := m.Run(ctx); err != nil { - m.Close() - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - - // Notify user that initialization is done. - fmt.Printf("Initialized with %d databases.\n", len(m.DBs)) - - // Wait for signal to stop program. - <-ctx.Done() - signal.Reset() - - // Gracefully close - if err := m.Close(); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} - -type Main struct { +type ReplicateCommand struct { ConfigPath string Config Config @@ -70,55 +19,71 @@ type Main struct { DBs []*litestream.DB } -func NewMain() *Main { - return &Main{} +func NewReplicateCommand() *ReplicateCommand { + return &ReplicateCommand{} } -// ParseFlags parses the flag set from args & loads the configuration. -func (m *Main) ParseFlags(ctx context.Context, args []string) (err error) { - fs := flag.NewFlagSet("litestreamd", flag.ContinueOnError) - fs.StringVar(&m.ConfigPath, "config", DefaultConfigPath, "configuration path") - fs.Usage = m.Usage +// Run loads all databases specified in the configuration. +func (c *ReplicateCommand) Run(ctx context.Context, args []string) (err error) { + fs := flag.NewFlagSet("litestream-replicate", flag.ContinueOnError) + registerConfigFlag(fs, &c.ConfigPath) + fs.Usage = c.Usage if err := fs.Parse(args); err != nil { return err } - // Initialize log. - log.SetFlags(0) - // Load configuration. - if m.ConfigPath == "" { + if c.ConfigPath == "" { return errors.New("-config required") - } else if m.Config, err = ReadConfigFile(m.ConfigPath); err != nil { + } + config, err := ReadConfigFile(c.ConfigPath) + if err != nil { return err } - return nil -} + // Setup signal handler. + ctx, cancel := context.WithCancel(context.Background()) + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt) + go func() { <-ch; cancel() }() -// Run loads all databases specified in the configuration. -func (m *Main) Run(ctx context.Context) (err error) { - if len(m.Config.DBs) == 0 { + // Display version information. + fmt.Printf("litestream %s\n", Version) + + if len(config.DBs) == 0 { return errors.New("configuration must specify at least one database") } - for _, dbc := range m.Config.DBs { - if err := m.openDB(dbc); err != nil { + for _, dbc := range config.DBs { + if err := c.openDB(dbc); err != nil { return err } } + // Notify user that initialization is done. + fmt.Printf("Initialized with %d databases.\n", len(c.DBs)) + + // Wait for signal to stop program. + <-ctx.Done() + signal.Reset() + + // Gracefully close + if err := c.Close(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + return nil } // openDB instantiates and initializes a DB based on a configuration. -func (m *Main) openDB(config *DBConfig) error { +func (c *ReplicateCommand) openDB(config *DBConfig) error { // Initialize database with given path. db := litestream.NewDB(config.Path) // Instantiate and attach replicators. for _, rconfig := range config.Replicas { - r, err := m.createReplicator(db, rconfig) + r, err := c.createReplicator(db, rconfig) if err != nil { return err } @@ -129,23 +94,23 @@ func (m *Main) openDB(config *DBConfig) error { if err := db.Open(); err != nil { return err } - m.DBs = append(m.DBs, db) + c.DBs = append(c.DBs, db) return nil } // createReplicator instantiates a replicator for a DB based on a config. -func (m *Main) createReplicator(db *litestream.DB, config *ReplicaConfig) (litestream.Replicator, error) { +func (c *ReplicateCommand) createReplicator(db *litestream.DB, config *ReplicaConfig) (litestream.Replicator, error) { switch config.Type { case "", "file": - return m.createFileReplicator(db, config) + return c.createFileReplicator(db, config) default: return nil, fmt.Errorf("unknown replicator type in config: %q", config.Type) } } // createFileReplicator returns a new instance of FileReplicator build from config. -func (m *Main) createFileReplicator(db *litestream.DB, config *ReplicaConfig) (*litestream.FileReplicator, error) { +func (c *ReplicateCommand) createFileReplicator(db *litestream.DB, config *ReplicaConfig) (*litestream.FileReplicator, error) { if config.Path == "" { return nil, fmt.Errorf("file replicator path require for db %q", db.Path()) } @@ -153,8 +118,8 @@ func (m *Main) createFileReplicator(db *litestream.DB, config *ReplicaConfig) (* } // Close closes all open databases. -func (m *Main) Close() (err error) { - for _, db := range m.DBs { +func (c *ReplicateCommand) Close() (err error) { + for _, db := range c.DBs { if e := db.SoftClose(); e != nil { fmt.Printf("error closing db: path=%s err=%s\n", db.Path(), e) if err == nil { @@ -165,18 +130,19 @@ func (m *Main) Close() (err error) { return err } -func (m *Main) Usage() { +func (c *ReplicateCommand) Usage() { fmt.Println(` -litestreamd is a daemon for replicating SQLite databases. +The replicate command starts a server to monitor & replicate databases +specified in your configuration file. Usage: - litestreamd [arguments] + litestream replicate [arguments] Arguments: -config PATH - Specifies the configuration file. Required. + Specifies the configuration file. Defaults to ~/litestream.yml `[1:]) } diff --git a/cmd/litestream/version.go b/cmd/litestream/version.go new file mode 100644 index 0000000..fc5c7ec --- /dev/null +++ b/cmd/litestream/version.go @@ -0,0 +1,31 @@ +package main + +import ( + "context" + "flag" + "fmt" +) + +type VersionCommand struct{} + +func (c *VersionCommand) Run(ctx context.Context, args []string) (err error) { + fs := flag.NewFlagSet("litestream-version", flag.ContinueOnError) + fs.Usage = c.Usage + if err := fs.Parse(args); err != nil { + return err + } + + fmt.Println("litestream", Version) + + return nil +} + +func (c *VersionCommand) Usage() { + fmt.Println(` +Prints the version. + +Usage: + + litestream version +`[1:]) +}