Remove config.json file and update config.py to load configuration from a new directory. Add timezone support in the NotificationScheduler for accurate silent time checks. Update requirements.txt to include pytz for timezone handling.
This commit is contained in:
parent
fe7fa89573
commit
d27d30b038
|
|
@ -0,0 +1,53 @@
|
|||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
env
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
.tox
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.log
|
||||
.git
|
||||
.mypy_cache
|
||||
.pytest_cache
|
||||
.hypothesis
|
||||
|
||||
# IDEs
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Claude
|
||||
.claude
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
CLAUDE.md
|
||||
|
||||
# Test files
|
||||
test_*.py
|
||||
*_test.py
|
||||
quick_test.py
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# Multi-stage build for minimal image size
|
||||
FROM python:3.11-alpine AS builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache --virtual .build-deps \
|
||||
gcc \
|
||||
musl-dev \
|
||||
libffi-dev \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy requirements first for better caching
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install Python dependencies
|
||||
RUN pip install --no-cache-dir --user -r requirements.txt
|
||||
|
||||
# Final stage - use Alpine without Python to copy only what's needed
|
||||
FROM alpine:3.18
|
||||
|
||||
# Install runtime dependencies only
|
||||
RUN apk add --no-cache \
|
||||
python3 \
|
||||
py3-pip \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# Create non-root user for security
|
||||
RUN addgroup -g 1000 appuser && \
|
||||
adduser -D -s /bin/sh -u 1000 -G appuser appuser
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy Python packages from builder stage
|
||||
COPY --from=builder /root/.local /home/appuser/.local
|
||||
|
||||
# Copy application code
|
||||
COPY --chown=appuser:appuser . .
|
||||
|
||||
# Switch to non-root user
|
||||
USER appuser
|
||||
|
||||
# Add local bin to PATH
|
||||
ENV PATH=/home/appuser/.local/bin:$PATH
|
||||
|
||||
# Expose port if needed (adjust as required)
|
||||
# EXPOSE 8000
|
||||
|
||||
# Run the application
|
||||
CMD ["python3", "main.py"]
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# Dom
|
||||
|
||||
A notification scheduling system with Docker support.
|
||||
|
||||
## Quick Start with Docker Compose
|
||||
|
||||
1. **Build and start the service:**
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
2. **View logs:**
|
||||
```bash
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
3. **Stop the service:**
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The configuration file is located in the `config/` directory and is mapped as a volume, allowing you to modify it without rebuilding the container.
|
||||
|
||||
### Modifying Configuration
|
||||
|
||||
1. Edit `config/config.json` on your host machine
|
||||
2. The changes will be automatically picked up by the container
|
||||
3. Restart the container if needed: `docker-compose restart`
|
||||
|
||||
### Configuration Options
|
||||
|
||||
- `ollama_endpoint`: Ollama API endpoint
|
||||
- `ollama_model`: Model to use for text generation
|
||||
- `silent_start`: Start time for silent period (HH:MM)
|
||||
- `silent_end`: End time for silent period (HH:MM)
|
||||
- `timezone`: Timezone for scheduling
|
||||
- `min_interval`: Minimum interval between notifications (minutes)
|
||||
- `max_interval`: Maximum interval between notifications (minutes)
|
||||
- `bark_api_url`: Bark notification service URL
|
||||
- `bark_device_key`: Bark device key
|
||||
- `ntfy_api_url`: Ntfy notification service URL
|
||||
- `ntfy_topic`: Ntfy topic
|
||||
- `ntfy_access_token`: Ntfy access token
|
||||
- `templates_dir`: Directory containing notification templates
|
||||
|
||||
## Development
|
||||
|
||||
### Local Development
|
||||
```bash
|
||||
python3 main.py
|
||||
```
|
||||
|
||||
### Building Docker Image
|
||||
```bash
|
||||
docker build -t dom .
|
||||
```
|
||||
|
||||
### Running with Docker
|
||||
```bash
|
||||
docker run -d \
|
||||
--name dom \
|
||||
-v $(pwd)/config:/app/config \
|
||||
-v $(pwd)/templates:/app/templates \
|
||||
dom
|
||||
```
|
||||
|
||||
## Volumes
|
||||
|
||||
- `./config:/app/config`: Configuration directory (read-write)
|
||||
- `./templates:/app/templates`: Templates directory (read-only)
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `TZ`: Timezone (default: Asia/Shanghai)
|
||||
|
|
@ -13,6 +13,7 @@ class Config:
|
|||
# Silent time configuration (12pm to 8am)
|
||||
silent_start: str = "20:00"
|
||||
silent_end: str = "12:00"
|
||||
timezone: str = "UTC"
|
||||
|
||||
# Interval configuration (in minutes)
|
||||
min_interval: int = 3
|
||||
|
|
@ -32,7 +33,7 @@ class Config:
|
|||
|
||||
def __post_init__(self):
|
||||
"""Load configuration from file if exists."""
|
||||
config_file = Path("config.json")
|
||||
config_file = Path("config/config.json")
|
||||
if config_file.exists():
|
||||
with open(config_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
|
@ -42,6 +43,6 @@ class Config:
|
|||
|
||||
def save(self):
|
||||
"""Save current configuration to file."""
|
||||
config_file = Path("config.json")
|
||||
config_file = Path("config/config.json")
|
||||
with open(config_file, 'w') as f:
|
||||
json.dump(asdict(self), f, indent=2)
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
"ollama_endpoint": "http://192.168.2.245:11434",
|
||||
"ollama_model": "goekdenizguelmez/JOSIEFIED-Qwen3:8b",
|
||||
"silent_start": "20:00",
|
||||
"silent_end": "12:00",
|
||||
"silent_end": "07:00",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"min_interval": 3,
|
||||
"max_interval": 180,
|
||||
"bark_api_url": "https://bark.xorbitlab.xyz",
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
dom:
|
||||
build: .
|
||||
container_name: dom
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# Map the config directory for external modification
|
||||
- ./config:/app/config:rw
|
||||
# Map templates directory if needed
|
||||
- ./templates:/app/templates:rw
|
||||
environment:
|
||||
# Set timezone for the container
|
||||
- TZ=Asia/Shanghai
|
||||
networks:
|
||||
- dom-network
|
||||
# Health check to ensure the service is running
|
||||
healthcheck:
|
||||
test: ["CMD", "python3", "-c", "import sys; sys.exit(0)"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
networks:
|
||||
dom-network:
|
||||
driver: bridge
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
aiohttp>=3.8.0
|
||||
asyncio
|
||||
pytz
|
||||
27
scheduler.py
27
scheduler.py
|
|
@ -21,17 +21,38 @@ class NotificationScheduler:
|
|||
|
||||
def _is_silent_time(self) -> bool:
|
||||
"""Check if current time is within silent hours."""
|
||||
current_time = datetime.now().time()
|
||||
import pytz
|
||||
|
||||
# Get the configured timezone
|
||||
timezone_name = getattr(self.config, 'timezone', 'UTC')
|
||||
try:
|
||||
tz = pytz.timezone(timezone_name)
|
||||
current_datetime = datetime.now(tz)
|
||||
time_type = f"timezone {timezone_name}"
|
||||
except pytz.exceptions.UnknownTimeZoneError:
|
||||
logger.warning(f"Unknown timezone '{timezone_name}', falling back to UTC")
|
||||
tz = pytz.UTC
|
||||
current_datetime = datetime.now(tz)
|
||||
time_type = "UTC (fallback)"
|
||||
|
||||
current_time = current_datetime.time()
|
||||
|
||||
# Parse silent time configuration
|
||||
silent_start = datetime.strptime(self.config.silent_start, "%H:%M").time()
|
||||
silent_end = datetime.strptime(self.config.silent_end, "%H:%M").time()
|
||||
|
||||
# Log current time for debugging
|
||||
logger.info(f"Current time: {current_datetime.strftime('%Y-%m-%d %H:%M:%S')} ({time_type})")
|
||||
logger.info(f"Silent period: {self.config.silent_start} to {self.config.silent_end}")
|
||||
|
||||
# Handle overnight silent period (e.g., 20:00 to 12:00)
|
||||
if silent_start >= silent_end:
|
||||
return current_time >= silent_start or current_time <= silent_end
|
||||
is_silent = current_time >= silent_start or current_time <= silent_end
|
||||
else:
|
||||
return silent_start <= current_time <= silent_end
|
||||
is_silent = silent_start <= current_time <= silent_end
|
||||
|
||||
logger.info(f"Silent time check: {is_silent}")
|
||||
return is_silent
|
||||
|
||||
def _get_next_interval(self) -> int:
|
||||
"""Get random interval in seconds between min and max."""
|
||||
|
|
|
|||
Loading…
Reference in New Issue