diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 79a16af..05ad1f8 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -7,8 +7,19 @@ on:
jobs:
build:
- runs-on: ubuntu-latest
+ runs-on: ${{ matrix.runner }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - platform: linux/amd64
+ runner: ubuntu-latest
+ - platform: linux/arm64
+ runner: ubuntu-24.04-arm
steps:
+ - name: Prepare platform tag-safe name
+ run: echo "PLATFORM_PAIR=$(echo ${{ matrix.platform }} | sed 's|/|-|g')" >> $GITHUB_ENV
+
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
@@ -18,8 +29,63 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- - uses: docker/build-push-action@v6
+ - name: Build and push by digest
+ id: build
+ uses: docker/build-push-action@v6
with:
context: .
- push: true
- tags: mauriceboe/nomad:latest
+ platforms: ${{ matrix.platform }}
+ outputs: type=image,name=mauriceboe/trek,push-by-digest=true,name-canonical=true,push=true
+ no-cache: true
+
+ - name: Export digest
+ run: |
+ mkdir -p /tmp/digests
+ digest="${{ steps.build.outputs.digest }}"
+ touch "/tmp/digests/${digest#sha256:}"
+
+ - name: Upload digest artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: digests-${{ env.PLATFORM_PAIR }}
+ path: /tmp/digests/*
+ if-no-files-found: error
+ retention-days: 1
+
+ merge:
+ runs-on: ubuntu-latest
+ needs: build
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Get version from package.json
+ id: version
+ run: echo "VERSION=$(node -p "require('./server/package.json').version")" >> $GITHUB_OUTPUT
+
+ - name: Download build digests
+ uses: actions/download-artifact@v4
+ with:
+ path: /tmp/digests
+ pattern: digests-*
+ merge-multiple: true
+
+ - uses: docker/setup-buildx-action@v3
+
+ - uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Create and push multi-arch manifest
+ working-directory: /tmp/digests
+ run: |
+ mapfile -t digests < <(printf 'mauriceboe/trek@sha256:%s\n' *)
+ docker buildx imagetools create \
+ -t mauriceboe/trek:latest \
+ -t mauriceboe/trek:${{ steps.version.outputs.VERSION }} \
+ -t mauriceboe/nomad:latest \
+ -t mauriceboe/nomad:${{ steps.version.outputs.VERSION }} \
+ "${digests[@]}"
+
+ - name: Inspect manifest
+ run: docker buildx imagetools inspect mauriceboe/trek:latest
diff --git a/README.md b/README.md
index 056804f..c5954f1 100644
--- a/README.md
+++ b/README.md
@@ -2,26 +2,26 @@
+
- Navigation Organizer for Maps, Activities & Destinations
+ Your Trips. Your Plan.
A self-hosted, real-time collaborative travel planner with interactive maps, budgets, packing lists, and more.
- Live Demo — Try NOMAD without installing. Resets hourly.
+ Live Demo — Try TREK without installing. Resets hourly.
- {t('admin.addons.subtitleBefore')}{t('admin.addons.subtitleAfter')}
+ {t('admin.addons.subtitleBefore')}
{t('admin.addons.subtitleAfter')}
{addon.description}
+{label.description}
{t('admin.github.error')}
-{error}
-{t('admin.github.error')}
+{error}
+{getTime(z.tz)}
+{getTime(z.tz, locale)}
{z.label} {getOffset(z.tz)}
{file.description}
+ )} + +- {files.length === 1 ? t('files.countSingular') : t('files.count', { count: files.length })} + {showTrash + ? `${trashFiles.length} ${trashFiles.length === 1 ? 'file' : 'files'}` + : (files.length === 1 ? t('files.countSingular') : t('files.count', { count: files.length }))}
{t('files.trashEmpty') || 'Trash is empty'}
+{t('files.dropzone')}
+{t('files.dropzoneHint')}
++ {(allowedFileTypes || 'jpg,jpeg,png,gif,webp,heic,pdf,doc,docx,xls,xlsx,txt,csv').toUpperCase().split(',').join(', ')} · Max 50 MB +
+ > + )}{t('files.dropzone')}
-{t('files.dropzoneHint')}
-- {(allowedFileTypes || 'jpg,jpeg,png,gif,webp,heic,pdf,doc,docx,xls,xlsx,txt,csv').toUpperCase().split(',').join(', ')} · Max 50 MB -
- > - )} -{t('files.empty')}
-{t('files.emptyHint')}
+ {/* Filter tabs */} +{file.description}
- )} -{t('files.empty')}
+{t('files.emptyHint')}
+{t.whatIsDesc}
@@ -213,7 +245,7 @@ export default function DemoBanner(): React.ReactElement | null {- {photos.length} Foto{photos.length !== 1 ? 's' : ''} + {photos.length} {photos.length !== 1 ? 'Fotos' : 'Foto'}
- {place.description || place.notes} + {place.description || place.notes || googleDetails?.summary}
{toast.message}
-{t('admin.oidcOnlyMode')}
+{t('admin.oidcOnlyModeHint')}
+{t('login.tagline')}
{t('login.tagline')}
{t('login.oidcOnly')}
+ {error && ( +{t('vacay.subtitle')}