feat(deploy): add fast code-only update script with guardrails and docs
This commit is contained in:
parent
08f40977f9
commit
468814ca8d
@ -258,6 +258,32 @@ crontab -e
|
||||
|
||||
## 🔄 Opdatering til Ny Version
|
||||
|
||||
### Valg Af Update-metode
|
||||
|
||||
| Situation | Brug metode | Hvorfor |
|
||||
|---|---|---|
|
||||
| Små kodeændringer i `app/*` eller `main.py` | `./update_fast.sh --ref <ref>` | Hurtig update uden ny release-tag/pakke |
|
||||
| Ændringer i `migrations/*` | `./updateto.sh <version>` | Kræver kontrolleret release + migrations-flow |
|
||||
| Ændringer i `requirements.txt` eller `Dockerfile` | `./updateto.sh <version>` | Kræver fuld image-build og versionsstyring |
|
||||
| Ændringer i `docker-compose*.yml`, scripts eller `.env` | `./updateto.sh <version>` | Drift/infra-konfiguration skal deployes fuldt |
|
||||
| Når du er i tvivl | `./updateto.sh <version>` | Sikreste og mest forudsigelige metode |
|
||||
|
||||
Hurtig start for fast mode:
|
||||
|
||||
```bash
|
||||
# Tjek først scope
|
||||
./update_fast.sh --ref main --dry-run --allow-prod
|
||||
|
||||
# Kør update
|
||||
./update_fast.sh --ref <commit-eller-tag> --allow-prod
|
||||
```
|
||||
|
||||
Rollback i fast mode:
|
||||
|
||||
```bash
|
||||
./update_fast.sh --rollback <backup-id> --allow-prod
|
||||
```
|
||||
|
||||
### På din Mac:
|
||||
|
||||
```bash
|
||||
|
||||
@ -22,6 +22,34 @@ cd /srv/podman/bmc_hub_v1.0
|
||||
./updateto.sh v1.3.16
|
||||
```
|
||||
|
||||
## Fast small update (kode-only, uden ny release tag)
|
||||
|
||||
Brug denne metode til meget små ændringer i `app/*` eller `main.py`, hvor du ikke vil lave en fuld release-pakke.
|
||||
|
||||
```bash
|
||||
ssh bmcadmin@172.16.31.183
|
||||
cd /srv/podman/bmc_hub_v1.0
|
||||
|
||||
# Download/refresh fast update script
|
||||
curl -O https://g.bmcnetworks.dk/ct/bmc_hub/raw/branch/main/update_fast.sh
|
||||
chmod +x update_fast.sh
|
||||
|
||||
# Tjek først hvad der ændres (anbefalet)
|
||||
./update_fast.sh --ref main --dry-run --allow-prod
|
||||
|
||||
# Kør fast update (eksempel: specifik commit)
|
||||
./update_fast.sh --ref 08f4097 --allow-prod
|
||||
```
|
||||
|
||||
Vigtigt:
|
||||
- `update_fast.sh` er kun til kode/templates/static ændringer i fast scope.
|
||||
- Hvis der er ændringer i migrationer, dependencies, docker/compose eller env: brug `./updateto.sh`.
|
||||
- Rollback kan køres med backup-id:
|
||||
|
||||
```bash
|
||||
./update_fast.sh --rollback 20260517-142155 --allow-prod
|
||||
```
|
||||
|
||||
## Manuel deployment (hvis scriptet ikke virker)
|
||||
|
||||
```bash
|
||||
|
||||
515
update_fast.sh
Executable file
515
update_fast.sh
Executable file
@ -0,0 +1,515 @@
|
||||
#!/bin/bash
|
||||
# Fast code-only update for BMC Hub (DEV/PROD)
|
||||
#
|
||||
# Purpose:
|
||||
# - Deploy small app code/template/static changes without creating a new tag/package.
|
||||
# - Keep full release workflow in updateto.sh for dependency/runtime/migration changes.
|
||||
#
|
||||
# Safety model:
|
||||
# - Allowed paths: app/* and main.py
|
||||
# - Disallowed: migrations, Docker/compose, requirements, env files, deploy scripts.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PODMAN_COMPOSE_FILE="docker-compose.prod.yml"
|
||||
STATE_DIR=".fast-update"
|
||||
STATE_FILE="${STATE_DIR}/current_ref"
|
||||
LAST_BACKUP_FILE="${STATE_DIR}/last_backup"
|
||||
|
||||
TARGET_REF=""
|
||||
DRY_RUN=false
|
||||
ALLOW_PROD=false
|
||||
ROLLBACK_ID=""
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./update_fast.sh --ref <git-ref> [--dry-run] [--allow-prod]
|
||||
./update_fast.sh --rollback <backup-id> [--allow-prod]
|
||||
|
||||
Examples:
|
||||
./update_fast.sh --ref main --dry-run
|
||||
./update_fast.sh --ref 08f4097
|
||||
./update_fast.sh --rollback 20260517-142155
|
||||
|
||||
Notes:
|
||||
- Fast mode is ONLY for code/template/static updates in app/* and main.py.
|
||||
- For migrations, dependencies, Docker/compose, or env changes: use ./updateto.sh.
|
||||
- On production hosts, --allow-prod is required.
|
||||
EOF
|
||||
}
|
||||
|
||||
if [ "${EUID:-$(id -u)}" -eq 0 ]; then
|
||||
echo "Error: do not run as root. Use the normal rootless podman user."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--ref)
|
||||
TARGET_REF="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--allow-prod)
|
||||
ALLOW_PROD=true
|
||||
shift
|
||||
;;
|
||||
--rollback)
|
||||
ROLLBACK_ID="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Error: unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -n "$ROLLBACK_ID" ] && [ -n "$TARGET_REF" ]; then
|
||||
echo "Error: use either --ref or --rollback, not both."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$ROLLBACK_ID" ] && [ -z "$TARGET_REF" ]; then
|
||||
echo "Error: missing required option --ref <git-ref> or --rollback <backup-id>."
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f ".env" ]; then
|
||||
echo "Error: .env not found in $(pwd)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$PODMAN_COMPOSE_FILE" ]; then
|
||||
echo "Error: $PODMAN_COMPOSE_FILE not found in $(pwd)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
load_env_file() {
|
||||
local env_file="$1"
|
||||
local line=""
|
||||
local trimmed=""
|
||||
local key=""
|
||||
local value=""
|
||||
local first_char=""
|
||||
local last_char=""
|
||||
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
line="${line%$'\r'}"
|
||||
trimmed="${line#"${line%%[![:space:]]*}"}"
|
||||
|
||||
if [ -z "$trimmed" ] || [[ "$trimmed" == \#* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" != *=* ]]; then
|
||||
echo "Error: invalid line in .env: $line"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
key="${line%%=*}"
|
||||
value="${line#*=}"
|
||||
|
||||
key="${key#"${key%%[![:space:]]*}"}"
|
||||
key="${key%"${key##*[![:space:]]}"}"
|
||||
|
||||
if [[ ! "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
|
||||
echo "Error: invalid .env key: $key"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${#value}" -ge 2 ]; then
|
||||
first_char="${value:0:1}"
|
||||
last_char="${value: -1}"
|
||||
if { [ "$first_char" = '"' ] && [ "$last_char" = '"' ]; } || { [ "$first_char" = "'" ] && [ "$last_char" = "'" ]; }; then
|
||||
value="${value:1:${#value}-2}"
|
||||
fi
|
||||
fi
|
||||
|
||||
export "$key=$value"
|
||||
done < "$env_file"
|
||||
}
|
||||
|
||||
load_env_file .env
|
||||
|
||||
get_current_ip() {
|
||||
local ip=""
|
||||
|
||||
if ip=$(hostname -I 2>/dev/null | awk '{print $1}'); then
|
||||
if [ -n "$ip" ]; then
|
||||
echo "$ip"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v ipconfig >/dev/null 2>&1; then
|
||||
ip=$(ipconfig getifaddr en0 2>/dev/null || true)
|
||||
if [ -z "$ip" ]; then
|
||||
ip=$(ipconfig getifaddr en1 2>/dev/null || true)
|
||||
fi
|
||||
if [ -n "$ip" ]; then
|
||||
echo "$ip"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "unknown"
|
||||
}
|
||||
|
||||
CURRENT_IP="$(get_current_ip)"
|
||||
CURRENT_DIR=$(pwd)
|
||||
IS_PROD=false
|
||||
if [[ "$CURRENT_IP" == "172.16.31.183" ]] || [[ "$CURRENT_DIR" == "/srv/podman/bmc_hub_v2" ]] || [[ "$CURRENT_DIR" == "/srv/podman/bmc_hub_v1.0" ]]; then
|
||||
IS_PROD=true
|
||||
fi
|
||||
|
||||
if [ "$IS_PROD" = true ] && [ "$ALLOW_PROD" != true ]; then
|
||||
echo "Error: production host detected. Re-run with --allow-prod to continue."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$IS_PROD" = true ] && [ "$DRY_RUN" != true ]; then
|
||||
echo "Production safety check:"
|
||||
echo " host ip: $CURRENT_IP"
|
||||
echo " cwd: $CURRENT_DIR"
|
||||
read -r -p "Type FAST-UPDATE to continue: " CONFIRM
|
||||
if [ "$CONFIRM" != "FAST-UPDATE" ]; then
|
||||
echo "Aborted."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$STATE_DIR/backups"
|
||||
|
||||
GITEA_BASE="${GITEA_URL:-https://g.bmcnetworks.dk}"
|
||||
REPO="${GITHUB_REPO:-ct/bmc_hub}"
|
||||
API_PORT="${API_PORT:-8000}"
|
||||
|
||||
url_encode() {
|
||||
python3 - "$1" <<'PY'
|
||||
import sys
|
||||
from urllib.parse import quote
|
||||
print(quote(sys.argv[1], safe=""))
|
||||
PY
|
||||
}
|
||||
|
||||
fetch_url_to_file() {
|
||||
local url="$1"
|
||||
local out_file="$2"
|
||||
|
||||
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
curl -fsSL -H "Authorization: token ${GITHUB_TOKEN}" "$url" -o "$out_file"
|
||||
else
|
||||
curl -fsSL "$url" -o "$out_file"
|
||||
fi
|
||||
}
|
||||
|
||||
build_changed_from_compare_json() {
|
||||
local compare_file="$1"
|
||||
local out_file="$2"
|
||||
|
||||
python3 - "$compare_file" > "$out_file" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
with open(sys.argv[1], "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
files = data.get("files") or []
|
||||
for item in files:
|
||||
status = str(item.get("status") or "")
|
||||
filename = str(item.get("filename") or "")
|
||||
prev = str(item.get("previous_filename") or "")
|
||||
print(f"{status}\t{filename}\t{prev}")
|
||||
PY
|
||||
}
|
||||
|
||||
build_changed_from_local_git() {
|
||||
local base_ref="$1"
|
||||
local target_ref="$2"
|
||||
local out_file="$3"
|
||||
|
||||
if ! git rev-parse --verify "$base_ref" >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
if ! git rev-parse --verify "$target_ref" >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
git diff --name-status "$base_ref".."$target_ref" \
|
||||
| awk 'BEGIN{OFS="\t"}
|
||||
/^A\t/ {print "added", $2, ""}
|
||||
/^M\t/ {print "modified", $2, ""}
|
||||
/^D\t/ {print "deleted", $2, ""}
|
||||
/^R[0-9]*\t/ {print "renamed", $3, $2}
|
||||
/^C[0-9]*\t/ {print "copied", $3, $2}
|
||||
/^T\t/ {print "type_changed", $2, ""}
|
||||
/^U\t/ {print "unmerged", $2, ""}
|
||||
/^X\t/ {print "unknown", $2, ""}
|
||||
/^B\t/ {print "broken", $2, ""}' > "$out_file"
|
||||
}
|
||||
|
||||
is_disallowed_path() {
|
||||
local path="$1"
|
||||
case "$path" in
|
||||
migrations/*|Dockerfile|requirements.txt|docker-compose.yml|docker-compose.prod.yml|.env|.env.*|updateto.sh|update_fast.sh|scripts/*)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_allowed_path() {
|
||||
local path="$1"
|
||||
case "$path" in
|
||||
app/*|main.py)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
health_check() {
|
||||
local attempts=30
|
||||
local i=1
|
||||
while [ "$i" -le "$attempts" ]; do
|
||||
if curl -fsS "http://localhost:${API_PORT}/health" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
echo "Waiting for API health (${i}/${attempts})"
|
||||
sleep 2
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
rebuild_api_latest() {
|
||||
RELEASE_VERSION=latest podman-compose -f "$PODMAN_COMPOSE_FILE" build api
|
||||
RELEASE_VERSION=latest podman-compose -f "$PODMAN_COMPOSE_FILE" up -d --no-deps api
|
||||
}
|
||||
|
||||
restore_backup() {
|
||||
local backup_id="$1"
|
||||
local backup_dir="${STATE_DIR}/backups/${backup_id}"
|
||||
local manifest="${backup_dir}/manifest.tsv"
|
||||
|
||||
if [ ! -d "$backup_dir" ]; then
|
||||
echo "Error: backup id not found: $backup_id"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$manifest" ]; then
|
||||
echo "Error: manifest missing: $manifest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Restoring backup: ${backup_id}"
|
||||
|
||||
while IFS=$'\t' read -r status path existed; do
|
||||
[ -z "$path" ] && continue
|
||||
mkdir -p "$(dirname "$path")"
|
||||
if [ "$existed" = "1" ]; then
|
||||
cp "${backup_dir}/original/${path}" "$path"
|
||||
else
|
||||
rm -f "$path"
|
||||
fi
|
||||
done < "$manifest"
|
||||
|
||||
rebuild_api_latest
|
||||
if ! health_check; then
|
||||
echo "Error: health check failed after rollback."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "${backup_dir}/meta.env" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "${backup_dir}/meta.env"
|
||||
if [ -n "${BASE_REF:-}" ]; then
|
||||
echo "$BASE_REF" > "$STATE_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Rollback completed: ${backup_id}"
|
||||
}
|
||||
|
||||
if [ -n "$ROLLBACK_ID" ]; then
|
||||
restore_backup "$ROLLBACK_ID"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BASE_REF=""
|
||||
if [ -f "$STATE_FILE" ]; then
|
||||
BASE_REF="$(cat "$STATE_FILE")"
|
||||
elif [ -n "${RELEASE_VERSION:-}" ] && [ "${RELEASE_VERSION}" != "latest" ]; then
|
||||
BASE_REF="$RELEASE_VERSION"
|
||||
elif [ -n "${FAST_BASE_REF:-}" ]; then
|
||||
BASE_REF="$FAST_BASE_REF"
|
||||
else
|
||||
echo "Error: cannot determine base ref."
|
||||
echo "Set RELEASE_VERSION in .env, or create ${STATE_FILE}, or provide FAST_BASE_REF env var."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Fast update"
|
||||
echo " base ref: $BASE_REF"
|
||||
echo " target ref: $TARGET_REF"
|
||||
|
||||
BASE_ENC="$(url_encode "$BASE_REF")"
|
||||
TARGET_ENC="$(url_encode "$TARGET_REF")"
|
||||
COMPARE_URL="${GITEA_BASE}/api/v1/repos/${REPO}/compare/${BASE_ENC}...${TARGET_ENC}"
|
||||
|
||||
TMP_COMPARE="$(mktemp)"
|
||||
TMP_CHANGED="$(mktemp)"
|
||||
trap 'rm -f "$TMP_COMPARE" "$TMP_CHANGED"' EXIT
|
||||
|
||||
COMPARE_SOURCE=""
|
||||
if fetch_url_to_file "$COMPARE_URL" "$TMP_COMPARE"; then
|
||||
build_changed_from_compare_json "$TMP_COMPARE" "$TMP_CHANGED"
|
||||
COMPARE_SOURCE="gitea-api"
|
||||
else
|
||||
if [ -d ".git" ] && build_changed_from_local_git "$BASE_REF" "$TARGET_REF" "$TMP_CHANGED"; then
|
||||
COMPARE_SOURCE="local-git"
|
||||
echo "Warning: compare API unavailable, using local git diff fallback."
|
||||
else
|
||||
echo "Error: could not fetch compare data from: $COMPARE_URL"
|
||||
echo "Hint: ensure GITHUB_TOKEN is set, refs exist, or run in a git clone where both refs are available."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Compare source: ${COMPARE_SOURCE}"
|
||||
|
||||
if [ ! -s "$TMP_CHANGED" ]; then
|
||||
echo "No changed files between ${BASE_REF} and ${TARGET_REF}."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
declare -a DISALLOWED_FILES=()
|
||||
declare -a NOT_ALLOWED_FILES=()
|
||||
declare -a UNSUPPORTED_STATUS=()
|
||||
|
||||
while IFS=$'\t' read -r status file_path prev_path; do
|
||||
[ -z "$file_path" ] && continue
|
||||
|
||||
case "$status" in
|
||||
added|modified)
|
||||
;;
|
||||
*)
|
||||
UNSUPPORTED_STATUS+=("${status}: ${file_path}")
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
if is_disallowed_path "$file_path"; then
|
||||
DISALLOWED_FILES+=("$file_path")
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! is_allowed_path "$file_path"; then
|
||||
NOT_ALLOWED_FILES+=("$file_path")
|
||||
continue
|
||||
fi
|
||||
|
||||
done < "$TMP_CHANGED"
|
||||
|
||||
if [ "${#UNSUPPORTED_STATUS[@]}" -gt 0 ]; then
|
||||
echo "Error: unsupported change types in fast mode:"
|
||||
for item in "${UNSUPPORTED_STATUS[@]}"; do
|
||||
echo " - $item"
|
||||
done
|
||||
echo "Use ./updateto.sh for these changes."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${#DISALLOWED_FILES[@]}" -gt 0 ]; then
|
||||
echo "Error: disallowed files changed for fast mode:"
|
||||
for file_path in "${DISALLOWED_FILES[@]}"; do
|
||||
echo " - $file_path"
|
||||
done
|
||||
echo "Use ./updateto.sh for this update."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${#NOT_ALLOWED_FILES[@]}" -gt 0 ]; then
|
||||
echo "Error: files outside fast-mode scope changed:"
|
||||
for file_path in "${NOT_ALLOWED_FILES[@]}"; do
|
||||
echo " - $file_path"
|
||||
done
|
||||
echo "Fast mode only allows app/* and main.py. Use ./updateto.sh instead."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Changed files in fast scope:"
|
||||
while IFS=$'\t' read -r status file_path prev_path; do
|
||||
[ -z "$file_path" ] && continue
|
||||
echo " - ${status}: ${file_path}"
|
||||
done < "$TMP_CHANGED"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
echo "Dry-run complete. No files updated."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BACKUP_ID="$(date +%Y%m%d-%H%M%S)"
|
||||
BACKUP_DIR="${STATE_DIR}/backups/${BACKUP_ID}"
|
||||
MANIFEST="${BACKUP_DIR}/manifest.tsv"
|
||||
mkdir -p "${BACKUP_DIR}/original"
|
||||
|
||||
while IFS=$'\t' read -r status file_path prev_path; do
|
||||
[ -z "$file_path" ] && continue
|
||||
|
||||
mkdir -p "$(dirname "$file_path")"
|
||||
|
||||
existed=0
|
||||
if [ -f "$file_path" ]; then
|
||||
mkdir -p "${BACKUP_DIR}/original/$(dirname "$file_path")"
|
||||
cp "$file_path" "${BACKUP_DIR}/original/${file_path}"
|
||||
existed=1
|
||||
fi
|
||||
|
||||
printf "%s\t%s\t%s\n" "$status" "$file_path" "$existed" >> "$MANIFEST"
|
||||
|
||||
file_enc="$(url_encode "$file_path")"
|
||||
raw_url="${GITEA_BASE}/api/v1/repos/${REPO}/raw/${file_enc}?ref=${TARGET_ENC}"
|
||||
tmp_file="${file_path}.tmp.fast"
|
||||
|
||||
fetch_url_to_file "$raw_url" "$tmp_file"
|
||||
mv "$tmp_file" "$file_path"
|
||||
done < "$TMP_CHANGED"
|
||||
|
||||
cat > "${BACKUP_DIR}/meta.env" <<EOF
|
||||
BASE_REF=${BASE_REF}
|
||||
TARGET_REF=${TARGET_REF}
|
||||
BACKUP_ID=${BACKUP_ID}
|
||||
EOF
|
||||
|
||||
echo "Building and restarting API container only"
|
||||
rebuild_api_latest
|
||||
|
||||
if ! health_check; then
|
||||
echo "Error: health check failed after fast update. Starting automatic rollback."
|
||||
restore_backup "$BACKUP_ID"
|
||||
echo "Rollback completed. Fast update aborted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$TARGET_REF" > "$STATE_FILE"
|
||||
echo "$BACKUP_ID" > "$LAST_BACKUP_FILE"
|
||||
|
||||
echo "Fast update complete"
|
||||
echo " backup id: $BACKUP_ID"
|
||||
echo " deployed: $TARGET_REF"
|
||||
echo " state file: $STATE_FILE"
|
||||
Loading…
Reference in New Issue
Block a user