From 82af32fb634d3778e142294f42031124e63a23c0 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Fri, 3 Jun 2022 09:35:12 +0000 Subject: [PATCH 01/36] Use new backup server --- scripts/backup/backup | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/scripts/backup/backup b/scripts/backup/backup index a0be32d..be0ebbe 100755 --- a/scripts/backup/backup +++ b/scripts/backup/backup @@ -98,39 +98,19 @@ tar \ # --verbose \ # --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?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 "" 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?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?alt=media" -# To download a file, we can the same endpoint upload_file() { local file_to_send="${1}" - local file_name="${2}" - # A random ID to avoid collisions - local random_id="$(tr -dc A-Za-z0-9 /dev/null + "https://account.runcitadel.space/api/upload" \ + -d "${upload_data}" \ + -H "Content-Type: application/json" \ + > /dev/null } if [[ $BITCOIN_NETWORK == "testnet" ]]; then From 2b376b2c1ea1903bf3ac423276cbf713475d24bb Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Sat, 4 Jun 2022 19:02:53 +0200 Subject: [PATCH 02/36] Update backup --- scripts/backup/backup | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/backup/backup b/scripts/backup/backup index be0ebbe..7aa7016 100755 --- a/scripts/backup/backup +++ b/scripts/backup/backup @@ -1,6 +1,7 @@ #!/usr/bin/env bash # SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com +# SPDX-FileCopyrightText: 2022 Citadel and contributors. https://runcitadel.space # # SPDX-License-Identifier: GPL-3.0-or-later @@ -110,6 +111,7 @@ upload_file() { "https://account.runcitadel.space/api/upload" \ -d "${upload_data}" \ -H "Content-Type: application/json" \ + --socks5 "localhost:${TOR_PROXY_PORT}" \ > /dev/null } From 66c0ca350b4063fc54ad11daeb75c66da5fd82a0 Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Sat, 4 Jun 2022 19:50:37 +0200 Subject: [PATCH 03/36] Fix app generation for the Samourai app (#44) This also only loads the schema file for validation once instead for every app, which should improve the performance of the app validation --- app/lib/composegenerator/v2/utils/networking.py | 8 ++++---- app/lib/composegenerator/v3/generate.py | 5 +++-- app/lib/composegenerator/v3/networking.py | 2 ++ app/lib/validate.py | 16 ++++++++-------- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/lib/composegenerator/v2/utils/networking.py b/app/lib/composegenerator/v2/utils/networking.py index aedb781..709969c 100644 --- a/app/lib/composegenerator/v2/utils/networking.py +++ b/app/lib/composegenerator/v2/utils/networking.py @@ -47,7 +47,7 @@ def getContainerHiddenService( if isinstance(container.hiddenServicePorts, int): return getHiddenServiceString( "{} {}".format(metadata.name, container.name), - metadata.id, + metadata.id if isMainContainer else "{}-{}".format(metadata.id, container.name), container.hiddenServicePorts, containerIp, container.hiddenServicePorts, @@ -55,7 +55,7 @@ def getContainerHiddenService( elif isinstance(container.hiddenServicePorts, list): return getHiddenServiceMultiPort( "{} {}".format(metadata.name, container.name), - metadata.id, + metadata.id if isMainContainer else "{}-{}".format(metadata.id, container.name), containerIp, container.hiddenServicePorts, ) @@ -77,14 +77,14 @@ def getContainerHiddenService( else: additionalHiddenServices[key] = value for key, value in additionalHiddenServices.items(): - otherHiddenServices += "\n" if isinstance(value, int): 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( value, containerIp, value ) + otherHiddenServices += "\n" elif isinstance(value, list): otherHiddenServices += getHiddenServiceMultiPort( "{} {}".format(metadata.name, key), "{}-{}".format(metadata.id, key), containerIp, value diff --git a/app/lib/composegenerator/v3/generate.py b/app/lib/composegenerator/v3/generate.py index 014a769..5b7f56e 100644 --- a/app/lib/composegenerator/v3/generate.py +++ b/app/lib/composegenerator/v3/generate.py @@ -85,11 +85,12 @@ def createComposeConfigFromV3(app: dict, nodeRoot: str): newApp = configureIps(newApp, networkingFile, envFile) # This is validated earlier for container in newApp.containers: - container.ports = container.requiredPorts + for tcpPort in container.requiredPorts: + container.ports.append("{}:{}".format(tcpPort, tcpPort)) del container.requiredPorts for container in newApp.containers: for udpPort in container.requiredUdpPorts: - container.ports.append("{}/udp".format(udpPort)) + container.ports.append("{}/udp:{}/udp".format(udpPort, udpPort)) del container.requiredUdpPorts newApp = configureMainPort(newApp, nodeRoot) newApp = configureHiddenServices(newApp, nodeRoot) diff --git a/app/lib/composegenerator/v3/networking.py b/app/lib/composegenerator/v3/networking.py index 8280c95..66d5a96 100644 --- a/app/lib/composegenerator/v3/networking.py +++ b/app/lib/composegenerator/v3/networking.py @@ -77,6 +77,8 @@ def configureMainPort(app: AppStage2, nodeRoot: str) -> AppStage3: # If it doesn't contain a :, it's the port itself if mainPort == False: mainPort = mainContainer.ports[0] + if mainPort.find(":") != -1: + mainPort = mainPort.split(":")[1] else: mainContainer.ports = [portToAppend] diff --git a/app/lib/validate.py b/app/lib/validate.py index f822659..9dfe236 100644 --- a/app/lib/validate.py +++ b/app/lib/validate.py @@ -7,18 +7,18 @@ import yaml from jsonschema import validate 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 # Returns true if valid, false otherwise -scriptDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") 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": try: validate(app, schemaVersion1) From a504257b0d9ef008c275a002273b2c137c528f8d Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Sat, 4 Jun 2022 20:16:57 +0200 Subject: [PATCH 04/36] Remove debug echp --- scripts/backup/backup | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/backup/backup b/scripts/backup/backup index 7aa7016..035716a 100755 --- a/scripts/backup/backup +++ b/scripts/backup/backup @@ -106,7 +106,6 @@ upload_file() { --arg name "$backup_id" \ --arg data "$(base64 $file_to_send)" \ '{"name": $name, "data": $data}') - echo $upload_data curl -X POST \ "https://account.runcitadel.space/api/upload" \ -d "${upload_data}" \ From a54237f9eaec059390435994e1790d5b313a17b4 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Sun, 5 Jun 2022 07:49:18 +0000 Subject: [PATCH 05/36] Revert "Switch app store to beta branch" This reverts commit 6b29a76d8119331f1730f781f44ad6c7d5f8a0e6. --- app-system/sources.list | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app-system/sources.list b/app-system/sources.list index f433ce1..2466e50 100644 --- a/app-system/sources.list +++ b/app-system/sources.list @@ -3,9 +3,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later # A collection of fully FLOSS app definitions and FLOSS apps for Citadel. -https://github.com/runcitadel/apps v3-beta +https://github.com/runcitadel/apps v3-stable # Some apps modified version of Umbrel apps, and their app definitions aren't FLOSS yet. # Include them anyway, but as a separate repo. # Add a # to the line below to disable the repo and only use FLOSS apps. -https://github.com/runcitadel/apps-nonfree v3-beta +https://github.com/runcitadel/apps-nonfree v3-stable From ce55a8e3d70e0c82fb7b14b398a08a0709ddc77a Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Sun, 5 Jun 2022 07:50:22 +0000 Subject: [PATCH 06/36] Citadel 0.0.5 --- info.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.json b/info.json index b85be19..f35a1c9 100644 --- a/info.json +++ b/info.json @@ -1,6 +1,6 @@ { - "version": "0.0.4-rc.1", - "name": "Citadel 0.0.4 Release Candidate 1", + "version": "0.0.5", + "name": "Citadel 0.0.5", "requires": ">=0.0.1", - "notes": "This update fixes multiple bugs in the 0.0.4 release." + "notes": "This update fixes a few bugs in the 0.0.4 release that were preventing some apps from working correctly." } From 29d67d9e9ebcf6114ce046353e644071cbb827e0 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Sun, 5 Jun 2022 08:54:34 +0000 Subject: [PATCH 07/36] Update dashboard to 0.0.13 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 488db1b..2f9b918 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,7 +100,7 @@ services: ipv4_address: $LND_IP dashboard: container_name: dashboard - image: ghcr.io/runcitadel/dashboard:v0.0.12@sha256:102a44b938765be8224c63785949b90bf796670f01aef4e282d58a4c26a42595 + image: ghcr.io/runcitadel/dashboard:v0.0.13@sha256:86be4a105e9599163866d2e4f79c4d4ab9725ce56d71d7d7341e4d0ab3441b54 restart: on-failure stop_grace_period: 1m30s networks: From 2a933eaa1be23a4738afdbb050e30c2ab6edf5e1 Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Tue, 7 Jun 2022 20:28:45 +0200 Subject: [PATCH 08/36] Fix the way UDP ports are declared (#49) Previously, the way they had been defined was invalid --- app/lib/composegenerator/v3/generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/composegenerator/v3/generate.py b/app/lib/composegenerator/v3/generate.py index 5b7f56e..3e2789f 100644 --- a/app/lib/composegenerator/v3/generate.py +++ b/app/lib/composegenerator/v3/generate.py @@ -90,7 +90,7 @@ def createComposeConfigFromV3(app: dict, nodeRoot: str): del container.requiredPorts for container in newApp.containers: for udpPort in container.requiredUdpPorts: - container.ports.append("{}/udp:{}/udp".format(udpPort, udpPort)) + container.ports.append("{}:{}/udp".format(udpPort, udpPort)) del container.requiredUdpPorts newApp = configureMainPort(newApp, nodeRoot) newApp = configureHiddenServices(newApp, nodeRoot) From b87c54e219dc8fc77ed0ba52cf74f9cca0508b76 Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Tue, 7 Jun 2022 20:33:14 +0200 Subject: [PATCH 09/36] Fix memory scripts (#50) This also reduces the script interval, because it is still very slow. --- scripts/memory-usage | 29 +++++++++++++++++------------ scripts/start | 2 +- scripts/status/memory | 7 +++++-- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/scripts/memory-usage b/scripts/memory-usage index 1ff1f49..9c2ea19 100755 --- a/scripts/memory-usage +++ b/scripts/memory-usage @@ -21,7 +21,17 @@ function get_memory_usage() { function mem_usage_to_percent() { local mem_usage="$1" 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() { @@ -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 get_app_containers () { 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 @@ -48,22 +58,17 @@ get_system_memory_usage() { } 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 echo "${service}: $(get_memory_usage "$service")%" & done 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 - 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 - wait - echo "${app}: $mem_usage%" + app_mem_usage "${app}" & 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 diff --git a/scripts/start b/scripts/start index 63eff7b..983a702 100755 --- a/scripts/start +++ b/scripts/start @@ -90,7 +90,7 @@ echo echo "Starting status monitors..." echo 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 temperature 15 &>> "${CITADEL_LOGS}/status-monitor.log" & ./scripts/status-monitor uptime 15 &>> "${CITADEL_LOGS}/status-monitor.log" & diff --git a/scripts/status/memory b/scripts/status/memory index 7ffe607..8b05f15 100755 --- a/scripts/status/memory +++ b/scripts/status/memory @@ -4,6 +4,9 @@ # # 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]}")/../..)" memory_total_bytes() { @@ -23,7 +26,7 @@ get_app_memory_use() { 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 local container_memory=$(get_container_memory_use "${container}") 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}') 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}") 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}]') From 7311369fc886eaf8be12be777d9b9e3bb7d36c8d Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Wed, 8 Jun 2022 07:47:29 +0200 Subject: [PATCH 10/36] Bump version --- info.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/info.json b/info.json index cfec533..c161c59 100644 --- a/info.json +++ b/info.json @@ -1,6 +1,6 @@ { - "version": "0.0.5-rc.1", - "name": "Citadel 0.0.5-rc.1", + "version": "0.0.5-rc.2", + "name": "Citadel 0.0.5-rc.2", "requires": ">=0.0.1", "notes": "This update fixes a few bugs in the 0.0.4 release that were preventing some apps from working correctly." } From ebc99bff8cee3eff41ea273d4525e7c23d5d8c77 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Wed, 8 Jun 2022 10:00:48 +0000 Subject: [PATCH 11/36] Actually handle the result properly --- app/lib/manage.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/lib/manage.py b/app/lib/manage.py index ae8dbe5..bd53c0b 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -97,8 +97,16 @@ def update(verbose: bool = False): appDefinition = yaml.safe_load(f) if 'citadel_version' in appDefinition: os.chown(os.path.join(appsDir, app), 1000, 1000) - print("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/docker-compose.yml --services 'lnd'".format(appsDir, app, app, app)) - os.system("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/docker-compose.yml --services 'lnd'".format(appsDir, app, app, app)) + print("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) + os.system("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) + with open(os.path.join(appsDir, app, "result.yml"), "r") as resultFile: + resultYml = yaml.safe_load(resultFile) + with open(composeFile, "w") as dockerComposeFile: + yaml.dump(resultYml["spec"], dockerComposeFile) + torDaemons = ["torrc-apps", "torrc-apps-2", "torrc-apps-3"] + torFileToAppend = torDaemons[random.randint(0, len(torDaemons) - 1)] + with open(path.join(nodeRoot, "tor", torFileToAppend), 'a') as f: + f.write(resultYml["new_tor_entries"]) else: appCompose = getApp(appDefinition, app) with open(composeFile, "w") as f: From 2762db44a663174c1ba8375708657a5695fb8bd3 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Wed, 8 Jun 2022 10:06:07 +0000 Subject: [PATCH 12/36] Missing import --- app/lib/manage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/lib/manage.py b/app/lib/manage.py index bd53c0b..e96d2ee 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -6,6 +6,7 @@ import stat import sys import tempfile import threading +import random from typing import List from sys import argv import os From e2ddd20c5022a4de13c8dd2514ccf3b8f915be2a Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Wed, 8 Jun 2022 10:07:45 +0000 Subject: [PATCH 13/36] Another fix --- app/lib/manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/manage.py b/app/lib/manage.py index e96d2ee..d881eee 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -106,7 +106,7 @@ def update(verbose: bool = False): yaml.dump(resultYml["spec"], dockerComposeFile) torDaemons = ["torrc-apps", "torrc-apps-2", "torrc-apps-3"] torFileToAppend = torDaemons[random.randint(0, len(torDaemons) - 1)] - with open(path.join(nodeRoot, "tor", torFileToAppend), 'a') as f: + with open(os.path.join(nodeRoot, "tor", torFileToAppend), 'a') as f: f.write(resultYml["new_tor_entries"]) else: appCompose = getApp(appDefinition, app) From 336aa55934d2aa58fb30607cda84bd8b43e6de9a Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Fri, 10 Jun 2022 05:57:57 +0000 Subject: [PATCH 14/36] Update middleware & manager --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 0b2983b..f736637 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -108,7 +108,7 @@ services: ipv4_address: $DASHBOARD_IP manager: container_name: manager - image: ghcr.io/runcitadel/manager:v0.0.14@sha256:656efb89b5e0e849c40666ae6c4a36cf0def136fe0fab2da52889dacd7c2b688 + image: ghcr.io/runcitadel/manager:v0.0.15@sha256:9fb5a86d9e40a04f93d5b6110d43a0f9a5c4ad6311a843b5442290013196a5ce depends_on: - tor - redis @@ -162,7 +162,7 @@ services: ipv4_address: $MANAGER_IP 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: - manager - bitcoin From 26a8aac173ab4da51809228c1b9dc31035f5e4ed Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Fri, 10 Jun 2022 09:09:25 +0000 Subject: [PATCH 15/36] Fix IP assignment for v4 apps --- app/lib/composegenerator/shared/networking.py | 49 +++++++++++++++++++ app/lib/metadata.py | 5 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/app/lib/composegenerator/shared/networking.py b/app/lib/composegenerator/shared/networking.py index 0b4e47e..6c62fb4 100644 --- a/app/lib/composegenerator/shared/networking.py +++ b/app/lib/composegenerator/shared/networking.py @@ -54,6 +54,55 @@ def getFreePort(networkingFile: str, appId: str): return port +def assignIpV4(appId: str, containerName: str): + cleanContainerName = containerName.strip() + # If the name still contains a newline, throw an error + if cleanContainerName.find("\n") != -1: + raise Exception("Newline in container name") + env_var = "APP_{}_{}_IP".format( + appId.upper().replace("-", "_"), + cleanContainerName.upper().replace("-", "_") + ) + # Write a list of used IPs to the usedIpFile as JSON, and read that file to check if an IP + # can be used + usedIps = [] + networkingData = {} + if path.isfile(networkingFile): + with open(networkingFile, 'r') as f: + networkingData = json.load(f) + + if 'ip_addresses' in networkingData: + usedIps = list(networkingData['ip_addresses'].values()) + else: + networkingData['ip_addresses'] = {} + # An IP 10.21.21.xx, with x being a random number above 40 is asigned to the container + # If the IP is already in use, it will be tried again until it's not in use + # If it's not in use, it will be added to the usedIps list and written to the usedIpFile + # If the usedIpsFile contains all IPs between 10.21.21.20 and 10.21.21.255 (inclusive), + # Throw an error, because no more IPs can be used + if len(usedIps) == 235: + raise Exception("No more IPs can be used") + + if "{}-{}".format(appId, cleanContainerName) in networkingData['ip_addresses']: + ip = networkingData['ip_addresses']["{}-{}".format( + appId, cleanContainerName)] + else: + while True: + ip = "10.21.21." + str(random.randint(20, 255)) + if ip not in usedIps: + networkingData['ip_addresses']["{}-{}".format( + appId, cleanContainerName)] = ip + break + + dotEnv = parse_dotenv(envFile) + if env_var in dotEnv and str(dotEnv[env_var]) == str(ip): + return + + with open(envFile, 'a') as f: + f.write("{}={}\n".format(env_var, ip)) + with open(networkingFile, 'w') as f: + json.dump(networkingData, f) + def assignIp(container: ContainerStage2, appId: str, networkingFile: str, envFile: str) -> ContainerStage2: # Strip leading/trailing whitespace from container.name container.name = container.name.strip() diff --git a/app/lib/metadata.py b/app/lib/metadata.py index cba7d23..ba427ff 100644 --- a/app/lib/metadata.py +++ b/app/lib/metadata.py @@ -8,7 +8,7 @@ import traceback from lib.composegenerator.next.stage1 import createCleanConfigFromV3 from lib.composegenerator.v2.networking import getMainContainer -from lib.composegenerator.shared.networking import getFreePort +from lib.composegenerator.shared.networking import getFreePort, assignIpV4 from lib.entropy import deriveEntropy from typing import List import json @@ -173,6 +173,9 @@ def getPortsV3App(app, appId): def getPortsV4App(app, appId): for appContainerName in app["services"].keys(): appContainer = app["services"][appContainerName] + if "enable_networking" in appContainer and not appContainer["enable_networking"]: + return + assignIpV4(appId, appContainerName) if "port" in appContainer: validatePort(appContainerName, appContainer, appContainer["port"], appId, 0) if "required_ports" in appContainer: From 40684f75425ce695d9c0b7536df1bee136abb760 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Fri, 10 Jun 2022 09:12:51 +0000 Subject: [PATCH 16/36] More fix --- app/lib/composegenerator/shared/networking.py | 3 +++ app/lib/metadata.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/lib/composegenerator/shared/networking.py b/app/lib/composegenerator/shared/networking.py index 6c62fb4..4361715 100644 --- a/app/lib/composegenerator/shared/networking.py +++ b/app/lib/composegenerator/shared/networking.py @@ -55,6 +55,9 @@ def getFreePort(networkingFile: str, appId: str): return port def assignIpV4(appId: str, containerName: str): + scriptDir = os.path.dirname(os.path.realpath(__file__)) + nodeRoot = os.path.join(scriptDir, "..", "..", "..", "..") + networkingFile = os.path.join(nodeRoot, "apps", "networking.json") cleanContainerName = containerName.strip() # If the name still contains a newline, throw an error if cleanContainerName.find("\n") != -1: diff --git a/app/lib/metadata.py b/app/lib/metadata.py index ba427ff..ef0d194 100644 --- a/app/lib/metadata.py +++ b/app/lib/metadata.py @@ -8,7 +8,7 @@ import traceback from lib.composegenerator.next.stage1 import createCleanConfigFromV3 from lib.composegenerator.v2.networking import getMainContainer -from lib.composegenerator.shared.networking import getFreePort, assignIpV4 +from lib.composegenerator.shared.networking import assignIpV4 from lib.entropy import deriveEntropy from typing import List import json From ed51529728deeda082e4ccb865b07d38f4baa502 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Fri, 10 Jun 2022 09:13:25 +0000 Subject: [PATCH 17/36] Fix missing import --- app/lib/composegenerator/shared/networking.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/lib/composegenerator/shared/networking.py b/app/lib/composegenerator/shared/networking.py index 4361715..743f05c 100644 --- a/app/lib/composegenerator/shared/networking.py +++ b/app/lib/composegenerator/shared/networking.py @@ -55,9 +55,9 @@ def getFreePort(networkingFile: str, appId: str): return port def assignIpV4(appId: str, containerName: str): - scriptDir = os.path.dirname(os.path.realpath(__file__)) - nodeRoot = os.path.join(scriptDir, "..", "..", "..", "..") - networkingFile = os.path.join(nodeRoot, "apps", "networking.json") + scriptDir = path.dirname(path.realpath(__file__)) + nodeRoot = path.join(scriptDir, "..", "..", "..", "..") + networkingFile = path.join(nodeRoot, "apps", "networking.json") cleanContainerName = containerName.strip() # If the name still contains a newline, throw an error if cleanContainerName.find("\n") != -1: From f5db1c7b16366bea492fa97e9e29491b97c7de4c Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Fri, 10 Jun 2022 09:14:23 +0000 Subject: [PATCH 18/36] Missing definition --- app/lib/composegenerator/shared/networking.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/lib/composegenerator/shared/networking.py b/app/lib/composegenerator/shared/networking.py index 743f05c..ff9bc98 100644 --- a/app/lib/composegenerator/shared/networking.py +++ b/app/lib/composegenerator/shared/networking.py @@ -58,6 +58,7 @@ def assignIpV4(appId: str, containerName: str): scriptDir = path.dirname(path.realpath(__file__)) nodeRoot = path.join(scriptDir, "..", "..", "..", "..") networkingFile = path.join(nodeRoot, "apps", "networking.json") + envFile = path.join(nodeRoot, ".env") cleanContainerName = containerName.strip() # If the name still contains a newline, throw an error if cleanContainerName.find("\n") != -1: From f05c69cecca25aaf0cfab6cbbcc62f6cc0224ac5 Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Fri, 10 Jun 2022 21:33:40 +0200 Subject: [PATCH 19/36] Update dashboard --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index f736637..60eb321 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,7 +100,7 @@ services: ipv4_address: $LND_IP dashboard: container_name: dashboard - image: ghcr.io/runcitadel/dashboard:v0.0.13@sha256:86be4a105e9599163866d2e4f79c4d4ab9725ce56d71d7d7341e4d0ab3441b54 + image: ghcr.io/runcitadel/dashboard:v0.0.15@sha256:a2cf5ad79367fb083db0f61e5a296aafee655c99af0c228680644c248ec674a5 restart: on-failure stop_grace_period: 1m30s networks: From 54e82e31ed12b3929a08c490a1ac16c60e9cfe8c Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Sun, 5 Jun 2022 10:12:11 +0200 Subject: [PATCH 20/36] fix: update lncli binary docker container --- bin/lncli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/lncli b/bin/lncli index 3de018f..67753c0 100755 --- a/bin/lncli +++ b/bin/lncli @@ -13,7 +13,7 @@ CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)" result=$(docker compose \ --file "${CITADEL_ROOT}/docker-compose.yml" \ --env-file "${CITADEL_ROOT}/.env" \ - exec lnd lncli "$@") + exec lightning lncli "$@") # We need to echo with quotes to preserve output formatting echo "$result" From 964377869a47a4d92f810771c277537b0e4e4f84 Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Sat, 4 Jun 2022 23:43:47 +0200 Subject: [PATCH 21/36] add some validation to setService --- services/manage.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/services/manage.py b/services/manage.py index 3e9a3dc..c965b2d 100755 --- a/services/manage.py +++ b/services/manage.py @@ -31,6 +31,21 @@ args = parser.parse_args() # 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) 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 with open(os.path.join(nodeRoot, "services", name, implementation + ".yml"), 'r') as stream: service = yaml.safe_load(stream) From 62636fefa3d0d2bafbd872182a0b6f3d5061c3fa Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Sat, 11 Jun 2022 11:53:04 +0000 Subject: [PATCH 22/36] More fixes for app.yml v4 --- app/lib/manage.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/lib/manage.py b/app/lib/manage.py index d881eee..ddf3542 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -108,6 +108,22 @@ def update(verbose: bool = False): torFileToAppend = torDaemons[random.randint(0, len(torDaemons) - 1)] with open(os.path.join(nodeRoot, "tor", torFileToAppend), 'a') as f: f.write(resultYml["new_tor_entries"]) + mainPort = resultYml["port"] + registryFile = os.path.join(nodeRoot, "apps", "registry.json") + registry: list = [] + if os.path.isfile(registryFile): + with open(registryFile, 'r') as f: + registry = json.load(f) + else: + raise Exception("Registry file not found") + + for registryApp in registry: + if registryApp['id'] == app.metadata.id: + registry[registry.index(registryApp)]['port'] = resultYml["port"] + break + + with open(registryFile, 'w') as f: + json.dump(registry, f, indent=4, sort_keys=True) else: appCompose = getApp(appDefinition, app) with open(composeFile, "w") as f: From 675cd1502551eb216eed62e748b7bb25e76789ed Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Sat, 11 Jun 2022 11:55:18 +0000 Subject: [PATCH 23/36] Minor fix --- app/lib/manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/manage.py b/app/lib/manage.py index ddf3542..d7fd7e6 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -118,7 +118,7 @@ def update(verbose: bool = False): raise Exception("Registry file not found") for registryApp in registry: - if registryApp['id'] == app.metadata.id: + if registryApp['id'] == app: registry[registry.index(registryApp)]['port'] = resultYml["port"] break From 85f9886137a0e5e37db993083cb132d10003be5d Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Sat, 11 Jun 2022 18:35:47 +0200 Subject: [PATCH 24/36] Citadel 0.0.5 (#45) Co-authored-by: Philipp Walter --- .gitignore | 3 -- app/lib/composegenerator/v3/generate.py | 2 +- bin/lncli | 2 +- docker-compose.yml | 7 ++-- events/signals/.gitkeep | 0 .../{use-beta-builds => set-update-channel} | 2 +- events/triggers/use-stable-builds | 9 ---- info.json | 6 +-- scripts/backup/backup | 41 +++++-------------- scripts/backup/decoy-trigger | 2 +- scripts/backup/monitor | 4 +- scripts/memory-usage | 29 +++++++------ scripts/start | 2 +- scripts/status/memory | 7 +++- scripts/update/.updateignore | 1 - services/manage.py | 15 +++++++ 16 files changed, 61 insertions(+), 71 deletions(-) delete mode 100644 events/signals/.gitkeep rename events/triggers/{use-beta-builds => set-update-channel} (79%) delete mode 100755 events/triggers/use-stable-builds diff --git a/.gitignore b/.gitignore index 94b4eb5..5e50f48 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ electrs/* fulcrumx/* nginx/* redis/* -events/signals/* docker-compose.override.yml # Commit these empty directories @@ -36,7 +35,6 @@ docker-compose.override.yml !db/citadel-seed db/citadel-seed/* !db/citadel-seed/.gitkeep -!events/signals/.gitkeep !lnd/.gitkeep !logs/.gitkeep !tor/data/.gitkeep @@ -46,7 +44,6 @@ db/citadel-seed/* !nginx/.gitkeep !redis/.gitkeep !fulcrumx/.gitkeep -!events/signals/.gitkeep !**/*.license services/installed.json diff --git a/app/lib/composegenerator/v3/generate.py b/app/lib/composegenerator/v3/generate.py index 5b7f56e..3e2789f 100644 --- a/app/lib/composegenerator/v3/generate.py +++ b/app/lib/composegenerator/v3/generate.py @@ -90,7 +90,7 @@ def createComposeConfigFromV3(app: dict, nodeRoot: str): del container.requiredPorts for container in newApp.containers: for udpPort in container.requiredUdpPorts: - container.ports.append("{}/udp:{}/udp".format(udpPort, udpPort)) + container.ports.append("{}:{}/udp".format(udpPort, udpPort)) del container.requiredUdpPorts newApp = configureMainPort(newApp, nodeRoot) newApp = configureHiddenServices(newApp, nodeRoot) diff --git a/bin/lncli b/bin/lncli index 3de018f..67753c0 100755 --- a/bin/lncli +++ b/bin/lncli @@ -13,7 +13,7 @@ CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)" result=$(docker compose \ --file "${CITADEL_ROOT}/docker-compose.yml" \ --env-file "${CITADEL_ROOT}/.env" \ - exec lnd lncli "$@") + exec lightning lncli "$@") # We need to echo with quotes to preserve output formatting echo "$result" diff --git a/docker-compose.yml b/docker-compose.yml index ba90ff9..60eb321 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,7 +100,7 @@ services: ipv4_address: $LND_IP 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 stop_grace_period: 1m30s networks: @@ -108,7 +108,7 @@ services: ipv4_address: $DASHBOARD_IP manager: container_name: manager - image: ghcr.io/runcitadel/manager:v0.0.13@sha256:3ced2643e12253fea46abb48a69dcd999d69d3d86ed9956ae0298d4f6be4f06d + image: ghcr.io/runcitadel/manager:v0.0.15@sha256:9fb5a86d9e40a04f93d5b6110d43a0f9a5c4ad6311a843b5442290013196a5ce depends_on: - tor - redis @@ -120,7 +120,6 @@ services: volumes: - ${PWD}/info.json:/info.json - ${PWD}/db:/db - - ${PWD}/events/signals:/signals - ${PWD}/events:/events - ${PWD}/apps:/apps - ${PWD}/lnd:/lnd:ro @@ -163,7 +162,7 @@ services: ipv4_address: $MANAGER_IP 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: - manager - bitcoin diff --git a/events/signals/.gitkeep b/events/signals/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/events/triggers/use-beta-builds b/events/triggers/set-update-channel similarity index 79% rename from events/triggers/use-beta-builds rename to events/triggers/set-update-channel index 252e9b5..38fdec5 100755 --- a/events/triggers/use-beta-builds +++ b/events/triggers/set-update-channel @@ -6,4 +6,4 @@ CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)" -"${CITADEL_ROOT}/scripts/set-update-channel" beta +"${CITADEL_ROOT}/scripts/set-update-channel" "${1}" diff --git a/events/triggers/use-stable-builds b/events/triggers/use-stable-builds deleted file mode 100755 index 9a60e13..0000000 --- a/events/triggers/use-stable-builds +++ /dev/null @@ -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 diff --git a/info.json b/info.json index 5737f16..f35a1c9 100644 --- a/info.json +++ b/info.json @@ -1,6 +1,6 @@ { - "version": "0.0.4", - "name": "Citadel 0.0.4", + "version": "0.0.5", + "name": "Citadel 0.0.5", "requires": ">=0.0.1", - "notes": "This update fixes multiple bugs in the 0.0.3 release." + "notes": "This update fixes a few bugs in the 0.0.4 release that were preventing some apps from working correctly." } diff --git a/scripts/backup/backup b/scripts/backup/backup index a0be32d..035716a 100755 --- a/scripts/backup/backup +++ b/scripts/backup/backup @@ -1,6 +1,7 @@ #!/usr/bin/env bash # SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com +# SPDX-FileCopyrightText: 2022 Citadel and contributors. https://runcitadel.space # # SPDX-License-Identifier: GPL-3.0-or-later @@ -98,39 +99,19 @@ tar \ # --verbose \ # --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?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 "" 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?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?alt=media" -# To download a file, we can the same endpoint upload_file() { local file_to_send="${1}" - local file_name="${2}" - # A random ID to avoid collisions - local random_id="$(tr -dc A-Za-z0-9 /dev/null + "https://account.runcitadel.space/api/upload" \ + -d "${upload_data}" \ + -H "Content-Type: application/json" \ + --socks5 "localhost:${TOR_PROXY_PORT}" \ + > /dev/null } if [[ $BITCOIN_NETWORK == "testnet" ]]; then diff --git a/scripts/backup/decoy-trigger b/scripts/backup/decoy-trigger index 9d0465b..ddc8918 100755 --- a/scripts/backup/decoy-trigger +++ b/scripts/backup/decoy-trigger @@ -39,7 +39,7 @@ main () { echo "Sleeping for ${delay} seconds..." sleep $delay echo "Triggering decoy backup..." - touch "${CITADEL_ROOT}/events/signals/backup" + "${CITADEL_ROOT}/scripts/backup/backup" done } diff --git a/scripts/backup/monitor b/scripts/backup/monitor index 5234ac9..6bc499c 100755 --- a/scripts/backup/monitor +++ b/scripts/backup/monitor @@ -48,10 +48,10 @@ monitor_file () { sleep 1 done echo "$file_path created! Triggering backup..." - touch "${CITADEL_ROOT}/events/signals/backup" + "${CITADEL_ROOT}/scripts/backup/backup" 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 diff --git a/scripts/memory-usage b/scripts/memory-usage index 1ff1f49..9c2ea19 100755 --- a/scripts/memory-usage +++ b/scripts/memory-usage @@ -21,7 +21,17 @@ function get_memory_usage() { function mem_usage_to_percent() { local mem_usage="$1" 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() { @@ -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 get_app_containers () { 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 @@ -48,22 +58,17 @@ get_system_memory_usage() { } 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 echo "${service}: $(get_memory_usage "$service")%" & done 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 - 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 - wait - echo "${app}: $mem_usage%" + app_mem_usage "${app}" & 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 diff --git a/scripts/start b/scripts/start index 63eff7b..983a702 100755 --- a/scripts/start +++ b/scripts/start @@ -90,7 +90,7 @@ echo echo "Starting status monitors..." echo 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 temperature 15 &>> "${CITADEL_LOGS}/status-monitor.log" & ./scripts/status-monitor uptime 15 &>> "${CITADEL_LOGS}/status-monitor.log" & diff --git a/scripts/status/memory b/scripts/status/memory index 7ffe607..8b05f15 100755 --- a/scripts/status/memory +++ b/scripts/status/memory @@ -4,6 +4,9 @@ # # 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]}")/../..)" memory_total_bytes() { @@ -23,7 +26,7 @@ get_app_memory_use() { 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 local container_memory=$(get_container_memory_use "${container}") 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}') 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}") 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}]') diff --git a/scripts/update/.updateignore b/scripts/update/.updateignore index b7915c6..92b78e1 100644 --- a/scripts/update/.updateignore +++ b/scripts/update/.updateignore @@ -5,7 +5,6 @@ lnd/* statuses tor/* electrs/* -events/signals logs/* app-data/* apps/networking.json diff --git a/services/manage.py b/services/manage.py index 3e9a3dc..c965b2d 100755 --- a/services/manage.py +++ b/services/manage.py @@ -31,6 +31,21 @@ args = parser.parse_args() # 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) 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 with open(os.path.join(nodeRoot, "services", name, implementation + ".yml"), 'r') as stream: service = yaml.safe_load(stream) From 4f08bef2ebc4fc0d48a0f7dc710784ee1021eba0 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Tue, 14 Jun 2022 13:35:47 +0000 Subject: [PATCH 25/36] Remove unused cache files --- app/lib/composegenerator/next/stage1.py | 24 ------------------------ app/lib/metadata.py | 4 ---- 2 files changed, 28 deletions(-) delete mode 100644 app/lib/composegenerator/next/stage1.py diff --git a/app/lib/composegenerator/next/stage1.py b/app/lib/composegenerator/next/stage1.py deleted file mode 100644 index ab0b825..0000000 --- a/app/lib/composegenerator/next/stage1.py +++ /dev/null @@ -1,24 +0,0 @@ -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 diff --git a/app/lib/metadata.py b/app/lib/metadata.py index ef0d194..cf19672 100644 --- a/app/lib/metadata.py +++ b/app/lib/metadata.py @@ -6,7 +6,6 @@ import os import yaml import traceback -from lib.composegenerator.next.stage1 import createCleanConfigFromV3 from lib.composegenerator.v2.networking import getMainContainer from lib.composegenerator.shared.networking import assignIpV4 from lib.entropy import deriveEntropy @@ -42,7 +41,6 @@ def getAppRegistry(apps, app_path): app_metadata = [] for app in apps: 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): try: with open(app_yml_path, 'r') as f: @@ -65,8 +63,6 @@ def getAppRegistry(apps, app_path): getPortsOldApp(app_yml, app) elif version == 3: getPortsV3App(app_yml, app) - with open(app_cache_path, 'w') as f: - json.dump(createCleanConfigFromV3(app_yml, os.path.dirname(app_path)), f) elif version == 4: getPortsV4App(app_yml, app) except Exception as e: From 82097a46f821ada96ab274264929660d5ba0cc43 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Tue, 14 Jun 2022 16:41:50 +0000 Subject: [PATCH 26/36] Move app.yml v4 handling to a function --- app/lib/manage.py | 61 +++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/app/lib/manage.py b/app/lib/manage.py index d7fd7e6..f8d7d39 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later +import asyncio import stat import sys import tempfile @@ -33,8 +34,6 @@ from lib.metadata import getAppRegistry from lib.entropy import deriveEntropy # For an array of threads, join them and wait for them to finish - - def joinThreads(threads: List[threading.Thread]): for thread in threads: thread.join() @@ -52,14 +51,40 @@ userFile = os.path.join(nodeRoot, "db", "user.json") legacyScript = os.path.join(nodeRoot, "scripts", "app") # Returns a list of every argument after the second one in sys.argv joined into a string by spaces - - def getArguments(): arguments = "" for i in range(3, len(argv)): arguments += argv[i] + " " return arguments +async def handleAppV4(app): + os.chown(os.path.join(appsDir, app), 1000, 1000) + print("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) + os.system("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) + with open(os.path.join(appsDir, app, "result.yml"), "r") as resultFile: + resultYml = yaml.safe_load(resultFile) + with open(composeFile, "w") as dockerComposeFile: + yaml.dump(resultYml["spec"], dockerComposeFile) + torDaemons = ["torrc-apps", "torrc-apps-2", "torrc-apps-3"] + torFileToAppend = torDaemons[random.randint(0, len(torDaemons) - 1)] + with open(os.path.join(nodeRoot, "tor", torFileToAppend), 'a') as f: + f.write(resultYml["new_tor_entries"]) + mainPort = resultYml["port"] + registryFile = os.path.join(nodeRoot, "apps", "registry.json") + registry: list = [] + if os.path.isfile(registryFile): + with open(registryFile, 'r') as f: + registry = json.load(f) + else: + raise Exception("Registry file not found") + + for registryApp in registry: + if registryApp['id'] == app: + registry[registry.index(registryApp)]['port'] = resultYml["port"] + break + + with open(registryFile, 'w') as f: + json.dump(registry, f, indent=4, sort_keys=True) def getAppYml(name): with open(os.path.join(appsDir, "sourceMap.json"), "r") as f: @@ -97,33 +122,7 @@ def update(verbose: bool = False): with open(appYml, 'r') as f: appDefinition = yaml.safe_load(f) if 'citadel_version' in appDefinition: - os.chown(os.path.join(appsDir, app), 1000, 1000) - print("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) - os.system("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) - with open(os.path.join(appsDir, app, "result.yml"), "r") as resultFile: - resultYml = yaml.safe_load(resultFile) - with open(composeFile, "w") as dockerComposeFile: - yaml.dump(resultYml["spec"], dockerComposeFile) - torDaemons = ["torrc-apps", "torrc-apps-2", "torrc-apps-3"] - torFileToAppend = torDaemons[random.randint(0, len(torDaemons) - 1)] - with open(os.path.join(nodeRoot, "tor", torFileToAppend), 'a') as f: - f.write(resultYml["new_tor_entries"]) - mainPort = resultYml["port"] - registryFile = os.path.join(nodeRoot, "apps", "registry.json") - registry: list = [] - if os.path.isfile(registryFile): - with open(registryFile, 'r') as f: - registry = json.load(f) - else: - raise Exception("Registry file not found") - - for registryApp in registry: - if registryApp['id'] == app: - registry[registry.index(registryApp)]['port'] = resultYml["port"] - break - - with open(registryFile, 'w') as f: - json.dump(registry, f, indent=4, sort_keys=True) + handleAppV4(app) else: appCompose = getApp(appDefinition, app) with open(composeFile, "w") as f: From ce6d59992b4a679dc6269e962835f38b2b8dbc68 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Tue, 14 Jun 2022 17:01:27 +0000 Subject: [PATCH 27/36] Multi-threading --- app/lib/manage.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/lib/manage.py b/app/lib/manage.py index f8d7d39..a097c50 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -114,6 +114,7 @@ def update(verbose: bool = False): json.dump(registry["ports"], f, sort_keys=True) print("Wrote registry to registry.json") + threads = list() # Loop through the apps and generate valid compose files from them, then put these into the app dir for app in apps: try: @@ -122,7 +123,9 @@ def update(verbose: bool = False): with open(appYml, 'r') as f: appDefinition = yaml.safe_load(f) if 'citadel_version' in appDefinition: - handleAppV4(app) + thread = threading.Thread(target=handleAppV4, args=(app)) + thread.start() + threads.append(thread) else: appCompose = getApp(appDefinition, app) with open(composeFile, "w") as f: @@ -134,6 +137,7 @@ def update(verbose: bool = False): print("Failed to convert app {}".format(app)) print(err) + joinThreads(threads) print("Generated configuration successfully") From 9d210e6996ce24a3ddeb574b72b9374d523fc012 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Tue, 14 Jun 2022 17:06:08 +0000 Subject: [PATCH 28/36] Minor fix --- app/lib/manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/manage.py b/app/lib/manage.py index a097c50..f72c18a 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -123,7 +123,7 @@ def update(verbose: bool = False): with open(appYml, 'r') as f: appDefinition = yaml.safe_load(f) if 'citadel_version' in appDefinition: - thread = threading.Thread(target=handleAppV4, args=(app)) + thread = threading.Thread(target=handleAppV4, args=(app,)) thread.start() threads.append(thread) else: From fae9c31ba037426c34526e73f102db65d0927b8f Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Tue, 14 Jun 2022 17:07:46 +0000 Subject: [PATCH 29/36] Remove unused async --- app/lib/manage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/lib/manage.py b/app/lib/manage.py index f72c18a..ed41a0b 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -import asyncio import stat import sys import tempfile @@ -57,7 +56,7 @@ def getArguments(): arguments += argv[i] + " " return arguments -async def handleAppV4(app): +def handleAppV4(app): os.chown(os.path.join(appsDir, app), 1000, 1000) print("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) os.system("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) From dc6f7dfbfdf6b2b019fa31186ed54f449c1027b4 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Tue, 14 Jun 2022 17:09:41 +0000 Subject: [PATCH 30/36] Another fix --- app/lib/manage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/lib/manage.py b/app/lib/manage.py index ed41a0b..4367705 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -57,6 +57,7 @@ def getArguments(): return arguments def handleAppV4(app): + composeFile = os.path.join(appsDir, app, "docker-compose.yml") os.chown(os.path.join(appsDir, app), 1000, 1000) print("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) os.system("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) From 0358ba84e6769f0fc1f7bcefb31f4d9cc40d883d Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Tue, 14 Jun 2022 17:24:51 +0000 Subject: [PATCH 31/36] Better support for dependencies.yml --- db/dependencies.yml | 6 +++--- scripts/configure | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/db/dependencies.yml b/db/dependencies.yml index 05fddd8..de2a5d4 100644 --- a/db/dependencies.yml +++ b/db/dependencies.yml @@ -1,6 +1,6 @@ compose: v2.6.0 bitcoin: 22.0 lnd: 0.14.3 -dashboard: v0.0.13@sha256:86be4a105e9599163866d2e4f79c4d4ab9725ce56d71d7d7341e4d0ab3441b54 -manager: v0.0.14@sha256:656efb89b5e0e849c40666ae6c4a36cf0def136fe0fab2da52889dacd7c2b688 -middleware: v0.0.10@sha256:afd6e2b6f5ba27cde32f6f6d630ddc6dd46d1072871f7834d7424d0554d0f53d +dashboard: ghcr.io/runcitadel/dashboard:v0.0.15@sha256:a2cf5ad79367fb083db0f61e5a296aafee655c99af0c228680644c248ec674a5 +manager: ghcr.io/runcitadel/manager:v0.0.15@sha256:9fb5a86d9e40a04f93d5b6110d43a0f9a5c4ad6311a843b5442290013196a5ce +middleware: ghcr.io/runcitadel/middleware:v0.0.11@sha256:e472da8cbfa67d9a9dbf321334fe65cdf20a0f9b6d6bab33fdf07210f54e7002 diff --git a/scripts/configure b/scripts/configure index 971f94a..9123c77 100755 --- a/scripts/configure +++ b/scripts/configure @@ -371,6 +371,17 @@ print("Generated configuration files\n") print("Checking if Docker Compose is installed...") download_docker_compose() +print("Updating core services...") +print() +with open("docker-compose.yml", 'r') as stream: + compose = yaml.safe_load(stream) +with open("db/dependencies.yml", 'r') as stream: + dependencies = yaml.safe_load(stream) +for service in ["manager", "middleware", "dashboard"]: + compose[service][image] = dependencies[service] +with open("docker-compose.yml") as stream: + yaml.dump(compose, stream, sort_keys=False) + if not reconfiguring: print("Updating apps...\n") os.system('./scripts/app --invoked-by-configure update') From 86c4a1b572a9c5acf9d4f82bb4f89964ba174582 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Tue, 14 Jun 2022 17:26:38 +0000 Subject: [PATCH 32/36] Another fix --- scripts/configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/configure b/scripts/configure index 9123c77..45278a2 100755 --- a/scripts/configure +++ b/scripts/configure @@ -378,7 +378,7 @@ with open("docker-compose.yml", 'r') as stream: with open("db/dependencies.yml", 'r') as stream: dependencies = yaml.safe_load(stream) for service in ["manager", "middleware", "dashboard"]: - compose[service][image] = dependencies[service] + compose["services"][service][image] = dependencies[service] with open("docker-compose.yml") as stream: yaml.dump(compose, stream, sort_keys=False) From 61efdcba255583b87ce84de18ade72ccb77f5861 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Tue, 14 Jun 2022 17:27:31 +0000 Subject: [PATCH 33/36] More fix --- scripts/configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/configure b/scripts/configure index 45278a2..9133591 100755 --- a/scripts/configure +++ b/scripts/configure @@ -378,7 +378,7 @@ with open("docker-compose.yml", 'r') as stream: with open("db/dependencies.yml", 'r') as stream: dependencies = yaml.safe_load(stream) for service in ["manager", "middleware", "dashboard"]: - compose["services"][service][image] = dependencies[service] + compose["services"][service]["image"] = dependencies[service] with open("docker-compose.yml") as stream: yaml.dump(compose, stream, sort_keys=False) From 229cc962353feccd4bef55dffd48b63d3c34d132 Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Tue, 14 Jun 2022 17:28:35 +0000 Subject: [PATCH 34/36] Open with correct permissions --- scripts/configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/configure b/scripts/configure index 9133591..5f7c386 100755 --- a/scripts/configure +++ b/scripts/configure @@ -379,7 +379,7 @@ with open("db/dependencies.yml", 'r') as stream: dependencies = yaml.safe_load(stream) for service in ["manager", "middleware", "dashboard"]: compose["services"][service]["image"] = dependencies[service] -with open("docker-compose.yml") as stream: +with open("docker-compose.yml", "w") as stream: yaml.dump(compose, stream, sort_keys=False) if not reconfiguring: From c6505387b54f46935f5c06e282ce7f83bbdb2dca Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Tue, 14 Jun 2022 17:42:12 +0000 Subject: [PATCH 35/36] Implement file locking --- app/lib/manage.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/app/lib/manage.py b/app/lib/manage.py index 4367705..d3342a1 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -10,6 +10,7 @@ import random from typing import List from sys import argv import os +import fcntl import requests import shutil import json @@ -32,6 +33,32 @@ from lib.validate import findAndValidateApps from lib.metadata import getAppRegistry from lib.entropy import deriveEntropy + + +class FileLock: + """Implements a file-based lock using flock(2). + The lock file is saved in directory dir with name lock_name. + dir is the current directory by default. + """ + + def __init__(self, lock_name, dir="."): + self.lock_file = open(os.path.join(dir, lock_name), "w") + + def acquire(self, blocking=True): + """Acquire the lock. + If the lock is not already acquired, return None. If the lock is + acquired and blocking is True, block until the lock is released. If + the lock is acquired and blocking is False, raise an IOError. + """ + ops = fcntl.LOCK_EX + if not blocking: + ops |= fcntl.LOCK_NB + fcntl.flock(self.lock_file, ops) + + def release(self): + """Release the lock. Return None even if lock not currently acquired""" + fcntl.flock(self.lock_file, fcntl.LOCK_UN) + # For an array of threads, join them and wait for them to finish def joinThreads(threads: List[threading.Thread]): for thread in threads: @@ -72,6 +99,8 @@ def handleAppV4(app): mainPort = resultYml["port"] registryFile = os.path.join(nodeRoot, "apps", "registry.json") registry: list = [] + lock = FileLock("citadeL_registry_lock", dir="/tmp") + lock.acquire() if os.path.isfile(registryFile): with open(registryFile, 'r') as f: registry = json.load(f) @@ -85,6 +114,7 @@ def handleAppV4(app): with open(registryFile, 'w') as f: json.dump(registry, f, indent=4, sort_keys=True) + lock.release() def getAppYml(name): with open(os.path.join(appsDir, "sourceMap.json"), "r") as f: From 983731b3df333c843bd2a6e9fce3df65260fb38a Mon Sep 17 00:00:00 2001 From: AaronDewes Date: Wed, 15 Jun 2022 07:13:08 +0000 Subject: [PATCH 36/36] Improve dependency managment --- app/lib/manage.py | 8 ++++---- db/dependencies.yml | 1 + scripts/configure | 2 -- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/lib/manage.py b/app/lib/manage.py index d3342a1..98400f6 100644 --- a/app/lib/manage.py +++ b/app/lib/manage.py @@ -33,8 +33,6 @@ from lib.validate import findAndValidateApps from lib.metadata import getAppRegistry from lib.entropy import deriveEntropy - - class FileLock: """Implements a file-based lock using flock(2). The lock file is saved in directory dir with name lock_name. @@ -75,6 +73,9 @@ updateIgnore = os.path.join(appsDir, ".updateignore") appDataDir = os.path.join(nodeRoot, "app-data") userFile = os.path.join(nodeRoot, "db", "user.json") legacyScript = os.path.join(nodeRoot, "scripts", "app") +with open(os.path.join(nodeRoot, "db", "dependencies.yml"), "r") as file: + dependencies = yaml.safe_load(file) + # Returns a list of every argument after the second one in sys.argv joined into a string by spaces def getArguments(): @@ -86,8 +87,7 @@ def getArguments(): def handleAppV4(app): composeFile = os.path.join(appsDir, app, "docker-compose.yml") os.chown(os.path.join(appsDir, app), 1000, 1000) - print("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) - os.system("docker run --rm -v {}:/apps -u 1000:1000 ghcr.io/runcitadel/app-cli:main /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, app, app, app)) + os.system("docker run --rm -v {}:/apps -u 1000:1000 {} /app-cli convert --app-name '{}' --port-map /apps/ports.json /apps/{}/app.yml /apps/{}/result.yml --services 'lnd'".format(appsDir, dependencies['app-cli'], app, app, app)) with open(os.path.join(appsDir, app, "result.yml"), "r") as resultFile: resultYml = yaml.safe_load(resultFile) with open(composeFile, "w") as dockerComposeFile: diff --git a/db/dependencies.yml b/db/dependencies.yml index de2a5d4..9b54520 100644 --- a/db/dependencies.yml +++ b/db/dependencies.yml @@ -4,3 +4,4 @@ lnd: 0.14.3 dashboard: ghcr.io/runcitadel/dashboard:v0.0.15@sha256:a2cf5ad79367fb083db0f61e5a296aafee655c99af0c228680644c248ec674a5 manager: ghcr.io/runcitadel/manager:v0.0.15@sha256:9fb5a86d9e40a04f93d5b6110d43a0f9a5c4ad6311a843b5442290013196a5ce middleware: ghcr.io/runcitadel/middleware:v0.0.11@sha256:e472da8cbfa67d9a9dbf321334fe65cdf20a0f9b6d6bab33fdf07210f54e7002 +app-cli: ghcr.io/runcitadel/app-cli:main@sha256:f532923eac28cfac03579cbb440397bcf16c8730f291b39eeada8278331f7054 diff --git a/scripts/configure b/scripts/configure index 5f7c386..8596f0b 100755 --- a/scripts/configure +++ b/scripts/configure @@ -375,8 +375,6 @@ print("Updating core services...") print() with open("docker-compose.yml", 'r') as stream: compose = yaml.safe_load(stream) -with open("db/dependencies.yml", 'r') as stream: - dependencies = yaml.safe_load(stream) for service in ["manager", "middleware", "dashboard"]: compose["services"][service]["image"] = dependencies[service] with open("docker-compose.yml", "w") as stream: