Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 35 additions & 17 deletions .github/actions/arcane-deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ Deploy Docker Compose stacks to [Arcane](https://github.com/getarcaneapp/arcane)
git-token: ${{ secrets.REPO_TOKEN }}
```

**SSH deploy key** — use a GitHub deploy key instead of a PAT:

```yaml
- name: Deploy stacks to Arcane
uses: nsheaps/github-actions/.github/actions/arcane-deploy@main
with:
arcane-url: ${{ secrets.ARCANE_URL }}
arcane-api-key: ${{ secrets.ARCANE_API_KEY }}
environment-id: '1'
compose-dir: stacks
auth-type: ssh
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
```

> **Note:** When `auth-type: ssh`, the `repository-url` defaults to the SSH format (`git@github.com:owner/repo.git`) automatically. You can override it with `repository-url` if needed.

**With workflow environment variables** (available to subsequent steps, not inside containers):

```yaml
Expand All @@ -61,23 +77,25 @@ Deploy Docker Compose stacks to [Arcane](https://github.com/getarcaneapp/arcane)

## Inputs

| Input | Required | Default | Description |
| ------------------ | -------- | --------------------- | ------------------------------------------------------------------------------- |
| `arcane-url` | Yes | | Base URL of the Arcane instance (must use HTTPS) |
| `arcane-api-key` | Yes | | API key (from Arcane Settings > API Keys) |
| `environment-id` | Yes | | Arcane environment ID |
| `compose-dir` | No | | Directory to scan for compose files (up to 2 levels deep) |
| `compose-files` | No | | Newline-separated list of compose file paths |
| `repository-url` | No | GitHub repo HTTPS URL | Git URL for Arcane to clone |
| `repository-name` | No | GitHub repo name | Display name in Arcane |
| `branch` | No | Triggering branch | Branch to sync from |
| `auth-type` | No | `http` | Git auth type: `none` or `http` |
| `git-token` | No | | Token for HTTP git auth. Required when auth-type=http. |
| `auto-sync` | No | `true` | Enable Arcane auto-sync polling |
| `sync-interval` | No | `5` | Minutes between auto-sync polls |
| `trigger-sync` | No | `true` | Trigger immediate sync after create/update |
| `sync-name-prefix` | No | GitHub repo name | Prefix for sync names in Arcane |
| `env-vars` | No | | Runner env vars (`KEY=VALUE` per line) for subsequent steps. Values are masked. |
| Input | Required | Default | Description |
| --------------------------- | -------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `arcane-url` | Yes | | Base URL of the Arcane instance (must use HTTPS) |
| `arcane-api-key` | Yes | | API key (from Arcane Settings > API Keys) |
| `environment-id` | Yes | | Arcane environment ID |
| `compose-dir` | No | | Directory to scan for compose files (up to 2 levels deep) |
| `compose-files` | No | | Newline-separated list of compose file paths |
| `repository-url` | No | GitHub repo HTTPS URL | Git URL for Arcane to clone. Defaults to SSH format when auth-type=ssh. |
| `repository-name` | No | GitHub repo name | Display name in Arcane |
| `branch` | No | Triggering branch | Branch to sync from |
| `auth-type` | No | `http` | Git auth type: `none`, `http`, or `ssh` |
| `git-token` | No | | Token for HTTP git auth. Required when auth-type=http. |
| `ssh-private-key` | No | | SSH private key for git auth (e.g. deploy key). Required when auth-type=ssh. |
| `ssh-host-key-verification` | No | `accept_new` | SSH host key verification mode: `accept_new`, `accept_all`, or `reject`. **Warning:** `accept_all` disables host key checking and should only be used for testing. |
| `auto-sync` | No | `true` | Enable Arcane auto-sync polling |
| `sync-interval` | No | `5` | Minutes between auto-sync polls |
| `trigger-sync` | No | `true` | Trigger immediate sync after create/update |
| `sync-name-prefix` | No | GitHub repo name | Prefix for sync names in Arcane |
| `env-vars` | No | | Runner env vars (`KEY=VALUE` per line) for subsequent steps. Values are masked. |

## Outputs

Expand Down
51 changes: 47 additions & 4 deletions .github/actions/arcane-deploy/action.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ API_KEY="${INPUT_ARCANE_API_KEY}"
ENV_ID="${INPUT_ENVIRONMENT_ID}"
COMPOSE_DIR="${INPUT_COMPOSE_DIR:-}"
COMPOSE_FILES_INPUT="${INPUT_COMPOSE_FILES:-}"
REPO_URL="${INPUT_REPOSITORY_URL:-https://github.com/${GITHUB_REPOSITORY}.git}"
# Default to SSH URL format when auth-type is ssh, HTTPS otherwise
if [[ -z "${INPUT_REPOSITORY_URL:-}" && "${INPUT_AUTH_TYPE:-http}" == "ssh" ]]; then
REPO_URL="git@github.com:${GITHUB_REPOSITORY}.git"
else
REPO_URL="${INPUT_REPOSITORY_URL:-https://github.com/${GITHUB_REPOSITORY}.git}"
fi
REPO_NAME="${INPUT_REPOSITORY_NAME:-${GITHUB_REPOSITORY##*/}}"
BRANCH="${INPUT_BRANCH:-${GITHUB_REF_NAME:-main}}"
AUTH_TYPE="${INPUT_AUTH_TYPE:-http}"
GIT_TOKEN="${INPUT_GIT_TOKEN:-}"
SSH_PRIVATE_KEY="${INPUT_SSH_PRIVATE_KEY:-}"
SSH_HOST_KEY_VERIFICATION="${INPUT_SSH_HOST_KEY_VERIFICATION:-accept_new}"
AUTO_SYNC="${INPUT_AUTO_SYNC:-true}"
SYNC_INTERVAL="${INPUT_SYNC_INTERVAL:-5}"
TRIGGER_SYNC="${INPUT_TRIGGER_SYNC:-true}"
Expand All @@ -20,6 +27,14 @@ ENV_VARS="${INPUT_ENV_VARS:-}"
# [C1/C2] Mask secrets immediately, before any logging or API calls
[[ -n "${API_KEY}" ]] && echo "::add-mask::${API_KEY}"
[[ -n "${GIT_TOKEN}" ]] && echo "::add-mask::${GIT_TOKEN}"
# SSH private keys are multiline; ::add-mask:: works per-line, so mask each line
# to prevent any part of the key from appearing in logs.
if [[ -n "${SSH_PRIVATE_KEY}" ]]; then
echo "::add-mask::${SSH_PRIVATE_KEY}"
while IFS= read -r _line; do
[[ -n "${_line}" ]] && echo "::add-mask::${_line}"
done <<< "${SSH_PRIVATE_KEY}"
fi

SYNCS_CREATED=0
SYNCS_UPDATED=0
Expand Down Expand Up @@ -196,7 +211,7 @@ ensure_repository() {
if [[ -n "${REPOSITORY_ID}" && "${REPOSITORY_ID}" != "null" ]]; then
log_info "Found existing repository: ${REPOSITORY_ID}"

# Update credentials so the token stays current
# Update credentials so they stay current
if [[ "${AUTH_TYPE}" == "http" && -n "${GIT_TOKEN}" ]]; then
local update_payload
update_payload=$(jq -n \
Expand All @@ -206,6 +221,19 @@ ensure_repository() {
arcane_api PUT "/customize/git-repositories/${REPOSITORY_ID}" \
-d "${update_payload}" > /dev/null
log_info "Updated repository credentials"
elif [[ "${AUTH_TYPE}" == "ssh" && -n "${SSH_PRIVATE_KEY}" ]]; then
# Arcane API field names use camelCase (matching existing authType/token convention).
# Backend model: ssh_key -> sshKey, ssh_host_key_verification -> sshHostKeyVerification
local update_payload
update_payload=$(jq -n \
--arg sshKey "${SSH_PRIVATE_KEY}" \
--arg username "git" \
--arg sshHostKeyVerification "${SSH_HOST_KEY_VERIFICATION}" \
'{sshKey: $sshKey, username: $username, sshHostKeyVerification: $sshHostKeyVerification}')

arcane_api PUT "/customize/git-repositories/${REPOSITORY_ID}" \
-d "${update_payload}" > /dev/null
log_info "Updated repository SSH credentials"
fi
else
log_info "Creating new repository: ${REPO_NAME}"
Expand All @@ -218,6 +246,15 @@ ensure_repository() {
--arg authType "${AUTH_TYPE}" \
--arg token "${GIT_TOKEN}" \
'{name: $name, url: $url, authType: $authType, token: $token}')
elif [[ "${AUTH_TYPE}" == "ssh" ]]; then
create_payload=$(jq -n \
--arg name "${REPO_NAME}" \
--arg url "${REPO_URL}" \
--arg authType "${AUTH_TYPE}" \
--arg sshKey "${SSH_PRIVATE_KEY}" \
--arg username "git" \
--arg sshHostKeyVerification "${SSH_HOST_KEY_VERIFICATION}" \
'{name: $name, url: $url, authType: $authType, sshKey: $sshKey, username: $username, sshHostKeyVerification: $sshHostKeyVerification}')
else
create_payload=$(jq -n \
--arg name "${REPO_NAME}" \
Expand Down Expand Up @@ -379,9 +416,15 @@ if [[ "${AUTH_TYPE}" == "http" && -z "${GIT_TOKEN}" ]]; then
exit 1
fi

# Validate ssh-private-key is set when auth-type is ssh
if [[ "${AUTH_TYPE}" == "ssh" && -z "${SSH_PRIVATE_KEY}" ]]; then
log_error "ssh-private-key is required when auth-type is ssh."
exit 1
fi

# [H4] Reject unsupported auth-type values
if [[ "${AUTH_TYPE}" != "none" && "${AUTH_TYPE}" != "http" ]]; then
log_error "auth-type '${AUTH_TYPE}' is not supported. Valid values: none, http."
if [[ "${AUTH_TYPE}" != "none" && "${AUTH_TYPE}" != "http" && "${AUTH_TYPE}" != "ssh" ]]; then
log_error "auth-type '${AUTH_TYPE}' is not supported. Valid values: none, http, ssh."
exit 1
fi

Expand Down
14 changes: 13 additions & 1 deletion .github/actions/arcane-deploy/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ inputs:
default: ''

auth-type:
description: 'Git authentication type: none or http'
description: 'Git authentication type: none, http, or ssh'
required: false
default: 'http'

Expand All @@ -56,6 +56,16 @@ inputs:
required: false
default: ''

ssh-private-key:
description: 'SSH private key for git authentication (e.g. a GitHub deploy key). Required when auth-type is ssh.'
required: false
default: ''

ssh-host-key-verification:
description: 'SSH host key verification mode for Arcane. Valid values: accept_new, accept_all, reject.'
required: false
default: 'accept_new'

# Sync behavior
auto-sync:
description: 'Enable auto-sync polling in Arcane so it periodically pulls changes'
Expand Down Expand Up @@ -109,6 +119,8 @@ runs:
INPUT_BRANCH: ${{ inputs.branch }}
INPUT_AUTH_TYPE: ${{ inputs.auth-type }}
INPUT_GIT_TOKEN: ${{ inputs.git-token }}
INPUT_SSH_PRIVATE_KEY: ${{ inputs.ssh-private-key }}
INPUT_SSH_HOST_KEY_VERIFICATION: ${{ inputs.ssh-host-key-verification }}
INPUT_AUTO_SYNC: ${{ inputs.auto-sync }}
INPUT_SYNC_INTERVAL: ${{ inputs.sync-interval }}
INPUT_TRIGGER_SYNC: ${{ inputs.trigger-sync }}
Expand Down
Loading