Add gitea-host backup scripts

This commit is contained in:
alejo committed 2026-10-17 04:10:15 -03:00
1 parent a6ad39e605
commit 8a8edf9fcb
2 files changed
+503 -30

No files matched your search

+413
View File
@@ -0,0 +1,413 @@
#!/usr/bin/env bash
#
# backup_create.sh
#
# Companion to setup-git-server.sh: creates ONE zip file that contains
# everything needed to bring a Gitea instance installed by that script back
# with backup_restore.sh:
#
# - the database (as a portable SQL dump, SQLite)
# - all repositories
# - data/ (LFS objects, attachments, avatars, packages, ...)
# - custom/ (templates, logos, ...)
# - app.ini (including SECRET_KEY / JWT secrets -- needed for 2FA etc.)
# - gitea-backup-info.txt (Gitea version, date, host -- lets the restore
# refuse a too-old Gitea *before* it touches anything)
#
# It uses Gitea's own `gitea dump`, so the archive is also a perfectly normal
# dump that `gitea doctor`/manual restores understand.
#
# By default Gitea is stopped while the dump runs, so the database and the
# repositories are consistent with each other, and started again right after
# (also if something goes wrong). Use --no-stop to back up a running server.
#
# Usage:
# sudo ./backup_create.sh [options]
#
# Run with --help for the full list of options.
set -euo pipefail
# ----------------------------------------------------------------------------
# Defaults (same variables as setup-git-server.sh, overridable via env vars)
# ----------------------------------------------------------------------------
GITEA_USER="${GITEA_USER:-git}"
GITEA_HOME="${GITEA_HOME:-/home/${GITEA_USER}}"
GITEA_WORK_DIR="${GITEA_WORK_DIR:-/var/lib/gitea}"
GITEA_CONFIG_DIR="${GITEA_CONFIG_DIR:-/etc/gitea}"
GITEA_CONFIG="${GITEA_CONFIG_DIR}/app.ini"
GITEA_BIN="/usr/local/bin/gitea"
OUTPUT=""
KEEP=""
STOP_SERVICE="true"
STAGE_BASE="${STAGE_BASE:-}"
# Filled in while the script runs
DISTRO_FAMILY=""
GITEA_GROUP=""
OUTPUT_DIR=""
OUTPUT_FILE=""
FINAL=""
PARTIAL=""
STAGE=""
STOPPED="false"
BACKUP_VERSION=""
# ----------------------------------------------------------------------------
# Small helpers
# ----------------------------------------------------------------------------
C_GREEN='\033[0;32m'; C_YELLOW='\033[0;33m'; C_RED='\033[0;31m'; C_RESET='\033[0m'
log() { printf '%b\n' "${C_GREEN}[+]${C_RESET} $*"; }
warn() { printf '%b\n' "${C_YELLOW}[!]${C_RESET} $*" >&2; }
error_exit() { printf '%b\n' "${C_RED}[x]${C_RESET} $*" >&2; exit 1; }
start_service_if_stopped() {
# Idempotent: only acts if *this script* stopped the service.
if [[ "$STOPPED" == "true" ]]; then
STOPPED="false"
log "Starting Gitea again..."
systemctl start gitea \
|| warn "Could not start Gitea -- start it by hand: systemctl start gitea"
fi
}
cleanup() {
local rc=$?
# Whatever happened, don't leave the server down.
start_service_if_stopped || true
if [[ -n "${STAGE:-}" && -d "$STAGE" ]]; then
rm -rf -- "$STAGE"
fi
if [[ -n "${PARTIAL:-}" ]]; then
rm -f -- "$PARTIAL"
fi
if [[ $rc -ne 0 ]]; then
warn "The backup did not complete; no backup file was written."
fi
}
trap cleanup EXIT
print_help() {
cat <<'EOF'
Usage: sudo ./backup_create.sh [options]
Creates a single zip file with the whole Gitea instance (database,
repositories, data, custom files, app.ini + secrets). Restore it with
backup_restore.sh.
Options:
-o, --output <path> Where to put the backup. A folder gets a file named
gitea-backup-<host>-<date>-<time>.zip inside it; a path
ending in .zip is used as the exact file name.
(default: the current directory)
--keep <n> After a successful backup, delete all but the <n> newest
gitea-backup-*.zip files in the output folder (for cron).
--no-stop Don't stop Gitea during the dump. No downtime, but the
database and repositories may differ slightly if
someone pushes while the dump runs.
--tmp-dir <dir> Where the archive is built before it is moved to its
final place (default: the Gitea work dir). Needs free
space of about the size of the backup and must be
reachable by the Gitea service user.
-h, --help Show this help and exit
The environment variables of setup-git-server.sh (GITEA_USER, GITEA_HOME,
GITEA_WORK_DIR, GITEA_CONFIG_DIR) are honoured if you used non-default ones.
The backup contains secrets (app.ini, SECRET_KEY, ...). It is written with
mode 600; keep it somewhere private.
Examples:
sudo ./backup_create.sh
sudo ./backup_create.sh -o /mnt/usb/backups --keep 7
sudo ./backup_create.sh -o /root/gitea-before-upgrade.zip
EOF
}
require_root() {
if [[ "${EUID}" -ne 0 ]]; then
error_exit "Please run this script as root, e.g.: sudo $0"
fi
command -v systemctl &>/dev/null || error_exit "This script requires systemd."
[[ -d /run/systemd/system ]] \
|| error_exit "systemd is not running as init here (container/chroot/WSL?). Run this on a real VM or host."
}
need_arg() { # usage: need_arg "$@" -- an option that takes a value must have one
[[ $# -ge 2 ]] || error_exit "Option $1 needs a value (see --help)"
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output) need_arg "$@"; OUTPUT="$2"; shift 2 ;;
--keep) need_arg "$@"; KEEP="$2"; shift 2 ;;
--no-stop) STOP_SERVICE="false"; shift ;;
--tmp-dir) need_arg "$@"; STAGE_BASE="$(readlink -f -- "$2")"; shift 2 ;;
-h|--help) print_help; exit 0 ;;
*) error_exit "Unknown option: $1 (see --help)" ;;
esac
done
if [[ -n "$KEEP" ]]; then
[[ "$KEEP" =~ ^[0-9]+$ ]] && (( 10#$KEEP >= 1 )) \
|| error_exit "Invalid --keep value '${KEEP}' (expected a number >= 1)."
KEEP=$((10#$KEEP))
[[ "${OUTPUT,,}" != *.zip ]] \
|| error_exit "--keep needs an output *folder*, not a fixed .zip file name."
fi
}
# ----------------------------------------------------------------------------
# app.ini helper (section-aware)
# ----------------------------------------------------------------------------
ini_get() { # usage: ini_get FILE SECTION KEY (SECTION "" = top of the file)
local file="$1" section="$2" key="$3"
[[ -r "$file" ]] || return 0
awk -v want="$section" -v key="$key" '
/^[[:space:]]*[;#]/ { next }
/^[[:space:]]*\[/ { s = $0; gsub(/^[[:space:]]*\[|\][[:space:]]*$/, "", s); cur = s; next }
tolower(cur) == tolower(want) {
i = index($0, "=")
if (!i) next
k = substr($0, 1, i - 1); gsub(/[[:space:]]/, "", k)
if (k == key) {
v = substr($0, i + 1)
sub(/^[[:space:]]+/, "", v); sub(/[[:space:]]+$/, "", v)
print v; exit
}
}' "$file"
}
# ----------------------------------------------------------------------------
# Detection helpers / dependencies
# ----------------------------------------------------------------------------
detect_distro_family() {
[[ -r /etc/os-release ]] || error_exit "Cannot read /etc/os-release; unsupported system."
# shellcheck disable=SC1091
. /etc/os-release
case "${ID:-}" in
ubuntu|debian|linuxmint|pop|raspbian) DISTRO_FAMILY="debian" ;;
fedora|rhel|centos|rocky|almalinux|nobara) DISTRO_FAMILY="fedora" ;;
arch|archarm|manjaro|endeavouros) DISTRO_FAMILY="arch" ;;
*)
case "${ID_LIKE:-}" in
*debian*) DISTRO_FAMILY="debian" ;;
*fedora*|*rhel*) DISTRO_FAMILY="fedora" ;;
*arch*) DISTRO_FAMILY="arch" ;;
*) error_exit "Unsupported distro: ${PRETTY_NAME:-${ID:-unknown}}. This script supports Arch, Debian, Ubuntu and Fedora." ;;
esac
;;
esac
}
install_dependencies() {
# Only what's missing: zip (to add the info file) and unzip (to verify).
local pkgs=()
command -v zip &>/dev/null || pkgs+=(zip)
command -v unzip &>/dev/null || pkgs+=(unzip)
(( ${#pkgs[@]} )) || return 0
detect_distro_family
log "Installing dependencies: ${pkgs[*]}..."
case "$DISTRO_FAMILY" in
debian)
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y "${pkgs[@]}"
;;
fedora) dnf install -y "${pkgs[@]}" ;;
# Never do a partial upgrade (-Sy without -u) on Arch.
arch) pacman -Syu --noconfirm --needed "${pkgs[@]}" ;;
esac
}
run_as_gitea() {
# Run a command as the service user, from a directory it can read, with the
# same work dir the service uses.
( cd "$GITEA_WORK_DIR" && sudo -H -u "$GITEA_USER" env GITEA_WORK_DIR="$GITEA_WORK_DIR" "$@" )
}
gitea_version() {
"$GITEA_BIN" --version 2>/dev/null \
| sed -nE 's/^Gitea version v?([0-9]+(\.[0-9]+)+).*/\1/p' \
| head -n1 || true
}
# ----------------------------------------------------------------------------
# Backup steps
# ----------------------------------------------------------------------------
check_installation() {
[[ -x "$GITEA_BIN" ]] \
|| error_exit "Gitea is not installed at ${GITEA_BIN}. Nothing to back up."
[[ -r "$GITEA_CONFIG" ]] \
|| error_exit "${GITEA_CONFIG} not found. Nothing to back up."
id -u "$GITEA_USER" &>/dev/null \
|| error_exit "User '${GITEA_USER}' does not exist."
command -v sudo &>/dev/null || error_exit "sudo is required (setup-git-server.sh installs it)."
GITEA_HOME="$(getent passwd "$GITEA_USER" | cut -d: -f6)"
GITEA_GROUP="$(id -gn "$GITEA_USER")"
local db_type
db_type="$(ini_get "$GITEA_CONFIG" database DB_TYPE | tr -d '"`')"
if [[ "${db_type,,}" != "sqlite3" ]]; then
warn "DB_TYPE is '${db_type}': the backup will work, but backup_restore.sh can only restore SQLite instances."
fi
}
resolve_output() {
local ts host
ts="$(date +%Y%m%d-%H%M%S)"
host="$(printf '%s' "$(uname -n)" | tr -c 'A-Za-z0-9._-' '_')"
if [[ -z "$OUTPUT" ]]; then
OUTPUT_DIR="$PWD"
OUTPUT_FILE="gitea-backup-${host}-${ts}.zip"
elif [[ "${OUTPUT,,}" == *.zip ]]; then
OUTPUT_DIR="$(dirname -- "$OUTPUT")"
OUTPUT_FILE="$(basename -- "$OUTPUT")"
else
OUTPUT_DIR="$OUTPUT"
OUTPUT_FILE="gitea-backup-${host}-${ts}.zip"
fi
mkdir -p -- "$OUTPUT_DIR"
OUTPUT_DIR="$(cd -- "$OUTPUT_DIR" && pwd -P)"
FINAL="${OUTPUT_DIR}/${OUTPUT_FILE}"
[[ ! -e "$FINAL" ]] || error_exit "${FINAL} already exists; refusing to overwrite it."
}
stop_service() {
if [[ "$STOP_SERVICE" != "true" ]]; then
warn "Backing up while Gitea keeps running (--no-stop)."
return 0
fi
if systemctl is-active --quiet gitea 2>/dev/null; then
log "Stopping Gitea so the backup is consistent..."
systemctl stop gitea
STOPPED="true"
fi
}
create_dump() {
: "${STAGE_BASE:=$GITEA_WORK_DIR}"
mkdir -p -- "$STAGE_BASE"
STAGE="$(mktemp -d -p "$STAGE_BASE" .backup.XXXXXX)"
# The dump runs as the service user (it reads app.ini via the group), so it
# must own the folder it writes into.
chown "${GITEA_USER}:${GITEA_GROUP}" "$STAGE"
chmod 700 "$STAGE"
log "Running 'gitea dump' (this can take a while for big instances)..."
run_as_gitea "$GITEA_BIN" --config "$GITEA_CONFIG" dump \
--file "$STAGE/dump.zip" --type zip --tempdir "$STAGE" --skip-log \
|| error_exit "'gitea dump' failed -- see the error above."
[[ -s "$STAGE/dump.zip" ]] || error_exit "'gitea dump' did not produce a file."
}
add_manifest() {
local info="${STAGE}/gitea-backup-info.txt"
BACKUP_VERSION="$(gitea_version)"
# Plain KEY=VALUE, read back (never sourced) by backup_restore.sh.
cat > "$info" <<EOF
# Written by backup_create.sh -- read by backup_restore.sh
FORMAT=1
CREATED_UTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)
HOSTNAME=$(uname -n)
GITEA_VERSION=${BACKUP_VERSION}
DOMAIN=$(ini_get "$GITEA_CONFIG" server DOMAIN)
DB_TYPE=$(ini_get "$GITEA_CONFIG" database DB_TYPE)
EOF
(cd "$STAGE" && zip -q -g dump.zip gitea-backup-info.txt) \
|| error_exit "Could not add the info file to the archive."
}
verify_archive() {
log "Verifying the archive..."
unzip -tq "$STAGE/dump.zip" >/dev/null \
|| error_exit "The archive failed its integrity test."
# (awk reads everything; 'grep -q' could end the pipe early and trip pipefail.)
unzip -Z1 "$STAGE/dump.zip" \
| awk '$0 == "gitea-db.sql" { found = 1 } END { exit !found }' \
|| error_exit "The archive contains no gitea-db.sql -- the dump is incomplete."
}
deliver_archive() {
# Move to the destination under a temporary name first, so a half-copied
# file never looks like a finished backup.
PARTIAL="${FINAL}.partial"
mv -- "$STAGE/dump.zip" "$PARTIAL"
chown root:root "$PARTIAL"
chmod 600 "$PARTIAL"
# When started through sudo, hand the file to the person who ran it.
if [[ -n "${SUDO_USER:-}" && "$SUDO_USER" != "root" ]] && id -u "$SUDO_USER" &>/dev/null; then
chown -- "$SUDO_USER" "$PARTIAL"
fi
mv -- "$PARTIAL" "$FINAL"
PARTIAL=""
}
prune_old() {
[[ -n "$KEEP" ]] || return 0
local entry n=0
while IFS= read -r -d '' entry; do
n=$((n + 1))
(( n > KEEP )) || continue
rm -f -- "${entry#* }"
log "Removed old backup: ${entry#* }"
done < <(find "$OUTPUT_DIR" -maxdepth 1 -type f -name 'gitea-backup-*.zip' -printf '%T@ %p\0' | sort -z -rn)
}
print_summary() {
local size
size="$(du -h -- "$FINAL" | awk '{print $1}')"
cat <<EOF
============================================================================
Backup created
============================================================================
File ${FINAL}
Size ${size}
Gitea ${BACKUP_VERSION:-unknown}
Contains the database, all repositories, data (LFS, attachments, avatars),
custom files and app.ini with its secrets -- keep it private.
Restore it with:
sudo ./backup_restore.sh ${FINAL}
On a new machine, first install the same (or a newer) Gitea:
sudo ./setup-git-server.sh --version ${BACKUP_VERSION:-<x.y.z>}
============================================================================
EOF
}
# ----------------------------------------------------------------------------
# Main
# ----------------------------------------------------------------------------
main() {
require_root
install_dependencies
check_installation
resolve_output
log "Backing up to ${FINAL}"
stop_service
create_dump
start_service_if_stopped # downtime ends here; the rest doesn't need it down
add_manifest
verify_archive
deliver_archive
prune_old
print_summary
}
# Only run when executed, not when sourced (handy for testing single functions).
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
parse_args "$@"
main
fi
@@ -1,9 +1,10 @@
#!/usr/bin/env bash
#
# restore-gitea-dump.sh
# backup_restore.sh
#
# Companion to setup-git-server.sh: restores a backup made with `gitea dump`
# into a Gitea instance that was installed by that script, keeping its layout:
# Companion to setup-git-server.sh and backup_create.sh: restores a backup zip
# made by backup_create.sh (or any plain `gitea dump` archive) into a Gitea
# instance that was installed by setup-git-server.sh, keeping its layout:
#
# binary /usr/local/bin/gitea
# config /etc/gitea/app.ini
@@ -12,23 +13,25 @@
# database SQLite
#
# What it does:
# 1. unpacks the dump into a staging directory and imports its SQL into a
# 1. unpacks the backup into a staging directory, checks that the installed
# Gitea is not older than the one that made it, and imports the SQL into a
# brand-new SQLite file there -- nothing on the server is touched until
# that has worked;
# all of that has worked;
# 2. stops Gitea and moves the current data / custom / repositories aside
# (as <name>.bak.<timestamp>);
# 3. puts the dump's data, custom files, repositories and database in place;
# 4. copies the secrets (SECRET_KEY, JWT secrets, ...) from the dump's app.ini
# into the current one, so 2FA, OAuth2 and tokens keep working, while the
# domain / ports / paths of this server stay as they are;
# 3. puts the backup's data, custom files, repositories and database in place;
# 4. copies the secrets (SECRET_KEY, JWT secrets, ...) from the backup's
# app.ini into the current one, so 2FA, OAuth2 and tokens keep working,
# while the domain / ports / paths of this server stay as they are;
# 5. runs 'gitea migrate', regenerates the repo hooks and authorized_keys,
# and starts Gitea again.
#
# Install a Gitea version equal to (or newer than) the one that made the dump:
# On a fresh machine, install a Gitea version equal to (or newer than) the one
# that made the backup first (backup_create.sh prints the exact command):
# sudo ./setup-git-server.sh --version <x.y.z>
#
# Usage:
# sudo ./restore-gitea-dump.sh [options] <gitea-dump-file>
# sudo ./backup_restore.sh [options] <backup-file>
#
# Run with --help for the full list of options.
@@ -56,6 +59,10 @@ DB_PATH=""
STAGE=""
DUMP_ROOT=""
DUMP_INI=""
MANIFEST=""
BACKUP_VERSION=""
BACKUP_CREATED=""
BACKUP_HOST=""
DUMP_USERS="?"
DUMP_REPOS="?"
TS=""
@@ -87,21 +94,22 @@ trap cleanup EXIT
print_help() {
cat <<'EOF'
Usage: sudo ./restore-gitea-dump.sh [options] <gitea-dump-file>
Usage: sudo ./backup_restore.sh [options] <backup-file>
Restores a `gitea dump` archive (.zip, .tar, .tar.gz, .tar.bz2, .tar.xz,
.tar.zst) into a Gitea instance installed by setup-git-server.sh.
Restores a backup made by backup_create.sh (a .zip; plain `gitea dump`
archives in .zip, .tar, .tar.gz, .tar.bz2, .tar.xz or .tar.zst work too)
into a Gitea instance installed by setup-git-server.sh.
Options:
-y, --yes Don't ask for confirmation
--tmp-dir <dir> Where the dump is unpacked (default: the Gitea work
dir; needs free space of about twice the dump size)
--tmp-dir <dir> Where the backup is unpacked (default: the Gitea work
dir; needs free space of about twice the backup size)
-h, --help Show this help and exit
The environment variables of setup-git-server.sh (GITEA_USER, GITEA_HOME,
GITEA_WORK_DIR, GITEA_CONFIG_DIR) are honoured if you used non-default ones.
Only SQLite dumps can be restored (that is what setup-git-server.sh uses).
Only SQLite backups can be restored (that is what setup-git-server.sh uses).
EOF
}
@@ -126,7 +134,7 @@ parse_args() {
-h|--help) print_help; exit 0 ;;
-*) error_exit "Unknown option: $1 (see --help)" ;;
*)
[[ -z "$DUMP_FILE" ]] || error_exit "Only one dump file can be given (got '${DUMP_FILE}' and '$1')."
[[ -z "$DUMP_FILE" ]] || error_exit "Only one backup file can be given (got '${DUMP_FILE}' and '$1')."
DUMP_FILE="$1"; shift ;;
esac
done
@@ -134,7 +142,7 @@ parse_args() {
print_help >&2
exit 1
fi
[[ -f "$DUMP_FILE" && -r "$DUMP_FILE" ]] || error_exit "Cannot read dump file: ${DUMP_FILE}"
[[ -f "$DUMP_FILE" && -r "$DUMP_FILE" ]] || error_exit "Cannot read backup file: ${DUMP_FILE}"
DUMP_FILE="$(readlink -f -- "$DUMP_FILE")"
}
@@ -181,6 +189,24 @@ ini_set() { # usage: ini_set FILE SECTION KEY VALUE -- fails if KEY isn't in
rm -f "$out"
}
# ----------------------------------------------------------------------------
# Backup info file / version helpers
# ----------------------------------------------------------------------------
manifest_get() { # usage: manifest_get KEY -- read from gitea-backup-info.txt, never sourced
[[ -n "$MANIFEST" && -r "$MANIFEST" ]] || return 0
sed -nE "s/^$1=(.*)\$/\\1/p" "$MANIFEST" | head -n1 || true
}
installed_version() {
"$GITEA_BIN" --version 2>/dev/null \
| sed -nE 's/^Gitea version v?([0-9]+(\.[0-9]+)+).*/\1/p' \
| head -n1 || true
}
version_lt() { # usage: version_lt A B -> true if A is an older version than B
[[ "$1" != "$2" && "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" == "$1" ]]
}
# ----------------------------------------------------------------------------
# Detection helpers / dependencies
# ----------------------------------------------------------------------------
@@ -270,7 +296,7 @@ extract_dump() {
STAGE="$(mktemp -d -p "$STAGE_BASE" .restore.XXXXXX)"
mkdir -p -- "$STAGE/dump"
log "Unpacking $(basename -- "$DUMP_FILE") (this can take a while for big dumps)..."
log "Unpacking $(basename -- "$DUMP_FILE") (this can take a while for big backups)..."
case "${DUMP_FILE,,}" in
*.zip)
unzip -q -o "$DUMP_FILE" -d "$STAGE/dump" ;;
@@ -284,10 +310,38 @@ extract_dump() {
local sql
sql="$(find "$STAGE/dump" -maxdepth 2 -name gitea-db.sql -print -quit)"
[[ -n "$sql" ]] \
|| error_exit "No gitea-db.sql in the archive -- is this a 'gitea dump' file (made without --skip-db)?"
|| error_exit "No gitea-db.sql in the archive -- is this a backup from backup_create.sh / 'gitea dump' (made without --skip-db)?"
DUMP_ROOT="$(dirname -- "$sql")"
}
check_backup_version() {
# Gitea can migrate a database forward, never backward -- so find out now,
# not after the current data has been moved aside.
if [[ ! -f "$DUMP_ROOT/gitea-backup-info.txt" ]]; then
warn "No gitea-backup-info.txt (not made by backup_create.sh?): can't check the Gitea version up front."
return 0
fi
MANIFEST="$DUMP_ROOT/gitea-backup-info.txt"
BACKUP_VERSION="$(manifest_get GITEA_VERSION)"
BACKUP_CREATED="$(manifest_get CREATED_UTC)"
BACKUP_HOST="$(manifest_get HOSTNAME)"
local current
current="$(installed_version)"
if [[ -z "$BACKUP_VERSION" || -z "$current" ]]; then
warn "Could not determine the Gitea versions to compare (backup: '${BACKUP_VERSION:-?}', installed: '${current:-?}')."
return 0
fi
if version_lt "$current" "$BACKUP_VERSION"; then
error_exit "This backup was made with Gitea ${BACKUP_VERSION}, but ${current} is installed, and a database can't be migrated to an older release.
Install the same or a newer version first: sudo ./setup-git-server.sh --version ${BACKUP_VERSION}
Nothing was changed."
fi
if version_lt "$BACKUP_VERSION" "$current"; then
log "Backup is from Gitea ${BACKUP_VERSION}; its database will be migrated to ${current}."
fi
}
inspect_dump() {
local f
for f in "$DUMP_ROOT/app.ini" "$DUMP_ROOT/data/conf/app.ini"; do
@@ -298,10 +352,10 @@ inspect_dump() {
local src_db_type
src_db_type="$(ini_get "$DUMP_INI" database DB_TYPE | tr -d '"`')"
if [[ -n "$src_db_type" && "${src_db_type,,}" != "sqlite3" ]]; then
error_exit "The dump comes from a '${src_db_type}' database. Only SQLite dumps can be restored by this script."
error_exit "The backup comes from a '${src_db_type}' database. Only SQLite backups can be restored by this script."
fi
else
warn "No app.ini in the dump: SECRET_KEY can't be carried over, so 2FA secrets and similar will not work after the restore."
warn "No app.ini in the backup: SECRET_KEY can't be carried over, so 2FA secrets and similar will not work after the restore."
fi
# Dumps made by old Gitea versions keep the repositories in a nested zip.
@@ -309,7 +363,7 @@ inspect_dump() {
log "Unpacking the nested gitea-repo.zip..."
unzip -q -o "$DUMP_ROOT/gitea-repo.zip" -d "$DUMP_ROOT/repos"
fi
[[ -d "$DUMP_ROOT/repos" ]] || warn "The dump contains no repositories directory."
[[ -d "$DUMP_ROOT/repos" ]] || warn "The backup contains no repositories directory."
}
stage_database() {
@@ -317,14 +371,19 @@ stage_database() {
sqlite3 -bail "$STAGE/gitea.db" < "$DUMP_ROOT/gitea-db.sql" \
|| error_exit "Importing gitea-db.sql into SQLite failed -- see the error above. Nothing was changed."
DUMP_USERS="$(sqlite3 "$STAGE/gitea.db" 'SELECT COUNT(*) FROM "user";' 2>/dev/null)" \
|| error_exit "The imported database has no 'user' table -- this doesn't look like a Gitea dump. Nothing was changed."
|| error_exit "The imported database has no 'user' table -- this doesn't look like a Gitea backup. Nothing was changed."
DUMP_REPOS="$(sqlite3 "$STAGE/gitea.db" 'SELECT COUNT(*) FROM repository;' 2>/dev/null || echo '?')"
}
confirm_restore() {
local origin=""
if [[ -n "$BACKUP_CREATED" || -n "$BACKUP_HOST" ]]; then
origin="Made on ${BACKUP_HOST:-?} at ${BACKUP_CREATED:-?} (Gitea ${BACKUP_VERSION:-?})."
fi
cat <<EOF
The dump contains ${DUMP_USERS} user account(s) and ${DUMP_REPOS} repositories.
The backup contains ${DUMP_USERS} user account(s) and ${DUMP_REPOS} repositories.
${origin}
This will REPLACE the current Gitea data on this machine:
database ${DB_PATH}
@@ -334,7 +393,7 @@ This will REPLACE the current Gitea data on this machine:
(the current ones are kept next to them as <name>.bak.<timestamp>)
The accounts made by setup-git-server.sh (e.g. the initial admin) are replaced
by the dump's accounts -- you'll log in with the credentials from the old server.
by the backup's accounts -- you'll log in with the credentials from the old server.
EOF
if [[ "$ASSUME_YES" == "true" ]]; then
@@ -401,13 +460,13 @@ swap_in_files() {
restore_secrets() {
[[ -n "$DUMP_INI" ]] || return 0
log "Carrying the secrets over from the dump's app.ini..."
log "Carrying the secrets over from the backup's app.ini..."
# Keep the old config around for reference (mailer, OAuth, etc. settings).
install -m 640 -o root -g "$GITEA_GROUP" "$DUMP_INI" "${GITEA_CONFIG}.from-dump"
# SECRET_KEY encrypts 2FA secrets and other values stored in the database,
# so it must be the one the dump was made with. The JWT secrets and
# so it must be the one the backup was made with. The JWT secrets and
# INTERNAL_TOKEN are carried over too so tokens issued before keep working.
local pair section key value
for pair in security:SECRET_KEY security:INTERNAL_TOKEN server:LFS_JWT_SECRET oauth2:JWT_SECRET; do
@@ -415,7 +474,7 @@ restore_secrets() {
key="${pair##*:}"
value="$(ini_get "$DUMP_INI" "$section" "$key")"
if [[ -z "$value" ]]; then
warn "No ${key} in the dump's app.ini (maybe set through a *_URI option); keeping the current value."
warn "No ${key} in the backup's app.ini (maybe set through a *_URI option); keeping the current value."
continue
fi
ini_set "$GITEA_CONFIG" "$section" "$key" "$value" \
@@ -511,6 +570,7 @@ main() {
check_installation
extract_dump
check_backup_version
inspect_dump
stage_database
confirm_restore