Compare commits

..

No commits in common. "deployment-ready" and "main" have entirely different histories.

5 changed files with 48 additions and 42 deletions

View File

@ -154,45 +154,20 @@ RUN npm run build
- `Dockerfile` - Build process improvements - `Dockerfile` - Build process improvements
- Various form components - File handling updates - Various form components - File handling updates
The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration. The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
# CI/CD Guide: Free Setup with Database Persistence The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
## Overview The combination of these changes resolved alsl deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
This guide explains how to set up a completely free CI/CD pipeline using open-source tools and addresses database persistence concerns when deploying containerized applications.
## Free CI/CD Solutions (No Portainer Webhooks Needed) The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
### Option 1: Gitea Actions (Recommended) The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
Since you already have Gitea and a runner container:
**How it works:** The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
1. **Gitea Actions** (built into Gitea) works like GitHub Actions
2. Your runner container executes workflows defined in `.gitea/workflows/*.yml`
3. On push to main branch → workflow triggers → builds Docker image → deploys directly to your server
**Deployment Methods:** The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
- **SSH Deployment**: Workflow SSHs into your server and runs `docker-compose up -d`
- **Docker API**: Direct API calls to Docker daemon on your server
- **Docker Swarm**: Use `docker service update` for rolling updates
- **Simple Container Restart**: Pull new image and restart containers
### Option 2: Other Free Alternatives The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
- **Drone CI**: Lightweight, Docker-native CI/CD
- **Jenkins**: Classic option, can run in Docker
- **GitLab CE**: Self-hosted with built-in CI/CD
- **Woodpecker CI**: Fork of Drone, simpler setup
### Option 3: Simple Script-Based Approach
- **Git Hooks**: Post-receive hook on your Git server
- **Cron + Git Pull**: Simple script that checks for updates
- **Webhook Alternatives**: Use free services like webhook.site or build simple webhook receiver
## Database Persistence (Your Data is Safe! 🎉)
**Short Answer:** You will NOT lose database data when rebuilding your app image.
**Here's why:**
### Container vs Data Separation

View File

@ -12,6 +12,11 @@ interface ErrorPageProps {
} }
const ErrorPage = ({ error, reset }: ErrorPageProps) => { const ErrorPage = ({ error, reset }: ErrorPageProps) => {
console.error('🔥 ERROR PAGE: Error caught:', error);
console.error('🔥 ERROR PAGE: Error message:', error?.message);
console.error('🔥 ERROR PAGE: Error stack:', error?.stack);
console.error('🔥 ERROR PAGE: Error digest:', error?.digest);
return ( return (
<div className="h-screen flex flex-col gap-y-4 items-center justify-center"> <div className="h-screen flex flex-col gap-y-4 items-center justify-center">
<AlertTriangle className="size-6 text-red-500" /> <AlertTriangle className="size-6 text-red-500" />

View File

@ -6,6 +6,8 @@ import { Toaster } from "@/components/ui/sonner";
import { ClientErrorHandler } from "@/components/client-error-handler"; import { ClientErrorHandler } from "@/components/client-error-handler";
import "./globals.css"; import "./globals.css";
console.log('🔥 LAYOUT: Root layout module loading...')
const geistSans = localFont({ const geistSans = localFont({
src: "./fonts/GeistVF.woff", src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans", variable: "--font-geist-sans",
@ -30,6 +32,8 @@ export default function RootLayout({
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
console.log('🔥 LAYOUT: Root layout component rendering...')
try { try {
return ( return (
<html lang="en"> <html lang="en">
@ -49,6 +53,7 @@ export default function RootLayout({
</html> </html>
); );
} catch (error) { } catch (error) {
console.error('🔥 LAYOUT ERROR:', error)
throw error throw error
} }
} }

View File

@ -7,6 +7,8 @@ import { ZodError } from "zod";
import { loginSchema } from "./features/auth/schemas"; import { loginSchema } from "./features/auth/schemas";
import AuthentikProvider from "next-auth/providers/authentik"; import AuthentikProvider from "next-auth/providers/authentik";
console.log('🔥 AUTH: Auth configuration loading...')
export const { handlers, signIn, signOut, auth } = NextAuth({ export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma), adapter: PrismaAdapter(prisma),
providers: [ providers: [
@ -19,6 +21,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
issuer: "https://authentik.lci.ge/application/o/jira/", issuer: "https://authentik.lci.ge/application/o/jira/",
checks: ["pkce", "state"], checks: ["pkce", "state"],
profile(profile) { profile(profile) {
console.log('🔥 AUTH: Authentik profile received:', profile)
return { return {
id: profile.sub, id: profile.sub,
name: profile.name || profile.preferred_username, name: profile.name || profile.preferred_username,
@ -33,12 +36,15 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
password: { label: "Password", type: "password" }, password: { label: "Password", type: "password" },
}, },
authorize: async (credentials) => { authorize: async (credentials) => {
console.log('🔥 AUTH: Credentials authorize called')
try { try {
if (!credentials?.email || !credentials?.password) { if (!credentials?.email || !credentials?.password) {
console.log('🔥 AUTH: Missing credentials')
return null; return null;
} }
const { email, password } = await loginSchema.parseAsync(credentials); const { email, password } = await loginSchema.parseAsync(credentials);
console.log('🔥 AUTH: Looking up user:', email)
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { where: {
@ -47,6 +53,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
}); });
if (!user || !user.hashedPassword) { if (!user || !user.hashedPassword) {
console.log("🔥 AUTH: User not found or no password");
return null; return null;
} }
@ -56,15 +63,18 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
); );
if (!passwordsMatch) { if (!passwordsMatch) {
console.log("🔥 AUTH: Passwords don't match");
return null; return null;
} }
console.log("🔥 AUTH: User authenticated successfully:", user.id);
return { return {
id: user.id, id: user.id,
email: user.email, email: user.email,
name: user.name name: user.name
}; };
} catch (error) { } catch (error) {
console.error("🔥 AUTH ERROR:", error);
return null; return null;
} }
}, },
@ -75,14 +85,18 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
debug: process.env.NODE_ENV === "development", debug: process.env.NODE_ENV === "development",
secret: process.env.AUTH_SECRET || "your-fallback-secret-for-development", secret: process.env.AUTH_SECRET || "your-fallback-secret-for-development",
callbacks: { callbacks: {
session: ({ session, token }) => ({ session: ({ session, token }) => {
...session, console.log('🔥 AUTH: Session callback called')
user: { return {
...session.user, ...session,
id: token.id as string, user: {
}, ...session.user,
}), id: token.id,
},
}
},
jwt: ({ token, user }) => { jwt: ({ token, user }) => {
console.log('🔥 AUTH: JWT callback called')
if (user) { if (user) {
token.id = user.id; token.id = user.id;
} }
@ -91,6 +105,8 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
}, },
}); });
console.log('🔥 AUTH: Auth configuration completed')
export const authOptions = { export const authOptions = {
adapter: PrismaAdapter(prisma), adapter: PrismaAdapter(prisma),
providers: [Credentials], providers: [Credentials],

View File

@ -4,12 +4,17 @@ import { useEffect } from 'react';
export const ClientErrorHandler = () => { export const ClientErrorHandler = () => {
useEffect(() => { useEffect(() => {
console.log('🔥 CLIENT: Setting up global error handlers...');
const handleError = (event: ErrorEvent) => { const handleError = (event: ErrorEvent) => {
// Handle client-side errors silently console.error('🔥 CLIENT ERROR:', event.error);
console.error('🔥 CLIENT ERROR Stack:', event.error?.stack);
console.error('🔥 CLIENT ERROR Message:', event.message);
console.error('🔥 CLIENT ERROR Source:', event.filename, 'Line:', event.lineno);
}; };
const handleUnhandledRejection = (event: PromiseRejectionEvent) => { const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
// Handle unhandled promise rejections silently console.error('🔥 CLIENT UNHANDLED PROMISE REJECTION:', event.reason);
}; };
window.addEventListener('error', handleError); window.addEventListener('error', handleError);