mirror of
https://github.com/runcitadel/core.git
synced 2024-11-11 16:30:38 +00:00
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
This commit is contained in:
parent
d09beb6390
commit
8bcb66f0fa
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -15,7 +15,6 @@
|
|||
.backup
|
||||
*.pyc
|
||||
bitcoin/*
|
||||
lnd/*
|
||||
tor/*
|
||||
db/*
|
||||
statuses/*
|
||||
|
@ -33,7 +32,6 @@ docker-compose.override.yml
|
|||
!db/citadel-seed
|
||||
db/citadel-seed/*
|
||||
!db/citadel-seed/.gitkeep
|
||||
!lnd/.gitkeep
|
||||
!logs/.gitkeep
|
||||
!tor/data/.gitkeep
|
||||
!tor/run/.gitkeep
|
||||
|
@ -42,8 +40,6 @@ db/citadel-seed/*
|
|||
!caddy/.gitkeep
|
||||
!i2p/.gitkeep
|
||||
|
||||
!**/*.license
|
||||
services/installed.json
|
||||
services/installed.yml
|
||||
!**/*.License
|
||||
|
||||
events/karen
|
||||
karen.socket
|
||||
|
|
1
app-data/.gitkeep
Normal file
1
app-data/.gitkeep
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2021-2022 Citadel and contributors
|
||||
#
|
||||
# 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 0.1.0
|
||||
|
||||
# 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 v4-stable
|
|
@ -7,11 +7,12 @@
|
|||
import argparse
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
from lib.manage import (compose, convert_to_upper, createDataDir, deleteData,
|
||||
download, downloadAll, get_var_safe,
|
||||
download, downloadAll, downloadNew, get_var_safe,
|
||||
getAvailableUpdates, getUserData, setInstalled,
|
||||
setRemoved, update)
|
||||
setRemoved, update, getAppRegistryEntry)
|
||||
|
||||
# Print an error if user is not root
|
||||
if os.getuid() != 0:
|
||||
|
@ -25,10 +26,11 @@ 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"])
|
||||
"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='?')
|
||||
|
@ -46,6 +48,9 @@ if args.action == "list-updates":
|
|||
elif args.action == 'download':
|
||||
downloadAll()
|
||||
exit(0)
|
||||
elif args.action == 'download-new':
|
||||
downloadNew()
|
||||
exit(0)
|
||||
elif args.action == 'generate':
|
||||
update()
|
||||
exit(0)
|
||||
|
@ -64,7 +69,13 @@ elif args.action == 'ls-installed':
|
|||
with open(userFile, "r") as f:
|
||||
userData = json.load(f)
|
||||
if "installedApps" in userData:
|
||||
print("\n".join(userData["installedApps"]))
|
||||
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")
|
||||
|
@ -74,6 +85,29 @@ 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()
|
||||
|
@ -173,7 +207,21 @@ elif args.action == "get-ip":
|
|||
print("Not an virtual app")
|
||||
exit(1)
|
||||
|
||||
else:
|
||||
print("Error: Unknown action")
|
||||
print("See --help for usage")
|
||||
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)
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
version: "3.8"
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: citadel_main_network
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
jwt-public-key:
|
||||
external: true
|
||||
name: citadel-jwt-public-key
|
|
@ -17,7 +17,7 @@ def parse_dotenv(file_path):
|
|||
value = value.strip('"').strip("'")
|
||||
envVars[key] = value
|
||||
else:
|
||||
print("Error: Invalid line in {}: {}".format(file_path, line))
|
||||
print("Warning: Invalid line in {}: {}".format(file_path, line))
|
||||
print("Line should be in the format KEY=VALUE or KEY=\"VALUE\" or KEY='VALUE'")
|
||||
exit(1)
|
||||
continue
|
||||
return envVars
|
||||
|
|
|
@ -15,8 +15,7 @@ def deriveEntropy(identifier: str):
|
|||
if os.path.isfile(alternativeSeedFile):
|
||||
seedFile = alternativeSeedFile
|
||||
else:
|
||||
print("No seed file found, exiting...")
|
||||
exit(1)
|
||||
raise Exception("No seed file found")
|
||||
with open(seedFile, "r") as f:
|
||||
node_seed = f.read().strip()
|
||||
entropy = subprocess.check_output(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# SPDX-FileCopyrightText: 2021-2022 Citadel and contributors
|
||||
# SPDX-FileCopyrightText: 2021-2023 Citadel and contributors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
@ -19,8 +19,7 @@ from lib.entropy import deriveEntropy
|
|||
scriptDir = os.path.dirname(os.path.realpath(__file__))
|
||||
nodeRoot = os.path.join(scriptDir, "..", "..")
|
||||
appsDir = os.path.join(nodeRoot, "apps")
|
||||
appSystemDir = os.path.join(nodeRoot, "app-system")
|
||||
updateIgnore = os.path.join(appsDir, ".updateignore")
|
||||
appSystemDir = os.path.join(nodeRoot, "app")
|
||||
appDataDir = os.path.join(nodeRoot, "app-data")
|
||||
userFile = os.path.join(nodeRoot, "db", "user.json")
|
||||
with open(os.path.join(nodeRoot, "db", "dependencies.yml"), "r") as file:
|
||||
|
@ -28,13 +27,6 @@ with open(os.path.join(nodeRoot, "db", "dependencies.yml"), "r") as file:
|
|||
|
||||
dotenv = {}
|
||||
|
||||
# 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
|
||||
|
||||
def get_var_safe(var_name):
|
||||
dotenv = parse_dotenv(os.path.join(nodeRoot, ".env"))
|
||||
if var_name in dotenv:
|
||||
|
@ -51,17 +43,6 @@ def get_var(var_name):
|
|||
print("Error: {} is not defined!".format(var_name))
|
||||
exit(1)
|
||||
|
||||
def getInstalledVirtualApps():
|
||||
installedApps = []
|
||||
with open(os.path.join(appsDir, "virtual-apps.json"), "r") as f:
|
||||
virtual_apps = json.load(f)
|
||||
userData = getUserData()
|
||||
for virtual_app in virtual_apps.keys():
|
||||
for implementation in virtual_apps[virtual_app]:
|
||||
if "installedApps" in userData and implementation in userData["installedApps"]:
|
||||
installedApps.append(virtual_app)
|
||||
return installedApps
|
||||
|
||||
# Converts a string to uppercase, also replaces all - with _
|
||||
def convert_to_upper(string):
|
||||
return string.upper().replace('-', '_')
|
||||
|
@ -71,11 +52,14 @@ def convert_to_upper(string):
|
|||
def replace_vars(file_content: str):
|
||||
return re.sub(r'<(.*?)>', lambda m: get_var(convert_to_upper(m.group(1))), file_content)
|
||||
|
||||
|
||||
def update():
|
||||
os.system("docker run --rm -v {}:/citadel -u 1000:1000 {} /app-cli convert /citadel".format(nodeRoot, dependencies['app-cli']))
|
||||
print("Generated configuration successfully")
|
||||
|
||||
def downloadNew():
|
||||
os.system("docker run --rm -v {}:/citadel -u 1000:1000 {} /app-cli download-new /citadel".format(nodeRoot, dependencies['app-cli']))
|
||||
print("Generated configuration successfully")
|
||||
|
||||
def downloadAll():
|
||||
os.system("docker run --rm -v {}:/citadel -u 1000:1000 {} /app-cli download-apps /citadel".format(nodeRoot, dependencies['app-cli']))
|
||||
print("Generated configuration successfully")
|
||||
|
@ -120,11 +104,14 @@ def compose(app, arguments):
|
|||
"hostname -s 2>/dev/null || echo 'citadel'", shell=True).decode("utf-8").strip() + ".local"
|
||||
os.environ["APP_HIDDEN_SERVICE"] = subprocess.check_output("cat {} 2>/dev/null || echo 'notyetset.onion'".format(
|
||||
os.path.join(nodeRoot, "tor", "data", "app-{}/hostname".format(app))), shell=True).decode("utf-8").strip()
|
||||
os.environ["APP_SEED"] = deriveEntropy("app-{}-seed".format(app))
|
||||
# Allow more app seeds, with random numbers from 1-5 assigned in a loop
|
||||
for i in range(1, 6):
|
||||
os.environ["APP_SEED_{}".format(i)] = deriveEntropy("app-{}-seed{}".format(app, i))
|
||||
try:
|
||||
os.environ["APP_SEED"] = deriveEntropy("app-{}-seed".format(app))
|
||||
# Allow more app seeds, with random numbers from 1-5 assigned in a loop
|
||||
for i in range(1, 6):
|
||||
os.environ["APP_SEED_{}".format(i)] = deriveEntropy("app-{}-seed{}".format(app, i))
|
||||
except: pass
|
||||
os.environ["APP_DATA_DIR"] = os.path.join(appDataDir, app)
|
||||
os.environ["CITADEL_APP_DATA"] = appDataDir
|
||||
# Chown and chmod dataDir to have the owner 1000:1000 and the same permissions as appDir
|
||||
subprocess.call("chown -R 1000:1000 {}".format(os.path.join(appDataDir, app)), shell=True)
|
||||
try:
|
||||
|
@ -135,10 +122,9 @@ def compose(app, arguments):
|
|||
subprocess.call("chown -R 33:33 {}".format(os.path.join(appDataDir, app, "data", "nextcloud")), shell=True)
|
||||
subprocess.call("chmod -R 770 {}".format(os.path.join(appDataDir, app, "data", "nextcloud")), shell=True)
|
||||
os.environ["BITCOIN_DATA_DIR"] = os.path.join(nodeRoot, "bitcoin")
|
||||
os.environ["LND_DATA_DIR"] = os.path.join(nodeRoot, "lnd")
|
||||
os.environ["CITADEL_ROOT"] = nodeRoot
|
||||
# List all hidden services for an app and put their hostname in the environment
|
||||
hiddenServices: List[str] = getAppHiddenServices(app)
|
||||
hiddenServices: List[str] = getAppRegistryEntry(app).get("hiddenServices", [])
|
||||
for service in hiddenServices:
|
||||
appHiddenServiceFile = os.path.join(
|
||||
nodeRoot, "tor", "data", "app-{}-{}/hostname".format(app, service))
|
||||
|
@ -196,13 +182,13 @@ def setRemoved(app: str):
|
|||
with open(userFile, "w") as f:
|
||||
json.dump(userData, f)
|
||||
|
||||
|
||||
def getAppHiddenServices(app: str):
|
||||
torDir = os.path.join(nodeRoot, "tor", "data")
|
||||
# List all subdirectories of torDir which start with app-${APP}-
|
||||
# but return them without the app-${APP}- prefix
|
||||
results = []
|
||||
for subdir in os.listdir(torDir):
|
||||
if subdir.startswith("app-{}-".format(app)):
|
||||
results.append(subdir[len("app-{}-".format(app)):])
|
||||
return results
|
||||
# Gets the app's registry entry from the registry.json file
|
||||
# The file is an array of objects, each object is an app's registry entry
|
||||
# We can filter by the "id" property to get the app's registry entry
|
||||
def getAppRegistryEntry(app: str):
|
||||
with open(os.path.join(appsDir, "registry.json")) as f:
|
||||
registry = json.load(f)
|
||||
for appRegistryEntry in registry:
|
||||
if appRegistryEntry["id"] == app:
|
||||
return appRegistryEntry
|
||||
return None
|
||||
|
|
19
bin/lncli
19
bin/lncli
|
@ -1,19 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com
|
||||
# SPDX-FileCopyrightText: 2021-2022 Citadel and contributors
|
||||
# SPDX-FileCopyrightText: 2021 https://github.com/o3o3o
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
|
||||
|
||||
result=$(docker compose \
|
||||
--file "${CITADEL_ROOT}/docker-compose.yml" \
|
||||
--env-file "${CITADEL_ROOT}/.env" \
|
||||
exec lightning lncli "$@")
|
||||
|
||||
# We need to echo with quotes to preserve output formatting
|
||||
echo "$result"
|
|
@ -365,13 +365,8 @@ if [[ "$command" = "configure" ]]; then
|
|||
fi
|
||||
|
||||
if [[ "$2" = "lnd" ]]; then
|
||||
if $persist; then
|
||||
edit_file $CITADEL_ROOT/templates/lnd-sample.conf
|
||||
prompt_apply_config lightning true
|
||||
else
|
||||
edit_file $CITADEL_ROOT/lnd/lnd.conf
|
||||
prompt_apply_config lightning false
|
||||
fi
|
||||
edit_file $CITADEL_ROOT/app-data/lnd/lnd.conf
|
||||
prompt_apply_config lnd-service-1 false
|
||||
exit
|
||||
fi
|
||||
|
||||
|
|
|
@ -5,6 +5,5 @@
|
|||
compose: v2.17.2
|
||||
dashboard: ghcr.io/runcitadel/dashboard:citadel-0.0.10@sha256:54214fbb0e9d7b08771e0c5af912ef1dc4ae1bc1650d8640b74ed01554696beb
|
||||
manager: harbor.nirvati.org/citadel/api:main@sha256:12d299d8850d830fa5abd0e64c8537dfcbaec662de18376b0d7b01fa59895132
|
||||
middleware: ghcr.io/runcitadel/middleware:main@sha256:cbd5fd2ab5afe420025c61e276d21c79a004d6148b8dfdd58649adb55907682b
|
||||
app-cli: harbor.nirvati.org/citadel/app-manager:backports@sha256:e5d54e0559eef96472aca56b9343bfc06f45777283967015dfdbf618d25cadd0
|
||||
app-cli: harbor.nirvati.org/citadel/app-manager:main@sha256:b841a27f14aa6785a357c4af3632f5fd98dbf2e3b1530e5a6dcd1175fda94f8a
|
||||
tor: ghcr.io/runcitadel/tor-latest:main@sha256:761948a86f8367238eb61f991cf87094b12a8a772be0eabec00d66164d13075f
|
||||
|
|
|
@ -76,27 +76,6 @@ services:
|
|||
networks:
|
||||
default:
|
||||
ipv4_address: $BITCOIN_IP
|
||||
lightning:
|
||||
container_name: lightning
|
||||
image: lightninglabs/lnd:v0.15.4-beta@sha256:f5b19812ab7d28faa350838dac4bb88e7fcf9ae905e44d3539be41a97b80ca23
|
||||
user: 1000:1000
|
||||
depends_on:
|
||||
- tor
|
||||
- bitcoin
|
||||
volumes:
|
||||
- ${PWD}/lnd:/data/.lnd
|
||||
- ${PWD}/walletpassword:/walletpassword
|
||||
environment:
|
||||
HOME: /data
|
||||
restart: on-failure
|
||||
stop_grace_period: 5m30s
|
||||
ports:
|
||||
- 9735:9735
|
||||
- $LND_REST_PORT:$LND_REST_PORT
|
||||
- $LND_GRPC_PORT:$LND_GRPC_PORT
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: $LND_IP
|
||||
dashboard:
|
||||
container_name: dashboard
|
||||
image: ghcr.io/runcitadel/dashboard:no-https@sha256:7fc5a5b70496240e6e48a381e8ac3c7978e7343285fda4951c00846580d6216d
|
||||
|
@ -118,7 +97,6 @@ services:
|
|||
- ${PWD}/db:/db
|
||||
- ${PWD}/events:/events
|
||||
- ${PWD}/apps:/apps
|
||||
- ${PWD}/lnd:/lnd:ro
|
||||
- ${PWD}/statuses:/statuses
|
||||
- ${PWD}/tor/data:/var/lib/tor/
|
||||
- jwt-public-key:/jwt-public-key
|
||||
|
@ -138,8 +116,6 @@ services:
|
|||
BITCOIN_RPC_PORT: $BITCOIN_RPC_PORT
|
||||
BITCOIN_RPC_USER: $BITCOIN_RPC_USER
|
||||
BITCOIN_RPC_PASSWORD: $BITCOIN_RPC_PASS
|
||||
LND_CERT_FILE: /lnd/tls.cert
|
||||
LND_ADMIN_MACAROON_FILE: /lnd/data/chain/bitcoin/${BITCOIN_NETWORK}/admin.macaroon
|
||||
GITHUB_REPO: runcitadel/core
|
||||
GITHUB_BRANCH: ${UPDATE_CHANNEL:-"stable"}
|
||||
VERSION_FILE: /info.json
|
||||
|
@ -159,51 +135,6 @@ services:
|
|||
networks:
|
||||
default:
|
||||
ipv4_address: $MANAGER_IP
|
||||
middleware:
|
||||
container_name: middleware
|
||||
image: ghcr.io/runcitadel/middleware:main@sha256:cbd5fd2ab5afe420025c61e276d21c79a004d6148b8dfdd58649adb55907682b
|
||||
depends_on:
|
||||
- bitcoin
|
||||
- lightning
|
||||
command: sh -c "./wait-for-manager.sh $MANAGER_IP && ././start.sh"
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ${PWD}/lnd:/lnd
|
||||
- jwt-public-key:/jwt-public-key
|
||||
environment:
|
||||
PORT: '3000'
|
||||
BITCOIN_HOST: $BITCOIN_IP
|
||||
RPC_PORT: $BITCOIN_RPC_PORT
|
||||
RPC_USER: $BITCOIN_RPC_USER
|
||||
RPC_PASSWORD: $BITCOIN_RPC_PASS
|
||||
LND_NETWORK: $BITCOIN_NETWORK
|
||||
LND_HOST: ${LND_IP}
|
||||
JWT_PUBLIC_KEY_FILE: /jwt-public-key/jwt.pem
|
||||
DEVICE_HOSTS: ${DEVICE_HOSTS:-"http://citadel.local"}
|
||||
UNSAFE_REMOVE_CORS_CHECK: true
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: $MIDDLEWARE_IP
|
||||
neutrino-switcher:
|
||||
container_name: neutrino-switcher
|
||||
image: lncm/neutrino-switcher:1.0.5@sha256:3ddf58c5599ba22db8414f2694bfeeba086455d4a19b4955b26c3ae5e967d42a
|
||||
depends_on:
|
||||
- bitcoin
|
||||
- lightning
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ${PWD}/lnd:/lnd
|
||||
- ${PWD}/statuses:/statuses
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
JSONRPCURL: http://${BITCOIN_IP}:${BITCOIN_RPC_PORT}
|
||||
RPCUSER: $BITCOIN_RPC_USER
|
||||
RPCPASS: $BITCOIN_RPC_PASS
|
||||
LND_CONTAINER_NAME: lightning
|
||||
SLEEPTIME: 3600
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: $NEUTRINO_SWITCHER_IP
|
||||
i2p:
|
||||
container_name: i2p
|
||||
user: 1000:1000
|
||||
|
|
0
events/.gitkeep
Normal file
0
events/.gitkeep
Normal file
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"version": "0.1.7",
|
||||
"name": "Citadel 0.1.7",
|
||||
"version": "0.1.8",
|
||||
"name": "Citadel 0.1.8",
|
||||
"requires": ">=0.1.5",
|
||||
"isQuickUpdate": false,
|
||||
"notes": "This update should hopefully fix a few more bugs in Citadel. It also contains a few more changes to prepare the Nirvati release and enables HTTPS for everyone."
|
||||
"notes": "This update moves LND into an app that can not be uninstalled to make updates in the future easier and to prepare for Nirvati. It also fixes a bug that prevented Tailscale from working properly."
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com
|
||||
# SPDX-FileCopyrightText: 2022 Citadel and contributors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
karen
4
karen
|
@ -4,8 +4,8 @@
|
|||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import socket
|
||||
import os
|
||||
import socket
|
||||
|
||||
rootDir = os.path.dirname(os.path.abspath(__file__))
|
||||
os.chdir(rootDir)
|
||||
|
@ -27,7 +27,7 @@ while True:
|
|||
trigger = instructions[1]
|
||||
instructions.pop(0)
|
||||
instructions.pop(0)
|
||||
os.system("events/triggers/{} {}".format(trigger, " ".join(instructions)))
|
||||
os.system("scripts/triggers/{} {}".format(trigger, " ".join(instructions)))
|
||||
elif cmd == "exec":
|
||||
instructions.pop(0)
|
||||
os.system(" ".join(instructions))
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
# Automatic Encrypted Backups
|
||||
|
||||
The backups are encrypted client side before being uploaded over Tor and are padded with random data. Backups are made immediately as soon as the channel state changes. However, Citadel also makes decoy backups at random intervals to prevent timing-analysis attacks.
|
||||
|
||||
These features combined ensure that the backup server doesn't learn any sensitive information about the user's Citadel.
|
||||
|
||||
- The IP address of user is hidden due to Tor.
|
||||
- User's channel data are encrypted client side with a key only known to the Citadel device.
|
||||
- Random interval decoy backups ensure the server can't correlate backup activity with channel state changes on the Lightning network and correlate a backup ID with a channel pubkey.
|
||||
- Random padding obscures if the backup size has increased/decreased or remains unchanged due to it being a decoy.
|
||||
|
||||
Due to the key/id being deterministically derived from the Citadel seed, all that's needed to fully recover an Citadel is the mnemonic seed phrase. Upon recovery the device can automatically regenerate the same backup id/encryption key, request the latest backup from the backup server, decrypt it, and restore the user's settings and Lightning network channel data.
|
||||
|
||||
There is currently no way to disable backups or recover from them in the dashboard yet. Both of these features will be introduced in the coming updates.
|
|
@ -1,160 +0,0 @@
|
|||
#!/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
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
|
||||
BACKUP_ROOT="${CITADEL_ROOT}/.backup/$RANDOM"
|
||||
BACKUP_FOLDER_NAME="backup"
|
||||
BACKUP_FOLDER_PATH="${BACKUP_ROOT}/${BACKUP_FOLDER_NAME}"
|
||||
BACKUP_FILE="${BACKUP_ROOT}/backup.tar.gz.pgp"
|
||||
BACKUP_STATUS_FILE="${CITADEL_ROOT}/statuses/backup-status.json"
|
||||
|
||||
check_dependencies () {
|
||||
for cmd in "$@"; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
echo "This script requires \"${cmd}\" to be installed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_dependencies openssl tar gpg shuf curl
|
||||
|
||||
# Deterministically derives 128 bits of cryptographically secure entropy
|
||||
derive_entropy () {
|
||||
identifier="${1}"
|
||||
citadel_seed=$(cat "${CITADEL_ROOT}/db/citadel-seed/seed") || true
|
||||
|
||||
if [[ -z "$citadel_seed" ]] || [[ -z "$identifier" ]]; then
|
||||
>&2 echo "Missing derivation parameter, this is unsafe, exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# We need `sed 's/^.* //'` to trim the "(stdin)= " prefix from some versions of openssl
|
||||
printf "%s" "${identifier}" | openssl dgst -sha256 -hmac "${citadel_seed}" | sed 's/^.* //'
|
||||
}
|
||||
|
||||
[[ -f "${CITADEL_ROOT}/.env" ]] && source "${CITADEL_ROOT}/.env"
|
||||
BITCOIN_NETWORK=${BITCOIN_NETWORK:-mainnet}
|
||||
|
||||
echo "Deriving keys..."
|
||||
|
||||
backup_id=$(derive_entropy "citadel_backup_id")
|
||||
encryption_key=$(derive_entropy "citadel_backup_encryption_key")
|
||||
|
||||
echo "Creating backup..."
|
||||
|
||||
if [[ ! -f "${CITADEL_ROOT}/lnd/data/chain/bitcoin/${BITCOIN_NETWORK}/channel.backup" ]]; then
|
||||
echo "No channel.backup file found, skipping backup..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${BACKUP_FOLDER_PATH}"
|
||||
|
||||
cp --archive "${CITADEL_ROOT}/lnd/data/chain/bitcoin/${BITCOIN_NETWORK}/channel.backup" "${BACKUP_FOLDER_PATH}/channel.backup"
|
||||
|
||||
# We want to back up user settings too, however we currently store the encrypted
|
||||
# mnemonic in this file which is not safe to backup remotely.
|
||||
# Uncomment this in the future once we've ensured there's no critical data in
|
||||
# this file.
|
||||
# cp --archive "${CITADEL_ROOT}/db/user.json" "${BACKUP_FOLDER_PATH}/user.json"
|
||||
|
||||
echo "Adding random padding..."
|
||||
|
||||
# Up to 10KB of random binary data
|
||||
# This prevents the server from being able to tell if the backup has increased
|
||||
# decreased or stayed the sme size. Combined with random interval decoy backups
|
||||
# this makes a (already very difficult) timing analysis attack to correlate backup
|
||||
# activity with channel state changes practically impossible.
|
||||
padding="$(shuf -i 0-10240 -n 1)"
|
||||
dd if=/dev/urandom bs="${padding}" count=1 > "${BACKUP_FOLDER_PATH}/.padding"
|
||||
|
||||
echo "Creating encrypted tarball..."
|
||||
|
||||
tar \
|
||||
--create \
|
||||
--gzip \
|
||||
--verbose \
|
||||
--directory "${BACKUP_FOLDER_PATH}/.." \
|
||||
"${BACKUP_FOLDER_NAME}" \
|
||||
| gpg \
|
||||
--batch \
|
||||
--symmetric \
|
||||
--cipher-algo AES256 \
|
||||
--passphrase "${encryption_key}" \
|
||||
--output "${BACKUP_FILE}"
|
||||
|
||||
# To decrypt:
|
||||
# cat "${BACKUP_FILE}" | gpg \
|
||||
# --batch \
|
||||
# --decrypt \
|
||||
# --passphrase "${encryption_key}" \
|
||||
# | tar \
|
||||
# --extract \
|
||||
# --verbose \
|
||||
# --gzip
|
||||
|
||||
upload_file() {
|
||||
local file_to_send="${1}"
|
||||
local backup_id="${2}"
|
||||
local upload_data=$(jq --null-input \
|
||||
--arg name "$backup_id" \
|
||||
--arg data "$(base64 $file_to_send)" \
|
||||
'{"name": $name, "data": $data}')
|
||||
curl -X POST \
|
||||
"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
|
||||
rm -rf "${BACKUP_ROOT}"
|
||||
cat <<EOF > ${BACKUP_STATUS_FILE}
|
||||
{"status": "skipped", "timestamp": $(date +%s000)}
|
||||
EOF
|
||||
exit
|
||||
fi
|
||||
if [[ $BITCOIN_NETWORK == "signet" ]]; then
|
||||
rm -rf "${BACKUP_ROOT}"
|
||||
cat <<EOF > ${BACKUP_STATUS_FILE}
|
||||
{"status": "skipped", "timestamp": $(date +%s000)}
|
||||
EOF
|
||||
exit
|
||||
fi
|
||||
if [[ $BITCOIN_NETWORK == "regtest" ]]; then
|
||||
rm -rf "${BACKUP_ROOT}"
|
||||
cat <<EOF > ${BACKUP_STATUS_FILE}
|
||||
{"status": "skipped", "timestamp": $(date +%s000)}
|
||||
EOF
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "Uploading backup..."
|
||||
if upload_file "${BACKUP_FILE}" "${backup_id}"; then
|
||||
status="success"
|
||||
else
|
||||
status="failed"
|
||||
fi
|
||||
echo
|
||||
|
||||
~/citadel-backup-upload-hook "${BACKUP_FILE}" || true
|
||||
|
||||
rm -rf "${BACKUP_ROOT}"
|
||||
|
||||
# Update status file
|
||||
cat <<EOF > ${BACKUP_STATUS_FILE}
|
||||
{"status": "${status}", "timestamp": $(date +%s000)}
|
||||
EOF
|
||||
|
||||
echo "============================="
|
||||
echo "====== Backup ${status} ======="
|
||||
echo "============================="
|
||||
|
||||
exit 0
|
|
@ -1,46 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
|
||||
MAX_BACKUP_INTERVAL_IN_HOURS="12"
|
||||
|
||||
check_if_not_already_running() {
|
||||
if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep
|
||||
then
|
||||
echo "decoy trigger is already running"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_dependencies () {
|
||||
for cmd in "$@"; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
echo "This script requires \"${cmd}\" to be installed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_if_not_already_running
|
||||
|
||||
check_dependencies shuf
|
||||
|
||||
main () {
|
||||
while true; do
|
||||
minutes_in_seconds="60"
|
||||
hours_in_seconds="$((60 * ${minutes_in_seconds}))"
|
||||
max_interval="$((${MAX_BACKUP_INTERVAL_IN_HOURS} * ${hours_in_seconds}))"
|
||||
delay="$(shuf -i 0-${max_interval} -n 1)"
|
||||
echo "Sleeping for ${delay} seconds..."
|
||||
sleep $delay
|
||||
echo "Triggering decoy backup..."
|
||||
"${CITADEL_ROOT}/scripts/backup/backup"
|
||||
done
|
||||
}
|
||||
|
||||
main
|
|
@ -1,73 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
check_root () {
|
||||
if [[ $UID != 0 ]]; then
|
||||
echo "Error: This script must be run as root."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_if_not_already_running() {
|
||||
if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep
|
||||
then
|
||||
echo "backup monitor is already running"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_dependencies () {
|
||||
for cmd in "$@"; do
|
||||
if ! command -v $cmd >/dev/null 2>&1; then
|
||||
echo "This script requires \"${cmd}\" to be installed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_root
|
||||
|
||||
check_if_not_already_running
|
||||
|
||||
check_dependencies fswatch readlink dirname
|
||||
|
||||
CITADEL_ROOT="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))/../.."
|
||||
|
||||
monitor_file () {
|
||||
local file_path="${1}"
|
||||
echo "Monitoring $file_path"
|
||||
echo
|
||||
|
||||
if [[ ! -e "${file_path}" ]]; then
|
||||
echo "$file_path doesn't exist, waiting for it to be created..."
|
||||
echo
|
||||
until [[ -e "${file_path}" ]]; do
|
||||
sleep 1
|
||||
done
|
||||
echo "$file_path created! Triggering backup..."
|
||||
"${CITADEL_ROOT}/scripts/backup/backup"
|
||||
fi
|
||||
|
||||
fswatch -0 --event Updated $file_path | xargs -0 -n 1 -I {} "${CITADEL_ROOT}/scripts/backup/backup"
|
||||
}
|
||||
|
||||
if [[ ! -d "${CITADEL_ROOT}" ]]; then
|
||||
echo "Root dir does not exist '$CITADEL_ROOT'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[[ -f "${CITADEL_ROOT}/.env" ]] && source "${CITADEL_ROOT}/.env"
|
||||
BITCOIN_NETWORK=${BITCOIN_NETWORK:-mainnet}
|
||||
|
||||
# Monitor LND channel.backup
|
||||
monitor_file "${CITADEL_ROOT}/lnd/data/chain/bitcoin/${BITCOIN_NETWORK}/channel.backup" &
|
||||
|
||||
# Monitor db/user.json
|
||||
# We want to back up user settings too, however we currently store the encrypted
|
||||
# mnemonic in this file which is not safe to backup remotely.
|
||||
# Uncomment this in the future once we've ensured there's no critical data in
|
||||
# this file.
|
||||
# monitor_file "${CITADEL_ROOT}/db/user.json" &
|
|
@ -1,77 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-FileCopyrightText: 2022 Citadel and contributors. https://runcitadel.space
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
|
||||
BACKUP_ROOT="${CITADEL_ROOT}/.backup/restore-$RANDOM"
|
||||
BACKUP_FOLDER_NAME="backup"
|
||||
BACKUP_FOLDER_PATH="${BACKUP_ROOT}/${BACKUP_FOLDER_NAME}"
|
||||
|
||||
check_dependencies () {
|
||||
for cmd in "$@"; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
echo "This script requires \"${cmd}\" to be installed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_dependencies openssl tar gpg curl
|
||||
|
||||
[[ -f "${CITADEL_ROOT}/.env" ]] && source "${CITADEL_ROOT}/.env"
|
||||
BITCOIN_NETWORK=${BITCOIN_NETWORK:-mainnet}
|
||||
|
||||
if [[ $BITCOIN_NETWORK == "testnet" ]] || [[ $BITCOIN_NETWORK == "signet" ]] || [[ $BITCOIN_NETWORK == "regtest" ]]; then
|
||||
echo "Backups can only be restored on mainnet"
|
||||
exit
|
||||
fi
|
||||
|
||||
# Deterministically derives 128 bits of cryptographically secure entropy
|
||||
derive_entropy () {
|
||||
identifier="${1}"
|
||||
citadel_seed=$(cat "${CITADEL_ROOT}/db/citadel-seed/seed") || true
|
||||
|
||||
if [[ -z "$citadel_seed" ]] || [[ -z "$identifier" ]]; then
|
||||
>&2 echo "Missing derivation parameter, this is unsafe, exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# We need `sed 's/^.* //'` to trim the "(stdin)= " prefix from some versions of openssl
|
||||
printf "%s" "${identifier}" | openssl dgst -sha256 -hmac "${citadel_seed}" | sed 's/^.* //'
|
||||
}
|
||||
|
||||
echo "Deriving keys..."
|
||||
|
||||
backup_id=$(derive_entropy "citadel_backup_id")
|
||||
encryption_key=$(derive_entropy "citadel_backup_encryption_key")
|
||||
|
||||
echo "Your node id is: "
|
||||
echo $(derive_entropy "citadel_backup_id")
|
||||
|
||||
# If the first argument is not given, fail
|
||||
if [[ -z "$1" ]]; then
|
||||
echo "Usage: $0 <backup-id>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Downloading backup..."
|
||||
|
||||
mkdir -p "${BACKUP_ROOT}"
|
||||
cd "${BACKUP_ROOT}"
|
||||
|
||||
curl -X POST https://account.runcitadel.space/api/get-backup-by-id --data "{ \"id\": \"$1\", \"name\": \"$backup_id\" }" -H "Content-Type: application/json" | jq ".data" -r | base64 -d - > channels.encrypted.backup
|
||||
|
||||
cat "channels.encrypted.backup" | gpg --batch --decrypt --passphrase "$encryption_key" | tar --extract --verbose --gzip
|
||||
|
||||
echo "Restoring backup..."
|
||||
|
||||
cd "${BACKUP_FOLDER_PATH}"
|
||||
cp channel.backup "${CITADEL_ROOT}/channel.backup"
|
||||
|
||||
sudo docker exec -it lightning lncli restorechanbackup --multi_file /data/.lnd/channel.backup
|
||||
|
||||
echo "Backup restored successfully!"
|
|
@ -145,6 +145,7 @@ setup_new_device () {
|
|||
|
||||
# Copy Docker data dir to external storage
|
||||
copy_docker_to_external_storage () {
|
||||
mkdir -p "${DOCKER_DIR}"
|
||||
mkdir -p "${EXTERNAL_DOCKER_DIR}"
|
||||
cp --recursive \
|
||||
--archive \
|
||||
|
@ -285,7 +286,8 @@ main () {
|
|||
swapon "${SWAP_FILE}"
|
||||
|
||||
echo "Checking SD Card root is bind mounted at /sd-root..."
|
||||
df -h "/sd-root${CITADEL_ROOT}" | grep --quiet "/dev/root"
|
||||
# Skip this for now
|
||||
#df -h "/sd-root${CITADEL_ROOT}" | grep --quiet "/dev/root"
|
||||
|
||||
if [[ $block_device != nvme* ]]; then
|
||||
echo "unknown" > "${CITADEL_ROOT}/statuses/external_storage"
|
||||
|
|
84
scripts/configure
vendored
84
scripts/configure
vendored
|
@ -174,8 +174,6 @@ if reconfiguring:
|
|||
I2P_PASSWORD=dotenv['I2P_PASSWORD']
|
||||
else:
|
||||
I2P_PASSWORD=generate_password(64)
|
||||
TOR_PASSWORD=dotenv['TOR_PASSWORD']
|
||||
TOR_HASHED_PASSWORD=dotenv['TOR_HASHED_PASSWORD']
|
||||
else:
|
||||
# Generate RPC credentials
|
||||
print("Generating auth credentials\n")
|
||||
|
@ -183,64 +181,18 @@ else:
|
|||
BITCOIN_RPC_DETAILS=get_data(BITCOIN_RPC_USER)
|
||||
BITCOIN_RPC_AUTH=BITCOIN_RPC_DETAILS['auth']
|
||||
BITCOIN_RPC_PASS=BITCOIN_RPC_DETAILS['password']
|
||||
# Pull Tor image and generate Tor password
|
||||
print("Generating Tor password\n")
|
||||
os.system('docker pull --quiet {}'.format(dependencies["tor"]))
|
||||
TOR_PASSWORD=get_data('itdoesntmatter')['password']
|
||||
TOR_HASHED_PASSWORD=os.popen('docker run --rm {} --quiet --hash-password "{}"'.format(dependencies["tor"], TOR_PASSWORD)).read()[:-1]
|
||||
I2P_PASSWORD=generate_password(64)
|
||||
|
||||
BITCOIN_NODE="neutrino"
|
||||
ALIAS_AND_COLOR=""
|
||||
ADDITIONAL_BITCOIN_OPTIONS=""
|
||||
BOLT_DB_OPTIONS=""
|
||||
CHANNEL_LIMITATIONS=""
|
||||
BASEFEE = "bitcoin.basefee=0"
|
||||
EXTERNAL_IP = ""
|
||||
if os.path.isfile('./tor/data/bitcoin-p2p/hostname'):
|
||||
EXTERNAL_IP="externalip=" + open('./tor/data/bitcoin-p2p/hostname').read()
|
||||
|
||||
if os.path.isfile("./lnd/lnd.conf"):
|
||||
with open("./lnd/lnd.conf", 'r') as file:
|
||||
# We generally don't want to allow changing lnd.conf, but we keep as many custom settings as possible
|
||||
for line in file:
|
||||
if line.startswith("bitcoin.node="):
|
||||
BITCOIN_NODE = line.split("=")[1].strip()
|
||||
if line.startswith("alias="):
|
||||
ALIAS_AND_COLOR += "\n" + line.strip()
|
||||
if line.startswith("color="):
|
||||
ALIAS_AND_COLOR += "\n" + line.strip()
|
||||
if line.startswith("bitcoin.basefee"):
|
||||
BASEFEE = line.strip()
|
||||
if line.startswith("bitcoin.feerate"):
|
||||
ADDITIONAL_BITCOIN_OPTIONS += "\n" + line.strip()
|
||||
if line.startswith("minchansize"):
|
||||
CHANNEL_LIMITATIONS += "\n" + line.strip()
|
||||
if line.startswith("maxchansize"):
|
||||
CHANNEL_LIMITATIONS += "\n" + line.strip()
|
||||
if line.startswith("maxpendingchannels"):
|
||||
CHANNEL_LIMITATIONS += "\n" + line.strip()
|
||||
if line.startswith("db.bolt.auto-compact"):
|
||||
BOLT_DB_OPTIONS += "\n" + line.strip()
|
||||
|
||||
if BOLT_DB_OPTIONS != "":
|
||||
BOLT_DB_OPTIONS = "[bolt]\n" + BOLT_DB_OPTIONS
|
||||
|
||||
if CHANNEL_LIMITATIONS == "":
|
||||
CHANNEL_LIMITATIONS = "maxpendingchannels=3\nminchansize=10000"
|
||||
|
||||
NEUTRINO_PEERS=""
|
||||
if BITCOIN_NETWORK == "mainnet":
|
||||
BITCOIN_RPC_PORT=8332
|
||||
BITCOIN_P2P_PORT=8333
|
||||
elif BITCOIN_NETWORK == "testnet":
|
||||
BITCOIN_RPC_PORT=18332
|
||||
BITCOIN_P2P_PORT=18333
|
||||
NEUTRINO_PEERS='''
|
||||
[neutrino]
|
||||
neutrino.addpeer=testnet1-btcd.zaphq.io
|
||||
neutrino.addpeer=testnet2-btcd.zaphq.io
|
||||
'''
|
||||
elif BITCOIN_NETWORK == "signet":
|
||||
BITCOIN_RPC_PORT=38332
|
||||
BITCOIN_P2P_PORT=38333
|
||||
|
@ -263,10 +215,10 @@ NETWORK_IP="10.21.21.0"
|
|||
GATEWAY_IP="10.21.21.1"
|
||||
DASHBOARD_IP="10.21.21.3"
|
||||
MANAGER_IP="10.21.21.4"
|
||||
MIDDLEWARE_IP="10.21.21.5"
|
||||
NEUTRINO_SWITCHER_IP="10.21.21.6"
|
||||
#MIDDLEWARE_IP="10.21.21.5"
|
||||
#NEUTRINO_SWITCHER_IP="10.21.21.6"
|
||||
BITCOIN_IP="10.21.21.7"
|
||||
LND_IP="10.21.21.8"
|
||||
#LND_IP="10.21.21.8"
|
||||
TOR_PROXY_IP="10.21.21.9"
|
||||
APPS_TOR_IP="10.21.21.10"
|
||||
APPS_2_TOR_IP="10.21.21.11"
|
||||
|
@ -280,8 +232,6 @@ BITCOIN_ZMQ_RAWBLOCK_PORT="28332"
|
|||
BITCOIN_ZMQ_RAWTX_PORT="28333"
|
||||
BITCOIN_ZMQ_HASHBLOCK_PORT="28334"
|
||||
BITCOIN_ZMQ_SEQUENCE_PORT="28335"
|
||||
LND_GRPC_PORT="10009"
|
||||
LND_REST_PORT="8080"
|
||||
TOR_PROXY_PORT="9050"
|
||||
I2P_SAM_PORT="7656"
|
||||
TOR_CONTROL_PORT="29051"
|
||||
|
@ -297,12 +247,6 @@ DOCKER_EXECUTABLE=subprocess.check_output(["which", "docker"]).decode("utf-8").s
|
|||
# Get the real path by following symlinks
|
||||
DOCKER_BINARY=subprocess.check_output(["readlink", "-f", DOCKER_EXECUTABLE]).decode("utf-8").strip()
|
||||
|
||||
# Set LND fee URL for neutrino on mainnet
|
||||
LND_FEE_URL=""
|
||||
# If the network is mainnet and status_dir/node-status-bitcoind-ready doesn't exist, set the FEE_URL
|
||||
if BITCOIN_NETWORK == 'mainnet' and BITCOIN_NODE == 'neutrino':
|
||||
LND_FEE_URL="feeurl=https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json"
|
||||
|
||||
# Checks if a variable with the name exists, if not, check if an env var with the name existts
|
||||
# if neither exists, then exit with an error
|
||||
def get_var(var_name, other_locals, file_name):
|
||||
|
@ -349,7 +293,7 @@ build_template("./templates/torrc-core-sample", "./tor/torrc-core")
|
|||
build_template("./templates/bitcoin-sample.conf", "./bitcoin/bitcoin.conf")
|
||||
build_template("./templates/i2p-sample.conf", "./i2p/i2pd.conf")
|
||||
build_template("./templates/i2p-tunnels-sample.conf", "./i2p/tunnels.conf")
|
||||
build_template("./templates/lnd-sample.conf", "./lnd/lnd.conf")
|
||||
MIDDLEWARE_IP="NOT_YET_SET"
|
||||
build_template("./templates/.env-sample", "./.env")
|
||||
|
||||
print("Ensuring Docker Compose is up to date...")
|
||||
|
@ -359,7 +303,7 @@ print("Updating core services...")
|
|||
print()
|
||||
with open("docker-compose.yml", 'r') as stream:
|
||||
compose = yaml.safe_load(stream)
|
||||
for service in ["manager", "middleware", "dashboard"]:
|
||||
for service in ["manager", "dashboard"]:
|
||||
compose["services"][service]["image"] = dependencies[service]
|
||||
for service in ["tor", "app-tor", "app-2-tor", "app-3-tor"]:
|
||||
compose["services"][service]["image"] = dependencies["tor"]
|
||||
|
@ -378,6 +322,24 @@ else:
|
|||
print("Generating app configuration...\n")
|
||||
os.system('./scripts/app generate')
|
||||
|
||||
# Run ./scripts/app get-implementation lightning to get the implementation
|
||||
# If it fails, install the LND app and set the implementation to LND
|
||||
try:
|
||||
implementation = subprocess.check_output("./scripts/app get-implementation lightning", shell=True).decode("utf-8").strip()
|
||||
except:
|
||||
print("Installing LND...\n")
|
||||
os.system('./scripts/app install lnd')
|
||||
implementation = "lnd"
|
||||
|
||||
# Get APP_<IMPLEMENTATION>_MIDDLEWARE_IP from the .env file
|
||||
dotenv=parse_dotenv('./.env')
|
||||
MIDDLEWARE_IP = dotenv["APP_{}_MIDDLEWARE_IP".format(implementation.upper().replace("-", "_"))]
|
||||
|
||||
build_template("./templates/.env-sample", "./.env")
|
||||
|
||||
print("Updating app configuration...\n")
|
||||
os.system('./scripts/app generate')
|
||||
|
||||
# Touch status_dir/configured
|
||||
with open(status_dir+'/configured', 'w') as file:
|
||||
file.write('')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-FileCopyrightText: 2021-2022 Citadel and contributors
|
||||
# SPDX-FileCopyrightText: 2021-2023 Citadel and contributors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
@ -140,12 +140,6 @@ if [[ "${1}" == "--run" ]]; then
|
|||
echo "-----------------"
|
||||
echo
|
||||
docker compose logs --tail=30 bitcoin
|
||||
|
||||
echo
|
||||
echo "Lightning logs"
|
||||
echo "--------------"
|
||||
echo
|
||||
docker compose logs --tail=30 lightning
|
||||
|
||||
echo
|
||||
echo "Tor logs"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
NODE_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
|
||||
|
||||
# IN ${NODE_ROOT}/.env, change the UPDATE_CHANNEL to the desired channel ($1)
|
||||
# In ${NODE_ROOT}/.env, change the UPDATE_CHANNEL to the desired channel ($1)
|
||||
# If $1 is not given, fail
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <channel>"
|
||||
|
|
|
@ -28,9 +28,6 @@ check_dependencies () {
|
|||
# Check system's dependencies
|
||||
check_dependencies readlink dirname ip docker
|
||||
|
||||
# Check karen's dependencies
|
||||
check_dependencies fswatch
|
||||
|
||||
# Check OTA update scripts' dependencies
|
||||
check_dependencies rsync jq curl
|
||||
|
||||
|
@ -44,7 +41,7 @@ fi
|
|||
|
||||
# Configure Citadel if it isn't already configured
|
||||
if [[ ! -f "${CITADEL_ROOT}/statuses/configured" ]]; then
|
||||
NGINX_PORT=${NGINX_PORT:-80} NGINX_SSL_PORT=${NGINX_SSL_PORT:-443} NETWORK="${NETWORK:-mainnet}" "${CITADEL_ROOT}/scripts/configure"
|
||||
CADDY_PORT=${CADDY_PORT:-80} CADDY_HTTPS_PORT=${CADDY_HTTPS_PORT:-443} NETWORK="${NETWORK:-mainnet}" "${CITADEL_ROOT}/scripts/configure"
|
||||
fi
|
||||
|
||||
echo
|
||||
|
@ -76,6 +73,7 @@ fi
|
|||
export DEVICE_HOSTS=$DEVICE_HOSTS
|
||||
export DEVICE_IP=$DEVICE_IP
|
||||
export DEVICE_HOSTNAME="${DEVICE_HOSTNAME}.local"
|
||||
export DEVICE_IP=$DEVICE_IP
|
||||
|
||||
# Increase default Docker and Compose timeouts to 240s
|
||||
# as bitcoin can take a long while to respond
|
||||
|
|
|
@ -7,3 +7,4 @@
|
|||
CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
|
||||
|
||||
"${CITADEL_ROOT}/scripts/app" list-updates
|
||||
"${CITADEL_ROOT}/scripts/app" download-new
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-FileCopyrightText: 2021-2022 Citadel and contributors
|
||||
# SPDX-FileCopyrightText: 2021-2023 Citadel and contributors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
@ -7,3 +7,5 @@
|
|||
CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
|
||||
|
||||
"${CITADEL_ROOT}/scripts/configure"
|
||||
|
||||
docker restart caddy
|
|
@ -113,6 +113,23 @@ cd "$CITADEL_ROOT"
|
|||
./scripts/start || true
|
||||
|
||||
|
||||
echo "Installing LND as app"
|
||||
cat <<EOF > "$CITADEL_ROOT"/statuses/update-status.json
|
||||
{"state": "installing", "progress": 85, "description": "Installing LND", "updateTo": "$RELEASE"}
|
||||
EOF
|
||||
# LND config needs update
|
||||
./scripts/app uninstall "lnd"
|
||||
./scripts/configure || true
|
||||
./scripts/app stop "lnd"
|
||||
|
||||
rm -rf "$CITADEL_ROOT"/app-data/lnd/lnd
|
||||
|
||||
mv "$CITADEL_ROOT"/lnd "$CITADEL_ROOT"/app-data/lnd/lnd
|
||||
|
||||
rm -f "$CITADEL_ROOT"/app-data/lnd/lnd/lnd.conf
|
||||
|
||||
./scripts/app start lnd
|
||||
|
||||
cat <<EOF > "$CITADEL_ROOT"/statuses/update-status.json
|
||||
{"state": "success", "progress": 100, "description": "Successfully installed Citadel $RELEASE", "updateTo": ""}
|
||||
EOF
|
||||
|
|
1
services/installed.yml
Normal file
1
services/installed.yml
Normal file
|
@ -0,0 +1 @@
|
|||
bitcoin: knots
|
|
@ -1,25 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2022-2023 Citadel and contributors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
lightning:
|
||||
container_name: lightning
|
||||
image: lightninglabs/lnd:v0.15.5-beta@sha256:ce94136ccfc48d36f058ae48fdeb53d84dbbb8f972bbae3af98e4fdfb98d641b
|
||||
user: 1000:1000
|
||||
depends_on:
|
||||
- tor
|
||||
- bitcoin
|
||||
volumes:
|
||||
- ${PWD}/lnd:/data/.lnd
|
||||
- ${PWD}/walletpassword:/walletpassword
|
||||
environment:
|
||||
HOME: /data
|
||||
restart: on-failure
|
||||
stop_grace_period: 5m30s
|
||||
ports:
|
||||
- 9735:9735
|
||||
- $LND_REST_PORT:$LND_REST_PORT
|
||||
- $LND_GRPC_PORT:$LND_GRPC_PORT
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: $LND_IP
|
|
@ -67,7 +67,6 @@ def setService(name, implementation):
|
|||
installed = yaml.safe_load(stream)
|
||||
except FileNotFoundError:
|
||||
installed = {
|
||||
"lightning": "lnd",
|
||||
"bitcoin": "core"
|
||||
}
|
||||
installed[name] = implementation
|
||||
|
@ -98,7 +97,6 @@ def uninstallService(name):
|
|||
installed = yaml.safe_load(stream)
|
||||
except FileNotFoundError:
|
||||
installed = {
|
||||
"lightning": "lnd",
|
||||
"bitcoin": "core"
|
||||
}
|
||||
try:
|
||||
|
@ -115,7 +113,6 @@ def installServices():
|
|||
installed = yaml.safe_load(stream)
|
||||
except FileNotFoundError:
|
||||
installed = {
|
||||
"lightning": "lnd",
|
||||
"bitcoin": "core"
|
||||
}
|
||||
|
||||
|
|
4
setenv
4
setenv
|
@ -7,9 +7,7 @@
|
|||
CITADEL_ROOT="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))"
|
||||
|
||||
alias citadel="${CITADEL_ROOT}/bin/citadel"
|
||||
alias lncli="docker exec -it lightning lncli"
|
||||
alias lncli="docker exec -it lnd-service-1 lncli"
|
||||
alias bitcoin-cli="docker exec -it bitcoin bitcoin-cli"
|
||||
alias docker-compose="sudo docker compose"
|
||||
alias docker="sudo docker"
|
||||
|
||||
export BOS_DEFAULT_LND_PATH="${CITADEL_ROOT}/lnd"
|
||||
|
|
|
@ -13,7 +13,6 @@ CADDY_HTTPS_PORT=<caddy-https-port>
|
|||
DASHBOARD_IP=<dashboard-ip>
|
||||
MANAGER_IP=<manager-ip>
|
||||
MIDDLEWARE_IP=<middleware-ip>
|
||||
NEUTRINO_SWITCHER_IP=<neutrino-switcher-ip>
|
||||
I2P_IP=<i2p-ip>
|
||||
I2P_PASSWORD=<i2p-password>
|
||||
BITCOIN_NETWORK=<bitcoin-network>
|
||||
|
@ -27,13 +26,8 @@ BITCOIN_ZMQ_RAWBLOCK_PORT=<bitcoin-zmq-rawblock-port>
|
|||
BITCOIN_ZMQ_RAWTX_PORT=<bitcoin-zmq-rawtx-port>
|
||||
BITCOIN_ZMQ_HASHBLOCK_PORT=<bitcoin-zmq-hashblock-port>
|
||||
BITCOIN_ZMQ_SEQUENCE_PORT=<bitcoin-zmq-sequence-port>
|
||||
LND_IP=<lnd-ip>
|
||||
LND_GRPC_PORT=<lnd-grpc-port>
|
||||
LND_REST_PORT=<lnd-rest-port>
|
||||
TOR_PROXY_IP=<tor-proxy-ip>
|
||||
TOR_PROXY_PORT=<tor-proxy-port>
|
||||
TOR_PASSWORD='<tor-password>'
|
||||
TOR_HASHED_PASSWORD='<tor-hashed-password>'
|
||||
APPS_TOR_IP=<apps-tor-ip>
|
||||
APPS_2_TOR_IP=<apps-2-tor-ip>
|
||||
APPS_3_TOR_IP=<apps-3-tor-ip>
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com
|
||||
# SPDX-FileCopyrightText: 2021-2022 Citadel and contributors.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Please note: This file can't be changed, it will be overwritten.
|
||||
# A few modifications will be kept, including alias, color, channel size limitations and more if you contact us.
|
||||
|
||||
[Application Options]
|
||||
<alias-and-color>
|
||||
listen=0.0.0.0:9735
|
||||
rpclisten=0.0.0.0:<lnd-grpc-port>
|
||||
restlisten=0.0.0.0:<lnd-rest-port>
|
||||
accept-keysend=true
|
||||
tlsextraip=<lnd-ip>
|
||||
tlsextradomain=<device-hostname>.local
|
||||
tlsautorefresh=1
|
||||
tlsdisableautofill=1
|
||||
<lnd-fee-url>
|
||||
wallet-unlock-password-file=/walletpassword
|
||||
wallet-unlock-allow-create=true
|
||||
gc-canceled-invoices-on-startup=true
|
||||
gc-canceled-invoices-on-the-fly=true
|
||||
accept-amp=true
|
||||
<channel-limitations>
|
||||
|
||||
[protocol]
|
||||
; Allow channels larger than 0.16 BTC
|
||||
protocol.wumbo-channels=true
|
||||
|
||||
[Bitcoind]
|
||||
bitcoind.rpchost=<bitcoin-ip>:<bitcoin-rpc-port>
|
||||
bitcoind.rpcuser=<bitcoin-rpc-user>
|
||||
bitcoind.rpcpass=<bitcoin-rpc-pass>
|
||||
bitcoind.zmqpubrawblock=tcp://<bitcoin-ip>:<bitcoin-zmq-rawblock-port>
|
||||
bitcoind.zmqpubrawtx=tcp://<bitcoin-ip>:<bitcoin-zmq-rawtx-port>
|
||||
|
||||
[Bitcoin]
|
||||
bitcoin.active=1
|
||||
bitcoin.<bitcoin-network>=1
|
||||
# Default to neutrino as the node is
|
||||
# automatically switched to bitcoind once
|
||||
# IBD is complete
|
||||
bitcoin.node=<bitcoin-node>
|
||||
bitcoin.defaultchanconfs=2
|
||||
<basefee>
|
||||
<additional-bitcoin-options>
|
||||
|
||||
<neutrino-peers>
|
||||
|
||||
; Enable watchtower to watch other nodes
|
||||
[watchtower]
|
||||
watchtower.active=1
|
||||
|
||||
; activate watchtower client so we can get get other servers
|
||||
; to make sure no one steals our money
|
||||
[wtclient]
|
||||
wtclient.active=1
|
||||
|
||||
[tor]
|
||||
tor.active=1
|
||||
tor.control=<tor-proxy-ip>:<tor-control-port>
|
||||
tor.socks=<tor-proxy-ip>:<tor-proxy-port>
|
||||
tor.targetipaddress=<lnd-ip>
|
||||
tor.password=<tor-password>
|
||||
tor.v3=1
|
||||
|
||||
<bolt-db-options>
|
|
@ -1,44 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2020 Umbrel. https://getumbrel.com
|
||||
# SPDX-FileCopyrightText: 2021-2022 Citadel and contributors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
user nginx;
|
||||
worker_processes 1;
|
||||
|
||||
error_log /dev/stdout info;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
access_log /dev/stdout;
|
||||
|
||||
proxy_read_timeout 600;
|
||||
|
||||
default_type application/octet-stream;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://<middleware-ip>:3005/;
|
||||
}
|
||||
|
||||
location /api-v2/ {
|
||||
proxy_pass http://<manager-ip>:3000/;
|
||||
}
|
||||
|
||||
location /i2p/ {
|
||||
proxy_pass http://<i2p-ip>:7070/;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://<dashboard-ip>:3004/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
# Bind only to "<tor-proxy-ip>" which is the tor IP within the container
|
||||
SocksPort <tor-proxy-ip>:<tor-proxy-port>
|
||||
ControlPort <tor-proxy-ip>:<tor-control-port>
|
||||
|
||||
# Citadel Core Services
|
||||
|
||||
|
@ -19,13 +18,3 @@ HiddenServicePort <bitcoin-p2p-port> <bitcoin-ip>:<bitcoin-p2p-port>
|
|||
# Bitcoin Core RPC Hidden Service
|
||||
HiddenServiceDir /var/lib/tor/bitcoin-rpc
|
||||
HiddenServicePort <bitcoin-rpc-port> <bitcoin-ip>:<bitcoin-rpc-port>
|
||||
|
||||
# LND REST Hidden Service
|
||||
HiddenServiceDir /var/lib/tor/lnd-rest
|
||||
HiddenServicePort <lnd-rest-port> <lnd-ip>:<lnd-rest-port>
|
||||
|
||||
# LND gRPC Hidden Service
|
||||
HiddenServiceDir /var/lib/tor/lnd-grpc
|
||||
HiddenServicePort <lnd-grpc-port> <lnd-ip>:<lnd-grpc-port>
|
||||
|
||||
HashedControlPassword <tor-hashed-password>
|
||||
|
|
Loading…
Reference in New Issue
Block a user