Skip to content

Commit

Permalink
Allow notifications via ntfy and telegram
Browse files Browse the repository at this point in the history
  • Loading branch information
dormant-user committed Sep 10, 2024
1 parent caaa3a1 commit e73ba9c
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 9 deletions.
56 changes: 54 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,58 @@ docker compose up -d

- **GIT_TOKEN** - Required for authentication to add runners.
- **GIT_OWNER** - GitHub account username [OR] organization name.
- **GIT_REPOSITORY** - Repository name (required to create runners dedicated to a particular repo)
- **GIT_REPOSITORY** - Repository name _(required to create runners dedicated to a particular repo)_
- **RUNNER_GROUP** - Runner group. Uses `default`
- **RUNNER_NAME** - Runner name. Uses a random instance ID.
- **WORK_DIR** - Work directory. Uses `_work`
- **LABELS** - Runner labels (comma separated). Uses `"docker-node,${os_name}-${architecture}"`
- **REUSE_EXISTING** - Re-use existing configuration. Defaults to `false`

> `REUSE_EXISTING` can come in handy when a container restarts due to a problem,
> or when a container is deleted without gracefully shutting down.
> or when a container is terminated without gracefully shutting down.
> [!WARNING]
Using this image **without** the env var `GIT_REPOSITORY` will create an organization level runner.
Using self-hosted runners in public repositories pose some considerable security threats.
> - [#self-hosted-runner-security]
> - [#restricting-the-use-of-self-hosted-runners]
> - [#configuring-required-approval-for-workflows-from-public-forks]
>
> **Author Note:** _Be mindful of the env vars you set when spinning up containers_
<details>
<summary><strong>Env vars for startup notifications</strong></summary>

> This project supports [ntfy] and [telegram bot] for startup notifications.
**NTFY**

Choose ntfy setup instructions with [basic][ntfy-setup-basic] **OR** [authentication][ntfy-setup-auth] abilities

- **NTFY_USERNAME** - Ntfy username for authentication _(if topic is protected)_
- **NTFY_PASSWORD** - Ntfy password for authentication _(if topic is protected)_
- **NTFY_URL** - Ntfy endpoint for notifications.
- **NTFY_TOPIC** - Topic to which the notifications have to be sent.

**Telegram**

Steps for telegram bot configuration

1. Use [BotFather] to create a telegram bot token
2. Send a test message to the Telegram bot you created
3. Use the URL https://api.telegram.org/bot{token}/getUpdates to get the Chat ID
- You can also use Thread ID to send notifications to a particular thread within a chat window

```shell
export TELEGRAM_BOT_TOKEN="your-bot-token"
export CHAT_ID=$(curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates" | jq -r '.result[0].message.chat.id')
export THREAD_ID=$(curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates" | jq -r '.result[0]|.update_id')
```

- **TELEGRAM_BOT_TOKEN** - Telegram Bot token
- **TELEGRAM_CHAT_ID** - Chat ID to which the notifications have to be sent.

</details>

## Development

Expand All @@ -57,3 +100,12 @@ docker build --build-arg RUNNER_VERSION=$RUNNER_VERSION -t runner .
[badges-build]: https://github.com/thevickypedia/github-runner-linux/actions/workflows/main.yml/badge.svg
[links-build]: https://github.com/thevickypedia/github-runner-linux/actions/workflows/main.yml
[docker-compose]: https://github.com/thevickypedia/github-runner-linux/blob/main/docker-compose.yml
[ntfy]: https://ntfy.sh/
[telegram bot]: https://core.telegram.org/bots/api
[ntfy-setup-basic]: https://docs.ntfy.sh/install/
[ntfy-setup-auth]: https://community.home-assistant.io/t/setting-up-private-and-secure-ntfy-messaging-for-ha-notifications/632952
[BotFather]: https://t.me/botfather

[#restricting-the-use-of-self-hosted-runners]: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#restricting-the-use-of-self-hosted-runners
[#self-hosted-runner-security]: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#self-hosted-runner-security
[#configuring-required-approval-for-workflows-from-public-forks]: https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization#configuring-required-approval-for-workflows-from-public-forks
118 changes: 111 additions & 7 deletions scripts/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ architecture=""
current_dir="$(dirname "$(realpath "$0")")"
source "${current_dir}/detector.sh"

log() {
dt_stamp=$(date -u +"%Y-%m-%d %H:%M:%SZ")
echo "${dt_stamp}: $1"
}

instance_id() {
# Use randomly generated instance IDs (AWS format) as default runner names
letters=$(tr -dc '[:lower:]' < /dev/urandom | head -c 4)
Expand All @@ -25,6 +30,95 @@ RUNNER_GROUP="${RUNNER_GROUP:-"default"}"
WORK_DIR="${WORK_DIR:-"_work"}"
LABELS="${LABELS:-"docker-node,$os_name-$architecture"}"
REUSE_EXISTING="${REUSE_EXISTING:-"false"}"
NOTIFICATION_TITLE="GitHub Actions Runner - Docker Node"

ntfy_fn() {
# Send NTFY notification
body="$1"

if [[ -n "$NTFY_TOPIC" && -n "$NTFY_URL" ]]; then
# Remove trailing '/' if present
# https://github.com/binwiederhier/ntfy/issues/370
NTFY_URL=${NTFY_URL%/}
response=$(curl -s -o /tmp/ntfy -w "%{http_code}" -X POST \
-u "$NTFY_USERNAME:$NTFY_PASSWORD" \
-H "X-Title: ${NOTIFICATION_TITLE}" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "${body}" \
"$NTFY_URL/$NTFY_TOPIC")
status_code="${response: -3}"
if [ "$status_code" -eq 200 ]; then
log "Ntfy notification was successful"
elif [[ -f "/tmp/ntfy" ]]; then
log "Failed to send ntfy notification"
response_payload="$(cat /tmp/ntfy)"
reason=$(echo "$response_payload" | jq '.error')
# Output the extracted description or the full response if jq fails
if [ "$reason" != "null" ]; then
log "[$status_code]: $reason"
else
log "[$status_code]: $(cat /tmp/ntfy)"
fi
else
log "Failed to send ntfy notification - ${status_code}"
fi
rm -f /tmp/ntfy
else
log "Ntfy notifications is not setup"
fi
}

telegram_fn() {
# Send Telegram notification
body="$1"

if [[ -n "$TELEGRAM_BOT_TOKEN" && -n "$TELEGRAM_CHAT_ID" ]]; then
notification_preference=${DISABLE_TELEGRAM_NOTIFICATION:-false}

# Base JSON payload
message=$(printf "*%s*\n\n%s" "${NOTIFICATION_TITLE}" "${body}")
payload=$(jq -n \
--arg chat_id "$TELEGRAM_CHAT_ID" \
--arg text "$message" \
--arg parse_mode "markdown" \
--arg disable_notification "$notification_preference" \
'{
chat_id: $chat_id,
text: $text,
parse_mode: $parse_mode,
disable_notification: $disable_notification
}')

# Add 'message_thread_id' if TELEGRAM_THREAD_ID is available and not null
if [ -n "$TELEGRAM_THREAD_ID" ]; then
payload=$(echo "$payload" | jq --arg thread_id "$TELEGRAM_THREAD_ID" '. + {message_thread_id: $thread_id}')
fi

response=$(curl -s -o /tmp/telegram -w "%{http_code}" -X POST \
-H 'Content-Type: application/json' \
-d "$payload" \
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage")
status_code="${response: -3}"
if [ "$status_code" -eq 200 ]; then
log "Telegram notification was successful"
elif [[ -f "/tmp/telegram" ]]; then
log "Failed to send telegram notification"
response_payload="$(cat /tmp/telegram)"
reason=$(echo "$response_payload" | jq '.description')
# Output the extracted description or the full response if jq fails
if [ "$reason" != "null" ]; then
log "[$status_code]: $reason"
else
log "[$status_code]: $(cat /tmp/telegram)"
fi
else
log "Failed to send telegram notification - ${status_code}"
fi
rm -f /tmp/telegram
else
log "Telegram notifications is not setup"
fi
}

repo_level_runner() {
# https://docs.github.com/en/rest/actions/self-hosted-runners#create-a-registration-token-for-a-repository
Expand Down Expand Up @@ -64,25 +158,35 @@ org_level_runner() {

if [[ "$REUSE_EXISTING" == "true" || "$REUSE_EXISTING" == "1" ]] &&
[[ -d "/home/docker/actions-runner" ]] &&
[[ -f "/home/docker/actions-runner/.credentials" ]] &&
[[ -f "/home/docker/actions-runner/.credentials_rsaparams" ]] &&
[[ -f "/home/docker/actions-runner/config.sh" ]] &&
[[ -f "/home/docker/actions-runner/run.sh" ]]; then
echo "Existing configuration found. Re-using it..."
log "Existing configuration found. Re-using it..."
reused="reusing existing configuration"
cd "/home/docker/actions-runner" || exit 1
else
if [[ -n "$GIT_REPOSITORY" ]]; then
echo "Creating a repository level self-hosted runner ['${RUNNER_NAME}'] for ${GIT_REPOSITORY}"
repo_level_runner
log "Creating a repository level self-hosted runner ['${RUNNER_NAME}'] for ${GIT_REPOSITORY}"
repo_level_runner
else
echo "Creating an organization level self-hosted runner '${RUNNER_NAME}'"
org_level_runner
log "Creating an organization level self-hosted runner '${RUNNER_NAME}'"
org_level_runner
fi
reused="creating a new configuration"
fi

cleanup() {
echo "Removing runner..."
./config.sh remove --token "${REG_TOKEN}"
log "Removing runner..."
ntfy_fn "Removing runner: '${RUNNER_NAME}'"
telegram_fn "Removing runner: '${RUNNER_NAME}'"
./config.sh remove --token "${REG_TOKEN}"
}

trap 'cleanup; exit 130' INT
trap 'cleanup; exit 143' TERM

ntfy_fn "Starting GitHub actions runner: '${RUNNER_NAME}' using docker node by ${reused}" &
telegram_fn "Starting GitHub actions runner: '${RUNNER_NAME}' using docker node by ${reused}" &

./run.sh & wait $!

0 comments on commit e73ba9c

Please sign in to comment.