chore: fix upload error visibility + install.sh update mode ownership bug

This commit is contained in:
Claus Lohmar 2026-06-17 11:27:33 +00:00
parent 422aaa08ab
commit 290f803996
2 changed files with 35 additions and 10 deletions

View file

@ -73,9 +73,23 @@ if [ "${1:-}" = "-update" ]; then
cd "$INSTALL_DIR"
fi
# Determine the app user from the existing service file (most reliable).
APP_USER=""
if [ -f "/etc/systemd/system/${SERVICE_NAME}.service" ]; then
APP_USER=$(grep -Po '^User=\K.*' "/etc/systemd/system/${SERVICE_NAME}.service" 2>/dev/null || true)
fi
# Fallback: check the owner of the install directory or binary.
if [ -z "$APP_USER" ]; then
APP_USER=$(stat -c '%U' "$INSTALL_DIR/app" 2>/dev/null || stat -c '%U' "$INSTALL_DIR" 2>/dev/null || true)
fi
if [ -z "$APP_USER" ] || [ "$APP_USER" = "root" ]; then
APP_USER="${SUDO_USER:-$(whoami)}"
fi
# Reset ownership so the app user can write files.
APP_USER="${APP_USER:-${SUDO_USER:-$(whoami)}}"
sudo_if chown -R "${APP_USER}:${APP_USER}" "$INSTALL_DIR" 2>/dev/null || true
# Ensure key directories have correct permissions.
sudo_if chmod 755 "$INSTALL_DIR" "$INSTALL_DIR/templates" "$INSTALL_DIR/static" 2>/dev/null || true
sudo_if chmod 775 "$INSTALL_DIR/storage" 2>/dev/null || true
# Backup the database before making any changes.
if [ -f "$INSTALL_DIR/expenses.db" ]; then
@ -118,12 +132,12 @@ if [ "${1:-}" = "-update" ]; then
export GOPATH="${INSTALL_DIR}/.go"
export GOCACHE="${INSTALL_DIR}/.go/build"
mkdir -p "${GOMODCACHE}" "${GOCACHE}" 2>/dev/null || sudo_if mkdir -p "${GOMODCACHE}" "${GOCACHE}"
sudo_if chown -R "${APP_USER:-${SUDO_USER:-$(whoami)}}" "${INSTALL_DIR}/.go" "${INSTALL_DIR}/app" 2>/dev/null || true
sudo_if chown -R "${APP_USER}" "${INSTALL_DIR}/.go" "${INSTALL_DIR}/app" 2>/dev/null || true
sudo_if rm -f app
CGO_ENABLED=0 go build -buildvcs=false -ldflags="-s -w" -o app .
# Fix ownership of all files (build cache may be root-owned from earlier builds).
sudo_if chown -R "${APP_USER:-${SUDO_USER:-$(whoami)}}:${APP_USER:-${SUDO_USER:-$(whoami)}}" "$INSTALL_DIR" 2>/dev/null || true
sudo_if chown -R "${APP_USER}:${APP_USER}" "$INSTALL_DIR" 2>/dev/null || true
info "Starting service..."
sudo_if systemctl start "$SERVICE_NAME"

View file

@ -7,6 +7,7 @@ package handlers
import (
"database/sql"
"fmt"
"html/template"
"io"
"log"
"net/http"
@ -59,7 +60,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(10 << 20); err != nil {
log.Printf("ERROR [%s] handlers: UploadReceipt: parse form: %v",
time.Now().Format(time.RFC3339), err)
http.Error(w, "Failed to parse upload form", http.StatusBadRequest)
renderUploadError(w, "Failed to parse upload form.")
return
}
defer r.MultipartForm.RemoveAll()
@ -69,7 +70,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
if err != nil {
log.Printf("ERROR [%s] handlers: UploadReceipt: missing receipt field: %v",
time.Now().Format(time.RFC3339), err)
http.Error(w, "Missing receipt file", http.StatusBadRequest)
renderUploadError(w, "Missing receipt file.")
return
}
defer file.Close()
@ -78,7 +79,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
if header.Size > 10<<20 {
log.Printf("ERROR [%s] handlers: UploadReceipt: file too large: %d bytes",
time.Now().Format(time.RFC3339), header.Size)
http.Error(w, "File too large. Maximum size is 10 MB.", http.StatusBadRequest)
renderUploadError(w, "File too large. Maximum size is 10 MB.")
return
}
@ -87,7 +88,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
if err != nil {
log.Printf("ERROR [%s] handlers: UploadReceipt: read file: %v",
time.Now().Format(time.RFC3339), err)
http.Error(w, "Failed to read uploaded file", http.StatusInternalServerError)
renderUploadError(w, "Failed to read uploaded file.")
return
}
@ -96,7 +97,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
if ext == "" {
log.Printf("ERROR [%s] handlers: UploadReceipt: unsupported file type: %q",
time.Now().Format(time.RFC3339), ext)
http.Error(w, "Unsupported file format. Please upload a receipt image (JPEG, PNG, HEIC) or PDF.", http.StatusBadRequest)
renderUploadError(w, "Unsupported file format. Please upload a receipt image (JPEG, PNG, HEIC) or PDF.")
return
}
@ -107,7 +108,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
if err := os.MkdirAll("storage", 0755); err != nil {
log.Printf("ERROR [%s] handlers: UploadReceipt: mkdir storage: %v",
time.Now().Format(time.RFC3339), err)
http.Error(w, "Server error", http.StatusInternalServerError)
renderUploadError(w, "Server error. Please try again.")
return
}
@ -115,7 +116,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
if err := os.WriteFile(storagePath, fileData, 0644); err != nil {
log.Printf("ERROR [%s] handlers: UploadReceipt: write file: %v",
time.Now().Format(time.RFC3339), err)
http.Error(w, "Failed to save receipt image", http.StatusInternalServerError)
renderUploadError(w, "Failed to save receipt image. Please try again.")
return
}
@ -586,6 +587,16 @@ func setCurrentEventID(w http.ResponseWriter, eventID string) {
// Helpers
// ---------------------------------------------------------------------------
// renderUploadError writes an HTMX-compatible error fragment into the
// #receipt-form container (the upload target). Uses HTTP 200 so HTMX
// always swaps the content (HTMX skips 4xx/5xx by default).
func renderUploadError(w http.ResponseWriter, message string) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `<div id="receipt-form"><div class="error-message" style="background: #450a0a; border: 1px solid #7f1d1d; color: #fca5a5; padding: 0.75rem; border-radius: 0.5rem; margin-bottom: 1rem;">%s</div></div>`,
template.HTMLEscapeString(message))
}
// detectImageExtension examines the magic bytes of the provided data to
// determine its image format. Supports JPEG, PNG, WebP, GIF, BMP, TIFF,
// and HEIC/HEIF (common on iPhones). Returns the file extension (without