From f5876f29ae5476a76ae15947420fab5d1b0bc50c Mon Sep 17 00:00:00 2001 From: Emil Rossing Date: Mon, 31 Aug 2026 19:14:51 +0200 Subject: [PATCH 1/3] chore: docs update --- docs/development/architecture.md | 1 + docs/guides/deployment.md | 4 ++++ docs/guides/server-architecture-for-it.md | 3 +++ docs/reference/formulus.md | 9 ++++++++- docs/reference/synkronus-server.md | 14 ++++++++++++++ docs/using/formulus-features.md | 8 +++++--- docs/using/synchronization.md | 19 +++++++++++++++++-- docs/using/troubleshooting.md | 2 ++ 8 files changed, 54 insertions(+), 6 deletions(-) diff --git a/docs/development/architecture.md b/docs/development/architecture.md index c68b7ed..5fdf2d0 100644 --- a/docs/development/architecture.md +++ b/docs/development/architecture.md @@ -223,6 +223,7 @@ See [Security reference](/docs/reference/security) for deployment checklist and - **Local Database**: Fast queries using WatermelonDB - **Incremental Sync**: Only sync changes since last sync +- **Adaptive pages**: Formulus starts at 32 pull / 4 push, grows toward 500 / 100, floor 1 - **Lazy Loading**: Load attachments on demand - **Caching**: Cache app bundles and form specifications diff --git a/docs/guides/deployment.md b/docs/guides/deployment.md index 2b4cb32..7e1e96e 100644 --- a/docs/guides/deployment.md +++ b/docs/guides/deployment.md @@ -432,6 +432,10 @@ sudo ufw enable ## Performance Tuning +### Reverse proxy timeouts + +Field sync, photo upload, and app-bundle download can run for minutes on slow radio. The bundled [`nginx.conf`](https://github.com/OpenDataEnsemble/ode/blob/main/synkronus/nginx.conf) sets `proxy_send_timeout` and `proxy_read_timeout` to **600s**. If you use Caddy, Apache, or an institutional load balancer, set equivalent send/read (or idle) timeouts to at least 10 minutes. Leave login/refresh on the default short path — Synkronus already bounds `/api/auth/*` at 25s. + ### PostgreSQL Optimization Add to `docker-compose.yml` under postgres service: diff --git a/docs/guides/server-architecture-for-it.md b/docs/guides/server-architecture-for-it.md index 7f523ce..ea4b27d 100644 --- a/docs/guides/server-architecture-for-it.md +++ b/docs/guides/server-architecture-for-it.md @@ -112,6 +112,8 @@ Details: [Installing Formulus](/docs/getting-started/installation/installing-for Synkronus limits attachment uploads to **32 MB** per file. Configure your reverse proxy body size limit to at least 32 MB. +**Proxy timeouts:** field devices on slow radio can take minutes for a pull, push, photo, or app-bundle zip. Set reverse-proxy send/read timeouts to at least **600 seconds** (the bundled `nginx.conf` uses `proxy_send_timeout` / `proxy_read_timeout 600s`). Default nginx/Caddy idle values (~60s) will drop those transfers. Login and token refresh are bounded at **25 seconds** inside Synkronus; do not apply that short deadline to sync routes. + ## Reference deployment pattern Typical self-hosted pattern (e.g. research institutions running custom apps like AnthroCollect): @@ -129,6 +131,7 @@ Coordinate **Formulus and Synkronus versions** on upgrade—the mobile app check - [ ] Hardened reverse proxy with TLS (TLS 1.2+) - [ ] Pin Synkronus image tag (e.g. `v1.3.0`) rather than `:latest` in production - [ ] Proxy upload limit ≥ 32 MB per attachment +- [ ] Proxy send/read timeouts ≥ 600s (sync, attachments, bundle zip) - [ ] Automated Postgres backups + tested restore - [ ] Backup `appdata` volume (attachments + bundles) - [ ] Volume/disk encryption at platform level diff --git a/docs/reference/formulus.md b/docs/reference/formulus.md index 70cfcd1..83fb7d1 100644 --- a/docs/reference/formulus.md +++ b/docs/reference/formulus.md @@ -34,6 +34,7 @@ formulus/ │ ├── navigation/ # Navigation configuration │ ├── screens/ # Screen components │ ├── services/ # Business logic services +│ ├── sync/ # Adaptive pull/push sizes, retries │ ├── webview/ # WebView integration and bridge │ └── utils/ # Utility functions ├── android/ # Android native code @@ -68,6 +69,8 @@ Two-phase synchronization protocol: 1. **Observation Sync**: JSON metadata synchronization 2. **Attachment Sync**: Binary file synchronization +Pull and push unit sizes are **adaptive** (no enumerator preset). Fresh devices start at **32** observations per pull page and **4** per push batch, grow toward 500 / 100 on a fast link, and can shrink to **1** on very poor radio. Attachment downloads are serial. See [How Formulus sizes each request](/docs/using/synchronization#how-formulus-sizes-each-request). + ### Form Rendering Integration with Formplayer for form rendering: @@ -276,13 +279,17 @@ When opening forms programmatically, `openFormplayer` accepts: 3. **Push**: Send local changes to server 4. **Resolve Conflicts**: Handle conflicts if any +Page and batch sizes AIMD from conservative starts (pull **32**, push **4**) between floor **1** and ceiling **500** / **100**. Constants: `formulus/src/sync/networkProfile.ts`. Axios JSON timeout is 10 minutes. + #### Phase 2: Attachment Sync 1. **Download Manifest**: Get list of attachments to download -2. **Download Files**: Download missing attachments +2. **Download Files**: Download missing attachments (concurrency 1) 3. **Upload Files**: Upload pending attachments 4. **Update Status**: Mark attachments as synced +Observation JSON can complete while attachment files remain pending. + ### Sync State Management The app maintains sync state: diff --git a/docs/reference/synkronus-server.md b/docs/reference/synkronus-server.md index 9343c0e..be251ea 100644 --- a/docs/reference/synkronus-server.md +++ b/docs/reference/synkronus-server.md @@ -34,6 +34,19 @@ Images are published on [GitHub Container Registry](https://github.com/OpenDataE - **API**: RESTful HTTP API - **Documentation**: OpenAPI 3.0 specification +### HTTP timeouts + +Go `ReadTimeout` / `WriteTimeout` are **unset**. Those clocks start when the request begins, so a short cap (historically 15s) aborted legitimate sync, photo, and app-bundle transfers on slow radio. + +| Bound | Duration | Scope | +|-------|----------|--------| +| Request headers (`ReadHeaderTimeout`) | 25s | Slowloris protection only | +| Login / refresh | 25s | `http.TimeoutHandler` on `/api/auth/*` only | +| Keep-alive idle | 60s | Between requests | +| Reverse proxy send/read | 600s | Reference `nginx.conf` in the Synkronus tree | + +Do not wrap `/sync`, attachments, or bundle download in a short handler timeout. Pull `limit` default is 50, maximum 500 (OpenAPI). Formulus requests smaller pages first — see [Synchronization — How Formulus sizes each request](/docs/using/synchronization#how-formulus-sizes-each-request). + ### Project Structure ``` @@ -49,6 +62,7 @@ synkronus/ ├── auth/ # Authentication utilities ├── database/ # Database connection and migrations ├── logger/ # Structured logging + ├── httptimeout/ # ReadHeaderTimeout, auth TimeoutHandler ├── middleware/ # HTTP middleware └── openapi/ # OpenAPI generated code ``` diff --git a/docs/using/formulus-features.md b/docs/using/formulus-features.md index 5243f5b..ec621da 100644 --- a/docs/using/formulus-features.md +++ b/docs/using/formulus-features.md @@ -212,11 +212,13 @@ The app automatically syncs when: ### Sync Process -1. **Pull**: Download new forms and server data -2. **Push**: Upload pending observations -3. **Attachments**: Upload photos, audio, and other files +1. **Pull**: Download new forms and server data (starts at 32 observations per page) +2. **Push**: Upload pending observations (starts at 4 per batch) +3. **Attachments**: Upload photos, audio, and other files (one at a time) 4. **Confirmation**: Server acknowledges receipt +Observation records can finish syncing while photos are still transferring. Page sizes grow on a good link and shrink on a slow one; there is no Settings control for this. + ### Sync Indicators - **Sync Icon**: Appears in status bar when syncing diff --git a/docs/using/synchronization.md b/docs/using/synchronization.md index a6a08d3..5f82b67 100644 --- a/docs/using/synchronization.md +++ b/docs/using/synchronization.md @@ -46,7 +46,7 @@ Syncs observation records (forms) and their metadata: - Client updates its last seen `change_id` 2. **Push Phase** - Send local observations to server - - Client sends all unsync ed observations + - Client sends unsynced observations in small batches - Each observation includes a transmission ID for idempotency - Server validates and stores observations - Server returns success/failure for each record @@ -74,6 +74,21 @@ Syncs binary files attached to observations (photos, audio, documents): - Once successful, remove from local pending queue - Mark as synced +## How Formulus sizes each request + +Formulus does not expose a network-quality setting. Every device starts with **small** pages so a first attempt can finish on slow radio, then grows on a good link: + +| Direction | First attempt | Smallest (poor radio) | Largest (good link) | +|-----------|---------------|------------------------|---------------------| +| Pull (download observations) | 32 | 1 | 500 | +| Push (upload observations) | 4 | 1 | 100 | + +- Fast pages grow; slow or failed pages shrink (halve), down to one observation at a time. +- Photos and other attachments download **one at a time**. Observation JSON can finish while photos are still pending. +- There is nothing to configure in Settings for this. + +A reverse proxy in front of Synkronus must allow long transfers (about **10 minutes**). See [Server Architecture for IT](/docs/guides/server-architecture-for-it). + ## Understanding the Sync Algorithm ### Change Detection with `change_id` @@ -271,7 +286,7 @@ A conflict occurs when: ## Sync Settings -Users can configure synchronization behavior: +Users can configure when sync runs (interval, Wi-Fi vs cellular). **Page and batch sizes are automatic** — there is no network-quality picker. See [How Formulus sizes each request](#how-formulus-sizes-each-request). ### Auto-Sync Interval diff --git a/docs/using/troubleshooting.md b/docs/using/troubleshooting.md index 744996b..bd6aa66 100644 --- a/docs/using/troubleshooting.md +++ b/docs/using/troubleshooting.md @@ -483,6 +483,8 @@ If you encounter errors not covered here: - Check server performance and load - Verify attachment sizes are reasonable - Consider syncing during off-peak hours +- On slow radio, Formulus starts with small pages (32 pulled / 4 pushed) and can drop to one observation at a time. Observation data can finish while photos are still downloading. +- Confirm the reverse proxy allows ~10 minute transfers (see [Server Architecture for IT](/docs/guides/server-architecture-for-it)) ## Getting Additional Help From 4362537217f9a565a8a1b625037026f071791c67 Mon Sep 17 00:00:00 2001 From: Emil Rossing Date: Mon, 31 Aug 2026 19:19:33 +0200 Subject: [PATCH 2/3] chore: add container image tag info --- docs/development/building-testing.md | 12 +++++--- docs/guides/deployment.md | 41 ++++++++++++++++++++++------ docs/reference/synkronus-server.md | 2 +- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/docs/development/building-testing.md b/docs/development/building-testing.md index f1cf732..b1dc7be 100644 --- a/docs/development/building-testing.md +++ b/docs/development/building-testing.md @@ -234,10 +234,14 @@ The project uses GitHub Actions for continuous integration: ### Workflows **Synkronus Docker Build:** -- Triggers on push to `main` or PRs affecting `synkronus/` -- Builds Docker image -- Publishes to GitHub Container Registry -- Tags: `latest`, `v{version}`, `{branch-name}` +- Builds on relevant pushes to `main` or `dev`, pull requests, published GitHub Releases, and manual dispatches +- Publishes multi-platform images to GitHub Container Registry (pull requests build without publishing) +- Stable releases publish `v{version}`, major/minor pointers, and `latest` +- Pre-releases publish `v{version}-{pre}` and `latest-pre-release` +- Branch pushes publish `main` or `dev` plus an immutable `sha-{short}` tag +- Manual dispatches publish only `sha-{short}`; feature-branch images are not published automatically + +See the [Deployment guide](/docs/guides/deployment) for the image-tag channels and recommended uses. **Frontend Quality Checks:** - Runs on all PRs diff --git a/docs/guides/deployment.md b/docs/guides/deployment.md index 7e1e96e..4e22ce9 100644 --- a/docs/guides/deployment.md +++ b/docs/guides/deployment.md @@ -160,20 +160,43 @@ services: Pre-built images are automatically published to GitHub Container Registry (GHCR) via CI/CD. -### Pull the Latest Image +### Choose an Image Tag + +Choose a deployment tag based on the update channel you want: + +| Tag | What it tracks | Recommended use | +|-----|----------------|-----------------| +| `latest` | Most recently published **stable** GitHub Release | Production deployments that intentionally auto-update between stable releases | +| `latest-pre-release` | Most recently published GitHub Release marked **pre-release** | Demo/staging deployments and Watchtower-managed pre-release testing | +| `dev` | Tip of the `dev` branch | Bleeding-edge integration testing; may contain unpublished work | +| `main` | Tip of the `main` branch | Testing current main between releases | +| `v1.2.3-alpha.4` | One specific pre-release | Reproducible pre-release deployment; does not auto-update | +| `v1.2.3` | One specific stable release | Reproducible production deployment; does not auto-update | +| `sha-abc1234` | One specific commit | Debugging or exact-build reproduction; does not auto-update | + +`dev` is a branch-head channel, **not** the published pre-release channel. To track published alpha or release-candidate images, use `latest-pre-release`: ```bash -docker pull ghcr.io/opendataensemble/synkronus:latest +docker pull ghcr.io/opendataensemble/synkronus:latest-pre-release +``` + +Versioned and moving release tags are produced only when a GitHub Release is **published**. Merely pushing a Git tag is not enough, and the release must be marked as a pre-release for `latest-pre-release` to move. Publishing a stable release updates `latest` but does not update `latest-pre-release`; there is no single tag that tracks the newest release regardless of whether it is stable or pre-release. + +Feature-branch images are not published automatically. A manually dispatched workflow run publishes only an immutable `sha-{short}` tag and does not move `latest`, `latest-pre-release`, `main`, or `dev`. + +### Automatic Updates with Watchtower + +Watchtower follows the tag configured on the running container. For a demo server that should receive each published pre-release, configure the Synkronus service with the moving pre-release tag: + +```yaml +services: + synkronus: + image: ghcr.io/opendataensemble/synkronus:latest-pre-release ``` -### Available Tags +Use `latest` instead to follow stable releases. Do not use a versioned tag such as `v1.2.3-alpha.4` if you expect automatic upgrades; versioned and `sha-*` tags identify fixed builds. -| Tag | Description | -|-----|-------------| -| `latest` | Latest stable release from main branch | -| `v1.0.0` | Specific version tags | -| `develop` | Development branch (pre-release) | -| `feature-xyz` | Feature branches (pre-release) | +For production, pin a tested version tag and perform controlled upgrades rather than relying on an automatically moving tag. ### Run Pre-built Image diff --git a/docs/reference/synkronus-server.md b/docs/reference/synkronus-server.md index be251ea..887fd93 100644 --- a/docs/reference/synkronus-server.md +++ b/docs/reference/synkronus-server.md @@ -22,7 +22,7 @@ Production deployments should pin a release tag rather than `:latest`: ghcr.io/opendataensemble/synkronus:v1.3.0 ``` -Images are published on [GitHub Container Registry](https://github.com/OpenDataEnsemble/ode/pkgs/container/synkronus) for each [ODE release](https://github.com/OpenDataEnsemble/ode/releases). +Images are published on [GitHub Container Registry](https://github.com/OpenDataEnsemble/ode/pkgs/container/synkronus) for each [ODE release](https://github.com/OpenDataEnsemble/ode/releases). See the [Deployment guide](/docs/guides/deployment) for stable, pre-release, and branch tracking channels. ## Architecture From 00991eb296c24fa63baf50cd4e11a0f73dca692e Mon Sep 17 00:00:00 2001 From: Emil Rossing Date: Tue, 1 Sep 2026 17:35:57 +0200 Subject: [PATCH 3/3] chore: release prep for v1.3.2 --- docs/collector/collector-getting-started.md | 32 +- docs/development/installing-formulus-dev.md | 12 +- docs/getting-started/architecture-overview.md | 2 +- docs/getting-started/faq.md | 2 +- docs/getting-started/installation.md | 7 +- .../installation/installing-formulus.md | 32 +- .../installation/installing-ode-desktop.md | 8 +- docs/guides/deployment.md | 2 +- docs/guides/server-architecture-for-it.md | 8 +- docs/reference/security.md | 6 +- docs/reference/synkronus-server.md | 2 +- docusaurus.config.ts | 9 + sidebars.ts | 5 + src/pages/downloads/index.tsx | 294 ++++++++++++++++++ src/pages/downloads/styles.module.css | 117 +++++++ 15 files changed, 482 insertions(+), 56 deletions(-) create mode 100644 src/pages/downloads/index.tsx create mode 100644 src/pages/downloads/styles.module.css diff --git a/docs/collector/collector-getting-started.md b/docs/collector/collector-getting-started.md index 8c280e4..841deb4 100644 --- a/docs/collector/collector-getting-started.md +++ b/docs/collector/collector-getting-started.md @@ -17,33 +17,25 @@ Before starting, make sure you have: ## Step 1: Install Formulus -### Option A: From Google Play Store (Android) +### Option A: F-Droid (Android) -1. Open **Google Play Store** on your Android phone -2. Search for **"Formulus"** -3. Tap **Install** -4. Wait for installation to complete (usually 1-2 minutes) +1. Open the [Formulus page on F-Droid](https://f-droid.org/en/packages/org.opendataensemble.formulus/) +2. Install the F-Droid client if prompted +3. Tap **Install** and wait for the download to complete -### Option B: From App Store (iOS) +### Option B: App Store (iPhone and iPad) -1. Open **App Store** on your iPhone -2. Search for **"Formulus"** -3. Tap **Get** -4. Authenticate with Face ID, Touch ID, or Apple ID -5. Wait for installation to complete +1. Open the [Formulus App Store page](https://apps.apple.com/dk/app/formulus/id6798318215) +2. Tap **Get** +3. Authenticate with Face ID, Touch ID, or Apple ID +4. Wait for installation to complete -### Option C: Direct Installation (Android) +### Option C: Obtainium or direct APK (Android) -If your project manager provided an APK file: - -1. Download the APK file to your phone -2. Open your file manager and locate the APK -3. Tap the file to install -4. If prompted, allow installation from "Unknown Sources" -5. Tap **Install** +Use [Obtainium](https://github.com/ImranR98/Obtainium) with `https://github.com/OpenDataEnsemble/ode` for updates from GitHub Releases, or download the APK directly from [Downloads](/downloads). :::note -If you don't have Google Play Store access or need a specific version, contact your project manager for a direct download link. +The [Downloads](/downloads) page has the current Android APK and all desktop/CLI downloads. ::: ## Step 2: Open Formulus & Connect to Your Project diff --git a/docs/development/installing-formulus-dev.md b/docs/development/installing-formulus-dev.md index 1be0fb4..8119c52 100644 --- a/docs/development/installing-formulus-dev.md +++ b/docs/development/installing-formulus-dev.md @@ -161,9 +161,9 @@ adb install app-debug.apk ```bash # Browse the release and download the arm64-v8a APK for most phones: -# https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.0 -# Asset names look like: formulus-v1.3.0-35-arm64-v8a-release-YYYYMMDD.apk -adb install /path/to/formulus-v1.3.0-*-arm64-v8a-release-*.apk +# https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.2 +# Asset names look like: formulus-v1.3.2-64-universal-release-YYYYMMDD.apk +adb install /path/to/formulus-v1.3.2-*-universal-release-*.apk ``` @@ -171,9 +171,9 @@ adb install /path/to/formulus-v1.3.0-*-arm64-v8a-release-*.apk ```powershell # Browse the release and download the arm64-v8a APK for most phones: -# https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.0 -# Asset names look like: formulus-v1.3.0-35-arm64-v8a-release-YYYYMMDD.apk -adb install "C:\path\to\formulus-v1.3.0-*-arm64-v8a-release-*.apk" +# https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.2 +# Asset names look like: formulus-v1.3.2-64-universal-release-YYYYMMDD.apk +adb install "C:\path\to\formulus-v1.3.2-*-universal-release-*.apk" ``` diff --git a/docs/getting-started/architecture-overview.md b/docs/getting-started/architecture-overview.md index 87c4bb5..944787e 100644 --- a/docs/getting-started/architecture-overview.md +++ b/docs/getting-started/architecture-overview.md @@ -6,7 +6,7 @@ sidebar_position: 1 ODE (Open Data Ensemble) is a comprehensive platform for mobile data collection and synchronization. This guide explains the core architecture and components. -> **Current ODE release:** [v1.3.0](https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.0) +> **Current ODE release:** [v1.3.2](https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.2) · [Downloads](/downloads) ## Core Components diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md index 8f9169c..0e7bba6 100644 --- a/docs/getting-started/faq.md +++ b/docs/getting-started/faq.md @@ -6,7 +6,7 @@ sidebar_position: 5 Common questions about ODE installation, usage, and development. -> **Current ODE release:** [v1.3.0](https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.0) (Synkronus container, Formulus APK, Desktop, Portal) +> **Current ODE release:** [v1.3.2](https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.2) (Synkronus container, Formulus, Desktop, CLI, Portal) · [Downloads](/downloads) ## General Questions diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index abff6ca..d9fd4ba 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -11,15 +11,16 @@ To run ODE you need two things: a **server** (Synkronus) that stores and syncs d | Component | What it is | Guide | |-----------|------------|--------| | **Server (Synkronus)** | Backend that hosts the API, portal, and database. Runs on a Linux server or VPS. | [Install Synkronus](installation/installing-synkronus) | -| **Client (Formulus)** | Mobile app for Android that field workers use to fill forms and sync data. | [Install Formulus](installation/installing-formulus) | +| **Client (Formulus)** | Mobile app for Android and iOS that field workers use to fill forms and sync data. | [Install Formulus](installation/installing-formulus) | Install the server first so that the client has something to connect to. Then install Formulus (or your client app) on each device and point it at your Synkronus server. ## For IT / infrastructure teams -Hosting Synkronus for a study? See **[Server Architecture for IT](/docs/guides/server-architecture-for-it)** for a one-page overview: container layout, TLS, backups, and how custom apps (app bundles) relate to the server. Current platform release: **v1.3.0**. +Hosting Synkronus for a study? See **[Server Architecture for IT](/docs/guides/server-architecture-for-it)** for a one-page overview: container layout, TLS, backups, and how custom apps (app bundles) relate to the server. Current platform release: **v1.3.2**. ## Next steps - **[Install Synkronus](installation/installing-synkronus)** — Set up the server on a Linux machine or VPS. -- **[Install Formulus](installation/installing-formulus)** — Put the Formulus app on Android devices and connect it to your server. +- **[Downloads](/downloads)** — Get Formulus, ODE Desktop, or the Synkronus CLI for your platform. +- **[Install Formulus](installation/installing-formulus)** — Put the Formulus app on Android or iOS devices and connect it to your server. diff --git a/docs/getting-started/installation/installing-formulus.md b/docs/getting-started/installation/installing-formulus.md index 50af4a8..be08f45 100644 --- a/docs/getting-started/installation/installing-formulus.md +++ b/docs/getting-started/installation/installing-formulus.md @@ -4,15 +4,16 @@ sidebar_position: 2 # Installing Formulus App -Complete guide for installing the Formulus mobile application on Android devices. +Complete guide for installing the Formulus mobile application on Android and iOS devices. For the current links, see [Downloads](/downloads). ## Overview -Formulus is available for Android devices through multiple installation methods. Choose the method that best fits your needs: +Formulus is available for Android and iOS. Choose the method that best fits your device: -- **Obtainium** (Recommended) - Installs Formulus from GitHub releases with automatic updates. Install Obtainium via F-Droid or direct download. -- **F-Droid** - Install Formulus directly from [F-Droid](https://f-droid.org/packages/org.opendataensemble.formulus/) -- **Direct APK** - Download and install the APK file directly from [GitHub releases](https://github.com/OpenDataEnsemble/ode/releases) (current: **v1.3.0**) +- **F-Droid** (recommended for Android) - Install Formulus directly from [F-Droid](https://f-droid.org/en/packages/org.opendataensemble.formulus/) +- **Obtainium** (Android) - Installs Formulus from GitHub releases with automatic updates. +- **Direct APK** (Android) - Download the current APK from [Downloads](/downloads) or [GitHub releases](https://github.com/OpenDataEnsemble/ode/releases). +- **App Store** (iPhone/iPad) - Install Formulus from the [Apple App Store](https://apps.apple.com/dk/app/formulus/id6798318215). - **Development Build** - For developers who want to build from source ## System Requirements @@ -22,6 +23,7 @@ Before installing, ensure your device meets these requirements: | Requirement | Minimum | |-------------|---------| | **Android Version** | Android 7.0 (API level 24) or higher | +| **iOS Version** | iOS 15.1 or higher | | **Storage Space** | 50 MB free space | | **Internet Connection** | Required for initial setup and synchronization | | **Permissions** | Camera, Storage, Location (for form features) | @@ -86,7 +88,7 @@ You have two options to install Obtainium: ![Obtainium Add App Screen](/img/installation/obtainium-add-app.png) -**Stable release:** Install **v1.3.0** (or the latest [GitHub release](https://github.com/OpenDataEnsemble/ode/releases)). Pre-release toggles are only needed for alpha/beta testing. +**Stable release:** Install **v1.3.2** (or the latest [GitHub release](https://github.com/OpenDataEnsemble/ode/releases)). Pre-release toggles are only needed for alpha/beta testing. #### Step 3: Install Formulus @@ -97,7 +99,7 @@ You have two options to install Obtainium: - App name: **ode** - Developer: **OpenDataEnsemble** - Package: `org.opendataensemble.formulus` - - Latest version: **v1.3.0** (or current [release](https://github.com/OpenDataEnsemble/ode/releases)) + - Latest version: **v1.3.2** (or current [release](https://github.com/OpenDataEnsemble/ode/releases)) - Status: **Not installed** 5. **Tap the "Install" button** at the bottom of the screen 6. **Confirm installation** when prompted: @@ -127,7 +129,7 @@ Obtainium will automatically check for updates: 5. **Confirm the update** when prompted 6. **App data is preserved** during update -### Method 2: F-Droid +### Method 2: F-Droid (recommended for Android) Install Formulus directly from F-Droid (no Obtainium required): @@ -136,13 +138,13 @@ Install Formulus directly from F-Droid (no Obtainium required): 3. Tap **Install** and wait for the download to complete 4. Updates are available through F-Droid when a new version is published -### Method 3: Direct APK Installation +### Method 3: Direct APK Installation (Android) If Obtainium is not available or you prefer direct installation: #### Step 1: Download the APK -1. **Download the latest APK** from the [releases page](https://github.com/OpenDataEnsemble/ode/releases) +1. **Download the latest APK** from [Downloads](/downloads) or the [releases page](https://github.com/OpenDataEnsemble/ode/releases) 2. **Save the file** to your device's Downloads folder #### Step 2: Enable Unknown Sources @@ -165,7 +167,13 @@ If Obtainium is not available or you prefer direct installation: 6. **Wait for installation** to complete 7. **Tap "Open"** to launch the app -### Method 4: Development Build +### Method 4: App Store (iPhone and iPad) + +1. Open the [Formulus App Store page](https://apps.apple.com/dk/app/formulus/id6798318215) on your iPhone or iPad. +2. Tap **Get**, then authenticate with Face ID, Touch ID, or your Apple ID. +3. Wait for Formulus to install, then open it from your home screen. + +### Method 5: Development Build For developers who want to build and install from source, see the [Development Installation Guide](/docs/development/formulus-development). @@ -273,7 +281,7 @@ To verify that Formulus is installed correctly: ### Via Direct APK -1. **Download the latest APK** from the [releases page](https://github.com/OpenDataEnsemble/ode/releases) +1. **Download the latest APK** from [Downloads](/downloads) or the [releases page](https://github.com/OpenDataEnsemble/ode/releases) 2. **Install over existing installation** (no need to uninstall) 3. **App data is preserved** during update diff --git a/docs/getting-started/installation/installing-ode-desktop.md b/docs/getting-started/installation/installing-ode-desktop.md index 975c5c8..869eaef 100644 --- a/docs/getting-started/installation/installing-ode-desktop.md +++ b/docs/getting-started/installation/installing-ode-desktop.md @@ -6,9 +6,9 @@ sidebar_position: 3 Complete guide for installing **ODE Desktop** on Windows, macOS, and Linux. -:::info ODE v1.1.0 +:::info ODE v1.3.2 -ODE Desktop is part of the **ODE v1.1.0** release. Pre-built installers are published on [GitHub Releases](https://github.com/OpenDataEnsemble/ode/releases). +ODE Desktop is part of the **ODE v1.3.2** release. Use the [Downloads](/downloads) page for direct, platform-matched installers or browse [GitHub Releases](https://github.com/OpenDataEnsemble/ode/releases). ::: @@ -36,8 +36,8 @@ Choose the installation method that fits your role: ## Method 1: GitHub Releases (recommended) -1. Open [OpenDataEnsemble/ode releases](https://github.com/OpenDataEnsemble/ode/releases). -2. Select the **v1.1.0** release (or the latest stable tag). +1. Open [Downloads](/downloads) to download the installer matched to your platform, or open [OpenDataEnsemble/ode releases](https://github.com/OpenDataEnsemble/ode/releases). +2. Select the **v1.3.2** release (or the latest stable tag). 3. Download the artifact for your platform: | Platform | Typical artifact | diff --git a/docs/guides/deployment.md b/docs/guides/deployment.md index 4e22ce9..3f3ad05 100644 --- a/docs/guides/deployment.md +++ b/docs/guides/deployment.md @@ -13,7 +13,7 @@ Complete guide to deploying ODE in production environments using containers (Doc ODE production deployments center on the **Synkronus container image** (`ghcr.io/opendataensemble/synkronus`). The reference stack is [synkronus-quickstart](https://github.com/OpenDataEnsemble/synkronus-quickstart): Synkronus, PostgreSQL, and **Caddy** for TLS. Your IT team may use any hardened reverse proxy (Nginx, Apache, cloud load balancer) instead of Caddy—the requirement is **TLS termination** forwarding to Synkronus on port 8080. -Pin the image tag in production (e.g. `ghcr.io/opendataensemble/synkronus:v1.3.0`), not `:latest`. +Pin the image tag in production (e.g. `ghcr.io/opendataensemble/synkronus:v1.3.2`), not `:latest`. ## Recommended Production Setup diff --git a/docs/guides/server-architecture-for-it.md b/docs/guides/server-architecture-for-it.md index ea4b27d..db9ab19 100644 --- a/docs/guides/server-architecture-for-it.md +++ b/docs/guides/server-architecture-for-it.md @@ -7,7 +7,7 @@ title: Server Architecture for IT One-page overview for infrastructure teams evaluating or hosting ODE (Synkronus). -> **Current ODE release:** [v1.3.0](https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.0) · **Reference stack:** [synkronus-quickstart](https://github.com/OpenDataEnsemble/synkronus-quickstart) +> **Current ODE release:** [v1.3.2](https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.2) · [Downloads](/downloads) · **Reference stack:** [synkronus-quickstart](https://github.com/OpenDataEnsemble/synkronus-quickstart) ## Summary @@ -41,7 +41,7 @@ Reference layout from [synkronus-quickstart](https://github.com/OpenDataEnsemble | Container / role | Image | Purpose | |------------------|-------|---------| | Reverse proxy | Caddy 2 (quickstart) or IT-standard proxy | TLS termination, forward to Synkronus | -| `synkronus` | `ghcr.io/opendataensemble/synkronus:v1.3.0` | API, sync, auth, app-bundle hosting, Portal | +| `synkronus` | `ghcr.io/opendataensemble/synkronus:v1.3.2` | API, sync, auth, app-bundle hosting, Portal | | `db` | `postgres:17` (quickstart) | Observations, users, metadata | ### Common deployment variants @@ -122,14 +122,14 @@ Typical self-hosted pattern (e.g. research institutions running custom apps like 2. [synkronus-quickstart](https://github.com/OpenDataEnsemble/synkronus-quickstart) installer → Caddy + Synkronus + Postgres 3. DNS points to server; TLS via Let's Encrypt or institutional certificates on your proxy 4. Project team uploads the app bundle via Portal or `synk` CLI -5. Field tablets install Formulus **v1.3.0** via Obtainium or F-Droid; configure server URL in app settings +5. Field tablets install Formulus **v1.3.2** via F-Droid, Obtainium, the App Store, or direct APK; configure server URL in app settings Coordinate **Formulus and Synkronus versions** on upgrade—the mobile app checks server compatibility and may refuse sync on mismatch. ## Operator checklist - [ ] Hardened reverse proxy with TLS (TLS 1.2+) -- [ ] Pin Synkronus image tag (e.g. `v1.3.0`) rather than `:latest` in production +- [ ] Pin Synkronus image tag (e.g. `v1.3.2`) rather than `:latest` in production - [ ] Proxy upload limit ≥ 32 MB per attachment - [ ] Proxy send/read timeouts ≥ 600s (sync, attachments, bundle zip) - [ ] Automated Postgres backups + tested restore diff --git a/docs/reference/security.md b/docs/reference/security.md index a94d16d..2a99149 100644 --- a/docs/reference/security.md +++ b/docs/reference/security.md @@ -14,7 +14,7 @@ For a one-page infrastructure overview aimed at IT departments, see [Server Arch ## Supported versions -Security updates are provided for the latest release and the immediately preceding major version. **Current ODE release: [v1.3.0](https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.0).** +Security updates are provided for the latest release and the immediately preceding major version. **Current ODE release: [v1.3.2](https://github.com/OpenDataEnsemble/ode/releases/tag/v1.3.2).** | Component | Supported | |-----------|-----------| @@ -77,7 +77,7 @@ Recommend **device passcode or biometric lock** and **MDM remote wipe** for lost ### Container images -- Production: pin `ghcr.io/opendataensemble/synkronus:v1.3.0` (not `:latest`). +- Production: pin `ghcr.io/opendataensemble/synkronus:v1.3.2` (not `:latest`). - Scan images for vulnerabilities as part of your supply-chain process. ### Network @@ -149,7 +149,7 @@ Before production: - [ ] OS and image dependencies patched - [ ] Reverse proxy rate limiting configured - [ ] Proxy upload limit ≥ 32 MB -- [ ] Synkronus image tag pinned (e.g. `v1.3.0`) +- [ ] Synkronus image tag pinned (e.g. `v1.3.2`) - [ ] Device passcode/MDM policy for field tablets ## Security updates diff --git a/docs/reference/synkronus-server.md b/docs/reference/synkronus-server.md index 887fd93..24fabc7 100644 --- a/docs/reference/synkronus-server.md +++ b/docs/reference/synkronus-server.md @@ -19,7 +19,7 @@ Synkronus is a robust synchronization API server built with Go. It provides REST Production deployments should pin a release tag rather than `:latest`: ``` -ghcr.io/opendataensemble/synkronus:v1.3.0 +ghcr.io/opendataensemble/synkronus:v1.3.2 ``` Images are published on [GitHub Container Registry](https://github.com/OpenDataEnsemble/ode/pkgs/container/synkronus) for each [ODE release](https://github.com/OpenDataEnsemble/ode/releases). See the [Deployment guide](/docs/guides/deployment) for stable, pre-release, and branch tracking channels. diff --git a/docusaurus.config.ts b/docusaurus.config.ts index f7b1c46..d7512d4 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -73,6 +73,11 @@ const config: Config = { label: 'Documentation', position: 'right', }, + { + to: '/downloads', + label: 'Downloads', + position: 'right', + }, { label: 'Components', position: 'right', @@ -166,6 +171,10 @@ const config: Config = { label: 'Overview', to: '/docs', }, + { + label: 'Downloads', + to: '/downloads', + }, { label: 'Getting Started', to: '/docs/getting-started', diff --git a/sidebars.ts b/sidebars.ts index dd57e1c..a433de7 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -30,6 +30,11 @@ const sidebars: SidebarsConfig = { items: [ 'getting-started/why-ode', 'getting-started/key-concepts', + { + type: 'link', + label: 'Downloads', + href: '/downloads', + }, { type: 'category', label: 'Installation', diff --git a/src/pages/downloads/index.tsx b/src/pages/downloads/index.tsx new file mode 100644 index 0000000..73446aa --- /dev/null +++ b/src/pages/downloads/index.tsx @@ -0,0 +1,294 @@ +import React, {useEffect, useState} from 'react'; +import Layout from '@theme/Layout'; +import Link from '@docusaurus/Link'; + +import styles from './styles.module.css'; + +const RELEASE_API_URL = 'https://api.github.com/repos/OpenDataEnsemble/ode/releases/latest'; +const RELEASES_URL = 'https://github.com/OpenDataEnsemble/ode/releases'; +const F_DROID_URL = 'https://f-droid.org/en/packages/org.opendataensemble.formulus/'; +const OBTAINIUM_URL = 'https://github.com/ImranR98/Obtainium'; +const APP_STORE_URL = 'https://apps.apple.com/dk/app/formulus/id6798318215'; + +type Platform = 'macOS' | 'Windows' | 'Linux' | null; +type Architecture = 'arm64' | 'amd64'; + +type ReleaseAsset = { + name: string; + browser_download_url: string; +}; + +type Release = { + tag_name: string; + html_url: string; + assets: ReleaseAsset[]; +}; + +type PlatformDownload = { + label: string; + platform: Platform; + architecture: Architecture; + asset?: ReleaseAsset; + note: string; +}; + +function detectPlatform(): {platform: Platform; architecture: Architecture} { + if (typeof navigator === 'undefined') { + return {platform: null, architecture: 'amd64'}; + } + + const userAgent = navigator.userAgent.toLowerCase(); + const platform = navigator.platform.toLowerCase(); + const architecture: Architecture = + /arm64|aarch64|arm/.test(userAgent) || /arm64|aarch64|arm/.test(platform) + ? 'arm64' + : 'amd64'; + + if (/macintosh|mac os x/.test(userAgent)) { + return {platform: 'macOS', architecture}; + } + if (/windows/.test(userAgent)) { + return {platform: 'Windows', architecture}; + } + if (/linux/.test(userAgent)) { + return {platform: 'Linux', architecture}; + } + return {platform: null, architecture}; +} + +function findAsset( + assets: ReleaseAsset[], + required: string[], + extensions: string[], +): ReleaseAsset | undefined { + return assets.find(asset => { + const name = asset.name.toLowerCase(); + return ( + required.every(part => name.includes(part)) && + extensions.some(extension => name.endsWith(extension)) + ); + }); +} + +function DownloadLink({asset, children}: {asset?: ReleaseAsset; children: string}) { + return asset ? ( + + {children} + + ) : ( + + View release assets + + ); +} + +function DownloadTable({downloads}: {downloads: PlatformDownload[]}) { + return ( +
+ {downloads.map(download => ( +
+
+ {download.label} + {download.note} +
+ Download +
+ ))} +
+ ); +} + +export default function Downloads(): React.ReactElement { + const [release, setRelease] = useState(null); + const [loadFailed, setLoadFailed] = useState(false); + const [detected, setDetected] = useState<{platform: Platform; architecture: Architecture}>( + {platform: null, architecture: 'amd64'}, + ); + + useEffect(() => { + setDetected(detectPlatform()); + void fetch(RELEASE_API_URL) + .then(response => { + if (!response.ok) { + throw new Error(`GitHub responded with ${response.status}`); + } + return response.json() as Promise; + }) + .then(setRelease) + .catch(() => setLoadFailed(true)); + }, []); + + const assets = release?.assets ?? []; + const desktopDownloads: PlatformDownload[] = [ + { + label: 'macOS — Apple silicon', + platform: 'macOS', + architecture: 'arm64', + asset: findAsset(assets, ['ode-desktop-darwin-arm64'], ['.dmg', '.zip']), + note: 'M-series Mac', + }, + { + label: 'macOS — Intel', + platform: 'macOS', + architecture: 'amd64', + asset: findAsset(assets, ['ode-desktop-darwin-amd64'], ['.dmg', '.zip']), + note: 'Intel Mac', + }, + { + label: 'Windows — ARM64', + platform: 'Windows', + architecture: 'arm64', + asset: findAsset(assets, ['ode-desktop-windows-arm64'], ['.msi', '.exe']), + note: 'Windows on ARM', + }, + { + label: 'Windows — x64', + platform: 'Windows', + architecture: 'amd64', + asset: findAsset(assets, ['ode-desktop-windows-amd64'], ['.msi', '.exe']), + note: 'Most Windows PCs', + }, + { + label: 'Linux — ARM64', + platform: 'Linux', + architecture: 'arm64', + asset: findAsset(assets, ['ode-desktop-linux-arm64'], ['.appimage', '.deb', '.rpm']), + note: 'AppImage when available', + }, + { + label: 'Linux — x64', + platform: 'Linux', + architecture: 'amd64', + asset: findAsset(assets, ['ode-desktop-linux-amd64'], ['.appimage', '.deb', '.rpm']), + note: 'AppImage when available', + }, + ]; + const cliDownloads: PlatformDownload[] = [ + { + label: 'macOS — Apple silicon', + platform: 'macOS', + architecture: 'arm64', + asset: findAsset(assets, ['synkronus-cli-darwin-arm64'], ['']), + note: 'M-series Mac binary', + }, + { + label: 'macOS — Intel', + platform: 'macOS', + architecture: 'amd64', + asset: findAsset(assets, ['synkronus-cli-darwin-amd64'], ['']), + note: 'Intel Mac binary', + }, + { + label: 'Windows — ARM64', + platform: 'Windows', + architecture: 'arm64', + asset: findAsset(assets, ['synkronus-cli-windows-arm64'], ['.exe']), + note: 'Windows on ARM binary', + }, + { + label: 'Windows — x64', + platform: 'Windows', + architecture: 'amd64', + asset: findAsset(assets, ['synkronus-cli-windows-amd64'], ['.exe']), + note: 'Most Windows PCs', + }, + { + label: 'Linux — ARM64', + platform: 'Linux', + architecture: 'arm64', + asset: findAsset(assets, ['synkronus-cli-linux-arm64'], ['']), + note: 'ARM64 binary', + }, + { + label: 'Linux — x64', + platform: 'Linux', + architecture: 'amd64', + asset: findAsset(assets, ['synkronus-cli-linux-amd64'], ['']), + note: 'Most Linux PCs and servers', + }, + ]; + const recommendedDesktop = desktopDownloads.find( + download => + download.platform === detected.platform && + download.architecture === detected.architecture, + ); + const recommendedCli = cliDownloads.find( + download => + download.platform === detected.platform && + download.architecture === detected.architecture, + ); + const formulusApk = + findAsset(assets, ['formulus-', '-universal-'], ['.apk']) ?? + findAsset(assets, ['formulus-', 'arm64-v8a'], ['.apk']) ?? + findAsset(assets, ['formulus-'], ['.apk']); + + return ( + +
+
+
+

Open Data Ensemble

+

Downloads

+

+ Get the latest stable ODE applications. Download links below are loaded from the + latest GitHub release and are matched to your device when possible. +

+ {release ? ( +

Latest stable release: {release.tag_name}

+ ) : loadFailed ? ( +

Could not load the latest release automatically. Browse the GitHub releases.

+ ) : ( +

Loading the latest stable release…

+ )} +
+ +
+
+

Formulus

+

Mobile data collection for Android and iPhone/iPad.

+
+
+
+

Android

+

F-Droid is the recommended store for open-source Android apps.

+ Get Formulus on F-Droid +

Alternatively, use Obtainium with https://github.com/OpenDataEnsemble/ode for GitHub-release updates.

+ Download APK directly +
+ +
+
+ +
+
+

ODE Desktop

+

Manage observations, synchronize data, and work with forms and custom app bundles.

+ {recommendedDesktop ? ( +

Recommended for your device: {recommendedDesktop.label}

+ ) : null} +
+ {recommendedDesktop ? Download ODE Desktop : null} + +
+ +
+
+

Synkronus CLI

+

The synk command-line client for login, synchronization, bundles, and exports.

+ {recommendedCli ? ( +

Recommended for your device: {recommendedCli.label}

+ ) : null} +
+ {recommendedCli ? Download Synkronus CLI : null} + +

After downloading a macOS or Linux binary, make it executable with chmod +x <filename>. See the CLI reference for setup and usage.

+
+
+
+
+ ); +} diff --git a/src/pages/downloads/styles.module.css b/src/pages/downloads/styles.module.css new file mode 100644 index 0000000..4228e45 --- /dev/null +++ b/src/pages/downloads/styles.module.css @@ -0,0 +1,117 @@ +.page { + padding: 3rem 0 5rem; +} + +.header { + max-width: 48rem; + margin-bottom: 3rem; +} + +.header h1 { + margin-top: 0; +} + +.header > p { + font-size: 1.125rem; +} + +.eyebrow { + color: var(--ifm-color-primary); + font-size: 0.875rem !important; + font-weight: 700; + letter-spacing: 0.08em; + margin-bottom: 0.25rem; + text-transform: uppercase; +} + +.release { + color: var(--ifm-color-content-secondary); + font-size: 1rem !important; +} + +.product { + border-top: 1px solid var(--ifm-color-emphasis-300); + display: grid; + gap: 1.25rem; + padding: 2.25rem 0; +} + +.product h2, +.product h3 { + margin-top: 0; +} + +.product > div > p { + max-width: 46rem; +} + +.mobileOptions { + display: grid; + gap: 1.5rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.mobileOptions article { + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: var(--ifm-card-border-radius); + padding: 1.5rem; +} + +.mobileOptions article p { + min-height: 3rem; +} + +.downloadTable { + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: var(--ifm-card-border-radius); + overflow: hidden; +} + +.downloadRow { + align-items: center; + border-bottom: 1px solid var(--ifm-color-emphasis-200); + display: flex; + gap: 1rem; + justify-content: space-between; + padding: 1rem 1.25rem; +} + +.downloadRow:last-child { + border-bottom: 0; +} + +.downloadRow span { + color: var(--ifm-color-content-secondary); + display: block; + font-size: 0.875rem; + margin-top: 0.125rem; +} + +.recommended { + color: var(--ifm-color-primary-dark); +} + +.hint { + color: var(--ifm-color-content-secondary); + margin: 0; +} + +@media screen and (max-width: 768px) { + .page { + padding-top: 2rem; + } + + .mobileOptions { + grid-template-columns: 1fr; + } + + .mobileOptions article p { + min-height: 0; + } + + .downloadRow { + align-items: flex-start; + flex-direction: column; + } +}