This document details how Grazr manages multiple versions and instances of PostgreSQL, including the bundling process, configuration structure, and the role of postgres_manager.py. It's intended for contributors who need to understand or work on PostgreSQL-related features.
- Overview of PostgreSQL in Grazr
- PostgreSQL Bundling (
bundle_postgres.sh) - Configuration (
config.pyfor PostgreSQL) - PostgreSQL Manager (
postgres_manager.py)- Core Responsibilities
- Instance Path Resolution (
_get_instance_paths) - Instance Configuration (
_ensure_instance_config_files)postgresql.confpg_hba.conf
- Data Directory Initialization (
_ensure_instance_datadir) - Process Control via
pg_ctl(start_postgres,stop_postgres) - Status Checking (
get_postgres_instance_status,get_postgres_status) - Version Retrieval (
get_postgres_version)
- Interaction with Other Components
- Troubleshooting PostgreSQL Instances
- Contributing to PostgreSQL Management
Grazr aims to provide robust support for running local PostgreSQL instances, allowing users to select different major versions (e.g., 16, 15, 14) and manage separate instances, each with its own data, configuration, and port.
- Multiple Versions: Achieved by bundling specific PostgreSQL versions compiled from source. Each version resides in its own subdirectory within
~/.local/share/grazr/bundles/postgres/. - Multiple Instances: Users can create multiple instances of a given PostgreSQL version (or different versions). Each instance will have:
- A unique ID.
- A dedicated data directory (e.g.,
~/.local/share/grazr/postgres_data/{instance_id}/). - A dedicated configuration directory (e.g.,
~/.config/grazr/postgres/{instance_id}/) containing itspostgresql.confandpg_hba.conf. - A unique port.
- An instance-specific log file and socket directory.
The script packaging/bundling/bundle_postgres.sh is used to download, compile, and install specific versions of PostgreSQL into Grazr's bundle directory.
- Automates the fetching of PostgreSQL source code for a given version.
- Compiles PostgreSQL with standard options suitable for local development (including SSL support).
- Installs the compiled binaries and support files into a versioned subdirectory under
config.POSTGRES_BUNDLES_DIR.
The script takes a full PostgreSQL version string as an argument (e.g., 16.2, 15.5). This version string is used to determine the download URL and the final bundle path (e.g., ~/.local/share/grazr/bundles/postgres/16.2/).
Compiling PostgreSQL requires gcc, make, and development libraries such as libreadline-dev, zlib1g-dev, and libssl-dev (for --with-openssl). The script includes a prerequisite check and may list common dependencies for Ubuntu. Contributors must ensure these are installed on the system where the bundling script is run.
The script downloads the source tarball (e.g., postgresql-16.2.tar.gz) from https://ftp.postgresql.org/pub/source/.
The script runs ./configure from the extracted source directory with the following key options:
--prefix=\${TARGET_INSTALL_PREFIX}: Where\${TARGET_INSTALL_PREFIX}is~/.local/share/grazr/bundles/postgres/VERSION_FULL/. This ensures all installed files for that version (binaries, libraries, share files) go into this specific versioned bundle directory.--with-openssl: Enables SSL support.--with-readline: Enables readline support forpsql.--with-zlib: Enables zlib compression support.- Other options like
--enable-debug(for development builds) or--with-icu(for ICU collation support, requiringlibicu-dev) can be added if necessary.
make -j$(nproc): Compiles the source code.make install: Installs the compiled PostgreSQL into the directory specified by--prefix. This creates subdirectories likebin/,lib/,share/,include/within the versioned bundle directory.
A successful run of bundle_postgres.sh <VERSION> will result in a directory structure like:
~/.local/share/grazr/bundles/postgres/
└── <VERSION>/ (e.g., 16.2)
├── bin/ (postgres, pg_ctl, initdb, psql, etc.)
├── include/
├── lib/ (shared libraries, e.g., libpq.so)
└── share/ (documentation, locale data, extensions)
The grazr/core/config.py file defines how Grazr understands and locates PostgreSQL versions and instances.
For each major PostgreSQL version Grazr supports (e.g., 16, 15), there's an entry in config.AVAILABLE_BUNDLED_SERVICES:
"postgres16": {
"display_name": "PostgreSQL 16",
"category": "Database",
"service_group": "postgres",
"major_version": "16",
"bundle_version_full": "16.2", # Crucial: Exact version string of the bundled files
"process_id_template": "internal-postgres-16-{instance_id}",
"default_port": 5432,
"binary_name": "postgres",
"initdb_name": "initdb",
"pg_ctl_name": "pg_ctl",
"psql_name": "psql",
"manager_module": "postgres_manager",
"doc_url": "https://www.postgresql.org/docs/16/",
# Names of template constants defined below in config.py
"log_file_template_name": "INTERNAL_POSTGRES_INSTANCE_LOG_TEMPLATE",
"pid_file_template_name": "INTERNAL_POSTGRES_INSTANCE_PID_TEMPLATE",
"data_dir_template_name": "INTERNAL_POSTGRES_INSTANCE_DATA_DIR_TEMPLATE",
"config_dir_template_name": "INTERNAL_POSTGRES_INSTANCE_CONFIG_DIR_TEMPLATE",
"socket_dir_template_name": "INTERNAL_POSTGRES_INSTANCE_SOCK_DIR_TEMPLATE",
"bundle_path_template_name": "POSTGRES_BUNDLE_PATH_TEMPLATE",
"binary_path_template_name": "POSTGRES_BINARY_TEMPLATE", # For main 'postgres' binary
"lib_dir_template_name": "POSTGRES_LIB_DIR_TEMPLATE",
"share_dir_template_name": "POSTGRES_SHARE_DIR_TEMPLATE",
# ...
},major_version: Used for display or grouping (e.g., "16").bundle_version_full: Specifies the exact version string (e.g., "16.2") that matches the directory created bybundle_postgres.sh. This is used to find the correct bundle.process_id_template: Used bypostgres_manager.pyto create a unique ID forprocess_manager.pyif it were to directly manage thepostgresprocess (currently,pg_ctlmanages the daemon).binary_name,initdb_name, etc.: The names of the executables within the bundle'sbin/directory.*_template_name: These keys refer to the names of path template constants also defined inconfig.py.
config.py defines string templates for all paths related to PostgreSQL instances. These templates use placeholders like {version_full} and {instance_id}.
POSTGRES_BUNDLES_DIR = BUNDLES_DIR / 'postgres'
POSTGRES_BINARY_DIR_NAME = 'bin'
POSTGRES_BUNDLE_PATH_TEMPLATE = str(POSTGRES_BUNDLES_DIR / "{version_full}")
POSTGRES_BINARY_TEMPLATE = str(Path(POSTGRES_BUNDLE_PATH_TEMPLATE) / POSTGRES_BINARY_DIR_NAME / "{binary_name}")
POSTGRES_LIB_DIR_TEMPLATE = str(Path(POSTGRES_BUNDLE_PATH_TEMPLATE) / 'lib')
POSTGRES_SHARE_DIR_TEMPLATE = str(Path(POSTGRES_BUNDLE_PATH_TEMPLATE) / 'share')
INTERNAL_POSTGRES_INSTANCE_CONFIG_DIR_TEMPLATE = str(CONFIG_DIR / 'postgres' / '{instance_id}')
INTERNAL_POSTGRES_INSTANCE_CONF_FILE_TEMPLATE = str(Path(INTERNAL_POSTGRES_INSTANCE_CONFIG_DIR_TEMPLATE) / 'postgresql.conf')
INTERNAL_POSTGRES_INSTANCE_HBA_FILE_TEMPLATE = str(Path(INTERNAL_POSTGRES_INSTANCE_CONFIG_DIR_TEMPLATE) / 'pg_hba.conf')
INTERNAL_POSTGRES_INSTANCE_DATA_DIR_TEMPLATE = str(DATA_DIR / 'postgres_data' / '{instance_id}')
INTERNAL_POSTGRES_INSTANCE_PID_TEMPLATE = str(Path(INTERNAL_POSTGRES_INSTANCE_DATA_DIR_TEMPLATE) / "postmaster.pid")
INTERNAL_POSTGRES_INSTANCE_LOG_TEMPLATE = str(LOG_DIR / 'postgres-{instance_id}.log')
INTERNAL_POSTGRES_INSTANCE_SOCK_DIR_TEMPLATE = str(RUN_DIR / 'postgres_sock_{instance_id}') The postgres_manager.py uses these templates by formatting them with the specific bundle_version_full (from AVAILABLE_BUNDLED_SERVICES) and the instance_id (from the service instance configuration).
The refactored grazr/managers/postgres_manager.py is central to managing PostgreSQL instances.
- Resolving all necessary paths for a given PostgreSQL instance (binaries, data, config, logs, PID, socket).
- Ensuring an instance's data directory is initialized (
initdb). - Ensuring instance-specific configuration files (
postgresql.conf,pg_hba.conf) are created and correctly populated. - Starting and stopping PostgreSQL instances using the appropriate versioned
pg_ctlcommand. - Checking the status of instances.
- Retrieving the version of a bundled PostgreSQL installation.
This internal helper function is critical.
def _get_instance_paths(service_instance_config: dict):
# ... (gets instance_id, service_type from service_instance_config)
# ... (gets service_def from config.AVAILABLE_BUNDLED_SERVICES using service_type)
# ... (gets bundle_version_full, binary_name, etc. from service_def)
# ... (formats all path templates from config.py using bundle_version_full and instance_id)
return paths_dictionary It takes the service_instance_config (which contains the unique instance_id and service_type like "postgres16") and returns a dictionary of fully resolved Path objects for that instance.
Called before starting an instance:
- Creates the instance-specific configuration directory (e.g.,
~/.config/grazr/postgres/{instance_id}/). - Calls
_get_default_postgres_config_content()and_get_default_pg_hba_content()to generate basic configurations. - Writes
postgresql.conffor the instance. Key settings include:listen_addresses = '127.0.0.1, ::1'port = {port_to_use}(fromservice_instance_config)unix_socket_directories = '/path/to/instance/sock_dir'hba_file = '/path/to/instance/pg_hba.conf'logging_collector = on(to enable log file redirection viapg_ctl -l)
- Writes
pg_hba.conffor the instance, typically allowingtrustauthentication for the current user onlocal(Unix socket) connections.
Called before starting an instance if the data directory doesn't exist or isn't initialized:
- Creates the instance-specific data directory (e.g.,
~/.local/share/grazr/postgres_data/{instance_id}/) with0700permissions. - Uses the version-specific
initdbbinary (path resolved via_get_instance_paths). - Runs
initdbwith:-D /path/to/instance/data_dir-U {current_username}(orconfig.POSTGRES_DEFAULT_USER_VAR)-A trust(for easy local development)-E UTF8-L /path/to/bundle/VERSION_FULL/share(for locale data, etc.)
- Sets
LD_LIBRARY_PATHto include the bundle'slibdirectory when runninginitdb.
These functions operate on a specific service_instance_config.
start_postgres(service_instance_config):- Gets instance paths.
- Ensures config files and data directory are set up.
- Constructs the
pg_ctl startcommand:/path/to/bundle/VERSION_FULL/bin/pg_ctl start \ -D /path/to/instance/data_dir \ -l /path/to/instance/log_file \ -s # Silent mode for pg_ctl -w # Wait for start -t 60 # Timeout -o "-c config_file='/path/to/instance/postgresql.conf' \ -c hba_file='/path/to/instance/pg_hba.conf' \ -c unix_socket_directories='/path/to/instance/sock_dir'" - Sets
LD_LIBRARY_PATHto include the bundle'slibdirectory. - Runs the command using
subprocess.run(). - Checks status after
pg_ctlreports success.
stop_postgres(service_instance_config):- Gets instance paths.
- Constructs the
pg_ctl stopcommand:/path/to/bundle/VERSION_FULL/bin/pg_ctl stop \ -D /path/to/instance/data_dir \ -m fast # Shutdown mode (smart, fast, immediate) -s # Silent -w # Wait -t 30 # Timeout - Sets
LD_LIBRARY_PATH. - Runs the command using
subprocess.run().
get_postgres_instance_status(instance_paths):- Checks for the existence and content of
postmaster.pidin the instance's data directory using local helpers_local_read_pid_from_fileand_local_check_pid_running. - As a fallback, can use
pg_ctl -D /path/to/instance/data_dir status.
- Checks for the existence and content of
get_postgres_status(instance_id): Public function called byMainWindow. It loads theservice_instance_configusingget_service_config_by_id(instance_id)and then callsget_postgres_instance_status().
- Takes
service_instance_config. - Gets the path to the
postgresbinary for the instance'sbundle_version_fullusing_get_instance_paths(). - Runs
/path/to/bundle/VERSION_FULL/bin/postgres --version. - Parses the output to get the version string.
- Sets
LD_LIBRARY_PATHwhen running the command.
services_config_manager.py:- Stores configured PostgreSQL instances in
services.json. Each entry includes:id: Unique instance ID (generated UUID).service_type: e.g., "postgres16", "postgres15".name: User-defined display name for the instance.port: Configured port for this instance.autostart: Boolean.
- Stores configured PostgreSQL instances in
worker.py:- The
doWorkmethod has task handlers forstart_postgresandstop_postgres. - These handlers now receive an
instance_idin theirdatadictionary. - They call
get_service_config_by_id(instance_id)to get the full configuration for the instance. - They then pass this
service_instance_configdictionary to the refactoredpostgres_manager.start_postgres()orstop_postgres()functions.
- The
ServicesPage.py&AddServiceDialog.py:AddServiceDialog: Lists available PostgreSQL service types (e.g., "PostgreSQL 16", "PostgreSQL 15") based onconfig.AVAILABLE_BUNDLED_SERVICES. When a user adds one, it saves theservice_type, user-chosenname, andport.services_config_manager.add_configured_service()generates theinstance_id.ServicesPage:refresh_data(): Loads all configured services. For each PostgreSQL instance, it creates aServiceItemWidget. The key forself.service_widgetsfor these instances is their uniqueinstance_id.- Action signals (
actionClicked,settingsClicked) fromServiceItemWidgetpass theinstance_id. _trigger_single_refresh()callsMainWindow.refresh_postgres_instance_status_on_page(instance_id).
initdbFails:- Check build dependencies for PostgreSQL on the bundling system.
- Ensure the data directory path is writable by the user running Grazr and has
0700permissions beforeinitdbis called. - Examine
initdbstdout/stderr logged bypostgres_manager.py. - Ensure
LD_LIBRARY_PATHis correctly set to the bundle'slibdirectory wheninitdbruns.
pg_ctl startFails (Code 1):- Check the PostgreSQL instance log file: This is the most important step. The log file path is
~/.config/grazr/logs/postgres-{instance_id}.log. However, iflogging_collector = oninpostgresql.conf, the actual detailed startup logs will be in thelogsubdirectory of the instance's data directory (e.g.,~/.local/share/grazr/postgres_data/{instance_id}/log/). Look forFATALorERRORmessages. - Port Conflict: Ensure the port configured for the instance is not already in use (
sudo ss -tulnp | grep ':PORT'). - Permissions: Verify ownership and permissions (
0700) for the instance data directory. The user running Grazr must own it. - Socket Directory: Ensure the
unix_socket_directoriespath specified inpostgresql.conf(e.g.,~/.config/grazr/run/postgres_sock_{instance_id}/) is writable by the user. - Configuration Errors: Check the instance-specific
postgresql.confandpg_hba.conffor syntax errors or incorrect settings.pg_ctlmight log messages about this. LD_LIBRARY_PATH: Ensure it's set correctly whenpg_ctlis invoked, pointing to the bundle'slibdirectory.
- Check the PostgreSQL instance log file: This is the most important step. The log file path is
- Connection Issues (
psql, UI, applications):- Verify the PostgreSQL instance is running and on the correct port.
- Check
pg_hba.conffor the instance to ensure it allows connections fromlocalhostfor the user (e.g.,local all your_username trust). - Ensure applications are trying to connect to the correct host (
127.0.0.1), port, and Unix socket path (if applicable).
- Improving the default
postgresql.confandpg_hba.conftemplates for local development. - Adding UI features to manage users, databases, and view logs for specific instances.
- Enhancing the
bundle_postgres.shscript (e.g., more configurable compile options, support for different architectures). - Implementing backup/restore functionality for instances.
- More robust error handling and reporting in
postgres_manager.py.