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.
- ybox/__init__.py +1 -1
- ybox/cmd.py +17 -1
- ybox/conf/completions/ybox.fish +2 -0
- ybox/conf/distros/arch/init-user.sh +2 -2
- ybox/conf/distros/arch/init.sh +1 -0
- ybox/conf/distros/arch/pkgdeps.py +2 -0
- ybox/conf/distros/deb-generic/pkgdeps.py +2 -1
- ybox/conf/profiles/apps.ini +10 -5
- ybox/conf/profiles/basic.ini +48 -23
- ybox/conf/profiles/dev.ini +4 -6
- ybox/conf/resources/entrypoint-cp.sh +1 -1
- ybox/conf/resources/entrypoint-root.sh +4 -3
- ybox/conf/resources/entrypoint-user.sh +5 -3
- ybox/conf/resources/entrypoint.sh +24 -22
- ybox/conf/resources/prime-run +0 -2
- ybox/conf/resources/run-in-dir +30 -16
- ybox/conf/resources/run-user-bash-cmd +17 -1
- ybox/conf/resources/ybox-systemd.template +24 -0
- ybox/config.py +9 -1
- ybox/env.py +18 -7
- ybox/migrate/{0.9.0-0.9.7:0.9.8.py → 0.9.0-0.9.10:0.9.11.py} +6 -5
- ybox/pkg/clean.py +1 -7
- ybox/pkg/info.py +1 -7
- ybox/pkg/inst.py +40 -22
- ybox/pkg/list.py +1 -6
- ybox/pkg/mark.py +1 -1
- ybox/pkg/repair.py +4 -0
- ybox/pkg/search.py +1 -7
- ybox/run/cmd.py +2 -1
- ybox/run/control.py +107 -25
- ybox/run/create.py +254 -63
- ybox/run/destroy.py +89 -4
- ybox/run/graphics.py +37 -17
- ybox/run/logs.py +2 -1
- ybox/run/ls.py +2 -1
- ybox/run/pkg.py +49 -7
- ybox/state.py +22 -3
- ybox/util.py +5 -5
- {ybox-0.9.8.1.dist-info → ybox-0.9.11.dist-info}/METADATA +68 -34
- ybox-0.9.11.dist-info/RECORD +77 -0
- {ybox-0.9.8.1.dist-info → ybox-0.9.11.dist-info}/WHEEL +1 -1
- ybox-0.9.8.1.dist-info/RECORD +0 -76
- {ybox-0.9.8.1.dist-info → ybox-0.9.11.dist-info}/entry_points.txt +0 -0
- {ybox-0.9.8.1.dist-info → ybox-0.9.11.dist-info/licenses}/LICENSE +0 -0
- {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 = ""
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
121
|
+
elif args.needs_state:
|
97
122
|
code = args.func(args, pkgmgr, docker_cmd, conf, runtime_conf, state)
|
98
|
-
|
99
|
-
|
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 -
|
355
|
-
subparser.add_argument("-
|
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 -
|
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
|
-
|
348
|
-
|
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(
|
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
|
271
|
-
print_error(f"TIMED OUT waiting for ready container after {
|
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.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: ybox
|
3
|
-
Version: 0.9.
|
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`.
|
148
|
-
approach works well for `ybox` which has a very minimal set of dependencies
|
149
|
-
|
150
|
-
|
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 -
|
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 -
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
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.
|
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
|
-
|
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
|
-
|
437
|
-
|
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
|
-
|
442
|
-
|
443
|
-
systemctl --user
|
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
|
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
|
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.
|
516
|
+
source pyenv/activate.zsh
|
483
517
|
source .venv/bin/activate
|
484
518
|
```
|
485
519
|
|
486
|
-
|
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,,
|