Skip to content

Commit e2e644d

Browse files
authored
Merge pull request #94 from 101t/3.0.3
3.0.3
2 parents 4734e1b + 48630b6 commit e2e644d

20 files changed

Lines changed: 1246 additions & 67 deletions

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ before_install:
88
- cp -rf sample.env .env
99
before_script:
1010
# install requirements:
11-
- pip install -U pip wheel uv && uv pip install -r pyproject.toml
11+
- pip install -U pip wheel uv && uv pip install -r pyproject.toml --extra prod
1212
services:
1313
- redis-server
1414
addons:

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,11 @@ TELNET_USERNAME=jcliadmin
159159
TELNET_PW=jclipwd
160160
TELNET_TIMEOUT=10
161161

162-
# Redis & Celery
163-
REDIS_URL=redis://localhost:6379/0
164-
CELERY_BROKER_URL=amqp://guest:guest@localhost:5672//
162+
# Redis for Cache & Celery
163+
REDIS_HOST=redis
164+
REDIS_PORT=6379
165+
REDIS_DB=0
166+
REDIS_PASSWORD=<Optional>
165167

166168
# Submit Log Feature
167169
SUBMIT_LOG=True
@@ -284,8 +286,9 @@ For ARM-based systems:
284286
| `SECRET_KEY` | Django secret key | - ||
285287
| `ALLOWED_HOSTS` | Allowed hosts | `*` ||
286288
| `PRODB_URL` | PostgreSQL URL | - ||
287-
| `REDIS_URL` | Redis URL | `redis://redis:6379/0` ||
288-
| `CELERY_BROKER_URL` | RabbitMQ URL | `amqp://guest:guest@rabbit-mq:5672//` ||
289+
| `REDIS_HOST` | Redis host | `redis` ||
290+
| `REDIS_PORT` | Redis port | `6379` ||
291+
| `REDIS_DB` | Redis database | `0` ||
289292
| `TELNET_HOST` | Jasmin telnet host | `127.0.0.1` ||
290293
| `TELNET_PORT` | Jasmin telnet port | `8990` ||
291294
| `TELNET_USERNAME` | Jasmin admin username | `jcliadmin` ||

config/celery.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import os
2+
import django
3+
4+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.pro")
5+
6+
# Setup Django before importing any Django modules
7+
django.setup()
8+
29
from django.conf import settings
310
from celery import Celery
411
from celery.utils.log import get_task_logger
5-
612
from django.utils import timezone
713

814
logger = get_task_logger(__name__)
9-
10-
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.pro")
11-
CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", default="redis://localhost:6379/0")
12-
CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", default="redis://localhost:6379/0")
15+
CELERY_BROKER_URL = settings.REDIS_URL
16+
CELERY_RESULT_BACKEND = settings.REDIS_URL
1317

1418

1519
app = Celery('config')
@@ -21,6 +25,20 @@
2125
app.conf.broker_connection_retry_on_startup = True
2226
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS, related_name='tasks')
2327

28+
# Close database connections after each task to prevent leaks
29+
from django.db import connection
30+
from celery.signals import task_postrun, task_prerun
31+
32+
@task_prerun.connect
33+
def task_prerun_handler(sender=None, **kwargs):
34+
"""Close database connections before task starts."""
35+
connection.close()
36+
37+
@task_postrun.connect
38+
def task_postrun_handler(sender=None, **kwargs):
39+
"""Close database connections after task completes."""
40+
connection.close()
41+
2442
BROKER_CONNECTION_TIMEOUT = 120
2543

2644
CELERY_DEFAULT_UP_TIME = timezone.now()

config/settings/com.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,20 @@
144144
REDIS_HOST = os.environ.get("REDIS_HOST", default="redis")
145145
REDIS_PORT = int(os.environ.get("REDIS_PORT", default=6379))
146146
REDIS_DB = int(os.environ.get("REDIS_DB", default=0))
147-
REDIS_URL = (REDIS_HOST, REDIS_PORT)
147+
REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD", default="")
148+
if REDIS_PASSWORD:
149+
REDIS_URL = f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}'
150+
else:
151+
REDIS_URL = f'redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}'
152+
153+
CACHES = {
154+
'default': {
155+
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
156+
'LOCATION': REDIS_URL,
157+
'KEY_PREFIX': 'jasmin_cache',
158+
'TIMEOUT': 300, # 5 minutes default
159+
}
160+
}
148161

149162
DEFAULT_USER_AVATAR = STATIC_URL + "assets/img/user.png"
150163
DEFAULT_USER_FOLDER = "users"

config/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "3.0.1"
1+
VERSION = "3.0.3"

docker-compose.yml

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: '3.8'
22

33
services:
44
jasmin-web:
5-
image: tarekaec/jasmin_web_panel:1.3
5+
image: tarekaec/jasmin_web_panel:1.4.2
66
ports:
77
- "${JASMIN_WEB_PORT:-8999}:8000"
88
deploy:
@@ -22,11 +22,11 @@ services:
2222
depends_on:
2323
- redis
2424
- db
25-
- rabbit-mq
25+
- rabbitmq
2626
restart: unless-stopped
2727

2828
jasmin-celery:
29-
image: tarekaec/jasmin_web_panel:1.3
29+
image: tarekaec/jasmin_web_panel:1.4.2
3030
entrypoint: bash ./docker-entrypoint-celery.sh
3131
deploy:
3232
replicas: 1
@@ -35,8 +35,6 @@ services:
3535
environment:
3636
DEBUG: 0
3737
DJANGO_SETTINGS_MODULE: config.settings.pro
38-
CELERY_BROKER_URL: redis://redis:6379/0
39-
CELERY_RESULT_BACKEND: redis://redis:6379/0
4038
CELERY_LOG_LEVEL: info
4139
healthcheck:
4240
disable: true
@@ -70,19 +68,25 @@ services:
7068
security_opt:
7169
- no-new-privileges:true
7270

73-
rabbit-mq:
71+
rabbitmq:
7472
image: rabbitmq:3.10-management-alpine
7573
restart: unless-stopped
7674
environment:
7775
RABBITMQ_DEFAULT_USER: guest
7876
RABBITMQ_DEFAULT_PASS: guest
77+
#RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: "-ssl_opts.server_name_indication disable"
78+
volumes:
79+
- ./rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
80+
- rabbitmq_data:/var/lib/rabbitmq
7981
healthcheck:
8082
test: rabbitmq-diagnostics -q ping
8183
deploy:
8284
resources:
8385
limits:
84-
cpus: "${RABBITMQ_CPU:-2}"
85-
memory: ${RABBITMQ_MEM:-512M}
86+
cpus: "${RABBITMQ_CPU:-4}"
87+
memory: ${RABBITMQ_MEM:-1024M}
88+
reservations:
89+
memory: 512M
8690
security_opt:
8791
- no-new-privileges:true
8892

@@ -108,17 +112,22 @@ services:
108112
- ./jasmin_config/resource:/etc/jasmin/resource
109113
- ./jasmin_config/store:/etc/jasmin/store
110114
- ./jasmin_config:/etc/jasmin
115+
- ./logs:/var/log/jasmin
111116
ports:
112117
- "${JASMIN_SMS_PORT:-2775}:2775"
113118
- "${JASMIN_DASHBOARD_PORT:-8990}:8990"
114119
- "${JASMIN_HTTP_API_PORT:-1401}:1401"
115120
depends_on:
116121
- redis
117-
- rabbit-mq
122+
- rabbitmq
118123
environment:
119124
REDIS_CLIENT_HOST: redis
120-
AMQP_BROKER_HOST: rabbit-mq
125+
AMQP_BROKER_HOST: rabbitmq
121126
AMQP_BROKER_PORT: 5672
127+
sysctls:
128+
- net.ipv4.tcp_keepalive_time=60
129+
- net.ipv4.tcp_keepalive_intvl=10
130+
- net.ipv4.tcp_keepalive_probes=5
122131
deploy:
123132
resources:
124133
limits:
@@ -136,21 +145,32 @@ services:
136145
command: bash -c "sleep 15 && pip install -U pip psycopg2-binary mysql-connector-python && exec python /build/misc/scripts/sms_logger.py"
137146
environment:
138147
DB_TYPE_MYSQL: ${DB_TYPE_MYSQL:-0}
139-
AMQP_BROKER_HOST: ${AMQP_BROKER_HOST:-rabbit-mq}
148+
AMQP_BROKER_HOST: ${AMQP_BROKER_HOST:-rabbitmq}
140149
AMQP_BROKER_PORT: ${AMQP_BROKER_PORT:-5672}
141150
AMQP_SPEC_FILE: '/etc/jasmin/resource/amqp0-9-1.xml'
151+
AMQP_HEARTBEAT: 60 # Enable 60-second heartbeats
152+
RECONNECT_DELAY: 5 # Initial reconnect delay
153+
MAX_RECONNECT_DELAY: 60 # Max reconnect delay
142154
DB_HOST: ${DB_HOST:-db}
143155
DB_DATABASE: ${DB_DATABASE:-jasmin}
144156
DB_TABLE: ${DB_TABLE:-submit_log}
145157
DB_USER: ${DB_USER:-jasmin}
146158
DB_PASS: ${DB_PASS:-jasmin}
147159
depends_on:
148-
- rabbit-mq
160+
- rabbitmq
149161
- db
150162
- jasmin
151163
restart: unless-stopped
152164
healthcheck:
153-
disable: true
165+
test: ["CMD", "pgrep", "-f", "sms_logger.py"]
166+
interval: 30s
167+
timeout: 10s
168+
retries: 3
169+
start_period: 40s
170+
sysctls:
171+
net.ipv4.tcp_keepalive_time: 60
172+
net.ipv4.tcp_keepalive_intvl: 10
173+
net.ipv4.tcp_keepalive_probes: 5
154174

155175
volumes:
156176
web_public:
@@ -159,7 +179,9 @@ volumes:
159179
driver: local
160180
redis_data:
161181
driver: local
182+
rabbitmq_data:
183+
driver: local
162184
postgres_data:
163185
driver: local
164186
monitoring_data:
165-
driver: local
187+
driver: local

main/core/middleware.py

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.conf import settings
22
from django.utils.deprecation import MiddlewareMixin
33
from django.core.cache import cache
4-
from django.db import transaction
4+
from django.db import transaction, connection
55
from .utils import get_user_agent, get_client_ip, LazyEncoder
66
from .models import ActivityLog
77

@@ -73,20 +73,54 @@ def process_request(self, request):
7373
self._enqueue_activity_log_creation(request, user_agent)
7474

7575
def _enqueue_activity_log_creation(self, request, user_agent):
76-
from concurrent.futures import ThreadPoolExecutor
77-
with ThreadPoolExecutor(max_workers=1) as executor:
78-
executor.submit(self._create_activity_log, request, user_agent)
76+
# Extract data from request before threading to avoid context issues
77+
log_data = {
78+
'user_id': request.user.id,
79+
'service': request.POST.get("s", "unknown"),
80+
'method': request.method,
81+
'params': self.clean_params(request.POST or request.GET or {}),
82+
'path': request.path,
83+
'ip': get_client_ip(request),
84+
'user_agent': user_agent,
85+
}
86+
87+
# Use threading.Thread instead of ThreadPoolExecutor for simpler lifecycle
88+
import threading
89+
thread = threading.Thread(target=self._create_activity_log_safe, args=(log_data,), daemon=True)
90+
thread.start()
7991

92+
def clean_params(self, params):
93+
"""Clean sensitive parameters before logging."""
94+
cleaned = dict(params)
95+
sensitive_keys = ['password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey']
96+
for key in list(cleaned.keys()):
97+
if any(sensitive in key.lower() for sensitive in sensitive_keys):
98+
cleaned[key] = '***REDACTED***'
99+
return cleaned
100+
101+
def _create_activity_log_safe(self, log_data):
102+
"""Thread-safe activity log creation with proper DB connection handling."""
103+
try:
104+
self._create_activity_log(log_data)
105+
except Exception as e:
106+
logger.error(f"Failed to create activity log: {str(e)}", exc_info=True)
107+
finally:
108+
# Close database connection for this thread
109+
connection.close()
110+
80111
@transaction.atomic
81-
def _create_activity_log(self, request, user_agent):
82-
params = self.clean_params(request.POST or request.GET or {})
112+
def _create_activity_log(self, log_data):
113+
from django.contrib.auth import get_user_model
114+
User = get_user_model()
115+
116+
user = User.objects.get(id=log_data['user_id'])
83117
activity_log = ActivityLog(
84-
user=request.user,
85-
service=request.POST.get("s", "unknown"),
86-
method=request.method,
87-
params=json.dumps(params, cls=LazyEncoder),
88-
path=request.path,
89-
ip=get_client_ip(request),
90-
user_agent=json.dumps(user_agent.__dict__ or {}, cls=LazyEncoder),
118+
user=user,
119+
service=log_data['service'],
120+
method=log_data['method'],
121+
params=json.dumps(log_data['params'], cls=LazyEncoder),
122+
path=log_data['path'],
123+
ip=log_data['ip'],
124+
user_agent=json.dumps(log_data['user_agent'].__dict__ or {}, cls=LazyEncoder),
91125
)
92126
activity_log.save()

main/core/tasks/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .mail_html import mail_html_mails, mail_html_envelopes
2+
from .export_submit_logs import export_submit_logs_task

0 commit comments

Comments
 (0)