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:
tigeren 2025-08-31 06:04:59 +00:00
parent fe7fa89573
commit d27d30b038
8 changed files with 240 additions and 7 deletions

53
.dockerignore Normal file
View File

@ -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

52
Dockerfile Normal file
View File

@ -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"]

76
README.md Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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",

28
docker-compose.yml Normal file
View File

@ -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

View File

@ -1,2 +1,3 @@
aiohttp>=3.8.0
asyncio
pytz

View File

@ -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."""