Compare commits

..

16 Commits

11 changed files with 370 additions and 233 deletions

1
.gitea/README.md Symbolic link
View File

@@ -0,0 +1 @@
../templates/README.md

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@ config/
key.txt key.txt
passphrase passphrase
ssh/ ssh/
/README.md
/notify.sh

View File

@@ -38,7 +38,7 @@ rebase:
git pull git pull
git checkout - git checkout -
git rebase master git rebase master
./initial-setup.sh --update-paths ./initial-setup.sh --update
systemctl daemon-reload systemctl daemon-reload
git status git status

174
Pipfile.lock generated
View File

@@ -18,10 +18,11 @@
"default": { "default": {
"bracex": { "bracex": {
"hashes": [ "hashes": [
"sha256:01f715cd0ed7a622ec8b32322e715813f7574de531f09b70f6f3b2c10f682425", "sha256:a27eaf1df42cf561fed58b7a8f3fdf129d1ea16a81e1fadd1d17989bc6384beb",
"sha256:64e2a6d14de9c8e022cf40539ac8468ba7c4b99550a2b05fc87fd20e392e568f" "sha256:efdc71eff95eaff5e0f8cfebe7d01adf2c8637c8c92edaf63ef348c241a82418"
], ],
"version": "==2.1.1" "markers": "python_version >= '3.8'",
"version": "==2.4"
}, },
"humanfriendly": { "humanfriendly": {
"hashes": [ "hashes": [
@@ -33,107 +34,126 @@
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
"sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5",
"sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc",
"sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df",
"sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741",
"sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206",
"sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27",
"sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595",
"sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62",
"sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98",
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696",
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290",
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9",
"sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d",
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6",
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867",
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47",
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486",
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6",
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3",
"sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007",
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938",
"sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0",
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c",
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735",
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d",
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28",
"sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4",
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba",
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8",
"sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5",
"sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd",
"sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3",
"sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0",
"sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515",
"sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c",
"sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c",
"sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924",
"sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34",
"sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43",
"sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859",
"sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673",
"sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54",
"sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a",
"sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b",
"sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab",
"sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa",
"sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c",
"sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585",
"sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d",
"sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.4.1" "version": "==6.0.1"
}, },
"wcmatch": { "wcmatch": {
"hashes": [ "hashes": [
"sha256:4d54ddb506c90b5a5bba3a96a1cfb0bb07127909e19046a71d689ddfb18c3617", "sha256:14554e409b142edeefab901dc68ad570b30a72a8ab9a79106c5d5e9a6d241bd5",
"sha256:9146b1ab9354e0797ef6ef69bc89cb32cb9f46d1b9eeef69c559aeec8f3bffb6" "sha256:86c17572d0f75cbf3bcb1a18f3bf2f9e72b39a9c08c9b4a74e991e1882a8efb3"
], ],
"index": "pypi", "index": "pypi",
"version": "==8.2" "version": "==8.5"
} }
}, },
"develop": { "develop": {
"mypy": { "mypy": {
"hashes": [ "hashes": [
"sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9", "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7",
"sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a", "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e",
"sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9", "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c",
"sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e", "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169",
"sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2", "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208",
"sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212", "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0",
"sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b", "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1",
"sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885", "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1",
"sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150", "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7",
"sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703", "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45",
"sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072", "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143",
"sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457", "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5",
"sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e", "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f",
"sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0", "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd",
"sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb", "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245",
"sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97", "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f",
"sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8", "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332",
"sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811", "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30",
"sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6", "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183",
"sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de", "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f",
"sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504", "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85",
"sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921", "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46",
"sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d" "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71",
"sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660",
"sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb",
"sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c",
"sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.910" "version": "==1.6.1"
}, },
"mypy-extensions": { "mypy-extensions": {
"hashes": [ "hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
], ],
"version": "==0.4.3" "markers": "python_version >= '3.5'",
}, "version": "==1.0.0"
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"version": "==0.10.2"
}, },
"types-pyyaml": { "types-pyyaml": {
"hashes": [ "hashes": [
"sha256:1d9e431e9f1f78a65ea957c558535a3b15ad67ea4912bce48a6c1b613dcf81ad", "sha256:334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062",
"sha256:f1d1357168988e45fa20c65aecb3911462246a84809015dd889ebf8b1db74124" "sha256:c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.4.10" "version": "==6.0.12.12"
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
"sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0",
"sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"
"sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
], ],
"version": "==3.10.0.2" "markers": "python_version >= '3.8'",
"version": "==4.8.0"
} }
} }
} }

View File

@@ -486,8 +486,8 @@ def main(argv: typing.List[str]):
body_text = write_logs("Logged errors and warnings:", "EWN") body_text = write_logs("Logged errors and warnings:", "EWN")
body_text += "\n" body_text += b"\n\n"
body_text += write_logs("All logs:") body_text += write_logs("All log messages:")
# Subject summary # Subject summary
if errmsg and warnmsg: if errmsg and warnmsg:

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -1,21 +1,40 @@
#!/bin/bash #!/bin/bash
# These can be overridden when running this script # These can be overridden when running this script
set_default_variables()
{
HOSTNAME=${HOSTNAME:-$(hostname)} HOSTNAME=${HOSTNAME:-$(hostname)}
BACKUP_HOST=${BACKUP_HOST:-backup.jim.sh} BACKUP_HOST=${BACKUP_HOST:-backup.jim.sh}
BACKUP_PORT=${BACKUP_PORT:-222}
BACKUP_USER=${BACKUP_USER:-jim-backups} BACKUP_USER=${BACKUP_USER:-jim-backups}
BACKUP_REPO=${BACKUP_REPO:-borg/${HOSTNAME}} BACKUP_REPO=${BACKUP_REPO:-borg/${HOSTNAME}}
SYSTEMD_UNIT=${SYSTEMD_UNIT:-borg-backup}
# Use stable host ID in case MAC address changes.
# Note that this host ID is only used to manage locks, so it's
# not crucial that it remains stable.
UUID=$(python3 -c 'import uuid;print(uuid.getnode())')
HOSTID=${BORG_HOST_ID:-"${HOSTNAME}@${UUID}"}
log "Configuration:"
log " HOSTNAME Local hostname: \033[1m${HOSTNAME}"
log " HOSTID Local host ID: \033[1m${HOSTID}"
log " BACKUP_USER Backup server user: \033[1m${BACKUP_USER}"
log " BACKUP_HOST Backup server host: \033[1m${BACKUP_HOST}"
log " BACKUP_PORT Backup server port: \033[1m${BACKUP_PORT}"
log " BACKUP_REPO Server repository: \033[1m${BACKUP_REPO}"
log " SYSTEMD_UNIT Systemd unit name: \033[1m${SYSTEMD_UNIT}"
for i in $(seq 15 -1 1); do
printf "\rPress ENTER or wait $i seconds to continue... \b"
if read -t 1 ; then break; fi
done
}
# Main dir is where this repo was checked out # Main dir is where this repo was checked out
BORG_DIR="$(realpath "$(dirname "$0")")" BORG_DIR="$(realpath "$(dirname "$0")")"
cd "${BORG_DIR}" cd "${BORG_DIR}"
# This is named with uppercase so that it doesn't tab-complete for BORG_BIN="${BORG_DIR}/bin/borg.$(uname -m)"
# "./b<tab>", which should give us "./borg.sh"
BORG_BIN="${BORG_DIR}/Borg.bin"
# Use stable host ID in case MAC address changes
HOSTID="${HOSTNAME}@$(python -c 'import uuid;print(uuid.getnode())')"
function error_handler() { function error_handler() {
echo "Error at $1 line $2:" echo "Error at $1 line $2:"
@@ -27,37 +46,100 @@ trap 'error_handler ${BASH_SOURCE} ${LINENO} $?' ERR
set -o errexit set -o errexit
set -o errtrace set -o errtrace
update_paths() # Create pip environment
setup_venv()
{ {
sed -i \ if ! which pipenv >/dev/null 2>&1 ; then
-e "s!\${HOSTNAME}!${HOSTNAME}!g" \ error "pipenv not found, try: sudo apt install pipenv"
fi
mkdir -p .venv
pipenv install
}
# Create shell script with environment variables
create_borg_vars()
{
VARS=${BORG_DIR}/vars.sh
# These variables are used elsewhere in this script
BORG_REPO="ssh://${BACKUP_USER}@${BACKUP_HOST}:${BACKUP_PORT}/./${BACKUP_REPO}"
BORG=${BORG_DIR}/borg.sh
SSH=$BORG_DIR/ssh
cat >"$VARS" <<EOF
export BACKUP_USER=${BACKUP_USER}
export BACKUP_HOST=${BACKUP_HOST}
export BACKUP_PORT=${BACKUP_PORT}
export BACKUP_REPO=${BACKUP_REPO}
export HOSTNAME=${HOSTNAME}
export BORG_REPO=${BORG_REPO}
export BORG_HOST_ID=${HOSTID}
export BORG_PASSCOMMAND="cat ${BORG_DIR}/passphrase"
export BORG_DIR=${BORG_DIR}
export SSH=${SSH}
export BORG=${BORG}
export BORG_BIN=${BORG_BIN}
export SYSTEMD_UNIT=${SYSTEMD_UNIT}
EOF
if ! "$BORG" -h >/dev/null ; then
error "Can't run the borg wrapper; does borg work?"
fi
}
# Copy templated files, filling in templates as needed
install_templated_files()
{
DOCS="README.md"
SCRIPTS="notify.sh logs.sh"
for i in ${DOCS} ${SCRIPTS}; do
sed -e "s!\${HOSTNAME}!${HOSTNAME}!g" \
-e "s!\${BORG_DIR}!${BORG_DIR}!g" \ -e "s!\${BORG_DIR}!${BORG_DIR}!g" \
-e "s!\${BORG_BIN}!${BORG_BIN}!g" \
-e "s!\${BACKUP_USER}!${BACKUP_USER}!g" \ -e "s!\${BACKUP_USER}!${BACKUP_USER}!g" \
-e "s!\${BACKUP_HOST}!${BACKUP_HOST}!g" \ -e "s!\${BACKUP_HOST}!${BACKUP_HOST}!g" \
-e "s!\${BACKUP_PORT}!${BACKUP_PORT}!g" \
-e "s!\${BACKUP_REPO}!${BACKUP_REPO}!g" \ -e "s!\${BACKUP_REPO}!${BACKUP_REPO}!g" \
README.md -e "s!\${SYSTEMD_UNIT}!${SYSTEMD_UNIT}!g" \
templates/$i > $i
done
chmod +x ${SCRIPTS}
}
# Update local paths in scripts
update_paths()
{
sed -i\ sed -i\
-e "1c#!${BORG_DIR}/.venv/bin/python" \ -e "1c#!${BORG_DIR}/.venv/bin/python" \
backup.py backup.py
} }
if [ "$1" == "--update-paths" ] ; then # See if we're just supposed to update an existing install, or recovering
parse_args()
{
RECOVER=0
UPDATE=0
if [ "$1" == "--recover" ] ; then
if [ -e "vars.sh" ]; then if [ -e "vars.sh" ]; then
echo "Updating paths" error "It looks like this borg was already set up, can only recover from fresh start"
update_paths
exit 0
else
echo "Can't update paths, not set up yet"
exit 1
fi
fi fi
RECOVER=1
if [ -e "vars.sh" ]; then elif [ "$1" == "--update-paths" ] || [ "$1" == "--update" ] ; then
echo "Error: BORG_DIR $BORG_DIR already looks set up; giving up." if [ ! -e "vars.sh" ]; then
echo "Use \"git clean\" to return it to original state if desired" error "Can't update, not set up yet"
exit 1
fi fi
UPDATE=1
elif [ -n "$1" ] ; then
error "Unknown arg $1"
elif [ -e "vars.sh" ]; then
warn "Error: BORG_DIR $BORG_DIR already looks set up."
warn "Use \"git clean\" to return it to original state if desired".
warn "Or specify --update to refresh things from latest git."
error "Giving up"
fi
}
# Make a temp dir to work in # Make a temp dir to work in
TMP=$(mktemp -d) TMP=$(mktemp -d)
@@ -92,46 +174,6 @@ notice() { msg 32 "$@" ; }
warn() { msg 31 "$@" ; } warn() { msg 31 "$@" ; }
error() { msg 31 "Error:" "$@" ; exit 1 ; } error() { msg 31 "Error:" "$@" ; exit 1 ; }
# Create pip environment
setup_venv()
{
if ! which pipenv >/dev/null 2>&1 ; then
echo "pipenv not found, try: sudo apt install pipenv"
exit 1
fi
mkdir .venv
pipenv install
}
# Create shell script with environment variables
create_borg_vars()
{
VARS=${BORG_DIR}/vars.sh
# These variables are used elsewhere in this script
BORG_REPO="ssh://${BACKUP_USER}@${BACKUP_HOST}/./${BACKUP_REPO}"
BORG=${BORG_DIR}/borg.sh
SSH=$BORG_DIR/ssh
cat >"$VARS" <<EOF
export BACKUP_USER=${BACKUP_USER}
export BACKUP_HOST=${BACKUP_HOST}
export BACKUP_REPO=${BACKUP_REPO}
export HOSTNAME=${HOSTNAME}
export BORG_REPO=${BORG_REPO}
export BORG_HOST_ID=${HOSTID}
export BORG_PASSCOMMAND="cat ${BORG_DIR}/passphrase"
export BORG_DIR=${BORG_DIR}
export SSH=${SSH}
export BORG=${BORG}
export BORG_BIN=${BORG_BIN}
EOF
if ! "$BORG" -h >/dev/null ; then
error "Can't run the borg wrapper; does borg work?"
fi
}
print_random_key() print_random_key()
{ {
dd if=/dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 16 dd if=/dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 16
@@ -139,7 +181,18 @@ print_random_key()
generate_keys() generate_keys()
{ {
if [ $RECOVER -eq 1 ] ; then
notice "Recovering configuration in order to use an existing backup"
read -s -p "Repo key for \"borg ${HOSTNAME}\": " PASS_REPOKEY
echo
read -s -p "Again: " PASS_REPOKEY2
echo
if [ -z "$PASS_REPOKEY" ] || [ $PASS_REPOKEY != $PASS_REPOKEY2 ] ; then
error "Bad repo key"
fi
else
PASS_REPOKEY=$(print_random_key) PASS_REPOKEY=$(print_random_key)
fi
echo "$PASS_REPOKEY" > passphrase echo "$PASS_REPOKEY" > passphrase
chmod 600 passphrase chmod 600 passphrase
} }
@@ -182,15 +235,18 @@ EOF
ssh -F "$SSH/config" -o BatchMode=no -o PubkeyAuthentication=no \ ssh -F "$SSH/config" -o BatchMode=no -o PubkeyAuthentication=no \
-o ControlMaster=yes -o ControlPath="$TMP/ssh-control" \ -o ControlMaster=yes -o ControlPath="$TMP/ssh-control" \
-o StrictHostKeyChecking=accept-new \ -o StrictHostKeyChecking=accept-new \
-p "${BACKUP_PORT}" \
-f "${BACKUP_USER}@${BACKUP_HOST}" sleep 600 -f "${BACKUP_USER}@${BACKUP_HOST}" sleep 600
if ! run_ssh_command true >/dev/null 2>&1 </dev/null ; then if ! run_ssh_command true >/dev/null 2>&1 </dev/null ; then
error "SSH failed" error "SSH failed"
fi fi
log "Connected to ${BACKUP_USER}@${BACKUP_HOST}" log "Connected to ${BACKUP_USER}@${BACKUP_HOST}:${BACKUP_PORT}"
# Since we now have an SSH connection, check that the repo doesn't exist # Since we now have an SSH connection, check repo existence
if run_ssh_command "test -e $BACKUP_REPO" ; then if [ $RECOVER -eq 0 ] && run_ssh_command "test -e $BACKUP_REPO"; then
error "$BACKUP_REPO already exists on the server, bailing out" error "$BACKUP_REPO already exists on the server, bailing out"
elif [ $RECOVER -ne 0 ] && ! run_ssh_command "test -e $BACKUP_REPO"; then
error "$BACKUP_REPO does NOT exist on the server, can't recover backup config"
fi fi
# Copy SSH keys to the server's authorized_keys file, removing any # Copy SSH keys to the server's authorized_keys file, removing any
@@ -212,7 +268,8 @@ EOF
# Test that everything worked # Test that everything worked
log "Testing SSH login with new key" log "Testing SSH login with new key"
if ! ssh -F "$SSH/config" -i "$SSH/id_ecdsa_appendonly" -T \ if ! ssh -F "$SSH/config" -i "$SSH/id_ecdsa_appendonly" -T \
"${BACKUP_USER}@${BACKUP_HOST}" "$REMOTE_BORG" --version </dev/null ; then -p "${BACKUP_PORT}" "${BACKUP_USER}@${BACKUP_HOST}" "$REMOTE_BORG" \
--version </dev/null ; then
error "Logging in with a key failed -- is server set up correctly?" error "Logging in with a key failed -- is server set up correctly?"
fi fi
log "Remote connection OK!" log "Remote connection OK!"
@@ -241,8 +298,8 @@ EOF
configure_systemd() configure_systemd()
{ {
TIMER=borg-backup.timer TIMER=${SYSTEMD_UNIT}.timer
SERVICE=borg-backup.service SERVICE=${SYSTEMD_UNIT}.service
TIMER_UNIT=${BORG_DIR}/${TIMER} TIMER_UNIT=${BORG_DIR}/${TIMER}
SERVICE_UNIT=${BORG_DIR}/${SERVICE} SERVICE_UNIT=${BORG_DIR}/${SERVICE}
@@ -282,6 +339,19 @@ Restart=on-failure
RestartSec=600 RestartSec=600
EOF EOF
if [ $RECOVER -eq 1 ] ; then
log "Partially setting up systemd"
ln -sfv "${TIMER_UNIT}" /etc/systemd/system
ln -sfv "${SERVICE_UNIT}" /etc/systemd/system
systemctl --no-ask-password daemon-reload
systemctl --no-ask-password stop ${TIMER}
systemctl --no-ask-password disable ${TIMER}
warn "Since we're recovering, systemd automatic backups aren't enabled"
warn "Do something like this to configure automatic backups:"
echo " sudo systemctl enable ${TIMER} &&"
echo " sudo systemctl start ${TIMER}"
warn ""
else
log "Setting up systemd" log "Setting up systemd"
if ( if (
ln -sfv "${TIMER_UNIT}" /etc/systemd/system && ln -sfv "${TIMER_UNIT}" /etc/systemd/system &&
@@ -303,36 +373,63 @@ EOF
echo " sudo systemctl start ${TIMER}" echo " sudo systemctl start ${TIMER}"
warn "" warn ""
fi fi
fi
} }
git_setup() git_setup()
{ {
if ! git checkout -b "setup-${HOSTNAME}" ; then log "Committing local changes to git"
if ! git checkout -b "setup-${HOSTNAME}" ||
! git add ${SYSTEMD_UNIT}.service ${SYSTEMD_UNIT}.timer vars.sh ||
! git commit -a -m "autocommit after initial setup on ${HOSTNAME}" ; then
warn "Git setup failed; ignoring" warn "Git setup failed; ignoring"
return return
fi fi
log "Committing local changes to git"
git add README.md borg-backup.service borg-backup.timer vars.sh
git commit -a -m "autocommit after initial setup on ${HOSTNAME}"
} }
log "Configuration:" main() {
log " Backup server host: ${BACKUP_HOST}" if [ $UPDATE -eq 1 ] ; then
log " Backup server user: ${BACKUP_USER}" notice "Non-destructively updating paths, variables, and venv..."
log " Repository path: ${BACKUP_REPO}" source vars.sh
set_default_variables
install_templated_files
update_paths
setup_venv
create_borg_vars
notice "Testing SSH"
ssh -F "$SSH/config" -i "$SSH/id_ecdsa_appendonly" \
-o BatchMode=no -o StrictHostKeyChecking=ask \
-p "${BACKUP_PORT}" "${BACKUP_USER}@${BACKUP_HOST}" info >/dev/null
notice "Testing borg: if host location changed, say 'y' here"
${BORG_DIR}/borg.sh info
notice "Done -- check 'git diff' and verify changes."
exit 0
fi
set_default_variables
setup_venv setup_venv
create_borg_vars create_borg_vars
generate_keys generate_keys
configure_ssh configure_ssh
create_repo [ $RECOVER -eq 0 ] && create_repo
export_keys export_keys
configure_systemd configure_systemd
install_templated_files
update_paths update_paths
git_setup git_setup
echo echo
if [ $RECOVER -eq 1 ] ; then
notice "You should be set up with borg pointing to the existing repo now."
notice "Use commands like these to look at the backup:"
notice " sudo ${BORG_DIR}/borg.sh info"
notice " sudo ${BORG_DIR}/borg.sh list"
notice "You'll want to now restore files like ${BORG_DIR}/config.yaml before enabling systemd timers"
else
notice "Add this password to Bitwarden:" notice "Add this password to Bitwarden:"
notice "" notice ""
notice " Name: borg ${HOSTNAME}" notice " Name: borg ${HOSTNAME}"
@@ -343,7 +440,12 @@ notice "Test the backup file list with"
notice " sudo ${BORG_DIR}/backup.py --dry-run" notice " sudo ${BORG_DIR}/backup.py --dry-run"
notice "and make any necessary adjustments to:" notice "and make any necessary adjustments to:"
notice " ${BORG_DIR}/config.yaml" notice " ${BORG_DIR}/config.yaml"
fi
echo echo
echo "All done" echo "All done"
}
parse_args "$@"
main

View File

@@ -9,41 +9,37 @@ Run on client:
Customize `/opt/borg/config.yaml` as desired. Customize `/opt/borg/config.yaml` as desired.
Cheat sheet Cheat sheet
=========== ===========
*After setup, the copy of this file on the client will have the *After setup, /opt/borg/README.md will have the variables in this
variables in this section filled in automatically* section filled in automatically*
## Configuration ## Configuration
Hostname: ${HOSTNAME} Hostname: ${HOSTNAME}
Base directory: ${BORG_DIR} Base directory: ${BORG_DIR}
Destination: ${BACKUP_USER}@${BACKUP_HOST} Destination: ${BACKUP_USER}@${BACKUP_HOST}:${BACKUP_PORT}
Repository: ${BACKUP_REPO} Repository: ${BACKUP_REPO}
## Commands ## Commands
See when next backup is scheduled: See when next backup is scheduled:
systemctl list-timers borg-backup.timer systemctl list-timers ${SYSTEMD_UNIT}.timer
See status of most recent backup: See log of most recent backup attempt:
systemctl status --full --lines 999999 --no-pager --all borg-backup ${BORG_DIR}/logs.sh
${BORG_DIR}/logs.sh -f
Watch log:
journalctl --all --follow --unit borg-backup
Start backup now: Start backup now:
sudo systemctl start borg-backup sudo systemctl restart ${SYSTEMD_UNIT}
Interrupt backup in progress: Interrupt backup in progress:
sudo systemctl stop borg-backup sudo systemctl stop ${SYSTEMD_UNIT}
Show backups and related info: Show backups and related info:
@@ -61,6 +57,13 @@ Mount and look at files:
sudo -s # to explore as root sudo -s # to explore as root
sudo umount mnt sudo umount mnt
Update borg-setup from git:
cd /opt/borg
sudo git remote update
sudo git rebase origin/master
sudo ./inital-setup.sh --update
sudo git commit -m 'Update borg-setup'
## Compaction and remote access ## Compaction and remote access
@@ -71,13 +74,13 @@ compromised, then you can run compaction manually directly on the
backup host by logging in via SSH (bitwarden `ssh ${BACKUP_HOST} / backup host by logging in via SSH (bitwarden `ssh ${BACKUP_HOST} /
${BACKUP_USER}`) and compacting there: ${BACKUP_USER}`) and compacting there:
ssh ${BACKUP_USER}@${BACKUP_HOST} borg/borg compact --verbose --progress ${BACKUP_REPO} ssh -p ${BACKUP_PORT} ${BACKUP_USER}@${BACKUP_HOST} borg/borg compact --verbose --progress ${BACKUP_REPO}
This doesn't require the repo key. It shouldn't be entered on the untrusted This doesn't require the repo key. That key shouldn't be entered on
backup host, so for operations that need it, use a trusted host and run borg the untrusted backup host, so for operations that need it, use a
remotely instead, e.g.: trusted host and run borg remotely instead, e.g.:
${BORG_DIR}/Borg.bin --remote-path borg/borg info ${BACKUP_USER}@${BACKUP_HOST}:borg/${HOSTNAME} ${BORG_BIN} --remote-path borg/borg info ssh://${BACKUP_USER}@${BACKUP_HOST}:${BACKUP_PORT}/borg/${HOSTNAME}
The repo passphrase is in bitwarden `borg ${HOSTNAME} / repo key`. The repo passphrase is in bitwarden `borg ${HOSTNAME} / repo key`.
@@ -115,11 +118,12 @@ Design
Notes Notes
===== =====
# Building Borg.bin binary from git # Building Borg binary from git
sudo apt install python3.9 scons libacl1-dev libfuse-dev libpython3.9-dev patchelf
git clone https://github.com/borgbackup/borg.git git clone https://github.com/borgbackup/borg.git
cd borg cd borg
virtualenv --python=python3 borg-env virtualenv --python=python3.9 borg-env
source borg-env/bin/activate source borg-env/bin/activate
pip install -r requirements.d/development.txt pip install -r requirements.d/development.txt
pip install pyinstaller pip install pyinstaller
@@ -127,11 +131,15 @@ Notes
pip install -e .[llfuse] pip install -e .[llfuse]
pyinstaller --clean --noconfirm scripts/borg.exe.spec pyinstaller --clean --noconfirm scripts/borg.exe.spec
pip install staticx pip install staticx
staticx -l /lib/x86_64-linux-gnu/libm.so.6 dist/borg.exe Borg.bin
Then see `dist/borg.exe`. Confirm the version with `dist/borg.exe --version`. # for x86
staticx -l /lib/x86_64-linux-gnu/libm.so.6 dist/borg.exe borg.x86_64
# for ARM; see https://github.com/JonathonReinhart/staticx/issues/209
staticx -l /lib/arm-linux-gnueabihf/libm.so.6 dist/borg.exe borg.armv7l
Then run `borg.x86_64`. Confirm the version with `borg.armv7l --version`.
*Note:* This uses the deprecated `llfuse` instead of the newer `pyfuse3`. *Note:* This uses the deprecated `llfuse` instead of the newer `pyfuse3`.
`pyfuse3` doesn't work because, at minimum, it pulls in `trio` which `pyfuse3` doesn't work because, at minimum, it pulls in `trio` which
requires `ssl` which is explicitly excluded by requires `ssl` which is explicitly excluded by `scripts/borg.exe.spec`.
`scripts/borg.exe.spec`.

3
templates/logs.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
exec journalctl --all --unit ${SYSTEMD_UNIT} --since "$(systemctl show -P ExecMainStartTimestamp ${SYSTEMD_UNIT})" "$@"

1
notify.sh → templates/notify.sh Executable file → Normal file
View File

@@ -22,5 +22,6 @@ EMAIL="$2"
ssh \ ssh \
-F "$SSH/config" \ -F "$SSH/config" \
-i "$SSH/id_ecdsa_notify" \ -i "$SSH/id_ecdsa_notify" \
-p "$BACKUP_PORT" \
"$BACKUP_USER@$BACKUP_HOST" \ "$BACKUP_USER@$BACKUP_HOST" \
borg/notify.sh "$EMAIL" borg/notify.sh "$EMAIL"