Keep similar-ai from tripping the global circuit on a lone URL 502, clamp Qdrant search to 100, and add Server-Timing plus slow-request logging. Studio shared props, Academy S3 exists caching, heat chunking, and Redis/scheduler hygiene stay in this rollout.
227 lines
11 KiB
Bash
227 lines
11 KiB
Bash
#!/usr/bin/env bash
|
|
# Skinbase.org M0 production baseline collector
|
|
# READ-ONLY / non-destructive / secret-safe
|
|
#
|
|
# Run on the production application host from the live app directory, e.g.:
|
|
# cd /opt/www/virtual/SkinbaseNova && sudo -u www-data bash scripts/collect-production-baseline.sh
|
|
#
|
|
# Never prints: passwords, tokens, keys, APP_KEY, DSNs, cookies, session payloads, .env dumps.
|
|
# Does not: write to MySQL, flush Redis, restart services, clear caches, run jobs, migrate, deploy.
|
|
|
|
set -u
|
|
set -o pipefail
|
|
|
|
umask 077
|
|
|
|
APPROVED_ENV_KEYS='^(APP_ENV|APP_DEBUG|CACHE_STORE|SESSION_DRIVER|QUEUE_CONNECTION|SCOUT_DRIVER|FILESYSTEM_DISK|UPLOAD_QUEUE_DERIVATIVES|DOWNLOAD_ACCEL_ENABLED|DOWNLOAD_ACCEL_PATH|SITEMAPS_BUILD_ON_REQUEST|SITEMAPS_FALLBACK_TO_LIVE_BUILD|SITEMAPS_PREGENERATED_ENABLED|SITEMAPS_PREFER_PUBLISHED_RELEASE|REDIS_CLIENT|HOMEPAGE_CACHE_STORE|BROADCAST_CONNECTION|SCOUT_QUEUE_NAME|HORIZON_PATH)$'
|
|
|
|
redact() {
|
|
sed -E \
|
|
-e 's/(password|passwd|secret|token|apikey|api_key|access_key|private_key|dsn|authorization)[=:][[:space:]]*[^[:space:]]+/\1=[REDACTED]/Ig' \
|
|
-e 's#mysql://[^[:space:]]+#mysql://[REDACTED]#g' \
|
|
-e 's#redis://[^[:space:]]+#redis://[REDACTED]#g'
|
|
}
|
|
|
|
run() {
|
|
local name="$1"
|
|
shift
|
|
echo "=== ${name} ===" >>"${LOG}"
|
|
echo "\$ $*" >>"${LOG}"
|
|
if "$@" >>"${OUT}/${name}.txt" 2>>"${OUT}/${name}.err"; then
|
|
echo "OK ${name}" | tee -a "${LOG}"
|
|
else
|
|
echo "FAIL ${name} exit=$?" | tee -a "${LOG}"
|
|
fi
|
|
}
|
|
|
|
sql() {
|
|
local name="$1"
|
|
local query="$2"
|
|
echo "=== sql:${name} ===" >>"${LOG}"
|
|
if command -v php >/dev/null 2>&1 && [[ -f artisan ]]; then
|
|
php artisan db:show --counts=false >/dev/null 2>&1 || true
|
|
php -r '
|
|
require "vendor/autoload.php";
|
|
$app = require "bootstrap/app.php";
|
|
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
|
$kernel->bootstrap();
|
|
$q = $argv[1];
|
|
try {
|
|
$rows = Illuminate\Support\Facades\DB::select($q);
|
|
echo json_encode($rows, JSON_PRETTY_PRINT), PHP_EOL;
|
|
} catch (Throwable $e) {
|
|
fwrite(STDERR, "SQL_ERROR " . $e->getMessage() . PHP_EOL);
|
|
exit(1);
|
|
}
|
|
' -- "$query" >"${OUT}/sql-${name}.json" 2>"${OUT}/sql-${name}.err" && echo "OK sql:${name}" | tee -a "${LOG}" || echo "FAIL sql:${name}" | tee -a "${LOG}"
|
|
else
|
|
echo "SKIP sql:${name} (no php/artisan)" | tee -a "${LOG}"
|
|
fi
|
|
}
|
|
|
|
ROOT="$(pwd)"
|
|
if [[ -f artisan ]]; then
|
|
ROOT="$(pwd)"
|
|
elif [[ -f ../artisan ]]; then
|
|
cd ..
|
|
ROOT="$(pwd)"
|
|
fi
|
|
|
|
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
OUT="${ROOT}/storage/app/optimization-baseline/${STAMP}"
|
|
mkdir -p "${OUT}"
|
|
LOG="${OUT}/collector.log"
|
|
touch "${LOG}"
|
|
echo "Skinbase M0 collector start ${STAMP}" | tee -a "${LOG}"
|
|
echo "cwd=${ROOT}" | tee -a "${LOG}"
|
|
|
|
# --- host ---
|
|
run 01-hostname hostname
|
|
run 01b-hostname-f hostname -f
|
|
run 02-uname uname -a
|
|
run 03-os-release cat /etc/os-release
|
|
run 04-lscpu lscpu
|
|
run 05-free free -h
|
|
run 06-lsblk lsblk
|
|
run 07-df df -h
|
|
run 08-uptime uptime
|
|
run 09-vmstat vmstat 1 5
|
|
|
|
if command -v iostat >/dev/null 2>&1; then
|
|
run 10-iostat iostat -x 1 5
|
|
else
|
|
echo "SKIP iostat" | tee -a "${LOG}"
|
|
fi
|
|
|
|
run 11-ps-cpu ps aux --sort=-%cpu
|
|
run 12-ps-mem ps aux --sort=-%mem
|
|
# keep only heads in summary copies
|
|
if [[ -f "${OUT}/11-ps-cpu.txt" ]]; then head -n 30 "${OUT}/11-ps-cpu.txt" >"${OUT}/11-ps-cpu-top30.txt"; fi
|
|
if [[ -f "${OUT}/12-ps-mem.txt" ]]; then head -n 30 "${OUT}/12-ps-mem.txt" >"${OUT}/12-ps-mem-top30.txt"; fi
|
|
|
|
run 13-ps-stack bash -c "ps aux | egrep 'nginx|php-fpm|redis|mysql|mysqld|meili|horizon|queue:work|supervisord|ssr.js|reverb|artisan' | grep -v grep || true"
|
|
run 14-systemctl-running systemctl --type=service --state=running --no-pager
|
|
|
|
# --- nginx ---
|
|
run 15-nginx-v nginx -v
|
|
run 16-nginx-V nginx -V
|
|
if command -v nginx >/dev/null 2>&1; then
|
|
if nginx -T >/dev/null 2>&1; then
|
|
nginx -T 2>"${OUT}/17-nginx-T.err" | redact >"${OUT}/17-nginx-T.txt" || true
|
|
echo "OK 17-nginx-T" | tee -a "${LOG}"
|
|
elif sudo -n nginx -T >/dev/null 2>&1; then
|
|
sudo -n nginx -T 2>"${OUT}/17-nginx-T.err" | redact >"${OUT}/17-nginx-T.txt" || true
|
|
echo "OK 17-nginx-T via sudo -n" | tee -a "${LOG}"
|
|
else
|
|
echo "SKIP 17-nginx-T (need permission)" | tee -a "${LOG}"
|
|
fi
|
|
fi
|
|
|
|
# --- php ---
|
|
run 18-php-v php -v
|
|
run 19-php-ini php --ini
|
|
run 20-php-m php -m
|
|
run 21-php-limits bash -c "php -i | egrep 'memory_limit|max_execution_time|max_input_time|upload_max_filesize|post_max_size|max_file_uploads|realpath_cache_size|realpath_cache_ttl' || true"
|
|
run 22-php-opcache bash -c "php -i | grep -i opcache || true"
|
|
|
|
run 23-php-fpm-ps bash -c "ps aux | grep php-fpm | grep -v grep || true"
|
|
run 24-php-units systemctl list-units --no-pager
|
|
# filter later
|
|
|
|
# copy fpm pool configs if readable
|
|
mkdir -p "${OUT}/php-fpm"
|
|
for f in /etc/php/*/fpm/pool.d/*.conf /etc/php-fpm.d/*.conf /usr/local/etc/php-fpm.d/*.conf; do
|
|
if [[ -r "$f" ]]; then
|
|
cp "$f" "${OUT}/php-fpm/$(basename "$f")" || true
|
|
fi
|
|
done
|
|
|
|
# --- laravel non-secret env ---
|
|
if [[ -f .env ]]; then
|
|
grep -E "${APPROVED_ENV_KEYS}" .env >"${OUT}/25-env-approved.txt" 2>"${OUT}/25-env-approved.err" || true
|
|
echo "OK 25-env-approved (filtered)" | tee -a "${LOG}"
|
|
else
|
|
echo "SKIP 25-env-approved (no .env in cwd)" | tee -a "${LOG}"
|
|
fi
|
|
|
|
if [[ -f artisan ]]; then
|
|
php artisan about --only=environment,cache,drivers,storage 2>"${OUT}/26-artisan-about.err" | redact >"${OUT}/26-artisan-about.txt" || echo "FAIL artisan about" | tee -a "${LOG}"
|
|
php artisan horizon:status >"${OUT}/27-horizon-status.txt" 2>"${OUT}/27-horizon-status.err" || true
|
|
php artisan queue:failed --json >"${OUT}/28-queue-failed.json" 2>"${OUT}/28-queue-failed.err" || php artisan queue:failed >"${OUT}/28-queue-failed.txt" 2>"${OUT}/28-queue-failed.err" || true
|
|
php artisan schedule:list >"${OUT}/29-schedule-list.txt" 2>"${OUT}/29-schedule-list.err" || true
|
|
php artisan route:list --columns=method,uri,name,action 2>/dev/null | egrep -i 'debugbar|telescope|clockwork' >"${OUT}/30-debug-routes.txt" || true
|
|
fi
|
|
|
|
# laravel cache files
|
|
ls -la bootstrap/cache >"${OUT}/31-bootstrap-cache.txt" 2>"${OUT}/31-bootstrap-cache.err" || true
|
|
ls -la storage/framework/views | head -n 20 >"${OUT}/32-compiled-views.txt" 2>"${OUT}/32-compiled-views.err" || true
|
|
ls -la public/sitemaps | head -n 50 >"${OUT}/33-public-sitemaps.txt" 2>"${OUT}/33-public-sitemaps.err" || true
|
|
ls -la public/sitemap.xml public/robots.txt >"${OUT}/34-public-seo-files.txt" 2>"${OUT}/34-public-seo-files.err" || true
|
|
|
|
# composer install mode
|
|
if [[ -f vendor/composer/installed.json ]]; then
|
|
php -r '$j=json_decode(file_get_contents("vendor/composer/installed.json"), true); echo "dev-package-count="; $n=0; foreach (($j["packages-dev"] ?? []) as $p) { $n++; } echo $n, PHP_EOL; echo "packages-count=", count($j["packages"] ?? []), PHP_EOL;' >"${OUT}/35-composer-dev-state.txt" 2>"${OUT}/35-composer-dev-state.err" || true
|
|
fi
|
|
|
|
# --- mysql ---
|
|
run 36-mysql-version mysql --version
|
|
sql 37-version "SELECT VERSION() AS version"
|
|
sql 38-vars "SHOW VARIABLES WHERE Variable_name IN ('innodb_buffer_pool_size','innodb_buffer_pool_instances','max_connections','thread_cache_size','tmp_table_size','max_heap_table_size','slow_query_log','slow_query_log_file','long_query_time','log_queries_not_using_indexes','performance_schema')"
|
|
sql 39-status "SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected','Threads_running','Max_used_connections','Slow_queries','Queries','Questions','Uptime')"
|
|
sql 40-dbsize "SELECT table_schema, ROUND(SUM(data_length + index_length)/1024/1024,1) AS total_mb, ROUND(SUM(data_length)/1024/1024,1) AS data_mb, ROUND(SUM(index_length)/1024/1024,1) AS index_mb FROM information_schema.tables WHERE table_schema = DATABASE() GROUP BY table_schema"
|
|
sql 41-largest "SELECT table_name, table_rows, ROUND(data_length/1024/1024,1) AS data_mb, ROUND(index_length/1024/1024,1) AS index_mb, ROUND((data_length+index_length)/1024/1024,1) AS total_mb FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY data_length+index_length DESC LIMIT 50"
|
|
sql 42-cache-sessions "SELECT table_name, table_rows, ROUND((data_length+index_length)/1024/1024,1) AS mb FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name IN ('cache','cache_locks','sessions')"
|
|
sql 43-innodb-reads "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%'"
|
|
sql 44-innodb-rows "SHOW GLOBAL STATUS LIKE 'Innodb_rows_%'"
|
|
sql 45-tmp "SHOW GLOBAL STATUS LIKE 'Created_tmp%'"
|
|
sql 46-handler "SHOW GLOBAL STATUS LIKE 'Handler_read%'"
|
|
|
|
for tbl in artworks artwork_stats artwork_metric_snapshots_hourly rank_artwork_scores tags artwork_view_events artwork_downloads; do
|
|
sql "index-${tbl}" "SHOW INDEX FROM \`${tbl}\`"
|
|
done
|
|
|
|
# --- redis ---
|
|
if command -v redis-cli >/dev/null 2>&1; then
|
|
redis-cli INFO server >"${OUT}/50-redis-server.txt" 2>"${OUT}/50-redis-server.err" || true
|
|
redis-cli INFO memory >"${OUT}/51-redis-memory.txt" 2>"${OUT}/51-redis-memory.err" || true
|
|
redis-cli INFO stats >"${OUT}/52-redis-stats.txt" 2>"${OUT}/52-redis-stats.err" || true
|
|
redis-cli INFO clients >"${OUT}/53-redis-clients.txt" 2>"${OUT}/53-redis-clients.err" || true
|
|
redis-cli INFO keyspace >"${OUT}/54-redis-keyspace.txt" 2>"${OUT}/54-redis-keyspace.err" || true
|
|
for q in default search vision recommendations discovery mail notifications broadcasts forum-security forum-moderation; do
|
|
echo -n "queues:${q}: " >>"${OUT}/55-redis-queue-llen.txt"
|
|
redis-cli LLEN "queues:${q}" >>"${OUT}/55-redis-queue-llen.txt" 2>>"${OUT}/55-redis-queue-llen.err" || echo "fail" >>"${OUT}/55-redis-queue-llen.txt"
|
|
done
|
|
echo "OK redis-cli probes" | tee -a "${LOG}"
|
|
else
|
|
echo "SKIP redis-cli" | tee -a "${LOG}"
|
|
fi
|
|
|
|
# --- processes / workers ---
|
|
run 56-queue-ps bash -c "ps aux | grep -E 'queue:work|horizon|ssr.js|reverb' | grep -v grep || true"
|
|
run 57-supervisor bash -c "command -v supervisorctl >/dev/null && supervisorctl status || echo 'no supervisorctl'"
|
|
run 58-cron-user bash -c "crontab -l || echo 'no user crontab'"
|
|
run 59-systemd-timers systemctl list-timers --no-pager
|
|
|
|
# --- meilisearch ---
|
|
run 60-meili-ps bash -c "ps aux | grep -i meili | grep -v grep || true"
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -sS --max-time 5 http://127.0.0.1:7700/health >"${OUT}/61-meili-health.txt" 2>"${OUT}/61-meili-health.err" || true
|
|
curl -sS --max-time 5 http://127.0.0.1:7700/version >"${OUT}/62-meili-version.txt" 2>"${OUT}/62-meili-version.err" || true
|
|
fi
|
|
|
|
# --- public HTTP from the server itself ---
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -sI --max-time 15 https://skinbase.org/ >"${OUT}/70-head-home.txt" 2>"${OUT}/70-head-home.err" || true
|
|
curl -sI --max-time 15 https://skinbase.org/sitemap.xml >"${OUT}/71-head-sitemap.txt" 2>"${OUT}/71-head-sitemap.err" || true
|
|
curl -sI --max-time 15 https://skinbase.org/robots.txt >"${OUT}/72-head-robots.txt" 2>"${OUT}/72-head-robots.err" || true
|
|
fi
|
|
|
|
# strip cookies if any slipped into HTTP dumps
|
|
for f in "${OUT}"/*.txt; do
|
|
[[ -f "$f" ]] || continue
|
|
sed -i -E 's/^(Set-Cookie:).*/\1 [REDACTED]/I' "$f" 2>/dev/null || true
|
|
done
|
|
|
|
echo "DONE output=${OUT}" | tee -a "${LOG}"
|
|
echo "${OUT}"
|