Skip to content

Aleph Upload Service VM #50

Aleph Upload Service VM

Aleph Upload Service VM #50

name: Aleph Upload Service VM
on:
push:
branches: [main]
paths:
- 'aleph/**'
- 'local-storacha-api/**'
- 'web/package.json'
- 'web/package-lock.json'
- '.github/workflows/aleph-service-vm.yml'
workflow_dispatch:
inputs:
publish:
description: Publish the built rootfs to Aleph
required: false
default: true
type: boolean
deploy_vm:
description: Deploy a ucan-store upload-service VM after publishing
required: false
default: false
type: boolean
rootfs_version:
description: Optional explicit rootfs version
required: false
type: string
rootfs_item_hash:
description: Optional already-published Aleph STORE item hash to deploy without republishing the rootfs
required: false
type: string
vm_name:
description: Aleph VM instance name
required: false
default: ucan-store
type: string
vm_ssh_public_key:
description: Optional SSH public key override for the deployed VM
required: false
type: string
vm_crn_hash:
description: Optional target CRN hash
required: false
type: string
vm_vcpus:
description: Requested vCPU count
required: false
default: '1'
type: string
vm_memory_mib:
description: Requested memory in MiB
required: false
default: '2048'
type: string
vm_rootfs_size_mib:
description: Rootfs disk size in MiB
required: false
default: '20480'
type: string
vm_preferred_country_code:
description: Preferred two-letter country code for automatic CRN selection
required: false
default: DE
type: string
vm_max_crn_attempts:
description: Maximum number of distinct CRNs to try
required: false
default: '5'
type: string
vm_api_hosts:
description: Comma-, space-, or newline-separated Aleph API hosts to try in order during deploy-vm
required: false
default: 'https://api.aleph.im,https://api2.aleph.im,https://api3.aleph.im'
type: string
admin_did:
description: Optional admin DID override. Prefer automatic bootstrap derivation or the UCAN_STORE_ADMIN_DID repository variable.
required: false
type: string
verify_reachability:
description: Verify public reachability before completing deploy. Disable for debugging to keep the VM alive.
required: false
default: true
type: boolean
permissions:
actions: read
contents: read
concurrency:
group: ucan-store-service-vm-${{ github.ref }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
build-rootfs:
if: ${{ github.event_name == 'push' || inputs.rootfs_item_hash == '' }}
uses: NiKrause/relay-button/.github/workflows/aleph-rootfs-build-publish-deploy.yml@codex/ucan-store-caddy-service-origin
with:
profile: ucan-store
publish: ${{ github.event_name == 'push' || inputs.publish }}
rootfs_version: ${{ github.event_name == 'workflow_dispatch' && inputs.rootfs_version || '' }}
rootfs_contract_path: aleph/ucan-store.json
rootfs_driver: docker
tooling_repository: NiKrause/relay-button
tooling_ref: codex/ucan-store-caddy-service-origin
secrets:
ALEPH_PRIVATE_KEY: ${{ secrets.ALEPH_PRIVATE_KEY }}
deploy-vm:
needs: build-rootfs
if: ${{ always() && github.event_name == 'workflow_dispatch' && inputs.deploy_vm && (needs.build-rootfs.result == 'success' || needs.build-rootfs.result == 'skipped') }}
runs-on: ubuntu-24.04
steps:
- name: Deploy ucan-store upload-service VM
id: deploy
uses: NiKrause/relay-button/.github/actions/aleph-vm-deploy@codex/ucan-store-caddy-service-origin
env:
ALEPH_VM_ADMIN_DID: ${{ inputs.admin_did != '' && inputs.admin_did || vars.UCAN_STORE_ADMIN_DID }}
ALEPH_VM_UCAN_STORE_BOOTSTRAP_JSON: ${{ secrets.UCAN_STORE_BOOTSTRAP_JSON }}
with:
profile: ucan-store
name: ${{ inputs.vm_name }}
aleph_private_key: ${{ secrets.ALEPH_PRIVATE_KEY }}
ssh_public_key: ${{ inputs.vm_ssh_public_key != '' && inputs.vm_ssh_public_key || secrets.VM_SSH_PUBLIC_KEY }}
rootfs_item_hash: ${{ inputs.rootfs_item_hash != '' && inputs.rootfs_item_hash || needs.build-rootfs.outputs.rootfs_item_hash }}
rootfs_version: ${{ inputs.rootfs_version != '' && inputs.rootfs_version || needs.build-rootfs.outputs.rootfs_version || 'ucan-store-v0.1.0' }}
rootfs_size_mib: ${{ inputs.vm_rootfs_size_mib }}
api_hosts: ${{ inputs.vm_api_hosts != '' && inputs.vm_api_hosts || vars.ALEPH_VM_API_HOSTS }}
crn_hash: ${{ inputs.vm_crn_hash != '' && inputs.vm_crn_hash || vars.ALEPH_VM_CRN_HASH }}
preferred_country_code: ${{ inputs.vm_preferred_country_code }}
max_crn_attempts: ${{ inputs.vm_max_crn_attempts }}
vcpus: ${{ inputs.vm_vcpus }}
memory_mib: ${{ inputs.vm_memory_mib }}
channel: TEST
instance_custom_domain: ${{ vars.UCAN_STORE_API_DOMAIN }}
enable_caddy_proxy: 'true'
auto_configure: 'true'
verify_reachability: ${{ inputs.verify_reachability }}
ucan_store_bootstrap_mode: ${{ vars.UCAN_STORE_BOOTSTRAP_MODE != '' && vars.UCAN_STORE_BOOTSTRAP_MODE || 'derive-from-aleph-private-key' }}
ucan_store_service_did: ${{ vars.UCAN_STORE_SERVICE_DID }}
ucan_store_service_origin: ${{ vars.UCAN_STORE_SERVICE_ORIGIN }}
ucan_store_pwa_origin: ${{ vars.UCAN_STORE_PWA_ORIGIN }}
ucan_store_allowed_capabilities: ${{ vars.UCAN_STORE_ALLOWED_CAPABILITIES }}
ucan_store_default_user_delegation_expiration: ${{ vars.UCAN_STORE_DEFAULT_USER_DELEGATION_EXPIRATION }}
ucan_store_max_delegation_expiration: ${{ vars.UCAN_STORE_MAX_DELEGATION_EXPIRATION }}
ucan_store_derivation_context: ${{ vars.UCAN_STORE_DERIVATION_CONTEXT }}
ucan_store_derivation_salt: ${{ vars.UCAN_STORE_DERIVATION_SALT }}
ucan_store_space_did_mode: ${{ vars.UCAN_STORE_SPACE_DID_MODE }}
ucan_store_admin_api_token: ${{ secrets.UCAN_STORE_ADMIN_API_TOKEN }}
required_ports_json: >-
[{"port":22,"tcp":true,"udp":false,"purpose":"SSH"},{"port":80,"tcp":true,"udp":false,"purpose":"Temporary setup endpoint"},{"port":443,"tcp":true,"udp":false,"purpose":"HTTPS upload API and service manifest"}]
- name: Summarize upload-service deployment
env:
ROOTFS_ITEM_HASH: ${{ inputs.rootfs_item_hash != '' && inputs.rootfs_item_hash || needs.build-rootfs.outputs.rootfs_item_hash }}
ROOTFS_VERSION: ${{ inputs.rootfs_version != '' && inputs.rootfs_version || needs.build-rootfs.outputs.rootfs_version || 'ucan-store-v0.1.0' }}
ROOTFS_MANIFEST_GATEWAY_URL: ${{ needs.build-rootfs.outputs.rootfs_manifest_gateway_url }}
INSTANCE_ITEM_HASH: ${{ steps.deploy.outputs.instance_item_hash }}
INSTANCE_STATUS: ${{ steps.deploy.outputs.instance_status }}
PORT_FORWARD_AGGREGATE_ITEM_HASH: ${{ steps.deploy.outputs.port_forward_aggregate_item_hash }}
PORT_FORWARD_STATUS: ${{ steps.deploy.outputs.port_forward_status }}
INSTANCE_CUSTOM_DOMAIN: ${{ steps.deploy.outputs.instance_custom_domain }}
INSTANCE_CUSTOM_DOMAIN_URL: ${{ steps.deploy.outputs.instance_custom_domain_url }}
INSTANCE_CUSTOM_DOMAIN_AGGREGATE_ITEM_HASH: ${{ steps.deploy.outputs.instance_custom_domain_aggregate_item_hash }}
INSTANCE_CUSTOM_DOMAIN_STATUS: ${{ steps.deploy.outputs.instance_custom_domain_status }}
CRN_NAME: ${{ steps.deploy.outputs.crn_name }}
CRN_URL: ${{ steps.deploy.outputs.crn_url }}
HOST_IPV4: ${{ steps.deploy.outputs.host_ipv4 }}
IPV6: ${{ steps.deploy.outputs.ipv6 }}
WEB_PROXY_URL: ${{ steps.deploy.outputs.web_proxy_url }}
SSH_COMMAND: ${{ steps.deploy.outputs.ssh_command }}
SETUP_ENDPOINT_OK: ${{ steps.deploy.outputs.setup_endpoint_ok }}
VERIFICATION_OK: ${{ steps.deploy.outputs.verification_ok }}
MAPPED_PORTS_JSON: ${{ steps.deploy.outputs.mapped_ports_json }}
CONFIGURATION_JSON: ${{ steps.deploy.outputs.configuration_json }}
RUNTIME_JSON: ${{ steps.deploy.outputs.runtime_json }}
UCAN_STORE_BOOTSTRAP_ADMIN_DID: ${{ steps.deploy.outputs.ucan_store_bootstrap_admin_did }}
UCAN_STORE_BOOTSTRAP_SPACE_DID: ${{ steps.deploy.outputs.ucan_store_bootstrap_space_did }}
run: |
{
echo '## ucan-store upload-service VM'
echo
echo '### Build input'
echo
echo "- Rootfs version: ${ROOTFS_VERSION:-unknown}"
echo "- Rootfs Aleph item hash: ${ROOTFS_ITEM_HASH:-unknown}"
if [[ -n "${ROOTFS_MANIFEST_GATEWAY_URL:-}" ]]; then
echo "- Rootfs manifest: ${ROOTFS_MANIFEST_GATEWAY_URL}"
fi
echo
echo '### Aleph instance'
echo
echo "- Instance item hash: ${INSTANCE_ITEM_HASH:-unknown}"
echo "- Status: ${INSTANCE_STATUS:-unknown}"
echo "- CRN: ${CRN_NAME:-unknown}"
echo "- CRN URL: ${CRN_URL:-unknown}"
echo "- Host IPv4: ${HOST_IPV4:-unknown}"
echo "- IPv6: ${IPV6:-unknown}"
echo "- Port-forward aggregate: ${PORT_FORWARD_AGGREGATE_ITEM_HASH:-none} (${PORT_FORWARD_STATUS:-unknown})"
echo "- Instance custom domain: ${INSTANCE_CUSTOM_DOMAIN_URL:-not configured}"
echo "- Instance custom-domain aggregate: ${INSTANCE_CUSTOM_DOMAIN_AGGREGATE_ITEM_HASH:-none} (${INSTANCE_CUSTOM_DOMAIN_STATUS:-unknown})"
echo "- Web proxy URL: ${WEB_PROXY_URL:-unknown}"
echo "- Setup endpoint OK: ${SETUP_ENDPOINT_OK:-unknown}"
echo "- Verification OK: ${VERIFICATION_OK:-unknown}"
if [[ -n "${SSH_COMMAND:-}" ]]; then
echo "- SSH: \`${SSH_COMMAND}\`"
fi
echo
echo '> Runtime access is a point-in-time snapshot. If the instance is later forgotten, retained away, or migrated, re-check the INSTANCE item hash before reusing the SSH command or custom-domain result.'
if [[ -n "${UCAN_STORE_BOOTSTRAP_ADMIN_DID:-}" ]]; then
echo "- Derived UCAN admin DID: \`${UCAN_STORE_BOOTSTRAP_ADMIN_DID}\`"
fi
if [[ -n "${UCAN_STORE_BOOTSTRAP_SPACE_DID:-}" ]]; then
echo "- Derived UCAN space DID: \`${UCAN_STORE_BOOTSTRAP_SPACE_DID}\`"
fi
} >> "$GITHUB_STEP_SUMMARY"
node --input-type=module <<'NODE'
import { appendFileSync } from 'node:fs'
const summaryPath = process.env.GITHUB_STEP_SUMMARY
if (!summaryPath) process.exit(0)
function parseJson(name, fallback) {
const raw = process.env[name]
if (!raw) return fallback
try {
return JSON.parse(raw)
} catch {
return fallback
}
}
const configuration = parseJson('CONFIGURATION_JSON', {})
const metadata = configuration?.metadata ?? {}
const bootstrapValidation = metadata?.bootstrap_validation ?? {}
const proofValidation = metadata?.bootstrap_proof_validation ?? {}
const mappedPorts = parseJson('MAPPED_PORTS_JSON', {})
const runtime = parseJson('RUNTIME_JSON', {})
const diagnostics = runtime?.diagnostics ?? {}
const lines = [
'',
'### Service configuration',
'',
`- Upload service URL: ${metadata.upload_service_url ?? 'unknown'}`,
`- Upload service DID: \`${metadata.upload_service_did ?? 'unknown'}\``,
`- Service manifest: ${metadata.service_manifest?.endpoint ?? 'unknown'}`,
`- Revocation URL: ${metadata.revocation_url ?? 'unknown'}`,
`- Receipts URL: ${metadata.receipts_url ?? 'unknown'}`,
`- WebAuthn origin: ${metadata.webauthn_origin ?? 'unknown'}`,
`- Bootstrap package: \`${bootstrapValidation.status ?? 'unknown'}\``,
`- Bootstrap proof: \`${proofValidation.status ?? 'unknown'}\``,
`- Runtime diagnostics: \`${diagnostics.state ?? 'unknown'}\``,
`- Runtime reason: \`${diagnostics.reason ?? 'none'}\``,
'',
'### Port mappings',
'',
'```json',
JSON.stringify(mappedPorts, null, 2),
'```',
'',
]
appendFileSync(summaryPath, `${lines.join('\n')}\n`)
NODE