mirror of
https://github.com/fergalmoran/bitchmin.git
synced 2025-12-22 09:27:53 +00:00
Basic scheduling working
This commit is contained in:
6
client/.nginx/default.conf
Normal file
6
client/.nginx/default.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
server {
|
||||
location / {
|
||||
root /var/www/html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
23
client/.nginx/nginx.conf
Normal file
23
client/.nginx/nginx.conf
Normal file
@@ -0,0 +1,23 @@
|
||||
user nginx;
|
||||
worker_processes 1;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile off;
|
||||
|
||||
keepalive_timeout 60;
|
||||
|
||||
#gzip on;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
@@ -2,26 +2,31 @@ import logging
|
||||
import os
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
from celery import Celery
|
||||
from flask import Flask
|
||||
from flask_cors import CORS
|
||||
from flask_jwt_extended import JWTManager, verify_jwt_in_request_optional, get_jwt_identity
|
||||
from flask_jwt_extended import JWTManager
|
||||
from flask_mail import Mail
|
||||
from flask_migrate import Migrate
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
from app.utils.encoders import IPAddressFieldEncoder
|
||||
|
||||
from app.config import Config
|
||||
from app.utils.encoders import IPAddressFieldEncoder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
db = SQLAlchemy()
|
||||
jwt = JWTManager()
|
||||
mail = Mail()
|
||||
|
||||
migrate = Migrate()
|
||||
|
||||
CELERY_TASK_LIST = [
|
||||
'app.tasks.hosts',
|
||||
]
|
||||
|
||||
|
||||
def create_app(app_name='bitchmin', config_class=Config):
|
||||
|
||||
logger.info('Creating app {}'.format(app_name))
|
||||
app = Flask(app_name)
|
||||
app.config.from_object(config_class)
|
||||
@@ -31,6 +36,7 @@ def create_app(app_name='bitchmin', config_class=Config):
|
||||
jwt.init_app(app)
|
||||
logger.info('Performing migrations')
|
||||
migrate.init_app(app, db)
|
||||
mail.init_app(app)
|
||||
|
||||
# cors = CORS(app, resources={r"/*": {"origins": "*"}})
|
||||
CORS(app, supports_credentials=True)
|
||||
@@ -66,3 +72,28 @@ def create_app(app_name='bitchmin', config_class=Config):
|
||||
db.init_app(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def create_celery_app(app=None):
|
||||
app = app or create_app()
|
||||
|
||||
celery = Celery(
|
||||
app.import_name,
|
||||
broker=app.config['CELERY_BROKER_URL'],
|
||||
include=CELERY_TASK_LIST)
|
||||
|
||||
celery.conf.update(app.config)
|
||||
TaskBase = celery.Task
|
||||
|
||||
class ContextTask(TaskBase):
|
||||
abstract = True
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
with app.app_context():
|
||||
return TaskBase.__call__(self, *args, **kwargs)
|
||||
|
||||
celery.Task = ContextTask
|
||||
return celery
|
||||
|
||||
|
||||
celery = create_celery_app()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
@@ -17,6 +19,23 @@ class Config(object):
|
||||
ADMINS = ['Ferg@lMoran.me']
|
||||
LANGUAGES = ['en', 'ie']
|
||||
|
||||
CELERY_BROKER_URL = os.getenv('CELERY_BROKER_URL')
|
||||
CELERY_RESULT_BACKEND = os.getenv('CELERY_RESULT_BACKEND')
|
||||
|
||||
MAIL_SERVER = os.getenv('MAIL_SERVER')
|
||||
MAIL_PORT = os.getenv('MAIL_PORT')
|
||||
MAIL_USE_TLS = os.getenv('MAIL_USE_TLS')
|
||||
MAIL_USE_SSL = os.getenv('MAIL_USE_SSL')
|
||||
MAIL_USERNAME = os.getenv('MAIL_USERNAME')
|
||||
MAIL_PASSWORD = os.getenv('MAIL_PASSWORD')
|
||||
|
||||
CELERYBEAT_SCHEDULE = {
|
||||
'add-every-15-minutes': {
|
||||
'task': 'app.tasks.hosts.check_host_records',
|
||||
'schedule': timedelta(minutes=15)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ProductionConfig(Config):
|
||||
DEBUG = False
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
from .dnsupdate import DnsUpdate
|
||||
from .user import User
|
||||
from .bindstate import BindState
|
||||
18
server/app/models/bindstate.py
Normal file
18
server/app/models/bindstate.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from sqlalchemy_utils import IPAddressType
|
||||
|
||||
from app import db
|
||||
from app.models._basemodel import _BaseModelMixin
|
||||
|
||||
"""
|
||||
This is the state (as best we can determine)
|
||||
of the BIND server. Most important is to store the IP for ns1.bitchmints.com
|
||||
and check if this has changed on every run
|
||||
"""
|
||||
|
||||
|
||||
class BindState(db.Model, _BaseModelMixin):
|
||||
__tablename__ = 'bind_state'
|
||||
|
||||
nameserver_1_ip = db.Column(IPAddressType(255), nullable=False)
|
||||
nameserver_1_host = db.Column(db.String(255), unique=True, nullable=False)
|
||||
|
||||
@@ -19,7 +19,7 @@ class DnsUpdate(db.Model, _BaseModelMixin):
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
host = db.Column(db.String(120), unique=True, nullable=False)
|
||||
host = db.Column(db.String(255), unique=True, nullable=False)
|
||||
ip = db.Column(IPAddressType(255), nullable=False)
|
||||
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||
|
||||
1
server/app/tasks/__init__.py
Normal file
1
server/app/tasks/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .hosts import check_host_records
|
||||
88
server/app/tasks/hosts.py
Normal file
88
server/app/tasks/hosts.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from ipaddress import ip_address
|
||||
|
||||
from flask_mail import Message
|
||||
import os
|
||||
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
from app import create_celery_app, mail, db
|
||||
from app.models import DnsUpdate, BindState, dnsupdate
|
||||
import logging
|
||||
from app.utils import dnsupdate, iputils
|
||||
from app.utils.twilio import send_sms
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
celery = create_celery_app()
|
||||
|
||||
|
||||
@celery.task
|
||||
def check_host_records():
|
||||
platform_ip = iputils.get_my_external_ip()
|
||||
logger.debug('Current platform ip: {}'.format(platform_ip))
|
||||
|
||||
try:
|
||||
logger.info('Checking for existing state')
|
||||
bind_state = BindState.query.one()
|
||||
except NoResultFound:
|
||||
bind_state = BindState(
|
||||
nameserver_1_ip=platform_ip,
|
||||
nameserver_1_host=os.getenv('DNS_SERVER')
|
||||
)
|
||||
db.session.add(bind_state)
|
||||
db.session.commit()
|
||||
|
||||
previous_ip = bind_state.nameserver_1_ip
|
||||
if ip_address(platform_ip) != previous_ip:
|
||||
logger.info('External IP has changed')
|
||||
bind_state.nameserver_1_ip = platform_ip
|
||||
|
||||
logger.info('Updating nameserver record')
|
||||
dnsupdate.update_dns(
|
||||
os.getenv('DNS_SERVER'),
|
||||
os.getenv('DNS_ZONE'),
|
||||
os.getenv('DNS_KEY'),
|
||||
bind_state.nameserver_1_host,
|
||||
platform_ip
|
||||
)
|
||||
|
||||
logger.info('Checking host records')
|
||||
hosts = DnsUpdate.query.filter_by(ip=previous_ip)
|
||||
result = 'Here are the hosts we have\n'
|
||||
for host in hosts:
|
||||
result += '\tHost: {} IP: {} Date Updated: {}'.format(
|
||||
host.host,
|
||||
host.ip,
|
||||
host.updated_on
|
||||
)
|
||||
dnsupdate.update_dns(
|
||||
os.getenv('DNS_SERVER'),
|
||||
os.getenv('DNS_ZONE'),
|
||||
os.getenv('DNS_KEY'),
|
||||
host.host,
|
||||
platform_ip
|
||||
)
|
||||
host.ip = platform_ip
|
||||
|
||||
logger.info('Saving host details')
|
||||
db.session.commit()
|
||||
|
||||
result = 'End of hosts'
|
||||
logger.debug(result)
|
||||
logger.info('Sending mail')
|
||||
try:
|
||||
send_sms(
|
||||
os.getenv('SMS_NOTIFY_TO'),
|
||||
os.getenv('SMS_NOTIFY_FROM'),
|
||||
'IP address for {} changed from {} to {}'.format(
|
||||
bind_state.nameserver_1_host,
|
||||
previous_ip,
|
||||
platform_ip
|
||||
)
|
||||
)
|
||||
|
||||
logger.info('Mail sent successfully')
|
||||
except Exception as e:
|
||||
logger.error('Unable to send mail report')
|
||||
logger.error(e)
|
||||
else:
|
||||
logger.info('No IP changes detected')
|
||||
@@ -1,6 +1,6 @@
|
||||
import ipaddress
|
||||
import re
|
||||
|
||||
import requests
|
||||
import sys
|
||||
|
||||
|
||||
@@ -24,3 +24,8 @@ def is_valid_hostname(hostname):
|
||||
hostname = hostname[:-1] # strip exactly one dot from the right, if present
|
||||
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
|
||||
return all(allowed.match(x) for x in hostname.split("."))
|
||||
|
||||
|
||||
def get_my_external_ip():
|
||||
ip = requests.get('http://ipinfo.io/json').json()['ip']
|
||||
return ip
|
||||
|
||||
16
server/app/utils/twilio.py
Normal file
16
server/app/utils/twilio.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from twilio.rest import Client
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def send_sms(number_to, number_from, message):
|
||||
client = Client(os.getenv('TWILIO_ACCOUNT_SID'), os.getenv('TWILIO_AUTH_TOKEN'))
|
||||
logger.debug('Sending SMS')
|
||||
message = client.messages.create(
|
||||
to=number_to,
|
||||
from_=number_from,
|
||||
body=message
|
||||
)
|
||||
logger.debug(message)
|
||||
36
server/migrations/versions/7de781bdfffe_added_bind_state.py
Normal file
36
server/migrations/versions/7de781bdfffe_added_bind_state.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""Added Bind State
|
||||
|
||||
Revision ID: 7de781bdfffe
|
||||
Revises: e4b3d04da7fc
|
||||
Create Date: 2020-09-04 21:47:57.400947
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7de781bdfffe'
|
||||
down_revision = 'e4b3d04da7fc'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('bind_state',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_on', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||
sa.Column('updated_on', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||
sa.Column('nameserver_1_ip', sqlalchemy_utils.types.ip_address.IPAddressType(length=255), nullable=False),
|
||||
sa.Column('nameserver_1_host', sa.String(length=255), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('nameserver_1_host')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('bind_state')
|
||||
# ### end Alembic commands ###
|
||||
@@ -3,6 +3,7 @@ Flask-Cors
|
||||
Flask-SQLAlchemy
|
||||
Flask_Migrate
|
||||
flask_jwt_extended
|
||||
Flask-Mail
|
||||
sqlalchemy_utils
|
||||
phue
|
||||
python-dotenv
|
||||
@@ -11,6 +12,9 @@ psycopg2-binary==2.8.5
|
||||
Psycopg2
|
||||
pylint
|
||||
werkzeug
|
||||
|
||||
celery
|
||||
redis
|
||||
flower
|
||||
requests
|
||||
IPy
|
||||
pydig
|
||||
9
server/start_with_jobs.sh
Executable file
9
server/start_with_jobs.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Run Celery worker
|
||||
celery -A app.celery worker --loglevel=DEBUG --detach --pidfile=''
|
||||
|
||||
# Run Celery Beat
|
||||
celery -A app.celery beat --loglevel=DEBUG --detach --pidfile=''
|
||||
|
||||
flask run
|
||||
Reference in New Issue
Block a user