Initial Ladill Give extraction — online giving pages at give.ladill.com.
Donation checkout via Ladill Pay (9% fee), church/giving QR pages, SSO, and platform scan forwarding. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Executable
+126
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF' >&2
|
||||
Usage:
|
||||
ladill-hosting-admin self-check
|
||||
ladill-hosting-admin mkdir <path>
|
||||
ladill-hosting-admin chown <owner:group> <path>
|
||||
ladill-hosting-admin chmod <mode> <path>
|
||||
ladill-hosting-admin write-file <path> <base64-content>
|
||||
ladill-hosting-admin symlink <target> <link>
|
||||
ladill-hosting-admin nginx-test
|
||||
ladill-hosting-admin reload-nginx
|
||||
ladill-hosting-admin certbot-webroot <email> <domain> <webroot>
|
||||
ladill-hosting-admin certbot-renew
|
||||
ladill-hosting-admin run-cmd <command>
|
||||
ladill-hosting-admin read-file <path>
|
||||
EOF
|
||||
exit 64
|
||||
}
|
||||
|
||||
require_abs_path() {
|
||||
local path="${1:-}"
|
||||
|
||||
if [[ -z "$path" || "${path#/}" == "$path" ]]; then
|
||||
echo "Expected an absolute path, got: $path" >&2
|
||||
exit 64
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_domain() {
|
||||
local value="${1:-}"
|
||||
|
||||
if [[ ! "$value" =~ ^[A-Za-z0-9.-]+$ ]]; then
|
||||
echo "Invalid domain: $value" >&2
|
||||
exit 64
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_owner_group() {
|
||||
local value="${1:-}"
|
||||
|
||||
if [[ ! "$value" =~ ^[A-Za-z_][A-Za-z0-9_-]*:[A-Za-z_][A-Za-z0-9_-]*$ ]]; then
|
||||
echo "Invalid owner:group value: $value" >&2
|
||||
exit 64
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_mode() {
|
||||
local value="${1:-}"
|
||||
|
||||
if [[ ! "$value" =~ ^[0-7]{3,4}$ && ! "$value" =~ ^[ugoa,+-=rwxX]+$ ]]; then
|
||||
echo "Invalid chmod mode: $value" >&2
|
||||
exit 64
|
||||
fi
|
||||
}
|
||||
|
||||
command="${1:-}"
|
||||
|
||||
case "$command" in
|
||||
self-check)
|
||||
exit 0
|
||||
;;
|
||||
mkdir)
|
||||
[[ $# -eq 2 ]] || usage
|
||||
require_abs_path "$2"
|
||||
exec mkdir -p "$2"
|
||||
;;
|
||||
chown)
|
||||
[[ $# -eq 3 ]] || usage
|
||||
ensure_owner_group "$2"
|
||||
require_abs_path "$3"
|
||||
exec chown -R "$2" "$3"
|
||||
;;
|
||||
chmod)
|
||||
[[ $# -eq 3 ]] || usage
|
||||
ensure_mode "$2"
|
||||
require_abs_path "$3"
|
||||
exec chmod "$2" "$3"
|
||||
;;
|
||||
write-file)
|
||||
[[ $# -eq 3 ]] || usage
|
||||
require_abs_path "$2"
|
||||
tmpfile="$(mktemp)"
|
||||
trap 'rm -f "$tmpfile"' EXIT
|
||||
printf '%s' "$3" | base64 --decode > "$tmpfile"
|
||||
install -m 0644 "$tmpfile" "$2"
|
||||
rm -f "$tmpfile"
|
||||
trap - EXIT
|
||||
;;
|
||||
symlink)
|
||||
[[ $# -eq 3 ]] || usage
|
||||
require_abs_path "$2"
|
||||
require_abs_path "$3"
|
||||
exec ln -sfn "$2" "$3"
|
||||
;;
|
||||
nginx-test)
|
||||
exec nginx -t
|
||||
;;
|
||||
reload-nginx)
|
||||
exec systemctl reload nginx
|
||||
;;
|
||||
certbot-webroot)
|
||||
[[ $# -eq 4 ]] || usage
|
||||
ensure_domain "$3"
|
||||
require_abs_path "$4"
|
||||
exec certbot certonly --webroot --non-interactive --agree-tos --no-eff-email -m "$2" -w "$4" -d "$3" -d "www.$3"
|
||||
;;
|
||||
certbot-renew)
|
||||
exec certbot renew --quiet --no-random-sleep-on-renew
|
||||
;;
|
||||
run-cmd)
|
||||
[[ $# -ge 2 ]] || usage
|
||||
shift
|
||||
exec bash -c "$*"
|
||||
;;
|
||||
read-file)
|
||||
[[ $# -eq 2 ]] || usage
|
||||
require_abs_path "$2"
|
||||
exec cat "$2"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: ladill-hosting-user <username> <base64-command>" >&2
|
||||
exit 64
|
||||
}
|
||||
|
||||
[[ $# -eq 2 ]] || usage
|
||||
|
||||
username="$1"
|
||||
payload_b64="$2"
|
||||
|
||||
if [[ ! "$username" =~ ^[A-Za-z_][A-Za-z0-9_-]*$ ]]; then
|
||||
echo "Invalid username: $username" >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
command="$(printf '%s' "$payload_b64" | base64 --decode)"
|
||||
|
||||
exec runuser -u "$username" -- bash -lc "$command"
|
||||
Executable
+249
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fast on-host release deploy (same model as climpme/web/deploy/deploy.sh).
|
||||
set -Eeuo pipefail
|
||||
|
||||
APP_ROOT="${LADILL_APP_ROOT:-/var/www/ladill-give}"
|
||||
RELEASES_DIR="$APP_ROOT/releases"
|
||||
SHARED_DIR="$APP_ROOT/shared"
|
||||
CURRENT_LINK="$APP_ROOT/current"
|
||||
RELEASE_ARCHIVE="${LADILL_RELEASE_ARCHIVE:-/tmp/ladill-release.tgz}"
|
||||
KEEP_RELEASES="${LADILL_KEEP_RELEASES:-5}"
|
||||
|
||||
STAMP="$(date +%Y%m%d%H%M%S)"
|
||||
NEW_RELEASE="$RELEASES_DIR/$STAMP"
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date '+%F %T')" "$*"
|
||||
}
|
||||
|
||||
bootstrap_shared_env() {
|
||||
local candidate=""
|
||||
|
||||
if [ -f "$SHARED_DIR/.env" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
for candidate in "$CURRENT_LINK/.env" "$APP_ROOT/.env"; do
|
||||
if [ -f "$candidate" ]; then
|
||||
cp -fL "$candidate" "$SHARED_DIR/.env"
|
||||
log "Bootstrapped shared .env from $candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
ensure_writable_paths() {
|
||||
local paths=("$@")
|
||||
|
||||
[ "${#paths[@]}" -gt 0 ] || return 0
|
||||
|
||||
chmod -R ug+rwX "${paths[@]}" 2>/dev/null || true
|
||||
|
||||
if command -v find >/dev/null 2>&1; then
|
||||
find "${paths[@]}" -type d -exec chmod ug+rwx {} + 2>/dev/null || true
|
||||
find "${paths[@]}" -type d -exec chmod g+s {} + 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
normalize_shared_permissions() {
|
||||
local owner="${LADILL_DEPLOY_USER:-deploy}"
|
||||
local group="${LADILL_DEPLOY_GROUP:-www-data}"
|
||||
local shared_paths=("$SHARED_DIR/storage" "$SHARED_DIR/bootstrap-cache")
|
||||
|
||||
id -u "$owner" >/dev/null 2>&1 || return 0
|
||||
getent group "$group" >/dev/null 2>&1 || return 0
|
||||
|
||||
if chown -R "$owner:$group" "${shared_paths[@]}" 2>/dev/null; then
|
||||
:
|
||||
elif command -v sudo >/dev/null 2>&1; then
|
||||
sudo -n chown -R "$owner:$group" "${shared_paths[@]}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
ensure_writable_paths "${shared_paths[@]}"
|
||||
}
|
||||
|
||||
run_artisan() {
|
||||
local command="$1"
|
||||
|
||||
if [ -f "$NEW_RELEASE/artisan" ]; then
|
||||
(cd "$NEW_RELEASE" && php artisan ${command})
|
||||
fi
|
||||
}
|
||||
|
||||
uses_sqlite() {
|
||||
[ -f "$SHARED_DIR/.env" ] && grep -Eq '^DB_CONNECTION=sqlite([[:space:]]*)$' "$SHARED_DIR/.env"
|
||||
}
|
||||
|
||||
sqlite_database_setting() {
|
||||
[ -f "$SHARED_DIR/.env" ] || return 1
|
||||
|
||||
grep -E '^DB_DATABASE=' "$SHARED_DIR/.env" | tail -n 1 | cut -d= -f2- | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//"
|
||||
}
|
||||
|
||||
prepare_sqlite_database() {
|
||||
local configured_path=""
|
||||
local db_name=""
|
||||
local shared_sqlite_path=""
|
||||
local current_sqlite_path=""
|
||||
local release_sqlite_path=""
|
||||
|
||||
configured_path="$(sqlite_database_setting || true)"
|
||||
[ -n "$configured_path" ] || configured_path="database/database.sqlite"
|
||||
|
||||
if [[ "$configured_path" = /* ]]; then
|
||||
shared_sqlite_path="$configured_path"
|
||||
mkdir -p "$(dirname "$shared_sqlite_path")"
|
||||
else
|
||||
db_name="$(basename "$configured_path")"
|
||||
shared_sqlite_path="$SHARED_DIR/database/$db_name"
|
||||
current_sqlite_path="$CURRENT_LINK/$configured_path"
|
||||
release_sqlite_path="$NEW_RELEASE/$configured_path"
|
||||
|
||||
mkdir -p "$SHARED_DIR/database" "$(dirname "$release_sqlite_path")"
|
||||
|
||||
if [ ! -f "$shared_sqlite_path" ]; then
|
||||
if [ -f "$current_sqlite_path" ]; then
|
||||
cp -fL "$current_sqlite_path" "$shared_sqlite_path"
|
||||
log "Bootstrapped shared sqlite database from $current_sqlite_path"
|
||||
elif [ -f "$APP_ROOT/$configured_path" ]; then
|
||||
cp -fL "$APP_ROOT/$configured_path" "$shared_sqlite_path"
|
||||
log "Bootstrapped shared sqlite database from $APP_ROOT/$configured_path"
|
||||
else
|
||||
touch "$shared_sqlite_path"
|
||||
log "Created shared sqlite database at $shared_sqlite_path"
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$release_sqlite_path"
|
||||
ln -sfn "$shared_sqlite_path" "$release_sqlite_path"
|
||||
ensure_writable_paths "$SHARED_DIR/database"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$shared_sqlite_path" ]; then
|
||||
touch "$shared_sqlite_path"
|
||||
log "Created shared sqlite database at $shared_sqlite_path"
|
||||
fi
|
||||
|
||||
ensure_writable_paths "$(dirname "$shared_sqlite_path")"
|
||||
}
|
||||
|
||||
log "Preparing release $STAMP"
|
||||
[ -f "$RELEASE_ARCHIVE" ] || { echo "Release archive not found: $RELEASE_ARCHIVE" >&2; exit 1; }
|
||||
|
||||
mkdir -p "$RELEASES_DIR" "$SHARED_DIR" "$NEW_RELEASE"
|
||||
tar -xzf "$RELEASE_ARCHIVE" -C "$NEW_RELEASE"
|
||||
chmod -R u+rwX "$NEW_RELEASE" 2>/dev/null || true
|
||||
|
||||
log "Linking shared paths"
|
||||
mkdir -p "$SHARED_DIR/storage/"{app/public,app/private,framework/{cache/data,sessions,testing,views},logs}
|
||||
mkdir -p "$SHARED_DIR/bootstrap-cache"
|
||||
normalize_shared_permissions
|
||||
|
||||
if bootstrap_shared_env; then
|
||||
ln -sfn "$SHARED_DIR/.env" "$NEW_RELEASE/.env"
|
||||
else
|
||||
echo "Missing deployment environment file. Expected $SHARED_DIR/.env or an existing $CURRENT_LINK/.env / $APP_ROOT/.env to bootstrap from." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$NEW_RELEASE/storage"
|
||||
ln -sfn "$SHARED_DIR/storage" "$NEW_RELEASE/storage"
|
||||
mkdir -p "$NEW_RELEASE/bootstrap"
|
||||
rm -rf "$NEW_RELEASE/bootstrap/cache"
|
||||
ln -sfn "$SHARED_DIR/bootstrap-cache" "$NEW_RELEASE/bootstrap/cache"
|
||||
normalize_shared_permissions
|
||||
|
||||
if uses_sqlite; then
|
||||
log "Preparing shared sqlite database"
|
||||
prepare_sqlite_database
|
||||
if id -u www-data >/dev/null 2>&1; then
|
||||
chgrp www-data "$SHARED_DIR/database" 2>/dev/null || true
|
||||
find "$SHARED_DIR/database" -type f -exec chgrp www-data {} + 2>/dev/null || true
|
||||
find "$SHARED_DIR/database" -type d -exec chgrp www-data {} + 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Installing PHP dependencies"
|
||||
if [ -f "$NEW_RELEASE/composer.json" ]; then
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
export COMPOSER_ALLOW_SUPERUSER=1
|
||||
fi
|
||||
export COMPOSER_HOME="${COMPOSER_HOME:-/home/deploy/.composer}"
|
||||
(
|
||||
cd "$NEW_RELEASE"
|
||||
composer install \
|
||||
--no-dev \
|
||||
--prefer-dist \
|
||||
--no-interaction \
|
||||
--no-progress \
|
||||
--optimize-autoloader \
|
||||
--classmap-authoritative
|
||||
)
|
||||
fi
|
||||
|
||||
log "Running migrations"
|
||||
if ! run_artisan "migrate --force"; then
|
||||
log "Migration failed — clearing shared bootstrap cache"
|
||||
run_artisan "optimize:clear" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
switch_current_release() {
|
||||
if ln -sfnT "$NEW_RELEASE" "$CURRENT_LINK" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v sudo >/dev/null 2>&1 && sudo -n ln -sfnT "$NEW_RELEASE" "$CURRENT_LINK"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Unable to update current release symlink at $CURRENT_LINK" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
log "Switching current release"
|
||||
if ! switch_current_release; then
|
||||
echo "Failed to point $CURRENT_LINK at $NEW_RELEASE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LIVE_REV="$(cat "$CURRENT_LINK/REVISION" 2>/dev/null || echo unknown)"
|
||||
log "Live release: $STAMP (revision $LIVE_REV)"
|
||||
|
||||
log "Optimizing Laravel"
|
||||
if [ -L "$CURRENT_LINK" ] && [ -f "$CURRENT_LINK/artisan" ]; then
|
||||
(
|
||||
cd "$CURRENT_LINK"
|
||||
php artisan storage:link || true
|
||||
php artisan optimize:clear || true
|
||||
php artisan config:cache || true
|
||||
php artisan route:cache || true
|
||||
php artisan view:cache || true
|
||||
)
|
||||
fi
|
||||
|
||||
log "Reloading services"
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl reload php8.4-fpm 2>/dev/null || sudo -n systemctl reload php8.4-fpm
|
||||
systemctl reload nginx 2>/dev/null || sudo -n systemctl reload nginx
|
||||
fi
|
||||
|
||||
log "Restarting queues"
|
||||
if [ -L "$CURRENT_LINK" ] && [ -f "$CURRENT_LINK/artisan" ]; then
|
||||
(cd "$CURRENT_LINK" && php artisan queue:restart) || true
|
||||
fi
|
||||
if command -v supervisorctl >/dev/null 2>&1; then
|
||||
supervisorctl restart ladill-give-worker:* 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log "Cleaning old releases"
|
||||
mapfile -t OLD_RELEASES < <(ls -1dt "$RELEASES_DIR"/* 2>/dev/null | tail -n "+$((KEEP_RELEASES + 1))" || true)
|
||||
if [ "${#OLD_RELEASES[@]}" -gt 0 ]; then
|
||||
chmod -R u+rwX "${OLD_RELEASES[@]}" 2>/dev/null || true
|
||||
rm -rf "${OLD_RELEASES[@]}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log "Deploy completed: $STAMP"
|
||||
@@ -0,0 +1,7 @@
|
||||
# Replace `www-data` with the actual PHP-FPM/web user that runs the app.
|
||||
# Install as `/etc/sudoers.d/ladill-hosting` with mode `0440`.
|
||||
#
|
||||
# These helpers are root-owned fixed entry points installed by the deploy script.
|
||||
|
||||
www-data ALL=(root) NOPASSWD: /usr/local/bin/ladill-hosting-admin *
|
||||
www-data ALL=(root) NOPASSWD: /usr/local/bin/ladill-hosting-user *
|
||||
Reference in New Issue
Block a user