"""Prepare a Docker .env file for a strategy.
To run this script standalone::
.. code-block:: shell
source ~/secrets.env
prepare-docker-env < env/pancake-eth-usd-sma.env > ~/pancake-eth-usd-sma-final.env
"""
import os
import sys
from typing import TextIO
from dotenv import dotenv_values
import typer
from tradeexecutor.cli.env import get_available_env_vars
#: List of minimal options needed to launch a live trading strategy
REQUIRED_LIVE_TRADING_ENV = [
# Secret options needed
"PRIVATE_KEY",
"TRADING_STRATEGY_API_KEY",
# "DISCORD_WEBHOOK_URL",
("JSON_RPC_BINANCE", "JSON_RPC_POLYGON", "JSON_RPC_ETHEREUM", "JSON_RPC_AVALANCHE"),
# Public options required
"NAME",
"HTTP_ENABLED",
"STRATEGY_FILE",
"EXECUTION_TYPE"
]
[docs]def check_required_live_trading_vars(env, required_list):
"""Check that we have required"""
# Show what we got in the case we need to debug
env_output = ", ".join(env.keys())
for s in required_list:
if type(s) == tuple:
for s2 in s:
if s2 in env.keys():
break
else:
assert False, f"You need to pass one of environment variable {s} for this script in .env file or as environment variable. Also remember to source the secrets file before running. Got: {env_output}"
else:
assert s in env, f"You need to pass environment variable {s} for this script in .env file or as environment variable. Also remember to source the secrets file before running. Got: {env_output}"
# Docker cannot read newlines from .env
# https://github.com/moby/moby/issues/12997
for key, value in env.items():
assert "\n" not in value, f"Detected new line in {key}"
[docs]def merge_secrets(public: dict, secret: dict) -> dict:
return secret | public
[docs]def filter_config(config: dict) -> dict:
"""
Because we are reading full host OS environment,
we get a lot of env vars we do not want to pass.
"""
vars = get_available_env_vars()
names = [v.name for v in vars]
return {k: v for k, v in config.items() if k in names}
[docs]def write_config(out: TextIO, config):
print("# This is automatically generated by prepare-docker-env command - do not hand edit", file=out)
print("#", file=out)
for k, v in config.items():
print(f'{k}={v}', file=out)
print("", file=out)
[docs]def app():
"""Creates .env file for a trade-executor Docker container.
- Reads public .env configuration variables from a given file
- Reads secret environment variables from passed environment variables
- Validates we have required variables to run a live strategy
- Writes the output to a single .env file that can be read by Docker
- Operates on environment variables, stdin and stdout streams
Usage:
# Load your non-committed secrets from the server root
source ~/my-secrets.env
# Combine the env configuration file with the secrets
prepare-docker-env env/my-config.env ~/docker-final.env
Never publish or commit the final generated environment file, as it contains
security critical variables. Leaking these allow stealing of your assets.
"""
strategy_config = dotenv_values(stream=sys.stdin)
secret_config = {k: v for k, v in os.environ.items()}
merged_config = merge_secrets(secret_config, strategy_config)
filtered_config = filter_config(merged_config)
check_required_live_trading_vars(filtered_config, REQUIRED_LIVE_TRADING_ENV)
write_config(sys.stdout, filtered_config)
print(f"Environment variables prepared for Docker .env:", file=sys.stderr)
for k in filtered_config.keys():
print(f" {k}", file=sys.stderr)
[docs]def main():
typer.run(app)