diff --git a/Makefile b/Makefile index d770e0c..28a67a8 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,20 @@ # BUILDER ?= docker +PLATFORMS ?= linux/amd64 VERSION ?= latest IMAGE ?= weechat +REGISTRY ?= docker.io +REGISTRY_PROJECT ?= weechat +REGISTRY_PROJECT_URL = $(REGISTRY)/$(REGISTRY_PROJECT) +REGISTRY_IMAGE = $(REGISTRY_PROJECT_URL)/$(IMAGE) + +ALPINE_BASE_IMAGE = alpine:3.15 +DEBIAN_BASE_IMAGE = debian:bullseye-slim + +ifeq ($(strip $(PUSH)),true) +PUSH_ARG = "--push" +endif .PHONY: all debian debian-slim alpine alpine-slim @@ -25,22 +37,52 @@ all: debian all-images: debian debian-slim alpine alpine-slim -debian: - ./build.py -b "$(BUILDER)" -d "debian" "$(VERSION)" +debian: weechat-multiarch + ./build.py -b "$(BUILDER)" -p $(PLATFORMS) -r "$(REGISTRY_PROJECT_URL)" $(PUSH_ARG) -d "debian" "$(VERSION)" + +debian-slim: weechat-multiarch + ./build.py -b "$(BUILDER)" -p $(PLATFORMS) -r "$(REGISTRY_PROJECT_URL)" $(PUSH_ARG) -d "debian" --slim "$(VERSION)" -debian-slim: - ./build.py -b "$(BUILDER)" -d "debian" --slim "$(VERSION)" +alpine: weechat-multiarch + ./build.py -b "$(BUILDER)" -p $(PLATFORMS) -r "$(REGISTRY_PROJECT_URL)" $(PUSH_ARG) -d "alpine" "$(VERSION)" -alpine: - ./build.py -b "$(BUILDER)" -d "alpine" "$(VERSION)" +alpine-slim: weechat-multiarch + ./build.py -b "$(BUILDER)" -p $(PLATFORMS) -r "$(REGISTRY_PROJECT_URL)" $(PUSH_ARG) -d "alpine" --slim "$(VERSION)" -alpine-slim: - ./build.py -b "$(BUILDER)" -d "alpine" --slim "$(VERSION)" +weechat-multiarch: + if [ "$(BUILDER)" == "docker" ]; then \ + docker buildx ls | grep weechat-multiarch || docker buildx create --name weechat-multiarch ; \ + docker buildx use weechat-multiarch; \ + fi -test-container: - "$(BUILDER)" run "$(IMAGE)" weechat --version - "$(BUILDER)" run "$(IMAGE)" weechat-headless --version +test-images: + for PLATFORM in $(PLATFORMS); do \ + for TAG in $(VERSION)-{debian,alpine}{,-slim}; do \ + REGISTRY_IMAGE_TAG=$(REGISTRY_IMAGE):$$TAG; \ + echo "Testing $$REGISTRY_IMAGE_TAG for $$PLATFORM platform" ; \ + $(BUILDER) pull --platform $$PLATFORM $$REGISTRY_IMAGE_TAG &>/dev/null; \ + echo -n "weechat --version: "; \ + $(BUILDER) run --rm --platform $$PLATFORM $$REGISTRY_IMAGE_TAG --version; \ + echo -n "weechat-headless --version: "; \ + $(BUILDER) run --rm --platform $$PLATFORM --entrypoint /usr/bin/weechat-headless $$REGISTRY_IMAGE_TAG --version ; \ + $(BUILDER) rmi $$REGISTRY_IMAGE_TAG &>/dev/null; \ + done; \ + done +clean-images: + if [ "$(BUILDER)" == "podman" ]; then \ + buildah rm --all; \ + $(BUILDER) images --format="{{.Repository}}:{{.Tag}}" localhost/$(IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \ + $(BUILDER) images --format="{{.Repository}}:{{.Tag}}" $(REGISTRY_PROJECT)/$(IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \ + $(BUILDER) images --format="{{.Repository}}:{{.Tag}}" $(ALPINE_BASE_IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \ + $(BUILDER) images --format="{{.Repository}}:{{.Tag}}" $(DEBIAN_BASE_IMAGE) | xargs --no-run-if-empty $(BUILDER) rmi -f; \ + $(BUILDER) image prune -f; \ + fi + if [ "$(BUILDER)" == "docker" ]; then \ + $(BUILDER) buildx rm weechat-multiarch; \ + $(BUILDER) rmi moby/buildkit:buildx-stable-1 + fi + lint: flake8 pylint mypy bandit flake8: diff --git a/README.md b/README.md index 3243a97..6fbb583 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,20 @@ The slim version includes all plugins except these ones: - scripting languages: perl, python, ruby, lua, tcl, guile, php - spell +## Supported platforms + +It's possible to build for different linux platforms other than amd64, +the only prerequisite is to have `qemu-user-static` package installed + +- `linux/386` +- `linux/amd64` +- `linux/arm/v6` +- `linux/arm/v7` +- `linux/arm64` +- `linux/ppc64le` +- `linux/s390x` + + ## Install from Docker Hub You can install directly the latest version from the Docker Hub: @@ -72,6 +86,13 @@ Build all images with latest stable version of WeeChat: $ make all-images ``` +Build all images with latest stable version of WeeChat for AMD64 and ARM64 platforms +and push them to the `docker.io/weechat` registry project: + +``` +$ make PLATFORMS="linux/amd64 linux/arm64" REGISTRY="docker.io" REGISTRY_PROJECT="weechat" PUSH=true all-images +``` + Build an Alpine-based image with Podman, slim version, WeeChat 3.6: ``` diff --git a/alpine/Containerfile b/alpine/Containerfile index 6de6dd7..7d7540c 100644 --- a/alpine/Containerfile +++ b/alpine/Containerfile @@ -18,21 +18,12 @@ ENV VERSION="${VERSION}" ARG SLIM="" ENV SLIM="${SLIM}" -ARG HOME="/home/user" +ARG HOME="/weechat" ENV HOME="${HOME}" ENV LANG="C.UTF-8" ENV TERM="xterm-256color" -# create a user -RUN set -eux ; \ - adduser -u 1001 -D -h "$HOME" user ; \ - mkdir -p "$HOME/.weechat" ; \ - mkdir -p "$HOME/.config/weechat" ; \ - mkdir -p "$HOME/.local/share/weechat" ; \ - mkdir -p "$HOME/.cache/weechat" ; \ - chown -R user:user "$HOME" - # ==== build ==== FROM base as build @@ -126,6 +117,8 @@ RUN set -eux ; \ libgcrypt \ ncurses-libs \ ncurses-terminfo \ + shadow \ + setpriv \ tzdata \ zlib \ zstd \ @@ -151,8 +144,17 @@ COPY --from=build /opt/weechat /opt/weechat RUN ln -sf /opt/weechat/bin/weechat /usr/bin/weechat RUN ln -sf /opt/weechat/bin/weechat-headless /usr/bin/weechat-headless -WORKDIR $HOME +# create a user +RUN set -eux ; \ + adduser -u 1000 -D -h "$HOME" weechat; \ + mkdir -p "$HOME/.weechat" ; \ + mkdir -p "$HOME/.config/weechat" ; \ + mkdir -p "$HOME/.local/share/weechat" ; \ + mkdir -p "$HOME/.cache/weechat" ; \ + chown -R weechat:weechat "$HOME" -USER user +ADD ../run.sh / + +WORKDIR $HOME -CMD ["weechat"] +ENTRYPOINT [ "/run.sh" ] diff --git a/build.py b/build.py index 376e838..c2a5e7a 100755 --- a/build.py +++ b/build.py @@ -18,17 +18,45 @@ """Build WeeChat container image.""" -from typing import List, Sequence, Tuple +from typing import List, Sequence, Tuple, Dict import argparse import urllib.request import subprocess # nosec +BUILDERS: Sequence[str] = ( + "docker", + "podman", +) + DISTROS: Sequence[str] = ( "debian", "alpine", ) +SUPPORTED_PLATFORMS: Dict[str, Dict[str, List[str]]] = { + "linux": { + "386": [], + "amd64": [], + "arm": ["v6", "v7"], + "arm64": [], + "ppc64le": [], + "s390x": [], + }, +} + + +def generate_valid_platforms() -> list[str]: + """Return the list of supported platforms.""" + valid_platforms = [] + for os_name, archs in SUPPORTED_PLATFORMS.items(): + for arch, variants in archs.items(): + if not variants: + valid_platforms.append(f"{os_name}/{arch}") + for variant in variants: + valid_platforms.append(f"{os_name}/{arch}/{variant}") + return valid_platforms + def get_parser() -> argparse.ArgumentParser: """Return the command line parser.""" @@ -36,7 +64,8 @@ def get_parser() -> argparse.ArgumentParser: parser.add_argument( "-b", "--builder", - default="docker", + choices=BUILDERS, + default=BUILDERS[0], help=( "program used to build the container image, " "like docker (default) or podman" @@ -47,7 +76,29 @@ def get_parser() -> argparse.ArgumentParser: "--distro", choices=DISTROS, default=DISTROS[0], - help="base Linux distribution for the container", + help="base Linux distribution for the container, default %(default)s", + ) + parser.add_argument( + "-p", + "--platforms", + default="linux/amd64", + choices=generate_valid_platforms(), + nargs="+", + help="build platforms", + ) + parser.add_argument( + "--push", + action="store_true", + default=False, + help=( + "push the images to the registry, default: %(default)s" + ), + ) + parser.add_argument( + "-r", + "--registry-project", + default="docker.io/weechat", + help="registry project, default: %(default)s", ) parser.add_argument( "-n", @@ -81,7 +132,7 @@ def get_version_tags( :param version: x.y, x.y.z, "latest", "stable", "devel" :param distro: "debian" or "alpine" - :paral slim: slim version + :param slim: slim version :return: tuple (version, tags) where version if the version of WeeChat to build and tags is a list of tag arguments for command line, for example: ['-t', 'weechat:3.0-alpine', @@ -111,25 +162,61 @@ def get_version_tags( tags_args = [] for tag in reversed(tags): for suffix in suffixes: - tags_args.extend( - [ - "-t", + tags_args.append( f"weechat:{tag}{suffix}", - "-t", - f"weechat/weechat:{tag}{suffix}", - ] ) return (version, tags_args) +def run_command(command: List[str], dry_run: bool) -> None: + """ + Prints the command to run and runs it if 'dry_run=False' + + :param command: ["docker", "images"] + :param dry_run: True + :return: None + """ + print(f'Running: {" ".join(command)}') + if not dry_run: + try: + subprocess.run(command, check=False) # nosec + except KeyboardInterrupt: + pass + except Exception as exc: # pylint: disable=broad-except + print(exc) + + def main() -> None: """Main function.""" + build_args = ["buildx", "build"] args = get_parser().parse_args() slim = ["--build-arg", "SLIM=1"] if args.slim else [] version, tags = get_version_tags(args.version, args.distro, args.slim) + + if args.builder == "docker": + docker_tags = [] + for tag in tags: + docker_tags.extend(["-t", f"{args.registry_project}/{tag}"]) + tags = docker_tags + if args.push: + build_args.append("--push") + + podman_slim = "" + if args.slim: + podman_slim = "-slim" + podman_manifest_image = f"localhost/weechat:{version}" \ + + f"-{args.distro}{podman_slim}-manifest" + podman_tags = [tag for tag in tags if tag.startswith("weechat:")] + + if args.builder == "podman": + build_args = ["build", "--jobs", "0"] + tags = ["--manifest", podman_manifest_image] + command = [ f"{args.builder}", - "build", + *build_args, + "--platform", + ",".join(args.platforms), "-f", f"{args.distro}/Containerfile", "--build-arg", @@ -138,14 +225,27 @@ def main() -> None: *tags, ".", ] - print(f'Running: {" ".join(command)}') - if not args.dry_run: - try: - subprocess.run(command, check=False) # nosec - except KeyboardInterrupt: - pass - except Exception as exc: # pylint: disable=broad-except - print(exc) + run_command(command, args.dry_run) + + if args.builder == "podman": + command = [ + f"{args.builder}", + "tag", + podman_manifest_image, + *[f"localhost/{tag}" for tag in podman_tags] + ] + run_command(command, args.dry_run) + + if args.push: + for tag in podman_tags: + command = [ + f"{args.builder}", + "manifest", + "push", + f"localhost/{tag}", + f"{args.registry_project}/{tag}" + ] + run_command(command, args.dry_run) if __name__ == "__main__": diff --git a/debian/Containerfile b/debian/Containerfile index 2a45b1f..efb9e84 100644 --- a/debian/Containerfile +++ b/debian/Containerfile @@ -18,21 +18,12 @@ ENV VERSION="${VERSION}" ARG SLIM="" ENV SLIM="${SLIM}" -ARG HOME="/home/user" +ARG HOME="/weechat" ENV HOME="${HOME}" ENV LANG="C.UTF-8" ENV TERM="xterm-256color" -# create a user -RUN set -eux ; \ - useradd --create-home --home-dir "$HOME" user ; \ - mkdir -p "$HOME/.weechat" ; \ - mkdir -p "$HOME/.config/weechat" ; \ - mkdir -p "$HOME/.local/share/weechat" ; \ - mkdir -p "$HOME/.cache/weechat" ; \ - chown -R user:user "$HOME" - # ==== build ==== FROM base as build @@ -41,6 +32,7 @@ LABEL stage=build RUN set -eux ; \ \ # install download/build dependencies + export DEBIAN_FRONTEND="noninteractive"; \ apt-get update ; \ apt-get install -y --no-install-recommends \ asciidoctor \ @@ -121,6 +113,7 @@ FROM base RUN set -eux ; \ \ # install runtime dependencies + export DEBIAN_FRONTEND="noninteractive"; \ apt-get update ; \ apt-get install -y --no-install-recommends \ ca-certificates \ @@ -155,8 +148,17 @@ COPY --from=build /opt/weechat /opt/weechat RUN ln -sf /opt/weechat/bin/weechat /usr/bin/weechat RUN ln -sf /opt/weechat/bin/weechat-headless /usr/bin/weechat-headless -WORKDIR $HOME +# create a user +RUN set -eux ; \ + useradd --create-home --home-dir "$HOME" weechat; \ + mkdir -p "$HOME/.weechat" ; \ + mkdir -p "$HOME/.config/weechat" ; \ + mkdir -p "$HOME/.local/share/weechat" ; \ + mkdir -p "$HOME/.cache/weechat" ; \ + chown -R weechat:weechat "$HOME" -USER user +ADD ../run.sh / + +WORKDIR $HOME -CMD ["weechat"] +ENTRYPOINT [ "/run.sh" ] diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..aa91bd1 --- /dev/null +++ b/run.sh @@ -0,0 +1,8 @@ +#! /bin/sh + +usermod -o -u "${PUID:-1000}" weechat 2>&1 > /dev/null +groupmod -o -g "${PGID:-1000}" weechat 2>&1 > /dev/null + +chown -R weechat:weechat /weechat + +exec setpriv --reuid weechat --regid weechat --clear-groups /usr/bin/weechat "$@"