Merge branch 'docker_immutable_feature' into pre-release

This commit is contained in:
João Vitória Silva
2025-06-25 11:20:16 +01:00
20 changed files with 47 additions and 92 deletions

View File

@@ -1,4 +1,3 @@
import os
from zoneinfo import ZoneInfo
from datetime import datetime
@@ -7,6 +6,8 @@ import activities.activity_laps.schema as activity_laps_schema
import activities.activity.schema as activities_schema
import core.config as core_config
def serialize_activity_lap(activity: activities_schema.Activity, activity_lap: activity_laps_schema.ActivityLaps):
def make_aware_and_format(dt, timezone):
if isinstance(dt, str):
@@ -18,7 +19,7 @@ def serialize_activity_lap(activity: activities_schema.Activity, activity_lap: a
timezone = (
ZoneInfo(activity.timezone)
if activity.timezone
else ZoneInfo(os.environ.get("TZ"))
else ZoneInfo(core_config.TZ)
)
activity_lap.start_time = make_aware_and_format(activity_lap.start_time, timezone)

View File

@@ -7,6 +7,8 @@ import activities.activity_sets.schema as activity_sets_schema
import activities.activity.schema as activities_schema
import core.config as core_config
def serialize_activity_set(activity: activities_schema.Activity, activity_set: activity_sets_schema.ActivitySets):
def make_aware_and_format(dt, timezone):
if isinstance(dt, str):
@@ -18,7 +20,7 @@ def serialize_activity_set(activity: activities_schema.Activity, activity_set: a
timezone = (
ZoneInfo(activity.timezone)
if activity.timezone
else ZoneInfo(os.environ.get("TZ"))
else ZoneInfo(core_config.TZ)
)
activity_set.start_time = make_aware_and_format(activity_set.start_time, timezone)

View File

@@ -9,30 +9,17 @@ LICENSE_IDENTIFIER = "AGPL-3.0-or-later"
LICENSE_URL = "https://spdx.org/licenses/AGPL-3.0-or-later.html"
ROOT_PATH = "/api/v1"
FRONTEND_DIR = "/app/frontend/dist"
ENVIRONMENT = os.getenv("ENVIRONMENT")
ENVIRONMENT = os.getenv("ENVIRONMENT", "production")
TZ = os.getenv("TZ", "UTC")
def check_required_env_vars():
required_env_vars = [
"TZ",
"DB_TYPE",
"DB_HOST",
"DB_PORT",
"DB_USER",
"DB_PASSWORD",
"DB_DATABASE",
"SECRET_KEY",
"FERNET_KEY",
"ALGORITHM",
"ACCESS_TOKEN_EXPIRE_MINUTES",
"REFRESH_TOKEN_EXPIRE_DAYS",
"JAEGER_ENABLED",
"JAEGER_PROTOCOL",
"JAEGER_HOST",
"JAGGER_PORT",
"ENDURAIN_HOST",
"GEOCODES_MAPS_API",
"ENVIRONMENT",
]
for var in required_env_vars:

View File

@@ -5,34 +5,25 @@ from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.engine.url import URL
# Helper to fetch environment variables
def get_env_variable(var_name: str) -> str:
value = os.environ.get(var_name)
if value is None:
raise EnvironmentError(f"Environment variable {var_name} is not set")
return value
# Fetch the database type (e.g., mariadb or postgresql)
db_type = get_env_variable("DB_TYPE").lower()
db_type = os.environ.get("DB_TYPE", "postgres").lower()
# Define supported database drivers
supported_drivers = {
"mariadb": "mysql+mysqldb",
"postgres": "postgresql+psycopg"
}
supported_drivers = {"mariadb": "mysql+mysqldb", "postgres": "postgresql+psycopg"}
if db_type not in supported_drivers:
raise ValueError(f"Unsupported DB_TYPE: {db_type}. Supported types are {list(supported_drivers.keys())}")
raise ValueError(
f"Unsupported DB_TYPE: {db_type}. Supported types are {list(supported_drivers.keys())}"
)
# Define the database connection URL using environment variables
db_url = URL.create(
drivername=supported_drivers[db_type],
username=get_env_variable("DB_USER"),
password=get_env_variable("DB_PASSWORD"),
host=get_env_variable("DB_HOST"),
port=get_env_variable("DB_PORT"),
database=get_env_variable("DB_DATABASE"),
username=os.environ.get("DB_USER", "endurain"),
password=os.environ.get("DB_PASSWORD"),
host=os.environ.get("DB_HOST", "postgres"),
port=os.environ.get("DB_PORT", "5432"),
database=os.environ.get("DB_DATABASE", "endurain"),
)
# Create the SQLAlchemy engine

View File

@@ -9,7 +9,7 @@ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
def setup_tracing(app):
# Check if Jaeger tracing is enabled using the 'JAEGER_ENABLED' environment variable
if os.environ.get("JAEGER_ENABLED") == "true":
if os.environ.get("JAEGER_ENABLED", "true") == "true":
# Configure OpenTelemetry with a specified service name
trace.set_tracer_provider(
TracerProvider(resource=Resource.create({"service.name": "backend_api"}))
@@ -17,11 +17,11 @@ def setup_tracing(app):
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(
endpoint=os.environ.get("JAEGER_PROTOCOL")
endpoint=os.environ.get("JAEGER_PROTOCOL", "http")
+ "://"
+ os.environ.get("JAEGER_HOST")
+ os.environ.get("JAEGER_HOST", "jaeger")
+ ":"
+ os.environ.get("JAGGER_PORT")
+ os.environ.get("JAEGER_PORT", "4317")
)
)
)

View File

@@ -26,6 +26,8 @@ import users.user_privacy_settings.schema as users_privacy_settings_schema
import core.logger as core_logger
import core.config as core_config
def create_activity_objects(
sessions_records: dict,
@@ -38,7 +40,7 @@ def create_activity_objects(
try:
# Create an instance of TimezoneFinder
tf = TimezoneFinder()
timezone = os.environ.get("TZ")
timezone = core_config.TZ
# Define variables
gear_id = None

View File

@@ -1,6 +1,4 @@
import gpxpy
import gpxpy.gpx
import os
from geopy.distance import geodesic
from timezonefinder import TimezoneFinder
from sqlalchemy.orm import Session
@@ -16,6 +14,7 @@ import users.user_default_gear.utils as user_default_gear_utils
import users.user_privacy_settings.schema as users_privacy_settings_schema
import core.logger as core_logger
import core.config as core_config
def parse_gpx_file(
@@ -27,7 +26,7 @@ def parse_gpx_file(
try:
# Create an instance of TimezoneFinder
tf = TimezoneFinder()
timezone = os.environ.get("TZ")
timezone = core_config.TZ
# Initialize default values for various variables
activity_type = "Workout"

View File

@@ -11,6 +11,7 @@ import migrations.crud as migrations_crud
import health_data.crud as health_data_crud
import core.logger as core_logger
import core.config as core_config
def process_migration_2(db: Session):
@@ -47,7 +48,7 @@ def process_migration_2(db: Session):
)
continue
timezone = os.environ.get("TZ")
timezone = core_config.TZ
# Get activity stream
try:

View File

@@ -1,9 +1,9 @@
import os
# JWT Token constants
JWT_ALGORITHM = os.environ.get("ALGORITHM")
JWT_ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES"))
JWT_REFRESH_TOKEN_EXPIRE_DAYS = int(os.environ.get("REFRESH_TOKEN_EXPIRE_DAYS"))
JWT_ALGORITHM = os.environ.get("ALGORITHM", "HS256")
JWT_ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES", "15"))
JWT_REFRESH_TOKEN_EXPIRE_DAYS = int(os.environ.get("REFRESH_TOKEN_EXPIRE_DAYS", "7"))
JWT_SECRET_KEY = os.environ.get("SECRET_KEY")
# Scopes definition

View File

@@ -1,5 +1,3 @@
import os
from fastapi import HTTPException, status
from datetime import datetime, timedelta, timezone
from sqlalchemy.orm import Session
@@ -8,6 +6,7 @@ from stravalib.exc import AccessUnauthorized
from timezonefinder import TimezoneFinder
import core.logger as core_logger
import core.config as core_config
import activities.activity.schema as activities_schema
import activities.activity.crud as activities_crud
@@ -131,7 +130,7 @@ def parse_activity(
) -> dict:
# Create an instance of TimezoneFinder
tf = TimezoneFinder()
timezone = os.environ.get("TZ")
timezone = core_config.TZ
# Get the detailed activity
try:

View File

@@ -62,21 +62,7 @@ RUN set -eux; \
# Define environment variables
ENV UID=1000 \
GID=1000 \
TZ="UTC" \
DB_TYPE="postgres" \
DB_HOST="postgres" \
DB_PORT=5432 \
DB_USER="endurain" \
DB_DATABASE="endurain" \
ALGORITHM="HS256" \
ACCESS_TOKEN_EXPIRE_MINUTES=15 \
REFRESH_TOKEN_EXPIRE_DAYS=7 \
JAEGER_ENABLED="false" \
JAEGER_HOST="jaeger" \
JAEGER_PROTOCOL="http" \
JAGGER_PORT=4317 \
BEHIND_PROXY=false \
ENVIRONMENT="production"
BEHIND_PROXY=false
# Set the working directory to /app/frontend
WORKDIR /app/frontend

View File

@@ -19,30 +19,14 @@ validate_id() {
esac
}
adjust_folder_ownership() {
if [ -d "$1" ]; then
chown -R "$UID:$GID" "$1"
echo_info_log "Ownership adjusted for $1"
else
echo_info_log "Directory $1 does not exist, skipping."
fi
}
validate_id "$UID"
validate_id "$GID"
echo_info_log "UID=$UID, GID=$GID"
# List of directories (space-separated for POSIX shell)
directories="/app/backend/logs /app/backend/user_images /app/backend/files /app/backend/server_images"
for dir in $directories; do
adjust_folder_ownership "$dir"
done
if [ -n "$ENDURAIN_HOST" ]; then
echo_info_log "Substituting MY_APP_ENDURAIN_HOST with $ENDURAIN_HOST"
find /app/frontend/dist -type f \( -name '*.js' -o -name '*.css' \) -exec sed -i "s|MY_APP_ENDURAIN_HOST|$ENDURAIN_HOST|g" {} +
echo "window.env = { ENDURAIN_HOST: \"$ENDURAIN_HOST\" };" > /app/frontend/dist/env.js
echo_info_log "Runtime env.js written with ENDURAIN_HOST=$ENDURAIN_HOST"
fi
echo_info_log "Starting FastAPI with BEHIND_PROXY=$BEHIND_PROXY"

View File

@@ -35,7 +35,7 @@ Environment variable | Default value | Optional | Notes |
| JAEGER_ENABLED | false | Yes | N/A |
| JAEGER_PROTOCOL | http | Yes | N/A |
| JAEGER_HOST | jaeger | Yes | N/A |
| JAGGER_PORT | 4317 | Yes | N/A |
| JAEGER_PORT | 4317 | Yes | N/A |
| BEHIND_PROXY | false | Yes | Change to true if behind reverse proxy |
| ENVIRONMENT | production | Yes | "production" and "development" allowed. "development" allows connections from localhost:8080 and localhost:5173 at the CORS level |

View File

@@ -1 +0,0 @@
VITE_ENDURAIN_HOST=MY_APP_ENDURAIN_HOST

View File

@@ -15,6 +15,7 @@
<link rel="icon" href="/logo/favicon-16x16.png" type="image/png" sizes="16x16">
<link rel="apple-touch-icon" href="/logo/apple-touch-icon.png">
<link rel="manifest" href="/manifest.webmanifest">
<script src="/env.js"></script>
</head>
<body>
<div id="app"></div>

View File

@@ -0,0 +1,3 @@
window.env = {
ENDURAIN_HOST: "http://localhost:8080"
};

View File

@@ -31,7 +31,7 @@ export default {
emits: ['userDeleted'],
setup(props) {
const altText = ref('User Avatar');
const userPhotoUrl = ref(props.user?.photo_path ? `${import.meta.env.VITE_ENDURAIN_HOST}/${props.user.photo_path}` : null);
const userPhotoUrl = ref(props.user?.photo_path ? `${window.env.ENDURAIN_HOST}/${props.user.photo_path}` : null);
const alignTopValue = ref(props.alignTop);
return {

View File

@@ -12,7 +12,7 @@ export const strava = {
return fetchPutRequest('strava/client', data);
},
linkStrava(state, stravaClientId) {
let redirectUri = `${import.meta.env.VITE_ENDURAIN_HOST}`;
let redirectUri = `${window.env.ENDURAIN_HOST}`;
redirectUri = encodeURIComponent(redirectUri);
const scope = 'read,read_all,profile:read_all,activity:read,activity:read_all';

View File

@@ -3,8 +3,8 @@ import { useAuthStore } from "@/stores/authStore";
let refreshTokenPromise = null;
export const API_URL = `${import.meta.env.VITE_ENDURAIN_HOST}/api/v1/`;
export const FRONTEND_URL = `${import.meta.env.VITE_ENDURAIN_HOST}/`;
export const API_URL = `${window.env.ENDURAIN_HOST}/api/v1/`
export const FRONTEND_URL = `${window.env.ENDURAIN_HOST}/`
async function fetchWithRetry(url, options) {
// Add CSRF token to headers for state-changing requests

View File

@@ -65,7 +65,7 @@ export default {
const serverSettingsStore = useServerSettingsStore();
const showPassword = ref(false);
const loginPhotoUrl = serverSettingsStore.serverSettings.login_photo_set
? `${import.meta.env.VITE_ENDURAIN_HOST}/server_images/login.png`
? `${window.env.ENDURAIN_HOST}/server_images/login.png`
: null;
// Toggle password visibility