├── .dockerignore ├── .gitignore ├── entrypoint.sh ├── LICENSE ├── homesync.sh ├── Dockerfile ├── README.md └── amet.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | home-* 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | home-* 2 | 3 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | 5 | USER=$(whoami) 6 | GROUP=$(id -gn) 7 | syncFreq=${DEV_SYNC_FREQ:-900} 8 | 9 | cleanup() { 10 | /homesync.sh -d /home/$USER -p 11 | } 12 | 13 | # ON FIRST RUN ONLY 14 | if [ "$$" -eq 1 ]; then 15 | # Check if we're doing an active sync 16 | if [ -d "/sync" ]; then 17 | # Sync existing data into user home if this is the first run 18 | sudo chown -R $USER:$GROUP /sync 19 | rsync -a /sync/ /home/$USER 20 | 21 | # Start sync service 22 | /homesync.sh -d /home/$USER -l $syncFreq & 23 | 24 | # Continue this script after a SIGTERM 25 | trap 'cleanup' TERM 26 | fi 27 | 28 | # Start Docker 29 | sudo service docker start 30 | 31 | # Set up SSH 32 | sudo chown -R $USER:root /etc/ssh/$USER 33 | sudo service ssh start 34 | fi 35 | 36 | # pre-eval the env vars 37 | CMD=$(eval echo $@) 38 | 39 | # Run the command 40 | echo ">>> Executing: $CMD" 41 | ${CMD} & 42 | 43 | # Wait for the signal 44 | wait $! 45 | 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /homesync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # CONSTANTS 4 | LOCKFILE=/tmp/homesync.lock 5 | 6 | # DEFAULTS 7 | loopSecs=0 8 | pauseForParent=0 9 | forceQuit=0 10 | syncDir="" 11 | 12 | # PROFESSIONAL HELP 13 | showHelp() { 14 | echo "Usage: $0 -d sync_directory [options]" 15 | echo "" 16 | echo " Available options:" 17 | echo "" 18 | echo " -d The directory to be synced" 19 | echo " -h Show this help" 20 | echo " -l Loop the sync this many seconds. 0 to disable. Default $loopSecs." 21 | echo " -p Pause exiting until previous PID exits" 22 | echo "" 23 | echo " This script is concurrency-safe and will exit without taking action" 24 | echo " if another instance is already running. Use the -p flag to prevent" 25 | echo " the script from exiting until the previous process has exited." 26 | echo "" 27 | } 28 | 29 | # PARSE CLI OPTIONS 30 | while getopts ':d:hl:p' OPT; do 31 | case "$OPT" in 32 | d) syncDir="$OPTARG" ;; 33 | h) showHelp; exit 0 ;; 34 | l) loopSecs="$OPTARG" ;; 35 | p) pauseForParent=1 ;; 36 | ?) showHelp; exit 1 ;; 37 | esac 38 | done 39 | 40 | # VERIFY THAT WE HAVE A SYNC DIR 41 | if [ -z "$syncDir" ] || [ ! -d "$syncDir" ]; then 42 | echo "Cannot find sync dir: $syncDir" 43 | exit 2 44 | fi 45 | 46 | # MAKE SURE THE SYNC DIR ENDS IN A SLASH 47 | strPos=$((${#syncDir}-1)) 48 | lastChar=${syncDir:$strPos:1} 49 | [[ $lastChar != "/" ]] && syncDir+="/" 50 | 51 | # CHECK FOR A PREVIOUSLY-STARTED PROCESS 52 | if [ -f "/tmp/homesync.lock" ]; then 53 | pid=$(cat $LOCKFILE) 54 | if kill -0 $pid > /dev/null 2>&1; then 55 | if [ $pauseForParent -eq 0 ]; then 56 | echo ">>> [homesync:$$] Already running in PID $pid; exiting" 57 | else 58 | echo ">>> [homesync:$$] Already running in PID $pid; waiting..." 59 | while kill -0 $pid > /dev/null 2>&1; do 60 | sleep 1 61 | done 62 | fi 63 | exit 0 64 | fi 65 | fi 66 | 67 | # WRITE CURRENT PID TO LOCKFILE 68 | echo $$ > $LOCKFILE 69 | 70 | # THIS IS HOW WE SYNC 71 | running=0 72 | runSync() { 73 | [ $running -eq 1 ] && return 0 74 | running=1 75 | echo ">>> [homesync:$$] Syncing $syncDir..." 76 | rsync -a "$syncDir" /sync 77 | 78 | if [ "$?" -ne 0 ]; then 79 | echo ">>> [homesync:$$] Failed to sync $syncDir" 80 | exit 3 81 | fi 82 | 83 | echo ">>> [homesync:$$] Done" 84 | running=0 85 | } 86 | 87 | # TERMINATE CLEANLY 88 | trap 'loopSecs=0; forceQuit=1; runSync' TERM 89 | 90 | # SYNC IT LIKE YOU MEAN IT 91 | while true; do 92 | runSync 93 | [ $loopSecs -eq 0 ] && break 94 | sleep $loopSecs 95 | [ $forceQuit -eq 1 ] && exit 0 96 | done 97 | 98 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | # SETUP, CONFIG 4 | ARG username=amet 5 | ARG password=password 6 | ARG shell=bash 7 | ARG timezone=UTC 8 | ARG lang=en_US.UTF-8 9 | ARG syncFreq=900 10 | ARG fsEngine=aufs 11 | ARG groupname=$username 12 | ARG userUid=1000 13 | ARG userGid=1000 14 | 15 | ENV DEV_USERNAME $username 16 | ENV DEV_PASSWORD $password 17 | ENV DEV_SHELL /bin/$shell 18 | ENV DEV_SYNC_FREQ $syncFreq 19 | ENV TZ $timezone 20 | 21 | EXPOSE 3000 22 | 23 | # REQUIRED FOR RUNNING CODE-SERVER AND OTHER AMET FEATURES 24 | RUN apt-get update && apt-get install -y \ 25 | git zsh apt-transport-https \ 26 | ca-certificates curl software-properties-common \ 27 | build-essential wget openssl net-tools locales \ 28 | sudo openssh-server rsync vim && \ 29 | echo "AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2 /etc/ssh/%u/authorized_keys" >> /etc/ssh/sshd_config && \ 30 | mkdir -p /etc/ssh/$username && \ 31 | (locale-gen $lang || locale-gen en_US.UTF-8) && \ 32 | ln -snf /usr/share/zoneinfo/$timezone /etc/localtime && echo $TZ > /etc/timezone && \ 33 | apt-get install tzdata 34 | 35 | # INSTALL CODE-SERVER 36 | RUN wget https://github.com/codercom/code-server/releases/download/1.691-vsc1.33.0/code-server1.691-vsc1.33.0-linux-x64.tar.gz && \ 37 | tar -xvzf code-server1.691-vsc1.33.0-linux-x64.tar.gz -C /tmp && \ 38 | mv /tmp/code-server1.691-vsc1.33.0-linux-x64/code-server /bin/code-server 39 | 40 | # INSTALL DOCKER 41 | RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && \ 42 | apt-key fingerprint 0EBFCD88 && \ 43 | add-apt-repository \ 44 | "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ 45 | $(lsb_release -cs) \ 46 | stable" && \ 47 | apt-get update && \ 48 | apt-get install -y docker-ce && \ 49 | mkdir /etc/docker && \ 50 | echo "{\"storage-driver\":\"$fsEngine\"}" > /etc/docker/daemon.json && \ 51 | service docker start 52 | 53 | # CREATE USER 54 | RUN groupadd $groupname -g $userGid && \ 55 | useradd \ 56 | -ms $DEV_SHELL \ 57 | -u $userUid \ 58 | -g $groupname \ 59 | -p "$(openssl passwd -1 $DEV_PASSWORD)" \ 60 | $username && \ 61 | usermod -a -G docker,root $username && \ 62 | echo "$username ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/nopasswd && \ 63 | chmod 400 /etc/sudoers.d/nopasswd && \ 64 | chown -R $username:root /etc/ssh/$username 65 | 66 | # SWITCH TO USER. EVERYTHING BEYOND THIS POINT WILL BE DONE WITH USER'S UID/GID 67 | WORKDIR /home/$username 68 | USER $username:$groupname 69 | 70 | # RUN USER CUSTOMIZATIONS 71 | COPY ./.amet-customizer.sh /customizer.sh 72 | RUN sh /customizer.sh 73 | 74 | # STARTUP 75 | COPY ./.amet-key.pub /etc/ssh/$username/authorized_keys 76 | COPY ./homesync.sh / 77 | COPY ./entrypoint.sh / 78 | ENTRYPOINT [ "/entrypoint.sh" ] 79 | CMD ["code-server", "/home/$DEV_USERNAME/workspace", "-p", "3000", "-d", "/home/$DEV_USERNAME/code-server", "--password=$DEV_PASSWORD"] 80 | 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amet 2 | 3 | Containerized, portable development environment. 4 | 5 | ## Introduction 6 | 7 | The concept of this project is to create a development environment that utilizes [Docker](https://www.docker.com) 8 | and [Code-Server](https://github.com/codercom/code-server) to create a fully functional, browser-based, portable 9 | development environment. 10 | 11 | ## Quick Start 12 | 13 | Clone the repo: 14 | 15 | ``` 16 | git clone git@github.com:Fluidbyte/amet.git 17 | ``` 18 | 19 | Run the script, supplying desired `username` and `password` for your development environment. 20 | 21 | ```shell 22 | ./amet.sh -u -p [-s ] 23 | ``` 24 | 25 | _Note: the `` can be `bash` or `zsh` (default: `bash`)_ 26 | 27 | After this script completes the editor will be running at `https://:3000`. It will prompt 28 | you for the password you entered when running the script. 29 | 30 | If you would like to access the environment over SSH: 31 | 32 | ```shell 33 | ssh -p 3022 localhost 34 | ``` 35 | 36 | Just enter the password you specified above. 37 | 38 | ## Customizing the Environment 39 | 40 | The idea with this project is that your entire development environment is built as an ubuntu container. To add in 41 | support for the programming languages you work with, libraries you need, or even to bootstrap your dotfiles, Amet allows 42 | you to specify a customizer script. The script will be run during the build in the Dockerfile, under permissions of the 43 | non-root user. But don't fret, you can `sudo` without a password! 44 | 45 | ### Example customizer 46 | 47 | ```shell 48 | #!/bin/bash 49 | set -e # Break the build if there are any errors in this script 50 | set -x # Print out each instruction in the build output 51 | 52 | USER=$(whoami) 53 | 54 | # Install Node.js 55 | curl -sL https://deb.nodesource.com/setup_11.x | sudo -E bash - 56 | sudo apt-get install -y nodejs 57 | 58 | # Install python and tmux 59 | sudo apt-get install -y tmux python 60 | 61 | # Global NPM packages 62 | sudo npm i -g yarn binci 63 | 64 | # Take ownership of local caches 65 | sudo chown -R $USER:$USER ~/.config ~/.npm 66 | 67 | # Install ripgrep 68 | curl -LO https://github.com/BurntSushi/ripgrep/releases/download/0.10.0/ripgrep_0.10.0_amd64.deb 69 | sudo dpkg -i ripgrep_0.10.0_amd64.deb 70 | rm -f ripgrep_0.10.0_amd64.deb 71 | ``` 72 | 73 | To build with the customizer script, just pass the `-c path/to/customizer.sh` option to `amet.sh`. It doesn't need to be chmodded executable, Amet will take care of it! 74 | 75 | ## Docker-in-Docker 76 | 77 | The Docker container builds a docker client and service which can be used without conflicting with the host docker instance. 78 | 79 | ## Persisting Data 80 | 81 | When the container is started it will mount a volume to a `/sync` directory in the container and continually sync 82 | the `/home/` directory. This directory will appear in the working directory where the `./amet.sh ...` startup command was run. 83 | 84 | Additionally, the following directories will be created (on first run) or synced internally (any subsequent runs): 85 | 86 | - `~/code-server`: maintains all data, config, extensions, etc for Code-Server 87 | - `~/workspace`: working environment that Code-Server opens initially 88 | 89 | ## Troubleshooting 90 | 91 | **Getting "error creating aufs mount to ..." when launching containers inside Amet** 92 | On some systems, docker's default and super-efficient `aufs` storage driver can't be used in docker-in-docker 93 | containers like Amet. Simply re-run `amet.sh` and add the `-f vfs` option. This increases the disk space required 94 | to store docker images and can moderately slow builds and launches, but is highly compatible. 95 | 96 | -------------------------------------------------------------------------------- /amet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | getTimezone() { 5 | if [ -n "$TZ" ]; then 6 | echo $TZ 7 | elif [ -f /etc/timezone ]; then 8 | echo $(cat /etc/timezone) 9 | else 10 | echo $(ls -l /etc/localtime | awk '{print $NF}' | sed 's/.*zoneinfo\///') 11 | fi 12 | } 13 | 14 | # DEFAULTS 15 | username=$(id -un) 16 | groupname=$(id -gn) 17 | password=password 18 | shell=bash 19 | runArgs="-d" 20 | portRangeArgs="" 21 | forceRebuild=0 22 | appPort=3000 23 | sshPort=3022 24 | mountHome=1 25 | sshKeyPath=$HOME/.ssh/id_rsa.pub 26 | timezone=$(getTimezone) 27 | syncFreq=900 28 | fsEngine=aufs 29 | customizer="" 30 | isMac=$([[ "$(uname -s)" == "Darwin" ]] && echo "1" || echo "0") 31 | 32 | # HELP, I NEED SOMEBODY 33 | showHelp() { 34 | echo "Usage: $(basename $0) ARGUMENTS [-- docker run arguments]" 35 | echo "" 36 | echo " Available arguments:" 37 | echo "" 38 | echo " -a The port that code-server should be exposed on. Defaults to $appPort." 39 | echo " -b The frequency with which to back up the home folder, or 0 to disable." 40 | echo " Defaults to $syncFreq." 41 | echo " -c