mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
run corrosion in Docker as uncloud user, bind mount data dir
This commit is contained in:
+21
-29
@@ -4,44 +4,36 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MkDataDir(dir, owner string) error {
|
// LookupUIDGID returns the user and group IDs for the given username.
|
||||||
parent, _ := filepath.Split(dir)
|
func LookupUIDGID(username string) (uid, gid int, err error) {
|
||||||
// Use 0711 for parent directories to allow `owner` to access its nested data directory.
|
usr, err := user.Lookup(username)
|
||||||
if err := os.MkdirAll(parent, 0711); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create directory %q: %w", parent, err)
|
err = fmt.Errorf("lookup user %q: %w", username, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if err := os.Mkdir(dir, 0700); err != nil {
|
uid, err = strconv.Atoi(usr.Uid)
|
||||||
if !os.IsExist(err) {
|
if err != nil {
|
||||||
return fmt.Errorf("create directory %q: %w", dir, err)
|
err = fmt.Errorf("parse %q user ID (UID) %q: %w", username, usr.Uid, err)
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
if err := Chown(dir, owner); err != nil {
|
gid, err = strconv.Atoi(usr.Gid)
|
||||||
return err
|
if err != nil {
|
||||||
|
err = fmt.Errorf("parse %q user group ID (GID) %q: %w", username, usr.Gid, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Chown(path, owner string) error {
|
func Chown(path, owner string) error {
|
||||||
if owner != "" {
|
uid, gid, err := LookupUIDGID(owner)
|
||||||
usr, err := user.Lookup(owner)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return fmt.Errorf("lookup user %q: %w", owner, err)
|
}
|
||||||
}
|
if err = os.Chown(path, uid, gid); err != nil {
|
||||||
uid, err := strconv.Atoi(usr.Uid)
|
return fmt.Errorf("chown %q: %w", path, err)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse %q user ID (UID) %q: %w", owner, usr.Uid, err)
|
|
||||||
}
|
|
||||||
gid, err := strconv.Atoi(usr.Gid)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse %q user group ID (GID) %q: %w", owner, usr.Gid, err)
|
|
||||||
}
|
|
||||||
if err = os.Chown(path, uid, gid); err != nil {
|
|
||||||
return fmt.Errorf("chown %q: %w", path, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"uncloud/internal/fs"
|
"uncloud/internal/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,3 +58,23 @@ func (c *Config) Write(path, owner string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MkDataDir(dir, owner string) error {
|
||||||
|
parent, _ := filepath.Split(dir)
|
||||||
|
// Use 0711 for parent directories to allow `owner` to access its nested data directory.
|
||||||
|
if err := os.MkdirAll(parent, 0711); err != nil {
|
||||||
|
return fmt.Errorf("create directory %q: %w", parent, err)
|
||||||
|
}
|
||||||
|
if err := os.Mkdir(dir, 0700); err != nil {
|
||||||
|
if !os.IsExist(err) {
|
||||||
|
return fmt.Errorf("create directory %q: %w", dir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if owner != "" {
|
||||||
|
if err := fs.Chown(dir, owner); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
|
"github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"io"
|
"io"
|
||||||
@@ -22,7 +23,7 @@ type DockerService struct {
|
|||||||
Image string
|
Image string
|
||||||
Name string
|
Name string
|
||||||
DataDir string
|
DataDir string
|
||||||
// TODO: uid/guid
|
User string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDockerService(cli *client.Client, image, name, dataDir string) *DockerService {
|
func NewDockerService(cli *client.Client, image, name, dataDir string) *DockerService {
|
||||||
@@ -71,6 +72,7 @@ func (s *DockerService) containerConfig() *container.Config {
|
|||||||
return &container.Config{
|
return &container.Config{
|
||||||
Image: s.Image,
|
Image: s.Image,
|
||||||
Cmd: []string{"corrosion", "agent", "-c", filepath.Join(s.DataDir, "config.toml")},
|
Cmd: []string{"corrosion", "agent", "-c", filepath.Join(s.DataDir, "config.toml")},
|
||||||
|
User: s.User,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +82,14 @@ func (s *DockerService) hostConfig() *container.HostConfig {
|
|||||||
RestartPolicy: container.RestartPolicy{
|
RestartPolicy: container.RestartPolicy{
|
||||||
Name: container.RestartPolicyAlways,
|
Name: container.RestartPolicyAlways,
|
||||||
},
|
},
|
||||||
|
Mounts: []mount.Mount{
|
||||||
|
// Bind mount the data directory at the same path inside the container to simplify path handling.
|
||||||
|
{
|
||||||
|
Type: mount.TypeBind,
|
||||||
|
Source: s.DataDir,
|
||||||
|
Target: s.DataDir,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-18
@@ -32,9 +32,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultMachineSockPath = "/run/uncloud/machine.sock"
|
DefaultMachineSockPath = "/run/uncloud/machine.sock"
|
||||||
DefaultUncloudSockPath = "/run/uncloud/uncloud.sock"
|
DefaultUncloudSockPath = "/run/uncloud/uncloud.sock"
|
||||||
DefaultUncloudSockGroup = "uncloud"
|
DefaultSockGroup = "uncloud"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -48,6 +48,8 @@ type Config struct {
|
|||||||
CorrosionAPIAddr netip.AddrPort
|
CorrosionAPIAddr netip.AddrPort
|
||||||
CorrosionAdminSockPath string
|
CorrosionAdminSockPath string
|
||||||
CorrosionService corroservice.Service
|
CorrosionService corroservice.Service
|
||||||
|
// CorrosionUser sets the Linux user for running the corrosion service.
|
||||||
|
CorrosionUser string
|
||||||
|
|
||||||
// DockerClient manages system and user containers using the local Docker daemon.
|
// DockerClient manages system and user containers using the local Docker daemon.
|
||||||
DockerClient *client.Client
|
DockerClient *client.Client
|
||||||
@@ -90,15 +92,24 @@ func (c *Config) SetDefaults() (*Config, error) {
|
|||||||
if cfg.CorrosionAdminSockPath == "" {
|
if cfg.CorrosionAdminSockPath == "" {
|
||||||
cfg.CorrosionAdminSockPath = filepath.Join(cfg.CorrosionDir, "admin.sock")
|
cfg.CorrosionAdminSockPath = filepath.Join(cfg.CorrosionDir, "admin.sock")
|
||||||
}
|
}
|
||||||
|
if cfg.CorrosionUser == "" {
|
||||||
|
cfg.CorrosionUser = corroservice.DefaultUser
|
||||||
|
}
|
||||||
if cfg.CorrosionService == nil {
|
if cfg.CorrosionService == nil {
|
||||||
if isRunningInDocker() {
|
if isRunningInDocker() {
|
||||||
// Run corrosion in a nested Docker container if the machine is running in a container.
|
// Run corrosion in a nested Docker container if the machine is running in a container.
|
||||||
cfg.CorrosionService = corroservice.NewDockerService(
|
uid, gid, err := fs.LookupUIDGID(cfg.CorrosionUser)
|
||||||
cfg.DockerClient,
|
if err != nil {
|
||||||
corroservice.LatestImage,
|
return nil, fmt.Errorf("lookup corrosion user %q: %w", cfg.CorrosionUser, err)
|
||||||
"uncloud-corrosion",
|
}
|
||||||
cfg.CorrosionDir,
|
|
||||||
)
|
cfg.CorrosionService = &corroservice.DockerService{
|
||||||
|
Client: cfg.DockerClient,
|
||||||
|
Image: corroservice.LatestImage,
|
||||||
|
Name: "uncloud-corrosion",
|
||||||
|
DataDir: cfg.CorrosionDir,
|
||||||
|
User: fmt.Sprintf("%d:%d", uid, gid),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
cfg.CorrosionService = corroservice.DefaultSystemdService(cfg.CorrosionDir)
|
cfg.CorrosionService = corroservice.DefaultSystemdService(cfg.CorrosionDir)
|
||||||
}
|
}
|
||||||
@@ -400,21 +411,21 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
// access mode and uncloud group if the group is found, otherwise it falls back to the root group.
|
// access mode and uncloud group if the group is found, otherwise it falls back to the root group.
|
||||||
func listenUnixSocket(path string) (net.Listener, error) {
|
func listenUnixSocket(path string) (net.Listener, error) {
|
||||||
gid := 0 // Fall back to the root group if the uncloud group is not found.
|
gid := 0 // Fall back to the root group if the uncloud group is not found.
|
||||||
group, err := user.LookupGroup(DefaultUncloudSockGroup)
|
group, err := user.LookupGroup(DefaultSockGroup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//goland:noinspection GoTypeAssertionOnErrors
|
//goland:noinspection GoTypeAssertionOnErrors
|
||||||
if _, ok := err.(user.UnknownGroupError); ok {
|
if _, ok := err.(user.UnknownGroupError); ok {
|
||||||
slog.Info(
|
slog.Info(
|
||||||
"Specified group not found, using root group for the API socket.",
|
"Specified group not found, using root group for the API socket.",
|
||||||
"group", DefaultUncloudSockGroup, "path", path,
|
"group", DefaultSockGroup, "path", path,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("lookup %q group ID (GID): %w", DefaultUncloudSockGroup, err)
|
return nil, fmt.Errorf("lookup %q group ID (GID): %w", DefaultSockGroup, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gid, err = strconv.Atoi(group.Gid)
|
gid, err = strconv.Atoi(group.Gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parse %q group ID (GID) %q: %w", DefaultUncloudSockGroup, group.Gid, err)
|
return nil, fmt.Errorf("parse %q group ID (GID) %q: %w", DefaultSockGroup, group.Gid, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,7 +442,7 @@ func listenUnixSocket(path string) (net.Listener, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Machine) configureCorrosion() error {
|
func (m *Machine) configureCorrosion() error {
|
||||||
if err := fs.MkDataDir(m.config.CorrosionDir, corroservice.DefaultUser); err != nil {
|
if err := corroservice.MkDataDir(m.config.CorrosionDir, m.config.CorrosionUser); err != nil {
|
||||||
return fmt.Errorf("create corrosion data directory: %w", err)
|
return fmt.Errorf("create corrosion data directory: %w", err)
|
||||||
}
|
}
|
||||||
configPath := filepath.Join(m.config.CorrosionDir, "config.toml")
|
configPath := filepath.Join(m.config.CorrosionDir, "config.toml")
|
||||||
@@ -469,16 +480,14 @@ func (m *Machine) configureCorrosion() error {
|
|||||||
Path: filepath.Join(m.config.CorrosionDir, "admin.sock"),
|
Path: filepath.Join(m.config.CorrosionDir, "admin.sock"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := cfg.Write(configPath, corroservice.DefaultUser); err != nil {
|
// TODO: change file permissions to 0640 root:uncloud to emphasize the owner is the machine, not corrosion.
|
||||||
|
if err := cfg.Write(configPath, m.config.CorrosionUser); err != nil {
|
||||||
return fmt.Errorf("write corrosion config: %w", err)
|
return fmt.Errorf("write corrosion config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(schemaPath, []byte(store.Schema), 0644); err != nil {
|
if err := os.WriteFile(schemaPath, []byte(store.Schema), 0644); err != nil {
|
||||||
return fmt.Errorf("write corrosion schema: %w", err)
|
return fmt.Errorf("write corrosion schema: %w", err)
|
||||||
}
|
}
|
||||||
if err := fs.Chown(schemaPath, corroservice.DefaultUser); err != nil {
|
|
||||||
return fmt.Errorf("chown corrosion schema: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user