Reapply "FEATURE (ssr): Migrate to NextJS"
This reverts commit 042e10c49c.
48
.gitignore
vendored
@@ -1,7 +1,41 @@
|
||||
backend/
|
||||
frontend/
|
||||
.env
|
||||
pgdata/
|
||||
docker-compose.yml
|
||||
postgresus-data/
|
||||
node_modules/
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
semi: true,
|
||||
trailingComma: 'all',
|
||||
singleQuote: true,
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
arrowParens: 'always',
|
||||
plugins: ['prettier-plugin-tailwindcss'],
|
||||
};
|
||||
237
404.html
@@ -1,237 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>404 - Page Not Found | Postgresus</title>
|
||||
<meta name="title" content="404 - Page Not Found | Postgresus" />
|
||||
<meta
|
||||
name="description"
|
||||
content="The page you're looking for doesn't exist. Return to Postgresus - PostgreSQL monitoring and backup tool."
|
||||
/>
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="canonical" href="https://postgresus.com/404" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://postgresus.com/404" />
|
||||
<meta property="og:title" content="404 - Page Not Found | Postgresus" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="The page you're looking for doesn't exist. Return to Postgresus - PostgreSQL monitoring and backup tool."
|
||||
/>
|
||||
<meta property="og:image" content="https://postgresus.com/logo.svg" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary" />
|
||||
<meta property="twitter:url" content="https://postgresus.com/404" />
|
||||
<meta property="twitter:title" content="404 - Page Not Found | Postgresus" />
|
||||
<meta
|
||||
property="twitter:description"
|
||||
content="The page you're looking for doesn't exist. Return to Postgresus - PostgreSQL monitoring and backup tool."
|
||||
/>
|
||||
<meta property="twitter:image" content="https://postgresus.com/logo.svg" />
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="favicon.svg" />
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-GE01THYR9X"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-GE01THYR9X');
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-50">
|
||||
<!------------------ NAVBAR ------------------------->
|
||||
<nav
|
||||
class="fixed left-0 right-0 top-0 z-50 flex h-[60px] justify-center bg-white px-4 shadow-sm sm:h-[70px] md:h-[80px]"
|
||||
>
|
||||
<div
|
||||
class="flex w-[320px] min-w-0 max-w-[320px] grow items-center sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<a href="/" class="ml-2 text-lg font-bold sm:ml-3 sm:text-xl md:ml-4 md:text-2xl"
|
||||
>Postgresus</a
|
||||
>
|
||||
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://github.com/RostislavDugin/postgresus"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div
|
||||
class="flex items-center rounded-lg border bg-[#f5f7f9] px-2 py-1 hover:bg-gray-100 md:px-4 md:py-2"
|
||||
>
|
||||
<svg
|
||||
class="mr-1 sm:mr-2 md:mr-3"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm sm:text-base">Star on GitHub</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<!------------------ END OF NAVBAR ------------------>
|
||||
|
||||
<!------------------ 404 ERROR SECTION ------------------------->
|
||||
<div class="flex min-h-screen items-center justify-center px-4 pt-[80px] md:pt-[100px]">
|
||||
<div class="text-center">
|
||||
<div class="mb-8">
|
||||
<h1 class="mb-4 text-9xl font-bold text-blue-600">404</h1>
|
||||
<h2 class="mb-4 text-2xl font-bold text-gray-800 sm:text-3xl md:text-4xl">
|
||||
Page Not Found
|
||||
</h2>
|
||||
<p class="mx-auto max-w-md text-lg text-gray-600 sm:text-xl">
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center space-y-4 sm:flex-row sm:justify-center sm:space-x-4 sm:space-y-0"
|
||||
>
|
||||
<a
|
||||
href="/"
|
||||
class="inline-flex items-center rounded-lg border-2 border-blue-600 bg-blue-600 px-6 py-3 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
>
|
||||
<svg class="mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||
></path>
|
||||
</svg>
|
||||
Go Home
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<p class="text-gray-500">
|
||||
Need help? Visit our
|
||||
<a
|
||||
href="https://t.me/postgresus_community"
|
||||
target="_blank"
|
||||
class="text-blue-600 hover:underline"
|
||||
>
|
||||
Community
|
||||
</a>
|
||||
|
||||
or check out our
|
||||
<a
|
||||
href="https://github.com/RostislavDugin/postgresus"
|
||||
target="_blank"
|
||||
class="text-blue-600 hover:underline"
|
||||
>
|
||||
GitHub repository
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!------------------ END OF 404 ERROR SECTION ------------------>
|
||||
|
||||
<!------------------ FOOTER SECTION -------------------------------->
|
||||
<div class="flex justify-center bg-blue-600 py-[50px]">
|
||||
<div
|
||||
class="w-[320px] max-w-[320px] sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="flex flex-wrap justify-center gap-6 text-white">
|
||||
<a
|
||||
href="https://github.com/RostislavDugin/postgresus"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://t.me/postgresus_community"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
Community
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://rostislav-dugin.com"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
Developer
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-center text-sm text-white">© 2025 Postgresus. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!------------------ END OF FOOTER SECTION ------------------------->
|
||||
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script type="text/javascript">
|
||||
(function (m, e, t, r, i, k, a) {
|
||||
m[i] =
|
||||
m[i] ||
|
||||
function () {
|
||||
(m[i].a = m[i].a || []).push(arguments);
|
||||
};
|
||||
m[i].l = 1 * new Date();
|
||||
for (var j = 0; j < document.scripts.length; j++) {
|
||||
if (document.scripts[j].src === r) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
((k = e.createElement(t)),
|
||||
(a = e.getElementsByTagName(t)[0]),
|
||||
(k.async = 1),
|
||||
(k.src = r),
|
||||
a.parentNode.insertBefore(k, a));
|
||||
})(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js?id=103482608', 'ym');
|
||||
|
||||
ym(103482608, 'init', {
|
||||
ssr: true,
|
||||
clickmap: true,
|
||||
ecommerce: 'dataLayer',
|
||||
accurateTrackBounce: true,
|
||||
trackLinks: true,
|
||||
});
|
||||
</script>
|
||||
<noscript
|
||||
><div>
|
||||
<img
|
||||
src="https://mc.yandex.ru/watch/103482608"
|
||||
style="position: absolute; left: -9999px"
|
||||
alt=""
|
||||
/></div
|
||||
></noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
</body>
|
||||
</html>
|
||||
52
README.md
@@ -1,40 +1,36 @@
|
||||
# Postgresus Website
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## CSS Development Workflow
|
||||
## Getting Started
|
||||
|
||||
This project uses TailwindCSS. **You must build CSS before committing.**
|
||||
|
||||
### Setup (one time)
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
### Development Process
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
1. **Make changes** to `index.html` or other HTML files
|
||||
2. **Build CSS** before committing:
|
||||
```bash
|
||||
npm run build-css
|
||||
```
|
||||
3. **Commit both files**:
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Your changes"
|
||||
git push
|
||||
```
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
### For Active Development
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
```bash
|
||||
# Watch for changes (rebuilds automatically)
|
||||
npm run watch-css
|
||||
```
|
||||
## Learn More
|
||||
|
||||
### Important Files
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- `src/input.css` - Source CSS (edit this)
|
||||
- `styles.min.css` - Generated CSS (don't edit manually)
|
||||
- `index.html` - Uses the generated CSS
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
**Always run `npm run build-css` before pushing to GitHub!**
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
|
||||
59
app/components/CopyButton.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
interface CopyButtonProps {
|
||||
text: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function CopyButton({ text, className = "" }: CopyButtonProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copyToClipboard = () => {
|
||||
if (!navigator.clipboard) {
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(textArea);
|
||||
showCopyFeedback();
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
showCopyFeedback();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to copy text: ", err);
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(textArea);
|
||||
showCopyFeedback();
|
||||
});
|
||||
};
|
||||
|
||||
const showCopyFeedback = () => {
|
||||
setCopied(true);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className={`rounded px-2 py-1 text-xs text-white transition-colors ${
|
||||
copied ? "bg-green-500" : "bg-blue-600 hover:bg-blue-700"
|
||||
} ${className}`}
|
||||
>
|
||||
{copied ? "Copied!" : "Copy"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
100
app/components/DocTableOfContentComponent.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
|
||||
interface Heading {
|
||||
id: string;
|
||||
text: string;
|
||||
level: number;
|
||||
}
|
||||
|
||||
export default function DocTableOfContentComponent() {
|
||||
const [headings, setHeadings] = useState<Heading[]>([]);
|
||||
const [activeId, setActiveId] = useState<string>("");
|
||||
const observerRef = useRef<IntersectionObserver | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Use a timeout to avoid cascading renders
|
||||
const timeoutId = setTimeout(() => {
|
||||
// Get all h1, h2, h3 headings from the page
|
||||
const elements = Array.from(
|
||||
document.querySelectorAll("article h1, article h2, article h3")
|
||||
);
|
||||
|
||||
if (elements.length === 0) return;
|
||||
|
||||
const headingData: Heading[] = elements.map((element) => ({
|
||||
id: element.id,
|
||||
text: element.textContent || "",
|
||||
level: parseInt(element.tagName.substring(1)),
|
||||
}));
|
||||
|
||||
setHeadings(headingData);
|
||||
|
||||
// Set up intersection observer to track active heading
|
||||
observerRef.current = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
setActiveId(entry.target.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ rootMargin: "-100px 0px -80% 0px" }
|
||||
);
|
||||
|
||||
elements.forEach((element) => {
|
||||
observerRef.current?.observe(element);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
if (observerRef.current) {
|
||||
observerRef.current.disconnect();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClick = (id: string) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
};
|
||||
|
||||
if (headings.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="hidden w-64 border-l border-gray-200 bg-white xl:block">
|
||||
<div className="sticky top-0 h-screen overflow-y-auto p-6">
|
||||
<h3 className="mb-4 text-sm font-semibold text-gray-900">
|
||||
On This Page
|
||||
</h3>
|
||||
<nav>
|
||||
<ul className="space-y-2 text-sm">
|
||||
{headings.map((heading) => (
|
||||
<li
|
||||
key={heading.id}
|
||||
style={{ paddingLeft: `${(heading.level - 1) * 0.75}rem` }}
|
||||
>
|
||||
<button
|
||||
onClick={() => handleClick(heading.id)}
|
||||
className={`block w-full text-left transition-colors cursor-pointer hover:text-blue-600 ${
|
||||
activeId === heading.id
|
||||
? "font-medium text-blue-600"
|
||||
: "text-gray-600"
|
||||
}`}
|
||||
>
|
||||
{heading.text}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
62
app/components/DocsNavbarComponent.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function DocsNavbarComponent() {
|
||||
return (
|
||||
<nav className="flex h-[60px] w-full justify-center border-b border-gray-200 bg-white sm:h-[70px] md:h-[80px]">
|
||||
<div className="flex min-w-0 grow items-center px-4 sm:px-6 md:px-10">
|
||||
<Link href="/" className="flex items-center">
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt="Postgresus logo"
|
||||
width={30}
|
||||
height={30}
|
||||
className="shrink-0 sm:h-[40px] sm:w-[40px] md:h-[50px] md:w-[50px]"
|
||||
priority
|
||||
/>
|
||||
|
||||
<div className="ml-2 select-none text-lg font-bold sm:ml-3 sm:text-xl md:ml-4 md:text-2xl">
|
||||
Postgresus
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className="ml-auto mr-4 hidden gap-3 sm:mr-6 md:mr-10 lg:flex lg:gap-5">
|
||||
<a
|
||||
className="hover:opacity-70"
|
||||
href="https://t.me/postgresus_community"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Community
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a
|
||||
className="ml-auto lg:ml-0"
|
||||
href="https://github.com/RostislavDugin/postgresus"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="flex items-center rounded-lg border border-gray-200 bg-[#f5f7f9] px-2 py-1 hover:bg-gray-100 md:px-4 md:py-2">
|
||||
<Image
|
||||
src="/images/index/github.svg"
|
||||
className="mr-1 h-4 w-4 sm:mr-2 md:mr-3"
|
||||
alt="GitHub icon"
|
||||
width={16}
|
||||
height={16}
|
||||
priority
|
||||
/>
|
||||
<span className="text-sm sm:text-base">
|
||||
Star on GitHub
|
||||
<span className="hidden sm:inline">
|
||||
, it's really important ❤️
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
261
app/components/DocsSidebarComponent.tsx
Normal file
@@ -0,0 +1,261 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
interface NavItem {
|
||||
title: string;
|
||||
href: string;
|
||||
children?: NavItem[];
|
||||
}
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{
|
||||
title: "Installation",
|
||||
href: "/installation",
|
||||
},
|
||||
{
|
||||
title: "Storages",
|
||||
href: "/storages",
|
||||
children: [
|
||||
{ title: "Google Drive", href: "/storages/google-drive" },
|
||||
{ title: "Cloudflare R2", href: "/storages/cloudflare-r2" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Notifiers",
|
||||
href: "/notifiers",
|
||||
children: [
|
||||
{ title: "Slack", href: "/notifiers/slack" },
|
||||
{ title: "Microsoft Teams", href: "/notifiers/teams" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Reset password",
|
||||
href: "/password",
|
||||
},
|
||||
];
|
||||
|
||||
export default function DocsSidebarComponent() {
|
||||
const pathname = usePathname();
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [manuallyToggledSections, setManuallyToggledSections] = useState<
|
||||
Set<string>
|
||||
>(new Set());
|
||||
|
||||
const isActive = (href: string) => {
|
||||
return pathname === href;
|
||||
};
|
||||
|
||||
const isParentActive = (item: NavItem) => {
|
||||
if (item.children) {
|
||||
return item.children.some((child) => pathname === child.href);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Determine if a section should be expanded
|
||||
const isSectionExpanded = (href: string) => {
|
||||
// If manually toggled, respect that
|
||||
if (manuallyToggledSections.has(href)) {
|
||||
return true;
|
||||
}
|
||||
// Auto-expand if a child page is active
|
||||
const item = navItems.find((i) => i.href === href);
|
||||
if (item && isParentActive(item)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Manage body overflow when mobile menu is open
|
||||
useEffect(() => {
|
||||
if (isMobileMenuOpen) {
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
|
||||
// Cleanup on unmount
|
||||
return () => {
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [isMobileMenuOpen]);
|
||||
|
||||
const toggleSection = (href: string, hasChildren: boolean) => {
|
||||
if (!hasChildren) return;
|
||||
|
||||
setManuallyToggledSections((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(href)) {
|
||||
newSet.delete(href);
|
||||
} else {
|
||||
newSet.add(href);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const renderSidebarContent = () => (
|
||||
<nav className="space-y-1">
|
||||
{navItems.map((item) => (
|
||||
<div key={item.href}>
|
||||
<div className="flex items-center">
|
||||
<Link
|
||||
href={item.href}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className={`flex-1 rounded-lg px-3 py-2 text-sm font-medium transition-colors ${
|
||||
isActive(item.href)
|
||||
? "bg-blue-50 text-blue-700"
|
||||
: "text-gray-700 hover:bg-gray-100 hover:text-gray-900"
|
||||
}`}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
{item.children && (
|
||||
<button
|
||||
onClick={() => toggleSection(item.href, !!item.children)}
|
||||
className={`ml-1 rounded-lg p-2 transition-all duration-200 ${
|
||||
isActive(item.href)
|
||||
? "text-blue-700 hover:bg-blue-100"
|
||||
: "text-gray-500 hover:bg-gray-100 hover:text-gray-900"
|
||||
}`}
|
||||
aria-label={`Toggle ${item.title} section`}
|
||||
>
|
||||
<svg
|
||||
className={`h-4 w-4 transition-transform duration-200 ${
|
||||
isSectionExpanded(item.href) ? "rotate-180" : ""
|
||||
}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{item.children && (
|
||||
<div
|
||||
className={`overflow-hidden transition-all duration-300 ease-in-out ${
|
||||
isSectionExpanded(item.href)
|
||||
? "max-h-96 opacity-100"
|
||||
: "max-h-0 opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div className="ml-4 mt-1 space-y-1 border-l-2 border-gray-200 pl-3">
|
||||
{item.children.map((child) => (
|
||||
<Link
|
||||
key={child.href}
|
||||
href={child.href}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className={`block rounded-lg px-3 py-2 text-sm transition-colors ${
|
||||
isActive(child.href)
|
||||
? "bg-blue-50 text-blue-700 font-medium"
|
||||
: "text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
||||
}`}
|
||||
>
|
||||
{child.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
className="fixed bottom-4 right-4 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-blue-600 text-white shadow-lg hover:bg-blue-700 lg:hidden"
|
||||
aria-label="Toggle navigation menu"
|
||||
>
|
||||
{isMobileMenuOpen ? (
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Mobile Menu Overlay */}
|
||||
{isMobileMenuOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-40 backdrop-blur-md bg-white/10 lg:hidden"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<aside
|
||||
className={`fixed bottom-0 left-0 right-0 z-40 max-h-[80vh] overflow-y-auto rounded-t-2xl border-t border-gray-200 bg-white p-6 shadow-2xl transition-transform duration-300 lg:hidden ${
|
||||
isMobileMenuOpen ? "translate-y-0" : "translate-y-full"
|
||||
}`}
|
||||
>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Navigation</h2>
|
||||
<button
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className="rounded-lg p-2 text-gray-500 hover:bg-gray-100"
|
||||
aria-label="Close menu"
|
||||
>
|
||||
<svg
|
||||
className="h-5 w-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{renderSidebarContent()}
|
||||
</aside>
|
||||
|
||||
{/* Desktop Sidebar */}
|
||||
<aside className="hidden w-64 border-r border-gray-200 bg-white lg:block">
|
||||
<div className="sticky top-0 h-screen overflow-y-auto p-6">
|
||||
{renderSidebarContent()}
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
146
app/globals.css
Normal file
@@ -0,0 +1,146 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-jost);
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Jost, Arial, Helvetica, sans-serif;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Article/prose context headings - for documentation pages */
|
||||
article h1 {
|
||||
font-size: 2.75rem;
|
||||
font-weight: 800;
|
||||
color: #1a202c;
|
||||
border-bottom: 2px solid #3b82f6;
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 2rem;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
article h2 {
|
||||
font-size: 1.875rem;
|
||||
font-weight: 700;
|
||||
color: #2563eb;
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
article h3 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
article a {
|
||||
color: #3b82f6;
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
article p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
article ul {
|
||||
margin-bottom: 1.5rem;
|
||||
padding-left: 1.5rem;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
article ol {
|
||||
margin-bottom: 1.5rem;
|
||||
padding-left: 1.5rem;
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
article li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
article code {
|
||||
background-color: #f3f4f6;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
color: #1f2937;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
"Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
article pre {
|
||||
margin-bottom: 1.5rem;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
article pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
font-size: 0.875rem;
|
||||
color: inherit;
|
||||
display: block;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
article h1 {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
article h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
article h3 {
|
||||
font-size: 1.25rem;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
article ul,
|
||||
article ol {
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
article .relative {
|
||||
margin-left: -1rem;
|
||||
margin-right: -1rem;
|
||||
width: calc(100vw - 3rem);
|
||||
max-width: calc(100vw - 3rem);
|
||||
}
|
||||
|
||||
article pre {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.75rem !important;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
article pre code {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
article .relative {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
78
app/gone/route.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET() {
|
||||
return new NextResponse(
|
||||
`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title>410 - Content Removed</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background-color: #f9fafb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 1rem;
|
||||
}
|
||||
.container {
|
||||
max-width: 28rem;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
font-size: 4rem;
|
||||
font-weight: bold;
|
||||
color: #111827;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
p {
|
||||
color: #4b5563;
|
||||
margin-bottom: 2rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
a {
|
||||
display: inline-block;
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
a:hover {
|
||||
background-color: #1d4ed8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>410</h1>
|
||||
<h2>Content Permanently Removed</h2>
|
||||
<p>The content you are looking for has been permanently removed and is no longer available.</p>
|
||||
<a href="/">Return to Home</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>`,
|
||||
{
|
||||
status: 410,
|
||||
headers: {
|
||||
"Content-Type": "text/html; charset=utf-8",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
369
app/installation/page.tsx
Normal file
@@ -0,0 +1,369 @@
|
||||
import type { Metadata } from "next";
|
||||
import { CopyButton } from "../components/CopyButton";
|
||||
import DocsNavbarComponent from "../components/DocsNavbarComponent";
|
||||
import DocsSidebarComponent from "../components/DocsSidebarComponent";
|
||||
import DocTableOfContentComponent from "../components/DocTableOfContentComponent";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Installation - Postgresus Documentation",
|
||||
description:
|
||||
"Learn how to install Postgresus using automated script, Docker run or Docker Compose. Simple zero-config installation for your self-hosted PostgreSQL backup system.",
|
||||
keywords: [
|
||||
"Postgresus installation",
|
||||
"Docker installation",
|
||||
"PostgreSQL backup setup",
|
||||
"self-hosted backup",
|
||||
"Docker Compose",
|
||||
"database backup installation",
|
||||
"pg_dump setup",
|
||||
],
|
||||
openGraph: {
|
||||
title: "Installation - Postgresus Documentation",
|
||||
description:
|
||||
"Learn how to install Postgresus using automated script, Docker run or Docker Compose. Simple zero-config installation for your self-hosted PostgreSQL backup system.",
|
||||
type: "article",
|
||||
url: "https://postgresus.com/installation",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
title: "Installation - Postgresus Documentation",
|
||||
description:
|
||||
"Learn how to install Postgresus using automated script, Docker run or Docker Compose. Simple zero-config installation for your self-hosted PostgreSQL backup system.",
|
||||
},
|
||||
alternates: {
|
||||
canonical: "https://postgresus.com/installation",
|
||||
},
|
||||
robots: "index, follow",
|
||||
};
|
||||
|
||||
export default function InstallationPage() {
|
||||
const installScript = `sudo apt-get install -y curl && \\
|
||||
sudo curl -sSL https://raw.githubusercontent.com/RostislavDugin/postgresus/refs/heads/main/install-postgresus.sh | sudo bash`;
|
||||
|
||||
const dockerRun = `docker run -d \\
|
||||
--name postgresus \\
|
||||
-p 4005:4005 \\
|
||||
-v ./postgresus-data:/postgresus-data \\
|
||||
--restart unless-stopped \\
|
||||
rostislavdugin/postgresus:latest`;
|
||||
|
||||
const dockerCompose = `services:
|
||||
postgresus:
|
||||
container_name: postgresus
|
||||
image: rostislavdugin/postgresus:latest
|
||||
ports:
|
||||
- "4005:4005"
|
||||
volumes:
|
||||
- ./postgresus-data:/postgresus-data
|
||||
restart: unless-stopped`;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* JSON-LD Structured Data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "TechArticle",
|
||||
headline: "Installation - Postgresus Documentation",
|
||||
description:
|
||||
"Learn how to install Postgresus using automated script, Docker run or Docker Compose. Simple zero-config installation for your self-hosted PostgreSQL backup system.",
|
||||
author: {
|
||||
"@type": "Organization",
|
||||
name: "Postgresus",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Postgresus",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: "https://postgresus.com/logo.svg",
|
||||
},
|
||||
},
|
||||
datePublished: "2025-01-01",
|
||||
dateModified: "2025-01-01",
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "HowTo",
|
||||
name: "How to install Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to install Postgresus PostgreSQL backup tool",
|
||||
step: [
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Automated installation script",
|
||||
text: "Run the automated installation script to install Docker and set up Postgresus with automatic startup configuration.",
|
||||
itemListElement: [
|
||||
{
|
||||
"@type": "HowToDirection",
|
||||
text: "Execute the curl command to download and run the installation script",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Docker Run",
|
||||
text: "Use Docker run command to quickly start Postgresus container with data persistence.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Docker Compose",
|
||||
text: "Create a docker-compose.yml file and use Docker Compose for managed deployment.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
<DocsNavbarComponent />
|
||||
|
||||
<div className="flex min-h-screen">
|
||||
{/* Sidebar */}
|
||||
<DocsSidebarComponent />
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 px-4 py-6 sm:px-6 sm:py-8 lg:px-12">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<article className="prose prose-blue max-w-none">
|
||||
<h1 id="installation">Installation</h1>
|
||||
|
||||
<p className="text-lg text-gray-700">
|
||||
You have three ways to install Postgresus: automated script
|
||||
(recommended), simple Docker run or Docker Compose setup.
|
||||
</p>
|
||||
|
||||
<h2 id="system-requirements">System requirements</h2>
|
||||
|
||||
<p>
|
||||
Postgresus requires the following minimum system resources to
|
||||
run properly:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>CPU</strong>: At least 1 CPU cores
|
||||
</li>
|
||||
<li>
|
||||
<strong>RAM</strong>: Minimum 500 MB RAM
|
||||
</li>
|
||||
<li>
|
||||
<strong>Storage</strong>: 5 GB for installation and as much as
|
||||
you need for backups
|
||||
</li>
|
||||
<li>
|
||||
<strong>Docker</strong>: Docker Engine 20.10+ and Docker
|
||||
Compose v2.0+
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="option-1-automated-script">
|
||||
Option 1: installation script (recommended, Linux only)
|
||||
</h2>
|
||||
|
||||
<p>The installation script will:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
✅ Install Docker with Docker Compose (if not already
|
||||
installed)
|
||||
</li>
|
||||
<li>✅ Set up Postgresus</li>
|
||||
<li>✅ Configure automatic startup on system reboot</li>
|
||||
</ul>
|
||||
|
||||
<div className="relative my-6">
|
||||
<pre className="overflow-x-auto rounded-lg bg-gray-900 p-4 text-sm text-gray-100">
|
||||
<code>{installScript}</code>
|
||||
</pre>
|
||||
<div className="absolute right-2 top-2">
|
||||
<CopyButton text={installScript} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
In this case Postgresus will be installed in{" "}
|
||||
<code>/opt/postgresus</code> directory.
|
||||
</p>
|
||||
|
||||
<h2 id="option-2-docker-run">Option 2: Simple Docker run</h2>
|
||||
|
||||
<p>The easiest way to run Postgresus:</p>
|
||||
|
||||
<div className="relative my-6">
|
||||
<pre className="overflow-x-auto rounded-lg bg-gray-900 p-4 text-sm text-gray-100">
|
||||
<code>{dockerRun}</code>
|
||||
</pre>
|
||||
<div className="absolute right-2 top-2">
|
||||
<CopyButton text={dockerRun} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>This single command will:</p>
|
||||
|
||||
<ul>
|
||||
<li>✅ Start Postgresus</li>
|
||||
<li>
|
||||
✅ Store all data in <code>./postgresus-data</code> directory
|
||||
</li>
|
||||
<li>✅ Automatically restart on system reboot</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="option-3-docker-compose">
|
||||
Option 3: Docker Compose setup
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
Create a <code>docker-compose.yml</code> file with the following
|
||||
configuration:
|
||||
</p>
|
||||
|
||||
<div className="relative my-6">
|
||||
<pre className="overflow-x-auto rounded-lg bg-gray-900 p-4 text-sm text-gray-100">
|
||||
<code>{dockerCompose}</code>
|
||||
</pre>
|
||||
<div className="absolute right-2 top-2">
|
||||
<CopyButton text={dockerCompose} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Then run:</p>
|
||||
|
||||
<div className="relative my-6">
|
||||
<pre className="overflow-x-auto rounded-lg bg-gray-900 p-4 text-sm text-gray-100">
|
||||
<code>docker compose up -d</code>
|
||||
</pre>
|
||||
<div className="absolute right-2 top-2">
|
||||
<CopyButton text="docker compose up -d" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Keep in mind that start up can take up to ~2 minutes.</p>
|
||||
|
||||
<h2 id="getting-started">Getting started</h2>
|
||||
|
||||
<p>After installation:</p>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
<strong>Launch and access Postgresus</strong>: Start
|
||||
Postgresus and navigate to <code>http://localhost:4005</code>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Create your first backup job</strong>: Click "New
|
||||
Backup" and configure your PostgreSQL database connection
|
||||
</li>
|
||||
<li>
|
||||
<strong>Configure schedule</strong>: Set up your backup
|
||||
schedule (hourly, daily, weekly or monthly)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Choose storage destination</strong>: Select where to
|
||||
store your backups (local, S3, Google Drive, etc.)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Set up notifications</strong>: Add notification
|
||||
channels (Slack, Telegram, Discord) to get alerts about backup
|
||||
status
|
||||
</li>
|
||||
<li>
|
||||
<strong>Start backing up</strong>: Save your configuration and
|
||||
watch your first backup run!
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="how-to-update">How to update Postgresus?</h2>
|
||||
|
||||
<p>
|
||||
To update Postgresus, you need to stop it, clean up Docker cache
|
||||
and restart the container.
|
||||
</p>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
Go to the directory where Postgresus is installed (usually{" "}
|
||||
<code>/opt/postgresus</code>)
|
||||
</li>
|
||||
<li>
|
||||
Stop the container: <code>docker compose stop</code>
|
||||
</li>
|
||||
<li>
|
||||
Clean up Docker cache: <code>docker system prune -a</code>
|
||||
</li>
|
||||
<li>
|
||||
Restart the container: <code>docker compose up -d</code>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
It will get the latest version of Postgresus from the Docker Hub
|
||||
(if you have not fixed the version in the{" "}
|
||||
<code>docker-compose.yml</code> file).
|
||||
</p>
|
||||
|
||||
<h2 id="troubleshooting">Troubleshooting</h2>
|
||||
|
||||
<h3 id="container-wont-start">Container won't start</h3>
|
||||
|
||||
<p>If the container fails to start, check the logs:</p>
|
||||
|
||||
<div className="relative my-6">
|
||||
<pre className="overflow-x-auto rounded-lg bg-gray-900 p-4 text-sm text-gray-100">
|
||||
<code>docker logs postgresus</code>
|
||||
</pre>
|
||||
<div className="absolute right-2 top-2">
|
||||
<CopyButton text="docker logs postgresus" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 id="port-already-in-use">Port already in use</h3>
|
||||
|
||||
<p>
|
||||
If port 4005 is already in use, you can change it in your
|
||||
docker-compose.yml:
|
||||
</p>
|
||||
|
||||
<div className="relative my-6">
|
||||
<pre className="overflow-x-auto rounded-lg bg-gray-900 p-4 text-sm text-gray-100">
|
||||
<code>
|
||||
ports:
|
||||
{"\n "}- "8080:4005" # Change 8080 to any
|
||||
available port
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<h3 id="permission-denied">Permission denied errors</h3>
|
||||
|
||||
<p>If you encounter permission issues with the data directory:</p>
|
||||
|
||||
<div className="relative my-6">
|
||||
<pre className="overflow-x-auto rounded-lg bg-gray-900 p-4 text-sm text-gray-100">
|
||||
<code>
|
||||
sudo chown -R $USER:$USER ./postgresus-data
|
||||
{"\n"}
|
||||
chmod -R 755 ./postgresus-data
|
||||
</code>
|
||||
</pre>
|
||||
<div className="absolute right-2 top-2">
|
||||
<CopyButton
|
||||
text={`sudo chown -R $USER:$USER ./postgresus-data\nchmod -R 755 ./postgresus-data`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Table of Contents */}
|
||||
<DocTableOfContentComponent />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
55
app/layout.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Jost } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Script from "next/script";
|
||||
|
||||
const jost = Jost({
|
||||
weight: ["400", "500", "600", "700", "800"],
|
||||
style: ["normal"],
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={jost.className}
|
||||
style={{ fontFamily: "Jost, sans-serif" }}
|
||||
>
|
||||
{children}
|
||||
<Script
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-GE01THYR9X"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<Script id="google-analytics" strategy="afterInteractive">
|
||||
{`
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-GE01THYR9X');
|
||||
`}
|
||||
</Script>
|
||||
<Script id="yandex-metrika" strategy="afterInteractive">
|
||||
{`
|
||||
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||
m[i].l=1*new Date();
|
||||
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
|
||||
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
|
||||
(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js?id=103482608', 'ym');
|
||||
ym(103482608, 'init', {
|
||||
ssr: true,
|
||||
clickmap: true,
|
||||
ecommerce: 'dataLayer',
|
||||
accurateTrackBounce: true,
|
||||
trackLinks: true,
|
||||
});
|
||||
`}
|
||||
</Script>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
112
app/not-found.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "404 - Page Not Found | Postgresus",
|
||||
description: "The page you're looking for doesn't exist.",
|
||||
robots: "noindex, nofollow",
|
||||
};
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
{/* Navbar */}
|
||||
<nav className="flex h-[60px] w-full justify-center border-b border-gray-200 bg-white sm:h-[70px] md:h-[80px]">
|
||||
<div className="flex min-w-0 grow items-center px-4 sm:px-6 md:px-10">
|
||||
<Link href="/" className="flex items-center">
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt="Postgresus logo"
|
||||
width={30}
|
||||
height={30}
|
||||
className="shrink-0 sm:h-[40px] sm:w-[40px] md:h-[50px] md:w-[50px]"
|
||||
priority
|
||||
/>
|
||||
|
||||
<div className="ml-2 select-none text-lg font-bold sm:ml-3 sm:text-xl md:ml-4 md:text-2xl">
|
||||
Postgresus
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className="ml-auto mr-4 hidden gap-3 sm:mr-6 md:mr-10 lg:flex lg:gap-5">
|
||||
<a className="hover:opacity-70" href="/docs">
|
||||
Docs
|
||||
</a>
|
||||
<a
|
||||
className="hover:opacity-70"
|
||||
href="https://t.me/postgresus_community"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Community
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a
|
||||
className="ml-auto lg:ml-0"
|
||||
href="https://github.com/RostislavDugin/postgresus"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="flex items-center rounded-lg border border-gray-200 bg-[#f5f7f9] px-2 py-1 hover:bg-gray-100 md:px-4 md:py-2">
|
||||
<Image
|
||||
src="/images/index/github.svg"
|
||||
className="mr-1 h-4 w-4 sm:mr-2 md:mr-3"
|
||||
alt="GitHub icon"
|
||||
width={16}
|
||||
height={16}
|
||||
priority
|
||||
/>
|
||||
<span className="text-sm sm:text-base">
|
||||
Star on GitHub
|
||||
<span className="hidden sm:inline">
|
||||
, it's really important ❤️
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* 404 Content */}
|
||||
<div className="flex grow flex-col items-center justify-center bg-linear-to-b from-white to-gray-50 px-6 py-12 text-center">
|
||||
<div className="mb-4">
|
||||
<h1 className="text-7xl font-bold text-blue-600 md:text-8xl">404</h1>
|
||||
</div>
|
||||
|
||||
<h2 className="mb-3 text-2xl font-bold text-gray-800 md:text-3xl">
|
||||
Page Not Found
|
||||
</h2>
|
||||
|
||||
<p className="mb-6 max-w-md text-base text-gray-600">
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<Link
|
||||
href="/"
|
||||
className="rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-blue-700 md:px-6 md:py-3 md:text-base"
|
||||
>
|
||||
Go to Homepage
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/installation"
|
||||
className="rounded-lg border-2 border-blue-600 bg-white px-5 py-2.5 text-sm font-semibold text-blue-600 transition-colors hover:bg-blue-50 md:px-6 md:py-3 md:text-base"
|
||||
>
|
||||
View Documentation
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-gray-200 bg-white py-8 text-center text-sm text-gray-600">
|
||||
<p>
|
||||
© {new Date().getFullYear()} Postgresus. Open source PostgreSQL backup
|
||||
tool.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
136
app/notifiers/page.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import DocsNavbarComponent from "../components/DocsNavbarComponent";
|
||||
import DocsSidebarComponent from "../components/DocsSidebarComponent";
|
||||
import DocTableOfContentComponent from "../components/DocTableOfContentComponent";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Notifiers - Postgresus Documentation",
|
||||
description:
|
||||
"List of supported notification channels for Postgresus backup alerts including Slack, Discord, Telegram, Microsoft Teams, Email and Webhooks.",
|
||||
keywords: [
|
||||
"Postgresus notifiers",
|
||||
"backup notifications",
|
||||
"Slack notifications",
|
||||
"Discord alerts",
|
||||
"Telegram notifications",
|
||||
"Teams notifications",
|
||||
"Email alerts",
|
||||
"Webhook notifications",
|
||||
],
|
||||
openGraph: {
|
||||
title: "Notifiers - Postgresus Documentation",
|
||||
description:
|
||||
"List of supported notification channels for Postgresus backup alerts including Slack, Discord, Telegram, Microsoft Teams, Email and Webhooks.",
|
||||
type: "article",
|
||||
url: "https://postgresus.com/notifiers",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
title: "Notifiers - Postgresus Documentation",
|
||||
description:
|
||||
"List of supported notification channels for Postgresus backup alerts including Slack, Discord, Telegram, Microsoft Teams, Email and Webhooks.",
|
||||
},
|
||||
alternates: {
|
||||
canonical: "https://postgresus.com/notifiers",
|
||||
},
|
||||
robots: "index, follow",
|
||||
};
|
||||
|
||||
export default function NotifiersPage() {
|
||||
return (
|
||||
<>
|
||||
{/* JSON-LD Structured Data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "TechArticle",
|
||||
headline: "Notifiers - Postgresus Documentation",
|
||||
description:
|
||||
"List of supported notification channels for Postgresus backup alerts including Slack, Discord, Telegram, Microsoft Teams, Email and Webhooks.",
|
||||
author: {
|
||||
"@type": "Organization",
|
||||
name: "Postgresus",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Postgresus",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: "https://postgresus.com/logo.svg",
|
||||
},
|
||||
},
|
||||
datePublished: "2025-01-01",
|
||||
dateModified: "2025-01-01",
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
<DocsNavbarComponent />
|
||||
|
||||
<div className="flex min-h-screen">
|
||||
{/* Sidebar */}
|
||||
<DocsSidebarComponent />
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 px-4 py-6 sm:px-6 sm:py-8 lg:px-12">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<article className="prose prose-blue max-w-none">
|
||||
<h1 id="notifiers">Notifiers</h1>
|
||||
|
||||
<p className="text-lg text-gray-700">
|
||||
Postgresus supports multiple notification channels to keep you
|
||||
informed about your PostgreSQL backup status. Get instant alerts
|
||||
when backups succeed, fail or encounter issues.
|
||||
</p>
|
||||
|
||||
<h2 id="supported-notifiers">Supported notifiers</h2>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<Link
|
||||
href="/notifiers/slack"
|
||||
className="font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
Slack
|
||||
</Link>{" "}
|
||||
- Send notifications to Slack channels via webhooks
|
||||
</li>
|
||||
<li>
|
||||
<strong>Discord</strong> - Post backup alerts to Discord
|
||||
channels
|
||||
</li>
|
||||
<li>
|
||||
<strong>Telegram</strong> - Receive notifications through
|
||||
Telegram bots
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/notifiers/teams"
|
||||
className="font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
Microsoft Teams
|
||||
</Link>{" "}
|
||||
- Notify your team via Microsoft Teams channels
|
||||
</li>
|
||||
<li>
|
||||
<strong>Email</strong> - Send email notifications for backup
|
||||
events
|
||||
</li>
|
||||
<li>
|
||||
<strong>Webhook</strong> - Custom webhook integration for any
|
||||
service
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Table of Contents */}
|
||||
<DocTableOfContentComponent />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
262
app/notifiers/slack/page.tsx
Normal file
@@ -0,0 +1,262 @@
|
||||
import type { Metadata } from "next";
|
||||
import DocsNavbarComponent from "../../components/DocsNavbarComponent";
|
||||
import DocsSidebarComponent from "../../components/DocsSidebarComponent";
|
||||
import DocTableOfContentComponent from "../../components/DocTableOfContentComponent";
|
||||
import Image from "next/image";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "How to configure Slack notifications for Postgresus | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to set up Slack notifications for PostgreSQL backup alerts with Postgresus. Learn how to create Slack webhook and configure notifications.",
|
||||
keywords: [
|
||||
"Postgresus",
|
||||
"Slack notifications",
|
||||
"PostgreSQL backup",
|
||||
"Slack webhook",
|
||||
"backup alerts",
|
||||
"database notifications",
|
||||
],
|
||||
openGraph: {
|
||||
title: "How to configure Slack notifications for Postgresus | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to set up Slack notifications for PostgreSQL backup alerts with Postgresus. Learn how to create Slack webhook and configure notifications.",
|
||||
type: "article",
|
||||
url: "https://postgresus.com/notifiers/slack",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
title: "How to configure Slack notifications for Postgresus | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to set up Slack notifications for PostgreSQL backup alerts with Postgresus. Learn how to create Slack webhook and configure notifications.",
|
||||
},
|
||||
alternates: {
|
||||
canonical: "https://postgresus.com/notifiers/slack",
|
||||
},
|
||||
robots: "index, follow",
|
||||
};
|
||||
|
||||
export default function SlackPage() {
|
||||
return (
|
||||
<>
|
||||
{/* JSON-LD Structured Data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "HowTo",
|
||||
name: "How to configure Slack notifications for Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to set up Slack notifications for PostgreSQL backup alerts with Postgresus",
|
||||
step: [
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Open Slack workspace",
|
||||
text: "Navigate to your Slack workspace where you want to receive notifications.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Access Slack apps",
|
||||
text: "Go to your Slack workspace and access the Apps section.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Create incoming webhook",
|
||||
text: "Search for and add the Incoming Webhooks app to your workspace.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Select channel",
|
||||
text: "Choose the channel where you want to receive backup notifications.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Copy webhook URL",
|
||||
text: "Copy the generated webhook URL from Slack.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Configure in Postgresus",
|
||||
text: "Paste the webhook URL into Postgresus notifier configuration.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Test the notification",
|
||||
text: "Test the notification to ensure it's working correctly.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
<DocsNavbarComponent />
|
||||
|
||||
<div className="flex min-h-screen">
|
||||
{/* Sidebar */}
|
||||
<DocsSidebarComponent />
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 px-4 py-6 sm:px-6 sm:py-8 lg:px-12">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<article className="prose prose-blue max-w-none">
|
||||
<h1 id="slack-notifications">Slack notifications</h1>
|
||||
|
||||
<p className="text-lg text-gray-700">
|
||||
Configure Slack to receive instant notifications about your
|
||||
PostgreSQL backup status. Get alerts for successful backups,
|
||||
failures and warnings directly in your Slack channels.
|
||||
</p>
|
||||
|
||||
<h2 id="setup-slack-webhook">Setup Slack webhook</h2>
|
||||
|
||||
<h3 id="open-slack-workspace">1. Open your Slack workspace</h3>
|
||||
|
||||
<p>
|
||||
Navigate to your Slack workspace where you want to receive
|
||||
backup notifications.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-slack/image-1.png"
|
||||
alt="Open Slack workspace"
|
||||
width={800}
|
||||
height={500}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="access-slack-apps">2. Access Slack apps</h3>
|
||||
|
||||
<p>
|
||||
Click on your workspace name in the top left, then select{" "}
|
||||
<strong>"Settings & administration"</strong> →{" "}
|
||||
<strong>"Manage apps"</strong>.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-slack/image-2.png"
|
||||
alt="Access Slack Apps"
|
||||
width={600}
|
||||
height={400}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="search-incoming-webhooks">
|
||||
3. Search for incoming webhooks
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
In the App Directory, search for{" "}
|
||||
<strong>"Incoming Webhooks"</strong> and click on it.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-slack/image-3.png"
|
||||
alt="Search for Incoming Webhooks"
|
||||
width={700}
|
||||
height={400}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="add-to-slack">4. Add to Slack</h3>
|
||||
|
||||
<p>
|
||||
Click the <strong>"Add to Slack"</strong> button to
|
||||
install the Incoming Webhooks app to your workspace.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-slack/image-4.png"
|
||||
alt="Add to Slack"
|
||||
width={700}
|
||||
height={400}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="select-channel">5. Select channel</h3>
|
||||
|
||||
<p>
|
||||
Choose the channel where you want to receive backup
|
||||
notifications, then click{" "}
|
||||
<strong>"Add Incoming Webhooks integration"</strong>.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-slack/image-5.png"
|
||||
alt="Select channel for notifications"
|
||||
width={600}
|
||||
height={400}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="copy-webhook-url">6. Copy webhook URL</h3>
|
||||
|
||||
<p>
|
||||
After creating the webhook, you'll see the{" "}
|
||||
<strong>Webhook URL</strong>. Copy this URL - you'll need
|
||||
it for Postgresus configuration.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-slack/image-6.png"
|
||||
alt="Copy webhook URL"
|
||||
width={700}
|
||||
height={500}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h2 id="configure-postgresus">Configure in Postgresus</h2>
|
||||
|
||||
<h3 id="add-slack-notifier">1. Add Slack notifier</h3>
|
||||
|
||||
<p>
|
||||
In Postgresus, navigate to the notifiers settings and add a new
|
||||
Slack notifier. Paste the webhook URL you copied from Slack.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-slack/image-7.png"
|
||||
alt="Configure Slack in Postgresus"
|
||||
width={600}
|
||||
height={400}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="test-notification">2. Test the notification</h3>
|
||||
|
||||
<p>
|
||||
After configuring the webhook, test the notification to ensure
|
||||
it's working correctly. You should receive a test message
|
||||
in your selected Slack channel.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
That's it! Your Slack workspace is now configured to
|
||||
receive PostgreSQL backup notifications from Postgresus.
|
||||
</p>
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="mt-12 border-t border-gray-200 pt-8">
|
||||
<a
|
||||
href="/notifiers"
|
||||
className="inline-flex items-center font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
← Back to notifiers
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Table of Contents */}
|
||||
<DocTableOfContentComponent />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
274
app/notifiers/teams/page.tsx
Normal file
@@ -0,0 +1,274 @@
|
||||
import type { Metadata } from "next";
|
||||
import DocsNavbarComponent from "../../components/DocsNavbarComponent";
|
||||
import DocsSidebarComponent from "../../components/DocsSidebarComponent";
|
||||
import DocTableOfContentComponent from "../../components/DocTableOfContentComponent";
|
||||
import Image from "next/image";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title:
|
||||
"How to configure Microsoft Teams notifications for Postgresus | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to set up Microsoft Teams notifications for PostgreSQL backup alerts with Postgresus. Learn how to create Teams webhook and configure notifications.",
|
||||
keywords: [
|
||||
"Postgresus",
|
||||
"Microsoft Teams notifications",
|
||||
"PostgreSQL backup",
|
||||
"Teams webhook",
|
||||
"backup alerts",
|
||||
"database notifications",
|
||||
],
|
||||
openGraph: {
|
||||
title:
|
||||
"How to configure Microsoft Teams notifications for Postgresus | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to set up Microsoft Teams notifications for PostgreSQL backup alerts with Postgresus. Learn how to create Teams webhook and configure notifications.",
|
||||
type: "article",
|
||||
url: "https://postgresus.com/notifiers/teams",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
title:
|
||||
"How to configure Microsoft Teams notifications for Postgresus | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to set up Microsoft Teams notifications for PostgreSQL backup alerts with Postgresus. Learn how to create Teams webhook and configure notifications.",
|
||||
},
|
||||
alternates: {
|
||||
canonical: "https://postgresus.com/notifiers/teams",
|
||||
},
|
||||
robots: "index, follow",
|
||||
};
|
||||
|
||||
export default function TeamsPage() {
|
||||
return (
|
||||
<>
|
||||
{/* JSON-LD Structured Data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "HowTo",
|
||||
name: "How to configure Microsoft Teams notifications for Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to set up Microsoft Teams notifications for PostgreSQL backup alerts with Postgresus",
|
||||
step: [
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Open Teams channel",
|
||||
text: "Navigate to the Microsoft Teams channel where you want to receive notifications.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Access workflows",
|
||||
text: "Open the Workflows feature in your Teams channel.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Create new workflow",
|
||||
text: "Create a new workflow for incoming webhooks.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Select webhook template",
|
||||
text: "Choose the incoming webhook template from available options.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Configure webhook",
|
||||
text: "Set up the webhook name and channel.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Copy webhook URL",
|
||||
text: "Copy the generated webhook URL from Teams.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Configure in Postgresus",
|
||||
text: "Paste the webhook URL into Postgresus notifier configuration.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
<DocsNavbarComponent />
|
||||
|
||||
<div className="flex min-h-screen">
|
||||
{/* Sidebar */}
|
||||
<DocsSidebarComponent />
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 px-4 py-6 sm:px-6 sm:py-8 lg:px-12">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<article className="prose prose-blue max-w-none">
|
||||
<h1 id="teams-notifications">Microsoft Teams notifications</h1>
|
||||
|
||||
<p className="text-lg text-gray-700">
|
||||
Configure Microsoft Teams to receive instant notifications about
|
||||
your PostgreSQL backup status. Get alerts for successful
|
||||
backups, failures and warnings directly in your Teams channels.
|
||||
</p>
|
||||
|
||||
<h2 id="setup-teams-webhook">Setup Teams webhook</h2>
|
||||
|
||||
<h3 id="open-teams-channel">1. Open your Teams channel</h3>
|
||||
|
||||
<p>
|
||||
Navigate to the Microsoft Teams channel where you want to
|
||||
receive backup notifications. Click on the three dots (
|
||||
<strong>•••</strong>) next to the channel name.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-teams/image-01.png"
|
||||
alt="Open Teams channel"
|
||||
width={800}
|
||||
height={500}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="access-workflows">2. Access workflows</h3>
|
||||
|
||||
<p>
|
||||
From the channel menu, select{" "}
|
||||
<strong>"Workflows"</strong> to open the Power
|
||||
Automate integration.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-teams/image-02.png"
|
||||
alt="Access Workflows"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="create-new-workflow">3. Create new workflow</h3>
|
||||
|
||||
<p>
|
||||
In the Workflows panel, click on{" "}
|
||||
<strong>"Create"</strong> or search for{" "}
|
||||
<strong>
|
||||
"Post to a channel when a webhook request is
|
||||
received"
|
||||
</strong>{" "}
|
||||
template.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-teams/image-03.png"
|
||||
alt="Create new workflow"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="select-webhook-template">4. Select webhook template</h3>
|
||||
|
||||
<p>
|
||||
Choose the{" "}
|
||||
<strong>
|
||||
"Post to a channel when a webhook request is
|
||||
received"
|
||||
</strong>{" "}
|
||||
template from the available options.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-teams/image-04.png"
|
||||
alt="Select webhook template"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="configure-webhook">5. Configure webhook</h3>
|
||||
|
||||
<p>
|
||||
Set up the webhook by providing a name (e.g.,{" "}
|
||||
<strong>"Postgresus Backup Notifications"</strong>)
|
||||
and confirm the channel where notifications will be posted.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-teams/image-05.png"
|
||||
alt="Configure webhook"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="copy-webhook-url">6. Copy webhook URL</h3>
|
||||
|
||||
<p>
|
||||
After creating the workflow, you'll see the{" "}
|
||||
<strong>HTTP POST URL</strong>. Copy this URL - you'll need
|
||||
it for Postgresus configuration.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-teams/image-06.png"
|
||||
alt="Copy webhook URL"
|
||||
width={500}
|
||||
height={500}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h2 id="configure-postgresus">Configure in Postgresus</h2>
|
||||
|
||||
<h3 id="add-teams-notifier">1. Add Teams notifier</h3>
|
||||
|
||||
<p>
|
||||
In Postgresus, navigate to the notifiers settings and add a new
|
||||
Microsoft Teams notifier. Paste the webhook URL you copied from
|
||||
Teams.
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/notifier-teams/image-07.png"
|
||||
alt="Configure Teams in Postgresus"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="test-notification">2. Test the notification</h3>
|
||||
|
||||
<p>
|
||||
After configuring the webhook, test the notification to ensure
|
||||
it's working correctly. You should receive a test message
|
||||
in your selected Teams channel.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
That's it! Your Microsoft Teams channel is now configured
|
||||
to receive PostgreSQL backup notifications from Postgresus.
|
||||
</p>
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="mt-12 border-t border-gray-200 pt-8">
|
||||
<a
|
||||
href="/notifiers"
|
||||
className="inline-flex items-center font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
← Back to notifiers
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Table of Contents */}
|
||||
<DocTableOfContentComponent />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
1081
app/page.tsx
Normal file
154
app/password/page.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import type { Metadata } from "next";
|
||||
import { CopyButton } from "../components/CopyButton";
|
||||
import DocsNavbarComponent from "../components/DocsNavbarComponent";
|
||||
import DocsSidebarComponent from "../components/DocsSidebarComponent";
|
||||
import DocTableOfContentComponent from "../components/DocTableOfContentComponent";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Reset password - Postgresus Documentation",
|
||||
description:
|
||||
"Learn how to reset user passwords in Postgresus using the built-in command-line tool. Quick and secure password recovery for your PostgreSQL backup system.",
|
||||
keywords: [
|
||||
"Postgresus password reset",
|
||||
"reset user password",
|
||||
"PostgreSQL backup password",
|
||||
"Docker password recovery",
|
||||
"password recovery",
|
||||
"Postgresus authentication",
|
||||
],
|
||||
openGraph: {
|
||||
title: "Reset Password - Postgresus Documentation",
|
||||
description:
|
||||
"Learn how to reset user passwords in Postgresus using the built-in command-line tool. Quick and secure password recovery for your PostgreSQL backup system.",
|
||||
type: "article",
|
||||
url: "https://postgresus.com/password",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
title: "Reset Password - Postgresus Documentation",
|
||||
description:
|
||||
"Learn how to reset user passwords in Postgresus using the built-in command-line tool. Quick and secure password recovery for your PostgreSQL backup system.",
|
||||
},
|
||||
alternates: {
|
||||
canonical: "https://postgresus.com/password",
|
||||
},
|
||||
robots: "index, follow",
|
||||
};
|
||||
|
||||
export default function PasswordResetPage() {
|
||||
const resetPasswordCommand = `docker exec -it postgresus ./main --new-password="YourNewSecurePassword123" --email="admin"`;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* JSON-LD Structured Data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "TechArticle",
|
||||
headline: "Reset Password - Postgresus Documentation",
|
||||
description:
|
||||
"Learn how to reset user passwords in Postgresus using the built-in command-line tool.",
|
||||
author: {
|
||||
"@type": "Organization",
|
||||
name: "Postgresus",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Postgresus",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: "https://postgresus.com/logo.svg",
|
||||
},
|
||||
},
|
||||
datePublished: "2025-01-01",
|
||||
dateModified: "2025-01-01",
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "HowTo",
|
||||
name: "How to reset Postgresus user password",
|
||||
description:
|
||||
"Step-by-step guide to reset user passwords in Postgresus",
|
||||
step: [
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Run password reset command",
|
||||
text: "Execute the docker exec command with your new password and user email.",
|
||||
itemListElement: [
|
||||
{
|
||||
"@type": "HowToDirection",
|
||||
text: "Use docker exec to run the password reset command inside the Postgresus container",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Verify password change",
|
||||
text: "Log in to Postgresus with your new password to confirm the change was successful.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
<DocsNavbarComponent />
|
||||
|
||||
<div className="flex min-h-screen">
|
||||
{/* Sidebar */}
|
||||
<DocsSidebarComponent />
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 px-4 py-6 sm:px-6 sm:py-8 lg:px-12">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<article className="prose prose-blue max-w-none">
|
||||
<h1 id="reset-password">Reset user password</h1>
|
||||
|
||||
<h2 id="reset-password-command">Reset password command</h2>
|
||||
|
||||
<p>
|
||||
To reset a user's password, use the following command on
|
||||
the server where Postgresus is running:
|
||||
</p>
|
||||
|
||||
<div className="relative my-6">
|
||||
<pre className="overflow-x-auto rounded-lg bg-gray-900 p-4 text-sm text-gray-100">
|
||||
<code>{resetPasswordCommand}</code>
|
||||
</pre>
|
||||
<div className="absolute right-2 top-2">
|
||||
<CopyButton text={resetPasswordCommand} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="parameters">Parameters</h2>
|
||||
|
||||
<p>The command accepts the following parameters:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>--new-password</strong>: The new password. Make sure
|
||||
it's secure and contains a mix of letters, numbers and
|
||||
special characters.
|
||||
</li>
|
||||
<li>
|
||||
<strong>--email</strong>: The email address of the user whose
|
||||
password you want to reset (e.g., <code>admin</code>,{" "}
|
||||
<code>user@example.com</code>).
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Table of Contents */}
|
||||
<DocTableOfContentComponent />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
11
app/robots.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { MetadataRoute } from "next";
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: "*",
|
||||
allow: "/",
|
||||
},
|
||||
sitemap: "https://postgresus.com/sitemap.xml",
|
||||
};
|
||||
}
|
||||
63
app/sitemap.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { MetadataRoute } from "next";
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const baseUrl = "https://postgresus.com";
|
||||
const currentDate = new Date().toISOString();
|
||||
|
||||
return [
|
||||
{
|
||||
url: baseUrl,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "weekly",
|
||||
priority: 1.0,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/installation`,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "weekly",
|
||||
priority: 0.9,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/password`,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/storages`,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/storages/google-drive`,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/storages/cloudflare-r2`,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/notifiers`,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/notifiers/slack`,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/notifiers/teams`,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.7,
|
||||
},
|
||||
];
|
||||
}
|
||||
220
app/storages/cloudflare-r2/page.tsx
Normal file
@@ -0,0 +1,220 @@
|
||||
import type { Metadata } from "next";
|
||||
import DocsNavbarComponent from "../../components/DocsNavbarComponent";
|
||||
import DocsSidebarComponent from "../../components/DocsSidebarComponent";
|
||||
import DocTableOfContentComponent from "../../components/DocTableOfContentComponent";
|
||||
import Image from "next/image";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "How to use Postgresus with Cloudflare R2 | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to configure Cloudflare R2 storage for PostgreSQL backups with Postgresus. Learn how to set up S3-compatible storage with R2.",
|
||||
keywords: [
|
||||
"Postgresus",
|
||||
"Cloudflare R2",
|
||||
"PostgreSQL backup",
|
||||
"S3 storage",
|
||||
"cloud storage",
|
||||
"database backup",
|
||||
],
|
||||
openGraph: {
|
||||
title: "How to use Postgresus with Cloudflare R2 | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to configure Cloudflare R2 storage for PostgreSQL backups with Postgresus. Learn how to set up S3-compatible storage with R2.",
|
||||
type: "article",
|
||||
url: "https://postgresus.com/storages/cloudflare-r2",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
title: "How to use Postgresus with Cloudflare R2 | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to configure Cloudflare R2 storage for PostgreSQL backups with Postgresus. Learn how to set up S3-compatible storage with R2.",
|
||||
},
|
||||
alternates: {
|
||||
canonical: "https://postgresus.com/storages/cloudflare-r2",
|
||||
},
|
||||
robots: "index, follow",
|
||||
};
|
||||
|
||||
export default function CloudflareR2Page() {
|
||||
return (
|
||||
<>
|
||||
{/* JSON-LD Structured Data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "HowTo",
|
||||
name: "How to use Postgresus with Cloudflare R2",
|
||||
description:
|
||||
"Step-by-step guide to configure Cloudflare R2 storage for PostgreSQL backups with Postgresus",
|
||||
step: [
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Fill your bucket name",
|
||||
text: "Enter your R2 bucket name in the storage configuration.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Set the region",
|
||||
text: 'In region field, fill "auto"',
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Generate an access key ID & secret access key",
|
||||
text: "In the Cloudflare dashboard, go to R2 → API → Manage API Tokens. Create the token and grant it the permissions you need.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Find your account ID",
|
||||
text: "On any R2 page in the dashboard, you'll see your Account ID near the top.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Construct the S3 endpoint",
|
||||
text: "Replace <ACCOUNT_ID> with the value from your dashboard in the format: https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
<DocsNavbarComponent />
|
||||
|
||||
<div className="flex min-h-screen">
|
||||
{/* Sidebar */}
|
||||
<DocsSidebarComponent />
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 px-4 py-6 sm:px-6 sm:py-8 lg:px-12">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<article className="prose prose-blue max-w-none">
|
||||
<h1 id="cloudflare-r2">Cloudflare R2 storage</h1>
|
||||
|
||||
<p className="text-lg text-gray-700">
|
||||
To use Cloudflare R2 as an S3-compatible storage for your
|
||||
PostgreSQL backups, you'll need to configure your R2 bucket
|
||||
credentials and endpoint.
|
||||
</p>
|
||||
|
||||
<h2 id="configuration-steps">Configuration steps</h2>
|
||||
|
||||
<h3 id="fill-bucket-name">1. Fill your bucket name</h3>
|
||||
|
||||
<p>Enter your R2 bucket name in the storage configuration:</p>
|
||||
|
||||
<Image
|
||||
src="/images/cloudflare-r2-storage/image-1.webp"
|
||||
alt="Fill your bucket name in Cloudflare R2"
|
||||
width={500}
|
||||
height={300}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="set-region">2. Set the region</h3>
|
||||
|
||||
<p>
|
||||
In the region field, fill <code>"auto"</code>
|
||||
</p>
|
||||
|
||||
<h3 id="generate-access-key">
|
||||
3. Generate an Access Key ID & Secret Access Key
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
In the Cloudflare dashboard, go to{" "}
|
||||
<strong>R2 → API → Manage API Tokens</strong>. Create a new
|
||||
token and grant it the permissions you need (e.g.{" "}
|
||||
<strong>"Object Read & Write"</strong>).
|
||||
</p>
|
||||
|
||||
<p>When the token is created, you'll see:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Access Key ID</strong> (the token's ID)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Secret Access Key</strong> (the SHA-256 hash of the
|
||||
token value)
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Copy both values to Postgresus:</p>
|
||||
|
||||
<Image
|
||||
src="/images/cloudflare-r2-storage/image-2.gif"
|
||||
alt="Generate Access Key ID and Secret Access Key"
|
||||
width={1000}
|
||||
height={600}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="find-account-id">4. Find your account ID</h3>
|
||||
|
||||
<p>
|
||||
On any R2 page in the dashboard, you'll see your Account ID
|
||||
near the top (or in your account settings):
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/cloudflare-r2-storage/image-3.webp"
|
||||
alt="Find your Account ID in Cloudflare dashboard"
|
||||
width={600}
|
||||
height={600}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="construct-endpoint">5. Construct the S3 endpoint</h3>
|
||||
|
||||
<p>Use the following format for your S3 endpoint:</p>
|
||||
|
||||
<pre>
|
||||
<code>https://<ACCOUNT_ID>.r2.cloudflarestorage.com</code>
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Replace <code><ACCOUNT_ID></code> with the value from your
|
||||
dashboard and enter it in Postgresus.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
That's it! Your configuration should now look like this:
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/cloudflare-r2-storage/image-4.png"
|
||||
alt="Configuration complete"
|
||||
width={500}
|
||||
height={600}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<p>
|
||||
Your Postgresus is now ready to use Cloudflare R2 as storage for
|
||||
your PostgreSQL backups.
|
||||
</p>
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="mt-12 border-t border-gray-200 pt-8">
|
||||
<a
|
||||
href="/storages"
|
||||
className="inline-flex items-center font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
← Back to storages
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Table of Contents */}
|
||||
<DocTableOfContentComponent />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
313
app/storages/google-drive/page.tsx
Normal file
@@ -0,0 +1,313 @@
|
||||
import type { Metadata } from "next";
|
||||
import DocsNavbarComponent from "../../components/DocsNavbarComponent";
|
||||
import DocsSidebarComponent from "../../components/DocsSidebarComponent";
|
||||
import DocTableOfContentComponent from "../../components/DocTableOfContentComponent";
|
||||
import Image from "next/image";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "How to connect Google Drive to Postgresus | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to configure Google Drive storage for PostgreSQL backups with Postgresus. Learn how to set up Google Cloud project and OAuth.",
|
||||
keywords: [
|
||||
"Postgresus",
|
||||
"Google Drive",
|
||||
"PostgreSQL backup",
|
||||
"Google Cloud",
|
||||
"OAuth",
|
||||
"cloud storage",
|
||||
"database backup",
|
||||
],
|
||||
openGraph: {
|
||||
title: "How to connect Google Drive to Postgresus | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to configure Google Drive storage for PostgreSQL backups with Postgresus. Learn how to set up Google Cloud project and OAuth.",
|
||||
type: "article",
|
||||
url: "https://postgresus.com/storages/google-drive",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
title: "How to connect Google Drive to Postgresus | Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to configure Google Drive storage for PostgreSQL backups with Postgresus. Learn how to set up Google Cloud project and OAuth.",
|
||||
},
|
||||
alternates: {
|
||||
canonical: "https://postgresus.com/storages/google-drive",
|
||||
},
|
||||
robots: "index, follow",
|
||||
};
|
||||
|
||||
export default function GoogleDrivePage() {
|
||||
return (
|
||||
<>
|
||||
{/* JSON-LD Structured Data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "HowTo",
|
||||
name: "How to connect Google Drive to Postgresus",
|
||||
description:
|
||||
"Step-by-step guide to configure Google Drive storage for PostgreSQL backups with Postgresus",
|
||||
step: [
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Create new project",
|
||||
text: "Go to Google Cloud Console and create a new project.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Enable Google Drive API",
|
||||
text: "Go to API & Services tab, then to API library and enable Google Drive API.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Configure consent screen",
|
||||
text: "Go to Credentials → Create credentials → Configure consent screen and fill required data.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Create OAuth client ID",
|
||||
text: "Go to Credentials → Create credentials → OAuth client ID.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Configure application settings",
|
||||
text: "Set application type to Web application and configure authorized origins and redirect URIs.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Add scope",
|
||||
text: 'Go to Data Access and add scope "/auth/drive.file".',
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Publish the app",
|
||||
text: "Go to Audience and publish the app.",
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
name: "Sign in via Google account",
|
||||
text: "Fill credentials data and sign in with your Google Account.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
<DocsNavbarComponent />
|
||||
|
||||
<div className="flex min-h-screen">
|
||||
{/* Sidebar */}
|
||||
<DocsSidebarComponent />
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 px-4 py-6 sm:px-6 sm:py-8 lg:px-12">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<article className="prose prose-blue max-w-none">
|
||||
<h1 id="google-drive">Google Drive storage</h1>
|
||||
|
||||
<p className="text-lg text-gray-700">
|
||||
To keep your backups in Google Drive, you need to create a
|
||||
Google Cloud project to access the Google Drive API, then sign
|
||||
in via your Google Account.
|
||||
</p>
|
||||
|
||||
<h2 id="create-google-cloud-project">
|
||||
Create Google Cloud project
|
||||
</h2>
|
||||
|
||||
<h3 id="create-new-project">1. Create new project</h3>
|
||||
|
||||
<p>
|
||||
Go to{" "}
|
||||
<a
|
||||
href="https://console.cloud.google.com/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
https://console.cloud.google.com/
|
||||
</a>{" "}
|
||||
and choose <strong>"new project"</strong> (top left).
|
||||
</p>
|
||||
|
||||
<h3 id="enable-google-drive-api">2. Enable Google Drive API</h3>
|
||||
|
||||
<p>
|
||||
Go to <strong>"API & Services"</strong> tab, then
|
||||
to <strong>"API library"</strong>. Choose{" "}
|
||||
<strong>Google Drive API</strong> and enable it:
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/google-drive-storage/image-1.webp"
|
||||
alt="Enable Google Drive API"
|
||||
width={500}
|
||||
height={300}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="configure-consent-screen">3. Configure consent screen</h3>
|
||||
|
||||
<p>
|
||||
Go to <strong>"Credentials"</strong> →{" "}
|
||||
<strong>"Create credentials"</strong> →{" "}
|
||||
<strong>"Configure consent screen"</strong> and fill
|
||||
any data there:
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/google-drive-storage/image-2.webp"
|
||||
alt="Configure consent screen"
|
||||
width={500}
|
||||
height={300}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="create-oauth-client-id">4. Create OAuth client ID</h3>
|
||||
|
||||
<p>
|
||||
Go to <strong>"Credentials"</strong> →{" "}
|
||||
<strong>"Create credentials"</strong> →{" "}
|
||||
<strong>"OAuth client ID"</strong>:
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/google-drive-storage/image-3.webp"
|
||||
alt="Create OAuth client ID"
|
||||
width={500}
|
||||
height={300}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="configure-application-settings">
|
||||
5. Configure application settings
|
||||
</h3>
|
||||
|
||||
<p>Fill the following data:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Application type:</strong> Web application
|
||||
</li>
|
||||
<li>
|
||||
<strong>Authorized JavaScript origins:</strong>{" "}
|
||||
<code>https://postgresus.com</code>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Authorized redirect URIs:</strong>{" "}
|
||||
<code>https://postgresus.com/storages/google-oauth</code>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Then copy the credentials:</p>
|
||||
|
||||
<Image
|
||||
src="/images/google-drive-storage/image-4.webp"
|
||||
alt="Configure application settings - part 1"
|
||||
width={1000}
|
||||
height={600}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<Image
|
||||
src="/images/google-drive-storage/image-5.png"
|
||||
alt="Configure application settings - part 2"
|
||||
width={450}
|
||||
height={600}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="add-scope">6. Add scope</h3>
|
||||
|
||||
<p>
|
||||
Go to <strong>"Data Access"</strong> and add scope{" "}
|
||||
<code>"/auth/drive.file"</code>:
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/google-drive-storage/image-6.png"
|
||||
alt="Add scope"
|
||||
width={600}
|
||||
height={600}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="publish-app">7. Publish the app</h3>
|
||||
|
||||
<p>
|
||||
Go to <strong>"Audience"</strong> and publish the app:
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src="/images/google-drive-storage/image-7.png"
|
||||
alt="Publish the app"
|
||||
width={600}
|
||||
height={600}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h2 id="sign-in-google-account">Sign in via Google account</h2>
|
||||
|
||||
<h3 id="fill-credentials">1. Fill credentials data</h3>
|
||||
|
||||
<p>Fill the credentials from the previous steps in Postgresus:</p>
|
||||
|
||||
<Image
|
||||
src="/images/google-drive-storage/image-8.png"
|
||||
alt="Fill credentials data"
|
||||
width={600}
|
||||
height={600}
|
||||
className="my-6 rounded-lg border border-gray-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<h3 id="choose-account">2. Choose your account</h3>
|
||||
|
||||
<p>Choose your Google account to sign in.</p>
|
||||
|
||||
<h3 id="handle-security-warning">3. Handle security warning</h3>
|
||||
|
||||
<p>
|
||||
If you see a warning, click{" "}
|
||||
<strong>"Advanced"</strong> (left bottom corner) and
|
||||
choose <strong>"Proceed anyway"</strong>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Note:</strong> This warning appears because your app is
|
||||
not yet verified by Google. It's safe to proceed for your
|
||||
own application.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
That's it! Your Google Drive is now connected to Postgresus
|
||||
and ready to store your PostgreSQL backups.
|
||||
</p>
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="mt-12 border-t border-gray-200 pt-8">
|
||||
<a
|
||||
href="/storages"
|
||||
className="inline-flex items-center font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
← Back to storages
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Table of Contents */}
|
||||
<DocTableOfContentComponent />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
54
app/storages/google-oauth/page.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
robots: "noindex, nofollow",
|
||||
};
|
||||
|
||||
export default function GoogleOAuthPage() {
|
||||
useEffect(() => {
|
||||
// Get current URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Extract state and code from current URL
|
||||
const state = urlParams.get("state");
|
||||
const code = urlParams.get("code");
|
||||
|
||||
if (state && code) {
|
||||
try {
|
||||
// Parse the StorageOauthDto from the state parameter
|
||||
const oauthDto = JSON.parse(decodeURIComponent(state));
|
||||
|
||||
// Update the authCode field with the received code
|
||||
oauthDto.authCode = code;
|
||||
|
||||
// Construct the redirect URL with the updated DTO
|
||||
const redirectUrl = `${
|
||||
oauthDto.redirectUrl
|
||||
}?oauthDto=${encodeURIComponent(JSON.stringify(oauthDto))}`;
|
||||
|
||||
// Redirect to the constructed URL
|
||||
window.location.href = redirectUrl;
|
||||
} catch (error) {
|
||||
console.error("Error parsing state parameter:", error);
|
||||
}
|
||||
} else {
|
||||
console.error("Missing state or code parameter");
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
padding: "50px",
|
||||
fontFamily: "Arial, sans-serif",
|
||||
}}
|
||||
>
|
||||
<h2>Processing OAuth...</h2>
|
||||
<p>Please wait while we redirect you back to the application.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
131
app/storages/page.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import DocsNavbarComponent from "../components/DocsNavbarComponent";
|
||||
import DocsSidebarComponent from "../components/DocsSidebarComponent";
|
||||
import DocTableOfContentComponent from "../components/DocTableOfContentComponent";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Storages - Postgresus Documentation",
|
||||
description:
|
||||
"List of supported storage destinations for Postgresus backups including local storage, S3, Cloudflare R2, Google Drive, NAS and Dropbox.",
|
||||
keywords: [
|
||||
"Postgresus storages",
|
||||
"backup storage",
|
||||
"S3 storage",
|
||||
"Google Drive backup",
|
||||
"Cloudflare R2",
|
||||
"NAS backup",
|
||||
"Dropbox backup",
|
||||
"local storage",
|
||||
],
|
||||
openGraph: {
|
||||
title: "Storages - Postgresus Documentation",
|
||||
description:
|
||||
"List of supported storage destinations for Postgresus backups including local storage, S3, Cloudflare R2, Google Drive, NAS and Dropbox.",
|
||||
type: "article",
|
||||
url: "https://postgresus.com/storages",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
title: "Storages - Postgresus Documentation",
|
||||
description:
|
||||
"List of supported storage destinations for Postgresus backups including local storage, S3, Cloudflare R2, Google Drive, NAS and Dropbox.",
|
||||
},
|
||||
alternates: {
|
||||
canonical: "https://postgresus.com/storages",
|
||||
},
|
||||
robots: "index, follow",
|
||||
};
|
||||
|
||||
export default function StoragesPage() {
|
||||
return (
|
||||
<>
|
||||
{/* JSON-LD Structured Data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "TechArticle",
|
||||
headline: "Storages - Postgresus Documentation",
|
||||
description:
|
||||
"List of supported storage destinations for Postgresus backups including local storage, S3, Cloudflare R2, Google Drive, NAS and Dropbox.",
|
||||
author: {
|
||||
"@type": "Organization",
|
||||
name: "Postgresus",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Postgresus",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: "https://postgresus.com/logo.svg",
|
||||
},
|
||||
},
|
||||
datePublished: "2025-01-01",
|
||||
dateModified: "2025-01-01",
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
<DocsNavbarComponent />
|
||||
|
||||
<div className="flex min-h-screen">
|
||||
{/* Sidebar */}
|
||||
<DocsSidebarComponent />
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 px-4 py-6 sm:px-6 sm:py-8 lg:px-12">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<article className="prose prose-blue max-w-none">
|
||||
<h1 id="storages">Storages</h1>
|
||||
|
||||
<p className="text-lg text-gray-700">
|
||||
Postgresus supports multiple storage destinations for your
|
||||
PostgreSQL backups. Choose where to store your backup files
|
||||
based on your infrastructure and requirements.
|
||||
</p>
|
||||
|
||||
<h2 id="supported-storages">Supported storages</h2>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Local Storage</strong> - Store backups directly on
|
||||
your server or VPS
|
||||
</li>
|
||||
<li>
|
||||
<strong>S3</strong> - Amazon S3 and S3-compatible storage
|
||||
services
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/storages/cloudflare-r2"
|
||||
className="font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
Cloudflare R2
|
||||
</Link>{" "}
|
||||
- S3-compatible object storage from Cloudflare
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/storages/google-drive"
|
||||
className="font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
Google Drive
|
||||
</Link>{" "}
|
||||
- Cloud storage from Google
|
||||
</li>
|
||||
<li>
|
||||
<strong>NAS</strong> - Network-attached storage devices
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Table of Contents */}
|
||||
<DocTableOfContentComponent />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
383
blog.html
@@ -1,383 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- Minified TailwindCSS -->
|
||||
<link rel="stylesheet" href="styles.min.css" />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>Blog | Postgresus</title>
|
||||
<meta name="title" content="Blog | Postgresus" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Learn how to configure Postgresus with different storage providers and notification systems. Step-by-step guides for PostgreSQL backup management."
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="Postgresus blog, PostgreSQL backup guides, cloud storage setup, Slack notifications, Google Drive, Cloudflare R2"
|
||||
/>
|
||||
<link rel="canonical" href="https://postgresus.com/blog" />
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="favicon.svg" />
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-GE01THYR9X"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-GE01THYR9X');
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!------------------ NAVBAR ------------------------->
|
||||
<nav
|
||||
class="fixed left-0 right-0 top-0 z-50 flex h-[60px] justify-center bg-white px-4 sm:h-[70px] md:h-[80px]"
|
||||
>
|
||||
<div
|
||||
class="flex w-[320px] min-w-0 max-w-[320px] grow items-center border-b sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<a href="/" class="flex items-center">
|
||||
<img
|
||||
src="logo.svg"
|
||||
loading="eager"
|
||||
alt="Postgresus logo"
|
||||
width="35"
|
||||
height="35"
|
||||
class="flex-shrink-0 sm:h-[40px] sm:w-[40px] md:h-[50px] md:w-[50px]"
|
||||
/>
|
||||
|
||||
<div class="ml-2 text-lg font-bold sm:ml-3 sm:text-xl md:ml-4 md:text-2xl">
|
||||
Postgresus
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div
|
||||
class="ml-auto mr-4 hidden gap-3 text-base sm:mr-6 md:mr-10 lg:flex lg:gap-5 lg:text-lg"
|
||||
>
|
||||
<a class="hover:opacity-70" href="/#guide">Guide</a>
|
||||
<a class="hover:opacity-70" href="/#installation">Installation</a>
|
||||
<a class="hover:opacity-70" href="/#features">Features</a>
|
||||
<a class="hover:opacity-70" href="https://t.me/postgresus_community" target="_blank"
|
||||
>Community</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<a
|
||||
class="ml-auto lg:ml-0"
|
||||
href="https://github.com/RostislavDugin/postgresus"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div
|
||||
class="flex items-center rounded-lg border bg-[#f5f7f9] px-2 py-1 hover:bg-gray-100 md:px-4 md:py-2"
|
||||
>
|
||||
<img
|
||||
src="images/index/github.svg"
|
||||
class="mr-1 sm:mr-2 md:mr-3"
|
||||
alt="GitHub icon"
|
||||
width="16"
|
||||
height="16"
|
||||
style="width: 16px; height: 16px"
|
||||
loading="eager"
|
||||
/>
|
||||
<span class="text-sm sm:text-base">Star on GitHub</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<!------------------ END OF NAVBAR ------------------>
|
||||
|
||||
<!------------------ MAIN CONTENT ------------------------->
|
||||
<div class="flex justify-center py-[50px] pt-[110px] md:py-[100px] md:pt-[180px]">
|
||||
<main
|
||||
class="w-[320px] max-w-[320px] sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<div class="max-w-[800px]">
|
||||
<h1 class="mb-6 text-2xl font-bold sm:text-4xl">Blog</h1>
|
||||
|
||||
<p class="mb-12 max-w-[600px] text-lg text-gray-700">
|
||||
Learn how to configure Postgresus with different storage providers and notification
|
||||
systems. Step-by-step guides to help you get the most out of your PostgreSQL backup
|
||||
solution.
|
||||
</p>
|
||||
|
||||
<!-- Blog Posts -->
|
||||
<div class="space-y-8">
|
||||
<!-- Post 1: Cloudflare R2 -->
|
||||
<article
|
||||
class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md"
|
||||
>
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-orange-100">
|
||||
<svg class="h-6 w-6 text-orange-600" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h2 class="mb-3 text-xl font-semibold">
|
||||
<a href="cloudflare-r2-storage" class="text-gray-900 hover:text-blue-600">
|
||||
How to use Postgresus with Cloudflare R2
|
||||
</a>
|
||||
</h2>
|
||||
<p class="mb-4 text-gray-600">
|
||||
Learn how to configure Cloudflare R2 as your S3-compatible storage for
|
||||
PostgreSQL backups. Step-by-step guide to set up credentials, endpoints, and
|
||||
bucket configuration.
|
||||
</p>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="rounded-full bg-orange-100 px-3 py-1 text-sm text-orange-800"
|
||||
>Storage</span
|
||||
>
|
||||
<span class="rounded-full bg-blue-100 px-3 py-1 text-sm text-blue-800"
|
||||
>Cloud</span
|
||||
>
|
||||
</div>
|
||||
<a
|
||||
href="cloudflare-r2-storage"
|
||||
class="font-medium text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
Read more →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Post 2: Google Drive -->
|
||||
<article
|
||||
class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md"
|
||||
>
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-green-100">
|
||||
<svg class="h-6 w-6 text-green-600" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h2 class="mb-3 text-xl font-semibold">
|
||||
<a href="google-drive-storage" class="text-gray-900 hover:text-blue-600">
|
||||
How to connect Google Drive to Postgresus
|
||||
</a>
|
||||
</h2>
|
||||
<p class="mb-4 text-gray-600">
|
||||
Complete guide to setting up Google Drive as your backup storage. Learn how to
|
||||
create a Google Cloud project, configure OAuth, and connect your Drive account.
|
||||
</p>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="rounded-full bg-green-100 px-3 py-1 text-sm text-green-800"
|
||||
>Storage</span
|
||||
>
|
||||
<span class="rounded-full bg-yellow-100 px-3 py-1 text-sm text-yellow-800"
|
||||
>OAuth</span
|
||||
>
|
||||
</div>
|
||||
<a
|
||||
href="google-drive-storage"
|
||||
class="font-medium text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
Read more →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Post 3: Slack Notifications -->
|
||||
<article
|
||||
class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md"
|
||||
>
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-purple-100">
|
||||
<svg class="h-6 w-6 text-purple-600" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h2 class="mb-3 text-xl font-semibold">
|
||||
<a href="notifier-slack" class="text-gray-900 hover:text-blue-600">
|
||||
How to send notifications from Postgresus to Slack
|
||||
</a>
|
||||
</h2>
|
||||
<p class="mb-4 text-gray-600">
|
||||
Set up Slack notifications for your PostgreSQL backups. Learn how to create a
|
||||
Slack bot, configure permissions, and get channel IDs for backup notifications.
|
||||
</p>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="rounded-full bg-purple-100 px-3 py-1 text-sm text-purple-800"
|
||||
>Notifications</span
|
||||
>
|
||||
<span class="rounded-full bg-indigo-100 px-3 py-1 text-sm text-indigo-800"
|
||||
>Bot</span
|
||||
>
|
||||
</div>
|
||||
<a href="notifier-slack" class="font-medium text-blue-600 hover:text-blue-800">
|
||||
Read more →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Post 4: Teams Notifications -->
|
||||
<article
|
||||
class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md"
|
||||
>
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-blue-100">
|
||||
<svg class="h-6 w-6 text-blue-600" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h2 class="mb-3 text-xl font-semibold">
|
||||
<a href="notifier-teams" class="text-gray-900 hover:text-blue-600">
|
||||
How to send notifications from Postgresus to Microsoft Teams
|
||||
</a>
|
||||
</h2>
|
||||
<p class="mb-4 text-gray-600">
|
||||
Configure Microsoft Teams notifications for your PostgreSQL backups using Power
|
||||
Automate. Step-by-step guide to create an HTTP-triggered flow and connect it to
|
||||
your Teams channel.
|
||||
</p>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="rounded-full bg-blue-100 px-3 py-1 text-sm text-blue-800"
|
||||
>Notifications</span
|
||||
>
|
||||
<span class="rounded-full bg-cyan-100 px-3 py-1 text-sm text-cyan-800"
|
||||
>Power Automate</span
|
||||
>
|
||||
</div>
|
||||
<a href="notifier-teams" class="font-medium text-blue-600 hover:text-blue-800">
|
||||
Read more →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="mt-12 border-t border-gray-200 pt-8">
|
||||
<a
|
||||
href="/"
|
||||
class="inline-flex items-center font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
← Back to main page
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<!------------------ END OF MAIN CONTENT ------------------>
|
||||
|
||||
<!------------------ FOOTER SECTION -------------------------------->
|
||||
<div id="footer" class="flex justify-center bg-blue-600 py-[50px] md:py-[50px]">
|
||||
<div
|
||||
class="w-[320px] max-w-[320px] sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="flex flex-wrap justify-center gap-6 text-white">
|
||||
<a
|
||||
href="https://github.com/RostislavDugin/postgresus"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://t.me/postgresus_community"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
Community
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://rostislav-dugin.com"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>Developer
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-center text-sm text-white">© 2025 Postgresus. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!------------------ END OF FOOTER SECTION ------------------------->
|
||||
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script type="text/javascript">
|
||||
(function (m, e, t, r, i, k, a) {
|
||||
m[i] =
|
||||
m[i] ||
|
||||
function () {
|
||||
(m[i].a = m[i].a || []).push(arguments);
|
||||
};
|
||||
m[i].l = 1 * new Date();
|
||||
for (var j = 0; j < document.scripts.length; j++) {
|
||||
if (document.scripts[j].src === r) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
((k = e.createElement(t)),
|
||||
(a = e.getElementsByTagName(t)[0]),
|
||||
(k.async = 1),
|
||||
(k.src = r),
|
||||
a.parentNode.insertBefore(k, a));
|
||||
})(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js?id=103482608', 'ym');
|
||||
|
||||
ym(103482608, 'init', {
|
||||
ssr: true,
|
||||
clickmap: true,
|
||||
ecommerce: 'dataLayer',
|
||||
accurateTrackBounce: true,
|
||||
trackLinks: true,
|
||||
});
|
||||
</script>
|
||||
<noscript
|
||||
><div>
|
||||
<img
|
||||
src="https://mc.yandex.ru/watch/103482608"
|
||||
style="position: absolute; left: -9999px"
|
||||
alt=""
|
||||
/></div
|
||||
></noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,319 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Minified TailwindCSS -->
|
||||
<link rel="stylesheet" href="styles.min.css" />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>How to use Postgresus with Cloudflare R2 | Postgresus</title>
|
||||
<meta name="title" content="How to use Postgresus with Cloudflare R2 | Postgresus" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Step-by-step guide to configure Cloudflare R2 storage for PostgreSQL backups with Postgresus. Learn how to set up S3-compatible storage with R2."
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="Postgresus, Cloudflare R2, PostgreSQL backup, S3 storage, cloud storage, database backup"
|
||||
/>
|
||||
<link rel="canonical" href="https://postgresus.com/cloudflare-r2-storage" />
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="favicon.svg" />
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-GE01THYR9X"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-GE01THYR9X');
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!------------------ NAVBAR ------------------------->
|
||||
<nav
|
||||
class="fixed left-0 right-0 top-0 z-50 flex h-[60px] justify-center bg-white px-4 sm:h-[70px] md:h-[80px]"
|
||||
>
|
||||
<div
|
||||
class="flex w-[320px] min-w-0 max-w-[320px] grow items-center border-b sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<a href="/" class="flex items-center">
|
||||
<img
|
||||
src="logo.svg"
|
||||
loading="eager"
|
||||
alt="Postgresus logo"
|
||||
width="35"
|
||||
height="35"
|
||||
class="flex-shrink-0 sm:h-[40px] sm:w-[40px] md:h-[50px] md:w-[50px]"
|
||||
/>
|
||||
|
||||
<div class="ml-2 text-lg font-bold sm:ml-3 sm:text-xl md:ml-4 md:text-2xl">
|
||||
Postgresus
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<!------------------ END OF NAVBAR ------------------>
|
||||
|
||||
<!------------------ MAIN CONTENT ------------------------->
|
||||
<div class="flex justify-center py-[50px] pt-[110px] md:py-[100px] md:pt-[180px]">
|
||||
<main
|
||||
class="w-[320px] max-w-[320px] sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<div class="max-w-[800px]">
|
||||
<h1 class="mb-6 text-2xl font-bold sm:text-4xl">
|
||||
How to use Postgresus with Cloudflare R2?
|
||||
</h1>
|
||||
|
||||
<p class="mb-8 max-w-[500px] text-lg text-gray-700">
|
||||
To obtain the credentials and endpoint for using your R2 bucket as an S3-compatible
|
||||
source, you'll need to:
|
||||
</p>
|
||||
|
||||
<div class="space-y-8">
|
||||
<!-- Step 1 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h2 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>1</span
|
||||
>
|
||||
Fill your bucket name
|
||||
</h2>
|
||||
|
||||
<p class="mb-2 text-gray-700">
|
||||
Enter your R2 bucket name in the storage configuration.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src="images/cloudflare-r2-storage/image-1.webp"
|
||||
alt="Step 1"
|
||||
class="mb-8 max-w-[500px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<!-- Step 2 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h2 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>2</span
|
||||
>
|
||||
Set the region
|
||||
</h2>
|
||||
|
||||
<p class="mb-2 text-gray-700">
|
||||
In region field, fill
|
||||
<code class="rounded bg-gray-100 px-2 py-1 text-sm">"auto"</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Step 3 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h2 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>3</span
|
||||
>
|
||||
Generate an Access Key ID & Secret Access Key
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<p class="text-gray-700">
|
||||
In the Cloudflare dashboard, go to <strong>R2 → API → Manage API Tokens</strong>.
|
||||
Create the token.
|
||||
</p>
|
||||
<p class="text-gray-700">
|
||||
Grant it the permissions you need (e.g. <strong>"Object Read & Write"</strong>).
|
||||
</p>
|
||||
|
||||
<div class="rounded-lg bg-blue-50 p-4">
|
||||
<p class="mb-2 text-gray-700">When the token is created you'll see:</p>
|
||||
<ul class="list-inside list-disc space-y-1 text-gray-700">
|
||||
<li><strong>Access Key ID</strong> (the token's ID)</li>
|
||||
<li>
|
||||
<strong>Secret Access Key</strong> (the SHA-256 hash of the token value)
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-2 font-semibold text-blue-600">Copy both now to Postgresus.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src="images/cloudflare-r2-storage/image-2.gif"
|
||||
alt="Step 2"
|
||||
class="mb-8 max-w-[1000px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<!-- Step 4 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h2 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>4</span
|
||||
>
|
||||
Find your Account ID
|
||||
</h2>
|
||||
<p class="text-gray-700">
|
||||
On any R2 page in the dashboard, you'll see your Account ID near the top (or in your
|
||||
account settings).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src="images/cloudflare-r2-storage/image-3.webp"
|
||||
alt="Step 3"
|
||||
class="mb-8 max-w-[1000px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<!-- Step 5 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h2 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>5</span
|
||||
>
|
||||
Construct the S3 endpoint
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="rounded-lg bg-gray-100 p-4">
|
||||
<code class="text-sm">https://<ACCOUNT_ID>.r2.cloudflarestorage.com</code>
|
||||
</div>
|
||||
<p class="text-gray-700">
|
||||
Replace
|
||||
<code class="rounded bg-gray-100 px-2 py-1 text-sm"><ACCOUNT_ID></code> with
|
||||
the value from your dashboard.
|
||||
</p>
|
||||
<p class="text-gray-700">Fill it to Postgresus as well.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Final Step -->
|
||||
<div class="max-w-[500px] rounded-lg border border-green-200 bg-green-50 p-6">
|
||||
<h2 class="mb-3 text-xl font-semibold text-green-800">🎉 Configuration Complete!</h2>
|
||||
<p class="text-green-700">
|
||||
Now you have all fields filled and your Postgresus is ready to use Cloudflare R2 as
|
||||
storage for your PostgreSQL backups.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src="images/cloudflare-r2-storage/image-4.png"
|
||||
alt="Step 4"
|
||||
class="mb-8 max-w-[1000px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="mt-12 border-t border-gray-200 pt-8">
|
||||
<a
|
||||
href="/"
|
||||
class="inline-flex items-center font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
← Back to main page
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<!------------------ END OF MAIN CONTENT ------------------>
|
||||
|
||||
<!------------------ FOOTER SECTION -------------------------------->
|
||||
<div id="footer" class="flex justify-center bg-blue-600 py-[50px] md:py-[50px]">
|
||||
<div
|
||||
class="w-[320px] max-w-[320px] sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="flex flex-wrap justify-center gap-6 text-white">
|
||||
<a
|
||||
href="https://github.com/RostislavDugin/postgresus"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://t.me/postgresus_community"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
Community
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://rostislav-dugin.com"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
Developer
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-center text-sm text-white">© 2025 Postgresus. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!------------------ END OF FOOTER SECTION ------------------------->
|
||||
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script type="text/javascript">
|
||||
(function (m, e, t, r, i, k, a) {
|
||||
m[i] =
|
||||
m[i] ||
|
||||
function () {
|
||||
(m[i].a = m[i].a || []).push(arguments);
|
||||
};
|
||||
m[i].l = 1 * new Date();
|
||||
for (var j = 0; j < document.scripts.length; j++) {
|
||||
if (document.scripts[j].src === r) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
((k = e.createElement(t)),
|
||||
(a = e.getElementsByTagName(t)[0]),
|
||||
(k.async = 1),
|
||||
(k.src = r),
|
||||
a.parentNode.insertBefore(k, a));
|
||||
})(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js?id=103482608', 'ym');
|
||||
|
||||
ym(103482608, 'init', {
|
||||
ssr: true,
|
||||
clickmap: true,
|
||||
ecommerce: 'dataLayer',
|
||||
accurateTrackBounce: true,
|
||||
trackLinks: true,
|
||||
});
|
||||
</script>
|
||||
<noscript
|
||||
><div>
|
||||
<img
|
||||
src="https://mc.yandex.ru/watch/103482608"
|
||||
style="position: absolute; left: -9999px"
|
||||
alt=""
|
||||
/></div
|
||||
></noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
</body>
|
||||
</html>
|
||||
23
eslint.config.mjs
Normal file
@@ -0,0 +1,23 @@
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import nextTs from "eslint-config-next/typescript";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
...nextTs,
|
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
]),
|
||||
{
|
||||
rules: {
|
||||
"@next/next/no-img-element": "off",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export default eslintConfig;
|
||||
@@ -1,461 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Minified TailwindCSS -->
|
||||
<link rel="stylesheet" href="styles.min.css" />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>How to connect Google Drive to Postgresus | Postgresus</title>
|
||||
<meta name="title" content="How to connect Google Drive to Postgresus | Postgresus" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Step-by-step guide to configure Google Drive storage for PostgreSQL backups with Postgresus. Learn how to set up Google Cloud project and OAuth."
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="Postgresus, Google Drive, PostgreSQL backup, Google Cloud, OAuth, cloud storage, database backup"
|
||||
/>
|
||||
<link rel="canonical" href="https://postgresus.com/google-drive-storage" />
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="favicon.svg" />
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-GE01THYR9X"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-GE01THYR9X');
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!------------------ NAVBAR ------------------------->
|
||||
<nav
|
||||
class="fixed left-0 right-0 top-0 z-50 flex h-[60px] justify-center bg-white px-4 sm:h-[70px] md:h-[80px]"
|
||||
>
|
||||
<div
|
||||
class="flex w-[320px] min-w-0 max-w-[320px] grow items-center border-b sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<a href="/" class="flex items-center">
|
||||
<img
|
||||
src="logo.svg"
|
||||
loading="eager"
|
||||
alt="Postgresus logo"
|
||||
width="35"
|
||||
height="35"
|
||||
class="flex-shrink-0 sm:h-[40px] sm:w-[40px] md:h-[50px] md:w-[50px]"
|
||||
/>
|
||||
|
||||
<div class="ml-2 text-lg font-bold sm:ml-3 sm:text-xl md:ml-4 md:text-2xl">
|
||||
Postgresus
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<!------------------ END OF NAVBAR ------------------>
|
||||
|
||||
<!------------------ MAIN CONTENT ------------------------->
|
||||
<div class="flex justify-center py-[50px] pt-[110px] md:py-[100px] md:pt-[180px]">
|
||||
<main
|
||||
class="w-[320px] max-w-[320px] sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<div class="max-w-[800px]">
|
||||
<h1 class="mb-6 text-2xl font-bold sm:text-4xl">
|
||||
How to connect Google Drive to Postgresus?
|
||||
</h1>
|
||||
|
||||
<p class="mb-8 max-w-[500px] text-lg text-gray-700">
|
||||
To keep your backups in Google Drive you need:
|
||||
</p>
|
||||
|
||||
<div class="mb-8 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="rounded-lg bg-blue-50 p-4">
|
||||
<h3 class="mb-2 font-semibold text-blue-800">Step 1</h3>
|
||||
<p class="text-blue-700">
|
||||
Create Google Cloud project so you can access Google Drive API
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg bg-blue-50 p-4">
|
||||
<h3 class="mb-2 font-semibold text-blue-800">Step 2</h3>
|
||||
<p class="text-blue-700">Sign in via your Google Account</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8">
|
||||
<!-- How to create Google Cloud project -->
|
||||
<div class="pl-6">
|
||||
<h2 class="mb-4 text-2xl font-semibold">How to create Google Cloud project?</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Step 1 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>1</span
|
||||
>
|
||||
Create new project
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
<p class="text-gray-700">
|
||||
Go to
|
||||
<a
|
||||
href="https://console.cloud.google.com/"
|
||||
target="_blank"
|
||||
class="text-blue-600 underline hover:text-blue-800"
|
||||
>https://console.cloud.google.com/</a
|
||||
>
|
||||
</p>
|
||||
<p class="text-gray-700">Choose <strong>"new project"</strong> (top left)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>2</span
|
||||
>
|
||||
Enable Google Drive API
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
<p class="text-gray-700">
|
||||
Go to <strong>"API & Services"</strong> tab, then to
|
||||
<strong>"API library"</strong>
|
||||
</p>
|
||||
<p class="pb-10 text-gray-700">
|
||||
Choose <strong>Google Drive API</strong> and enable it:
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src="images/google-drive-storage/image-1.webp"
|
||||
alt="Step 2"
|
||||
class="mb-8 max-w-[500px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Step 3 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>3</span
|
||||
>
|
||||
Configure consent screen
|
||||
</h3>
|
||||
<p class="pb-10 text-gray-700">
|
||||
Go to <strong>"Credentials"</strong> → <strong>"Create credentials"</strong> →
|
||||
<strong>"Configure consent screen"</strong> and fill any data there:
|
||||
</p>
|
||||
|
||||
<img
|
||||
src="images/google-drive-storage/image-2.webp"
|
||||
alt="Step 3"
|
||||
class="mb-8 max-w-[500px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Step 4 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>4</span
|
||||
>
|
||||
Create OAuth client ID
|
||||
</h3>
|
||||
<p class="pb-10 text-gray-700">
|
||||
Go to <strong>"Credentials"</strong> → <strong>"Create credentials"</strong> →
|
||||
<strong>"OAuth client ID"</strong>:
|
||||
</p>
|
||||
|
||||
<img
|
||||
src="images/google-drive-storage/image-3.webp"
|
||||
alt="Step 4"
|
||||
class="mb-8 max-w-[500px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Step 5 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>5</span
|
||||
>
|
||||
Configure application settings
|
||||
</h3>
|
||||
<p class="mb-4 text-gray-700">Fill the following data:</p>
|
||||
<div class="space-y-2">
|
||||
<div class="rounded-lg bg-gray-100 p-3">
|
||||
<p class="text-sm"><strong>Application type:</strong> Web application</p>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-100 p-3">
|
||||
<p class="text-sm">
|
||||
<strong>Authorized JavaScript origins:</strong>
|
||||
<code class="rounded bg-gray-200 px-2 py-1 text-sm"
|
||||
>https://postgresus.com</code
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-100 p-3">
|
||||
<p class="text-sm">
|
||||
<strong>Authorized redirect URIs:</strong>
|
||||
<code class="rounded bg-gray-200 px-2 py-1 text-sm"
|
||||
>https://postgresus.com/storages/google-oauth</code
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-gray-700">And copy credentials</div>
|
||||
</div>
|
||||
|
||||
<div class="pb-10"></div>
|
||||
|
||||
<img
|
||||
src="images/google-drive-storage/image-4.webp"
|
||||
alt="Step 5 - 1"
|
||||
class="mb-8 max-w-[1000px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<img
|
||||
src="images/google-drive-storage/image-5.png"
|
||||
alt="Step 5 - 2"
|
||||
class="mb-8 max-w-[1000px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Step 6 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>6</span
|
||||
>
|
||||
Add scope
|
||||
</h3>
|
||||
|
||||
<p class="pb-5 text-gray-700">
|
||||
Go to <strong>"Data Access"</strong> and add scope
|
||||
<code class="rounded bg-gray-100 px-2 py-1 text-sm">"/auth/drive.file"</code>
|
||||
</p>
|
||||
|
||||
<img
|
||||
src="images/google-drive-storage/image-6.png"
|
||||
alt="Step 6"
|
||||
class="mb-8 max-w-[1000px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Step 7 -->
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-sm text-white"
|
||||
>7</span
|
||||
>
|
||||
Publish the app
|
||||
</h3>
|
||||
<p class="pb-10 text-gray-700">
|
||||
Go to <strong>"Audience"</strong> and publish an app
|
||||
</p>
|
||||
|
||||
<img
|
||||
src="images/google-drive-storage/image-7.png"
|
||||
alt="Step 7"
|
||||
class="mb-8 max-w-[1000px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sign in via Google Account -->
|
||||
<div class="pl-6">
|
||||
<h2 class="mb-4 text-2xl font-semibold">Sign in via your Google Account</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Step 1 -->
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>1</span
|
||||
>
|
||||
Fill credentials data
|
||||
</h3>
|
||||
|
||||
<p class="pb-10 text-gray-700">Fill data from the previous step:</p>
|
||||
|
||||
<img
|
||||
src="images/google-drive-storage/image-8.png"
|
||||
alt="Step 1"
|
||||
class="mb-8 max-w-[1000px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Step 2 -->
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>2</span
|
||||
>
|
||||
Choose your account
|
||||
</h3>
|
||||
<p class="text-gray-700">Choose your account to sign in</p>
|
||||
</div>
|
||||
|
||||
<!-- Step 3 -->
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>3</span
|
||||
>
|
||||
Handle security warning
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
<p class="text-gray-700">
|
||||
If you see a warning, click <strong>"Advanced"</strong> (left bottom corner)
|
||||
and choose <strong>"Proceed anyway"</strong>
|
||||
</p>
|
||||
<div class="rounded-lg bg-yellow-50 p-4">
|
||||
<p class="text-sm text-yellow-800">
|
||||
⚠️ This warning appears because your app is not yet verified by Google. It's
|
||||
safe to proceed for your own application.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Final Step -->
|
||||
<div class="max-w-[500px] rounded-lg border border-green-200 bg-green-50 p-6">
|
||||
<h2 class="mb-3 text-xl font-semibold text-green-800">🎉 Setup Complete!</h2>
|
||||
<p class="text-green-700">
|
||||
Your Google Drive is now connected to Postgresus and ready to store your PostgreSQL
|
||||
backups securely in the cloud.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="mt-12 border-t border-gray-200 pt-8">
|
||||
<a
|
||||
href="/"
|
||||
class="inline-flex items-center font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
← Back to main page
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<!------------------ END OF MAIN CONTENT ------------------>
|
||||
|
||||
<!------------------ FOOTER SECTION -------------------------------->
|
||||
<div id="footer" class="flex justify-center bg-blue-600 py-[50px] md:py-[50px]">
|
||||
<div
|
||||
class="w-[320px] max-w-[320px] sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="flex flex-wrap justify-center gap-6 text-white">
|
||||
<a
|
||||
href="https://github.com/RostislavDugin/postgresus"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://t.me/postgresus_community"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
Community
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://rostislav-dugin.com"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
Developer
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-center text-sm text-white">© 2025 Postgresus. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!------------------ END OF FOOTER SECTION ------------------------->
|
||||
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script type="text/javascript">
|
||||
(function (m, e, t, r, i, k, a) {
|
||||
m[i] =
|
||||
m[i] ||
|
||||
function () {
|
||||
(m[i].a = m[i].a || []).push(arguments);
|
||||
};
|
||||
m[i].l = 1 * new Date();
|
||||
for (var j = 0; j < document.scripts.length; j++) {
|
||||
if (document.scripts[j].src === r) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
((k = e.createElement(t)),
|
||||
(a = e.getElementsByTagName(t)[0]),
|
||||
(k.async = 1),
|
||||
(k.src = r),
|
||||
a.parentNode.insertBefore(k, a));
|
||||
})(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js?id=103482608', 'ym');
|
||||
|
||||
ym(103482608, 'init', {
|
||||
ssr: true,
|
||||
clickmap: true,
|
||||
ecommerce: 'dataLayer',
|
||||
accurateTrackBounce: true,
|
||||
trackLinks: true,
|
||||
});
|
||||
</script>
|
||||
<noscript
|
||||
><div>
|
||||
<img
|
||||
src="https://mc.yandex.ru/watch/103482608"
|
||||
style="position: absolute; left: -9999px"
|
||||
alt=""
|
||||
/></div
|
||||
></noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
</body>
|
||||
</html>
|
||||
35
index.css
@@ -1,35 +0,0 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Jost', sans-serif;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font-family: inherit;
|
||||
}
|
||||
1131
index.html
38
next.config.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
async redirects() {
|
||||
return [
|
||||
// Old storage URLs to new structure
|
||||
{
|
||||
source: "/cloudflare-r2-storage",
|
||||
destination: "/storages/cloudflare-r2",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/google-drive-storage",
|
||||
destination: "/storages/google-drive",
|
||||
permanent: true,
|
||||
},
|
||||
// Old notifier URLs to new structure
|
||||
{
|
||||
source: "/notifier-slack",
|
||||
destination: "/notifiers/slack",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/notifier-teams",
|
||||
destination: "/notifiers/teams",
|
||||
permanent: true,
|
||||
},
|
||||
// Blog removed permanently
|
||||
{
|
||||
source: "/blog",
|
||||
destination: "/gone",
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -1,404 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Minified TailwindCSS -->
|
||||
<link rel="stylesheet" href="styles.min.css" />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>How to send notifications from Postgresus to Slack | Postgresus</title>
|
||||
<meta name="title" content="How to send notifications from Postgresus to Slack | Postgresus" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Step-by-step guide to configure Slack notifications for PostgreSQL backups with Postgresus. Learn how to create Slack bot and get chat ID."
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="Postgresus, Slack notifications, PostgreSQL backup, Slack bot, chat notifications, database backup"
|
||||
/>
|
||||
<link rel="canonical" href="https://postgresus.com/notifier-slack" />
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="favicon.svg" />
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-GE01THYR9X"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-GE01THYR9X');
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!------------------ NAVBAR ------------------------->
|
||||
<nav
|
||||
class="fixed left-0 right-0 top-0 z-50 flex h-[60px] justify-center bg-white px-4 sm:h-[70px] md:h-[80px]"
|
||||
>
|
||||
<div
|
||||
class="flex w-[320px] min-w-0 max-w-[320px] grow items-center border-b sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<a href="/" class="flex items-center">
|
||||
<img
|
||||
src="logo.svg"
|
||||
loading="eager"
|
||||
alt="Postgresus logo"
|
||||
width="35"
|
||||
height="35"
|
||||
class="flex-shrink-0 sm:h-[40px] sm:w-[40px] md:h-[50px] md:w-[50px]"
|
||||
/>
|
||||
|
||||
<div class="ml-2 text-lg font-bold sm:ml-3 sm:text-xl md:ml-4 md:text-2xl">
|
||||
Postgresus
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<!------------------ END OF NAVBAR ------------------>
|
||||
|
||||
<!------------------ MAIN CONTENT ------------------------->
|
||||
<div class="flex justify-center py-[50px] pt-[110px] md:py-[100px] md:pt-[180px]">
|
||||
<main
|
||||
class="w-[320px] max-w-[320px] sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<div class="max-w-[800px]">
|
||||
<h1 class="mb-6 max-w-[500px] text-2xl font-bold sm:text-4xl">
|
||||
How to send notifications from Postgresus to Slack?
|
||||
</h1>
|
||||
|
||||
<p class="mb-8 max-w-[500px] text-lg text-gray-700">
|
||||
To send notifications to Slack you need to follow 2 steps:
|
||||
</p>
|
||||
|
||||
<div class="mb-8 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="rounded-lg bg-blue-50 p-4">
|
||||
<h3 class="mb-2 font-semibold text-blue-800">Step 1</h3>
|
||||
<p class="text-blue-700">Create Slack bot</p>
|
||||
</div>
|
||||
<div class="rounded-lg bg-blue-50 p-4">
|
||||
<h3 class="mb-2 font-semibold text-blue-800">Step 2</h3>
|
||||
<p class="text-blue-700">Get chat ID (channel, group or user)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8">
|
||||
<!-- How to create Slack bot -->
|
||||
<div class="pl-6">
|
||||
<h2 class="mb-4 text-2xl font-semibold">How to create Slack bot?</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>1</span
|
||||
>
|
||||
Create the app
|
||||
</h3>
|
||||
|
||||
<div class="space-y-2">
|
||||
<p class="text-gray-700">
|
||||
Go to
|
||||
<a
|
||||
href="https://api.slack.com/apps"
|
||||
target="_blank"
|
||||
class="text-blue-600 underline hover:text-blue-800"
|
||||
>https://api.slack.com/apps</a
|
||||
>
|
||||
</p>
|
||||
|
||||
<p class="text-gray-700">
|
||||
Choose <strong>"create an app"</strong> →
|
||||
<strong>"create an app from scratch"</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg bg-orange-50 p-4">
|
||||
<div class="mb-2 font-semibold text-orange-800">After bot creation:</div>
|
||||
</div>
|
||||
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>2</span
|
||||
>
|
||||
Configure OAuth & Permissions
|
||||
</h3>
|
||||
<p class="mb-2 text-gray-700">Go to <strong>"OAuth & Permissions"</strong> tab</p>
|
||||
|
||||
<img
|
||||
src="images/notifier-slack/image-1.png"
|
||||
alt="Step 1"
|
||||
class="mb-8 mt-5 max-w-[1000px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>3</span
|
||||
>
|
||||
Add bot scopes
|
||||
</h3>
|
||||
|
||||
<p class="mb-2 text-gray-700">Add to bot scopes:</p>
|
||||
|
||||
<ul class="ml-4 list-inside list-disc space-y-1 text-gray-700">
|
||||
<li><code class="rounded bg-gray-100 px-2 py-1 text-sm">im:write</code></li>
|
||||
<li><code class="rounded bg-gray-100 px-2 py-1 text-sm">chat:write</code></li>
|
||||
<li>
|
||||
<code class="rounded bg-gray-100 px-2 py-1 text-sm">channels:history</code>
|
||||
</li>
|
||||
<li>
|
||||
<code class="rounded bg-gray-100 px-2 py-1 text-sm">channels:join</code>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<img
|
||||
src="images/notifier-slack/image-2.png"
|
||||
alt="Step 1"
|
||||
class="mb-8 mt-5 max-w-[500px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>4</span
|
||||
>
|
||||
Install to workspace
|
||||
</h3>
|
||||
|
||||
<p class="text-gray-700">
|
||||
Click <strong>"Install to {YOUR_WORKSPACE}"</strong> and complete steps
|
||||
</p>
|
||||
|
||||
<img
|
||||
src="images/notifier-slack/image-3.png"
|
||||
alt="Step 1"
|
||||
class="mb-8 mt-5 max-w-[500px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How to get chat ID -->
|
||||
<div class="pl-6">
|
||||
<h2 class="mb-4 text-2xl font-semibold">How to get chat ID?</h2>
|
||||
|
||||
<p class="mb-4 text-gray-700">You can send messages to:</p>
|
||||
<ul class="mb-6 ml-4 list-inside list-disc space-y-1 text-gray-700">
|
||||
<li>Public channel</li>
|
||||
<li>Private channel</li>
|
||||
<li>Group</li>
|
||||
<li>Direct messages</li>
|
||||
</ul>
|
||||
|
||||
<div class="mb-6 rounded-lg border border-yellow-200 bg-yellow-50 p-4">
|
||||
<div class="mb-2 font-semibold text-yellow-800">⚠️ Warning</div>
|
||||
<ul class="list-inside list-disc space-y-1 text-yellow-700">
|
||||
<li>
|
||||
For sending messages to <strong>private channel</strong> bot should be added via
|
||||
<code class="rounded bg-yellow-100 px-2 py-1 text-sm">@invite</code> command
|
||||
</li>
|
||||
<li>
|
||||
For sending messages to <strong>group</strong>, group should be created with bot
|
||||
initially
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="border-l-4 border-indigo-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-indigo-500 text-sm text-white"
|
||||
>1</span
|
||||
>
|
||||
Get channel or group ID
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
<p class="text-gray-700">Navigate to chat and take ID from URL</p>
|
||||
|
||||
<div class="rounded-lg bg-gray-100 p-3">
|
||||
<p class="text-sm text-gray-600">
|
||||
💡 <strong>Tip:</strong> ID usually starts from
|
||||
<code class="rounded bg-gray-200 px-2 py-1 text-sm">"C"</code> letter
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src="images/notifier-slack/image-4.png"
|
||||
alt="Step 1"
|
||||
class="mb-8 mt-5 max-w-[500px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-l-4 border-indigo-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-indigo-500 text-sm text-white"
|
||||
>2</span
|
||||
>
|
||||
Get user ID
|
||||
</h3>
|
||||
|
||||
<div class="space-y-2">
|
||||
<p class="text-gray-700">
|
||||
Go to <strong>"Profile"</strong> and click
|
||||
<strong>"copy link to profile"</strong>
|
||||
</p>
|
||||
|
||||
<img
|
||||
src="images/notifier-slack/image-5.png"
|
||||
alt="Step 1"
|
||||
class="mb-8 mt-5 max-w-[500px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<p class="text-gray-700">Then extract end part of URL, this is your user ID</p>
|
||||
|
||||
<img
|
||||
src="images/notifier-slack/image-6.png"
|
||||
alt="Step 1"
|
||||
class="mb-8 mt-5 max-w-[500px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Final Step -->
|
||||
<div class="max-w-[500px] rounded-lg border border-green-200 bg-green-50 p-6">
|
||||
<h2 class="mb-3 text-xl font-semibold text-green-800">🎉 Setup Complete!</h2>
|
||||
<p class="text-green-700">
|
||||
Copy both token and ID to Postgresus, notification should appear and you're ready to
|
||||
receive backup notifications in Slack!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src="images/notifier-slack/image-7.png"
|
||||
alt="Step 1"
|
||||
class="mb-8 mt-5 max-w-[1000px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="mt-12 border-t border-gray-200 pt-8">
|
||||
<a
|
||||
href="/"
|
||||
class="inline-flex items-center font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
← Back to main page
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<!------------------ END OF MAIN CONTENT ------------------>
|
||||
|
||||
<!------------------ FOOTER SECTION -------------------------------->
|
||||
<div id="footer" class="flex justify-center bg-blue-600 py-[50px] md:py-[50px]">
|
||||
<div
|
||||
class="w-[320px] max-w-[320px] sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="flex flex-wrap justify-center gap-6 text-white">
|
||||
<a
|
||||
href="https://github.com/RostislavDugin/postgresus"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://t.me/postgresus_community"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
Community
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://rostislav-dugin.com"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
Developer
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-center text-sm text-white">© 2025 Postgresus. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!------------------ END OF FOOTER SECTION ------------------------->
|
||||
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script type="text/javascript">
|
||||
(function (m, e, t, r, i, k, a) {
|
||||
m[i] =
|
||||
m[i] ||
|
||||
function () {
|
||||
(m[i].a = m[i].a || []).push(arguments);
|
||||
};
|
||||
m[i].l = 1 * new Date();
|
||||
for (var j = 0; j < document.scripts.length; j++) {
|
||||
if (document.scripts[j].src === r) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
((k = e.createElement(t)),
|
||||
(a = e.getElementsByTagName(t)[0]),
|
||||
(k.async = 1),
|
||||
(k.src = r),
|
||||
a.parentNode.insertBefore(k, a));
|
||||
})(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js?id=103482608', 'ym');
|
||||
|
||||
ym(103482608, 'init', {
|
||||
ssr: true,
|
||||
clickmap: true,
|
||||
ecommerce: 'dataLayer',
|
||||
accurateTrackBounce: true,
|
||||
trackLinks: true,
|
||||
});
|
||||
</script>
|
||||
<noscript
|
||||
><div>
|
||||
<img
|
||||
src="https://mc.yandex.ru/watch/103482608"
|
||||
style="position: absolute; left: -9999px"
|
||||
alt=""
|
||||
/></div
|
||||
></noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,364 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Minified TailwindCSS -->
|
||||
<link rel="stylesheet" href="styles.min.css" />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>How to send notifications from Postgresus to Teams | Postgresus</title>
|
||||
<meta name="title" content="How to send notifications from Postgresus to Teams | Postgresus" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Step-by-step guide to configure Microsoft Teams notifications for PostgreSQL backups with Postgresus using Power Automate. Create an HTTP-triggered flow in Teams and copy its URL."
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="Postgresus, Microsoft Teams notifications, Power Automate, HTTP request trigger, PostgreSQL backup, Teams channel notifications, database backup"
|
||||
/>
|
||||
<link rel="canonical" href="https://postgresus.com/notifier-teams" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://postgresus.com/notifier-teams" />
|
||||
<meta property="og:title" content="How to send notifications from Postgresus to Teams | Postgresus" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Step-by-step guide to configure Microsoft Teams notifications for PostgreSQL backups with Postgresus using Power Automate. Create an HTTP-triggered flow in Teams and copy its URL."
|
||||
/>
|
||||
<meta property="og:image" content="https://postgresus.com/logo.svg" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary" />
|
||||
<meta property="twitter:url" content="https://postgresus.com/notifier-teams" />
|
||||
<meta property="twitter:title" content="How to send notifications from Postgresus to Teams | Postgresus" />
|
||||
<meta
|
||||
property="twitter:description"
|
||||
content="Step-by-step guide to configure Microsoft Teams notifications for PostgreSQL backups with Postgresus using Power Automate. Create an HTTP-triggered flow in Teams and copy its URL."
|
||||
/>
|
||||
<meta property="twitter:image" content="https://postgresus.com/logo.svg" />
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="favicon.svg" />
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-GE01THYR9X"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-GE01THYR9X');
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!------------------ NAVBAR ------------------------->
|
||||
<nav
|
||||
class="fixed left-0 right-0 top-0 z-50 flex h-[60px] justify-center bg-white px-4 sm:h-[70px] md:h-[80px]"
|
||||
>
|
||||
<div
|
||||
class="flex w-[320px] min-w-0 max-w-[320px] grow items-center border-b sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<a href="/" class="flex items-center">
|
||||
<img
|
||||
src="logo.svg"
|
||||
loading="eager"
|
||||
alt="Postgresus logo"
|
||||
width="35"
|
||||
height="35"
|
||||
class="flex-shrink-0 sm:h-[40px] sm:w-[40px] md:h-[50px] md:w-[50px]"
|
||||
/>
|
||||
|
||||
<div class="ml-2 text-lg font-bold sm:ml-3 sm:text-xl md:ml-4 md:text-2xl">
|
||||
Postgresus
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<!------------------ END OF NAVBAR ------------------>
|
||||
|
||||
<!------------------ MAIN CONTENT ------------------------->
|
||||
<div class="flex justify-center py-[50px] pt-[110px] md:py-[100px] md:pt-[180px]">
|
||||
<main
|
||||
class="w-[320px] max-w-[320px] sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<div class="max-w-[800px]">
|
||||
<h1 class="mb-6 max-w-[650px] text-2xl font-bold sm:text-4xl">
|
||||
How to send notifications from Postgresus to Microsoft Teams
|
||||
</h1>
|
||||
|
||||
<p class="mb-8 max-w-[600px] text-lg text-gray-700">
|
||||
To send notifications to Microsoft Teams you will:
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<div class="space-y-8">
|
||||
<!-- How to create Power Automate URL -->
|
||||
<div class="pl-6">
|
||||
<h2 class="mb-4 text-2xl font-semibold">How to create a Power Automate URL (inside Teams)</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Step 1 -->
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>1</span
|
||||
>
|
||||
Open Teams and go to the Teams section on the left side
|
||||
</h3>
|
||||
|
||||
<div class="space-y-2">
|
||||
<p class="text-gray-700">
|
||||
If you don't have a group yet, create it as shown in the screenshot, if it already exists, then go to section #2
|
||||
|
||||
</p>
|
||||
<img
|
||||
src="images/notifier-teams/image-01.png"
|
||||
alt="Open Power Automate app in Microsoft Teams"
|
||||
class="mb-4 mt-3 max-w-[600px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2 -->
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>2</span
|
||||
>
|
||||
Creation of flow
|
||||
</h3>
|
||||
<p class="mb-2 text-gray-700">
|
||||
Select the desired channel and click on the three dots. Then select Workflows
|
||||
</p>
|
||||
<img
|
||||
src="images/notifier-teams/image-02.png"
|
||||
alt="Select environment in Power Automate"
|
||||
class="mb-4 mt-3 max-w-[600px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Step 3 -->
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>3</span
|
||||
>
|
||||
Create the flow (HTTP trigger)
|
||||
</h3>
|
||||
<p class="text-gray-700">
|
||||
In the new window, select the flow type - Send webhook alerts to a channel.
|
||||
</p>
|
||||
<img
|
||||
src="images/notifier-teams/image-03.png"
|
||||
alt="Create Automated cloud flow with HTTP trigger"
|
||||
class="mb-4 mt-3 max-w-[600px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Step 4 -->
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>4</span
|
||||
>
|
||||
(Optional) If necessary, you can change the name of the Flow (optional)
|
||||
</h3>
|
||||
<p class="mb-2 text-gray-700">
|
||||
The name Flow does not affect its operation.
|
||||
</p>
|
||||
<img
|
||||
src="images/notifier-teams/image-04.png"
|
||||
alt="Create Automated cloud flow with HTTP trigger"
|
||||
class="mb-4 mt-3 max-w-[600px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Step 5 -->
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>5</span
|
||||
>
|
||||
(Optional)You can choose which channel to send Flow to.
|
||||
</h3>
|
||||
|
||||
<img
|
||||
src="images/notifier-teams/image-05.png"
|
||||
alt="Add Teams action: Post a message in a chat or channel"
|
||||
class="mb-4 mt-3 max-w-[600px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Step 6 -->
|
||||
<div class="border-l-4 border-green-500 pl-6">
|
||||
<h3 class="mb-3 flex items-center text-xl font-semibold">
|
||||
<span
|
||||
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-sm text-white"
|
||||
>6</span
|
||||
>
|
||||
Save and copy the HTTP URL
|
||||
</h3>
|
||||
<p class="text-gray-700">
|
||||
At the bottom of the window you will have a url that you need to copy and paste into the Postgresus settings.
|
||||
</p>
|
||||
<img
|
||||
src="images/notifier-teams/image-06.png"
|
||||
alt="Copy HTTP POST URL from the trigger"
|
||||
class="mb-4 mt-3 max-w-[600px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Connect in Postgresus -->
|
||||
<div class="pl-6">
|
||||
<h2 class="mb-4 text-2xl font-semibold">Connect the URL in Postgresus</h2>
|
||||
|
||||
<ol class="ml-2 list-inside list-decimal space-y-2 text-gray-700">
|
||||
<li>Open <strong>Postgresus</strong>.</li>
|
||||
<li>Go to <strong>Settings</strong> → <strong>Notifiers</strong> → <strong>Teams</strong>.</li>
|
||||
<li>Paste the <strong>URL</strong> you copied from Teams.</li>
|
||||
|
||||
<li>Click <strong>Send test notification</strong> in Postgresus and check the chosen Teams channel for the message.</li>
|
||||
</ol>
|
||||
|
||||
<img
|
||||
src="images/notifier-teams/image-07.png"
|
||||
alt="Paste the flow URL into Postgresus"
|
||||
class="mb-8 mt-5 max-w-[600px] rounded-lg border border-gray-200 shadow-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Final Step -->
|
||||
<div class="max-w-[600px] rounded-lg border border-green-200 bg-green-50 p-6">
|
||||
<h2 class="mb-3 text-xl font-semibold text-green-800">🎉 Setup Complete!</h2>
|
||||
<p class="text-green-700">
|
||||
Your Power Automate flow is connected. Postgresus can now send backup notifications
|
||||
directly to your selected Microsoft Teams chat or channel.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="mt-12 border-t border-gray-200 pt-8">
|
||||
<a
|
||||
href="/"
|
||||
class="inline-flex items-center font-semibold text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
← Back to main page
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<!------------------ END OF MAIN CONTENT ------------------>
|
||||
|
||||
<!------------------ FOOTER SECTION -------------------------------->
|
||||
<div id="footer" class="flex justify-center bg-blue-600 py-[50px] md:py-[50px]">
|
||||
<div
|
||||
class="w-[320px] max-w-[320px] sm:w-[640px] sm:max-w-[640px] lg:w-[1200px] lg:max-w-[1200px]"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="flex flex-wrap justify-center gap-6 text-white">
|
||||
<a
|
||||
href="https://github.com/RostislavDugin/postgresus"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://t.me/postgresus_community"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
Community
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://rostislav-dugin.com"
|
||||
target="_blank"
|
||||
class="transition-colors hover:text-blue-200"
|
||||
>
|
||||
Developer
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-center text-sm text-white">© 2025 Postgresus. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!------------------ END OF FOOTER SECTION ------------------------->
|
||||
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script type="text/javascript">
|
||||
(function (m, e, t, r, i, k, a) {
|
||||
m[i] =
|
||||
m[i] ||
|
||||
function () {
|
||||
(m[i].a = m[i].a || []).push(arguments);
|
||||
};
|
||||
m[i].l = 1 * new Date();
|
||||
for (var j = 0; j < document.scripts.length; j++) {
|
||||
if (document.scripts[j].src === r) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
((k = e.createElement(t)),
|
||||
(a = e.getElementsByTagName(t)[0]),
|
||||
(k.async = 1),
|
||||
(k.src = r),
|
||||
a.parentNode.insertBefore(k, a));
|
||||
})(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js?id=103482608', 'ym');
|
||||
|
||||
ym(103482608, 'init', {
|
||||
ssr: true,
|
||||
clickmap: true,
|
||||
ecommerce: 'dataLayer',
|
||||
accurateTrackBounce: true,
|
||||
trackLinks: true,
|
||||
});
|
||||
</script>
|
||||
<noscript
|
||||
><div>
|
||||
<img
|
||||
src="https://mc.yandex.ru/watch/103482608"
|
||||
style="position: absolute; left: -9999px"
|
||||
alt=""
|
||||
/></div
|
||||
></noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
</body>
|
||||
</html>
|
||||
6531
package-lock.json
generated
34
package.json
@@ -1,20 +1,26 @@
|
||||
{
|
||||
"name": "postgresus-website",
|
||||
"version": "1.0.0",
|
||||
"description": "PostgreSQL backup and monitoring tool",
|
||||
"main": "index.js",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"build-css": "tailwindcss -i ./src/input.css -o ./styles.min.css --minify",
|
||||
"watch-css": "tailwindcss -i ./src/input.css -o ./styles.min.css --watch",
|
||||
"build": "npm run build-css"
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"next": "16.0.1"
|
||||
},
|
||||
"author": "Postgresus",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
"tailwindcss": "^3.4.16"
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"tailwindcss": "^4",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
postcss.config.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 905 KiB After Width: | Height: | Size: 905 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 282 B |
|
Before Width: | Height: | Size: 795 KiB After Width: | Height: | Size: 795 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
183
public/llms.txt
Normal file
@@ -0,0 +1,183 @@
|
||||
# Postgresus - PostgreSQL Backup Tool
|
||||
|
||||
> Free and open source tool for PostgreSQL scheduled backups. Save them locally and to clouds. Notifications to Slack, Discord, etc.
|
||||
|
||||
## Project Information
|
||||
|
||||
- Name: Postgresus
|
||||
- Type: Self-hosted PostgreSQL backup tool
|
||||
- License: Apache 2.0
|
||||
- Repository: https://github.com/RostislavDugin/postgresus
|
||||
- Website: https://postgresus.com
|
||||
- Community: https://t.me/postgresus_community
|
||||
|
||||
## Key Features
|
||||
|
||||
- Scheduled PostgreSQL backups (hourly, daily, weekly, monthly)
|
||||
- Multiple storage destinations: Local, S3, Cloudflare R2, Google Drive, Dropbox, NAS and more
|
||||
- Real-time notifications: Slack, Telegram, Discord, Microsoft Teams, Email, Webhooks and more
|
||||
- Database health monitoring with configurable health checks
|
||||
- Self-hosted via Docker with zero-config installation
|
||||
- Support for PostgreSQL versions 13, 14, 15, 16, 17 and 18
|
||||
- Backup compression (4-8x size reduction)
|
||||
- Open source and free
|
||||
|
||||
## Documentation
|
||||
|
||||
### Installation
|
||||
URL: /installation
|
||||
Content: Three installation methods - automated script, Docker run, Docker Compose
|
||||
|
||||
### Password Reset
|
||||
URL: /password
|
||||
Content: How to reset user passwords using command-line tool
|
||||
|
||||
### Storage Configuration
|
||||
URL: /storages
|
||||
Content: Overview of supported storage destinations
|
||||
|
||||
#### Google Drive Storage
|
||||
URL: /storages/google-drive
|
||||
Content: Step-by-step guide to configure Google Drive storage with OAuth setup
|
||||
|
||||
#### Cloudflare R2 Storage
|
||||
URL: /storages/cloudflare-r2
|
||||
Content: Guide to configure Cloudflare R2 S3-compatible storage
|
||||
|
||||
### Notification Configuration
|
||||
URL: /notifiers
|
||||
Content: Overview of supported notification channels
|
||||
|
||||
#### Slack Notifications
|
||||
URL: /notifiers/slack
|
||||
Content: How to set up Slack webhook notifications
|
||||
|
||||
#### Microsoft Teams Notifications
|
||||
URL: /notifiers/teams
|
||||
Content: How to configure Teams webhook notifications
|
||||
|
||||
## Technical Details
|
||||
|
||||
### System Requirements
|
||||
- CPU: At least 1 core
|
||||
- RAM: Minimum 500 MB
|
||||
- Storage: 5 GB + backup storage space
|
||||
- Docker Engine 20.10+
|
||||
- Docker Compose v2.0+
|
||||
|
||||
### Installation Options
|
||||
|
||||
1. **Automated Script (Recommended for Linux)**
|
||||
- Installs Docker and Docker Compose automatically
|
||||
- Sets up Postgresus with auto-restart
|
||||
- Installation directory: /opt/postgresus
|
||||
|
||||
2. **Docker Run**
|
||||
- Quick single-command deployment
|
||||
- Data stored in ./postgresus-data
|
||||
- Port: 4005
|
||||
|
||||
3. **Docker Compose**
|
||||
- Managed deployment with configuration file
|
||||
- Automatic restart on system reboot
|
||||
|
||||
### Backup Process
|
||||
|
||||
1. Select schedule (hourly, daily, weekly, monthly, specific time)
|
||||
2. Configure database connection (host, port, credentials, version, SSL)
|
||||
3. Choose storage destination(s)
|
||||
4. Set up notification channels (optional)
|
||||
5. Automated backup execution with compression
|
||||
|
||||
### Storage Destinations
|
||||
|
||||
- **Local Storage**: Direct server/VPS storage
|
||||
- **S3**: Amazon S3 and S3-compatible services
|
||||
- **Cloudflare R2**: S3-compatible object storage
|
||||
- **Google Drive**: Cloud storage via Google Cloud API
|
||||
- **NAS**: Network-attached storage devices
|
||||
- **Dropbox**: Cloud storage integration
|
||||
- and more
|
||||
|
||||
### Notification Channels
|
||||
|
||||
- **Slack**: Via webhook integration
|
||||
- **Discord**: Channel notifications
|
||||
- **Telegram**: Bot-based notifications
|
||||
- **Microsoft Teams**: Workflow webhook integration
|
||||
- **Email**: SMTP-based email alerts
|
||||
- **Webhook**: Custom HTTP webhook integration
|
||||
- and more
|
||||
|
||||
## Target Audience
|
||||
|
||||
Postgresus is suitable for:
|
||||
- **Single Developers**: Managing personal projects and side projects
|
||||
- **DevOps Teams**: Maintaining production databases and infrastructure
|
||||
- **Organizations**: Requiring compliance, audit trails and enterprise backup solutions
|
||||
- **Startups**: Need reliable backups without enterprise costs
|
||||
- **System Administrators**: Managing multiple database environments
|
||||
- **IT Departments**: Seeking self-hosted backup solutions
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Single developers managing personal projects
|
||||
- DevOps teams maintaining production databases
|
||||
- Organizations requiring compliance and audit trails
|
||||
- Self-hosted database backup solutions
|
||||
- Multi-environment database management
|
||||
- Automated disaster recovery preparation
|
||||
|
||||
## Backup Features
|
||||
|
||||
- **Compression**: Balanced compression with 4-8x size reduction
|
||||
- **Scheduling**: Flexible scheduling with exact time specification
|
||||
- **Health Checks**: Configurable database monitoring
|
||||
- **Multi-target**: Store backups to multiple destinations simultaneously
|
||||
- **Restoration**: One-click restore functionality
|
||||
- **Security**: All data stays within your infrastructure
|
||||
|
||||
## FAQ
|
||||
|
||||
### What is Postgresus?
|
||||
Apache 2.0 licensed, self-hosted service for backing up PostgreSQL v13-v18. Features frontend for scheduling, compression, multiple storage targets and team notifications. Designed to be more UX-friendly alternative to PgBackRest, Barman, pg_dump scripts and other traditional backup tools.
|
||||
|
||||
### How is Postgresus different from PgBackRest, Barman or pg_dump?
|
||||
Postgresus provides a modern, user-friendly web interface instead of complex configuration files and command-line tools. While PgBackRest and Barman require extensive configuration and command-line expertise, Postgresus offers intuitive point-and-click setup. Unlike raw pg_dump scripts, it includes built-in scheduling, compression, multiple storage destinations, health monitoring and real-time notifications - all managed through a simple web UI. It's the UX-friendly alternative for developers and DevOps teams who want powerful backup capabilities without the complexity.
|
||||
|
||||
### Installation Time
|
||||
Typically less than 2 minutes on a standard VPS using the automated installer.
|
||||
|
||||
### Backup Schedules
|
||||
Hourly, daily, weekly or monthly cycles with exact run time specification (e.g., 04:00). Weekly schedules support specific weekdays, monthly schedules support specific calendar days.
|
||||
|
||||
### Storage Space
|
||||
Archives saved to local volumes, S3-compatible buckets, Google Drive, Dropbox and other cloud targets. Balanced compression typically shrinks dump size by 4-8x with ~20% runtime overhead.
|
||||
|
||||
### Failure Notifications
|
||||
Real-time notifications via email, Slack, Telegram, webhooks, Mattermost, Discord and more. DevOps teams receive immediate alerts for successes and failures.
|
||||
|
||||
### Security
|
||||
All data executes within containers you control on servers you own. Credentials and backup files remain on your server or in your cloud account. Open source allows full code inspection.
|
||||
|
||||
## Developer Information
|
||||
|
||||
- Docker Image: rostislavdugin/postgresus:latest
|
||||
- Default Port: 4005
|
||||
- Data Directory: ./postgresus-data or /postgresus-data
|
||||
- Configuration: Web-based UI
|
||||
- API: Internal backend API
|
||||
|
||||
## Related Technologies
|
||||
|
||||
- PostgreSQL: Supported versions 13-18
|
||||
- Docker: Containerization platform
|
||||
- pg_dump: PostgreSQL backup utility
|
||||
- S3 API: Storage protocol compatibility
|
||||
- OAuth 2.0: Google Drive authentication
|
||||
- Webhook: Notification integration protocol
|
||||
|
||||
## Keywords
|
||||
|
||||
PostgreSQL, backup, monitoring, database, scheduled backups, Docker, self-hosted, open source, S3, Google Drive, Slack notifications, Discord, DevOps, database monitoring, pg_dump, database restore, Cloudflare R2, Microsoft Teams, health checks, compression, automation
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,4 +0,0 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
|
||||
Sitemap: https://postgresus.com/sitemap.xml
|
||||
23
sitemap.xml
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||
<url>
|
||||
<loc>https://postgresus.com/</loc>
|
||||
<lastmod>2025-07-16T01:03:35+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://postgresus.com/blog</loc>
|
||||
<lastmod>2025-07-25T19:48:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://postgresus.com/cloudflare-r2-storage</loc>
|
||||
<lastmod>2025-07-16T01:03:35+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://postgresus.com/notifier-slack</loc>
|
||||
<lastmod>2025-07-16T01:03:35+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://postgresus.com/google-drive-storage</loc>
|
||||
<lastmod>2025-07-16T01:03:35+00:00</lastmod>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -1,40 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Custom styles from index.css */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Jost', sans-serif;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font-family: inherit;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<title>Google OAuth - Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="text-align: center; padding: 50px; font-family: Arial, sans-serif">
|
||||
<h2>Processing OAuth...</h2>
|
||||
<p>Please wait while we redirect you back to the application.</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onload = function () {
|
||||
// Get current URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Extract state and code from current URL
|
||||
const state = urlParams.get('state');
|
||||
const code = urlParams.get('code');
|
||||
|
||||
if (state && code) {
|
||||
try {
|
||||
// Parse the StorageOauthDto from the state parameter
|
||||
const oauthDto = JSON.parse(decodeURIComponent(state));
|
||||
|
||||
// Update the authCode field with the received code
|
||||
oauthDto.authCode = code;
|
||||
|
||||
// Construct the redirect URL with the updated DTO
|
||||
const redirectUrl = `${oauthDto.redirectUrl}?oauthDto=${encodeURIComponent(JSON.stringify(oauthDto))}`;
|
||||
|
||||
// Redirect to the constructed URL
|
||||
window.location.href = redirectUrl;
|
||||
} catch (error) {
|
||||
console.error('Error parsing state parameter:', error);
|
||||
}
|
||||
} else {
|
||||
console.error('Missing state or code parameter');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1934
styles.min.css
vendored
@@ -1,12 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./index.html', './*.html', './storages/**/*.html', './pages/**/*.html'],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
jost: ['Jost', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
34
tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||