mirror of
https://github.com/databasus/databasus.git
synced 2026-04-06 08:41:58 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02c735bc5a | ||
|
|
793b575146 | ||
|
|
a6e84b45f2 | ||
|
|
a941fbd093 | ||
|
|
4492ba41f5 | ||
|
|
3a5ac4b479 | ||
|
|
77aaabeaa1 | ||
|
|
01911dbf72 | ||
|
|
1a16f27a5d |
2
.github/workflows/ci-release.yml
vendored
2
.github/workflows/ci-release.yml
vendored
@@ -127,6 +127,7 @@ jobs:
|
||||
TEST_GOOGLE_DRIVE_CLIENT_SECRET=${{ secrets.TEST_GOOGLE_DRIVE_CLIENT_SECRET }}
|
||||
TEST_GOOGLE_DRIVE_TOKEN_JSON=${{ secrets.TEST_GOOGLE_DRIVE_TOKEN_JSON }}
|
||||
# testing DBs
|
||||
TEST_POSTGRES_12_PORT=5000
|
||||
TEST_POSTGRES_13_PORT=5001
|
||||
TEST_POSTGRES_14_PORT=5002
|
||||
TEST_POSTGRES_15_PORT=5003
|
||||
@@ -154,6 +155,7 @@ jobs:
|
||||
timeout 60 bash -c 'until docker exec dev-db pg_isready -h localhost -p 5437 -U postgres; do sleep 2; done'
|
||||
|
||||
# Wait for test databases
|
||||
timeout 60 bash -c 'until nc -z localhost 5000; do sleep 2; done'
|
||||
timeout 60 bash -c 'until nc -z localhost 5001; do sleep 2; done'
|
||||
timeout 60 bash -c 'until nc -z localhost 5002; do sleep 2; done'
|
||||
timeout 60 bash -c 'until nc -z localhost 5003; do sleep 2; done'
|
||||
|
||||
@@ -77,7 +77,7 @@ ENV APP_VERSION=$APP_VERSION
|
||||
# Set production mode for Docker containers
|
||||
ENV ENV_MODE=production
|
||||
|
||||
# Install PostgreSQL server and client tools (versions 13-17)
|
||||
# Install PostgreSQL server and client tools (versions 12-18)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
wget ca-certificates gnupg lsb-release sudo gosu && \
|
||||
wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
|
||||
@@ -85,7 +85,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
> /etc/apt/sources.list.d/pgdg.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
postgresql-17 postgresql-18 postgresql-client-13 postgresql-client-14 postgresql-client-15 \
|
||||
postgresql-17 postgresql-18 postgresql-client-12 postgresql-client-13 postgresql-client-14 postgresql-client-15 \
|
||||
postgresql-client-16 postgresql-client-17 postgresql-client-18 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
25
README.md
25
README.md
@@ -9,7 +9,7 @@
|
||||
[](https://hub.docker.com/r/rostislavdugin/postgresus)
|
||||
[](https://github.com/RostislavDugin/postgresus)
|
||||
|
||||
[](https://www.postgresql.org/)
|
||||
[](https://www.postgresql.org/)
|
||||
[](https://github.com/RostislavDugin/postgresus)
|
||||
[](https://github.com/RostislavDugin/postgresus)
|
||||
|
||||
@@ -40,13 +40,13 @@
|
||||
- **Precise timing**: run backups at specific times (e.g., 4 AM during low traffic)
|
||||
- **Smart compression**: 4-8x space savings with balanced compression (~20% overhead)
|
||||
|
||||
### 🗄️ **Multiple Storage Destinations**
|
||||
### 🗄️ **Multiple Storage Destinations** <a href="https://postgresus.com/storages">(docs)</a>
|
||||
|
||||
- **Local storage**: Keep backups on your VPS/server
|
||||
- **Cloud storage**: S3, Cloudflare R2, Google Drive, NAS, Dropbox and more
|
||||
- **Secure**: All data stays under your control
|
||||
|
||||
### 📱 **Smart Notifications**
|
||||
### 📱 **Smart Notifications** <a href="https://postgresus.com/notifiers">(docs)</a>
|
||||
|
||||
- **Multiple channels**: Email, Telegram, Slack, Discord, webhooks
|
||||
- **Real-time updates**: Success and failure notifications
|
||||
@@ -54,17 +54,24 @@
|
||||
|
||||
### 🐘 **PostgreSQL Support**
|
||||
|
||||
- **Multiple versions**: PostgreSQL 13, 14, 15, 16, 17 and 18
|
||||
- **Multiple versions**: PostgreSQL 12, 13, 14, 15, 16, 17 and 18
|
||||
- **SSL support**: Secure connections available
|
||||
- **Easy restoration**: One-click restore from any backup
|
||||
|
||||
### 👥 **Suitable for Teams** <a href="https://postgresus.com/access-management">(docs)</a>
|
||||
|
||||
- **Workspaces**: Group databases, notifiers and storages for different projects or teams
|
||||
- **Access management**: Control who can view or manage specific databases with role-based permissions
|
||||
- **Audit logs**: Track all system activities and changes made by users
|
||||
- **User roles**: Assign viewer, member, admin or owner roles within workspaces
|
||||
|
||||
### 🐳 **Self-Hosted & Secure**
|
||||
|
||||
- **Docker-based**: Easy deployment and management
|
||||
- **Privacy-first**: All your data stays on your infrastructure
|
||||
- **Open source**: Apache 2.0 licensed, inspect every line of code
|
||||
|
||||
### 📦 Installation
|
||||
### 📦 Installation <a href="https://postgresus.com/installation">(docs)</a>
|
||||
|
||||
You have three ways to install Postgresus:
|
||||
|
||||
@@ -118,8 +125,6 @@ This single command will:
|
||||
Create a `docker-compose.yml` file with the following configuration:
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
postgresus:
|
||||
container_name: postgresus
|
||||
@@ -149,9 +154,9 @@ docker compose up -d
|
||||
6. **Add notifications** (optional): Configure email, Telegram, Slack, or webhook notifications
|
||||
7. **Save and start**: Postgresus will validate settings and begin the backup schedule
|
||||
|
||||
### 🔑 Resetting Admin Password
|
||||
### 🔑 Resetting Password <a href="https://postgresus.com/password">(docs)</a>
|
||||
|
||||
If you need to reset the admin password, you can use the built-in password reset command:
|
||||
If you need to reset the password, you can use the built-in password reset command:
|
||||
|
||||
```bash
|
||||
docker exec -it postgresus ./main --new-password="YourNewSecurePassword123" --email="admin"
|
||||
@@ -169,4 +174,4 @@ This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENS
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Read [contributing guide](contribute/README.md) for more details, prioerities and rules are specified there. If you want to contribute, but don't know what and how - message me on Telegram [@rostislav_dugin](https://t.me/rostislav_dugin)
|
||||
Contributions are welcome! Read <a href="https://postgresus.com/contributing">contributing guide</a> for more details, prioerities and rules are specified there. If you want to contribute, but don't know what and how - message me on Telegram [@rostislav_dugin](https://t.me/rostislav_dugin)
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
@@ -17,6 +17,7 @@ TEST_GOOGLE_DRIVE_CLIENT_ID=
|
||||
TEST_GOOGLE_DRIVE_CLIENT_SECRET=
|
||||
TEST_GOOGLE_DRIVE_TOKEN_JSON="{\"access_token\":\"ya29..."
|
||||
# testing DBs
|
||||
TEST_POSTGRES_12_PORT=5000
|
||||
TEST_POSTGRES_13_PORT=5001
|
||||
TEST_POSTGRES_14_PORT=5002
|
||||
TEST_POSTGRES_15_PORT=5003
|
||||
|
||||
@@ -32,6 +32,17 @@ services:
|
||||
command: server /data --console-address ":9001"
|
||||
|
||||
# Test PostgreSQL containers
|
||||
test-postgres-12:
|
||||
image: postgres:12
|
||||
ports:
|
||||
- "${TEST_POSTGRES_12_PORT}:5432"
|
||||
environment:
|
||||
- POSTGRES_DB=testdb
|
||||
- POSTGRES_USER=testuser
|
||||
- POSTGRES_PASSWORD=testpassword
|
||||
container_name: test-postgres-12
|
||||
shm_size: 1gb
|
||||
|
||||
test-postgres-13:
|
||||
image: postgres:13
|
||||
ports:
|
||||
|
||||
@@ -33,6 +33,7 @@ type EnvVariables struct {
|
||||
TestGoogleDriveClientSecret string `env:"TEST_GOOGLE_DRIVE_CLIENT_SECRET"`
|
||||
TestGoogleDriveTokenJSON string `env:"TEST_GOOGLE_DRIVE_TOKEN_JSON"`
|
||||
|
||||
TestPostgres12Port string `env:"TEST_POSTGRES_12_PORT"`
|
||||
TestPostgres13Port string `env:"TEST_POSTGRES_13_PORT"`
|
||||
TestPostgres14Port string `env:"TEST_POSTGRES_14_PORT"`
|
||||
TestPostgres15Port string `env:"TEST_POSTGRES_15_PORT"`
|
||||
@@ -145,6 +146,10 @@ func loadEnvVariables() {
|
||||
env.TempFolder = filepath.Join(filepath.Dir(backendRoot), "postgresus-data", "temp")
|
||||
|
||||
if env.IsTesting {
|
||||
if env.TestPostgres12Port == "" {
|
||||
log.Error("TEST_POSTGRES_12_PORT is empty")
|
||||
os.Exit(1)
|
||||
}
|
||||
if env.TestPostgres13Port == "" {
|
||||
log.Error("TEST_POSTGRES_13_PORT is empty")
|
||||
os.Exit(1)
|
||||
|
||||
@@ -516,7 +516,10 @@ func (s *BackupService) deleteBackup(backup *Backup) error {
|
||||
|
||||
err = storage.DeleteFile(backup.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
// we do not return error here, because sometimes clean up performed
|
||||
// before unavailable storage removal or change - therefore we should
|
||||
// proceed even in case of error
|
||||
s.logger.Error("Failed to delete backup file", "error", err)
|
||||
}
|
||||
|
||||
return s.backupRepository.DeleteByID(backup.ID)
|
||||
|
||||
@@ -70,10 +70,10 @@ func (uc *CreatePostgresqlBackupUsecase) Execute(
|
||||
"--verbose", // Add verbose output to help with debugging
|
||||
}
|
||||
|
||||
// Use zstd compression level 5 for PostgreSQL 15+ (better compression and speed)
|
||||
// Fall back to gzip compression level 5 for older versions
|
||||
if pg.Version == tools.PostgresqlVersion13 || pg.Version == tools.PostgresqlVersion14 ||
|
||||
pg.Version == tools.PostgresqlVersion15 {
|
||||
// Use zstd compression level 5 for PostgreSQL 16+ (better compression and speed)
|
||||
// Fall back to gzip compression level 5 for older versions (12-15)
|
||||
if pg.Version == tools.PostgresqlVersion12 || pg.Version == tools.PostgresqlVersion13 ||
|
||||
pg.Version == tools.PostgresqlVersion14 || pg.Version == tools.PostgresqlVersion15 {
|
||||
args = append(args, "-Z", "5")
|
||||
uc.logger.Info("Using gzip compression level 5 (zstd not available)", "version", pg.Version)
|
||||
} else {
|
||||
|
||||
@@ -94,7 +94,6 @@ func (c *BackupConfigController) GetBackupConfigByDbID(ctx *gin.Context) {
|
||||
// @Tags backup-configs
|
||||
// @Produce json
|
||||
// @Param id path string true "Storage ID"
|
||||
// @Param workspace_id query string true "Workspace ID"
|
||||
// @Success 200 {object} map[string]bool
|
||||
// @Failure 400
|
||||
// @Failure 401
|
||||
@@ -113,19 +112,7 @@ func (c *BackupConfigController) IsStorageUsing(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
workspaceIDStr := ctx.Query("workspace_id")
|
||||
if workspaceIDStr == "" {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "workspace_id query parameter is required"})
|
||||
return
|
||||
}
|
||||
|
||||
workspaceID, err := uuid.Parse(workspaceIDStr)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace_id"})
|
||||
return
|
||||
}
|
||||
|
||||
isUsing, err := c.backupConfigService.IsStorageUsing(user, workspaceID, id)
|
||||
isUsing, err := c.backupConfigService.IsStorageUsing(user, id)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
|
||||
@@ -341,7 +341,7 @@ func Test_IsStorageUsing_PermissionsEnforced(t *testing.T) {
|
||||
test_utils.MakeGetRequestAndUnmarshal(
|
||||
t,
|
||||
router,
|
||||
"/api/v1/backup-configs/storage/"+storage.ID.String()+"/is-using?workspace_id="+workspace.ID.String(),
|
||||
"/api/v1/backup-configs/storage/"+storage.ID.String()+"/is-using",
|
||||
"Bearer "+testUserToken,
|
||||
tt.expectedStatusCode,
|
||||
&response,
|
||||
@@ -354,7 +354,7 @@ func Test_IsStorageUsing_PermissionsEnforced(t *testing.T) {
|
||||
testResp := test_utils.MakeGetRequest(
|
||||
t,
|
||||
router,
|
||||
"/api/v1/backup-configs/storage/"+storage.ID.String()+"/is-using?workspace_id="+workspace.ID.String(),
|
||||
"/api/v1/backup-configs/storage/"+storage.ID.String()+"/is-using",
|
||||
"Bearer "+testUserToken,
|
||||
tt.expectedStatusCode,
|
||||
)
|
||||
|
||||
@@ -119,7 +119,6 @@ func (s *BackupConfigService) GetBackupConfigByDbId(
|
||||
|
||||
func (s *BackupConfigService) IsStorageUsing(
|
||||
user *users_models.User,
|
||||
workspaceID uuid.UUID,
|
||||
storageID uuid.UUID,
|
||||
) (bool, error) {
|
||||
_, err := s.storageService.GetStorage(user, storageID)
|
||||
|
||||
@@ -271,7 +271,6 @@ func (c *DatabaseController) TestDatabaseConnectionDirect(ctx *gin.Context) {
|
||||
// @Tags databases
|
||||
// @Produce json
|
||||
// @Param id path string true "Notifier ID"
|
||||
// @Param workspace_id query string true "Workspace ID"
|
||||
// @Success 200 {object} map[string]bool
|
||||
// @Failure 400
|
||||
// @Failure 401
|
||||
@@ -290,19 +289,7 @@ func (c *DatabaseController) IsNotifierUsing(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
workspaceIDStr := ctx.Query("workspace_id")
|
||||
if workspaceIDStr == "" {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "workspace_id query parameter is required"})
|
||||
return
|
||||
}
|
||||
|
||||
workspaceID, err := uuid.Parse(workspaceIDStr)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace_id"})
|
||||
return
|
||||
}
|
||||
|
||||
isUsing, err := c.databaseService.IsNotifierUsing(user, workspaceID, id)
|
||||
isUsing, err := c.databaseService.IsNotifierUsing(user, id)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
|
||||
@@ -67,6 +67,7 @@ func (d *Database) HideSensitiveData() {
|
||||
func (d *Database) Update(incoming *Database) {
|
||||
d.Name = incoming.Name
|
||||
d.Type = incoming.Type
|
||||
d.Notifiers = incoming.Notifiers
|
||||
|
||||
switch d.Type {
|
||||
case DatabaseTypePostgres:
|
||||
|
||||
@@ -219,7 +219,6 @@ func (s *DatabaseService) GetDatabasesByWorkspace(
|
||||
|
||||
func (s *DatabaseService) IsNotifierUsing(
|
||||
user *users_models.User,
|
||||
workspaceID uuid.UUID,
|
||||
notifierID uuid.UUID,
|
||||
) (bool, error) {
|
||||
_, err := s.notifierService.GetNotifier(user, notifierID)
|
||||
|
||||
@@ -69,6 +69,7 @@ func Test_BackupAndRestorePostgresql_RestoreIsSuccesful(t *testing.T) {
|
||||
version string
|
||||
port string
|
||||
}{
|
||||
{"PostgreSQL 12", "12", env.TestPostgres12Port},
|
||||
{"PostgreSQL 13", "13", env.TestPostgres13Port},
|
||||
{"PostgreSQL 14", "14", env.TestPostgres14Port},
|
||||
{"PostgreSQL 15", "15", env.TestPostgres15Port},
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
type PostgresqlVersion string
|
||||
|
||||
const (
|
||||
PostgresqlVersion12 PostgresqlVersion = "12"
|
||||
PostgresqlVersion13 PostgresqlVersion = "13"
|
||||
PostgresqlVersion14 PostgresqlVersion = "14"
|
||||
PostgresqlVersion15 PostgresqlVersion = "15"
|
||||
@@ -32,6 +33,8 @@ const (
|
||||
|
||||
func GetPostgresqlVersionEnum(version string) PostgresqlVersion {
|
||||
switch version {
|
||||
case "12":
|
||||
return PostgresqlVersion12
|
||||
case "13":
|
||||
return PostgresqlVersion13
|
||||
case "14":
|
||||
|
||||
@@ -30,7 +30,7 @@ func GetPostgresqlExecutable(
|
||||
return filepath.Join(basePath, executableName)
|
||||
}
|
||||
|
||||
// VerifyPostgresesInstallation verifies that PostgreSQL versions 13-17 are installed
|
||||
// VerifyPostgresesInstallation verifies that PostgreSQL versions 12-18 are installed
|
||||
// in the current environment. Each version should be installed with the required
|
||||
// client tools (pg_dump, psql) available.
|
||||
// In development: ./tools/postgresql/postgresql-{VERSION}/bin
|
||||
@@ -41,6 +41,7 @@ func VerifyPostgresesInstallation(
|
||||
postgresesInstallDir string,
|
||||
) {
|
||||
versions := []PostgresqlVersion{
|
||||
PostgresqlVersion12,
|
||||
PostgresqlVersion13,
|
||||
PostgresqlVersion14,
|
||||
PostgresqlVersion15,
|
||||
|
||||
@@ -5,7 +5,7 @@ set -e # Exit on any error
|
||||
# Ensure non-interactive mode for apt
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
echo "Installing PostgreSQL client tools versions 13-18 for Linux (Debian/Ubuntu)..."
|
||||
echo "Installing PostgreSQL client tools versions 12-18 for Linux (Debian/Ubuntu)..."
|
||||
echo
|
||||
|
||||
# Check if running on supported system
|
||||
@@ -47,7 +47,7 @@ echo "Updating package list..."
|
||||
$SUDO apt-get update -qq -y
|
||||
|
||||
# Install client tools for each version
|
||||
versions="13 14 15 16 17 18"
|
||||
versions="12 13 14 15 16 17 18"
|
||||
|
||||
for version in $versions; do
|
||||
echo "Installing PostgreSQL $version client tools..."
|
||||
|
||||
5
backend/tools/download_macos.sh
Normal file → Executable file
5
backend/tools/download_macos.sh
Normal file → Executable file
@@ -2,7 +2,7 @@
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
echo "Installing PostgreSQL client tools versions 13-18 for MacOS..."
|
||||
echo "Installing PostgreSQL client tools versions 12-18 for MacOS..."
|
||||
echo
|
||||
|
||||
# Check if Homebrew is installed
|
||||
@@ -31,6 +31,7 @@ brew install wget openssl readline zlib
|
||||
|
||||
# PostgreSQL source URLs
|
||||
declare -A PG_URLS=(
|
||||
["12"]="https://ftp.postgresql.org/pub/source/v12.20/postgresql-12.20.tar.gz"
|
||||
["13"]="https://ftp.postgresql.org/pub/source/v13.16/postgresql-13.16.tar.gz"
|
||||
["14"]="https://ftp.postgresql.org/pub/source/v14.13/postgresql-14.13.tar.gz"
|
||||
["15"]="https://ftp.postgresql.org/pub/source/v15.8/postgresql-15.8.tar.gz"
|
||||
@@ -107,7 +108,7 @@ build_postgresql_client() {
|
||||
}
|
||||
|
||||
# Build each version
|
||||
versions="13 14 15 16 17 18"
|
||||
versions="12 13 14 15 16 17 18"
|
||||
|
||||
for version in $versions; do
|
||||
url=${PG_URLS[$version]}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
echo Downloading and installing PostgreSQL versions 13-18 for Windows...
|
||||
echo Downloading and installing PostgreSQL versions 12-18 for Windows...
|
||||
echo.
|
||||
|
||||
:: Create downloads and postgresql directories if they don't exist
|
||||
@@ -17,6 +17,7 @@ cd downloads
|
||||
set "BASE_URL=https://get.enterprisedb.com/postgresql"
|
||||
|
||||
:: Define versions and their corresponding download URLs
|
||||
set "PG12_URL=%BASE_URL%/postgresql-12.20-1-windows-x64.exe"
|
||||
set "PG13_URL=%BASE_URL%/postgresql-13.16-1-windows-x64.exe"
|
||||
set "PG14_URL=%BASE_URL%/postgresql-14.13-1-windows-x64.exe"
|
||||
set "PG15_URL=%BASE_URL%/postgresql-15.8-1-windows-x64.exe"
|
||||
@@ -25,7 +26,7 @@ set "PG17_URL=%BASE_URL%/postgresql-17.0-1-windows-x64.exe"
|
||||
set "PG18_URL=%BASE_URL%/postgresql-18.0-1-windows-x64.exe"
|
||||
|
||||
:: Array of versions
|
||||
set "versions=13 14 15 16 17 18"
|
||||
set "versions=12 13 14 15 16 17 18"
|
||||
|
||||
:: Download and install each version
|
||||
for %%v in (%versions%) do (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
This directory is needed only for development and CI\CD.
|
||||
|
||||
We have to download and install all the PostgreSQL versions from 13 to 18 locally.
|
||||
We have to download and install all the PostgreSQL versions from 12 to 18 locally.
|
||||
This is needed so we can call pg_dump, pg_dumpall, etc. on each version of the PostgreSQL database.
|
||||
|
||||
You do not need to install PostgreSQL fully with all the components.
|
||||
@@ -8,6 +8,7 @@ We only need the client tools (pg_dump, pg_dumpall, psql, etc.) for each version
|
||||
|
||||
We have to install the following:
|
||||
|
||||
- PostgreSQL 12
|
||||
- PostgreSQL 13
|
||||
- PostgreSQL 14
|
||||
- PostgreSQL 15
|
||||
@@ -72,6 +73,7 @@ The final directory structure should match:
|
||||
|
||||
For example:
|
||||
|
||||
- `./tools/postgresql/postgresql-12/bin/pg_dump`
|
||||
- `./tools/postgresql/postgresql-13/bin/pg_dump`
|
||||
- `./tools/postgresql/postgresql-14/bin/pg_dump`
|
||||
- `./tools/postgresql/postgresql-15/bin/pg_dump`
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
### Prerequisites
|
||||
|
||||
1. Read docs in /docs folder, README.md in /backend and /frontend folders
|
||||
2. Run both backend and frontend following the instructions in their respective README.md files (for development)
|
||||
3. Read this file till the end
|
||||
|
||||
### How to create a pull request?
|
||||
|
||||
We use gitflow approach.
|
||||
|
||||
1. Create a new branch from main
|
||||
2. Make changes
|
||||
3. Create a pull request to main
|
||||
4. Wait for review
|
||||
5. Merge pull request
|
||||
|
||||
Commits should be named in the following format depending on the type of change:
|
||||
|
||||
- `FEATURE (area): What was done`
|
||||
- `FIX (area): What was fixed`
|
||||
- `REFACTOR (area): What was refactored`
|
||||
|
||||
To see examples, look at commit history in main branch.
|
||||
|
||||
Branches should be named in the following format:
|
||||
|
||||
- `feature/what_was_done`
|
||||
- `fix/what_was_fixed`
|
||||
- `refactor/what_was_refactored`
|
||||
|
||||
Example:
|
||||
|
||||
- `feature/add_support_of_kubernetes_helm`
|
||||
- `fix/make_healthcheck_optional`
|
||||
- `refactor/refactor_navbar`
|
||||
|
||||
Before any commit, make sure:
|
||||
|
||||
1. You created critical tests for your changes
|
||||
2. `make lint` is passing (for backend) and `npm run lint` is passing (for frontend)
|
||||
3. All tests are passing
|
||||
4. Project is building successfully
|
||||
5. All your commits should be squashed into one commit with proper message (or to meaningful parts)
|
||||
6. Code do really refactored and production ready
|
||||
7. You have one single PR per one feature (at least, if features not connected)
|
||||
|
||||
### Automated Versioning
|
||||
|
||||
This project uses automated versioning based on commit messages:
|
||||
|
||||
- **FEATURE (area)**: Creates a **minor** version bump (e.g., 1.0.0 → 1.1.0)
|
||||
- **FIX (area)**: Creates a **patch** version bump (e.g., 1.0.0 → 1.0.1)
|
||||
- **REFACTOR (area)**: Creates a **patch** version bump (e.g., 1.0.0 → 1.0.1)
|
||||
- **BREAKING CHANGE**: Creates a **major** version bump (e.g., 1.0.0 → 2.0.0)
|
||||
|
||||
The system automatically:
|
||||
|
||||
- Analyzes commits since the last release
|
||||
- Determines the appropriate version bump
|
||||
- Generates a changelog grouped by area (frontend/backend/etc.)
|
||||
- Creates GitHub releases with detailed release notes
|
||||
- Updates package.json version numbers
|
||||
|
||||
To skip automated release (for documentation updates, etc.), add `[skip-release]` to your commit message.
|
||||
|
||||
### Docs
|
||||
|
||||
If you need to add some explanation, do it in appropriate place in the code. Or in the /docs folder if it is something general. For charts, use Mermaid.
|
||||
|
||||
### Priorities
|
||||
|
||||
Before taking anything more than a couple of lines of code, please write Rostislav via Telegram (@rostislav_dugin) and confirm priority. It is possible that we already have something in the works, it is not needed or it's not project priority.
|
||||
|
||||
Nearsest features:
|
||||
- add API keys and API actions
|
||||
- add encryption
|
||||
|
||||
Storages tasks:
|
||||
- check AWS S3 support
|
||||
- check Google Cloud S3 support
|
||||
- add FTP
|
||||
- add Dropbox
|
||||
- add OneDrive
|
||||
- add NAS
|
||||
- add Yandex Drive
|
||||
|
||||
Notifications tasks:
|
||||
- add Mattermost
|
||||
- make webhooks flexible
|
||||
- add Gotify
|
||||
|
||||
Extra:
|
||||
|
||||
- add HTTPS for Postgresus
|
||||
@@ -1,45 +0,0 @@
|
||||
# How to add new notifier to Postgresus (Discord, Slack, Telegram, Email, Webhook, etc.)
|
||||
|
||||
## Backend part
|
||||
|
||||
1. Create new model in `backend/internal/features/notifiers/models/{notifier_name}/` folder. Implement `NotificationSender` interface from parent folder.
|
||||
- The model should implement `Send(logger *slog.Logger, heading string, message string) error` and `Validate() error` methods
|
||||
- Use UUID primary key as `NotifierID` that references the main notifiers table
|
||||
|
||||
2. Add new notifier type to `backend/internal/features/notifiers/enums.go` in the `NotifierType` constants.
|
||||
|
||||
3. Update the main `Notifier` model in `backend/internal/features/notifiers/model.go`:
|
||||
- Add new notifier field with GORM foreign key relation
|
||||
- Update `getSpecificNotifier()` method to handle the new type
|
||||
- Update `Send()` method to route to the new notifier
|
||||
|
||||
4. If you need to add some .env variables to test, add them in `backend/internal/config/config.go` (so we can use it in tests)
|
||||
|
||||
5. If you need some Docker container to test, add it to `backend/docker-compose.yml.example`. For sensitive data - keep it blank.
|
||||
|
||||
6. If you need some sensitive envs to test in pipeline, message @rostislav_dugin so I can add it to GitHub Actions. For example, API keys or credentials.
|
||||
|
||||
7. Create new migration in `backend/migrations` folder:
|
||||
- Create table with `notifier_id` as UUID primary key
|
||||
- Add foreign key constraint to `notifiers` table with CASCADE DELETE
|
||||
- Look at existing notifier migrations for reference
|
||||
|
||||
8. Make sure that all tests are passing.
|
||||
|
||||
## Frontend part
|
||||
|
||||
If you are able to develop only backend - it's fine, message @rostislav_dugin so I can complete UI part.
|
||||
|
||||
1. Add models and validator to `frontend/src/entity/notifiers/models/{notifier_name}/` folder and update `index.ts` file to include new model exports.
|
||||
|
||||
2. Upload an SVG icon to `public/icons/notifiers/`, update `src/entity/notifiers/models/getNotifierLogoFromType.ts` to return new icon path, update `src/entity/notifiers/models/NotifierType.ts` to include new type, and update `src/entity/notifiers/models/getNotifierNameFromType.ts` to return new name.
|
||||
|
||||
3. Add UI components to manage your notifier:
|
||||
- `src/features/notifiers/ui/edit/notifiers/Edit{NotifierName}Component.tsx` (for editing)
|
||||
- `src/features/notifiers/ui/show/notifier/Show{NotifierName}Component.tsx` (for display)
|
||||
|
||||
4. Update main components to handle the new notifier type:
|
||||
- `EditNotifierComponent.tsx` - add import, validation function, and component rendering
|
||||
- `ShowNotifierComponent.tsx` - add import and component rendering
|
||||
|
||||
5. Make sure everything is working as expected.
|
||||
@@ -1,51 +0,0 @@
|
||||
# How to add new storage to Postgresus (S3, FTP, Google Drive, NAS, etc.)
|
||||
|
||||
## Backend part
|
||||
|
||||
1. Create new model in `backend/internal/features/storages/models/{storage_name}/` folder. Implement `StorageFileSaver` interface from parent folder.
|
||||
- The model should implement `SaveFile(logger *slog.Logger, fileID uuid.UUID, file io.Reader) error`, `GetFile(fileID uuid.UUID) (io.ReadCloser, error)`, `DeleteFile(fileID uuid.UUID) error`, `Validate() error`, and `TestConnection() error` methods
|
||||
- Use UUID primary key as `StorageID` that references the main storages table
|
||||
- Add `TableName() string` method to return the proper table name
|
||||
|
||||
2. Add new storage type to `backend/internal/features/storages/enums.go` in the `StorageType` constants.
|
||||
|
||||
3. Update the main `Storage` model in `backend/internal/features/storages/model.go`:
|
||||
- Add new storage field with GORM foreign key relation
|
||||
- Update `getSpecificStorage()` method to handle the new type
|
||||
- Update `SaveFile()`, `GetFile()`, and `DeleteFile()` methods to route to the new storage
|
||||
- Update `Validate()` method to include new storage validation
|
||||
|
||||
4. If you need to add some .env variables to test, add them in `backend/internal/config/config.go` (so we can use it in tests)
|
||||
|
||||
5. If you need some Docker container to test, add it to `backend/docker-compose.yml.example`. For sensitive data - keep it blank.
|
||||
|
||||
6. If you need some sensitive envs to test in pipeline, message @rostislav_dugin so I can add it to GitHub Actions. For example, Google Drive envs or FTP credentials.
|
||||
|
||||
7. Create new migration in `backend/migrations` folder:
|
||||
- Create table with `storage_id` as UUID primary key
|
||||
- Add foreign key constraint to `storages` table with CASCADE DELETE
|
||||
- Look at existing storage migrations for reference
|
||||
|
||||
8. Update tests in `backend/internal/features/storages/model_test.go` to test new storage
|
||||
|
||||
9. Make sure that all tests are passing.
|
||||
|
||||
## Frontend part
|
||||
|
||||
If you are able to develop only backend - it's fine, message @rostislav_dugin so I can complete UI part.
|
||||
|
||||
1. Add models and api to `frontend/src/entity/storages/models/` folder and update `index.ts` file to include new model exports.
|
||||
- Create TypeScript interface for your storage model
|
||||
- Add validation function if needed
|
||||
|
||||
2. Upload an SVG icon to `public/icons/storages/`, update `src/entity/storages/models/getStorageLogoFromType.ts` to return new icon path, update `src/entity/storages/models/StorageType.ts` to include new type, and update `src/entity/storages/models/getStorageNameFromType.ts` to return new name.
|
||||
|
||||
3. Add UI components to manage your storage:
|
||||
- `src/features/storages/ui/edit/storages/Edit{StorageName}Component.tsx` (for editing)
|
||||
- `src/features/storages/ui/show/storages/Show{StorageName}Component.tsx` (for display)
|
||||
|
||||
4. Update main components to handle the new storage type:
|
||||
- `EditStorageComponent.tsx` - add import and component rendering
|
||||
- `ShowStorageComponent.tsx` - add import and component rendering
|
||||
|
||||
5. Make sure everything is working as expected.
|
||||
@@ -1,4 +1,5 @@
|
||||
export enum PostgresqlVersion {
|
||||
PostgresqlVersion12 = '12',
|
||||
PostgresqlVersion13 = '13',
|
||||
PostgresqlVersion14 = '14',
|
||||
PostgresqlVersion15 = '15',
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FormValidator } from '../../../../shared/lib';
|
||||
import type { EmailNotifier } from './EmailNotifier';
|
||||
|
||||
export const validateEmailNotifier = (isCreate: boolean, notifier: EmailNotifier): boolean => {
|
||||
if (!notifier.targetEmail) {
|
||||
if (!notifier.targetEmail || !FormValidator.isValidEmail(notifier.targetEmail)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -17,5 +18,9 @@ export const validateEmailNotifier = (isCreate: boolean, notifier: EmailNotifier
|
||||
return false;
|
||||
}
|
||||
|
||||
if (notifier.smtpUser && !FormValidator.isValidEmail(notifier.smtpUser)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -175,6 +175,7 @@ export const BackupsComponent = ({ database, isCanManageDBs, scrollContainerRef
|
||||
|
||||
try {
|
||||
await backupsApi.makeBackup(database.id);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
setCurrentLimit(BACKUPS_PAGE_SIZE);
|
||||
setHasMore(true);
|
||||
await loadBackups(BACKUPS_PAGE_SIZE);
|
||||
|
||||
@@ -128,6 +128,7 @@ export const EditDatabaseSpecificDataComponent = ({
|
||||
className="max-w-[200px] grow"
|
||||
placeholder="Select PG version"
|
||||
options={[
|
||||
{ label: '12', value: PostgresqlVersion.PostgresqlVersion12 },
|
||||
{ label: '13', value: PostgresqlVersion.PostgresqlVersion13 },
|
||||
{ label: '14', value: PostgresqlVersion.PostgresqlVersion14 },
|
||||
{ label: '15', value: PostgresqlVersion.PostgresqlVersion15 },
|
||||
|
||||
@@ -5,6 +5,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const postgresqlVersionLabels = {
|
||||
[PostgresqlVersion.PostgresqlVersion12]: '12',
|
||||
[PostgresqlVersion.PostgresqlVersion13]: '13',
|
||||
[PostgresqlVersion.PostgresqlVersion14]: '14',
|
||||
[PostgresqlVersion.PostgresqlVersion15]: '15',
|
||||
|
||||
@@ -2,6 +2,7 @@ import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Input, Tooltip } from 'antd';
|
||||
|
||||
import type { Notifier } from '../../../../../entity/notifiers';
|
||||
import { FormValidator } from '../../../../../shared/lib';
|
||||
|
||||
interface Props {
|
||||
notifier: Notifier;
|
||||
@@ -31,6 +32,12 @@ export function EditEmailNotifierComponent({ notifier, setNotifier, setIsUnsaved
|
||||
size="small"
|
||||
className="w-full max-w-[250px]"
|
||||
placeholder="example@gmail.com"
|
||||
status={
|
||||
notifier?.emailNotifier?.targetEmail &&
|
||||
!FormValidator.isValidEmail(notifier.emailNotifier.targetEmail)
|
||||
? 'error'
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
<Tooltip className="cursor-pointer" title="The email where you want to receive the message">
|
||||
@@ -102,6 +109,12 @@ export function EditEmailNotifierComponent({ notifier, setNotifier, setIsUnsaved
|
||||
size="small"
|
||||
className="w-full max-w-[250px]"
|
||||
placeholder="user@gmail.com"
|
||||
status={
|
||||
notifier?.emailNotifier?.smtpUser &&
|
||||
!FormValidator.isValidEmail(notifier.emailNotifier.smtpUser)
|
||||
? 'error'
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export function EditSlackNotifierComponent({ notifier, setNotifier, setIsUnsaved
|
||||
<div className="mb-1 ml-[130px] max-w-[200px]" style={{ lineHeight: 1 }}>
|
||||
<a
|
||||
className="text-xs !text-blue-600"
|
||||
href="https://postgresus.com/notifier-slack"
|
||||
href="https://postgresus.com/notifiers/slack"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function EditTeamsNotifierComponent({ notifier, setNotifier, setIsUnsaved
|
||||
<div className="mb-1 ml-[130px] max-w-[200px]" style={{ lineHeight: 1 }}>
|
||||
<a
|
||||
className="text-xs !text-blue-600"
|
||||
href="https://postgresus.com/notifier-teams"
|
||||
href="https://postgresus.com/notifiers/teams"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
||||
@@ -212,7 +212,7 @@ export function SettingsComponent({ contentHeight }: Props) {
|
||||
<div className="mt-3 text-sm text-gray-500">
|
||||
Read more about settings you can{' '}
|
||||
<a
|
||||
href="https://postgresus.com/settings"
|
||||
href="https://postgresus.com/access-management/#global-settings"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="!text-blue-600"
|
||||
@@ -230,23 +230,18 @@ export function SettingsComponent({ contentHeight }: Props) {
|
||||
<code
|
||||
className="flex-1 cursor-pointer transition-colors select-all hover:text-blue-600"
|
||||
onClick={() => {
|
||||
window.open(
|
||||
`${getApplicationServer()}/api/v1/downdetect/is-available`,
|
||||
'_blank',
|
||||
);
|
||||
window.open(`${getApplicationServer()}/api/v1/system/health`, '_blank');
|
||||
}}
|
||||
title="Click to open in new tab"
|
||||
>
|
||||
{getApplicationServer()}/api/v1/downdetect/is-available
|
||||
{getApplicationServer()}/api/v1/system/health
|
||||
</code>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
className="ml-2 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${getApplicationServer()}/api/v1/downdetect/is-available`,
|
||||
);
|
||||
navigator.clipboard.writeText(`${getApplicationServer()}/api/v1/system/health`);
|
||||
message.success('Health-check endpoint copied to clipboard');
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -40,7 +40,7 @@ export function EditGoogleDriveStorageComponent({ storage, setStorage, setIsUnsa
|
||||
<div className="min-w-[110px]" />
|
||||
|
||||
<div className="text-xs text-blue-600">
|
||||
<a href="https://postgresus.com/google-drive-storage" target="_blank" rel="noreferrer">
|
||||
<a href="https://postgresus.com/storages/google-drive" target="_blank" rel="noreferrer">
|
||||
How to connect Google Drive?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ export function EditS3StorageComponent({ storage, setStorage, setIsUnsaved }: Pr
|
||||
<div className="min-w-[110px]" />
|
||||
|
||||
<div className="text-xs text-blue-600">
|
||||
<a href="https://postgresus.com/cloudflare-r2-storage" target="_blank" rel="noreferrer">
|
||||
<a href="https://postgresus.com/storages/cloudflare-r2" target="_blank" rel="noreferrer">
|
||||
How to use with Cloudflare R2?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user