├── .gitignore ├── screenshot.png ├── executer ├── Dockerfile └── entrypoint.py ├── webapp ├── requirements.txt ├── uwsgi.ini ├── Dockerfile ├── main.py ├── static │ ├── css │ │ └── style.css │ └── js │ │ └── editor.js └── templates │ ├── base.html │ └── index.html ├── README.md ├── nginx.conf ├── docker-compose.yml ├── LICENSE └── tools └── sgweb.sh /.gitignore: -------------------------------------------------------------------------------- 1 | webapp/env/ 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekeho/SGWeb/HEAD/screenshot.png -------------------------------------------------------------------------------- /executer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM theoldmoon0602/shellgeibot:latest 2 | 3 | RUN mkdir /images 4 | 5 | COPY entrypoint.py /entrypoint.py 6 | 7 | CMD [ "python3", "/entrypoint.py" ] 8 | -------------------------------------------------------------------------------- /webapp/requirements.txt: -------------------------------------------------------------------------------- 1 | Click==7.0 2 | Flask==1.1.1 3 | itsdangerous==1.1.0 4 | Jinja2==2.10.1 5 | MarkupSafe==1.1.1 6 | six==1.12.0 7 | uWSGI==2.0.18 8 | Werkzeug==0.15.5 9 | -------------------------------------------------------------------------------- /webapp/uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | wsgi-file = main.py 3 | callable = app 4 | master = true 5 | processes = 8 6 | socket = :3031 7 | chmod-socket = 666 8 | vacuum = true 9 | die-on-term = true 10 | py-autoreload = 1 11 | -------------------------------------------------------------------------------- /webapp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker 2 | 3 | # Install python3 4 | RUN apk update 5 | RUN apk add python3 python3-dev build-base linux-headers pcre-dev shadow 6 | 7 | # Install dependencies 8 | COPY requirements.txt / 9 | RUN pip3 install --no-cache-dir -r requirements.txt 10 | 11 | RUN mkdir /var/www 12 | WORKDIR /var/www 13 | 14 | CMD ["uwsgi", "--ini", "/var/www/uwsgi.ini"] 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SGWeb 2 | 3 |  4 | 5 | @theoldmoon0602氏の[ShellgeiBot](https://github.com/theoldmoon0602/ShellgeiBot)のWEBアプリ版 6 | 人気を博しているTwitterアカウント: [シェル芸bot](https://twitter.com/minyoruminyon)に投稿する前に、まずはこっちでリハーサルしようという趣旨。 7 | 8 | ## 依存関係 9 | 10 | - docker 11 | - docker-compose 12 | 13 | ## ビルド 14 | 15 | ```sh 16 | docker-compose build 17 | ``` 18 | 19 | ## 起動 20 | 21 | ```sh 22 | docker-compose up 23 | ``` 24 | 25 | [localhost:80](http://localhost:80)にアクセスすることでSGWebを利用できます 26 | 27 | 28 | ## 使用しているもの 29 | 30 | [ShellgeiBot-Image](https://github.com/theoldmoon0602/ShellgeiBot-Image): Apache-2.0 license 31 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 8; 3 | 4 | daemon off; 5 | 6 | error_log /var/log/nginx/error.log warn; 7 | pid /var/run/nginx.pid; 8 | 9 | 10 | events { 11 | worker_connections 1024; 12 | } 13 | 14 | 15 | http { 16 | include /etc/nginx/mime.types; 17 | default_type application/octet-stream; 18 | 19 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 20 | '$status $body_bytes_sent "$http_referer" ' 21 | '"$http_user_agent" "$http_x_forwarded_for"'; 22 | 23 | access_log /var/log/nginx/access.log main; 24 | 25 | sendfile on; 26 | #tcp_nopush on; 27 | 28 | keepalive_timeout 65; 29 | 30 | #gzip on; 31 | 32 | upstream uwsgi { 33 | server webapp:3031; 34 | } 35 | 36 | server { 37 | listen 80; 38 | charset utf-8; 39 | 40 | location / { 41 | include uwsgi_params; 42 | uwsgi_pass uwsgi; 43 | } 44 | 45 | } 46 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | webapp: 4 | build: ./webapp 5 | volumes: 6 | - ./webapp:/var/www/ 7 | - /var/run/docker.sock:/var/run/docker.sock:ro 8 | ports: 9 | - "3031:3031" 10 | environment: 11 | TZ: "Asia/Tokyo" 12 | depends_on: 13 | - executer 14 | 15 | nginx: 16 | image: nginx 17 | volumes: 18 | - ./nginx.conf:/etc/nginx/nginx.conf 19 | - /tmp/nginx_log:/var/log/nginx # Output log to host 20 | links: 21 | - webapp 22 | environment: 23 | TZ: "Asia/Tokyo" 24 | command: nginx -c /etc/nginx/nginx.conf 25 | 26 | executer: 27 | # only for build 28 | build: ./executer 29 | command: echo "shellgei executer already initialized" 30 | 31 | https-portal: 32 | image: steveltn/https-portal:1 33 | ports: 34 | - "80:80" 35 | - "443:443" 36 | links: 37 | - nginx 38 | environment: 39 | STAGE: local 40 | #STAGE: production 41 | DOMAINS: 'localhost -> http://nginx:80' 42 | #DOMAINS: 'shellgei-web.net -> http://nginx:80, www.shellgei-web.net -> http://nginx:80' 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Hiroki Takemura (kekeho) 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. -------------------------------------------------------------------------------- /executer/entrypoint.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | import glob 5 | import base64 6 | import json 7 | 8 | with sys.stdin as f: 9 | code = f.read() 10 | 11 | if code == '': 12 | exit(0) 13 | 14 | shellscript_filename = '/task.sh' 15 | 16 | # create script file 17 | with open(shellscript_filename, 'w') as f: 18 | f.write(code) 19 | 20 | # chmod +x 21 | current_permit = os.stat(shellscript_filename) 22 | os.chmod(shellscript_filename, 555) 23 | 24 | with subprocess.Popen(['bash', '-c', shellscript_filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p: 25 | stdout, stderr = map(lambda x: x.decode(), p.communicate()) 26 | 27 | generated_images = [x for x in glob.glob('/images/*') if x.split('.')[-1] in ['jpg', 'JPG', 'png', 'PNG', 'gif', 'GIF']] 28 | # encode base64 29 | encoded = [] 30 | for img_filename in generated_images: 31 | with open(img_filename, 'rb') as f: 32 | content = f.read() 33 | encoded.append(base64.b64encode(content)) 34 | 35 | resp = {'stdout': stdout, 'stderr': stderr, 'images': [x.decode() for x in encoded]} 36 | resp_json = json.dumps(resp) 37 | 38 | print(resp_json) 39 | -------------------------------------------------------------------------------- /webapp/main.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Hiroki Takemura (kekeho) 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | 7 | from flask import Flask, render_template, request 8 | import uuid 9 | from subprocess import Popen, PIPE, TimeoutExpired 10 | import json 11 | import os 12 | 13 | 14 | app = Flask(__name__) # Flask app 15 | app.config['SECRET_KEY'] = str(uuid.uuid4()) 16 | 17 | 18 | @app.route('/') 19 | def index(): 20 | return render_template('index.html') 21 | 22 | 23 | @app.route('/post_code/', methods=['POST']) 24 | def post_code(): 25 | docker_command = [ 26 | 'docker', 'run', 27 | '-i', 28 | '--rm', 29 | '--net=none', 30 | '--memory=128m', 31 | '--pids-limit=1024', 32 | 'sgweb_executer', 33 | ] 34 | 35 | p = Popen(docker_command, stdout=PIPE, stderr=PIPE, stdin=PIPE) 36 | try: 37 | code = request.json['code'] 38 | resp_json, sysmsg = map(lambda x: x.decode(), p.communicate(input=code.encode(), timeout=20.0)) 39 | print('sysmessage', sysmsg) 40 | resp = json.loads(resp_json) 41 | stdout = resp['stdout'] 42 | stderr = resp['stderr'] 43 | images = resp['images'] 44 | except json.JSONDecodeError: 45 | stdout, stderr, sysmsg = ('', '', '') 46 | images = [] 47 | except TimeoutExpired: 48 | stdout, stderr, sysmsg = ('', '', '[Error]: Timeout (over 20s)') 49 | images = [] 50 | p.kill() 51 | 52 | return json.dumps({'stdout': stdout, 'stderr': stderr, 'sysmsg': sysmsg, 'images': images}) 53 | 54 | 55 | if __name__ == "__main__": 56 | app.run(host='0.0.0.0') 57 | -------------------------------------------------------------------------------- /webapp/static/css/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Hiroki Takemura (kekeho) 3 | * 4 | * This software is released under the MIT License. 5 | * https://opensource.org/licenses/MIT 6 | */ 7 | 8 | 9 | .navbar-nav > li > .dropdown-menu { 10 | background-color: #343a40; 11 | } 12 | 13 | .navbar-nav > li > .dropdown-menu > .dropdown-item { 14 | color: rgba(255, 255, 255, .75); 15 | } 16 | 17 | .navbar-nav > li > .dropdown-menu > .dropdown-item:hover { 18 | background-color: #343a40; 19 | color: rgb(255, 255, 255); 20 | } 21 | 22 | body { 23 | background-color: rgb(0, 0, 10) !important; 24 | color: #eaeaea !important; 25 | } 26 | 27 | .editor-result-grid { 28 | padding-top: 2rem; 29 | padding-bottom: 2rem; 30 | } 31 | 32 | .run_button { 33 | border: #eaeaea; 34 | border-width: 1px; 35 | border-style: solid; 36 | text-align: center; 37 | margin: 0; 38 | } 39 | .run_button:hover { 40 | cursor: pointer; 41 | } 42 | .run_button:active { 43 | cursor: pointer; 44 | position: relative; 45 | top: 2px; 46 | } 47 | 48 | .window-header { 49 | display: flex; 50 | align-items: center; 51 | margin: 1rem; 52 | } 53 | 54 | pre { 55 | color: #eaeaea !important; 56 | white-space: pre-wrap; 57 | } 58 | 59 | .footer-col { 60 | text-align: center; 61 | } 62 | 63 | #image_row img { 64 | width: 100%; 65 | } 66 | 67 | #recents { 68 | list-style: none; 69 | padding: 0; 70 | } 71 | .recent { 72 | border: 1px solid transparent; 73 | padding: 5px; 74 | box-shadow: 0 0 2px #eee; 75 | cursor: pointer; 76 | max-height: 3em; 77 | overflow: auto; 78 | } 79 | .recent:hover { 80 | border: 1px solid #eee; 81 | } 82 | -------------------------------------------------------------------------------- /tools/sgweb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | URL="https://shellgei-web.net/post_code/" 4 | FILE="-" 5 | MODE="default" 6 | code="" 7 | 8 | help(){ 9 | echo "Usage: $0 [OPTION] [FILE|COMMAND]" 10 | echo "With no FILE, or when FILE is -, read standard input." 11 | echo " -r : print raw response" 12 | echo " -f : print responce in readable format" 13 | echo " -p : read standard input and pass it to the standard input of COMMAND on the SGWeb" 14 | echo " -h : display this help and exit" 15 | } 16 | 17 | if [ $# -ge 1 ]&&[ $1 != "-" ]&&[ ${1:0:1} = "-" ];then 18 | case $1 in 19 | "-r") MODE="raw";; 20 | "-f") MODE="format";; 21 | "-p") MODE="pipe";; 22 | "-h") help;exit 0;; 23 | esac 24 | shift 25 | fi 26 | 27 | if [ $# -ge 1 ];then 28 | FILE="$1" 29 | fi 30 | 31 | if [ "$MODE" = "pipe" ];then 32 | code="\"echo $(base64 -w0)|base64 -d|$1\"" 33 | else 34 | code="$(cat "$FILE"|jq -Rs .)" 35 | fi 36 | 37 | data=$( 38 | curl \ 39 | -sS \ 40 | -X POST \ 41 | -H 'Content-Type: application/json;charset=UTF-8' \ 42 | -H 'Accept-Encoding: gzip' \ 43 | -H 'Accept: application/json, text/plain, */*' \ 44 | -d '{"code":'"$code"'}' \ 45 | "$URL"|gzip -d 46 | ) 47 | 48 | if [ "$MODE" = "raw" ];then 49 | echo "$data" 50 | exit 0 51 | fi 52 | 53 | sysmsg="$(jq -r .sysmsg <<<"$data")" 54 | 55 | case "$MODE" in 56 | "default" | "pipe") 57 | jq -r .stdout <<<"$data"|head -c -1 58 | [ "$sysmsg" ]&&echo "$sysmsg" >&2 59 | ;; 60 | "format") 61 | echo "[stdout]" 62 | jq -r .stdout <<<"$data" 63 | echo "[stderr]" 64 | jq -r .stderr <<<"$data" 65 | echo "[system message]" 66 | jq -r .sysmsg <<<"$data" 67 | ;; 68 | esac 69 | 70 | num=$(($(jq '.images |length' <<<"$data")-1)) 71 | for i in $(seq 0 $num);do 72 | jq -r ".images[$i]" <<<"$data"|base64 -d >$i 73 | type=($(file -b $i)) 74 | mv "$i" "$i.${type[0]}" 75 | done 76 | -------------------------------------------------------------------------------- /webapp/templates/base.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 |[stdout]
43 | 44 |[/images]
45 | 46 |[stderr]
47 | 48 |[system message]
49 | 50 |