diff --git a/etc/shipcat.toml b/etc/shipcat.toml
index d54b767b449ed37933df7d7ac111e3f99ffcc7b2..6d092968265f327ecc01cb901325e843a3165d44 100644
--- a/etc/shipcat.toml
+++ b/etc/shipcat.toml
@@ -3,5 +3,8 @@
# Where to find container configuration files
container_config_dir = "/etc/shipcat/containers"
+# Where to find container roots (can be overridden by container's config)
+container_root_dir = "/aux/containers"
+
# Overall verbosity
verbosity = 0
diff --git a/shipcat/config.py b/shipcat/config.py
index b8a6bdbcfd1f849de3092fe28239b2731532244b..f756e64696e357d86f3499d2fd41bb8c55f37380 100644
--- a/shipcat/config.py
+++ b/shipcat/config.py
@@ -16,7 +16,8 @@ class ConfigError(RuntimeError):
class GlobalConfig:
- container_config_dir: Path
+ container_config_path: Path
+ container_root_path: Path
verbosity: int
@classmethod
@@ -39,16 +40,19 @@ class GlobalConfig:
def parse(self, walker: Walker) -> None:
with walker.enter_object() as w:
- self.container_config_dir = Path(w['container_config_dir'].as_str())
+ self.container_config_path = Path(w['container_config_dir'].as_str())
+ self.container_root_path = Path(w['container_root_dir'].as_str())
self.verbosity = w['verbosity'].as_int(0)
class ContainerConfig:
- container: str
- root_dir: str
+ name: str
+ root_path: Path
+ data_path: Path
image: str
allowed_users: Set[int]
allowed_groups: Set[int]
+ global_config: GlobalConfig
# Automatically generated
pid_file: str
@@ -59,7 +63,7 @@ class ContainerConfig:
if not re.fullmatch(r'[0-9A-Za-z_-]+', name):
raise ConfigError(f'Invalid container name {name}')
- path = global_config.container_config_dir / (name + '.toml')
+ path = global_config.container_config_path / (name + '.toml')
try:
with open(path, 'rb') as f:
root = tomllib.load(f)
@@ -69,7 +73,8 @@ class ContainerConfig:
raise ConfigError(f'Cannot parse container configuration {path}: {e}')
cc = ContainerConfig()
- cc.container = name
+ cc.name = name
+ cc.global_config = global_config
try:
cc.parse(Walker(root))
except WalkerError as e:
@@ -79,6 +84,13 @@ class ContainerConfig:
def parse(self, walker: Walker) -> None:
with walker.enter_object() as w:
+ rd = w['root_dir']
+ if rd.is_present():
+ self.root_path = Path(rd.as_str())
+ else:
+ self.root_path = self.global_config.container_root_path / self.name
+ self.data_path = self.root_path / 'data'
+
self.root_dir = w['root_dir'].as_str()
self.image = w['image'].as_str()
@@ -100,5 +112,5 @@ class ContainerConfig:
wu.raise_error(f'Unknown group {name}')
self.allowed_groups.add(grp.gr_gid)
- self.pid_file = f'/run/shc/{self.container}.pid'
- self.user_name = self.container
+ self.pid_file = f'/run/shc/{self.name}.pid'
+ self.user_name = self.name
diff --git a/shipcat/main.py b/shipcat/main.py
index 1b6d02327c65af463aaa99345b9dd2a534dd9306..7c1f2f52cb7c3e27ef5cc4f03ed657b6779170b3 100755
--- a/shipcat/main.py
+++ b/shipcat/main.py
@@ -145,7 +145,7 @@ def cmd_init(args: argparse.Namespace) -> None:
progress(f'Using user {cc.user_name}, uid {uid}, subuids {sur.first}+{sur.count}, subgids {sgr.first}+{sgr.count}\n')
- root_path = Path(cc.root_dir)
+ root_path = cc.root_path
progress(f'Container directory {root_path}: ')
if root_path.is_dir():
progress('already exists\n')
@@ -154,7 +154,7 @@ def cmd_init(args: argparse.Namespace) -> None:
os.chown(root_path, 0, sgr.first)
progress('created\n')
- data_path = root_path / 'data'
+ data_path = cc.data_path
progress(f'Data directory {data_path}: ')
if data_path.is_dir():
progress('already exists\n')
@@ -173,35 +173,36 @@ def cmd_init(args: argparse.Namespace) -> None:
def service_action(cc: ContainerConfig, action: str) -> None:
run_command(
- ['systemctl', action, f'shc@{cc.container}.service']
+ ['systemctl', action, f'shc@{cc.name}.service']
)
def cmd_create(args: argparse.Namespace) -> None:
- name = args.name
cc = setup_container(args, False)
- data_dir = Path(cc.root_dir) / 'data'
- ip = socket.gethostbyname(name)
-
run_command(
['podman', 'pull', cc.image]
)
service_action(cc, 'stop')
+ create_container(cc)
+
+
+def create_container(cc: ContainerConfig) -> None:
+ ip = socket.gethostbyname(cc.name)
run_command(
- ['podman', 'rm', '-if', name]
+ ['podman', 'rm', '-if', cc.name]
)
run_command(
[
'podman', 'create',
- '--name', name,
+ '--name', cc.name,
'--conmon-pidfile', cc.pid_file,
'--log-driver', 'journald',
- '--hostname', name,
- '--volume', f'{data_dir}:/data',
+ '--hostname', cc.name,
+ '--volume', f'{cc.data_path}:/data',
'--network', 'static',
'--ip', ip,
'--subuidname', cc.user_name,
@@ -240,7 +241,7 @@ def cmd_shell(args: argparse.Namespace) -> None:
cc = setup_container(args, False)
run_command(
- ['podman', 'exec', '-it', cc.container, '/bin/bash']
+ ['podman', 'exec', '-it', cc.name, '/bin/bash']
)
@@ -252,7 +253,7 @@ def cmd_exec(args: argparse.Namespace) -> None:
cmd.append('--tty')
if args.user is not None:
cmd.extend(['--user', args.user])
- cmd.append(cc.container)
+ cmd.append(cc.name)
cmd.extend(args.arg)
run_command(cmd)
@@ -282,33 +283,36 @@ def cmd_rsync(args: argparse.Namespace) -> None:
cmd = ['podman', 'exec', '-i']
if user is not None:
cmd.extend(['--user', user])
- cmd.append(cc.container)
+ cmd.append(cc.name)
cmd.extend(rsync)
run_command(cmd)
def cmd_list(args: argparse.Namespace) -> None:
- for conf in Path(config.container_config_dir).iterdir():
+ for conf in config.container_config_path.iterdir():
if conf.suffix == '.toml':
cc = ContainerConfig.load(config, conf.stem)
if args.all or check_rights(cc, args.as_user, args.as_groups):
- print(cc.container)
+ print(cc.name)
def main_service_start():
# Service helper for service start, called by root
if len(sys.argv) != 2:
- die("Expected arguments: container")
+ die("Expected arguments: name")
config = load_config()
cc = ContainerConfig.load(config, sys.argv[1])
- pid_file = f'/run/shc/{cc.container}.pid'
+ # Just to be sure
+ run_command(['podman', 'stop', '--ignore', cc.name])
+
+ pid_file = f'/run/shc/{cc.name}.pid'
Path(pid_file).unlink(missing_ok=True)
- run_command(['podman', 'start', cc.container])
+ run_command(['podman', 'start', cc.name])
def main_service_stop():
@@ -320,7 +324,7 @@ def main_service_stop():
config = load_config()
cc = ContainerConfig.load(config, sys.argv[1])
- run_command(['podman', 'stop', '--ignore', cc.container])
+ run_command(['podman', 'stop', '--ignore', cc.name])
def parse_int_list(s: str) -> List[int]: