Merge remote-tracking branch 'origin/add-dockerfile-method-and-start-stop-container' into AppOnK8s

This commit is contained in:
panshuxiao
2025-05-06 10:12:48 +08:00
repo.diff.parent 234a5087fc 16c75737f0
repo.diff.commit 574ebd7f61
repo.diff.stats_desc%!(EXTRA int=13, int=311, int=115)

repo.diff.view_file

@@ -75,6 +75,8 @@ RUN mkdir -p /var/lib/gitea /etc/gitea
RUN chown git:git /var/lib/gitea /etc/gitea
COPY --from=build-env /tmp/local /
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/devcontainer_init.sh /var/lib/gitea/devcontainer_init.sh
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/devcontainer_restart.sh /var/lib/gitea/devcontainer_restart.sh
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh

repo.diff.view_file

@@ -50,7 +50,7 @@ git checkout -b YOUR_BRANCH
code devstar
# in VS Code Terminal
TAGS="bindata timetzdata sqlite sqlite_unlock_notify" make watch # for debuging
TAGS="timetzdata sqlite sqlite_unlock_notify" make watch # for debuging
make test # testing
TAGS="bindata timetzdata sqlite sqlite_unlock_notify" make build # 生成可执行文件
./gitea
@@ -60,23 +60,25 @@ git add FILES
git commit -m "commit log"
git push
```
在DevStar Git仓库发起Pull Request合并代码后会自动触发CI流水线完成容器镜像的构建并上传到devstar.cn/devstar/devstar-studio:latest
### Start from Container Image
#### Start from Container Image
```
sudo apt update && sudo apt install docker.io
sudo docker pull devstar.cn/devstar/devstar-studio:latest
# 创建devstar_data目录用于持久化存储DevStar相关的配置和用户数据
mkdir ~/devstar_data
# 启动devstar-studio容器
sudo docker run --restart=always --name devstar-studio -d -p 8080:3000 -v /var/run/docker.sock:/var/run/docker.sock -v ~/devstar_data:/var/lib/gitea -v ~/devstar_data:/etc/gitea devstar.cn/devstar/devstar-studio:latest
# 打开 `http://localhost:8080` 完成安装。
make docker
public/assets/install.sh start --image=devstar-studio:latest
# 查看devstar-studio容器的启动日志
sudo docker logs devstar-studio
# 查看日志
public/assets/install.sh logs
# 停止并删除devstar-studio容器
sudo docker stop devstar-studio && sudo docker rm -f devstar-studio
public/assets/install.sh clean
# 删除所有容器
sudo docker stop $(docker ps -aq) && sudo docker rm -f $(docker ps -aq)
```
在DevStar Git仓库发起Pull Request合并代码后会自动触发CI流水线完成容器镜像的构建并上传到devstar.cn/devstar/devstar-studio:latest
```
public/assets/install.sh start
```
## 提示

74
devcontainer_init.sh Normal file
repo.diff.view_file

@@ -0,0 +1,74 @@
#!/bin/bash
# Copyright 2025 Mengning Software All rights reserved.
# Exit immediately if a command exits with a non-zero status
set -e
# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Function to display success message
function success {
echo -e "${GREEN}$1${NC}"
}
# Function to display failure message
function failure {
echo -e "${RED}$1${NC}"
}
# Detect the OS type and install dependencies
function install_dependencies {
# Install dependencies based on the OS type
success "dependencies install begin: "
OS_ID=$(grep '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
case $OS_ID in
ubuntu|debian)
sudo apt-get update -y
sudo apt-get install ssh -y
;;
centos)
# sudo yum update -y
# sudo yum install -y epel-release
# sudo yum groupinstall -y "Development Tools"
# sudo yum install -y yaml-cpp yaml-cpp-devel
;;
fedora)
# sudo dnf update -y
# sudo dnf group install -y "Development Tools"
# sudo dnf install -y yaml-cpp yaml-cpp-devel
;;
*)
failure "Unsupported OS: $OS_ID"
exit 1
;;
esac
}
install_dependencies
echo -e "PubkeyAuthentication yes\nPermitRootLogin yes\n" | tee -a /etc/ssh/sshd_config
rm -f /etc/ssh/ssh_host_*; ssh-keygen -A; service ssh restart
if [ -z "${host_docker_internal+x}" ]; then
echo "$HOST_DOCKER_INTERNAL host.docker.internal" | tee -a /etc/hosts;
fi
if [ ! -d "$WORKDIR" ]; then
git clone $REPO_URL $WORKDIR && echo "Git Repository cloned.";
else
echo "Folder already exists.";
fi
mkdir -p ~/test
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "$AUTHORIZED_KEYS" > ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
git clone "https://devstar.cn/init/ttyd.git" "/usr/bin/ttyd"
apt-get install -y build-essential cmake git libjson-c-dev libwebsockets-dev
/usr/bin/ttyd/ttyd/ttyd -W -w $WORKDIR bash &

5
devcontainer_restart.sh Normal file
repo.diff.view_file

@@ -0,0 +1,5 @@
#!/bin/bash
# Copyright 2025 Mengning Software All rights reserved.
service ssh restart
/usr/bin/ttyd/ttyd/ttyd -W -w $WORKDIR bash &

repo.diff.view_file

@@ -1,6 +1,7 @@
package docker
import (
"archive/tar"
"bytes"
"context"
"fmt"
@@ -262,6 +263,22 @@ func CreateAndStartContainer(cli *client.Client, opts *CreateDevcontainerOptions
log.Info("fail to start container %v", err)
return "", err
}
// 创建 tar 归档文件
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
defer tw.Close()
// 添加文件到 tar 归档
addFileToTar(tw, "devcontainer_init.sh", opts.InitializeCommand, 0777)
addFileToTar(tw, "devcontainer_restart.sh", opts.RestartCommand, 0777)
//ExecCommandInContainer(&ctx, cli, resp.ID, "touch /home/devcontainer_init.sh && chomd +x /home/devcontainer_init.sh")
err = cli.CopyToContainer(ctx, resp.ID, "/home", bytes.NewReader(buf.Bytes()), types.CopyToContainerOptions{})
if err != nil {
log.Info("%v", err)
return "", err
}
// 获取日志流
out, _ := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{
ShowStdout: true,
@@ -274,3 +291,19 @@ func CreateAndStartContainer(cli *client.Client, opts *CreateDevcontainerOptions
_, _ = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, out)
return stdoutBuf.String() + "\n" + stderrBuf.String(), nil
}
// addFileToTar 将文件添加到 tar 归档
func addFileToTar(tw *tar.Writer, filename string, content string, mode int64) error {
hdr := &tar.Header{
Name: filename,
Mode: mode,
Size: int64(len(content)),
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
if _, err := tw.Write([]byte(content)); err != nil {
return err
}
return nil
}

repo.diff.view_file

@@ -6,21 +6,22 @@ import (
// CreateDevcontainerOptions 定义创建开发容器选项
type CreateDevcontainerOptions struct {
DockerfileContent string
Name string `json:"name"`
Namespace string `json:"namespace"`
Image string `json:"image"`
CommandList []string `json:"command"`
ContainerPort uint16 `json:"containerPort"`
ServicePort uint16 `json:"servicePort"`
SSHPublicKeyList []string `json:"sshPublicKeyList"`
GitRepositoryURL string `json:"gitRepositoryURL"`
RepoId int64
UserId int64
ForwardPorts nat.PortSet
ContainerEnv []string
PostCreateCommand []string
Binds []string
SystemCommandCount int
PortBindings nat.PortMap
DockerfileContent string
Name string `json:"name"`
Namespace string `json:"namespace"`
Image string `json:"image"`
CommandList []string `json:"command"`
ContainerPort uint16 `json:"containerPort"`
ServicePort uint16 `json:"servicePort"`
SSHPublicKeyList []string `json:"sshPublicKeyList"`
GitRepositoryURL string `json:"gitRepositoryURL"`
RepoId int64
UserId int64
ForwardPorts nat.PortSet
ContainerEnv []string
PostCreateCommand []string
Binds []string
PortBindings nat.PortMap
InitializeCommand string
RestartCommand string
}

repo.diff.view_file

@@ -8,7 +8,8 @@ IMAGE_NAME=devstar-studio
VERSION=latest # DevStar Studio的默认版本为最新版本
PORT=8080 # 设置端口默认值为 8080
SSH_PORT=2222 # 设置ssh默认端口号2222
DATA_DIR=~/devstar_data
DATA_DIR=${HOME}/devstar_data
APP_INI=${DATA_DIR}/app.ini
# 错误处理函数
error_handler() {
@@ -89,17 +90,35 @@ function install {
# Function to start
function start {
install
if [[ -z "$IMAGE_STR" ]]; then
install
fi
# 创建devstar_data目录用于持久化存储DevStar相关的配置和用户数据
mkdir -p $DATA_DIR
sudo chown 1000:1000 $DATA_DIR
sudo chmod 666 /var/run/docker.sock
if [ ! -f "$APP_INI" ]; then
DOMAIN_NAME=$(hostname -I | awk '{print $1}')
echo "DOMAIN_NAME=$DOMAIN_NAME"
else
# 读取 DOMAIN 值
DOMAIN_NAME=$(grep -E '^\s*DOMAIN\s*=' "$APP_INI" | cut -d'=' -f2 | xargs)
# 检查是否成功读取到值
if [[ -z "$DOMAIN_NAME" ]]; then
DOMAIN_NAME="localhost"
fi
echo "DOMAIN_NAME=$DOMAIN_NAME"
fi
# 启动devstar-studio容器
stop
sudo docker run --restart=always --name $NAME -d -p $PORT:3000 -p $SSH_PORT:$SSH_PORT -v /var/run/docker.sock:/var/run/docker.sock -v ~/devstar_data:/var/lib/gitea -v ~/devstar_data:/etc/gitea $IMAGE_REGISTRY_USER/$IMAGE_NAME:$VERSION
if [[ -z "$IMAGE_STR" ]]; then
IMAGE_STR="$IMAGE_REGISTRY_USER/$IMAGE_NAME:$VERSION"
fi
echo "image=$IMAGE_STR"
sudo docker run --restart=always --name $NAME -d -p $PORT:3000 -p $SSH_PORT:$SSH_PORT -v /var/run/docker.sock:/var/run/docker.sock -v ~/devstar_data:/var/lib/gitea -v ~/devstar_data:/etc/gitea $IMAGE_STR
# 打开 `http://localhost:8080` 完成安装。
success "-------------------------------------------------------"
success "DevStar started in http://localhost:$PORT successfully!"
success "DevStar started in http://$DOMAIN_NAME:$PORT successfully!"
success "-------------------------------------------------------"
exit 0
}
@@ -109,7 +128,9 @@ function stop {
if [ $(docker ps -a --filter "name=^/${NAME}$" -q | wc -l) -gt 0 ]; then
sudo docker stop $NAME && sudo docker rm -f $NAME
fi
if [ $(docker ps -a --filter "name=^/devstar-studio$" -q | wc -l) -gt 0 ]; then
sudo docker stop devstar-studio && sudo docker rm -f devstar-studio
fi
}
# Function to logs
@@ -133,6 +154,7 @@ function usage {
success " --port=<arg> Specify the port number (default port is 8080)"
success " --ssh-port=<arg> Specify the ssh-port number (default ssh-port is 2222)"
success " --version=<arg> Specify the DevStar Studio Image Version (default verson is latest)"
success " --image=<arg> Specify the DevStar Studio Image example: devstar-studio:latest "
success " stop Stop the running DevStar Studio"
success " logs View the logs of the devstar-studio container"
failure " clean Clean up the running DevStar Studio, including deleting user data. Please use with caution."
@@ -146,7 +168,7 @@ case "$1" in
usage
;;
start)
ARGS=$(getopt --long port::,ssh-port::,version:: -- "$@")
ARGS=$(getopt --long port::,ssh-port::,version::,image:: -- "$@")
if [ $? -ne 0 ]; then
failure "ARGS ERROR!"
exit 1
@@ -166,7 +188,11 @@ case "$1" in
--version)
VERSION="$2"
echo "The DevStar Studio Image Version is: $VERSION"
shift 2 ;;
shift 2 ;;
--image)
IMAGE_STR="$2"
echo "The DevStar Studio Image: $IMAGE_STR"
shift 2 ;;
--)
shift
break ;;

repo.diff.view_file

@@ -315,7 +315,7 @@ func RestartContainer(ctx *context.Context) {
if err != nil {
ctx.Flash.Error("fail to restart container")
}
ctx.Redirect(ctx.Repo.RepoLink + "/dev-container")
ctx.JSON(http.StatusOK, map[string]string{})
}
func StopContainer(ctx *context.Context) {
opt := &devcontainer_service.RepoDevcontainerOptions{
@@ -328,5 +328,5 @@ func StopContainer(ctx *context.Context) {
if err != nil {
ctx.Flash.Error("fail to stop container")
}
ctx.Redirect(ctx.Repo.RepoLink + "/dev-container")
ctx.JSON(http.StatusOK, map[string]string{})
}

repo.diff.view_file

@@ -44,7 +44,7 @@ func GetWechatQRTicket(ctx *context.Context) (wechatQrTicket string, QRImageURL
qrExpireSeconds := setting.Wechat.TempQrExpireSeconds
// 构建请求的 URL
url := fmt.Sprintf("https://%s/api/wechat/official-account/login/qr/generate?qrExpireSeconds=%d&sceneStr=%s", setting.Wechat.DefaultDomainName, qrExpireSeconds, sceneStr)
url := fmt.Sprintf("https://%s/api/wechat/login/qr/generate?qrExpireSeconds=%d&sceneStr=%s", setting.Wechat.DefaultDomainName, qrExpireSeconds, sceneStr)
// 发送 GET 请求
resp, err := http.Get(url)
@@ -103,7 +103,7 @@ type Response struct {
// 假设这是用于检查二维码状态的函数
func checkWechatQrTicketStatus(ctx *context.Context, qrTicket string, quit chan bool) {
url := fmt.Sprintf("https://%s/api/wechat/official-account/login/qr/check-status?ticket=%s&_=%d",
url := fmt.Sprintf("https://%s/api/wechat/login/qr/check-status?ticket=%s&_=%d",
setting.Wechat.DefaultDomainName, qrTicket, time.Now().UnixMilli())
resp, err := http.Get(url)

repo.diff.view_file

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"regexp"
@@ -622,13 +623,51 @@ func claimDevcontainerResource(ctx *context.Context, newDevContainer *CreateDevc
}
}
// 解析仓库 URL
parsedURL, err := url.Parse(newDevContainer.GitRepositoryURL)
if err != nil {
log.Info("解析仓库URL失败: %v", err)
return err
}
hostParts := strings.Split(parsedURL.Host, ":")
port := ""
if len(hostParts) > 1 {
port = hostParts[1]
}
newHost := "host.docker.internal"
if port != "" {
newHost += ":" + port
}
parsedURL.Host = newHost
// 生成git仓库的 URL
newURL := parsedURL.String()
// Read the init script from file
initializeScriptContent, err := os.ReadFile("devcontainer_init.sh")
if err != nil {
return fmt.Errorf("读取初始化脚本失败: %v", err)
}
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
if err != nil {
log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err)
return err
}
initializeScript := strings.ReplaceAll(string(initializeScriptContent), "$AUTHORIZED_KEYS", strings.Join(newDevContainer.SSHPublicKeyList, "\n"))
initializeScript = strings.ReplaceAll(initializeScript, "$HOST_DOCKER_INTERNAL", cfg.Section("server").Key("DOMAIN").Value())
initializeScript = strings.ReplaceAll(initializeScript, "$WORKDIR", newDevContainer.DevcontainerWorkDir)
initializeScript = strings.ReplaceAll(initializeScript, "$REPO_URL", newURL)
log.Info("devcontainer_init.sh: %s", initializeScript)
restartScriptContent, err := os.ReadFile("devcontainer_restart.sh")
restartScript := strings.ReplaceAll(string(restartScriptContent), "$WORKDIR", newDevContainer.DevcontainerWorkDir)
// 2. 根据配置文件中指定的 DevContainer Agent 派遣创建任务
switch setting.Devcontainer.Agent {
case setting.KUBERNETES, "k8s":
// k8s Operator
return AssignDevcontainerCreation2K8sOperator(ctx, newDevContainer)
case setting.DOCKER:
return CreateDevcontainer(ctx, newDevContainer, devContainerJSON)
return CreateDevcontainer(ctx, newDevContainer, devContainerJSON, initializeScript, restartScript)
default:
// 未知 Agent直接报错
return devcontainer_models_errors.ErrFailedToOperateDevcontainerDB{

repo.diff.view_file

@@ -34,7 +34,6 @@ type DevStarJSON struct {
}
func CreateDevcontainerJSON(ctx *gitea_context.Context) {
log.Info("%v %v", ctx.Repo.Repository, ctx.Doer)
jsonString := `{
"image":"mcr.microsoft.com/devcontainers/base:dev-ubuntu-20.04",
"forwardPorts": [
@@ -68,7 +67,6 @@ func CreateDevcontainerJSON(ctx *gitea_context.Context) {
NewBranch: "main",
Message: "add container configuration",
})
//log.Info(resp.Commit.URL)
if err != nil {
log.Info("error ChangeRepoFiles:", err)
ctx.JSON(500, map[string]string{
@@ -116,7 +114,6 @@ func GetDevcontainerJsonModel(ctx context.Context, repo *repo.Repository) (*DevS
log.Error("Failed to unmarshal .devcontainer/devcontainer.json: %v", err)
return nil, err
}
log.Info("%v", devContainerJson)
devStarJson := &DevStarJSON{
Image: devContainerJson.Image,
@@ -140,7 +137,6 @@ func GetDevcontainerJsonModel(ctx context.Context, repo *repo.Repository) (*DevS
devStarJson.DockerfilePath = devContainerJson.Build.Dockerfile
}
log.Info("%v", devStarJson)
return devStarJson, nil
}
func GetFileContentByPath(ctx context.Context, repo *repo.Repository, path string) (string, error) {

repo.diff.view_file

@@ -6,7 +6,6 @@ import (
"bytes"
"context"
"fmt"
"net/url"
"os/exec"
"regexp"
"strconv"
@@ -17,7 +16,6 @@ import (
devcontainer_models "code.gitea.io/gitea/models/devcontainer"
docker_module "code.gitea.io/gitea/modules/docker"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
gitea_web_context "code.gitea.io/gitea/services/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
@@ -25,9 +23,10 @@ import (
"github.com/docker/go-connections/nat"
)
func CreateDevcontainer(ctx *context.Context, newDevContainer *CreateDevcontainerDTO, devContainerJSON *DevStarJSON) error {
func CreateDevcontainer(ctx *context.Context, newDevContainer *CreateDevcontainerDTO, devContainerJSON *DevStarJSON, initializeScript string, restartScript string) error {
log.Info("开始创建容器.....")
// 1. 创建docker client
cli, err := docker_module.CreateDockerClient(ctx)
if err != nil {
@@ -35,44 +34,6 @@ func CreateDevcontainer(ctx *context.Context, newDevContainer *CreateDevcontaine
}
defer cli.Close()
// 将公钥数组转换为字符串
keysString := strings.Join(newDevContainer.SSHPublicKeyList, "\n")
// 解析仓库 URL
parsedURL, err := url.Parse(newDevContainer.GitRepositoryURL)
if err != nil {
log.Info("解析仓库URL失败: %v", err)
return err
}
// 获取ip和端口
hostParts := strings.Split(parsedURL.Host, ":")
port := ""
if len(hostParts) > 1 {
port = hostParts[1]
}
newHost := "host.docker.internal"
if port != "" {
newHost += ":" + port
}
parsedURL.Host = newHost
// 生成git仓库的 URL
newURL := parsedURL.String()
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
if err != nil {
log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err)
}
cmd := []string{
"apt-get update -y; ",
"apt-get install ssh -y;",
"echo \"PubkeyAuthentication yes\nPermitRootLogin yes\n\" | tee -a /etc/ssh/sshd_config",
"rm -f /etc/ssh/ssh_host_*; ssh-keygen -A ; service ssh restart",
"if [ -z \"${host_docker_internal+x}\" ];then echo \"" + cfg.Section("server").Key("DOMAIN").Value() + " host.docker.internal\" | tee -a /etc/hosts; fi; ",
"if [ ! -d '/data/workspace' ]; then git clone " + newURL + " /data/workspace && echo \"Git Repository cloned.\"; else echo \"Folder already exists.\"; fi; ",
"mkdir -p ~/test; mkdir -p ~/.ssh ; chmod 700 ~/.ssh; echo \"" + keysString + "\" > ~/.ssh/authorized_keys ; chmod 600 ~/.ssh/authorized_keys; ",
"git clone " + "https://devstar.cn/init/ttyd.git" + " /usr/bin/ttyd;",
"apt-get install -y build-essential cmake git libjson-c-dev libwebsockets-dev;",
"/usr/bin/ttyd/ttyd/ttyd -W -w " + newDevContainer.DevcontainerWorkDir + " bash & ",
}
// 加入22 7681集合
natPort22 := nat.Port("22/tcp")
natPort7681 := nat.Port("7681/tcp")
@@ -88,14 +49,15 @@ func CreateDevcontainer(ctx *context.Context, newDevContainer *CreateDevcontaine
"-c",
strings.Join(devContainerJSON.InitializeCommand, "") + "tail -f /dev/null;",
},
RepoId: newDevContainer.RepoId,
UserId: newDevContainer.UserId,
SSHPublicKeyList: newDevContainer.SSHPublicKeyList,
GitRepositoryURL: newDevContainer.GitRepositoryURL,
ContainerEnv: devContainerJSON.ContainerEnv,
PostCreateCommand: append(cmd, devContainerJSON.PostCreateCommand...),
ForwardPorts: devContainerJSON.ForwardPorts,
SystemCommandCount: 8,
RepoId: newDevContainer.RepoId,
UserId: newDevContainer.UserId,
SSHPublicKeyList: newDevContainer.SSHPublicKeyList,
GitRepositoryURL: newDevContainer.GitRepositoryURL,
ContainerEnv: devContainerJSON.ContainerEnv,
PostCreateCommand: append([]string{"/home/devcontainer_init.sh"}, devContainerJSON.PostCreateCommand...),
ForwardPorts: devContainerJSON.ForwardPorts,
InitializeCommand: initializeScript,
RestartCommand: restartScript,
}
var flag string
for _, content := range devContainerJSON.RunArgs {
@@ -377,7 +339,7 @@ func PullImageAsyncAndStartContainer(ctx *context.Context, cli *client.Client, d
return err
}
if len(opts.PostCreateCommand) >= opts.SystemCommandCount {
if len(opts.PostCreateCommand) > 1 {
_, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{
Output: "",
Status: "running",
@@ -466,7 +428,7 @@ func PullImageAsyncAndStartContainer(ctx *context.Context, cli *client.Client, d
var state int = 2
for index, cmd := range opts.PostCreateCommand {
if index == opts.SystemCommandCount {
if index == 1 {
_, err = dbEngine.Table("devcontainer_output").
Where("user_id = ? AND repo_id = ? AND list_id = ?", opts.UserId, opts.RepoId, state).
Update(&devcontainer_models.DevcontainerOutput{
@@ -477,6 +439,7 @@ func PullImageAsyncAndStartContainer(ctx *context.Context, cli *client.Client, d
}
buffer = ""
state = 3
continue
}
output, err = docker_module.ExecCommandInContainer(ctx, cli, containerID, cmd)
@@ -530,10 +493,10 @@ func DockerRestartContainer(gitea_ctx *gitea_web_context.Context, opts *RepoDevC
}
devContainerJson, err := GetDevcontainerJsonModel(*gitea_ctx, gitea_ctx.Repo.Repository)
cmd := []string{
"/usr/bin/ttyd/ttyd/ttyd -W -w " + opts.DevContainerWorkDir + " bash & ",
"service ssh restart",
if err != nil {
return err
}
cmd := []string{"/home/devcontainer_restart.sh"}
postCreateCommand := append(cmd, devContainerJson.PostCreateCommand...)
// 创建 exec 实例
dbEngine := db.GetEngine(ctx)
@@ -544,15 +507,23 @@ func DockerRestartContainer(gitea_ctx *gitea_web_context.Context, opts *RepoDevC
Update(&devcontainer_models.DevcontainerOutput{
Status: "running",
})
_, err = dbEngine.Table("devcontainer_output").
Where("user_id = ? AND repo_id = ? AND list_id = ?", opts.UserId, opts.RepoId, state+1).
Update(&devcontainer_models.DevcontainerOutput{
Status: "running",
})
if err != nil {
return err
}
if len(devContainerJson.PostCreateCommand) > 1 {
_, err = dbEngine.Table("devcontainer_output").
Where("user_id = ? AND repo_id = ? AND list_id = ?", opts.UserId, opts.RepoId, state+1).
Update(&devcontainer_models.DevcontainerOutput{
Status: "running",
})
if err != nil {
return err
}
}
for index, cmd := range postCreateCommand {
if index == len(cmd) {
if index == 1 {
_, err = dbEngine.Table("devcontainer_output").
Where("user_id = ? AND repo_id = ? AND list_id = ?", opts.UserId, opts.RepoId, state).
Update(&devcontainer_models.DevcontainerOutput{
@@ -563,6 +534,7 @@ func DockerRestartContainer(gitea_ctx *gitea_web_context.Context, opts *RepoDevC
}
buffer = ""
state = 3
continue
}
output, err := docker_module.ExecCommandInContainer(&ctx, cli, containerID, cmd)
@@ -578,6 +550,7 @@ func DockerRestartContainer(gitea_ctx *gitea_web_context.Context, opts *RepoDevC
})
if err != nil {
log.Info("Error storing output for command %v: %v\n", cmd, err)
return err
}
}
_, err = dbEngine.Table("devcontainer_output").
@@ -585,13 +558,19 @@ func DockerRestartContainer(gitea_ctx *gitea_web_context.Context, opts *RepoDevC
Update(&devcontainer_models.DevcontainerOutput{
Status: "success",
})
_, err = dbEngine.Table("devcontainer_output").
Where("user_id = ? AND repo_id = ? AND list_id = ?", opts.UserId, opts.RepoId, 3).
Update(&devcontainer_models.DevcontainerOutput{
Status: "success",
})
if err != nil {
log.Info("Error storing output for command %v: %v\n", cmd, err)
return err
}
if len(devContainerJson.PostCreateCommand) > 1 {
_, err = dbEngine.Table("devcontainer_output").
Where("user_id = ? AND repo_id = ? AND list_id = ?", opts.UserId, opts.RepoId, 3).
Update(&devcontainer_models.DevcontainerOutput{
Status: "success",
})
if err != nil {
log.Info("Error storing output for command %v: %v\n", cmd, err)
return err
}
}
return nil
}

repo.diff.view_file

@@ -52,8 +52,9 @@
<div class="item"><a class="delete-button flex-text-inline" style="color:black;" data-modal-id="updatemodal" href="#">{{svg "octicon-database"}}{{ctx.Locale.Tr "repo.dev_container_control.update"}}</a></div>
{{if .InitializedContainer}}
<div class="item"><a class="flex-text-inline" style="color:black;" href="{{.Repository.Link}}/dev-container/restart">{{svg "octicon-terminal" 14 "tw-mr-2"}} Restart Dev Container</a></div>
<div class="item"><a class="flex-text-inline" style="color:black;" href="{{.Repository.Link}}/dev-container/stop">{{svg "octicon-terminal" 14 "tw-mr-2"}} Stop Dev Container</a></div>
<div class="item"><button id="restartButton" class="flex-text-inline" style="color:black;" onclick="handleClick(event, '{{.Repository.Link}}/dev-container/restart', document.getElementById('stopButton'))" >{{svg "octicon-terminal" 14 "tw-mr-2"}} Restart Dev Container</button></div>
<div class="item"><button id="stopButton" class="flex-text-inline" style="color:black;" onclick="handleClick(event, '{{.Repository.Link}}/dev-container/stop', document.getElementById('restartButton'))" >{{svg "octicon-terminal" 14 "tw-mr-2"}} Stop Dev Container</button></div>
<div class="item"><a class="flex-text-inline" style="color:black;" href="{{.WebSSHUrl}}" target="_blank">{{svg "octicon-code" 14}}open with WebTerminal</a></div>
<div class="item"><a class="flex-text-inline" style="color:black;" onclick="window.location.href = '{{.VSCodeUrl}}'">{{svg "octicon-code" 14}}open with VSCode</a ></div>
<div class="item"><a class="flex-text-inline" style="color:black;" onclick="window.location.href = '{{.CursorUrl}}'">{{svg "octicon-code" 14}}open with Cursor</a ></div>
@@ -175,5 +176,43 @@
</div>
<script>
function handleClick(event, targetLink, other) {
event.preventDefault();
const link = event.target;
link.disabled = true; // 禁用链接
link.style.cursor = 'auto';
link.style.color = 'gray';
if(other){
other.disabled = true;
other.style.cursor = 'auto';
other.style.color = 'gray';
}
console.log(targetLink);
// 发送网络请求
fetch(targetLink)
.then(response => response.json())
.then(data => {
//console.log('响应数据:', data);
// 处理响应数据
})
.catch(error => {
//console.error('请求错误:', error);
})
.finally(() => {
// 无论请求成功还是失败,都重新启用链接
link.style.color = 'black';
if(other){
other.disabled = false;
other.style.color = 'black';
other.style.cursor = 'pointer';
}
link.disabled = false;
link.style.cursor = 'pointer';
location.reload();
});
}
</script>
{{template "base/footer" .}}