#!/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 #################### ### command #################### def welcome(): '''Show welcome message when running the 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]) #################### ### 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()