diff --git a/.env.development b/.env.development index afbb96d..e95f5ff 100644 --- a/.env.development +++ b/.env.development @@ -3,6 +3,9 @@ ADMIN_TOKEN=devadmintoken # Docker host binding (used by docker compose for port mapping) HOST=127.0.0.1 PORT=8080 +# User and group for file ownership (should match system user on host) +UID=1000 +GID=1000 # Optional: Service configuration (defaults shown) DEPLOY_ROOT=/var/www/docs diff --git a/.env.example b/.env.example index fed7e5f..6cf0d10 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,16 @@ # Docker Compose Variables (used for port mapping) -# Copy this file to .env for docker-compose to use +# Copy this file to .env for docker compose to use # The Go service always binds to 0.0.0.0:8080 inside the container HOST=0.0.0.0 PORT=8080 +# User and group for file ownership (MUST match system user on host) +UID=1000 +GID=1000 + # Required: Admin bearer token for API access -ADMIN_TOKEN=your-secure-admin-token-here # TODO: generate a secure token +ADMIN_TOKEN=your-secure-admin-token-here + # Optional: Service configuration (defaults shown) DEPLOY_ROOT=/var/www/tingz-docs RELEASE_ROOT=/var/www/tingz-deploys diff --git a/Dockerfile b/Dockerfile index 11fffc0..ff5e9fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,12 +17,16 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build \ FROM alpine:latest -RUN adduser -D -u 1000 deployer && \ - chown -R deployer:deployer /tmp +ARG UID +ARG GID + +RUN addgroup -g ${GID} -S tingz && \ + adduser -u ${UID} -S -G tingz -s /sbin/nologin tingz && \ + chown -R tingz:tingz /tmp COPY --from=builder /bin/deployer /deployer -USER deployer +USER tingz EXPOSE 8080 diff --git a/docker-compose.yml b/docker-compose.yml index 9c82c1c..2e24efc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,17 @@ services: server: - build: . container_name: tingz-server - restart: unless-stopped - env_file: .env.development + env_file: .env + build: + context: . + args: + UID: ${UID} + GID: ${GID} volumes: - ./data:/data - ./docs:/var/www/docs - ./deploys:/var/www/deploys ports: - "${HOST}:${PORT}:8080" - user: "1000:1000" - + user: "${UID}:${GID}" + restart: unless-stopped diff --git a/internal/deploy/manager.go b/internal/deploy/manager.go index b1c6986..fc1708f 100644 --- a/internal/deploy/manager.go +++ b/internal/deploy/manager.go @@ -68,6 +68,10 @@ func (m *Manager) Deploy(ctx context.Context, username, project string, r io.Rea return "", fmt.Errorf("failed to extract tarball: %w", err) } + if err := m.lockdownPermissions(releasePath); err != nil { + return "", fmt.Errorf("failed to set read-only permissions: %w", err) + } + deployPath := filepath.Join(m.deployRoot, username, project) deployParentDir := filepath.Dir(deployPath) if err := os.MkdirAll(deployParentDir, 0755); err != nil { @@ -178,6 +182,26 @@ func validateTarPath(path string) error { return nil } +func (m *Manager) lockdownPermissions(root string) error { + return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + if err := os.Chmod(path, 0550); err != nil { + return fmt.Errorf("failed to chmod directory %s: %w", path, err) + } + } else { + if err := os.Chmod(path, 0440); err != nil { + return fmt.Errorf("failed to chmod file %s: %w", path, err) + } + } + + return nil + }) +} + func (m *Manager) cleanupOldReleases(username, project string) error { releasesDir := filepath.Join(m.releaseRoot, username, project)