mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-08 12:36:44 +03:00
feat: add event cleanup scripts and Docker Compose setup for automated maintenance
This commit is contained in:
26
README.md
26
README.md
@@ -445,6 +445,19 @@ Try the following steps:
|
|||||||
> -v gitea-mirror-data:/app/data \
|
> -v gitea-mirror-data:/app/data \
|
||||||
> ghcr.io/arunavo4/gitea-mirror:latest
|
> ghcr.io/arunavo4/gitea-mirror:latest
|
||||||
> ```
|
> ```
|
||||||
|
>
|
||||||
|
> For homelab/self-hosted setups, you can use the provided Docker Compose file with automatic event cleanup:
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> # Clone the repository
|
||||||
|
> git clone https://github.com/arunavo4/gitea-mirror.git
|
||||||
|
> cd gitea-mirror
|
||||||
|
>
|
||||||
|
> # Start the application with Docker Compose
|
||||||
|
> docker-compose -f docker-compose.homelab.yml up -d
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> This setup includes a cron job that runs daily to clean up old events and prevent the database from growing too large.
|
||||||
|
|
||||||
|
|
||||||
#### Database Maintenance
|
#### Database Maintenance
|
||||||
@@ -461,6 +474,19 @@ Try the following steps:
|
|||||||
>
|
>
|
||||||
> # Reset user accounts (for development)
|
> # Reset user accounts (for development)
|
||||||
> bun run reset-users
|
> bun run reset-users
|
||||||
|
>
|
||||||
|
> # Clean up old events (keeps last 7 days by default)
|
||||||
|
> bun run cleanup-events
|
||||||
|
>
|
||||||
|
> # Clean up old events with custom retention period (e.g., 30 days)
|
||||||
|
> bun run cleanup-events 30
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> For automated maintenance, consider setting up a cron job to run the cleanup script periodically:
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> # Add this to your crontab (runs daily at 2 AM)
|
||||||
|
> 0 2 * * * cd /path/to/gitea-mirror && bun run cleanup-events
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
4
crontab
Normal file
4
crontab
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Run event cleanup daily at 2 AM
|
||||||
|
0 2 * * * cd /app && bun run cleanup-events 30 >> /app/data/cleanup-events.log 2>&1
|
||||||
|
|
||||||
|
# Empty line at the end is required for cron to work properly
|
||||||
38
docker-compose.homelab.yml
Normal file
38
docker-compose.homelab.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
gitea-mirror:
|
||||||
|
image: ghcr.io/arunavo4/gitea-mirror:latest
|
||||||
|
container_name: gitea-mirror
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "4321:4321"
|
||||||
|
volumes:
|
||||||
|
- gitea-mirror-data:/app/data
|
||||||
|
# Mount the crontab file
|
||||||
|
- ./crontab:/etc/cron.d/gitea-mirror-cron
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- HOST=0.0.0.0
|
||||||
|
- PORT=4321
|
||||||
|
- DATABASE_URL=sqlite://data/gitea-mirror.db
|
||||||
|
- DELAY=${DELAY:-3600}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:4321/health"]
|
||||||
|
interval: 1m
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
# Install cron in the container and set up the cron job
|
||||||
|
command: >
|
||||||
|
sh -c "
|
||||||
|
apt-get update && apt-get install -y cron curl &&
|
||||||
|
chmod 0644 /etc/cron.d/gitea-mirror-cron &&
|
||||||
|
crontab /etc/cron.d/gitea-mirror-cron &&
|
||||||
|
service cron start &&
|
||||||
|
bun dist/server/entry.mjs
|
||||||
|
"
|
||||||
|
|
||||||
|
# Define named volumes for database persistence
|
||||||
|
volumes:
|
||||||
|
gitea-mirror-data: # Database volume
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
"reset-users": "bun scripts/manage-db.ts reset-users",
|
"reset-users": "bun scripts/manage-db.ts reset-users",
|
||||||
"migrate-db": "bun scripts/migrate-db.ts",
|
"migrate-db": "bun scripts/migrate-db.ts",
|
||||||
"cleanup-redis": "bun scripts/cleanup-redis.ts",
|
"cleanup-redis": "bun scripts/cleanup-redis.ts",
|
||||||
|
"cleanup-events": "bun scripts/cleanup-events.ts",
|
||||||
"preview": "bunx --bun astro preview",
|
"preview": "bunx --bun astro preview",
|
||||||
"start": "bun dist/server/entry.mjs",
|
"start": "bun dist/server/entry.mjs",
|
||||||
"start:fresh": "bun run cleanup-db && bun run manage-db init && bun dist/server/entry.mjs",
|
"start:fresh": "bun run cleanup-db && bun run manage-db init && bun dist/server/entry.mjs",
|
||||||
|
|||||||
43
scripts/cleanup-events.ts
Normal file
43
scripts/cleanup-events.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
/**
|
||||||
|
* Script to clean up old events from the database
|
||||||
|
* This script should be run periodically (e.g., daily) to prevent the events table from growing too large
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* bun scripts/cleanup-events.ts [days]
|
||||||
|
*
|
||||||
|
* Where [days] is the number of days to keep events (default: 7)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { cleanupOldEvents } from "../src/lib/events";
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const daysToKeep = args.length > 0 ? parseInt(args[0], 10) : 7;
|
||||||
|
|
||||||
|
if (isNaN(daysToKeep) || daysToKeep < 1) {
|
||||||
|
console.error("Error: Days to keep must be a positive number");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runCleanup() {
|
||||||
|
try {
|
||||||
|
console.log(`Starting event cleanup (retention: ${daysToKeep} days)...`);
|
||||||
|
|
||||||
|
// Call the cleanupOldEvents function from the events module
|
||||||
|
const result = await cleanupOldEvents(daysToKeep);
|
||||||
|
|
||||||
|
console.log(`Cleanup summary:`);
|
||||||
|
console.log(`- Read events deleted: ${result.readEventsDeleted}`);
|
||||||
|
console.log(`- Unread events deleted: ${result.unreadEventsDeleted}`);
|
||||||
|
console.log(`- Total events deleted: ${result.readEventsDeleted + result.unreadEventsDeleted}`);
|
||||||
|
|
||||||
|
console.log("Event cleanup completed successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error running event cleanup:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the cleanup
|
||||||
|
runCleanup();
|
||||||
29
scripts/make-events-old.ts
Normal file
29
scripts/make-events-old.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
/**
|
||||||
|
* Script to make events appear older for testing cleanup
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { db, events } from "../src/lib/db";
|
||||||
|
|
||||||
|
async function makeEventsOld() {
|
||||||
|
try {
|
||||||
|
console.log("Making events appear older...");
|
||||||
|
|
||||||
|
// Calculate a timestamp from 2 days ago
|
||||||
|
const oldDate = new Date();
|
||||||
|
oldDate.setDate(oldDate.getDate() - 2);
|
||||||
|
|
||||||
|
// Update all events to have an older timestamp
|
||||||
|
const result = await db
|
||||||
|
.update(events)
|
||||||
|
.set({ createdAt: oldDate });
|
||||||
|
|
||||||
|
console.log(`Updated ${result.changes || 0} events to appear older`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating event timestamps:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the function
|
||||||
|
makeEventsOld();
|
||||||
27
scripts/mark-events-read.ts
Normal file
27
scripts/mark-events-read.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
/**
|
||||||
|
* Script to mark all events as read
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { db, events } from "../src/lib/db";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
async function markEventsAsRead() {
|
||||||
|
try {
|
||||||
|
console.log("Marking all events as read...");
|
||||||
|
|
||||||
|
// Update all events to mark them as read
|
||||||
|
const result = await db
|
||||||
|
.update(events)
|
||||||
|
.set({ read: true })
|
||||||
|
.where(eq(events.read, false));
|
||||||
|
|
||||||
|
console.log(`Marked ${result.changes || 0} events as read`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error marking events as read:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the function
|
||||||
|
markEventsAsRead();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { db, events } from "./db";
|
import { db, events } from "./db";
|
||||||
import { eq, and, gt } from "drizzle-orm";
|
import { eq, and, gt, lt } from "drizzle-orm";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publishes an event to a specific channel for a user
|
* Publishes an event to a specific channel for a user
|
||||||
@@ -106,25 +106,56 @@ export async function getNewEvents({
|
|||||||
/**
|
/**
|
||||||
* Cleans up old events to prevent the database from growing too large
|
* Cleans up old events to prevent the database from growing too large
|
||||||
* Should be called periodically (e.g., daily via a cron job)
|
* Should be called periodically (e.g., daily via a cron job)
|
||||||
|
*
|
||||||
|
* @param maxAgeInDays Number of days to keep events (default: 7)
|
||||||
|
* @param cleanupUnreadAfterDays Number of days after which to clean up unread events (default: 2x maxAgeInDays)
|
||||||
|
* @returns Object containing the number of read and unread events deleted
|
||||||
*/
|
*/
|
||||||
export async function cleanupOldEvents(maxAgeInDays: number = 7): Promise<number> {
|
export async function cleanupOldEvents(
|
||||||
|
maxAgeInDays: number = 7,
|
||||||
|
cleanupUnreadAfterDays?: number
|
||||||
|
): Promise<{ readEventsDeleted: number; unreadEventsDeleted: number }> {
|
||||||
try {
|
try {
|
||||||
|
console.log(`Cleaning up events older than ${maxAgeInDays} days...`);
|
||||||
|
|
||||||
|
// Calculate the cutoff date for read events
|
||||||
const cutoffDate = new Date();
|
const cutoffDate = new Date();
|
||||||
cutoffDate.setDate(cutoffDate.getDate() - maxAgeInDays);
|
cutoffDate.setDate(cutoffDate.getDate() - maxAgeInDays);
|
||||||
|
|
||||||
// Delete events older than the cutoff date
|
// Delete read events older than the cutoff date
|
||||||
const result = await db
|
const readResult = await db
|
||||||
.delete(events)
|
.delete(events)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(events.read, true),
|
eq(events.read, true),
|
||||||
gt(cutoffDate, events.createdAt)
|
lt(events.createdAt, cutoffDate)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return result.changes || 0;
|
const readEventsDeleted = readResult.changes || 0;
|
||||||
|
console.log(`Deleted ${readEventsDeleted} read events`);
|
||||||
|
|
||||||
|
// Calculate the cutoff date for unread events (default to 2x the retention period)
|
||||||
|
const unreadCutoffDate = new Date();
|
||||||
|
const unreadMaxAge = cleanupUnreadAfterDays || (maxAgeInDays * 2);
|
||||||
|
unreadCutoffDate.setDate(unreadCutoffDate.getDate() - unreadMaxAge);
|
||||||
|
|
||||||
|
// Delete unread events that are significantly older
|
||||||
|
const unreadResult = await db
|
||||||
|
.delete(events)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(events.read, false),
|
||||||
|
lt(events.createdAt, unreadCutoffDate)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const unreadEventsDeleted = unreadResult.changes || 0;
|
||||||
|
console.log(`Deleted ${unreadEventsDeleted} unread events`);
|
||||||
|
|
||||||
|
return { readEventsDeleted, unreadEventsDeleted };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error cleaning up old events:", error);
|
console.error("Error cleaning up old events:", error);
|
||||||
return 0;
|
return { readEventsDeleted: 0, unreadEventsDeleted: 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const GET: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
const channel = `mirror-status:${userId}`;
|
const channel = `mirror-status:${userId}`;
|
||||||
let isClosed = false;
|
let isClosed = false;
|
||||||
const POLL_INTERVAL = 2000; // Poll every 2 seconds
|
const POLL_INTERVAL = 5000; // Poll every 5 seconds (reduced from 2 seconds for low-traffic usage)
|
||||||
|
|
||||||
const stream = new ReadableStream({
|
const stream = new ReadableStream({
|
||||||
start(controller) {
|
start(controller) {
|
||||||
|
|||||||
Reference in New Issue
Block a user