#!/usr/bin/python2.7
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
# Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
#
from __future__ import print_function
# pkg.depotd - package repository daemon
# XXX The prototype pkg.depotd combines both the version management server that
# answers to pkgsend(1) sessions and the HTTP file server that answers to the
# various GET operations that a pkg(1) client makes. This split is expected to
# be made more explicit, by constraining the pkg(1) operations such that they
# can be served as a typical HTTP/HTTPS session. Thus, pkg.depotd will reduce
# to a special purpose HTTP/HTTPS server explicitly for the version management
# operations, and must manipulate the various state files--catalogs, in
# particular--such that the pkg(1) pull client can operately accurately with
# only a basic HTTP/HTTPS server in place.
# XXX Although we pushed the evaluation of next-version, etc. to the pull
# client, we should probably provide a query API to do same on the server, for
# dumb clients (like a notification service).
# The default path for static and other web content.
CONTENT_PATH_DEFAULT = "/usr/share/lib/pkg"
# cherrypy has a max_request_body_size parameter that determines whether the
# server should abort requests with REQUEST_ENTITY_TOO_LARGE when the request
# body is larger than the specified size (in bytes). The maximum size supported
# by cherrypy is 2048 * 1024 * 1024 - 1 (just short of 2048MB), but the default
# here is purposefully conservative.
MAX_REQUEST_BODY_SIZE = 512 * 1024 * 1024
# The default host/port(s) to serve data from.
HOST_DEFAULT = "0.0.0.0"
PORT_DEFAULT = 80
SSL_PORT_DEFAULT = 443
# The minimum number of threads allowed.
THREADS_MIN = 1
# The default number of threads to start.
THREADS_DEFAULT = 60
# The maximum number of threads that can be started.
THREADS_MAX = 5000
# The default server socket timeout in seconds. We want this to be longer than
# the normal default of 10 seconds to accommodate clients with poor quality
# connections.
SOCKET_TIMEOUT_DEFAULT = 60
import getopt
import gettext
import locale
import logging
import os
import os.path
import OpenSSL.crypto as crypto
import shlex
import six
import string
import subprocess
import sys
import tempfile
if sys.version_info[:2] >= (3, 4):
from importlib import reload
else:
from imp import reload
from six.moves.urllib.parse import urlparse, urlunparse
try:
import cherrypy
version = cherrypy.__version__.split('.')
# comparison requires same type, therefore list conversion is needed
if list(map(int, version)) < [3, 1, 0]:
raise ImportError
except ImportError:
print("""cherrypy 3.1.0 or greater is required to use this program.""",
file=sys.stderr)
sys.exit(2)
import cherrypy.process.servers
from cherrypy.process.plugins import Daemonizer
from cherrypy._cpdispatch import Dispatcher
from pkg.misc import msg, emsg, setlocale
from pkg.client.debugvalues import DebugValues
import pkg
import pkg.client.api_errors as api_errors
import pkg.config as cfg
import pkg.portable.util as os_util
import pkg.search_errors as search_errors
import pkg.server.depot as ds
import pkg.server.repository as sr
# Starting in CherryPy 3.2, its default dispatcher converts all punctuation to
# underscore. Since publisher name can contain the hyphen symbol "-", in order
# to let the dispatcher to find the correct page handler, we need to skip
# converting the hyphen symbol.
punc = string.punctuation.replace("-", "_")
if six.PY2:
translate = string.maketrans(punc, "_" * len(string.punctuation))
else:
translate = str.maketrans(punc, "_" * len(string.punctuation))
class Pkg5Dispatcher(Dispatcher):
def __init__(self, **args):
Dispatcher.__init__(self, translate=translate)
class LogSink(object):
"""This is a dummy object that we can use to discard log entries
without relying on non-portable interfaces such as /dev/null."""
def write(self, *args, **kwargs):
"""Discard the bits."""
pass
def flush(self, *args, **kwargs):
"""Discard the bits."""
pass
def usage(text=None, retcode=2, full=False):
"""Optionally emit a usage message and then exit using the specified
exit code."""
if text:
emsg(text)
if not full:
# The full usage message isn't desired.
emsg(_("Try `pkg.depotd --help or -?' for more "
"information."))
sys.exit(retcode)
print("""\
Usage: /usr/lib/pkg.depotd [-a address] [-d inst_root] [-p port] [-s threads]
[-t socket_timeout] [--cfg] [--content-root]
[--disable-ops op[/1][,...]] [--debug feature_list]
[--image-root dir] [--log-access dest] [--log-errors dest]
[--mirror] [--nasty] [--nasty-sleep] [--proxy-base url]
[--readonly] [--ssl-cert-file] [--ssl-dialog] [--ssl-key-file]
[--sort-file-max-size size] [--writable-root dir]
-a address The IP address on which to listen for connections. The
default value is 0.0.0.0 (INADDR_ANY) which will listen
on all active interfaces. To listen on all active IPv6
interfaces, use '::'.
-d inst_root The file system path at which the server should find its
repository data. Required unless PKG_REPO has been set
in the environment.
-p port The port number on which the instance should listen for
incoming package requests. The default value is 80 if
ssl certificate and key information has not been
provided; otherwise, the default value is 443.
-s threads The number of threads that will be started to serve
requests. The default value is 10.
-t timeout The maximum number of seconds the server should wait for
a response from a client before closing a connection.
The default value is 60.
--cfg The pathname of the file to use when reading and writing
depot configuration data, or a fully qualified service
fault management resource identifier (FMRI) of the SMF
service or instance to read configuration data from.
--content-root The file system path to the directory containing the
the static and other web content used by the depot's
browser user interface. The default value is
'/usr/share/lib/pkg'.
--disable-ops A comma separated list of operations that the depot
should not configure. If, for example, you wanted
to omit loading search v1, 'search/1' should be
provided as an argument, or to disable all search
operations, simply 'search'.
--debug The name of a debug feature to enable; or a whitespace
or comma separated list of features to enable.
Possible values are: headers, hash=sha1+sha256,
hash=sha256, hash=sha1+sha512t_256, hash=sha512t_256
--image-root The path to the image whose file information will be
used as a cache for file data.
--log-access The destination for any access related information
logged by the depot process. Possible values are:
stderr, stdout, none, or an absolute pathname. The
default value is stdout if stdout is a tty; otherwise
the default value is none.
--log-errors The destination for any errors or other information
logged by the depot process. Possible values are:
stderr, stdout, none, or an absolute pathname. The
default value is stderr.
--mirror Package mirror mode; publishing and metadata operations
disallowed. Cannot be used with --readonly or
--rebuild.
--nasty Instruct the server to misbehave. At random intervals
it will time-out, send bad responses, hang up on
clients, and generally be hostile. The option
takes a value (1 to 100) for how nasty the server
should be.
--nasty-sleep In nasty mode (see --nasty), how many seconds to
randomly sleep when a random sleep occurs.
--proxy-base The url to use as the base for generating internal
redirects and content.
--readonly Read-only operation; modifying operations disallowed.
Cannot be used with --mirror or --rebuild.
--ssl-cert-file The absolute pathname to a PEM-encoded Certificate file.
This option must be used with --ssl-key-file. Usage of
this option will cause the depot to only respond to SSL
requests on the provided port.
--ssl-dialog Specifies what method should be used to obtain the
passphrase needed to decrypt the file specified by
--ssl-key-file. Supported values are: builtin,
exec:/path/to/program, smf, or an SMF FMRI. The
default value is builtin. If smf is specified, an
SMF FMRI must be provided using the --cfg option.
--ssl-key-file The absolute pathname to a PEM-encoded Private Key file.
This option must be used with --ssl-cert-file. Usage of
this option will cause the depot to only respond to SSL
requests on the provided port.
--sort-file-max-size
The maximum size of the indexer sort file. Used to
limit the amount of RAM the depot uses for indexing,
or increase it for speed.
--writable-root The path to a directory to which the program has write
access. Used with --readonly to allow server to
create needed files, such as search indices, without
needing write access to the package information.
Options:
--help or -?
Environment:
PKG_REPO Used as default inst_root if -d not provided.
PKG_DEPOT_CONTENT Used as default content_root if --content-root
not provided.""")
sys.exit(retcode)
class OptionError(Exception):
"""Option exception. """
def __init__(self, *args):
Exception.__init__(self, *args)
if __name__ == "__main__":
setlocale(locale.LC_ALL, "")
gettext.install("pkg", "/usr/share/locale",
codeset=locale.getpreferredencoding())
add_content = False
exit_ready = False
rebuild = False
reindex = False
nasty = False
# Track initial configuration values.
ivalues = { "pkg": {}, "nasty": {} }
if "PKG_REPO" in os.environ:
ivalues["pkg"]["inst_root"] = os.environ["PKG_REPO"]
try:
content_root = os.environ["PKG_DEPOT_CONTENT"]
ivalues["pkg"]["content_root"] = content_root
except KeyError:
try:
content_root = os.path.join(os.environ['PKG_HOME'],
'share/lib/pkg')
ivalues["pkg"]["content_root"] = content_root
except KeyError:
pass
opt = None
addresses = set()
debug_features = []
disable_ops = []
repo_props = {}
socket_path = ""
user_cfg = None
try:
long_opts = ["add-content", "cfg=", "cfg-file=",
"content-root=", "debug=", "disable-ops=", "exit-ready",
"help", "image-root=", "log-access=", "log-errors=",
"llmirror", "mirror", "nasty=", "nasty-sleep=",
"proxy-base=", "readonly", "rebuild", "refresh-index",
"set-property=", "ssl-cert-file=", "ssl-dialog=",
"ssl-key-file=", "sort-file-max-size=", "writable-root="]
opts, pargs = getopt.getopt(sys.argv[1:], "a:d:np:s:t:?",
long_opts)
show_usage = False
for opt, arg in opts:
if opt == "-a":
addresses.add(arg)
elif opt == "-n":
sys.exit(0)
elif opt == "-d":
ivalues["pkg"]["inst_root"] = arg
elif opt == "-p":
ivalues["pkg"]["port"] = arg
elif opt == "-s":
threads = int(arg)
if threads < THREADS_MIN:
raise OptionError(
"minimum value is {0:d}".format(
THREADS_MIN))
if threads > THREADS_MAX:
raise OptionError(
"maximum value is {0:d}".format(
THREADS_MAX))
ivalues["pkg"]["threads"] = threads
elif opt == "-t":
ivalues["pkg"]["socket_timeout"] = arg
elif opt == "--add-content":
add_content = True
elif opt == "--cfg":
user_cfg = arg
elif opt == "--cfg-file":
ivalues["pkg"]["cfg_file"] = arg
elif opt == "--content-root":
ivalues["pkg"]["content_root"] = arg
elif opt == "--debug":
if arg is None or arg == "":
continue
# A list of features can be specified using a
# "," or any whitespace character as separators.
if "," in arg:
features = arg.split(",")
else:
features = arg.split()
debug_features.extend(features)
# We also allow key=value debug flags, which
# get set in pkg.client.debugvalues
for feature in features:
try:
key, val = feature.split("=", 1)
DebugValues.set_value(key, val)
except (AttributeError, ValueError):
pass
elif opt == "--disable-ops":
if arg is None or arg == "":
raise OptionError(
"An argument must be specified.")
disableops = arg.split(",")
for s in disableops:
if "/" in s:
op, ver = s.rsplit("/", 1)
else:
op = s
ver = "*"
if op not in \
ds.DepotHTTP.REPO_OPS_DEFAULT:
raise OptionError(
"Invalid operation "
"'{0}'.".format(s))
disable_ops.append(s)
elif opt == "--exit-ready":
exit_ready = True
elif opt == "--image-root":
ivalues["pkg"]["image_root"] = arg
elif opt.startswith("--log-"):
prop = "log_{0}".format(opt.lstrip("--log-"))
ivalues["pkg"][prop] = arg
elif opt in ("--help", "-?"):
show_usage = True
elif opt == "--mirror":
ivalues["pkg"]["mirror"] = True
elif opt == "--llmirror":
ivalues["pkg"]["mirror"] = True
ivalues["pkg"]["ll_mirror"] = True
ivalues["pkg"]["readonly"] = True
elif opt == "--nasty":
# ValueError is caught by caller.
nasty_value = int(arg)
if (nasty_value > 100 or nasty_value < 1):
raise OptionError("Invalid value "
"for nasty option.\n Please "
"choose a value between 1 and 100.")
nasty = True
ivalues["nasty"]["nasty_level"] = nasty_value
elif opt == "--nasty-sleep":
# ValueError is caught by caller.
sleep_value = int(arg)
ivalues["nasty"]["nasty_sleep"] = sleep_value
elif opt == "--proxy-base":
# Attempt to decompose the url provided into
# its base parts. This is done so we can
# remove any scheme information since we
# don't need it.
scheme, netloc, path, params, query, \
fragment = urlparse(arg,
"http", allow_fragments=0)
if not netloc:
raise OptionError("Unable to "
"determine the hostname from "
"the provided URL; please use a "
"fully qualified URL.")
scheme = scheme.lower()
if scheme not in ("http", "https"):
raise OptionError("Invalid URL; http "
"and https are the only supported "
"schemes.")
# Rebuild the url with the sanitized components.
ivalues["pkg"]["proxy_base"] = \
urlunparse((scheme, netloc, path,
params, query, fragment))
elif opt == "--readonly":
ivalues["pkg"]["readonly"] = True
elif opt == "--rebuild":
rebuild = True
elif opt == "--refresh-index":
# Note: This argument is for internal use
# only.
#
# This flag is purposefully omitted in usage.
# The supported way to forcefully reindex is to
# kill any pkg.depot using that directory,
# remove the index directory, and restart the
# pkg.depot process. The index will be rebuilt
# automatically on startup.
reindex = True
exit_ready = True
elif opt == "--set-property":
try:
prop, p_value = arg.split("=", 1)
p_sec, p_name = prop.split(".", 1)
except ValueError:
usage(_("property arguments must be of "
"the form '<section.property>="
"<value>'."))
repo_props.setdefault(p_sec, {})
repo_props[p_sec][p_name] = p_value
elif opt == "--ssl-cert-file":
if arg == "none" or arg == "":
# Assume this is an override to clear
# the value.
arg = ""
elif not os.path.isabs(arg):
raise OptionError("The path to "
"the Certificate file must be "
"absolute.")
elif not os.path.exists(arg):
raise OptionError("The specified "
"file does not exist.")
elif not os.path.isfile(arg):
raise OptionError("The specified "
"pathname is not a file.")
ivalues["pkg"]["ssl_cert_file"] = arg
elif opt == "--ssl-key-file":
if arg == "none" or arg == "":
# Assume this is an override to clear
# the value.
arg = ""
elif not os.path.isabs(arg):
raise OptionError("The path to "
"the Private Key file must be "
"absolute.")
elif not os.path.exists(arg):
raise OptionError("The specified "
"file does not exist.")
elif not os.path.isfile(arg):
raise OptionError("The specified "
"pathname is not a file.")
ivalues["pkg"]["ssl_key_file"] = arg
elif opt == "--ssl-dialog":
if arg != "builtin" and \
arg != "smf" and not \
arg.startswith("exec:/") and not \
arg.startswith("svc:"):
raise OptionError("Invalid value "
"specified. Expected: builtin, "
"exec:/path/to/program, smf, or "
"an SMF FMRI.")
if arg.startswith("exec:"):
if os_util.get_canonical_os_type() != \
"unix":
# Don't allow a somewhat
# insecure authentication method
# on some platforms.
raise OptionError("exec is "
"not a supported dialog "
"type for this operating "
"system.")
f = os.path.abspath(arg.split(
"exec:")[1])
if not os.path.isfile(f):
raise OptionError("Invalid "
"file path specified for "
"exec.")
ivalues["pkg"]["ssl_dialog"] = arg
elif opt == "--sort-file-max-size":
ivalues["pkg"]["sort_file_max_size"] = arg
elif opt == "--writable-root":
ivalues["pkg"]["writable_root"] = arg
# Set accumulated values.
if debug_features:
ivalues["pkg"]["debug"] = debug_features
if disable_ops:
ivalues["pkg"]["disable_ops"] = disable_ops
if addresses:
ivalues["pkg"]["address"] = list(addresses)
if DebugValues:
reload(pkg.digest)
# Build configuration object.
dconf = ds.DepotConfig(target=user_cfg, overrides=ivalues)
except getopt.GetoptError as _e:
usage("pkg.depotd: {0}".format(_e.msg))
except api_errors.ApiException as _e:
usage("pkg.depotd: {0}".format(str(_e)))
except OptionError as _e:
usage("pkg.depotd: option: {0} -- {1}".format(opt, _e))
except (ArithmeticError, ValueError):
usage("pkg.depotd: illegal option value: {0} specified " \
"for option: {1}".format(arg, opt))
if show_usage:
usage(retcode=0, full=True)
if not dconf.get_property("pkg", "log_errors"):
dconf.set_property("pkg", "log_errors", "stderr")
# If stdout is a tty, then send access output there by default instead
# of discarding it.
if not dconf.get_property("pkg", "log_access"):
if os.isatty(sys.stdout.fileno()):
dconf.set_property("pkg", "log_access", "stdout")
else:
dconf.set_property("pkg", "log_access", "none")
# Check for invalid option combinations.
image_root = dconf.get_property("pkg", "image_root")
inst_root = dconf.get_property("pkg", "inst_root")
mirror = dconf.get_property("pkg", "mirror")
ll_mirror = dconf.get_property("pkg", "ll_mirror")
readonly = dconf.get_property("pkg", "readonly")
writable_root = dconf.get_property("pkg", "writable_root")
if rebuild and add_content:
usage("--add-content cannot be used with --rebuild")
if rebuild and reindex:
usage("--refresh-index cannot be used with --rebuild")
if (rebuild or add_content) and (readonly or mirror):
usage("--readonly and --mirror cannot be used with --rebuild "
"or --add-content")
if reindex and mirror:
usage("--mirror cannot be used with --refresh-index")
if reindex and readonly and not writable_root:
usage("--readonly can only be used with --refresh-index if "
"--writable-root is used")
if image_root and not ll_mirror:
usage("--image-root can only be used with --llmirror.")
if image_root and writable_root:
usage("--image_root and --writable-root cannot be used "
"together.")
if image_root and inst_root:
usage("--image-root and -d cannot be used together.")
# If the image format changes this may need to be reexamined.
if image_root:
inst_root = os.path.join(image_root, "var", "pkg")
# Set any values using defaults if they weren't provided.
# Only use the first value for now; multiple bind addresses may be
# supported later.
address = dconf.get_property("pkg", "address")
if address:
address = address[0]
elif not address:
dconf.set_property("pkg", "address", [HOST_DEFAULT])
address = dconf.get_property("pkg", "address")[0]
if not inst_root:
usage("Either PKG_REPO or -d must be provided")
content_root = dconf.get_property("pkg", "content_root")
if not content_root:
dconf.set_property("pkg", "content_root", CONTENT_PATH_DEFAULT)
content_root = dconf.get_property("pkg", "content_root")
port = dconf.get_property("pkg", "port")
ssl_cert_file = dconf.get_property("pkg", "ssl_cert_file")
ssl_key_file = dconf.get_property("pkg", "ssl_key_file")
if (ssl_cert_file and not ssl_key_file) or (ssl_key_file and not
ssl_cert_file):
usage("The --ssl-cert-file and --ssl-key-file options must "
"must both be provided when using either option.")
elif not port:
if ssl_cert_file and ssl_key_file:
dconf.set_property("pkg", "port", SSL_PORT_DEFAULT)
else:
dconf.set_property("pkg", "port", PORT_DEFAULT)
port = dconf.get_property("pkg", "port")
socket_timeout = dconf.get_property("pkg", "socket_timeout")
if not socket_timeout:
dconf.set_property("pkg", "socket_timeout",
SOCKET_TIMEOUT_DEFAULT)
socket_timeout = dconf.get_property("pkg", "socket_timeout")
threads = dconf.get_property("pkg", "threads")
if not threads:
dconf.set_property("pkg", "threads", THREADS_DEFAULT)
threads = dconf.get_property("pkg", "threads")
# If the program is going to reindex, the port is irrelevant since
# the program will not bind to a port.
if not exit_ready:
try:
cherrypy.process.servers.check_port(address, port)
except Exception as e:
emsg("pkg.depotd: unable to bind to the specified "
"port: {0:d}. Reason: {1}".format(port, e))
sys.exit(1)
else:
# Not applicable if we're not going to serve content
dconf.set_property("pkg", "content_root", "")
# Any relative paths should be made absolute using pkg_root. 'pkg_root'
# is a special property that was added to enable internal deployment of
# multiple disparate versions of the pkg.depotd software.
pkg_root = dconf.get_property("pkg", "pkg_root")
repo_config_file = dconf.get_property("pkg", "cfg_file")
if repo_config_file and not os.path.isabs(repo_config_file):
repo_config_file = os.path.join(pkg_root, repo_config_file)
if content_root and not os.path.isabs(content_root):
content_root = os.path.join(pkg_root, content_root)
if inst_root and not os.path.isabs(inst_root):
inst_root = os.path.join(pkg_root, inst_root)
if ssl_cert_file:
if ssl_cert_file == "none":
ssl_cert_file = None
elif not os.path.isabs(ssl_cert_file):
ssl_cert_file = os.path.join(pkg_root, ssl_cert_file)
if ssl_key_file:
if ssl_key_file == "none":
ssl_key_file = None
elif not os.path.isabs(ssl_key_file):
ssl_key_file = os.path.join(pkg_root, ssl_key_file)
if writable_root and not os.path.isabs(writable_root):
writable_root = os.path.join(pkg_root, writable_root)
# Setup SSL if requested.
key_data = None
ssl_dialog = dconf.get_property("pkg", "ssl_dialog")
if not exit_ready and ssl_cert_file and ssl_key_file and \
ssl_dialog != "builtin":
cmdline = None
def get_ssl_passphrase(*ignored):
p = None
try:
if cmdline:
cmdargs = shlex.split(cmdline)
else:
cmdargs = []
p = subprocess.Popen(cmdargs,
stdout=subprocess.PIPE,
stderr=None)
p.wait()
except Exception as __e:
emsg("pkg.depotd: an error occurred while "
"executing [{0}]; unable to obtain the "
"passphrase needed to decrypt the SSL "
"private key file: {1}".format(cmdline,
__e))
sys.exit(1)
return p.stdout.read().strip(b"\n")
if ssl_dialog.startswith("exec:"):
exec_path = ssl_dialog.split("exec:")[1]
if not os.path.isabs(exec_path):
exec_path = os.path.join(pkg_root, exec_path)
cmdline = "{0} {1} {2:d}".format(exec_path, "''", port)
elif ssl_dialog == "smf" or ssl_dialog.startswith("svc:"):
if ssl_dialog == "smf":
# Assume the configuration target was an SMF
# FMRI and let svcprop fail with an error if
# it wasn't.
svc_fmri = dconf.target
else:
svc_fmri = ssl_dialog
cmdline = "/usr/bin/svcprop -p " \
"pkg_secure/ssl_key_passphrase {0}".format(svc_fmri)
# The key file requires decryption, but the user has requested
# exec-based authentication, so it will have to be decoded first
# to an un-named temporary file.
try:
with open(ssl_key_file, "rb") as key_file:
pkey = crypto.load_privatekey(
crypto.FILETYPE_PEM, key_file.read(),
get_ssl_passphrase)
key_data = tempfile.NamedTemporaryFile(dir=pkg_root,
delete=True)
key_data.write(crypto.dump_privatekey(
crypto.FILETYPE_PEM, pkey))
key_data.seek(0)
except EnvironmentError as _e:
emsg("pkg.depotd: unable to read the SSL private key "
"file: {0}".format(_e))
sys.exit(1)
except crypto.Error as _e:
emsg("pkg.depotd: authentication or cryptography "
"failure while attempting to decode\nthe SSL "
"private key file: {0}".format(_e))
sys.exit(1)
else:
# Redirect the server to the decrypted key file.
ssl_key_file = key_data.name
# Setup our global configuration.
gconf = {
"checker.on": True,
"environment": "production",
"log.screen": False,
"server.max_request_body_size": MAX_REQUEST_BODY_SIZE,
"server.shutdown_timeout": 0,
"server.socket_host": address,
"server.socket_port": port,
"server.socket_timeout": socket_timeout,
"server.ssl_certificate": ssl_cert_file,
"server.ssl_private_key": ssl_key_file,
"server.thread_pool": threads,
"tools.log_headers.on": True,
"tools.encode.on": True,
"tools.encode.encoding": "utf-8",
}
if "headers" in dconf.get_property("pkg", "debug"):
# Despite its name, this only logs headers when there is an
# error; it's redundant with the debug feature enabled.
gconf["tools.log_headers.on"] = False
# Causes the headers of every request to be logged to the error
# log; even if an exception occurs.
gconf["tools.log_headers_always.on"] = True
cherrypy.tools.log_headers_always = cherrypy.Tool(
"on_start_resource",
cherrypy.lib.cptools.log_request_headers)
log_cfg = {
"access": dconf.get_property("pkg", "log_access"),
"errors": dconf.get_property("pkg", "log_errors")
}
# If stdin is not a tty and the pkgdepot controller isn't being used,
# then assume process will be daemonized and redirect output.
if not os.environ.get("PKGDEPOT_CONTROLLER") and \
not os.isatty(sys.stdin.fileno()):
# Ensure log handlers are setup to use the file descriptors for
# stdout and stderr as the Daemonizer (used for test suite and
# SMF service) requires this.
if log_cfg["access"] == "stdout":
log_cfg["access"] = "/dev/fd/{0:d}".format(
sys.stdout.fileno())
elif log_cfg["access"] == "stderr":
log_cfg["access"] = "/dev/fd/{0:d}".format(
sys.stderr.fileno())
elif log_cfg["access"] == "none":
log_cfg["access"] = "/dev/null"
if log_cfg["errors"] == "stderr":
log_cfg["errors"] = "/dev/fd/{0:d}".format(
sys.stderr.fileno())
elif log_cfg["errors"] == "stdout":
log_cfg["errors"] = "/dev/fd/{0:d}".format(
sys.stdout.fileno())
elif log_cfg["errors"] == "none":
log_cfg["errors"] = "/dev/null"
log_type_map = {
"errors": {
"param": "log.error_file",
"attr": "error_log"
},
"access": {
"param": "log.access_file",
"attr": "access_log"
}
}
for log_type in log_type_map:
dest = log_cfg[log_type]
if dest in ("stdout", "stderr", "none"):
if dest == "none":
h = logging.StreamHandler(LogSink())
else:
h = logging.StreamHandler(eval("sys.{0}".format(
dest)))
h.setLevel(logging.DEBUG)
h.setFormatter(cherrypy._cplogging.logfmt)
log_obj = eval("cherrypy.log.{0}".format(
log_type_map[log_type]["attr"]))
log_obj.addHandler(h)
# Since we've replaced cherrypy's log handler with our
# own, we don't want the output directed to a file.
dest = ""
elif dest:
if not os.path.isabs(dest):
dest = os.path.join(pkg_root, dest)
gconf[log_type_map[log_type]["param"]] = dest
cherrypy.config.update(gconf)
# Now that our logging, etc. has been setup, it's safe to perform any
# remaining preparation.
# Initialize repository state.
if not readonly:
# Not readonly, so assume a new repository should be created.
try:
sr.repository_create(inst_root, properties=repo_props)
except sr.RepositoryExistsError:
# Already exists, nothing to do.
pass
except (api_errors.ApiException, sr.RepositoryError) as _e:
emsg("pkg.depotd: {0}".format(_e))
sys.exit(1)
try:
sort_file_max_size = dconf.get_property("pkg",
"sort_file_max_size")
repo = sr.Repository(cfgpathname=repo_config_file,
log_obj=cherrypy, mirror=mirror, properties=repo_props,
read_only=readonly, root=inst_root,
sort_file_max_size=sort_file_max_size,
writable_root=writable_root)
except (RuntimeError, sr.RepositoryError) as _e:
emsg("pkg.depotd: {0}".format(_e))
sys.exit(1)
except search_errors.IndexingException as _e:
emsg("pkg.depotd: {0}".format(str(_e)), "INDEX")
sys.exit(1)
except api_errors.ApiException as _e:
emsg("pkg.depotd: {0}".format(str(_e)))
sys.exit(1)
if not rebuild and not add_content and not repo.mirror and \
not (repo.read_only and not repo.writable_root):
# Automatically update search indexes on startup if not already
# told to, and not in readonly/mirror mode.
reindex = True
if reindex:
try:
# Only execute a index refresh here if --exit-ready was
# requested; it will be handled later in the setup
# process for other cases.
if repo.root and exit_ready:
repo.refresh_index()
except (sr.RepositoryError, search_errors.IndexingException,
api_errors.ApiException) as e:
emsg(str(e), "INDEX")
sys.exit(1)
elif rebuild:
try:
repo.rebuild(build_index=True)
except sr.RepositoryError as e:
emsg(str(e), "REBUILD")
sys.exit(1)
except (search_errors.IndexingException,
api_errors.UnknownErrors,
api_errors.PermissionsException) as e:
emsg(str(e), "INDEX")
sys.exit(1)
elif add_content:
try:
repo.add_content()
repo.refresh_index()
except sr.RepositoryError as e:
emsg(str(e), "ADD_CONTENT")
sys.exit(1)
except (search_errors.IndexingException,
api_errors.UnknownErrors,
api_errors.PermissionsException) as e:
emsg(str(e), "INDEX")
sys.exit(1)
# Ready to start depot; exit now if requested.
if exit_ready:
sys.exit(0)
# Next, initialize depot.
if nasty:
depot = ds.NastyDepotHTTP(repo, dconf)
else:
depot = ds.DepotHTTP(repo, dconf)
# Now build our site configuration.
conf = {
"/": {},
"/robots.txt": {
"tools.staticfile.on": True,
"tools.staticfile.filename": os.path.join(depot.web_root,
"robots.txt")
},
}
if list(map(int, version)) >= [3, 2, 0]:
conf["/"]["request.dispatch"] = Pkg5Dispatcher()
proxy_base = dconf.get_property("pkg", "proxy_base")
if proxy_base:
# This changes the base URL for our server, and is primarily
# intended to allow our depot process to operate behind Apache
# or some other webserver process.
#
# Visit the following URL for more information:
# http://cherrypy.org/wiki/BuiltinTools#tools.proxy
proxy_conf = {
"tools.proxy.on": True,
"tools.proxy.local": "",
"tools.proxy.base": proxy_base
}
# Now merge or add our proxy configuration information into the
# existing configuration.
for entry in proxy_conf:
conf["/"][entry] = proxy_conf[entry]
if ll_mirror:
ds.DNSSD_Plugin(cherrypy.engine, gconf).subscribe()
if reindex:
# Tell depot to update search indexes when possible;
# this is done as a background task so that packages
# can be served immediately while search indexes are
# still being updated.
depot._queue_refresh_index()
# If stdin is not a tty and the pkgdepot controller isn't being used,
# then assume process should be daemonized.
if not os.environ.get("PKGDEPOT_CONTROLLER") and \
not os.isatty(sys.stdin.fileno()):
# Translate the values in log_cfg into paths.
Daemonizer(cherrypy.engine, stderr=log_cfg["errors"],
stdout=log_cfg["access"]).subscribe()
try:
root = cherrypy.Application(depot)
cherrypy.quickstart(root, config=conf)
except Exception as _e:
emsg("pkg.depotd: unknown error starting depot server, " \
"illegal option value specified?")
emsg(_e)
sys.exit(1)