Skip to content

Initial Docker Image Setup for Notesium #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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" ]
9 changes: 7 additions & 2 deletions notesium.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
Expand Down Expand Up @@ -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)
}
}

Expand Down
20 changes: 11 additions & 9 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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="):
Expand All @@ -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":
Expand Down
76 changes: 76 additions & 0 deletions start-docker.sh
Original file line number Diff line number Diff line change
@@ -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