├── .github └── workflows │ └── main.yml ├── Dockerfile ├── Makefile ├── README.md ├── cs50.sh ├── next.css ├── scripts.js └── styles.css /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | jobs: 3 | build-and-deploy: 4 | runs-on: ubuntu-latest 5 | steps: 6 | - uses: actions/checkout@v3 7 | - name: Build 8 | run: rm -rf gh-pages && mkdir gh-pages && cp next.css scripts.js styles.css gh-pages 9 | - name: Deploy 10 | uses: peaceiris/actions-gh-pages@v3 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | publish_dir: gh-pages 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | # RStudio 4 | EXPOSE 8787 5 | 6 | COPY cs50.sh /tmp/ 7 | RUN bash /tmp/cs50.sh && rm -f /tmp/cs50.sh 8 | USER ubuntu 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | docker run -it -P --rm --security-opt seccomp=unconfined cs50/sandbox bash -l || true 3 | 4 | build: 5 | docker build -t cs50/sandbox . 6 | 7 | rebuild: 8 | docker build --no-cache -t cs50/sandbox . 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | Build and run: 4 | ``` 5 | make build 6 | make run 7 | ``` 8 | 9 | Rebuild: 10 | ``` 11 | make rebuild 12 | ``` 13 | 14 | # References 15 | 16 | * https://support.rstudio.com/hc/en-us/articles/200552316-Configuring-the-Server 17 | * http://docs.rstudio.com/ide/server-pro 18 | -------------------------------------------------------------------------------- /cs50.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | # Suggested build environment for Python, per pyenv, even though we're building ourselves 5 | # https://github.com/pyenv/pyenv/wiki#suggested-build-environment 6 | apt update && \ 7 | DEBIAN_FRONTEND=noninteractive apt install --no-install-recommends --yes \ 8 | make build-essential libssl-dev zlib1g-dev \ 9 | libbz2-dev libreadline-dev libsqlite3-dev llvm ca-certificates curl wget unzip \ 10 | libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev 11 | 12 | # Install Python 3.10.x 13 | # https://www.python.org/downloads/ 14 | cd /tmp && \ 15 | curl https://www.python.org/ftp/python/3.10.5/Python-3.10.5.tgz --output Python-3.10.5.tgz && \ 16 | tar xzf Python-3.10.5.tgz && \ 17 | rm --force Python-3.10.5.tgz && \ 18 | cd Python-3.10.5 && \ 19 | ./configure && \ 20 | make && \ 21 | make install && \ 22 | cd .. && \ 23 | rm --force --recursive Python-3.10.5 && \ 24 | ln --relative --symbolic /usr/local/bin/pip3 /usr/local/bin/pip && \ 25 | ln --relative --symbolic /usr/local/bin/python3 /usr/local/bin/python && \ 26 | pip3 install --upgrade pip 27 | 28 | # Install Ruby 3.1.x 29 | # https://www.ruby-lang.org/en/downloads/ 30 | cd /tmp && \ 31 | curl https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.2.tar.gz --output ruby-3.1.2.tar.gz && \ 32 | tar xzf ruby-3.1.2.tar.gz && \ 33 | rm --force ruby-3.1.2.tar.gz && \ 34 | cd ruby-3.1.2 && \ 35 | ./configure && \ 36 | make && \ 37 | make install && \ 38 | cd .. && \ 39 | rm --force --recursive ruby-3.1.2 40 | 41 | # Ubuntu-specific 42 | apt-get update && \ 43 | DEBIAN_FRONTEND=noninteractive apt-get install -y \ 44 | apt-file \ 45 | apt-transport-https \ 46 | apt-utils \ 47 | bash-completion \ 48 | bc \ 49 | bsdtar \ 50 | build-essential \ 51 | clang-8 \ 52 | cmake \ 53 | composer \ 54 | curl \ 55 | dnsutils \ 56 | dos2unix \ 57 | erlang \ 58 | exiftool \ 59 | expect `# For unbuffer` \ 60 | gdb \ 61 | gettext \ 62 | git \ 63 | golang \ 64 | haskell-platform \ 65 | imagemagick \ 66 | info \ 67 | libjpeg8-dev `For Pillow` \ 68 | lua5.3 \ 69 | luarocks \ 70 | man \ 71 | mysql-client \ 72 | nano \ 73 | ocaml \ 74 | octave \ 75 | perl \ 76 | php7.2-cli \ 77 | php7.2-curl \ 78 | php7.2-gmp \ 79 | php7.2-intl \ 80 | rpm \ 81 | ruby \ 82 | ruby-dev `# Avoid "can't find header files for ruby" for gem` \ 83 | s3cmd \ 84 | sqlite3 \ 85 | swi-prolog \ 86 | telnet \ 87 | tk-dev \ 88 | tree \ 89 | unzip \ 90 | valgrind \ 91 | vim \ 92 | zip 93 | 94 | # Git-specific 95 | # https://packagecloud.io/github/git-lfs/install 96 | curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash -e && \ 97 | DEBIAN_FRONTEND=noninteractive apt-get install -y git-lfs 98 | 99 | # Install Java 18.x 100 | # http://jdk.java.net/18/ 101 | cd /tmp && \ 102 | wget https://download.java.net/java/GA/jdk18.0.1.1/65ae32619e2f40f3a9af3af1851d6e19/2/GPL/openjdk-18.0.1.1_linux-x64_bin.tar.gz && \ 103 | tar xzf openjdk-18.0.1.1_linux-x64_bin.tar.gz && \ 104 | rm --force openjdk-18.0.1.1_linux-x64_bin.tar.gz && \ 105 | mv jdk-18.0.1.1 /opt/ && \ 106 | mkdir --parent /opt/bin && \ 107 | ln --symbolic /opt/jdk-18.0.1.1/bin/* /opt/bin/ && \ 108 | chmod a+rx /opt/bin/* 109 | 110 | # Install Node.js 16.x 111 | # https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions-enterprise-linux-fedora-and-snap-packages 112 | # https://github.com/nodesource/distributions/blob/master/README.md#debinstall 113 | curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ 114 | DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs && \ 115 | npm install -g npm `# Upgrades npm to latest` && \ 116 | npm install -g grunt http-server nodemon 117 | 118 | # Install Swift 5.1 119 | cd /tmp && \ 120 | wget https://swift.org/builds/swift-5.1.3-release/ubuntu1804/swift-5.1.3-RELEASE/swift-5.1.3-RELEASE-ubuntu18.04.tar.gz && \ 121 | tar xzf swift-5.1.3-RELEASE-ubuntu18.04.tar.gz --strip-components=1 -C / && \ 122 | rm -f swift-5.1.3-RELEASE-ubuntu18.04.tar.gz && \ 123 | apt-get update && \ 124 | DEBIAN_FRONTEND=noninteractive apt-get install -y libpython2.7 125 | 126 | # Install CS50 packages 127 | pip3 install \ 128 | check50 \ 129 | cs50 \ 130 | Flask \ 131 | Flask-Session \ 132 | style50 \ 133 | submit50 134 | pip3 install \ 135 | awscli \ 136 | compare50 \ 137 | help50 \ 138 | matplotlib \ 139 | numpy \ 140 | pandas \ 141 | render50 \ 142 | virtualenv 143 | 144 | # Install Heroku CLI 145 | curl https://cli-assets.heroku.com/install.sh | sh 146 | 147 | # Install fpm, asciidoctor 148 | # https://github.com/asciidoctor/jekyll-asciidoc/issues/135#issuecomment-241948040 149 | # https://github.com/asciidoctor/jekyll-asciidoc#development 150 | gem install \ 151 | asciidoctor \ 152 | bundler \ 153 | fpm \ 154 | github-pages:224 \ 155 | jekyll \ 156 | jekyll-asciidoc \ 157 | pygments.rb 158 | 159 | # Lua-specific 160 | # https://askubuntu.com/a/1035151 161 | update-alternatives --install /usr/bin/lua lua-interpreter /usr/bin/lua5.3 130 --slave /usr/share/man/man1/lua.1.gz lua-manual /usr/share/man/man1/lua5.3.1.gz 162 | update-alternatives --install /usr/bin/luac lua-compiler /usr/bin/luac5.3 130 --slave /usr/share/man/man1/luac.1.gz lua-compiler-manual /usr/share/man/man1/luac5.3.1.gz 163 | 164 | # LÖVE-specific 165 | # https://github.com/love2d/love/releases 166 | wget -P /tmp https://github.com/love2d/love/releases/download/0.10.2/love_0.10.2ppa1_amd64.deb && \ 167 | wget -P /tmp https://github.com/love2d/love/releases/download/0.10.2/liblove0_0.10.2ppa1_amd64.deb && \ 168 | (dpkg -i /tmp/love_0.10.2ppa1_amd64.deb /tmp/liblove0_0.10.2ppa1_amd64.deb || true) && \ 169 | DEBIAN_FRONTEND=noninteractive apt-get -f install -y && \ 170 | rm -f /tmp/love_0.10.2ppa1_amd64.deb /tmp/liblove0_0.10.2ppa1_amd64.deb 171 | 172 | # R-specific 173 | # https://www.rstudio.com/products/rstudio/download-server/ 174 | echo "deb https://cloud.r-project.org/bin/linux/ubuntu bionic-cran35/" > /etc/apt/sources.list.d/cran.list && \ 175 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9 176 | apt-get update && \ 177 | DEBIAN_FRONTEND=noninteractive apt-get install -y r-base && \ 178 | DEBIAN_FRONTEND=noninteractive apt-get install -y gdebi-core && \ 179 | wget -P /tmp https://download2.rstudio.org/rstudio-server-1.1.456-amd64.deb && \ 180 | DEBIAN_FRONTEND=noninteractive gdebi --non-interactive /tmp/rstudio-server-1.1.456-amd64.deb && \ 181 | rm -f /tmp/rstudio-server-1.1.456-amd64.deb && \ 182 | echo "server-app-armor-enabled=0" >> /etc/rstudio/rserver.conf && \ 183 | echo "www-frame-origin=any" >> /etc/rstudio/rserver.conf && \ 184 | echo "session-timeout-minutes=1" >> /etc/rstudio/rsession.conf 185 | cat <<'EOF' > /etc/rstudio/login.html 186 | 198 | EOF 199 | 200 | # CS50-specific 201 | curl -s https://packagecloud.io/install/repositories/cs50/repo/script.deb.sh | bash -e && \ 202 | DEBIAN_FRONTEND=noninteractive apt-get install -y astyle libcs50 libcs50-java php-cs50 203 | 204 | # Configure clang 8 last, else 7 takes priority 205 | (update-alternatives --remove-all clang || true) && \ 206 | update-alternatives --install /usr/bin/clang clang $(which clang-8) 1 207 | 208 | # Bash-specific 209 | mkdir -p /home/ubuntu/sandbox 210 | mkdir -p /home/ubuntu/.bashrcs 211 | cat <<'EOF' > /home/ubuntu/.bashrcs/~cs50.sh 212 | # File mode creation mask 213 | if [ "$(id -u)" != "0" ]; then 214 | umask 0077 215 | fi 216 | 217 | # PATH 218 | export PATH=/opt/cs50/bin:"$HOME"/.local/bin:/opt/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 219 | 220 | # Interactive shells 221 | if [ "$PS1" ]; then 222 | 223 | # Simplify prompt 224 | export PS1='$ ' 225 | 226 | # Override HOME for cd if ~/sandbox exists 227 | cd() 228 | { 229 | if [ -d "$HOME"/sandbox ]; then 230 | HOME=~/sandbox command cd "$@" 231 | else 232 | command cd "$@" 233 | fi 234 | } 235 | 236 | # Aliases 237 | alias cp="cp -i" 238 | alias gdb="gdb -q" 239 | alias ls="ls --color=auto -F" 240 | alias ll="ls -F -l" 241 | alias mv="mv -i" 242 | alias pip="pip3 --no-cache-dir" 243 | alias pip3="pip3 --no-cache-dir" 244 | alias python="python3" 245 | alias rm="rm -i" 246 | alias swift="swift 2> /dev/null" # https://github.com/cs50/sandbox/issues/26 247 | 248 | # Environment variables 249 | export CC="clang" 250 | export CFLAGS="-fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow" 251 | export CLASSPATH=".:/usr/share/java/cs50.jar" 252 | export EDITOR=nano 253 | export FLASK_APP=application.py 254 | export FLASK_ENV=development 255 | export LANG=C 256 | export LANGUAGE=C.UTF-8 257 | export LC_ALL=C.UTF-8 258 | export LDLIBS="-lcrypt -lcs50 -lm" 259 | export JAVA_HOME="/opt/jdk-16.0.2" 260 | export PYTHONDONTWRITEBYTECODE="1" 261 | export VALGRIND_OPTS="--memcheck:leak-check=full --memcheck:show-leak-kinds=all --memcheck:track-origins=yes" 262 | 263 | # History 264 | # https://www.shellhacks.com/tune-command-line-history-bash/ 265 | shopt -s histappend # Append Bash Commands to History File 266 | export PROMPT_COMMAND='history -a' # Store Bash History Immediately 267 | shopt -s cmdhist # Use one command per line 268 | if [ "$(id -u)" == "0" ]; then 269 | export HISTFILE=~/sandbox/.bash_history # Change the History File Name 270 | fi 271 | 272 | # make 273 | make () { 274 | local args="" 275 | local invalid_args=0 276 | 277 | for arg; do 278 | case "$arg" in 279 | (*.c) arg=${arg%.c}; invalid_args=1;; 280 | esac 281 | args="$args $arg" 282 | done 283 | 284 | if [ $invalid_args -eq 1 ]; then 285 | echo "Did you mean 'make$args'?" 286 | return 1 287 | else 288 | command make -B $* 289 | fi 290 | } 291 | fi 292 | 293 | # cmd 294 | EOF 295 | chmod a+rwx /home/ubuntu/* 296 | 297 | # /opt/cs50/bin 298 | mkdir -p /opt/cs50/bin 299 | cat <<'EOF' > /opt/cs50/bin/flask 300 | #!/bin/bash 301 | 302 | if [[ "$1" == "run" ]]; then 303 | 304 | # Default options 305 | host="--host=0.0.0.0" 306 | port="--port=8080" 307 | reload="--reload" 308 | 309 | # Override default options 310 | options="" 311 | shift 312 | while test ${#} -gt 0 313 | do 314 | if [[ "$1" =~ ^--host= || "$1" =~ ^-h[^\s]+ ]]; then 315 | host="$1" 316 | elif [[ "$1" == "-h" || "$1" == "--host" ]]; then 317 | host="$1 $2" 318 | shift 319 | elif [[ "$1" =~ ^--port= || "$1" =~ ^-p[^\s]+ ]]; then 320 | port="$1" 321 | elif [[ "$1" == "-p" || "$1" == "--port" ]]; then 322 | port="$1 $2" 323 | shift 324 | elif [[ "$1" =~ ^--(no-)?reload$ ]]; then 325 | reload="$1" 326 | elif [[ "$1" =~ ^--with(out)?-threads$ ]]; then 327 | threads="$1" 328 | else 329 | options+=" $1" 330 | fi 331 | shift 332 | done 333 | 334 | # Kill any process listing on the specified port 335 | # using regex to handle -pxxxx, -p xxxx, --port xxxx, --port=xxxx 336 | fuser --kill -INT "${port//[^0-9]}/tcp" &> /dev/null 337 | 338 | # Spawn flask 339 | FLASK_APP="$FLASK_APP" FLASK_DEBUG="${FLASK_DEBUG:-0}" unbuffer /usr/local/bin/flask run $host $port $reload $threads $options | sed "s#\(.*http://\)[^:]\+\(:.\+\)#\1localhost\2#" 340 | else 341 | /usr/local/bin/flask "$@" 342 | fi 343 | EOF 344 | cat <<'EOF' > /opt/cs50/bin/http-server 345 | #!/bin/bash 346 | # Default options 347 | a="-a 0.0.0.0" 348 | c="-c-1" 349 | cors="--cors" 350 | i="-i false" 351 | port="-p 8080" 352 | options="--no-dotfiles" 353 | # Override default options 354 | while test ${#} -gt 0 355 | do 356 | if [[ "$1" =~ ^-a$ ]]; then 357 | a="$1 $2" 358 | shift 359 | shift 360 | elif [[ "$1" =~ ^-c-?[0-9]+$ ]]; then 361 | c="$1" 362 | shift 363 | elif [[ "$1" =~ ^--cors(=.*)?$ ]]; then 364 | cors="$1" 365 | shift 366 | elif [[ "$1" =~ ^-i$ ]]; then 367 | i="$1 $2" 368 | shift 369 | shift 370 | elif [[ "$1" =~ ^-p[^\s]+ ]]; then 371 | port="$1" 372 | shift 373 | elif [[ "$1" == "-p" ]]; then 374 | port="$1 $2" 375 | shift 376 | shift 377 | else 378 | options+=" $1" 379 | shift 380 | fi 381 | done 382 | # Kill any process listing on the specified port 383 | # using regex to handle -pxxxx, -p xxxx, --port xxxx, --port=xxxx 384 | fuser --kill "${port//[^0-9]}/tcp" &> /dev/null 385 | # Spawn http-server, retaining colorized output using expect's unbuffer 386 | unbuffer "$(npm prefix -g)/bin/http-server" $a $c $cors $i $port $options | unbuffer -p sed "s#\(.*http://\)[^:]\+\(:.\+\)#\1localhost\2#" | uniq 387 | EOF 388 | chmod a+rx /opt/cs50/bin/* 389 | 390 | # Ubuntu-specific 391 | useradd --home-dir /home/ubuntu/sandbox --shell /bin/bash ubuntu && \ 392 | umask 0077 && \ 393 | mkdir -p /home/ubuntu && \ 394 | chown -R ubuntu:ubuntu /home/ubuntu 395 | -------------------------------------------------------------------------------- /next.css: -------------------------------------------------------------------------------- 1 | /* Pixelate images */ 2 | #project:not(.editable) .remote-img-inner { 3 | image-rendering: pixelated; 4 | } 5 | 6 | /* Hide icons */ 7 | #project:not(.editable) .pane-bottom-nav { 8 | display: none; 9 | } 10 | #project:not(.editable) .code-space .codemirror-ide-editor { 11 | max-height: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /scripts.js: -------------------------------------------------------------------------------- 1 | /* CS50 scripts.js */ 2 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* Pixelate images */ 2 | #project:not(.editable) .remote-img-inner { 3 | image-rendering: pixelated; 4 | } 5 | 6 | /* Eliminate min-width for mobile's sake */ 7 | #project { 8 | min-width: auto; 9 | } 10 | .modal-dialog { 11 | width: auto; 12 | } 13 | 14 | /* Hide lefthand sidebar */ 15 | #project:not(.editable) > #left-sidebar-nav { 16 | display: none; 17 | } 18 | #project:not(.editable) > .project-inner { 19 | max-width: unset; 20 | } 21 | 22 | /* https://feedback.codevolve.com/bug-reports/p/thin-white-border-along-righthand-side-of-vertical-project-innerreflex-element */ 23 | #project:not(.editable) > .project-inner > .reflex-element:not(.hide) { 24 | flex-grow: 1 !important; 25 | } 26 | 27 | /* Match padding around text in tabs */ 28 | #project:not(.editable) #directory-sidebar > .file-list { 29 | padding-left: 8px; 30 | padding-top: 3px; 31 | } 32 | 33 | /* Hide filetree's header */ 34 | #project:not(.editable) #directory-sidebar > .directory-sidebar__header { 35 | display: none; 36 | } 37 | 38 | /* Reveal filetree's ellipses */ 39 | #project #directory-sidebar .file-list .file .file-menu .file__menu-button { 40 | display: initial; 41 | } 42 | 43 | /* Shift filetree downward */ 44 | #project:not(.editable) #directory-sidebar .file-list > ul > li > .file { 45 | margin-top: 2px; 46 | } 47 | 48 | /* Don't highlight filetree's root on hover */ 49 | #project:not(.editable) #directory-sidebar .file-list > ul > li > .file:hover .active-bar { 50 | background-color: initial; 51 | } 52 | 53 | /* Hide filetree's root */ 54 | #project:not(.editable) #directory-sidebar .file-list > ul > li > .file .filename { 55 | display: none; 56 | } 57 | 58 | /* Deindent root's children */ 59 | #project:not(.editable) #directory-sidebar .file-list > ul > li > ul { 60 | padding-left: 0 !important; 61 | } 62 | 63 | /* Change filetree's first ellipsis to a plus */ 64 | /* https://fontawesome.com/icons/plus?style=solid, http://fatopng.com/fontawesome-to-base64 */ 65 | #project:not(.editable) #directory-sidebar .file-list > ul > li > .file .file-menu .file__menu-button::before { 66 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABaklEQVQ4jaWUsUoEMRRFz12WjGyxlcgWIhYiaCP4CfoLFv6Fn+NPWFgLgghWIiiINhYiFltYLSJmkL0WzmiIcdfVC4FM8nLe5b1kZExJday7wADo20ZSDQxDFZ6LBxp1JuytAPvAuaQL28fAziQYQHfC3iKwbrvXfC8BG/8BdgAkfVubpKkBqexyvWcCppDM7ezAprtTIakUY+wDu7bXJHUSyJLtLaCfxF9KOs0SjoEr4CBU4VkxxlXbZ5LmSw6/OSg7fgC2QxXuOkCQNN8ezmuWj1Ii2wtAgK8ajtvg1IFt0kTtvOBy3E66wAg4AZazoF5ThvSujoCnnCbpxvYIPpoSbK8CA0lp1zdt70kaJI4PJe23jiVheww8SroLVXjrhirUwHUzPlXHGkkvmZOHUIWj3GGqH+/hb17FTMDS9fjX02tqk6u09jugpCFwn0CegNtpQP30x46vMTQd7gPYroFhNVeN/gT8q94B/WeewuZkyj4AAAAASUVORK5CYII='); 67 | } 68 | 69 | /* Hide "No Files" */ 70 | #project:not(.editable) #directory-sidebar .file-list .directory-no-files { 71 | display: none; 72 | } 73 | 74 | /* Hide text in empty panes */ 75 | #project:not(.editable) .empty-pane-inner { 76 | display: none; 77 | } 78 | 79 | /* Hide icons */ 80 | #project:not(.editable) .pane-bottom-nav { 81 | display: none; 82 | } 83 | #project:not(.editable) .code-space .codemirror-ide-editor { 84 | max-height: 100%; 85 | } 86 | 87 | /* Allow GUI to grow */ 88 | #project .full-gui .inner .full-gui-frame-outer { 89 | max-height: none; 90 | } 91 | --------------------------------------------------------------------------------