Compare commits

...

58 Commits
v1.2 ... main

Author SHA1 Message Date
Asif Bacchus 12ff81b91c feature(readme): update readme
- include monitoring multiple directories
2022-05-02 00:38:44 -06:00
Asif Bacchus 4b8e019b84 chore(ide): update docker build params 2022-05-02 00:38:16 -06:00
Asif Bacchus 6970776aa8 feature: watch 5 directories vs 1 2022-05-02 00:31:19 -06:00
Asif Bacchus a17a9d38fd refactor(readme): update readme
- add changelog section
- reformat file
2022-02-26 22:13:08 -07:00
Asif Bacchus f1b0dc4c8b chore(ide): update docker build args 2022-02-26 22:12:51 -07:00
Asif Bacchus dc732efdb7 feature(dockerfile): multi-stage build
- install node dependencies in builder stage
- final stage based on alpine with minimal nodejs installation
- npm not present, prevents idiotic lingering security issues
- 50% smaller final image
2022-02-26 21:59:05 -07:00
Asif Bacchus ec07b94cd3 chore(ide): update docker build args 2022-02-26 20:49:52 -07:00
Asif Bacchus ef87879b60 chore(dockerfile): bump alpine base version 2022-02-26 20:46:28 -07:00
Asif Bacchus 48745eccbb chore(ide): add Rider docker build configuration 2022-02-26 20:46:09 -07:00
Asif Bacchus c6ccca24f7 update readme 2021-08-01 02:32:09 -06:00
Asif Bacchus 5bc0555579 chore(license): add license, import relevant licenses 2021-08-01 02:22:59 -06:00
Asif Bacchus 81267cdb7c fix(dockerfile): fix readme location in documentation label 2021-07-30 17:18:39 -06:00
Asif Bacchus f673f94283 fix(dockerfile): use tini to correct error code on exit
- node returns error on sigterm but this normal for docker termination
- coupled with javascript signal capture, tini ignores return 143,
reports 0
- allows for proper automatic restart since no error is reported
2021-07-30 17:10:37 -06:00
Asif Bacchus 5994f3b82e perf(dockerfile): moved copy file order
- moved node file copy order, allow for package.json usage
- invocation js moved after node install, faster builds
2021-07-30 17:07:46 -06:00
Asif Bacchus 106fe86cb9 feature(livereload): rewrite javascript: graceful async socket close
- rewrite javascript livereload script to use object for healthcheck
- aysnc operations shutting down healthcheck and livereload sockets
- handle signals properly
- fix error code reported by signals (node issue)
2021-07-30 17:06:05 -06:00
Asif Bacchus 9717e8a6fa refactor: rename repo, image, containers
- rename for integration with node-dev environment and production
deployment
- update image and product name across all files
- use generated project file to install node dependencies
- bump internal version to 2.6
2021-07-30 11:53:41 -06:00
Asif Bacchus c0a4fc1c6c fix(env): update env file to match new exclusion defaults 2021-07-29 22:53:51 -06:00
Asif Bacchus cad203a7c0 fix(dockerfile): reorganize and add new labels
- add deprecated maintainer tag and label
- fix internal label names
- add git commit label
- reorganize labels
2021-07-28 04:41:28 -06:00
Asif Bacchus 8cafe6e164 fix readme formatting error 2021-07-28 04:24:42 -06:00
Asif Bacchus 99cc14532c docs(readme): update readme to reflect fixed exclusions 2021-07-28 04:20:50 -06:00
Asif Bacchus 13ec1a72e9 style(livereload): format code 2021-07-28 04:12:19 -06:00
Asif Bacchus 1194fc63c6 fix(livereload): fix exclusions bug
- read environment var as array
- convert array elements to regex objects
- update environment variable defaults
2021-07-28 03:51:56 -06:00
Asif Bacchus 574b6e1e07 refactor(dockerfile): update existing packages to latest versions 2021-07-28 03:50:03 -06:00
Asif Bacchus d26d8a822f updated README for version 2.x 2021-07-24 19:56:09 -06:00
Asif Bacchus d0eb4cc821 feature(dockerfile): allow setting separate GID 2021-07-24 16:21:07 -06:00
Asif Bacchus 762b97d19d refactor(livereload): update default exclusions
- use RegEx format
- still seems to be ignored, pending issue upstream
2021-07-24 04:09:37 -06:00
Asif Bacchus 2889c153f3 feature(compose): nginx dependant on healthy livereload 2021-07-24 03:03:31 -06:00
Asif Bacchus dfa50ea6b5 feature: add healthcheck
- add express npm to allow creation of health endpoint
- create health end pt as callback to livereload server listening state
- configure healthcheck in container
- allows true 'stack' operation since other services can query status
2021-07-24 03:01:01 -06:00
Asif Bacchus 41dcd6a5bb refactor(entrypoint): mod permissions on fullchain 2021-07-24 02:12:44 -06:00
Asif Bacchus 2dbcd4a845 fix(entrypoint): fix permissions on generated certs
- set private key to be group readable
- create chain.pem from fullchain.pem
- generate dhparams for TLS1.2
2021-07-24 02:09:50 -06:00
Asif Bacchus 81cfe975b4 refactor(entrypoint): move HTTP/S message to javascript 2021-07-23 23:45:58 -06:00
Asif Bacchus e83dce3304 fix(livereload): process env vars as strings not boolean 2021-07-23 23:04:44 -06:00
Asif Bacchus 7ce5738399 fix(compose): certs dir writable by livereload
- readonly mapping would prevent cert auto-generation
2021-07-23 19:25:23 -06:00
Asif Bacchus 34ffb96025 refactor(dockerfile): switch to env file
- replace livereload.params with .env
- allows subst in compose file itself
- activate defaults by default
2021-07-23 19:23:13 -06:00
Asif Bacchus 1e7531e632 chore: update and use consistent project name 2021-07-23 18:36:14 -06:00
Asif Bacchus b2a02407b9 refactor(compose): use proper image tag 2021-07-23 18:29:38 -06:00
Asif Bacchus f196697569 chore(dockerfile): re-enable extra labels 2021-07-23 18:22:40 -06:00
Asif Bacchus f77961145e struct(entrypoint): add notation for HTTP-only mode 2021-07-23 18:20:47 -06:00
Asif Bacchus 3001dbca73 feature(entrypoint): show loaded certificate
- warn if private key missing
- change error return codes
2021-07-23 18:19:36 -06:00
Asif Bacchus 5ded2bc320 refactor(entrypoint): use lowercase boolean values
- convert boolean environment vars to lowercase
- allows single use between shell and JavaScript
2021-07-23 18:04:48 -06:00
Asif Bacchus a184866de3 refactor(entrypoint): remove export function
- never a need to export keypair, cert is always enough
2021-07-23 17:53:42 -06:00
Asif Bacchus c48e985d23 feature(entrypoint): generate self-signed certificate
- generate via specific invocation
- auto-generate if SSL and no mounted certificate found
- allow specifying hostname for certificate
2021-07-23 17:19:58 -06:00
Asif Bacchus e8d238f3c3 feature(compose): sample stack with NGINX server 2021-07-23 14:48:50 -06:00
Asif Bacchus 8f35aaef11 feature: allow non-secure connections
- default to secure
- parameterize via env var
2021-07-23 13:31:01 -06:00
Asif Bacchus ee38b2468e feature(livereload): add debug output as default 2021-07-22 21:21:59 -06:00
Asif Bacchus fdfba8ab4f fix(livereload): match watch dir with dockerfile 2021-07-22 20:36:12 -06:00
Asif Bacchus 2c3bcf27d7 refactor(dockerfile): change order for faster build
- move file copy to near end since scripts change often
2021-07-22 18:45:29 -06:00
Asif Bacchus ba7d33943e fix(entrypoint): fix shell action 2021-07-22 18:44:29 -06:00
Asif Bacchus f30e22b333 build(dockerfile): temp comment labels for faster build 2021-07-22 18:44:00 -06:00
Asif Bacchus 9ee212fcef fix(dockerfile): fix minor bugs stopping build 2021-07-22 17:38:43 -06:00
Asif Bacchus 1ca3b48d0d refactor(dockerfile): remove old dockerfile
- delete non-tls version of dockerfile
- set internal version as build-arg
2021-07-22 17:28:21 -06:00
Asif Bacchus d864231db1 refactor(dockerfile): cleanup permissions
- set proper permissions for server script and entrypoint
2021-07-22 17:21:55 -06:00
Asif Bacchus 7828ff2125 struct(dockerfile): add openssl 2021-07-22 17:16:15 -06:00
Asif Bacchus 0fbd3303e4 struct(entrypoint): skeleton entrypoint script
- basic operation flow with placeholder functions
- implement server and shell launch
- allow commands to pass to shell
- update Dockerfile to load entrypoint only
- update Dockerfile to set permissions for certs directory
2021-07-22 17:15:31 -06:00
Asif Bacchus e82fad68ac feature(server invocation script): 2021-07-22 16:43:17 -06:00
Asif Bacchus cc10ee2241 struct(Dockerfile): align with previous customizations 2021-07-22 16:36:36 -06:00
Asif Bacchus 9e9abe945f struct(dockerfile): initial build skeleton 2021-07-22 16:22:40 -06:00
Asif Bacchus f21510cdf7 chore(git): update gitignore, gitattributes 2021-07-22 16:22:05 -06:00
21 changed files with 2230 additions and 129 deletions

120
.env Normal file
View File

@ -0,0 +1,120 @@
#
# Parameters for ab-livereload stack:
# This file makes it easier to customize your ab-livereload stack deployment by providing centralized configuration options.
# This file is *not required* since all values have (sane) default settings.
# There is *no* sensitive information in this file.
#
# COMMON PARAMETERS
# TZ:
# Timezone used in logs and console messages. No effect on operation, purely aesthetic.
# REQUIRED: NO
# DEFAULT: Etc/UTC
# VALID OPTIONS: Any valid IANA TZ formatted timezone. Refer to https://en.wikipedia.org/wiki/List_of_tz_database_time_zones.
TZ=Etc/UTC
# GID:
# You may wish to change the GroupID of the container's user. This allows it access certain resources on the host like certificates or files.
# REQUIRED: NO
# DEFAULT:
# VALID OPTIONS: Any valid UID/GID
#GID=
# WATCHDIR:
# Directory containing files you want to monitor for changes and trigger a browser reload.
# REQUIRED: NO
# DEFAULT:
# VALID OPTIONS: Any valid directory on the host
WATCHDIR=~/myWebProject
#
# AB-NGINX parameters
# refer to https://git.asifbacchus.dev/ab-docker/ab-nginx/wiki for more details
# NGINX_HTTP:
# Port to map on the host for ab-nginx to listen for HTTP connections.
# REQUIRED: NO
# DEFAULT: 80
# VALID OPTIONS: Any valid TCP port that does not cause conflicts in your environment
#NGINX_HTTP=80
# NGINX_HTTPS:
# Port to map on the host for ab-nginx to listen for HTTPS connections.
# REQUIRED: NO
# DEFAULT: 443
# VALID OPTIONS: Any valid TCP port that does not cause conflicts in your environment
#NGINX_HTTPS=443
# SERVER_NAMES:
# Space-delimited list of names to which the server should respond. This needs to match any certificates being used.
# REQUIRED: NO
# DEFAULT: _
# VALID OPTIONS: Any valid hostnames for your environment
SERVER_NAMES=_
# TLS13_ONLY:
# Use and accept only TLS version 1.3 connections. If false, both TLS versions 1.2 and 1.3 will be accepted.
# REQUIRED: NO
# DEFAULT: TRUE
# VALID OPTIONS: TRUE, FALSE
TLS13_ONLY=TRUE
#
# node-livereload parameters
# LR_PORT:
# Port on which the server should listen. Virtually all clients expect the default setting.
# REQUIRED: NO
# DEFAULT: 35729
# VALID OPTIONS: Any valid TCP port number that does not conflict within your environment
#LR_PORT=35729
# LR_EXTS:
# Comma-delimited list of extensions to watch for changes and trigger a browser reload.
# REQUIRED: NO
# DEFAULT: "html,xml,css,js,jsx,ts,tsx,php,py"
# VALID OPTIONS: Any valid file extension(s)
LR_EXTS="html,xml,css,js,jsx,ts,tsx,php,py"
# LR_EXCLUDE:
# Comma-delimited set of regular-expressions defining what to exclude from monitoring in addition to the defaults.
# Upstream node-livereload already ignores: ".git/,.svn/,.hg/"
# REQUIRED: NO
# DEFAULT: ".vscode/,.idea/,.tmp$,.swp$"
# VALID OPTIONS: Any valid RegEx that matches files or directories
LR_EXCLUDE=".vscode/,.idea/,.tmp$,.swp$"
# LR_DELAY:
# Amount of time in milliseconds before detecting a change and sending a trigger for a browser reload. Useful if you need to allow time for background recompilation, etc.
# REQUIRED: NO
# DEFAULT: 500
# VALID OPTIONS: Any integer representing a number of milliseconds (ms)
LR_DELAY=500
# LR_DEBUG:
# Whether or not to print diagnostic debugging messages about the server's operation. Usually a good idea to leave this set to 'true'.
# REQUIRED: NO
# DEFAULT: true
# VALID OPTIONS: true, false
LR_DEBUG=true
# LR_HTTPS:
# Whether or not to enable SSL/TLS on the server's listening port. This may be required depending on your domain and environment configuration.
# REQUIRED: NO
# DEFAULT: true
# VALID OPTIONS: true, false
LR_HTTPS=true
# CERT_HOSTNAME:
# Hostname to use if container is auto-generating a self-signed certificate.
# REQUIRED: NO
# DEFAULT: $HOSTNAME
# VALID OPTIONS: Any valid hostname
CERT_HOSTNAME=${SERVER_NAMES}
#EOF

36
.gitattributes vendored
View File

@ -4,9 +4,6 @@
# https://www.davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
* text=auto
# JetBrains: Do not touch line endings, but show diff
.idea/** -text
#
# The above will handle all files NOT found below
#
@ -23,7 +20,7 @@
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.md text
*.md text diff=markdown
*.tex text diff=tex
*.adoc text
*.textile text
@ -33,6 +30,7 @@
*.tsv text
*.txt text
*.sql text
*.ps1 text eol=crlf
# Graphics
*.png binary
@ -56,7 +54,22 @@
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
# web frontend stack -- force LF so SRI hashes are always correct
*.html text eol=lf
*.htm text eol=lf
*.css text eol=lf
*.min.css text eol=lf
*.js text eol=lf
*.min.js text eol=lf
# Visual Studio projects (Rider also)
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Serialisation
*.json text
@ -72,9 +85,6 @@
*.tgz binary
*.zip binary
# Docker build files
Dockerfile text eol=lf
# Text files where line endings should be preserved
*.patch -text
@ -82,8 +92,8 @@ Dockerfile text eol=lf
# Exclude files from exporting
#
.gitattributes export-ignore
.gitignore export-ignore
.gitkeep export-ignore
.vscode export-ignore
.idea export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.gitkeep export-ignore
.idea export-ignore
.vscode export-ignore

83
.gitignore vendored
View File

@ -1,10 +1,75 @@
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/numbered-bookmarks.json
*.code-workspace
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# Local History for Visual Studio Code
.history/
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# node modules
build/node_modules

View File

@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/projectSettingsUpdater.xml
/contentModel.xml
/modules.xml
/.idea.ab-livereload.iml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationConfigOverride">
<CommitMessageValidationOverride>
<option name="enabled" value="true" />
</CommitMessageValidationOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<enabledExtensions>
<entry key="MermaidLanguageExtension" value="false" />
<entry key="PlantUMLLanguageExtension" value="false" />
</enabledExtensions>
</component>
</project>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CommitMessageInspectionProfile">
<profile version="1.0">
<inspection_tool class="BodyLimit" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="SubjectBodySeparation" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="SubjectLimit" enabled="true" level="ERROR" enabled_by_default="true" />
</profile>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageValidationOverride">
<BoolValueOverride>
<option name="value" value="true" />
</BoolValueOverride>
</option>
</component>
</project>

View File

@ -0,0 +1,29 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="build/Dockerfile" type="docker-deploy" factoryName="dockerfile" server-name="Docker LOCAL">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="docker.asifbacchus.dev/ab-livereload/ab-livereload:4.0.0" />
<option name="buildArgs">
<list>
<DockerEnvVarImpl>
<option name="name" value="BUILD_DATE" />
<option name="value" value="2022-05-02" />
</DockerEnvVarImpl>
<DockerEnvVarImpl>
<option name="name" value="GIT_COMMIT" />
<option name="value" value="6970776aa8" />
</DockerEnvVarImpl>
<DockerEnvVarImpl>
<option name="name" value="INTERNAL_VERSION" />
<option name="value" value="4.0.0" />
</DockerEnvVarImpl>
</list>
</option>
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="build/Dockerfile" />
</settings>
</deployment>
<EXTENSION ID="com.jetbrains.rider.docker.debug" isFastModeEnabled="true" isPublishEnabled="true" />
<method v="2" />
</configuration>
</component>

View File

@ -1,58 +0,0 @@
#
# nodejs with livereload
#
# allow dynamic build by specifying base image as build arg
ARG NODE_TAG="16.4.2-alpine3.14"
FROM node:${NODE_TAG}
# change user id of node user
ARG NODE_UID=9999
RUN deluser --remove-home node \
&& addgroup -g ${NODE_UID} -S node \
&& adduser -G node -S -u ${NODE_UID} node
# add tini, timezone support
RUN apk --update --no-cache add tzdata tini
# labels
LABEL org.opencontainers.image.authors="Asif Bacchus <asif@asifbacchus.dev>"
LABEL org.opencontainers.image.title="livereload npm"
LABEL org.opencontainers.image.description="Dockerized npm livereload running under limited user account. Environment variables allow specifying files to watch/exclude and notification delay."
LABEL org.opencontainers.image.url="https://git.asifbacchus.dev/ab-docker/livereload"
LABEL org.opencontainers.image.documentation="https://git.asifbacchus.dev/ab-docker/livereload/raw/branch/master/README.md"
LABEL org.opencontainers.image.source="https://git.asifbacchus.dev/ab-docker/livereload.git"
# create default volume in case user forgets to map one
VOLUME [ "/var/watch" ]
# expose port
EXPOSE 35729
# default environment variables
ENV TZ=Etc/UTC
ENV NODE_ENV=production
ENV NPM_CONFIG_PREFIX=/home/node/.npm-global
ENV PATH=$PATH:/home/node/.npm-global/bin
ENV EXT="html,xml,css,js,jsx,ts,tsx,php,py"
ENV EXCLUDE=".git/,.svn/"
ENV DELAY=500
# install livereload for node user
USER node
WORKDIR /home/node
RUN mkdir -p .npm-global/bin .npm-global/lib \
&& npm install -g livereload
# run node via tini by default
ENTRYPOINT [ "/sbin/tini", "--" ]
CMD livereload /var/watch --debug --exts $EXT --exclusions $EXCLUDE -u true --wait $DELAY
# set build timestamp and version labels
ARG BUILD_DATE
LABEL org.opencontainers.image.version="16.4.2"
LABEL org.opencontainers.image.vendor="nodeJS"
LABEL dev.asifbacchus.image.name="livereload npm"
LABEL dev.asifbacchus.image.version="1.2"
LABEL org.opencontainers.image.created=${BUILD_DATE}
#EOF

43
LICENSE Normal file
View File

@ -0,0 +1,43 @@
AB-LIVERELOAD:
Copyright (c) 2021 Asif Bacchus <asif@asifbacchus.dev>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------------
NODE-LIVERELOAD:
Copyright (c) 2010-2021 Brian P. Hogan and Joshua Peek
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

252
README.md
View File

@ -1,23 +1,38 @@
# livereload (dockerized nodejs)
# ab-livereload (dockerized Livereload)
Containerized implementation of [npm livereload](https://www.npmjs.com/package/livereload) as forked by [Brian Hogan](https://github.com/napcs) ([github repo](https://github.com/napcs/node-livereload)). This container is based on Node running on Alpine and provides for easy version and node user UID/GID changes via build args. Time zone, monitored extensions, excluded files/directories and polling delays can be set via environment variables passed at runtime. The container runs under the non-root user *'node'* over the standard livereload port *35729* for compatibility with browser addons.
Containerized implementation of [node-livereload](https://www.npmjs.com/package/livereload) as forked by [Brian Hogan](https://github.com/napcs) ([github repo](https://github.com/napcs/node-livereload)). This container is based on Node running on Alpine and provides for easy version-pinning and node user UID/GID changes via build args. Time zone, monitored extensions, excluded files/directories and polling delays can be set via environment variables passed at runtime. The container runs under the non-root user *'node'* over the standard livereload port *35729* for compatibility with browser addons.
**Please note:** This container only generates notifications on port 35729 for livereload clients. It does NOT contain a webserver! Please see [Example run commands](#example-run-commands) and [Docker-Compose](#docker-compose) for how to add this to your webdev-stack.
**Please note:** This container only generates notifications for livereload clients. It does NOT contain a webserver! Please see [Examples](#examples) and [Docker-Compose](#docker-compose) for how to add this to your webdev-stack.
## Contents
<!-- toc -->
- [Private docker repository](#private-docker-repository)
- [Source/Issues](#source-issues)
- [Source/Issues](#sourceissues)
- [Environment variables](#environment-variables)
- [Volume mapping](#volume-mapping)
- [Example run commands](#example-run-commands)
- [Using environment variables](#using-environment-variables)
* [Certificate mount (HTTPS only)](#certificate-mount-https-only)
* [Content mount](#content-mount)
- [Commands](#commands)
- [Examples](#examples)
* [Run in HTTP (unsecured) mode](#run-in-http-unsecured-mode)
* [Run in HTTPS mode with supplied certificate](#run-in-https-mode-with-supplied-certificate)
* [Run in HTTPS mode with generated certificate](#run-in-https-mode-with-generated-certificate)
- [Livereload client](#livereload-client)
- [Permissions](#permissions)
* [Option 1: rebuild with different UID/GID](#option-1-rebuild-with-different-uidgid)
* [Option 2: specify runtime GID](#option-2-specify-runtime-gid)
* [Using Lets Encrypt](#using-lets-encrypt)
- [Docker-Compose](#docker-compose)
- [Brief changelog](#brief-changelog)
- [Final thoughts](#final-thoughts)
<!-- tocstop -->
## Private docker repository
If you prefer, you can also use my private repository to download possibly newer containers. Simply change `asifbacchus/livereload:tag` to `docker.asifbacchus.dev/livereload/livereload:tag`.
If you prefer, you can also use my private repository to download possibly newer containers. Simply change `asifbacchus/livereload:tag` to `docker.asifbacchus.dev/ab-livereload/ab-livereload:tag`.
## Source/Issues
@ -27,70 +42,209 @@ If you want the Dockerfile or if you want to bring an issue/request to my attent
All environment variables have sensible defaults and, thus, are *not* required to be set for the container to run successfully.
| variable | description | default |
| -------- | ------------------------------------------------------------ | --------------------------------- |
| TZ | Set the container's time zone. NO impact on runtime, included for convenience. | Etc/UTC |
| EXT | Defines monitored extensions. | html,xml,css,js,jsx,ts,tsx,php,py |
| EXCLUDE | Defines *paths* to ignore. | .git/,.svn/ |
| DELAY | Time (ms) between polling for changed files. | 500 |
| variable | description | default |
|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|
| TZ | Set the container's time zone. NO impact on runtime, included for convenience. | Etc/UTC |
| LR_PORT | Port over which Livereload will communicate. All clients presently expect port 35729, so I suggest leaving this alone. | 35729 |
| LR_EXTS | Defines monitored extensions. | html,xml,css,js,jsx,ts,tsx,php,py |
| LR_EXCLUDE | Comma-delimited regular-expressions (Regex) that define paths or files to ignore. These are *appended* to the node-livereload upstream defaults which ignore everything in the `.git/`, `.svn/` and `.hg/` directories.<br />**N.B.** You do *not* have to use JavaScript format. The script will automatically convert things to JS-RegEx. You do, however, need to escape any special characters. | .vscode/,.idea/,.tmp$,.swp$/ |
| LR_DELAY | Time (ms) between polling for changed files. | 500 |
| LR_DEBUG | Print informational messages to the console. Allows you to see Livereload working. | true |
| LR_HTTPS | Use HTTPS and WSS. In other words, use a certificate for SSL/TLS operation. | true |
| CERT_HOSTNAME | If the container needs to generate a self-signed certificate, this is the hostname it will use. | Container hostname -- this almost *never* what you really want so dont use this default. |
## Volume mapping
Obviously, this container needs something to monitor to determine whether changes have been made. This is accomplished via bind-mounting a directory from the host and is why 'polling' is necessary. Map a directory with files to be monitored to the container at */var/watch*.
The container needs two mounts to function correctly in HTTPS mode and only one in HTTP mode:
## Example run commands
### Certificate mount (HTTPS only)
If you do not bind-mount a directory, the container will create a volume for you. Bind-mounting or supplying a manually created volume is a much better option. The container reads certificates from this directory or, alternatively, will generate a certificate and key in this directory. Whatever you are mounting, it must map to */certs* in the container.
If you are mounting existing certificates:
- your certificate *must* be named *fullchain.pem* and be readable by the container user (UID=9999, GID=9999 by default)
- your private key *must* be named *privkey.pem* and be readable by the container user (UID=9999, GID=9999 by default)
> Important: The container runs as user *node* with UID and GID *9999* by default. You can change this by rebuilding the container or at runtime by supplying `--user "uid:gid"`. This may be necessary especially if you are bind-mounting since the container needs permissions to read both a supplied certificate *and* key. If it is generating said certificate and key, then obviously it needs *write* permissions to said mounted directory. If you are using a volume, permissions are easier. This is discussed in the [Permissions](#permissions) section.
### Content mount
Obviously, this container needs something to monitor to determine whether changes have been made. This is accomplished via bind-mounting a directory from the host and is why 'polling' is necessary. Mount a directory with files to be monitored to */watch* in the container.
> Starting with v4.0.0 you can watch up to 5 directories! Simply mount them to `/watch`, `/watch2` ... `/watch5'.
## Commands
The containers entrypoint script recognizes a few commands that tell it what you want to do:
| command | description |
|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| listen | Activate Livereload server using configured parameters.<br />Aliases: run \ | server \| start<br />`docker run --rm ... asifbacchus/livereload listen` |
| shell | Start container but drop to an Ash shell. Alternatvely, if you supply a command, the container will run that command in the shell, output results and then exit.<br />`docker run -it --rm ... asifbacchus/livereload shell`<br />`docker run --rm ... asifbacchus/livereload shell ls -lAsh /certs` |
| new-cert | Generate a new self-signed certificate with CN=CERT_HOSTNAME and matching DNS.1 value. Certificate and private key will be stored in */certs* as *fullchain.pem* and *privkey.pem*, respectively.<br />I strongly suggest running the container with `--user "uid:gid"` where the *gid* corresponds to one matching your webserver user, for example. That way your webserver would have read access to the generated private key. More information in the [Permissions](#permissions) section.<br />For example, running `docker run --rm -u "9999:6001" -v /etc/mycerts:/certs -e CERT_HOSTNAME=sub.domain.tld asifbacchus/livereload new-cert` would generate a new certificate and key pair in the */etc/mycerts/* directory on the host. Importantly, the private key would be readable by GID 6001 which, in this example, might be your webdev programs group including your webserver and you as the web-dev. |
| show-cert | Display the currently loaded certificate. This can be either a generated or a supplied certificate. Great way to confirm you mounted the right one!<br />`docker run --rm -v /etc/mycerts:/certs asifbacchus/livereload show-cert` |
## Examples
### Run in HTTP (unsecured) mode
```bash
docker run -d --name livereload --restart unless-stopped \
-v /home/user/Documents/myWebPage:/var/watch \
-p 35729:35729 \
asifbacchus/livereload:latest
docker run -d --rm -v /var/www:/watch:ro -e LR_HTTPS=false -p 35729:35729 asifbacchus/livereload listen
```
The above command will run the container with a name of *livereload*, restarting with your machine unless explicitly stopped, using the default livereload port. It will monitor all files in */home/user/Documents/myWebPage* for changes.
- `-d --rm`: run in the background and remove container upon exit
- `-v ...`: mount directory to monitor
- `-e LR_HTTPS=false`: run in HTTP instead of default HTTPS mode
- `-p 35729:35729`: map on all interfaces port 35729 on the host --> port 35729 in container
- `listen`: start the Livereload server
### Using environment variables
> Depending on your environment, you may *not* want to expose your Livereload server on all interfaces! You may want to map your port to something like `127.0.0.1:35729:35729` and then establishing an SSH-tunnel from your client. This is completely dependent on your environment and beyond the scope of this readme, sorry.
Say you want to only monitor html and css files and you want to ignore anything going on in your 'oldversion' folder. You can set environment variables as follows:
### Run in HTTPS mode with supplied certificate
```bash
docker run -d --name livereload --restart unless-stopped \
-v /home/user/Documents/myWebPage:/var/watch \
-p 35729:35729 \
-e EXT="html,css" \
-e EXCLUDE="oldversion/"
asifbacchus/livereload:latest
docker run -d --rm -v /etc/mycerts:/certs:ro -v /var/www:/watch:ro -p 35729:35729 asifbacchus/livereload listen
```
If you wanted a longer polling period, run as follows:
- all options same as above except weve included a bind-mount for the certificates
- HTTPS is the default operating mode, so it is *not necessary* to supply `LR_HTTPS=true`
### Run in HTTPS mode with generated certificate
You have two options for running with a self-signed generated certificate. If the container starts up in HTTPS mode and does not find an existing certificate, it will just make one for you. Alternatively, you can generate a certificate first and then run the container manually after -- there are use-cases for both options. Lets start with the second option first:
```bash
docker run -d --name livereload --restart unless-stopped \
-v /home/user/Documents/myWebPage:/var/watch \
-p 35729:35729 \
-e DELAY=3000 \
asifbacchus/livereload:latest
# create volume
docker volume create livereload-certs
# generate a certificate readable by GID=6001 in new volume and exit
docker run --rm --user "9999:6001" -v livereload-certs:/certs -e CERT_HOSTNAME=webdev.mydomain.tld asifbacchus/livereload new-cert
# run container using our new certificate
docker run -d --rm -v livereload-certs:/certs:ro -v /home/janedoe/myWebProject:/watch:ro -p 35729:35729 asifbacchus/livereload listen
```
Or, do it all in one-shot:
```bash
# write new certificate readable by GID=5100 to a bind-mounted directory and run container in one-step
docker run -d --rm --user "9999:5100" -v /etc/mycerts:/certs -v /home/janedoe/myWebProject:/watch:ro -e CERT_HOSTNAME=webdev.myserver.tld -p 35729:35729 asifbacchus/livereload listen
```
## Livereload client
There arent a lot of currently updated Livereload clients and/or browser addons out there, but the ones that do exist seem to only work over HTTP. In fact, that was the impetus behind creating this container. I develop on both *.dev* and *.app* domains -- both of which *require* HTTPS. As a result, I couldnt use any existing clients nor could I use the preconfigured node-livereload distribution via the command-line as version 1.x of this container did.
If you are running in an HTTP-permissive environment then lucky you! You can run this container in HTTP mode (`LR_HTTPS=false`) and use any of the clients and addons out there. If you want to use a snippet in your code instead of a client, simply insert this in the `<head>` of your page while using Livereload during dev:
```html
<script>
document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] +
':35729/livereload.js?snipver=1"></' + 'script>')
</script>
```
If, however, you are like me and want/need to use HTTPS then things are a little different. As I said, I cant find a single client or addon that works over HTTPS. Therefore, you *must* use a snippet in your webpage. Its the exact same as above, just use HTTPS instead -- again inserting in the `<head>` of your page:
```html
<script>
document.write('<script src="https://' + (location.host || 'localhost').split(':')[0] +
':35729/livereload.js?snipver=1"></' + 'script>')
</script>
```
Thats it. The advantage of using the snippet is that you dont need any clients or addons or any other garbage. Things just work so long as this is in your code. When youre done developing and ready to go to production, just remove the snippet and Livereload is disabled like it never existed.
## Permissions
The container is run as a limited user, *node*, with UID=9999 and GID=9999 by default. While this is much more secure than running as root, it does cause some complications especially with certificates. If you are supplying a certificate then the container user must be able to read both the certificate and the private key. If you are generating a certificate-key pair, then the container needs to be able to write them somewhere *and* they have to be generated with permissions making them usable to other services such as a web server. By default the container generates *fullchain.pem* with *644* permissions and *privkey.pem* with ***640*** permissions.
> Private keys are usually generated with *600* permissions. However, this is useless in our case since this container is not a web server and, thus, this key must be shared with at least one other service (i.e. the web server). That is why it is GROUP readable via the 6**4**0 permissions. As long as your other services are in the same group, they can use this generated certificate.
Heres the catch: By default, the node users GID is the same as their UID so the certificate is still only readable by the node user itself. There are two ways around this:
### Option 1: rebuild with different UID/GID
If you already have an infrastructure set up and need to plug this in, it might just be easier to alter the container users IDs so everything works in your environment. Clone the [git repo](https://git.asifbacchus.dev/ab-docker/livereload) and build as follows:
```bash
# clone repo
cd /usr/local/src
git clone https://git.asifbacchus.dev/ab-docker/livereload
# change directory and build
cd livereload/build
docker build --build-arg NODE_UID=1101 --build-arg NODE_GID=6001 --build-arg BUILD_DATE=$(date +%F_%T) -t livereload:latest .
```
- `NODE_UID`: optional -- desired UID for node user, in most cases the default is fine
- `NODE_GID`: desired GID for node user --> this is probably what you really want to change
- `BUILD_DATE`: optional -- applies container build date in a standardized label
- `livereload:latest`: you can of course choose any imageName:tag that suits you
Now a generated certificate-key pair will be owned by your defined UID and will be readable by any other user sharing the defined GID.
### Option 2: specify runtime GID
Maybe easier and more customizable, you can simply specify a GID to use at runtime so that things work in your environment. For example, lets say your web server has a *www-data* group with GID 6001 which already has access to your web files. Now you want to secure everything with a certificate and add Livereload. Ok, lets just run it with the right IDs:
```bash
# make a certificates directory with secure permissions
sudo mkdir /devCerts && chown root:www-data /devCerts && chmod 770 /devCerts
sudo ls -ldsh /devCerts
4.0K drwxrwx--- 2 root www-data 4.0K Jul 24 16:44 /devCerts
# create certificate with hostname myserver.tld
docker run --rm --user "9999:6001" -v /devCerts:/certs -e CERT_HOSTNAME=myserver.tld asifbacchus/livereload new-cert
# check our work -- looks good!
sudo ls -lAsh /devCerts
total 16K
4.0K -rw-r--r-- 1 9999 www-data 1.8K Jul 24 16:46 chain.pem
4.0K -rw-r--r-- 1 9999 www-data 1.5K Jul 24 16:46 dhparam.pem
4.0K -rw-r--r-- 1 9999 www-data 1.8K Jul 24 16:46 fullchain.pem
4.0K -rw-r----- 1 9999 www-data 3.2K Jul 24 16:46 privkey.pem
# run server
docker run -d --rm -v /devCerts:/certs:ro -v /usr/share/nginx/html:/watch -p 35729:35729 asifbacchus/livereload listen
```
### Using Lets Encrypt
I wont get too much into details here, but while Lets Encrypt is awesome it does present a little extra work when dealing with containers. Basically, you have to remember that the *live* directory contains symlinks to the latest version of your certificate. However, if you try to mount a symlink to your container youll quickly find out that doesnt work since the *target* of the link does not exist in the container also.
The most robust solution is setting up a post-renew script for your Lets Encrypt management solution that copies these certificates to a location your container can access and use the above information to use that certificate.
Alternatively, you can alter the *group* permissions on the */etc/letsencrypt/live* and */etc/letsencrypt/archive* directories. Then change the *group* permissions on */etc/letsencrypt/archive/certname/privkey1.pem* to allow reading it. This example is for Certbot, but most LE managers should work similarly. Assuming your LE client maintains permissions (like Certbot), the GID in question can read what is needed.
A final note: You **cannot** bind-mount `/etc/letsencrypt/live/certname:/certs`. Its the same reason as above, it will bind symlinks that are not valid within the container. You need to bind-mount each individual link so it is resolved by Docker when running the container:
```bash
docker run --rm ... -v /etc/letsencrypt/live/certname/fullchain.pem:/certs/fullchain.pem:ro -v /etc/letsencrypt/certname/privkey.pem:/certs/privkey.pem:ro ...
```
## Docker-Compose
It is very likely this would be integrated via docker-compose with an existing webserver container (like Nginx or Apache). Add this to your docker-compose.yml:
Containers, like people, often get lonely and enjoy working with others. In the case of this container, it is quite useless if not paired with at least a web server. Ive included the core of the actual set up I use for web development -- a customized NGINX container and this Livereload container all secured with a certificate so everything even in testing is working over TLS like in real life. Take a look at the *docker-compose.yml* for more details. If youre using Lets Encrypt certificates, read the section above and remember to mount the files individually. If you are interested in my AB-NGINX container which has several useful additions to the official container including a healthcheck, then [check out the repo](https://git.asifbacchus.dev/ab-docker/ab-nginx).
```yaml
livereload:
image: asifbacchus/livereload:latest
container_name: livereload
restart: unless-stopped
volumes:
- /local/directory/to/watch:/var/watch
ports:
- 35729:35729
environment:
- TZ=Region/Locality
```
## Brief changelog
Obviously, you should change `/local/directory/to/watch` and `TZ=Region/Locality` as needed. Also, please remember to verify the scope of port mapping as appropriate to your environment! You may *not* need to bind to all addresses as I have in this example.
### Version 3.0.0
- `npm` no longer present in final build, too many un-patched security vulnerabilities.
- multi-stage build with the final image being a minimal node installation directly on the Alpine base.
- container is now ~50% smaller due to multi-stage build :-)
### Version 2.x
Starting with the 2.x version line, Ive added two *very* important features:
- SSL/TLS support with auto-generated self-signed certificates if you dont have your own certificates
- Healthcheck allowing for proper integration using docker-compose into a webstack
## Final thoughts
That's it. Hopefully this is useful for you and makes it easier to run a live-reload server without having to install node on your machine. As always, let me know if you have any issues/suggestions by filing an issue on either git repo.
That's it. Hopefully this is useful for you and makes it easier to run a live-reload server without having to install node on your machine. As always, let me know if you have any issues/suggestions or if something isnt well documented by filing an issue on either git repo.

96
build/Dockerfile Normal file
View File

@ -0,0 +1,96 @@
# node-livereload server supporting SSL/TLS
# allow dynamic building by specifying base image elements as build-args
ARG NODE_VERSION=16
ARG ALPINE_VERSION=3.15
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} as builder
ARG NODE_VERSION
ARG ALPINE_VERSION
# install node dependences
WORKDIR /build
COPY [ "package.json", "package-lock.json", "./" ]
RUN npm ci --production
# final container
FROM alpine:${ALPINE_VERSION} as final
ARG NODE_VERSION
ARG ALPINE_VERSION
# create new node user with set UID and GID from build-args and create volume directories
ARG NODE_UID=9999
ARG NODE_GID=9999
RUN addgroup -g ${NODE_GID} -S node \
&& adduser -G node -S -u ${NODE_UID} node \
&& mkdir /watch /watch2 /watch3 /watch4 /watch5 /certs \
&& chown root:node /certs \
&& chmod 770 /certs
# create default volumes in case user forgets, expose default port
VOLUME [ "/watch", "/certs" ]
EXPOSE 35729
# add tini, timezone support, nodejs and create certificate directories
RUN apk --update --no-cache add \
tini \
tzdata \
openssl \
nodejs~${NODE_VERSION} \
&& apk --update --no-cache upgrade
# labels
MAINTAINER Asif Bacchus <asif@asifbacchus.dev>
LABEL maintainer="Asif Bacchus <asif@asifbacchus.dev>"
LABEL dev.asifbacchus.docker.internalName="ab-livereload"
LABEL org.opencontainers.image.authors="Asif Bacchus <asif@asifbacchus.dev>"
LABEL org.opencontainers.image.description="Dockerized node-livereload supporting TLS and running under limited user account. Environment variables allow specifying files to watch/exclude and notification delay."
LABEL org.opencontainers.image.documentation="https://git.asifbacchus.dev/ab-docker/ab-livereload/raw/branch/main/README.md"
LABEL org.opencontainers.image.source="https://git.asifbacchus.dev/ab-docker/ab-livereload.git"
LABEL org.opencontainers.image.title="ab-livereload"
LABEL org.opencontainers.image.url="https://git.asifbacchus.dev/ab-docker/ab-livereload"
LABEL org.opencontainers.image.vendor="Asif Bacchus <asif@asifbacchus.dev>"
# default environment variables
ENV NODE_ENV=production
ENV TZ="Etc/UTC"
ENV LR_PORT=35729
ENV LR_EXTS="html,xml,css,js,jsx,ts,tsx,php,py"
ENV LR_EXCLUDE=".vscode/,.idea/,.tmp$,.swp$"
ENV LR_DELAY=500
ENV LR_DEBUG=true
ENV LR_HTTPS=true
ENV CERT_HOSTNAME=""
# set-up application and copy dependencies from builder
WORKDIR /home/node
COPY [ "selfsigned.cnf", "/etc/selfsigned.cnf" ]
COPY [ "entrypoint.sh", "/usr/local/bin/entrypoint.sh" ]
COPY --chown=node:node [ "ab-livereload.js", "/home/node/"]
COPY --from=builder [ "/build/node_modules", "/home/node/node_modules" ]
RUN chown -R node:node /home/node/* \
&& chmod 644 /home/node/ab-livereload.js \
&& chmod 755 /usr/local/bin/entrypoint.sh \
&& chmod 644 /etc/selfsigned.cnf
HEALTHCHECK \
--interval=10s \
--timeout=5s \
--start-period=60s \
--retries=3 \
CMD wget --spider -T 3 -q localhost:3000/api/v1/health || exit 1
# switch to node user, run entrypoint script by default
USER node
WORKDIR /home/node
ENTRYPOINT [ "/sbin/tini", "-e", "143", "--", "/usr/local/bin/entrypoint.sh" ]
# set build timestamp and version labels
ARG INTERNAL_VERSION
ARG GIT_COMMIT
ARG BUILD_DATE
LABEL org.opencontainers.image.version="NODE=${NODE_VERSION}, node-livereload=0.9.3"
LABEL dev.asifbacchus.docker.internalVersion=${INTERNAL_VERSION}
LABEL org.opencontainers.image.revision=${GIT_COMMIT}
LABEL org.opencontainers.image.created=${BUILD_DATE}
#EOF

100
build/ab-livereload.js Normal file
View File

@ -0,0 +1,100 @@
/*
Implement node-livereload over HTTP or HTTPS connection with integrated
health-check, signal handling and graceful socket shutdown.
*/
// load required modules
const livereload = require('livereload');
const fs = require('fs');
const http = require('http');
// health check object
const healthCheck = {
app: healthCheckApp(),
server: function () {
return http.createServer(this.app);
},
start: function () {
this.server().listen(3000);
},
stop: function (callback) {
this.server().close(callback());
}
};
// set LiveReload server options
const extraExclusions = process.env.LR_EXCLUDE.split(",");
extraExclusions.forEach((exclusion, idx) => {
extraExclusions[idx] = new RegExp(exclusion);
});
// noinspection SpellCheckingInspection
const lrOptions = {
port: process.env.LR_PORT,
exts: process.env.LR_EXTS,
exclusions: extraExclusions,
usePolling: true,
delay: process.env.LR_DELAY
};
if (process.env.LR_DEBUG === 'true') {
lrOptions.debug = true;
console.log("[Debug output ENABLED]");
}
if (process.env.LR_HTTPS === 'true') {
lrOptions.https = {
cert: fs.readFileSync('/certs/fullchain.pem'),
key: fs.readFileSync('/certs/privkey.pem')
};
console.log("[HTTPS mode]");
}
else {
console.log("[HTTP mode]");
}
// start LiveReload server
// noinspection JSVoidFunctionReturnValueUsed
const lrServer = livereload.createServer(lrOptions, healthCheck.start());
lrServer.watch(['/watch', '/watch2', '/watch3', '/watch4', '/watch5']);
// graceful termination on signals
const termSignals = {
'SIGHUP': 1,
'SIGINT': 2,
'SIGTERM': 15
};
const shutdown = async (signal, value) => {
console.log("shutting down...\n");
await lrServer.close();
await healthCheck.stop(() => {
console.log("health-check STOPPED\n");
});
console.log(`server stopped after receiving ${signal}(${value}).`);
}
// iterate signals and create listener for each
Object.keys(termSignals).forEach((signal) => {
process.on(signal, () => {
console.log("\n! received signal:", signal, " !");
shutdown(signal, termSignals[signal]).then(() => {
process.exit(128 + termSignals[signal]);
});
});
});
//
// functions
function healthCheckApp() {
const express = require('express');
const app = express();
const router = express.Router();
router.use((req, res, next) => {
res.header('Access-Control-Allow-Methods', 'GET');
next();
});
router.get('/health', (req, res) => {
res.status(200).send('Ok');
});
return app.use('/api/v1', router);
}

177
build/entrypoint.sh Normal file
View File

@ -0,0 +1,177 @@
#!/bin/sh
#
# entrypoint script for ab-livereload container
#
# functions
certificateCheckExist() {
if [ -n "$(find /certs/ -type d -empty -print)" ]; then
printf "noexist"
elif ! [ -r "/certs/fullchain.pem" ]; then
printf "noread_certificate"
elif ! [ -r "/certs/privkey.pem" ]; then
printf "noread_key"
else
printf "ok"
fi
}
certificateGenerateNew() {
# generate self-signed certificate and export as PFX
printf "\nGenerating new self-signed certificate:\n"
# shellcheck disable=SC3028
if [ -z "$CERT_HOSTNAME" ]; then export CERT_HOSTNAME="$HOSTNAME"; fi
# create placeholder files to set permissions
touch /certs/fullchain.pem && chmod 644 /certs/fullchain.pem
touch /certs/privkey.pem && chmod 640 /certs/privkey.pem
# generate certificate
if ! openssl req -new -x509 -days 365 -nodes -out /certs/fullchain.pem -keyout /certs/privkey.pem -config /etc/selfsigned.cnf; then
printf "\nUnable to generate certificate. Is your 'certs' directory writable by this container?\n\n"
exit 55
fi
cp /certs/fullchain.pem /certs/chain.pem
# generate dh-params for TLS1.2
if ! openssl dhparam -dsaparam -out /certs/dhparam.pem 4096; then
printf "\nUnable to generate dh-params. Is you 'certs' directory writable by this container?\n\n"
exit 56
fi
# print message to user
printf "\n\nA self-signed certificate has been generated and saved in the location mounted to '/certs' in this container.\n"
printf "The certificate and private key are PEM formatted with names 'fullchain.pem' and 'privkey.pem', respectively.\n"
printf "Remember to import 'fullchain.pem' to the trusted store on any client machines or you will get warnings.\n\n"
}
certificateShow() {
printf "\nCurrently loaded certificate:\n"
certStatus="$(certificateCheckExist)"
case "$certStatus" in
noexist)
printf "[ERROR]: No certificate is loaded (certificate directory empty).\n\n"
exit 51
;;
noread_certificate)
printf "[ERROR]: Cannot read loaded certificate.\n\n"
exit 52
;;
noread_key)
printf "\n[WARNING]: Cannot find private key associated with certificate!\n\n"
;;
esac
if ! openssl x509 -noout -text -nameopt align,multiline -certopt no_pubkey,no_sigdump -in /certs/fullchain.pem; then
printf "\n[ERROR]: Unable to display loaded certificate.\n\n"
exit 52
fi
}
convertCaseLower() {
printf "%s" "$1" | tr "[:upper:]" "[:lower:]"
}
# default variable values
doCertNew=0
doCertShow=0
doServer=0
doShell=0
# clean-up boolean environment variables for this script and JavaScript
enableHTTPS="$(convertCaseLower "$LR_HTTPS")"
enableDebug="$(convertCaseLower "$LR_DEBUG")"
export LR_HTTPS="$enableHTTPS"
export LR_DEBUG="$enableDebug"
# process action parameter
case "$1" in
listen | server | run | start)
doServer=1
;;
shell)
doShell=1
;;
new-cert)
doCertNew=1
;;
show-cert)
doCertShow=1
;;
*)
# invalid or unknown option
printf "\nUnknown action requested: %s\n" "$1"
printf "Valid actions: [listen | server | run | start] | shell | new-cert | show-cert\n\n"
exit 1
;;
esac
# action: run server
if [ "$doServer" -eq 1 ]; then
printf "Starting ab-livereload server:\n"
# https pre-flight check
if [ "$enableHTTPS" = "true" ]; then
certStatus="$(certificateCheckExist)"
case "$certStatus" in
noexist)
printf "[Generating certificate]\n"
certificateGenerateNew
;;
noread_certificate)
printf "[Checking mounted certificate]"
printf "\nERROR: SSL/TLS mode selected but unable to read certificate!\n\n"
exit 52
;;
noread_key)
printf "[Checking mounted certificate]"
printf "\nERROR: SSL/TLS mode selected but unable to read private key!\n\n"
exit 53
;;
ok)
printf "[Certificate OK]\n"
;;
esac
fi
exec node ab-livereload.js
exit "$?"
fi
# action: drop to shell
if [ "$doShell" -eq 1 ]; then
if [ -z "$2" ]; then
printf "\nExecuting interactive shell:\n"
exec /bin/sh
else
shift
printf "\nExecuting shell: '%s'\n" "$*"
exec /bin/sh -c "$*"
fi
exit "$?"
fi
# action: generate new self-signed certificate
if [ "$doCertNew" -eq 1 ]; then
certificateGenerateNew
exit 0
fi
# action: show loaded certificate
if [ "$doCertShow" -eq 1 ]; then
certificateShow
exit 0
fi
# failsafe exit - terminate with code 99: this code should never be executed!
exit 99
# exit codes:
# 0: normal exit, no errors
# 1: invalid or invalid parameter passed to script
# 2: interactive shell required
# 50: certificate errors
# 51: certificate directory empty
# 52: unable to read certificate/chain
# 53: unable to read private key
# 55: unable to generate new certificate
# 56: unable to generate dh-params
# 99: code error
#EOF

1208
build/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

8
build/package.json Normal file
View File

@ -0,0 +1,8 @@
{
"name": "ab-livereload",
"version": "3.0.0",
"dependencies": {
"express": "^4.17.1",
"livereload": "^0.9.3"
}
}

16
build/selfsigned.cnf Normal file
View File

@ -0,0 +1,16 @@
default_bits = 4096
default_md = sha256
distinguished_name = dn
req_extensions = san
x509_extensions = san
prompt = no
[dn]
organizationName = LiveReload WebServer
CN = ${ENV::CERT_HOSTNAME}
[san]
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${ENV::CERT_HOSTNAME}

54
docker-compose.yml Normal file
View File

@ -0,0 +1,54 @@
#
# ab-livereload stack
#
version: '2.4'
services:
livereload:
image: docker.asifbacchus.dev/livereload/livereload:latest
container_name: livereload
volumes:
- ./certs:certs
- ${WATCHDIR}:/watch:ro
networks:
- network
ports:
- "${LR_PORT:-35729}:${LR_PORT:-35729}"
environment:
- TZ=${TZ}
- LR_PORT=${LR_PORT}
- LR_EXTS=${LR_EXTS}
- LR_EXCLUDE=${LR_EXCLUDE}
- LR_DELAY=${LR_DELAY}
- LR_DEBUG=${LR_DEBUG}
- LR_HTTPS=${LR_HTTPS}
- CERT_HOSTNAME=${CERT_HOSTNAME}
user: "9999:${GID:-9999}"
command: listen
ab-nginx:
image: docker.asifbacchus.dev/nginx/ab-nginx:latest
container_name: ab-nginx
depends_on:
livereload:
condition: service_healthy
volumes:
- ./certs/certs:ro
- ${WATCHDIR}:/usr/share/nginx/html:ro
- ./nginx/config:/etc/nginx/config:ro
networks:
- network
ports:
- "${NGINX_HTTP:-80}:80"
- "${NGINX_HTTPS:-443}:443"
environment:
- TZ=${TZ}
- SERVER_NAMES=${SERVER_NAMES}
- TLS13_ONLY=${TLS13_ONLY}
user: "8080:${GID:-8080}"
networks:
network:
external: false
#EOF