forked from michael.heier/citadel-core
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:
parent
e5c427fe79
commit
53545ba21a
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
24
app/lib/composegenerator/next/stage1.py
Normal file
24
app/lib/composegenerator/next/stage1.py
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}"
|
|
||||||
|
|
|
@ -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}"
|
|
@ -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
|
|
|
@ -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
100
karen
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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" &
|
||||||
|
|
|
@ -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}]')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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/*
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user