infinitely writes skel for some reason
parent
d8e9d7a738
commit
f48a4fc108
10
Makefile
10
Makefile
|
@ -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
|
||||||
|
|
|
@ -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
2
go.mod
|
@ -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
34
main.go
|
@ -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")
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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":
|
||||||
|
c.Abort()
|
||||||
|
case "/filebrowser.js":
|
||||||
|
c.Abort()
|
||||||
|
case "/files/socket.io":
|
||||||
|
c.Abort()
|
||||||
|
|
||||||
backendURL, err := url.Parse(backendService)
|
default:
|
||||||
if err != nil {
|
c.Next()
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue