├── LICENSE ├── README.md ├── backup-terminal.py ├── daemon_0.6.4-2_amd64.deb ├── daemonize.sh ├── deploy_terminal.py ├── merger.sh ├── qa.rb ├── reactive.py ├── resize_terminal.py ├── runimage.py ├── script-terminals.py ├── share-credit.py ├── startsnap.py ├── startsnap.rb ├── terlib.sh ├── terminal.py ├── terminal_init ├── chkconfig ├── container_startup.sh ├── daemon ├── terminal-server └── terminal-server.conf └── tlinks.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cloudlabs INC. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terminal.com Tools 2 | -------------------------------------------------------------------------------- /backup-terminal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | import time 5 | import errno 6 | import logging 7 | import argparse 8 | from terminalcloud import terminal 9 | 10 | 11 | def mkdir_p(path): 12 | try: 13 | os.makedirs(path) 14 | except OSError as exc: 15 | if exc.errno == errno.EEXIST and os.path.isdir(path): 16 | pass 17 | else: 18 | raise 19 | 20 | 21 | def snapshot_terminal(container_key, title, body, readme, tags): 22 | output = terminal.snapshot_terminal(container_key,body,title,readme,tags) 23 | request_id = output['request_id'] 24 | time.sleep(1) 25 | output = terminal.request_progress(request_id) 26 | while output['status'] != 'success': 27 | time.sleep(3) 28 | terminal.request_progress(request_id) 29 | return output 30 | 31 | 32 | def initialize_logger(log_file): 33 | logger = logging.getLogger(__name__) 34 | logger.setLevel(logging.DEBUG) 35 | debug_handler = logging.FileHandler(log_file) 36 | console_handler = logging.StreamHandler(sys.stdout) 37 | debug_handler.setLevel(logging.DEBUG) 38 | console_handler.setLevel(logging.INFO) 39 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 40 | debug_handler.setFormatter(formatter) 41 | console_handler.setFormatter(formatter) 42 | logger.addHandler(debug_handler) 43 | logger.addHandler(console_handler) 44 | return logger 45 | 46 | 47 | def cleanup(user_token, access_token, delete_threshold, cache_file): 48 | line_num = sum(1 for line in open(cache_file) if line.rstrip()) 49 | head_num = line_num - delete_threshold 50 | with open(cache_file) as cache: 51 | del_list = [next(cache) for x in xrange(head_num)] 52 | for snapshot_id in range(len(del_list)): 53 | logger.info("Deleting snapshot %s" % del_list[snapshot_id]) 54 | terminal.delete_snapshot(snapshot_id) 55 | if (len(del_list) > 0): 56 | with open(cache_file, 'r') as cache: 57 | alldata = cache.read().splitlines(True) 58 | with open(cache_file, 'w') as cache: 59 | cache.writelines(alldata[(len(del_list)):]) 60 | 61 | 62 | def backup(user_token, access_token, logger, subdomain, job_name, delete_threshold, cache_file): 63 | logger.info('Starting %s %s backup snapshot' % (subdomain, job_name)) 64 | container_key = terminal.get_terminal(subdomain)['terminal']['container_key'] 65 | logger.debug('%s container key: %s' % (subdomain, container_key)) 66 | snapshot_id = \ 67 | snapshot_terminal(container_key,(subdomain + '-' + job_name), "backup snap","backup snap", "backup")['result'] 68 | logger.info('%s snapshoted in %s' % (subdomain, snapshot_id)) 69 | with open(cache_file, "a") as cache: 70 | logger.debug('Writing to cache file %s, snapshot_id: %s' % (cache_file, snapshot_id)) 71 | cache.write('%s \n' % snapshot_id) 72 | 73 | 74 | if __name__ == '__main__': 75 | parser = argparse.ArgumentParser() 76 | parser.add_argument("action", help="Desired action [list, backup]") 77 | parser.add_argument("-s", "--subdomain", help="Subdomain of the Terminal to be backed up") 78 | parser.add_argument("-j", "--job_name", type=str, 79 | help="Unique backup job name [could be daily, weekly, monthly... etc]") 80 | parser.add_argument("-u", "--utoken", type=str, help="User Token") 81 | parser.add_argument("-a", "--atoken", type=str, help="Access Token") 82 | parser.add_argument("-c", "--credsfile", type=str, default="~/.creds.json", 83 | help="Credentials Json file [creds.json by default]") 84 | parser.add_argument("-d", "--delete", type=int, default=3, 85 | help="Deletion threshold. How many terminals need to keep before delete [3 by default]") 86 | parser.add_argument("-l", "--log", type=str, 87 | help="Where to locate the log file [default /var/log/subdomain-job_name-snapshots.log]") 88 | parser.add_argument("-r", "--cache_file", type=str, 89 | help="Cache file [default: /var/cache/terminal_backups/subdomain-job_name-snapshots.cache]") 90 | args = parser.parse_args() 91 | 92 | credsfile = os.path.expanduser(args.credsfile) 93 | user_token, access_token = terminal.setup_credentials(args.utoken, args.atoken, credsfile) 94 | 95 | if args.log: 96 | logfile = args.log 97 | else: 98 | logfile = ('/var/log/%s-%s-snapshots.log' % (args.subdomain, args.job_name)) 99 | 100 | if args.cache_file: 101 | cache_file = args.cache_file 102 | else: 103 | mkdir_p('/var/cache/terminal_backups/') 104 | cache_file = ('/var/cache/terminal_backups/%s-%s-snapshots.cache' % (args.subdomain, args.job_name)) 105 | 106 | logger = initialize_logger(logfile) 107 | 108 | if (args.action == "list"): 109 | raw_list = terminal.list_terminals()['terminals'] 110 | for term in range(len(raw_list)): 111 | print '%s|%s|%s' % (raw_list[term]['name'], raw_list[term]['container_key'], raw_list[term]['status']) 112 | elif (args.action == "backup"): 113 | backup(user_token, access_token, logger, args.subdomain, args.job_name, args.delete, cache_file) 114 | cleanup(user_token, access_token, args.delete, cache_file) 115 | else: 116 | print 'Action not valid. Check syntax.' -------------------------------------------------------------------------------- /daemon_0.6.4-2_amd64.deb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terminalcloud/terminal-tools/2db36334f9df068a30b42adfe270117f7ac5ae0d/daemon_0.6.4-2_amd64.deb -------------------------------------------------------------------------------- /daemonize.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #The MIT License (MIT) 3 | # 4 | #Copyright (c) 2015 Cloudlabs, INC 5 | 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 8 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished 9 | # to do so, subject to the following conditions: 10 | 11 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 14 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 15 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 16 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | 19 | get_full_path(){ 20 | echo $(cd $(dirname "$1") && pwd -P)/$(basename "$1") 21 | } 22 | 23 | 24 | get_osflavor(){ 25 | if [[ -f "/etc/lsb-release" ]] 26 | then 27 | echo "ubuntu" 28 | elif [[ -f "/etc/redhat-release" ]] 29 | then 30 | echo "rpm" 31 | elif [[ -f "/etc/debian_version" ]] 32 | then 33 | echo "debian" 34 | else 35 | echo "ERROR: Cannot get the system type. Exiting." 36 | exit 1 37 | fi 38 | } 39 | 40 | install_daemon(){ 41 | echo 'Attempting Daemon Installation' 42 | cd /tmp 43 | if [[ $1 == "debian" ]] || [[ "$1" == "ubuntu" ]] 44 | then 45 | wget -q https://github.com/terminalcloud/terminal-tools/raw/master/daemon_0.6.4-2_amd64.deb || exit -1 46 | dpkg -i daemon_0.6.4-2_amd64.deb 47 | else 48 | wget -q http://libslack.org/daemon/download/daemon-0.6.4-1.x86_64.rpm || exit -1 49 | rpm -i daemon-0.6.4-1.x86_64.rpm 50 | fi 51 | } 52 | 53 | 54 | install_upstart_init() { 55 | cat > /tmp/"$name" << END 56 | description "Daemonized Test service - Upstart script" 57 | 58 | start on runlevel [2345] 59 | stop on runlevel [!2345] 60 | respawn 61 | 62 | env name="$name" 63 | env command="$command" 64 | env command_args="$command_args" 65 | env daemon="$daemon" 66 | env daemon_start_args="--respawn" 67 | env pidfiles="/var/run" 68 | env user="" 69 | env chroot="" 70 | env chdir="" 71 | env umask="" 72 | env stdout="daemon.info" 73 | env stderr="daemon.err" 74 | 75 | 76 | pre-start script 77 | [ -x "\$daemon" ] || exit 0 78 | end script 79 | 80 | exec "\$daemon" "\$daemon_start_args" --name "\$name" --pidfiles "\$pidfiles" \ 81 | \${user:+--user \$user} \${chroot:+--chroot \$chroot} \ 82 | \${chdir:+--chdir \$chdir} \${umask:+--umask \$umask} \ 83 | \${stdout:+--stdout \$stdout} \${stderr:+--stderr \$stderr} \ 84 | -- "\$command" \$command_args 85 | 86 | pre-stop script 87 | "\$daemon" --stop --name "\$name" --pidfiles "\$pidfiles" 88 | end script 89 | END 90 | 91 | # We only replace the init script if the file does not already exists, and finally we enable it. 92 | if [[ -f /etc/init/"$name".conf ]] 93 | then 94 | echo "The file /etc/init/$name.conf already exist." 95 | echo "If you want to overwrite it execute \`cp /tmp/$name /etc/init/$name.conf \`" 96 | else 97 | cp /tmp/"$name" /etc/init/"$name".conf 98 | chmod +x /etc/init/"$name".conf 99 | fi 100 | } 101 | 102 | 103 | install_sysv_init(){ 104 | cat > /tmp/"$name" << EOF 105 | #!/bin/sh 106 | # 107 | ### BEGIN INIT INFO 108 | # Provides: $name 109 | # Required-Start: 110 | # Required-Stop: 111 | # Default-Start: 2 3 4 5 112 | # Default-Stop: 0 1 6 113 | # Should-Start: 114 | # Should-Stop: 115 | # Short-Description: Daemonized $name service. 116 | ### END INIT INFO 117 | # chkconfig: 2345 90 60 118 | name="$name" 119 | command="$command" 120 | command_args="$command_args" 121 | daemon="$daemon" 122 | 123 | [ -x "\$daemon" ] || exit 0 124 | 125 | # This can be customized as needed 126 | daemon_start_args="--respawn" 127 | pidfiles="/var/run" 128 | user="" 129 | chroot="" 130 | chdir="" 131 | umask="" 132 | stdout="daemon.info" 133 | stderr="daemon.err" 134 | 135 | case "\$1" in 136 | start) 137 | if "\$daemon" --running --name "\$name" --pidfiles "\$pidfiles" 138 | then 139 | echo "\$name is already running." 140 | else 141 | echo -n "Starting \$name..." 142 | "\$daemon" \$daemon_start_args --name "\$name" --pidfiles "\$pidfiles" \${user:+--user \$user} \${chroot:+--chroot \$chroot} \ 143 | \${chdir:+--chdir \$chdir} \${umask:+--umask \$umask} \${stdout:+--stdout \$stdout} \${stderr:+--stderr \$stderr} -- "\$command" \$command_args 144 | echo done. 145 | fi 146 | ;; 147 | 148 | stop) 149 | if "\$daemon" --running --name "\$name" --pidfiles "\$pidfiles" 150 | then 151 | echo -n "Stopping \$name..." 152 | "\$daemon" --stop --name "\$name" --pidfiles "\$pidfiles" 153 | echo done. 154 | else 155 | echo "\$name is not running." 156 | fi 157 | ;; 158 | 159 | restart|reload) 160 | if "\$daemon" --running --name "\$name" --pidfiles "\$pidfiles" 161 | then 162 | echo -n "Restarting \$name..." 163 | "\$daemon" --restart --name "\$name" --pidfiles "\$pidfiles" 164 | echo done. 165 | else 166 | echo "\$name is not running." 167 | exit 1 168 | fi 169 | ;; 170 | 171 | status) 172 | "\$daemon" --running --name "\$name" --pidfiles "\$pidfiles" --verbose 173 | ;; 174 | 175 | *) 176 | echo "usage: \$0 " >&2 177 | exit 1 178 | esac 179 | 180 | exit 0 181 | EOF 182 | 183 | # We only replace the init script if the file does not already exists, and finally we enable it. 184 | if [[ -f /etc/init.d/"$name" ]] 185 | then 186 | echo "The file /etc/init.d/$name already exist." 187 | echo "If you want to overwrite it execute \`cp /tmp/$name /etc/init.d/ \`" 188 | else 189 | cp /tmp/"$name" /etc/init.d/ 190 | chmod +x /etc/init.d/"$name" 191 | 192 | chkconfig --add "$name" 193 | fi 194 | } 195 | 196 | chkconfig_install(){ 197 | if [[ "$1" == "debian" ]] 198 | then 199 | echo "Installing chkconfig, please wait" 200 | apt-get update >> /dev/null 201 | apt-get -y install chkconfig >> /dev/null 202 | elif [[ "$1" == "rpm" ]] 203 | then 204 | echo "Installing chkconfig, please wait" 205 | yum -y install chkconfig >> /dev/null 206 | else 207 | echo "Cannot install chkconfig utility" 208 | fi 209 | } 210 | 211 | ##### Main ##### 212 | 213 | [[ "$#" -gt 1 ]] || { echo "Usage: $0 service_name command 'command_parameters'"; exit 0 ; } 214 | 215 | name=$1 216 | command=$(get_full_path $2) 217 | command_args=$3 218 | 219 | 220 | # Check file permissions 221 | [[ -e "$command" ]] || { echo "$command does not exist"; echo "Usage: $0 service_name command 'command_parameters'"; exit 1 ; } 222 | [[ -x "$command" ]] || chmod +x "$command" 223 | 224 | # Install 'Daemon' if needed 225 | which daemon > /dev/null && echo "Daemon already installed" || install_daemon $(get_osflavor) 226 | 227 | # Install SysV init script or Upstart according with the Linux Flavour 228 | daemon=$(which daemon) 229 | if [[ $(get_osflavor) == "ubuntu" ]] 230 | then 231 | echo "Installing Upstart Script" 232 | install_upstart_init 233 | elif [[ $(get_osflavor) == "debian" ]] || [[ $(get_osflavor) == "rpm" ]] 234 | then 235 | echo 'Checking if chkconfig is available' 236 | which chkconfig > /dev/null && echo "chkconfig installed" || chkconfig_install $(get_osflavor) 237 | echo "Installing SysV Init script" 238 | install_sysv_init 239 | else 240 | echo "Cannot get the OS Flavor. Exiting." 241 | exit 1 242 | fi 243 | 244 | # Starting the Daemonized service 245 | service "$name" start -------------------------------------------------------------------------------- /deploy_terminal.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/env python 2 | -------------------------------------------------------------------------------- /merger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | length=$(wc -l $1 | awk '{print $1}') 3 | count=1 4 | while [ "$count" -le "$length" ] ; do 5 | a=$(head -$count $1 | tail -1) 6 | b=$(head -$count $2 | tail -1) 7 | echo "$a,$b" 8 | count=$(expr $count + 1) 9 | done -------------------------------------------------------------------------------- /qa.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'terminal.com/api' 4 | require 'date' 5 | require 'json' 6 | 7 | # Usage: ./qa.rb [production Terminal container_key] [how many Terminals to spin up] [Git ref] 8 | def usage 9 | abort("Usage: #{$0} [container_key] [terminals_number] [git_ref]") 10 | end 11 | 12 | container_key = ARGV.shift || usage 13 | terminal_count = ARGV.shift.to_i || usage 14 | git_ref = ARGV.shift || usage 15 | creds= JSON.parse(File.read('creds.json')) 16 | 17 | 18 | api = Terminal::API.new(creds['user_token'],creds['access_token']) 19 | 20 | # https://www.terminal.com/api/docs#snapshot-terminal 21 | puts "~ Taking a snapshot of the production environment." 22 | response = api.snapshot_terminal(container_key, title: "Backup #{Time.now.strftime('%Y-%m-%d-%H-%M')}") 23 | request_id = response['request_id'] 24 | 25 | snapshot_id = loop do 26 | print "." 27 | response = Terminal.request_progress(request_id) 28 | snapshot_id = response['result'] 29 | if snapshot_id 30 | break snapshot_id 31 | elsif response['status'] == 'failed' 32 | abort "\nError occurred when trying to snapshot the production Terminal: #{response.inspect}" 33 | end 34 | sleep 1 35 | end 36 | 37 | puts "\n~ Snapshot is ready, spinning up Terminals ..." 38 | 39 | # https://www.terminal.com/api/docs#start-snapshot 40 | startup_script = DATA.read.gsub(/%GIT_REF%/, git_ref) 41 | terminal_count.times.map do |i| 42 | name = "QA #{git_ref} #{i}" 43 | puts "~ Spinning up Terminal #{name}" 44 | api.start_snapshot(snapshot_id, name: name, startup_script: startup_script) 45 | end 46 | 47 | __END__ 48 | cd /var/www/expense-tracker/server; 49 | stop expense-tracker-server; 50 | git fetch; 51 | git checkout %GIT_REF% ; 52 | start expense-tracker-server; -------------------------------------------------------------------------------- /reactive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import json 4 | import urllib 5 | import urllib2 6 | import argparse 7 | 8 | 9 | def get_load_average(): # :rtype : dict 10 | raw_average = os.getloadavg() 11 | load_average = {'1min': raw_average[0], '5min': raw_average[1], '15min': raw_average[2]} 12 | return load_average 13 | 14 | 15 | def get_terminal_details(user_token, access_token, subdomain): 16 | output = json.loads(urllib2.urlopen('https://www.terminal.com/api/v0.1/get_terminal', 17 | urllib.urlencode({ 18 | 'user_token': user_token, 19 | 'access_token': access_token, 20 | 'subdomain': subdomain, 21 | })).read()) 22 | return output 23 | 24 | 25 | def set_terminal_size(user_token, access_token, container_key, cpu, ram, diskspace): 26 | output = json.loads(urllib2.urlopen('https://www.terminal.com/api/v0.1/edit_terminal', 27 | urllib.urlencode({ 28 | 'user_token': user_token, 29 | 'access_token': access_token, 30 | 'container_key': container_key, 31 | 'cpu': cpu, 32 | 'ram': ram, 33 | 'diskspace': diskspace, 34 | })).read()) 35 | return output 36 | 37 | 38 | def get_new_size(cpu_size, action): # :rtype : dict 39 | cpu_index = 0 40 | terminals = [{"cpu": 25, "ram": 256}, 41 | {"cpu": 50, "ram": 800}, 42 | {"cpu": 100, "ram": 1600}, 43 | {"cpu": 200, "ram": 3200}, 44 | {"cpu": 400, "ram": 6400}, 45 | {"cpu": 800, "ram": 12800}, 46 | {"cpu": 1600, "ram": 25600}, 47 | {"cpu": 3200, "ram": 51200}] 48 | for index in range(0, len(terminals)): 49 | if str(int(cpu_size)) == str(terminals[index]['cpu']): 50 | cpu_index = index 51 | if action == 'increase': 52 | return terminals[(cpu_index + 1)]['cpu'], terminals[(cpu_index + 1)]['ram'] 53 | elif action == 'decrease': 54 | return terminals[(cpu_index - 1)]['cpu'], terminals[(cpu_index - 1)]['ram'] 55 | 56 | 57 | def decide_cpu(subdomain, cpu_size, load_average, min_size=100, max_size=3200, low_margin=0.2, up_margin=0.7, 58 | resolution='15m'): 59 | print "load average for %s resolution: %s" % (resolution, load_average[resolution]) 60 | if cpu_size < max_size: 61 | if load_average[resolution] > (cpu_size / 100 * up_margin): 62 | print 'Load average is higher than expected: %s' % load_average['15min'] 63 | print "Upsizing" 64 | cpu, ram = get_new_size(cpu_size, 'increase') 65 | container_key = get_terminal_details(user_token, access_token, subdomain)['terminal']['container_key'] 66 | diskspace = get_terminal_details(user_token, access_token, subdomain)['terminal']['diskspace'] 67 | print set_terminal_size(user_token, access_token, container_key, cpu, ram, diskspace) 68 | elif cpu_size > min_size: 69 | if load_average[resolution] <= low_margin: 70 | print "You're wasting computing power, downsizing" 71 | cpu, ram = get_new_size(cpu_size, 'decrease') 72 | container_key = get_terminal_details(user_token, access_token, subdomain)['terminal']['container_key'] 73 | diskspace = get_terminal_details(user_token, access_token, subdomain)['terminal']['diskspace'] 74 | print set_terminal_size(user_token, access_token, container_key, cpu, ram, diskspace) 75 | else: 76 | print "Optimus Prime" 77 | else: 78 | print "The size of the terminal is already the minimum. Downsize is not possible" 79 | else: 80 | print "The size of the terminal is already the maximum. Upsize is not possible" 81 | 82 | 83 | def get_credentials(utoken, atoken, credsfile): 84 | if utoken is None and atoken is None: 85 | try: 86 | creds = json.load(open(credsfile, 'r')) 87 | utoken = creds['user_token'] 88 | atoken = creds['access_token'] 89 | except: 90 | print "Can't open credentials file. \n ", \ 91 | "You must provide a user token and a access token at least one time, or a valid credentials file" 92 | exit(127) 93 | elif (utoken is not None and atoken is None) or (utoken is None and atoken is not None): 94 | print "--utoken AND --atoken parameters must be passed together" 95 | exit(1) 96 | else: 97 | with open(credsfile, 'w') as cfile: 98 | json.dump({'user_token': utoken, 'access_token': atoken}, cfile) 99 | return utoken, atoken 100 | 101 | 102 | if __name__ == '__main__': 103 | parser = argparse.ArgumentParser() 104 | parser.add_argument("subdomain", 105 | help="The subdomain name of your terminal. This is your Terminal.com username plus a number") 106 | parser.add_argument('-m', '--minsize', type=int, default=50, 107 | help="Min size of your resultant instance (25 for micro, 50 for mini, 100 for small...)") 108 | parser.add_argument('-M', '--maxsize', type=int, default=800, 109 | help="Max size of your resultant instance (200 for medium, 400 for xlarge, 800 for 2xlarge...)") 110 | parser.add_argument('-l', '--lmargin', type=float, default=0.0, 111 | help="The lower margin of 15min load average needed to downscale your instance") 112 | parser.add_argument('-u', '--umargin', type=float, default=0.7, 113 | help="The upper margin of 15min load average needed to upscale your instance") 114 | parser.add_argument('-t', '--res', type=str, default='standard', 115 | help="Resolution (15m , 5m and 1m). Accepted values: 'standard', 'high', 'higher'") 116 | parser.add_argument('-U', '--utoken', type=str, default=None, help='Your Terminal.com user token.') 117 | parser.add_argument('-K', '--atoken', type=str, default=None, help='Your Terminal.com access token.') 118 | parser.add_argument('-F', '--creds', type=str, default='creds.json', help='Your json credentials file.') 119 | args = parser.parse_args() 120 | 121 | subdomain = args.subdomain 122 | user_token, access_token = get_credentials(args.utoken, args.atoken, args.creds) 123 | 124 | if get_terminal_details(user_token, access_token, subdomain)['terminal']['cpu'] == '2 (max)': 125 | cpu_size = float('25') 126 | else: 127 | cpu_size = float(get_terminal_details(user_token, access_token, subdomain)['terminal']['cpu']) 128 | 129 | if args.res == 'standard': 130 | resolution = '15min' 131 | elif args.res == 'high': 132 | resolution = '5min' 133 | elif args.res == 'higher': 134 | resolution = '1min' 135 | else: 136 | print "Resolution value not valid" 137 | exit(1) 138 | 139 | load_average = get_load_average() 140 | decide_cpu(subdomain, cpu_size, load_average, args.minsize, args.maxsize, args.lmargin, args.umargin, resolution) 141 | -------------------------------------------------------------------------------- /resize_terminal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import json 3 | import urllib 4 | import urllib2 5 | import argparse 6 | 7 | 8 | def get_terminal_details(user_token, access_token, subdomain): 9 | output = json.loads(urllib2.urlopen('https://www.terminal.com/api/v0.1/get_terminal', 10 | urllib.urlencode({ 11 | 'user_token': user_token, 12 | 'access_token': access_token, 13 | 'subdomain': subdomain, 14 | })).read()) 15 | return output 16 | 17 | 18 | def set_terminal_size(user_token, access_token, container_key, cpu, ram, diskspace): 19 | output = json.loads(urllib2.urlopen('https://www.terminal.com/api/v0.1/edit_terminal', 20 | urllib.urlencode({ 21 | 'user_token': user_token, 22 | 'access_token': access_token, 23 | 'container_key': container_key, 24 | 'cpu': cpu, 25 | 'ram': ram, 26 | 'diskspace': diskspace, 27 | })).read()) 28 | return output 29 | 30 | 31 | def get_new_size(cpu_size, action): # :rtype : dict 32 | cpu_index = 0 33 | terminals = [{"cpu": 25, "ram": 256}, 34 | {"cpu": 50, "ram": 800}, 35 | {"cpu": 100, "ram": 1600}, 36 | {"cpu": 200, "ram": 3200}, 37 | {"cpu": 400, "ram": 6400}, 38 | {"cpu": 800, "ram": 12800}, 39 | {"cpu": 1600, "ram": 25600}, 40 | {"cpu": 3200, "ram": 51200}] 41 | for index in range(0, len(terminals)): 42 | if str(int(cpu_size)) == str(terminals[index]['cpu']): 43 | cpu_index = index 44 | if action == 'increase': 45 | return terminals[(cpu_index + 1)]['cpu'], terminals[(cpu_index + 1)]['ram'] 46 | elif action == 'decrease': 47 | return terminals[(cpu_index - 1)]['cpu'], terminals[(cpu_index - 1)]['ram'] 48 | 49 | 50 | def get_credentials(utoken, atoken, credsfile): 51 | if utoken is None and atoken is None: 52 | try: 53 | creds = json.load(open(credsfile, 'r')) 54 | utoken = creds['user_token'] 55 | atoken = creds['access_token'] 56 | except: 57 | print "Can't open credentials file. \n ", \ 58 | "You must provide a user token and a access token at least one time, or a valid credentials file" 59 | exit(127) 60 | elif (utoken is not None and atoken is None) or (utoken is None and atoken is not None): 61 | print "--utoken AND --atoken parameters must be passed together" 62 | exit(1) 63 | else: 64 | with open(credsfile, 'w') as cfile: 65 | json.dump({'user_token': utoken, 'access_token': atoken}, cfile) 66 | return utoken, atoken 67 | 68 | 69 | def upsize_terminal(cpu_size): 70 | print "upsizing" 71 | cpu, ram = get_new_size(cpu_size, 'increase') 72 | container_key = get_terminal_details(user_token, access_token, subdomain)['terminal']['container_key'] 73 | diskspace = get_terminal_details(user_token, access_token, subdomain)['terminal']['diskspace'] 74 | return set_terminal_size(user_token, access_token, container_key, cpu, ram, diskspace) 75 | 76 | 77 | def downsize_terminal(cpu_size): 78 | print "downsizing" 79 | cpu, ram = get_new_size(cpu_size, 'decrease') 80 | container_key = get_terminal_details(user_token, access_token, subdomain)['terminal']['container_key'] 81 | diskspace = get_terminal_details(user_token, access_token, subdomain)['terminal']['diskspace'] 82 | return set_terminal_size(user_token, access_token, container_key, cpu, ram, diskspace) 83 | 84 | 85 | if __name__ == '__main__': 86 | parser = argparse.ArgumentParser() 87 | parser.add_argument("action", help="upsize or downsize") 88 | parser.add_argument("subdomain", 89 | help="The subdomain name of your terminal. This is your Terminal.com username plus a number") 90 | parser.add_argument('-m', '--minsize', type=int, default=50, 91 | help="Min size of your resultant instance (25 for micro, 50 for mini, 100 for small...)") 92 | parser.add_argument('-M', '--maxsize', type=int, default=1600, 93 | help="Max size of your resultant instance (200 for medium, 400 for xlarge, 800 for 2xlarge...)") 94 | parser.add_argument('-U', '--utoken', type=str, default=None, help='Your Terminal.com user token.') 95 | parser.add_argument('-K', '--atoken', type=str, default=None, help='Your Terminal.com access token.') 96 | parser.add_argument('-F', '--creds', type=str, default='/etc/creds.json', help='Your json credentials file.') 97 | args = parser.parse_args() 98 | 99 | subdomain = args.subdomain 100 | user_token, access_token = get_credentials(args.utoken, args.atoken, args.creds) 101 | 102 | if get_terminal_details(user_token, access_token, subdomain)['terminal']['cpu'] == '2 (max)': 103 | cpu_size = float('25') 104 | else: 105 | cpu_size = float(get_terminal_details(user_token, access_token, subdomain)['terminal']['cpu']) 106 | 107 | if args.action == 'upsize': 108 | if cpu_size <= args.maxsize: 109 | upsize_terminal(cpu_size) 110 | else: 111 | print "Max Terminal size reached" 112 | elif args.action == 'downsize': 113 | if cpu_size >= args.minsize: 114 | downsize_terminal(cpu_size) 115 | else: 116 | print "Min Terminal size reached" 117 | else: 118 | print 'option not found' 119 | exit(1) -------------------------------------------------------------------------------- /runimage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import re 4 | import json 5 | import time 6 | import errno 7 | import shutil 8 | import urllib 9 | import urllib2 10 | import tarfile 11 | import argparse 12 | import subprocess 13 | 14 | bin_filename = 'pulldocker' 15 | dockerhub_url = 'https://registry.hub.docker.com' 16 | defaults = {'user':'root', 'entrypoint':'/entrypoint.sh ', 'cmd':'', 'wdir':'/root'} 17 | 18 | 19 | def get_pulldocker_bin(filename): 20 | for path in os.environ["PATH"].split(":"): 21 | fullpath = '%s/%s'% (path, filename) 22 | if os.path.exists(fullpath): 23 | if os.path.isfile(fullpath): 24 | if os.access(fullpath,os.X_OK): 25 | return fullpath 26 | else: 27 | exit('Cannot execute %s. Check file permissions.'% fullpath) 28 | else: 29 | exit('%s is not a file.'% fullpath) 30 | else: 31 | pass 32 | print '%s not found in path, Trying to install it.'% filename 33 | install_pulldocker() 34 | return '/usr/local/bin/pulldocker' 35 | 36 | def get_dockerfile_details(user, repo): 37 | raw_dockerfile = '%s/u/%s/%s/dockerfile/raw' % (dockerhub_url,user,repo) 38 | output = {} 39 | try: 40 | response = urllib2.urlopen(raw_dockerfile) 41 | dockerfile = response.read() 42 | dockerfile = dockerfile.replace('\\\n', ' ') 43 | f = [a[1] for a in re.findall(r'(FROM|from)\s+(.*)', dockerfile)][0] 44 | maintainer = [a[1] for a in re.findall(r'(MAINTAINER|maintainer)\s+(.*)', dockerfile)] 45 | lines = re.findall(r'\n(COPY|copy|ADD|add|RUN|run|WORKDIR|workdir|ENV|env)\s+(.*)', dockerfile) 46 | cmds = [a[1] for a in re.findall(r'\n(CMD|cmd)\s+(.*)', dockerfile)] 47 | envs = [a[1] for a in re.findall(r'\n(ENV|env)\s+(.*)', dockerfile)] 48 | volumes = [a[1] for a in re.findall(r'\n(volume|VOLUME)\s+(.*)', dockerfile)] 49 | wdir = [a[1] for a in re.findall(r'\n(workdir|WORKDIR)\s+(.*)', dockerfile)] 50 | ports = [a[1] for a in re.findall(r'\n(expose|EXPOSE)\s+(.*)', dockerfile)] 51 | duser = [a[1] for a in re.findall(r'\n(user|USER)\s+(.*)', dockerfile)] 52 | entrypoint = [a[1] for a in re.findall(r'\n(ENTRYPOINT|entrypoint)\s+(.*)', dockerfile)] 53 | output = {'FROM': f, 54 | 'lines': lines, 55 | 'ENV': envs, 56 | 'VOL': volumes, 57 | 'PORTS': ports} 58 | try: 59 | output['CMD'] = json.loads(cmds[0]) 60 | except: 61 | output['CMD'] = cmds 62 | output['WDIR'] = wdir[0] if wdir != [] else None 63 | output['ENTRYPOINT'] = re.sub('["\[\]]','',entrypoint[0]) if entrypoint != [] else None 64 | output['MAINTAINER'] = maintainer[0] if maintainer != [] else None 65 | output['USER'] = duser[0] if duser != [] else 'root' 66 | except Exception, e: 67 | print '(Cannot get Dockerfile >>>%s)'% e 68 | for key in ('FROM', 'CMD', 'ENV', 'VOL', 'WDIR', 'PORTS', 'ENTRYPOINT', 'MAINTAINER', 'USER'): output[key] = None 69 | return output 70 | 71 | def get_customdockerfile_details(filename): 72 | output = {} 73 | try: 74 | with open(filename, 'r') as f: 75 | dockerfile = f.read() 76 | dockerfile = dockerfile.replace('\\\n', ' ') 77 | f = [a[1] for a in re.findall(r'(FROM|from)\s+(.*)', dockerfile)][0] 78 | maintainer = [a[1] for a in re.findall(r'(MAINTAINER|maintainer)\s+(.*)', dockerfile)] 79 | lines = re.findall(r'\n(COPY|copy|ADD|add|RUN|run|WORKDIR|workdir|ENV|env)\s+(.*)', dockerfile) 80 | cmds = [a[1] for a in re.findall(r'\n(CMD|cmd)\s+(.*)', dockerfile)] 81 | envs = [a[1] for a in re.findall(r'\n(ENV|env)\s+(.*)', dockerfile)] 82 | volumes = [a[1] for a in re.findall(r'\n(volume|VOLUME)\s+(.*)', dockerfile)] 83 | wdir = [a[1] for a in re.findall(r'\n(workdir|WORKDIR)\s+(.*)', dockerfile)] 84 | ports = [a[1] for a in re.findall(r'\n(expose|EXPOSE)\s+(.*)', dockerfile)] 85 | duser = [a[1] for a in re.findall(r'\n(user|USER)\s+(.*)', dockerfile)] 86 | entrypoint = [a[1] for a in re.findall(r'\n(ENTRYPOINT|entrypoint)\s+(.*)', dockerfile)] 87 | output = {'FROM': f, 88 | 'lines': lines, 89 | 'ENV': envs, 90 | 'VOL': volumes, 91 | 'PORTS': ports} 92 | try: 93 | output['CMD'] = json.loads(cmds[0]) 94 | except: 95 | output['CMD'] = cmds 96 | output['WDIR'] = wdir[0] if wdir != [] else None 97 | output['ENTRYPOINT'] = re.sub('["\[\]]','',entrypoint[0]) if entrypoint != [] else None 98 | output['MAINTAINER'] = maintainer[0] if maintainer != [] else None 99 | output['USER'] = duser[0] if duser != [] else 'root' 100 | except Exception, e: 101 | print '(%s)'% e 102 | for key in ('FROM', 'CMD', 'ENV', 'VOL', 'WDIR', 'PORTS', 'ENTRYPOINT', 'MAINTAINER', 'USER'): output[key] = None 103 | return output 104 | 105 | def get_startup_commands(parsed, customs, defaults, rootdir, custom_exports): 106 | script = [] 107 | 108 | exports = get_envs(parsed) 109 | if len(exports) > 0: 110 | script.append(get_envs(parsed)) 111 | 112 | if custom_exports is not None: 113 | script.append(get_custom_envs(custom_exports)) 114 | 115 | if customs['wdir'] is not None: 116 | script.append('cd %s ;'% customs['wdir']) 117 | else: 118 | if parsed['WDIR'] is not None: 119 | if len(parsed['WDIR']) > 0: 120 | script.append('cd %s \n'% parsed['WDIR']) 121 | else: 122 | script.append('cd %s ;'% defaults['wdir']) 123 | 124 | if customs['entrypoint'] is not None: 125 | script.append('%s '% customs['entrypoint']) 126 | else: 127 | if parsed['ENTRYPOINT'] is not None: 128 | if len(parsed['ENTRYPOINT']) > 0: 129 | script.append('%s '% parsed['ENTRYPOINT']) 130 | else: 131 | if customs['cmd'] is None and parsed['CMD'] is None: 132 | if os.path.isfile(os.path.join(rootdir,'entrypoint.sh')): 133 | script.append('%s'% defaults['entrypoint']) 134 | 135 | if customs['cmd'] is not None: 136 | script.append('%s'% customs['cmd']) 137 | else: 138 | if parsed['CMD'] is not None: 139 | if len(parsed['CMD']) > 0: 140 | for cmd in range(len(parsed['CMD'])): 141 | script.append('%s'% parsed['CMD'][cmd]) 142 | else: 143 | script.append('%s'% defaults['cmd']) 144 | return script 145 | 146 | def sanitize_image(raw_image): 147 | try: 148 | if len(raw_image.rsplit(':',-1)) == 2: 149 | userrepo, tag = raw_image.rsplit(':',-1) 150 | else: 151 | userrepo, tag = raw_image, None 152 | if len(userrepo.rstrip('/').lstrip('/').split('/')) == 2: 153 | user, repo = userrepo.rstrip('/').lstrip('/').split('/') 154 | else: 155 | user, repo = '_', userrepo.rstrip('/').lstrip('/') 156 | user, repo = re.sub('/','',user), re.sub('/','',repo) 157 | if user == '_' and tag is not None: 158 | image = '%s:%s'% (repo,tag) 159 | else: 160 | if user == '_' and tag is None: 161 | image = '%s'% repo 162 | else: 163 | if user != '_' and tag is None: 164 | image = '%s/%s'% (user, repo) 165 | else: 166 | image = '%s/%s:%s'% (user, repo, tag) 167 | return {'user':user, 'repo':repo, 'tag':tag, 'image':image} 168 | except: 169 | exit('ERROR: Image name format not supported') 170 | 171 | def pullimage(image, rootdir): 172 | pulldocker = get_pulldocker_bin(bin_filename) 173 | try: 174 | if rootdir is not None: 175 | return subprocess.call([pulldocker,'-o', '%s'% rootdir, '%s'% image]) 176 | else: 177 | return subprocess.call([pulldocker,image['image']]) 178 | except Exception, e: 179 | print '(%s)'% e 180 | return False 181 | 182 | def get_rootdir(image, custom_dir): 183 | if custom_dir is None: 184 | if image['user'] == '_': 185 | if image['tag'] is None: 186 | return image['repo'] 187 | else: 188 | return '%s:%s'%(image['repo'], image['tag']) 189 | else: 190 | return image['image'] 191 | else: 192 | if custom_dir[0] != '/': 193 | custom_dir = os.path.join(os.getcwd(),custom_dir) 194 | if os.path.isdir(custom_dir): 195 | return custom_dir 196 | else: 197 | exit('ERROR: rootdir not found') 198 | 199 | def get_user(parsed, custom_user): 200 | if custom_user is None: 201 | if parsed['USER'] is None: 202 | return defaults['user'] 203 | else: 204 | return parsed['USER'] 205 | else: 206 | return custom_user 207 | 208 | def get_envs(parsed): 209 | exports = '' 210 | envs = parsed['ENV'] 211 | if envs is not None: 212 | for env in envs: 213 | e, val = re.split('\s+', env, 1) 214 | exports += 'export %s=\"%s\"; ' % (e, val) 215 | return exports 216 | 217 | def get_custom_envs(custom_exports): 218 | exports = '' 219 | envs = custom_exports.split(',') 220 | for env in range(len(envs)): 221 | e,val = envs[env].split('=') 222 | exports = exports + 'export %s=\"%s\"; '% (e, val) 223 | return exports 224 | 225 | def mkdir_p(path): 226 | try: 227 | os.makedirs(path) 228 | except OSError as exc: 229 | if exc.errno == errno.EEXIST and os.path.isdir(path): 230 | pass 231 | else: 232 | raise 233 | 234 | def make_startup_script(runscript, comms): 235 | print runscript 236 | if os.path.exists(runscript): 237 | print 'Docker startup script ' + runscript + ' already exists - Overwriting' 238 | try: 239 | with open(runscript, 'w') as f: 240 | for line in comms: 241 | f.write(line) 242 | os.chmod(runscript, 0755) 243 | except Exception, e: 244 | exit('ERROR: Cannot write %s script (%s)'% (runscript,e)) 245 | 246 | def write_bashrc(bashrc, string): 247 | with open(bashrc, "a") as file: 248 | file.write('\n') 249 | file.write(string) 250 | file.write('\n') 251 | file.close() 252 | 253 | def mount_binds(rootdir): 254 | chrootdir = os.path.join(os.getcwd(),rootdir) 255 | #mkdir_p(os.path.join(chrootdir,'/CL/readonly')) 256 | #subprocess.call(['mount', '--bind', '/CL/readonly', '%s'% os.path.join(chrootdir,'/CL/readonly')]) 257 | subprocess.call(['mount', '--bind', '/dev', '%s'% os.path.join(chrootdir,'dev')]) 258 | subprocess.call(['mount', '--bind', '/dev/pts', '%s'% os.path.join(chrootdir,'dev/pts')]) 259 | subprocess.call(['mount', '--bind', '/sys', '%s'% os.path.join(chrootdir,'sys')]) 260 | subprocess.call(['mount', '--bind', '/dev/pts', '%s'% os.path.join(chrootdir,'dev/pts')]) 261 | subprocess.call(['mount', '--bind', '/run', '%s'% os.path.join(chrootdir,'run')]) 262 | subprocess.call(['mount', '--bind', '/run/lock', '%s'% os.path.join(chrootdir,'run/lock')]) 263 | subprocess.call(['mount', '--bind', '/run/user', '%s'% os.path.join(chrootdir,'run/user')]) 264 | 265 | def run_in_tab(tab,command): 266 | sendmessage='/srv/cloudlabs/scripts/send_message.sh CLIENTMESSAGE' 267 | data_j = json.dumps({'type':'write_to_term', 'id':str(tab), 'data':'%s \n'% command, 'to':'computer'}) 268 | data = '%s \'%s\''% (sendmessage, data_j) 269 | subprocess.Popen([data], shell=True) 270 | 271 | def install_pulldocker(): 272 | url = 'https://www.terminal.com/pulldocker.tgz' 273 | try: 274 | subprocess.call(['wget', '--no-check-certificate', url]) 275 | tfile = tarfile.open("pulldocker.tgz", 'r:gz') 276 | tfile.extractall('.') 277 | shutil.copy2('pulldocker','/usr/local/bin/pulldocker') 278 | os.chmod('/usr/local/bin/pulldocker',0755) 279 | os.remove('pulldocker') 280 | os.remove('pulldocker.tgz') 281 | except Exception, e: 282 | exit('ERROR: Cannot install pulldocker - (%s) \n please install it manually and try again'% e) 283 | 284 | 285 | if __name__ == "__main__": 286 | parser = argparse.ArgumentParser() 287 | parser.description='RUNIMAGE - Run a docker image inside a Terminal, using a chroot jail and without Docker' 288 | parser.add_argument('image', type=str, help='Docker image to be dumped') 289 | parser.add_argument('-u', '--user', type=str, default=None ,help='Run the container with a custom user [%s]'% defaults['user']) 290 | parser.add_argument('-e', '--entrypoint', type=str, default=None, help='Entrypoint bin/script [%s]'% defaults['entrypoint']) 291 | parser.add_argument('-c', '--cmd', type=str, default=None, help='Command/argument executed by the entrypoint [%s]'% defaults['cmd']) 292 | parser.add_argument('-D', '--rootdir', type=str, default=None, help='Custom path to run/pull the docker image') 293 | parser.add_argument('-d', '--wdir', type=str, default=None, help='Custom internal work dir') 294 | parser.add_argument('-f', '--dockerfile', type=str, default=None, help='Custom dockerfile') 295 | parser.add_argument('-w', '--overwrite', type=bool, default=False, help='DANGER - Overwrite image if it already exists') 296 | parser.add_argument('-t', '--tab', type=int, default=2, help='Terminal tab where the image will be mounted and executed') 297 | parser.add_argument('-n', '--nomounts', type=bool, default=False, help='Do NOT mount any additional FS. [FALSE]') 298 | parser.add_argument('-x', '--custom_exports', type=str, default=None, help='List of additional exported variable values /var=value/ comma separated.') 299 | 300 | args = vars(parser.parse_args()) 301 | 302 | 303 | print 'Analyzing container information...' 304 | image=sanitize_image(args['image']) 305 | parsed_dockerfile = get_customdockerfile_details(args['dockerfile']) if args['dockerfile'] is not None else get_dockerfile_details(image['user'],image['repo']) 306 | rootdir = get_rootdir(image, args['rootdir']) 307 | runscript = '/run.sh' 308 | user = get_user(parsed_dockerfile,args['user']) 309 | script_array = get_startup_commands(parsed_dockerfile, args, defaults, rootdir, args['custom_exports']) 310 | 311 | print'Pulling image from dockerhub...' 312 | if os.path.exists(rootdir) is not True: 313 | print 'Pulling %s...'% image['image'] 314 | pullimage(image['image'],rootdir) 315 | prepare = True 316 | else: 317 | if args['overwrite'] is True: 318 | os.rename(rootdir,'%s_BCK'% rootdir) 319 | print 'Pulling %s...'% image['image'] 320 | pullimage(image['image'],rootdir) 321 | prepare = True 322 | else: 323 | prepare = False 324 | print '%s already exists. Not pulling.'%rootdir 325 | 326 | print 'Preparing jail...' 327 | if prepare is True or args['overwrite'] is True: 328 | # write_bashrc('/root/.bashrc','/usr/sbin/chroot %s'% rootdir) 329 | # write_bashrc('/root/.bashrc','mount -t proc proc /proc') 330 | shutil.copy2('/etc/resolv.conf', os.path.join(os.getcwd(), rootdir, 'etc/resolv.conf')) 331 | if args['nomounts'] is False: 332 | mount_binds(rootdir) 333 | make_startup_script('%s/%s/%s'% (os.getcwd(),rootdir,runscript), script_array) 334 | 335 | print 'Executing chrooted Jail in a new tab...' 336 | time.sleep(1) 337 | cmdchain = 'su -l %s -c %s'% (user,runscript) 338 | run_in_tab(args['tab'], '/usr/sbin/chroot %s'% rootdir) 339 | time.sleep(2) 340 | if args['nomounts'] is False: 341 | run_in_tab(args['tab'], 'mount -t proc proc /proc') 342 | time.sleep(1) 343 | run_in_tab(args['tab'], cmdchain) 344 | 345 | # Install permanent Jail :) 346 | #write_bashrc('/root/.bashrc','/usr/sbin/chroot %s'% rootdir) -------------------------------------------------------------------------------- /script-terminals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import json 4 | import time 5 | import signal 6 | import socket 7 | import argparse 8 | import subprocess 9 | import threading 10 | from terminalcloud import terminal 11 | 12 | key_name = '/root/.ssh/id_rsa' 13 | 14 | class timeout: 15 | def __init__(self, seconds=1, error_message='Timeout'): 16 | self.seconds = seconds 17 | self.error_message = error_message 18 | 19 | def handle_timeout(self, signum, frame): 20 | print self.error_message 21 | 22 | def __enter__(self): 23 | signal.signal(signal.SIGALRM, self.handle_timeout) 24 | signal.alarm(self.seconds) 25 | 26 | def __exit__(self, type, value, traceback): 27 | signal.alarm(0) 28 | 29 | def generate_ssh_key(key_file): 30 | subprocess.call(['ssh-keygen','-f', key_file,'-P','']) 31 | os.chmod(key_file, 0600) 32 | 33 | def get_public_key(key_file): 34 | with open(key_file) as f: 35 | content = f.readlines()[0].rstrip('\n') 36 | return str(content) 37 | 38 | def start_snap(name, snapshot_id, size=None, script=None): 39 | output = terminal.start_snapshot(snapshot_id, size, None, name, None, script) 40 | time.sleep(int(int(args.quantity) * 0.04) + 1) 41 | request_id = output['request_id'] 42 | output = terminal.request_progress(request_id) 43 | try: 44 | if output['status'] != 'success': 45 | pass 46 | except: 47 | time.sleep(int(int(args.quantity) * 0.03) + 1) 48 | output = terminal.request_progress(request_id) 49 | 50 | state = 'None' 51 | while output['status'] != 'success': 52 | try: 53 | time.sleep(int(args.quantity) * 0.03 + 1) 54 | output = terminal.request_progress(request_id) 55 | if output['state'] != state: 56 | state = output['state'] 57 | print('%s - (%s)' % (name, state)) 58 | except: 59 | print "Retrying %s" % name 60 | time.sleep(int(args.quantity * 0.02) + 2) 61 | done = False 62 | while done is False: 63 | try: 64 | output = terminal.request_progress(request_id) 65 | container_key = output['result']['container_key'] 66 | subdomain = output['result']['subdomain'] 67 | container_ip = output['result']['container_ip'] 68 | done = True 69 | except: 70 | print "Retrying %s" % name 71 | time.sleep(int(int(args.quantity) * 0.02) + 1) 72 | done = False 73 | return container_key, container_ip, subdomain 74 | 75 | def run_on_terminal(cip, user, pemfile, script): 76 | try: 77 | p = subprocess.Popen( 78 | ['ssh', '-q' ,'-o', 'StrictHostKeyChecking=no', '-o', 'UserKnownHostsFile=/dev/null', '-i', pemfile, 79 | user + '@' + cip, script], 0, None, None, None, None) 80 | p.wait() 81 | return p.returncode 82 | except Exception, e: 83 | return 'Error: [%s]' % e 84 | 85 | def get_script(filename): 86 | try: 87 | with open(filename) as f: 88 | data = f.read() 89 | data.replace('\n',';') 90 | return data 91 | except Exception, e: 92 | print '(%s)' % e 93 | return None 94 | 95 | def send_script(cip, user, pemfile, script,): 96 | print (cip, user, script, pemfile) 97 | destination='%s@%s:' % (user,cip) 98 | try: 99 | p = subprocess.Popen( 100 | ['scp' , '-i', pemfile, '-o', 'StrictHostKeyChecking=no', '-o', 'UserKnownHostsFile=/dev/null', 101 | script, destination]) 102 | p.wait() 103 | return p.returncode 104 | except Exception, e: 105 | return 'Error: [%s]' % e 106 | 107 | def args_sanitizer(args): 108 | if args.size not in terminal.instance_types()['instance_types']: 109 | print "Error. Wrong instance type" 110 | exit(1) 111 | if (int(args.quantity) < 1 or int(args.quantity) > 250): 112 | print "Error. Terminal amount out of bounds ( should be in between 1 and 100 )" 113 | exit(1) 114 | if args.method not in ('ssh, startup, startup_key'): 115 | print "Error. Not a valid method. use: [ssh], startup or startup_key" 116 | exit(1) 117 | if args.ssh_key_file is None: 118 | key_name = args.ssh_key_file 119 | 120 | def start_terminal_worker(name): 121 | container_key, container_ip, subdomain = start_snap(name, snapshot_id, args.size, script) 122 | terms.append({'container_key':container_key, 'container_ip':container_ip, 'subdomain':subdomain, 'name':name}) 123 | time.sleep(2) 124 | terminal.add_authorized_key_to_terminal(container_key,publicKey) 125 | if args.method == 'ssh' and args.script is not None: 126 | print 'Sending Script' 127 | send_script(container_ip, 'root', key_name, args.script) 128 | print 'Running Script' 129 | run_on_terminal(container_ip, 'root', key_name, '/bin/bash /root/%s' % os.path.basename(args.script)) 130 | 131 | def add_terminal_links_worker(container_key,links): 132 | output = terminal.add_terminal_links(container_key,links) 133 | print output 134 | return output 135 | 136 | def single_thread(): 137 | terms = [] 138 | for i in range(args.quantity): 139 | name = '%s-%s' % (args.name,i) 140 | print "Starting Terminal %s" % name 141 | container_key, container_ip, subdomain = start_snap(name, snapshot_id, args.size, script) 142 | terms.append({'container_key':container_key, 'container_ip':container_ip, 'subdomain':subdomain, 'name':name}) 143 | time.sleep(1) # Prevent race-condition issues 144 | 145 | # Installing stuff by ssh method 146 | if args.method == 'ssh': 147 | for i in range(len(terms)): 148 | terminal.add_authorized_key_to_terminal(terms[i]['container_key'],publicKey) 149 | time.sleep(1) 150 | if args.script is not None: 151 | print "Sending Script" 152 | send_script(terms[i]['container_ip'], 'root', key_name ,args.script) 153 | print "Running Script" 154 | run_on_terminal(terms[i]['container_ip'], 'root', key_name ,'/bin/bash /root/%s' % os.path.basename(args.script)) 155 | elif args.method == 'startup_key': 156 | for i in range(len(terms)): 157 | terminal.add_authorized_key_to_terminal(terms[i]['container_key'],publicKey) 158 | time.sleep(1) 159 | 160 | if args.ports is not None: 161 | host_subdomain=socket.gethostname() 162 | ports=args.ports.split(',') 163 | terms.append(terminal.get_terminal(None,host_subdomain)['terminal']) 164 | for t in range(len(terms)): 165 | container_key=terms[t]['container_key'] 166 | links=[] 167 | for s in range(len(terms)): 168 | for port in range(len(ports)): 169 | link={'port':(ports[port]),'source':terms[s]['subdomain']} 170 | links.append(link) 171 | terminal.add_terminal_links(container_key,links) 172 | return terms 173 | 174 | def multi_thread(): 175 | global terms 176 | terms =[] 177 | threads=[] 178 | 179 | for i in range(args.quantity): 180 | name = '%s-%s' % (args.name,i) 181 | t = threading.Thread(target=start_terminal_worker, args=(name,), name=name) 182 | threads.append(t) 183 | t.setDaemon(True) 184 | print 'Initializying %s' % name 185 | if i%3 == 0: 186 | time.sleep(int(int(args.quantity) * 0.04) + 1) 187 | t.start() 188 | 189 | for th in range(len(threads)): 190 | while threads[th].is_alive(): 191 | time.sleep(1) 192 | 193 | if args.ports is not None: 194 | threads=[] 195 | host_subdomain=socket.gethostname() 196 | ports=args.ports.split(',') 197 | terms.append(terminal.get_terminal(None,host_subdomain)['terminal']) 198 | for term in range(len(terms)): 199 | container_key=terms[term]['container_key'] 200 | links=[] 201 | for s in range(len(terms)): 202 | for port in range(len(ports)): 203 | link={'port':str(ports[port]),'source':terms[s]['subdomain']} 204 | links.append(link) 205 | t = threading.Thread(target=add_terminal_links_worker, args=(container_key,links), name=terms[term]['subdomain']) 206 | threads.append(t) 207 | t.setDaemon(True) 208 | if (term + 1)%8 == 0: 209 | time.sleep(int(int(len(terms)) * 0.02) + 1) 210 | print '%s - Configuring links' % t.name 211 | t.start() 212 | 213 | for th in range(len(threads)): 214 | while threads[th].is_alive(): 215 | time.sleep(1) 216 | return terms 217 | 218 | 219 | if __name__ == '__main__': 220 | parser = argparse.ArgumentParser() 221 | parser.add_argument("quantity", type=int, help="How many nodes will have your deploy") 222 | parser.add_argument('-b', "--snapshot_id", type=str, default='7067f369f7b76f0a3276beb561820a21c9b5204ab60fbd90524560db96d7cb38'\ 223 | , help="Base Snapshot ID. Default is Ubuntu") 224 | parser.add_argument("-s", "--size", type=str, default='medium', help="micro, mini, small, [medium], xlarge.. etc") 225 | parser.add_argument("-u", "--utoken", type=str, default=None ,help="Your user token") 226 | parser.add_argument("-a", "--atoken", type=str, default=None, help="Your access token") 227 | parser.add_argument("-c", "--creds", type=str, default='/etc/creds.json', help="A credentials json file") 228 | parser.add_argument("-x", "--script", type=str, default=None, help="A script file to be executed in the new Terminals. \n\ 229 | With ssh method you can also use a binary executable. \n\ 230 | If a script is not provided, the terminals will be created and ssh keys installed on them.") 231 | parser.add_argument('-m', "--method", type=str, default='ssh', help="[ssh], startup or startup_key") 232 | parser.add_argument('-n', "--name", type=str, default='Scripted Terminal', help="The name of your Terminal") 233 | parser.add_argument('-k', "--ssh_key_file", type=str, default=None, help="Use your own ssh key instead of create a new one - \ 234 | Use your private key name") 235 | parser.add_argument('-p', "--ports", type=str, default=None, help="List of open ports to open between Terminals, csv.") 236 | parser.add_argument('-t', "--threading", type=str, default='single', help="[single] or multi - Multithreading is quick but \ 237 | requires non-interactive scripts") 238 | parser.description="Utility to start and setup Terminals" 239 | args = parser.parse_args() 240 | 241 | terminal.setup_credentials(args.utoken, args.atoken, args.creds) 242 | args_sanitizer(args) 243 | 244 | # Preparing 245 | snapshot_id=args.snapshot_id 246 | 247 | if args.method == 'ssh': 248 | script = None 249 | else: 250 | if args.script is not None: 251 | script = get_script(args.script) 252 | else: 253 | script = None 254 | 255 | if args.method == 'ssh' or args.method == 'startup_key': 256 | if args.ssh_key_file is None: 257 | generate_ssh_key(key_name) 258 | else: 259 | key_name=args.ssh_key_file 260 | publicKey=get_public_key('%s.pub' % key_name) 261 | 262 | # Creating Terminals 263 | if args.threading == 'multi': 264 | terminals = multi_thread() 265 | else: 266 | terminals = single_thread() 267 | 268 | # Print results in json format 269 | host=terminals.pop() 270 | print json.dumps(terminals) -------------------------------------------------------------------------------- /share-credit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | from terminalcloud import terminal 5 | 6 | if __name__ == '__main__': 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument('email', type=str, help='account email receiving the credit') 9 | parser.add_argument('amount', type=int, help='how much to share, in cents') 10 | parser.add_argument("-u", "--utoken", type=str, default=None ,help="Your user token") 11 | parser.add_argument("-a", "--atoken", type=str, default=None, help="Your access token") 12 | parser.add_argument("-c", "--creds", type=str, default='creds.json', help="A credentials json file") 13 | parser.description="Utility to share Terminal.com credit" 14 | args = parser.parse_args() 15 | 16 | terminal.setup_credentials(args.utoken, args.atoken, args.creds) 17 | print terminal.gift(args.email, args.amount) -------------------------------------------------------------------------------- /startsnap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import json 3 | import time 4 | import urllib 5 | import urllib2 6 | import argparse 7 | 8 | def get_credentials(utoken, atoken, credsfile): 9 | if utoken is None and atoken is None: 10 | try: 11 | creds = json.load(open(credsfile, 'r')) 12 | utoken = creds['user_token'] 13 | atoken = creds['access_token'] 14 | except: 15 | print "Can't open credentials file. \n ", \ 16 | "You must provide a user token and a access token at least one time, or a valid credentials file" 17 | exit(127) 18 | elif (utoken is not None and atoken is None) or (utoken is None and atoken is not None): 19 | print "--utoken AND --atoken parameters must be passed together" 20 | exit(1) 21 | else: 22 | with open(credsfile, 'w') as cfile: 23 | json.dump({'user_token': utoken, 'access_token': atoken}, cfile) 24 | return utoken, atoken 25 | 26 | 27 | def get_cpu_and_ram(size): 28 | cpu = None 29 | cpu_index = 0 30 | terminals = [{"size": "micro", "cpu": "2 (max)", "ram": "256"}, 31 | {"size": "mini", "cpu": "50", "ram": "800"}, 32 | {"size": "small", "cpu": "100", "ram": "1600"}, 33 | {"size": "medium", "cpu": "200", "ram": "3200"}, 34 | {"size": "xlarge", "cpu": "400", "ram": "6400"}, 35 | {"size": "2xlarge", "cpu": "800", "ram": "12800"}, 36 | {"size": "4xlarge", "cpu": "1600", "ram": "25600"}, 37 | {"size": "8xlarge", "cpu": "3200", "ram": "51200"}] 38 | for index in range(0, len(terminals)): 39 | if str(size) == str(terminals[index]['size']): 40 | print str(terminals[index]['size']) 41 | cpu = str(terminals[index]['cpu']) 42 | ram = str(terminals[index]['ram']) 43 | if cpu: 44 | return cpu, ram 45 | else: 46 | print "Terminal size not found" 47 | exit(0) 48 | 49 | 50 | def start_snapshot(name, snapshot_id, utoken, atoken, cpu, ram, script): 51 | output = json.loads(urllib2.urlopen('https://www.terminal.com/api/v0.1/start_snapshot', 52 | urllib.urlencode({ 53 | 'user_token': utoken, 54 | 'access_token': atoken, 55 | 'snapshot_id': snapshot_id, 56 | 'cpu': cpu, 57 | 'ram': ram, 58 | 'temporary': 'false', 59 | 'name': name, 60 | 'startup_script': script, 61 | 'autopause': False, 62 | })).read()) 63 | request_id = output['request_id'] 64 | print 'request_id:', request_id 65 | 66 | while output.get('status') != 'success': 67 | output = json.loads(urllib2.urlopen('https://www.terminal.com/api/v0.1/request_progress', 68 | urllib.urlencode({ 69 | 'request_id': request_id, 70 | })).read()) 71 | print '.' 72 | time.sleep(1) 73 | 74 | container_key = output['result']['container_key'] 75 | subdomain = output['result']['subdomain'] 76 | container_ip = output['result']['container_ip'] 77 | 78 | return container_key, container_ip, subdomain 79 | 80 | 81 | if __name__ == '__main__': 82 | parser = argparse.ArgumentParser() 83 | parser.add_argument("name", help="The name of your new Terminal") 84 | parser.add_argument("snapshot_id", help="The ID of the snapshot that you want to start") 85 | parser.add_argument("script", help="A string with the commands to be executed") 86 | parser.add_argument('-s', '--script', type=str, help="The startup command string") 87 | parser.add_argument('-u', '--utoken', type=str, help="Your user token (see https://www.terminal.com/settings/api)") 88 | parser.add_argument('-a', '--atoken', type=str, 89 | help="Your access token (see https://www.terminal.com/settings/api)") 90 | parser.add_argument('-f', '--creds', type=str, default='creds.json', 91 | help="Your credentials file (creds.json by default)") 92 | parser.add_argument('-z', '--size', type=str, default='micro') 93 | args = parser.parse_args() 94 | 95 | snapshot_id = args.snapshot_id 96 | utoken, atoken = get_credentials(args.utoken, args.atoken, args.creds) 97 | cpu, ram = get_cpu_and_ram(args.size) 98 | c_key, c_ip, subdomain = start_snapshot(args.name, args.snapshot_id, utoken, atoken, cpu, ram, args.script) 99 | print 'Key: ', c_key 100 | print 'IP: ', c_ip 101 | print 'Subdomain: ', subdomain -------------------------------------------------------------------------------- /startsnap.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'terminal.com/api' 4 | require 'date' 5 | require 'json' 6 | 7 | # Usage: ./qa.rb [production Terminal container_key] [how many Terminals to spin up] [Git ref] 8 | def usage 9 | abort("Usage: #{$0} [container_key] [terminals_number] [git_ref]") 10 | end 11 | 12 | container_key = ARGV.shift || usage 13 | terminal_count = ARGV.shift.to_i || usage 14 | git_ref = ARGV.shift || usage 15 | creds= JSON.parse(File.read('creds.json')) 16 | 17 | 18 | api = Terminal::API.new(creds['user_token'],creds['access_token']) 19 | 20 | # https://www.terminal.com/api/docs#snapshot-terminal 21 | puts "~ Taking a snapshot of the production environment." 22 | response = api.snapshot_terminal(container_key, title: "Backup #{Time.now.strftime('%Y-%m-%d-%H-%M')}") 23 | request_id = response['request_id'] 24 | 25 | snapshot_id = loop do 26 | print "." 27 | response = Terminal.request_progress(request_id) 28 | snapshot_id = response['result'] 29 | if snapshot_id 30 | break snapshot_id 31 | elsif response['status'] == 'failed' 32 | abort "\nError occurred when trying to snapshot the production Terminal: #{response.inspect}" 33 | end 34 | sleep 1 35 | end 36 | 37 | puts "\n~ Snapshot is ready, spinning up Terminals ..." 38 | 39 | # https://www.terminal.com/api/docs#start-snapshot 40 | startup_script = DATA.read.gsub(/%GIT_REF%/, git_ref) 41 | terminal_count.times.map do |i| 42 | name = "QA #{git_ref} #{i}" 43 | puts "~ Spinning up Terminal #{name}" 44 | api.start_snapshot(snapshot_id, name: name, startup_script: startup_script) 45 | end 46 | 47 | __END__ 48 | cd /var/www/expense-tracker/server; 49 | stop expense-tracker-server; 50 | git fetch; 51 | git checkout %GIT_REF% ; 52 | start expense-tracker-server; -------------------------------------------------------------------------------- /terlib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Terminal.com 3 | # This simple library is intended to provide a standard method to install 4 | # software from OS repositories. 5 | 6 | pkg_update(){ 7 | [[ -f /etc/debian_version ]] && apt-get -y update 8 | } 9 | 10 | system_cleanup(){ 11 | [[ -f /etc/debian_version ]] && apt-get -y autoremove --purge samba* apache2* \ 12 | || yum -y remove httpd* samba* 13 | echo "nameserver 8.8.8.8" >> /etc/resolv.conf 14 | echo "nameserver 8.8.8.9" >> /etc/resolv.conf 15 | #[[ -f /etc/debian_version ]] && DEBIAN_FRONTEND=noninteractive apt-get -y upgrade || yum -y update 16 | } 17 | 18 | basics_install(){ 19 | [[ -f /etc/debian_version ]] && apt-get -y install curl git software-properties-common unzip markdown bash\ 20 | || yum -y install curl git unzip python-markdown bash #libcurl4-openssl-dev 21 | } 22 | 23 | puppet_install(){ 24 | [[ -f /etc/debian_version ]] && apt-get -y install puppet \ 25 | || yum -y install puppet 26 | } 27 | 28 | composer_install(){ 29 | curl -sS https://getcomposer.org/installer | php -- --install-dir=/bin --filename=composer 30 | chmod 755 /bin/composer 31 | } 32 | 33 | apache_install(){ 34 | if [[ -f /etc/debian_version ]]; then 35 | apt-get -y install apache2 && a2enmod rewrite && service apache2 restart 36 | else 37 | yum -y install httpd 38 | fi 39 | } 40 | 41 | nginx_install(){ 42 | if [[ -f /etc/debian_version ]]; then 43 | apt-get -y install nginx nginx-extras 44 | else 45 | yum -y install nginx 46 | fi 47 | } 48 | 49 | 50 | php5_install(){ 51 | if [[ -f /etc/debian_version ]]; then 52 | apt-get -y install php5 php-pear php5-gd php5-mcrypt php5-mysql php5-gd libssh2-php php5-sqlite php5-curl libapache2-mod-php5 && php5enmod curl mcrypt gd pdo_mysql 53 | service apache2 restart 54 | else 55 | yum install php php-pear php-gd php-mcrypt php-mysql libssh2-php php5-sqlite php5-curl 56 | service httpd restart 57 | fi 58 | } 59 | 60 | mysql_install(){ # Default pass for root user is "root", if no argument is given. 61 | [[ -z "$1" ]] && pass="root" || pass=$1 62 | if [[ -f /etc/debian_version ]]; then 63 | debconf-set-selections <<< "mysql-server mysql-server/root_password password $pass" 64 | debconf-set-selections <<< "mysql-server mysql-server/root_password_again password $pass" 65 | apt-get -y install mysql-server 66 | else 67 | echo "Sorry, On this OS install MySql manually, go manually from here" ; return 1 68 | fi 69 | } 70 | 71 | mysql_setup(){ # Arguments: . Default (for empty) values = testdb test terminal 72 | [ -z "$1" ] && db="testdb" || db="$1" 73 | [ -z "$2" ] && user="test" || user="$2" 74 | [ -z "$3" ] && pass="terminal" || pass="$3" 75 | mysql -uroot -proot -e"CREATE DATABASE $db CHARACTER SET utf8 COLLATE utf8_general_ci;" || return 1 76 | mysql -uroot -proot -e"CREATE USER '$user'@'localhost' IDENTIFIED BY '$pass';" || return 1 77 | mysql -uroot -proot -e"GRANT ALL PRIVILEGES ON $db.* to $user@localhost;" || return 1 78 | } 79 | 80 | apache_default_vhost(){ # Arguments: . Default values = default.conf /var/www/html 81 | [[ -f /etc/debian_version ]] && vpath="/etc/apache2/sites-available/" || vpath="/etc/httpd/config.d/" 82 | [ -z "$1" ] && filename="default.conf" || filename="$1" 83 | [ -z "$2" ] && DocumentRoot="/var/www/html" || DocumentRoot="$2" 84 | # Start filling the file 85 | echo "" > $vpath/$filename 86 | echo "DocumentRoot $DocumentRoot" >> $vpath/$filename 87 | echo "" >> $vpath/$filename 88 | cat >> $vpath/$filename <<_EOF_ 89 | Options FollowSymLinks 90 | AllowOverride All 91 | # 92 | # RewriteEngine On 93 | # RewriteBase / 94 | # RewriteCond %{REQUEST_FILENAME} !-f 95 | # RewriteCond %{REQUEST_FILENAME} !-d 96 | # RewriteRule . /index.php [L] 97 | # 98 | 99 | 100 | _EOF_ 101 | 102 | # Remove default vhost file, enable the new one and restart Apache. 103 | if [[ -f /etc/debian_version ]]; then 104 | [[ -f /etc/apache2/sites-enabled/000-default.conf ]] && rm /etc/apache2/sites-enabled/000-default.conf 105 | ln -s /etc/apache2/sites-available/$filename /etc/apache2/sites-enabled/$filename 106 | service apache2 restart 107 | else 108 | [[ -f /etc/httpd/conf.d/000-default.conf ]] && rm /etc/httpd/conf.d/000-default.conf 109 | service httpd restart 110 | fi 111 | } 112 | 113 | golang_install(){ 114 | if [[ -f /etc/debian_version ]]; then 115 | apt-get -y install bison gcc make binutils build-essential mercurial golang 116 | else 117 | yum -y install bison gcc make glibc-devel mercurial golang 118 | fi 119 | bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) 120 | source /root/.gvm/scripts/gvm 121 | echo "export GOPATH=\$HOME/go" >> ~/.profile 122 | echo "export PATH=\$PATH:\$GOROOT/bin:\$GOPATH/bin" >> ~/.profile 123 | source ~/.profile 124 | } 125 | 126 | start_hooks_install(){ 127 | mkdir -p /CL/hooks 128 | cat > /CL/hooks/startup.sh << _EOF_ 129 | #!/bin/bash 130 | cat > /root/info.html << EOF 131 | 132 | External Browser Link 133 | 134 | Check out your installation here! 135 | 136 | 137 | EOF 138 | 139 | cat | /srv/cloudlabs/scripts/run_in_term.js << EOF 140 | /srv/cloudlabs/scripts/display.sh /root/info.html 141 | EOF 142 | 143 | _EOF_ 144 | chmod 755 /CL/hooks/startup.sh 145 | } 146 | 147 | ruby_install(){ 148 | gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3 149 | curl -L get.rvm.io | bash -s stable # Requires Basics 150 | echo "source /usr/local/rvm/scripts/rvm" >> ~/.bashrc 151 | source ~/.bashrc 152 | rvm install 2.1.2 153 | rvm use current --default 154 | rvm rubygems current 155 | [[ $1 == "rails" ]] && gem install rails 156 | } 157 | 158 | python_install(){ # This will install django in /opt/myenv virtual-env 159 | if [[ -f /etc/debian_version ]]; then 160 | apt-get -y install python-pip python2.7 dh-virtualenv 161 | else 162 | yum -y install python-pip python2.7 dh-virtualenv 163 | fi 164 | } 165 | 166 | postgres_install(){ 167 | if [[ -f /etc/debian_version ]]; then 168 | apt-get -y install libpq-dev python-dev 169 | apt-get -y install postgresql postgresql-contrib 170 | service postgresql start 171 | else 172 | yum -y install postgresql 173 | service postgresql start 174 | fi 175 | } 176 | 177 | gunicorn_install(){ # by now assuming the virtualvend exists, otherwise going global 178 | source /opt/myenv/bin/activate 179 | pip install gunicorn 180 | } 181 | 182 | xforwarding_setup(){ 183 | # This functions will configure ssh to allow direct xforwarding. 184 | sed -i 's/X11UseLocalhost\ no/X11UseLocalhost\ yes/g' /etc/ssh/sshd_config 185 | sed -i 's/\#GSSAPICleanupCredentials\ yes/AddressFamily\ inet/g' /etc/ssh/sshd_config 186 | touch .Xauthority 187 | service ssh reload 188 | echo "root:t3rminal" | chpasswd # This will set a weak password - DANGER 189 | } 190 | 191 | xrdp_install(){ 192 | # This function will setup X11rdp/Xrdp in the container 193 | # It required to do a ssh tunnel between the container and the client machine 194 | # For more information, check online help 195 | apt-get -y install xrdp vnc4server x11-apps x11-common x11-session-utils \ 196 | x11-utils x11-xfs-utils 197 | mkdir -p .ssh/ 198 | echo "Now go and:" 199 | echo "1 - Add your public key to .ssh/authorized_keys file" 200 | echo "2 - Make a ssh tunnel from your computer to the local rdp port: \ 201 | ssh -C root@qmaxquique540.terminal.com -L 3389:qmaxquique540.terminal.com:3389" 202 | echo "3 - Connect remote desktop to your local host (in your computer): rdesktop localhost" 203 | } 204 | 205 | xfce_install(){ 206 | apt-get -y install xfce4 xfce4* shimmer-themes xubuntu-icon-theme 207 | echo xfce4-session >~/.xsession 208 | } 209 | 210 | java_install(){ 211 | apt-get -y install icedtea-7-plugin openjdk-7-jre openjdk-7-jdk 212 | } 213 | 214 | java8_oracle_install(){ 215 | add-apt-repository ppa:webupd8team/java 216 | apt-get update 217 | apt-get -y install oracle-java8-installer oracle-java8-set-default 218 | update-java-alternatives -s java-8-oracle 219 | } 220 | 221 | java7_oracle_install(){ 222 | add-apt-repository ppa:webupd8team/java 223 | apt-get update 224 | apt-get -y install oracle-java7-installer 225 | update-java-alternatives -s java-7-oracle 226 | } 227 | 228 | config_prep(){ 229 | sed -i "s/$(hostname)/terminalservername/g" $1 230 | } 231 | 232 | pulldocker_install(){ 233 | wget --no-check-certificate https://www.terminal.com/pulldocker.tgz 234 | tar -xzf pulldocker.tgz -C /usr/local/bin 235 | chmod +x /usr/local/bin/pulldocker 236 | } 237 | -------------------------------------------------------------------------------- /terminal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import json 3 | import urllib 4 | import urllib2 5 | 6 | # Authentication 7 | def setup_credentials(utoken, atoken, credsfile): 8 | if utoken is None and atoken is None: 9 | try: 10 | creds = json.load(open(credsfile, 'r')) 11 | utoken = creds['user_token'] 12 | atoken = creds['access_token'] 13 | except: 14 | print "Can't open credentials file. \n ", \ 15 | "You must provide a user token and a access token at least one time, or a valid credentials file" 16 | exit(127) 17 | elif (utoken is not None and atoken is None) or (utoken is None and atoken is not None): 18 | print "--utoken AND --atoken parameters must be passed together" 19 | exit(1) 20 | else: 21 | with open(credsfile, 'w') as cfile: 22 | json.dump({'user_token': utoken, 'access_token': atoken}, cfile) 23 | global user_token 24 | global access_token 25 | user_token, access_token = str(utoken), str(atoken) 26 | return user_token, access_token 27 | 28 | 29 | # Manage Request 30 | def make_request(call, params=None, url=None, headers=None, raw=False): 31 | try: 32 | if url is None: 33 | url = 'https://api.terminal.com/v0.2/%s' % call 34 | if headers is None: 35 | headers={'user-token': user_token,'access-token':access_token, 'Content-Type':'application/json'} 36 | if params is None: 37 | data = json.dumps({}) 38 | else: 39 | parsed_params={} 40 | for key in params.keys(): 41 | if params[key] is not None: 42 | parsed_params.update({key:params[key]}) 43 | if raw: 44 | data = urllib.urlencode(parsed_params) 45 | headers.pop('Content-Type') 46 | else: 47 | data = json.dumps(parsed_params) 48 | req = urllib2.Request(url, data, headers) 49 | response = urllib2.urlopen(req) 50 | results = json.loads(response.read()) 51 | results.update({u'success':True}) 52 | map(str,results) 53 | return results 54 | except urllib2.HTTPError as e: 55 | return json.loads(e.read()) 56 | 57 | # Browse Snapshots and Users 58 | def get_snapshot(snapshot_id): 59 | call = 'get_snapshot' 60 | params = {"snapshot_id":snapshot_id} 61 | response = make_request(call, params) 62 | return response 63 | 64 | def get_profile(username): 65 | call = 'get_profile' 66 | params = {"username":username} 67 | response = make_request(call, params) 68 | return response 69 | 70 | def list_public_snapshots(username, tag=None, featured=None, title=None, page=0, perPage=1000 ,sortby='popularity'): 71 | call = 'list_public_snapshots' 72 | params = {'username':username, 'tag':tag, 'featured':featured, 'title':title,\ 73 | 'page':page, 'perPage':perPage, 'sortby':sortby} 74 | response = make_request(call, params) 75 | return response 76 | 77 | def count_public_snapshots(username=None, tag=None, featured=None, title=None): 78 | call = 'count_public_snapshots' 79 | params = {'username':username, 'tag':tag, 'featured':featured, 'title':title} 80 | response = make_request(call, params) 81 | return response 82 | 83 | 84 | # Create and Manage Terminals 85 | def list_terminals(): 86 | call = 'list_terminals' 87 | params = None 88 | response = make_request(call, params) 89 | return response 90 | 91 | def get_terminal(container_key=None, subdomain=None): 92 | if (container_key is None and subdomain is None): 93 | return {'error':'container_key OR subdomain must be passed'} 94 | call = 'get_terminal' 95 | params = {'container_key':container_key, 'subdomain':subdomain} 96 | response = make_request(call, params) 97 | return response 98 | 99 | 100 | def start_snapshot(snapshot_id, instance_type=None, temporary=None, name=None, autopause=None, startup_script=None): 101 | call = 'start_snapshot' 102 | params = {'snapshot_id': snapshot_id, 'instance_type': instance_type, 'temporary': temporary, 'name': name, 103 | 'autopause': autopause, 'startup_script': startup_script} 104 | response = make_request(call, params) 105 | return response 106 | 107 | def delete_terminal(container_key): 108 | call = 'delete_terminal' 109 | params = {'container_key':container_key} 110 | response = make_request(call, params) 111 | return response 112 | 113 | def restart_terminal(container_key): 114 | call = 'restart_terminal' 115 | params = {'container_key':container_key} 116 | response = make_request(call, params) 117 | return response 118 | 119 | def pause_terminal(container_key): 120 | call = 'pause_terminal' 121 | params = {'container_key':container_key} 122 | response = make_request(call, params) 123 | return response 124 | 125 | def resume_terminal(container_key): 126 | call = 'resume_terminal' 127 | params = {'container_key':container_key} 128 | response = make_request(call, params) 129 | return response 130 | 131 | def edit_terminal(container_key, instance_type=None, diskspace=None, name=None, custom_data=None): 132 | call = 'edit_terminal' 133 | params = {'container_key':container_key, 'instance_type':instance_type, 'diskspace':diskspace, \ 134 | 'name':name, 'custom_data':custom_data} 135 | response = make_request(call, params) 136 | return response 137 | 138 | 139 | # Create and Manage Snapshots 140 | def list_snapshots(tag=None, featured=None, title=None, page=0, perPage=1000 ,sortby='popularity'): 141 | call = 'list_snapshots' 142 | params = {'tag':tag, 'featured':featured, 'title':title,'page':page, 'perPage':perPage, 'sortby':sortby} 143 | response = make_request(call, params) 144 | return response 145 | 146 | def count_snapshots(tag=None, featured=None, title=None): 147 | call = 'count_snapshots' 148 | params = {'tag':tag, 'featured':featured, 'title':title} 149 | response = make_request(call, params) 150 | return response 151 | 152 | def delete_snapshot(snapshot_id): 153 | call = 'delete_snapshot' 154 | params = {'snapshot_id':snapshot_id} 155 | response = make_request(call, params) 156 | return response 157 | 158 | def edit_snapshot(snapshot_id, body=None, title=None, readme=None, tags=None): 159 | call = 'edit_snapshot' 160 | params = {'snapshot_id':snapshot_id, 'body':body, 'title':title, 'readme':readme, 'tags':tags} 161 | response = make_request(call, params) 162 | return response 163 | 164 | def snapshot_terminal(container_key, body=None, title=None, readme=None, tags=None, public=None): 165 | call = 'snapshot_terminal' 166 | params = {'container_key':container_key, 'body':body, 'title':title, 'readme':readme, \ 167 | 'tags':tags, 'public':public} 168 | response = make_request(call, params) 169 | return response 170 | 171 | 172 | # Manage Terminal Access 173 | def add_terminal_links(container_key, links): 174 | call = 'add_terminal_links' 175 | params= {'container_key':container_key, 'links':links} 176 | response = make_request(call, params) 177 | return response 178 | 179 | def remove_terminal_links(container_key, links): 180 | call = 'remove_terminal_links' 181 | params= {'container_key':container_key, 'links':links} 182 | response = make_request(call, params) 183 | return response 184 | 185 | def list_terminal_access(container_key): 186 | call = 'list_terminal_access' 187 | params = {'container_key':container_key} 188 | response = make_request(call, params) 189 | return response 190 | 191 | def edit_terminal_access(container_key, is_public_list, access_rules): 192 | call = 'edit_terminal_access' 193 | params = {'container_key':container_key, 'is_public_list':is_public_list, 'access_rules':access_rules} 194 | response = make_request(call, params) 195 | return response 196 | 197 | 198 | # Manage Terminal DNS & Domains 199 | def get_cname_records(): 200 | call = 'get_cname_records' 201 | params = {} 202 | response = make_request(call, params) 203 | return response 204 | 205 | def add_domain_to_pool(domain): 206 | call = 'add_domain_to_pool' 207 | params = {'domain':domain} 208 | response = make_request(call, params) 209 | return response 210 | 211 | def remove_domain_from_pool(domain): 212 | call = 'remove_domain_from_pool' 213 | params = {'domain':domain} 214 | response = make_request(call, params) 215 | return response 216 | 217 | def add_cname_record(domain, subdomain, port): 218 | call = add_cname_record 219 | params = {'domain':domain, 'subdomain':subdomain, 'port':port} 220 | response = make_request(call, params) 221 | return response 222 | 223 | def remove_cname_record(domain): 224 | call = 'remove_cname_record' 225 | params = {'domain':domain} 226 | response = make_request(call, params) 227 | return response 228 | 229 | 230 | # Manage Terminal Idle Settings 231 | def set_terminal_idle_settings(container_key, triggers=None, action=None): 232 | call = 'set_terminal_idle_settings' 233 | params = {'container_key':container_key} 234 | response = make_request(call, params) 235 | return response 236 | 237 | def get_terminal_idle_setting(container_key): 238 | call = 'get_terminal_idle_setting' 239 | params = {'container_key':container_key} 240 | response = make_request(call, params) 241 | return response 242 | 243 | 244 | # Manage Usage and Credits 245 | def instance_types(): 246 | call = 'instance_types' 247 | params = None 248 | response = make_request(call, params) 249 | return response 250 | 251 | def instance_price(instance_type): 252 | call = 'instance_price' 253 | params = {'instance_type':instance_type} 254 | response = make_request(call, params) 255 | return response 256 | 257 | def balance(): 258 | call = 'balance' 259 | params = None 260 | response = make_request(call, params) 261 | return response 262 | 263 | def balance_added(): 264 | call = 'balance_added' 265 | params = None 266 | response = make_request(call, params) 267 | return response 268 | 269 | def gift(email, cents): 270 | call = 'gift' 271 | params = {'email':email, 'cents':cents} 272 | response = make_request(call, params) 273 | return response 274 | 275 | def burn_history(): 276 | call = 'burn_history' 277 | params = None 278 | response = make_request(call, params) 279 | return response 280 | 281 | def terminal_usage_history(): 282 | call = 'terminal_usage_history' 283 | params = None 284 | response = make_request(call, params) 285 | return response 286 | 287 | def burn_state(): 288 | call = 'burn_state' 289 | params = None 290 | response = make_request(call, params) 291 | return response 292 | 293 | def burn_estimates(): 294 | call = 'burn_estimates' 295 | params = None 296 | response = make_request(call, params) 297 | return response 298 | 299 | 300 | # Manage SSH Public Keys 301 | def add_authorized_key_to_terminal(container_key, publicKey): 302 | call = 'add_authorized_key_to_terminal' 303 | params = {'container_key':container_key, 'publicKey':publicKey} 304 | response = make_request(call, params) 305 | return response 306 | 307 | def add_authorized_key_to_ssh_proxy(name, publicKey): 308 | call = 'add_authorized_key_to_ssh_proxy' 309 | params = {'name':name, 'publicKey':publicKey} 310 | try: 311 | response = make_request(call, params) 312 | except Exception, e: 313 | return {'status':e} 314 | return response 315 | 316 | def del_authorized_key_from_ssh_proxy(name, fingerprint): 317 | call = 'del_authorized_key_from_ssh_proxy' 318 | params = {'name':name, 'fingerprint':fingerprint} 319 | response = make_request(call, params) 320 | return response 321 | 322 | def get_authorized_keys_from_ssh_proxy(): 323 | call = 'get_authorized_keys_from_ssh_proxy' 324 | params = None 325 | response = make_request(call, params) 326 | return response 327 | 328 | 329 | # Other 330 | def request_progress(request_id): 331 | call = 'request_progress' 332 | params = {'request_id':request_id} 333 | response = make_request(call, params) 334 | return response 335 | 336 | def who_am_i(): 337 | call = 'who_am_i' 338 | response = make_request(call) 339 | return response -------------------------------------------------------------------------------- /terminal_init/chkconfig: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Getopt::Long; 5 | use File::Temp 'tempfile'; 6 | 7 | my $initdir = '/etc/init.d'; 8 | my $inetddir = '/etc/inetd.d'; 9 | my $xinetddir = '/etc/xinetd.d'; 10 | 11 | my %to_d = ( 12 | '0' => 'rc0.d', '1' => 'rc1.d', '2' => 'rc2.d', '3' => 'rc3.d', 13 | '4' => 'rc4.d', '5' => 'rc5.d', 'S' => 'rcS.d', 'B' => 'boot.d' 14 | ); 15 | 16 | # which files to skip in $initdir 17 | my %skips_rc = map {$_ => 1} qw {rc rx skeleton powerfail boot halt reboot single boot.local halt.local}; 18 | 19 | # which services are known 20 | my %known_rc = (); 21 | my %known_inetd = (); 22 | my %known_xinetd = (); 23 | my %known_all = (); 24 | 25 | # 26 | # get the contents of a directory 27 | # 28 | sub ls { 29 | my $dir = shift; 30 | 31 | local *D; 32 | return () unless opendir(D, $dir); 33 | my @ret = grep {$_ ne '.' && $_ ne '..'} readdir(D); 34 | closedir D; 35 | return @ret; 36 | } 37 | 38 | # 39 | # unify an array 40 | # 41 | sub unify { 42 | my %h = map {$_ => 1} @_; 43 | return grep {delete $h{$_}} @_; 44 | } 45 | 46 | 47 | ################################################################## 48 | # runlevel part 49 | ################################################################## 50 | 51 | # which services are currently on? this is a cache to speed things up 52 | # initialized by initlinks_rc(), used in getreal_rc() 53 | my %links = (); 54 | my %links_unknown = (); 55 | 56 | # 57 | # 58 | # calculate the default runlevels of a service by reading the 59 | # insserv header. regexes taken from insserv.c 60 | # 61 | my %getdef_rc_cache = (); 62 | 63 | sub getdef_rc { 64 | my $s = shift; 65 | 66 | return $getdef_rc_cache{$s} if exists $getdef_rc_cache{$s}; 67 | my $file = "$initdir/$s"; 68 | local *F; 69 | if (!open(F, "<$file")) { 70 | print STDERR "$file: $!\n"; 71 | $getdef_rc_cache{$s} = undef; 72 | return undef; 73 | } 74 | while () { 75 | chomp; 76 | if (/^#[[:blank:]]*default[-_]?start:[[:blank:]]*([[:print:][:blank:]]*)/i) { 77 | my $ret = $1; 78 | close F; 79 | $ret =~ s/[[:blank:]]+//g; 80 | my @ret = split('', $ret); 81 | $ret = ''; 82 | for (sort @ret) { 83 | $_ = uc($_); 84 | $ret .= $_ if /[0123456SB]/; 85 | } 86 | $getdef_rc_cache{$s} = $ret; 87 | return $ret; 88 | } 89 | } 90 | $getdef_rc_cache{$s} = '35'; 91 | return '35'; 92 | } 93 | 94 | # 95 | # calculate the required services by reading the insserv header. 96 | # regexes taken from insserv.c 97 | # 98 | sub getdeps_rc { 99 | my $s = shift; 100 | 101 | my $file = "$initdir/$s"; 102 | local *F; 103 | open(F, "<$file") || return undef; 104 | while () { 105 | chomp; 106 | if (/^#[[:blank:]]*required[-_]?start:[[:blank:]]*([[:print:][:blank:]]*)/i) { 107 | my $ret = $1; 108 | close F; 109 | $ret =~ s/\s+$//; 110 | return $ret; 111 | } 112 | } 113 | return ''; 114 | } 115 | 116 | # 117 | # calculate the active runlevels of a service. Uses global %links 118 | # hash. 119 | # 120 | sub getreal_rc { 121 | my $s = shift; 122 | 123 | my $start = ''; 124 | my $l; 125 | initlinks_rc() if $links_unknown{$s}; 126 | for $l (sort keys %links) { 127 | $start .= $l if $links{$l}->{$s}; 128 | } 129 | return $start; 130 | } 131 | 132 | # 133 | # initializes global %links hash by scanning the link directories 134 | # for each runlevel. 135 | # 136 | sub initlinks_rc { 137 | my $l; 138 | for $l (keys %to_d) { 139 | my @links = grep {s/^S\d\d//} ls("$initdir/../$to_d{$l}"); 140 | $links{$l} = { map {$_ => 1} @links }; 141 | } 142 | %links_unknown = (); 143 | } 144 | 145 | my $force; 146 | my $allservices; 147 | 148 | # 149 | # run insserv 150 | # 151 | sub insserv { 152 | my @i = ("/sbin/insserv"); 153 | push @i, "-f" if $force; 154 | my $r = system(@i, @_); 155 | if ($r == -1) { 156 | printf STDERR "/sbin/insserv: $!\n"; 157 | return undef; 158 | } elsif ($r) { 159 | printf STDERR "/sbin/insserv failed, exit code %d\n", $? >> 8; 160 | return undef; 161 | } 162 | return 1; 163 | } 164 | 165 | 166 | ################################################################## 167 | # xinetd part 168 | ################################################################## 169 | 170 | # 171 | # get the state of a xinetd service 172 | # 173 | sub getreal_xinetd { 174 | my $s = shift; 175 | 176 | my $file = "$xinetddir/$s"; 177 | local *F; 178 | open(F, "<$file") || return undef; 179 | my $dis = 1; 180 | while () { 181 | if (/^\s*service\s*\S/) { 182 | if (!$dis) { 183 | close F; 184 | return 'X'; 185 | } 186 | $dis = 0; 187 | } 188 | if (/^\s*disable\s*=\s*yes/) { 189 | $dis = 1; 190 | next; 191 | } 192 | } 193 | close F; 194 | return $dis ? '' : 'X'; 195 | } 196 | 197 | # 198 | # change the state of a xinetd service 199 | # 200 | sub set_xinetd { 201 | my $s = shift; 202 | my $state = shift; 203 | 204 | if (!$known_xinetd{$s}) { 205 | print STDERR "$s: not a xinetd service\n"; 206 | return; 207 | } 208 | local *F; 209 | local *N; 210 | my $file = "$xinetddir/$s"; 211 | if (!open(F, "<$file")) { 212 | print STDERR "$file: $!\n"; 213 | return; 214 | } 215 | if (!open(N, ">$file.chkconfig~")) { 216 | print STDERR "$file.chkconfig~: $!\n"; 217 | return; 218 | } 219 | while () { 220 | if (/^\s*service\s*\S/) { 221 | if (!/{/) { #} 222 | print N $_; 223 | $_ = ; 224 | } 225 | print N $_; 226 | print N "\tdisable = yes\n" unless $state; 227 | next; 228 | } 229 | print N $_ unless /^\s*disable\s*=\s*yes/; 230 | } 231 | close F; 232 | if (!close N) { 233 | print STDERR "$file.chkconfig~: $!\n"; 234 | unlink("$file.chkconfig~"); 235 | return; 236 | } 237 | if (!rename("$file.chkconfig~", "$file")) { 238 | print STDERR "rename $file.chkconfig~ $file: $!\n"; 239 | unlink("$file.chkconfig~"); 240 | return; 241 | } 242 | return 1; 243 | } 244 | 245 | 246 | ################################################################## 247 | # inetd part 248 | ################################################################## 249 | 250 | # 251 | # get the state of a inetd service 252 | # 253 | sub getreal_inetd { 254 | my $s = shift; 255 | 256 | my $file = "$inetddir/$s"; 257 | local *F; 258 | open(F, "<$file") || return undef; 259 | while () { 260 | chomp; 261 | next if /^\s*#/; 262 | next if /^\s*$/; 263 | close F; 264 | return 'T'; 265 | } 266 | close F; 267 | return ''; 268 | } 269 | 270 | # 271 | # does the line look like a inetd service? 272 | # 273 | sub looks_ok_inetd { 274 | return 1 if $_[0] =~ /^![\|<]/; 275 | my @x = split(' ', $_[0]); 276 | my %oktype = map {$_ => 1} qw{stream dgram raw rdm seqpacket}; 277 | return 0 unless $oktype{$x[1]}; 278 | return 0 unless $x[3] =~ /^(no)?wait/; 279 | return 1; 280 | } 281 | 282 | # 283 | # change the state of a inetd service 284 | # 285 | sub set_inetd { 286 | my $s = shift; 287 | my $state = shift; 288 | 289 | if (!$known_inetd{$s}) { 290 | print STDERR "$s: not an inetd service\n"; 291 | return; 292 | } 293 | local *F; 294 | local *N; 295 | my $file = "$inetddir/$s"; 296 | if (!open(F, "<$file")) { 297 | print STDERR "$file: $!\n"; 298 | return; 299 | } 300 | if (!open(N, ">$file.chkconfig~")) { 301 | print STDERR "$file.chkconfig~: $!\n"; 302 | return; 303 | } 304 | while () { 305 | chomp; 306 | if (/^#\s*(.*)/) { 307 | my $l = $1; 308 | if (looks_ok_inetd($l)) { 309 | print N $state ? "$l\n" : "## $l\n"; 310 | next; 311 | } 312 | } 313 | if (!$state && looks_ok_inetd($_)) { 314 | print N "# $_\n"; 315 | next; 316 | } 317 | print N "$_\n"; 318 | } 319 | if (!close N) { 320 | print STDERR "$file.chkconfig~: $!\n"; 321 | unlink("$file.chkconfig~"); 322 | return; 323 | } 324 | if (!rename("$file.chkconfig~", "$file")) { 325 | print STDERR "rename $file.chkconfig~ $file: $!\n"; 326 | unlink("$file.chkconfig~"); 327 | return; 328 | } 329 | return 1; 330 | } 331 | 332 | 333 | ################################################################## 334 | # common functions 335 | ################################################################## 336 | 337 | # 338 | # calculate current status 339 | # 340 | sub getcurrent { 341 | my $s = shift; 342 | 343 | if (!$known_all{$s}) { 344 | print STDERR "$s: unknown service\n"; 345 | return undef; 346 | } 347 | my $start = ''; 348 | $start .= getreal_rc($s) if $known_rc{$s}; 349 | $start .= getreal_inetd($s) if $known_inetd{$s}; 350 | $start .= getreal_xinetd($s) if $known_xinetd{$s}; 351 | return $start; 352 | } 353 | 354 | 355 | # 356 | # return all services we know about by scanning $initdir for init 357 | # scripts. 358 | # 359 | sub findknown { 360 | for (ls($initdir)) { 361 | next unless -f "$initdir/$_"; 362 | next if /^README/ || /^core/; 363 | next if /~$/ || /^[\d\$\.#_\-\\\*]/ || /\.(rpm|ba|old|new|save|swp|core)/; 364 | $known_rc{$_} = 1; 365 | $known_all{$_} = 1; 366 | } 367 | for (ls($xinetddir)) { 368 | next unless -f "$xinetddir/$_"; 369 | next if /~$/ || /\./; 370 | $known_xinetd{$_} = 1; 371 | $known_all{$_} = 1; 372 | } 373 | return unless -d $inetddir; 374 | return unless -f "/etc/inetd.conf"; 375 | local *F; 376 | my $gotinetd = 0; 377 | if (!open(F, ") { 382 | chomp; 383 | if (/^!\|\s*\/usr\/lib\/inetd\/includedir\s+\Q$inetddir\E\s*$/) { 384 | $gotinetd = 1; 385 | last; 386 | } 387 | } 388 | close F; 389 | return unless $gotinetd; 390 | for (ls($inetddir)) { 391 | next unless -f "$inetddir/$_"; 392 | next if /~$/ || /\./; 393 | $known_inetd{$_} = 1; 394 | $known_all{$_} = 1; 395 | } 396 | } 397 | 398 | # 399 | # normalize runlevel 400 | # 401 | my $level; # overwrite on with $level 402 | 403 | sub normalize { 404 | my $s = shift; 405 | my $rl = shift; 406 | 407 | $rl = lc($rl); 408 | return '' if $rl eq 'off' || $rl eq ''; 409 | my $def = '35'; 410 | $def = 'inetd' if $known_inetd{$s}; 411 | $def = 'xinetd' if $known_xinetd{$s}; 412 | $def = getdef_rc($s) if $known_rc{$s}; 413 | return undef unless defined $def; 414 | $rl = ",$rl,"; 415 | $rl =~ s/,on,/,$level,/g if defined $level; 416 | $rl =~ s/,on,/,$def,/g; 417 | $rl =~ s/,xinetd,/,X,/g; 418 | $rl =~ s/,inetd,/,T,/g; 419 | $rl =~ s/s/S/g; 420 | $rl =~ s/b/B/g; 421 | $rl =~ s/,//g; 422 | $rl = join('', sort unify(split('', $rl))); 423 | if ($rl =~ /([^0123456SBTX])/) { 424 | print STDERR "illegal runlevel specified for $s: $1\n"; 425 | return undef; 426 | } 427 | return $rl; 428 | } 429 | 430 | # 431 | # convert runlevels into a nice human readable form 432 | # 433 | sub readable { 434 | my $s = shift; 435 | my $rl = shift; 436 | 437 | return 'off' if $rl eq ''; 438 | my $def = ''; 439 | $def = getdef_rc($s) if $known_rc{$s}; 440 | return undef unless defined $def; 441 | $rl = ",$rl,"; 442 | $rl =~ s/T/,inetd,/g; 443 | $rl =~ s/X/,xinetd,/g; 444 | $rl =~ s/,\Q$def\E,/,on,/ if $def ne ''; 445 | $rl =~ s/,,+/,/g; 446 | $rl =~ s/^,//; 447 | $rl =~ s/,$//; 448 | return $rl; 449 | } 450 | 451 | 452 | ################################################################## 453 | # main program 454 | ################################################################## 455 | 456 | my $mode = ''; 457 | my $printdeps; 458 | my $root = '/'; 459 | 460 | sub addmode { 461 | die("Please specify only one mode.\n") if $mode; 462 | $mode = substr($_[0], 0, 1); 463 | } 464 | 465 | sub usage { 466 | print < ... use as the root file system 482 | EOF 483 | } 484 | 485 | Getopt::Long::Configure('no_ignore_case'); 486 | 487 | if (!GetOptions('list|l' => \&addmode, 488 | 'terse|t' => \&addmode, 489 | 'add|a' => \&addmode, 490 | 'del|d' => \&addmode, 491 | 'edit|e' => \&addmode, 492 | 'help|h' => \&addmode, 493 | 'set|s' => \&addmode, 494 | 'check|c' => \&addmode, 495 | 'level=s' => \$level, 496 | 'force|f' => \$force, 497 | 'allservices|A' => \$allservices, 498 | 'deps' => \$printdeps, 499 | 'root=s' => \$root 500 | 501 | )) { 502 | usage(); 503 | exit 1; 504 | } 505 | if ($mode eq 'h') { 506 | usage(); 507 | exit 0; 508 | } 509 | my (@services, $s); 510 | my (@remove, $s); 511 | my (@enable, $s); 512 | 513 | $initdir = "$root/etc/init.d"; 514 | $inetddir = "$root/etc/inetd.d"; 515 | $xinetddir = "$root/etc/xinetd.d"; 516 | 517 | findknown(); 518 | 519 | if (@ARGV) { 520 | @services = @ARGV; 521 | $mode = @services == 1 ? 't' : 's' if $mode eq ''; 522 | } else { 523 | die("Please specify a service\n") if $mode eq 'c' || $mode eq 'a' || $mode eq 'd'; 524 | @services = sort grep {!$skips_rc{$_}} keys %known_all if $mode ne 's'; 525 | } 526 | $mode = 't' if $mode eq ''; 527 | 528 | initlinks_rc() if $mode eq 'e' || $mode eq 't' || $mode eq 's' || $mode eq 'c' || $mode eq 'l'; 529 | 530 | if (!@ARGV && !$allservices) { 531 | my $l; 532 | my %ison; 533 | for $l (0, 1, 2, 3, 4, 5, 6) { 534 | $ison{$_} = 1 for keys %{$links{$l}}; 535 | } 536 | @services = grep {!/^boot\./ || $ison{$_}} @services; 537 | } 538 | 539 | my %current = (); 540 | 541 | if ($mode eq 'c') { 542 | die("Please specify only one service to check\n") if @services > 2; 543 | $s = $services[0]; 544 | my $want; 545 | if (@services == 1) { 546 | $want = `/sbin/runlevel`; 547 | chomp($want); 548 | die("Can't determine current runlevel\n") unless $want =~ s/^. (.)$/$1/; 549 | } else { 550 | $want = $services[1]; 551 | } 552 | $want = normalize($s, $want); 553 | exit 1 unless defined $want; 554 | exit 0 if $want eq ''; 555 | my $l; 556 | for $l (split('', $want)) { 557 | if ($l eq 'T') { 558 | exit 1 unless getreal_inetd($s) ne ''; 559 | next; 560 | } 561 | if ($l eq 'X') { 562 | exit 1 unless getreal_xinetd($s) ne ''; 563 | next; 564 | } 565 | exit 1 unless $links{$l}->{$s}; 566 | } 567 | exit 0; 568 | } 569 | 570 | if ($mode eq 'e' || $mode eq 't') { 571 | my ($fh, $tmpname); 572 | my $maxlen = 0; 573 | $maxlen >= length($_) or $maxlen = length($_) for @services; 574 | if ($mode eq 'e') { 575 | ($fh, $tmpname) = tempfile("chkconfig.XXXXX", DIR => '/tmp', UNLINK => 1); 576 | die("Could not create temporary file\n") unless $tmpname ne ''; 577 | } else { 578 | $fh = *STDOUT; 579 | } 580 | for $s (@services) { 581 | $current{$s} = getcurrent($s); 582 | next unless defined $current{$s}; 583 | my $r = readable($s, $current{$s}); 584 | next unless defined $r; 585 | printf $fh "%-*s %s\n", $maxlen, $s, $r; 586 | } 587 | exit 0 unless $mode eq 'e'; 588 | close $fh; 589 | system("\${VISUAL:-vi} $tmpname"); 590 | open(STDIN, "<$tmpname") or die("Could not open temporary file\n"); 591 | $mode = 's'; 592 | @services = (); 593 | } 594 | 595 | if ($mode eq 's') { 596 | my $status = 0; 597 | my $usestdin = !@services; 598 | my $ln = 0; 599 | do { 600 | if ($usestdin) { 601 | while () { 602 | $ln++; 603 | chomp; 604 | next if /^\s*#/; 605 | next if /^\s*$/; 606 | my @line = split(' ', $_); 607 | if (@line != 2) { 608 | print STDERR "parse error line $ln: $_\n"; 609 | $status = 1; 610 | next; 611 | } 612 | @services = @line; 613 | last; 614 | } 615 | exit 1 unless @services; 616 | } 617 | if (@services & 1) { 618 | printf("Usage: chkconfig -s service on|off|runlevels\n"); 619 | exit 1; 620 | } 621 | @remove = (); 622 | @enable = (); 623 | while (@services) { 624 | $s = shift @services; 625 | my $want = shift @services; 626 | $want = normalize($s, $want); 627 | $status = 1, next unless defined $want; 628 | $current{$s} = getcurrent($s) unless defined $current{$s}; 629 | $status = 1, next unless defined $current{$s}; 630 | my $current = $current{$s}; 631 | next if $want eq $current; 632 | delete $current{$s}; 633 | if (($want =~ /T/) && ($current !~ /T/)) { 634 | $status = 1 unless set_inetd($s, 1); 635 | } elsif (($want !~ /T/) && ($current =~ /T/)) { 636 | $status = 1 unless set_inetd($s, 0); 637 | } 638 | if (($want =~ /X/) && ($current !~ /X/)) { 639 | $status = 1 unless set_xinetd($s, 1); 640 | } elsif (($want !~ /X/) && ($current =~ /X/)) { 641 | $status = 1 unless set_xinetd($s, 0); 642 | } 643 | $want =~ s/[TX]//g; 644 | $current =~ s/[TX]//g; 645 | next if $want eq $current; 646 | 647 | if (!$known_rc{$s}) { 648 | print STDERR "$s: not a runlevel service\n"; 649 | $status = 1; 650 | next; 651 | } 652 | if ($want eq '') { 653 | push @remove, $s; 654 | } elsif ($want eq getdef_rc($s)) { 655 | push @enable, $s; 656 | } else { 657 | push @remove, $s; 658 | push @enable, "$s,start=".join(',', split('', $want)); 659 | } 660 | $links_unknown{$s} = 1; # check again for this service 661 | } 662 | if (scalar(@remove)) { 663 | $status = 1 unless insserv('-r', '-d', '-p', "$initdir", @remove); 664 | } 665 | if (scalar(@enable)) { 666 | $status = 1 unless insserv('-p', "$initdir", @enable); 667 | } 668 | } while ($usestdin); 669 | exit $status; 670 | } 671 | 672 | # 673 | # compatibility section 674 | # 675 | my $status = 0; 676 | if ($mode eq 'a' || $mode eq 'd') { 677 | for $s (splice @services) { 678 | if (!$known_all{$s}) { 679 | print STDERR "$s: unknown service\n"; 680 | $status = 1; 681 | next; 682 | } 683 | if (!$known_rc{$s}) { 684 | print STDERR "$s: not a runlevel service\n"; 685 | $status = 1; 686 | next; 687 | } 688 | push @services, $s; 689 | } 690 | if (!$status) { 691 | if ($mode eq 'a') { 692 | insserv('-p', "$initdir", @services) or $status = 1; 693 | } else { 694 | insserv('-p', "$initdir", '-r', @services) or $status = 1; 695 | } 696 | } 697 | $mode = 'l'; 698 | initlinks_rc(); 699 | } 700 | if ($mode eq 'l') { 701 | my $usecolor = -t STDOUT; 702 | for $s (@services) { 703 | if (!$known_rc{$s}) { 704 | print STDERR "$s: unknown service\n" unless $known_all{$s}; 705 | next; 706 | } 707 | printf "%-24s", $s; 708 | my $l; 709 | for $l (0, 1, 2, 3, 4, 5, 6, 'B', 'S') { 710 | next if ($l eq 'B' || $l eq 'S') && !$links{$l}->{$s}; 711 | if ($usecolor) { 712 | print $links{$l}->{$s} ? " \e[0;1;32m$l:on\e[m " : " $l:off"; 713 | } else { 714 | print $links{$l}->{$s} ? " $l:on " : " $l:off"; 715 | } 716 | } 717 | print "\t", getdeps_rc($s) if $printdeps; 718 | print "\n"; 719 | } 720 | my @inetd_services = grep {$known_inetd{$_}} @services; 721 | if (@inetd_services) { 722 | print "inetd based services:\n"; 723 | for $s (@inetd_services) { 724 | printf " %-19s ", "$s:"; 725 | if (getreal_inetd($s) ne '') { 726 | print $usecolor ? "\e[0;1;32mon\e[m\n" : "on\n"; 727 | } else { 728 | print "off\n"; 729 | } 730 | } 731 | } 732 | my @xinetd_services = grep {$known_xinetd{$_}} @services; 733 | if (@xinetd_services) { 734 | print "xinetd based services:\n"; 735 | for $s (@xinetd_services) { 736 | printf " %-19s ", "$s:"; 737 | if (getreal_xinetd($s) ne '') { 738 | print $usecolor ? "\e[0;1;32mon\e[m\n" : "on\n"; 739 | } else { 740 | print "off\n"; 741 | } 742 | } 743 | } 744 | exit($status); 745 | } 746 | -------------------------------------------------------------------------------- /terminal_init/container_startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #The MIT License (MIT) 3 | # 4 | #Copyright (c) 2015 Cloudlabs, INC 5 | 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 8 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished 9 | # to do so, subject to the following conditions: 10 | 11 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 14 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 15 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 16 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | 19 | TOOLS_PATH='/CL/readonly/cloudlabs/latest/terminal_init' 20 | DAEMON_BIN="$TOOLS_PATH/daemon" 21 | CHKCONFIG_BIN="$TOOLS_PATH/chkconfig" 22 | UPSTART_CONF="$TOOLS_PATH/terminal-server.conf" 23 | SYSV_INIT_FILE="$TOOLS_PATH/terminal-server" 24 | 25 | . /srv/cloudlabs/container_scripts/vars.sh 26 | 27 | get_osflavor(){ 28 | if [[ -f "/etc/lsb-release" ]] 29 | then 30 | echo "ubuntu" 31 | elif [[ -f "/etc/redhat-release" ]] 32 | then 33 | echo "rpm" 34 | elif [[ -f "/etc/debian_version" ]] 35 | then 36 | echo "debian" 37 | else 38 | echo "ERROR: Cannot get the system type. Exiting." 39 | exit 1 40 | fi 41 | } 42 | 43 | get_daemon(){ 44 | if [[ ! -x /usr/local/bin/daemon ]]; then 45 | echo "Copying Daemon" 46 | cp "$DAEMON_BIN" /usr/local/bin/daemon 47 | chmod +x /usr/local/bin/daemon 48 | fi 49 | } 50 | 51 | get_chkconfig(){ 52 | if [[ ! -x /sbin/chkconfig ]]; then 53 | echo "Copying Chkconfig" 54 | cp "$CHKCONFIG_BIN" /sbin/chkconfig 55 | chmod +x /sbin/chkconfig 56 | fi 57 | } 58 | 59 | install_daemon(){ 60 | echo "Installing Daemon from its package" 61 | cd /tmp 62 | if [[ $1 == "debian" ]] || [[ "$1" == "ubuntu" ]] 63 | then 64 | wget -q https://github.com/terminalcloud/terminal-tools/raw/master/daemon_0.6.4-2_amd64.deb || exit -1 65 | dpkg -i daemon_0.6.4-2_amd64.deb 66 | else 67 | wget -q http://libslack.org/daemon/download/daemon-0.6.4-1.x86_64.rpm || exit -1 68 | rpm -i daemon-0.6.4-1.x86_64.rpm 69 | fi 70 | } 71 | 72 | 73 | install_chkconfig(){ 74 | if [[ "$1" == "debian" ]] 75 | then 76 | echo "Installing chkconfig, please wait" 77 | apt-get update >> /dev/null 78 | apt-get -y install chkconfig >> /dev/null 79 | elif [[ "$1" == "rpm" ]] 80 | then 81 | echo "Installing chkconfig, please wait" 82 | yum -y install chkconfig >> /dev/null 83 | else 84 | echo "Cannot install chkconfig utility" 85 | fi 86 | } 87 | 88 | get_requisites(){ 89 | flavor=$1 90 | if [[ "$flavor" == "debian" ]] 91 | then 92 | get_daemon 93 | get_chkconfig || install_chkconfig "$flavor" 94 | elif [[ "$flavor" == "rpm" ]] 95 | then 96 | get_daemon 97 | which chkconfig >/dev/null || install_chkconfig "$flavor" 98 | else 99 | get_daemon 100 | fi 101 | } 102 | 103 | install_upstart-conf(){ 104 | if [[ ! -f /etc/init/terminal-server.conf ]] 105 | then 106 | cp "$UPSTART_CONF" /etc/init/terminal-server.conf 107 | chmod +x /etc/init/terminal-server.conf 108 | fi 109 | } 110 | 111 | install_sysv_init(){ 112 | if [[ ! -x /etc/init.d/terminal-server.conf ]] 113 | then 114 | cp "$SYSV_INIT_FILE" /etc/init.d/terminal-server 115 | chmod +x /etc/init.d/terminal-server 116 | chkconfig --add terminal-server 117 | fi 118 | } 119 | 120 | comment_rc.local(){ 121 | if [[ -f /etc/rc.local ]] 122 | then 123 | sed -i '/^bash\ \/srv\/cloudlabs\/container_scripts\/container_startup.sh/ s/^#*/#/' /etc/rc.local 124 | fi 125 | } 126 | 127 | remove_cloudlabside(){ 128 | if [[ -x /etc/init.d/cloudlabside ]] 129 | then 130 | rm /etc/init.d/cloudlabside 131 | fi 132 | } 133 | 134 | 135 | prepare_container(){ 136 | mkdir -p /uploads 137 | mkdir -p /local 138 | mkfifo /local/node.fifo 139 | chmod o+wr /local/node.fifo 140 | mkfifo /local/from_node.fifo 141 | chmod o+r /local/from_node.fifo 142 | rm -rf /local/*.json 143 | rm -rf /local/history.* 144 | rm -rf /tmp/* 145 | rm -f /local/diary.fifo 146 | rm -f /local/diary.txt 147 | ln -s /dev/null /local/diary.fifo 148 | rm -rf /var/log/cloudlabs 149 | mkdir -p /var/log/cloudlabs 150 | mkdir -p /var/log/nginx 151 | rm -rf /etc/logrotate.d/cloudlabs 152 | ln -s $SRV/logrotate.conf /etc/logrotate.d/cloudlabs 153 | cp /CL/readonly/cloudlabs/latest/ttyjs/node_modules/pty.js/src/unix/ptyserved /CL/ptyserved 154 | 155 | if [[ -e /root/.octaverc ]] 156 | then 157 | rm -f /root/.octaverc 158 | ln -s $SRV/compute/octaverc /root/.octaverc 159 | fi 160 | 161 | if [[ ! -e /home/.bashrc ]]; 162 | then 163 | ln -s $SRV/bashrc /home/.bashrc 164 | fi 165 | 166 | } 167 | 168 | 169 | ### Main Process ## 170 | 171 | # Check the Linux flavor 172 | flavor=$(get_osflavor) 173 | 174 | # Install pre-requisites if needed 175 | get_requisites "$flavor" 176 | prepare_container 177 | 178 | # Install init/upstart scripts 179 | if [[ "$flavor" == "ubuntu" ]] 180 | then 181 | install_upstart-conf 182 | else 183 | install_sysv_init 184 | fi 185 | 186 | # Comment out the old initialization mode and remove old init scripts 187 | [[ $? -eq 0 ]] && ( comment_rc.local ; remove_cloudlabside ) 188 | service terminal-server start -------------------------------------------------------------------------------- /terminal_init/daemon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terminalcloud/terminal-tools/2db36334f9df068a30b42adfe270117f7ac5ae0d/terminal_init/daemon -------------------------------------------------------------------------------- /terminal_init/terminal-server: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | ### BEGIN INIT INFO 4 | # Provides: terminal-server 5 | # Required-Start: 6 | # Required-Stop: 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Should-Start: 10 | # Should-Stop: 11 | # Short-Description: Daemonized terminal-server service. 12 | ### END INIT INFO 13 | # chkconfig: 2345 90 60 14 | name="terminal-server" 15 | command="/CL/readonly/cloudlabs/latest/node" 16 | command_args="/srv/cloudlabs/compute/compute_server" 17 | daemon="/usr/local/bin/daemon" 18 | 19 | [ -x "$daemon" ] || exit 0 20 | 21 | # This can be customized as needed 22 | daemon_start_args="--respawn" 23 | pidfiles="/var/run" 24 | user="" 25 | chroot="" 26 | chdir="" 27 | umask="" 28 | stdout="/var/log/terminal-server.log" 29 | stderr="/var/log/terminal-server.err" 30 | 31 | case "$1" in 32 | start) 33 | if "$daemon" --running --name "$name" --pidfiles "$pidfiles" 34 | then 35 | echo "$name is already running." 36 | else 37 | echo -n "Starting $name..." 38 | pgrep -f '/CL/ptyserved' || /CL/ptyserved 39 | pgrep -f '/usr/local/openresty/nginx/sbin/nginx -c /srv/cloudlabs/nginx.conf' || ( rm /CL/nginx.sock || true; /usr/local/openresty/nginx/sbin/nginx -c /srv/cloudlabs/nginx.conf ) 40 | pkill -f 'Terminal server' || true 41 | "$daemon" $daemon_start_args --name "$name" --pidfiles "$pidfiles" ${user:+--user $user} ${chroot:+--chroot $chroot} ${chdir:+--chdir $chdir} ${umask:+--umask $umask} ${stdout:+--stdout $stdout} ${stderr:+--stderr $stderr} -- "$command" $command_args 42 | echo done. 43 | fi 44 | ;; 45 | 46 | stop) 47 | if "$daemon" --running --name "$name" --pidfiles "$pidfiles" 48 | then 49 | echo -n "Stopping $name..." 50 | "$daemon" --stop --name "$name" --pidfiles "$pidfiles" 51 | echo done. 52 | else 53 | echo "$name is not running." 54 | fi 55 | ;; 56 | 57 | restart|reload) 58 | if "$daemon" --running --name "$name" --pidfiles "$pidfiles" 59 | then 60 | echo -n "Restarting $name..." 61 | "$daemon" --restart --name "$name" --pidfiles "$pidfiles" 62 | echo done. 63 | else 64 | echo "$name is not running." 65 | exit 1 66 | fi 67 | ;; 68 | 69 | status) 70 | "$daemon" --running --name "$name" --pidfiles "$pidfiles" --verbose 71 | ;; 72 | 73 | *) 74 | echo "usage: $0 " >&2 75 | exit 1 76 | esac 77 | 78 | exit 0 -------------------------------------------------------------------------------- /terminal_init/terminal-server.conf: -------------------------------------------------------------------------------- 1 | description "Daemonized Test service - Upstart script" 2 | 3 | start on runlevel [2345] 4 | stop on runlevel [!2345] 5 | respawn 6 | 7 | env name="terminal-server" 8 | env command="/CL/readonly/cloudlabs/latest/node" 9 | env command_args="/srv/cloudlabs/compute/compute_server" 10 | env daemon="/usr/local/bin/daemon" 11 | env daemon_start_args="--respawn" 12 | env pidfiles="/var/run" 13 | env user="" 14 | env chroot="" 15 | env chdir="" 16 | env umask="" 17 | env stdout="/var/log/terminal-server.log" 18 | env stderr="/var/log/terminal-server.err" 19 | 20 | 21 | pre-start script 22 | [ -x "$daemon" ] || exit 0 23 | pgrep -f '/CL/ptyserved' || /CL/ptyserved 24 | pgrep -f '/usr/local/openresty/nginx/sbin/nginx -c /srv/cloudlabs/nginx.conf' || ( rm /CL/nginx.sock || true; /usr/local/openresty/nginx/sbin/nginx -c /srv/cloudlabs/nginx.conf ) 25 | pkill -f 'Terminal server' || true 26 | end script 27 | 28 | exec "$daemon" "$daemon_start_args" --name "$name" --pidfiles "$pidfiles" ${user:+--user $user} ${chroot:+--chroot $chroot} ${chdir:+--chdir $chdir} ${umask:+--umask $umask} ${stdout:+--stdout $stdout} ${stderr:+--stderr $stderr} -- "$command" $command_args 29 | 30 | pre-stop script 31 | "$daemon" --stop --name "$name" --pidfiles "$pidfiles" 32 | end script -------------------------------------------------------------------------------- /tlinks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | from terminalcloud import terminal 5 | 6 | 7 | def link_terminals(term, src_term, ports): 8 | try: 9 | ckey = terminal.get_terminal(subdomain=term)['terminal']['container_key'] 10 | links = [{"port": ports[port], "source": src_term} for port in range(len(ports))] 11 | output = terminal.add_terminal_links(ckey, links) 12 | return output['success'] 13 | except Exception, e: 14 | return e 15 | 16 | def unlink_terminals(term, src_term, ports): 17 | try: 18 | ckey = terminal.get_terminal(subdomain=term)['terminal']['container_key'] 19 | links = [{"port": ports[port], "source": src_term} for port in range(len(ports))] 20 | output = terminal.remove_terminal_links(ckey,links) 21 | return output['success'] 22 | except Exception, e: 23 | return e 24 | 25 | def clean_terminal_links(term): 26 | try: 27 | ckey = terminal.get_terminal(subdomain=term)['terminal']['container_key'] 28 | links = terminal.list_terminal_access(ckey)['links'] 29 | flinks = map(lambda x: {'port':x.split(':')[0],'source':x.split(':')[-1]},links) 30 | terminal.remove_terminal_links(ckey,flinks) 31 | return 'success' 32 | except Exception, e: 33 | print e 34 | 35 | def show_terminal_links(term): 36 | try: 37 | ckey = terminal.get_terminal(subdomain=term)['terminal']['container_key'] 38 | links = terminal.list_terminal_access(ckey)['links'] 39 | flinks = map(lambda x: {'port':x.split(':')[0],'source':x.split(':')[-1]},links) 40 | print '%s\t\t%s' % ('Source','Port') 41 | print '---------------------' 42 | for link in range(len(flinks)): 43 | print '%s\t%s' % (flinks[link]['source'],flinks[link]['port']) 44 | except Exception, e: 45 | return e 46 | 47 | if __name__ == '__main__': 48 | parser = argparse.ArgumentParser() 49 | parser.add_argument("action", type=str, help="link, unlink, clean, show") 50 | parser.add_argument("terminal", type=str, help="Terminal to be modified") 51 | parser.add_argument("-s", "--source", type=str, default=None, help="Source Terminal. (subdomain)") 52 | parser.add_argument("-p", "--ports", type=str, default='*', help="Ports, separated by comma. By default '*' (all ports)") 53 | parser.add_argument('-u', '--utoken', type=str, help="Your user token (see https://www.terminal.com/settings/api)") 54 | parser.add_argument('-a', '--atoken', type=str, help="Your access token (see https://www.terminal.com/settings/api)") 55 | parser.add_argument('-c', '--creds', type=str, default='creds.json', help="Credentials Json file. By default 'creds.json'") 56 | args = parser.parse_args() 57 | 58 | try: 59 | terminal.setup_credentials(args.utoken, args.atoken, args.creds) 60 | except Exception, e: 61 | print "Cannot setup your credentials. Check if they're correct" 62 | exit(e) 63 | 64 | if args.action == 'link': 65 | print link_terminals(args.terminal, args.source, args.ports.split(',')) 66 | else: 67 | if args.action == 'unlink': 68 | print unlink_terminals(args.terminal, args.source, args.ports.split(',')) 69 | else: 70 | if args.action == 'clean': 71 | clean_terminal_links(args.terminal) 72 | else: 73 | if args.action == 'show': 74 | show_terminal_links(args.terminal) 75 | else: 76 | exit('Action not found. Exiting.') --------------------------------------------------------------------------------