forked from External/grumble
249 lines
6.4 KiB
Go
249 lines
6.4 KiB
Go
// Copyright (c) 2010 The Grumble Authors
|
|
// The use of this source code is goverened by a BSD-style
|
|
// license that can be found in the LICENSE-file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"grumble/blobstore"
|
|
"grumble/logtarget"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
)
|
|
|
|
var servers map[int64]*Server
|
|
|
|
func main() {
|
|
var err error
|
|
|
|
flag.Parse()
|
|
if Args.ShowHelp == true {
|
|
Usage()
|
|
return
|
|
}
|
|
|
|
// Open the data dir to check whether it exists.
|
|
dataDir, err := os.Open(Args.DataDir)
|
|
if err != nil {
|
|
log.Fatalf("Unable to open data directory: %v", err)
|
|
return
|
|
}
|
|
dataDir.Close()
|
|
|
|
// Set up logging
|
|
err = logtarget.Target.OpenFile(Args.LogPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Unable to open log file: %v", err)
|
|
return
|
|
}
|
|
log.SetPrefix("[G] ")
|
|
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
|
log.SetOutput(&logtarget.Target)
|
|
log.Printf("Grumble")
|
|
log.Printf("Using data directory: %s", Args.DataDir)
|
|
|
|
// Open the blobstore. If the directory doesn't
|
|
// already exist, create the directory and open
|
|
// the blobstore.
|
|
// The Open method of the blobstore performs simple
|
|
// sanity checking of content of the blob directory,
|
|
// and will return an error if something's amiss.
|
|
blobDir := filepath.Join(Args.DataDir, "blob")
|
|
err = os.Mkdir(blobDir, 0700)
|
|
if err != nil {
|
|
exists := false
|
|
if e, ok := err.(*os.PathError); ok {
|
|
if e.Err == os.EEXIST {
|
|
exists = true
|
|
}
|
|
}
|
|
if !exists {
|
|
log.Fatal("Unable to create blob directory: %v", err.Error())
|
|
}
|
|
}
|
|
err = blobstore.Open(blobDir)
|
|
if err != nil {
|
|
log.Fatalf("Unable to initialize blobstore: %v", err.Error())
|
|
}
|
|
|
|
// Check whether we should regenerate the default global keypair
|
|
// and corresponding certificate.
|
|
// These are used as the default certificate of all virtual servers
|
|
// and the SSH admin console, but can be overridden using the "key"
|
|
// and "cert" arguments to Grumble.
|
|
certFn := filepath.Join(Args.DataDir, "cert.pem")
|
|
keyFn := filepath.Join(Args.DataDir, "key.pem")
|
|
shouldRegen := false
|
|
if Args.RegenKeys {
|
|
shouldRegen = true
|
|
} else {
|
|
// OK. Here's the idea: We check for the existence of the cert.pem
|
|
// and key.pem files in the data directory on launch. Although these
|
|
// might be deleted later (and this check could be deemed useless),
|
|
// it's simply here to be convenient for admins.
|
|
hasKey := true
|
|
hasCert := true
|
|
_, err = os.Stat(certFn)
|
|
if err != nil {
|
|
if e, ok := err.(*os.PathError); ok {
|
|
if e.Err == os.ENOENT {
|
|
hasCert = false
|
|
}
|
|
}
|
|
}
|
|
_, err = os.Stat(keyFn)
|
|
if err != nil {
|
|
if e, ok := err.(*os.PathError); ok {
|
|
if e.Err == os.ENOENT {
|
|
hasKey = false
|
|
}
|
|
}
|
|
}
|
|
if !hasCert && !hasKey {
|
|
shouldRegen = true
|
|
} else if !hasCert || !hasKey {
|
|
if !hasCert {
|
|
log.Fatal("Grumble could not find its default certificate (cert.pem)")
|
|
}
|
|
if !hasKey {
|
|
log.Fatal("Grumble could not find its default private key (key.pem)")
|
|
}
|
|
}
|
|
}
|
|
if shouldRegen {
|
|
log.Printf("Generating 4096-bit RSA keypair for self-signed certificate...")
|
|
|
|
err := GenerateSelfSignedCert(certFn, keyFn)
|
|
if err != nil {
|
|
log.Printf("Error: %v", err)
|
|
return
|
|
}
|
|
|
|
log.Printf("Certificate output to %v", certFn)
|
|
log.Printf("Private key output to %v", keyFn)
|
|
}
|
|
|
|
// Should we import data from a Murmur SQLite file?
|
|
if SQLiteSupport && len(Args.SQLiteDB) > 0 {
|
|
f, err := os.Open(Args.DataDir)
|
|
if err != nil {
|
|
log.Fatalf("Murmur import failed: %s", err.Error())
|
|
}
|
|
defer f.Close()
|
|
|
|
names, err := f.Readdirnames(-1)
|
|
if err != nil {
|
|
log.Fatalf("Murmur import failed: %s", err.Error())
|
|
}
|
|
|
|
if !Args.CleanUp && len(names) > 0 {
|
|
log.Fatalf("Non-empty datadir. Refusing to import Murmur data.")
|
|
}
|
|
if Args.CleanUp {
|
|
log.Print("Cleaning up existing data directory")
|
|
for _, name := range names {
|
|
if err := os.RemoveAll(filepath.Join(Args.DataDir, name)); err != nil {
|
|
log.Fatalf("Unable to cleanup file: %s", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Printf("Importing Murmur data from '%s'", Args.SQLiteDB)
|
|
if err = MurmurImport(Args.SQLiteDB); err != nil {
|
|
log.Fatalf("Murmur import failed: %s", err.Error())
|
|
}
|
|
|
|
log.Printf("Import from Murmur SQLite database succeeded.")
|
|
log.Printf("Please restart Grumble to make use of the imported data.")
|
|
|
|
return
|
|
}
|
|
|
|
// Create the servers directory if it doesn't already
|
|
// exist.
|
|
serversDirPath := filepath.Join(Args.DataDir, "servers")
|
|
err = os.Mkdir(serversDirPath, 0700)
|
|
if err != nil {
|
|
exists := false
|
|
if e, ok := err.(*os.PathError); ok {
|
|
if e.Err == os.EEXIST {
|
|
exists = true
|
|
}
|
|
}
|
|
if !exists {
|
|
log.Fatal("Unable to create servers directory: %v", err.Error())
|
|
}
|
|
}
|
|
|
|
// Read all entries of the servers directory.
|
|
// We need these to load our virtual servers.
|
|
serversDir, err := os.Open(serversDirPath)
|
|
if err != nil {
|
|
log.Fatalf("Unable to open the servers directory: %v", err.Error())
|
|
}
|
|
names, err := serversDir.Readdirnames(-1)
|
|
if err != nil {
|
|
log.Fatal("Unable to read file from data directory: %v", err.Error())
|
|
}
|
|
// The data dir file descriptor.
|
|
err = serversDir.Close()
|
|
if err != nil {
|
|
log.Fatalf("Unable to close data directory: %v", err.Error())
|
|
return
|
|
}
|
|
|
|
// Look through the list of files in the data directory, and
|
|
// load all virtual servers from disk.
|
|
servers = make(map[int64]*Server)
|
|
for _, name := range names {
|
|
if matched, _ := regexp.MatchString("^[0-9]+$", name); matched {
|
|
log.Printf("Loading server %v", name)
|
|
s, err := NewServerFromFrozen(name)
|
|
if err != nil {
|
|
log.Fatalf("Unable to load server: %v", err.Error())
|
|
}
|
|
err = s.FreezeToFile()
|
|
if err != nil {
|
|
log.Fatalf("Unable to freeze server to disk: %v", err.Error())
|
|
}
|
|
servers[s.Id] = s
|
|
}
|
|
}
|
|
|
|
// If no servers were found, create the default virtual server.
|
|
if len(servers) == 0 {
|
|
s, err := NewServer(1)
|
|
if err != nil {
|
|
log.Fatalf("Couldn't start server: %s", err.Error())
|
|
}
|
|
|
|
servers[s.Id] = s
|
|
os.Mkdir(filepath.Join(serversDirPath, fmt.Sprintf("%v", 1)), 0750)
|
|
err = s.FreezeToFile()
|
|
if err != nil {
|
|
log.Fatalf("Unable to freeze newly created server to disk: %v", err.Error())
|
|
}
|
|
}
|
|
|
|
// Run the SSH admin console.
|
|
RunSSH()
|
|
|
|
// Launch the servers we found during launch...
|
|
for _, server := range servers {
|
|
err = server.Start()
|
|
if err != nil {
|
|
log.Printf("Unable to start server %v: %v", server.Id, err.Error())
|
|
}
|
|
}
|
|
|
|
// If any servers were loaded, launch the signal
|
|
// handler goroutine and sleep...
|
|
if len(servers) > 0 {
|
|
go SignalHandler()
|
|
select {}
|
|
}
|
|
}
|