From 0232cd0c20f26870f64b28225779ab96b50d30b0 Mon Sep 17 00:00:00 2001 From: bdnugget Date: Mon, 7 Jul 2025 21:42:30 +0200 Subject: [PATCH] Add Coolify deployment configuration --- .dockerignore | 43 +++++ DEPLOYMENT.md | 143 +++++++++++++++++ Dockerfile | 38 +++++ docker-compose.yml | 14 ++ main.go | 29 +++- static/style.css | 356 +++++++++++++++++++++++++++++++++++++---- templates/contact.html | 48 +++++- templates/index.html | 108 ++++++++++++- 8 files changed, 731 insertions(+), 48 deletions(-) create mode 100644 .dockerignore create mode 100644 DEPLOYMENT.md create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a914c29 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,43 @@ +# Git +.git +.gitignore + +# Documentation +README.md +*.md + +# Build artifacts +main +*.exe +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool +*.out + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log + +# Temporary files +tmp/ +temp/ + +# Docker files (not needed in container) +Dockerfile* +docker-compose*.yml +.dockerignore \ No newline at end of file diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..cc92a7c --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,143 @@ +# Coolify Deployment Guide + +This guide explains how to deploy the Linux Service website to your self-hosted Coolify instance with automatic updates from your Gitea repository. + +## Prerequisites + +- Coolify installed and running +- Access to your Gitea instance (gitea.boner.be) +- Domain name configured for your service + +## Environment Variables + +The application supports the following environment variables: + +| Variable | Default Value | Description | +|----------|---------------|-------------| +| `PORT` | `8080` | Port the application listens on | +| `COMPANY_NAME` | `Hogeland Linux` | Company name displayed on the website | +| `KVK` | `12345678` | KVK number for contact information | +| `EMAIL` | `info@hogelandlinux.nl` | Contact email address | +| `PHONE` | `+31 6 12345678` | Contact phone number | + +## Coolify Deployment Steps + +### 1. Create New Resource in Coolify + +1. Open your Coolify dashboard +2. Click "New Resource" +3. Select "Public Repository" or "Private Repository" (if your Gitea repo is private) + +### 2. Configure Repository + +**Repository URL:** `https://gitea.boner.be/[your-username]/[repository-name]` + +**Branch:** `master` + +**Build Pack:** `Docker` + +### 3. Configure Build Settings + +- **Dockerfile Location:** `./Dockerfile` +- **Build Context:** `.` +- **Ports:** `8080` + +### 4. Set Environment Variables + +In the Coolify environment variables section, add: + +``` +PORT=8080 +COMPANY_NAME=Hogeland Linux +KVK=12345678 +EMAIL=info@hogelandlinux.nl +PHONE=+31 6 12345678 +``` + +### 5. Configure Domain + +- Set your desired domain/subdomain +- Coolify will automatically handle SSL certificate generation + +### 6. Enable Auto-Deploy + +1. Go to the "Settings" tab of your application +2. Enable "Auto Deploy" +3. Set the branch to `master` +4. Configure webhook URL in your Gitea repository + +### 7. Gitea Webhook Configuration + +To enable automatic deployments when you push to master: + +1. Go to your repository on gitea.boner.be +2. Navigate to Settings → Webhooks +3. Click "Add Webhook" → "Gitea" +4. Set the Payload URL to your Coolify webhook URL (found in your app settings) +5. Set Content Type to `application/json` +6. Select "Just the push event" +7. Check "Active" +8. Click "Add Webhook" + +## Docker Commands for Local Testing + +```bash +# Build the image +docker build -t linuxservice . + +# Run locally +docker run -p 8080:8080 \ + -e COMPANY_NAME="Hogeland Linux" \ + -e EMAIL="info@hogelandlinux.nl" \ + linuxservice + +# Or use docker-compose +docker-compose up --build +``` + +## Troubleshooting + +### Common Issues + +1. **Build Fails**: Check that all files (templates/, static/) are committed to your repository +2. **Port Issues**: Ensure PORT environment variable matches the exposed port +3. **Template Errors**: Verify that templates directory is included in the Docker image + +### Logs + +Check application logs in Coolify dashboard under the "Logs" tab. + +### Health Check + +The application should respond to `GET /` with the homepage. You can check this endpoint to verify the deployment. + +## File Structure + +``` +linuxservice/ +├── Dockerfile # Container configuration +├── docker-compose.yml # Local development +├── .dockerignore # Docker build optimization +├── main.go # Go application +├── go.mod # Go module definition +├── static/ # Static assets (CSS, images, etc.) +│ └── style.css +├── templates/ # HTML templates +│ ├── index.html +│ └── contact.html +└── DEPLOYMENT.md # This file +``` + +## Performance Considerations + +- The Docker image uses multi-stage builds for smaller size +- Static files are served directly by the Go application +- No external dependencies required +- Minimal resource usage (suitable for small VPS instances) + +## Security Notes + +- The application runs as a non-root user in the container +- Only port 8080 is exposed +- No sensitive data is stored in the application +- Environment variables should be used for configuration \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..436c482 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +# Build stage +FROM golang:1.24.4-alpine AS builder + +# Set working directory +WORKDIR /app + +# Copy go mod files +COPY go.mod go.sum* ./ + +# Download dependencies +RUN go mod download + +# Copy source code +COPY . . + +# Build the application +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . + +# Final stage +FROM alpine:latest + +# Install ca-certificates for HTTPS requests +RUN apk --no-cache add ca-certificates + +WORKDIR /root/ + +# Copy the binary from builder stage +COPY --from=builder /app/main . + +# Copy static files and templates +COPY --from=builder /app/static ./static +COPY --from=builder /app/templates ./templates + +# Expose port +EXPOSE 8080 + +# Command to run +CMD ["./main"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2e29690 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.8' + +services: + linuxservice: + build: . + ports: + - "8080:8080" + environment: + - PORT=8080 + - COMPANY_NAME=Hogeland Linux + - KVK=12345678 + - EMAIL=info@hogelandlinux.nl + - PHONE=+31 6 12345678 + restart: unless-stopped \ No newline at end of file diff --git a/main.go b/main.go index a65617b..dd9ce99 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "html/template" "log" "net/http" + "os" ) // Configuration holds all website configuration @@ -30,15 +31,23 @@ type Server struct { templates *template.Template } +// getEnv returns environment variable or default value +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + // NewServer creates a new server instance func NewServer() *Server { - // Configuration - replace these with your actual values + // Configuration - can be overridden with environment variables config := Config{ - CompanyName: "Hogeland Linux", - KVK: "12345678", // Replace with actual KVK number - Email: "info@hogelandlinux.nl", - Phone: "+31 6 12345678", - Port: ":8080", + CompanyName: getEnv("COMPANY_NAME", "Hogeland Linux"), + KVK: getEnv("KVK", "12345678"), // Replace with actual KVK number + Email: getEnv("EMAIL", "info@hogelandlinux.nl"), + Phone: getEnv("PHONE", "+31 6 12345678"), + Port: ":" + getEnv("PORT", "8080"), } // Parse templates with error handling @@ -84,6 +93,13 @@ func (s *Server) contactHandler(w http.ResponseWriter, r *http.Request) { s.renderTemplate(w, "contact.html", data) } +// healthHandler provides a simple health check endpoint +func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status":"healthy","service":"linuxservice"}`)) +} + // setupRoutes configures all HTTP routes func (s *Server) setupRoutes() { // Static files @@ -93,6 +109,7 @@ func (s *Server) setupRoutes() { // Page routes http.HandleFunc("/", s.homeHandler) http.HandleFunc("/contact", s.contactHandler) + http.HandleFunc("/health", s.healthHandler) } func main() { diff --git a/static/style.css b/static/style.css index dfe5f16..ec66231 100644 --- a/static/style.css +++ b/static/style.css @@ -19,7 +19,7 @@ body { .container { max-width: 1200px; margin: 0 auto; - padding: 0 20px; + padding: 0 1rem; } /* Typography */ @@ -40,20 +40,22 @@ p { /* Navigation */ .navbar { - background: #fff; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + background: #1f2937; + padding: 1rem 0; position: sticky; top: 0; - z-index: 1000; + z-index: 100; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } .nav-container { display: flex; justify-content: space-between; align-items: center; - padding: 1rem 20px; max-width: 1200px; margin: 0 auto; + padding: 0 1rem; + position: relative; } .nav-logo h1 { @@ -75,14 +77,60 @@ p { .nav-links a { text-decoration: none; - color: #4b5563; + color: white; font-weight: 500; - transition: color 0.3s ease; + transition: all 0.3s ease; + padding: 0.5rem 1rem; + border-radius: 4px; } .nav-links a:hover, .nav-links a.active { - color: #059669; + color: #10b981; + background: rgba(16, 185, 129, 0.1); +} + +/* Mobile Navigation */ +.mobile-menu-toggle { + display: none; + background: none; + border: none; + color: white; + font-size: 1.5rem; + cursor: pointer; + padding: 0.5rem; +} + +.mobile-menu-toggle:hover { + color: #10b981; +} + +.mobile-menu { + display: none; + position: absolute; + top: 100%; + left: 0; + right: 0; + background: #1f2937; + border-top: 1px solid #374151; + z-index: 1000; +} + +.mobile-menu.active { + display: block; +} + +.mobile-menu a { + display: block; + padding: 1rem 1.5rem; + color: white; + text-decoration: none; + border-bottom: 1px solid #374151; +} + +.mobile-menu a:hover { + background: #374151; + color: #10b981; } /* Hero Section */ @@ -217,15 +265,19 @@ p { /* Buttons */ .btn { - display: inline-block; padding: 12px 24px; + font-size: 1rem; border-radius: 8px; - text-decoration: none; - font-weight: 500; - transition: all 0.3s ease; border: 2px solid transparent; cursor: pointer; - font-size: 1rem; + transition: all 0.3s ease; + text-decoration: none; + display: inline-block; + font-weight: 500; + text-align: center; + min-height: 44px; + min-width: 44px; + line-height: 1.5; } .btn-primary { @@ -834,12 +886,14 @@ p { .form-group input, .form-group select, .form-group textarea { - width: 100%; - padding: 12px; - border: 1px solid #d1d5db; + padding: 12px 16px; + border: 2px solid #d1d5db; border-radius: 8px; - font-size: 1rem; + font-size: 16px; transition: border-color 0.3s ease; + width: 100%; + box-sizing: border-box; + min-height: 44px; } .form-group input:focus, @@ -926,11 +980,41 @@ footer { } /* Responsive Design */ +@media (max-width: 1024px) { + .container { + padding: 0 1.5rem; + } + + .hero-content { + gap: 2rem; + } + + .benefits-grid { + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + } + + .distros-grid { + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + } + + .services-grid { + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + } +} + @media (max-width: 768px) { .nav-links { display: none; } + .mobile-menu-toggle { + display: block; + } + + .hero { + padding: 3rem 0; + } + .hero-content { grid-template-columns: 1fr; gap: 2rem; @@ -939,30 +1023,63 @@ footer { .hero-content h2 { font-size: 2.5rem; + line-height: 1.2; + } + + .hero-subtitle { + font-size: 1.2rem; + margin-bottom: 2rem; } .hero-buttons { justify-content: center; + gap: 1rem; + } + + .terminal-window { + max-width: 100%; + margin: 0; + border-radius: 8px; + } + + .terminal-body { + padding: 16px; + font-size: 13px; + min-height: 180px; + overflow-x: auto; } .benefits-grid { grid-template-columns: 1fr; + gap: 1.5rem; + } + + .benefit-card { + padding: 1.5rem; } .eol-content { grid-template-columns: 1fr; + gap: 2rem; } .distros-grid { grid-template-columns: 1fr; + gap: 1.5rem; + } + + .distro-card { + padding: 1.5rem; } .contact-grid { grid-template-columns: 1fr; + gap: 2rem; } .services-grid { grid-template-columns: 1fr; + gap: 1.5rem; } .features-grid { @@ -972,48 +1089,233 @@ footer { .cta-benefits { grid-template-columns: 1fr; + gap: 1.5rem; } .footer-content { grid-template-columns: 1fr; text-align: center; + gap: 2rem; + } + + /* Section padding adjustments */ + .benefits, .windows-eol, .distros, .services, .linux-features, .cta { + padding: 3rem 0; + } + + /* Typography adjustments */ + h1 { font-size: 2.2rem; } + h2 { font-size: 1.8rem; } + h3 { font-size: 1.3rem; } + + /* Performance bars responsive */ + .performance-bars { + gap: 1rem; + } + + .performance-item { + margin-bottom: 1rem; + } + + .perf-label { + font-size: 0.9rem; + margin-bottom: 0.5rem; } } @media (max-width: 480px) { + .container { + padding: 0 1rem; + } + + .hero { + padding: 2rem 0; + } + .hero-content h2 { font-size: 2rem; + line-height: 1.3; } .hero-subtitle { font-size: 1.1rem; + margin-bottom: 1.5rem; + } + + .hero-buttons { + flex-direction: column; + gap: 0.75rem; } .btn { padding: 10px 20px; font-size: 0.9rem; - } - - .hero-buttons { - flex-direction: column; - gap: 1rem; + width: 100%; + max-width: 300px; } .terminal-window { - max-width: 100%; - margin: 0 1rem; + border-radius: 6px; + overflow: hidden; } .terminal-body { - padding: 16px; + padding: 12px; font-size: 12px; - min-height: 150px; + min-height: 160px; } .prompt { - min-width: 130px; + min-width: 120px; font-size: 11px; } + + /* Card padding adjustments */ + .benefit-card, .service-card, .distro-card, .cta-benefit { + padding: 1.25rem; + } + + /* Icon size adjustments */ + .benefit-icon, .cta-benefit .benefit-icon { + font-size: 2.5rem; + } + + /* Contact form improvements */ + .contact-form { + padding: 1.5rem; + } + + .form-group { + margin-bottom: 1.25rem; + } + + .form-group label { + margin-bottom: 0.5rem; + font-size: 0.9rem; + } + + /* Footer adjustments */ + footer { + padding: 2rem 0 1rem; + } + + .footer-content { + gap: 1.5rem; + } + + .footer-section { + padding: 0 1rem; + } + + /* Section padding for very small screens */ + .benefits, .windows-eol, .distros, .services, .linux-features, .cta { + padding: 2rem 0; + } + + /* Typography for very small screens */ + h1 { font-size: 1.8rem; } + h2 { font-size: 1.5rem; } + h3 { font-size: 1.2rem; } + + p { + font-size: 0.95rem; + } + + /* Stats grid for mobile */ + .eol-stats { + grid-template-columns: 1fr; + gap: 1rem; + } + + .stat { + padding: 1rem; + } + + .stat h3 { + font-size: 1.8rem; + } + + /* Distro features on mobile */ + .distro-features { + gap: 0.5rem; + } + + .feature { + padding: 0.4rem 0.8rem; + font-size: 0.8rem; + } + + /* Desktop mockup adjustments */ + .feature-mockup { + min-height: 200px; + } + + .mockup-content { + padding: 1rem; + } + + .app-item { + padding: 0.5rem; + margin-bottom: 0.5rem; + } +} + +/* Landscape phone adjustments */ +@media (max-width: 768px) and (orientation: landscape) { + .hero { + padding: 2rem 0; + } + + .hero-content { + grid-template-columns: 1fr 1fr; + gap: 2rem; + text-align: left; + } + + .hero-content h2 { + font-size: 2rem; + } + + .hero-buttons { + justify-content: flex-start; + } + + .terminal-window { + max-height: 250px; + overflow-y: auto; + } +} + +/* Very small screens */ +@media (max-width: 360px) { + .hero-content h2 { + font-size: 1.7rem; + } + + .hero-subtitle { + font-size: 1rem; + } + + .terminal-body { + font-size: 11px; + padding: 10px; + } + + .benefit-card, .service-card, .distro-card, .cta-benefit { + padding: 1rem; + } + + .btn { + padding: 8px 16px; + font-size: 0.85rem; + } +} + +/* High DPI screens */ +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + .terminal-body { + font-size: 13px; + } } /* Animations */ diff --git a/templates/contact.html b/templates/contact.html index 4e8ddd2..f12f479 100644 --- a/templates/contact.html +++ b/templates/contact.html @@ -14,13 +14,23 @@ - + + +
+ Home + Voordelen + Linux Keuze + Diensten + Contact +
@@ -166,5 +176,29 @@ + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index e37a9ae..f40dc95 100644 --- a/templates/index.html +++ b/templates/index.html @@ -12,15 +12,23 @@ @@ -394,5 +402,89 @@ + + \ No newline at end of file