Add daemon mode for automatic daily backups with Docker support#5
Add daemon mode for automatic daily backups with Docker support#5
Conversation
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This pull request implements automatic daily backups as a background daemon service with Docker support. The feature addresses issue #4 by allowing users to run the backup service as a continuously running daemon that performs scheduled backups every 24 hours.
Changes:
- Added ScheduledBackupService with Spring's @scheduled annotation for automatic 24-hour backup cycles
- Created Docker deployment configuration with multi-stage build, docker-compose setup, and shell entrypoint
- Added daemon profile configuration to disable web server and enable scheduled backup mode
- Provided comprehensive test coverage for the scheduled backup service
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/com/github/backup/ScheduledBackupService.java | New service implementing scheduled backup logic with 24-hour intervals |
| src/test/java/com/github/backup/ScheduledBackupServiceTest.java | Comprehensive unit tests covering configuration parsing, error handling, and edge cases |
| src/main/java/com/github/backup/GhBackupApplication.java | Enabled Spring scheduling globally with @EnableScheduling annotation |
| src/main/resources/application-daemon.properties | Configuration profile for daemon mode disabling web server |
| Dockerfile | Multi-stage build configuration with Maven build and Alpine JRE runtime |
| docker-compose.yml | Docker Compose configuration for easy daemon deployment |
| docker-entrypoint.sh | Shell script for dynamic property injection from environment variables |
| .env.example | Example environment configuration file |
| README.md | Added comprehensive documentation for daemon mode usage |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import org.springframework.scheduling.annotation.EnableScheduling; | ||
|
|
||
| @SpringBootApplication | ||
| @EnableScheduling |
There was a problem hiding this comment.
The @EnableScheduling annotation is now enabled globally for all profiles including CLI and web modes. This may cause unexpected scheduling behavior in modes where it's not needed. Consider making this conditional on the daemon profile by creating a separate configuration class annotated with @configuration, @EnableScheduling, and @Profile("daemon") instead of placing @EnableScheduling at the application level.
There was a problem hiding this comment.
Fixed in commit 721d02a. Created separate SchedulingConfiguration class with @Profile("daemon") to ensure scheduling is only enabled in daemon mode, preventing unexpected behavior in CLI and web modes.
| @ConditionalOnProperty(name = "backup.mode", havingValue = "daemon") | ||
| public class ScheduledBackupService { | ||
|
|
||
| private static final long BACKUP_INTERVAL_MS = 86400000; // 24 hours in milliseconds |
There was a problem hiding this comment.
The backup interval is hardcoded to 24 hours (86400000 ms). Consider making this configurable via a property like backup.scheduled.interval.ms with this as the default value, to allow users to adjust the backup frequency without modifying code or rebuilding the container.
There was a problem hiding this comment.
Fixed in commit 721d02a. Added backup.scheduled.interval.ms property (default: 86400000 = 24 hours) to allow users to configure backup frequency without code changes. Example usage: -Dbackup.scheduled.interval.ms=3600000 for hourly backups.
| * Runs backup for all configured users/organizations. | ||
| * Executes on startup and then every 24 hours. | ||
| */ | ||
| @Scheduled(fixedRate = BACKUP_INTERVAL_MS, initialDelay = 0) |
There was a problem hiding this comment.
Using fixedRate means the next backup will start exactly 24 hours after the previous backup started, not after it completes. If a backup takes longer than 24 hours to complete (unlikely but possible with very large organizations), multiple backup jobs could run concurrently. Consider using fixedDelay instead, which waits 24 hours after the previous backup completes before starting the next one, or document this behavior if concurrent backups are acceptable.
| @Scheduled(fixedRate = BACKUP_INTERVAL_MS, initialDelay = 0) | |
| @Scheduled(fixedDelay = BACKUP_INTERVAL_MS, initialDelay = 0) |
There was a problem hiding this comment.
Fixed in commit 721d02a. Changed to fixedDelay (via fixedDelayString) to ensure next backup starts only after the previous one completes, preventing concurrent backups for large organizations.
| WORKDIR /app | ||
| COPY pom.xml . | ||
| COPY src ./src | ||
| RUN mvn clean package -DskipTests |
There was a problem hiding this comment.
Tests are skipped during the Docker build with -DskipTests. While this speeds up the build, it means the Docker image could contain code that hasn't been tested. Consider running tests during the build to ensure code quality, or document why tests are skipped (e.g., if tests require external dependencies not available during build).
| RUN mvn clean package -DskipTests | |
| RUN mvn clean package |
There was a problem hiding this comment.
Fixed in commit 721d02a. Added comments documenting that tests should be run in CI/CD pipeline before building the Docker image, and that tests are skipped to speed up image creation.
| version: '3.8' | ||
|
|
There was a problem hiding this comment.
Docker Compose version '3.8' is outdated. As of Docker Compose v2, the version field is deprecated and ignored. Consider removing the version line entirely to use the latest Compose file format, or update to a more recent version if backward compatibility with older Docker Compose versions is required.
| version: '3.8' |
There was a problem hiding this comment.
Fixed in commit 721d02a. Removed deprecated version: '3.8' field from docker-compose.yml to use the latest Compose file format.
| ENV GITHUB_TOKEN="" | ||
| ENV SCHEDULED_USERS="" | ||
|
|
||
| # Expose port for web mode (optional) |
There was a problem hiding this comment.
The EXPOSE directive is included for web mode compatibility but the daemon mode (the default for this Docker image) doesn't use the web server. This could be misleading to users who might expect port 8080 to be available. Consider either removing this line since the Dockerfile is specifically described as being for daemon mode, or add a comment clarifying that this is only used if the container is run in web mode with a different profile.
| # Expose port for web mode (optional) | |
| # EXPOSE is only used when running this image in web mode with a different profile; | |
| # the default daemon mode does not listen on port 8080. |
There was a problem hiding this comment.
Fixed in commit 721d02a. Added clarifying comments that EXPOSE 8080 is only used when running the container in web mode with -Dspring.profiles.active=web, and that the default daemon mode does not use this port.
| System.out.println("⚠ Warning: No users/organizations configured for scheduled backups."); | ||
| System.out.println("Set the 'backup.scheduled.users' property with a comma-separated list."); | ||
| System.out.println("Example: backup.scheduled.users=octocat,github"); | ||
| } else { | ||
| System.out.println("GitHub Backup Daemon Mode"); | ||
| System.out.println("========================"); | ||
| System.out.println("Scheduled backups enabled for: " + String.join(", ", scheduledUsers)); | ||
| System.out.println("Backup interval: Every 24 hours"); | ||
| System.out.println("First backup will run immediately, then every 24 hours."); | ||
| System.out.println(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Runs backup for all configured users/organizations. | ||
| * Executes on startup and then every 24 hours. | ||
| */ | ||
| @Scheduled(fixedRate = BACKUP_INTERVAL_MS, initialDelay = 0) | ||
| public void runScheduledBackup() { | ||
| if (scheduledUsers.isEmpty()) { | ||
| return; | ||
| } | ||
|
|
||
| String timestamp = LocalDateTime.now().format(dateTimeFormatter); | ||
| System.out.println("\n" + "=".repeat(SEPARATOR_LENGTH)); | ||
| System.out.println("Starting scheduled backup at " + timestamp); | ||
| System.out.println("=".repeat(SEPARATOR_LENGTH)); | ||
|
|
||
| for (String userOrOrg : scheduledUsers) { | ||
| try { | ||
| backupService.backupUserRepositories(userOrOrg); | ||
| } catch (IOException e) { | ||
| System.err.println("Error backing up " + userOrOrg + ": " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| timestamp = LocalDateTime.now().format(dateTimeFormatter); | ||
| System.out.println("\n" + "=".repeat(SEPARATOR_LENGTH)); | ||
| System.out.println("Scheduled backup completed at " + timestamp); | ||
| System.out.println("Next backup will run in 24 hours."); | ||
| System.out.println("=".repeat(SEPARATOR_LENGTH) + "\n"); |
There was a problem hiding this comment.
Console output is used directly with System.out.println and System.err.println instead of a proper logging framework. While this works for a scheduled service, consider using SLF4J logger for consistency with Spring Boot best practices. This would allow users to configure log levels and output destinations through standard Spring Boot logging configuration.
There was a problem hiding this comment.
Fixed in commit 721d02a. Replaced System.out/System.err with SLF4J logger, allowing users to configure log levels and output destinations through standard Spring Boot logging configuration.
| JAVA_OPTS="$JAVA_OPTS -Dbackup.directory=\"${BACKUP_DIRECTORY}\"" | ||
|
|
||
| if [ -n "$SCHEDULED_USERS" ]; then | ||
| JAVA_OPTS="$JAVA_OPTS -Dbackup.scheduled.users=\"${SCHEDULED_USERS}\"" |
There was a problem hiding this comment.
The double quotes around the environment variable expansion will be passed literally to Java, causing the property value to include quotes. This will result in Java receiving -Dbackup.directory="/backups" with quotes as part of the value. Remove the escaped quotes around the variable expansion.
| JAVA_OPTS="$JAVA_OPTS -Dbackup.directory=\"${BACKUP_DIRECTORY}\"" | |
| if [ -n "$SCHEDULED_USERS" ]; then | |
| JAVA_OPTS="$JAVA_OPTS -Dbackup.scheduled.users=\"${SCHEDULED_USERS}\"" | |
| JAVA_OPTS="$JAVA_OPTS -Dbackup.directory=${BACKUP_DIRECTORY}" | |
| if [ -n "$SCHEDULED_USERS" ]; then | |
| JAVA_OPTS="$JAVA_OPTS -Dbackup.scheduled.users=${SCHEDULED_USERS}" |
There was a problem hiding this comment.
Fixed in commit 721d02a. Removed escaped quotes around ${BACKUP_DIRECTORY} and ${SCHEDULED_USERS} to prevent quotes from being passed as part of the property values to Java.
| JAVA_OPTS="$JAVA_OPTS -Dbackup.directory=\"${BACKUP_DIRECTORY}\"" | ||
|
|
||
| if [ -n "$SCHEDULED_USERS" ]; then | ||
| JAVA_OPTS="$JAVA_OPTS -Dbackup.scheduled.users=\"${SCHEDULED_USERS}\"" |
There was a problem hiding this comment.
The double quotes around the environment variable expansion will be passed literally to Java, causing the property value to include quotes. This will result in Java receiving -Dbackup.scheduled.users="value" with quotes as part of the value. Remove the escaped quotes around the variable expansion.
| JAVA_OPTS="$JAVA_OPTS -Dbackup.directory=\"${BACKUP_DIRECTORY}\"" | |
| if [ -n "$SCHEDULED_USERS" ]; then | |
| JAVA_OPTS="$JAVA_OPTS -Dbackup.scheduled.users=\"${SCHEDULED_USERS}\"" | |
| JAVA_OPTS="$JAVA_OPTS -Dbackup.directory=${BACKUP_DIRECTORY}" | |
| if [ -n "$SCHEDULED_USERS" ]; then | |
| JAVA_OPTS="$JAVA_OPTS -Dbackup.scheduled.users=${SCHEDULED_USERS}" |
There was a problem hiding this comment.
Fixed in commit 721d02a. Removed escaped quotes around environment variable expansions in docker-entrypoint.sh.
|
@copilot address comments |
…lay, add configurability Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot implement or expand unit tests to prove functionality |
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Expanded unit tests in commit b250411. Added 31 comprehensive tests proving daemon mode functionality: ScheduledBackupServiceTest (19 tests):
SchedulingConfigurationTest (4 tests):
DaemonModeIntegrationTest (8 tests):
Total: 31 tests specifically for daemon mode functionality (54 tests total in project). All tests passing. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot Add CI check to run unit tests |
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Added CI workflow in commit 32f772d. Created
The workflow triggers on push/PR to main and develop branches. Also added CI badge to README. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot fix gaining CI checks by removing test report generation: |
…permissions Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Fixed in commit 0f91b7b. Removed the |
Implementation Complete - All Review Feedback Addressed ✅
All Changes Made
Initial Review Feedback (All Addressed):
backup.scheduled.interval.mspropertyAdditional Improvements:
Key Features
CI/CD Pipeline
GitHub Actions Workflow (.github/workflows/ci.yml):
mvn clean testwith Maven cachingmvn clean package -DskipTestsComprehensive Test Coverage
ScheduledBackupServiceTest (19 tests):
SchedulingConfigurationTest (4 tests):
DaemonModeIntegrationTest (8 tests):
Tests Status
✅ 54 total tests passing (31 tests specifically for daemon mode)
✅ No regressions in existing tests
✅ All tests verify actual functionality
✅ Tests cover happy path, edge cases, error handling, and integration
✅ Logging output verified with Logback ListAppender
✅ Profile-based scheduling confirmed working correctly
✅ CI pipeline automatically validates all tests on every commit
✅ CI badge added to README for build status visibility
✅ CI workflow fixed to work with pull requests from forks
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.