M12 — the Android client, end to end #2
@@ -0,0 +1,54 @@
|
|||||||
|
# ThoughtSync production settings. Copy to `.env` and edit:
|
||||||
|
#
|
||||||
|
# cp .env.example .env
|
||||||
|
#
|
||||||
|
# Only POSTGRES_PASSWORD has no default — compose refuses to start without it.
|
||||||
|
# Everything else here is optional. Anything NOT in this file (site name, signups,
|
||||||
|
# attachment limits, trash retention, link previews) is configured in the admin
|
||||||
|
# Settings UI and stored in the database, not here.
|
||||||
|
|
||||||
|
# --- required ---------------------------------------------------------------
|
||||||
|
|
||||||
|
# Generate one and keep it: changing it later means also changing it inside the
|
||||||
|
# database, or Postgres will reject the app's connection.
|
||||||
|
#
|
||||||
|
# openssl rand -base64 24 | tr -d '/+=' | head -c 32
|
||||||
|
#
|
||||||
|
# Stick to letters and digits. This value goes into a connection URL, so a `@`,
|
||||||
|
# `/`, `:` or `#` in it will be misparsed as URL structure rather than password.
|
||||||
|
POSTGRES_PASSWORD=
|
||||||
|
|
||||||
|
# --- optional ---------------------------------------------------------------
|
||||||
|
|
||||||
|
# Which build to run.
|
||||||
|
#
|
||||||
|
# latest tracks the `main` branch — the production line (default)
|
||||||
|
# dev tracks the `dev` branch — newer, less settled
|
||||||
|
# <commit sha> pins one exact build; every push publishes one, and this is
|
||||||
|
# the rollback lever when an upgrade misbehaves
|
||||||
|
#
|
||||||
|
# NOTE: `main` can sit well behind `dev`. If a feature you expect is missing,
|
||||||
|
# check which branch it actually landed on before assuming a bug.
|
||||||
|
#THOUGHTSYNC_TAG=latest
|
||||||
|
|
||||||
|
# The host port the app is published on.
|
||||||
|
#THOUGHTSYNC_PORT=5000
|
||||||
|
|
||||||
|
# Which interface to bind. The default (all interfaces) is what lets desktop
|
||||||
|
# clients on your network reach the server. Behind a reverse proxy, set this to
|
||||||
|
# 127.0.0.1 so only the proxy can talk to it.
|
||||||
|
#THOUGHTSYNC_BIND=0.0.0.0
|
||||||
|
|
||||||
|
# Database identity. Changing these AFTER the first start does not rename anything
|
||||||
|
# that already exists — the volume keeps whatever the first run created.
|
||||||
|
#POSTGRES_USER=thoughtsync
|
||||||
|
#POSTGRES_DB=thoughtsync
|
||||||
|
|
||||||
|
# --- a note on HTTPS --------------------------------------------------------
|
||||||
|
#
|
||||||
|
# The app marks its session cookie Secure automatically when a request arrives over
|
||||||
|
# HTTPS, directly or via a proxy setting X-Forwarded-Proto — no setting needed.
|
||||||
|
#
|
||||||
|
# Worth knowing if you use the desktop app: typing a bare hostname there defaults to
|
||||||
|
# https://, deliberately, so a device token never crosses the wire in cleartext by
|
||||||
|
# accident. Serving over plain HTTP means typing the `http://` yourself.
|
||||||
@@ -91,8 +91,11 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "No TAURI_SIGNING_PRIVATE_KEY — building unsigned, no updater artifacts."
|
echo "No TAURI_SIGNING_PRIVATE_KEY — building unsigned, no updater artifacts."
|
||||||
fi
|
fi
|
||||||
|
version="$(sh ../packaging/build-version.sh)"
|
||||||
|
echo "Building version $version"
|
||||||
cargo tauri build \
|
cargo tauri build \
|
||||||
--config '{"build":{"beforeBuildCommand":""}}' \
|
--config '{"build":{"beforeBuildCommand":""}}' \
|
||||||
|
--config "{\"version\":\"$version\"}" \
|
||||||
--config "$updater"
|
--config "$updater"
|
||||||
working-directory: desktop/src-tauri
|
working-directory: desktop/src-tauri
|
||||||
|
|
||||||
@@ -217,12 +220,23 @@ jobs:
|
|||||||
# the MSVC CRT/SDK, pre-warmed into the image, and links with lld-link).
|
# the MSVC CRT/SDK, pre-warmed into the image, and links with lld-link).
|
||||||
# Frontend already built above; skip the beforeBuildCommand rebuild.
|
# Frontend already built above; skip the beforeBuildCommand rebuild.
|
||||||
- name: Tauri build (NSIS installer)
|
- name: Tauri build (NSIS installer)
|
||||||
|
env:
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
|
version="$(sh ../packaging/build-version.sh)"
|
||||||
|
echo "Building version $version"
|
||||||
|
updater='{}'
|
||||||
|
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
||||||
|
updater='{"bundle":{"createUpdaterArtifacts":true}}'
|
||||||
|
fi
|
||||||
cargo tauri build \
|
cargo tauri build \
|
||||||
--runner cargo-xwin \
|
--runner cargo-xwin \
|
||||||
--target x86_64-pc-windows-msvc \
|
--target x86_64-pc-windows-msvc \
|
||||||
--bundles nsis \
|
--bundles nsis \
|
||||||
--config '{"build":{"beforeBuildCommand":""}}'
|
--config '{"build":{"beforeBuildCommand":""}}' \
|
||||||
|
--config "{\"version\":\"$version\"}" \
|
||||||
|
--config "$updater"
|
||||||
working-directory: desktop/src-tauri
|
working-directory: desktop/src-tauri
|
||||||
|
|
||||||
- name: Upload installer
|
- name: Upload installer
|
||||||
@@ -292,8 +306,10 @@ jobs:
|
|||||||
echo "manifest to write. Add the secret to enable in-app updates."
|
echo "manifest to write. Add the secret to enable in-app updates."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
# The version the bundles carry, read from the crate rather than guessed.
|
# The SAME helper the bundles were built with — a second derivation here
|
||||||
version="$(grep -m1 '^version' desktop/src-tauri/Cargo.toml | sed -E 's/.*"([^"]+)".*/\1/')"
|
# could drift, and a manifest whose version doesn't match the binary it
|
||||||
|
# points at is an updater that never settles.
|
||||||
|
version="$(sh desktop/packaging/build-version.sh)"
|
||||||
if [ "${GITHUB_REF_NAME}" = "dev" ]; then
|
if [ "${GITHUB_REF_NAME}" = "dev" ]; then
|
||||||
export RELEASE_TAG=dev
|
export RELEASE_TAG=dev
|
||||||
export RELEASE_NOTES="Development build from ${GITHUB_SHA}"
|
export RELEASE_NOTES="Development build from ${GITHUB_SHA}"
|
||||||
|
|||||||
Executable
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
#
|
||||||
|
# Echo the version this build should carry. One definition, used in three places
|
||||||
|
# (both bundle jobs and the manifest writer) — if they ever disagreed, the app would
|
||||||
|
# compare its own version against a manifest describing a different build, and the
|
||||||
|
# updater would either offer nothing or loop forever offering the same thing.
|
||||||
|
#
|
||||||
|
# WHY DEV BUILDS NEED THEIR OWN VERSION AT ALL:
|
||||||
|
# an updater decides by comparing semver. Every dev build carries the version in
|
||||||
|
# Cargo.toml, so without this they'd all be `0.1.0` — an installed build would see a
|
||||||
|
# manifest advertising the version it already has, conclude it was current, and never
|
||||||
|
# update. The rolling channel needs a number that actually rises.
|
||||||
|
#
|
||||||
|
# The CI run number is that number: monotonic, already unique per build, and it needs
|
||||||
|
# no state carried between runs. `0.1.0` + run 2932 becomes `0.1.2932`.
|
||||||
|
#
|
||||||
|
# Plain semver on purpose, NOT a `-dev.N` prerelease tag: prerelease versions sort
|
||||||
|
# BELOW the release they qualify (`0.1.0-dev.5` < `0.1.0`), so a tagged build would
|
||||||
|
# never update to a newer dev one, and Windows installer metadata wants a numeric
|
||||||
|
# X.Y.Z anyway. Bumping the minor in Cargo.toml still wins over any dev build on the
|
||||||
|
# old line, which is the ordering you want: 0.2.0 > 0.1.2932.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
CARGO_TOML="$(dirname "$0")/../src-tauri/Cargo.toml"
|
||||||
|
base="$(grep -m1 '^version' "$CARGO_TOML" | sed -E 's/.*"([^"]+)".*/\1/')"
|
||||||
|
|
||||||
|
# Dev builds only. Anything else (a v* tag, main) ships the version as written.
|
||||||
|
if [ "${GITHUB_REF_NAME:-}" = "dev" ] && [ -n "${GITHUB_RUN_NUMBER:-}" ]; then
|
||||||
|
printf '%s.%s\n' "${base%.*}" "$GITHUB_RUN_NUMBER"
|
||||||
|
else
|
||||||
|
printf '%s\n' "$base"
|
||||||
|
fi
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"endpoints": [
|
"endpoints": [
|
||||||
"https://git.fabledsword.com/bvandeusen/thoughtsync/releases/download/stable/latest.json"
|
"https://git.fabledsword.com/bvandeusen/thoughtsync/releases/download/stable/latest.json"
|
||||||
],
|
],
|
||||||
"pubkey": "",
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDkwRTk2RkVBMkY2RDlCNkEKUldScW0yMHY2bS9wa0VBdWFpM3c1d2trQnlNVUJXUUtwZXBzQjduM3FRVzdGa3dXNGxObkZFV28K",
|
||||||
"windows": {
|
"windows": {
|
||||||
"installMode": "passive"
|
"installMode": "passive"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,11 @@
|
|||||||
# docker compose -f docker-compose.dev.yml up
|
# docker compose -f docker-compose.dev.yml up
|
||||||
#
|
#
|
||||||
# Then open http://localhost:5173 (the Vite dev server proxies /api to the backend).
|
# Then open http://localhost:5173 (the Vite dev server proxies /api to the backend).
|
||||||
# (docker-compose.yml, by contrast, builds + runs the production image on :5000.)
|
#
|
||||||
|
# docker-compose.yml — the DEFAULT file — is the production stack instead: it pulls
|
||||||
|
# the published image, keeps Postgres off the host network, and expects a .env. This
|
||||||
|
# one builds nothing and is deliberately insecure-by-convenience (weak password,
|
||||||
|
# Postgres published on 5432) because it is meant for a laptop, not a deployment.
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
|
|||||||
+76
-12
@@ -1,34 +1,98 @@
|
|||||||
# Local two-service stack (app + Postgres). Provided for convenience — per family
|
# ThoughtSync — PRODUCTION stack (app + Postgres).
|
||||||
# rule 12 the agent does NOT start this; run it yourself with `docker compose up`.
|
#
|
||||||
|
# This is the default compose file: `docker compose up -d` runs a real deployment
|
||||||
|
# from the published image. Development lives in docker-compose.dev.yml (hot-reload,
|
||||||
|
# builds from source).
|
||||||
|
#
|
||||||
|
# cp .env.example .env # then set POSTGRES_PASSWORD
|
||||||
|
# docker compose up -d
|
||||||
|
#
|
||||||
|
# Per family rule 12 the agent does NOT start this — run it yourself.
|
||||||
|
#
|
||||||
|
# Upgrades: docker compose pull && docker compose up -d
|
||||||
|
# Rollback: set THOUGHTSYNC_TAG to a commit sha in .env, then the same two commands.
|
||||||
|
# Every push publishes an immutable :<sha> image for exactly this.
|
||||||
|
#
|
||||||
|
# Schema migrations run automatically at container start (see the Dockerfile CMD),
|
||||||
|
# so an upgrade is just a pull and a restart. Take a backup first anyway:
|
||||||
|
#
|
||||||
|
# docker compose exec -T db pg_dump -U thoughtsync thoughtsync > backup.sql
|
||||||
|
#
|
||||||
|
# Attachments are files, not rows — they live in the `thoughtsync-data` volume and
|
||||||
|
# a pg_dump does NOT contain them. Back up both or you'll restore notes whose images
|
||||||
|
# are gone.
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: thoughtsync
|
POSTGRES_USER: ${POSTGRES_USER:-thoughtsync}
|
||||||
POSTGRES_PASSWORD: thoughtsync
|
# No default on purpose. A production compose that ships a known password is
|
||||||
POSTGRES_DB: thoughtsync
|
# how self-hosted databases end up in search engines; compose fails fast here
|
||||||
|
# instead, with the message below.
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env — see .env.example}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB:-thoughtsync}
|
||||||
volumes:
|
volumes:
|
||||||
|
# Volume names kept from the previous compose file so an existing deployment
|
||||||
|
# upgrades in place. Renaming them would silently start against an empty
|
||||||
|
# database while the old one sat there, orphaned and looking like data loss.
|
||||||
- thoughtsync-db:/var/lib/postgresql/data
|
- thoughtsync-db:/var/lib/postgresql/data
|
||||||
ports:
|
# Deliberately NOT published to the host. The app reaches Postgres over the
|
||||||
- "5432:5432"
|
# compose network; exposing 5432 only widens the attack surface. If you need
|
||||||
|
# psql, `docker compose exec db psql -U thoughtsync` gets you there without it.
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U thoughtsync"]
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-thoughtsync} -d ${POSTGRES_DB:-thoughtsync}"]
|
||||||
interval: 5s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
start_period: 30s
|
||||||
|
logging: &logging
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
# Unbounded container logs are a slow-motion disk-full outage on a
|
||||||
|
# long-running self-hosted box.
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
app:
|
app:
|
||||||
build: .
|
# :latest tracks `main`. Set THOUGHTSYNC_TAG=dev in .env to follow the
|
||||||
|
# development line instead, or a commit sha to pin exactly.
|
||||||
|
image: git.fabledsword.com/bvandeusen/thoughtsync:${THOUGHTSYNC_TAG:-latest}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
THOUGHTSYNC_DATABASE_URL: postgresql+asyncpg://thoughtsync:thoughtsync@db:5432/thoughtsync
|
# The only required application setting. Everything else a person might want
|
||||||
|
# to tune lives in the admin Settings UI, backed by the database (rule 25).
|
||||||
|
THOUGHTSYNC_DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-thoughtsync}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-thoughtsync}
|
||||||
volumes:
|
volumes:
|
||||||
|
# Uploaded attachments. /var/thoughtsync is fixed in the app (Config.DATA_DIR),
|
||||||
|
# not configurable — mount it or lose every image on container recreation.
|
||||||
- thoughtsync-data:/var/thoughtsync
|
- thoughtsync-data:/var/thoughtsync
|
||||||
ports:
|
ports:
|
||||||
- "5000:5000"
|
# Default binds every interface, which is what lets desktop clients on the LAN
|
||||||
|
# reach it. Behind a reverse proxy, set THOUGHTSYNC_BIND=127.0.0.1 so only the
|
||||||
|
# proxy can talk to it.
|
||||||
|
- "${THOUGHTSYNC_BIND:-0.0.0.0}:${THOUGHTSYNC_PORT:-5000}:5000"
|
||||||
|
healthcheck:
|
||||||
|
# python rather than curl: the runtime image is python:3.12-slim and carries no
|
||||||
|
# HTTP client binary. Hits the app's own /api/health.
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- python
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
import sys, urllib.request
|
||||||
|
sys.exit(0 if urllib.request.urlopen("http://127.0.0.1:5000/api/health", timeout=5).status == 200 else 1)
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
# Generous: the container waits for Postgres and runs migrations before it
|
||||||
|
# serves anything, and a first boot builds the whole schema.
|
||||||
|
start_period: 60s
|
||||||
|
logging: *logging
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
thoughtsync-db:
|
thoughtsync-db:
|
||||||
|
|||||||
Reference in New Issue
Block a user