owpengram-server/docs/update-service.md
2026-09-01 12:06:31 +03:00

6.5 KiB

Native client update service

cmd/telesrv-update is a standalone HTTP service that provides two Telegram-compatible update surfaces:

  • /current4 and /files/* for the built-in Telegram Desktop updater;
  • /v1/resolve for the main telesrv process to answer help.getAppUpdate for Android, iOS, and other supported builds.

The service has no HTTP upload endpoint. Operators publish a release by placing an immutable artifact in the configured files directory and atomically replacing manifest.json. The catalog validates the file size and SHA-256 before exposing it, so truncated or accidentally replaced packages fail closed.

Quick start

Create the working directories and start with the disabled example catalog:

New-Item -ItemType Directory -Force data\updates\files
Copy-Item deploy\update\manifest.example.json data\updates\manifest.json

go run ./cmd/telesrv-update -check
go run ./cmd/telesrv-update

Check the local endpoints:

Invoke-RestMethod http://127.0.0.1:2402/readyz
Invoke-RestMethod http://127.0.0.1:2402/current4

Connect the main server:

TELESRV_UPDATE_PUBLIC_URL=https://updates.example.test
TELESRV_UPDATE_SERVICE_URL=http://127.0.0.1:2402
TELESRV_UPDATE_REQUEST_TIMEOUT=2s

PUBLIC_URL must be reachable by clients and is advertised as help.getConfig.autoupdate_url_prefix. SERVICE_URL may remain a loopback or private route. When both routes are identical, SERVICE_URL may be omitted. Production deployments should place an HTTPS reverse proxy in front of the service without rewriting /current4 or /files/*.

Standalone service settings:

TELESRV_UPDATE_LISTEN=127.0.0.1:2402
TELESRV_UPDATE_MANIFEST=data/updates/manifest.json
TELESRV_UPDATE_FILES_DIR=data/updates/files

The manifest is reloaded automatically when its timestamp or size changes. A malformed replacement makes readiness and catalog requests return 503; it is never combined with the previously validated snapshot. Validate a candidate before atomically replacing the active file:

go run ./cmd/telesrv-update `
  -manifest .\manifest.next.json `
  -files .\data\updates\files `
  -check

Telegram Desktop contract

TDesktop requests <autoupdate_url_prefix>/current4. A Windows x64 stable release is represented as:

{
  "win64": {
    "stable": {
      "released": 7000007,
      "link": "/files/tx64upd7000007"
    }
  }
}

The client compares released with its numeric AppVersion, downloads the artifact with HTTP Range support, verifies the embedded RSA signature, unpacks it, and only then exposes the normal update banner. A regular EXE or ZIP is not a valid update package.

Build packages with TDesktop's Packer target. The equivalent Windows x64 command is:

Packer.exe `
  -version 7000007 `
  -path Telegram.exe `
  -path Updater.exe `
  -path "modules\x64\d3d\d3dcompiler_47.dll" `
  -target win64

It produces tx64upd7000007 and must report Signature verified! before the artifact is published.

Platform /current4 key Typical package name
Windows x64 win64 tx64upd<build>
Windows ARM64 winarm tarm64upd<build>
Windows x86 win tupdate<build>
macOS Intel mac tmacupd<build>
macOS Apple Silicon armac tarmacupd<build>
Linux linux tlinuxupd<build>

Update signing

The public TDesktop sources contain Telegram's public update key; the matching private key is not published. A custom deployment must establish its own update signing identity:

  1. Generate a dedicated RSA-1024 key pair and keep the private key only in a protected build secret store.
  2. Provide the private key to TDesktop's local DesktopPrivate/packer_private.h.
  3. Embed the matching public key in both the client update verifier and Packer.
  4. Rebuild the bootstrap client and Packer before publishing updates.
  5. Do not rotate the key without a transition client that trusts both identities.

The update service never reads the private key and never creates signatures. It checks SHA-256 and serves an artifact that Packer has already signed. A stock TDesktop binary cannot install a package signed only by a custom key; the first custom client build must be distributed out of band.

HTTP behavior

  • /healthz reports process liveness.
  • /readyz validates the current catalog.
  • /current, /current1 ... /current4 return desktop metadata with Cache-Control: no-cache.
  • /files/<name> serves only artifacts referenced by the current validated catalog, supports GET/HEAD and Range, and emits an immutable cache policy and a SHA-256-based ETag.
  • /v1/resolve returns a newer application release or 204 No Content.

Unknown package names are not exposed merely because a file exists in the directory. Published package names are immutable: changing an active file makes the endpoint return 503 until a matching manifest snapshot is loaded.

Android and iOS

The main server forwards the client platform, current app_version, source, channel, and lang_code to /v1/resolve. The resolver selects localized notes, does not offer an equal or older version, and applies url_by_source when a matching installer/store source is configured.

  • A standalone Android build may open or install an APK URL, but the APK must be signed with the same Android application signing key as the installed build.
  • Google Play and other store builds should use the corresponding store URL; this mechanism does not bypass store policy.
  • iOS may display information returned by help.getAppUpdate, but installation still happens through App Store, TestFlight, or MDM. A normal iOS application cannot replace itself from an arbitrary IPA URL.
  • Set can_not_skip only after confirming that the target release is actually available to every affected client.

Manifest fields

  • desktop.<platform>.<channel>.build: numeric TDesktop AppVersion.
  • file, sha256, size: immutable signed artifact and integrity metadata.
  • apps.<platform>.<channel>.id: stable positive release identifier.
  • version: value compared with the client's initConnection.app_version.
  • notes: localized text keyed by en, ru, ru-ru, and similar codes.
  • url_by_source: source-specific installer or store URL.
  • can_not_skip: whether the client may dismiss the application update.
  • disabled: keep a valid entry as a draft without publishing it.

Supported channels are stable, beta, and alpha. See deploy/update/manifest.example.json for a complete disabled example.