Listen on 0.0.0.0 by default and log LAN addresses

- New CANON_HOST (default 0.0.0.0; 127.0.0.1 keeps it on one machine)
- On start, log the http:// addresses other devices can use, skipping
  Docker/VM bridge interfaces
- docker-compose publishes 0.0.0.0:4242 explicitly
- README: opening Canon from other devices and firewall note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-24 18:05:20 -07:00
1 parent 7320e3f707
commit bcc5e69094
5 files changed
+66 -2

No files matched your search

+1
View File
@@ -17,5 +17,6 @@ CANON_IGDB_CLIENT_SECRET=
# CANON_TRUST_PROXY=true # honor X-Forwarded-For / -Proto / -Host
# CANON_SECURE_COOKIES=true # always mark the session cookie Secure
# CANON_HOST=0.0.0.0 # all network interfaces; 127.0.0.1 = this machine only
# CANON_PORT=4242
# CANON_LOG_LEVEL=debug
+5
View File
@@ -30,6 +30,10 @@ Open `http://localhost:4242/setup`, enter the setup code and create your account
To skip the setup page, set `CANON_ADMIN_USERNAME` and `CANON_ADMIN_PASSWORD` in `.env` before the first start. They're only used when no account exists yet.
### Opening it from other devices
Canon listens on all network interfaces (`0.0.0.0`) by default, so phones and other computers on your network can open `http://<this computer's IP>:4242`. When run outside Docker, the startup log prints the exact addresses. If another device can't connect, the host's firewall is probably blocking the port (for example `sudo ufw allow 4242/tcp`). To keep Canon private to one machine, set `CANON_HOST=127.0.0.1`, or publish `127.0.0.1:4242:4242` in `docker-compose.yml`.
### Without Compose
```sh
@@ -60,6 +64,7 @@ Keys entered in the UI are stored in the SQLite database on your server. Keys se
| Variable | Default | |
|---|---|---|
| `CANON_HOST` | `0.0.0.0` | Interface to listen on. `0.0.0.0` makes it reachable from other devices on your network; `127.0.0.1` keeps it on this machine |
| `CANON_PORT` | `4242` | Listen port |
| `CANON_DATA_DIR` | `/data` (Docker), `./data` | Database + cached images |
| `CANON_ADMIN_USERNAME` / `_PASSWORD` | | Create the owner account on first start |
+48
View File
@@ -9,6 +9,7 @@ import (
"fmt"
"log/slog"
"math/big"
"net"
"net/http"
"os"
"os/signal"
@@ -93,6 +94,7 @@ func run(log *slog.Logger) error {
errc := make(chan error, 1)
go func() {
log.Info("Canon is running", "addr", cfg.ListenAddr, "data", cfg.DataDir, "version", version)
logURLs(log, cfg)
errc <- httpSrv.ListenAndServe()
}()
select {
@@ -149,3 +151,49 @@ func setupCode() string {
}
return string(b[:4]) + "-" + string(b[4:])
}
// logURLs prints the addresses other devices can use to reach Canon.
func logURLs(log *slog.Logger, cfg config.Config) {
if cfg.Host != "0.0.0.0" && cfg.Host != "" && cfg.Host != "::" {
log.Info("open Canon at", "url", "http://"+net.JoinHostPort(cfg.Host, cfg.Port))
return
}
// Inside Docker the container's own addresses aren't reachable from the
// network; the host's address and published port are what matter.
if _, err := os.Stat("/.dockerenv"); err == nil {
log.Info("listening on all interfaces; open http://<this computer's IP>:<published port> from any device on your network")
return
}
log.Info("open Canon at", "url", "http://localhost:"+cfg.Port)
ifaces, err := net.Interfaces()
if err != nil {
return
}
for _, iface := range ifaces {
// Skip interfaces that are down and container/VM bridges, which
// other devices can't reach.
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 || isVirtualBridge(iface.Name) {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, a := range addrs {
ipnet, ok := a.(*net.IPNet)
if !ok || ipnet.IP.To4() == nil || ipnet.IP.IsLinkLocalUnicast() {
continue
}
log.Info("open Canon on your network at", "url", "http://"+net.JoinHostPort(ipnet.IP.String(), cfg.Port))
}
}
}
func isVirtualBridge(name string) bool {
for _, prefix := range []string{"docker", "br-", "veth", "virbr", "cni", "flannel", "podman"} {
if strings.HasPrefix(name, prefix) {
return true
}
}
return false
}
+3 -1
View File
@@ -7,7 +7,9 @@ services:
container_name: canon
restart: unless-stopped
ports:
- "4242:4242"
# 0.0.0.0 makes Canon reachable from other devices on your network.
# Use "127.0.0.1:4242:4242" to keep it on this machine only.
- "0.0.0.0:4242:4242"
volumes:
- canon-data:/data
env_file:
+9 -1
View File
@@ -2,6 +2,7 @@
package config
import (
"net"
"os"
"strconv"
"strings"
@@ -12,6 +13,8 @@ import (
// fields here only act as overrides when set.
type Config struct {
ListenAddr string
Host string
Port string
DataDir string
AdminUsername string
AdminPassword string
@@ -28,9 +31,14 @@ type Config struct {
}
func Load() Config {
// 0.0.0.0 accepts connections from other devices on the network, not
// just this machine. Use 127.0.0.1 to keep it local.
host := env("CANON_HOST", "0.0.0.0")
port := env("CANON_PORT", "4242")
return Config{
ListenAddr: env("CANON_LISTEN", ":"+port),
ListenAddr: env("CANON_LISTEN", net.JoinHostPort(host, port)),
Host: host,
Port: port,
DataDir: env("CANON_DATA_DIR", "./data"),
AdminUsername: env("CANON_ADMIN_USERNAME", ""),
AdminPassword: env("CANON_ADMIN_PASSWORD", ""),