Compare commits

...

23 Commits

Author SHA1 Message Date
InsanePrawn
15049285e3 image.py: build and use kupfer-config instead of running post_cmds 2022-07-07 03:23:00 +02:00
InsanePrawn
d277d926fd Chroot.run_cmd(): add user parameter 2022-07-07 03:21:02 +02:00
InsanePrawn
064f265247 image.py: install_rootfs(): fill passed profile with defaults for user- and hostname 2022-07-06 22:41:45 +02:00
Evan Deaubl
0da9feeda0 constants.py: add oneplus-fajita device to support OP6T 2022-07-04 21:37:12 +00:00
Prawn
35a79363a4 docs: add sphinx docs and gitlab pages 2022-06-24 01:43:34 +00:00
InsanePrawn
e28239454a packages: cmd_check: extend set of characters that justify quoting 2022-06-08 04:02:05 +02:00
Syboxez Blank
fc90e30bd7 Update README.md with new dependencies 2022-05-21 18:45:56 -05:00
InsanePrawn
2778038b19 constants: temporarily replace squeekboard with phosh-osk-stub 2022-05-10 06:47:47 +02:00
InsanePrawn
e460e7d0be image: fix runtime conf access for native architecture 2022-05-09 01:35:04 +02:00
InsanePrawn
6000679817 image: cmd_build(): add --[no-]local-repos to build images from https repos only 2022-05-08 18:05:15 +02:00
InsanePrawn
cb95846fb5 distro: put extra_repos before core repos in pacman.conf 2022-05-08 18:05:15 +02:00
InsanePrawn
e288918e58 image.py: make device paths work with cmd_flash() again, add dd debug output 2022-05-08 18:05:15 +02:00
InsanePrawn
232254948d config: add pacman.repo_branch, adjust KUPFER_HTTPS url to include $arch and use repo_branch 2022-05-08 18:05:09 +02:00
InsanePrawn
933ffd833c logger: add colors 2022-05-07 15:28:06 +02:00
InsanePrawn
42a82a10e8 packages: fix up cmd_clean() 2022-05-07 13:49:39 +02:00
InsanePrawn
a746e938cd Chroot.deactivate(ignore_rootfs=True): also ignore /boot 2022-05-07 13:34:14 +02:00
InsanePrawn
b13b00e85b get_makepkg_env(): filter out CI variables 2022-05-07 05:32:25 +02:00
InsanePrawn
6917347219 chroot: fix rsync --exclude (use CHROOT_PATHS.values instead of implict keys()) 2022-05-06 05:27:08 +02:00
InsanePrawn
3874b4e626 chroot: cleanups, initialize(): unmount everything except / before create_rootfs() 2022-05-06 04:07:41 +02:00
InsanePrawn
686c94c3ad chroot: exclude all CHROOT_PATHS from rsync, keep leading slashes 2022-05-05 21:22:53 +02:00
InsanePrawn
560b5bcd45 chroot: fix accidental static Chroot members, add uuid 2022-05-05 20:43:03 +02:00
InsanePrawn
ac99b0bca3 wrapper: fix self.type.capitalise() vs self.type.upper() 2022-05-05 15:54:50 +02:00
InsanePrawn
75e4efe0d7 config.py: satisfy mypy on TypedDict access via runtime string 2022-05-05 14:27:21 +02:00
28 changed files with 661 additions and 66 deletions

View File

@@ -1,6 +1,7 @@
stages:
- check
- build
- deploy
format:
stage: check
@@ -34,3 +35,26 @@ build_docker:
only:
- main
- dev
.docs:
image: "${CI_REGISTRY_IMAGE}:dev"
before_script:
- pacman -Sy --noconfirm python-sphinx-{click,furo}
script:
- (cd docs && make)
- mv docs/html public
artifacts:
paths:
- public
build_docs:
stage: build
extends: .docs
except:
- dev
pages:
stage: deploy
extends: .docs
only:
- dev

View File

@@ -3,11 +3,11 @@
Kupfer Linux bootstrapping tool - drives pacstrap, makepkg, mkfs and fastboot, just to name a few.
## Installation
Install Docker, Python 3 with libraries `click`, `appdirs`, `joblib`, `toml` and put `bin/` into your `PATH`.
Install Docker, Python 3 with libraries `click`, `appdirs`, `joblib`, `toml`, `typing_extentions`, and `coloredlogs` and put `bin/` into your `PATH`.
Then use `kupferbootstrap`.
## Usage
1. Initialise config with defaults: `kupferbootstrap config init -N`
1. Initialize config with defaults: `kupferbootstrap config init -N`
1. Configure your device profile: `kupferbootstrap config profile init`
1. Build an image and packages along the way: `kupferbootstrap image build`

View File

@@ -8,7 +8,6 @@ from wrapper import enforce_wrap
from .abstract import Chroot
from .base import get_base_chroot
from .build import get_build_chroot, BuildChroot
#from .device import get_device_chroot, DeviceChroot
from .helpers import get_chroot_path
# export Chroot class
@@ -59,4 +58,3 @@ def cmd_chroot(type: str = 'build', arch: str = None, enable_crossdirect=True):
chroot.activate()
logging.debug(f'Starting shell in {chroot.name}:')
chroot.run_cmd('bash', attach_tty=True)
chroot.run_cmd('bash', attach_tty=True)

View File

@@ -5,6 +5,7 @@ import subprocess
from copy import deepcopy
from shlex import quote as shell_quote
from typing import Protocol, Union, Optional, Mapping
from uuid import uuid4
from config import config
from constants import Arch, CHROOT_PATHS
@@ -20,11 +21,11 @@ class AbstractChroot(Protocol):
arch: Arch
path: str
copy_base: bool
initialized: bool = False
active: bool = False
initialized: bool
active: bool
active_mounts: list[str]
extra_repos: Mapping[str, RepoInfo]
base_packages: list[str] = ['base']
base_packages: list[str]
def __init__(
self,
@@ -88,9 +89,12 @@ class Chroot(AbstractChroot):
base_packages: list[str] = ['base', 'base-devel', 'git'],
path_override: str = None,
):
self.uuid = uuid4()
if copy_base is None:
logging.debug(f'{name}: copy_base is none!')
copy_base = (name == base_chroot_name(arch))
self.active = False
self.initialized = False
self.active_mounts = list[str]()
self.name = name
self.arch = arch
@@ -112,11 +116,12 @@ class Chroot(AbstractChroot):
if self.initialized and not reset:
# chroot must have been initialized already!
if fail_if_initialized:
raise Exception(f"Chroot {self.name} is already initialized, this seems like a bug")
raise Exception(f"Chroot {self.name} ({self.uuid}) is already initialized, this seems like a bug")
logging.debug(f"Base chroot {self.name} ({self.uuid}) already initialized")
return
active_previously = self.active
self.deactivate_core()
self.deactivate(fail_if_inactive=False, ignore_rootfs=True)
self.create_rootfs(reset, pacman_conf_target, active_previously)
@@ -199,16 +204,17 @@ class Chroot(AbstractChroot):
# additional mounts like crossdirect are intentionally left intact. Is such a chroot still `active` afterwards?
self.active = False
def deactivate(self, fail_if_inactive: bool = False):
def deactivate(self, fail_if_inactive: bool = False, ignore_rootfs: bool = False):
if not self.active:
if fail_if_inactive:
raise Exception(f"Chroot {self.name} not activated, can't deactivate!")
self.umount_many(self.active_mounts)
self.umount_many([mnt for mnt in self.active_mounts if mnt not in ['/', '/boot'] or not ignore_rootfs])
self.active = False
def run_cmd(
self,
script: Union[str, list[str]],
user: str = 'root',
inner_env: dict[str, str] = {},
outer_env: dict[str, str] = os.environ.copy() | {'QEMU_LD_PREFIX': '/usr/aarch64-linux-gnu'},
attach_tty: bool = False,
@@ -222,6 +228,7 @@ class Chroot(AbstractChroot):
if outer_env is None:
outer_env = os.environ.copy()
env_cmd = ['/usr/bin/env'] + [f'{shell_quote(key)}={shell_quote(value)}' for key, value in inner_env.items()]
su_cmd = []
kwargs: dict = {
'env': outer_env,
}
@@ -232,7 +239,9 @@ class Chroot(AbstractChroot):
script = ' '.join(script)
if cwd:
script = f"cd {shell_quote(cwd)} && ( {script} )"
cmd = ['chroot', self.path] + env_cmd + [
if user != 'root':
su_cmd = ['su', user, '--']
cmd = ['chroot', self.path] + su_cmd + env_cmd + [
'/bin/bash',
'-c',
script,
@@ -347,10 +356,13 @@ def get_chroot(
) -> Chroot:
global chroots
if default and name not in chroots:
logging.debug(f'Adding chroot {name} to chroot map')
logging.debug(f'Adding chroot {name} to chroot map: {default.uuid}')
chroots[name] = default
elif fail_if_exists:
raise Exception(f'chroot {name} already exists')
else:
existing = chroots[name]
if fail_if_exists:
raise Exception(f'chroot {name} already exists: {existing.uuid}')
logging.debug(f"returning existing chroot {name}: {existing.uuid}")
chroot = chroots[name]
if extra_repos is not None:
chroot.extra_repos = dict(extra_repos) # copy to new dict

View File

@@ -24,20 +24,12 @@ class BuildChroot(Chroot):
raise Exception('base_chroot == self, bailing out. this is a bug')
base_chroot.initialize()
logging.info(f'Copying {base_chroot.name} chroot to {self.name}')
result = subprocess.run([
'rsync',
'-a',
'--delete',
'-q',
'-W',
'-x',
'--exclude',
CHROOT_PATHS['pkgbuilds'].strip('/'),
'--exclude',
CHROOT_PATHS['packages'].strip('/'),
f'{base_chroot.path}/',
f'{self.path}/',
])
cmd = ['rsync', '-a', '--delete', '-q', '-W', '-x']
for mountpoint in CHROOT_PATHS.values():
cmd += ['--exclude', mountpoint.rstrip('/')]
cmd += [f'{base_chroot.path}/', f'{self.path}/']
logging.debug(f"running rsync: {cmd}")
result = subprocess.run(cmd)
if result.returncode != 0:
raise Exception(f'Failed to copy {base_chroot.name} to {self.name}')

View File

@@ -6,6 +6,8 @@ import logging
from copy import deepcopy
from typing import Optional, Union, TypedDict, Any, Mapping
from constants import DEFAULT_PACKAGE_BRANCH
CONFIG_DIR = appdirs.user_config_dir('kupfer')
CACHE_DIR = appdirs.user_cache_dir('kupfer')
@@ -51,10 +53,11 @@ CONFIG_DEFAULTS: dict = {
},
'pkgbuilds': {
'git_repo': 'https://gitlab.com/kupfer/packages/pkgbuilds.git',
'git_branch': 'dev',
'git_branch': DEFAULT_PACKAGE_BRANCH,
},
'pacman': {
'parallel_downloads': 4,
'repo_branch': DEFAULT_PACKAGE_BRANCH,
},
'paths': {
'cache_dir': CACHE_DIR,
@@ -144,9 +147,9 @@ def resolve_profile(
# now init missing keys
for key, value in PROFILE_DEFAULTS.items():
if key not in full.keys():
full[key] = None # type: ignore[misc]
full[key] = None # type: ignore[literal-required]
if type(value) == list:
full[key] = [] # type: ignore[misc]
full[key] = [] # type: ignore[literal-required]
full['size_extra_mb'] = int(full['size_extra_mb'] or 0)

View File

@@ -17,6 +17,7 @@ JUMPDRIVE_VERSION = '0.8'
BOOT_STRATEGIES: dict[str, str] = {
'oneplus-enchilada': FASTBOOT,
'oneplus-fajita': FASTBOOT,
'xiaomi-beryllium-ebbg': FASTBOOT,
'xiaomi-beryllium-tianma': FASTBOOT,
'bq-paella': FASTBOOT,
@@ -24,6 +25,7 @@ BOOT_STRATEGIES: dict[str, str] = {
DEVICES: dict[str, list[str]] = {
'oneplus-enchilada': ['device-sdm845-oneplus-enchilada'],
'oneplus-fajita': ['device-sdm845-oneplus-fajita'],
'xiaomi-beryllium-ebbg': ['device-sdm845-xiaomi-beryllium-ebbg'],
'xiaomi-beryllium-tianma': ['device-sdm845-xiaomi-beryllium-tianma'],
'bq-paella': ['device-msm8916-bq-paella'],
@@ -58,7 +60,7 @@ FLAVOURS: dict[str, Flavour] = {
'phosh': {
'packages': [
'phosh',
# 'squeekboard', #temporarily disabled
'phosh-osk-stub', # temporary replacement for 'squeekboard',
'gnome-control-center',
'gnome-software',
'gnome-software-packagekit-plugin',
@@ -82,7 +84,8 @@ REPOSITORIES = [
'phosh',
]
KUPFER_HTTPS = 'https://gitlab.com/kupfer/packages/prebuilts/-/raw/main/$repo'
DEFAULT_PACKAGE_BRANCH = 'dev'
KUPFER_HTTPS = 'https://gitlab.com/kupfer/packages/prebuilts/-/raw/%branch%/$arch/$repo'
Arch: TypeAlias = str
ARCHES = [

View File

@@ -35,7 +35,7 @@ class Distro:
def repos_config_snippet(self, extra_repos: Mapping[str, RepoInfo] = {}) -> str:
extras = [Repo(name, url_template=info.url_template, arch=self.arch, options=info.options, scan=False) for name, info in extra_repos.items()]
return '\n\n'.join(repo.config_snippet() for repo in (list(self.repos.values()) + extras))
return '\n\n'.join(repo.config_snippet() for repo in (extras + list(self.repos.values())))
def get_pacman_conf(self, extra_repos: Mapping[str, RepoInfo] = {}, check_space: bool = True):
body = generate_pacman_conf_body(self.arch, check_space=check_space)
@@ -56,7 +56,7 @@ def get_kupfer(arch: str, url_template: str) -> Distro:
def get_kupfer_https(arch: str) -> Distro:
return get_kupfer(arch, KUPFER_HTTPS)
return get_kupfer(arch, KUPFER_HTTPS.replace('%branch%', config.file['pacman']['repo_branch']))
def get_kupfer_local(arch: Optional[str] = None, in_chroot: bool = True) -> Distro:

4
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.buildinfo
.doctrees
html
source/cli

16
docs/Makefile Normal file
View File

@@ -0,0 +1,16 @@
buildargs := -b dirhtml -aE source html
.PHONY: cleanbuild clean
cleanbuild:
@make clean
@make html
clean:
rm -rf html source/cli
html:
sphinx-build $(buildargs)
serve: html
(cd html && python -m http.server 9999)

3
docs/requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
sphinx-click
# furo sphinx theme
furo

17
docs/source/cli.rst Normal file
View File

@@ -0,0 +1,17 @@
#############
CLI Interface
#############
.. click:: main:cli
:nested: none
:prog: kupferbootstrap
Commands
========
.. generated by cmd.rst
.. toctree::
:glob:
cli/*

21
docs/source/cmd.rst Normal file
View File

@@ -0,0 +1,21 @@
:orphan:
:nosearch:
only used to trigger builds of the submodule docs!
.. autosummary::
:toctree: cli
:template: command.rst
:recursive:
boot
cache
chroot
config
flash
forwarding
image
packages
ssh
telnet

21
docs/source/conf.py Normal file
View File

@@ -0,0 +1,21 @@
import os
import sys
sys.path.insert(0, os.path.abspath('../..'))
extensions = [
'sphinx_click',
'sphinx.ext.autosummary', # Create neat summary tables
]
templates_path = ['templates']
project = 'Kupfer👢strap'
html_title = 'Kupferbootstrap'
html_theme = 'furo'
html_static_path = ['static']
html_css_files = ['kupfer_docs.css']
html_favicon = 'static/kupfer-white-filled.svg'
html_theme_options = {
"globaltoc_maxdepth": 5,
"globaltoc_collapse": True,
"light_logo": "kupfer-black-transparent.svg",
"dark_logo": "kupfer-white-transparent.svg",
}

129
docs/source/config.rst Normal file
View File

@@ -0,0 +1,129 @@
#############
Configuration
#############
Kupferbootstrap uses `toml <https://en.wikipedia.org/wiki/TOML>`_ for its configuration file.
The file can either be edited manually or managed via the :doc:`cli/config` subcommand.
You can quickly generate a default config by running :code:`kupferbootstrap config init -N`.
File Location
#############
The configuration is stored in ``~/.config/kupfer/kupferbootstrap.toml``, where ``~`` is your user's home folder.
Sections
########
A config file is split into sections like so:
.. code-block:: toml
[pkgbuilds]
git_repo = "https://gitlab.com/kupfer/packages/pkgbuilds.git"
git_branch = "dev"
[pacman]
parallel_downloads = 3
Here, we have two sections: ``pkgbuilds`` and ``pacman``.
Flavours
########
Flavours are preset collections of software and functionality to enable,
i.e. desktop environments like `Gnome <https://en.wikipedia.org/wiki/GNOME>`_
and `Phosh <https://en.wikipedia.org/wiki/Phosh>`_.
Profiles
########
The last section and currently the only one with subsections is the ``profiles`` section.
A profile is the configuration of a specific device image. It specifies (amongst others):
* the device model
* the flavour (desktop environment)
* the host- and user name
* extra packages to install
Using a profile's ``parent`` key,
you can inherit settings from another profile.
This allows you to easily keep a number of slight variations of the same target profile around
without the need to constantly modify your Kupferbootstrap configuration file.
You can easily create new profiles with
`kupferbootstrap config profile init <../cli/config/#kupferbootstrap-config-profile-init>`_.
Here's an example:
.. code:: toml
[profiles]
current = "graphical"
[profiles.default]
parent = ""
device = "oneplus-enchilada"
flavour = "phosh"
pkgs_include = [ "wget", "rsync", "nano", "tmux", "zsh", "pv", ]
pkgs_exclude = []
hostname = "kupferphone"
username = "prawn"
size_extra_mb = 800
[profiles.graphical]
parent = "default"
pkgs_include = [ "firefox", "tilix", "gnome-tweaks" ]
size_extra_mb = "+3000"
[profiles.hades]
parent = "graphical"
flavour = "phosh"
hostname = "hades"
[profiles.recovery]
parent = "default"
flavour = "debug-shell"
[profiles.beryllium]
parent = "graphical"
device = "xiaomi-beryllium-ebbg"
flavour = "gnome"
hostname = "pocof1"
The ``current`` key in the ``profiles`` section controlls which profile gets used by Kupferbootstrap by default.
The first subsection (``profiles.default``) describes the `default` profile
which gets created by `config init <../cli/config/#kupferbootstrap-config-init>`_.
Next, we have a `graphical` profile that defines a couple of graphical programs for all but the `recovery` profile,
since that doesn't have a GUI.
``size_extra_mb``
-----------------
Note how ``size_extra_mb`` can either be a plain integer (``800``) or a string,
optionally leading with a plus sign (``+3000``),
which instructs Kupferbootstrap to add the value to the parent profile's ``size_extra_mb``.
``pkgs_include`` / ``pkgs_exclude``
-----------------------------------
Like ``size_extra_mb``, ``pkgs_include`` will be merged with the parent profile's ``pkgs_include``.
To exclude unwanted packages from being inherited from a parent profile, use ``pkgs_exclude`` in the child profile.
.. hint::
``pkgs_exclude`` has no influence on Pacman's dependency resolution.
It only blocks packages during image build that would usually be explicitly installed
due to being listed in a parent profile or the selected flavour.

16
docs/source/index.rst Normal file
View File

@@ -0,0 +1,16 @@
#############################
Kupferbootstrap Documentation
#############################
This is the documentation for `Kupferbootstrap <https://gitlab.com/kupfer/kupferbootstrap>`_,
a tool to build and flash packages and images for the `Kupfer <https://gitlab.com/kupfer/>`_ mobile Linux distro.
Documentation pages
===================
.. toctree::
install
config
cli

35
docs/source/install.rst Normal file
View File

@@ -0,0 +1,35 @@
############
Installation
############
#.
Install Python 3, Docker, and git.
On Arch: ``pacman -S python docker git --needed --noconfirm``
.. Hint::
After installing Docker you will have to add your user to the ``docker`` group:
``sudo usermod -aG docker "$(whoami)"``
Then restart your desktop session for the new group to take effect.
#. Pick which Kupferbootstrap branch to clone: usually either ``main`` or ``dev``
#. Clone the repository: ``git clone -b INSERT_BRANCHNAME_HERE https://gitlab.com/kupfer/kupferbootstrap``
#. Change into the folder: ``cd kupferbootstrap``
#.
Install python dependencies: ``pip3 install -r requirements.txt``
.. Note::
Most of our python dependencies are available as distro packages on most distros,
sadly it's incomplete on Arch.
See ``requirements.txt`` for the list of required python packages.
#. Symlink ``kupferbootstrap`` into your ``$PATH``: ``sudo ln -s "$(pwd)/bin/kupferbootstrap" /usr/local/bin/``
#. You should now be able to run ``kupferbootstrap --help``!

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="190"
height="190"
viewBox="0 0 190 190"
version="1.1"
id="svg5"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2">
<linearGradient
id="linearGradient2922">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop2918" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop2920" />
</linearGradient>
<rect
x="13.627879"
y="59.548416"
width="111.21325"
height="97.633041"
id="rect5030" />
<linearGradient
xlink:href="#linearGradient2922"
id="linearGradient2924"
x1="90.118146"
y1="164.56091"
x2="170.81263"
y2="164.56091"
gradientUnits="userSpaceOnUse" />
</defs>
<g
id="layer2"
style="display:none">
<rect
style="fill:#343a40;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.04836;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect53-7"
width="184.064"
height="184.064"
x="3.0180202"
y="3.0180202"
ry="15.325292" />
</g>
<g
id="layer1">
<path
style="color:#000000;fill:#000000;fill-rule:evenodd;stroke-linejoin:round;-inkscape-stroke:hairline;fill-opacity:1;stroke:#ffffff;stroke-opacity:1;stroke-width:0.000001;vector-effect:non-scaling-stroke;stroke-miterlimit:4;stroke-dasharray:none"
d="M 19.966797,4 C 11.138816,4 4,11.138816 4,19.966797 V 169.78516 c 0,8.82798 7.138816,15.96679 15.966797,15.96679 H 169.73242 c 8.82798,0 15.9668,-7.13881 15.9668,-15.96679 V 19.966797 C 185.69922,11.138816 178.5604,4 169.73242,4 Z m 0,2 H 169.73242 c 7.75458,0 13.9668,6.21222 13.9668,13.966797 V 169.78516 c 0,7.75457 -6.21222,13.96679 -13.9668,13.96679 H 19.966797 C 12.21222,183.75195 6,177.53973 6,169.78516 V 19.966797 C 6,12.21222 12.21222,6 19.966797,6 Z"
id="rect53" />
<text
xml:space="preserve"
id="text5028"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:55.9664px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;shape-inside:url(#rect5030);fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;vector-effect:non-scaling-stroke;-inkscape-stroke:hairline"
transform="matrix(1.7767576,0,0,1.5652748,1.1199194,-51.120758)"><tspan
x="13.626953"
y="111.31775"
id="tspan42"><tspan
style="vector-effect:non-scaling-stroke"
id="tspan40">Cu</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal;vector-effect:non-scaling-stroke;-inkscape-stroke:hairline"
x="15.241241"
y="34.91935"
id="text66922"><tspan
id="tspan66920"
x="15.241241"
y="34.91935"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;vector-effect:non-scaling-stroke;-inkscape-stroke:hairline">29</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
x="91.402611"
y="168.75438"
id="text66922-3"><tspan
id="tspan66920-6"
x="91.402611"
y="168.75438"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">63.546</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="190"
height="190"
viewBox="0 0 190 190"
version="1.1"
id="svg5"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2">
<linearGradient
id="linearGradient2922">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop2918" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop2920" />
</linearGradient>
<rect
x="13.627879"
y="59.548416"
width="111.21325"
height="97.633041"
id="rect5030" />
<linearGradient
xlink:href="#linearGradient2922"
id="linearGradient2924"
x1="90.118146"
y1="164.56091"
x2="170.81263"
y2="164.56091"
gradientUnits="userSpaceOnUse" />
</defs>
<g
id="layer2"
style="display:inline">
<rect
style="display:inline;fill:#343a40;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.04836;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect53-7"
width="184.064"
height="184.064"
x="3.0180202"
y="3.0180202"
ry="17" />
</g>
<g
id="layer1">
<path
style="color:#000000;fill:#ffffff;fill-rule:evenodd;stroke-linejoin:round;-inkscape-stroke:hairline;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.000001;vector-effect:non-scaling-stroke;stroke-miterlimit:4;stroke-dasharray:none"
d="M 19.966797,4 C 11.138816,4 4,11.138816 4,19.966797 V 169.78516 c 0,8.82798 7.138816,15.96679 15.966797,15.96679 H 169.73242 c 8.82798,0 15.9668,-7.13881 15.9668,-15.96679 V 19.966797 C 185.69922,11.138816 178.5604,4 169.73242,4 Z m 0,2 H 169.73242 c 7.75458,0 13.9668,6.21222 13.9668,13.966797 V 169.78516 c 0,7.75457 -6.21222,13.96679 -13.9668,13.96679 H 19.966797 C 12.21222,183.75195 6,177.53973 6,169.78516 V 19.966797 C 6,12.21222 12.21222,6 19.966797,6 Z"
id="rect53" />
<text
xml:space="preserve"
id="text5028"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:55.9664px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;shape-inside:url(#rect5030);display:inline;vector-effect:non-scaling-stroke;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;-inkscape-stroke:hairline"
transform="matrix(1.7767576,0,0,1.5652748,1.1199194,-51.120758)"><tspan
x="13.626953"
y="110.47127"
id="tspan42"><tspan
style="vector-effect:non-scaling-stroke"
id="tspan40">Cu</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal;vector-effect:non-scaling-stroke;-inkscape-stroke:hairline"
x="15.241241"
y="34.91935"
id="text66922"><tspan
id="tspan66920"
x="15.241241"
y="34.91935"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;vector-effect:non-scaling-stroke;-inkscape-stroke:hairline">29</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
x="91.402611"
y="168.75438"
id="text66922-3"><tspan
id="tspan66920-6"
x="91.402611"
y="168.75438"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">63.546</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="190"
height="190"
viewBox="0 0 190 190"
version="1.1"
id="svg5"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2">
<linearGradient
id="linearGradient2922">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop2918" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop2920" />
</linearGradient>
<rect
x="13.627879"
y="59.548416"
width="111.21325"
height="97.633041"
id="rect5030" />
<linearGradient
xlink:href="#linearGradient2922"
id="linearGradient2924"
x1="90.118146"
y1="164.56091"
x2="170.81263"
y2="164.56091"
gradientUnits="userSpaceOnUse" />
</defs>
<g
id="layer2"
style="display:none">
<rect
style="fill:#343a40;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.04836;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect53-7"
width="184.064"
height="184.064"
x="3.0180202"
y="3.0180202"
ry="15.325292" />
</g>
<g
id="layer1">
<path
style="color:#000000;fill:#ffffff;fill-rule:evenodd;stroke-linejoin:round;-inkscape-stroke:hairline;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.000001;vector-effect:non-scaling-stroke;stroke-miterlimit:4;stroke-dasharray:none"
d="M 19.966797,4 C 11.138816,4 4,11.138816 4,19.966797 V 169.78516 c 0,8.82798 7.138816,15.96679 15.966797,15.96679 H 169.73242 c 8.82798,0 15.9668,-7.13881 15.9668,-15.96679 V 19.966797 C 185.69922,11.138816 178.5604,4 169.73242,4 Z m 0,2 H 169.73242 c 7.75458,0 13.9668,6.21222 13.9668,13.966797 V 169.78516 c 0,7.75457 -6.21222,13.96679 -13.9668,13.96679 H 19.966797 C 12.21222,183.75195 6,177.53973 6,169.78516 V 19.966797 C 6,12.21222 12.21222,6 19.966797,6 Z"
id="rect53" />
<text
xml:space="preserve"
id="text5028"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:55.9664px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;shape-inside:url(#rect5030);fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;vector-effect:non-scaling-stroke;-inkscape-stroke:hairline"
transform="matrix(1.7767576,0,0,1.5652748,1.1199194,-51.120758)"><tspan
x="13.626953"
y="111.31775"
id="tspan42"><tspan
style="vector-effect:non-scaling-stroke"
id="tspan40">Cu</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal;vector-effect:non-scaling-stroke;-inkscape-stroke:hairline"
x="15.241241"
y="34.91935"
id="text66922"><tspan
id="tspan66920"
x="15.241241"
y="34.91935"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;vector-effect:non-scaling-stroke;-inkscape-stroke:hairline">29</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
x="91.402611"
y="168.75438"
id="text66922-3"><tspan
id="tspan66920-6"
x="91.402611"
y="168.75438"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">63.546</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1,3 @@
.sidebar-brand-text {
text-align: center;
}

View File

@@ -0,0 +1,5 @@
.. title: {{fullname}}
.. click:: {% if fullname == 'main' %}main:cli{% else %}{{fullname}}:cmd_{{fullname}}{% endif %}
:prog: kupferbootstrap {{fullname}}
:nested: full

View File

@@ -18,9 +18,9 @@ ROOTFS = FLASH_PARTS['ROOTFS']
@click.command(name='flash')
@click.argument('what', type=click.Choice(list(FLASH_PARTS.values())))
@click.argument('location', required=False, type=click.Choice(LOCATIONS))
def cmd_flash(what, location):
"""Flash a partition onto a device"""
@click.argument('location', type=str, required=False)
def cmd_flash(what: str, location: str):
"""Flash a partition onto a device. `location` takes either a path to a block device or one of emmc, sdcard"""
enforce_wrap()
device, flavour = get_device_and_flavour()
device_image_name = get_image_name(device, flavour)

View File

@@ -11,7 +11,7 @@ from typing import Optional
from chroot.device import DeviceChroot, get_device_chroot
from constants import Arch, BASE_PACKAGES, DEVICES, FLAVOURS
from config import config, Profile
from config import config, Profile, PROFILE_DEFAULTS
from distro.distro import get_base_distro, get_kupfer_https
from packages import build_enable_qemu_binfmt, discover_packages, build_packages
from ssh import copy_ssh_keys
@@ -23,7 +23,7 @@ IMG_FILE_BOOT_DEFAULT_SIZE = "90M"
def dd_image(input: str, output: str, blocksize='1M') -> CompletedProcess:
return subprocess.run([
cmd = [
'dd',
f'if={input}',
f'of={output}',
@@ -32,7 +32,9 @@ def dd_image(input: str, output: str, blocksize='1M') -> CompletedProcess:
'oflag=direct',
'status=progress',
'conv=sync,noerror',
])
]
logging.debug(f'running dd cmd: {cmd}')
return subprocess.run(cmd)
def partprobe(device: str):
@@ -305,9 +307,11 @@ def install_rootfs(
packages: list[str],
use_local_repos: bool,
profile: Profile,
kupfer_config_apply: bool = True,
):
user = profile['username'] or 'kupfer'
post_cmds = FLAVOURS[flavour].get('post_cmds', [])
packages += ['kupfer-config'] if kupfer_config_apply else []
profile = PROFILE_DEFAULTS | profile
user = profile['username']
chroot = get_device_chroot(device=device, flavour=flavour, arch=arch, packages=packages, use_local_repos=use_local_repos)
mount_chroot(rootfs_device, bootfs_device, chroot)
@@ -330,11 +334,12 @@ def install_rootfs(
for target, content in files.items():
with open(os.path.join(chroot.path, target.lstrip('/')), 'w') as file:
file.write(content)
if post_cmds:
result = chroot.run_cmd(' && '.join(post_cmds))
if kupfer_config_apply:
result = chroot.run_cmd(['kupfer-config', 'apply'])
assert isinstance(result, subprocess.CompletedProcess)
if result.returncode != 0:
raise Exception('Error running post_cmds')
raise Exception('Error running kupfer-config apply')
logging.info('Preparing to unmount chroot')
res = chroot.run_cmd('sync && umount /boot', attach_tty=True)
@@ -353,10 +358,12 @@ def cmd_image():
@cmd_image.command(name='build')
@click.argument('profile_name', required=False)
@click.option('--build-pkgs/--no-build-pkgs', '-p/-P', default=True, help='Whether to build missing/outdated packages. Defaults to true.')
@click.option('--local-repos/--no-local-repos', '-l/-L', is_flag=True, default=True, help='Whether to use local packages. Defaults to true.')
@click.option('--build-pkgs/--no-build-pkgs', '-p/-P', is_flag=True, default=True, help='Whether to build missing/outdated local packages. Defaults to true.')
@click.option('--block-target', default=None, help='Override the block device file to target')
@click.option('--skip-part-images', default=False, help='Skip creating image files for the partitions and directly work on the target block device.')
def cmd_build(profile_name: str = None, build_pkgs: bool = True, block_target: str = None, skip_part_images: bool = False):
@click.option('--no-kupfer-config-apply', is_flag=True, help='skip applying kupfer-config, which mainly enables services according to the image flavor')
def cmd_build(profile_name: str = None, local_repos: bool = True, build_pkgs: bool = True, block_target: str = None, skip_part_images: bool = False, no_kupfer_config_apply: bool = False):
"""Build a device image"""
enforce_wrap()
profile: Profile = config.get_profile(profile_name)
@@ -368,13 +375,13 @@ def cmd_build(profile_name: str = None, build_pkgs: bool = True, block_target: s
sector_size = 4096
rootfs_size_mb = FLAVOURS[flavour].get('size', 2) * 1000
build_enable_qemu_binfmt(arch)
packages = BASE_PACKAGES + DEVICES[device] + FLAVOURS[flavour]['packages'] + profile['pkgs_include'] + ([] if no_kupfer_config_apply else ['kupfer-config'])
packages_dir = config.get_package_dir(arch)
use_local_repos = os.path.exists(os.path.join(packages_dir, 'main'))
packages = BASE_PACKAGES + DEVICES[device] + FLAVOURS[flavour]['packages'] + profile['pkgs_include']
if arch != config.runtime['arch']:
build_enable_qemu_binfmt(arch)
if build_pkgs:
if local_repos and build_pkgs:
logging.info("Making sure all packages are built")
repo = discover_packages()
build_packages(repo, [p for name, p in repo.items() if name in packages], arch)
@@ -412,7 +419,7 @@ def cmd_build(profile_name: str = None, build_pkgs: bool = True, block_target: s
flavour,
arch,
packages,
use_local_repos,
local_repos,
profile,
)

View File

@@ -1,15 +1,20 @@
import click
import coloredlogs
import logging
import sys
def setup_logging(verbose: bool):
level_colors = coloredlogs.DEFAULT_LEVEL_STYLES | {'info': {'color': 'magenta', 'bright': True}, 'debug': {'color': 'blue', 'bright': True}}
field_colors = coloredlogs.DEFAULT_FIELD_STYLES | {'asctime': {'color': 'white', 'faint': True}}
level = logging.DEBUG if verbose else logging.INFO
logging.basicConfig(
coloredlogs.install(
stream=sys.stdout,
format='%(asctime)s %(levelname)s: %(message)s',
datefmt='%m/%d/%Y %H:%M:%S',
fmt='%(asctime)s %(levelname)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=level,
level_styles=level_colors,
field_styles=field_colors,
)
logging.debug('Logging set up.')

View File

@@ -31,7 +31,7 @@ pacman_cmd = [
def get_makepkg_env():
# has to be a function because calls to `config` must be done after config file was read
threads = config.file['build']['threads'] or multiprocessing.cpu_count()
return os.environ.copy() | {
return {key: val for key, val in os.environ.items() if not key.split('_', maxsplit=1)[0] in ['CI', 'GITLAB', 'FF']} | {
'LANG': 'C',
'CARGO_BUILD_JOBS': str(threads),
'MAKEFLAGS': f"-j{threads}",
@@ -553,13 +553,13 @@ def build_packages_by_paths(
def build_enable_qemu_binfmt(arch: Arch, repo: dict[str, Pkgbuild] = None):
if arch not in ARCHES:
raise Exception(f'Unknown architecture "{arch}". Choices: {", ".join(ARCHES)}')
logging.info('Installing qemu-user (building if necessary)')
enforce_wrap()
if not repo:
repo = discover_packages()
native = config.runtime['arch']
# build qemu-user, binfmt, crossdirect
chroot = setup_build_chroot(native)
logging.info('Installing qemu-user (building if necessary)')
build_packages_by_paths(
['cross/' + pkg for pkg in CROSSDIRECT_PKGS],
native,
@@ -647,12 +647,14 @@ def cmd_sideload(paths: Iterable[str]):
@click.option('-n', '--noop', is_flag=True, default=False, help="Print what would be removed but dont execute")
@click.argument('what', type=click.Choice(['all', 'src', 'pkg']), nargs=-1)
def cmd_clean(what: Iterable[str] = ['all'], force: bool = False, noop: bool = False):
"""Remove files and directories not tracked in PKGBUILDs.git"""
"""Remove files and directories not tracked in PKGBUILDs.git. Passing in an empty `what` defaults it to `['all']`"""
enforce_wrap()
if noop:
logging.debug('Running in noop mode!')
if force:
logging.debug('Running in FORCE mode!')
what = what or ['all']
logging.debug(f'Clearing {what} from PKGBUILDs')
pkgbuilds = config.get_path('pkgbuilds')
if 'all' in what:
warning = "Really reset PKGBUILDs to git state completely?\nThis will erase any untracked changes to your PKGBUILDs directory."
@@ -677,7 +679,7 @@ def cmd_clean(what: Iterable[str] = ['all'], force: bool = False, noop: bool = F
dirs += glob(os.path.join(pkgbuilds, '*', '*', loc))
dir_lines = '\n'.join(dirs)
verb = 'Would remove' if noop or force else 'Removing'
verb = 'Would remove' if noop else 'Removing'
logging.info(verb + ' directories:\n' + dir_lines)
if not (noop or force):
@@ -706,6 +708,14 @@ def cmd_list():
def cmd_check(paths):
"""Check that specified PKGBUILDs are formatted correctly"""
enforce_wrap()
def check_quoteworthy(s: str) -> bool:
quoteworthy = ['"', "'", "$", " ", ";", "&", "<", ">", "*", "?"]
for symbol in quoteworthy:
if symbol in s:
return True
return False
paths = list(paths)
packages = filter_packages_by_paths(discover_packages(), paths, allow_empty_results=False)
@@ -812,11 +822,11 @@ def cmd_check(paths):
formatted = False
reason = 'Multiline variables should be indented with 4 spaces'
if '"' in line and '$' not in line and ' ' not in line and ';' not in line:
if '"' in line and not check_quoteworthy(line):
formatted = False
reason = 'Found literal " although no "$", " " or ";" was found in the line justifying the usage of a literal "'
reason = 'Found literal " although no special character was found in the line to justify the usage of a literal "'
if '\'' in line:
if "'" in line and not '"' in line:
formatted = False
reason = 'Found literal \' although either a literal " or no qoutes should be used'

View File

@@ -3,3 +3,4 @@ appdirs==1.4.4
joblib==1.0.1
toml
typing_extensions
coloredlogs

View File

@@ -93,7 +93,7 @@ class BaseWrapper(Wrapper):
raise NotImplementedError()
def is_wrapped(self):
return os.getenv('KUPFERBOOTSTRAP_WRAPPED') == self.type.capitalize()
return os.getenv('KUPFERBOOTSTRAP_WRAPPED') == self.type.upper()
def get_bind_mounts_default(self, wrapped_config_path: str = None, ssh_dir: str = None, target_home: str = '/root'):
wrapped_config_path = wrapped_config_path or self.wrapped_config_path