|
|
@@ -0,0 +1,300 @@
|
|
|
+---
|
|
|
+title: "Fabfile"
|
|
|
+date: 2024-01-04T14:21:55+07:00
|
|
|
+draft: false
|
|
|
+---
|
|
|
+
|
|
|
+## 工程部署一般腳本
|
|
|
+
|
|
|
+### Install
|
|
|
+
|
|
|
+```shell
|
|
|
+apt install python3
|
|
|
+apt install python3-pip
|
|
|
+pip install fabric
|
|
|
+touch fabfile.py
|
|
|
+```
|
|
|
+
|
|
|
+### fabfile.py
|
|
|
+
|
|
|
+```python
|
|
|
+# BsGRaUtSYaX5
|
|
|
+# 206.238.113.33
|
|
|
+import os
|
|
|
+import re
|
|
|
+import abc
|
|
|
+import shutil
|
|
|
+import platform
|
|
|
+import requests
|
|
|
+import subprocess
|
|
|
+
|
|
|
+from datetime import datetime
|
|
|
+
|
|
|
+from fabric import task, Connection
|
|
|
+
|
|
|
+
|
|
|
+'''
|
|
|
+部署工具
|
|
|
+部署时,受限于PHP本身。自动生成 version 文件,记录版本信息。
|
|
|
+'''
|
|
|
+
|
|
|
+
|
|
|
+class Tar:
|
|
|
+ @classmethod
|
|
|
+ def compress(cls, args):
|
|
|
+ pass
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def uncompress(cls, args):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class Zip:
|
|
|
+ @classmethod
|
|
|
+ def compress(cls, args):
|
|
|
+ pass
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def uncompress(cls, args):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class MessengerNull:
|
|
|
+ '''
|
|
|
+ No need to do more complicated implements.
|
|
|
+ I simply use Duck-Type.
|
|
|
+ '''
|
|
|
+ @classmethod
|
|
|
+ def send(cls, msg=''):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class MessengerTg:
|
|
|
+ @classmethod
|
|
|
+ def send(cls, msg=''):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class Remote:
|
|
|
+ """
|
|
|
+ Remote
|
|
|
+
|
|
|
+ Server where my project deployed. An instance refers a remote server.
|
|
|
+
|
|
|
+ NOTE: I leave it's defination here for simplicity, so no need to maintain multi-files.
|
|
|
+ """
|
|
|
+ def __init__(self, host, *, name='服务器', branch='', port=22, user='root', password='',
|
|
|
+ user_group='www:www', pem='', deploy_path='/home', domain='', backup=False, tgmsg=False) -> None:
|
|
|
+ '''
|
|
|
+ Most people cares nothing, so do i.
|
|
|
+ '''
|
|
|
+ self.host = host
|
|
|
+ self.name = name
|
|
|
+ self.branch = branch # MUST run in specified git branch
|
|
|
+ self.port = port
|
|
|
+ self.user = user
|
|
|
+ self.password = password
|
|
|
+ self.user_group = user_group
|
|
|
+ self.pem = pem
|
|
|
+ self.deploy_path = deploy_path # install path
|
|
|
+ self.domain = domain #
|
|
|
+ self.backup = backup # if backup before substitution
|
|
|
+ self.tgmsg = tgmsg # deliver message to telegram
|
|
|
+
|
|
|
+
|
|
|
+''' <System Configures> '''
|
|
|
+# 要部署的文件/目录列表
|
|
|
+ITEMS = ('app', )
|
|
|
+
|
|
|
+# 部署是压缩上传。这个是压缩上传时压缩包文件
|
|
|
+FILE_TAR = './admin_api.tar.gz'
|
|
|
+
|
|
|
+# 上传位置 (注意不是部署目录,远程服务器必须首先接收文件)
|
|
|
+UPLOAD_PATH = '~'
|
|
|
+
|
|
|
+# 服务器列表
|
|
|
+mapModeRemote = {
|
|
|
+ 'dev': Remote(
|
|
|
+ '192.168.1.15',
|
|
|
+ name='dev',
|
|
|
+ password='123456',
|
|
|
+ deploy_path='/www',
|
|
|
+ ),
|
|
|
+ 'prod': Remote(
|
|
|
+ '192.168.3.2',
|
|
|
+ name='2server',
|
|
|
+ backup=True,
|
|
|
+ password='23423423',
|
|
|
+ deploy_path='/home/ddd',
|
|
|
+ ),
|
|
|
+}
|
|
|
+''' </System Confirgures> '''
|
|
|
+
|
|
|
+
|
|
|
+def check_quit_mode(mode):
|
|
|
+ '''
|
|
|
+ Exit process if mode is invalid
|
|
|
+ '''
|
|
|
+ if mode not in mapModeRemote.keys():
|
|
|
+ print(f"Invalid mode: {mode}")
|
|
|
+ exit(-1)
|
|
|
+
|
|
|
+
|
|
|
+def get_server(mode):
|
|
|
+ return mapModeRemote.get(mode)
|
|
|
+
|
|
|
+
|
|
|
+def git_branch_name():
|
|
|
+ '''
|
|
|
+ Get active(current) git branch
|
|
|
+
|
|
|
+ @return str
|
|
|
+ '''
|
|
|
+ cmd = ['git', 'rev-parse', '--abbrev-ref', 'HEAD']
|
|
|
+ return subprocess.check_output(cmd).decode('ascii').strip()
|
|
|
+
|
|
|
+
|
|
|
+def git_commit_hash(short=True):
|
|
|
+ '''
|
|
|
+ Get git latest commit hash
|
|
|
+
|
|
|
+ @short: is short hash or long(full)
|
|
|
+ @return str
|
|
|
+ '''
|
|
|
+ cmd = ['git', 'rev-parse', '--short', 'HEAD']
|
|
|
+ if not short:
|
|
|
+ cmd = ['git', 'rev-parse', 'HEAD']
|
|
|
+ return subprocess.check_output(cmd).decode('ascii').strip()
|
|
|
+
|
|
|
+
|
|
|
+def git_last_n_log(n=3):
|
|
|
+ cmd = ['git', 'log', '--oneline', f"-{n}"]
|
|
|
+ return subprocess.check_output(cmd).decode('utf8').strip()
|
|
|
+
|
|
|
+
|
|
|
+def run(c, cmd):
|
|
|
+ ''' Fabric Connection.run() wrapper'''
|
|
|
+ return c.run(cmd, hide=True)
|
|
|
+
|
|
|
+
|
|
|
+def generate_version_file():
|
|
|
+ '''
|
|
|
+ '''
|
|
|
+ commit = git_commit_hash()
|
|
|
+ with open('./app/version', 'wt', encoding='utf8') as fh:
|
|
|
+ content = f"{commit}"
|
|
|
+ fh.write(f"export const version='{content}'\n")
|
|
|
+
|
|
|
+
|
|
|
+def backup(r, server):
|
|
|
+ '''
|
|
|
+ backup to homedir before substitution
|
|
|
+
|
|
|
+ @r: remote connection
|
|
|
+ '''
|
|
|
+ now = datetime.now().strftime('%Y_%m_%d_%H_%M_%S')
|
|
|
+ filename = os.path.basename(server.deploy_path)
|
|
|
+ backup_filepath = os.path.join('~', f"{filename}{now}.tar.gz")
|
|
|
+ run(r, f"tar czf {backup_filepath} {server.deploy_path}")
|
|
|
+
|
|
|
+
|
|
|
+@task
|
|
|
+def test(c):
|
|
|
+ backup(None)
|
|
|
+
|
|
|
+
|
|
|
+def tar_posix(c):
|
|
|
+ run(c, f"rm -f {FILE_TAR}")
|
|
|
+ run(c, f"tar czf {FILE_TAR} {' '.join(ITEMS)}")
|
|
|
+
|
|
|
+
|
|
|
+def tar_win(c):
|
|
|
+ print('Unsupport function')
|
|
|
+ exit(-2)
|
|
|
+ # try:
|
|
|
+ # os.unlink(FILE_TAR)
|
|
|
+ # except Exception as e:
|
|
|
+ # print('Remove file warn: {}'.format(e))
|
|
|
+ # name, _ = os.path.splitext(FILE_TAR)
|
|
|
+ # shutil.make_archive(name, 'zip', ITEMS)
|
|
|
+ # print('zip file generated: {}'.format(FILE_TAR))
|
|
|
+
|
|
|
+
|
|
|
+@task
|
|
|
+def tar(c):
|
|
|
+ plt = platform.platform()
|
|
|
+ pattern_win = re.compile(r'windows', re.IGNORECASE)
|
|
|
+ match = pattern_win.search(plt)
|
|
|
+ _ = tar_win(c) if match else tar_posix(c)
|
|
|
+
|
|
|
+
|
|
|
+@task(help={'mode': 'Upload destination: "prod" or "dev"'})
|
|
|
+def upload(c, mode='dev'):
|
|
|
+ '''
|
|
|
+ Upload tarball(generated by tar command) to a remote server
|
|
|
+
|
|
|
+ @mode: specify server by mode. @see mapModeRemote
|
|
|
+ '''
|
|
|
+ check_quit_mode(mode)
|
|
|
+
|
|
|
+ if not os.path.isfile(FILE_TAR):
|
|
|
+ print("Please run tar command first.")
|
|
|
+ return
|
|
|
+
|
|
|
+ server = get_server(mode)
|
|
|
+
|
|
|
+ with Connection(host=server.host, port=server.port,
|
|
|
+ user=server.user, connect_kwargs={'password': server.password}) as r:
|
|
|
+ with r.cd(UPLOAD_PATH):
|
|
|
+ r.put(FILE_TAR)
|
|
|
+ if server.backup:
|
|
|
+ backup(r, server)
|
|
|
+
|
|
|
+ run(r, f"tar xzf {FILE_TAR} -C {server.deploy_path}")
|
|
|
+ run(r, f"rm -f {FILE_TAR}")
|
|
|
+ if server.tgmsg:
|
|
|
+ send(c, f"新版本 {VersionString} 已发布 {server.name}: *.*.*.{server.host.split('.')[3]}")
|
|
|
+
|
|
|
+
|
|
|
+@task
|
|
|
+def send(c, msg='hello'):
|
|
|
+ '''
|
|
|
+ Send a telegram message
|
|
|
+
|
|
|
+ @msg: what to be sent
|
|
|
+ '''
|
|
|
+ chat_id = '1234567'
|
|
|
+ token = 'x454565465:Adskjfskldfjdsklfjskldfjskld-ppdfVdfd3f'
|
|
|
+ url = f"https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}&text={msg}"
|
|
|
+ ret = requests.get(url).json()
|
|
|
+ if not ret['ok']:
|
|
|
+ print('send Telegram msg failed:', ret)
|
|
|
+
|
|
|
+
|
|
|
+@task(help={'mode': 'Deploy mode: "prod" or "dev"'})
|
|
|
+def deploy(c, mode='dev'):
|
|
|
+ check_quit_mode(mode)
|
|
|
+ server = get_server(mode)
|
|
|
+ branch = git_branch_name()
|
|
|
+ generate_version_file()
|
|
|
+
|
|
|
+ if server.branch and branch != branch:
|
|
|
+ print(f"Wrong branch. mode={mode}, branch={branch}. branch should be {server.branch}")
|
|
|
+ exit(-3)
|
|
|
+
|
|
|
+ # dependencies
|
|
|
+ tar(c)
|
|
|
+ upload(c, mode)
|
|
|
+ print('Done.')
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+### 更改配置
|
|
|
+
|
|
|
+修改爲與當前工程適應的配置
|
|
|
+
|
|
|
+### 運行
|
|
|
+
|
|
|
+```shell
|
|
|
+fab deploy -m=<remoteName>
|
|
|
+```
|