Skip to content

Commit f68c985

Browse files
committed
BREAKING CHANGE: move Uncloud API socket to /run/uncloud/api/uncloud.sock, activate it by systemd socket unit
1 parent c65f352 commit f68c985

24 files changed

Lines changed: 345 additions & 112 deletions

File tree

‎README.md‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,9 @@ $ uc machine init --name oracle-vm [email protected]
166166
✓ uncloudd binary installed: /usr/local/bin/uncloudd
167167
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
168168
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
169+
✓ Systemd unit file created: /etc/systemd/system/uncloud.socket
169170
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
171+
Created symlink /etc/systemd/system/sockets.target.wants/uncloud.socket → /etc/systemd/system/uncloud.socket.
170172
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
171173
⏳ Starting Uncloud machine daemon (uncloud.service)...
172174
✓ Uncloud machine daemon started.
@@ -208,7 +210,9 @@ $ uc machine add --name hetzner-server [email protected]
208210
✓ uncloudd binary installed: /usr/local/bin/uncloudd
209211
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
210212
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
213+
✓ Systemd unit file created: /etc/systemd/system/uncloud.socket
211214
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
215+
Created symlink /etc/systemd/system/sockets.target.wants/uncloud.socket → /etc/systemd/system/uncloud.socket.
212216
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
213217
⏳ Starting Uncloud machine daemon (uncloud.service)...
214218
✓ Uncloud machine daemon started.
@@ -319,9 +323,9 @@ I'm grateful to the following projects that inspired Uncloud's design and implem
319323
* [Tailscale](https://tailscale.com/) — for pioneering the vision of decentralised flat mesh networking with an amazing
320324
user experience that feels like magic.
321325
* [Talos Linux](https://github.com/siderolabs/talos)
322-
and [KubeSpan](https://www.talos.dev/v1.10/talos-guides/network/kubespan/) — for the machine API design using
323-
[grpc-proxy](https://github.com/siderolabs/grpc-proxy) and for its elegant approach to secure WireGuard-based overlay
324-
networking with zero configuration.
326+
and [KubeSpan](https://www.talos.dev/v1.10/talos-guides/network/kubespan/) — for inspiring Uncloud's API routing
327+
design with [grpc-proxy](https://github.com/siderolabs/grpc-proxy) and for their elegant approach to secure
328+
WireGuard-based overlay networking with zero configuration.
325329
* [Docker Swarm Classic](https://github.com/docker-archive/classicswarm) and
326330
[Rancher 1.x](http://rancher-com-website-main-elb-elb-1798790864.us-west-2.elb.amazonaws.com/docs/rancher/v1.6/en/)
327331
— for showing the power of simplicity and pragmatism in container orchestration and that not every problem needs the

‎caddystorage/module.go‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const (
2222
// ModuleID is the Caddy module ID for Uncloud storage.
2323
ModuleID = "caddy.storage.uncloud"
2424
// DefaultSocketPath is the default path to the Uncloud API socket.
25-
DefaultSocketPath = "/run/uncloud/uncloud.sock"
25+
DefaultSocketPath = "/run/uncloud/api/uncloud.sock"
2626
// DefaultLockTTL is the default duration of a distributed lock lease.
2727
DefaultLockTTL = 20 * time.Second
2828
// lockCleanupTimeout bounds how long an unloaded module waits for active lock operations when cleaning up.
@@ -37,7 +37,7 @@ func init() {
3737
// Storage implements a Caddy storage backend that uses an Uncloud cluster to store assets such as TLS certificates.
3838
type Storage struct {
3939
// Socket is the path to the Uncloud API socket.
40-
// Defaults to /run/uncloud/uncloud.sock when not set.
40+
// Defaults to /run/uncloud/api/uncloud.sock when not set.
4141
Socket string `json:"socket,omitempty"`
4242
// LockTTL is the duration of a distributed lock after which it expires if not renewed. Locks renew automatically
4343
// until unlocked. If an instance crashes or cannot renew, expiry allows another instance to acquire the stale lock.
@@ -154,7 +154,7 @@ func (s *Storage) CertMagicStorage() (certmagic.Storage, error) {
154154
//
155155
// {
156156
// storage uncloud {
157-
// socket /run/uncloud/uncloud.sock
157+
// socket /run/uncloud/api/uncloud.sock
158158
// lock_ttl 20s
159159
// }
160160
// }

‎cmd/uc/machine/add.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
192192
// Deploy a Caddy service container to the added machine. If caddy service is already deployed on other machines,
193193
// use the deployed image version.
194194
// NOTE: We use the cluster client to inspect and scale the Caddy service because the newly added machine may have
195-
// issues accessing the Machine API of existing machines in the cluster.
195+
// issues accessing the API of existing machines in the cluster.
196196
// See the issue for more details: https://github.com/psviderski/uncloud/issues/65.
197197
caddyImage := ""
198198
caddySvc, err := clusterClient.InspectService(ctx, client.CaddyServiceName)

‎cmd/uc/main.go‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,11 @@ func main() {
8888

8989
configPath := fs.ExpandHomeDir(opts.configPath)
9090

91+
// Make uc connect via the local Unix socket when running on a cluster machine.
9192
if opts.connect == "" {
92-
if !fs.Exists(configPath) && fs.Exists(machine.DefaultUncloudSockPath) {
93+
if !fs.Exists(configPath) && fs.Exists(machine.DefaultClusterAPISockPath) {
9394
conn = &config.MachineConnection{
94-
Unix: machine.DefaultUncloudSockPath,
95+
Unix: machine.DefaultClusterAPISockPath,
9596
}
9697
}
9798
}

‎cmd/uncloudd/dialstdio.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func newDialStdioCommand() *cobra.Command {
2323
},
2424
}
2525

26-
cmd.Flags().StringVar(&socketPath, "socket", machine.DefaultUncloudSockPath,
26+
cmd.Flags().StringVar(&socketPath, "socket", machine.DefaultClusterAPISockPath,
2727
"Path to the Uncloud API socket")
2828

2929
return cmd

‎experiment/wg/connector.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func (c *WireGuardConnector) Connect(ctx context.Context) (*grpc.ClientConn, err
6060
}
6161
endpoint := netip.AddrPortFrom(endpointAddr, DefaultEndpointPort)
6262
machineManagementIP := network.ManagementIP(machine.PublicKey)
63-
machineAPIAddr := net.JoinHostPort(machineManagementIP.String(), strconv.Itoa(constants.MachineAPIPort))
63+
machineAPIAddr := net.JoinHostPort(machineManagementIP.String(), strconv.Itoa(constants.UncloudAPIPort))
6464

6565
tunCfg := &Config{
6666
LocalAddress: c.user.ManagementIP(),

‎internal/cli/cli.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ func provisionOrConnectRemoteMachine(
603603
if remoteMachine.User != rootUser {
604604
// provisionMachine has just added the user to the uncloud group. Any SSH ControlMaster left over from
605605
// a previous uc invocation (e.g. a failed uc command against the uninitialised machine) still holds
606-
// the old user groups and would deny access to /run/uncloud/uncloud.sock. Close the current session
606+
// the old user groups and would deny access to /run/uncloud/api/uncloud.sock. Close the current session
607607
// if it exists so the next session picks up the new groups.
608608
conn.CloseControlMaster(ctx)
609609
}

‎internal/cli/config/connection_test.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ func TestMachineConnection_String(t *testing.T) {
7070
{
7171
name: "unix connection",
7272
conn: MachineConnection{
73-
Unix: "/run/uncloud/uncloud.sock",
73+
Unix: "/run/uncloud/api/uncloud.sock",
7474
},
75-
want: "unix:///run/uncloud/uncloud.sock",
75+
want: "unix:///run/uncloud/api/uncloud.sock",
7676
},
7777
{
7878
name: "no connection",

‎internal/daemon/daemon.go‎

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,91 @@ package daemon
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"log/slog"
8+
"net"
79

10+
"github.com/coreos/go-systemd/activation"
811
systemd "github.com/coreos/go-systemd/daemon"
912
"github.com/psviderski/uncloud/internal/machine"
1013
)
1114

15+
const systemdSocketUnit = "uncloud.socket"
16+
1217
type Daemon struct {
1318
machine *machine.Machine
1419
}
1520

1621
func New(dataDir string) (*Daemon, error) {
22+
listeners, err := activation.ListenersWithNames()
23+
if err != nil {
24+
return nil, fmt.Errorf("get systemd-activated sockets: %w", err)
25+
}
26+
27+
listener, err := selectActivatedListener(listeners)
28+
if err != nil {
29+
return nil, err
30+
}
31+
if listener != nil {
32+
slog.Info("Using systemd-activated API socket.", "addr", listener.Addr().String())
33+
}
34+
1735
config := &machine.Config{
18-
DataDir: dataDir,
36+
DataDir: dataDir,
37+
ClusterAPISockPath: machine.DefaultClusterAPISockPath,
38+
ClusterAPIListener: listener,
1939
}
2040
mach, err := machine.NewMachine(config)
2141
if err != nil {
22-
return nil, fmt.Errorf("init machine: %w", err)
42+
initErr := fmt.Errorf("init machine: %w", err)
43+
if listener != nil {
44+
if closeErr := listener.Close(); closeErr != nil {
45+
initErr = errors.Join(initErr, fmt.Errorf("close systemd-activated socket: %w", closeErr))
46+
}
47+
}
48+
return nil, initErr
2349
}
2450

2551
return &Daemon{
2652
machine: mach,
2753
}, nil
2854
}
2955

56+
// selectActivatedListener returns the systemd activated listener. If systemd supplied multiple listeners,
57+
// it returns the one associated with systemdSocketUnit.
58+
func selectActivatedListener(listeners map[string][]net.Listener) (net.Listener, error) {
59+
var activated []net.Listener
60+
for _, named := range listeners {
61+
activated = append(activated, named...)
62+
}
63+
64+
if len(activated) == 0 {
65+
return nil, nil
66+
}
67+
if len(activated) == 1 {
68+
return activated[0], nil
69+
}
70+
71+
named, ok := listeners[systemdSocketUnit]
72+
if !ok || len(named) != 1 {
73+
return nil, fmt.Errorf("expected one systemd-activated socket named '%s', received %d",
74+
systemdSocketUnit, len(named))
75+
}
76+
77+
// Close all other unused listeners.
78+
for name, unused := range listeners {
79+
if name == systemdSocketUnit {
80+
continue
81+
}
82+
for _, l := range unused {
83+
_ = l.Close()
84+
}
85+
}
86+
87+
return named[0], nil
88+
}
89+
3090
func (d *Daemon) Run(ctx context.Context) error {
3191
slog.Info("Starting machine.")
3292

‎internal/daemon/daemon_test.go‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package daemon
2+
3+
import (
4+
"net"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestSelectActivatedListener(t *testing.T) {
12+
t.Run("no activated listeners", func(t *testing.T) {
13+
listener, err := selectActivatedListener(nil)
14+
require.NoError(t, err)
15+
assert.Nil(t, listener)
16+
})
17+
18+
t.Run("only listener regardless of name", func(t *testing.T) {
19+
activated := testListener(t)
20+
21+
listener, err := selectActivatedListener(map[string][]net.Listener{"another.socket": {activated}})
22+
require.NoError(t, err)
23+
assert.Same(t, activated, listener)
24+
})
25+
26+
t.Run("named listener when multiple", func(t *testing.T) {
27+
activated := testListener(t)
28+
extra := testListener(t)
29+
30+
listener, err := selectActivatedListener(map[string][]net.Listener{
31+
systemdSocketUnit: {activated},
32+
"another.socket": {extra},
33+
})
34+
require.NoError(t, err)
35+
assert.Same(t, activated, listener)
36+
assert.Error(t, extra.Close())
37+
})
38+
39+
t.Run("multiple listeners without expected name", func(t *testing.T) {
40+
first := testListener(t)
41+
second := testListener(t)
42+
43+
listener, err := selectActivatedListener(map[string][]net.Listener{
44+
"first.socket": {first},
45+
"second.socket": {second},
46+
})
47+
require.ErrorContains(t, err, "expected one systemd-activated socket")
48+
assert.Nil(t, listener)
49+
})
50+
51+
t.Run("multiple listeners under expected name", func(t *testing.T) {
52+
first := testListener(t)
53+
second := testListener(t)
54+
55+
listener, err := selectActivatedListener(map[string][]net.Listener{
56+
systemdSocketUnit: {first, second},
57+
})
58+
require.ErrorContains(t, err, "expected one systemd-activated socket")
59+
assert.Nil(t, listener)
60+
})
61+
}
62+
63+
func testListener(t *testing.T) net.Listener {
64+
t.Helper()
65+
66+
listener, err := net.Listen("tcp", "127.0.0.1:0")
67+
require.NoError(t, err)
68+
t.Cleanup(func() { _ = listener.Close() })
69+
70+
return listener
71+
}

0 commit comments

Comments
 (0)