494 lines
14 KiB
Bash
Executable File
494 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
|
|
CONFIG_FILE="${CONFIG_FILE:-$ROOT_DIR/config.toml}"
|
|
|
|
# Capture env overrides before we assign defaults so config.toml can sit between
|
|
# defaults and environment: CLI > env > config > defaults.
|
|
ENV_DEPLOY_HOST="${DEPLOY_HOST-}"
|
|
ENV_DEPLOY_USER="${DEPLOY_USER-}"
|
|
ENV_DEPLOY_PATH="${DEPLOY_PATH-}"
|
|
ENV_WWW_USER="${WWW_USER-}"
|
|
ENV_NPM_CACHE_DIR="${NPM_CACHE_DIR-}"
|
|
ENV_GITEA_REMOTE="${GITEA_REMOTE-}"
|
|
ENV_GITEA_URL="${GITEA_URL-}"
|
|
ENV_GITHUB_REMOTE="${GITHUB_REMOTE-}"
|
|
ENV_GITHUB_URL="${GITHUB_URL-}"
|
|
ENV_PUSH_BRANCH="${PUSH_BRANCH-}"
|
|
|
|
DEPLOY_HOST="192.168.100.50"
|
|
DEPLOY_USER="root"
|
|
DEPLOY_PATH="/var/www/jabali"
|
|
WWW_USER="www-data"
|
|
NPM_CACHE_DIR=""
|
|
GITEA_REMOTE="gitea"
|
|
GITEA_URL=""
|
|
GITHUB_REMOTE="origin"
|
|
GITHUB_URL=""
|
|
PUSH_BRANCH=""
|
|
|
|
trim_ws() {
|
|
local s="${1:-}"
|
|
s="$(echo "$s" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
|
|
printf '%s' "$s"
|
|
}
|
|
|
|
toml_unquote() {
|
|
local v
|
|
v="$(trim_ws "${1:-}")"
|
|
|
|
# Only accept simple double-quoted strings, booleans, or integers.
|
|
if [[ ${#v} -ge 2 && "${v:0:1}" == '"' && "${v: -1}" == '"' ]]; then
|
|
printf '%s' "${v:1:${#v}-2}"
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$v" =~ ^(true|false)$ ]]; then
|
|
printf '%s' "$v"
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$v" =~ ^-?[0-9]+$ ]]; then
|
|
printf '%s' "$v"
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
load_config_toml() {
|
|
local file section line key raw value
|
|
file="$1"
|
|
section=""
|
|
|
|
[[ -f "$file" ]] || return 0
|
|
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
# Strip comments and whitespace.
|
|
line="${line%%#*}"
|
|
line="$(trim_ws "$line")"
|
|
[[ -z "$line" ]] && continue
|
|
|
|
if [[ "$line" =~ ^\[([A-Za-z0-9_.-]+)\]$ ]]; then
|
|
section="${BASH_REMATCH[1]}"
|
|
continue
|
|
fi
|
|
|
|
[[ "$section" == "deploy" ]] || continue
|
|
|
|
if [[ "$line" =~ ^([A-Za-z0-9_]+)[[:space:]]*=[[:space:]]*(.+)$ ]]; then
|
|
key="${BASH_REMATCH[1]}"
|
|
raw="${BASH_REMATCH[2]}"
|
|
value=""
|
|
|
|
if ! value="$(toml_unquote "$raw")"; then
|
|
continue
|
|
fi
|
|
|
|
case "$key" in
|
|
host) DEPLOY_HOST="$value" ;;
|
|
user) DEPLOY_USER="$value" ;;
|
|
path) DEPLOY_PATH="$value" ;;
|
|
www_user) WWW_USER="$value" ;;
|
|
npm_cache_dir) NPM_CACHE_DIR="$value" ;;
|
|
gitea_remote) GITEA_REMOTE="$value" ;;
|
|
gitea_url) GITEA_URL="$value" ;;
|
|
github_remote) GITHUB_REMOTE="$value" ;;
|
|
github_url) GITHUB_URL="$value" ;;
|
|
push_branch) PUSH_BRANCH="$value" ;;
|
|
esac
|
|
fi
|
|
done < "$file"
|
|
}
|
|
|
|
load_config_toml "$CONFIG_FILE"
|
|
|
|
# Apply environment overrides on top of config.
|
|
if [[ -n "${ENV_DEPLOY_HOST:-}" ]]; then DEPLOY_HOST="$ENV_DEPLOY_HOST"; fi
|
|
if [[ -n "${ENV_DEPLOY_USER:-}" ]]; then DEPLOY_USER="$ENV_DEPLOY_USER"; fi
|
|
if [[ -n "${ENV_DEPLOY_PATH:-}" ]]; then DEPLOY_PATH="$ENV_DEPLOY_PATH"; fi
|
|
if [[ -n "${ENV_WWW_USER:-}" ]]; then WWW_USER="$ENV_WWW_USER"; fi
|
|
if [[ -n "${ENV_NPM_CACHE_DIR:-}" ]]; then NPM_CACHE_DIR="$ENV_NPM_CACHE_DIR"; fi
|
|
if [[ -n "${ENV_GITEA_REMOTE:-}" ]]; then GITEA_REMOTE="$ENV_GITEA_REMOTE"; fi
|
|
if [[ -n "${ENV_GITEA_URL:-}" ]]; then GITEA_URL="$ENV_GITEA_URL"; fi
|
|
if [[ -n "${ENV_GITHUB_REMOTE:-}" ]]; then GITHUB_REMOTE="$ENV_GITHUB_REMOTE"; fi
|
|
if [[ -n "${ENV_GITHUB_URL:-}" ]]; then GITHUB_URL="$ENV_GITHUB_URL"; fi
|
|
if [[ -n "${ENV_PUSH_BRANCH:-}" ]]; then PUSH_BRANCH="$ENV_PUSH_BRANCH"; fi
|
|
|
|
SKIP_SYNC=0
|
|
SKIP_COMPOSER=0
|
|
SKIP_NPM=0
|
|
SKIP_MIGRATE=0
|
|
SKIP_CACHE=0
|
|
SKIP_AGENT_RESTART=0
|
|
DELETE_REMOTE=0
|
|
DRY_RUN=0
|
|
SKIP_PUSH=0
|
|
PUSH_GITEA=1
|
|
PUSH_GITHUB=1
|
|
SET_VERSION=""
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: scripts/deploy.sh [options]
|
|
|
|
Options:
|
|
--host HOST Remote host (default: 192.168.100.50)
|
|
--user USER SSH user (default: root)
|
|
--path PATH Remote path (default: /var/www/jabali)
|
|
--www-user USER Remote runtime user (default: www-data)
|
|
--skip-sync Skip rsync sync step
|
|
--skip-composer Skip composer install
|
|
--skip-npm Skip npm install/build
|
|
--skip-migrate Skip php artisan migrate
|
|
--skip-cache Skip cache clear/rebuild
|
|
--skip-agent-restart Skip restarting jabali-agent service
|
|
--delete Pass --delete to rsync (dangerous)
|
|
--dry-run Dry-run rsync only
|
|
--skip-push Skip all git push operations
|
|
--push-gitea Push current branch to Gitea from deploy server (default: on)
|
|
--no-push-gitea Disable Gitea push
|
|
--gitea-remote NAME Gitea git remote name (default: gitea)
|
|
--gitea-url URL Push to this URL instead of a named remote
|
|
--push-github Push current branch to GitHub from deploy server (default: on)
|
|
--no-push-github Disable GitHub push
|
|
--github-remote NAME GitHub git remote name (default: origin)
|
|
--github-url URL Push to this URL instead of a named remote
|
|
--version VALUE Set VERSION to a specific value before remote push
|
|
-h, --help Show this help
|
|
|
|
Environment overrides:
|
|
CONFIG_FILE points to a TOML file (default: ./config.toml). The script reads [deploy] keys.
|
|
CONFIG_FILE, DEPLOY_HOST, DEPLOY_USER, DEPLOY_PATH, WWW_USER, NPM_CACHE_DIR, GITEA_REMOTE, GITEA_URL, GITHUB_REMOTE, GITHUB_URL, PUSH_BRANCH
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--host)
|
|
DEPLOY_HOST="$2"
|
|
shift 2
|
|
;;
|
|
--user)
|
|
DEPLOY_USER="$2"
|
|
shift 2
|
|
;;
|
|
--path)
|
|
DEPLOY_PATH="$2"
|
|
shift 2
|
|
;;
|
|
--www-user)
|
|
WWW_USER="$2"
|
|
shift 2
|
|
;;
|
|
--skip-sync)
|
|
SKIP_SYNC=1
|
|
shift
|
|
;;
|
|
--skip-composer)
|
|
SKIP_COMPOSER=1
|
|
shift
|
|
;;
|
|
--skip-npm)
|
|
SKIP_NPM=1
|
|
shift
|
|
;;
|
|
--skip-migrate)
|
|
SKIP_MIGRATE=1
|
|
shift
|
|
;;
|
|
--skip-cache)
|
|
SKIP_CACHE=1
|
|
shift
|
|
;;
|
|
--skip-agent-restart)
|
|
SKIP_AGENT_RESTART=1
|
|
shift
|
|
;;
|
|
--delete)
|
|
DELETE_REMOTE=1
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=1
|
|
shift
|
|
;;
|
|
--skip-push)
|
|
SKIP_PUSH=1
|
|
PUSH_GITEA=0
|
|
PUSH_GITHUB=0
|
|
shift
|
|
;;
|
|
--push-gitea)
|
|
PUSH_GITEA=1
|
|
shift
|
|
;;
|
|
--no-push-gitea)
|
|
PUSH_GITEA=0
|
|
shift
|
|
;;
|
|
--gitea-remote)
|
|
GITEA_REMOTE="$2"
|
|
shift 2
|
|
;;
|
|
--gitea-url)
|
|
GITEA_URL="$2"
|
|
shift 2
|
|
;;
|
|
--push-github)
|
|
PUSH_GITHUB=1
|
|
shift
|
|
;;
|
|
--no-push-github)
|
|
PUSH_GITHUB=0
|
|
shift
|
|
;;
|
|
--github-remote)
|
|
GITHUB_REMOTE="$2"
|
|
shift 2
|
|
;;
|
|
--github-url)
|
|
GITHUB_URL="$2"
|
|
shift 2
|
|
;;
|
|
--version)
|
|
SET_VERSION="$2"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
REMOTE="${DEPLOY_USER}@${DEPLOY_HOST}"
|
|
|
|
ensure_remote_git_clean() {
|
|
local status_output
|
|
status_output="$(remote_run "if [[ ! -d \"$DEPLOY_PATH/.git\" ]]; then echo '__NO_GIT__'; exit 0; fi; cd \"$DEPLOY_PATH\" && git status --porcelain")"
|
|
|
|
if [[ "$status_output" == "__NO_GIT__" ]]; then
|
|
echo "Remote path is not a git repository: $DEPLOY_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "$status_output" ]]; then
|
|
echo "Remote git worktree is dirty at $DEPLOY_PATH. Commit or stash remote changes first."
|
|
echo "$status_output"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
remote_commit_and_push() {
|
|
local local_head push_branch
|
|
local_head="$(git -C "$ROOT_DIR" rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
|
|
|
if [[ -n "$PUSH_BRANCH" ]]; then
|
|
push_branch="$PUSH_BRANCH"
|
|
else
|
|
push_branch="$(remote_run "cd \"$DEPLOY_PATH\" && git rev-parse --abbrev-ref HEAD")"
|
|
if [[ -z "$push_branch" || "$push_branch" == "HEAD" ]]; then
|
|
push_branch="main"
|
|
fi
|
|
fi
|
|
|
|
ssh -o StrictHostKeyChecking=no "$REMOTE" \
|
|
DEPLOY_PATH="$DEPLOY_PATH" \
|
|
PUSH_BRANCH="$push_branch" \
|
|
PUSH_GITEA="$PUSH_GITEA" \
|
|
PUSH_GITHUB="$PUSH_GITHUB" \
|
|
GITEA_REMOTE="$GITEA_REMOTE" \
|
|
GITEA_URL="$GITEA_URL" \
|
|
GITHUB_REMOTE="$GITHUB_REMOTE" \
|
|
GITHUB_URL="$GITHUB_URL" \
|
|
SET_VERSION="$SET_VERSION" \
|
|
LOCAL_HEAD="$local_head" \
|
|
bash -s <<'EOF'
|
|
set -euo pipefail
|
|
|
|
cd "$DEPLOY_PATH"
|
|
|
|
if [[ ! -d .git ]]; then
|
|
echo "Remote path is not a git repository: $DEPLOY_PATH" >&2
|
|
exit 1
|
|
fi
|
|
|
|
git config --global --add safe.directory "$DEPLOY_PATH" >/dev/null 2>&1 || true
|
|
if ! git config user.name >/dev/null; then
|
|
git config user.name "Jabali Deploy"
|
|
fi
|
|
if ! git config user.email >/dev/null; then
|
|
git config user.email "root@$(hostname -f 2>/dev/null || hostname)"
|
|
fi
|
|
|
|
current="$(sed -n 's/^VERSION=//p' VERSION || true)"
|
|
if [[ -z "$current" ]]; then
|
|
echo "VERSION file missing or invalid on remote." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "${SET_VERSION:-}" ]]; then
|
|
new="$SET_VERSION"
|
|
else
|
|
if [[ "$current" =~ ^(.+-rc)([0-9]+)?$ ]]; then
|
|
base="${BASH_REMATCH[1]}"
|
|
num="${BASH_REMATCH[2]}"
|
|
if [[ -z "$num" ]]; then
|
|
num=1
|
|
else
|
|
num=$((num + 1))
|
|
fi
|
|
new="${base}${num}"
|
|
elif [[ "$current" =~ ^(.+?)([0-9]+)$ ]]; then
|
|
new="${BASH_REMATCH[1]}$((BASH_REMATCH[2] + 1))"
|
|
else
|
|
echo "Cannot auto-bump VERSION from '$current'. Use --version to set it explicitly." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [[ "$new" == "$current" ]]; then
|
|
echo "VERSION is already '$current'. Use --version to set a new value." >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf 'VERSION=%s\n' "$new" > VERSION
|
|
sed -i -E "s|JABALI_VERSION=\"\\$\\{JABALI_VERSION:-[^}]+\\}\"|JABALI_VERSION=\"\\\${JABALI_VERSION:-$new}\"|" install.sh
|
|
|
|
git add -A
|
|
if git diff --cached --quiet; then
|
|
echo "No changes detected after sync/version bump; skipping commit."
|
|
else
|
|
git commit -m "Deploy sync from ${LOCAL_HEAD} (v${new})"
|
|
fi
|
|
|
|
if [[ "$PUSH_GITEA" -eq 1 ]]; then
|
|
if [[ -n "$GITEA_URL" ]]; then
|
|
git push "$GITEA_URL" "$PUSH_BRANCH"
|
|
else
|
|
git push "$GITEA_REMOTE" "$PUSH_BRANCH"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$PUSH_GITHUB" -eq 1 ]]; then
|
|
if [[ -n "$GITHUB_URL" ]]; then
|
|
git push "$GITHUB_URL" "$PUSH_BRANCH"
|
|
else
|
|
git push "$GITHUB_REMOTE" "$PUSH_BRANCH"
|
|
fi
|
|
fi
|
|
EOF
|
|
}
|
|
|
|
rsync_project() {
|
|
local -a rsync_opts
|
|
rsync_opts=(-az --info=progress2)
|
|
if [[ "$DELETE_REMOTE" -eq 1 ]]; then
|
|
rsync_opts+=(--delete)
|
|
fi
|
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
rsync_opts+=(--dry-run)
|
|
fi
|
|
|
|
rsync "${rsync_opts[@]}" \
|
|
--exclude ".git/" \
|
|
--exclude "node_modules/" \
|
|
--exclude "vendor/" \
|
|
--exclude "storage/" \
|
|
--exclude "bootstrap/cache/" \
|
|
--exclude "public/build/" \
|
|
--exclude ".env" \
|
|
--exclude ".env.*" \
|
|
--exclude "database/*.sqlite" \
|
|
--exclude "database/*.sqlite-wal" \
|
|
--exclude "database/*.sqlite-shm" \
|
|
"$ROOT_DIR/" \
|
|
"${REMOTE}:${DEPLOY_PATH}/"
|
|
}
|
|
|
|
remote_run() {
|
|
ssh -o StrictHostKeyChecking=no "$REMOTE" "bash -lc '$1'"
|
|
}
|
|
|
|
remote_run_www() {
|
|
ssh -o StrictHostKeyChecking=no "$REMOTE" "bash -lc 'cd \"$DEPLOY_PATH\" && sudo -u \"$WWW_USER\" -H bash -lc \"$1\"'"
|
|
}
|
|
|
|
ensure_remote_permissions() {
|
|
local parent_dir
|
|
parent_dir="$(dirname "$DEPLOY_PATH")"
|
|
|
|
if [[ -z "$NPM_CACHE_DIR" ]]; then
|
|
NPM_CACHE_DIR="${parent_dir}/.npm"
|
|
fi
|
|
|
|
remote_run "mkdir -p \"$DEPLOY_PATH/storage\" \"$DEPLOY_PATH/bootstrap/cache\" \"$DEPLOY_PATH/public/build\" \"$DEPLOY_PATH/node_modules\" \"$DEPLOY_PATH/database\" \"$NPM_CACHE_DIR\""
|
|
remote_run "chown -R \"$WWW_USER\":\"$WWW_USER\" \"$DEPLOY_PATH/storage\" \"$DEPLOY_PATH/bootstrap/cache\" \"$DEPLOY_PATH/public\" \"$DEPLOY_PATH/public/build\" \"$DEPLOY_PATH/node_modules\" \"$DEPLOY_PATH/database\" \"$NPM_CACHE_DIR\""
|
|
remote_run "if [[ -f \"$DEPLOY_PATH/auth.json\" ]]; then chown \"$WWW_USER\":\"$WWW_USER\" \"$DEPLOY_PATH/auth.json\" && chmod 600 \"$DEPLOY_PATH/auth.json\"; fi"
|
|
}
|
|
|
|
echo "Deploying to ${REMOTE}:${DEPLOY_PATH}"
|
|
|
|
if [[ "$DRY_RUN" -eq 0 && "$SKIP_PUSH" -eq 0 && ( "$PUSH_GITEA" -eq 1 || "$PUSH_GITHUB" -eq 1 ) ]]; then
|
|
echo "Validating remote git worktree..."
|
|
ensure_remote_git_clean
|
|
fi
|
|
|
|
if [[ "$SKIP_SYNC" -eq 0 ]]; then
|
|
echo "Syncing project files..."
|
|
rsync_project
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
echo "Dry run complete. No remote commands executed."
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "$SKIP_PUSH" -eq 0 && ( "$PUSH_GITEA" -eq 1 || "$PUSH_GITHUB" -eq 1 ) ]]; then
|
|
echo "Committing and pushing from ${REMOTE}..."
|
|
remote_commit_and_push
|
|
fi
|
|
|
|
echo "Ensuring remote permissions..."
|
|
ensure_remote_permissions
|
|
|
|
if [[ "$SKIP_COMPOSER" -eq 0 ]]; then
|
|
echo "Installing composer dependencies..."
|
|
remote_run_www "composer install --no-interaction --prefer-dist --optimize-autoloader"
|
|
fi
|
|
|
|
if [[ "$SKIP_NPM" -eq 0 ]]; then
|
|
echo "Building frontend assets..."
|
|
remote_run_www "npm ci"
|
|
remote_run_www "npm run build"
|
|
fi
|
|
|
|
if [[ "$SKIP_MIGRATE" -eq 0 ]]; then
|
|
echo "Running migrations..."
|
|
remote_run_www "php artisan migrate --force"
|
|
fi
|
|
|
|
if [[ "$SKIP_CACHE" -eq 0 ]]; then
|
|
echo "Refreshing caches..."
|
|
remote_run_www "php artisan optimize:clear"
|
|
remote_run_www "php artisan config:cache"
|
|
remote_run_www "php artisan route:cache"
|
|
remote_run_www "php artisan view:cache"
|
|
fi
|
|
|
|
if [[ "$SKIP_AGENT_RESTART" -eq 0 ]]; then
|
|
echo "Restarting jabali-agent service..."
|
|
remote_run "if systemctl list-unit-files jabali-agent.service --no-legend 2>/dev/null | grep -q '^jabali-agent\\.service'; then systemctl restart jabali-agent; fi"
|
|
fi
|
|
|
|
echo "Deploy complete."
|