ybox 0.9.8__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 (76) hide show
  1. ybox/__init__.py +2 -0
  2. ybox/cmd.py +307 -0
  3. ybox/conf/completions/ybox.fish +93 -0
  4. ybox/conf/distros/arch/add-gpg-key.sh +29 -0
  5. ybox/conf/distros/arch/distro.ini +192 -0
  6. ybox/conf/distros/arch/init-base.sh +10 -0
  7. ybox/conf/distros/arch/init-user.sh +35 -0
  8. ybox/conf/distros/arch/init.sh +82 -0
  9. ybox/conf/distros/arch/list_fmt_long.py +76 -0
  10. ybox/conf/distros/arch/pkgdeps.py +276 -0
  11. ybox/conf/distros/deb-generic/check-package.sh +77 -0
  12. ybox/conf/distros/deb-generic/distro.ini +190 -0
  13. ybox/conf/distros/deb-generic/fetch-gpg-key-id.sh +30 -0
  14. ybox/conf/distros/deb-generic/init-base.sh +11 -0
  15. ybox/conf/distros/deb-generic/init-user.sh +3 -0
  16. ybox/conf/distros/deb-generic/init.sh +136 -0
  17. ybox/conf/distros/deb-generic/list_fmt_long.py +114 -0
  18. ybox/conf/distros/deb-generic/pkgdeps.py +208 -0
  19. ybox/conf/distros/deb-oldstable/distro.ini +21 -0
  20. ybox/conf/distros/deb-stable/distro.ini +21 -0
  21. ybox/conf/distros/supported.list +5 -0
  22. ybox/conf/distros/ubuntu2204/distro.ini +21 -0
  23. ybox/conf/distros/ubuntu2404/distro.ini +21 -0
  24. ybox/conf/profiles/apps.ini +26 -0
  25. ybox/conf/profiles/basic.ini +310 -0
  26. ybox/conf/profiles/dev.ini +25 -0
  27. ybox/conf/profiles/games.ini +39 -0
  28. ybox/conf/resources/entrypoint-base.sh +170 -0
  29. ybox/conf/resources/entrypoint-common.sh +23 -0
  30. ybox/conf/resources/entrypoint-cp.sh +32 -0
  31. ybox/conf/resources/entrypoint-root.sh +20 -0
  32. ybox/conf/resources/entrypoint-user.sh +21 -0
  33. ybox/conf/resources/entrypoint.sh +249 -0
  34. ybox/conf/resources/prime-run +13 -0
  35. ybox/conf/resources/run-in-dir +60 -0
  36. ybox/conf/resources/run-user-bash-cmd +14 -0
  37. ybox/config.py +255 -0
  38. ybox/env.py +205 -0
  39. ybox/filelock.py +77 -0
  40. ybox/migrate/0.9.0-0.9.7:0.9.8.py +33 -0
  41. ybox/pkg/__init__.py +0 -0
  42. ybox/pkg/clean.py +33 -0
  43. ybox/pkg/info.py +40 -0
  44. ybox/pkg/inst.py +638 -0
  45. ybox/pkg/list.py +191 -0
  46. ybox/pkg/mark.py +68 -0
  47. ybox/pkg/repair.py +150 -0
  48. ybox/pkg/repo.py +251 -0
  49. ybox/pkg/search.py +52 -0
  50. ybox/pkg/uninst.py +92 -0
  51. ybox/pkg/update.py +56 -0
  52. ybox/print.py +121 -0
  53. ybox/run/__init__.py +0 -0
  54. ybox/run/cmd.py +54 -0
  55. ybox/run/control.py +102 -0
  56. ybox/run/create.py +1116 -0
  57. ybox/run/destroy.py +64 -0
  58. ybox/run/graphics.py +367 -0
  59. ybox/run/logs.py +57 -0
  60. ybox/run/ls.py +64 -0
  61. ybox/run/pkg.py +445 -0
  62. ybox/schema/0.9.1-added.sql +27 -0
  63. ybox/schema/0.9.6-added.sql +18 -0
  64. ybox/schema/init.sql +39 -0
  65. ybox/schema/migrate/0.9.0:0.9.1.sql +42 -0
  66. ybox/schema/migrate/0.9.1:0.9.2.sql +8 -0
  67. ybox/schema/migrate/0.9.2:0.9.3.sql +2 -0
  68. ybox/schema/migrate/0.9.5:0.9.6.sql +2 -0
  69. ybox/state.py +914 -0
  70. ybox/util.py +351 -0
  71. ybox-0.9.8.dist-info/LICENSE +19 -0
  72. ybox-0.9.8.dist-info/METADATA +533 -0
  73. ybox-0.9.8.dist-info/RECORD +76 -0
  74. ybox-0.9.8.dist-info/WHEEL +5 -0
  75. ybox-0.9.8.dist-info/entry_points.txt +8 -0
  76. ybox-0.9.8.dist-info/top_level.txt +1 -0
ybox/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ """`ybox` is a tool to easily manage linux distributions in containers"""
2
+ __version__ = "0.9.8"
ybox/cmd.py ADDED
@@ -0,0 +1,307 @@
1
+ """
2
+ Utilities related to command execution like running a command, get podman/docker executable etc.
3
+ """
4
+
5
+ import argparse
6
+ import errno
7
+ import shlex
8
+ import subprocess
9
+ import sys
10
+ from enum import Enum
11
+ from typing import Callable, Iterable, Optional, Union
12
+
13
+ from ybox.config import Consts
14
+
15
+ from .print import print_error, print_info, print_notice, print_warn
16
+
17
+
18
+ class PkgMgr(str, Enum):
19
+ """
20
+ Package manager actions that are defined for each Linux distribution in its distro.ini file.
21
+ """
22
+ INSTALL = "install"
23
+ CHECK_AVAIL = "check_avail"
24
+ CHECK_INSTALL = "check_install"
25
+ QUIET_FLAG = "quiet_flag"
26
+ QUIET_DETAILS_FLAG = "quiet_details_flag"
27
+ OPT_DEPS = "opt_deps"
28
+ OPT_DEP_FLAG = "opt_dep_flag"
29
+ UNINSTALL = "uninstall"
30
+ PURGE_FLAG = "purge_flag"
31
+ REMOVE_DEPS_FLAG = "remove_deps_flag"
32
+ ORPHANS = "orphans"
33
+ UPDATE_META = "update_meta"
34
+ UPDATE = "update"
35
+ UPDATE_ALL = "update_all"
36
+ CLEAN = "clean"
37
+ CLEAN_QUIET = "clean_quiet"
38
+ MARK_EXPLICIT = "mark_explicit"
39
+ INFO = "info"
40
+ INFO_ALL = "info_all"
41
+ LIST = "list"
42
+ LIST_ALL = "list_all"
43
+ LIST_LONG = "list_long"
44
+ LIST_ALL_LONG = "list_all_long"
45
+ LIST_FILES = "list_files"
46
+ SEARCH = "search"
47
+ SEARCH_ALL = "search_all"
48
+ SEARCH_OFFICIAL_FLAG = "search_official_flag"
49
+ SEARCH_WORD_START_FLAG = "search_word_start_flag"
50
+ SEARCH_WORD_END_FLAG = "search_word_end_flag"
51
+ PROCESSES_PATTERN = "processes_pattern"
52
+ LOCKS_PATTERN = "locks_pattern"
53
+ REPAIR = "repair"
54
+ REPAIR_ALL = "repair_all"
55
+
56
+
57
+ class RepoCmd(str, Enum):
58
+ """
59
+ Repository management actions defined for each Linux distribution in its distro.ini file.
60
+ """
61
+ EXISTS = "exists"
62
+ DEFAULT_GPG_KEY_SERVER = "default_gpg_key_server"
63
+ ADD_KEY = "add_key"
64
+ ADD_KEY_ID = "add_key_id"
65
+ ADD = "add"
66
+ ADD_SOURCE = "add_source"
67
+ REMOVE_KEY = "remove_key"
68
+ REMOVE = "remove"
69
+
70
+
71
+ class YboxLabel(str, Enum):
72
+ """
73
+ Labels for ybox created objects.
74
+ """
75
+ CONTAINER_LABEL_GROUP = "io.ybox.container"
76
+ CONTAINER_TYPE = f"{CONTAINER_LABEL_GROUP}.type"
77
+ CONTAINER_DISTRIBUTION = f"{CONTAINER_LABEL_GROUP}.distribution"
78
+
79
+ # ybox container types (first two are temporary ones)
80
+ CONTAINER_BASE = f"{CONTAINER_TYPE}=base"
81
+ CONTAINER_COPY = f"{CONTAINER_TYPE}=copy"
82
+ CONTAINER_PRIMARY = f"{CONTAINER_TYPE}=primary"
83
+
84
+
85
+ def get_ybox_state(docker_cmd: str, box_name: str, expected_states: Iterable[str],
86
+ exit_on_error: bool = False, state_msg: str = "") -> tuple[str, str]:
87
+ """
88
+ Check if the given ybox container exists and is in one of the given states, or get the state
89
+ if the given `expected_states` is empty.
90
+
91
+ :param docker_cmd: the podman/docker executable to use
92
+ :param box_name: name of the ybox container
93
+ :param expected_states: Iterable of one or more expected states like 'running', 'exited';
94
+ empty value means any state is permissible which is returned
95
+ :param exit_on_error: whether to exit using `sys.exit` if the check fails
96
+ :param state_msg: string to be inserted in the error message "No...ybox container ..."
97
+ when `exit_on_error` is false, so this should be a display name for the
98
+ `expected_states` with a space at the start e.g. ' active', ' stopped'
99
+ :return: if `exit_on_error` is False, then return a tuple of (state, distribution) of the
100
+ container if it was in the set of `expected_states` or if `expected_states` was empty
101
+ """
102
+ check_result = subprocess.run(
103
+ [docker_cmd, "inspect", "--type=container", '--format={{index .Config.Labels "' +
104
+ YboxLabel.CONTAINER_TYPE.value + '"}} {{index .Config.Labels "' +
105
+ YboxLabel.CONTAINER_DISTRIBUTION.value + '"}} {{.State.Status}}', box_name],
106
+ check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
107
+ if check_result.returncode != 0:
108
+ if exit_on_error:
109
+ print_error(f"No{state_msg} ybox container '{box_name}' found")
110
+ sys.exit(check_result.returncode)
111
+ return tuple[str, str]()
112
+ result = check_result.stdout.decode("utf-8").split()
113
+ if len(result) == 3 and result[0] == "primary":
114
+ distribution = result[1]
115
+ state = result[2]
116
+ if not expected_states or state in expected_states:
117
+ return (state, distribution)
118
+ if exit_on_error:
119
+ print_error(f"Unexpected state for ybox container '{box_name}': {state}")
120
+ sys.exit(1)
121
+ return tuple[str, str]()
122
+ if exit_on_error:
123
+ print_error(f"Container '{box_name}' not a ybox container!")
124
+ sys.exit(1)
125
+ return tuple[str, str]()
126
+
127
+
128
+ def check_active_ybox(docker_cmd: str, box_name: str, exit_on_error: bool = False) -> bool:
129
+ """
130
+ Check if the given ybox container is up and running.
131
+
132
+ :param docker_cmd: the podman/docker executable to use
133
+ :param box_name: name of the ybox container
134
+ :param exit_on_error: whether to exit using `sys.exit` if the check fails
135
+ :return: if `exit_on_error` is False, then return the result of verification as True or False
136
+ """
137
+ return bool(get_ybox_state(docker_cmd, box_name, expected_states=("running",),
138
+ exit_on_error=exit_on_error, state_msg=" active"))
139
+
140
+
141
+ def check_ybox_exists(docker_cmd: str, box_name: str, exit_on_error: bool = False) -> bool:
142
+ """
143
+ Check if the given ybox container exists in either active or inactive state.
144
+
145
+ :param docker_cmd: the podman/docker executable to use
146
+ :param box_name: name of the ybox container
147
+ :param exit_on_error: whether to exit using `sys.exit` if the check fails
148
+ :return: if `exit_on_error` is False, then return the result of verification as True or False
149
+ """
150
+ return bool(get_ybox_state(docker_cmd, box_name, expected_states=(),
151
+ exit_on_error=exit_on_error))
152
+
153
+
154
+ def build_shell_command(docker_cmd: str, box_name: str, cmd: str,
155
+ enable_pty: bool = True) -> list[str]:
156
+ """
157
+ Build a podman/docker command (as a list) to be run using `run-user-bash-cmd` in the given
158
+ ybox container (that in turn runs the command as non-root user using `sudo` with `/bin/bash`).
159
+
160
+ :param docker_cmd: the podman/docker executable to use
161
+ :param box_name: name of the ybox container
162
+ :param cmd: the command to be run in the container
163
+ :param enable_pty: if True then enable pseudo-pty allocation for the `podman/docker exec`
164
+ command and set interactive mode else no pty is allocated, defaults to True
165
+ :return: command to be executed (e.g. in `subprocess`) as a list of strings
166
+ """
167
+ shell = f"/usr/local/bin/{Consts.run_user_bash_cmd()}"
168
+ if enable_pty:
169
+ return [docker_cmd, "exec", "-it", box_name, shell, cmd]
170
+ return [docker_cmd, "exec", box_name, shell, cmd]
171
+
172
+
173
+ def run_command(cmd: Union[str, list[str]], capture_output: bool = False,
174
+ exit_on_error: bool = True, error_msg: Optional[str] = None) -> Union[str, int]:
175
+ """
176
+ Helper wrapper around `subprocess.run` to display failure message (in red foreground color)
177
+ for the case of failure, exit on failure and capturing and returning output if required.
178
+
179
+ :param cmd: the command to be run which can be either a list of strings, or a single string
180
+ which will be split like done by unix shell using `shlex.split`
181
+ :param capture_output: if True then capture stdout and return it but stderr is still displayed
182
+ on screen using `print_warn` method in purple foreground color
183
+ :param exit_on_error: whether to exit using `sys.exit` if command fails
184
+ :param error_msg: string to be inserted in error message "FAILURE in ..." so should be a
185
+ user-friendly name of the action that the command was supposed to do;
186
+ if not specified then the entire command string is displayed;
187
+ the special value 'SKIP' can be used to skip printing any error message
188
+ :return: the captured standard output if `capture_output` is true and command is successful
189
+ encoded as UTF-8 string else the return code of the command as an integer
190
+ (if `exit_on_error` is False)
191
+ """
192
+ args = shlex.split(cmd) if isinstance(cmd, str) else cmd
193
+ try:
194
+ result = subprocess.run(args, capture_output=capture_output, check=False)
195
+ except OSError as err:
196
+ if exit_on_error:
197
+ raise # an unexpected internal issue, so keep the full stack trace
198
+ print_error(f"FAILURE invoking '{cmd}': {err}")
199
+ return err.errno or errno.ENOENT
200
+ if result.returncode != 0:
201
+ _print_subprocess_output(result)
202
+ if not error_msg:
203
+ error_msg = f"'{cmd}'"
204
+ if error_msg != "SKIP":
205
+ print_error(f"FAILURE in {error_msg} -- see the output above for details")
206
+ if exit_on_error:
207
+ sys.exit(result.returncode)
208
+ else:
209
+ return result.returncode
210
+ if capture_output and result.stderr:
211
+ print_warn(result.stderr.decode("utf-8"), file=sys.stderr)
212
+ return result.stdout.decode("utf-8") if capture_output else result.returncode
213
+
214
+
215
+ def _print_subprocess_output(result: subprocess.CompletedProcess[bytes]) -> None:
216
+ """print completed subprocess output in color (orange for standard output and purple
217
+ for standard error)"""
218
+ if result.stdout:
219
+ print_notice(result.stdout.decode("utf-8"))
220
+ if result.stderr:
221
+ print_warn(result.stderr.decode("utf-8"), file=sys.stderr)
222
+
223
+
224
+ def parse_opt_deps_args(argv: list[str]) -> argparse.Namespace:
225
+ """
226
+ Common command-line parser for `opt_deps` utilities (see [pkgmgr] section of distro.ini)
227
+ that parses given arguments for the program and returns the result :class:`argparse.Namespace`.
228
+
229
+ :param argv: the list of arguments to be parsed
230
+ :return: the result of parsing using the `argparse` library as a :class:`argparse.Namespace`
231
+ """
232
+ parser = argparse.ArgumentParser(
233
+ description="Recursively find optional dependencies of a package")
234
+ # default separator is something that does not appear in descriptions (at least so far)
235
+ parser.add_argument("-s", "--separator", type=str, default="::::",
236
+ help="separator to use between the columns")
237
+ parser.add_argument("-p", "--prefix", type=str, default="",
238
+ help="prefix string before each line of result")
239
+ parser.add_argument("-H", "--header", type=str, default="",
240
+ help="header line to print before the results (without trailing newline)")
241
+ parser.add_argument("-l", "--level", type=int, default=2,
242
+ help="maximum level to search for optional dependencies")
243
+ parser.add_argument("package", type=str, help="name of the package")
244
+ return parser.parse_args(argv)
245
+
246
+
247
+ def page_output(output: Iterable[bytes], pager: str) -> int:
248
+ """
249
+ Display an `Iterable` of bytes on the terminal one screenful at a time using the given `pager`.
250
+
251
+ :param output: the `Iterable` of `bytes` to be displayed (e.g. file/stream object, `list` etc)
252
+ :param pager: the command to be executed for pagination as a separate process with any
253
+ arguments that are required; the arguments are split using :func:`shlex.split`
254
+ so you can use shell-like quoting/escaping if they have spaces
255
+ :return: the exit code of the `pager` command
256
+ """
257
+ if not pager:
258
+ for out in output:
259
+ sys.stdout.write(out.decode("utf-8"))
260
+ sys.stdout.flush()
261
+ return 0
262
+ try:
263
+ with subprocess.Popen(shlex.split(pager), stdin=subprocess.PIPE) as page_in:
264
+ assert page_in.stdin is not None
265
+ for out in output:
266
+ page_in.stdin.write(out)
267
+ page_in.communicate()
268
+ return page_in.returncode
269
+ except BrokenPipeError:
270
+ return 0 # this can happen if pager ends (e.g. using 'q' in less)
271
+ except OSError as err:
272
+ print_error(f"FAILURE invoking pager '{pager}': {err}")
273
+ return err.errno or errno.ENOENT
274
+ except KeyboardInterrupt:
275
+ # fail cleanly for user interrupt in the pager
276
+ print()
277
+ print_info("Interrupt")
278
+ return 130 # see https://tldp.org/LDP/abs/html/exitcodes.html
279
+
280
+
281
+ def page_command(cmd: Union[str, list[str]], pager: str, error_msg: Optional[str] = None,
282
+ transform: Optional[Callable[[str], str]] = None, header: str = "") -> int:
283
+ """
284
+ Execute a given command using `subprocess.run` and show its output one screenful at a time
285
+ as UTF-8 string using the given `pager` command. In case of failure of either the command
286
+ or the `pager`, a failure message is shown and the exit code of the failed process is returned.
287
+
288
+ :param cmd: the command to be run which can be either a list of strings, or a single string
289
+ which will be split like done by unix shell using `shlex.split`
290
+ :param pager: the command to be executed for pagination as a separate process,
291
+ or empty to skip pagination
292
+ :param error_msg: string to be inserted in error message "FAILURE in ..." so should be a
293
+ user-friendly name of the action that the command was supposed to do;
294
+ if not specified then the entire command string is displayed;
295
+ the special value 'SKIP' can be used to skip printing any error message
296
+ :param transform: optional function to apply to the output of command before pagination
297
+ :param header: header to be inserted at the start of output, default is empty string
298
+ :return: the exit code of `cmd` if it failed else the exit code of the `pager` command
299
+ """
300
+ result = run_command(cmd, capture_output=True, exit_on_error=False, error_msg=error_msg)
301
+ if isinstance(result, int):
302
+ return result
303
+ if not result:
304
+ return 0
305
+ result_bytes = transform(result).encode("utf-8") if transform else result.encode("utf-8")
306
+ output = (header.encode("utf-8"), result_bytes) if header else (result_bytes,)
307
+ return page_output(output, pager)
@@ -0,0 +1,93 @@
1
+ # Completions for ybox commands, a "Manage containers hosting Linux distributions and apps"
2
+
3
+ function __fish_ybox_complete_containers
4
+ /usr/bin/python3 (type -p ybox-ls) --format="{{ .Names }}"
5
+ end
6
+
7
+ function __fish_ybox_complete_all_containers
8
+ /usr/bin/python3 (type -p ybox-ls) --all --format="{{ .Names }}"
9
+ end
10
+
11
+ function __fish_ybox_complete_stopped_containers
12
+ /usr/bin/python3 (type -p ybox-ls) --filter="status=exited" --format="{{ .Names }}"
13
+ end
14
+
15
+ function __fish_ybox_complete_distributions
16
+ set user_supported ~/.config/ybox/distros/supported.list
17
+ set sys_supported ~/.local/lib/python3*/site-packages/ybox/conf/distros/supported.list
18
+ set local_supported src/ybox/conf/distros/supported.list
19
+ if test -r "$user_supported"
20
+ /usr/bin/cat $user_supported
21
+ else if test -r "$sys_supported" 2>/dev/null
22
+ /usr/bin/cat $sys_supported
23
+ else if test -r "$local_supported" 2>/dev/null
24
+ /usr/bin/cat $local_supported
25
+ end
26
+ end
27
+
28
+
29
+ complete -f -c ybox-create -s h -l help -d "show help"
30
+ complete -c ybox-create -s n -l name -d "name of the ybox container" -r
31
+ complete -f -c ybox-create -s F -l force-own-orphans -d "force ownership of orphans on shared root"
32
+ complete -f -c ybox-create -s q -l quiet -d "skip interactive questions"
33
+ complete -f -c ybox-create -n "not __fish_seen_subcommand_from (__fish_ybox_complete_distributions)" -a "(__fish_ybox_complete_distributions)"
34
+
35
+
36
+ complete -f -c ybox-destroy -s h -l help -d "show help"
37
+ complete -f -c ybox-destroy -s f -l force -d "force destroy the container using SIGKILL if required"
38
+ complete -f -c ybox-destroy -n "not __fish_seen_subcommand_from (__fish_ybox_complete_all_containers)" -a "(__fish_ybox_complete_all_containers)"
39
+
40
+ complete -f -c ybox-logs -s h -l help -d "show help"
41
+ complete -f -c ybox-logs -s f -l follow -d "follow log output like 'tail -f'"
42
+ complete -f -c ybox-logs -n "not __fish_seen_subcommand_from (__fish_ybox_complete_all_containers)" -a "(__fish_ybox_complete_all_containers)"
43
+
44
+
45
+ complete -f -c ybox-ls -s h -l help -d "show help"
46
+ complete -f -c ybox-ls -s a -l all -d "show all containers including stopped"
47
+ complete -f -c ybox-ls -s f -l filter -d "filter in <key>=<value> format" -r
48
+ complete -f -c ybox-ls -s s -l format -d "format output using a JSON/Go template string" -r
49
+ complete -f -c ybox-ls -s l -l long-format -d "show more extended information"
50
+
51
+
52
+ complete -c ybox-cmd -s h -l help -d "show help"
53
+ complete -c ybox-cmd -n "not __fish_seen_subcommand_from (__fish_ybox_complete_containers)" -a "(__fish_ybox_complete_containers)" -f
54
+
55
+
56
+ # define subcommands for ybox-control
57
+ set -l control_commands start stop restart status
58
+ complete -c ybox-control -f
59
+ # common options for all ybox-control subcommands
60
+ complete -c ybox-control -s h -l help -d "show help"
61
+ # available subcommands for ybox-control with descriptions
62
+ complete -c ybox-control -n "not __fish_seen_subcommand_from $control_commands" -a start -d "start an inactive ybox container"
63
+ complete -c ybox-control -n "not __fish_seen_subcommand_from $control_commands" -a stop -d "stop an active ybox container"
64
+ complete -c ybox-control -n "not __fish_seen_subcommand_from $control_commands" -a restart -d "restart a ybox container"
65
+ complete -c ybox-control -n "not __fish_seen_subcommand_from $control_commands" -a status -d "show status of a ybox container"
66
+ # define arguments for individual subcommands using the "-n" option for the subcommand
67
+ complete -c ybox-control -n "__fish_seen_subcommand_from start" -a "(__fish_ybox_complete_stopped_containers)"
68
+ complete -c ybox-control -n "__fish_seen_subcommand_from stop" -a "(__fish_ybox_complete_containers)"
69
+ complete -c ybox-control -n "__fish_seen_subcommand_from restart" -a "(__fish_ybox_complete_all_containers)"
70
+ complete -c ybox-control -n "__fish_seen_subcommand_from status" -a "(__fish_ybox_complete_all_containers)"
71
+
72
+
73
+ # define subcommands for ybox-pkg
74
+ set -l pkg_commands install uninstall update list list-files info search mark clean repair
75
+ complete -c ybox-pkg -f
76
+ # common options for all ybox-pkg subcommands
77
+ complete -c ybox-pkg -s h -l help -d "show help"
78
+ complete -c ybox-pkg -s z -l ybox -d "ybox container to use for package operation" -rfa "(__fish_ybox_complete_containers)"
79
+ complete -c ybox-pkg -s C -l distribution-config -d "path to distribution configuration file" -rF
80
+ complete -c ybox-pkg -s q -l quiet -d "proceed without asking any questions using defaults"
81
+ # available subcommands for ybox-pkg with descriptions
82
+ complete -c ybox-pkg -n "not __fish_seen_subcommand_from $pkg_commands" -a install -d "install a package with dependencies"
83
+ complete -c ybox-pkg -n "not __fish_seen_subcommand_from $pkg_commands" -a uninstall -d "uninstall a package and optionally its dependencies"
84
+ complete -c ybox-pkg -n "not __fish_seen_subcommand_from $pkg_commands" -a update -d "update some or all packages"
85
+ complete -c ybox-pkg -n "not __fish_seen_subcommand_from $pkg_commands" -a list -d "list installed packages"
86
+ complete -c ybox-pkg -n "not __fish_seen_subcommand_from $pkg_commands" -a list-files -d "list files of an installed package"
87
+ complete -c ybox-pkg -n "not __fish_seen_subcommand_from $pkg_commands" -a info -d "show detailed information of package(s)"
88
+ complete -c ybox-pkg -n "not __fish_seen_subcommand_from $pkg_commands" -a search -d "search repositories"
89
+ complete -c ybox-pkg -n "not __fish_seen_subcommand_from $pkg_commands" -a mark -d "mark package as dependency or explicitly installed"
90
+ complete -c ybox-pkg -n "not __fish_seen_subcommand_from $pkg_commands" -a clean -d "clean package cache"
91
+ complete -c ybox-pkg -n "not __fish_seen_subcommand_from $pkg_commands" -a repair -d "try to repair package state"
92
+ # define arguments for individual subcommands using the "-n" option for the subcommand
93
+ complete -c ybox-pkg -n "__fish_seen_subcommand_from install" -s o -l skip-opt-deps -d "skip optional dependencies"
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+
3
+ # this script fetches a GPG/PGP key file from a URL and adds the key to pacman
4
+
5
+ set -e
6
+
7
+ # Check if all arguments are provided
8
+ if [ $# -ne 1 ]; then
9
+ echo "Usage: $0 <url>"
10
+ exit 1
11
+ fi
12
+
13
+ # ensure that only system paths are searched for all the system utilities
14
+ export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
15
+
16
+ file="$(mktemp /tmp/gpg-key-XXXXXXXXXX)"
17
+
18
+ trap "rm -f $file" 0 1 2 3 15
19
+
20
+ curl -sSL "$1" -o "$file"
21
+ KEYIDS="$(gpg --show-keys --with-colons "$file" | sed -n 's/^fpr:*\([^:]*\).*/\1/p' | tr '\n' ' ')"
22
+ sudo pacman-key --add "$file"
23
+
24
+ for keyid in $KEYIDS; do
25
+ sudo pacman-key --lsign-key $keyid
26
+ done
27
+ echo "KEYID=$KEYIDS"
28
+
29
+ exit 0
@@ -0,0 +1,192 @@
1
+ # Configuration specific to each distribution (INI style file)
2
+
3
+ # The following environment variables are set when running the init.sh and init-user.sh scripts.
4
+ # The scripts are required to honor the meaning of the corresponding variables as described in
5
+ # their comments.
6
+ # CONFIGURE_FASTEST_MIRRORS: empty or non-empty corresponding to configure_fastest_mirrors below
7
+ # REQUIRED_PKGS: packages specified in `packages.required` below
8
+ # REQUIRED_DEPS: packages specified in `packages.required_deps` below
9
+ # RECOMMENDED_PKGS: packages specified in `packages.recommended` below
10
+ # RECOMMENDED_DEPS: packages specified in `packages.recommended_deps` below
11
+ # SUGGESTED_PKGS: packages specified in `packages.suggested` below
12
+ # SUGGESTED_DEPS: packages specified in `packages.suggested_deps` below
13
+ # EXTRA_PKGS: packages specified in `packages.extra` below
14
+
15
+
16
+ # Base configuration for the distribution
17
+ [base]
18
+ # name is required
19
+ name = Arch Linux
20
+ # Comma separated files to include before applying these settings.
21
+ # Paths can be absolute or relative to the location of this file.
22
+ includes =
23
+ # points to the daily updated image to minimize upgrade size
24
+ image = quay.io/archlinux/archlinux
25
+ # directories which are shared between the containers of a distribution when
26
+ # `shared_root` is provided in the container configuration
27
+ shared_root_dirs = /etc,/opt,/usr,/var
28
+ # the secondary groups of the container user; it requires to include at least the equivalent of
29
+ # nobody/nogroup to work correctly (the last field in /etc/subgid)
30
+ secondary_groups = nobody,wheel,video,input,lp,mail
31
+ # whether to search for and configure fastest available mirrors for packages
32
+ configure_fastest_mirrors = true
33
+ # distribution scripts that need to be copied to the container in $YBOX_TARGET_SCRIPTS_DIR
34
+ # (should include init.sh, init-base.sh and init-user.sh scripts that are are normally required
35
+ # for all distributions)
36
+ scripts = init-base.sh,init.sh,init-user.sh,pkgdeps.py,list_fmt_long.py,add-gpg-key.sh
37
+
38
+
39
+ # Initial set of packages to be installed in the distribution image
40
+ [packages]
41
+ # packages required for a functional ybox container
42
+ required = base-devel python-ijson expac lesspipe
43
+ # dependencies of the `required` packages
44
+ required_deps = git ed unzip fastjar
45
+ # recommended packages required for many GUI/CLI apps to work properly
46
+ recommended = aria2 bash-completion bc man-db man-pages pulseaudio-alsa zip wget shared-mime-info
47
+ libva-utils mesa-utils vulkan-tools ttf-liberation
48
+ # dependencies of the `recommended` packages
49
+ recommended_deps = intel-media-driver libva-mesa-driver vulkan-intel vulkan-mesa-layers python-pip
50
+ # optional packages for enhanced experience in shell and GUI apps
51
+ # (for some reason TERMINFO_DIRS does not work for root user, so explicitly installing terminfo
52
+ # packages for other terminal emulators available in arch which occupy only a tiny space)
53
+ suggested = cantarell-fonts ttf-fira-code noto-fonts neovim eza ncdu fd bat
54
+ kitty-terminfo rxvt-unicode-terminfo realtime-privileges tree starship
55
+ # dependencies of the `suggested` packages
56
+ suggested_deps = xsel
57
+ # additional packages that are in AUR and installed by paru in init-user.sh
58
+ extra = neovim-symlinks tinyxxd autojump
59
+
60
+
61
+ # The commands here will be run as normal userns mapped user, so use sudo if the command
62
+ # needs to run as root inside the container
63
+ [pkgmgr]
64
+ # the variables here are all required ones unless noted otherwise
65
+
66
+ # install command does not have a placeholder for {package} since it is also used by
67
+ # entrypoint scripts to install packages, so this assumes that this command accepts a list of one
68
+ # or more space-separated packages at the end
69
+ install = /usr/bin/paru -S --needed --removemake --cleanafter {quiet} {opt_dep}
70
+ # Show the packages that satisfy the given {package} where latter can be a virtual package
71
+ # (i.e. "Provides") or an actual package or a combination of both (e.g. some packages provide and
72
+ # replace another actual package).
73
+ # Not required for Arch since install command itself shows the selection if required.
74
+ check_avail
75
+ # check an installed actual or virtual (i.e. "Provides") package and list them in reverse
76
+ # install/upgrade time order (i.e. most recently installed/upgraded first)
77
+ check_install = /usr/bin/expac -Qs --timefmt=%%s '%%l\t%%n' '^{package}$' | \
78
+ /usr/bin/sort -nr | /usr/bin/cut -f2
79
+ # proceed quietly without asking questions
80
+ quiet_flag = --noconfirm
81
+ # this is substituted for `{quiet}` placeholder in `info`, `info_all`, `search` and `search_all`
82
+ quiet_details_flag = -q
83
+ # --asdeps works correctly only if an optional dependency is actually marked to be so in
84
+ # the arch package, but will cause trouble for cases where user wants to mark a package as
85
+ # an optional dependency of another even otherwise (e.g. qt5ct as optional dependency of qt5-base)
86
+ # opt_dep_flag = --asdeps
87
+ opt_dep_flag =
88
+ # Expected output of the `opt_deps` command is:
89
+ # {header}
90
+ # {prefix}<name>{separator}<level>{separator}<installed>{separator}<description>
91
+ # where
92
+ # <name>: name of the optional dependency
93
+ # <level>: level of the dependency i.e. 1 for direct dependency, 2 for dependency of dependency and
94
+ # so on; resolution of level > 2 is not required since caller currently ignores those
95
+ # <installed>: true if the dependency already installed and false otherwise
96
+ opt_deps = /usr/bin/python3 $YBOX_TARGET_SCRIPTS_DIR/pkgdeps.py \
97
+ -s '{separator}' -p '{prefix}' -H '{header}'
98
+ uninstall = /usr/bin/paru -R {quiet} {purge} {remove_deps} {package}
99
+ purge_flag = --nosave
100
+ remove_deps_flag = --recursive
101
+ orphans = /usr/bin/pacman -Qdtq
102
+ update_meta = /usr/bin/paru -Sy
103
+ update = /usr/bin/paru -S --needed --removemake --cleanafter {quiet} {packages}
104
+ update_all = /usr/bin/paru -Syu --removemake --cleanafter {quiet}
105
+ clean = /usr/bin/paru -Sccd
106
+ clean_quiet = /bin/yes | %(clean)s
107
+ mark_explicit = /usr/bin/paru -D --asexplicit {package}
108
+
109
+ info = /usr/bin/pacman -Qi {quiet} {packages}
110
+ info_all = /usr/bin/paru -Si {quiet} {packages}
111
+
112
+ # next two variables are not required ones rather are used for expansion in list variables
113
+ # to avoid repetition
114
+ list_fmt = /usr/bin/sed 's/^\([^[:space:]]*\)[[:space:]]\+/\1{separator}/'
115
+ list_fmt_long = /usr/bin/python3 $YBOX_TARGET_SCRIPTS_DIR/list_fmt_long.py '{separator}'
116
+
117
+ # list and list_all show package name and version separated by {separator}; the list command only
118
+ # shows explicitly installed packages while the "_all" variant also shows dependents
119
+ list = /usr/bin/pacman -Qe {packages} | %(list_fmt)s
120
+ list_all = /usr/bin/pacman -Q {packages} | %(list_fmt)s
121
+ # list_long and list_all_long show package name, version, dependency-of and description separated
122
+ # by {separator}; the dependency-of column gives the required and optional dependencies in the
123
+ # format: req(<pkg> <pkg> ...),opt(<pkg> <pkg> ...); like above the "_all" variant shows all
124
+ # packages including dependents while other one shows only explicitly installed ones
125
+ list_long = /usr/bin/pacman -Qie {packages} | %(list_fmt_long)s
126
+ list_all_long = /usr/bin/pacman -Qi {packages} | %(list_fmt_long)s
127
+
128
+ list_files = /usr/bin/pacman -Qlq {package}
129
+
130
+ # next five variables are not required ones rather are used for expansion in search variables
131
+
132
+ # --searchby=name only applies to the AUR packages, so filter out packages where only the package
133
+ # names match; the weird "set ..." subcommand converts space-separated quoted arguments into a
134
+ # regex i.e. ' one ' 'two' ' three' to " one |two | three" which is accomplished by declaring
135
+ # those arguments as positional arguments $1, $2 etc, then echo all arguments with IFS as "|"
136
+ args_re = "$(set -- {search}; IFS="|"; echo "$*")"
137
+ # word matching works correctly after removing color codes, but keep those in the final output
138
+ search_filter_prefix = /usr/bin/gawk 'BEGIN {{ IGNORECASE=1 }} /^[^[:space:]].*/ {{
139
+ pkg = $0; gsub(/\x1B\[[0-9;]+m/, ""); pkg_nc = $0; getline; '
140
+ search_filter_suffix = 'if (pkg_nc ~ /{word_start}('%(args_re)s'){word_end}/) print pkg"\n"$0 }}'
141
+ name_filter = %(search_filter_prefix)s%(search_filter_suffix)s
142
+ all_filter = %(search_filter_prefix)s'pkg_nc = pkg_nc $0; '%(search_filter_suffix)s
143
+
144
+ # search in package names in the repositories
145
+ search = /usr/bin/paru -Ss {quiet} {official} --searchby=name --sortby=popularity --color=always \
146
+ {search} | %(name_filter)s
147
+ # search in package names and descriptions in the repositories
148
+ search_all = /usr/bin/paru -Ss {quiet} {official} --sortby=popularity --color=always \
149
+ {search} | %(all_filter)s
150
+ # this is substituted for `{official}` placeholder in `search` and `search_all`
151
+ search_official_flag = --repo
152
+ # this is substituted for `{word_start}` placeholder in `search` and `search_all`
153
+ search_word_start_flag = \<
154
+ # this is substituted for `{word_end}` placeholder in `search` and `search_all`
155
+ search_word_end_flag = \>
156
+
157
+ # regex pattern matching processes that may be invoked by package manager directly or indirectly
158
+ # that may need to be terminated for cleanup
159
+ processes_pattern = \b(pacman|paru|keyboxd|dirmngr)\b
160
+ # comma separated globs for package manager related lock files (no spaces in names or within)
161
+ locks_pattern = /var/lib/pacman/db.lck,$HOME/.gnupg/public-keys.d/*.lock
162
+ repair = /usr/bin/paru -Syyuu {quiet}
163
+ # reinstall all packages
164
+ repair_all = %(repair)s && (/usr/bin/pacman -Qqn | /usr/bin/paru -S {quiet} -) && \
165
+ (/usr/bin/pacman -Qqm | /usr/bin/paru -S {quiet} -) && %(clean_quiet)s
166
+
167
+
168
+ # Commands related to repository management
169
+ [repo]
170
+ conf_file = /etc/pacman.conf
171
+ exists = /usr/bin/grep -q '^[[:space:]]*\[{name}\][[:space:]]*$' %(conf_file)s
172
+ # Ubuntu's keyserver is more reliable than the standard gnupg.net/pgp.net ones
173
+ default_gpg_key_server = hkps://keyserver.ubuntu.com
174
+ # output of the `add_key` command should have a line of the form "KEYID=..." that provides the
175
+ # registered keys that can be passed to `remove_key`
176
+ add_key = /bin/bash $YBOX_TARGET_SCRIPTS_DIR/add-gpg-key.sh '{url}'
177
+ add_key_id = /usr/bin/sudo /usr/bin/pacman-key --keyserver '{server}' --recv-keys '{key}' && \
178
+ /usr/bin/sudo /usr/bin/pacman-key --lsign-key '{key}'
179
+ # additional options if supported should be mentioned using {options} below
180
+ add = /bin/echo '[{name}],{urls}' | /usr/bin/sed 's/,/\nServer = /g' | \
181
+ /usr/bin/sudo /usr/bin/tee -a %(conf_file)s
182
+ # if source repository is supported, then it should be provided in `add_source` like for `add`
183
+ add_source
184
+ remove_key = /usr/bin/sudo /usr/bin/pacman-key --delete {key}
185
+ # The sed pattern below removes the [{name}] section from pacman.conf
186
+ # (S_PAT matches start of the section while E_PAT matches next section which has to be retained).
187
+ # If source repositories are supported in `add_source` command above, then {remove_source} boolean
188
+ # should be checked to remove source repository too.
189
+ rm_s_pat = ^[[:space:]]*\[{name}\][[:space:]]*$
190
+ rm_e_pat = ^[[:space:]]*\[.*
191
+ remove = /usr/bin/sudo /usr/bin/sed -i \
192
+ '/%(rm_s_pat)s/,/%(rm_e_pat)s/ {{/%(rm_s_pat)s/d;/%(rm_e_pat)s/!d;}}' %(conf_file)s
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ # disable the sandbox in the newer pacman versions that does not work in container
6
+ sed -i 's/^#[ ]*DisableSandbox/DisableSandbox/' /etc/pacman.conf
7
+ # sudo is not present in the upstream archlinux image
8
+ pacman -Sy
9
+ pacman -S --noconfirm --needed sudo
10
+ yes | pacman -Scc 2>/dev/null >/dev/null