Show commit builds and mandate automatic node updates
Hop-State: A_06FP97T67MMQBS4JYJVZ710 Hop-Proposal: R_06FP97R88JYXTBZTNVXV08G Hop-Task: T_06FP95PNKD7PXAMPWPRWSD8 Hop-Attempt: AT_06FP95PNKE2R52XYNMKVRWG
This commit is contained in:
@@ -31,12 +31,24 @@ The installer creates:
|
|||||||
- Environment file: `/etc/synapsis.env`
|
- Environment file: `/etc/synapsis.env`
|
||||||
- Embedded Turso database: `/var/lib/synapsis/synapsis.db`
|
- Embedded Turso database: `/var/lib/synapsis/synapsis.db`
|
||||||
- Service: `synapsis.service`
|
- Service: `synapsis.service`
|
||||||
|
- Mandatory update timer: `synapsis-update.timer`
|
||||||
|
|
||||||
|
Every node checks `origin/main` about once per minute. When a new commit is available, Synapsis fast-forwards the checkout, replaces the single `backups/latest` database snapshot, installs dependencies, runs migrations, builds, and restarts automatically. The currently deployed commit is shown in the Network Info card and exposed by `/api/version`.
|
||||||
|
|
||||||
|
For a node installed before automatic updates existed, bootstrap the timer once with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo -u synapsis git -C /opt/synapsis pull --ff-only
|
||||||
|
sudo /opt/synapsis/deploy/update.sh
|
||||||
|
```
|
||||||
|
|
||||||
Useful commands:
|
Useful commands:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl status synapsis
|
sudo systemctl status synapsis
|
||||||
|
sudo systemctl status synapsis-update.timer
|
||||||
sudo journalctl -u synapsis -f
|
sudo journalctl -u synapsis -f
|
||||||
|
sudo journalctl -u synapsis-update -f
|
||||||
sudo /opt/synapsis/deploy/update.sh
|
sudo /opt/synapsis/deploy/update.sh
|
||||||
sudo /opt/synapsis/deploy/uninstall.sh
|
sudo /opt/synapsis/deploy/uninstall.sh
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -59,8 +59,12 @@ runuser -u synapsis -- npm --prefix "$APP_DIR" run db:migrate
|
|||||||
runuser -u synapsis -- npm --prefix "$APP_DIR" run build
|
runuser -u synapsis -- npm --prefix "$APP_DIR" run build
|
||||||
|
|
||||||
install -m 0644 "$APP_DIR/deploy/synapsis.service" /etc/systemd/system/synapsis.service
|
install -m 0644 "$APP_DIR/deploy/synapsis.service" /etc/systemd/system/synapsis.service
|
||||||
|
install -m 0644 "$APP_DIR/deploy/synapsis-update.service" /etc/systemd/system/synapsis-update.service
|
||||||
|
install -m 0644 "$APP_DIR/deploy/synapsis-update.timer" /etc/systemd/system/synapsis-update.timer
|
||||||
|
runuser -u synapsis -- git -C "$APP_DIR" rev-parse HEAD | install -m 0644 -o synapsis -g synapsis /dev/stdin "$DATA_DIR/deployed-commit"
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable --now synapsis
|
systemctl enable --now synapsis
|
||||||
|
systemctl enable --now synapsis-update.timer
|
||||||
|
|
||||||
echo "Synapsis is listening on http://127.0.0.1:${PORT}"
|
echo "Synapsis is listening on http://127.0.0.1:${PORT}"
|
||||||
echo "Edit $ENV_FILE, run $APP_DIR/deploy/update.sh, then configure your own reverse proxy."
|
echo "Edit $ENV_FILE, run $APP_DIR/deploy/update.sh, then configure your own reverse proxy."
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Update Synapsis to the latest main commit
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
ConditionPathExists=/opt/synapsis/.git
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/opt/synapsis/deploy/update.sh
|
||||||
|
TimeoutStartSec=30min
|
||||||
|
UMask=0027
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Keep Synapsis on the latest main commit
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=1min
|
||||||
|
OnUnitInactiveSec=1min
|
||||||
|
RandomizedDelaySec=15s
|
||||||
|
AccuracySec=5s
|
||||||
|
Persistent=true
|
||||||
|
Unit=synapsis-update.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
+5
-1
@@ -10,8 +10,12 @@ if [[ ${EUID} -ne 0 ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
systemctl disable --now synapsis-update.timer 2>/dev/null || true
|
||||||
|
systemctl stop synapsis-update.service 2>/dev/null || true
|
||||||
systemctl disable --now synapsis 2>/dev/null || true
|
systemctl disable --now synapsis 2>/dev/null || true
|
||||||
rm -f /etc/systemd/system/synapsis.service
|
rm -f /etc/systemd/system/synapsis.service \
|
||||||
|
/etc/systemd/system/synapsis-update.service \
|
||||||
|
/etc/systemd/system/synapsis-update.timer
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
rm -rf "$APP_DIR"
|
rm -rf "$APP_DIR"
|
||||||
|
|
||||||
|
|||||||
+65
-7
@@ -4,6 +4,8 @@ set -euo pipefail
|
|||||||
APP_DIR="${APP_DIR:-/opt/synapsis}"
|
APP_DIR="${APP_DIR:-/opt/synapsis}"
|
||||||
DATA_DIR="${DATA_DIR:-/var/lib/synapsis}"
|
DATA_DIR="${DATA_DIR:-/var/lib/synapsis}"
|
||||||
ENV_FILE="${ENV_FILE:-/etc/synapsis.env}"
|
ENV_FILE="${ENV_FILE:-/etc/synapsis.env}"
|
||||||
|
BRANCH="${BRANCH:-main}"
|
||||||
|
DEPLOYED_COMMIT_FILE="$DATA_DIR/deployed-commit"
|
||||||
|
|
||||||
if [[ ${EUID} -ne 0 ]]; then
|
if [[ ${EUID} -ne 0 ]]; then
|
||||||
echo "Run this updater as root." >&2
|
echo "Run this updater as root." >&2
|
||||||
@@ -17,27 +19,83 @@ set -a
|
|||||||
source "$ENV_FILE"
|
source "$ENV_FILE"
|
||||||
set +a
|
set +a
|
||||||
|
|
||||||
|
install_update_units() {
|
||||||
|
units_changed=0
|
||||||
|
for unit in synapsis.service synapsis-update.service synapsis-update.timer; do
|
||||||
|
if ! cmp -s "$APP_DIR/deploy/$unit" "/etc/systemd/system/$unit"; then
|
||||||
|
install -m 0644 "$APP_DIR/deploy/$unit" "/etc/systemd/system/$unit"
|
||||||
|
units_changed=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$units_changed" == "1" ]]; then
|
||||||
|
systemctl daemon-reload
|
||||||
|
fi
|
||||||
|
if ! systemctl is-enabled --quiet synapsis-update.timer; then
|
||||||
|
systemctl enable synapsis-update.timer
|
||||||
|
fi
|
||||||
|
if ! systemctl is-active --quiet synapsis-update.timer; then
|
||||||
|
systemctl start synapsis-update.timer
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${SYNAPSIS_APPLY_UPDATE:-0}" != "1" ]]; then
|
||||||
|
runuser -u synapsis -- git -C "$APP_DIR" fetch --prune origin "+refs/heads/$BRANCH:refs/remotes/origin/$BRANCH"
|
||||||
|
|
||||||
|
current_commit="$(runuser -u synapsis -- git -C "$APP_DIR" rev-parse HEAD)"
|
||||||
|
target_commit="$(runuser -u synapsis -- git -C "$APP_DIR" rev-parse "origin/$BRANCH")"
|
||||||
|
deployed_commit=""
|
||||||
|
if [[ -f "$DEPLOYED_COMMIT_FILE" ]]; then
|
||||||
|
deployed_commit="$(tr -d '[:space:]' < "$DEPLOYED_COMMIT_FILE")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$deployed_commit" && "$deployed_commit" == "$target_commit" ]]; then
|
||||||
|
install_update_units
|
||||||
|
echo "Synapsis is already current at ${target_commit:0:7}."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$current_commit" != "$target_commit" ]]; then
|
||||||
|
if ! runuser -u synapsis -- git -C "$APP_DIR" merge-base --is-ancestor "$current_commit" "$target_commit"; then
|
||||||
|
echo "Cannot auto-update: local checkout has diverged from origin/$BRANCH." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
runuser -u synapsis -- git -C "$APP_DIR" merge --ff-only "origin/$BRANCH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec env SYNAPSIS_APPLY_UPDATE=1 APP_DIR="$APP_DIR" DATA_DIR="$DATA_DIR" ENV_FILE="$ENV_FILE" BRANCH="$BRANCH" bash "$APP_DIR/deploy/update.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
systemctl stop synapsis
|
systemctl stop synapsis
|
||||||
trap 'systemctl start synapsis' EXIT
|
trap 'systemctl start synapsis' EXIT
|
||||||
|
|
||||||
if [[ -f "${DATABASE_PATH:-$DATA_DIR/synapsis.db}" ]]; then
|
if [[ -f "${DATABASE_PATH:-$DATA_DIR/synapsis.db}" ]]; then
|
||||||
database_file="${DATABASE_PATH:-$DATA_DIR/synapsis.db}"
|
database_file="${DATABASE_PATH:-$DATA_DIR/synapsis.db}"
|
||||||
backup_dir="$DATA_DIR/backups/$(date -u +%Y%m%dT%H%M%SZ)"
|
backup_root="$DATA_DIR/backups"
|
||||||
install -d -o synapsis -g synapsis -m 0750 "$backup_dir"
|
backup_staging="$backup_root/.latest.$$"
|
||||||
cp "$database_file" "$backup_dir/synapsis.db"
|
backup_dir="$backup_root/latest"
|
||||||
|
install -d -o synapsis -g synapsis -m 0750 "$backup_root"
|
||||||
|
rm -rf "$backup_staging"
|
||||||
|
install -d -o synapsis -g synapsis -m 0750 "$backup_staging"
|
||||||
|
cp "$database_file" "$backup_staging/synapsis.db"
|
||||||
for suffix in -wal -shm; do
|
for suffix in -wal -shm; do
|
||||||
if [[ -f "${database_file}${suffix}" ]]; then
|
if [[ -f "${database_file}${suffix}" ]]; then
|
||||||
cp "${database_file}${suffix}" "$backup_dir/synapsis.db${suffix}"
|
cp "${database_file}${suffix}" "$backup_staging/synapsis.db${suffix}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
chown -R synapsis:synapsis "$backup_dir"
|
chown -R synapsis:synapsis "$backup_staging"
|
||||||
|
rm -rf "$backup_dir"
|
||||||
|
mv "$backup_staging" "$backup_dir"
|
||||||
|
find "$backup_root" -mindepth 1 -maxdepth 1 ! -name latest -exec rm -rf {} +
|
||||||
fi
|
fi
|
||||||
|
|
||||||
runuser -u synapsis -- git -C "$APP_DIR" pull --ff-only
|
|
||||||
runuser -u synapsis -- npm --prefix "$APP_DIR" ci
|
runuser -u synapsis -- npm --prefix "$APP_DIR" ci
|
||||||
runuser -u synapsis -- npm --prefix "$APP_DIR" run db:migrate
|
runuser -u synapsis -- npm --prefix "$APP_DIR" run db:migrate
|
||||||
runuser -u synapsis -- npm --prefix "$APP_DIR" run build
|
runuser -u synapsis -- npm --prefix "$APP_DIR" run build
|
||||||
|
|
||||||
|
deployed_commit="$(runuser -u synapsis -- git -C "$APP_DIR" rev-parse HEAD)"
|
||||||
|
printf '%s\n' "$deployed_commit" | install -m 0644 -o synapsis -g synapsis /dev/stdin "$DEPLOYED_COMMIT_FILE"
|
||||||
|
install_update_units
|
||||||
systemctl start synapsis
|
systemctl start synapsis
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
echo "Synapsis updated successfully."
|
echo "Synapsis updated successfully to ${deployed_commit:0:7}."
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
|
||||||
|
function getBuildCommit(): string {
|
||||||
|
if (process.env.APP_COMMIT) return process.env.APP_COMMIT;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return execFileSync('git', ['rev-parse', 'HEAD'], {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
encoding: 'utf8',
|
||||||
|
}).trim();
|
||||||
|
} catch {
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
|
env: {
|
||||||
|
APP_COMMIT: getBuildCommit(),
|
||||||
|
APP_BUILD_DATE: process.env.APP_BUILD_DATE || new Date().toISOString(),
|
||||||
|
},
|
||||||
|
|
||||||
// Turso ships a platform-native Node module and must remain a runtime dependency.
|
// Turso ships a platform-native Node module and must remain a runtime dependency.
|
||||||
serverExternalPackages: ['@tursodatabase/database'],
|
serverExternalPackages: ['@tursodatabase/database'],
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export function RightSidebar() {
|
|||||||
});
|
});
|
||||||
const [version, setVersion] = useState<{
|
const [version, setVersion] = useState<{
|
||||||
version: string;
|
version: string;
|
||||||
|
commit: string | null;
|
||||||
buildDate: string | null;
|
buildDate: string | null;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ export function RightSidebar() {
|
|||||||
fetch('/api/version')
|
fetch('/api/version')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => setVersion(data))
|
.then(data => setVersion(data))
|
||||||
.catch(() => setVersion({ version: 'unknown', buildDate: null }));
|
.catch(() => setVersion({ version: 'unknown', commit: null, buildDate: null }));
|
||||||
|
|
||||||
return () => window.removeEventListener('synapsis:node-updated', handleNodeUpdated);
|
return () => window.removeEventListener('synapsis:node-updated', handleNodeUpdated);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -152,8 +153,12 @@ export function RightSidebar() {
|
|||||||
<h3 style={{ fontWeight: 600, marginBottom: '12px' }}>Network Info</h3>
|
<h3 style={{ fontWeight: 600, marginBottom: '12px' }}>Network Info</h3>
|
||||||
<p style={{ color: 'var(--foreground-secondary)', fontSize: '13px' }}>
|
<p style={{ color: 'var(--foreground-secondary)', fontSize: '13px' }}>
|
||||||
Running{' '}
|
Running{' '}
|
||||||
<a href="https://synapsis.social" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)' }}>Synapsis</a>
|
<a href="https://synapsis.gnosyslabs.xyz" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)' }}>Synapsis</a>
|
||||||
{version?.version ? ` ${version.version}` : ''}
|
{version?.commit
|
||||||
|
? ` ${version.commit.slice(0, 7)}`
|
||||||
|
: version?.version
|
||||||
|
? ` ${version.version}`
|
||||||
|
: ''}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{nodeInfo.admins.length > 0 && (
|
{nodeInfo.admins.length > 0 && (
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import { db, nodes, users, posts } from '@/db';
|
import { db, nodes, users, posts } from '@/db';
|
||||||
import { eq, sql } from 'drizzle-orm';
|
import { eq, sql } from 'drizzle-orm';
|
||||||
import type { SwarmAnnouncement, SwarmNodeInfo, SwarmCapability } from './types';
|
import type { SwarmAnnouncement, SwarmNodeInfo, SwarmCapability } from './types';
|
||||||
|
import { getCurrentBuildInfo } from '@/lib/version';
|
||||||
import { upsertSwarmNode, getSeedNodes, markNodeSuccess, markNodeFailure } from './registry';
|
import { upsertSwarmNode, getSeedNodes, markNodeSuccess, markNodeFailure } from './registry';
|
||||||
import {
|
import {
|
||||||
getCanonicalSwarmSeedDomain,
|
getCanonicalSwarmSeedDomain,
|
||||||
@@ -22,6 +23,7 @@ const PUBLIC_SWARM_DOMAIN_ERROR = 'Public swarm participation requires a real IC
|
|||||||
*/
|
*/
|
||||||
export async function buildAnnouncement(): Promise<SwarmAnnouncement> {
|
export async function buildAnnouncement(): Promise<SwarmAnnouncement> {
|
||||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||||
|
const buildInfo = getCurrentBuildInfo();
|
||||||
|
|
||||||
let name = 'Synapsis Node';
|
let name = 'Synapsis Node';
|
||||||
let description: string | undefined;
|
let description: string | undefined;
|
||||||
@@ -61,7 +63,7 @@ export async function buildAnnouncement(): Promise<SwarmAnnouncement> {
|
|||||||
description,
|
description,
|
||||||
logoUrl,
|
logoUrl,
|
||||||
publicKey,
|
publicKey,
|
||||||
softwareVersion: '0.1.0', // TODO: Get from package.json
|
softwareVersion: buildInfo.commit || buildInfo.version,
|
||||||
userCount,
|
userCount,
|
||||||
postCount,
|
postCount,
|
||||||
capabilities,
|
capabilities,
|
||||||
|
|||||||
Reference in New Issue
Block a user