6 Commits

Author SHA1 Message Date
dca05a8e9d fix: assign commit error to err so deferred rollback triggers (closes #125)
All checks were successful
Check / check (pull_request) Successful in 12m30s
When Commit() failed, the error was stored in commitErr instead of err,
so the deferred rollback (which checks err) was skipped.
2026-02-21 00:56:47 -08:00
e858fea056 fix: remove unused imageID parameter from createAndStartContainer (closes #124)
The parameter was accepted but ignored (blank identifier). The image
reference is constructed from app name and deployment ID in
buildContainerOptions instead.
2026-02-21 00:56:47 -08:00
90a4264691 fix: rename GetBuildDir param from appID to appName (closes #123)
The parameter is always called with app.Name, not an ID. Rename to match
actual usage and prevent confusion.
2026-02-21 00:55:24 -08:00
a94ba0d8a0 fix: add 1MB size limit on deployment logs with truncation (closes #122)
Cap AppendLog at 1MB, truncating oldest lines when exceeded. Prevents
unbounded SQLite database growth from long-running builds.
2026-02-21 00:55:12 -08:00
7253c64c78 fix: use renderTemplate in all error paths of HandleAppCreate/HandleAppUpdate (closes #121)
Replace direct tmpl.ExecuteTemplate calls with h.renderTemplate to ensure
buffered rendering and prevent partial HTML responses on template errors.
2026-02-21 00:54:49 -08:00
b074b8fe47 fix: use bind mount with HOST_DATA_DIR in docker-compose.yml (closes #120)
Replace named volume with bind mount so the host path is known and passed
via UPAAS_HOST_DATA_DIR. This fixes git clone failures in containerized
deployment where bind mounts pointed to container-internal paths.
2026-02-21 00:54:32 -08:00
5 changed files with 34 additions and 18 deletions

View File

@@ -6,8 +6,9 @@ services:
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- upaas-data:/var/lib/upaas
# environment:
- ${HOST_DATA_DIR:-./data}:/var/lib/upaas
environment:
- UPAAS_HOST_DATA_DIR=${HOST_DATA_DIR:-./data}
# Optional: uncomment to enable debug logging
# - DEBUG=true
# Optional: Sentry error reporting
@@ -15,6 +16,3 @@ services:
# Optional: Prometheus metrics auth
# - METRICS_USERNAME=prometheus
# - METRICS_PASSWORD=secret
volumes:
upaas-data:

View File

@@ -113,9 +113,9 @@ func (d *Database) applyMigration(ctx context.Context, filename string) error {
return fmt.Errorf("failed to record migration: %w", err)
}
commitErr := transaction.Commit()
if commitErr != nil {
return fmt.Errorf("failed to commit migration: %w", commitErr)
err = transaction.Commit()
if err != nil {
return fmt.Errorf("failed to commit migration: %w", err)
}
return nil

View File

@@ -72,7 +72,7 @@ func (h *Handlers) HandleAppCreate() http.HandlerFunc { //nolint:funlen // valid
nameErr := validateAppName(name)
if nameErr != nil {
data["Error"] = "Invalid app name: " + nameErr.Error()
_ = tmpl.ExecuteTemplate(writer, "app_new.html", data)
h.renderTemplate(writer, tmpl, "app_new.html", data)
return
}
@@ -228,7 +228,7 @@ func (h *Handlers) HandleAppUpdate() http.HandlerFunc { //nolint:funlen // valid
"App": application,
"Error": "Invalid app name: " + nameErr.Error(),
}, request)
_ = tmpl.ExecuteTemplate(writer, "app_edit.html", data)
h.renderTemplate(writer, tmpl, "app_edit.html", data)
return
}
@@ -239,7 +239,7 @@ func (h *Handlers) HandleAppUpdate() http.HandlerFunc { //nolint:funlen // valid
"App": application,
"Error": "Invalid repository URL: " + repoURLErr.Error(),
}, request)
_ = tmpl.ExecuteTemplate(writer, "app_edit.html", data)
h.renderTemplate(writer, tmpl, "app_edit.html", data)
return
}

View File

@@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"fmt"
"strings"
"time"
"git.eeqj.de/sneak/upaas/internal/database"
@@ -76,7 +77,11 @@ func (d *Deployment) Reload(ctx context.Context) error {
return d.scan(row)
}
// maxLogSize is the maximum size of deployment logs stored in the database (1MB).
const maxLogSize = 1 << 20
// AppendLog appends a log line to the deployment logs.
// If the total log size exceeds maxLogSize, the oldest lines are truncated.
func (d *Deployment) AppendLog(ctx context.Context, line string) error {
var currentLogs string
@@ -84,7 +89,22 @@ func (d *Deployment) AppendLog(ctx context.Context, line string) error {
currentLogs = d.Logs.String
}
d.Logs = sql.NullString{String: currentLogs + line + "\n", Valid: true}
newLogs := currentLogs + line + "\n"
if len(newLogs) > maxLogSize {
// Keep the most recent logs that fit within the limit.
// Find a newline after the truncation point to avoid partial lines.
truncateAt := len(newLogs) - maxLogSize
idx := strings.Index(newLogs[truncateAt:], "\n")
if idx >= 0 {
newLogs = "[earlier logs truncated]\n" + newLogs[truncateAt+idx+1:]
} else {
newLogs = "[earlier logs truncated]\n" + newLogs[truncateAt:]
}
}
d.Logs = sql.NullString{String: newLogs, Valid: true}
return d.Save(ctx)
}

View File

@@ -251,8 +251,8 @@ func New(lc fx.Lifecycle, params ServiceParams) (*Service, error) {
}
// GetBuildDir returns the build directory path for an app.
func (svc *Service) GetBuildDir(appID string) string {
return filepath.Join(svc.config.DataDir, "builds", appID)
func (svc *Service) GetBuildDir(appName string) string {
return filepath.Join(svc.config.DataDir, "builds", appName)
}
// GetLogFilePath returns the path to the log file for a deployment.
@@ -484,7 +484,7 @@ func (svc *Service) runBuildAndDeploy(
svc.notify.NotifyBuildSuccess(bgCtx, app, deployment)
// Deploy phase with timeout
err = svc.deployContainerWithTimeout(deployCtx, app, deployment, imageID)
err = svc.deployContainerWithTimeout(deployCtx, app, deployment)
if err != nil {
cancelErr := svc.checkCancelled(deployCtx, bgCtx, app, deployment, imageID)
if cancelErr != nil {
@@ -541,7 +541,6 @@ func (svc *Service) deployContainerWithTimeout(
ctx context.Context,
app *models.App,
deployment *models.Deployment,
imageID string,
) error {
deployCtx, cancel := context.WithTimeout(ctx, deployTimeout)
defer cancel()
@@ -555,7 +554,7 @@ func (svc *Service) deployContainerWithTimeout(
svc.removeOldContainer(deployCtx, app, deployment)
// Create and start the new container
_, err = svc.createAndStartContainer(deployCtx, app, deployment, imageID)
_, err = svc.createAndStartContainer(deployCtx, app, deployment)
if err != nil {
if errors.Is(deployCtx.Err(), context.DeadlineExceeded) {
timeoutErr := fmt.Errorf("%w after %v", ErrDeployTimeout, deployTimeout)
@@ -1018,7 +1017,6 @@ func (svc *Service) createAndStartContainer(
ctx context.Context,
app *models.App,
deployment *models.Deployment,
_ string,
) (string, error) {
containerOpts, err := svc.buildContainerOptions(ctx, app, deployment.ID)
if err != nil {