Compare commits

..

25 commits
v5 ... main

Author SHA1 Message Date
c7ed0f148f
Disable oom killer completely
All checks were successful
Unittests / deploy-dev (push) Successful in 57s
Unittests / unittests (push) Successful in 1m1s
Release / deploy-production (release) Successful in 1m6s
2025-06-14 12:38:08 +02:00
ec1f0d3f3a
No node-26
All checks were successful
Unittests / unittests (push) Successful in 15s
Unittests / deploy-dev (push) Successful in 42s
Release / deploy-production (release) Successful in 1m5s
2025-06-14 03:05:58 +02:00
3d559fc94d
Add wait time when container was in starting phase
Some checks failed
Unittests / unittests (push) Successful in 1m6s
Unittests / deploy-dev (push) Successful in 54s
Release / deploy-production (release) Failing after 1m6s
2025-06-14 02:52:31 +02:00
cx
0bc10b7154 Update .gitea/workflows/release.yml
All checks were successful
Unittests / unittests (push) Successful in 1m0s
Unittests / deploy-dev (push) Successful in 47s
Release / deploy-production (release) Successful in 1m29s
2025-05-28 21:43:46 +00:00
cx
0d81ffaa86 node-28.rosti.cz
All checks were successful
Unittests / deploy-dev (push) Successful in 1m5s
Unittests / unittests (push) Successful in 1m29s
Release / deploy-production (release) Successful in 1m6s
2025-04-01 18:03:29 +00:00
cx
a94ccbc554 Update .gitea/workflows/unittests.yml
All checks were successful
Unittests / deploy-dev (push) Successful in 45s
Unittests / unittests (push) Successful in 1m8s
2025-01-30 23:13:16 +00:00
cx
08f213e0de Update .gitea/workflows/unittests.yml
Some checks failed
Unittests / deploy-dev (push) Has been cancelled
Unittests / unittests (push) Has been cancelled
2025-01-30 23:12:49 +00:00
cx
231441f980 Update .gitea/workflows/unittests.yml
Some checks are pending
Unittests / unittests (push) Waiting to run
Unittests / deploy-dev (push) Waiting to run
2025-01-30 23:12:30 +00:00
622dd91ba6
node-26
All checks were successful
Unittests / unittests (push) Successful in 1m7s
Unittests / deploy-dev (push) Successful in 1m2s
Release / deploy-production (release) Successful in 2m17s
2024-11-19 14:50:14 +01:00
bc875b83a4
Add Bun
All checks were successful
Unittests / unittests (push) Successful in 14s
Unittests / deploy-dev (push) Successful in 47s
2024-07-13 12:05:40 +02:00
27152bad72
Debug line
All checks were successful
Unittests / unittests (push) Successful in 9s
Unittests / deploy-dev (push) Successful in 46s
2024-05-28 00:38:33 +02:00
27948ee5b6
HTTP API for snapshots
All checks were successful
Unittests / unittests (push) Successful in 9s
Unittests / deploy-dev (push) Successful in 41s
2024-05-27 18:51:56 +02:00
8c5a419efc
Fix default tech
All checks were successful
Unittests / unittests (push) Successful in 9s
Unittests / deploy-dev (push) Successful in 43s
2024-05-26 01:10:14 +02:00
88262a27d8
Better debug
All checks were successful
Unittests / unittests (push) Successful in 9s
Unittests / deploy-dev (push) Successful in 43s
2024-05-26 01:08:44 +02:00
088b6bdcf6
Get isPasswordSet when getting app
All checks were successful
Unittests / unittests (push) Successful in 8s
Unittests / deploy-dev (push) Successful in 41s
2024-05-25 16:23:10 +02:00
5f80da8cbd
Is password set field
All checks were successful
Unittests / unittests (push) Successful in 9s
Unittests / deploy-dev (push) Successful in 43s
2024-05-25 14:19:03 +02:00
2077271306
Clear password feature
All checks were successful
Unittests / unittests (push) Successful in 50s
Unittests / deploy-dev (push) Successful in 1m27s
2024-05-25 13:35:47 +02:00
cx
9e82bfc2b5 Chagne node-x ip
All checks were successful
Unittests / unittests (push) Successful in 1m23s
Unittests / deploy-dev (push) Successful in 1m15s
2024-04-15 10:30:05 +00:00
e0b5832e75
Fix deps and crashing when deleting nonexistant app
All checks were successful
Unittests / unittests (push) Successful in 9s
Unittests / deploy-dev (push) Successful in 59s
2024-03-03 02:55:05 +01:00
863d857283
Restart container when tech changes
All checks were successful
Unittests / unittests (push) Successful in 10s
Unittests / deploy-dev (push) Successful in 47s
2024-02-01 22:02:22 +01:00
1c5b8d8f50
Add better debug message to SetTechnology
All checks were successful
Unittests / unittests (push) Successful in 9s
Unittests / deploy-dev (push) Successful in 46s
2024-02-01 21:57:34 +01:00
bc4b6c7bff
Possibility to set tech during update
All checks were successful
Unittests / unittests (push) Successful in 9s
Unittests / deploy-dev (push) Successful in 53s
2024-02-01 20:57:13 +01:00
45899f3b0c
Set owner of metadata
All checks were successful
Unittests / unittests (push) Successful in 9s
Unittests / deploy-dev (push) Successful in 45s
2024-01-31 00:16:40 +01:00
5513da35b3
Fix endpoint name for metadata
All checks were successful
Unittests / unittests (push) Successful in 9s
Unittests / deploy-dev (push) Successful in 44s
2024-01-30 23:52:33 +01:00
31ba1ce5a3
Save metadata endpoint
All checks were successful
Unittests / unittests (push) Successful in 9s
Unittests / deploy-dev (push) Successful in 46s
2024-01-30 23:50:03 +01:00
16 changed files with 2324 additions and 591 deletions

View file

@ -11,7 +11,7 @@ jobs:
deploy-production: deploy-production:
runs-on: [amd64, prod] runs-on: [amd64, prod]
env: env:
NODES: node-22.rosti.cz node-23.rosti.cz node-24.rosti.cz node-25.rosti.cz NODES: node-22.rosti.cz node-23.rosti.cz node-24.rosti.cz node-25.rosti.cz node-28.rosti.cz node-29.rosti.cz
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: deploy - name: deploy

View file

@ -7,7 +7,7 @@ on:
jobs: jobs:
unittests: unittests:
runs-on: [amd64, moon] runs-on: [amd64, dev]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v3 - uses: actions/setup-go@v3
@ -36,11 +36,14 @@ jobs:
# - 9001:9001 # - 9001:9001
# options: server /data --console-address :9001 # options: server /data --console-address :9001
deploy-dev: deploy-dev:
runs-on: [amd64, moon] runs-on: [amd64, dev]
env: env:
NODES: "192.168.1.236" NODES: "192.168.1.199"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_MASTER_KEY }}
- name: deploy - name: deploy
run: | run: |
# echo LS1 # echo LS1

View file

@ -115,7 +115,7 @@ func (a *AppsProcessor) Update(name string, SSHPort int, HTTPPort int, image str
} }
// UpdateResources updates various metrics saved in the database // UpdateResources updates various metrics saved in the database
func (a *AppsProcessor) UpdateResources(name string, state string, OOMKilled bool, CPUUsage float64, memory int, diskUsageBytes int, diskUsageInodes int, flags detector.Flags) error { func (a *AppsProcessor) UpdateResources(name string, state string, OOMKilled bool, CPUUsage float64, memory int, diskUsageBytes int, diskUsageInodes int, flags detector.Flags, isPasswordSet bool) error {
err := a.DB.Model(&App{}).Where("name = ?", name).Updates(App{ err := a.DB.Model(&App{}).Where("name = ?", name).Updates(App{
State: state, State: state,
OOMKilled: OOMKilled, OOMKilled: OOMKilled,
@ -124,6 +124,7 @@ func (a *AppsProcessor) UpdateResources(name string, state string, OOMKilled boo
DiskUsageBytes: diskUsageBytes, DiskUsageBytes: diskUsageBytes,
DiskUsageInodes: diskUsageInodes, DiskUsageInodes: diskUsageInodes,
Flags: flags.String(), Flags: flags.String(),
IsPasswordSet: isPasswordSet,
}).Error }).Error
return err return err
} }

View file

@ -95,7 +95,7 @@ func TestAppsProcessorUpdateResources(t *testing.T) {
err := processor.New("updateresources_1224", 1002, 1003, "testimage", 2, 256) err := processor.New("updateresources_1224", 1002, 1003, "testimage", 2, 256)
assert.Nil(t, err) assert.Nil(t, err)
err = processor.UpdateResources("updateresources_1224", "running", true, 1000, 256, 100, 200, detector.Flags{"test"}) err = processor.UpdateResources("updateresources_1224", "running", true, 1000, 256, 100, 200, detector.Flags{"test"}, true)
assert.Nil(t, err) assert.Nil(t, err)
app, err := processor.Get("updateresources_1224") app, err := processor.Get("updateresources_1224")
@ -107,6 +107,7 @@ func TestAppsProcessorUpdateResources(t *testing.T) {
assert.Equal(t, 256, app.MemoryUsage) assert.Equal(t, 256, app.MemoryUsage)
assert.Equal(t, 100, app.DiskUsageBytes) assert.Equal(t, 100, app.DiskUsageBytes)
assert.Equal(t, 200, app.DiskUsageInodes) assert.Equal(t, 200, app.DiskUsageInodes)
assert.Equal(t, true, app.IsPasswordSet)
assert.Contains(t, app.Flags, "test") assert.Contains(t, app.Flags, "test")
} }

View file

@ -38,6 +38,7 @@ type AppState struct {
DiskUsageBytes int `json:"disk_usage_bytes"` DiskUsageBytes int `json:"disk_usage_bytes"`
DiskUsageInodes int `json:"disk_usage_inodes"` DiskUsageInodes int `json:"disk_usage_inodes"`
Flags detector.Flags `json:"flags"` Flags detector.Flags `json:"flags"`
IsPasswordSet bool `json:"is_password_set"`
} }
// Apps is list of applications // Apps is list of applications
@ -95,6 +96,7 @@ type App struct {
DiskUsageInodes int `json:"disk_usage_inodes"` DiskUsageInodes int `json:"disk_usage_inodes"`
// Flags from detector of problems in the container // Flags from detector of problems in the container
Flags string `json:"flags"` // flags are separated by comma Flags string `json:"flags"` // flags are separated by comma
IsPasswordSet bool `json:"is_password_set"` // True if the password is set in the container (file with the password exists)
// this is gathered in docker package and has to be assembled externally // this is gathered in docker package and has to be assembled externally
Techs AppTechs `json:"techs,omitempty" gorm:"-"` // list of available technologies in the image Techs AppTechs `json:"techs,omitempty" gorm:"-"` // list of available technologies in the image

View file

@ -202,13 +202,13 @@ func (d *Driver) Remove(name string) error {
return err return err
} }
timeout := time.Duration(dockerTimeout * time.Second) timeout := dockerTimeout
err = cli.ContainerStop(context.TODO(), containerID, &timeout) err = cli.ContainerStop(context.TODO(), containerID, container.StopOptions{Timeout: &timeout})
if err != nil { if err != nil {
return err return err
} }
err = cli.ContainerRemove(context.TODO(), containerID, types.ContainerRemoveOptions{}) err = cli.ContainerRemove(context.TODO(), containerID, container.RemoveOptions{})
return err return err
} }
@ -246,8 +246,8 @@ func (d *Driver) Stop(name string) error {
return err return err
} }
timeout := time.Duration(dockerTimeout * time.Second) timeout := dockerTimeout
err = cli.ContainerStop(context.TODO(), containerID, &timeout) err = cli.ContainerStop(context.TODO(), containerID, container.StopOptions{Timeout: &timeout})
return err return err
} }
@ -338,10 +338,13 @@ func (d *Driver) Create(name string, image string, volumePath string, HTTPPort i
} }
} }
OOMKillDisable := false // OOMKillDisable := false
if memory < 1500 { // if memory < 1500 {
OOMKillDisable = true // OOMKillDisable = true
} // }
// We disable OOM killer because it keeps containers in resource heavy loop
// This is from some discussion: If OOM-killer is disabled, tasks under cgroup will hang/sleep in memory cgroup's OOM-waitqueue when they request accountable memory
OOMKillDisable := true
envList := []string{} envList := []string{}
for key, value := range env { for key, value := range env {

View file

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"os"
"path" "path"
"strconv" "strconv"
"strings" "strings"
@ -17,6 +18,7 @@ import (
const appUsername = "app" const appUsername = "app"
const owner = "app:app" const owner = "app:app"
const passwordFile = "/srv/.rosti" const passwordFile = "/srv/.rosti"
const passwordFileNoPath = ".rosti"
const deployKeyType = "ed25519" const deployKeyType = "ed25519"
const deployKeyPrefix = "rosti_deploy" const deployKeyPrefix = "rosti_deploy"
@ -96,6 +98,11 @@ func (c *Container) GetState() (*apps.AppState, error) {
return nil, err return nil, err
} }
isPasswordSet, err := c.IsPasswordSet()
if err != nil {
return nil, err
}
state := apps.AppState{ state := apps.AppState{
State: status.Status, State: status.Status,
OOMKilled: status.OOMKilled, OOMKilled: status.OOMKilled,
@ -106,6 +113,7 @@ func (c *Container) GetState() (*apps.AppState, error) {
DiskUsageBytes: bytes, DiskUsageBytes: bytes,
DiskUsageInodes: inodes, DiskUsageInodes: inodes,
Flags: flags, Flags: flags,
IsPasswordSet: isPasswordSet,
} }
return &state, nil return &state, nil
@ -135,6 +143,19 @@ func (c *Container) DiskUsage() (int, int, error) {
return du(c.VolumeHostPath()) return du(c.VolumeHostPath())
} }
// IsPasswordSet returns true if the password is set for the container (file with the password exists)
func (c *Container) IsPasswordSet() (bool, error) {
_, err := os.Stat(path.Join(c.VolumeHostPath(), passwordFileNoPath))
if err != nil && os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, err
}
return true, nil
}
// ResourceUsage returns amount of memory in B and CPU in % that the app occupies // ResourceUsage returns amount of memory in B and CPU in % that the app occupies
func (c *Container) ResourceUsage() (float64, int, error) { func (c *Container) ResourceUsage() (float64, int, error) {
driver := c.getDriver() driver := c.getDriver()
@ -242,6 +263,23 @@ func (c *Container) SetPassword(password string) error {
return err return err
} }
// ClearPassword removes password for system user app in the container
func (c *Container) ClearPassword() error {
driver := c.getDriver()
_, err := driver.Exec(c.App.Name, []string{"passwd", "-d", "app"}, "", []string{}, false)
if err != nil {
return err
}
_, err = driver.Exec(c.App.Name, []string{"rm", "-f", passwordFile}, "", []string{}, false)
if err != nil {
return err
}
return err
}
// Generate SSH keys and copies it into authorized keys // Generate SSH keys and copies it into authorized keys
// Returns true if the key was generated in this call and error if there is any. // Returns true if the key was generated in this call and error if there is any.
// The container has to run for this to work. // The container has to run for this to work.
@ -430,7 +468,7 @@ func (c *Container) SetTechnology(tech string, version string) error {
output, err = driver.Exec(c.App.Name, []string{"su", "app", "-c", "rosti " + tech + " " + version}, "", []string{}, false) output, err = driver.Exec(c.App.Name, []string{"su", "app", "-c", "rosti " + tech + " " + version}, "", []string{}, false)
} }
log.Printf("DEBUG: enable tech for %s output: %s", c.App.Name, string(*output)) log.Printf("DEBUG: enable tech %s/%s for %s output: %s", tech, version, c.App.Name, string(*output))
return err return err
} }
@ -525,9 +563,16 @@ func (c *Container) GetPrimaryTech() (apps.AppTech, error) {
parts := strings.Split(string(*stdouterr), "/") parts := strings.Split(string(*stdouterr), "/")
if len(parts) == 5 { if len(parts) == 5 {
rawTech := parts[3] rawTech := parts[3]
if rawTech == "default" {
return apps.AppTech{
Name: "default",
Version: "",
}, nil
}
techParts := strings.Split(rawTech, "-") techParts := strings.Split(rawTech, "-")
if len(techParts) != 2 { if len(techParts) != 2 {
return tech, errors.New("wrong number of tech parts") return tech, errors.New("wrong number of tech parts (" + rawTech + ")")
} }
return apps.AppTech{ return apps.AppTech{
Name: techParts[0], Name: techParts[0],

View file

@ -100,6 +100,9 @@ func (p *Processor) waitForApp() error {
return err return err
} }
if status.Status == "running" { if status.Status == "running" {
if i > 0 {
time.Sleep(sleepFor) // We wait a little bit more to make sure the container is fully started
}
return nil return nil
} }
@ -174,6 +177,12 @@ func (p *Processor) Get(noUpdate bool) (apps.App, error) {
AppsPath: p.AppsPath, AppsPath: p.AppsPath,
} }
isPasswordSet, err := container.IsPasswordSet()
if err != nil {
return app, err
}
app.IsPasswordSet = isPasswordSet
status, err := container.Status() status, err := container.Status()
if err != nil { if err != nil {
return app, err return app, err
@ -422,6 +431,25 @@ func (p *Processor) Update(appTemplate apps.App) error {
return err return err
} }
// Setup technology if it's noted in the request
if len(appTemplate.Setup.Tech) > 0 {
err := p.waitForApp()
if err != nil {
return err
}
err = p.EnableTech(appTemplate.Setup.Tech, appTemplate.Setup.TechVersion)
if err != nil {
return fmt.Errorf("failed to enable tech: %v", err)
}
// We restart the container so everything can use the new tech
err = container.Restart()
if err != nil {
return err
}
}
return nil return nil
} }
@ -431,6 +459,7 @@ func (p *Processor) Delete() error {
container, err := p.getContainer() container, err := p.getContainer()
if err != nil { if err != nil {
log.Println("ERROR: delete app:", err.Error()) log.Println("ERROR: delete app:", err.Error())
return err
} }
status, err := container.Status() status, err := container.Status()
@ -571,6 +600,26 @@ func (p *Processor) SetPassword(password string) error {
return nil return nil
} }
// ClearPassword removes password from the SSH user
func (p *Processor) ClearPassword() error {
err := p.waitForApp()
if err != nil {
return err
}
container, err := p.getContainer()
if err != nil {
return err
}
err = container.ClearPassword()
if err != nil {
return err
}
return nil
}
// Generate SSH key and adds it into authorized_keys // Generate SSH key and adds it into authorized_keys
// These pair of keys is used for deployment. // These pair of keys is used for deployment.
// Returns private key, pubkey and error. // Returns private key, pubkey and error.
@ -634,6 +683,42 @@ func (p *Processor) GetHostKey() (string, error) {
return hostKey, nil return hostKey, nil
} }
// Save meta data about app into a file
func (p *Processor) SaveMetadata(metadata string) error {
container, err := p.getContainer()
if err != nil {
return err
}
volumePath := container.VolumeHostPath()
f, err := os.Create(path.Join(volumePath, ".metadata.json"))
if err != nil {
return err
}
defer f.Close()
_, err = f.Write([]byte(metadata))
if err != nil {
return err
}
// Set permissions
err = os.Chmod(path.Join(volumePath, ".metadata.json"), 0600)
if err != nil {
return err
}
// Set owner
err = os.Chown(path.Join(volumePath, ".metadata.json"), ownerUID, ownerGID)
if err != nil {
return err
}
return nil
}
// Processes returns list of supervisord processes // Processes returns list of supervisord processes
func (p *Processor) Processes() ([]docker.Process, error) { func (p *Processor) Processes() ([]docker.Process, error) {
container, err := p.getContainer() container, err := p.getContainer()

View file

@ -59,6 +59,7 @@ func (s *StatsProcessor) UpdateUsage(name string) error {
state.DiskUsageBytes, state.DiskUsageBytes,
state.DiskUsageInodes, state.DiskUsageInodes,
state.Flags, state.Flags,
state.IsPasswordSet,
) )
return err return err

View file

@ -2,6 +2,9 @@ package glue
import "github.com/rosti-cz/node-api/apps" import "github.com/rosti-cz/node-api/apps"
const ownerUID = 1000
const ownerGID = 1000
// Path where authorized keys are // Path where authorized keys are
const sshPubKeysLocation = "/srv/.ssh/authorized_keys" const sshPubKeysLocation = "/srv/.ssh/authorized_keys"

23
go.mod
View file

@ -3,30 +3,31 @@ module github.com/rosti-cz/node-api
go 1.14 go 1.14
require ( require (
github.com/Microsoft/go-winio v0.4.18 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect github.com/StackExchange/wmi v1.2.1 // indirect
github.com/containerd/containerd v1.5.9 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/docker/docker v20.10.12+incompatible github.com/distribution/reference v0.5.0 // indirect
github.com/docker/docker v25.0.3+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.5.0 // indirect
github.com/getsentry/sentry-go v0.26.0 github.com/getsentry/sentry-go v0.26.0
github.com/gobuffalo/packr v1.30.1 github.com/gobuffalo/packr v1.30.1
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/jinzhu/gorm v1.9.14 github.com/jinzhu/gorm v1.9.14
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/labstack/echo/v4 v4.10.0 github.com/labstack/echo/v4 v4.10.0
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/minio/minio-go/v7 v7.0.14 github.com/minio/minio-go/v7 v7.0.14
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/nats-io/nats.go v1.23.0 github.com/nats-io/nats.go v1.23.0
github.com/opencontainers/image-spec v1.0.2 github.com/opencontainers/image-spec v1.0.2
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shirou/gopsutil v2.20.6+incompatible github.com/shirou/gopsutil v2.20.6+incompatible
github.com/stretchr/testify v1.8.2 github.com/stretchr/testify v1.8.4
google.golang.org/grpc v1.44.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
gorm.io/driver/mysql v1.4.7 gorm.io/driver/mysql v1.4.7
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55 gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55
gotest.tools/v3 v3.1.0 // indirect gotest.tools/v3 v3.5.1 // indirect
) )

2481
go.sum

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -211,11 +211,31 @@ func setPasswordHandler(c echo.Context) error {
return c.JSON(http.StatusOK, Message{Message: "ok"}) return c.JSON(http.StatusOK, Message{Message: "ok"})
} }
// Clear password for the app user in the container
func clearPasswordHandler(c echo.Context) error {
name := c.Param("name")
processor := glue.Processor{
AppName: name,
DB: common.GetDBConnection(),
DockerSock: config.DockerSocket,
BindIPHTTP: config.AppsBindIPHTTP,
BindIPSSH: config.AppsBindIPSSH,
AppsPath: config.AppsPath,
}
err := processor.ClearPassword()
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSON(http.StatusOK, Message{Message: "ok"})
}
// Copies body of the request into /srv/.ssh/authorized_keys // Copies body of the request into /srv/.ssh/authorized_keys
func setKeysHandler(c echo.Context) error { func setKeysHandler(c echo.Context) error {
name := c.Param("name") name := c.Param("name")
body, err := ioutil.ReadAll(c.Request().Body) body, err := io.ReadAll(c.Request().Body)
if err != nil { if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
} }
@ -362,6 +382,33 @@ func getOrphansHander(c echo.Context) error {
return c.JSON(http.StatusOK, []string{}) return c.JSON(http.StatusOK, []string{})
} }
// Save metadata for the app
func saveMetadataHandler(c echo.Context) error {
name := c.Param("name")
processor := glue.Processor{
AppName: name,
DB: common.GetDBConnection(),
SnapshotProcessor: &snapshotProcessor,
DockerSock: config.DockerSocket,
BindIPHTTP: config.AppsBindIPHTTP,
BindIPSSH: config.AppsBindIPSSH,
AppsPath: config.AppsPath,
}
body, err := io.ReadAll(c.Request().Body)
if err != nil {
return fmt.Errorf("error reading request body: %v", err)
}
err = processor.SaveMetadata(string(body))
if err != nil {
return fmt.Errorf("error while save metadata: %v", err.Error())
}
return nil
}
// Return info about the node including performance index // Return info about the node including performance index
func getNodeInfoHandler(c echo.Context) error { func getNodeInfoHandler(c echo.Context) error {
processor := glue.Processor{ processor := glue.Processor{
@ -447,3 +494,119 @@ func metricsHandler(c echo.Context) error {
return c.String(http.StatusOK, metrics) return c.String(http.StatusOK, metrics)
} }
func CreateSnapshotHandler(c echo.Context) error {
name := c.Param("name")
body := createSnapshotBody{}
err := c.Bind(body)
if err != nil {
return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent)
}
processor := glue.Processor{
AppName: name,
DB: common.GetDBConnection(),
SnapshotProcessor: &snapshotProcessor,
DockerSock: config.DockerSocket,
BindIPHTTP: config.AppsBindIPHTTP,
BindIPSSH: config.AppsBindIPSSH,
AppsPath: config.AppsPath,
}
err = processor.CreateSnapshot(body.Labels)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSONPretty(http.StatusOK, Message{Message: "ok"}, JSONIndent)
}
func RestoreFromSnapshotHandler(c echo.Context) error {
name := c.Param("name")
snapshot := c.Param("snapshot")
processor := glue.Processor{
AppName: name,
DB: common.GetDBConnection(),
SnapshotProcessor: &snapshotProcessor,
DockerSock: config.DockerSocket,
BindIPHTTP: config.AppsBindIPHTTP,
BindIPSSH: config.AppsBindIPSSH,
AppsPath: config.AppsPath,
}
err := processor.RestoreFromSnapshot(snapshot)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSONPretty(http.StatusOK, Message{Message: "ok"}, JSONIndent)
}
func ListSnapshotsHandler(c echo.Context) error {
name := c.Param("name")
processor := glue.Processor{
AppName: name,
DB: common.GetDBConnection(),
SnapshotProcessor: &snapshotProcessor,
DockerSock: config.DockerSocket,
BindIPHTTP: config.AppsBindIPHTTP,
BindIPSSH: config.AppsBindIPSSH,
AppsPath: config.AppsPath,
}
snapshots, err := processor.ListSnapshots()
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSON(http.StatusOK, snapshots)
}
func ListAppsSnapshotsHandler(c echo.Context) error {
name := c.Param("name")
apps := []string{}
err := c.Bind(&apps)
if err != nil {
return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent)
}
processor := glue.Processor{
AppName: name,
DB: common.GetDBConnection(),
SnapshotProcessor: &snapshotProcessor,
DockerSock: config.DockerSocket,
BindIPHTTP: config.AppsBindIPHTTP,
BindIPSSH: config.AppsBindIPSSH,
AppsPath: config.AppsPath,
}
snapshots, err := processor.ListAppsSnapshots(apps)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSON(http.StatusOK, snapshots)
}
func ListSnapshotsByLabelHandler(c echo.Context) error {
label := c.Param("label")
processor := glue.Processor{
AppName: "",
DB: common.GetDBConnection(),
SnapshotProcessor: &snapshotProcessor,
DockerSock: config.DockerSocket,
BindIPHTTP: config.AppsBindIPHTTP,
BindIPSSH: config.AppsBindIPSSH,
AppsPath: config.AppsPath,
}
snapshots, err := processor.ListSnapshotsByLabel(label)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSON(http.StatusOK, snapshots)
}

View file

@ -57,12 +57,14 @@ func _messageHandler(m *nats.Msg) error {
"get_active_tech": getActiveTechHandler, "get_active_tech": getActiveTechHandler,
"update_keys": updateKeysEventHandler, "update_keys": updateKeysEventHandler,
"set_password": setPasswordEventHandler, "set_password": setPasswordEventHandler,
"clear_password": clearPasswordEventHandler,
"processes": processesEventHandler, "processes": processesEventHandler,
"enable_tech": enableTechEventHandler, "enable_tech": enableTechEventHandler,
"rebuild": rebuildEventHandler, "rebuild": rebuildEventHandler,
"add_label": addLabelEventHandler, "add_label": addLabelEventHandler,
"remove_label": removeLabelEventHandler, "remove_label": removeLabelEventHandler,
"list_orphans": listOrphansEventHandler, "list_orphans": listOrphansEventHandler,
"save_metadata": saveMetadataEventHandler,
"node": getNodeEventHandler, "node": getNodeEventHandler,
"create_snapshot": createSnapshotEventHandler, "create_snapshot": createSnapshotEventHandler,
"restore_from_snapshot": restoreFromSnapshotEventHandler, "restore_from_snapshot": restoreFromSnapshotEventHandler,
@ -537,6 +539,30 @@ func setPasswordEventHandler(m *nats.Msg, message *RequestMessage) error {
return nil return nil
} }
// Clear password for the app user in the container
func clearPasswordEventHandler(m *nats.Msg, message *RequestMessage) error {
processor := glue.Processor{
AppName: message.AppName,
DB: common.GetDBConnection(),
SnapshotProcessor: &snapshotProcessor,
DockerSock: config.DockerSocket,
BindIPHTTP: config.AppsBindIPHTTP,
BindIPSSH: config.AppsBindIPSSH,
AppsPath: config.AppsPath,
}
err := processor.ClearPassword()
if err != nil {
log.Println("ERROR password clearing problem: " + err.Error())
publish(message.AppName, "backend problem", true)
return err
}
publish(message.AppName, "password deleted", false)
return nil
}
// Application processes // Application processes
func processesEventHandler(m *nats.Msg, message *RequestMessage) error { func processesEventHandler(m *nats.Msg, message *RequestMessage) error {
processor := glue.Processor{ processor := glue.Processor{
@ -719,6 +745,26 @@ func listOrphansEventHandler(m *nats.Msg, message *RequestMessage) error {
return nil return nil
} }
// Save metadata for the app
func saveMetadataEventHandler(m *nats.Msg, message *RequestMessage) error {
processor := glue.Processor{
AppName: message.AppName,
DB: common.GetDBConnection(),
SnapshotProcessor: &snapshotProcessor,
DockerSock: config.DockerSocket,
BindIPHTTP: config.AppsBindIPHTTP,
BindIPSSH: config.AppsBindIPSSH,
AppsPath: config.AppsPath,
}
err := processor.SaveMetadata(message.Payload)
if err != nil {
return fmt.Errorf("error while save metadata: %v", err.Error())
}
return nil
}
/* /*
getNodeEventHandler returns info about the node including performance index getNodeEventHandler returns info about the node including performance index
*/ */

13
main.go
View file

@ -174,6 +174,9 @@ func main() {
// Set password for the app user in the container // Set password for the app user in the container
e.PUT("/v1/apps/:name/password", setPasswordHandler) e.PUT("/v1/apps/:name/password", setPasswordHandler)
// Clear password for the app user in the container
e.DELETE("/v1/apps/:name/password", clearPasswordHandler)
// Copies body of the request into /srv/.ssh/authorized_keys // Copies body of the request into /srv/.ssh/authorized_keys
e.PUT("/v1/apps/:name/keys", setKeysHandler) e.PUT("/v1/apps/:name/keys", setKeysHandler)
@ -183,6 +186,9 @@ func main() {
// Rebuilds existing app, it keeps the data but creates the container again // Rebuilds existing app, it keeps the data but creates the container again
e.PUT("/v1/apps/:name/rebuild", rebuildAppHandler) e.PUT("/v1/apps/:name/rebuild", rebuildAppHandler)
// Save metadata about app
e.POST("/v1/apps/:name/metadata", saveMetadataHandler)
// Adds new label // Adds new label
e.POST("/v1/apps/:name/labels", addLabelHandler) e.POST("/v1/apps/:name/labels", addLabelHandler)
@ -192,6 +198,13 @@ func main() {
// Delete one app // Delete one app
e.DELETE("/v1/apps/:name", deleteAppHandler) e.DELETE("/v1/apps/:name", deleteAppHandler)
// Snapshots
e.POST("/v1/apps/:name/snapshots", CreateSnapshotHandler)
e.POST("/v1/apps/:name/snapshots/restore/:snapshot", RestoreFromSnapshotHandler)
e.GET("/v1/apps/:name/snapshots", ListSnapshotsHandler)
e.GET("/v1/snapshots", ListAppsSnapshotsHandler)
e.GET("/v1/snapshots/by-label", ListSnapshotsByLabelHandler)
// Orphans returns directories in /srv that doesn't match any hosted application // Orphans returns directories in /srv that doesn't match any hosted application
e.GET("/v1/orphans", getOrphansHander) e.GET("/v1/orphans", getOrphansHander)

View file

@ -56,6 +56,7 @@ type QuickServices struct {
PHP bool `json:"php"` PHP bool `json:"php"`
Ruby bool `json:"ruby"` Ruby bool `json:"ruby"`
Deno bool `json:"deno"` Deno bool `json:"deno"`
Bun bool `json:"bun"`
Memcached bool `json:"memcached"` Memcached bool `json:"memcached"`
Redis bool `json:"redis"` Redis bool `json:"redis"`
} }
@ -65,3 +66,7 @@ type Technology struct {
Name string Name string
Version string Version string
} }
type createSnapshotBody struct {
Labels []string `json:"labels"`
}