ybox 0.9.8.1__py3-none-any.whl → 0.9.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. ybox/__init__.py +1 -1
  2. ybox/cmd.py +17 -1
  3. ybox/conf/completions/ybox.fish +2 -0
  4. ybox/conf/distros/arch/init-user.sh +2 -2
  5. ybox/conf/distros/arch/init.sh +1 -0
  6. ybox/conf/distros/arch/pkgdeps.py +2 -0
  7. ybox/conf/distros/deb-generic/pkgdeps.py +2 -1
  8. ybox/conf/profiles/apps.ini +10 -5
  9. ybox/conf/profiles/basic.ini +48 -23
  10. ybox/conf/profiles/dev.ini +4 -6
  11. ybox/conf/resources/entrypoint-cp.sh +1 -1
  12. ybox/conf/resources/entrypoint-root.sh +4 -3
  13. ybox/conf/resources/entrypoint-user.sh +5 -3
  14. ybox/conf/resources/entrypoint.sh +24 -22
  15. ybox/conf/resources/prime-run +0 -2
  16. ybox/conf/resources/run-in-dir +30 -16
  17. ybox/conf/resources/run-user-bash-cmd +17 -1
  18. ybox/conf/resources/ybox-systemd.template +24 -0
  19. ybox/config.py +9 -1
  20. ybox/env.py +18 -7
  21. ybox/migrate/{0.9.0-0.9.7:0.9.8.py → 0.9.0-0.9.10:0.9.11.py} +6 -5
  22. ybox/pkg/clean.py +1 -7
  23. ybox/pkg/info.py +1 -7
  24. ybox/pkg/inst.py +40 -22
  25. ybox/pkg/list.py +1 -6
  26. ybox/pkg/mark.py +1 -1
  27. ybox/pkg/repair.py +4 -0
  28. ybox/pkg/search.py +1 -7
  29. ybox/run/cmd.py +2 -1
  30. ybox/run/control.py +107 -25
  31. ybox/run/create.py +254 -63
  32. ybox/run/destroy.py +89 -4
  33. ybox/run/graphics.py +37 -17
  34. ybox/run/logs.py +2 -1
  35. ybox/run/ls.py +2 -1
  36. ybox/run/pkg.py +49 -7
  37. ybox/state.py +22 -3
  38. ybox/util.py +5 -5
  39. {ybox-0.9.8.1.dist-info → ybox-0.9.11.dist-info}/METADATA +68 -34
  40. ybox-0.9.11.dist-info/RECORD +77 -0
  41. {ybox-0.9.8.1.dist-info → ybox-0.9.11.dist-info}/WHEEL +1 -1
  42. ybox-0.9.8.1.dist-info/RECORD +0 -76
  43. {ybox-0.9.8.1.dist-info → ybox-0.9.11.dist-info}/entry_points.txt +0 -0
  44. {ybox-0.9.8.1.dist-info → ybox-0.9.11.dist-info/licenses}/LICENSE +0 -0
  45. {ybox-0.9.8.1.dist-info → ybox-0.9.11.dist-info}/top_level.txt +0 -0
ybox/run/graphics.py CHANGED
@@ -20,7 +20,8 @@ _STD_LIB_DIR_PATTERNS = ["&/usr/lib/*-linux-gnu", "&/lib/*-linux-gnu", "&/usr/li
20
20
  "&/lib64/*-linux-gnu", "&/usr/lib32/*-linux-gnu", "&/lib32/*-linux-gnu"]
21
21
  _STD_LD_LIB_PATH_VARS = ["LD_LIBRARY_PATH", "LD_LIBRARY_PATH_64", "LD_LIBRARY_PATH_32"]
22
22
  _NVIDIA_LIB_PATTERNS = ["*nvidia*.so*", "*NVIDIA*.so*", "libcuda*.so*", "libnvcuvid*.so*",
23
- "libnvoptix*.so*", "gbm/*nvidia*.so*", "vdpau/*nvidia*.so*"]
23
+ "libnvoptix*.so*", "gbm/*nvidia*.so*", "vdpau/*nvidia*.so*",
24
+ "libXNVCtrl.so*"]
24
25
  _NVIDIA_BIN_PATTERNS = ["nvidia-smi", "nvidia-cuda*", "nvidia-debug*", "nvidia-bug*"]
25
26
  # note that the code below assumes that file name pattern below is always of the form *nvidia*
26
27
  # (while others are directories), so if that changes then update _process_nvidia_data_files
@@ -45,7 +46,8 @@ def add_env_option(docker_args: list[str], env_var: str, env_val: Optional[str]
45
46
  docker_args.append(f"-e={env_var}={env_val}")
46
47
 
47
48
 
48
- def add_mount_option(docker_args: list[str], src: str, dest: str, flags: str = "") -> None:
49
+ def add_mount_option(docker_args: list[str], src: str, dest: str, flags: str = "",
50
+ check_exists: bool = False) -> None:
49
51
  """
50
52
  Add option to the list of podman/docker arguments to bind mount a source directory to
51
53
  given destination directory.
@@ -54,11 +56,40 @@ def add_mount_option(docker_args: list[str], src: str, dest: str, flags: str = "
54
56
  :param src: the source directory in the host system
55
57
  :param dest: the destination directory in the container
56
58
  :param flags: any additional flags to be passed to `-v` podman/docker argument, defaults to ""
59
+ :param check_exists: check if the bind mount was already added (and skip if so)
57
60
  """
58
- if flags:
59
- docker_args.append(f"-v={src}:{dest}:{flags}")
61
+ mount_arg = f"-v={src}:{dest}:{flags}" if flags else f"-v={src}:{dest}"
62
+ if not check_exists or mount_arg not in docker_args:
63
+ docker_args.append(mount_arg)
64
+
65
+
66
+ def handle_variable_mount(docker_args: list[str], env: Environ, mount_path: str) -> str:
67
+ """
68
+ Handle the case where a mount point may change in different starts or even within the same
69
+ started container instance. In these cases the "base" directory of the mount point is
70
+ mounted instead which should normally be `/tmp` or `$XDG_RUNTIME_DIR`. The variable values
71
+ are assumed to lie between these two, or the parent directory of the mount point if it does
72
+ not lie within these two base directories. The actual passing of the required environment
73
+ variable (that can change) is handled by the `run-in-dir` script that will adjust the variable
74
+ value to reflect that mount point added by this method.
75
+
76
+ :param docker_args: list of podman/docker arguments to which the options have to be appended
77
+ :param env: an instance of the current :class:`Environ`
78
+ :param mount_path: the variable path which is usually the value of an environment variable
79
+ :return: the result mount point inside the container for the `mount_path`
80
+ """
81
+ base_dir = os.path.dirname(mount_path)
82
+ # check if parent_dir is in $XDG_RUNTIME_DIR or /tmp
83
+ if not env.xdg_rt_dir:
84
+ base_dirs = {base_dir, "/tmp"}
85
+ elif mount_path.startswith(env.xdg_rt_dir + "/") or mount_path.startswith("/tmp/"):
86
+ base_dirs = (env.xdg_rt_dir, "/tmp")
87
+ base_dir = "/tmp" if base_dir.startswith("/tmp") else env.xdg_rt_dir
60
88
  else:
61
- docker_args.append(f"-v={src}:{dest}")
89
+ base_dirs = (base_dir, env.xdg_rt_dir, "/tmp")
90
+ for b_dir in base_dirs:
91
+ add_mount_option(docker_args, b_dir, f"{b_dir}-host", "ro", check_exists=True)
92
+ return mount_path.replace(base_dir, f"{base_dir}-host")
62
93
 
63
94
 
64
95
  def enable_x11(docker_args: list[str], env: Environ) -> None:
@@ -82,18 +113,7 @@ def enable_x11(docker_args: list[str], env: Environ) -> None:
82
113
  # parent can cause trouble if one changes the display manager, for example, which
83
114
  # uses an entirely different mount point (e.g. gdm uses /run/user/... while sddm
84
115
  # uses /tmp)
85
- parent_dir = os.path.dirname(xauth)
86
- # check if parent_dir is in $XDG_RUNTIME_DIR or /tmp
87
- if not env.xdg_rt_dir:
88
- parent_dirs = {parent_dir, "/tmp"}
89
- elif xauth.startswith(f"{env.xdg_rt_dir}/") or xauth.startswith("/tmp/"):
90
- parent_dirs = (env.xdg_rt_dir, "/tmp")
91
- parent_dir = "/tmp" if parent_dir.startswith("/tmp") else env.xdg_rt_dir
92
- else:
93
- parent_dirs = (parent_dir, env.xdg_rt_dir, "/tmp")
94
- for p_dir in parent_dirs:
95
- add_mount_option(docker_args, p_dir, f"{p_dir}-host", "ro")
96
- target_xauth = xauth.replace(parent_dir, f"{parent_dir}-host")
116
+ target_xauth = handle_variable_mount(docker_args, env, xauth)
97
117
  add_env_option(docker_args, "XAUTHORITY", target_xauth)
98
118
  add_env_option(docker_args, "XAUTHORITY_ORIG", target_xauth)
99
119
 
ybox/run/logs.py CHANGED
@@ -6,7 +6,7 @@ ybox container.
6
6
  import argparse
7
7
  import sys
8
8
 
9
- from ybox.cmd import check_ybox_exists, run_command
9
+ from ybox.cmd import check_ybox_exists, parser_version_check, run_command
10
10
  from ybox.env import get_docker_command
11
11
  from ybox.print import print_info
12
12
 
@@ -54,4 +54,5 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
54
54
  parser.add_argument("-f", "--follow", action="store_true",
55
55
  help="follow log output like 'tail -f'")
56
56
  parser.add_argument("container_name", type=str, help="name of the running ybox")
57
+ parser_version_check(parser, argv)
57
58
  return parser.parse_args(argv)
ybox/run/ls.py CHANGED
@@ -5,7 +5,7 @@ Code for the `ybox-ls` script that is used to show the active or stopped ybox co
5
5
  import argparse
6
6
  import sys
7
7
 
8
- from ybox.cmd import YboxLabel, run_command
8
+ from ybox.cmd import YboxLabel, parser_version_check, run_command
9
9
  from ybox.env import get_docker_command
10
10
 
11
11
 
@@ -61,4 +61,5 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
61
61
  "https://docs.docker.com/reference/cli/docker/container/ls)")
62
62
  parser.add_argument("-l", "--long-format", action="store_true",
63
63
  help="display extended information without truncating fields")
64
+ parser_version_check(parser, argv)
64
65
  return parser.parse_args(argv)
ybox/run/pkg.py CHANGED
@@ -7,7 +7,8 @@ import argparse
7
7
  import sys
8
8
  from typing import cast
9
9
 
10
- from ybox.cmd import YboxLabel, check_active_ybox, run_command
10
+ from ybox.cmd import (YboxLabel, check_active_ybox, parser_version_check,
11
+ run_command)
11
12
  from ybox.config import Consts, StaticConfiguration
12
13
  from ybox.env import Environ
13
14
  from ybox.pkg.clean import clean_cache
@@ -61,6 +62,29 @@ def main_argv(argv: list[str]) -> None:
61
62
  elif not containers:
62
63
  print_error("No active ybox container found!")
63
64
  sys.exit(1)
65
+ elif args.group_by_shared_root:
66
+ with YboxStateManagement(env) as state:
67
+ entries = state.get_containers_grouped_by_shared_root(containers)
68
+ if not entries:
69
+ print_error(f"No containers in state database for: {', '.join(containers)}")
70
+ sys.exit(1)
71
+ if len(entries) == 1:
72
+ # select the first container in the list for the shared_root
73
+ container_name = entries[0][0][0]
74
+ elif args.quiet:
75
+ msg = "\n ".join([", ".join(t[0]) + (" on " + t[1] if t[1] else " (not shared)")
76
+ for t in entries])
77
+ print_error(
78
+ f"Expected one activate container or shared root but found:\n {msg}")
79
+ sys.exit(1)
80
+ else:
81
+ # display as container names grouped by distribution and shared_root
82
+ selection_list = [" / ".join(t[0]) + " : distro '" + t[2] +
83
+ ("' on " + t[1] if t[1] else "' (not shared)") for t in entries]
84
+ print_info("Please select the container to use:", file=sys.stderr)
85
+ if (selection := select_item_from_menu(selection_list)) is None:
86
+ sys.exit(1)
87
+ container_name = selection[:selection.index(" ")]
64
88
  elif args.quiet:
65
89
  print_error(
66
90
  f"Expected one active ybox container but found: {', '.join(containers)}")
@@ -73,6 +97,7 @@ def main_argv(argv: list[str]) -> None:
73
97
  if not args.quiet:
74
98
  print_info(f"Running the operation on '{container_name}'", file=sys.stderr)
75
99
 
100
+ code = 0
76
101
  with YboxStateManagement(env) as state:
77
102
  # ensure that all state database changes are done as a single transaction and only applied
78
103
  # if there were no failures (commit/rollback are automatic at the end of `with`)
@@ -93,10 +118,14 @@ def main_argv(argv: list[str]) -> None:
93
118
  if args.is_repo_cmd:
94
119
  code = args.func(args, pkgmgr, distro_config["repo"], docker_cmd, conf,
95
120
  runtime_conf, state)
96
- else:
121
+ elif args.needs_state:
97
122
  code = args.func(args, pkgmgr, docker_cmd, conf, runtime_conf, state)
98
- if code != 0:
99
- sys.exit(code) # state will be automatically rolled back for exceptions
123
+
124
+ # when "state" is not needed then run the command outside the with block to release the db lock
125
+ if not args.is_repo_cmd and not args.needs_state:
126
+ code = args.func(args, pkgmgr, docker_cmd, conf)
127
+ if code != 0:
128
+ sys.exit(code) # state will be automatically rolled back for exceptions
100
129
 
101
130
 
102
131
  def parse_args(argv: list[str]) -> argparse.Namespace:
@@ -129,6 +158,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
129
158
  "mark a package as a dependency or an explicitly installed package"))
130
159
  add_repair(add_subparser(operations, "repair",
131
160
  "try to repair state after a failed operation or an interrupt/kill"))
161
+ parser_version_check(parser, argv)
132
162
  return parser.parse_args(argv)
133
163
 
134
164
 
@@ -146,6 +176,10 @@ def add_subparser(operations, name: str, hlp: str) -> argparse.ArgumentParser:
146
176
  add_common_args(subparser)
147
177
  # by default set the flag for repository command as false
148
178
  subparser.set_defaults(is_repo_cmd=False)
179
+ # set the flag to require state database by default
180
+ subparser.set_defaults(needs_state=True)
181
+ # don't group by shared_root by default
182
+ subparser.set_defaults(group_by_shared_root=False)
149
183
  return subparser
150
184
 
151
185
 
@@ -245,6 +279,7 @@ def add_update(subparser: argparse.ArgumentParser) -> None:
245
279
  help="the packages to update if provided, else update the entire "
246
280
  "installation of the container (which will end up updating all "
247
281
  "other containers sharing the same root if configured)")
282
+ subparser.set_defaults(group_by_shared_root=True)
248
283
  subparser.set_defaults(func=update_packages)
249
284
 
250
285
 
@@ -291,6 +326,7 @@ def add_list_files(subparser: argparse.ArgumentParser) -> None:
291
326
  """
292
327
  add_pager_arg(subparser)
293
328
  subparser.add_argument("package", type=str, help="list files of this package")
329
+ subparser.set_defaults(needs_state=False)
294
330
  subparser.set_defaults(func=list_files)
295
331
 
296
332
 
@@ -310,6 +346,8 @@ def add_search(subparser: argparse.ArgumentParser) -> None:
310
346
  "(e.g. skip AUR repository on Arch Linux)")
311
347
  add_pager_arg(subparser)
312
348
  subparser.add_argument("search", nargs="+", help="one or more search terms")
349
+ subparser.set_defaults(needs_state=False)
350
+ subparser.set_defaults(group_by_shared_root=True)
313
351
  subparser.set_defaults(func=search_packages)
314
352
 
315
353
 
@@ -326,6 +364,7 @@ def add_info(subparser: argparse.ArgumentParser) -> None:
326
364
  "otherwise search only among the installed packages")
327
365
  add_pager_arg(subparser)
328
366
  subparser.add_argument("packages", nargs="+", help="one or more packages")
367
+ subparser.set_defaults(needs_state=False)
329
368
  subparser.set_defaults(func=info_packages)
330
369
 
331
370
 
@@ -337,6 +376,8 @@ def add_clean(subparser: argparse.ArgumentParser) -> None:
337
376
 
338
377
  :param subparser: the :class:`argparse.ArgumentParser` object for the sub-command
339
378
  """
379
+ subparser.set_defaults(needs_state=False)
380
+ subparser.set_defaults(group_by_shared_root=True)
340
381
  subparser.set_defaults(func=clean_cache)
341
382
 
342
383
 
@@ -351,11 +392,11 @@ def add_mark(subparser: argparse.ArgumentParser) -> None:
351
392
  subparser.add_argument("-e", "--explicit", action="store_true",
352
393
  help="mark the package as explicitly installed; the package will "
353
394
  "henceforth be managed by `ybox-pkg` if not already; "
354
- "exactly one of -e or -D option must be specified")
355
- subparser.add_argument("-D", "--dependency-of", type=str,
395
+ "exactly one of -e or -d option must be specified")
396
+ subparser.add_argument("-d", "--dependency-of", type=str,
356
397
  help="mark the package as a dependency of given package; both the "
357
398
  "packages will henceforth be managed by `ybox-pkg` if not "
358
- "already; exactly one of -e or -D option must be specified")
399
+ "already; exactly one of -e or -d option must be specified")
359
400
  subparser.add_argument("package", type=str, help="the package to be marked")
360
401
  subparser.set_defaults(func=mark_package)
361
402
 
@@ -372,6 +413,7 @@ def add_repair(subparser: argparse.ArgumentParser) -> None:
372
413
  help="repair thoroughly by reinstalling all packages; CAUTION: use "
373
414
  "this only if the normal repair fails and the system cannot be "
374
415
  "recovered otherwise")
416
+ subparser.set_defaults(group_by_shared_root=True)
375
417
  subparser.set_defaults(func=repair_package_state)
376
418
 
377
419
 
ybox/state.py CHANGED
@@ -344,9 +344,8 @@ class YboxStateManagement:
344
344
  if self._version == old_version:
345
345
  return
346
346
  # run appropriate SQL migration scripts for product version change
347
- if not (scripts := self._filter_and_sort_files_by_version(
348
- files("ybox").joinpath("migrate").iterdir(), old_version, self._version, ".py")):
349
- return
347
+ scripts = self._filter_and_sort_files_by_version(
348
+ files("ybox").joinpath("migrate").iterdir(), old_version, self._version, ".py")
350
349
  for script in scripts:
351
350
  print_color(f"Running migration script '{script}' for container version upgrade from "
352
351
  f"{old_version} to {self._version}")
@@ -559,6 +558,26 @@ class YboxStateManagement:
559
558
  pass
560
559
  return shared_containers
561
560
 
561
+ def get_containers_grouped_by_shared_root(self, containers: list[str]) -> list[
562
+ tuple[list[str], str, str]]:
563
+ """
564
+ Get the containers grouped by their `shared_root`s, if present, else as separate entries.
565
+
566
+ :param containers: list of containers to include, or empty to include all containers
567
+ :return: list of tuple of (container list, shared_root, distribution) matching given
568
+ containers (or all containers if empty list provided)
569
+ """
570
+ in_list = "name IN (" + ("?, " * (len(containers) - 1)) + "?) AND " if containers else ""
571
+ # using default "," to separate container names since a container name cannot have it
572
+ # (see the regex check in ybox.run.create.process_args)
573
+ with closing(cursor := self._conn.execute(
574
+ f"""SELECT STRING_AGG(name, ','), shared_root, MIN(distribution) FROM containers
575
+ WHERE {in_list}NOT destroyed
576
+ GROUP BY CASE WHEN length(shared_root) = 0 THEN name ELSE shared_root END""",
577
+ containers)):
578
+ rows = cursor.fetchall()
579
+ return [(str(row[0]).split(","), str(row[1]), str(row[2])) for row in rows]
580
+
562
581
  def register_package(self, container_name: str, package: str, local_copies: list[str],
563
582
  copy_type: CopyType, app_flags: dict[str, str], shared_root: str,
564
583
  dep_type: Optional[DependencyType], dep_of: str,
ybox/util.py CHANGED
@@ -220,7 +220,7 @@ def get_ybox_version(conf: StaticConfiguration) -> str:
220
220
  return ""
221
221
 
222
222
 
223
- def wait_for_ybox_container(docker_cmd: str, conf: StaticConfiguration) -> None:
223
+ def wait_for_ybox_container(docker_cmd: str, conf: StaticConfiguration, timeout: int) -> None:
224
224
  """
225
225
  Wait for container created with `create.start_container` to finish all its initialization.
226
226
  This depends on the specific entrypoint script used by `create.start_container` to write
@@ -229,10 +229,10 @@ def wait_for_ybox_container(docker_cmd: str, conf: StaticConfiguration) -> None:
229
229
 
230
230
  :param docker_cmd: the podman/docker executable to use
231
231
  :param conf: the :class:`StaticConfiguration` for the container
232
+ :param timeout: seconds to wait for container to start before exiting with failure code 1
232
233
  """
233
234
  sys.stdout.flush()
234
235
  box_name = conf.box_name
235
- max_wait_secs = 600
236
236
  status_line = "" # keeps the last valid line read from status file
237
237
  with open(conf.status_file, "r", encoding="utf-8") as status_fd:
238
238
 
@@ -251,7 +251,7 @@ def wait_for_ybox_container(docker_cmd: str, conf: StaticConfiguration) -> None:
251
251
  print(line, end="") # line already includes the terminating newline
252
252
  return False
253
253
 
254
- for _ in range(max_wait_secs):
254
+ for _ in range(timeout):
255
255
  # check the container status first which may be running or stopping
256
256
  # in which case sleep and retry (if stopped, then read_lines should succeed)
257
257
  if get_ybox_state(docker_cmd, box_name, expected_states=("running", "stopping")):
@@ -267,8 +267,8 @@ def wait_for_ybox_container(docker_cmd: str, conf: StaticConfiguration) -> None:
267
267
  # using simple poll per second rather than inotify or similar because the
268
268
  # initialization can take a good amount of time and second granularity is enough
269
269
  time.sleep(1)
270
- # reading did not end after max_wait_secs
271
- print_error(f"TIMED OUT waiting for ready container after {max_wait_secs}secs (last status: "
270
+ # reading did not end after timeout
271
+ print_error(f"TIMED OUT waiting for ready container after {timeout}secs (last status: "
272
272
  f"{status_line}).\nCheck 'ybox-logs -f {box_name}' for more details.")
273
273
  sys.exit(1)
274
274
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: ybox
3
- Version: 0.9.8.1
3
+ Version: 0.9.11
4
4
  Summary: Securely run Linux distribution inside a container
5
5
  Author-email: Sumedh Wale <sumwale@yahoo.com>, Vishal Rao <vishalrao@gmail.com>
6
6
  License: Copyright (c) 2024-2025 Sumedh Wale and contributors
@@ -25,7 +25,7 @@ License: Copyright (c) 2024-2025 Sumedh Wale and contributors
25
25
 
26
26
  Project-URL: Homepage, https://github.com/sumwale/ybox
27
27
  Project-URL: Issues, https://github.com/sumwale/ybox/issues
28
- Keywords: Linux in container,toolbox
28
+ Keywords: Linux in container,toolbox,distrobox
29
29
  Classifier: Development Status :: 4 - Beta
30
30
  Classifier: Intended Audience :: End Users/Desktop
31
31
  Classifier: License :: OSI Approved :: MIT License
@@ -35,12 +35,14 @@ Classifier: Programming Language :: Python :: 3.9
35
35
  Classifier: Programming Language :: Python :: 3.10
36
36
  Classifier: Programming Language :: Python :: 3.11
37
37
  Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Programming Language :: Python :: 3.13
38
39
  Requires-Python: >=3.9
39
40
  Description-Content-Type: text/markdown
40
41
  License-File: LICENSE
41
42
  Requires-Dist: packaging
42
43
  Requires-Dist: simple-term-menu
43
44
  Requires-Dist: tabulate>=0.9.0
45
+ Dynamic: license-file
44
46
 
45
47
  ## Introduction
46
48
 
@@ -54,7 +56,17 @@ of the container including directories to be shared, logging etc.
54
56
 
55
57
  Special emphasis is given on security where users can choose to lock down
56
58
  or open up the container as required with reasonable defaults out of the
57
- box. There is no sharing of HOME or no privileged mode container.
59
+ box. There is no sharing of HOME or no privileged mode container. This sets
60
+ it apart from other similar solutions like distrobox/toolbx and the reason
61
+ for starting this project since those other solutions don't care about
62
+ security/sandboxing at all and share the entire HOME while running the
63
+ containers in privileged mode. The other problem with those solutions is that
64
+ the shared HOME means that the user's configuration dot files also get shared
65
+ and can cause all kinds of trouble where container apps can overwrite
66
+ with their own versions (especially for updated apps in the containers)
67
+ breaking the app in the host system. It is, however, possible to share the
68
+ entire HOME if user really wants but that needs to be explcitly configured
69
+ in the ini profile.
58
70
 
59
71
  Expected usage is for users to group similar applications in a container
60
72
  and separate out containers depending on different needs like higher/lower
@@ -88,7 +100,7 @@ So, for example, if you want to run the latest and greatest Intellij IDEA commun
88
100
  to do is:
89
101
 
90
102
  ```sh
91
- # create an Arch Linux based container
103
+ # create an Arch Linux based container and generate systemd service file (if possible)
92
104
  ybox-create arch
93
105
  # then select an appropriate built-in profile e.g. "dev.ini" from the menu
94
106
 
@@ -144,10 +156,11 @@ require you to install in a custom virtual environment which can be done manuall
144
156
  fish: `python3 -m venv ybox-venv && source ybox-env/bin/activate.fish`)
145
157
  or automatically using `pipx`. Alternatively you can add `--break-system-packages`
146
158
  flag to the `pip` command above or add it globally for all future packages using
147
- `python3 -m pip config set global.break-system-packages true`. The alternative
148
- approach works well for `ybox` which has a very minimal set of dependencies but
149
- in the rare case you see any issues due to package conflicts, use `pipx` or
150
- manual virtual environment.
159
+ `python3 -m pip config set global.break-system-packages true`. This alternative
160
+ approach works well for `ybox` which has a very minimal set of dependencies which will
161
+ not conflict with system packages (rather work with whatever system version is installed),
162
+ but if you prefer keeping the installation separate then use `pipx` or
163
+ a manual virtual environment.
151
164
 
152
165
  Now you can run the `ybox-create` and other utilities that are normally installed
153
166
  in your `~/.local/bin` directory which should be in PATH for modern Linux distributions.
@@ -185,6 +198,8 @@ to point to the full path of the podman or docker executable.
185
198
  ybox-create
186
199
  ```
187
200
 
201
+ By default this will also generate a user systemd service if possible (add `-S` or
202
+ `--skip-systemd-service` option to skip creation of a user systemd service).
188
203
  This will allow choosing from supported distributions, then from the available profiles.
189
204
  You can start with the Arch Linux distribution and `apps.ini` profile to try it out. The container
190
205
  will have a name like `ybox-<distribution>_<profile>` by default like `ybox-arch_apps` for the
@@ -209,7 +224,9 @@ does not run properly as root, then you cannot run it when using docker unless y
209
224
  `sudo/su` to the host user in the container command. However, running as host user when running
210
225
  rootless docker will map to a different user ID in the host (as specified in `/etc/subuid` on the
211
226
  host) so files shared with the host, including devices like those in `/dev/dri`, will cause
212
- permission issues that can hinder or break the application.
227
+ permission issues that can hinder or break the application. Hence it is recommended to
228
+ just install podman (even if you already have docker installed) which works out of the
229
+ box in rootless mode in all tested distributions.
213
230
 
214
231
 
215
232
  ### Package management: install/uninstall/list/search/...
@@ -255,7 +272,7 @@ ybox-pkg list -o
255
272
  ```
256
273
  To show more details of the packages (combine with -a/-o as required):
257
274
  ```sh
258
- ybox-pkg list -o
275
+ ybox-pkg list -v
259
276
  ```
260
277
 
261
278
  List all the files installed by the package:
@@ -295,6 +312,8 @@ Clean package cache, temporary downloads etc:
295
312
  ```sh
296
313
  ybox-pkg clean
297
314
  ```
315
+ Add `-q` option to answer yes for any questions automatically if all your containers use
316
+ the same shared root.
298
317
 
299
318
  Mark a package as explicitly installed (also registers with `ybox-pkg` if not present):
300
319
  ```sh
@@ -303,7 +322,7 @@ ybox-pkg mark firefox -e
303
322
 
304
323
  Mark a package as a dependency of another (also registers with `ybox-pkg` if not present):
305
324
  ```sh
306
- ybox-pkg mark qt5ct -D zoom # mark qt5ct as an optional dependency of zoom
325
+ ybox-pkg mark qt5ct -d zoom # mark qt5ct as an optional dependency of zoom
307
326
  ```
308
327
 
309
328
  Repair package installation after a failure or interrupt:
@@ -342,10 +361,10 @@ ybox-destroy ybox-arch_apps
342
361
  ```
343
362
 
344
363
  Will destroy the `apps` container created in the example before. This does not delete the
345
- $HOME files, nor does it delete the shared root directory (if enabled). Hence, if you create
364
+ `$HOME` files, nor does it delete the shared root directory (if enabled). Hence, if you create
346
365
  a new container having the same shared root, then it will inherit everything installed
347
366
  previously. Likewise, if you create the container with the same profile again, then it
348
- will also have the $HOME as before if you do not explicitly delete the directories
367
+ will also have the `$HOME` as before if you do not explicitly delete the directories
349
368
  in `~/.local/share/ybox`.
350
369
 
351
370
 
@@ -374,10 +393,10 @@ ybox-cmd ybox-arch_apps -- ls -l
374
393
  ```
375
394
 
376
395
  The default profiles also link the .bashrc and starship configuration files from your host
377
- $HOME directory by default, so you should see the same bash shell configuration as in your
396
+ `$HOME` directory by default, so you should see the same bash shell configuration as in your
378
397
  host. These are linked in read-only mode, so if you want to change these auto-linked
379
398
  configuration files inside the container, then you will need to create a copy from the symlink
380
- first (but then it will lose the link from the host $HOME).
399
+ first (but then it will lose the link from the host `$HOME`).
381
400
 
382
401
  A shell on a container will act like a native Linux distribution environment for most purposes.
383
402
  The one prominent missing thing is systemd which is not enabled deliberately since it requires
@@ -423,33 +442,39 @@ for a ybox container. See the full set of options with `ybox-control -h/--help`.
423
442
  ### Auto-starting containers
424
443
 
425
444
  Containers can be auto-started as per the usual way for rootless podman/docker services.
426
- This is triggered by systemd on user login which is exactly what we want for ybox
445
+ This is triggered by systemd on user login which is exactly what is required for ybox
427
446
  containers so that the container applications are available on login and are stopped on
428
- session logout. For docker the following should suffice:
429
-
430
- ```sh
431
- systemctl --user enable docker
432
- ```
447
+ session logout. All the tested Linux distributions support this and provide for user
448
+ systemd daemon on user login.
433
449
 
434
- See [docker docs](https://docs.docker.com/engine/security/rootless/#daemon) for details.
450
+ The `ybox-create` command autogenerates the systemd service file (in absence of `-S` or
451
+ `--skip-systemd-service` option) which is also removed by `ybox-destroy` automatically.
452
+ The name of the generated service is `ybox-<NAME>` where `<NAME>` is the name of the
453
+ container if `<NAME>` does not start with `ybox-` prefix, else it is just `<NAME>`.
435
454
 
436
- For podman you will need to explicitly generate systemd service file for each container and
437
- copy to your systemd configuration directory since podman does not use a background daemon.
438
- For the `ybox-arch_apps` container in the examples before:
455
+ With a user service installed, the `systemctl` commands can be used to control the
456
+ ybox container (`<SERVICE_NAME>` is `ybox-<NAME>/<NAME>` mentioned above):
439
457
 
440
458
  ```sh
441
- mkdir -p ~/.config/systemd/user/
442
- podman generate systemd --name ybox-arch_apps > ~/.config/systemd/user/container-ybox-arch_apps.service
443
- systemctl --user enable container-ybox-arch_apps.service
459
+ systemctl --user status <SERVICE_NAME> # show status of the service
460
+ systemctl --user stop <SERVICE_NAME> # stop the service
461
+ systemctl --user start <SERVICE_NAME> # start the service
444
462
  ```
445
463
 
464
+ If your Linux distribution does not use systemd, then the autostart has to be handled
465
+ manually as per the distribution's preferred way. For instance an appropriate desktop
466
+ file can be added to `~/.config/autostart` directory to start a ybox container on
467
+ graphical login, though performing a clean stop can be hard with this approach.
468
+ Note that the preferred way to start/stop a ybox container is using the `ybox-control`
469
+ command rather than directly using podman/docker.
470
+
446
471
 
447
472
  ## Development
448
473
 
449
474
  Virtual environment setup have been provided for consistent development, test and build
450
475
  with multiple python versions. The minimum python version required is 3.9 and tests are
451
- run against all major python versions higher than that (i.e. 3.10, 3.11, 3.12 and others
452
- in future).
476
+ run against all major python versions higher than that (i.e. 3.10, 3.11, 3.12, 3.13 and
477
+ others in future).
453
478
 
454
479
  The setup uses pyenv with venv which can be used for development with IDEA/PyCharm/VSCode
455
480
  or in terminal, running tests against all supported python versions using `tox` etc.
@@ -476,14 +501,23 @@ Next you can install the required python versions and venv environment:
476
501
  pyenv/setup-venv.sh
477
502
  ```
478
503
 
479
- Finally, you can activate it in bash/zsh:
504
+ Finally, you can activate it.
505
+
506
+ bash:
507
+
508
+ ```sh
509
+ source pyenv/activate.bash
510
+ source .venv/bin/activate
511
+ ```
512
+
513
+ zsh:
480
514
 
481
515
  ```sh
482
- source pyenv/activate.sh
516
+ source pyenv/activate.zsh
483
517
  source .venv/bin/activate
484
518
  ```
485
519
 
486
- Or in fish shell:
520
+ fish:
487
521
 
488
522
  ```
489
523
  source pyenv/activate.fish
@@ -0,0 +1,77 @@
1
+ ybox/__init__.py,sha256=DJW4aoiXY2arxFg1eBQPfjHm19Ur_tXdLu332z66Esw,97
2
+ ybox/cmd.py,sha256=RaNZ7LBqUNwpqQkitR29WLoItjkMfZmaFEeryLTR_tM,14962
3
+ ybox/config.py,sha256=inmuUhlAZb6EKLGYWdymsqoHggtiLd58kc125l25ACA,9943
4
+ ybox/env.py,sha256=6o0tJ163KWhVZHnhDRWw_16nMMYoy4-2yAHSJYBSL-0,9420
5
+ ybox/filelock.py,sha256=nWBp3jvbtrNziRzNcWm6FVVA_lhMccLwgLCVT2IDK5c,3185
6
+ ybox/print.py,sha256=hAQjTb6JmtjWh0sF4GdZHcKRph5iMKP5x23s8LE85q4,4343
7
+ ybox/state.py,sha256=QSAfa4LGy7oDZVe6muBXFIylKB7CMalv3OgEQ3VtmAo,50174
8
+ ybox/util.py,sha256=D4OgNCH0LXyHR9x0dCXCgkLFGvfgy--mqUbDbZRPobg,16520
9
+ ybox/conf/completions/ybox.fish,sha256=zTfCb0XvTzzZUbYuIxV1BNSSpUZ1qbkbA5L4SfReLhc,6035
10
+ ybox/conf/distros/supported.list,sha256=KAN7lbNfbRqF20KqEd0-BzF8U5uPx4n9h0W9zVh9z5o,52
11
+ ybox/conf/distros/arch/add-gpg-key.sh,sha256=kLMCT15hYadjhInYwQiiyF4u6rJl8n4gGfzEKuMKvTg,644
12
+ ybox/conf/distros/arch/distro.ini,sha256=NlCnhU4mmO9nE08BcO7hEQ83osMbHgIhLYybGG5rCNc,11026
13
+ ybox/conf/distros/arch/init-base.sh,sha256=eqdVzrMqP0hP52S_xxYZJ8n1lStlPn-eSP2e4DpHvWc,311
14
+ ybox/conf/distros/arch/init-user.sh,sha256=tb_NX4xNRU2pSuuVf2jSb7019uLfxBDbKI7ZyOtzSA4,1127
15
+ ybox/conf/distros/arch/init.sh,sha256=7f96YVjgQnOWItaxDjllgOl4LD9ibqzuhSk2QbDutvU,3578
16
+ ybox/conf/distros/arch/list_fmt_long.py,sha256=Ws9Mt9CKInxqUSn98P6-Csik2QqEjyBHp5NyUHlbcoU,2825
17
+ ybox/conf/distros/arch/pkgdeps.py,sha256=Y77N1xHmHZRyZ9_diFVvoqBd44lZXIIJ2ipX7YVlrT8,14081
18
+ ybox/conf/distros/deb-generic/check-package.sh,sha256=bMiOi6Cp_UrtXm_sXOWBq4Xng2EwXYerqBuRLtGYIlo,2621
19
+ ybox/conf/distros/deb-generic/distro.ini,sha256=ZDWJG-cvx1BuI2RwynIvDwxzuzvVaYNv68PD-J57t84,11361
20
+ ybox/conf/distros/deb-generic/fetch-gpg-key-id.sh,sha256=sTsLWILZosYJcGV1u8qBdczQAFBda2Zlsp1xbpy8nzo,836
21
+ ybox/conf/distros/deb-generic/init-base.sh,sha256=rim7UckGos0phyOAiNYbIjOVG9C4gKQSB-YbZcTFIms,191
22
+ ybox/conf/distros/deb-generic/init-user.sh,sha256=RDt9sZuv6cSX1WBaRGcLMq2ZTBhVZmZVWD1t0wjvizU,20
23
+ ybox/conf/distros/deb-generic/init.sh,sha256=KW0a76BJm75VHDaLPCE4MdLThcy__NpE6Z8pLB0Q7IM,5748
24
+ ybox/conf/distros/deb-generic/list_fmt_long.py,sha256=66sOC0Uzv5nuTRaQQ_9R-GFJju4PSPXNlXv_eHmx57k,5701
25
+ ybox/conf/distros/deb-generic/pkgdeps.py,sha256=CKIJbftIjicGoGoYqScqb9zPZuKqT3r-gL9CRyraNbk,10571
26
+ ybox/conf/distros/deb-oldstable/distro.ini,sha256=lr7140ftndEvs3Liavf3hg3v0qHHWAn0RRm_HAQTw8M,1103
27
+ ybox/conf/distros/deb-stable/distro.ini,sha256=xyQ31mLOpj3Z1MK-UYuc_9NfEkb7Gy10MdDKXMgnqDo,1100
28
+ ybox/conf/distros/ubuntu2204/distro.ini,sha256=3Pw1Q30USyKMUcHp_cvlqhwyU0FPo8O2AHWkgd0cdE4,1105
29
+ ybox/conf/distros/ubuntu2404/distro.ini,sha256=ra4_EteDsHrw9UcehP4qLGTn98gAHc4JCb56CDzz1Wo,1102
30
+ ybox/conf/profiles/apps.ini,sha256=vxVVy9oUw31to8CaagNZj9MQpGHIK2xARN4nzHy4vZ0,1270
31
+ ybox/conf/profiles/basic.ini,sha256=FGSDOooI4meXhQlwFpPo_RxZ62it3bvdjqWkpVVDcA4,19713
32
+ ybox/conf/profiles/dev.ini,sha256=Qicas_96ZoG3nsm1MSE1urarlBzIj9kBGmUqrk_cD3g,976
33
+ ybox/conf/profiles/games.ini,sha256=FiBILNFOMpjKhrIR9nPbPj9QDUeI5h9wZaNXIb6Z7dc,1792
34
+ ybox/conf/resources/entrypoint-base.sh,sha256=hcW8ZLHM-jlHx7McoKTo8HIFz_VBBPD0I3WqlX7LjHs,4890
35
+ ybox/conf/resources/entrypoint-common.sh,sha256=fMopKBLeGuVV0ukANXh_ZHGTR1yGq108CTN_M2MNPyQ,461
36
+ ybox/conf/resources/entrypoint-cp.sh,sha256=jemWFCqfme9blVtfVxqBzCsCp7b-bN2aFLajruF0GGQ,814
37
+ ybox/conf/resources/entrypoint-root.sh,sha256=58xcDX58ZtEFSr7ZSUFmzKt1OyGU29OrxjOmDiR6V8Q,706
38
+ ybox/conf/resources/entrypoint-user.sh,sha256=Tps_UyQGyGn30LbgKm3gpzZtjOHnJpnlKx-px2GAd7A,698
39
+ ybox/conf/resources/entrypoint.sh,sha256=jbfFfUprU7ycp0moZLXNdnxk_bhtDxAk6byEiPMNz-s,8560
40
+ ybox/conf/resources/prime-run,sha256=en8wEspc2Hzod9rq8KKnPQrjIPTLjUk44TqmtX-g0sw,339
41
+ ybox/conf/resources/run-in-dir,sha256=-Xnwp4n6TK_2yPeBhGX6agenztyOtZ0316-vUlfOIag,2299
42
+ ybox/conf/resources/run-user-bash-cmd,sha256=LMlPoHtzYNDcOI885ouBha9xGRnQ6AWCFVsSu2dxy10,1065
43
+ ybox/conf/resources/ybox-systemd.template,sha256=bA-bJOmc07Be-mQYtoFzl0yq4SJACv-FkAoKscPTh3g,779
44
+ ybox/migrate/0.9.0-0.9.10:0.9.11.py,sha256=WHvIWvUhXnruzSYwWYnv2zPNOlkXfAyaaJ_nZwG2wwE,1627
45
+ ybox/pkg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
+ ybox/pkg/clean.py,sha256=UlsrJLfZOyKg-7BE8s9xPvAyuUXSwvU5x-E7MMjGJSE,1219
47
+ ybox/pkg/info.py,sha256=HoEsiAi-iPEnAOWBMy9SsA4TOfyqHonRURU8k5EeAWA,1607
48
+ ybox/pkg/inst.py,sha256=wxFJo1GNrUGyjIqtEtOc_44Ty7zdZKj-qHcs9RdZudQ,36912
49
+ ybox/pkg/list.py,sha256=Sk5THAmF132HKEZxJVDlqLLR8Z2w1wRA_E-gBsxlesQ,10097
50
+ ybox/pkg/mark.py,sha256=Hb1FaowdBx8x3GFcakXsq4ERNqI60Gjljr24qXsDGR8,3748
51
+ ybox/pkg/repair.py,sha256=rCwXXJbilJYU73feIIVWGFdbkh6SDcquxgiEnYV6pNs,8080
52
+ ybox/pkg/repo.py,sha256=25gQgGMPeHgX83iqqWXyG6V6rIZ4i_KEnIbNG-CrxTk,13677
53
+ ybox/pkg/search.py,sha256=GKfonxCLHtH-kojZpRJlRfe0qf768wUehVv20nM4lKY,2526
54
+ ybox/pkg/uninst.py,sha256=ndqZ_WYr9HE5jVL1tQ_tTmSPyTFM6OJRCTLLmG-Qm4w,5338
55
+ ybox/pkg/update.py,sha256=M-1MC8oh-baTHdSPWUUSTU_AhN3eu2nTCSge3bb6GmI,3269
56
+ ybox/run/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
+ ybox/run/cmd.py,sha256=a77RMC14Vovx2NV3LgdAzXVZXStC1TucgjcHPz4x03A,2105
58
+ ybox/run/control.py,sha256=Lvew2tqtWy5xcZMPXuh5sTrM2K92MykXfdMfBuKsIDc,7540
59
+ ybox/run/create.py,sha256=N2ULSoBsMWamBcm_JMLeZQdvMPhilhzFeM1aMkPDw7s,73062
60
+ ybox/run/destroy.py,sha256=i3N2zRCgrQM_lGA6W28O6Y-D1NIwBP64PA1e6vhOkL4,6315
61
+ ybox/run/graphics.py,sha256=mKQFU0la83Rv2V5l9TS25KIbqYmMnZjGQgGghLlfQp8,20309
62
+ ybox/run/logs.py,sha256=pIdMWgNBNl-MgixArbMryUuBNNbi5JvDFP62IZ7jwr8,2050
63
+ ybox/run/ls.py,sha256=7ylyxOOYEsVWK8baM0GaZcUlVQBwpdGiF7EhU09xf2s,2787
64
+ ybox/run/pkg.py,sha256=6pes_wpVmPDiFYGfr8568GpyMByzor4dK5DSWaYmdsE,27219
65
+ ybox/schema/0.9.1-added.sql,sha256=1rGp2DczZmmC_xwjmheeZNPSbDpFzasu6LO3tpTy3zI,1049
66
+ ybox/schema/0.9.6-added.sql,sha256=Qcho6dP5OUpPUW3IBWl_kv88agMPHzueUAKqnZPnt3U,809
67
+ ybox/schema/init.sql,sha256=fei8lPvjb-EIjm5zuA_XkEdjsIE3vtROhgRPt7QMlSs,1599
68
+ ybox/schema/migrate/0.9.0:0.9.1.sql,sha256=e9JGwrjFZXdWKGv2JQZlKcWz8DmOuUARpToSsCyiksQ,1555
69
+ ybox/schema/migrate/0.9.1:0.9.2.sql,sha256=X5J3unDS0eLeVvYKxQgx-iUBoAOk9T2suO34pWlQ-lE,362
70
+ ybox/schema/migrate/0.9.2:0.9.3.sql,sha256=Y7GeBSuEEs7Hs9hh-KYDARgeeMgwQwercvTB5P_-k6I,102
71
+ ybox/schema/migrate/0.9.5:0.9.6.sql,sha256=wqYhmputlUQzBI5zfP7O5kqIFWAbZQ05kolyHK4714A,70
72
+ ybox-0.9.11.dist-info/licenses/LICENSE,sha256=7GbFgERMXSwD1VyLA5bo_XHvJipmNEUhDW5Sy51TFeY,1077
73
+ ybox-0.9.11.dist-info/METADATA,sha256=64TAyPzQLQVg4KBazcBTshFP8OPYS5D1npAgIvu9QDQ,25772
74
+ ybox-0.9.11.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
75
+ ybox-0.9.11.dist-info/entry_points.txt,sha256=xDlI_84Hl3ytYO_ERyt0rkJ4ioUF8Z1r49Hx1xL28Rk,243
76
+ ybox-0.9.11.dist-info/top_level.txt,sha256=DYX7jvndHcBaJXLJ8vDyKrq0_KWoSeXXFq8r0d5L6Nk,5
77
+ ybox-0.9.11.dist-info/RECORD,,