diff --git a/.env.example b/.env.example index 5a434dfc1e1..2de2526be16 100644 --- a/.env.example +++ b/.env.example @@ -11,4 +11,4 @@ NEXTAUTH_SECRET="anything" # Disable analytics NEXT_PUBLIC_DISABLE_ANALYTICS="true" -DEFAULT_COLOR_SCHEME="light" \ No newline at end of file +DEFAULT_COLOR_SCHEME="light" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index d244cd03960..098d8350dcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,106 @@ -FROM node:20.2.0-slim +FROM node:20.2.0-slim as compiler + +#RUN apt-get update && apt-get -y install git wget openssl + WORKDIR /app -# Define node.js environment variables +#RUN git clone https://github.com/ajnart/homarr.git . +COPY . . + +RUN yarn install +COPY .env.example .env +RUN yarn build + + +FROM node:20.2.0-alpine3.18 + +#ARGS is only for build + ARG PORT=7575 +# Keep free id >= 1000 for user, under node:x image by default node user uses 1000:1000 +ARG NODE_UID=800 +ARG NODE_GID=800 + +#PUID can be set during build and run time +ARG PUID=801 +ARG PGID=801 + +#it must be the same as the host, temporary 802 or any, automatically changed at runtime +ARG DOCKER_GID=802 + +#By default, ping group using gid 999, keep free to possible docker host gid +ARG PING_GID=803 + +# Expose the default application port +EXPOSE $PORT +ENV PORT=${PORT} + +# Define node.js environment variables ENV NEXT_TELEMETRY_DISABLED 1 ENV NODE_ENV production ENV NODE_OPTIONS '--no-experimental-fetch' -COPY next.config.js ./ -COPY public ./public -COPY package.json ./temp_package.json -COPY yarn.lock ./temp_yarn.lock +# App environment variables +ENV DATABASE_URL "file:/app/data/db.sqlite" +ENV NEXTAUTH_URL "http://localhost:7575" +ENV NEXTAUTH_SECRET NOT_IN_USE_BECAUSE_JWTS_ARE_UNUSED + +# Must be same as host user when using bind mount volumes +ENV PUID $PUID +ENV PGID $PGID + +RUN apk update && apk add --no-cache \ + supervisor docker-cli shadow + +RUN usermod -u $NODE_UID node +RUN groupmod -g $NODE_GID node + +RUN groupmod -g $PING_GID ping + +# Creating local homarr user and group +RUN groupadd -g $PGID homarr +RUN useradd homarr -u $PUID -g homarr --home-dir /app --shell /sbin/nologin +RUN usermod -aG node homarr + +# Creating a local Docker group and add docker group to homarr user +RUN groupadd -g $DOCKER_GID docker +RUN usermod -aG docker homarr + +# Enable sudo for homarr user, only for debug and testing purposes +#RUN apk add sudo +#RUN echo "homarr ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +# Configure entrypoint +COPY ./docker/entrypoint / +RUN chmod +x /entrypoint.sh +RUN chmod +x /docker-entrypoint.d/*.sh + +# Configure supervisord +COPY ./docker/etc/supervisord.conf /etc/supervisord.conf +COPY ./docker/etc/supervisor /etc/supervisor + +#RUN chown homarr:homarr /app +USER node +WORKDIR /app + +COPY --from=compiler --chown=node:homarr /app/next.config.js ./ +COPY --from=compiler --chown=node:homarr /app/public ./public +COPY --from=compiler --chown=node:homarr /app/package.json ./temp_package.json +COPY --from=compiler --chown=node:homarr /app/yarn.lock ./temp_yarn.lock # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing -COPY .next/standalone ./ -COPY .next/static ./.next/static -COPY ./scripts/run.sh ./scripts/run.sh -RUN chmod +x ./scripts/run.sh -COPY ./drizzle ./drizzle -COPY ./drizzle/migrate ./migrate -COPY ./tsconfig.json ./migrate/tsconfig.json -COPY ./cli ./cli +COPY --from=compiler --chown=node:homarr /app/.next/standalone ./ +COPY --from=compiler --chown=node:homarr /app/.next/static ./.next/static -RUN mkdir /data +COPY --from=compiler --chown=node:homarr /app/scripts/run.sh ./scripts/run.sh +RUN chmod +x ./scripts/run.sh +COPY --from=compiler --chown=node:homarr /app/drizzle ./drizzle -# Install dependencies -RUN apt update && apt install -y openssl wget +COPY --from=compiler --chown=node:homarr /app/drizzle/migrate ./migrate +COPY --from=compiler --chown=node:homarr /app/tsconfig.json ./migrate/tsconfig.json +COPY --from=compiler --chown=node:homarr /app/cli ./cli # Move node_modules to temp location to avoid overwriting RUN mv node_modules _node_modules @@ -45,22 +118,16 @@ RUN mv node_modules ./migrate/node_modules # Copy temp node_modules of app to app folder RUN mv _node_modules node_modules -RUN echo '#!/bin/bash\nnode /app/cli/cli.js "$@"' > /usr/bin/homarr -RUN chmod +x /usr/bin/homarr RUN cd /app/cli && yarn --immutable -# Expose the default application port -EXPOSE $PORT -ENV PORT=${PORT} +# Root is needed for supervisord +USER root -ENV DATABASE_URL "file:/data/db.sqlite" -ENV NEXTAUTH_URL "http://localhost:7575" -ENV PORT 7575 -ENV NEXTAUTH_SECRET NOT_IN_USE_BECAUSE_JWTS_ARE_UNUSED +RUN echo '#!/bin/bash\nnode /app/cli/cli.js "$@"' > /usr/bin/homarr +RUN chmod +x /usr/bin/homarr HEALTHCHECK --interval=10s --timeout=5s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT} || exit 1 -VOLUME [ "/app/data/configs" ] -VOLUME [ "/data" ] -ENTRYPOINT ["sh", "./scripts/run.sh"] +ENTRYPOINT [ "/entrypoint.sh" ] +CMD [] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000000..062b64fcfea --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,23 @@ +version: "2.1" +services: +#---------------------------------------------------------------------# +# Homarr - A simple, yet powerful dashboard for your server. # +#---------------------------------------------------------------------# + homarr: + container_name: homarr + #image: ghcr.io/ajnart/homarr:latest + build: # only for dev branch... + context: . + dockerfile: Dockerfile + restart: unless-stopped + environment: + - PUID=1000 + - PGID=1000 + - DOCKER_GID=999 # Must be same as host docker group id + - DATABASE_URL=file:/app/data/configs/db.sqlite + volumes: + - /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration + - ./homarr_persistence/configs:/app/data/configs + - ./homarr_persistence/icons:/app/public/icons + ports: + - '7575:7575' \ No newline at end of file diff --git a/docker/entrypoint/docker-entrypoint.d/00-user-setup.sh b/docker/entrypoint/docker-entrypoint.d/00-user-setup.sh new file mode 100755 index 00000000000..c7e1671aebf --- /dev/null +++ b/docker/entrypoint/docker-entrypoint.d/00-user-setup.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +HOMARR_USER_PATHS="/app/data /app/public/icons" + +for path in $HOMARR_USER_PATHS +do + if [ ! -d "$path" ]; then + mkdir -p $path + fi + + find $path ! -user $PUID -print0 | while read -d $'\0' FILE + do + echo "${FILE} is not own by current user, fixing..." + chown $PUID:$PGID ${FILE} + done +done + +echo Setting homarr UID to $PUID and GID to $PGID please wait... +usermod -u $PUID homarr +groupmod -g $PGID homarr + +DOCKER_GID=$(stat -c %g /var/run/docker.sock 2>/dev/null) +if [[ $? -eq 0 ]]; then + echo "SETTING DOCKER GID TO ${DOCKER_GID}" + groupmod -g $DOCKER_GID docker +fi \ No newline at end of file diff --git a/docker/entrypoint/entrypoint.sh b/docker/entrypoint/entrypoint.sh new file mode 100755 index 00000000000..c2cda9b1f25 --- /dev/null +++ b/docker/entrypoint/entrypoint.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# vim:sw=4:ts=4:et + +set -e +echo "Entering entrypoint..." + +echo "Param \$1: $1" +echo "User: "$(whoami) + + +entrypoint_log() { + if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then + echo "$@" + fi +} + +if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then + entrypoint_log "$0: /docker-entrypoint.d/ is not empty, will attempt to perform configuration" + + entrypoint_log "$0: Looking for shell scripts in /docker-entrypoint.d/" + find "/docker-entrypoint.d/" -follow -type f -print | sort -V | while read -r f; do + case "$f" in + *.envsh) + if [ -x "$f" ]; then + entrypoint_log "$0: Sourcing $f"; + . "$f" + else + # warn on shell scripts without exec bit + entrypoint_log "$0: Ignoring $f, not executable"; + fi + ;; + *.sh) + if [ -x "$f" ]; then + entrypoint_log "$0: Launching $f"; + "$f" + else + # warn on shell scripts without exec bit + entrypoint_log "$0: Ignoring $f, not executable"; + fi + ;; + *) entrypoint_log "$0: Ignoring $f";; + esac + done + + entrypoint_log "$0: Configuration complete; ready for start up" +else + entrypoint_log "$0: No files found in /docker-entrypoint.d/, skipping configuration" +fi + +#exec "$@" + +# sys container init: +# +# If no command is passed to the container, supervisord becomes init and +# starts all its configured programs (per /etc/supervisord.conf). +# +# If a command is passed to the container, it runs in the foreground; +# supervisord runs in the background and starts all its configured +# programs. +# +# In either case, supervisord always starts its configured programs. + +if [ "$#" -eq 0 ] || [ "${1#-}" != "$1" ]; then + exec supervisord -n "$@" +else + supervisord -c /etc/supervisord.conf & + exec "$@" +fi \ No newline at end of file diff --git a/docker/etc/supervisor/conf.d/homarr.ini b/docker/etc/supervisor/conf.d/homarr.ini new file mode 100644 index 00000000000..b7ce3fe804f --- /dev/null +++ b/docker/etc/supervisor/conf.d/homarr.ini @@ -0,0 +1,13 @@ +[program:homarr] +command=/app/scripts/run.sh +environment=HOME="/app",USER="homarr",LOGNAME="homarr" +user=homarr +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true +startretries=0 +stopasgroup=true +killasgroup=true +stopsignal=KILL \ No newline at end of file diff --git a/docker/etc/supervisord.conf b/docker/etc/supervisord.conf new file mode 100644 index 00000000000..bcde0a9af92 --- /dev/null +++ b/docker/etc/supervisord.conf @@ -0,0 +1,185 @@ +; Sample supervisor config file. +; +; For more information on the config file, please see: +; http://supervisord.org/configuration.html +; +; Notes: +; - Shell expansion ("~" or "$HOME") is not supported. Environment +; variables can be expanded using this syntax: "%(ENV_HOME)s". +; - Quotes around values are not supported, except in the case of +; the environment= options as shown below. +; - Comments must have a leading space: "a=b ;comment" not "a=b;comment". +; - Command will be truncated if it looks like a config file comment, e.g. +; "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ". +; +; Warning: +; Paths throughout this example file use /tmp because it is available on most +; systems. You will likely need to change these to locations more appropriate +; for your system. Some systems periodically delete older files in /tmp. +; Notably, if the socket file defined in the [unix_http_server] section below +; is deleted, supervisorctl will be unable to connect to supervisord. + +[unix_http_server] +file=/run/supervisord.sock ; the path to the socket file +;chmod=0700 ; socket file mode (default 0700) +;chown=nobody:nogroup ; socket file uid:gid owner +;username=user ; default is no username (open server) +;password=123 ; default is no password (open server) + +; Security Warning: +; The inet HTTP server is not enabled by default. The inet HTTP server is +; enabled by uncommenting the [inet_http_server] section below. The inet +; HTTP server is intended for use within a trusted environment only. It +; should only be bound to localhost or only accessible from within an +; isolated, trusted network. The inet HTTP server does not support any +; form of encryption. The inet HTTP server does not use authentication +; by default (see the username= and password= options to add authentication). +; Never expose the inet HTTP server to the public internet. + +;[inet_http_server] ; inet (TCP) server disabled by default +;port=127.0.0.1:9001 ; ip_address:port specifier, *:port for all iface +;username=user ; default is no username (open server) +;password=123 ; default is no password (open server) + +[supervisord] +#logfile=/var/log/supervisord.log ; main log file; default $CWD/supervisord.log +logfile=/dev/null +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +;logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB +;logfile_backups=10 ; # of main logfile backups; 0 means none, default 10 +;loglevel=info ; log level; default info; others: debug,warn,trace +;pidfile=/run/supervisord.pid ; supervisord pidfile; default supervisord.pid +;nodaemon=false ; start in foreground if true; default false +nodaemon=true +;silent=false ; no logs to stdout if true; default false +;minfds=1024 ; min. avail startup file descriptors; default 1024 +;minprocs=200 ; min. avail process descriptors;default 200 +;umask=022 ; process file creation umask; default 022 +;user=chrism ; setuid to this UNIX account at startup; recommended if root +;identifier=supervisor ; supervisord identifier, default is 'supervisor' +;directory=/tmp ; default is not to cd during start +;nocleanup=true ; don't clean up tempfiles at start; default false +;childlogdir=/var/log/supervisor ; 'AUTO' child log dir, default $TEMP +;environment=KEY="value" ; key value pairs to add to environment +;strip_ansi=false ; strip ansi escape codes in logs; def. false + +#logfile=/dev/null +#logfile_maxbytes=0 + +#[eventlistener:stdout] +#command = /app/.local/bin/supervisor_stdout +#buffer_size = 1 +#events = PROCESS_LOG +#result_handler = supervisor_stdout:event_handler + +; The rpcinterface:supervisor section must remain in the config file for +; RPC (supervisorctl/web interface) to work. Additional interfaces may be +; added by defining them in separate [rpcinterface:x] sections. + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +; The supervisorctl section configures how supervisorctl will connect to +; supervisord. configure it match the settings in either the unix_http_server +; or inet_http_server section. + +[supervisorctl] +serverurl=unix:///run/supervisord.sock ; use a unix:// URL for a unix socket +;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket +;username=chris ; should be same as in [*_http_server] if set +;password=123 ; should be same as in [*_http_server] if set +;prompt=mysupervisor ; cmd line prompt (default "supervisor") +;history_file=~/.sc_history ; use readline history if available + +; The sample program section below shows all possible program subsection values. +; Create one or more 'real' program: sections to be able to control them under +; supervisor. + +;[program:theprogramname] +;command=/bin/cat ; the program (relative uses PATH, can take args) +;process_name=%(program_name)s ; process_name expr (default %(program_name)s) +;numprocs=1 ; number of processes copies to start (def 1) +;directory=/tmp ; directory to cwd to before exec (def no cwd) +;umask=022 ; umask for process (default None) +;priority=999 ; the relative start priority (default 999) +;autostart=true ; start at supervisord start (default: true) +;startsecs=1 ; # of secs prog must stay up to be running (def. 1) +;startretries=3 ; max # of serial start failures when starting (default 3) +;autorestart=unexpected ; when to restart if exited after running (def: unexpected) +;exitcodes=0 ; 'expected' exit codes used with autorestart (default 0) +;stopsignal=QUIT ; signal used to kill process (default TERM) +;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) +;stopasgroup=false ; send stop signal to the UNIX process group (default false) +;killasgroup=false ; SIGKILL the UNIX process group (def false) +;user=chrism ; setuid to this UNIX account to run the program +;redirect_stderr=true ; redirect proc stderr to stdout (default false) +;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO +;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10) +;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) +;stdout_events_enabled=false ; emit events on stdout writes (default false) +;stdout_syslog=false ; send stdout to syslog with process name (default false) +;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO +;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10) +;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) +;stderr_events_enabled=false ; emit events on stderr writes (default false) +;stderr_syslog=false ; send stderr to syslog with process name (default false) +;environment=A="1",B="2" ; process environment additions (def no adds) +;serverurl=AUTO ; override serverurl computation (childutils) + +; The sample eventlistener section below shows all possible eventlistener +; subsection values. Create one or more 'real' eventlistener: sections to be +; able to handle event notifications sent by supervisord. + +;[eventlistener:theeventlistenername] +;command=/bin/eventlistener ; the program (relative uses PATH, can take args) +;process_name=%(program_name)s ; process_name expr (default %(program_name)s) +;numprocs=1 ; number of processes copies to start (def 1) +;events=EVENT ; event notif. types to subscribe to (req'd) +;buffer_size=10 ; event buffer queue size (default 10) +;directory=/tmp ; directory to cwd to before exec (def no cwd) +;umask=022 ; umask for process (default None) +;priority=-1 ; the relative start priority (default -1) +;autostart=true ; start at supervisord start (default: true) +;startsecs=1 ; # of secs prog must stay up to be running (def. 1) +;startretries=3 ; max # of serial start failures when starting (default 3) +;autorestart=unexpected ; autorestart if exited after running (def: unexpected) +;exitcodes=0 ; 'expected' exit codes used with autorestart (default 0) +;stopsignal=QUIT ; signal used to kill process (default TERM) +;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) +;stopasgroup=false ; send stop signal to the UNIX process group (default false) +;killasgroup=false ; SIGKILL the UNIX process group (def false) +;user=chrism ; setuid to this UNIX account to run the program +;redirect_stderr=false ; redirect_stderr=true is not allowed for eventlisteners +;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO +;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10) +;stdout_events_enabled=false ; emit events on stdout writes (default false) +;stdout_syslog=false ; send stdout to syslog with process name (default false) +;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO +;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10) +;stderr_events_enabled=false ; emit events on stderr writes (default false) +;stderr_syslog=false ; send stderr to syslog with process name (default false) +;environment=A="1",B="2" ; process environment additions +;serverurl=AUTO ; override serverurl computation (childutils) + +; The sample group section below shows all possible group values. Create one +; or more 'real' group: sections to create "heterogeneous" process groups. + +;[group:thegroupname] +;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions +;priority=999 ; the relative start priority (default 999) + +; The [include] section can just contain the "files" setting. This +; setting can list multiple files (separated by whitespace or +; newlines). It can also contain wildcards. The filenames are +; interpreted as relative to this file. Included files *cannot* +; include files themselves. + + + +[include] +files = /etc/supervisor/conf.d/*.ini