From 4411515b34c5630e240dfb4ce6190e4008615344 Mon Sep 17 00:00:00 2001
From: Lucendio <dev@lucend.io>
Date: Mon, 5 Oct 2020 00:26:19 +0200
Subject: [PATCH] Polish 'creating to' server test

* adjust app readme
* revert change in server 'scripts'
* renamed 'stack' into 'hack' and split up Makefile into two to better separate
  devops scenarios from scoped deps management
* updated readme according to the make target changes
---
 .gitignore                        |   2 +-
 Makefile                          | 259 +++++++++---------------------
 README.md                         | 126 ++++++++++-----
 app/README.md                     |  12 +-
 app/client/package-lock.json      |   2 +-
 app/client/package.json           |   2 +-
 app/server/package.json           |   2 +-
 hack/Makefile                     | 152 ++++++++++++++++++
 {stack => hack}/local.mongod.conf |   0
 hack/versions/mongodb.256.sums    |   2 +
 hack/versions/nodejs.256.sums     |   2 +
 stack/versions/mongodb.256.sums   |   2 -
 stack/versions/nodejs.256.sums    |   2 -
 13 files changed, 324 insertions(+), 241 deletions(-)
 create mode 100644 hack/Makefile
 rename {stack => hack}/local.mongod.conf (100%)
 create mode 100644 hack/versions/mongodb.256.sums
 create mode 100644 hack/versions/nodejs.256.sums
 delete mode 100644 stack/versions/mongodb.256.sums
 delete mode 100644 stack/versions/nodejs.256.sums

diff --git a/.gitignore b/.gitignore
index 44476bb..a103539 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-./local
+.local
diff --git a/Makefile b/Makefile
index a489ad5..fd19a65 100644
--- a/Makefile
+++ b/Makefile
@@ -4,91 +4,62 @@ SHELL = /usr/bin/env bash -eo pipefail
 
 
 MKFILE_DIR = $(abspath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
-LOCAL_DIR = $(MKFILE_DIR)/.local
+LOCAL_DIR = $(abspath $(MKFILE_DIR)/.local)
+
 
-BIN_DIR = $(LOCAL_DIR)/bin
-LIB_DIR = $(LOCAL_DIR)/lib
-TEMP_DIR = $(LOCAL_DIR)/tmp
 DATA_DIR = $(LOCAL_DIR)/data
 LOG_DIR = $(LOCAL_DIR)/logs
-STACK_DIR = $(MKFILE_DIR)/stack
-
-APP_NODE_MODULE_DIRS = $(foreach dir, client server, $(subst %,$(dir),$(MKFILE_DIR)/app/%/node_modules))
-
-
-
-NODEJS_VERSION ?= 12.16.3
-NPM_VERSION ?= 6.14.4
-MONGODB_VERSION ?= 4.2.6
-REACT_APP_VERSION = 3.4.1
-
-
-
-PLATFORM := $(shell if echo $$OSTYPE | grep -q darwin; then echo darwin; else echo linux; fi)
 
 
-NODEJS_URL = https://nodejs.org/dist/v$(NODEJS_VERSION)/node-v$(NODEJS_VERSION)-$(PLATFORM)-x64.tar.gz
-NODEJS_ARTIFACT = $(TEMP_DIR)/node-v$(NODEJS_VERSION)-$(PLATFORM)-x64.tar.gz
-NODEJS_ARCHIVE = $(patsubst %.tar.gz,%,$(notdir $(NODEJS_ARTIFACT)))
-NODEJS_BIN = $(BIN_DIR)/node
-NPM_BIN = $(BIN_DIR)/npm
-NODEJS_SHA256 ?= $(shell cat $(STACK_DIR)/versions/nodejs.256.sums | grep v$(NODEJS_VERSION)-$(PLATFORM)-x64 | awk '{ print $$ 2 }')
-
-
-ifeq ($(PLATFORM), darwin)
-MONGODB_URL = https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-$(MONGODB_VERSION).tgz
-else ifeq ($(PLATFORM), linux)
-# NOTE: hard-coded Debian version. Others can be found here: https://www.mongodb.com/download-center/community
-MONGODB_URL = https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian10-$(MONGODB_VERSION).tgz
-else
-	fail 'Unknown platform. No condition met'
-endif
-MONGODB_ARTIFACT = $(TEMP_DIR)/mongodb-$(PLATFORM)-$(MONGODB_VERSION).tar.gz
-MONGODB_ARCHIVE = $(patsubst %.tar.gz,%,$(notdir $(MONGODB_ARTIFACT)))
-MONGODB_BIN = $(BIN_DIR)/mongod
-MONGODB_SHA256 ?= $(shell cat $(STACK_DIR)/versions/mongodb.256.sums | grep $(PLATFORM)-x86_64-$(MONGODB_VERSION) | awk '{ print $$ 2 }')
-
-
-
-SERVER_PUBLIC_URL ?= http://localhost:3000
-CLIENT_BUILD_PATH ?= $(MKFILE_DIR)/app/server/src/public
+APP_NODE_MODULE_DIRS = $(foreach dir, client server, $(subst %,$(dir),$(MKFILE_DIR)/app/%/node_modules))
 
 
 
 
 default: all
 
-all: install-stack install-deps
+all: install build
 
 
 
-install-stack: node npm mongod
 
+.PHONY: install
+install: $(APP_NODE_MODULE_DIRS)
+$(APP_NODE_MODULE_DIRS):
+	cd $(@D) \
+	&& npm install
 
-install-deps: $(APP_NODE_MODULE_DIRS)
 
+$(LOCAL_DIR)/%/:
+	mkdir -p $(@)
 
 
-.PHONY: run-db
-run-db: export PATH := $(BIN_DIR):$(PATH)
-run-db: | $(LOG_DIR)/ $(DATA_DIR)/
-	mkdir -p $(DATA_DIR)/db
-	mongod --config $(STACK_DIR)/local.mongod.conf
+.PHONY: clean
+clean:
+	for nodeModulesDir in $(APP_NODE_MODULE_DIRS); do \
+		rm -rf "$${nodeModulesDir}"; \
+	done
 
 
-.PHONY: run-local
-run-local: export PATH := $(BIN_DIR):$(PATH)
-run-local: build
-	cd $(MKFILE_DIR)/app/server \
-	&& npm run start:dev
+
+.PHONY: build
+build: CLIENT_BUILD_PATH ?= $(MKFILE_DIR)/app/server/src/public
+build: SERVER_PUBLIC_URL ?= http://localhost:3000
+build:
+	rm -rf $(CLIENT_BUILD_PATH)
+	cd $(MKFILE_DIR)/app/client \
+	&& \
+		PUBLIC_URL=$(SERVER_PUBLIC_URL) \
+		BUILD_PATH=$(CLIENT_BUILD_PATH) \
+		node ./scripts/build.js
 
 
 .PHONY: test
-test: export PATH := $(BIN_DIR):$(PATH)
 test: randomString = $(shell LC_ALL=C tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w 32 | head -n 1)
 test:
 	cd $(MKFILE_DIR)/app/client \
 	&& npm run test
+
 	cd $(MKFILE_DIR)/app/server \
 	&& \
 		PORT=3002 \
@@ -97,46 +68,46 @@ test:
 	 	npm run test
 
 
-.PHONY: test-client-local
-test-client-local: export PATH := $(BIN_DIR):$(PATH)
-test-client-local:
-	cd $(MKFILE_DIR)/app/client \
-	&& npm run test:dev
 
 
-.PHONY: build
-build: export PATH := $(BIN_DIR):$(PATH)
-build:
-	rm -rf $(CLIENT_BUILD_PATH)
+.PHONY: dev-test-client
+dev-test-client:
 	cd $(MKFILE_DIR)/app/client \
-	&& PUBLIC_URL=$(SERVER_PUBLIC_URL) \
-		BUILD_PATH=$(CLIENT_BUILD_PATH) \
-		node ./scripts/build.js
+	&& npm run test:dev
 
 
+.PHONY: dev-start-db
+dev-start-db: | $(LOG_DIR)/ $(DATA_DIR)/
+	mkdir -p $(DATA_DIR)/db
+	mongod --config $(MKFILE_DIR)/hack/local.mongod.conf
 
-.PHONY: clean
-clean: clean-stack clean-modules
 
+.PHONY: dev-start-app
+dev-start-app: build
+	cd $(MKFILE_DIR)/app/server \
+	&& npm run start:dev
 
 
 
-.PHONY: start
-start: export PATH := $(BIN_DIR):$(PATH)
-start: SERVER_PUBLIC_URL = http://localhost:3001
-start: build
-start: | $(DATA_DIR)/ $(LOG_DIR)/
+.PHONY: run
+run: PUBLIC_URL = http://localhost
+run: SERVER_PORT = 3001
+run: DB_HOST = localhost
+run: DB_PORT = 27017
+run: | $(DATA_DIR)/ $(LOG_DIR)/
 	mkdir -p $(DATA_DIR)/db
 
+	make build SERVER_PUBLIC_URL=$(PUBLIC_URL):$(SERVER_PORT)
+
 	(exec mongod \
-		--port 27017 \
-		--bind_ip localhost \
+		--port $(DB_PORT) \
+		--bind_ip $(DB_HOST) \
 		--logpath /dev/stdout \
 		--dbpath $(DATA_DIR)/db \
 	) & PIDS[1]=$$!; \
 	\
-	(PORT=3001 \
-	MONGODB_URL=mongodb://localhost:27017/todo-app \
+	(PORT=$(SERVER_PORT) \
+	MONGODB_URL=mongodb://$(DB_HOST):$(DB_PORT)/todo-app \
 	JWT_SECRET=myjwtsecret \
 	exec node $(MKFILE_DIR)/app/server/src/index.js \
 	) & PIDS[2]=$$!; \
@@ -146,110 +117,24 @@ start: | $(DATA_DIR)/ $(LOG_DIR)/
 
 
 
-
-$(LOCAL_DIR)/%/:
-	mkdir -p $(@)
-
-
-.PHONY: node
-node: $(NODEJS_BIN)
-$(NODEJS_BIN): | $(NODEJS_ARTIFACT) $(BIN_DIR)/
-	@ [ $$(openssl dgst -sha256 "$(NODEJS_ARTIFACT)" | awk '{ print $$ 2 }') == $(NODEJS_SHA256) ] || ( echo "Invalid SHA256." && rm $(NODEJS_ARTIFACT) && exit 1 )
-	tar \
-		--extract \
-		--verbose \
-		--strip-components 2 \
-		--directory "$(BIN_DIR)" \
-		--file "$(NODEJS_ARTIFACT)" \
-		$(NODEJS_ARCHIVE)/bin/node
-	chmod +x "$@"
-
-$(NODEJS_ARTIFACT): | $(TEMP_DIR)/
-	curl \
-		--silent --show-error \
-		--location \
-		$(NODEJS_URL) \
-		> $(NODEJS_ARTIFACT)
-
-.PHONY: npm
-npm: $(NPM_BIN)
-npm: export PATH := $(BIN_DIR):$(PATH)
-$(NPM_BIN): | $(NODEJS_BIN)
-	mkdir -p $(LIB_DIR)
-	tar \
-		--extract \
-		--verbose \
-		--strip-components 2 \
-		--directory "$(LIB_DIR)" \
-		--file "$(NODEJS_ARTIFACT)" \
-		$(NODEJS_ARCHIVE)/lib/node_modules
-	chmod +x $(LIB_DIR)/node_modules/npm/bin/npm-cli.js
-	ln -s $(LIB_DIR)/node_modules/npm/bin/npm-cli.js $(NPM_BIN)
-	npm install -g npm@$(NPM_VERSION)
-
-
-.PHONY: mongod
-mongod: $(MONGODB_BIN)
-$(MONGODB_BIN): | $(MONGODB_ARTIFACT) $(BIN_DIR)/
-	@ [ $$(openssl dgst -sha256 "$(MONGODB_ARTIFACT)" | awk '{ print $$ 2 }') == $(MONGODB_SHA256) ] || ( echo "Invalid SHA256." && rm $(MONGODB_ARTIFACT) && exit 1 )
-	mkdir -p $(TEMP_DIR)/$(MONGODB_ARCHIVE)
-	tar \
-		--extract \
-		--verbose \
-		--strip-components 2 \
-		--directory "$(TEMP_DIR)/$(MONGODB_ARCHIVE)" \
-		--file "$(MONGODB_ARTIFACT)"
-	mv $(TEMP_DIR)/$(MONGODB_ARCHIVE)/mongod "$@"
-	chmod +x "$@"
-	rm -rf $(TEMP_DIR)/$(MONGODB_ARCHIVE)
-
-$(MONGODB_ARTIFACT): | $(TEMP_DIR)/
-	curl \
-		--silent --show-error \
-		--location \
-		$(MONGODB_URL) \
-		> $(MONGODB_ARTIFACT)
-
-
-.PHONY: clean-stack
-clean-stack:
-	rm -rf \
-		$(LOCAL_DIR)
-
-
-
-.PHONY: $(APP_NODE_MODULE_DIRS)
-$(APP_NODE_MODULE_DIRS): export PATH := $(BIN_DIR):$(PATH)
-$(APP_NODE_MODULE_DIRS): $(MKFILE_DIR)/app/%/node_modules:
-	cd $(@D) \
-	&& npm install
-
-.PHONY: clean-modules
-clean-modules:
-	for nodeModulesDir in $(APP_NODE_MODULE_DIRS); do \
-		rm -rf "$${nodeModulesDir}"; \
-	done
-
-
-
-
-
-.PHONY: update-react-app-template
-update-react-app-template: export PATH := $(BIN_DIR):$(PATH)
-update-react-app-template:
-	rm -rf $(TEMP_DIR)/npm-project-scope
-	mkdir -p $(TEMP_DIR)/npm-project-scope
-	cd $(TEMP_DIR)/npm-project-scope \
-		&& npm install --save-dev react-scripts@$(REACT_APP_VERSION) \
-		&& npm init react-app $(TEMP_DIR)/npm-project-scope/cra
-	cp \
-		$(TEMP_DIR)/npm-project-scope/cra/src/* \
-		$(MKFILE_DIR)/app/client/src/
-	cp \
-		$(TEMP_DIR)/npm-project-scope/cra/public/* \
-		$(MKFILE_DIR)/app/client/public/
-	cp \
-		$(TEMP_DIR)/npm-project-scope/cra/package.json \
-		$(MKFILE_DIR)/app/client/
-	rm -rf \
-		$(MKFILE_DIR)/app/client/src/logo.svg
+.PHONY: deps
+deps:
+	cd $(MKFILE_DIR)/hack \
+	&& \
+		NODEJS_VERSION=12.16.3  \
+		NPM_VERSION=6.14.4      \
+		MONGODB_VERSION=4.2.6   \
+		make install
+	@echo ''
+	@echo 'In order to use the installed dependencies, the $$PATH variable must be adjusted.'
+	@echo 'Run:'
+	@echo ''
+	@echo '  export PATH=$$(pwd)/.local/bin:$${PATH}'
+	@echo ''
+
+
+.PHONY: nuke
+nuke:
+	cd $(MKFILE_DIR)/hack \
+		&& make clean
+	make clean
diff --git a/README.md b/README.md
index 652b6e3..f1af2cc 100644
--- a/README.md
+++ b/README.md
@@ -2,85 +2,131 @@ Lecture: DevOps - application
 =============================
 
 
-This repository contains the [application](./app/README.md) that should be used as *deployable workload* in the
-[exercise](https://github.com/lucendio/lecture-devops-material/blob/master/exercise.md) implementation.  
+This repository contains the [application](./app/README.md) that should be used as *deployable workload* for the
+[exercise](https://github.com/lucendio/lecture-devops-material/blob/master/assignments/exercise.md) implementation.  
 
 
 ### Getting started 
 
 For more information regarding the app, please take a look into its [README](./app/README.md).
 
-The `Makefile` is the main entry point for this repository. It's meant to locally play and mess around with the
-application to figure out how it works and to tear it apart if necessary. Additionally, it should document all  
-invocations relevant to help you adapt this application as *workload* for the exercise implementation. 
+The `Makefile` in this directory can be seen as the main entry point for this repository. It's meant to locally run the
+application and mess around with the source code in order to better understand how it works and to be able to tear it
+apart if necessary.
+Additionally, it documents various invocations that may help you adapting this application as *workload* for the exercise. 
 
-**_Please note, that this `Makefile` is solely meant to showcase how to interact with the application and the code base.
-It is not recommended to invoke `make` targets from the CI/CD, but rather use automation-specific interfaces 
-(e.g. `Jenkinsfile`, `.travis.yml`, etc.), which would then invoke commands shown under some make target or in on eof the
-`package.json` files._**
+**_Please note, that this `Makefile` is only meant to showcase typical steps that need to be taken in order to automate
+the deployment lifecycle of such an application and code base.
+It is NOT recommended to invoke `make` targets from the CI/CD, but rather to utilize platform-specific interfaces 
+(e.g. `Jenkinsfile`, `.travis.yml`, etc.), which may then invoke commands shown in the `make` target or in the `scripts`
+ section of one of the `package.json` files._**
 
-The following commands are available:
 
+### Prerequisites
 
-#### `make install-stack`
+The following software must be installed and available in your `${PATH}`:
 
-* install technology stack (Nodejs, npm, MongoDB) as prebuild binaries locally within the project
-* in order for the application-related targets to pick up these binaries, the `PATH` variable is adjusted and exported
-  for the corresponding target
+* `node` ([NodeJS](https://nodejs.org/en/download)) 
+* `npm` ([npm](https://www.npmjs.com/get-npm))
+* `mongod` ([MongoDB](https://docs.mongodb.com/manual/installation/))
 
+*NOTE: [required versions](https://github.com/lucendio/lecture-devops-app/blob/master/Makefile#L14-L126)*
 
-#### `make install-deps`
 
-* install npm dependencies for server and client
+#### Option 1
 
+Choose for yourself how you want to install these dependencies. Perhaps you can use the package manager
+available on your operating system, or maybe you prefer using container images. 
 
-#### `make build`
 
-* start a local mongo database
+#### Option 2
+
+Install all executables via `Makefile` into this project structure.
+
+a) from the root directory:
+```sh
+make deps
+```
 
+b) from the `./hack` folder:
+```sh
+$(cd ./hack && make install)
+```
 
-#### `make run-db`
+__Don't forget to add the new folder (`./.local/bin` ) to your `${PATH}` variable in your shell environment:__ 
+```sh
+export PATH=$(pwd)/.local/bin:${PATH}
+```
 
-* start a local mongo database
 
+### Commands
 
-#### `make run-local`
+The following commands are available from the root directory:
 
-*NOTE: it might be desired to start a database first (e.g. `make run-db`)*
 
-* build client 
-* start server with development configuration
+#### `make install`
+
+* installs all dependencies via `npm` for *server* and *client*
+
+
+#### `make build`
+
+* builds the client code
+* copies it over into the server
 
 
 #### `make test`
 
-*NOTE: requires MongoDB to be running (e.g. `make run-db`)*
+*NOTE: requires a MongoDB service to already run (see `MONGODB_URL` in target on where it's assumed to be running)*
 
-* run client tests in [CI mode](https://jestjs.io/docs/en/cli.html#--ci) (exits regardless of the test outcome; closed tty)
-* run server tests in [CI mode](https://jestjs.io/docs/en/cli.html#--ci) (exits regardless of the test outcome; closed tty)
+* runs client & server tests in [CI mode](https://jestjs.io/docs/en/cli.html#--ci) (exits regardless of the test outcome; closed tty)
 
 
-#### `make test-client-local`
+#### `make dev-test-client`
 
-* run client tests
+*NOTE: only demonstrates a use case during local development and are not meant to run in any other context (e.g. automation)*
 
+* runs client tests in a local development mode
 
-#### `make build`
 
-* build client
+#### `make dev-start-db`
+
+*NOTE: only demonstrates a use case during local development and are not meant to run in any other context (e.g. automation)*
+
+* starts a local MongoDB service
+
+
+#### `make dev-start-app`
+
+*NOTE (1): only demonstrates a use case during local development and are not meant to run in any other context (e.g. automation)*
+*NOTE (2): it might be desired to first start a database service (e.g. `make dev-start-db`)*
+
+* builds client (see `make build`) 
+* starts server in development mode and with development configuration
+
+
+#### `make run`
+
+*NOTE (1): showcases plain executable invocation with a shared parent process. Press `Ctrl+C` to send termination signal.*
+*NOTE (2): in reality those two services would always be invoked independently and __never__ share a parent process!*
+
+* starts a MongoDB service as a child process with an explicit inline-configuration
+* starts the application service with variables being set in a way so that they are only *visible* to that invocation
+  (as an alternative to the configuration file `app/server/dev.env` that is used to set environment variables)
+* blocks terminal and keeps it as output device until termination signal is being send.
 
 
 #### `make clean`
 
-* removes all dependencies that were installed locally 
-    * node, npm, mongo
-    * npm modules for server and client
+* removes all `node_modules` dependencies that have been installed locally via `npm`
+
+
+#### `make deps`
+
+* installs the software prerequisites as prebuild binaries locally in `.local/bin`
 
 
-#### `make start`
+#### `make nuke`
 
-* start a MongoDB process with an explicit inline-configuration
-* start the application process with variables being set in a way so that they are only visible to that invocation
-  (as an alternative to the
-  environment configuration file `app/server/dev.env`)
-* block terminal to keep it as the output medium. To stop again, send a termination signal via `Ctrl+C`
+* removes all `npm` dependencies (see `make clean`)
+* throws away `.local` folder and thus all software prerequisites that were installed within it
diff --git a/app/README.md b/app/README.md
index 6fdac2c..fdcd04e 100644
--- a/app/README.md
+++ b/app/README.md
@@ -2,14 +2,18 @@ Todo-App
 ========
 
 
-This application functions as the deployable workload for the lecture: [*DevOps*](https://github.com/lucendio/lecture-devops-material) 
+This application represents the *deployable workload* for the
+[lecture assignment](https://github.com/lucendio/lecture-devops-material/blob/master/assignments/exercise.md). 
 
 The application consists of two parts:
 
 * frontend (`./client`)
 * backend (`./server`)
 
-and utilizes the following technologies (aka. MERN-stack):
+During the build process, the client code is moved into the `./public` directory within the server source code.
+Aside from providing an HTTP API, the backend also functions as a static file server for the client.
+
+The following technologies have been utilized (aka. MERN-stack):
 
 * React (rendering engine of the web-based graphical user interface)
 * Express (web-server framework)
@@ -23,10 +27,6 @@ Other, most noticeable dependencies are:
 * [Babel](https://babeljs.io/) to transpile and therewith support latest Ecmascript versions
 * [ESLint](https://eslint.org/) to ensure code quality (linting); is invoked as part of the webpack build chain 
 * [Mongoose](https://mongoosejs.com/docs/api.html) as the database driver
- 
-
-*Please see the `scripts` sections in the respective `package.json` files to find out which commands are available for
-each part.*
 
 
 ##### Full disclosure
diff --git a/app/client/package-lock.json b/app/client/package-lock.json
index e9b4e5c..a1ae8a9 100644
--- a/app/client/package-lock.json
+++ b/app/client/package-lock.json
@@ -1,5 +1,5 @@
 {
-  "name": "todo-app",
+  "name": "todo-app-client",
   "version": "2.0.0",
   "lockfileVersion": 1,
   "requires": true,
diff --git a/app/client/package.json b/app/client/package.json
index ccb7714..5d651c4 100644
--- a/app/client/package.json
+++ b/app/client/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "todo-app",
+  "name": "todo-app-client",
   "version": "2.0.0",
   "private": true,
   "dependencies": {
diff --git a/app/server/package.json b/app/server/package.json
index 618a338..0b0171d 100644
--- a/app/server/package.json
+++ b/app/server/package.json
@@ -6,7 +6,7 @@
   "scripts": {
     "start": "node src/index.js",
     "start:dev": "env-cmd -f ./dev.env nodemon src/index.js",
-    "test": "jest --ci --detectOpenHandles",
+    "test": "jest --ci",
     "test:dev": "env-cmd -f ./dev.env jest"
   },
   "devDependencies": {
diff --git a/hack/Makefile b/hack/Makefile
new file mode 100644
index 0000000..ffb9d6c
--- /dev/null
+++ b/hack/Makefile
@@ -0,0 +1,152 @@
+.DEFAULT_GOAL := default
+SHELL = /usr/bin/env bash -eo pipefail
+
+
+
+MKFILE_DIR = $(abspath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
+PLATFORM := $(shell if echo $$OSTYPE | grep -q darwin; then echo darwin; else echo linux; fi)
+LOCAL_DIR = $(abspath $(MKFILE_DIR)/../.local)
+
+
+BIN_DIR = $(LOCAL_DIR)/bin
+LIB_DIR = $(LOCAL_DIR)/lib
+TEMP_DIR = $(LOCAL_DIR)/tmp
+
+
+
+# next LTS: 14.13.0
+NODEJS_VERSION ?= 12.16.3
+NPM_VERSION ?= 6.14.4
+MONGODB_VERSION ?= 4.2.6
+
+REACT_APP_VERSION = 3.4.1
+
+
+
+NODEJS_URL = https://nodejs.org/dist/v$(NODEJS_VERSION)/node-v$(NODEJS_VERSION)-$(PLATFORM)-x64.tar.gz
+NODEJS_ARTIFACT = $(TEMP_DIR)/node-v$(NODEJS_VERSION)-$(PLATFORM)-x64.tar.gz
+NODEJS_ARCHIVE = $(patsubst %.tar.gz,%,$(notdir $(NODEJS_ARTIFACT)))
+NODEJS_BIN = $(BIN_DIR)/node
+NPM_BIN = $(BIN_DIR)/npm
+NODEJS_SHA256 ?= $(shell cat $(MKFILE_DIR)/versions/nodejs.256.sums | grep v$(NODEJS_VERSION)-$(PLATFORM)-x64 | awk '{ print $$ 1 }')
+
+
+ifeq ($(PLATFORM), darwin)
+MONGODB_URL = https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-$(MONGODB_VERSION).tgz
+else ifeq ($(PLATFORM), linux)
+# NOTE: hard-coded Debian version. Others can be found here: https://www.mongodb.com/download-center/community
+MONGODB_URL = https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian10-$(MONGODB_VERSION).tgz
+else
+	fail 'Unknown platform. No condition met'
+endif
+MONGODB_ARTIFACT = $(TEMP_DIR)/mongodb-$(PLATFORM)-$(MONGODB_VERSION).tar.gz
+MONGODB_ARCHIVE = $(patsubst %.tar.gz,%,$(notdir $(MONGODB_ARTIFACT)))
+MONGODB_BIN = $(BIN_DIR)/mongod
+MONGODB_SHA256 ?= $(shell cat $(MKFILE_DIR)/versions/mongodb.256.sums | grep $(PLATFORM)-x86_64-$(MONGODB_VERSION) | awk '{ print $$ 1 }')
+
+
+
+
+default: all
+
+all: install
+
+
+
+.PHONY: install
+install: node npm mongod
+
+
+$(LOCAL_DIR)/%/:
+	mkdir -p $(@)
+
+
+.PHONY: clean
+clean:
+	rm -rf \
+		$(LOCAL_DIR)
+
+
+
+.PHONY: node
+node: $(NODEJS_BIN)
+$(NODEJS_BIN): | $(NODEJS_ARTIFACT) $(BIN_DIR)/
+	@ [ $$(openssl dgst -sha256 "$(NODEJS_ARTIFACT)" | awk '{ print $$ 2 }') == $(NODEJS_SHA256) ] || ( echo "Invalid SHA256." && rm $(NODEJS_ARTIFACT) && exit 1 )
+	tar \
+		--extract \
+		--verbose \
+		--strip-components 2 \
+		--directory "$(BIN_DIR)" \
+		--file "$(NODEJS_ARTIFACT)" \
+		$(NODEJS_ARCHIVE)/bin/node
+	chmod +x "$@"
+
+$(NODEJS_ARTIFACT): | $(TEMP_DIR)/
+	curl \
+		--silent --show-error \
+		--location \
+		$(NODEJS_URL) \
+		> $(NODEJS_ARTIFACT)
+
+
+.PHONY: npm
+npm: $(NPM_BIN)
+npm: export PATH := $(BIN_DIR):$(PATH)
+$(NPM_BIN): | $(NODEJS_BIN)
+	mkdir -p $(LIB_DIR)
+	tar \
+		--extract \
+		--verbose \
+		--strip-components 2 \
+		--directory "$(LIB_DIR)" \
+		--file "$(NODEJS_ARTIFACT)" \
+		$(NODEJS_ARCHIVE)/lib/node_modules
+	chmod +x $(LIB_DIR)/node_modules/npm/bin/npm-cli.js
+	ln -s $(LIB_DIR)/node_modules/npm/bin/npm-cli.js $(NPM_BIN)
+	npm install -g npm@$(NPM_VERSION)
+
+
+.PHONY: mongod
+mongod: $(MONGODB_BIN)
+$(MONGODB_BIN): | $(MONGODB_ARTIFACT) $(BIN_DIR)/
+	@ [ $$(openssl dgst -sha256 "$(MONGODB_ARTIFACT)" | awk '{ print $$ 2 }') == $(MONGODB_SHA256) ] || ( echo "Invalid SHA256." && rm $(MONGODB_ARTIFACT) && exit 1 )
+	mkdir -p $(TEMP_DIR)/$(MONGODB_ARCHIVE)
+	tar \
+		--extract \
+		--verbose \
+		--strip-components 2 \
+		--directory "$(TEMP_DIR)/$(MONGODB_ARCHIVE)" \
+		--file "$(MONGODB_ARTIFACT)"
+	mv $(TEMP_DIR)/$(MONGODB_ARCHIVE)/mongod "$@"
+	chmod +x "$@"
+	rm -rf $(TEMP_DIR)/$(MONGODB_ARCHIVE)
+
+$(MONGODB_ARTIFACT): | $(TEMP_DIR)/
+	curl \
+		--silent --show-error \
+		--location \
+		$(MONGODB_URL) \
+		> $(MONGODB_ARTIFACT)
+
+
+
+
+.PHONY: update-react-app-template
+update-react-app-template: export PATH := $(BIN_DIR):$(PATH)
+update-react-app-template:
+	rm -rf $(TEMP_DIR)/npm-project-scope
+	mkdir -p $(TEMP_DIR)/npm-project-scope
+	cd $(TEMP_DIR)/npm-project-scope \
+		&& npm install --save-dev react-scripts@$(REACT_APP_VERSION) \
+		&& npm init react-app $(TEMP_DIR)/npm-project-scope/cra
+	cp \
+		$(TEMP_DIR)/npm-project-scope/cra/src/* \
+		$(MKFILE_DIR)/../app/client/src/
+	cp \
+		$(TEMP_DIR)/npm-project-scope/cra/public/* \
+		$(MKFILE_DIR)/../app/client/public/
+	cp \
+		$(TEMP_DIR)/npm-project-scope/cra/package.json \
+		$(MKFILE_DIR)/../app/client/
+	rm -rf \
+		$(MKFILE_DIR)/../app/client/src/logo.svg
diff --git a/stack/local.mongod.conf b/hack/local.mongod.conf
similarity index 100%
rename from stack/local.mongod.conf
rename to hack/local.mongod.conf
diff --git a/hack/versions/mongodb.256.sums b/hack/versions/mongodb.256.sums
new file mode 100644
index 0000000..baaa137
--- /dev/null
+++ b/hack/versions/mongodb.256.sums
@@ -0,0 +1,2 @@
+7c763aea275b9b3ebb6b746e9e356d81305dcd0c760319bfa78abd1b30eb4612 mongodb-linux-x86_64-4.2.6.tgz
+701fda6ab0b49121913204596d527d89d4a533a3a7d1ca2f245c7908e1342c5b mongodb-darwin-x86_64-4.2.6.tgz
diff --git a/hack/versions/nodejs.256.sums b/hack/versions/nodejs.256.sums
new file mode 100644
index 0000000..17081fb
--- /dev/null
+++ b/hack/versions/nodejs.256.sums
@@ -0,0 +1,2 @@
+66518c31ea7735ae5a0bb8ea27edfee846702dbdc708fea6ad4a308d43ef5652 node-v12.16.3-linux-x64.tar.gz
+0718812b3ab8e77e8d1354f4d10428ae99d78f721bdcceee527c4b592ea7fed0 node-v12.16.3-darwin-x64.tar.gz
diff --git a/stack/versions/mongodb.256.sums b/stack/versions/mongodb.256.sums
deleted file mode 100644
index b26ea39..0000000
--- a/stack/versions/mongodb.256.sums
+++ /dev/null
@@ -1,2 +0,0 @@
-mongodb-linux-x86_64-4.2.6.tgz 7c763aea275b9b3ebb6b746e9e356d81305dcd0c760319bfa78abd1b30eb4612
-mongodb-darwin-x86_64-4.2.6.tgz 701fda6ab0b49121913204596d527d89d4a533a3a7d1ca2f245c7908e1342c5b
diff --git a/stack/versions/nodejs.256.sums b/stack/versions/nodejs.256.sums
deleted file mode 100644
index 2f59ed0..0000000
--- a/stack/versions/nodejs.256.sums
+++ /dev/null
@@ -1,2 +0,0 @@
-node-v12.16.3-linux-x64.tar.gz 66518c31ea7735ae5a0bb8ea27edfee846702dbdc708fea6ad4a308d43ef5652
-node-v12.16.3-darwin-x64.tar.gz 0718812b3ab8e77e8d1354f4d10428ae99d78f721bdcceee527c4b592ea7fed0
-- 
GitLab