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 {