Compare commits
23 Commits
prawn/pack
...
kupfer-con
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15049285e3 | ||
|
|
d277d926fd | ||
|
|
064f265247 | ||
|
|
0da9feeda0 | ||
|
|
35a79363a4 | ||
|
|
e28239454a | ||
|
|
fc90e30bd7 | ||
|
|
2778038b19 | ||
|
|
e460e7d0be | ||
|
|
6000679817 | ||
|
|
cb95846fb5 | ||
|
|
e288918e58 | ||
|
|
232254948d | ||
|
|
933ffd833c | ||
|
|
42a82a10e8 | ||
|
|
a746e938cd | ||
|
|
b13b00e85b | ||
|
|
6917347219 | ||
|
|
3874b4e626 | ||
|
|
686c94c3ad | ||
|
|
560b5bcd45 | ||
|
|
ac99b0bca3 | ||
|
|
75e4efe0d7 |
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}')
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
4
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.buildinfo
|
||||
.doctrees
|
||||
html
|
||||
source/cli
|
||||
16
docs/Makefile
Normal file
16
docs/Makefile
Normal 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
3
docs/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
sphinx-click
|
||||
# furo sphinx theme
|
||||
furo
|
||||
17
docs/source/cli.rst
Normal file
17
docs/source/cli.rst
Normal 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
21
docs/source/cmd.rst
Normal 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
21
docs/source/conf.py
Normal 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
129
docs/source/config.rst
Normal 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
16
docs/source/index.rst
Normal 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
35
docs/source/install.rst
Normal 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``!
|
||||
90
docs/source/static/kupfer-black-transparent.svg
Normal file
90
docs/source/static/kupfer-black-transparent.svg
Normal 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 |
90
docs/source/static/kupfer-white-filled.svg
Normal file
90
docs/source/static/kupfer-white-filled.svg
Normal 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 |
90
docs/source/static/kupfer-white-transparent.svg
Normal file
90
docs/source/static/kupfer-white-transparent.svg
Normal 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 |
3
docs/source/static/kupfer_docs.css
Normal file
3
docs/source/static/kupfer_docs.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.sidebar-brand-text {
|
||||
text-align: center;
|
||||
}
|
||||
5
docs/source/templates/command.rst
Normal file
5
docs/source/templates/command.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
.. title: {{fullname}}
|
||||
|
||||
.. click:: {% if fullname == 'main' %}main:cli{% else %}{{fullname}}:cmd_{{fullname}}{% endif %}
|
||||
:prog: kupferbootstrap {{fullname}}
|
||||
:nested: full
|
||||
6
flash.py
6
flash.py
@@ -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)
|
||||
|
||||
39
image.py
39
image.py
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
11
logger.py
11
logger.py
@@ -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.')
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -3,3 +3,4 @@ appdirs==1.4.4
|
||||
joblib==1.0.1
|
||||
toml
|
||||
typing_extensions
|
||||
coloredlogs
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user