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 += `