diff --git a/.dockerignore b/.dockerignore index 9b75277..3850038 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ -.git +# .git is sent so the build can stamp the version, without its config. +.git/config *.md !README.md neoircd diff --git a/Dockerfile b/Dockerfile index 32acda7..b90d0ca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,8 +39,19 @@ COPY --from=web-builder /web/dist/ web/dist/ RUN make test # Build static binaries (no cgo needed at runtime — modernc.org/sqlite is pure Go) -ARG VERSION=dev -RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.Version=${VERSION}" -o /neoircd ./cmd/neoircd/ +# +# neoircd is stamped with the VERSION build arg when one is given, otherwise +# with the tag or short commit from the .git in the build context. With .git +# present, a version that is still empty, dev or unknown fails the build. +ARG VERSION +RUN VERSION="${VERSION:-$(git describe --tags --always)}"; \ + if [ -e .git ]; then \ + case "$VERSION" in ""|dev|unknown) \ + echo "version is '$VERSION' although .git is present" >&2; \ + exit 1 ;; \ + esac; \ + fi; \ + CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.Version=${VERSION}" -o /neoircd ./cmd/neoircd/ RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /neoirc-cli ./cmd/neoirc-cli/ # Runtime stage diff --git a/Makefile b/Makefile index 67ceb3e..6758284 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,7 @@ BINARY := neoircd VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") -BUILDARCH := $(shell go env GOARCH) -LDFLAGS := -X main.Version=$(VERSION) -X main.Buildarch=$(BUILDARCH) +LDFLAGS := -X main.Version=$(VERSION) all: check build @@ -32,7 +31,7 @@ fmt-check: @test -z "$$(gofmt -l .)" || (echo "Files not formatted:" && gofmt -l . && exit 1) test: ensure-web-dist - go test -timeout 30s -race -cover ./... || go test -timeout 30s -race -v ./... + go test -timeout 120s -race -cover ./... || go test -timeout 120s -race -v ./... # check runs all validation without making changes # Used by CI and Docker build — fails if anything is wrong diff --git a/internal/ircserver/relay.go b/internal/ircserver/relay.go index cb7d185..6d4195f 100644 --- a/internal/ircserver/relay.go +++ b/internal/ircserver/relay.go @@ -128,7 +128,11 @@ func (c *Conn) deliverIRCMessage( default: // Unknown command — deliver as server notice. if text != "" { - c.sendFromServer("NOTICE", c.nick, text) + c.mu.Lock() + nick := c.nick + c.mu.Unlock() + + c.sendFromServer("NOTICE", nick, text) } } } @@ -158,8 +162,12 @@ func (c *Conn) deliverNumeric( _ = json.Unmarshal(msg.Params, ¶ms) } + c.mu.Lock() + nick := c.nick + c.mu.Unlock() + allParams := make([]string, 0, 1+len(params)+1) - allParams = append(allParams, c.nick) + allParams = append(allParams, nick) allParams = append(allParams, params...) if text != "" { @@ -177,8 +185,12 @@ func (c *Conn) deliverTextMessage( from := msg.From target := msg.To + c.mu.Lock() + nick := c.nick + c.mu.Unlock() + // Don't echo our own messages back. - if strings.EqualFold(from, c.nick) { + if strings.EqualFold(from, nick) { return } @@ -192,9 +204,13 @@ func (c *Conn) deliverTextMessage( // deliverJoin sends a JOIN notification. func (c *Conn) deliverJoin(msg *db.IRCMessage) { + c.mu.Lock() + nick := c.nick + c.mu.Unlock() + // Don't echo our own JOINs (we already sent them // during joinChannel). - if strings.EqualFold(msg.From, c.nick) { + if strings.EqualFold(msg.From, nick) { return } @@ -206,7 +222,11 @@ func (c *Conn) deliverJoin(msg *db.IRCMessage) { // deliverPart sends a PART notification. func (c *Conn) deliverPart(msg *db.IRCMessage, text string) { - if strings.EqualFold(msg.From, c.nick) { + c.mu.Lock() + nick := c.nick + c.mu.Unlock() + + if strings.EqualFold(msg.From, nick) { return } @@ -227,7 +247,11 @@ func (c *Conn) deliverNickChange( msg *db.IRCMessage, newNick string, ) { - if strings.EqualFold(msg.From, c.nick) { + c.mu.Lock() + nick := c.nick + c.mu.Unlock() + + if strings.EqualFold(msg.From, nick) { return } @@ -241,7 +265,11 @@ func (c *Conn) deliverQuitMsg( msg *db.IRCMessage, text string, ) { - if strings.EqualFold(msg.From, c.nick) { + c.mu.Lock() + nick := c.nick + c.mu.Unlock() + + if strings.EqualFold(msg.From, nick) { return } @@ -302,7 +330,11 @@ func (c *Conn) deliverInviteMsg( _ *db.IRCMessage, text string, ) { - c.sendFromServer("NOTICE", c.nick, text) + c.mu.Lock() + nick := c.nick + c.mu.Unlock() + + c.sendFromServer("NOTICE", nick, text) } // deliverMode sends a MODE change notification. diff --git a/internal/server/server.go b/internal/server/server.go index 50216fa..1006342 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -71,7 +71,14 @@ func New( lifecycle.Append(fx.Hook{ OnStart: func(_ context.Context) error { srv.startupTime = time.Now() - go srv.Run() //nolint:contextcheck + + // Build the router before OnStart returns, so srv can + // handle requests as soon as the app has started. + srv.configure() + srv.enableSentry() + srv.SetupRoutes() + + go srv.serve() //nolint:contextcheck return nil }, @@ -83,13 +90,6 @@ func New( return srv, nil } -// Run starts the server configuration, Sentry, and begins serving. -func (srv *Server) Run() { - srv.configure() - srv.enableSentry() - srv.serve() -} - // ServeHTTP delegates to the chi router. func (srv *Server) ServeHTTP( writer http.ResponseWriter, @@ -202,8 +202,6 @@ func (srv *Server) serveUntilShutdown() { Handler: srv, } - srv.SetupRoutes() - srv.log.Info( "http begin listen", "listenaddr", listenAddr, )