devcontainer /root /etc/ssh 改为持久化
修改open with vscode返回连接
This commit is contained in:
14
go.mod
14
go.mod
@@ -129,6 +129,7 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.32.3
|
||||
k8s.io/apimachinery v0.32.3
|
||||
k8s.io/kubectl v0.32.3
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
xorm.io/builder v0.3.13
|
||||
@@ -141,22 +142,22 @@ require (
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/cel-go v0.22.0 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
@@ -166,7 +167,6 @@ require (
|
||||
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
@@ -312,8 +312,8 @@ require (
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
||||
github.com/onsi/gomega v1.37.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
@@ -385,3 +385,5 @@ exclude github.com/gofrs/uuid v4.0.0+incompatible
|
||||
exclude github.com/goccy/go-json v0.4.11
|
||||
|
||||
exclude github.com/satori/go.uuid v1.2.0
|
||||
|
||||
replace github.com/docker/distribution => github.com/distribution/distribution v2.8.0+incompatible
|
||||
|
||||
17
go.sum
17
go.sum
@@ -243,8 +243,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 h1:PdsjTl0Cg+ZJgOx/CFV5NNgO1ThTreqdgKYiDCMHJwA=
|
||||
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21/go.mod h1:xJvkyD6Y2rZapGvPJLYo9dyx1s5dxBEDPa8T3YTuOk0=
|
||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/distribution/distribution v2.8.0+incompatible h1:YKTAHrTIHNQ1HnaTYCudkxWkW9LujmtudCTQh3/5AYk=
|
||||
github.com/distribution/distribution v2.8.0+incompatible/go.mod h1:EgLm2NgWtdKgzF9NpMzUKgzmR7AMmb0VQi2B+ZzDRjc=
|
||||
github.com/djherbis/buffer v1.1.0/go.mod h1:VwN8VdFkMY0DCALdY8o00d3IZ6Amz/UNVMWcSaJT44o=
|
||||
github.com/djherbis/buffer v1.2.0 h1:PH5Dd2ss0C7CRRhQCZ2u7MssF+No9ide8Ye71nPHcrQ=
|
||||
github.com/djherbis/buffer v1.2.0/go.mod h1:fjnebbZjCUpPinBRD+TDwXSOeNQ7fPQWLfGQqiAiUyE=
|
||||
@@ -255,8 +255,6 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
@@ -382,6 +380,7 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-swagger/go-swagger v0.31.0 h1:H8eOYQnY2u7vNKWDNykv2xJP3pBhRG/R+SOCAmKrLlc=
|
||||
github.com/go-swagger/go-swagger v0.31.0/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/HoaZJ5edupq7po=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
@@ -487,6 +486,8 @@ github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pw
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
|
||||
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
@@ -637,6 +638,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -655,6 +658,8 @@ github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
|
||||
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
|
||||
@@ -712,8 +717,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
@@ -1131,6 +1134,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
|
||||
k8s.io/kubectl v0.32.3 h1:VMi584rbboso+yjfv0d8uBHwwxbC438LKq+dXd5tOAI=
|
||||
k8s.io/kubectl v0.32.3/go.mod h1:6Euv2aso5GKzo/UVMacV6C7miuyevpfI91SvBvV9Zdg=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
|
||||
@@ -22,20 +22,126 @@ spec:
|
||||
# 安全策略,禁止挂载 ServiceAccount Token
|
||||
automountServiceAccountToken: false
|
||||
volumes:
|
||||
- name: root-ssh-dir
|
||||
# 添加 ttyd 共享卷
|
||||
- name: ttyd-shared
|
||||
emptyDir: {}
|
||||
initContainers:
|
||||
# 用户配置初始化
|
||||
- name: init-user-config
|
||||
image: {{.Spec.StatefulSet.Image}}
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
echo "=== Checking /target-root directory ==="
|
||||
ls -la /target-root/ 2>/dev/null || echo "Directory not found"
|
||||
|
||||
# 检查是否为空目录或首次初始化
|
||||
file_count=$(find /target-root -maxdepth 1 \( -type f -o -type d \) ! -name '.' ! -name '..' 2>/dev/null | wc -l)
|
||||
echo "Found $file_count items in /target-root"
|
||||
|
||||
if [ "$file_count" -lt 2 ]; then
|
||||
echo "Empty or minimal directory detected - initializing user home..."
|
||||
cp -a /root/. /target-root/
|
||||
echo "User config initialized from image defaults"
|
||||
else
|
||||
echo "User config already exists - skipping initialization to preserve user data"
|
||||
echo "Current contents:"
|
||||
ls -la /target-root/
|
||||
fi
|
||||
volumeMounts:
|
||||
- name: pvc-devcontainer
|
||||
mountPath: /target-root
|
||||
subPath: user-home
|
||||
|
||||
# SSH 配置和公钥初始化
|
||||
- name: init-root-ssh-dir
|
||||
image: devstar.cn/public/busybox:27a71e19c956
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- {{range .Spec.StatefulSet.SSHPublicKeyList}} echo "{{.}}" >> /root/.ssh/authorized_keys && {{end}} chmod -R 700 /root/.ssh/ && echo 'SSH Public Key(s) imported.'
|
||||
# 注意,必须递归设置 ~/.ssh/ 目录下权限 700,否则即使配置了 ~/.ssh/authorized_keys 也不会生效
|
||||
- |
|
||||
# 确保目录存在
|
||||
mkdir -p /root/.ssh
|
||||
mkdir -p /etc/ssh
|
||||
|
||||
# 创建标准的 sshd_config 文件(如果不存在)
|
||||
if [ ! -f /etc/ssh/sshd_config ]; then
|
||||
cat > /etc/ssh/sshd_config << 'EOF'
|
||||
# OpenSSH Server Configuration
|
||||
Port 22
|
||||
AddressFamily any
|
||||
ListenAddress 0.0.0.0
|
||||
|
||||
# Host Keys
|
||||
HostKey /etc/ssh/ssh_host_rsa_key
|
||||
HostKey /etc/ssh/ssh_host_ecdsa_key
|
||||
HostKey /etc/ssh/ssh_host_ed25519_key
|
||||
|
||||
# Logging
|
||||
SyslogFacility AUTH
|
||||
LogLevel INFO
|
||||
|
||||
# Authentication
|
||||
LoginGraceTime 2m
|
||||
PermitRootLogin yes
|
||||
StrictModes yes
|
||||
MaxAuthTries 6
|
||||
MaxSessions 10
|
||||
|
||||
PubkeyAuthentication yes
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
|
||||
PasswordAuthentication no
|
||||
PermitEmptyPasswords no
|
||||
ChallengeResponseAuthentication no
|
||||
|
||||
# Forwarding
|
||||
X11Forwarding yes
|
||||
X11DisplayOffset 10
|
||||
PrintMotd no
|
||||
PrintLastLog yes
|
||||
TCPKeepAlive yes
|
||||
|
||||
# Environment
|
||||
AcceptEnv LANG LC_*
|
||||
|
||||
# Subsystem
|
||||
Subsystem sftp /usr/lib/openssh/sftp-server
|
||||
|
||||
# PAM
|
||||
UsePAM yes
|
||||
EOF
|
||||
echo "Created sshd_config"
|
||||
fi
|
||||
|
||||
# 导入 SSH 公钥(如果不存在)
|
||||
{{range .Spec.StatefulSet.SSHPublicKeyList}}
|
||||
if ! grep -q "{{.}}" /root/.ssh/authorized_keys 2>/dev/null; then
|
||||
echo "{{.}}" >> /root/.ssh/authorized_keys
|
||||
fi
|
||||
{{end}}
|
||||
|
||||
# 设置正确的权限
|
||||
chmod 755 /root
|
||||
chmod 700 /root/.ssh/
|
||||
chmod 600 /root/.ssh/authorized_keys 2>/dev/null || true
|
||||
chmod 644 /etc/ssh/sshd_config 2>/dev/null || true
|
||||
|
||||
# 确保文件所有者正确
|
||||
chown -R root:root /root/.ssh/
|
||||
|
||||
echo 'SSH configuration and keys initialized.'
|
||||
volumeMounts:
|
||||
- name: root-ssh-dir
|
||||
mountPath: /root/.ssh
|
||||
- name: pvc-devcontainer
|
||||
mountPath: /root
|
||||
subPath: user-home
|
||||
- name: pvc-devcontainer
|
||||
mountPath: /etc/ssh
|
||||
subPath: ssh-host-keys
|
||||
|
||||
- name: init-git-repo-dir
|
||||
image: {{.Spec.StatefulSet.Image}}
|
||||
imagePullPolicy: IfNotPresent
|
||||
@@ -46,6 +152,25 @@ spec:
|
||||
volumeMounts:
|
||||
- name: pvc-devcontainer
|
||||
mountPath: /data
|
||||
subPath: user-data
|
||||
|
||||
# ttyd 二进制文件复制
|
||||
- name: init-ttyd
|
||||
image: tsl0922/ttyd:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
echo "Copying ttyd binary to shared volume..."
|
||||
cp /usr/bin/ttyd /ttyd-shared/ttyd
|
||||
chmod +x /ttyd-shared/ttyd
|
||||
echo "ttyd binary copied successfully"
|
||||
ls -la /ttyd-shared/ttyd
|
||||
volumeMounts:
|
||||
- name: ttyd-shared
|
||||
mountPath: /ttyd-shared
|
||||
|
||||
containers:
|
||||
- name: {{.ObjectMeta.Name}}
|
||||
image: {{.Spec.StatefulSet.Image}}
|
||||
@@ -67,8 +192,17 @@ spec:
|
||||
volumeMounts:
|
||||
- name: pvc-devcontainer
|
||||
mountPath: /data
|
||||
- name: root-ssh-dir
|
||||
mountPath: /root/.ssh
|
||||
subPath: user-data
|
||||
- name: pvc-devcontainer
|
||||
mountPath: /root
|
||||
subPath: user-home
|
||||
- name: pvc-devcontainer
|
||||
mountPath: /etc/ssh
|
||||
subPath: ssh-host-keys
|
||||
# 挂载 ttyd 共享卷
|
||||
- name: ttyd-shared
|
||||
mountPath: /ttyd-shared
|
||||
# 其他配置保持不变...
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
|
||||
@@ -567,6 +567,13 @@ func Get_IDE_TerminalURL(ctx *gitea_context.Context, devcontainer *RepoDevContai
|
||||
return "", fmt.Errorf("不支持的 DevContainer Agent 类型: %s", setting.Devcontainer.Agent)
|
||||
}
|
||||
|
||||
// 加载配置文件
|
||||
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
||||
if err != nil {
|
||||
log.Error("Get_IDE_TerminalURL: 加载配置文件失败: %v", err)
|
||||
return "", err
|
||||
}
|
||||
log.Info("Get_IDE_TerminalURL: 配置文件加载成功, ROOT_URL=%s", cfg.Section("server").Key("ROOT_URL").Value())
|
||||
// 构建并返回 URL
|
||||
return "://mengning.devstar/" +
|
||||
"openProject?host=" + devcontainer.RepoName +
|
||||
@@ -575,13 +582,128 @@ func Get_IDE_TerminalURL(ctx *gitea_context.Context, devcontainer *RepoDevContai
|
||||
"&username=" + devcontainer.DevContainerUsername +
|
||||
"&path=" + devcontainer.DevContainerWorkDir +
|
||||
"&access_token=" + access_token +
|
||||
"&devstar_username=" + devcontainer.RepoOwnerName, nil
|
||||
"&devstar_username=" + devcontainer.RepoOwnerName +
|
||||
"&devstar_domain=" + cfg.Section("server").Key("ROOT_URL").Value(), nil
|
||||
}
|
||||
|
||||
func AddPublicKeyToAllRunningDevContainer(ctx context.Context, user *user_model.User, publicKey string) error {
|
||||
switch setting.Devcontainer.Agent {
|
||||
case setting.KUBERNETES, "k8s":
|
||||
return fmt.Errorf("unsupported agent")
|
||||
log.Info("AddPublicKeyToAllRunningDevContainer: 开始为用户 %s (ID=%d) 的所有运行中容器添加公钥",
|
||||
user.Name, user.ID)
|
||||
|
||||
// 1. 获取用户的所有 DevContainer
|
||||
opts := &SearchUserDevcontainerListItemVoOptions{
|
||||
Actor: user,
|
||||
}
|
||||
userDevcontainersVO, err := GetUserDevcontainersList(ctx, opts)
|
||||
if err != nil {
|
||||
log.Error("AddPublicKeyToAllRunningDevContainer: 获取用户容器列表失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
repoDevContainerList := userDevcontainersVO.DevContainers
|
||||
if len(repoDevContainerList) == 0 {
|
||||
log.Info("AddPublicKeyToAllRunningDevContainer: 用户 %s 没有任何 DevContainer", user.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("AddPublicKeyToAllRunningDevContainer: 找到 %d 个 DevContainer", len(repoDevContainerList))
|
||||
|
||||
// 2. 获取 K8s 客户端
|
||||
k8sClient, err := devcontainer_k8s_agent_module.GetKubernetesClient(&ctx)
|
||||
if err != nil {
|
||||
log.Error("AddPublicKeyToAllRunningDevContainer: 获取 K8s 客户端失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 获取标准 K8s 客户端用于执行命令
|
||||
stdClient, err := getStandardKubernetesClient()
|
||||
if err != nil {
|
||||
log.Error("AddPublicKeyToAllRunningDevContainer: 获取标准 K8s 客户端失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. 遍历所有容器,检查状态并添加公钥
|
||||
successCount := 0
|
||||
errorCount := 0
|
||||
|
||||
for _, repoDevContainer := range repoDevContainerList {
|
||||
log.Info("AddPublicKeyToAllRunningDevContainer: 处理容器 %s", repoDevContainer.DevContainerName)
|
||||
|
||||
// 4.1 检查 DevContainer 是否运行
|
||||
getOpts := &devcontainer_k8s_agent_module.GetDevcontainerOptions{
|
||||
GetOptions: metav1.GetOptions{},
|
||||
Name: repoDevContainer.DevContainerName,
|
||||
Namespace: setting.Devcontainer.Namespace,
|
||||
Wait: false,
|
||||
}
|
||||
|
||||
devcontainerApp, err := devcontainer_k8s_agent_module.GetDevcontainer(&ctx, k8sClient, getOpts)
|
||||
if err != nil {
|
||||
log.Error("AddPublicKeyToAllRunningDevContainer: 获取容器 %s 状态失败: %v",
|
||||
repoDevContainer.DevContainerName, err)
|
||||
errorCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// 4.2 检查容器是否就绪
|
||||
if !devcontainerApp.Status.Ready {
|
||||
log.Info("AddPublicKeyToAllRunningDevContainer: 容器 %s 未就绪,跳过",
|
||||
repoDevContainer.DevContainerName)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Info("AddPublicKeyToAllRunningDevContainer: 容器 %s 就绪,开始添加公钥",
|
||||
repoDevContainer.DevContainerName)
|
||||
|
||||
// 4.3 构建添加公钥的命令
|
||||
// 使用更安全的方式添加公钥,避免重复添加
|
||||
addKeyCommand := fmt.Sprintf(`
|
||||
# 确保 .ssh 目录存在
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
|
||||
# 检查公钥是否已存在
|
||||
if ! grep -Fxq "%s" ~/.ssh/authorized_keys 2>/dev/null; then
|
||||
echo "%s" >> ~/.ssh/authorized_keys
|
||||
chmod 600 ~/.ssh/authorized_keys
|
||||
echo "Public key added successfully"
|
||||
else
|
||||
echo "Public key already exists"
|
||||
fi
|
||||
|
||||
# 验证文件内容
|
||||
wc -l ~/.ssh/authorized_keys
|
||||
`, publicKey, publicKey)
|
||||
|
||||
// 4.4 在容器中执行命令
|
||||
err = executeCommandInK8sPod(&ctx, stdClient,
|
||||
setting.Devcontainer.Namespace,
|
||||
repoDevContainer.DevContainerName, // 传递 DevContainer 名称而不是 Pod 名称
|
||||
repoDevContainer.DevContainerName, // 容器名通常与 DevContainer 名相同
|
||||
[]string{"/bin/bash", "-c", addKeyCommand})
|
||||
|
||||
if err != nil {
|
||||
log.Error("AddPublicKeyToAllRunningDevContainer: 在容器 %s 中执行添加公钥命令失败: %v",
|
||||
repoDevContainer.DevContainerName, err)
|
||||
errorCount++
|
||||
} else {
|
||||
log.Info("AddPublicKeyToAllRunningDevContainer: 成功为容器 %s 添加公钥",
|
||||
repoDevContainer.DevContainerName)
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("AddPublicKeyToAllRunningDevContainer: 完成处理 - 成功: %d, 失败: %d",
|
||||
successCount, errorCount)
|
||||
|
||||
if errorCount > 0 && successCount == 0 {
|
||||
return fmt.Errorf("所有容器添加公钥都失败了,错误数量: %d", errorCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case setting.DOCKER:
|
||||
cli, err := docker.CreateDockerClient(&ctx)
|
||||
if err != nil {
|
||||
@@ -623,8 +745,9 @@ func AddPublicKeyToAllRunningDevContainer(ctx context.Context, user *user_model.
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown agent")
|
||||
return fmt.Errorf("unknown agent: %s", setting.Devcontainer.Agent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/devcontainer/errors"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -24,6 +26,8 @@ import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
)
|
||||
|
||||
var k8sGroupVersionResource = schema.GroupVersionResource{
|
||||
@@ -242,12 +246,24 @@ func AssignDevcontainerCreation2K8sOperator(ctx *context.Context, newDevContaine
|
||||
command := []string{
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
"rm -f /etc/ssh/ssh_host_* && ssh-keygen -A && service ssh start && " +
|
||||
"export DEBIAN_FRONTEND=noninteractive && " +
|
||||
"apt-get update -y && " +
|
||||
"apt-get install -y build-essential cmake git libjson-c-dev libwebsockets-dev && " +
|
||||
"git clone https://github.com/tsl0922/ttyd.git /tmp/ttyd && " +
|
||||
"cd /tmp/ttyd && mkdir build && cd build && cmake .. && make && make install && " +
|
||||
"nohup ttyd -p 7681 -W bash > /dev/null 2>&1 & " +
|
||||
"apt-get install -y ssh && " +
|
||||
// 改为条件生成:只有在密钥不存在时才生成
|
||||
"if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then " +
|
||||
" echo 'Generating SSH host keys...' && " +
|
||||
" ssh-keygen -A && " +
|
||||
" echo 'SSH host keys generated' ; " +
|
||||
"else " +
|
||||
" echo 'SSH host keys already exist' ; " +
|
||||
"fi && " +
|
||||
"mkdir -p /var/run/sshd && " +
|
||||
"/usr/sbin/sshd && " +
|
||||
"if [ -f /ttyd-shared/ttyd ]; then " +
|
||||
"mkdir -p /data/workspace && " +
|
||||
"cd /data/workspace && " +
|
||||
"/ttyd-shared/ttyd -p 7681 -i 0.0.0.0 --writable bash > /tmp/ttyd.log 2>&1 & " +
|
||||
"fi && " +
|
||||
"while true; do sleep 60; done",
|
||||
}
|
||||
log.Info("AssignDevcontainerCreation2K8sOperator: Command includes ttyd installation and startup")
|
||||
@@ -642,3 +658,90 @@ func getStandardKubernetesClient() (*kubernetes.Clientset, error) {
|
||||
|
||||
return stdClient, nil
|
||||
}
|
||||
|
||||
// executeCommandInK8sPod 在 K8s Pod 中执行命令的辅助函数
|
||||
func executeCommandInK8sPod(ctx *context.Context, client *kubernetes.Clientset, namespace, devcontainerName, containerName string, command []string) error {
|
||||
log.Info("executeCommandInK8sPod: 开始为 DevContainer %s 查找对应的 Pod", devcontainerName)
|
||||
|
||||
// 1. 首先根据标签选择器查找对应的 Pod
|
||||
labelSelector := fmt.Sprintf("app=%s", devcontainerName)
|
||||
pods, err := client.CoreV1().Pods(namespace).List(*ctx, metav1.ListOptions{
|
||||
LabelSelector: labelSelector,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("executeCommandInK8sPod: 查找 Pod 失败: %v", err)
|
||||
return fmt.Errorf("查找 Pod 失败: %v", err)
|
||||
}
|
||||
|
||||
if len(pods.Items) == 0 {
|
||||
log.Error("executeCommandInK8sPod: 未找到 DevContainer %s 对应的 Pod", devcontainerName)
|
||||
return fmt.Errorf("未找到 DevContainer %s 对应的 Pod", devcontainerName)
|
||||
}
|
||||
|
||||
// 2. 找到第一个运行中的 Pod
|
||||
var targetPod *v1.Pod
|
||||
for i := range pods.Items {
|
||||
pod := &pods.Items[i]
|
||||
if pod.Status.Phase == v1.PodRunning {
|
||||
targetPod = pod
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetPod == nil {
|
||||
log.Error("executeCommandInK8sPod: DevContainer %s 没有运行中的 Pod", devcontainerName)
|
||||
return fmt.Errorf("DevContainer %s 没有运行中的 Pod", devcontainerName)
|
||||
}
|
||||
|
||||
podName := targetPod.Name
|
||||
log.Info("executeCommandInK8sPod: 找到运行中的 Pod: %s, 在容器 %s 中执行命令",
|
||||
podName, containerName)
|
||||
|
||||
// 3. 执行命令
|
||||
req := client.CoreV1().RESTClient().Post().
|
||||
Resource("pods").
|
||||
Name(podName).
|
||||
Namespace(namespace).
|
||||
SubResource("exec").
|
||||
Param("container", containerName)
|
||||
|
||||
req.VersionedParams(&v1.PodExecOptions{
|
||||
Container: containerName,
|
||||
Command: command,
|
||||
Stdin: false,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
TTY: false,
|
||||
}, scheme.ParameterCodec)
|
||||
|
||||
// 获取 executor
|
||||
config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
|
||||
if err != nil {
|
||||
// 如果集群外配置失败,尝试集群内配置
|
||||
config, err = rest.InClusterConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 K8s 配置失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
executor, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建命令执行器失败: %v", err)
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
var stdout, stderr bytes.Buffer
|
||||
err = executor.StreamWithContext(*ctx, remotecommand.StreamOptions{
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error("executeCommandInK8sPod: 命令执行失败: %v, stderr: %s",
|
||||
err, stderr.String())
|
||||
return fmt.Errorf("命令执行失败: %v, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
log.Info("executeCommandInK8sPod: 命令执行成功, stdout: %s", stdout.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user