First functional release
This commit is contained in:
parent
b9920f9acb
commit
f793ba7bb6
421
configsync.py
Normal file
421
configsync.py
Normal file
@ -0,0 +1,421 @@
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import configparser
|
||||
import socket
|
||||
import datetime
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import stat
|
||||
import pwd
|
||||
import grp
|
||||
|
||||
CONFIG_SYNC_VERSION = "1.0.0"
|
||||
CONFIG_SYNC_FILE = "/etc/configsync.conf"
|
||||
|
||||
def check_dependencies():
|
||||
status = True
|
||||
try:
|
||||
subprocess.call(["git", "version"], stdout=subprocess.DEVNULL)
|
||||
except OSError:
|
||||
status = False
|
||||
print("git cannot be found on your system!")
|
||||
print("Install git: pacman -S git")
|
||||
|
||||
return status
|
||||
|
||||
def print_splash():
|
||||
print("---=== CONFIG-SYNC ===---")
|
||||
print("Version: " + CONFIG_SYNC_VERSION)
|
||||
print()
|
||||
print("---=== USAGE ===---")
|
||||
print("configsync init")
|
||||
print("configsync add <file path>")
|
||||
print("configsync remove <file path>")
|
||||
print("configsync store")
|
||||
print("configsync restore")
|
||||
sys.exit(1)
|
||||
|
||||
def validate_or_create_config(config):
|
||||
if not os.path.exists(CONFIG_SYNC_FILE):
|
||||
print("Creating default configuration file: " + CONFIG_SYNC_FILE)
|
||||
config['DEFAULT'] = {}
|
||||
config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] = "/opt/configsync/storage"
|
||||
config['DEFAULT']['INITIALIZED'] = ""
|
||||
config['GIT'] = {}
|
||||
config['GIT']['REMOTE_REPOSITORY'] = ""
|
||||
config['GIT']['USER'] = "configsync"
|
||||
config['GIT']['EMAIL'] = "configsync@" + socket.gethostname()
|
||||
config['GIT']['SSHKEY'] = str(Path.home()) + "/.ssh/id_rsa"
|
||||
|
||||
update_config(config)
|
||||
else:
|
||||
config.read(CONFIG_SYNC_FILE)
|
||||
|
||||
return config
|
||||
|
||||
def get_config():
|
||||
config = configparser.ConfigParser()
|
||||
config = validate_or_create_config(config)
|
||||
|
||||
return config
|
||||
|
||||
def update_config(config):
|
||||
try:
|
||||
config.write(open(CONFIG_SYNC_FILE, 'w'))
|
||||
except PermissionError as e:
|
||||
print("Unable to write config file! Error: " + e.strerror)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print("Unable to write config file! Error: " + str(e))
|
||||
sys.exit(1)
|
||||
|
||||
def init_dialog():
|
||||
config = get_config()
|
||||
print("Welcome to CONFIG-SYNC!")
|
||||
print()
|
||||
|
||||
if config['DEFAULT']['INITIALIZED']:
|
||||
reinitialize = input("WARNING: CONFIG-SYNC has already been initialized! Do you really want to change the configuration? [y/N]: ")
|
||||
if not reinitialize or reinitialize != "y":
|
||||
print("Skipping new initialization!")
|
||||
sys.exit(0)
|
||||
|
||||
local_path = input("Enter the local storage directory for configuration files [" + config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] + "]: ")
|
||||
if not local_path:
|
||||
local_path = config['DEFAULT']['LOCAL_STORAGE_DIRECTORY']
|
||||
config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] = local_path
|
||||
|
||||
remote_repo = input("Enter the remote repository url [" + config['GIT']['REMOTE_REPOSITORY'] + "]: ")
|
||||
if not remote_repo:
|
||||
remote_repo = config['GIT']['REMOTE_REPOSITORY']
|
||||
config['GIT']['REMOTE_REPOSITORY'] = remote_repo
|
||||
|
||||
git_ssh_key = input("Enter the path to the ssh key to use [" + config['GIT']['SSHKEY'] + "]: ")
|
||||
if not git_ssh_key:
|
||||
git_ssh_key = config['GIT']['SSHKEY']
|
||||
config['GIT']['SSHKEY'] = git_ssh_key
|
||||
|
||||
git_user = input("Enter the git user name (displayname) [" + config['GIT']['USER'] + "]: ")
|
||||
if not git_user:
|
||||
git_user = config['GIT']['USER']
|
||||
config['GIT']['USER'] = git_user
|
||||
|
||||
git_email = input("Enter the git user email address [" + config['GIT']['EMAIL'] + "]: ")
|
||||
if not git_email:
|
||||
git_email = config['GIT']['EMAIL']
|
||||
config['GIT']['EMAIL'] = git_email
|
||||
|
||||
config['DEFAULT']['INITIALIZED'] = "True"
|
||||
|
||||
update_config(config)
|
||||
|
||||
def create_initialization_file(path):
|
||||
try:
|
||||
with open(path + "/configsync.info", mode='w') as file:
|
||||
file.write('CONFIG-SYNC Repo initialized: %s.\n' % ( datetime.datetime.now()))
|
||||
except PermissionError as e:
|
||||
print("Unable to write initialization file! Error: " + e.strerror)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print("Unable to write initialization file! Error: " + str(e))
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
open(path + "/configsync.db", mode='a').close()
|
||||
except PermissionError as e:
|
||||
print("Unable to write database file! Error: " + e.strerror)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print("Unable to write database file! Error: " + str(e))
|
||||
sys.exit(1)
|
||||
|
||||
def add_file_to_db(file_path):
|
||||
config = get_config()
|
||||
try:
|
||||
insert = True
|
||||
with open(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] + "/configsync.db", "r+") as search:
|
||||
for line in search:
|
||||
line = line.rstrip() # remove '\n' at end of line
|
||||
if file_path == line:
|
||||
insert = False
|
||||
if insert:
|
||||
with open(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] + "/configsync.db", mode='a') as file:
|
||||
file.write('%s\n' % (file_path))
|
||||
except PermissionError as e:
|
||||
print("Unable to write db file! Error: " + e.strerror)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print("Unable to write db file! Error: " + str(e))
|
||||
sys.exit(1)
|
||||
|
||||
def delete_file_from_db(file_path):
|
||||
config = get_config()
|
||||
try:
|
||||
with open(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] + "/configsync.db", "r+") as search:
|
||||
lines = search.readlines()
|
||||
search.seek(0)
|
||||
for line in lines:
|
||||
if line.rstrip() != file_path:
|
||||
search.write(line.rstrip() + "\n")
|
||||
search.truncate()
|
||||
except PermissionError as e:
|
||||
print("Unable to write db file! Error: " + e.strerror)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print("Unable to write db file! Error: " + str(e))
|
||||
sys.exit(1)
|
||||
|
||||
def setup_git_ssh_environment():
|
||||
config = get_config()
|
||||
|
||||
os.environ['GIT_SSH_COMMAND'] = "ssh -i " + config['GIT']['SSHKEY']
|
||||
|
||||
def git_is_repo(path):
|
||||
return subprocess.call(["git", "branch"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, cwd=path) == 0
|
||||
|
||||
def git_set_author(path, user, email):
|
||||
status = subprocess.call(["git", "config", "user.name", user], stderr=subprocess.STDOUT, stdout=subprocess.DEVNULL, cwd=path) == 0
|
||||
status |= subprocess.call(["git", "config", "user.email", email], stderr=subprocess.STDOUT, stdout=subprocess.DEVNULL, cwd=path) == 0
|
||||
|
||||
return status
|
||||
|
||||
def git_set_upstream(path, remote_url):
|
||||
# make sure that remote origin does not exist
|
||||
status = subprocess.call(["git", "remote", "rm", "origin"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, cwd=path) == 0
|
||||
# now add new origin
|
||||
status |= subprocess.call(["git", "remote", "add", "origin", remote_url], cwd=path) == 0
|
||||
|
||||
return status
|
||||
|
||||
def git_init_repo(path):
|
||||
status = subprocess.call(["git", "init"], cwd=path) == 0
|
||||
|
||||
return status
|
||||
|
||||
def git_push(path, message = "configuration update"):
|
||||
status = subprocess.call(["git", "add", "-A"], cwd=path) == 0
|
||||
status |= subprocess.call(["git", "commit", "-a", "-m", message], cwd=path) == 0
|
||||
status |= subprocess.call(["git", "push", "-u", "origin", "master"], cwd=path) == 0
|
||||
|
||||
return status
|
||||
|
||||
def git_pull(path):
|
||||
status = subprocess.call(["git", "pull", "origin", "master"], cwd=path) == 0
|
||||
|
||||
return status
|
||||
|
||||
def git_check_status(status):
|
||||
config = get_config()
|
||||
if not status:
|
||||
print("FATAL: Git operation failed! Repository has to be recovered manually!")
|
||||
print("FATAL: Repository path: " + config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'])
|
||||
sys.exit(1)
|
||||
|
||||
def init_local_repo():
|
||||
config = get_config()
|
||||
|
||||
print("Setting up local repository.")
|
||||
if not os.path.exists(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY']):
|
||||
print("Creating new directory: " + config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'])
|
||||
try:
|
||||
os.makedirs(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'])
|
||||
except OSError as e:
|
||||
print("Failed to create local repository directory: " + e.strerror)
|
||||
sys.exit(1)
|
||||
|
||||
# init git repo
|
||||
if not git_is_repo(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY']):
|
||||
git_init_repo(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'])
|
||||
|
||||
git_check_status(git_set_author(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'], config['GIT']['USER'], config['GIT']['EMAIL']))
|
||||
git_check_status(git_set_upstream(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'], config['GIT']['REMOTE_REPOSITORY']))
|
||||
|
||||
setup_git_ssh_environment()
|
||||
|
||||
git_check_status(git_pull(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY']))
|
||||
create_initialization_file(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'])
|
||||
git_check_status(git_push(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'], "configsync initialized"))
|
||||
git_check_status(git_pull(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY']))
|
||||
|
||||
def add_file(filepath):
|
||||
config = get_config()
|
||||
abs_path = os.path.abspath(filepath)
|
||||
|
||||
if not os.path.exists(abs_path):
|
||||
print("Invalid file, skipping!")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Adding '" + abs_path + "' to CONFIG-SYNC...")
|
||||
|
||||
local_path = config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] + abs_path
|
||||
|
||||
if os.path.exists(local_path):
|
||||
print("File is already registered to CONFIG-SYNC, skipping!")
|
||||
sys.exit(1)
|
||||
else:
|
||||
target_directory = os.path.abspath(os.path.join(local_path, os.pardir))
|
||||
if not os.path.exists(target_directory):
|
||||
os.makedirs(target_directory)
|
||||
|
||||
file_stat = os.stat(abs_path)
|
||||
file_permissions = file_stat[stat.ST_MODE]
|
||||
file_owner = file_stat[stat.ST_UID]
|
||||
file_group = file_stat[stat.ST_GID]
|
||||
file_owner_name = pwd.getpwuid(file_owner).pw_name
|
||||
file_group_name = grp.getgrgid(file_group).gr_name
|
||||
|
||||
try:
|
||||
with open(local_path + ".cfsinfo", mode='w') as file:
|
||||
file.write('%s;%s;%s;%s;%s' % (file_permissions, file_owner, file_group, file_owner_name, file_group_name))
|
||||
except PermissionError as e:
|
||||
print("Unable to write stat file! Error: " + e.strerror)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print("Unable to write stat file! Error: " + str(e))
|
||||
sys.exit(1)
|
||||
|
||||
shutil.copy2(abs_path, local_path)
|
||||
os.chown(local_path, file_owner, file_group)
|
||||
os.remove(abs_path)
|
||||
os.symlink(local_path, abs_path)
|
||||
os.chown(abs_path, file_owner, file_group, follow_symlinks=False)
|
||||
|
||||
add_file_to_db(abs_path)
|
||||
|
||||
print("File added to CONFIG-SYNC!")
|
||||
print("Use configsync store to push the file to the remote repository!")
|
||||
|
||||
def remove_file(filepath):
|
||||
config = get_config()
|
||||
abs_path = os.path.abspath(filepath)
|
||||
|
||||
local_path = config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] + abs_path
|
||||
|
||||
if not os.path.exists(local_path):
|
||||
print("File not registered to CONFIG-SYNC, skipping!")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Removing '" + abs_path + "' from CONFIG-SYNC...")
|
||||
|
||||
target_directory = os.path.abspath(os.path.join(abs_path, os.pardir))
|
||||
if not os.path.exists(target_directory):
|
||||
os.makedirs(target_directory)
|
||||
|
||||
if os.path.exists(abs_path):
|
||||
confirmed = input("Do you really want to override the local file '" + abs_path + "'? [y/N]: ")
|
||||
if not confirmed or confirmed != "y":
|
||||
print("Skipping removal process!")
|
||||
sys.exit(0)
|
||||
else:
|
||||
os.remove(abs_path)
|
||||
|
||||
shutil.copy2(local_path, abs_path)
|
||||
|
||||
file_stat = os.stat(local_path)
|
||||
file_permissions = file_stat[stat.ST_MODE]
|
||||
file_owner = file_stat[stat.ST_UID]
|
||||
file_group = file_stat[stat.ST_GID]
|
||||
|
||||
os.chown(abs_path, file_owner, file_group)
|
||||
os.chmod(abs_path, file_permissions)
|
||||
os.remove(local_path)
|
||||
os.remove(local_path + ".cfsinfo")
|
||||
|
||||
delete_file_from_db(abs_path)
|
||||
|
||||
print("File removed from CONFIG-SYNC!")
|
||||
print("Use configsync store to push the file to the remote repository!")
|
||||
|
||||
def store():
|
||||
config = get_config()
|
||||
setup_git_ssh_environment()
|
||||
git_push(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'])
|
||||
|
||||
def update_local_metadata():
|
||||
config = get_config()
|
||||
with open(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] + "/configsync.db", "r") as search:
|
||||
for line in search:
|
||||
abs_path = line.rstrip() # remove '\n' at end of line
|
||||
local_path = config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] + abs_path
|
||||
|
||||
if not os.path.exists(local_path):
|
||||
print("Invalid file in database, skipping!")
|
||||
else:
|
||||
with open(local_path + ".cfsinfo", "r") as metadata_file:
|
||||
metadata_string = metadata_file.readline().rstrip()
|
||||
metadata = metadata_string.split(";")
|
||||
|
||||
file_permissions = int(metadata[0])
|
||||
file_owner = int(metadata[1])
|
||||
file_group = int(metadata[2])
|
||||
|
||||
os.chmod(local_path, file_permissions)
|
||||
os.chown(local_path, file_owner, file_group)
|
||||
|
||||
def restore():
|
||||
config = get_config()
|
||||
setup_git_ssh_environment()
|
||||
git_pull(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'])
|
||||
|
||||
update_local_metadata()
|
||||
|
||||
confirmed = input("Do you really want to restore files from the repository? Local files will be overwritten! [y/N]: ")
|
||||
if not confirmed or confirmed != "y":
|
||||
print("Skipping restore process!")
|
||||
sys.exit(0)
|
||||
else:
|
||||
with open(config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] + "/configsync.db", "r") as search:
|
||||
for line in search:
|
||||
abs_path = line.rstrip() # remove '\n' at end of line
|
||||
local_path = config['DEFAULT']['LOCAL_STORAGE_DIRECTORY'] + abs_path
|
||||
|
||||
if not os.path.exists(local_path):
|
||||
print("Invalid file, skipping!")
|
||||
else:
|
||||
target_directory = os.path.abspath(os.path.join(abs_path, os.pardir))
|
||||
if not os.path.exists(target_directory):
|
||||
os.makedirs(target_directory)
|
||||
|
||||
if os.path.exists(abs_path):
|
||||
os.remove(abs_path)
|
||||
|
||||
os.symlink(local_path, abs_path)
|
||||
|
||||
file_stat = os.stat(local_path)
|
||||
file_owner = file_stat[stat.ST_UID]
|
||||
file_group = file_stat[stat.ST_GID]
|
||||
|
||||
os.chown(abs_path, file_owner, file_group, follow_symlinks=False)
|
||||
|
||||
def main(argv):
|
||||
if not check_dependencies():
|
||||
sys.exit(1)
|
||||
|
||||
if len(argv) == 2:
|
||||
arg = argv[1]
|
||||
if arg == "init":
|
||||
init_dialog()
|
||||
init_local_repo()
|
||||
sys.exit(0)
|
||||
elif arg == "store":
|
||||
store()
|
||||
elif arg == "restore":
|
||||
restore()
|
||||
else:
|
||||
print_splash()
|
||||
elif len(argv) == 3:
|
||||
arg = argv[1]
|
||||
config_file = argv[2]
|
||||
|
||||
if arg == "add":
|
||||
add_file(config_file)
|
||||
elif arg == "remove":
|
||||
remove_file(config_file)
|
||||
else:
|
||||
print_splash()
|
||||
else:
|
||||
print_splash()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
Loading…
Reference in New Issue
Block a user