Commit 519ca223 authored by Hanno Schlichting's avatar Hanno Schlichting

Move ZServer related console scripts into ZServer.

parent 62d6e254
......@@ -8,7 +8,8 @@ extends =
versions.cfg
parts =
test
scripts
zopescripts
zserverscripts
zopepy
alltests
ztktests
......@@ -30,11 +31,16 @@ initialization =
eggs = Zope2
[scripts]
[zopescripts]
recipe = zc.recipe.egg
eggs = Zope2
[zserverscripts]
recipe = zc.recipe.egg
eggs = ZServer
[zopepy]
recipe = zc.recipe.egg
eggs = Zope2
......
......@@ -144,12 +144,6 @@ used.
instancehome $INSTANCE
A fully-annotated sample can be found in the Zope2 egg::
$ cat eggs/Zope2--*/Zope2/utilities/skel/etc/example.conf.in
<rest of the stuff that goes into a zope.conf, e.g. databases and log files.>
.. highlight:: bash
An example session::
......
......@@ -113,12 +113,9 @@ setup(
'httpexceptions=Zope2.Startup.httpexceptions:main',
],
'console_scripts': [
'mkzopeinstance=Zope2.utilities.mkzopeinstance:main',
'runwsgi=Zope2.Startup.serve:main',
'runzope=Zope2.Startup.run:run',
'zopectl=Zope2.Startup.zopectl:run',
'zpasswd=Zope2.utilities.zpasswd:main',
'addzope2user=Zope2.utilities.adduser:main',
'runwsgi=Zope2.Startup.serve:main',
'mkwsgiinstance=Zope2.utilities.mkwsgiinstance:main',
],
},
)
......@@ -20,7 +20,7 @@ from zope.deferredimport import deprecated
# BBB Zope 5.0
deprecated(
'Please import from ZServer.Startup.starter',
'Please import from ZServer.Zope2.Startup.starter',
UnixZopeStarter='ZServer.Zope2.Startup.starter:UnixZopeStarter',
WindowsZopeStarter='ZServer.Zope2.Startup.starter:WindowsZopeStarter',
ZopeStarter='ZServer.Zope2.Startup.starter:ZopeStarter',
......
......@@ -12,19 +12,13 @@
#
##############################################################################
from zope.deferredimport import deprecated
def run():
""" Start a Zope instance """
import Zope2.Startup
starter = Zope2.Startup.get_starter(wsgi=False)
opts = _setconfig()
starter.setConfiguration(opts.configroot)
try:
starter.prepare()
except:
starter.shutdown()
raise
starter.run()
# BBB Zope 5.0
deprecated(
'Please import from ZServer.Zope2.Startup.run',
run='ZServer.Zope2.Startup.run:run',
)
def configure(configfile):
......@@ -75,7 +69,3 @@ def make_wsgi_app(global_config, zope_conf):
starter.setConfiguration(opts.configroot)
starter.prepare()
return publish_module
if __name__ == '__main__':
run()
......@@ -62,7 +62,7 @@ class WSGIStartupTestCase(unittest.TestCase):
def test_load_config_template(self):
import Zope2.utilities
base = os.path.dirname(Zope2.utilities.__file__)
fn = os.path.join(base, "skel", "etc", "base.conf.in")
fn = os.path.join(base, "skel", "etc", "wsgi.conf.in")
f = open(fn)
text = f.read()
f.close()
......
This diff is collapsed.
......@@ -13,26 +13,6 @@
##############################################################################
"""Zope application package."""
# Before this version of Zope, "import Zope" always opened the
# database automatically. Unfortunately, that strategy caused the
# Python import lock to be held by the main thread during database
# initialization, which lead to a deadlock if other threads required
# something to be imported before completing initialization. This can
# be a big problem for ZEO.
# Now the database is opened when you call startup(), app(), or
# debug().
# This version is transitional. If you have a script that no longer
# works because it needs the database to be opened on calling "import
# Zope", you can set the environment variable ZOPE_COMPATIBLE_STARTUP
# to a non-empty value. Then "import Zope2" will automatically open
# the database as it used to. Or better, update the script to call
# Zope2.startup() right after importing the Zope package. A future
# version of Zope will remove this backward compatibility, since the
# old behavior is likely to cause problems as ZODB backends, like ZEO,
# gain new features.
import os
from Zope2.Startup.run import configure
......@@ -68,7 +48,6 @@ def debug(*args, **kw):
def _configure():
# Load configuration file from (optional) environment variable
# Also see http://zope.org/Collectors/Zope/1233
import os
configfile = os.environ.get('ZOPE_CONFIG')
if configfile is not None:
configure(configfile)
......@@ -81,8 +60,3 @@ zpublisher_transactions_manager = None
zpublisher_validated_hook = None
zpublisher_exception_hook = None
__bobo_before__ = None
if os.environ.get('ZOPE_COMPATIBLE_STARTUP'):
# Open the database immediately (see comment above).
startup()
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""%(program)s: Create a Zope WSGI instance home.
usage: %(program)s [options]
Options:
-h/--help -- print this help text
-d/--dir -- the dir in which the instance home should be created
-u/--user NAME:PASSWORD -- set the user name and password of the initial user
-s/--skelsrc -- the dir from which skeleton files should be copied
-p/--python -- the Python interpreter to use
When run without arguments, this script will ask for the information
necessary to create a Zope WSGI instance home.
"""
import getopt
import os
import sys
import copyzopeskel
if sys.version_info > (3, 0):
raw_input = input
def main():
try:
opts, args = getopt.getopt(
sys.argv[1:],
"hu:d:s:p:",
["help", "user=", "dir=", "skelsrc=", "python="]
)
except getopt.GetoptError as msg:
usage(sys.stderr, msg)
sys.exit(2)
script_path = os.path.abspath(os.path.dirname(sys.argv[0]))
user = None
password = None
skeltarget = None
skelsrc = None
python = None
if check_buildout(script_path):
python = os.path.join(script_path, 'zopepy')
for opt, arg in opts:
if opt in ("-d", "--dir"):
skeltarget = os.path.abspath(os.path.expanduser(arg))
if not skeltarget:
usage(sys.stderr, "dir must not be empty")
sys.exit(2)
if opt in ("-s", "--skelsrc"):
skelsrc = os.path.abspath(os.path.expanduser(arg))
if not skelsrc:
usage(sys.stderr, "skelsrc must not be empty")
sys.exit(2)
if opt in ("-p", "--python"):
python = os.path.abspath(os.path.expanduser(arg))
if not os.path.exists(python) and os.path.isfile(python):
usage(sys.stderr, "The Python interpreter does not exist.")
sys.exit(2)
if opt in ("-h", "--help"):
usage(sys.stdout)
sys.exit()
if opt in ("-u", "--user"):
if not arg:
usage(sys.stderr, "user must not be empty")
sys.exit(2)
if ":" not in arg:
usage(sys.stderr, "user must be specified as name:password")
sys.exit(2)
user, password = arg.split(":", 1)
if not skeltarget:
# interactively ask for skeltarget and initial user name/passwd.
# cant set custom instancehome in interactive mode, we default
# to skeltarget.
skeltarget = instancehome = os.path.abspath(
os.path.expanduser(get_skeltarget())
)
instancehome = skeltarget
if skelsrc is None:
# default to using stock Zope skeleton source
skelsrc = os.path.join(os.path.dirname(__file__), "skel")
inituser = os.path.join(instancehome, "inituser")
if not (user or os.path.exists(inituser)):
user, password = get_inituser()
# we need to distinguish between python.exe and pythonw.exe under
# Windows. Zope is always run using 'python.exe' (even for services),
# however, it may be installed via pythonw.exe (as a sub-process of an
# installer). Thus, sys.executable may not be the executable we use.
# We still provide both PYTHON and PYTHONW, but PYTHONW should never
# need be used.
if python is None:
python = sys.executable
psplit = os.path.split(python)
exedir = os.path.join(*psplit[:-1])
pythonexe = os.path.join(exedir, 'python.exe')
pythonwexe = os.path.join(exedir, 'pythonw.exe')
if (os.path.isfile(pythonwexe) and os.path.isfile(pythonexe) and
(python in [pythonwexe, pythonexe])):
# we're using a Windows build with both python.exe and pythonw.exe
# in the same directory
PYTHON = pythonexe
PYTHONW = pythonwexe
else:
# we're on UNIX or we have a nonstandard Windows setup
PYTHON = PYTHONW = python
zope2path = get_zope2path(PYTHON)
kw = {
"PYTHON": PYTHON,
"PYTHONW": PYTHONW,
"INSTANCE_HOME": instancehome,
"ZOPE_SCRIPTS": script_path,
"ZOPE2PATH": zope2path,
}
copyzopeskel.copyskel(skelsrc, skeltarget, None, None, **kw)
if user and password:
write_inituser(inituser, user, password)
def usage(stream, msg=None):
if msg:
stream.write(msg)
stream.write('\n')
program = os.path.basename(sys.argv[0])
stream.write(__doc__ % {"program": program})
def get_skeltarget():
print('Please choose a directory in which you\'d like to install')
print('Zope "instance home" files such as database files, configuration')
print('files, etc.')
print
while 1:
skeltarget = raw_input("Directory: ").strip()
if skeltarget == '':
print('You must specify a directory')
continue
else:
break
return skeltarget
def get_inituser():
import getpass
print('Please choose a username and password for the initial user.')
print('These will be the credentials you use to initially manage')
print('your new Zope instance.')
print
user = raw_input("Username: ").strip()
if user == '':
return None, None
while 1:
passwd = getpass.getpass("Password: ")
verify = getpass.getpass("Verify password: ")
if verify == passwd:
break
else:
passwd = verify = ''
print("Password mismatch, please try again...")
return user, passwd
def write_inituser(fn, user, password):
import binascii
try:
from hashlib import sha1 as sha
except:
from sha import new as sha
fp = open(fn, "w")
pw = binascii.b2a_base64(sha(password).digest())[:-1]
fp.write('%s:{SHA}%s\n' % (user, pw))
fp.close()
os.chmod(fn, 0o644)
def check_buildout(script_path):
""" Are we running from within a buildout which supplies 'zopepy'?
"""
buildout_cfg = os.path.join(os.path.dirname(script_path), 'buildout.cfg')
if os.path.exists(buildout_cfg):
from ConfigParser import RawConfigParser
parser = RawConfigParser()
parser.read(buildout_cfg)
return 'zopepy' in parser.sections()
def get_zope2path(python):
""" Get Zope2 path from selected Python interpreter.
"""
zope2file = ''
p = os.popen('"%s" -c"import Zope2; print Zope2.__file__"' % python)
try:
zope2file = p.readline()[:-1]
finally:
p.close()
if not zope2file:
# fall back to current Python interpreter
import Zope2
zope2file = Zope2.__file__
return os.path.abspath(os.path.dirname(os.path.dirname(zope2file)))
if __name__ == "__main__":
main()
......@@ -11,216 +11,18 @@
#
##############################################################################
"""%(program)s: Create a Zope instance home.
usage: %(program)s [options]
Options:
-h/--help -- print this help text
-d/--dir -- the dir in which the instance home should be created
-u/--user NAME:PASSWORD -- set the user name and password of the initial user
-s/--skelsrc -- the dir from which skeleton files should be copied
-p/--python -- the Python interpreter to use
When run without arguments, this script will ask for the information necessary
to create a Zope instance home.
"""
import getopt
import os
import sys
import copyzopeskel
if sys.version_info > (3, 0):
raw_input = input
def main():
try:
opts, args = getopt.getopt(
sys.argv[1:],
"hu:d:s:p:",
["help", "user=", "dir=", "skelsrc=", "python="]
)
except getopt.GetoptError as msg:
usage(sys.stderr, msg)
sys.exit(2)
script_path = os.path.abspath(os.path.dirname(sys.argv[0]))
user = None
password = None
skeltarget = None
skelsrc = None
python = None
if check_buildout(script_path):
python = os.path.join(script_path, 'zopepy')
for opt, arg in opts:
if opt in ("-d", "--dir"):
skeltarget = os.path.abspath(os.path.expanduser(arg))
if not skeltarget:
usage(sys.stderr, "dir must not be empty")
sys.exit(2)
if opt in ("-s", "--skelsrc"):
skelsrc = os.path.abspath(os.path.expanduser(arg))
if not skelsrc:
usage(sys.stderr, "skelsrc must not be empty")
sys.exit(2)
if opt in ("-p", "--python"):
python = os.path.abspath(os.path.expanduser(arg))
if not os.path.exists(python) and os.path.isfile(python):
usage(sys.stderr, "The Python interpreter does not exist.")
sys.exit(2)
if opt in ("-h", "--help"):
usage(sys.stdout)
sys.exit()
if opt in ("-u", "--user"):
if not arg:
usage(sys.stderr, "user must not be empty")
sys.exit(2)
if ":" not in arg:
usage(sys.stderr, "user must be specified as name:password")
sys.exit(2)
user, password = arg.split(":", 1)
if not skeltarget:
# interactively ask for skeltarget and initial user name/passwd.
# cant set custom instancehome in interactive mode, we default
# to skeltarget.
skeltarget = instancehome = os.path.abspath(
os.path.expanduser(get_skeltarget())
)
instancehome = skeltarget
if skelsrc is None:
# default to using stock Zope skeleton source
skelsrc = os.path.join(os.path.dirname(__file__), "skel")
inituser = os.path.join(instancehome, "inituser")
if not (user or os.path.exists(inituser)):
user, password = get_inituser()
# we need to distinguish between python.exe and pythonw.exe under
# Windows. Zope is always run using 'python.exe' (even for services),
# however, it may be installed via pythonw.exe (as a sub-process of an
# installer). Thus, sys.executable may not be the executable we use.
# We still provide both PYTHON and PYTHONW, but PYTHONW should never
# need be used.
if python is None:
python = sys.executable
psplit = os.path.split(python)
exedir = os.path.join(*psplit[:-1])
pythonexe = os.path.join(exedir, 'python.exe')
pythonwexe = os.path.join(exedir, 'pythonw.exe')
if (os.path.isfile(pythonwexe) and os.path.isfile(pythonexe) and
(python in [pythonwexe, pythonexe])):
# we're using a Windows build with both python.exe and pythonw.exe
# in the same directory
PYTHON = pythonexe
PYTHONW = pythonwexe
else:
# we're on UNIX or we have a nonstandard Windows setup
PYTHON = PYTHONW = python
zope2path = get_zope2path(PYTHON)
kw = {
"PYTHON": PYTHON,
"PYTHONW": PYTHONW,
"INSTANCE_HOME": instancehome,
"ZOPE_SCRIPTS": script_path,
"ZOPE2PATH": zope2path,
}
copyzopeskel.copyskel(skelsrc, skeltarget, None, None, **kw)
if user and password:
write_inituser(inituser, user, password)
def usage(stream, msg=None):
if msg:
print >>stream, msg
print >>stream
program = os.path.basename(sys.argv[0])
print >>stream, __doc__ % {"program": program}
def get_skeltarget():
print('Please choose a directory in which you\'d like to install')
print('Zope "instance home" files such as database files, configuration')
print('files, etc.')
print
while 1:
skeltarget = raw_input("Directory: ").strip()
if skeltarget == '':
print('You must specify a directory')
continue
else:
break
return skeltarget
def get_inituser():
import getpass
print('Please choose a username and password for the initial user.')
print('These will be the credentials you use to initially manage')
print('your new Zope instance.')
print
user = raw_input("Username: ").strip()
if user == '':
return None, None
while 1:
passwd = getpass.getpass("Password: ")
verify = getpass.getpass("Verify password: ")
if verify == passwd:
break
else:
passwd = verify = ''
print("Password mismatch, please try again...")
return user, passwd
def write_inituser(fn, user, password):
import binascii
try:
from hashlib import sha1 as sha
except:
from sha import new as sha
fp = open(fn, "w")
pw = binascii.b2a_base64(sha(password).digest())[:-1]
fp.write('%s:{SHA}%s\n' % (user, pw))
fp.close()
os.chmod(fn, 0o644)
def check_buildout(script_path):
""" Are we running from within a buildout which supplies 'zopepy'?
"""
buildout_cfg = os.path.join(os.path.dirname(script_path), 'buildout.cfg')
if os.path.exists(buildout_cfg):
from ConfigParser import RawConfigParser
parser = RawConfigParser()
parser.read(buildout_cfg)
return 'zopepy' in parser.sections()
def get_zope2path(python):
""" Get Zope2 path from selected Python interpreter.
"""
zope2file = ''
p = os.popen('"%s" -c"import Zope2; print Zope2.__file__"' % python)
try:
zope2file = p.readline()[:-1]
finally:
p.close()
if not zope2file:
# fall back to current Python interpreter
import Zope2
zope2file = Zope2.__file__
return os.path.abspath(os.path.dirname(os.path.dirname(zope2file)))
if __name__ == "__main__":
main()
from zope.deferredimport import deprecated
_prefix = 'ZServer.Zope2.utilities.mkzopeinstance:'
# BBB Zope 5.0
deprecated(
'Please import from ZServer.Zope2.utilities.mkzopeinstance.',
main=_prefix + 'main',
usage=_prefix + 'usage',
get_skeltarget=_prefix + 'get_skeltarget',
get_inituser=_prefix + 'get_inituser',
write_inituser=_prefix + 'write_inituser',
check_buildout=_prefix + 'check_buildout',
get_zope2path=_prefix + 'get_zope2path',
)
%define INSTANCE <<INSTANCE_HOME>>
instancehome $INSTANCE
<eventlog>
level info
<logfile>
path $INSTANCE/log/event.log
level info
</logfile>
</eventlog>
<logger access>
level WARN
<logfile>
path $INSTANCE/log/Z2.log
format %(message)s
</logfile>
</logger>
<zodb_db main>
<filestorage>
path $INSTANCE/var/Data.fs
</filestorage>
mount-point /
</zodb_db>
This diff is collapsed.
%include <<INSTANCE_HOME>>/etc/base.conf
%define INSTANCE <<INSTANCE_HOME>>
instancehome $INSTANCE
<zodb_db main>
<filestorage>
path $INSTANCE/var/Data.fs
</filestorage>
mount-point /
</zodb_db>
%include <<INSTANCE_HOME>>/etc/base.conf
<http-server>
address 8080
</http-server>
from Zope2.Startup.run import configure
from Zope2 import startup
configure('<<INSTANCE_HOME>>/etc/zope.conf')
startup()
# mod_wsgi looks for the special name 'application'.
from ZPublisher.WSGIPublisher import publish_module as application
This is the directory used to hold log files by default.
This directory contains files created and maintained by the Zope
application server and related utilities while the server is running.
This can include the object database as well as supplemental files
(such as "pidfiles"). Log files are normally stored in the log/
directory in the instance home.
This directory typically contains the database files.
......@@ -11,220 +11,19 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Zope user bootstrap system
Usage: %(PROGRAM)s [options] filename
If this program is called without command-line options, it will prompt
for all necessary information. The available options are:
-u / --username=
Set the username to be used for the initial user or the emergency user
-p / --password=
Set the password
-e / --encoding=
Set the encryption/encoding rules. Defaults to SHA-1. OPTIONAL
-d / --domains=
Set the domain names that the user user can log in from. Defaults to
any. OPTIONAL.
-h / --help
Print this help text and exit.
Filename is required and should be the name of the file to store the
information in (usually "inituser" or "access").
"""
import binascii
import getopt
import getpass
import os
import random
import sha
import sys
try:
from crypt import crypt
except ImportError:
crypt = None
if sys.version_info > (3, 0):
raw_input = input
PROGRAM = sys.argv[0]
COMMASPACE = ', '
def generate_salt():
"""Generate a salt value for the crypt function."""
salt_choices = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789./")
return random.choice(salt_choices) + random.choice(salt_choices)
def generate_passwd(password, encoding):
encoding = encoding.upper()
if encoding == 'SHA':
pw = '{SHA}' + binascii.b2a_base64(sha.new(password).digest())[:-1]
elif encoding == 'CRYPT':
pw = '{CRYPT}' + crypt(password, generate_salt())
elif encoding == 'CLEARTEXT':
pw = password
else:
raise ValueError('Unsupported encoding: %s' % encoding)
return pw
def write_generated_password(home, ac_path, username):
pw_choices = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789!")
acfile = open(ac_path, 'w')
pw = ''
for i in range(8):
pw = pw + random.choice(pw_choices)
acfile.write('%s:%s\n' % (username, generate_passwd(pw, 'SHA')))
acfile.close()
os.chmod(ac_path, 0o644)
return pw
def write_access(home, user='', group=''):
ac_path = os.path.join(home, 'access')
if not os.path.exists(ac_path):
print('-' * 78)
print('creating default access file')
pw = write_generated_password(home, ac_path, 'emergency')
print("""Note:
The emergency user name and password are 'emergency'
and '%s'.
You can change the emergency name and password with the
zpasswd script. To find out more, type:
%s zpasswd.py
""" % (pw, sys.executable))
import do
do.ch(ac_path, user, group)
def get_password():
while 1:
password = getpass.getpass("Password: ")
verify = getpass.getpass("Verify password: ")
if verify == password:
return password
else:
password = verify = ''
print("Password mismatch, please try again...")
def write_inituser(home, user='', group=''):
ac_path = os.path.join(home, 'inituser')
if not os.path.exists(ac_path):
print('-' * 78)
print('creating default inituser file')
pw = write_generated_password(home, ac_path, 'admin')
print("""Note:
The initial user name and password are 'admin'
and '%s'.
You can change the name and password through the web
interface or using the 'zpasswd.py' script.
""" % pw)
import do
do.ch(ac_path, user, group)
def usage(code, msg=''):
sys.stderr.write(__doc__ % globals())
if msg:
sys.stderr.write(msg)
sys.exit(code)
def main():
shortopts = 'u:p:e:d:h'
longopts = ['username=',
'password=',
'encoding=',
'domains=',
'help']
try:
opts, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
except getopt.error as msg:
usage(1, msg)
# Defaults
username = password = None
domains = ''
encoding = 'SHA'
for opt, arg in opts:
if opt in ('-h', '--help'):
usage(0)
elif opt in ('-u', '--username'):
username = arg
elif opt in ('-p', '--password'):
password = arg
elif opt in ('-e', '--encoding'):
encoding = arg
elif opt in ('-d', '--domains'):
domains = ':' + arg
# Extra command line arguments?
if len(args) == 0:
usage(1, 'filename is required')
elif len(args) == 1:
access_file = open(args[0], 'w')
else:
usage(1, 'Extra command line arguments: ' + COMMASPACE.join(args))
if opts:
# There were some command line args, so verify
if username is not None and password is None:
password = get_password()
else:
# No command line args, so prompt
while 1:
username = raw_input("Username: ")
if username != '':
break
password = get_password()
while 1:
print("""
Please choose a format from:
SHA - SHA-1 hashed password (default)
CRYPT - UNIX-style crypt password
CLEARTEXT - no protection
""")
encoding = raw_input("Encoding: ")
if encoding == '':
encoding = 'SHA'
break
if encoding.upper() in ['SHA', 'CRYPT', 'CLEARTEXT']:
break
domains = raw_input("Domain restrictions: ")
if domains:
domains = ":" + domains
# Done with prompts and args
access_file.write(username + ":" +
generate_passwd(password, encoding) +
domains)
# If called from the command line
if __name__ == '__main__':
main()
from zope.deferredimport import deprecated
# BBB Zope 5.0
deprecated(
'Please import from ZServer.Zope2.utilities.zpasswd',
generate_salt='ZServer.Zope2.utilities.zpasswd:generate_salt',
generate_passwd='ZServer.Zope2.utilities.zpasswd:generate_passwd',
write_generated_password=('ZServer.Zope2.utilities.zpasswd:'
'write_generated_password'),
write_access='ZServer.Zope2.utilities.zpasswd:write_access',
get_password='ZServer.Zope2.utilities.zpasswd:get_password',
write_inituser='ZServer.Zope2.utilities.zpasswd:write_inituser',
usage='ZServer.Zope2.utilities.zpasswd:usage',
main='ZServer.Zope2.utilities.zpasswd:main',
)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment