Skip to content

Repository files navigation

Tracksync

Syncs track files from GPS loggers to self-hosted location tracking services like Dawarich. Plug in your device, tracks get uploaded automatically.

Features

  • Format conversion - automatically converts between device and target formats to preserve all data
  • Deduplication - files are only uploaded once
  • Multi-client - sync from multiple machines to the same server
  • Extensible device support
  • Extensible target support
  • Per-client auth tokens

NixOS module

  • Automatic sync on USB plug-in
  • Desktop notifications on sync progress
  • Mounts device automatically via udisks2
  • Integrates with sops-nix for secret management

How it works

Tracksync consists of three parts:

  • Client - a CLI tool that reads track files from a mounted GPS device and uploads them to the server.
  • Server - receives uploads, deduplicates them, converts them to the best format for the target, and forwards files to a configured target like Dawarich. Runs as a Docker container.
  • NixOS module - automates the client by detecting USB plug-in via udev, mounting the device, running the sync, and sending desktop notifications.

Install the client

Go

go install github.com/Quadrubo/tracksync/tracksync@latest

Nix

nix run github:Quadrubo/tracksync

Configure the NixOS Module

You can use a NixOS module to automate the complete process.

Run the server

Docker Compose

services:
tracksync-server:
image: ghcr.io/quadrubo/tracksync/server:latestports:
- "8080:8080"environment:
- ACCOUNT__0__DEVICE_ID=my-columbus
- ACCOUNT__0__TARGET_URL=http://dawarich:3000
- ACCOUNT__0__API_KEY=your-api-key
- CLIENT__0__ID=my-laptop
- CLIENT__0__TOKEN=your-client-token
- CLIENT__0__ALLOWED_DEVICES=my-columbusvolumes:
- data:/app/datavolumes:
data:

Server configuration

All configuration is done via environment variables.

VariableDefaultRequiredDescription
PORT8080NoServer port
STATE_DBdata/state.dbNoSQLite database path
TARGET_TIMEOUT30sNoHTTP timeout for target requests
ACCOUNT__N__DEVICE_IDYesDevice identifier
ACCOUNT__N__TARGET_TYPEdawarichNoTarget type
ACCOUNT__N__TARGET_URLYesTarget instance URL
ACCOUNT__N__API_KEYYes if not API_KEY_FILEAPI key (inline)
ACCOUNT__N__API_KEY_FILEYes if not API_KEYAPI key (file path)
ACCOUNT__N__MARKERSNoComma-separated marker:functionality rules assigning behavior to point markers (see Marker functionalities)
ACCOUNT__N__SPLIT_MARKER_POSITIONstartNoFor the split functionality: where the marked point goes, start of the new track or end of the previous one
ACCOUNT__N__SPLIT_MODEtracksNoFor the split functionality: tracks (all tracks in one file) or files (one upload per track)
TARGET__DAWARICH__EMIT_TRACKER_IDfalseNoTag each track with a stable tracker_id so Dawarich keeps split tracks separate (see Keeping split tracks separate in Dawarich)
CLIENT__N__IDYesClient identifier
CLIENT__N__TOKENYes if not TOKEN_FILEAuth token (inline)
CLIENT__N__TOKEN_FILEYes if not TOKENAuth token (file path)
CLIENT__N__ALLOWED_DEVICESYesComma-separated allowed device IDs

* One of the inline or file variant is required.

Replace N with 0, 1, 2, etc. for multiple accounts/clients.

Client usage

tracksync \
--server-url http://localhost:8080 \
--token your-client-token \
--device-type columbus-p10-pro \
--device-id my-columbus \
--mount-point /mnt/gps

By default, the client picks up all file formats the device supports. Use --device-format to restrict to a specific one (e.g. --device-format columbus-csv).

FlagDefaultRequiredDescription
--server-urlYesTracksync server URL
--tokenYes if not token-fileAuth token inline
--token-fileYes if not tokenAuth token from file
--device-typeYesGPS device type
--device-formatNoRestrict to a specific device format
--device-idYesDevice identifier
--mount-pointYesDevice mount point
--state-db~/.local/share/tracksync/state.dbNoClient state database
--log-formattextNoLog format: text, json
--timeout30sNoHTTP request timeout
--clearfalseNoClear upload history

API

EndpointMethodAuthDescription
/healthGETNoLiveness check
/uploadPOSTBearer tokenUpload a track file (multipart, field file)

The /upload endpoint requires X-Device-ID and X-Source-Format headers.

NixOS module

Add tracksync as a flake input:

# flake.nixinputs.tracksync.url="github:Quadrubo/tracksync";

Import the module and configure:

{imports=[inputs.tracksync.nixosModules.default];services.tracksync={enable=true;user="your-username";serverUrl="https://tracksync.example.com";tokenFile="/run/secrets/tracksync-token";devices=[{deviceId="my-device";deviceType="columbus-p10-pro";# deviceFormat = "columbus-csv"; # optional, omit to find all supported formatsusbVendorId="xxxx";# from lsusbusbProductId="xxxx";# from lsusbdiskById="usb-xxxx-part1";# from ls /dev/disk/by-id/}];};}

The sync service runs outside of a desktop session, so it needs a polkit rule in order to automatically mount the gps logger device. You can find the device serial with lsblk --nodeps -o name,serial while the device is connected.

{security.polkit.extraConfig='' polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.udisks2.filesystem-mount" && subject.user == "your-username" && action.lookup("drive.serial") == "your-device-serial") { return polkit.Result.YES; } }); '';}

When the device is plugged in, a systemd service automatically mounts it, syncs all track files, and sends a desktop notification with the result.

Format conversion

The server automatically converts between file formats to preserve the most data. Each device declares the formats it can produce, and each target declares the formats it accepts. The server parses the source file into a universal track model, then serializes to the target format that preserves the most fields.

For example, when a Columbus P-10 Pro is configured to output CSV (which includes speed and heading), the server converts to GeoJSON before forwarding to Dawarich, since GeoJSON can represent these fields while GPX 1.1 cannot.

Supported formats

FormatTypeSpeedHeadingElevationSatellitesDOP
gpx_1.1Parse + SerializeNoNoYesYesYes
columbus-csvParseYesYesYesNoNo
geojsonSerializeYesYesYesYesYes

Marker functionalities

Points can carry a marker, a source-format annotation such as a manually placed POI or waypoint. You can assign a functionality to each marker so that tracksync acts on it. This works at the universal-track level and is format-agnostic: every parser maps its format's native markers onto a point marker, and functionalities operate on those.

Configure rules with ACCOUNT__N__MARKERS as comma-separated marker:functionality pairs:

ACCOUNT__0__MARKERS=C:split
# multiple markers, each with its own functionality:
ACCOUNT__0__MARKERS=C:split,D:split

Which marker values are available depends on the source format:

FormatMarkers
columbus-csvTAG column values other than T: C (function-key POI), D (second POI), G (automatic wake-up point, usually leave unmapped)

Available functionalities

FunctionalityEffect
splitStart a new track at the marked point. Useful for separating legs of a journey. For example pressing a logger's function key when boarding and leaving a bus so the walking and bus legs become distinct tracks.

For the split functionality, ACCOUNT__N__SPLIT_MARKER_POSITION controls which side of the split the marked point lands on: start (default) makes it the first point of the new track, end keeps it as the last point of the previous one.

ACCOUNT__N__SPLIT_MODE controls how the split tracks are delivered:

  • tracks (default): all tracks are written into a single file.
  • files: each track is uploaded as its own file, with suffixed filenames (track-1.geojson, track-2.geojson, …). Splitting into files does not by itself keep tracks separate in Dawarich, which re-segments the points by time gap; see Keeping split tracks separate in Dawarich.

Keeping split tracks separate in Dawarich

Dawarich rebuilds tracks from the uploaded points by splitting on time gaps, so split legs that are close in time get merged back together. Set TARGET__DAWARICH__EMIT_TRACKER_ID=true to tag each track with a stable tracker_id; Dawarich groups points into tracks by that id and keeps the split legs separate. This works in either SPLIT_MODE, and regardless of the uploaded file format: the Dawarich target always forwards points as GeoJSON, so the tracker_id is what keeps the legs apart.

Supported targets

TypeServiceAccepted formats
dawarichDawarichgpx_1.1, geojson

Adding a new target requires implementing the Target interface in server/internal/target/.

Supported devices

TypeDeviceSupported formats
columbus-p10-proColumbus P-10 Progpx_1.1, columbus-csv

Adding a new device type requires implementing the Device interface in tracksync/internal/device/.

Development

Nix

This repository provides a flake.nix with a devshell for development.

Enter the repository and run direnv allow or use nix develop to start the devshell.

Other OS

Make sure the following is installed:

  • Go
  • Just

General

# Run server locally
just run-server
# Run client
just run-client --server-url http://localhost:8080 --token test-token \
--device-type columbus-p10-pro --device-id my-columbus --mount-point /mnt/gps
# Update nix vendor hash after changing Go dependencies
just update-vendor-hash

About

Automatically syncs track files from GPS loggers to self-hosted location tracking services

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - Quadrubo/tracksync: Automatically syncs track files from GPS loggers to self-hosted location tracking services · GitHub
Skip to content

Repository files navigation

Tracksync

Syncs track files from GPS loggers to self-hosted location tracking services like Dawarich. Plug in your device, tracks get uploaded automatically.

Features

  • Format conversion - automatically converts between device and target formats to preserve all data
  • Deduplication - files are only uploaded once
  • Multi-client - sync from multiple machines to the same server
  • Extensible device support
  • Extensible target support
  • Per-client auth tokens

NixOS module

  • Automatic sync on USB plug-in
  • Desktop notifications on sync progress
  • Mounts device automatically via udisks2
  • Integrates with sops-nix for secret management

How it works

Tracksync consists of three parts:

  • Client - a CLI tool that reads track files from a mounted GPS device and uploads them to the server.
  • Server - receives uploads, deduplicates them, converts them to the best format for the target, and forwards files to a configured target like Dawarich. Runs as a Docker container.
  • NixOS module - automates the client by detecting USB plug-in via udev, mounting the device, running the sync, and sending desktop notifications.

Install the client

Go

go install github.com/Quadrubo/tracksync/tracksync@latest

Nix

nix run github:Quadrubo/tracksync

Configure the NixOS Module

You can use a NixOS module to automate the complete process.

Run the server

Docker Compose

services:
tracksync-server:
image: ghcr.io/quadrubo/tracksync/server:latestports:
- "8080:8080"environment:
- ACCOUNT__0__DEVICE_ID=my-columbus
- ACCOUNT__0__TARGET_URL=http://dawarich:3000
- ACCOUNT__0__API_KEY=your-api-key
- CLIENT__0__ID=my-laptop
- CLIENT__0__TOKEN=your-client-token
- CLIENT__0__ALLOWED_DEVICES=my-columbusvolumes:
- data:/app/datavolumes:
data:

Server configuration

All configuration is done via environment variables.

VariableDefaultRequiredDescription
PORT8080NoServer port
STATE_DBdata/state.dbNoSQLite database path
TARGET_TIMEOUT30sNoHTTP timeout for target requests
ACCOUNT__N__DEVICE_IDYesDevice identifier
ACCOUNT__N__TARGET_TYPEdawarichNoTarget type
ACCOUNT__N__TARGET_URLYesTarget instance URL
ACCOUNT__N__API_KEYYes if not API_KEY_FILEAPI key (inline)
ACCOUNT__N__API_KEY_FILEYes if not API_KEYAPI key (file path)
ACCOUNT__N__MARKERSNoComma-separated marker:functionality rules assigning behavior to point markers (see Marker functionalities)
ACCOUNT__N__SPLIT_MARKER_POSITIONstartNoFor the split functionality: where the marked point goes, start of the new track or end of the previous one
ACCOUNT__N__SPLIT_MODEtracksNoFor the split functionality: tracks (all tracks in one file) or files (one upload per track)
TARGET__DAWARICH__EMIT_TRACKER_IDfalseNoTag each track with a stable tracker_id so Dawarich keeps split tracks separate (see Keeping split tracks separate in Dawarich)
CLIENT__N__IDYesClient identifier
CLIENT__N__TOKENYes if not TOKEN_FILEAuth token (inline)
CLIENT__N__TOKEN_FILEYes if not TOKENAuth token (file path)
CLIENT__N__ALLOWED_DEVICESYesComma-separated allowed device IDs

* One of the inline or file variant is required.

Replace N with 0, 1, 2, etc. for multiple accounts/clients.

Client usage

tracksync \
--server-url http://localhost:8080 \
--token your-client-token \
--device-type columbus-p10-pro \
--device-id my-columbus \
--mount-point /mnt/gps

By default, the client picks up all file formats the device supports. Use --device-format to restrict to a specific one (e.g. --device-format columbus-csv).

FlagDefaultRequiredDescription
--server-urlYesTracksync server URL
--tokenYes if not token-fileAuth token inline
--token-fileYes if not tokenAuth token from file
--device-typeYesGPS device type
--device-formatNoRestrict to a specific device format
--device-idYesDevice identifier
--mount-pointYesDevice mount point
--state-db~/.local/share/tracksync/state.dbNoClient state database
--log-formattextNoLog format: text, json
--timeout30sNoHTTP request timeout
--clearfalseNoClear upload history

API

EndpointMethodAuthDescription
/healthGETNoLiveness check
/uploadPOSTBearer tokenUpload a track file (multipart, field file)

The /upload endpoint requires X-Device-ID and X-Source-Format headers.

NixOS module

Add tracksync as a flake input:

# flake.nixinputs.tracksync.url="github:Quadrubo/tracksync";

Import the module and configure:

{imports=[inputs.tracksync.nixosModules.default];services.tracksync={enable=true;user="your-username";serverUrl="https://tracksync.example.com";tokenFile="/run/secrets/tracksync-token";devices=[{deviceId="my-device";deviceType="columbus-p10-pro";# deviceFormat = "columbus-csv"; # optional, omit to find all supported formatsusbVendorId="xxxx";# from lsusbusbProductId="xxxx";# from lsusbdiskById="usb-xxxx-part1";# from ls /dev/disk/by-id/}];};}

The sync service runs outside of a desktop session, so it needs a polkit rule in order to automatically mount the gps logger device. You can find the device serial with lsblk --nodeps -o name,serial while the device is connected.

{security.polkit.extraConfig='' polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.udisks2.filesystem-mount" && subject.user == "your-username" && action.lookup("drive.serial") == "your-device-serial") { return polkit.Result.YES; } }); '';}

When the device is plugged in, a systemd service automatically mounts it, syncs all track files, and sends a desktop notification with the result.

Format conversion

The server automatically converts between file formats to preserve the most data. Each device declares the formats it can produce, and each target declares the formats it accepts. The server parses the source file into a universal track model, then serializes to the target format that preserves the most fields.

For example, when a Columbus P-10 Pro is configured to output CSV (which includes speed and heading), the server converts to GeoJSON before forwarding to Dawarich, since GeoJSON can represent these fields while GPX 1.1 cannot.

Supported formats

FormatTypeSpeedHeadingElevationSatellitesDOP
gpx_1.1Parse + SerializeNoNoYesYesYes
columbus-csvParseYesYesYesNoNo
geojsonSerializeYesYesYesYesYes

Marker functionalities

Points can carry a marker, a source-format annotation such as a manually placed POI or waypoint. You can assign a functionality to each marker so that tracksync acts on it. This works at the universal-track level and is format-agnostic: every parser maps its format's native markers onto a point marker, and functionalities operate on those.

Configure rules with ACCOUNT__N__MARKERS as comma-separated marker:functionality pairs:

ACCOUNT__0__MARKERS=C:split
# multiple markers, each with its own functionality:
ACCOUNT__0__MARKERS=C:split,D:split

Which marker values are available depends on the source format:

FormatMarkers
columbus-csvTAG column values other than T: C (function-key POI), D (second POI), G (automatic wake-up point, usually leave unmapped)

Available functionalities

FunctionalityEffect
splitStart a new track at the marked point. Useful for separating legs of a journey. For example pressing a logger's function key when boarding and leaving a bus so the walking and bus legs become distinct tracks.

For the split functionality, ACCOUNT__N__SPLIT_MARKER_POSITION controls which side of the split the marked point lands on: start (default) makes it the first point of the new track, end keeps it as the last point of the previous one.

ACCOUNT__N__SPLIT_MODE controls how the split tracks are delivered:

  • tracks (default): all tracks are written into a single file.
  • files: each track is uploaded as its own file, with suffixed filenames (track-1.geojson, track-2.geojson, …). Splitting into files does not by itself keep tracks separate in Dawarich, which re-segments the points by time gap; see Keeping split tracks separate in Dawarich.

Keeping split tracks separate in Dawarich

Dawarich rebuilds tracks from the uploaded points by splitting on time gaps, so split legs that are close in time get merged back together. Set TARGET__DAWARICH__EMIT_TRACKER_ID=true to tag each track with a stable tracker_id; Dawarich groups points into tracks by that id and keeps the split legs separate. This works in either SPLIT_MODE, and regardless of the uploaded file format: the Dawarich target always forwards points as GeoJSON, so the tracker_id is what keeps the legs apart.

Supported targets

TypeServiceAccepted formats
dawarichDawarichgpx_1.1, geojson

Adding a new target requires implementing the Target interface in server/internal/target/.

Supported devices

TypeDeviceSupported formats
columbus-p10-proColumbus P-10 Progpx_1.1, columbus-csv

Adding a new device type requires implementing the Device interface in tracksync/internal/device/.

Development

Nix

This repository provides a flake.nix with a devshell for development.

Enter the repository and run direnv allow or use nix develop to start the devshell.

Other OS

Make sure the following is installed:

  • Go
  • Just

General

# Run server locally
just run-server
# Run client
just run-client --server-url http://localhost:8080 --token test-token \
--device-type columbus-p10-pro --device-id my-columbus --mount-point /mnt/gps
# Update nix vendor hash after changing Go dependencies
just update-vendor-hash

About

Automatically syncs track files from GPS loggers to self-hosted location tracking services

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - Quadrubo/tracksync: Automatically syncs track files from GPS loggers to self-hosted location tracking services · GitHub
Skip to content

Repository files navigation

Tracksync

Syncs track files from GPS loggers to self-hosted location tracking services like Dawarich. Plug in your device, tracks get uploaded automatically.

Features

  • Format conversion - automatically converts between device and target formats to preserve all data
  • Deduplication - files are only uploaded once
  • Multi-client - sync from multiple machines to the same server
  • Extensible device support
  • Extensible target support
  • Per-client auth tokens

NixOS module

  • Automatic sync on USB plug-in
  • Desktop notifications on sync progress
  • Mounts device automatically via udisks2
  • Integrates with sops-nix for secret management

How it works

Tracksync consists of three parts:

  • Client - a CLI tool that reads track files from a mounted GPS device and uploads them to the server.
  • Server - receives uploads, deduplicates them, converts them to the best format for the target, and forwards files to a configured target like Dawarich. Runs as a Docker container.
  • NixOS module - automates the client by detecting USB plug-in via udev, mounting the device, running the sync, and sending desktop notifications.

Install the client

Go

go install github.com/Quadrubo/tracksync/tracksync@latest

Nix

nix run github:Quadrubo/tracksync

Configure the NixOS Module

You can use a NixOS module to automate the complete process.

Run the server

Docker Compose

services:
tracksync-server:
image: ghcr.io/quadrubo/tracksync/server:latestports:
- "8080:8080"environment:
- ACCOUNT__0__DEVICE_ID=my-columbus
- ACCOUNT__0__TARGET_URL=http://dawarich:3000
- ACCOUNT__0__API_KEY=your-api-key
- CLIENT__0__ID=my-laptop
- CLIENT__0__TOKEN=your-client-token
- CLIENT__0__ALLOWED_DEVICES=my-columbusvolumes:
- data:/app/datavolumes:
data:

Server configuration

All configuration is done via environment variables.

VariableDefaultRequiredDescription
PORT8080NoServer port
STATE_DBdata/state.dbNoSQLite database path
TARGET_TIMEOUT30sNoHTTP timeout for target requests
ACCOUNT__N__DEVICE_IDYesDevice identifier
ACCOUNT__N__TARGET_TYPEdawarichNoTarget type
ACCOUNT__N__TARGET_URLYesTarget instance URL
ACCOUNT__N__API_KEYYes if not API_KEY_FILEAPI key (inline)
ACCOUNT__N__API_KEY_FILEYes if not API_KEYAPI key (file path)
ACCOUNT__N__MARKERSNoComma-separated marker:functionality rules assigning behavior to point markers (see Marker functionalities)
ACCOUNT__N__SPLIT_MARKER_POSITIONstartNoFor the split functionality: where the marked point goes, start of the new track or end of the previous one
ACCOUNT__N__SPLIT_MODEtracksNoFor the split functionality: tracks (all tracks in one file) or files (one upload per track)
TARGET__DAWARICH__EMIT_TRACKER_IDfalseNoTag each track with a stable tracker_id so Dawarich keeps split tracks separate (see Keeping split tracks separate in Dawarich)
CLIENT__N__IDYesClient identifier
CLIENT__N__TOKENYes if not TOKEN_FILEAuth token (inline)
CLIENT__N__TOKEN_FILEYes if not TOKENAuth token (file path)
CLIENT__N__ALLOWED_DEVICESYesComma-separated allowed device IDs

* One of the inline or file variant is required.

Replace N with 0, 1, 2, etc. for multiple accounts/clients.

Client usage

tracksync \
--server-url http://localhost:8080 \
--token your-client-token \
--device-type columbus-p10-pro \
--device-id my-columbus \
--mount-point /mnt/gps

By default, the client picks up all file formats the device supports. Use --device-format to restrict to a specific one (e.g. --device-format columbus-csv).

FlagDefaultRequiredDescription
--server-urlYesTracksync server URL
--tokenYes if not token-fileAuth token inline
--token-fileYes if not tokenAuth token from file
--device-typeYesGPS device type
--device-formatNoRestrict to a specific device format
--device-idYesDevice identifier
--mount-pointYesDevice mount point
--state-db~/.local/share/tracksync/state.dbNoClient state database
--log-formattextNoLog format: text, json
--timeout30sNoHTTP request timeout
--clearfalseNoClear upload history

API

EndpointMethodAuthDescription
/healthGETNoLiveness check
/uploadPOSTBearer tokenUpload a track file (multipart, field file)

The /upload endpoint requires X-Device-ID and X-Source-Format headers.

NixOS module

Add tracksync as a flake input:

# flake.nixinputs.tracksync.url="github:Quadrubo/tracksync";

Import the module and configure:

{imports=[inputs.tracksync.nixosModules.default];services.tracksync={enable=true;user="your-username";serverUrl="https://tracksync.example.com";tokenFile="/run/secrets/tracksync-token";devices=[{deviceId="my-device";deviceType="columbus-p10-pro";# deviceFormat = "columbus-csv"; # optional, omit to find all supported formatsusbVendorId="xxxx";# from lsusbusbProductId="xxxx";# from lsusbdiskById="usb-xxxx-part1";# from ls /dev/disk/by-id/}];};}

The sync service runs outside of a desktop session, so it needs a polkit rule in order to automatically mount the gps logger device. You can find the device serial with lsblk --nodeps -o name,serial while the device is connected.

{security.polkit.extraConfig='' polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.udisks2.filesystem-mount" && subject.user == "your-username" && action.lookup("drive.serial") == "your-device-serial") { return polkit.Result.YES; } }); '';}

When the device is plugged in, a systemd service automatically mounts it, syncs all track files, and sends a desktop notification with the result.

Format conversion

The server automatically converts between file formats to preserve the most data. Each device declares the formats it can produce, and each target declares the formats it accepts. The server parses the source file into a universal track model, then serializes to the target format that preserves the most fields.

For example, when a Columbus P-10 Pro is configured to output CSV (which includes speed and heading), the server converts to GeoJSON before forwarding to Dawarich, since GeoJSON can represent these fields while GPX 1.1 cannot.

Supported formats

FormatTypeSpeedHeadingElevationSatellitesDOP
gpx_1.1Parse + SerializeNoNoYesYesYes
columbus-csvParseYesYesYesNoNo
geojsonSerializeYesYesYesYesYes

Marker functionalities

Points can carry a marker, a source-format annotation such as a manually placed POI or waypoint. You can assign a functionality to each marker so that tracksync acts on it. This works at the universal-track level and is format-agnostic: every parser maps its format's native markers onto a point marker, and functionalities operate on those.

Configure rules with ACCOUNT__N__MARKERS as comma-separated marker:functionality pairs:

ACCOUNT__0__MARKERS=C:split
# multiple markers, each with its own functionality:
ACCOUNT__0__MARKERS=C:split,D:split

Which marker values are available depends on the source format:

FormatMarkers
columbus-csvTAG column values other than T: C (function-key POI), D (second POI), G (automatic wake-up point, usually leave unmapped)

Available functionalities

FunctionalityEffect
splitStart a new track at the marked point. Useful for separating legs of a journey. For example pressing a logger's function key when boarding and leaving a bus so the walking and bus legs become distinct tracks.

For the split functionality, ACCOUNT__N__SPLIT_MARKER_POSITION controls which side of the split the marked point lands on: start (default) makes it the first point of the new track, end keeps it as the last point of the previous one.

ACCOUNT__N__SPLIT_MODE controls how the split tracks are delivered:

  • tracks (default): all tracks are written into a single file.
  • files: each track is uploaded as its own file, with suffixed filenames (track-1.geojson, track-2.geojson, …). Splitting into files does not by itself keep tracks separate in Dawarich, which re-segments the points by time gap; see Keeping split tracks separate in Dawarich.

Keeping split tracks separate in Dawarich

Dawarich rebuilds tracks from the uploaded points by splitting on time gaps, so split legs that are close in time get merged back together. Set TARGET__DAWARICH__EMIT_TRACKER_ID=true to tag each track with a stable tracker_id; Dawarich groups points into tracks by that id and keeps the split legs separate. This works in either SPLIT_MODE, and regardless of the uploaded file format: the Dawarich target always forwards points as GeoJSON, so the tracker_id is what keeps the legs apart.

Supported targets

TypeServiceAccepted formats
dawarichDawarichgpx_1.1, geojson

Adding a new target requires implementing the Target interface in server/internal/target/.

Supported devices

TypeDeviceSupported formats
columbus-p10-proColumbus P-10 Progpx_1.1, columbus-csv

Adding a new device type requires implementing the Device interface in tracksync/internal/device/.

Development

Nix

This repository provides a flake.nix with a devshell for development.

Enter the repository and run direnv allow or use nix develop to start the devshell.

Other OS

Make sure the following is installed:

  • Go
  • Just

General

# Run server locally
just run-server
# Run client
just run-client --server-url http://localhost:8080 --token test-token \
--device-type columbus-p10-pro --device-id my-columbus --mount-point /mnt/gps
# Update nix vendor hash after changing Go dependencies
just update-vendor-hash

About

Automatically syncs track files from GPS loggers to self-hosted location tracking services

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - Quadrubo/tracksync: Automatically syncs track files from GPS loggers to self-hosted location tracking services · GitHub
Skip to content

Repository files navigation

Tracksync

Syncs track files from GPS loggers to self-hosted location tracking services like Dawarich. Plug in your device, tracks get uploaded automatically.

Features

  • Format conversion - automatically converts between device and target formats to preserve all data
  • Deduplication - files are only uploaded once
  • Multi-client - sync from multiple machines to the same server
  • Extensible device support
  • Extensible target support
  • Per-client auth tokens

NixOS module

  • Automatic sync on USB plug-in
  • Desktop notifications on sync progress
  • Mounts device automatically via udisks2
  • Integrates with sops-nix for secret management

How it works

Tracksync consists of three parts:

  • Client - a CLI tool that reads track files from a mounted GPS device and uploads them to the server.
  • Server - receives uploads, deduplicates them, converts them to the best format for the target, and forwards files to a configured target like Dawarich. Runs as a Docker container.
  • NixOS module - automates the client by detecting USB plug-in via udev, mounting the device, running the sync, and sending desktop notifications.

Install the client

Go

go install github.com/Quadrubo/tracksync/tracksync@latest

Nix

nix run github:Quadrubo/tracksync

Configure the NixOS Module

You can use a NixOS module to automate the complete process.

Run the server

Docker Compose

services:
tracksync-server:
image: ghcr.io/quadrubo/tracksync/server:latestports:
- "8080:8080"environment:
- ACCOUNT__0__DEVICE_ID=my-columbus
- ACCOUNT__0__TARGET_URL=http://dawarich:3000
- ACCOUNT__0__API_KEY=your-api-key
- CLIENT__0__ID=my-laptop
- CLIENT__0__TOKEN=your-client-token
- CLIENT__0__ALLOWED_DEVICES=my-columbusvolumes:
- data:/app/datavolumes:
data:

Server configuration

All configuration is done via environment variables.

VariableDefaultRequiredDescription
PORT8080NoServer port
STATE_DBdata/state.dbNoSQLite database path
TARGET_TIMEOUT30sNoHTTP timeout for target requests
ACCOUNT__N__DEVICE_IDYesDevice identifier
ACCOUNT__N__TARGET_TYPEdawarichNoTarget type
ACCOUNT__N__TARGET_URLYesTarget instance URL
ACCOUNT__N__API_KEYYes if not API_KEY_FILEAPI key (inline)
ACCOUNT__N__API_KEY_FILEYes if not API_KEYAPI key (file path)
ACCOUNT__N__MARKERSNoComma-separated marker:functionality rules assigning behavior to point markers (see Marker functionalities)
ACCOUNT__N__SPLIT_MARKER_POSITIONstartNoFor the split functionality: where the marked point goes, start of the new track or end of the previous one
ACCOUNT__N__SPLIT_MODEtracksNoFor the split functionality: tracks (all tracks in one file) or files (one upload per track)
TARGET__DAWARICH__EMIT_TRACKER_IDfalseNoTag each track with a stable tracker_id so Dawarich keeps split tracks separate (see Keeping split tracks separate in Dawarich)
CLIENT__N__IDYesClient identifier
CLIENT__N__TOKENYes if not TOKEN_FILEAuth token (inline)
CLIENT__N__TOKEN_FILEYes if not TOKENAuth token (file path)
CLIENT__N__ALLOWED_DEVICESYesComma-separated allowed device IDs

* One of the inline or file variant is required.

Replace N with 0, 1, 2, etc. for multiple accounts/clients.

Client usage

tracksync \
--server-url http://localhost:8080 \
--token your-client-token \
--device-type columbus-p10-pro \
--device-id my-columbus \
--mount-point /mnt/gps

By default, the client picks up all file formats the device supports. Use --device-format to restrict to a specific one (e.g. --device-format columbus-csv).

FlagDefaultRequiredDescription
--server-urlYesTracksync server URL
--tokenYes if not token-fileAuth token inline
--token-fileYes if not tokenAuth token from file
--device-typeYesGPS device type
--device-formatNoRestrict to a specific device format
--device-idYesDevice identifier
--mount-pointYesDevice mount point
--state-db~/.local/share/tracksync/state.dbNoClient state database
--log-formattextNoLog format: text, json
--timeout30sNoHTTP request timeout
--clearfalseNoClear upload history

API

EndpointMethodAuthDescription
/healthGETNoLiveness check
/uploadPOSTBearer tokenUpload a track file (multipart, field file)

The /upload endpoint requires X-Device-ID and X-Source-Format headers.

NixOS module

Add tracksync as a flake input:

# flake.nixinputs.tracksync.url="github:Quadrubo/tracksync";

Import the module and configure:

{imports=[inputs.tracksync.nixosModules.default];services.tracksync={enable=true;user="your-username";serverUrl="https://tracksync.example.com";tokenFile="/run/secrets/tracksync-token";devices=[{deviceId="my-device";deviceType="columbus-p10-pro";# deviceFormat = "columbus-csv"; # optional, omit to find all supported formatsusbVendorId="xxxx";# from lsusbusbProductId="xxxx";# from lsusbdiskById="usb-xxxx-part1";# from ls /dev/disk/by-id/}];};}

The sync service runs outside of a desktop session, so it needs a polkit rule in order to automatically mount the gps logger device. You can find the device serial with lsblk --nodeps -o name,serial while the device is connected.

{security.polkit.extraConfig='' polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.udisks2.filesystem-mount" && subject.user == "your-username" && action.lookup("drive.serial") == "your-device-serial") { return polkit.Result.YES; } }); '';}

When the device is plugged in, a systemd service automatically mounts it, syncs all track files, and sends a desktop notification with the result.

Format conversion

The server automatically converts between file formats to preserve the most data. Each device declares the formats it can produce, and each target declares the formats it accepts. The server parses the source file into a universal track model, then serializes to the target format that preserves the most fields.

For example, when a Columbus P-10 Pro is configured to output CSV (which includes speed and heading), the server converts to GeoJSON before forwarding to Dawarich, since GeoJSON can represent these fields while GPX 1.1 cannot.

Supported formats

FormatTypeSpeedHeadingElevationSatellitesDOP
gpx_1.1Parse + SerializeNoNoYesYesYes
columbus-csvParseYesYesYesNoNo
geojsonSerializeYesYesYesYesYes

Marker functionalities

Points can carry a marker, a source-format annotation such as a manually placed POI or waypoint. You can assign a functionality to each marker so that tracksync acts on it. This works at the universal-track level and is format-agnostic: every parser maps its format's native markers onto a point marker, and functionalities operate on those.

Configure rules with ACCOUNT__N__MARKERS as comma-separated marker:functionality pairs:

ACCOUNT__0__MARKERS=C:split
# multiple markers, each with its own functionality:
ACCOUNT__0__MARKERS=C:split,D:split

Which marker values are available depends on the source format:

FormatMarkers
columbus-csvTAG column values other than T: C (function-key POI), D (second POI), G (automatic wake-up point, usually leave unmapped)

Available functionalities

FunctionalityEffect
splitStart a new track at the marked point. Useful for separating legs of a journey. For example pressing a logger's function key when boarding and leaving a bus so the walking and bus legs become distinct tracks.

For the split functionality, ACCOUNT__N__SPLIT_MARKER_POSITION controls which side of the split the marked point lands on: start (default) makes it the first point of the new track, end keeps it as the last point of the previous one.

ACCOUNT__N__SPLIT_MODE controls how the split tracks are delivered:

  • tracks (default): all tracks are written into a single file.
  • files: each track is uploaded as its own file, with suffixed filenames (track-1.geojson, track-2.geojson, …). Splitting into files does not by itself keep tracks separate in Dawarich, which re-segments the points by time gap; see Keeping split tracks separate in Dawarich.

Keeping split tracks separate in Dawarich

Dawarich rebuilds tracks from the uploaded points by splitting on time gaps, so split legs that are close in time get merged back together. Set TARGET__DAWARICH__EMIT_TRACKER_ID=true to tag each track with a stable tracker_id; Dawarich groups points into tracks by that id and keeps the split legs separate. This works in either SPLIT_MODE, and regardless of the uploaded file format: the Dawarich target always forwards points as GeoJSON, so the tracker_id is what keeps the legs apart.

Supported targets

TypeServiceAccepted formats
dawarichDawarichgpx_1.1, geojson

Adding a new target requires implementing the Target interface in server/internal/target/.

Supported devices

TypeDeviceSupported formats
columbus-p10-proColumbus P-10 Progpx_1.1, columbus-csv

Adding a new device type requires implementing the Device interface in tracksync/internal/device/.

Development

Nix

This repository provides a flake.nix with a devshell for development.

Enter the repository and run direnv allow or use nix develop to start the devshell.

Other OS

Make sure the following is installed:

  • Go
  • Just

General

# Run server locally
just run-server
# Run client
just run-client --server-url http://localhost:8080 --token test-token \
--device-type columbus-p10-pro --device-id my-columbus --mount-point /mnt/gps
# Update nix vendor hash after changing Go dependencies
just update-vendor-hash

About

Automatically syncs track files from GPS loggers to self-hosted location tracking services

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - Quadrubo/tracksync: Automatically syncs track files from GPS loggers to self-hosted location tracking services · GitHub
Skip to content

Repository files navigation

Tracksync

Syncs track files from GPS loggers to self-hosted location tracking services like Dawarich. Plug in your device, tracks get uploaded automatically.

Features

  • Format conversion - automatically converts between device and target formats to preserve all data
  • Deduplication - files are only uploaded once
  • Multi-client - sync from multiple machines to the same server
  • Extensible device support
  • Extensible target support
  • Per-client auth tokens

NixOS module

  • Automatic sync on USB plug-in
  • Desktop notifications on sync progress
  • Mounts device automatically via udisks2
  • Integrates with sops-nix for secret management

How it works

Tracksync consists of three parts:

  • Client - a CLI tool that reads track files from a mounted GPS device and uploads them to the server.
  • Server - receives uploads, deduplicates them, converts them to the best format for the target, and forwards files to a configured target like Dawarich. Runs as a Docker container.
  • NixOS module - automates the client by detecting USB plug-in via udev, mounting the device, running the sync, and sending desktop notifications.

Install the client

Go

go install github.com/Quadrubo/tracksync/tracksync@latest

Nix

nix run github:Quadrubo/tracksync

Configure the NixOS Module

You can use a NixOS module to automate the complete process.

Run the server

Docker Compose

services:
tracksync-server:
image: ghcr.io/quadrubo/tracksync/server:latestports:
- "8080:8080"environment:
- ACCOUNT__0__DEVICE_ID=my-columbus
- ACCOUNT__0__TARGET_URL=http://dawarich:3000
- ACCOUNT__0__API_KEY=your-api-key
- CLIENT__0__ID=my-laptop
- CLIENT__0__TOKEN=your-client-token
- CLIENT__0__ALLOWED_DEVICES=my-columbusvolumes:
- data:/app/datavolumes:
data:

Server configuration

All configuration is done via environment variables.

VariableDefaultRequiredDescription
PORT8080NoServer port
STATE_DBdata/state.dbNoSQLite database path
TARGET_TIMEOUT30sNoHTTP timeout for target requests
ACCOUNT__N__DEVICE_IDYesDevice identifier
ACCOUNT__N__TARGET_TYPEdawarichNoTarget type
ACCOUNT__N__TARGET_URLYesTarget instance URL
ACCOUNT__N__API_KEYYes if not API_KEY_FILEAPI key (inline)
ACCOUNT__N__API_KEY_FILEYes if not API_KEYAPI key (file path)
ACCOUNT__N__MARKERSNoComma-separated marker:functionality rules assigning behavior to point markers (see Marker functionalities)
ACCOUNT__N__SPLIT_MARKER_POSITIONstartNoFor the split functionality: where the marked point goes, start of the new track or end of the previous one
ACCOUNT__N__SPLIT_MODEtracksNoFor the split functionality: tracks (all tracks in one file) or files (one upload per track)
TARGET__DAWARICH__EMIT_TRACKER_IDfalseNoTag each track with a stable tracker_id so Dawarich keeps split tracks separate (see Keeping split tracks separate in Dawarich)
CLIENT__N__IDYesClient identifier
CLIENT__N__TOKENYes if not TOKEN_FILEAuth token (inline)
CLIENT__N__TOKEN_FILEYes if not TOKENAuth token (file path)
CLIENT__N__ALLOWED_DEVICESYesComma-separated allowed device IDs

* One of the inline or file variant is required.

Replace N with 0, 1, 2, etc. for multiple accounts/clients.

Client usage

tracksync \
--server-url http://localhost:8080 \
--token your-client-token \
--device-type columbus-p10-pro \
--device-id my-columbus \
--mount-point /mnt/gps

By default, the client picks up all file formats the device supports. Use --device-format to restrict to a specific one (e.g. --device-format columbus-csv).

FlagDefaultRequiredDescription
--server-urlYesTracksync server URL
--tokenYes if not token-fileAuth token inline
--token-fileYes if not tokenAuth token from file
--device-typeYesGPS device type
--device-formatNoRestrict to a specific device format
--device-idYesDevice identifier
--mount-pointYesDevice mount point
--state-db~/.local/share/tracksync/state.dbNoClient state database
--log-formattextNoLog format: text, json
--timeout30sNoHTTP request timeout
--clearfalseNoClear upload history

API

EndpointMethodAuthDescription
/healthGETNoLiveness check
/uploadPOSTBearer tokenUpload a track file (multipart, field file)

The /upload endpoint requires X-Device-ID and X-Source-Format headers.

NixOS module

Add tracksync as a flake input:

# flake.nixinputs.tracksync.url="github:Quadrubo/tracksync";

Import the module and configure:

{imports=[inputs.tracksync.nixosModules.default];services.tracksync={enable=true;user="your-username";serverUrl="https://tracksync.example.com";tokenFile="/run/secrets/tracksync-token";devices=[{deviceId="my-device";deviceType="columbus-p10-pro";# deviceFormat = "columbus-csv"; # optional, omit to find all supported formatsusbVendorId="xxxx";# from lsusbusbProductId="xxxx";# from lsusbdiskById="usb-xxxx-part1";# from ls /dev/disk/by-id/}];};}

The sync service runs outside of a desktop session, so it needs a polkit rule in order to automatically mount the gps logger device. You can find the device serial with lsblk --nodeps -o name,serial while the device is connected.

{security.polkit.extraConfig='' polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.udisks2.filesystem-mount" && subject.user == "your-username" && action.lookup("drive.serial") == "your-device-serial") { return polkit.Result.YES; } }); '';}

When the device is plugged in, a systemd service automatically mounts it, syncs all track files, and sends a desktop notification with the result.

Format conversion

The server automatically converts between file formats to preserve the most data. Each device declares the formats it can produce, and each target declares the formats it accepts. The server parses the source file into a universal track model, then serializes to the target format that preserves the most fields.

For example, when a Columbus P-10 Pro is configured to output CSV (which includes speed and heading), the server converts to GeoJSON before forwarding to Dawarich, since GeoJSON can represent these fields while GPX 1.1 cannot.

Supported formats

FormatTypeSpeedHeadingElevationSatellitesDOP
gpx_1.1Parse + SerializeNoNoYesYesYes
columbus-csvParseYesYesYesNoNo
geojsonSerializeYesYesYesYesYes

Marker functionalities

Points can carry a marker, a source-format annotation such as a manually placed POI or waypoint. You can assign a functionality to each marker so that tracksync acts on it. This works at the universal-track level and is format-agnostic: every parser maps its format's native markers onto a point marker, and functionalities operate on those.

Configure rules with ACCOUNT__N__MARKERS as comma-separated marker:functionality pairs:

ACCOUNT__0__MARKERS=C:split
# multiple markers, each with its own functionality:
ACCOUNT__0__MARKERS=C:split,D:split

Which marker values are available depends on the source format:

FormatMarkers
columbus-csvTAG column values other than T: C (function-key POI), D (second POI), G (automatic wake-up point, usually leave unmapped)

Available functionalities

FunctionalityEffect
splitStart a new track at the marked point. Useful for separating legs of a journey. For example pressing a logger's function key when boarding and leaving a bus so the walking and bus legs become distinct tracks.

For the split functionality, ACCOUNT__N__SPLIT_MARKER_POSITION controls which side of the split the marked point lands on: start (default) makes it the first point of the new track, end keeps it as the last point of the previous one.

ACCOUNT__N__SPLIT_MODE controls how the split tracks are delivered:

  • tracks (default): all tracks are written into a single file.
  • files: each track is uploaded as its own file, with suffixed filenames (track-1.geojson, track-2.geojson, …). Splitting into files does not by itself keep tracks separate in Dawarich, which re-segments the points by time gap; see Keeping split tracks separate in Dawarich.

Keeping split tracks separate in Dawarich

Dawarich rebuilds tracks from the uploaded points by splitting on time gaps, so split legs that are close in time get merged back together. Set TARGET__DAWARICH__EMIT_TRACKER_ID=true to tag each track with a stable tracker_id; Dawarich groups points into tracks by that id and keeps the split legs separate. This works in either SPLIT_MODE, and regardless of the uploaded file format: the Dawarich target always forwards points as GeoJSON, so the tracker_id is what keeps the legs apart.

Supported targets

TypeServiceAccepted formats
dawarichDawarichgpx_1.1, geojson

Adding a new target requires implementing the Target interface in server/internal/target/.

Supported devices

TypeDeviceSupported formats
columbus-p10-proColumbus P-10 Progpx_1.1, columbus-csv

Adding a new device type requires implementing the Device interface in tracksync/internal/device/.

Development

Nix

This repository provides a flake.nix with a devshell for development.

Enter the repository and run direnv allow or use nix develop to start the devshell.

Other OS

Make sure the following is installed:

  • Go
  • Just

General

# Run server locally
just run-server
# Run client
just run-client --server-url http://localhost:8080 --token test-token \
--device-type columbus-p10-pro --device-id my-columbus --mount-point /mnt/gps
# Update nix vendor hash after changing Go dependencies
just update-vendor-hash

About

Automatically syncs track files from GPS loggers to self-hosted location tracking services

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - Quadrubo/tracksync: Automatically syncs track files from GPS loggers to self-hosted location tracking services · GitHub
Skip to content

Repository files navigation

Tracksync

Syncs track files from GPS loggers to self-hosted location tracking services like Dawarich. Plug in your device, tracks get uploaded automatically.

Features

  • Format conversion - automatically converts between device and target formats to preserve all data
  • Deduplication - files are only uploaded once
  • Multi-client - sync from multiple machines to the same server
  • Extensible device support
  • Extensible target support
  • Per-client auth tokens

NixOS module

  • Automatic sync on USB plug-in
  • Desktop notifications on sync progress
  • Mounts device automatically via udisks2
  • Integrates with sops-nix for secret management

How it works

Tracksync consists of three parts:

  • Client - a CLI tool that reads track files from a mounted GPS device and uploads them to the server.
  • Server - receives uploads, deduplicates them, converts them to the best format for the target, and forwards files to a configured target like Dawarich. Runs as a Docker container.
  • NixOS module - automates the client by detecting USB plug-in via udev, mounting the device, running the sync, and sending desktop notifications.

Install the client

Go

go install github.com/Quadrubo/tracksync/tracksync@latest

Nix

nix run github:Quadrubo/tracksync

Configure the NixOS Module

You can use a NixOS module to automate the complete process.

Run the server

Docker Compose

services:
tracksync-server:
image: ghcr.io/quadrubo/tracksync/server:latestports:
- "8080:8080"environment:
- ACCOUNT__0__DEVICE_ID=my-columbus
- ACCOUNT__0__TARGET_URL=http://dawarich:3000
- ACCOUNT__0__API_KEY=your-api-key
- CLIENT__0__ID=my-laptop
- CLIENT__0__TOKEN=your-client-token
- CLIENT__0__ALLOWED_DEVICES=my-columbusvolumes:
- data:/app/datavolumes:
data:

Server configuration

All configuration is done via environment variables.

VariableDefaultRequiredDescription
PORT8080NoServer port
STATE_DBdata/state.dbNoSQLite database path
TARGET_TIMEOUT30sNoHTTP timeout for target requests
ACCOUNT__N__DEVICE_IDYesDevice identifier
ACCOUNT__N__TARGET_TYPEdawarichNoTarget type
ACCOUNT__N__TARGET_URLYesTarget instance URL
ACCOUNT__N__API_KEYYes if not API_KEY_FILEAPI key (inline)
ACCOUNT__N__API_KEY_FILEYes if not API_KEYAPI key (file path)
ACCOUNT__N__MARKERSNoComma-separated marker:functionality rules assigning behavior to point markers (see Marker functionalities)
ACCOUNT__N__SPLIT_MARKER_POSITIONstartNoFor the split functionality: where the marked point goes, start of the new track or end of the previous one
ACCOUNT__N__SPLIT_MODEtracksNoFor the split functionality: tracks (all tracks in one file) or files (one upload per track)
TARGET__DAWARICH__EMIT_TRACKER_IDfalseNoTag each track with a stable tracker_id so Dawarich keeps split tracks separate (see Keeping split tracks separate in Dawarich)
CLIENT__N__IDYesClient identifier
CLIENT__N__TOKENYes if not TOKEN_FILEAuth token (inline)
CLIENT__N__TOKEN_FILEYes if not TOKENAuth token (file path)
CLIENT__N__ALLOWED_DEVICESYesComma-separated allowed device IDs

* One of the inline or file variant is required.

Replace N with 0, 1, 2, etc. for multiple accounts/clients.

Client usage

tracksync \
--server-url http://localhost:8080 \
--token your-client-token \
--device-type columbus-p10-pro \
--device-id my-columbus \
--mount-point /mnt/gps

By default, the client picks up all file formats the device supports. Use --device-format to restrict to a specific one (e.g. --device-format columbus-csv).

FlagDefaultRequiredDescription
--server-urlYesTracksync server URL
--tokenYes if not token-fileAuth token inline
--token-fileYes if not tokenAuth token from file
--device-typeYesGPS device type
--device-formatNoRestrict to a specific device format
--device-idYesDevice identifier
--mount-pointYesDevice mount point
--state-db~/.local/share/tracksync/state.dbNoClient state database
--log-formattextNoLog format: text, json
--timeout30sNoHTTP request timeout
--clearfalseNoClear upload history

API

EndpointMethodAuthDescription
/healthGETNoLiveness check
/uploadPOSTBearer tokenUpload a track file (multipart, field file)

The /upload endpoint requires X-Device-ID and X-Source-Format headers.

NixOS module

Add tracksync as a flake input:

# flake.nixinputs.tracksync.url="github:Quadrubo/tracksync";

Import the module and configure:

{imports=[inputs.tracksync.nixosModules.default];services.tracksync={enable=true;user="your-username";serverUrl="https://tracksync.example.com";tokenFile="/run/secrets/tracksync-token";devices=[{deviceId="my-device";deviceType="columbus-p10-pro";# deviceFormat = "columbus-csv"; # optional, omit to find all supported formatsusbVendorId="xxxx";# from lsusbusbProductId="xxxx";# from lsusbdiskById="usb-xxxx-part1";# from ls /dev/disk/by-id/}];};}

The sync service runs outside of a desktop session, so it needs a polkit rule in order to automatically mount the gps logger device. You can find the device serial with lsblk --nodeps -o name,serial while the device is connected.

{security.polkit.extraConfig='' polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.udisks2.filesystem-mount" && subject.user == "your-username" && action.lookup("drive.serial") == "your-device-serial") { return polkit.Result.YES; } }); '';}

When the device is plugged in, a systemd service automatically mounts it, syncs all track files, and sends a desktop notification with the result.

Format conversion

The server automatically converts between file formats to preserve the most data. Each device declares the formats it can produce, and each target declares the formats it accepts. The server parses the source file into a universal track model, then serializes to the target format that preserves the most fields.

For example, when a Columbus P-10 Pro is configured to output CSV (which includes speed and heading), the server converts to GeoJSON before forwarding to Dawarich, since GeoJSON can represent these fields while GPX 1.1 cannot.

Supported formats

FormatTypeSpeedHeadingElevationSatellitesDOP
gpx_1.1Parse + SerializeNoNoYesYesYes
columbus-csvParseYesYesYesNoNo
geojsonSerializeYesYesYesYesYes

Marker functionalities

Points can carry a marker, a source-format annotation such as a manually placed POI or waypoint. You can assign a functionality to each marker so that tracksync acts on it. This works at the universal-track level and is format-agnostic: every parser maps its format's native markers onto a point marker, and functionalities operate on those.

Configure rules with ACCOUNT__N__MARKERS as comma-separated marker:functionality pairs:

ACCOUNT__0__MARKERS=C:split
# multiple markers, each with its own functionality:
ACCOUNT__0__MARKERS=C:split,D:split

Which marker values are available depends on the source format:

FormatMarkers
columbus-csvTAG column values other than T: C (function-key POI), D (second POI), G (automatic wake-up point, usually leave unmapped)

Available functionalities

FunctionalityEffect
splitStart a new track at the marked point. Useful for separating legs of a journey. For example pressing a logger's function key when boarding and leaving a bus so the walking and bus legs become distinct tracks.

For the split functionality, ACCOUNT__N__SPLIT_MARKER_POSITION controls which side of the split the marked point lands on: start (default) makes it the first point of the new track, end keeps it as the last point of the previous one.

ACCOUNT__N__SPLIT_MODE controls how the split tracks are delivered:

  • tracks (default): all tracks are written into a single file.
  • files: each track is uploaded as its own file, with suffixed filenames (track-1.geojson, track-2.geojson, …). Splitting into files does not by itself keep tracks separate in Dawarich, which re-segments the points by time gap; see Keeping split tracks separate in Dawarich.

Keeping split tracks separate in Dawarich

Dawarich rebuilds tracks from the uploaded points by splitting on time gaps, so split legs that are close in time get merged back together. Set TARGET__DAWARICH__EMIT_TRACKER_ID=true to tag each track with a stable tracker_id; Dawarich groups points into tracks by that id and keeps the split legs separate. This works in either SPLIT_MODE, and regardless of the uploaded file format: the Dawarich target always forwards points as GeoJSON, so the tracker_id is what keeps the legs apart.

Supported targets

TypeServiceAccepted formats
dawarichDawarichgpx_1.1, geojson

Adding a new target requires implementing the Target interface in server/internal/target/.

Supported devices

TypeDeviceSupported formats
columbus-p10-proColumbus P-10 Progpx_1.1, columbus-csv

Adding a new device type requires implementing the Device interface in tracksync/internal/device/.

Development

Nix

This repository provides a flake.nix with a devshell for development.

Enter the repository and run direnv allow or use nix develop to start the devshell.

Other OS

Make sure the following is installed:

  • Go
  • Just

General

# Run server locally
just run-server
# Run client
just run-client --server-url http://localhost:8080 --token test-token \
--device-type columbus-p10-pro --device-id my-columbus --mount-point /mnt/gps
# Update nix vendor hash after changing Go dependencies
just update-vendor-hash

About

Automatically syncs track files from GPS loggers to self-hosted location tracking services

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - Quadrubo/tracksync: Automatically syncs track files from GPS loggers to self-hosted location tracking services · GitHub
Skip to content

Repository files navigation

Tracksync

Syncs track files from GPS loggers to self-hosted location tracking services like Dawarich. Plug in your device, tracks get uploaded automatically.

Features

  • Format conversion - automatically converts between device and target formats to preserve all data
  • Deduplication - files are only uploaded once
  • Multi-client - sync from multiple machines to the same server
  • Extensible device support
  • Extensible target support
  • Per-client auth tokens

NixOS module

  • Automatic sync on USB plug-in
  • Desktop notifications on sync progress
  • Mounts device automatically via udisks2
  • Integrates with sops-nix for secret management

How it works

Tracksync consists of three parts:

  • Client - a CLI tool that reads track files from a mounted GPS device and uploads them to the server.
  • Server - receives uploads, deduplicates them, converts them to the best format for the target, and forwards files to a configured target like Dawarich. Runs as a Docker container.
  • NixOS module - automates the client by detecting USB plug-in via udev, mounting the device, running the sync, and sending desktop notifications.

Install the client

Go

go install github.com/Quadrubo/tracksync/tracksync@latest

Nix

nix run github:Quadrubo/tracksync

Configure the NixOS Module

You can use a NixOS module to automate the complete process.

Run the server

Docker Compose

services:
tracksync-server:
image: ghcr.io/quadrubo/tracksync/server:latestports:
- "8080:8080"environment:
- ACCOUNT__0__DEVICE_ID=my-columbus
- ACCOUNT__0__TARGET_URL=http://dawarich:3000
- ACCOUNT__0__API_KEY=your-api-key
- CLIENT__0__ID=my-laptop
- CLIENT__0__TOKEN=your-client-token
- CLIENT__0__ALLOWED_DEVICES=my-columbusvolumes:
- data:/app/datavolumes:
data:

Server configuration

All configuration is done via environment variables.

VariableDefaultRequiredDescription
PORT8080NoServer port
STATE_DBdata/state.dbNoSQLite database path
TARGET_TIMEOUT30sNoHTTP timeout for target requests
ACCOUNT__N__DEVICE_IDYesDevice identifier
ACCOUNT__N__TARGET_TYPEdawarichNoTarget type
ACCOUNT__N__TARGET_URLYesTarget instance URL
ACCOUNT__N__API_KEYYes if not API_KEY_FILEAPI key (inline)
ACCOUNT__N__API_KEY_FILEYes if not API_KEYAPI key (file path)
ACCOUNT__N__MARKERSNoComma-separated marker:functionality rules assigning behavior to point markers (see Marker functionalities)
ACCOUNT__N__SPLIT_MARKER_POSITIONstartNoFor the split functionality: where the marked point goes, start of the new track or end of the previous one
ACCOUNT__N__SPLIT_MODEtracksNoFor the split functionality: tracks (all tracks in one file) or files (one upload per track)
TARGET__DAWARICH__EMIT_TRACKER_IDfalseNoTag each track with a stable tracker_id so Dawarich keeps split tracks separate (see Keeping split tracks separate in Dawarich)
CLIENT__N__IDYesClient identifier
CLIENT__N__TOKENYes if not TOKEN_FILEAuth token (inline)
CLIENT__N__TOKEN_FILEYes if not TOKENAuth token (file path)
CLIENT__N__ALLOWED_DEVICESYesComma-separated allowed device IDs

* One of the inline or file variant is required.

Replace N with 0, 1, 2, etc. for multiple accounts/clients.

Client usage

tracksync \
--server-url http://localhost:8080 \
--token your-client-token \
--device-type columbus-p10-pro \
--device-id my-columbus \
--mount-point /mnt/gps

By default, the client picks up all file formats the device supports. Use --device-format to restrict to a specific one (e.g. --device-format columbus-csv).

FlagDefaultRequiredDescription
--server-urlYesTracksync server URL
--tokenYes if not token-fileAuth token inline
--token-fileYes if not tokenAuth token from file
--device-typeYesGPS device type
--device-formatNoRestrict to a specific device format
--device-idYesDevice identifier
--mount-pointYesDevice mount point
--state-db~/.local/share/tracksync/state.dbNoClient state database
--log-formattextNoLog format: text, json
--timeout30sNoHTTP request timeout
--clearfalseNoClear upload history

API

EndpointMethodAuthDescription
/healthGETNoLiveness check
/uploadPOSTBearer tokenUpload a track file (multipart, field file)

The /upload endpoint requires X-Device-ID and X-Source-Format headers.

NixOS module

Add tracksync as a flake input:

# flake.nixinputs.tracksync.url="github:Quadrubo/tracksync";

Import the module and configure:

{imports=[inputs.tracksync.nixosModules.default];services.tracksync={enable=true;user="your-username";serverUrl="https://tracksync.example.com";tokenFile="/run/secrets/tracksync-token";devices=[{deviceId="my-device";deviceType="columbus-p10-pro";# deviceFormat = "columbus-csv"; # optional, omit to find all supported formatsusbVendorId="xxxx";# from lsusbusbProductId="xxxx";# from lsusbdiskById="usb-xxxx-part1";# from ls /dev/disk/by-id/}];};}

The sync service runs outside of a desktop session, so it needs a polkit rule in order to automatically mount the gps logger device. You can find the device serial with lsblk --nodeps -o name,serial while the device is connected.

{security.polkit.extraConfig='' polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.udisks2.filesystem-mount" && subject.user == "your-username" && action.lookup("drive.serial") == "your-device-serial") { return polkit.Result.YES; } }); '';}

When the device is plugged in, a systemd service automatically mounts it, syncs all track files, and sends a desktop notification with the result.

Format conversion

The server automatically converts between file formats to preserve the most data. Each device declares the formats it can produce, and each target declares the formats it accepts. The server parses the source file into a universal track model, then serializes to the target format that preserves the most fields.

For example, when a Columbus P-10 Pro is configured to output CSV (which includes speed and heading), the server converts to GeoJSON before forwarding to Dawarich, since GeoJSON can represent these fields while GPX 1.1 cannot.

Supported formats

FormatTypeSpeedHeadingElevationSatellitesDOP
gpx_1.1Parse + SerializeNoNoYesYesYes
columbus-csvParseYesYesYesNoNo
geojsonSerializeYesYesYesYesYes

Marker functionalities

Points can carry a marker, a source-format annotation such as a manually placed POI or waypoint. You can assign a functionality to each marker so that tracksync acts on it. This works at the universal-track level and is format-agnostic: every parser maps its format's native markers onto a point marker, and functionalities operate on those.

Configure rules with ACCOUNT__N__MARKERS as comma-separated marker:functionality pairs:

ACCOUNT__0__MARKERS=C:split
# multiple markers, each with its own functionality:
ACCOUNT__0__MARKERS=C:split,D:split

Which marker values are available depends on the source format:

FormatMarkers
columbus-csvTAG column values other than T: C (function-key POI), D (second POI), G (automatic wake-up point, usually leave unmapped)

Available functionalities

FunctionalityEffect
splitStart a new track at the marked point. Useful for separating legs of a journey. For example pressing a logger's function key when boarding and leaving a bus so the walking and bus legs become distinct tracks.

For the split functionality, ACCOUNT__N__SPLIT_MARKER_POSITION controls which side of the split the marked point lands on: start (default) makes it the first point of the new track, end keeps it as the last point of the previous one.

ACCOUNT__N__SPLIT_MODE controls how the split tracks are delivered:

  • tracks (default): all tracks are written into a single file.
  • files: each track is uploaded as its own file, with suffixed filenames (track-1.geojson, track-2.geojson, …). Splitting into files does not by itself keep tracks separate in Dawarich, which re-segments the points by time gap; see Keeping split tracks separate in Dawarich.

Keeping split tracks separate in Dawarich

Dawarich rebuilds tracks from the uploaded points by splitting on time gaps, so split legs that are close in time get merged back together. Set TARGET__DAWARICH__EMIT_TRACKER_ID=true to tag each track with a stable tracker_id; Dawarich groups points into tracks by that id and keeps the split legs separate. This works in either SPLIT_MODE, and regardless of the uploaded file format: the Dawarich target always forwards points as GeoJSON, so the tracker_id is what keeps the legs apart.

Supported targets

TypeServiceAccepted formats
dawarichDawarichgpx_1.1, geojson

Adding a new target requires implementing the Target interface in server/internal/target/.

Supported devices

TypeDeviceSupported formats
columbus-p10-proColumbus P-10 Progpx_1.1, columbus-csv

Adding a new device type requires implementing the Device interface in tracksync/internal/device/.

Development

Nix

This repository provides a flake.nix with a devshell for development.

Enter the repository and run direnv allow or use nix develop to start the devshell.

Other OS

Make sure the following is installed:

  • Go
  • Just

General

# Run server locally
just run-server
# Run client
just run-client --server-url http://localhost:8080 --token test-token \
--device-type columbus-p10-pro --device-id my-columbus --mount-point /mnt/gps
# Update nix vendor hash after changing Go dependencies
just update-vendor-hash

About

Automatically syncs track files from GPS loggers to self-hosted location tracking services

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - Quadrubo/tracksync: Automatically syncs track files from GPS loggers to self-hosted location tracking services · GitHub
Skip to content

Repository files navigation

Tracksync

Syncs track files from GPS loggers to self-hosted location tracking services like Dawarich. Plug in your device, tracks get uploaded automatically.

Features

  • Format conversion - automatically converts between device and target formats to preserve all data
  • Deduplication - files are only uploaded once
  • Multi-client - sync from multiple machines to the same server
  • Extensible device support
  • Extensible target support
  • Per-client auth tokens

NixOS module

  • Automatic sync on USB plug-in
  • Desktop notifications on sync progress
  • Mounts device automatically via udisks2
  • Integrates with sops-nix for secret management

How it works

Tracksync consists of three parts:

  • Client - a CLI tool that reads track files from a mounted GPS device and uploads them to the server.
  • Server - receives uploads, deduplicates them, converts them to the best format for the target, and forwards files to a configured target like Dawarich. Runs as a Docker container.
  • NixOS module - automates the client by detecting USB plug-in via udev, mounting the device, running the sync, and sending desktop notifications.

Install the client

Go

go install github.com/Quadrubo/tracksync/tracksync@latest

Nix

nix run github:Quadrubo/tracksync

Configure the NixOS Module

You can use a NixOS module to automate the complete process.

Run the server

Docker Compose

services:
tracksync-server:
image: ghcr.io/quadrubo/tracksync/server:latestports:
- "8080:8080"environment:
- ACCOUNT__0__DEVICE_ID=my-columbus
- ACCOUNT__0__TARGET_URL=http://dawarich:3000
- ACCOUNT__0__API_KEY=your-api-key
- CLIENT__0__ID=my-laptop
- CLIENT__0__TOKEN=your-client-token
- CLIENT__0__ALLOWED_DEVICES=my-columbusvolumes:
- data:/app/datavolumes:
data:

Server configuration

All configuration is done via environment variables.

VariableDefaultRequiredDescription
PORT8080NoServer port
STATE_DBdata/state.dbNoSQLite database path
TARGET_TIMEOUT30sNoHTTP timeout for target requests
ACCOUNT__N__DEVICE_IDYesDevice identifier
ACCOUNT__N__TARGET_TYPEdawarichNoTarget type
ACCOUNT__N__TARGET_URLYesTarget instance URL
ACCOUNT__N__API_KEYYes if not API_KEY_FILEAPI key (inline)
ACCOUNT__N__API_KEY_FILEYes if not API_KEYAPI key (file path)
ACCOUNT__N__MARKERSNoComma-separated marker:functionality rules assigning behavior to point markers (see Marker functionalities)
ACCOUNT__N__SPLIT_MARKER_POSITIONstartNoFor the split functionality: where the marked point goes, start of the new track or end of the previous one
ACCOUNT__N__SPLIT_MODEtracksNoFor the split functionality: tracks (all tracks in one file) or files (one upload per track)
TARGET__DAWARICH__EMIT_TRACKER_IDfalseNoTag each track with a stable tracker_id so Dawarich keeps split tracks separate (see Keeping split tracks separate in Dawarich)
CLIENT__N__IDYesClient identifier
CLIENT__N__TOKENYes if not TOKEN_FILEAuth token (inline)
CLIENT__N__TOKEN_FILEYes if not TOKENAuth token (file path)
CLIENT__N__ALLOWED_DEVICESYesComma-separated allowed device IDs

* One of the inline or file variant is required.

Replace N with 0, 1, 2, etc. for multiple accounts/clients.

Client usage

tracksync \
--server-url http://localhost:8080 \
--token your-client-token \
--device-type columbus-p10-pro \
--device-id my-columbus \
--mount-point /mnt/gps

By default, the client picks up all file formats the device supports. Use --device-format to restrict to a specific one (e.g. --device-format columbus-csv).

FlagDefaultRequiredDescription
--server-urlYesTracksync server URL
--tokenYes if not token-fileAuth token inline
--token-fileYes if not tokenAuth token from file
--device-typeYesGPS device type
--device-formatNoRestrict to a specific device format
--device-idYesDevice identifier
--mount-pointYesDevice mount point
--state-db~/.local/share/tracksync/state.dbNoClient state database
--log-formattextNoLog format: text, json
--timeout30sNoHTTP request timeout
--clearfalseNoClear upload history

API

EndpointMethodAuthDescription
/healthGETNoLiveness check
/uploadPOSTBearer tokenUpload a track file (multipart, field file)

The /upload endpoint requires X-Device-ID and X-Source-Format headers.

NixOS module

Add tracksync as a flake input:

# flake.nixinputs.tracksync.url="github:Quadrubo/tracksync";

Import the module and configure:

{imports=[inputs.tracksync.nixosModules.default];services.tracksync={enable=true;user="your-username";serverUrl="https://tracksync.example.com";tokenFile="/run/secrets/tracksync-token";devices=[{deviceId="my-device";deviceType="columbus-p10-pro";# deviceFormat = "columbus-csv"; # optional, omit to find all supported formatsusbVendorId="xxxx";# from lsusbusbProductId="xxxx";# from lsusbdiskById="usb-xxxx-part1";# from ls /dev/disk/by-id/}];};}

The sync service runs outside of a desktop session, so it needs a polkit rule in order to automatically mount the gps logger device. You can find the device serial with lsblk --nodeps -o name,serial while the device is connected.

{security.polkit.extraConfig='' polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.udisks2.filesystem-mount" && subject.user == "your-username" && action.lookup("drive.serial") == "your-device-serial") { return polkit.Result.YES; } }); '';}

When the device is plugged in, a systemd service automatically mounts it, syncs all track files, and sends a desktop notification with the result.

Format conversion

The server automatically converts between file formats to preserve the most data. Each device declares the formats it can produce, and each target declares the formats it accepts. The server parses the source file into a universal track model, then serializes to the target format that preserves the most fields.

For example, when a Columbus P-10 Pro is configured to output CSV (which includes speed and heading), the server converts to GeoJSON before forwarding to Dawarich, since GeoJSON can represent these fields while GPX 1.1 cannot.

Supported formats

FormatTypeSpeedHeadingElevationSatellitesDOP
gpx_1.1Parse + SerializeNoNoYesYesYes
columbus-csvParseYesYesYesNoNo
geojsonSerializeYesYesYesYesYes

Marker functionalities

Points can carry a marker, a source-format annotation such as a manually placed POI or waypoint. You can assign a functionality to each marker so that tracksync acts on it. This works at the universal-track level and is format-agnostic: every parser maps its format's native markers onto a point marker, and functionalities operate on those.

Configure rules with ACCOUNT__N__MARKERS as comma-separated marker:functionality pairs:

ACCOUNT__0__MARKERS=C:split
# multiple markers, each with its own functionality:
ACCOUNT__0__MARKERS=C:split,D:split

Which marker values are available depends on the source format:

FormatMarkers
columbus-csvTAG column values other than T: C (function-key POI), D (second POI), G (automatic wake-up point, usually leave unmapped)

Available functionalities

FunctionalityEffect
splitStart a new track at the marked point. Useful for separating legs of a journey. For example pressing a logger's function key when boarding and leaving a bus so the walking and bus legs become distinct tracks.

For the split functionality, ACCOUNT__N__SPLIT_MARKER_POSITION controls which side of the split the marked point lands on: start (default) makes it the first point of the new track, end keeps it as the last point of the previous one.

ACCOUNT__N__SPLIT_MODE controls how the split tracks are delivered:

  • tracks (default): all tracks are written into a single file.
  • files: each track is uploaded as its own file, with suffixed filenames (track-1.geojson, track-2.geojson, …). Splitting into files does not by itself keep tracks separate in Dawarich, which re-segments the points by time gap; see Keeping split tracks separate in Dawarich.

Keeping split tracks separate in Dawarich

Dawarich rebuilds tracks from the uploaded points by splitting on time gaps, so split legs that are close in time get merged back together. Set TARGET__DAWARICH__EMIT_TRACKER_ID=true to tag each track with a stable tracker_id; Dawarich groups points into tracks by that id and keeps the split legs separate. This works in either SPLIT_MODE, and regardless of the uploaded file format: the Dawarich target always forwards points as GeoJSON, so the tracker_id is what keeps the legs apart.

Supported targets

TypeServiceAccepted formats
dawarichDawarichgpx_1.1, geojson

Adding a new target requires implementing the Target interface in server/internal/target/.

Supported devices

TypeDeviceSupported formats
columbus-p10-proColumbus P-10 Progpx_1.1, columbus-csv

Adding a new device type requires implementing the Device interface in tracksync/internal/device/.

Development

Nix

This repository provides a flake.nix with a devshell for development.

Enter the repository and run direnv allow or use nix develop to start the devshell.

Other OS

Make sure the following is installed:

  • Go
  • Just

General

# Run server locally
just run-server
# Run client
just run-client --server-url http://localhost:8080 --token test-token \
--device-type columbus-p10-pro --device-id my-columbus --mount-point /mnt/gps
# Update nix vendor hash after changing Go dependencies
just update-vendor-hash

About

Automatically syncs track files from GPS loggers to self-hosted location tracking services

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages