Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix freezing-web docker-compose setup #316

Merged
merged 14 commits into from
Dec 31, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*.pyc
*.tmp
.env*
.gitkeep
.venv*
/*.iml
/build
Expand Down
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 24.10.0
hooks:
- id: black
- repo: https://github.com/jackdewinter/pymarkdown
rev: v0.9.26
hooks:
- id: pymarkdown
args:
- "-d MD013 scan ."
4 changes: 2 additions & 2 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ members of the project's leadership.
## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html)

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq)
196 changes: 111 additions & 85 deletions README.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion bin/fmt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ echo "*** isort ***"
isort freezing
echo "*** djlint ***"
djlint --reformat freezing/web/templates

1 change: 0 additions & 1 deletion bin/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@ flake8 freezing
echo "*** mypy ***"
echo "*** djlint ***"
djlint --check freezing/web/templates

File renamed without changes.
67 changes: 35 additions & 32 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,55 +1,58 @@
---
version: '3'

volumes:
freezing-data:
external: true

services:

freezing-db:
image: mysql:5.7
container_name: freezing-db
image: mysql:8.0
container_name: freezing-db-dev
ports:
- "3306:3306"
- "${MYSQL_PORT:-3306}:3306"
volumes:
- freezing-data:/var/lib/mysql
- ./docker/db/sql-scripts:/sql
- ./data/mysql/data:/var/lib/mysql
- ./data/mysql/sql:/sql
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-fr33z3}
MYSQL_DATABASE: ${MYSQL_DATABASE:-freezing}
MYSQL_USER: ${MYSQL_USER:-freezing}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-zeer0}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-fr33z3}
MYSQL_ROOT_USER: ${MYSQL_ROOT_USER:-root}
MYSQL_USER: ${MYSQL_USER:-freezing}

freezing-web:
build: .
container_name: freezing-web
command: bafs-server
build: ./
container_name: freezing-web-dev
depends_on:
- freezing-db
ports:
- "5000:8000"
- "${FREEZING_WEB_PORT:-8000}:8000"
volumes:
- ./data/cache:/data/cache
- ./leaderboards:/data/leaderboards
- ./data/sessions:/data/sessions
- ./resources/docker/settings.cfg:/config/settings.cfg
links:
- freezing-db:freezing-db.container
environment:
BEANSTALKD_HOST: ${BEANSTALKD_HOST:-beanstalkd.invalid}
BEANSTALKD_PORT: ${BEANSTALKD_PORT:-11300}
BIND_INTERFACE: "0.0.0.0"
VIRTUAL_HOST: ${FREEZING_WEB_FQDN}
LETSENCRYPT_HOST: ${FREEZING_WEB_FQDN}
LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}
SECRET_KEY: ${SECRET_KEY}
DEBUG: ${DEBUG}
SQLALCHEMY_URL: ${SQLALCHEMY_URL:-mysql+pymysql://freezing:[email protected]/freezing?charset=utf8mb4&binary_prefix=true}
BEANSTALKD_HOST: beanstalkd.container
BEANSTALKD_PORT: 11300
STRAVA_CLIENT_ID: ${STRAVA_CLIENT_ID}
STRAVA_CLIENT_SECRET: ${STRAVA_CLIENT_SECRET}
TEAMS: ${TEAMS}
OBSERVER_TEAMS: ${OBSERVER_TEAMS}
MAIN_TEAM: ${MAIN_TEAM}
START_DATE: ${START_DATE}
END_DATE: ${END_DATE}
TIMEZONE: ${TIMEZONE:-America/New_York}
COMPETITION_TITLE: ${COMPETITION_TITLE:-Freezing Saddles Local Dev}
DEBUG: ${DEBUG:-True}
END_DATE: ${END_DATE:-2118-03-20T00:01:00-04:00}
ENVIRONMENT: ${ENVIRONMENT:-localdev}
FAULTHANDLER: ${FAULT_HANDLER:-True}
FORUM_SITE: https://www.bikearlingtonforum.com/forums/forum/freezing-saddles-winter-riding-competition/ # yamllint disable-line
LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL:[email protected]}
LETSENCRYPT_HOST: ${FREEZING_WEB_FQDN:-localhost.localdomain}
LOG_LEVEL: ${LOG_LEVEL:-DEBUG}
MAIN_TEAM: ${MAIN_TEAM:-324147}
OBSERVER_TEAMS: ${OBSERVER_TEAMS:-5678,9013}
REGISTRATION_SITE: ${REGISTRATION_SITE:-https://freezingsaddles.info/}
SECRET_KEY: ${SECRET_KEY:-e6c07402-0307-11e8-b087-000000000000} # yamllint disable-line
SQLALCHEMY_URL: "mysql+pymysql://${MYSQL_USER:-freezing}:${MYSQL_PASSWORD:-zeer0}@freezing-db-dev/${MYSQL_DATABASE:-freezing}?charset=utf8mb4&binary_prefix=true" # yamllint disable-line
SQLALCHEMY_URL_ROOT: "mysql+pymysql://${MYSQL_ROOT_USER:-freezing}:${MYSQL_ROOT_PASSWORD:-zeer0}@freezing-db-dev/mysql?charset=utf8mb4&binary_prefix=true" # yamllint disable-line
START_DATE: ${START_DATE:-2018-01-01T00:00:00-05:00}
STRAVA_CLIENT_ID: ${STRAVA_CLIENT_ID:-?}
STRAVA_CLIENT_SECRET: ${STRAVA_CLIENT_SECRET:-?}
TEAMS: ${TEAMS:-1234,1235}
TIMEZONE: ${TIMEZONE:-America/New_York}
VIRTUAL_HOST: ${FREEZING_WEB_FQDN:-localhost.localdomain}
41 changes: 27 additions & 14 deletions example.cfg
Original file line number Diff line number Diff line change
@@ -1,44 +1,57 @@
# You can use a file containing environment vars like this:
# APP_SETTINGS=/path/to/envfile.cfg freezing-server
#
# See freezing/webapp/config.py for more information on these settings.

# Suitable same-machine testing. Set this to 0.0.0.0 to allow connections from other machines.
BIND_INTERFACE=127.0.0.1

DEBUG=true

# By default this uses the localdev environment for local development, which enables
# several features useful for developers, such as user impersonation.
# See
#ENVIRONMENT=localdev

# The SECRET_KEY is used by Flask to sign sessions. Set this to something else.
SECRET_KEY=e6c07402-0307-11e8-b087-000000000000

# The URL to the database. Note that the pymysql driver must be explicitly specified.
SQLALCHEMY_URL=mysql+pymysql://freezing:[email protected]/freezing?charset=utf8mb4&binary_prefix=true
#
# It is important to use the utf8mb4 charset to get full support for Unicode characters,
# including emoji.
#
# This URL is suitable for use with the database running in the freezing-compose setup in this repository.
SQLALCHEMY_URL="mysql+pymysql://freezing:zeer0mfreezing-db-dev/freezing?charset=utf8mb4&binary_prefix=true"
# Use this one if you are running this on your host vs. the database in the
# https://github.com/freezingsaddles/freezing-compose project.
# NOTE: If you are using a MySQL docker via freezing-compose use 127.0.0.1 as the host, NOT localhost
#SQLALCHEMY_URL="mysql+pymysql://freezing:[email protected]/freezing?charset=utf8mb4&binary_prefix=true"
# If you keep your MySQL database somewhere else, fix this up to match.
#SQLALCHEMY_URL="mysql+pymysql://freezing:[email protected]/freezing?charset=utf8mb4&binary_prefix=true""

# Configuration for the Strava client. These settings come from your App setup.
# Setting this is only required if you are testing parts of this application that exercise the Strava API,
# such as user registration. That is an advanced topic and not required to make most changes to
# the web site. Most of the action with the Strava API happens in freezing-sync, not here.
STRAVA_CLIENT_ID=?
STRAVA_CLIENT_SECRET=?
STRAVA_ACTIVITY_CACHE_DIR=/path/to/cache/activities

WUNDERGROUND_API_KEY=?
WUNDERGROUND_CACHE_DIR=/path/to/cache/wunderground

# Main team ID that everyone should join
MAIN_TEAM=324147
# Comma-separated list of Strava Clubs that are the participating teams.
TEAMS=1234,1235

# Comma-separated list of teams that should be included for overall stats but are not "playing the game"
OBSERVER_TEAMS=5678,9013

# Main team ID that everyone should join
MAIN_TEAM=324147

# The competition title
COMPETITION_TITLE='BikeArlington Freezing Saddles 2019'
COMPETITION_TITLE='BikeArlington Freezing Saddles 2018 - localdev'

# The start date of the competition -- WITH TIME ZONE
START_DATE=2018-01-01T00:00:00-05:00

# The end date of the competition -- WITH TIME ZONE. The sync will stop fetching rides after this date (plus grace
# period)
# The end date of the competition -- WITH TIME ZONE.
END_DATE=2018-03-20T00:01:00-04:00

# Related web sites
REGISTRATION_SITE=https://freezingsaddles.info/
FORUM_SITE=https://www.bikearlingtonforum.com/forums/forum/freezing-saddles-winter-riding-competition/
REGISTRATION_SITE=https://freezingsaddles.info/
137 changes: 98 additions & 39 deletions freezing/web/__init__.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,74 @@
"""
This is the main entry point for the freezing-web application.

It sets up the Flask app and initializes the database and logging.
It also sets up a fault handler with a signal early to ensure stack traces happen.
"""

import freezing.web._faulthandler # noqa isort: skip

from socket import gethostbyname
from time import sleep
from urllib.parse import urlparse

from flask import Flask, g, session
from freezing.model import init_model, meta

from .config import config

# Thanks https://stackoverflow.com/a/17073583
app = Flask(
__name__,
static_folder="static",
static_url_path="/",
instance_path=config.INSTANCE_PATH,
)
app.config.from_object(config)

init_model(config.SQLALCHEMY_URL)

# This needs to be after the app is created, unfortunately.
from freezing.web.views import ( # noqa
alt_scoring,
api,
chartdata,
general,
leaderboard,
people,
photos,
pointless,
tribes,
user,
)

# Register our blueprints

app.register_blueprint(general.blueprint)
app.register_blueprint(leaderboard.blueprint, url_prefix="/leaderboard")
app.register_blueprint(alt_scoring.blueprint, url_prefix="/alt_scoring")
app.register_blueprint(chartdata.blueprint, url_prefix="/chartdata")
app.register_blueprint(people.blueprint, url_prefix="/people")
app.register_blueprint(pointless.blueprint, url_prefix="/pointless")
app.register_blueprint(photos.blueprint, url_prefix="/photos")
app.register_blueprint(user.blueprint, url_prefix="/my")
app.register_blueprint(api.blueprint, url_prefix="/api")
app.register_blueprint(tribes.blueprint, url_prefix="/tribes")
from freezing.web.autolog import log

from .config import config, init_logging

init_logging(color=config.DEBUG is False)


_BANNER = "*************************"


def _get_app():
log.info(
f"{_BANNER} Configuring Flask app with instance_path={config.INSTANCE_PATH} {_BANNER}"
)
# Thanks https://stackoverflow.com/a/17073583
app = Flask(
__name__,
static_folder="static",
static_url_path="/",
instance_path=config.INSTANCE_PATH,
)
app.config.from_object(config)
return app


def _register_blueprints(app):
# This needs to be after the app is created, unfortunately.
from freezing.web.views import (
alt_scoring,
api,
chartdata,
general,
leaderboard,
people,
photos,
pointless,
tribes,
user,
)

app.register_blueprint(general.blueprint)
app.register_blueprint(leaderboard.blueprint, url_prefix="/leaderboard")
app.register_blueprint(alt_scoring.blueprint, url_prefix="/alt_scoring")
app.register_blueprint(chartdata.blueprint, url_prefix="/chartdata")
app.register_blueprint(people.blueprint, url_prefix="/people")
app.register_blueprint(pointless.blueprint, url_prefix="/pointless")
app.register_blueprint(photos.blueprint, url_prefix="/photos")
app.register_blueprint(user.blueprint, url_prefix="/my")
app.register_blueprint(api.blueprint, url_prefix="/api")
app.register_blueprint(tribes.blueprint, url_prefix="/tribes")


# This has to be done before we define the functions with @app decorators
app = _get_app()
_register_blueprints(app)


@app.before_request
Expand Down Expand Up @@ -69,3 +98,33 @@ def inject_config():
"forum_site": config.FORUM_SITE,
"version_string": config.VERSION_STRING,
}


def init_db():
"""
Initialize the database. If the database is not available, keep trying for a bit.
"""
TRIES = 6
delay = 2
for x in range(1, TRIES + 1):
try:
# Use urllib to extract the host and test whether it can be resolved
url = urlparse(config.SQLALCHEMY_URL)
host = gethostbyname(url.hostname)
log.debug(f"gethostbyname({url.hostname})=={host}")
log.info(f"{_BANNER} Connecting to database on {url.hostname} {_BANNER}")
init_model(config.SQLALCHEMY_URL)
break
except Exception as ex:
if x == TRIES:
raise ex from None
log.warning(
f"Failed to connect to database, retrying in {delay}s ({x}) - error was {str(ex)}"
)
delay = delay * 2
sleep(delay)


init_db()

log.info(f"{_BANNER} freezing-web initialized in {config.ENVIRONMENT} mode {_BANNER}")
6 changes: 6 additions & 0 deletions freezing/web/_faulthandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import faulthandler
import signal

# Register this super early so we can get a stack trace if
# there is a problem in initializing the config module
faulthandler.register(signal.SIGUSR1)
Loading
Loading