IceWhaleTech/CasaOS · error
err.Error()
Error message
err.Error()
What it means
In ZerotierProxy (route/v1/zerotier.go) this is the raw error surfaced when ioutil.ReadFile fails to read /var/lib/zerotier-one/zerotier-one.port, the file in which a running ZeroTier service publishes its local control-API port. http.Error() writes the message to the client, but the code does NOT return, so execution continues with a nil/empty port and produces a bogus target URL — the real defect this error reveals is the missing early return.
Source
Thrown at route/v1/zerotier.go:26
"net/http"
"strings"
"time"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/common"
"github.com/IceWhaleTech/CasaOS/pkg/utils/httper"
"github.com/labstack/echo/v4"
"github.com/tidwall/gjson"
"go.uber.org/zap"
)
func ZerotierProxy(ctx echo.Context) error {
// Read the port number from the file
w := ctx.Response().Writer
r := ctx.Request()
port, err := ioutil.ReadFile("/var/lib/zerotier-one/zerotier-one.port")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
// Get the request path and remove "/zt"
path := strings.TrimPrefix(r.URL.Path, "/v1/zt")
fmt.Println(path)
// Build the target URL
targetURL := fmt.Sprintf("http://localhost:%s%s", strings.TrimSpace(string(port)), path)
// Create a new request
req, err := http.NewRequest(r.Method, targetURL, r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
// Add the X-ZT1-AUTH header
authToken, err := ioutil.ReadFile("/var/lib/zerotier-one/authtoken.secret")
if err != nil {View on GitHub (pinned to 0d3b2f444e)
Solutions
- Confirm zerotier-one is running (zerotier-cli info / systemctl status zerotier-one).
- Verify the file exists and is readable: ls -l /var/lib/zerotier-one/zerotier-one.port.
- Add `return` immediately after the http.Error call so a failed read does not cascade into an invalid proxy request.
- When proxying from a container, bind-mount the host's /var/lib/zerotier-one into it.
Example fix
// before
port, err := ioutil.ReadFile("/var/lib/zerotier-one/zerotier-one.port")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
// after
port, err := os.ReadFile("/var/lib/zerotier-one/zerotier-one.port")
if err != nil {
http.Error(w, "zerotier service not available: "+err.Error(), http.StatusInternalServerError)
return
} Defensive patterns
Strategy: validation
Validate before calling
// before proxying any /v1/zt request
portBytes, err := os.ReadFile("/var/lib/zerotier-one/zerotier-one.port")
if err != nil {
// service down: fail fast with a clean message instead of proxying
return ctx.String(http.StatusServiceUnavailable, "zerotier-one service is not running")
}
port := strings.TrimSpace(string(portBytes))
if _, err := strconv.Atoi(port); err != nil {
return ctx.String(http.StatusServiceUnavailable, "zerotier port file invalid")
} Prevention
- Always `return` after http.Error in Echo/net-http handlers — the response writer does not stop execution.
- Check zerotier-one service state before enabling the ZT UI panel.
- When containerized, bind-mount the host /var/lib/zerotier-one into the CasaOS container.
- Replace deprecated ioutil with os.
When it happens
Trigger: GET/POST any /v1/zt/* route when (1) zerotier-one is not installed, (2) the service is stopped so the port file was never created/removed, (3) CasaOS runs as a user without read permission on /var/lib/zerotier-one, (4) ZeroTier version predates the port-file mechanism.
Common situations: Zerotier add-on removed but UI still issues proxy calls; fresh install where the user opened the ZT panel before starting the service; containerized CasaOS where /var/lib/zerotier-one is not bind-mounted from the host; permission hardening (chmod 700) on the zerotier state dir.
Related errors
AI-assisted analysis of IceWhaleTech/CasaOS@0d3b2f444e (2026-08-15).
Data as JSON: /api/errors/326123eb4a772ac9.
Report an issue: GitHub.