Enhance Docker and Build Configuration

- Added support for build arguments in Dockerfile to specify UID, GID, version, build time, and commit hash.
- Updated docker-compose.yaml to utilize UID and GID for non-root user execution, improving file permission handling.
- Introduced entrypoint.sh script to manage user permissions and environment setup at container startup.
- Enhanced application logging to include version information during startup.
- Updated README with new instructions for running the application with specific user IDs and environment variables.
This commit is contained in:
StarFleetCPTN
2025-03-18 21:23:15 -07:00
parent 83be259754
commit 1981764f80
12 changed files with 509 additions and 42 deletions
@@ -26,6 +26,24 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 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 # Set up Docker Buildx for efficient builds
- name: Set up Docker Buildx - name: Set up Docker Buildx
@@ -61,5 +79,11 @@ jobs:
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} 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-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
+24
View File
@@ -27,6 +27,24 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 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 # Set up Docker Buildx for efficient builds
- name: Set up Docker Buildx - name: Set up Docker Buildx
@@ -62,5 +80,11 @@ jobs:
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} 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-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
+13 -7
View File
@@ -57,23 +57,29 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
fi 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 - name: Build for multiple platforms
run: | run: |
mkdir -p dist 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 # 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=amd64 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -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=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="-X main.version=$VERSION" -o dist/gomft-$VERSION-linux-armv7 . GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o dist/gomft-$VERSION-linux-armv7 .
# macOS builds # 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=amd64 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -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=arm64 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o dist/gomft-$VERSION-darwin-arm64 .
# Windows builds # 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=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="-X main.version=$VERSION" -o dist/gomft-$VERSION-windows-arm64.exe . GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o dist/gomft-$VERSION-windows-arm64.exe .
# Create checksums # Create checksums
cd dist cd dist
+34 -3
View File
@@ -2,6 +2,11 @@ FROM golang:1.24-alpine AS builder
WORKDIR /app WORKDIR /app
# Accept build arguments for version information
ARG VERSION=dev
ARG BUILD_TIME=unknown
ARG COMMIT=unknown
# Install build dependencies # Install build dependencies
RUN apk add --no-cache git build-base RUN apk add --no-cache git build-base
@@ -18,8 +23,10 @@ COPY . .
# Generate template files from .templ files # Generate template files from .templ files
RUN templ generate RUN templ generate
# Build the application # Compile the application with version information
RUN CGO_ENABLED=1 GOOS=linux go build -o gomft 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 # Install rclone
RUN apk add --no-cache curl unzip && \ RUN apk add --no-cache curl unzip && \
@@ -34,10 +41,21 @@ RUN apk add --no-cache curl unzip && \
# Create a smaller runtime image # Create a smaller runtime image
FROM alpine:3.19 FROM alpine:3.19
# Add arguments for UID and GID with defaults
ARG UID=1000
ARG GID=1000
ARG USERNAME=gomft
WORKDIR /app WORKDIR /app
# Install runtime dependencies # 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 the binary from the builder stage
COPY --from=builder /app/gomft /app/ 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 static/ /app/static/
COPY components/ /app/components/ COPY components/ /app/components/
# Copy entrypoint script
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Create data and backup directories # Create data and backup directories
RUN mkdir -p /app/data /app/backups 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 # Set executable permissions
RUN chmod +x /app/gomft RUN chmod +x /app/gomft
# Set ownership of application files
RUN chown -R ${USERNAME}:${USERNAME} /app
# Expose the application port # Expose the application port
EXPOSE 8080 EXPOSE 8080
# Use our entrypoint script
ENTRYPOINT ["/entrypoint.sh"]
# Run the application # Run the application
CMD ["/app/gomft"] CMD ["/app/gomft"]
+143 -26
View File
@@ -9,6 +9,7 @@ GoMFT is a web-based managed file transfer application built with Go, leveraging
> [!WARNING] > [!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. > 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 ## Screenshots
@@ -29,6 +30,8 @@ GoMFT is a web-based managed file transfer application built with Go, leveraging
</tr> </tr>
</table> </table>
---
## Features ## Features
- **Multiple Storage Support**: Leverage rclone's extensive support for cloud storage providers: - **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 - **Docker Support**: Easy deployment with Docker images and Docker Compose support
- **Portable Deployment**: Run on any platform that supports Docker or Go - **Portable Deployment**: Run on any platform that supports Docker or Go
---
## Prerequisites ## Prerequisites
- Go 1.21 or later - Go 1.21 or later
- rclone installed and configured - rclone installed and configured
- SQLite 3 - SQLite 3
---
## Installation ## Installation
### Standard Installation ### Standard Installation
@@ -116,6 +123,8 @@ docker pull starfleetcptn/gomft:latest
``` ```
2. Run the container: 2. Run the container:
#### Basic run
```bash ```bash
docker run -d \ docker run -d \
--name gomft \ --name gomft \
@@ -125,6 +134,42 @@ docker run -d \
starfleetcptn/gomft:latest 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` 3. Access the web interface at `http://localhost:8080`
#### Docker Compose Example #### Docker Compose Example
@@ -143,19 +188,19 @@ services:
volumes: volumes:
- ./data:/app/data - ./data:/app/data
- ./backups:/app/backups - ./backups:/app/backups
- ./.env:/app/.env
environment: environment:
- PUID=1000
- PGID=1000
- TZ=UTC - TZ=UTC
- SERVER_ADDRESS=:8080 - SERVER_ADDRESS=:8080
- DATA_DIR=/app/data - DATA_DIR=/app/data
- BACKUP_DIR=/app/backups - BACKUP_DIR=/app/backups
- JWT_SECRET=change_this_to_a_secure_random_string - JWT_SECRET=change_this_to_a_secure_random_string
- BASE_URL=http://localhost:8080 - BASE_URL=http://localhost:8080
# Google OAuth configuration (optional)
- GOOGLE_CLIENT_ID=your_google_client_id - GOOGLE_CLIENT_ID=your_google_client_id
- GOOGLE_CLIENT_SECRET=your_google_client_secret - GOOGLE_CLIENT_SECRET=your_google_client_secret
# Two-Factor Authentication configuration - TOTP_ENCRYPTION_KEY=your_32_byte_encryption_key_here
- TOTP_ENCRYPTION_KEY=your_32_byte_secure_encryption_key
# Email configuration
- EMAIL_ENABLED=true - EMAIL_ENABLED=true
- EMAIL_HOST=smtp.example.com - EMAIL_HOST=smtp.example.com
- EMAIL_PORT=587 - EMAIL_PORT=587
@@ -165,34 +210,14 @@ services:
- EMAIL_REQUIRE_AUTH=true - EMAIL_REQUIRE_AUTH=true
- EMAIL_USERNAME=smtp_username - EMAIL_USERNAME=smtp_username
- EMAIL_PASSWORD=smtp_password - EMAIL_PASSWORD=smtp_password
# Logging configuration
- LOGS_DIR=/app/data/logs - LOGS_DIR=/app/data/logs
- LOG_MAX_SIZE=10 - LOG_MAX_SIZE=10
- LOG_MAX_BACKUPS=5 - LOG_MAX_BACKUPS=5
- LOG_MAX_AGE=30 - LOG_MAX_AGE=30
- LOG_COMPRESS=true - LOG_COMPRESS=true
- LOG_LEVEL=info - 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: Save this as `docker-compose.yml` and run:
```bash ```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). For more information and available tags, visit the [GoMFT Docker Hub page](https://hub.docker.com/r/starfleetcptn/gomft).
---
## Configuration ## 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: 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 SERVER_ADDRESS=:8080
DATA_DIR=/app/data DATA_DIR=/app/data
BACKUP_DIR=/app/backups BACKUP_DIR=/app/backups
@@ -230,6 +258,10 @@ EMAIL_PASSWORD=smtp_password
# Two-Factor Authentication configuration # Two-Factor Authentication configuration
TOTP_ENCRYPTION_KEY=your_32_byte_encryption_key_here TOTP_ENCRYPTION_KEY=your_32_byte_encryption_key_here
# UserID and GroupID
PUID=1000
PGID=1000
``` ```
### Configuration Options ### 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. Log files contain detailed information about file transfers, job execution, and system operations, which can be useful for troubleshooting and auditing.
---
## Usage ## Usage
1. Start the server: 1. Start the server:
@@ -552,6 +586,8 @@ The Admin Tools interface also includes database management capabilities:
- View system statistics - View system statistics
- Optimize the database with maintenance tools - Optimize the database with maintenance tools
---
## Development ## Development
### Project Structure ### Project Structure
@@ -602,6 +638,8 @@ templ generate
air air
``` ```
---
## Contributing ## Contributing
1. Fork the repository 1. Fork the repository
@@ -610,6 +648,8 @@ air
4. Push to the branch 4. Push to the branch
5. Create a Pull Request 5. Create a Pull Request
---
## Directory Structure ## Directory Structure
GoMFT uses the following 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`. 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 ## License
+103
View File
@@ -513,11 +513,96 @@ templ AdminTools(ctx context.Context, data AdminToolsData) {
{ fmt.Sprint(data.TotalJobs) } { fmt.Sprint(data.TotalJobs) }
</dd> </dd>
</div> </div>
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-secondary-500 dark:text-secondary-400">Application Version</dt>
<dd class="mt-1 text-sm text-secondary-900 dark:text-secondary-100">
<div class="flex items-center">
<span>{ AppVersion }</span>
if AppVersion == "dev" {
<a href="https://github.com/starfleetcptn/gomft/releases"
class="ml-2 text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300"
target="_blank" rel="noopener">
<i class="fas fa-external-link-alt text-xs"></i>
</a>
} else if AppVersion == "1.0.0" {
<a href="https://github.com/starfleetcptn/gomft/releases/tag/v1.0.0"
class="ml-2 text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300"
target="_blank" rel="noopener">
<i class="fas fa-external-link-alt text-xs"></i>
</a>
} else if AppVersion == "1.1.0" {
<a href="https://github.com/starfleetcptn/gomft/releases/tag/v1.1.0"
class="ml-2 text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300"
target="_blank" rel="noopener">
<i class="fas fa-external-link-alt text-xs"></i>
</a>
} else {
<a href="https://github.com/starfleetcptn/gomft/releases"
class="ml-2 text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300"
target="_blank" rel="noopener">
<i class="fas fa-external-link-alt text-xs"></i>
</a>
}
</div>
</dd>
</div>
</dl> </dl>
</div> </div>
</div> </div>
</div> </div>
<!-- Build Information -->
<div class="mt-6">
<div class="card">
<div class="card-header">
<h3 class="text-lg font-medium text-secondary-900 dark:text-secondary-100">
<i class="fas fa-code-branch mr-2 text-primary-500"></i>
Build Information
</h3>
</div>
<div class="card-body">
<div class="relative overflow-hidden bg-secondary-50 dark:bg-secondary-900 rounded-lg">
<div class="px-4 py-5 sm:p-6">
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-3">
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-secondary-500 dark:text-secondary-400">Version</dt>
<dd class="mt-1 text-sm text-secondary-900 dark:text-secondary-100 font-mono">
{ AppVersion }
</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-secondary-500 dark:text-secondary-400">Build Time</dt>
<dd class="mt-1 text-sm text-secondary-900 dark:text-secondary-100 font-mono">
{ getBuildTime() }
</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-secondary-500 dark:text-secondary-400">Commit</dt>
<dd class="mt-1 text-sm text-secondary-900 dark:text-secondary-100 font-mono">
{ getCommit() }
</dd>
</div>
</dl>
<div class="mt-6 flex justify-end">
<a
href="https://github.com/starfleetcptn/gomft"
class="inline-flex items-center px-4 py-2 text-sm text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 transition-colors"
target="_blank" rel="noopener"
>
<i class="fab fa-github mr-2"></i>
View on GitHub
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Log Viewer --> <!-- Log Viewer -->
<div id="logs-container" class="mt-8"> <div id="logs-container" class="mt-8">
@AdminLogViewer(data) @AdminLogViewer(data)
@@ -897,3 +982,21 @@ templ AdminLogContent(data AdminToolsData) {
} }
</div> </div>
} }
// 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
}
+25 -1
View File
@@ -3,8 +3,20 @@ package components
import ( import (
"context" "context"
"github.com/gin-gonic/gin" "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 // CreateTemplateContext creates a new context with user information from Gin's context
func CreateTemplateContext(c *gin.Context) context.Context { func CreateTemplateContext(c *gin.Context) context.Context {
ctx := context.Background() ctx := context.Background()
@@ -437,6 +449,18 @@ templ LayoutWithContext(title string, ctx context.Context) {
<p class="text-center text-sm text-secondary-500 dark:text-secondary-400"> <p class="text-center text-sm text-secondary-500 dark:text-secondary-400">
GoMFT &copy; { getCurrentYear() } | Secure File Transfer Solution GoMFT &copy; { getCurrentYear() } | Secure File Transfer Solution
</p> </p>
<div class="flex justify-center items-center space-x-4 mt-1">
<p class="text-xs text-secondary-400 dark:text-secondary-500">
<a href="https://github.com/starfleetcptn/gomft" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200" target="_blank" rel="noopener">
<i class="fab fa-github mr-1"></i>GitHub
</a>
</p>
<p class="text-xs text-secondary-400 dark:text-secondary-500">
<a href={ GetReleaseURL() } class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200" target="_blank" rel="noopener">
<i class="fas fa-tag mr-1"></i>Version { AppVersion }
</a>
</p>
</div>
</div> </div>
</footer> </footer>
@@ -544,5 +568,5 @@ func getUserEmail(ctx context.Context) string {
// Helper function to get current year // Helper function to get current year
func getCurrentYear() string { func getCurrentYear() string {
return "2025" return time.Now().Format("2006")
} }
+12
View File
@@ -3,6 +3,15 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile 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 container_name: gomft
restart: unless-stopped restart: unless-stopped
ports: ports:
@@ -22,6 +31,9 @@ services:
# - LOG_LEVEL=info # - LOG_LEVEL=info
networks: networks:
- gomft-network - 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: networks:
gomft-network: gomft-network:
+89
View File
@@ -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
+20 -2
View File
@@ -3,6 +3,7 @@ package migrations
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"time" "time"
"github.com/go-gormigrate/gormigrate/v2" "github.com/go-gormigrate/gormigrate/v2"
@@ -33,8 +34,25 @@ func InitialSchema() *gormigrate.Migration {
return fmt.Errorf("failed to get database path: %v", err) return fmt.Errorf("failed to get database path: %v", err)
} }
// Create backup file with timestamp // Get backup directory from environment variable or use default
backupFile := fmt.Sprintf("%s.backup.%s", dbPath, time.Now().Format("20060102_150405")) 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 // Read original database
data, err := os.ReadFile(dbPath) data, err := os.ReadFile(dbPath)
+20 -2
View File
@@ -3,6 +3,7 @@ package migrations
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"time" "time"
"github.com/go-gormigrate/gormigrate/v2" "github.com/go-gormigrate/gormigrate/v2"
@@ -34,8 +35,25 @@ func Add2FA() *gormigrate.Migration {
return fmt.Errorf("failed to get database path: %v", err) return fmt.Errorf("failed to get database path: %v", err)
} }
// Create backup file with timestamp // Get backup directory from environment variable or use default
backupFile := fmt.Sprintf("%s.backup.%s", dbPath, time.Now().Format("20060102_150405")) 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 // Read original database
data, err := os.ReadFile(dbPath) data, err := os.ReadFile(dbPath)
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
// "github.com/starfleetcptn/gomft/internal/api" // "github.com/starfleetcptn/gomft/internal/api"
"github.com/starfleetcptn/gomft/components"
"github.com/starfleetcptn/gomft/internal/config" "github.com/starfleetcptn/gomft/internal/config"
"github.com/starfleetcptn/gomft/internal/db" "github.com/starfleetcptn/gomft/internal/db"
"github.com/starfleetcptn/gomft/internal/scheduler" "github.com/starfleetcptn/gomft/internal/scheduler"
@@ -27,7 +28,7 @@ func main() {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
log.SetFlags(log.LstdFlags | log.Lshortfile) log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Printf("Starting GoMFT server...") log.Printf("Starting GoMFT server version %s...", components.AppVersion)
// Initialize configuration // Initialize configuration
cfg, err := config.Load() cfg, err := config.Load()