deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 1m32s

This commit is contained in:
2026-01-26 02:19:48 +01:00
parent ac1e22017e
commit 574d5a8a9a
6 changed files with 293 additions and 27 deletions

25
.env.example Normal file
View File

@@ -0,0 +1,25 @@
# WooCommerce API (Legacy - not currently used)
WOOCOMMERCE_URL=https://klz-cables.com
WOOCOMMERCE_CONSUMER_KEY=
WOOCOMMERCE_CONSUMER_SECRET=
WORDPRESS_APP_PASSWORD=
# Umami Analytics
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
# GlitchTip (Sentry protocol)
SENTRY_DSN=
NEXT_PUBLIC_SENTRY_DSN=
# SMTP Configuration
MAIL_HOST=smtp.eu.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_FROM=KLZ Cables <noreply@klz-cables.com>
MAIL_RECIPIENTS=info@klz-cables.com
# Redis Configuration (optional)
REDIS_URL=redis://redis:6379/2
REDIS_KEY_PREFIX=klz:

View File

@@ -39,6 +39,9 @@ jobs:
# docker push registry.infra.mintel.me/mintel/klz-cables.com:latest # docker push registry.infra.mintel.me/mintel/klz-cables.com:latest
- name: Deploy to production server - name: Deploy to production server
env:
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_PASS: ${{ secrets.REGISTRY_PASS }}
run: | run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.ALPHA_SSH_KEY }}" > ~/.ssh/id_ed25519 echo "${{ secrets.ALPHA_SSH_KEY }}" > ~/.ssh/id_ed25519
@@ -46,13 +49,11 @@ jobs:
ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts 2>/dev/null ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts 2>/dev/null
ssh -o StrictHostKeyChecking=accept-new deploy@alpha.mintel.me << EOF ssh -o StrictHostKeyChecking=accept-new deploy@alpha.mintel.me bash -c "'
set -e set -e
echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me \ echo \"$REGISTRY_PASS\" | docker login registry.infra.mintel.me -u \"$REGISTRY_USER\" --password-stdin
-u "${{ secrets.REGISTRY_USER }}" --password-stdin
cd /home/deploy/sites/klz-cables.com cd /home/deploy/sites/klz-cables.com
docker compose pull docker compose pull
docker compose up -d --force-recreate --remove-orphans docker compose up -d --force-recreate --remove-orphans
docker image prune -f docker image prune -f
EOF '"

View File

@@ -225,31 +225,61 @@ GET /robots.txt
## 🚀 Deployment ## 🚀 Deployment
### Vercel (Recommended) ### Automatic Deployment (Current Setup)
```bash
# Install Vercel CLI
npm i -g vercel
# Deploy The project uses **Gitea Actions** for CI/CD. Every push to `main` triggers:
vercel --prod
1. **Build**: Docker image built for `linux/arm64`
2. **Push**: Image pushed to `registry.infra.mintel.me`
3. **Deploy**: SSH to production server, pull and restart containers
**Workflow**: `.gitea/workflows/deploy.yml`
**Required Secrets** (configure in Gitea repository settings):
- `REGISTRY_USER` - Docker registry username
- `REGISTRY_PASS` - Docker registry password
- `ALPHA_SSH_KEY` - SSH private key for deployment
- `NEXT_PUBLIC_UMAMI_WEBSITE_ID` - Analytics ID
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL` - Analytics script URL
- `SENTRY_DSN` - Error tracking DSN
### Manual Deployment
```bash
# SSH into production server
ssh deploy@alpha.mintel.me
# Navigate to project
cd /home/deploy/sites/klz-cables.com
# Pull latest image and restart
docker compose pull
docker compose up -d --force-recreate --remove-orphans
docker image prune -f
``` ```
### Static Export Or use the convenience script:
```bash ```bash
# Build and export bash scripts/deploy-webhook.sh
npm run build
npm run export
# Deploy to any static host
# Upload /out directory
``` ```
### Netlify ### Architecture
```bash
# Connect repository
# Set build command: npm run build
# Set publish directory: out
``` ```
Client → Traefik (TLS) → Varnish (Cache) → Next.js App
```
**Domains**:
- `klz-cables.com` - Production
- `www.klz-cables.com` - Production (www)
- `staging.klz-cables.com` - Staging
**Services**:
- `app`: Next.js application (port 3000)
- `varnish`: HTTP cache layer
- `traefik`: Reverse proxy (external)
For detailed deployment documentation, see [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md).
## 📈 Performance ## 📈 Performance

View File

@@ -16,21 +16,23 @@ export default function ContactForm() {
setStatus('submitting'); setStatus('submitting');
const formData = new FormData(e.currentTarget); const formData = new FormData(e.currentTarget);
const email = formData.get('email') as string;
try { try {
const result = await sendContactFormAction(formData); const result = await sendContactFormAction(formData);
if (result.success) { if (result.success) {
trackEvent('contact_form_submission', { trackEvent('contact_form_submission', {
form_type: 'general', form_type: 'general',
email: formData.get('email') as string, email,
}); });
setStatus('success'); setStatus('success');
(e.target as HTMLFormElement).reset(); (e.target as HTMLFormElement).reset();
} else { } else {
console.error('Contact form submission failed:', { email, error: result.error });
setStatus('error'); setStatus('error');
} }
} catch (error) { } catch (error) {
console.error('Form submission error:', error); console.error('Contact form submission error:', { email, error });
setStatus('error'); setStatus('error');
} }
} }

197
docs/DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,197 @@
# Deployment Guide
This document describes the deployment setup for KLZ Cables website.
## Automatic Deployment (Gitea Actions)
The project uses Gitea Actions for CI/CD. On every push to the `main` branch:
1. **Build**: Docker image is built with platform `linux/arm64`
2. **Push**: Image is pushed to `registry.infra.mintel.me/mintel/klz-cables.com:latest`
3. **Deploy**: SSH connection to production server pulls and restarts containers
### Workflow File
Location: `.gitea/workflows/deploy.yml`
### Required Secrets
Configure these in your Gitea repository settings:
- `REGISTRY_USER` - Docker registry username
- `REGISTRY_PASS` - Docker registry password
- `ALPHA_SSH_KEY` - SSH private key for deployment user
- `NEXT_PUBLIC_UMAMI_WEBSITE_ID` - Umami analytics website ID
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL` - Umami analytics script URL
- `SENTRY_DSN` - Sentry/GlitchTip DSN for error tracking
## Manual Deployment
If you need to deploy manually:
### On the Production Server
```bash
# SSH into the server
ssh deploy@alpha.mintel.me
# Navigate to the project directory
cd /home/deploy/sites/klz-cables.com
# Pull the latest image
docker compose pull
# Restart containers
docker compose up -d --force-recreate --remove-orphans
# Clean up old images
docker image prune -f
```
## Troubleshooting
### Workflow Not Triggering
1. Check Gitea Actions is enabled in repository settings
2. Verify the workflow file syntax
3. Check runner availability with label `docker`
### Build Failures
1. Check build logs in Gitea Actions tab
2. Verify all secrets are configured correctly
3. Ensure Dockerfile is valid
### Deployment Failures
1. Verify SSH key has correct permissions (600)
2. Check deploy user has Docker permissions
3. Verify registry credentials are correct
4. Check server disk space: `df -h`
### Container Issues
```bash
# Check container status
docker compose ps
# View logs
docker compose logs -f app
docker compose logs -f varnish
# Check health
docker compose exec app wget -O- http://localhost:3000/health
```
## Architecture
```
Client
Traefik (TLS termination, routing)
Varnish (HTTP caching)
Next.js App (port 3000)
```
### Services
- **app**: Next.js application
- **varnish**: HTTP cache layer
- **traefik**: Reverse proxy (external network)
### Domains
- `klz-cables.com` - Production
- `www.klz-cables.com` - Production (www)
- `staging.klz-cables.com` - Staging
## Environment Variables
### Build-time (in Dockerfile/Workflow)
- `NEXT_PUBLIC_UMAMI_WEBSITE_ID`
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL`
- `NEXT_PUBLIC_SENTRY_DSN`
### Runtime (in docker-compose.yml)
- `SENTRY_DSN`
- `REDIS_URL`
- `REDIS_KEY_PREFIX`
## Monitoring
### Health Checks
- App: `https://klz-cables.com/health`
- Varnish: Configured in docker-compose.yml
### Logs
```bash
# Application logs
docker compose logs -f app
# Varnish logs
docker compose logs -f varnish
# All logs
docker compose logs -f
```
### Analytics
- Umami: Configured via environment variables
- Sentry/GlitchTip: Error tracking
## Rollback
To rollback to a previous version:
```bash
# On the server
cd /home/deploy/sites/klz-cables.com
# Pull a specific version (if tagged)
docker pull registry.infra.mintel.me/mintel/klz-cables.com:TAG
# Or rebuild from a specific commit
# (requires access to the repository on the server)
# Restart with the older image
docker compose up -d --force-recreate
```
## Performance
### Cache Invalidation
Varnish caches static assets. To clear cache:
```bash
docker compose exec varnish varnishadm "ban req.url ~ ."
```
### Cache Configuration
Edit `varnish/default.vcl` and restart:
```bash
docker compose restart varnish
```
## Security
- All secrets are stored in Gitea repository settings
- SSH key is injected at deployment time
- Registry credentials are not stored in the repository
- Deploy webhook requires secret token
## Support
For issues or questions:
1. Check logs first
2. Review this documentation
3. Contact the development team

View File

@@ -1,4 +1,5 @@
import type { AnalyticsEventProperties, AnalyticsService } from './analytics-service'; import type { AnalyticsEventProperties, AnalyticsService } from './analytics-service';
import { getServerAppServices } from '../create-services.server';
/** /**
* Type definition for the Umami global object. * Type definition for the Umami global object.
@@ -79,11 +80,16 @@ export class UmamiAnalyticsService implements AnalyticsService {
if (!websiteId) return; if (!websiteId) return;
const logger = getServerAppServices().logger.child({ component: 'analytics' });
logger.info('Sending analytics event', { eventName, props });
fetch(`${umamiUrl}/api/send`, { fetch(`${umamiUrl}/api/send`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', 'User-Agent': 'KLZ-Server' }, headers: { 'Content-Type': 'application/json', 'User-Agent': 'KLZ-Server' },
body: JSON.stringify({ type: 'event', payload: { website: websiteId, name: eventName, data: props } }), body: JSON.stringify({ type: 'event', payload: { website: websiteId, name: eventName, data: props } }),
}).catch(() => {}); }).catch((error) => {
logger.error('Failed to send analytics event', { eventName, props, error });
});
return; return;
} }
@@ -121,11 +127,16 @@ export class UmamiAnalyticsService implements AnalyticsService {
if (!websiteId || !url) return; if (!websiteId || !url) return;
const logger = getServerAppServices().logger.child({ component: 'analytics' });
logger.info('Sending analytics pageview', { url });
fetch(`${umamiUrl}/api/send`, { fetch(`${umamiUrl}/api/send`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', 'User-Agent': 'KLZ-Server' }, headers: { 'Content-Type': 'application/json', 'User-Agent': 'KLZ-Server' },
body: JSON.stringify({ type: 'event', payload: { website: websiteId, url } }), body: JSON.stringify({ type: 'event', payload: { website: websiteId, url } }),
}).catch(() => {}); }).catch((error) => {
logger.error('Failed to send analytics pageview', { url, error });
});
return; return;
} }