From 308fcafc7a4f3c293b2ff45156a4c690a76c4f74 Mon Sep 17 00:00:00 2001 From: NavyStack <137406386+Navystack@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:23:01 +0900 Subject: [PATCH] Initial Docker Image Setup for Notesium --- Dockerfile | 100 ++++++++++++++++++++++++++++++++++++++++++++++++ notesium.go | 9 ++++- options.go | 20 +++++----- start-docker.sh | 76 ++++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 Dockerfile create mode 100755 start-docker.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..83c2526 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,100 @@ +# Use Node.js LTS version as the base image for the application build stage +FROM node:lts-bookworm AS app-builder + +# Copy Golang from a secondary Golang image to have the Go environment ready +COPY --from=golang:bookworm /usr/local/go/ /usr/local/go/ + +# Set up the PATH environment variable to include Go's binary directory +ENV PATH="/usr/local/go/bin:${PATH}" + +# Set the working directory to /notesium in the container +WORKDIR /notesium + +# Copy all project files from the host into the /notesium directory +COPY . /notesium/ + +# Install Tailwind CSS globally, required for frontend styling +RUN npm install -g tailwindcss + +# Execute the make.sh script to build the frontend assets located in web/app +RUN ./web/app/make.sh all + +# Build the Go application with version and build time metadata +RUN go build -ldflags "-X main.gitversion=$(git describe --tags --long --always --dirty) \ + -X main.buildtime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" + +# Start a new, minimal Node.js-based stage for the final application image +FROM node:lts-bookworm-slim AS final + +# Define environment variables for versions and user settings +## Gosu version for privilege management +## Tini version for init process +## User ID for notesium user +## Group ID for notesium user +## Username for notesium user +## Directory for notesium data storage + +ENV GOSU_VERSION=1.17 \ + TINI_VERSION=v0.19.0 \ + UID=1000 \ + GID=1000 \ + USERNAME=notesium \ + NOTESIUM_DIR=/notesium/data + +# Install essential tools and verify signatures for security +RUN set -eux; \ + # Save a list of currently installed packages for later cleanup + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends ca-certificates gnupg wget; \ + rm -rf /var/lib/apt/lists/*; \ + \ + # Install Gosu for executing commands as a different user + dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \ + wget -q -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \ + wget -q -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \ + export GNUPGHOME="$(mktemp -d)"; \ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \ + gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \ + gpgconf --kill all; \ + rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \ + chmod +x /usr/local/bin/gosu; \ + gosu --version; \ + gosu nobody true; \ + \ + # Install Tini for proper process handling within Docker + : "${TINI_VERSION:?TINI_VERSION is not set}"; \ + dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \ + echo "Downloading Tini version ${TINI_VERSION} for architecture ${dpkgArch}"; \ + wget -q -O /usr/bin/tini "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-$dpkgArch"; \ + wget -q -O /usr/bin/tini.asc "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-$dpkgArch.asc"; \ + export GNUPGHOME="$(mktemp -d)"; \ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys 595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7; \ + gpg --batch --verify /usr/bin/tini.asc /usr/bin/tini; \ + gpgconf --kill all; \ + rm -rf "$GNUPGHOME" /usr/bin/tini.asc; \ + chmod +x /usr/bin/tini; \ + echo "Tini version: $(/usr/bin/tini --version)"; \ + \ + # Clean up apt cache to reduce the final image size + apt-mark auto '.*' > /dev/null; \ + [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false + +# Copy the compiled notesium binary from the app-builder stage to /usr/bin +COPY --from=app-builder --chown=1000:1000 /notesium/notesium /usr/bin/notesium + +# Copy the custom startup script for Docker entry into /usr/bin +COPY start-docker.sh /usr/bin/start-docker + +# Create the data directory where application data will be stored +RUN mkdir -p /notesium/data + +# Define /notesium/data as a Docker volume, making it easier to persist data +VOLUME [ "/notesium/data" ] + +# Set Tini as the entrypoint to ensure proper process handling +ENTRYPOINT [ "tini", "--", "start-docker" ] + +# Set the default command to start notesium in writable mode on host 0.0.0.0 and port 8080 +CMD [ "notesium", "web", "--writable", "--host=0.0.0.0","--port=8080" ] diff --git a/notesium.go b/notesium.go index e288821..bbece46 100644 --- a/notesium.go +++ b/notesium.go @@ -342,7 +342,12 @@ func notesiumWeb(dir string, opts webOptions) { } defer ln.Close() - url := "http://localhost:" + strings.Split(ln.Addr().String(), ":")[1] + host, port, _ := net.SplitHostPort(ln.Addr().String()) + if host == "0.0.0.0" || host == "::" { + host = "localhost" + } + url := fmt.Sprintf("http://%s:%s", host, port) + server := &http.Server{ Addr: ln.Addr().String(), } @@ -395,7 +400,7 @@ func notesiumWeb(dir string, opts webOptions) { fmt.Printf("Serving on %s (bind address %s)\n", url, opts.host) fmt.Printf("Press Ctrl+C to stop%s\n", idleStopMsg) if err := server.Serve(ln); err != http.ErrServerClosed { - log.Fatalf("Server closed unexpected:%+v", err) + log.Fatalf("Server closed unexpectedly: %+v", err) } } diff --git a/options.go b/options.go index 5661320..c734c8d 100644 --- a/options.go +++ b/options.go @@ -240,12 +240,13 @@ func parseOptions(args []string) (Command, error) { return cmd, nil case "web": - opts := webOptions{} - opts.host = "127.0.0.1" - opts.port = 0 - opts.readOnly = true - opts.webroot = "embedded" - opts.check = true + opts := webOptions{ + host: "127.0.0.1", + port: 0, + readOnly: true, + webroot: "embedded", + check: true, + } for _, opt := range args[1:] { switch { case strings.HasPrefix(opt, "--webroot="): @@ -255,12 +256,13 @@ func parseOptions(args []string) (Command, error) { case opt == "--stop-on-idle": opts.heartbeat = true case strings.HasPrefix(opt, "--port="): - portStr := strings.TrimPrefix(opt, "--port=") - port, err := strconv.Atoi(portStr) + port, err := strconv.Atoi(strings.TrimPrefix(opt, "--port=")) if err != nil || port < 1024 || port > 65535 { - return Command{}, fmt.Errorf("invalid or out of range port number: %s", portStr) + return Command{}, fmt.Errorf("invalid or out of range port number: %s", opt) } opts.port = port + case strings.HasPrefix(opt, "--host="): + opts.host = strings.TrimPrefix(opt, "--host=") case opt == "--no-check": opts.check = false case opt == "--writable": diff --git a/start-docker.sh b/start-docker.sh new file mode 100755 index 0000000..ac7401c --- /dev/null +++ b/start-docker.sh @@ -0,0 +1,76 @@ +#!/bin/bash +set -e + +# Define colors +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' + +setup_user() { + UID="${UID:-1000}" + GID="${GID:-1000}" + UNAME="${USERNAME:-notesium}" + + # Rename group and create if necessary + group=$(getent group "${GID}" | cut -d: -f1) + if [ -n "${group}" ] && [ "${group}" != "${UNAME}" ]; then + groupmod -n "${UNAME}" "${group}" + else + groupadd -g "${GID}" "${UNAME}" + fi + + # Rename user and create if necessary + user=$(getent passwd "${UID}" | cut -d: -f1) + if [ -n "${user}" ]; then + if [ "${user}" != "${UNAME}" ]; then + usermod -l "${UNAME}" "${user}" + usermod -d "/home/${UNAME}" "${UNAME}" + + if [ -d "/home/${user}" ]; then + mv "/home/${user}" "/home/${UNAME}" || { echo "Failed to rename home directory"; exit 1; } + else + echo "Home directory for ${user} does not exist" + fi + fi + else + useradd -u "${UID}" -g "${GID}" -m "${UNAME}" + fi + + # Set ownership of the home directory + echo -e "${GREEN}Setting ownership of home directory for ${UNAME}...${NC}" + mkdir -p "/home/${UNAME}/notes" + chown -R "${UID}:${GID}" "/home/${UNAME}" + echo -e "${GREEN}Ownership set successfully for ${UNAME}!${NC}" +} + +setup_notesium_dir() { + if [ -n "${NOTESIUM_DIR}" ]; then + echo -e "${YELLOW}NOTESIUM_DIR is set to ${NOTESIUM_DIR}${NC}" + + # Create directory if it does not exist + if [ ! -d "${NOTESIUM_DIR}" ]; then + echo -e "${YELLOW}Directory ${NOTESIUM_DIR} does not exist. Creating...${NC}" + mkdir -p "${NOTESIUM_DIR}" + echo -e "${GREEN}Directory ${NOTESIUM_DIR} created successfully.${NC}" + fi + + # Set permissions if needed + if [ "$(stat -c '%u:%g' "${NOTESIUM_DIR}")" != "${UID}:${GID}" ]; then + echo -e "${YELLOW}Setting ownership of ${NOTESIUM_DIR} to ${UNAME}...${NC}" + chown -R "${UID}:${GID}" "${NOTESIUM_DIR}" + echo -e "${GREEN}Ownership set successfully for ${NOTESIUM_DIR}!${NC}" + else + echo -e "${GREEN}Ownership for ${NOTESIUM_DIR} is already set correctly.${NC}" + fi + else + echo -e "${YELLOW}NOTESIUM_DIR is not set. Skipping additional directory setup.${NC}" + fi +} + +if [ "$(id -u)" -eq 0 ]; then + setup_user # Call the setup_user function + setup_notesium_dir # Call the setup_notesium_dir function + exec gosu "${UNAME}" "$@" # Switch to the specified user +else + exec "$@" # If not root, execute the command directly +fi