diff --git a/Dockerfile b/Dockerfile index a28dd2b8..3ddeca05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ FROM alpine:3.19.1 as dev ARG GH_CLI_VER=2.34.0 # install packages -RUN apk add --update --no-cache bash make git zsh curl tmux musl openssh git-lfs vim yq +RUN apk add --update --no-cache bash make git zsh curl tmux musl openssh git-lfs vim yq gnupg tig RUN wget https://github.com/cli/cli/releases/download/v${GH_CLI_VER}/gh_${GH_CLI_VER}_linux_386.tar.gz -O ghcli.tar.gz RUN tar --strip-components=1 -xf ghcli.tar.gz @@ -20,7 +20,8 @@ RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh ADD src/*.sh /bin/ RUN chmod +x /bin/entrypoint.sh \ && chmod +x /bin/sync_template.sh \ - && chmod +x /bin/sync_common.sh + && chmod +x /bin/sync_common.sh \ + && chmod +x /bin/gpg_no_tty.sh RUN mkdir -p /root/.ssh \ && ssh-keyscan -t rsa github.com >> /root/.ssh/known_hosts diff --git a/Makefile b/Makefile index 95d3cd06..5060ec00 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,10 @@ markdownlint: ## Validate markdown files zsh: ## open dev container with build environment docker-compose run --service-ports dev /bin/zsh +.PHONY: prod +prod: ## run the prod docker image with bash + docker-compose run prod + .PHONY: prune prune: ## delete the whole environment docker-compose down -v --rmi all --remove-orphans diff --git a/README.md b/README.md index 103a5464..3bcd5486 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,7 @@ jobs: | git_user_name | `[optional]` set the committer git user.name | `false` | `${GITHUB_ACTOR}` | | git_user_email | `[optional]` set the committer git user.email | `false` | `github-action@actions-template-sync.noreply.${SOURCE_REPO_HOSTNAME}` | | git_remote_pull_params | `[optional]` set remote pull parameters | `false` | `--allow-unrelated-histories --squash --strategy=recursive -X theirs` | +| gpg_private_key | `[optional]` set if you want to sign commits | `false` | | ### Docker @@ -293,6 +294,65 @@ E.g. when you like to disable the sync for all files with exceptions, you need t * ``` +## Sign commits + +It is recommended to [sign your commits][devto-sign-commits]. This action is able to sign commits. + +First, [generate a GPG key][github-create-gpg-key] and export the GPG private key as an ASCII armored version to your clipboard: + +```bash +# macOS +gpg --armor --export-secret-key jon@doe.example | pbcopy + +# Ubuntu (assuming GNU base64) +gpg --armor --export-secret-key jon@doe.example -w0 | xclip + +# Arch +gpg --armor --export-secret-key jon@doe.example | xclip -selection clipboard -i + +# FreeBSD (assuming BSD base64) +gpg --armor --export-secret-key jon@doe.example | xclip +``` + +:warning: the gpg username and email must match the `git_user_name` and `git_user_email` parameters. +Paste your clipboard as a [secret][github-create-secret] named `GPG_PRIVATE_KEY` for example. +:warning: currently a pgp key with passphrase is not supported (yet). + +```yaml +# File: .github/workflows/template-sync.yml + +on: + # cronjob trigger + schedule: + - cron: "0 0 1 * *" + # manual trigger + workflow_dispatch: +jobs: + repo-sync: + runs-on: ubuntu-latest + # https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs + permissions: + contents: write + pull-requests: write + + steps: + # To use this repository's private action, you must check out the repository + - name: Checkout + uses: actions/checkout@v4 + + - name: actions-template-sync + uses: AndreasAugustin/actions-template-sync@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + source_repo_path: + git_user_name: # add the gpg username + git_user_email: # add the gpg email + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + # uncomment if your key has a passpharse + # gpg_passpharse: ${{ secrets.GPG_PASSPHRASE }} + +``` + ## Lifecycle hooks Different lifecycle hooks are supported. You need to enable the functionality with the option `is_allow_hooks` and set it to `true` @@ -443,6 +503,7 @@ There are other great tools available within GitHub. Here you can find a compari | dry run | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | | ignore files | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | | creates a PR | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | +| sign commits | :heavy_check_mark: | :x: | :x: | :x: | | remarks | The action is placed within the target repositories | The action is placed within the target repositories | CLI meant for local use | The action will be based within the base repository with a list of dependent repositories | ## DEV @@ -516,6 +577,7 @@ specification. Contributions of any kind are welcome! [self-usage]: https://github.com/AndreasAugustin/actions-template-sync/blob/main/.github/workflows/actions_template_sync.yml [pr-labels]: https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels [devto-example]: https://dev.to/andreasaugustin/github-actions-template-sync-1g9k +[devto-sign-commits]: https://dev.to/andreasaugustin/git-how-and-why-to-sign-commits-35dn [github-example]: https://github.com/AndreasAugustin/teaching/blob/main/docs/git/git_action_sync.md [github-app]: https://docs.github.com/en/developers/apps/getting-started-with-apps/about-apps#about-github-apps [glob-pattern]: https://en.wikipedia.org/wiki/Glob_(programming) @@ -530,3 +592,4 @@ specification. Contributions of any kind are welcome! [dotdc-blog]: https://0xdc.me/blog/github-templates-and-repository-sync/ [github-create-pat]: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token [github-create-secret]: https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository +[github-create-gpg-key]: https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key diff --git a/action.yml b/action.yml index f3f654d1..9479443c 100644 --- a/action.yml +++ b/action.yml @@ -52,6 +52,8 @@ inputs: description: "[optional] set the committer git user.email for the merge commit" git_remote_pull_params: description: "[optional] set the pull parameters for the remote repository" + gpg_private_key: + description: "[optional] set the gpg private key if you want to sign your commits" runs: using: "docker" image: "src/Dockerfile" @@ -74,3 +76,4 @@ runs: GIT_USER_NAME: ${{ inputs.git_user_name }} GIT_USER_EMAIL: ${{ inputs.git_user_email }} GIT_REMOTE_PULL_PARAMS: ${{ inputs.git_remote_pull_params }} + GPG_PRIVATE_KEY: ${{ inputs.gpg_private_key }} diff --git a/docker-compose.yml b/docker-compose.yml index a72254db..2ab78b42 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,3 +23,9 @@ services: volumes: - .:/app/ working_dir: /app/ + + prod: + build: + context: ./src/ + tty: true + entrypoint: ["/bin/bash"] diff --git a/src/Dockerfile b/src/Dockerfile index 11fee153..3799db38 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -15,7 +15,7 @@ LABEL org.opencontainers.image.title="actions-template-sync image" LABEL org.opencontainers.image.description="contains actions-template-sync" # install packages -RUN apk add --update --no-cache bash git curl musl openssh git-lfs yq +RUN apk add --update --no-cache bash git curl musl openssh git-lfs yq gnupg tig RUN wget https://github.com/cli/cli/releases/download/v${GH_CLI_VER}/gh_${GH_CLI_VER}_linux_386.tar.gz -O ghcli.tar.gz RUN tar --strip-components=1 -xf ghcli.tar.gz @@ -23,9 +23,10 @@ RUN tar --strip-components=1 -xf ghcli.tar.gz ADD *.sh /bin/ RUN chmod +x /bin/entrypoint.sh \ && chmod +x /bin/sync_template.sh \ - && chmod +x /bin/sync_common.sh + && chmod +x /bin/sync_common.sh \ + && chmod +x /bin/gpg_no_tty.sh RUN mkdir -p /root/.ssh \ && ssh-keyscan -t rsa github.com >> /root/.ssh/known_hosts -ENTRYPOINT ["/bin/entrypoint.sh"] +ENTRYPOINT ["/bin/bash", "/bin/entrypoint.sh"] diff --git a/src/entrypoint.sh b/src/entrypoint.sh index c5c34869..4d788e13 100644 --- a/src/entrypoint.sh +++ b/src/entrypoint.sh @@ -1,4 +1,4 @@ -#! /usr/bin/env bash +#!/usr/bin/env bash set -e # set -u # set -x @@ -41,6 +41,21 @@ function ssh_setup() { echo "::endgroup::" } +function gpg_setup() { + echo "::group::gpg setup" + info "start prepare gpg" + echo -e "$GPG_PRIVATE_KEY" | gpg --import --batch + for fpr in $(gpg --list-key --with-colons "${GIT_USER_EMAIL}" | awk -F: '/fpr:/ {print $10}' | sort -u); do echo -e "5\ny\n" | gpg --no-tty --command-fd 0 --expert --edit-key "$fpr" trust; done + + KEY_ID="$(gpg --list-secret-key --with-colons "${GIT_USER_EMAIL}" | awk -F: '/sec:/ {print $5}')" + git config --global user.signingkey "${KEY_ID}" + git config --global commit.gpgsign true + git config --global gpg.program /bin/gpg_no_tty.sh + + info "done prepare gpg" + echo "::endgroup::"for fpr in +} + # Forward to /dev/null to swallow the output of the private key if [[ -n "${SSH_PRIVATE_KEY_SRC}" ]] &>/dev/null; then ssh_setup @@ -73,5 +88,9 @@ function git_init() { git_init +if [[ -n "${GPG_PRIVATE_KEY}" ]] &>/dev/null; then + gpg_setup +fi + # shellcheck source=src/sync_template.sh source sync_template.sh diff --git a/src/gpg_no_tty.sh b/src/gpg_no_tty.sh new file mode 100644 index 00000000..e742a973 --- /dev/null +++ b/src/gpg_no_tty.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +if [[ -n "${GPG_PASSPHRASE}" ]] &>/dev/null; then + # echo -e "${GPG_PASSPHRASE}" | gpg --pinentry-mode loopback --batch --yes --passphrase-fd 0 "$@" <&0 + echo "::error::currently gpg with passphrase is not supported" + exit 1 +else + gpg --pinentry-mode loopback --yes --batch "$@" <&0 +fi + +exit $? diff --git a/src/sync_common.sh b/src/sync_common.sh index 8677a408..e8abc366 100755 --- a/src/sync_common.sh +++ b/src/sync_common.sh @@ -1,4 +1,4 @@ -#! /usr/bin/env bash +#!/usr/bin/env bash set -e # set -u diff --git a/src/sync_template.sh b/src/sync_template.sh index 7894a55c..d4cb6921 100644 --- a/src/sync_template.sh +++ b/src/sync_template.sh @@ -1,4 +1,4 @@ -#! /usr/bin/env bash +#!/usr/bin/env bash set -e # set -u @@ -139,7 +139,7 @@ if git diff --quiet && git diff --staged --quiet; then exit 0 fi -git commit -m "${PR_COMMIT_MSG}" +git commit --signoff -m "${PR_COMMIT_MSG}" echo "::endgroup::"