ybox 0.9.8.1__py3-none-any.whl → 0.9.10__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/run/destroy.py CHANGED
@@ -3,11 +3,15 @@ Code for the `ybox-destroy` script that is used to destroy an active or stopped
3
3
  """
4
4
 
5
5
  import argparse
6
+ import os
7
+ import shutil
6
8
  import sys
7
9
 
8
- from ybox.cmd import check_ybox_exists, run_command
10
+ from ybox.cmd import check_ybox_exists, parser_version_check, run_command
11
+ from ybox.config import Consts
9
12
  from ybox.env import Environ
10
- from ybox.print import fgcolor, print_color, print_error, print_warn
13
+ from ybox.print import (fgcolor, print_color, print_error, print_notice,
14
+ print_warn)
11
15
  from ybox.state import YboxStateManagement
12
16
 
13
17
 
@@ -31,9 +35,21 @@ def main_argv(argv: list[str]) -> None:
31
35
 
32
36
  check_ybox_exists(docker_cmd, container_name, exit_on_error=True)
33
37
  print_color(f"Stopping ybox container '{container_name}'", fg=fgcolor.cyan)
38
+ # check if there is a systemd service for the container
39
+ systemd_dir = f"{env.home}/.config/systemd/user"
40
+ ybox_svc = f"ybox-{container_name}.service"
41
+ ybox_svc_path = ""
42
+ if (systemctl := shutil.which("systemctl", path=os.pathsep.join(Consts.sys_bin_dirs()))) and \
43
+ not os.access(ybox_svc_path := f"{systemd_dir}/{ybox_svc}", os.R_OK):
44
+ ybox_svc_path = ""
45
+
34
46
  # continue even if this fails since the container may already be in stopped state
35
- run_command([docker_cmd, "container", "stop", container_name],
36
- exit_on_error=False, error_msg=f"stopping '{container_name}'")
47
+ if systemctl and ybox_svc_path:
48
+ run_command([systemctl, "--user", "stop", ybox_svc],
49
+ exit_on_error=False, error_msg=f"stopping '{container_name}'")
50
+ else:
51
+ run_command([docker_cmd, "container", "stop", container_name],
52
+ exit_on_error=False, error_msg=f"stopping '{container_name}'")
37
53
 
38
54
  print_warn(f"Removing ybox container '{container_name}'")
39
55
  rm_args = [docker_cmd, "container", "rm"]
@@ -42,12 +58,28 @@ def main_argv(argv: list[str]) -> None:
42
58
  rm_args.append(container_name)
43
59
  run_command(rm_args, error_msg=f"removing '{container_name}'")
44
60
 
61
+ # remove systemd service file and reload daemon
62
+ if systemctl and ybox_svc_path:
63
+ print_color(f"Removing systemd service '{ybox_svc}' and reloading daemon", fg=fgcolor.cyan)
64
+ run_command([systemctl, "--user", "disable", ybox_svc], exit_on_error=False)
65
+ os.unlink(ybox_svc_path)
66
+ try:
67
+ os.unlink(f"{systemd_dir}/.ybox-{container_name}.env")
68
+ except OSError:
69
+ pass
70
+ run_command([systemctl, "--user", "daemon-reload"], exit_on_error=False)
71
+
72
+ # check and remove any dangling container references in state database
73
+ valid_containers = set(get_all_containers(docker_cmd))
74
+
45
75
  # remove the state from the database
46
76
  print_warn(f"Clearing ybox state for '{container_name}'")
47
77
  with YboxStateManagement(env) as state:
78
+ state.begin_transaction()
48
79
  if not state.unregister_container(container_name):
49
80
  print_error(f"No entry found for '{container_name}' in the state database")
50
81
  sys.exit(1)
82
+ remove_orphans_from_db(valid_containers, state)
51
83
 
52
84
 
53
85
  def parse_args(argv: list[str]) -> argparse.Namespace:
@@ -61,4 +93,35 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
61
93
  parser.add_argument("-f", "--force", action="store_true",
62
94
  help="force destroy the container using SIGKILL if required")
63
95
  parser.add_argument("container_name", type=str, help="name of the active ybox")
96
+ parser_version_check(parser, argv)
64
97
  return parser.parse_args(argv)
98
+
99
+
100
+ def get_all_containers(docker_cmd: str) -> list[str]:
101
+ """
102
+ Get all the valid containers as known to the container manager.
103
+
104
+ :param docker_cmd: the podman/docker executable to use
105
+ :return: list of valid container names
106
+ """
107
+ result = run_command([docker_cmd, "container", "ls", "--all", "--format={{ .Names }}"],
108
+ capture_output=True, exit_on_error=False, error_msg="listing containers")
109
+ return [] if isinstance(result, int) else result.splitlines()
110
+
111
+
112
+ def remove_orphans_from_db(valid_containers: set[str], state: YboxStateManagement) -> None:
113
+ """
114
+ Unregister orphan container entries from the state database. This takes the output of
115
+ :func:`get_all_containers` as argument and should be invoked inside `YboxStateManagement`
116
+ context manager (i.e. with state database as locked), while the call to `get_all_containers`
117
+ can be outside the lock.
118
+
119
+ :param valid_containers: set of valid container names from :func:`get_all_containers`
120
+ :param state: instance of `YboxStateManagement` having the state of all ybox containers
121
+ """
122
+ if not os.environ.get("YBOX_TESTING"):
123
+ orphans = set(state.get_containers()) - valid_containers
124
+ if orphans:
125
+ print_notice(f"Removing orphan container entries from database: {', '.join(orphans)}")
126
+ for orphan in orphans:
127
+ state.unregister_container(orphan)
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
 
@@ -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
1
  Metadata-Version: 2.2
2
2
  Name: ybox
3
- Version: 0.9.8.1
3
+ Version: 0.9.10
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
@@ -35,6 +35,7 @@ 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
@@ -54,7 +55,17 @@ of the container including directories to be shared, logging etc.
54
55
 
55
56
  Special emphasis is given on security where users can choose to lock down
56
57
  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.
58
+ box. There is no sharing of HOME or no privileged mode container. This sets
59
+ it apart from other similar solutions like distrobox/toolbx and the reason
60
+ for starting this project since those other solutions don't care about
61
+ security/sandboxing at all and share the entire HOME while running the
62
+ containers in privileged mode. The other problem with those solutions is that
63
+ the shared HOME means that the user's configuration dot files also get shared
64
+ and can cause all kinds of trouble where container apps can overwrite
65
+ with their own versions (especially for updated apps in the containers)
66
+ breaking the app in the host system. It is, however, possible to share the
67
+ entire HOME if user really wants but that needs to be explcitly configured
68
+ in the ini profile.
58
69
 
59
70
  Expected usage is for users to group similar applications in a container
60
71
  and separate out containers depending on different needs like higher/lower
@@ -144,10 +155,11 @@ require you to install in a custom virtual environment which can be done manuall
144
155
  fish: `python3 -m venv ybox-venv && source ybox-env/bin/activate.fish`)
145
156
  or automatically using `pipx`. Alternatively you can add `--break-system-packages`
146
157
  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.
158
+ `python3 -m pip config set global.break-system-packages true`. This alternative
159
+ approach works well for `ybox` which has a very minimal set of dependencies which will
160
+ not conflict with system packages (rather work with whatever system version is installed),
161
+ but if you prefer keeping the installation separate then use `pipx` or
162
+ a manual virtual environment.
151
163
 
152
164
  Now you can run the `ybox-create` and other utilities that are normally installed
153
165
  in your `~/.local/bin` directory which should be in PATH for modern Linux distributions.
@@ -209,7 +221,9 @@ does not run properly as root, then you cannot run it when using docker unless y
209
221
  `sudo/su` to the host user in the container command. However, running as host user when running
210
222
  rootless docker will map to a different user ID in the host (as specified in `/etc/subuid` on the
211
223
  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.
224
+ permission issues that can hinder or break the application. Hence it is recommended to
225
+ just install podman (even if you already have docker installed) which works out of the
226
+ box in rootless mode in all tested distributions.
213
227
 
214
228
 
215
229
  ### Package management: install/uninstall/list/search/...
@@ -342,10 +356,10 @@ ybox-destroy ybox-arch_apps
342
356
  ```
343
357
 
344
358
  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
359
+ `$HOME` files, nor does it delete the shared root directory (if enabled). Hence, if you create
346
360
  a new container having the same shared root, then it will inherit everything installed
347
361
  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
362
+ will also have the `$HOME` as before if you do not explicitly delete the directories
349
363
  in `~/.local/share/ybox`.
350
364
 
351
365
 
@@ -374,10 +388,10 @@ ybox-cmd ybox-arch_apps -- ls -l
374
388
  ```
375
389
 
376
390
  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
391
+ `$HOME` directory by default, so you should see the same bash shell configuration as in your
378
392
  host. These are linked in read-only mode, so if you want to change these auto-linked
379
393
  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).
394
+ first (but then it will lose the link from the host `$HOME`).
381
395
 
382
396
  A shell on a container will act like a native Linux distribution environment for most purposes.
383
397
  The one prominent missing thing is systemd which is not enabled deliberately since it requires
@@ -448,8 +462,8 @@ systemctl --user enable container-ybox-arch_apps.service
448
462
 
449
463
  Virtual environment setup have been provided for consistent development, test and build
450
464
  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).
465
+ run against all major python versions higher than that (i.e. 3.10, 3.11, 3.12, 3.13 and
466
+ others in future).
453
467
 
454
468
  The setup uses pyenv with venv which can be used for development with IDEA/PyCharm/VSCode
455
469
  or in terminal, running tests against all supported python versions using `tox` etc.
@@ -476,14 +490,23 @@ Next you can install the required python versions and venv environment:
476
490
  pyenv/setup-venv.sh
477
491
  ```
478
492
 
479
- Finally, you can activate it in bash/zsh:
493
+ Finally, you can activate it.
494
+
495
+ bash:
496
+
497
+ ```sh
498
+ source pyenv/activate.bash
499
+ source .venv/bin/activate
500
+ ```
501
+
502
+ zsh:
480
503
 
481
504
  ```sh
482
- source pyenv/activate.sh
505
+ source pyenv/activate.zsh
483
506
  source .venv/bin/activate
484
507
  ```
485
508
 
486
- Or in fish shell:
509
+ fish:
487
510
 
488
511
  ```
489
512
  source pyenv/activate.fish
@@ -1,20 +1,20 @@
1
- ybox/__init__.py,sha256=kl9lbkmRtTLHzBTo46cujYUSW33NWkFAXy2YJ9KXVzQ,98
2
- ybox/cmd.py,sha256=49a1Oo1YgLbXlHqZ1PL1okKGtxlXya1DClM-uh1TuKI,14213
3
- ybox/config.py,sha256=bFz6MIVCEbwOjG0m6L5JPngQqta5J9aXU4osKtD2QDE,9632
1
+ ybox/__init__.py,sha256=jQDT4EtS19JjBbrvOVG5G0Vzpcw9ZsATD9wt6wNRr1s,97
2
+ ybox/cmd.py,sha256=RaNZ7LBqUNwpqQkitR29WLoItjkMfZmaFEeryLTR_tM,14962
3
+ ybox/config.py,sha256=inmuUhlAZb6EKLGYWdymsqoHggtiLd58kc125l25ACA,9943
4
4
  ybox/env.py,sha256=vfHuvTOpApR4fLx1vePWRrTYxzo50c-7dFcnm1-vDHo,8738
5
5
  ybox/filelock.py,sha256=nWBp3jvbtrNziRzNcWm6FVVA_lhMccLwgLCVT2IDK5c,3185
6
6
  ybox/print.py,sha256=hAQjTb6JmtjWh0sF4GdZHcKRph5iMKP5x23s8LE85q4,4343
7
- ybox/state.py,sha256=ZEJr3Aib2UT1N7JFfQAWWESx5Vd0GtL3srY9T3Iq1mQ,49008
8
- ybox/util.py,sha256=2kuUsRCzSuoTypRTeyJXZMWh-XZK_P7M_wsGa3LcIng,16454
9
- ybox/conf/completions/ybox.fish,sha256=n62vAH60G_kGytPmdNPSvHaiu9k87Yg33maoje_pRl8,5851
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
10
  ybox/conf/distros/supported.list,sha256=KAN7lbNfbRqF20KqEd0-BzF8U5uPx4n9h0W9zVh9z5o,52
11
11
  ybox/conf/distros/arch/add-gpg-key.sh,sha256=kLMCT15hYadjhInYwQiiyF4u6rJl8n4gGfzEKuMKvTg,644
12
12
  ybox/conf/distros/arch/distro.ini,sha256=NlCnhU4mmO9nE08BcO7hEQ83osMbHgIhLYybGG5rCNc,11026
13
13
  ybox/conf/distros/arch/init-base.sh,sha256=eqdVzrMqP0hP52S_xxYZJ8n1lStlPn-eSP2e4DpHvWc,311
14
- ybox/conf/distros/arch/init-user.sh,sha256=4PqUP6QsE50SeymCeNv70PcabrTpEvt8UO_0t4pDksw,1106
15
- ybox/conf/distros/arch/init.sh,sha256=jGyDLo1sKDTjUWWW9XnUDejHKNR2w7dnsYfdzHg5E9Y,3560
14
+ ybox/conf/distros/arch/init-user.sh,sha256=tb_NX4xNRU2pSuuVf2jSb7019uLfxBDbKI7ZyOtzSA4,1127
15
+ ybox/conf/distros/arch/init.sh,sha256=7f96YVjgQnOWItaxDjllgOl4LD9ibqzuhSk2QbDutvU,3578
16
16
  ybox/conf/distros/arch/list_fmt_long.py,sha256=Ws9Mt9CKInxqUSn98P6-Csik2QqEjyBHp5NyUHlbcoU,2825
17
- ybox/conf/distros/arch/pkgdeps.py,sha256=26s92G99CR-JqVBl8UANASHHHjONRSwzNDxKJyd6Pqo,13998
17
+ ybox/conf/distros/arch/pkgdeps.py,sha256=Y77N1xHmHZRyZ9_diFVvoqBd44lZXIIJ2ipX7YVlrT8,14081
18
18
  ybox/conf/distros/deb-generic/check-package.sh,sha256=bMiOi6Cp_UrtXm_sXOWBq4Xng2EwXYerqBuRLtGYIlo,2621
19
19
  ybox/conf/distros/deb-generic/distro.ini,sha256=ZDWJG-cvx1BuI2RwynIvDwxzuzvVaYNv68PD-J57t84,11361
20
20
  ybox/conf/distros/deb-generic/fetch-gpg-key-id.sh,sha256=sTsLWILZosYJcGV1u8qBdczQAFBda2Zlsp1xbpy8nzo,836
@@ -22,45 +22,46 @@ ybox/conf/distros/deb-generic/init-base.sh,sha256=rim7UckGos0phyOAiNYbIjOVG9C4gK
22
22
  ybox/conf/distros/deb-generic/init-user.sh,sha256=RDt9sZuv6cSX1WBaRGcLMq2ZTBhVZmZVWD1t0wjvizU,20
23
23
  ybox/conf/distros/deb-generic/init.sh,sha256=KW0a76BJm75VHDaLPCE4MdLThcy__NpE6Z8pLB0Q7IM,5748
24
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=corAt8k8QMiQwL2zJMsfFhDeyFJKaY53G7v5odqnyZY,10549
25
+ ybox/conf/distros/deb-generic/pkgdeps.py,sha256=CKIJbftIjicGoGoYqScqb9zPZuKqT3r-gL9CRyraNbk,10571
26
26
  ybox/conf/distros/deb-oldstable/distro.ini,sha256=lr7140ftndEvs3Liavf3hg3v0qHHWAn0RRm_HAQTw8M,1103
27
27
  ybox/conf/distros/deb-stable/distro.ini,sha256=xyQ31mLOpj3Z1MK-UYuc_9NfEkb7Gy10MdDKXMgnqDo,1100
28
28
  ybox/conf/distros/ubuntu2204/distro.ini,sha256=3Pw1Q30USyKMUcHp_cvlqhwyU0FPo8O2AHWkgd0cdE4,1105
29
29
  ybox/conf/distros/ubuntu2404/distro.ini,sha256=ra4_EteDsHrw9UcehP4qLGTn98gAHc4JCb56CDzz1Wo,1102
30
30
  ybox/conf/profiles/apps.ini,sha256=9GCZJvdB5yOfx-mXyti3M-YQjeLnugrhOp43sQQvqQ0,1001
31
- ybox/conf/profiles/basic.ini,sha256=ObEGUmtEm6hABAv9XrDk_Uivv7ib_utcgfxA3qBYNj0,17783
32
- ybox/conf/profiles/dev.ini,sha256=D0Sj1orfhn7UnTbRJg5FSTn8yXFQ08MdcSXTQq7qPAE,930
31
+ ybox/conf/profiles/basic.ini,sha256=mQuksPFcIoi1K-tsZxIpKhbNGe8Ltjquo0MacPoUUwE,18952
32
+ ybox/conf/profiles/dev.ini,sha256=joSqLKrtokX4Tabn-EcxB4t7YhXlIV6ZnX3imCAPGaI,755
33
33
  ybox/conf/profiles/games.ini,sha256=FiBILNFOMpjKhrIR9nPbPj9QDUeI5h9wZaNXIb6Z7dc,1792
34
34
  ybox/conf/resources/entrypoint-base.sh,sha256=hcW8ZLHM-jlHx7McoKTo8HIFz_VBBPD0I3WqlX7LjHs,4890
35
35
  ybox/conf/resources/entrypoint-common.sh,sha256=fMopKBLeGuVV0ukANXh_ZHGTR1yGq108CTN_M2MNPyQ,461
36
36
  ybox/conf/resources/entrypoint-cp.sh,sha256=4R1UA2YLcKH_tUcyiioHSJA5B_t7GT1Z9gVFnCMi2a0,813
37
- ybox/conf/resources/entrypoint-root.sh,sha256=wTn5Sl6Fy8T_zOYgP2IlCHOYYY3Qa07kK-NC7O03OqI,685
38
- ybox/conf/resources/entrypoint-user.sh,sha256=ypHVX2jCvKTPd-_UzIWgY13n9C1glmDdeHYqh_Ahrbk,637
39
- ybox/conf/resources/entrypoint.sh,sha256=iatkLEBGoZRNm9idDeIOAiN7enYVIZVedbDgq0KLbtE,8280
40
- ybox/conf/resources/prime-run,sha256=Yu9VuT1aZ8c36s_pOCnkGYn1aL8K1BONiUUv63cw9P4,347
41
- ybox/conf/resources/run-in-dir,sha256=Z9xHEUDpOc7h3yCowPeZ47PzzgbTq__9aCO2S9TCqdI,1784
42
- ybox/conf/resources/run-user-bash-cmd,sha256=DgcBNcxeN6EWVLetGj9ZZtcqwWPJ4jGDPNt3WA_F3fI,337
37
+ ybox/conf/resources/entrypoint-root.sh,sha256=YhCftc4hYh3qKw0uEgYhHgitGY-KFGfHCxrHIMw9lHA,703
38
+ ybox/conf/resources/entrypoint-user.sh,sha256=Tps_UyQGyGn30LbgKm3gpzZtjOHnJpnlKx-px2GAd7A,698
39
+ ybox/conf/resources/entrypoint.sh,sha256=jD4_C-2YtGwJ4pe2KfxfOtJll2Kv3F4YBU0WuhFQg9w,8716
40
+ ybox/conf/resources/prime-run,sha256=en8wEspc2Hzod9rq8KKnPQrjIPTLjUk44TqmtX-g0sw,339
41
+ ybox/conf/resources/run-in-dir,sha256=WyYwCE56yuaMqoI1wGeil6UzycnlI6xxfy2iC92LDt4,1934
42
+ ybox/conf/resources/run-user-bash-cmd,sha256=LMlPoHtzYNDcOI885ouBha9xGRnQ6AWCFVsSu2dxy10,1065
43
+ ybox/conf/resources/ybox-systemd.template,sha256=-6Ui-J3GgW0VGoBdHlL7KCcGIrIgylXSKQvyzrKk7yE,647
43
44
  ybox/migrate/0.9.0-0.9.7:0.9.8.py,sha256=N7O7HtoMh_l404v2bHG5Od-_4fCDFMUBZ1SeT1P9F4M,1582
44
45
  ybox/pkg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- ybox/pkg/clean.py,sha256=t8WlVZHL-6xLnU5E5-L4otAwX-qSz9NQmupVwkGAcJw,1592
46
- ybox/pkg/info.py,sha256=izuVJNG4PjCqahzO5_tn8GAuGFX-1bf1rT0HbAO5a4c,1982
47
- ybox/pkg/inst.py,sha256=bADv0lOX3W9rsM98QYWszmCX9fFegsCn0cwpxi2Y0Nc,35574
48
- ybox/pkg/list.py,sha256=FTDYTkuf4PzB2PhHl4vdGnHILQeR8ZMFHyyp4p7LTXg,10404
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=MxOfkY166En8vzyBN1FysXlpopxb4_0u5bAdmL7jgmw,36046
49
+ ybox/pkg/list.py,sha256=Sk5THAmF132HKEZxJVDlqLLR8Z2w1wRA_E-gBsxlesQ,10097
49
50
  ybox/pkg/mark.py,sha256=cdTOKpo7Vb8j8lM0oZxdvAsJpIktnskyHWd6vEt-pG0,3748
50
- ybox/pkg/repair.py,sha256=hfJ5ETQVHSHX4F5On-8WLKJvWGFfsRaQkZ5fKyeZKSQ,7798
51
+ ybox/pkg/repair.py,sha256=rCwXXJbilJYU73feIIVWGFdbkh6SDcquxgiEnYV6pNs,8080
51
52
  ybox/pkg/repo.py,sha256=25gQgGMPeHgX83iqqWXyG6V6rIZ4i_KEnIbNG-CrxTk,13677
52
- ybox/pkg/search.py,sha256=mOJaQ2Tk-Tr3wSk4jHqCFsEtyEt00J8ULLow-cYa-0E,2903
53
+ ybox/pkg/search.py,sha256=GKfonxCLHtH-kojZpRJlRfe0qf768wUehVv20nM4lKY,2526
53
54
  ybox/pkg/uninst.py,sha256=ndqZ_WYr9HE5jVL1tQ_tTmSPyTFM6OJRCTLLmG-Qm4w,5338
54
55
  ybox/pkg/update.py,sha256=M-1MC8oh-baTHdSPWUUSTU_AhN3eu2nTCSge3bb6GmI,3269
55
56
  ybox/run/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- ybox/run/cmd.py,sha256=f4-iyS5GTHbz1e3XirVii5XvLnGxQfPjXxZGb37GhzE,2044
57
- ybox/run/control.py,sha256=PH85xf8ex28x-ojqwTjdIMgg9KH7cJmUwZPoCTblPFI,4192
58
- ybox/run/create.py,sha256=uy0NAKh5JudzakqBmKFYuedVkEB1cfWl02qD6gO7C6U,62097
59
- ybox/run/destroy.py,sha256=xwp_JMPPCl6oF1mY3kXTVfk2Nn3sdMA3iIkh00zCKB4,2570
57
+ ybox/run/cmd.py,sha256=a77RMC14Vovx2NV3LgdAzXVZXStC1TucgjcHPz4x03A,2105
58
+ ybox/run/control.py,sha256=9-ETodWye6A6eUNGKYWrXtNGCHz0eC452N_ZQNXiIiI,7006
59
+ ybox/run/create.py,sha256=DXEs1bXlC7tDK4PKNI2_O8PzRaDwuwmgN9Hy0CFDSV4,70109
60
+ ybox/run/destroy.py,sha256=nbF0dvjTqqepFo2c3a0IlUxDuLrBtFqPqP-z96eRMH4,5467
60
61
  ybox/run/graphics.py,sha256=635Ry0sXIKCpM14ZPDHsQfw-xHNARbvaaAFNDpx3oo4,18991
61
- ybox/run/logs.py,sha256=i2q0wLrsUwXYSrzfBADyiYiQ2MymSBn3N4JqPmqq03U,1989
62
- ybox/run/ls.py,sha256=Mek493Z9GbLZ5o3FytgJSzZ9Z4olhMjz3vOUe_apMOw,2726
63
- ybox/run/pkg.py,sha256=fYaQwXTUyd8MbZMLoXiogEO6l_i94lCovbarsCReT3I,24947
62
+ ybox/run/logs.py,sha256=pIdMWgNBNl-MgixArbMryUuBNNbi5JvDFP62IZ7jwr8,2050
63
+ ybox/run/ls.py,sha256=7ylyxOOYEsVWK8baM0GaZcUlVQBwpdGiF7EhU09xf2s,2787
64
+ ybox/run/pkg.py,sha256=dmv0iW-0KmcG400lRCcxQ1QCiiWJTda2kGJ1dRwyP_k,27219
64
65
  ybox/schema/0.9.1-added.sql,sha256=1rGp2DczZmmC_xwjmheeZNPSbDpFzasu6LO3tpTy3zI,1049
65
66
  ybox/schema/0.9.6-added.sql,sha256=Qcho6dP5OUpPUW3IBWl_kv88agMPHzueUAKqnZPnt3U,809
66
67
  ybox/schema/init.sql,sha256=fei8lPvjb-EIjm5zuA_XkEdjsIE3vtROhgRPt7QMlSs,1599
@@ -68,9 +69,9 @@ ybox/schema/migrate/0.9.0:0.9.1.sql,sha256=e9JGwrjFZXdWKGv2JQZlKcWz8DmOuUARpToSs
68
69
  ybox/schema/migrate/0.9.1:0.9.2.sql,sha256=X5J3unDS0eLeVvYKxQgx-iUBoAOk9T2suO34pWlQ-lE,362
69
70
  ybox/schema/migrate/0.9.2:0.9.3.sql,sha256=Y7GeBSuEEs7Hs9hh-KYDARgeeMgwQwercvTB5P_-k6I,102
70
71
  ybox/schema/migrate/0.9.5:0.9.6.sql,sha256=wqYhmputlUQzBI5zfP7O5kqIFWAbZQ05kolyHK4714A,70
71
- ybox-0.9.8.1.dist-info/LICENSE,sha256=7GbFgERMXSwD1VyLA5bo_XHvJipmNEUhDW5Sy51TFeY,1077
72
- ybox-0.9.8.1.dist-info/METADATA,sha256=spbTqpj_dPdL2S9_17cmeiPcDuvFTY0mGzq0XMYvRcw,23682
73
- ybox-0.9.8.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
74
- ybox-0.9.8.1.dist-info/entry_points.txt,sha256=xDlI_84Hl3ytYO_ERyt0rkJ4ioUF8Z1r49Hx1xL28Rk,243
75
- ybox-0.9.8.1.dist-info/top_level.txt,sha256=DYX7jvndHcBaJXLJ8vDyKrq0_KWoSeXXFq8r0d5L6Nk,5
76
- ybox-0.9.8.1.dist-info/RECORD,,
72
+ ybox-0.9.10.dist-info/LICENSE,sha256=7GbFgERMXSwD1VyLA5bo_XHvJipmNEUhDW5Sy51TFeY,1077
73
+ ybox-0.9.10.dist-info/METADATA,sha256=oRc9oG6OSP28XaYhfRtp3sAXXqjkPKDEamSCtU-NHLk,24759
74
+ ybox-0.9.10.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
75
+ ybox-0.9.10.dist-info/entry_points.txt,sha256=xDlI_84Hl3ytYO_ERyt0rkJ4ioUF8Z1r49Hx1xL28Rk,243
76
+ ybox-0.9.10.dist-info/top_level.txt,sha256=DYX7jvndHcBaJXLJ8vDyKrq0_KWoSeXXFq8r0d5L6Nk,5
77
+ ybox-0.9.10.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5