mirror of
https://github.com/jonasrosland/gitmirror.git
synced 2025-05-10 20:05:33 +02:00
Moved logging to volume instead of mount, and added better rotation
Signed-off-by: Jonas Rosland <jonas.rosland@gmail.com>
This commit is contained in:
parent
423b4f64f6
commit
9a35774df1
17 changed files with 133 additions and 131 deletions
19
.env.example
19
.env.example
|
@ -1,15 +1,20 @@
|
||||||
# GitHub Personal Access Token (create one at https://github.com/settings/tokens)
|
# GitHub Personal Access Token (create one at https://github.com/settings/tokens)
|
||||||
# Required scopes: repo
|
# Required scopes: repo (for private repositories)
|
||||||
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
# For public repositories, this is optional but recommended
|
||||||
|
GITHUB_TOKEN=your_github_token
|
||||||
|
|
||||||
# Gitea Access Token (create one in your Gitea instance under Settings > Applications)
|
# Gitea Access Token (create one in your Gitea instance under Settings > Applications)
|
||||||
# Required permissions: write:repository, write:issue
|
# Required permissions: read:user, write:repository, write:issue
|
||||||
GITEA_TOKEN=gta_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
GITEA_TOKEN=your_gitea_token
|
||||||
|
|
||||||
# Your Gitea instance URL (no trailing slash)
|
# Your Gitea instance URL (no trailing slash)
|
||||||
GITEA_URL=https://gitea.example.com
|
GITEA_URL=https://your-gitea-instance.com
|
||||||
|
|
||||||
# Secret key for the web UI (optional)
|
# Secret key for the web UI (OPTIONAL)
|
||||||
# This key is used to secure Flask sessions and flash messages
|
# This key is used to secure Flask sessions and flash messages
|
||||||
# If not provided, a random key will be automatically generated at container start
|
# If not provided, a random key will be automatically generated at container start
|
||||||
# SECRET_KEY=your_secret_key
|
# SECRET_KEY=your_secret_key
|
||||||
|
|
||||||
|
# Log retention days (OPTIONAL, defaults to 30 days)
|
||||||
|
# This setting controls how long log files are kept before being automatically deleted
|
||||||
|
LOG_RETENTION_DAYS=30
|
11
.gitignore
vendored
11
.gitignore
vendored
|
@ -4,6 +4,9 @@
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
|
# Configuration files
|
||||||
|
config.json
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs/
|
logs/
|
||||||
*.log
|
*.log
|
||||||
|
@ -53,4 +56,10 @@ venv.bak/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Docker artifacts
|
# Docker artifacts
|
||||||
.dockerignore
|
.dockerignore
|
||||||
|
|
||||||
|
# Data directory
|
||||||
|
data/
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
.docker/
|
20
Dockerfile
20
Dockerfile
|
@ -36,20 +36,22 @@ COPY . .
|
||||||
# Install the package in development mode
|
# Install the package in development mode
|
||||||
RUN pip install -e .
|
RUN pip install -e .
|
||||||
|
|
||||||
# Create logs and config directories
|
# Create a non-root user for security
|
||||||
RUN mkdir -p logs
|
RUN groupadd -g ${PGID:-1000} appgroup && \
|
||||||
RUN mkdir -p /app/data/config
|
useradd -m -u ${PUID:-1000} -g appgroup appuser && \
|
||||||
|
chown -R appuser:appgroup /app && \
|
||||||
|
chmod +x start.sh
|
||||||
|
|
||||||
|
# Create config directory
|
||||||
|
RUN mkdir -p /app/data/config && \
|
||||||
|
chown -R appuser:appgroup /app/data/config
|
||||||
|
|
||||||
|
USER appuser
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
ENV GITMIRROR_CONFIG_DIR=/app/data/config
|
ENV GITMIRROR_CONFIG_DIR=/app/data/config
|
||||||
|
|
||||||
# Create a non-root user for security
|
|
||||||
RUN useradd -m appuser && \
|
|
||||||
chown -R appuser:appuser /app && \
|
|
||||||
chmod +x start.sh
|
|
||||||
USER appuser
|
|
||||||
|
|
||||||
# Expose port for web UI
|
# Expose port for web UI
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
||||||
|
|
53
README.md
53
README.md
|
@ -73,6 +73,9 @@ GITEA_URL=https://your-gitea-instance.com
|
||||||
# This key is used to secure Flask sessions and flash messages
|
# This key is used to secure Flask sessions and flash messages
|
||||||
# If not provided, a random key will be automatically generated at container start
|
# If not provided, a random key will be automatically generated at container start
|
||||||
# SECRET_KEY=your_secret_key
|
# SECRET_KEY=your_secret_key
|
||||||
|
|
||||||
|
# Log retention days (OPTIONAL, defaults to 30 days)
|
||||||
|
LOG_RETENTION_DAYS=30
|
||||||
```
|
```
|
||||||
|
|
||||||
### Authentication for Private Repositories
|
### Authentication for Private Repositories
|
||||||
|
@ -124,6 +127,44 @@ docker-compose logs -f
|
||||||
docker-compose down
|
docker-compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Using Bind Mounts for Logs
|
||||||
|
|
||||||
|
By default, logs are stored in a Docker volume for better permission handling. If you prefer to use bind mounts instead (to access logs directly on the host filesystem), you can modify the `docker-compose.yml` file:
|
||||||
|
|
||||||
|
1. Change the volume configuration from:
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- gitmirror_logs:/app/logs
|
||||||
|
```
|
||||||
|
|
||||||
|
to:
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ./logs:/app/logs
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create the logs directory with the correct permissions:
|
||||||
|
```bash
|
||||||
|
mkdir -p logs
|
||||||
|
chmod 755 logs
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Update the container user to match your host user's UID/GID:
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- PUID=$(id -u)
|
||||||
|
- PGID=$(id -g)
|
||||||
|
user: "${PUID}:${PGID}"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Remove the `volumes` section at the bottom of the file that defines `gitmirror_logs`
|
||||||
|
|
||||||
|
This setup will:
|
||||||
|
- Store logs directly in the `logs` directory on your host
|
||||||
|
- Allow you to access logs without using Docker commands
|
||||||
|
- Maintain proper permissions for the container to write logs
|
||||||
|
- Keep the same log rotation and retention settings
|
||||||
|
|
||||||
### Using Docker Directly
|
### Using Docker Directly
|
||||||
|
|
||||||
To run the application with Docker directly:
|
To run the application with Docker directly:
|
||||||
|
@ -293,7 +334,17 @@ When an error occurs during mirroring, you can click on the error message to vie
|
||||||
|
|
||||||
## Logs
|
## Logs
|
||||||
|
|
||||||
Logs are stored in the `logs` directory with a date-based naming convention. The web UI provides a convenient way to view these logs, with direct links from error messages.
|
Logs are stored in the `logs` directory and are:
|
||||||
|
- Rotated daily at midnight
|
||||||
|
- Retained for a configurable number of days (default: 30)
|
||||||
|
- Separated by service (web and mirror)
|
||||||
|
- Viewable through the web UI
|
||||||
|
|
||||||
|
Log files follow this naming convention:
|
||||||
|
- Current log file: `web.log` or `mirror.log`
|
||||||
|
- Rotated log files: `web.log.2024-03-20`, `web.log.2024-03-19`, etc.
|
||||||
|
|
||||||
|
The log retention period can be configured using the `LOG_RETENTION_DAYS` environment variable in your `.env` file.
|
||||||
|
|
||||||
## Development and Testing
|
## Development and Testing
|
||||||
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"scheduler_enabled": false,
|
|
||||||
"mirror_interval": 8,
|
|
||||||
"last_run": null,
|
|
||||||
"next_run": 1741959205,
|
|
||||||
"log_level": "INFO",
|
|
||||||
"mirror_interval_hours": 8,
|
|
||||||
"last_mirror_run": 1741806267.980561
|
|
||||||
}
|
|
24
config.json.example
Normal file
24
config.json.example
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"scheduler_enabled": false,
|
||||||
|
"mirror_interval": 8,
|
||||||
|
"last_run": null,
|
||||||
|
"next_run": null,
|
||||||
|
"log_level": "INFO",
|
||||||
|
"repositories": {
|
||||||
|
"owner/repo": {
|
||||||
|
"gitea_owner": "gitea_owner",
|
||||||
|
"gitea_repo": "gitea_repo",
|
||||||
|
"mirror_issues": true,
|
||||||
|
"mirror_pull_requests": true,
|
||||||
|
"mirror_labels": true,
|
||||||
|
"mirror_milestones": true,
|
||||||
|
"mirror_wiki": false,
|
||||||
|
"mirror_releases": false,
|
||||||
|
"last_mirror_timestamp": null,
|
||||||
|
"last_mirror_date": null,
|
||||||
|
"last_mirror_status": null,
|
||||||
|
"last_mirror_messages": [],
|
||||||
|
"last_mirror_log": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"mirror_metadata": true,
|
|
||||||
"mirror_issues": true,
|
|
||||||
"mirror_pull_requests": true,
|
|
||||||
"mirror_labels": true,
|
|
||||||
"mirror_milestones": true,
|
|
||||||
"mirror_wiki": true,
|
|
||||||
"last_mirror_timestamp": 1741891663,
|
|
||||||
"last_mirror_date": "2025-03-13 18:47:43",
|
|
||||||
"last_mirror_log": "mirror-2025-03-13.log"
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"mirror_metadata": true,
|
|
||||||
"mirror_issues": false,
|
|
||||||
"mirror_pull_requests": false,
|
|
||||||
"mirror_labels": false,
|
|
||||||
"mirror_milestones": false,
|
|
||||||
"mirror_wiki": false,
|
|
||||||
"mirror_releases": false,
|
|
||||||
"last_mirror_timestamp": 1741891512,
|
|
||||||
"last_mirror_date": "2025-03-13 18:45:12",
|
|
||||||
"last_mirror_log": "mirror-2025-03-13.log"
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"mirror_metadata": true,
|
|
||||||
"mirror_issues": true,
|
|
||||||
"mirror_pull_requests": true,
|
|
||||||
"mirror_labels": true,
|
|
||||||
"mirror_milestones": true,
|
|
||||||
"mirror_wiki": true,
|
|
||||||
"mirror_releases": true,
|
|
||||||
"last_mirror_timestamp": 1741934040,
|
|
||||||
"last_mirror_date": "2025-03-14 06:34:00",
|
|
||||||
"last_mirror_status": "warning",
|
|
||||||
"last_mirror_messages": [
|
|
||||||
"Failed to mirror wiki: Wiki mirroring failed"
|
|
||||||
],
|
|
||||||
"last_mirror_log": "mirror-2025-03-14.log"
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"mirror_metadata": false,
|
|
||||||
"mirror_issues": false,
|
|
||||||
"mirror_pull_requests": false,
|
|
||||||
"mirror_labels": false,
|
|
||||||
"mirror_milestones": false,
|
|
||||||
"mirror_wiki": false,
|
|
||||||
"mirror_releases": true
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"mirror_metadata": true,
|
|
||||||
"mirror_issues": true,
|
|
||||||
"mirror_pull_requests": true,
|
|
||||||
"mirror_labels": true,
|
|
||||||
"mirror_milestones": true,
|
|
||||||
"mirror_wiki": true,
|
|
||||||
"mirror_releases": true,
|
|
||||||
"last_mirror_timestamp": 1741933088,
|
|
||||||
"last_mirror_date": "2025-03-14 06:18:08",
|
|
||||||
"last_mirror_status": "warning",
|
|
||||||
"last_mirror_messages": [
|
|
||||||
"Failed to mirror wiki: Wiki mirroring failed"
|
|
||||||
],
|
|
||||||
"last_mirror_log": "mirror-2025-03-14.log"
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"mirror_metadata": true,
|
|
||||||
"mirror_issues": false,
|
|
||||||
"mirror_pull_requests": false,
|
|
||||||
"mirror_labels": false,
|
|
||||||
"mirror_milestones": false,
|
|
||||||
"mirror_wiki": true,
|
|
||||||
"mirror_releases": true,
|
|
||||||
"last_mirror_timestamp": 1741935405,
|
|
||||||
"last_mirror_date": "2025-03-14 06:56:45",
|
|
||||||
"last_mirror_status": "success",
|
|
||||||
"last_mirror_messages": [],
|
|
||||||
"last_mirror_log": "mirror-2025-03-14.log"
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "5000:5000"
|
- "5000:5000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./logs:/app/logs
|
- gitmirror_logs:/app/logs
|
||||||
- ./config.json:/app/config.json
|
- ./config.json:/app/config.json
|
||||||
- ./data/config:/app/data/config
|
- ./data/config:/app/data/config
|
||||||
env_file:
|
env_file:
|
||||||
|
@ -21,10 +21,13 @@ services:
|
||||||
mirror:
|
mirror:
|
||||||
build: .
|
build: .
|
||||||
volumes:
|
volumes:
|
||||||
- ./logs:/app/logs
|
- gitmirror_logs:/app/logs
|
||||||
- ./config.json:/app/config.json
|
- ./config.json:/app/config.json
|
||||||
- ./data/config:/app/data/config
|
- ./data/config:/app/data/config
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
command: mirror
|
command: mirror
|
||||||
restart: "no"
|
restart: "no"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
gitmirror_logs:
|
|
@ -11,9 +11,9 @@ from .gitea.repository import (
|
||||||
from .gitea.release import create_gitea_release
|
from .gitea.release import create_gitea_release
|
||||||
from .gitea.metadata import mirror_github_metadata
|
from .gitea.metadata import mirror_github_metadata
|
||||||
from .utils.config import get_repo_config, save_repo_config
|
from .utils.config import get_repo_config, save_repo_config
|
||||||
from .utils.logging import get_current_log_filename
|
from .utils.logging import setup_logging, get_current_log_filename
|
||||||
|
|
||||||
logger = logging.getLogger('github-gitea-mirror')
|
logger = setup_logging(service_name='mirror')
|
||||||
|
|
||||||
def mirror_repository(github_token, gitea_token, gitea_url, github_repo, gitea_owner, gitea_repo, skip_repo_config=False, mirror_metadata=None, mirror_releases=None, repo_config=None, force_recreate=False):
|
def mirror_repository(github_token, gitea_token, gitea_url, github_repo, gitea_owner, gitea_repo, skip_repo_config=False, mirror_metadata=None, mirror_releases=None, repo_config=None, force_recreate=False):
|
||||||
"""Set up a repository as a pull mirror from GitHub to Gitea and sync releases"""
|
"""Set up a repository as a pull mirror from GitHub to Gitea and sync releases"""
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
||||||
|
|
||||||
def setup_logging(log_level='INFO'):
|
def setup_logging(log_level='INFO', service_name='mirror'):
|
||||||
"""Set up logging configuration with log rotation
|
"""Set up logging configuration with log rotation
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
log_level (str): The logging level to use (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
log_level (str): The logging level to use (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||||
|
service_name (str): The name of the service (web or mirror)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
logging.Logger: The configured logger
|
logging.Logger: The configured logger
|
||||||
|
@ -15,8 +16,8 @@ def setup_logging(log_level='INFO'):
|
||||||
log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'logs')
|
log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'logs')
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
|
||||||
timestamp = datetime.now().strftime('%Y-%m-%d')
|
# Get log retention period from environment variable (default to 30 days)
|
||||||
log_file = os.path.join(log_dir, f'mirror-{timestamp}.log')
|
retention_days = int(os.getenv('LOG_RETENTION_DAYS', '30'))
|
||||||
|
|
||||||
# Convert string log level to logging constant
|
# Convert string log level to logging constant
|
||||||
numeric_level = getattr(logging, log_level.upper(), None)
|
numeric_level = getattr(logging, log_level.upper(), None)
|
||||||
|
@ -29,11 +30,13 @@ def setup_logging(log_level='INFO'):
|
||||||
level=numeric_level,
|
level=numeric_level,
|
||||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||||
handlers=[
|
handlers=[
|
||||||
# Use RotatingFileHandler instead of FileHandler for log rotation
|
# Use TimedRotatingFileHandler for daily rotation and retention
|
||||||
RotatingFileHandler(
|
TimedRotatingFileHandler(
|
||||||
log_file,
|
os.path.join(log_dir, f'{service_name}.log'),
|
||||||
maxBytes=10 * 1024 * 1024, # 10 MB
|
when='midnight',
|
||||||
backupCount=5, # Keep 5 backup files
|
interval=1, # daily
|
||||||
|
backupCount=retention_days, # keep logs for specified number of days
|
||||||
|
encoding='utf-8'
|
||||||
),
|
),
|
||||||
logging.StreamHandler()
|
logging.StreamHandler()
|
||||||
]
|
]
|
||||||
|
@ -43,7 +46,7 @@ def setup_logging(log_level='INFO'):
|
||||||
logging.getLogger('requests').setLevel(logging.WARNING)
|
logging.getLogger('requests').setLevel(logging.WARNING)
|
||||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||||
|
|
||||||
return logging.getLogger('github-gitea-mirror')
|
return logging.getLogger(f'github-gitea-mirror-{service_name}')
|
||||||
|
|
||||||
def get_current_log_filename(logger):
|
def get_current_log_filename(logger):
|
||||||
"""Get the current log file name from the logger handlers
|
"""Get the current log file name from the logger handlers
|
||||||
|
@ -55,7 +58,7 @@ def get_current_log_filename(logger):
|
||||||
str: The basename of the log file, or a fallback name if not found
|
str: The basename of the log file, or a fallback name if not found
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Check for both RotatingFileHandler and regular FileHandler
|
# Check for both RotatingFileHandler and TimedRotatingFileHandler
|
||||||
for handler in logger.handlers:
|
for handler in logger.handlers:
|
||||||
if hasattr(handler, 'baseFilename'):
|
if hasattr(handler, 'baseFilename'):
|
||||||
return os.path.basename(handler.baseFilename)
|
return os.path.basename(handler.baseFilename)
|
||||||
|
|
|
@ -26,7 +26,7 @@ from .gitea.metadata import mirror_github_metadata, delete_all_issues_and_prs
|
||||||
from .mirror import mirror_repository, process_all_repositories
|
from .mirror import mirror_repository, process_all_repositories
|
||||||
|
|
||||||
# Set up logging
|
# Set up logging
|
||||||
logger = logging.getLogger('github-gitea-mirror')
|
logger = setup_logging(service_name='web')
|
||||||
|
|
||||||
# Create Flask app
|
# Create Flask app
|
||||||
app = Flask(__name__, template_folder=os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'templates'))
|
app = Flask(__name__, template_folder=os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'templates'))
|
||||||
|
|
8
start.sh
8
start.sh
|
@ -1,14 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Create necessary directories
|
|
||||||
mkdir -p /app/logs
|
|
||||||
mkdir -p /app/data/config
|
|
||||||
|
|
||||||
# Set default permissions
|
|
||||||
chmod -R 755 /app/logs
|
|
||||||
chmod -R 755 /app/data/config
|
|
||||||
|
|
||||||
# Generate a random SECRET_KEY if not provided
|
# Generate a random SECRET_KEY if not provided
|
||||||
if [ -z "$SECRET_KEY" ]; then
|
if [ -z "$SECRET_KEY" ]; then
|
||||||
echo "No SECRET_KEY found in environment, generating a random one..."
|
echo "No SECRET_KEY found in environment, generating a random one..."
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue