Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ LABEL org.opencontainers.image.title="Miabi Agent" \
org.opencontainers.image.url="https://github.com/miabi-io/agent" \
org.opencontainers.image.source="https://github.com/miabi-io/agent" \
org.opencontainers.image.documentation="https://github.com/miabi-io/agent#readme" \
org.opencontainers.image.licenses="Apache-2.0"
org.opencontainers.image.licenses="AGPL-3.0-or-later"
RUN apk add --no-cache ca-certificates && adduser -D -u 10001 agent
COPY --from=build /miabi-agent /usr/local/bin/miabi-agent
# Note: reaching /var/run/docker.sock typically requires the host's docker group
Expand Down
862 changes: 661 additions & 201 deletions LICENSE

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,6 @@ page if leaked.

## License

Apache-2.0 — see [LICENSE](./LICENSE).
AGPL-3.0-or-later — see [LICENSE](./LICENSE). A commercial license is available
for uses that don't fit the AGPL; see the [Miabi](https://github.com/miabi-io/miabi)
project's licensing.
106 changes: 28 additions & 78 deletions agent.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,18 @@
/*
* Copyright 2026 Jonas Kaninda
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// SPDX-FileCopyrightText: 2026 Jonas Kaninda
// SPDX-License-Identifier: AGPL-3.0-or-later

package main

import (
"context"
"crypto/tls"
"io"
"net"
"net/http"
"strings"
"time"

"github.com/gorilla/websocket"
"github.com/hashicorp/yamux"
"github.com/jkaninda/logger"
"github.com/miabi-io/wstunnel"
)

// Config configures the agent runtime.
Expand All @@ -43,62 +28,39 @@ type Config struct {
const connectPath = "/api/v1/agent/connect"

// Run connects to the control plane and serves Docker over the tunnel until ctx
// is cancelled, reconnecting with exponential backoff.
// is cancelled, reconnecting with exponential backoff. The WebSocket + yamux
// transport (framing, keepalive, dial, reconnect loop) is the shared wstunnel
// module, so the agent and control plane always speak the same wire protocol.
func Run(ctx context.Context, cfg Config) error {
backoff := time.Second
for {
if err := serve(ctx, cfg); err != nil && ctx.Err() == nil {
logger.Warn("agent disconnected", "error", err, "retry_in", backoff.String())
} else if ctx.Err() == nil {
backoff = time.Second // a clean session resets backoff
}
if ctx.Err() != nil {
return ctx.Err()
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(backoff):
}
if backoff < 30*time.Second {
backoff *= 2
}
}
}

// serve runs one connection lifecycle.
func serve(ctx context.Context, cfg Config) error {
header := http.Header{}
header.Set("Authorization", "Bearer "+cfg.Token)
header.Set("X-Agent-Version", cfg.Version)
if cfg.ContainerID != "" {
header.Set("X-Agent-Container-ID", cfg.ContainerID)
}

dialer := *websocket.DefaultDialer
if cfg.Insecure {
dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
ws, _, err := dialer.DialContext(ctx, wsURL(cfg.ControlURL), header)
if err != nil {
return err
opts := wstunnel.ClientOptions{
URL: wstunnel.URL(cfg.ControlURL, connectPath),
Header: header,
Insecure: cfg.Insecure,
OnConnect: func() {
logger.Info("connected to control plane", "control_url", cfg.ControlURL)
},
OnError: func(err error) {
logger.Warn("agent disconnected", "error", err)
},
}
defer func() { _ = ws.Close() }()

sess, err := tunnelServer(ws)
if err != nil {
return err
}
defer func() { _ = sess.Close() }()
logger.Info("connected to control plane", "control_url", cfg.ControlURL)

for {
stream, err := sess.AcceptStream()
if err != nil {
return err
// Each accepted stream is one Docker API request the control plane opened;
// pipe it to the local Docker daemon. The handler blocks for the session's
// lifetime, so wstunnel.Serve reconnects when it returns.
return wstunnel.Serve(ctx, opts, func(_ context.Context, sess *yamux.Session) error {
for {
stream, err := sess.AcceptStream()
if err != nil {
return err
}
go pipeToDocker(stream, cfg.DockerHost)
}
go pipeToDocker(stream, cfg.DockerHost)
}
})
}

// pipeToDocker proxies one tunnel stream to the local Docker daemon.
Expand Down Expand Up @@ -131,15 +93,3 @@ func dialDocker(host string) (net.Conn, error) {
return net.Dial("unix", host)
}
}

// wsURL converts the control-plane base URL to the agent WebSocket endpoint.
func wsURL(base string) string {
base = strings.TrimRight(base, "/")
switch {
case strings.HasPrefix(base, "https://"):
base = "wss://" + strings.TrimPrefix(base, "https://")
case strings.HasPrefix(base, "http://"):
base = "ws://" + strings.TrimPrefix(base, "http://")
}
return base + connectPath
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ module github.com/miabi-io/miabi-agent
go 1.25.0

require (
github.com/gorilla/websocket v1.5.3
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/yamux v0.1.2
)

require (
github.com/jkaninda/go-utils v0.1.4
github.com/jkaninda/logger v0.0.5
github.com/miabi-io/wstunnel v0.0.1
)

require gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ github.com/jkaninda/go-utils v0.1.4 h1:ZdNlI+yLWc4/S0qKcCNQIPj+6lHSdJcGaxtRADAif
github.com/jkaninda/go-utils v0.1.4/go.mod h1:Aa54jEAcDykc3CnOdreqZG80UfSZOvrYecyusu+oPb4=
github.com/jkaninda/logger v0.0.5 h1:fTHKgDsHtuN8rkSBvwe6QStfi8yIdm8O3r0g99dRDTY=
github.com/jkaninda/logger v0.0.5/go.mod h1:ZUXJ2BdxDPG6e8t6mbKhc2ZFaFi2Iuy/4ukquFrPkFE=
github.com/miabi-io/wstunnel v0.0.1 h1:LDUjrYXYDPLeHcvUxBDC4b1JrD90GJ15xTQx0oAtz2I=
github.com/miabi-io/wstunnel v0.0.1/go.mod h1:lW1sYEWx4eHMoTFUxewGp+3jHWNEZO1LJpwB9b/eLtA=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
18 changes: 2 additions & 16 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
/*
* Copyright 2026 Jonas Kaninda
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// SPDX-FileCopyrightText: 2026 Jonas Kaninda
// SPDX-License-Identifier: AGPL-3.0-or-later

// Command miabi-agent is the node-side runtime for Miabi multi-node.
// It dials the control plane over an outbound WebSocket and pipes each tunnel
Expand Down
18 changes: 2 additions & 16 deletions selfcontainer.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
/*
* Copyright 2026 Jonas Kaninda
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// SPDX-FileCopyrightText: 2026 Jonas Kaninda
// SPDX-License-Identifier: AGPL-3.0-or-later

package main

Expand Down
99 changes: 0 additions & 99 deletions tunnel.go

This file was deleted.