1
0
mirror of https://github.com/haiwen/seafile-server.git synced 2025-04-27 11:10:49 +00:00
seafile-server/tools/seafile-admin
2019-10-22 19:53:25 -07:00

929 lines
26 KiB
Python
Executable File

#!/usr/bin/env python
# coding: UTF-8
'''This is the helper script to setup/manage your seafile server
'''
import sys
####################
### Requires Python 2.6+
####################
if sys.version_info.major == 3:
print 'Python 3 not supported yet. Quit now'
sys.exit(1)
if sys.version_info.minor < 6:
print 'Python 2.6 or above is required. Quit now'
sys.exit(1)
import os
import time
import re
import shutil
import subprocess
import argparse
import uuid
try:
import readline
# Avoid pylint 'unused import' warning
dummy = readline
except ImportError:
pass
####################
### Cosntants
####################
SERVER_MANUAL_HTTP = 'https://github.com/haiwen/seafile/wiki'
SEAFILE_GOOGLE_GROUP = 'https://groups.google.com/forum/?fromgroups#!forum/seafile'
SEAFILE_WEBSITE = 'http://www.seafile.com'
SEAHUB_DOWNLOAD_URL = 'https://seafile.com.cn/downloads/seahub-latest.tar.gz'
####################
### Global variables
####################
cwd = os.getcwd()
SCRIPT_NAME = os.path.basename(sys.argv[0])
PYTHON = sys.executable
conf = {}
CONF_CCNET_DIR = 'ccnet_dir'
CONF_SEAFILE_DIR = 'seafile_dir'
CONF_SEAHUB_DIR = 'seafile_dir'
CONF_SEAFILE_PORT = 'seafile_port'
CONF_FILESERVER_PORT = 'fileserver_port'
CONF_IP_OR_DOMAIN = 'ip_or_domain'
CONF_SEAHUB_CONF = 'seahub_conf'
CONF_SEAHUB_DIR = 'seahub_dir'
CONF_SEAHUB_PORT = 'seahub_port'
CONF_SEAHUB_PIDFILE = 'seahub_pidfile'
CONF_SEAHUB_OUTLOG = 'seahub_outlog'
CONF_SEAHUB_ERRLOG = 'seahub_errlog'
CONF_CCNET_CONF_EXISTS = 'ccnet_conf_exists'
CONF_SEAFILE_CONF_EXISTS = 'seafile_conf_exists'
CONF_ADMIN_EMAIL = 'admin_email'
CONF_ADMIN_PASSWORD = 'admin_password'
CONF_SEAFILE_CENTRAL_CONF_DIR = 'central_config_dir'
####################
### Common helper functions
def highlight(content):
'''Add ANSI color to content to get it highlighted on terminal'''
return '\x1b[33m%s\x1b[m' % content
def info(msg):
print msg
def error(msg):
print 'Error: ' + msg
sys.exit(1)
def ask_question(desc,
key=None,
note=None,
default=None,
validate=None,
yes_or_no=False,
invalidate_msg=None):
'''Ask a question, return the answer. The optional validate param is a
function used to validate the answer. If yes_or_no is True, then a boolean
value would be returned.
'''
assert key or yes_or_no
desc = highlight(desc)
if note:
desc += ' (%s)' % note
if default:
desc += '\n' + ('[default %s ]' % default)
else:
if yes_or_no:
desc += '\n[yes or no]'
else:
desc += '\n' + ('[%s ]' % key)
desc += ' '
while True:
answer = raw_input(desc)
if not answer:
if default:
print ''
return default
else:
continue
answer = answer.strip()
if yes_or_no:
if answer != 'yes' and answer != 'no':
print '\nPlease answer yes or no\n'
continue
else:
return answer == 'yes'
else:
if validate and not validate(answer):
if invalidate_msg:
print '\n%s\n' % invalidate_msg
else:
print '\n"%s" is not a valid %s\n' % (answer, key)
continue
print ''
return answer
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()
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()
def is_running(process):
'''Detect if there is a process with the given name running'''
argv = ['pgrep', '-f', process]
return run_argv(argv, suppress_stdout=True) == 0
def pkill(process):
'''Kill the program with the given name'''
argv = ['pkill', '-f', process]
run_argv(argv)
def kill(pid):
'''Kill the program with the given pid'''
argv = ['kill', pid]
run_argv(argv)
def must_mkdir(path):
'''Create a directory, exit on failure'''
try:
os.mkdir(path)
except OSError, e:
error('failed to create directory %s:%s' % (path, e))
### END of Common helper functions
####################
def check_seafile_install():
'''Check if seafile has been correctly built and installed in this
system
'''
dirs = os.environ['PATH'].split(':')
def exist_in_path(prog):
'''Test whether prog exists in system path'''
for d in dirs:
if d == '':
continue
path = os.path.join(d, prog)
if os.path.exists(path):
return True
return False
def check_prog(name):
if not exist_in_path(name):
error(
'%s not found in PATH. Have you built and installed seafile server?'
% name)
progs = [
'ccnet-init',
'seaf-server-init',
'seaf-server',
'ccnet-server',
'seafile-controller',
]
for prog in progs:
check_prog(prog)
def get_seahub_env():
'''And PYTHONPATH and CCNET_CONF_DIR/SEAFILE_CONF_DIR to env, which is
needed by seahub
'''
seahub_dir = conf[CONF_SEAHUB_DIR]
seahub_thirdpart_dir = os.path.join(seahub_dir, 'thirdpart')
env = dict(os.environ)
pypath = env.get('PYTHONPATH', '')
pathlist = [p for p in pypath.split(':') if p != '']
pathlist.append(seahub_thirdpart_dir)
newpypath = ':'.join(pathlist)
env['PYTHONPATH'] = newpypath
env['CCNET_CONF_DIR'] = conf[CONF_CCNET_DIR]
env['SEAFILE_CONF_DIR'] = conf[CONF_SEAFILE_DIR]
env['SEAFILE_CENTRAL_CONF_DIR'] = conf[CONF_SEAFILE_CENTRAL_CONF_DIR]
return env
####################
### <setup> command
####################
def welcome():
'''Show welcome message when running the <setup> command'''
welcome_msg = '''\
-----------------------------------------------------------------
This script will guide you to config and setup your seafile server.
Make sure you have read seafile server manual at
%s
Press [ENTER] to continue
-----------------------------------------------------------------
''' % SERVER_MANUAL_HTTP
print welcome_msg
raw_input()
def get_server_ip_or_domain():
def validate(s):
r = r'^[^.].+\..+[^.]$'
return bool(re.match(r, s))
question = 'What is the ip of the server?'
key = 'ip or domain'
note = 'For example: www.mycompany.com, 192.168.1.101'
conf[CONF_IP_OR_DOMAIN] = ask_question(question,
key=key,
note=note,
validate=validate)
def get_ccnet_conf_dir():
ccnet_conf_dir = os.path.join(cwd, 'ccnet')
if os.path.exists(ccnet_conf_dir):
question = 'It seems there already exists ccnet config files in %s, Do you want to use them?' % ccnet_conf_dir
yesno = ask_question(question, yes_or_no=True)
if not yesno:
print highlight(
'\nRemove the directory %s first, and run the script again.\n'
% ccnet_conf_dir)
sys.exit(1)
else:
conf[CONF_CCNET_CONF_EXISTS] = True
else:
conf[CONF_CCNET_CONF_EXISTS] = False
conf[CONF_CCNET_DIR] = ccnet_conf_dir
def get_seafile_port():
def validate(s):
try:
port = int(s)
except ValueError:
return False
return port > 0 and port < 65536
question = 'Which port do you want to use for the seafile server?'
key = 'seafile server port'
default = '12001'
conf[CONF_SEAFILE_PORT] = ask_question(question,
key=key,
default=default,
validate=validate)
def get_fileserver_port():
def validate(s):
try:
port = int(s)
except ValueError:
return False
return port > 0 and port < 65536
question = 'Which port do you want to use for the seafile fileserver?'
key = 'seafile fileserver port'
default = '8082'
conf[CONF_FILESERVER_PORT] = ask_question(question,
key=key,
default=default,
validate=validate)
def get_seafile_data_dir():
question = 'Where do you want to put your seafile data?'
key = 'seafile-data'
note = 'Please use a volume with enough free space'
default = os.path.join(cwd, 'seafile-data')
seafile_data_dir = ask_question(question,
key=key,
note=note,
default=default)
if os.path.exists(seafile_data_dir):
question = 'It seems there already exists seafile data in %s, Do you want to use them?' % seafile_data_dir
yesno = ask_question(question, yes_or_no=True)
if not yesno:
print highlight(
'\nRemove the directory %s first, and run the script again.\n'
% seafile_data_dir)
sys.exit(1)
else:
conf[CONF_SEAFILE_CONF_EXISTS] = True
else:
conf[CONF_SEAFILE_CONF_EXISTS] = False
conf[CONF_SEAFILE_DIR] = seafile_data_dir
def create_gunicorn_conf():
runtime_dir = os.path.join(cwd, 'seafile-server', 'runtime')
confpath = os.path.join(runtime_dir, 'seahub.conf')
if os.path.exists(confpath):
return
if not os.path.exists(runtime_dir):
must_mkdir(runtime_dir)
content = '''\
import os
daemon = True
workers = 3
# Logging
runtime_dir = os.path.dirname(__file__)
pidfile = os.path.join(runtime_dir, 'seahub.pid')
errorlog = os.path.join(runtime_dir, 'error.log')
accesslog = os.path.join(runtime_dir, 'access.log')
'''
try:
with open(confpath, 'w') as fp:
fp.write(content)
except:
error('Failed to write seahub config')
def gen_seahub_secret_key():
data = str(uuid.uuid4()) + str(uuid.uuid4())
return data[:40]
def create_seahub_settings_py():
seahub_settings_py = os.path.join(cwd, 'conf', 'seahub_settings.py')
try:
with open(seahub_settings_py, 'w') as fp:
line = "SECRET_KEY = '%s'" % gen_seahub_secret_key()
fp.write(line)
except Exception, e:
error('failed to create %s: %s' % (seahub_settings_py, e))
def move_avatar():
seahub_data_dir = os.path.join(cwd, 'seahub-data')
outside_avatar_dir = os.path.join(seahub_data_dir, 'avatars')
seahub_avatar_dir = os.path.join(conf[CONF_SEAHUB_DIR], 'media', 'avatars')
if os.path.exists(outside_avatar_dir):
return
if not os.path.exists(seahub_data_dir):
must_mkdir(seahub_data_dir)
# move the avatars dir outside
shutil.move(seahub_avatar_dir, outside_avatar_dir)
# make the the original avatars dir a symlink pointing to the outside dir
os.symlink(outside_avatar_dir, seahub_avatar_dir)
def init_seahub():
seahub_dir = conf[CONF_SEAHUB_DIR]
# create seahub_settings.py
create_seahub_settings_py()
argv = [PYTHON, 'manage.py', 'syncdb']
# Set proper PYTHONPATH before run django syncdb command
env = get_seahub_env()
print
print
info('Now initializing seahub database, please wait...')
print
if run_argv(argv, cwd=seahub_dir, env=env) != 0:
error('Seahub syncdb failed')
info('done')
move_avatar()
create_gunicorn_conf()
def check_django_version():
'''Requires django 1.8'''
import django
if django.VERSION[0] != 1 or django.VERSION[1] != 8:
error('Django 1.8 is required')
del django
def check_python_module(import_name, package_name=None, silent=False):
package_name = package_name or import_name
if not silent:
info('checking %s' % package_name)
try:
__import__(import_name)
except ImportError:
error('Python module "%s" not found. Please install it first' %
package_name)
def check_python_dependencies(silent=False):
'''Ensure all python libraries we need are installed'''
if not silent:
info('check python modules ...')
check_django_version()
def check(*a, **kw):
kw.setdefault('silent', silent)
check_python_module(*a, **kw)
pkgs = [
'sqlite3',
'chardet',
'six',
'pytz',
'rest_framework',
'compressor',
'statici18n',
'jsonfield',
'dateutil',
'constance',
'openpyxl',
] # yapf: disable
for pkg in pkgs:
check(pkg)
check('PIL', 'python imaging library(PIL)')
print
def config_ccnet_seafile():
get_ccnet_conf_dir()
if not conf[CONF_CCNET_CONF_EXISTS]:
get_server_ip_or_domain()
get_seafile_data_dir()
if not conf[CONF_SEAFILE_CONF_EXISTS]:
get_seafile_port()
get_fileserver_port()
info('This is your configuration')
info('------------------------------------------')
if conf[CONF_CCNET_CONF_EXISTS]:
info('ccnet config: use existing config in %s' %
highlight(conf[CONF_CCNET_DIR]))
else:
info('ccnet conf dir: %s' % highlight(conf[CONF_CCNET_DIR]))
info('server host: %s' %
highlight(conf[CONF_IP_OR_DOMAIN]))
if conf[CONF_SEAFILE_CONF_EXISTS]:
info('seafile: use existing config in %s' %
highlight(conf[CONF_SEAFILE_DIR]))
else:
info('seafile data dir: %s' %
highlight(conf[CONF_SEAFILE_DIR]))
info('seafile port: %s' %
highlight(conf[CONF_SEAFILE_PORT]))
info('seafile fileserver port: %s' %
highlight(conf[CONF_FILESERVER_PORT]))
info('------------------------------------------')
info('Press ENTER if the config is right, or anything else to re-config ')
if raw_input() != '':
config_ccnet_seafile()
else:
return
def init_ccnet_seafile():
if not conf[CONF_CCNET_CONF_EXISTS]:
info('Generating ccnet configuration...')
argv = [
'ccnet-init',
'-F',
conf[CONF_SEAFILE_CENTRAL_CONF_DIR],
'-c',
conf[CONF_CCNET_DIR],
'--host',
conf[CONF_IP_OR_DOMAIN],
]
if run_argv(argv) != 0:
error('failed to init ccnet configuration')
info('done')
if not conf[CONF_SEAFILE_CONF_EXISTS]:
info('Generating seafile configuration...')
argv = [
'seaf-server-init',
'-F',
conf[CONF_SEAFILE_CENTRAL_CONF_DIR],
'--seafile-dir',
conf[CONF_SEAFILE_DIR],
'--port',
conf[CONF_SEAFILE_PORT],
'--fileserver-port',
conf[CONF_FILESERVER_PORT],
]
if run_argv(argv) != 0:
error('failed to init seafile configuration')
info('done')
seafile_ini = os.path.join(conf[CONF_CCNET_DIR], 'seafile.ini')
with open(seafile_ini, 'w') as fp:
fp.write(conf[CONF_SEAFILE_DIR])
####################
### <start> command
####################
def start_controller():
argv = [
'seafile-controller',
'-c',
conf[CONF_CCNET_DIR],
'-d',
conf[CONF_SEAFILE_DIR],
'-F',
conf[CONF_SEAFILE_CENTRAL_CONF_DIR],
]
info('Starting seafile-server...')
if run_argv(argv) != 0:
error('Failed to start seafile')
# check again after several seconds
time.sleep(10)
if not is_running('seafile-controller'):
error('Failed to start seafile')
def start_seahub_gunicorn():
argv = [
'gunicorn',
'seahub.wsgi:application',
'-c',
conf[CONF_SEAHUB_CONF],
'-b',
'0.0.0.0:%s' % conf[CONF_SEAHUB_PORT],
]
info('Starting seahub...')
env = get_seahub_env()
if run_argv(argv, cwd=conf[CONF_SEAHUB_DIR], env=env) != 0:
error('Failed to start seahub')
info('Seahub running on port %s' % conf[CONF_SEAHUB_PORT])
def start_seahub_fastcgi():
info('Starting seahub in fastcgi mode...')
argv = [
PYTHON,
'manage.py',
'runfcgi',
'host=%(host)s',
'port=%(port)s',
'pidfile=%(pidfile)s',
'outlog=%(outlog)s',
'errlog=%(errlog)s',
]
host = os.environ.get('SEAFILE_FASTCGI_HOST', '127.0.0.1')
cmdline = ' '.join(argv) % \
dict(host=host,
port=conf[CONF_SEAHUB_PORT],
pidfile=conf[CONF_SEAHUB_PIDFILE],
outlog=conf[CONF_SEAHUB_OUTLOG],
errlog=conf[CONF_SEAHUB_ERRLOG])
env = get_seahub_env()
if run(cmdline, cwd=conf[CONF_SEAHUB_DIR], env=env) != 0:
error('Failed to start seahub in fastcgi mode')
info('Seahub running on port %s (fastcgi)' % conf[CONF_SEAHUB_PORT])
def read_seafile_data_dir(ccnet_conf_dir):
'''Read the location of seafile-data from seafile.ini, also consider the
upgrade from older version which do not has the seafile.ini feature
'''
seafile_ini = os.path.join(ccnet_conf_dir, 'seafile.ini')
if os.path.exists(seafile_ini):
with open(seafile_ini, 'r') as fp:
seafile_data_dir = fp.read().strip()
else:
# In previous seafile-admin, seafiled-data folder must be under
# the top level directory, so we do not store the location of
# seafile-data folder in seafile.ini
seafile_data_dir = os.path.join(cwd, 'seafile-data')
if os.path.exists(seafile_data_dir):
with open(seafile_ini, 'w') as fp:
fp.write(seafile_data_dir)
return seafile_data_dir
def check_layout(args):
def error_not_found(path):
error('%s not found' % path)
ccnet_conf_dir = os.path.join(cwd, 'ccnet')
if not os.path.exists(ccnet_conf_dir):
error_not_found(ccnet_conf_dir)
central_config_dir = os.path.join(cwd, 'conf')
ccnet_conf = os.path.join(central_config_dir, 'ccnet.conf')
if not os.path.exists(ccnet_conf):
error_not_found(ccnet_conf)
seafile_data_dir = read_seafile_data_dir(ccnet_conf_dir)
if not os.path.exists(seafile_data_dir):
error_not_found(seafile_data_dir)
seafile_conf = os.path.join(central_config_dir, 'seafile.conf')
if not os.path.exists(seafile_conf):
error_not_found(seafile_conf)
runtime_dir = os.path.join(cwd, 'seafile-server', 'runtime')
seahub_conf = os.path.join(runtime_dir, 'seahub.conf')
if not os.path.exists(seahub_conf):
error_not_found(seahub_conf)
seahub_dir = os.path.join(cwd, 'seafile-server', 'seahub')
if not os.path.exists(seahub_conf):
error_not_found(seahub_dir)
conf[CONF_SEAFILE_CENTRAL_CONF_DIR] = central_config_dir
conf[CONF_CCNET_DIR] = ccnet_conf_dir
conf[CONF_SEAFILE_DIR] = seafile_data_dir
conf[CONF_SEAHUB_DIR] = seahub_dir
conf[CONF_SEAHUB_CONF] = seahub_conf
conf[CONF_SEAHUB_PIDFILE] = os.path.join(runtime_dir, 'seahub.pid')
conf[CONF_SEAHUB_OUTLOG] = os.path.join(runtime_dir, 'access.log')
conf[CONF_SEAHUB_ERRLOG] = os.path.join(runtime_dir, 'error.log')
def check_config(args):
check_layout(args)
try:
port = int(args.port)
except ValueError:
error('invalid port: %s' % args.port)
else:
if port <= 0 or port > 65535:
error('invalid port: %s' % args.port)
conf[CONF_SEAHUB_PORT] = port
def check_directory_layout():
seaf_server_dir = os.path.join(cwd, 'seafile-server')
if not os.path.exists(seaf_server_dir):
error(
'"seafile-server/" not found in current directory. \nPlease run seafile-admin in the correct directory.')
seahub_dir = os.path.join(seaf_server_dir, 'seahub')
if not os.path.exists(seahub_dir):
error(
'"seafile-server/seahub/" not found. \nPlease download seahub first.')
conf[CONF_SEAHUB_DIR] = seahub_dir
def setup_seafile(args):
# avoid pylint "unused variable" warning
dummy = args
welcome()
check_python_dependencies()
conf[CONF_SEAFILE_CENTRAL_CONF_DIR] = os.path.join(cwd, 'conf')
config_ccnet_seafile()
init_ccnet_seafile()
init_seahub()
print
print '-----------------------------------------------------------------'
print '-----------------------------------------------------------------'
print 'Your seafile server configuration has been finished successfully.'
print '-----------------------------------------------------------------'
print '-----------------------------------------------------------------'
print
print 'To start/stop seafile server:'
print
print highlight(' $ cd %s' % cwd)
print highlight(' $ %s { start | stop }' % SCRIPT_NAME)
print
print 'If you have any problem, refer to\n'
print
print ' Seafile server manual: %s' % SERVER_MANUAL_HTTP
print
print ' Seafile discussion group: %s' % SEAFILE_GOOGLE_GROUP
print
print ' Seafile website: %s' % SEAFILE_WEBSITE
print
print 'for more information.'
print
def check_necessary_files():
files = [
os.path.join(cwd, 'conf', 'ccnet.conf'),
os.path.join(cwd, 'seafile-server', 'runtime', 'seahub.conf'),
os.path.join(cwd, 'seahub.db'),
os.path.join(cwd, 'conf', 'seahub_settings.py'),
]
for fpath in files:
if not os.path.exists(fpath):
error('%s not found' % fpath)
def start_seafile(args):
'''start ccnet/seafile/seahub/fileserver'''
if is_running('seafile-controller'):
error(highlight('NOTE: Seafile is already running'))
check_python_dependencies(silent=True)
if args.fastcgi:
check_python_module('flup', 'flup', silent=True)
else:
check_python_module('gunicorn', 'gunicorn', silent=True)
check_necessary_files()
check_config(args)
start_controller()
if args.port:
try:
port = int(args.port)
except ValueError:
error('invalid port: %s' % args.port)
else:
if port <= 0 or port > 65535:
error('invalid port: %s' % args.port)
if args.fastcgi:
start_seahub_fastcgi()
else:
start_seahub_gunicorn()
info('Done')
def stop_seafile(dummy):
info('Stopping seafile server')
pkill('seafile-controller')
runtime_dir = os.path.join(cwd, 'seafile-server', 'runtime')
pidfile = os.path.join(runtime_dir, 'seahub.pid')
try:
with open(pidfile, 'r') as fp:
pid = fp.read().strip('\n ')
if pid:
kill(pid)
except:
pass
info('done')
def reset_admin(args):
'''reset seafile admin account'''
check_layout(args)
env = get_seahub_env()
argv = [PYTHON, 'manage.py', 'createsuperuser']
env = get_seahub_env()
seahub_dir = conf[CONF_SEAHUB_DIR]
run_argv(argv, cwd=seahub_dir, env=env)
def main():
check_seafile_install()
check_directory_layout()
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='subcommands', description='')
parser_setup = subparsers.add_parser('setup',
help='setup the seafile server')
parser_setup.set_defaults(func=setup_seafile)
parser_start = subparsers.add_parser('start',
help='start the seafile server')
parser_start.set_defaults(func=start_seafile)
parser_start.add_argument('--fastcgi',
help='start seahub in fastcgi mode',
action='store_true')
parser_start.add_argument('--port',
help='start seahub in fastcgi mode',
default='8000')
parser_stop = subparsers.add_parser('stop', help='stop the seafile server')
parser_stop.set_defaults(func=stop_seafile)
parser_reset_admin = subparsers.add_parser(
'reset-admin',
help='reset seafile admin account')
parser_reset_admin.set_defaults(func=reset_admin)
parser_create_admin = subparsers.add_parser(
'create-admin',
help='create seafile admin account')
parser_create_admin.set_defaults(func=reset_admin)
args = parser.parse_args()
args.func(args)
if __name__ == '__main__':
main()