Blog

  • NextJS: RBAC & ABAC

    NextJS: RBAC & ABAC

    RBAC

    export type User = { roles: Role[]; id: string }
    
    type Role = keyof typeof ROLES
    type Permission = (typeof ROLES)[Role][number]
    
    const ROLES = {
      admin: [
        "view:comments",
        "create:comments",
        "update:comments",
        "delete:comments",
      ],
      moderator: ["view:comments", "create:comments", "delete:comments"],
      user: ["view:comments", "create:comments"],
    } as const
    
    export function hasPermission(user: User, permission: Permission) {
      return user.roles.some(role =>
        (ROLES[role] as readonly Permission[]).includes(permission)
      )
    }
    
    // USAGE:
    const user: User = { id: "1", roles: ["user"] }
    
    // Can create a comment
    hasPermission(user, "create:comments")
    
    // Can view all comments
    hasPermission(user, "view:comments")
    

    ABAC

    type Comment = {
      id: string
      body: string
      authorId: string
      createdAt: Date
    }
    
    type Todo = {
      id: string
      title: string
      userId: string
      completed: boolean
      invitedUsers: string[]
    }
    
    type Role = "admin" | "moderator" | "user"
    type User = { blockedBy: string[]; roles: Role[]; id: string }
    
    type PermissionCheck<Key extends keyof Permissions> =
      | boolean
      | ((user: User, data: Permissions[Key]["dataType"]) => boolean)
    
    type RolesWithPermissions = {
      [R in Role]: Partial<{
        [Key in keyof Permissions]: Partial<{
          [Action in Permissions[Key]["action"]]: PermissionCheck<Key>
        }>
      }>
    }
    
    type Permissions = {
      comments: {
        dataType: Comment
        action: "view" | "create" | "update"
      }
      todos: {
        // Can do something like Pick<Todo, "userId"> to get just the rows you use
        dataType: Todo
        action: "view" | "create" | "update" | "delete"
      }
    }
    
    const ROLES = {
      admin: {
        comments: {
          view: true,
          create: true,
          update: true,
        },
        todos: {
          view: true,
          create: true,
          update: true,
          delete: true,
        },
      },
      moderator: {
        comments: {
          view: true,
          create: true,
          update: true,
        },
        todos: {
          view: true,
          create: true,
          update: true,
          delete: (user, todo) => todo.completed,
        },
      },
      user: {
        comments: {
          view: (user, comment) => !user.blockedBy.includes(comment.authorId),
          create: true,
          update: (user, comment) => comment.authorId === user.id,
        },
        todos: {
          view: (user, todo) => !user.blockedBy.includes(todo.userId),
          create: true,
          update: (user, todo) =>
            todo.userId === user.id || todo.invitedUsers.includes(user.id),
          delete: (user, todo) =>
            (todo.userId === user.id || todo.invitedUsers.includes(user.id)) &&
            todo.completed,
        },
      },
    } as const satisfies RolesWithPermissions
    
    export function hasPermission<Resource extends keyof Permissions>(
      user: User,
      resource: Resource,
      action: Permissions[Resource]["action"],
      data?: Permissions[Resource]["dataType"]
    ) {
      return user.roles.some(role => {
        const permission = (ROLES as RolesWithPermissions)[role][resource]?.[action]
        if (permission == null) return false
    
        if (typeof permission === "boolean") return permission
        return data != null && permission(user, data)
      })
    }
    
    // USAGE:
    const user: User = { blockedBy: ["2"], id: "1", roles: ["user"] }
    const todo: Todo = {
      completed: false,
      id: "3",
      invitedUsers: [],
      title: "Test Todo",
      userId: "1",
    }
    
    // Can create a comment
    hasPermission(user, "comments", "create")
    
    // Can view the `todo` Todo
    hasPermission(user, "todos", "view", todo)
    
    // Can view all todos
    hasPermission(user, "todos", "view")
    
  • NextJS: Rate limiter

    NextJS: Rate limiter

    route.ts

    const rateLimitMap = new Map();
    
    export default function rateLimitMiddleware(handler) {
      return (req, res) => {
        const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
        const limit = 5; // Limiting requests to 5 per minute per IP
        const windowMs = 60 * 1000; // 1 minute
    
        if (!rateLimitMap.has(ip)) {
          rateLimitMap.set(ip, {
            count: 0,
            lastReset: Date.now(),
          });
        }
    
        const ipData = rateLimitMap.get(ip);
    
        if (Date.now() - ipData.lastReset > windowMs) {
          ipData.count = 0;
          ipData.lastReset = Date.now();
        }
    
        if (ipData.count >= limit) {
          return res.status(429).send("Too Many Requests");
        }
    
        ipData.count += 1;
    
        return handler(req, res);
      };
    }
    
  • Better auth : Login Page

    Better auth : Login Page

    login-form.tsx

    "use client";
    import { cn } from "@/lib/utils";
    import { Button } from "@/components/ui/button";
    import {
      Card,
      CardContent,
      CardDescription,
      CardHeader,
      CardTitle,
    } from "@/components/ui/card";
    import { Input } from "@/components/ui/input";
    import { useForm } from "react-hook-form";
    import { loginSchema, LoginSchemaType } from "@/lib/zod-schema";
    import { zodResolver } from "@hookform/resolvers/zod";
    import {
      Form,
      FormControl,
      FormField,
      FormItem,
      FormLabel,
      FormMessage,
    } from "./ui/form";
    import { useTransition } from "react";
    import { authClient } from "@/lib/auth-client";
    import { toast } from "sonner";
    import Link from "next/link";
    import { useRouter, useSearchParams } from "next/navigation";
    
    export function LoginForm({
      className,
      ...props
    }: React.ComponentProps<"div">) {
      const form = useForm<LoginSchemaType>({
        resolver: zodResolver(loginSchema),
        defaultValues: {
          email: "",
          password: "",
        },
      });
      const [pending, startTransisition] = useTransition();
      const searchParams = useSearchParams();
      const router = useRouter();
    
      const handleLogin = (data: LoginSchemaType) => {
        startTransisition(async () => {
          await authClient.signIn.email({
            email: data.email,
            password: data.password,
            fetchOptions: {
              async onSuccess(ctx) {
                toast.success("Login sucess!!!, Redirecting!!!");
                const params = new URLSearchParams(searchParams);
                const callbackUrl = params.get("callbackUrl");
                if (callbackUrl) {
                  window.location.href = callbackUrl;
                }
                if (ctx.data.twoFactorRedirect) {
                  router.push("/two-factor");
                }
              },
              onError: (ctx) => {
                toast.error(ctx.error.message);
              },
            },
          });
        });
      };
    
      return (
        <div className={cn("flex flex-col gap-6", className)} {...props}>
          <Card>
            <CardHeader className="text-center">
              <CardTitle className="text-xl">Welcome back</CardTitle>
              <CardDescription>
                Login with your Apple or Google account
              </CardDescription>
            </CardHeader>
            <CardContent>
              <Form {...form}>
                <form onSubmit={form.handleSubmit(handleLogin)}>
                  <div className="grid gap-6">
                    <div className="flex flex-col gap-4">
                      <Button variant="outline" className="w-full">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                          <path
                            d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
                            fill="currentColor"
                          />
                        </svg>
                        Login with Apple
                      </Button>
                      <Button variant="outline" className="w-full">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                          <path
                            d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
                            fill="currentColor"
                          />
                        </svg>
                        Login with Google
                      </Button>
                    </div>
                    <div className="after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t">
                      <span className="bg-card text-muted-foreground relative z-10 px-2">
                        Or continue with
                      </span>
                    </div>
                    <div className="grid gap-6">
                      <FormField
                        control={form.control}
                        name="email"
                        render={({ field }) => (
                          <FormItem>
                            <div className="grid gap-3">
                              <FormLabel>Email</FormLabel>
                              <FormControl>
                                <Input placeholder="Enter email" {...field} />
                              </FormControl>
                              <FormMessage />
                            </div>
                          </FormItem>
                        )}
                      />
                      <FormField
                        control={form.control}
                        name="password"
                        render={({ field }) => (
                          <FormItem>
                            <div className="grid gap-3">
                              <div className="flex items-center">
                                <FormLabel>Password</FormLabel>
                                <Link
                                  href="#"
                                  className="ml-auto text-sm underline-offset-4 hover:underline"
                                >
                                  Forgot your password?
                                </Link>
                              </div>
                              <FormControl>
                                <Input type="password" {...field} />
                              </FormControl>
                              <FormMessage />
                            </div>
                          </FormItem>
                        )}
                      />
                      <Button type="submit" className="w-full">
                        Login
                      </Button>
                    </div>
                    <div className="text-center text-sm">
                      Don't have an account?{" "}
                      <a href="#" className="underline underline-offset-4">
                        Sign up
                      </a>
                    </div>
                  </div>
                </form>
              </Form>
            </CardContent>
          </Card>
          <div className="text-muted-foreground *:[a]:hover:text-primary text-center text-xs text-balance *:[a]:underline *:[a]:underline-offset-4">
            By clicking continue, you agree to our <a href="#">Terms of Service</a>{" "}
            and <a href="#">Privacy Policy</a>.
          </div>
        </div>
      );
    }
    

    login/page.tsx

    import { GalleryVerticalEnd } from "lucide-react"
    
    import { LoginForm } from "@/components/login-form"
    
    export default function LoginPage() {
      return (
        <div className="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
          <div className="flex w-full max-w-sm flex-col gap-6">
            <a href="#" className="flex items-center gap-2 self-center font-medium">
              <div className="bg-primary text-primary-foreground flex size-6 items-center justify-center rounded-md">
                <GalleryVerticalEnd className="size-4" />
              </div>
              Acme Inc.
            </a>
            <LoginForm />
          </div>
        </div>
      )
    }
    
  • AuthJS: Register

    AuthJS: Register

    Register-form.tsx

    "use client";
    import { cn } from "@/lib/utils";
    import { Button } from "@/components/ui/button";
    import {
      Card,
      CardContent,
      CardDescription,
      CardHeader,
      CardTitle,
    } from "@/components/ui/card";
    import { Input } from "@/components/ui/input";
    import { Label } from "@/components/ui/label";
    import { registerSchema, RegisterSchemaType } from "@/lib/zod-schema";
    import { zodResolver } from "@hookform/resolvers/zod";
    import {
      Form,
      FormControl,
      FormField,
      FormItem,
      FormLabel,
      FormMessage,
    } from "./ui/form";
    import { useForm } from "react-hook-form";
    import Link from "next/link";
    import { useTransition } from "react";
    import { authClient } from "@/lib/auth-client";
    import { toast } from "sonner";
    import Conditional from "@/lib/conditional";
    import { Loader2Icon } from "lucide-react";
    import { useRouter } from "next/navigation";
    
    export function RegisterForm({
      className,
      ...props
    }: React.ComponentProps<"div">) {
      const form = useForm<RegisterSchemaType>({
        resolver: zodResolver(registerSchema),
        defaultValues: {
          name: "",
          email: "",
          password: "",
          confirm: "",
        },
      });
      const [pending, startTransition] = useTransition();
      const router = useRouter();
    
      const handleRegister = (data: RegisterSchemaType) => {
        console.log(data, "data");
        startTransition(async () => {
          await authClient.signUp.email({
            name: data.name,
            email: data.email,
            password: data.password,
            callbackURL: "/",
            fetchOptions: {
              onSuccess: () => {
                toast.success(
                  "Registration successful!  Redirecting to home page..."
                );
                router.push("/");
              },
              onError: (ctx) => {
                toast.error(ctx.error.message);
              },
            },
          });
        });
      };
    
      return (
        <div className={cn("flex flex-col gap-6", className)} {...props}>
          <Card>
            <CardHeader className="text-center">
              <CardTitle className="text-xl">Welcome back</CardTitle>
              <CardDescription>
                Register with your Apple or Google account
              </CardDescription>
            </CardHeader>
            <CardContent>
              <Form {...form}>
                <form onSubmit={form.handleSubmit(handleRegister)}>
                  <div className="grid gap-6">
                    <div className="flex flex-col gap-4">
                      <Button variant="outline" className="w-full">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                          <path
                            d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
                            fill="currentColor"
                          />
                        </svg>
                        Login with Apple
                      </Button>
                      <Button variant="outline" className="w-full">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                          <path
                            d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
                            fill="currentColor"
                          />
                        </svg>
                        Login with Google
                      </Button>
                    </div>
                    <div className="after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t">
                      <span className="bg-card text-muted-foreground relative z-10 px-2">
                        Or continue with
                      </span>
                    </div>
                    <div className="grid gap-6">
                      <FormField
                        control={form.control}
                        name="name"
                        render={({ field }) => (
                          <FormItem>
                            <div className="grid gap-3">
                              <FormLabel>Name</FormLabel>
                              <FormControl>
                                <Input placeholder="Enter Name" {...field} />
                              </FormControl>
                              <FormMessage />
                            </div>
                          </FormItem>
                        )}
                      />
                      <FormField
                        control={form.control}
                        name="email"
                        render={({ field }) => (
                          <FormItem>
                            <div className="grid gap-3">
                              <FormLabel>Email</FormLabel>
                              <FormControl>
                                <Input placeholder="[email protected]" {...field} />
                              </FormControl>
                              <FormMessage />
                            </div>
                          </FormItem>
                        )}
                      />
                      <FormField
                        control={form.control}
                        name="password"
                        render={({ field }) => (
                          <FormItem>
                            <div className="grid gap-3">
                              <FormLabel>Password</FormLabel>
                              <FormControl>
                                <Input placeholder="Password" {...field} />
                              </FormControl>
                              <FormMessage />
                            </div>
                          </FormItem>
                        )}
                      />
                      <FormField
                        control={form.control}
                        name="confirm"
                        render={({ field }) => (
                          <FormItem>
                            <div className="grid gap-3">
                              <FormLabel>Confirm Password</FormLabel>
                              <FormControl>
                                <Input placeholder="Confirm Password" {...field} />
                              </FormControl>
                              <FormMessage />
                            </div>
                          </FormItem>
                        )}
                      />
                      <Button type="submit" className="w-full">
                        <Conditional
                          test={!pending}
                          fallback={<Loader2Icon className="size-4 animate-spin" />}
                        >
                          Register
                        </Conditional>
                      </Button>
                    </div>
                    <div className="text-center text-sm">
                      Already have an account?{" "}
                      <Link href="/login" className="underline underline-offset-4">
                        Login
                      </Link>
                    </div>
                  </div>
                </form>
              </Form>
            </CardContent>
          </Card>
          <div className="text-muted-foreground *:[a]:hover:text-primary text-center text-xs text-balance *:[a]:underline *:[a]:underline-offset-4">
            By clicking continue, you agree to our <a href="#">Terms of Service</a>{" "}
            and <a href="#">Privacy Policy</a>.
          </div>
        </div>
      );
    }
    

    register/page.tsx

    import { RegisterForm } from "@/components/register-form";
    
    export default function RegisterPage() {
      return (
        <div className="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
          <RegisterForm />
        </div>
      );
    }
    
  • Setup Auth.js for NextJS 15

    Setup Auth.js for NextJS 15

    Installing Auth.js

    npm install next-auth@beta

    Setup Environment

    npx auth secret

    Configure

    // ./auth.ts
    
    import { PrismaAdapter } from "@auth/prisma-adapter";
    import NextAuth from "next-auth";
    import Credentials from "next-auth/providers/credentials";
    import prisma from "./prisma";
    import bcrypt from "bcrypt";
    
    export const { handlers, signIn, signOut, auth } = NextAuth({
      adapter: PrismaAdapter(prisma),
      secret: process.env.NEXTAUTH_SECRET,
      pages: {
        signIn: "/login",
      },
      session: {
        strategy: "jwt",
        maxAge: 30 * 24 * 60 * 60, // 30 days
      },
      providers: [
        Credentials({
          name: "credentials",
          credentials: {
            email: { label: "Email", type: "email" },
            password: { label: "Password", type: "password" },
          },
          authorize: async (credentials: Record<string, string>) => {
            try {
              const { userId, password } = credentials;
              const user = await prisma.user.findFirst({
                where: {
                  OR: [{ email: userId }, { username: userId }],
                },
              });
              if (!user) return null;
              // Check if the password is correct
              const isValidPassword = await bcrypt.compare(
                password,
                user.hashedPassword
              );
              if (!isValidPassword) return null;
              return {
                fullname: user?.fullname ?? "",
                email: user?.email ?? "",
                role: user?.role?.toLocaleLowerCase() ?? "user",
                id: user?.id ?? "",
              };
            } catch (error) {
              console.error("Error authorizing credentials:", error);
              throw new Error("Invalid credentials");
            }
          },
        }),
      ],
      callbacks: {
        async jwt({ token, user }) {
          if (user) {
            token.fullname = user.fullname;
            token.role = user.role;
          }
          return token;
        },
        async session({ session, token }) {
          if (token?.role) {
            session.user.fullname = token.fullname as string;
            session.user.role = token.role as string;
          }
          return session;
        },
      },
    });
    
    // ./app/api/auth/[...nextauth]/route.ts
    
    import { handlers } from "@/auth" // Referring to the auth.ts we just created
    export const { GET, POST } = handlers
    // ./middleware.ts
    
    export { auth as middleware } from "@/auth"

    Prisma Adapter

    npm install @prisma/client @auth/prisma-adapter
    npm install prisma --save-dev
    DATABASE_URL= "postgresql://postgres:[email protected]:5432/shopcart?schema=public"
    // prima.ts
    
    import { PrismaClient } from "@prisma/client"
     
    const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
     
    export const prisma = globalForPrisma.prisma || new PrismaClient()
     
    if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma
    // prisma/schema-postgres.prisma
    
    datasource db {
      provider = "postgresql"
      url      = env("DATABASE_URL")
    }
     
    generator client {
      provider = "prisma-client-js"
    }
     
    model User {
      id            String          @id @default(cuid())
      name          String?
      email         String          @unique
      emailVerified DateTime?
      image         String?
      accounts      Account[]
      sessions      Session[]
      // Optional for WebAuthn support
      Authenticator Authenticator[]
     
      createdAt DateTime @default(now())
      updatedAt DateTime @updatedAt
    }
     
    model Account {
      userId            String
      type              String
      provider          String
      providerAccountId String
      refresh_token     String?
      access_token      String?
      expires_at        Int?
      token_type        String?
      scope             String?
      id_token          String?
      session_state     String?
     
      createdAt DateTime @default(now())
      updatedAt DateTime @updatedAt
     
      user User @relation(fields: [userId], references: [id], onDelete: Cascade)
     
      @@id([provider, providerAccountId])
    }
    npx prisma db push

  • Access Ollama remotely

    Access Ollama remotely

    Edit /etc/systemd/system/ollama.service (I used nano) Add (under the service category):

    Environment=”OLLAMA_HOST=0.0.0.0″

    [Unit]
    Description=Ollama Service
    After=network-online.target
    
    [Service]
    Environment="OLLAMA_HOST=0.0.0.0"
    Broadcast message from root@jellyfinollama (Mon 2025-06-30 04:03:01 UTC):
    User=ollama
    The system will reboot at Mon 2025-06-30 04:05:01 UTC!
    Restart=always
    RestartSec=3
    Broadcast message from root@jellyfinollama (Mon 2025-06-30 04:03:01 UTC)::

    Start Ollama with the command: ollama serve

  • How can I schedule a nightly reboot?

    How can I schedule a nightly reboot?

    I’d use cron (should already be installed):

    sudo crontab -e

    The first time you might have to choose your preferred editor (like nano)

    Insert a line like

    0 4   *   *   *    /sbin/shutdown -r +5

    at the bottom. Explanation:

    m      h    dom        mon   dow       command
    minute hour dayOfMonth Month dayOfWeek commandToRun

    would announce the reboot every day at 4:00am, then reboot 5 minutes later (at 4:05am).

    Ctrl+XYEnter should get you out of crontab (if using nano)

    Note: you might have to run crontab -e as root, because shutdown needs root. crontab -e opens a file in /tmp instead of the actual crontab so that it can check your new crontab for errors. If there are no errors, then your actual crontab will be updated.

  • Nginx config for webserver

    Nginx config for webserver

    apt install nginx
    
    cd /etc/nginx/sites-available
    vim serivce
    
    cd ../sites-enabled
    ln -s ../sites-available/service
    
    nginx -t
    
    systemctl reload nginx
    server {
        server_name *.rajubk.com;
        client_max_body_size 1G;
    
        location / {
            proxy_pass http://localhost:9000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        ssl_certificate /etc/letsencrypt/live/domain.tld/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/domain.tld/privkey.pem;
    
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50m;
        ssl_session_tickets off;
    
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256';
    
        add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Referrer-Policy "no-referrer" always;
        add_header Permissions-Policy "geolocation=(), microphone=()" always;
        add_header X-XSS-Protection "1; mode=block" always;
    }
    
    # HTTP redirect block
    server {
        listen 80;
        server_name *.rajubk.com;
        return 301 https://$host$request_uri;
    }
    cd /etc/nginx/sites-enabled/
    ln -s ../sites-available/minio
    nginx -t
    systemctl reload nginx
  • Install minio on ubuntu with docker compose

    Install minio on ubuntu with docker compose

    services:
      minio:
        image: minio/minio:RELEASE.2025-03-12T18-04-18Z-cpuv1
        container_name: minio-server
        environment:
          MINIO_ROOT_USER: minioadmin
          MINIO_ROOT_PASSWORD: minioadmin
        ports:
          - "9000:9000" # MinIO API and Console
          - "9001:9001" # MinIO Console (if using separate port for console)
        volumes:
          - minio_data:/data # Mount a named volume for persistent data
        command: server /data --console-address ":9001"
        restart: unless-stopped
    
    volumes:
      minio_data:
  • Nginx config for wordpress

    Nginx config for wordpress

    server {
        listen 80;
        server_name *.rajubk.com rajubk.com;
        client_max_body_size 512M;
    
        return 301 https://$host$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        server_name *.rajubk.com rajubk.com;
        client_max_body_size 512M;
    
        root /var/www/html/wordpress;
        index index.php;
    
        # SSL parameters
        ssl_certificate /etc/letsencrypt/live/rajubk.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/rajubk.com/privkey.pem;
        ssl_trusted_certificate /etc/letsencrypt/live/rajubk.com/chain.pem;
    
        # Log files
        access_log /var/log/nginx/sample.com.access.log;
        error_log /var/log/nginx/sample.com.error.log;
    
        location = /favicon.ico {
            log_not_found off;
            access_log off;
        }
    
        location = /robots.txt {
            allow all;
            log_not_found off;
            access_log off;
        }
    
        location / {
            try_files $uri $uri/ /index.php?$args;
        }
    
        location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/run/php/php8.3-fpm.sock;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
            expires max;
            log_not_found off;
        }
    
        # START Nginx Rewrites for Rank Math Sitemaps
        rewrite ^/sitemap_index\.xml$ /index.php?sitemap=1 last;
        rewrite ^/([^/]+?)-sitemap([0-9]+)?.xml$ /index.php?sitemap=$1&sitemap_n=$2 last;
        # END Nginx Rewrites for Rank Math Sitemaps
    }