Skip to content

Commit 18138da

Browse files
mpasternakclaude
andcommitted
Initial commit: bpp-backup.sh
Shell script to pull a full backup of a remote BPP deployment to the local machine. Packages ~/bpp-deploy and $BPP_CONFIGS_DIR (read from the remote .env) into a single ./backup-<host>-<project>-<ts>.tar.gz. Includes MIT license, GitHub Actions CI (bash -n + ShellCheck + pre-commit), and pre-commit config with detect-private-key. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0 parents  commit 18138da

6 files changed

Lines changed: 207 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
shell:
11+
name: Shell syntax + ShellCheck
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: bash -n
17+
run: bash -n bpp-backup.sh
18+
19+
- name: ShellCheck
20+
uses: ludeeus/action-shellcheck@2.0.0
21+
with:
22+
scandir: '.'
23+
severity: warning
24+
25+
pre-commit:
26+
name: Pre-commit hooks
27+
runs-on: ubuntu-latest
28+
steps:
29+
- uses: actions/checkout@v4
30+
- uses: actions/setup-python@v5
31+
with:
32+
python-version: '3.12'
33+
- uses: pre-commit/action@v3.0.1

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
backup*tar.gz

.pre-commit-config.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v5.0.0
4+
hooks:
5+
- id: trailing-whitespace
6+
- id: end-of-file-fixer
7+
- id: check-yaml
8+
- id: check-merge-conflict
9+
- id: check-added-large-files
10+
args: ['--maxkb=500']
11+
- id: detect-private-key
12+
13+
- repo: https://github.com/shellcheck-py/shellcheck-py
14+
rev: v0.10.0.1
15+
hooks:
16+
- id: shellcheck
17+
args: ['-e', 'SC1091,SC2086']

CLAUDE.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## What this repo is
6+
7+
A single-purpose helper for pulling backups of a remote BPP (Bibliografia Publikacji Pracowników) deployment to the local machine. The entire codebase is one shell script: `bpp-backup.sh`. Backup archives produced by the script (`backup-*.tar.gz`) live alongside it.
8+
9+
## How the backup script works
10+
11+
`./bpp-backup.sh <ssh-host>` connects to the given host and:
12+
13+
1. SSHes in and reads `$HOME/bpp-deploy/.env`, extracting `BPP_CONFIGS_DIR` and `COMPOSE_PROJECT_NAME` (falling back to `basename "$BPP_CONFIGS_DIR"` when the latter is unset — this matches the convention documented in the upstream `bpp-deploy/.env.sample`).
14+
2. Streams a single `tar -czf -` over SSH that packages **two** directories: `$HOME/bpp-deploy` and `$BPP_CONFIGS_DIR`. Two `-C` flags are used so the archive contains `bpp-deploy/...` and `<configs-basename>/...` at the top level (no `/home/<user>/` prefix).
15+
3. Writes the stream locally to `./backup-<host>-<compose_project>-<YYYYMMDD-HHMMSS>.tar.gz` via a `.partial` file that is `mv`'d into place only on success; an `EXIT` trap cleans up the partial on failure.
16+
17+
The remote host is assumed to follow the upstream layout: `bpp-deploy` checked out at `$HOME/bpp-deploy`, with `.env` defining `BPP_CONFIGS_DIR` pointing at a configs directory **outside** the deploy repo. Reference layout (for understanding the format) lives at `/Users/mpasternak/Programowanie/bpp-deploy/` on this machine.
18+
19+
## Common commands
20+
21+
- Run a backup: `./bpp-backup.sh deploy@host`
22+
- Syntax check before committing changes: `bash -n bpp-backup.sh`
23+
- Inspect a produced archive: `tar -tzf backup-<host>-<project>-<timestamp>.tar.gz | head`
24+
25+
## Conventions when editing the script
26+
27+
- Keep `set -euo pipefail` and the `trap 'rm -f "$PARTIAL"' EXIT` (cleared with `trap - EXIT` on success) — together they guarantee no half-written archive masquerades as a real backup.
28+
- The remote-side logic runs inside heredocs passed to `ssh "$HOST" 'bash -s'`. Watch the heredoc quoting carefully: the env-reading heredoc uses `<<'REMOTE'` (no expansion, everything resolved on the remote), but the tar heredoc uses unquoted `<<REMOTE_TAR` so that `${BPP_CONFIGS_DIR}` is interpolated locally while `\$HOME`, `\$(dirname …)`, etc. are escaped to run remotely.
29+
- Per the user's global instructions: never silently swallow errors. Any new `if`/`grep`/parse step that can fail must either log+exit or propagate the error.
30+
31+
## License
32+
33+
MIT — see `LICENSE` at the repo root. Copyright © 2026 Michał Pasternak.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Michał Pasternak
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

bpp-backup.sh

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env bash
2+
#
3+
# bpp-backup.sh — pobiera lokalnie kopię zapasową instancji BPP ze zdalnego hosta.
4+
#
5+
# Backup obejmuje:
6+
# - $HOME/bpp-deploy na zdalnym hoście
7+
# - katalog wskazany przez BPP_CONFIGS_DIR z $HOME/bpp-deploy/.env
8+
#
9+
# Wynik: ./backup-<host>-<compose_project>-<YYYYMMDD-HHMMSS>.tar.gz
10+
#
11+
# Użycie: ./bpp-backup.sh <host-ssh>
12+
13+
set -euo pipefail
14+
15+
if [ "$#" -lt 1 ] || [ -z "${1:-}" ]; then
16+
echo "Użycie: $0 <host-ssh>" >&2
17+
echo "Przykład: $0 deploy@bpp.uczelnia.pl" >&2
18+
exit 1
19+
fi
20+
21+
HOST="$1"
22+
23+
echo "==> Odczytuję ~/bpp-deploy/.env z ${HOST}..." >&2
24+
25+
remote_env=$(ssh "$HOST" 'bash -s' <<'REMOTE'
26+
set -euo pipefail
27+
ENV_FILE="$HOME/bpp-deploy/.env"
28+
if [ ! -f "$ENV_FILE" ]; then
29+
echo "ERR: brak $ENV_FILE na zdalnym hoscie" >&2
30+
exit 1
31+
fi
32+
33+
get() {
34+
grep -E "^$1=" "$ENV_FILE" | tail -n1 | cut -d= -f2- \
35+
| sed -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'$/\1/"
36+
}
37+
38+
BPP_CONFIGS_DIR=$(get BPP_CONFIGS_DIR)
39+
COMPOSE_PROJECT_NAME=$(get COMPOSE_PROJECT_NAME)
40+
41+
if [ -z "$BPP_CONFIGS_DIR" ]; then
42+
echo "ERR: BPP_CONFIGS_DIR puste w $ENV_FILE" >&2
43+
exit 1
44+
fi
45+
if [ ! -d "$BPP_CONFIGS_DIR" ]; then
46+
echo "ERR: katalog $BPP_CONFIGS_DIR nie istnieje na zdalnym hoscie" >&2
47+
exit 1
48+
fi
49+
50+
if [ -z "$COMPOSE_PROJECT_NAME" ]; then
51+
COMPOSE_PROJECT_NAME=$(basename "$BPP_CONFIGS_DIR")
52+
fi
53+
54+
printf 'BPP_CONFIGS_DIR=%s\n' "$BPP_CONFIGS_DIR"
55+
printf 'COMPOSE_PROJECT_NAME=%s\n' "$COMPOSE_PROJECT_NAME"
56+
REMOTE
57+
)
58+
59+
BPP_CONFIGS_DIR=$(printf '%s\n' "$remote_env" | sed -n 's/^BPP_CONFIGS_DIR=//p')
60+
COMPOSE_PROJECT_NAME=$(printf '%s\n' "$remote_env" | sed -n 's/^COMPOSE_PROJECT_NAME=//p')
61+
62+
if [ -z "$BPP_CONFIGS_DIR" ] || [ -z "$COMPOSE_PROJECT_NAME" ]; then
63+
echo "ERR: nie udało się odczytać zmiennych ze zdalnego .env" >&2
64+
exit 1
65+
fi
66+
67+
echo " BPP_CONFIGS_DIR=${BPP_CONFIGS_DIR}" >&2
68+
echo " COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME}" >&2
69+
70+
TS=$(date +%Y%m%d-%H%M%S)
71+
HOST_TAG=$(printf '%s' "$HOST" | tr '@:/' '___')
72+
OUT="./backup-${HOST_TAG}-${COMPOSE_PROJECT_NAME}-${TS}.tar.gz"
73+
PARTIAL="${OUT}.partial"
74+
75+
trap 'rm -f "$PARTIAL"' EXIT
76+
77+
echo "==> Pakuję zdalnie i pobieram do ${OUT}..." >&2
78+
79+
# shellcheck disable=SC2087 # heredoc celowo niesquotowane: ${BPP_CONFIGS_DIR}
80+
# rozwija się lokalnie, a \$HOME / \$CONFIGS_DIR na zdalnym hoście.
81+
ssh "$HOST" 'bash -s' > "$PARTIAL" <<REMOTE_TAR
82+
set -euo pipefail
83+
CONFIGS_DIR="${BPP_CONFIGS_DIR}"
84+
CONFIGS_PARENT=\$(dirname "\$CONFIGS_DIR")
85+
CONFIGS_BASE=\$(basename "\$CONFIGS_DIR")
86+
# Każde -C zmienia katalog roboczy tar-a, więc w archiwum zapisujemy
87+
# wyłącznie nazwy bazowe (bpp-deploy/, <configs>/) bez prefiksu /home/...
88+
tar -czf - \\
89+
-C "\$HOME" bpp-deploy \\
90+
-C "\$CONFIGS_PARENT" "\$CONFIGS_BASE"
91+
REMOTE_TAR
92+
93+
if [ ! -s "$PARTIAL" ]; then
94+
echo "ERR: pusty plik backupu — coś poszło nie tak po stronie SSH/tar" >&2
95+
exit 1
96+
fi
97+
98+
mv "$PARTIAL" "$OUT"
99+
trap - EXIT
100+
101+
echo "==> Gotowe: ${OUT}" >&2
102+
du -h "$OUT" >&2

0 commit comments

Comments
 (0)