GitHub에 구성 파일 백업
중요한 서버 구성 파일을 GitHub 리포지토리에 커밋하는 샘플 Python 코드
이 연습은 GitHub를 사용한 분산 서버 관리에 설명된 것과 일종의 반대입니다. 이번에는 HAproxy 또는 nginx 파일과 같은 중요한 서버 구성 파일을 GitHub에 백업하려고 합니다. 이러한 파일 편집은 일반적으로 시행착오를 통해 수행됩니다. 파일을 편집하고 구문을 확인하고 프로세스를 다시 시작하고 변경 내용이 의도한 대로 적용되었는지 확인합니다. 이것은 분명히 프로덕션이 아닌 개발 시스템에서 수행됩니다.
이러한 파일을 GitHub에 저장하는 것은 절대적으로 좋은 생각이지만 저장소에 변경 사항을 커밋하고 서버에서 가져오는 일반적인 GitHub 워크플로는 실제로 적용되지 않습니다. 이러한 구성 파일은 일반적으로 환경에 따라 다르며, DEV 구성 파일을 프로덕션 서버로 가져갈 수 없으며, DNS 이름, 다양한 로깅 매개변수 및 이들을 다르게 만드는 많은 것들이 있습니다. 개인적으로 사용하기에 가장 좋은 워크플로는 일반적인 GitHub 워크플로와 거의 반대이며 파일을 변경하고 의도한 대로 작동하는지 테스트하고 확인한 다음 파일의 새 버전을 GitHub에 커밋합니다. 지원. 서버를 재구축해야 하는 경우 백업을 사용하거나 문제가 발생할 경우 다음 변경 사항을 이전 작업 버전으로 되돌리기 위한 백아웃 옵션으로 사용할 수 있습니다.
이 Python 스크립트의 기본 목적은 이러한 파일이 변경되었을 때 GitHub에 커밋하는 것입니다. 스크립트는 또한 암호와 같은 특정 매개변수를 수정할 수도 있습니다. 물론 이는 GitHub 또는 다른 코드 저장소에 저장하고 싶지 않습니다. 이 스크립트의 또 다른 장점은 각 서버에 대해 별도의 리포지토리를 요구하지 않고 서버 이름을 따서 명명된 디렉터리의 단일 GitHub 리포지토리에 파일을 업로드할 수 있다는 것입니다.
스크립트는 PyGitHub 라이브러리 를 사용하므로 Yaml 라이브러리와 함께 설치부터 시작하겠습니다.
pip3 install PyGithub
GitHub에서 API 토큰 얻기
다음 스크립트를 사용하려면 먼저 GitHub에서 API 토큰을 가져와야 합니다. GitHub 계정에서 "설정" 및 "개발자 설정"으로 이동한 다음 "개인 액세스 토큰"을 클릭하고 새 토큰을 만듭니다. 이 기사 에 따라 "repo"를 범위로 선택하십시오.
특정 저장소뿐만 아니라 계정에 대한 액세스를 허용하는 개인 액세스 토큰을 반드시 보호해야 합니다. 이를 위해 RSA-crypto 라이브러리 를 사용하여 암호화하기로 결정했습니다. 다음 코드는 라이브러리를 설치하고 새 RSA 키 세트를 만들고 구성 파일의 개인 액세스 토큰을 ghe_token 이라는 옵션 값으로 암호화합니다. 해당 라이브러리 및 관련 명령줄 도구 사용에 대한 자세한 내용은 공개/개인 키 RSA 암호화 및 Python 암호화 를 참조하십시오.
먼저 새 암호화가 없다고 가정하고 새 암호화를 만든 다음 위에서 얻은 GitHub API 토큰 값을 구성 파일에 ghe_token 이라는 항목(옵션)으로 설정합니다.
pip3 install rsa-crypto
rsa_crypto create
Enter key password:
Re-enter key password:
Creating key...
Created password-protected private/public keys file /Users/me/rsa_key.bin
Use the "extract" keyword to create public and private key files.
rsa_crypto set -o ghe_token
Using key: /Users/me/rsa_key.bin
Opening encrypted key /Users/me/rsa_key.bin
Enter key password:
Enter value:
set section: option:ghe_token
Updated /Users/me/.rsa_values.conf
그런 다음 Python 스크립트는 개인 액세스 토큰을 검색하고 사용하기 위해 ghe_token 구성 "옵션"을 읽고 해독하도록 구성됩니다. 이것이 너무 복잡해 보이는 경우 다른 솔루션을 자유롭게 찾을 수 있지만 액세스 토큰을 스크립트에 직접 포함하지 마십시오. GitHub에서 누구나 보고 사용할 수 있게 될 가능성이 높습니다!
파이썬 스크립트
구성 변수는 매우 간단합니다. file_list 는 로컬에 존재하는 경우 GitHub에 커밋할 파일 목록을 포함합니다.
ghe_repo 변수는 GitHub 저장소를 가리켜야 합니다. GitHub 스크립트와 함께 분산 서버 관리를 사용하는 경우 구성 파일을 백업할 목적으로 다른 저장소와 다른 저장소를 사용하는 것이 좋습니다.
ghe_branch 변수를 사용하면 GitHub를 사용하는 방법에 따라 다른 분기를 가리킬 수 있습니다.
remote_base_path 변수는 원격 경로가 /로 시작해야 했던 PyGitHub 라이브러리의 이전 버그 때문에 존재하지만 더 이상 그렇지 않습니다.
스크립트는 로그 파일을 만들고 커밋 주석으로 포함할 스크립트를 실행하는 사용자의 이름을 확인하려고 시도합니다.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
__author__ = "Christophe Gauge"
__version__ = "1.0.4"
'''
Backup HAproxy and nginx configuration files.
'''
# I M P O R T S ###############################################################
import github
import os
import sys
import time
import io
import logging
import traceback
import hashlib
import rsa_crypto
import argparse
import socket
import getpass
import base64
if (sys.version_info > (3, 0)):
from urllib.parse import urljoin
else:
from urlparse import urljoin
# G L O B A L S ###############################################################
file_list = ['/tmp/test.txt', '/etc/haproxy/haproxy.cfg', '/etc/nginx/nginx.conf']
# # Uncomment if you are using your own internal GitHub Repository
# ghe_organization = 'my_ghe_repo'
# ghe_hostname = mydomain.com
ghe_repo = 'Christophe-Gauge/GitHub'
remote_base_path = ''
ghe_branch = 'main'
logger = logging.getLogger()
logging.basicConfig(level=logging.INFO)
logger.info("Path: %s" % (os.path.realpath(__file__)))
logger.info("Version: %s" % (__version__))
args = argparse.Namespace(option='ghe_token')
ghe_token = rsa_crypto.decrypt_value(args)
# C O D E #####################################################################
def main():
"""Main function."""
global logger
global repo
global args
# Get the server name to use as a directory in GitHub
server_name = socket.gethostname().split('.')[0].lower()
# Get the username to log who made the change, nobody will be a Cron task or such
try:
user_name = getpass.getuser()
except Exception as e:
user_name = None
if user_name is None:
try:
user_name = os.getlogin()
except OSError as e:
user_name = 'nobody'
except Exception as e:
user_name = 'unknown'
gh = github.Github(login_or_token=ghe_token)
repo = gh.get_repo(ghe_repo)
# # Uncomment if you are using your own internal GitHub Repository
# gh = github.Github(base_url=f"https://{ghe_hostname}/api/v3", login_or_token=ghe_token)
# org = gh.get_organization(ghe_organization)
# repo = org.get_repo(ghe_repo)
for local_file_name in file_list:
file_content = ''
if os.path.exists(local_file_name):
logger.info('File %s exists, processing.' % local_file_name)
# Redacting HAproxy auth passwords, more may be needed for your use-case
with io.open(local_file_name, "r", encoding="utf-8") as f:
for line in f:
if 'auth' in line:
file_content += line[:line.find('auth')] + 'auth <REMOVED>\n'
else:
file_content += line
# print(file_content)
data = file_content.encode('utf-8', 'ignore')
filesize = len(data)
content = "blob " + str(filesize) + "\0" + data.decode('utf-8')
encoded_content = content.encode('utf-8')
localSHA = hashlib.sha1(encoded_content).hexdigest()
remote_file_name = urljoin(remote_base_path, server_name + '/' + os.path.basename(local_file_name))
logger.info(f"Saving local file {local_file_name} to remote GitHub repo {repo.full_name} file {remote_file_name}")
try:
remoteSHA = repo.get_contents(remote_file_name, ref=ghe_branch).sha
except github.UnknownObjectException as e:
logger.error(f"Remote file not found {remote_file_name}")
remoteSHA = None
except Exception as e:
logger.error("Error {0}".format(str(e)))
logger.error(traceback.format_exc())
remoteSHA = None
if remoteSHA == localSHA:
logger.info('Remote file is present, hash is the same, NOT updating.')
continue
else:
try:
if remoteSHA is None:
logger.info('Remote file file is NOT present, creating new file')
repo.create_file(remote_file_name, f"Updated by {user_name}", data, branch=ghe_branch)
else:
logger.info('Remote file file is present but hash has changed, updating file')
repo.update_file(remote_file_name, f"Updated by {user_name}", data, remoteSHA, branch=ghe_branch)
except Exception as e:
logger.error("Error {0}".format(str(e)))
logger.error(traceback.format_exc())
logger.info('Done updating GitHub')
else:
logger.warning('File does not exist %s' % local_file_name)
logger.info('*** DONE ***')
sys.exit(0)
###############################################################################
if __name__ == "__main__":
main()
# E N D O F F I L E #######################################################