depot.py revision 1633
1516N/A#!/usr/bin/python2.6
290N/A#
290N/A# CDDL HEADER START
290N/A#
290N/A# The contents of this file are subject to the terms of the
290N/A# Common Development and Distribution License (the "License").
290N/A# You may not use this file except in compliance with the License.
290N/A#
290N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
290N/A# or http://www.opensolaris.org/os/licensing.
290N/A# See the License for the specific language governing permissions
290N/A# and limitations under the License.
290N/A#
290N/A# When distributing Covered Code, include this CDDL HEADER in each
290N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
290N/A# If applicable, add the following below this CDDL HEADER, with the
290N/A# fields enclosed by brackets "[]" replaced with your own identifying
290N/A# information: Portions Copyright [yyyy] [name of copyright owner]
290N/A#
290N/A# CDDL HEADER END
290N/A#
1660N/A# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
395N/A# Use is subject to license terms.
395N/A#
290N/A
883N/A# pkg.depotd - package repository daemon
454N/A
290N/A# XXX The prototype pkg.depotd combines both the version management server that
448N/A# answers to pkgsend(1) sessions and the HTTP file server that answers to the
290N/A# various GET operations that a pkg(1) client makes. This split is expected to
290N/A# be made more explicit, by constraining the pkg(1) operations such that they
290N/A# can be served as a typical HTTP/HTTPS session. Thus, pkg.depotd will reduce
383N/A# to a special purpose HTTP/HTTPS server explicitly for the version management
290N/A# operations, and must manipulate the various state files--catalogs, in
395N/A# particular--such that the pkg(1) pull client can operately accurately with
290N/A# only a basic HTTP/HTTPS server in place.
395N/A
849N/A# XXX Although we pushed the evaluation of next-version, etc. to the pull
1516N/A# client, we should probably provide a query API to do same on the server, for
290N/A# dumb clients (like a notification service).
849N/A
290N/A# The default repository path.
290N/AREPO_PATH_DEFAULT = "/var/pkg/repo"
290N/A# The default path for static and other web content.
290N/ACONTENT_PATH_DEFAULT = "/usr/share/lib/pkg"
383N/A# cherrypy has a max_request_body_size parameter that determines whether the
290N/A# server should abort requests with REQUEST_ENTITY_TOO_LARGE when the request
290N/A# body is larger than the specified size (in bytes). The maximum size supported
290N/A# by cherrypy is 2048 * 1024 * 1024 - 1 (just short of 2048MB), but the default
290N/A# here is purposefully conservative.
290N/AMAX_REQUEST_BODY_SIZE = 128 * 1024 * 1024
290N/A# The default port(s) to serve data from.
290N/APORT_DEFAULT = 80
290N/ASSL_PORT_DEFAULT = 443
1660N/A# The minimum number of threads allowed.
1660N/ATHREADS_MIN = 1
1660N/A# The default number of threads to start.
1660N/ATHREADS_DEFAULT = 10
1660N/A# The maximum number of threads that can be started.
1660N/ATHREADS_MAX = 500
1660N/A# The default server socket timeout in seconds. We want this to be longer than
1660N/A# the normal default of 10 seconds to accommodate clients with poor quality
1660N/A# connections.
1660N/ASOCKET_TIMEOUT_DEFAULT = 60
1660N/A# Whether modify operations should be allowed.
1660N/AREADONLY_DEFAULT = False
1660N/A# Whether the repository catalog should be rebuilt on startup.
1660N/AREBUILD_DEFAULT = False
1660N/A# Whether the indexes should be rebuilt
1660N/AREINDEX_DEFAULT = False
1660N/A# Not in mirror mode by default
1660N/AMIRROR_DEFAULT = False
465N/A
465N/Aimport getopt
465N/Aimport gettext
1516N/Aimport locale
465N/Aimport logging
465N/Aimport os
465N/Aimport os.path
1516N/Aimport OpenSSL.crypto as crypto
465N/Aimport subprocess
465N/Aimport sys
465N/Aimport tempfile
465N/Aimport urlparse
465N/A
465N/Atry:
465N/A import cherrypy
1099N/A version = cherrypy.__version__.split('.')
465N/A if map(int, version) < [3, 1, 0]:
1513N/A raise ImportError
1513N/A elif map(int, version) >= [3, 2, 0]:
1514N/A raise ImportError
1513N/Aexcept ImportError:
1513N/A print >> sys.stderr, """cherrypy 3.1.0 or greater (but less than """ \
1513N/A """3.2.0) is required to use this program."""
1099N/A sys.exit(2)
1513N/A
708N/Afrom pkg.misc import port_available, msg, emsg, setlocale
1391N/Aimport pkg.client.api_errors as api_errors
1391N/Aimport pkg.indexer as indexer
1391N/Aimport pkg.portable.util as os_util
1391N/Aimport pkg.search_errors as search_errors
1391N/Aimport pkg.server.depot as ds
1391N/Aimport pkg.server.depotresponse as dr
1391N/Aimport pkg.server.repository as sr
1391N/Aimport pkg.server.repositoryconfig as rc
1391N/A
1391N/A
1391N/Aclass LogSink(object):
742N/A """This is a dummy object that we can use to discard log entries
742N/A without relying on non-portable interfaces such as /dev/null."""
742N/A
742N/A def write(self, *args, **kwargs):
742N/A """Discard the bits."""
742N/A pass
1099N/A
742N/A def flush(self, *args, **kwargs):
941N/A """Discard the bits."""
941N/A pass
941N/A
941N/A
941N/Adef usage(text=None, retcode=2, full=False):
941N/A """Optionally emit a usage message and then exit using the specified
1099N/A exit code."""
941N/A
1191N/A if text:
1513N/A emsg(text)
1191N/A
1191N/A if not full:
1191N/A # The full usage message isn't desired.
1191N/A emsg(_("Try `pkg.depotd --help or -?' for more "
1191N/A "information."))
1660N/A sys.exit(retcode)
1660N/A
1660N/A print """\
290N/AUsage: /usr/lib/pkg.depotd [-d repo_dir] [-p port] [-s threads]
448N/A [-t socket_timeout] [--cfg-file] [--content-root]
448N/A [--disable-ops op[/1][,...]] [--debug feature_list]
534N/A [--log-access dest] [--log-errors dest] [--mirror] [--nasty]
534N/A [--set-property <section.property>=<value>]
534N/A [--proxy-base url] [--readonly] [--rebuild] [--ssl-cert-file]
534N/A [--ssl-dialog] [--ssl-key-file] [--sort-file-max-size size]
534N/A [--writable-root dir]
534N/A
534N/A --add-content Check the repository on startup and add any new
290N/A packages found. Cannot be used with --mirror or
290N/A --readonly.
954N/A --cfg-file The pathname of the file from which to read and to
954N/A write configuration information.
954N/A --content-root The file system path to the directory containing the
954N/A the static and other web content used by the depot's
534N/A browser user interface. The default value is
1099N/A '/usr/share/lib/pkg'.
290N/A --disable-ops A comma separated list of operations that the depot
1191N/A should not configure. If, for example, you wanted
1191N/A to omit loading search v1, 'search/1' should be
1191N/A provided as an argument, or to disable all search
1516N/A operations, simply 'search'.
290N/A --debug The name of a debug feature to enable; or a whitespace
290N/A or comma separated list of features to enable.
290N/A Possible values are: headers.
661N/A --exit-ready Perform startup processing (including rebuilding
290N/A catalog or indices, if requested) and exit when
290N/A ready to start serving packages.
290N/A --log-access The destination for any access related information
395N/A logged by the depot process. Possible values are:
290N/A stderr, stdout, none, or an absolute pathname. The
290N/A default value is stdout if stdout is a tty; otherwise
290N/A the default value is none.
1483N/A --log-errors The destination for any errors or other information
290N/A logged by the depot process. Possible values are:
1498N/A stderr, stdout, none, or an absolute pathname. The
1498N/A default value is stderr.
290N/A --mirror Package mirror mode; publishing and metadata operations
1674N/A disallowed. Cannot be used with --readonly or
1674N/A --rebuild.
1674N/A --nasty Instruct the server to misbehave. At random intervals
1674N/A it will time-out, send bad responses, hang up on
1674N/A clients, and generally be hostile. The option
1674N/A takes a value (1 to 100) for how nasty the server
1674N/A should be.
395N/A --proxy-base The url to use as the base for generating internal
430N/A redirects and content.
395N/A --readonly Read-only operation; modifying operations disallowed.
1544N/A Cannot be used with --mirror or --rebuild.
1557N/A --rebuild Re-build the catalog from pkgs in depot. Cannot be
1506N/A used with --mirror or --readonly.
395N/A --set-property Used to specify initial repository configuration
395N/A property values or to update existing ones; can
424N/A be specified multiple times. If used with --readonly
1024N/A this acts as a temporary override.
395N/A --ssl-cert-file The absolute pathname to a PEM-encoded Certificate file.
395N/A This option must be used with --ssl-key-file. Usage of
395N/A this option will cause the depot to only respond to SSL
578N/A requests on the provided port.
1228N/A --ssl-dialog Specifies what method should be used to obtain the
1172N/A passphrase needed to decrypt the file specified by
395N/A --ssl-key-file. Supported values are: builtin,
661N/A exec:/path/to/program, or smf:fmri. The default value
1099N/A is builtin.
661N/A --ssl-key-file The absolute pathname to a PEM-encoded Private Key file.
395N/A This option must be used with --ssl-cert-file. Usage of
849N/A this option will cause the depot to only respond to SSL
290N/A requests on the provided port.
395N/A --sort-file-max-size
395N/A The maximum size of the indexer sort file. Used to
395N/A limit the amount of RAM the depot uses for indexing,
395N/A or increase it for speed.
395N/A --writable-root The path to a directory to which the program has write
395N/A access. Used with --readonly to allow server to
395N/A create needed files, such as search indices, without
395N/A needing write access to the package information.
395N/A
395N/AOptions:
395N/A --help or -?
395N/A
395N/AEnvironment:
290N/A PKG_REPO Used as default repo_dir if -d not provided.
290N/A PKG_DEPOT_CONTENT Used as default content_root if --content-root
395N/A not provided."""
395N/A sys.exit(retcode)
1231N/A
1557N/Aclass OptionError(Exception):
1557N/A """Option exception. """
395N/A
395N/A def __init__(self, *args):
395N/A Exception.__init__(self, *args)
395N/A
395N/Aif __name__ == "__main__":
395N/A
395N/A setlocale(locale.LC_ALL, "")
395N/A gettext.install("pkg", "/usr/share/locale")
395N/A
395N/A debug_features = {
395N/A "headers": False,
290N/A }
290N/A disable_ops = {}
430N/A port = PORT_DEFAULT
395N/A port_provided = False
395N/A threads = THREADS_DEFAULT
395N/A socket_timeout = SOCKET_TIMEOUT_DEFAULT
395N/A readonly = READONLY_DEFAULT
1302N/A rebuild = REBUILD_DEFAULT
395N/A reindex = REINDEX_DEFAULT
395N/A proxy_base = None
290N/A mirror = MIRROR_DEFAULT
395N/A nasty = False
1024N/A nasty_value = 0
413N/A repo_config_file = None
1544N/A sort_file_max_size = indexer.SORT_FILE_MAX_SIZE
1557N/A ssl_cert_file = None
1506N/A ssl_key_file = None
413N/A ssl_dialog = "builtin"
413N/A writable_root = None
1024N/A add_content = False
395N/A exit_ready = False
395N/A
413N/A if "PKG_REPO" in os.environ:
395N/A repo_path = os.environ["PKG_REPO"]
395N/A else:
413N/A repo_path = REPO_PATH_DEFAULT
395N/A
395N/A try:
395N/A content_root = os.environ["PKG_DEPOT_CONTENT"]
395N/A except KeyError:
395N/A try:
395N/A content_root = os.path.join(os.environ['PKG_HOME'],
1191N/A 'share/lib/pkg')
1452N/A except KeyError:
1231N/A content_root = CONTENT_PATH_DEFAULT
395N/A
395N/A # By default, if the destination for a particular log type is not
424N/A # specified, this is where we will send the output.
395N/A log_routes = {
742N/A "access": "none",
742N/A "errors": "stderr"
742N/A }
742N/A log_opts = ["--log-%s" % log_type for log_type in log_routes]
742N/A
742N/A # If stdout is a tty, then send access output there by default instead
742N/A # of discarding it.
742N/A if os.isatty(sys.stdout.fileno()):
742N/A log_routes["access"] = "stdout"
742N/A
742N/A opt = None
395N/A repo_props = {}
395N/A try:
395N/A long_opts = ["add-content", "cfg-file=", "content-root=",
395N/A "debug=", "disable-ops=", "exit-ready", "help", "mirror",
395N/A "nasty=", "set-property=", "proxy-base=", "readonly",
954N/A "rebuild", "refresh-index", "ssl-cert-file=", "ssl-dialog=",
954N/A "ssl-key-file=", "sort-file-max-size=", "writable-root="]
954N/A
954N/A for opt in log_opts:
954N/A long_opts.append("%s=" % opt.lstrip('--'))
954N/A opts, pargs = getopt.getopt(sys.argv[1:], "d:np:s:t:?",
954N/A long_opts)
395N/A
1483N/A show_usage = False
1483N/A for opt, arg in opts:
1483N/A if opt == "-n":
1483N/A sys.exit(0)
395N/A elif opt == "-d":
1099N/A repo_path = arg
1099N/A elif opt == "-p":
395N/A port = int(arg)
1498N/A port_provided = True
1498N/A elif opt == "-s":
691N/A threads = int(arg)
691N/A if threads < THREADS_MIN:
691N/A raise OptionError, \
395N/A "minimum value is %d" % THREADS_MIN
395N/A if threads > THREADS_MAX:
395N/A raise OptionError, \
395N/A "maximum value is %d" % THREADS_MAX
395N/A elif opt == "-t":
290N/A socket_timeout = int(arg)
395N/A elif opt == "--add-content":
395N/A add_content = True
591N/A elif opt == "--cfg-file":
591N/A repo_config_file = os.path.abspath(arg)
591N/A elif opt == "--content-root":
1505N/A if arg == "":
1505N/A raise OptionError, "You must specify " \
1505N/A "a directory path."
1505N/A content_root = arg
1632N/A elif opt == "--debug":
1632N/A if arg is None or arg == "":
1632N/A raise OptionError, \
1632N/A "A debug feature must be specified."
395N/A
395N/A # A list of features can be specified using a
290N/A # "," or any whitespace character as separators.
290N/A if "," in arg:
290N/A features = arg.split(",")
290N/A else:
290N/A features = arg.split()
290N/A
290N/A for f in features:
290N/A if f not in debug_features:
290N/A raise OptionError, \
290N/A "Invalid debug feature: " \
290N/A "%s." % f
290N/A debug_features[f] = True
290N/A elif opt == "--disable-ops":
290N/A if arg is None or arg == "":
395N/A raise OptionError, \
395N/A "An argument must be specified."
290N/A
290N/A disableops = arg.split(",")
290N/A for s in disableops:
290N/A if "/" in s:
290N/A op, ver = s.rsplit("/", 1)
395N/A else:
395N/A op = s
395N/A ver = "*"
290N/A
395N/A if op not in \
395N/A ds.DepotHTTP.REPO_OPS_DEFAULT:
395N/A raise OptionError(
395N/A "Invalid operation "
591N/A "'%s'." % s)
591N/A
591N/A disable_ops.setdefault(op, [])
591N/A disable_ops[op].append(ver)
691N/A elif opt == "--exit-ready":
691N/A exit_ready = True
691N/A elif opt in log_opts:
691N/A if arg is None or arg == "":
290N/A raise OptionError, \
290N/A "You must specify a log " \
290N/A "destination."
290N/A log_routes[opt.lstrip("--log-")] = arg
290N/A elif opt in ("--help", "-?"):
591N/A show_usage = True
591N/A elif opt == "--mirror":
691N/A mirror = True
691N/A elif opt == "--nasty":
290N/A value_err = None
395N/A try:
395N/A nasty_value = int(arg)
290N/A except ValueError, e:
395N/A value_err = e
395N/A
395N/A if value_err or (nasty_value > 100 or
395N/A nasty_value < 1):
290N/A raise OptionError, "Invalid value " \
290N/A "for nasty option.\n Please " \
290N/A "choose a value between 1 and 100."
395N/A nasty = True
395N/A elif opt == "--set-property":
395N/A try:
395N/A prop, p_value = arg.split("=", 1)
395N/A p_sec, p_name = prop.split(".", 1)
395N/A except ValueError:
290N/A usage(_("property arguments must be of "
290N/A "the form '<section.property>="
290N/A "<value>'."))
290N/A repo_props.setdefault(p_sec, {})
290N/A repo_props[p_sec][p_name] = p_value
430N/A elif opt == "--proxy-base":
290N/A # Attempt to decompose the url provided into
290N/A # its base parts. This is done so we can
395N/A # remove any scheme information since we
290N/A # don't need it.
395N/A scheme, netloc, path, params, query, \
506N/A fragment = urlparse.urlparse(arg,
506N/A "http", allow_fragments=0)
506N/A
506N/A if not netloc:
506N/A raise OptionError, "Unable to " \
506N/A "determine the hostname from " \
506N/A "the provided URL; please use a " \
506N/A "fully qualified URL."
834N/A
506N/A scheme = scheme.lower()
506N/A if scheme not in ("http", "https"):
506N/A raise OptionError, "Invalid URL; http " \
513N/A "and https are the only supported " \
506N/A "schemes."
506N/A
506N/A # Rebuild the url with the sanitized components.
506N/A proxy_base = urlparse.urlunparse((scheme,
290N/A netloc, path, params, query, fragment))
290N/A elif opt == "--readonly":
395N/A readonly = True
395N/A elif opt == "--rebuild":
849N/A rebuild = True
849N/A elif opt == "--refresh-index":
883N/A # Note: This argument is for internal use
883N/A # only. It's used when pkg.depotd is reexecing
395N/A # itself and needs to know that's the case.
413N/A # This flag is purposefully omitted in usage.
413N/A # The supported way to forcefully reindex is to
413N/A # kill any pkg.depot using that directory,
395N/A # remove the index directory, and restart the
290N/A # pkg.depot process. The index will be rebuilt
1674N/A # automatically on startup.
1674N/A reindex = True
1674N/A elif opt == "--ssl-cert-file":
1674N/A if arg == "none":
1674N/A continue
1674N/A
1674N/A ssl_cert_file = arg
1674N/A if not os.path.isabs(ssl_cert_file):
1674N/A raise OptionError, "The path to " \
1674N/A "the Certificate file must be " \
1674N/A "absolute."
1674N/A elif not os.path.exists(ssl_cert_file):
1674N/A raise OptionError, "The specified " \
1674N/A "file does not exist."
1674N/A elif not os.path.isfile(ssl_cert_file):
395N/A raise OptionError, "The specified " \
395N/A "pathname is not a file."
506N/A elif opt == "--ssl-key-file":
506N/A if arg == "none":
395N/A continue
395N/A
395N/A ssl_key_file = arg
395N/A if not os.path.isabs(ssl_key_file):
430N/A raise OptionError, "The path to " \
849N/A "the Private Key file must be " \
834N/A "absolute."
290N/A elif not os.path.exists(ssl_key_file):
1191N/A raise OptionError, "The specified " \
1191N/A "file does not exist."
1191N/A elif not os.path.isfile(ssl_key_file):
1191N/A raise OptionError, "The specified " \
1391N/A "pathname is not a file."
1391N/A elif opt == "--ssl-dialog":
1401N/A if arg != "builtin" and not \
1391N/A arg.startswith("exec:/") and not \
1391N/A arg.startswith("smf:"):
1391N/A raise OptionError, "Invalid value " \
1391N/A "specified. Expected: builtin, " \
1391N/A "exec:/path/to/program, or " \
1391N/A "smf:fmri."
1391N/A
1391N/A f = arg
836N/A if f.startswith("exec:"):
836N/A if os_util.get_canonical_os_type() != \
849N/A "unix":
849N/A # Don't allow a somewhat
849N/A # insecure authentication method
849N/A # on some platforms.
849N/A raise OptionError, "exec is " \
849N/A "not a supported dialog " \
849N/A "type for this operating " \
849N/A "system."
849N/A
849N/A f = os.path.abspath(f.split(
849N/A "exec:")[1])
849N/A
1513N/A if not os.path.isfile(f):
1391N/A raise OptionError, "Invalid " \
849N/A "file path specified for " \
1513N/A "exec."
1391N/A
1513N/A f = "exec:%s" % f
1391N/A
1391N/A ssl_dialog = f
1660N/A elif opt == "--sort-file-max-size":
1513N/A if arg == "":
1513N/A raise OptionError, "You must specify " \
290N/A "a maximum sort file size."
883N/A sort_file_max_size = arg
883N/A elif opt == "--writable-root":
883N/A if arg == "":
883N/A raise OptionError, "You must specify " \
883N/A "a directory path."
883N/A writable_root = arg
883N/A except getopt.GetoptError, _e:
883N/A usage("pkg.depotd: %s" % _e.msg)
883N/A except OptionError, _e:
883N/A usage("pkg.depotd: option: %s -- %s" % (opt, _e))
883N/A except (ArithmeticError, ValueError):
883N/A usage("pkg.depotd: illegal option value: %s specified " \
883N/A "for option: %s" % (arg, opt))
883N/A
883N/A if show_usage:
883N/A usage(retcode=0, full=True)
1099N/A
1099N/A if rebuild and add_content:
1099N/A usage("--add-content cannot be used with --rebuild")
1099N/A if rebuild and reindex:
1099N/A usage("--refresh-index cannot be used with --rebuild")
1516N/A if (rebuild or add_content) and (readonly or mirror):
1265N/A usage("--readonly and --mirror cannot be used with --rebuild "
1099N/A "or --add-content")
1099N/A if reindex and mirror:
1099N/A usage("--mirror cannot be used with --refresh-index")
1099N/A if reindex and readonly and not writable_root:
1099N/A usage("--readonly can only be used with --refresh-index if "
1099N/A "--writable-root is used")
1099N/A
1099N/A if (ssl_cert_file and not ssl_key_file) or (ssl_key_file and not
1099N/A ssl_cert_file):
1099N/A usage("The --ssl-cert-file and --ssl-key-file options must "
1208N/A "must both be provided when using either option.")
1208N/A elif ssl_cert_file and ssl_key_file and not port_provided:
1099N/A # If they didn't already specify a particular port, use the
1099N/A # default SSL port instead.
1191N/A port = SSL_PORT_DEFAULT
1191N/A
1191N/A # If the program is going to reindex, the port is irrelevant since
1191N/A # the program will not bind to a port.
1191N/A if not reindex and not exit_ready:
1191N/A available, msg = port_available(None, port)
1191N/A if not available:
1191N/A print "pkg.depotd: unable to bind to the specified " \
1191N/A "port: %d. Reason: %s" % (port, msg)
1191N/A sys.exit(1)
1191N/A else:
1191N/A # Not applicable if we're not going to serve content
1191N/A content_root = None
1191N/A
1265N/A key_data = None
1191N/A if not reindex and ssl_cert_file and ssl_key_file and \
1191N/A ssl_dialog != "builtin":
1191N/A cmdline = None
1191N/A def get_ssl_passphrase(*ignored):
1191N/A p = None
1191N/A try:
1191N/A p = subprocess.Popen(cmdline, shell=True,
1191N/A stdout=subprocess.PIPE,
1191N/A stderr=None)
1191N/A p.wait()
1191N/A except Exception, __e:
1191N/A print "pkg.depotd: an error occurred while " \
1265N/A "executing [%s]; unable to obtain the " \
1265N/A "passphrase needed to decrypt the SSL" \
1265N/A "private key file: %s" % (cmdline, __e)
1265N/A sys.exit(1)
1099N/A return p.stdout.read().strip("\n")
1391N/A
1099N/A if ssl_dialog.startswith("exec:"):
1099N/A cmdline = "%s %s %d" % (ssl_dialog.split("exec:")[1],
1099N/A "''", port)
1099N/A elif ssl_dialog.startswith("smf:"):
1099N/A cmdline = "/usr/bin/svcprop -p " \
465N/A "pkg_secure/ssl_key_passphrase %s" % (
465N/A ssl_dialog.split("smf:")[1])
395N/A
465N/A # The key file requires decryption, but the user has requested
395N/A # exec-based authentication, so it will have to be decoded first
395N/A # to an un-named temporary file.
465N/A try:
465N/A key_file = file(ssl_key_file, "rb")
465N/A pkey = crypto.load_privatekey(crypto.FILETYPE_PEM,
1208N/A key_file.read(), get_ssl_passphrase)
1208N/A
465N/A key_data = tempfile.TemporaryFile()
395N/A key_data.write(crypto.dump_privatekey(
465N/A crypto.FILETYPE_PEM, pkey))
395N/A key_data.seek(0)
465N/A except EnvironmentError, _e:
1099N/A print "pkg.depotd: unable to read the SSL private " \
1099N/A "key file: %s" % _e
1099N/A sys.exit(1)
465N/A except crypto.Error, _e:
465N/A print "pkg.depotd: authentication or cryptography " \
395N/A "failure while attempting to decode\nthe SSL " \
395N/A "private key file: %s" % _e
1099N/A sys.exit(1)
395N/A else:
1191N/A # Redirect the server to the decrypted key file.
1191N/A ssl_key_file = "/dev/fd/%d" % key_data.fileno()
1191N/A
1191N/A # Setup our global configuration.
1191N/A gconf = {
1191N/A "checker.on": True,
1191N/A "environment": "production",
1191N/A "log.screen": False,
1191N/A "server.max_request_body_size": MAX_REQUEST_BODY_SIZE,
1191N/A "server.shutdown_timeout": 0,
1265N/A "server.socket_host": "0.0.0.0",
1265N/A "server.socket_port": port,
1265N/A "server.socket_timeout": socket_timeout,
1208N/A "server.ssl_certificate": ssl_cert_file,
1208N/A "server.ssl_private_key": ssl_key_file,
1208N/A "server.thread_pool": threads,
1208N/A "tools.log_headers.on": True,
1208N/A "tools.encode.on": True
1208N/A }
1208N/A
1191N/A if debug_features["headers"]:
1191N/A # Despite its name, this only logs headers when there is an
1391N/A # error; it's redundant with the debug feature enabled.
1391N/A gconf["tools.log_headers.on"] = False
1391N/A
1391N/A # Causes the headers of every request to be logged to the error
1391N/A # log; even if an exception occurs.
1391N/A gconf["tools.log_headers_always.on"] = True
1391N/A cherrypy.tools.log_headers_always = cherrypy.Tool(
1394N/A "on_start_resource",
1394N/A cherrypy.lib.cptools.log_request_headers)
1391N/A
1391N/A log_type_map = {
1391N/A "errors": {
1391N/A "param": "log.error_file",
1391N/A "attr": "error_log"
1391N/A },
1391N/A "access": {
1660N/A "param": "log.access_file",
1391N/A "attr": "access_log"
465N/A }
1660N/A }
1660N/A
1660N/A for log_type in log_type_map:
1660N/A dest = log_routes[log_type]
465N/A if dest in ("stdout", "stderr", "none"):
465N/A if dest == "none":
1516N/A h = logging.StreamHandler(LogSink())
498N/A else:
498N/A h = logging.StreamHandler(eval("sys.%s" % \
849N/A dest))
1660N/A
1391N/A h.setLevel(logging.DEBUG)
1660N/A h.setFormatter(cherrypy._cplogging.logfmt)
1660N/A log_obj = eval("cherrypy.log.%s" % \
1660N/A log_type_map[log_type]["attr"])
1660N/A log_obj.addHandler(h)
849N/A # Since we've replaced cherrypy's log handler with our
1208N/A # own, we don't want the output directed to a file.
1208N/A dest = ""
1208N/A gconf[log_type_map[log_type]["param"]] = dest
1208N/A
849N/A cherrypy.config.update(gconf)
290N/A
465N/A # Now that our logging, etc. has been setup, it's safe to perform any
465N/A # remaining preparation.
1099N/A
465N/A # Initialize repository state.
1099N/A fork_allowed = not reindex and not exit_ready
1099N/A try:
1099N/A repo = sr.Repository(auto_create=not readonly,
454N/A cfgpathname=repo_config_file, fork_allowed=fork_allowed,
1099N/A log_obj=cherrypy, mirror=mirror, properties=repo_props,
849N/A read_only=readonly, refresh_index=not add_content,
290N/A repo_root=repo_path, sort_file_max_size=sort_file_max_size,
430N/A writable_root=writable_root)
395N/A except sr.RepositoryError, _e:
395N/A emsg("pkg.depotd: %s" % _e)
290N/A sys.exit(1)
383N/A except rc.RequiredPropertyValueError, _e:
383N/A emsg("pkg.depotd: repository configuration error: %s" % _e)
395N/A emsg("Please use the --set-property option to provide a value, "
383N/A "or update the cfg_cache file for the repository to "
383N/A "correct this.")
384N/A sys.exit(1)
383N/A except rc.PropertyError, _e:
849N/A emsg("pkg.depotd: repository configuration error: %s" % _e)
849N/A sys.exit(1)
849N/A except (search_errors.IndexingException,
849N/A api_errors.PermissionsException), _e:
849N/A emsg(str(_e), "INDEX")
849N/A sys.exit(1)
849N/A
849N/A if reindex:
849N/A # Initializing the repository above updated search indices
849N/A # as needed; nothing left to do, so exit.
849N/A sys.exit(0)
849N/A
849N/A if nasty:
383N/A repo.cfg.set_nasty(nasty_value)
383N/A
383N/A if rebuild:
849N/A try:
383N/A repo.rebuild()
422N/A except sr.RepositoryError, e:
422N/A emsg(str(e), "REBUILD")
422N/A sys.exit(1)
422N/A except (search_errors.IndexingException,
422N/A api_errors.PermissionsException), e:
422N/A emsg(str(e), "INDEX")
422N/A sys.exit(1)
422N/A
422N/A elif add_content:
422N/A try:
422N/A repo.add_content()
422N/A except sr.RepositoryError, e:
422N/A emsg(str(e), "ADD_CONTENT")
422N/A sys.exit(1)
422N/A except (search_errors.IndexingException,
422N/A api_errors.PermissionsException), e:
422N/A emsg(str(e), "INDEX")
383N/A sys.exit(1)
422N/A
383N/A # ready to start depot; exit now if requested
383N/A if exit_ready:
383N/A sys.exit(0)
383N/A
383N/A # Next, initialize depot.
383N/A if nasty:
383N/A depot = ds.NastyDepotHTTP(repo, content_root,
422N/A disable_ops=disable_ops)
849N/A else:
849N/A depot = ds.DepotHTTP(repo, content_root,
849N/A disable_ops=disable_ops)
383N/A
383N/A # Now build our site configuration.
290N/A conf = {
430N/A "/": {
395N/A # We have to override cherrypy's default response_class so that
395N/A # we have access to the write() callable to stream data
290N/A # directly to the client.
290N/A "wsgi.response_class": dr.DepotResponse,
290N/A },
290N/A "/robots.txt": {
290N/A "tools.staticfile.on": True,
290N/A "tools.staticfile.filename": os.path.join(depot.web_root,
290N/A "robots.txt")
290N/A },
290N/A }
290N/A
290N/A if proxy_base:
395N/A # This changes the base URL for our server, and is primarily
290N/A # intended to allow our depot process to operate behind Apache
395N/A # or some other webserver process.
290N/A #
395N/A # Visit the following URL for more information:
290N/A # http://cherrypy.org/wiki/BuiltinTools#tools.proxy
534N/A proxy_conf = {
534N/A "tools.proxy.on": True,
1099N/A "tools.proxy.local": "",
1099N/A "tools.proxy.base": proxy_base
290N/A }
290N/A
1101N/A # Now merge or add our proxy configuration information into the
1101N/A # existing configuration.
1101N/A for entry in proxy_conf:
1513N/A conf["/"][entry] = proxy_conf[entry]
1715N/A
1513N/A try:
1513N/A root = cherrypy.Application(depot)
448N/A cherrypy.quickstart(root, config=conf)
1513N/A except Exception, _e:
448N/A emsg("pkg.depotd: unknown error starting depot server, " \
1101N/A "illegal option value specified?")
1513N/A emsg(_e)
1715N/A sys.exit(1)
1715N/A