From 4b2856c065f4b2df290249a1080e10a081450de7 Mon Sep 17 00:00:00 2001 From: Rody Davis Date: Tue, 13 Oct 2026 09:36:00 -0700 Subject: [PATCH] feat: add reusable truenas-deploy composite action --- README.md | 3 +- truenas-deploy/README.md | 23 +++++++++++ truenas-deploy/action.yml | 82 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 truenas-deploy/README.md create mode 100644 truenas-deploy/action.yml diff --git a/README.md b/README.md index 6497084..1a25d7f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ This repository contains reusable composite actions for CI/CD workflows across o | [Enforce Flutter Version Constraint](./enforce-flutter-version) | Scans all pubspec.yaml files to ensure they use the required Flutter SDK constraint. | `rodydavis/shared-actions/enforce-flutter-version@main` | | [Run Buildifier](./run-buildifier) | Downloads and runs buildifier on Starlark files. | `rodydavis/shared-actions/run-buildifier@main` | | [Setup Bazel (Bazelisk)](./setup-bazel) | Installs Node, Bazelisk, and configures Git for private SSH dependencies. | `rodydavis/shared-actions/setup-bazel@main` | -| [Setup Flutter](./setup-flutter) | Cross-platform setup for Flutter with caching on macOS, Windows, and Linux. | `rodydavis/shared-actions/setup-flutter@main` | +| [Setup Flutter](./setup-flutter) | Cross-platform setup for Flutter with local mirror and multi-runner concurrency support. | `rodydavis/shared-actions/setup-flutter@main` | +| [Deploy to TrueNAS](./truenas-deploy) | Deploys a Docker Compose stack to TrueNAS SCALE over SSH and pulls latest container images. | `rodydavis/shared-actions/truenas-deploy@main` | diff --git a/truenas-deploy/README.md b/truenas-deploy/README.md new file mode 100644 index 0000000..17df704 --- /dev/null +++ b/truenas-deploy/README.md @@ -0,0 +1,23 @@ +# Deploy to TrueNAS (`truenas-deploy`) + +Deploys a Docker Compose stack directly to TrueNAS SCALE (`/mnt/jupiter/apps/`) over SSH and runs `docker compose pull && docker compose up -d --remove-orphans`. + +## Usage + +```yaml +- name: Deploy to TrueNAS + uses: https://git.rodydavis.dev/rodydavis/shared-actions/truenas-deploy@main + with: + ssh_key: ${{ secrets.TRUENAS_SSH_KEY }} +``` + +### Optional Inputs + +| Input | Description | Default | +| :--- | :--- | :--- | +| `ssh_key` | **(Required)** Private SSH key for authenticating to TrueNAS (`secrets.TRUENAS_SSH_KEY`). | — | +| `app_name` | Application directory name under `/mnt/jupiter/apps/`. | Repository name | +| `compose_file` | Local compose file path (auto-detects `docker-compose.yaml` or `docker-compose.yml`). | `docker-compose.yaml` | +| `host` | TrueNAS SSH host IP or hostname. | `10.0.0.218` | +| `user` | TrueNAS SSH user. | `root` | +| `env_file_content` | Optional `.env` file contents to write to `/mnt/jupiter/apps//.env`. | `''` | diff --git a/truenas-deploy/action.yml b/truenas-deploy/action.yml new file mode 100644 index 0000000..2f8013c --- /dev/null +++ b/truenas-deploy/action.yml @@ -0,0 +1,82 @@ +name: 'Deploy to TrueNAS' +description: 'Deploys a Docker Compose stack to TrueNAS SCALE over SSH and pulls latest container images.' +inputs: + ssh_key: + description: 'Private SSH key for authenticating to TrueNAS' + required: true + app_name: + description: 'Application directory name under /mnt/jupiter/apps/ (defaults to repository name)' + required: false + default: '' + compose_file: + description: 'Path to the local docker-compose file in the repository' + required: false + default: 'docker-compose.yaml' + host: + description: 'TrueNAS SSH host IP or domain' + required: false + default: '10.0.0.218' + user: + description: 'TrueNAS SSH user' + required: false + default: 'root' + env_file_content: + description: 'Optional .env file content to write to the remote application directory' + required: false + default: '' + +runs: + using: 'composite' + steps: + - name: Deploy Docker Compose Stack to TrueNAS + shell: bash + env: + INPUT_SSH_KEY: ${{ inputs.ssh_key }} + INPUT_APP_NAME: ${{ inputs.app_name }} + INPUT_COMPOSE_FILE: ${{ inputs.compose_file }} + INPUT_HOST: ${{ inputs.host }} + INPUT_USER: ${{ inputs.user }} + INPUT_ENV_CONTENT: ${{ inputs.env_file_content }} + REPO_NAME: ${{ github.event.repository.name }} + run: | + set -euo pipefail + + APP_DIR_NAME="${INPUT_APP_NAME:-$REPO_NAME}" + if [ -z "$APP_DIR_NAME" ]; then + APP_DIR_NAME=$(basename "$GITHUB_REPOSITORY") + fi + + COMPOSE_FILE="$INPUT_COMPOSE_FILE" + if [ ! -f "$COMPOSE_FILE" ]; then + if [ -f "docker-compose.yml" ]; then + COMPOSE_FILE="docker-compose.yml" + elif [ -f "docker-compose.yaml" ]; then + COMPOSE_FILE="docker-compose.yaml" + else + echo "::error::Compose file '$COMPOSE_FILE' not found!" + exit 1 + fi + fi + + mkdir -p ~/.ssh + echo "$INPUT_SSH_KEY" > ~/.ssh/truenas_deploy_key + chmod 600 ~/.ssh/truenas_deploy_key + ssh-keyscan -H "$INPUT_HOST" >> ~/.ssh/known_hosts 2>/dev/null + + REMOTE_DIR="/mnt/jupiter/apps/${APP_DIR_NAME}" + echo "Ensuring remote directory ${REMOTE_DIR} on ${INPUT_HOST}..." + ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${INPUT_USER}@${INPUT_HOST}" "mkdir -p ${REMOTE_DIR}" + + if [ -n "$INPUT_ENV_CONTENT" ]; then + echo "Writing .env configuration to ${REMOTE_DIR}/.env..." + printf "%s\n" "$INPUT_ENV_CONTENT" | ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${INPUT_USER}@${INPUT_HOST}" "cat > ${REMOTE_DIR}/.env && chmod 600 ${REMOTE_DIR}/.env" + fi + + echo "Syncing ${COMPOSE_FILE} to ${REMOTE_DIR}/docker-compose.yml..." + scp -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "$COMPOSE_FILE" "${INPUT_USER}@${INPUT_HOST}:${REMOTE_DIR}/docker-compose.yml" + + echo "Pulling images and starting containers in ${REMOTE_DIR}..." + ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${INPUT_USER}@${INPUT_HOST}" "cd ${REMOTE_DIR} && docker compose pull && docker compose up -d --remove-orphans" + + rm -f ~/.ssh/truenas_deploy_key + echo "✅ Successfully deployed ${APP_DIR_NAME} to TrueNAS!"