Create admin user without user interaction.

This commit is contained in:
Shuai Lin 2016-11-12 11:28:49 +08:00
parent ede2fcdd58
commit c0cf376ba3
6 changed files with 449 additions and 21 deletions

View file

@ -13,3 +13,4 @@ script:
- cd ..
- cp samples/server-sqlite3.conf bootstrap/bootstrap.conf
- ./launcher bootstrap
- ./launcher start

View file

@ -5,10 +5,11 @@ set -o pipefail
version=6.0.5
image=seafileorg/server:$version
topdir=$(cd $(dirname $0); pwd -P)
sharedir=$topdir/shared
dockerdir=$(cd $(dirname $0); pwd -P)
sharedir=$dockerdir/shared
installdir=/opt/seafile/seafile-server-$version
cd $topdir
cd $dockerdir
dbg() {
if [[ $debug == "true" ]]; then
@ -36,8 +37,9 @@ set_volumes() {
mounts=(
$sharedir:/shared
$topdir/bootstrap:/bootstrap:ro
$topdir/scripts:/scripts:ro
$dockerdir/bootstrap:/bootstrap:ro
$dockerdir/scripts:/scripts:ro
$dockerdir/scripts/tmp/check_init_admin.py:$installdir/check_init_admin.py:ro
$bash_history:/root/.bash_history
)
volumes=""
@ -54,7 +56,7 @@ bootstrap() {
start() {
set_volumes
docker run --rm -it $volumes $image # scripts/start.py
docker run --rm -it $volumes $image scripts/start.py
}
enter() {

View file

@ -7,26 +7,16 @@ setup-seafile.sh or setup-seafile-mysql.sh. It's supposed to run inside the
container.
"""
from ConfigParser import ConfigParser
import os
from os.path import abspath, basename, exists, dirname, join, isdir
import shutil
import sys
from utils import call, get_install_dir, get_script
from utils import call, get_conf, get_install_dir, get_script
installdir = get_install_dir()
topdir = dirname(installdir)
_config = None
def get_conf(key):
global _config
if _config is None:
_config = ConfigParser()
_config.read("/bootstrap/bootstrap.conf")
return _config.get("server", key)
def main():
env = {
'SERVER_NAME': 'seafile',

38
scripts/start.py Normal file → Executable file
View file

@ -0,0 +1,38 @@
#!/usr/bin/env python
#coding: UTF-8
"""
This script calls the appropriate seafile init scripts (e.g.
setup-seafile.sh or setup-seafile-mysql.sh. It's supposed to run inside the
container.
"""
import json
import os
from os.path import abspath, basename, exists, dirname, join, isdir
import shutil
import sys
from utils import call, get_conf, get_install_dir, get_script
installdir = get_install_dir()
topdir = dirname(installdir)
def main():
admin_pw = {
'email': get_conf('admin.email'),
'password': get_conf('admin.password'),
}
password_file = join(topdir, 'conf', 'admin.txt')
with open(password_file, 'w') as fp:
json.dump(admin_pw, fp)
try:
call('{} start'.format(get_script('seafile.sh')), check_call=True)
call('{} start'.format(get_script('seahub.sh')), check_call=True)
finally:
if exists(password_file):
os.unlink(password_file)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,382 @@
#coding: UTF-8
'''This script would check if there is admin, and prompt the user to create a new one if non exist'''
import json
import sys
import os
import time
import re
import shutil
import glob
import subprocess
import hashlib
import getpass
import uuid
import warnings
from ConfigParser import ConfigParser
try:
import readline # pylint: disable=W0611
except ImportError:
pass
SERVER_MANUAL_HTTP = 'https://github.com/haiwen/seafile/wiki'
class Utils(object):
'''Groups all helper functions here'''
@staticmethod
def welcome():
'''Show welcome message'''
welcome_msg = '''\
-----------------------------------------------------------------
This script will guide you to setup your seafile server using MySQL.
Make sure you have read seafile server manual at
%s
Press ENTER to continue
-----------------------------------------------------------------''' % SERVER_MANUAL_HTTP
print welcome_msg
raw_input()
@staticmethod
def highlight(content):
'''Add ANSI color to content to get it highlighted on terminal'''
return '\x1b[33m%s\x1b[m' % content
@staticmethod
def info(msg):
print msg
@staticmethod
def error(msg):
'''Print error and exit'''
print
print 'Error: ' + msg
sys.exit(1)
@staticmethod
def run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):
'''Run a program and wait it to finish, and return its exit code. The
standard output of this program is supressed.
'''
with open(os.devnull, 'w') as devnull:
if suppress_stdout:
stdout = devnull
else:
stdout = sys.stdout
if suppress_stderr:
stderr = devnull
else:
stderr = sys.stderr
proc = subprocess.Popen(argv,
cwd=cwd,
stdout=stdout,
stderr=stderr,
env=env)
return proc.wait()
@staticmethod
def run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):
'''Like run_argv but specify a command line string instead of argv'''
with open(os.devnull, 'w') as devnull:
if suppress_stdout:
stdout = devnull
else:
stdout = sys.stdout
if suppress_stderr:
stderr = devnull
else:
stderr = sys.stderr
proc = subprocess.Popen(cmdline,
cwd=cwd,
stdout=stdout,
stderr=stderr,
env=env,
shell=True)
return proc.wait()
@staticmethod
def prepend_env_value(name, value, env=None, seperator=':'):
'''prepend a new value to a list'''
if env is None:
env = os.environ
try:
current_value = env[name]
except KeyError:
current_value = ''
new_value = value
if current_value:
new_value += seperator + current_value
env[name] = new_value
@staticmethod
def must_mkdir(path):
'''Create a directory, exit on failure'''
try:
os.mkdir(path)
except OSError, e:
Utils.error('failed to create directory %s:%s' % (path, e))
@staticmethod
def must_copy(src, dst):
'''Copy src to dst, exit on failure'''
try:
shutil.copy(src, dst)
except Exception, e:
Utils.error('failed to copy %s to %s: %s' % (src, dst, e))
@staticmethod
def find_in_path(prog):
if 'win32' in sys.platform:
sep = ';'
else:
sep = ':'
dirs = os.environ['PATH'].split(sep)
for d in dirs:
d = d.strip()
if d == '':
continue
path = os.path.join(d, prog)
if os.path.exists(path):
return path
return None
@staticmethod
def get_python_executable():
'''Return the python executable. This should be the PYTHON environment
variable which is set in setup-seafile-mysql.sh
'''
return os.environ['PYTHON']
@staticmethod
def read_config(fn):
'''Return a case sensitive ConfigParser by reading the file "fn"'''
cp = ConfigParser()
cp.optionxform = str
cp.read(fn)
return cp
@staticmethod
def write_config(cp, fn):
'''Return a case sensitive ConfigParser by reading the file "fn"'''
with open(fn, 'w') as fp:
cp.write(fp)
@staticmethod
def ask_question(desc,
key=None,
note=None,
default=None,
validate=None,
yes_or_no=False,
password=False):
'''Ask a question, return the answer.
@desc description, e.g. "What is the port of ccnet?"
@key a name to represent the target of the question, e.g. "port for
ccnet server"
@note additional information for the question, e.g. "Must be a valid
port number"
@default the default value of the question. If the default value is
not None, when the user enter nothing and press [ENTER], the default
value would be returned
@validate a function that takes the user input as the only parameter
and validate it. It should return a validated value, or throws an
"InvalidAnswer" exception if the input is not valid.
@yes_or_no If true, the user must answer "yes" or "no", and a boolean
value would be returned
@password If true, the user input would not be echoed to the
console
'''
assert key or yes_or_no
# Format description
print
if note:
desc += '\n' + note
desc += '\n'
if yes_or_no:
desc += '[ yes or no ]'
else:
if default:
desc += '[ default "%s" ]' % default
else:
desc += '[ %s ]' % key
desc += ' '
while True:
# prompt for user input
if password:
answer = getpass.getpass(desc).strip()
else:
answer = raw_input(desc).strip()
# No user input: use default
if not answer:
if default:
answer = default
else:
continue
# Have user input: validate answer
if yes_or_no:
if answer not in ['yes', 'no']:
print Utils.highlight('\nPlease answer yes or no\n')
continue
else:
return answer == 'yes'
else:
if validate:
try:
return validate(answer)
except InvalidAnswer, e:
print Utils.highlight('\n%s\n' % e)
continue
else:
return answer
@staticmethod
def validate_port(port):
try:
port = int(port)
except ValueError:
raise InvalidAnswer('%s is not a valid port' % Utils.highlight(port))
if port <= 0 or port > 65535:
raise InvalidAnswer('%s is not a valid port' % Utils.highlight(port))
return port
class InvalidAnswer(Exception):
def __init__(self, msg):
Exception.__init__(self)
self.msg = msg
def __str__(self):
return self.msg
### END of Utils
####################
class RPC(object):
def __init__(self):
import ccnet
ccnet_dir = os.environ['CCNET_CONF_DIR']
central_config_dir = os.environ['SEAFILE_CENTRAL_CONF_DIR']
self.rpc_client = ccnet.CcnetThreadedRpcClient(
ccnet.ClientPool(ccnet_dir, central_config_dir=central_config_dir))
def get_db_email_users(self):
return self.rpc_client.get_emailusers('DB', 0, 1)
def create_admin(self, email, user):
return self.rpc_client.add_emailuser(email, user, 1, 1)
def need_create_admin():
users = rpc.get_db_email_users()
return len(users) == 0
def create_admin(email, passwd):
if rpc.create_admin(email, passwd) < 0:
raise Exception('failed to create admin')
else:
print '\n\n'
print '----------------------------------------'
print 'Successfully created seafile admin'
print '----------------------------------------'
print '\n\n'
def ask_admin_email():
print
print '----------------------------------------'
print 'It\'s the first time you start the seafile server. Now let\'s create the admin account'
print '----------------------------------------'
def validate(email):
# whitespace is not allowed
if re.match(r'[\s]', email):
raise InvalidAnswer('%s is not a valid email address' % Utils.highlight(email))
# must be a valid email address
if not re.match(r'^.+@.*\..+$', email):
raise InvalidAnswer('%s is not a valid email address' % Utils.highlight(email))
return email
key = 'admin email'
question = 'What is the ' + Utils.highlight('email') + ' for the admin account?'
return Utils.ask_question(question,
key=key,
validate=validate)
def ask_admin_password():
def validate(password):
key = 'admin password again'
question = 'Enter the ' + Utils.highlight('password again:')
password_again = Utils.ask_question(question,
key=key,
password=True)
if password_again != password:
raise InvalidAnswer('password mismatch')
return password
key = 'admin password'
question = 'What is the ' + Utils.highlight('password') + ' for the admin account?'
return Utils.ask_question(question,
key=key,
password=True,
validate=validate)
rpc = RPC()
def main():
if not need_create_admin():
return
password_file = os.path.join(os.environ['SEAFILE_CENTRAL_CONF_DIR'], 'admin.txt')
if os.path.exists(password_file):
with open(password_file, 'r') as fp:
pwinfo = json.load(fp)
email = pwinfo['email']
passwd = pwinfo['password']
os.unlink(password_file)
else:
email = ask_admin_email()
passwd = ask_admin_password()
create_admin(email, passwd)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print '\n\n\n'
print Utils.highlight('Aborted.')
print
sys.exit(1)
except Exception, e:
print
print Utils.highlight('Error happened during creating seafile admin.')
print

View file

@ -1,17 +1,18 @@
# coding: UTF-8
from __future__ import print_function
from ConfigParser import ConfigParser
from contextlib import contextmanager
import os
from os.path import abspath, basename, exists, dirname, join, isdir, expanduser
import platform
import sys
import termcolor
import subprocess
import logging
import logging.config
import click
import termcolor
import colorlog
from os.path import abspath, basename, exists, dirname, join, isdir, expanduser
from contextlib import contextmanager
logger = logging.getLogger('.utils')
@ -47,6 +48,7 @@ def call(*a, **kw):
dry_run = kw.pop('dry_run', False)
quiet = kw.pop('quiet', False)
cwd = kw.get('cwd', os.getcwd())
check_call = kw.pop('check_call', False)
reduct_args = kw.pop('reduct_args', [])
if not quiet:
toprint = a[0]
@ -58,6 +60,9 @@ def call(*a, **kw):
eprint('cwd: ', green(cwd))
kw.setdefault('shell', True)
if not dry_run:
if check_call:
return subprocess.check_call(*a, **kw)
else:
return subprocess.Popen(*a, **kw).wait()
@contextmanager
@ -204,3 +209,13 @@ def get_install_dir():
def get_script(script):
return join(get_install_dir(), script)
_config = None
def get_conf(key):
global _config
if _config is None:
_config = ConfigParser()
_config.read("/bootstrap/bootstrap.conf")
return _config.get("server", key)