citadel-core/app/app-manager.py
Aaron Dewes 8bcb66f0fa
Citadel 0.1.8 (#90)
* Move LND into an app

* Add JWT pubkey module

* Remove old LND dir

* Clean up

* Some cleanups

* WIP: LND app

* Clean up output of ls-installed

* Clean up app system

* Various cleanups

* Fix volume name

* Update dependencies.yml

* Update app-manager

* Fix some minor issues

* Update manager

* Some fixes for the LND app

* Some fixes

* WIP: Caddy

* WIP: More https

* Caddy improvements

* Some more fixes

* Fix caddy port

* Fix for LND app

* Fixes for some apps

* Code cleanups

* Fix entry name

* Fix python

* Update app-manager

* Some Caddy fixes

* Update app-manager

* Fix tor

* Fix Caddy

* Fix caddy

* Minor fix

* Fix

* Fix https

* Update dependencies.yml

* Fix for CLN (#1)

* Update dependencies.yml

* Fix Caddyfile

* Expose IP address to manager

* Update API

* Use API from Docker Hub

* Update dependencies.yml

* Update dependencies.yml

* Update dependencies.yml

* Some fixes

* Minor syntax fix

* How did I even do that?

* Update docker-compose.yml

* Allow restarting Caddy

* Add configure trigger

* Replace configure with a caddy config update

* Update dependencies.yml

* Update Tor

* Update dependencies.yml

* Update dependencies.yml

* Update dependencies.yml

* Latest dashboard

* Move to ghcr.io

* Update 01-run.sh

* Update 01-run.sh

* Update 01-run.sh

* Update dependencies.yml

* Clean up

* Fix mount

* Update mount

* Create .gitkeep

* Dynamic caddy updates

* Update app-cli

* Update dependencies.yml

* Update dependencies.yml

* Remove Lightning logs from debug script

* Update app manager

* Clean up

* Update app-cli

* Citadel 0.1.8

* Remove host gateway
2023-04-16 21:12:12 +02:00

228 lines
8.0 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2021-2023 Citadel and contributors
#
# SPDX-License-Identifier: GPL-3.0-or-later
import argparse
import json
import os
import time
from lib.manage import (compose, convert_to_upper, createDataDir, deleteData,
download, downloadAll, downloadNew, get_var_safe,
getAvailableUpdates, getUserData, setInstalled,
setRemoved, update, getAppRegistryEntry)
# Print an error if user is not root
if os.getuid() != 0:
print('This script must be run as root!')
exit(1)
# The directory with this script
scriptDir = os.path.dirname(os.path.realpath(__file__))
nodeRoot = os.path.join(scriptDir, "..")
appsDir = os.path.join(nodeRoot, "apps")
appDataDir = os.path.join(nodeRoot, "app-data")
userFile = os.path.join(nodeRoot, "db", "user.json")
legacyScript = os.path.join(nodeRoot, "scripts", "app")
torDataDir = os.path.join(nodeRoot, "tor", "data")
parser = argparse.ArgumentParser(description="Manage apps on your Citadel")
parser.add_argument('action', help='What to do with the app database.', choices=[
"download", "generate", "update", "list-updates", "ls-installed", "install", "uninstall", "stop", "start", "compose", "restart", "get-ip", "get-implementation"])
parser.add_argument('--verbose', '-v', action='store_true')
parser.add_argument(
'app', help='Optional, the app to perform an action on. (For install, uninstall, stop, start and compose)', nargs='?')
parser.add_argument(
'other', help='Anything else (For compose)', nargs="*")
args = parser.parse_args()
# If no action is specified, the list action is used
if args.action is None:
args.action = 'list'
if args.action == "list-updates":
getAvailableUpdates()
exit(0)
elif args.action == 'download':
downloadAll()
exit(0)
elif args.action == 'download-new':
downloadNew()
exit(0)
elif args.action == 'generate':
update()
exit(0)
elif args.action == 'update':
if args.app is None:
downloadAll()
print("Downloaded all updates")
else:
download(args.app)
print("Downloaded latest {} version".format(args.app))
update()
exit(0)
elif args.action == 'ls-installed':
try:
# Load the userFile as JSON, check if installedApps is in it, and if so, print the apps
with open(userFile, "r") as f:
userData = json.load(f)
if "installedApps" in userData:
with open(os.path.join(appsDir, "virtual-apps.json"), "r") as f:
virtual_apps = json.load(f)
# Print the apps
# Filter out virtual apps (virtual_apps.keys())
for app in userData["installedApps"]:
if app not in virtual_apps.keys():
print(app)
else:
# To match the behavior of the old script, print a newline if there are no apps installed
print("\n")
except:
pass
elif args.action == 'install':
if not args.app:
print("No app provided")
exit(1)
registryEntry = getAppRegistryEntry(args.app)
# If registryEntry is None, fail
if registryEntry is None:
print("App {} does not seem to exist".format(args.app))
exit(1)
if isinstance(registryEntry['hiddenServices'], list):
for entry in registryEntry['hiddenServices']:
if not os.path.exists(os.path.join(torDataDir, entry, "hostname")):
print("Restarting Tor containers...")
try:
os.system("docker restart app-tor app-2-tor app-3-tor")
except:
print("Failed to restart Tor containers")
exit(1)
print("Waiting for Tor containers to restart...")
for i in range(60):
if os.path.exists(os.path.join(torDataDir, entry, "hostname")):
break
time.sleep(1)
else:
print("Tor containers did not restart in time")
exit(1)
update()
with open(os.path.join(appsDir, "virtual-apps.json"), "r") as f:
virtual_apps = json.load(f)
userData = getUserData()
implements_service = False
for virtual_app in virtual_apps.keys():
implementations = virtual_apps[virtual_app]
if args.app in implementations:
for implementation in implementations:
if "installedApps" in userData and implementation in userData["installedApps"]:
print("Another implementation of {} is already installed: {}. Uninstall it first to install this app.".format(virtual_app, implementation))
exit(1)
implements_service = virtual_app
createDataDir(args.app)
compose(args.app, "pull")
compose(args.app, "up --detach")
setInstalled(args.app)
if implements_service:
setInstalled(implements_service)
update()
elif args.action == 'uninstall':
if not args.app:
print("No app provided")
exit(1)
userData = getUserData()
if not "installedApps" in userData or args.app not in userData["installedApps"]:
print("App {} is not installed".format(args.app))
exit(1)
print("Stopping app {}...".format(args.app))
try:
compose(args.app, "rm --force --stop")
print("Deleting data...")
deleteData(args.app)
except:
pass
print("Removing from the list of installed apps...")
setRemoved(args.app)
with open(os.path.join(appsDir, "virtual-apps.json"), "r") as f:
virtual_apps = json.load(f)
implements_service = False
for virtual_app in virtual_apps.keys():
implementations = virtual_apps[virtual_app]
if args.app in implementations:
setRemoved(virtual_app)
update()
elif args.action == 'stop':
if not args.app:
print("No app provided")
exit(1)
userData = getUserData()
print("Stopping app {}...".format(args.app))
compose(args.app, "rm --force --stop")
elif args.action == 'start':
if not args.app:
print("No app provided")
exit(1)
userData = getUserData()
if not "installedApps" in userData or args.app not in userData["installedApps"]:
print("App {} is not yet installed".format(args.app))
exit(1)
compose(args.app, "up --detach")
elif args.action == 'restart':
if not args.app:
print("No app provided")
exit(1)
userData = getUserData()
if not "installedApps" in userData or args.app not in userData["installedApps"]:
print("App {} is not yet installed".format(args.app))
exit(1)
compose(args.app, "rm --force --stop")
compose(args.app, "up --detach")
elif args.action == 'compose':
if not args.app:
print("No app provided")
exit(1)
compose(args.app, " ".join(args.other))
elif args.action == "get-ip":
if args.app == "":
print("Missing app")
exit(1)
with open(os.path.join(appsDir, "virtual-apps.json"), "r") as f:
virtual_apps = json.load(f)
userData = getUserData()
implements_service = False
if args.app in virtual_apps:
for implementation in virtual_apps[args.app]:
if "installedApps" in userData and implementation in userData["installedApps"]:
print(get_var_safe("APP_{}_SERVICE_IP".format(convert_to_upper(implementation))))
exit(0)
else:
print("Not an virtual app")
exit(1)
elif args.action == "get-implementation":
if args.app == "":
print("Missing app")
exit(1)
with open(os.path.join(appsDir, "virtual-apps.json"), "r") as f:
virtual_apps = json.load(f)
userData = getUserData()
implements_service = False
if args.app in virtual_apps:
for implementation in virtual_apps[args.app]:
if "installedApps" in userData and implementation in userData["installedApps"]:
print(implementation)
exit(0)
else:
print("Not an virtual app")
exit(1)
print("Virtual app not found")
exit(1)