diff --git a/README.md b/README.md index 59c28f6..dbf8467 100644 --- a/README.md +++ b/README.md @@ -31,19 +31,6 @@ Run an instance of Postgres (I used docker for this, you can use whatever you wa docker run --rm -it -e POSTGRES_PASSWORD=dev -p 5432:5432 postgres ``` -Create a `.env` file to set up the application configuration: - -``` -nano .env -``` - -Enter your SMTP credentials like this: -``` -MAIL_USERNAME=forest@nullhex.com -MAIL_DEFAULT_SENDER=forest@nullhex.com -MAIL_PASSWORD=************** -``` - Run the app ``` @@ -56,6 +43,23 @@ Run the app in gunicorn: pipenv run gunicorn --bind 127.0.0.1:5000 -k gevent --worker-connections 1000 app:app ``` +## configuration: + +Create a `.env` file to set up the application configuration: + +``` +nano .env +``` + +You can enter any environment variables referenced in `__init__.py` to this file. + +For example you may enter your SMTP credentials like this: +``` +MAIL_USERNAME=forest@nullhex.com +MAIL_DEFAULT_SENDER=forest@nullhex.com +MAIL_PASSWORD=************** +``` + ## how to view the logs on the database server (legion.cyberia.club) `sudo -u postgres pg_dump capsul-flask | gzip -9 > capsul-backup-2021-02-15.gz` @@ -127,10 +131,10 @@ right now I have 2 types of operations, immediate mode and async. both types of operations do assignment synchronously. so if the system cant assign the operation to one or more hosts (spokes), or whatever the operation requires, then it will fail. -some operations tolerate partial failures, like, capacity_avaliable will succeed if at least one spoke succeeds -for immediate mode, assignment and completion of the operation (like `list`, `capacity_avaliable`, `destroy`) are the same thing +some operations tolerate partial failures, like, `capacity_avaliable` will succeed if at least one spoke succeeds. +for immediate mode requests (like `list`, `capacity_avaliable`, `destroy`), assignment and completion of the operation are the same thing. -for async ones, they can be assigned without knowing whether or not they succeeded (`create`) +for async ones, they can be assigned without knowing whether or not they succeeded (`create`). ![](readme/hub-and-spoke2.png) diff --git a/capsulflask/__init__.py b/capsulflask/__init__.py index 5d3dd98..7b1e9a2 100644 --- a/capsulflask/__init__.py +++ b/capsulflask/__init__.py @@ -10,7 +10,7 @@ import sys import stripe from dotenv import load_dotenv, find_dotenv from flask import Flask -from flask_mail import Mail +from flask_mail import Mail, Message from flask import render_template from flask import url_for from flask import current_app @@ -22,6 +22,9 @@ from capsulflask import hub_model, spoke_model, cli from capsulflask.btcpay import client as btcpay from capsulflask.http_client import MyHTTPClient +class StdoutMockFlaskMail: + def send(self, message: Message): + current_app.logger.info(f"Email would have been sent if configured:\n\nto: {','.join(message.recipients)}\nsubject: {message.subject}\nbody:\n\n{message.body}\n\n") load_dotenv(find_dotenv()) @@ -31,10 +34,10 @@ app.config.from_mapping( BASE_URL=os.environ.get("BASE_URL", default="http://localhost:5000"), SECRET_KEY=os.environ.get("SECRET_KEY", default="dev"), - HUB_MODE_ENABLED=os.environ.get("HUB_MODE_ENABLED", default="False").lower() in ['true', '1', 't', 'y', 'yes'], + HUB_MODE_ENABLED=os.environ.get("HUB_MODE_ENABLED", default="True").lower() in ['true', '1', 't', 'y', 'yes'], SPOKE_MODE_ENABLED=os.environ.get("SPOKE_MODE_ENABLED", default="True").lower() in ['true', '1', 't', 'y', 'yes'], INTERNAL_HTTP_TIMEOUT_SECONDS=os.environ.get("INTERNAL_HTTP_TIMEOUT_SECONDS", default="300"), - HUB_MODEL=os.environ.get("HUB_MODEL", default="mock"), + HUB_MODEL=os.environ.get("HUB_MODEL", default="capsul-flask"), SPOKE_MODEL=os.environ.get("SPOKE_MODEL", default="mock"), LOG_LEVEL=os.environ.get("LOG_LEVEL", default="INFO"), SPOKE_HOST_ID=os.environ.get("SPOKE_HOST_ID", default="default"), @@ -51,12 +54,12 @@ app.config.from_mapping( DATABASE_SCHEMA=os.environ.get("DATABASE_SCHEMA", default="public"), - MAIL_SERVER=os.environ.get("MAIL_SERVER", default="smtp.nullhex.com"), + MAIL_SERVER=os.environ.get("MAIL_SERVER", default=""), MAIL_PORT=os.environ.get("MAIL_PORT", default="465"), MAIL_USE_TLS=os.environ.get("MAIL_USE_TLS", default="True").lower() in ['true', '1', 't', 'y', 'yes'], - MAIL_USERNAME=os.environ.get("MAIL_USERNAME", default="forest@nullhex.com"), + MAIL_USERNAME=os.environ.get("MAIL_USERNAME", default=""), MAIL_PASSWORD=os.environ.get("MAIL_PASSWORD", default=""), - MAIL_DEFAULT_SENDER=os.environ.get("MAIL_DEFAULT_SENDER", default="forest@nullhex.com"), + MAIL_DEFAULT_SENDER=os.environ.get("MAIL_DEFAULT_SENDER", default="no-reply@capsul.org"), ADMIN_EMAIL_ADDRESSES=os.environ.get("ADMIN_EMAIL_ADDRESSES", default="ops@cyberia.club"), PROMETHEUS_URL=os.environ.get("PROMETHEUS_URL", default="https://prometheus.cyberia.club"), @@ -127,13 +130,18 @@ logging_dict_config({ stripe.api_key = app.config['STRIPE_SECRET_KEY'] stripe.api_version = app.config['STRIPE_API_VERSION'] -app.config['FLASK_MAIL_INSTANCE'] = Mail(app) +if app.config['MAIL_SERVER'] != "": + app.config['FLASK_MAIL_INSTANCE'] = Mail(app) +else: + app.logger.warning("No MAIL_SERVER configured. capsul will simply print emails to stdout.") + app.config['FLASK_MAIL_INSTANCE'] = StdoutMockFlaskMail() + app.config['HTTP_CLIENT'] = MyHTTPClient(timeout_seconds=int(app.config['INTERNAL_HTTP_TIMEOUT_SECONDS'])) try: app.config['BTCPAY_CLIENT'] = btcpay.Client(api_uri=app.config['BTCPAY_URL'], pem=app.config['BTCPAY_PRIVATE_KEY']) except: - app.logger.warning("unable to create btcpay client: " + my_exec_info_message(sys.exc_info())) + app.logger.warning("unable to create btcpay client. Capsul will work fine except cryptocurrency payments will not work. The error was: " + my_exec_info_message(sys.exc_info())) if app.config['HUB_MODE_ENABLED']: diff --git a/capsulflask/auth.py b/capsulflask/auth.py index a98a286..4aacc74 100644 --- a/capsulflask/auth.py +++ b/capsulflask/auth.py @@ -64,12 +64,13 @@ def login(): current_app.config["FLASK_MAIL_INSTANCE"].send( Message( "Click This Link to Login to Capsul", + sender=current_app.config["MAIL_DEFAULT_SENDER"], body=message, recipients=[email] ) ) - return render_template("login-landing.html", email=email) + return render_template("login-landing.html", email=email, has_smtp=(current_app.config["MAIL_SERVER"] != "")) for error in errors: flash(error) diff --git a/capsulflask/cli.py b/capsulflask/cli.py index 790627a..f13c304 100644 --- a/capsulflask/cli.py +++ b/capsulflask/cli.py @@ -243,6 +243,7 @@ def notify_users_about_account_balance(): Message( get_subject(pluralize_capsul), body=get_body(current_app.config['BASE_URL'], pluralize_capsul), + sender=current_app.config["MAIL_DEFAULT_SENDER"], recipients=[account['email']] ) ) @@ -288,6 +289,7 @@ def ensure_vms_and_db_are_synced(): current_app.config["FLASK_MAIL_INSTANCE"].send( Message( "Capsul Consistency Check Failed", + sender=current_app.config["MAIL_DEFAULT_SENDER"], body="\n".join(errors), recipients=email_addresses ) diff --git a/capsulflask/templates/login-landing.html b/capsulflask/templates/login-landing.html index 83f56ee..213d566 100644 --- a/capsulflask/templates/login-landing.html +++ b/capsulflask/templates/login-landing.html @@ -3,7 +3,11 @@ {% block title %}check your email{% endblock %} {% block content %} -
Check your email. A login link has been sent to {{ email }}
+ {% if has_smtp %} +
Check your email. A login link has been sent to {{ email }}
+ {% else %} +
No SMTP server configured. A login link has been printed to stdout.
+ {% endif %} {% endblock %} {% block pagesource %}/templates/login-landing.html{% endblock %} \ No newline at end of file