From de73408c8d1bc79fa0fbce221021b8d90029952f Mon Sep 17 00:00:00 2001 From: oliveratgithub Date: Wed, 12 Nov 2025 23:07:01 +0100 Subject: [PATCH 1/2] teleirc: Adds options to disable message bridges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allows disabling either bridge to go just "one-way": Telegram → IRC, and IRC → Telegram Each are naturally ENABLED by default, but can be individually be disabled using: - a) the CLI arguments `--noirc` and `--notelegram`, - b) via settings env vars `DISABLE_RELAY_TO_IRC=true` and `DISABLE_RELAY_TO_TELEGRAM=true` --- cmd/teleirc.go | 14 +++++++-- docs/user/config-file-glossary.rst | 23 ++++++++++++-- env.example | 13 ++++++++ internal/handlers/irc/irc.go | 9 ++++-- internal/handlers/telegram/telegram.go | 42 +++++++++++++++----------- 5 files changed, 78 insertions(+), 23 deletions(-) diff --git a/cmd/teleirc.go b/cmd/teleirc.go index fa8921c..0714781 100644 --- a/cmd/teleirc.go +++ b/cmd/teleirc.go @@ -16,6 +16,9 @@ import ( var ( flagPath = flag.String("conf", ".env", "config file") flagDebug = flag.Bool("debug", false, "enable debugging output") + flagDebug = flag.Bool("debug", func() bool { env, _ := strconv.ParseBool(os.Getenv("DEBUG")); return env }(), "enable debugging output") + flagMuteIrc = flag.Bool("muteirc", func() bool { env, _ := strconv.ParseBool(os.Getenv("DISABLE_RELAY_TO_IRC")); return env }(), "disable Telegram messages to IRC") + flagMuteTg = flag.Bool("mutetelegram", func() bool { env, _ := strconv.ParseBool(os.Getenv("DISABLE_RELAY_TO_TELEGRAM")); return env }(), "disable IRC messages to Telegram") flagVersion = flag.Bool("version", false, "displays current version of TeleIRC") version string ) @@ -33,6 +36,13 @@ func main() { // Notify that logger is enabled logger.LogDebug("Debug mode enabled!") + if *flagMuteIrc { + logger.LogInfo("Relaying messages to IRC is turned OFF!") + } + if *flagMuteTg { + logger.LogInfo("Relaying messages to Telegram is turned OFF!") + } + settings, err := internal.LoadConfig(*flagPath) if err != nil { logger.LogError(err) @@ -43,10 +53,10 @@ func main() { signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) var tgapi *tgbotapi.BotAPI - tgClient := tg.NewClient(&settings.Telegram, &settings.IRC, &settings.Imgur, tgapi, logger) + tgClient := tg.NewClient(&settings.Telegram, &settings.IRC, &settings.Imgur, tgapi, logger, *flagMuteIrc) tgChan := make(chan error) - ircClient := irc.NewClient(&settings.IRC, &settings.Telegram, logger) + ircClient := irc.NewClient(&settings.IRC, &settings.Telegram, logger, *flagMuteTg) ircChan := make(chan error) go ircClient.StartBot(ircChan, tgClient.SendMessage) diff --git a/docs/user/config-file-glossary.rst b/docs/user/config-file-glossary.rst index 21beffc..95eb951 100644 --- a/docs/user/config-file-glossary.rst +++ b/docs/user/config-file-glossary.rst @@ -3,8 +3,27 @@ Config file glossary #################### This page is a glossary of different settings in the ``env.example`` configuration file. -All values shown are the default settings. -This glossary is intended for advanced users. + +.. note:: + All values shown are the default settings. + This glossary is intended for advanced users. + + +************ +General settings +************ + +Configuration settings +======================== + +``DEBUG=false`` + (Optional) Verbose logging, enabled when set to `true` + +``DISABLE_RELAY_TO_IRC=false`` + (Optional) Fully disables bridging messages from Telegram → IRC when set to `true` + +``DISABLE_RELAY_TO_TELEGRAM=false`` + (Optional) Fully disables bridging messages from IRC → Telegram when set to `true` ************ diff --git a/env.example b/env.example index c9282e7..389b292 100644 --- a/env.example +++ b/env.example @@ -2,6 +2,19 @@ # See the Config File Glossary for instructions. # https://docs.teleirc.com/en/latest/user/config-file-glossary/ +############################################################################### +# # +# General settings # +# # +############################################################################### + +#####----- Configuration settings -----##### +DEBUG=false +DISABLE_RELAY_TO_IRC=false +DISABLE_RELAY_TO_TELEGRAM=false + + + ############################################################################### # # # IRC configuration settings # diff --git a/internal/handlers/irc/irc.go b/internal/handlers/irc/irc.go index ccbccce..c2d6947 100644 --- a/internal/handlers/irc/irc.go +++ b/internal/handlers/irc/irc.go @@ -18,12 +18,13 @@ type Client struct { TelegramSettings *internal.TelegramSettings logger internal.DebugLogger sendToTg func(string) + disableTgRelay bool } /* NewClient returns a new IRCClient based on the provided settings */ -func NewClient(settings *internal.IRCSettings, telegramSettings *internal.TelegramSettings, logger internal.DebugLogger) Client { +func NewClient(settings *internal.IRCSettings, telegramSettings *internal.TelegramSettings, logger internal.DebugLogger, disableTgRelay bool) Client { logger.LogInfo("Creating new IRC bot client...") client := girc.New(girc.Config{ Server: settings.Server, @@ -52,7 +53,7 @@ func NewClient(settings *internal.IRCSettings, telegramSettings *internal.Telegr } } - return Client{client, settings, telegramSettings, logger, nil} + return Client{client, settings, telegramSettings, logger, nil, disableTgRelay} } /* @@ -120,6 +121,10 @@ func (c Client) Logger() internal.DebugLogger { SendToTg sends a message to Telegram */ func (c Client) SendToTg(msg string) { + if c.disableTgRelay { + c.logger.LogDebug("Relaying to Telegram is disabled, skipping IRC message") + return + } c.sendToTg(msg) } diff --git a/internal/handlers/telegram/telegram.go b/internal/handlers/telegram/telegram.go index 1479f86..aa29581 100644 --- a/internal/handlers/telegram/telegram.go +++ b/internal/handlers/telegram/telegram.go @@ -11,20 +11,21 @@ Client contains information for the Telegram bridge, including the TelegramSettings needed to run the bot */ type Client struct { - api *tgbotapi.BotAPI - Settings *internal.TelegramSettings - IRCSettings *internal.IRCSettings - ImgurSettings *internal.ImgurSettings - logger internal.DebugLogger - sendToIrc func(string) + api *tgbotapi.BotAPI + Settings *internal.TelegramSettings + IRCSettings *internal.IRCSettings + ImgurSettings *internal.ImgurSettings + logger internal.DebugLogger + sendToIrc func(string) + disableIrcRelay bool } /* NewClient creates a new Telegram bot client */ -func NewClient(settings *internal.TelegramSettings, ircsettings *internal.IRCSettings, imgur *internal.ImgurSettings, tgapi *tgbotapi.BotAPI, logger internal.DebugLogger) *Client { +func NewClient(settings *internal.TelegramSettings, ircsettings *internal.IRCSettings, imgur *internal.ImgurSettings, tgapi *tgbotapi.BotAPI, logger internal.DebugLogger, disableIrcRelay bool) *Client { logger.LogInfo("Creating new Telegram bot client...") - return &Client{api: tgapi, Settings: settings, IRCSettings: ircsettings, ImgurSettings: imgur, logger: logger} + return &Client{api: tgapi, Settings: settings, IRCSettings: ircsettings, ImgurSettings: imgur, logger: logger, disableIrcRelay: disableIrcRelay} } /* @@ -66,16 +67,23 @@ func (tg *Client) StartBot(errChan chan<- error, sendMessage func(string)) { tg.logger.LogInfo("Authorized on account", tg.api.Self.UserName) tg.sendToIrc = sendMessage - u := tgbotapi.NewUpdate(0) - u.Timeout = 60 + if !tg.disableIrcRelay { + u := tgbotapi.NewUpdate(0) + u.Timeout = 60 - updates, err := tg.api.GetUpdatesChan(u) - if err != nil { - errChan <- err - tg.logger.LogError(err) - } + updates, err := tg.api.GetUpdatesChan(u) + if err != nil { + errChan <- err + tg.logger.LogError(err) + return + } - updateHandler(tg, updates) + updateHandler(tg, updates) - errChan <- nil + errChan <- nil + } else { + tg.logger.LogInfo("Telegram -> IRC relay disabled, but Telegram bot remains active for IRC -> Telegram messages") + // Block forever to keep the goroutine alive + select {} + } } From 2fd83c6818a619da86dc6fe0b51204ca0783ec6a Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 14 Nov 2025 19:51:45 +0100 Subject: [PATCH 2/2] Fix/publish docker image (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Test fix to for pushing to GHCR * Update circleCI template * docker(build): Updates to `golang:1.25-alpine` * docker-compose(environment): Supports ENV vars Allows building docker image using `docker compose` remotely, and adds support for passable process ENVIRONMENT variables - not failing when no dedicated .env-file is being mapped / available. While it should however retain the backwards-compatibility for still using a .env config file, but ideally using: - a) the CLI argument `--env-file /path/to/.env`, - or b) a `volumes:`-mapping on the docker compose service definition. * circleci: Uses golangci-lint v2.6.1 for `go_1-25` * circleci(go_1-25): Tries to replace codeclimate CodeClimate seems to be no longer existing, «Qlty is from the makers of Code Climate, who built the first cloud-based code quality platform in 2011.»: https://codeclimate.com/blog/code-climate-quality-is-now-qlty-software --------- Co-authored-by: Tim Zabel --- .circleci/config.yml | 45 +++++++++++---- .github/workflows/publish_docker_image.yml | 56 +++++++++---------- cmd/teleirc.go | 5 +- deployments/container/Dockerfile | 4 +- .../container/docker-compose.yml.example | 5 +- docs/user/config-file-glossary.rst | 17 +++++- docs/user/quick-start.md | 28 ++++++++-- env.example | 12 ++++ internal/config.go | 31 ++++++++-- 9 files changed, 145 insertions(+), 58 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d22e92b..1dadb53 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,9 +9,10 @@ version: 2.1 workflows: main: jobs: - - go_1-15 - - go_1-16 - - go_1-17 + - go_1-18 + - go_1-19 + - go_1-20 + - go_1-25 - build_docs commands: @@ -19,7 +20,7 @@ commands: description: Run linter checks on TeleIRC. steps: - checkout - - run: + - run: name: Download and install golintci-lint. command: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sudo sh -s -- -b $(go env GOPATH)/bin v1.43.0 - run: @@ -34,21 +35,21 @@ commands: command: go test -coverprofile=c.out ./... jobs: - go_1-15: + go_1-18: docker: - - image: cimg/go:1.15 + - image: cimg/go:1.18 steps: - golintci-lint - teleirc-test - go_1-16: + go_1-19: docker: - - image: cimg/go:1.16 + - image: cimg/go:1.19 steps: - golintci-lint - teleirc-test - go_1-17: + go_1-20: docker: - - image: cimg/go:1.17 + - image: cimg/go:1.20 steps: - golintci-lint - run: @@ -64,6 +65,30 @@ jobs: command: | sed -i 's/github.com\/ritlug\/teleirc\///g' c.out /tmp/cc-test-reporter after-build + go_1-25: + docker: + - image: cimg/go:1.25 + steps: + - checkout + - run: + name: Download and install golintci-lint. + command: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sudo sh -s -- -b $(go env GOPATH)/bin v2.6.1 + - run: + name: Run Go linter checks. + command: golangci-lint run + - teleirc-test + - run: + name: Display test coverage summary. + command: | + go tool cover -func=c.out + echo "Total coverage:" + go tool cover -func=c.out | grep total | awk '{print $3}' + - run: + name: Generate HTML coverage report. + command: go tool cover -html=c.out -o coverage.html + - store_artifacts: + path: coverage.html + destination: coverage-report build_docs: docker: - image: cimg/python:3.10 diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml index 822d48f..bec890c 100644 --- a/.github/workflows/publish_docker_image.yml +++ b/.github/workflows/publish_docker_image.yml @@ -1,49 +1,47 @@ -name: Build and Push Docker Image - +name: Build and Publish Docker Image + +#on: +# push: +# branches: +# - main +# tags: +# - 'v*' on: + pull_request: + branches: + - main push: - branches: [main] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -concurrency: - group: teleirc-docker-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + tags: + - 'v*' jobs: - build-and-push-image: - name: Build and Push Image - + build-and-push: + name: Build and Push Docker Image to GHCR runs-on: ubuntu-latest permissions: contents: read packages: write + id-token: write steps: - - name: Checkout repository + - name: Checkout code uses: actions/checkout@v4 - - name: Log in to the Container registry - uses: docker/login-action@v2 + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} + registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Set up docker buildx + uses: docker/setup-buildx-action@v3 - - name: Build and push Docker image - uses: docker/build-push-action@v4 + - name: Build and push docker image + uses: docker/build-push-action@v6 with: - push: true context: . file: ./deployments/container/Dockerfile - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + push: true + tags: | + ghcr.io/ritlug/${{ github.repository##*/ }}:${{ github.sha }} diff --git a/cmd/teleirc.go b/cmd/teleirc.go index fa8921c..c24e5a7 100644 --- a/cmd/teleirc.go +++ b/cmd/teleirc.go @@ -6,6 +6,7 @@ import ( "os" "os/signal" "syscall" + "strconv" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" "github.com/ritlug/teleirc/internal" @@ -14,8 +15,8 @@ import ( ) var ( - flagPath = flag.String("conf", ".env", "config file") - flagDebug = flag.Bool("debug", false, "enable debugging output") + flagPath = flag.String("conf", "", "config file") + flagDebug = flag.Bool("debug", func() bool { env, _ := strconv.ParseBool(os.Getenv("DEBUG")); return env }(), "enable debugging output") flagVersion = flag.Bool("version", false, "displays current version of TeleIRC") version string ) diff --git a/deployments/container/Dockerfile b/deployments/container/Dockerfile index 633cd8a..d813999 100644 --- a/deployments/container/Dockerfile +++ b/deployments/container/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22-alpine AS builder +FROM golang:1.25-alpine AS builder WORKDIR /app @@ -16,8 +16,6 @@ RUN adduser -D teleirc-user USER teleirc-user COPY --from=builder /app/teleirc /opt/teleirc/teleirc -COPY --from=builder /app/.env /opt/teleirc/conf WORKDIR /opt/teleirc ENTRYPOINT [ "./teleirc" ] -CMD [ "-conf", "/opt/teleirc/conf", "-debug", "true" ] diff --git a/deployments/container/docker-compose.yml.example b/deployments/container/docker-compose.yml.example index 59ce7db..63797c1 100644 --- a/deployments/container/docker-compose.yml.example +++ b/deployments/container/docker-compose.yml.example @@ -6,7 +6,6 @@ version: '3' services: teleirc: build: - context: ../../ - dockerfile: ./deployments/container/Dockerfile - env_file: ../../.env + context: https://github.com/RITlug/teleirc.git + dockerfile: deployments/container/Dockerfile user: teleirc diff --git a/docs/user/config-file-glossary.rst b/docs/user/config-file-glossary.rst index 21beffc..0491833 100644 --- a/docs/user/config-file-glossary.rst +++ b/docs/user/config-file-glossary.rst @@ -3,8 +3,21 @@ Config file glossary #################### This page is a glossary of different settings in the ``env.example`` configuration file. -All values shown are the default settings. -This glossary is intended for advanced users. + +.. note:: + All values shown are the default settings. + This glossary is intended for advanced users. + + +************ +General settings +************ + +Configuration settings +======================== + +``DEBUG=false`` + (Optional) Verbose logging, enabled when set to `true` ************ diff --git a/docs/user/quick-start.md b/docs/user/quick-start.md index 53e2fe9..9c19560 100644 --- a/docs/user/quick-start.md +++ b/docs/user/quick-start.md @@ -132,15 +132,35 @@ There are two ways to deploy TeleIRC persistently: Containers are the easiest way to deploy TeleIRC. Dockerfiles and other deployment resources are available in ``deployments/``. -#### Build TeleIRC +Ensure you have [docker](https://www.docker.com/) installed. + +#### Build TeleIRC docker image -1. Ensure you have [docker](https://www.docker.com/) installed 1. Enter container deployment directory (`cd deployments/container`) 1. Build image (`./build_image.sh`) 1. Run container (`docker run teleirc:latest`) -**NOTE**: -**This deployment method assumes you have a complete .env file** +> [!NOTE] +> This deployment can optionally copy a standalone .env file + + +#### Run TeleIRC using Docker compose + +1. Enter container deployment directory (`cd deployments/container`) +1. Run service using `docker compose`: + +```bash +IRC_SERVER=chat.freenode.net \ +IRC_CHANNEL='#channelname' \ +IRC_BOT_NAME='teleirc' \ +TELEIRC_TOKEN='000000000:AAAAAAaAAa2AaAAaoAAAA-a_aaAAaAaaaAA' \ +TELEGRAM_CHAT_ID='-0000000000000' \ +docker compose up -d teleirc +``` + +> [!TIP] +> Instead you can also add `environment:` entries via `docker-compose.yml`, or pass a standalone `.env` file using the CLI: +> `docker compose --env-file ../../.env up --build -d teleirc` ### Run binary diff --git a/env.example b/env.example index c9282e7..ed5650c 100644 --- a/env.example +++ b/env.example @@ -2,6 +2,17 @@ # See the Config File Glossary for instructions. # https://docs.teleirc.com/en/latest/user/config-file-glossary/ +############################################################################### +# # +# General settings # +# # +############################################################################### + +#####----- Configuration settings -----##### +DEBUG=false + + + ############################################################################### # # # IRC configuration settings # @@ -77,6 +88,7 @@ LEAVE_MESSAGE_ALLOW_LIST="" SHOW_DISCONNECT_MESSAGE=true + ################################################################################ # # # Imgur configuration settings # diff --git a/internal/config.go b/internal/config.go index 4d22cc9..0381263 100644 --- a/internal/config.go +++ b/internal/config.go @@ -3,6 +3,7 @@ package internal import ( "fmt" "os" + "path/filepath" "strings" "github.com/caarlos0/env/v6" @@ -135,13 +136,33 @@ func LoadConfig(path string) (*Settings, error) { if err := validate.RegisterValidation("notempty", validateEmptyString); err != nil { return nil, err } - // Attempt to load environment variables from path if path was provided - if path != ".env" && path != "" { - if err := godotenv.Load(path); err != nil { - return nil, err + // If a path was provided, try to load it. + if path != "" { + if info, err := os.Stat(path); err == nil { + // If the path is a directory, look for /.env. + if info.IsDir() { + envFile := filepath.Join(path, defaultPath) + if _, err := os.Stat(envFile); err == nil { + if err := godotenv.Load(envFile); err != nil { + return nil, err + } + } + } else { + // path exists and is a file — attempt to load it + if err := godotenv.Load(path); err != nil { + return nil, err + } + } + } else { + // If the provided path does not exist, continue and rely on passed process ENV variables + if os.IsNotExist(err) { + warning.Printf("config path %q not provided or does not exist; continuing and using process environment variables", path) + } else { + return nil, err + } } } else if _, err := os.Stat(defaultPath); !os.IsNotExist(err) { - // Attempt to load from defaultPath if defaultPath exists + // Attempt to load from defaultPath if it exists if err := godotenv.Load(defaultPath); err != nil { return nil, err }