From 5cf74e67331fc544e28b9e915dc2a4e233f1b5cf Mon Sep 17 00:00:00 2001 From: Quadrubo <71718414+Quadrubo@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:37:40 +0200 Subject: [PATCH 1/2] fix: unmount device after syncing --- nixos/tracksync.nix | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nixos/tracksync.nix b/nixos/tracksync.nix index b5d28be..796d9b2 100644 --- a/nixos/tracksync.nix +++ b/nixos/tracksync.nix @@ -38,12 +38,14 @@ let # Wait for device to be mounted or udisks2 to be ready (up to 60s) MOUNT="" + SELF_MOUNTED=0 for i in $(seq 1 60); do MOUNT=$(${pkgs.util-linux}/bin/findmnt -n -o TARGET "$DEV" 2>/dev/null || true) [ -n "$MOUNT" ] && break if ${pkgs.udisks2}/bin/udisksctl info -b "$DEV" >/dev/null 2>&1; then ${pkgs.udisks2}/bin/udisksctl mount -b "$DEV" --no-user-interaction 2>&1 MOUNT=$(${pkgs.util-linux}/bin/findmnt -n -o TARGET "$DEV" 2>/dev/null || true) + SELF_MOUNTED=1 break fi sleep 1 @@ -70,6 +72,11 @@ let SKIPPED=$(echo "$SUMMARY" | ${pkgs.jq}/bin/jq -r '.skipped // 0') ERRORS=$(echo "$SUMMARY" | ${pkgs.jq}/bin/jq -r '.errors // 0') + # Only unmount if we mounted it ourselves + if [ "$SELF_MOUNTED" = 1 ]; then + ${pkgs.udisks2}/bin/udisksctl unmount -b "$DEV" --no-user-interaction 2>/dev/null || true + fi + if [ "$RC" = 0 ]; then ${pkgs.libnotify}/bin/notify-send -i emblem-ok "Tracksync" "$UPLOADED uploaded, $SKIPPED skipped" 2>/dev/null || true else From a61a11982a669f0bf2776e7ea831dc0785bf9643 Mon Sep 17 00:00:00 2001 From: Quadrubo <71718414+Quadrubo@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:39:27 +0200 Subject: [PATCH 2/2] fix: pin golangci-lint to v2.11.4 for Go 1.26 support --- .github/workflows/ci.yml | 6 +++-- server/internal/migrations/migrations.go | 4 ++-- server/internal/server/server.go | 10 ++++----- server/internal/server/server_test.go | 12 +++++----- server/internal/target/dawarich/dawarich.go | 2 +- .../internal/target/dawarich/dawarich_test.go | 6 ++--- server/main.go | 2 +- .../internal/device/columbus/columbus_test.go | 22 +++++++++---------- tracksync/internal/migrations/migrations.go | 4 ++-- tracksync/internal/sync/sync.go | 6 ++--- tracksync/internal/sync/sync_test.go | 14 ++++++------ tracksync/main.go | 6 ++--- 12 files changed, 48 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f77b5ff..569d17c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,13 +32,15 @@ jobs: run: cd server && go test ./... - name: Lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: + version: v2.11.4 working-directory: tracksync - name: Lint server - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: + version: v2.11.4 working-directory: server nix: diff --git a/server/internal/migrations/migrations.go b/server/internal/migrations/migrations.go index e95301b..c6eb33f 100644 --- a/server/internal/migrations/migrations.go +++ b/server/internal/migrations/migrations.go @@ -55,12 +55,12 @@ func Run(db *sql.DB) error { } if _, err := tx.Exec(string(data)); err != nil { - tx.Rollback() + _ = tx.Rollback() return fmt.Errorf("applying migration %s: %w", name, err) } if _, err := tx.Exec("INSERT INTO schema_migrations (version) VALUES (?)", name); err != nil { - tx.Rollback() + _ = tx.Rollback() return fmt.Errorf("recording migration %s: %w", name, err) } diff --git a/server/internal/server/server.go b/server/internal/server/server.go index a47b5d2..c3da704 100644 --- a/server/internal/server/server.go +++ b/server/internal/server/server.go @@ -62,7 +62,7 @@ func (s *Server) authenticate(r *http.Request) *config.Client { func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - fmt.Fprintln(w, "ok") + _, _ = fmt.Fprintln(w, "ok") } func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) { @@ -95,7 +95,7 @@ func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) { http.Error(w, "missing file field", http.StatusBadRequest) return } - defer file.Close() + defer func() { _ = file.Close() }() data, err := io.ReadAll(file) if err != nil { @@ -117,7 +117,7 @@ func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) { if exists { slog.Info("duplicate", "file", header.Filename, "sha256", hashHex[:12], "client", client.ID) w.WriteHeader(http.StatusOK) - fmt.Fprintln(w, "duplicate") + _, _ = fmt.Fprintln(w, "duplicate") return } @@ -150,11 +150,11 @@ func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) { if rows == 0 { slog.Info("duplicate (concurrent)", "file", header.Filename, "sha256", hashHex[:12], "client", client.ID) w.WriteHeader(http.StatusOK) - fmt.Fprintln(w, "duplicate") + _, _ = fmt.Fprintln(w, "duplicate") return } slog.Info("uploaded", "file", header.Filename, "sha256", hashHex[:12], "client", client.ID, "device", deviceID) w.WriteHeader(http.StatusCreated) - fmt.Fprintln(w, "uploaded") + _, _ = fmt.Fprintln(w, "uploaded") } diff --git a/server/internal/server/server_test.go b/server/internal/server/server_test.go index 48b8621..48a7575 100644 --- a/server/internal/server/server_test.go +++ b/server/internal/server/server_test.go @@ -39,7 +39,7 @@ func setupTestServer(t *testing.T, targetErr error) *Server { db, err := sql.Open("sqlite", ":memory:") require.NoError(t, err) require.NoError(t, InitDB(db)) - t.Cleanup(func() { db.Close() }) + t.Cleanup(func() { _ = db.Close() }) cfg := &config.Config{ Clients: []config.Client{ @@ -58,8 +58,8 @@ func uploadRequest(token, deviceID, filename string, body []byte) *http.Request var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", filename) - part.Write(body) - writer.Close() + _, _ = part.Write(body) + _ = writer.Close() req := httptest.NewRequest("POST", "/upload", &buf) req.Header.Set("Content-Type", writer.FormDataContentType()) @@ -153,7 +153,7 @@ func TestUpload_TargetNotForwarded_OnDuplicate(t *testing.T) { db, err := sql.Open("sqlite", ":memory:") require.NoError(t, err) require.NoError(t, InitDB(db)) - t.Cleanup(func() { db.Close() }) + t.Cleanup(func() { _ = db.Close() }) cfg := &config.Config{ Clients: []config.Client{{ID: "c", Token: "tok", AllowedDeviceIDs: []string{"dev"}}}, @@ -172,8 +172,8 @@ func TestUpload_MissingFileField(t *testing.T) { var buf bytes.Buffer writer := multipart.NewWriter(&buf) - writer.WriteField("other", "value") - writer.Close() + _ = writer.WriteField("other", "value") + _ = writer.Close() req := httptest.NewRequest("POST", "/upload", &buf) req.Header.Set("Content-Type", writer.FormDataContentType()) diff --git a/server/internal/target/dawarich/dawarich.go b/server/internal/target/dawarich/dawarich.go index bda317a..4039573 100644 --- a/server/internal/target/dawarich/dawarich.go +++ b/server/internal/target/dawarich/dawarich.go @@ -67,7 +67,7 @@ func (d *Dawarich) Send(filename string, data []byte) error { if err != nil { return fmt.Errorf("sending to dawarich: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode < 200 || resp.StatusCode >= 300 { body, _ := io.ReadAll(resp.Body) diff --git a/server/internal/target/dawarich/dawarich_test.go b/server/internal/target/dawarich/dawarich_test.go index 9568218..0a3241f 100644 --- a/server/internal/target/dawarich/dawarich_test.go +++ b/server/internal/target/dawarich/dawarich_test.go @@ -50,14 +50,14 @@ func TestSend_PostsToImportsEndpoint(t *testing.T) { cfg: target.Config{URL: ts.URL, APIKey: "k"}, client: &http.Client{Timeout: 5 * time.Second}, } - d.Send("f.gpx", []byte("data")) + _ = d.Send("f.gpx", []byte("data")) assert.Equal(t, "/api/v1/imports", gotPath) } func TestSend_ErrorOnNon2xx(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("something broke")) + _, _ = w.Write([]byte("something broke")) })) defer ts.Close() @@ -80,7 +80,7 @@ func TestReadAPIKey_Inline(t *testing.T) { func TestReadAPIKey_File(t *testing.T) { path := filepath.Join(t.TempDir(), "api-key") - os.WriteFile(path, []byte(" file-key\n"), 0600) + require.NoError(t, os.WriteFile(path, []byte(" file-key\n"), 0600)) d := &Dawarich{cfg: target.Config{APIKeyFile: path}} key, err := d.readAPIKey() diff --git a/server/main.go b/server/main.go index e901064..4c72009 100644 --- a/server/main.go +++ b/server/main.go @@ -56,7 +56,7 @@ func main() { slog.Error("failed to open database", "error", err) os.Exit(1) } - defer db.Close() + defer func() { _ = db.Close() }() if err := server.InitDB(db); err != nil { slog.Error("failed to init database", "error", err) diff --git a/tracksync/internal/device/columbus/columbus_test.go b/tracksync/internal/device/columbus/columbus_test.go index 7a4d3f4..a3e747f 100644 --- a/tracksync/internal/device/columbus/columbus_test.go +++ b/tracksync/internal/device/columbus/columbus_test.go @@ -11,8 +11,8 @@ import ( func TestFindFiles_GPX(t *testing.T) { dir := t.TempDir() - os.WriteFile(filepath.Join(dir, "track1.gpx"), []byte(""), 0644) - os.WriteFile(filepath.Join(dir, "track2.gpx"), []byte(""), 0644) + require.NoError(t, os.WriteFile(filepath.Join(dir, "track1.gpx"), []byte(""), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "track2.gpx"), []byte(""), 0644)) files, err := (&P10Pro{}).FindFiles(dir) require.NoError(t, err) @@ -21,9 +21,9 @@ func TestFindFiles_GPX(t *testing.T) { func TestFindFiles_CaseInsensitive(t *testing.T) { dir := t.TempDir() - os.WriteFile(filepath.Join(dir, "lower.gpx"), []byte(""), 0644) - os.WriteFile(filepath.Join(dir, "UPPER.GPX"), []byte(""), 0644) - os.WriteFile(filepath.Join(dir, "Mixed.Gpx"), []byte(""), 0644) + require.NoError(t, os.WriteFile(filepath.Join(dir, "lower.gpx"), []byte(""), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "UPPER.GPX"), []byte(""), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "Mixed.Gpx"), []byte(""), 0644)) files, err := (&P10Pro{}).FindFiles(dir) require.NoError(t, err) @@ -32,9 +32,9 @@ func TestFindFiles_CaseInsensitive(t *testing.T) { func TestFindFiles_SkipsNonGPX(t *testing.T) { dir := t.TempDir() - os.WriteFile(filepath.Join(dir, "track.gpx"), []byte(""), 0644) - os.WriteFile(filepath.Join(dir, "photo.jpg"), []byte("jpeg"), 0644) - os.WriteFile(filepath.Join(dir, "notes.txt"), []byte("text"), 0644) + require.NoError(t, os.WriteFile(filepath.Join(dir, "track.gpx"), []byte(""), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "photo.jpg"), []byte("jpeg"), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "notes.txt"), []byte("text"), 0644)) files, err := (&P10Pro{}).FindFiles(dir) require.NoError(t, err) @@ -45,8 +45,8 @@ func TestFindFiles_Recursive(t *testing.T) { dir := t.TempDir() sub := filepath.Join(dir, "subdir", "nested") require.NoError(t, os.MkdirAll(sub, 0755)) - os.WriteFile(filepath.Join(dir, "root.gpx"), []byte(""), 0644) - os.WriteFile(filepath.Join(sub, "nested.gpx"), []byte(""), 0644) + require.NoError(t, os.WriteFile(filepath.Join(dir, "root.gpx"), []byte(""), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(sub, "nested.gpx"), []byte(""), 0644)) files, err := (&P10Pro{}).FindFiles(dir) require.NoError(t, err) @@ -68,7 +68,7 @@ func TestFindFiles_EmptyDir(t *testing.T) { func TestFindFiles_AbsolutePaths(t *testing.T) { dir := t.TempDir() - os.WriteFile(filepath.Join(dir, "track.gpx"), []byte(""), 0644) + require.NoError(t, os.WriteFile(filepath.Join(dir, "track.gpx"), []byte(""), 0644)) files, err := (&P10Pro{}).FindFiles(dir) require.NoError(t, err) diff --git a/tracksync/internal/migrations/migrations.go b/tracksync/internal/migrations/migrations.go index e95301b..c6eb33f 100644 --- a/tracksync/internal/migrations/migrations.go +++ b/tracksync/internal/migrations/migrations.go @@ -55,12 +55,12 @@ func Run(db *sql.DB) error { } if _, err := tx.Exec(string(data)); err != nil { - tx.Rollback() + _ = tx.Rollback() return fmt.Errorf("applying migration %s: %w", name, err) } if _, err := tx.Exec("INSERT INTO schema_migrations (version) VALUES (?)", name); err != nil { - tx.Rollback() + _ = tx.Rollback() return fmt.Errorf("recording migration %s: %w", name, err) } diff --git a/tracksync/internal/sync/sync.go b/tracksync/internal/sync/sync.go index ab24d64..f93f609 100644 --- a/tracksync/internal/sync/sync.go +++ b/tracksync/internal/sync/sync.go @@ -26,11 +26,11 @@ func OpenStateDB(path string) (*sql.DB, error) { return nil, err } if _, err := db.Exec("PRAGMA journal_mode=wal"); err != nil { - db.Close() + _ = db.Close() return nil, err } if err := migrations.Run(db); err != nil { - db.Close() + _ = db.Close() return nil, err } return db, nil @@ -98,7 +98,7 @@ func Upload(client *http.Client, serverURL, token, deviceID, hostname, filename if err != nil { return "", fmt.Errorf("sending request: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() body, _ := io.ReadAll(resp.Body) status := strings.TrimSpace(string(body)) diff --git a/tracksync/internal/sync/sync_test.go b/tracksync/internal/sync/sync_test.go index fdb260a..96ad911 100644 --- a/tracksync/internal/sync/sync_test.go +++ b/tracksync/internal/sync/sync_test.go @@ -19,7 +19,7 @@ func openTestDB(t *testing.T) *sql.DB { t.Helper() db, err := OpenStateDB(filepath.Join(t.TempDir(), "state.db")) require.NoError(t, err) - t.Cleanup(func() { db.Close() }) + t.Cleanup(func() { _ = db.Close() }) return db } @@ -45,14 +45,14 @@ func TestRecordUpload_Idempotent(t *testing.T) { require.NoError(t, RecordUpload(db, "hash1", "a.gpx", "dev"), "INSERT OR IGNORE should not error") var count int - db.QueryRow("SELECT COUNT(*) FROM uploaded WHERE sha256 = ?", "hash1").Scan(&count) + require.NoError(t, db.QueryRow("SELECT COUNT(*) FROM uploaded WHERE sha256 = ?", "hash1").Scan(&count)) assert.Equal(t, 1, count) } func TestClearUploads(t *testing.T) { db := openTestDB(t) for i := 0; i < 5; i++ { - RecordUpload(db, fmt.Sprintf("hash-%d", i), fmt.Sprintf("file-%d.gpx", i), "dev") + require.NoError(t, RecordUpload(db, fmt.Sprintf("hash-%d", i), fmt.Sprintf("file-%d.gpx", i), "dev")) } n, err := ClearUploads(db) @@ -101,7 +101,7 @@ func TestUpload_Created(t *testing.T) { assert.Equal(t, "dev-1", r.Header.Get("X-Device-ID")) assert.Equal(t, "myhost", r.Header.Get("X-Client-Host")) w.WriteHeader(http.StatusCreated) - fmt.Fprintln(w, "uploaded") + _, _ = fmt.Fprintln(w, "uploaded") })) defer ts.Close() @@ -114,7 +114,7 @@ func TestUpload_Created(t *testing.T) { func TestUpload_Duplicate(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - fmt.Fprintln(w, "duplicate") + _, _ = fmt.Fprintln(w, "duplicate") })) defer ts.Close() @@ -127,7 +127,7 @@ func TestUpload_Duplicate(t *testing.T) { func TestUpload_ServerError(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintln(w, "internal error") + _, _ = fmt.Fprintln(w, "internal error") })) defer ts.Close() @@ -140,7 +140,7 @@ func TestOpenStateDB_CreatesDirectory(t *testing.T) { path := filepath.Join(t.TempDir(), "nested", "dir", "state.db") db, err := OpenStateDB(path) require.NoError(t, err) - db.Close() + _ = db.Close() _, err = os.Stat(path) assert.NoError(t, err, "database file should exist") diff --git a/tracksync/main.go b/tracksync/main.go index 7e9927e..f573234 100644 --- a/tracksync/main.go +++ b/tracksync/main.go @@ -49,7 +49,7 @@ func main() { slog.Error("failed to open state db", "error", err) os.Exit(1) } - defer db.Close() + defer func() { _ = db.Close() }() n, err := sync.ClearUploads(db) if err != nil { slog.Error("failed to clear uploads", "error", err) @@ -85,7 +85,7 @@ func main() { slog.Error("failed to open state db", "error", err) os.Exit(1) } - defer db.Close() + defer func() { _ = db.Close() }() dev, ok := device.Get(*deviceType) if !ok { @@ -171,7 +171,7 @@ func main() { // writeSummary outputs the summary as JSON to stdout for machine consumption. func writeSummary(s Summary) { - json.NewEncoder(os.Stdout).Encode(s) + _ = json.NewEncoder(os.Stdout).Encode(s) } func defaultStateDB() string {