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]: