|
|
@@ -1,4 +1,4 @@
|
|
|
-#!/usr/bin/python
|
|
|
+#!/usr/bin/env python
|
|
|
# -*- coding: utf-8 -*-
|
|
|
#
|
|
|
# This file is part of Radicale Server - Calendar Server
|
|
|
@@ -26,10 +26,9 @@
|
|
|
# pylint: disable-msg=W0406
|
|
|
|
|
|
"""
|
|
|
-Radicale Server entry point.
|
|
|
+Radicale CalDAV Server.
|
|
|
|
|
|
-Launch the Radicale Server according to configuration and command-line
|
|
|
-arguments.
|
|
|
+Launch the server according to configuration and command-line options.
|
|
|
|
|
|
"""
|
|
|
|
|
|
@@ -38,15 +37,13 @@ arguments.
|
|
|
import os
|
|
|
import sys
|
|
|
import optparse
|
|
|
+import signal
|
|
|
+import threading
|
|
|
|
|
|
import radicale
|
|
|
|
|
|
# Get command-line options
|
|
|
-parser = optparse.OptionParser()
|
|
|
-parser.add_option(
|
|
|
- "-v", "--version", action="store_true",
|
|
|
- default=False,
|
|
|
- help="show version and exit")
|
|
|
+parser = optparse.OptionParser(version=radicale.VERSION)
|
|
|
parser.add_option(
|
|
|
"-d", "--daemon", action="store_true",
|
|
|
default=radicale.config.getboolean("server", "daemon"),
|
|
|
@@ -55,13 +52,9 @@ parser.add_option(
|
|
|
"-f", "--foreground", action="store_false", dest="daemon",
|
|
|
help="launch in foreground (opposite of --daemon)")
|
|
|
parser.add_option(
|
|
|
- "-H", "--host",
|
|
|
- default=radicale.config.get("server", "host"),
|
|
|
- help="set server hostname")
|
|
|
-parser.add_option(
|
|
|
- "-p", "--port", type="int",
|
|
|
- default=radicale.config.getint("server", "port"),
|
|
|
- help="set server port")
|
|
|
+ "-H", "--hosts",
|
|
|
+ default=radicale.config.get("server", "hosts"),
|
|
|
+ help="set server hostnames and ports")
|
|
|
parser.add_option(
|
|
|
"-s", "--ssl", action="store_true",
|
|
|
default=radicale.config.getboolean("server", "ssl"),
|
|
|
@@ -72,11 +65,11 @@ parser.add_option(
|
|
|
parser.add_option(
|
|
|
"-k", "--key",
|
|
|
default=radicale.config.get("server", "key"),
|
|
|
- help="private key file ")
|
|
|
+ help="set private key file")
|
|
|
parser.add_option(
|
|
|
"-c", "--certificate",
|
|
|
default=radicale.config.get("server", "certificate"),
|
|
|
- help="certificate file ")
|
|
|
+ help="set certificate file")
|
|
|
options = parser.parse_args()[0]
|
|
|
|
|
|
# Update Radicale configuration according to options
|
|
|
@@ -86,19 +79,52 @@ for option in parser.option_list:
|
|
|
value = getattr(options, key)
|
|
|
radicale.config.set("server", key, value)
|
|
|
|
|
|
-# Print version and exit if the option is given
|
|
|
-if options.version:
|
|
|
- print(radicale.VERSION)
|
|
|
- sys.exit()
|
|
|
-
|
|
|
# Fork if Radicale is launched as daemon
|
|
|
if options.daemon:
|
|
|
if os.fork():
|
|
|
sys.exit()
|
|
|
sys.stdout = sys.stderr = open(os.devnull, "w")
|
|
|
|
|
|
-# Launch calendar server
|
|
|
+# Create calendar servers
|
|
|
+servers = []
|
|
|
server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer
|
|
|
-server = server_class(
|
|
|
- (options.host, options.port), radicale.CalendarHTTPHandler)
|
|
|
-server.serve_forever()
|
|
|
+shutdown_program = threading.Event()
|
|
|
+
|
|
|
+for host in options.hosts.split(','):
|
|
|
+ address, port = host.strip().rsplit(':', 1)
|
|
|
+ address, port = address.strip('[] '), int(port)
|
|
|
+ servers.append(server_class((address, port), radicale.CalendarHTTPHandler))
|
|
|
+
|
|
|
+# SIGTERM and SIGINT (aka KeyboardInterrupt) should just mark this for shutdown
|
|
|
+signal.signal(signal.SIGTERM, lambda *_: shutdown_program.set())
|
|
|
+signal.signal(signal.SIGINT, lambda *_: shutdown_program.set())
|
|
|
+
|
|
|
+def serve_forever(server):
|
|
|
+ """Serve a server forever, cleanly shutdown when things go wrong."""
|
|
|
+ try:
|
|
|
+ server.serve_forever()
|
|
|
+ finally:
|
|
|
+ shutdown_program.set()
|
|
|
+
|
|
|
+# Start the servers in a different loop to avoid possible race-conditions, when
|
|
|
+# a server exists but another server is added to the list at the same time
|
|
|
+for server in servers:
|
|
|
+ threading.Thread(target=serve_forever, args=(server,)).start()
|
|
|
+
|
|
|
+# Main loop: wait until all servers are exited
|
|
|
+try:
|
|
|
+ # We must do the busy-waiting here, as all ``.join()`` calls completly
|
|
|
+ # block the thread, such that signals are not received
|
|
|
+ while True:
|
|
|
+ # The number is irrelevant, it only needs to be greater than 0.05 due
|
|
|
+ # to python implementing its own busy-waiting logic
|
|
|
+ shutdown_program.wait(5.0)
|
|
|
+ if shutdown_program.is_set():
|
|
|
+ break
|
|
|
+finally:
|
|
|
+ # Ignore signals, so that they cannot interfere
|
|
|
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
+ signal.signal(signal.SIGTERM, signal.SIG_IGN)
|
|
|
+
|
|
|
+ for server in servers:
|
|
|
+ server.shutdown()
|