forked from 1b-agent/fieldcraft-upstream
Release: sign SHA256SUMS.txt with the release key; docs/updates.md (#231)
The release job signs SHA256SUMS.txt into SHA256SUMS.txt.minisig with rsign2 when the FIELDCRAFT_SIGNING_KEY secret exists, and checks the signature against the public key the app is built with; without the secret the draft is unsigned, the run warns and the notes say the app cannot install that release itself. docs/releasing.md says how the key is set up, kept and rotated; docs/updates.md describes checking, verifying and installing, the web app's Reload to update, and FIELDCRAFT_UPDATE_HOST for tests. privacy.md, control-protocol.md, web.md and the roadmap say what changed. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
93b2ae343c
commit
86ba1bda5c
8 files changed
+183
-8
No files matched your search
@@ -21,6 +21,8 @@ modal problems with its own solver. AI agents can drive it over MCP (`fieldcraft
|
||||
To start with a sample model, run the app with `--sample cantilever` (macOS:
|
||||
`/Applications/FieldCraft.app/Contents/MacOS/FieldCraft --sample cantilever`).
|
||||
|
||||
**Updates.** {update_signing}
|
||||
|
||||
**Signing.** {macos_signing} The Linux and Windows builds are not signed; on Windows, SmartScreen
|
||||
may say "Windows protected your PC": choose **More info**, then **Run anyway**.
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
#
|
||||
# A push to `main` (fast-forwarded from a verified `develop` commit; main holds released code only)
|
||||
# builds every package and creates or updates the
|
||||
# DRAFT GitHub Release "FieldCraft v<version>", with SHA256SUMS.txt and generated notes. The
|
||||
# DRAFT GitHub Release "FieldCraft v<version>", with SHA256SUMS.txt (signed with the release key
|
||||
# into SHA256SUMS.txt.minisig when the FIELDCRAFT_SIGNING_KEY secret exists) and generated notes. The
|
||||
# version is [workspace.package] version in Cargo.toml; a suffix (0.2.0-rc.1) makes a
|
||||
# pre-release. A person verifies the draft and publishes it; this workflow never publishes,
|
||||
# and pushes to develop or tags never start it.
|
||||
@@ -245,7 +246,7 @@ jobs:
|
||||
pattern: release-*
|
||||
path: artifacts
|
||||
merge-multiple: true
|
||||
- name: Check the set, checksums and notes
|
||||
- name: Check the set and write the checksums
|
||||
run: |
|
||||
for f in "fieldcraft-$VERSION-macos-universal.dmg" "fieldcraft-cli-$VERSION-macos-universal.zip" \
|
||||
"fieldcraft-$VERSION-linux-x86_64.tar.gz" "fieldcraft-$VERSION-linux-x86_64.AppImage" \
|
||||
@@ -254,13 +255,45 @@ jobs:
|
||||
test -s "artifacts/$f" || { echo "::error::missing $f"; exit 1; }
|
||||
done
|
||||
(cd artifacts && sha256sum -- * | tee SHA256SUMS.txt)
|
||||
- name: Sign SHA256SUMS.txt with the release key (skipped without FIELDCRAFT_SIGNING_KEY)
|
||||
id: sign
|
||||
env:
|
||||
FIELDCRAFT_SIGNING_KEY: ${{ secrets.FIELDCRAFT_SIGNING_KEY }}
|
||||
# rsign2 (MIT), a minisign-compatible signer, pinned.
|
||||
RSIGN_VERSION: 0.6.7
|
||||
run: |
|
||||
if [ -z "$FIELDCRAFT_SIGNING_KEY" ]; then
|
||||
echo "::warning::FIELDCRAFT_SIGNING_KEY is not set: SHA256SUMS.txt is not signed, so the app cannot install this release itself (docs/releasing.md)"
|
||||
echo "signed=false" >>"$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
cargo install --locked --quiet --root "$RUNNER_TEMP/rsign" rsign2 --version "$RSIGN_VERSION"
|
||||
rsign="$RUNNER_TEMP/rsign/bin/rsign"
|
||||
key="$RUNNER_TEMP/release.key"
|
||||
(umask 077 && printf '%s\n' "$FIELDCRAFT_SIGNING_KEY" >"$key")
|
||||
"$rsign" sign -W -s "$key" -t "FieldCraft v$VERSION SHA256SUMS.txt" -x artifacts/SHA256SUMS.txt.minisig artifacts/SHA256SUMS.txt
|
||||
rm -f "$key"
|
||||
# The signature must verify with the public key the app is built with.
|
||||
public="$(sed -n 's/^pub const PUBLIC_KEY: &str = "\(.*\)";$/\1/p' crates/update/src/lib.rs)"
|
||||
"$rsign" verify -q -P "$public" -x artifacts/SHA256SUMS.txt.minisig artifacts/SHA256SUMS.txt
|
||||
echo "signed=true" >>"$GITHUB_OUTPUT"
|
||||
- name: Write the notes
|
||||
env:
|
||||
SIGNED: ${{ steps.sign.outputs.signed }}
|
||||
run: |
|
||||
# The macOS paragraph says how the app was signed (packaging/macos/package.sh).
|
||||
case "$MACOS_SIGNING" in
|
||||
notarized) macos="The macOS app is signed with a Developer ID and notarized by Apple." ;;
|
||||
signed) macos="The macOS app is signed with a Developer ID but **not notarized**: the first time, right-click \`FieldCraft.app\` in Applications and choose **Open**." ;;
|
||||
*) macos="The macOS app is **ad-hoc signed and not notarized**: macOS blocks the first launch. Right-click \`FieldCraft.app\` in Applications and choose **Open** (then **Open** again), or run \`xattr -dr com.apple.quarantine /Applications/FieldCraft.app\`." ;;
|
||||
esac
|
||||
awk -v v="$VERSION" -v m="$macos" '{ gsub(/\{version\}/, v); gsub(/\{macos_signing\}/, m); print }' \
|
||||
# The updates paragraph says whether the app can install this release itself.
|
||||
if [ "$SIGNED" = true ]; then
|
||||
updates="\`SHA256SUMS.txt\` is signed with the FieldCraft release key (\`SHA256SUMS.txt.minisig\`), so FieldCraft's Help › Check for Updates can verify and install this release."
|
||||
else
|
||||
updates="This release's \`SHA256SUMS.txt\` is **not signed**: FieldCraft's in-app updates can't verify it and won't install it; download it from this page instead."
|
||||
fi
|
||||
awk -v v="$VERSION" -v m="$macos" -v u="$updates" '{ gsub(/\{version\}/, v); gsub(/\{macos_signing\}/, m); gsub(/\{update_signing\}/, u); print }' \
|
||||
.github/release-notes.md >notes.md
|
||||
cat notes.md >>"$GITHUB_STEP_SUMMARY"
|
||||
- name: Upload the dry-run artifacts
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ pressure) is a degree of freedom of the existing DOF framework, not a separate s
|
||||
| M23 Wave propagation and ultrasound | Elastic, acoustic and coupled waves from the explicit solver's 1D bar today to guided and ultrasonic waves, by two methods. Finite differences first, for fast 2D (then 3D) ultrasound on regular grids: staggered-grid velocity–stress FDTD (Virieux) and the elastodynamic finite integration technique for solids, the rotated staggered grid for anisotropic media, a k-space pseudospectral scheme for weakly heterogeneous tissue and water, all on grids voxelised from the face-tagged geometry by the LBM's exact voxeliser, with heterogeneous, layered and fluid–solid media, CPML boundaries, threads and the GPU backend; staircased curved boundaries scatter spuriously, so the FD grid is checked against the finite elements on curved parts. Finite elements for curved geometry and coupling with structures: spectral elements (high-order Gauss–Lobatto–Legendre hexes with diagonal mass) on the hex meshes, and a mesh check in elements per wavelength with the dispersion error it implies; absorbing boundaries (convolutional PML, absorbing layers with increasing damping) and infinite media; tone-burst sources (Hann-windowed cycles), point, line and surface excitation, and piezoelectric transducers (M19); anisotropic media (composites, crystals) and viscoelastic, power-law-in-frequency attenuation; fluid–solid interfaces and fluid-loaded plates; ultrasound in water and tissue, nonlinear acoustics (Westervelt, KZK) for focused ultrasound. Guided waves: dispersion curves by semi-analytical finite elements (SAFE) for plates, pipes, rails and layered and anisotropic laminates — Lamb, Rayleigh, Love and SH modes — with phase and group velocities and mode shapes; mode-selective excitation. Output: A-scans and B-scans at sensors, wavefield snapshots, 2D FFT (frequency–wavenumber) for mode identification, and time–frequency diagrams of any signal — short-time Fourier spectrograms, continuous wavelet transforms (Morlet), reassigned spectrograms and smoothed pseudo Wigner–Ville — with the SAFE or Rayleigh–Lamb group-velocity curves overlaid as arrival times, and group velocities extracted from the ridges. GPU paths (M12's backend) for long runs. Verified by Lamb's problem (point load on a half-space), Rayleigh-wave speed, Rayleigh–Lamb dispersion, Love waves in a layer over a half-space, reflection and transmission at interfaces, and published guided-wave measurements | planned |
|
||||
| Continuous: verification and validation | Every capability checked against theory (closed forms), experiments (published measurements) and published benchmarks (NAFEMS, vendor benchmark and verification manuals such as Abaqus's), each case our own model in `tests/oracle/` with its cited reference and tolerance; `cargo xtask oracle` runs them and writes docs/oracle.md; once a case matches, our result is frozen as a fixture so fidelity cannot regress (docs/verification.md). Runner, fixtures and CI job: done; 26 theory cases (patch tests of every solid element, cantilevers, Lamé in 3D, plane strain and axisymmetric, Kirsch in 3D and plane stress, rotating disc and cylinder, beam frequencies, couplings, explicit waves, impact and J2 plasticity, CDT invariants), 9 published benchmarks (MacNeal–Harder solid tests, Scordelis–Lo with solids, NAFEMS LE1 in 3D and 2D, LE10, FV32) and 9 pending cases with citations (shells, thermal, static plasticity, Taylor bar, CFD); `mesh.block` mapped blocks and 2D patches build their meshes | started |
|
||||
| Continuous: performance | CPU first, deterministic: results never depend on the thread count (fixed partitions, sums in a fixed order; checked by hashing results on 1, 2 and 7 threads). Supernodal left-looking Cholesky (11–15× faster than the column solver on one thread), parallel element matrices, stresses and Lanczos products, explicit element updates and LBM cell blocks on rayon threads (done — `fieldcraft-parallel`, `threads` on the solve commands, `FIELDCRAFT_THREADS`); parallel forward/back substitution and explicit tet4 assembly; GPU paths later, within a stated tolerance of the CPU | started |
|
||||
| Continuous: desktop and agents | Desktop app (viewport, model tree, commands, control channel, the open design drawn, `ui.capture` of the whole window offscreen: done; toolbar of every capability from the catalogue, ready or coming soon with its milestone, icons drawn with VectorCraft, docs/features.md: done; the Craft-family shell — title bar, workspaces and ribbon drop-downs, browser, context panel, command search, journal timeline, light and dark themes — native file pickers with background opening, drag and drop and recent files, samples and one-click Solve, and an examples gallery of ready-to-solve samples, each checked by a test (docs/examples.md): done; MCP server over stdio, headless or bridged to the app, with read and write roots, docs/mcp.md: done; caps labels in Inter SemiBold; the README's analysis video recorded through MCP by `tools/demo-video`: done; support, load, reference-point and coupling symbols in the viewport and headless renders, with their browser toggles: done; symbols as wireframes, one mark per constrained degree of freedom, a constant size on screen with a size setting, highlighted with their selected definition, concentrated loads drawn over the part: done; selection by picking in the viewport — geometry faces, edges, vertices and bodies, mesh nodes, element faces and elements, box selection, hover, a Target row in command forms, `ui.select`/`ui.selection` and MCP `select`: done; a camera parameter for screenshots, a ribbon that follows the command being run: in progress; the browser build — the same app in a web page with browser file pickers, downloads, drops and autosave to browser storage, deployed to GitHub Pages from each release, docs/web.md: done), command schema as the public API | started |
|
||||
| Continuous: desktop and agents | Desktop app (viewport, model tree, commands, control channel, the open design drawn, `ui.capture` of the whole window offscreen: done; toolbar of every capability from the catalogue, ready or coming soon with its milestone, icons drawn with VectorCraft, docs/features.md: done; the Craft-family shell — title bar, workspaces and ribbon drop-downs, browser, context panel, command search, journal timeline, light and dark themes — native file pickers with background opening, drag and drop and recent files, samples and one-click Solve, and an examples gallery of ready-to-solve samples, each checked by a test (docs/examples.md): done; MCP server over stdio, headless or bridged to the app, with read and write roots, docs/mcp.md: done; caps labels in Inter SemiBold; the README's analysis video recorded through MCP by `tools/demo-video`: done; support, load, reference-point and coupling symbols in the viewport and headless renders, with their browser toggles: done; symbols as wireframes, one mark per constrained degree of freedom, a constant size on screen with a size setting, highlighted with their selected definition, concentrated loads drawn over the part: done; selection by picking in the viewport — geometry faces, edges, vertices and bodies, mesh nodes, element faces and elements, box selection, hover, a Target row in command forms, `ui.select`/`ui.selection` and MCP `select`: done; a camera parameter for screenshots, a ribbon that follows the command being run: in progress; the browser build — the same app in a web page with browser file pickers, downloads, drops and autosave to browser storage, deployed to GitHub Pages from each release, docs/web.md: done; in-app updates — a daily check of GitHub Releases, Help › Check for Updates and Reinstall, downloads verified against the release key's signature of SHA256SUMS.txt and swapped in after closing, `app.update.check`, `fieldcraft-cli update`, Reload to update in the browser, docs/updates.md: done), command schema as the public API | started |
|
||||
|
||||
## Next
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ commands (files, undo, `model.info`) leave the ribbon where it is.
|
||||
|
||||
| Method | Params | Notes |
|
||||
|---|---|---|
|
||||
| `ui.inspect` | — | view settings (`ui`), camera, viewport rect (points), renderer (`gpu`/`cpu`) and adapter, open command form, status bar, node and element counts, the results display (`results`: what the `results.*` commands reply: view, `stale`, contour range, modes), the probed node (`probe`), `solve` (the title bar's Solve: `enabled`, and `unavailable` as `ui.toolbar`'s entries give it, listing everything `solve.run` misses), `busy` (the background job: a file opening, a solve, a mesher run from its form by a click on OK), `prompt` (`{"unsaved": path}`, `{"design": path}` (a design of another part opened over a meshed or defined model), `"recover"`, `"samples"`), `recent` (recent files), `selected` (the browser's selected definition), `browser` (each top-level browser node's setup state: `[{node, state, tip, add}]`, `state` `done`, `next`, `blocked` (⚠: its command cannot run yet; `tip` says what it needs and where), `todo`, `optional` or null for nodes that are no setup stage, `add` its ghost rows `{stage, label, command, status}`; a ghost row's region is `layout`'s `browser/add/<stage>`), `nav` (the left-drag mode), `layout` (each region of the window drawn last frame as `[x, y, w, h]` in points: `app_bar`, `ribbon`, `workspace_switcher`, `document_tab`, `app_bar:<action>`, `browser`, `viewport`, `nav_bar`, `axis_view`, `selection_bar`, `legend` (while results are shown), `context`, `command_line`, `timeline`, `toolbar_dropdown`, `palette`), version |
|
||||
| `ui.inspect` | — | view settings (`ui`), camera, viewport rect (points), renderer (`gpu`/`cpu`) and adapter, open command form, status bar, node and element counts, the results display (`results`: what the `results.*` commands reply: view, `stale`, contour range, modes), the probed node (`probe`), `solve` (the title bar's Solve: `enabled`, and `unavailable` as `ui.toolbar`'s entries give it, listing everything `solve.run` misses), `busy` (the background job: a file opening, a solve, a mesher run from its form by a click on OK), `prompt` (`{"unsaved": path}`, `{"design": path}` (a design of another part opened over a meshed or defined model), `"recover"`, `"samples"`), `recent` (recent files), `updates` (docs/updates.md: `automatic`, `checked_at`, `skip`, `available` (a newer version the status bar offers), `busy`, `staged`, `unsupported`; null when the run does not check), `selected` (the browser's selected definition), `browser` (each top-level browser node's setup state: `[{node, state, tip, add}]`, `state` `done`, `next`, `blocked` (⚠: its command cannot run yet; `tip` says what it needs and where), `todo`, `optional` or null for nodes that are no setup stage, `add` its ghost rows `{stage, label, command, status}`; a ghost row's region is `layout`'s `browser/add/<stage>`), `nav` (the left-drag mode), `layout` (each region of the window drawn last frame as `[x, y, w, h]` in points: `app_bar`, `ribbon`, `workspace_switcher`, `document_tab`, `app_bar:<action>`, `browser`, `viewport`, `nav_bar`, `axis_view`, `selection_bar`, `legend` (while results are shown), `context`, `command_line`, `timeline`, `toolbar_dropdown`, `palette`), version |
|
||||
| `ui.set` | any view setting: `style` (`shaded_edges`, `shaded`, `wireframe`), `show_nodes`, `show_geometry`, `show_mesh`, `show_loads` (boundary condition and load symbols), `show_interactions` (reference points, couplings, contact and tie surfaces, rigid walls), `symbol_size` (the setup symbols' size on screen in points, 6–48, at any zoom), `perspective`, `dark`, `show_tree` (the browser), `show_timeline`, `nav_preset` (the mouse navigation: `fieldcraft`, `fusion`, `solidworks`, `inventor`, `alias`, `tinkercad`, `openscad`; an unknown id acts as `fieldcraft`) and `reverse_zoom` (both remembered per user), `workspace` (`prepare`, `model`, `analysis`, `results`), `tab` (the toolbar tab: `geometry`, `mesh`, `properties`, `assembly`, `steps`, `loads`, `interactions`, `solve`, `results`, `tools`; a tab of another workspace brings that workspace), `palette` (the command search open), `menu` (the open menu: `file`, `help`, `workspace`, `view`, `more`, or a panel's drop-down as `<tab>/<panel>` such as `mesh/Tet`; `null` closes it) | an unknown setting or value is an error; returns the settings |
|
||||
| `ui.view` | `view`: `front`, `back`, `left`, `right`, `top`, `bottom`, `iso`, `home` (iso and fit), `fit` | every view frames the model as drawn (the deformed shape included; the setup's symbols keep their size on screen and are not framed) in the part of the viewport the overlays (`layout`'s `legend`, `selection_bar`, `axis_view`, `nav_bar`) leave free; `fit` keeps the direction of view. Opening another model, sample or design frames it in the home view (the same design opened again after an edit keeps the camera), and results that come up with part of the model out of sight are framed. Returns the camera |
|
||||
| `ui.camera` | `yaw`, `pitch` (degrees), `distance?` (model units) | turns the camera to any orientation about its target: `yaw` about Z (0 looks along +Y, the front view; −45 is the iso view's, −90 the right view's), `pitch` the elevation above the XY plane; `distance` moves the eye (the zoom of an orthographic view). Angles must be finite and the distance above 0: the yaw is wrapped, the pitch clamped to ±90 and the distance to 0.001–10⁹; anything else is an error. A sequence of calls orbits smoothly. Returns the camera (`ui.inspect`'s, angles in radians) |
|
||||
@@ -50,7 +50,7 @@ commands (files, undo, `model.info`) leave the ribbon where it is.
|
||||
| `ui.select` | `kind` (`set`, `material`, `section`, `step`, `boundary_condition`, `load`, `reference_point`, `coupling`, `fluid_domain`), `name`; nothing to deselect | selects a definition as a browser click does; its properties (with Edit and Delete) show in the context panel |
|
||||
| `ui.select` (viewport) | any of `filter`, `box_mode`, `mode`, `clear`, `at`, `box`, `geometry`, `nodes`, `elements`, `faces` (and no `kind`) | changes the viewport's selection (see [Selection](#selection)); returns `ui.selection`'s reply |
|
||||
| `ui.selection` | — | the viewport's selection: `filter`, `box_mode` (box selection on), `count`, `capped`, `summary`, `items`, `params` (the selection as a set's members), `hover` (what a click would pick under the pointer) |
|
||||
| `ui.prompt` | `answer`: for `unsaved` `save`, `discard` or `cancel`; for `design` `new` (a new model with the design), `keep` (the design over the current model) or `cancel`; for `replace_mesh` `replace` (runs the form, replacing the mesh) or `cancel` (the form stays open); for `delete` (the item menu's Delete… or the Delete key: `{delete: question, kind, name, users, allowed}`) `delete` (with a datum's partitions when it has any; refused when `allowed` is false) or `cancel`; for `recover` `recover` (with `id` from `file.recovery.list`) or `close`; for `samples` `open` (with `name`) or `close`; for `crashed` (the start after a crash: `{crashed: [reports]}`) `report` (opens Report a Bug for the newest) or `close`; for `report_bug` (`{report_bug: draft}`) `set` (any of `title`, `observed`, `share_session`, `include_model`; replies with the draft) or `close` — the browser opens only from a click (docs/crash-reports.md); for `consent` (the first run's question about anonymous usage) `share` or `decline`; for `settings` (Help › Settings…) `set` (any of `usage_sharing`, `usage_automation`) or `close` (docs/privacy.md) | answers the window's question (`ui.inspect`'s `prompt`) |
|
||||
| `ui.prompt` | `answer`: for `unsaved` `save`, `discard` or `cancel`; for `design` `new` (a new model with the design), `keep` (the design over the current model) or `cancel`; for `replace_mesh` `replace` (runs the form, replacing the mesh) or `cancel` (the form stays open); for `delete` (the item menu's Delete… or the Delete key: `{delete: question, kind, name, users, allowed}`) `delete` (with a datum's partitions when it has any; refused when `allowed` is false) or `cancel`; for `recover` `recover` (with `id` from `file.recovery.list`) or `close`; for `samples` `open` (with `name`) or `close`; for `crashed` (the start after a crash: `{crashed: [reports]}`) `report` (opens Report a Bug for the newest) or `close`; for `report_bug` (`{report_bug: draft}`) `set` (any of `title`, `observed`, `share_session`, `include_model`; replies with the draft) or `close` — the browser opens only from a click (docs/crash-reports.md); for `consent` (the first run's question about anonymous usage) `share` or `decline`; for `settings` (Help › Settings…) `set` (any of `usage_sharing`, `usage_automation`; or `update_check`, `update_skip` (a version, `""` for none)) or `close` (docs/privacy.md, docs/updates.md); for `update` (a downloaded and verified update: `{update: state}`) `restart` (saves or asks about unsaved changes first, then closes the window to install) or `later` (installs when the app quits) | answers the window's question (`ui.inspect`'s `prompt`) |
|
||||
| `ui.settings` | — | opens Help › Settings… (answered with `ui.prompt`); replies with the privacy state as `ui.inspect`'s `privacy` gives it: `available` (this run may share usage), `usage_sharing` (`null` until asked), `usage_automation`, `recording`, `queued` |
|
||||
| `ui.item` | `kind` (`body`, `surface`, or a definition kind as `ui.select` takes it, `datum`, `partition`), `name`, `action`: `edit`, `hide`, `show`, `isolate`, `show_all`, `delete`, or `activate`/`deactivate` with `step` | an item menu's entry, as a right click on the item and a click on the entry: runs the same command (`delete` opens the delete prompt, answered with `ui.prompt`) |
|
||||
| `ui.dialog` | `params?` (JSON object, fills the form), `use_selection?`, `run?`, `submit?`, `cancel?` | drives the open command form, in that order; `use_selection` is the form's Target row (see [Selection](#selection)); `run` returns the command's result and closes the form on success; `submit` is the form's OK button: a mesher that would replace a body's mesh first asks (`prompt` `replace_mesh`, with the question and what else goes: sets listing the old labels, solved results), and meshers run in the background (`busy`); a form opened by another form's **Create …** button (a reference field with nothing to pick) goes back to that form on `run` (with the new item picked) or `cancel` |
|
||||
|
||||
@@ -11,6 +11,12 @@ Change it any time in **Help › Settings… › Privacy**, which also has **Inc
|
||||
agents (MCP)**: commands run by scripts, the control channel and AI agents are counted apart from
|
||||
yours (`from`), and this leaves them out.
|
||||
|
||||
Checking for updates is separate from usage sharing: at start-up, at most once a day (and from
|
||||
Help › Check for Updates), the desktop app asks GitHub for the latest release, which sends the
|
||||
request itself (FieldCraft's version in the `User-Agent`; GitHub sees the IP address) and nothing
|
||||
else. Turn it off in **Help › Settings… › Updates** (or with the first run's question): then no
|
||||
request is made ([updates.md](updates.md)).
|
||||
|
||||
Crash reports and the log never leave your computer on their own; Help › Report a Bug sends only
|
||||
what you submit yourself ([crash-reports.md](crash-reports.md)).
|
||||
|
||||
|
||||
+42
-2
@@ -14,7 +14,7 @@ are in `packaging/` and run the same way on a developer machine.
|
||||
| Linux x86_64, glibc 2.35+ | `fieldcraft-<v>-linux-x86_64.tar.gz` (`bin/`, `share/`), `fieldcraft-<v>-linux-x86_64.AppImage`, `fieldcraft_<v>_amd64.deb` (Debian, Ubuntu 22.04+), `fieldcraft-<v>.x86_64.rpm` (Fedora, RHEL, openSUSE) | `packaging/linux/package.sh` |
|
||||
| Windows x64 | `fieldcraft-<v>-windows-x64.zip` (`fieldcraft.exe`, `fieldcraft-cli.exe`) | `packaging/windows/package.ps1` |
|
||||
|
||||
Plus `SHA256SUMS.txt` and the notes from `.github/release-notes.md` followed by GitHub's
|
||||
Plus `SHA256SUMS.txt`, its signature `SHA256SUMS.txt.minisig` (below), and the notes from `.github/release-notes.md` followed by GitHub's
|
||||
generated list of changes. Every package carries `README.md`, `LICENSE-MIT`, `LICENSE-APACHE`,
|
||||
`NOTICE` and `THIRD-PARTY-LICENSES.txt` (on macOS in `FieldCraft.app/Contents/Resources/Licenses`).
|
||||
The application id is `io.github.jyviko.fieldcraft`: the macOS bundle id, the Linux `.desktop`
|
||||
@@ -56,6 +56,44 @@ three notarization secrets the app is signed and not notarized. A dmg made local
|
||||
`packaging/macos/package.sh` is ad-hoc signed unless `MACOS_SIGN_IDENTITY` names an identity
|
||||
in the keychain.
|
||||
|
||||
**The release key (updates).** The app installs a release only when `SHA256SUMS.txt` is signed
|
||||
with the FieldCraft release key ([updates.md](updates.md)): an Ed25519 key in minisign's format.
|
||||
Its public half is built into the app (`fieldcraft_update::PUBLIC_KEY` in
|
||||
`crates/update/src/lib.rs`):
|
||||
|
||||
```
|
||||
untrusted comment: minisign public key: FEF410EFEF5BAB21
|
||||
RWQhq1vv7xD0/uAOt8fqERP1hP+1UCM9U9QHTDYTYytWKiuedTkT7Lgr
|
||||
```
|
||||
|
||||
The secret half is the repository secret `FIELDCRAFT_SIGNING_KEY`: the whole secret key file
|
||||
(two lines), made without a password. With it, the release job signs `SHA256SUMS.txt` with
|
||||
[rsign2](https://github.com/jedisct1/rsign2) (pinned in `release.yml`), checks the signature
|
||||
against the public key in the source, and uploads `SHA256SUMS.txt.minisig`. Without it the
|
||||
draft is built unsigned, the run warns, and the notes say the app cannot install that release
|
||||
itself (users download it from the page). Anyone can check a release by hand with `minisign -Vm
|
||||
SHA256SUMS.txt -P RWQhq1vv7xD0/uAOt8fqERP1hP+1UCM9U9QHTDYTYytWKiuedTkT7Lgr`.
|
||||
|
||||
Setting it up, once:
|
||||
|
||||
```sh
|
||||
cargo install --locked rsign2 --version 0.6.7
|
||||
rsign generate -W -p fieldcraft-release.pub -s fieldcraft-release.key # -W: no password
|
||||
gh secret set FIELDCRAFT_SIGNING_KEY -R jyviko/fieldcraft < fieldcraft-release.key
|
||||
```
|
||||
|
||||
Keep an offline copy of the secret key file (a password manager, an encrypted backup) and
|
||||
delete it from the working machine. Never commit it.
|
||||
|
||||
Rotating it (a key that leaked, or a planned change): generate a new pair, put the new public
|
||||
key into `PUBLIC_KEY` and the block above, re-sign `crates/update/tests/data/SHA256SUMS.txt`
|
||||
with it (`rsign sign -W -s <new key> -x crates/update/tests/data/SHA256SUMS.txt.minisig
|
||||
crates/update/tests/data/SHA256SUMS.txt`, which a test checks), release that version, and only
|
||||
then replace the secret. Copies that still carry the old public key cannot verify releases
|
||||
signed with the new one: they still show the update, but Install refuses it ("not signed by the
|
||||
FieldCraft release key"), and a download from the release page brings the new key. If the old key leaked, also delete the secret at once (releases go out
|
||||
unsigned until the new one is in place) and say so in the next release notes.
|
||||
|
||||
**Linux and Windows** builds are not signed; the checksums are the integrity check. On Windows
|
||||
SmartScreen warns about an unsigned download (More info › Run anyway) until a code-signing
|
||||
certificate is added.
|
||||
@@ -100,7 +138,9 @@ Publishing is a maintainer's call; an agent preparing a release stops at the dra
|
||||
1. Check the run succeeded for the intended commit and the draft targets it
|
||||
(`gh release view v<version> -R jyviko/fieldcraft`).
|
||||
2. Download the assets into an empty folder (`gh release download v<version> -R jyviko/fieldcraft`),
|
||||
run `shasum -a 256 -c SHA256SUMS.txt`, and check every platform's files are there.
|
||||
run `shasum -a 256 -c SHA256SUMS.txt`, check the signature
|
||||
(`minisign -Vm SHA256SUMS.txt -P <the public key above>`, or `rsign verify -P …`), and check
|
||||
every platform's files are there.
|
||||
3. Smoke-test what you can run: open the dmg, drag the app to Applications, launch it with
|
||||
`--sample cantilever`; run `fieldcraft-cli --version` from the zip or tar.gz; on Linux run the
|
||||
AppImage, or install the `.deb` (`sudo apt install ./fieldcraft_<v>_amd64.deb`) or `.rpm`
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Updates
|
||||
|
||||
FieldCraft checks for new releases, verifies them and installs them itself, with your consent at
|
||||
each step. The code is `crates/update` (`fieldcraft-update`); the window's side is
|
||||
`crates/ui-egui/src/updates.rs`, the desktop app's `apps/fieldcraft/src/update.rs`.
|
||||
|
||||
## Checking
|
||||
|
||||
The app asks the GitHub Releases API for the latest published release
|
||||
(`GET https://api.github.com/repos/jyviko/fieldcraft/releases/latest`; drafts and pre-releases
|
||||
are never offered) and compares its version with the running one (Semantic Versioning order).
|
||||
|
||||
- **At start-up**, at most once a day, when **Check for updates automatically** is on (the
|
||||
default). It is shown with the first run's question and in **Help › Settings… › Updates**.
|
||||
Off means no request at all. A failed automatic check is silent (it goes to the log). Runs
|
||||
driven by automation (`--control`) and CI never check automatically.
|
||||
- **Help › Check for Updates** asks now, and says when FieldCraft is up to date or why the check
|
||||
failed.
|
||||
- **Agents and scripts**: the `app.update.check` command (in the app, and through MCP connected
|
||||
to it) and `fieldcraft-cli update --check` reply with `current`, `latest`, `newer`, `url` (the
|
||||
release page), `published`, `signed` and `asset` (the file this install would download; `null`
|
||||
when it cannot update itself). They only report.
|
||||
|
||||
The request carries the FieldCraft version in its `User-Agent`; GitHub sees the request's IP
|
||||
address, as with any download. Nothing else is sent ([privacy.md](privacy.md)).
|
||||
|
||||
A newer release shows a notice in the status bar: **FieldCraft 0.5.9 is available — What's new ·
|
||||
Install · Skip**. What's new opens the release page; Skip stops offering that version (Settings
|
||||
can offer it again).
|
||||
|
||||
## Verifying
|
||||
|
||||
Every release carries `SHA256SUMS.txt` and, signed with the FieldCraft release key,
|
||||
`SHA256SUMS.txt.minisig` (Ed25519 in minisign's format; the public key is built into the app,
|
||||
`fieldcraft_update::PUBLIC_KEY`, and printed in [releasing.md](releasing.md)). Installing:
|
||||
|
||||
1. downloads the list and its signature, and refuses unless the signature is the release key's;
|
||||
2. downloads the asset for this install and refuses unless its SHA-256 is the one the list gives;
|
||||
3. on macOS, refuses an app that fails `codesign --verify --deep --strict`, is not
|
||||
`io.github.jyviko.fieldcraft`, or is rejected by Gatekeeper (`spctl`) when the installed app
|
||||
is accepted.
|
||||
|
||||
A release without a signature cannot be installed from the app: Install opens its page instead.
|
||||
A development build (run from `target/`) checks but never installs.
|
||||
|
||||
## Installing
|
||||
|
||||
Install downloads and verifies in the background, unpacks the release beside the installed copy
|
||||
(`.fieldcraft-update-<version>`, so the swap is a rename on one file system), then asks to restart.
|
||||
**Restart Now** saves or asks about unsaved changes first, as opening another model does; the
|
||||
window closes, the autosave is closed, the swap is made and the new version starts. **Later**
|
||||
installs when you quit. A failed swap puts the previous version back; a staging folder left
|
||||
behind is removed at the next start.
|
||||
|
||||
| Installed as | Asset | Swap |
|
||||
|---|---|---|
|
||||
| `FieldCraft.app` (macOS) | `fieldcraft-<v>-macos-universal.dmg` | the bundle is replaced, then opened |
|
||||
| `fieldcraft-cli` (macOS) | `fieldcraft-cli-<v>-macos-universal.zip` | the binary is replaced |
|
||||
| A Windows folder | `fieldcraft-<v>-windows-x64.zip` | each file is replaced (a running `.exe` is renamed away, never overwritten); the app restarts |
|
||||
| An AppImage | `fieldcraft-<v>-linux-x86_64.AppImage` | the file is replaced and started |
|
||||
| A tar.gz folder | `fieldcraft-<v>-linux-x86_64.tar.gz` | the folder is replaced; when its parent is not writable the app says so and links the download |
|
||||
| `.deb` / `.rpm` | `fieldcraft_<v>_amd64.deb` / `fieldcraft-<v>.x86_64.rpm` | never replaced by the app: the verified package is downloaded to the app's data folder (`updates/`), and the app offers **Open with Installer** and the `sudo apt install …` / `sudo dnf install …` command |
|
||||
|
||||
**Help › Reinstall FieldCraft…** downloads and verifies the running version again and swaps it
|
||||
in the same way, to repair a damaged install.
|
||||
|
||||
`fieldcraft-cli update --install` does the same from the command line for the install the CLI
|
||||
belongs to (the tar.gz folder or Windows folder it is in, or the macOS CLI binary), without
|
||||
restarting anything; for `.deb`/`.rpm` it prints the package manager command.
|
||||
|
||||
## The web app
|
||||
|
||||
The browser build has no downloads to install: every half hour the page fetches its own address
|
||||
past the browser's cache, and when the deployed page loads another build than the running one
|
||||
(Trunk names each build's files by their content hash), the status bar offers **Reload to
|
||||
update**. Reloading with unsaved changes asks first, as closing the page does.
|
||||
|
||||
## Testing
|
||||
|
||||
`FIELDCRAFT_UPDATE_HOST` replaces `https://api.github.com` with another server, and allows plain
|
||||
`http` for it. It is for tests against a local server only (`fieldcraft_update::mock::Server`,
|
||||
behind the crate's `mock` feature, plays the Releases API); with it set, the app also checks
|
||||
automatically in automation runs, so the notice can be looked at:
|
||||
|
||||
```sh
|
||||
FIELDCRAFT_UPDATE_HOST=http://127.0.0.1:8899 fieldcraft-cli update --check
|
||||
```
|
||||
|
||||
The tests cover version order, the daily schedule and no request when checks are off, asset
|
||||
selection and the swap plan for every install kind, a checksum mismatch and a missing or foreign
|
||||
signature refused (without downloading the asset), archives that try to write outside their
|
||||
folder, undoing a failed swap, and the CLI against a local server. They never replace a running
|
||||
program.
|
||||
@@ -49,6 +49,7 @@ The browser has no file system the app can reach, so files live in the page
|
||||
| Autosave to the recovery folder, File › Recover… | Autosaves are kept in the browser's local storage; a page closed or crashed with unsaved changes offers them at the next visit (File › Recover…) |
|
||||
| Leaving with unsaved changes asks first | The browser's "leave site?" prompt |
|
||||
| Recent files | This visit's files only |
|
||||
| Help › Check for Updates installs a new release (docs/updates.md) | A new deployment shows **Reload to update** in the status bar |
|
||||
|
||||
A `.fieldcraft` model refers to its design and meshes by path. Paths in the model's own folder
|
||||
are stored relative to it, so picking or dropping the model with those files opens them again; a
|
||||
|
||||
Reference in new issue
Block a user