depot.py revision 3143
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#
2639N/A# Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
395N/A#
290N/A
883N/Afrom __future__ import print_function
454N/A
290N/A# pkg.depotd - package repository daemon
448N/A
290N/A# XXX The prototype pkg.depotd combines both the version management server that
290N/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
383N/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
395N/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
849N/A# only a basic HTTP/HTTPS server in place.
1516N/A
2508N/A# XXX Although we pushed the evaluation of next-version, etc. to the pull
290N/A# client, we should probably provide a query API to do same on the server, for
2535N/A# dumb clients (like a notification service).
290N/A
290N/A# The default path for static and other web content.
290N/ACONTENT_PATH_DEFAULT = "/usr/share/lib/pkg"
2535N/A# cherrypy has a max_request_body_size parameter that determines whether the
2561N/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
2508N/A# by cherrypy is 2048 * 1024 * 1024 - 1 (just short of 2048MB), but the default
383N/A# here is purposefully conservative.
290N/AMAX_REQUEST_BODY_SIZE = 512 * 1024 * 1024
290N/A# The default host/port(s) to serve data from.
2339N/AHOST_DEFAULT = "0.0.0.0"
2535N/APORT_DEFAULT = 80
290N/ASSL_PORT_DEFAULT = 443
290N/A# The minimum number of threads allowed.
2535N/ATHREADS_MIN = 1
2535N/A# The default number of threads to start.
290N/ATHREADS_DEFAULT = 60
290N/A# The maximum number of threads that can be started.
2508N/ATHREADS_MAX = 5000
2508N/A# The default server socket timeout in seconds. We want this to be longer than
290N/A# the normal default of 10 seconds to accommodate clients with poor quality
1660N/A# connections.
1660N/ASOCKET_TIMEOUT_DEFAULT = 60
1660N/A
1660N/Aimport getopt
1660N/Aimport gettext
1660N/Aimport locale
1660N/Aimport logging
1660N/Aimport os
1660N/Aimport os.path
1660N/Aimport OpenSSL.crypto as crypto
1660N/Aimport subprocess
1660N/Aimport sys
1660N/Aimport tempfile
1660N/Aimport urlparse
1660N/A
1660N/Atry:
1660N/A import cherrypy
1660N/A version = cherrypy.__version__.split('.')
448N/A if map(int, version) < [3, 1, 0]:
448N/A raise ImportError
534N/A elif map(int, version) >= [3, 2, 0]:
534N/A raise ImportError
534N/Aexcept ImportError:
534N/A print("""cherrypy 3.1.0 or greater (but less than """
534N/A """3.2.0) is required to use this program.""", file=sys.stderr)
534N/A sys.exit(2)
534N/A
290N/Aimport cherrypy.process.servers
290N/Afrom cherrypy.process.plugins import Daemonizer
954N/A
954N/Afrom pkg.misc import msg, emsg, setlocale
954N/Afrom pkg.client.debugvalues import DebugValues
954N/A
534N/Aimport pkg
1099N/Aimport pkg.client.api_errors as api_errors
290N/Aimport pkg.config as cfg
1516N/Aimport pkg.portable.util as os_util
290N/Aimport pkg.search_errors as search_errors
290N/Aimport pkg.server.depot as ds
290N/Aimport pkg.server.depotresponse as dr
661N/Aimport pkg.server.repository as sr
290N/A
2494N/A
2494N/Aclass LogSink(object):
2494N/A """This is a dummy object that we can use to discard log entries
2516N/A without relying on non-portable interfaces such as /dev/null."""
2516N/A
2516N/A def write(self, *args, **kwargs):
2516N/A """Discard the bits."""
2516N/A pass
2516N/A
2516N/A def flush(self, *args, **kwargs):
290N/A """Discard the bits."""
2523N/A pass
2390N/A
1498N/A
1498N/Adef usage(text=None, retcode=2, full=False):
2310N/A """Optionally emit a usage message and then exit using the specified
2310N/A exit code."""
2310N/A
2535N/A if text:
2535N/A emsg(text)
2535N/A
2535N/A if not full:
2535N/A # The full usage message isn't desired.
2535N/A emsg(_("Try `pkg.depotd --help or -?' for more "
2535N/A "information."))
2535N/A sys.exit(retcode)
2535N/A
2535N/A print("""\
2310N/AUsage: /usr/lib/pkg.depotd [-a address] [-d inst_root] [-p port] [-s threads]
290N/A [-t socket_timeout] [--cfg] [--content-root]
1674N/A [--disable-ops op[/1][,...]] [--debug feature_list]
1674N/A [--image-root dir] [--log-access dest] [--log-errors dest]
2262N/A [--mirror] [--nasty] [--nasty-sleep] [--proxy-base url]
1674N/A [--readonly] [--ssl-cert-file] [--ssl-dialog] [--ssl-key-file]
395N/A [--sort-file-max-size size] [--writable-root dir]
430N/A
395N/A -a address The IP address on which to listen for connections. The
1544N/A default value is 0.0.0.0 (INADDR_ANY) which will listen
1968N/A on all active interfaces. To listen on all active IPv6
1557N/A interfaces, use '::'.
1903N/A -d inst_root The file system path at which the server should find its
2046N/A repository data. Required unless PKG_REPO has been set
2240N/A in the environment.
1506N/A -p port The port number on which the instance should listen for
395N/A incoming package requests. The default value is 80 if
395N/A ssl certificate and key information has not been
2026N/A provided; otherwise, the default value is 443.
424N/A -s threads The number of threads that will be started to serve
1024N/A requests. The default value is 10.
395N/A -t timeout The maximum number of seconds the server should wait for
395N/A a response from a client before closing a connection.
395N/A The default value is 60.
2078N/A --cfg The pathname of the file to use when reading and writing
578N/A depot configuration data, or a fully qualified service
1172N/A fault management resource identifier (FMRI) of the SMF
2310N/A service or instance to read configuration data from.
395N/A --content-root The file system path to the directory containing the
2535N/A the static and other web content used by the depot's
2535N/A browser user interface. The default value is
2535N/A '/usr/share/lib/pkg'.
661N/A --disable-ops A comma separated list of operations that the depot
1099N/A should not configure. If, for example, you wanted
1902N/A to omit loading search v1, 'search/1' should be
2310N/A provided as an argument, or to disable all search
2535N/A operations, simply 'search'.
661N/A --debug The name of a debug feature to enable; or a whitespace
395N/A or comma separated list of features to enable.
849N/A Possible values are: headers, hash=sha1+sha256,
290N/A hash=sha256, hash=sha1+sha512_256, hash=sha512_256
395N/A --image-root The path to the image whose file information will be
395N/A used as a cache for file data.
1968N/A --log-access The destination for any access related information
395N/A logged by the depot process. Possible values are:
395N/A stderr, stdout, none, or an absolute pathname. The
395N/A default value is stdout if stdout is a tty; otherwise
395N/A the default value is none.
395N/A --log-errors The destination for any errors or other information
395N/A logged by the depot process. Possible values are:
395N/A stderr, stdout, none, or an absolute pathname. The
395N/A default value is stderr.
395N/A --mirror Package mirror mode; publishing and metadata operations
395N/A disallowed. Cannot be used with --readonly or
395N/A --rebuild.
290N/A --nasty Instruct the server to misbehave. At random intervals
290N/A it will time-out, send bad responses, hang up on
395N/A clients, and generally be hostile. The option
395N/A takes a value (1 to 100) for how nasty the server
1231N/A should be.
1557N/A --nasty-sleep In nasty mode (see --nasty), how many seconds to
1903N/A randomly sleep when a random sleep occurs.
1557N/A --proxy-base The url to use as the base for generating internal
395N/A redirects and content.
395N/A --readonly Read-only operation; modifying operations disallowed.
395N/A Cannot be used with --mirror or --rebuild.
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
395N/A requests on the provided port.
395N/A --ssl-dialog Specifies what method should be used to obtain the
395N/A passphrase needed to decrypt the file specified by
395N/A --ssl-key-file. Supported values are: builtin,
395N/A exec:/path/to/program, smf, or an SMF FMRI. The
290N/A default value is builtin. If smf is specified, an
290N/A SMF FMRI must be provided using the --cfg option.
430N/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
395N/A this option will cause the depot to only respond to SSL
395N/A requests on the provided port.
395N/A --sort-file-max-size
1302N/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.
290N/A --writable-root The path to a directory to which the program has write
395N/A access. Used with --readonly to allow server to
1024N/A create needed files, such as search indices, without
413N/A needing write access to the package information.
1544N/AOptions:
1557N/A --help or -?
1903N/A
2046N/AEnvironment:
2240N/A PKG_REPO Used as default inst_root if -d not provided.
1506N/A PKG_DEPOT_CONTENT Used as default content_root if --content-root
413N/A not provided.""")
2026N/A sys.exit(retcode)
413N/A
1978N/Aclass OptionError(Exception):
1024N/A """Option exception. """
395N/A
395N/A def __init__(self, *args):
2310N/A Exception.__init__(self, *args)
2310N/A
395N/Aif __name__ == "__main__":
395N/A
413N/A setlocale(locale.LC_ALL, "")
395N/A gettext.install("pkg", "/usr/share/locale",
2516N/A codeset=locale.getpreferredencoding())
2516N/A
2516N/A add_content = False
2516N/A exit_ready = False
2516N/A rebuild = False
2516N/A reindex = False
2516N/A nasty = False
2516N/A
2516N/A # Track initial configuration values.
2516N/A ivalues = { "pkg": {}, "nasty": {} }
2516N/A if "PKG_REPO" in os.environ:
2516N/A ivalues["pkg"]["inst_root"] = os.environ["PKG_REPO"]
2516N/A
2516N/A try:
2516N/A content_root = os.environ["PKG_DEPOT_CONTENT"]
2516N/A ivalues["pkg"]["content_root"] = content_root
2516N/A except KeyError:
2516N/A try:
2516N/A content_root = os.path.join(os.environ['PKG_HOME'],
2516N/A 'share/lib/pkg')
2516N/A ivalues["pkg"]["content_root"] = content_root
2516N/A except KeyError:
2516N/A pass
2516N/A
2516N/A opt = None
2516N/A addresses = set()
2516N/A debug_features = []
2516N/A disable_ops = []
2516N/A repo_props = {}
2516N/A socket_path = ""
2516N/A user_cfg = None
2516N/A try:
2516N/A long_opts = ["add-content", "cfg=", "cfg-file=",
2516N/A "content-root=", "debug=", "disable-ops=", "exit-ready",
2516N/A "help", "image-root=", "log-access=", "log-errors=",
2516N/A "llmirror", "mirror", "nasty=", "nasty-sleep=",
2516N/A "proxy-base=", "readonly", "rebuild", "refresh-index",
2516N/A "set-property=", "ssl-cert-file=", "ssl-dialog=",
2516N/A "ssl-key-file=", "sort-file-max-size=", "writable-root="]
2516N/A
2516N/A opts, pargs = getopt.getopt(sys.argv[1:], "a:d:np:s:t:?",
2516N/A long_opts)
2516N/A
2516N/A show_usage = False
2516N/A for opt, arg in opts:
2516N/A if opt == "-a":
2516N/A addresses.add(arg)
395N/A elif opt == "-n":
395N/A sys.exit(0)
395N/A elif opt == "-d":
395N/A ivalues["pkg"]["inst_root"] = arg
395N/A elif opt == "-p":
2339N/A ivalues["pkg"]["port"] = arg
1191N/A elif opt == "-s":
1452N/A threads = int(arg)
1231N/A if threads < THREADS_MIN:
2535N/A raise OptionError, \
2046N/A "minimum value is %d" % THREADS_MIN
395N/A if threads > THREADS_MAX:
395N/A raise OptionError, \
424N/A "maximum value is %d" % THREADS_MAX
395N/A ivalues["pkg"]["threads"] = threads
742N/A elif opt == "-t":
2339N/A ivalues["pkg"]["socket_timeout"] = arg
2339N/A elif opt == "--add-content":
2339N/A add_content = True
2339N/A elif opt == "--cfg":
2339N/A user_cfg = arg
2339N/A elif opt == "--cfg-file":
742N/A ivalues["pkg"]["cfg_file"] = arg
742N/A elif opt == "--content-root":
742N/A ivalues["pkg"]["content_root"] = arg
742N/A elif opt == "--debug":
742N/A if arg is None or arg == "":
742N/A continue
742N/A
742N/A # A list of features can be specified using a
742N/A # "," or any whitespace character as separators.
2688N/A if "," in arg:
2688N/A features = arg.split(",")
2688N/A else:
2688N/A features = arg.split()
2688N/A debug_features.extend(features)
2688N/A
2688N/A # We also allow key=value debug flags, which
2688N/A # get set in pkg.client.debugvalues
2688N/A for feature in features:
2688N/A try:
742N/A key, val = feature.split("=", 1)
2310N/A DebugValues.set_value(key, val)
1902N/A except (AttributeError, ValueError):
1099N/A pass
2390N/A
2335N/A elif opt == "--disable-ops":
2338N/A if arg is None or arg == "":
2338N/A raise OptionError, \
2310N/A "An argument must be specified."
2046N/A
2223N/A disableops = arg.split(",")
2046N/A for s in disableops:
2046N/A if "/" in s:
2523N/A op, ver = s.rsplit("/", 1)
2523N/A else:
2523N/A op = s
2523N/A ver = "*"
2523N/A
2523N/A if op not in \
2310N/A ds.DepotHTTP.REPO_OPS_DEFAULT:
2677N/A raise OptionError(
2310N/A "Invalid operation "
2310N/A "'%s'." % s)
2310N/A disable_ops.append(s)
2310N/A elif opt == "--exit-ready":
2310N/A exit_ready = True
2310N/A elif opt == "--image-root":
2310N/A ivalues["pkg"]["image_root"] = arg
2508N/A elif opt.startswith("--log-"):
2508N/A prop = "log_%s" % opt.lstrip("--log-")
2508N/A ivalues["pkg"][prop] = arg
2508N/A elif opt in ("--help", "-?"):
2508N/A show_usage = True
2535N/A elif opt == "--mirror":
2535N/A ivalues["pkg"]["mirror"] = True
2535N/A elif opt == "--llmirror":
2535N/A ivalues["pkg"]["mirror"] = True
2535N/A ivalues["pkg"]["ll_mirror"] = True
2535N/A ivalues["pkg"]["readonly"] = True
2535N/A elif opt == "--nasty":
2535N/A # ValueError is caught by caller.
2535N/A nasty_value = int(arg)
2535N/A if (nasty_value > 100 or nasty_value < 1):
2535N/A raise OptionError, "Invalid value " \
2535N/A "for nasty option.\n Please " \
2535N/A "choose a value between 1 and 100."
2535N/A nasty = True
2535N/A ivalues["nasty"]["nasty_level"] = nasty_value
2535N/A elif opt == "--nasty-sleep":
2535N/A # ValueError is caught by caller.
2535N/A sleep_value = int(arg)
2535N/A ivalues["nasty"]["nasty_sleep"] = sleep_value
2535N/A elif opt == "--proxy-base":
2535N/A # Attempt to decompose the url provided into
2535N/A # its base parts. This is done so we can
2535N/A # remove any scheme information since we
2535N/A # don't need it.
2535N/A scheme, netloc, path, params, query, \
2535N/A fragment = urlparse.urlparse(arg,
2535N/A "http", allow_fragments=0)
2535N/A
2535N/A if not netloc:
2535N/A raise OptionError, "Unable to " \
2535N/A "determine the hostname from " \
2535N/A "the provided URL; please use a " \
2535N/A "fully qualified URL."
2535N/A
2688N/A scheme = scheme.lower()
2535N/A if scheme not in ("http", "https"):
2535N/A raise OptionError, "Invalid URL; http " \
2688N/A "and https are the only supported " \
2535N/A "schemes."
2535N/A
2535N/A # Rebuild the url with the sanitized components.
2535N/A ivalues["pkg"]["proxy_base"] = \
2535N/A urlparse.urlunparse((scheme, netloc, path,
2535N/A params, query, fragment))
2535N/A elif opt == "--readonly":
2535N/A ivalues["pkg"]["readonly"] = True
2535N/A elif opt == "--rebuild":
2535N/A rebuild = True
2535N/A elif opt == "--refresh-index":
2535N/A # Note: This argument is for internal use
2535N/A # only.
2535N/A #
2535N/A # This flag is purposefully omitted in usage.
2535N/A # The supported way to forcefully reindex is to
2535N/A # kill any pkg.depot using that directory,
2535N/A # remove the index directory, and restart the
2535N/A # pkg.depot process. The index will be rebuilt
2535N/A # automatically on startup.
2535N/A reindex = True
2339N/A exit_ready = True
2339N/A elif opt == "--set-property":
2339N/A try:
691N/A prop, p_value = arg.split("=", 1)
691N/A p_sec, p_name = prop.split(".", 1)
691N/A except ValueError:
395N/A usage(_("property arguments must be of "
395N/A "the form '<section.property>="
395N/A "<value>'."))
395N/A repo_props.setdefault(p_sec, {})
395N/A repo_props[p_sec][p_name] = p_value
290N/A elif opt == "--ssl-cert-file":
395N/A if arg == "none" or arg == "":
395N/A # Assume this is an override to clear
591N/A # the value.
591N/A arg = ""
591N/A elif not os.path.isabs(arg):
2639N/A raise OptionError, "The path to " \
2639N/A "the Certificate file must be " \
2639N/A "absolute."
2639N/A elif not os.path.exists(arg):
2639N/A raise OptionError, "The specified " \
2639N/A "file does not exist."
1505N/A elif not os.path.isfile(arg):
2516N/A raise OptionError, "The specified " \
1505N/A "pathname is not a file."
1505N/A ivalues["pkg"]["ssl_cert_file"] = arg
1632N/A elif opt == "--ssl-key-file":
1632N/A if arg == "none" or arg == "":
1632N/A # Assume this is an override to clear
1632N/A # the value.
2339N/A arg = ""
2339N/A elif not os.path.isabs(arg):
2339N/A raise OptionError, "The path to " \
2339N/A "the Private Key file must be " \
2339N/A "absolute."
2339N/A elif not os.path.exists(arg):
2339N/A raise OptionError, "The specified " \
2339N/A "file does not exist."
2339N/A elif not os.path.isfile(arg):
2339N/A raise OptionError, "The specified " \
2339N/A "pathname is not a file."
2339N/A ivalues["pkg"]["ssl_key_file"] = arg
2339N/A elif opt == "--ssl-dialog":
2339N/A if arg != "builtin" and \
2339N/A arg != "smf" and not \
2339N/A arg.startswith("exec:/") and not \
2364N/A arg.startswith("svc:"):
2339N/A raise OptionError, "Invalid value " \
2339N/A "specified. Expected: builtin, " \
2339N/A "exec:/path/to/program, smf, or " \
2339N/A "an SMF FMRI."
2339N/A
2339N/A if arg.startswith("exec:"):
2339N/A if os_util.get_canonical_os_type() != \
2339N/A "unix":
2339N/A # Don't allow a somewhat
2339N/A # insecure authentication method
2339N/A # on some platforms.
2339N/A raise OptionError, "exec is " \
2339N/A "not a supported dialog " \
2339N/A "type for this operating " \
2339N/A "system."
2339N/A
2339N/A f = os.path.abspath(arg.split(
2339N/A "exec:")[1])
2339N/A if not os.path.isfile(f):
2339N/A raise OptionError, "Invalid " \
2364N/A "file path specified for " \
2364N/A "exec."
2364N/A ivalues["pkg"]["ssl_dialog"] = arg
2364N/A elif opt == "--sort-file-max-size":
2364N/A ivalues["pkg"]["sort_file_max_size"] = arg
2364N/A elif opt == "--writable-root":
2364N/A ivalues["pkg"]["writable_root"] = arg
2364N/A
2364N/A # Set accumulated values.
2364N/A if debug_features:
2364N/A ivalues["pkg"]["debug"] = debug_features
2364N/A if disable_ops:
2364N/A ivalues["pkg"]["disable_ops"] = disable_ops
2339N/A if addresses:
395N/A ivalues["pkg"]["address"] = list(addresses)
395N/A
290N/A if DebugValues:
290N/A reload(pkg.digest)
2339N/A
2339N/A # Build configuration object.
290N/A dconf = ds.DepotConfig(target=user_cfg, overrides=ivalues)
290N/A except getopt.GetoptError, _e:
290N/A usage("pkg.depotd: %s" % _e.msg)
290N/A except api_errors.ApiException, _e:
290N/A usage("pkg.depotd: %s" % str(_e))
290N/A except OptionError, _e:
290N/A usage("pkg.depotd: option: %s -- %s" % (opt, _e))
290N/A except (ArithmeticError, ValueError):
290N/A usage("pkg.depotd: illegal option value: %s specified " \
290N/A "for option: %s" % (arg, opt))
395N/A
395N/A if show_usage:
290N/A usage(retcode=0, full=True)
290N/A
2674N/A if not dconf.get_property("pkg", "log_errors"):
2674N/A dconf.set_property("pkg", "log_errors", "stderr")
2674N/A
2674N/A # If stdout is a tty, then send access output there by default instead
290N/A # of discarding it.
2674N/A if not dconf.get_property("pkg", "log_access"):
2674N/A if os.isatty(sys.stdout.fileno()):
395N/A dconf.set_property("pkg", "log_access", "stdout")
395N/A else:
395N/A dconf.set_property("pkg", "log_access", "none")
2674N/A
395N/A # Check for invalid option combinations.
395N/A image_root = dconf.get_property("pkg", "image_root")
395N/A inst_root = dconf.get_property("pkg", "inst_root")
395N/A mirror = dconf.get_property("pkg", "mirror")
2674N/A ll_mirror = dconf.get_property("pkg", "ll_mirror")
591N/A readonly = dconf.get_property("pkg", "readonly")
591N/A writable_root = dconf.get_property("pkg", "writable_root")
591N/A if rebuild and add_content:
2674N/A usage("--add-content cannot be used with --rebuild")
2639N/A if rebuild and reindex:
2639N/A usage("--refresh-index cannot be used with --rebuild")
2639N/A if (rebuild or add_content) and (readonly or mirror):
2674N/A usage("--readonly and --mirror cannot be used with --rebuild "
2639N/A "or --add-content")
2639N/A if reindex and mirror:
2639N/A usage("--mirror cannot be used with --refresh-index")
2674N/A if reindex and readonly and not writable_root:
2674N/A usage("--readonly can only be used with --refresh-index if "
691N/A "--writable-root is used")
691N/A if image_root and not ll_mirror:
691N/A usage("--image-root can only be used with --llmirror.")
2674N/A if image_root and writable_root:
2674N/A usage("--image_root and --writable-root cannot be used "
2339N/A "together.")
2339N/A if image_root and inst_root:
2339N/A usage("--image-root and -d cannot be used together.")
290N/A
290N/A # If the image format changes this may need to be reexamined.
290N/A if image_root:
290N/A inst_root = os.path.join(image_root, "var", "pkg")
290N/A
591N/A # Set any values using defaults if they weren't provided.
591N/A
2639N/A # Only use the first value for now; multiple bind addresses may be
2639N/A # supported later.
2639N/A address = dconf.get_property("pkg", "address")
2639N/A if address:
691N/A address = address[0]
691N/A elif not address:
2339N/A dconf.set_property("pkg", "address", [HOST_DEFAULT])
2339N/A address = dconf.get_property("pkg", "address")[0]
290N/A
290N/A if not inst_root:
2339N/A usage("Either PKG_REPO or -d must be provided")
2339N/A
2339N/A content_root = dconf.get_property("pkg", "content_root")
2339N/A if not content_root:
2339N/A dconf.set_property("pkg", "content_root", CONTENT_PATH_DEFAULT)
2339N/A content_root = dconf.get_property("pkg", "content_root")
2339N/A
290N/A port = dconf.get_property("pkg", "port")
2339N/A ssl_cert_file = dconf.get_property("pkg", "ssl_cert_file")
2339N/A ssl_key_file = dconf.get_property("pkg", "ssl_key_file")
290N/A if (ssl_cert_file and not ssl_key_file) or (ssl_key_file and not
2339N/A ssl_cert_file):
2339N/A usage("The --ssl-cert-file and --ssl-key-file options must "
2339N/A "must both be provided when using either option.")
2339N/A elif not port:
2339N/A if ssl_cert_file and ssl_key_file:
2339N/A dconf.set_property("pkg", "port", SSL_PORT_DEFAULT)
2339N/A else:
2339N/A dconf.set_property("pkg", "port", PORT_DEFAULT)
290N/A port = dconf.get_property("pkg", "port")
395N/A
290N/A socket_timeout = dconf.get_property("pkg", "socket_timeout")
395N/A if not socket_timeout:
506N/A dconf.set_property("pkg", "socket_timeout",
506N/A SOCKET_TIMEOUT_DEFAULT)
506N/A socket_timeout = dconf.get_property("pkg", "socket_timeout")
506N/A
506N/A threads = dconf.get_property("pkg", "threads")
506N/A if not threads:
506N/A dconf.set_property("pkg", "threads", THREADS_DEFAULT)
506N/A threads = dconf.get_property("pkg", "threads")
834N/A
506N/A # If the program is going to reindex, the port is irrelevant since
506N/A # the program will not bind to a port.
506N/A if not exit_ready:
513N/A try:
506N/A cherrypy.process.servers.check_port(address, port)
506N/A except Exception, e:
506N/A emsg("pkg.depotd: unable to bind to the specified "
506N/A "port: %d. Reason: %s" % (port, e))
290N/A sys.exit(1)
290N/A else:
2535N/A # Not applicable if we're not going to serve content
2535N/A dconf.set_property("pkg", "content_root", "")
2535N/A
395N/A # Any relative paths should be made absolute using pkg_root. 'pkg_root'
413N/A # is a special property that was added to enable internal deployment of
395N/A # multiple disparate versions of the pkg.depotd software.
290N/A pkg_root = dconf.get_property("pkg", "pkg_root")
1674N/A
1674N/A repo_config_file = dconf.get_property("pkg", "cfg_file")
1674N/A if repo_config_file and not os.path.isabs(repo_config_file):
1674N/A repo_config_file = os.path.join(pkg_root, repo_config_file)
1674N/A
1674N/A if content_root and not os.path.isabs(content_root):
1674N/A content_root = os.path.join(pkg_root, content_root)
1674N/A
1674N/A if inst_root and not os.path.isabs(inst_root):
1674N/A inst_root = os.path.join(pkg_root, inst_root)
1674N/A
1674N/A if ssl_cert_file:
1674N/A if ssl_cert_file == "none":
1674N/A ssl_cert_file = None
1674N/A elif not os.path.isabs(ssl_cert_file):
395N/A ssl_cert_file = os.path.join(pkg_root, ssl_cert_file)
395N/A
506N/A if ssl_key_file:
506N/A if ssl_key_file == "none":
395N/A ssl_key_file = None
2535N/A elif not os.path.isabs(ssl_key_file):
2535N/A ssl_key_file = os.path.join(pkg_root, ssl_key_file)
395N/A
430N/A if writable_root and not os.path.isabs(writable_root):
849N/A writable_root = os.path.join(pkg_root, writable_root)
834N/A
290N/A # Setup SSL if requested.
2561N/A key_data = None
2561N/A ssl_dialog = dconf.get_property("pkg", "ssl_dialog")
2561N/A if not exit_ready and ssl_cert_file and ssl_key_file and \
2561N/A ssl_dialog != "builtin":
2561N/A cmdline = None
2561N/A def get_ssl_passphrase(*ignored):
2561N/A p = None
2561N/A try:
2561N/A p = subprocess.Popen(cmdline, shell=True,
2561N/A stdout=subprocess.PIPE,
2561N/A stderr=None)
2561N/A p.wait()
2561N/A except Exception, __e:
2561N/A emsg("pkg.depotd: an error occurred while "
2561N/A "executing [%s]; unable to obtain the "
2561N/A "passphrase needed to decrypt the SSL "
2561N/A "private key file: %s" % (cmdline, __e))
2561N/A sys.exit(1)
2561N/A return p.stdout.read().strip("\n")
2561N/A
2535N/A if ssl_dialog.startswith("exec:"):
2535N/A exec_path = ssl_dialog.split("exec:")[1]
2535N/A if not os.path.isabs(exec_path):
2535N/A exec_path = os.path.join(pkg_root, exec_path)
2535N/A cmdline = "%s %s %d" % (exec_path, "''", port)
1099N/A elif ssl_dialog == "smf" or ssl_dialog.startswith("svc:"):
2535N/A if ssl_dialog == "smf":
2535N/A # Assume the configuration target was an SMF
2535N/A # FMRI and let svcprop fail with an error if
2535N/A # it wasn't.
2535N/A svc_fmri = dconf.target
2535N/A else:
2535N/A svc_fmri = ssl_dialog
2535N/A cmdline = "/usr/bin/svcprop -p " \
2535N/A "pkg_secure/ssl_key_passphrase %s" % svc_fmri
2535N/A
1099N/A # The key file requires decryption, but the user has requested
2535N/A # exec-based authentication, so it will have to be decoded first
2535N/A # to an un-named temporary file.
2535N/A try:
2535N/A with file(ssl_key_file, "rb") as key_file:
2535N/A pkey = crypto.load_privatekey(
2535N/A crypto.FILETYPE_PEM, key_file.read(),
2535N/A get_ssl_passphrase)
2535N/A
2535N/A key_data = tempfile.TemporaryFile()
2535N/A key_data.write(crypto.dump_privatekey(
2535N/A crypto.FILETYPE_PEM, pkey))
2535N/A key_data.seek(0)
2535N/A except EnvironmentError, _e:
2535N/A emsg("pkg.depotd: unable to read the SSL private key "
2535N/A "file: %s" % _e)
2535N/A sys.exit(1)
2535N/A except crypto.Error, _e:
1099N/A emsg("pkg.depotd: authentication or cryptography "
2535N/A "failure while attempting to decode\nthe SSL "
2535N/A "private key file: %s" % _e)
2535N/A sys.exit(1)
2535N/A else:
395N/A # Redirect the server to the decrypted key file.
2535N/A ssl_key_file = "/dev/fd/%d" % key_data.fileno()
2535N/A
2535N/A # Setup our global configuration.
2535N/A gconf = {
2535N/A "checker.on": True,
2535N/A "environment": "production",
1191N/A "log.screen": False,
2535N/A "server.max_request_body_size": MAX_REQUEST_BODY_SIZE,
2535N/A "server.shutdown_timeout": 0,
2535N/A "server.socket_host": address,
2535N/A "server.socket_port": port,
1191N/A "server.socket_timeout": socket_timeout,
2688N/A "server.ssl_certificate": ssl_cert_file,
2688N/A "server.ssl_private_key": ssl_key_file,
2688N/A "server.thread_pool": threads,
2688N/A "tools.log_headers.on": True,
2688N/A "tools.encode.on": True
2688N/A }
2688N/A
1660N/A if "headers" in dconf.get_property("pkg", "debug"):
2688N/A # Despite its name, this only logs headers when there is an
2688N/A # error; it's redundant with the debug feature enabled.
2688N/A gconf["tools.log_headers.on"] = False
2688N/A
2688N/A # Causes the headers of every request to be logged to the error
2688N/A # log; even if an exception occurs.
2688N/A gconf["tools.log_headers_always.on"] = True
2688N/A cherrypy.tools.log_headers_always = cherrypy.Tool(
849N/A "on_start_resource",
2688N/A cherrypy.lib.cptools.log_request_headers)
2688N/A
1208N/A log_cfg = {
1208N/A "access": dconf.get_property("pkg", "log_access"),
1208N/A "errors": dconf.get_property("pkg", "log_errors")
1208N/A }
849N/A
2688N/A # If stdin is not a tty and the pkgdepot controller isn't being used,
2688N/A # then assume process will be daemonized and redirect output.
290N/A if not os.environ.get("PKGDEPOT_CONTROLLER") and \
2535N/A not os.isatty(sys.stdin.fileno()):
2535N/A # Ensure log handlers are setup to use the file descriptors for
2597N/A # stdout and stderr as the Daemonizer (used for test suite and
2597N/A # SMF service) requires this.
2597N/A if log_cfg["access"] == "stdout":
2535N/A log_cfg["access"] = "/dev/fd/%d" % sys.stdout.fileno()
2535N/A elif log_cfg["access"] == "stderr":
2535N/A log_cfg["access"] = "/dev/fd/%d" % sys.stderr.fileno()
2535N/A elif log_cfg["access"] == "none":
2535N/A log_cfg["access"] = "/dev/null"
2535N/A
2535N/A if log_cfg["errors"] == "stderr":
2535N/A log_cfg["errors"] = "/dev/fd/%d" % sys.stderr.fileno()
2535N/A elif log_cfg["errors"] == "stdout":
2535N/A log_cfg["errors"] = "/dev/fd/%d" % sys.stdout.fileno()
2535N/A elif log_cfg["errors"] == "none":
2535N/A log_cfg["errors"] = "/dev/null"
2535N/A
2535N/A log_type_map = {
2535N/A "errors": {
2535N/A "param": "log.error_file",
2535N/A "attr": "error_log"
2535N/A },
2535N/A "access": {
2597N/A "param": "log.access_file",
2597N/A "attr": "access_log"
2597N/A }
2597N/A }
2597N/A
2597N/A for log_type in log_type_map:
2597N/A dest = log_cfg[log_type]
2597N/A if dest in ("stdout", "stderr", "none"):
2597N/A if dest == "none":
2597N/A h = logging.StreamHandler(LogSink())
2597N/A else:
2597N/A h = logging.StreamHandler(eval("sys.%s" % \
2597N/A dest))
2597N/A
2597N/A h.setLevel(logging.DEBUG)
2597N/A h.setFormatter(cherrypy._cplogging.logfmt)
2535N/A log_obj = eval("cherrypy.log.%s" % \
2535N/A log_type_map[log_type]["attr"])
2535N/A log_obj.addHandler(h)
2535N/A # Since we've replaced cherrypy's log handler with our
2535N/A # own, we don't want the output directed to a file.
2688N/A dest = ""
2688N/A elif dest:
2688N/A if not os.path.isabs(dest):
2688N/A dest = os.path.join(pkg_root, dest)
2688N/A gconf[log_type_map[log_type]["param"]] = dest
2688N/A
2688N/A cherrypy.config.update(gconf)
2688N/A
2688N/A # Now that our logging, etc. has been setup, it's safe to perform any
2688N/A # remaining preparation.
2688N/A
2688N/A # Initialize repository state.
2688N/A if not readonly:
2688N/A # Not readonly, so assume a new repository should be created.
2688N/A try:
2688N/A sr.repository_create(inst_root, properties=repo_props)
2688N/A except sr.RepositoryExistsError:
2688N/A # Already exists, nothing to do.
2688N/A pass
2688N/A except (api_errors.ApiException, sr.RepositoryError), _e:
2688N/A emsg("pkg.depotd: %s" % _e)
2688N/A sys.exit(1)
2688N/A
2688N/A try:
2688N/A sort_file_max_size = dconf.get_property("pkg",
2688N/A "sort_file_max_size")
2688N/A
2688N/A repo = sr.Repository(cfgpathname=repo_config_file,
2688N/A log_obj=cherrypy, mirror=mirror, properties=repo_props,
2688N/A read_only=readonly, root=inst_root,
2688N/A sort_file_max_size=sort_file_max_size,
2688N/A writable_root=writable_root)
2688N/A except (RuntimeError, sr.RepositoryError), _e:
2688N/A emsg("pkg.depotd: %s" % _e)
2688N/A sys.exit(1)
2688N/A except search_errors.IndexingException, _e:
2688N/A emsg("pkg.depotd: %s" % str(_e), "INDEX")
2688N/A sys.exit(1)
2688N/A except api_errors.ApiException, _e:
2688N/A emsg("pkg.depotd: %s" % str(_e))
2688N/A sys.exit(1)
2688N/A
2688N/A if not rebuild and not add_content and not repo.mirror and \
2688N/A not (repo.read_only and not repo.writable_root):
2688N/A # Automatically update search indexes on startup if not already
2688N/A # told to, and not in readonly/mirror mode.
2688N/A reindex = True
2688N/A
2688N/A if reindex:
2688N/A try:
2688N/A # Only execute a index refresh here if --exit-ready was
2688N/A # requested; it will be handled later in the setup
2688N/A # process for other cases.
2688N/A if repo.root and exit_ready:
2688N/A repo.refresh_index()
2688N/A except (sr.RepositoryError, search_errors.IndexingException,
2688N/A api_errors.ApiException), e:
2688N/A emsg(str(e), "INDEX")
2688N/A sys.exit(1)
2688N/A elif rebuild:
2688N/A try:
2688N/A repo.rebuild(build_index=True)
2688N/A except sr.RepositoryError, e:
2688N/A emsg(str(e), "REBUILD")
2535N/A sys.exit(1)
2535N/A except (search_errors.IndexingException,
2535N/A api_errors.UnknownErrors,
2535N/A api_errors.PermissionsException), e:
2535N/A emsg(str(e), "INDEX")
2535N/A sys.exit(1)
2535N/A elif add_content:
2535N/A try:
2535N/A repo.add_content()
2688N/A repo.refresh_index()
2535N/A except sr.RepositoryError, e:
2535N/A emsg(str(e), "ADD_CONTENT")
2535N/A sys.exit(1)
2535N/A except (search_errors.IndexingException,
2535N/A api_errors.UnknownErrors,
2535N/A api_errors.PermissionsException), e:
2535N/A emsg(str(e), "INDEX")
2535N/A sys.exit(1)
2535N/A
2688N/A # Ready to start depot; exit now if requested.
2688N/A if exit_ready:
2688N/A sys.exit(0)
2688N/A
2688N/A # Next, initialize depot.
2688N/A if nasty:
2688N/A depot = ds.NastyDepotHTTP(repo, dconf)
2688N/A else:
2688N/A depot = ds.DepotHTTP(repo, dconf)
2688N/A
2688N/A # Now build our site configuration.
2688N/A conf = {
2688N/A "/": {
2688N/A # We have to override cherrypy's default response_class so that
2688N/A # we have access to the write() callable to stream data
2688N/A # directly to the client.
2688N/A "wsgi.response_class": dr.DepotResponse,
2688N/A },
2688N/A "/robots.txt": {
2688N/A "tools.staticfile.on": True,
2688N/A "tools.staticfile.filename": os.path.join(depot.web_root,
2688N/A "robots.txt")
2688N/A },
2688N/A }
2688N/A
2688N/A proxy_base = dconf.get_property("pkg", "proxy_base")
2688N/A if proxy_base:
2688N/A # This changes the base URL for our server, and is primarily
2688N/A # intended to allow our depot process to operate behind Apache
2688N/A # or some other webserver process.
2688N/A #
2688N/A # Visit the following URL for more information:
2688N/A # http://cherrypy.org/wiki/BuiltinTools#tools.proxy
2688N/A proxy_conf = {
2688N/A "tools.proxy.on": True,
2688N/A "tools.proxy.local": "",
2688N/A "tools.proxy.base": proxy_base
2688N/A }
2688N/A
2688N/A # Now merge or add our proxy configuration information into the
2688N/A # existing configuration.
2688N/A for entry in proxy_conf:
2688N/A conf["/"][entry] = proxy_conf[entry]
2688N/A
2535N/A if ll_mirror:
2535N/A ds.DNSSD_Plugin(cherrypy.engine, gconf).subscribe()
2535N/A
2535N/A if reindex:
2535N/A # Tell depot to update search indexes when possible;
2535N/A # this is done as a background task so that packages
2535N/A # can be served immediately while search indexes are
2535N/A # still being updated.
2535N/A depot._queue_refresh_index()
2535N/A
2535N/A # If stdin is not a tty and the pkgdepot controller isn't being used,
2535N/A # then assume process should be daemonized.
2535N/A if not os.environ.get("PKGDEPOT_CONTROLLER") and \
2535N/A not os.isatty(sys.stdin.fileno()):
2535N/A # Translate the values in log_cfg into paths.
2535N/A Daemonizer(cherrypy.engine, stderr=log_cfg["errors"],
2535N/A stdout=log_cfg["access"]).subscribe()
2535N/A
2535N/A try:
2535N/A root = cherrypy.Application(depot)
2535N/A cherrypy.quickstart(root, config=conf)
2535N/A except Exception, _e:
2535N/A emsg("pkg.depotd: unknown error starting depot server, " \
2535N/A "illegal option value specified?")
2535N/A emsg(_e)
2535N/A sys.exit(1)
2535N/A