AWS S3 Storage
Store your Hot Updater bundles in AWS S3 or any S3-compatible storage service (including Cloudflare R2).
Installation
npm install @hot-updater/aws --save-devFor a custom server, install the runtime dependency in the server project:
npm install @hot-updater/server @hot-updater/awsSetup
For a complete provider deployment, follow the AWS setup. For an existing bucket or a custom server, use the storage configuration below; you do not need to provision the provider's database and server to use this adapter.
Configuration
import type { S3ClientConfig } from "@aws-sdk/client-s3";
interface S3StorageConfig extends S3ClientConfig {
bucketName: string; // S3 bucket name
basePath?: string; // Optional base path within bucket
getDownloadUrl?: (input: { storageUri: string }) => Promise<{ url: string }>;
}The inherited AWS SDK options include region, endpoint, and credentials
(including credential providers and temporary session tokens). If credentials
is omitted, the AWS SDK default credential chain is used. This lets you rely
on local AWS CLI sessions, AWS_PROFILE, shared config files, or other
standard AWS credential sources.
Usage
AWS S3
import { s3Storage } from "@hot-updater/aws";
import { defineConfig } from "hot-updater";
export default defineConfig({
// ... build, database, plugins, and updateStrategy
storage: s3Storage({
region: "us-east-1",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
bucketName: "my-hot-updater-bucket",
}),
});Cloudflare R2
Use r2Storage for R2, including the
managed Cloudflare Worker. It records r2:// locations that the Worker can
resolve. s3Storage always records s3:// locations, even with an R2 endpoint,
so it requires a server that registers an s3 storage adapter.
Protocol
s3://bucket/encoded/key
For example, s3://my-bucket/releases/1.0.0/bundle%20ios.zip is stored in the
storageUri field. Keys retain their slash hierarchy and encode each segment.
Capabilities
s3Storage implements streaming upload, streaming Web-response reads,
existence checks, exact deletion, and download URLs. A download URL is an S3
presigned URL that expires in an hour, signed with the adapter's credentials:
devices download from the bucket, which stays private. Pass getDownloadUrl
to return another URL instead.
For CloudFront delivery, pass cloudFrontDownloadUrl(...) as getDownloadUrl:
import { cloudFrontDownloadUrl, s3Storage } from "@hot-updater/aws";
const storage = s3Storage({
region: process.env.AWS_REGION!,
bucketName: process.env.AWS_S3_BUNDLES_BUCKET!,
getDownloadUrl: cloudFrontDownloadUrl({
publicBaseUrl: "https://updates.example.com",
keyPairId: process.env.CLOUDFRONT_KEY_PAIR_ID!,
ssmRegion: process.env.AWS_REGION!,
ssmParameterName: process.env.CLOUDFRONT_PRIVATE_KEY_PARAMETER!,
}),
});An update check returns a URL for every file of its Bundle, hundreds for an
app with many assets. cloudFrontDownloadUrl signs one custom policy for a
Bundle's files (bundles/<bundleId>/*) and one for the shared,
content-addressed assets (assets/*), and reuses each until half of
expiresSeconds (one year by default) has passed. A URL holder can therefore
read any shared asset whose SHA-256 name it knows, which no one can list
without a manifest that names it.
Use the CloudFront distribution's artifact domain and public key ID. The SSM
parameter stores JSON containing privateKey; grant the server role permission
to read and decrypt it. These settings and the private key remain on the
server. If your secret store is elsewhere, provide
getPrivateKey: async () => privateKeyPem instead of the two SSM options.
Managed AWS initialization configures its CloudFront delivery path for you.
Maintenance
s3Storage provides the object listing and exact deletion capabilities used by
hot-updater storage prune. Follow the Storage Cleanup
guide to remove unreferenced bundle objects and shared assets safely.
Use a separate basePath for every database or environment that shares a
bucket. Pruning assumes that the configured database owns every object below
its storage prefix; without basePath, that is the whole bucket. Do not run
hot-updater storage prune --yes while another active Hot Updater database
uses the same bucket and prefix.
Custom server usage
Use s3Storage with createHotUpdater for self-hosted servers:
import { s3Storage } from "@hot-updater/aws";
import { createHotUpdater } from "@hot-updater/server";
import { apiKeys, insights } from "@hot-updater/server/plugins";
const hotUpdater = createHotUpdater({
database,
plugins: [insights(), apiKeys()],
storage: s3Storage({
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
bucketName: process.env.AWS_S3_BUNDLES_BUCKET!,
}),
// ... other options
});