From 356df3fc46bbf9368e479b123d92f449df871c02 Mon Sep 17 00:00:00 2001 From: Yigid BALABAN Date: Tue, 14 Oct 2025 22:35:11 +0300 Subject: [PATCH] this should do it --- cmd/deployer/main.go | 8 +++++- docker-compose.prod.yml | 40 ++++++++++++++++++++++++++++ internal/config/config.go | 6 +++++ internal/db/db.go | 55 +++++++++++++++++++++++++++++++++++---- 4 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 docker-compose.prod.yml diff --git a/cmd/deployer/main.go b/cmd/deployer/main.go index 1c14bcf..7c4e42c 100644 --- a/cmd/deployer/main.go +++ b/cmd/deployer/main.go @@ -34,7 +34,13 @@ func run() error { logger.Info("deployer service starting", "log_level", cfg.LogLevel) - database, err := db.OpenDB(cfg.DBPath) + dbConfig := &db.DBConfig{ + JournalMode: cfg.DBJournalMode, + Synchronous: cfg.DBSynchronous, + BusyTimeout: cfg.DBBusyTimeout, + } + + database, err := db.OpenDB(cfg.DBPath, dbConfig) if err != nil { return fmt.Errorf("failed to open database: %w", err) } diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..0f24413 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,40 @@ +# Production Docker Compose configuration +# This configuration is optimized for production environments +# and addresses common SQLite issues in containerized deployments + +version: "3.8" + +services: + server: + container_name: tingz-server + env_file: .env + build: + context: . + args: + UID: ${UID} + GID: ${GID} + environment: + # Database configuration for production + DB_JOURNAL_MODE: "DELETE" # Use DELETE mode instead of WAL for better volume mount compatibility + DB_SYNCHRONOUS: "FULL" # Full synchronous mode for data integrity + DB_BUSY_TIMEOUT: "30000" # 30 second timeout for busy database + + # Optional: Override other settings for production + # LOG_LEVEL: "warn" # Reduce log verbosity in production + # MAX_UPLOAD_SIZE: "52428800" # 50MB limit for production + volumes: + - ./data:/data + - ./docs:/var/www/docs + - ./deploys:/var/www/deploys + ports: + - "${HOST}:${PORT}:8080" + user: "${UID}:${GID}" + restart: unless-stopped + + # Health check to ensure service is running + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/api/v1/status"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s diff --git a/internal/config/config.go b/internal/config/config.go index 8f9d739..e3d2c89 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,6 +11,9 @@ type Config struct { DeployRoot string ReleaseRoot string DBPath string + DBJournalMode string + DBSynchronous string + DBBusyTimeout int MaxUploadSize int64 LogLevel string ReleasesToKeep int @@ -25,6 +28,9 @@ func LoadConfig() (*Config, error) { DeployRoot: getEnvOrDefault("DEPLOY_ROOT", "/var/www/docs"), ReleaseRoot: getEnvOrDefault("RELEASE_ROOT", "/var/www/deploys"), DBPath: getEnvOrDefault("DB_PATH", "/data/deployer.db"), + DBJournalMode: getEnvOrDefault("DB_JOURNAL_MODE", "DELETE"), + DBSynchronous: getEnvOrDefault("DB_SYNCHRONOUS", "FULL"), + DBBusyTimeout: getEnvAsInt("DB_BUSY_TIMEOUT", 10000), MaxUploadSize: getEnvAsInt64("MAX_UPLOAD_SIZE", 104857600), LogLevel: getEnvOrDefault("LOG_LEVEL", "info"), ReleasesToKeep: getEnvAsInt("RELEASES_TO_KEEP", 5), diff --git a/internal/db/db.go b/internal/db/db.go index 8553625..e297b3a 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -5,16 +5,29 @@ import ( "fmt" "os" "path/filepath" + "syscall" _ "modernc.org/sqlite" ) -func OpenDB(path string) (*sql.DB, error) { +type DBConfig struct { + JournalMode string + Synchronous string + BusyTimeout int +} + +func OpenDB(path string, config *DBConfig) (*sql.DB, error) { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0755); err != nil { return nil, fmt.Errorf("failed to create database directory: %w", err) } + // Check if database file exists and get file info for diagnostics + var fileInfo os.FileInfo + if info, err := os.Stat(path); err == nil { + fileInfo = info + } + db, err := sql.Open("sqlite", path) if err != nil { return nil, fmt.Errorf("failed to open database: %w", err) @@ -22,14 +35,46 @@ func OpenDB(path string) (*sql.DB, error) { if err := db.Ping(); err != nil { db.Close() - return nil, fmt.Errorf("failed to ping database: %w", err) + + // Enhanced error diagnostics + diag := fmt.Sprintf("database path: %s", path) + if fileInfo != nil { + diag += fmt.Sprintf(", file size: %d bytes, mode: %s", fileInfo.Size(), fileInfo.Mode()) + } + + // Check directory permissions + if dirInfo, err := os.Stat(dir); err == nil { + diag += fmt.Sprintf(", dir mode: %s", dirInfo.Mode()) + } + + // Check if it's a file system issue + if pathErr, ok := err.(*os.PathError); ok { + if pathErr.Err == syscall.ENOSPC { + diag += ", filesystem full" + } else if pathErr.Err == syscall.EACCES { + diag += ", permission denied" + } + } + + return nil, fmt.Errorf("failed to ping database: %w (%s)", err, diag) + } + + // Set default config if not provided + if config == nil { + config = &DBConfig{ + JournalMode: "DELETE", + Synchronous: "FULL", + BusyTimeout: 10000, + } } pragmas := []string{ - "PRAGMA journal_mode=WAL", + fmt.Sprintf("PRAGMA journal_mode=%s", config.JournalMode), "PRAGMA foreign_keys=ON", - "PRAGMA synchronous=NORMAL", - "PRAGMA busy_timeout=5000", + fmt.Sprintf("PRAGMA synchronous=%s", config.Synchronous), + fmt.Sprintf("PRAGMA busy_timeout=%d", config.BusyTimeout), + "PRAGMA temp_store=MEMORY", + "PRAGMA mmap_size=268435456", } for _, pragma := range pragmas {