diff --git a/Dockerfile b/Dockerfile index 9563b70..c0bfc80 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -ARG V_GOLANG -ARG V_NODE -ARG V_ALPINE -FROM golang:${V_GOLANG}-alpine AS goBuilder +ARG V_GOLANG=1.23 +ARG V_NODE=20 +ARG V_ALPINE=3 +FROM golang:${V_GOLANG}-alpine AS golang WORKDIR /app RUN go install github.com/a-h/templ/cmd/templ@latest @@ -14,7 +14,7 @@ COPY . . RUN templ generate RUN go build -ldflags="-s -w" -o godash main.go -FROM node:${V_NODE}-alpine AS nodeBuilder +FROM node:${V_NODE}-alpine AS node WORKDIR /app COPY package.json yarn.lock ./ @@ -32,22 +32,25 @@ RUN apk add figlet RUN figlet GoDash > logo.txt FROM alpine:${V_ALPINE} AS final +RUN apk --no-cache add tzdata ca-certificates dumb-init && \ + rm -rf /tmp/* /var/tmp/* /usr/share/man /var/cache/apk/* + +RUN addgroup -S appgroup && adduser -S appuser -G appgroup + WORKDIR /app -RUN apk add tzdata - -COPY scripts/entrypoint.sh . - COPY assets/favicon ./assets/favicon COPY --from=logo /app/logo.txt . -COPY --from=nodeBuilder /app/assets/css/style.css ./assets/css/style.css -COPY --from=nodeBuilder /app/node_modules/simple-icons/icons ./node_modules/simple-icons/icons -COPY --from=nodeBuilder /app/node_modules/simple-icons/_data ./node_modules/simple-icons/_data -COPY --from=goBuilder /app/views ./views -COPY --from=goBuilder /app/components ./components -COPY --from=goBuilder /app/godash . +COPY --from=node /app/assets/css/style.css ./assets/css/style.css +COPY --from=golang /app/views ./views +COPY --from=golang /app/components ./components +COPY --from=golang /app/godash . ARG APP_VERSION ENV APP_VERSION=$APP_VERSION -ENTRYPOINT ["/app/entrypoint.sh"] +RUN chown -R appuser:appgroup /app + +ENTRYPOINT ["dumb-init", "--"] +USER appuser +CMD ["/app/godash"] diff --git a/go.mod b/go.mod index 5a40b46..adf1768 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/go-playground/validator/v10 v10.22.1 github.com/gorilla/securecookie v1.1.2 github.com/gorilla/sessions v1.4.0 + github.com/lmittmann/tint v1.0.5 github.com/r3labs/sse/v2 v2.10.0 github.com/shirou/gopsutil/v4 v4.24.8 github.com/thanhpk/randstr v1.0.6 diff --git a/go.sum b/go.sum index 9f6aac6..e97f142 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw= +github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..fa8c47d --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,18 @@ +package logger + +import ( + "log/slog" + "os" + "time" + + "github.com/lmittmann/tint" +) + +func NewLogger() *slog.Logger { + w := os.Stderr + + return slog.New(tint.NewHandler(w, &tint.Options{ + Level: slog.LevelDebug, + TimeFormat: time.Kitchen, + })) +} diff --git a/main.go b/main.go index f241f43..1c927f9 100644 --- a/main.go +++ b/main.go @@ -14,10 +14,13 @@ import ( "gitlab.unjx.de/flohoss/godash/handlers" "gitlab.unjx.de/flohoss/godash/internal/env" + "gitlab.unjx.de/flohoss/godash/internal/logger" "gitlab.unjx.de/flohoss/godash/services" ) func main() { + slog.SetDefault(logger.NewLogger()) + env, err := env.Parse() if err != nil { slog.Error("cannot parse environment variables", "err", err) diff --git a/package.json b/package.json index bf8c108..bdde0c6 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,5 @@ "@iconify/tailwind": "^1.0.1", "daisyui": "^4.7.3", "tailwindcss": "^3.4.1" - }, - "dependencies": { - "simple-icons": "^13.6.0" } } diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh deleted file mode 100755 index be9bcec..0000000 --- a/scripts/entrypoint.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh - -cat logo.txt -CMD=./godash - -if [ -n "$PUID" ] || [ -n "$PGID" ]; then - USER=appuser - HOME=/app - - if ! grep -q "$USER" /etc/passwd; then - # Usage: addgroup [-g GID] [-S] [USER] GROUP - # - # Add a group or add a user to a group - # -g GID Group id - addgroup -g "$PGID" "$USER" - - # Usage: adduser [OPTIONS] USER [GROUP] - # Create new user, or add USER to GROUP - # -h DIR Home directory - # -g GECOS GECOS field - # -G GRP Group - # -D Don't assign a password - # -H Don't create home directory - # -u UID User id - adduser -h "$HOME" -g "" -G "$USER" -D -H -u "$PUID" "$USER" - fi - - chown "$USER":"$USER" "$HOME" -R - printf "\nUID: %s GID: %s\n\n" "$PUID" "$PGID" - exec su -c - $USER "$CMD" -else - printf "\nWARNING: Running docker as root\n\n" - exec "$CMD" -fi diff --git a/services/bookmark.services.go b/services/bookmark.services.go index 3d6f912..ba8dcb6 100644 --- a/services/bookmark.services.go +++ b/services/bookmark.services.go @@ -1,9 +1,10 @@ package services import ( - "encoding/json" "io" + "io/fs" "log/slog" + "net/http" "os" "path/filepath" "strings" @@ -11,8 +12,6 @@ import ( "gopkg.in/yaml.v3" ) -const simpleIconsFolder = "node_modules/simple-icons/icons/" -const simpleIconsInfo = "node_modules/simple-icons/_data/simple-icons.json" const storageFolder = "storage/" const iconsFolder = storageFolder + "icons/" const bookmarkFile = storageFolder + "bookmarks.yaml" @@ -26,11 +25,11 @@ applications: - category: "Code" entries: - name: "GitHub" - icon: "si/github.svg" + icon: "shi/github.svg" ignore_color: true url: "https://github.com" - name: "Home Assistant" - icon: "si/homeassistant.svg" + icon: "shi/homeassistant.svg" url: "https://www.home-assistant.io/"` func init() { @@ -48,7 +47,7 @@ func init() { func NewBookmarkService() *BookmarkService { bs := BookmarkService{} bs.parseBookmarks() - bs.parseIcons() + bs.replaceIconStrings() return &bs } @@ -84,41 +83,51 @@ func (bs *BookmarkService) readBookmarksFile() []byte { return byteValue } -func (bs *BookmarkService) replaceIconString() { - iconsByTitle := make(map[string]string) - for _, icon := range bs.SimpleIcons.Icons { - iconsByTitle[icon.Title] = icon.Hex - } - +func (bs *BookmarkService) replaceIconStrings() { for i, v := range bs.bookmarks.Applications { for j, bookmark := range v.Entries { - if filepath.Ext(bookmark.Icon) == ".svg" { - var data []byte - var err error - if strings.HasPrefix(bookmark.Icon, "si/") { - title := strings.Replace(bookmark.Icon, "si/", "", 1) - data, err = os.ReadFile(simpleIconsFolder + title) + ext := filepath.Ext(bookmark.Icon) + if ext != ".svg" { + slog.Error("icon must be an svg file") + continue + } + if strings.HasPrefix(bookmark.Icon, "shi/") { + title := strings.Replace(bookmark.Icon, "shi/", "", 1) + if title == "" { + slog.Error("icon title is empty") + continue + } + data, err := os.ReadFile(iconsFolder + title) + if os.IsNotExist(err) { + slog.Debug("icon not found, downloading...", "title", title) + resp, err := http.Get("https://cdn.jsdelivr.net/gh/selfhst/icons/" + ext + "/" + title) if err != nil { + slog.Error("failed to get icon", "err", err.Error()) continue } - color, ok := iconsByTitle[bookmark.Name] - if bookmark.OverwriteColor != "" { - ok = true - color = bookmark.OverwriteColor + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + slog.Error("failed to get icon", "status", resp.Status) + continue } - if !(bookmark.IgnoreColor || !ok || color == "") { - data = []byte(insertColor(string(data), color)) - } - } else { - data, err = os.ReadFile(iconsFolder + bookmark.Icon) + data, err = io.ReadAll(resp.Body) if err != nil { + slog.Error("failed to read icon", "err", err.Error()) + continue + } + err = os.WriteFile(iconsFolder+title, data, fs.FileMode(0640)) + if err != nil { + slog.Error("failed to write icon", "err", err.Error()) continue } } + if data == nil { + slog.Error("icon data is null") + continue + } bs.bookmarks.Applications[i].Entries[j].Icon = insertWidthHeight(string(data)) - } else { - bs.bookmarks.Applications[i].Entries[j].Icon = "" } + } } } @@ -132,32 +141,6 @@ func (bs *BookmarkService) parseBookmarks() { } } -func (bs *BookmarkService) parseIcons() { - file, err := os.Open(simpleIconsInfo) - if err != nil { - slog.Error(err.Error()) - } - defer file.Close() - byteValue, err := io.ReadAll(file) - if err != nil { - slog.Error(err.Error()) - } - err = json.Unmarshal(byteValue, &bs.SimpleIcons) - if err != nil { - slog.Error(err.Error()) - return - } - bs.replaceIconString() -} - -func insertColor(svg, color string) string { - parts := strings.SplitN(svg, "