Skip to content

Commit 340d926

Browse files
authored
Merge pull request #491 from aojea/feat/sam-one-zero-config-deploy
feat(sam-one): zero-config cloud deploy, link-local filtering, and developer-oriented URL docs
2 parents b9259cc + 33fa7cf commit 340d926

14 files changed

Lines changed: 783 additions & 123 deletions

File tree

‎cmd/sam-one/main.go‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func main() {
5555
allowedAudiencesFlag string
5656
logLevel string
5757
tunnelProvider string
58+
tunnelTokenPath string
5859
tunnelInstall bool
5960
cloudflaredPath string
6061
enrollQR bool
@@ -98,6 +99,10 @@ func main() {
9899
if err != nil {
99100
logger.Fatalf("Invalid --admin-token-path: %v", err)
100101
}
102+
tunnelToken, err := secretFromPathOrEnv(tunnelTokenPath, "SAM_TUNNEL_TOKEN")
103+
if err != nil {
104+
logger.Fatalf("Invalid --tunnel-token-path: %v", err)
105+
}
101106
if externalURL == "" {
102107
externalURL = os.Getenv("SAM_EXTERNAL_URL")
103108
}
@@ -114,8 +119,11 @@ func main() {
114119
// port settled now rather than at bind time.
115120
var tun tunnel.Tunnel
116121
if tunnelProvider != "" {
117-
if externalURL != "" {
118-
logger.Fatal("--tunnel and --external-url are mutually exclusive: the tunnel provides the external URL")
122+
if tunnelToken == "" && externalURL != "" {
123+
logger.Fatal("--tunnel and --external-url are mutually exclusive unless --tunnel-token-path / SAM_TUNNEL_TOKEN is set")
124+
}
125+
if tunnelToken != "" && externalURL == "" {
126+
logger.Fatal("--tunnel-token-path / SAM_TUNNEL_TOKEN requires --external-url (the public https:// hostname routed to the tunnel)")
119127
}
120128
if port == 0 {
121129
var err error
@@ -131,6 +139,8 @@ func main() {
131139
dataDir: dataDir,
132140
cloudflaredPath: cloudflaredPath,
133141
install: tunnelInstall,
142+
token: tunnelToken,
143+
externalURL: externalURL,
134144
}); err != nil {
135145
logger.Fatalf("Failed to open tunnel: %v", err)
136146
}
@@ -202,6 +212,7 @@ func main() {
202212
rootCmd.Flags().StringVar(&allowedAudiencesFlag, "allowed-audiences", api.DefaultAudience, "Comma-separated list of allowed OIDC audiences")
203213
rootCmd.Flags().StringVar(&logLevel, "log-level", "", "Log level: debug, info, warn, error")
204214
rootCmd.Flags().StringVar(&tunnelProvider, "tunnel", "", "Publish the listener on a public URL through a tunnel provider ("+strings.Join(tunnel.Names(), ", ")+"); sets the external URL")
215+
rootCmd.Flags().StringVar(&tunnelTokenPath, "tunnel-token-path", "", "File containing the tunnel provider token (or env SAM_TUNNEL_TOKEN); use with --tunnel <provider> --external-url https://...")
205216
rootCmd.Flags().BoolVar(&tunnelInstall, "tunnel-install", false, "Download the pinned connector binary (cloudflared "+tunnel.CloudflaredVersion+", digest-verified) into <data-dir>/bin without asking; implies accepting its license")
206217
rootCmd.Flags().StringVar(&cloudflaredPath, "cloudflared-path", "", "Explicit cloudflared executable for --tunnel cloudflare (default: PATH, then <data-dir>/bin)")
207218
rootCmd.Flags().BoolVar(&enrollQR, "enroll-qr", stdoutIsTerminal(), "Print a device enrollment QR code at startup (default: when stdout is a terminal)")
@@ -304,6 +315,8 @@ type tunnelOptions struct {
304315
dataDir string
305316
cloudflaredPath string
306317
install bool
318+
token string
319+
externalURL string
307320
}
308321

309322
// openTunnel publishes the listener that Start is about to bind. The
@@ -317,6 +330,8 @@ func openTunnel(ctx context.Context, o tunnelOptions) (tunnel.Tunnel, error) {
317330
cf.Binary = o.cloudflaredPath
318331
cf.InstallDir = filepath.Join(o.dataDir, "bin")
319332
cf.Consent = installConsent(o.install)
333+
cf.Token = o.token
334+
cf.ExternalURL = o.externalURL
320335
}
321336
host := o.bindAddress
322337
if ip := net.ParseIP(host); host == "" || (ip != nil && ip.IsUnspecified()) {

‎deploy/skypilot/sam-one.yaml‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# SkyPilot recipe for deploying a standalone SAM control plane + router (sam-one)
2+
# on any cloud provider (GCP, AWS, Azure, OCI, or Kubernetes).
3+
#
4+
# Production deployment with a permanent custom domain:
5+
# sky launch -y -c sam-hub deploy/skypilot/sam-one.yaml --detach-run \
6+
# --env SAM_EXTERNAL_URL=https://mesh.example.com \
7+
# --secret SAM_TUNNEL_TOKEN
8+
#
9+
# Quick deployment with an automatic HTTPS tunnel:
10+
# sky launch -y -c sam-hub deploy/skypilot/sam-one.yaml --detach-run
11+
# sky logs sam-hub 1 --no-follow
12+
#
13+
# Note: For lightweight testing, you can lower `cpus: 0.25+` and `memory: 1+`
14+
# to run on cloud free-tier instances (such as GCP `e2-micro` or AWS `t4g.micro`).
15+
16+
name: sam-hub
17+
18+
resources:
19+
disk_size: 20
20+
use_spot: false
21+
any_of:
22+
- infra: gcp
23+
instance_type: e2-standard-2
24+
- cpus: 2+
25+
memory: 4+
26+
27+
envs:
28+
SAM_EXTERNAL_URL: ""
29+
30+
secrets:
31+
SAM_TUNNEL_TOKEN: ""
32+
33+
setup: |
34+
set -euo pipefail
35+
if ! command -v sam-one >/dev/null 2>&1; then
36+
ARCH="$(uname -m)"
37+
case "${ARCH}" in
38+
x86_64*) ARCH_NAME="x86_64" ;;
39+
aarch64*|arm64*) ARCH_NAME="arm64" ;;
40+
*) echo "Unsupported architecture: ${ARCH}" >&2; exit 1 ;;
41+
esac
42+
VERSION=$(curl -fsSL -o /dev/null -w "%{url_effective}" "https://github.com/google/sam/releases/latest" | sed 's|.*/||')
43+
if [[ -z "${VERSION}" || "${VERSION}" == "releases" ]]; then
44+
VERSION=$(curl -fsSL "https://api.github.com/repos/google/sam/releases?per_page=1" | grep '"tag_name":' | head -n 1 | sed -E 's/.*"([^"]+)".*/\1/')
45+
fi
46+
TMP_DIR=$(mktemp -d)
47+
curl -fsSL -o "${TMP_DIR}/sam.tar.gz" "https://github.com/google/sam/releases/download/${VERSION}/sam_Linux_${ARCH_NAME}.tar.gz"
48+
tar -xzf "${TMP_DIR}/sam.tar.gz" -C "${TMP_DIR}"
49+
sudo mv "${TMP_DIR}/sam-one" "${TMP_DIR}/sam-node" /usr/local/bin/
50+
rm -rf "${TMP_DIR}"
51+
fi
52+
53+
run: |
54+
set -euo pipefail
55+
mkdir -p "${HOME}/sam-one"
56+
57+
ARGS=(
58+
--data-dir "${HOME}/sam-one"
59+
--port 8080
60+
--tunnel cloudflare
61+
--tunnel-install
62+
)
63+
if [[ -n "${SAM_EXTERNAL_URL:-}" ]]; then
64+
ARGS+=(--external-url "${SAM_EXTERNAL_URL}")
65+
fi
66+
67+
exec sam-one "${ARGS[@]}"

‎internal/router/router.go‎

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -358,14 +358,15 @@ func (r *Router) Start() error {
358358
libp2p.EnableAutoNATv2(),
359359
libp2p.EnableNATService(),
360360
libp2p.AddrsFactory(func(addrs []multiaddr.Multiaddr) []multiaddr.Multiaddr {
361-
if r.config.AllowLoopback {
362-
return addrs
363-
}
364361
var filtered []multiaddr.Multiaddr
365362
for _, addr := range addrs {
366-
if !isLoopbackOrLinkLocal(addr) {
367-
filtered = append(filtered, addr)
363+
if isLinkLocal(addr) {
364+
continue
368365
}
366+
if !r.config.AllowLoopback && isLoopbackOrLinkLocal(addr) {
367+
continue
368+
}
369+
filtered = append(filtered, addr)
369370
}
370371
return filtered
371372
}),
@@ -1361,6 +1362,22 @@ func getOrGeneratePeerKey(keyPath string) (crypto.PrivKey, error) {
13611362
return priv, nil
13621363
}
13631364

1365+
func isLinkLocal(addr multiaddr.Multiaddr) bool {
1366+
for _, proto := range addr.Protocols() {
1367+
if proto.Code == multiaddr.P_IP4 || proto.Code == multiaddr.P_IP6 {
1368+
value, err := addr.ValueForProtocol(proto.Code)
1369+
if err == nil {
1370+
if ip := net.ParseIP(value); ip != nil {
1371+
if ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
1372+
return true
1373+
}
1374+
}
1375+
}
1376+
}
1377+
}
1378+
return false
1379+
}
1380+
13641381
func isLoopbackOrLinkLocal(addr multiaddr.Multiaddr) bool {
13651382
for _, proto := range addr.Protocols() {
13661383
if proto.Code == multiaddr.P_IP4 || proto.Code == multiaddr.P_IP6 {

0 commit comments

Comments
 (0)