Let an empty IRC_LISTEN_ADDR disable the IRC listener (closes #124)
check / check (push) Waiting to run

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 <sneak+clawbot@sneak.cloud>
This commit was merged in pull request #137.
This commit is contained in:
2026-10-20 12:35:18 +02:00
committed by clawbot
parent 1019419b17
commit 5b430cf835
3 changed files with 81 additions and 5 deletions
+12 -5
View File
@@ -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: ""
+8
View File
@@ -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: &params,
}
// 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()
+61
View File
@@ -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)
}
}