diff --git a/Dockerfile b/Dockerfile index 72127a190..5a7810721 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18.19-alpine +FROM node:18.19-alpine AS build WORKDIR /app RUN chown -R node:node /app @@ -11,7 +11,23 @@ RUN npm install RUN npm run init RUN npm run build +FROM node:18.19-alpine + +ENV PYTHONUNBUFFERED=1 +RUN apk add --no-cache python3 py3-pip py3-ruamel.yaml + +WORKDIR /app +RUN chown -R node:node /app + + +COPY --from=build --chown=node /app/*.json /app/ +COPY --from=build --chown=node /app/dist /app/dist +COPY --from=build --chown=node /app/node_modules /app/node_modules +COPY --from=build --chown=node /app/config /app/config +COPY entrypoint.py /app/entrypoint.py + +USER node + EXPOSE 3001 -RUN chmod +x entrypoint.sh +CMD ["python", "entrypoint.py"] -CMD ["./entrypoint.sh"] diff --git a/config/config.placeholder.yaml b/config/config.placeholder.yaml deleted file mode 100644 index e764432d9..000000000 --- a/config/config.placeholder.yaml +++ /dev/null @@ -1,171 +0,0 @@ -network: 'DAPP_CONFIG' -metaChainShardId: 4294967295 -api: - public: true - publicPort: 3001 - private: true - privatePort: 4001 - websocket: true -cron: - cacheWarmer: true - fastWarm: true - queueWorker: true - elasticUpdater: false -flags: - useRequestCaching: true - useKeepAliveAgent: true - useTracing: false - useRequestLogging: false - useVmQueryTracing: false - processNfts: true - collectionPropertiesFromGateway: false -features: - eventsNotifier: - enabled: false - port: 5674 - url: 'amqp://guest:guest@127.0.0.1:5673' - exchange: 'all_events' - queue: 'api-process-logs-and-events' - guestCaching: - enabled: false - hitsThreshold: 100 - ttl: 12 - transactionPool: - enabled: false - transactionPoolWarmer: - enabled: false - cronExpression: '*/5 * * * * *' - ttlInSeconds: 60 - updateCollectionExtraDetails: - enabled: false - updateAccountExtraDetails: - enabled: false - marketplace: - enabled: false - serviceUrl: 'MARKETPLACE_URL' - exchange: - enabled: false - serviceUrl: 'EXCHANGE_URL' - dataApi: - enabled: false - serviceUrl: 'DATAAPI_URL' - assetsFetch: - enabled: true - assetesUrl: 'ASSETSFETCH_URL' - auth: - enabled: false - maxExpirySeconds: 86400 - acceptedOrigins: - - '' - admins: - - '' - jwtSecret: '' - stakingV4: - enabled: false - cronExpression: '*/5 * * * * *' - activationEpoch: 1043 - nodeEpochsLeft: - enabled: false - transactionProcessor: - enabled: false - maxLookBehind: 100 - transactionCompleted: - enabled: false - maxLookBehind: 100 - logLevel: 'Error' - transactionBatch: - enabled: true - maxLookBehind: 100 - statusChecker: - enabled: false - thresholds: - tokens: 500 - nodes: 3000 - providers: 10 - tokenSupplyCount: 20 - tokenAssets: 20 - tokenAccounts: 500 - tokenTransactions: 500 - nodeValidators: 300 - nftScamInfo: - enabled: false - processNfts: - enabled: false - nftQueueName: 'api-process-nfts' - deadLetterQueueName: 'api-process-nfts-dlq' - tps: - enabled: false - maxLookBehindNonces: 100 - nodesFetch: - enabled: true - serviceUrl: 'NODESFETCH_URL' - tokensFetch: - enabled: true - serviceUrl: 'TOKENSFETCH_URL' - providersFetch: - enabled: true - serviceUrl: 'PROVIDERSFETCH_URL' -image: - width: 600 - height: 600 - type: 'png' -aws: - s3KeyId: '' - s3Secret: '' - s3Bucket: 'devnet-media.elrond.com' - s3Region: '' -urls: - self: 'https://devnet-api.multiversx.com' - elastic: - - 'ELASTICSEARCH_URL' - gateway: - - 'GATEWAY_URL' - verifier: 'https://play-api.multiversx.com' - redis: 'REDIS_IP' - rabbitmq: 'RABBITMQ_URL' - providers: 'PROVIDERS_URL' - delegation: 'DELEGATION_URL' - media: 'https://devnet-media.elrond.com' - nftThumbnails: 'https://devnet-media.elrond.com/nfts/thumbnail' - tmp: '/tmp' - ipfs: 'https://ipfs.io/ipfs' - socket: 'SOCKET_URL' - maiarId: 'https://devnet-id-api.multiversx.com' -indexer: - type: 'elastic' - maxPagination: 10000 -database: - enabled: false - url: 'mongodb://127.0.0.1:27017/api?authSource=admin' - type: 'mysql' - host: 'localhost' - port: 3306 - username: 'root' - password: 'root' - database: 'api' -caching: - cacheTtl: 6 - processTtl: 600 - poolLimit: 50 - cacheDuration: 3 -keepAliveTimeout: - downstream: 61000 - upstream: 60000 -contracts: - esdt: 'erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u' - auction: 'erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l' - staking: 'erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqllls0lczs7' - delegationManager: 'erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6' - delegation: 'erd1qqqqqqqqqqqqqpgq97wezxw6l7lgg7k9rxvycrz66vn92ksh2tssxwf7ep' - metabonding: 'erd1qqqqqqqqqqqqqpgqkg7we73j769ew5we4yyx7uyvnn0nefqgd8ssm6vjc2' -inflation: - - 1952123 - - 1746637 - - 1541150 - - 1335663 - - 1130177 - - 924690 - - 719203 -nftProcess: - parallelism: 1 - maxRetries: 3 diff --git a/config/dapp.config.placeholder.json b/config/dapp.config.placeholder.json deleted file mode 100644 index b042458a2..000000000 --- a/config/dapp.config.placeholder.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "PLACEHOLDER_DAPP_id", - "name": "PLACEHOLDER_DAPP_name", - "egldLabel": "PLACEHOLDER_DAPP_egldLabel", - "decimals": "4", - "egldDenomination": "18", - "gasPerDataByte": "1500", - "apiTimeout": "4000", - "walletConnectDeepLink": "https://maiar.page.link/?apn=com.multiversx.maiar.wallet&isi=1519405832&ibi=com.multiversx.maiar.wallet&link=https://maiar.com/", - "walletConnectBridgeAddresses": [ - "https://bridge.walletconnect.org" - ], - "walletAddress": "PLACEHOLDER_DAPP_walletAddress", - "apiAddress": "PLACEHOLDER_DAPP_apiAddress", - "explorerAddress": "PLACEHOLDER_DAPP_explorerAddress", - "chainId": "PLACEHOLDER_DAPP_chainId" -} \ No newline at end of file diff --git a/entrypoint.py b/entrypoint.py new file mode 100644 index 000000000..1a7ad344b --- /dev/null +++ b/entrypoint.py @@ -0,0 +1,173 @@ +import os +import json +from ruamel.yaml import YAML + +yaml = YAML(typ='rt') +yaml.preserve_quotes = True +yaml.default_flow_style = False +yaml.width = 4096 # to avoid long string values being placed on the next line + +# Load the YAML file +def load_yaml(file_path): + with open(file_path, 'r') as file: + return yaml.load(file) + +# Save the updated YAML file +def save_yaml(file_path, data): + with open(file_path, 'w') as file: + yaml.dump(data, file) + +# Load the JSON file +def load_json(file_path): + with open(file_path, 'r') as file: + return json.load(file) + +# Save the updated JSON file +def save_json(file_path, data): + with open(file_path, 'w') as file: + json.dump(data, file, indent=4) + +# Function to convert string to the proper type based on the prefix (bool, num, raw) +def convert_value(value_str): + # Check if the value string contains a colon + if ":" not in value_str: + # If no colon, assume it's a plain string + return value_str + + # Split the string by the ':' delimiter + prefix, value = value_str.split(":", 1) + + match prefix: + # BOOLEAN + case 'bool': + return value.lower() == 'true' # Convert to boolean (True/False) + # NUMBER + case 'num': + try: + return int(value) # Convert to integer + except ValueError: + print(f"Error: Cannot convert '{value_str}' to a number.") + return None + # ARRAY + case 'arr': + # Check if the value looks like a JSON array (starts with '[' and ends with ']') + if value.startswith('[') and value.endswith(']'): + try: + return json.loads(value) # Convert the string to a list + except json.JSONDecodeError: + print(f"Error: Cannot decode '{value_str}' as a JSON array.") + return None + print(f"Error: '{value_str}' is not a valid array format.") + return None # Return None if the array format is incorrect + # RAW + case 'raw': + return value # Return exactly as received (raw string) + # DEFAULT STRING + case _: + return value_str # Default to string if no match + +# Modify the value in the YAML structure based on the variable name +def modify_yaml_variable(data, variable_name, new_value): + keys = variable_name[4:].split('_') # Remove 'CFG_' prefix + sub_data = data + + # Traverse the YAML structure using the keys to reach the variable and modify its value + for key in keys[:-1]: + if key in sub_data: + sub_data = sub_data[key] + else: + print(f"Key '{key}' not found in the YAML structure.") + return + + # Check if the final key exists in the structure + final_key = keys[-1] + if final_key in sub_data: + # Check if it's an array (arr: prefix) + if isinstance(new_value, str) and new_value.startswith('arr:'): + try: + # Parse the value as a JSON array + sub_data[final_key] = json.loads(new_value[4:]) # Strip 'arr:' and parse + except json.JSONDecodeError: + print(f"Error decoding JSON array in value: {new_value}") + else: + sub_data[final_key] = new_value + else: + print(f"Key '{final_key}' not found at the end of the path.") + return + +# Modify the value in the JSON structure based on the variable name +def modify_json_variable(data, variable_name, new_value): + keys = variable_name[5:].split('_') # Remove 'DAPP_' prefix + sub_data = data + + # Traverse the JSON structure using the keys to reach the variable and modify its value + for key in keys[:-1]: + if key in sub_data: + sub_data = sub_data[key] + else: + print(f"Key '{key}' not found in the JSON structure.") + return + + # Check if the value is a JSON array (list) and parse it + final_key = keys[-1] + if final_key in sub_data: + # If the new value is a string representing a JSON array, parse it + if isinstance(new_value, str) and new_value.startswith('[') and new_value.endswith(']'): + try: + # Parse the string as a JSON array + sub_data[final_key] = json.loads(new_value) + except json.JSONDecodeError: + print(f"Error decoding JSON array in value: {new_value}") + else: + sub_data[final_key] = new_value + else: + print(f"Key '{final_key}' not found at the end of the path.") + return + +# Main function +def main(): + # Input and output file paths + default_cfg_file = os.getenv('DEFAULT_CFG_FILE', 'devnet') + + config_yaml_input_file = f'config/config.{default_cfg_file}.yaml' + config_yaml_output_file = '/app/dist/config/config.yaml' + + dapp_config_json_input_file = f'config/dapp.config.{default_cfg_file}.json' + dapp_config_json_output_file = f'config/dapp.config.{default_cfg_file}.json' + + # Load the YAML file + config_yaml = load_yaml(config_yaml_input_file) + + # Load the JSON file + config_json = load_json(dapp_config_json_input_file) + + # Iterate over all environment variables starting with 'CFG_' for YAML + for variable_name, new_value in os.environ.items(): + if variable_name.startswith('CFG_'): + print(f"Updating YAML variable: {variable_name} with value: {new_value}") + # Convert value based on the type (bool, num, raw, or default to string) + converted_value = convert_value(new_value) + if converted_value is not None: + modify_yaml_variable(config_yaml, variable_name, converted_value) + + # Iterate over all environment variables starting with 'DAPP_' for JSON + for variable_name, new_value in os.environ.items(): + if variable_name.startswith('DAPP_'): + print(f"Updating JSON variable: {variable_name} with value: {new_value}") + # Convert value based on the type (bool, num, raw, or default to string) + converted_value = convert_value(new_value) + if converted_value is not None: + modify_json_variable(config_json, variable_name, converted_value) + + # Save the updated YAML file + save_yaml(config_yaml_output_file, config_yaml) + print(f"Updated YAML file saved as {config_yaml_output_file}") + + # Save the updated JSON file + save_json(dapp_config_json_output_file, config_json) + print(f"Updated JSON file saved as {dapp_config_json_output_file}") + + os.execvp('node', ['node', 'dist/src/main.js']) + +if __name__ == "__main__": + main()