Compare commits

...

4 Commits

Author SHA1 Message Date
Rostislav Dugin
14700130b7 FIX (email): Add login auth in case if plain fails 2025-12-01 23:16:54 +03:00
Rostislav Dugin
06282bb435 FIX (connection): Avoid usage of prepare statements to get rid of problem with PgBounder 2025-11-30 20:50:25 +03:00
Rostislav Dugin
a3b263bbac FIX (installation): Fix installation on Debian 2025-11-30 20:25:28 +03:00
Rostislav Dugin
a956dccf7c FIX (whitelist): Show hint about Postgresus whitelist in case of connection failure 2025-11-28 23:59:20 +03:00
6 changed files with 139 additions and 30 deletions

View File

@@ -593,7 +593,8 @@ func buildConnectionStringForDB(p *PostgresqlDatabase, dbName string, password s
sslMode = "require"
}
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
return fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s default_query_exec_mode=simple_protocol",
p.Host,
p.Port,
p.Username,

View File

@@ -0,0 +1,28 @@
package email_notifier
import (
"errors"
"net/smtp"
)
type loginAuth struct {
username, password string
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("unknown LOGIN challenge: " + string(fromServer))
}
}
return nil, nil
}

View File

@@ -132,9 +132,8 @@ func (e *EmailNotifier) Send(
// Set up authentication only if credentials are provided
if isAuthRequired {
auth := smtp.PlainAuth("", e.SMTPUser, smtpPassword, e.SMTPHost)
if err := client.Auth(auth); err != nil {
return fmt.Errorf("SMTP authentication failed: %w", err)
if err := e.authenticate(client, smtpPassword); err != nil {
return err
}
}
@@ -195,9 +194,8 @@ func (e *EmailNotifier) Send(
// Authenticate only if credentials are provided
if isAuthRequired {
auth := smtp.PlainAuth("", e.SMTPUser, smtpPassword, e.SMTPHost)
if err := client.Auth(auth); err != nil {
return fmt.Errorf("SMTP authentication failed: %w", err)
if err := e.authenticate(client, smtpPassword); err != nil {
return err
}
}
@@ -256,3 +254,19 @@ func (e *EmailNotifier) EncryptSensitiveData(encryptor encryption.FieldEncryptor
}
return nil
}
func (e *EmailNotifier) authenticate(client *smtp.Client, password string) error {
// Try PLAIN auth first (most common)
plainAuth := smtp.PlainAuth("", e.SMTPUser, password, e.SMTPHost)
if err := client.Auth(plainAuth); err == nil {
return nil
}
// If PLAIN fails, try LOGIN auth (required by Office 365 and some providers)
loginAuth := &loginAuth{e.SMTPUser, password}
if err := client.Auth(loginAuth); err != nil {
return fmt.Errorf("SMTP authentication failed: %w", err)
}
return nil
}

View File

@@ -48,10 +48,12 @@ export const EditDatabaseSpecificDataComponent = ({
const [isConnectionTested, setIsConnectionTested] = useState(false);
const [isTestingConnection, setIsTestingConnection] = useState(false);
const [isConnectionFailed, setIsConnectionFailed] = useState(false);
const testConnection = async () => {
if (!editingDatabase) return;
setIsTestingConnection(true);
setIsConnectionFailed(false);
try {
await databaseApi.testDatabaseConnectionDirect(editingDatabase);
@@ -61,6 +63,7 @@ export const EditDatabaseSpecificDataComponent = ({
description: 'You can continue with the next step',
});
} catch (e) {
setIsConnectionFailed(true);
alert((e as Error).message);
}
@@ -89,6 +92,7 @@ export const EditDatabaseSpecificDataComponent = ({
setIsSaving(false);
setIsConnectionTested(false);
setIsTestingConnection(false);
setIsConnectionFailed(false);
setEditingDatabase({ ...database });
}, [database]);
@@ -327,6 +331,13 @@ export const EditDatabaseSpecificDataComponent = ({
</Button>
)}
</div>
{isConnectionFailed && (
<div className="mt-3 text-sm text-gray-500 dark:text-gray-400">
If your database uses IP whitelist, make sure Postgresus server IP is added to the allowed
list.
</div>
)}
</div>
);
};

View File

@@ -217,7 +217,7 @@ export function EditWebhookNotifierComponent({ notifier, setNotifier, setUnsaved
)}
{notifier?.webhookNotifier?.webhookMethod === WebhookMethod.POST && (
<div className="rounded bg-gray-100 p-2 px-3 font-mono text-sm break-all whitespace-pre-line dark:bg-gray-800">
<div className="rounded bg-gray-100 p-2 px-3 font-mono text-sm break-words whitespace-pre-wrap dark:bg-gray-800">
<div className="font-semibold text-blue-600 dark:text-blue-400">
POST {notifier?.webhookNotifier?.webhookUrl}
</div>
@@ -230,7 +230,7 @@ export function EditWebhookNotifierComponent({ notifier, setNotifier, setUnsaved
.map((h) => `\n${h.key}: ${h.value}`)
.join('')}
</div>
<div className="mt-2 whitespace-pre">
<div className="mt-2 break-words whitespace-pre-wrap">
{notifier?.webhookNotifier?.bodyTemplate
? notifier.webhookNotifier.bodyTemplate
.replace(
@@ -242,8 +242,8 @@ export function EditWebhookNotifierComponent({ notifier, setNotifier, setUnsaved
'Backup completed successfully in 1m 23s.\\nCompressed backup size: 256.00 MB',
)
: `{
"heading": "✅ Backup completed for database \\"my-database\\" (workspace \\"My workspace\\")",
"message": "Backup completed successfully in 1m 23s.\\nCompressed backup size: 256.00 MB"
"heading": "✅ Backup completed for database "my-database" (workspace "My workspace")",
"message": "Backup completed successfully in 1m 23s. Compressed backup size: 256.00 MB"
}`}
</div>
</div>

View File

@@ -1,5 +1,7 @@
#!/bin/bash
set -e # Exit on any error
# Check if script is run as root
if [ "$(id -u)" -ne 0 ]; then
echo "Error: This script must be run as root (sudo ./install-postgresus.sh)" >&2
@@ -27,39 +29,88 @@ else
log "Directory already exists: $INSTALL_DIR"
fi
# Detect OS
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
VERSION_CODENAME=${VERSION_CODENAME:-}
else
log "ERROR: Cannot detect OS. /etc/os-release not found."
exit 1
fi
}
# Check if Docker is installed
if ! command -v docker &> /dev/null; then
log "Docker not found. Installing Docker..."
# Install Docker
detect_os
log "Detected OS: $OS, Codename: $VERSION_CODENAME"
# Install prerequisites
apt-get update
apt-get remove -y docker docker-engine docker.io containerd runc
apt-get install -y ca-certificates curl gnupg lsb-release
mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get install -y ca-certificates curl gnupg
# Set up Docker repository
install -m 0755 -d /etc/apt/keyrings
# Determine Docker repo URL based on OS
case "$OS" in
ubuntu)
DOCKER_URL="https://download.docker.com/linux/ubuntu"
# Fallback for unsupported versions
case "$VERSION_CODENAME" in
plucky|oracular) VERSION_CODENAME="noble" ;; # Ubuntu 25.x -> 24.04
esac
;;
debian)
DOCKER_URL="https://download.docker.com/linux/debian"
# Fallback for unsupported versions
case "$VERSION_CODENAME" in
trixie|forky) VERSION_CODENAME="bookworm" ;; # Debian 13/14 -> 12
esac
;;
*)
log "ERROR: Unsupported OS: $OS. Please install Docker manually."
exit 1
;;
esac
log "Using Docker repository: $DOCKER_URL with codename: $VERSION_CODENAME"
# Download and add Docker GPG key (no sudo needed - already root)
curl -fsSL "$DOCKER_URL/gpg" | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# Add Docker repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] $DOCKER_URL $VERSION_CODENAME stable" > /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Verify Docker installation
if ! command -v docker &> /dev/null; then
log "ERROR: Docker installation failed!"
exit 1
fi
log "Docker installed successfully"
else
log "Docker already installed"
fi
# Check if docker-compose is installed
if ! command -v docker-compose &> /dev/null && ! command -v docker compose &> /dev/null; then
log "Docker Compose not found. Installing Docker Compose..."
apt-get update
apt-get install -y docker-compose-plugin
log "Docker Compose installed successfully"
# Check if docker compose is available
if ! docker compose version &> /dev/null; then
log "ERROR: Docker Compose plugin not available!"
exit 1
else
log "Docker Compose already installed"
log "Docker Compose available"
fi
# Write docker-compose.yml
log "Writing docker-compose.yml to $INSTALL_DIR"
cat > "$INSTALL_DIR/docker-compose.yml" << 'EOF'
version: "3"
services:
postgresus:
container_name: postgresus
@@ -75,11 +126,15 @@ log "docker-compose.yml created successfully"
# Start PostgresUS
log "Starting PostgresUS..."
cd "$INSTALL_DIR"
docker compose up -d
log "PostgresUS started successfully"
if docker compose up -d; then
log "PostgresUS started successfully"
else
log "ERROR: Failed to start PostgresUS!"
exit 1
fi
log "Postgresus installation completed successfully!"
log "-------------------------------------------"
log "To launch:"
log "> cd $INSTALL_DIR && docker compose up -d"
log "Access Postgresus at: http://localhost:4005"
log "Access Postgresus at: http://localhost:4005"