diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..eac7b74 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,33 @@ +# Caddyfile - Reverse Proxy Configuration +# +# 使用方法: +# 1. example.com を実際のドメインに置き換えてください +# 2. DNS の A レコードをこのサーバーの IP アドレスに向けてください +# 3. docker compose up -d で起動すると、自動的に HTTPS 証明書が取得されます +# +# ローカル開発用の場合は、以下のように変更してください +# :80 { +# reverse_proxy app:8080 +# } + +example.com { + reverse_proxy app:8080 + + # ログ設定 + log { + output file /data/access.log + format json + } + + # セキュリティヘッダー + header { + X-Content-Type-Options nosniff + X-Frame-Options DENY + X-XSS-Protection "1; mode=block" + Referrer-Policy strict-origin-when-cross-origin + -Server + } + + # gzip 圧縮 + encode gzip +} diff --git a/Dockerfile b/Dockerfile index bb0939d..f098686 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,8 @@ # Builder stage -FROM golang:1.24-alpine AS builder +FROM golang:1.24-trixie AS builder -# Set working directory WORKDIR /app -# Install git if needed for fetching dependencies (sometimes needed even with go modules) -# RUN apk add --no-cache git - # Copy go mod and sum files COPY go.mod go.sum ./ @@ -21,12 +17,18 @@ COPY . . RUN CGO_ENABLED=0 go build -o server ./cmd/server/main.go # Runtime stage -FROM alpine:latest +FROM debian:trixie-slim # Install runtime dependencies -RUN apk add --no-cache ca-certificates tzdata +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + tzdata \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Create non-root user +RUN useradd -r -u 1000 -s /bin/false appuser -# Set working directory WORKDIR /app # Copy binary from builder @@ -35,8 +37,21 @@ COPY --from=builder /app/server . # Copy web assets (templates, static files) COPY --from=builder /app/web ./web -# Expose port (adjust if your app uses a different port) +# Create data directory for SQLite +RUN mkdir -p /app/data && chown -R appuser:appuser /app + +# Switch to non-root user +USER appuser + +# Expose port EXPOSE 8080 +# Volume for persistent data +VOLUME ["/app/data"] + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8080/ || exit 1 + # Run the application ENTRYPOINT ["./server"] diff --git a/config.ini.docker.example b/config.ini.docker.example new file mode 100644 index 0000000..7518f1e --- /dev/null +++ b/config.ini.docker.example @@ -0,0 +1,34 @@ +; Homework Manager 設定ファイル (Docker用) +; docker-compose.yml と一緒に使用してください +; ローカル実行する場合securityセクションのhttpsをfalseに変更するのを忘れないでください!! + +[server] +port = 8080 +debug = false + +[database] +driver = mysql +host = db +port = 3306 +user = homework +password = homework_password +name = homework_manager + +[session] +; 本番環境では必ず変更してください +secret = CHANGE_THIS_TO_A_SECURE_RANDOM_STRING + +[auth] +allow_registration = true + +[security] +https = true +; こちらも本番環境では必ず変更してください +csrf_secret = CHANGE_THIS_TO_A_SECURE_RANDOM_STRING +rate_limit_enabled = true +rate_limit_requests = 100 +rate_limit_window = 60 +trusted_proxies = 172.16.0.0/12 + +[notification] +telegram_bot_token = diff --git a/docker-compose.yml b/docker-compose.yml index f2e2c6f..3443d8b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,64 @@ services: app: build: . - ports: - - "8080:8080" + container_name: homework-manager volumes: - - ./homework.db:/app/homework.db - - ./config.ini:/app/config.ini + - ./config.ini:/app/config.ini:ro environment: - TZ=Asia/Tokyo restart: unless-stopped + networks: + - internal + expose: + - "8080" + depends_on: + db: + condition: service_healthy + + db: + image: mysql:8.0 + container_name: homework-db + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword} + MYSQL_DATABASE: ${MYSQL_DATABASE:-homework_manager} + MYSQL_USER: ${MYSQL_USER:-homework} + MYSQL_PASSWORD: ${MYSQL_PASSWORD:-homework_password} + TZ: Asia/Tokyo + volumes: + - db-data:/var/lib/mysql + networks: + - internal + restart: unless-stopped + healthcheck: + test: [ "CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-rootpassword}" ] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + caddy: + image: caddy:2-alpine + container_name: homework-caddy + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-data:/data + - caddy-config:/config + environment: + - TZ=Asia/Tokyo + restart: unless-stopped + networks: + - internal + depends_on: + - app + +networks: + internal: + driver: bridge + +volumes: + db-data: + caddy-data: + caddy-config: diff --git a/internal/models/api_key.go b/internal/models/api_key.go index a86a6ec..7cc6c32 100644 --- a/internal/models/api_key.go +++ b/internal/models/api_key.go @@ -10,7 +10,7 @@ type APIKey struct { ID uint `gorm:"primarykey" json:"id"` UserID uint `gorm:"not null;index" json:"user_id"` Name string `gorm:"not null" json:"name"` - KeyHash string `gorm:"not null;uniqueIndex" json:"-"` + KeyHash string `gorm:"not null;uniqueIndex;size:255" json:"-"` LastUsed *time.Time `json:"last_used,omitempty"` CreatedAt time.Time `json:"created_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` diff --git a/internal/models/user.go b/internal/models/user.go index 4ca7c80..f6d8d1b 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -8,7 +8,7 @@ import ( type User struct { ID uint `gorm:"primarykey" json:"id"` - Email string `gorm:"uniqueIndex;not null" json:"email"` + Email string `gorm:"uniqueIndex;not null;size:255" json:"email"` PasswordHash string `gorm:"not null" json:"-"` Name string `gorm:"not null" json:"name"` Role string `gorm:"not null;default:user" json:"role"` // "admin" or "user"