# HG changeset patch # User František Kučera # Date 1555596726 -7200 # Node ID 6207c211aafdb1710344b2aa05cb0e4b22a3a5a9 first version: IPC (datagram unix domain socket + SSH), create Btrfs subvolume, clone Mercurial or Git repository diff -r 000000000000 -r 6207c211aafd vcs-backup.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vcs-backup.sh Thu Apr 18 16:12:06 2019 +0200 @@ -0,0 +1,142 @@ +#!/bin/bash + +# VCS Backup +# Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +# Server-side configuration: +VCS_BACKUP_DATA_DIR="/mnt/data"; +VCS_BACKUP_CURRENT_DIR="$VCS_BACKUP_DATA_DIR/current"; +VCS_BACKUP_CONFIG_DIR="$VCS_BACKUP_DATA_DIR/config"; +VCS_BACKUP_SNAPSHOT_DIR="$VCS_BACKUP_DATA_DIR/snapshot"; +VCS_BACKUP_SUBVOLUME_SOCKET="/run/vcs-backup-subvolume"; +VCS_BACKUP_CLONE_SOCKET="/run/vcs-backup-clone"; +VCS_BACKUP_USER="vcs-backup"; +VCS_BACKUP_MANAGER="vcs-backup-manager"; + +# Installation – check and do it by hand: +# There should be already mounted Btrfs at $VCS_BACKUP_DATA_DIR +installInstructions() { +cp vcs-backup.sh /usr/local/bin/ +adduser --disabled-password "$VCS_BACKUP" +adduser --disabled-password "$VCS_BACKUP_MANAGER" + +mkdir "$VCS_BACKUP_CURRENT_DIR"; +mkdir "$VCS_BACKUP_CONFIG_DIR"; +mkdir "$VCS_BACKUP_SNAPSHOT_DIR"; + +chown "${VCS_BACKUP_USER}:${VCS_BACKUP_USER}" "$VCS_BACKUP_CURRENT_DIR" +chown "${VCS_BACKUP_MANAGER}:${VCS_BACKUP_MANAGER}" "$VCS_BACKUP_CONFIG_DIR" +} + + +# --- Private functions: --------------------------------------------------------------------------- + +# Environment: all +# $1 = VCS type: hg, git +# $2 = URL +isValidTypeAndURL() { ([[ "$1" == "hg" || "$1" == "git" ]]) && [[ $(echo "$2" | wc -l) == 1 ]] && [[ $(echo "$2" | grep -E '^(http|https|ssh)://([a-zA-Z0-9_-][a-zA-Z0-9_-.]*/?)+$' | wc -l) == 1 ]]; } + +# Environment: all +# $1 = path to the config file +loadConfigFile() { if [ -f "$1" ]; then . "$1"; else echo "Missing config file: $1" >&2; exit 1; fi } + +# Environment: server +# $1 = URL +urlToRelativeDirectoryPath() { + echo "$1" | sed -E 's@^[^:]+://@@g'; +} + +# --- Public interface functions: ------------------------------------------------------------------ + +# Environment: client +# $1 = VCS type: hg, git +# $2 = URL +vcs_backup_public_clientSubmitBackupRequest() { + if isValidTypeAndURL "$1" "$2"; then + loadConfigFile ~/.config/vcs-backup/client.cfg + ${VCS_BACKUP_SSH_COMMAND[@]} vcs-backup.sh serverSubmitBackupRequest "$1" "$2" + else + echo "Unsupported VCS type: '$1' or URL: '$2'" >&2; + fi +} + +# Environment: server +# $1 = VCS type: hg, git +# $2 = URL +vcs_backup_public_serverSubmitBackupRequest() { + if isValidTypeAndURL "$1" "$2"; then + loadConfigFile "/etc/vcs-backup/server.cfg"; + relativePath=$1/$(urlToRelativeDirectoryPath "$2"); + absolutePath="$VCS_BACKUP_CONFIG_DIR/$relativePath"; + mkdir -p "$absolutePath"; + echo "$1" > "$absolutePath/type.txt" + echo "$2" > "$absolutePath/url.txt" + echo "submited" > "$absolutePath/state.txt" + setfacl -m u:${VCS_BACKUP_USER}:r "$absolutePath/type.txt" + setfacl -m u:${VCS_BACKUP_USER}:r "$absolutePath/url.txt" + setfacl -m u:${VCS_BACKUP_USER}:rw "$absolutePath/state.txt" + echo "$relativePath" | socat -u - unix-send:${VCS_BACKUP_SUBVOLUME_SOCKET}; + else + echo "Unsupported VCS type: '$1' or URL: '$2'" >&2; + fi +} + +# Environment: server +# Should be started as a systemd/init service. +# - reads messages from from the subvolume socket – message contains the relative directory path +# - creates a subvolume for given repository + necesary parent directories +# - sends a message to the clone service → start cloning into the created subvolume +vcs_backup_public_serverStartSubvolumeService() { + socat -u "unix-recv:${VCS_BACKUP_SUBVOLUME_SOCKET},group=${VCS_BACKUP_MANAGER},mode=770" - | while read d; do + mkdir -p $(dirname "$VCS_BACKUP_CURRENT_DIR/$d"); + btrfs subvolume create "$VCS_BACKUP_CURRENT_DIR/$d" && \ + echo "subvolumeCreated" > "$VCS_BACKUP_CONFIG_DIR/$d/state.txt" && \ + chown "${VCS_BACKUP_USER}:${VCS_BACKUP_USER}" "$VCS_BACKUP_CURRENT_DIR/$d" && \ + echo "$d" | socat -u - unix-send:${VCS_BACKUP_CLONE_SOCKET}; + done +} + +# Environment: server +# should be started as a systemd/init service +vcs_backup_public_serverStartCloneService() { + socat -u "unix-recv:${VCS_BACKUP_CLONE_SOCKET},mode=700" - | while read d; do + # FIXME: hg vs. git, clone + vcsType=$(cat "$VCS_BACKUP_CONFIG_DIR/$d/type.txt"); + url=$(cat "$VCS_BACKUP_CONFIG_DIR/$d/url.txt"); + if isValidTypeAndURL "$vcsType" "$url"; then + if [[ "$vcsType" == "hg" ]]; then hg clone -U "$url" "$VCS_BACKUP_CURRENT_DIR/$d"; + elif [[ "$vcsType" == "git" ]]; then git clone --bare "$url" "$VCS_BACKUP_CURRENT_DIR/$d"; + fi && echo "cloned" > "$VCS_BACKUP_CONFIG_DIR/$d/state.txt"; + else + echo "Unsupported VCS type: '$vcsType' or URL: '$url'" >&2; + fi + # TODO: call-back to the client if client is waiting (socket exist in the config dir) + done +} + +# --- Single entry-point: -------------------------------------------------------------------------- + +PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; +PUBLIC_FUNCTION_PREFIX="vcs_backup_public_"; +if type -t "$PUBLIC_FUNCTION_PREFIX$1" > /dev/null; then + "$PUBLIC_FUNCTION_PREFIX${@:1}"; +else + echo "Unsupported sub-command: $1" >&2 + echo "Available sub-commands:" >&2 + declare -F | grep "$PUBLIC_FUNCTION_PREFIX" | sed "s/.*$PUBLIC_FUNCTION_PREFIX/ /g" >&2 + exit 1; +fi