diff --git a/build-chromium.sh b/build-chromium.sh new file mode 100755 index 000000000..3bf402a6f --- /dev/null +++ b/build-chromium.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +VERSION=${1:-101.0.4951.64-0ubuntu0.18.04.1} +TAG=${2:-chromium_101.0} +BASE_TAG=${3:-7.3.6} + +# Cleanup stuff +export BUILDKIT_PROGRESS=plain +docker rmi -f selenoid/vnc:$TAG browsers/base:$BASE_TAG $(docker images -q selenoid/dev_chromium:*) +rm -rf ../selenoid-container-tests + +# Prepare for building images +go get github.com/markbates/pkger/cmd/pkger +go generate github.com/aerokube/images +go build + +# Forked tests with a bugfix +git clone -b add-missing-dependency https://github.com/sskorol/selenoid-container-tests.git ../selenoid-container-tests + +# Force build browsers/base image as it has arm64-specific updates +cd ./selenium/base && docker build --no-cache --build-arg UBUNTU_VERSION=18.04 -t browsers/base:$BASE_TAG . && docker system prune -f + +# Build chromium image +cd ../../ && ./images chromium -b $VERSION -t selenoid/vnc:$TAG --test && docker system prune -f diff --git a/build/chromium.go b/build/chromium.go new file mode 100644 index 000000000..0fde5f87e --- /dev/null +++ b/build/chromium.go @@ -0,0 +1,94 @@ +package build + +import ( + "fmt" + "os" + "path/filepath" +) + +type Chromium struct { + Requirements +} + +func (c *Chromium) Build() error { + + pkgSrcPath, pkgVersion, err := c.BrowserSource.Prepare() + if err != nil { + return fmt.Errorf("invalid browser source: %v", err) + } + + pkgTagVersion := extractVersion(pkgVersion) + + if err != nil { + return fmt.Errorf("parse chromiumdriver version: %v", err) + } + + // Build dev image + devDestDir, err := tmpDir() + if err != nil { + return fmt.Errorf("create dev temporary dir: %v", err) + } + + srcDir := "chromium/apt" + + if pkgSrcPath != "" { + srcDir = "chromium/local" + pkgDestDir := filepath.Join(devDestDir, srcDir) + err := os.MkdirAll(pkgDestDir, 0755) + if err != nil { + return fmt.Errorf("create %v temporary dir: %v", pkgDestDir, err) + } + pkgDestPath := filepath.Join(pkgDestDir, "chromium.deb") + err = os.Rename(pkgSrcPath, pkgDestPath) + if err != nil { + return fmt.Errorf("move package: %v", err) + } + } + + devImageTag := fmt.Sprintf("selenoid/dev_chromium:%s", pkgTagVersion) + devImageRequirements := Requirements{NoCache: c.NoCache, Tags: []string{devImageTag}} + devImage, err := NewImage(srcDir, devDestDir, devImageRequirements) + if err != nil { + return fmt.Errorf("init dev image: %v", err) + } + devBuildArgs := []string{fmt.Sprintf("VERSION=%s", pkgVersion)} + devImage.BuildArgs = devBuildArgs + if pkgSrcPath != "" { + devImage.FileServer = true + } + + err = devImage.Build() + if err != nil { + return fmt.Errorf("build dev image: %v", err) + } + + // Build main image + destDir, err := tmpDir() + if err != nil { + return fmt.Errorf("create temporary dir: %v", err) + } + + image, err := NewImage("chromium", destDir, c.Requirements) + if err != nil { + return fmt.Errorf("init image: %v", err) + } + image.BuildArgs = append(image.BuildArgs, fmt.Sprintf("VERSION=%s", pkgTagVersion)) + + err = image.Build() + if err != nil { + return fmt.Errorf("build image: %v", err) + } + + // Must be Chrome even it's Chromium-based container + err = image.Test(c.TestsDir, "chrome", pkgTagVersion) + if err != nil { + return fmt.Errorf("test image: %v", err) + } + + err = image.Push() + if err != nil { + return fmt.Errorf("push image: %v", err) + } + + return nil +} diff --git a/cmd/chromium.go b/cmd/chromium.go new file mode 100644 index 000000000..4e0d71836 --- /dev/null +++ b/cmd/chromium.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "github.com/aerokube/images/build" + "github.com/spf13/cobra" +) + +var ( + chromiumCmd = &cobra.Command{ + Use: "chromium", + Short: "build Chromium image", + RunE: func(cmd *cobra.Command, args []string) error { + req := build.Requirements{ + BrowserSource: build.BrowserSource(browserSource), + NoCache: noCache, + TestsDir: testsDir, + RunTests: test, + IgnoreTests: ignoreTests, + Tags: tags, + PushImage: push, + } + chromium := &build.Chromium{req} + return chromium.Build() + }, + } +) diff --git a/cmd/root.go b/cmd/root.go index daf4a3423..c4f0150ad 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -51,6 +51,7 @@ func initFlags() { func init() { initFlags() rootCmd.AddCommand(chromeCmd) + rootCmd.AddCommand(chromiumCmd) rootCmd.AddCommand(edgeCmd) rootCmd.AddCommand(firefoxCmd) rootCmd.AddCommand(operaCmd) diff --git a/selenium/base/Dockerfile b/selenium/base/Dockerfile index dd1cedec9..f6f7a7642 100644 --- a/selenium/base/Dockerfile +++ b/selenium/base/Dockerfile @@ -1,3 +1,5 @@ +ARG UBUNTU_VERSION=20.04 + FROM golang:1.17 as go COPY xseld /xseld @@ -5,17 +7,19 @@ COPY xseld /xseld COPY fileserver /fileserver RUN \ + if [ `uname -m` = "aarch64" ]; then ARCH="arm64"; else ARCH="amd64"; fi && \ apt-get update && \ apt-get install -y upx-ucl libx11-dev && \ cd /xseld && \ - GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" && \ + GOOS=linux GOARCH=$ARCH go build -ldflags="-s -w" && \ upx /xseld/xseld && \ cd /fileserver && \ go test -race && \ - GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" && \ + GOOS=linux GOARCH=$ARCH go build -ldflags="-s -w" && \ upx /fileserver/fileserver -FROM ubuntu:20.04 +# For M1 Chromium images it's required to override a version to 18.04 as latest Ubuntu distributions don't ship updates +FROM ubuntu:$UBUNTU_VERSION RUN \ apt update && \ @@ -49,7 +53,6 @@ RUN \ xfonts-base \ xfonts-encodings \ xfonts-utils \ - flashplugin-installer \ xvfb \ pulseaudio \ fluxbox \ @@ -58,6 +61,7 @@ RUN \ wmctrl \ libnss-wrapper \ xsel && \ + if [ `uname -m` = "amd64" ]; then apt install -y flashplugin-installer; fi && \ mkdir -p /var/lib/locales/supported.d/ && grep UTF-8 /usr/share/i18n/SUPPORTED > /var/lib/locales/supported.d/all && \ locale-gen && update-locale && \ fc-cache -f -v && \ diff --git a/static/chromium/Dockerfile b/static/chromium/Dockerfile new file mode 100644 index 000000000..e73cfef7c --- /dev/null +++ b/static/chromium/Dockerfile @@ -0,0 +1,11 @@ +ARG VERSION +FROM selenoid/dev_chromium:$VERSION + +ENV DBUS_SESSION_BUS_ADDRESS=/dev/null +COPY entrypoint.sh / + +RUN chmod +x /usr/bin/chromedriver +USER selenium + +EXPOSE 4444 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/static/chromium/apt/Dockerfile b/static/chromium/apt/Dockerfile new file mode 100644 index 000000000..bd8f81ceb --- /dev/null +++ b/static/chromium/apt/Dockerfile @@ -0,0 +1,16 @@ +FROM browsers/base:7.3.6 + +ARG VERSION +ARG PACKAGE=chromium-browser + +LABEL browser=$PACKAGE:$VERSION + +RUN \ + apt-get update && \ + apt-get -y --no-install-recommends install \ + iproute2 \ + libgtk-3-0 \ + ${PACKAGE}=${VERSION} \ + chromium-chromedriver=${VERSION} && \ + chromium-browser --version && \ + rm -Rf /tmp/* && rm -Rf /var/lib/apt/lists/* diff --git a/static/chromium/entrypoint.sh b/static/chromium/entrypoint.sh new file mode 100755 index 000000000..b364ec463 --- /dev/null +++ b/static/chromium/entrypoint.sh @@ -0,0 +1,87 @@ +#!/bin/bash +SCREEN_RESOLUTION=${SCREEN_RESOLUTION:-"1920x1080x24"} +DISPLAY_NUM=99 +export DISPLAY=":$DISPLAY_NUM" + +VERBOSE=${VERBOSE:-""} +DRIVER_ARGS=${DRIVER_ARGS:-""} +if [ -n "$VERBOSE" ]; then + DRIVER_ARGS="$DRIVER_ARGS --verbose" +fi + +clean() { + if [ -n "$FILESERVER_PID" ]; then + kill -TERM "$FILESERVER_PID" + fi + if [ -n "$XSELD_PID" ]; then + kill -TERM "$XSELD_PID" + fi + if [ -n "$XVFB_PID" ]; then + kill -TERM "$XVFB_PID" + fi + if [ -n "$DRIVER_PID" ]; then + kill -TERM "$DRIVER_PID" + fi + if [ -n "$X11VNC_PID" ]; then + kill -TERM "$X11VNC_PID" + fi + if [ -n "$PULSE_PID" ]; then + kill -TERM "$PULSE_PID" + fi +} + +trap clean SIGINT SIGTERM + +if env | grep -q ROOT_CA_; then + mkdir -p $HOME/.pki/nssdb + certutil -N --empty-password -d sql:$HOME/.pki/nssdb + for e in $(env | grep ROOT_CA_ | sed -e 's/=.*$//'); do + certname=$(echo -n $e | sed -e 's/ROOT_CA_//') + echo ${!e} | base64 -d >/tmp/cert.pem + certutil -A -n ${certname} -t "TC,C,T" -i /tmp/cert.pem -d sql:$HOME/.pki/nssdb + if cat tmp/cert.pem | grep -q "PRIVATE KEY"; then + PRIVATE_KEY_PASS=${PRIVATE_KEY_PASS:-\'\'} + openssl pkcs12 -export -in /tmp/cert.pem -clcerts -nodes -out /tmp/key.p12 -passout pass:${PRIVATE_KEY_PASS} -passin pass:${PRIVATE_KEY_PASS} + pk12util -d sql:$HOME/.pki/nssdb -i /tmp/key.p12 -W ${PRIVATE_KEY_PASS} + rm /tmp/key.p12 + fi + rm /tmp/cert.pem + done +fi + +/usr/bin/fileserver & +FILESERVER_PID=$! + +DISPLAY="$DISPLAY" /usr/bin/xseld & +XSELD_PID=$! + +while ip addr | grep inet | grep -q tentative > /dev/null; do sleep 0.1; done + +mkdir -p ~/pulse/.config/pulse +echo -n 'gIvST5iz2S0J1+JlXC1lD3HWvg61vDTV1xbmiGxZnjB6E3psXsjWUVQS4SRrch6rygQgtpw7qmghDFTaekt8qWiCjGvB0LNzQbvhfs1SFYDMakmIXuoqYoWFqTJ+GOXYByxpgCMylMKwpOoANEDePUCj36nwGaJNTNSjL8WBv+Bf3rJXqWnJ/43a0hUhmBBt28Dhiz6Yqowa83Y4iDRNJbxih6rB1vRNDKqRr/J9XJV+dOlM0dI+K6Vf5Ag+2LGZ3rc5sPVqgHgKK0mcNcsn+yCmO+XLQHD1K+QgL8RITs7nNeF1ikYPVgEYnc0CGzHTMvFR7JLgwL2gTXulCdwPbg=='| base64 -d>~/pulse/.config/pulse/cookie +HOME=$HOME/pulse pulseaudio --start --exit-idle-time=-1 +HOME=$HOME/pulse pactl load-module module-native-protocol-tcp +PULSE_PID=$(ps --no-headers -C pulseaudio -o pid | sed -r 's/( )+//g') + +/usr/bin/xvfb-run -l -n "$DISPLAY_NUM" -s "-ac -screen 0 $SCREEN_RESOLUTION -noreset -listen tcp" /usr/bin/fluxbox -display "$DISPLAY" -log /dev/null 2>/dev/null & +XVFB_PID=$! + +retcode=1 +until [ $retcode -eq 0 ]; do + DISPLAY="$DISPLAY" wmctrl -m >/dev/null 2>&1 + retcode=$? + if [ $retcode -ne 0 ]; then + echo Waiting X server... + sleep 0.1 + fi +done + +if [ "$ENABLE_VNC" == "true" ]; then + x11vnc -display "$DISPLAY" -passwd selenoid -shared -forever -loop500 -rfbport 5900 -rfbportv6 5900 -logfile /dev/null & + X11VNC_PID=$! +fi + +DISPLAY="$DISPLAY" /usr/bin/chromedriver --port=4444 --allowed-ips='' --allowed-origins='*' ${DRIVER_ARGS} & +DRIVER_PID=$! + +wait diff --git a/static/chromium/local/Dockerfile b/static/chromium/local/Dockerfile new file mode 100644 index 000000000..599bcbb71 --- /dev/null +++ b/static/chromium/local/Dockerfile @@ -0,0 +1,58 @@ +FROM browsers/base:7.3.6 + +ARG VERSION=noop +ARG PACKAGE=chromium-browser + +LABEL browser=$PACKAGE:$VERSION + +RUN \ + apt-get update && \ + apt-get -y --no-install-recommends install gconf-service \ + libasound2 \ + libatk1.0-0 \ + libc6 \ + libcairo2 \ + libcups2 \ + libdbus-1-3 \ + libexpat1 \ + libfontconfig1 \ + libfreetype6 \ + libgcc1 \ + libgconf-2-4 \ + libgdk-pixbuf2.0-0 \ + libglib2.0-0 \ + libgtk2.0-0 \ + libgtk-3-0 \ + libnspr4 \ + libnss3 \ + libpango1.0-0 \ + libstdc++6 \ + libx11-6 \ + libx11-xcb1 \ + libxcb1 \ + libxcomposite1 \ + libxcursor1 \ + libxdamage1 \ + libxext6 \ + libxfixes3 \ + libxi6 \ + libxrandr2 \ + libxrender1 \ + libxss1 \ + libxtst6 \ + ca-certificates \ + fonts-liberation \ + libappindicator3-1 \ + libnss3 \ + lsb-base \ + xdg-utils \ + libcurl4 \ + iproute2 \ + curl \ + chromium-chromedriver && \ + curl -O http://host.docker.internal:8080/chromium-browser.deb && \ + apt-get -y purge curl && \ + dpkg -i chromium-browser.deb && \ + chromium-browser --version && \ + rm -Rf /tmp/* && \ + rm -Rf /var/lib/apt/lists/*