diff --git a/README.md b/README.md index 3b33a72..3cae063 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,11 @@ make help # Показать все команды Можно переопределить путь через `-localdb` или переменную окружения `QFS_DB_PATH`. +### Локальный config.yaml + +По умолчанию `qfs` ищет `config.yaml` в той же user-state папке, где лежит `qfs.db` (а не рядом с бинарником). +Можно переопределить путь через `-config` или `QFS_CONFIG_PATH`. + ## Docker ```bash @@ -258,6 +263,7 @@ CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/qfs ./cmd/qfs | `QF_SERVER_PORT` | Порт сервера | 8080 | | `QFS_DB_PATH` | Полный путь к локальной SQLite БД | OS-specific user state dir | | `QFS_STATE_DIR` | Каталог state (если `QFS_DB_PATH` не задан) | OS-specific user state dir | +| `QFS_CONFIG_PATH` | Полный путь к `config.yaml` | OS-specific user state dir | ## Интеграция с существующей БД diff --git a/RELEASE_v0.2.6.md b/RELEASE_v0.2.6.md deleted file mode 100644 index 8779b2c..0000000 --- a/RELEASE_v0.2.6.md +++ /dev/null @@ -1,60 +0,0 @@ -## Release v0.2.6 - Build & Release Improvements - -Minor release focused on developer experience and release automation. - -### Changes - -**Build System** -- 🎯 Renamed binary from `quoteforge` to `qfs` (shorter, easier to type) -- 🏷️ Added `-version` flag to display build version -- 📦 Added Makefile with build targets for all platforms -- 🚀 Added automated release script for multi-platform binaries - -**New Commands** -```bash -make build-release # Optimized build with version info -make build-all # Build for Linux + macOS (Intel/ARM) -make release # Create release packages with checksums -./bin/qfs -version # Show version -``` - -**Release Artifacts** -- Linux AMD64 -- macOS Intel (AMD64) -- macOS Apple Silicon (ARM64) -- Windows AMD64 -- SHA256 checksums - -### Installation - -Download the appropriate binary for your platform: -```bash -# Linux -wget https://git.mchus.pro/mchus/QuoteForge/releases/download/v0.2.6/qfs-v0.2.6-linux-amd64.tar.gz -tar -xzf qfs-v0.2.6-linux-amd64.tar.gz -chmod +x qfs-linux-amd64 -./qfs-linux-amd64 - -# macOS Intel -curl -L -O https://git.mchus.pro/mchus/QuoteForge/releases/download/v0.2.6/qfs-v0.2.6-darwin-amd64.tar.gz -tar -xzf qfs-v0.2.6-darwin-amd64.tar.gz -chmod +x qfs-darwin-amd64 -./qfs-darwin-amd64 - -# macOS Apple Silicon -curl -L -O https://git.mchus.pro/mchus/QuoteForge/releases/download/v0.2.6/qfs-v0.2.6-darwin-arm64.tar.gz -tar -xzf qfs-v0.2.6-darwin-arm64.tar.gz -chmod +x qfs-darwin-arm64 -./qfs-darwin-arm64 - -# Windows -curl -L -O https://git.mchus.pro/mchus/QuoteForge/releases/download/v0.2.6/qfs-v0.2.6-windows-amd64.zip -unzip qfs-v0.2.6-windows-amd64.zip -qfs-windows-amd64.exe -``` - -### Breaking Changes -None - fully backward compatible with v0.2.5 - -### Full Changelog -https://git.mchus.pro/mchus/QuoteForge/compare/v0.2.5...v0.2.6 diff --git a/RELEASE_v0.2.7.md b/RELEASE_v0.2.7.md deleted file mode 100644 index 98f31e8..0000000 --- a/RELEASE_v0.2.7.md +++ /dev/null @@ -1,57 +0,0 @@ -## Release v0.2.7 - Windows Support & Localhost Binding - -Bug fix release improving Windows compatibility and default network binding. - -### Changes - -**Windows Support** -- ✅ Fixed template loading errors on Windows -- ✅ All file paths now use `filepath.Join` for cross-platform compatibility -- ✅ Binary now works correctly on Windows without path errors - -**Localhost Binding** -- ✅ Server now binds to `127.0.0.1` by default (instead of `0.0.0.0`) -- ✅ Browser always opens to `http://127.0.0.1:8080` -- ✅ Setup mode properly opens browser automatically -- ✅ More secure default - only accessible from local machine - -> **Note:** To bind to all network interfaces (for network access), set `host: "0.0.0.0"` in config.yaml - -### Installation - -Download the appropriate binary for your platform: - -```bash -# Linux -wget https://git.mchus.pro/mchus/QuoteForge/releases/download/v0.2.7/qfs-v0.2.7-linux-amd64.tar.gz -tar -xzf qfs-v0.2.7-linux-amd64.tar.gz -chmod +x qfs-linux-amd64 -./qfs-linux-amd64 - -# macOS Intel -curl -L -O https://git.mchus.pro/mchus/QuoteForge/releases/download/v0.2.7/qfs-v0.2.7-darwin-amd64.tar.gz -tar -xzf qfs-v0.2.7-darwin-amd64.tar.gz -chmod +x qfs-darwin-amd64 -./qfs-darwin-amd64 - -# macOS Apple Silicon -curl -L -O https://git.mchus.pro/mchus/QuoteForge/releases/download/v0.2.7/qfs-v0.2.7-darwin-arm64.tar.gz -tar -xzf qfs-v0.2.7-darwin-arm64.tar.gz -chmod +x qfs-darwin-arm64 -./qfs-darwin-arm64 - -# Windows -# Download and extract: https://git.mchus.pro/mchus/QuoteForge/releases/download/v0.2.7/qfs-v0.2.7-windows-amd64.zip -# Run: qfs-windows-amd64.exe -``` - -### Breaking Changes -None - fully backward compatible with v0.2.6 - -### Upgrade Notes -- If you use config.yaml with `host: "0.0.0.0"`, the app will respect that setting -- Default behavior now binds only to localhost for security -- Windows users no longer need workarounds for path errors - -### Full Changelog -https://git.mchus.pro/mchus/QuoteForge/compare/v0.2.6...v0.2.7 diff --git a/cmd/qfs/main.go b/cmd/qfs/main.go index 90b6622..c1f7d3f 100644 --- a/cmd/qfs/main.go +++ b/cmd/qfs/main.go @@ -41,7 +41,7 @@ import ( var Version = "dev" func main() { - configPath := flag.String("config", "config.yaml", "path to config file (optional, for server settings)") + configPath := flag.String("config", "", "path to config file (default: user state dir or QFS_CONFIG_PATH)") localDBPath := flag.String("localdb", "", "path to local SQLite database (default: user state dir or QFS_DB_PATH)") migrate := flag.Bool("migrate", false, "run database migrations") version := flag.Bool("version", false, "show version information") @@ -56,12 +56,28 @@ func main() { exePath, _ := os.Executable() slog.Info("starting qfs", "version", Version, "executable", exePath) + resolvedConfigPath, err := appstate.ResolveConfigPath(*configPath) + if err != nil { + slog.Error("failed to resolve config path", "error", err) + os.Exit(1) + } + resolvedLocalDBPath, err := appstate.ResolveDBPath(*localDBPath) if err != nil { slog.Error("failed to resolve local database path", "error", err) os.Exit(1) } + // Migrate legacy project-local config path to the user state directory when using defaults. + if *configPath == "" && os.Getenv("QFS_CONFIG_PATH") == "" { + migratedFrom, migrateErr := appstate.MigrateLegacyFile(resolvedConfigPath, []string{"config.yaml"}) + if migrateErr != nil { + slog.Warn("failed to migrate legacy config file", "error", migrateErr) + } else if migratedFrom != "" { + slog.Info("migrated legacy config file", "from", migratedFrom, "to", resolvedConfigPath) + } + } + // Migrate legacy project-local DB path to the user state directory when using defaults. if *localDBPath == "" && os.Getenv("QFS_DB_PATH") == "" { legacyPaths := []string{ @@ -91,18 +107,19 @@ func main() { } // Load config for server settings (optional) - cfg, err := config.Load(*configPath) + cfg, err := config.Load(resolvedConfigPath) if err != nil { if errors.Is(err, fs.ErrNotExist) { // Use defaults if config file doesn't exist - slog.Info("config file not found, using defaults", "path", *configPath) + slog.Info("config file not found, using defaults", "path", resolvedConfigPath) cfg = &config.Config{} } else { - slog.Error("failed to load config", "path", *configPath, "error", err) + slog.Error("failed to load config", "path", resolvedConfigPath, "error", err) os.Exit(1) } } setConfigDefaults(cfg) + slog.Info("resolved runtime files", "config_path", resolvedConfigPath, "localdb_path", resolvedLocalDBPath) setupLogger(cfg.Logging) diff --git a/internal/appstate/path.go b/internal/appstate/path.go index a7102e6..2468ee9 100644 --- a/internal/appstate/path.go +++ b/internal/appstate/path.go @@ -11,8 +11,10 @@ import ( const ( appDirName = "QuoteForge" defaultDB = "qfs.db" + defaultCfg = "config.yaml" envDBPath = "QFS_DB_PATH" envStateDir = "QFS_STATE_DIR" + envCfgPath = "QFS_CONFIG_PATH" ) // ResolveDBPath returns the local SQLite path using priority: @@ -34,6 +36,25 @@ func ResolveDBPath(explicitPath string) (string, error) { return filepath.Join(dir, defaultDB), nil } +// ResolveConfigPath returns the config path using priority: +// explicit CLI path > QFS_CONFIG_PATH > OS-specific user state directory. +func ResolveConfigPath(explicitPath string) (string, error) { + if explicitPath != "" { + return filepath.Clean(explicitPath), nil + } + + if fromEnv := os.Getenv(envCfgPath); fromEnv != "" { + return filepath.Clean(fromEnv), nil + } + + dir, err := defaultStateDir() + if err != nil { + return "", err + } + + return filepath.Join(dir, defaultCfg), nil +} + // MigrateLegacyDB copies an existing legacy DB (and optional SQLite sidecars) // to targetPath if targetPath does not already exist. // Returns source path if migration happened. @@ -73,6 +94,38 @@ func MigrateLegacyDB(targetPath string, legacyPaths []string) (string, error) { return "", nil } +// MigrateLegacyFile copies an existing legacy file to targetPath +// if targetPath does not already exist. +func MigrateLegacyFile(targetPath string, legacyPaths []string) (string, error) { + if targetPath == "" { + return "", nil + } + + if exists(targetPath) { + return "", nil + } + + if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { + return "", fmt.Errorf("creating target directory: %w", err) + } + + for _, src := range legacyPaths { + if src == "" { + continue + } + src = filepath.Clean(src) + if src == targetPath || !exists(src) { + continue + } + if err := copyFile(src, targetPath); err != nil { + return "", fmt.Errorf("migrating legacy file from %s: %w", src, err) + } + return src, nil + } + + return "", nil +} + func defaultStateDir() (string, error) { if override := os.Getenv(envStateDir); override != "" { return filepath.Clean(override), nil