Skip to content

Commit 122a363

Browse files
authored
APP_ENV removal and secure cookie handling (#333)
Hey, made the tweaks we discussed, plus a couple related fixes :) - Removed APP_ENV entirely. All dev-specific functionality is enabled via `DEBUG: true` env var - Set secure cookie handling to false by default, added to the readme to enable if exclusively using HTTPS connection - Fixed healthcheck potentially not working with auth enabled - Removed APP_ENV from docker compose files and made sure app.db lines are included in all versions. APP_ENV in people's existing composes should get ignored entirely and will be put on the default env, so no issues when updating.
1 parent 0e25800 commit 122a363

File tree

14 files changed

+68
-55
lines changed

14 files changed

+68
-55
lines changed

Dockerfile

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ ENV DEBIAN_FRONTEND=noninteractive \
4949
# UID/GID will be handled by entrypoint script, but TZ/Locale are still needed
5050
LANG=en_US.UTF-8 \
5151
LANGUAGE=en_US:en \
52-
LC_ALL=en_US.UTF-8 \
53-
APP_ENV=dev
52+
LC_ALL=en_US.UTF-8
5453

5554
# Set ARG for build-time expansion (FLASK_PORT), ENV for runtime access
5655
ENV FLASK_PORT=8084
@@ -108,9 +107,9 @@ RUN mkdir -p /var/log/cwa-book-downloader /cwa-book-ingest && \
108107
EXPOSE ${FLASK_PORT}
109108

110109
# Add healthcheck for container status
111-
# This will run as root initially, but check localhost which should work if the app binds correctly.
110+
# Uses /api/health which doesn't require authentication
112111
HEALTHCHECK --interval=60s --timeout=60s --start-period=60s --retries=3 \
113-
CMD curl -s http://localhost:${FLASK_PORT}/api/status > /dev/null || exit 1
112+
CMD curl -s http://localhost:${FLASK_PORT}/api/health > /dev/null || exit 1
114113

115114
# Use dumb-init as the entrypoint to handle signals properly
116115
ENTRYPOINT ["/usr/bin/dumb-init", "--"]

app.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from logger import setup_logger
1818
from config import _SUPPORTED_BOOK_LANGUAGE, BOOK_LANGUAGE, SUPPORTED_FORMATS
19-
from env import FLASK_HOST, FLASK_PORT, APP_ENV, CWA_DB_PATH, DEBUG, USING_EXTERNAL_BYPASSER, BUILD_VERSION, RELEASE_VERSION, CALIBRE_WEB_URL
19+
from env import FLASK_HOST, FLASK_PORT, CWA_DB_PATH, DEBUG, USING_EXTERNAL_BYPASSER, BUILD_VERSION, RELEASE_VERSION, CALIBRE_WEB_URL
2020
import backend
2121

2222
from models import SearchFilters
@@ -28,13 +28,13 @@
2828
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # Disable caching
2929
app.config['APPLICATION_ROOT'] = '/'
3030

31-
# Determine async mode based on environment
32-
# In production with Gunicorn + gevent worker, use 'gevent'
33-
# In development with Flask dev server, use 'threading'
34-
if APP_ENV == 'prod':
35-
async_mode = 'gevent'
36-
else:
31+
# Determine async mode based on DEBUG setting
32+
# In production (DEBUG=False) with Gunicorn + gevent worker, use 'gevent'
33+
# In development (DEBUG=True) with Flask dev server, use 'threading'
34+
if DEBUG:
3735
async_mode = 'threading'
36+
else:
37+
async_mode = 'gevent'
3838

3939
# Initialize Flask-SocketIO with reverse proxy support
4040
socketio = SocketIO(
@@ -152,16 +152,9 @@ def filter(self, record):
152152
# The secret key will reset every time we restart, which will
153153
# require users to authenticate again
154154

155-
# Secure cookie handling (HTTP vs HTTPS)
156-
# Can be overridden with SESSION_COOKIE_SECURE environment variable
157-
session_cookie_secure_env = os.getenv('SESSION_COOKIE_SECURE', 'auto').lower()
158-
if session_cookie_secure_env in ['true', 'yes', '1']:
159-
SESSION_COOKIE_SECURE = True
160-
elif session_cookie_secure_env in ['false', 'no', '0']:
161-
SESSION_COOKIE_SECURE = False
162-
else:
163-
# Auto mode: align with deployment environment
164-
SESSION_COOKIE_SECURE = APP_ENV == 'prod'
155+
# Session cookie security - set to 'true' if exclusively using HTTPS
156+
session_cookie_secure_env = os.getenv('SESSION_COOKIE_SECURE', 'false').lower()
157+
SESSION_COOKIE_SECURE = session_cookie_secure_env in ['true', 'yes', '1']
165158

166159
app.config.update(
167160
SECRET_KEY = os.urandom(64),
@@ -382,7 +375,6 @@ def api_config() -> Union[Response, Tuple[Response, int]]:
382375
config = {
383376
"calibre_web_url": CALIBRE_WEB_URL,
384377
"debug": DEBUG,
385-
"app_env": APP_ENV,
386378
"build_version": BUILD_VERSION,
387379
"release_version": RELEASE_VERSION,
388380
"book_languages": _SUPPORTED_BOOK_LANGUAGE,
@@ -394,6 +386,17 @@ def api_config() -> Union[Response, Tuple[Response, int]]:
394386
logger.error_trace(f"Config error: {e}")
395387
return jsonify({"error": str(e)}), 500
396388

389+
@app.route('/api/health', methods=['GET'])
390+
def api_health() -> Union[Response, Tuple[Response, int]]:
391+
"""
392+
Health check endpoint for container orchestration.
393+
No authentication required.
394+
395+
Returns:
396+
flask.Response: JSON with status "ok".
397+
"""
398+
return jsonify({"status": "ok"})
399+
397400
@app.route('/api/status', methods=['GET'])
398401
@login_required
399402
def api_status() -> Union[Response, Tuple[Response, int]]:
@@ -812,7 +815,7 @@ def handle_status_request():
812815
logger.log_resource_usage()
813816

814817
if __name__ == '__main__':
815-
logger.info(f"Starting Flask application with WebSocket support on {FLASK_HOST}:{FLASK_PORT} IN {APP_ENV} mode")
818+
logger.info(f"Starting Flask application with WebSocket support on {FLASK_HOST}:{FLASK_PORT} (debug={DEBUG})")
816819
socketio.run(
817820
app,
818821
host=FLASK_HOST,

docker-compose.dev.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ services:
99
target: cwa-bd
1010
environment:
1111
DEBUG: true
12-
APP_ENV: dev
1312
USE_DOH: true
1413
CUSTOM_DNS: cloudflare
1514
volumes:

docker-compose.extbp.dev.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ services:
1010
target: cwa-bd-extbp
1111
environment:
1212
DEBUG: true
13-
APP_ENV: dev
1413
USE_DOH: true
1514
CUSTOM_DNS: cloudflare
1615
USE_CF_BYPASS: true # Enable Cloudflare bypass (default: true)

docker-compose.extbp.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ services:
77
BOOK_LANGUAGE: en
88
USE_BOOK_TITLE: true
99
TZ: America/New_York
10-
APP_ENV: prod
1110
UID: 1000
1211
GID: 100
12+
# CWA_DB_PATH: /auth/app.db # Uncomment to enable authentication
13+
# SESSION_COOKIE_SECURE: 'true' # Set to 'true' if accessing ONLY via HTTPS
14+
# DEBUG: 'true' # Enable debug mode (debug button, verbose logging)
1315
EXT_BYPASSER_URL: http://flaresolverr:8191
1416
ports:
1517
- 8084:8084
@@ -19,7 +21,7 @@ services:
1921
# the same as whatever you gave in "calibre-web-automated"
2022
- /tmp/data/calibre-web/ingest:/cwa-book-ingest
2123
# This is the location of CWA's app.db, which contains authentication
22-
# details
24+
# details. Uncomment to enable authentication (also uncomment CWA_DB_PATH above)
2325
#- /cwa/config/path/app.db:/auth/app.db:ro
2426
flaresolverr:
2527
image: ghcr.io/flaresolverr/flaresolverr:latest

docker-compose.tor.dev.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ services:
99
target: cwa-bd-tor
1010
environment:
1111
DEBUG: true
12-
APP_ENV: dev
1312
volumes:
1413
- /tmp/cwa-book-downloader:/tmp/cwa-book-downloader
1514
- /tmp/cwa-book-downloader-log:/var/log/cwa-book-downloader

docker-compose.tor.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@ services:
88
USE_BOOK_TITLE: true
99
TZ: America/New_York
1010
USING_TOR: true
11-
APP_ENV: prod
11+
# CWA_DB_PATH: /auth/app.db # Uncomment to enable authentication
12+
# SESSION_COOKIE_SECURE: 'true' # Set to 'true' if accessing ONLY via HTTPS
13+
# DEBUG: 'true' # Enable debug mode (debug button, verbose logging)
1214
cap_add:
1315
- NET_ADMIN
1416
- NET_RAW
1517
ports:
1618
- 8084:8084
1719
restart: unless-stopped
1820
volumes:
19-
# This is where the books will be downloaded to, usually it would be
20-
# the same as whatever you gave in "calibre-web-automated"
21+
# This is where the books will be downloaded to, usually it would be
22+
# the same as whatever you gave in "calibre-web-automated"
2123
- /tmp/data/calibre-web/ingest:/cwa-book-ingest
24+
# This is the location of CWA's app.db, which contains authentication
25+
# details. Uncomment to enable authentication (also uncomment CWA_DB_PATH above)
26+
#- /cwa/config/path/app.db:/auth/app.db:ro

docker-compose.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ services:
1111
BOOK_LANGUAGE: en
1212
USE_BOOK_TITLE: true
1313
TZ: America/New_York
14-
APP_ENV: prod
1514
UID: 1000
1615
GID: 100
17-
# CWA_DB_PATH: /auth/app.db # Comment out to disable authentication
16+
# CWA_DB_PATH: /auth/app.db # Uncomment to enable authentication (also uncomment volume below)
17+
# CALIBRE_WEB_URL: http://localhost:8080 # Uncomment and add your custom library URL to enable "Go To Library" button in the Web UI
18+
# SESSION_COOKIE_SECURE: 'true' # Set to 'true' if accessing ONLY via HTTPS
19+
# DEBUG: 'true' # Enable debug mode (debug button, verbose logging)
1820
# Queue management settings
1921
MAX_CONCURRENT_DOWNLOADS: 3
2022
DOWNLOAD_PROGRESS_UPDATE_INTERVAL: 5
@@ -26,5 +28,5 @@ services:
2628
# the same as whatever you gave in "calibre-web-automated"
2729
- /tmp/data/calibre-web/ingest:/cwa-book-ingest
2830
# This is the location of CWA's app.db, which contains authentication
29-
# details. Comment out to disable authentication
31+
# details. Uncomment to enable authentication (also uncomment CWA_DB_PATH above)
3032
#- /cwa/config/path/app.db:/auth/app.db:ro

entrypoint.sh

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,17 @@ change_ownership /tmp/cwa-book-downloader
105105
# Test write to all folders
106106
make_writable /cwa-book-ingest
107107

108-
# Set the command to run based on the environment
109-
is_prod=$(echo "$APP_ENV" | tr '[:upper:]' '[:lower:]')
110-
if [ "$is_prod" = "prod" ]; then
108+
# Set the command to run based on DEBUG setting
109+
# DEBUG=true uses Flask dev server, otherwise uses gunicorn for production
110+
is_debug=$(echo "$DEBUG" | tr '[:upper:]' '[:lower:]')
111+
if [ "$is_debug" = "true" ]; then
112+
command="python3 app.py"
113+
else
111114
# Use geventwebsocket worker for SocketIO + WebSocket compatibility
112115
# This special worker class handles WebSocket upgrades properly
113116
# --workers 1: SocketIO requires sticky sessions, use 1 worker or configure sticky sessions
114117
# -t 300: 300 second timeout for long-running requests
115118
command="gunicorn --worker-class geventwebsocket.gunicorn.workers.GeventWebSocketWorker --workers 1 -t 300 -b ${FLASK_HOST:-0.0.0.0}:${FLASK_PORT:-8084} app:app"
116-
else
117-
command="python3 app.py"
118119
fi
119120

120121
# If DEBUG and not using an external bypass
@@ -179,7 +180,7 @@ sum=$(python3 -c "print(sum(int(l.strip()) for l in open('/tmp/test.cwa-bd').rea
179180
[ "$sum" == 11250075000 ] && echo "Success: /tmp is writable" || (echo "Failure: /tmp is not writable" && exit 1)
180181
rm /tmp/test.cwa-bd
181182

182-
echo "Running command: '$command' as '$USERNAME' in '$APP_ENV' mode"
183+
echo "Running command: '$command' as '$USERNAME' (debug=$is_debug)"
183184

184185
# Stop logging
185186
exec 1>&3 2>&4

env.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ def string_to_bool(s: str) -> bool:
55
return s.lower() in ["true", "yes", "1", "y"]
66

77
# Authentication and session settings
8-
# SESSION_COOKIE_SECURE: Controls whether session cookies are marked as secure (HTTPS only)
9-
# - 'auto' (default): Uses False in dev, True in prod, can be overridden with environment variable
10-
# - 'true'/'yes'/'1': Always use secure cookies (recommended for production with HTTPS)
11-
# - 'false'/'no'/'0': Never use secure cookies (only for local HTTP)
12-
SESSION_COOKIE_SECURE_ENV = os.getenv("SESSION_COOKIE_SECURE", "auto")
8+
SESSION_COOKIE_SECURE_ENV = os.getenv("SESSION_COOKIE_SECURE", "false")
139

1410
CWA_DB = os.getenv("CWA_DB_PATH")
1511
CWA_DB_PATH = Path(CWA_DB) if CWA_DB else None
@@ -54,7 +50,6 @@ def string_to_bool(s: str) -> bool:
5450
FLASK_HOST = os.getenv("FLASK_HOST", "0.0.0.0")
5551
FLASK_PORT = int(os.getenv("FLASK_PORT", "8084"))
5652
DEBUG = string_to_bool(os.getenv("DEBUG", "false"))
57-
APP_ENV = os.getenv("APP_ENV", "N/A").lower()
5853
PRIORITIZE_WELIB = string_to_bool(os.getenv("PRIORITIZE_WELIB", "false"))
5954
ALLOW_USE_WELIB = string_to_bool(os.getenv("ALLOW_USE_WELIB", "true"))
6055

0 commit comments

Comments
 (0)