mirror of
https://gitlab.postmarketos.org/postmarketOS/pmbootstrap.git
synced 2025-07-13 03:19:47 +03:00
Introduce a new "cache" subdirectory in the pmbootstrap workdir, all the cache and config bits go in here, anything that needs to be accessible from inside a chroot. The whole dir is then bind-mounted into the chroot as /cache with appropriate symlinks. This dir is in the config as config.cache. In addition, all the cache_* and other config dirs are renamed to be closer to the names of the equivalent dirs in the chroot (e.g. abuild-config) and to avoid redundant naming since they are now under a "cache" dir. Signed-off-by: Casey Connolly <kcxt@postmarketos.org>
202 lines
6.7 KiB
Python
202 lines
6.7 KiB
Python
# Copyright 2023 Oliver Smith
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
from pmb.core.context import get_context
|
|
from pmb.core.pkgrepo import pkgrepo_default_path
|
|
from pmb.helpers import logging
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import pmb.chroot
|
|
import pmb.config
|
|
import pmb.config.init
|
|
import pmb.helpers.pmaports
|
|
import pmb.helpers.run
|
|
from typing import Any
|
|
from pmb.helpers.exceptions import NonBugError
|
|
|
|
|
|
def folder_size(path: Path) -> int:
|
|
"""Run `du` to calculate the size of a folder.
|
|
|
|
(this is less code and faster than doing the same task in pure Python)
|
|
This result is only approximately right, but good enough for pmbootstrap's use case (#760).
|
|
|
|
:returns: folder size in kilobytes
|
|
"""
|
|
output = pmb.helpers.run.root(["du", "-ks", path], output_return=True)
|
|
|
|
# Only look at last line to filter out sudo garbage (#1766)
|
|
last_line = output.split("\n")[-2]
|
|
|
|
ret = int(last_line.split("\t")[0])
|
|
return ret
|
|
|
|
|
|
def check_grsec() -> None:
|
|
"""Check if the current kernel is based on the grsec patchset.
|
|
|
|
Also check if the chroot_deny_chmod option is enabled.
|
|
Raise an exception in that case, with a link to the issue. Otherwise, do nothing.
|
|
"""
|
|
path = "/proc/sys/kernel/grsecurity/chroot_deny_chmod"
|
|
if not os.path.exists(path):
|
|
return
|
|
|
|
raise RuntimeError(
|
|
"You're running a kernel based on the grsec patchset. This is not supported."
|
|
)
|
|
|
|
|
|
def migrate_success(localdir: Path, version: int) -> None:
|
|
logging.info("Migration to version " + str(version) + " done")
|
|
with open(localdir / "version", "w") as handle:
|
|
handle.write(str(version) + "\n")
|
|
|
|
|
|
def migrate_localdir() -> None:
|
|
# Read current version
|
|
context = get_context()
|
|
current = 0
|
|
suffix: str | None = None
|
|
current_with_suffix = ""
|
|
path = context.config.work / "version"
|
|
if os.path.exists(path):
|
|
with open(path) as f:
|
|
# pmb 2.3.x added a suffix due to conflicting work versions
|
|
# We need to be able to handle that going forward
|
|
current_with_suffix = f.read().rstrip()
|
|
version_parts = current_with_suffix.split("-")
|
|
current = int(version_parts[0])
|
|
if len(version_parts) == 2:
|
|
suffix = version_parts[1]
|
|
|
|
# Compare version, print warning or do nothing
|
|
required = pmb.config.work_version
|
|
if current == required:
|
|
return
|
|
logging.info(
|
|
"WARNING: Your work folder version needs to be migrated"
|
|
f" (from version {current_with_suffix} to {required})!"
|
|
)
|
|
|
|
# version 6 and version 7 from 2.3.x branch are equivalent for this and we need to migrate
|
|
if current == 6 or (current == 7 and suffix == "2.x"):
|
|
# Ask for confirmation
|
|
logging.info("Changelog:")
|
|
logging.info("* Major refactor for pmb 3.0.0")
|
|
logging.info("Migration will do the following:")
|
|
logging.info("* Zap your chroots")
|
|
if not pmb.helpers.cli.confirm():
|
|
raise RuntimeError("Aborted.")
|
|
|
|
# Zap chroots
|
|
pmb.chroot.zap(False)
|
|
|
|
# Update version file
|
|
if suffix == "2.x":
|
|
# If we come from 7-2.x, then we already updated the git urls and
|
|
# can skip the 7->8 migration step
|
|
migrate_success(context.config.work, 8)
|
|
current = 8
|
|
else:
|
|
migrate_success(context.config.work, 7)
|
|
current = 7
|
|
|
|
if current == 7:
|
|
# Ask for confirmation
|
|
logging.info("Changelog:")
|
|
logging.info("* Moved from gitlab.com to gitlab.postmarketOS.org")
|
|
logging.info("Migration will do the following:")
|
|
logging.info("* Update your pmaports remote URL")
|
|
if not pmb.helpers.cli.confirm():
|
|
raise RuntimeError("Aborted.")
|
|
|
|
pmb.helpers.git.migrate_upstream_remote()
|
|
try:
|
|
pmb.helpers.git.get_upstream_remote(pkgrepo_default_path())
|
|
except RuntimeError:
|
|
logging.error(
|
|
"Couldn't find new upstream remote, migration failed."
|
|
" Please try updating the remote manually with:\n"
|
|
f" $ git -C '{pkgrepo_default_path()}' remote set-url origin 'https://gitlab.postmarketos.org/postmarketOS/pmaports.git'"
|
|
)
|
|
raise RuntimeError("Migration failed.")
|
|
|
|
# Update version file
|
|
migrate_success(context.config.work, 8)
|
|
current = 8
|
|
|
|
# Can't migrate, user must delete it
|
|
if current != required:
|
|
raise NonBugError(
|
|
"Sorry, we can't migrate that automatically. Please"
|
|
" run 'pmbootstrap shutdown', then delete your"
|
|
" current localdir manually ('sudo rm -rf "
|
|
f"{context.config.work}') and start over with 'pmbootstrap"
|
|
" init'. All your binary packages and caches will"
|
|
" be lost."
|
|
)
|
|
|
|
|
|
def normalize_hostname(hostname: str) -> str:
|
|
"""Fixup default hostnames so that they don't fail validate_hostname()
|
|
|
|
This should not be called on user-chosen hostnames as those should fail
|
|
"""
|
|
# Truncate length
|
|
if len(hostname) > 63:
|
|
hostname = hostname[:63]
|
|
|
|
# Replace underscores with dashes
|
|
if "_" in hostname:
|
|
hostname = hostname.replace("_", "-")
|
|
|
|
# We shouldn't have to fix the rest of the regex because the APKBUILDs'
|
|
# device names shouldn't have any more invalid characters
|
|
|
|
return hostname
|
|
|
|
|
|
def validate_hostname(hostname: str) -> bool:
|
|
"""Check whether the string is a valid hostname.
|
|
|
|
Check is performed according to
|
|
<http://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names>
|
|
"""
|
|
# Check length
|
|
if len(hostname) > 63:
|
|
logging.fatal("ERROR: Hostname '" + hostname + "' is too long.")
|
|
return False
|
|
|
|
# Check that it only contains valid chars
|
|
if not re.match(r"^[0-9a-z-\.]*$", hostname):
|
|
logging.fatal(
|
|
"ERROR: Hostname must only contain letters (a-z),"
|
|
" digits (0-9), minus signs (-), or periods (.)"
|
|
)
|
|
return False
|
|
|
|
# Check that doesn't begin or end with a minus sign or period
|
|
if re.search(r"^-|^\.|-$|\.$", hostname):
|
|
logging.fatal("ERROR: Hostname must not begin or end with a minus sign or period")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
"""
|
|
pmbootstrap uses this dictionary to save the result of expensive
|
|
results, so they work a lot faster the next time they are needed in the
|
|
same session. Usually the cache is written to and read from in the same
|
|
Python file, with code similar to the following:
|
|
|
|
def lookup(key):
|
|
if key in pmb.helpers.other.cache["mycache"]:
|
|
return pmb.helpers.other.cache["mycache"][key]
|
|
ret = expensive_operation(args, key)
|
|
pmb.helpers.other.cache["mycache"][key] = ret
|
|
return ret
|
|
"""
|
|
cache: dict[str, Any] = {
|
|
"apkindex": {},
|
|
}
|