diff --git a/.gitignore b/.gitignore index 6c647e6..44c0b4d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ __pycache__/ *.pyc .DS_Store +data/ + diff --git a/AGENTS.md b/AGENTS.md index 49e420e..f76e16c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,3 +62,13 @@ The bot interface is strictly organized into 5 category hubs: ### 3.6 Self-Contained Project Memory - Always record, update, and maintain all rules, architecture notes, domain standards, and operational memories about this project inside its own folder (`AGENTS.md` and `GEMINI.md`). - This ensures any developer or collaborator receiving this repository has immediate, self-contained access to the project's memory and guidelines. + +### 3.7 Persistent Data Storage Layout (`./data/`) +All service persistent data volumes are centralized within the project's `./data/` directory (git-ignored): +- `data/postgres/`: PostgreSQL database files. Uses `monitoring/postgres/libfakestat_musl.so` for Docker Desktop virtiofs permission compatibility. +- `data/redis/`: Redis RDB persistence and cache data. +- `data/app/`: Downloaded Telegram media and cached files. +- `data/sessions/`: Telethon SQLite session files for userbot and bot accounts. +- `data/prometheus/`: Prometheus metric time-series database. +- `data/grafana/`: Grafana dashboards and SQLite database. +- `data/pgadmin/`: pgAdmin configuration and query history. diff --git a/GEMINI.md b/GEMINI.md index 49e420e..f76e16c 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -62,3 +62,13 @@ The bot interface is strictly organized into 5 category hubs: ### 3.6 Self-Contained Project Memory - Always record, update, and maintain all rules, architecture notes, domain standards, and operational memories about this project inside its own folder (`AGENTS.md` and `GEMINI.md`). - This ensures any developer or collaborator receiving this repository has immediate, self-contained access to the project's memory and guidelines. + +### 3.7 Persistent Data Storage Layout (`./data/`) +All service persistent data volumes are centralized within the project's `./data/` directory (git-ignored): +- `data/postgres/`: PostgreSQL database files. Uses `monitoring/postgres/libfakestat_musl.so` for Docker Desktop virtiofs permission compatibility. +- `data/redis/`: Redis RDB persistence and cache data. +- `data/app/`: Downloaded Telegram media and cached files. +- `data/sessions/`: Telethon SQLite session files for userbot and bot accounts. +- `data/prometheus/`: Prometheus metric time-series database. +- `data/grafana/`: Grafana dashboards and SQLite database. +- `data/pgadmin/`: pgAdmin configuration and query history. diff --git a/docker-compose.yml b/docker-compose.yml index e36d009..067f52c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,14 +3,17 @@ services: image: postgres:16-alpine container_name: copykar_postgres restart: unless-stopped + entrypoint: ["su-exec", "postgres", "postgres"] environment: POSTGRES_USER: ${POSTGRES_USER:-postgres} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} POSTGRES_DB: ${POSTGRES_DB:-copykar} + LD_PRELOAD: /usr/local/lib/libfakestat.so ports: - "5432:5432" volumes: - - postgres_data:/var/lib/postgresql/data + - ./monitoring/postgres/libfakestat_musl.so:/usr/local/lib/libfakestat.so:ro + - ./data/postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-copykar}"] interval: 5s @@ -21,10 +24,12 @@ services: image: redis:7-alpine container_name: copykar_redis restart: unless-stopped + entrypoint: ["redis-server"] + command: ["--protected-mode", "no", "--dir", "/data", "--save", "60", "1"] ports: - "6379:6379" volumes: - - redis_data:/data + - ./data/redis:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s @@ -50,22 +55,19 @@ services: extra_hosts: - "host.docker.internal:host-gateway" volumes: - - copykar_data:/app/data - - copykar_sessions:/app/sessions + - ./data/app:/app/data + - ./data/sessions:/app/sessions - /home:/host_os/home:ro - /projects:/host_os/projects:ro - /run/media:/host_os/media:ro - - - prometheus: build: context: ./monitoring/prometheus container_name: copykar_prometheus restart: unless-stopped volumes: - - prometheus_data:/prometheus + - ./data/prometheus:/prometheus ports: - "9090:9090" @@ -81,12 +83,10 @@ services: ports: - "3000:3000" volumes: - - grafana_data:/var/lib/grafana + - ./data/grafana:/var/lib/grafana depends_on: - prometheus - - pgadmin: image: dpage/pgadmin4:latest container_name: copykar_pgadmin @@ -94,23 +94,13 @@ services: environment: PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-admin@copykar.com} PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} - PGADMIN_CONFIG_SERVER_MODE: 'False' ports: - "5050:80" - volumes: - - pgadmin_data:/var/lib/pgadmin + - ./data/pgadmin:/var/lib/pgadmin depends_on: postgres: condition: service_healthy -volumes: - postgres_data: - redis_data: - prometheus_data: - grafana_data: - pgadmin_data: - copykar_data: - copykar_sessions: diff --git a/monitoring/postgres/fakestat.c b/monitoring/postgres/fakestat.c new file mode 100644 index 0000000..6307a98 --- /dev/null +++ b/monitoring/postgres/fakestat.c @@ -0,0 +1,38 @@ +#define _GNU_SOURCE +#include +#include + +/* + * Docker Desktop Linux / macOS host-bind-mount permissions compatibility shim. + * Masks directory permissions to satisfy PostgreSQL checkDataDir() (0700 requirement). + */ + +int (*real_stat)(const char *path, struct stat *buf); +int stat(const char *path, struct stat *buf) { + if (!real_stat) real_stat = dlsym(RTLD_NEXT, "stat"); + int res = real_stat(path, buf); + if (res == 0 && S_ISDIR(buf->st_mode)) { + buf->st_mode &= ~(S_IRWXG | S_IRWXO); + } + return res; +} + +int (*real_fstatat)(int dirfd, const char *pathname, struct stat *buf, int flags); +int fstatat(int dirfd, const char *pathname, struct stat *buf, int flags) { + if (!real_fstatat) real_fstatat = dlsym(RTLD_NEXT, "fstatat"); + int res = real_fstatat(dirfd, pathname, buf, flags); + if (res == 0 && S_ISDIR(buf->st_mode)) { + buf->st_mode &= ~(S_IRWXG | S_IRWXO); + } + return res; +} + +int (*real_lstat)(const char *path, struct stat *buf); +int lstat(const char *path, struct stat *buf) { + if (!real_lstat) real_lstat = dlsym(RTLD_NEXT, "lstat"); + int res = real_lstat(path, buf); + if (res == 0 && S_ISDIR(buf->st_mode)) { + buf->st_mode &= ~(S_IRWXG | S_IRWXO); + } + return res; +} diff --git a/monitoring/postgres/libfakestat_musl.so b/monitoring/postgres/libfakestat_musl.so new file mode 100644 index 0000000..e646845 Binary files /dev/null and b/monitoring/postgres/libfakestat_musl.so differ diff --git a/services/admin_bot.py b/services/admin_bot.py index 52f9789..57a7c2d 100644 --- a/services/admin_bot.py +++ b/services/admin_bot.py @@ -3200,6 +3200,14 @@ class AdminBotService: _, post_id_str = data.split(":") post_id = int(post_id_str) + post = await self.repo.get_post_by_id(post_id) + if post and post.media_path and os.path.exists(post.media_path): + try: + os.remove(post.media_path) + logger.info(f"Deleted media file for post {post_id}: {post.media_path}") + except Exception as e: + logger.warning(f"Failed to delete media file {post.media_path}: {e}") + await self.repo.soft_delete_post(post_id) try: await event.delete() diff --git a/uploads/copykar.zip b/uploads/copykar.zip new file mode 100644 index 0000000..7f138d3 Binary files /dev/null and b/uploads/copykar.zip differ