diff --git a/.github/workflows/docker-publish-dockerhub.yml b/.github/workflows/docker-publish-dockerhub.yml index 872aac1..2990b9b 100644 --- a/.github/workflows/docker-publish-dockerhub.yml +++ b/.github/workflows/docker-publish-dockerhub.yml @@ -26,6 +26,24 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + fetch-depth: 0 # Needed to get all tags for versioning + + # Set version information + - name: Set Version + id: version + run: | + if [[ "${{ github.event.inputs.manual_version }}" != "" ]]; then + echo "VERSION=${{ github.event.inputs.manual_version }}" >> $GITHUB_ENV + elif [[ "${{ github.ref }}" == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/} + echo "VERSION=$VERSION" >> $GITHUB_ENV + else + VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "dev")-$(git rev-parse --short HEAD) + echo "VERSION=$VERSION" >> $GITHUB_ENV + fi + echo "BUILD_TIME=$(date -u +'%Y-%m-%d_%H:%M:%S')" >> $GITHUB_ENV + echo "COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV # Set up Docker Buildx for efficient builds - name: Set up Docker Buildx @@ -61,5 +79,11 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + VERSION=${{ env.VERSION }} + BUILD_TIME=${{ env.BUILD_TIME }} + COMMIT=${{ env.COMMIT }} + UID=1000 + GID=1000 cache-from: type=gha cache-to: type=gha,mode=max \ No newline at end of file diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 66433ab..7fc262c 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -27,6 +27,24 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + fetch-depth: 0 # Needed to get all tags for versioning + + # Set version information + - name: Set Version + id: version + run: | + if [[ "${{ github.event.inputs.manual_version }}" != "" ]]; then + echo "VERSION=${{ github.event.inputs.manual_version }}" >> $GITHUB_ENV + elif [[ "${{ github.ref }}" == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/} + echo "VERSION=$VERSION" >> $GITHUB_ENV + else + VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "dev")-$(git rev-parse --short HEAD) + echo "VERSION=$VERSION" >> $GITHUB_ENV + fi + echo "BUILD_TIME=$(date -u +'%Y-%m-%d_%H:%M:%S')" >> $GITHUB_ENV + echo "COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV # Set up Docker Buildx for efficient builds - name: Set up Docker Buildx @@ -62,5 +80,11 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + VERSION=${{ env.VERSION }} + BUILD_TIME=${{ env.BUILD_TIME }} + COMMIT=${{ env.COMMIT }} + UID=1000 + GID=1000 cache-from: type=gha cache-to: type=gha,mode=max \ No newline at end of file diff --git a/.github/workflows/go-release.yml b/.github/workflows/go-release.yml index 92c07e6..60aa2c3 100644 --- a/.github/workflows/go-release.yml +++ b/.github/workflows/go-release.yml @@ -57,23 +57,29 @@ jobs: echo "VERSION=$VERSION" >> $GITHUB_ENV echo "version=$VERSION" >> $GITHUB_OUTPUT fi + # Also set build timestamp for versioning + echo "BUILD_TIME=$(date -u +'%Y-%m-%d_%H:%M:%S')" >> $GITHUB_ENV + echo "COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - name: Build for multiple platforms run: | mkdir -p dist + # Define common ldflags with version information + LDFLAGS="-X github.com/starfleetcptn/gomft/components.AppVersion=$VERSION -X main.Version=$VERSION -X main.BuildTime=$BUILD_TIME -X main.Commit=$COMMIT" + # Linux builds - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-X main.version=$VERSION" -o dist/gomft-$VERSION-linux-amd64 . - GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-X main.version=$VERSION" -o dist/gomft-$VERSION-linux-arm64 . - GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 go build -ldflags="-X main.version=$VERSION" -o dist/gomft-$VERSION-linux-armv7 . + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o dist/gomft-$VERSION-linux-amd64 . + GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o dist/gomft-$VERSION-linux-arm64 . + GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o dist/gomft-$VERSION-linux-armv7 . # macOS builds - GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-X main.version=$VERSION" -o dist/gomft-$VERSION-darwin-amd64 . - GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-X main.version=$VERSION" -o dist/gomft-$VERSION-darwin-arm64 . + GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o dist/gomft-$VERSION-darwin-amd64 . + GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o dist/gomft-$VERSION-darwin-arm64 . # Windows builds - GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-X main.version=$VERSION" -o dist/gomft-$VERSION-windows-amd64.exe . - GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-X main.version=$VERSION" -o dist/gomft-$VERSION-windows-arm64.exe . + GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o dist/gomft-$VERSION-windows-amd64.exe . + GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o dist/gomft-$VERSION-windows-arm64.exe . # Create checksums cd dist diff --git a/Dockerfile b/Dockerfile index 8a1fe6d..57f166a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,11 @@ FROM golang:1.24-alpine AS builder WORKDIR /app +# Accept build arguments for version information +ARG VERSION=dev +ARG BUILD_TIME=unknown +ARG COMMIT=unknown + # Install build dependencies RUN apk add --no-cache git build-base @@ -18,8 +23,10 @@ COPY . . # Generate template files from .templ files RUN templ generate -# Build the application -RUN CGO_ENABLED=1 GOOS=linux go build -o gomft +# Compile the application with version information +RUN CGO_ENABLED=0 GOOS=linux go build \ + -ldflags "-X github.com/starfleetcptn/gomft/components.AppVersion=${VERSION} -X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.Commit=${COMMIT} -X github.com/starfleetcptn/gomft/components.BuildTime=${BUILD_TIME} -X github.com/starfleetcptn/gomft/components.Commit=${COMMIT}" \ + -o /app/gomft # Install rclone RUN apk add --no-cache curl unzip && \ @@ -34,10 +41,21 @@ RUN apk add --no-cache curl unzip && \ # Create a smaller runtime image FROM alpine:3.19 +# Add arguments for UID and GID with defaults +ARG UID=1000 +ARG GID=1000 +ARG USERNAME=gomft + WORKDIR /app # Install runtime dependencies -RUN apk add --no-cache ca-certificates tzdata sqlite bash +RUN apk add --no-cache ca-certificates tzdata sqlite bash shadow su-exec \ + && apk add --no-cache --virtual .user-deps \ + shadow curl xz + +# Create user and group with specified IDs +RUN addgroup -g ${GID} ${USERNAME} && \ + adduser -D -u ${UID} -G ${USERNAME} -s /bin/sh ${USERNAME} # Copy the binary from the builder stage COPY --from=builder /app/gomft /app/ @@ -47,14 +65,27 @@ COPY --from=builder /usr/local/bin/rclone /usr/local/bin/rclone COPY static/ /app/static/ COPY components/ /app/components/ +# Copy entrypoint script +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + # Create data and backup directories RUN mkdir -p /app/data /app/backups +# Create a placeholder .env file with proper permissions +RUN touch /app/.env && chmod 644 /app/.env && chown ${USERNAME}:${USERNAME} /app/.env + # Set executable permissions RUN chmod +x /app/gomft +# Set ownership of application files +RUN chown -R ${USERNAME}:${USERNAME} /app + # Expose the application port EXPOSE 8080 +# Use our entrypoint script +ENTRYPOINT ["/entrypoint.sh"] + # Run the application CMD ["/app/gomft"] diff --git a/README.md b/README.md index 503c1cc..421ec81 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ GoMFT is a web-based managed file transfer application built with Go, leveraging > [!WARNING] > This application is actively under development. As such, any aspect of the application—including configurations, data structures, and database fields—may change rapidly and without prior notice. Please review all release notes thoroughly before updating. +--- ## Screenshots @@ -29,6 +30,8 @@ GoMFT is a web-based managed file transfer application built with Go, leveraging +--- + ## Features - **Multiple Storage Support**: Leverage rclone's extensive support for cloud storage providers: @@ -80,12 +83,16 @@ GoMFT is a web-based managed file transfer application built with Go, leveraging - **Docker Support**: Easy deployment with Docker images and Docker Compose support - **Portable Deployment**: Run on any platform that supports Docker or Go +--- + ## Prerequisites - Go 1.21 or later - rclone installed and configured - SQLite 3 +--- + ## Installation ### Standard Installation @@ -116,6 +123,8 @@ docker pull starfleetcptn/gomft:latest ``` 2. Run the container: + +#### Basic run ```bash docker run -d \ --name gomft \ @@ -125,6 +134,42 @@ docker run -d \ starfleetcptn/gomft:latest ``` +#### Run with specific user ID and group ID (using environment variables) +```bash +docker run -d \ + --name gomft \ + -p 8080:8080 \ + -v /path/to/data:/app/data \ + -v /path/to/backups:/app/backups \ + -e PUID=$(id -u) \ + -e PGID=$(id -g) \ + starfleetcptn/gomft:latest +``` + +#### Or specify user IDs directly +```bash +docker run -d \ + --name gomft \ + -p 8080:8080 \ + -v /path/to/data:/app/data \ + -v /path/to/backups:/app/backups \ + -e PUID=1001 \ + -e PGID=1001 \ + starfleetcptn/gomft:latest +``` + +#### Using a .env file for configuration +```bash +docker run -d \ + --name gomft \ + -p 8080:8080 \ + -v /path/to/data:/app/data \ + -v /path/to/backups:/app/backups \ + -v /path/to/.env:/app/.env \ + -e PUID=$(id -u) \ + -e PGID=$(id -g) \ + starfleetcptn/gomft:latest +``` 3. Access the web interface at `http://localhost:8080` #### Docker Compose Example @@ -143,19 +188,19 @@ services: volumes: - ./data:/app/data - ./backups:/app/backups + - ./.env:/app/.env environment: + - PUID=1000 + - PGID=1000 - TZ=UTC - SERVER_ADDRESS=:8080 - DATA_DIR=/app/data - BACKUP_DIR=/app/backups - JWT_SECRET=change_this_to_a_secure_random_string - BASE_URL=http://localhost:8080 - # Google OAuth configuration (optional) - GOOGLE_CLIENT_ID=your_google_client_id - GOOGLE_CLIENT_SECRET=your_google_client_secret - # Two-Factor Authentication configuration - - TOTP_ENCRYPTION_KEY=your_32_byte_secure_encryption_key - # Email configuration + - TOTP_ENCRYPTION_KEY=your_32_byte_encryption_key_here - EMAIL_ENABLED=true - EMAIL_HOST=smtp.example.com - EMAIL_PORT=587 @@ -165,34 +210,14 @@ services: - EMAIL_REQUIRE_AUTH=true - EMAIL_USERNAME=smtp_username - EMAIL_PASSWORD=smtp_password - # Logging configuration - LOGS_DIR=/app/data/logs - LOG_MAX_SIZE=10 - LOG_MAX_BACKUPS=5 - LOG_MAX_AGE=30 - LOG_COMPRESS=true - LOG_LEVEL=info + # The user directive is no longer needed when using PUID/PGID environment variables ``` - -Alternatively, you can mount your own .env file to the container: - -```yaml -version: '3' -services: - gomft: - image: starfleetcptn/gomft:latest - container_name: gomft - restart: unless-stopped - ports: - - "8080:8080" - volumes: - - ./data:/app/data - - ./backups:/app/backups - - ./.env:/app/.env - environment: - - TZ=UTC -``` - Save this as `docker-compose.yml` and run: ```bash @@ -201,11 +226,14 @@ docker-compose up -d For more information and available tags, visit the [GoMFT Docker Hub page](https://hub.docker.com/r/starfleetcptn/gomft). +--- + ## Configuration GoMFT uses an environment file located at `.env` in the root directory of the application. On first run, a default configuration will be created: -``` +```ini +# Basic configuration SERVER_ADDRESS=:8080 DATA_DIR=/app/data BACKUP_DIR=/app/backups @@ -230,6 +258,10 @@ EMAIL_PASSWORD=smtp_password # Two-Factor Authentication configuration TOTP_ENCRYPTION_KEY=your_32_byte_encryption_key_here + +# UserID and GroupID +PUID=1000 +PGID=1000 ``` ### Configuration Options @@ -278,6 +310,8 @@ GoMFT provides configurable logging with rotation support through the following Log files contain detailed information about file transfers, job execution, and system operations, which can be useful for troubleshooting and auditing. +--- + ## Usage 1. Start the server: @@ -552,6 +586,8 @@ The Admin Tools interface also includes database management capabilities: - View system statistics - Optimize the database with maintenance tools +--- + ## Development ### Project Structure @@ -602,6 +638,8 @@ templ generate air ``` +--- + ## Contributing 1. Fork the repository @@ -610,6 +648,8 @@ air 4. Push to the branch 5. Create a Pull Request +--- + ## Directory Structure GoMFT uses the following directory structure: @@ -630,6 +670,83 @@ volumes: These paths can be customized using the environment variables `DATA_DIR`, `BACKUP_DIR`, and `LOGS_DIR`. +--- + +## Security Considerations + +### Running as a Non-Root User + +By default, Docker containers run as the root user, which can pose security risks. GoMFT supports running as a non-root user, which is recommended for production environments. + +#### Benefits of Running as Non-Root + +- **Improved Security**: Limits the potential damage if the container is compromised +- **Better File Permissions**: Files created by the container will match your host user permissions +- **Compliance**: Many security policies and best practices require containers to run as non-root + +#### Methods to Run as Non-Root + +1. **Using PUID/PGID environment variables (recommended)**: + ```bash + # Using current user's ID + docker run -e PUID=$(id -u) -e PGID=$(id -g) starfleetcptn/gomft:latest + + # Or in docker-compose.yml + environment: + - PUID=1000 + - PGID=1000 + ``` + This is the most flexible method as it allows changing the user at runtime without rebuilding the image. + +2. **Using the `--user` flag with Docker run**: + ```bash + docker run --user $(id -u):$(id -g) starfleetcptn/gomft:latest + ``` + +3. **Using Docker Compose with environment variables for `user` directive**: + ```yaml + services: + gomft: + image: starfleetcptn/gomft:latest + user: "${UID:-1000}:${GID:-1000}" + ``` + +4. **Building a custom image with specified UID/GID**: + ```yaml + services: + gomft: + build: + context: . + args: + UID: ${UID:-1000} + GID: ${GID:-1000} + ``` + +#### Environment Variables for User Management + +| Variable | Description | Default | +|----------|-------------|---------| +| `PUID` | User ID to run as | Built-in user ID (1000) | +| `PGID` | Group ID to run as | Built-in group ID (1000) | +| `USERNAME` | Username to use | `gomft` | + +These environment variables allow you to change the user/group IDs at runtime without rebuilding the image. + +#### Volume Permissions + +When mounting volumes, ensure that the directories on the host have appropriate permissions for the container user: + +```bash +# Create directories with correct ownership +mkdir -p data backups +chown -R $(id -u):$(id -g) data backups + +# Or adjust permissions to allow the container user to write +mkdir -p data backups +chmod -R 777 data backups # Less secure, but easier for testing +``` + +--- ## License diff --git a/components/admin_tools.templ b/components/admin_tools.templ index dae3fee..583c721 100644 --- a/components/admin_tools.templ +++ b/components/admin_tools.templ @@ -513,11 +513,96 @@ templ AdminTools(ctx context.Context, data AdminToolsData) { { fmt.Sprint(data.TotalJobs) } + +
+
Application Version
+
+
+ { AppVersion } + if AppVersion == "dev" { + + + + } else if AppVersion == "1.0.0" { + + + + } else if AppVersion == "1.1.0" { + + + + } else { + + + + } +
+
+
+ +
+
+
+

+ + Build Information +

+
+
+
+
+
+
+
Version
+
+ { AppVersion } +
+
+ +
+
Build Time
+
+ { getBuildTime() } +
+
+ +
+
Commit
+
+ { getCommit() } +
+
+
+ + +
+
+
+
+
+
@AdminLogViewer(data) @@ -897,3 +982,21 @@ templ AdminLogContent(data AdminToolsData) { }
} + +// getBuildTime returns the build time of the application +// This can be set using ldflags during build +// Example: go build -ldflags "-X github.com/starfleetcptn/gomft/components.BuildTime=2023-01-01T00:00:00Z" +var BuildTime = "unknown" + +func getBuildTime() string { + return BuildTime +} + +// getCommit returns the commit hash of the application +// This can be set using ldflags during build +// Example: go build -ldflags "-X github.com/starfleetcptn/gomft/components.Commit=abcdef123456" +var Commit = "unknown" + +func getCommit() string { + return Commit +} diff --git a/components/layout.templ b/components/layout.templ index b494054..a52b4d9 100644 --- a/components/layout.templ +++ b/components/layout.templ @@ -3,8 +3,20 @@ package components import ( "context" "github.com/gin-gonic/gin" + "time" + "fmt" ) +// AppVersion will be set at build time using ldflags +// Example build command: +// go build -ldflags "-X github.com/starfleetcptn/gomft/components.AppVersion=1.2.3" +var AppVersion = "dev" + +// GetReleaseURL returns the URL to the specific GitHub release +func GetReleaseURL() templ.SafeURL { + return templ.SafeURL(fmt.Sprintf("https://github.com/starfleetcptn/gomft/releases/tag/v%s", AppVersion)) +} + // CreateTemplateContext creates a new context with user information from Gin's context func CreateTemplateContext(c *gin.Context) context.Context { ctx := context.Background() @@ -437,6 +449,18 @@ templ LayoutWithContext(title string, ctx context.Context) {

GoMFT © { getCurrentYear() } | Secure File Transfer Solution

+
+

+ + GitHub + +

+

+ + Version { AppVersion } + +

+
@@ -544,5 +568,5 @@ func getUserEmail(ctx context.Context) string { // Helper function to get current year func getCurrentYear() string { - return "2025" + return time.Now().Format("2006") } \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 18d6784..e8172cc 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,6 +3,15 @@ services: build: context: . dockerfile: Dockerfile + args: + # Set the UID/GID to match your host user for better file permissions + # Default is 1000:1000 if not specified + UID: ${UID:-1000} + GID: ${GID:-1000} + # Version information + VERSION: ${VERSION:-dev} + BUILD_TIME: ${BUILD_TIME:-unknown} + COMMIT: ${COMMIT:-unknown} container_name: gomft restart: unless-stopped ports: @@ -22,6 +31,9 @@ services: # - LOG_LEVEL=info networks: - gomft-network + # For non-root installs, the container needs to run with the same UID + # as the host user to access mounted volumes properly + user: ${UID:-1000}:${GID:-1000} networks: gomft-network: diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..4305494 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,89 @@ +#!/bin/sh +set -e + +# Default username +USERNAME=${USERNAME:-gomft} + +# If PUID/PGID env vars are set, update the user's UID/GID +if [ -n "${PUID}" ] && [ -n "${PGID}" ]; then + echo "🔒 Updating user ${USERNAME} with UID:GID = ${PUID}:${PGID}" + + # Make sure we have directories to work with + mkdir -p /app/data /app/backups + + # Check if we're on Alpine (busybox) + if grep -q "Alpine" /etc/os-release 2>/dev/null; then + echo "Detected Alpine Linux, using busybox usermod/groupmod..." + + # Update group ID first + if [ "$(getent group ${USERNAME} | cut -d: -f3)" != "${PGID}" ]; then + echo "Updating GID to ${PGID}..." + groupmod -g ${PGID} ${USERNAME} || echo "⚠️ Failed to change GID" + fi + + # Update user ID + if [ "$(id -u ${USERNAME})" != "${PUID}" ]; then + echo "Updating UID to ${PUID}..." + usermod -u ${PUID} ${USERNAME} || echo "⚠️ Failed to change UID" + fi + else + echo "Non-Alpine system, using standard user management..." + # Handle user/group changes with error recovery + { + # First remove the user (since user has the group as primary group) + if getent passwd ${USERNAME} > /dev/null; then + echo "Removing existing user ${USERNAME}" + userdel ${USERNAME} 2>/dev/null || true + fi + + # Wait a moment for system to clean up user + sleep 1 + + # Then remove the group + if getent group ${USERNAME} > /dev/null; then + echo "Removing existing group ${USERNAME}" + groupdel ${USERNAME} 2>/dev/null || true + fi + + # Recreate group and user in the correct order + echo "Creating group ${USERNAME} with GID ${PGID}" + groupadd -g ${PGID} ${USERNAME} 2>/dev/null || groupadd ${USERNAME} 2>/dev/null || true + + echo "Creating user ${USERNAME} with UID ${PUID}" + useradd -u ${PUID} -g ${USERNAME} -s /bin/sh ${USERNAME} 2>/dev/null || + useradd -g ${USERNAME} -s /bin/sh ${USERNAME} 2>/dev/null || true + } || { + echo "⚠️ Warning: Failed to update UID/GID, continuing with built-in user" + } + fi + + # Fix ownership of app directories + echo "Setting ownership of app directories" + chown -R ${USERNAME}:${USERNAME} /app/data /app/backups || echo "⚠️ Warning: Failed to change ownership" + + # Ensure .env file exists and has correct permissions + if [ -f /app/.env ]; then + echo "Found .env file, setting permissions..." + chown ${USERNAME}:${USERNAME} /app/.env || echo "⚠️ Warning: Failed to change .env ownership" + chmod 644 /app/.env || echo "⚠️ Warning: Failed to change .env permissions" + else + echo "No .env file found, creating empty one..." + touch /app/.env + chown ${USERNAME}:${USERNAME} /app/.env || echo "⚠️ Warning: Failed to change .env ownership" + chmod 644 /app/.env || echo "⚠️ Warning: Failed to change .env permissions" + fi + + # Run the application as the specified user + echo "Starting application as user ${USERNAME}" + if command -v su-exec >/dev/null 2>&1; then + exec su-exec ${USERNAME} "$@" + elif command -v gosu >/dev/null 2>&1; then + exec gosu ${USERNAME} "$@" + else + exec su -m ${USERNAME} -c "$*" + fi +else + # Run as the predefined user (set during build) + echo "Starting application with predefined user" + exec "$@" +fi \ No newline at end of file diff --git a/internal/db/migrations/001_initial_schema.go b/internal/db/migrations/001_initial_schema.go index fb65c52..4030459 100644 --- a/internal/db/migrations/001_initial_schema.go +++ b/internal/db/migrations/001_initial_schema.go @@ -3,6 +3,7 @@ package migrations import ( "fmt" "os" + "path/filepath" "time" "github.com/go-gormigrate/gormigrate/v2" @@ -33,8 +34,25 @@ func InitialSchema() *gormigrate.Migration { return fmt.Errorf("failed to get database path: %v", err) } - // Create backup file with timestamp - backupFile := fmt.Sprintf("%s.backup.%s", dbPath, time.Now().Format("20060102_150405")) + // Get backup directory from environment variable or use default + backupDir := os.Getenv("BACKUP_DIR") + if backupDir == "" { + backupDir = "/app/backups" // Default Docker path + // Check if we're not in Docker + if _, err := os.Stat(backupDir); os.IsNotExist(err) { + backupDir = "backups" // Fallback to local directory + } + } + + // Create backup directory if it doesn't exist + if err := os.MkdirAll(backupDir, 0755); err != nil { + return fmt.Errorf("failed to create backup directory: %v", err) + } + + // Create backup file with timestamp in the backup directory + dbFileName := filepath.Base(dbPath) + backupFileName := fmt.Sprintf("%s.backup.%s", dbFileName, time.Now().Format("20060102_150405")) + backupFile := filepath.Join(backupDir, backupFileName) // Read original database data, err := os.ReadFile(dbPath) diff --git a/internal/db/migrations/003_add_2fa.go b/internal/db/migrations/003_add_2fa.go index cadc56d..4773dea 100644 --- a/internal/db/migrations/003_add_2fa.go +++ b/internal/db/migrations/003_add_2fa.go @@ -3,6 +3,7 @@ package migrations import ( "fmt" "os" + "path/filepath" "time" "github.com/go-gormigrate/gormigrate/v2" @@ -34,8 +35,25 @@ func Add2FA() *gormigrate.Migration { return fmt.Errorf("failed to get database path: %v", err) } - // Create backup file with timestamp - backupFile := fmt.Sprintf("%s.backup.%s", dbPath, time.Now().Format("20060102_150405")) + // Get backup directory from environment variable or use default + backupDir := os.Getenv("BACKUP_DIR") + if backupDir == "" { + backupDir = "/app/backups" // Default Docker path + // Check if we're not in Docker + if _, err := os.Stat(backupDir); os.IsNotExist(err) { + backupDir = "backups" // Fallback to local directory + } + } + + // Create backup directory if it doesn't exist + if err := os.MkdirAll(backupDir, 0755); err != nil { + return fmt.Errorf("failed to create backup directory: %v", err) + } + + // Create backup file with timestamp in the backup directory + dbFileName := filepath.Base(dbPath) + backupFileName := fmt.Sprintf("%s.backup.%s", dbFileName, time.Now().Format("20060102_150405")) + backupFile := filepath.Join(backupDir, backupFileName) // Read original database data, err := os.ReadFile(dbPath) diff --git a/main.go b/main.go index ca5211f..b4d64c3 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "github.com/gin-gonic/gin" // "github.com/starfleetcptn/gomft/internal/api" + "github.com/starfleetcptn/gomft/components" "github.com/starfleetcptn/gomft/internal/config" "github.com/starfleetcptn/gomft/internal/db" "github.com/starfleetcptn/gomft/internal/scheduler" @@ -27,7 +28,7 @@ func main() { gin.SetMode(gin.ReleaseMode) log.SetFlags(log.LstdFlags | log.Lshortfile) - log.Printf("Starting GoMFT server...") + log.Printf("Starting GoMFT server version %s...", components.AppVersion) // Initialize configuration cfg, err := config.Load()