Merge branch 'master' into donate

This commit is contained in:
Luke Berndt
2024-02-22 22:12:49 -05:00
committed by GitHub
23 changed files with 2997 additions and 281 deletions

View File

@@ -14,7 +14,7 @@ There are a bunch of experiments lurking in the code. There is some code for add
I haven't done a great job of keeping all the packages up to date... and I never got around to adding tests. Both of these would be great things for folks to go after.
## PROD vs DEV Env
## PROD vs TEST Env
The Prod environment expects you to be using HTTPS for your domains. It is pretty easy to use Let's Encrypt to grab Certs for the domains you are using. If not, just run things using the `docker-test.sh` script.
## Path Forward
@@ -43,8 +43,9 @@ CNAMEs also need to be created for the various services. Create the CNAMEs below
- api
- account
- admin
- www
After doing this, you should have the following domains: `api.domain.com`, `account.domain.com`, `admin.domain.com`
After doing this, you should have the following domains: `api.domain.com`, `account.domain.com`, `admin.domain.com`, `www.domain.com`
### S3 Storage
Currently, both **test** and **prod** expect to use S3-based storage instead of local storage. Switching to use local storage would be relatively easy - but for the sake of testing, let's just say use something S3-compatible. Make sure that ~/.aws/credentials has the credentials you'd like to use with your S3-compatible storage provider.. IE:
@@ -54,6 +55,29 @@ aws_access_key_id = [..]
aws_secret_access_key = [..]
```
# Automatically renewing SSL Certificates
SSL certs are automatically fetched from Let's Encrypt using the CertBot tool. The approach taken is based on this [Medium post](https://pentacent.medium.com/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71) and accompanying [GitHub repo](https://github.com/wmnnd/nginx-certbot/tree/master).
You do need to jump start the process and do an initial fetch. To get started, make sure you have your `prod.env` file filled out. If you don't, copy prod.env.example to prod.env and fill in the details. Make sure **DOMAIN_NAME** and **REACT_APP_ADMIN_EMAIL** are correct and are the version you want to use in production. Those values will be used when requesting an SSL cert from Let's Encrypt.
This script uses Let's Encrypt's [Certbot tool](https://eff-certbot.readthedocs.io/en/latest/).
In the main directory of the **Trunk Server** repo, run the following commands:
```bash
source prod.env
docker compose -f certbot-compose.yml up
```
Check the output from CertBot - and when it is done, just hit `ctrl + c` to exit.
And then run the following to make sure everything has stopped:
```bash
docker compose -f certbot-compose.yml down
```
### Configure
The configurations for both **test** and **prod** come from environment variable files that are read in before the containers are started.
Copy the example files and fill in the required info:
@@ -218,14 +242,14 @@ It is a huge pain to upgrade MongoDB in place. It turns out to be easier to dump
Rough Playbook (use common sense, I may not have this exact):
- get into the shell of the mongo container
- `mongodump --db scanner —out /data/db/backup`
- `mongodump --uri="mongodb://127.0.0.1" --db scanner --out /data/db/backup`
- exit contianer and go back to host machine
- `cd data/db`
- `rm *` erase everything... but not the sub-directories because that is where the backup is
- upgrade to the latest version of mongo
- build and launch the mongo container, which will create an empty DB
- get into the shell of the mongo container
- `mongorestore --db scanner --drop /data/db/backup/`
- `mongorestore --db scanner --drop /data/db/backup/scanner`
### Privlege Port Error
@@ -237,7 +261,7 @@ https://docs.docker.com/engine/security/rootless/#exposing-privileged-ports
## Setting up Logging
I have had good luck with Loggly. Their free tier provides enough capabilities for most small sites.
The Doocker Logging Driver works well and is easy to install:
The Docker Logging Driver works well and is easy to install:
https://documentation.solarwinds.com/en/Success_Center/loggly/Content/admin/docker-logging-driver.htm

View File

@@ -110,7 +110,7 @@ exports.deleteGroup = async function (req, res, next) {
exports.reorderGroups = function (req, res, next) {
console.log(req.body)
//router.post('/system/:shortName/group_order', isLoggedIn, function(req, res) {
process.nextTick(async function () {
const system = await System.findOne({ shortName: req.params.shortName.toLowerCase() });
@@ -131,7 +131,7 @@ exports.reorderGroups = function (req, res, next) {
});
return;
}
console.log(req.body)
if (!req.body.groupOrder) {
res.status(500);
res.json({
@@ -142,8 +142,6 @@ exports.reorderGroups = function (req, res, next) {
}
var groupOrder = JSON.parse(req.body.groupOrder);
for (var i = 0; i < groupOrder.length; i++) {
console.log("in group: " + groupOrder[i] + " position: " + i);
const group = await Group.findOneAndUpdate(
{
_id: new mongoose.Types.ObjectId(groupOrder[i]),
@@ -177,10 +175,8 @@ exports.reorderGroups = function (req, res, next) {
};
exports.upsertGroup = function (req, res, next) {
console.log(req.body)
//router.post('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) {
process.nextTick(async function () {
console.log(req.body)
let system = await System.findOne({ shortName: req.params.shortName.toLowerCase() });
if (!system) {

View File

@@ -58,7 +58,6 @@ exports.listUserSystems = async function (req,res,next) {
}
const users = await User.find({}, "_id email firstName lastName lastLogin");
let list = [];
console.log(users);
for (let index = 0; index < users.length; index++) {
let user = users[index].toObject();
const userId = new mongoose.Types.ObjectId(user._id);

View File

@@ -94,7 +94,6 @@ async function csv_import(shortName, filename, callback) {
for (var i = 0; i < data.length; i++) {
var row = data[i];
console.log(row)
if (!( "Decimal" in row) || !("Description" in row) || !("Alpha Tag" in row)) {
console.error("Error: csv import column header error");
callback("Column Headers are required - Required Columns: 'Decimal' 'Decscription' 'Alpha Tag' Optional Column: 'Priority' ", null);

View File

@@ -0,0 +1,23 @@
// tracing.js
'use strict';
const { HoneycombSDK } = require('@honeycombio/opentelemetry-node');
const {
getNodeAutoInstrumentations,
} = require('@opentelemetry/auto-instrumentations-node');
// uses the HONEYCOMB_API_KEY and OTEL_SERVICE_NAME environment variables
const sdk = new HoneycombSDK({
sampleRate: 5,
instrumentations: [
getNodeAutoInstrumentations({
// we recommend disabling fs autoinstrumentation since it can be noisy
// and expensive during startup
'@opentelemetry/instrumentation-fs': {
enabled: false,
},
}),
],
});
sdk.start();

View File

@@ -2,7 +2,7 @@
const { ObjectId } = require('mongodb');
var { callModel: Call } = require("../models/call");
const Group = require("../models/group");
const opentelemetry = require('@opentelemetry/api');
var defaultNumResults = 50;
var channels = {};

View File

@@ -1,10 +1,12 @@
var path = require('path');
var fs = require('fs');
const opentelemetry = require('@opentelemetry/api');
var sysStats = require("../sys_stats");
var System = require("../models/system");
var Talkgroup = require("../models/talkgroup");
var { callModel: Call } = require("../models/call");
const { PutObjectCommand, S3Client } = require("@aws-sdk/client-s3");
const { fromIni } = require("@aws-sdk/credential-providers");
@@ -18,183 +20,236 @@ const client = new S3Client({
credentials: fromIni({ profile: s3_profile }),
endpoint: s3_endpoint,
region: s3_region,
maxAttempts: 1
maxAttempts: 3
});
exports.upload = async function (req, res, next) {
process.nextTick( async () => {
if (!req.file || ((path.extname(req.file.originalname) != '.m4a') && (path.extname(req.file.originalname) != '.mp3'))) {
console.warn("[" + req.params.shortName + "] Error file name is wrong or file does not exist");
res.status(500);
res.send("Error, invalid filename:\n");
return;
}
var shortName = req.params.shortName.toLowerCase();
var apiKey = req.body.api_key;
let item = null;
try {
item = await System.findOne({ 'shortName': shortName }, ["key", "ignoreUnknownTalkgroup"]);
} catch (err) {
console.warn("[" + req.params.shortName + "] Error /:shortName/upload - Error: " + err);
res.status(500);
res.send("Error, invalid shotName:\n" + err);
return;
}
process.nextTick(async () => {
if (!req.file || ((path.extname(req.file.originalname) != '.m4a') && (path.extname(req.file.originalname) != '.mp3'))) {
console.warn("[" + req.params.shortName + "] Error file name is wrong or file does not exist");
res.status(500);
res.send("Error, invalid filename:\n");
return;
}
var shortName = req.params.shortName.toLowerCase();
var apiKey = req.body.api_key;
var talkgroupNum = parseInt(req.body.talkgroup_num);
var freq = parseFloat(req.body.freq);
var time = new Date(parseInt(req.body.start_time) * 1000);
var stopTime = new Date();
if (req.body.stop_time) {
var stopTime = new Date(parseInt(req.body.stop_time) * 1000);
}
var startTime = req.body.start_time;
var emergency = parseInt(req.body.emergency);
if (!item) {
console.info("[" + req.params.shortName + "] Error /:shortName/upload ShortName does not exist");
res.status(500);
res.send("ShortName does not exist: " + shortName + "\n");
return;
}
if (apiKey != item.key) {
console.warn("[" + req.params.shortName + "] Error /:shortName/upload API Key Mismatch - Provided key: " + apiKey);
res.status(500);
res.send("API Keys do not match!\n");
return;
}
let errorCount = parseInt(req.body.error_count);
let spikeCount = parseInt(req.body.spike_count);
var talkgroupNum = parseInt(req.body.talkgroup_num);
var freq = parseFloat(req.body.freq);
var time = new Date(parseInt(req.body.start_time) * 1000);
var stopTime = new Date();
if (req.body.stop_time) {
var stopTime = new Date(parseInt(req.body.stop_time) * 1000);
}
var startTime = req.body.start_time;
var emergency = parseInt(req.body.emergency);
if (Number.isNaN(errorCount)) {
errorCount = 0;
}
if (Number.isNaN(spikeCount)) {
spikeCount = 0;
}
// Blocking sensitive talkgroups
if ((shortName == "hennearmer") && ((talkgroupNum == 3421) || (talkgroupNum == 3423))) {
res.status(200).end();
return;
}
try {
var srcList = JSON.parse(req.body.source_list);
} catch (err) {
var srcList = [];
console.warn("[" + req.params.shortName + "] Error /:shortName/upload Parsing Source/Freq List - Error: " + err);
res.status(500);
res.send("Error parsing sourcelist " + err);
return;
}
let item = null;
const span = opentelemetry.trace.getActiveSpan();
span.setAttribute('call.shortName', shortName);
span.setAttribute('call.talkgroupNum', talkgroupNum);
span.setAttribute('call.startTime', startTime);
if (item.ignoreUnknownTalkgroup == true) {
talkgroupExists = await Talkgroup.exists({
'shortName': shortName,
'num': talkgroupNum
// Validate that the system exists and the API key is correct
try {
item = await System.findOne({ 'shortName': shortName }, ["key", "ignoreUnknownTalkgroup"]);
} catch (err) {
span.setStatus({
code: opentelemetry.SpanStatusCode.ERROR,
message: "Error /:shortName/upload - Error: " + err,
});
console.warn("[" + req.params.shortName + "] Error /:shortName/upload - Error: " + err);
res.status(500);
res.send("Error, invalid shortName:\n" + err);
return;
}
if (!item) {
span.setStatus({
code: opentelemetry.SpanStatusCode.ERROR,
message: "Error /:shortName/upload API Key Mismatch - Provided key: " + apiKey,
});
console.info("[" + req.params.shortName + "] Error /:shortName/upload ShortName does not exist");
res.status(500);
res.send("ShortName does not exist: " + shortName + "\n");
return;
}
if (apiKey != item.key) {
span.setStatus({
code: opentelemetry.SpanStatusCode.ERROR,
message: "Error /:shortName/upload API Key Mismatch - Provided key: " + apiKey,
});
console.warn("[" + req.params.shortName + "] Error /:shortName/upload API Key Mismatch - Provided key: " + apiKey);
res.status(500);
res.send("API Keys do not match!\n");
return;
}
// Blocking sensitive talkgroups
if ((shortName == "hennearmer") && ((talkgroupNum == 3421) || (talkgroupNum == 3423))) {
span.setStatus({
code: opentelemetry.SpanStatusCode.ERROR,
message: "Sensitive Talkgroup",
});
res.status(200).end();
return;
}
if (item.ignoreUnknownTalkgroup == true) {
talkgroupExists = await Talkgroup.exists({
'shortName': shortName,
'num': talkgroupNum
});
if (!talkgroupExists) {
try {
fs.unlinkSync(req.file.path)
//file removed
} catch (err) {
console.log("[" + call.shortName + "] error deleting: " + req.file.path);
}
res.status(500);
res.send("Talkgroup does not exist, skipping.\n");
span.setStatus({
code: opentelemetry.SpanStatusCode.ERROR,
message: "Ignore Unknown Talkgroup Set - Talkgroup does not exist: " + talkgroupNum + " ShortName: " + shortName,
});
return;
}
}
// Add in an API Key check
res.status(200).end();
var local_path = "/" + shortName + "/" + time.getFullYear() + "/" + (time.getMonth() + 1) + "/" + time.getDate() + "/";
var object_key = "media/" + shortName + "-" + talkgroupNum + "-" + startTime + path.extname(req.file.originalname);
var endpoint = s3_endpoint;
var bucket = s3_bucket;
var url = s3_endpoint + "/" + s3_bucket + "/" + object_key;
var call = new Call({
shortName: shortName,
talkgroupNum: talkgroupNum,
objectKey: object_key,
endpoint: endpoint,
bucket: bucket,
time: time,
name: talkgroupNum + "-" + startTime + ".m4a",
freq: freq,
errorCount: errorCount,
spikeCount: spikeCount,
url: url,
emergency: emergency,
path: local_path,
srcList: srcList,
len: -1
});
if (!talkgroupExists) {
try {
fs.unlinkSync(req.file.path)
//file removed
} catch (err) {
console.log("[" + call.shortName + "] error deleting: " + req.file.path);
try {
if (req.body.call_length) {
call.len = parseFloat(req.body.call_length);
} else {
call.len = (stopTime - time) / 1000;
}
res.status(500);
res.send("Talkgroup does not exist, skipping.\n");
} catch (err) {
console.error(err);
}
let fileContent;
try {
fileContent = fs.readFileSync(req.file.path);
//s3Src = fs.createReadStream(req.file.path);
} catch (err) {
span.setStatus({
code: opentelemetry.SpanStatusCode.ERROR,
message: "Unable to open file: " + req.file.path + " Error: " + err,
});
console.error("[" + call.shortName + "] Unable to open file: " + req.file.path + " Error: " + err);
return;
}
}
// Add in an API Key check
const command = new PutObjectCommand({
Bucket: s3_bucket,
Key: object_key,
Body: fileContent,
ACL: 'public-read'
});
let errorCount = parseInt(req.body.error_count);
let spikeCount = parseInt(req.body.spike_count);
try {
await client.send(command);
if (Number.isNaN(errorCount)) {
errorCount = 0;
}
if (Number.isNaN(spikeCount)) {
spikeCount = 0;
}
try {
var srcList = JSON.parse(req.body.source_list);
} catch (err) {
var srcList = [];
console.warn("[" + req.params.shortName + "] Error /:shortName/upload Parsing Source/Freq List - Error: " + err);
res.status(500);
res.send("Error parsing sourcelist " + err);
return;
}
res.status(200).end();
var local_path = "/" + shortName + "/" + time.getFullYear() + "/" + (time.getMonth() + 1) + "/" + time.getDate() + "/";
var object_key = "media/" + shortName + "-" + talkgroupNum + "-" + startTime + path.extname(req.file.originalname);
var endpoint = s3_endpoint;
var bucket = s3_bucket;
var url = s3_endpoint + "/" + s3_bucket + "/" + object_key;
var call = new Call({
shortName: shortName,
talkgroupNum: talkgroupNum,
objectKey: object_key,
endpoint: endpoint,
bucket: bucket,
time: time,
name: talkgroupNum + "-" + startTime + ".m4a",
freq: freq,
errorCount: errorCount,
spikeCount: spikeCount,
url: url,
emergency: emergency,
path: local_path,
srcList: srcList,
len: -1
});
try {
if (req.body.call_length) {
call.len = parseFloat(req.body.call_length);
} else {
call.len = (stopTime - time) / 1000;
} catch (err) {
span.setStatus({
code: opentelemetry.SpanStatusCode.ERROR,
message: "Upload Error: " + err + " Filename: " + call.name + " content-length: " + req.headers['content-length'] + " Key: " + object_key + " Bucket: " + s3_bucket,
});
console.warn("[" + call.shortName + "] Upload Error: " + err + " Filename: " + call.name + " content-length: " + req.headers['content-length'] + " Key: " + object_key + " Bucket: " + s3_bucket);
/*res.status(500);
sysStats.addError(call.toObject());
res.contentType('json');
res.status(500);
res.send(JSON.stringify({
success: false,
error: "File Upload"
}));*/
}
} catch (err) {
console.error(err);
}
let fileContent;
try {
fileContent = fs.readFileSync(req.file.path);
//s3Src = fs.createReadStream(req.file.path);
} catch (err) {
console.error("[" + call.shortName + "] Unable to open file: " + req.file.path + " Error: " + err);
return;
}
const command = new PutObjectCommand({
Bucket: s3_bucket,
Key: object_key,
Body: fileContent,
ACL: 'public-read'
});
try {
await client.send(command);
await call.save();
sysStats.addCall(call.toObject());
// we only want to notify clients if the clip is longer than 1 second.
if (call.len >= 1) {
req.call = call.toObject();
next();
try {
await call.save();
sysStats.addCall(call.toObject());
// we only want to notify clients if the clip is longer than 1 second.
if (call.len >= 1) {
req.call = call.toObject();
next();
}
} catch (err) {
console.warn("[" + call.shortName + "] Error saving call: " + err);
span.setStatus({
code: opentelemetry.SpanStatusCode.ERROR,
message: "Error saving call: " + err,
});
/*res.status(500);
sysStats.addError(call.toObject());
res.contentType('json');
res.status(500);
res.send(JSON.stringify({
success: false,
error: "File Upload"
}));*/
}
} catch (err) {
console.warn("[" + call.shortName + "] Upload Error: " + err + " Filename: " + call.name + " content-length: " + req.headers['content-length'] + " Key: " + object_key + " Bucket: " + s3_bucket);
/*res.status(500);
sysStats.addError(call.toObject());
res.contentType('json');
res.status(500);
res.send(JSON.stringify({
success: false,
error: "File Upload"
}));*/
}
try {
fs.unlinkSync(req.file.path);
} catch (err) {
console.warn("There was an Error uploading an deleting: " + req.file.path);
};
});
}

View File

@@ -1,4 +1,4 @@
const tracing = require("./agents/honeycomb-tracing");
var express = require("express");
var configureExpress = require("./config/express");

2631
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,9 @@
"@aws-sdk/client-s3": "^3.309.0",
"@aws-sdk/credential-provider-process": "^3.306.0",
"@aws-sdk/credential-providers": "^3.309.0",
"@honeycombio/opentelemetry-node": "^0.6.1",
"@opentelemetry/api": "^1.7.0",
"@opentelemetry/auto-instrumentations-node": "^0.41.0",
"adm-zip": "^0.5.10",
"aws-sdk": "^2.1353",
"axios": "1.3.5",
@@ -22,12 +25,12 @@
"ffprobe-static": "3.1.0",
"find-remove": "4.1.0",
"get-folder-size": "^4.0.0",
"node-mailjet": "6.0.2",
"mkdirp": "^2.1.6",
"mongodb": "5.2.0",
"mongoose": "7.0.3",
"multer": "1.3.0",
"node-ffprobe": "https://github.com/ListenerApproved/node-ffprobe",
"node-mailjet": "6.0.2",
"node-schedule": "2.1.1",
"passport": "0.6.0",
"passport-local": "1.0.0",

View File

@@ -10,10 +10,7 @@ var spots = (24 * 60) / timePeriod; // the number of spots needed to keep track
async function updateActiveSystems() {
// Go through all of the Systems
for await (let item of System.find()) {
// go through all the systems
// if you have recieved some calls during that last period, make the system active

37
certbot-compose.yml Normal file
View File

@@ -0,0 +1,37 @@
version: "3"
services:
nginx:
build:
context: ./nginx-proxy
args:
STAGE: "cert"
environment:
- DOMAIN_NAME
image: "nginx:${TAG}-cert"
networks:
- proxy
ports:
- "80:80"
restart: always
volumes:
- ${PWD}/data/log/syslog:/var/log/syslog
- ${PWD}/data/log/nginx:/var/log/nginx
- ${PWD}/data/certbot/www/:/var/www/certbot/:ro
- ${PWD}/data/certbot/conf/:/etc/letsencrypt/:ro
command: /bin/bash -c "envsubst '$$DOMAIN_NAME' < /etc/nginx/conf.d/site.template > /etc/nginx/conf.d/default.conf && while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g 'daemon off;'"
certbot:
image: certbot/certbot:latest
volumes:
- ${PWD}/data/certbot/www/:/var/www/certbot/:rw
- ${PWD}/data/certbot/conf/:/etc/letsencrypt/:rw
entrypoint: "certbot certonly --webroot -w /var/www/certbot \
--email ${REACT_APP_ADMIN_EMAIL} \
-d ${DOMAIN_NAME} -d www.${DOMAIN_NAME} -d api.${DOMAIN_NAME} -d admin.${DOMAIN_NAME} -d account.${DOMAIN_NAME} \
--rsa-key-size 4096 \
--agree-tos \
--force-renewal"
networks:
proxy:
node:

View File

@@ -33,6 +33,10 @@ services:
- ${PWD}/data/certbot/www/:/var/www/certbot/:rw
- ${PWD}/data/certbot/conf/:/etc/letsencrypt/:rw
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
logging:
driver: syslog
options:
tag: "certbot"
mongo:
build:
@@ -73,6 +77,8 @@ services:
- S3_BUCKET
- S3_PROFILE
- S3_REGION
- OTEL_SERVICE_NAME
- HONEYCOMB_API_KEY
volumes:
- "~/.aws:/home/app/.aws"
depends_on:

View File

@@ -1,3 +1,5 @@
source test.env
echo "Domains: " $DOMAIN_NAME
echo "Protocol: " $PROTOCOL
echo "Docker Compose Command: " $@
docker compose -f docker-compose.yml -f test-compose.yml $@

View File

@@ -66,15 +66,17 @@ const MediaPlayer = (props) => {
const audio = audioRef.current.audioEl.current;
setSourceIndex(0);
navigator.mediaSession.metadata = new MediaMetadata({
title: "Waiting for Call...",
album: 'OpenMHz',
artwork: [
{ src: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
{ src: '/android-chrome-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: '/android-chrome-192x192.png', sizes: '512x512', type: 'image/png' },
]
});
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: "Waiting for Call...",
album: 'OpenMHz',
artwork: [
{ src: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
{ src: '/android-chrome-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: '/android-chrome-192x192.png', sizes: '512x512', type: 'image/png' },
]
});
}
audio.src = "data:audio/mpeg;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA/+M4wAAAAAAAAAAAAEluZm8AAAAPAAAAAwAAAbAAqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV////////////////////////////////////////////AAAAAExhdmM1OC4xMwAAAAAAAAAAAAAAACQDkAAAAAAAAAGw9wrNaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+MYxAAAAANIAAAAAExBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/+MYxDsAAANIAAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/+MYxHYAAANIAAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV";
const playPromise = audio.play();

View File

@@ -1,91 +0,0 @@
#!/bin/bash
# from https://github.com/wmnnd/nginx-certbot/blob/master/init-letsencrypt.sh
# Load environment variables from prod.env.example
if [ -f prod.env ]; then
source test.env
fi
# Rest of the script...
if ! [ -x "$(command -v docker compose)" ]; then
echo 'Error: docker compose is not installed.' >&2
exit 1
fi
domains=("$DOMAIN_NAME" "account.$DOMAIN_NAME" "admin.$DOMAIN_NAME" "api.$DOMAIN_NAME" "www.$DOMAIN_NAME")
rsa_key_size=4096
data_path="./data/certbot"
email=$REACT_APP_ADMIN_EMAIL
staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits
if [ -d "$data_path" ]; then
read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
exit
fi
fi
if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
echo "### Downloading recommended TLS parameters ..."
mkdir -p "$data_path/conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
echo
fi
echo "### Creating dummy certificate for $domains ..."
path="/etc/letsencrypt/live/$domains"
mkdir -p "$data_path/conf/live/$domains"
docker compose -f docker-compose.yml -f test-compose.yml run --rm --entrypoint "\
openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\
-keyout '$path/privkey.pem' \
-out '$path/fullchain.pem' \
-subj '/CN=localhost'" certbot
echo
echo "### Starting nginx ..."
docker compose -f docker-compose.yml -f test-compose.yml up -d nginx
echo
echo "### Deleting dummy certificate for $domains ..."
docker compose -f docker-compose.yml -f test-compose.yml run --rm --entrypoint "\
rm -Rf /etc/letsencrypt/live/$domains && \
rm -Rf /etc/letsencrypt/archive/$domains && \
rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo
echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args=""
for domain in "${domains[@]}"; do
domain_args="$domain_args -d $domain"
done
# Select appropriate email arg
case "$email" in
"") email_arg="--register-unsafely-without-email" ;;
*) email_arg="--email $email" ;;
esac
# Enable staging mode if needed
if [ $staging != "0" ]; then staging_arg="--staging"; fi
docker compose -f docker-compose.yml -f test-compose.yml run --rm --entrypoint "\
certbot certonly --webroot -w /var/www/certbot \
$staging_arg \
$email_arg \
$domain_args \
--rsa-key-size $rsa_key_size \
--agree-tos \
--force-renewal" certbot
echo
echo "### Reloading nginx ..."
docker compose -f docker-compose.yml -f test-compose.yml exec nginx nginx -s reload

View File

@@ -2,7 +2,7 @@
#
# docker build -t mongo .
FROM mongo:5.0
FROM mongo:7-jammy
MAINTAINER Luke Berndt <lukekb@gmail.com>

View File

@@ -21,7 +21,6 @@ RUN apt-get update \
# Add main NGINX config
COPY *.conf /etc/nginx/
COPY ${STAGE}/certs/ /etc/nginx/certs/
# Add virtual hosts
COPY ${STAGE}/vhosts/ /etc/nginx/conf.d/

View File

@@ -0,0 +1,8 @@
server {
listen 80;
server_name account.${DOMAIN_NAME} api.${DOMAIN_NAME} admin.${DOMAIN_NAME} www.${DOMAIN_NAME} ${DOMAIN_NAME};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}

View File

@@ -1 +0,0 @@
# Copy your certs here

View File

@@ -27,6 +27,10 @@ server {
server_name ${DOMAIN_NAME} www.${DOMAIN_NAME};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location /static {
proxy_pass http://frontend:3000;
access_log off;
@@ -63,6 +67,10 @@ server {
server_name api.${DOMAIN_NAME};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location /marcsip/upload {
deny all;
}
@@ -100,6 +108,10 @@ server {
server_name account.${DOMAIN_NAME};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
proxy_pass http://account:3009;
proxy_http_version 1.1;
@@ -127,6 +139,10 @@ server {
server_name admin.${DOMAIN_NAME};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
proxy_pass http://admin:3008;
proxy_http_version 1.1;

View File

@@ -1 +0,0 @@
# Copy your certs here

View File

@@ -1,8 +1,14 @@
server {
listen 80;
server_name ${DOMAIN_NAME} www.${DOMAIN_NAME};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location /static {
proxy_pass http://frontend:3000;
access_log off;
@@ -30,6 +36,10 @@ server {
server_name account.${DOMAIN_NAME};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
proxy_pass http://account:3009;
include /etc/nginx/proxy.conf;
@@ -42,6 +52,10 @@ server {
server_name admin.${DOMAIN_NAME};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
proxy_pass http://admin:3008;
include /etc/nginx/proxy.conf;
@@ -54,6 +68,10 @@ server {
server_name api.${DOMAIN_NAME};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
proxy_pass http://backend:3005;
include /etc/nginx/proxy.conf;