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:
2018-03-05 22:29:31 +00:00
parent bf1ca421aa
commit 3ad8c6f59f
313 changed files with 160897 additions and 43 deletions

View File

@@ -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
}