Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions server/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,3 @@ def setup_periodic_tasks(sender, **kwargs):
send_statistics,
name="send usage statistics",
)


# send report after start
if Configuration.COLLECT_STATISTICS:
send_statistics.delay()
9 changes: 0 additions & 9 deletions server/mergin/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,6 @@ def create_simple_app() -> Flask:
if Configuration.GEVENT_WORKER:
flask_app.wsgi_app = GeventTimeoutMiddleware(flask_app.wsgi_app)

@flask_app.cli.command()
def init_db():
"""Re-creates application database"""
print("Database initialization ...")
db.drop_all(bind=None)
db.create_all(bind=None)
db.session.commit()
print("Done. Tables created.")

add_commands(flask_app)

return flask_app
Expand Down
226 changes: 171 additions & 55 deletions server/mergin/commands.py
Original file line number Diff line number Diff line change
@@ -1,95 +1,211 @@
import click
from flask import Flask
from sqlalchemy import or_, func
import random
import string
from datetime import datetime, timezone


from .config import Configuration
def _echo_title(title):
click.echo("")
click.echo(f"# {title}")
click.echo()


def _echo_error(msg):
click.secho("Error: ", fg="red", nl=False, bold=True)
click.secho(msg, fg="bright_red")


def add_commands(app: Flask):
from .app import db
from mergin.auth.models import UserProfile

@app.cli.group()
def server():
"""Server management commands."""
pass
def _check_celery():
from celery import current_app

@server.command()
@click.option("--email", required=True)
def send_check_email(email: str): # pylint: disable=W0612
ping_celery = current_app.control.inspect().ping()
if not ping_celery:
_echo_error(
"Celery process not running properly. Configure celery worker and celery beat. This breaks also email sending from the system.",
)
return
click.secho("Celery is running properly", fg="green")
return True

def _send_statistics():
from .stats.tasks import send_statistics, save_statistics

_echo_title("Sending statistics.")
# save rows to MerginStatistics table
save_statistics.delay()

if not app.config.get("COLLECT_STATISTICS"):
click.secho(
"Statistics sending is disabled.",
)
return

if not _check_celery():
return
send_statistics.delay()
click.secho("Statistics sent.", fg="green")

def _send_email(email: str):
"""Send check email to specified email address."""
from .celery import send_email_async

_echo_title(f"Sending check email to specified email address {email}.")
if app.config["MAIL_SUPPRESS_SEND"]:
click.echo(
click.style(
"Sending emails is disabled. Please set MAIL_SUPPRESS_SEND=False to enable sending emails.",
fg="red",
)
_echo_error(
"Sending emails is disabled. Please set MAIL_SUPPRESS_SEND=False to enable sending emails."
)
return
if not app.config["MAIL_DEFAULT_SENDER"]:
click.echo(
click.style(
"No default sender set. Please set MAIL_DEFAULT_SENDER environment variable",
fg="red",
)
default_sender = app.config.get("MAIL_DEFAULT_SENDER")
if not default_sender:
_echo_error(
"No default sender set. Please set MAIL_DEFAULT_SENDER environment variable",
)
return
email_data = {
"subject": "Mergin Maps server check",
"html": "Awesome, your email configuration of Mergin Maps server is working.",
"recipients": [email],
"sender": app.config["MAIL_DEFAULT_SENDER"],
"sender": default_sender,
}
click.echo(
f"Sending email to specified email address {email}. Check your inbox."
)
try:
is_celery_running = _check_celery()
if not is_celery_running:
return
send_email_async.delay(**email_data)
except Exception as e:
click.echo(
click.style(
f"Error sending email: {e}",
fg="red",
)
_echo_error(
f"Error sending email: {e}",
)

@server.command()
def check(): # pylint: disable=W0612
"""Check server configuration. Define email to send testing email."""
from celery import current_app
def _check_server(): # pylint: disable=W0612
"""Check server configuration."""

click.echo(f"Mergin Maps server version: {app.config['VERSION']}")
_echo_title("Server health check")
edition_map = {
"ce": "Community Edition",
"ee": "Enterprise Edition",
}
edition = edition_map.get(app.config["SERVER_TYPE"])
if edition:
click.echo(f"Mergin Maps edition: {edition}")
click.echo(f"Mergin Maps version: {app.config['VERSION']}")

base_url = app.config["MERGIN_BASE_URL"]
if not base_url:
click.echo(
click.style(
"No base URL set. Please set MERGIN_BASE_URL environment variable",
fg="red",
),
_echo_error(
"No base URL set. Please set MERGIN_BASE_URL environment variable",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we also check contact_email variable? I'd maybe use it for admin/test email

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can, but it's not required variable - see docs https://merginmaps.com/docs/server/administer/environment/

)
else:
click.secho(f"Base URL of server is {base_url}", fg="green")

contact_email = app.config["CONTACT_EMAIL"]
if not contact_email:
_echo_error(
"No contact email set. Please set CONTACT_EMAIL environment variable",
)
else:
click.echo(f"Base URL of server is {base_url}")
click.secho(f"Base URL of server is {base_url}", fg="green")

tables = db.engine.table_names()
if not tables:
click.echo(
click.style(
"Database not initialized. Run flask init-db command", fg="red"
)
)
_echo_error("Database not initialized. Run flask init-db command")
else:
click.echo("Database initialized properly")
click.secho("Database initialized properly", fg="green")

ping_celery = current_app.control.inspect().ping()
if not ping_celery:
click.echo(
click.style(
"Celery not running. Configure celery worker and celery beat",
fg="red",
)
_check_celery()

def _init_db():
"""Create database tables."""
from .stats.models import MerginInfo

_echo_title("Database initialization")
with click.progressbar(
label="Creating database", length=4, show_eta=False
) as progress_bar:
progress_bar.update(0)
db.drop_all(bind=None)
progress_bar.update(1)
db.create_all(bind=None)
progress_bar.update(2)
db.session.commit()
progress_bar.update(3)
info = MerginInfo.query.first()
if not info and app.config.get("COLLECT_STATISTICS"):
# create new info with random service id
service_id = app.config.get("SERVICE_ID", None)
info = MerginInfo(service_id)
db.session.add(info)
db.session.commit()
progress_bar.update(4)

click.secho("Tables created.", fg="green")

@app.cli.command()
def init_db():
"""Re-create database tables."""
_init_db()

@app.cli.command()
@click.option("--email", "-e", required=True, envvar="CONTACT_EMAIL")
@click.option(
"--recreate",
"-r",
help="Recreate database and admin user.",
is_flag=True,
required=False,
)
def init(email: str, recreate: bool):
"""Initialize database if does not exist or -r is provided. Perform check of server configuration. Send statistics, respecting your setup."""

from .auth.models import User

tables = db.engine.table_names()
if recreate and tables:
click.confirm(
"Are you sure you want to recreate database and admin user? This will remove all data!",
default=False,
abort=True,
)
else:
click.echo("Celery running properly")

if not tables or recreate:
_init_db()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this recreate flag might be dangerous, shall we ask for user confirmation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added


_echo_title("Creating admin user. Copy generated password.")
username = User.generate_username(email)
password_chars = string.ascii_letters + string.digits
password = "".join(random.choice(password_chars) for i in range(12))
user = User(username=username, passwd=password, email=email, is_admin=True)
user.profile = UserProfile()
user.active = True
db.session.add(user)
db.session.commit()
click.secho(
"Admin user created. Please save generated password.", fg="green"
)
click.secho(f"Email: {email}")
click.secho(f"Username: {username}")
click.secho(f"Password: {password}")
_check_server()
_send_email(email)
_send_statistics()

@app.cli.group()
def server():
"""Server management commands."""
pass

@server.command()
@click.option("--email", required=True)
def send_check_email(email: str): # pylint: disable=W0612
"""Send check email to specified email address."""
_send_email(email)

@server.command()
def check():
"""Check server configuration."""
_check_server()
Loading