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:
1 parent
7320e3f707
commit
bcc5e69094
5 files changed
+66
-2
No files matched your search
@@ -17,5 +17,6 @@ CANON_IGDB_CLIENT_SECRET=
|
|||||||
# CANON_TRUST_PROXY=true # honor X-Forwarded-For / -Proto / -Host
|
# CANON_TRUST_PROXY=true # honor X-Forwarded-For / -Proto / -Host
|
||||||
# CANON_SECURE_COOKIES=true # always mark the session cookie Secure
|
# 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_PORT=4242
|
||||||
# CANON_LOG_LEVEL=debug
|
# CANON_LOG_LEVEL=debug
|
||||||
@@ -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.
|
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
|
### Without Compose
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -60,6 +64,7 @@ Keys entered in the UI are stored in the SQLite database on your server. Keys se
|
|||||||
|
|
||||||
| Variable | Default | |
|
| 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_PORT` | `4242` | Listen port |
|
||||||
| `CANON_DATA_DIR` | `/data` (Docker), `./data` | Database + cached images |
|
| `CANON_DATA_DIR` | `/data` (Docker), `./data` | Database + cached images |
|
||||||
| `CANON_ADMIN_USERNAME` / `_PASSWORD` | | Create the owner account on first start |
|
| `CANON_ADMIN_USERNAME` / `_PASSWORD` | | Create the owner account on first start |
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -93,6 +94,7 @@ func run(log *slog.Logger) error {
|
|||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
log.Info("Canon is running", "addr", cfg.ListenAddr, "data", cfg.DataDir, "version", version)
|
log.Info("Canon is running", "addr", cfg.ListenAddr, "data", cfg.DataDir, "version", version)
|
||||||
|
logURLs(log, cfg)
|
||||||
errc <- httpSrv.ListenAndServe()
|
errc <- httpSrv.ListenAndServe()
|
||||||
}()
|
}()
|
||||||
select {
|
select {
|
||||||
@@ -149,3 +151,49 @@ func setupCode() string {
|
|||||||
}
|
}
|
||||||
return string(b[:4]) + "-" + string(b[4:])
|
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
@@ -7,7 +7,9 @@ services:
|
|||||||
container_name: canon
|
container_name: canon
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
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:
|
volumes:
|
||||||
- canon-data:/data
|
- canon-data:/data
|
||||||
env_file:
|
env_file:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -12,6 +13,8 @@ import (
|
|||||||
// fields here only act as overrides when set.
|
// fields here only act as overrides when set.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ListenAddr string
|
ListenAddr string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
DataDir string
|
DataDir string
|
||||||
AdminUsername string
|
AdminUsername string
|
||||||
AdminPassword string
|
AdminPassword string
|
||||||
@@ -28,9 +31,14 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Load() Config {
|
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")
|
port := env("CANON_PORT", "4242")
|
||||||
return Config{
|
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"),
|
DataDir: env("CANON_DATA_DIR", "./data"),
|
||||||
AdminUsername: env("CANON_ADMIN_USERNAME", ""),
|
AdminUsername: env("CANON_ADMIN_USERNAME", ""),
|
||||||
AdminPassword: env("CANON_ADMIN_PASSWORD", ""),
|
AdminPassword: env("CANON_ADMIN_PASSWORD", ""),
|
||||||
|
|||||||
Reference in new issue
Block a user