Compare commits
1 Commits
main
...
deployment
| Author | SHA1 | Date | |
|---|---|---|---|
| 90da3b07f2 |
@ -154,20 +154,45 @@ 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.
|
||||||
|
|
||||||
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 alsl deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
|
## Overview
|
||||||
|
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.
|
||||||
|
|
||||||
The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
|
## 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)
|
||||||
|
Since you already have Gitea and a runner container:
|
||||||
|
|
||||||
The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
|
**How it works:**
|
||||||
|
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
|
||||||
|
|
||||||
The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
|
**Deployment Methods:**
|
||||||
|
- **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
|
||||||
|
|
||||||
The combination of these changes resolved all deployment issues and enabled successful containerized deployment with Portainer and nginx-proxy-manager integration.
|
### Option 2: Other Free Alternatives
|
||||||
|
- **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
|
||||||
|
|
||||||
|
|||||||
@ -12,11 +12,6 @@ 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" />
|
||||||
|
|||||||
@ -6,8 +6,6 @@ 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",
|
||||||
@ -32,8 +30,6 @@ 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">
|
||||||
@ -53,7 +49,6 @@ export default function RootLayout({
|
|||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('🔥 LAYOUT ERROR:', error)
|
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/auth.ts
30
src/auth.ts
@ -7,8 +7,6 @@ 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: [
|
||||||
@ -21,7 +19,6 @@ 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,
|
||||||
@ -36,15 +33,12 @@ 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: {
|
||||||
@ -53,7 +47,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,18 +56,15 @@ 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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -85,18 +75,14 @@ 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 }) => ({
|
||||||
console.log('🔥 AUTH: Session callback called')
|
...session,
|
||||||
return {
|
user: {
|
||||||
...session,
|
...session.user,
|
||||||
user: {
|
id: token.id as string,
|
||||||
...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;
|
||||||
}
|
}
|
||||||
@ -105,8 +91,6 @@ 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],
|
||||||
|
|||||||
@ -4,17 +4,12 @@ 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) => {
|
||||||
console.error('🔥 CLIENT ERROR:', event.error);
|
// Handle client-side errors silently
|
||||||
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) => {
|
||||||
console.error('🔥 CLIENT UNHANDLED PROMISE REJECTION:', event.reason);
|
// Handle unhandled promise rejections silently
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('error', handleError);
|
window.addEventListener('error', handleError);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user