diff --git a/.githooks/post-receive b/.githooks/post-receive index 009704c..c509e99 100644 --- a/.githooks/post-receive +++ b/.githooks/post-receive @@ -14,64 +14,49 @@ } get_log_file_path() { - echo "/srv/jasonpoage.com/logs/receive-${TIMESTAMP}.log" -} - -copy_env_file() { - local branch="$1" - local env_file - env_file="$(get_deploy_env_file "$branch")" - source "$env_file" - # Copy the .env file for the test environment - cp "$env_file" "$tmpdir/.env" || { - echo "Error: Failed to copy .env file for test environment." - return 1 - } -} - -clone_branch() { - local branch="$1" - local tmpdir="$2" - # This creates a proper working tree at $tmpdir, which is essential for submodules. - git clone --branch "$branch" "$GIT_DIR" "$tmpdir" || { - echo "Error: Failed to clone main application for tests to $tmpdir" - return 1 - } -} - -# Function to wait for a service to become available -_wait_for_service() { - local url="$1" - local timeout=30 # seconds - local start_time - start_time=$(date +%s) - echo "Waiting for service at $url to become available (timeout: ${timeout}s)..." - while :; do - if curl --silent --fail "$url" >/dev/null; then - echo "Service at $url is up!" - return 0 - fi - current_time=$(date +%s) - if ((current_time - start_time >= timeout)); then - echo "Error: Service at $url did not become available within ${timeout}s." - return 1 - fi - sleep 1 - done + echo "/srv/jasonpoage.com/logs/post-receive-${TIMESTAMP}.log" } quick_deploy_expressjs_blog() { local branch="$1" - local test_dir="$2" - local deploy_path="$3" - local envfile="$4" - echo "Moving tested deployment from '$test_dir' to '$deploy_path'..." + local deploy_path="$2" + local envfile="$3" + echo "Performing quick deployment for '$branch' to '$deploy_path'..." + + # Remove old deployment and create fresh one [[ -d "$deploy_path" ]] && rm -rf "$deploy_path" - mv "$test_dir" "$deploy_path" - git config -f /srv/jasonpoage.com/expressjs-blog.git/modules/content/config core.worktree "/srv/jasonpoage.com/expressjs-blog-testing/content" + + # Clone the latest version + git clone --branch "$branch" "$GIT_DIR" "$deploy_path" || { + echo "Error: Failed to clone branch $branch to $deploy_path" + return 1 + } + + cd "$deploy_path" || { + echo "Error: Could not change directory to $deploy_path" + return 1 + } + + # Update submodules + git submodule update --init --recursive || { + echo "Error: Failed to initialize submodules" + return 1 + } + + # Set up environment file ln -f "$envfile" "$deploy_path/.env" + + # Install dependencies and build + yarn + yarn --production=false combine:css || { + echo "Error: CSS combination failed" + return 1 + } + # Restart the service systemctl --user restart express-blog@"$branch".service + + echo "Quick deployment complete for branch '$branch'" } # Smart dependency installation (only if package files changed) @@ -79,6 +64,7 @@ local oldrev="$1" local newrev="$2" local install_required=true + if [[ -n "$oldrev" && "$oldrev" != "0000000000000000000000000000000000000000" ]]; then if ! git --git-dir="$GIT_DIR" diff-tree --name-only -r "$oldrev..$newrev" | grep -qE "(package\.json|yarn\.lock)$"; then echo "No changes detected in package.json or yarn.lock. Skipping yarn install." @@ -106,11 +92,10 @@ echo "Error: Could not change directory to $deploy_path" return 1 } + SAVED_GIT_DIR="$GIT_DIR" unset GIT_DIR - git remote show - git branch -l # Update main repository - always fetch and hard reset (handles force pushes) echo "Fetching and updating main repository..." git fetch origin || { @@ -125,7 +110,7 @@ # Update submodules - handle force pushes by resetting each submodule echo "Updating submodules..." - git submodule update --init --recursive --force || { + git submodule update --init --recursive --force || { echo "Standard submodule update failed, cleaning and retrying..." return 1 } @@ -136,7 +121,7 @@ unset SAVED_GIT_DIR } -# Updated conservative deployment function using the unified update +# Conservative deployment function for production environments conservative_deploy_expressjs_blog() { local branch="$1" local oldrev="${2:-}" @@ -144,9 +129,7 @@ local deploy_path="${4:-}" local envfile="${5:-}" - echo "Using preserving deployment method for production environment..." - - # set -x + echo "Using conservative deployment method for production environment..." # Initialize the deployment directory if it doesn't exist if [[ ! -d "$deploy_path" ]]; then @@ -157,166 +140,75 @@ } fi - # Single unified update function handles everything - update_repo_and_submodules "$branch" "$deploy_path" "$GIT_DIR" || return 1 + # Update repository and submodules + update_repo_and_submodules "$branch" "$deploy_path" || return 1 cd "$deploy_path" || { echo "Error: Could not change directory to $deploy_path" return 1 } + # Set up environment file ln -f "$envfile" "$deploy_path/.env" || return 1 + # Install dependencies if needed get_dependencies "$oldrev" "$newrev" || return 1 + # Build CSS yarn combine:css "$oldrev" "$newrev" "$GIT_DIR" || return 1 + # Restart service systemctl --user restart express-blog@"$branch".service - # set +x + echo "Conservative deployment complete for branch '$branch'" } deploy_expressjs_blog() { local branch="$1" - local test_dir="$2" - local oldrev="${3:-}" - local newrev="${4:-}" + local oldrev="${2:-}" + local newrev="${3:-}" # Define deployment path and env file path local deploy_path envfile deploy_path=$(get_deploy_path "$branch") envfile=$(get_deploy_env_file "$branch") - case "$branch" in "testing") - quick_deploy_expressjs_blog "$branch" "$test_dir" "$deploy_path" "$envfile" - return 0 - ;; - "staging") - # For production/main, use the preserving method (keep logs, update in place) + quick_deploy_expressjs_blog "$branch" "$deploy_path" "$envfile" + ;; + "staging"|"main") + # For staging/production, use the preserving method (keep logs, update in place) conservative_deploy_expressjs_blog "$branch" "$oldrev" "$newrev" "$deploy_path" "$envfile" + ;; + *) + echo "Branch '$branch' not configured for deployment. Skipping." return 0 - ;; - "main") - conservative_deploy_expressjs_blog "$branch" "$oldrev" "$newrev" "$deploy_path" "$envfile" - # echo "Deployment available at: $deploy_path" - ;; - + ;; esac } - -run_postreceive_tests() { - local branch="$1" - local tmpdir pidfile logfile - tmpdir="$2" - pidfile="$tmpdir/test.pid" - logfile=$(get_log_file_path) - # Trap to ensure cleanup even if script exits unexpectedly - trap "kill \"$(cat "$pidfile" 2>/dev/null)\" 2>/dev/null || true; rm -f \"$pidfile\"" EXIT - - echo "Running post-receive tests for branch '$branch' in temporary environment." - - clone_branch "$branch" "$tmpdir" || return 1 - - cd "$tmpdir" || { - echo "Error: Could not change directory to $tmpdir" - return 1 - } - - copy_env_file "$branch" || return 1 - - initialize_submodules "$tmpdir" || return 1 - - # echo "Skipping tests" - # return 0 - - echo "Running build scripts..." - yarn - combine_css || return 1 - - echo "Starting application for tests..." - systemctl --user stop express-blog@"$branch".service - nohup node src/app.js >>"$logfile" 2>&1 & - echo $! >"$pidfile" - - # set +x - wait_for_service "$logfile" - # set -x - - echo "Running tests..." - run_tests "$branch" "$pidfile" "$logfile" || return 1 - - kill "$(cat "$pidfile")" 2>/dev/null || true - - echo "Tests passed for branch '$branch' in temporary environment." - return 0 -} - -initialize_submodules() { - local worktree="$1" - echo "Initializing and updating submodules for test environment..." - git --git-dir="$worktree/.git" --work-tree="$worktree" submodule update --init --recursive || { - ls "$worktree" -a - echo "Error: Failed to initialize/update submodules for test environment." - return 1 - } -} -update_submodules() { - local worktree="$1" - git --work-tree="$worktree" submodule update --init --recursive || { - echo "Error: Failed to initialize/update submodules in deployment" - return 1 - } -} -wait_for_service() { - local logfile="$1" - # Wait for the application to become responsive - if ! _wait_for_service "$SERVER_SCHEMA://$SERVER_DOMAIN"; then - echo "Application did not start or respond for tests. Check logs in $logfile:" - cat "$logfile" # Display logs on failure - return 1 - fi -} - -run_tests() { - branch="$1" - pidfile="$2" - logfile="$3" - echo "Running npm tests..." - if ! npm run test:postreceive; then - kill "$(cat "$pidfile")" 2>/dev/null || true - echo "Tests failed for branch '$branch'. Application logs from $logfile:" - cat "$logfile" - return 1 - fi -} - -combine_css() { - yarn --production=false combine:css || { - echo "Error: yarn combine:css failed." - return 1 - } -} - -ssh-add -l - -set -x -# Main script execution loop +# Post-receive hook: Deploy the changes that were just pushed while read -r oldrev newrev ref; do - tmpdir=$(mktemp -d) branch="${ref#refs/heads/}" - echo "--- Processing push for branch: $branch (from $oldrev to $newrev) ---" - if run_postreceive_tests "$branch" "$tmpdir"; then - echo "Tests passed for $branch. Proceeding with deployment." - deploy_expressjs_blog "$branch" "$tmpdir" "$oldrev" "$newrev" - - # deploy_expressjs_blog "$branch" "$tmpdir" - echo "Deployment of $branch complete." - else - echo "Post-receive tests failed for $branch. Deployment aborted." - exit 1 - fi + + echo "--- Post-receive deployment for branch: $branch (from $oldrev to $newrev) ---" + + # Only deploy branches we care about + case "$branch" in + "testing"|"staging"|"main") + if deploy_expressjs_blog "$branch" "$oldrev" "$newrev"; then + echo "Deployment of $branch complete." + else + echo "Deployment of $branch failed." + # Note: We don't exit 1 here because the push has already been accepted + # Log the failure but continue processing other branches + fi + ;; + *) + echo "Branch '$branch' not configured for deployment. Skipping." + ;; + esac done -set +x + +echo "Post-receive processing complete." diff --git a/.githooks/pre-push b/.githooks/pre-push index 3413108..93153bc 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -1,183 +1,44 @@ -#!/bin/bash -set -euo pipefail # -e: exit on error, -u: exit on unset var, -o pipefail: catch errors in pipes +#!/bin/sh +# Push content subrepo before pushing main repo +remote="$1" +#export GIT_TRACE=1 +#export GIT_TRACE_PACKET=1 +#export GIT_TRACE_PERFORMANCE=1 +#export GIT_SSH_COMMAND="ssh -vvv" -TIMESTAMP=$(date +%Y%m%d-%H%M%S) +set -euo pipefail +set -x -get_deploy_env_file() { - branch="$1" - echo "/srv/jasonpoage.com/${branch}.env" -} +node src/app.js >/dev/null 2>&1 & + APP_PID=$! -get_log_file_path() { - echo "/srv/jasonpoage.com/logs/pre-receive-${TIMESTAMP}.log" -} +sleep 2 -copy_env_file() { - local branch="$1" - local env_file - env_file="$(get_deploy_env_file "$branch")" - source "$env_file" - # Copy the .env file for the test environment - cp "$env_file" "$tmpdir/.env" || { - echo "Error: Failed to copy .env file for test environment." - return 1 - } -} +npm run test:prepush +TEST_RESULT=$? -clone_branch() { - local branch="$1" - local newrev="$2" - local tmpdir="$3" - # Clone the new revision for testing - git clone "$GIT_DIR" "$tmpdir" || { - echo "Error: Failed to clone repository for tests to $tmpdir" - return 1 - } - cd "$tmpdir" - git checkout "$newrev" || { - echo "Error: Failed to checkout revision $newrev" - return 1 - } -} - -# Function to wait for a service to become available -_wait_for_service() { - local url="$1" - local timeout=30 # seconds - local start_time - start_time=$(date +%s) - echo "Waiting for service at $url to become available (timeout: ${timeout}s)..." - while :; do - if curl --silent --fail "$url" >/dev/null; then - echo "Service at $url is up!" - return 0 - fi - current_time=$(date +%s) - if ((current_time - start_time >= timeout)); then - echo "Error: Service at $url did not become available within ${timeout}s." - return 1 - fi + # Clean up the app process + if kill -0 $APP_PID 2>/dev/null; then + echo "Stopping app (PID: $APP_PID)..." + kill $APP_PID + # Give it time to shut down gracefully sleep 1 - done -} - -run_prereceive_tests() { - local branch="$1" - local newrev="$2" - local tmpdir pidfile logfile - tmpdir="$3" - pidfile="$tmpdir/test.pid" - logfile=$(get_log_file_path) - - # Trap to ensure cleanup even if script exits unexpectedly - trap "kill \"$(cat "$pidfile" 2>/dev/null)\" 2>/dev/null || true; rm -f \"$pidfile\"" EXIT - - echo "Running pre-receive tests for branch '$branch' (revision $newrev) in temporary environment." - - clone_branch "$branch" "$newrev" "$tmpdir" || return 1 - - cd "$tmpdir" || { - echo "Error: Could not change directory to $tmpdir" - return 1 - } - - copy_env_file "$branch" || return 1 - - initialize_submodules "$tmpdir" || return 1 - - echo "Running build scripts..." - yarn - combine_css || return 1 - - echo "Starting application for tests..." - # Stop any existing test service to avoid conflicts - systemctl --user stop express-blog@"$branch".service 2>/dev/null || true - nohup node src/app.js >>"$logfile" 2>&1 & - echo $! >"$pidfile" - - wait_for_service "$logfile" - - echo "Running tests..." - run_tests "$branch" "$pidfile" "$logfile" || return 1 - - kill "$(cat "$pidfile")" 2>/dev/null || true - - echo "Pre-receive tests passed for branch '$branch' (revision $newrev)." - return 0 -} -initialize_submodules() { - local worktree="$1" - - echo "Manually extracting submodules from quarantined tree..." - - git --git-dir="$GIT_DIR" ls-tree -r "$newrev" | - awk '$2 == "commit" {print $3, $4}' | - while read -r sub_sha sub_path; do - submodule_path="$worktree/$sub_path" - mkdir -p "$submodule_path" - - # Use the submodule object hash from superproject tree - git --git-dir="$GIT_DIR" archive "$sub_sha" | tar -x -C "$submodule_path" || { - echo "Error: Failed to extract submodule $sub_path at $sub_sha" - return 1 - } - done -} - -wait_for_service() { - local logfile="$1" - # Wait for the application to become responsive - if ! _wait_for_service "$SERVER_SCHEMA://$SERVER_DOMAIN"; then - echo "Application did not start or respond for tests. Check logs in $logfile:" - cat "$logfile" # Display logs on failure - return 1 + # Force kill if still running + if kill -0 $APP_PID 2>/dev/null; then + kill -9 $APP_PID 2>/dev/null || true + fi fi -} - -run_tests() { - branch="$1" - pidfile="$2" - logfile="$3" - echo "Running npm tests..." - if ! npm run test:postreceive; then - kill "$(cat "$pidfile")" 2>/dev/null || true - echo "Tests failed for branch '$branch'. Application logs from $logfile:" - cat "$logfile" - return 1 - fi -} - -combine_css() { - yarn --production=false combine:css || { - echo "Error: yarn combine:css failed." - return 1 - } -} - -# Pre-receive hook: Test incoming changes before accepting them -while read -r oldrev newrev ref; do - tmpdir=$(mktemp -d) - branch="${ref#refs/heads/}" - echo "--- Pre-receive validation for branch: $branch (from $oldrev to $newrev) ---" + # Wait for process to fully terminate + wait $APP_PID 2>/dev/null || true - # Only test branches we care about - case "$branch" in - "testing"|"staging"|"main") - if run_prereceive_tests "$branch" "$newrev" "$tmpdir"; then - echo "Pre-receive tests passed for $branch. Push will be accepted." - rm -rf "$tmpdir" - else - echo "Pre-receive tests failed for $branch. Push rejected." - rm -rf "$tmpdir" - exit 1 - fi - ;; - *) - echo "Branch '$branch' not configured for testing. Push accepted." - rm -rf "$tmpdir" - ;; - esac -done -echo "All pre-receive checks passed. Push accepted." +if [ $TEST_RESULT -ne 0 ]; then + echo "Tests failed. Push aborted." + exit 1 +fi + +cd content +git push "$remote" main +cd .. +set +x diff --git a/.githooks/pre-receive b/.githooks/pre-receive index 74e744b..3413108 100644 --- a/.githooks/pre-receive +++ b/.githooks/pre-receive @@ -1,31 +1,183 @@ #!/bin/bash -set -e +set -euo pipefail # -e: exit on error, -u: exit on unset var, -o pipefail: catch errors in pipes -verify_main_not_ahead() { - local new_main_rev="$1" - local testing_rev - testing_rev=$(git rev-parse refs/heads/testing) +TIMESTAMP=$(date +%Y%m%d-%H%M%S) - # Allow if main and testing are the same commit - if [[ "$new_main_rev" == "$testing_rev" ]]; then - return 0 - fi - - # Reject if main would be ahead of testing - # (i.e., if testing is an ancestor of the new main commit) - if git merge-base --is-ancestor "$testing_rev" "$new_main_rev"; then - echo "ERROR: 'main' would be ahead of 'testing'." - echo "All changes must be tested before merging to 'main'." - echo "Push to 'testing' first and verify before pushing to 'main'." - exit 1 - fi +get_deploy_env_file() { + branch="$1" + echo "/srv/jasonpoage.com/${branch}.env" } -run_hook() { - while read -r oldrev newrev refname; do - branch="${refname#refs/heads/}" - if [[ "$branch" == "main" ]]; then - verify_main_not_ahead "$newrev" + +get_log_file_path() { + echo "/srv/jasonpoage.com/logs/pre-receive-${TIMESTAMP}.log" +} + +copy_env_file() { + local branch="$1" + local env_file + env_file="$(get_deploy_env_file "$branch")" + source "$env_file" + # Copy the .env file for the test environment + cp "$env_file" "$tmpdir/.env" || { + echo "Error: Failed to copy .env file for test environment." + return 1 + } +} + +clone_branch() { + local branch="$1" + local newrev="$2" + local tmpdir="$3" + # Clone the new revision for testing + git clone "$GIT_DIR" "$tmpdir" || { + echo "Error: Failed to clone repository for tests to $tmpdir" + return 1 + } + cd "$tmpdir" + git checkout "$newrev" || { + echo "Error: Failed to checkout revision $newrev" + return 1 + } +} + +# Function to wait for a service to become available +_wait_for_service() { + local url="$1" + local timeout=30 # seconds + local start_time + start_time=$(date +%s) + echo "Waiting for service at $url to become available (timeout: ${timeout}s)..." + while :; do + if curl --silent --fail "$url" >/dev/null; then + echo "Service at $url is up!" + return 0 fi + current_time=$(date +%s) + if ((current_time - start_time >= timeout)); then + echo "Error: Service at $url did not become available within ${timeout}s." + return 1 + fi + sleep 1 done } -#run_hook() + +run_prereceive_tests() { + local branch="$1" + local newrev="$2" + local tmpdir pidfile logfile + tmpdir="$3" + pidfile="$tmpdir/test.pid" + logfile=$(get_log_file_path) + + # Trap to ensure cleanup even if script exits unexpectedly + trap "kill \"$(cat "$pidfile" 2>/dev/null)\" 2>/dev/null || true; rm -f \"$pidfile\"" EXIT + + echo "Running pre-receive tests for branch '$branch' (revision $newrev) in temporary environment." + + clone_branch "$branch" "$newrev" "$tmpdir" || return 1 + + cd "$tmpdir" || { + echo "Error: Could not change directory to $tmpdir" + return 1 + } + + copy_env_file "$branch" || return 1 + + initialize_submodules "$tmpdir" || return 1 + + echo "Running build scripts..." + yarn + combine_css || return 1 + + echo "Starting application for tests..." + # Stop any existing test service to avoid conflicts + systemctl --user stop express-blog@"$branch".service 2>/dev/null || true + nohup node src/app.js >>"$logfile" 2>&1 & + echo $! >"$pidfile" + + wait_for_service "$logfile" + + echo "Running tests..." + run_tests "$branch" "$pidfile" "$logfile" || return 1 + + kill "$(cat "$pidfile")" 2>/dev/null || true + + echo "Pre-receive tests passed for branch '$branch' (revision $newrev)." + return 0 +} +initialize_submodules() { + local worktree="$1" + + echo "Manually extracting submodules from quarantined tree..." + + git --git-dir="$GIT_DIR" ls-tree -r "$newrev" | + awk '$2 == "commit" {print $3, $4}' | + while read -r sub_sha sub_path; do + submodule_path="$worktree/$sub_path" + mkdir -p "$submodule_path" + + # Use the submodule object hash from superproject tree + git --git-dir="$GIT_DIR" archive "$sub_sha" | tar -x -C "$submodule_path" || { + echo "Error: Failed to extract submodule $sub_path at $sub_sha" + return 1 + } + done +} + +wait_for_service() { + local logfile="$1" + # Wait for the application to become responsive + if ! _wait_for_service "$SERVER_SCHEMA://$SERVER_DOMAIN"; then + echo "Application did not start or respond for tests. Check logs in $logfile:" + cat "$logfile" # Display logs on failure + return 1 + fi +} + +run_tests() { + branch="$1" + pidfile="$2" + logfile="$3" + echo "Running npm tests..." + if ! npm run test:postreceive; then + kill "$(cat "$pidfile")" 2>/dev/null || true + echo "Tests failed for branch '$branch'. Application logs from $logfile:" + cat "$logfile" + return 1 + fi +} + +combine_css() { + yarn --production=false combine:css || { + echo "Error: yarn combine:css failed." + return 1 + } +} + +# Pre-receive hook: Test incoming changes before accepting them +while read -r oldrev newrev ref; do + tmpdir=$(mktemp -d) + branch="${ref#refs/heads/}" + + echo "--- Pre-receive validation for branch: $branch (from $oldrev to $newrev) ---" + + # Only test branches we care about + case "$branch" in + "testing"|"staging"|"main") + if run_prereceive_tests "$branch" "$newrev" "$tmpdir"; then + echo "Pre-receive tests passed for $branch. Push will be accepted." + rm -rf "$tmpdir" + else + echo "Pre-receive tests failed for $branch. Push rejected." + rm -rf "$tmpdir" + exit 1 + fi + ;; + *) + echo "Branch '$branch' not configured for testing. Push accepted." + rm -rf "$tmpdir" + ;; + esac +done + +echo "All pre-receive checks passed. Push accepted." diff --git a/.githooks/pre-receive copy b/.githooks/pre-receive copy new file mode 100644 index 0000000..74e744b --- /dev/null +++ b/.githooks/pre-receive copy @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +verify_main_not_ahead() { + local new_main_rev="$1" + local testing_rev + testing_rev=$(git rev-parse refs/heads/testing) + + # Allow if main and testing are the same commit + if [[ "$new_main_rev" == "$testing_rev" ]]; then + return 0 + fi + + # Reject if main would be ahead of testing + # (i.e., if testing is an ancestor of the new main commit) + if git merge-base --is-ancestor "$testing_rev" "$new_main_rev"; then + echo "ERROR: 'main' would be ahead of 'testing'." + echo "All changes must be tested before merging to 'main'." + echo "Push to 'testing' first and verify before pushing to 'main'." + exit 1 + fi +} +run_hook() { + while read -r oldrev newrev refname; do + branch="${refname#refs/heads/}" + if [[ "$branch" == "main" ]]; then + verify_main_not_ahead "$newrev" + fi + done +} +#run_hook()