First split into crockery init
and crockery run
The init command creates a crockery.db file containing the domain name and TLS keypair. The run command starts IMAP and SMTP services based on that file. Supporting only a single domain is starting to look a bit unnecessary. We'll see how that goes.
This commit is contained in:
@@ -2,58 +2,145 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"ur.gs/crockery/internal/services"
|
||||
"ur.gs/crockery/internal/store"
|
||||
)
|
||||
|
||||
var (
|
||||
domain = flag.String("domain", "", "Domain to serve email for")
|
||||
certFile = flag.String("cert", "", "Path to a PEM-encoded certificate bundle for TLS support")
|
||||
keyFile = flag.String("key", "", "Path to a PEM-encoded key for TLS support")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *domain == "" {
|
||||
log.Fatal("A domain must be specified on the command line (for now!)")
|
||||
app := cli.NewApp()
|
||||
app.EnableBashCompletion = true
|
||||
app.Name = "crockery"
|
||||
app.Usage = "A vertically integrated, single-domain email system"
|
||||
app.Authors = []cli.Author{
|
||||
{Name: "Nick Thomas", Email: "crockery@ur.gs"},
|
||||
}
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "db",
|
||||
Usage: "Database file to use",
|
||||
EnvVar: "CROCKERY_DB",
|
||||
Value: "crockery.db",
|
||||
},
|
||||
}
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "init",
|
||||
Usage: "Create a new Crockery database",
|
||||
Action: crockeryInit,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "domain",
|
||||
Usage: "Domain to serve email for",
|
||||
EnvVar: "CROCKERY_DOMAIN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cert",
|
||||
Usage: "File containing the PEM-encoded certificate bundle for the domain",
|
||||
EnvVar: "CROCKERY_CERT_FILE",
|
||||
},
|
||||
|
||||
cli.StringFlag{
|
||||
Name: "key",
|
||||
Usage: "File containing the PEM-encoded private key for the domain",
|
||||
EnvVar: "CROCKERY_KEY_FILE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "run",
|
||||
Usage: "Run crockery",
|
||||
Action: crockeryRun,
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func crockeryInit(c *cli.Context) error {
|
||||
db := c.GlobalString("db")
|
||||
domain := c.String("domain")
|
||||
certFile := c.String("cert")
|
||||
keyFile := c.String("key")
|
||||
|
||||
if db == "" {
|
||||
return fmt.Errorf("No crockery database file specified")
|
||||
}
|
||||
|
||||
if domain == "" {
|
||||
return fmt.Errorf("A domain must be specified")
|
||||
}
|
||||
|
||||
// FIXME: we can make cert+key optional at some point and have them be
|
||||
// generated on-demand via ACME during `crockery run` instead.
|
||||
if certFile == "" {
|
||||
return fmt.Errorf("A certificate file must be specified")
|
||||
}
|
||||
|
||||
if keyFile == "" {
|
||||
return fmt.Errorf("A key file must be specified")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(db); !os.IsNotExist(err) {
|
||||
return fmt.Errorf("File %q already exists, refusing to overwrite", db)
|
||||
}
|
||||
|
||||
certPEM, err := ioutil.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to read certificate from %q: %v", certFile, err)
|
||||
}
|
||||
|
||||
keyPEM, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to read key from %q: %v", keyFile, err)
|
||||
}
|
||||
|
||||
if err := store.Init(db, domain, certPEM, keyPEM); err != nil {
|
||||
return fmt.Errorf("Couldn't initialize datase %q: %v", db, err)
|
||||
}
|
||||
|
||||
log.Printf("Created %q for domain %q. Next step:\n\t%s -db %s run", db, domain, os.Args[0], db)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func crockeryRun(c *cli.Context) error {
|
||||
db := c.GlobalString("db")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
datastore, err := store.New(ctx, "crockery.db")
|
||||
if db == "" {
|
||||
return fmt.Errorf("No crockery database file specified")
|
||||
}
|
||||
|
||||
log.Printf("Loading config from file %q", db)
|
||||
|
||||
datastore, err := store.New(context.Background(), db)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't open crockery.db:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME: This will eventually come from the datastore itself, via `crockery init`
|
||||
datastore.SetDomain(*domain)
|
||||
log.Printf("Running as %s", datastore.Domain())
|
||||
defer datastore.Close()
|
||||
|
||||
// FIXME: This will eventually come from the datastore itself, via ACME
|
||||
if *certFile != "" || *keyFile != "" {
|
||||
cert, err := tls.LoadX509KeyPair(*certFile, *keyFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't setup TLS: %v", err)
|
||||
}
|
||||
|
||||
if cert.PrivateKey == nil {
|
||||
log.Fatal("No private key for TLS certificate")
|
||||
}
|
||||
|
||||
datastore.SetTLS(cert)
|
||||
log.Print("Successfully loaded TLS certificate and key")
|
||||
if datastore.Domain() == "" {
|
||||
return fmt.Errorf("No domain configured in file %q", db)
|
||||
}
|
||||
|
||||
log.Printf("Starting crockery for domain %q", datastore.Domain())
|
||||
|
||||
srv, err := services.New(ctx, datastore)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't start services:", err)
|
||||
return fmt.Errorf("Couldn't start services:", err)
|
||||
}
|
||||
|
||||
sig := make(chan os.Signal, 1)
|
||||
@@ -73,7 +160,8 @@ func main() {
|
||||
}()
|
||||
|
||||
<-done
|
||||
|
||||
log.Println("All services finished, exiting")
|
||||
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user