Skip to content

Commit 8bf2fee

Browse files
authored
Merge pull request #39 from coderbuzz/feat/cleanup-old-server-on-app-move
feat: cleanup old server when app is moved to a different server
2 parents 322ca22 + af647c2 commit 8bf2fee

6 files changed

Lines changed: 96 additions & 8 deletions

File tree

‎internal/app/handler.go‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/http"
7+
"net/url"
78
"sort"
89
"strconv"
910
"strings"
@@ -573,6 +574,7 @@ func (h *WebHandler) AppEditForm(w http.ResponseWriter, r *http.Request, render
573574
render(w, r, http.StatusNotFound, "error.html", map[string]interface{}{"Message": "app not found"})
574575
return
575576
}
577+
oldServerID := app.ServerID
576578
if err := r.ParseForm(); err != nil {
577579
servers, _ := h.serverRepo.List()
578580
render(w, r, http.StatusBadRequest, "apps_add.html", map[string]interface{}{
@@ -655,6 +657,28 @@ func (h *WebHandler) AppEditForm(w http.ResponseWriter, r *http.Request, render
655657
return
656658
}
657659

660+
var flashMsg string
661+
if oldServerID != 0 && oldServerID != serverID {
662+
oldServerName := fmt.Sprintf("#%d", oldServerID)
663+
newServerName := fmt.Sprintf("#%d", serverID)
664+
if servers, err := h.serverRepo.List(); err == nil {
665+
for _, s := range servers {
666+
if s.ID == oldServerID {
667+
oldServerName = s.Name
668+
}
669+
if s.ID == serverID {
670+
newServerName = s.Name
671+
}
672+
}
673+
}
674+
h.service.DeleteRoutes(app.ID)
675+
go h.service.CleanupFromServer(id, oldServerID)
676+
flashMsg = url.QueryEscape(fmt.Sprintf(
677+
"App moved from %s to %s. Containers stopped on %s, but app folder remains — check and delete manually if no longer needed.",
678+
oldServerName, newServerName, oldServerName,
679+
))
680+
}
681+
658682
saveFormSecrets(r, h.service, id)
659683
saveFormFiles(r, h.service, id)
660684

@@ -704,7 +728,11 @@ func (h *WebHandler) AppEditForm(w http.ResponseWriter, r *http.Request, render
704728

705729
go h.service.Redeploy(id, removedDomains...)
706730

707-
http.Redirect(w, r, "/apps/"+strconv.FormatInt(id, 10), http.StatusSeeOther)
731+
redirectURL := "/apps/" + strconv.FormatInt(id, 10)
732+
if flashMsg != "" {
733+
redirectURL += "?flash=" + flashMsg
734+
}
735+
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
708736
}
709737

710738
func (h *WebHandler) AppDetailPage(w http.ResponseWriter, r *http.Request, render RenderFunc) {
@@ -759,6 +787,7 @@ func (h *WebHandler) AppDetailPage(w http.ResponseWriter, r *http.Request, rende
759787
"Files": files,
760788
"Routes": routes,
761789
"Domains": domains,
790+
"Flash": r.URL.Query().Get("flash"),
762791
"DomainCount": len(domains),
763792
"ExtraDomainCount": len(domains) - 1,
764793
})

‎internal/app/service.go‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ func (s *Service) Delete(id int64) error {
9696
return s.repo.Delete(id)
9797
}
9898

99+
func (s *Service) DeleteRoutes(appID int64) error {
100+
return s.repo.DeleteRoutes(appID)
101+
}
102+
99103
func (s *Service) SaveRoute(route *Route) error {
100104
return s.repo.SaveRoute(route)
101105
}
@@ -343,6 +347,49 @@ func (s *Service) Undeploy(id int64) error {
343347
return nil
344348
}
345349

350+
// CleanupFromServer stops containers and removes Caddy routes from a server
351+
// without deleting the app folder or DB rows. Used when moving an app to
352+
// a different server.
353+
func (s *Service) CleanupFromServer(appID, serverID int64) {
354+
app, err := s.repo.Get(appID)
355+
if err != nil || app == nil {
356+
log.Printf("CleanupFromServer: app %d not found", appID)
357+
return
358+
}
359+
360+
svr, err := s.serverRepo.Get(serverID)
361+
if err != nil || svr == nil {
362+
log.Printf("CleanupFromServer %q: server %d not found, skipping", app.Name, serverID)
363+
return
364+
}
365+
366+
client, err := s.connFactory(svr.Host, svr.Port, svr.User, svr.SSHKey)
367+
if err != nil {
368+
log.Printf("CleanupFromServer %q: SSH connect to %s failed, skipping: %v", app.Name, svr.Name, err)
369+
return
370+
}
371+
defer client.Close()
372+
373+
dc := DockerComposeCmd(client)
374+
remoteDir := fmt.Sprintf("/opt/dockify/apps/app-%d", app.ID)
375+
composePath := fmt.Sprintf("%s/docker-compose.yml", remoteDir)
376+
377+
log.Printf("Cleaning up %q from %s (containers stopped, folder kept)...", app.Name, svr.Name)
378+
379+
client.Exec(fmt.Sprintf("%s -f %s down 2>&1 || true", dc, composePath))
380+
381+
routes, _ := s.repo.GetRoutes(app.ID)
382+
for _, r := range routes {
383+
caddyClient := caddy.NewClient(client)
384+
caddyClient.RemoveRoute(r.Domain)
385+
}
386+
if len(routes) > 0 {
387+
caddy.NewClient(client).SaveConfig()
388+
}
389+
390+
log.Printf("App %q cleaned up from %s (folder preserved at %s)", app.Name, svr.Name, remoteDir)
391+
}
392+
346393
func (s *Service) setupRouteAndDNSForDomain(route Route, app *App, svr *server.Server, client ssh.Connector, composeContent string, logs *[]string) {
347394
target := fmt.Sprintf("%s:%d", getServiceName(composeContent), app.Port)
348395
caddyClient := caddy.NewClient(client)

‎internal/caddy/client.go‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (c *Client) AddRouteWithAuth(domain, target, user, pass string) error {
7777
}
7878

7979
func (c *Client) postRoute(route Route) error {
80-
// Hapus route lama dengan ID yang sama (kalau ada, ignore error)
80+
// Remove old route with same ID (if any, ignore error)
8181
c.ssh.Exec(fmt.Sprintf(
8282
`docker exec caddy curl -s -o /dev/null -X DELETE http://localhost:2019/id/%s`,
8383
route.ID,
@@ -88,8 +88,8 @@ func (c *Client) postRoute(route Route) error {
8888
return fmt.Errorf("marshal route: %w", err)
8989
}
9090

91-
// Pastikan routes array ada (bisa null di Caddy fresh). POST [] hanya
92-
// berhasil kalau routes null; kalau sudah ada, gagal harmless tanpa side effect.
91+
// Ensure routes array exists (may be null on fresh Caddy). POST [] only
92+
// succeeds if routes is null; if already populated, fails harmlessly.
9393
c.ssh.Exec(`docker exec caddy curl -s -o /dev/null -X POST http://localhost:2019/config/apps/http/servers/srv0/routes -H 'Content-Type: application/json' -d '[]'`)
9494

9595
cmd := fmt.Sprintf(

‎internal/db/db.go‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,17 @@ func Open(path string) (*sql.DB, error) {
3636
return nil, fmt.Errorf("migrate: %w", err)
3737
}
3838

39-
// Migrasi: hapus kolom webhook_secret dari apps (global settings sekarang)
39+
// Migration: drop webhook_secret column from apps (global settings now)
4040
db.Exec("ALTER TABLE apps DROP COLUMN webhook_secret")
4141

42-
// Migrasi: tambah kolom disk_usage (v0.4.0)
42+
// Migration: add disk_usage column (v0.4.0)
4343
db.Exec("ALTER TABLE servers ADD COLUMN disk_usage REAL")
4444
db.Exec("ALTER TABLE servers ADD COLUMN resources_updated_at DATETIME")
4545

46-
// Migrasi: tambah kolom unique_service_name (v0.3.0)
46+
// Migration: add unique_service_name column (v0.3.0)
4747
db.Exec("ALTER TABLE apps ADD COLUMN unique_service_name INTEGER DEFAULT 0")
4848

49-
// Migrasi: tambah kolom compose_mode, gantikan unique_service_name (v0.5.0)
49+
// Migration: add compose_mode column, replaces unique_service_name (v0.5.0)
5050
db.Exec("ALTER TABLE apps ADD COLUMN compose_mode TEXT DEFAULT 'advanced'")
5151
db.Exec("UPDATE apps SET compose_mode = 'simple' WHERE unique_service_name = 1")
5252

‎internal/http/templates/apps_detail.html‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<body>
55
{{template "nav" .}}
66
<div class="container">
7+
{{if .Flash}}<div class="flash-banner">{{.Flash}}</div>{{end}}
78
<div class="breadcrumb">
89
<a href="apps">Apps</a>
910
<span>/</span>

‎internal/http/templates/layout.html‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,17 @@
280280
text-transform: uppercase;
281281
}
282282

283+
/* Flash messages */
284+
.flash-banner {
285+
background: var(--blue);
286+
color: #fff;
287+
padding: 0.6em 1em;
288+
border-radius: 4px;
289+
margin-bottom: 1em;
290+
font-size: 0.85em;
291+
line-height: 1.4;
292+
}
293+
283294
@media (max-width: 600px) {
284295
.container { padding: 0.8em 1em; }
285296
.grid { grid-template-columns: 1fr; }

0 commit comments

Comments
 (0)