From 586ba48de7584e4e50679884934839bd6e48403f Mon Sep 17 00:00:00 2001 From: Jose Henrique Date: Fri, 10 Jul 2026 07:19:53 -0300 Subject: [PATCH] feat: new ssh-deploy job --- README.md | 36 ++++++++++++++ ssh-deploy/action.yaml | 104 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 ssh-deploy/action.yaml diff --git a/README.md b/README.md index 0f2df47..b79894f 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,38 @@ jobs: | `namespace` | ❌ | `default` | Kubernetes namespace | | `kube_version` | ❌ | `stable` | kubectl version (`stable` or e.g. `v1.31.0`) | +### `ssh-deploy` + +SSH into a remote host and run `docker compose pull` + `docker compose up -d --force-recreate` to deploy the latest images. No third-party SSH action — uses native `ssh` and `ssh-keyscan`. + +```yaml +jobs: + deploy: + runs-on: ubuntu-amd64 + needs: build + steps: + - uses: ivanch/pipeline-actions/ssh-deploy@main + with: + ssh_host: ${{ secrets.DEPLOY_HOST }} + ssh_username: ${{ secrets.DEPLOY_USERNAME }} + ssh_key: ${{ secrets.DEPLOY_KEY }} + remote_dir: ${{ secrets.DEPLOY_DIR }} + # optional: + # ssh_port: "22" + # compose_files: docker-compose.yml compose.override.yml +``` + +| Input | Required | Default | Description | +|---|---|---|---| +| `ssh_host` | ✅ | — | Remote server hostname or IP | +| `ssh_username` | ✅ | — | SSH username | +| `ssh_key` | ✅ | — | SSH private key (use secrets) | +| `remote_dir` | ✅ | — | Directory on the remote server with `docker-compose.yml` | +| `ssh_port` | ❌ | `22` | SSH port | +| `compose_files` | ❌ | _(empty)_ | Space-separated compose file paths (applied via `-f` flags; when empty docker compose uses default detection) | + +--- + ## Required Secrets Consumer repositories need these secrets configured in Gitea: @@ -79,3 +111,7 @@ Consumer repositories need these secrets configured in Gitea: | `REGISTRY_PASSWORD` | `build-and-push` | Container registry password/token | | `SSH_KEY_DOCKERBUILD` | `build-and-push` | SSH private key for `docker-build.haven` | | `KUBE_CONFIG` | `deploy-restart` | Full kubeconfig YAML for cluster access | +| `DEPLOY_HOST` | `ssh-deploy` | Remote server hostname or IP | +| `DEPLOY_USERNAME` | `ssh-deploy` | SSH username for the remote server | +| `DEPLOY_KEY` | `ssh-deploy` | SSH private key for the remote server | +| `DEPLOY_DIR` | `ssh-deploy` | Directory on the remote server with `docker-compose.yml` | diff --git a/ssh-deploy/action.yaml b/ssh-deploy/action.yaml new file mode 100644 index 0000000..b01fe29 --- /dev/null +++ b/ssh-deploy/action.yaml @@ -0,0 +1,104 @@ +name: SSH Deploy (docker compose) +description: SSH into a remote host, pull the latest images, and recreate containers with docker compose. + +inputs: + ssh_host: + description: Remote server hostname or IP address + required: true + ssh_port: + description: SSH port + required: false + default: "22" + ssh_username: + description: SSH username for the remote server + required: true + ssh_key: + description: SSH private key for the remote server (pass via secrets) + required: true + remote_dir: + description: Directory on the remote server containing docker-compose.yml + required: true + compose_files: + description: > + Compose file(s) to use, space-separated (applied via -f flags). + When empty, docker compose uses its default file detection. + required: false + default: "" + +runs: + using: composite + steps: + - name: Validate inputs + shell: bash + env: + SSH_HOST: ${{ inputs.ssh_host }} + SSH_USERNAME: ${{ inputs.ssh_username }} + SSH_KEY: ${{ inputs.ssh_key }} + REMOTE_DIR: ${{ inputs.remote_dir }} + run: | + if [ -z "${SSH_HOST}" ] || [ "${SSH_HOST// }" = "" ]; then + echo "::error::ssh_host is not set or is empty." + exit 1 + fi + if [ -z "${SSH_USERNAME}" ] || [ "${SSH_USERNAME// }" = "" ]; then + echo "::error::ssh_username is not set or is empty." + exit 1 + fi + if [ -z "${SSH_KEY}" ] || [ "${SSH_KEY// }" = "" ]; then + echo "::error::ssh_key is not set or is empty." + exit 1 + fi + if [ -z "${REMOTE_DIR}" ] || [ "${REMOTE_DIR// }" = "" ]; then + echo "::error::remote_dir is not set or is empty." + exit 1 + fi + + - name: Set up SSH key and known_hosts + shell: bash + env: + SSH_KEY: ${{ inputs.ssh_key }} + SSH_HOST: ${{ inputs.ssh_host }} + SSH_PORT: ${{ inputs.ssh_port }} + run: | + mkdir -p ~/.ssh + echo "${SSH_KEY}" > ~/.ssh/id_deploy + chmod 600 ~/.ssh/id_deploy + + # Populate known_hosts so StrictHostKeyChecking works without prompts + ssh-keyscan -p "${SSH_PORT}" "${SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null + + if ! grep -q "${SSH_HOST}" ~/.ssh/known_hosts; then + echo "::error::Failed to add ${SSH_HOST} to known_hosts (host may be unreachable)." + exit 1 + fi + + - name: Pull and recreate containers + shell: bash + env: + SSH_HOST: ${{ inputs.ssh_host }} + SSH_PORT: ${{ inputs.ssh_port }} + SSH_USERNAME: ${{ inputs.ssh_username }} + REMOTE_DIR: ${{ inputs.remote_dir }} + COMPOSE_FILES: ${{ inputs.compose_files }} + run: | + # Build -f flags if compose_files is provided + COMPOSE_FLAGS="" + if [ -n "${COMPOSE_FILES}" ] && [ "${COMPOSE_FILES// }" != "" ]; then + for f in ${COMPOSE_FILES}; do + COMPOSE_FLAGS="${COMPOSE_FLAGS} -f \"${f}\"" + done + fi + + echo "Connecting to ${SSH_USERNAME}@${SSH_HOST}:${SSH_PORT}" + echo "Working directory: ${REMOTE_DIR}" + if [ -n "${COMPOSE_FLAGS}" ]; then + echo "Compose files: ${COMPOSE_FILES}" + fi + + ssh -i ~/.ssh/id_deploy \ + -p "${SSH_PORT}" \ + -o StrictHostKeyChecking=yes \ + "${SSH_USERNAME}@${SSH_HOST}" \ + "cd '${REMOTE_DIR}' \ + && docker compose ${COMPOSE_FLAGS} pull \ + && docker compose ${COMPOSE_FLAGS} up -d --force-recreate"