feat: OTA updates for start-tunnel via apt repository (untested)

- Add apt repo publish script (build/apt/publish-deb.sh) for S3-hosted repo
- Add apt source config and GPG key placeholder (apt/)
- Add tunnel.update.check and tunnel.update.apply RPC endpoints
- Wire up update API in tunnel frontend (api service + mock)
- Uses systemd-run --scope to survive service restart during update
This commit is contained in:
Aiden McClelland
2026-02-19 22:38:39 -07:00
parent 9af5b87c92
commit 35f3274f29
12 changed files with 333 additions and 0 deletions

View File

@@ -25,6 +25,9 @@ export abstract class ApiService {
// forwards
abstract addForward(params: AddForwardReq): Promise<null> // port-forward.add
abstract deleteForward(params: DeleteForwardReq): Promise<null> // port-forward.remove
// update
abstract checkUpdate(): Promise<TunnelUpdateResult> // update.check
abstract applyUpdate(): Promise<TunnelUpdateResult> // update.apply
}
export type SubscribeRes = {
@@ -62,3 +65,9 @@ export type AddForwardReq = {
export type DeleteForwardReq = {
source: string
}
export type TunnelUpdateResult = {
status: string
installed: string
candidate: string
}

View File

@@ -16,6 +16,7 @@ import {
DeleteSubnetReq,
LoginReq,
SubscribeRes,
TunnelUpdateResult,
UpsertDeviceReq,
UpsertSubnetReq,
} from './api.service'
@@ -103,6 +104,16 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'port-forward.remove', params })
}
// update
async checkUpdate(): Promise<TunnelUpdateResult> {
return this.rpcRequest({ method: 'update.check', params: {} })
}
async applyUpdate(): Promise<TunnelUpdateResult> {
return this.rpcRequest({ method: 'update.apply', params: {} })
}
// private
private async upsertSubnet(params: UpsertSubnetReq): Promise<null> {

View File

@@ -9,6 +9,7 @@ import {
DeleteSubnetReq,
LoginReq,
SubscribeRes,
TunnelUpdateResult,
UpsertDeviceReq,
UpsertSubnetReq,
} from './api.service'
@@ -196,6 +197,24 @@ export class MockApiService extends ApiService {
return null
}
async checkUpdate(): Promise<TunnelUpdateResult> {
await pauseFor(1000)
return {
status: 'update-available',
installed: '0.4.0-alpha.19',
candidate: '0.4.0-alpha.20',
}
}
async applyUpdate(): Promise<TunnelUpdateResult> {
await pauseFor(2000)
return {
status: 'updating',
installed: '0.4.0-alpha.19',
candidate: '0.4.0-alpha.20',
}
}
private async mockRevision<T>(patch: Operation<T>[]): Promise<void> {
const revision = {
id: ++this.sequence,