diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 67731ef..6ce9f30 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,7 +1,7 @@ on: push jobs: build: - runs-on: ubuntu-latest-16-cores + runs-on: ubuntu-latest-64-cores steps: - name: Set up QEMU uses: docker/setup-qemu-action@v2 @@ -15,6 +15,11 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Build for linux/amd64 uses: docker/build-push-action@v3 with: @@ -26,6 +31,12 @@ jobs: cache-from: type=registry,ref=cs50/cli:amd64-buildcache cache-to: type=registry,ref=cs50/cli:amd64-buildcache,mode=max + - name: Squash for linux/amd64 + if: ${{ github.ref == 'refs/heads/develop' }} + run: | + pip3 install docker-squash + docker-squash --tag cs50/cli:amd64 cs50/cli:amd64 + - name: Push linux/amd64 build to Docker Hub if: ${{ github.ref == 'refs/heads/main' }} run: | @@ -42,6 +53,12 @@ jobs: cache-from: type=registry,ref=cs50/cli:arm64-buildcache cache-to: type=registry,ref=cs50/cli:arm64-buildcache,mode=max + - name: Squash for linux/arm64 + if: ${{ github.ref == 'refs/heads/develop' }} + run: | + pip3 install docker-squash + docker-squash --cleanup --tag cs50/cli:arm64 cs50/cli:arm64 + - name: Push linux/arm64 build to Docker Hub if: ${{ github.ref == 'refs/heads/main' }} run: | diff --git a/Dockerfile b/Dockerfile index ae2d949..da2dd9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,54 @@ -# Build statge +# Build stage FROM ubuntu:22.04 as builder ARG DEBIAN_FRONTEND=noninteractive +# Stage-wide dependencies +RUN apt update && \ + apt install --no-install-recommends --no-install-suggests --yes \ + build-essential \ + ca-certificates \ + curl + + +# Install Java 21.x +# http://jdk.java.net/21/ +RUN cd /tmp && \ + if [ $(uname -m) = "x86_64" ]; then ARCH="x64"; else ARCH="aarch64"; fi && \ + curl --remote-name https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-${ARCH}_bin.tar.gz && \ + tar xzf openjdk-21.0.2_linux-${ARCH}_bin.tar.gz && \ + rm --force openjdk-21.0.2_linux-${ARCH}_bin.tar.gz && \ + mv jdk-21.0.2 /opt/jdk && \ + mkdir --parent /opt/bin && \ + ln --symbolic /opt/jdk/bin/* /opt/bin/ && \ + chmod a+rx /opt/bin/* + + +# Install Node.js 21.x +# https://nodejs.dev/en/download/ +# https://github.com/tj/n#installation +RUN curl --location https://raw.githubusercontent.com/tj/n/master/bin/n --output /usr/local/bin/n && \ + chmod a+x /usr/local/bin/n && \ + n 21.6.1 + + +# Install Node.js packages +RUN npm install --global \ + http-server + + +# Patch index.js in http-server +COPY index.js.patch /tmp +RUN cd /usr/local/lib/node_modules/http-server/lib/core/show-dir && \ + patch index.js < /tmp/index.js.patch && \ + rm --force /tmp/index.js.patch + + # Suggested build environment for Python, per pyenv, even though we're building ourselves # https://github.com/pyenv/pyenv/wiki#suggested-build-environment RUN apt update && \ apt install --no-install-recommends --no-install-suggests --yes \ - curl ca-certificates build-essential git \ + build-essential ca-certificates curl git \ libssl-dev libbz2-dev libreadline-dev libsqlite3-dev \ llvm libncursesw5-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev \ make tk-dev unzip wget xz-utils zlib1g-dev @@ -16,11 +57,11 @@ RUN apt update && \ # Install Python 3.11.x # https://www.python.org/downloads/ RUN cd /tmp && \ - curl https://www.python.org/ftp/python/3.11.7/Python-3.11.7.tgz --output Python-3.11.7.tgz && \ + curl --remote-name https://www.python.org/ftp/python/3.11.7/Python-3.11.7.tgz && \ tar xzf Python-3.11.7.tgz && \ rm --force Python-3.11.7.tgz && \ cd Python-3.11.7 && \ - CFLAGS="-Os" ./configure --enable-optimizations --without-tests && \ + CFLAGS="-Os" ./configure --disable-static --enable-optimizations --enable-shared --with-lto --without-tests && \ ./configure && \ make && \ make install && \ @@ -31,6 +72,24 @@ RUN cd /tmp && \ pip3 install --no-cache-dir --upgrade pip +# Install R +# https://docs.posit.co/resources/install-r-source/#build-and-install-r +# https://cran.rstudio.com/src/base/R-4/ +RUN sed --in-place "/^#.*deb-src.*universe$/s/^# //g" /etc/apt/sources.list && \ + apt update && \ + apt build-dep --yes r-base && \ + cd /tmp && \ + curl --remote-name https://cran.rstudio.com/src/base/R-4/R-4.3.2.tar.gz && \ + tar xzf R-4.3.2.tar.gz && \ + rm --force R-4.3.2.tar.gz && \ + cd R-4.3.2 && \ + ./configure --enable-memory-profiling --enable-R-shlib && \ + make && \ + make install && \ + cd .. && \ + rm --force --recursive R-4.3.2 + + # Install Ruby 3.2.x # https://www.ruby-lang.org/en/downloads/ RUN apt update && \ @@ -38,7 +97,7 @@ RUN apt update && \ autoconf \ libyaml-dev && \ apt clean && \ - rm -rf /var/lib/apt/lists/* && \ + rm --force --recursive /var/lib/apt/lists/* && \ cd /tmp && \ curl https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.2.tar.gz --output ruby-3.2.2.tar.gz && \ tar xzf ruby-3.2.2.tar.gz && \ @@ -66,46 +125,17 @@ RUN gem install --no-document \ # https://www.sqlite.org/howtocompile.html#compiling_the_command_line_interface COPY shell.c.patch /tmp RUN cd /tmp && \ - curl --remote-name https://www.sqlite.org/2023/sqlite-amalgamation-3440000.zip && \ - unzip sqlite-amalgamation-3440000.zip && \ - rm --force sqlite-amalgamation-3440000.zip && \ - cd sqlite-amalgamation-3440000 && \ + curl --remote-name https://www.sqlite.org/2024/sqlite-amalgamation-3450000.zip && \ + unzip sqlite-amalgamation-3450000.zip && \ + rm --force sqlite-amalgamation-3450000.zip && \ + cd sqlite-amalgamation-3450000 && \ patch shell.c < /tmp/shell.c.patch && \ gcc -D HAVE_READLINE -D SQLITE_DEFAULT_FOREIGN_KEYS=1 -D SQLITE_OMIT_DYNAPROMPT=1 shell.c sqlite3.c -lpthread -ldl -lm -lreadline -lncurses -o /usr/local/bin/sqlite3 && \ cd .. && \ - rm --force --recursive sqlite-amalgamation-3440000 && \ + rm --force --recursive sqlite-amalgamation-3450000 && \ rm --force /tmp/shell.c.patch -# Install Java 21.x -# http://jdk.java.net/21/ -RUN cd /tmp && \ - curl --remote-name https://download.java.net/java/GA/jdk21.0.1/415e3f918a1f4062a0074a2794853d0d/12/GPL/openjdk-21.0.1_linux-x64_bin.tar.gz && \ - tar xzf openjdk-21.0.1_linux-x64_bin.tar.gz && \ - rm --force openjdk-21.0.1_linux-x64_bin.tar.gz && \ - mv jdk-21.0.1 /opt/ && \ - mkdir --parent /opt/bin && \ - ln --symbolic /opt/jdk-21.0.1/bin/* /opt/bin/ && \ - chmod a+rx /opt/bin/* - - -# Install Node.js 21.x -# https://nodejs.dev/en/download/ -# https://github.com/tj/n#installation -RUN curl --location https://raw.githubusercontent.com/tj/n/master/bin/n --output /usr/local/bin/n && \ - chmod a+x /usr/local/bin/n && \ - n 21.2.0 - - -# Install GitHub CLI -# https://github.com/cli/cli/blob/trunk/docs/install_linux.md#debian-ubuntu-linux-raspberry-pi-os-apt -RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \ - chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && \ - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null && \ - apt update && \ - apt install gh --no-install-recommends --no-install-suggests --yes - - # Final stage FROM ubuntu:22.04 LABEL maintainer="sysadmins@cs50.harvard.edu" @@ -113,8 +143,8 @@ ARG DEBIAN_FRONTEND=noninteractive # Copy files from builder -COPY --from=builder /usr /usr COPY --from=builder /opt /opt +COPY --from=builder /usr/local /usr/local # Avoid "delaying package configuration, since apt-utils is not installed" @@ -122,8 +152,6 @@ COPY --from=builder /opt /opt RUN apt update && \ apt install --no-install-recommends --no-install-suggests --yes \ apt-utils \ - curl \ - ca-certificates \ locales && \ locale-gen \ en_US.utf8 \ @@ -145,16 +173,18 @@ RUN apt update && \ ENV LANG=C.UTF-8 -# Install CS50, Ubuntu, and Python packages -RUN curl https://packagecloud.io/install/repositories/cs50/repo/script.deb.sh | bash && \ - apt update && \ +# Install Ubuntu packages +RUN apt update && \ + apt upgrade --yes && \ apt install --no-install-recommends --no-install-suggests --yes \ astyle \ bash-completion \ build-essential `# dpkg-dev, libc, gcc, g++, make, etc.`\ + ca-certificates \ clang \ coreutils `# For fold` \ cowsay \ + curl \ dos2unix \ dnsutils `# For nslookup` \ fonts-noto-color-emoji `# For render50` \ @@ -163,24 +193,36 @@ RUN curl https://packagecloud.io/install/repositories/cs50/repo/script.deb.sh | git-lfs \ jq \ less \ - libcs50 \ + liblapack3 `# For R` \ libmagic-dev `# For style50` \ libpango-1.0-0 libharfbuzz0b libpangoft2-1.0-0 `# For render50` \ + libpangocairo-1.0-0 `# For R` \ + libtiff5 `# For R` \ + libxt6 `# For R` \ libyaml-0-2 `# Runtime package for gem` \ man \ man-db \ nano \ openssh-client `# For ssh-keygen` \ psmisc `# For fuser` \ - ruby-dev `# Ruby development headers` \ sudo \ tzdata `# For TZ` \ unzip \ valgrind \ vim \ + wget \ zip && \ - apt clean && \ - pip3 install --no-cache-dir \ + apt clean + + +# Install CS50 library +RUN curl https://packagecloud.io/install/repositories/cs50/repo/script.deb.sh | bash && \ + apt update && \ + apt install --yes libcs50 + + +# Install Python packages +RUN pip3 install --no-cache-dir \ autopep8 \ black \ "check50<4" \ @@ -196,18 +238,6 @@ RUN curl https://packagecloud.io/install/repositories/cs50/repo/script.deb.sh | "submit50<4" -# Install Node.js packages -RUN npm install --global \ - http-server - - -# Patch index.js in http-server -COPY index.js.patch /tmp -RUN cd /usr/local/lib/node_modules/http-server/lib/core/show-dir && \ - patch index.js < /tmp/index.js.patch && \ - rm --force /tmp/index.js.patch - - # Copy files to image COPY ./etc /etc COPY ./opt /opt diff --git a/Makefile b/Makefile index 790902b..94b2727 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,21 @@ +.PHONY: default default: run build: - docker build --build-arg VCS_REF="$(shell git rev-parse HEAD)" -t cs50/cli . + docker build --build-arg VCS_REF="$(shell git rev-parse HEAD)" --tag cs50/cli . + $(MAKE) squash + +depends: + pip3 install docker-squash rebuild: - docker build --no-cache -t cs50/cli . + docker build --no-cache --tag cs50/cli . + $(MAKE) squash run: - docker run --env LANG="$(LANG)" -it -P --rm --security-opt seccomp=unconfined -v "$(PWD)":/home/ubuntu cs50/cli bash --login || true + docker run --env LANG="$(LANG)" --interactive --publish-all --rm --security-opt seccomp=unconfined --tty --volume "$(PWD)":/home/ubuntu cs50/cli bash --login || true + +squash: depends + docker images cs50/codespace + docker-squash --tag cs50/cli cs50/cli + docker images cs50/codespace diff --git a/etc/profile.d/cli.sh b/etc/profile.d/cli.sh index 971de08..66dbba7 100644 --- a/etc/profile.d/cli.sh +++ b/etc/profile.d/cli.sh @@ -25,6 +25,7 @@ if [ "$(whoami)" != "root" ]; then alias mv="mv -i" alias pip="pip --no-cache-dir" alias python="python -q" + alias R="R --vanilla" alias rm="rm -i" alias sudo="sudo " # Trailing space enables elevated command to be an alias diff --git a/index.js.patch b/index.js.patch index 167a1fe..8e95084 100644 --- a/index.js.patch +++ b/index.js.patch @@ -1,3 +1,4 @@ +# diff /usr/local/lib/node_modules/http-server/lib/core/show-dir/index.js index.js > index.js.patch 129,131c129 < html += `
Node.js ${ < process.version diff --git a/shell.c.patch b/shell.c.patch index 2ffbcfd..35b0a32 100644 --- a/shell.c.patch +++ b/shell.c.patch @@ -1,25 +1,4 @@ -# diff shell.c shell_modified.c > shell.c.patch -28631,28641c28631,28641 -< printf( -< "SQLite version %s %.19s%s\n" /*extra-version-info*/ -< "Enter \".help\" for usage hints.\n", -< sqlite3_libversion(), sqlite3_sourceid(), zCharset -< ); -< if( warnInmemoryDb ){ -< printf("Connected to a "); -< printBold("transient in-memory database"); -< printf(".\nUse \".open FILENAME\" to reopen on a " -< "persistent database.\n"); -< } ---- -> // printf( -> // "SQLite version %s %.19s%s\n" /*extra-version-info*/ -> // "Enter \".help\" for usage hints.\n", -> // sqlite3_libversion(), sqlite3_sourceid(), zCharset -> // ); -> // if( warnInmemoryDb ){ -> // printf("Connected to a "); -> // printBold("transient in-memory database"); -> // printf(".\nUse \".open FILENAME\" to reopen on a " -> // "persistent database.\n"); -> // } +29438,29440d29437 +< sputf(stdout, "SQLite version %s %.19s%s\n" /*extra-version-info*/ +< "Enter \".help\" for usage hints.\n", +< sqlite3_libversion(), sqlite3_sourceid(), SHELL_CIO_CHAR_SET);