New primitive: `VisibilityIntent::Announcement`, a public post whose
author MUST be the hardcoded bootstrap anchor posting identity
(`DEFAULT_ANCHOR_POSTING_ID`) and whose content carries an ed25519
signature by that key. Forged announcements (any other author, or
bad signature) are rejected by `control::receive_post` before storage
— they never enter the DB and never propagate via neighbor-manifest
diffs. Only the real anchor can publish announcements, and it does so
sparingly as part of the release deploy flow.
Uses release announcements to drive an in-app upgrade banner:
- Anchor publishes a signed `{category:release, version, channel,
download_url, ...}` post during every deploy.
- Clients receive it via the normal CDN; `apply_announcement_if_applicable`
stores the latest-per-category/channel in the settings kv, keyed
e.g. `announcement:release:stable`.
- Welcome screen checks storage on startup; if the stored release
version > CARGO_PKG_VERSION on the user's selected channel, a banner
appears with a Download button that opens the system browser.
- Settings gets "Updates" section with Stable / Beta radio + Check-now
button + current status line.
Core:
- `DEFAULT_ANCHOR_POSTING_ID: NodeId` constant (32 bytes, the anchor's
current posting id — `17af141956ae...`).
- New `VisibilityIntent::Announcement` variant; feed filters in all 6
`get_feed*` / `list_posts*` query sites updated to also exclude the
new intent AND the pre-existing `GroupKeyDistribute` intent.
- `types::AnnouncementContent` + `ReleaseAnnouncement` structs.
- `crypto::{sign,verify}_announcement` — length-prefixed field digest
with a "has release" 1-byte flag.
- New `announcement` module with `verify_announcement_post`,
`apply_announcement_if_applicable`, `latest_release`,
`build_announcement_post`, and a `StoredAnnouncement` envelope saved
to settings so the UI can render without a full post scan.
- `Node::publish_announcement` refuses to run unless the default posting
id equals the anchor constant — accidental use on client installs
fails loud.
Wire / receive:
- `control::receive_post` verifies announcement signatures upfront
alongside Control and Profile. Same pattern; same guarantees.
CLI one-shots (no daemon):
- `itsgoin <data_dir> --print-identity` — prints network_id +
default_posting_id, exits.
- `itsgoin <data_dir> --announce --ann-category release
--ann-channel stable --ann-version X --ann-title ... --ann-body ...
--ann-url https://itsgoin.com/download.html` — builds + stores +
propagates the signed post, exits.
deploy.sh:
- Now runs the announce one-shot inside the anchor-restart window
(after binary swap, before start). The DB is free during that gap,
so the one-shot can write without conflicting with the running
daemon. The restarted daemon loads all storage on boot and serves
the new announcement to pulling peers.
Tauri IPC:
- `check_release_announcement(channel)` → Option<ReleaseAnnouncementDto>
— returns None when up-to-date.
- `get_update_channel` / `set_update_channel(channel)` — persists in
settings kv key `ui_update_channel`; defaults to stable.
- `open_url_external(url)` — desktop-only (xdg-open / open / cmd start);
refuses non-http(s) URLs. Android needs the opener plugin — TODO.
Frontend:
- Upgrade banner on the welcome screen, populated by
`loadUpgradeBanner()`. Hidden when no newer release is known.
- Settings → Updates section with Stable/Beta radio + Check-now button
+ current status line.
Tests: announcement signature roundtrip; non-anchor author rejection;
non-announcement intent is a no-op. 124 / 124 core tests pass.
105 lines
3.7 KiB
Bash
Executable file
105 lines
3.7 KiB
Bash
Executable file
#!/bin/bash
|
|
# Full build + deploy: AppImage, APK, CLI, website, anchor
|
|
# Usage: ./deploy.sh
|
|
# Requires: cargo, cargo-tauri, Android SDK, sshpass
|
|
set -e
|
|
|
|
# Load credentials from .deploy-creds (not in git)
|
|
if [ -f .deploy-creds ]; then
|
|
source .deploy-creds
|
|
else
|
|
echo "ERROR: .deploy-creds not found. Create it with:"
|
|
echo ' SSH_PASS="your-ssh-password"'
|
|
echo ' KS_PASS="your-keystore-password"'
|
|
exit 1
|
|
fi
|
|
SSH_HOST="itsgoin@itsgoin.com"
|
|
SSH_OPTS="-o StrictHostKeyChecking=no"
|
|
KEYSTORE="itsgoin.keystore"
|
|
KS_ALIAS="itsgoin"
|
|
|
|
VERSION=$(grep '"version"' crates/tauri-app/tauri.conf.json | head -1 | sed 's/.*"\([0-9.]*\)".*/\1/')
|
|
echo "=== Deploying v${VERSION} ==="
|
|
|
|
# Build CLI
|
|
echo "=== Building CLI ==="
|
|
cargo build -p itsgoin-cli --release &
|
|
CLI_PID=$!
|
|
|
|
# Build APK
|
|
echo "=== Building APK ==="
|
|
cargo tauri android build --apk &
|
|
APK_PID=$!
|
|
|
|
# Build AppImage (includes GStreamer patch)
|
|
echo "=== Building AppImage ==="
|
|
./build-appimage.sh
|
|
wait $CLI_PID
|
|
echo "=== CLI build complete ==="
|
|
wait $APK_PID
|
|
echo "=== APK build complete ==="
|
|
|
|
# Sign APK
|
|
echo "=== Signing APK ==="
|
|
UNSIGNED="crates/tauri-app/gen/android/app/build/outputs/apk/universal/release/app-universal-release-unsigned.apk"
|
|
ALIGNED="itsgoin-aligned.apk"
|
|
SIGNED="itsgoin-${VERSION}.apk"
|
|
ZIPALIGN=$(find ~/Android/Sdk/build-tools -name "zipalign" 2>/dev/null | sort -V | tail -1)
|
|
APKSIGNER=$(find ~/Android/Sdk/build-tools -name "apksigner" 2>/dev/null | sort -V | tail -1)
|
|
"$ZIPALIGN" -f 4 "$UNSIGNED" "$ALIGNED"
|
|
"$APKSIGNER" sign --ks "$KEYSTORE" --ks-pass "pass:$KS_PASS" --ks-key-alias "$KS_ALIAS" --out "$SIGNED" "$ALIGNED"
|
|
echo "Signed: $SIGNED"
|
|
|
|
# Upload (sequential to avoid SSH rate limiting)
|
|
echo "=== Uploading website ==="
|
|
sshpass -p "$SSH_PASS" scp $SSH_OPTS website/*.html website/style.css "$SSH_HOST:~/public_html/"
|
|
sleep 5
|
|
|
|
echo "=== Uploading AppImage ==="
|
|
sshpass -p "$SSH_PASS" scp $SSH_OPTS "target/release/bundle/appimage/itsgoin_${VERSION}_amd64.AppImage" "$SSH_HOST:~/public_html/itsgoin_${VERSION}_amd64.AppImage"
|
|
sleep 5
|
|
|
|
echo "=== Uploading APK ==="
|
|
sshpass -p "$SSH_PASS" scp $SSH_OPTS "$SIGNED" "$SSH_HOST:~/public_html/"
|
|
sleep 5
|
|
|
|
echo "=== Uploading CLI ==="
|
|
sshpass -p "$SSH_PASS" scp $SSH_OPTS target/release/itsgoin "$SSH_HOST:~/public_html/itsgoin-cli-${VERSION}-linux-amd64"
|
|
sleep 5
|
|
|
|
echo "=== Uploading anchor binary ==="
|
|
sshpass -p "$SSH_PASS" scp $SSH_OPTS target/release/itsgoin "$SSH_HOST:~/bin/itsgoin.new"
|
|
sleep 5
|
|
|
|
echo "=== Swapping anchor + publishing release announcement ==="
|
|
# Kill running anchor, swap binary, post signed release announcement as a
|
|
# one-shot (anchor DB is free during the gap), then restart the anchor.
|
|
# The new announcement post propagates via the normal CDN on next sync.
|
|
sshpass -p "$SSH_PASS" ssh $SSH_OPTS "$SSH_HOST" "
|
|
kill \$(cat ~/itsgoin-anchor.pid 2>/dev/null) 2>/dev/null
|
|
sleep 1
|
|
mv ~/bin/itsgoin.new ~/bin/itsgoin && chmod +x ~/bin/itsgoin
|
|
~/bin/itsgoin ~/itsgoin-anchor-data \
|
|
--announce \
|
|
--ann-category release \
|
|
--ann-channel stable \
|
|
--ann-version ${VERSION} \
|
|
--ann-url https://itsgoin.com/download.html \
|
|
--ann-title 'ItsGoin v${VERSION} available' \
|
|
--ann-body 'See changelog at https://itsgoin.com/download.html' \
|
|
2>&1 | tail -5
|
|
bash ~/bin/start-anchor.sh
|
|
"
|
|
sleep 2
|
|
|
|
echo "=== Verifying anchor ==="
|
|
sshpass -p "$SSH_PASS" ssh $SSH_OPTS "$SSH_HOST" 'ps aux | grep "[i]tsgoin.*daemon"'
|
|
|
|
echo ""
|
|
echo "=== Deploy complete: v${VERSION} ==="
|
|
echo ""
|
|
echo "Windows installer: this script does NOT build the Windows .exe."
|
|
echo "download.html carries a link to itsgoin-${VERSION}-windows-x64-setup.exe"
|
|
echo "The Windows build host needs to produce that file and SCP it to:"
|
|
echo " $SSH_HOST:~/public_html/itsgoin-${VERSION}-windows-x64-setup.exe"
|
|
echo "Until they do, the Windows download link 404s (stable-target pattern)."
|