| 1 | name: "smbCloud Deploy" |
| 2 | description: "Install the smbCloud CLI (smb) and deploy an app non-interactively from CI." |
| 3 | author: "smbCloud (Splitfire AB)" |
| 4 | |
| 5 | branding: |
| 6 | icon: "upload-cloud" |
| 7 | color: "blue" |
| 8 | |
| 9 | inputs: |
| 10 | token: |
| 11 | description: >- |
| 12 | smbCloud auth token contents. Provision it once (see docs/ci.md in |
| 13 | smbcloud-cli) and pass it from a CI secret. Written to the CLI state dir |
| 14 | so `smb` is authenticated without an interactive `smb login`. |
| 15 | required: true |
| 16 | environment: |
| 17 | description: "smbCloud environment: production or dev." |
| 18 | required: false |
| 19 | default: "production" |
| 20 | project: |
| 21 | description: >- |
| 22 | Sub-project name to deploy for monorepo configs with [[projects]] entries. |
| 23 | Matches the `name` field in .smb/config.toml. Omit for single-project configs. |
| 24 | required: false |
| 25 | default: "" |
| 26 | working-directory: |
| 27 | description: "Directory containing .smb/config.toml. Defaults to the repo root." |
| 28 | required: false |
| 29 | default: "." |
| 30 | version: |
| 31 | description: >- |
| 32 | smbCloud CLI version to install (e.g. 0.5.0 or v0.5.0). Use 'latest' for |
| 33 | the most recent GitHub release. |
| 34 | required: false |
| 35 | default: "latest" |
| 36 | ssh-private-key: |
| 37 | description: >- |
| 38 | Optional SSH private key contents for rsync/SSH deploy paths. Installed at |
| 39 | ~/.ssh/<ssh-key-name>. |
| 40 | required: false |
| 41 | default: "" |
| 42 | ssh-key-name: |
| 43 | description: >- |
| 44 | Filename for the installed SSH private key under ~/.ssh. smbCloud rsync |
| 45 | deploys expect the account-scoped name `id_<user-id>@smbcloud`. Required |
| 46 | when ssh-private-key is set. |
| 47 | required: false |
| 48 | default: "" |
| 49 | ssh-known-hosts: |
| 50 | description: >- |
| 51 | Optional known_hosts entries appended to ~/.ssh/known_hosts. Recommended |
| 52 | for SSH deploys instead of disabling host-key checking. |
| 53 | required: false |
| 54 | default: "" |
| 55 | args: |
| 56 | description: "Extra arguments appended to the `smb deploy` invocation." |
| 57 | required: false |
| 58 | default: "" |
| 59 | cli-repository: |
| 60 | description: "GitHub repository to download the CLI binary from." |
| 61 | required: false |
| 62 | default: "smbcloudXYZ/smbcloud-cli" |
| 63 | |
| 64 | outputs: |
| 65 | smb-path: |
| 66 | description: "Absolute path to the installed smb binary." |
| 67 | value: ${{ steps.install.outputs.smb-path }} |
| 68 | version: |
| 69 | description: "Resolved CLI version that was installed." |
| 70 | value: ${{ steps.install.outputs.version }} |
| 71 | |
| 72 | runs: |
| 73 | using: "composite" |
| 74 | steps: |
| 75 | - name: Install smbCloud CLI |
| 76 | id: install |
| 77 | shell: bash |
| 78 | env: |
| 79 | INPUT_VERSION: ${{ inputs.version }} |
| 80 | INPUT_CLI_REPOSITORY: ${{ inputs.cli-repository }} |
| 81 | run: | |
| 82 | set -euo pipefail |
| 83 | |
| 84 | case "$(uname -s)" in |
| 85 | Linux) os="linux" ;; |
| 86 | Darwin) os="macos" ;; |
| 87 | MINGW*|MSYS*|CYGWIN*) os="win" ;; |
| 88 | *) echo "::error::Unsupported OS: $(uname -s)"; exit 1 ;; |
| 89 | esac |
| 90 | |
| 91 | case "$(uname -m)" in |
| 92 | x86_64|amd64) arch="amd64" ;; |
| 93 | arm64|aarch64) arch="arm64" ;; |
| 94 | *) echo "::error::Unsupported architecture: $(uname -m)"; exit 1 ;; |
| 95 | esac |
| 96 | |
| 97 | asset="smb-${os}-${arch}" |
| 98 | bin_name="smb" |
| 99 | if [ "$os" = "win" ]; then |
| 100 | asset="${asset}.exe" |
| 101 | bin_name="smb.exe" |
| 102 | fi |
| 103 | |
| 104 | if [ "${INPUT_VERSION}" = "latest" ]; then |
| 105 | url="https://github.com/${INPUT_CLI_REPOSITORY}/releases/latest/download/${asset}" |
| 106 | else |
| 107 | tag="${INPUT_VERSION#v}" |
| 108 | url="https://github.com/${INPUT_CLI_REPOSITORY}/releases/download/v${tag}/${asset}" |
| 109 | fi |
| 110 | |
| 111 | install_dir="${RUNNER_TEMP:-/tmp}/smbcloud-cli-bin" |
| 112 | mkdir -p "$install_dir" |
| 113 | smb_path="${install_dir}/${bin_name}" |
| 114 | |
| 115 | echo "Downloading ${asset} from ${url}" |
| 116 | curl --fail --location --retry 3 --retry-delay 2 --proto '=https' --tlsv1.2 \ |
| 117 | --output "$smb_path" "$url" |
| 118 | chmod +x "$smb_path" |
| 119 | |
| 120 | echo "$install_dir" >> "$GITHUB_PATH" |
| 121 | echo "smb-path=${smb_path}" >> "$GITHUB_OUTPUT" |
| 122 | |
| 123 | resolved_version="$("$smb_path" --version 2>/dev/null | awk '{print $NF}' || echo "${INPUT_VERSION}")" |
| 124 | echo "version=${resolved_version}" >> "$GITHUB_OUTPUT" |
| 125 | echo "Installed smb ${resolved_version} at ${smb_path}" |
| 126 | |
| 127 | - name: Configure smbCloud credentials |
| 128 | shell: bash |
| 129 | env: |
| 130 | INPUT_TOKEN: ${{ inputs.token }} |
| 131 | INPUT_ENVIRONMENT: ${{ inputs.environment }} |
| 132 | INPUT_SSH_PRIVATE_KEY: ${{ inputs.ssh-private-key }} |
| 133 | INPUT_SSH_KEY_NAME: ${{ inputs.ssh-key-name }} |
| 134 | INPUT_SSH_KNOWN_HOSTS: ${{ inputs.ssh-known-hosts }} |
| 135 | run: | |
| 136 | set -euo pipefail |
| 137 | |
| 138 | if [ -z "${INPUT_TOKEN}" ]; then |
| 139 | echo "::error::'token' input is required." |
| 140 | exit 1 |
| 141 | fi |
| 142 | |
| 143 | case "${INPUT_ENVIRONMENT}" in |
| 144 | production) smb_dir=".smb" ;; |
| 145 | dev) smb_dir=".smb-dev" ;; |
| 146 | *) echo "::error::Invalid environment '${INPUT_ENVIRONMENT}' (use 'production' or 'dev')."; exit 1 ;; |
| 147 | esac |
| 148 | |
| 149 | state_dir="${HOME}/${smb_dir}" |
| 150 | mkdir -p "$state_dir" |
| 151 | umask 077 |
| 152 | printf '%s' "${INPUT_TOKEN}" > "${state_dir}/token" |
| 153 | chmod 600 "${state_dir}/token" |
| 154 | echo "Wrote auth token to ${state_dir}/token" |
| 155 | |
| 156 | if [ -n "${INPUT_SSH_PRIVATE_KEY}" ]; then |
| 157 | if [ -z "${INPUT_SSH_KEY_NAME}" ]; then |
| 158 | echo "::error::'ssh-key-name' is required when 'ssh-private-key' is set." |
| 159 | exit 1 |
| 160 | fi |
| 161 | ssh_dir="${HOME}/.ssh" |
| 162 | mkdir -p "$ssh_dir" |
| 163 | chmod 700 "$ssh_dir" |
| 164 | key_path="${ssh_dir}/${INPUT_SSH_KEY_NAME}" |
| 165 | printf '%s\n' "${INPUT_SSH_PRIVATE_KEY}" > "$key_path" |
| 166 | chmod 600 "$key_path" |
| 167 | echo "Installed SSH key at ${key_path}" |
| 168 | |
| 169 | if [ -n "${INPUT_SSH_KNOWN_HOSTS}" ]; then |
| 170 | printf '%s\n' "${INPUT_SSH_KNOWN_HOSTS}" >> "${ssh_dir}/known_hosts" |
| 171 | chmod 600 "${ssh_dir}/known_hosts" |
| 172 | echo "Appended entries to ${ssh_dir}/known_hosts" |
| 173 | fi |
| 174 | fi |
| 175 | |
| 176 | - name: Deploy |
| 177 | shell: bash |
| 178 | working-directory: ${{ inputs.working-directory }} |
| 179 | env: |
| 180 | # The CLI's --ci flag binds env SMB_CI and parses it as a strict bool, |
| 181 | # so this must be "true"/"false", not "1". We also pass --ci explicitly. |
| 182 | SMB_CI: "true" |
| 183 | INPUT_ENVIRONMENT: ${{ inputs.environment }} |
| 184 | INPUT_PROJECT: ${{ inputs.project }} |
| 185 | INPUT_ARGS: ${{ inputs.args }} |
| 186 | run: | |
| 187 | set -euo pipefail |
| 188 | |
| 189 | cmd=(smb --ci --environment "${INPUT_ENVIRONMENT}" deploy) |
| 190 | if [ -n "${INPUT_PROJECT}" ]; then |
| 191 | cmd+=(--project "${INPUT_PROJECT}") |
| 192 | fi |
| 193 | if [ -n "${INPUT_ARGS}" ]; then |
| 194 | # shellcheck disable=SC2206 |
| 195 | extra=(${INPUT_ARGS}) |
| 196 | cmd+=("${extra[@]}") |
| 197 | fi |
| 198 | |
| 199 | echo "Running: ${cmd[*]}" |
| 200 | "${cmd[@]}" |