feat(skills): add coruna-lab-migrate and coruna-lab-cleanup skills

- coruna-lab-migrate: server migration SOP (code/db/file transfer,
  supervisor setup, DNS cutover, verification, common pitfalls)
- coruna-lab-cleanup: disk cleanup skill with disk_cleanup.sh and
  cleanup_scanned_photos.php scripts

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
root
2026-10-03 16:52:09 +00:00
parent e654f65cf9
commit 00e440a0b4
4 changed files with 664 additions and 0 deletions
@@ -0,0 +1,96 @@
<?php
/**
* Clean up photos that were scanned (scan_status=1, "已扫-空") but have no mnemonic findings.
*
* These photos were scanned for mnemonic phrases but nothing was found,
* so they are safe to delete: file blobs, photo_reads rows, and photos rows.
*
* Usage:
* php cleanup_scanned_photos.php [project-path] [--execute]
*
* Without --execute: dry-run (counts only, deletes nothing)
* With --execute: deletes files and DB rows in batches of 500
*
* Requires: Laravel project with photos, photo_reads, mnemonic_findings tables.
*/
require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$kernel->bootstrap();
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
$execute = in_array('--execute', $argv ?? []);
$dryRun = !$execute;
echo ($dryRun ? 'DRY-RUN' : 'EXECUTE') . " clean scan_status=1 photos without mnemonic findings\n";
$disk = Storage::disk('local');
$batchSize = 500;
$totalSeen = 0;
$totalFiles = 0;
$totalRows = 0;
$totalReads = 0;
$lastId = 0;
$batchNum = 0;
while (true) {
$photos = DB::table('photos')
->select(['id', 'device_id', 'path'])
->where('scan_status', 1)
->where('id', '>', $lastId)
->whereNotIn('id', function ($q) {
$q->select('photo_id')->from('mnemonic_findings')->whereNotNull('photo_id');
})
->orderBy('id')
->limit($batchSize)
->get();
if ($photos->isEmpty()) {
break;
}
$batchNum++;
$ids = [];
foreach ($photos as $photo) {
$ids[] = $photo->id;
$totalSeen++;
if (!$dryRun) {
$path = trim((string) ($photo->path ?? ''));
if ($path !== '' && $disk->exists($path)) {
$disk->delete($path);
$totalFiles++;
}
}
}
if (!$dryRun && !empty($ids)) {
$deletedReads = DB::table('photo_reads')->whereIn('photo_id', $ids)->delete();
$totalReads += $deletedReads;
DB::table('photos')->whereIn('id', $ids)->delete();
$totalRows += count($ids);
}
$lastId = (int) $photos->last()->id;
if ($batchNum % 20 === 0 || $photos->count() < $batchSize) {
echo sprintf(
" batch=%d seen=%d %s reads=%d\n",
$batchNum,
$totalSeen,
$dryRun ? '(dry-run)' : "deleted_files=$totalFiles deleted_rows=$totalRows",
$totalReads
);
}
}
echo "\n=== Done ===\n";
echo "matched_photos: $totalSeen\n";
if (!$dryRun) {
echo "deleted_files: $totalFiles\n";
echo "deleted_photo_reads: $totalReads\n";
echo "deleted_photos_rows: $totalRows\n";
} else {
echo "dry-run: pass --execute to delete\n";
}
+136
View File
@@ -0,0 +1,136 @@
#!/bin/bash
#
# coruna-lab disk cleanup — full pipeline
#
# Usage: sudo bash disk_cleanup.sh [project_root] [--execute]
#
# Without --execute: dry-run (shows what would be cleaned, deletes nothing)
# With --execute: performs all cleanup steps
#
# Env vars (override defaults):
# LOG_RETENTION_DAYS=7 public/log retention (days)
# DS_RESULTS_RETENTION_DAYS=1 ds-results retention (days)
# DS_CHUNK_RETENTION_DAYS=1 ds-chunks retention (days)
# INBOX_RETENTION_DAYS=3 inbox retention (days)
# CLEAN_NGINX_LOGS=1 truncate large nginx logs (1=yes, 0=no)
# CLEAN_BACKUPS=1 delete /www/backup contents (1=yes, 0=no)
#
set -euo pipefail
PROJECT="${1:-/www/wwwroot/coruna-lab}"
EXECUTE_FLAG="${2:-}"
EXECUTE=0
[ "$EXECUTE_FLAG" = "--execute" ] && EXECUTE=1
# Retention settings (can be overridden by env vars)
LOG_DAYS="${LOG_RETENTION_DAYS:-7}"
DS_RESULTS_DAYS="${DS_RESULTS_RETENTION_DAYS:-1}"
DS_CHUNK_DAYS="${DS_CHUNK_RETENTION_DAYS:-1}"
INBOX_DAYS="${INBOX_RETENTION_DAYS:-3}"
CLEAN_NGINX_LOGS="${CLEAN_NGINX_LOGS:-1}"
CLEAN_BACKUPS="${CLEAN_BACKUPS:-1}"
STORAGE_C2="$PROJECT/storage/app/private/c2"
PUBLIC_LOG="$PROJECT/public/log"
DELETE_CMD=""
if [ $EXECUTE -eq 1 ]; then
DELETE_CMD="-delete"
echo "=== EXECUTE MODE — files will be deleted ==="
else
echo "=== DRY-RUN MODE — no files will be deleted ==="
fi
echo ""
df -h / 2>/dev/null | tail -1
echo ""
# ─── 1. Old application logs (public/log) ───────────────────────────
echo ">>> 1. Cleaning public/log (>$LOG_DAYS days)"
find "$PUBLIC_LOG" -type f -name "*.log" -mtime +$LOG_DAYS -print $DELETE_CMD 2>/dev/null | wc -l | xargs -I{} echo " matched files: {}"
rm -f "$PUBLIC_LOG"/*.tar.gz 2>/dev/null && echo " removed tar.gz archives" || true
# ─── 2. Scanned photos without mnemonic findings ────────────────────
echo ">>> 2. Cleaning scan_status=1 photos (no mnemonic findings)"
if [ -f "$PROJECT/cleanup_scanned_photos.php" ]; then
SCRIPT="$PROJECT/cleanup_scanned_photos.php"
elif [ -f "$(dirname "$0")/cleanup_scanned_photos.php" ]; then
SCRIPT="$(dirname "$0")/cleanup_scanned_photos.php"
cp "$SCRIPT" "$PROJECT/cleanup_scanned_photos.php"
else
echo " ERROR: cleanup_scanned_photos.php not found, skipping"
SCRIPT=""
fi
if [ -n "$SCRIPT" ]; then
if [ $EXECUTE -eq 1 ]; then
(cd "$PROJECT" && php cleanup_scanned_photos.php --execute 2>&1 | tail -5)
rm -f "$PROJECT/cleanup_scanned_photos.php"
else
(cd "$PROJECT" && php cleanup_scanned_photos.php 2>&1 | tail -3)
fi
fi
# ─── 3. Broken images (file missing, DB row exists) ─────────────────
echo ">>> 3. Pruning broken-image DB rows (file missing on disk)"
if [ $EXECUTE -eq 1 ]; then
(cd "$PROJECT" && php artisan coruna:prune-missing-photo-files --execute 2>&1 | tail -3)
else
(cd "$PROJECT" && php artisan coruna:prune-missing-photo-files 2>&1 | tail -3)
fi
# ─── 4. Photo preview caches ────────────────────────────────────────
echo ">>> 4. Clearing photo preview caches"
du -sh "$STORAGE_C2/photo-previews/" 2>/dev/null || true
if [ $EXECUTE -eq 1 ]; then
rm -rf "$STORAGE_C2/photo-previews/"* 2>/dev/null
echo " cleared"
fi
# ─── 5. DS results (keep N days) ────────────────────────────────────
echo ">>> 5. Cleaning ds-results (>$DS_RESULTS_DAYS days)"
find "$STORAGE_C2/ds-results/" -type f -mtime +$DS_RESULTS_DAYS -print $DELETE_CMD 2>/dev/null | wc -l | xargs -I{} echo " deleted files: {}"
if [ $EXECUTE -eq 1 ]; then
find "$STORAGE_C2/ds-results/" -type d -empty -delete 2>/dev/null
fi
# ─── 6. DS chunks (keep N days) ────────────────────────────────────
echo ">>> 6. Cleaning ds-chunks (>$DS_CHUNK_DAYS days)"
find "$STORAGE_C2/ds-chunks/" -type f -mtime +$DS_CHUNK_DAYS -print $DELETE_CMD 2>/dev/null | wc -l | xargs -I{} echo " deleted files: {}"
if [ $EXECUTE -eq 1 ]; then
find "$STORAGE_C2/ds-chunks/" -type d -empty -delete 2>/dev/null
fi
# ─── 7. Inbox (keep N days) ─────────────────────────────────────────
echo ">>> 7. Cleaning inbox (>$INBOX_DAYS days)"
find "$STORAGE_C2/inbox/" -type f -mtime +$INBOX_DAYS -print $DELETE_CMD 2>/dev/null | wc -l | xargs -I{} echo " deleted files: {}"
if [ $EXECUTE -eq 1 ]; then
find "$STORAGE_C2/inbox/" -type d -empty -delete 2>/dev/null
fi
# ─── 8. Nginx logs (truncate large active logs) ─────────────────────
if [ "$CLEAN_NGINX_LOGS" = "1" ] && [ $EXECUTE -eq 1 ]; then
echo ">>> 8. Truncating nginx logs (/www/wwwlogs)"
for f in /www/wwwlogs/*.log; do
sz=$(stat -c%s "$f" 2>/dev/null || echo 0)
if [ "$sz" -gt 104857600 ]; then
truncate -s 0 "$f"
echo " truncated $(basename $f) ($(numfmt --to=iec $sz))"
fi
done
else
echo ">>> 8. Nginx logs: skipped (CLEAN_NGINX_LOGS=$CLEAN_NGINX_LOGS or dry-run)"
fi
# ─── 9. Backups ────────────────────────────────────────────────────
if [ "$CLEAN_BACKUPS" = "1" ] && [ $EXECUTE -eq 1 ]; then
echo ">>> 9. Cleaning /www/backup"
rm -rf /www/backup/backup_restore/* /www/backup/database/* /www/backup/file_history/* /www/backup/panel/* 2>/dev/null || true
rm -f /www/backup/*.Bak /www/backup/*.bak /www/backup/nginxBak 2>/dev/null || true
echo " cleaned"
else
echo ">>> 9. Backups: skipped (CLEAN_BACKUPS=$CLEAN_BACKUPS or dry-run)"
fi
echo ""
echo "=== Result ==="
df -h / 2>/dev/null | tail -1