├── .gitignore ├── Dockerfile ├── Makefile ├── README.md └── files ├── check-environment └── workspace-cs50.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | files/plugins/ 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cloud9/ws-php 2 | MAINTAINER Dan Armendariz 3 | 4 | # increment version to force flushing the cache 5 | RUN echo "Version 2.0" 6 | 7 | ENV DEBIAN_FRONTEND noninteractive 8 | 9 | RUN wget -O - http://mirror.cs50.net/ide50/2015/keyFile | sudo apt-key add - 10 | RUN add-apt-repository "deb http://mirror.cs50.net/ide50/2015/dists/trusty/main/binary-amd64/ /" 11 | RUN rm -rf /etc/php5/mods-available/xdebug.ini /home/ubuntu/workspace/* 12 | 13 | ENV PATH="/usr/local/rvm/bin/:$PATH" 14 | RUN curl -H 'Cache-Control: no-cache' -sL https://cs50.ly/update50 | sudo -H -u ubuntu bash 15 | 16 | RUN echo "Success" > /var/www/html/file 17 | 18 | RUN chown -R ubuntu:ubuntu /home/ubuntu && \ 19 | chown -R ubuntu:ubuntu /home/ubuntu 20 | RUN curl -sL https://deb.nodesource.com/setup | bash - && \ 21 | apt-get install nodejs -y 22 | 23 | # populate some env variables 24 | RUN echo "export USER=ubuntu\n\ 25 | export C9_PROJECT=ide50-offline\n\ 26 | export C9_USER=jharvard\n\ 27 | export C9_HOSTNAME=\$IP\n\ 28 | export C9_PORT=\$PORT\n\ 29 | export IDE_OFFLINE=1\n\ 30 | alias c9=/var/c9sdk/bin/c9" >/etc/profile.d/offline.sh 31 | 32 | # since C9_USER didn't exist until now, mysql.sh doesn't have username 33 | RUN sed -i 's/MYSQL_USERNAME.*/MYSQL_USERNAME="jharvard"/' \ 34 | /etc/profile.d/mysql.sh 35 | 36 | USER ubuntu 37 | 38 | # download C9 core 39 | WORKDIR /var 40 | RUN sudo rm -rf c9sdk && \ 41 | sudo mkdir c9sdk && \ 42 | sudo chown ubuntu:ubuntu c9sdk && \ 43 | git clone https://github.com/c9/core.git c9sdk 44 | 45 | # install CS50's plugins 46 | WORKDIR c9sdk 47 | COPY ./files/plugins plugins/ 48 | RUN sudo chown -R ubuntu:ubuntu plugins 49 | 50 | # install CS50's configuration 51 | ADD ./files/workspace-cs50.js configs/ide/ 52 | 53 | # install C9 54 | RUN scripts/install-sdk.sh 55 | 56 | # set defaults 57 | RUN sudo chown -R ubuntu:ubuntu /home/ubuntu/workspace/ && \ 58 | sudo chown -R ubuntu:ubuntu /home/ubuntu/.c9/ 59 | 60 | ADD files/check-environment /.check-environment/cs50 61 | 62 | EXPOSE 5050 8080 8081 8082 63 | ENTRYPOINT ["node", "server.js", \ 64 | "-w", "/home/ubuntu/workspace", \ 65 | "--workspacetype=cs50", \ 66 | "--auth", ":", \ 67 | "--collab", \ 68 | "--listen", "0.0.0.0", \ 69 | "--port", "5050"] 70 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | IMG_WKSPC=workspace 2 | IMG_IDE=cs50/ide 3 | CON_OFF=ide50 4 | IP := 127.0.0.1 5 | 6 | PLUGINS := audioplayer browser cat debug gist hex info presentation simple statuspage theme 7 | 8 | # pick right tool for opening IDE in browser 9 | ifeq ($(shell uname), Linux) 10 | OPEN=xdg-open 11 | else 12 | OPEN=open 13 | endif 14 | 15 | define getplugin 16 | @echo "\nFetching $(1)..." 17 | @plugin_dir="files/plugins/c9.ide.cs50.$(1)"; \ 18 | mkdir -p "$$plugin_dir"; \ 19 | git clone --depth=1 "git@github.com:cs50/harvard.cs50.$(1).git" "$$plugin_dir"; \ 20 | rm -rf "$$plugin_dir/README.md" "$$plugin_dir/.git"* 21 | 22 | endef 23 | 24 | run: 25 | docker run -e "IP=$(IP)" -e "PORT=8080" \ 26 | --name $(CON_OFF) -d -t \ 27 | --security-opt seccomp=unconfined \ 28 | -p 5050:5050 -p 8080:8080 -p 8081:8081 -p 8082:8082 \ 29 | $(IMG_IDE) 2>/dev/null \ 30 | || docker start $(CON_OFF) 31 | 32 | open: 33 | $(OPEN) http://$(IP):5050/ide.html >/dev/null 2>&1 34 | 35 | shell: run 36 | docker exec -it $(CON_OFF) /bin/bash 37 | 38 | restart: 39 | docker restart $(CON_OFF) || true 40 | 41 | stop: 42 | docker stop $(CON_OFF) || true 43 | 44 | build: 45 | rm -rf files/plugins 46 | mkdir files/plugins 47 | $(foreach plugin,$(PLUGINS),$(call getplugin,$(plugin))) 48 | rm -rf files/plugins/*/.{git,gitignore} 49 | docker build --no-cache -t $(IMG_IDE) . 50 | 51 | # removal 52 | clean: stop 53 | rm -rf files/plugins || true 54 | docker rm $(CON_OFF) || true 55 | docker rmi $(IMG_IDE) || true 56 | 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `cs50/ide` 2 | 3 | CS50 IDE's Docker configuration for Cloud9 4 | 5 | Since Docker containers are built incrementally, building an offline container 6 | requires first creating an underlying workspace image, then building 7 | an ide50 image on top of that, and then finally creating the 8 | ide50-offline image on top. There are some `make` commands to 9 | simplify this process. 10 | 11 | # Making changes to offline: 12 | 13 | The primary way to make changes to the offline workspace is to add things 14 | (required packages, scripting various installation steps, etc) to the [ide50 15 | Debian package](https://github.com/cs50/ide50) which is also used for the 16 | online version of the IDE. The build process below builds an offline IDE 17 | based on the publicly-accessible IDE50 package on mirror.cs50.net. 18 | 19 | # Testing offline changes: 20 | 21 | First build an offline workspace from scratch using the "To build all from 22 | scratch" steps below. 23 | 24 | Next, make changes to the [ide50 Debian package](https://github.com/cs50/ide50), 25 | build it, and follow the directions on that repo to locally install the deb 26 | on the offline workspace (also be sure to test it on an online workspace!) 27 | you generated above. 28 | 29 | If all goes well, publish the deb to the mirror and build the offline 30 | workspace from scratch, once there has been sufficient time for all mirrors 31 | to receive the new deb. 32 | 33 | # To build all from scratch: 34 | 35 | 1. Install [Docker Engine](https://docs.docker.com/engine/installation/). 36 | 1. Open a terminal window. 37 | 1. Execute `cd /path/to/ide50-docker`. 38 | 1. Execute `make build` and wait. This builds all images sequentially. 39 | 1. Start the built offline container with `make run`. 40 | 1. (Mac OS X only) Execute `make open` to automatically open a tab in your favorite 41 | browser that loads the offline IDE. 42 | 43 | # Rebuild after making changes 44 | 45 | 1. Stop the running offline container with `make stop` 46 | 1. Delete offline image with `make clean` 47 | 1. Re-build offline (only) with `make offline` 48 | 49 | Rebuilding from an earlier version (say, if you need to build a new 50 | ide50 container with a new version of the ide50 deb) will require 51 | rebuilding from that container. It's probably easiest to delete all images 52 | and then run `make build`. 53 | 54 | # Deploying a new offline image 55 | 56 | We generally deploy a new offline version when a new version of the 57 | [ide50 deb](https://github.com/cs50/ide50) is released. 58 | This way, people that download the offline version are sure to have the 59 | very latest. 60 | 61 | ## Preparation 62 | 63 | 1. Clean all existing images and containers. Building from scratch is 64 | generally preferred since it ensures the latest version of Ubuntu 65 | and other packages. Use `docker ps -a` to see a full list of docker 66 | containers, stopping any relevant ones if necessary, and remove them 67 | with `docker rm`. Use `docker images` to see a list of images, 68 | and use `docker rmi` to delete those images. I usually delete all images 69 | that are associated with the offline IDE to be sure to build from scratch. 70 | 2. Run `make build` to build from scratch. 71 | 3. Run the offline with `make run` to ensure the latest deb was installed 72 | and all changes are as they appear. 73 | 74 | ## Deployment 75 | 76 | If all looks good after a successful complete build, begin the actual 77 | deployment steps: 78 | 79 | 1. Run `make squash` to [squash](https://github.com/jwilder/docker-squash) the 80 | docker image into as small of a size as possible. Note: docker squash 81 | tools tend to change rapidly, so you may need to update the `squash` rule 82 | in the Makefile periodically, or update the copy of the docker-squash. 83 | 2. Once that's done, apply both "beta" and "latest" tags to the build 84 | version. "Beta" builds are at least as new as the "latest", but sometimes 85 | its useful to release just a "beta" build with features that others test: 86 | ```shell 87 | docker tag ide50-offline cs50/ide50-offline:beta 88 | docker tag ide50-offline cs50/ide50-offline:latest 89 | ``` 90 | 3. Push the tags to Docker hub: 91 | ```shell 92 | docker push cs50/ide50-offline:beta 93 | docker push cs50/ide50-offline:latest 94 | ``` 95 | 96 | ## Command list 97 | 98 | There are a variety of commands in `make` to help re-build an image. 99 | * `make build` Builds the `wkspc` image, then the `ide` image, then 100 | the `offline` image. 101 | * Build individual images with `make wkspc`, `make ide`, and 102 | `make offline`, respectively. 103 | * `make run` runs an already-built offline container 104 | * `make stop` then stops that running container 105 | * `make open` (Mac OS X only, probably) opens the offline IDE in your browser. 106 | * `make shell` to open a shell in the running container 107 | * `make clean` removes the offline image and container *only* 108 | 109 | # To install for cloud9: 110 | 1. Change line 1 of ide50/Dockerfile to FROM cloud9/workspace 111 | 2. Zip ide50 (zip -r ide50 ide50). 112 | 3. Email to Nikolai Onken, nikolai@c9.io 113 | 114 | # HOWTOs 115 | 116 | ## Re-build SDK 117 | 118 | After making changes to CSS (e.g., in `/var/c9sdk/plugins/*`) or config files (e.g., `/var/c9sdk/configs/ide/workspace-cs50.js`): 119 | 120 | cd /path/to/ide50-docker 121 | make shell 122 | /var/c9sdk/scripts/install-sdk.sh 123 | exit 124 | make restart 125 | 126 | # Troubleshooting 127 | 128 | ## Error checking TLS connection: Error checking and/or regenerating the certs 129 | 130 | docker-machine regenerate-certs default 131 | 132 | If something still seems awry with Docker, odds are the below will help. **The below will delete and recreate the virtual machine used by Docker.** 133 | 134 | docker-machine stop default 135 | docker-machine rm default 136 | docker-machine create --driver virtualbox default 137 | eval $(docker-machine env default) 138 | -------------------------------------------------------------------------------- /files/check-environment: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # parse command-line arguments 4 | while [ $# -gt 0 ]; do 5 | case "$1" in 6 | 7 | # whether to match ide50 version on mirror 8 | --check-mirror) 9 | check_mirror=1 10 | ;; 11 | esac 12 | 13 | shift 14 | done 15 | 16 | RED="\e[0;31m" 17 | GREEN="\e[0;32m" 18 | NO_COLOR="\e[0m" 19 | 20 | color_print() { 21 | eval echo -en "\$$2" 22 | echo -n "$1" 23 | echo -e "$NO_COLOR" 24 | } 25 | 26 | scripts_on_path() { 27 | while [ $# -gt 0 ]; do 28 | test -x "$(which $1 2>&1 /dev/null)" 29 | if [ $? -ne 0 ]; then 30 | color_print "$1 is not on PATH" RED 31 | exit $? 32 | fi 33 | shift 34 | done 35 | } 36 | 37 | version_matches() { 38 | eval "$1 2>&1 | grep -q \"$2\"" 39 | if [ $? -ne 0 ]; then 40 | color_print "$1's version doesn't match" RED 41 | exit $? 42 | fi 43 | } 44 | 45 | py_pkg_version_matches() { 46 | version_matches "pip show $1" "Version: $2" 47 | } 48 | 49 | # verify IDE was installed 50 | ide50_version="$(version50)" 51 | if [ -z "$ide50_version" ]; then 52 | color_print "ide50 package is not properly installed" RED 53 | exit 1 54 | fi 55 | 56 | # check installed version against latest in mirror 57 | if [ $check_mirror ]; then 58 | MIRROR=http://mirror.cs50.net/ide50/2015/dists/trusty/main/binary-amd64/Packages 59 | latest_ide50_version=$(curl $MIRROR 2>/dev/null | sed -n "s/.*ide50_\([0-9]\+\)_.*/\1/p") 60 | 61 | if [ -z "$latest_ide50_version" ]; then 62 | color_print "Could not fetch latest ide50 package version from $MIRROR" RED 63 | exit 1 64 | fi 65 | 66 | if [ "$ide50_version" -ne "$latest_ide50_version" ]; then 67 | color_print "ide50 installed version does not match repo version" RED 68 | color_print "Did you increment 'Version' in ws-cs50/Dockerfile?" RED 69 | exit 1 70 | fi 71 | fi 72 | 73 | # Java 7 74 | version_matches "java -version" "java version.*1.7" 75 | 76 | # Pyhton 3.6 77 | version_matches "python --version" 3.6 78 | 79 | # node 80 | version_matches "node --version" 9.8.0 81 | 82 | # ruby 2.4.0 83 | version_matches "ruby --version" 2.4.0 84 | 85 | # Python packages 86 | py_pkg_version_matches check50 2.2.1 87 | py_pkg_version_matches cs50 2.4.0 88 | py_pkg_version_matches style50 2.4.2 89 | py_pkg_version_matches render50 2.4.4 90 | py_pkg_version_matches submit50 2.4.6 91 | 92 | # scripts on PATH 93 | scripts_on_path \ 94 | .info50 \ 95 | check50 \ 96 | debug50 \ 97 | flask \ 98 | hostname50 \ 99 | phpliteadmin \ 100 | render50 \ 101 | style50 \ 102 | submit50 \ 103 | update50 104 | 105 | color_print PASSED GREEN 106 | -------------------------------------------------------------------------------- /files/workspace-cs50.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(options) { 4 | // Remove runners we don't want 5 | delete options.runners["C (simple)"]; 6 | delete options.runners["PHP (cli)"]; 7 | delete options.runners["PHP (built-in web server)"]; 8 | delete options.runners["Apache httpd (PHP, HTML)"]; 9 | 10 | options.projectName = "ide50-offline"; 11 | 12 | var config = require("./default")(options); 13 | 14 | var includes = [ 15 | { 16 | packagePath: "plugins/c9.ide.cs50.audioplayer/audioplayer", 17 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.audioplayer" 18 | }, 19 | { 20 | packagePath: "plugins/c9.ide.cs50.browser/browser", 21 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.browser" 22 | }, 23 | { 24 | packagePath: "plugins/c9.ide.cs50.cat/cat", 25 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.cat" 26 | }, 27 | { 28 | packagePath: "plugins/c9.ide.cs50.debug/debug", 29 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.debug" 30 | }, 31 | { 32 | packagePath: "plugins/c9.ide.cs50.gist/gist", 33 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.gist" 34 | }, 35 | { 36 | packagePath: "plugins/c9.ide.cs50.hex/hex", 37 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.hex" 38 | }, 39 | { 40 | packagePath: "plugins/c9.ide.cs50.hex/openhex", 41 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.hex" 42 | }, 43 | { 44 | packagePath: "plugins/c9.ide.cs50.info/info", 45 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.info" 46 | }, 47 | { 48 | packagePath: "plugins/c9.ide.cs50.presentation/presentation", 49 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.presentation" 50 | }, 51 | { 52 | packagePath: "plugins/c9.ide.cs50.simple/simple", 53 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.simple" 54 | }, 55 | { 56 | packagePath: "plugins/c9.ide.cs50.statuspage/statuspage", 57 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.statuspage" 58 | }, 59 | { 60 | packagePath: "plugins/c9.ide.cs50.theme/theme", 61 | staticPrefix: options.staticPrefix + "/plugins/c9.ide.cs50.theme" 62 | } 63 | ]; 64 | 65 | var excludes = { 66 | "plugins/c9.ide.collab/chat/chat": true, 67 | "plugins/c9.ide.collab/collabpanel": true, 68 | "plugins/c9.ide.collab/members/members": true, 69 | "plugins/c9.ide.collab/members/members_panel": true, 70 | "plugins/c9.ide.collab/notifications/notifications": true, 71 | "plugins/c9.ide.collab/share/share": true, 72 | "plugins/c9.ide.welcome/welcome": true 73 | }; 74 | 75 | config = config.concat(includes).map(function(p) { 76 | if (typeof p == "string") 77 | p = { packagePath: p }; 78 | return p; 79 | }).filter(function (p) { 80 | if (p.packagePath == "plugins/c9.ide.layout.classic/preload") { 81 | p.defaultTheme = "flat-light"; // set flat theme as default 82 | } 83 | else if (p.packagePath == "plugins/c9.fs/fs.cache.xml") { 84 | p.rootLabel = "~/workspace"; 85 | } 86 | else if (p.packagePath == "plugins/c9.ide.console/console") { 87 | p.defaultState = { 88 | type: "pane", 89 | nodes: [{ 90 | type: "tab", 91 | editorType: "terminal", 92 | active: "true" 93 | }] 94 | }; 95 | } 96 | else if (p.packagePath == "plugins/c9.ide.tree/favorites") { 97 | p.realRoot = false; 98 | } 99 | return !excludes[p.packagePath]; 100 | }); 101 | 102 | return config; 103 | }; 104 | --------------------------------------------------------------------------------