From 5b430cf8354bc83290a016c3071a0d4bf2f3e52a Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 20 Oct 2026 12:35:18 +0200 Subject: [PATCH] Let an empty IRC_LISTEN_ADDR disable the IRC listener (closes #124) Viper treats an environment variable set to the empty string as unset, so an empty IRC_LISTEN_ADDR fell back to the :6667 default and the IRC listener could not be disabled from the environment. The config now reads IRC_LISTEN_ADDR with os.LookupEnv as well, so an empty value disables the listener and an unset one keeps :6667. Viper's own switch for this applies to every setting, so it is not used. New config tests cover both cases, and README.md says how to disable the listener. Model: opus-5-5 Co-authored-by: clawbot --- README.md | 17 +++++++--- internal/config/config.go | 8 +++++ internal/config/config_test.go | 61 ++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 internal/config/config_test.go diff --git a/README.md b/README.md index 11cca0e..d81f1c7 100644 --- a/README.md +++ b/README.md @@ -2583,7 +2583,9 @@ is also loaded automatically via [godotenv](https://github.com/joho/godotenv), and Viper also reads a YAML file `neoirc.yaml` from `/etc/neoirc/` or `$HOME/.config/neoirc/` if one exists, with the same names as keys; an environment variable wins over the file. An environment variable set to the -empty string counts as unset, so the file's value or the default applies. +empty string counts as unset, so the file's value or the default applies; the +one exception is `IRC_LISTEN_ADDR`, where the empty string disables the IRC +listener. | Variable | Type | Default | Description | | ---------------------- | ------ | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -2605,7 +2607,7 @@ empty string counts as unset, so the file's value or the default applies. | `NEOIRC_OPER_PASSWORD` | string | `""` | Server operator (o-line) password. Both name and password must be set to enable OPER. | | `LOGIN_RATE_LIMIT` | float | `1` | Allowed login attempts per second per IP address. | | `LOGIN_RATE_BURST` | int | `5` | Maximum burst of login attempts per IP before rate limiting kicks in. | -| `IRC_LISTEN_ADDR` | string | `:6667` | TCP address for the traditional IRC protocol listener. Only an empty value in `neoirc.yaml` disables it; an empty environment variable does not. | +| `IRC_LISTEN_ADDR` | string | `:6667` | TCP address for the traditional IRC protocol listener. Set it to the empty string, in the environment or in `neoirc.yaml`, to disable the listener. | | `MAINTENANCE_MODE` | bool | `false` | Maintenance mode flag (reserved) | ### Example `.env` file @@ -2632,9 +2634,14 @@ and others. ### Configuration The IRC listener is **enabled by default** on `:6667`. To disable it, set -`IRC_LISTEN_ADDR` to an empty string in `neoirc.yaml` (see -[Configuration](#configuration)); an empty environment variable counts as unset -and leaves it on `:6667`: +`IRC_LISTEN_ADDR` to an empty string (see [Configuration](#configuration)), +either in the environment: + +```bash +IRC_LISTEN_ADDR= +``` + +or in `neoirc.yaml`: ```yaml IRC_LISTEN_ADDR: "" diff --git a/internal/config/config.go b/internal/config/config.go index a3db54d..23f85b6 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,6 +4,7 @@ package config import ( "errors" "log/slog" + "os" "github.com/spf13/viper" "go.uber.org/fx" @@ -129,6 +130,13 @@ func New( params: ¶ms, } + // Viper counts an empty environment variable as unset, but an + // empty IRC_LISTEN_ADDR must turn the IRC listener off. + ircListenAddr, set := os.LookupEnv("IRC_LISTEN_ADDR") + if set { + cfg.IRCListenAddr = ircListenAddr + } + if cfg.Debug { params.Logger.EnableDebugLogging() cfg.log = params.Logger.Get() diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..fc8163a --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,61 @@ +package config_test + +import ( + "os" + "testing" + "time" + + "sneak.berlin/go/neoirc/internal/config" + "sneak.berlin/go/neoirc/internal/globals" + "sneak.berlin/go/neoirc/internal/logger" +) + +func newConfig(t *testing.T) *config.Config { + t.Helper() + + globs := &globals.Globals{ + Appname: "neoirc-test", + Version: "test", + StartTime: time.Now(), + } + + log, err := logger.New(nil, logger.Params{Globals: globs}) + if err != nil { + t.Fatal(err) + } + + cfg, err := config.New( + nil, config.Params{Globals: globs, Logger: log}, + ) + if err != nil { + t.Fatal(err) + } + + return cfg +} + +func TestIRCListenAddrUnsetKeepsDefault(t *testing.T) { + // t.Setenv puts back whatever value was there after the test. + t.Setenv("IRC_LISTEN_ADDR", "") + + err := os.Unsetenv("IRC_LISTEN_ADDR") + if err != nil { + t.Fatal(err) + } + + const want = ":6667" + + cfg := newConfig(t) + if cfg.IRCListenAddr != want { + t.Errorf("IRCListenAddr = %q, want %q", cfg.IRCListenAddr, want) + } +} + +func TestIRCListenAddrEmptyTurnsListenerOff(t *testing.T) { + t.Setenv("IRC_LISTEN_ADDR", "") + + cfg := newConfig(t) + if cfg.IRCListenAddr != "" { + t.Errorf("IRCListenAddr = %q, want empty", cfg.IRCListenAddr) + } +}