diff --git a/apps/__init__.py b/apps/__init__.py index 5c66b357e..6110e1346 100644 --- a/apps/__init__.py +++ b/apps/__init__.py @@ -2,4 +2,4 @@ # -*- coding: utf-8 -*- # -__version__ = "0.5.0" +__version__ = "1.0.0" diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py index 24807ca08..487d2714b 100644 --- a/apps/assets/forms/user.py +++ b/apps/assets/forms/user.py @@ -114,22 +114,15 @@ class SystemUserForm(PasswordAndKeyAuthForm): fields = [ 'name', 'username', 'protocol', 'auto_generate_key', 'password', 'private_key_file', 'auto_push', 'sudo', - 'comment', 'shell', 'nodes', 'priority', + 'comment', 'shell', 'priority', ] widgets = { 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), - 'nodes': forms.SelectMultiple( - attrs={ - 'class': 'select2', - 'data-placeholder': _('Nodes') - } - ), } help_texts = { 'name': '* required', 'username': '* required', - 'nodes': _('If auto push checked, system user will be create at node assets'), 'auto_push': _('Auto push system user to asset'), 'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'), } \ No newline at end of file diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index da1b0be58..3841b34bb 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -655,6 +655,7 @@ $(document).ready(function(){ default: break; } + $(".ipt_check_all").prop("checked", false) }); diff --git a/docs/step_by_step.rst b/docs/step_by_step.rst index 6549b4bde..484013e1b 100644 --- a/docs/step_by_step.rst +++ b/docs/step_by_step.rst @@ -224,7 +224,7 @@ Luna 已改为纯前端,需要 Nginx 来运行访问 # 注意:这里一定要改写一下本机的IP地址, 否则会出错 - docker run -d \ + docker run --name jms_guacamole -d \ -p 8081:8080 -v /opt/guacamole/key:/config/guacamole/key \ -e JUMPSERVER_KEY_DIR=/config/guacamole/key \ -e JUMPSERVER_SERVER=http://<填写本机的IP地址>:8080 \ diff --git a/docs/upgrade.rst b/docs/upgrade.rst index 2a11a6288..2b423dfd5 100644 --- a/docs/upgrade.rst +++ b/docs/upgrade.rst @@ -1,5 +1,5 @@ -升级 ----- +更新升级 +------------- 1. 升级 Jumpserver @@ -22,8 +22,121 @@ :: $ docker pull registry.jumpserver.org/public/guacamole:latest - $ docker stop - $ docker run -d \ - -p 8081:8080 \ + $ docker stop jms_guacamole # 或者写guacamole的容器ID + $ docker run --name jms_guacamole -d \ + -p 8081:8080 -v /opt/guacamole/key:/config/guacamole/key \ + -e JUMPSERVER_KEY_DIR=/config/guacamole/key \ -e JUMPSERVER_SERVER=http://<填写本机的IP地址>:8080 \ registry.jumpserver.org/public/guacamole:latest + + +切换分支或离线升级 +------------------------------- + + +**Jumpserver** + +说明: 以下操作,都在jumpserver所在目录运行 + +1. 备份配置文件 + +:: + + $ jumpserver_backup=/tmp/jumpserver_backup + $ mkdir -p $jumpserver_backup + $ cp config.py $jumpserver_backup + +2. 备份migrations migrations中存的是数据库表结构的变更,切换分支会丢失 + +:: + + $ for app in common users assets ops perms terminal;do + mkdir -p $jumpserver_backup/${app}_migrations + cp apps/${app}/migrations/*.py $jumpserver_backup/${app}_migrations + done + + +3. 备份数据库,已被不时之需 + +:: + + $ mysqldump -u你的数据库账号 -h数据库地址 -p 数据库名称 > $jumpserver_backup/db_backup.sql + +4. 备份录像文件 + +:: + + $ cp -r data/media $jumpserver_backup/ + +5. 切换分支或下载离线包, 更新代码 + +:: + + $ git checkout master # or other branch + + +6. 还原配置文件 + +:: + + $ cp $jumpserver_backup/config.py . + +7. 还原数据库表结构记录 + +:: + + $ for app in common users assets ops perms terminal;do + cp $jumpserver_backup/${app}_migrations/*.py ${app}/migrations/ + done + +8. 还原录像文件 + +:: + + $ cp -r $jumpserver_backup/media/* data/media/ + +9. 更新依赖或表结构 + +:: + $ pip install -r requirements/requirements.txt && cd utils && sh make_migrations.sh + + +**Coco** + +说明: 以下操作都在 coco 项目所在目录 + +coco是无状态的,备份 keys 目录即可 + +1. 备份keys + +:: + + $ cp -r keys $jumpserver_backup/ + + +2. 离线更新升级coco + +3. 还原 keys目录 + +:: + + $ mv keys keys_backup + $ cp -r $jumpserver_backup/keys . + +4. 升级依赖 + +:: + + $ git pull && cd requirements && pip install -r requirements.txt + + +**Luna** + +直接下载最新Release包替换即可 + + +**Guacamole** + +直接参考上面的升级即可, 需要注意的是如果更换机器,请备份 + + diff --git a/jms b/jms new file mode 100755 index 000000000..08505cc17 --- /dev/null +++ b/jms @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +# coding: utf-8 + +import os +import subprocess +import threading +import time +import argparse +import sys +import signal + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(BASE_DIR) + +from apps import __version__ + +try: + from config import config as CONFIG +except ImportError: + print("Could not find config file, `cp config_example.py config.py`") + sys.exit(1) + +os.environ["PYTHONIOENCODING"] = "UTF-8" +APPS_DIR = os.path.join(BASE_DIR, 'apps') +LOG_DIR = os.path.join(BASE_DIR, 'logs') +TMP_DIR = os.path.join(BASE_DIR, 'tmp') +HTTP_HOST = CONFIG.HTTP_BIND_HOST or '127.0.0.1' +HTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080 +DEBUG = CONFIG.DEBUG +LOG_LEVEL = CONFIG.LOG_LEVEL + +START_TIMEOUT = 15 +WORKERS = 4 +DAEMON = False + +EXIT_EVENT = threading.Event() +all_services = ['gunicorn', 'celery', 'beat'] + +try: + os.makedirs(os.path.join(BASE_DIR, "data", "static")) + os.makedirs(os.path.join(BASE_DIR, "data", "media")) +except: + pass + + +def make_migrations(): + print("Check database structure change ...") + os.chdir(os.path.join(BASE_DIR, 'apps')) + subprocess.call('python3 manage.py migrate', shell=True) + + +def collect_static(): + print("Collect static files") + os.chdir(os.path.join(BASE_DIR, 'apps')) + subprocess.call('python3 manage.py collectstatic --no-input', shell=True) + + +def prepare(): + make_migrations() + collect_static() + + +def check_pid(pid): + """ Check For the existence of a unix pid. """ + try: + os.kill(pid, 0) + except OSError: + return False + else: + return True + + +def get_pid_file_path(service): + return os.path.join(TMP_DIR, '{}.pid'.format(service)) + + +def get_log_file_path(service): + return os.path.join(LOG_DIR, '{}.log'.format(service)) + + +def get_pid(service): + pid_file = get_pid_file_path(service) + if os.path.isfile(pid_file): + with open(pid_file) as f: + return int(f.read().strip()) + return 0 + + +def is_running(s, unlink=True): + pid_file = get_pid_file_path(s) + + if os.path.isfile(pid_file): + with open(pid_file, 'r') as f: + pid = get_pid(s) + if check_pid(pid): + return True + + if unlink: + os.unlink(pid_file) + return False + + +def parse_service(s): + if s == 'all': + return all_services + else: + return [s] + + +def start_gunicorn(): + print("\n- Start Gunicorn WSGI HTTP Server") + prepare() + service = 'gunicorn' + bind = '{}:{}'.format(HTTP_HOST, HTTP_PORT) + log_format = '%(h)s %(t)s "%(r)s" %(s)s %(b)s ' + pid_file = get_pid_file_path(service) + log_file = get_log_file_path(service) + + cmd = [ + 'gunicorn', 'jumpserver.wsgi', + '-b', bind, + '-w', str(WORKERS), + '--access-logformat', log_format, + '-p', pid_file, + ] + + if DAEMON: + cmd.extend([ + '--access-logfile', log_file, + '--daemon', + ]) + else: + cmd.extend([ + '--access-logfile', '-' + ]) + if DEBUG: + cmd.append('--reload') + p = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, cwd=APPS_DIR) + return p + + +def start_celery(): + print("\n- Start Celery as Distributed Task Queue") + # Todo: Must set this environment, otherwise not no ansible result return + os.environ.setdefault('PYTHONOPTIMIZE', '1') + + if os.getuid() == 0: + os.environ.setdefault('C_FORCE_ROOT', '1') + + service = 'celery' + pid_file = get_pid_file_path(service) + + cmd = [ + 'celery', 'worker', + '-A', 'common', + '-l', LOG_LEVEL.lower(), + '--pidfile', pid_file, + '-c', str(WORKERS), + ] + if DAEMON: + cmd.extend([ + '--logfile', os.path.join(LOG_DIR, 'celery.log'), + '--detach', + ]) + p = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, cwd=APPS_DIR) + return p + + +def start_beat(): + print("\n- Start Beat as Periodic Task Scheduler") + + pid_file = get_pid_file_path('beat') + log_file = get_log_file_path('beat') + + os.environ.setdefault('PYTHONOPTIMIZE', '1') + if os.getuid() == 0: + os.environ.setdefault('C_FORCE_ROOT', '1') + + scheduler = "django_celery_beat.schedulers:DatabaseScheduler" + cmd = [ + 'celery', 'beat', + '-A', 'common', + '--pidfile', pid_file, + '-l', LOG_LEVEL, + '--scheduler', scheduler, + '--max-interval', '60' + ] + if DAEMON: + cmd.extend([ + '--logfile', log_file, + '--detach', + ]) + p = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, cwd=APPS_DIR) + return p + + +def start_service(s): + print(time.ctime()) + print('Jumpserver version {}, more see https://www.jumpserver.org'.format( + __version__)) + + services_handler = { + "gunicorn": start_gunicorn, + "celery": start_celery, + "beat": start_beat + } + + services_set = parse_service(s) + processes = [] + for i in services_set: + if is_running(i): + show_service_status(i) + continue + func = services_handler.get(i) + p = func() + processes.append(p) + + now = int(time.time()) + for i in services_set: + while not is_running(i): + if int(time.time()) - now < START_TIMEOUT: + time.sleep(1) + continue + else: + print("Error: {} start error".format(i)) + stop_multi_services(services_set) + return + + stop_event = threading.Event() + + if not DAEMON: + signal.signal(signal.SIGTERM, lambda x, y: stop_event.set()) + while not stop_event.is_set(): + try: + time.sleep(10) + except KeyboardInterrupt: + stop_event.set() + break + + print("Stop services") + for p in processes: + p.terminate() + + for i in services_set: + stop_service(i) + else: + print() + show_service_status(s) + + +def stop_service(s, sig=15): + services_set = parse_service(s) + for s in services_set: + if not is_running(s): + show_service_status(s) + continue + print("Stop service: {}".format(s)) + pid = get_pid(s) + os.kill(pid, sig) + + +def stop_multi_services(services): + for s in services: + stop_service(s, sig=9) + + +def stop_service_force(s): + stop_service(s, sig=9) + + +def show_service_status(s): + services_set = parse_service(s) + for ns in services_set: + if is_running(ns): + pid = get_pid(ns) + print("{} is running: {}".format(ns, pid)) + else: + print("{} is stopped".format(ns)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=""" + Jumpserver service control tools; + + Example: \r\n + + %(prog)s start all -d; + """ + ) + parser.add_argument( + 'action', type=str, + choices=("start", "stop", "restart", "status"), + help="Action to run" + ) + parser.add_argument( + "service", type=str, default="all", nargs="?", + choices=("all", "gunicorn", "celery", "beat"), + help="The service to start", + ) + parser.add_argument('-d', '--daemon', nargs="?", const=1) + parser.add_argument('-w', '--worker', type=int, nargs="?", const=4) + args = parser.parse_args() + if args.daemon: + DAEMON = True + + if args.worker: + WORKERS = args.worker + + action = args.action + srv = args.service + + if action == "start": + start_service(srv) + elif action == "stop": + stop_service(srv) + elif action == "restart": + DAEMON = True + stop_service(srv) + time.sleep(5) + start_service(srv) + else: + show_service_status(srv) diff --git a/run_server.py b/run_server.py index 011453a0c..c7ec7bccb 100644 --- a/run_server.py +++ b/run_server.py @@ -1,158 +1,11 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # coding: utf-8 -import os -import subprocess -import threading -import time -import argparse import sys - -from apps import __version__ - -try: - from config import config as CONFIG -except ImportError: - CONFIG = type('_', (), {'__getattr__': lambda *arg: None})() - -os.environ["PYTHONIOENCODING"] = "UTF-8" - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -APPS_DIR = os.path.join(BASE_DIR, 'apps') -HTTP_HOST = CONFIG.HTTP_BIND_HOST or '127.0.0.1' -HTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080 -DEBUG = CONFIG.DEBUG -LOG_LEVEL = CONFIG.LOG_LEVEL -WORKERS = 4 - -EXIT_EVENT = threading.Event() -processes = {} - -try: - os.makedirs(os.path.join(BASE_DIR, "data", "static")) - os.makedirs(os.path.join(BASE_DIR, "data", "media")) -except: - pass - - -def make_migrations(): - print("Check database change, make migrations") - os.chdir(os.path.join(BASE_DIR, 'apps')) - subprocess.call('python manage.py migrate', shell=True) - - -def collect_static(): - print("Collect static files") - os.chdir(os.path.join(BASE_DIR, 'apps')) - subprocess.call('python manage.py collectstatic --no-input', shell=True) - - -def start_gunicorn(): - print("- Start Gunicorn WSGI HTTP Server") - make_migrations() - collect_static() - os.chdir(APPS_DIR) - cmd = "gunicorn jumpserver.wsgi -b {}:{} -w {} ".format( - HTTP_HOST, HTTP_PORT, WORKERS - ) - log_format = '%(h)s %(t)s "%(r)s" %(s)s %(b)s ' - log = " --access-logfile - --access-logformat '{}' ".format(log_format) - cmd += log - if DEBUG: - cmd += " --reload" - p = subprocess.Popen(cmd, shell=True, stdout=sys.stdout, stderr=sys.stderr) - return p - - -def start_celery(): - print("- Start Celery as Distributed Task Queue") - os.chdir(APPS_DIR) - # Todo: Must set this environment, otherwise not no ansible result return - os.environ.setdefault('PYTHONOPTIMIZE', '1') - - cmd = """ - export C_FORCE_ROOT=1; - celery -A common worker -l {} - """.format(LOG_LEVEL.lower()) - - p = subprocess.Popen(cmd, shell=True, stdout=sys.stdout, stderr=sys.stderr) - return p - - -def start_beat(): - print("- Start Beat as Periodic Task Scheduler") - os.chdir(APPS_DIR) - os.environ.setdefault('PYTHONOPTIMIZE', '1') - os.environ.setdefault('C_FORCE_ROOT', '1') - pidfile = '/tmp/beat.pid' - if os.path.exists(pidfile): - print("Beat pid file `{}` exist, remove it".format(pidfile)) - os.unlink(pidfile) - time.sleep(0.5) - - if os.path.exists(pidfile): - print("Beat pid file `{}` exist yet, may be something wrong".format(pidfile)) - os.unlink(pidfile) - time.sleep(0.5) - - scheduler = "django_celery_beat.schedulers:DatabaseScheduler" - options = "--pidfile {} -l {} --scheduler {} --max-interval 60".format( - pidfile, LOG_LEVEL, scheduler, - ) - cmd = 'celery -A common beat {} '.format(options) - p = subprocess.Popen(cmd, shell=True, stdout=sys.stdout, stderr=sys.stderr) - return p - - -def start_service(services): - print(time.ctime()) - print('Jumpserver version {}, more see https://www.jumpserver.org'.format( - __version__)) - print('Quit the server with CONTROL-C.') - - services_all = { - "gunicorn": start_gunicorn, - "celery": start_celery, - "beat": start_beat - } - - if 'all' in services: - for name, func in services_all.items(): - processes[name] = func() - else: - for name in services: - func = services_all.get(name) - processes[name] = func() - - stop_event = threading.Event() - while not stop_event.is_set(): - for name, proc in processes.items(): - if proc.poll() is not None: - print("\n\n" + "####"*10 + " ERROR OCCUR " + "####"*10) - print("Start service {} [FAILED]".format(name)) - for _, p in processes.items(): - p.terminate() - stop_event.set() - print("Exited".format(name)) - break - time.sleep(5) - - -def stop_service(): - for name, proc in processes.items(): - print("Stop service {}".format(name)) - proc.terminate() - - if os.path.exists("/tmp/beat.pid"): - os.unlink('/tmp/beat.pid') +import subprocess if __name__ == '__main__': - parser = argparse.ArgumentParser(description="Jumpserver start tools") - parser.add_argument("services", type=str, nargs='+', default="all", - choices=("all", "gunicorn", "celery", "beat"), - help="The service to start", - ) - args = parser.parse_args() - start_service(args.services) + subprocess.call('python3 jms start all', shell=True, + stdin=sys.stdin, stdout=sys.stdout) diff --git a/utils/build_docker.sh b/utils/build_docker.sh deleted file mode 100755 index 0c8411498..000000000 --- a/utils/build_docker.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -# - -cd .. -docker build -t jumpserver/jumpserver:v0.4.0-beta1 . diff --git a/utils/export_init_data.sh b/utils/export_init_data.sh deleted file mode 100644 index f305475e1..000000000 --- a/utils/export_init_data.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# - -python ../apps/manage.py shell << EOF -from users.models import * -init_model() - -from assets.models import * -init_model() - -EOF - -python ../apps/manage.py dbshell << EOF -delete from auth_permission; -delete from django_content_type; -EOF - -python ../apps/manage.py dumpdata > ../apps/fixtures/init.json diff --git a/utils/init_db.sh b/utils/init_db.sh deleted file mode 100755 index 4073ad001..000000000 --- a/utils/init_db.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -# - -python ../apps/manage.py loaddata init diff --git a/utils/make_migrations.sh b/utils/make_migrations.sh index 0704b9cc9..fdf1f6efb 100755 --- a/utils/make_migrations.sh +++ b/utils/make_migrations.sh @@ -1,6 +1,6 @@ #!/bin/bash # -python ../apps/manage.py makemigrations +python3 ../apps/manage.py makemigrations -python ../apps/manage.py migrate +python3 ../apps/manage.py migrate diff --git a/utils/run_docker.sh b/utils/run_docker.sh deleted file mode 100755 index ff1869a48..000000000 --- a/utils/run_docker.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -# - - -# Run redis -docker run --name redis -d redis - -# Run jumpserver -docker run -d --name jumpserver -p 8080:8080 --link redis:redis jumpserver/jumpserver:v0.4.0-beta1 - -# Finished -echo -e "Please visit http://ServerIP:8080\n Username: admin\nPassword: admin\n" \ No newline at end of file