--- 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 ''' ''' # 要部署的文件/目录列表 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', ), } ''' ''' 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= ```