diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 67731ef..5c7320d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,6 +15,23 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build minimized image for linux/amd64 + uses: docker/build-push-action@v3 + with: + build-args: | + VCS_REF=${{ github.sha }} + load: true + platforms: linux/amd64 + tags: cs50/cli:amd64-minimized + cache-from: type=registry,ref=cs50/cli:amd64-minimized-buildcache + cache-to: type=registry,ref=cs50/cli:amd64-minimized-buildcache,mode=max + + - name: Push linux/amd64 build to Docker Hub + if: ${{ github.ref == 'refs/heads/minimizing' }} + run: | + docker push cs50/cli:amd64-minimized + + - name: Build for linux/amd64 uses: docker/build-push-action@v3 with: diff --git a/Dockerfile b/Dockerfile index cb12c6a..3959f82 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,84 +1,29 @@ -FROM ubuntu:22.04 -LABEL maintainer="sysadmins@cs50.harvard.edu" +# Build statge +FROM ubuntu:22.04 as builder ARG DEBIAN_FRONTEND=noninteractive -# Avoid "delaying package configuration, since apt-utils is not installed" -RUN apt update && apt install --yes apt-utils - - -# Install locales -RUN apt update && \ - apt install --yes locales && \ - locale-gen \ - en_US.utf8 \ - zh_CN.utf8 \ - zh_TW.utf8 \ - fr_FR.utf8 \ - de_DE.utf8 \ - it_IT.utf8 \ - es_ES.utf8 \ - ja_JP.utf8 \ - ko_KR.utf8 \ - ru_RU.utf8 \ - pt_BR.utf8 \ - tr_TR.utf8 \ - pl_PL.utf8 \ - cs_CZ.utf8 \ - hu_HU.utf8 -ENV LANG=C.UTF-8 - - -# Unminimize system -RUN yes | unminimize - - -# Install curl -RUN apt update && \ - apt install --yes curl - - -# Install Java 19.x -# http://jdk.java.net/19/ -RUN cd /tmp && \ - curl --remote-name https://download.java.net/java/GA/jdk19.0.2/fdb695a9d9064ad6b064dc6df578380c/7/GPL/openjdk-19.0.2_linux-x64_bin.tar.gz && \ - tar xzf openjdk-19.0.2_linux-x64_bin.tar.gz && \ - rm --force openjdk-19.0.2_linux-x64_bin.tar.gz && \ - mv jdk-19.0.2 /opt/ && \ - mkdir --parent /opt/bin && \ - ln --symbolic /opt/jdk-19.0.2/bin/* /opt/bin/ && \ - chmod a+rx /opt/bin/* - - -# Install Node.js 19.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 19.8.1 - - # 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 --yes \ + apt install --no-install-recommends --no-install-suggests --yes \ make build-essential libssl-dev zlib1g-dev \ libbz2-dev libreadline-dev libsqlite3-dev llvm ca-certificates curl wget unzip \ libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev -# Install Python 3.11.x +# Install Python 3.12.x # https://www.python.org/downloads/ RUN cd /tmp && \ - curl https://www.python.org/ftp/python/3.11.3/Python-3.11.3.tgz --output Python-3.11.3.tgz && \ - tar xzf Python-3.11.3.tgz && \ - rm --force Python-3.11.3.tgz && \ - cd Python-3.11.3 && \ - ./configure && \ + curl --remote-name https://www.python.org/ftp/python/3.12.0/Python-3.12.0.tgz && \ + tar xzf Python-3.12.0.tgz && \ + rm --force Python-3.12.0.tgz && \ + cd Python-3.12.0 && \ + CFLAGS="-Os" ./configure --enable-optimizations --without-tests && \ make && \ make install && \ cd .. && \ - rm --force --recursive Python-3.11.3 && \ + rm --force --recursive Python-3.12.0 && \ ln --relative --symbolic /usr/local/bin/pip3 /usr/local/bin/pip && \ ln --relative --symbolic /usr/local/bin/python3 /usr/local/bin/python && \ pip3 install --upgrade pip @@ -87,60 +32,125 @@ RUN cd /tmp && \ # Install Ruby 3.2.x # https://www.ruby-lang.org/en/downloads/ RUN apt update && \ - apt install --no-install-recommends --yes \ + apt install --no-install-recommends --no-install-suggests --yes \ autoconf \ libyaml-dev && \ 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 && \ + curl --remote-name https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.2.tar.gz && \ tar xzf ruby-3.2.2.tar.gz && \ rm --force ruby-3.2.2.tar.gz && \ cd ruby-3.2.2 && \ - ./configure && \ + CFLAGS="-Os" ./configure --disable-install-doc --enable-load-relative && \ make && \ make install && \ cd .. && \ rm --force --recursive ruby-3.2.2 -# Install SQLite 3.x +# Install Ruby packages +RUN apt install --yes git +RUN gem install --no-document \ + jekyll \ + minitest `# So that Bundler needn't install` \ + pygments.rb \ + specific_install && \ + gem specific_install https://github.com/cs50/jekyll-theme-cs50 develop && \ + gem cleanup + + +# Install SQLite 3.4x # https://www.sqlite.org/download.html # 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-3420000.zip && \ - unzip sqlite-amalgamation-3420000.zip && \ - rm --force sqlite-amalgamation-3420000.zip && \ - cd sqlite-amalgamation-3420000 && \ + 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 && \ 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-3420000 && \ + rm --force --recursive sqlite-amalgamation-3440000 && \ 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 cd /usr/local/bin && \ + curl --remote-name https://raw.githubusercontent.com/tj/n/master/bin/n && \ + chmod a+x n && \ + ./n 21.2.0 && \ + npm install --global http-server + + # 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 --yes + apt install gh --no-install-recommends --no-install-suggests --yes -# Install CS50 packages -RUN curl https://packagecloud.io/install/repositories/cs50/repo/script.deb.sh | bash && \ - apt update && \ - apt install --yes \ - libcs50 +# Final stage +FROM ubuntu:22.04 +LABEL maintainer="sysadmins@cs50.harvard.edu" +ARG DEBIAN_FRONTEND=noninteractive + +COPY --from=builder /usr /usr +COPY --from=builder /opt /opt -# Install Ubuntu packages +# Avoid "delaying package configuration, since apt-utils is not installed" RUN apt update && \ - apt install --no-install-recommends --yes \ + apt install --no-install-recommends --no-install-suggests --yes \ + apt-utils \ + ca-certificates \ + curl \ + locales && \ + locale-gen \ + en_US.utf8 \ + zh_CN.utf8 \ + zh_TW.utf8 \ + fr_FR.utf8 \ + de_DE.utf8 \ + it_IT.utf8 \ + es_ES.utf8 \ + ja_JP.utf8 \ + ko_KR.utf8 \ + ru_RU.utf8 \ + pt_BR.utf8 \ + tr_TR.utf8 \ + pl_PL.utf8 \ + cs_CZ.utf8 \ + hu_HU.utf8 \ + bg_BG.UTF-8 && \ + apt clean +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 && \ + apt install --no-install-recommends --no-install-suggests --yes \ astyle \ bash-completion \ + build-essential `# dpkg-dev, libc, gcc, g++, make, etc.`\ clang \ - coreutils `# for fold` \ + coreutils `# For fold` \ cowsay \ dos2unix \ dnsutils `# For nslookup` \ @@ -150,28 +160,24 @@ RUN apt update && \ git-lfs \ jq \ less \ - make \ + libcs50 \ + libmagic-dev `# For style50` \ + libpango-1.0-0 libharfbuzz0b libpangoft2-1.0-0 `# For render50` \ + 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 \ - weasyprint `# For render50` \ - zip - - -# Install Node.js packages -RUN npm install --global http-server - - -# Install Python packages -RUN apt update && \ - apt install --yes libmagic-dev `# For style50` && \ - pip3 install \ + zip && \ + apt clean && \ + pip3 install --no-cache-dir \ awscli \ "check50<4" \ compare50 \ @@ -182,29 +188,11 @@ RUN apt update && \ pytest \ render50 \ s3cmd \ + setuptools \ style50 \ "submit50<4" -# Install Ruby packages -RUN gem install \ - bundler \ - jekyll \ - minitest `# So that Bundler needn't install` \ - pygments.rb \ - specific_install && \ - gem specific_install https://github.com/cs50/jekyll-theme-cs50 develop - - -# Temporary fix for "libssl.so.1.1: cannot open shared object file: No such file or directory" on Ubuntu 22.04 -# https://stackoverflow.com/questions/72133316/ubuntu-22-04-libssl-so-1-1-cannot-open-shared-object-file-no-such-file-or-di -RUN cd /tmp && \ - curl --remote-name http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.18_amd64.deb && \ - curl --remote-name http://ports.ubuntu.com/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.18_arm64.deb && \ - (dpkg --install libssl1.1_1.1.1f-1ubuntu2.18_amd64.deb || dpkg --install libssl1.1_1.1.1f-1ubuntu2.18_arm64.deb) && \ - rm --force --recursive libssl1.1_1.1.1f-1ubuntu2.18* - - # Copy files to image COPY ./etc /etc COPY ./opt /opt @@ -218,15 +206,12 @@ RUN echo >> /etc/inputrc && \ echo "set enable-bracketed-paste off" >> /etc/inputrc -# Add user +# Add user to sudoers RUN useradd --home-dir /home/ubuntu --shell /bin/bash ubuntu && \ umask 0077 && \ mkdir --parents /home/ubuntu && \ - chown --recursive ubuntu:ubuntu /home/ubuntu - - -# Add user to sudoers -RUN echo "\n# CS50 CLI" >> /etc/sudoers && \ + chown --recursive ubuntu:ubuntu /home/ubuntu && \ + echo "\n# CS50 CLI" >> /etc/sudoers && \ echo "ubuntu ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \ echo "Defaults umask_override" >> /etc/sudoers && \ echo "Defaults umask=0022" >> /etc/sudoers && \ diff --git a/etc/profile.d/cli.sh b/etc/profile.d/cli.sh index abfc837..8850566 100644 --- a/etc/profile.d/cli.sh +++ b/etc/profile.d/cli.sh @@ -35,11 +35,11 @@ if [ "$(whoami)" != "root" ]; then # History # https://www.shellhacks.com/tune-command-line-history-bash/ - export HISTCONTROL='ignoreboth' # Ignore duplicates and command lines starting space + export HISTCONTROL='ignoredupes' # Ignore duplicates export PROMPT_COMMAND='history -a' # Store Bash History Immediately # Java - export JAVA_HOME="/opt/jdk-19.0.2" + export JAVA_HOME="/opt/jdk-21.0.1" # Make export CC="clang" diff --git a/opt/cs50/bin/http-server b/opt/cs50/bin/http-server index 3ed1bca..a0f1c26 100755 --- a/opt/cs50/bin/http-server +++ b/opt/cs50/bin/http-server @@ -9,6 +9,26 @@ port="-p 8080" options="--no-dotfiles" t="-t0" +# Formatting +bold=$(tput bold) +normal=$(tput sgr0) + +# Check for app.py or wsgi.py +if [[ -f app.py ]] || [[ -f wsgi.py ]]; then + read -p "Are you sure you want to run ${bold}http-server${normal} and not ${bold}flask${normal}? [y/N] " -r + if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then + exit 1 + fi +fi + +# Check for path +if [[ $# -eq 1 ]] && [[ $1 != -* ]] && [[ ! $1 =~ ^\./?$ ]]; then + read -p "Are you sure you want to serve ${bold}${1}${normal} and not your current directory? [y/N] " -r + if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then + exit 1 + fi +fi + # Override default options while test ${#} -gt 0 do diff --git a/opt/cs50/bin/sqlite3 b/opt/cs50/bin/sqlite3 index 5627292..3c1c0e7 100755 --- a/opt/cs50/bin/sqlite3 +++ b/opt/cs50/bin/sqlite3 @@ -1,8 +1,18 @@ #!/bin/bash +# Formatting +bold=$(tput bold) +normal=$(tput sgr0) + +# If data is coming from stdin (pipe or redirection) +if [[ -p /dev/stdin || ! -t 0 ]]; then + /usr/local/bin/sqlite3 -nullvalue NULL -table "$@" < /dev/stdin + exit $? +fi + # If no command-line argument if [[ $# -eq 0 ]]; then - read -p "Are you sure you want to run $(tput bold)sqlite3$(tput sgr0) without a command-line argument (e.g., the filename of a database)? [y/N] " -r + read -p "Are you sure you want to run ${bold}sqlite3${normal} without a command-line argument (e.g., the filename of a database)? [y/N] " -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi @@ -11,12 +21,12 @@ if [[ $# -eq 0 ]]; then elif [[ $# -eq 1 ]] && [[ ! "$1" =~ ^- ]]; then if [[ ! -f "$1" ]]; then if [[ ! "$1" =~ \.db$ ]]; then - read -p "Are you sure you want to create $(tput bold)$1$(tput sgr0)? SQLite filenames usually end in $(tput bold).db$(tput sgr0). [y/N] " -r + read -p "Are you sure you want to create ${bold}$1${normal}? SQLite filenames usually end in ${bold}.db${normal}. [y/N] " -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi else - read -p "Are you sure you want to create $(tput bold)$1$(tput sgr0)? [y/N] " -r + read -p "Are you sure you want to create ${bold}$1${normal}? [y/N] " -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi diff --git a/shell.c.patch b/shell.c.patch index a1db6a0..2ffbcfd 100644 --- a/shell.c.patch +++ b/shell.c.patch @@ -1,9 +1,9 @@ # diff shell.c shell_modified.c > shell.c.patch -27832,27842c27832,27842 +28631,28641c28631,28641 < printf( -< "SQLite version %s %.19s\n" /*extra-version-info*/ +< "SQLite version %s %.19s%s\n" /*extra-version-info*/ < "Enter \".help\" for usage hints.\n", -< sqlite3_libversion(), sqlite3_sourceid() +< sqlite3_libversion(), sqlite3_sourceid(), zCharset < ); < if( warnInmemoryDb ){ < printf("Connected to a "); @@ -12,14 +12,14 @@ < "persistent database.\n"); < } --- -> // printf( -> // "SQLite version %s %.19s\n" /*extra-version-info*/ -> // "Enter \".help\" for usage hints.\n", -> // sqlite3_libversion(), sqlite3_sourceid() -> // ); -> // 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"); +> // }