Skip to content

Instantly share code, notes, and snippets.

@Cmion
Created March 26, 2025 10:09
Show Gist options
  • Save Cmion/5baa5c9af43baffede0f5166d378fa5a to your computer and use it in GitHub Desktop.
Save Cmion/5baa5c9af43baffede0f5166d378fa5a to your computer and use it in GitHub Desktop.
/// <reference path="./.sst/platform/config.d.ts" />
import { readFileSync } from "node:fs";
export default $config({
app(input) {
return {
name: "hello-zero",
removal: input?.stage === "production" ? "retain" : "remove",
home: "aws",
providers: {
command: true,
"azure-native": {
version: "2.89.2",
subscriptionId: process.env.AZURE_SUBSCRIPTION_ID,
tenantId: process.env.AZURE_TENANT_ID,
location: "eastus2",
},
},
};
},
async run() {
const resourceGroup = new azurenative.resources.ResourceGroup(
"resource-group"
);
const storageAccount = new azurenative.storage.StorageAccount(
"storage-account",
{
resourceGroupName: resourceGroup.name,
location: resourceGroup.location,
sku: {
name: azurenative.storage.SkuName.Standard_LRS,
},
kind: azurenative.storage.Kind.StorageV2,
minimumTlsVersion: azurenative.storage.MinimumTlsVersion.TLS1_2,
allowBlobPublicAccess: false,
allowSharedKeyAccess: true,
defaultToOAuthAuthentication: false,
isHnsEnabled: true,
isSftpEnabled: true,
isLocalUserEnabled: true,
tags: {
"sst:stage": $app.stage,
"sst:app": $app.name,
},
}
);
const storageAccountKey = azurenative.storage
.listStorageAccountKeysOutput({
resourceGroupName: resourceGroup.name,
accountName: storageAccount.name,
})
.keys.apply((keys) => keys[0].value);
const replicationContainer = new azurenative.storage.BlobContainer(
"replication-container",
{
accountName: storageAccount.name,
publicAccess: azurenative.storage.PublicAccess.None,
resourceGroupName: resourceGroup.name,
}
);
const environment = new azurenative.app.ManagedEnvironment(
"app-environment",
{
resourceGroupName: resourceGroup.name,
location: resourceGroup.location,
tags: {
"sst:stage": $app.stage,
"sst:app": $app.name,
},
}
);
const tag = $dev
? `latest`
: JSON.parse(
readFileSync("./node_modules/@rocicorp/zero/package.json").toString()
).version.replace("+", "-");
const image = `registry.hub.docker.com/rocicorp/zero:${tag}`;
const conn = new sst.Secret("PostgresConnectionString");
const zeroAuthSecret = new sst.Secret("ZeroAuthSecret");
const commonEnv = [
{ name: "ZERO_LOG_LEVEL", value: "debug" },
{ name: "ZERO_LITESTREAM_LOG_LEVEL", value: "debug" },
{ name: "ZERO_UPSTREAM_DB", value: conn.value },
{ name: "ZERO_CVR_DB", value: conn.value },
{ name: "ZERO_CHANGE_DB", value: conn.value },
{ name: "ZERO_REPLICA_FILE", value: "/tmp/sync-replica.db" },
{
name: "ZERO_AUTH_SECRET",
value: zeroAuthSecret.value,
},
{ name: "ZERO_LITESTREAM_RESTORE_PARALLELISM", value: "64" },
{ name: "ZERO_APP_ID", value: $app.stage },
{
name: "ZERO_LITESTREAM_BACKUP_URL",
value: $interpolate`abs://${storageAccount.name}@${replicationContainer.name}/backup`,
},
{
name: "LITESTREAM_AZURE_ACCOUNT_KEY", // Needed when using Azure Blob Storage (ABS)
value: storageAccountKey,
},
];
const replicationManager = new azurenative.app.ContainerApp(
"replication-manager",
{
resourceGroupName: resourceGroup.name,
location: resourceGroup.location,
environmentId: environment.id,
configuration: {
ingress: {
external: false,
targetPort: 4849,
transport: azurenative.app.IngressTransportMethod.Auto,
},
},
template: {
containers: [
{
name: "rocicorp-zero",
image,
resources: { cpu: 0.5, memory: "1Gi" }, // Optimized for consumption pricing
env: [
...commonEnv,
{ name: "ZERO_CHANGE_MAX_CONNS", value: "3" },
{ name: "ZERO_NUM_SYNC_WORKERS", value: "0" },
],
probes: [
{
httpGet: {
path: "/",
port: 4849,
},
initialDelaySeconds: 30,
periodSeconds: 5,
type: azurenative.app.Type.Liveness,
},
],
volumeMounts: [
{
mountPath: "/data",
volumeName: "volume",
},
],
},
],
volumes: [
{
name: "volume",
storageType: "EmptyDir",
},
],
scale: {
maxReplicas: 2,
minReplicas: 1,
},
},
tags: {
"sst:stage": $app.stage,
"sst:app": $app.name,
},
}
);
// Deploy your permissions
if (replicationManager)
new command.local.Command(
"zero-permission",
{
dir: process.cwd() + "/apps/api", // Change this to your app path
environment: {
ZERO_UPSTREAM_DB: conn.value,
ZERO_APP_ID: $app.stage,
},
create: "bun run zero-deploy", // Or npx zero-deploy-permissions
triggers: [Date.now()],
},
{
dependsOn: [replicationManager],
}
);
const viewSyncer = new azurenative.app.ContainerApp("view-syncer", {
resourceGroupName: resourceGroup.name,
location: resourceGroup.location,
environmentId: environment.id,
configuration: {
ingress: {
external: true,
targetPort: 4848,
transport: azurenative.app.IngressTransportMethod.Auto,
},
},
template: {
containers: [
{
name: "rocicorp-zero",
image,
resources: { cpu: 1, memory: "2Gi" },
env: [
...commonEnv,
...($dev
? [
{
name: "ZERO_NUM_SYNC_WORKERS",
value: "1",
},
]
: [
{
name: "ZERO_CHANGE_STREAMER_URI",
value: $interpolate`https://${replicationManager.configuration.apply((c) => c?.ingress?.fqdn)}`,
},
{
name: "ZERO_CVR_MAX_CONNS",
value: "10",
},
{ name: "ZERO_UPSTREAM_MAX_CONNS", value: "10" },
]),
],
probes: [
{
httpGet: {
path: "/",
port: 4848,
scheme: "HTTP",
},
initialDelaySeconds: 30,
periodSeconds: 5,
type: azurenative.app.Type.Liveness,
},
],
volumeMounts: [
{
mountPath: "/data",
volumeName: "volume",
},
],
},
],
volumes: [
{
name: "volume",
storageType: "EmptyDir",
},
],
scale: {
maxReplicas: 4,
minReplicas: 1,
},
},
tags: {
"sst:stage": $app.stage,
"sst:app": $app.name,
},
});
return {
viewSyncerURL: viewSyncer.configuration?.ingress?.fqdn,
};
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment