基于Jetbrains开源的projector搭建远程linux开发环境

1,668 阅读7分钟

Projector

总所周知,vscode这款轻量编辑器最吸引人的地方就是有一个remote开发的插件,通过这个remote的插件可以连接上远程的linux主机,比如阿里云或者腾讯云的轻量应用服务,然后构建一个比较通用的开发环境,但是vscode这款编辑器太轻量级了,导致我个人使用感受来说,代码提示和高亮的插件会比较慢,而且配置起来不是很方便,比如cpp的开发配置,需要配置几个json文件放在当前目录的.vscode下面才能进行正确的语法提示,通常需要指定gcc或者clang的stl或者是其他的库文件的路径,才能#include进来进行相对提示。而Java想在vscode上开发,是一件更难的事情,毕竟jetbrains的idea才是王道。Projector的地址为lp.jetbrains.com/projector/

在linux中搭建Projector

  1. 找到对应的projector源码,github.com/JetBrains/p… 在projector的源码的md中可以看到jetbrains提供了官方编译好的镜像文件供大家使用

image.png

本文不使用jetbrains官方提供的镜像,因为这些镜像使用的是debian作为base-image的,并且所使用的的idea版本也是相对老旧,是2020的版本,本文构建基于centos8和idea-2021.3的projector远程开发镜像

  1. 在提供镜像的下方有一个如何自定义镜像的一个说明

image.png 我们主要看这3个bash文件,这3个bash的作用在下方注释

# clone-projector-core.sh  主要是把远程server的代码拉到本地,用于后续的镜像构建
set -e # Any command which returns non-zero exit code will cause this shell script to exit immediately
set -x # Activate debugging to show execution details: all commands will be printed before execution

git clone https://github.com/JetBrains/projector-server.git ../projector-server
# build-container.sh 从jebtrains官网的下载链接中下载idea的压缩文件,然后使用docker构建,核心在Dockerfile
containerName=${1:-projector-idea-c}
downloadUrl=${2:-https://download.jetbrains.com/idea/ideaIC-2019.3.5.tar.gz}
# build container:
DOCKER_BUILDKIT=1 docker build --progress=plain -t "$containerName" --build-arg buildGradle=true --build-arg "downloadUrl=$downloadUrl" -f Dockerfile ..
# run-container.sh 这个就是简单run一个docker镜像,没什么好说的
set -e # Any command which returns non-zero exit code will cause this shell script to exit immediately
set -x # Activate debugging to show execution details: all commands will be printed before execution
containerName=${1:-projector-idea-c}
docker run --rm -p 8887:8887 -it "$containerName"

  1. 核心文件dockerfile(含注释)
FROM debian AS ideDownloader #使用debian作为下载idea的tar包的基础镜像,以下命令就是在下载idea的tar

# prepare tools:
RUN apt-get update
RUN apt-get install wget -y
# download IDE to the /ide dir:
WORKDIR /download
ARG downloadUrl
RUN wget -q $downloadUrl -O - | tar -xz
RUN find . -maxdepth 1 -type d -name * -execdir mv {} /ide \;

FROM amazoncorretto:11 as projectorGradleBuilder #使用amazoncorretto作为prjector-core的编译镜像,然后使用gradel编译projector-server

ENV PROJECTOR_DIR /projector

# projector-server:
ADD projector-server $PROJECTOR_DIR/projector-server
WORKDIR $PROJECTOR_DIR/projector-server
ARG buildGradle
RUN if [ "$buildGradle" = "true" ]; then ./gradlew clean; else echo "Skipping gradle build"; fi
RUN if [ "$buildGradle" = "true" ]; then ./gradlew :projector-server:distZip; else echo "Skipping gradle build"; fi
RUN cd projector-server/build/distributions && find . -maxdepth 1 -type f -name projector-server-*.zip -exec mv {} projector-server.zip \;

FROM debian AS projectorStaticFiles #执行了一些文件的处理操作和idea启动相关

# prepare tools:
RUN apt-get update
RUN apt-get install unzip -y
# create the Projector dir:
ENV PROJECTOR_DIR /projector
RUN mkdir -p $PROJECTOR_DIR
# copy IDE:
COPY --from=ideDownloader /ide $PROJECTOR_DIR/ide
# copy projector files to the container:
ADD projector-docker/static $PROJECTOR_DIR
# copy projector:
COPY --from=projectorGradleBuilder $PROJECTOR_DIR/projector-server/projector-server/build/distributions/projector-server.zip $PROJECTOR_DIR
# prepare IDE - apply projector-server:
RUN unzip $PROJECTOR_DIR/projector-server.zip
RUN rm $PROJECTOR_DIR/projector-server.zip
RUN find . -maxdepth 1 -type d -name projector-server-* -exec mv {} projector-server \;
RUN mv projector-server $PROJECTOR_DIR/ide/projector-server
RUN mv $PROJECTOR_DIR/ide-projector-launcher.sh $PROJECTOR_DIR/ide/bin
RUN chmod 644 $PROJECTOR_DIR/ide/projector-server/lib/*

FROM debian:10 #构建了idea的运行环境,基于debian的环境,然后安装了awt需要的各种so动态库环境,然后把8887端口暴露出去,启动docker镜像是执行run.sh文件

RUN true \
# Any command which returns non-zero exit code will cause this shell script to exit immediately:
   && set -e \
# Activate debugging to show execution details: all commands will be printed before execution
   && set -x \
# install packages:
    && apt-get update \
# packages for awt:
    && apt-get install libxext6 libxrender1 libxtst6 libxi6 libfreetype6 -y \
# packages for user convenience:
    && apt-get install git bash-completion sudo -y \
# packages for IDEA (to disable warnings):
    && apt-get install procps -y \
# clean apt to reduce image size:
    && rm -rf /var/lib/apt/lists/* \
    && rm -rf /var/cache/apt

ARG downloadUrl

RUN true \
# Any command which returns non-zero exit code will cause this shell script to exit immediately:
    && set -e \
# Activate debugging to show execution details: all commands will be printed before execution
    && set -x \
# install specific packages for IDEs:
    && apt-get update \
    && if [ "${downloadUrl#*CLion}" != "$downloadUrl" ]; then apt-get install build-essential clang -y; else echo "Not CLion"; fi \
    && if [ "${downloadUrl#*pycharm}" != "$downloadUrl" ]; then apt-get install python2 python3 python3-distutils python3-pip python3-setuptools -y; else echo "Not pycharm"; fi \
    && if [ "${downloadUrl#*rider}" != "$downloadUrl" ]; then apt install apt-transport-https dirmngr gnupg ca-certificates -y && apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && echo "deb https://download.mono-project.com/repo/debian stable-buster main" | tee /etc/apt/sources.list.d/mono-official-stable.list && apt update && apt install mono-devel -y && apt install wget -y && wget https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && dpkg -i packages-microsoft-prod.deb && rm packages-microsoft-prod.deb && apt-get update && apt-get install -y apt-transport-https && apt-get update && apt-get install -y dotnet-sdk-3.1 aspnetcore-runtime-3.1; else echo "Not rider"; fi \
# clean apt to reduce image size:
    && rm -rf /var/lib/apt/lists/* \
    && rm -rf /var/cache/apt

# copy the Projector dir:
ENV PROJECTOR_DIR /projector
COPY --from=projectorStaticFiles $PROJECTOR_DIR $PROJECTOR_DIR

ENV PROJECTOR_USER_NAME projector-user

RUN true \
# Any command which returns non-zero exit code will cause this shell script to exit immediately:
    && set -e \
# Activate debugging to show execution details: all commands will be printed before execution
    && set -x \
# move run scipt:
    && mv $PROJECTOR_DIR/run.sh run.sh \
# change user to non-root (http://pjdietz.com/2016/08/28/nginx-in-docker-without-root.html):
    && mv $PROJECTOR_DIR/$PROJECTOR_USER_NAME /home \
    && useradd -d /home/$PROJECTOR_USER_NAME -s /bin/bash -G sudo $PROJECTOR_USER_NAME \
    && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \
    && chown -R $PROJECTOR_USER_NAME.$PROJECTOR_USER_NAME /home/$PROJECTOR_USER_NAME \
    && chown -R $PROJECTOR_USER_NAME.$PROJECTOR_USER_NAME $PROJECTOR_DIR/ide/bin \
    && chown $PROJECTOR_USER_NAME.$PROJECTOR_USER_NAME run.sh

USER $PROJECTOR_USER_NAME
ENV HOME /home/$PROJECTOR_USER_NAME

EXPOSE 8887

CMD ["bash", "-c", "/run.sh"]

  1. 看完了上面的这4个文件,其实想要构建centos8 + idea最新版本已经很明了了,在build_container.sh里修改最新的idea下载链接或者是jetbrains其他全家桶,连接如下

image.png 注意选用linux版本 接着需要修稿dockerfile里面的baseimage和其中的一些环境搭建的命令。具体文件如下

# build_container.sh 修改过后的sh文件
set -e # Any command which returns non-zero exit code will cause this shell script to exit immediately
set -x # Activate debugging to show execution details: all commands will be printed before execution
containerName=${1:-projector-idea-u}
downloadUrl=${2:-https://download.jetbrains.com/idea/ideaIU-2021.2.3.tar.gz}

# build container:
DOCKER_BUILDKIT=1 docker build --progress=plain -t "$containerName" --build-arg buildGradle=true --build-arg "downloadUrl=$downloadUrl" -f Dockerfile ..
FROM debian AS ideDownloader

# prepare tools:
RUN apt-get update
RUN apt-get install wget -y
# download IDE to the /ide dir:
WORKDIR /download
ARG downloadUrl
RUN wget -q $downloadUrl -O - | tar -xz
RUN find . -maxdepth 1 -type d -name * -execdir mv {} /ide \;

FROM amazoncorretto:11 as projectorGradleBuilder

ENV PROJECTOR_DIR /projector

# projector-server:
ADD ../projector-server $PROJECTOR_DIR/projector-server
WORKDIR $PROJECTOR_DIR/projector-server
ARG buildGradle
RUN if [ "$buildGradle" = "true" ]; then ./gradlew clean; else echo "Skipping gradle build"; fi
RUN if [ "$buildGradle" = "true" ]; then ./gradlew :projector-server:distZip; else echo "Skipping gradle build"; fi
RUN cd projector-server/build/distributions && find . -maxdepth 1 -type f -name projector-server-*.zip -exec mv {} projector-server.zip \;

FROM debian AS projectorStaticFiles

# prepare tools:
RUN apt-get update
RUN apt-get install unzip -y
# create the Projector dir:
ENV PROJECTOR_DIR /projector
RUN mkdir -p $PROJECTOR_DIR
# copy IDE:
COPY --from=ideDownloader /ide $PROJECTOR_DIR/ide
# copy projector files to the container:
ADD projector-docker/static $PROJECTOR_DIR
# copy projector:
COPY --from=projectorGradleBuilder $PROJECTOR_DIR/projector-server/projector-server/build/distributions/projector-server.zip $PROJECTOR_DIR
# prepare IDE - apply projector-server:
RUN unzip $PROJECTOR_DIR/projector-server.zip
RUN rm $PROJECTOR_DIR/projector-server.zip
RUN find . -maxdepth 1 -type d -name projector-server-* -exec mv {} projector-server \;
RUN mv projector-server $PROJECTOR_DIR/ide/projector-server
RUN mv $PROJECTOR_DIR/ide-projector-launcher.sh $PROJECTOR_DIR/ide/bin
RUN chmod 644 $PROJECTOR_DIR/ide/projector-server/lib/*

FROM centos:centos8.1.1911 #这里是重点

RUN true \
# Any command which returns non-zero exit code will cause this shell script to exit immediately:
   && set -e \
# Activate debugging to show execution details: all commands will be printed before execution
   && set -x \
# install packages:
    && yum update -y \
# packages for awt:
    # && yum install libxext6 libxrender1 libxtst6 libxi6 libfreetype6 -y \
    && yum install libXtst.i686 libXtst libXext.x86_64 libXrender.x86_64 libXtst.x86_64 freetype -y \
# packages for user convenience:
    && yum install git bash-completion -y \
# packages for IDEA (to disable warnings):
    && yum install procps -y 
# clean apt to reduce image size:
    # && rm -rf /var/lib/apt/lists/* \
    # && rm -rf /var/cache/apt

ARG downloadUrl

RUN true \
# Any command which returns non-zero exit code will cause this shell script to exit immediately:
    && set -e \
# Activate debugging to show execution details: all commands will be printed before execution
    && set -x \
# install specific packages for IDEs:
    && yum update \
    && if [ "${downloadUrl#*CLion}" != "$downloadUrl" ]; then yum install build-essential clang -y; else echo "Not CLion"; fi \
    && if [ "${downloadUrl#*pycharm}" != "$downloadUrl" ]; then yum install python2 python3 python3-distutils python3-pip python3-setuptools -y; else echo "Not pycharm"; fi 
# clean apt to reduce image size:
    # && rm -rf /var/lib/apt/lists/* \
    # && rm -rf /var/cache/apt

# copy the Projector dir:
ENV PROJECTOR_DIR /projector
COPY --from=projectorStaticFiles $PROJECTOR_DIR $PROJECTOR_DIR

ENV PROJECTOR_USER_NAME projector-user

RUN true \
# Any command which returns non-zero exit code will cause this shell script to exit immediately:
    && set -e \
# Activate debugging to show execution details: all commands will be printed before execution
    && set -x \
# move run scipt:
    && mv $PROJECTOR_DIR/run.sh run.sh \
# change user to non-root (http://pjdietz.com/2016/08/28/nginx-in-docker-without-root.html):
    && mv $PROJECTOR_DIR/$PROJECTOR_USER_NAME /home \
    && useradd -m -d /home/$PROJECTOR_USER_NAME -s /bin/bash $PROJECTOR_USER_NAME \
    && chown -R $PROJECTOR_USER_NAME.$PROJECTOR_USER_NAME /home/$PROJECTOR_USER_NAME \
    && chown -R $PROJECTOR_USER_NAME.$PROJECTOR_USER_NAME $PROJECTOR_DIR/ide/bin \
    && chown $PROJECTOR_USER_NAME.$PROJECTOR_USER_NAME run.sh

USER $PROJECTOR_USER_NAME
ENV HOME /home/$PROJECTOR_USER_NAME

EXPOSE 8887

CMD ["bash", "-c", "/run.sh"]
  1. 然后执行官方md的3个指令
./clone-projector-core.sh
./build-container.sh
./run-container.sh

然后容器就成功启动了!通过vscode的端口转发设置一下,然后访问127.0.0.1:8887就可以访问到远程的idea了,这个idea的使用体验取决于带宽和延迟!

基于上述镜像的二次开发

上面镜像构建了一个没有任何java环境和编译环境的idea远程镜像,现在基于上面构造的镜像构建一个java8和bazel的开发环境,dockefile如下

FROM hkzhao123/centos-idea

user root

#复制本地的ssh秘钥,方便拉git
run mkdir -p /root/.ssh
copy .ssh/* /root/.ssh/

#指定bazel的安装路径在/usr/bin下
env HOME=/usr
env JAVA_HOME=/usr/java/jdk1.8.0_311-i586
env JRE_HOME=/usr/java/jdk1.8.0_311-i586/jre
#指定projector的默认目录为/home/projector-user
WORKDIR /home/projector-user

#修改yum的配置 让Yum不报错
run rm -f /var/lib/rpm/__db.*
run rpm --rebuilddb

#安装需要的依赖
run yum -y install wget
run yum -y install gcc
run wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/centos8_base.repo
run yum clean all
run yum makecache

#安装oraclejdk
copy ./jdk-8u311-linux-i586.rpm /home/projector-user/
run yum localinstall -y jdk-8u311-linux-i586.rpm
run yum -y install vim 
run sed  -e '1i\\JAVA_HOME=/usr/java/jdk1.8.0_311-i586\nJRE_HOME=/usr/java/jdk1.8.0_311-i586/jre\nPATH=$PATH:$JAVA_HOME/bin\nCLASSPATH=.:$JAVA_HOME/lib\nHOME=/usr\nexport JAVA_HOME CLASSPATH HOME' /etc/profile | tee /etc/profile > /dev/null
#source在dockerfile里没啥用,可以进去容器source一遍或者加在docker的env上
run source /etc/profile 

#也是起到一个source的作用
run sed '$a\\source /etc/profile' /root/.bashrc | tee /root/.bashrc > /dev/null
run source /root/.bashrc 

#安装bazel 由于设置了env 所以默认安装在/usr/bin下
run yum install -y unzip
run wget https://github.com/bazelbuild/bazel/releases/download/4.2.0/bazel-4.2.0-installer-linux-x86_64.sh
run chmod +x bazel-4.2.0-installer-linux-x86_64.sh
run ./bazel-4.2.0-installer-linux-x86_64.sh --user
run rm bazel-4.2.0-installer-linux-x86_64.sh
run bazel --version

#安装git 和 git-lfs lfs大文件专用
run yum install -y git
run curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.rpm.sh |  bash
run yum install git-lfs -y
run git lfs install
run rm -rf *.sh
run git config --global user.name "hk"
run git config --global user.email "hk@tc.com"

上线的dockerfile需要下面的1个文件 jdk-8u311-linux-i586.rpm 这个可以在oracle的官方网站下载,是rpm文件就可以了,将这个rpm文件放在dockefile的同级路径下,最后可以执行

docker build  -t hk/idea:v1 .

使用nginx代理这个projector-server的端口

由于在使用途中,projector-server存在以下问题,在http的情况下,ctrl+c\ctrl+v是存在Bug的,这对我们程序员来说是不能接受的问题。因此在projector的issue中发现,使用https能够解决这个问题,所以下文使用nginx构造一个假证书然后使用https连接projector-server来避免http的这个问题。

yum update -y
yum install nginx -y
openssl genrsa -des3 -out server.pass.key 2048 
openssl rsa -in server.pass.key -out server.key
openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=Shenzhen/L=Shenzhen/O=cetc/OU=cetc/CN=hkz.cetc.cn"
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
rm -rf /etc/nginx/nginx.conf.default
cp server.crt /etc/nginx/server.crt
cp server.csr /etc/nginx/server.csr
cp server.key /etc/nginx/server.key
systemctl start nginx
nginx -t

修改Nginx.conf文件

rm -rf /etc/nginx/nginx.conf

下面是一条命令
echo "worker_processes  1;

events {
    worker_connections  1024;
}


http {
    # include       mime.types;
    # default_type  application/json;

    sendfile        on;
    keepalive_timeout  300;

    gzip  on;

    server {
        listen       443 ssl;
        keepalive_timeout   300;
        server_name  localhost;
    
        location /idea/ {
            proxy_pass http://127.0.0.1:8887/;
            client_max_body_size 10m; #表示最大上传10M,需要多大设置多大。
            #设置主机头和客户端真实地址,以便服务器获取客户端真实IP
            proxy_http_version 1.1; 
            proxy_set_header Host \$host;
            proxy_set_header X-Real-IP \$remote_addr;
            proxy_set_header Upgrade \$http_upgrade;
            proxy_set_header Connection "Upgrade";  
            proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
            # proxy_set_header X-Forwarded-Scheme \$scheme;
        }
        ssl_certificate      server.crt;
        ssl_certificate_key  server.key;
        # #减少点击劫持
        add_header          X-Frame-Options DENY;
        #禁止服务器自动解析资源类型
        add_header          X-Content-Type-Options nosniff;
        # 防XSS攻击
        add_header          X-Xss-Protection 1;
        #优先采取服务器算法
        ssl_prefer_server_ciphers on;
        #
        ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers         HIGH:!aNULL:!MD5;
        ssl_session_cache   shared:SSL:10m;
        ssl_session_timeout 10m;
    }

}" > /etc/nginx/nginx.conf

nginx -t
nginx -s reload

然后启动docker镜像,下面使用设置环境变量的启动方式,可以加一个token防止其他人连到自己的idea里

docker run --rm -p 8887:8887 --env ORG_JETBRAINS_PROJECTOR_SERVER_HANDSHAKE_TOKEN=hk --env ORG_JETBRAINS_PROJECTOR_SERVER_RO_HANDSHAKE_TOKEN=hk -v /home/hk/root:/root -v /home/hk/home:/home -it -d hk/idea:v1

然后在chrome浏览器中访问https://127.0.0.1/idea?token=hk, 如果是mac电脑需要采用以下连接的方式信任这个假证书,然后就可以使用projector-launch客户端去连接,不再需要用chrome

以上就是全文了