Add 'grumble ctl'.

This commit is contained in:
Mikkel Krautz 2011-05-13 15:06:54 +02:00
parent 01182d36b3
commit 41f6af2334
10 changed files with 343 additions and 34 deletions

View file

@ -11,6 +11,7 @@ PACKAGES = \
pkg/cryptstate \
pkg/mumbleproto \
pkg/blobstore \
pkg/serverconf \
pkg/sqlite
GCFLAGS = \
@ -18,6 +19,7 @@ GCFLAGS = \
-Ipkg/packetdatastream/_obj \
-Ipkg/mumbleproto/_obj \
-Ipkg/blobstore/_obj \
-Ipkg/serverconf/_obj \
-Ipkg/sqlite/_obj
LDFLAGS = \
@ -25,6 +27,7 @@ LDFLAGS = \
-Lpkg/packetdatastream/_obj \
-Lpkg/mumbleproto/_obj \
-Lpkg/blobstore/_obj \
-Lpkg/serverconf/_obj \
-Lpkg/sqlite/_obj
GOFILES = \
@ -39,7 +42,9 @@ GOFILES = \
murmurdb.go \
freeze.go \
gencert.go \
register.go
register.go \
ctlrpc.go \
ctl.go
.PHONY: grumble
grumble: pkg

82
ctl.go Normal file
View file

@ -0,0 +1,82 @@
// Copyright (c) 2011 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 (
"log"
"os"
"path/filepath"
"rpc"
"strconv"
)
var CtlUsage = `grumble ctl
help
Show this help
start [id]
Start a server
stop [id]
Stop a server
setconf [id] [key] [value]
Set a config value for server with id
getconf [id] [key] [value]
Get a config value for server with id
`
func GrumbleCtl(args []string) {
log.SetFlags(0)
if len(args) <= 1 || args[0] == "help" {
log.Printf(CtlUsage)
return
}
sid, _ := strconv.Atoi64(args[1])
client, err := rpc.Dial("unix", filepath.Join(os.Getenv("HOME"), ".grumble", "ctl"))
if err != nil {
log.Fatalf("Could not connect to control socket: %v", err)
}
switch args[0] {
case "start":
err := client.Call("ctl.Start", sid, nil)
if err != nil {
log.Fatalf("Unable to start: %v", err)
}
log.Printf("[%v] Started")
case "stop":
err := client.Call("ctl.Stop", sid, nil)
if err != nil {
log.Fatalf("Unable to stop: %v", err)
}
log.Printf("[%v] Stopped", sid)
case "setconf":
if len(args) < 4 {
return
}
result := &ConfigValue{}
err := client.Call("ctl.SetConfig", &ConfigValue{sid, args[2], args[3]}, result)
if err != nil {
log.Fatalf("Unable to set config: %v", err)
}
log.Printf("[%v] %v=%v", result.Id, result.Key, result.Value)
case "getconf":
if len(args) < 3 {
return
}
result := &ConfigValue{}
err := client.Call("ctl.GetConfig", &ConfigValue{sid, args[2], ""}, result)
if err != nil {
log.Fatalf("Unable to get config: %v", err)
}
log.Printf("[%v] %v=%v", result.Id, result.Key, result.Value)
}
}

46
ctlrpc.go Normal file
View file

@ -0,0 +1,46 @@
// Copyright (c) 2011 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 (
"os"
)
type ControlRPC struct {
}
// Start a server
func (c *ControlRPC) Start(in *int, out *int) os.Error {
return nil
}
// Stop a server
func (c *ControlRPC) Stop(in *int, out *int) os.Error {
return nil
}
type ConfigValue struct {
Id int64
Key string
Value string
}
// Set a config value
func (c *ControlRPC) SetConfig(in *ConfigValue, out *ConfigValue) os.Error {
servers[in.Id].SetConfig(in.Key, in.Value)
out.Id = in.Id
out.Key = in.Key
out.Value = in.Value
return nil
}
// Get a config value
func (c *ControlRPC) GetConfig(in *ConfigValue, out *ConfigValue) os.Error {
out.Id = in.Id
out.Key = in.Key
out.Value = servers[in.Id].GetConfig(in.Key)
return nil
}

View file

@ -15,7 +15,7 @@ import (
type frozenServer struct {
Id int "id"
MaxUsers int "max_user"
Config map[string]string "config"
Channels []frozenChannel "channels"
Users []frozenUser "users"
}
@ -98,7 +98,7 @@ func (server *Server) FreezeToFile(filename string) (err os.Error) {
// Freeze a server
func (server *Server) Freeze() (fs frozenServer, err os.Error) {
fs.Id = int(server.Id)
fs.MaxUsers = server.MaxUsers
fs.Config = server.cfg
channels := []frozenChannel{}
for _, c := range server.Channels {
@ -224,6 +224,10 @@ func NewServerFromFrozen(filename string) (s *Server, err os.Error) {
return nil, err
}
if fs.Config != nil {
s.cfg = fs.Config
}
// Add all channels, but don't hook up parent/child relationships
// until all of them are loaded.
for _, fc := range fs.Channels {

View file

@ -11,20 +11,24 @@ import (
"os"
"os/signal"
"log"
"net"
"sqlite"
"path/filepath"
"regexp"
"rpc"
"time"
)
var help *bool = flag.Bool("help", false, "Show this help")
var datadir *string = flag.String("datadir", "", "Directory to use for server storage")
var blobdir *string = flag.String("blobdir", "", "Directory to use for blob storage")
var ctlpath *string = flag.String("ctlpath", "", "File to use for ctl socket")
var sqlitedb *string = flag.String("murmurdb", "", "Path to murmur.sqlite to import server structure from")
var cleanup *bool = flag.Bool("clean", false, "Clean up existing data dir content before importing Murmur data")
var gencert *bool = flag.Bool("gencert", false, "Generate a self-signed certificate for use with Grumble")
var globalBlobstore *blobstore.BlobStore
var servers map[int64]*Server
func Usage() {
fmt.Fprintf(os.Stderr, "usage: grumble [options]\n")
@ -42,16 +46,16 @@ func MurmurImport(filename string) (err os.Error) {
panic(err.String())
}
var servers []int64
var serverids []int64
var sid int64
for stmt.Next() {
stmt.Scan(&sid)
servers = append(servers, sid)
serverids = append(serverids, sid)
}
log.Printf("Found servers: %v (%v servers)", servers, len(servers))
log.Printf("Found servers: %v (%v servers)", serverids, len(serverids))
for _, sid := range servers {
for _, sid := range serverids {
m, err := NewServerFromSQLite(sid, db)
if err != nil {
return err
@ -70,6 +74,12 @@ func MurmurImport(filename string) (err os.Error) {
func main() {
var err os.Error
if len(os.Args) >= 2 && os.Args[1] == "ctl" {
GrumbleCtl(os.Args[2:])
return
}
flag.Parse()
if *help == true {
Usage()
@ -88,6 +98,10 @@ func main() {
*blobdir = filepath.Join(os.Getenv("HOME"), ".grumble", "blob")
}
if len(*ctlpath) == 0 {
*ctlpath = filepath.Join(os.Getenv("HOME"), ".grumble", "ctl")
}
log.Printf("Using blob directory: %s", *blobdir)
globalBlobstore, err = blobstore.NewBlobStore(*blobdir, true)
if err != nil {
@ -161,7 +175,7 @@ func main() {
log.Fatalf("Murmur import failed: %s", err.String())
}
servers := make(map[int64]*Server)
servers = make(map[int64]*Server)
for _, name := range names {
if matched, _ := regexp.MatchString("^[0-9]+$", name); matched {
log.Printf("Loading server %v", name)
@ -184,6 +198,22 @@ func main() {
go s.ListenAndMurmur()
}
os.Remove(*ctlpath)
addr, err := net.ResolveUnixAddr("unix", *ctlpath)
if err != nil {
log.Panicf("Unable to resolve ctl addr: %v", err)
}
lis, err := net.ListenUnix("unix", addr)
if err != nil {
log.Panicf("Unable to listen on ctl socket: %v", err)
}
ctl := &ControlRPC{}
rpc.RegisterName("ctl", ctl)
go rpc.Accept(lis)
if len(servers) > 0 {
ticker := time.NewTicker(10e9) // 10 secs
for {

6
pkg/serverconf/Makefile Normal file
View file

@ -0,0 +1,6 @@
include $(GOROOT)/src/Make.inc
TARG = grumble/serverconf
GOFILES = config.go
include $(GOROOT)/src/Make.pkg

64
pkg/serverconf/config.go Normal file
View file

@ -0,0 +1,64 @@
// Copyright (c) 2011 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 serverconf
import (
"strconv"
)
var defaultCfg = map[string]string{
"MaxBandwidth": "72000",
"MaxUsers": "1000",
"MaxUsersPerChannel": "0",
"MaxTextMessageLength": "5000",
"MaxImageMessageLength": "131072",
"AllowHTML": "true",
"DefaultChannel": "0",
"RememberChannel": "true",
"WelcomeText": "Welcome to this server running <b>Grumble</b>.",
"SendVersion": "true",
}
type Config map[string]string
func (cfg Config) Set(key string, value string) {
cfg[key] = value
}
func (cfg Config) StringValue(key string) (value string) {
value, exists := cfg[key]
if exists {
return value
}
value, exists = defaultCfg[key]
if exists {
return value
}
return ""
}
func (cfg Config) IntValue(key string) (intval int) {
str := cfg.StringValue(key)
intval, _ = strconv.Atoi(str)
return
}
func (cfg Config) Uint32Value(key string) (uint32val uint32) {
str := cfg.StringValue(key)
uintval, _ := strconv.Atoui(str)
return uint32(uintval)
}
func (cfg Config) BoolValue(key string) (boolval bool) {
str := cfg.StringValue(key)
boolval, _ = strconv.Atob(str)
return
}
func (cfg Config) Reset(key string) {
cfg[key] = "", false
}

View file

@ -0,0 +1,40 @@
// Copyright (c) 2011 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 serverconf
import (
"testing"
)
func TestIntValue(t *testing.T) {
cfg := make(Config)
cfg.Set("Test", "13")
if cfg.IntValue("Test") != 13 {
t.Errorf("Expected 13")
}
}
func TestFloatAsInt(t *testing.T) {
cfg := make(Config)
cfg.Set("Test", "13.4")
if cfg.IntValue("Test") != 0 {
t.Errorf("Expected 0")
}
}
func TestDefaultValue(t *testing.T) {
cfg := make(Config)
if cfg.IntValue("MaxBandwidth") != 72000 {
t.Errorf("Expected 72000")
}
}
func TestBoolValue(t *testing.T) {
cfg := make(Config)
cfg.Set("DoStuffOnStartup", "true")
if cfg.BoolValue("DoStuffOnStartup") != true {
t.Errorf("Expected true")
}
}

View file

@ -74,16 +74,16 @@ func newTLSClientAuthConn(addr string, cfg *tls.Config) (c *http.ClientConn, err
// This function is used to determine whether or not to periodically
// contact the master server list and update this server's metadata.
func (server *Server) IsPublic() bool {
if len(server.RegisterName) == 0 {
if len(server.cfg.StringValue("RegisterName")) == 0 {
return false
}
if len(server.RegisterHost) == 0 {
if len(server.cfg.StringValue("RegisterHost")) == 0 {
return false
}
if len(server.RegisterPassword) == 0 {
if len(server.cfg.StringValue("RegisterPassword")) == 0 {
return false
}
if len(server.RegisterWebUrl) == 0 {
if len(server.cfg.StringValue("RegisterWebUrl")) == 0 {
return false
}
return true
@ -121,11 +121,11 @@ func (server *Server) RegisterPublicServer() {
return
}
err = t.Execute(buf, map[string]string{
"name": server.RegisterName,
"host": server.RegisterHost,
"password": server.RegisterPassword,
"url": server.RegisterWebUrl,
"location": server.RegisterLocation,
"name": server.cfg.StringValue("RegisterName"),
"host": server.cfg.StringValue("RegisterHost"),
"password": server.cfg.StringValue("RegisterPassword"),
"url": server.cfg.StringValue("RegisterWebUrl"),
"location": server.cfg.StringValue("RegisterLocation"),
"port": strconv.Itoa(server.port),
"digest": digest,
"users": strconv.Itoa(len(server.clients)),

View file

@ -21,6 +21,7 @@ import (
"cryptstate"
"fmt"
"gob"
"grumble/serverconf"
"hash"
"io"
"path/filepath"
@ -57,19 +58,14 @@ type Server struct {
udpsend chan *Message
voicebroadcast chan *VoiceBroadcast
freezeRequest chan *freezeRequest
configRequest chan *configRequest
// Signals to the server that a client has been successfully
// authenticated.
clientAuthenticated chan *Client
// Config-related
MaxUsers int
MaxBandwidth uint32
RegisterName string
RegisterHost string
RegisterPassword string
RegisterWebUrl string
RegisterLocation string
// Server configuration
cfg serverconf.Config
// Clients
clients map[uint32]*Client
@ -128,6 +124,13 @@ type freezeRequest struct {
readCloser io.ReadCloser
}
type configRequest struct {
done chan bool
set bool
key string
value string
}
// Allocate a new Murmur instance
func NewServer(id int64, addr string, port int) (s *Server, err os.Error) {
s = new(Server)
@ -137,6 +140,8 @@ func NewServer(id int64, addr string, port int) (s *Server, err os.Error) {
s.port = port
s.running = false
s.cfg = make(map[string]string)
s.sessions = make(map[uint32]bool)
s.clients = make(map[uint32]*Client)
s.Users = make(map[uint32]*User)
@ -150,11 +155,9 @@ func NewServer(id int64, addr string, port int) (s *Server, err os.Error) {
s.udpsend = make(chan *Message)
s.voicebroadcast = make(chan *VoiceBroadcast)
s.freezeRequest = make(chan *freezeRequest)
s.configRequest = make(chan *configRequest)
s.clientAuthenticated = make(chan *Client)
s.MaxBandwidth = 300000
s.MaxUsers = 10
s.Channels = make(map[int]*Channel)
s.root = s.NewChannel(0, "Root")
s.aclcache = NewACLCache()
@ -391,6 +394,10 @@ func (server *Server) handler() {
}
go server.handleFreezeRequest(req, &fs)
// Synchronzied config get/set
case req := <-server.configRequest:
server.handleConfigRequest(req)
// Server registration update
// Tick every hour + a minute offset based on the server id.
case <-time.Tick((3600 + ((server.Id * 60) % 600)) * 1e9):
@ -426,6 +433,15 @@ func (server *Server) handleFreezeRequest(freq *freezeRequest, fs *frozenServer)
}
}
func (server *Server) handleConfigRequest(cfgReq *configRequest) {
if cfgReq.set {
server.cfg.Set(cfgReq.key, cfgReq.value)
} else {
cfgReq.value = server.cfg.StringValue(cfgReq.key)
}
cfgReq.done <- true
}
// Handle an Authenticate protobuf message. This is handled in a separate
// goroutine to allow for remote authenticators that are slow to respond.
//
@ -639,7 +655,7 @@ func (server *Server) finishAuthenticate(client *Client) {
sync := &mumbleproto.ServerSync{}
sync.Session = proto.Uint32(client.Session)
sync.MaxBandwidth = proto.Uint32(server.MaxBandwidth)
sync.MaxBandwidth = proto.Uint32(server.cfg.Uint32Value("MaxBandwidth"))
if client.IsSuperUser() {
sync.Permissions = proto.Uint64(uint64(AllPermissions))
} else {
@ -951,8 +967,8 @@ func (server *Server) ListenUDP() {
_ = binary.Write(buffer, binary.BigEndian, uint32((1<<16)|(2<<8)|2))
_ = binary.Write(buffer, binary.BigEndian, rand)
_ = binary.Write(buffer, binary.BigEndian, uint32(len(server.clients)))
_ = binary.Write(buffer, binary.BigEndian, uint32(server.MaxUsers))
_ = binary.Write(buffer, binary.BigEndian, uint32(server.MaxBandwidth))
_ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("MaxUsers"))
_ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("MaxBandwidth"))
server.udpsend <- &Message{
buf: buffer.Bytes(),
@ -1059,6 +1075,22 @@ func (s *Server) FreezeServer() io.ReadCloser {
return fr.readCloser
}
// Set the value of a config key
func (s *Server) SetConfig(key string, value string) {
cfgReq := &configRequest{make(chan bool), true, key, value}
s.configRequest <- cfgReq
<-cfgReq.done
return
}
// Get the value of a config key
func (s *Server) GetConfig(key string) (value string) {
cfgReq := &configRequest{make(chan bool), false, key, ""}
s.configRequest <- cfgReq
<-cfgReq.done
return cfgReq.value
}
// Register a client on the server.
func (s *Server) RegisterClient(client *Client) (uid uint32) {
// Increment nextUserId only if registration succeeded.