├── .dockerignore ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── ARCHITECTURE.md ├── DEVELOPMENT.md ├── Dockerfile ├── Dockerfile.amd64 ├── Dockerfile.arm64 ├── Dockerfile.armhf ├── Dockerfile.j2 ├── LICENSE ├── LICENSE-original ├── Makefile ├── README-original.md ├── README.md ├── TODO ├── ai-robot-book.sh ├── build.bash ├── flavors ├── lxde.yml ├── lxqt.yml └── xfce4.yml ├── hooks └── pre_build ├── rdp-run.bash ├── rootfs ├── etc │ ├── asound.conf │ ├── nginx │ │ └── sites-enabled │ │ │ └── default │ └── supervisor │ │ └── conf.d │ │ ├── supervisord.conf │ │ └── supervisord.conf.j2 ├── root │ └── .gtkrc-2.0 ├── startup.sh └── usr │ └── local │ ├── bin │ └── xvfb.sh │ ├── lib │ └── web │ │ ├── backend │ │ ├── config │ │ │ └── __init__.py │ │ ├── log │ │ │ ├── __init__.py │ │ │ └── config.py │ │ ├── requirements.txt │ │ ├── run.py │ │ └── vnc │ │ │ ├── __init__.py │ │ │ ├── app.py │ │ │ ├── log.py │ │ │ ├── response.py │ │ │ ├── state.py │ │ │ └── util.py │ │ └── frontend │ │ └── .gitkeep │ └── share │ └── doro-lxde-wallpapers │ ├── bg1.jpg │ ├── bg2.jpg │ ├── bg3.jpg │ ├── bg4.jpg │ └── desktop-items-0.conf ├── run.bash ├── run.bat ├── screenshots └── lxde.png ├── web ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── build │ ├── build.js │ ├── check-versions.js │ ├── logo.png │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── config │ ├── dev.env.js │ ├── index.js │ ├── prod.env.js │ └── test.env.js ├── index.html ├── novnc-armhf-1.patch ├── package.json ├── src │ ├── App.vue │ ├── assets │ │ └── .gitkeep │ ├── components │ │ └── Vnc.vue │ ├── main.js │ └── router │ │ └── index.js ├── static │ ├── .gitkeep │ ├── scripts │ │ ├── flv.min.js │ │ └── jquery-3.3.1.min.js │ ├── video.html │ └── vnc.html ├── test │ ├── e2e │ │ ├── custom-assertions │ │ │ └── elementCount.js │ │ ├── nightwatch.conf.js │ │ ├── runner.js │ │ └── specs │ │ │ └── test.js │ └── unit │ │ ├── .eslintrc │ │ └── specs │ │ └── HelloWorld.spec.js └── yarn.lock └── xrdp └── pulse └── default.pa /.dockerignore: -------------------------------------------------------------------------------- 1 | web/node_modules 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Versions (please complete the following information):** 27 | - OS: [e.g. Ubuntu 18.04] 28 | - image tag [e.g. develop] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | tags 3 | cscope* 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "web/static/novnc"] 2 | path = web/static/novnc 3 | url = https://github.com/novnc/noVNC 4 | [submodule "web/static/websockify"] 5 | path = web/static/websockify 6 | url = https://github.com/novnc/websockify 7 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Architecture of the container # 2 | 3 | Components 4 | ============ 5 | 6 | The container contains the following components : 7 | - An Ubuntu base system 8 | - The tini + supervisord startup and daemon control system 9 | - Nginx Web server 10 | - A backend ("novnc2") Python Web app providing an API (written with 11 | Flask) on port 6079 12 | - A frontend VueJS Web app displayed to the user, which will wrap noVNC 13 | - noVNC + WebSockify providing the Web VNC client in an HTML5 canvas 14 | - Xvfb running the X11 server in memory 15 | - x11vnc exporting the X11 display through VNC 16 | - and all regular X applications, like the LXDE desktop and apps 17 | 18 | Wiring them all 19 | ------------------ 20 | 21 | Internally, Xvfb will be started in DISPLAY :1, then x11vnc will 22 | provide access to it on the default VNC port (5900). 23 | 24 | noVNC will be started listening to HTTP requests on port 6081. 25 | It is possible to connect directly to port 6081 of the container, to 26 | only use the regular noVNC Web interface (provided it is exported by 27 | the container). 28 | 29 | Above noVNC stands the VueJS frontend Web app provided by nginx, which 30 | will proxy the noVNC canvas, and will add some useful features over 31 | noVNC. 32 | 33 | User-oriented features 34 | ========================== 35 | 36 | The Web frontend adds the following features : 37 | - upon display of the Web page, the app will detect the size of the 38 | Web browser's window, and will invoke the backend API so as to make 39 | sure the noVNC rendering ajusts to that size 40 | - provide a flash video rendering transporting the sound (???) 41 | 42 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Get code 2 | 3 | ``` 4 | git clone --recursive https://github.com/fcwu/docker-ubuntu-vnc-desktop 5 | ``` 6 | 7 | or, if you have already cloned it, get submodules contents : 8 | ``` 9 | git submodule init; git submodule update 10 | ``` 11 | 12 | # Test local code 13 | 14 | ## Test-run in container rebuilt from local repo 15 | 16 | You may edit the code in your local copy of the repo, rebuild the 17 | container, and test the changes: 18 | 19 | ``` 20 | make clean 21 | FLAVOR=lxqt ARCH=amd64 IMAGE=ubuntu:18.04 make build 22 | make run 23 | ``` 24 | 25 | ## develop backend 26 | 27 | You may wish to work on the backend app. As the "make run" makes sure 28 | to mount the current dir contents under /src in the container, you can 29 | proceed as such (no compilation of the Python code): 30 | ``` 31 | make shell 32 | supervisorctl -c /etc/supervisor/supervisord.conf stop web 33 | cd /src/image/usr/local/lib/web/backend 34 | ./run.py --debug 35 | ``` 36 | 37 | ## develop frontend 38 | 39 | ``` 40 | cd web 41 | yarn add 42 | BACKEND=http://127.0.0.1:6080 npm run dev 43 | ``` 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | Dockerfile.amd64 -------------------------------------------------------------------------------- /Dockerfile.amd64: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # base system 3 | ################################################################################ 4 | 5 | FROM ubuntu:20.04 as system 6 | 7 | RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list; 8 | 9 | # built-in packages 10 | ENV DEBIAN_FRONTEND noninteractive 11 | RUN apt update \ 12 | && apt install -y --no-install-recommends software-properties-common curl apache2-utils \ 13 | && apt update \ 14 | && apt install -y --no-install-recommends --allow-unauthenticated \ 15 | supervisor nginx sudo net-tools zenity xz-utils \ 16 | dbus-x11 x11-utils alsa-utils \ 17 | mesa-utils libgl1-mesa-dri \ 18 | && apt autoclean -y \ 19 | && apt autoremove -y \ 20 | && rm -rf /var/lib/apt/lists/* 21 | 22 | # install debs error if combine together 23 | RUN apt update \ 24 | && apt install -y --no-install-recommends --allow-unauthenticated \ 25 | xvfb x11vnc \ 26 | vim-tiny firefox ttf-ubuntu-font-family ttf-wqy-zenhei \ 27 | && apt autoclean -y \ 28 | && apt autoremove -y \ 29 | && rm -rf /var/lib/apt/lists/* 30 | 31 | RUN apt update \ 32 | && apt install -y --no-install-recommends --allow-unauthenticated \ 33 | lxde gtk2-engines-murrine gnome-themes-standard gtk2-engines-pixbuf gtk2-engines-murrine arc-theme \ 34 | && apt autoclean -y \ 35 | && apt autoremove -y \ 36 | && rm -rf /var/lib/apt/lists/* 37 | 38 | # tini to fix subreap 39 | ARG TINI_VERSION=v0.19.0 40 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /bin/tini 41 | RUN chmod +x /bin/tini 42 | 43 | # python library 44 | COPY rootfs/usr/local/lib/web/backend/requirements.txt /tmp/ 45 | RUN apt-get update \ 46 | && dpkg-query -W -f='${Package}\n' > /tmp/a.txt \ 47 | && apt-get install -y python3-pip python3-dev build-essential \ 48 | && pip3 install setuptools wheel && pip3 install -r /tmp/requirements.txt \ 49 | && ln -s /usr/bin/python3 /usr/local/bin/python \ 50 | && dpkg-query -W -f='${Package}\n' > /tmp/b.txt \ 51 | && apt-get remove -y `diff --changed-group-format='%>' --unchanged-group-format='' /tmp/a.txt /tmp/b.txt | xargs` \ 52 | && apt-get autoclean -y \ 53 | && apt-get autoremove -y \ 54 | && rm -rf /var/lib/apt/lists/* \ 55 | && rm -rf /var/cache/apt/* /tmp/a.txt /tmp/b.txt 56 | 57 | 58 | ################################################################################ 59 | # builder 60 | ################################################################################ 61 | FROM ubuntu:20.04 as builder 62 | 63 | RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list; 64 | 65 | RUN apt-get update \ 66 | && apt-get install -y --no-install-recommends curl ca-certificates gnupg patch 67 | 68 | # nodejs 69 | RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \ 70 | && apt-get install -y nodejs 71 | 72 | # yarn 73 | RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ 74 | && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ 75 | && apt-get update \ 76 | && apt-get install -y yarn 77 | 78 | # build frontend 79 | COPY web /src/web 80 | RUN cd /src/web \ 81 | && yarn \ 82 | && yarn build 83 | RUN sed -i 's#app/locale/#novnc/app/locale/#' /src/web/dist/static/novnc/app/ui.js 84 | 85 | 86 | ################################################################################ 87 | # builder_rdp 88 | ################################################################################ 89 | FROM ubuntu:20.04 as builder_rdp 90 | 91 | # Install packages 92 | 93 | ENV DEBIAN_FRONTEND noninteractive 94 | RUN sed -i "s/# deb-src/deb-src/g" /etc/apt/sources.list 95 | RUN apt-get -y update 96 | RUN apt-get -yy upgrade 97 | ENV BUILD_DEPS="git autoconf pkg-config libssl-dev libpam0g-dev \ 98 | libx11-dev libxfixes-dev libxrandr-dev nasm xsltproc flex \ 99 | bison libxml2-dev dpkg-dev libcap-dev" 100 | RUN apt-get -yy install sudo apt-utils software-properties-common $BUILD_DEPS 101 | 102 | # Build xrdp 103 | 104 | WORKDIR /tmp 105 | RUN apt-get source pulseaudio 106 | RUN apt-get build-dep -yy pulseaudio 107 | WORKDIR /tmp/pulseaudio-13.99.1 108 | RUN dpkg-buildpackage -rfakeroot -uc -b 109 | WORKDIR /tmp 110 | RUN git clone --branch devel --recursive https://github.com/neutrinolabs/xrdp.git 111 | WORKDIR /tmp/xrdp 112 | RUN ./bootstrap 113 | RUN ./configure 114 | RUN make 115 | RUN make install 116 | WORKDIR /tmp 117 | RUN apt -yy install libpulse-dev 118 | RUN git clone --recursive https://github.com/neutrinolabs/pulseaudio-module-xrdp.git 119 | WORKDIR /tmp/pulseaudio-module-xrdp 120 | RUN ./bootstrap && ./configure PULSE_DIR=/tmp/pulseaudio-13.99.1 121 | RUN make 122 | RUN mkdir -p /tmp/so 123 | RUN cp src/.libs/*.so /tmp/so 124 | 125 | ################################################################################ 126 | # merge 127 | ################################################################################ 128 | FROM system 129 | LABEL maintainer="MASUTANI Yasuhiro " 130 | 131 | COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/ 132 | COPY rootfs / 133 | RUN ln -sf /usr/local/lib/web/frontend/static/websockify /usr/local/lib/web/frontend/static/novnc/utils/websockify && \ 134 | chmod +x /usr/local/lib/web/frontend/static/websockify/run 135 | 136 | RUN apt-get update -q && \ 137 | apt-get upgrade -yq && \ 138 | DEBIAN_FRONTEND=noninteractive apt-get install -yq crudini pulseaudio uuid-runtime xrdp locales && \ 139 | rm -rf /var/lib/apt/lists/* 140 | RUN mkdir -p /var/lib/xrdp-pulseaudio-installer 141 | COPY --from=builder_rdp /tmp/so/module-xrdp-source.so /var/lib/xrdp-pulseaudio-installer 142 | COPY --from=builder_rdp /tmp/so/module-xrdp-sink.so /var/lib/xrdp-pulseaudio-installer 143 | COPY xrdp /etc 144 | 145 | # Configure 146 | RUN mkdir /var/run/dbus && \ 147 | cp /etc/X11/xrdp/xorg.conf /etc/X11 && \ 148 | sed -i "s/console/anybody/g" /etc/X11/Xwrapper.config && \ 149 | sed -i "s/xrdp\/xorg/xorg/g" /etc/xrdp/sesman.ini && \ 150 | locale-gen ja_JP.UTF-8 && \ 151 | rm -rf /etc/xrdp/rsakeys.ini /etc/xrdp/*.pem 152 | 153 | # RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - 154 | RUN apt-get update -q && \ 155 | apt-get upgrade -yq && \ 156 | DEBIAN_FRONTEND=noninteractive apt-get install -yq wget curl git g++ build-essential vim sudo lsb-release locales bash-completion tzdata gosu && \ 157 | rm -rf /var/lib/apt/lists/* 158 | RUN wget -qO - https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/raw/master/pub.gpg \ 159 | | gpg --dearmor \ 160 | | dd of=/usr/share/keyrings/vscodium-archive-keyring.gpg && \ 161 | echo 'deb [ signed-by=/usr/share/keyrings/vscodium-archive-keyring.gpg ] https://paulcarroty.gitlab.io/vscodium-deb-rpm-repo/debs vscodium main' \ 162 | | tee /etc/apt/sources.list.d/vscodium.list && \ 163 | apt-get update -q && \ 164 | apt-get -y install python3-pip && \ 165 | apt-get -y install language-pack-ja fonts-takao fcitx-mozc dbus-x11 && \ 166 | apt-get -y install codium && \ 167 | apt-get -y install xscreensaver && \ 168 | rm -rf /var/lib/apt/lists/* 169 | RUN useradd --create-home --home-dir /home/ubuntu --shell /bin/bash --user-group --groups adm,sudo ubuntu && \ 170 | echo ubuntu:ubuntu | chpasswd && \ 171 | echo "ubuntu ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 172 | 173 | RUN gosu ubuntu echo 'export DONT_PROMPT_WSL_INSTALL=1' >> /home/ubuntu/.bashrc && \ 174 | gosu ubuntu bash -c "DONT_PROMPT_WSL_INSTALL=1 codium --install-extension ms-ceintl.vscode-language-pack-ja" && \ 175 | gosu ubuntu bash -c "DONT_PROMPT_WSL_INSTALL=1 codium --install-extension ms-python.python" 176 | 177 | RUN gosu ubuntu cp /root/.gtkrc-2.0 /home/ubuntu 178 | 179 | ARG ROS_DISTRO=foxy 180 | ARG INSTALL_PACKAGE=desktop 181 | 182 | # rosdep initを除いたもの 183 | ### 184 | RUN apt-get update -q && \ 185 | apt-get install -y curl gnupg2 lsb-release && \ 186 | curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg && \ 187 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/ros2.list > /dev/null && \ 188 | apt-get update -q && \ 189 | apt-get install -y ros-${ROS_DISTRO}-${INSTALL_PACKAGE} \ 190 | python3-argcomplete \ 191 | python3-colcon-common-extensions \ 192 | python3-rosdep python3-vcstool \ 193 | ros-${ROS_DISTRO}-gazebo-ros-pkgs && \ 194 | rm -rf /var/lib/apt/lists/* 195 | ### 196 | 197 | # rosdep init するもの 198 | ### 199 | # RUN apt-get update -q && \ 200 | # apt-get install -y curl gnupg2 lsb-release && \ 201 | # curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg && \ 202 | # echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/ros2.list > /dev/null && \ 203 | # apt-get update -q && \ 204 | # apt-get install -y ros-${ROS_DISTRO}-${INSTALL_PACKAGE} \ 205 | # python3-argcomplete \ 206 | # python3-colcon-common-extensions \ 207 | # python3-rosdep python3-vcstool \ 208 | # ros-${ROS_DISTRO}-gazebo-ros-pkgs && \ 209 | # rosdep init && \ 210 | # rm -rf /var/lib/apt/lists/* 211 | ### 212 | 213 | RUN gosu ubuntu rosdep update && \ 214 | grep -F "source /opt/ros/${ROS_DISTRO}/setup.bash" /home/ubuntu/.bashrc || echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> /home/ubuntu/.bashrc && \ 215 | sudo chown ubuntu:ubuntu /home/ubuntu/.bashrc 216 | 217 | # temporally fix to enable resize uri 218 | # https://github.com/fcwu/docker-ubuntu-vnc-desktop/pull/247 219 | RUN sed -i "s#location ~ .*/(api/.*|websockify) {#location ~ .*/(api/.*|websockify|resize) {#" /etc/nginx/sites-enabled/default 220 | 221 | ENV USER ubuntu 222 | 223 | # docker run の際に 以下のエラーを回避するために 224 | # ImportError: cannot import name 'soft_unicode' from 'markupsafe' 225 | # (/home/ubuntu/.local/lib/python3.8/site-packages/markupsafe/__init__.py) 226 | # werkzeugの問題回避 227 | # https://github.com/mjmeijer/donald_2021/issues/4 228 | RUN gosu ubuntu pip3 install werkzeug==2.0.3 229 | 230 | COPY ./ai-robot-book.sh /ai-robot-book.sh 231 | RUN mkdir -p /tmp/ros_setup_scripts_ubuntu && mv /ai-robot-book.sh /tmp/ros_setup_scripts_ubuntu/ && \ 232 | gosu ubuntu /tmp/ros_setup_scripts_ubuntu/ai-robot-book.sh 233 | 234 | EXPOSE 80 235 | WORKDIR /root 236 | ENV HOME=/home/ubuntu \ 237 | SHELL=/bin/bash 238 | HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://127.0.0.1:6079/api/health 239 | ENTRYPOINT ["/startup.sh"] 240 | -------------------------------------------------------------------------------- /Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | # Built with arch: arm64 flavor: lxde image: ubuntu:18.04 2 | # 3 | ################################################################################ 4 | # base system 5 | ################################################################################ 6 | 7 | # qemu helper for arm build 8 | FROM ubuntu:20.04 as amd64 9 | RUN apt update && apt install -y qemu-user-static 10 | FROM arm64v8/ubuntu:20.04 as system 11 | COPY --from=amd64 /usr/bin/qemu-aarch64-static /usr/bin/ 12 | 13 | 14 | 15 | RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list; 16 | 17 | 18 | # built-in packages 19 | ENV DEBIAN_FRONTEND noninteractive 20 | RUN apt update \ 21 | && apt install -y --no-install-recommends software-properties-common curl apache2-utils \ 22 | && apt update \ 23 | && apt install -y --no-install-recommends --allow-unauthenticated \ 24 | supervisor nginx sudo net-tools zenity xz-utils \ 25 | dbus-x11 x11-utils alsa-utils \ 26 | mesa-utils libgl1-mesa-dri \ 27 | && apt autoclean -y \ 28 | && apt autoremove -y \ 29 | && rm -rf /var/lib/apt/lists/* 30 | # install debs error if combine together 31 | RUN apt update \ 32 | && apt install -y --no-install-recommends --allow-unauthenticated \ 33 | xvfb x11vnc \ 34 | vim-tiny firefox chromium-browser ttf-ubuntu-font-family ttf-wqy-zenhei \ 35 | && add-apt-repository -r ppa:fcwu-tw/apps \ 36 | && apt autoclean -y \ 37 | && apt autoremove -y \ 38 | && rm -rf /var/lib/apt/lists/* 39 | 40 | RUN apt update \ 41 | && apt install -y --no-install-recommends --allow-unauthenticated \ 42 | lxde gtk2-engines-murrine gnome-themes-standard gtk2-engines-pixbuf gtk2-engines-murrine arc-theme \ 43 | && apt autoclean -y \ 44 | && apt autoremove -y \ 45 | && rm -rf /var/lib/apt/lists/* 46 | 47 | 48 | # Additional packages require ~600MB 49 | # libreoffice pinta language-pack-zh-hant language-pack-gnome-zh-hant firefox-locale-zh-hant libreoffice-l10n-zh-tw 50 | 51 | # tini for subreap 52 | ARG TINI_VERSION=v0.18.0 53 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-arm64 /bin/tini 54 | RUN chmod +x /bin/tini 55 | 56 | # ffmpeg 57 | RUN apt update \ 58 | && apt install -y --no-install-recommends --allow-unauthenticated \ 59 | ffmpeg \ 60 | && rm -rf /var/lib/apt/lists/* \ 61 | && mkdir /usr/local/ffmpeg \ 62 | && ln -s /usr/bin/ffmpeg /usr/local/ffmpeg/ffmpeg 63 | 64 | # python library 65 | COPY rootfs/usr/local/lib/web/backend/requirements.txt /tmp/ 66 | RUN apt-get update \ 67 | && dpkg-query -W -f='${Package}\n' > /tmp/a.txt \ 68 | && apt-get install -y python3-pip python3-dev build-essential \ 69 | && pip3 install setuptools wheel && pip3 install -r /tmp/requirements.txt \ 70 | && ln -s /usr/bin/python3 /usr/local/bin/python \ 71 | && dpkg-query -W -f='${Package}\n' > /tmp/b.txt \ 72 | && apt-get remove -y `diff --changed-group-format='%>' --unchanged-group-format='' /tmp/a.txt /tmp/b.txt | xargs` \ 73 | && apt-get autoclean -y \ 74 | && apt-get autoremove -y \ 75 | && rm -rf /var/lib/apt/lists/* \ 76 | && rm -rf /var/cache/apt/* /tmp/a.txt /tmp/b.txt 77 | 78 | 79 | ################################################################################ 80 | # builder 81 | ################################################################################ 82 | FROM ubuntu:20.04 as builder 83 | 84 | 85 | RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list; 86 | 87 | 88 | RUN apt-get update \ 89 | && apt-get install -y --no-install-recommends curl ca-certificates gnupg patch 90 | 91 | # nodejs 92 | RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \ 93 | && apt-get install -y nodejs 94 | 95 | # yarn 96 | RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ 97 | && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ 98 | && apt-get update \ 99 | && apt-get install -y yarn 100 | 101 | # build frontend 102 | COPY web /src/web 103 | RUN cd /src/web \ 104 | && yarn \ 105 | && yarn build 106 | RUN sed -i 's#app/locale/#novnc/app/locale/#' /src/web/dist/static/novnc/app/ui.js 107 | 108 | 109 | RUN cd /src/web/dist/static/novnc && patch -p0 < /src/web/novnc-armhf-1.patch 110 | 111 | 112 | ################################################################################ 113 | # merge 114 | ################################################################################ 115 | FROM system 116 | LABEL maintainer="fcwu.tw@gmail.com" 117 | 118 | COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/ 119 | COPY rootfs / 120 | RUN ln -sf /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 121 | RUN ln -sf /usr/local/lib/web/frontend/static/websockify /usr/local/lib/web/frontend/static/novnc/utils/websockify && \ 122 | chmod +x /usr/local/lib/web/frontend/static/websockify/run 123 | 124 | EXPOSE 80 125 | WORKDIR /root 126 | ENV HOME=/home/ubuntu \ 127 | SHELL=/bin/bash 128 | HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://127.0.0.1:6079/api/health 129 | ENTRYPOINT ["/startup.sh"] 130 | -------------------------------------------------------------------------------- /Dockerfile.armhf: -------------------------------------------------------------------------------- 1 | # Built with arch: armhf flavor: lxde image: ubuntu:18.04 2 | # 3 | ################################################################################ 4 | # base system 5 | ################################################################################ 6 | 7 | # qemu helper for arm build 8 | FROM ubuntu:18.04 as amd64 9 | RUN apt update && apt install -y qemu-user-static 10 | FROM arm32v7/ubuntu:18.04 as system 11 | COPY --from=amd64 /usr/bin/qemu-arm-static /usr/bin/ 12 | 13 | 14 | 15 | RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list; 16 | 17 | 18 | # built-in packages 19 | ENV DEBIAN_FRONTEND noninteractive 20 | RUN apt update \ 21 | && apt install -y --no-install-recommends software-properties-common curl apache2-utils \ 22 | && apt update \ 23 | && apt install -y --no-install-recommends --allow-unauthenticated \ 24 | supervisor nginx sudo net-tools zenity xz-utils \ 25 | dbus-x11 x11-utils alsa-utils \ 26 | mesa-utils libgl1-mesa-dri \ 27 | && apt autoclean -y \ 28 | && apt autoremove -y \ 29 | && rm -rf /var/lib/apt/lists/* 30 | # install debs error if combine together 31 | RUN add-apt-repository -y ppa:fcwu-tw/apps \ 32 | && apt update \ 33 | && apt install -y --no-install-recommends --allow-unauthenticated \ 34 | xvfb x11vnc=0.9.16-1 \ 35 | vim-tiny firefox chromium-browser ttf-ubuntu-font-family ttf-wqy-zenhei \ 36 | && add-apt-repository -r ppa:fcwu-tw/apps \ 37 | && apt autoclean -y \ 38 | && apt autoremove -y \ 39 | && rm -rf /var/lib/apt/lists/* 40 | 41 | RUN apt update \ 42 | && apt install -y --no-install-recommends --allow-unauthenticated \ 43 | lxde gtk2-engines-murrine gnome-themes-standard gtk2-engines-pixbuf gtk2-engines-murrine arc-theme \ 44 | && apt autoclean -y \ 45 | && apt autoremove -y \ 46 | && rm -rf /var/lib/apt/lists/* 47 | 48 | 49 | # Additional packages require ~600MB 50 | # libreoffice pinta language-pack-zh-hant language-pack-gnome-zh-hant firefox-locale-zh-hant libreoffice-l10n-zh-tw 51 | 52 | # tini for subreap 53 | ARG TINI_VERSION=v0.18.0 54 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-armhf /bin/tini 55 | RUN chmod +x /bin/tini 56 | 57 | # ffmpeg 58 | RUN mkdir -p /usr/local/ffmpeg \ 59 | && curl -sSL https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz | tar xJvf - -C /usr/local/ffmpeg/ --strip 1 60 | 61 | # python library 62 | COPY rootfs/usr/local/lib/web/backend/requirements.txt /tmp/ 63 | RUN apt-get update \ 64 | && dpkg-query -W -f='${Package}\n' > /tmp/a.txt \ 65 | && apt-get install -y python-pip python-dev build-essential \ 66 | && pip install setuptools wheel && pip install -r /tmp/requirements.txt \ 67 | && dpkg-query -W -f='${Package}\n' > /tmp/b.txt \ 68 | && apt-get remove -y `diff --changed-group-format='%>' --unchanged-group-format='' /tmp/a.txt /tmp/b.txt | xargs` \ 69 | && apt-get autoclean -y \ 70 | && apt-get autoremove -y \ 71 | && rm -rf /var/lib/apt/lists/* \ 72 | && rm -rf /var/cache/apt/* /tmp/a.txt /tmp/b.txt 73 | 74 | 75 | ################################################################################ 76 | # builder 77 | ################################################################################ 78 | FROM ubuntu:18.04 as builder 79 | 80 | 81 | RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list; 82 | 83 | 84 | RUN apt-get update \ 85 | && apt-get install -y --no-install-recommends curl ca-certificates gnupg patch 86 | 87 | # nodejs 88 | RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \ 89 | && apt-get install -y nodejs 90 | 91 | # yarn 92 | RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ 93 | && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ 94 | && apt-get update \ 95 | && apt-get install -y yarn 96 | 97 | # build frontend 98 | COPY web /src/web 99 | RUN cd /src/web \ 100 | && yarn \ 101 | && yarn run build 102 | RUN sed -i 's#app/locale/#novnc/app/locale/#' /src/web/dist/static/novnc/app/ui.js 103 | 104 | RUN cd /src/web/dist/static/novnc && patch -p0 < /src/web/novnc-armhf-1.patch 105 | 106 | 107 | ################################################################################ 108 | # merge 109 | ################################################################################ 110 | FROM system 111 | LABEL maintainer="fcwu.tw@gmail.com" 112 | 113 | COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/ 114 | COPY rootfs / 115 | RUN ln -sf /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 116 | RUN ln -sf /usr/local/lib/web/frontend/static/websockify /usr/local/lib/web/frontend/static/novnc/utils/websockify && \ 117 | chmod +x /usr/local/lib/web/frontend/static/websockify/run 118 | 119 | EXPOSE 80 120 | WORKDIR /root 121 | ENV HOME=/home/ubuntu \ 122 | SHELL=/bin/bash 123 | HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://127.0.0.1:6079/api/health 124 | ENTRYPOINT ["/startup.sh"] 125 | -------------------------------------------------------------------------------- /Dockerfile.j2: -------------------------------------------------------------------------------- 1 | # Built with arch: {{ arch }} flavor: {{ flavor }} image: {{ image }} 2 | # 3 | ################################################################################ 4 | # base system 5 | ################################################################################ 6 | {%if arch == "amd64"%} 7 | FROM {{image}} as system 8 | {%elif arch == "armhf"%} 9 | # qemu helper for arm build 10 | FROM {{image}} as amd64 11 | RUN apt update && apt install -y qemu-user-static 12 | FROM arm32v7/{{image}} as system 13 | COPY --from=amd64 /usr/bin/qemu-arm-static /usr/bin/ 14 | {%endif%} 15 | 16 | RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list; 17 | 18 | # built-in packages 19 | ENV DEBIAN_FRONTEND noninteractive 20 | RUN apt update \ 21 | && apt install -y --no-install-recommends software-properties-common curl apache2-utils \ 22 | && apt update \ 23 | && apt install -y --no-install-recommends --allow-unauthenticated \ 24 | supervisor nginx sudo net-tools zenity xz-utils \ 25 | dbus-x11 x11-utils alsa-utils \ 26 | mesa-utils libgl1-mesa-dri \ 27 | && apt autoclean -y \ 28 | && apt autoremove -y \ 29 | && rm -rf /var/lib/apt/lists/* 30 | # install debs error if combine together 31 | RUN apt update \ 32 | && apt install -y --no-install-recommends --allow-unauthenticated \ 33 | xvfb x11vnc \ 34 | vim-tiny firefox ttf-ubuntu-font-family ttf-wqy-zenhei \ 35 | && apt autoclean -y \ 36 | && apt autoremove -y \ 37 | && rm -rf /var/lib/apt/lists/* 38 | RUN apt update \ 39 | && apt install -y gpg-agent \ 40 | && curl -LO https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \ 41 | && (dpkg -i ./google-chrome-stable_current_amd64.deb || apt-get install -fy) \ 42 | && curl -sSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add \ 43 | && rm google-chrome-stable_current_amd64.deb \ 44 | && rm -rf /var/lib/apt/lists/* 45 | {%if desktop == "lxde" %} 46 | {%endif%} 47 | {%if desktop == "lxqt" %} 48 | {%endif%} 49 | {%if desktop == "xfce4" %} 50 | {%endif%} 51 | RUN apt update \ 52 | && apt install -y --no-install-recommends --allow-unauthenticated \ 53 | lxde gtk2-engines-murrine gnome-themes-standard gtk2-engines-pixbuf gtk2-engines-murrine arc-theme \ 54 | && apt autoclean -y \ 55 | && apt autoremove -y \ 56 | && rm -rf /var/lib/apt/lists/* 57 | # Additional packages require ~600MB 58 | # libreoffice pinta language-pack-zh-hant language-pack-gnome-zh-hant firefox-locale-zh-hant libreoffice-l10n-zh-tw 59 | 60 | # tini to fix subreap 61 | ARG TINI_VERSION=v0.18.0 62 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /bin/tini 63 | RUN chmod +x /bin/tini 64 | 65 | # ffmpeg 66 | RUN apt update \ 67 | && apt install -y --no-install-recommends --allow-unauthenticated \ 68 | ffmpeg \ 69 | && rm -rf /var/lib/apt/lists/* \ 70 | && mkdir /usr/local/ffmpeg \ 71 | && ln -s /usr/bin/ffmpeg /usr/local/ffmpeg/ffmpeg 72 | 73 | # python library 74 | COPY rootfs/usr/local/lib/web/backend/requirements.txt /tmp/ 75 | RUN apt-get update \ 76 | && dpkg-query -W -f='${Package}\n' > /tmp/a.txt \ 77 | && apt-get install -y python3-pip python3-dev build-essential \ 78 | && pip3 install setuptools wheel && pip3 install -r /tmp/requirements.txt \ 79 | && ln -s /usr/bin/python3 /usr/local/bin/python \ 80 | && dpkg-query -W -f='${Package}\n' > /tmp/b.txt \ 81 | && apt-get remove -y `diff --changed-group-format='%>' --unchanged-group-format='' /tmp/a.txt /tmp/b.txt | xargs` \ 82 | && apt-get autoclean -y \ 83 | && apt-get autoremove -y \ 84 | && rm -rf /var/lib/apt/lists/* \ 85 | && rm -rf /var/cache/apt/* /tmp/a.txt /tmp/b.txt 86 | 87 | 88 | ################################################################################ 89 | # builder 90 | ################################################################################ 91 | FROM {{image}} as builder 92 | 93 | {% if localbuild == 1 %} 94 | RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list; 95 | {% endif %} 96 | 97 | RUN apt-get update \ 98 | && apt-get install -y --no-install-recommends curl ca-certificates gnupg patch 99 | 100 | # nodejs 101 | RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \ 102 | && apt-get install -y nodejs 103 | 104 | # yarn 105 | RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ 106 | && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ 107 | && apt-get update \ 108 | && apt-get install -y yarn 109 | 110 | # build frontend 111 | COPY web /src/web 112 | RUN cd /src/web \ 113 | && yarn \ 114 | && yarn build 115 | RUN sed -i 's#app/locale/#novnc/app/locale/#' /src/web/dist/static/novnc/app/ui.js 116 | 117 | {%if arch == "armhf"%} 118 | RUN cd /src/web/dist/static/novnc && patch -p0 < /src/web/novnc-armhf-1.patch 119 | {%endif%} 120 | 121 | ################################################################################ 122 | # merge 123 | ################################################################################ 124 | FROM system 125 | LABEL maintainer="fcwu.tw@gmail.com" 126 | 127 | COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/ 128 | COPY rootfs / 129 | RUN ln -sf /usr/local/lib/web/frontend/static/websockify /usr/local/lib/web/frontend/static/novnc/utils/websockify && \ 130 | chmod +x /usr/local/lib/web/frontend/static/websockify/run 131 | 132 | EXPOSE 80 133 | WORKDIR /root 134 | ENV HOME=/home/ubuntu \ 135 | SHELL=/bin/bash 136 | HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://127.0.0.1:6079/api/health 137 | ENTRYPOINT ["/startup.sh"] 138 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 MASUTANI Yasuhiro 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-original: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build run 2 | 3 | # Default values for variables 4 | REPO ?= dorowu/ubuntu-desktop-lxde-vnc 5 | TAG ?= latest 6 | # you can choose other base image versions 7 | IMAGE ?= ubuntu:20.04 8 | # IMAGE ?= nvidia/cuda:10.1-cudnn7-devel-ubuntu18.04 9 | # choose from supported flavors (see available ones in ./flavors/*.yml) 10 | FLAVOR ?= lxde 11 | # armhf or amd64 12 | ARCH ?= amd64 13 | 14 | # These files will be generated from teh Jinja templates (.j2 sources) 15 | templates = Dockerfile rootfs/etc/supervisor/conf.d/supervisord.conf 16 | 17 | # Rebuild the container image 18 | build: $(templates) 19 | docker build -t $(REPO):$(TAG) . 20 | 21 | # Test run the container 22 | # the local dir will be mounted under /src read-only 23 | run: 24 | docker run --privileged --rm \ 25 | -p 6080:80 -p 6081:443 \ 26 | -v ${PWD}:/src:ro \ 27 | -e USER=doro -e PASSWORD=mypassword \ 28 | -e ALSADEV=hw:2,0 \ 29 | -e SSL_PORT=443 \ 30 | -e RELATIVE_URL_ROOT=approot \ 31 | -e OPENBOX_ARGS="--startup /usr/bin/galculator" \ 32 | -v ${PWD}/ssl:/etc/nginx/ssl \ 33 | --device /dev/snd \ 34 | --name ubuntu-desktop-lxde-test \ 35 | $(REPO):$(TAG) 36 | 37 | # Connect inside the running container for debugging 38 | shell: 39 | docker exec -it ubuntu-desktop-lxde-test bash 40 | 41 | # Generate the SSL/TLS config for HTTPS 42 | gen-ssl: 43 | mkdir -p ssl 44 | openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ 45 | -keyout ssl/nginx.key -out ssl/nginx.crt 46 | 47 | clean: 48 | rm -f $(templates) 49 | 50 | extra-clean: 51 | docker rmi $(REPO):$(TAG) 52 | docker image prune -f 53 | 54 | # Run jinja2cli to parse Jinja template applying rules defined in the flavors definitions 55 | %: %.j2 flavors/$(FLAVOR).yml 56 | docker run -v $(shell pwd):/data vikingco/jinja2cli \ 57 | -D flavor=$(FLAVOR) \ 58 | -D image=$(IMAGE) \ 59 | -D localbuild=$(LOCALBUILD) \ 60 | -D arch=$(ARCH) \ 61 | $< flavors/$(FLAVOR).yml > $@ || rm $@ 62 | -------------------------------------------------------------------------------- /README-original.md: -------------------------------------------------------------------------------- 1 | # docker-ubuntu-vnc-desktop 2 | 3 | [![Docker Pulls](https://img.shields.io/docker/pulls/dorowu/ubuntu-desktop-lxde-vnc.svg)](https://hub.docker.com/r/dorowu/ubuntu-desktop-lxde-vnc/) 4 | [![Docker Stars](https://img.shields.io/docker/stars/dorowu/ubuntu-desktop-lxde-vnc.svg)](https://hub.docker.com/r/dorowu/ubuntu-desktop-lxde-vnc/) 5 | 6 | docker-ubuntu-vnc-desktop is a Docker image to provide web VNC interface to access Ubuntu LXDE/LxQT desktop environment. 7 | 8 | 9 | 10 | 11 | 12 | - [Quick Start](#quick-start) 13 | - [VNC Viewer](#vnc-viewer) 14 | - [HTTP Base Authentication](#http-base-authentication) 15 | - [SSL](#ssl) 16 | - [Screen Resolution](#screen-resolution) 17 | - [Default Desktop User](#default-desktop-user) 18 | - [Deploy to a subdirectory (relative url root)](#deploy-to-a-subdirectory-relative-url-root) 19 | - [Sound (Preview version and Linux only)](#sound-preview-version-and-linux-only) 20 | - [Generate Dockerfile from jinja template](#generate-dockerfile-from-jinja-template) 21 | - [Troubleshooting and FAQ](#troubleshooting-and-faq) 22 | - [License](#license) 23 | 24 | 25 | 26 | ## Quick Start 27 | 28 | Run the docker container and access with port `6080` 29 | 30 | ```shell 31 | docker run -p 6080:80 -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc 32 | ``` 33 | 34 | Browse http://127.0.0.1:6080/ 35 | 36 | 37 | 38 | ### Ubuntu Flavors 39 | 40 | Choose your favorite Ubuntu version with [tags](https://hub.docker.com/r/dorowu/ubuntu-desktop-lxde-vnc/tags/) 41 | 42 | - focal: Ubuntu 20.04 (latest) 43 | - focal-lxqt: Ubuntu 20.04 LXQt 44 | - bionic: Ubuntu 18.04 45 | - bionic-lxqt: Ubuntu 18.04 LXQt 46 | - xenial: Ubuntu 16.04 (deprecated) 47 | - trusty: Ubuntu 14.04 (deprecated) 48 | 49 | ## VNC Viewer 50 | 51 | Forward VNC service port 5900 to host by 52 | 53 | ```shell 54 | docker run -p 6080:80 -p 5900:5900 -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc 55 | ``` 56 | 57 | Now, open the vnc viewer and connect to port 5900. If you would like to protect vnc service by password, set environment variable `VNC_PASSWORD`, for example 58 | 59 | ```shell 60 | docker run -p 6080:80 -p 5900:5900 -e VNC_PASSWORD=mypassword -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc 61 | ``` 62 | 63 | A prompt will ask password either in the browser or vnc viewer. 64 | 65 | ## HTTP Base Authentication 66 | 67 | This image provides base access authentication of HTTP via `HTTP_PASSWORD` 68 | 69 | ```shell 70 | docker run -p 6080:80 -e HTTP_PASSWORD=mypassword -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc 71 | ``` 72 | 73 | ## SSL 74 | 75 | To connect with SSL, generate self signed SSL certificate first if you don't have it 76 | 77 | ```shell 78 | mkdir -p ssl 79 | openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl/nginx.key -out ssl/nginx.crt 80 | ``` 81 | 82 | Specify SSL port by `SSL_PORT`, certificate path to `/etc/nginx/ssl`, and forward it to 6081 83 | 84 | ```shell 85 | docker run -p 6081:443 -e SSL_PORT=443 -v ${PWD}/ssl:/etc/nginx/ssl -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc 86 | ``` 87 | 88 | ## Screen Resolution 89 | 90 | The Resolution of virtual desktop adapts browser window size when first connecting the server. You may choose a fixed resolution by passing `RESOLUTION` environment variable, for example 91 | 92 | ```shell 93 | docker run -p 6080:80 -e RESOLUTION=1920x1080 -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc 94 | ``` 95 | 96 | ## Default Desktop User 97 | 98 | The default user is `root`. You may change the user and password respectively by `USER` and `PASSWORD` environment variable, for example, 99 | 100 | ```shell 101 | docker run -p 6080:80 -e USER=doro -e PASSWORD=password -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc 102 | ``` 103 | 104 | ## Deploy to a subdirectory (relative url root) 105 | 106 | You may deploy this application to a subdirectory, for example `/some-prefix/`. You then can access application by `http://127.0.0.1:6080/some-prefix/`. This can be specified using the `RELATIVE_URL_ROOT` configuration option like this 107 | 108 | ```shell 109 | docker run -p 6080:80 -e RELATIVE_URL_ROOT=some-prefix dorowu/ubuntu-desktop-lxde-vnc 110 | ``` 111 | 112 | NOTE: this variable should not have any leading and trailing splash (/) 113 | 114 | ## Sound (Preview version and Linux only) 115 | 116 | It only works in Linux. 117 | 118 | First of all, insert kernel module `snd-aloop` and specify `2` as the index of sound loop device 119 | 120 | ```shell 121 | sudo modprobe snd-aloop index=2 122 | ``` 123 | 124 | Start the container 125 | 126 | ```shell 127 | docker run -it --rm -p 6080:80 --device /dev/snd -e ALSADEV=hw:2,0 dorowu/ubuntu-desktop-lxde-vnc 128 | ``` 129 | 130 | where `--device /dev/snd -e ALSADEV=hw:2,0` means to grant sound device to container and set basic ASLA config to use card 2. 131 | 132 | Launch a browser with URL http://127.0.0.1:6080/#/?video, where `video` means to start with video mode. Now you can start Chromium in start menu (Internet -> Chromium Web Browser Sound) and try to play some video. 133 | 134 | Following is the screen capture of these operations. Turn on your sound at the end of video! 135 | 136 | [![demo video](http://img.youtube.com/vi/Kv9FGClP1-k/0.jpg)](http://www.youtube.com/watch?v=Kv9FGClP1-k) 137 | 138 | 139 | ## Generate Dockerfile from jinja template 140 | 141 | WARNING: Deprecated 142 | 143 | Dockerfile and configuration can be generated by template. 144 | 145 | - arch: one of `amd64` or `armhf` 146 | - flavor: refer to file in flavor/`flavor`.yml 147 | - image: base image 148 | - desktop: desktop environment which is set in flavor 149 | - addon_package: Debian package to be installed which is set in flavor 150 | 151 | Dockerfile and configuration are re-generate if they do not exist. Or you may force to re-generate by removing them with the command `make clean`. 152 | 153 | ## Troubleshooting and FAQ 154 | 155 | 1. boot2docker connection issue, https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/2 156 | 2. Multi-language supports, https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/80 157 | 3. Autostart, https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/85#issuecomment-466778407 158 | 4. x11vnc arguments(multiptr), https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/101 159 | 5. firefox/chrome crash (/dev/shm), https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/112 160 | 6. resize display size without destroying container, https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/115#issuecomment-522426037 161 | 162 | ## License 163 | 164 | See the LICENSE file for details. 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 「ROS2とPythonで作って学ぶAIロボット入門」の教材一式を提供するDockerイメージの作成 2 | 3 | ## 概要 4 | - [fcwu/docker-ubuntu-vnc-desktop](https://github.com/fcwu/docker-ubuntu-vnc-desktop)をフォーク. 5 | - フォークした時点の内容は[develop](https://github.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/tree/develop)ブランチ. 6 | - オリジナルと違い,amd64, Ubuntu 20.04, LXDEしか想定していない. 7 | - [tiryoh/ros2-desktop-vnc:foxy](https://github.com/Tiryoh/docker-ros2-desktop-vnc) を真似して,ROS2の環境を追加. 8 | - VSCodium 9 | - 日本語環境 10 | - [「ROS2とPythonで作って学ぶAIロボット入門」](https://github.com/AI-Robot-Book/)に必要なライブラリ,パッケージ,サンプルプログラムを全て含む. 11 | 12 | ## イメージ作成 13 | 14 | ``` 15 | git clone --recursive https://github.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book 16 | cd docker-ros2-desktop-ai-robot-book 17 | ./build.bash 18 | ``` 19 | 20 | ## イメージの公開場所 21 | 22 | https://hub.docker.com/repository/docker/airobotbook/ros2-desktop-ai-robot-book 23 | 24 | ## 実行(Linux) 25 | 26 | ### コンテナ起動 27 | 28 | ``` 29 | ./run.bash 30 | ``` 31 | ### デスクトップの利用 32 | 33 | - ウェブブラウザをVNCビューアにする場合 34 | - `http://127.0.0.1:6080` にアクセス. 35 | 36 | - VNCビューア(Remminaなど)を使う場合 37 | - `127.0.0.1:15900` に接続. 38 | 39 | ## 実行(Windows) 40 | 41 | ### コンテナ起動 42 | 43 | ``` 44 | run.bat 45 | ``` 46 | 47 | ### デスクトップの利用 48 | 49 | - ウェブブラウザをVNCビューアにする場合 50 | - `http://127.0.0.1:6080` にアクセス. 51 | 52 | - VNCビューア(TightVNC Viewerなど)を使う場合 53 | - `127.0.0.1:15900` に接続. 54 | 55 | - Windows標準のリモートデスクトップを使う場合 56 | - `127.0.0.1:13389` に接続. 57 | 58 | ## 既知の問題・今後の課題 59 | 60 | - オーディオ機能付きRDPサーバを使う場合,pyaudioを使う場合にreadがうまく行かない. 61 | - イメージのビルド中にrosdep initの実行に失敗する(現状ではrosdepを使わないようにしている) 62 | - 容量の削減 63 | 64 | ## 著者 65 | 66 | 升谷 保博 67 | 68 | ## 履歴 69 | 70 | - 2022/8/31 (v1.0) 71 | - 書籍の発行に合わせて正式リリース 72 | - イメージサイズ: 10GB 73 | 74 | - 2022/8/25 75 | - オーディオ機能付きRDPサーバを組み込む. 76 | - イメージサイズ: 9.8GB 77 | 78 | - 2022/8/4 79 | - [fcwu/docker-ubuntu-vnc-desktop](https://github.com/fcwu/docker-ubuntu-vnc-desktop)からフォークしたものにマージ. 80 | - イメージサイズ: 9.67GB 81 | 82 | ## ライセンス 83 | 84 | Copyright (c) 2022, MASUTANI Yasuhiro 85 | All rights reserved. 86 | This project is licensed under the Apache License 2.0 license found in the LICENSE file in the root directory of this project. 87 | 88 | ## 参考文献 89 | 90 | - Doro Wu: A Docker image to provide web VNC interface to access Ubuntu LXDE/LxQT desktop environment, [GitHub fcwu/docker-ubuntu-vnc-desktop](https://github.com/fcwu/docker-ubuntu-vnc-desktop) 91 | 92 | - Daisuke Sato: Dockerfiles to provide HTML5 VNC interface to access Ubuntu LXDE + ROS2, [GitHub tiryoh/ros2-desktop-vnc:foxy](https://github.com/Tiryoh/docker-ros2-desktop-vnc) 93 | 94 | - danielguerra69: Ubuntu 20.04/18.04/16.04 Multi User Remote Desktop Server, [GitHub danielguerra69/ubuntu-xrdp](https://github.com/danielguerra69/ubuntu-xrdp) 95 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - upgrade frontend packages 2 | - rewrite backend by golang 3 | -------------------------------------------------------------------------------- /ai-robot-book.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source /opt/ros/foxy/setup.bash 4 | 5 | set -eu 6 | set -v 7 | 8 | colcon_build_options= # '--symlink-install' 9 | 10 | sudo -E apt-get update -q 11 | 12 | sudo -E apt-get upgrade -yq 13 | 14 | # Gazeboのモデルのダウンロード 15 | # https://gist.github.com/Tiryoh/bf24f61992bfa8e32f2e75fc0672a647 16 | function download_model(){ 17 | if [[ -d $HOME'/.gazebo/models/'$1 ]]; then 18 | echo model $1 is ready. 19 | else 20 | wget -l1 -np -nc -r "http://models.gazebosim.org/"$1 --accept=gz 21 | fi 22 | } 23 | 24 | mkdir -p ~/.gazebo/models && cd ~/.gazebo/models 25 | cd /tmp 26 | TMPDIR=$(mktemp -d tmp.XXXXXXXXXX) 27 | cd $TMPDIR 28 | download_model sun 29 | download_model ground_plane 30 | download_model table 31 | download_model wood_cube_5cm 32 | download_model cafe_table 33 | download_model first_2015_trash_can 34 | download_model table_marble 35 | download_model mailbox 36 | if [[ -d "models.gazebosim.org" ]]; then 37 | cd "models.gazebosim.org" 38 | for i in *; do tar -zvxf "$i/model.tar.gz"; done 39 | cp -vfR * ~/.gazebo/models/ 40 | fi 41 | rm -rf $TMPDIR 42 | 43 | # 前処理 44 | mkdir -p ~/airobot_ws/src 45 | 46 | # 第1章 47 | sudo -E apt-get -y install terminator 48 | 49 | # 第2章 50 | cd ~/airobot_ws/src 51 | git clone https://github.com/AI-Robot-Book/chapter2 52 | cd ~/airobot_ws 53 | colcon build $colcon_build_options 54 | sudo -E apt-get -y install xterm 55 | sudo cp ~/airobot_ws/src/chapter2/turtlesim_launch/mysim.launch.py /opt/ros/foxy/share/turtlesim/launch 56 | 57 | # 第3章 58 | sudo -E apt-get -y install pulseaudio 59 | sudo -E apt-get -y install portaudio19-dev 60 | pip3 install pyaudio 61 | pip3 install SpeechRecognition 62 | pip3 install gTTS 63 | sudo -E apt-get -y install mpg123 64 | pip3 install mpg123 65 | cd ~/airobot_ws/src 66 | git clone https://github.com/AI-Robot-Book/chapter3 67 | cd ~/airobot_ws 68 | colcon build $colcon_build_options 69 | 70 | # 第4章 71 | sudo -E apt-get -y install ros-foxy-navigation2 72 | sudo -E apt-get -y install ros-foxy-nav2-bringup 73 | sudo -E apt-get -y install ros-foxy-slam-toolbox 74 | sudo -E apt-get -y install ros-foxy-dynamixel-sdk 75 | sudo -E apt-get -y install ros-foxy-turtlebot3-msgs 76 | sudo -E apt-get -y install ros-foxy-turtlebot3 77 | sudo -E apt-get -y install ros-foxy-teleop-tools 78 | pip3 install matplotlib 79 | pip3 install seaborn 80 | cd ~/airobot_ws/src 81 | git clone https://github.com/AI-Robot-Book/chapter4 82 | git clone https://github.com/AI-Robot-Book/happy_mini_turtlebot3_sim 83 | cd ~/airobot_ws 84 | colcon build $colcon_build_options 85 | echo "export TURTLEBOT3_MODEL=happy_mini" >> ~/.bashrc 86 | cp -r ~/airobot_ws/src/chapter4/map ~ 87 | 88 | # 第5章 89 | pip3 install opencv-contrib-python 90 | sudo -E apt-get -y install ros-foxy-vision-opencv 91 | sudo -E apt-get -y install ros-foxy-camera-calibration-parsers 92 | sudo -E apt-get -y install ros-foxy-camera-info-manager 93 | sudo -E apt-get -y install ros-foxy-launch-testing-ament-cmake 94 | sudo -E apt-get -y install ros-foxy-image-pipeline 95 | sudo -E apt-get -y install ros-foxy-usb-cam 96 | sudo -E apt-get -y install ros-foxy-realsense2-camera 97 | cd ~/airobot_ws 98 | git clone -bv6.2 https://github.com/ultralytics/yolov5 99 | cd yolov5 100 | pip3 install -r requirements.txt 101 | cd ~/airobot_ws/src 102 | git clone -b foxy https://github.com/JMU-ROBOTICS-VIVA/ros2_aruco 103 | git clone https://github.com/AI-Robot-Book/chapter5 104 | cd ~/airobot_ws 105 | colcon build $colcon_build_options 106 | 107 | # 第6章 108 | sudo -E apt-get -y install ros-foxy-joint-state-publisher-gui 109 | cd ~/airobot_ws/src 110 | git clone -b foxy-devel https://github.com/rt-net/crane_plus 111 | ### rosdep回避 112 | # rosdep install -r -y -i --from-paths . 113 | ### rosdepの代わりに依存パッケージの列挙 114 | sudo -E apt-get -y install \ 115 | ros-foxy-controller-manager ros-foxy-dynamixel-sdk ros-foxy-gazebo-ros2-control \ 116 | ros-foxy-gazebo-ros-pkgs ros-foxy-geometry-msgs ros-foxy-hardware-interface \ 117 | ros-foxy-ign-ros2-control ros-foxy-joint-state-publisher-gui ros-foxy-launch \ 118 | ros-foxy-moveit ros-foxy-moveit-ros-planning-interface ros-foxy-pluginlib \ 119 | ros-foxy-rclcpp ros-foxy-robot-state-publisher ros-foxy-ros2-controllers \ 120 | ros-foxy-ros2controlcli ros-foxy-ros-ign ros-foxy-rviz2 ros-foxy-tf2-geometry-msgs \ 121 | ros-foxy-tf2-ros ros-foxy-xacro 122 | ### 123 | sudo -E apt-get -y install ros-foxy-tf-transformations 124 | pip3 install transforms3d 125 | echo 'KERNEL=="ttyUSB*", DRIVERS=="ftdi_sio", MODE="0666", ATTR{device/latency_timer}="1"' > ~/99-ftdi_sio.rules 126 | sudo -E mv ~/99-ftdi_sio.rules /etc/udev/rules.d/ 127 | git clone https://github.com/AI-Robot-Book/chapter6 128 | cd ~/airobot_ws 129 | colcon build $colcon_build_options 130 | ## Ignition Gazeboのモデルのダウンロード 131 | ign fuel download -v 4 -u https://fuel.ignitionrobotics.org/1.0/openrobotics/models/sun 132 | ign fuel download -v 4 -u https://fuel.ignitionrobotics.org/1.0/openrobotics/models/ground%20plane 133 | ign fuel download -v 4 -u https://fuel.ignitionrobotics.org/1.0/openrobotics/models/wood%20cube%205cm 134 | ign fuel download -v 4 -u https://fuel.ignitionrobotics.org/1.0/openrobotics/models/table 135 | ign fuel download -v 4 -u https://fuel.ignitionrobotics.org/1.0/openrobotics/models/cafe%20table 136 | ign fuel download -v 4 -u https://fuel.ignitionrobotics.org/1.0/openrobotics/models/first%202015%20trash%20can 137 | ign fuel download -v 4 -u https://fuel.ignitionrobotics.org/1.0/openrobotics/models/table%20marble 138 | ign fuel download -v 4 -u https://fuel.ignitionrobotics.org/1.0/openrobotics/models/mailbox 139 | 140 | # 第7章 141 | cd ~/airobot_ws/src 142 | git clone -b ros2-foxy https://github.com/jeffrey870517/executive_smach 143 | git clone https://github.com/AI-Robot-Book/chapter7 144 | cd ~/airobot_ws 145 | colcon build $colcon_build_options 146 | 147 | # 付録E 148 | sudo -E apt-get -y install ros-foxy-tf-transformations 149 | pip3 install transforms3d 150 | cd ~/airobot_ws/src 151 | git clone https://github.com/AI-Robot-Book/appendixE 152 | cd ~/airobot_ws 153 | colcon build $colcon_build_options 154 | 155 | # Turtlebot2 156 | sudo -E apt-get -y install ros-foxy-gazebo-ros ros-foxy-camera-info-manager 157 | sudo -E apt-get -y install ros-foxy-realsense2-description 158 | sudo -E apt-get -y install ros-foxy-diagnostic-updater ros-foxy-ecl-linear-algebra ros-foxy-kobuki-core ros-foxy-xacro ros-foxy-joint-state-publisher 159 | sudo -E apt-get -y install ros-foxy-navigation2 ros-foxy-nav2-bringup 160 | sudo -E apt-get -y install ros-foxy-urg-node 161 | sudo -E apt-get -y install ros-foxy-tf-transformations 162 | sudo -E apt-get -y install ros-foxy-rqt-robot-steering 163 | 164 | cd ~/airobot_ws/src 165 | git clone https://github.com/kobuki-base/kobuki_ros_interfaces 166 | git clone -b foxy-devel https://github.com/pal-robotics/realsense_gazebo_plugin 167 | git clone https://github.com/AI-Robot-Book/kobuki_ros_airobotbook 168 | git clone https://github.com/AI-Robot-Book/turtlebot2_airobotbook 169 | 170 | cd ~/airobot_ws 171 | colcon build $colcon_build_options 172 | 173 | # 後処理 174 | 175 | echo "source ~/airobot_ws/install/setup.bash" >> ~/.bashrc 176 | 177 | set +u 178 | 179 | sudo -E rm -rf /var/lib/apt/lists/* 180 | -------------------------------------------------------------------------------- /build.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build \ 3 | $* \ 4 | --progress=plain \ 5 | --build-arg http_proxy=${http_proxy} \ 6 | --build-arg https_proxy=${https_proxy} \ 7 | --build-arg HTTP_PROXY=${HTTP_PROXY} \ 8 | --build-arg HTTPS_PROXY=${HTTPS_PROXY} \ 9 | -t airobotbook/ros2-desktop-ai-robot-book \ 10 | . 11 | -------------------------------------------------------------------------------- /flavors/lxde.yml: -------------------------------------------------------------------------------- 1 | --- 2 | addon_packages: 3 | - vim-tiny 4 | - firefox 5 | - chromium-browser 6 | - ttf-ubuntu-font-family 7 | - ttf-wqy-zenhei 8 | desktop: lxde 9 | -------------------------------------------------------------------------------- /flavors/lxqt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | addon_packages: 3 | - vim-tiny 4 | - firefox 5 | - chromium-browser 6 | - ttf-ubuntu-font-family 7 | - ttf-wqy-zenhei 8 | desktop: lxqt 9 | -------------------------------------------------------------------------------- /flavors/xfce4.yml: -------------------------------------------------------------------------------- 1 | --- 2 | addon_packages: 3 | - vim-tiny 4 | - firefox 5 | - xfce4-terminal 6 | desktop: xfce4 7 | -------------------------------------------------------------------------------- /hooks/pre_build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Register qemu-*-static for all supported processors except the 3 | # current one, but also remove all registered binfmt_misc before 4 | docker run --rm --privileged multiarch/qemu-user-static:register --reset 5 | -------------------------------------------------------------------------------- /rdp-run.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run \ 3 | -e http_proxy=${http_proxy} \ 4 | -e https_proxy=${https_proxy} \ 5 | -e HTTP_PROXY=${HTTP_PROXY} \ 6 | -e HTTPS_PROXY=${HTTPS_PROXY} \ 7 | -e RESOLUTION=1920x1080 \ 8 | --name ai_robot_book \ 9 | -p 13389:3389 \ 10 | --shm-size=512m \ 11 | --privileged \ 12 | $* \ 13 | airobotbook/ros2-desktop-ai-robot-book:latest -------------------------------------------------------------------------------- /rootfs/etc/asound.conf: -------------------------------------------------------------------------------- 1 | pcm.pulse { 2 | type pulse 3 | } 4 | 5 | ctl.pulse { 6 | type pulse 7 | } 8 | 9 | pcm.!default { 10 | type pulse 11 | } 12 | 13 | ctl.!default { 14 | type pulse 15 | } -------------------------------------------------------------------------------- /rootfs/etc/nginx/sites-enabled/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | # listen [::]:80 default_server ipv6only=on; 4 | 5 | #_SSL_PORT_#listen 443 ssl default_server; 6 | #_SSL_PORT_#listen [::]:443 ssl default_server ipv6only=on; 7 | #_SSL_PORT_#ssl_certificate /etc/nginx/ssl/nginx.crt; 8 | #_SSL_PORT_#ssl_certificate_key /etc/nginx/ssl/nginx.key; 9 | 10 | #_HTTP_PASSWORD_#auth_basic "Private Property"; 11 | #_HTTP_PASSWORD_#auth_basic_user_file /etc/nginx/.htpasswd; 12 | 13 | root /usr/local/lib/web/frontend/; 14 | index index.html index.htm; 15 | 16 | #_RELATIVE_URL_ROOT_location /_RELATIVE_URL_ROOT_/ { 17 | #_RELATIVE_URL_ROOT_ rewrite /_RELATIVE_URL_ROOT_/(.*) /$1 break; 18 | #_RELATIVE_URL_ROOT_ root /usr/local/lib/web/frontend/; 19 | #_RELATIVE_URL_ROOT_} 20 | 21 | location ~ .*/(api/.*|websockify) { 22 | try_files $uri @api$http_upgrade; 23 | } 24 | 25 | location / { 26 | rewrite /approot/(.*) /$1 break; 27 | root /usr/local/lib/web/frontend/; 28 | } 29 | 30 | location @apiwebsocket { 31 | #_RELATIVE_URL_ROOT_rewrite /_RELATIVE_URL_ROOT_/(.*) $1 break; 32 | proxy_connect_timeout 7d; 33 | proxy_send_timeout 7d; 34 | proxy_read_timeout 7d; 35 | proxy_buffering off; 36 | 37 | proxy_http_version 1.1; 38 | proxy_set_header Upgrade $http_upgrade; 39 | proxy_set_header Connection "upgrade"; 40 | proxy_pass http://127.0.0.1:6081; 41 | } 42 | 43 | location @api { 44 | #_RELATIVE_URL_ROOT_rewrite /_RELATIVE_URL_ROOT_/(.*) $1 break; 45 | proxy_set_header X-Real-IP $remote_addr; 46 | proxy_set_header X-Forwarded-For $remote_addr; 47 | proxy_set_header Host $host; 48 | max_ranges 0; 49 | proxy_pass http://127.0.0.1:6079; 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /rootfs/etc/supervisor/conf.d/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | redirect_stderr=true 3 | stopsignal=QUIT 4 | autorestart=true 5 | directory=/root 6 | 7 | [program:nginx] 8 | priority=10 9 | command=nginx -c /etc/nginx/nginx.conf -g 'daemon off;' 10 | 11 | [program:web] 12 | priority=10 13 | directory=/usr/local/lib/web/backend 14 | command=/usr/local/lib/web/backend/run.py 15 | stdout_logfile=/dev/fd/1 16 | stdout_logfile_maxbytes=0 17 | stderr_logfile=/dev/fd/1 18 | stderr_logfile_maxbytes=0 19 | 20 | 21 | [group:x] 22 | programs=xvfb,wm,lxpanel,pcmanfm,x11vnc,novnc 23 | 24 | [program:wm] 25 | priority=15 26 | command=/usr/bin/openbox 27 | environment=DISPLAY=":1",HOME="/root",USER="root" 28 | 29 | [program:lxpanel] 30 | priority=15 31 | directory=%HOME% 32 | command=/usr/bin/lxpanel --profile LXDE 33 | user=%USER% 34 | environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%" 35 | 36 | [program:pcmanfm] 37 | priority=15 38 | directory=%HOME% 39 | command=/usr/bin/pcmanfm --desktop --profile LXDE 40 | user=%USER% 41 | stopwaitsecs=3 42 | environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%" 43 | 44 | 45 | 46 | 47 | 48 | 49 | [program:xvfb] 50 | priority=10 51 | command=/usr/local/bin/xvfb.sh 52 | stopsignal=KILL 53 | 54 | [program:x11vnc] 55 | priority=20 56 | command=x11vnc -display :1 -xkb -forever -shared -repeat -capslock 57 | 58 | [program:novnc] 59 | priority=25 60 | directory=/usr/local/lib/web/frontend/static/novnc 61 | command=bash /usr/local/lib/web/frontend/static/novnc/utils/launch.sh --listen 6081 62 | stopasgroup=true 63 | 64 | [program:xrdp-sesman] 65 | command=/usr/sbin/xrdp-sesman --nodaemon 66 | user=root 67 | autorestart=true 68 | priority=400 69 | 70 | [program:xrdp] 71 | command=/usr/sbin/xrdp --nodaemon 72 | user=root 73 | autorestart=true 74 | priority=400 75 | 76 | [program:fcitx] 77 | priority=30 78 | directory=%HOME% 79 | command=fcitx -D 80 | user=%USER% 81 | environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%" 82 | -------------------------------------------------------------------------------- /rootfs/etc/supervisor/conf.d/supervisord.conf.j2: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | redirect_stderr=true 3 | stopsignal=QUIT 4 | autorestart=true 5 | directory=/root 6 | 7 | [program:nginx] 8 | priority=10 9 | command=nginx -c /etc/nginx/nginx.conf -g 'daemon off;' 10 | 11 | [program:web] 12 | priority=10 13 | directory=/usr/local/lib/web/backend 14 | command=/usr/local/lib/web/backend/run.py 15 | stdout_logfile=/dev/fd/1 16 | stdout_logfile_maxbytes=0 17 | stderr_logfile=/dev/fd/1 18 | stderr_logfile_maxbytes=0 19 | 20 | {% if desktop == "lxde" %} 21 | [group:x] 22 | programs=xvfb,wm,lxpanel,pcmanfm,x11vnc,novnc 23 | 24 | [program:wm] 25 | priority=15 26 | command=/usr/bin/openbox 27 | environment=DISPLAY=":1",HOME="/root",USER="root" 28 | 29 | [program:lxpanel] 30 | priority=15 31 | directory=%HOME% 32 | command=/usr/bin/lxpanel --profile LXDE 33 | user=%USER% 34 | environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%" 35 | 36 | [program:pcmanfm] 37 | priority=15 38 | directory=%HOME% 39 | command=/usr/bin/pcmanfm --desktop --profile LXDE 40 | user=%USER% 41 | environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%" 42 | {% endif %} 43 | 44 | {% if desktop == "lxqt" %} 45 | [group:x] 46 | programs=xvfb,wm,lxpanel,x11vnc,novnc 47 | 48 | [program:wm] 49 | priority=15 50 | command=/usr/bin/openbox 51 | environment=DISPLAY=":1",HOME="/root",USER="root" 52 | 53 | [program:lxpanel] 54 | priority=15 55 | directory=%HOME% 56 | command=/usr/bin/startlxqt 57 | user=%USER% 58 | environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%" 59 | {% endif %} 60 | 61 | {% if desktop == "xfce4" %} 62 | [group:x] 63 | programs=xvfb,lxpanel,x11vnc,novnc 64 | 65 | [program:lxpanel] 66 | priority=15 67 | directory=%HOME% 68 | command=/usr/bin/startxfce4 69 | user=%USER% 70 | environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%" 71 | {% endif %} 72 | 73 | [program:xvfb] 74 | priority=10 75 | command=/usr/local/bin/xvfb.sh 76 | stopsignal=KILL 77 | 78 | [program:x11vnc] 79 | priority=20 80 | command=x11vnc -display :1 -xkb -forever -shared -repeat -capslock 81 | 82 | [program:novnc] 83 | priority=25 84 | directory=/usr/local/lib/web/frontend/static/novnc 85 | command=bash /usr/local/lib/web/frontend/static/novnc/utils/launch.sh --listen 6081 86 | stopasgroup=true 87 | -------------------------------------------------------------------------------- /rootfs/root/.gtkrc-2.0: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT! This file will be overwritten by LXAppearance. 2 | # Any customization should be done in ~/.gtkrc-2.0.mine instead. 3 | include "/usr/share/themes/Arc/gtk-2.0/gtkrc" 4 | 5 | gtk-theme-name="Arc" 6 | gtk-icon-theme-name="nuoveXT2" 7 | gtk-font-name="Sans 10" 8 | gtk-cursor-theme-name="DMZ-White" 9 | gtk-cursor-theme-size=18 10 | gtk-toolbar-style=GTK_TOOLBAR_BOTH_HORIZ 11 | gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR 12 | gtk-button-images=1 13 | gtk-menu-images=1 14 | gtk-enable-event-sounds=1 15 | gtk-enable-input-feedback-sounds=1 16 | gtk-xft-antialias=1 17 | gtk-xft-hinting=1 18 | gtk-xft-hintstyle="hintslight" 19 | gtk-xft-rgba="rgb" 20 | include "/root/.gtkrc-2.0.mine" 21 | -------------------------------------------------------------------------------- /rootfs/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #デバッグ用 4 | #set -vx 5 | 6 | update-locale LANG=ja_JP.UTF-8 7 | 8 | export LANG=ja_JP.UTF-8 9 | export TZ=Asia/Tokyo 10 | 11 | if [ -n "$VNC_PASSWORD" ]; then 12 | echo -n "$VNC_PASSWORD" > /.password1 13 | x11vnc -storepasswd $(cat /.password1) /.password2 14 | chmod 400 /.password* 15 | sed -i 's/^command=x11vnc.*/& -rfbauth \/.password2/' /etc/supervisor/conf.d/supervisord.conf 16 | export VNC_PASSWORD= 17 | fi 18 | 19 | if [ -n "$X11VNC_ARGS" ]; then 20 | sed -i "s/^command=x11vnc.*/& ${X11VNC_ARGS}/" /etc/supervisor/conf.d/supervisord.conf 21 | fi 22 | 23 | if [ -n "$OPENBOX_ARGS" ]; then 24 | sed -i "s#^command=/usr/bin/openbox\$#& ${OPENBOX_ARGS}#" /etc/supervisor/conf.d/supervisord.conf 25 | fi 26 | 27 | if [ -n "$RESOLUTION" ]; then 28 | sed -i "s/1024x768/$RESOLUTION/" /usr/local/bin/xvfb.sh 29 | fi 30 | 31 | sed -i -e "s|%USER%|$USER|" -e "s|%HOME%|$HOME|" /etc/supervisor/conf.d/supervisord.conf 32 | 33 | cat << EOF > ${HOME}/.xsession 34 | pulseaudio -D --enable-memfd=True 35 | export TZ=${TZ} 36 | export http_proxy=${http_proxy} 37 | export https_proxy=${https_proxy} 38 | export HTTP_PROXY=${HTTP_PROXY} 39 | export HTTPS_PROXY=${HTTPS_PROXY} 40 | sudo supervisorctl stop fcitx 41 | sed -i -e "s/EnabledIMList=/EnabledIMList=fcitx-keyboard-jp:True,/" -e "s/,fcitx-keyboard-jp:[^,]*//g" ${HOME}/.config/fcitx/profile 42 | fcitx-remote -r 43 | lxsession -s LXDE -e LXDE 44 | EOF 45 | 46 | chown -R $USER:$USER ${HOME}/.xsession 47 | 48 | # Error No session for pid XXXX の回避のためlxpolkitを無効化 49 | mv /bin/lxpolkit /bin/lxpolkit.bak 50 | 51 | # home folder 52 | if [ ! -x "$HOME/.config/pcmanfm/LXDE/" ]; then 53 | mkdir -p $HOME/.config/pcmanfm/LXDE/ 54 | ln -sf /usr/local/share/doro-lxde-wallpapers/desktop-items-0.conf $HOME/.config/pcmanfm/LXDE/ 55 | chown -R $USER:$USER $HOME/.config/pcmanfm/LXDE 56 | fi 57 | 58 | # nginx workers 59 | sed -i 's|worker_processes .*|worker_processes 1;|' /etc/nginx/nginx.conf 60 | 61 | # nginx ssl 62 | if [ -n "$SSL_PORT" ] && [ -e "/etc/nginx/ssl/nginx.key" ]; then 63 | echo "* enable SSL" 64 | sed -i 's|#_SSL_PORT_#\(.*\)443\(.*\)|\1'$SSL_PORT'\2|' /etc/nginx/sites-enabled/default 65 | sed -i 's|#_SSL_PORT_#||' /etc/nginx/sites-enabled/default 66 | fi 67 | 68 | # nginx http base authentication 69 | if [ -n "$HTTP_PASSWORD" ]; then 70 | echo "* enable HTTP base authentication" 71 | htpasswd -bc /etc/nginx/.htpasswd $USER $HTTP_PASSWORD 72 | sed -i 's|#_HTTP_PASSWORD_#||' /etc/nginx/sites-enabled/default 73 | fi 74 | 75 | # dynamic prefix path renaming 76 | if [ -n "$RELATIVE_URL_ROOT" ]; then 77 | echo "* enable RELATIVE_URL_ROOT: $RELATIVE_URL_ROOT" 78 | sed -i 's|#_RELATIVE_URL_ROOT_||' /etc/nginx/sites-enabled/default 79 | sed -i 's|_RELATIVE_URL_ROOT_|'$RELATIVE_URL_ROOT'|' /etc/nginx/sites-enabled/default 80 | fi 81 | # generate xrdp key 82 | if [ ! -f "/etc/xrdp/rsakeys.ini" ]; 83 | then 84 | xrdp-keygen xrdp auto 85 | fi 86 | 87 | # generate certificate for tls connection 88 | if [ ! -f "/etc/xrdp/cert.pem" ]; 89 | then 90 | # delete eventual leftover private key 91 | rm -f /etc/xrdp/key.pem || true 92 | cd /etc/xrdp 93 | if [ ! $CERTIFICATE_SUBJECT ]; then 94 | CERTIFICATE_SUBJECT="/C=US/ST=Some State/L=Some City/O=Some Org/OU=Some Unit/CN=Terminalserver" 95 | fi 96 | openssl req -x509 -newkey rsa:2048 -nodes -keyout /etc/xrdp/key.pem -out /etc/xrdp/cert.pem -days 365 -subj "$CERTIFICATE_SUBJECT" 97 | crudini --set /etc/xrdp/xrdp.ini Globals security_layer tls 98 | crudini --set /etc/xrdp/xrdp.ini Globals certificate /etc/xrdp/cert.pem 99 | crudini --set /etc/xrdp/xrdp.ini Globals key_file /etc/xrdp/key.pem 100 | 101 | fi 102 | 103 | # generate machine-id 104 | uuidgen > /etc/machine-id 105 | 106 | # clearup 107 | PASSWORD= 108 | HTTP_PASSWORD= 109 | 110 | ### 別の場所で設定すべきかも 111 | export GTK_IM_MODULE=fcitx 112 | export QT_IM_MODULE=fcitx 113 | export XMODIFIERS=@im=fcitx 114 | export DefaultIMModule=fcitx 115 | ### 116 | 117 | exec /bin/tini -- supervisord -n -c /etc/supervisor/supervisord.conf 118 | -------------------------------------------------------------------------------- /rootfs/usr/local/bin/xvfb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec /usr/bin/Xvfb :1 -screen 0 1024x768x24 4 | -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/backend/config/__init__.py: -------------------------------------------------------------------------------- 1 | class Default(object): 2 | DEBUG = True 3 | 4 | 5 | class Development(Default): 6 | PHASE = 'development' 7 | 8 | 9 | class Production(Default): 10 | PHASE = 'production' 11 | DEBUG = False 12 | -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/backend/log/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/037ed330804758ee020479d2c5fcf072bd9ff1fc/rootfs/usr/local/lib/web/backend/log/__init__.py -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/backend/log/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import logging 4 | import logging.handlers 5 | 6 | 7 | # The terminal has 8 colors with codes from 0 to 7 8 | BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) 9 | 10 | # These are the sequences need to get colored ouput 11 | RESET_SEQ = "\033[0m" 12 | COLOR_SEQ = "\033[1;%dm" 13 | BOLD_SEQ = "\033[1m" 14 | 15 | # The background is set with 40 plus the number of the color, 16 | # and the foreground with 30 17 | COLORS = { 18 | 'WARNING': COLOR_SEQ % (30 + YELLOW) + 'WARN ' + RESET_SEQ, 19 | 'INFO': COLOR_SEQ % (30 + WHITE) + 'INFO ' + RESET_SEQ, 20 | 'DEBUG': COLOR_SEQ % (30 + BLUE) + 'DEBUG' + RESET_SEQ, 21 | 'CRITICAL': COLOR_SEQ % (30 + YELLOW) + 'CRITI' + RESET_SEQ, 22 | 'ERROR': COLOR_SEQ % (30 + RED) + 'ERROR' + RESET_SEQ, 23 | } 24 | 25 | 26 | class ColoredFormatter(logging.Formatter): 27 | def __init__(self, msg, use_color=True): 28 | logging.Formatter.__init__(self, msg) 29 | self.use_color = use_color 30 | 31 | def format(self, record): 32 | if self.use_color: 33 | record.levelname = COLORS.get(record.levelname, record.levelname) 34 | return logging.Formatter.format(self, record) 35 | 36 | 37 | class LoggingConfiguration(object): 38 | COLOR_FORMAT = "%(asctime)s" + \ 39 | " %(levelname)s %(message)s " + \ 40 | "(" + BOLD_SEQ + "%(filename)s" + RESET_SEQ + ":%(lineno)d)" 41 | NO_COLOR_FORMAT = "%(asctime)s %(levelname)s " + \ 42 | "%(message)s " + \ 43 | "(%(filename)s:%(lineno)d)" 44 | FILE_FORMAT = "%(asctime)s %(levelname)s " + \ 45 | "%(message)s " 46 | 47 | @classmethod 48 | def set(cls, log_level, log_filename, append=None, **kwargs): 49 | """ Configure a rotating file logging 50 | """ 51 | logger = logging.getLogger() 52 | logger.setLevel(log_level) 53 | 54 | COLOR_FORMAT = cls.COLOR_FORMAT 55 | NO_COLOR_FORMAT = cls.NO_COLOR_FORMAT 56 | FILE_FORMAT = cls.FILE_FORMAT 57 | if 'name' in kwargs: 58 | COLOR_FORMAT = COLOR_FORMAT.replace('%(threadName)-22s', 59 | '%-22s' % (kwargs['name'])) 60 | NO_COLOR_FORMAT = NO_COLOR_FORMAT.replace( 61 | '%(threadName)-22s', '%-22s' % (kwargs['name'])) 62 | FILE_FORMAT = FILE_FORMAT.replace( 63 | '%(threadName)-22s', '%s' % (kwargs['name'])) 64 | 65 | # Log to rotating file 66 | try: 67 | fh = logging.handlers.RotatingFileHandler( 68 | log_filename, 69 | mode='a+', 70 | backupCount=3 71 | ) 72 | fh.setFormatter(ColoredFormatter(FILE_FORMAT, False)) 73 | fh.setLevel(log_level) 74 | logger.addHandler(fh) 75 | if not append: 76 | # Create a new log file on every new 77 | fh.doRollover() 78 | except IOError as e: 79 | print('ignore to log to {}: {}'.format(log_filename, e)) 80 | 81 | # Log to sys.stderr using log level passed through command line 82 | if log_level != logging.NOTSET: 83 | log_handler = logging.StreamHandler(sys.stdout) 84 | if sys.platform.find('linux') >= 0: 85 | formatter = ColoredFormatter(COLOR_FORMAT) 86 | else: 87 | formatter = ColoredFormatter(NO_COLOR_FORMAT, False) 88 | log_handler.setFormatter(formatter) 89 | log_handler.setLevel(log_level) 90 | logger.addHandler(log_handler) 91 | -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/backend/requirements.txt: -------------------------------------------------------------------------------- 1 | backports.ssl-match-hostname==3.7.0.1 2 | certifi==2019.9.11 3 | chardet==3.0.4 4 | Click==7.0 5 | Flask==1.1.1 6 | Flask-Login==0.4.1 7 | gevent==1.4.0 8 | gevent-websocket==0.10.1 9 | greenlet==0.4.15 10 | idna==2.8 11 | itsdangerous==1.1.0 12 | Jinja2==2.11.3 13 | MarkupSafe==1.1.1 14 | meld3==2.0.0 15 | requests==2.22.0 16 | six==1.12.0 17 | urllib3==1.25.6 18 | websocket-client==0.47.0 19 | Werkzeug==0.16.0 20 | -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/backend/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from __future__ import ( 3 | absolute_import, division, print_function, with_statement 4 | ) 5 | import os 6 | import time 7 | import sys 8 | import subprocess 9 | from vnc.util import ignored 10 | 11 | 12 | def main(): 13 | def run_with_reloader(main_func, extra_files=None, interval=3): 14 | """Run the given function in an independent python interpreter.""" 15 | def find_files(directory="./"): 16 | for root, dirs, files in os.walk(directory): 17 | for basename in files: 18 | if basename.endswith('.py'): 19 | filename = os.path.join(root, basename) 20 | yield filename 21 | 22 | if os.environ.get('WERKZEUG_RUN_MAIN') == 'true': 23 | try: 24 | main_func() 25 | except KeyboardInterrupt: 26 | pass 27 | return 28 | 29 | proc = None 30 | try: 31 | while True: 32 | log.info('Restarting with reloader {} {}'.format( 33 | sys.executable, 34 | ' '.join(sys.argv)) 35 | ) 36 | args = [sys.executable] + sys.argv 37 | new_environ = os.environ.copy() 38 | new_environ['WERKZEUG_RUN_MAIN'] = 'true' 39 | 40 | proc = subprocess.Popen( 41 | args, 42 | env=new_environ, 43 | close_fds=True, 44 | preexec_fn=os.setsid 45 | ) 46 | mtimes = {} 47 | restart = False 48 | while not restart: 49 | for filename in find_files(): 50 | try: 51 | mtime = os.stat(filename).st_mtime 52 | except OSError: 53 | continue 54 | 55 | old_time = mtimes.get(filename) 56 | if old_time is None: 57 | mtimes[filename] = mtime 58 | continue 59 | elif mtime > old_time: 60 | log.info( 61 | 'Detected change in {}, reloading'.format( 62 | filename 63 | ) 64 | ) 65 | restart = True 66 | proc.terminate() 67 | break 68 | time.sleep(interval) 69 | except KeyboardInterrupt: 70 | pass 71 | finally: 72 | with ignored(Exception): 73 | proc.terminate() 74 | 75 | def run_server(): 76 | import socket 77 | from gevent.pywsgi import WSGIServer 78 | from vnc.app import app 79 | 80 | # websocket conflict: WebSocketHandler 81 | if DEBUG: 82 | # from werkzeug.debug import DebuggedApplication 83 | app.debug = True 84 | # app = DebuggedApplication(app, evalex=True) 85 | 86 | try: 87 | log.info('Listening on http://localhost:{}'.format(PORT)) 88 | http_server = WSGIServer(('localhost', PORT), app) 89 | http_server.serve_forever() 90 | # app.run(host='localhost', port=PORT) 91 | except socket.error as e: 92 | log.exception(e) 93 | except KeyboardInterrupt: 94 | pass 95 | finally: 96 | http_server.stop(timeout=10) 97 | log.info('shutdown gracefully') 98 | 99 | PORT = 6079 100 | DEBUG = False 101 | os.environ['CONFIG'] = 'config.Production' 102 | entrypoint = run_server 103 | if '--debug' in sys.argv: 104 | DEBUG = True 105 | os.environ['CONFIG'] = 'config.Development' 106 | entrypoint = lambda: run_with_reloader(run_server) 107 | 108 | # logging 109 | import logging 110 | from log.config import LoggingConfiguration 111 | LoggingConfiguration.set( 112 | logging.DEBUG if DEBUG else logging.INFO, 113 | '/var/log/web.log' 114 | ) 115 | logging.getLogger("werkzeug").setLevel(logging.WARNING) 116 | log = logging.getLogger('novnc2') 117 | 118 | entrypoint() 119 | 120 | 121 | if __name__ == "__main__": 122 | main() 123 | -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/backend/vnc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/037ed330804758ee020479d2c5fcf072bd9ff1fc/rootfs/usr/local/lib/web/backend/vnc/__init__.py -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/backend/vnc/app.py: -------------------------------------------------------------------------------- 1 | from __future__ import ( 2 | absolute_import, division, print_function, with_statement 3 | ) 4 | import re 5 | import os 6 | from flask import ( 7 | Flask, 8 | request, 9 | Response, 10 | jsonify, 11 | abort, 12 | ) 13 | from gevent import subprocess as gsp, spawn, sleep 14 | from geventwebsocket.exceptions import WebSocketError 15 | from .response import httperror 16 | from .util import ignored 17 | from .state import state 18 | from .log import log 19 | 20 | 21 | # Flask app 22 | app = Flask('novnc2') 23 | app.config.from_object('config.Default') 24 | app.config.from_object(os.environ.get('CONFIG') or 'config.Development') 25 | 26 | 27 | @app.route('/api/state') 28 | @httperror 29 | def apistate(): 30 | state.wait(int(request.args.get('id', -1)), 30) 31 | state.switch_video(request.args.get('video', 'false') == 'true') 32 | mystate = state.to_dict() 33 | return jsonify({ 34 | 'code': 200, 35 | 'data': mystate, 36 | }) 37 | 38 | 39 | @app.route('/api/health') 40 | def apihealth(): 41 | if state.health: 42 | return 'success' 43 | abort(503, 'unhealthy') 44 | 45 | 46 | @app.route('/api/reset') 47 | def reset(): 48 | if 'w' in request.args and 'h' in request.args: 49 | args = { 50 | 'w': int(request.args.get('w')), 51 | 'h': int(request.args.get('h')), 52 | } 53 | state.set_size(args['w'], args['h']) 54 | 55 | state.apply_and_restart() 56 | 57 | # check all running 58 | for i in range(40): 59 | if state.health: 60 | break 61 | sleep(1) 62 | log.info('wait services is ready...') 63 | else: 64 | return jsonify({ 65 | 'code': 500, 66 | 'errorMessage': 'service is not ready, please restart container' 67 | }) 68 | return jsonify({'code': 200}) 69 | 70 | 71 | @app.route('/resize') 72 | @httperror 73 | def apiresize(): 74 | state.reset_size() 75 | return '' 76 | 77 | 78 | @app.route('/api/live.flv') 79 | @httperror 80 | def liveflv(): 81 | def generate(): 82 | xenvs = { 83 | 'DISPLAY': ':1', 84 | } 85 | bufsize = 1024 * 1 86 | framerate = 20 87 | 88 | # sound 89 | sound_cmd_input = [] 90 | sound_cmd_parameters = [] 91 | zero_latency_make_sound_not_good = [ 92 | '-tune', 'zerolatency', 93 | ] 94 | 95 | xenvs['X_WIDTH'] = state.w 96 | xenvs['X_HEIGHT'] = state.h 97 | xenvs['X_WIDTH'] -= state.w % 2 98 | xenvs['X_HEIGHT'] -= state.h % 2 99 | 100 | pixels_count = xenvs['X_WIDTH'] * xenvs['X_HEIGHT'] 101 | # factor (720p) 102 | # 383: 2400k 103 | # 300: 3000k 104 | # 230: 4000k 105 | factor = 265 106 | maxbitrate_cmd = [ 107 | '-maxrate', str(int(pixels_count / factor)) + 'k', 108 | '-bufsize', str(int(pixels_count / factor / 3)) + 'k' 109 | ] 110 | 111 | # TODO move to global 112 | # get default source 113 | sound_cmd_input = [ 114 | '-f', 'alsa', 115 | '-i', 'hw:2,1', 116 | ] 117 | sound_cmd_parameters = [ 118 | '-ar', '44100', 119 | '-c:a', 'mp3', 120 | ] 121 | # flv.js report error if enabling hw acceleration 122 | # hwaccel_dev = ['-vaapi_device', '/dev/dri/renderD128'] 123 | # hwaccel_if = ['-vf', 'format=nv12,hwupload'] 124 | # vcodec = 'h264_vaapi' 125 | hwaccel_dev = [] 126 | hwaccel_if = [] 127 | vcodec = 'libx264' 128 | # zero_latency_make_sound_not_good = [] 129 | # sound_cmd_parameters = [] 130 | # sound_cmd_input = [] 131 | cmd = ['/usr/local/ffmpeg/ffmpeg'] + sound_cmd_input + hwaccel_dev + [ 132 | '-video_size', '{X_WIDTH}x{X_HEIGHT}'.format(**xenvs), 133 | '-framerate', '{}'.format(framerate), 134 | '-f', 'x11grab', '-draw_mouse', '1', 135 | '-i', '{DISPLAY}'.format(**xenvs), 136 | ] + hwaccel_if + [ 137 | '-r', '{}'.format(framerate), 138 | '-g', '{}'.format(framerate), 139 | '-flags:v', '+global_header', 140 | '-vcodec', vcodec, 141 | '-preset', 'ultrafast', 142 | '-b_strategy', '0', 143 | '-pix_fmt', 'yuv420p', 144 | '-bsf:v', 'dump_extra=freq=e', 145 | ] + maxbitrate_cmd \ 146 | + sound_cmd_parameters + zero_latency_make_sound_not_good + [ 147 | '-f', 'flv', 'pipe:1', 148 | ] 149 | log.info('command: ' + ' '.join(cmd)) 150 | pobj = gsp.Popen( 151 | cmd, 152 | stdout=gsp.PIPE, 153 | stderr=gsp.PIPE, 154 | env={k: str(v) for k, v in xenvs.items()}, 155 | ) 156 | 157 | def readerr(f): 158 | reobj = re.compile(r'bitrate=(\S+)') 159 | global av_bitrate 160 | try: 161 | while True: 162 | buf = f.read(bufsize) 163 | if len(buf) == 0: 164 | break 165 | patterns = reobj.findall(buf.decode('utf-8', 'ignore')) 166 | if len(patterns) > 0: 167 | av_bitrate = patterns[-1] 168 | # log.info(str(buf)) 169 | except Exception as e: 170 | log.exception(e) 171 | 172 | preaderr = None 173 | try: 174 | preaderr = spawn(readerr, pobj.stderr) 175 | try: 176 | while True: 177 | buf = pobj.stdout.read(bufsize) 178 | if len(buf) == 0: 179 | break 180 | # ws.send(buf) 181 | yield buf 182 | except WebSocketError: 183 | pass 184 | except Exception as e: 185 | log.exception(e) 186 | finally: 187 | with ignored(Exception): 188 | pobj.kill() 189 | preaderr.join() 190 | except Exception as e: 191 | log.exception(e) 192 | finally: 193 | log.info('exiting') 194 | with ignored(Exception): 195 | pobj.kill() 196 | with ignored(Exception): 197 | preaderr.kill() 198 | log.info('exited') 199 | return Response(generate(), mimetype='video/x-flv') 200 | 201 | 202 | if __name__ == '__main__': 203 | app.run(host=app.config['ADDRESS'], port=app.config['PORT']) 204 | -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/backend/vnc/log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | log = logging.getLogger('novnc2') 3 | -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/backend/vnc/response.py: -------------------------------------------------------------------------------- 1 | from __future__ import ( 2 | absolute_import, division, print_function, with_statement 3 | ) 4 | from functools import wraps 5 | import logging 6 | from flask import jsonify 7 | 8 | 9 | log = logging.getLogger() 10 | 11 | 12 | class PermissionDenied(Exception): 13 | pass 14 | 15 | 16 | class BadRequest(Exception): 17 | pass 18 | 19 | 20 | def httperror(f): 21 | @wraps(f) 22 | def func(*args, **kwargs): 23 | result = { 24 | 'code': 400, 25 | 'errorMessage': '', 26 | } 27 | try: 28 | return f(*args, **kwargs) 29 | except PermissionDenied as e: 30 | result['code'] = 403 31 | result['errorMessage'] = str(e) 32 | except BadRequest as e: 33 | result['code'] = 400 34 | result['errorMessage'] = str(e) 35 | except Exception as e: 36 | logging.exception(e) 37 | result['code'] = 500 38 | result['errorMessage'] = str(e) 39 | return jsonify(result) 40 | return func 41 | -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/backend/vnc/state.py: -------------------------------------------------------------------------------- 1 | from __future__ import ( 2 | absolute_import, division, print_function, with_statement 3 | ) 4 | from os import environ 5 | from gevent.event import Event 6 | from gevent import subprocess as gsp 7 | from re import search as research 8 | from .log import log 9 | 10 | 11 | class State(object): 12 | def __init__(self): 13 | self._eid = 0 14 | self._event = Event() 15 | self._w = self._h = self._health = None 16 | self.size_changed_count = 0 17 | 18 | def wait(self, eid, timeout=5): 19 | if eid < self._eid: 20 | return 21 | self._event.clear() 22 | self._event.wait(timeout) 23 | return self._eid 24 | 25 | def notify(self): 26 | self._eid += 1 27 | self._event.set() 28 | 29 | def _update_health(self): 30 | health = True 31 | output = gsp.check_output([ 32 | 'supervisorctl', '-c', '/etc/supervisor/supervisord.conf', 33 | 'status' 34 | ], encoding='UTF-8') 35 | for line in output.strip().split('\n'): 36 | if not line.startswith('web') and line.find('RUNNING') < 0: 37 | health = False 38 | break 39 | if self._health != health: 40 | self._health = health 41 | self.notify() 42 | return self._health 43 | 44 | def to_dict(self): 45 | self._update_health() 46 | 47 | state = { 48 | 'id': self._eid, 49 | 'config': { 50 | 'fixedResolution': 'RESOLUTION' in environ, 51 | 'sizeChangedCount': self.size_changed_count 52 | } 53 | } 54 | 55 | self._update_size() 56 | state.update({ 57 | 'width': self.w, 58 | 'height': self.h, 59 | }) 60 | 61 | return state 62 | 63 | def set_size(self, w, h): 64 | gsp.check_call(( 65 | 'sed -i \'s#' 66 | '^exec /usr/bin/Xvfb.*$' 67 | '#' 68 | 'exec /usr/bin/Xvfb :1 -screen 0 {}x{}x24' 69 | '#\' /usr/local/bin/xvfb.sh' 70 | ).format(w, h), shell=True) 71 | self.size_changed_count += 1 72 | 73 | def apply_and_restart(self): 74 | gsp.check_call([ 75 | 'supervisorctl', '-c', '/etc/supervisor/supervisord.conf', 76 | 'restart', 'x:' 77 | ]) 78 | self._w = self._h = self._health = None 79 | self.notify() 80 | 81 | def switch_video(self, onoff): 82 | xenvs = { 83 | 'DISPLAY': ':1', 84 | } 85 | try: 86 | cmd = 'nofb' if onoff else 'fb' 87 | gsp.check_output(['x11vnc', '-remote', cmd], env=xenvs) 88 | except gsp.CalledProcessError as e: 89 | log.warn('failed to set x11vnc fb: ' + str(e)) 90 | 91 | def _update_size(self): 92 | if self._w is not None and self._h is not None: 93 | return 94 | xenvs = { 95 | 'DISPLAY': ':1', 96 | } 97 | try: 98 | output = gsp.check_output([ 99 | 'x11vnc', '-query', 'dpy_x,dpy_y' 100 | ], env=xenvs).decode('utf-8') 101 | mobj = research(r'dpy_x:(\d+).*dpy_y:(\d+)', output) 102 | if mobj is not None: 103 | w, h = int(mobj.group(1)), int(mobj.group(2)) 104 | changed = False 105 | if self._w != w: 106 | changed = True 107 | self._w = w 108 | if self._h != h: 109 | changed = True 110 | self._h = h 111 | if changed: 112 | self.notify() 113 | except gsp.CalledProcessError as e: 114 | log.warn('failed to get dispaly size: ' + str(e)) 115 | 116 | def reset_size(self): 117 | self.size_changed_count = 0 118 | 119 | @property 120 | def w(self): 121 | return self._w 122 | 123 | @property 124 | def h(self): 125 | return self._h 126 | 127 | @property 128 | def health(self): 129 | self._update_health() 130 | return self._health 131 | 132 | 133 | state = State() 134 | -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/backend/vnc/util.py: -------------------------------------------------------------------------------- 1 | from __future__ import ( 2 | absolute_import, division, print_function, with_statement 3 | ) 4 | from contextlib import contextmanager 5 | from gevent import GreenletExit 6 | 7 | 8 | @contextmanager 9 | def ignored(*exceptions): 10 | try: 11 | yield 12 | except GreenletExit as e: 13 | raise e 14 | except exceptions: 15 | pass 16 | -------------------------------------------------------------------------------- /rootfs/usr/local/lib/web/frontend/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/037ed330804758ee020479d2c5fcf072bd9ff1fc/rootfs/usr/local/lib/web/frontend/.gitkeep -------------------------------------------------------------------------------- /rootfs/usr/local/share/doro-lxde-wallpapers/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/037ed330804758ee020479d2c5fcf072bd9ff1fc/rootfs/usr/local/share/doro-lxde-wallpapers/bg1.jpg -------------------------------------------------------------------------------- /rootfs/usr/local/share/doro-lxde-wallpapers/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/037ed330804758ee020479d2c5fcf072bd9ff1fc/rootfs/usr/local/share/doro-lxde-wallpapers/bg2.jpg -------------------------------------------------------------------------------- /rootfs/usr/local/share/doro-lxde-wallpapers/bg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/037ed330804758ee020479d2c5fcf072bd9ff1fc/rootfs/usr/local/share/doro-lxde-wallpapers/bg3.jpg -------------------------------------------------------------------------------- /rootfs/usr/local/share/doro-lxde-wallpapers/bg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/037ed330804758ee020479d2c5fcf072bd9ff1fc/rootfs/usr/local/share/doro-lxde-wallpapers/bg4.jpg -------------------------------------------------------------------------------- /rootfs/usr/local/share/doro-lxde-wallpapers/desktop-items-0.conf: -------------------------------------------------------------------------------- 1 | [*] 2 | wallpaper_mode=stretch 3 | wallpaper_common=0 4 | wallpapers_configured=4 5 | wallpaper0=/usr/local/share/doro-lxde-wallpapers/bg1.jpg 6 | wallpaper1=/usr/local/share/doro-lxde-wallpapers/bg2.jpg 7 | wallpaper2=/usr/local/share/doro-lxde-wallpapers/bg3.jpg 8 | wallpaper3=/usr/local/share/doro-lxde-wallpapers/bg4.jpg 9 | desktop_bg=#000000 10 | desktop_fg=#ffffff 11 | desktop_shadow=#000000 12 | desktop_font=Sans 12 13 | show_wm_menu=0 14 | sort=mtime;ascending;mingle; 15 | show_documents=0 16 | show_trash=0 17 | show_mounts=0 18 | 19 | -------------------------------------------------------------------------------- /run.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run \ 3 | -e http_proxy=${http_proxy} \ 4 | -e https_proxy=${https_proxy} \ 5 | -e HTTP_PROXY=${HTTP_PROXY} \ 6 | -e HTTPS_PROXY=${HTTPS_PROXY} \ 7 | -e RESOLUTION=1920x1080 \ 8 | -e PULSE_COOKIE=/tmp/pulse/cookie \ 9 | -e PULSE_SERVER=unix:/tmp/pulse/native \ 10 | -v /run/user/$(id -u)/pulse/native:/tmp/pulse/native \ 11 | -v ~/.config/pulse/cookie:/tmp/pulse/cookie:ro \ 12 | --name ai_robot_book \ 13 | -p 15900:5900 \ 14 | -p 6080:80 \ 15 | --shm-size=512m \ 16 | --privileged \ 17 | $* \ 18 | airobotbook/ros2-desktop-ai-robot-book:latest -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | docker run ^ 3 | -e http_proxy=%http_proxy% ^ 4 | -e https_proxy=%https_proxy% ^ 5 | -e HTTP_PROXY=%HTTP_PROXY% ^ 6 | -e HTTPS_PROXY=%HTTPS_PROXY% ^ 7 | -e RESOLUTION=1920x1080 ^ 8 | --name ai_robot_book ^ 9 | -p 15900:5900 ^ 10 | -p 13389:3389 ^ 11 | -p 6080:80 ^ 12 | --shm-size=512m ^ 13 | --privileged ^ 14 | %* ^ 15 | airobotbook/ros2-desktop-ai-robot-book:latest -------------------------------------------------------------------------------- /screenshots/lxde.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/037ed330804758ee020479d2c5fcf072bd9ff1fc/screenshots/lxde.png -------------------------------------------------------------------------------- /web/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /web/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /test/unit/coverage/ 6 | -------------------------------------------------------------------------------- /web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | /test/unit/coverage/ 8 | /test/e2e/reports/ 9 | selenium-debug.log 10 | 11 | # Editor directories and files 12 | .idea 13 | .vscode 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | -------------------------------------------------------------------------------- /web/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # novnc2 2 | 3 | > extended novnc 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | 20 | # run unit tests 21 | npm run unit 22 | 23 | # run e2e tests 24 | npm run e2e 25 | 26 | # run all tests 27 | npm test 28 | ``` 29 | 30 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 31 | -------------------------------------------------------------------------------- /web/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /web/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /web/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/037ed330804758ee020479d2c5fcf072bd9ff1fc/web/build/logo.png -------------------------------------------------------------------------------- /web/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /web/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }) 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/main.js' 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: '[name].js', 30 | publicPath: process.env.NODE_ENV === 'production' 31 | ? config.build.assetsPublicPath 32 | : config.dev.assetsPublicPath 33 | }, 34 | resolve: { 35 | extensions: ['.js', '.vue', '.json'], 36 | alias: { 37 | 'vue$': 'vue/dist/vue.esm.js', 38 | '@': resolve('src'), 39 | } 40 | }, 41 | module: { 42 | rules: [ 43 | ...(config.dev.useEslint ? [createLintingRule()] : []), 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue-loader', 47 | options: vueLoaderConfig 48 | }, 49 | { 50 | test: /\.js$/, 51 | loader: 'babel-loader', 52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 53 | }, 54 | { 55 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 56 | loader: 'url-loader', 57 | options: { 58 | limit: 10000, 59 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 60 | } 61 | }, 62 | { 63 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 64 | loader: 'url-loader', 65 | options: { 66 | limit: 10000, 67 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 68 | } 69 | }, 70 | { 71 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 72 | loader: 'url-loader', 73 | options: { 74 | limit: 10000, 75 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 76 | } 77 | } 78 | ] 79 | }, 80 | node: { 81 | // prevent webpack from injecting useless setImmediate polyfill because Vue 82 | // source contains it (although only uses it if it's native). 83 | setImmediate: false, 84 | // prevent webpack from injecting mocks to Node native modules 85 | // that does not make sense for the client 86 | dgram: 'empty', 87 | fs: 'empty', 88 | net: 'empty', 89 | tls: 'empty', 90 | child_process: 'empty' 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /web/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /web/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = process.env.NODE_ENV === 'testing' 15 | ? require('../config/test.env') 16 | : require('../config/prod.env') 17 | 18 | const webpackConfig = merge(baseWebpackConfig, { 19 | module: { 20 | rules: utils.styleLoaders({ 21 | sourceMap: config.build.productionSourceMap, 22 | extract: true, 23 | usePostCSS: true 24 | }) 25 | }, 26 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 30 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 31 | }, 32 | plugins: [ 33 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 34 | new webpack.DefinePlugin({ 35 | 'process.env': env 36 | }), 37 | new UglifyJsPlugin({ 38 | uglifyOptions: { 39 | compress: { 40 | warnings: false 41 | } 42 | }, 43 | sourceMap: config.build.productionSourceMap, 44 | parallel: true 45 | }), 46 | // extract css into its own file 47 | new ExtractTextPlugin({ 48 | filename: utils.assetsPath('css/[name].[contenthash].css'), 49 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 50 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 51 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 52 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 53 | allChunks: true, 54 | }), 55 | // Compress extracted CSS. We are using this plugin so that possible 56 | // duplicated CSS from different components can be deduped. 57 | new OptimizeCSSPlugin({ 58 | cssProcessorOptions: config.build.productionSourceMap 59 | ? { safe: true, map: { inline: false } } 60 | : { safe: true } 61 | }), 62 | // generate dist index.html with correct asset hash for caching. 63 | // you can customize output by editing /index.html 64 | // see https://github.com/ampedandwired/html-webpack-plugin 65 | new HtmlWebpackPlugin({ 66 | filename: process.env.NODE_ENV === 'testing' 67 | ? 'index.html' 68 | : config.build.index, 69 | template: 'index.html', 70 | inject: true, 71 | minify: { 72 | removeComments: true, 73 | collapseWhitespace: true, 74 | removeAttributeQuotes: true 75 | // more options: 76 | // https://github.com/kangax/html-minifier#options-quick-reference 77 | }, 78 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 79 | chunksSortMode: 'dependency' 80 | }), 81 | // keep module.id stable when vendor modules does not change 82 | new webpack.HashedModuleIdsPlugin(), 83 | // enable scope hoisting 84 | new webpack.optimize.ModuleConcatenationPlugin(), 85 | // split vendor js into its own file 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'vendor', 88 | minChunks (module) { 89 | // any required modules inside node_modules are extracted to vendor 90 | return ( 91 | module.resource && 92 | /\.js$/.test(module.resource) && 93 | module.resource.indexOf( 94 | path.join(__dirname, '../node_modules') 95 | ) === 0 96 | ) 97 | } 98 | }), 99 | // extract webpack runtime and module manifest to its own file in order to 100 | // prevent vendor hash from being updated whenever app bundle is updated 101 | new webpack.optimize.CommonsChunkPlugin({ 102 | name: 'manifest', 103 | minChunks: Infinity 104 | }), 105 | // This instance extracts shared chunks from code splitted chunks and bundles them 106 | // in a separate chunk, similar to the vendor chunk 107 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 108 | new webpack.optimize.CommonsChunkPlugin({ 109 | name: 'app', 110 | async: 'vendor-async', 111 | children: true, 112 | minChunks: 3 113 | }), 114 | 115 | // copy custom static assets 116 | new CopyWebpackPlugin([ 117 | { 118 | from: path.resolve(__dirname, '../static'), 119 | to: config.build.assetsSubDirectory, 120 | ignore: ['.*'] 121 | } 122 | ]) 123 | ] 124 | }) 125 | 126 | if (config.build.productionGzip) { 127 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 128 | 129 | webpackConfig.plugins.push( 130 | new CompressionWebpackPlugin({ 131 | asset: '[path].gz[query]', 132 | algorithm: 'gzip', 133 | test: new RegExp( 134 | '\\.(' + 135 | config.build.productionGzipExtensions.join('|') + 136 | ')$' 137 | ), 138 | threshold: 10240, 139 | minRatio: 0.8 140 | }) 141 | ) 142 | } 143 | 144 | if (config.build.bundleAnalyzerReport) { 145 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 146 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 147 | } 148 | 149 | module.exports = webpackConfig 150 | -------------------------------------------------------------------------------- /web/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"', 7 | PREFIX_PATH: `"${process.env.PREFIX_PATH}"` 8 | }) 9 | -------------------------------------------------------------------------------- /web/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | const BACKEND = process.env.BACKEND || 'http://127.0.0.1:6080' 7 | 8 | module.exports = { 9 | dev: { 10 | 11 | // Paths 12 | assetsSubDirectory: 'static', 13 | assetsPublicPath: './', 14 | proxyTable: { 15 | '/api': { 16 | target: BACKEND, 17 | changeOrigin: true, 18 | secure: false 19 | }, 20 | '/websockify': { 21 | target: BACKEND, 22 | // logLevel: 'debug', 23 | ws: true, 24 | changeOrigin: true 25 | } 26 | }, 27 | 28 | // Various Dev Server settings 29 | host: 'localhost', // can be overwritten by process.env.HOST 30 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 31 | autoOpenBrowser: false, 32 | errorOverlay: true, 33 | notifyOnErrors: true, 34 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 35 | 36 | // Use Eslint Loader? 37 | // If true, your code will be linted during bundling and 38 | // linting errors and warnings will be shown in the console. 39 | useEslint: true, 40 | // If true, eslint errors and warnings will also be shown in the error overlay 41 | // in the browser. 42 | showEslintErrorsInOverlay: false, 43 | 44 | /** 45 | * Source Maps 46 | */ 47 | 48 | // https://webpack.js.org/configuration/devtool/#development 49 | devtool: 'cheap-module-eval-source-map', 50 | 51 | // If you have problems debugging vue-files in devtools, 52 | // set this to false - it *may* help 53 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 54 | cacheBusting: true, 55 | 56 | cssSourceMap: true 57 | }, 58 | 59 | build: { 60 | // Template for index.html 61 | index: path.resolve(__dirname, '../dist/index.html'), 62 | 63 | // Paths 64 | assetsRoot: path.resolve(__dirname, '../dist'), 65 | assetsSubDirectory: 'static', 66 | assetsPublicPath: './', 67 | 68 | /** 69 | * Source Maps 70 | */ 71 | 72 | productionSourceMap: true, 73 | // https://webpack.js.org/configuration/devtool/#production 74 | devtool: '#source-map', 75 | 76 | // Gzip off by default as many popular static hosts such as 77 | // Surge or Netlify already gzip all static assets for you. 78 | // Before setting to `true`, make sure to: 79 | // npm install --save-dev compression-webpack-plugin 80 | productionGzip: false, 81 | productionGzipExtensions: ['js', 'css'], 82 | 83 | // Run the build command with an extra argument to 84 | // View the bundle analyzer report after build finishes: 85 | // `npm run build --report` 86 | // Set to `true` or `false` to always turn it on or off 87 | bundleAnalyzerReport: process.env.npm_config_report 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /web/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"', 4 | PREFIX_PATH: `"${process.env.PREFIX_PATH}"` 5 | } 6 | -------------------------------------------------------------------------------- /web/config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | novnc2 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/novnc-armhf-1.patch: -------------------------------------------------------------------------------- 1 | --- core/rfb.js 2019-02-24 21:17:04.402944311 +0800 2 | +++ core/rfb.js 2019-02-24 21:17:34.782484107 +0800 3 | @@ -1256,8 +1256,8 @@ 4 | encs.push(encodings.encodingCopyRect); 5 | // Only supported with full depth support 6 | if (this._fb_depth == 24) { 7 | - encs.push(encodings.encodingTight); 8 | - encs.push(encodings.encodingTightPNG); 9 | + // encs.push(encodings.encodingTight); 10 | + // encs.push(encodings.encodingTightPNG); 11 | encs.push(encodings.encodingHextile); 12 | encs.push(encodings.encodingRRE); 13 | } 14 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "novnc2", 3 | "version": "1.0.0", 4 | "description": "extended novnc", 5 | "author": "Doro Wu ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "e2e": "node test/e2e/runner.js", 11 | "test": "npm run unit && npm run e2e", 12 | "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", 13 | "build": "node build/build.js" 14 | }, 15 | "dependencies": { 16 | "axios": "^0.21.1", 17 | "vue": "^2.5.2", 18 | "vue-material": "^1.0.0-beta-10.2", 19 | "vue-router": "^3.0.1" 20 | }, 21 | "devDependencies": { 22 | "autoprefixer": "^7.1.2", 23 | "babel-core": "^6.22.1", 24 | "babel-eslint": "^8.2.1", 25 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 26 | "babel-loader": "^7.1.1", 27 | "babel-plugin-syntax-jsx": "^6.18.0", 28 | "babel-plugin-transform-runtime": "^6.22.0", 29 | "babel-plugin-transform-vue-jsx": "^3.5.0", 30 | "babel-preset-env": "^1.3.2", 31 | "babel-preset-stage-2": "^6.22.0", 32 | "babel-register": "^6.22.0", 33 | "chalk": "^2.0.1", 34 | "chromedriver": "^2.27.2", 35 | "copy-webpack-plugin": "^4.0.1", 36 | "cross-spawn": "^5.0.1", 37 | "css-loader": "^0.28.0", 38 | "eslint": "^4.15.0", 39 | "eslint-config-standard": "^10.2.1", 40 | "eslint-friendly-formatter": "^3.0.0", 41 | "eslint-loader": "^1.7.1", 42 | "eslint-plugin-import": "^2.7.0", 43 | "eslint-plugin-node": "^5.2.0", 44 | "eslint-plugin-promise": "^3.4.0", 45 | "eslint-plugin-standard": "^3.0.1", 46 | "eslint-plugin-vue": "^4.0.0", 47 | "extract-text-webpack-plugin": "^3.0.0", 48 | "file-loader": "^1.1.4", 49 | "friendly-errors-webpack-plugin": "^1.6.1", 50 | "html-webpack-plugin": "^2.30.1", 51 | "nightwatch": "^0.9.12", 52 | "node-notifier": "^8.0.1", 53 | "optimize-css-assets-webpack-plugin": "^3.2.0", 54 | "ora": "^1.2.0", 55 | "portfinder": "^1.0.13", 56 | "postcss-import": "^11.0.0", 57 | "postcss-loader": "^2.0.8", 58 | "postcss-url": "^7.2.1", 59 | "rimraf": "^2.6.0", 60 | "selenium-server": "^3.0.1", 61 | "semver": "^5.3.0", 62 | "shelljs": "^0.7.6", 63 | "uglifyjs-webpack-plugin": "^1.1.1", 64 | "url-loader": "^0.5.8", 65 | "vue-loader": "^13.3.0", 66 | "vue-style-loader": "^3.0.1", 67 | "vue-template-compiler": "^2.5.2", 68 | "webpack": "^3.6.0", 69 | "webpack-bundle-analyzer": "^2.9.0", 70 | "webpack-cli": "^3.3.7", 71 | "webpack-dev-server": "^3.2.1", 72 | "webpack-merge": "^4.1.0" 73 | }, 74 | "engines": { 75 | "node": ">= 8.0.0", 76 | "npm": ">= 3.0.0" 77 | }, 78 | "browserslist": [ 79 | "> 1%", 80 | "last 2 versions", 81 | "not ie <= 8" 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /web/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /web/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/037ed330804758ee020479d2c5fcf072bd9ff1fc/web/src/assets/.gitkeep -------------------------------------------------------------------------------- /web/src/components/Vnc.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 199 | 200 | 218 | -------------------------------------------------------------------------------- /web/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import axios from 'axios' 7 | 8 | Vue.config.productionTip = false 9 | Vue.prototype.$http = axios 10 | 11 | /* eslint-disable no-new */ 12 | new Vue({ 13 | el: '#app', 14 | router, 15 | components: { App }, 16 | template: '' 17 | }) 18 | -------------------------------------------------------------------------------- /web/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Vnc from '@/components/Vnc' 4 | 5 | Vue.use(Router) 6 | 7 | export default new Router({ 8 | mode: 'history', 9 | base: window.location.pathname, 10 | routes: [ 11 | { 12 | path: '/', 13 | name: 'Vnc', 14 | component: Vnc 15 | } 16 | ] 17 | }) 18 | -------------------------------------------------------------------------------- /web/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AI-Robot-Book/docker-ros2-desktop-ai-robot-book/037ed330804758ee020479d2c5fcf072bd9ff1fc/web/static/.gitkeep -------------------------------------------------------------------------------- /web/static/video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | video 5 | 32 | 33 | 34 |
35 | 38 |
39 |
40 | 41 | 42 | 43 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /web/static/vnc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | noVNC 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 63 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
79 |
80 |
noVNC encountered an error:
81 |
82 |
83 |
84 |
85 | 86 | 87 |
88 | 89 |
90 |
91 | 92 |
93 | 94 |

no
VNC

95 | 96 | 97 | 100 | 101 | 102 |
103 | 106 | 109 | 112 | 115 | 117 |
118 | 119 | 120 |
121 | 124 |
125 |
126 | 129 | 132 | 135 | 138 | 141 | 144 |
145 |
146 |
147 | 148 | 149 | 152 |
153 |
154 |
155 | Power 156 |
157 | 158 | 159 | 160 |
161 |
162 | 163 | 164 | 167 |
168 |
169 |
170 | Clipboard 171 |
172 | 173 |
174 | 176 |
177 |
178 | 179 | 180 | 183 | 184 | 185 | 188 |
189 |
190 |
    191 |
  • 192 | Settings 193 |
  • 194 |
  • 195 | 196 |
  • 197 |
  • 198 | 199 |
  • 200 |

  • 201 |
  • 202 | 203 |
  • 204 |
  • 205 | 206 | 211 |
  • 212 |

  • 213 |
  • 214 |
    Advanced
    215 |
      216 |
    • 217 | 218 | 219 |
    • 220 |
    • 221 |
      WebSocket
      222 |
        223 |
      • 224 | 225 |
      • 226 |
      • 227 | 228 | 229 |
      • 230 |
      • 231 | 232 | 233 |
      • 234 |
      • 235 | 236 | 237 |
      • 238 |
      239 |
    • 240 |

    • 241 |
    • 242 | 243 |
    • 244 |
    • 245 | 246 | 247 |
    • 248 |

    • 249 |
    • 250 | 251 |
    • 252 |

    • 253 | 254 |
    • 255 | 259 |
    • 260 |
    261 |
  • 262 |
263 |
264 |
265 | 266 | 267 | 270 | 271 |
272 |
273 | 274 |
275 | 276 |
277 | 278 | 279 |
280 | 281 | 282 |
283 |
284 | 285 |
286 | Connect 287 |
288 |
289 |
290 | 291 | 292 |
293 |
294 |
    295 |
  • 296 | 297 | 298 |
  • 299 |
  • 300 | 301 |
  • 302 |
303 |
304 |
305 | 306 | 307 |
308 |
309 |
310 | 311 |
312 |
313 |
314 | 315 | 316 |
317 | 321 | 323 |
324 | 325 | 329 | 349 | 350 | 351 | -------------------------------------------------------------------------------- /web/test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // The assertion name is the filename. 3 | // Example usage: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // For more information on custom assertions see: 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | 10 | exports.assertion = function (selector, count) { 11 | this.message = 'Testing if element <' + selector + '> has count: ' + count 12 | this.expected = count 13 | this.pass = function (val) { 14 | return val === this.expected 15 | } 16 | this.value = function (res) { 17 | return res.value 18 | } 19 | this.command = function (cb) { 20 | var self = this 21 | return this.api.execute(function (selector) { 22 | return document.querySelectorAll(selector).length 23 | }, [selector], function (res) { 24 | cb.call(self, res) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /web/test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /web/test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | 4 | const webpack = require('webpack') 5 | const DevServer = require('webpack-dev-server') 6 | 7 | const webpackConfig = require('../../build/webpack.prod.conf') 8 | const devConfigPromise = require('../../build/webpack.dev.conf') 9 | 10 | let server 11 | 12 | devConfigPromise.then(devConfig => { 13 | const devServerOptions = devConfig.devServer 14 | const compiler = webpack(webpackConfig) 15 | server = new DevServer(compiler, devServerOptions) 16 | const port = devServerOptions.port 17 | const host = devServerOptions.host 18 | return server.listen(port, host) 19 | }) 20 | .then(() => { 21 | // 2. run the nightwatch test suite against it 22 | // to run in additional browsers: 23 | // 1. add an entry in test/e2e/nightwatch.conf.js under "test_settings" 24 | // 2. add it to the --env flag below 25 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 26 | // For more information on Nightwatch's config file, see 27 | // http://nightwatchjs.org/guide#settings-file 28 | let opts = process.argv.slice(2) 29 | if (opts.indexOf('--config') === -1) { 30 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 31 | } 32 | if (opts.indexOf('--env') === -1) { 33 | opts = opts.concat(['--env', 'chrome']) 34 | } 35 | 36 | const spawn = require('cross-spawn') 37 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 38 | 39 | runner.on('exit', function (code) { 40 | server.close() 41 | process.exit(code) 42 | }) 43 | 44 | runner.on('error', function (err) { 45 | server.close() 46 | throw err 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /web/test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web/test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | }, 4 | "globals": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /web/test/unit/specs/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import HelloWorld from '@/components/HelloWorld' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(HelloWorld) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /xrdp/pulse/default.pa: -------------------------------------------------------------------------------- 1 | ### Load audio drivers statically 2 | ### (it's probably better to not load these drivers manually, but instead 3 | ### use module-udev-detect -- see below -- for doing this automatically) 4 | # load-module module-alsa-sink 5 | # load-module module-alsa-source device=hw:1,0 6 | # load-module module-oss device="/dev/dsp" sink_name=output source_name=input 7 | # load-module module-oss-mmap device="/dev/dsp" sink_name=output source_name=input 8 | # load-module module-null-sink 9 | # load-module module-pipe-sink 10 | 11 | 12 | .nofail 13 | .fail 14 | load-module module-augment-properties 15 | load-module module-always-sink 16 | .ifexists module-xrdp-sink.so 17 | load-module module-xrdp-sink 18 | .endif 19 | .ifexists module-xrdp-source.so 20 | load-module module-xrdp-source 21 | .endif 22 | .ifexists /var/lib/xrdp-pulseaudio-installer/module-xrdp-sink.so 23 | load-module /var/lib/xrdp-pulseaudio-installer/module-xrdp-sink.so 24 | .endif 25 | .ifexists /var/lib/xrdp-pulseaudio-installer/module-xrdp-source.so 26 | load-module /var/lib/xrdp-pulseaudio-installer/module-xrdp-source.so 27 | .endif 28 | load-module module-native-protocol-unix 29 | --------------------------------------------------------------------------------