├── console ├── service │ ├── VERSION │ ├── pkg │ │ ├── tracer │ │ │ └── cid.go │ │ └── patroni │ │ │ └── models.go │ ├── internal │ │ ├── controllers │ │ │ ├── errors.go │ │ │ ├── utils.go │ │ │ ├── secret │ │ │ │ └── delete_secret.go │ │ │ └── cluster │ │ │ │ └── delete_cluster.go │ │ ├── xdocker │ │ │ ├── images.go │ │ │ └── imanager.go │ │ ├── watcher │ │ │ ├── consts.go │ │ │ └── models.go │ │ ├── storage │ │ │ ├── cluster_flags_test.go │ │ │ └── cluster_flags.go │ │ └── convert │ │ │ ├── postgres_versions.go │ │ │ ├── database_extensions.go │ │ │ ├── settings.go │ │ │ └── projects.go │ ├── env.sh │ ├── migrations │ │ ├── migrate.go │ │ └── goose_logger.go │ ├── Dockerfile │ ├── middleware │ │ └── cid.go │ └── Makefile ├── .env.example ├── ui │ ├── src │ │ ├── entities │ │ │ ├── cluster │ │ │ │ ├── storage-block │ │ │ │ │ ├── lib │ │ │ │ │ │ └── functions.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model │ │ │ │ │ │ └── const.ts │ │ │ │ ├── cluster-info │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model │ │ │ │ │ │ └── types.ts │ │ │ │ ├── connection-info │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── model │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── ui │ │ │ │ │ │ └── ConnectionInfoRowConteiner.tsx │ │ │ │ │ └── assets │ │ │ │ │ │ └── eyeIcon.svg │ │ │ │ ├── ssh-key-block │ │ │ │ │ ├── model │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── vip-address-block │ │ │ │ │ └── index.ts │ │ │ │ ├── expert-mode │ │ │ │ │ ├── network-block │ │ │ │ │ │ └── model │ │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ │ └── validation.ts │ │ │ │ │ ├── data-directory-block │ │ │ │ │ │ └── model │ │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── kernel-parameters-block │ │ │ │ │ │ └── model │ │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ │ └── validation.ts │ │ │ │ │ ├── extensions-block │ │ │ │ │ │ ├── assets │ │ │ │ │ │ │ ├── citus.png │ │ │ │ │ │ │ ├── pgaudit.png │ │ │ │ │ │ │ ├── postgis.png │ │ │ │ │ │ │ ├── pgrouting.png │ │ │ │ │ │ │ └── timescaledb.png │ │ │ │ │ │ ├── model │ │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ ├── ui │ │ │ │ │ │ │ └── styles.css │ │ │ │ │ │ └── lib │ │ │ │ │ │ │ └── functions.ts │ │ │ │ │ ├── postgres-parameters-block │ │ │ │ │ │ └── model │ │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ │ └── validation.ts │ │ │ │ │ ├── additional-settings-block │ │ │ │ │ │ └── model │ │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ │ └── validation.ts │ │ │ │ │ ├── backups-block │ │ │ │ │ │ └── model │ │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── databases-block │ │ │ │ │ │ └── model │ │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ │ ├── validation.ts │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── dcs-block │ │ │ │ │ │ └── model │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── connection-pools-block │ │ │ │ │ │ └── model │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ └── validation.ts │ │ │ │ ├── cloud-region-block │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── model │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── lib │ │ │ │ │ │ └── hooks.tsx │ │ │ │ ├── instances-block │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ └── types.ts │ │ │ │ ├── load-balancers-block │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ └── types.ts │ │ │ │ ├── database-servers-block │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ └── types.ts │ │ │ │ ├── description-block │ │ │ │ │ └── index.ts │ │ │ │ ├── instances-amount-block │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model │ │ │ │ │ │ └── const.ts │ │ │ │ ├── postgres-version-block │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model │ │ │ │ │ │ └── types.ts │ │ │ │ ├── providers-block │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── model │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── ui │ │ │ │ │ │ └── ClusterFormCloudProviderBox.tsx │ │ │ │ ├── environment-block │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model │ │ │ │ │ │ └── types.ts │ │ │ │ ├── cluster-name-block │ │ │ │ │ └── index.ts │ │ │ │ ├── cluster-instance-config-box │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model │ │ │ │ │ │ └── types.ts │ │ │ │ └── cluster-name-description-block │ │ │ │ │ └── index.ts │ │ │ ├── sidebar-item │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ │ └── types.ts │ │ │ ├── breadcumb-item │ │ │ │ ├── model │ │ │ │ │ └── types.ts │ │ │ │ ├── index.ts │ │ │ │ └── ui │ │ │ │ │ └── index.tsx │ │ │ ├── secret-form-block │ │ │ │ └── index.ts │ │ │ ├── settings │ │ │ │ └── proxy-block │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model │ │ │ │ │ ├── constants.ts │ │ │ │ │ └── types.ts │ │ │ └── authentification-method-form-block │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ └── constants.ts │ │ ├── features │ │ │ ├── cluster-secret-modal │ │ │ │ ├── model │ │ │ │ │ └── validation.ts │ │ │ │ └── index.ts │ │ │ ├── theme-toggle │ │ │ │ └── index.ts │ │ │ ├── settings-table-row-actions │ │ │ │ ├── model │ │ │ │ │ └── constants.ts │ │ │ │ └── index.ts │ │ │ ├── add-project │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── validation.ts │ │ │ ├── bradcrumbs │ │ │ │ ├── index.ts │ │ │ │ └── hooks │ │ │ │ │ └── useBreadcrumbs.tsx │ │ │ ├── clusters-table-buttons │ │ │ │ ├── model │ │ │ │ │ └── types.ts │ │ │ │ └── index.ts │ │ │ ├── logout-button │ │ │ │ └── index.ts │ │ │ ├── add-secret │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ │ ├── constants.ts │ │ │ │ │ └── types.ts │ │ │ ├── add-environment │ │ │ │ └── index.ts │ │ │ ├── settings-table-buttons │ │ │ │ ├── index.ts │ │ │ │ ├── lib │ │ │ │ │ └── functions.ts │ │ │ │ └── ui │ │ │ │ │ └── index.tsx │ │ │ ├── operations-table-buttons │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ │ ├── types.ts │ │ │ │ │ └── constants.ts │ │ │ ├── clusters-table-row-actions │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ │ └── types.ts │ │ │ ├── pojects-table-row-actions │ │ │ │ └── index.ts │ │ │ ├── operations-table-row-actions │ │ │ │ └── index.ts │ │ │ └── clusters-overview-table-row-actions │ │ │ │ └── index.ts │ │ ├── app │ │ │ ├── layout │ │ │ │ ├── index.ts │ │ │ │ └── ui │ │ │ │ │ └── index.tsx │ │ │ ├── vite-env.d.ts │ │ │ ├── redux │ │ │ │ ├── slices │ │ │ │ │ ├── projectSlice │ │ │ │ │ │ ├── projectSelectors.ts │ │ │ │ │ │ └── projectSlice.ts │ │ │ │ │ └── themeSlice │ │ │ │ │ │ └── themeSelectors.ts │ │ │ │ └── store │ │ │ │ │ └── hooks.ts │ │ │ ├── router │ │ │ │ ├── routerPathsConfig │ │ │ │ │ ├── routerOperationsPathsConfig.ts │ │ │ │ │ ├── routerClustersPathsConfig.ts │ │ │ │ │ ├── routerSettingsPathsConfig.ts │ │ │ │ │ └── index.ts │ │ │ │ └── routerConfig │ │ │ │ │ └── OperationsRoutes.tsx │ │ │ └── main.tsx │ │ ├── pages │ │ │ ├── 404 │ │ │ │ └── index.ts │ │ │ ├── login │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ │ ├── constants.ts │ │ │ │ │ └── types.ts │ │ │ ├── clusters │ │ │ │ ├── index.ts │ │ │ │ └── ui │ │ │ │ │ └── index.tsx │ │ │ ├── settings │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ │ └── constants.ts │ │ │ ├── operations │ │ │ │ ├── index.ts │ │ │ │ └── ui │ │ │ │ │ └── index.tsx │ │ │ ├── add-cluster │ │ │ │ └── index.ts │ │ │ ├── operation-log │ │ │ │ └── index.ts │ │ │ └── overview-cluster │ │ │ │ └── index.ts │ │ ├── widgets │ │ │ ├── main │ │ │ │ ├── index.ts │ │ │ │ └── ui │ │ │ │ │ └── index.tsx │ │ │ ├── sidebar │ │ │ │ └── index.ts │ │ │ ├── header │ │ │ │ └── index.ts │ │ │ ├── cluster-form │ │ │ │ ├── index.ts │ │ │ │ ├── model │ │ │ │ │ └── types.ts │ │ │ │ └── ui │ │ │ │ │ └── ClusterFormLocalMachineFormPart.tsx │ │ │ ├── secrets-table │ │ │ │ ├── index.ts │ │ │ │ ├── model │ │ │ │ │ └── types.ts │ │ │ │ └── lib │ │ │ │ │ └── hooks.tsx │ │ │ ├── settings-form │ │ │ │ └── index.ts │ │ │ ├── clusters-table │ │ │ │ ├── index.ts │ │ │ │ ├── assets │ │ │ │ │ ├── warningIcon.svg │ │ │ │ │ ├── errorIcon.svg │ │ │ │ │ └── correctIcon.svg │ │ │ │ └── model │ │ │ │ │ └── types.ts │ │ │ ├── projects-table │ │ │ │ ├── index.tsx │ │ │ │ ├── model │ │ │ │ │ └── types.ts │ │ │ │ ├── ui │ │ │ │ │ └── ProjectsTableButtons.tsx │ │ │ │ └── lib │ │ │ │ │ └── hooks.tsx │ │ │ ├── cluster-summary │ │ │ │ ├── index.ts │ │ │ │ └── assets │ │ │ │ │ ├── hetznerIcon2.svg │ │ │ │ │ └── digitaloceanIcon.svg │ │ │ ├── operations-table │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ │ └── types.ts │ │ │ ├── environments-table │ │ │ │ ├── index.ts │ │ │ │ ├── ui │ │ │ │ │ └── EnvironmentsTableButtons.tsx │ │ │ │ ├── model │ │ │ │ │ └── types.ts │ │ │ │ └── lib │ │ │ │ │ └── hooks.tsx │ │ │ ├── cluster-overview-table │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ │ └── types.ts │ │ │ └── yaml-editor-form │ │ │ │ ├── model │ │ │ │ ├── types.ts │ │ │ │ └── const.ts │ │ │ │ └── lib │ │ │ │ └── functions.ts │ │ └── shared │ │ │ ├── ui │ │ │ ├── error-box │ │ │ │ ├── model │ │ │ │ │ └── types.ts │ │ │ │ └── ui │ │ │ │ │ └── index.tsx │ │ │ ├── copy-icon │ │ │ │ ├── model │ │ │ │ │ └── types.ts │ │ │ │ ├── index.ts │ │ │ │ ├── assets │ │ │ │ │ └── copyIcon.svg │ │ │ │ └── ui │ │ │ │ │ └── index.tsx │ │ │ ├── spinner │ │ │ │ ├── index.ts │ │ │ │ └── ui │ │ │ │ │ └── index.tsx │ │ │ ├── default-table │ │ │ │ └── index.ts │ │ │ ├── info-card-body │ │ │ │ ├── index.ts │ │ │ │ ├── model │ │ │ │ │ └── types.ts │ │ │ │ └── ui │ │ │ │ │ └── index.tsx │ │ │ ├── slider-box │ │ │ │ ├── index.ts │ │ │ │ ├── lib │ │ │ │ │ └── functions.ts │ │ │ │ └── model │ │ │ │ │ └── types.ts │ │ │ ├── selectable-box │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ │ └── types.ts │ │ │ ├── default-form-buttons │ │ │ │ ├── index.ts │ │ │ │ └── model │ │ │ │ │ └── types.ts │ │ │ └── settings-add-entity │ │ │ │ └── model │ │ │ │ ├── constants.ts │ │ │ │ ├── validation.ts │ │ │ │ └── types.ts │ │ │ ├── assets │ │ │ ├── checkIcon.svg │ │ │ ├── flagIcon.svg │ │ │ ├── HomeOutlinedIcon.svg │ │ │ ├── collapseIcon.svg │ │ │ ├── lanIcon.svg │ │ │ ├── settingsIcon.svg │ │ │ ├── supportIcon.svg │ │ │ ├── storageIcon.svg │ │ │ ├── ramIcon.svg │ │ │ ├── calendarClockICon.svg │ │ │ ├── docsIcon.svg │ │ │ ├── cpuIcon.svg │ │ │ ├── githubIcon.svg │ │ │ └── instanceIcon.svg │ │ │ ├── model │ │ │ ├── types.ts │ │ │ ├── validation.ts │ │ │ └── constants.ts │ │ │ ├── api │ │ │ ├── enhancedSecretsApi.ts │ │ │ └── baseApi.ts │ │ │ └── i18n │ │ │ └── locales │ │ │ └── en │ │ │ ├── validation.json │ │ │ └── operations.json │ ├── .prettierignore │ ├── .env │ ├── .prettierrc │ ├── tsconfig.node.json │ ├── .gitignore │ ├── .env.production │ ├── index.html │ └── .eslintrc.cjs └── db │ ├── pg_hba.conf │ └── migrations │ ├── 20250323121343_2.2.0.sql │ └── 20250927122311_2.4.0.sql ├── automation ├── .dockerignore ├── playbooks │ └── files │ │ └── .gitkeep ├── changelog.yaml ├── roles │ ├── consul │ │ ├── version.txt │ │ ├── vars │ │ │ ├── VMware Photon OS.yml │ │ │ ├── FreeBSD.yml │ │ │ ├── Archlinux.yml │ │ │ ├── Flatcar.yml │ │ │ ├── Solaris.yml │ │ │ ├── Debian.yml │ │ │ ├── Amazon.yml │ │ │ ├── RedHat.yml │ │ │ ├── Darwin.yml │ │ │ └── Windows.yml │ │ ├── meta │ │ │ └── main.yml │ │ ├── templates │ │ │ ├── consul_profile.sh.j2 │ │ │ ├── rsyslogd_00-consul.conf.j2 │ │ │ ├── consul_resolved.conf.j2 │ │ │ ├── configd_50custom.json.j2 │ │ │ ├── syslogng_consul.conf.j2 │ │ │ └── consul_systemd_service.override.j2 │ │ ├── requirements.txt │ │ ├── files │ │ │ └── README.md │ │ ├── handlers │ │ │ ├── restart_consul_mac.yml │ │ │ ├── restart_syslogng.yml │ │ │ ├── restart_rsyslog.yml │ │ │ ├── start_snapshot.yml │ │ │ ├── reload_consul_conf.yml │ │ │ └── start_consul.yml │ │ └── tasks │ │ │ ├── systemd_resolved.yml │ │ │ └── user_group.yml │ ├── firewall │ │ ├── .gitignore │ │ ├── meta │ │ │ └── main.yml │ │ ├── .yamllint │ │ ├── handlers │ │ │ └── main.yml │ │ ├── templates │ │ │ └── firewall.unit.j2 │ │ ├── defaults │ │ │ └── main.yml │ │ └── .travis.yml │ ├── confd │ │ ├── meta │ │ │ └── main.yml │ │ ├── defaults │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ └── templates │ │ │ ├── confd.service.j2 │ │ │ └── haproxy.toml.j2 │ ├── copy │ │ ├── meta │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── cron │ │ ├── meta │ │ │ └── main.yml │ │ └── defaults │ │ │ └── main.yml │ ├── etcd │ │ ├── meta │ │ │ └── main.yml │ │ ├── defaults │ │ │ └── main.yml │ │ └── templates │ │ │ └── etcd.service.j2 │ ├── haproxy │ │ ├── meta │ │ │ └── main.yml │ │ └── handlers │ │ │ └── main.yml │ ├── locales │ │ └── meta │ │ │ └── main.yml │ ├── mount │ │ ├── meta │ │ │ └── main.yml │ │ └── defaults │ │ │ └── main.yml │ ├── netdata │ │ └── meta │ │ │ └── main.yml │ ├── ntp │ │ ├── meta │ │ │ └── main.yml │ │ └── handlers │ │ │ └── main.yml │ ├── patroni │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ ├── pg_hba.yml │ │ │ └── patroni.yml │ │ └── handlers │ │ │ └── main.yml │ ├── pgpass │ │ └── meta │ │ │ └── main.yml │ ├── sudo │ │ ├── meta │ │ │ └── main.yml │ │ └── README.md │ ├── swap │ │ ├── meta │ │ │ └── main.yml │ │ └── README.md │ ├── sysctl │ │ └── meta │ │ │ └── main.yml │ ├── update │ │ ├── meta │ │ │ └── main.yml │ │ └── defaults │ │ │ └── main.yml │ ├── upgrade │ │ ├── meta │ │ │ └── main.yml │ │ └── tasks │ │ │ ├── pgbouncer_resume.yml │ │ │ └── dcs_remove_cluster.yml │ ├── wal_g │ │ ├── meta │ │ │ └── main.yml │ │ ├── templates │ │ │ └── walg.json.j2 │ │ └── defaults │ │ │ └── main.yml │ ├── etc_hosts │ │ ├── meta │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── hostname │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ └── README.md │ ├── io_scheduler │ │ ├── meta │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ └── templates │ │ │ └── io-scheduler.service.j2 │ ├── keepalived │ │ ├── meta │ │ │ └── main.yml │ │ └── defaults │ │ │ └── main.yml │ ├── packages │ │ ├── meta │ │ │ └── main.yml │ │ └── defaults │ │ │ └── main.yml │ ├── pam_limits │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ └── README.md │ ├── pg_probackup │ │ └── meta │ │ │ └── main.yml │ ├── pgbackrest │ │ ├── meta │ │ │ └── main.yml │ │ ├── defaults │ │ │ └── main.yml │ │ ├── templates │ │ │ ├── pgbackrest.server.conf.j2 │ │ │ ├── pgbackrest.server.stanza.conf.j2 │ │ │ └── pgbackrest_bootstrap.sh.j2 │ │ └── tasks │ │ │ └── bootstrap_script.yml │ ├── pgbouncer │ │ ├── meta │ │ │ └── main.yml │ │ └── templates │ │ │ ├── userlist.txt.j2 │ │ │ └── pgbouncer.service.j2 │ ├── pre_checks │ │ ├── meta │ │ │ └── main.yml │ │ └── tasks │ │ │ └── system.yml │ ├── resolv_conf │ │ ├── meta │ │ │ └── main.yml │ │ ├── README.md │ │ └── tasks │ │ │ └── main.yml │ ├── ssh_keys │ │ └── meta │ │ │ └── main.yml │ ├── timezone │ │ ├── meta │ │ │ └── main.yml │ │ ├── README.md │ │ └── tasks │ │ │ └── main.yml │ ├── vip_manager │ │ ├── meta │ │ │ └── main.yml │ │ ├── defaults │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── disable.yml │ │ ├── templates │ │ │ └── vip-manager.service.j2 │ │ └── handlers │ │ │ └── main.yml │ ├── add_repository │ │ └── meta │ │ │ └── main.yml │ ├── authorized_keys │ │ ├── meta │ │ │ └── main.yml │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── cloud_resources │ │ └── meta │ │ │ └── main.yml │ ├── deploy_finish │ │ ├── meta │ │ │ └── main.yml │ │ └── README.md │ ├── postgresql_privs │ │ └── meta │ │ │ └── main.yml │ ├── postgresql_users │ │ └── meta │ │ │ └── main.yml │ ├── tls_certificate │ │ └── meta │ │ │ └── main.yml │ ├── postgresql_databases │ │ └── meta │ │ │ └── main.yml │ ├── postgresql_extensions │ │ └── meta │ │ │ └── main.yml │ ├── postgresql_schemas │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ └── README.md │ ├── transparent_huge_pages │ │ ├── meta │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ └── README.md │ └── bind_address │ │ └── README.md ├── requirements.txt ├── molecule │ ├── default │ │ ├── cleanup.yml │ │ └── prepare.yml │ ├── tests │ │ ├── postgres │ │ │ ├── replication.yml │ │ │ └── postgres.yml │ │ ├── roles │ │ │ ├── confd │ │ │ │ └── main.yml │ │ │ ├── patroni │ │ │ │ └── main.yml │ │ │ ├── swap │ │ │ │ └── main.yml │ │ │ ├── pre-checks │ │ │ │ └── main.yml │ │ │ └── haproxy │ │ │ │ └── main.yml │ │ └── variables │ │ │ └── main.yml │ └── pg_upgrade │ │ └── prepare.yml └── requirements.yml ├── .config ├── python_version.config ├── python │ └── dev │ │ └── requirements.txt ├── .flake8 ├── .yamllint ├── make │ ├── help.mak │ └── formatting.mak └── ansible-lint.yml ├── .prettierignore ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── config.yml └── workflows │ ├── flake8.yml │ ├── yamllint.yml │ ├── ansible-lint.yml │ ├── schedule_pg_debian12.yml │ ├── schedule_pg_debian13.yml │ ├── schedule_pg_ubuntu2204.yml │ ├── schedule_pg_ubuntu2404.yml │ ├── schedule_pg_rockylinux10.yml │ ├── schedule_pg_rockylinux9.yml │ ├── schedule_pg_almalinux9.yml │ ├── schedule_pg_almalinux10.yml │ └── schedule_pg_centosstream9.yml ├── images ├── TypeA.png ├── TypeB.png ├── TypeC.png ├── load_balancing.jpg ├── github-autobase.png ├── pg_cluster_scheme.png ├── autobase_create_cluster_demo.gif └── pg_cluster_scheme.dark_mode.png ├── .gitpod.yml ├── .editorconfig-checker.json ├── .gitignore ├── .prettierrc.json ├── .sql-formatter.json └── .editorconfig /console/service/VERSION: -------------------------------------------------------------------------------- 1 | 2.5.2 2 | -------------------------------------------------------------------------------- /automation/.dockerignore: -------------------------------------------------------------------------------- 1 | .venv/ 2 | -------------------------------------------------------------------------------- /automation/playbooks/files/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.config/python_version.config: -------------------------------------------------------------------------------- 1 | PYTHON_VERSION=3.12 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | console/service 2 | console/ui 3 | *.sql 4 | -------------------------------------------------------------------------------- /console/.env.example: -------------------------------------------------------------------------------- 1 | DOMAIN= 2 | EMAIL= 3 | AUTH_TOKEN= 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/storage-block/lib/functions.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/ui/src/features/cluster-secret-modal/model/validation.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | github: vitabaks 3 | patreon: vitabaks 4 | -------------------------------------------------------------------------------- /automation/changelog.yaml: -------------------------------------------------------------------------------- 1 | # changelog.yaml 2 | --- 3 | releases: {} 4 | -------------------------------------------------------------------------------- /automation/roles/consul/version.txt: -------------------------------------------------------------------------------- 1 | commit: 6251974 on Nov 15, 2022 2 | -------------------------------------------------------------------------------- /automation/roles/firewall/.gitignore: -------------------------------------------------------------------------------- 1 | *.retry 2 | */__pycache__ 3 | *.pyc 4 | -------------------------------------------------------------------------------- /console/ui/src/features/theme-toggle/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ui'; -------------------------------------------------------------------------------- /console/service/pkg/tracer/cid.go: -------------------------------------------------------------------------------- 1 | package tracer 2 | 3 | type CtxCidKey struct{} 4 | -------------------------------------------------------------------------------- /images/TypeA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/images/TypeA.png -------------------------------------------------------------------------------- /images/TypeB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/images/TypeB.png -------------------------------------------------------------------------------- /images/TypeC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/images/TypeC.png -------------------------------------------------------------------------------- /automation/roles/consul/vars/VMware Photon OS.yml: -------------------------------------------------------------------------------- 1 | --- 2 | consul_os_packages: 3 | - unzip 4 | -------------------------------------------------------------------------------- /console/ui/src/app/layout/index.ts: -------------------------------------------------------------------------------- 1 | import Layout from './ui'; 2 | 3 | export default Layout; 4 | -------------------------------------------------------------------------------- /automation/roles/confd/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/consul/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/copy/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/cron/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/etcd/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/haproxy/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/locales/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/mount/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/netdata/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/ntp/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/patroni/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/pgpass/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/sudo/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/swap/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/sysctl/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/update/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/upgrade/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/wal_g/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /images/load_balancing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/images/load_balancing.jpg -------------------------------------------------------------------------------- /automation/roles/etc_hosts/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/firewall/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/hostname/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/io_scheduler/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/keepalived/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/packages/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/pam_limits/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/pg_probackup/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/pgbackrest/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/pgbouncer/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/pre_checks/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/resolv_conf/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/ssh_keys/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/timezone/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/vip_manager/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /console/ui/.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ 4 | public/ 5 | package.json 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /console/ui/src/pages/404/index.ts: -------------------------------------------------------------------------------- 1 | import Page404 from '@pages/404/ui'; 2 | 3 | export default Page404; 4 | -------------------------------------------------------------------------------- /console/ui/src/pages/login/index.ts: -------------------------------------------------------------------------------- 1 | import Login from '@pages/login/ui'; 2 | 3 | export default Login; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/main/index.ts: -------------------------------------------------------------------------------- 1 | import Main from '@widgets/main/ui'; 2 | 3 | export default Main; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/sidebar/index.ts: -------------------------------------------------------------------------------- 1 | import Sidebar from './ui'; 2 | 3 | export default Sidebar; 4 | -------------------------------------------------------------------------------- /images/github-autobase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/images/github-autobase.png -------------------------------------------------------------------------------- /automation/roles/add_repository/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/authorized_keys/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/cloud_resources/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/deploy_finish/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/postgresql_privs/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/postgresql_users/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/tls_certificate/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/error-box/model/types.ts: -------------------------------------------------------------------------------- 1 | export interface ErrorBoxProps { 2 | text?: string; 3 | } 4 | -------------------------------------------------------------------------------- /images/pg_cluster_scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/images/pg_cluster_scheme.png -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | --- 2 | image: 3 | file: .config/gitpod/Dockerfile 4 | 5 | tasks: 6 | - init: make bootstrap-dev 7 | -------------------------------------------------------------------------------- /automation/roles/postgresql_databases/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/postgresql_extensions/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/postgresql_schemas/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /automation/roles/transparent_huge_pages/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: vitabaks.autobase.common 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/sidebar-item/index.ts: -------------------------------------------------------------------------------- 1 | import SidebarItem from './ui'; 2 | 3 | export default SidebarItem; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/header/index.ts: -------------------------------------------------------------------------------- 1 | import Header from '@widgets/header/ui'; 2 | 3 | export default Header; 4 | -------------------------------------------------------------------------------- /console/db/pg_hba.conf: -------------------------------------------------------------------------------- 1 | local all all trust 2 | host all all 127.0.0.1/32 trust 3 | host all all 0.0.0.0/0 scram-sha-256 4 | -------------------------------------------------------------------------------- /console/service/internal/controllers/errors.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | const ( 4 | BaseError = int64(100) 5 | ) 6 | -------------------------------------------------------------------------------- /console/ui/src/pages/clusters/index.ts: -------------------------------------------------------------------------------- 1 | import Clusters from '@pages/clusters/ui'; 2 | 3 | export default Clusters; 4 | -------------------------------------------------------------------------------- /console/ui/src/pages/settings/index.ts: -------------------------------------------------------------------------------- 1 | import Settings from '@pages/settings/ui'; 2 | 3 | export default Settings; 4 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/copy-icon/model/types.ts: -------------------------------------------------------------------------------- 1 | export interface CopyIconProps { 2 | valueToCopy?: string; 3 | } 4 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/spinner/index.ts: -------------------------------------------------------------------------------- 1 | import Spinner from '@shared/ui/spinner/ui'; 2 | 3 | export default Spinner; 4 | -------------------------------------------------------------------------------- /automation/requirements.txt: -------------------------------------------------------------------------------- 1 | ansible==12.2.0 2 | boto3==1.41.5 3 | dopy==0.3.7 4 | google-auth==2.43.0 5 | hcloud==2.11.1 6 | -------------------------------------------------------------------------------- /automation/roles/pgbouncer/templates/userlist.txt.j2: -------------------------------------------------------------------------------- 1 | "{{ patroni_superuser_username }}" "{{ patroni_superuser_password }}" 2 | -------------------------------------------------------------------------------- /console/ui/src/app/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /console/ui/src/features/settings-table-row-actions/model/constants.ts: -------------------------------------------------------------------------------- 1 | export const SECRET_TOAST_DISPLAY_CLUSTERS_LIMIT = 10; 2 | -------------------------------------------------------------------------------- /console/ui/src/pages/operations/index.ts: -------------------------------------------------------------------------------- 1 | import Operations from '@pages/operations/ui'; 2 | 3 | export default Operations; 4 | -------------------------------------------------------------------------------- /console/ui/src/pages/add-cluster/index.ts: -------------------------------------------------------------------------------- 1 | import AddCluster from '@pages/add-cluster/ui'; 2 | 3 | export default AddCluster; 4 | -------------------------------------------------------------------------------- /console/ui/src/pages/login/model/constants.ts: -------------------------------------------------------------------------------- 1 | export const LOGIN_FORM_FIELD_NAMES = Object.freeze({ 2 | TOKEN: 'token', 3 | }); 4 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/copy-icon/index.ts: -------------------------------------------------------------------------------- 1 | import CopyIcon from '@shared/ui/copy-icon/ui'; 2 | 3 | export default CopyIcon; 4 | -------------------------------------------------------------------------------- /images/autobase_create_cluster_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/images/autobase_create_cluster_demo.gif -------------------------------------------------------------------------------- /images/pg_cluster_scheme.dark_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/images/pg_cluster_scheme.dark_mode.png -------------------------------------------------------------------------------- /automation/roles/firewall/.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | rules: 4 | line-length: 5 | max: 120 6 | level: warning 7 | -------------------------------------------------------------------------------- /console/ui/src/features/add-project/index.ts: -------------------------------------------------------------------------------- 1 | import AddProject from '@features/add-project/ui'; 2 | 3 | export default AddProject; 4 | -------------------------------------------------------------------------------- /console/ui/src/features/bradcrumbs/index.ts: -------------------------------------------------------------------------------- 1 | import Breadcrumbs from '@/features/bradcrumbs/ui'; 2 | 3 | export default Breadcrumbs; 4 | -------------------------------------------------------------------------------- /console/ui/src/pages/operation-log/index.ts: -------------------------------------------------------------------------------- 1 | import OperationLog from '@pages/operation-log/ui'; 2 | 3 | export default OperationLog; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/cluster-form/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterForm from '@widgets/cluster-form/ui'; 2 | 3 | export default ClusterForm; 4 | -------------------------------------------------------------------------------- /automation/roles/consul/templates/consul_profile.sh.j2: -------------------------------------------------------------------------------- 1 | export CONSUL_CACERT={{ consul_tls_dir }}/ca.crt 2 | export CONSUL_HTTP_SSL=true 3 | -------------------------------------------------------------------------------- /automation/roles/consul/templates/rsyslogd_00-consul.conf.j2: -------------------------------------------------------------------------------- 1 | {{ consul_syslog_facility }}.* {{ consul_log_path }}/{{ consul_log_file }} 2 | -------------------------------------------------------------------------------- /automation/roles/packages/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pgdg_architecture_map: 3 | amd64: x86_64 4 | x86_64: x86_64 5 | aarch64: aarch64 6 | -------------------------------------------------------------------------------- /console/ui/src/features/clusters-table-buttons/model/types.ts: -------------------------------------------------------------------------------- 1 | export interface ClustersTableButtonsProps { 2 | refetch: () => void; 3 | } 4 | -------------------------------------------------------------------------------- /console/ui/src/features/logout-button/index.ts: -------------------------------------------------------------------------------- 1 | import LogoutButton from '@features/logout-button/ui'; 2 | 3 | export default LogoutButton; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/secrets-table/index.ts: -------------------------------------------------------------------------------- 1 | import SecretsTable from '@widgets/secrets-table/ui'; 2 | 3 | export default SecretsTable; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/settings-form/index.ts: -------------------------------------------------------------------------------- 1 | import SettingsForm from '@widgets/settings-form/ui'; 2 | 3 | export default SettingsForm; 4 | -------------------------------------------------------------------------------- /.editorconfig-checker.json: -------------------------------------------------------------------------------- 1 | { 2 | "Exclude": [".env", ".j2", "console/", "settings.json", ".sql", "go.mod", "go.sum", "patroni/library/"] 3 | } 4 | -------------------------------------------------------------------------------- /automation/roles/consul/requirements.txt: -------------------------------------------------------------------------------- 1 | rich>=14.2.0,<14.3.0 2 | molecule===2.22 3 | docker 4 | netaddr 5 | testinfra 6 | flake8 7 | yamllint 8 | -------------------------------------------------------------------------------- /automation/roles/pgbackrest/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pgdg_architecture_map: 3 | amd64: x86_64 4 | x86_64: x86_64 5 | aarch64: aarch64 6 | -------------------------------------------------------------------------------- /console/ui/src/entities/breadcumb-item/model/types.ts: -------------------------------------------------------------------------------- 1 | export interface BreadcrumbsItemProps { 2 | label: string; 3 | path: string; 4 | } 5 | -------------------------------------------------------------------------------- /console/ui/src/features/add-secret/index.ts: -------------------------------------------------------------------------------- 1 | import SettingsAddSecret from '@features/add-secret/ui'; 2 | 3 | export default SettingsAddSecret; 4 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/default-table/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTable from '@shared/ui/default-table/ui'; 2 | 3 | export default DefaultTable; 4 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/info-card-body/index.ts: -------------------------------------------------------------------------------- 1 | import InfoCardBody from '@shared/ui/info-card-body/ui'; 2 | 3 | export default InfoCardBody; 4 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/slider-box/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterSliderBox from '@shared/ui/slider-box/ui'; 2 | 3 | export default ClusterSliderBox; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/clusters-table/index.ts: -------------------------------------------------------------------------------- 1 | import ClustersTable from '@widgets/clusters-table/ui'; 2 | 3 | export default ClustersTable; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/projects-table/index.tsx: -------------------------------------------------------------------------------- 1 | import ProjectsTable from '@widgets/projects-table/ui'; 2 | 3 | export default ProjectsTable; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/breadcumb-item/index.ts: -------------------------------------------------------------------------------- 1 | import BreadcrumbsItem from '@entities/breadcumb-item/ui'; 2 | 3 | export default BreadcrumbsItem; 4 | -------------------------------------------------------------------------------- /console/ui/src/features/add-environment/index.ts: -------------------------------------------------------------------------------- 1 | import AddEnvironment from '@features/add-environment/ui'; 2 | 3 | export default AddEnvironment; 4 | -------------------------------------------------------------------------------- /console/ui/src/pages/overview-cluster/index.ts: -------------------------------------------------------------------------------- 1 | import OverviewCluster from '@pages/overview-cluster/ui'; 2 | 3 | export default OverviewCluster; 4 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/selectable-box/index.ts: -------------------------------------------------------------------------------- 1 | import SelectableBox from '@shared/ui/selectable-box/ui'; 2 | 3 | export default SelectableBox; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/cluster-summary/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterSummary from '@widgets/cluster-summary/ui'; 2 | 3 | export default ClusterSummary; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/operations-table/index.ts: -------------------------------------------------------------------------------- 1 | import OperationsTable from '@widgets/operations-table/ui'; 2 | 3 | export default OperationsTable; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/cluster-info/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterInfo from '@entities/cluster/cluster-info/ui'; 2 | 3 | export default ClusterInfo; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/secret-form-block/index.ts: -------------------------------------------------------------------------------- 1 | import SecretFormBlock from '@entities/secret-form-block/ui'; 2 | 3 | export default SecretFormBlock; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .venv 4 | .vscode/settings.json 5 | __pycache__/ 6 | *.log 7 | molecule/**/tmp 8 | .ansible 9 | .ansible-lint-env 10 | -------------------------------------------------------------------------------- /automation/roles/firewall/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart firewall 3 | ansible.builtin.service: 4 | name: firewall 5 | state: restarted 6 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/storage-block/index.ts: -------------------------------------------------------------------------------- 1 | import StorageBlock from '@entities/cluster/storage-block/ui'; 2 | 3 | export default StorageBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/environments-table/index.ts: -------------------------------------------------------------------------------- 1 | import EnvironmentsTable from '@widgets/environments-table/ui'; 2 | 3 | export default EnvironmentsTable; 4 | -------------------------------------------------------------------------------- /automation/roles/consul/files/README.md: -------------------------------------------------------------------------------- 1 | # files 2 | 3 | This directory is used for holding temporary files and should be present 4 | in the role even when empty. 5 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/connection-info/index.ts: -------------------------------------------------------------------------------- 1 | import ConnectionInfo from '@entities/cluster/connection-info/ui'; 2 | 3 | export default ConnectionInfo; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/ssh-key-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const SSH_KEY_BLOCK_FIELD_NAMES = Object.freeze({ 2 | SSH_PUBLIC_KEY: 'sshPublicKey', 3 | }); 4 | -------------------------------------------------------------------------------- /automation/roles/authorized_keys/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # ssh_public_keys: [] # Defined in roles/common/defaults/main.yml. Commented out here to prevent conflicts. 3 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/vip-address-block/index.ts: -------------------------------------------------------------------------------- 1 | import VipAddressBlock from '@entities/cluster/vip-address-block/ui'; 2 | 3 | export default VipAddressBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/settings/proxy-block/index.ts: -------------------------------------------------------------------------------- 1 | import SettingsProxyBlock from '@entities/settings/proxy-block/ui'; 2 | 3 | export default SettingsProxyBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/features/add-project/model/constants.ts: -------------------------------------------------------------------------------- 1 | export const PROJECT_FORM_NAMES = Object.freeze({ 2 | NAME: 'name', 3 | DESCRIPTION: 'description', 4 | }); 5 | -------------------------------------------------------------------------------- /console/ui/src/features/cluster-secret-modal/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterSecretModal from '@features/cluster-secret-modal/ui'; 2 | 3 | export default ClusterSecretModal; 4 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/default-form-buttons/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultFormButtons from '@shared/ui/default-form-buttons/ui'; 2 | 3 | export default DefaultFormButtons; 4 | -------------------------------------------------------------------------------- /automation/roles/confd/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | confd_architecture_map: 3 | amd64: amd64 4 | x86_64: amd64 5 | aarch64: arm64 6 | arm64: arm64 7 | 64-bit: amd64 8 | -------------------------------------------------------------------------------- /console/ui/src/features/clusters-table-buttons/index.ts: -------------------------------------------------------------------------------- 1 | import ClustersTableButtons from '@features/clusters-table-buttons/ui'; 2 | 3 | export default ClustersTableButtons; 4 | -------------------------------------------------------------------------------- /console/ui/src/features/settings-table-buttons/index.ts: -------------------------------------------------------------------------------- 1 | import SettingsTableButtons from '@features/settings-table-buttons/ui'; 2 | 3 | export default SettingsTableButtons; 4 | -------------------------------------------------------------------------------- /console/ui/src/widgets/cluster-overview-table/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterOverviewTable from '@widgets/cluster-overview-table/ui'; 2 | 3 | export default ClusterOverviewTable; 4 | -------------------------------------------------------------------------------- /automation/roles/consul/vars/FreeBSD.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # File: FreeBSD.yml - FreeBSD OS variables for Consul 3 | 4 | consul_os_packages: 5 | - unzip 6 | 7 | dnsmasq_package: dnsmasq 8 | -------------------------------------------------------------------------------- /console/service/internal/xdocker/images.go: -------------------------------------------------------------------------------- 1 | package xdocker 2 | 3 | const ( 4 | playbookCreateCluster = "deploy_pgcluster.yml" 5 | 6 | entryPoint = "ansible-playbook" 7 | ) 8 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/network-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const NETWORK_BLOCK_FIELD_NAMES = Object.freeze({ 2 | SERVER_NETWORK: 'serverNetwork', 3 | }); 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/ssh-key-block/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterFormSshKeyBlock from '@entities/cluster/ssh-key-block/ui'; 2 | 3 | export default ClusterFormSshKeyBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/settings-add-entity/model/constants.ts: -------------------------------------------------------------------------------- 1 | export const ADD_ENTITY_FORM_NAMES = Object.freeze({ 2 | NAME: 'name', 3 | DESCRIPTION: 'description', 4 | }); 5 | -------------------------------------------------------------------------------- /automation/roles/consul/handlers/restart_consul_mac.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - ansible.builtin.import_tasks: "stop_consul_mac.yml" 3 | 4 | - ansible.builtin.import_tasks: "start_consul_mac.yml" 5 | -------------------------------------------------------------------------------- /automation/roles/cron/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for cron 3 | 4 | # cron_jobs: [] # Defined in roles/common/defaults/main.yml. Commented out here to prevent conflicts. 5 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/cloud-region-block/index.ts: -------------------------------------------------------------------------------- 1 | import CloudFormRegionBlock from '@entities/cluster/cloud-region-block/ui'; 2 | 3 | export default CloudFormRegionBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/data-directory-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const DATA_DIRECTORY_FIELD_NAMES = Object.freeze({ 2 | DATA_DIRECTORY: 'dataDirectory', 3 | }); 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/instances-block/index.ts: -------------------------------------------------------------------------------- 1 | import CloudFormInstancesBlock from '@entities/cluster/instances-block/ui'; 2 | 3 | export default CloudFormInstancesBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/load-balancers-block/index.ts: -------------------------------------------------------------------------------- 1 | import LoadBalancersBlock from '@entities/cluster/load-balancers-block/ui'; 2 | 3 | export default LoadBalancersBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/features/operations-table-buttons/index.ts: -------------------------------------------------------------------------------- 1 | import OperationsTableButtons from '@features/operations-table-buttons/ui'; 2 | 3 | export default OperationsTableButtons; 4 | -------------------------------------------------------------------------------- /automation/roles/consul/vars/Archlinux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # File: Archlinux.yml - Archlinux variables for Consul 3 | 4 | consul_os_packages: 5 | - unzip 6 | 7 | consul_syslog_enable: false 8 | -------------------------------------------------------------------------------- /automation/roles/consul/vars/Flatcar.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # File: Flatcar.yml - Flatcar variables for Consul 3 | 4 | consul_os_packages: [] 5 | 6 | consul_systemd_unit_path: "/etc/systemd/system" 7 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/database-servers-block/index.ts: -------------------------------------------------------------------------------- 1 | import DatabaseServersBlock from '@entities/cluster/database-servers-block/ui'; 2 | 3 | export default DatabaseServersBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/description-block/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterDescriptionBlock from '@entities/cluster/description-block/ui'; 2 | 3 | export default ClusterDescriptionBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/instances-amount-block/index.ts: -------------------------------------------------------------------------------- 1 | import InstancesAmountBlock from '@entities/cluster/instances-amount-block/ui'; 2 | 3 | export default InstancesAmountBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/postgres-version-block/index.ts: -------------------------------------------------------------------------------- 1 | import PostgresVersionBox from '@entities/cluster/postgres-version-block/ui'; 2 | 3 | export default PostgresVersionBox; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/providers-block/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterFormProvidersBlock from '@entities/cluster/providers-block/ui'; 2 | 3 | export default ClusterFormProvidersBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/features/clusters-table-row-actions/index.ts: -------------------------------------------------------------------------------- 1 | import ClustersTableRowActions from '@features/clusters-table-row-actions/ui'; 2 | 3 | export default ClustersTableRowActions; 4 | -------------------------------------------------------------------------------- /console/ui/src/features/pojects-table-row-actions/index.ts: -------------------------------------------------------------------------------- 1 | import ProjectsTableRowActions from '@features/pojects-table-row-actions/ui'; 2 | 3 | export default ProjectsTableRowActions; 4 | -------------------------------------------------------------------------------- /console/ui/src/features/settings-table-buttons/lib/functions.ts: -------------------------------------------------------------------------------- 1 | export const handleDelete = () => {}; 2 | export const handleEdit = () => {}; 3 | export const handleAddSecret = () => {}; 4 | -------------------------------------------------------------------------------- /console/ui/src/features/settings-table-row-actions/index.ts: -------------------------------------------------------------------------------- 1 | import SettingsTableRowActions from '@features/settings-table-row-actions/ui'; 2 | 3 | export default SettingsTableRowActions; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/environment-block/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterFormEnvironmentBlock from '@entities/cluster/environment-block/ui'; 2 | 3 | export default ClusterFormEnvironmentBlock; 4 | -------------------------------------------------------------------------------- /automation/roles/wal_g/templates/walg.json.j2: -------------------------------------------------------------------------------- 1 | { 2 | {% for wal_g in wal_g_json %} 3 | "{{ wal_g.option }}": "{{ wal_g.value }}"{% if not loop.last %}, 4 | {% endif %} 5 | {% endfor %} 6 | 7 | } 8 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/cluster-name-block/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterFormClusterNameBlock from '@entities/cluster/cluster-name-block/ui'; 2 | 3 | export default ClusterFormClusterNameBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/kernel-parameters-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const KERNEL_PARAMETERS_FIELD_NAMES = Object.freeze({ 2 | KERNEL_PARAMETERS: 'kernelParameters', 3 | }); 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/instances-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const INSTANCES_BLOCK_FIELD_NAMES = Object.freeze({ 2 | INSTANCE_TYPE: 'instanceType', 3 | SERVER_TYPE: 'serverType', 4 | }); 5 | -------------------------------------------------------------------------------- /console/ui/src/features/operations-table-row-actions/index.ts: -------------------------------------------------------------------------------- 1 | import OperationsTableRowActions from '@features/operations-table-row-actions/ui'; 2 | 3 | export default OperationsTableRowActions; 4 | -------------------------------------------------------------------------------- /automation/roles/consul/handlers/restart_syslogng.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart syslog-ng 3 | ansible.builtin.service: 4 | name: syslog-ng 5 | state: restarted 6 | listen: "restart syslog-ng" 7 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/extensions-block/assets/citus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/console/ui/src/entities/cluster/expert-mode/extensions-block/assets/citus.png -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/postgres-parameters-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const POSTGRES_PARAMETERS_FIELD_NAMES = Object.freeze({ 2 | POSTGRES_PARAMETERS: 'postgresParameters', 3 | }); 4 | -------------------------------------------------------------------------------- /console/ui/src/features/clusters-table-row-actions/model/types.ts: -------------------------------------------------------------------------------- 1 | export interface ClustersTableRemoveButtonProps { 2 | clusterId: number; 3 | clusterName: string; 4 | closeMenu: () => void; 5 | } 6 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": false, 5 | "printWidth": 160, 6 | "tabWidth": 2, 7 | "endOfLine": "lf", 8 | "useTabs": false 9 | } 10 | -------------------------------------------------------------------------------- /automation/roles/vip_manager/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | vip_manager_architecture_map: 3 | amd64: x86_64 4 | x86_64: x86_64 5 | aarch64: arm64 6 | arm64: arm64 7 | 32-bit: "i386" 8 | 64-bit: x86_64 9 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/extensions-block/assets/pgaudit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/console/ui/src/entities/cluster/expert-mode/extensions-block/assets/pgaudit.png -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/extensions-block/assets/postgis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/console/ui/src/entities/cluster/expert-mode/extensions-block/assets/postgis.png -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/extensions-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const EXTENSION_BLOCK_FIELD_NAMES = Object.freeze({ 2 | EXTENSIONS: 'extensions', 3 | IS_ENABLED: 'isEnabled', 4 | }); 5 | -------------------------------------------------------------------------------- /console/ui/src/features/operations-table-buttons/model/types.ts: -------------------------------------------------------------------------------- 1 | export interface OperationsTableButtonsProps { 2 | refetch: () => void; 3 | startDate: Date; 4 | setStartDate: (date: Date) => void; 5 | } 6 | -------------------------------------------------------------------------------- /.config/python/dev/requirements.txt: -------------------------------------------------------------------------------- 1 | ansible==12.2.0 2 | ansible-lint==25.11.1 3 | ansible-compat==25.11.0 4 | yamllint==1.37.1 5 | molecule==25.11.1 6 | molecule-plugins==25.8.12 7 | docker==7.1.0 8 | flake8==7.3.0 9 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/extensions-block/assets/pgrouting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/console/ui/src/entities/cluster/expert-mode/extensions-block/assets/pgrouting.png -------------------------------------------------------------------------------- /console/ui/src/pages/login/model/types.ts: -------------------------------------------------------------------------------- 1 | import { LOGIN_FORM_FIELD_NAMES } from '@pages/login/model/constants.ts'; 2 | 3 | export interface LoginFormValues { 4 | [LOGIN_FORM_FIELD_NAMES.TOKEN]: string; 5 | } 6 | -------------------------------------------------------------------------------- /.config/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E501, W503, E402 3 | exclude = 4 | .git, 5 | __pycache__, 6 | docs/source/conf.py, 7 | old, 8 | build, 9 | dist, 10 | .venv, 11 | .ansible 12 | -------------------------------------------------------------------------------- /automation/roles/consul/templates/consul_resolved.conf.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | 3 | [Resolve] 4 | DNS={{ consul_addresses.dns }}:{{ consul_ports.dns }} 5 | Domains= 6 | Cache=no 7 | DNSStubListener=yes 8 | -------------------------------------------------------------------------------- /console/ui/src/app/redux/slices/projectSlice/projectSelectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from '@app/redux/store/store.ts'; 2 | 3 | export const selectCurrentProject = (state: RootState) => state.project.currentProject; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/authentification-method-form-block/index.ts: -------------------------------------------------------------------------------- 1 | import AuthenticationMethodFormBlock from '@entities/authentification-method-form-block/ui'; 2 | 3 | export default AuthenticationMethodFormBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/cluster-instance-config-box/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterFromInstanceConfigBox from '@entities/cluster/cluster-instance-config-box/ui'; 2 | 3 | export default ClusterFromInstanceConfigBox; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/cluster-name-description-block/index.ts: -------------------------------------------------------------------------------- 1 | import ClusterNameDescriptionBlock from '@entities/cluster/cluster-name-description-block/ui'; 2 | 3 | export default ClusterNameDescriptionBlock; 4 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/extensions-block/assets/timescaledb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitabaks/autobase/HEAD/console/ui/src/entities/cluster/expert-mode/extensions-block/assets/timescaledb.png -------------------------------------------------------------------------------- /console/ui/src/shared/assets/checkIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /automation/roles/vip_manager/tasks/disable.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Disabe vip-manager service 3 | ansible.builtin.systemd: 4 | daemon_reload: true 5 | name: vip-manager 6 | state: stopped 7 | enabled: false 8 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/extensions-block/ui/styles.css: -------------------------------------------------------------------------------- 1 | .swiper { 2 | width: 100%; 3 | margin: 0; 4 | } 5 | 6 | .swiper-pagination { 7 | position: relative; 8 | margin-top: 24px; 9 | } 10 | -------------------------------------------------------------------------------- /console/ui/src/features/clusters-overview-table-row-actions/index.ts: -------------------------------------------------------------------------------- 1 | import ClustersOverviewTableRowActions from '@features/clusters-overview-table-row-actions/ui'; 2 | 3 | export default ClustersOverviewTableRowActions; 4 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/info-card-body/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | export interface InfoCardBodyProps { 4 | config: { 5 | title: string; 6 | children: ReactNode; 7 | }[]; 8 | } 9 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/instances-amount-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const INSTANCES_AMOUNT_BLOCK_VALUES = Object.freeze({ 2 | INSTANCES_AMOUNT: 'instancesAmount', 3 | IS_SPOT_INSTANCES: 'isSpotInstances', 4 | }); 5 | -------------------------------------------------------------------------------- /automation/roles/confd/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart confd service 3 | ansible.builtin.systemd: 4 | daemon_reload: true 5 | name: confd 6 | enabled: true 7 | state: restarted 8 | listen: "restart confd" 9 | -------------------------------------------------------------------------------- /console/db/migrations/20250323121343_2.2.0.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | -- Extensions 3 | update 4 | public.extensions 5 | set 6 | postgres_max_version = '17' 7 | where 8 | extension_name = 'citus'; 9 | 10 | -- +goose Down 11 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/environment-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ResponseEnvironment } from '@shared/api/api/environments.ts'; 2 | 3 | export interface EnvironmentBlockProps { 4 | environments: ResponseEnvironment[]; 5 | } 6 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/cloud-region-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentInfoCloudRegion } from '@shared/api/api/other.ts'; 2 | 3 | export interface CloudFormRegionBlockProps { 4 | regions: DeploymentInfoCloudRegion[]; 5 | } 6 | -------------------------------------------------------------------------------- /automation/roles/etcd/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | etcd_architecture_map: 3 | amd64: amd64 4 | x86_64: amd64 5 | armv6l: armhfv6 6 | armv7l: armhfv6 7 | aarch64: arm64 8 | arm64: arm64 9 | 32-bit: "386" 10 | 64-bit: amd64 11 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/cluster-info/model/types.ts: -------------------------------------------------------------------------------- 1 | export interface ClusterInfoProps { 2 | postgresVersion?: number; 3 | clusterName?: string; 4 | description?: string; 5 | environment?: string; 6 | location?: string; 7 | } 8 | -------------------------------------------------------------------------------- /automation/roles/consul/templates/configd_50custom.json.j2: -------------------------------------------------------------------------------- 1 | {# consul_config_custom variables are free-style, passed through a hash -#} 2 | {% if consul_config_custom -%} 3 | {{ consul_config_custom | to_nice_json }} 4 | {% else %} 5 | {} 6 | {% endif %} -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/postgres-version-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ResponsePostgresVersion } from '@shared/api/api/other.ts'; 2 | 3 | export interface PostgresVersionBlockProps { 4 | postgresVersions: ResponsePostgresVersion[]; 5 | } 6 | -------------------------------------------------------------------------------- /console/ui/src/widgets/yaml-editor-form/model/types.ts: -------------------------------------------------------------------------------- 1 | import { YAML_EDITOR_FORM_FIELD_NAMES } from '@widgets/yaml-editor-form/model/const.ts'; 2 | 3 | export interface YamlEditorFormValues { 4 | [YAML_EDITOR_FORM_FIELD_NAMES.EDITOR]?: string; 5 | } 6 | -------------------------------------------------------------------------------- /automation/roles/io_scheduler/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Start io-scheduler service 3 | ansible.builtin.systemd: 4 | daemon_reload: true 5 | name: io-scheduler 6 | state: restarted 7 | enabled: true 8 | listen: "restart io-scheduler" 9 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/ssh-key-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { SSH_KEY_BLOCK_FIELD_NAMES } from '@entities/cluster/ssh-key-block/model/const.ts'; 2 | 3 | export interface SshKeyBlockValues { 4 | [SSH_KEY_BLOCK_FIELD_NAMES.SSH_PUBLIC_KEY]?: string; 5 | } 6 | -------------------------------------------------------------------------------- /console/ui/src/features/add-project/model/types.ts: -------------------------------------------------------------------------------- 1 | import { PROJECT_FORM_NAMES } from '@features/add-project/model/constants.ts'; 2 | 3 | export interface ProjectFormValues { 4 | [PROJECT_FORM_NAMES.NAME]: string; 5 | [PROJECT_FORM_NAMES.NAME]: string; 6 | } 7 | -------------------------------------------------------------------------------- /console/ui/src/shared/model/types.ts: -------------------------------------------------------------------------------- 1 | import { MRT_Row, MRT_RowData } from 'material-react-table'; 2 | 3 | export interface TableRowActionsProps { 4 | closeMenu: () => void; 5 | row: MRT_Row; 6 | } 7 | 8 | export type valueOf = T[keyof T]; 9 | -------------------------------------------------------------------------------- /console/ui/.env: -------------------------------------------------------------------------------- 1 | VITE_API_URL=http://localhost:8080/api/v1/ 2 | VITE_AUTH_TOKEN=auth_token 3 | VITE_CLUSTERS_POLLING_INTERVAL=60000 4 | VITE_CLUSTER_OVERVIEW_POLLING_INTERVAL=60000 5 | VITE_OPERATIONS_POLLING_INTERVAL=60000 6 | VITE_OPERATION_LOGS_POLLING_INTERVAL=10000 7 | -------------------------------------------------------------------------------- /automation/roles/consul/templates/syslogng_consul.conf.j2: -------------------------------------------------------------------------------- 1 | destination d_consul { file("{{ consul_log_path }}/{{ consul_log_file }}"); }; 2 | filter f_consul { facility({{ consul_syslog_facility }}); }; 3 | log { source(s_sys); filter(f_consul); destination(d_consul); }; 4 | -------------------------------------------------------------------------------- /console/ui/src/app/redux/slices/themeSlice/themeSelectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from '@app/redux/store/store'; 2 | 3 | export const selectThemeMode = (state: RootState) => state.theme.mode; 4 | export const selectActualTheme = (state: RootState) => state.theme.actualTheme; -------------------------------------------------------------------------------- /console/ui/src/widgets/yaml-editor-form/model/const.ts: -------------------------------------------------------------------------------- 1 | export const YAML_EDITOR_FORM_FIELD_NAMES = Object.freeze({ 2 | EDITOR: 'yamlEditor', 3 | }); 4 | 5 | export const YAML_EDITOR_FORM_DEFAULT_VALUES = Object.freeze({ 6 | [YAML_EDITOR_FORM_FIELD_NAMES.EDITOR]: '', 7 | }); 8 | -------------------------------------------------------------------------------- /automation/roles/consul/vars/Solaris.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # File: Solaris.yml - Solaris OS variables for Consul 3 | 4 | consul_os_packages: 5 | - unzip 6 | 7 | consul_pkg: "consul_{{ consul_version }}_solaris_amd64.zip" 8 | consul_smf_manifest: "/opt/local/lib/svc/manifest/consul.xml" 9 | -------------------------------------------------------------------------------- /console/db/migrations/20250927122311_2.4.0.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | insert into public.postgres_versions (major_version, release_date, end_of_life) 3 | values (18, '2025-09-25', '2030-11-14'); 4 | 5 | -- +goose Down 6 | delete from public.postgres_versions 7 | where major_version = 18; 8 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/network-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { NETWORK_BLOCK_FIELD_NAMES } from '@entities/cluster/expert-mode/network-block/model/const.ts'; 2 | 3 | export interface NetworkBlockValues { 4 | [NETWORK_BLOCK_FIELD_NAMES.SERVER_NETWORK]?: string; 5 | } 6 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/flagIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/ui/src/entities/settings/proxy-block/model/constants.ts: -------------------------------------------------------------------------------- 1 | export const SETTINGS_FORM_FIELDS_NAMES = Object.freeze({ 2 | HTTP_PROXY: 'http_proxy', 3 | HTTPS_PROXY: 'https_proxy', 4 | IS_EXPERT_MODE_ENABLED: 'is_expert_mode_enabled', 5 | IS_YAML_ENABLED: 'is_yaml_enabled', 6 | }); 7 | -------------------------------------------------------------------------------- /console/ui/src/widgets/projects-table/model/types.ts: -------------------------------------------------------------------------------- 1 | import { PROJECTS_TABLE_COLUMN_NAMES } from '@widgets/projects-table/model/constants.ts'; 2 | 3 | export interface ProjectsTableValues { 4 | [PROJECTS_TABLE_COLUMN_NAMES.ID]: string; 5 | [PROJECTS_TABLE_COLUMN_NAMES.NAME]: string; 6 | } 7 | -------------------------------------------------------------------------------- /automation/molecule/default/cleanup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Molecule.default.cleanup 3 | hosts: localhost 4 | gather_facts: false 5 | 6 | tasks: 7 | - name: Delete dcs_type.yml file 8 | ansible.builtin.file: 9 | path: "../../dcs_type.yml" 10 | state: absent 11 | -------------------------------------------------------------------------------- /automation/roles/consul/handlers/restart_rsyslog.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart rsyslog 3 | ansible.builtin.service: 4 | name: rsyslog 5 | state: restarted 6 | when: 7 | - ansible_os_family != "Darwin" 8 | - ansible_os_family != "Windows" 9 | listen: "restart rsyslog" 10 | -------------------------------------------------------------------------------- /automation/roles/consul/vars/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # File: Debian.yml - Debian OS variables for Consul 3 | 4 | consul_os_packages: 5 | - unzip 6 | 7 | dnsmasq_package: dnsmasq 8 | 9 | consul_repo_prerequisites: 10 | - gpg 11 | 12 | consul_repo_url: "https://apt.releases.hashicorp.com" 13 | -------------------------------------------------------------------------------- /console/ui/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSameLine": true, 9 | "arrowParens": "always", 10 | "endOfLine": "auto", 11 | "bracketSpacing": true 12 | } 13 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/data-directory-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { DATA_DIRECTORY_FIELD_NAMES } from '@entities/cluster/expert-mode/data-directory-block/model/const.ts'; 2 | 3 | export interface DataDirectoryFormValues { 4 | [DATA_DIRECTORY_FIELD_NAMES.DATA_DIRECTORY]?: string; 5 | } 6 | -------------------------------------------------------------------------------- /automation/roles/firewall/templates/firewall.unit.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Firewall 3 | After=syslog.target network.target 4 | 5 | [Service] 6 | Type=oneshot 7 | ExecStart=/etc/firewall.bash 8 | ExecStop=/sbin/iptables -F 9 | RemainAfterExit=yes 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /console/ui/src/features/add-secret/model/constants.ts: -------------------------------------------------------------------------------- 1 | import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; 2 | 3 | export const ADD_SECRET_FORM_FIELD_NAMES = Object.freeze({ 4 | SECRET_NAME: 'secretName', 5 | ...SECRET_MODAL_CONTENT_FORM_FIELD_NAMES, 6 | }); 7 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/selectable-box/model/types.ts: -------------------------------------------------------------------------------- 1 | import { SxProps } from '@mui/material'; 2 | import { ReactNode } from 'react'; 3 | 4 | export interface ClusterFormSelectableBoxProps { 5 | children?: ReactNode; 6 | isActive?: boolean; 7 | sx?: SxProps; 8 | [key: string]: unknown; 9 | } 10 | -------------------------------------------------------------------------------- /console/ui/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /automation/roles/pgbackrest/templates/pgbackrest.server.conf.j2: -------------------------------------------------------------------------------- 1 | [global] 2 | {% for global in pgbackrest_server_conf.global %} 3 | {{ global.option }}={{ global.value }} 4 | {% endfor %} 5 | 6 | # Include stanzas configuration files 7 | repo1-host-config-include-path = {{ pgbackrest_conf_file | dirname }}/conf.d 8 | 9 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/HomeOutlinedIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /automation/roles/wal_g/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | wal_g_architecture_map: 3 | amd64: amd64 4 | x86_64: amd64 5 | aarch64: aarch64 6 | arm64: aarch64 7 | 8 | # if 'wal_g_installation_method' is 'src' 9 | go_architecture_map: 10 | amd64: amd64 11 | x86_64: amd64 12 | aarch64: arm64 13 | arm64: arm64 14 | -------------------------------------------------------------------------------- /console/ui/src/features/operations-table-buttons/model/constants.ts: -------------------------------------------------------------------------------- 1 | export const DATE_RANGE_VALUES = Object.freeze({ 2 | LAST_DAY: 'lastDay', 3 | LAST_WEEK: 'lastWeek', 4 | LAST_MONTH: 'lastMonth', 5 | LAST_THREE_MONTHS: 'lastThreeMonths', 6 | LAST_SIX_MONTHS: 'lastSixMonths', 7 | LAST_YEAR: 'lastYear', 8 | }); 9 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/cluster-instance-config-box/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ClusterFormSelectableBoxProps } from '@shared/ui/selectable-box/model/types.ts'; 2 | 3 | export interface ClusterFromInstanceConfigBoxProps extends ClusterFormSelectableBoxProps { 4 | name: string; 5 | cpu: string; 6 | ram: string; 7 | } 8 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/kernel-parameters-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { KERNEL_PARAMETERS_FIELD_NAMES } from '@entities/cluster/expert-mode/kernel-parameters-block/model/const.ts'; 2 | 3 | export interface KernelParametersBlockValues { 4 | [KERNEL_PARAMETERS_FIELD_NAMES.KERNEL_PARAMETERS]?: string; 5 | } 6 | -------------------------------------------------------------------------------- /console/ui/src/entities/sidebar-item/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType, SVGProps } from 'react'; 2 | 3 | export interface SidebarItemProps { 4 | path: string; 5 | label: string; 6 | icon?: ComponentType>; 7 | isActive?: boolean; 8 | isCollapsed?: boolean; 9 | target?: string; 10 | } 11 | -------------------------------------------------------------------------------- /automation/roles/confd/templates/confd.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Сonfiguration management tool - confd 3 | After=network.target 4 | After=etcd.service 5 | 6 | [Service] 7 | Type=simple 8 | ExecStart=/usr/local/bin/confd 9 | TimeoutSec=30 10 | Restart=on-failure 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /automation/roles/consul/handlers/start_snapshot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: start consul snapshot on unix 3 | ansible.builtin.service: 4 | name: consul_snapshot 5 | state: started 6 | enabled: true 7 | when: 8 | - ansible_os_family != "Darwin" 9 | - ansible_os_family != "Windows" 10 | listen: "start snapshot" 11 | -------------------------------------------------------------------------------- /console/ui/src/app/router/routerPathsConfig/routerOperationsPathsConfig.ts: -------------------------------------------------------------------------------- 1 | const routerOperationsPathsConfig = { 2 | absolutePath: '/operations', 3 | log: { 4 | absolutePath: '/operations/:operationId/log', 5 | relativePath: ':operationId/log', 6 | }, 7 | }; 8 | 9 | export default routerOperationsPathsConfig; 10 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/collapseIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /automation/roles/consul/vars/Amazon.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # File: Amazon.yml - Amazonlinux variables for Consul 3 | consul_os_packages: 4 | - git 5 | - unzip 6 | 7 | consul_syslog_enable: false 8 | 9 | consul_repo_prerequisites: 10 | - yum-utils 11 | 12 | consul_repo_url: https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo 13 | -------------------------------------------------------------------------------- /console/service/internal/controllers/utils.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import "postgresql-cluster-console/models" 4 | 5 | func MakeErrorPayload(err error, code int64) *models.ResponseError { 6 | return &models.ResponseError{ 7 | Code: code, 8 | Description: err.Error(), 9 | Title: err.Error(), 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /console/service/internal/watcher/consts.go: -------------------------------------------------------------------------------- 1 | package watcher 2 | 3 | const ( 4 | ContainerStatusExited = "exited" 5 | ContainerStatusRemoving = "removing" 6 | ContainerStatusDead = "dead" 7 | 8 | LogFieldSystemInfo = "System info" 9 | LogFieldConnectionInfo = "vitabaks.autobase.deploy_finish : Connection info" 10 | ) 11 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/postgres-parameters-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { POSTGRES_PARAMETERS_FIELD_NAMES } from '@entities/cluster/expert-mode/postgres-parameters-block/model/const.ts'; 2 | 3 | export interface PostgresParametersBlockValues { 4 | [POSTGRES_PARAMETERS_FIELD_NAMES.POSTGRES_PARAMETERS]?: string; 5 | } 6 | -------------------------------------------------------------------------------- /console/ui/src/pages/clusters/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { Box } from '@mui/material'; 3 | import ClustersTable from '@widgets/clusters-table'; 4 | 5 | const Clusters: FC = () => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default Clusters; 14 | -------------------------------------------------------------------------------- /automation/roles/transparent_huge_pages/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Start disable-transparent-huge-pages service 3 | ansible.builtin.systemd: 4 | daemon_reload: true 5 | name: disable-transparent-huge-pages 6 | state: restarted 7 | enabled: true 8 | listen: "restart disable-thp" 9 | when: not ansible_check_mode 10 | -------------------------------------------------------------------------------- /console/ui/src/pages/operations/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import OperationsTable from '@widgets/operations-table'; 3 | import { Box } from '@mui/material'; 4 | 5 | const Operations: FC = () => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default Operations; 14 | -------------------------------------------------------------------------------- /console/service/env.sh: -------------------------------------------------------------------------------- 1 | export PG_CONSOLE_DB_MIGRATIONDIR='./db/migrations' 2 | export PG_CONSOLE_LOGGER_LEVEL=TRACE 3 | export PG_CONSOLE_DB_DBNAME=db_console 4 | export PG_CONSOLE_DOCKER_LOGDIR='/home/nikolay.gurban/log_dir' 5 | export PG_CONSOLE_DB_PASSWORD=postgres 6 | export PG_CONSOLE_LOGWATCHER_RUNEVERY=10m 7 | export PG_CONSOLE_CLUSTERWATCHER_RUNEVERY=10m -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/extensions-block/lib/functions.ts: -------------------------------------------------------------------------------- 1 | import { ResponseDatabaseExtension } from '@shared/api/api/other.ts'; 2 | 3 | export const filterValues = (searchValue: string, extensions: ResponseDatabaseExtension[]) => 4 | searchValue ? extensions?.filter((extension) => extension?.name?.includes(searchValue)) : extensions; 5 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/default-form-buttons/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react'; 2 | 3 | export interface DefaultFormButtonsProps { 4 | isDisabled?: boolean; 5 | isSubmitting?: boolean; 6 | submitButtonLabel?: string; 7 | cancelButtonLabel?: string; 8 | cancelHandler: () => void; 9 | children?: ReactElement; 10 | } 11 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/instances-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentInstanceType } from '@/shared/api/api/deployments'; 2 | 3 | export interface CloudFormInstancesBlockProps { 4 | instances: { 5 | small?: DeploymentInstanceType[]; 6 | medium?: DeploymentInstanceType[]; 7 | large?: DeploymentInstanceType[]; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /console/ui/src/features/add-secret/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ADD_SECRET_FORM_FIELD_NAMES } from '@features/add-secret/model/constants.ts'; 2 | 3 | import { SecretFormValues } from '@entities/secret-form-block/model/types.ts'; 4 | 5 | export interface AddSecretFormValues extends SecretFormValues { 6 | [ADD_SECRET_FORM_FIELD_NAMES.SECRET_NAME]: string; 7 | } 8 | -------------------------------------------------------------------------------- /automation/roles/upgrade/tasks/pgbouncer_resume.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Perform RESUME pgbouncers server 3 | - name: RESUME PgBouncer pools 4 | become: true 5 | become_user: postgres 6 | ansible.builtin.shell: kill -SIGUSR2 $(pidof pgbouncer) 7 | args: 8 | executable: /bin/bash 9 | ignore_errors: true # if there is an error, show the message and continue 10 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/lanIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/ui/src/app/redux/store/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from 'react-redux'; 2 | import type { AppDispatch, RootState } from './store'; 3 | 4 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 5 | export const useAppDispatch = useDispatch.withTypes(); 6 | export const useAppSelector = useSelector.withTypes(); 7 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/providers-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react'; 2 | 3 | export interface ProvidersBlockProps { 4 | providers: { code?: string; description?: string }[]; 5 | } 6 | 7 | export interface ClusterFormCloudProviderBoxProps { 8 | children?: ReactElement; 9 | isActive?: boolean; 10 | [key: string]: unknown; 11 | } 12 | -------------------------------------------------------------------------------- /automation/roles/confd/templates/haproxy.toml.j2: -------------------------------------------------------------------------------- 1 | [template] 2 | prefix = "/{{ patroni_etcd_namespace | default('service') }}/{{ patroni_cluster_name }}" 3 | src = "haproxy.tmpl" 4 | dest = "/etc/haproxy/haproxy.cfg" 5 | check_cmd = "/usr/sbin/haproxy -c -f {% raw %}{{ .src }}{% endraw %}" 6 | reload_cmd = "/bin/systemctl reload haproxy" 7 | 8 | keys = [ 9 | "/members/", 10 | ] 11 | -------------------------------------------------------------------------------- /console/ui/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/spinner/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { CircularProgress, Stack } from '@mui/material'; 3 | 4 | const Spinner: FC = () => { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default Spinner; 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: "Question" 5 | url: https://github.com/vitabaks/autobase/discussions/new?category=q-a 6 | about: Ask a question about Autobase 7 | - name: "Commercial support" 8 | url: https://autobase.tech/docs/support 9 | about: Find out more about the available Autobase packages 10 | -------------------------------------------------------------------------------- /automation/roles/etc_hosts/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add entries into /etc/hosts file 3 | ansible.builtin.lineinfile: 4 | path: /etc/hosts 5 | regexp: "^{{ item }}" 6 | line: "{{ item }}" 7 | unsafe_writes: true # to prevent failures in CI 8 | loop: "{{ etc_hosts }}" 9 | when: 10 | - etc_hosts is defined 11 | - etc_hosts | length > 0 12 | tags: etc_hosts 13 | -------------------------------------------------------------------------------- /automation/roles/pgbackrest/templates/pgbackrest.server.stanza.conf.j2: -------------------------------------------------------------------------------- 1 | [{{ pgbackrest_stanza }}] 2 | {% for host in groups['postgres_cluster'] %} 3 | pg{{ loop.index }}-host={{ hostvars[host].bind_address }} 4 | pg{{ loop.index }}-port={{ postgresql_port }} 5 | pg{{ loop.index }}-socket-path={{ postgresql_unix_socket_dir }} 6 | pg{{ loop.index }}-path={{ postgresql_data_dir }} 7 | {% endfor %} 8 | 9 | -------------------------------------------------------------------------------- /.sql-formatter.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "postgresql", 3 | "keywordCase": "lower", 4 | "dataTypeCase": "lower", 5 | "functionCase": "lower", 6 | "identifierCase": "lower", 7 | "tabWidth": 2, 8 | "useTabs": false, 9 | "linesBetweenQueries": 1, 10 | "logicalOperatorNewline": true, 11 | "expressionWidth": 160, 12 | "denseOperators": false, 13 | "newlineBeforeSemicolon": false 14 | } 15 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/network-block/model/validation.ts: -------------------------------------------------------------------------------- 1 | import { TFunction } from 'i18next'; 2 | import * as yup from 'yup'; 3 | import { NETWORK_BLOCK_FIELD_NAMES } from '@entities/cluster/expert-mode/network-block/model/const.ts'; 4 | 5 | export const NetworkBlockFormSchema = (t: TFunction) => 6 | yup.object({ 7 | [NETWORK_BLOCK_FIELD_NAMES.SERVER_NETWORK]: yup.string(), 8 | }); 9 | -------------------------------------------------------------------------------- /.config/.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | line-length: 6 | max: 160 7 | comments-indentation: disable 8 | comments: 9 | min-spaces-from-content: 1 10 | braces: 11 | min-spaces-inside: 0 12 | max-spaces-inside: 1 13 | new-lines: 14 | type: unix 15 | empty-lines: 16 | max: 1 17 | indentation: false 18 | 19 | ignore: | 20 | .github/ 21 | .venv 22 | -------------------------------------------------------------------------------- /automation/roles/consul/templates/consul_systemd_service.override.j2: -------------------------------------------------------------------------------- 1 | # WARNING!!! Ansible managed. 2 | 3 | [Unit] 4 | ConditionFileNotEmpty= 5 | ConditionFileNotEmpty={{ consul_config_path }}/config.{{ consul_config_type }} 6 | 7 | [Service] 8 | ExecStart= 9 | ExecStart=/usr/bin/consul agent -config-file={{ consul_config_path }}/config.{{ consul_config_type }} -config-dir={{ consul_configd_path }} 10 | 11 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/additional-settings-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES = Object.freeze({ 2 | SYNC_STANDBY_NODES: 'synchronousStandbyModes', 3 | IS_SYNC_MODE_STRICT: 'isSynchronousModeStrict', 4 | IS_DB_PUBLIC_ACCESS: 'isDbPublicAccess', 5 | IS_CLOUD_LOAD_BALANCER: 'isCloudLoadBalancer', 6 | IS_NETDATA_MONITORING: 'isNetdataMonitoring', 7 | }); 8 | -------------------------------------------------------------------------------- /.config/make/help.mak: -------------------------------------------------------------------------------- 1 | ## —— Help ——————————————————————————————————————————————————————————————————————————————————————— 2 | .PHONY: help 3 | help: ## Help command 4 | echo -e "$$HEADER" 5 | grep -E '(^[a-zA-Z0-9_-]+:.*?## .*$$)|(^## )' $(MAKEFILE_LIST) | sed 's/^[^:]*://g' | awk 'BEGIN {FS = ":.*?## | #"} ; {printf "${cyan}%-30s${reset} ${white}%s${reset} ${green}%s${reset}\n", $$1, $$2, $$3}' | sed -e 's/\[36m##/\n[32m##/' 6 | -------------------------------------------------------------------------------- /console/ui/src/app/router/routerPathsConfig/routerClustersPathsConfig.ts: -------------------------------------------------------------------------------- 1 | const routerClustersPathsConfig = { 2 | absolutePath: '/clusters', 3 | add: { 4 | absolutePath: '/clusters/add', 5 | relativePath: 'add', 6 | }, 7 | overview: { 8 | absolutePath: '/clusters/:clusterId/overview', 9 | relativePath: ':clusterId/overview', 10 | }, 11 | }; 12 | 13 | export default routerClustersPathsConfig; 14 | -------------------------------------------------------------------------------- /console/ui/src/widgets/projects-table/ui/ProjectsTableButtons.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import AddProject from '@features/add-project'; 3 | import { Stack } from '@mui/material'; 4 | 5 | const ProjectsTableButtons: FC = () => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default ProjectsTableButtons; 14 | -------------------------------------------------------------------------------- /console/ui/src/shared/api/enhancedSecretsApi.ts: -------------------------------------------------------------------------------- 1 | import { secretsApi } from '@shared/api/api/secrets.ts'; 2 | 3 | const enhancedSecretsApi = secretsApi.enhanceEndpoints({ 4 | addTagTypes: ['Secrets'], 5 | endpoints: { 6 | getSecrets: { 7 | providesTags: ['Secrets'], 8 | }, 9 | postSecrets: { 10 | invalidatesTags: ['Secrets'], 11 | }, 12 | }, 13 | }); 14 | 15 | export default enhancedSecretsApi; 16 | -------------------------------------------------------------------------------- /automation/roles/mount/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Example for --extra-vars 3 | # '{"mount": [{"path": "/pgdata", "src": "UUID=83304ebb-d942-4093-975b-8253be2aabe1", "fstype": "ext4", "opts": "defaults,noatime", "state": "mounted"}]}' 4 | 5 | # Defined in roles/common/defaults/main.yml. Commented out here to prevent conflicts. 6 | # mount: 7 | # - path: "" 8 | # src: "" 9 | # fstype: "" 10 | # opts: "" 11 | # state: "" 12 | -------------------------------------------------------------------------------- /console/ui/.env.production: -------------------------------------------------------------------------------- 1 | VITE_API_URL=REPLACE_ME_WITH_API_URL 2 | VITE_AUTH_TOKEN=REPLACE_ME_WITH_AUTH_TOKEN 3 | VITE_CLUSTERS_POLLING_INTERVAL=REPLACE_ME_WITH_CLUSTERS_POLLING_INTERVAL 4 | VITE_CLUSTER_OVERVIEW_POLLING_INTERVAL=REPLACE_ME_WITH_CLUSTER_OVERVIEW_POLLING_INTERVAL 5 | VITE_OPERATIONS_POLLING_INTERVAL=REPLACE_ME_WITH_OPERATIONS_POLLING_INTERVAL 6 | VITE_OPERATION_LOGS_POLLING_INTERVAL=REPLACE_ME_WITH_OPERATION_LOGS_POLLING_INTERVAL -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/connection-info/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | export interface ConnectionInfoProps { 4 | connectionInfo?: { 5 | address?: string | Record; 6 | port?: string | Record; 7 | superuser?: string; 8 | password?: string; 9 | }; 10 | } 11 | 12 | export interface ConnectionInfoRowContainerProps { 13 | children: ReactNode; 14 | } 15 | -------------------------------------------------------------------------------- /console/ui/src/entities/settings/proxy-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { SETTINGS_FORM_FIELDS_NAMES } from '@entities/settings/proxy-block/model/constants.ts'; 2 | 3 | export interface SettingsFormValues { 4 | [SETTINGS_FORM_FIELDS_NAMES.HTTP_PROXY]: string; 5 | [SETTINGS_FORM_FIELDS_NAMES.HTTPS_PROXY]: string; 6 | [SETTINGS_FORM_FIELDS_NAMES.IS_EXPERT_MODE_ENABLED]: boolean; 7 | [SETTINGS_FORM_FIELDS_NAMES.IS_YAML_ENABLED]: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /console/ui/src/shared/i18n/locales/en/validation.json: -------------------------------------------------------------------------------- 1 | { 2 | "requiredField": "Required field", 3 | "onlyNumbers": "Values should be only numbers", 4 | "clusterShouldHaveProperNaming": "Cluster name should have only letters, numbers, hyphens and have length equal or less than 24", 5 | "shouldBeACorrectV4Ip": "The value should be a valid IPv4 address", 6 | "configFormat": "Value should have \"key:value\" or \"key=value\" format" 7 | } 8 | -------------------------------------------------------------------------------- /automation/roles/patroni/tasks/pg_hba.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update pg_hba.conf 3 | ansible.builtin.template: 4 | src: templates/pg_hba.conf.j2 5 | dest: "{{ postgresql_conf_dir }}/pg_hba.conf" 6 | owner: postgres 7 | group: postgres 8 | mode: "0640" 9 | notify: "reload postgres" 10 | tags: pg_hba, pg_hba_generate 11 | 12 | - name: Make sure handlers are flushed immediately 13 | ansible.builtin.meta: flush_handlers 14 | -------------------------------------------------------------------------------- /automation/roles/pgbackrest/templates/pgbackrest_bootstrap.sh.j2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | {% raw %} 3 | while getopts ":-:" optchar; do 4 | [[ "${optchar}" == "-" ]] || continue 5 | case "${OPTARG}" in 6 | datadir=* ) 7 | DATA_DIR=${OPTARG#*=} 8 | ;; 9 | scope=* ) 10 | SCOPE=${OPTARG#*=} 11 | ;; 12 | esac 13 | done 14 | {% endraw %} 15 | 16 | {{ pgbackrest_patroni_cluster_restore_command }} 17 | 18 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/database-servers-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const DATABASE_SERVERS_FIELD_NAMES = Object.freeze({ 2 | IS_CLUSTER_EXISTS: 'databaseServerExistingCluster', 3 | DATABASE_SERVERS: 'databaseServers', 4 | DATABASE_HOSTNAME: 'databaseServerHostname', 5 | DATABASE_IP_ADDRESS: 'databaseServerIpAddress', 6 | DATABASE_LOCATION: 'databaseServerLocation', 7 | IS_POSTGRESQL_EXISTS: 'databaseServerIsPostgreSQLExist', 8 | }); 9 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/storage-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const STORAGE_BLOCK_FIELDS = Object.freeze({ 2 | STORAGE_AMOUNT: 'storageAmount', 3 | FILE_SYSTEM_TYPE: 'fileSystemType', 4 | VOLUME_TYPE: 'volumeType', 5 | }); 6 | 7 | export const fileSystemTypeOptions = Object.freeze([ 8 | { label: 'ext4', value: 'ext4' }, 9 | { 10 | label: 'xfs', 11 | value: 'xfs', 12 | }, 13 | { label: 'zfs', value: 'zfs' }, 14 | ]); 15 | -------------------------------------------------------------------------------- /console/ui/src/app/layout/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import Sidebar from '@widgets/sidebar'; 3 | import Header from '@widgets/header'; 4 | import Main from '@widgets/main'; 5 | 6 | const Layout: FC = () => { 7 | return ( 8 |
9 |
10 | 11 |
12 |
13 | ); 14 | }; 15 | 16 | export default Layout; 17 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/settingsIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/service/internal/storage/cluster_flags_test.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "gotest.tools/v3/assert" 5 | "testing" 6 | ) 7 | 8 | func TestClusterFlags(t *testing.T) { 9 | assert.Equal(t, uint32(1), *SetPatroniConnectStatus(0, 1)) 10 | assert.Equal(t, uint32(1), *SetPatroniConnectStatus(1, 1)) 11 | assert.Equal(t, uint32(0x11), *SetPatroniConnectStatus(0x10, 1)) 12 | assert.Equal(t, uint32(0), *SetPatroniConnectStatus(1, 0)) 13 | } 14 | -------------------------------------------------------------------------------- /console/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Autobase for PostgreSQL® 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /console/ui/src/features/add-project/model/validation.ts: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup'; 2 | import { TFunction } from 'i18next'; 3 | import { PROJECT_FORM_NAMES } from '@features/add-project/model/constants.ts'; 4 | 5 | export const AddProjectFormSchema = (t: TFunction) => 6 | yup.object({ 7 | [PROJECT_FORM_NAMES.NAME]: yup.string().required(t('requiredField', { ns: 'validation' })), 8 | [PROJECT_FORM_NAMES.DESCRIPTION]: yup.string(), 9 | }); 10 | -------------------------------------------------------------------------------- /console/ui/src/widgets/environments-table/ui/EnvironmentsTableButtons.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { Stack } from '@mui/material'; 3 | import AddEnvironment from '@features/add-environment'; 4 | 5 | const EnvironmentsTableButtons: FC = () => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default EnvironmentsTableButtons; 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.go] 13 | indent_style = tab 14 | 15 | [*.mak] 16 | indent_style = tab 17 | 18 | [Makefile] 19 | indent_style = tab 20 | 21 | [*.conf] 22 | indent_style = tab 23 | 24 | [*.md] 25 | trim_trailing_whitespace = false 26 | indent_size = 1 27 | -------------------------------------------------------------------------------- /automation/roles/consul/vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # File: RedHat.yml - Red Hat OS variables for Consul 3 | 4 | consul_os_packages: 5 | - python3-libselinux 6 | - unzip 7 | 8 | consul_repo_prerequisites: 9 | - yum-utils 10 | 11 | consul_repo_url: "{{ 'https://rpm.releases.hashicorp.com/fedora/hashicorp.repo' if ansible_distribution == 'Fedora' else 12 | 'https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo' }}" 13 | 14 | dnsmasq_package: dnsmasq 15 | -------------------------------------------------------------------------------- /console/ui/src/widgets/cluster-summary/assets/hetznerIcon2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /automation/roles/io_scheduler/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create systemd unit file io-scheduler.service 3 | ansible.builtin.template: 4 | src: templates/io-scheduler.service.j2 5 | dest: /etc/systemd/system/io-scheduler.service 6 | notify: "restart io-scheduler" 7 | when: set_scheduler is defined and set_scheduler|bool 8 | tags: scheduler, io_scheduler 9 | 10 | - name: Make sure handlers are flushed immediately 11 | ansible.builtin.meta: flush_handlers 12 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/settings-add-entity/model/validation.ts: -------------------------------------------------------------------------------- 1 | import { TFunction } from 'i18next'; 2 | import * as yup from 'yup'; 3 | import { ADD_ENTITY_FORM_NAMES } from '@shared/ui/settings-add-entity/model/constants.ts'; 4 | 5 | export const AddEntityFormSchema = (t: TFunction) => 6 | yup.object({ 7 | [ADD_ENTITY_FORM_NAMES.NAME]: yup.string().required(t('requiredField', { ns: 'validation' })), 8 | [ADD_ENTITY_FORM_NAMES.DESCRIPTION]: yup.string(), 9 | }); 10 | -------------------------------------------------------------------------------- /console/ui/src/widgets/clusters-table/assets/warningIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/ui/src/widgets/environments-table/model/types.ts: -------------------------------------------------------------------------------- 1 | import { PROJECTS_TABLE_COLUMN_NAMES } from '@widgets/projects-table/model/constants.ts'; 2 | 3 | export interface EnvironmentTableValues { 4 | [PROJECTS_TABLE_COLUMN_NAMES.ID]: string; 5 | [PROJECTS_TABLE_COLUMN_NAMES.NAME]: string; 6 | [PROJECTS_TABLE_COLUMN_NAMES.DESCRIPTION]: 'description'; 7 | [PROJECTS_TABLE_COLUMN_NAMES.CREATED]: 'created_at'; 8 | [PROJECTS_TABLE_COLUMN_NAMES.UPDATED]: 'updated_at'; 9 | } 10 | -------------------------------------------------------------------------------- /console/ui/src/widgets/secrets-table/model/types.ts: -------------------------------------------------------------------------------- 1 | import { SECRETS_TABLE_COLUMN_NAMES } from '@widgets/secrets-table/model/constants.ts'; 2 | 3 | export interface SecretsTableValues { 4 | [SECRETS_TABLE_COLUMN_NAMES.NAME]: string; 5 | [SECRETS_TABLE_COLUMN_NAMES.TYPE]: string; 6 | [SECRETS_TABLE_COLUMN_NAMES.CREATED]: string; 7 | [SECRETS_TABLE_COLUMN_NAMES.UPDATED]: string; 8 | [SECRETS_TABLE_COLUMN_NAMES.USED]: string; 9 | [SECRETS_TABLE_COLUMN_NAMES.ID]: number; 10 | } 11 | -------------------------------------------------------------------------------- /automation/roles/consul/handlers/reload_consul_conf.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use SIGHUP to reload most configurations as per https://www.consul.io/docs/agent/options.html 3 | # Cannot use `consul reload` because it requires the HTTP API to be bound to a non-loopback interface 4 | 5 | - name: reload consul configuration on unix 6 | ansible.builtin.command: "pkill --pidfile '{{ consul_run_path }}/consul.pid' --signal SIGHUP" 7 | when: ansible_os_family != "Windows" 8 | listen: "reload consul configuration" 9 | -------------------------------------------------------------------------------- /automation/roles/keepalived/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | keepalived_instances: 3 | - name: VI_1 4 | state: BACKUP 5 | interface: "{{ vip_interface }}" 6 | virtual_router_id: "{{ keepalived_virtual_router_id | default(123) }}" 7 | priority: 100 8 | advert_int: 2 9 | check_status_command: /usr/libexec/keepalived/haproxy_check.sh 10 | authentication: 11 | auth_type: PASS 12 | auth_pass: "1ce24b6e" 13 | virtual_ipaddresses: 14 | - "{{ cluster_vip }}" 15 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/settings-add-entity/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ADD_ENTITY_FORM_NAMES } from '@shared/ui/settings-add-entity/model/constants.ts'; 2 | 3 | export interface AddEntityFormValues { 4 | [ADD_ENTITY_FORM_NAMES.NAME]: string; 5 | [ADD_ENTITY_FORM_NAMES.NAME]: string; 6 | } 7 | 8 | export interface SettingsAddEntityProps { 9 | buttonLabel: string; 10 | submitButtonLabel: string; 11 | isLoading?: boolean; 12 | submitTrigger: (values: AddEntityFormValues) => void; 13 | } 14 | -------------------------------------------------------------------------------- /console/service/migrations/migrate.go: -------------------------------------------------------------------------------- 1 | package migrations 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/rs/zerolog/log" 7 | 8 | "github.com/jackc/pgx/v5/pgxpool" 9 | "github.com/jackc/pgx/v5/stdlib" 10 | "github.com/pressly/goose/v3" 11 | ) 12 | 13 | func Migrate(dbPool *pgxpool.Pool, migrationDir string) error { 14 | db := stdlib.OpenDBFromPool(dbPool) 15 | goose.SetLogger(NewZeroLogAdapter(log.Logger)) 16 | 17 | return goose.RunContext(context.Background(), "up", db, migrationDir) 18 | } 19 | -------------------------------------------------------------------------------- /console/ui/src/shared/i18n/locales/en/operations.json: -------------------------------------------------------------------------------- 1 | { 2 | "operations": "Operations", 3 | "operation": "Operation", 4 | "started": "Started", 5 | "finished": "Finished", 6 | "creationTime": "Creation time", 7 | "type": "Type", 8 | "showDetails": "Show details", 9 | "lastDay": "Last day", 10 | "lastWeek": "Last week", 11 | "lastMonth": "Last month", 12 | "lastThreeMonths": "Last three months", 13 | "lastSixMonths": "Last six months", 14 | "lastYear": "Last year", 15 | "log": "Log" 16 | } -------------------------------------------------------------------------------- /console/ui/src/widgets/clusters-table/assets/errorIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /automation/roles/consul/tasks/systemd_resolved.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure resolved.conf.d exists 3 | ansible.builtin.file: 4 | path: /etc/systemd/resolved.conf.d/ 5 | state: directory 6 | 7 | - name: Configure systemd-resolved DNS 8 | ansible.builtin.template: 9 | src: consul_resolved.conf.j2 10 | dest: /etc/systemd/resolved.conf.d/consul.conf 11 | mode: "0644" 12 | 13 | - name: Reload service systemd-resolved 14 | ansible.builtin.systemd: 15 | name: systemd-resolved.service 16 | state: restarted 17 | -------------------------------------------------------------------------------- /console/ui/src/shared/model/validation.ts: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup'; 2 | import { TFunction } from 'i18next'; 3 | 4 | export const configValidationSchema = (t: TFunction) => 5 | yup 6 | .string() 7 | .test( 8 | 'should have correct format', 9 | t('configFormat', { ns: 'validation' }), 10 | (value) => 11 | /^[^:=\n\r]+:[^:=\n\r]+([\n\r][^:=\n\r]+:[^:=\n\r]+)*$/i.test(value) || 12 | /^[^:=\n\r]+=[^:=\n\r]+([\n\r][^:=\n\r]+=[^:=\n\r]+)*$/i.test(value) || 13 | value === '', 14 | ); 15 | -------------------------------------------------------------------------------- /console/ui/src/widgets/cluster-summary/assets/digitaloceanIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/service/internal/storage/cluster_flags.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import "go.openly.dev/pointy" 4 | 5 | const ( 6 | patroniConnectStatusMaskSet = uint32(0x1) 7 | patroniConnectStatusMaskRemove = uint32(0xfffffff6) 8 | ) 9 | 10 | func SetPatroniConnectStatus(oldMask uint32, status uint32) *uint32 { 11 | return pointy.Uint32((oldMask & patroniConnectStatusMaskRemove) | (status & patroniConnectStatusMaskSet)) 12 | } 13 | 14 | func GetPatroniConnectStatus(mask uint32) uint32 { 15 | return mask & patroniConnectStatusMaskSet 16 | } 17 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/backups-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const BACKUPS_BLOCK_FIELD_NAMES = Object.freeze({ 2 | IS_BACKUPS_ENABLED: 'isBackupsEnabled', 3 | BACKUP_METHOD: 'backupMethod', 4 | BACKUP_START_TIME: 'backupStartTime', 5 | BACKUP_RETENTION: 'backupRetention', 6 | CONFIG: 'backupConfig', 7 | ACCESS_KEY: 'backupAccessKey', 8 | SECRET_KEY: 'backupSecretKey', 9 | }); 10 | 11 | export const BACKUP_METHODS = Object.freeze({ 12 | PG_BACK_REST: 'pgbackrest_install', 13 | WAL_G: 'wal_g_install', 14 | }); 15 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/kernel-parameters-block/model/validation.ts: -------------------------------------------------------------------------------- 1 | import { TFunction } from 'i18next'; 2 | import * as yup from 'yup'; 3 | import { configValidationSchema } from '@shared/model/validation.ts'; 4 | import { KERNEL_PARAMETERS_FIELD_NAMES } from '@entities/cluster/expert-mode/kernel-parameters-block/model/const.ts'; 5 | 6 | export const KernelParametersBlockFormSchema = (t: TFunction) => 7 | yup.object({ 8 | [KERNEL_PARAMETERS_FIELD_NAMES.KERNEL_PARAMETERS]: configValidationSchema(t).optional(), 9 | }); 10 | -------------------------------------------------------------------------------- /console/ui/src/widgets/clusters-table/assets/correctIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/postgres-parameters-block/model/validation.ts: -------------------------------------------------------------------------------- 1 | import { TFunction } from 'i18next'; 2 | import * as yup from 'yup'; 3 | import { POSTGRES_PARAMETERS_FIELD_NAMES } from '@entities/cluster/expert-mode/postgres-parameters-block/model/const.ts'; 4 | import { configValidationSchema } from '@shared/model/validation.ts'; 5 | 6 | export const PostgresParametersBlockFormSchema = (t: TFunction) => 7 | yup.object({ 8 | [POSTGRES_PARAMETERS_FIELD_NAMES.POSTGRES_PARAMETERS]: configValidationSchema(t).optional(), 9 | }); 10 | -------------------------------------------------------------------------------- /console/ui/src/features/settings-table-buttons/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { Stack } from '@mui/material'; 4 | import SettingsAddSecret from '@features/add-secret'; 5 | 6 | const SettingsTableButtons: React.FC = () => { 7 | const { t } = useTranslation(['shared', 'settings']); 8 | 9 | return ( 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | export default SettingsTableButtons; 17 | -------------------------------------------------------------------------------- /automation/molecule/tests/postgres/replication.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check PostgreSQL replication status 3 | postgresql_query: 4 | query: "SELECT pg_is_in_recovery(), count(*) FROM pg_stat_wal_receiver;" 5 | login_unix_socket: "{{ postgresql_unix_socket_dir }}" 6 | login_port: "{{ postgresql_port }}" 7 | login_user: "{{ patroni_superuser_username }}" 8 | login_db: template1 9 | register: pg_replication_status 10 | failed_when: "pg_replication_status.query_result[0].pg_is_in_recovery and pg_replication_status.query_result[0].count == 0" 11 | -------------------------------------------------------------------------------- /automation/roles/pam_limits/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Linux PAM limits | add or modify nofile limits 3 | community.general.pam_limits: 4 | domain: "{{ limits_user }}" 5 | limit_type: "{{ item.limit_type }}" 6 | limit_item: "{{ item.limit_item }}" 7 | value: "{{ item.value }}" 8 | loop: 9 | - { limit_type: "soft", limit_item: "nofile", value: "{{ soft_nofile }}" } 10 | - { limit_type: "hard", limit_item: "nofile", value: "{{ hard_nofile }}" } 11 | when: set_limits is defined and set_limits|bool 12 | tags: limits, pam_limits 13 | -------------------------------------------------------------------------------- /automation/roles/upgrade/tasks/dcs_remove_cluster.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Remove existing cluster "{{ patroni_cluster_name | default('') }}" from DCS 3 | ansible.builtin.expect: 4 | command: "patronictl -c {{ patroni_config_file }} remove {{ patroni_cluster_name }}" 5 | responses: 6 | (.*)Please confirm the cluster name to remove: "{{ patroni_cluster_name }}" 7 | (.*)"Yes I am aware": "Yes I am aware" 8 | environment: 9 | PATH: "{{ ansible_env.PATH }}:/usr/bin:/usr/local/bin" 10 | when: 11 | - inventory_hostname in groups['primary'] 12 | -------------------------------------------------------------------------------- /console/ui/src/app/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | import 'normalize.css/normalize.css'; 5 | import '@shared/i18n/i18n.ts'; 6 | import '@fontsource/roboto/300.css'; 7 | import '@fontsource/roboto/400.css'; 8 | import '@fontsource/roboto/500.css'; 9 | import '@fontsource/roboto/700.css'; 10 | import 'react-toastify/ReactToastify.css'; 11 | 12 | ReactDOM.createRoot(document.getElementById('root')!).render( 13 | 14 | 15 | , 16 | ); 17 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/connection-info/ui/ConnectionInfoRowConteiner.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { Stack } from '@mui/material'; 3 | import { ConnectionInfoRowContainerProps } from '@entities/cluster/connection-info/model/types.ts'; 4 | 5 | const ConnectionInfoRowContainer: FC = ({ children }) => { 6 | return ( 7 | 8 | {children} 9 | 10 | ); 11 | }; 12 | 13 | export default ConnectionInfoRowContainer; 14 | -------------------------------------------------------------------------------- /automation/roles/consul/vars/Darwin.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # File: MacOSX.yml - Mac OS X variables for Consul 3 | 4 | consul_config_path: "/Users/{{ consul_user }}/Library/Preferences/io.consul" 5 | 6 | consul_configd_path: "{{ consul_config_path }}/consul.d" 7 | 8 | consul_launchctl_ident: "io.consul" 9 | 10 | consul_launchctl_plist: "/Library/LaunchAgents/{{ consul_launchctl_ident }}.plist" 11 | 12 | consul_log_path: "/Users/{{ consul_user }}/Library/Logs/consul" 13 | 14 | consul_os_packages: [] 15 | 16 | consul_run_path: "/Users/{{ consul_user }}/Library/Caches/io.consul" 17 | -------------------------------------------------------------------------------- /console/ui/src/entities/breadcumb-item/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { BreadcrumbsItemProps } from '@entities/breadcumb-item/model/types.ts'; 3 | import { Link } from 'react-router-dom'; 4 | import { useTheme } from '@mui/material'; 5 | 6 | const BreadcrumbsItem: FC = ({ label, path }) => { 7 | const theme = useTheme(); 8 | 9 | return ( 10 | 11 | {label} 12 | 13 | ); 14 | }; 15 | 16 | export default BreadcrumbsItem; 17 | -------------------------------------------------------------------------------- /console/ui/src/widgets/clusters-table/model/types.ts: -------------------------------------------------------------------------------- 1 | import { CLUSTER_TABLE_COLUMN_NAMES } from '@widgets/clusters-table/model/constants.ts'; 2 | 3 | export interface ClustersTableValues { 4 | [CLUSTER_TABLE_COLUMN_NAMES.NAME]: string; 5 | [CLUSTER_TABLE_COLUMN_NAMES.STATUS]: Element; 6 | [CLUSTER_TABLE_COLUMN_NAMES.CREATION_TIME]: string; 7 | [CLUSTER_TABLE_COLUMN_NAMES.ENVIRONMENT]: string; 8 | [CLUSTER_TABLE_COLUMN_NAMES.SERVERS]: number; 9 | [CLUSTER_TABLE_COLUMN_NAMES.POSTGRES_VERSION]: number; 10 | [CLUSTER_TABLE_COLUMN_NAMES.LOCATION]: string; 11 | } 12 | -------------------------------------------------------------------------------- /automation/roles/vip_manager/templates/vip-manager.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Manages Virtual IP for Patroni 3 | After=network-online.target 4 | Before=patroni.service 5 | 6 | [Service] 7 | Type=simple 8 | 9 | ExecStart=/usr/bin/vip-manager --config={{ vip_manager_conf }} 10 | 11 | # VIP not released when service stopped https://github.com/cybertec-postgresql/vip-manager/issues/19 12 | ExecStopPost=/sbin/ip addr del {{ vip_manager_ip }}/{{ vip_manager_mask }} dev {{ vip_manager_iface }} 13 | 14 | Restart=on-failure 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /console/ui/src/widgets/operations-table/model/types.ts: -------------------------------------------------------------------------------- 1 | import { OPERATIONS_TABLE_COLUMN_NAMES } from '@widgets/operations-table/model/constants.ts'; 2 | 3 | export interface OperationsTableValues { 4 | [OPERATIONS_TABLE_COLUMN_NAMES.ID]: number; 5 | [OPERATIONS_TABLE_COLUMN_NAMES.STARTED]: string; 6 | [OPERATIONS_TABLE_COLUMN_NAMES.FINISHED]: string; 7 | [OPERATIONS_TABLE_COLUMN_NAMES.TYPE]: string; 8 | [OPERATIONS_TABLE_COLUMN_NAMES.STATUS]: string; 9 | [OPERATIONS_TABLE_COLUMN_NAMES.CLUSTER]: string; 10 | [OPERATIONS_TABLE_COLUMN_NAMES.ENVIRONMENT]: string; 11 | } 12 | -------------------------------------------------------------------------------- /automation/roles/haproxy/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart haproxy service 3 | ansible.builtin.systemd: 4 | daemon_reload: true 5 | name: haproxy 6 | enabled: true 7 | state: restarted 8 | listen: "restart haproxy" 9 | 10 | - name: Check HAProxy is started and accepting connections 11 | ansible.builtin.wait_for: 12 | port: "{{ haproxy_listen_port.stats }}" 13 | host: "{{ haproxy_bind_address | default(bind_address, true) }}" 14 | state: started 15 | timeout: 120 16 | delay: 10 17 | ignore_errors: false 18 | listen: "restart haproxy" 19 | -------------------------------------------------------------------------------- /console/service/internal/watcher/models.go: -------------------------------------------------------------------------------- 1 | package watcher 2 | 3 | type LogEntity struct { 4 | Task string `json:"task"` 5 | Failed bool `json:"failed"` 6 | Msg interface{} `json:"msg"` 7 | Summary interface{} `json:"summary,omitempty"` 8 | Status string `json:"status"` 9 | } 10 | 11 | type SystemInfo struct { 12 | ServerLocation *string `json:"server_location,omitempty" mapstructure:"server_location"` 13 | ServerName string `json:"server_name" mapstructure:"server_name"` 14 | IpAddress string `json:"ip_address" mapstructure:"ip_address"` 15 | } 16 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/databases-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const DATABASES_BLOCK_FIELD_NAMES = Object.freeze({ 2 | DATABASES: 'databasesBlock', 3 | NAMES: 'databasesBlockNames', 4 | DATABASE_NAME: 'databasesBlockDatabaseName', 5 | USER_NAME: 'databasesBlockUsername', 6 | USER_PASSWORD: 'databasesBlockUserPassword', 7 | ENCODING: 'databasesBlockEncoding', 8 | LOCALE: 'databasesBlockLocale', 9 | BLOCK_ID: 'databasesBlockId', // blockId is required to match database name and presence for extensions. Should not be passed to API call or YAML editor 10 | }); 11 | -------------------------------------------------------------------------------- /automation/roles/io_scheduler/templates/io-scheduler.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=I/O Scheduler Setter 3 | DefaultDependencies=no 4 | After=sysinit.target local-fs.target 5 | Before=basic.target 6 | 7 | [Service] 8 | Type=oneshot 9 | {% if set_scheduler|bool %} 10 | {% for set in scheduler %} 11 | ExecStart=/bin/bash -c 'echo {{ set.sched }} > /sys/block/{{ set.device }}/queue/scheduler && echo {{ set.nr_requests }} > /sys/block/{{ set.device }}/queue/nr_requests' 12 | {% endfor %} 13 | {% endif %} 14 | 15 | TimeoutSec=0 16 | RemainAfterExit=yes 17 | 18 | [Install] 19 | WantedBy=basic.target 20 | -------------------------------------------------------------------------------- /automation/roles/patroni/tasks/patroni.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Generate {{ patroni_config_file | default('/etc/patroni/patroni.yml') }}" 3 | ansible.builtin.template: 4 | src: templates/patroni.yml.j2 5 | dest: "{{ patroni_config_file | default('/etc/patroni/patroni.yml') }}" 6 | validate: patroni --ignore-listen-port --validate-config %s 7 | owner: postgres 8 | group: postgres 9 | mode: "0640" 10 | vars: 11 | etcd_hosts: "{{ groups['etcd_cluster'] | default([]) }}" 12 | notify: "reload patroni" 13 | 14 | - name: Flush handlers 15 | ansible.builtin.meta: flush_handlers 16 | -------------------------------------------------------------------------------- /console/ui/src/widgets/cluster-form/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ResponseDeploymentInfo } from '@shared/api/api/deployments.ts'; 2 | import { ResponseEnvironment } from '@shared/api/api/environments.ts'; 3 | import { ResponsePostgresVersion } from '@shared/api/api/other.ts'; 4 | 5 | export interface ClusterFormProps { 6 | deploymentsData?: ResponseDeploymentInfo[]; 7 | environmentsData?: ResponseEnvironment[]; 8 | postgresVersionsData?: ResponsePostgresVersion[]; 9 | } 10 | 11 | export interface ClusterFormRegionConfigBoxProps { 12 | name: string; 13 | place: string; 14 | isActive: boolean; 15 | } 16 | -------------------------------------------------------------------------------- /automation/roles/hostname/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - block: 3 | - name: Change hostname 4 | ansible.builtin.hostname: 5 | name: "{{ hostname }}" 6 | 7 | - name: Change hostname in /etc/hosts 8 | ansible.builtin.lineinfile: 9 | dest: /etc/hosts 10 | regexp: "{{ item.regexp }}" 11 | line: "{{ item.line }}" 12 | state: present 13 | no_log: true 14 | loop: 15 | - { regexp: '^127\.0\.0\.1[ \t]+localhost', line: "127.0.0.1 localhost {{ ansible_hostname }}" } 16 | when: hostname is defined and hostname | length > 0 17 | tags: hostname 18 | -------------------------------------------------------------------------------- /console/service/migrations/goose_logger.go: -------------------------------------------------------------------------------- 1 | package migrations 2 | 3 | import ( 4 | "github.com/pressly/goose/v3" 5 | "github.com/rs/zerolog" 6 | ) 7 | 8 | type zeroLogAdapter struct { 9 | log zerolog.Logger 10 | } 11 | 12 | func NewZeroLogAdapter(log zerolog.Logger) goose.Logger { 13 | return zeroLogAdapter{log: log.With().Str("module", "goouse").Logger()} 14 | } 15 | 16 | func (l zeroLogAdapter) Fatalf(format string, v ...interface{}) { 17 | l.log.Error().Msgf(format, v...) 18 | } 19 | 20 | func (l zeroLogAdapter) Printf(format string, v ...interface{}) { 21 | l.log.Info().Msgf(format, v...) 22 | } 23 | -------------------------------------------------------------------------------- /console/ui/src/pages/settings/model/constants.ts: -------------------------------------------------------------------------------- 1 | import RouterPaths from '@app/router/routerPathsConfig'; 2 | 3 | export const settingsTabsContent = [ 4 | { 5 | translateKey: 'generalSettings', 6 | path: RouterPaths.settings.general.absolutePath, 7 | }, 8 | { 9 | translateKey: 'secrets', 10 | path: RouterPaths.settings.secrets.absolutePath, 11 | }, 12 | { 13 | translateKey: 'projects', 14 | path: RouterPaths.settings.projects.absolutePath, 15 | }, 16 | { 17 | translateKey: 'environments', 18 | path: RouterPaths.settings.environments.absolutePath, 19 | }, 20 | ]; 21 | -------------------------------------------------------------------------------- /automation/roles/ntp/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart ntp service 3 | ansible.builtin.systemd: 4 | name: ntp 5 | enabled: true 6 | state: restarted 7 | listen: "restart ntp" 8 | 9 | - name: Restart chrony service 10 | ansible.builtin.systemd: 11 | name: chrony 12 | enabled: true 13 | state: restarted 14 | listen: "restart chrony" 15 | 16 | - name: Restart chronyd service 17 | ansible.builtin.systemd: 18 | name: chronyd 19 | enabled: true 20 | state: restarted 21 | ignore_errors: "{{ chronyd_ignore_errors | default(false) }}" 22 | listen: "restart chronyd" 23 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/supportIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/error-box/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { Box, Typography } from '@mui/material'; 3 | import { useTranslation } from 'react-i18next'; 4 | import { ErrorBoxProps } from '@shared/ui/error-box/model/types.ts'; 5 | 6 | const ErrorBox: FC = ({ text }) => { 7 | const { t } = useTranslation('shared'); 8 | 9 | return ( 10 | 11 | {text ?? t('somethingWentWrongWhileRendering')} 12 | 13 | ); 14 | }; 15 | 16 | export default ErrorBox; 17 | -------------------------------------------------------------------------------- /automation/roles/consul/tasks/user_group.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # File: user_group.yml - User and group settings 3 | 4 | # Add group 5 | - name: Add Consul group 6 | ansible.builtin.group: 7 | name: "{{ consul_group }}" 8 | state: present 9 | when: 10 | - consul_manage_group | bool 11 | - not consul_install_from_repo | bool 12 | 13 | # Add user 14 | - name: Add Consul user 15 | ansible.builtin.user: 16 | name: "{{ consul_user }}" 17 | comment: "Consul user" 18 | group: "{{ consul_group }}" 19 | system: true 20 | when: 21 | - consul_manage_user | bool 22 | - not consul_install_from_repo | bool 23 | -------------------------------------------------------------------------------- /console/ui/src/app/router/routerPathsConfig/routerSettingsPathsConfig.ts: -------------------------------------------------------------------------------- 1 | const routerSettingsPathsConfig = { 2 | absolutePath: '/settings', 3 | general: { 4 | absolutePath: '/settings/general', 5 | relativePath: 'general', 6 | }, 7 | secrets: { 8 | absolutePath: '/settings/secrets', 9 | relativePath: 'secrets', 10 | }, 11 | projects: { 12 | absolutePath: '/settings/projects', 13 | relativePath: 'projects', 14 | }, 15 | environments: { 16 | absolutePath: '/settings/environments', 17 | relativePath: 'environments', 18 | }, 19 | }; 20 | 21 | export default routerSettingsPathsConfig; 22 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/backups-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { BACKUPS_BLOCK_FIELD_NAMES } from '@entities/cluster/expert-mode/backups-block/model/const.ts'; 2 | 3 | export interface BackupsBlockValues { 4 | [BACKUPS_BLOCK_FIELD_NAMES.IS_BACKUPS_ENABLED]: boolean; 5 | [BACKUPS_BLOCK_FIELD_NAMES.BACKUP_METHOD]?: string; 6 | [BACKUPS_BLOCK_FIELD_NAMES.BACKUP_START_TIME]?: number; 7 | [BACKUPS_BLOCK_FIELD_NAMES.BACKUP_RETENTION]?: number; 8 | [BACKUPS_BLOCK_FIELD_NAMES.CONFIG]?: string; 9 | [BACKUPS_BLOCK_FIELD_NAMES.ACCESS_KEY]?: string; 10 | [BACKUPS_BLOCK_FIELD_NAMES.SECRET_KEY]?: string; 11 | } 12 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/connection-info/assets/eyeIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /console/service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.25-bookworm as builder 2 | WORKDIR /go/src/pg-console 3 | 4 | COPY console/service/ . 5 | 6 | RUN make build_in_docker 7 | 8 | FROM debian:bookworm-slim 9 | LABEL maintainer="Vitaliy Kukharik vitabaks@gmail.com" 10 | 11 | COPY --from=builder /go/src/pg-console/pg-console /usr/local/bin/ 12 | COPY console/db/migrations /etc/db/migrations 13 | 14 | RUN apt-get clean && rm -rf /var/lib/apt/lists/partial \ 15 | && apt-get update -o Acquire::CompressionTypes::Order::=gz \ 16 | && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y curl 17 | 18 | CMD ["/usr/local/bin/pg-console"] 19 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/additional-settings-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES } from '@entities/cluster/expert-mode/additional-settings-block/model/const.ts'; 2 | 3 | export interface AdditionalSettingsBlockValues { 4 | [ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES.SYNC_STANDBY_NODES]: number; 5 | [ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES.IS_SYNC_MODE_STRICT]?: string; 6 | [ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES.IS_DB_PUBLIC_ACCESS]?: number; 7 | [ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES.IS_CLOUD_LOAD_BALANCER]?: number; 8 | [ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES.IS_NETDATA_MONITORING]?: string; 9 | } 10 | -------------------------------------------------------------------------------- /automation/roles/pam_limits/README.md: -------------------------------------------------------------------------------- 1 | # Ansible Role: pam_limits 2 | 3 | Configures per-user file descriptor limits (nofile) via community.general.pam_limits 4 | 5 | ## Role Variables 6 | 7 | | Variable | Default | Description | 8 | |---|---|---| 9 | | set_limits | true | Enable applying PAM limits. When false, the role does nothing. | 10 | | limits_user | "postgres" | System user for which limits are applied. | 11 | | soft_nofile | 65536 | Soft nofile limit. | 12 | | hard_nofile | 200000 | Hard nofile limit. | 13 | 14 | ## Dependencies 15 | 16 | This role depends on: 17 | - `vitabaks.autobase.common` - Provides common variables and configurations 18 | -------------------------------------------------------------------------------- /console/ui/src/entities/authentification-method-form-block/model/constants.ts: -------------------------------------------------------------------------------- 1 | import { TFunction } from 'i18next'; 2 | import { AUTHENTICATION_METHODS } from '@shared/model/constants.ts'; 3 | 4 | export const authenticationMethods = (t: TFunction) => 5 | Object.freeze([ 6 | { 7 | id: AUTHENTICATION_METHODS.SSH, 8 | name: t('sshKey', { ns: 'clusters' }), 9 | description: t('sshKeyAuthDescription', { ns: 'clusters' }), 10 | }, 11 | { 12 | id: AUTHENTICATION_METHODS.PASSWORD, 13 | name: t('password', { ns: 'shared' }), 14 | description: t('passwordAuthDescription', { ns: 'clusters' }), 15 | }, 16 | ]); 17 | -------------------------------------------------------------------------------- /automation/molecule/tests/postgres/postgres.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check if PostgreSQL process is running 3 | command: pgrep -u postgres 4 | register: result 5 | failed_when: result.rc != 0 6 | 7 | - name: Check if PostgreSQL is listening on the default port 8 | ansible.builtin.wait_for: 9 | port: 5432 10 | timeout: 5 11 | register: is_listening 12 | failed_when: not is_listening 13 | 14 | - name: Try to connect to PostgreSQL 15 | postgresql_ping: 16 | login_unix_socket: "{{ postgresql_unix_socket_dir }}" 17 | login_port: "{{ postgresql_port }}" 18 | login_user: "{{ patroni_superuser_username }}" 19 | login_db: template1 20 | -------------------------------------------------------------------------------- /automation/roles/consul/handlers/start_consul.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: start consul on unix 3 | ansible.builtin.service: 4 | name: consul 5 | state: started 6 | when: 7 | - ansible_os_family != "Darwin" 8 | - ansible_os_family != "Windows" 9 | listen: "start consul" 10 | 11 | - name: start consul on Mac 12 | ansible.builtin.include_tasks: "{{ role_path }}/handlers/start_consul_mac.yml" 13 | when: ansible_os_family == "Darwin" 14 | listen: "start consul" 15 | 16 | - name: start consul on windows 17 | ansible.windows.win_service: 18 | name: consul 19 | state: started 20 | when: ansible_os_family == "Windows" 21 | listen: "start consul" 22 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/slider-box/lib/functions.ts: -------------------------------------------------------------------------------- 1 | import { GenerateMarkType, GenerateSliderMarksType } from '@shared/ui/slider-box/model/types.ts'; 2 | 3 | const generateMark: GenerateMarkType = (value, marksAdditionalLabel) => ({ 4 | value, 5 | label: `${value} ${marksAdditionalLabel}`, 6 | }); 7 | 8 | export const generateSliderMarks: GenerateSliderMarksType = (min, max, amount, marksAdditionalLabel) => { 9 | const step = (max - min) / (amount - 1); 10 | const marksArray = []; 11 | 12 | for (let i = min; i < max + step; i += step) { 13 | marksArray.push(generateMark(Math.trunc(i), marksAdditionalLabel)); 14 | } 15 | 16 | return marksArray; 17 | }; 18 | -------------------------------------------------------------------------------- /automation/roles/hostname/README.md: -------------------------------------------------------------------------------- 1 | # Ansible Role: hostname 2 | 3 | Sets the system hostname and ensures it is reflected in /etc/hosts for local resolution on PostgreSQL cluster nodes. 4 | 5 | ## Variables 6 | 7 | | Variable | Default | Description | 8 | |----------|---------|-------------| 9 | | hostname | "" | Desired system hostname (e.g., pgnode01). | 10 | 11 | ## Behavior 12 | - Uses the Ansible hostname module to set the hostname. 13 | - Adds/updates the localhost line in /etc/hosts to include the current ansible_hostname. 14 | 15 | ## Dependencies 16 | 17 | This role depends on: 18 | - `vitabaks.autobase.common` - Provides common variables and configurations 19 | 20 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/load-balancers-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const LOAD_BALANCERS_FIELD_NAMES = Object.freeze({ 2 | IS_HAPROXY_ENABLED: 'isHaproxyEnabled', 3 | IS_DEPLOY_TO_DATABASE_SERVERS: 'isDeployToDatabaseServers', 4 | LOAD_BALANCER_DATABASES: 'loadBalancerDatabases', 5 | LOAD_BALANCER_DATABASES_HOSTNAME: 'loadBalancerDatabasesHostname', 6 | LOAD_BALANCER_DATABASES_IP_ADDRESS: 'loadBalancerDatabasesIpAddress', 7 | }); 8 | 9 | export const LOAD_BALANCERS_DATABASES_DEFAULT_VALUES = Object.freeze({ 10 | [LOAD_BALANCERS_FIELD_NAMES.LOAD_BALANCER_DATABASES_HOSTNAME]: '', 11 | [LOAD_BALANCERS_FIELD_NAMES.LOAD_BALANCER_DATABASES_IP_ADDRESS]: '', 12 | }); 13 | -------------------------------------------------------------------------------- /automation/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | collections: 3 | - name: amazon.aws 4 | version: ">=10.1.2" 5 | - name: community.aws 6 | version: ">=10.0.0" 7 | - name: google.cloud 8 | version: ">=1.10.2" 9 | - name: azure.azcollection 10 | version: ">=3.10.1" 11 | - name: community.digitalocean 12 | version: ">=1.27.0" 13 | - name: hetzner.hcloud 14 | version: ">=5.4.0" 15 | - name: community.postgresql 16 | version: ">=4.1.0" 17 | - name: community.docker 18 | version: ">=4.8.2" 19 | - name: community.general 20 | version: ">=11.4.1" 21 | - name: ansible.posix 22 | version: ">=2.1.0" 23 | - name: ansible.utils 24 | version: ">=6.0.0" 25 | -------------------------------------------------------------------------------- /automation/roles/timezone/README.md: -------------------------------------------------------------------------------- 1 | # Ansible Role: timezone 2 | 3 | Sets the system timezone on target hosts. Installs `tzdata` when using package-based installations and applies the timezone via `community.general.timezone` module. 4 | 5 | ## Role Variables 6 | 7 | | Variable | Default | Description | 8 | |---------------------|--------------|-------------| 9 | | timezone | "" | IANA timezone string (e.g., "Etc/UTC", "Europe/Berlin"). | 10 | 11 | Note: The role runs only when timezone variable is non-empty. 12 | 13 | ## Dependencies 14 | 15 | This role depends on: 16 | - `vitabaks.autobase.common` - Provides common variables and configurations 17 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/cloud-region-block/lib/hooks.tsx: -------------------------------------------------------------------------------- 1 | import AWSIcon from '../assets/aws.svg'; 2 | import GCPIcon from '../assets/gcp.svg'; 3 | import AzureIcon from '../assets/azure.svg'; 4 | import DigitalOceanIcon from '../assets/digitalocean.svg'; 5 | import HetznerIcon from '../assets/hetzner.svg'; 6 | import { PROVIDERS } from '@shared/config/constants.ts'; 7 | 8 | export const useNameIconProvidersMap = () => ({ 9 | // TODO: refactor into moving from hooks to constant 10 | [PROVIDERS.AWS]: AWSIcon, 11 | [PROVIDERS.GCP]: GCPIcon, 12 | [PROVIDERS.AZURE]: AzureIcon, 13 | [PROVIDERS.DIGITAL_OCEAN]: DigitalOceanIcon, 14 | [PROVIDERS.HETZNER]: HetznerIcon, 15 | }); 16 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/storageIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /automation/roles/consul/vars/Windows.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # File: Windows.yml - Windows OS variables for Consul 3 | 4 | # paths 5 | consul_windows_path: /ProgramData/consul 6 | consul_bin_path: "{{ consul_windows_path }}/bin" 7 | consul_config_path: "{{ consul_windows_path }}/config" 8 | consul_configd_path: "{{ consul_config_path }}.d/" 9 | consul_bootstrap_state: "{{ consul_windows_path }}/.consul_bootstrapped" 10 | consul_data_path: "{{ consul_windows_path }}/data" 11 | consul_log_path: "{{ consul_windows_path }}/log" 12 | consul_run_path: "{{ consul_windows_path }}" 13 | consul_binary: "{{ consul_windows_path }}/bin/consul.exe" 14 | consul_syslog_enable: false 15 | 16 | # users 17 | consul_user: LocalSystem 18 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/ramIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/copy-icon/assets/copyIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /console/service/pkg/patroni/models.go: -------------------------------------------------------------------------------- 1 | package patroni 2 | 3 | type MonitoringInfo struct { 4 | State string `json:"state"` 5 | Role string `json:"role"` 6 | ServerVersion int `json:"server_version"` 7 | } 8 | 9 | type ClusterInfo struct { 10 | Members []struct { 11 | Name string `json:"name"` 12 | Role string `json:"role"` 13 | State string `json:"state"` 14 | Host string `json:"host"` 15 | Timeline int64 `json:"timeline"` 16 | Lag interface{} `json:"lag"` 17 | Tags interface{} `json:"tags"` 18 | PendingRestart bool `json:"pending_restart"` 19 | } `json:"members"` 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/flake8.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Flake8" 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Set TERM environment variable 18 | run: echo "TERM=xterm" >> $GITHUB_ENV 19 | 20 | - name: Checkout 21 | uses: actions/checkout@v6 22 | 23 | - name: Set up Python 24 | uses: actions/setup-python@v6 25 | with: 26 | python-version: "3.14" 27 | 28 | - name: Install dependencies 29 | run: make bootstrap-dev 30 | 31 | - name: Run Flake8 32 | run: make linter-flake8 33 | -------------------------------------------------------------------------------- /automation/roles/vip_manager/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart vip-manager service 3 | ansible.builtin.systemd: 4 | daemon_reload: true 5 | name: vip-manager 6 | state: restarted 7 | enabled: true 8 | listen: "restart vip-manager" 9 | 10 | - name: Wait for the cluster ip address (VIP) "{{ cluster_vip | default('') }}" is running 11 | ansible.builtin.wait_for: 12 | host: "{{ cluster_vip }}" 13 | port: "{{ pgbouncer_listen_port if pgbouncer_install | bool else postgresql_port }}" 14 | state: started 15 | timeout: 15 # max wait time: 30 seconds 16 | delay: 2 17 | ignore_errors: true # show the error and continue the playbook execution 18 | listen: "restart vip-manager" 19 | -------------------------------------------------------------------------------- /console/service/internal/xdocker/imanager.go: -------------------------------------------------------------------------------- 1 | package xdocker 2 | 3 | import "context" 4 | 5 | type InstanceID string 6 | type ManageClusterConfig struct { 7 | Envs []string 8 | ExtraVars string // JSON string 9 | Mounts []Mount 10 | } 11 | 12 | type Mount struct { 13 | DockerPath string 14 | HostPath string 15 | } 16 | 17 | type IManager interface { 18 | ManageCluster(ctx context.Context, req *ManageClusterConfig) (InstanceID, error) 19 | GetStatus(ctx context.Context, id InstanceID) (string, error) 20 | StoreContainerLogs(ctx context.Context, id InstanceID, store func(logMessage string)) 21 | PreloadImage(ctx context.Context) 22 | RemoveContainer(ctx context.Context, id InstanceID) error 23 | } 24 | -------------------------------------------------------------------------------- /console/ui/src/shared/api/baseApi.ts: -------------------------------------------------------------------------------- 1 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; 2 | import { API_URL } from '@shared/config/constants.ts'; 3 | import i18n from 'i18next'; 4 | 5 | export const baseApi = createApi({ 6 | baseQuery: fetchBaseQuery({ 7 | baseUrl: API_URL as string, 8 | prepareHeaders: (headers, { endpoint }) => { 9 | headers.set('Accept-Language', i18n.language); 10 | if (endpoint !== 'login') headers.set('Authorization', `Bearer ${String(localStorage.getItem('token'))}`); 11 | return headers; 12 | }, 13 | }), 14 | tagTypes: ['Clusters', 'Operations', 'Secrets', 'Projects', 'Environments', 'Settings'], 15 | endpoints: () => ({}), 16 | }); 17 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/calendarClockICon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/yamllint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Yamllint" 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Set TERM environment variable 18 | run: echo "TERM=xterm" >> $GITHUB_ENV 19 | 20 | - name: Checkout 21 | uses: actions/checkout@v6 22 | 23 | - name: Set up Python 24 | uses: actions/setup-python@v6 25 | with: 26 | python-version: "3.14" 27 | 28 | - name: Install dependencies 29 | run: make bootstrap-dev 30 | 31 | - name: Run Yamllint 32 | run: make linter-yamllint 33 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/copy-icon/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { CopyIconProps } from '@shared/ui/copy-icon/model/types.ts'; 3 | import { useCopyToClipboard } from '@shared/lib/hooks.tsx'; 4 | import CopyValueIcon from '@mui/icons-material/ContentCopyOutlined'; 5 | import { Box, SxProps, Theme } from '@mui/material'; 6 | 7 | const CopyIcon: FC }> = ({ valueToCopy, sx }) => { 8 | const [_, copyFunction] = useCopyToClipboard(); 9 | 10 | return ( 11 | copyFunction(valueToCopy)} sx={{ cursor: 'pointer', ...sx }}> 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default CopyIcon; 18 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/docsIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/load-balancers-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { UseFieldArrayRemove } from 'react-hook-form'; 2 | import { LOAD_BALANCERS_FIELD_NAMES } from './const'; 3 | 4 | export interface LoadBalancersDatabaseBoxProps { 5 | index: number; 6 | remove?: UseFieldArrayRemove; 7 | } 8 | 9 | export interface LoadBalancersBlockValues { 10 | [LOAD_BALANCERS_FIELD_NAMES.IS_HAPROXY_ENABLED]: boolean; 11 | [LOAD_BALANCERS_FIELD_NAMES.IS_DEPLOY_TO_DATABASE_SERVERS]?: boolean; 12 | [LOAD_BALANCERS_FIELD_NAMES.LOAD_BALANCER_DATABASES]: { 13 | [LOAD_BALANCERS_FIELD_NAMES.LOAD_BALANCER_DATABASES_HOSTNAME]?: string; 14 | [LOAD_BALANCERS_FIELD_NAMES.LOAD_BALANCER_DATABASES_IP_ADDRESS]?: string; 15 | }[]; 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/ansible-lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Ansible-lint" 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Set TERM environment variable 18 | run: echo "TERM=xterm" >> $GITHUB_ENV 19 | 20 | - name: Checkout 21 | uses: actions/checkout@v6 22 | 23 | - name: Set up Python 24 | uses: actions/setup-python@v6 25 | with: 26 | python-version: "3.14" 27 | 28 | - name: Install dependencies 29 | run: make bootstrap-dev 30 | 31 | - name: Run Ansible-lint 32 | run: make linter-ansible-lint 33 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/cpuIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/service/middleware/cid.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "postgresql-cluster-console/pkg/tracer" 7 | 8 | "github.com/google/uuid" 9 | ) 10 | 11 | func SetCorrelationId(next http.Handler) http.Handler { 12 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 | cid := getCid(r) 14 | if r.Header.Get(XCorrID) == "" { 15 | r.Header.Set(XCorrID, cid) 16 | } 17 | 18 | ctx := context.WithValue(r.Context(), tracer.CtxCidKey{}, cid) 19 | next.ServeHTTP(w, r.WithContext(ctx)) 20 | }) 21 | } 22 | 23 | func getCid(r *http.Request) string { 24 | cid := r.Header.Get(XCorrID) 25 | if cid != "" { 26 | return cid 27 | } 28 | 29 | return uuid.New().String() 30 | } 31 | -------------------------------------------------------------------------------- /automation/roles/deploy_finish/README.md: -------------------------------------------------------------------------------- 1 | # Ansible Role: deploy_finish 2 | 3 | Auxiliary role that prints PostgreSQL cluster information and connection details at the end of a playbook run. 4 | 5 | ## What it prints 6 | - PostgreSQL users list 7 | - PostgreSQL databases list 8 | - Patroni cluster status (patronictl list) 9 | - Connection endpoints depending on environment: 10 | - etcd + cluster_vip (with/without HAProxy) 11 | - etcd without VIP (HAProxy or direct PostgreSQL, with optional PgBouncer) 12 | - Consul-based DNS names 13 | - Cloud load balancers (AWS CLB/NLB, GCP, Azure, DigitalOcean, Hetzner) 14 | 15 | ## Dependencies 16 | 17 | This role depends on: 18 | - `vitabaks.autobase.common` - Provides common variables and configurations 19 | -------------------------------------------------------------------------------- /console/ui/src/app/router/routerPathsConfig/index.ts: -------------------------------------------------------------------------------- 1 | import routerClustersPathsConfig from '@app/router/routerPathsConfig/routerClustersPathsConfig.ts'; 2 | import routerOperationsPathsConfig from '@app/router/routerPathsConfig/routerOperationsPathsConfig.ts'; 3 | import routerSettingsPathsConfig from '@app/router/routerPathsConfig/routerSettingsPathsConfig.ts'; 4 | 5 | /* 6 | Combines route paths into one config 7 | */ 8 | const RouterPaths = { 9 | login: { 10 | absolutePath: 'login', 11 | }, 12 | notFound: { 13 | absolutePath: 'notFound', 14 | }, 15 | clusters: routerClustersPathsConfig, 16 | operations: routerOperationsPathsConfig, 17 | settings: routerSettingsPathsConfig, 18 | } as const; 19 | 20 | export default RouterPaths; 21 | -------------------------------------------------------------------------------- /automation/roles/patroni/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Reload patroni service 3 | ansible.builtin.systemd: 4 | daemon_reload: true 5 | name: patroni 6 | enabled: true 7 | state: reloaded 8 | register: patroni_reload_result 9 | failed_when: false # ignore 'Could not find the requested service' for initial deployment. 10 | listen: "reload patroni" 11 | 12 | - name: Reload postgres 13 | become: true 14 | become_user: postgres 15 | ansible.builtin.command: "{{ postgresql_bin_dir }}/pg_ctl reload -D {{ postgresql_data_dir }}" 16 | register: pg_ctl_reload_result 17 | changed_when: pg_ctl_reload_result.rc == 0 18 | failed_when: false # exec 'reload' on all running postgres (to re-run with --tag pg_hba). 19 | listen: "reload postgres" 20 | -------------------------------------------------------------------------------- /automation/roles/resolv_conf/README.md: -------------------------------------------------------------------------------- 1 | # Ansible Role: resolv_conf 2 | 3 | A role to manage system DNS resolvers by ensuring /etc/resolv.conf exists and contains the provided nameserver entries. 4 | 5 | ## Role Variables 6 | 7 | | Variable | Default | Description | 8 | |--------------|---------|-------------| 9 | | nameservers | not set (role does nothing if empty) | List of DNS servers to add to /etc/resolv.conf (e.g., ["1.1.1.1", "8.8.8.8"]). If inherited from the common role, the default is Cloudflare + Google. | 10 | 11 | Note: Existing lines are not removed or reordered; duplicates are avoided per entry via regexp. 12 | 13 | ## Dependencies 14 | 15 | This role depends on: 16 | - `vitabaks.autobase.common` - Provides common variables and configurations 17 | -------------------------------------------------------------------------------- /console/ui/src/widgets/projects-table/lib/hooks.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { ResponseProject } from '@shared/api/api/projects.ts'; 3 | import { PROJECTS_TABLE_COLUMN_NAMES } from '@widgets/projects-table/model/constants.ts'; 4 | 5 | export const useGetProjectsTableData = (data: ResponseProject[]) => 6 | useMemo( 7 | () => 8 | data?.map((secret) => ({ 9 | [PROJECTS_TABLE_COLUMN_NAMES.ID]: secret.id, 10 | [PROJECTS_TABLE_COLUMN_NAMES.NAME]: secret.name, 11 | [PROJECTS_TABLE_COLUMN_NAMES.CREATED]: secret.created_at, 12 | [PROJECTS_TABLE_COLUMN_NAMES.UPDATED]: secret.updated_at, 13 | [PROJECTS_TABLE_COLUMN_NAMES.DESCRIPTION]: secret.description ?? '-', 14 | })) ?? [], 15 | [data], 16 | ); 17 | -------------------------------------------------------------------------------- /automation/roles/firewall/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Note: Some variables defined in roles/common/defaults/main.yml. Commented out here to prevent conflicts. 3 | 4 | firewall_state: started 5 | # firewall_enabled_at_boot: true 6 | 7 | firewall_flush_rules_and_chains: true 8 | 9 | firewall_allowed_tcp_ports: 10 | - "22" 11 | - "25" 12 | - "80" 13 | - "443" 14 | firewall_allowed_udp_ports: [] 15 | firewall_forwarded_tcp_ports: [] 16 | firewall_forwarded_udp_ports: [] 17 | firewall_additional_rules: [] 18 | firewall_enable_ipv6: true 19 | firewall_ip6_additional_rules: [] 20 | firewall_log_dropped_packets: true 21 | # Set to true to ensure other firewall management software is disabled. 22 | # firewall_disable_firewalld: false 23 | # firewall_disable_ufw: false 24 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/databases-block/model/validation.ts: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup'; 2 | import { DATABASES_BLOCK_FIELD_NAMES } from '@entities/cluster/expert-mode/databases-block/model/const.ts'; 3 | 4 | export const DatabasesBlockSchema = yup.object({ 5 | [DATABASES_BLOCK_FIELD_NAMES.DATABASES]: yup.array( 6 | yup.object({ 7 | [DATABASES_BLOCK_FIELD_NAMES.DATABASE_NAME]: yup.string(), 8 | [DATABASES_BLOCK_FIELD_NAMES.USER_NAME]: yup.string(), 9 | [DATABASES_BLOCK_FIELD_NAMES.USER_PASSWORD]: yup.string(), 10 | [DATABASES_BLOCK_FIELD_NAMES.ENCODING]: yup.string(), 11 | [DATABASES_BLOCK_FIELD_NAMES.LOCALE]: yup.string(), 12 | [DATABASES_BLOCK_FIELD_NAMES.BLOCK_ID]: yup.string(), 13 | }), 14 | ), 15 | }); 16 | -------------------------------------------------------------------------------- /console/ui/src/app/redux/slices/projectSlice/projectSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | 3 | interface ProjectSliceState { 4 | currentProject: string | null; 5 | } 6 | 7 | const initialState: ProjectSliceState = { 8 | currentProject: localStorage.getItem('currentProject') ?? '', 9 | }; 10 | 11 | export const projectSlice = createSlice({ 12 | name: 'project', 13 | initialState, 14 | reducers: { 15 | setProject: (state: ProjectSliceState, action: PayloadAction) => { 16 | state.currentProject = action.payload; 17 | localStorage.setItem('currentProject', action.payload); 18 | }, 19 | }, 20 | }); 21 | 22 | export const { setProject } = projectSlice.actions; 23 | 24 | export default projectSlice.reducer; 25 | -------------------------------------------------------------------------------- /console/ui/src/features/bradcrumbs/hooks/useBreadcrumbs.tsx: -------------------------------------------------------------------------------- 1 | import { useMatches } from 'react-router-dom'; 2 | import { useTranslation } from 'react-i18next'; 3 | 4 | const useBreadcrumbs = (): { label: string; path: string }[] => { 5 | const { t } = useTranslation(); 6 | const matches = useMatches(); 7 | 8 | return matches 9 | .filter((match: any) => Boolean(match?.handle?.breadcrumb)) 10 | .map((match) => ({ 11 | label: 12 | typeof match.handle.breadcrumb.label === 'function' 13 | ? match.handle.breadcrumb.label({ ...match.params }) 14 | : t(match.handle.breadcrumb.label, { ns: match.handle.breadcrumb.ns }), 15 | path: match.handle.breadcrumb?.path ?? match.pathname, 16 | })); 17 | }; 18 | 19 | export default useBreadcrumbs; 20 | -------------------------------------------------------------------------------- /console/service/internal/convert/postgres_versions.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "postgresql-cluster-console/internal/storage" 5 | "postgresql-cluster-console/models" 6 | 7 | "github.com/go-openapi/strfmt" 8 | ) 9 | 10 | func PostgresVersion(pv *storage.PostgresVersion) *models.ResponsePostgresVersion { 11 | return &models.ResponsePostgresVersion{ 12 | EndOfLife: strfmt.Date(pv.EndOfLife), 13 | MajorVersion: pv.MajorVersion, 14 | ReleaseDate: strfmt.Date(pv.ReleaseDate), 15 | } 16 | } 17 | 18 | func PostgresVersions(pvs []storage.PostgresVersion) []*models.ResponsePostgresVersion { 19 | resp := make([]*models.ResponsePostgresVersion, 0, len(pvs)) 20 | for _, pv := range pvs { 21 | resp = append(resp, PostgresVersion(&pv)) 22 | } 23 | 24 | return resp 25 | } 26 | -------------------------------------------------------------------------------- /console/ui/src/widgets/main/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC, Suspense } from 'react'; 2 | import { Divider, Stack, Toolbar, useTheme } from '@mui/material'; 3 | import { Outlet } from 'react-router-dom'; 4 | import Breadcrumbs from '@features/bradcrumbs'; 5 | import Spinner from '@shared/ui/spinner'; 6 | 7 | const Main: FC = () => { 8 | const theme = useTheme(); 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 | }> 16 | 17 | 18 | 19 |
20 | )}; 21 | 22 | export default Main; 23 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/dcs-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { UseFieldArrayRemove } from 'react-hook-form'; 2 | import { DCS_BLOCK_FIELD_NAMES } from './const'; 3 | 4 | export interface DcsDatabaseBoxProps { 5 | index: number; 6 | remove?: UseFieldArrayRemove; 7 | fields: Record[]; 8 | } 9 | 10 | export interface DcsBlockFormValues { 11 | [DCS_BLOCK_FIELD_NAMES.TYPE]: string; 12 | [DCS_BLOCK_FIELD_NAMES.IS_DEPLOY_NEW_CLUSTER]: boolean; 13 | [DCS_BLOCK_FIELD_NAMES.IS_DEPLOY_TO_DB_SERVERS]: boolean; 14 | [DCS_BLOCK_FIELD_NAMES.DCS_DATABASES]?: { 15 | [DCS_BLOCK_FIELD_NAMES.DCS_DATABASE_HOSTNAME]?: string; 16 | [DCS_BLOCK_FIELD_NAMES.DCS_DATABASE_IP_ADDRESS]?: string; 17 | [DCS_BLOCK_FIELD_NAMES.DCS_DATABASE_PORT]?: string; 18 | }[]; 19 | } 20 | -------------------------------------------------------------------------------- /automation/roles/postgresql_schemas/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Make sure the PostgreSQL schemas are present 3 | become: true 4 | become_user: postgres 5 | community.postgresql.postgresql_schema: 6 | name: "{{ item.schema }}" 7 | owner: "{{ item.owner }}" 8 | login_db: "{{ item.db }}" 9 | login_host: "127.0.0.1" 10 | login_port: "{{ postgresql_port }}" 11 | login_user: "{{ patroni_superuser_username }}" 12 | login_password: "{{ patroni_superuser_password }}" 13 | state: present 14 | ignore_errors: true 15 | loop: "{{ postgresql_schemas | flatten(1) }}" 16 | when: 17 | - postgresql_schemas | default('') | length > 0 18 | - patroni_standby_cluster.host | default('') | length < 1 # do not perform on the Standby Cluster 19 | tags: postgresql_schemas 20 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/connection-pools-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { UseFieldArrayRemove } from 'react-hook-form'; 2 | import { CONNECTION_POOLS_BLOCK_FIELD_NAMES } from '@entities/cluster/expert-mode/connection-pools-block/model/const.ts'; 3 | 4 | export interface ConnectionPoolBlockProps { 5 | index: number; 6 | remove?: UseFieldArrayRemove; 7 | } 8 | 9 | export interface ConnectionPoolBlockValues { 10 | [CONNECTION_POOLS_BLOCK_FIELD_NAMES.IS_CONNECTION_POOLER_ENABLED]?: boolean; 11 | [CONNECTION_POOLS_BLOCK_FIELD_NAMES.POOLS]: [ 12 | { 13 | [CONNECTION_POOLS_BLOCK_FIELD_NAMES.POOL_NAME]?: string; 14 | [CONNECTION_POOLS_BLOCK_FIELD_NAMES.POOL_SIZE]?: number; 15 | [CONNECTION_POOLS_BLOCK_FIELD_NAMES.POOL_MODE]?: string; 16 | }, 17 | ]; 18 | } 19 | -------------------------------------------------------------------------------- /automation/roles/firewall/.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: python 3 | services: docker 4 | 5 | env: 6 | global: 7 | - ROLE_NAME: firewall 8 | matrix: 9 | - MOLECULE_DISTRO: centos7 10 | - MOLECULE_DISTRO: centos6 11 | - MOLECULE_DISTRO: ubuntu1804 12 | - MOLECULE_DISTRO: ubuntu1604 13 | - MOLECULE_DISTRO: debian9 14 | 15 | install: 16 | # Install test dependencies. 17 | - pip install molecule yamllint ansible-lint docker 18 | 19 | before_script: 20 | # Use actual Ansible Galaxy role name for the project directory. 21 | - cd ../ 22 | - mv ansible-role-$ROLE_NAME geerlingguy.$ROLE_NAME 23 | - cd geerlingguy.$ROLE_NAME 24 | 25 | script: 26 | # Run tests. 27 | - molecule test 28 | 29 | notifications: 30 | webhooks: https://galaxy.ansible.com/api/v1/notifications/ 31 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/database-servers-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { UseFieldArrayRemove } from 'react-hook-form'; 2 | import { DATABASE_SERVERS_FIELD_NAMES } from '@entities/cluster/database-servers-block/model/const.ts'; 3 | 4 | export interface DatabaseServerBlockProps { 5 | index: number; 6 | remove?: UseFieldArrayRemove; 7 | } 8 | 9 | export interface DatabaseServerBlockValues { 10 | [DATABASE_SERVERS_FIELD_NAMES.IS_CLUSTER_EXISTS]?: boolean; 11 | [DATABASE_SERVERS_FIELD_NAMES.DATABASE_SERVERS]: { 12 | [DATABASE_SERVERS_FIELD_NAMES.DATABASE_HOSTNAME]: string; 13 | [DATABASE_SERVERS_FIELD_NAMES.DATABASE_IP_ADDRESS]: string; 14 | [DATABASE_SERVERS_FIELD_NAMES.DATABASE_LOCATION]: string; 15 | [DATABASE_SERVERS_FIELD_NAMES.IS_POSTGRESQL_EXISTS]?: boolean; 16 | }[]; 17 | } 18 | -------------------------------------------------------------------------------- /automation/molecule/tests/roles/confd/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # 🚀 This task is designed to include variable tests for the main role in the Confd molecule tests 3 | # 🎯 The goal is to ensure that all variable tests are executed in a systematic and organised manner 4 | 5 | # 🔄 Including variable tests for the main role in the Confd molecule tests 6 | # We use a loop to include all YAML files in the 'variables' directory 7 | # Each file is included as a task, ensuring that all variable tests are executed 8 | - name: Molecule.tests.roles.confd.main | Include Variable Tests 9 | run_once: true 10 | ansible.builtin.include_tasks: "{{ molecule_tests_roles_confd_main_file }}" 11 | loop: "{{ lookup('fileglob', 'variables/*.yml', wantlist=True) }}" 12 | loop_control: 13 | loop_var: molecule_tests_roles_confd_main_file 14 | -------------------------------------------------------------------------------- /automation/molecule/tests/variables/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # 🚀 This task aims to include all assert tasks for the main variables in Molecule tests 3 | # 🎯 The objective is to ensure that all assert tasks are executed for comprehensive testing 4 | 5 | # 🔄 Including all assert tasks found in the 'asserts' directory 6 | # For each .yml file in the 'asserts' directory, we include the tasks defined in the file 7 | # This allows us to modularize our tests and keep our codebase organized 8 | - name: Molecule.tests.variables.main | Include All Assert Tasks for Comprehensive Testing 9 | run_once: true 10 | ansible.builtin.include_tasks: "{{ molecule_tests_variables_main_file }}" 11 | loop: "{{ lookup('fileglob', 'asserts/*.yml', wantlist=True) }}" 12 | loop_control: 13 | loop_var: molecule_tests_variables_main_file 14 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/connection-pools-block/model/const.ts: -------------------------------------------------------------------------------- 1 | export const CONNECTION_POOLS_BLOCK_FIELD_NAMES = Object.freeze({ 2 | IS_CONNECTION_POOLER_ENABLED: 'isConnectionPoolerEnabled', 3 | POOLS: 'pools', 4 | POOL_NAME: 'poolName', 5 | POOL_SIZE: 'poolSize', 6 | POOL_MODE: 'poolMode', 7 | }); 8 | 9 | export const POOL_MODES = Object.freeze([ 10 | { 11 | option: 'transaction', 12 | tooltip: 'Server is released back to pool after transaction finishes.', 13 | }, 14 | { option: 'session', tooltip: 'Server is released back to pool after client disconnects.' }, 15 | { 16 | option: 'statement', 17 | tooltip: 18 | 'Server is released back to pool after query finishes. Transactions spanning multiple statements are disallowed in this mode.', 19 | }, 20 | ]); 21 | -------------------------------------------------------------------------------- /console/ui/src/widgets/environments-table/lib/hooks.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { ENVIRONMENTS_TABLE_COLUMN_NAMES } from '@widgets/environments-table/model/constants.ts'; 3 | import { ResponseEnvironment } from '@shared/api/api/environments.ts'; 4 | 5 | export const useGetEnvironmentsTableData = (data: ResponseEnvironment[]) => 6 | useMemo( 7 | () => 8 | data?.map((secret) => ({ 9 | [ENVIRONMENTS_TABLE_COLUMN_NAMES.ID]: secret.id, 10 | [ENVIRONMENTS_TABLE_COLUMN_NAMES.NAME]: secret.name, 11 | [ENVIRONMENTS_TABLE_COLUMN_NAMES.CREATED]: secret.created_at, 12 | [ENVIRONMENTS_TABLE_COLUMN_NAMES.UPDATED]: secret.updated_at, 13 | [ENVIRONMENTS_TABLE_COLUMN_NAMES.DESCRIPTION]: secret.description ?? '-', 14 | })) ?? [], 15 | [data], 16 | ); 17 | -------------------------------------------------------------------------------- /automation/molecule/tests/roles/patroni/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # 🚀 The objective of this task is to include and execute variable tests for the main Patroni role 3 | # 🎯 This ensures that all variable tests are run, providing comprehensive coverage and validation of the role's variables 4 | 5 | # 🔄 Including and executing variable tests for the main Patroni role 6 | # We use a loop to iterate over all .yml files in the 'variables' directory 7 | # Each file is included and its tasks are executed 8 | - name: Molecule.tests.roles.patroni.main | Include and Execute Variable Tests 9 | run_once: true 10 | ansible.builtin.include_tasks: "{{ molecule_tests_roles_patroni_main_file }}" 11 | loop: "{{ lookup('fileglob', 'variables/*.yml', wantlist=True) }}" 12 | loop_control: 13 | loop_var: molecule_tests_roles_patroni_main_file 14 | -------------------------------------------------------------------------------- /automation/roles/etcd/templates/etcd.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Etcd Server 3 | After=network.target 4 | After=network-online.target 5 | Wants=network-online.target 6 | 7 | [Service] 8 | Type=notify 9 | WorkingDirectory={{ etcd_data_dir | default('/var/lib/etcd') }} 10 | EnvironmentFile=-{{ etcd_conf_dir | default('/etc/etcd') }}/etcd.conf 11 | User=etcd 12 | # set GOMAXPROCS to number of processors 13 | ExecStart=/bin/bash -c "GOMAXPROCS=$(nproc) /usr/local/bin/etcd" 14 | Restart=on-failure 15 | RestartSec=10 16 | StartLimitInterval=0 17 | TimeoutStartSec=180 18 | LimitNOFILE=65536 19 | 20 | {% if ansible_virtualization_type not in ['container', 'docker', 'lxc', 'podman'] %} 21 | IOSchedulingClass=realtime 22 | IOSchedulingPriority=0 23 | Nice=-20 24 | {% endif %} 25 | 26 | [Install] 27 | WantedBy=multi-user.target 28 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/providers-block/ui/ClusterFormCloudProviderBox.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import SelectableBox from '@shared/ui/selectable-box'; 3 | import { ClusterFormCloudProviderBoxProps } from '@entities/cluster/providers-block/model/types.ts'; 4 | 5 | const ClusterFormCloudProviderBox: FC = ({ children, isActive, ...props }) => { 6 | return ( 7 | 18 | {children} 19 | 20 | ); 21 | }; 22 | 23 | export default ClusterFormCloudProviderBox; 24 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/extensions-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { EXTENSION_BLOCK_FIELD_NAMES } from '@entities/cluster/expert-mode/extensions-block/model/const.ts'; 2 | import { ResponseDatabaseExtension } from '@shared/api/api/other.ts'; 3 | 4 | export interface ExtensionSelectorProps { 5 | extension: ResponseDatabaseExtension; 6 | } 7 | 8 | export interface ExtensionBoxProps extends ExtensionSelectorProps { 9 | extensionIcons: Record; 10 | } 11 | 12 | export interface ExtensionsSwiperProps extends Pick { 13 | isPending: boolean; 14 | filteredExtensions: ResponseDatabaseExtension[]; 15 | } 16 | 17 | export interface ExtensionsBlockValues { 18 | [EXTENSION_BLOCK_FIELD_NAMES.EXTENSIONS]?: Record; 19 | } 20 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/connection-pools-block/model/validation.ts: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup'; 2 | import { TFunction } from 'i18next'; 3 | import { CONNECTION_POOLS_BLOCK_FIELD_NAMES } from '@entities/cluster/expert-mode/connection-pools-block/model/const.ts'; 4 | 5 | export const ConnectionPoolsBlockSchema = (t: TFunction) => 6 | yup.object({ 7 | [CONNECTION_POOLS_BLOCK_FIELD_NAMES.IS_CONNECTION_POOLER_ENABLED]: yup.boolean(), 8 | [CONNECTION_POOLS_BLOCK_FIELD_NAMES.POOLS]: yup.array( 9 | yup.object({ 10 | [CONNECTION_POOLS_BLOCK_FIELD_NAMES.POOL_NAME]: yup.string(), 11 | [CONNECTION_POOLS_BLOCK_FIELD_NAMES.POOL_SIZE]: yup.number().typeError(t('onlyNumbers', { ns: 'validation' })), 12 | [CONNECTION_POOLS_BLOCK_FIELD_NAMES.POOL_MODE]: yup.string(), 13 | }), 14 | ), 15 | }); 16 | -------------------------------------------------------------------------------- /automation/roles/bind_address/README.md: -------------------------------------------------------------------------------- 1 | ## Ansible Role: bind_address 2 | 3 | This role automatically detects and sets the available private IPv4 address for each host as the variable `bind_address`, unless it is already defined in inventory or group_vars. 4 | 5 | #### How it works 6 | 7 | - Finds the first available private IPv4 address on the host (excluding the docker0 interface, if present). 8 | - Sets this address as the Ansible fact `bind_address` using `set_fact`. 9 | - If `bind_address` is already defined in inventory or variables, it will not be executed. 10 | - If no suitable private IP is found, the role fails with a clear message. 11 | 12 | #### Recommendation 13 | 14 | If a host has multiple IP addresses (e.g., multiple interfaces/VLANs), explicitly set `bind_address` in inventory for each host instead of relying on auto-detection. 15 | -------------------------------------------------------------------------------- /automation/roles/pgbackrest/tasks/bootstrap_script.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - block: # patroni cluster bootstrap script 3 | - name: Make sure the pgbackrest bootstrap script directory exist 4 | ansible.builtin.file: 5 | dest: /etc/patroni 6 | state: directory 7 | owner: postgres 8 | group: postgres 9 | 10 | - name: Create /etc/patroni/pgbackrest_bootstrap.sh script 11 | ansible.builtin.template: 12 | src: templates/pgbackrest_bootstrap.sh.j2 13 | dest: /etc/patroni/pgbackrest_bootstrap.sh 14 | owner: postgres 15 | group: postgres 16 | mode: "0775" 17 | when: 18 | - patroni_cluster_bootstrap_method is defined 19 | - patroni_cluster_bootstrap_method == "pgbackrest" 20 | - "'postgres_cluster' in group_names" 21 | tags: pgbackrest, pgbackrest_bootstrap_script 22 | -------------------------------------------------------------------------------- /automation/roles/transparent_huge_pages/README.md: -------------------------------------------------------------------------------- 1 | # Ansible Role: transparent_huge_pages 2 | 3 | Disables Linux Transparent Huge Pages (THP) for better PostgreSQL performance. The role creates and enables a systemd service that sets THP and defrag to "never". 4 | 5 | ## Role Variables 6 | 7 | | Variable | Default | Description | 8 | |-------------|---------|-------------| 9 | | disable_thp | true | When true, installs/enables a systemd service to disable THP and THP defrag. When false, the role does nothing. | 10 | 11 | ## Notes 12 | - Idempotent; updates the unit at /etc/systemd/system/disable-transparent-huge-pages.service 13 | - Not executed on virtualization types: container, docker, lxc, podman. 14 | 15 | ## Dependencies 16 | 17 | This role depends on: 18 | - `vitabaks.autobase.common` - Provides common variables and configurations 19 | -------------------------------------------------------------------------------- /automation/roles/swap/README.md: -------------------------------------------------------------------------------- 1 | # Ansible Role: swap 2 | 3 | Manages system swap via a swap file. Creates, resizes, or removes the swap file when needed, adds it to /etc/fstab, and enables it with swapon. 4 | 5 | ## Role Variables 6 | 7 | | Variable | Default | Description | 8 | |---------------------|------------|-------------| 9 | | swap_file_create | true | Master toggle. If false, the role does nothing. | 10 | | swap_file_path | /swapfile | Path to the swap file. | 11 | | swap_file_size_mb | "4096" | Desired swap file size in megabytes. If different from current, the role recreates the swap. | 12 | 13 | Notes: 14 | - Skips on virtualization types: container, docker, lxc, podman. 15 | 16 | ## Dependencies 17 | 18 | This role depends on: 19 | - `vitabaks.autobase.common` - Provides common variables and configurations 20 | -------------------------------------------------------------------------------- /automation/molecule/default/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Update docker network(s)" 3 | hosts: localhost 4 | gather_facts: false 5 | become: false 6 | tasks: 7 | - name: "Create docker network: test_docker_network" 8 | community.docker.docker_network: 9 | name: test_docker_network 10 | driver: bridge 11 | driver_options: 12 | com.docker.network.driver.mtu: 1440 13 | enable_ipv6: false 14 | internal: false 15 | ipam_config: 16 | - subnet: 10.172.0.0/24 17 | gateway: 10.172.0.1 18 | force: true 19 | state: present 20 | labels: 21 | owner: molecule 22 | 23 | - name: "Install netaddr dependency on control host" 24 | ansible.builtin.pip: 25 | name: netaddr 26 | environment: 27 | PIP_BREAK_SYSTEM_PACKAGES: "1" 28 | -------------------------------------------------------------------------------- /automation/molecule/tests/roles/swap/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # 🚀 This task aims to include variable tests for the main swap role in Molecule 3 | # 🎯 The objective is to ensure all conditions are tested by looping through each YAML file in the 'conditions' directory 4 | 5 | # 🔄 Including variable tests for the main swap role in Molecule 6 | # For each YAML file in the 'conditions' directory, we include its tasks in the current playbook 7 | # If a YAML file is not found or cannot be read, the playbook execution will fail at this point 8 | - name: Molecule.tests.roles.swap.main | Include Variable Tests from Conditions Directory 9 | run_once: true 10 | ansible.builtin.include_tasks: "{{ molecule_tests_roles_swap_main_file }}" 11 | loop: "{{ lookup('fileglob', 'conditions/*.yml', wantlist=True) }}" 12 | loop_control: 13 | loop_var: molecule_tests_roles_swap_main_file 14 | -------------------------------------------------------------------------------- /automation/molecule/tests/roles/pre-checks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # 🚀 This task is designed to include variable tests in the main pre-checks for Molecule tests 3 | # 🎯 The objective is to ensure that all variable tests are properly included and executed 4 | 5 | # 🔄 Including variable tests in the main pre-checks for Molecule tests 6 | # For each YAML file in the 'variables' directory, we include its tasks in the main pre-checks 7 | # If a file does not exist or cannot be read, the task will fail and an error message will be displayed 8 | - name: Molecule.tests.roles.pre_checks.main | Include Variable Tests in Main Pre-checks 9 | run_once: true 10 | ansible.builtin.include_tasks: "{{ molecule_tests_roles_pre_checks_main_file }}" 11 | loop: "{{ lookup('fileglob', 'variables/*.yml', wantlist=True) }}" 12 | loop_control: 13 | loop_var: molecule_tests_roles_pre_checks_main_file 14 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/additional-settings-block/model/validation.ts: -------------------------------------------------------------------------------- 1 | import { TFunction } from 'i18next'; 2 | import * as yup from 'yup'; 3 | import { ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES } from '@entities/cluster/expert-mode/additional-settings-block/model/const.ts'; 4 | 5 | export const AdditionalSettingsBlockFormSchema = (t: TFunction) => 6 | yup.object({ 7 | [ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES.SYNC_STANDBY_NODES]: yup 8 | .number() 9 | .typeError(t('onlyNumbers', { ns: 'validation' })), 10 | [ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES.IS_SYNC_MODE_STRICT]: yup.boolean(), 11 | [ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES.IS_DB_PUBLIC_ACCESS]: yup.boolean(), 12 | [ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES.IS_CLOUD_LOAD_BALANCER]: yup.boolean(), 13 | [ADDITIONAL_SETTINGS_BLOCK_FIELD_NAMES.IS_NETDATA_MONITORING]: yup.boolean(), 14 | }); 15 | -------------------------------------------------------------------------------- /console/ui/src/shared/model/constants.ts: -------------------------------------------------------------------------------- 1 | export const AUTHENTICATION_METHODS = Object.freeze({ 2 | // changing names might break secrets POST request 3 | SSH: 'ssh_key', 4 | PASSWORD: 'password', 5 | }); 6 | 7 | export const LOCAL_STORAGE_ITEMS = Object.freeze({ 8 | IS_EXPERT_MODE: 'isExpertMode', 9 | IS_YAML_ENABLED: 'isYamlEnabled', 10 | }); 11 | 12 | export let IS_EXPERT_MODE = localStorage.getItem(LOCAL_STORAGE_ITEMS.IS_EXPERT_MODE)?.toString() === 'true'; 13 | export let IS_YAML_ENABLED = localStorage.getItem(LOCAL_STORAGE_ITEMS.IS_YAML_ENABLED)?.toString() === 'true'; 14 | 15 | window.addEventListener('storage', () => { 16 | IS_EXPERT_MODE = localStorage.getItem(LOCAL_STORAGE_ITEMS.IS_EXPERT_MODE)?.toString() === 'true'; // TODO: refactor 17 | IS_YAML_ENABLED = localStorage.getItem(LOCAL_STORAGE_ITEMS.IS_YAML_ENABLED)?.toString() === 'true'; // TODO: refactor 18 | }); 19 | -------------------------------------------------------------------------------- /.config/ansible-lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | skip_list: 3 | - command-instead-of-module 4 | - command-instead-of-shell 5 | - experimental 6 | - ignore-errors 7 | - no-changed-when 8 | - no-handler 9 | - no-relative-paths 10 | - package-latest 11 | - key-order[task] 12 | - var-naming[no-role-prefix] 13 | - yaml[indentation] 14 | - yaml[line-length] 15 | - name[missing] 16 | - name[unique] 17 | - name[template] 18 | - name[casing] # TODO: All names should start with an uppercase letter. 19 | - risky-file-permissions # TODO: File permissions unset or incorrect. 20 | - role-name # TODO: Avoid using paths when importing roles. Role name XXX does not match ``^*$`` pattern. 21 | - schema[playbook] # TODO: Use FQCN for `become_method`. 22 | - schema[tasks] # TODO: Use FQCN for `become_method`. 23 | - galaxy[no-changelog] 24 | 25 | exclude_paths: 26 | - automation/molecule/ 27 | -------------------------------------------------------------------------------- /automation/roles/timezone/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - block: 3 | - name: Gather package facts 4 | ansible.builtin.package_facts: 5 | manager: auto 6 | when: ansible_facts.packages is not defined 7 | check_mode: false 8 | 9 | - name: Make sure that the tzdata package is installed 10 | become: true 11 | become_method: sudo 12 | ansible.builtin.package: 13 | name: tzdata 14 | state: present 15 | environment: "{{ proxy_env | default({}) }}" 16 | when: 17 | - installation_method == "packages" 18 | - "'tzdata' not in ansible_facts.packages" 19 | 20 | - name: Set timezone to "{{ timezone | default('') }}" 21 | become: true 22 | become_method: sudo 23 | community.general.timezone: 24 | name: "{{ timezone }}" 25 | when: timezone is defined and timezone | length > 0 26 | tags: timezone 27 | -------------------------------------------------------------------------------- /.config/make/formatting.mak: -------------------------------------------------------------------------------- 1 | ## —— Formatting ——————————————————————————————————————————————————————————————————————————------- 2 | 3 | .PHONY: prettier 4 | prettier: ## Run Prettier formatting 5 | npx prettier --write . 6 | 7 | .PHONY: prettier-check 8 | prettier-check: ## Check formatting with Prettier (without modifying files) 9 | npx prettier --check . 10 | 11 | .PHONY: sql-format 12 | sql-format: ## Format all SQL files using sql-formatter 13 | find . -name "*.sql" -print0 | xargs -0 -n1 sql-formatter --fix 14 | 15 | # https://hub.docker.com/r/backplane/pgformatter 16 | .PHONY: pg-format 17 | pg-format: ## Format all SQL files using pgFormatter (PostgreSQL SQL queries and PL/PGSQL code beautifier) 18 | find . -name "*.sql" -print0 | xargs -0 -I{} \ 19 | docker run --rm -v "$(shell pwd):/work" -u $(shell id -u):$(shell id -g) \ 20 | backplane/pgformatter -u 1 -U 1 -f 1 -s 2 -W 0 -w 160 -i "{}" 21 | -------------------------------------------------------------------------------- /automation/molecule/pg_upgrade/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Update docker network(s)" 3 | hosts: localhost 4 | gather_facts: false 5 | become: false 6 | tasks: 7 | - name: "Create docker network: upgrade_test_docker_network" 8 | community.docker.docker_network: 9 | name: upgrade_test_docker_network 10 | driver: bridge 11 | driver_options: 12 | com.docker.network.driver.mtu: 1440 13 | enable_ipv6: false 14 | internal: false 15 | ipam_config: 16 | - subnet: 10.172.2.0/24 17 | gateway: 10.172.2.1 18 | force: true 19 | state: present 20 | labels: 21 | owner: molecule 22 | 23 | - name: "Install netaddr dependency on control host" 24 | ansible.builtin.pip: 25 | name: netaddr 26 | become: false 27 | environment: 28 | PIP_BREAK_SYSTEM_PACKAGES: "1" 29 | -------------------------------------------------------------------------------- /automation/roles/authorized_keys/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - block: 3 | - name: Get system username 4 | become: false 5 | ansible.builtin.command: whoami 6 | register: system_user 7 | changed_when: false 8 | 9 | - name: "Add public keys to ~{{ system_user.stdout | default('') }}/.ssh/authorized_keys" 10 | ansible.posix.authorized_key: 11 | user: "{{ system_user.stdout }}" 12 | key: "{{ item | replace(\"'\", '') | replace('\"', '') | trim }}" 13 | state: present 14 | loop: >- 15 | {{ 16 | (ssh_public_keys 17 | | replace('\n', ',') 18 | | split(',') 19 | | reject('equalto', '') 20 | | list) 21 | if ssh_public_keys is string else ssh_public_keys 22 | }} 23 | when: 24 | - ssh_public_keys is defined 25 | - ssh_public_keys | length > 0 26 | tags: ssh_public_keys 27 | -------------------------------------------------------------------------------- /console/ui/src/widgets/cluster-form/ui/ClusterFormLocalMachineFormPart.tsx: -------------------------------------------------------------------------------- 1 | import { FC, lazy } from 'react'; 2 | import DatabaseServersBlock from '@entities/cluster/database-servers-block'; 3 | import AuthenticationMethodFormBlock from '@entities/authentification-method-form-block'; 4 | import VipAddressBlock from '@entities/cluster/vip-address-block'; 5 | import LoadBalancersBlock from '@entities/cluster/load-balancers-block'; 6 | import { IS_EXPERT_MODE } from '@shared/model/constants.ts'; 7 | 8 | const DcsBlock = lazy(() => import('@entities/cluster/expert-mode/dcs-block/ui')); 9 | 10 | const ClusterFormLocalMachineFormPart: FC = () => ( 11 | <> 12 | 13 | {IS_EXPERT_MODE ? : null} 14 | 15 | 16 | 17 | 18 | ); 19 | 20 | export default ClusterFormLocalMachineFormPart; 21 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/githubIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/slider-box/model/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react'; 2 | import { Mark } from '@mui/material/Slider/useSlider.types'; 3 | 4 | export interface SliderBoxProps { 5 | amount: number; 6 | changeAmount: (...event: any[]) => void; 7 | icon?: ReactElement; 8 | unit?: string; 9 | min: number; 10 | max: number; 11 | marks?: { label: unknown; value: unknown }[]; 12 | marksAmount?: number; 13 | marksAdditionalLabel?: string; 14 | step?: number | null; 15 | error?: object; 16 | limitMin?: boolean; 17 | limitMax?: boolean; 18 | topRightElements?: ReactElement | null; 19 | } 20 | 21 | export type GenerateMarkType = (value: number, marksAdditionalLabel: string) => { label: string; value: number }; 22 | 23 | export type GenerateSliderMarksType = ( 24 | min: number, 25 | max: number, 26 | amount: number, 27 | marksAdditionalLabel: string, 28 | ) => Mark[]; 29 | -------------------------------------------------------------------------------- /.github/workflows/schedule_pg_debian12.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: scheduled PostgreSQL (Debian 12) 3 | 4 | on: 5 | schedule: 6 | - cron: "15 0 * * *" 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.repository_owner == 'vitabaks' || github.event_name == 'workflow_dispatch' }} 12 | 13 | steps: 14 | - name: Set TERM environment variable 15 | run: echo "TERM=xterm" >> $GITHUB_ENV 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v6 22 | with: 23 | python-version: "3.14" 24 | 25 | - name: Install dependencies 26 | run: make bootstrap-dev 27 | 28 | - name: Run Molecule tests 29 | run: make molecule-test 30 | env: 31 | PY_COLORS: "1" 32 | ANSIBLE_FORCE_COLOR: "1" 33 | IMAGE_DISTRO: debian12 34 | -------------------------------------------------------------------------------- /.github/workflows/schedule_pg_debian13.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: scheduled PostgreSQL (Debian 13) 3 | 4 | on: 5 | schedule: 6 | - cron: "15 0 * * *" 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.repository_owner == 'vitabaks' || github.event_name == 'workflow_dispatch' }} 12 | 13 | steps: 14 | - name: Set TERM environment variable 15 | run: echo "TERM=xterm" >> $GITHUB_ENV 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v6 22 | with: 23 | python-version: "3.14" 24 | 25 | - name: Install dependencies 26 | run: make bootstrap-dev 27 | 28 | - name: Run Molecule tests 29 | run: make molecule-test 30 | env: 31 | PY_COLORS: "1" 32 | ANSIBLE_FORCE_COLOR: "1" 33 | IMAGE_DISTRO: debian13 34 | -------------------------------------------------------------------------------- /.github/workflows/schedule_pg_ubuntu2204.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: scheduled PostgreSQL (Ubuntu 22.04) 3 | 4 | on: 5 | schedule: 6 | - cron: "30 0 * * *" 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.repository_owner == 'vitabaks' || github.event_name == 'workflow_dispatch' }} 12 | 13 | steps: 14 | - name: Set TERM environment variable 15 | run: echo "TERM=xterm" >> $GITHUB_ENV 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v6 22 | with: 23 | python-version: "3.14" 24 | 25 | - name: Install dependencies 26 | run: make bootstrap-dev 27 | 28 | - name: Run Molecule tests 29 | run: make molecule-test 30 | env: 31 | PY_COLORS: "1" 32 | ANSIBLE_FORCE_COLOR: "1" 33 | IMAGE_DISTRO: ubuntu2204 34 | -------------------------------------------------------------------------------- /.github/workflows/schedule_pg_ubuntu2404.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: scheduled PostgreSQL (Ubuntu 24.04) 3 | 4 | on: 5 | schedule: 6 | - cron: "30 0 * * *" 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.repository_owner == 'vitabaks' || github.event_name == 'workflow_dispatch' }} 12 | 13 | steps: 14 | - name: Set TERM environment variable 15 | run: echo "TERM=xterm" >> $GITHUB_ENV 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v6 22 | with: 23 | python-version: "3.14" 24 | 25 | - name: Install dependencies 26 | run: make bootstrap-dev 27 | 28 | - name: Run Molecule tests 29 | run: make molecule-test 30 | env: 31 | PY_COLORS: "1" 32 | ANSIBLE_FORCE_COLOR: "1" 33 | IMAGE_DISTRO: ubuntu2404 34 | -------------------------------------------------------------------------------- /.github/workflows/schedule_pg_rockylinux10.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: scheduled PostgreSQL (RockyLinux 10) 3 | 4 | on: 5 | schedule: 6 | - cron: "15 1 * * *" 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.repository_owner == 'vitabaks' || github.event_name == 'workflow_dispatch' }} 12 | 13 | steps: 14 | - name: Set TERM environment variable 15 | run: echo "TERM=xterm" >> $GITHUB_ENV 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v6 22 | with: 23 | python-version: "3.14" 24 | 25 | - name: Install dependencies 26 | run: make bootstrap-dev 27 | 28 | - name: Run Molecule tests 29 | run: make molecule-test 30 | env: 31 | PY_COLORS: "1" 32 | ANSIBLE_FORCE_COLOR: "1" 33 | IMAGE_DISTRO: rockylinux10 34 | -------------------------------------------------------------------------------- /.github/workflows/schedule_pg_rockylinux9.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: scheduled PostgreSQL (RockyLinux 9) 3 | 4 | on: 5 | schedule: 6 | - cron: "15 1 * * *" 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.repository_owner == 'vitabaks' || github.event_name == 'workflow_dispatch' }} 12 | 13 | steps: 14 | - name: Set TERM environment variable 15 | run: echo "TERM=xterm" >> $GITHUB_ENV 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v6 22 | with: 23 | python-version: "3.14" 24 | 25 | - name: Install dependencies 26 | run: make bootstrap-dev 27 | 28 | - name: Run Molecule tests 29 | run: make molecule-test 30 | env: 31 | PY_COLORS: "1" 32 | ANSIBLE_FORCE_COLOR: "1" 33 | IMAGE_DISTRO: rockylinux9 34 | -------------------------------------------------------------------------------- /console/service/internal/convert/database_extensions.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "postgresql-cluster-console/internal/storage" 5 | "postgresql-cluster-console/models" 6 | ) 7 | 8 | func DbExtensionToSwagger(ext *storage.Extension) *models.ResponseDatabaseExtension { 9 | return &models.ResponseDatabaseExtension{ 10 | Contrib: ext.Contrib, 11 | Description: ext.Description, 12 | Image: ext.Image, 13 | Name: ext.Name, 14 | PostgresMaxVersion: ext.PostgresMaxVersion, 15 | PostgresMinVersion: ext.PostgresMinVersion, 16 | URL: ext.Url, 17 | } 18 | } 19 | 20 | func DbExtensionsToSwagger(exts []storage.Extension) []*models.ResponseDatabaseExtension { 21 | resp := make([]*models.ResponseDatabaseExtension, 0, len(exts)) 22 | for _, ext := range exts { 23 | resp = append(resp, DbExtensionToSwagger(&ext)) 24 | } 25 | 26 | return resp 27 | } 28 | -------------------------------------------------------------------------------- /console/service/internal/convert/settings.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "postgresql-cluster-console/internal/storage" 5 | "postgresql-cluster-console/models" 6 | 7 | "github.com/go-openapi/strfmt" 8 | ) 9 | 10 | func SettingToSwagger(s *storage.Setting) *models.ResponseSetting { 11 | return &models.ResponseSetting{ 12 | CreatedAt: strfmt.DateTime(s.CreatedAt), 13 | ID: s.ID, 14 | Name: s.Name, 15 | UpdatedAt: func() *strfmt.DateTime { 16 | if s.UpdatedAt == nil { 17 | return nil 18 | } 19 | updated := strfmt.DateTime(*s.UpdatedAt) 20 | 21 | return &updated 22 | }(), 23 | Value: s.Value, 24 | } 25 | } 26 | 27 | func SettingsToSwagger(settings []storage.Setting) []*models.ResponseSetting { 28 | resp := make([]*models.ResponseSetting, 0, len(settings)) 29 | for _, s := range settings { 30 | resp = append(resp, SettingToSwagger(&s)) 31 | } 32 | 33 | return resp 34 | } 35 | -------------------------------------------------------------------------------- /console/ui/src/shared/ui/info-card-body/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { Divider, Stack, Typography } from '@mui/material'; 3 | import { InfoCardBodyProps } from '@shared/ui/info-card-body/model/types.ts'; 4 | 5 | /** 6 | * Component renders body of a different summary and overview cards. 7 | * Recommended to use inside all similar looking card bodies. 8 | * @param config - Config with data to render. 9 | * @constructor 10 | */ 11 | const InfoCardBody: FC = ({ config }) => ( 12 | 13 | {config.map(({ title, children }, index) => ( 14 | 15 | 16 | {title} 17 | 18 | {children} 19 | {index < config.length - 1 ? : null} 20 | 21 | ))} 22 | 23 | ); 24 | 25 | export default InfoCardBody; 26 | -------------------------------------------------------------------------------- /console/ui/src/widgets/yaml-editor-form/lib/functions.ts: -------------------------------------------------------------------------------- 1 | import { ClusterFormValues } from '@features/cluster-secret-modal/model/types.ts'; 2 | import { 3 | getBaseClusterExtraVars, 4 | getCloudProviderExtraVars, 5 | getLocalMachineExtraVars, 6 | } from '@shared/lib/clusterValuesTransformFunctions.ts'; 7 | import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; 8 | import { PROVIDERS } from '@shared/config/constants.ts'; 9 | 10 | /** 11 | * Function converts passed form values into correct YAML "key:value" format with mapped keys. 12 | * @param values - Filled form values. 13 | */ 14 | export const mapFormValuesToYamlEditor = (values: ClusterFormValues) => ({ 15 | ...getBaseClusterExtraVars(values), 16 | ...(values[CLUSTER_FORM_FIELD_NAMES.PROVIDER]?.code !== PROVIDERS.LOCAL 17 | ? { ...getCloudProviderExtraVars(values) } 18 | : { ...getLocalMachineExtraVars(values) }), 19 | }); 20 | -------------------------------------------------------------------------------- /console/service/internal/controllers/secret/delete_secret.go: -------------------------------------------------------------------------------- 1 | package secret 2 | 3 | import ( 4 | "postgresql-cluster-console/internal/controllers" 5 | "postgresql-cluster-console/internal/storage" 6 | "postgresql-cluster-console/restapi/operations/secret" 7 | 8 | "github.com/go-openapi/runtime/middleware" 9 | ) 10 | 11 | type deleteSecretHandler struct { 12 | db storage.IStorage 13 | } 14 | 15 | func NewDeleteSecretHandler(db storage.IStorage) secret.DeleteSecretsIDHandler { 16 | return &deleteSecretHandler{ 17 | db: db, 18 | } 19 | } 20 | 21 | func (h *deleteSecretHandler) Handle(param secret.DeleteSecretsIDParams) middleware.Responder { 22 | err := h.db.DeleteSecret(param.HTTPRequest.Context(), param.ID) 23 | if err != nil { 24 | return secret.NewDeleteSecretsIDBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) 25 | } 26 | 27 | return secret.NewDeleteSecretsIDNoContent() 28 | } 29 | -------------------------------------------------------------------------------- /console/ui/src/widgets/cluster-overview-table/model/types.ts: -------------------------------------------------------------------------------- 1 | import { CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES } from '@widgets/cluster-overview-table/model/constants.ts'; 2 | import { ClusterInfoInstance } from '@shared/api/api/clusters.ts'; 3 | 4 | export interface ClusterOverviewTableProps { 5 | clusterName?: string; 6 | items?: ClusterInfoInstance[]; 7 | isLoading?: boolean; 8 | } 9 | 10 | export interface ClusterOverviewTableValues { 11 | [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.NAME]: string; 12 | [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.HOST]: string; 13 | [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.ROLE]: string; 14 | [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.STATE]: string; 15 | [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.TIMELINE]: number; 16 | [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.LAG_IN_MB]: number; 17 | [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.PENDING_RESTART]: string; 18 | [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.TAGS]: string; 19 | } 20 | -------------------------------------------------------------------------------- /automation/roles/pgbouncer/templates/pgbouncer.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=pgBouncer connection pooling for PostgreSQL 3 | After=syslog.target network.target 4 | 5 | [Service] 6 | Type=forking 7 | 8 | User=postgres 9 | Group=postgres 10 | 11 | RuntimeDirectory=pgbouncer{{ '-%d' % (idx + 1) if idx > 0 else '' }} 12 | RuntimeDirectoryMode=0755 13 | 14 | {% if ansible_os_family == "Debian" %} 15 | ExecStart=/usr/sbin/pgbouncer -d {{ pgbouncer_conf_dir }}/pgbouncer{{ '-%d' % (idx + 1) if idx > 0 else '' }}.ini 16 | {% endif %} 17 | {% if ansible_os_family == "RedHat" %} 18 | ExecStart=/usr/bin/pgbouncer -d {{ pgbouncer_conf_dir }}/pgbouncer{{ '-%d' % (idx + 1) if idx > 0 else '' }}.ini 19 | {% endif %} 20 | ExecReload=/bin/kill -SIGHUP $MAINPID 21 | PIDFile=/run/pgbouncer{{ '-%d' % (idx + 1) if idx > 0 else '' }}/pgbouncer.pid 22 | Restart=on-failure 23 | 24 | LimitNOFILE=100000 25 | 26 | [Install] 27 | WantedBy=multi-user.target 28 | -------------------------------------------------------------------------------- /automation/roles/resolv_conf/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - block: 3 | - name: Make sure /etc/resolv.conf exists 4 | ansible.builtin.stat: 5 | path: /etc/resolv.conf 6 | register: resolv_conf 7 | 8 | - name: Create /etc/resolv.conf 9 | ansible.builtin.file: 10 | path: /etc/resolv.conf 11 | state: touch 12 | owner: root 13 | group: root 14 | mode: u=rw,g=r,o=r 15 | when: not resolv_conf.stat.exists 16 | 17 | - name: Add DNS server(s) into /etc/resolv.conf 18 | ansible.builtin.lineinfile: 19 | path: /etc/resolv.conf 20 | regexp: "^nameserver {{ item }}" 21 | insertbefore: "^options" 22 | line: "nameserver {{ item }}" 23 | unsafe_writes: true # to prevent failures in CI 24 | loop: "{{ nameservers }}" 25 | when: 26 | - nameservers is defined 27 | - nameservers | length > 0 28 | tags: dns, nameservers 29 | -------------------------------------------------------------------------------- /console/ui/src/widgets/secrets-table/lib/hooks.tsx: -------------------------------------------------------------------------------- 1 | import { ResponseSecretInfo } from '@shared/api/api/secrets.ts'; 2 | import { useMemo } from 'react'; 3 | import { SECRETS_TABLE_COLUMN_NAMES } from '@widgets/secrets-table/model/constants.ts'; 4 | 5 | export const useGetSecretsTableData = (data: ResponseSecretInfo[]) => 6 | useMemo( 7 | () => 8 | data?.map((secret) => ({ 9 | [SECRETS_TABLE_COLUMN_NAMES.NAME]: secret.name!, 10 | [SECRETS_TABLE_COLUMN_NAMES.TYPE]: secret.type!, 11 | [SECRETS_TABLE_COLUMN_NAMES.CREATED]: secret.created_at, 12 | [SECRETS_TABLE_COLUMN_NAMES.UPDATED]: secret.updated_at, 13 | [SECRETS_TABLE_COLUMN_NAMES.USED]: String(!!secret.is_used), 14 | [SECRETS_TABLE_COLUMN_NAMES.ID]: secret.id!, 15 | [SECRETS_TABLE_COLUMN_NAMES.USED_BY]: secret.used_by_clusters, // not displayed, required only for logic purposed 16 | })) ?? [], 17 | [data], 18 | ); 19 | -------------------------------------------------------------------------------- /automation/roles/pre_checks/tasks/system.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://www.postgresql.org/docs/current/kernel-resources.html#SYSTEMD-REMOVEIPC 3 | # 4 | # Configure systemd RemoveIPC=no to prevent errors like: 5 | # WARNING: could not remove shared memory segment "/PostgreSQL.1450751626": No such file or directory 6 | # FATAL: could not open shared memory segment "/PostgreSQL.3317458760": No such file or directory 7 | 8 | - name: Ensure RemoveIPC is disabled (RemoveIPC=no in logind.conf) 9 | community.general.ini_file: 10 | path: /etc/systemd/logind.conf 11 | section: Login 12 | option: RemoveIPC 13 | value: "no" 14 | create: true 15 | backup: true 16 | register: removeipc_result 17 | 18 | - name: Restart systemd-logind service 19 | ansible.builtin.systemd: 20 | name: systemd-logind 21 | masked: false 22 | state: restarted 23 | when: 24 | - removeipc_result.changed 25 | - ansible_service_mgr == "systemd" 26 | -------------------------------------------------------------------------------- /console/ui/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended-type-checked', 7 | 'plugin:react-hooks/recommended', 8 | 'plugin:@typescript-eslint/stylistic-type-checked', 9 | 'plugin:react/recommended', 10 | 'plugin:react/jsx-runtime', 11 | ], 12 | ignorePatterns: ['dist', '.eslintrc.cjs'], 13 | parser: '@typescript-eslint/parser', 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | sourceType: 'module', 17 | project: ['./tsconfig.json', './tsconfig.node.json'], 18 | tsconfigRootDir: __dirname, 19 | }, 20 | plugins: ['react-refresh'], 21 | rules: { 22 | 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], 23 | '@typescript-eslint/no-misused-promises': 'off', 24 | '@typescript-eslint/no-unsafe-argument': 'off', 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /console/ui/src/entities/cluster/expert-mode/databases-block/model/types.ts: -------------------------------------------------------------------------------- 1 | import { UseFieldArrayRemove } from 'react-hook-form'; 2 | import { DATABASES_BLOCK_FIELD_NAMES } from '@entities/cluster/expert-mode/databases-block/model/const.ts'; 3 | 4 | export interface DatabasesBlockProps { 5 | index: number; 6 | remove?: UseFieldArrayRemove; 7 | } 8 | 9 | export interface DatabasesBlockSingleValue { 10 | [DATABASES_BLOCK_FIELD_NAMES.DATABASE_NAME]?: string; 11 | [DATABASES_BLOCK_FIELD_NAMES.USER_NAME]?: string; 12 | [DATABASES_BLOCK_FIELD_NAMES.USER_PASSWORD]?: string; 13 | [DATABASES_BLOCK_FIELD_NAMES.ENCODING]?: string; 14 | [DATABASES_BLOCK_FIELD_NAMES.LOCALE]?: string; 15 | [DATABASES_BLOCK_FIELD_NAMES.BLOCK_ID]: string; 16 | } 17 | 18 | export interface DatabasesBlockValues { 19 | [DATABASES_BLOCK_FIELD_NAMES.DATABASES]?: DatabasesBlockSingleValue[]; 20 | [DATABASES_BLOCK_FIELD_NAMES.NAMES]?: Record; 21 | } 22 | -------------------------------------------------------------------------------- /automation/roles/update/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | target: postgres # Defines the target for the update. Available values: 'postgres', 'patroni', 'system' 3 | 4 | update_extensions: true # Attempt will be made to automatically update all PostgreSQL extensions in all databases. 5 | 6 | # if target=system 7 | reboot_host_after_update: true # Restart the server if it is required after the update. 8 | reboot_host_timeout: 1800 # Maximum seconds to wait for machine to reboot and respond to a test command. 9 | reboot_host_post_delay: 5 # The waiting time (in minutes) for the caches to warm up after restarting the server before updating the next server. 10 | 11 | # pre-checks vars 12 | max_replication_lag_bytes: 10485760 # (10 MiB) Determines the size of the replication lag above which the update will not be performed. 13 | max_transaction_sec: 15 # (seconds) Determines the maximum transaction time, in the presence of which the update will not be performed. 14 | -------------------------------------------------------------------------------- /console/service/internal/controllers/cluster/delete_cluster.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "postgresql-cluster-console/internal/controllers" 5 | "postgresql-cluster-console/internal/storage" 6 | "postgresql-cluster-console/restapi/operations/cluster" 7 | 8 | "github.com/go-openapi/runtime/middleware" 9 | ) 10 | 11 | type deleteClusterHandler struct { 12 | db storage.IStorage 13 | } 14 | 15 | func NewDeleteClusterHandler(db storage.IStorage) cluster.DeleteClustersIDHandler { 16 | return &deleteClusterHandler{ 17 | db: db, 18 | } 19 | } 20 | 21 | func (h *deleteClusterHandler) Handle(param cluster.DeleteClustersIDParams) middleware.Responder { 22 | err := h.db.DeleteClusterSoft(param.HTTPRequest.Context(), param.ID) 23 | if err != nil { 24 | return cluster.NewDeleteClustersIDBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) 25 | } 26 | 27 | return cluster.NewDeleteClustersIDNoContent() 28 | } 29 | -------------------------------------------------------------------------------- /console/ui/src/app/router/routerConfig/OperationsRoutes.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react'; 2 | import { Route } from 'react-router-dom'; 3 | import RouterPaths from '@app/router/routerPathsConfig'; 4 | import OperationLog from '@pages/operation-log'; 5 | 6 | const Operations = lazy(() => import('@pages/operations')); 7 | 8 | const OperationsRoutes = () => ( 9 | 10 | 15 | } /> 16 | `${data.operationId}`, 21 | }, 22 | }} 23 | element={} 24 | /> 25 | 26 | 27 | ); 28 | 29 | export default OperationsRoutes; 30 | -------------------------------------------------------------------------------- /console/ui/src/shared/assets/instanceIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /automation/roles/copy/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Fetch files from the master 3 | become: true 4 | become_user: root 5 | run_once: true 6 | ansible.builtin.fetch: 7 | src: "{{ item.src }}" 8 | dest: "{{ item.dest }}" 9 | flat: true 10 | validate_checksum: true 11 | loop: "{{ fetch_files_from_master }}" 12 | delegate_to: "{{ groups.master[0] }}" 13 | when: 14 | - fetch_files_from_master is defined 15 | - fetch_files_from_master | length > 0 16 | tags: fetch_files 17 | 18 | - name: Copy files to all servers 19 | become: true 20 | become_user: root 21 | ansible.builtin.copy: 22 | src: "{{ item.src }}" 23 | dest: "{{ item.dest }}" 24 | owner: "{{ item.owner }}" 25 | group: "{{ item.group }}" 26 | mode: "{{ item.mode }}" 27 | loop: "{{ copy_files_to_all_server }}" 28 | when: 29 | - copy_files_to_all_server is defined 30 | - copy_files_to_all_server | length > 0 31 | tags: copy_files 32 | -------------------------------------------------------------------------------- /automation/roles/postgresql_schemas/README.md: -------------------------------------------------------------------------------- 1 | # Ansible Role: postgresql_schemas 2 | 3 | Creates and manages PostgreSQL schemas in specified databases. Ensures schemas exist with the desired owner, using [community.postgresql.postgresql_schema](https://docs.ansible.com/ansible/latest/collections/community/postgresql/postgresql_schema_module.html) module. 4 | 5 | ## Role Variables 6 | 7 | | Variable | Default | Description | 8 | |----------|---------|-------------| 9 | | `postgresql_schemas` | `[]` | List of schema definitions to apply. Each item typically includes: schema (name), db (database), owner (role). | 10 | 11 | ### Example: 12 | 13 | ```yaml 14 | postgresql_schemas: 15 | - { schema: "app_schema1", db: "app_db", owner: "app_user" } 16 | - { schema: "app_schema2", db: "app_db", owner: "app_user" } 17 | ``` 18 | 19 | ## Dependencies 20 | 21 | This role depends on: 22 | - `vitabaks.autobase.common` - Provides common variables and configurations 23 | -------------------------------------------------------------------------------- /automation/roles/sudo/README.md: -------------------------------------------------------------------------------- 1 | # Ansible Role: sudo 2 | 3 | Manages sudo privileges via drop-in files under /etc/sudoers.d for defined users. Supports password-less sudo (NOPASSWD) and command whitelisting. 4 | 5 | ## Role Variables 6 | 7 | | Variable | Default | Description | 8 | |------------|---------|-------------| 9 | | sudo_users | see below | List of user entries with sudo rules. Each item supports: name (string, required), nopasswd ("yes"/"no"), commands ("ALL" or comma-separated absolute paths). | 10 | 11 | Default: 12 | ```yaml 13 | sudo_users: 14 | - name: "postgres" 15 | nopasswd: "yes" # or "no" to require a password 16 | commands: "ALL" 17 | # - name: "joe" # other user (example) 18 | # nopasswd: "no" 19 | # commands: "/usr/bin/find, /usr/bin/less, /usr/bin/tail, /bin/kill" 20 | ``` 21 | 22 | ## Dependencies 23 | 24 | This role depends on: 25 | - `vitabaks.autobase.common` - Provides common variables and configurations 26 | -------------------------------------------------------------------------------- /.github/workflows/schedule_pg_almalinux9.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: scheduled PostgreSQL (AlmaLinux 9) 3 | 4 | on: 5 | schedule: 6 | - cron: "15 1 * * *" 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.repository_owner == 'vitabaks' || github.event_name == 'workflow_dispatch' }} 12 | 13 | steps: 14 | - name: Set TERM environment variable 15 | run: echo "TERM=xterm" >> $GITHUB_ENV 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v6 22 | with: 23 | python-version: "3.14" 24 | 25 | - name: Install dependencies 26 | run: make bootstrap-dev 27 | 28 | - name: Run Molecule tests 29 | run: make molecule-test 30 | env: 31 | PY_COLORS: "1" 32 | ANSIBLE_FORCE_COLOR: "1" 33 | IMAGE_DISTRO: almalinux9 34 | IMAGE_NAMESPACE: glillico 35 | -------------------------------------------------------------------------------- /console/service/Makefile: -------------------------------------------------------------------------------- 1 | ifndef GO_BIN 2 | override GO_BIN = "pg-console" 3 | endif 4 | 5 | APP = main.go 6 | 7 | swagger_install: 8 | { \ 9 | export my_dir=$$(pwd) ;\ 10 | export dir=$$(mktemp -d) ;\ 11 | retry_count=0 ;\ 12 | max_retries=5 ;\ 13 | until [ "$$retry_count" -ge "$$max_retries" ]; do \ 14 | git clone https://github.com/go-swagger/go-swagger "$$dir" && break ;\ 15 | retry_count=$$((retry_count+1)) ;\ 16 | echo "Retry $$retry_count/$$max_retries" ;\ 17 | sleep 1 ;\ 18 | done ;\ 19 | cd "$$dir" ;\ 20 | go install ./cmd/swagger ;\ 21 | cd "$$my_dir" ;\ 22 | swagger version ;\ 23 | } 24 | 25 | ensure_deps: ## Ensure Go module dependencies are tidy 26 | @go mod tidy 27 | 28 | build: ## Build app 29 | @go build -o $(GO_BIN) $(APP) 30 | 31 | swagger: 32 | @swagger generate server --name PgConsole --spec api/swagger.yaml --principal interface{} --exclude-main 33 | 34 | build_in_docker: swagger_install swagger ensure_deps build 35 | -------------------------------------------------------------------------------- /console/service/internal/convert/projects.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "postgresql-cluster-console/internal/storage" 5 | "postgresql-cluster-console/models" 6 | 7 | "github.com/go-openapi/strfmt" 8 | ) 9 | 10 | func ProjectToSwagger(prj *storage.Project) *models.ResponseProject { 11 | return &models.ResponseProject{ 12 | CreatedAt: strfmt.DateTime(prj.CreatedAt), 13 | Description: prj.Description, 14 | ID: prj.ID, 15 | Name: prj.Name, 16 | UpdatedAt: func() *strfmt.DateTime { 17 | if prj.UpdatedAt == nil { 18 | return nil 19 | } 20 | updated := strfmt.DateTime(*prj.UpdatedAt) 21 | 22 | return &updated 23 | }(), 24 | } 25 | } 26 | 27 | func ProjectsToSwagger(projects []storage.Project) []*models.ResponseProject { 28 | resp := make([]*models.ResponseProject, 0, len(projects)) 29 | for _, prj := range projects { 30 | resp = append(resp, ProjectToSwagger(&prj)) 31 | } 32 | 33 | return resp 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/schedule_pg_almalinux10.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: scheduled PostgreSQL (AlmaLinux 10) 3 | 4 | on: 5 | schedule: 6 | - cron: "15 1 * * *" 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.repository_owner == 'vitabaks' || github.event_name == 'workflow_dispatch' }} 12 | 13 | steps: 14 | - name: Set TERM environment variable 15 | run: echo "TERM=xterm" >> $GITHUB_ENV 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v6 22 | with: 23 | python-version: "3.14" 24 | 25 | - name: Install dependencies 26 | run: make bootstrap-dev 27 | 28 | - name: Run Molecule tests 29 | run: make molecule-test 30 | env: 31 | PY_COLORS: "1" 32 | ANSIBLE_FORCE_COLOR: "1" 33 | IMAGE_DISTRO: almalinux10 34 | IMAGE_NAMESPACE: glillico 35 | -------------------------------------------------------------------------------- /automation/molecule/tests/roles/haproxy/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # 🚀 The purpose of this task is to incorporate a series of variable tests for HAProxy, a reliable, high performance TCP/HTTP load balancer 3 | # 🎯 The objective is to ensure that the variables used in the HAProxy configuration are correctly defined and functional 4 | 5 | # 🔄 Including variable tests for HAProxy 6 | # This task iterates over all the YAML files in the 'variables' directory, and includes each file's tasks in the current playbook 7 | # If a variable test fails, it will be immediately apparent, aiding in debugging and ensuring the robustness of the HAProxy configuration 8 | - name: Molecule.tests.roles.haproxy.main | Incorporate Variable Tests 9 | run_once: true 10 | ansible.builtin.include_tasks: "{{ molecule_tests_roles_haproxy_main_file }}" 11 | loop: "{{ lookup('fileglob', 'variables/*.yml', wantlist=True) }}" 12 | loop_control: 13 | loop_var: molecule_tests_roles_haproxy_main_file 14 | -------------------------------------------------------------------------------- /.github/workflows/schedule_pg_centosstream9.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: scheduled PostgreSQL (CentOS Stream 9) 3 | 4 | on: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.repository_owner == 'vitabaks' || github.event_name == 'workflow_dispatch' }} 12 | 13 | steps: 14 | - name: Set TERM environment variable 15 | run: echo "TERM=xterm" >> $GITHUB_ENV 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v6 22 | with: 23 | python-version: "3.14" 24 | 25 | - name: Install dependencies 26 | run: make bootstrap-dev 27 | 28 | - name: Run Molecule tests 29 | run: make molecule-test 30 | env: 31 | PY_COLORS: "1" 32 | ANSIBLE_FORCE_COLOR: "1" 33 | IMAGE_DISTRO: centosstream9 34 | IMAGE_NAMESPACE: glillico 35 | --------------------------------------------------------------------------------