Aleph Upload Service VM #50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |