Index: trunk/cmd/soju/main.go
===================================================================
--- trunk/cmd/soju/main.go	(revision 690)
+++ trunk/cmd/soju/main.go	(revision 691)
@@ -38,17 +38,4 @@
 }
 
-func loadMOTD(srv *soju.Server, filename string) error {
-	if filename == "" {
-		return nil
-	}
-
-	b, err := ioutil.ReadFile(filename)
-	if err != nil {
-		return err
-	}
-	srv.SetMOTD(strings.TrimSuffix(string(b), "\n"))
-	return nil
-}
-
 func bumpOpenedFileLimit() error {
 	var rlimit syscall.Rlimit
@@ -63,8 +50,55 @@
 }
 
+var (
+	configPath string
+	debug      bool
+
+	tlsCert atomic.Value // *tls.Certificate
+)
+
+func loadConfig() (*config.Server, *soju.Config, error) {
+	var raw *config.Server
+	if configPath != "" {
+		var err error
+		raw, err = config.Load(configPath)
+		if err != nil {
+			return nil, nil, fmt.Errorf("failed to load config file: %v", err)
+		}
+	} else {
+		raw = config.Defaults()
+	}
+
+	var motd string
+	if raw.MOTDPath != "" {
+		b, err := ioutil.ReadFile(raw.MOTDPath)
+		if err != nil {
+			return nil, nil, fmt.Errorf("failed to load MOTD: %v", err)
+		}
+		motd = strings.TrimSuffix(string(b), "\n")
+	}
+
+	if raw.TLS != nil {
+		cert, err := tls.LoadX509KeyPair(raw.TLS.CertPath, raw.TLS.KeyPath)
+		if err != nil {
+			return nil, nil, fmt.Errorf("failed to load TLS certificate and key: %v", err)
+		}
+		tlsCert.Store(&cert)
+	}
+
+	cfg := &soju.Config{
+		Hostname:        raw.Hostname,
+		Title:           raw.Title,
+		LogPath:         raw.LogPath,
+		HTTPOrigins:     raw.HTTPOrigins,
+		AcceptProxyIPs:  raw.AcceptProxyIPs,
+		MaxUserNetworks: raw.MaxUserNetworks,
+		Debug:           debug,
+		MOTD:            motd,
+	}
+	return raw, cfg, nil
+}
+
 func main() {
 	var listen []string
-	var configPath string
-	var debug bool
 	flag.Var((*stringSliceFlag)(&listen), "listen", "listening address")
 	flag.StringVar(&configPath, "config", "", "path to configuration file")
@@ -72,13 +106,7 @@
 	flag.Parse()
 
-	var cfg *config.Server
-	if configPath != "" {
-		var err error
-		cfg, err = config.Load(configPath)
-		if err != nil {
-			log.Fatalf("failed to load config file: %v", err)
-		}
-	} else {
-		cfg = config.Defaults()
+	cfg, serverCfg, err := loadConfig()
+	if err != nil {
+		log.Fatal(err)
 	}
 
@@ -98,12 +126,5 @@
 
 	var tlsCfg *tls.Config
-	var tlsCert atomic.Value
 	if cfg.TLS != nil {
-		cert, err := tls.LoadX509KeyPair(cfg.TLS.CertPath, cfg.TLS.KeyPath)
-		if err != nil {
-			log.Fatalf("failed to load TLS certificate and key: %v", err)
-		}
-		tlsCert.Store(&cert)
-
 		tlsCfg = &tls.Config{
 			GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
@@ -114,15 +135,5 @@
 
 	srv := soju.NewServer(db)
-	srv.Hostname = cfg.Hostname
-	srv.Title = cfg.Title
-	srv.LogPath = cfg.LogPath
-	srv.HTTPOrigins = cfg.HTTPOrigins
-	srv.AcceptProxyIPs = cfg.AcceptProxyIPs
-	srv.MaxUserNetworks = cfg.MaxUserNetworks
-	srv.Debug = debug
-
-	if err := loadMOTD(srv, cfg.MOTDPath); err != nil {
-		log.Fatalf("failed to load MOTD: %v", err)
-	}
+	srv.SetConfig(serverCfg)
 
 	for _, listen := range cfg.Listen {
@@ -259,15 +270,10 @@
 		switch sig {
 		case syscall.SIGHUP:
-			log.Print("reloading TLS certificate and MOTD")
-			if cfg.TLS != nil {
-				cert, err := tls.LoadX509KeyPair(cfg.TLS.CertPath, cfg.TLS.KeyPath)
-				if err != nil {
-					log.Printf("failed to reload TLS certificate and key: %v", err)
-					break
-				}
-				tlsCert.Store(&cert)
-			}
-			if err := loadMOTD(srv, cfg.MOTDPath); err != nil {
-				log.Printf("failed to reload MOTD: %v", err)
+			log.Print("reloading configuration")
+			_, serverCfg, err := loadConfig()
+			if err != nil {
+				log.Printf("failed to reloading configuration: %v", err)
+			} else {
+				srv.SetConfig(serverCfg)
 			}
 		case syscall.SIGINT, syscall.SIGTERM:
@@ -287,5 +293,5 @@
 				return proxyproto.IGNORE, nil
 			}
-			if srv.AcceptProxyIPs.Contains(tcpAddr.IP) {
+			if srv.Config().AcceptProxyIPs.Contains(tcpAddr.IP) {
 				return proxyproto.USE, nil
 			}
Index: trunk/conn.go
===================================================================
--- trunk/conn.go	(revision 690)
+++ trunk/conn.go	(revision 691)
@@ -196,5 +196,5 @@
 			}
 
-			if c.srv.Debug {
+			if c.srv.Config().Debug {
 				c.logger.Printf("sent: %v", msg)
 			}
@@ -249,5 +249,5 @@
 	}
 
-	if c.srv.Debug {
+	if c.srv.Config().Debug {
 		c.logger.Printf("received: %v", msg)
 	}
Index: trunk/doc/soju.1.scd
===================================================================
--- trunk/doc/soju.1.scd	(revision 690)
+++ trunk/doc/soju.1.scd	(revision 691)
@@ -45,6 +45,7 @@
 be done by adding a "@<client>" suffix to the username.
 
-soju will reload the TLS certificate/key and the MOTD file when it receives the
-HUP signal.
+soju will reload the configuration file, the TLS certificate/key and the MOTD
+file when it receives the HUP signal. The configuration options _listen_, _db_
+and _log_ cannot be reloaded.
 
 Administrators can broadcast a message to all bouncer users via _/notice
Index: trunk/downstream.go
===================================================================
--- trunk/downstream.go	(revision 690)
+++ trunk/downstream.go	(revision 691)
@@ -291,5 +291,8 @@
 		dc.supportedCaps[k] = v
 	}
-	if srv.LogPath != "" {
+	// TODO: this is racy, we should only enable chathistory after
+	// authentication and then check that user.msgStore implements
+	// chatHistoryMessageStore
+	if srv.Config().LogPath != "" {
 		dc.supportedCaps["draft/chathistory"] = ""
 	}
@@ -997,5 +1000,5 @@
 	}
 
-	if dc.srv.LogPath != "" && dc.network != nil {
+	if _, ok := dc.user.msgStore.(chatHistoryMessageStore); ok && dc.network != nil {
 		dc.setSupportedCap("draft/event-playback", "")
 	} else {
@@ -1176,6 +1179,6 @@
 		isupport = append(isupport, fmt.Sprintf("BOUNCER_NETID=%v", dc.network.ID))
 	}
-	if dc.network == nil && dc.srv.Title != "" {
-		isupport = append(isupport, "NETWORK="+encodeISUPPORT(dc.srv.Title))
+	if title := dc.srv.Config().Title; dc.network == nil && title != "" {
+		isupport = append(isupport, "NETWORK="+encodeISUPPORT(title))
 	}
 	if dc.network == nil && dc.caps["soju.im/bouncer-networks"] {
@@ -1205,10 +1208,10 @@
 		Prefix:  dc.srv.prefix(),
 		Command: irc.RPL_YOURHOST,
-		Params:  []string{dc.nick, "Your host is " + dc.srv.Hostname},
+		Params:  []string{dc.nick, "Your host is " + dc.srv.Config().Hostname},
 	})
 	dc.SendMessage(&irc.Message{
 		Prefix:  dc.srv.prefix(),
 		Command: irc.RPL_MYINFO,
-		Params:  []string{dc.nick, dc.srv.Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
+		Params:  []string{dc.nick, dc.srv.Config().Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
 	})
 	for _, msg := range generateIsupport(dc.srv.prefix(), dc.nick, isupport) {
@@ -1230,5 +1233,5 @@
 	}
 
-	if motd := dc.user.srv.MOTD(); motd != "" && dc.network == nil {
+	if motd := dc.user.srv.Config().MOTD; motd != "" && dc.network == nil {
 		for _, msg := range generateMOTD(dc.srv.prefix(), dc.nick, motd) {
 			dc.SendMessage(msg)
@@ -1421,5 +1424,6 @@
 			destination = msg.Params[1]
 		}
-		if destination != "" && destination != dc.srv.Hostname {
+		hostname := dc.srv.Config().Hostname
+		if destination != "" && destination != hostname {
 			return ircError{&irc.Message{
 				Command: irc.ERR_NOSUCHSERVER,
@@ -1430,5 +1434,5 @@
 			Prefix:  dc.srv.prefix(),
 			Command: "PONG",
-			Params:  []string{dc.srv.Hostname, source},
+			Params:  []string{hostname, source},
 		})
 		return nil
@@ -1947,5 +1951,5 @@
 				Username: dc.user.Username,
 				Hostname: dc.hostname,
-				Server:   dc.srv.Hostname,
+				Server:   dc.srv.Config().Hostname,
 				Nickname: dc.nick,
 				Flags:    flags,
@@ -1966,5 +1970,5 @@
 				Username: servicePrefix.User,
 				Hostname: servicePrefix.Host,
-				Server:   dc.srv.Hostname,
+				Server:   dc.srv.Config().Hostname,
 				Nickname: serviceNick,
 				Flags:    "H*",
@@ -2026,5 +2030,5 @@
 				Prefix:  dc.srv.prefix(),
 				Command: irc.RPL_WHOISSERVER,
-				Params:  []string{dc.nick, dc.nick, dc.srv.Hostname, "soju"},
+				Params:  []string{dc.nick, dc.nick, dc.srv.Config().Hostname, "soju"},
 			})
 			if dc.user.Admin {
@@ -2056,5 +2060,5 @@
 				Prefix:  dc.srv.prefix(),
 				Command: irc.RPL_WHOISSERVER,
-				Params:  []string{dc.nick, serviceNick, dc.srv.Hostname, "soju"},
+				Params:  []string{dc.nick, serviceNick, dc.srv.Config().Hostname, "soju"},
 			})
 			dc.SendMessage(&irc.Message{
@@ -2105,5 +2109,5 @@
 
 		for _, name := range strings.Split(targetsStr, ",") {
-			if name == "$"+dc.srv.Hostname || (name == "$*" && dc.network == nil) {
+			if name == "$"+dc.srv.Config().Hostname || (name == "$*" && dc.network == nil) {
 				// "$" means a server mask follows. If it's the bouncer's
 				// hostname, broadcast the message to all bouncer users.
Index: trunk/server.go
===================================================================
--- trunk/server.go	(revision 690)
+++ trunk/server.go	(revision 691)
@@ -54,8 +54,7 @@
 }
 
-type Server struct {
+type Config struct {
 	Hostname        string
 	Title           string
-	Logger          Logger
 	LogPath         string
 	Debug           bool
@@ -63,6 +62,12 @@
 	AcceptProxyIPs  config.IPSet
 	MaxUserNetworks int
-	Identd          *Identd // can be nil
-
+	MOTD            string
+}
+
+type Server struct {
+	Logger Logger
+	Identd *Identd // can be nil
+
+	config    atomic.Value // *Config
 	db        Database
 	stopWG    sync.WaitGroup
@@ -72,22 +77,27 @@
 	listeners map[net.Listener]struct{}
 	users     map[string]*user
-
-	motd atomic.Value // string
 }
 
 func NewServer(db Database) *Server {
 	srv := &Server{
-		Logger:          log.New(log.Writer(), "", log.LstdFlags),
-		MaxUserNetworks: -1,
-		db:              db,
-		listeners:       make(map[net.Listener]struct{}),
-		users:           make(map[string]*user),
-	}
-	srv.motd.Store("")
+		Logger:    log.New(log.Writer(), "", log.LstdFlags),
+		db:        db,
+		listeners: make(map[net.Listener]struct{}),
+		users:     make(map[string]*user),
+	}
+	srv.config.Store(&Config{Hostname: "localhost", MaxUserNetworks: -1})
 	return srv
 }
 
 func (s *Server) prefix() *irc.Prefix {
-	return &irc.Prefix{Name: s.Hostname}
+	return &irc.Prefix{Name: s.Config().Hostname}
+}
+
+func (s *Server) Config() *Config {
+	return s.config.Load().(*Config)
+}
+
+func (s *Server) SetConfig(cfg *Config) {
+	s.config.Store(cfg)
 }
 
@@ -240,5 +250,5 @@
 	conn, err := websocket.Accept(w, req, &websocket.AcceptOptions{
 		Subprotocols:   []string{"text.ircv3.net"}, // non-compliant, fight me
-		OriginPatterns: s.HTTPOrigins,
+		OriginPatterns: s.Config().HTTPOrigins,
 	})
 	if err != nil {
@@ -250,5 +260,5 @@
 	if host, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
 		if ip := net.ParseIP(host); ip != nil {
-			isProxy = s.AcceptProxyIPs.Contains(ip)
+			isProxy = s.Config().AcceptProxyIPs.Contains(ip)
 		}
 	}
@@ -294,10 +304,2 @@
 	return &stats
 }
-
-func (s *Server) SetMOTD(motd string) {
-	s.motd.Store(motd)
-}
-
-func (s *Server) MOTD() string {
-	return s.motd.Load().(string)
-}
Index: trunk/service.go
===================================================================
--- trunk/service.go	(revision 690)
+++ trunk/service.go	(revision 691)
@@ -1051,5 +1051,5 @@
 		Prefix:  servicePrefix,
 		Command: "NOTICE",
-		Params:  []string{"$" + dc.srv.Hostname, text},
+		Params:  []string{"$" + dc.srv.Config().Hostname, text},
 	}
 	var err error
Index: trunk/user.go
===================================================================
--- trunk/user.go	(revision 690)
+++ trunk/user.go	(revision 691)
@@ -416,6 +416,6 @@
 
 	var msgStore messageStore
-	if srv.LogPath != "" {
-		msgStore = newFSMessageStore(srv.LogPath, record.Username)
+	if logPath := srv.Config().LogPath; logPath != "" {
+		msgStore = newFSMessageStore(logPath, record.Username)
 	} else {
 		msgStore = newMemoryMessageStore()
@@ -777,5 +777,5 @@
 	}
 
-	if u.srv.MaxUserNetworks >= 0 && len(u.networks) >= u.srv.MaxUserNetworks {
+	if max := u.srv.Config().MaxUserNetworks; max >= 0 && len(u.networks) >= max {
 		return nil, fmt.Errorf("maximum number of networks reached")
 	}
