diff --git a/cmd/coord-server/main.go b/cmd/coord-server/main.go
index 73b8afea5edb7734a158a5c1ad3707b24b2000ba..9bde32cbe5dcf9f71a7eaf0b06d875da11ab0deb 100644
--- a/cmd/coord-server/main.go
+++ b/cmd/coord-server/main.go
@@ -10,33 +10,96 @@ import (
 	"syscall"
 	"time"
 
+	"github.com/mkideal/cli"
+
 	"gitlab.irstea.fr/guillaume.perreal/coord/lib/safe"
 )
 
+type TCPAddr struct {
+	*net.TCPAddr
+}
+
+func (t *TCPAddr) Decode(s string) (err error) {
+	t.TCPAddr, err = net.ResolveTCPAddr("tcp", s)
+	return
+}
+
+type UnixAddr struct {
+	*net.UnixAddr
+}
+
+func (t *UnixAddr) Decode(s string) (err error) {
+	t.UnixAddr, err = net.ResolveUnixAddr("unix", s)
+	return
+}
+
+type LoggerConfig struct {
+	Verbose []bool `cli:"v,verbose" usage:"increase the log verbosity"`
+	Quiet   bool   `cli:"q,quiet" usage:"decrease the log verbosity"`
+}
+
+type serverConfig struct {
+	cli.Helper
+	TCP     []TCPAddr  `cli:"t,tcp" usage:"serve on TCP port"`
+	Unix    []UnixAddr `cli:"u,unix" usage:"serve on unix socket"`
+	Console bool       `cli:"C,console" usage:"open a connection to the console"`
+	LoggerConfig
+}
+
 func main() {
-	var stop safe.StopOnce
-	defer stop.Stop()
+	os.Exit(cli.Run(new(serverConfig), func(ctx *cli.Context) error {
+		conf := ctx.Argv().(*serverConfig)
+		return runServer(*conf)
+	}))
+}
+
+func runServer(conf serverConfig) (err error) {
+	defer log.Info("exiting")
+
+	setupLogger(conf.LoggerConfig)
+
+	log.Debugf("configuration: %+v", conf)
 
 	stream := NewStream()
-	stop.BindStopper(stream)
 
-	startConsoleConnection(stream)
+	if conf.Console {
+		startConsoleConnection(stream)
+	}
 
-	if err := startUnixListener(stream); err != nil {
-		log.Errorf("could not start socket listener: %s", err)
+	for _, unixAddr := range conf.Unix {
+		if err := startUnixListener(stream, unixAddr.UnixAddr); err != nil {
+			log.Errorf("could not start socket listener: %s", err)
+		}
 	}
-	if err := startTCPListener(stream); err != nil {
-		log.Errorf("could not start TCP listener: %s", err)
+
+	for _, tcpAddr := range conf.TCP {
+		if err := startTCPListener(stream, tcpAddr.TCPAddr); err != nil {
+			log.Errorf("could not start TCP listener: %s", err)
+		}
 	}
 
 	signals := make(chan os.Signal, 1)
 	signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
 
-	select {
-	case <-stop.Stopped():
-	case sig := <-signals:
-		log.Criticalf("received signal '%s', exiting", sig)
+	sig := <-signals
+	log.Criticalf("received signal '%s', shutting down", sig)
+	stream.Stop()
+
+	return
+}
+
+func setupLogger(conf LoggerConfig) {
+	level := log.NOTICE
+	if conf.Quiet {
+		level = log.ERROR
+	} else {
+		switch len(conf.Verbose) {
+		case 0: level = log.NOTICE
+		case 1: level = log.INFO
+		default: level = log.DEBUG
+		}
 	}
+	log.DefaultLogger = log.NewLogger(log.NewLogWriter(level, os.Stderr, log.TextFormatter))
 }
 
 func startConsoleConnection(stream *Stream) {
@@ -48,12 +111,7 @@ func startConsoleConnection(stream *Stream) {
 	)
 }
 
-func startUnixListener(stream *Stream) error {
-	addr, err := net.ResolveUnixAddr("unix", ".conn")
-	if err != nil {
-		return err
-	}
-
+func startUnixListener(stream *Stream, addr *net.UnixAddr) error {
 	listener, err := net.ListenUnix("unix", addr)
 	if err != nil {
 		return err
@@ -61,45 +119,50 @@ func startUnixListener(stream *Stream) error {
 	listener.SetUnlinkOnClose(true)
 
 	logger := log.DefaultLogger.WithField("server", fmt.Sprintf("%s://%s", addr.Net, addr.Name))
-	safe.Go(func() { runListener(stream, listener, logger) })
-	return nil
+	return runListener(stream, listener, logger)
 }
 
-func startTCPListener(stream *Stream) error {
-	addr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:8650")
-	if err != nil {
-		return err
-	}
-
+func startTCPListener(stream *Stream, addr *net.TCPAddr) error {
 	listener, err := net.ListenTCP("tcp", addr)
 	if err != nil {
 		return err
 	}
 
 	logger := log.DefaultLogger.WithField("server", fmt.Sprintf("%s://%s:%d", addr.Network(), addr.IP, addr.Port))
-	safe.Go(func() { runListener(stream, listener, logger) })
-	return nil
+	return runListener(stream, listener, logger)
 }
 
-func runListener(stream *Stream, listener net.Listener, logger *log.Logger) {
-	defer logger.Noticef("closed")
-	logger.Noticef("accepting connections")
+func runListener(stream *Stream, listener net.Listener, logger *log.Logger) error {
+	stream.BindCloser(listener)
 
-	for {
-		if subConn, err := waitNewConnection(listener); err == nil {
-			subLogger := logger.WithField("client", subConn.RemoteAddr())
-			NewConnection(subConn, stream, subLogger)
-		} else if netErr, ok := err.(net.Error); ok {
-			if netErr.Timeout() {
-				continue
+	safe.Go(func() {
+		defer func() {
+			if err := listener.Close(); err != nil {
+				logger.Noticef("error closing the listener: %s", err)
+			} else {
+				logger.Noticef("closed")
 			}
-			if !netErr.Temporary() {
-				logger.Errorf("%s", err)
-				return
+		}()
+		logger.Noticef("accepting connections")
+
+		for {
+			if subConn, err := waitNewConnection(listener); err == nil {
+				subLogger := logger.WithField("client", subConn.RemoteAddr())
+				NewConnection(subConn, stream, subLogger)
+			} else if netErr, ok := err.(net.Error); ok {
+				if netErr.Timeout() {
+					continue
+				}
+				if !netErr.Temporary() {
+					logger.Errorf("%s", err)
+					return
+				}
+				logger.Warningf("%s", err)
 			}
-			logger.Warningf("%s", err)
 		}
-	}
+	})
+
+	return nil
 }
 
 type SetDeadliner interface {
diff --git a/go.mod b/go.mod
index ac345c59c701f27f3638217aece5d7cde6a7175f..bb41a8d2153b41e4ead95a733432478d5b07b472 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,12 @@ module gitlab.irstea.fr/guillaume.perreal/coord
 go 1.12
 
 require (
+	github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5 // indirect
 	github.com/google/uuid v1.1.1
 	github.com/joomcode/errorx v0.8.0
+	github.com/labstack/gommon v0.2.9 // indirect
+	github.com/mkideal/cli v0.0.3
+	github.com/mkideal/pkg v0.0.0-20170503154153-3e188c9e7ecc // indirect
 	github.com/stretchr/testify v1.3.0
+	golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 // indirect
 )
diff --git a/go.sum b/go.sum
index 1fba44306de43e09907a512ae09db1e9af196a3d..bb09fed6c61e7d1b3344a1836c8171228d1b5a06 100644
--- a/go.sum
+++ b/go.sum
@@ -1,12 +1,39 @@
+github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5 h1:7tNlRGC3pUEPKS3DwgX5L0s+cBloaq/JBoi9ceN1MCM=
+github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ=
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/joomcode/errorx v0.8.0 h1:GhAqPtcYuo1O7TOIbtzEIDzPGQ3SrKJ3tdjXNmUtDNo=
 github.com/joomcode/errorx v0.8.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
+github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU=
+github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4=
+github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mkideal/cli v0.0.3 h1:Y1OXyfTVI9eQ9RTiXq12h7q88y22Q9ZU4VI09ifz6lE=
+github.com/mkideal/cli v0.0.3/go.mod h1:HLuSls75T7LFlTgByGeuLwcvdUmmx/aUQxnnEKxoZzY=
+github.com/mkideal/pkg v0.0.0-20170503154153-3e188c9e7ecc h1:eyN9UWVX+CeeCQZPudCUAPc84xQYTjEu9MWNa2HuJrs=
+github.com/mkideal/pkg v0.0.0-20170503154153-3e188c9e7ecc/go.mod h1:DECgB56amjU/mmmsKuooNPQ1856HASOMC3D4ntSVU70=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU=
+golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed h1:uPxWBzB3+mlnjy9W58qY1j/cjyFjutgw/Vhan2zLy/A=
+golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/lib/log/default.go b/lib/log/default.go
index 1f762d2454f5dd11534eb516966a33d3b08816be..97b4f34a3e817af3c626028df365c18a2d923c29 100644
--- a/lib/log/default.go
+++ b/lib/log/default.go
@@ -5,7 +5,7 @@ import (
 )
 
 // DefaultLogger is the package logger.
-var DefaultLogger = &Logger{NewLogWriter(DEBUG, os.Stderr, TextFormatter)}
+var DefaultLogger = NewLogger(NewLogWriter(DEBUG, os.Stderr, TextFormatter))
 
 // Debug logs a message at DEBUG level using the package logger.
 func Debug(message string)    { DefaultLogger.Debug(message) }
diff --git a/lib/log/logger.go b/lib/log/logger.go
index 40586d8412faf06965889906240f1e78b4a04730..669621241e3c7a8e491b26c433d349dc5b2822e1 100644
--- a/lib/log/logger.go
+++ b/lib/log/logger.go
@@ -16,6 +16,10 @@ func (f RawLoggerFunc) Log(entry *Entry) { f(entry) }
 // Logger adds convenience methods to a RawLogger.
 type Logger struct{ inner RawLogger }
 
+func NewLogger(inner RawLogger) *Logger {
+	return &Logger{inner}
+}
+
 func (l *Logger) Log(entry *Entry) { l.inner.Log(entry) }
 
 func (l *Logger) Debug(message string)    { l.inner.Log(NewEntry(DEBUG, message)) }