Merge stable to the disable-healthcheck branch (#55)

Co-authored-by: Philipp Walter <philippwalter@pm.me>
Co-authored-by: nolim1t - f6287b82CC84bcbd <nolim1t@users.noreply.github.com>
This commit is contained in:
Aaron Dewes 2022-06-15 18:05:56 +02:00 committed by GitHub
parent e5c427fe79
commit 53545ba21a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 167 additions and 199 deletions

4
.gitignore vendored
View File

@ -25,7 +25,6 @@ electrs/*
fulcrumx/* fulcrumx/*
nginx/* nginx/*
redis/* redis/*
events/signals/*
docker-compose.override.yml docker-compose.override.yml
# Commit these empty directories # Commit these empty directories
@ -36,7 +35,6 @@ docker-compose.override.yml
!db/citadel-seed !db/citadel-seed
db/citadel-seed/* db/citadel-seed/*
!db/citadel-seed/.gitkeep !db/citadel-seed/.gitkeep
!events/signals/.gitkeep
!lnd/.gitkeep !lnd/.gitkeep
!logs/.gitkeep !logs/.gitkeep
!tor/data/.gitkeep !tor/data/.gitkeep
@ -46,9 +44,9 @@ db/citadel-seed/*
!nginx/.gitkeep !nginx/.gitkeep
!redis/.gitkeep !redis/.gitkeep
!fulcrumx/.gitkeep !fulcrumx/.gitkeep
!events/signals/.gitkeep
!**/*.license !**/*.license
services/installed.json services/installed.json
services/installed.yml services/installed.yml
events/karen

View File

@ -75,7 +75,7 @@ def is_builtin_type(obj):
def classToDict(theClass): def classToDict(theClass):
obj: dict = {} obj: dict = {}
for key, value in theClass.__dict__.items(): for key, value in theClass.__dict__.items():
if value is None or (isinstance(value, list) and len(value) == 0): if type(value).__name__ == "NoneType" or (isinstance(value, list) and len(value) == 0):
continue continue
if isinstance(value, list): if isinstance(value, list):
newList = [] newList = []
@ -83,6 +83,7 @@ def classToDict(theClass):
if is_builtin_type(element): if is_builtin_type(element):
newList.append(element) newList.append(element)
else: else:
if type(element).__name__ != "NoneType":
newList.append(classToDict(element)) newList.append(classToDict(element))
obj[key] = newList obj[key] = newList
elif isinstance(value, dict): elif isinstance(value, dict):
@ -95,8 +96,7 @@ def classToDict(theClass):
obj[key] = newDict obj[key] = newDict
elif is_builtin_type(value): elif is_builtin_type(value):
obj[key] = value obj[key] = value
else: elif type(value).__name__ != "NoneType":
#print(value)
obj[key] = classToDict(value) obj[key] = classToDict(value)
return obj return obj

View File

@ -0,0 +1,24 @@
from lib.citadelutils import classToDict
from lib.composegenerator.shared.env import validateEnv
from lib.composegenerator.v3.types import App, generateApp
from lib.composegenerator.v3.generate import convertContainerPermissions
def createCleanConfigFromV3(app: dict, nodeRoot: str):
parsedApp: App = generateApp(app)
for container in range(len(parsedApp.containers)):
# TODO: Make this dynamic and not hardcoded
if parsedApp.containers[container].requires and "c-lightning" in parsedApp.containers[container].requires:
parsedApp.containers[container] = None
parsedApp = convertContainerPermissions(parsedApp)
parsedApp = validateEnv(parsedApp)
finalApp = classToDict(parsedApp)
try:
finalApp['permissions'] = finalApp['metadata']['dependencies']
except:
finalApp['permissions'] = []
finalApp['id'] = finalApp['metadata']['id']
del finalApp['metadata']
# Set version of the cache file format
finalApp['version'] = "1"
return finalApp

View File

@ -40,6 +40,7 @@ def validateEnvStringOrListorDict(env: Union[str, Union[list, dict]], existingEn
def validateEnv(app: App): def validateEnv(app: App):
# For every container of the app, check if all env vars in the strings in environment are defined in env # For every container of the app, check if all env vars in the strings in environment are defined in env
for container in app.containers: for container in app.containers:
if container is not None:
if container.environment_allow: if container.environment_allow:
existingEnv = container.environment_allow existingEnv = container.environment_allow
del container.environment_allow del container.environment_allow

View File

@ -47,7 +47,7 @@ def getContainerHiddenService(
if isinstance(container.hiddenServicePorts, int): if isinstance(container.hiddenServicePorts, int):
return getHiddenServiceString( return getHiddenServiceString(
"{} {}".format(metadata.name, container.name), "{} {}".format(metadata.name, container.name),
metadata.id, metadata.id if isMainContainer else "{}-{}".format(metadata.id, container.name),
container.hiddenServicePorts, container.hiddenServicePorts,
containerIp, containerIp,
container.hiddenServicePorts, container.hiddenServicePorts,
@ -55,7 +55,7 @@ def getContainerHiddenService(
elif isinstance(container.hiddenServicePorts, list): elif isinstance(container.hiddenServicePorts, list):
return getHiddenServiceMultiPort( return getHiddenServiceMultiPort(
"{} {}".format(metadata.name, container.name), "{} {}".format(metadata.name, container.name),
metadata.id, metadata.id if isMainContainer else "{}-{}".format(metadata.id, container.name),
containerIp, containerIp,
container.hiddenServicePorts, container.hiddenServicePorts,
) )
@ -77,14 +77,14 @@ def getContainerHiddenService(
else: else:
additionalHiddenServices[key] = value additionalHiddenServices[key] = value
for key, value in additionalHiddenServices.items(): for key, value in additionalHiddenServices.items():
otherHiddenServices += "\n"
if isinstance(value, int): if isinstance(value, int):
otherHiddenServices += "# {} {} {} Hidden Service\nHiddenServiceDir /var/lib/tor/app-{}-{}\n".format( otherHiddenServices += "# {} {} {} Hidden Service\nHiddenServiceDir /var/lib/tor/app-{}-{}\n".format(
metadata.name, container.name, key, metadata.id, container.name metadata.name, container.name, key, metadata.id, key
) )
otherHiddenServices += "HiddenServicePort {} {}:{}".format( otherHiddenServices += "HiddenServicePort {} {}:{}".format(
value, containerIp, value value, containerIp, value
) )
otherHiddenServices += "\n"
elif isinstance(value, list): elif isinstance(value, list):
otherHiddenServices += getHiddenServiceMultiPort( otherHiddenServices += getHiddenServiceMultiPort(
"{} {}".format(metadata.name, key), "{}-{}".format(metadata.id, key), containerIp, value "{} {}".format(metadata.name, key), "{}-{}".format(metadata.id, key), containerIp, value

View File

@ -16,6 +16,7 @@ from lib.composegenerator.shared.const import permissions
def convertContainerPermissions(app: App) -> App: def convertContainerPermissions(app: App) -> App:
for container in app.containers: for container in app.containers:
if container is not None:
for permission in app.metadata.dependencies: for permission in app.metadata.dependencies:
if isinstance(permission, str): if isinstance(permission, str):
if permission in permissions(): if permission in permissions():
@ -84,11 +85,12 @@ def createComposeConfigFromV3(app: dict, nodeRoot: str):
newApp = configureIps(newApp, networkingFile, envFile) newApp = configureIps(newApp, networkingFile, envFile)
# This is validated earlier # This is validated earlier
for container in newApp.containers: for container in newApp.containers:
container.ports = container.requiredPorts for tcpPort in container.requiredPorts:
container.ports.append("{}:{}".format(tcpPort, tcpPort))
del container.requiredPorts del container.requiredPorts
for container in newApp.containers: for container in newApp.containers:
for udpPort in container.requiredUdpPorts: for udpPort in container.requiredUdpPorts:
container.ports.append("{}/udp".format(udpPort)) container.ports.append("{}:{}/udp".format(udpPort, udpPort))
del container.requiredUdpPorts del container.requiredUdpPorts
newApp = configureMainPort(newApp, nodeRoot) newApp = configureMainPort(newApp, nodeRoot)
newApp = configureHiddenServices(newApp, nodeRoot) newApp = configureHiddenServices(newApp, nodeRoot)

View File

@ -77,6 +77,8 @@ def configureMainPort(app: AppStage2, nodeRoot: str) -> AppStage3:
# If it doesn't contain a :, it's the port itself # If it doesn't contain a :, it's the port itself
if mainPort == False: if mainPort == False:
mainPort = mainContainer.ports[0] mainPort = mainContainer.ports[0]
if mainPort.find(":") != -1:
mainPort = mainPort.split(":")[1]
else: else:
mainContainer.ports = [portToAppend] mainContainer.ports = [portToAppend]

View File

@ -38,7 +38,7 @@ class Container:
port: Union[int, None] = None port: Union[int, None] = None
requiredPorts: list = field(default_factory=list) requiredPorts: list = field(default_factory=list)
requiredUdpPorts: list = field(default_factory=list) requiredUdpPorts: list = field(default_factory=list)
preferredOutsidePort: Union[int, None] = None #preferredOutsidePort: Union[int, None] = None
requiresPort: Union[bool, None] = None requiresPort: Union[bool, None] = None
environment: Union[dict, None] = None environment: Union[dict, None] = None
data: list = field(default_factory=list) data: list = field(default_factory=list)

View File

@ -5,6 +5,7 @@
import os import os
import yaml import yaml
from lib.composegenerator.next.stage1 import createCleanConfigFromV3
from lib.composegenerator.v2.networking import getMainContainer from lib.composegenerator.v2.networking import getMainContainer
from lib.composegenerator.v1.networking import getFreePort from lib.composegenerator.v1.networking import getFreePort
from lib.entropy import deriveEntropy from lib.entropy import deriveEntropy
@ -40,6 +41,7 @@ def getAppRegistry(apps, app_path):
app_metadata = [] app_metadata = []
for app in apps: for app in apps:
app_yml_path = os.path.join(app_path, app, 'app.yml') app_yml_path = os.path.join(app_path, app, 'app.yml')
app_cache_path = os.path.join(app_path, app, 'app.cache.json')
if os.path.isfile(app_yml_path): if os.path.isfile(app_yml_path):
try: try:
with open(app_yml_path, 'r') as f: with open(app_yml_path, 'r') as f:
@ -57,6 +59,8 @@ def getAppRegistry(apps, app_path):
getPortsOldApp(app_yml, app) getPortsOldApp(app_yml, app)
else: else:
getPortsV3App(app_yml, app) getPortsV3App(app_yml, app)
with open(app_cache_path, 'w') as f:
json.dump(createCleanConfigFromV3(app_yml, os.path.dirname(app_path)), f)
except Exception as e: except Exception as e:
print(e) print(e)
print("App {} is invalid!".format(app)) print("App {} is invalid!".format(app))

View File

@ -7,18 +7,18 @@ import yaml
from jsonschema import validate from jsonschema import validate
import yaml import yaml
scriptDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")
with open(os.path.join(scriptDir, 'app-standard-v1.yml'), 'r') as f:
schemaVersion1 = yaml.safe_load(f)
with open(os.path.join(scriptDir, 'app-standard-v2.yml'), 'r') as f:
schemaVersion2 = yaml.safe_load(f)
with open(os.path.join(scriptDir, 'app-standard-v3.yml'), 'r') as f:
schemaVersion3 = yaml.safe_load(f)
# Validates app data # Validates app data
# Returns true if valid, false otherwise # Returns true if valid, false otherwise
scriptDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")
def validateApp(app: dict): def validateApp(app: dict):
with open(os.path.join(scriptDir, 'app-standard-v1.yml'), 'r') as f:
schemaVersion1 = yaml.safe_load(f)
with open(os.path.join(scriptDir, 'app-standard-v2.yml'), 'r') as f:
schemaVersion2 = yaml.safe_load(f)
with open(os.path.join(scriptDir, 'app-standard-v3.yml'), 'r') as f:
schemaVersion3 = yaml.safe_load(f)
if 'version' in app and str(app['version']) == "1": if 'version' in app and str(app['version']) == "1":
try: try:
validate(app, schemaVersion1) validate(app, schemaVersion1)

View File

@ -13,7 +13,7 @@ CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
result=$(docker compose \ result=$(docker compose \
--file "${CITADEL_ROOT}/docker-compose.yml" \ --file "${CITADEL_ROOT}/docker-compose.yml" \
--env-file "${CITADEL_ROOT}/.env" \ --env-file "${CITADEL_ROOT}/.env" \
exec lnd lncli "$@") exec lightning lncli "$@")
# We need to echo with quotes to preserve output formatting # We need to echo with quotes to preserve output formatting
echo "$result" echo "$result"

View File

@ -2,7 +2,7 @@ version: '3.8'
services: services:
tor: tor:
container_name: tor container_name: tor
image: lncm/tor:0.4.6.8@sha256:c262923ffd0bd224a4a4123cf1c88eea11e2314566b7b7e8a1f77969deeb0208 image: lncm/tor:0.4.7.7@sha256:3c4ae833d2fefbea7d960f833a1e89fc9b2069a6e5f360109b5ddc9334ac0227
user: toruser user: toruser
restart: on-failure restart: on-failure
volumes: volumes:
@ -15,7 +15,7 @@ services:
ipv4_address: $TOR_PROXY_IP ipv4_address: $TOR_PROXY_IP
app-tor: app-tor:
container_name: app-tor container_name: app-tor
image: lncm/tor:0.4.6.8@sha256:c262923ffd0bd224a4a4123cf1c88eea11e2314566b7b7e8a1f77969deeb0208 image: lncm/tor:0.4.7.7@sha256:3c4ae833d2fefbea7d960f833a1e89fc9b2069a6e5f360109b5ddc9334ac0227
user: toruser user: toruser
restart: on-failure restart: on-failure
volumes: volumes:
@ -26,7 +26,7 @@ services:
ipv4_address: $APPS_TOR_IP ipv4_address: $APPS_TOR_IP
app-2-tor: app-2-tor:
container_name: app-2-tor container_name: app-2-tor
image: lncm/tor:0.4.6.8@sha256:c262923ffd0bd224a4a4123cf1c88eea11e2314566b7b7e8a1f77969deeb0208 image: lncm/tor:0.4.7.7@sha256:3c4ae833d2fefbea7d960f833a1e89fc9b2069a6e5f360109b5ddc9334ac0227
user: toruser user: toruser
restart: on-failure restart: on-failure
volumes: volumes:
@ -37,7 +37,7 @@ services:
ipv4_address: $APPS_2_TOR_IP ipv4_address: $APPS_2_TOR_IP
app-3-tor: app-3-tor:
container_name: app-3-tor container_name: app-3-tor
image: lncm/tor:0.4.6.8@sha256:c262923ffd0bd224a4a4123cf1c88eea11e2314566b7b7e8a1f77969deeb0208 image: lncm/tor:0.4.7.7@sha256:3c4ae833d2fefbea7d960f833a1e89fc9b2069a6e5f360109b5ddc9334ac0227
user: toruser user: toruser
restart: on-failure restart: on-failure
volumes: volumes:
@ -100,7 +100,7 @@ services:
ipv4_address: $LND_IP ipv4_address: $LND_IP
dashboard: dashboard:
container_name: dashboard container_name: dashboard
image: ghcr.io/runcitadel/dashboard:v0.0.12@sha256:102a44b938765be8224c63785949b90bf796670f01aef4e282d58a4c26a42595 image: ghcr.io/runcitadel/dashboard:v0.0.15@sha256:a2cf5ad79367fb083db0f61e5a296aafee655c99af0c228680644c248ec674a5
restart: on-failure restart: on-failure
stop_grace_period: 1m30s stop_grace_period: 1m30s
networks: networks:
@ -108,7 +108,7 @@ services:
ipv4_address: $DASHBOARD_IP ipv4_address: $DASHBOARD_IP
manager: manager:
container_name: manager container_name: manager
image: ghcr.io/runcitadel/manager:v0.0.12@sha256:de97eeb6e8caae5d1a8aeb045213621cf8e6e928177236bb0b3c25e76f6cd041 image: ghcr.io/runcitadel/manager:v0.0.15@sha256:9fb5a86d9e40a04f93d5b6110d43a0f9a5c4ad6311a843b5442290013196a5ce
depends_on: depends_on:
- tor - tor
- redis - redis
@ -120,7 +120,7 @@ services:
volumes: volumes:
- ${PWD}/info.json:/info.json - ${PWD}/info.json:/info.json
- ${PWD}/db:/db - ${PWD}/db:/db
- ${PWD}/events/signals:/signals - ${PWD}/events:/events
- ${PWD}/apps:/apps - ${PWD}/apps:/apps
- ${PWD}/lnd:/lnd:ro - ${PWD}/lnd:/lnd:ro
- ${PWD}/statuses:/statuses - ${PWD}/statuses:/statuses
@ -162,7 +162,7 @@ services:
ipv4_address: $MANAGER_IP ipv4_address: $MANAGER_IP
middleware: middleware:
container_name: middleware container_name: middleware
image: ghcr.io/runcitadel/middleware:v0.0.10@sha256:afd6e2b6f5ba27cde32f6f6d630ddc6dd46d1072871f7834d7424d0554d0f53d image: ghcr.io/runcitadel/middleware:v0.0.11@sha256:e472da8cbfa67d9a9dbf321334fe65cdf20a0f9b6d6bab33fdf07210f54e7002
depends_on: depends_on:
- manager - manager
- bitcoin - bitcoin

View File

@ -6,8 +6,4 @@
CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)" CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
signal="${1}" "${CITADEL_ROOT}/scripts/app" "$@"
command=${signal%%"-"*}
app=${signal#*"-"}
"${CITADEL_ROOT}/scripts/app" "${command}" "${app}"

View File

@ -6,4 +6,4 @@
CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)" CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
"${CITADEL_ROOT}/scripts/set-update-channel" beta "${CITADEL_ROOT}/scripts/set-update-channel" "${1}"

View File

@ -1,9 +0,0 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2021-2022 Citadel and contributors
#
# SPDX-License-Identifier: GPL-3.0-or-later
CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
"${CITADEL_ROOT}/scripts/set-update-channel" stable

View File

@ -1,6 +1,6 @@
{ {
"version": "0.0.3", "version": "0.0.5",
"name": "Citadel 0.0.3", "name": "Citadel 0.0.5",
"requires": ">=0.0.1", "requires": ">=0.0.1",
"notes": "This update fixes multiple bugs on the dashboard and contains a rewritten app system to prepare for Core Lightning compatibility. We also added a new dark mode toggle and updated the instructions to get a Lightning address." "notes": "This update fixes a few bugs in the 0.0.4 release that were preventing some apps from working correctly."
} }

100
karen
View File

@ -1,83 +1,27 @@
#!/usr/bin/env bash #!/usr/bin/env python3
# SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com import socket
# SPDX-FileCopyrightText: 2021-2022 Citadel and contributors. import os
#
# SPDX-License-Identifier: GPL-3.0-or-later
# karen watches for signals and executes triggers in the events dir rootDir = os.path.dirname(os.path.abspath(__file__))
# karen gets triggered a lot os.chdir(rootDir)
check_root () { if os.path.exists("events/karen"):
if [[ $UID != 0 ]]; then os.remove("events/karen")
echo "Error: This script must be run as root."
echo "Can I speak to a manager please?"
exit 1
fi
}
check_if_not_already_running() { server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep server.bind("events/karen")
then
echo "karen is already running"
exit 1
fi
}
check_dependencies () { while True:
for cmd in "$@"; do server.listen(1)
if ! command -v $cmd >/dev/null 2>&1; then conn, addr = server.accept()
echo "This script requires \"${cmd}\" to be installed" datagram = conn.recv(1024)
exit 1 if datagram:
fi instructions = datagram.decode("utf-8").strip().split()
done cmd = instructions[0]
} if(cmd == "trigger"):
trigger = instructions[1]
check_root instructions.pop(0)
instructions.pop(0)
check_if_not_already_running os.system("events/triggers/{} {}".format(trigger, " ".join(instructions)))
conn.close()
check_dependencies fswatch readlink dirname
if [[ -n "$1" ]]; then
root_dir="$(readlink -f $1)"
else
root_dir="$(dirname $(readlink -f ${BASH_SOURCE[0]}))/events"
fi
signal_dir="$root_dir/signals"
trigger_dir="$root_dir/triggers"
if [[ ! -d "$root_dir" ]]; then
echo "Root dir does not exist '$root_dir'"
exit 1
fi
echo "karen will start in 30 seconds"
sleep 30
echo "karen is running in $root_dir"
fswatch -0 --event=PlatformSpecific $signal_dir | while read -d "" event; do
signal="${event#"$signal_dir"}"
signal="${signal#"/"}"
trigger="$trigger_dir/$signal"
args=""
# If signal isn't an empty string, then it's a signal
if [[ -n "$signal" ]]; then
echo "Got signal: $signal"
# If the signal starts wih app-, the trigger is "$trigger_dir/app"
# and the args are the rest of the signal
if [[ "$signal" =~ ^app- ]] && [[ "$signal" != "app-update" ]]; then
trigger="$trigger_dir/app"
args="${signal#"app-"}"
fi
if test -x "$trigger"; then
echo "karen is getting triggered!"
if [[ ! -f "statuses/update-in-progress" ]]; then
"$trigger" $args &
fi
else
echo "No trigger found"
fi
fi
done

View File

@ -1,6 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com # SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com
# SPDX-FileCopyrightText: 2022 Citadel and contributors. https://runcitadel.space
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
@ -98,38 +99,18 @@ tar \
# --verbose \ # --verbose \
# --gzip # --gzip
# Check if a file exists on Firebase by checking if
# "https://firebasestorage.googleapis.com/v0/b/citadel-user-backups.appspot.com/o/backups%2F<THE_FILE_NAME>?alt=media"
# returns a 200 response code.
#
check_if_exists() {
curl -s -o /dev/null -w "%{http_code}" \
-X GET \
"https://firebasestorage.googleapis.com/v0/b/citadel-user-backups.appspot.com/o/backups%2F${1}?alt=media"
}
# Upload a file to Firebase Cloud Storage by uloading its name + a random ID.
# Before uploading, we need to check if a file with the same name already exists, and if it does, change the random ID.
# For example, if we want to upload a file named "<THE_FILE_NAME>" with the content AA, we'd use this command to upload:
# curl -X POST "https://firebasestorage.googleapis.com/v0/b/citadel-user-backups.appspot.com/o/backups%2F<THE_FILE_NAME>?alt=media" -d "AA" -H "Content-Type: text/plain"
# To check if a file exists, we can check if this endpoint returns an error 404:
# curl "https://firebasestorage.googleapis.com/v0/b/citadel-user-backups.appspot.com/o/backups%2F<THE_FILE_NAME>?alt=media"
# To download a file, we can the same endpoint
upload_file() { upload_file() {
local file_to_send="${1}" local file_to_send="${1}"
local file_name="${2}" local backup_id="${2}"
# A random ID to avoid collisions local upload_data=$(jq --null-input \
local random_id="$(tr -dc A-Za-z0-9 </dev/urandom | head -c 60 ; echo '')" --arg name "$backup_id" \
# Check if the file already exists --arg data "$(base64 $file_to_send)" \
# While a file with the same name exists, we'll try to upload it with a different ID '{"name": $name, "data": $data}')
while [[ $(check_if_exists "${file_name}-${random_id}") == "200" ]]; do
random_id="$(tr -dc A-Za-z0-9 </dev/urandom | head -c 60 ; echo '')"
done
# Upload the file
curl -X POST \ curl -X POST \
"https://firebasestorage.googleapis.com/v0/b/citadel-user-backups.appspot.com/o/backups%2F${file_name}-${random_id}?alt=media" \ "https://account.runcitadel.space/api/upload" \
-d @"${file_to_send}" \ -d "${upload_data}" \
-H "Content-Type: application/octet-stream" \ -H "Content-Type: application/json" \
--socks5 "localhost:${TOR_PROXY_PORT}" \
> /dev/null > /dev/null
} }

View File

@ -39,7 +39,7 @@ main () {
echo "Sleeping for ${delay} seconds..." echo "Sleeping for ${delay} seconds..."
sleep $delay sleep $delay
echo "Triggering decoy backup..." echo "Triggering decoy backup..."
touch "${CITADEL_ROOT}/events/signals/backup" "${CITADEL_ROOT}/scripts/backup/backup"
done done
} }

View File

@ -48,10 +48,10 @@ monitor_file () {
sleep 1 sleep 1
done done
echo "$file_path created! Triggering backup..." echo "$file_path created! Triggering backup..."
touch "${CITADEL_ROOT}/events/signals/backup" "${CITADEL_ROOT}/scripts/backup/backup"
fi fi
fswatch -0 --event Updated $file_path | xargs -0 -n 1 -I {} touch "${CITADEL_ROOT}/events/signals/backup" fswatch -0 --event Updated $file_path | xargs -0 -n 1 -I {} "${CITADEL_ROOT}/scripts/backup/backup"
} }
if [[ ! -d "${CITADEL_ROOT}" ]]; then if [[ ! -d "${CITADEL_ROOT}" ]]; then

View File

@ -21,7 +21,17 @@ function get_memory_usage() {
function mem_usage_to_percent() { function mem_usage_to_percent() {
local mem_usage="$1" local mem_usage="$1"
local total_mem="$(free -m | awk 'NR==2 {print $2}')" local total_mem="$(free -m | awk 'NR==2 {print $2}')"
echo "$(awk "BEGIN {printf \"%.1f\", $mem_usage / $total_mem * 100}")" echo "$(awk "BEGIN {printf \"%.1f\", ${mem_usage/,/.} / ${total_mem/,/.} * 100}")"
}
function app_mem_usage() {
# For every container of the app, get the mem usage, save it, and at the end, print the total mem usage of the app
local mem_usage=0
for container in $(get_app_containers "$1"); do
# Use awk to add, it supports floating point numbers
mem_usage=$(awk "BEGIN {printf \"%.2f\", $mem_usage + $(get_memory_usage "$container")}")
done
echo "${1}: $mem_usage%"
} }
get_total_used_mem_raw() { get_total_used_mem_raw() {
@ -35,7 +45,7 @@ get_total_used_mem() {
# To get the containers of the app, list every container whose name starts with the name of the app # To get the containers of the app, list every container whose name starts with the name of the app
get_app_containers () { get_app_containers () {
local app_name="$1" local app_name="$1"
"${CITADEL_ROOT}/scripts/app" compose "${app_name}" ps | awk '{print $1}' | grep -v 'Name\|-----' "${CITADEL_ROOT}/scripts/app" compose "${app_name}" ps | awk '{print $1}' | grep -v 'NAME'
} }
# Get the memory usage of the whole system, excluding docker containers # Get the memory usage of the whole system, excluding docker containers
@ -48,22 +58,17 @@ get_system_memory_usage() {
} }
main() { main() {
echo "total: $(get_total_used_mem)%"& echo "total: $(get_total_used_mem)%"
echo "system: $(get_system_memory_usage)%"
for service in bitcoin lightning electrum tor; do for service in bitcoin lightning electrum tor; do
echo "${service}: $(get_memory_usage "$service")%" & echo "${service}: $(get_memory_usage "$service")%" &
done done
for app in $("${CITADEL_ROOT}/scripts/app" ls-installed); do for app in $("${CITADEL_ROOT}/scripts/app" ls-installed); do
# For every container of the app, get the mem usage, save it, and at the end, print the total mem usage of the app app_mem_usage "${app}" &
local mem_usage=0
for container in $(get_app_containers "$app"); do
# Use awk to add, it supports floating point numbers
mem_usage=$(awk "BEGIN {printf \"%.2f\", $mem_usage + $(get_memory_usage "$container")}")
done done
wait wait
echo "${app}: $mem_usage%"
done
echo "system: $(get_system_memory_usage)%"
wait
} }
echo "Calculating memory usage..."
echo "This may take a while, please wait..."
main | sort --key 2 --numeric-sort --reverse main | sort --key 2 --numeric-sort --reverse

View File

@ -90,7 +90,7 @@ echo
echo "Starting status monitors..." echo "Starting status monitors..."
echo echo
pkill -f ./scripts/status-monitor || true pkill -f ./scripts/status-monitor || true
./scripts/status-monitor memory 60 &>> "${CITADEL_LOGS}/status-monitor.log" & ./scripts/status-monitor memory 300 &>> "${CITADEL_LOGS}/status-monitor.log" &
./scripts/status-monitor storage 60 &>> "${CITADEL_LOGS}/status-monitor.log" & ./scripts/status-monitor storage 60 &>> "${CITADEL_LOGS}/status-monitor.log" &
./scripts/status-monitor temperature 15 &>> "${CITADEL_LOGS}/status-monitor.log" & ./scripts/status-monitor temperature 15 &>> "${CITADEL_LOGS}/status-monitor.log" &
./scripts/status-monitor uptime 15 &>> "${CITADEL_LOGS}/status-monitor.log" & ./scripts/status-monitor uptime 15 &>> "${CITADEL_LOGS}/status-monitor.log" &

View File

@ -4,6 +4,9 @@
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# To prevent tools we use from outputting in another language
LANG=C
CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)" CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
memory_total_bytes() { memory_total_bytes() {
@ -23,7 +26,7 @@ get_app_memory_use() {
local app_memory=0 local app_memory=0
local app_containers=$("${CITADEL_ROOT}/app/app-manager.py" compose "${app}" ps | awk '{print $1}' | grep -v 'Name\|-----') local app_containers=$("${CITADEL_ROOT}/scripts/app" compose "${app}" ps | awk '{print $1}' | grep -v 'NAME')
for container in $app_containers; do for container in $app_containers; do
local container_memory=$(get_container_memory_use "${container}") local container_memory=$(get_container_memory_use "${container}")
app_memory=$(awk "BEGIN {print ${app_memory}+${container_memory}}") app_memory=$(awk "BEGIN {print ${app_memory}+${container_memory}}")
@ -43,7 +46,7 @@ used=$(memory_used_bytes)
json=$(echo $json | jq --arg used "${used}" '. + {used: $used|tonumber}') json=$(echo $json | jq --arg used "${used}" '. + {used: $used|tonumber}')
cumulative_app_memory="0" cumulative_app_memory="0"
for app in $( "${CITADEL_ROOT}/app/app-manager.py" ls-installed); do for app in $( "${CITADEL_ROOT}/scripts/app" ls-installed); do
app_memory=$(get_app_memory_use "${app}") app_memory=$(get_app_memory_use "${app}")
cumulative_app_memory=$(($cumulative_app_memory+$app_memory)) cumulative_app_memory=$(($cumulative_app_memory+$app_memory))
json=$(echo $json | jq --arg app "${app}" --arg app_memory "${app_memory}" '.breakdown |= .+ [{id: $app, used: $app_memory|tonumber}]') json=$(echo $json | jq --arg app "${app}" --arg app_memory "${app_memory}" '.breakdown |= .+ [{id: $app, used: $app_memory|tonumber}]')

View File

@ -5,7 +5,6 @@ lnd/*
statuses statuses
tor/* tor/*
electrs/* electrs/*
events/signals
logs/* logs/*
app-data/* app-data/*
apps/networking.json apps/networking.json

View File

@ -6,3 +6,6 @@ tor/torrc-apps-3
tor/torrc-core tor/torrc-core
electrs/electrs.toml electrs/electrs.toml
apps/docker-compose.common.yml apps/docker-compose.common.yml
services/bitcoin/*
services/electrum/*
services/lightning/*

View File

@ -4,7 +4,7 @@
electrum: electrum:
container_name: electrum container_name: electrum
image: ghcr.io/runcitadel/electrs:v0.9.5@sha256:5fdd76415645de14f31c43844dc143b1477f86872d2f73a041c5005d469ed510 image: ghcr.io/runcitadel/electrs:v0.9.6@sha256:cf08513b8aa6f081e42ea92c5903d520db2caedbc20aaccf70f844663bbc8646
working_dir: /data working_dir: /data
volumes: volumes:
- ${PWD}/bitcoin:/bitcoin:ro - ${PWD}/bitcoin:/bitcoin:ro

View File

@ -31,6 +31,21 @@ args = parser.parse_args()
# Function to install a service # Function to install a service
# To install it, read the service's YAML file (nodeRoot/services/name.yml) and add it to the main compose file (nodeRoot/docker-compose.yml) # To install it, read the service's YAML file (nodeRoot/services/name.yml) and add it to the main compose file (nodeRoot/docker-compose.yml)
def setService(name, implementation): def setService(name, implementation):
# Get all available services
services = next(os.walk(os.path.join(nodeRoot, "services")))[1]
if not name in services:
print("\"{}\" is not a valid service.".format(name))
exit(1)
# Get all available implementations
implementations = next(os.walk(os.path.join(nodeRoot, "services", name)), (None, None, []))[2]
implementations = [x.split('.')[0] for x in implementations]
if not implementation in implementations:
print("\"{}\" is not a valid implementation.".format(implementation))
exit(1)
# Read the YAML file # Read the YAML file
with open(os.path.join(nodeRoot, "services", name, implementation + ".yml"), 'r') as stream: with open(os.path.join(nodeRoot, "services", name, implementation + ".yml"), 'r') as stream:
service = yaml.safe_load(stream) service = yaml.safe_load(stream)