Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/api/src/privacy/__tests__/dawarich-source-part.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import {

const tuple = {
version: 1,
image: "freikin/dawarich:1.15.3",
imageDigest: "sha256:589e3606b11b61c3fa2d9661832cb73645a9b93a9cc7d807a071e2bc6c584498",
upstreamCommit: "e32b70701b2467b862d4b5bc27ac428999997c1b",
image: "freikin/dawarich:1.19.0",
imageDigest: "sha256:d9385242202ae385a5d8064f41a59a0b58218399e60e7e499d2a6afb449bc304",
upstreamCommit: "752641768f37fdc07d5a23e0559fa7c5ff8fae0f",
subjectUserIdDigest: "a".repeat(64),
cutoff: "2026-01-01T00:00:00Z",
snapshotAt: "2026-01-01T00:00:00Z",
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/privacy/backup-source-part.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function fixture(
const dawarichManifest = Buffer.from(
JSON.stringify({
version: 1,
image: "freikin/dawarich:1.15.3",
image: "freikin/dawarich:1.19.0",
imageDigest: DAWARICH_SUPPORTED_IMAGE_DIGEST,
upstreamCommit: DAWARICH_SUPPORTED_COMMIT,
subjectUserIdDigest: digest(Buffer.from("subject-1")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function dawarichTar(subjectId: string, cutoff: string): Buffer {
const manifest = Buffer.from(
JSON.stringify({
version: 1,
image: "freikin/dawarich:1.15.3",
image: "freikin/dawarich:1.19.0",
imageDigest: DAWARICH_SUPPORTED_IMAGE_DIGEST,
upstreamCommit: DAWARICH_SUPPORTED_COMMIT,
subjectUserIdDigest: sha256(Buffer.from(subjectId)),
Expand Down Expand Up @@ -103,7 +103,7 @@ function backupTar(subjectId: string, cutoff: string): Buffer {
const dawarichManifest = Buffer.from(
JSON.stringify({
version: 1,
image: "freikin/dawarich:1.15.3",
image: "freikin/dawarich:1.19.0",
imageDigest: DAWARICH_SUPPORTED_IMAGE_DIGEST,
upstreamCommit: DAWARICH_SUPPORTED_COMMIT,
subjectUserIdDigest: sha256(Buffer.from(subjectId)),
Expand Down
12 changes: 6 additions & 6 deletions apps/api/src/routes/__tests__/admin-dawarich.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ const bundle = new Map([
{
manifest: {
id: "dawarich-app",
version: "1.15.3",
container: { image: "freikin/dawarich", tag: "1.15.3" },
version: "1.19.0",
container: { image: "freikin/dawarich", tag: "1.19.0" },
},
},
],
Expand All @@ -29,8 +29,8 @@ const bundle = new Map([
{
manifest: {
id: "dawarich-sidekiq",
version: "1.15.3",
container: { image: "freikin/dawarich", tag: "1.15.3" },
version: "1.19.0",
container: { image: "freikin/dawarich", tag: "1.19.0" },
},
},
],
Expand All @@ -49,8 +49,8 @@ const bundle = new Map([
{
manifest: {
id: "dawarich-redis",
version: "7.4",
container: { image: "redis", tag: "7.4-alpine" },
version: "8.10",
container: { image: "redis", tag: "8.10-alpine" },
},
},
],
Expand Down
6 changes: 3 additions & 3 deletions apps/api/src/routes/__tests__/admin-services-sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ const service = {
},
};
const bundle = new Map([
["dawarich-app", ["1.15.3", "freikin/dawarich", "1.15.3"]],
["dawarich-sidekiq", ["1.15.3", "freikin/dawarich", "1.15.3"]],
["dawarich-app", ["1.19.0", "freikin/dawarich", "1.19.0"]],
["dawarich-sidekiq", ["1.19.0", "freikin/dawarich", "1.19.0"]],
["dawarich-postgis", ["18-3.6", "ghcr.io/baosystems/postgis", "18-3.6"]],
["dawarich-redis", ["7.4", "redis", "7.4-alpine"]],
["dawarich-redis", ["8.10", "redis", "8.10-alpine"]],
]);
const getService = vi.fn((id: string) => {
const item = bundle.get(id);
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/routes/__tests__/admin-services.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ describe("service credentials", () => {
manifest: {
id: serviceId,
name: serviceId,
version: "1.15.3",
version: "1.19.0",
quality: "built-in",
configSchema: {
properties: {
Expand Down
12 changes: 6 additions & 6 deletions apps/api/src/routes/admin-dawarich.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ const ROTATION_CONFIRMATION = "ROTATE DAWARICH OIDC SECRET";
const EXPECTED_BUNDLE = [
{
id: DAWARICH_APP_SERVICE_ID,
version: "1.15.3",
version: "1.19.0",
image: "freikin/dawarich",
tag: "1.15.3",
tag: "1.19.0",
},
{
id: DAWARICH_WORKER_SERVICE_ID,
version: "1.15.3",
version: "1.19.0",
image: "freikin/dawarich",
tag: "1.15.3",
tag: "1.19.0",
},
{
id: DAWARICH_POSTGIS_SERVICE_ID,
Expand All @@ -40,9 +40,9 @@ const EXPECTED_BUNDLE = [
},
{
id: DAWARICH_REDIS_SERVICE_ID,
version: "7.4",
version: "8.10",
image: "redis",
tag: "7.4-alpine",
tag: "8.10-alpine",
},
] as const;

Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ const managedDawarichState = () => {
managed.manifest.subjectData?.strategy === "collector" &&
managed.manifest.subjectData.registrationIds?.includes("managed-dawarich") &&
managed.manifest.container.image === DAWARICH_SUPPORTED_IMAGE.split(":")[0] &&
managed.manifest.container.tag === "1.15.3" &&
managed.manifest.container.tag === "1.19.0" &&
managed.manifest.container.digest === DAWARICH_SUPPORTED_IMAGE_DIGEST &&
managed.manifest.container.environment?.OPENMAPX_DAWARICH_COMMIT ===
DAWARICH_SUPPORTED_COMMIT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function readyClient(overrides: Partial<ManagedOAuthClient> = {}): ManagedOAuthC
client_name: "OpenMapX Managed Dawarich",
client_uri: "https://timeline.example.test",
software_id: DAWARICH_SOFTWARE_ID,
software_version: "1.15.3",
software_version: "1.19.0",
reference_id: MANAGED_REFERENCE_ID,
redirect_uris: ["https://timeline.example.test/users/auth/openid_connect/callback"],
token_endpoint_auth_method: "client_secret_basic",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function baseClient(overrides: Partial<ManagedOAuthClient> = {}): ManagedOAuthCl
client_name: "OpenMapX Managed Dawarich",
client_uri: "https://timeline.example.test",
software_id: DAWARICH_SOFTWARE_ID,
software_version: "1.15.3",
software_version: "1.19.0",
reference_id: MANAGED_REFERENCE_ID,
redirect_uris: ["https://timeline.example.test/users/auth/openid_connect/callback"],
token_endpoint_auth_method: "client_secret_basic",
Expand Down Expand Up @@ -222,7 +222,7 @@ describe("provisionManagedDawarich", () => {
client_name: "OpenMapX Managed Dawarich",
client_uri: "https://timeline.example.test",
software_id: DAWARICH_SOFTWARE_ID,
software_version: "1.15.3",
software_version: "1.19.0",
redirect_uris: ["https://timeline.example.test/users/auth/openid_connect/callback"],
token_endpoint_auth_method: "client_secret_basic",
grant_types: ["authorization_code"],
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/services/dawarich/provisioning-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const DAWARICH_APP_SERVICE_ID = "dawarich-app";
export const DAWARICH_WORKER_SERVICE_ID = "dawarich-sidekiq";
export const DAWARICH_POSTGIS_SERVICE_ID = "dawarich-postgis";
export const DAWARICH_REDIS_SERVICE_ID = "dawarich-redis";
export const DAWARICH_VERSION = "1.15.3";
export const DAWARICH_VERSION = "1.19.0";
export const DAWARICH_PROVISIONING_GENERATION_KEY = "OPENMAPX_PROVISIONING_GENERATION";
export const DAWARICH_OIDC_RECOVERY_REQUIRED_KEY = "OPENMAPX_OIDC_RECOVERY_REQUIRED";

Expand Down
8 changes: 4 additions & 4 deletions apps/ops-agent/src/administrative-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,12 +440,12 @@ function parseManifest(rootDir: string, backupId: string): ParsedBackupManifest
const managed = provenance?.managedDawarich;
if (
managed &&
(managed.version !== "1.15.3" ||
(managed.version !== "1.19.0" ||
managed.image !== "freikin/dawarich" ||
managed.imageDigest !==
"sha256:589e3606b11b61c3fa2d9661832cb73645a9b93a9cc7d807a071e2bc6c584498" ||
managed.upstreamCommit !== "e32b70701b2467b862d4b5bc27ac428999997c1b" ||
managed.schemaContract !== "dawarich-1.15.3")
"sha256:d9385242202ae385a5d8064f41a59a0b58218399e60e7e499d2a6afb449bc304" ||
managed.upstreamCommit !== "752641768f37fdc07d5a23e0559fa7c5ff8fae0f" ||
managed.schemaContract !== "dawarich-1.19.0")
)
throw new Error("Backup authority rejected");
return {
Expand Down
80 changes: 80 additions & 0 deletions apps/ops-agent/src/dawarich-subject-export.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,22 @@ VALUES (1,1,1,'Subject unplanned place',TIMESTAMP '${at}',TIMESTAMP '${at}'),
INSERT INTO point_sources (id,digest,tracker_id,topic,created_at,updated_at)
VALUES (1,'subject-source-digest','subject-dimension-device','subject/topic',TIMESTAMP '${at}',TIMESTAMP '${at}');
UPDATE points SET source_id=1, tracker_id=NULL, topic=NULL WHERE id=1000;
INSERT INTO geofence_events (id,user_id,place_id,place_name,event_type,source,synthetic,occurred_at,received_at,lonlat,accuracy_m,device_id,metadata,created_at,updated_at)
VALUES (1,1,1,'Subject geofence place',0,0,false,TIMESTAMP '${at}',TIMESTAMP '${at}',ST_GeogFromText('POINT(13.4 52.5)'),12,'subject-geofence-device',${sql('{"note":"Subject geofence metadata","auth_token":"GEOFENCE_SECRET"}')}::jsonb,TIMESTAMP '${at}',TIMESTAMP '${at}'),
(2,2,2,'FOREIGN_SENTINEL',1,1,false,TIMESTAMP '${at}',TIMESTAMP '${at}',ST_GeogFromText('POINT(1 1)'),5,'FOREIGN_SENTINEL','{}'::jsonb,TIMESTAMP '${at}',TIMESTAMP '${at}');
INSERT INTO webhooks (id,user_id,name,url,secret,event_types,place_ids,active,consecutive_failures,created_at,updated_at)
VALUES (1,1,'Subject webhook','https://hooks.subject.test/api/webhook/WEBHOOK_PATH_TOKEN?token=WEBHOOK_QUERY_TOKEN','WEBHOOK_SIGNING_SECRET','{0,1}','{1}',true,0,TIMESTAMP '${at}',TIMESTAMP '${at}'),
(2,2,'FOREIGN_SENTINEL','https://foreign.test/FOREIGN_SENTINEL','FOREIGN_SECRET','{0}','{}',true,0,TIMESTAMP '${at}',TIMESTAMP '${at}'),
(3,1,'Malformed webhook','https://user:pa@WEBHOOK_SPLIT_TOKEN@hooks.subject.test/x','WEBHOOK_SIGNING_SECRET','{0}','{}',false,0,TIMESTAMP '${at}',TIMESTAMP '${at}');
INSERT INTO webhook_deliveries (id,webhook_id,geofence_event_id,status,attempt_count,response_status,response_body,error_message,delivered_at,created_at,updated_at)
VALUES (1,1,1,1,1,200,'DELIVERY_RESPONSE_SECRET','DELIVERY_ERROR_SECRET',TIMESTAMP '${at}',TIMESTAMP '${at}',TIMESTAMP '${at}'),
(2,2,2,2,3,500,'FOREIGN_SENTINEL','FOREIGN_SENTINEL',NULL,TIMESTAMP '${at}',TIMESTAMP '${at}');
INSERT INTO legacy_area_place_mappings (id,area_id,place_id,created_at,updated_at)
VALUES (1,1,1,TIMESTAMP '${at}',TIMESTAMP '${at}'), (2,2,2,TIMESTAMP '${at}',TIMESTAMP '${at}');
INSERT INTO point_revisions (id,user_id,month,revision)
VALUES (9001,1,DATE '2025-06-01','11111111-1111-4111-8111-111111111111'), (9002,2,DATE '2025-06-01','22222222-2222-4222-8222-222222222222');
INSERT INTO track_revisions (id,user_id,month,revision)
VALUES (9001,1,DATE '2025-06-01','33333333-3333-4333-8333-333333333333'), (9002,2,DATE '2025-06-01','44444444-4444-4444-8444-444444444444');
COMMIT;
`;
}
Expand Down Expand Up @@ -453,6 +469,12 @@ describe.skipIf(!runExactImage)("managed Dawarich exact-image export", () => {
"route-videos",
"service-settings",
"trip-sources",
"geofence-events",
"webhooks",
"webhook-deliveries",
"legacy-area-place-mappings",
"point-revisions",
"track-revisions",
"planned-days",
"planned-day-notes",
"planned-reservations",
Expand Down Expand Up @@ -498,9 +520,67 @@ describe.skipIf(!runExactImage)("managed Dawarich exact-image export", () => {
expect(plaintext).not.toContain("SERVICE_CREDENTIAL_SECRET");
expect(plaintext).not.toContain("TRIP_API_KEY_SECRET");
expect(plaintext).not.toContain("TRIP_SELECTION_SECRET");
expect(plaintext).toContain("Subject geofence place");
expect(plaintext).toContain("subject-geofence-device");
expect(plaintext).toContain("11111111-1111-4111-8111-111111111111");
expect(plaintext).toContain("33333333-3333-4333-8333-333333333333");
for (const secret of [
"GEOFENCE_SECRET",
"WEBHOOK_PATH_TOKEN",
"WEBHOOK_QUERY_TOKEN",
"WEBHOOK_SPLIT_TOKEN",
"WEBHOOK_SIGNING_SECRET",
"DELIVERY_RESPONSE_SECRET",
"DELIVERY_ERROR_SECRET",
"22222222-2222-4222-8222-222222222222",
"44444444-4444-4444-8444-444444444444",
]) {
expect(plaintext, secret).not.toContain(secret);
}
const webhookRows = entries
.find((entry) => entry.id === "webhooks")
?.content.toString("utf8")
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line) as Record<string, unknown>);
expect(webhookRows).toHaveLength(2);
expect(webhookRows?.[0]?.url).toBe("https://hooks.subject.test");
expect(webhookRows?.[1]?.url).toBeNull();
const geofenceRows = entries
.find((entry) => entry.id === "geofence-events")
?.content.toString("utf8")
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line) as Record<string, unknown>);
expect(geofenceRows).toHaveLength(1);
expect(geofenceRows?.[0]).toMatchObject({
event_type: "enter",
source: "native_app",
lonlat: [13.4, 52.5],
});
expect(plaintext).not.toContain("aaaaaaaa-0000-4000-8000-000000000001");
expect(plaintext).not.toContain("bbbbbbbb-0000-4000-8000-000000000002");
expect(plaintext).not.toContain("other-family-member@example.test");

// An owner column without a constraint is invisible to the FK graph; the
// reviewed table inventory must still refuse the unreviewed relation.
await runPsql(
database,
"CREATE TABLE openmapx_unreviewed_owner (id bigserial PRIMARY KEY, user_id bigint);",
);
const refused = await command(
"docker",
imageArgs(database, password, storageDirectory, [
"ruby",
"/opt/openmapx/openmapx-subject-export.rb",
]),
{ input: request },
);
expect(refused.code).not.toBe(0);
expect(refused.stdout.length).toBe(0);
expect(refused.stderr.toString("utf8")).toContain(
"openmapx_subject_export:unsupported_schema",
);
} finally {
await command("docker", ["rm", "--force", "--volumes", DATABASE_CONTAINER]);
if (existsSync(storageDirectory)) rmSync(storageDirectory, { recursive: true, force: true });
Expand Down
8 changes: 4 additions & 4 deletions apps/ops-agent/src/privacy-backup-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,11 @@ describe("privacy backup extraction runtime", () => {
openmapxVersion: "1.0.0",
privacySourceProvenance: {
managedDawarich: {
version: "1.15.3",
version: "1.19.0",
image: "freikin/dawarich",
imageDigest: `sha256:${"d".repeat(64)}`,
upstreamCommit: "c".repeat(40),
schemaContract: "dawarich-1.15.3",
schemaContract: "dawarich-1.19.0",
},
},
},
Expand Down Expand Up @@ -183,8 +183,8 @@ describe("privacy backup extraction runtime", () => {
for await (const _chunk of output) void _chunk;
expect(JSON.parse(body)).toMatchObject({
sources: [
{ family: "dawarich", schemaContract: "dawarich-1.15.3" },
{ family: "dawarich-storage", schemaContract: "dawarich-storage-1.15.3" },
{ family: "dawarich", schemaContract: "dawarich-1.19.0" },
{ family: "dawarich-storage", schemaContract: "dawarich-storage-1.19.0" },
],
});
});
Expand Down
2 changes: 1 addition & 1 deletion apps/ops-agent/src/privacy-backup-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ export function createPrivacyBackupExtractionRuntime(options: PrivacyBackupRunti
input.serviceId === "postgis"
? "openmapx-v1"
: input.serviceId === "dawarich-app"
? "dawarich-storage-1.15.3"
? "dawarich-storage-1.19.0"
: verified.manifest.privacySourceProvenance?.managedDawarich?.schemaContract,
}));
if (sources.length < 1 || sources.some((source) => !source.schemaContract))
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/features/personal-timeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The two credentials have different jobs:
email. Dawarich maintains a separate browser session; signing out of OpenMapX
is not single logout.
- The personal Dawarich API key authorizes OpenMapX's server-side, read-only API
calls. Dawarich 1.15.3 does not let the OIDC browser session or Better Auth
calls. Dawarich 1.19.0 does not let the OIDC browser session or Better Auth
token replace this key.

If a pre-existing Dawarich account has the same email, Dawarich requires its
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/guides/dawarich.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ the separate Dawarich browser session.

| Service | Role | Default memory ceiling |
| ------------------ | ----------------------------------- | ---------------------- |
| `dawarich-app` | Dawarich 1.15.3 web app | 4 GiB |
| `dawarich-app` | Dawarich 1.19.0 web app | 4 GiB |
| `dawarich-sidekiq` | Background worker, concurrency 3 | 2 GiB |
| `dawarich-postgis` | Dedicated PostgreSQL 18/PostGIS 3.6 | 2 GiB |
| `dawarich-redis` | Dedicated Redis 7.4 queue/cache | 512 MiB |
| `dawarich-redis` | Dedicated Redis 8.10 queue/cache | 512 MiB |

The app and worker use the same digest-pinned Dawarich image. Their database,
Redis, credentials, cookies, and volumes are separate from the OpenMapX platform
Expand Down Expand Up @@ -253,7 +253,7 @@ available. Wait before retrying rather than repeatedly reconnecting the same key
| `TIMELINE_PLAN_RESTRICTED` | Check whether the source account permits the required API access. |
| `TIMELINE_RATE_LIMITED` | Wait for the retry interval. |
| `TIMELINE_UPSTREAM_UNAVAILABLE` | Inspect container/network health and source availability. |
| `TIMELINE_RESPONSE_INVALID` | Check API compatibility with the managed 1.15.3 baseline and bounded response limits. |
| `TIMELINE_RESPONSE_INVALID` | Check API compatibility with the managed 1.19.0 baseline and bounded response limits. |

```bash
pnpm openmapx services status dawarich-app
Expand Down
Loading
Loading