fix Makefile

Kim committed Oct 21, 2025 at 11:56 UTC 68843fa4bed43a4782eac32a19d6478e548a26a1
6 files changed +359 -264
Dockerfile new
+22
@@ -0,0 +1,22 @@
1 +# Multi-stage build for relayserver
2 +FROM golang:1 AS builder
3 +
4 +WORKDIR /src
5 +
6 +COPY go.mod go.sum ./
7 +RUN --mount=type=cache,target=/go/pkg/mod \
8 + go mod download
9 +
10 +COPY . .
11 +RUN --mount=type=cache,target=/go/pkg/mod \
12 + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
13 + go build -trimpath -ldflags "-s -w" -o /out/relayserver ./cmd/server
14 +
15 +# Minimal runtime image
16 +FROM gcr.io/distroless/static-debian12:nonroot
17 +
18 +COPY --from=builder /out/relayserver /usr/bin/relayserver
19 +
20 +EXPOSE 8080 8082 4001/tcp 4001/udp
21 +
22 +ENTRYPOINT ["/usr/bin/relayserver"]
Makefile new
+51
@@ -0,0 +1,51 @@
1 +SHELL := /bin/sh
2 +.PHONY: help server-up server-down server-build client-run client-build fmt tidy
3 +
4 +# Detect docker compose command (override with `make DC="docker-compose"` if needed)
5 +DC ?= docker compose
6 +
7 +# ---------- Server (docker) ----------
8 +server-up:
9 + $(DC) up -d --build
10 +
11 +server-down:
12 + $(DC) down
13 +
14 +server-build:
15 + $(DC) build
16 +
17 +# ---------- Client (local go) ----------
18 +# Default flags (override via `make VAR=value`)
19 +SERVER_URL ?= http://localhost:8080
20 +BACKEND_HTTP ?= :8081
21 +# Optional repeated flags: BOOTSTRAPS="/dnsaddr/example/p2p/12D3... /ip4/1.2.3.4/tcp/4001/p2p/12D3..."
22 +BOOTSTRAPS ?=
23 +CLIENT_BOOTSTRAPS_FLAGS := $(foreach b,$(BOOTSTRAPS),--bootstrap $(b))
24 +
25 +CLIENT_FLAGS := \
26 + --server-url $(SERVER_URL) \
27 + --backend-http $(BACKEND_HTTP) \
28 + $(CLIENT_BOOTSTRAPS_FLAGS)
29 +
30 +client-run:
31 + go run ./cmd/example_client $(CLIENT_FLAGS)
32 +
33 +client-build:
34 + go build -trimpath -o bin/relaydns-client ./cmd/example_client
35 +
36 +# ---------- Dev helpers ----------
37 +fmt:
38 + go fmt ./...
39 +
40 +tidy:
41 + go mod tidy
42 +
43 +help:
44 + @echo "Server:"
45 + @echo " make server-up # build and start relayserver (docker compose)"
46 + @echo " make server-down # stop and remove containers"
47 + @echo "\nClient:"
48 + @echo " make client-run # run example_client locally with minimal flags"
49 + @echo " make client-build # build example_client to ./bin/relaydns-client"
50 + @echo "\nFlags (override with make VAR=value):"
51 + @echo " SERVER_URL BACKEND_HTTP BOOTSTRAPS"
README.md
+136 -94
@@ -1,94 +1,136 @@
1 -# RelayDNS
2 -> A lightweight, DNS-driven peer-to-peer proxy layer built on libp2p.
3 -
4 -`relaydns` provides a minimal DNS-entry proxy that routes traffic between arbitrary nodes over **libp2p**.
5 -It lets you expose and discover TCP services (like SSH, API endpoints, etc.) even behind NAT,
6 -without depending on centralized reverse-proxy services.
7 -
8 -## Features
9 -
10 -- 🛰 **Peer-to-peer routing** over libp2p (supports hole punching, relay, pubsub)
11 -- 🧩 **DNS-driven entrypoint** (server acts as a lightweight coordinator)
12 -- 🔄 **Automatic peer advertisement** via GossipSub
13 -- 🔌 **Pluggable client SDK** — embed the relaydns client directly into your Go applications
14 -- 🪶 **Lightweight** and dependency-minimal (Cobra CLI + Go libp2p only)
15 -
16 -## Architecture Overview
17 -
18 -```
19 -┌──────────────┐ pubsub (GossipSub) ┌──────────────┐
20 -│ relaydns │ <--------------------------> │ client(s) │
21 -│ server │ │ (imported in │
22 -│ (director) │ │ your app) │
23 -└──────────────┘ └──────────────┘
24 - │ │
25 - │ TCP stream (e.g. SSH, HTTP, custom) │
26 - ▼ ▼
27 - Your users Your local service
28 -```
29 -
30 -## Getting Started
31 -
32 -### 1️⃣ Run the RelayDNS Server
33 -
34 -The **server** acts as a public entrypoint that accepts incoming TCP connections
35 -and forwards them over libp2p to available clients.
36 -
37 -```bash
38 -go build -o relaydns ./cmd/relaydns
39 -
40 -./relaydns \
41 - --listen-tcp :22 \
42 - --listen-http :8080 \
43 - --protocol /relaydns/ssh/1.0 \
44 - --topic relaydns.backends
45 -```
46 -
47 -### 2️⃣ Embed the RelayDNS Client in Your App
48 -
49 -The client is a small Go library that you can embed in any Go program.
50 -It automatically advertises itself and tunnels incoming streams to your local TCP service.
51 -
52 -Install the module:
53 -```bash
54 -go get github.com/gosuda/relaydns
55 -```
56 -
57 -Example usage:
58 -```go
59 -package main
60 -
61 -import (
62 - "context"
63 - "log"
64 - "time"
65 -
66 - "github.com/libp2p/go-libp2p"
67 - "github.com/gosuda/relaydns/relaydns"
68 -)
69 -
70 -func main() {
71 - ctx := context.Background()
72 - h, err := libp2p.New(
73 - libp2p.EnableHolePunching(),
74 - libp2p.EnableNATService(),
75 - )
76 - if err != nil {
77 - log.Fatal(err)
78 - }
79 -
80 - client, err := relaydns.NewClient(ctx, h, relaydns.ClientConfig{
81 - Protocol: "/relaydns/ssh/1.0",
82 - Topic: "relaydns.backends",
83 - AdvertiseEvery: 5 * time.Second,
84 - TargetTCP: "127.0.0.1:22", // your local SSH or app port
85 - Name: "seoul-node",
86 - })
87 - if err != nil {
88 - log.Fatal(err)
89 - }
90 - defer client.Close()
91 -
92 - select {} // keep running
93 -}
94 -```
\ No newline at end of file
1 +# RelayDNS
2 +> A lightweight, DNS-driven peer-to-peer proxy layer built on libp2p.
3 +
4 +`relaydns` provides a minimal DNS-entry proxy that routes traffic between arbitrary nodes over **libp2p**.
5 +It lets you expose and discover TCP services (like SSH, API endpoints, etc.) even behind NAT,
6 +without depending on centralized reverse-proxy services.
7 +
8 +## Features
9 +
10 +- 🛰 **Peer-to-peer routing** over libp2p (supports hole punching, relay, pubsub)
11 +- 🧩 **DNS-driven entrypoint** (server acts as a lightweight coordinator)
12 +- 🔄 **Automatic peer advertisement** via GossipSub
13 +- 🔌 **Pluggable client SDK** — embed the relaydns client directly into your Go applications
14 +- 🪶 **Lightweight** and dependency-minimal (Cobra CLI + Go libp2p only)
15 +
16 +## Architecture Overview
17 +
18 +```
19 +┌──────────────┐ pubsub (GossipSub) ┌──────────────┐
20 +│ relaydns │ <--------------------------> │ client(s) │
21 +│ server │ │ (imported in │
22 +│ (director) │ │ your app) │
23 +└──────────────┘ └──────────────┘
24 + │ │
25 + │ TCP stream (e.g. SSH, HTTP, custom) │
26 + ▼ ▼
27 + Your users Your local service
28 +```
29 +
30 +## Getting Started
31 +
32 +### 1️⃣ Run the Server (Docker Compose)
33 +
34 +The server accepts incoming TCP connections and forwards them over libp2p to available clients.
35 +
36 +Option A — using Makefile:
37 +```bash
38 +make server-up # build + start
39 +make server-down # stop
40 +```
41 +
42 +Option B — raw docker compose:
43 +```bash
44 +docker compose up --build -d
45 +docker compose logs -f relayserver
46 +```
47 +
48 +Published ports:
49 +- Admin HTTP: `8080`
50 +- HTTP ingress (tcp-level): `8082`
51 +- libp2p TCP/QUIC: `4001/tcp`, `4001/udp`
52 +
53 +To add bootstraps, edit `docker-compose.yml` and append repeated `--bootstrap` flags under `relayserver.command`.
54 +
55 +### 2️⃣ Run the Example Client (Local Go)
56 +
57 +The example client runs a local HTTP backend and advertises it via libp2p.
58 +
59 +Option A — using Makefile (recommended):
60 +```bash
61 +make client-run \
62 + BACKEND_HTTP=:8081 \
63 + SERVER_URL=http://localhost:8080 \
64 + BOOTSTRAPS="/dnsaddr/your.bootstrap/p2p/12D3Koo..."
65 +```
66 +
67 +Option B — go run directly:
68 +```bash
69 +go run ./cmd/example_client \
70 + --backend-http :8081 \
71 + --server-url http://localhost:8080 \
72 + --bootstrap /dnsaddr/your.bootstrap/p2p/12D3Koo... \
73 +
74 +```
75 +
76 +The client exposes a tiny local HTTP server at `--backend-http` and tunnels traffic from the server to this address via libp2p streams.
77 +
78 +### 3️⃣ Embed the Client SDK in Your App
79 +
80 +Install the module:
81 +```bash
82 +go get github.com/gosuda/relaydns
83 +```
84 +
85 +Minimal snippet:
86 +```go
87 +package main
88 +
89 +import (
90 + "context"
91 + "time"
92 + "github.com/gosuda/relaydns/relaydns"
93 + "github.com/libp2p/go-libp2p"
94 +)
95 +
96 +func main() {
97 + ctx := context.Background()
98 + h, _ := libp2p.New(libp2p.EnableHolePunching(), libp2p.EnableNATService())
99 + client, _ := relaydns.NewClient(ctx, h, relaydns.ClientConfig{
100 + Protocol: "/relaydns/http/1.0",
101 + Topic: "relaydns.backends",
102 + AdvertiseEvery: 5 * time.Second,
103 + TargetTCP: "127.0.0.1:8081",
104 + Name: "demo-http",
105 + })
106 + defer client.Close()
107 + select {}
108 +}
109 +```
110 +
111 +## Configuration Reference
112 +
113 +Server flags (see `docker-compose.yml`):
114 +- `--admin-http` Admin API listen address (default `:8080`)
115 +- `--ingress-http` HTTP ingress listen address (default `:8082`)
116 +- `--bootstrap` Repeatable multiaddr with `/p2p/`
117 +
118 +Example client flags (see `make client-run`):
119 +- `--server-url` Admin base URL to fetch `/health` (default `http://localhost:8080`)
120 +- `--bootstrap` Repeatable multiaddr with `/p2p/`
121 +- `--backend-http` Local backend HTTP listen address (default `:8081`)
122 +
123 +## Logging
124 +
125 +This project uses `zerolog` for structured logging. Binaries initialize a human-friendly console logger by default.
126 +
127 +## Development
128 +
129 +Useful commands:
130 +```bash
131 +make server-up # build and start the server via docker compose
132 +make server-down # stop the compose stack
133 +
134 +make client-run # run the example client locally
135 +make client-build # build the example client to ./bin/relaydns-client
136 +```
cmd/example_client/main.go
+39 -59
@@ -1,19 +1,19 @@
1 package main
2
3 -import (
4 - "context"
5 - "fmt"
6 - "net/http"
7 - "os"
8 - "os/signal"
9 - "syscall"
10 - "text/template"
11 - "time"
12 -
13 - "github.com/gosuda/relaydns/relaydns"
14 - "github.com/rs/zerolog/log"
15 - "github.com/spf13/cobra"
16 -)
3 +import (
4 + "context"
5 + "fmt"
6 + "net/http"
7 + "os"
8 + "os/signal"
9 + "syscall"
10 + "text/template"
11 + "time"
12 +
13 + "github.com/gosuda/relaydns/relaydns"
14 + "github.com/rs/zerolog/log"
15 + "github.com/spf13/cobra"
16 +)
17
18 var rootCmd = &cobra.Command{
19 Use: "relaydns-client",
@@ -21,36 +21,18 @@ var rootCmd = &cobra.Command{
21 RunE: runClient,
22 }
23
24 -var (
25 - flagServerURL string
26 - flagBootstraps []string
27 - flagRelay bool
28 - flagBackendHTTP string
29 - flagProtocol string
30 - flagTopic string
31 - flagAdvertiseEvery time.Duration
32 - flagName string
33 - flagDNS string
34 - flagPreferQUIC bool
35 - flagPreferLocal bool
36 - flagHTTPTimeout time.Duration
37 -)
24 +var (
25 + flagServerURL string
26 + flagBootstraps []string
27 + flagBackendHTTP string
28 +)
29
39 -func init() {
40 - flags := rootCmd.PersistentFlags()
41 - flags.StringVar(&flagServerURL, "server-url", "http://localhost:8080", "relayserver admin base URL (e.g. http://127.0.0.1:9090) to auto-fetch multiaddrs from /health")
42 - flags.StringSliceVar(&flagBootstraps, "bootstrap", nil, "multiaddrs with /p2p/ (supports /dnsaddr/ that resolves to /p2p/)")
43 - flags.BoolVar(&flagRelay, "relay", true, "enable libp2p relay/hole-punch support")
44 - flags.StringVar(&flagBackendHTTP, "backend-http", ":8081", "local backend HTTP listen address")
45 - flags.StringVar(&flagProtocol, "protocol", "/relaydns/http/1.0", "libp2p protocol id for streams (must match server)")
46 - flags.StringVar(&flagTopic, "topic", "relaydns.backends", "pubsub topic for backend adverts")
47 - flags.DurationVar(&flagAdvertiseEvery, "advertise-every", 3*time.Second, "interval for backend adverts")
48 - flags.StringVar(&flagName, "name", "demo-http", "backend display name")
49 - flags.StringVar(&flagDNS, "dns", "demo-http.example", "backend DNS metadata (optional)")
50 - flags.BoolVar(&flagPreferQUIC, "prefer-quic", true, "prefer QUIC multiaddrs when available")
51 - flags.BoolVar(&flagPreferLocal, "prefer-local", true, "prefer loopback/local multiaddrs when available")
52 - flags.DurationVar(&flagHTTPTimeout, "http-timeout", 3*time.Second, "timeout for server /health fetch")
53 -}
30 +func init() {
31 + flags := rootCmd.PersistentFlags()
32 + flags.StringVar(&flagServerURL, "server-url", "http://localhost:8080", "relayserver admin base URL to auto-fetch multiaddrs from /health")
33 + flags.StringSliceVar(&flagBootstraps, "bootstrap", nil, "multiaddrs with /p2p/ (supports /dnsaddr/ that resolves to /p2p/)")
34 + flags.StringVar(&flagBackendHTTP, "backend-http", ":8081", "local backend HTTP listen address")
35 +}
36
37 func main() {
38 if err := rootCmd.Execute(); err != nil {
@@ -89,26 +71,24 @@ func runClient(cmd *cobra.Command, args []string) error {
71 }
72 }()
73
92 - // 2) libp2p host
93 - h, err := relaydns.MakeHost(ctx, 0, flagRelay)
74 + // 2) libp2p host
75 + h, err := relaydns.MakeHost(ctx, 0, true)
76 if err != nil {
77 return fmt.Errorf("make host: %w", err)
78 }
79
98 - client, err := relaydns.NewClient(ctx, h, relaydns.ClientConfig{
99 - Protocol: flagProtocol,
100 - Topic: flagTopic,
101 - AdvertiseEvery: flagAdvertiseEvery,
102 - Name: flagName,
103 - DNS: flagDNS,
104 - TargetTCP: addrToTarget(flagBackendHTTP),
105 -
106 - ServerURL: flagServerURL,
107 - Bootstraps: flagBootstraps,
108 - HTTPTimeout: flagHTTPTimeout,
109 - PreferQUIC: flagPreferQUIC,
110 - PreferLocal: flagPreferLocal,
111 - })
80 + client, err := relaydns.NewClient(ctx, h, relaydns.ClientConfig{
81 + Protocol: "/relaydns/http/1.0",
82 + Topic: "relaydns.backends",
83 + AdvertiseEvery: 3 * time.Second,
84 + TargetTCP: addrToTarget(flagBackendHTTP),
85 +
86 + ServerURL: flagServerURL,
87 + Bootstraps: flagBootstraps,
88 + HTTPTimeout: 3 * time.Second,
89 + PreferQUIC: true,
90 + PreferLocal: true,
91 + })
92 if err != nil {
93 return fmt.Errorf("new client: %w", err)
94 }
cmd/server/main.go
+89 -111
@@ -1,111 +1,89 @@
1 -package main
2 -
3 -import (
4 - "context"
5 - "os"
6 - "os/signal"
7 - "syscall"
8 - "time"
9 -
10 - "github.com/gosuda/relaydns/relaydns"
11 - "github.com/rs/zerolog/log"
12 - "github.com/spf13/cobra"
13 -)
14 -
15 -var rootCmd = &cobra.Command{
16 - Use: "relayserver",
17 - Short: "A lightweight, DNS-driven peer-to-peer proxy layer built on libp2p",
18 - RunE: runServer,
19 -}
20 -
21 -var (
22 - flagBootstraps []string
23 - flagRelay bool
24 -
25 - ingressTCP string // e.g. :22 (raw TCP ingress, SSH)
26 - ingressHTTP string // e.g. :8082 (HTTP ingress, Browser)
27 - adminHTTP string // e.g. :8080 (admin API)
28 - outBoundPort int // e.g. :4001 (outbound connections)
29 - protocol string // e.g. /relaydns/http/1.0
30 - topic string // e.g. relaydns.backends
31 -)
32 -
33 -func init() {
34 - flags := rootCmd.PersistentFlags()
35 - flags.StringSliceVar(&flagBootstraps, "bootstrap", nil, "multiaddrs with /p2p/ (supports /dnsaddr/ that resolves to /p2p/)")
36 - flags.BoolVar(&flagRelay, "relay", true, "enable libp2p relay support")
37 -
38 - flags.StringVar(&ingressTCP, "ingress-tcp", ":22", "L4 TCP ingress (e.g. :22 for SSH/raw TCP)")
39 - flags.StringVar(&ingressHTTP, "ingress-http", ":8082", "HTTP ingress (browser-friendly TCP port for HTTP backends)")
40 - flags.StringVar(&adminHTTP, "admin-http", ":8080", "Admin HTTP API (status/control)")
41 - flags.IntVar(&outBoundPort, "outbound-port", 4001, "Outbound connections")
42 -
43 - flags.StringVar(&protocol, "protocol", "/relaydns/http/1.0", "libp2p protocol id for streams (must match clients)")
44 - flags.StringVar(&topic, "topic", "relaydns.backends", "pubsub topic for backend adverts")
45 -}
46 -
47 -func main() {
48 - if err := rootCmd.Execute(); err != nil {
49 - log.Fatal().Err(err).Msg("execute root command")
50 - }
51 -}
52 -
53 -func runServer(cmd *cobra.Command, args []string) error {
54 - ctx, cancel := context.WithCancel(context.Background())
55 - defer cancel()
56 -
57 - h, err := relaydns.MakeHost(ctx, outBoundPort, flagRelay)
58 - if err != nil {
59 - return err
60 - }
61 - relaydns.ConnectBootstraps(ctx, h, flagBootstraps)
62 -
63 - d, err := relaydns.NewDirector(ctx, h, protocol, topic)
64 - if err != nil {
65 - return err
66 - }
67 -
68 - // 1) admin API
69 - go func() {
70 - if adminHTTP == "" {
71 - return
72 - }
73 - log.Info().Msgf("[server] admin http: %s", adminHTTP)
74 - if err := d.ServeHTTP(adminHTTP); err != nil {
75 - log.Error().Err(err).Msg("[server] admin http error")
76 - cancel()
77 - }
78 - }()
79 -
80 - // 2) L4 TCP ingress (SSH/raw TCP)
81 - go func() {
82 - if ingressTCP == "" {
83 - return
84 - }
85 - log.Info().Msgf("[server] tcp ingress: %s", ingressTCP)
86 - if err := d.ServeTCP(ingressTCP); err != nil {
87 - log.Error().Err(err).Msg("[server] tcp ingress error")
88 - cancel()
89 - }
90 - }()
91 -
92 - // 3) HTTP ingress (browser/HTTP traffic)
93 - go func() {
94 - if ingressHTTP == "" {
95 - return
96 - }
97 - log.Info().Msgf("[server] http ingress (tcp-level): %s", ingressHTTP)
98 - if err := d.ServeTCP(ingressHTTP); err != nil {
99 - log.Error().Err(err).Msg("[server] http ingress error")
100 - cancel()
101 - }
102 - }()
103 -
104 - // graceful shutdown
105 - sig := make(chan os.Signal, 1)
106 - signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
107 - <-sig
108 - cancel()
109 - time.Sleep(300 * time.Millisecond)
110 - return nil
111 -}
1 +package main
2 +
3 +import (
4 + "context"
5 + "os"
6 + "os/signal"
7 + "syscall"
8 + "time"
9 +
10 + "github.com/gosuda/relaydns/relaydns"
11 + "github.com/rs/zerolog/log"
12 + "github.com/spf13/cobra"
13 +)
14 +
15 +var rootCmd = &cobra.Command{
16 + Use: "relayserver",
17 + Short: "A lightweight, DNS-driven peer-to-peer proxy layer built on libp2p",
18 + RunE: runServer,
19 +}
20 +
21 +var (
22 + flagBootstraps []string
23 +
24 + ingressHTTP string // e.g. :8082 (HTTP ingress, Browser)
25 + adminHTTP string // e.g. :8080 (admin API)
26 +)
27 +
28 +func init() {
29 + flags := rootCmd.PersistentFlags()
30 + flags.StringSliceVar(&flagBootstraps, "bootstrap", nil, "multiaddrs with /p2p/ (supports /dnsaddr/ that resolves to /p2p/)")
31 +
32 + flags.StringVar(&ingressHTTP, "ingress-http", ":8082", "HTTP ingress (browser-friendly TCP port for HTTP backends)")
33 + flags.StringVar(&adminHTTP, "admin-http", ":8080", "Admin HTTP API (status/control)")
34 +}
35 +
36 +func main() {
37 + if err := rootCmd.Execute(); err != nil {
38 + log.Fatal().Err(err).Msg("execute root command")
39 + }
40 +}
41 +
42 +func runServer(cmd *cobra.Command, args []string) error {
43 + ctx, cancel := context.WithCancel(context.Background())
44 + defer cancel()
45 +
46 + const outBoundPort = 4001
47 + h, err := relaydns.MakeHost(ctx, outBoundPort, true)
48 + if err != nil {
49 + return err
50 + }
51 + relaydns.ConnectBootstraps(ctx, h, flagBootstraps)
52 +
53 + d, err := relaydns.NewDirector(ctx, h, "/relaydns/http/1.0", "relaydns.backends")
54 + if err != nil {
55 + return err
56 + }
57 +
58 + // 1) admin API
59 + go func() {
60 + if adminHTTP == "" {
61 + return
62 + }
63 + log.Info().Msgf("[server] admin http: %s", adminHTTP)
64 + if err := d.ServeHTTP(adminHTTP); err != nil {
65 + log.Error().Err(err).Msg("[server] admin http error")
66 + cancel()
67 + }
68 + }()
69 +
70 + // 2) HTTP ingress (browser/HTTP traffic)
71 + go func() {
72 + if ingressHTTP == "" {
73 + return
74 + }
75 + log.Info().Msgf("[server] http ingress (tcp-level): %s", ingressHTTP)
76 + if err := d.ServeTCP(ingressHTTP); err != nil {
77 + log.Error().Err(err).Msg("[server] http ingress error")
78 + cancel()
79 + }
80 + }()
81 +
82 + // graceful shutdown
83 + sig := make(chan os.Signal, 1)
84 + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
85 + <-sig
86 + cancel()
87 + time.Sleep(300 * time.Millisecond)
88 + return nil
89 +}
docker-compose.yml new
+22
@@ -0,0 +1,22 @@
1 +version: "3.9"
2 +
3 +services:
4 + relayserver:
5 + build:
6 + context: .
7 + dockerfile: Dockerfile
8 + image: relaydns/server:local
9 + command:
10 + - "--admin-http"
11 + - ":8080"
12 + - "--ingress-http"
13 + - ":8082"
14 + # To add bootstraps, add more lines like below (repeatable):
15 + # - "--bootstrap"
16 + # - "/dnsaddr/bootstrap.example/p2p/12D3Koo..."
17 + ports:
18 + - "8080:8080" # Admin HTTP
19 + - "8082:8082" # HTTP ingress (tcp-level)
20 + - "4001:4001/tcp" # libp2p tcp
21 + - "4001:4001/udp" # libp2p quic/udp
22 + restart: unless-stopped