Skip to content

Commit

Permalink
* Fix for perara#85 perara#86
Browse files Browse the repository at this point in the history
  • Loading branch information
m4xx1m authored and m4xx1m committed Mar 13, 2021
1 parent 3469c25 commit e5100f0
Show file tree
Hide file tree
Showing 34 changed files with 463 additions and 365 deletions.
89 changes: 89 additions & 0 deletions wg_dashboard_backend/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# A generic, single database configuration.

[alembic]
# path to migration scripts
script_location = migrations

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .

# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =

# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false

# version location specification; this defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

sqlalchemy.url = sqlite:///database.db


[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples

# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks=black
# black.type=console_scripts
# black.entrypoint=black
# black.options=-l 79

# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
13 changes: 0 additions & 13 deletions wg_dashboard_backend/database.py

This file was deleted.

File renamed without changes.
21 changes: 21 additions & 0 deletions wg_dashboard_backend/database/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import sqlalchemy
from sqlalchemy import MetaData
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import const

engine = sqlalchemy.create_engine(
const.DATABASE_URL, connect_args={"check_same_thread": False}
)


SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

meta = MetaData(naming_convention={
"ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(column_0_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s"
})
Base = declarative_base(metadata=meta)
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from sqlalchemy import Integer, Column, DateTime
from sqlalchemy.orm import relationship, backref
from database import Base
from database.database import Base


class User(Base):
Expand Down
59 changes: 59 additions & 0 deletions wg_dashboard_backend/database/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import os

import alembic.command
from alembic.config import Config
from sqlalchemy.orm import Session
from sqlalchemy_utils import database_exists

import middleware
from database.database import engine, Base, SessionLocal
from database import models
from loguru import logger


def perform_migrations():
logger.info("Performing migrations...")
alembic_cfg = Config("alembic.ini")

alembic_cfg.set_main_option('script_location', "migrations")
alembic_cfg.set_main_option('sqlalchemy.url', str(engine.url))
alembic.command.upgrade(alembic_cfg, 'head')
logger.info("Migrations done!")


def setup_initial_database():
if not database_exists(engine.url):
logger.info("Database does not exists. Creating initial database...")
# Create database from metadata
Base.metadata.create_all(engine)
logger.info("Database creation done!")

# Create default user
_db: Session = SessionLocal()

admin_exists = (
_db.query(models.User.id)
.filter_by(role="admin")
.first()
) is not None

if not admin_exists:
logger.info("Admin user does not exists. Creating with env variables ADMIN_USERNAME, ADMIN_PASSWORD")
ADMIN_USERNAME = os.getenv("ADMIN_USERNAME")
ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD")

if not ADMIN_USERNAME:
raise RuntimeError("Database does not exist and the environment variable ADMIN_USERNAME is set")
if not ADMIN_PASSWORD:
raise RuntimeError("Database does not exist and the environment variable ADMIN_PASSWORD is set")

_db.merge(models.User(
username=ADMIN_USERNAME,
password=middleware.get_password_hash(ADMIN_PASSWORD),
full_name="Admin",
role="admin",
email=""
))

_db.commit()
_db.close()
2 changes: 1 addition & 1 deletion wg_dashboard_backend/db/api_key.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from sqlalchemy.orm import Session

import models
from database import models


def add_initial_api_key_for_admin(sess: Session, api_key, ADMIN_USERNAME):
Expand Down
3 changes: 1 addition & 2 deletions wg_dashboard_backend/db/user.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from typing import Optional

from sqlalchemy.orm import Session
import models
from passlib.context import CryptContext
from database import models

import schemas

Expand Down
6 changes: 2 additions & 4 deletions wg_dashboard_backend/db/wireguard.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@

import const
import script.wireguard
from sqlalchemy import exists
from sqlalchemy.orm import Session, joinedload
import util
import models
from sqlalchemy.orm import Session
from database import models
import schemas
import logging

Expand Down
99 changes: 17 additions & 82 deletions wg_dashboard_backend/main.py
Original file line number Diff line number Diff line change
@@ -1,99 +1,22 @@
import logging
import os
import const
import time

import typing
from sqlalchemy_utils import database_exists
from starlette.middleware.base import BaseHTTPMiddleware

import const
import db.wireguard
import db.api_key
import middleware
from database import engine, SessionLocal
from routers.v1 import user, server, peer, wg
import script.wireguard
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
if not logger.hasHandlers():
sh = logging.StreamHandler()
fmt = logging.Formatter(fmt="%(asctime)s %(name)-12s %(levelname)-8s %(message)s")
sh.setFormatter(fmt)
logger.addHandler(sh)
import script.wireguard_startup
import pkg_resources
import uvicorn as uvicorn
from fastapi.staticfiles import StaticFiles
from sqlalchemy.orm import Session

from starlette.responses import FileResponse
from fastapi import Depends, FastAPI
from const import DATABASE_URL
from migrate import DatabaseAlreadyControlledError
from migrate.versioning.shell import main
import models

# Sleep the wait timer.
time.sleep(const.INIT_SLEEP)
import database.util

app = FastAPI()
app.add_middleware(BaseHTTPMiddleware, dispatch=middleware.db_session_middleware)

_db: Session = SessionLocal()

# Ensure database existence

if not database_exists(engine.url):
ADMIN_USERNAME = os.getenv("ADMIN_USERNAME")
if not ADMIN_USERNAME:
raise RuntimeError("Database does not exist and the environment variable ADMIN_USERNAME is set")

ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD")

if not ADMIN_PASSWORD:
raise RuntimeError("Database does not exist and the environment variable ADMIN_PASSWORD is set")

# Create database from metadata
models.Base.metadata.create_all(engine)

# Create default user
_db.merge(models.User(
username=ADMIN_USERNAME,
password=middleware.get_password_hash(ADMIN_PASSWORD),
full_name="Admin",
role="admin",
email=""
))
_db.commit()


# Do migrations
try:
main(["version_control", DATABASE_URL, "migrations"])
except DatabaseAlreadyControlledError:
pass
main(["upgrade", DATABASE_URL, "migrations"])


servers: typing.List[models.WGServer] = _db.query(models.WGServer).all()
for s in servers:
try:
last_state = s.is_running
if script.wireguard.is_installed() and last_state and not script.wireguard.is_running(s):
script.wireguard.start_interface(s)
except Exception as e:
print(e)

if const.CLIENT:
script.wireguard.load_environment_clients(_db)

if const.SERVER_INIT_INTERFACE is not None:
db.wireguard.server_add_on_init(_db)

if const.SERVER_STARTUP_API_KEY is not None:
ADMIN_USERNAME = os.getenv("ADMIN_USERNAME")
db.api_key.add_initial_api_key_for_admin(_db, const.SERVER_STARTUP_API_KEY, ADMIN_USERNAME)
_db.close()


# Configure web routers
app.include_router(
user.router,
prefix="/api/v1",
Expand Down Expand Up @@ -149,4 +72,16 @@ async def shutdown():


if __name__ == "__main__":
# Sleep the wait timer.
time.sleep(const.INIT_SLEEP)

# Ensure database existence
database.util.setup_initial_database()

# Perform Migrations
database.util.perform_migrations()

# Configure wireguard
script.wireguard_startup.setup_on_start()

uvicorn.run("__main__:app", reload=True)
5 changes: 2 additions & 3 deletions wg_dashboard_backend/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
from starlette.responses import Response

import const
import models
import schemas
from database import SessionLocal
import db.user
from database import models
from database.database import SessionLocal

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/login", auto_error=False)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
Expand Down
5 changes: 1 addition & 4 deletions wg_dashboard_backend/migrations/README
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
This is a database migration repository.

More information at
http://code.google.com/p/sqlalchemy-migrate/
Generic single-database configuration.
Loading

0 comments on commit e5100f0

Please sign in to comment.