基于gogs及code-server建立项目基于浏览器IDE的在线开发

环境及工具

gogs
code-server:基于vscode的在线浏览器版
以下涉及的域名yourdomain.com皆事先解析到某个服务器。

编辑gogs

增加webide入口

templates\repo\header.tmpl中添加,大概位于第84行

1
2
3
<a class="item" target="_blank" href="https://webide.yourdomain.com/?user={{.LoggedUser.Name}}&repo={{.Owner.Name}}/{{.Repository.Name}}">
<i class="octicon octicon-book"></i> WEB IDE
</a>

展示位置
图片1

重新编译gogs

1
2
git clone https://github.com/gogs/gogs.git
cd gogs

重复编译未知原因出现go-bindata不存在的问题。
通过更改Dockerfile,在第3行下增加

1
2
RUN go get -u github.com/jteeuwen/go-bindata/... ; \
export PATH=$PATH:$GOPATH/bin

来解决。
使用docker运行命令进行构建
1
docker build -t karoy/gogs:latest .

运行gogs

这里使用了traefik作为前端访问端,更多的请参考本博客其他文章。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mkdir -p /data/gogs/

docker volume create --driver local \
--opt device=/data/gogs/ \
--opt o=bind \
--opt type=none \
gogs_data

docker service create \
--name="gogs" \
--mount type=volume,source=gogs_data,destination=/data \
--network traefiknet \
--container-label traefik.backend="gogs" \
--container-label traefik.frontend.entryPoints="http,https" \
--container-label traefik.frontend.rule="Host: git.yourdomain.com" \
--container-label traefik.port='9000' \
--container-label traefik.protocol='http' \
--replicas 1 \
karoy/gogs:latest

traefiknet为自定义的docker网络

实现及部署中转应用

实现应用核心代码

该应用主要在于自动创建docker容器及自动鉴权访问。使用php写的index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?php

$action = $_GET['action']?:'default';
$id = substr(md5($_GET['user'].'-'.str_ireplace("/", "_", $_GET['repo'])), 5,10);
$workspace = "/data/workspace/".$id;
$workspace_cf = $workspace."/.qws";

switch($action){
case 'default':{
$domain = $id.'.webide.yourdomain.com';
ob_start();
system('curl -I -m 10 -o /dev/null -s -w %{http_code} '.$domain);
$code = ob_get_contents();
ob_end_clean();
echo '
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<p style="margin:0 auto;width:500px;height:100%;text-align:center">
<a style="margin-top: 50%;display: block;" href="//'.$domain.'">等待自动跳转,若初次创建应用,请等待长一点。</a>
</p>
<script>
var t1 = "";
var f = function(){
var s=$.ajax({url:"/?action=getStatus&user='.$_GET['user'].'&repo='.$_GET['repo'].'",async:false});
console.log(s.responseText);
if(s.responseText!="fail"&&s.responseText!=""){
window.clearInterval(t1);
$.removeCookie("password");
$.cookie("password",s.responseText,{
expires:7,
path:"/",
domain:"webide.yourdomain.com",
secure:true
})
location.href="//'.$domain.'"
}
}
t1 = window.setInterval(f,1000);

</script>
';
if($code!=302){
ob_start();
mkdir($workspace,0777,true);
mkdir($workspace_cf,0777,true);
$create_service = 'docker service create \
--name '.$id.' \
--mount type=bind,source='.$workspace.',target=/root/project \
--network traefiknet \
--container-label traefik.backend="'.$id.'-ide" \
--container-label traefik.frontend.entryPoints="http,https" \
--container-label traefik.frontend.rule="Host: '.$domain.'" \
--container-label traefik.port="8443" \
--container-label traefik.protocol="http" \
--container-label traefik.frontend.redirect.entryPoint="https" \
--replicas 1 \
karoy/code-server:latest \
code-server \
--allow-http 2>&1';

file_put_contents("${workspace_cf}/create.sh",$create_service);
pclose(popen("/bin/sh -e ${workspace_cf}/create.sh &",'r'));

ob_end_clean();
}
break;

}
case "getStatus":{
ob_start();
$cmd = 'docker ps -a --format "table {{.ID}} {{.Names}}"|awk \'{print $2}\'|grep "'.$id.'"|awk \'{print $1}\'';
system($cmd);
$cantainerid = str_replace("\n","",ob_get_contents());
ob_end_clean();
if(file_exists("${workspace_cf}/${cantainerid}.ps")){
$password = file_get_contents("${workspace_cf}/${cantainerid}.ps");
if(strlen($password)<10){
unlink("${workspace_cf}/${cantainerid}.ps");
}else{
echo str_replace("\n","",$password);
}
}else{
sleep(2);
if(strlen($cantainerid)>10){
$cmd = "docker logs ${cantainerid} | grep Password | awk '{print $3}' 2>&1 | tee ${workspace_cf}/${cantainerid}.ps";
file_put_contents("${workspace_cf}/getpass.sh",$cmd);
pclose(popen("/bin/sh -e ${workspace_cf}/getpass.sh &",'r'));
}else{
echo "fail";
}
}
}
}

部署中转应用

将其部署到服务器,这里使用的自建的docker服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
mkdir -p /data/dev/webide

##自定义的seesion目录,你也可以不用,使用默认的,那么相关的都要调整。
mkdir -p /data/session
docker volume create --driver local \
--opt device=/data/session \
--opt o=bind \
--opt type=none \
apps_session

##用于存放工作空间数据
mkdir -p /data/workspace

docker service create \
--name app-webide \
--mount type=bind,source=/data/dev/webide,target=/var/www/html/src \
--mount type=bind,source=/data/workspace,target=/data/workspace \
--mount type=bind,source=/usr/bin/docker,target=/usr/bin/docker \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--mount type=volume,source=apps_session,destination=/data/session \
--config source=webide.conf,target=/etc/apache2/sites-enabled/app.conf \
--network traefiknet \
--container-label traefik.backend="app-webide" \
--container-label traefik.frontend.entryPoints="http,https" \
--container-label traefik.frontend.rule="Host: webide.yourdomain.com" \
--container-label traefik.frontend.redirect.entryPoint="https" \
--container-label traefik.port='80' \
--container-label traefik.protocol='http' \
--replicas 1 \
karoy/php-7-apache-debian:latest

##注意/var/run/docker.sock权限,否则php执行命令将失败且无任何信息,主机可执行
chmod 777 /var/run/docker.sock

主要在于访问webide.yourdomain.com能够访问到index.php文件,你可以使用其他的方式部署文件

php-7-apache-debian示例

Dockerfile

1
2
3
4
FROM php:7.2-apache
RUN ln -s /etc/apache2/mods-available/rewrite.load /etc/apache2/mods-enabled/rewrite.load
RUN ln -s /etc/apache2/mods-available/headers.load /etc/apache2/mods-enabled/headers.load
RUN cp -r -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone

构建命令
1
docker build -t karoy/php-7-apache-debian:latest .

webide.conf内容示例

webide.conf内容,可以用作docker config

<VirtualHost *:80>
        ServerName webide.yourdomain.com
        ServerAdmin webmaster@yourdomain
        DocumentRoot /var/www/html/src
        DirectoryIndex  index.html  index.php
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

你也可以直接使用主机路径挂载

1
2
3
4
#将
--config source=webide.conf,target=/etc/apache2/sites-enabled/app.conf \
#替换为
--mount type=bind,source=/data/webide.conf,target=/etc/apache2/sites-enabled/app.conf \

构建code-server应用

Dockerfile

1
2
3
4
FROM codercom/code-server
##工作空间可以提供的工具能力
RUN apt-get install -y git nodejs npm
RUN npm install -g npm@5.6.0

build karoy/code-server:latest

1
docker build -t karoy/code-server:latest .

该镜像在中转应用创建项目空间时将使用到.

基本流程

git.yourdomain.com访问项目WEB IDE链接,
结构为https://webide.yourdomain.com/?user=name&repo=org/repo

将重定向到webide.yourdomain.com域,并执行index.php中的逻辑:判断是否已存在对应应用,

有则跳转实际项目域名,没有则先尝试创建项目容器,再跳转到创建的项目对应的域名,

结构为*.webide.yourdomain.com,如acbdfn5156.webide.yourdomain.com,

因为必须自动取得验证信息并自动授权cookie,所以中转域名和实际项目域名在同一个父域下。

结束

该文章操作的并未处理外网访问的安全问题,建议在内网使用,因为当访问https://webide.yourdomain.com/?user=name&repo=org/repo类型链接的时候,将自动提取验证信息授权,也意味着将自动进入项目空间。
但是如果使用项目空间对应的域名是需要输入一串密码字符串才能进去的如acbdfn5156.webide.yourdomain.com(在未进去过的情况下,使用了cookie)。