infinitely writes skel for some reason

why
Johannes Bülow 2024-01-30 16:45:03 +01:00
parent d8e9d7a738
commit f48a4fc108
Signed by untrusted user who does not match committer: jmb
GPG Key ID: B56971CF7B8F83A6
8 changed files with 243 additions and 112 deletions

View File

@ -1,4 +1,6 @@
.PHONY: release dev deps install
release: deps release: deps
CGO_ENABLED=1 go build -buildvcs=true . CGO_ENABLED=1 go build -buildvcs=true .
@ -12,9 +14,11 @@ deps:
install: install:
cp podterminal /usr/local/bin/podterminal cp podterminal /usr/local/bin/podterminal
mkdir -p /etc/podterminal ifeq ($(shell id podterminal 2>/dev/null))
useradd -r -s /bin/false podterminal useradd -r -s /bin/false podterminal
cp ./exampleconfig.yaml /etc/podterminal/config.yaml endif
cp podterminal.service /etc/systemd/system/ mkdir -p /etc/podterminal/skel
cp -n ./exampleconfig.yaml /etc/podterminal/config.yaml
cp -n podterminal.service /etc/systemd/system/
systemctl daemon-reload systemctl daemon-reload
systemctl enable --now podterminal systemctl enable --now podterminal

View File

@ -14,8 +14,10 @@ image: lscr.io/linuxserver/webtop
maxage: 10800 maxage: 10800
# Port Podterminal should listen to # Port Podterminal should listen to
port: 80 port: 80
# Files that will be copied into container on startup # Files that will be copied into container userhome on startup
skel: /etc/podterminal/skel/ skel: /etc/podterminal/skel/
ssl: false ssl: false
ssl_cert: /etc/ssl/certs/ssl-cert-snakeoil.pem ssl_cert: /etc/ssl/certs/ssl-cert-snakeoil.pem
ssl_cert_key: /etc/ssl/private/ssl-cert-snakeoil.key ssl_cert_key: /etc/ssl/private/ssl-cert-snakeoil.key
# Blocks accessing /files and /files/socket.io to avoid File transfer
block_filebrowser: false

2
go.mod
View File

@ -9,6 +9,7 @@ require (
github.com/google/uuid v1.4.0 github.com/google/uuid v1.4.0
github.com/opencontainers/runtime-spec v1.1.1-0.20230922153023-c0e90434df2a github.com/opencontainers/runtime-spec v1.1.1-0.20230922153023-c0e90434df2a
github.com/spf13/viper v1.18.2 github.com/spf13/viper v1.18.2
golang.org/x/sync v0.5.0
) )
require ( require (
@ -147,7 +148,6 @@ require (
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.13.0 // indirect golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.19.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect

34
main.go
View File

@ -2,9 +2,7 @@ package main
import ( import (
"log" "log"
"os"
"github.com/spf13/viper"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"git.jmbit.de/jmb/podterminal/pods" "git.jmbit.de/jmb/podterminal/pods"
@ -33,35 +31,3 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
} }
func readConfigFile() {
log.Println("Reading Config")
viper.SetConfigFile("/etc/podterminal/config.yaml")
viper.SetDefault("port", 80)
viper.SetDefault("ip_addr", "0.0.0.0")
viper.SetDefault("image", "lscr.io/linuxserver/webtop")
viper.SetDefault("maxAge", 10800)
viper.SetDefault("dri", false)
viper.SetDefault("dir_node", "/dev/dri/renderD128")
viper.SetDefault("skel", "/etc/podterminal/skel")
viper.SetDefault("envvars",
map[string]string{
"CUSTOM_USER": "user",
"PASSWORD": "",
"TITLE": "Podterminal",
},
)
if _, err := os.Stat("/etc/podterminal/config.yaml"); os.IsNotExist(err) {
log.Println("Config file does not exist, creating")
os.MkdirAll("/etc/podterminal", 755)
err := viper.WriteConfig()
if err != nil {
log.Fatalln("could not write default config", err)
}
}
err := viper.ReadInConfig()
if err != nil {
log.Fatalln("could not read config file", err)
}
log.Println("Finished reading Config")
}

79
pods/skel.go Normal file
View File

@ -0,0 +1,79 @@
package pods
import (
"bytes"
"io"
"log"
buildahCopyah "github.com/containers/buildah/copier"
"github.com/containers/podman/v4/pkg/bindings/containers"
"github.com/containers/podman/v4/pkg/copy"
"github.com/spf13/viper"
"golang.org/x/sync/errgroup"
)
// CopySkelToContainer copies an Archive into the containers home directory
// (Assumes the use of a Webtop container)
// This Function is heavily inspired by https://github.com/containers/podman/blob/main/cmd/podman/containers/cp.go#L337
func CopySkelToContainer(id string) error {
log.Println("Copying skel directory to container")
var errorGroup errgroup.Group
var buf bytes.Buffer
var err error
_, err = copy.ResolveHostPath(viper.GetString("skel"))
if err != nil {
log.Println("Could not find skel Directory", err)
return err
}
reader, writer := io.Pipe()
// Create Function for copying from host to pipe
hostCopy := func() error {
getOptions := buildahCopyah.GetOptions{
KeepDirectoryNames: true,
}
err := buildahCopyah.Get("/", "", getOptions, []string{viper.GetString("skel")}, &buf)
if err != nil {
log.Print("Error copying from Host: ", err)
}
//defer closeAndLogWriter(writer)
return nil
}
// create function to copy from pipe to container
containerCopy := func() error {
_, err := containers.CopyFromArchive(Socket, id, "/config/", &buf)
if err != nil {
log.Print("Error copying to Container: ", err)
}
//defer closeAndLogReader(reader)
return nil
}
errorGroup.Go(hostCopy)
errorGroup.Go(containerCopy)
if err := errorGroup.Wait(); err != nil {
log.Println("Could not Copy Skel", err)
}
closeAndLogReader(reader)
closeAndLogWriter(writer)
return err
}
// closeAndLogReader takes a reader and closes it, logging any error
func closeAndLogReader(reader *io.PipeReader) {
err := reader.Close()
if err != nil {
log.Println("Could not close reader: ", err)
}
}
// closeAndLogWriter takes a writer and closes it, logging any error
func closeAndLogWriter(writer *io.PipeWriter) {
err := writer.Close()
if err != nil {
log.Println("Could not close writer: ", err)
}
}

41
readConfig.go Normal file
View File

@ -0,0 +1,41 @@
package main
import (
"log"
"os"
"github.com/spf13/viper"
)
func readConfigFile() {
log.Println("Reading Config")
viper.SetConfigFile("/etc/podterminal/config.yaml")
viper.SetDefault("port", 80)
viper.SetDefault("ip_addr", "0.0.0.0")
viper.SetDefault("image", "lscr.io/linuxserver/webtop")
viper.SetDefault("maxAge", 10800)
viper.SetDefault("dri", false)
viper.SetDefault("dir_node", "/dev/dri/renderD128")
viper.SetDefault("skel", "/etc/podterminal/skel/")
viper.SetDefault("block_filebrowser", false)
viper.SetDefault("envvars",
map[string]string{
"CUSTOM_USER": "user",
"PASSWORD": "",
"TITLE": "Podterminal",
},
)
if _, err := os.Stat("/etc/podterminal/config.yaml"); os.IsNotExist(err) {
log.Println("Config file does not exist, creating")
os.MkdirAll("/etc/podterminal", 755)
err := viper.WriteConfig()
if err != nil {
log.Fatalln("could not write default config", err)
}
}
err := viper.ReadInConfig()
if err != nil {
log.Fatalln("could not read config file", err)
}
log.Println("Finished reading Config")
}

98
web/reverseProxy.go Normal file
View File

@ -0,0 +1,98 @@
package web
import (
"fmt"
"log"
"net/http/httputil"
"net/url"
"time"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"git.jmbit.de/jmb/podterminal/pods"
)
func createReverseProxy(backendService string) (*httputil.ReverseProxy, error) {
var err error
log.Println("Creating reverse Proxy for ", backendService)
backendURL, err := url.Parse(backendService)
if err != nil {
log.Printf("Could not parse backend URL: %v", err)
}
proxy := &httputil.ReverseProxy{
Rewrite: func(request *httputil.ProxyRequest) {
request.SetURL(backendURL)
request.Out.Host = request.In.Host
},
}
return proxy, err
}
func containerProxy(c *gin.Context) {
session := sessions.Default(c)
session.Save()
sessionID := session.ID()
if session.Get("ct") == nil {
log.Println("Creating Container for Session ", sessionID)
ct, err := pods.CreateContainer()
if err != nil {
c.HTML(500, "Error", fmt.Sprintf("[%s] Could not create Container: %v", sessionID, err))
c.Abort()
}
err = pods.StartContainer(ct)
if err != nil {
c.HTML(500, "Error", fmt.Sprintf("[%s] Could not start Container: %v", sessionID, err))
c.Abort()
}
// Hack to wait for Container to start up and get assigned an IP
time.Sleep(3 * time.Second)
ctip, err := pods.GetContainerIP(ct)
if err != nil {
c.HTML(500, "Error", fmt.Sprintf("[%s] Could not get Container ip: %v", sessionID, err))
c.Abort()
}
err = pods.CopySkelToContainer(ct)
if err != nil {
c.HTML(
500,
"Error",
fmt.Sprintf("[%s] Could not copy skel archive to container: %v", sessionID, err),
)
c.Abort()
}
proxies[ct], err = createReverseProxy(fmt.Sprintf("http://%s:3000", ctip))
if err != nil {
c.HTML(
500,
"Error",
fmt.Sprintf("[%s] Could not create Container Proxy: %v", sessionID, err),
)
c.Abort()
}
session.Set("ct", ct)
session.Save()
c.Redirect(301, "/")
} else {
sessionCT := session.Get("ct")
switch sessionCT.(type) {
case string:
default:
c.HTML(500, "Error", "Session Container ID is not a string")
c.Abort()
}
id := session.Get("ct").(string)
proxy := proxies[id]
proxy.ServeHTTP(c.Writer, c.Request)
}
}

View File

@ -4,16 +4,12 @@ import (
"fmt" "fmt"
"log" "log"
"net/http/httputil" "net/http/httputil"
"net/url"
"time"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie" "github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/spf13/viper" "github.com/spf13/viper"
"git.jmbit.de/jmb/podterminal/pods"
) )
var proxies = make(map[string]*httputil.ReverseProxy) var proxies = make(map[string]*httputil.ReverseProxy)
@ -52,6 +48,9 @@ func setupRouter() *gin.Engine {
router.Use(gin.Recovery()) router.Use(gin.Recovery())
router.Use(sessions.Sessions("session", store)) router.Use(sessions.Sessions("session", store))
//router.Use(urlLog()) //router.Use(urlLog())
if viper.GetBool("block_filebrowser") == true {
router.Use(blockFilebrowser)
}
router.Use(containerProxy) router.Use(containerProxy)
// router.Any("/", containerProxy) // router.Any("/", containerProxy)
return router return router
@ -69,75 +68,17 @@ func urlLog() gin.HandlerFunc {
} }
} }
func createReverseProxy(backendService string) (*httputil.ReverseProxy, error) { // blockFilebrowser blocks the URLs used by the Kclient File browser
var err error func blockFilebrowser(c *gin.Context) {
log.Println("Creating reverse Proxy for ", backendService) switch c.Request.URL.RawPath {
case "/files":
backendURL, err := url.Parse(backendService)
if err != nil {
log.Printf("Could not parse backend URL: %v", err)
}
proxy := &httputil.ReverseProxy{
Rewrite: func(request *httputil.ProxyRequest) {
request.SetURL(backendURL)
request.Out.Host = request.In.Host
},
}
return proxy, err
}
func containerProxy(c *gin.Context) {
session := sessions.Default(c)
session.Save()
sessionID := session.ID()
if session.Get("ct") == nil {
log.Println("Creating Container for Session ", sessionID)
ct, err := pods.CreateContainer()
if err != nil {
c.HTML(500, "Error", fmt.Sprintf("[%s] Could not create Container: %v", sessionID, err))
c.Abort() c.Abort()
} case "/filebrowser.js":
err = pods.StartContainer(ct)
if err != nil {
c.HTML(500, "Error", fmt.Sprintf("[%s] Could not start Container: %v", sessionID, err))
c.Abort() c.Abort()
} case "/files/socket.io":
// Hack to wait for Container to start up and get assigned an IP
time.Sleep(3 * time.Second)
ctip, err := pods.GetContainerIP(ct)
if err != nil {
c.HTML(500, "Error", fmt.Sprintf("[%s] Could not get Container ip: %v", sessionID, err))
c.Abort() c.Abort()
}
proxies[ct], err = createReverseProxy(fmt.Sprintf("http://%s:3000", ctip))
if err != nil {
c.HTML(
500,
"Error",
fmt.Sprintf("[%s] Could not create Container Proxy: %v", sessionID, err),
)
c.Abort()
}
session.Set("ct", ct)
session.Save()
c.Redirect(301, "/")
} else {
sessionCT := session.Get("ct")
switch sessionCT.(type) {
case string:
default: default:
c.HTML(500, "Error", "Session Container ID is not a string") c.Next()
c.Abort()
} }
id := session.Get("ct").(string)
proxy := proxies[id]
proxy.ServeHTTP(c.Writer, c.Request)
}
} }