diff --git a/Dockerfile b/Dockerfile index 0fc2233..52b504a 120000 --- a/Dockerfile +++ b/Dockerfile @@ -1 +1,111 @@ -Dockerfile.amd64 \ No newline at end of file +# Built with arch: amd64 flavor: lxde image: ubuntu:18.04 localbuild: 1 +# +################################################################################ +# base system +################################################################################ + +FROM ubuntu:16.04 as system + +RUN sed -i 's#http://archive.ubuntu.com/#http://tw.archive.ubuntu.com/#' /etc/apt/sources.list; + +# built-in packages +ENV DEBIAN_FRONTEND noninteractive +RUN apt update \ + && apt install -y --no-install-recommends software-properties-common curl apache2-utils \ + && apt update \ + && apt install -y --no-install-recommends --allow-unauthenticated \ + supervisor nginx sudo net-tools zenity xz-utils \ + dbus-x11 x11-utils alsa-utils \ + mesa-utils libgl1-mesa-dri \ + && apt autoclean -y \ + && apt autoremove -y \ + && rm -rf /var/lib/apt/lists/* +# install debs error if combine together +RUN add-apt-repository -y ppa:fcwu-tw/apps \ + && apt update \ + && apt install -y --no-install-recommends --allow-unauthenticated \ + xvfb x11vnc\ + vim-tiny firefox chromium-browser ttf-ubuntu-font-family ttf-wqy-zenhei \ + && add-apt-repository -r ppa:fcwu-tw/apps \ + && apt autoclean -y \ + && apt autoremove -y \ + && rm -rf /var/lib/apt/lists/* + +RUN apt update \ + && apt install -y --no-install-recommends --allow-unauthenticated \ + lxde gtk2-engines-murrine gnome-themes-standard gtk2-engines-pixbuf gtk2-engines-murrine \ + && apt autoclean -y \ + && apt autoremove -y \ + && rm -rf /var/lib/apt/lists/* + + +# Additional packages require ~600MB +# libreoffice pinta language-pack-zh-hant language-pack-gnome-zh-hant firefox-locale-zh-hant libreoffice-l10n-zh-tw + +# tini for subreap +ARG TINI_VERSION=v0.18.0 +ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /bin/tini +RUN chmod +x /bin/tini + +# ffmpeg +RUN apt update \ + && apt install -y --no-install-recommends --allow-unauthenticated \ + ffmpeg \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir /usr/local/ffmpeg \ + && ln -s /usr/bin/ffmpeg /usr/local/ffmpeg/ffmpeg + +# python library +COPY image/usr/local/lib/web/backend/requirements.txt /tmp/ +RUN apt-get update \ + && dpkg-query -W -f='${Package}\n' > /tmp/a.txt \ + && apt-get install -y python-pip python-dev build-essential \ + && pip install setuptools wheel && pip install -r /tmp/requirements.txt \ + && dpkg-query -W -f='${Package}\n' > /tmp/b.txt \ + && apt-get remove -y `diff --changed-group-format='%>' --unchanged-group-format='' /tmp/a.txt /tmp/b.txt | xargs` \ + && apt-get autoclean -y \ + && apt-get autoremove -y \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/cache/apt/* /tmp/a.txt /tmp/b.txt + + +################################################################################ +# builder +################################################################################ +# FROM ubuntu:18.04 as builder + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl ca-certificates gnupg patch + +# nodejs +RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - \ + && apt-get install -y nodejs + +# yarn +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ + && apt-get update \ + && apt-get install -y yarn + +ARG ABC +ENV PREFIX_PATH "/app" + +COPY image / + +# build frontend +COPY web /src/web +RUN /etc/install.sh + +LABEL maintainer="Tobias Stein, fcwu.tw@gmail.com" + +# COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/ +RUN mkdir -p /usr/local/lib/web/frontend + +RUN cp -R /src/web/dist/. /usr/local/lib/web/frontend/ + +EXPOSE 80 +WORKDIR /root +ENV HOME=/home/ubuntu \ + SHELL=/bin/bash +HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://127.0.0.1:6079/api/health +ENTRYPOINT ["/startup.sh"] diff --git a/Dockerfile.18.04 b/Dockerfile.18.04 new file mode 100644 index 0000000..c2ed2d3 --- /dev/null +++ b/Dockerfile.18.04 @@ -0,0 +1,117 @@ +# Built with arch: amd64 flavor: lxde image: ubuntu:18.04 localbuild: 1 +# +################################################################################ +# base system +################################################################################ + +FROM ubuntu:18.04 as system + + + +RUN sed -i 's#http://archive.ubuntu.com/#http://tw.archive.ubuntu.com/#' /etc/apt/sources.list; + + +# built-in packages +ENV DEBIAN_FRONTEND noninteractive +RUN apt update \ + && apt install -y --no-install-recommends software-properties-common curl apache2-utils \ + && apt update \ + && apt install -y --no-install-recommends --allow-unauthenticated \ + supervisor nginx sudo net-tools zenity xz-utils \ + dbus-x11 x11-utils alsa-utils \ + mesa-utils libgl1-mesa-dri \ + && apt autoclean -y \ + && apt autoremove -y \ + && rm -rf /var/lib/apt/lists/* +# install debs error if combine together +RUN add-apt-repository -y ppa:fcwu-tw/apps \ + && apt update \ + && apt install -y --no-install-recommends --allow-unauthenticated \ + xvfb x11vnc=0.9.16-1 \ + vim-tiny firefox chromium-browser ttf-ubuntu-font-family ttf-wqy-zenhei \ + && add-apt-repository -r ppa:fcwu-tw/apps \ + && apt autoclean -y \ + && apt autoremove -y \ + && rm -rf /var/lib/apt/lists/* + +RUN apt update \ + && apt install -y --no-install-recommends --allow-unauthenticated \ + lxde gtk2-engines-murrine gnome-themes-standard gtk2-engines-pixbuf gtk2-engines-murrine arc-theme \ + && apt autoclean -y \ + && apt autoremove -y \ + && rm -rf /var/lib/apt/lists/* + + +# Additional packages require ~600MB +# libreoffice pinta language-pack-zh-hant language-pack-gnome-zh-hant firefox-locale-zh-hant libreoffice-l10n-zh-tw + +# tini for subreap +ARG TINI_VERSION=v0.18.0 +ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /bin/tini +RUN chmod +x /bin/tini + +# ffmpeg +RUN apt update \ + && apt install -y --no-install-recommends --allow-unauthenticated \ + ffmpeg \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir /usr/local/ffmpeg \ + && ln -s /usr/bin/ffmpeg /usr/local/ffmpeg/ffmpeg + +# python library +COPY image/usr/local/lib/web/backend/requirements.txt /tmp/ +RUN apt-get update \ + && dpkg-query -W -f='${Package}\n' > /tmp/a.txt \ + && apt-get install -y python-pip python-dev build-essential \ + && pip install setuptools wheel && pip install -r /tmp/requirements.txt \ + && dpkg-query -W -f='${Package}\n' > /tmp/b.txt \ + && apt-get remove -y `diff --changed-group-format='%>' --unchanged-group-format='' /tmp/a.txt /tmp/b.txt | xargs` \ + && apt-get autoclean -y \ + && apt-get autoremove -y \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/cache/apt/* /tmp/a.txt /tmp/b.txt + + +################################################################################ +# builder +################################################################################ +# FROM ubuntu:18.04 as builder + + +RUN sed -i 's#http://archive.ubuntu.com/#http://tw.archive.ubuntu.com/#' /etc/apt/sources.list; + + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl ca-certificates gnupg patch + +# nodejs +RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - \ + && apt-get install -y nodejs + +# yarn +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ + && apt-get update \ + && apt-get install -y yarn + +ENV PREFIX_PATH "/app" + +COPY image / + +# build frontend +COPY web /src/web +RUN /etc/install.sh + +LABEL maintainer="Tobias Stein, fcwu.tw@gmail.com" + +# COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/ +RUN mkdir -p /usr/local/lib/web/frontend + +RUN cp -R /src/web/dist/. /usr/local/lib/web/frontend/ + +EXPOSE 80 +WORKDIR /root +ENV HOME=/home/ubuntu \ + SHELL=/bin/bash +HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://127.0.0.1:6079/api/health +ENTRYPOINT ["/startup.sh"] diff --git a/image/etc/RenameURL.py b/image/etc/RenameURL.py new file mode 100644 index 0000000..0494bec --- /dev/null +++ b/image/etc/RenameURL.py @@ -0,0 +1,23 @@ +import os, fnmatch + +toFind = "/app" +toReplace = os.getenv("PREFIX_PATH") + +if (toReplace is None): + print("No path given by environment variable.") + exit() + +print("using path {0} ...".format(toReplace)) + +def findReplace(directory, find, replace): + print("Rename {0} into {1} at {2}".format(toFind, toReplace, directory)) + for path, dirs, files in os.walk(os.path.abspath(directory)): + for filename in files: + filepath = os.path.join(path, filename) + with open(filepath) as f: + s = f.read() + s = s.replace(find, replace) + with open(filepath, "w") as f: + f.write(s) + +findReplace("/etc/nginx/sites-enabled/", toFind, toReplace) \ No newline at end of file diff --git a/image/etc/install.sh b/image/etc/install.sh new file mode 100644 index 0000000..2a0f42e --- /dev/null +++ b/image/etc/install.sh @@ -0,0 +1,8 @@ +#!/bin/bash +echo 'Building Node.js project...' +cd /src/web \ + && yarn \ + && npm run build + +cp -R /src/web/dist/. /usr/local/lib/web/frontend/ +echo 'Build finished.' \ No newline at end of file diff --git a/image/etc/nginx/sites-enabled/default b/image/etc/nginx/sites-enabled/default index 07a8170..6f785b9 100644 --- a/image/etc/nginx/sites-enabled/default +++ b/image/etc/nginx/sites-enabled/default @@ -13,11 +13,19 @@ server { root /usr/local/lib/web/frontend/; index index.html index.htm; - location ~ ^/api { + location = /app { + try_files $uri @rewrites; + } + + location @rewrites { + rewrite ^(.+)$ /index.html last; + } + + location ~ ^/app/api { try_files $uri @api; } - location = /websockify { + location = /app/websockify { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; diff --git a/image/etc/supervisor/conf.d/supervisord.conf b/image/etc/supervisor/conf.d/supervisord.conf index 2b538da..61e850f 100644 --- a/image/etc/supervisor/conf.d/supervisord.conf +++ b/image/etc/supervisor/conf.d/supervisord.conf @@ -7,6 +7,10 @@ directory=/root [program:nginx] priority=10 command=nginx -c /etc/nginx/nginx.conf -g 'daemon off;' +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/fd/1 +stderr_logfile_maxbytes=0 [program:web] priority=10 @@ -41,8 +45,6 @@ user=%USER% environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%" - - [program:xvfb] priority=10 command=/usr/local/bin/xvfb.sh @@ -52,8 +54,10 @@ stopsignal=KILL priority=20 command=x11vnc -display :1 -xkb -forever -shared -repeat + [program:novnc] priority=25 -directory=/usr/local/lib/web/frontend/static/novnc -command=bash /usr/local/lib/web/frontend/static/novnc/utils/launch.sh --listen 6081 +directory=/usr/local/lib/web/frontend%(ENV_PREFIX_PATH)s/static/novnc +command=bash /usr/local/lib/web/frontend%(ENV_PREFIX_PATH)s/static/novnc/utils/launch.sh --listen 6081 stopasgroup=true + diff --git a/image/startup.sh b/image/startup.sh index 875519d..c05c6e4 100755 --- a/image/startup.sh +++ b/image/startup.sh @@ -59,12 +59,21 @@ if [ -n "$HTTP_PASSWORD" ]; then sed -i 's|#_HTTP_PASSWORD_#||' /etc/nginx/sites-enabled/default fi +# dynamic prefix path renaming +if [ -n "$DYNAMIC_PREFIX_PATH" ]; then + PREFIX_PATH="$DYNAMIC_PREFIX_PATH" + echo "DYNAMIC_PREFIX_PATH: $PREFIX_PATH" + python /etc/RenameURL.py + /etc/install.sh + cat /etc/nginx/sites-enabled/default +fi + # novnc websockify -ln -s /usr/local/lib/web/frontend/static/websockify /usr/local/lib/web/frontend/static/novnc/utils/websockify -chmod +x /usr/local/lib/web/frontend/static/websockify/run +ln -s "/usr/local/lib/web/frontend$PREFIX_PATH/static/websockify" "/usr/local/lib/web/frontend$PREFIX_PATH/static/novnc/utils/websockify" +chmod +x "/usr/local/lib/web/frontend$PREFIX_PATH/static/websockify/run" # clearup -PASSWORD= +PASSWORD=+ HTTP_PASSWORD= -exec /bin/tini -- /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf +exec /bin/tini -- /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf \ No newline at end of file diff --git a/image/usr/local/lib/web/backend/run.py b/image/usr/local/lib/web/backend/run.py index 62f9b79..471a19c 100755 --- a/image/usr/local/lib/web/backend/run.py +++ b/image/usr/local/lib/web/backend/run.py @@ -114,6 +114,7 @@ def main(): ) logging.getLogger("werkzeug").setLevel(logging.WARNING) log = logging.getLogger('novnc2') + entrypoint() diff --git a/image/usr/local/lib/web/backend/vnc/app.py b/image/usr/local/lib/web/backend/vnc/app.py index 40f8d5a..eed936b 100644 --- a/image/usr/local/lib/web/backend/vnc/app.py +++ b/image/usr/local/lib/web/backend/vnc/app.py @@ -2,13 +2,14 @@ from __future__ import ( absolute_import, division, print_function, with_statement ) import re -from os import environ +import os from flask import ( Flask, request, Response, jsonify, abort, + url_for, ) from gevent import subprocess as gsp, spawn, sleep from geventwebsocket.exceptions import WebSocketError @@ -21,12 +22,14 @@ from .log import log # Flask app app = Flask('novnc2') app.config.from_object('config.Default') -app.config.from_object(environ.get('CONFIG') or 'config.Development') +app.config.from_object(os.environ.get('CONFIG') or 'config.Development') +PREFIX = os.getenv("PREFIX_PATH") -@app.route('/api/state') +@app.route(PREFIX+'/api/state') @httperror def apistate(): + print(url_for("apistate")) state.wait(int(request.args.get('id', -1)), 30) state.switch_video(request.args.get('video', 'false') == 'true') mystate = state.to_dict() @@ -36,14 +39,15 @@ def apistate(): }) -@app.route('/api/health') +@app.route(PREFIX+'/api/health') def apihealth(): + print(url_for("apihealth")) if state.health: return 'success' abort(503, 'unhealthy') -@app.route('/api/reset') +@app.route(PREFIX+'/api/reset') def reset(): if 'w' in request.args and 'h' in request.args: args = { @@ -68,7 +72,7 @@ def reset(): return jsonify({'code': 200}) -@app.route('/api/live.flv') +@app.route(PREFIX+'/api/live.flv') @httperror def liveflv(): def generate(): diff --git a/web/config/dev.env.js b/web/config/dev.env.js index 1e22973..961ded7 100644 --- a/web/config/dev.env.js +++ b/web/config/dev.env.js @@ -3,5 +3,6 @@ const merge = require('webpack-merge') const prodEnv = require('./prod.env') module.exports = merge(prodEnv, { - NODE_ENV: '"development"' + NODE_ENV: '"development"', + PREFIX_PATH: `"${process.env.PREFIX_PATH}"` }) diff --git a/web/config/index.js b/web/config/index.js index f54a519..d74e596 100644 --- a/web/config/index.js +++ b/web/config/index.js @@ -5,6 +5,13 @@ const path = require('path') const BACKEND = process.env.BACKEND || 'http://127.0.0.1:6080' +const PREFIX_PATH = process.env.PREFIX_PATH || '/app' + +console.log(`within /config/index.js: ${process.env.PREFIX_PATH}`) + +const api_key = `${PREFIX_PATH}/api` +const websockify_key = `${PREFIX_PATH}/websockfiy` + module.exports = { dev: { @@ -12,12 +19,12 @@ module.exports = { assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable: { - '/api': { + [api_key]: { target: BACKEND, changeOrigin: true, secure: false }, - '/websockify': { + [websockify_key]: { target: BACKEND, // logLevel: 'debug', ws: true, @@ -62,7 +69,7 @@ module.exports = { // Paths assetsRoot: path.resolve(__dirname, '../dist'), - assetsSubDirectory: 'static', + assetsSubDirectory: PREFIX_PATH.substr(1)+'/static', assetsPublicPath: '/', /** diff --git a/web/config/prod.env.js b/web/config/prod.env.js index a6f9976..1dea103 100644 --- a/web/config/prod.env.js +++ b/web/config/prod.env.js @@ -1,4 +1,5 @@ 'use strict' module.exports = { - NODE_ENV: '"production"' + NODE_ENV: '"production"', + PREFIX_PATH: `"${process.env.PREFIX_PATH}"` } diff --git a/web/src/components/Vnc.vue b/web/src/components/Vnc.vue index 873d0c3..fcaf9e5 100644 --- a/web/src/components/Vnc.vue +++ b/web/src/components/Vnc.vue @@ -56,7 +56,7 @@ export default { 'h': h } try { - const response = await this.$http.get('api/state', {params: params}) + const response = await this.$http.get(`${process.env.PREFIX_PATH}` + '/api/state', {params: params}) const body = response.data if (body.code !== 200) { this.stateErrorCount += 1 @@ -71,7 +71,7 @@ export default { // adaptive resolution if (!body.data.config.fixedResolution && body.data.config.sizeChangedCount === 0) { - const response = await this.$http.get('api/reset', {params: params}) + const response = await this.$http.get(`${process.env.PREFIX_PATH}` + '/api/reset', {params: params}) const body = response.data if (body.code !== 200) { this.stateErrorCount += 1 @@ -136,6 +136,9 @@ export default { // console.trace() console.log(`connecting...`) this.errorMessage = '' + let prefixName = `${process.env.PREFIX_PATH}`.substr(1) + console.log(prefixName) + let websockifyPath = prefixName + '/websockify' if (force || this.vncState === 'stopped') { this.vncState = 'connecting' let hostname = window.location.hostname @@ -143,10 +146,10 @@ export default { if (!port) { port = window.location.protocol[4] === 's' ? 443 : 80 } - let url = 'static/vnc.html?' + let url = prefixName + '/static/vnc.html?' url += 'autoconnect=1&' url += `host=${hostname}&port=${port}&` - url += `path=websockify&title=novnc2&` + url += `path=${websockifyPath}&title=novnc2&` url += `logging=warn` this.$refs.vncFrame.setAttribute('src', url) } @@ -154,7 +157,7 @@ export default { if (force || this.videoState === 'stopped') { const w = this.$refs.vncFrame.clientWidth const h = this.$refs.vncFrame.clientHeight - let url = `static/video.html?width=${w}&height=${h}&base=${window.location.host}` + let url = prefixName + `/static/video.html?width=${w}&height=${h}&base=${window.location.host}` this.$refs.videoFrame.setAttribute('src', url) this.videoState = 'connecting' } diff --git a/web/src/router/index.js b/web/src/router/index.js index ad0edb9..f1156f1 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -4,7 +4,11 @@ import Vnc from '@/components/Vnc' Vue.use(Router) +console.log(`within (router) /src/index.js: ${process.env.PREFIX_PATH}`) + export default new Router({ + mode: 'history', + base: `${process.env.PREFIX_PATH}`, routes: [ { path: '/',