├── .mdlrc ├── .gitignore ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── act │ ├── release.json │ └── release.sh ├── workflows │ ├── lock.yaml │ ├── stale.yaml │ ├── labels.yaml │ ├── release-drafter.yaml │ ├── ci.yaml │ ├── pr-labels.yaml │ └── deploy.yaml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md ├── release-drafter.yml ├── CONTRIBUTING.md ├── renovate.json └── CODE_OF_CONDUCT.md ├── .gitattributes ├── timescaledb ├── rootfs │ ├── etc │ │ └── s6-overlay │ │ │ └── s6-rc.d │ │ │ ├── init-user │ │ │ ├── type │ │ │ ├── dependencies.d │ │ │ │ └── base │ │ │ ├── up │ │ │ └── run │ │ │ ├── pgagent │ │ │ ├── type │ │ │ ├── dependencies.d │ │ │ │ ├── base │ │ │ │ └── postgres │ │ │ ├── finish │ │ │ └── run │ │ │ ├── postgres │ │ │ ├── type │ │ │ ├── dependencies.d │ │ │ │ ├── base │ │ │ │ ├── init-addon │ │ │ │ └── init-user │ │ │ ├── finish │ │ │ └── run │ │ │ ├── user │ │ │ └── contents.d │ │ │ │ ├── init-addon │ │ │ │ ├── init-user │ │ │ │ ├── pgagent │ │ │ │ └── postgres │ │ │ └── init-addon │ │ │ ├── dependencies.d │ │ │ ├── base │ │ │ ├── init-user │ │ │ └── base-addon-banner │ │ │ ├── type │ │ │ ├── up │ │ │ └── run │ ├── usr │ │ └── share │ │ │ └── timescaledb │ │ │ ├── 001_reenable_auth.sh │ │ │ ├── backup_post.sh │ │ │ ├── 000_install_timescaledb.sh │ │ │ ├── backup_pre.sh │ │ │ ├── 002_timescaledb_tune.sh │ │ │ ├── restore_from_backup.sh │ │ │ └── 003_apply_user_config.sh │ └── package │ │ └── admin │ │ └── s6-overlay │ │ └── etc │ │ └── s6-rc │ │ └── scripts │ │ └── base-addon-banner ├── .dockerignore ├── icon.png ├── logo.png ├── build.yaml ├── docker-dependencies │ ├── timescaledb-tools │ ├── pgagent-pg17 │ ├── pgagent-pg16 │ ├── pgvector-pg17 │ ├── postgresql-extension-system-stat-pg16 │ ├── postgresql-extension-system-stat-pg17 │ ├── postgis-pg16 │ ├── postgis-pg17 │ ├── timescaledb-toolkit-pg16 │ └── timescaledb-toolkit-pg17 ├── config.yaml ├── pgagent.patch ├── Dockerfile └── .README.j2 ├── .vs ├── slnx.sqlite ├── VSWorkspaceState.json └── hassos-addon-timescaledb │ └── v16 │ └── .suo ├── .gitmodules ├── .yamllint ├── README-DEV.md ├── BACKUP-RESTORE-IMPLEMENTATION.md ├── AGENTS.md ├── project.sh ├── LICENSE ├── DESIGN-postgres-config.md └── README.md /.mdlrc: -------------------------------------------------------------------------------- 1 | rules "~MD024" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | \.vscode/ 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | .github/* @expaso 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *. eol=lf 3 | *.sh eol=lf -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/init-user/type: -------------------------------------------------------------------------------- 1 | oneshot 2 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/pgagent/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/postgres/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/init-addon: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/init-user: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/pgagent: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/postgres: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/init-addon/dependencies.d/base: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/init-addon/type: -------------------------------------------------------------------------------- 1 | oneshot 2 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/init-user/dependencies.d/base: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/pgagent/dependencies.d/base: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/pgagent/dependencies.d/postgres: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/postgres/dependencies.d/base: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/init-addon/dependencies.d/init-user: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/postgres/dependencies.d/init-addon: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/postgres/dependencies.d/init-user: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/init-addon/dependencies.d/base-addon-banner: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | github: expaso 3 | custom: https://www.buymeacoffee.com/expaso 4 | -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expaso/hassos-addon-timescaledb/HEAD/.vs/slnx.sqlite -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/init-addon/up: -------------------------------------------------------------------------------- 1 | /etc/s6-overlay/s6-rc.d/init-addon/run 2 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/init-user/up: -------------------------------------------------------------------------------- 1 | /etc/s6-overlay/s6-rc.d/init-user/run 2 | -------------------------------------------------------------------------------- /timescaledb/.dockerignore: -------------------------------------------------------------------------------- 1 | rootfs/opt/filiibot/node_modules 2 | rootfs/opt/filiibot/npm-debug.log 3 | -------------------------------------------------------------------------------- /.vs/VSWorkspaceState.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExpandedNodes": [""], 3 | "PreviewInSolutionExplorer": false 4 | } 5 | -------------------------------------------------------------------------------- /timescaledb/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expaso/hassos-addon-timescaledb/HEAD/timescaledb/icon.png -------------------------------------------------------------------------------- /timescaledb/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expaso/hassos-addon-timescaledb/HEAD/timescaledb/logo.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "timescaledb/pgagent"] 2 | path = timescaledb/pgagent 3 | url = https://github.com/pgadmin-org/pgagent 4 | -------------------------------------------------------------------------------- /.vs/hassos-addon-timescaledb/v16/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expaso/hassos-addon-timescaledb/HEAD/.vs/hassos-addon-timescaledb/v16/.suo -------------------------------------------------------------------------------- /.github/act/release.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "created", 3 | "release": { 4 | "body": "- 🐛 Fixed a bug\r\n- 🚀 Added New Feature", 5 | "draft": false, 6 | "prerelease": true, 7 | "tag_name": "v3.0.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/lock.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lock 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | schedule: 7 | - cron: "0 9 * * *" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | workflows: 12 | uses: hassio-addons/workflows/.github/workflows/lock.yaml@main 13 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Stale 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | schedule: 7 | - cron: "0 8 * * *" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | workflows: 12 | uses: hassio-addons/workflows/.github/workflows/stale.yaml@main 13 | -------------------------------------------------------------------------------- /.github/workflows/labels.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Sync labels 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | schedule: 7 | - cron: "34 5 * * *" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | workflows: 12 | uses: hassio-addons/workflows/.github/workflows/labels.yaml@main 13 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release Drafter 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | workflows: 12 | uses: hassio-addons/workflows/.github/workflows/release-drafter.yaml@main 13 | -------------------------------------------------------------------------------- /timescaledb/rootfs/usr/share/timescaledb/001_reenable_auth.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "${POSTGRESQL_CONF_DIR:-}" ]; then 4 | POSTGRESQL_CONF_DIR=${PGDATA} 5 | fi 6 | 7 | # reenable password authentication 8 | sed -i "s/host all all all trust/host all all all md5/" "${POSTGRESQL_CONF_DIR}/pg_hba.conf" 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Proposed Changes 2 | 3 | > (Describe the changes and rationale behind them) 4 | 5 | ## Related Issues 6 | 7 | > ([Github link][autolink-references] to related issues or pull requests) 8 | 9 | [autolink-references]: https://help.github.com/articles/autolinked-references-and-urls/ 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | push: 7 | pull_request: 8 | types: 9 | - opened 10 | - reopened 11 | - synchronize 12 | workflow_dispatch: 13 | 14 | jobs: 15 | workflows: 16 | uses: hassio-addons/workflows/.github/workflows/addon-ci.yaml@main 17 | -------------------------------------------------------------------------------- /.github/workflows/pr-labels.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: PR Labels 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | pull_request_target: 7 | types: 8 | - opened 9 | - labeled 10 | - unlabeled 11 | - synchronize 12 | 13 | jobs: 14 | workflows: 15 | uses: hassio-addons/workflows/.github/workflows/pr-labels.yaml@main 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Problem/Motivation 2 | 3 | > (Why the issue was filed) 4 | 5 | ## Expected behavior 6 | 7 | > (What you expected to happen) 8 | 9 | ## Actual behavior 10 | 11 | > (What actually happened) 12 | 13 | ## Steps to reproduce 14 | 15 | > (How can someone else make/see it happen) 16 | 17 | ## Proposed changes 18 | 19 | > (If you have a proposed change, workaround or fix, 20 | > describe the rationale behind it) 21 | -------------------------------------------------------------------------------- /.github/act/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script is used to test the release action locally using ACT 3 | # See: https://github.com/nektos/act 4 | # 5 | # gh is Githubs official command line tool. 6 | # run 'gh auth login' first to authenticate with your github account. 7 | # 8 | # Usage: ./test.sh 9 | 10 | echo "Running act.." 11 | act release -s GITHUB_TOKEN="$(gh auth token)" -s PERSONAL_ACCESS_TOKEN="$(gh auth token)" -e ./.github/act/release.json 12 | -------------------------------------------------------------------------------- /timescaledb/build.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | build_from: 3 | aarch64: ghcr.io/hassio-addons/base/aarch64:17.2.5 4 | amd64: ghcr.io/hassio-addons/base/amd64:17.2.5 5 | armhf: ghcr.io/hassio-addons/base/armhf:17.2.5 6 | armv7: ghcr.io/hassio-addons/base/armv7:17.2.5 7 | i386: ghcr.io/hassio-addons/base/i386:17.2.5 8 | # identity: hans@expaso.nl 9 | # base_identity: codenotary@frenck.dev 10 | codenotary: 11 | base_image: codenotary@frenck.dev 12 | signer: hans@expaso.nl 13 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Deploy 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | release: 7 | types: 8 | - published 9 | workflow_run: 10 | workflows: ["CI"] 11 | branches: [main] 12 | types: 13 | - completed 14 | 15 | jobs: 16 | workflows: 17 | uses: hassio-addons/workflows/.github/workflows/addon-deploy.yaml@main 18 | with: 19 | repository: "hassos-addons" 20 | repository_edge: "hassos-addons-edge" 21 | repository_beta: "hassos-addons-beta" 22 | secrets: 23 | DISPATCH_TOKEN: ${{ secrets.DISPATCH_TOKEN }} 24 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name-template: 'v$RESOLVED_VERSION 🌈' 3 | tag-template: 'v$RESOLVED_VERSION' 4 | categories: 5 | - title: '🚀 Features' 6 | labels: 7 | - 'feature' 8 | - 'enhancement' 9 | - title: '🐛 Bug Fixes' 10 | labels: 11 | - 'fix' 12 | - 'bugfix' 13 | - 'bug' 14 | - title: '🧰 Maintenance' 15 | label: 'chore' 16 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 17 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 18 | version-resolver: 19 | major: 20 | labels: 21 | - 'major' 22 | minor: 23 | labels: 24 | - 'minor' 25 | patch: 26 | labels: 27 | - 'patch' 28 | default: patch 29 | template: | 30 | ## Changes 31 | 32 | $CHANGES 33 | -------------------------------------------------------------------------------- /timescaledb/rootfs/usr/share/timescaledb/backup_post.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # ============================================================================== 3 | # Home Assistant Add-on: TimescaleDb 4 | # Post-backup script - Cleans up SQL dump after Home Assistant backup completes 5 | # ============================================================================== 6 | declare BACKUP_FILE 7 | 8 | BACKUP_FILE="/data/backup_db.sql" 9 | 10 | bashio::log.info "Starting post-backup cleanup..." 11 | 12 | # Remove the backup file if it exists 13 | if [[ -f "${BACKUP_FILE}" ]]; then 14 | if rm -f "${BACKUP_FILE}"; then 15 | bashio::log.info "Backup SQL file removed successfully." 16 | else 17 | bashio::log.error "Failed to remove backup SQL file at ${BACKUP_FILE}" 18 | exit 1 19 | fi 20 | else 21 | bashio::log.debug "No backup SQL file to clean up." 22 | fi 23 | 24 | bashio::log.info "Post-backup cleanup completed." 25 | -------------------------------------------------------------------------------- /timescaledb/docker-dependencies/timescaledb-tools: -------------------------------------------------------------------------------- 1 | ARG GO_VERSION=1.22.1 2 | 3 | ########################################### 4 | # Build TimeScaleDB-tools 5 | ########################################### 6 | FROM golang:${GO_VERSION}-alpine AS timescaledb-tools 7 | 8 | ARG VERSION=latest 9 | ARG TARGETARCH 10 | ARG TARGETVARIANT 11 | 12 | ENV TOOLS_VERSION=0.18.1 13 | # hadolint ignore=DL3018 14 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 15 | apk update && apk add --no-cache \ 16 | git \ 17 | gcc \ 18 | musl-dev \ 19 | && go install github.com/timescale/timescaledb-tune/cmd/timescaledb-tune@${VERSION} \ 20 | && go install github.com/timescale/timescaledb-parallel-copy/cmd/timescaledb-parallel-copy@${VERSION} \ 21 | && go install github.com/timescale/timescaledb-backup/cmd/ts-dump@${VERSION} \ 22 | && go install github.com/timescale/timescaledb-backup/cmd/ts-restore@${VERSION} 23 | 24 | # Labels 25 | LABEL \ 26 | org.opencontainers.image.source="https://github.com/expaso/hassos-addon-timescaledb" 27 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/init-user/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck shell=bash 3 | # ============================================================================== 4 | # Installs custom user packages 5 | # Persists user settings and installs custom user packages. 6 | # ============================================================================== 7 | 8 | # Install user configured/requested packages 9 | bashio::log.info "Running user-init.." 10 | 11 | if bashio::config.has_value 'system_packages'; then 12 | apk update \ 13 | || bashio::exit.nok 'Failed updating Alpine packages repository indexes' 14 | 15 | for package in $(bashio::config 'system_packages'); do 16 | apk add "$package" \ 17 | || bashio::exit.nok "Failed installing system package ${package}" 18 | done 19 | fi 20 | 21 | # Executes user commands on startup 22 | if bashio::config.has_value 'init_commands'; then 23 | while read -r cmd; do 24 | eval "${cmd}" \ 25 | || bashio::exit.nok "Failed executing init command: ${cmd}" 26 | done <<< "$(bashio::config 'init_commands')" 27 | fi 28 | 29 | bashio::log.info "done" 30 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/pgagent/finish: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck shell=bash 3 | # ============================================================================== 4 | # Take down the S6 supervision tree when the code server fails 5 | # ============================================================================== 6 | declare exit_code 7 | readonly exit_code_container=$( /run/s6-linux-init-container-results/exitcode 19 | fi 20 | [[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt 21 | elif [[ "${exit_code_service}" -ne 0 ]]; then 22 | if [[ "${exit_code_container}" -eq 0 ]]; then 23 | echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode 24 | fi 25 | exec /run/s6/basedir/bin/halt 26 | fi 27 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/postgres/finish: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck shell=bash 3 | # ============================================================================== 4 | # Take down the S6 supervision tree when the code server fails 5 | # ============================================================================== 6 | declare exit_code 7 | readonly exit_code_container=$( /run/s6-linux-init-container-results/exitcode 19 | fi 20 | [[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt 21 | elif [[ "${exit_code_service}" -ne 0 ]]; then 22 | if [[ "${exit_code_container}" -eq 0 ]]; then 23 | echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode 24 | fi 25 | exec /run/s6/basedir/bin/halt 26 | fi 27 | -------------------------------------------------------------------------------- /timescaledb/rootfs/usr/share/timescaledb/000_install_timescaledb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Checks to support bitnami image with same scripts so they stay in sync 4 | if [ -n "${BITNAMI_IMAGE_VERSION:-}" ]; then 5 | if [ -z "${POSTGRES_USER:-}" ]; then 6 | POSTGRES_USER=${POSTGRESQL_USERNAME} 7 | fi 8 | 9 | if [ -z "${POSTGRES_DB:-}" ]; then 10 | POSTGRES_DB=${POSTGRESQL_DATABASE} 11 | fi 12 | 13 | if [ -z "${PGDATA:-}" ]; then 14 | PGDATA=${POSTGRESQL_DATA_DIR} 15 | fi 16 | fi 17 | 18 | if [ -z "${POSTGRESQL_CONF_DIR:-}" ]; then 19 | POSTGRESQL_CONF_DIR=${PGDATA} 20 | fi 21 | 22 | TS_TELEMETRY='basic' 23 | if [ "${TIMESCALEDB_TELEMETRY:-}" == "off" ]; then 24 | TS_TELEMETRY='off' 25 | fi 26 | 27 | echo "timescaledb.telemetry_level=${TS_TELEMETRY}" >> "${POSTGRESQL_CONF_DIR}/postgresql.conf" 28 | 29 | # create extension timescaledb in initial databases 30 | psql -U "${POSTGRES_USER}" postgres -c "CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;" 31 | psql -U "${POSTGRES_USER}" template1 -c "CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;" 32 | 33 | if [ "${POSTGRES_DB:-postgres}" != 'postgres' ]; then 34 | psql -U "${POSTGRES_USER}" "${POSTGRES_DB}" -c "CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;" 35 | fi 36 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish 4 | to make via issue, email, or any other method with the owners of this repository 5 | before making a change. 6 | 7 | Please note we have a code of conduct, please follow it in all your interactions 8 | with the project. 9 | 10 | ## Issues and feature requests 11 | 12 | You've found a bug in the source code, a mistake in the documentation or maybe 13 | you'd like a new feature? You can help us by submitting an issue to our 14 | [GitHub Repository][github]. Before you create an issue, make sure you search 15 | the archive, maybe your question was already answered. 16 | 17 | Even better: You could submit a pull request with a fix / new feature! 18 | 19 | ## Pull request process 20 | 21 | 1. Search our repository for open or closed [pull requests][prs] that relates 22 | to your submission. You don't want to duplicate effort. 23 | 24 | 1. You may merge the pull request in once you have the sign-off of two other 25 | developers, or if you do not have permission to do that, you may request 26 | the second reviewer to merge it for you. 27 | 28 | [github]: https://github.com/expaso/hassos-addon-timescaledb/issues 29 | [prs]: https://github.com/expaso/hassos-addon-timescaledb/pulls 30 | -------------------------------------------------------------------------------- /timescaledb/docker-dependencies/pgagent-pg17: -------------------------------------------------------------------------------- 1 | FROM timescale/timescaledb:latest-pg17 2 | ARG VERSION 3 | ARG TARGETARCH 4 | ARG TARGETVARIANT 5 | # -------------------------------------------- 6 | # pgAgent - build pached version and install 7 | # -------------------------------------------- 8 | RUN mkdir -p /build 9 | COPY timescaledb/pgagent.patch /build/ 10 | 11 | # hadolint ignore=DL3018, DL3003 12 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 13 | set -ex \ 14 | && apk update \ 15 | && apk add --virtual .fetch-deps \ 16 | ca-certificates \ 17 | git \ 18 | openssl \ 19 | openssl-dev \ 20 | tar \ 21 | && apk add -u musl \ 22 | && apk add --virtual .build-deps \ 23 | coreutils \ 24 | gcc \ 25 | make \ 26 | cmake \ 27 | build-base \ 28 | boost-dev \ 29 | openldap-dev \ 30 | && git clone https://github.com/postgres/pgagent /build/pgagent \ 31 | && cd /build/pgagent \ 32 | && git checkout ${VERSION} \ 33 | && git apply -v ../pgagent.patch \ 34 | && cmake . \ 35 | && make && make install \ 36 | && cd ~ \ 37 | && apk del .fetch-deps .build-deps \ 38 | && rm -rf /build \ 39 | && apk add \ 40 | boost-libs 41 | 42 | # Labels 43 | LABEL \ 44 | org.opencontainers.image.source="https://github.com/expaso/hassos-addon-timescaledb" -------------------------------------------------------------------------------- /timescaledb/docker-dependencies/pgagent-pg16: -------------------------------------------------------------------------------- 1 | FROM timescale/timescaledb:latest-pg16 2 | ARG VERSION 3 | ARG TARGETARCH 4 | ARG TARGETVARIANT 5 | 6 | # -------------------------------------------- 7 | # pgAgent - build pached version and install 8 | # -------------------------------------------- 9 | RUN mkdir -p /build 10 | COPY timescaledb/pgagent.patch /build/ 11 | 12 | # hadolint ignore=DL3018, DL3003 13 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 14 | set -ex \ 15 | && apk update \ 16 | && apk add --virtual .fetch-deps \ 17 | ca-certificates \ 18 | git \ 19 | openssl \ 20 | openssl-dev \ 21 | tar \ 22 | && apk add -u musl \ 23 | && apk add --virtual .build-deps \ 24 | coreutils \ 25 | gcc \ 26 | make \ 27 | cmake \ 28 | build-base \ 29 | boost-dev \ 30 | openldap-dev \ 31 | && git clone https://github.com/postgres/pgagent /build/pgagent \ 32 | && cd /build/pgagent \ 33 | && git checkout ${VERSION} \ 34 | && git apply -v ../pgagent.patch \ 35 | && cmake . \ 36 | && make && make install \ 37 | && cd ~ \ 38 | && apk del .fetch-deps .build-deps \ 39 | && rm -rf /build \ 40 | && apk add \ 41 | boost-libs 42 | 43 | 44 | # Labels 45 | LABEL \ 46 | org.opencontainers.image.source="https://github.com/expaso/hassos-addon-timescaledb" 47 | -------------------------------------------------------------------------------- /timescaledb/docker-dependencies/pgvector-pg17: -------------------------------------------------------------------------------- 1 | FROM timescale/timescaledb:latest-pg17 AS timescale-pg17 2 | 3 | FROM timescale-pg17 AS pgvector 4 | ARG VERSION 5 | ARG TARGETARCH 6 | ARG TARGETVARIANT 7 | 8 | # -------------------------------------------- 9 | # pgVector - build and install 10 | # -------------------------------------------- 11 | # hadolint ignore=DL3018, DL3003 12 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 13 | set -ex \ 14 | && apk update \ 15 | && apk add --virtual .fetch-deps \ 16 | ca-certificates \ 17 | git \ 18 | openssl \ 19 | openssl-dev \ 20 | tar \ 21 | wget \ 22 | && mkdir -p /build/ \ 23 | && apk add --virtual .build-deps \ 24 | coreutils \ 25 | gcc \ 26 | make \ 27 | cmake \ 28 | build-base \ 29 | libxml2-dev \ 30 | protobuf-c-dev \ 31 | clang19 \ 32 | llvm19 \ 33 | && mkdir -p /build \ 34 | && cd /build \ 35 | && git clone --branch v${VERSION} https://github.com/pgvector/pgvector.git \ 36 | && cd pgvector \ 37 | && make \ 38 | && make install \ 39 | && cd ~ \ 40 | && apk del .fetch-deps .build-deps \ 41 | && rm -rf /build 42 | 43 | 44 | # Labels 45 | LABEL \ 46 | org.opencontainers.image.source="https://github.com/expaso/hassos-addon-timescaledb" 47 | -------------------------------------------------------------------------------- /timescaledb/docker-dependencies/postgresql-extension-system-stat-pg16: -------------------------------------------------------------------------------- 1 | FROM timescale/timescaledb:latest-pg16 2 | ARG VERSION 3 | ARG TARGETARCH 4 | ARG TARGETVARIANT 5 | 6 | RUN mkdir -p /build 7 | # hadolint ignore=DL3018, DL3003 8 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 9 | set -ex \ 10 | && apk update \ 11 | && apk add --virtual .fetch-deps \ 12 | ca-certificates \ 13 | git \ 14 | openssl \ 15 | openssl-dev \ 16 | tar \ 17 | && apk add -u musl \ 18 | && apk add --virtual .build-deps \ 19 | coreutils \ 20 | gcc \ 21 | make \ 22 | cmake \ 23 | build-base \ 24 | boost-dev \ 25 | openldap-dev \ 26 | clang19 \ 27 | llvm19 \ 28 | && git clone https://github.com/EnterpriseDB/system_stats.git /build/system-stats \ 29 | && cd /build/system-stats \ 30 | && git checkout v${VERSION} 31 | 32 | WORKDIR /build/system-stats 33 | 34 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 35 | set -ex \ 36 | && PATH=/usr/local/pgsql/bin:$PATH make USE_PGXS=1 \ 37 | && PATH=/usr/local/pgsql/bin:$PATH make install USE_PGXS=1 \ 38 | && apk del .fetch-deps .build-deps \ 39 | && rm -rf /build 40 | 41 | # Labels 42 | LABEL \ 43 | org.opencontainers.image.source="https://github.com/expaso/hassos-addon-timescaledb" -------------------------------------------------------------------------------- /timescaledb/docker-dependencies/postgresql-extension-system-stat-pg17: -------------------------------------------------------------------------------- 1 | FROM timescale/timescaledb:latest-pg17 2 | ARG VERSION 3 | ARG TARGETARCH 4 | ARG TARGETVARIANT 5 | 6 | RUN mkdir -p /build 7 | # hadolint ignore=DL3018, DL3003 8 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 9 | set -ex \ 10 | && apk update \ 11 | && apk add --virtual .fetch-deps \ 12 | ca-certificates \ 13 | git \ 14 | openssl \ 15 | openssl-dev \ 16 | tar \ 17 | && apk add -u musl \ 18 | && apk add --virtual .build-deps \ 19 | coreutils \ 20 | gcc \ 21 | make \ 22 | cmake \ 23 | build-base \ 24 | boost-dev \ 25 | openldap-dev \ 26 | clang19 \ 27 | llvm19 \ 28 | && git clone https://github.com/EnterpriseDB/system_stats.git /build/system-stats \ 29 | && cd /build/system-stats \ 30 | && git checkout v${VERSION} 31 | 32 | WORKDIR /build/system-stats 33 | 34 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 35 | set -ex \ 36 | && PATH=/usr/local/pgsql/bin:$PATH make USE_PGXS=1 \ 37 | && PATH=/usr/local/pgsql/bin:$PATH make install USE_PGXS=1 \ 38 | && apk del .fetch-deps .build-deps \ 39 | && rm -rf /build 40 | 41 | # Labels 42 | LABEL \ 43 | org.opencontainers.image.source="https://github.com/expaso/hassos-addon-timescaledb" -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | braces: 4 | level: error 5 | min-spaces-inside: 0 6 | max-spaces-inside: 1 7 | min-spaces-inside-empty: -1 8 | max-spaces-inside-empty: -1 9 | brackets: 10 | level: error 11 | min-spaces-inside: 0 12 | max-spaces-inside: 0 13 | min-spaces-inside-empty: -1 14 | max-spaces-inside-empty: -1 15 | colons: 16 | level: error 17 | max-spaces-before: 0 18 | max-spaces-after: 1 19 | commas: 20 | level: error 21 | max-spaces-before: 0 22 | min-spaces-after: 1 23 | max-spaces-after: 1 24 | comments: 25 | level: error 26 | require-starting-space: true 27 | min-spaces-from-content: 2 28 | comments-indentation: 29 | level: error 30 | document-end: 31 | level: error 32 | present: false 33 | document-start: 34 | level: error 35 | present: true 36 | empty-lines: 37 | level: error 38 | max: 1 39 | max-start: 0 40 | max-end: 1 41 | hyphens: 42 | level: error 43 | max-spaces-after: 1 44 | indentation: 45 | level: error 46 | spaces: 2 47 | indent-sequences: true 48 | check-multi-line-strings: false 49 | key-duplicates: 50 | level: error 51 | line-length: 52 | ignore: | 53 | .github/support.yml 54 | level: warning 55 | max: 120 56 | allow-non-breakable-words: true 57 | allow-non-breakable-inline-mappings: true 58 | new-line-at-end-of-file: 59 | level: error 60 | new-lines: 61 | level: error 62 | type: unix 63 | trailing-spaces: 64 | level: error 65 | truthy: 66 | level: error 67 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/pgagent/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck shell=bash 3 | # ============================================================================== 4 | # Start PgAgent service if enabled 5 | # ============================================================================== 6 | declare UPDATE 7 | declare VERSION_FILE 8 | declare BUILD_TIMESTAMP 9 | 10 | UPDATE=false; 11 | VERSION_FILE=/data/version 12 | BUILD_TIMESTAMP="$(cat /build-timestamp)" 13 | 14 | # Check for updates 15 | if [[ $(< ${VERSION_FILE}) != ${BUILD_TIMESTAMP} ]]; then 16 | UPDATE=true 17 | fi 18 | 19 | # Wait for postgres to become available.. 20 | while ! psql -U "postgres" postgres -c "" 2> /dev/null; do 21 | sleep 1 22 | done 23 | 24 | # Check if we need to update.. 25 | if bashio::var.true "${UPDATE}"; then 26 | bashio::log.info "PgAgent: Waiting for update to complete.." 27 | while [[ $(< ${VERSION_FILE}) != ${BUILD_TIMESTAMP} ]]; do 28 | sleep 3 29 | bashio::log.info "PgAgent: Waiting for update to complete.." 30 | done 31 | 32 | bashio::log.info "Updating PgAgent Extension.." 33 | psql -U "postgres" -d "postgres" -X -c "ALTER EXTENSION pgagent UPDATE;" || true >> /var/log/pgagent.run.log 34 | fi 35 | 36 | # Enable pgAgent extension on postgres database 37 | if ! (psql -X -U postgres -d "postgres" -c "select 1 from pg_extension where extname = 'pgagent';" | grep -q 1); then 38 | bashio::log.info "Enabling PgAgent Extension.." 39 | psql -U "postgres" -d "postgres" -c "CREATE EXTENSION IF NOT EXISTS pgagent;" || true >> /var/log/pgagent.run.log 40 | fi; 41 | 42 | # Create a tmpfs for the pgagent log 43 | mkdir -p "/data/tmp/pgagent" 44 | export TMPDIR=/data/tmp/pgagent 45 | 46 | # And run the daemon in the foreground 47 | bashio::log.info "Starting PgAgent.." 48 | /usr/local/bin/pgagent hostaddr=127.0.0.1 dbname=postgres user=postgres -f -l 1 #-s /var/log/pgagent 49 | -------------------------------------------------------------------------------- /timescaledb/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: TimescaleDB 3 | version: dev 4 | slug: timescaledb 5 | description: "An open-source database built on PostgreSQL for 6 | analyzing time-series data with the power and convenience of SQL" 7 | url: https://github.com/expaso/hassos-addon-timescaledb 8 | codenotary: hans@expaso.nl 9 | arch: 10 | - armhf 11 | - aarch64 12 | - amd64 13 | - armv7 14 | - i386 15 | startup: system 16 | init: false 17 | tmpfs: true 18 | map: 19 | - share:rw 20 | - backup:rw 21 | - media:rw 22 | timeout: 60 23 | backup_pre: /usr/share/timescaledb/backup_pre.sh 24 | backup_post: /usr/share/timescaledb/backup_post.sh 25 | backup_exclude: 26 | - data/postgres/** 27 | - postgres/** 28 | ports: 29 | 5432/tcp: 30 | ports_description: 31 | 5432/tcp: "PostgreSQL Port. Beware! This port may already be in use. 32 | Choose another port-number if the add-on won't start." 33 | options: 34 | databases: 35 | - homeassistant 36 | timescale_enabled: 37 | - homeassistant 38 | timescaledb: 39 | telemetry: basic 40 | maxmemory: 512MB 41 | maxcpus: 4 42 | max_connections: 50 43 | system_packages: [] 44 | init_commands: [] 45 | retry_upgrade: false 46 | postgresql_config: {} 47 | pg_hba_config: [] 48 | schema: 49 | databases: 50 | - str 51 | timescale_enabled: 52 | - str 53 | timescaledb: 54 | telemetry: list(off|basic) 55 | maxmemory: str? 56 | maxcpus: int(1,)? 57 | max_connections: int(20,) 58 | system_packages: 59 | - str 60 | init_commands: 61 | - str 62 | retry_upgrade: bool? 63 | postgresql_config: 64 | match(^[a-z_]+$)?: str? 65 | pg_hba_config: 66 | - type: list(local|host|hostssl|hostnossl)? 67 | database: str? 68 | user: str? 69 | address: str? 70 | method: list(trust|reject|md5|password|scram-sha-256|gss|sspi|ident|peer|pam|ldap|radius|cert)? 71 | options: str? 72 | image: ghcr.io/expaso/timescaledb/{arch} 73 | -------------------------------------------------------------------------------- /timescaledb/rootfs/usr/share/timescaledb/backup_pre.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # ============================================================================== 3 | # Home Assistant Add-on: TimescaleDb 4 | # Pre-backup script - Creates a SQL dump before Home Assistant backup runs 5 | # ============================================================================== 6 | declare BACKUP_FILE 7 | 8 | BACKUP_FILE="/data/backup_db.sql" 9 | 10 | bashio::log.info "Starting pre-backup process..." 11 | 12 | # Check if postgres is running by trying to connect 13 | if pg_isready -U postgres -h localhost -p 5432 >/dev/null 2>&1; then 14 | bashio::log.info "PostgreSQL is running, creating database dump..." 15 | 16 | # Remove old backup file if it exists 17 | if [[ -f "${BACKUP_FILE}" ]]; then 18 | bashio::log.debug "Removing old backup file..." 19 | rm -f "${BACKUP_FILE}" 20 | fi 21 | 22 | # Create an empty file with proper ownership first 23 | touch "${BACKUP_FILE}" 24 | chown postgres:postgres "${BACKUP_FILE}" 25 | chmod 600 "${BACKUP_FILE}" 26 | 27 | # Create the SQL dump 28 | if su - postgres -c "pg_dumpall -U postgres --clean --if-exists -f ${BACKUP_FILE}"; then 29 | bashio::log.info "Database dump created successfully at ${BACKUP_FILE}" 30 | 31 | # Set proper permissions 32 | chmod 600 "${BACKUP_FILE}" 33 | chown postgres:postgres "${BACKUP_FILE}" 34 | 35 | # Log file size for verification 36 | BACKUP_SIZE=$(du -h "${BACKUP_FILE}" | cut -f1) 37 | bashio::log.info "Backup file size: ${BACKUP_SIZE}" 38 | else 39 | bashio::log.error "Failed to create database dump!" 40 | exit 1 41 | fi 42 | else 43 | bashio::log.warning "PostgreSQL is not running. Skipping database dump." 44 | bashio::log.warning "Note: Only file-level backup will be performed." 45 | fi 46 | 47 | bashio::log.info "Pre-backup process completed." 48 | -------------------------------------------------------------------------------- /timescaledb/docker-dependencies/postgis-pg16: -------------------------------------------------------------------------------- 1 | 2 | FROM timescale/timescaledb:latest-pg16 AS timescale-pg16 3 | 4 | # -------------------------------------- 5 | # PostGIS - Build and install OLD Postgis (for upgrade reasons) 6 | # -------------------------------------- 7 | ARG VERSION 8 | ARG TARGETARCH 9 | ARG TARGETVARIANT 10 | 11 | # PostGIS - Add required packages for runtime (see postgis dependencies) 12 | # hadolint ignore=DL3018 13 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 14 | set -ex \ 15 | && apk update \ 16 | && apk add \ 17 | openexr \ 18 | poppler \ 19 | gdal \ 20 | geos \ 21 | json-c \ 22 | libgcc \ 23 | libpq \ 24 | libstdc++ \ 25 | libxml2 \ 26 | musl \ 27 | pcre \ 28 | perl \ 29 | proj \ 30 | protobuf-c 31 | 32 | # hadolint ignore=DL3018, DL3003 33 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 34 | set -ex \ 35 | && apk update \ 36 | && apk add --virtual .fetch-deps \ 37 | ca-certificates \ 38 | git \ 39 | openssl \ 40 | openssl-dev \ 41 | tar \ 42 | wget \ 43 | && mkdir -p /build/ \ 44 | && wget --progress=dot:giga -P /build https://download.osgeo.org/postgis/source/postgis-${VERSION}.tar.gz \ 45 | && apk add --virtual .build-deps \ 46 | coreutils \ 47 | gcc \ 48 | make \ 49 | cmake \ 50 | build-base \ 51 | libxml2-dev \ 52 | protobuf-c-dev \ 53 | clang19 \ 54 | llvm19 \ 55 | && apk add --virtual .community-build-deps \ 56 | geos-dev \ 57 | proj-dev \ 58 | gdal-dev \ 59 | && cd /build \ 60 | && tar xvzf postgis-${VERSION}.tar.gz \ 61 | && cd /build/postgis-${VERSION} \ 62 | && ./configure --with-pgconfig=/usr/local/bin/pg_config \ 63 | && make && make install \ 64 | && cd ~ \ 65 | && apk del .fetch-deps .build-deps .community-build-deps \ 66 | && rm -rf /build 67 | 68 | # Labels 69 | LABEL \ 70 | org.opencontainers.image.source="https://github.com/expaso/hassos-addon-timescaledb" 71 | -------------------------------------------------------------------------------- /timescaledb/docker-dependencies/postgis-pg17: -------------------------------------------------------------------------------- 1 | 2 | FROM timescale/timescaledb:latest-pg17 AS timescale-pg17 3 | 4 | # -------------------------------------- 5 | # PostGIS - Build and install OLD Postgis (for upgrade reasons) 6 | # -------------------------------------- 7 | ARG VERSION 8 | ARG TARGETARCH 9 | ARG TARGETVARIANT 10 | 11 | # PostGIS - Add required packages for runtime (see postgis dependencies) 12 | # hadolint ignore=DL3018 13 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 14 | set -ex \ 15 | && apk update \ 16 | && apk add \ 17 | openexr \ 18 | poppler \ 19 | gdal \ 20 | geos \ 21 | json-c \ 22 | libgcc \ 23 | libpq \ 24 | libstdc++ \ 25 | libxml2 \ 26 | musl \ 27 | pcre \ 28 | perl \ 29 | proj \ 30 | protobuf-c 31 | 32 | # hadolint ignore=DL3018, DL3003 33 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 34 | set -ex \ 35 | && apk update \ 36 | && apk add --virtual .fetch-deps \ 37 | ca-certificates \ 38 | git \ 39 | openssl \ 40 | openssl-dev \ 41 | tar \ 42 | wget \ 43 | && mkdir -p /build/ \ 44 | && wget --progress=dot:giga -P /build https://download.osgeo.org/postgis/source/postgis-${VERSION}.tar.gz \ 45 | && apk add --virtual .build-deps \ 46 | coreutils \ 47 | gcc \ 48 | make \ 49 | cmake \ 50 | build-base \ 51 | libxml2-dev \ 52 | protobuf-c-dev \ 53 | clang19 \ 54 | llvm19 \ 55 | && apk add --virtual .community-build-deps \ 56 | geos-dev \ 57 | proj-dev \ 58 | gdal-dev \ 59 | && cd /build \ 60 | && tar xvzf postgis-${VERSION}.tar.gz \ 61 | && cd /build/postgis-${VERSION} \ 62 | && ./configure --with-pgconfig=/usr/local/bin/pg_config \ 63 | && make && make install \ 64 | && cd ~ \ 65 | && apk del .fetch-deps .build-deps .community-build-deps \ 66 | && rm -rf /build 67 | 68 | # Labels 69 | LABEL \ 70 | org.opencontainers.image.source="https://github.com/expaso/hassos-addon-timescaledb" 71 | -------------------------------------------------------------------------------- /timescaledb/rootfs/package/admin/s6-overlay/etc/s6-rc/scripts/base-addon-banner: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck shell=bash 3 | # ============================================================================== 4 | # Home Assistant Community Add-on: Base Images 5 | # Displays a simple add-on banner on startup 6 | # ============================================================================== 7 | declare BUILD_TIMESTAMP 8 | declare RUNNING_IN_HA 9 | 10 | BUILD_TIMESTAMP="$(cat /build-timestamp)" 11 | 12 | set +u 13 | if bashio::supervisor.ping ; then 14 | RUNNING_IN_HA="true" 15 | else 16 | RUNNING_IN_HA="false" 17 | bashio::log.magenta 'This is to be expected when running standalone.' 18 | fi 19 | set -u 20 | 21 | bashio::log.blue \ 22 | '-----------------------------------------------------------' 23 | bashio::log.blue " Add-on: TimescaleDB" 24 | bashio::log.blue " An open-source database built on PostgreSQL for analyzing time-series data with the power and convenience of SQL" 25 | bashio::log.blue \ 26 | '-----------------------------------------------------------' 27 | bashio::log.blue " Provided by: Hans van Essen " 28 | bashio::log.blue \ 29 | '-----------------------------------------------------------' 30 | 31 | if (bashio::var.true "${RUNNING_IN_HA}"); then 32 | bashio::log.blue " Add-on version: $(bashio::addon.version)" 33 | if bashio::var.true "$(bashio::addon.update_available)"; then 34 | bashio::log.magenta ' There is an update available for this add-on!' 35 | bashio::log.magenta " Latest add-on version: $(bashio::addon.version_latest)" 36 | bashio::log.magenta ' Please consider upgrading as soon as possible.' 37 | else 38 | bashio::log.green ' You are running the latest version of this add-on.' 39 | fi 40 | bashio::log.blue " System: $(bashio::info.operating_system)" \ 41 | " ($(bashio::info.arch) / $(bashio::info.machine))" 42 | 43 | bashio::log.blue " Home Assistant Core: $(bashio::info.homeassistant)" 44 | bashio::log.blue " Home Assistant Supervisor: $(bashio::info.supervisor)" 45 | else 46 | bashio::log.magenta " Addon running in standalone mode." 47 | bashio::log.magenta " Addon Compile Date: ${BUILD_TIMESTAMP}" 48 | bashio::log.green " You can safely ignore any API errors." 49 | fi 50 | 51 | bashio::log.blue \ 52 | '-----------------------------------------------------------' 53 | bashio::log.blue \ 54 | ' Please, share the above information when looking for help' 55 | bashio::log.blue \ 56 | ' or support in, e.g., GitHub, forums or the Discord chat.' 57 | bashio::log.blue \ 58 | '-----------------------------------------------------------' 59 | -------------------------------------------------------------------------------- /timescaledb/docker-dependencies/timescaledb-toolkit-pg16: -------------------------------------------------------------------------------- 1 | FROM timescale/timescaledb:latest-pg16 AS timescale-pg16 2 | 3 | FROM timescale-pg16 AS timescaledb-toolkit 4 | ARG VERSION 5 | ARG TARGETARCH 6 | ARG TARGETVARIANT 7 | 8 | # hadolint ignore=DL3018, SC3014 9 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 10 | --mount=type=cache,id=cargo-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/root/.cargo \ 11 | set -ex \ 12 | && apk update \ 13 | && if [ "$TARGETARCH" == "amd64" ] || [ "$TARGETARCH" == "arm64" ]; then \ 14 | apk add \ 15 | ca-certificates \ 16 | git \ 17 | openssl \ 18 | openssl-dev \ 19 | tar \ 20 | wget \ 21 | curl \ 22 | && apk add \ 23 | coreutils \ 24 | gcc \ 25 | make \ 26 | cmake \ 27 | build-base \ 28 | && apk add --repository http://dl-cdn.alpinelinux.org/alpine/v3.21/main/ \ 29 | rust \ 30 | cargo \ 31 | && export PATH="/root/.cargo/bin:${PATH}" \ 32 | && cargo install --version '=0.12.8' --locked --force cargo-pgrx \ 33 | && cargo install rustfmt; \ 34 | fi 35 | 36 | # Add compatibility libraries for timescaledb toolkit 37 | # hadolint ignore=DL3018, SC3014 38 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 39 | set -ex \ 40 | && apk update \ 41 | && if [ "$TARGETARCH" == "amd64" ] || [ "$TARGETARCH" == "arm64" ]; then \ 42 | apk add gcompat clang16-libclang clang15-libclang; \ 43 | fi 44 | 45 | ENV RUST_MIN_STACK=4194304 46 | # hadolint ignore=DL3003, SC3014 47 | RUN --mount=type=cache,id=pgrx-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/root/.pgrx \ 48 | --mount=type=cache,id=cargo-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/root/.cargo \ 49 | set -ex \ 50 | && if [ "$TARGETARCH" == "amd64" ] || [ "$TARGETARCH" == "arm64" ]; then \ 51 | export PATH="/root/.cargo/bin:${PATH}" \ 52 | && mkdir -p /build/ \ 53 | && cd /build \ 54 | && git clone https://github.com/timescale/timescaledb-toolkit.git . \ 55 | && git checkout ${VERSION} \ 56 | && cd extension \ 57 | # initdb fails as root so generate the correct config ourselves 58 | # && cargo pgx init --pg16 /usr/lib/postgresql/14/bin/pg_config 59 | && mkdir -p /root/.pgrx \ 60 | && printf '[configs]\npg16="/usr/local/bin/pg_config"\n' > /root/.pgrx/config.toml \ 61 | && cargo pgrx install --release \ 62 | && cargo run --manifest-path ../tools/post-install/Cargo.toml -- /usr/local/bin/pg_config \ 63 | && cd ~ \ 64 | && rm -rf /build \ 65 | && echo "OK" /toolkit; \ 66 | fi 67 | 68 | # Labels 69 | LABEL \ 70 | org.opencontainers.image.source="https://github.com/expaso/hassos-addon-timescaledb" 71 | -------------------------------------------------------------------------------- /timescaledb/docker-dependencies/timescaledb-toolkit-pg17: -------------------------------------------------------------------------------- 1 | FROM timescale/timescaledb:latest-pg17 AS timescale-pg17 2 | 3 | FROM timescale-pg17 AS timescaledb-toolkit 4 | ARG VERSION 5 | ARG TARGETARCH 6 | ARG TARGETVARIANT 7 | 8 | # hadolint ignore=DL3018, SC3014 9 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 10 | --mount=type=cache,id=cargo-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/root/.cargo \ 11 | set -ex \ 12 | && apk update \ 13 | && if [ "$TARGETARCH" == "amd64" ] || [ "$TARGETARCH" == "arm64" ]; then \ 14 | apk add \ 15 | ca-certificates \ 16 | git \ 17 | openssl \ 18 | openssl-dev \ 19 | tar \ 20 | wget \ 21 | curl \ 22 | && apk add \ 23 | coreutils \ 24 | gcc \ 25 | make \ 26 | cmake \ 27 | build-base \ 28 | && apk add --repository http://dl-cdn.alpinelinux.org/alpine/v3.21/main/ \ 29 | rust \ 30 | cargo \ 31 | && export PATH="/root/.cargo/bin:${PATH}" \ 32 | && cargo install --version '=0.12.8' --locked --force cargo-pgrx \ 33 | && cargo install rustfmt; \ 34 | fi 35 | 36 | # Add compatibility libraries for timescaledb toolkit 37 | # hadolint ignore=DL3018, SC3014 38 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 39 | set -ex \ 40 | && apk update \ 41 | && if [ "$TARGETARCH" == "amd64" ] || [ "$TARGETARCH" == "arm64" ]; then \ 42 | apk add gcompat clang16-libclang clang15-libclang; \ 43 | fi 44 | 45 | ENV RUST_MIN_STACK=4194304 46 | # hadolint ignore=DL3003, SC3014 47 | RUN --mount=type=cache,id=pgrx-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/root/.pgrx \ 48 | --mount=type=cache,id=cargo-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/root/.cargo \ 49 | set -ex \ 50 | && if [ "$TARGETARCH" == "amd64" ] || [ "$TARGETARCH" == "arm64" ]; then \ 51 | export PATH="/root/.cargo/bin:${PATH}" \ 52 | && mkdir -p /build/ \ 53 | && cd /build \ 54 | && git clone https://github.com/timescale/timescaledb-toolkit.git . \ 55 | && git checkout ${VERSION} \ 56 | && cd extension \ 57 | # initdb fails as root so generate the correct config ourselves 58 | # && cargo pgx init --pg17 /usr/lib/postgresql/17/bin/pg_config 59 | && mkdir -p /root/.pgrx \ 60 | && printf '[configs]\npg17="/usr/local/bin/pg_config"\n' > /root/.pgrx/config.toml \ 61 | && cargo pgrx install --release \ 62 | && cargo run --manifest-path ../tools/post-install/Cargo.toml -- /usr/local/bin/pg_config \ 63 | && cd ~ \ 64 | && rm -rf /build \ 65 | && echo "OK" /toolkit; \ 66 | fi 67 | 68 | # Labels 69 | LABEL \ 70 | org.opencontainers.image.source="https://github.com/expaso/hassos-addon-timescaledb" 71 | -------------------------------------------------------------------------------- /README-DEV.md: -------------------------------------------------------------------------------- 1 | # Local development 2 | 3 | ## Make sure cross-platform builds are enabled 4 | 5 | - Install QUEMO Emulation: `docker run --privileged --rm tonistiigi/binfmt --install all` 6 | - Make sure a docker build builder is applied: `docker buildx create --use --name mybuilder && docker buildx inspect mybuilder --bootstrap` 7 | 8 | ## The easy way 9 | 10 | Run `./project.sh` in the root of the project to build / debug / run the addon during development. 11 | 12 | Here are the commands explained: 13 | 14 | - `build` build the addon for the current architecture (see PLATFORM in the file) tag it as `dev` and push to docker hub. 15 | 16 | - `build-ha` Use the home-assistant builder to build all architechtures and push them to docker hub with the tag `latest`. 17 | 18 | - `run-hassos` build the addon for the current architecture (see PLATFORM in the file) tag it as `dev`, puish it to docker hub, SSH login to home-assistant, and pull the image. 19 | 20 | - `inspect` build the addon for the current architecture (see PLATFORM in the file) tag it as `dev`, and run it locally with an interactive shell (/bin/ash). 21 | 22 | - `debug` build the addon for the current architecture (see PLATFORM in the file) tag it as `dev`, and run it locally with normal startup (not interactive). 23 | 24 | ## The manual way 25 | 26 | To build the lastest version using local docker, switch to the folder where the `dockerfile` resides, and run: 27 | 28 | ``` 29 | docker build --platform linux/aarch64 --tag ghcr.io/expaso/timescaledb/aarch64:dev . 30 | ``` 31 | 32 | The dockerfile already contains the default build architecture and the default base image: 33 | 34 | ``` 35 | ARG BUILD_FROM=ghcr.io/hassio-addons/base/aarch64:14.0.2 36 | ARG BUILD_ARCH=aarch64 37 | ``` 38 | 39 | Hereafter, you can push the image to dockerhub using cmd of docker desktop for testing purposes. 40 | 41 | ## Build using Home Assistant Builder (https://github.com/home-assistant/builder) 42 | 43 | To build the latest version using the HomeAssistant Addon Builder container, for `aarch64 architecture` for example, run: 44 | 45 | ``` 46 | docker run --rm --privileged -v ~/.docker:/root/.docker -v ~/hassos-addon-timescaledb/timescaledb:/data homeassistant/amd64-builder -v /var/run/docker.sock:/var/run/docker.sock:ro --target timescaledb --aarch64 -t /data 47 | ``` 48 | 49 | To use it with codenotary CAS signing: 50 | 51 | ``` 52 | docker run --rm --privileged --env CAS_API_KEY=$CAS_API_KEY -v ~/.docker:/root/.docker -v /var/run/docker.sock:/var/run/docker.sock:ro -v ~/hassos-addon-timescaledb/timescaledb:/data homeassistant/amd64-builder --target timescaledb --aarch64 -t /data 53 | ``` 54 | 55 | This will use the base images from the `build.yaml` file, and the architecture specified. Use `--all` instead of `--aarch64` to build all architectures within the `config.yaml`for example. 56 | 57 | ## Push latest DEV image to repository 58 | 59 | docker image push ghcr.io/expaso/timescaledb/aarch64:dev 60 | 61 | ## Pull latest DEV image into your raspoberry pi 62 | 63 | SSH to a home assistant: `ssh -i hassos -l root -p 22222 10.50.1.104` 64 | From a system SSH (port 22222): 65 | 66 | ``` 67 | docker image pull ghcr.io/expaso/timescaledb/aarch64:dev 68 | ``` 69 | 70 | ## Run the addon with an interactive shell 71 | 72 | From a system SSH (port 22222), run the docker container with data attached: 73 | 74 | ``` 75 | docker run -it --entrypoint "/bin/sh" -v /mnt/data/supervisor/addons/data/local_timescaledb/:/data:rw ghcr.io/expaso/timescaledb/aarch64:dev 76 | ``` 77 | 78 | ## For simple DEV inspection, run the container with a shell 79 | 80 | ``` 81 | docker run -it --entrypoint "/bin/sh" ghcr.io/expaso/timescaledb/aarch64:dev 82 | ``` 83 | 84 | ## OR attach to a running container 85 | 86 | ``` 87 | docker exec -it addon_local_timescaledb bash 88 | ``` 89 | -------------------------------------------------------------------------------- /timescaledb/rootfs/usr/share/timescaledb/002_timescaledb_tune.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NO_TS_TUNE=${NO_TS_TUNE:-""} 4 | TS_TUNE_MEMORY=${TS_TUNE_MEMORY:-""} 5 | TS_TUNE_NUM_CPUS=${TS_TUNE_NUM_CPUS:-""} 6 | 7 | if [ -n "${NO_TS_TUNE:-}" ]; then 8 | # The user has explicitly requested not to run timescaledb-tune; exit this script 9 | exit 0 10 | fi 11 | 12 | if [ -z "${POSTGRESQL_CONF_DIR:-}" ]; then 13 | POSTGRESQL_CONF_DIR=${PGDATA} 14 | fi 15 | 16 | if [ -z "${TS_TUNE_MEMORY:-}" ]; then 17 | # See if we can get the container's total allocated memory from the cgroups metadata 18 | if [ -f /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then 19 | TS_TUNE_MEMORY=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes) 20 | 21 | if [ "${TS_TUNE_MEMORY}" = "18446744073709551615" ]; then 22 | # Bash seems to error out for numbers greater than signed 64-bit, 23 | # so if the value of limit_in_bytes is the 64-bit UNSIGNED max value 24 | # we should just bail out and hope timescaledb-tune can figure this 25 | # out. If we don't, the next comparison is likely going to fail 26 | # or it might store a negative value which will crash later. 27 | TS_TUNE_MEMORY="" 28 | fi 29 | 30 | FREE_MB=$(free -m | grep 'Mem' | awk '{print $2}') 31 | FREE_BYTES=$(( FREE_MB * 1024 * 1024 )) 32 | if [ "${TS_TUNE_MEMORY}" -gt ${FREE_BYTES} ]; then 33 | # Something weird is going on if the cgroups memory limit exceeds the total available 34 | # amount of system memory reported by "free", which is the total amount of memory available on the host. 35 | # Most likely, it is this issue: https://github.com/moby/moby/issues/18087 (if no limit is 36 | # set, the max limit is set to the max 64 bit integer). In this case, we just leave 37 | # TS_TUNE_MEMORY blank and let timescaledb-tune derive the memory itself using syscalls. 38 | TS_TUNE_MEMORY="" 39 | else 40 | # Convert the bytes to MB so it plays nicely with timescaledb-tune 41 | TS_TUNE_MEMORY="$(echo "${TS_TUNE_MEMORY}" | awk '{print int($1 / 1024 / 1024)}')MB" 42 | fi 43 | fi 44 | fi 45 | 46 | if [ -z "${TS_TUNE_NUM_CPUS:-}" ]; then 47 | # See if we can get the container's available CPUs from the cgroups metadata 48 | if [ -f /sys/fs/cgroup/cpuset/cpuset.cpus ]; then 49 | TS_TUNE_NUM_CPUS=$(cat /sys/fs/cgroup/cpuset/cpuset.cpus) 50 | if [[ ${TS_TUNE_NUM_CPUS} == *-* ]]; then 51 | # The CPU limits have been defined as a range (e.g., 0-3 for 4 CPUs). Subtract them and add 1 52 | # to convert the range to the number of CPUs. 53 | TS_TUNE_NUM_CPUS=$(echo "${TS_TUNE_NUM_CPUS}" | tr "-" " " | awk '{print ($2 - $1) + 1}') 54 | elif [[ ${TS_TUNE_NUM_CPUS} == *,* ]]; then 55 | # The CPU limits have been defined as a comma separated list (e.g., 0,1,2,3 for 4 CPUs). Count each CPU 56 | TS_TUNE_NUM_CPUS=$(echo "${TS_TUNE_NUM_CPUS}" | tr "," "\n" | wc -l) 57 | elif [ "$(echo -n "${TS_TUNE_NUM_CPUS}" | wc -c)" -eq 1 ]; then 58 | # The CPU limit has been defined as a single numbered CPU. In this case the CPU limit is 1 59 | # regardless of what that number is 60 | TS_TUNE_NUM_CPUS=1 61 | fi 62 | fi 63 | fi 64 | 65 | if [ -n "${TS_TUNE_MEMORY:-}" ]; then 66 | TS_TUNE_MEMORY_FLAGS=--memory="${TS_TUNE_MEMORY}" 67 | fi 68 | 69 | if [ -n "${TS_TUNE_NUM_CPUS:-}" ]; then 70 | TS_TUNE_NUM_CPUS_FLAGS=--cpus=${TS_TUNE_NUM_CPUS} 71 | fi 72 | 73 | if [ -n "${PG_MAJOR}" ]; then 74 | TS_TUNE_PG_VERSION=--pg-version=${PG_MAJOR} 75 | fi 76 | 77 | /usr/local/bin/timescaledb-tune --quiet --yes --conf-path="${POSTGRESQL_CONF_DIR}/postgresql.conf" "${TS_TUNE_MEMORY_FLAGS}" "${TS_TUNE_NUM_CPUS_FLAGS}" "${TS_TUNE_PG_VERSION}" 78 | -------------------------------------------------------------------------------- /timescaledb/pgagent.patch: -------------------------------------------------------------------------------- 1 | diff --git a/connection.cpp b/connection.cpp 2 | index 916d891..163e7f4 100644 3 | --- a/connection.cpp 4 | +++ b/connection.cpp 5 | @@ -231,7 +231,7 @@ void DBconn::Return() 6 | 7 | // Cleanup 8 | ExecuteVoid("RESET ALL"); 9 | - m_lastError.clear(); 10 | + m_lastError = ""; 11 | m_inUse = false; 12 | 13 | LogMessage(( 14 | @@ -307,6 +307,39 @@ void DBconn::ClearConnections(bool all) 15 | LogMessage("No connections found!", LOG_DEBUG); 16 | } 17 | 18 | +bool DBconn::ValidateConnection() 19 | +{ 20 | + // Check Connection Status 21 | + ConnStatusType status = PQstatus(m_conn); 22 | + if (status == CONNECTION_OK) { 23 | + return true; 24 | + } 25 | + 26 | + m_lastError = PQerrorMessage(m_conn); 27 | + LogMessage("Conenction was broken. Trying to recover: " + m_lastError, LOG_WARNING); 28 | + 29 | + // If not good, reset the connection 30 | + Reset(); 31 | + 32 | + // Check the status again 33 | + status = PQstatus(this->m_conn); 34 | + if (status != CONNECTION_OK) { 35 | + m_lastError = PQerrorMessage(m_conn); 36 | + LogMessage("Connection error: " + m_lastError, LOG_ERROR); 37 | + return false; 38 | + } 39 | + 40 | + return true; 41 | +} 42 | + 43 | +void DBconn::Reset() 44 | +{ 45 | + if (m_conn) 46 | + { 47 | + PQreset(m_conn); 48 | + } 49 | +} 50 | + 51 | 52 | DBresult *DBconn::Execute(const std::string &query) 53 | { 54 | @@ -364,6 +397,12 @@ std::string DBconn::GetLastError() 55 | 56 | DBresult::DBresult(DBconn *conn, const std::string &query) 57 | { 58 | + if (!conn->ValidateConnection()) 59 | + { 60 | + LogMessage("Connection error: " + conn->m_lastError, LOG_WARNING); 61 | + return; 62 | + } 63 | + 64 | m_currentRow = 0; 65 | m_maxRows = 0; 66 | 67 | @@ -377,8 +416,12 @@ DBresult::DBresult(DBconn *conn, const std::string &query) 68 | m_maxRows = PQntuples(m_result); 69 | else if (rc != PGRES_COMMAND_OK) 70 | { 71 | + // Log error 72 | + std::string errorStatus = PQresStatus((ExecStatusType) rc); 73 | + std::string errorResult = PQresultErrorMessage(m_result); 74 | conn->m_lastError = PQerrorMessage(conn->m_conn); 75 | - LogMessage("Query error: " + conn->m_lastError, LOG_WARNING); 76 | + LogMessage("pgAgent Query error: " + errorResult + " with status: " + errorStatus + " Connection Error: " + conn->m_lastError, LOG_WARNING); 77 | + 78 | PQclear(m_result); 79 | m_result = nullptr; 80 | } 81 | diff --git a/include/connection.h b/include/connection.h 82 | index 70ea408..8db0645 100644 83 | --- a/include/connection.h 84 | +++ b/include/connection.h 85 | @@ -57,6 +57,8 @@ public: 86 | std::string ExecuteScalar(const std::string &query); 87 | int ExecuteVoid(const std::string &query); 88 | void Return(); 89 | + bool ValidateConnection(); 90 | + void Reset(); // Reset the connection to the database 91 | 92 | const std::string &DebugConnectionStr() const; 93 | 94 | diff --git a/job.cpp b/job.cpp 95 | index fbc823e..a25a797 100644 96 | --- a/job.cpp 97 | +++ b/job.cpp 98 | @@ -391,6 +391,13 @@ int Job::Execute() 99 | else 100 | stepstatus = steps->GetString("jstonerror"); 101 | 102 | + LogMessage("Setting job status to finished", LOG_DEBUG); 103 | + 104 | + // Reset the connection to the database because 105 | + // we may have lost the connection during the batch run. 106 | + // This happens sometimes witrh the messase: could not receive data from server: Bad file descriptor 107 | + m_threadConn->Reset(); 108 | + 109 | rc = m_threadConn->ExecuteVoid( 110 | "UPDATE pgagent.pga_jobsteplog " 111 | " SET jslduration = now() - jslstart, " 112 | @@ -402,6 +409,9 @@ int Job::Execute() 113 | m_status = "f"; 114 | return -1; 115 | } 116 | + 117 | + LogMessage("Moving to next step..", LOG_DEBUG); 118 | + 119 | steps->MoveNext(); 120 | } 121 | 122 | -------------------------------------------------------------------------------- /timescaledb/rootfs/usr/share/timescaledb/restore_from_backup.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # ============================================================================== 3 | # Home Assistant Add-on: TimescaleDb 4 | # Restore script - Restores database from SQL dump 5 | # ============================================================================== 6 | declare BACKUP_FILE 7 | declare POSTGRES_DATA 8 | 9 | BACKUP_FILE="/data/backup_db.sql" 10 | POSTGRES_DATA="/data/postgres" 11 | 12 | # Function to restore from SQL backup 13 | restoreFromBackup() { 14 | bashio::log.notice "===================================================================" 15 | bashio::log.notice " DATABASE RESTORE IN PROGRESS" 16 | bashio::log.notice "===================================================================" 17 | bashio::log.notice "A backup SQL file was found. Attempting to restore database..." 18 | 19 | # Verify backup file exists and is readable 20 | if [[ ! -f "${BACKUP_FILE}" ]]; then 21 | bashio::log.error "Backup file not found at ${BACKUP_FILE}" 22 | return 1 23 | fi 24 | 25 | if [[ ! -r "${BACKUP_FILE}" ]]; then 26 | bashio::log.error "Backup file is not readable at ${BACKUP_FILE}" 27 | return 1 28 | fi 29 | 30 | # Log backup file info 31 | BACKUP_SIZE=$(du -h "${BACKUP_FILE}" | cut -f1) 32 | bashio::log.info "Backup file size: ${BACKUP_SIZE}" 33 | 34 | # Start postgres temporarily for restore 35 | bashio::log.info "Starting PostgreSQL for restore process..." 36 | su - postgres -c "postgres -D ${POSTGRES_DATA}" & 37 | POSTGRES_PID=$! 38 | 39 | # Wait for postgres to become available 40 | bashio::log.info "Waiting for PostgreSQL to be ready..." 41 | RETRY_COUNT=0 42 | MAX_RETRIES=30 43 | while ! psql -U postgres postgres -c "" 2>/dev/null; do 44 | sleep 1 45 | RETRY_COUNT=$((RETRY_COUNT + 1)) 46 | if [[ ${RETRY_COUNT} -ge ${MAX_RETRIES} ]]; then 47 | bashio::log.error "PostgreSQL failed to start within ${MAX_RETRIES} seconds" 48 | kill "${POSTGRES_PID}" 2>/dev/null || true 49 | return 1 50 | fi 51 | done 52 | 53 | bashio::log.info "PostgreSQL is ready. Starting restore..." 54 | 55 | # Restore the backup 56 | if su - postgres -c "psql -X -U postgres -f ${BACKUP_FILE} -d postgres" 2>&1 | tee /var/log/timescaledb.restore.log; then 57 | bashio::log.notice "Database restored successfully from backup!" 58 | 59 | # Stop postgres 60 | bashio::log.info "Stopping PostgreSQL..." 61 | kill "${POSTGRES_PID}" 62 | wait "${POSTGRES_PID}" || true 63 | 64 | # Remove the backup file after successful restore 65 | bashio::log.info "Removing backup file after successful restore..." 66 | rm -f "${BACKUP_FILE}" 67 | 68 | bashio::log.notice "===================================================================" 69 | bashio::log.notice " DATABASE RESTORE COMPLETED SUCCESSFULLY" 70 | bashio::log.notice "===================================================================" 71 | return 0 72 | else 73 | bashio::log.error "Failed to restore database from backup!" 74 | bashio::log.error "Check /var/log/timescaledb.restore.log for details" 75 | 76 | # Stop postgres 77 | kill "${POSTGRES_PID}" 2>/dev/null || true 78 | wait "${POSTGRES_PID}" 2>/dev/null || true 79 | 80 | bashio::log.notice "===================================================================" 81 | bashio::log.notice " DATABASE RESTORE FAILED" 82 | bashio::log.notice "===================================================================" 83 | return 1 84 | fi 85 | } 86 | 87 | # Export the function so it can be called from other scripts 88 | export -f restoreFromBackup 89 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/postgres/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck shell=bash 3 | # ============================================================================== 4 | # Start PostgreSQL service if enabled 5 | # ============================================================================== 6 | declare POSTGRES_DATA 7 | declare UPDATE 8 | declare POSTGRES_PID 9 | declare VERSION_FILE 10 | declare BUILD_TIMESTAMP 11 | 12 | POSTGRES_DATA=/data/postgres 13 | VERSION_FILE=/data/version 14 | BUILD_TIMESTAMP="$(cat /build-timestamp)" 15 | UPDATE=false 16 | 17 | # Check if we need to update 18 | if [[ $(cat ${VERSION_FILE}) != ${BUILD_TIMESTAMP} ]]; then 19 | UPDATE=true 20 | fi 21 | 22 | # Start Postgres 23 | bashio::log.info "Starting PostgreSQL.." 24 | su - postgres -c "postgres -D ${POSTGRES_DATA}" & 25 | POSTGRES_PID=$! 26 | bashio::log.info "done" 27 | 28 | # Wait for postgres to become available.. 29 | while ! psql -U "postgres" postgres -c "" 2> /dev/null; do 30 | sleep 1 31 | done 32 | 33 | # Setup postgres user on first run 34 | if [[ -f "/data/firstrun" ]]; then 35 | bashio::log.info "Setting up postgres user.." 36 | psql -U "postgres" postgres -c "ALTER USER postgres WITH PASSWORD 'homeassistant';" 37 | fi 38 | 39 | # Check for extension updates.. 40 | if bashio::var.true "${UPDATE}"; then 41 | 42 | bashio::log.notice "Addon was updated to version timestamp: ${BUILD_TIMESTAMP}. Performing database upgrades..." 43 | 44 | # Fetch all databases.. 45 | psql \ 46 | -X \ 47 | -U postgres \ 48 | -c "select datname from pg_database where datallowconn = true;" \ 49 | --set ON_ERROR_STOP=on \ 50 | --no-align \ 51 | -t \ 52 | --field-separator ' ' \ 53 | --quiet \ 54 | | while read datname; do 55 | # Update Timescale extension when needed 56 | psql -X -U postgres -d ${datname} -c "select 1 from pg_extension where extname = 'timescaledb';" | grep -q 1 \ 57 | && bashio::log.info "Try updating Timescale Extension for database: '${datname}'.." \ 58 | && psql -X -U postgres -d ${datname} -c "ALTER EXTENSION timescaledb UPDATE;" || true >> /var/log/timescaledb.upgrade.log 59 | 60 | # Update Postgis when extension needed 61 | psql -X -U postgres -d ${datname} -c "select 1 from pg_extension where extname = 'postgis';" | grep -q 1 \ 62 | && bashio::log.info "Try updating Postgis Extension for database: '${datname}'.." \ 63 | && psql -X -U postgres -d ${datname} -c "SELECT PostGIS_Extensions_Upgrade();" || true >> /var/log/postgis.upgrade.log 64 | 65 | # Update TimescaleDb Toolkit extension when needed 66 | psql -X -U postgres -d ${datname} -c "select 1 from pg_extension where extname = 'timescaledb_toolkit';" | grep -q 1 \ 67 | && bashio::log.info "Try updating TimescaleDb Toolkit Extension for database: '${datname}'.." \ 68 | && psql -X -U postgres -d ${datname} -c "ALTER EXTENSION timescaledb_toolkit UPDATE;" || true >> /var/log/timescaledb_toolkit.upgrade.log 69 | 70 | # Update Vector extension when needed 71 | psql -X -U postgres -d ${datname} -c "select 1 from pg_extension where extname = 'vector';" | grep -q 1 \ 72 | && bashio::log.info "Try updating Vector Extension for database: '${datname}'.." \ 73 | && psql -X -U postgres -d ${datname} -c "ALTER EXTENSION vector UPDATE;" || true >> /var/log/vector.upgrade.log 74 | 75 | done 76 | fi 77 | 78 | 79 | if bashio::config.has_value 'databases'; then 80 | # Create all databases if not exist 81 | for database in $(bashio::config "databases"); do 82 | bashio::log.info "Create database if not exist: '${database}'" 83 | psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = '${database}'" | grep -q 1 || psql -U postgres -c "CREATE DATABASE ${database}" 84 | done 85 | bashio::log.info "done" 86 | 87 | # Enable the timescale-extensions for all indicated databases 88 | for database in $(bashio::config "timescale_enabled"); do 89 | if ! (psql -X -U postgres -d ${database} -c "select 1 from pg_extension where extname = 'timescaledb';" | grep -q 1); then 90 | bashio::log.info "Enabling Timescale Extension for database: '${database}'" 91 | psql -U "postgres" -d ${database} -c "CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;" || true >> /var/log/timescaledb.create.log 92 | fi; 93 | done 94 | fi 95 | bashio::log.info "done" 96 | 97 | # Exit update mode 98 | rm -f /data/firstrun 99 | if bashio::var.true "${UPDATE}"; then 100 | bashio::log.info "Exiting update mode and restarting.." 101 | # Write new addon-version 102 | echo "${BUILD_TIMESTAMP}" > ${VERSION_FILE} 103 | sleep 3 104 | 105 | # Don't fail on the call. When running in standalone, this will fail. 106 | bashio::addon.restart || true 107 | fi 108 | 109 | # And let it roll 110 | bashio::log.notice "TimescaleDb is running!" 111 | wait "${POSTGRES_PID}" 112 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "rebaseWhen": "behind-base-branch", 4 | "dependencyDashboard": true, 5 | "labels": ["dependencies", "no-stale"], 6 | "commitMessagePrefix": "⬆️", 7 | "commitMessageTopic": "{{depName}}", 8 | "regexManagers": [ 9 | { 10 | "fileMatch": ["/Dockerfile$", "/build.yaml$"], 11 | "matchStringsStrategy": "any", 12 | "matchStrings": [ 13 | "ARG BUILD_FROM=(?.*?):(?.*?)\\s+", 14 | "(aarch64|amd64|armhf|armv7|i386):\\s[\"']?(?.*?):(?.*?)[\"']?\\s" 15 | ], 16 | "datasourceTemplate": "docker" 17 | }, 18 | { 19 | "fileMatch": ["/Dockerfile$"], 20 | "matchStringsStrategy": "any", 21 | "matchStrings": [ 22 | "\\s\\s(?[a-z0-9][a-z0-9-]+)=(?[a-z0-9-:_+~.]+)\\s+" 23 | ], 24 | "versioningTemplate": "deb", 25 | "datasourceTemplate": "repology", 26 | "depNameTemplate": "debian_12/{{{replace 'openssh-client' 'openssh' package}}}" 27 | }, 28 | { 29 | "fileMatch": ["/Dockerfile$"], 30 | "matchStrings": [ 31 | "ARG CODE_SERVER_VERSION=[\"']?(?.+?)[\"']?\\s+" 32 | ], 33 | "datasourceTemplate": "github-releases", 34 | "depNameTemplate": "cdr/code-server" 35 | }, 36 | { 37 | "fileMatch": ["/Dockerfile$"], 38 | "matchStrings": [ 39 | "ARG HA_CLI_VERSION=[\"']?(?.+?)[\"']?\\s+" 40 | ], 41 | "datasourceTemplate": "github-releases", 42 | "depNameTemplate": "home-assistant/cli", 43 | "versioningTemplate": "loose" 44 | }, 45 | { 46 | "fileMatch": ["vscode/vscode.extensions$"], 47 | "matchStrings": ["emilast\\.LogFileHighlighter#(?.+)\\s"], 48 | "datasourceTemplate": "github-releases", 49 | "depNameTemplate": "emilast/vscode-logfile-highlighter" 50 | }, 51 | { 52 | "fileMatch": ["vscode/vscode.extensions$"], 53 | "matchStrings": ["esbenp\\.prettier-vscode#(?.+)\\s"], 54 | "datasourceTemplate": "github-releases", 55 | "depNameTemplate": "prettier/prettier-vscode", 56 | "extractVersionTemplate": "^v(?.*)$" 57 | }, 58 | { 59 | "fileMatch": ["vscode/vscode.extensions$"], 60 | "matchStrings": ["ESPHome\\.esphome-vscode#(?.+)\\s"], 61 | "datasourceTemplate": "github-releases", 62 | "depNameTemplate": "esphome/esphome-vscode", 63 | "versioningTemplate": "loose", 64 | "extractVersionTemplate": "^v(?.*)$" 65 | }, 66 | { 67 | "fileMatch": ["vscode/vscode.extensions$"], 68 | "matchStrings": [ 69 | "keesschollaart\\.vscode-home-assistant#(?.+)\\s" 70 | ], 71 | "datasourceTemplate": "github-releases", 72 | "depNameTemplate": "keesschollaart81/vscode-home-assistant", 73 | "extractVersionTemplate": "^v(?.*)$" 74 | }, 75 | { 76 | "fileMatch": ["vscode/vscode.extensions$"], 77 | "matchStrings": [ 78 | "lukas-tr\\.materialdesignicons-intellisense#(?.+)\\s" 79 | ], 80 | "datasourceTemplate": "github-releases", 81 | "depNameTemplate": "lukas-tr/vscode-materialdesignicons-intellisense", 82 | "extractVersionTemplate": "^v(?.*)$" 83 | }, 84 | { 85 | "fileMatch": ["vscode/vscode.extensions$"], 86 | "matchStrings": ["netcorext\\.uuid-generator#(?.+)\\s"], 87 | "datasourceTemplate": "github-releases", 88 | "depNameTemplate": "arenchen/vscode-uuid-generator" 89 | }, 90 | { 91 | "fileMatch": ["vscode/vscode.extensions$"], 92 | "matchStrings": ["usernamehw\\.errorlens#(?.+)\\s"], 93 | "datasourceTemplate": "github-releases", 94 | "depNameTemplate": "usernamehw/vscode-error-lens", 95 | "extractVersionTemplate": "^v(?.*)$" 96 | } 97 | ], 98 | "packageRules": [ 99 | { 100 | "matchDatasources": ["repology"], 101 | "automerge": true 102 | }, 103 | { 104 | "matchDatasources": ["github-releases"], 105 | "matchDepNames": ["coder/code-server"], 106 | "matchUpdateTypes": ["minor", "patch"], 107 | "automerge": true 108 | }, 109 | { 110 | "matchDatasources": ["github-releases"], 111 | "matchDepNames": ["home-assistant/cli"], 112 | "automerge": true 113 | }, 114 | { 115 | "matchManagers": ["pip_requirements"], 116 | "addLabels": ["python"] 117 | }, 118 | { 119 | "matchManagers": ["pip_requirements"], 120 | "matchDepTypes": ["dev"], 121 | "rangeStrategy": "pin" 122 | }, 123 | { 124 | "matchManagers": ["pip_requirements"], 125 | "matchUpdateTypes": ["minor", "patch"], 126 | "automerge": true 127 | } 128 | ] 129 | } 130 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | frenck@frenck.dev. 64 | 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][mozilla coc]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][faq]. Translations are available 127 | at [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 131 | [mozilla coc]: https://github.com/mozilla/diversity 132 | [faq]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /timescaledb/rootfs/usr/share/timescaledb/003_apply_user_config.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck shell=bash 3 | # ============================================================================== 4 | # Apply user-provided PostgreSQL configuration 5 | # Applies custom postgresql.conf settings and pg_hba.conf rules from addon config 6 | # ============================================================================== 7 | 8 | declare POSTGRES_DATA 9 | declare POSTGRESQL_CONF 10 | declare PG_HBA_CONF 11 | declare -a FORBIDDEN_PARAMS 12 | declare PARAM_COUNT 13 | declare RULE_COUNT 14 | 15 | POSTGRES_DATA=/data/postgres 16 | POSTGRESQL_CONF="${POSTGRES_DATA}/postgresql.conf" 17 | PG_HBA_CONF="${POSTGRES_DATA}/pg_hba.conf" 18 | PARAM_COUNT=0 19 | RULE_COUNT=0 20 | 21 | # List of parameters that cannot be modified by users 22 | # These are critical for addon operation and Home Assistant integration 23 | FORBIDDEN_PARAMS=( 24 | "data_directory" 25 | "hba_file" 26 | "ident_file" 27 | "port" 28 | "unix_socket_directories" 29 | "shared_preload_libraries" 30 | ) 31 | 32 | # Check if a parameter is forbidden 33 | is_forbidden_param() { 34 | local param="${1}" 35 | local forbidden 36 | 37 | for forbidden in "${FORBIDDEN_PARAMS[@]}"; do 38 | if [[ "${param}" == "${forbidden}" ]]; then 39 | return 0 40 | fi 41 | done 42 | return 1 43 | } 44 | 45 | # Apply postgresql.conf configuration 46 | apply_postgresql_config() { 47 | if ! bashio::config.has_value 'postgresql_config'; then 48 | return 0 49 | fi 50 | 51 | bashio::log.info "Applying PostgreSQL configuration parameters..." 52 | 53 | # Get all keys from postgresql_config 54 | for key in $(bashio::config 'postgresql_config | keys[]'); do 55 | # Check if parameter is forbidden 56 | if is_forbidden_param "${key}"; then 57 | bashio::log.warning "Skipping forbidden parameter: ${key} (managed by addon)" 58 | continue 59 | fi 60 | 61 | # Get the value for this key 62 | local value 63 | value=$(bashio::config "postgresql_config.${key}") 64 | 65 | # Check if parameter already exists in the config file 66 | if grep -q "^[[:space:]]*${key}[[:space:]]*=" "${POSTGRESQL_CONF}"; then 67 | # Parameter exists, update it 68 | bashio::log.info "Updating postgresql.conf: ${key} = ${value}" 69 | sed -i "s|^[[:space:]]*${key}[[:space:]]*=.*|${key} = ${value}|" "${POSTGRESQL_CONF}" 70 | else 71 | # Parameter doesn't exist, append it 72 | bashio::log.info "Adding to postgresql.conf: ${key} = ${value}" 73 | echo "${key} = ${value}" >> "${POSTGRESQL_CONF}" 74 | fi 75 | 76 | ((PARAM_COUNT++)) 77 | done 78 | 79 | if [[ ${PARAM_COUNT} -gt 0 ]]; then 80 | bashio::log.info "Applied ${PARAM_COUNT} PostgreSQL configuration parameter(s)" 81 | fi 82 | } 83 | 84 | # Apply pg_hba.conf configuration 85 | apply_pg_hba_config() { 86 | if ! bashio::config.has_value 'pg_hba_config'; then 87 | return 0 88 | fi 89 | 90 | bashio::log.info "Applying pg_hba.conf authentication rules..." 91 | 92 | # Add a comment separator for user rules 93 | echo "" >> "${PG_HBA_CONF}" 94 | echo "# User-defined authentication rules" >> "${PG_HBA_CONF}" 95 | 96 | # Get the number of rules 97 | local rule_count 98 | rule_count=$(bashio::config 'pg_hba_config | length') 99 | 100 | # Process each rule 101 | for (( i=0; i> "${PG_HBA_CONF}" 142 | 143 | ((RULE_COUNT++)) 144 | done 145 | 146 | if [[ ${RULE_COUNT} -gt 0 ]]; then 147 | bashio::log.info "Applied ${RULE_COUNT} pg_hba.conf authentication rule(s)" 148 | fi 149 | } 150 | 151 | # Main execution 152 | main() { 153 | # Verify that configuration files exist 154 | if [[ ! -f "${POSTGRESQL_CONF}" ]]; then 155 | bashio::log.error "postgresql.conf not found at ${POSTGRESQL_CONF}" 156 | return 1 157 | fi 158 | 159 | if [[ ! -f "${PG_HBA_CONF}" ]]; then 160 | bashio::log.error "pg_hba.conf not found at ${PG_HBA_CONF}" 161 | return 1 162 | fi 163 | 164 | # Apply configurations 165 | apply_postgresql_config 166 | apply_pg_hba_config 167 | 168 | # Summary 169 | if [[ ${PARAM_COUNT} -eq 0 ]] && [[ ${RULE_COUNT} -eq 0 ]]; then 170 | bashio::log.info "No user configuration to apply" 171 | else 172 | bashio::log.info "User configuration applied successfully" 173 | fi 174 | 175 | return 0 176 | } 177 | 178 | # Run main function 179 | main 180 | -------------------------------------------------------------------------------- /BACKUP-RESTORE-IMPLEMENTATION.md: -------------------------------------------------------------------------------- 1 | # Backup and Restore Implementation Summary 2 | 3 | ## Overview 4 | 5 | This implementation adds a resilient backup and restore mechanism for the TimescaleDB addon that addresses the problem of backing up databases while they're running. 6 | 7 | ## Changes Made 8 | 9 | ### 1. Configuration (`timescaledb/config.yaml`) 10 | 11 | Added Home Assistant backup lifecycle hooks: 12 | 13 | ```yaml 14 | backup_pre: /usr/share/timescaledb/backup_pre.sh 15 | backup_post: /usr/share/timescaledb/backup_post.sh 16 | backup_exclude: 17 | - /data/postgres/* 18 | ``` 19 | 20 | These hooks ensure that: 21 | 22 | - Before backup: A SQL dump is created 23 | - After backup: The SQL dump is cleaned up 24 | - During backup: The PostgreSQL data directory is excluded (only the SQL dump is backed up) 25 | 26 | ### 2. Pre-Backup Script (`backup_pre.sh`) 27 | 28 | **Location:** `/usr/share/timescaledb/backup_pre.sh` 29 | 30 | **Functionality:** 31 | 32 | - Checks if PostgreSQL is running 33 | - Executes `pg_dumpall` to create a complete SQL dump 34 | - Creates the file `/data/backup_db.sql` 35 | - Sets proper permissions 36 | - Logs the backup size for verification 37 | - Gracefully handles cases where PostgreSQL isn't running 38 | 39 | **Key Features:** 40 | 41 | - Uses `pg_isready` to check PostgreSQL status 42 | - Runs as the postgres user 43 | - Includes `--clean --if-exists` flags for safe restore 44 | - Won't fail the backup if database isn't running 45 | 46 | ### 3. Post-Backup Script (`backup_post.sh`) 47 | 48 | **Location:** `/usr/share/timescaledb/backup_post.sh` 49 | 50 | **Functionality:** 51 | 52 | - Removes the temporary SQL dump file after backup completes 53 | - Saves disk space 54 | - Logs cleanup status 55 | 56 | ### 4. Restore Script (`restore_from_backup.sh`) 57 | 58 | **Location:** `/usr/share/timescaledb/restore_from_backup.sh` 59 | 60 | **Functionality:** 61 | 62 | - Provides `restoreFromBackup()` function 63 | - Starts PostgreSQL temporarily 64 | - Waits for PostgreSQL to be ready (with timeout) 65 | - Restores database from SQL dump using `psql` 66 | - Logs detailed progress and any errors 67 | - Cleans up the backup file after successful restore 68 | - Stops PostgreSQL cleanly after restore 69 | 70 | **Key Features:** 71 | 72 | - Comprehensive error handling 73 | - Progress logging for user visibility 74 | - Retry logic with timeout for PostgreSQL startup 75 | - Preserves backup file if restore fails (for manual recovery) 76 | - Creates detailed restore log at `/var/log/timescaledb.restore.log` 77 | 78 | ### 5. Initialization Script Updates (`init-addon/run`) 79 | 80 | **Enhanced Logic:** 81 | 82 | 1. **Fresh Installation with Backup:** 83 | - Detects if `backup_db.sql` exists on new install 84 | - Enables restore mode 85 | - Initializes fresh database 86 | - Automatically restores from SQL dump 87 | 88 | 2. **Corrupted Database Detection:** 89 | - Checks if PostgreSQL data directory is corrupted 90 | - Detects missing `PG_VERSION` file 91 | - If backup exists, moves corrupted data aside 92 | - Initializes fresh database and restores 93 | 94 | 3. **Automatic Recovery:** 95 | - Restores silently without user intervention 96 | - Skips firstrun setup after successful restore 97 | - Preserves backup file if restore fails 98 | 99 | **New Variables:** 100 | 101 | - `BACKUP_FILE`: Path to SQL dump file 102 | - `RESTORE_MODE`: Flag indicating restore should occur 103 | 104 | ### 6. Dockerfile Updates 105 | 106 | Added execution permissions for the new scripts: 107 | 108 | ```dockerfile 109 | RUN chmod +x /usr/share/timescaledb/backup_pre.sh \ 110 | && chmod +x /usr/share/timescaledb/backup_post.sh \ 111 | && chmod +x /usr/share/timescaledb/restore_from_backup.sh 112 | ``` 113 | 114 | ### 7. Documentation (`README.md`) 115 | 116 | Added comprehensive "Backup and Restore" section covering: 117 | 118 | - How backups work 119 | - How restore works 120 | - Manual backup procedures 121 | - Troubleshooting guide 122 | - Important notes and caveats 123 | 124 | ## How It Works 125 | 126 | ### Backup Flow 127 | 128 | ``` 129 | User triggers HA backup 130 | ↓ 131 | backup_pre.sh runs 132 | ↓ 133 | pg_dumpall creates /data/backup_db.sql 134 | ↓ 135 | HA backs up /data/* (excluding /data/postgres/*) 136 | ↓ 137 | backup_post.sh runs 138 | ↓ 139 | backup_db.sql is removed 140 | ↓ 141 | Backup complete 142 | ``` 143 | 144 | ### Restore Flow 145 | 146 | ``` 147 | User restores HA backup 148 | ↓ 149 | Addon starts with backup_db.sql 150 | ↓ 151 | init-addon/run detects restore scenario 152 | ↓ 153 | Initializes fresh PostgreSQL database 154 | ↓ 155 | restore_from_backup.sh runs 156 | ↓ 157 | Starts PostgreSQL temporarily 158 | ↓ 159 | Restores from SQL dump 160 | ↓ 161 | Stops PostgreSQL 162 | ↓ 163 | Removes backup_db.sql 164 | ↓ 165 | Normal startup continues 166 | ``` 167 | 168 | ## Benefits 169 | 170 | 1. **Consistency:** SQL dumps are transaction-consistent snapshots 171 | 2. **Safety:** No risk of backing up corrupted files 172 | 3. **Portability:** Can restore across PostgreSQL versions 173 | 4. **Size:** Excludes large data directory, only backs up SQL 174 | 5. **Automatic:** No user intervention required 175 | 6. **Resilient:** Handles corrupted databases automatically 176 | 7. **Recoverable:** Preserves backup file if restore fails 177 | 178 | ## Testing Recommendations 179 | 180 | 1. **Test normal backup/restore:** 181 | - Create some test data 182 | - Trigger Home Assistant backup 183 | - Delete database or corrupt it 184 | - Restore from backup 185 | - Verify all data is restored 186 | 187 | 2. **Test with PostgreSQL not running:** 188 | - Stop PostgreSQL 189 | - Trigger backup 190 | - Verify graceful handling 191 | 192 | 3. **Test corrupted database recovery:** 193 | - Corrupt `PG_VERSION` file 194 | - Place a valid `backup_db.sql` in /data/ 195 | - Restart addon 196 | - Verify automatic recovery 197 | 198 | 4. **Test fresh install with backup:** 199 | - Delete PostgreSQL data directory 200 | - Place a valid `backup_db.sql` in /data/ 201 | - Start addon 202 | - Verify restoration occurs 203 | 204 | ## Future Enhancements 205 | 206 | Possible improvements: 207 | 208 | - Add configuration option for backup retention 209 | - Support for compressed SQL dumps 210 | - Incremental backup support 211 | - Backup verification/testing 212 | - Email notifications on backup/restore events 213 | 214 | ## Compliance with Agent Guidelines 215 | 216 | This implementation follows the AGENTS.md guidelines: 217 | 218 | ✅ Uses `bashio::log.*` for all logging 219 | ✅ Quotes all variables properly 220 | ✅ Includes comprehensive error handling 221 | ✅ Documents non-obvious logic 222 | ✅ Uses meaningful variable names 223 | ✅ Follows existing project patterns 224 | ✅ Maintains backward compatibility 225 | ✅ Adds user-facing documentation 226 | ✅ Uses `#!/command/with-contenv bashio` shebang 227 | ✅ Handles edge cases gracefully 228 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # Agent Guidelines for Home Assistant TimescaleDB Addon Development 2 | 3 | ## Overview 4 | 5 | This document provides guidelines for AI coding agents working on this Home Assistant addon. Following these guidelines ensures consistent, maintainable, and high-quality code. 6 | 7 | ## Code Quality Standards 8 | 9 | ### General Principles 10 | 11 | - **Write clean, readable code**: Prioritize clarity over cleverness 12 | - **Follow existing patterns**: Match the style and structure of the existing codebase 13 | - **Document your changes**: Add comments for complex logic, especially in shell scripts 14 | - **Test thoroughly**: Verify changes work in the Home Assistant addon environment 15 | - **Keep it simple**: Avoid unnecessary complexity or over-engineering 16 | 17 | ### Shell Script Best Practices 18 | 19 | - Use `#!/usr/bin/with-contenv bashio` for addon scripts that need Home Assistant integration 20 | - Always quote variables: `"${variable}"` instead of `$variable` 21 | - Use `bashio::log.*` functions for logging (info, warning, error, debug) 22 | - Check return codes and handle errors gracefully 23 | - Use meaningful variable names in ALL_CAPS for constants 24 | - Add comments explaining non-obvious logic 25 | 26 | ### Docker Best Practices 27 | 28 | - Minimize layer count in Dockerfile 29 | - Clean up package manager caches after installations 30 | - Use specific version pins where stability is critical 31 | - Document why specific versions are chosen 32 | - Follow multi-stage build patterns when applicable 33 | 34 | ## Project Structure Understanding 35 | 36 | ### Key Components 37 | 38 | 1. **timescaledb/**: Main addon directory 39 | - `config.yaml`: Addon configuration (options, schema, ports) 40 | - `Dockerfile`: Container build instructions 41 | - `build.yaml`: Build configuration for different architectures 42 | 43 | 2. **rootfs/**: Container root filesystem overlay 44 | - `etc/s6-overlay/s6-rc.d/`: Service definitions using s6-overlay 45 | - `usr/share/timescaledb/`: Initialization scripts 46 | 47 | 3. **docker-dependencies/**: Pre-built extension binaries 48 | 49 | ### Service Management (s6-overlay) 50 | 51 | - Each service has its own directory under `s6-rc.d/` 52 | - Required files: `type`, `run`, and optionally `finish` 53 | - Use `dependencies.d/` to control service startup order 54 | - Services should be resilient and handle failures gracefully 55 | 56 | ## Home Assistant Addon Documentation 57 | 58 | ### Essential Resources 59 | 60 | - **Official Addon Documentation**: https://developers.home-assistant.io/docs/add-ons 61 | - **Addon Configuration**: https://developers.home-assistant.io/docs/add-ons/configuration 62 | - **Bashio Library**: https://github.com/hassio-addons/bashio (for interacting with Home Assistant) 63 | - **s6-overlay**: https://github.com/just-containers/s6-overlay (service supervision) 64 | 65 | ### Key Addon Concepts 66 | 67 | #### Configuration (`config.yaml`) 68 | 69 | ```yaml 70 | options: # User-configurable options 71 | key: value 72 | schema: # Validation schema for options 73 | key: type 74 | ``` 75 | 76 | #### Reading Configuration in Scripts 77 | 78 | ```bash 79 | #!/usr/bin/with-contenv bashio 80 | 81 | # Read option value 82 | VALUE=$(bashio::config 'option_name') 83 | 84 | # Check if option exists 85 | if bashio::config.exists 'option_name'; then 86 | # Option is set 87 | fi 88 | 89 | # Get with default value 90 | VALUE=$(bashio::config 'option_name' 'default_value') 91 | ``` 92 | 93 | #### Logging 94 | 95 | ```bash 96 | bashio::log.info "Informational message" 97 | bashio::log.warning "Warning message" 98 | bashio::log.error "Error message" 99 | bashio::log.debug "Debug message (only shown in debug mode)" 100 | ``` 101 | 102 | #### Service Scripts 103 | 104 | - **run**: Main service execution script (should not exit unless service stops) 105 | - **finish**: Cleanup script (runs when service stops) 106 | - **type**: Service type (usually `longrun` for daemons) 107 | 108 | ## Development Workflow 109 | 110 | ### Planning 111 | 112 | 1. **Understand the requirement**: Read the issue/request carefully 113 | 2. **Review existing code**: Check how similar features are implemented 114 | 3. **Plan your approach**: Think through the changes before coding 115 | 4. **Stick to the plan**: Don't introduce unrelated changes 116 | 117 | ### Implementation 118 | 119 | 1. **Make focused changes**: One feature or fix per commit/PR 120 | 2. **Preserve working functionality**: Don't break existing features 121 | 3. **Follow the existing architecture**: Use established patterns 122 | 4. **Add appropriate logging**: Help users debug issues 123 | 5. **Update documentation**: Modify README.md if user-facing changes are made 124 | 125 | ### Testing Considerations 126 | 127 | - Test initialization scripts handle both fresh installs and upgrades 128 | - Verify configuration options are properly validated 129 | - Check that services start and stop cleanly 130 | - Ensure PostgreSQL extensions load correctly 131 | - Test across different architectures if possible (amd64, aarch64, armv7) 132 | 133 | ## PostgreSQL & TimescaleDB Specifics 134 | 135 | ### Extension Management 136 | 137 | - Extensions are installed in initialization scripts 138 | - Use `CREATE EXTENSION IF NOT EXISTS` to avoid errors 139 | - Check compatibility with PostgreSQL version 140 | - Document any version-specific requirements 141 | 142 | ### Configuration Tuning 143 | 144 | - `timescaledb-tune` is used for automatic configuration 145 | - Custom settings can be added via `postgresql.conf.auto` or similar 146 | - Memory settings should respect container limits 147 | - Consider Home Assistant's resource constraints 148 | 149 | ## Common Pitfalls to Avoid 150 | 151 | ❌ **Don't:** 152 | 153 | - Hardcode paths that should be configurable 154 | - Ignore errors or return codes 155 | - Make breaking changes to configuration schema without migration 156 | - Assume specific PostgreSQL versions without checking 157 | - Leave debug code or commented-out blocks 158 | - Use `echo` for logging (use `bashio::log.*` instead) 159 | - Break backward compatibility without documenting 160 | 161 | ✅ **Do:** 162 | 163 | - Validate user input from configuration 164 | - Provide sensible defaults 165 | - Handle edge cases (missing files, permissions, etc.) 166 | - Use appropriate exit codes 167 | - Keep services idempotent where possible 168 | - Document environment variables and their purposes 169 | 170 | ## Code Review Checklist 171 | 172 | Before completing your work, verify: 173 | 174 | - [ ] Code follows existing style and patterns 175 | - [ ] All shell scripts have proper shebangs 176 | - [ ] Variables are properly quoted 177 | - [ ] Error handling is in place 178 | - [ ] Logging uses bashio functions 179 | - [ ] Configuration changes are reflected in `config.yaml` schema 180 | - [ ] Services have proper dependencies defined 181 | - [ ] Documentation is updated if needed 182 | - [ ] No hardcoded values that should be configurable 183 | - [ ] Changes are tested or testable 184 | 185 | ## Getting Help 186 | 187 | When stuck: 188 | 189 | 1. Review existing code for similar patterns 190 | 2. Check Home Assistant addon documentation 191 | 3. Examine bashio library capabilities 192 | 4. Look at PostgreSQL/TimescaleDB documentation 193 | 5. Ask specific questions about the architecture or requirements 194 | 195 | ## Summary 196 | 197 | **Core Principles:** 198 | 199 | - 📝 Write clean, documented code 200 | - 🎯 Stick to the plan and scope 201 | - 🏗️ Follow existing patterns 202 | - 🔍 Test thoroughly 203 | - 📚 Document changes 204 | - 🤝 Respect Home Assistant addon conventions 205 | 206 | Your work contributes to a critical infrastructure component for Home Assistant users. Quality and reliability are paramount. 207 | -------------------------------------------------------------------------------- /project.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e; 3 | 4 | PLATFORM="linux/arm64" 5 | 6 | function printInColor() { 7 | # Set the color code based on the color name 8 | color=0 9 | case $2 in 10 | "red") color=31;; 11 | "green") color=32;; 12 | "yellow") color=33;; 13 | "blue") color=34;; 14 | "purple") color=35;; 15 | "cyan") color=36;; 16 | "white") color=37;; 17 | esac 18 | 19 | # Set the background color code based on the color name 20 | background=0 21 | case $3 in 22 | "red") background=41;; 23 | "green") background=42;; 24 | "yellow") background=43;; 25 | "blue") background=44;; 26 | "purple") background=45;; 27 | "cyan") background=46;; 28 | "white") background=47;; 29 | esac 30 | 31 | # Print the message in the given color, then reset the color 32 | echo -e "\e[${background}m\e[${color}m$1\e[0m" 33 | } 34 | 35 | function build_dependency() { 36 | local component=$1 37 | local version=$2 38 | 39 | printInColor "Building docker dependency ${component}" "green" 40 | 41 | docker buildx build \ 42 | --push \ 43 | --platform "linux/amd64,linux/arm64,linux/arm/v7,linux/i386,linux/arm/v6" \ 44 | --cache-from "type=registry,ref=ghcr.io/expaso/timescaledb/dependency/${component}:cache" \ 45 | --cache-to "type=registry,ref=ghcr.io/expaso/timescaledb/dependency/${component}:cache,mode=max" \ 46 | --tag "ghcr.io/expaso/timescaledb/dependency/${component}:${version}" \ 47 | --progress plain \ 48 | --build-arg "VERSION=${version}" \ 49 | --file "./timescaledb/docker-dependencies/${component}" \ 50 | . \ 51 | && printInColor "Done building docker image!" "green" 52 | } 53 | 54 | function build() { 55 | local output=$1 56 | 57 | printInColor "Building docker image.." 58 | 59 | # Build the image conform the instructions 60 | # Push the dev image to docker hub 61 | # build the image 62 | docker buildx build \ 63 | --platform ${PLATFORM} \ 64 | --cache-from type=registry,ref=ghcr.io/expaso/timescaledb:cache \ 65 | --tag ghcr.io/expaso/timescaledb/aarch64:dev \ 66 | --progress plain \ 67 | --build-arg CACHE_BUST="$(date +%s)" \ 68 | --output "${output}" \ 69 | ./timescaledb \ 70 | && printInColor "Done building docker image!" "green" 71 | 72 | #Stop when an error occured 73 | # shellcheck disable=SC2181 74 | if [ $? -ne 0 ]; then 75 | printInColor "Error building docker image!" "red" 76 | exit 1 77 | fi 78 | } 79 | 80 | function run_hassos() { 81 | # Run the docker image on hassos 82 | printInColor "Pulling and restarting on HASSOS.. " 83 | 84 | # # Copy the docker image to hassos 85 | # printInColor "Pulling docker image on hassos.." "yellow" 86 | # # run the docker image pull command remote on Hassos 87 | ssh -i ~/.ssh/hassos -l root -p 22222 homeassistant "docker image pull ghcr.io/expaso/timescaledb/aarch64:dev \ 88 | && ha addons stop local_timescaledb \ 89 | && ha addons start local_timescaledb" 90 | printInColor "Done pulling docker image on hassos!" "green" 91 | } 92 | 93 | function run_local() { 94 | printInColor "Starting standalone docker image " 95 | 96 | # Run the docker image locally 97 | mkdir -p /tmp/timescale_data 98 | docker run --rm --name timescaledb --platform ${PLATFORM} -v /tmp/timescale_data:/data -p 5432:5432 ghcr.io/expaso/timescaledb/aarch64:dev 99 | } 100 | 101 | function release() { 102 | local tag=$1 103 | printInColor "Releasing docker images: retagging from [latest] with tag ${tag}.." 104 | 105 | #Get all platforms from /timescaledb/config.yaml 106 | platforms=$(yq -r '.arch[]' ./timescaledb/config.yaml) 107 | 108 | #And loop through them 109 | for platform in $platforms; do 110 | printInColor "Releasing platform ${platform} with tag ${tag}.." 111 | 112 | docker tag "ghcr.io/expaso/timescaledb/${platform}:latest" "ghcr.io/expaso/timescaledb/${platform}:${tag}" 113 | docker push "ghcr.io/expaso/timescaledb/${platform}:${tag}" 114 | done 115 | } 116 | 117 | function inspect() { 118 | local tag=$1 119 | printInColor "Starting standalone docker image shell" 120 | 121 | # Run the docker image locally 122 | mkdir -p /tmp/timescale_data 123 | docker run --entrypoint "/bin/ash" -it --rm --name timescaledb --platform ${PLATFORM} -v /tmp/timescale_data:/data -p 5432:5432 ghcr.io/expaso/timescaledb/aarch64:dev 124 | } 125 | 126 | function build_all() { 127 | local tag=$1 128 | printInColor "Building all platforms for Home Assistant with tag ${tag}" 129 | 130 | # Get all platforms from /timescaledb/config.yaml 131 | platforms=$(yq -r '.arch[]' ./timescaledb/config.yaml) 132 | 133 | # And loop through them 134 | for platform in $platforms; do 135 | 136 | # Get the value from timescaledb/build.yaml by looking it up in the build_from dictionary, whereby the key value of the list is the platform. 137 | build_from=$(yq -r ".build_from.${platform}" ./timescaledb/build.yaml) 138 | 139 | # Convert the platform to the correct format 140 | case $platform in 141 | "aarch64") docker_platform="linux/arm64";; 142 | "amd64") docker_platform="linux/amd64";; 143 | "armv7") docker_platform="linux/arm/v7";; 144 | "i386") docker_platform="linux/i386";; 145 | "armhf") docker_platform="linux/arm/v6";; 146 | esac 147 | 148 | printInColor "Building platform ${platform} (${docker_platform}) for Home Assistant with tag ${tag}" "green" 149 | 150 | docker buildx build \ 151 | --platform "${docker_platform}" \ 152 | --cache-from type=registry,ref=ghcr.io/expaso/timescaledb:cache \ 153 | --cache-to type=registry,ref=ghcr.io/expaso/timescaledb:cache,mode=max \ 154 | --tag "ghcr.io/expaso/timescaledb/${platform}:${tag}" \ 155 | --build-arg "BUILD_FROM=${build_from}" \ 156 | --build-arg "BUILD_ARCH=${platform}" \ 157 | --build-arg "VERSION=${tag}" \ 158 | --file ./timescaledb/Dockerfile \ 159 | --output type=registry,push=true \ 160 | ./timescaledb \ 161 | && printInColor "Done building docker image!" "green" 162 | done 163 | } 164 | 165 | # Builds a dev tagged image locally 166 | if [ "$1" == "build" ]; then 167 | build "type=docker" 168 | exit 0 169 | 170 | # Builds a dev tagged image, and pushes it to the registry 171 | elif [ "$1" == "build-push" ]; then 172 | build "type=registry,push=true" 173 | exit 0 174 | 175 | # Builds all dependencies or a specific one, an pushes it to the registry 176 | elif [ "$1" == "build-dependencies" ]; then 177 | 178 | # If the second argument is not set, then build all dependencies 179 | # Otherwise, only build the given dependency 180 | if [ -z "$2" ]; then 181 | printInColor "Building all dependencies.." 182 | 183 | build_dependency timescaledb-tools "latest" 184 | build_dependency pgagent-pg16 "pgagent-4.2.3" 185 | build_dependency pgagent-pg17 "pgagent-4.2.3" 186 | build_dependency timescaledb-toolkit-pg16 "1.21.0" 187 | build_dependency timescaledb-toolkit-pg17 "1.21.0" 188 | build_dependency postgis-pg16 "3.6.0" 189 | build_dependency postgis-pg17 "3.6.0" 190 | build_dependency postgresql-extension-system-stat-pg16 "3.2" 191 | build_dependency postgresql-extension-system-stat-pg17 "3.2" 192 | build_dependency pgvector-pg17 "0.8.1" 193 | else 194 | printInColor "Building dependency $2.." 195 | build_dependency "$2" "$3" 196 | fi 197 | exit 0 198 | 199 | # Build all architectures for Home Assistant with the latest tag and pushes it to the registry 200 | elif [ "$1" == "build-all" ]; then 201 | build_all latest 202 | exit 0 203 | 204 | # Builds a dev tagged image, pushes it tourgh the registy and restarts the addon on Hassos 205 | elif [ "$1" == "run-hassos" ]; then 206 | build "type=registry,push=true" 207 | run_hassos 208 | exit 0 209 | 210 | # Builds and runs a dev tagged image locally 211 | elif [ "$1" == "debug" ]; then 212 | build type=docker 213 | run_local 214 | exit 0 215 | 216 | # Runs a shell in the dev tagged image locally 217 | elif [ "$1" == "inspect" ]; then 218 | # build type=docker 219 | inspect "$2" 220 | exit 0 221 | 222 | # Retags the output images of build_all (latest) to the given tag and pushes them to the registry 223 | elif [ "$1" == "release" ]; then 224 | release "$2" 225 | exit 0 226 | 227 | else 228 | printInColor "Unknown command!" "red" 229 | exit 1 230 | 231 | fi 232 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /timescaledb/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BUILD_FROM=ghcr.io/hassio-addons/base/aarch64:17.2.5 2 | ARG BUILD_ARCH=aarch64 3 | 4 | ARG ALPINE_VERSION=3.21 5 | 6 | # Define the versions for all components of the addon 7 | # These images are all prebuild, otherwise building the docker images takes waaaay to long 8 | # Where possible, we just take the latest version from the Alpine repository, so it's 9 | # possible we won't mention the latest version here. 10 | FROM timescale/timescaledb:2.22.1-pg16 AS timescale-pg16 11 | FROM timescale/timescaledb:2.22.1-pg17 AS timescale-pg17 12 | 13 | # hadolint ignore=DL3007 14 | FROM ghcr.io/expaso/timescaledb/dependency/timescaledb-tools:latest AS timescaledb-tools 15 | FROM ghcr.io/expaso/timescaledb/dependency/pgagent-pg16:pgagent-4.2.3 AS pgagent-pg16 16 | FROM ghcr.io/expaso/timescaledb/dependency/pgagent-pg17:pgagent-4.2.3 AS pgagent-pg17 17 | FROM ghcr.io/expaso/timescaledb/dependency/postgis-pg16:3.6.0 AS postgis-pg16 18 | FROM ghcr.io/expaso/timescaledb/dependency/postgis-pg17:3.6.0 AS postgis-pg17 19 | FROM ghcr.io/expaso/timescaledb/dependency/timescaledb-toolkit-pg16:1.21.0 AS timescaledb-toolkit-pg16 20 | FROM ghcr.io/expaso/timescaledb/dependency/timescaledb-toolkit-pg17:1.21.0 AS timescaledb-toolkit-pg17 21 | FROM ghcr.io/expaso/timescaledb/dependency/postgresql-extension-system-stat-pg16:3.2 AS system-stat-pg16 22 | FROM ghcr.io/expaso/timescaledb/dependency/postgresql-extension-system-stat-pg17:3.2 AS system-stat-pg17 23 | FROM ghcr.io/expaso/timescaledb/dependency/pgvector-pg17:0.8.1 AS pgvector-pg17 24 | 25 | 26 | ########################################### 27 | # Define base Image 28 | ########################################### 29 | # hadolint ignore=DL3006 30 | FROM $BUILD_FROM AS addon 31 | ARG BUILD_ARCH 32 | ARG TARGETARCH=${BUILD_ARCH} 33 | ARG TARGETVARIANT 34 | 35 | # Add some handy tools 36 | # hadolint ignore=DL3018 37 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 38 | set -ex \ 39 | && apk update \ 40 | && apk add ca-certificates \ 41 | && apk add \ 42 | wget \ 43 | rsync \ 44 | nano 45 | 46 | # Upgrade SSL libraries 47 | # hadolint ignore=DL3018 48 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 49 | set -ex \ 50 | && apk add --upgrade libssl3 libcrypto3 openssl-dev 51 | 52 | ########################################### 53 | # Install all sub-components 54 | ########################################### 55 | 56 | # ----------------------------------------- 57 | # PostgreSql 16 (old) 58 | # ----------------------------------------- 59 | # hadolint ignore=DL3018 60 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 61 | set -ex \ 62 | && apk add \ 63 | postgresql16-jit \ 64 | postgresql16 \ 65 | postgresql16-dev \ 66 | postgresql16-contrib-jit \ 67 | postgresql16-contrib 68 | 69 | # ----------------------------------------- 70 | # PostgreSql 17 (new) 71 | # ----------------------------------------- 72 | # hadolint ignore=DL3018 73 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 74 | set -ex \ 75 | && apk add \ 76 | postgresql17-jit \ 77 | postgresql17 \ 78 | postgresql17-dev \ 79 | postgresql17-contrib-jit \ 80 | postgresql17-contrib 81 | 82 | # Set default postgresql version to 17 83 | RUN rm /usr/libexec/postgresql \ 84 | && ln -s /usr/libexec/postgresql17 /usr/libexec/postgresql \ 85 | && rm /usr/share/postgresql \ 86 | && ln -s /usr/share/postgresql17 /usr/share/postgresql 87 | 88 | # Create it's run directory 89 | RUN mkdir -p /run/postgresql \ 90 | && chown -R postgres:postgres /run/postgresql \ 91 | && mkdir -p /run/postgresql/extensions \ 92 | && chown -R postgres:postgres /run/postgresql/extensions 93 | 94 | # ----------------------------------------- 95 | # Extension Postgis pg-16 (old) 96 | # ----------------------------------------- 97 | 98 | # Postgis + Postgis-Raster 99 | COPY --from=postgis-pg16 --link /usr/local/lib/postgresql/postgis* /usr/lib/postgresql16/ 100 | 101 | COPY --from=postgis-pg16 --link /usr/local/lib/postgresql/bitcode/postgis-3/* /usr/lib/postgresql16/bitcode/postgis-3/ 102 | COPY --from=postgis-pg16 --link /usr/local/lib/postgresql/bitcode/postgis_raster-3/* /usr/lib/postgresql16/bitcode/postgis_raster-3/ 103 | 104 | COPY --from=postgis-pg16 --link /usr/local/share/postgresql/contrib/postgis* /usr/share/postgresql16/contrib/ 105 | COPY --from=postgis-pg16 --link /usr/local/share/postgresql/extension/postgis* /usr/share/postgresql16/extension/ 106 | 107 | # ----------------------------------------- 108 | # Extension Postgis pg-17 (new) 109 | # ----------------------------------------- 110 | #Add required packages for runtime (see postgis dependencies) 111 | # hadolint ignore=DL3018 112 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 113 | set -ex \ 114 | && apk add \ 115 | openexr \ 116 | poppler \ 117 | gdal \ 118 | geos \ 119 | json-c \ 120 | libgcc \ 121 | libpq \ 122 | libstdc++ \ 123 | libxml2 \ 124 | musl \ 125 | pcre \ 126 | perl \ 127 | proj \ 128 | protobuf-c 129 | 130 | # hadolint ignore=DL3018 131 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 132 | set -ex \ 133 | && apk add --repository https://dl-cdn.alpinelinux.org/alpine/v3.21/community --allow-untrusted \ 134 | #postgis=3.5.0-r5 \ 135 | gdal-driver-postgisraster \ 136 | && \ 137 | apk add --repository https://dl-cdn.alpinelinux.org/alpine/v3.21/main \ 138 | libturbojpeg 139 | 140 | # Postgis + Postgis-Raster 141 | COPY --from=postgis-pg17 --link /usr/local/lib/postgresql/postgis* /usr/lib/postgresql17/ 142 | 143 | COPY --from=postgis-pg17 --link /usr/local/lib/postgresql/bitcode/postgis-3/* /usr/lib/postgresql17/bitcode/postgis-3/ 144 | COPY --from=postgis-pg17 --link /usr/local/lib/postgresql/bitcode/postgis_raster-3/* /usr/lib/postgresql17/bitcode/postgis_raster-3/ 145 | 146 | COPY --from=postgis-pg17 --link /usr/local/share/postgresql/contrib/postgis* /usr/share/postgresql17/contrib/ 147 | COPY --from=postgis-pg17 --link /usr/local/share/postgresql/extension/postgis* /usr/share/postgresql17/extension/ 148 | 149 | # ----------------------------------------- 150 | # Extension Timescale pg-16 (old) 151 | # ----------------------------------------- 152 | # Copy the new timescale version to the old postgresql version 153 | COPY --from=timescale-pg16 --link /usr/local/lib/postgresql/timescaledb*.so /usr/lib/postgresql16/ 154 | COPY --from=timescale-pg16 --link /usr/local/share/postgresql/extension/timescaledb* /usr/share/postgresql16/extension/ 155 | 156 | # ----------------------------------------- 157 | # Extension Timescale pg-17 (new) 158 | # ----------------------------------------- 159 | # Copy the new timescale version to the new postgresql version 160 | COPY --from=timescale-pg17 --link /usr/local/lib/postgresql/timescaledb*.so /usr/lib/postgresql17/ 161 | COPY --from=timescale-pg17 --link /usr/local/share/postgresql/extension/timescaledb* /usr/share/postgresql17/extension/ 162 | 163 | # -------------------------------------------- 164 | # Extension pgAgent pg-17 (new) 165 | # -------------------------------------------- 166 | # Copy the new timescale version to the new postgresql version 167 | COPY --from=pgagent-pg17 --link /usr/local/lib/postgresql/pgagent*.so /usr/lib/postgresql17/ 168 | COPY --from=pgagent-pg17 --link /usr/local/share/postgresql/extension/pgagent* /usr/share/postgresql17/extension/ 169 | 170 | COPY --from=pgagent-pg17 --link /usr/local/share/pgagent.sql /usr/local/share/ 171 | COPY --from=pgagent-pg17 --link /usr/local/bin/pgagent /usr/local/bin/ 172 | 173 | # Add compatibility libraries 174 | # hadolint ignore=DL3018 175 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 176 | set -ex \ 177 | && apk add \ 178 | boost-libs 179 | 180 | # -------------------------------------- 181 | # Extension Timescaledb-Toolkit pg16 (old) 182 | # -------------------------------------- 183 | # Copy new timescaledb-toolkit to new postgresql version 184 | COPY --from=timescaledb-toolkit-pg16 --link /usr/local/lib/postgresql/timescaledb_toolkit* /usr/lib/postgresql16/ 185 | COPY --from=timescaledb-toolkit-pg16 --link /usr/local/share/postgresql/extension/timescaledb_toolkit* /usr/share/postgresql16/extension/ 186 | 187 | # -------------------------------------- 188 | # Extension Timescaledb-Toolkit pg17 (new) 189 | # -------------------------------------- 190 | # Copy new timescaledb-toolkit to new postgresql version 191 | COPY --from=timescaledb-toolkit-pg17 --link /usr/local/lib/postgresql/timescaledb_toolkit* /usr/lib/postgresql17/ 192 | COPY --from=timescaledb-toolkit-pg17 --link /usr/local/share/postgresql/extension/timescaledb_toolkit* /usr/share/postgresql17/extension/ 193 | 194 | # Add compatibility libraries for timescaledb toolkit 195 | # hadolint ignore=DL3018 196 | RUN --mount=type=cache,id=apk-${TARGETARCH}-${TARGETVARIANT},sharing=locked,target=/var/cache/apk \ 197 | set -ex \ 198 | && apk add \ 199 | gcompat \ 200 | clang16-libclang 201 | 202 | # -------------------------------------------- 203 | # Extension System-Stat pg-16 (old) 204 | # -------------------------------------------- 205 | COPY --from=system-stat-pg16 --link /usr/local/lib/postgresql/system_stats*.so /usr/lib/postgresql16/ 206 | COPY --from=system-stat-pg16 --link /usr/local/share/postgresql/extension/system_stats* /usr/share/postgresql16/extension/ 207 | 208 | # -------------------------------------------- 209 | # Extension System-Stat pg-17 (new) 210 | # -------------------------------------------- 211 | COPY --from=system-stat-pg17 --link /usr/local/lib/postgresql/system_stats*.so /usr/lib/postgresql17/ 212 | COPY --from=system-stat-pg17 --link /usr/local/share/postgresql/extension/system_stats* /usr/share/postgresql17/extension/ 213 | 214 | # -------------------------------------------- 215 | # Extension pgVector pg-17 (new) 216 | # -------------------------------------------- 217 | COPY --from=pgvector-pg17 --link /usr/local/lib/postgresql/vector*.so /usr/lib/postgresql17/ 218 | COPY --from=pgvector-pg17 --link /usr/local/share/postgresql/extension/vector* /usr/share/postgresql17/extension/ 219 | 220 | # -------------------------------------- 221 | # Timescaledb Tools (alwasys latest) 222 | # -------------------------------------- 223 | # Copy timescaledb-tools 224 | COPY --from=timescaledb-tools /go/bin/* /usr/local/bin/ 225 | 226 | ########################################### 227 | # Finalize Image 228 | ########################################### 229 | 230 | # Make sure that S6 is not so hard on our service startup/shutdown 231 | ENV \ 232 | S6_SERVICES_GRACETIME=50000 \ 233 | S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 234 | 235 | ARG CACHE_BUST 236 | 237 | # Timestamp the build 238 | RUN date +%Y%m%d%H%M%S > /build-timestamp 239 | 240 | # Copy root fs files 241 | # hadolint ignore=DL3021 242 | COPY --link rootfs /rootfs 243 | RUN rsync -LKavz /rootfs/ / && rm -rf /rootfs 244 | 245 | # Make backup and restore scripts executable 246 | RUN chmod +x /usr/share/timescaledb/backup_pre.sh \ 247 | && chmod +x /usr/share/timescaledb/backup_post.sh \ 248 | && chmod +x /usr/share/timescaledb/restore_from_backup.sh 249 | 250 | WORKDIR / 251 | 252 | # Build arugments 253 | ARG BUILD_DATE 254 | ARG BUILD_REF 255 | ARG BUILD_VERSION 256 | ARG BUILD_REPOSITORY 257 | 258 | # Labels 259 | LABEL \ 260 | io.hass.name="Timescaledb HA Addon (${BUILD_ARCH})" \ 261 | io.hass.description="Home Assistant Add-on: TimescaleDb" \ 262 | io.hass.arch="${BUILD_ARCH}" \ 263 | io.hass.type="base" \ 264 | io.hass.version=${BUILD_VERSION} \ 265 | io.hass.base.version=${BUILD_VERSION} \ 266 | io.hass.base.name="alpine" \ 267 | io.hass.base.image="hassioaddons/base" \ 268 | maintainer="Hans van Essen " \ 269 | org.opencontainers.image.title="Timescaledb HA Addon (${BUILD_ARCH})" \ 270 | org.opencontainers.image.description="Home Assistant Add-on: TimescaleDb" \ 271 | org.opencontainers.image.vendor="Expaso BV" \ 272 | org.opencontainers.image.authors="Hans van Essen " \ 273 | org.opencontainers.image.licenses="Apache 2.0" \ 274 | org.opencontainers.image.url="https://ghcr.io/expaso/timescaledb/${BUILD_ARCH}:" \ 275 | org.opencontainers.image.source="https://github.com/expaso/hassos-addon-timescaledb" \ 276 | org.opencontainers.image.documentation="https://github.com/expaso/hassos-addon-timescaledb/blob/master/README.md" \ 277 | org.opencontainers.image.created=${BUILD_DATE} \ 278 | org.opencontainers.image.revision=${BUILD_REF} \ 279 | org.opencontainers.image.version=${BUILD_VERSION} -------------------------------------------------------------------------------- /timescaledb/.README.j2: -------------------------------------------------------------------------------- 1 | ![Project Stage][project-stage-shield] 2 | ![Maintenance][maintenance-shield] 3 | [![License][license-shield]]({{ repo }}/blob/main/LICENSE) 4 | 5 | Buy Me A Coffee 6 | 7 | # Home Assistant Add-on: [PostgreSQL](https://www.postgresql.org/) [TimescaleDB](https://www.timescale.com/) 8 | 9 | ## [PostgreSql 17.6](https://www.postgresql.org/) & [Postgis 3.6.0](https://postgis.net/) & [TimescaleDB 2.22.1](https://www.timescale.com/) & [TimescaleDB Toolkit 1.21.0](https://github.com/timescale/timescaledb-toolkit) & [pgAgent 4.2.3](https://www.pgadmin.org/docs/pgadmin4/development/pgagent.html) 10 | 11 | #### Configuation 12 | 13 | Example add-on configuration: 14 | 15 | ``` 16 | { 17 | "databases": ["homeassistant"], 18 | "timescale_enabled": ["homeassistant"], 19 | "timescaledb": { 20 | "telemetry": "basic", 21 | "maxmemory": "512MB", 22 | "maxcpus": "4" 23 | }, 24 | "max_connections": 20, 25 | "system_packages": [], 26 | "init_commands": [] 27 | } 28 | ``` 29 | 30 | #### Option: `databases` 31 | 32 | Sets a list of database-names that will be created for you, once you start the add-on. 33 | You can also create databases on your own ofcourse, using a psql client of your choice. 34 | 35 | #### Option: `timescale_enabled` 36 | 37 | Sets a list of database-names where the timescale-extentions will be enabled for. 38 | Databases not in this list will act like normal Postgre databases. 39 | 40 | #### Option: `timescaledb.telemetry` 41 | 42 | Switches the telemetry of TimescaleDb on or off. 43 | Valid options are: 'basic' or 'off'. 44 | See: https://docs.timescale.com/latest/using-timescaledb/telemetry 45 | 46 | #### Option: `timescaledb.maxmemory` 47 | 48 | Sets the maximum amount of memory that PostgreSQL will claim. 49 | It's important to leave breathing room for other processes on your machine (or raspberry pi), so set these level not too high (say max 50% of your total ram). 50 | 51 | Example: `maxmemory="1024MB"` 52 | Or leave empty for accepting auto-tune. 53 | 54 | #### Option: `timescaledb.maxcpu` 55 | 56 | Sets the maximum number of cores that PostgreSQL will use. 57 | It's important to leave breathing room for other processes on your machine (or raspberry pi), so set these level not too high (say max 75% of your total number of cores). 58 | 59 | Example: `maxcpu="2"` 60 | Or leave empty for accepting auto-tune. 61 | 62 | See also: 63 | https://docs.timescale.com/latest/getting-started/configuring 64 | for further tuning. Your Postgres.config file is located in the addon's data directory. 65 | 66 | #### Option: `max_connections` 67 | 68 | Sets the maximum number of connections that PostgreSql will accept. 69 | Setting this higher could lead to more memory usage. 70 | 71 | Example: `max_connections=30` 72 | 73 | #### Option: `system_packages` 74 | 75 | Advanced users only! 76 | A list of extra alpine packages to iunstall during addon-startup. 77 | 78 | Example: ['nano'] 79 | 80 | #### Option: `init_commands` 81 | 82 | Advanced users only! 83 | A list of extra commands to run during startup. 84 | 85 | To alter something in the postgresql.conf file for example: 86 | 87 | Example: ['sed -i -e "/max_connections =/ s/= .*/= 50/" /data/postgres/postgresql.conf'] 88 | 89 | #### Option: `retry_upgrade` 90 | 91 | Advanced users only! 92 | When set, the upgrade from Postgres 14 to 15 could be retryed if it failed mid-flight. 93 | Basically this will try to find the old database-files from Postgres 12, and restore them before trying to upgrade to Postgres 14 again. 94 | 95 | !! Please don't set this if you don't know what you are doing or before taking a backup. !! 96 | 97 | #### Option: `postgresql_config` 98 | 99 | Allows you to customize PostgreSQL server parameters. These settings are applied to `postgresql.conf` and can override default settings. 100 | 101 | Example: 102 | ```yaml 103 | postgresql_config: 104 | log_statement: "all" 105 | work_mem: "16MB" 106 | maintenance_work_mem: "256MB" 107 | ``` 108 | 109 | See [PostgreSQL documentation](https://www.postgresql.org/docs/current/runtime-config.html) for available parameters. 110 | 111 | **Note:** Some critical parameters cannot be modified (e.g., `shared_preload_libraries`, `port`, `data_directory`) as they are managed by the addon. 112 | 113 | #### Option: `pg_hba_config` 114 | 115 | Allows you to add custom authentication rules to `pg_hba.conf`. Rules are appended to default rules. 116 | 117 | Example: 118 | ```yaml 119 | pg_hba_config: 120 | - type: "host" 121 | database: "homeassistant" 122 | user: "all" 123 | address: "192.168.1.0/24" 124 | method: "md5" 125 | ``` 126 | 127 | See [PostgreSQL documentation](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html) for authentication methods. 128 | 129 | **Warning:** Incorrect configuration can lock you out of the database. 130 | 131 | ### Running the container standalone. 132 | 133 | In this case, you need to have a working Docker installation on your machine. 134 | pull one of the images for the desired architecture from docker hub: 135 | 136 | ``` 137 | docker pull ghcr.io/expaso/timescaledb/amd64:stable 138 | docker pull ghcr.io/expaso/timescaledb/aarch64:stable 139 | docker pull ghcr.io/expaso/timescaledb/armv7:stable 140 | docker pull ghcr.io/expaso/timescaledb/armhf:stable 141 | docker pull ghcr.io/expaso/timescaledb/i386:stable 142 | ``` 143 | 144 | You can replace *stable* with the version number you want to use. 145 | 146 | Simply start it like this: 147 | 148 | ``` 149 | docker run \ 150 | --rm \ 151 | --name timescaledb \ 152 | --v ${PWD}/timescaledb_addon_data:/data \ 153 | -p 5432:5432 \ 154 | ghcr.io/expaso/timescaledb/amd64:stable 155 | ``` 156 | 157 | This will use ~/timescaledb_addon_data as the data directory for the container, and map the port 5432 to the host. 158 | 159 | If you want to start the container as a daemon, simply remove the `--rm` option and add the `-d` option like so: 160 | 161 | ``` 162 | docker run \ 163 | -d \ 164 | --name timescaledb \ 165 | --v ${PWD}/timescaledb_addon_data:/data \ 166 | -p 5432:5432 \ 167 | ghcr.io/expaso/timescaledb/amd64:stable 168 | ``` 169 | 170 | ## Usage 171 | 172 | You are now ready to start using Postgres with TimescaleDb extenstions enabled! 173 | 174 | Seeking a nice web-based client? **Try the pgAdmin4 addon.** 175 | 176 | Please do not forget to also map the TCP/IP port in the network-section of the addon to the desired port number. 177 | The default is port `5432` 178 | 179 | **Securiy Notice!** 180 | 181 | The default username is `postgres` with password `homeassistant`. 182 | Make sure you change this immediately after activating the add-on: 183 | 184 | ``` 185 | ALTER USER postgres WITH PASSWORD 'strongpassword'; 186 | ``` 187 | 188 | ⚠️ It's considered best practice to create a separate user for each database you create, and transfer ownership of the database to that user. 189 | In this configuration, The `postgres` user should only be used for administrative tasks. 190 | 191 | Use the following commands to create a user `homeassistant` with password `mypassword` and transfer ownership of the database `mydatabase` to that user, or use _pgAdmin_ for this task if you prefer a GUI. 192 | 193 | ``` 194 | CREATE USER homeassistant WITH PASSWORD 'mypassword'; 195 | ALTER DATABASE mydatabase OWNER TO homeassistant; 196 | ``` 197 | 198 | 199 | A default `pg_hba.conf` is created in the data directory with the following content, which allows local peer users and network users with passwords.: 200 | 201 | ``` 202 | # TYPE DATABASE USER ADDRESS METHOD 203 | host all all 0.0.0.0/0 md5" 204 | local all all 0.0.0.0/0 md5" 205 | local all all 0.0.0.0/0 peer" 206 | ``` 207 | 208 | Please review this configuration carefully by examine the docs: 209 | https://www.postgresql.org/docs/devel/auth-pg-hba-conf.html 210 | 211 | ## Backup and Restore 212 | 213 | This addon implements a robust backup and restore mechanism designed to protect your database from data loss. 214 | 215 | ### How Backups Work 216 | 217 | When Home Assistant's backup system runs, it automatically: 218 | 219 | 1. **Pre-Backup**: Executes `pg_dumpall` to create a complete SQL dump of all databases (`backup_db.sql`) 220 | 2. **Backup**: Backs up the SQL dump file along with other addon data (but excludes the PostgreSQL data directory) 221 | 3. **Post-Backup**: Removes the temporary SQL dump file to save space 222 | 223 | This approach has several advantages: 224 | 225 | - **Consistency**: SQL dumps are transaction-consistent snapshots 226 | - **Safety**: No risk of backing up corrupted or in-transition database files 227 | - **Portability**: SQL dumps can be restored across different PostgreSQL versions 228 | - **Small Size**: Excludes the large PostgreSQL data directory from backup 229 | 230 | ### How Restore Works 231 | 232 | When you restore a Home Assistant backup: 233 | 234 | 1. The addon starts up with the restored `backup_db.sql` file 235 | 2. If the PostgreSQL data directory is missing or corrupted, the addon: 236 | - Initializes a fresh PostgreSQL database 237 | - Automatically restores all data from the SQL dump 238 | - Removes the SQL dump after successful restoration 239 | 240 | This automatic recovery process ensures your data is safely restored even if: 241 | 242 | - The database files were corrupted 243 | - You're restoring to a different system 244 | - A PostgreSQL upgrade failed 245 | 246 | ### Manual Backup 247 | 248 | You can also create manual backups using the PostgreSQL command-line tools: 249 | 250 | **Create a backup:** 251 | 252 | ```bash 253 | docker exec addon_timescaledb_timescaledb su - postgres -c "pg_dumpall -U postgres --clean --if-exists -f /data/manual_backup_$(date +%Y%m%d).sql" 254 | ``` 255 | 256 | **Restore from a manual backup:** 257 | 258 | ```bash 259 | docker exec addon_timescaledb_timescaledb su - postgres -c "psql -U postgres -f /data/manual_backup_YYYYMMDD.sql -d postgres" 260 | ``` 261 | 262 | ### Important Notes 263 | 264 | - The SQL dump is only present during the backup process and is automatically cleaned up 265 | - If you need to keep a copy of the backup SQL file, copy it before the backup completes 266 | - The PostgreSQL data directory (`/data/postgres/*`) is excluded from backups to reduce backup size and improve reliability 267 | - Restore is automatic - no manual intervention required when restoring from a Home Assistant backup 268 | 269 | ### Troubleshooting 270 | 271 | **If restore fails:** 272 | 273 | 1. Check the addon logs for detailed error messages 274 | 2. The backup SQL file will be preserved at `/data/backup_db.sql` for manual recovery 275 | 3. You can attempt manual restoration using: 276 | 277 | ```bash 278 | docker exec -it addon_timescaledb_timescaledb su - postgres -c "psql -U postgres -f /data/backup_db.sql -d postgres" 279 | ``` 280 | 281 | **If backup fails:** 282 | 283 | - Check that PostgreSQL is running during the backup 284 | - Ensure there's sufficient disk space for the SQL dump 285 | - Review the addon logs for specific error messages 286 | 287 | ### Now what.. 288 | 289 | Well.. Dive in! 290 | 291 | You can read additional documentation on how you van work with your data and Grafana here: 292 | 293 | https://github.com/expaso/hassos-addons/issues/1 294 | 295 | ## Support 296 | 297 | - Got questions? 298 | [Open an issue here][issues] 299 | 300 | - For a general repository issue or add-on ideas? [Open an issue here][repo-issues] 301 | 302 | [issues]: https://github.com/expaso/hassos-addon-timescaledb/issues 303 | [repo-issues]: https://github.com/expaso/hassos-addons/issues 304 | 305 | 306 | {% if channel == "edge" %} 307 | ## WARNING! THIS IS AN EDGE VERSION! 308 | 309 | This Home Assistant Add-ons repository contains edge builds of add-ons. 310 | Edge builds add-ons are based upon the latest development version. 311 | 312 | - They may not work at all. 313 | - They might stop working at any time. 314 | - They could have a negative impact on your system. 315 | 316 | This repository was created for: 317 | 318 | - Anybody willing to test. 319 | - Anybody interested in trying out upcoming add-ons or add-on features. 320 | - Developers. 321 | 322 | If you are more interested in stable releases, use the stable repository: 323 | 324 | 325 | 326 | {% endif %} 327 | {% if channel == "beta" %} 328 | ## WARNING! THIS IS A BETA VERSION! 329 | 330 | This Home Assistant Add-ons repository contains beta releases of add-ons. 331 | 332 | - They might stop working at any time. 333 | - They could have a negative impact on your system. 334 | 335 | This repository was created for: 336 | 337 | - Anybody willing to test. 338 | - Anybody interested in trying out upcoming add-ons or add-on features. 339 | 340 | If you are more interested in stable releases, use the stable repository: 341 | 342 | 343 | 344 | {% endif %} 345 | 346 | [project-stage-shield]: https://img.shields.io/badge/project%20stage-production%20ready-brightgreen.svg 347 | [release-shield]: https://img.shields.io/badge/version-{{ version }}-blue.svg 348 | [release]: {{ repo }}/tree/{{ version }} 349 | [license-shield]: https://img.shields.io/github/license/expaso/hassos-addon-{{ name }}.svg 350 | [maintenance-shield]: https://img.shields.io/maintenance/yes/2025.svg 351 | -------------------------------------------------------------------------------- /timescaledb/rootfs/etc/s6-overlay/s6-rc.d/init-addon/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # ============================================================================== 3 | # Home Assistant Add-on: TimescaleDb 4 | # Initializes the container during startup 5 | # ============================================================================== 6 | declare POSTGRES_DATA 7 | declare VERSION_FILE 8 | declare NEW_INSTALL 9 | declare BUILD_TIMESTAMP 10 | declare BACKUP_FILE 11 | declare RESTORE_MODE 12 | 13 | POSTGRES_DATA=/data/postgres 14 | VERSION_FILE=/data/version 15 | BACKUP_FILE=/data/backup_db.sql 16 | NEW_INSTALL=false 17 | RESTORE_MODE=false 18 | BUILD_TIMESTAMP="$(cat /build-timestamp)" 19 | 20 | # Applies permission to the data directory for the postgres user 21 | applyPermissions () { 22 | chown -R postgres:postgres ${POSTGRES_DATA} 23 | chmod 700 ${POSTGRES_DATA} 24 | } 25 | 26 | # Initializes the data directory 27 | initializeDataDirectory () { 28 | # Init data-directory 29 | bashio::log.info "Initializing new postgres directory.." 30 | mkdir -p ${POSTGRES_DATA} 31 | applyPermissions 32 | su - postgres -c "initdb -D ${POSTGRES_DATA}" 33 | # Set timescaledb and pg_stat_statements as being enabled in the postgres config file. 34 | sed -r -i "s/[#]*\s*(shared_preload_libraries)\s*=\s*'(.*)'/\1 = 'timescaledb,pg_stat_statements,\2'/;s/,'''/'/;s/,'/'/" ${POSTGRES_DATA}/postgresql.conf 35 | 36 | # Set Password protect IPv4 hosts by default 37 | echo "host all all 0.0.0.0/0 md5" >> ${POSTGRES_DATA}/pg_hba.conf 38 | # Set Password protect IPv6 hosts by default 39 | echo "host all all ::/0 md5" >> ${POSTGRES_DATA}/pg_hba.conf 40 | echo "local all all md5" >> ${POSTGRES_DATA}/pg_hba.conf 41 | echo "local all all peer" >> ${POSTGRES_DATA}/pg_hba.conf 42 | # Set Listen on all addresses (*) 43 | sed -r -i "s/[#]listen_addresses.=.'.*'/listen_addresses\ \=\ \'\*\'/g" ${POSTGRES_DATA}/postgresql.conf 44 | 45 | echo "timescaledb.license_key='CommunityLicense'" >> ${POSTGRES_DATA}/postgresql.conf 46 | if bashio::config.has_value 'timescaledb.telemetry'; then 47 | # Set telemetry level 48 | echo "timescaledb.telemetry_level=$(bashio::config 'timescaledb.telemetry')" >> ${POSTGRES_DATA}/postgresql.conf 49 | fi 50 | bashio::log.info "done" 51 | } 52 | 53 | # Upgrades the timescale extensions on all databases. 54 | upgradeTimeScaleExtension () { 55 | # Upgrade Timescale.. 56 | bashio::log.info "Upgrading Timescale extentions.." 57 | bashio::log.info "Updating Timescale Extension for system databases.." 58 | 59 | # Fetch all databases.. 60 | psql \ 61 | -X \ 62 | -U "postgres" \ 63 | -c "select datname from pg_database where datallowconn = true;" \ 64 | --set ON_ERROR_STOP=off \ 65 | --no-align \ 66 | -t \ 67 | --field-separator ' ' \ 68 | --quiet \ 69 | | while read datname; do 70 | psql -X -U postgres -d ${datname} -c "select 1 from pg_extension where extname = 'timescaledb';" | grep -q 1 \ 71 | && ( 72 | bashio::log.info "Try updating Timescale Extension for database: '${datname}'.."; \ 73 | # Drop the old functions and views, these could be incompatible with the new version. 74 | # See: https://github.com/timescale/timescaledb-extras/issues/34 75 | # Please note that we do not drop the extension itself, because that would drop the hypertables as well. 76 | # The views and functions could be recreated from: https://github.com/timescale/timescaledb-extras 77 | psql -U "postgres" homeassistant -c "drop function if exists public.get_dimension_details(regclass)"; \ 78 | psql -U "postgres" homeassistant -c "drop view if exists public.chunks_tstz"; \ 79 | psql -U "postgres" homeassistant -c "drop view if exists public.chunks_ts"; \ 80 | (psql -X -U postgres -d ${datname} -c "ALTER EXTENSION timescaledb UPDATE;" || true) 81 | ) 82 | 83 | # Update Timescale Toolkit Extension when needed 84 | psql -X -U postgres -d ${datname} -c "select 1 from pg_extension where extname = 'timescaledb_toolkit';" | grep -q 1 \ 85 | && ( 86 | bashio::log.info "Try updating Timescale Toolkit Extension for database: '${datname}'.."; \ 87 | (psql -X -U postgres -d ${datname} -c "ALTER EXTENSION timescaledb_toolkit UPDATE;" || true) 88 | ) 89 | 90 | # Update Vector extension when needed 91 | psql -X -U postgres -d ${datname} -c "select 1 from pg_extension where extname = 'vector';" | grep -q 1 \ 92 | && ( 93 | bashio::log.info "Try updating Vector Extension for database: '${datname}'.."; \ 94 | (psql -X -U postgres -d ${datname} -c "ALTER EXTENSION vector UPDATE;" || true) 95 | ) 96 | 97 | done 98 | 99 | bashio::log.info "done" 100 | } 101 | 102 | # Upgrades the PostgreSQL databases 103 | # $1: The current version 104 | # $2: The new version 105 | upgradePostgreSQL() { 106 | local FROM_VERSION=${1} 107 | local TO_VERSION=${2} 108 | 109 | bashio::log.notice "Upgrading databases now. This could take a while. Please be patient..." 110 | 111 | # Move the old data directory out of our way.. 112 | mv ${POSTGRES_DATA} ${POSTGRES_DATA}${FROM_VERSION} 113 | 114 | # And upgrade PostgreSQL 115 | bashio::log.notice "Upgrading PostgreSql from ${FROM_VERSION} to ${TO_VERSION}.." 116 | 117 | # Backup old HBA.conf and create a temp one... 118 | mv ${POSTGRES_DATA}${FROM_VERSION}/pg_hba.conf ${POSTGRES_DATA}${FROM_VERSION}/pg_hba_backup.conf 119 | echo "local all all trust" > ${POSTGRES_DATA}${FROM_VERSION}/pg_hba.conf 120 | 121 | #Start postgres on the old data-dir 122 | bashio::log.info "Starting PostgreSQL-${FROM_VERSION} first.." 123 | su - postgres -c "/usr/libexec/postgresql${FROM_VERSION}/postgres -D ${POSTGRES_DATA}${FROM_VERSION}" & 124 | postgres_pid=$! 125 | 126 | # Wait for postgres to become available.. 127 | while ! psql -X -U "postgres" postgres -c "" 2> /dev/null; do 128 | sleep 1 129 | done 130 | 131 | # Upgrade Timescale first, otherwise, pg_upgrade will fail. 132 | upgradeTimeScaleExtension 133 | 134 | # Stop server 135 | bashio::log.notice "Stopping PostgreSql ${FROM_VERSION}.." 136 | kill ${postgres_pid} 137 | wait ${postgres_pid} || true 138 | bashio::log.notice "done" 139 | 140 | # Restore HBA.CONF 141 | rm ${POSTGRES_DATA}${FROM_VERSION}/pg_hba.conf 142 | mv ${POSTGRES_DATA}${FROM_VERSION}/pg_hba_backup.conf ${POSTGRES_DATA}${FROM_VERSION}/pg_hba.conf 143 | 144 | # Create a fresh data-directory 145 | initializeDataDirectory 146 | 147 | # And upgrade! 148 | bashio::log.notice "Upgrading databases.." 149 | cd ${POSTGRES_DATA}${FROM_VERSION} 150 | if su -c "pg_upgrade --old-bindir=/usr/libexec/postgresql${FROM_VERSION} --new-bindir=/usr/libexec/postgresql${TO_VERSION} --old-datadir=${POSTGRES_DATA}${FROM_VERSION} --new-datadir=${POSTGRES_DATA} --link --username=postgres" -s /bin/sh postgres; then 151 | bashio::log.notice "PostgreSQL upgraded succesfully!" 152 | # Remove the old cluster... 153 | rm -r ${POSTGRES_DATA}${FROM_VERSION} 154 | return 0 155 | else 156 | # copy logs 157 | cp -r /data/postgres/pg_upgrade_output.d ${POSTGRES_DATA}${FROM_VERSION} || true 158 | 159 | # Rollback.. 160 | rm -r ${POSTGRES_DATA} 161 | mv ${POSTGRES_DATA}${FROM_VERSION} ${POSTGRES_DATA} 162 | 163 | bashio::log.error "PostgreSQL could not upgrade! Please inspect any errors in the lines above!" 164 | return 1 165 | fi 166 | } 167 | 168 | # Prepare /tmp/pg_stat_tmp directory 169 | mkdir -p /tmp/pg_stat_tmp 170 | chown postgres:postgres /tmp/pg_stat_tmp 171 | 172 | if ! bashio::fs.directory_exists "${POSTGRES_DATA}"; then 173 | bashio::log.info "Detected a fresh installation! Welcome! We're setting things up for you.." 174 | NEW_INSTALL=true 175 | 176 | # Check if we have a backup to restore from 177 | if [[ -f "${BACKUP_FILE}" ]]; then 178 | bashio::log.notice "Found backup SQL file. Will restore after initialization." 179 | RESTORE_MODE=true 180 | fi 181 | else 182 | touch ${VERSION_FILE} 183 | # Always re-apply permissions, because they seem to be reset after a snapshot restore 184 | applyPermissions 185 | 186 | # Check if database is corrupted and we have a backup 187 | if [[ -f "${BACKUP_FILE}" ]]; then 188 | # Check if PG_VERSION exists and is valid 189 | if [[ ! -f "${POSTGRES_DATA}/PG_VERSION" ]]; then 190 | bashio::log.warning "Database appears corrupted (missing PG_VERSION). Backup SQL found." 191 | bashio::log.warning "Will attempt to restore from backup..." 192 | RESTORE_MODE=true 193 | NEW_INSTALL=true 194 | # Move corrupted data aside 195 | if [[ -d "${POSTGRES_DATA}" ]]; then 196 | bashio::log.info "Moving corrupted database aside..." 197 | mv "${POSTGRES_DATA}" "${POSTGRES_DATA}.corrupted.$(date +%Y%m%d_%H%M%S)" 198 | fi 199 | fi 200 | fi 201 | fi 202 | 203 | # Initialize for new installs 204 | if bashio::var.true "${NEW_INSTALL}"; then 205 | touch /data/firstrun 206 | echo "${BUILD_TIMESTAMP}" > ${VERSION_FILE} 207 | initializeDataDirectory 208 | 209 | # Restore from backup if available 210 | if bashio::var.true "${RESTORE_MODE}"; then 211 | bashio::log.notice "Restore mode enabled. Loading restore script..." 212 | source /usr/share/timescaledb/restore_from_backup.sh 213 | 214 | if restoreFromBackup; then 215 | bashio::log.notice "Database restored successfully from backup SQL file!" 216 | # Mark that we've restored, so postgres startup can skip firstrun setup 217 | rm -f /data/firstrun 218 | else 219 | bashio::log.error "Failed to restore from backup. Starting with fresh database." 220 | bashio::log.error "The backup file has been preserved at ${BACKUP_FILE}" 221 | bashio::log.error "You may attempt manual restoration if needed." 222 | # Keep the backup file for manual recovery 223 | fi 224 | fi 225 | else 226 | 227 | # Check if we need to restore again. 228 | if $(bashio::config.true 'retry_upgrade'); then 229 | if bashio::fs.directory_exists "${POSTGRES_DATA}14"; then 230 | bashio::log.notice "An aborted upgrade from Postgres 14 was detected. Restoring.." 231 | rm -r ${POSTGRES_DATA} 232 | mv ${POSTGRES_DATA}14 ${POSTGRES_DATA} 233 | else 234 | bashio::config.suggest.false 'retry_upgrade' 'This option is only for temporary reasons: to recover from a failed upgrade.' 235 | fi 236 | fi 237 | 238 | # Check if we need to upgrade from 12 to 15 239 | if [[ $(< ${POSTGRES_DATA}/PG_VERSION) == "12" ]]; then 240 | bashio::log.notice "A database upgrade is required from Postgres 12." 241 | bashio::log.error "Upgrade was not succesfull. This addon version does no longer support Postgres 12. Please upgrade to the previous version of the addon and upgrade to Postgres 14 first." 242 | exit 1 243 | fi 244 | 245 | # Check if we need to upgrade from 14 to 15 246 | if [[ $(< ${POSTGRES_DATA}/PG_VERSION) == "14" ]]; then 247 | bashio::log.notice "A database upgrade is required from Postgres 14." 248 | bashio::log.error "Upgrade was not succesfull. This addon version does no longer support Postgres 14. Please upgrade to the previous version of the addon and upgrade to Postgres 15 first." 249 | exit 1 250 | fi 251 | 252 | # Check if we need to upgrade from 15 to 17 253 | if [[ $(< ${POSTGRES_DATA}/PG_VERSION) == "15" ]]; then 254 | bashio::log.notice "A database upgrade is required from Postgres 15 -> 17" 255 | if upgradePostgreSQL 15 17; then 256 | bashio::log.notice "Upgrade was succesful" 257 | else 258 | bashio::log.error "Upgrade was not succesful." 259 | exit 1 260 | fi 261 | fi 262 | 263 | # Check if we need to upgrade from 16 to 17 264 | if [[ $(< ${POSTGRES_DATA}/PG_VERSION) == "16" ]]; then 265 | bashio::log.notice "A database upgrade is required from Postgres 16 -> 17" 266 | if upgradePostgreSQL 16 17; then 267 | bashio::log.notice "Upgrade was succesful" 268 | else 269 | bashio::log.error "Upgrade was not succesful." 270 | exit 1 271 | fi 272 | fi 273 | fi 274 | 275 | bashio::log.info "done" 276 | 277 | 278 | # Ensure pg_stat_statements is in shared_preload_libraries for existing installations 279 | if bashio::var.false "${NEW_INSTALL}"; then 280 | if ! grep -q "shared_preload_libraries.*pg_stat_statements" ${POSTGRES_DATA}/postgresql.conf; then 281 | bashio::log.info "Adding pg_stat_statements to shared_preload_libraries.." 282 | sed -r -i "s/(shared_preload_libraries\s*=\s*'[^']*)(timescaledb)([^']*')/\1\2,pg_stat_statements\3/" ${POSTGRES_DATA}/postgresql.conf 283 | bashio::log.info "done" 284 | fi 285 | fi 286 | 287 | # Patch telemetry level 288 | if bashio::config.has_value 'timescaledb.telemetry'; then 289 | sed -r -i "s/timescaledb.telemetry_level.=.'.*'/timescaledb.telemetry_level=$(bashio::config 'timescaledb.telemetry')/g" ${POSTGRES_DATA}/postgresql.conf 290 | fi 291 | if bashio::config.has_value 'timescaledb.maxmemory'; then 292 | # Apply TimescaleDb mem/cpu tuning settings 293 | bashio::log.info "Tuning resources.." 294 | 295 | chmod 707 "/usr/share/timescaledb/002_timescaledb_tune.sh" 296 | TS_TUNE_MEMORY=$(bashio::config 'timescaledb.maxmemory') \ 297 | TS_TUNE_NUM_CPUS=$(bashio::config 'timescaledb.maxcpus') \ 298 | POSTGRESQL_CONF_DIR=${POSTGRES_DATA} \ 299 | /usr/share/timescaledb/002_timescaledb_tune.sh 300 | bashio::log.info "done" 301 | fi 302 | 303 | # Appy max connections 304 | if bashio::config.has_value 'max_connections'; then 305 | bashio::log.info "Applying max connections.." 306 | sed -i -e "/max_connections =/ s/= .*/= $(bashio::config 'max_connections')/" ${POSTGRES_DATA}/postgresql.conf 307 | bashio::log.info "done" 308 | fi 309 | 310 | # Apply user-provided PostgreSQL configuration overrides 311 | if bashio::config.has_value 'postgresql_config' || bashio::config.has_value 'pg_hba_config'; then 312 | bashio::log.info "Applying user configuration overrides.." 313 | chmod 707 "/usr/share/timescaledb/003_apply_user_config.sh" 314 | /usr/share/timescaledb/003_apply_user_config.sh 315 | bashio::log.info "done" 316 | fi 317 | -------------------------------------------------------------------------------- /DESIGN-postgres-config.md: -------------------------------------------------------------------------------- 1 | # Design Document: PostgreSQL Configuration Management 2 | 3 | ## Overview 4 | 5 | This document describes the design for allowing users to customize PostgreSQL configuration files (`postgresql.conf` and `pg_hba.conf`) through Home Assistant addon configuration, replacing the current "advanced users only" approach of using shell commands. 6 | 7 | ## Current State 8 | 9 | ### Existing Configuration Approach 10 | 11 | Currently, users can modify PostgreSQL configuration files using the `init_commands` option: 12 | 13 | ```yaml 14 | init_commands: 15 | - 'sed -i -e "/max_connections =/ s/= .*/= 50/" /data/postgres/postgresql.conf' 16 | ``` 17 | 18 | **Problems with this approach:** 19 | 20 | - Requires shell scripting knowledge 21 | - Error-prone (sed syntax, regex, escaping) 22 | - Not validated 23 | - Runs in `init-user` service (timing may be unpredictable) 24 | - Hard to maintain and debug 25 | - No safety guardrails 26 | 27 | ### Current Configuration Flow 28 | 29 | 1. `init-addon/run` - Initializes PostgreSQL data directory and base configuration 30 | 2. `init-user/run` - Runs user packages and init_commands 31 | 3. `postgres/run` - Starts PostgreSQL service 32 | 33 | ## Proposed Solution 34 | 35 | ### Design Principles 36 | 37 | 1. **Declarative Configuration**: Users specify _what_ they want, not _how_ to do it 38 | 2. **Type Safety**: Configuration is validated by Home Assistant schema 39 | 3. **User-Friendly**: Simple YAML, no shell scripting required 40 | 4. **Safe**: Prevent modification of critical settings 41 | 5. **Transparent**: All changes are logged 42 | 6. **Backward Compatible**: Existing init_commands continue to work 43 | 7. **Idempotent**: Same configuration produces same result 44 | 45 | ### New Configuration Options 46 | 47 | #### 1. `postgresql_config` - Key-Value Configuration 48 | 49 | Allows users to set or override PostgreSQL configuration parameters in `postgresql.conf`. 50 | 51 | **Schema:** 52 | 53 | ```yaml 54 | postgresql_config: 55 | parameter_name: "value" 56 | ``` 57 | 58 | **Example:** 59 | 60 | ```yaml 61 | postgresql_config: 62 | log_statement: "all" 63 | log_duration: "on" 64 | work_mem: "16MB" 65 | maintenance_work_mem: "256MB" 66 | effective_cache_size: "4GB" 67 | random_page_cost: "1.1" 68 | checkpoint_completion_target: "0.9" 69 | ``` 70 | 71 | **Validation:** 72 | 73 | - Keys must be valid PostgreSQL parameter names (string validation) 74 | - Values are treated as strings (PostgreSQL handles type validation) 75 | - Certain critical parameters are forbidden (see Safety section) 76 | 77 | #### 2. `pg_hba_config` - Authentication Configuration 78 | 79 | Allows users to append custom authentication rules to `pg_hba.conf`. 80 | 81 | **Schema:** 82 | 83 | ```yaml 84 | pg_hba_config: 85 | - type: "host|local|hostssl|hostnossl" 86 | database: "database_name" 87 | user: "username" 88 | address: "CIDR_address" # Optional, not used for 'local' 89 | method: "md5|trust|reject|scram-sha-256|etc" 90 | options: "key=value" # Optional 91 | ``` 92 | 93 | **Example:** 94 | 95 | ```yaml 96 | pg_hba_config: 97 | # Allow specific subnet with password 98 | - type: "host" 99 | database: "homeassistant" 100 | user: "all" 101 | address: "192.168.1.0/24" 102 | method: "md5" 103 | 104 | # Require SSL for remote connections 105 | - type: "hostssl" 106 | database: "all" 107 | user: "admin" 108 | address: "0.0.0.0/0" 109 | method: "scram-sha-256" 110 | 111 | # Reject specific user 112 | - type: "host" 113 | database: "all" 114 | user: "guest" 115 | address: "0.0.0.0/0" 116 | method: "reject" 117 | ``` 118 | 119 | **Validation:** 120 | 121 | - `type` must be one of: host, local, hostssl, hostnossl 122 | - `database` and `user` are required strings 123 | - `address` is required for non-local types 124 | - `method` must be a valid authentication method 125 | 126 | **Behavior:** 127 | 128 | - Custom rules are **appended** to the default rules (not replaced) 129 | - Default rules remain in place for safety 130 | - Rules are applied in order (first match wins in pg_hba.conf) 131 | 132 | ### Implementation Architecture 133 | 134 | #### File Structure 135 | 136 | ``` 137 | timescaledb/ 138 | ├── config.yaml # Updated with new schema 139 | └── rootfs/ 140 | ├── etc/ 141 | │ └── s6-overlay/ 142 | │ └── s6-rc.d/ 143 | │ └── init-addon/ 144 | │ └── run # Updated to call helper script 145 | └── usr/ 146 | └── share/ 147 | └── timescaledb/ 148 | ├── 000_install_timescaledb.sh 149 | ├── 001_reenable_auth.sh 150 | ├── 002_timescaledb_tune.sh 151 | └── 003_apply_user_config.sh # NEW 152 | ``` 153 | 154 | #### New Script: `003_apply_user_config.sh` 155 | 156 | This helper script will: 157 | 158 | 1. Read configuration from Home Assistant addon options 159 | 2. Validate settings 160 | 3. Apply `postgresql_config` settings to `postgresql.conf` 161 | 4. Apply `pg_hba_config` rules to `pg_hba.conf` 162 | 5. Log all changes 163 | 164 | #### Integration Point in `init-addon/run` 165 | 166 | The configuration application will occur at the end of `init-addon/run`, after: 167 | 168 | - Data directory initialization 169 | - Timescaledb tuning 170 | - max_connections setting 171 | - But BEFORE the postgres service starts 172 | 173 | ```bash 174 | # At the end of init-addon/run, add: 175 | 176 | # Apply user configuration overrides 177 | if bashio::config.has_value 'postgresql_config' || bashio::config.has_value 'pg_hba_config'; then 178 | bashio::log.info "Applying user configuration overrides.." 179 | /usr/share/timescaledb/003_apply_user_config.sh 180 | bashio::log.info "done" 181 | fi 182 | ``` 183 | 184 | ### Configuration Schema (`config.yaml`) 185 | 186 | ```yaml 187 | options: 188 | # ... existing options ... 189 | postgresql_config: {} 190 | pg_hba_config: [] 191 | 192 | schema: 193 | # ... existing schema ... 194 | postgresql_config: 195 | match(^[a-z_]+$)?: str 196 | pg_hba_config: 197 | - type: list(local|host|hostssl|hostnossl)? 198 | database: str? 199 | user: str? 200 | address: str? 201 | method: list(trust|reject|md5|password|scram-sha-256|gss|sspi|ident|peer|pam|ldap|radius|cert)? 202 | options: str? 203 | ``` 204 | 205 | ### Safety Features 206 | 207 | #### Forbidden PostgreSQL Parameters 208 | 209 | These parameters cannot be modified via `postgresql_config` as they are critical for addon operation: 210 | 211 | - `data_directory` - Managed by addon 212 | - `hba_file` - Managed by addon 213 | - `ident_file` - Managed by addon 214 | - `port` - Managed by Home Assistant 215 | - `unix_socket_directories` - Managed by addon 216 | - `shared_preload_libraries` - Managed by addon (TimescaleDB requirement) 217 | 218 | Attempts to set these will be logged as warnings and ignored. 219 | 220 | #### Default pg_hba.conf Rules 221 | 222 | The default rules created by the addon are: 223 | 224 | ``` 225 | host all all 0.0.0.0/0 md5 226 | host all all ::/0 md5 227 | local all all md5 228 | local all all peer 229 | ``` 230 | 231 | These rules are **always present** and applied before user rules. User rules can only add additional rules, not remove these defaults. 232 | 233 | ### Error Handling 234 | 235 | 1. **Invalid Configuration**: 236 | - Validated by Home Assistant schema before applying 237 | - Schema errors prevent addon from starting 238 | 2. **Invalid PostgreSQL Parameter**: 239 | - Logged as warning 240 | - Parameter is skipped 241 | - Addon continues to start 242 | 3. **Invalid pg_hba Rule**: 243 | - Logged as warning 244 | - Rule is skipped 245 | - Addon continues to start 246 | 247 | 4. **File Write Errors**: 248 | - Logged as error 249 | - Addon fails to start (safe failure) 250 | 251 | ### Logging 252 | 253 | All configuration changes are logged with INFO level: 254 | 255 | ``` 256 | [INFO] Applying user configuration overrides.. 257 | [INFO] Setting postgresql.conf parameter: log_statement = 'all' 258 | [INFO] Setting postgresql.conf parameter: work_mem = '16MB' 259 | [INFO] Adding pg_hba.conf rule: host homeassistant all 192.168.1.0/24 md5 260 | [INFO] Applied 2 PostgreSQL config parameters and 1 pg_hba rules 261 | [INFO] done 262 | ``` 263 | 264 | Warnings for skipped settings: 265 | 266 | ``` 267 | [WARN] Skipping forbidden parameter: shared_preload_libraries 268 | [WARN] Skipping invalid pg_hba rule: missing required field 'database' 269 | ``` 270 | 271 | ### Documentation Requirements 272 | 273 | #### README.md Updates 274 | 275 | Add new section: "Advanced Configuration" 276 | 277 | ````markdown 278 | ### PostgreSQL Configuration 279 | 280 | #### Option: `postgresql_config` 281 | 282 | Allows you to customize PostgreSQL server parameters. These settings are applied to `postgresql.conf`. 283 | 284 | **Example:** 285 | 286 | ```yaml 287 | postgresql_config: 288 | log_statement: "all" 289 | log_min_duration_statement: "1000" # Log queries taking > 1 second 290 | work_mem: "16MB" 291 | maintenance_work_mem: "256MB" 292 | ``` 293 | ```` 294 | 295 | See [PostgreSQL documentation](https://www.postgresql.org/docs/current/runtime-config.html) for available parameters. 296 | 297 | **Note:** Some critical parameters cannot be modified (e.g., `shared_preload_libraries`, `port`, `data_directory`) as they are managed by the addon. 298 | 299 | #### Option: `pg_hba_config` 300 | 301 | Allows you to add custom authentication rules to `pg_hba.conf`. Rules are appended to the default rules. 302 | 303 | **Example:** 304 | 305 | ```yaml 306 | pg_hba_config: 307 | # Allow specific subnet 308 | - type: "host" 309 | database: "homeassistant" 310 | user: "all" 311 | address: "192.168.1.0/24" 312 | method: "md5" 313 | 314 | # Require SSL for admin user 315 | - type: "hostssl" 316 | database: "all" 317 | user: "admin" 318 | address: "0.0.0.0/0" 319 | method: "scram-sha-256" 320 | ``` 321 | 322 | See [PostgreSQL documentation](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html) for authentication methods. 323 | 324 | **Warning:** Be careful with authentication rules. Incorrect configuration can lock you out of the database. 325 | 326 | ```` 327 | 328 | ### Testing Approach 329 | 330 | #### Manual Testing 331 | 332 | 1. **Test Case 1: Basic postgresql_config** 333 | - Set simple parameters (log_statement, work_mem) 334 | - Verify they appear in postgresql.conf 335 | - Verify PostgreSQL starts successfully 336 | - Verify settings are active: `SHOW log_statement;` 337 | 338 | 2. **Test Case 2: Forbidden parameters** 339 | - Try to set `shared_preload_libraries` 340 | - Verify warning is logged 341 | - Verify parameter is not changed 342 | - Verify addon starts successfully 343 | 344 | 3. **Test Case 3: Basic pg_hba_config** 345 | - Add a custom rule 346 | - Verify it appears in pg_hba.conf 347 | - Verify default rules are still present 348 | - Test authentication works as expected 349 | 350 | 4. **Test Case 4: Invalid pg_hba_config** 351 | - Provide invalid rule (missing required field) 352 | - Verify warning is logged 353 | - Verify addon starts successfully 354 | 355 | 5. **Test Case 5: Empty configuration** 356 | - Don't set postgresql_config or pg_hba_config 357 | - Verify addon works normally 358 | - Verify backward compatibility 359 | 360 | 6. **Test Case 6: Upgrade scenario** 361 | - Start addon with old config 362 | - Add new options 363 | - Restart addon 364 | - Verify settings are applied on existing database 365 | 366 | #### Integration Testing 367 | 368 | - Test across different architectures (amd64, aarch64, armv7) 369 | - Test with fresh install vs upgrade 370 | - Test interaction with timescaledb-tune 371 | - Test interaction with max_connections setting 372 | 373 | ### Migration Path 374 | 375 | **Existing Users:** 376 | - No migration required 377 | - New options are optional 378 | - Existing `init_commands` continue to work 379 | - Users can gradually migrate from init_commands to declarative config 380 | 381 | **Documentation:** 382 | - Add migration guide showing how to convert common init_commands to declarative config 383 | - Example: 384 | ```yaml 385 | # Old way (still works) 386 | init_commands: 387 | - 'sed -i -e "/log_statement =/ s/= .*/= '\''all'\''/" /data/postgres/postgresql.conf' 388 | 389 | # New way (recommended) 390 | postgresql_config: 391 | log_statement: "all" 392 | ```` 393 | 394 | ### Future Enhancements 395 | 396 | 1. **Configuration Templates**: Pre-defined templates for common scenarios 397 | - Performance tuning 398 | - Security hardening 399 | - Development mode 400 | 2. **Validation Improvements**: Check parameter values against PostgreSQL docs 401 | 3. **pg_hba Management**: Allow replacing default rules (advanced option) 402 | 4. **Configuration Backup**: Automatic backup before applying changes 403 | 404 | 5. **Web UI**: Integration with Home Assistant UI for easier configuration 405 | 406 | ## Implementation Checklist 407 | 408 | - [ ] Update `config.yaml` with new schema 409 | - [ ] Create `003_apply_user_config.sh` script 410 | - [ ] Update `init-addon/run` to call new script 411 | - [ ] Add validation for forbidden parameters 412 | - [ ] Add logging for all changes 413 | - [ ] Update README.md with new options 414 | - [ ] Add examples to documentation 415 | - [ ] Test on fresh install 416 | - [ ] Test on upgrade scenario 417 | - [ ] Test forbidden parameters 418 | - [ ] Test invalid configurations 419 | - [ ] Test across architectures 420 | 421 | ## Summary 422 | 423 | This design provides a user-friendly, safe, and maintainable way to customize PostgreSQL configuration through declarative YAML configuration instead of shell commands. It maintains backward compatibility while providing a much better user experience for common configuration tasks. 424 | 425 | The implementation follows Home Assistant addon best practices and the guidelines in AGENTS.md, ensuring consistent code quality and maintainability. 426 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Home Assistant Add-on: [PostgreSQL](https://www.postgresql.org/) [TimescaleDB](https://www.timescale.com/) 2 | 3 | ## [PostgreSql](https://www.postgresql.org/) & [Postgis](https://postgis.net/) & [TimescaleDB](https://www.timescale.com/) & [TimescaleDB Toolkit](https://github.com/timescale/timescaledb-toolkit) & [pgAgent](https://www.pgadmin.org/docs/pgadmin4/development/pgagent.html) & [pgVector](https://github.com/pgvector/pgvector) 4 | 5 | Buy Me A Coffee 6 | 7 | ## PostgreSQL Overview 8 | 9 | From: https://www.postgresql.org/about/ 10 | 11 | PostgreSQL is a powerful, open source object-relational database system that uses and extends the SQL language combined with many features that safely store and scale the most complicated data workloads. The origins of PostgreSQL date back to 1986 as part of the POSTGRES project at the University of California at Berkeley and has more than 30 years of active development on the core platform. 12 | 13 | PostgreSQL has earned a strong reputation for its proven architecture, reliability, data integrity, robust feature set, extensibility, and the dedication of the open source community behind the software to consistently deliver performant and innovative solutions. 14 | 15 | ## TimescaleDB Overview 16 | 17 | From: https://docs.timescale.com/latest/introduction 18 | 19 | TimescaleDB is an open-source time-series database optimized for fast ingest and complex queries. It speaks "full SQL" and is correspondingly easy to use like a traditional relational database, yet scales in ways previously reserved for NoSQL databases. 20 | 21 | Compared to the trade-offs demanded by these two alternatives (relational vs. NoSQL), TimescaleDB offers the best of both worlds for time-series data: 22 | 23 | ### Easy to Use 24 | 25 | Full SQL interface for all SQL natively supported by PostgreSQL (including secondary indexes, non-time based aggregates, sub-queries, JOINs, window functions). 26 | 27 | - Connects to any client or tool that speaks PostgreSQL, no changes needed. 28 | - Time-oriented features, API functions, and optimizations. 29 | - Robust support for Data retention policies. 30 | 31 | ## Introduction 32 | 33 | Say, you want put all those nice Home Assistant measurements from your smarthome to good use, and for example, use something like [Grafana](https://grafana.com) for your dashboards, and maybe [Prometheus](https://prometheus.io/) for monitoring.. 34 | 35 | **That means you need a decent time-series database.** 36 | 37 | You could use [InfluxDB](www.influxdata.com) for this. 38 | This works pretty good.. but.. being a NoSQL database, this means you have to learn Flux (it's query language). Once you get there, you will quickly discover that updating existing data in Influx is near impossible (without overwriting it). That's a bummer, since my data needed some 'tweaking'. 39 | 40 | For the Home Assistant recorder, you probaly need some SQL storage too. That means you also need to 41 | bring stuff like MariaDb or Postgres to the table (unless you keep using the SqlLite database). 42 | 43 | So.. why not combine these? 44 | Seriously?! You ask... 45 | 46 | Yeah! Pleae read this blogpost to get a sense of why: 47 | 48 | https://blog.timescale.com/blog/why-sql-beating-nosql-what-this-means-for-future-of-data-time-series-database-348b777b847a/ 49 | 50 | And so.. Use the power of your already existing SQL skills for PostgreSQL, combined with powerfull time-series functionality of TimeScaleDb and be done with it! 51 | 52 | As a bonus, I also added a Geospatial extention: [Postgis](https://postgis.net/). 53 | You can now happily query around your data like a PRO 😎. 54 | 55 | ## Installation 56 | 57 | There are two ways to install this add-on: via the Home Assistant add-on store or, by running the container manually on a separate (more powerfull?) machine. 58 | This could come in handy if you want to use a more powerfull machine for your database, or if you want to use a different OS than Home Assistant OS. 59 | 60 | ### Home Assistant add-on store 61 | 62 | [![Open your Home Assistant instance and show the add add-on repository dialog with a specific repository URL pre-filled.](https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Fexpaso%2Fhassos-addons) 63 | 64 | Or in the Home-Assistant add-on store, a possibility to add a repository is provided. 65 | Use the following URL to add this repository: 66 | 67 | ```txt 68 | https://github.com/expaso/hassos-addons 69 | ``` 70 | 71 | Now scroll down and select the "TimeScaleDb" add-on. 72 | Press install to download the add-on and unpack it on your machine. This can take some time. 73 | 74 | Start the add-on, check the logs of the add-on to see if everything went well. 75 | 76 | ### Running the container standalone. 77 | 78 | In this case, you need to have a working Docker installation on your machine. 79 | pull one of the images for the desired architecture from docker hub: 80 | 81 | ``` 82 | docker pull ghcr.io/expaso/timescaledb/amd64:stable 83 | docker pull ghcr.io/expaso/timescaledb/aarch64:stable 84 | docker pull ghcr.io/expaso/timescaledb/armv7:stable 85 | docker pull ghcr.io/expaso/timescaledb/armhf:stable 86 | docker pull ghcr.io/expaso/timescaledb/i386:stable 87 | ``` 88 | 89 | You can replace latest with the version number you want to use. 90 | 91 | Simply start it like this: 92 | 93 | ``` 94 | docker run \ 95 | --rm \ 96 | --name timescaledb \ 97 | --v ${PWD}/timescaledb_addon_data:/data \ 98 | -p 5432:5432 \ 99 | ghcr.io/expaso/timescaledb/amd64:dev 100 | ``` 101 | 102 | This will use ~/timescaledb_addon_data as the data directory for the container, and map the port 5432 to the host. 103 | 104 | If you want to start the container as a daemon, simply remove the `--rm` option and add the `-d` option like so: 105 | 106 | ``` 107 | docker run \ 108 | -d \ 109 | --name timescaledb \ 110 | --v ${PWD}/timescaledb_addon_data:/data \ 111 | -p 5432:5432 \ 112 | ghcr.io/expaso/timescaledb/amd64:dev 113 | ``` 114 | 115 | ## Usage 116 | 117 | You are now ready to start using Postgres with TimescaleDb extenstions enabled! 118 | 119 | Seeking a nice web-based client? **Try the pgAdmin4 addon.** 120 | 121 | Please do not forget to also map the TCP/IP port in the network-section of the addon to the desired port number. 122 | The default is port `5432` 123 | 124 | **Securiy Notice!** 125 | 126 | The default username is `postgres` with password `homeassistant`. 127 | Make sure you change this immediately after activating the add-on: 128 | 129 | ``` 130 | ALTER USER user_name WITH PASSWORD 'strongpassword'; 131 | ``` 132 | 133 | A default `pg_hba.conf` is created in the data directory with the following content, which allows local peer users and network users with passwords.: 134 | 135 | ``` 136 | # TYPE DATABASE USER ADDRESS METHOD 137 | host all all 0.0.0.0/0 md5" 138 | local all all 0.0.0.0/0 md5" 139 | local all all 0.0.0.0/0 peer" 140 | ``` 141 | 142 | Please review this configuration carefully by examine the docs: 143 | https://www.postgresql.org/docs/devel/auth-pg-hba-conf.html 144 | 145 | ## Advanced Configuration 146 | 147 | ### PostgreSQL Configuration 148 | 149 | #### Option: `postgresql_config` 150 | 151 | Allows you to customize PostgreSQL server parameters. These settings are applied to `postgresql.conf` and can override default settings configured by the addon. 152 | 153 | **Example:** 154 | 155 | ```yaml 156 | postgresql_config: 157 | log_statement: "all" 158 | log_min_duration_statement: "1000" # Log queries taking > 1 second 159 | work_mem: "16MB" 160 | maintenance_work_mem: "256MB" 161 | effective_cache_size: "4GB" 162 | random_page_cost: "1.1" 163 | checkpoint_completion_target: "0.9" 164 | ``` 165 | 166 | See the [PostgreSQL documentation](https://www.postgresql.org/docs/current/runtime-config.html) for all available parameters and their meanings. 167 | 168 | **Important Notes:** 169 | 170 | - Configuration changes require a restart of the addon to take effect 171 | - Some critical parameters cannot be modified (e.g., `shared_preload_libraries`, `port`, `data_directory`) as they are managed by the addon 172 | - Invalid parameter names or values will be logged and skipped 173 | - Parameters are applied after TimescaleDB tuning, so you can override tuned values if needed 174 | 175 | **Common Use Cases:** 176 | 177 | **Performance Tuning:** 178 | 179 | ```yaml 180 | postgresql_config: 181 | work_mem: "32MB" 182 | maintenance_work_mem: "512MB" 183 | effective_cache_size: "8GB" 184 | ``` 185 | 186 | **Query Logging for Debugging:** 187 | 188 | ```yaml 189 | postgresql_config: 190 | log_statement: "all" 191 | log_duration: "on" 192 | log_min_duration_statement: "500" 193 | ``` 194 | 195 | **Connection Settings:** 196 | 197 | ```yaml 198 | postgresql_config: 199 | idle_in_transaction_session_timeout: "60000" 200 | statement_timeout: "30000" 201 | ``` 202 | 203 | #### Option: `pg_hba_config` 204 | 205 | Allows you to add custom authentication rules to `pg_hba.conf`. These rules control which hosts can connect to the database and how they authenticate. 206 | 207 | **Example:** 208 | 209 | ```yaml 210 | pg_hba_config: 211 | # Allow specific subnet with password authentication 212 | - type: "host" 213 | database: "homeassistant" 214 | user: "all" 215 | address: "192.168.1.0/24" 216 | method: "md5" 217 | 218 | # Require SSL for remote admin connections 219 | - type: "hostssl" 220 | database: "all" 221 | user: "admin" 222 | address: "0.0.0.0/0" 223 | method: "scram-sha-256" 224 | 225 | # Reject specific user from connecting 226 | - type: "host" 227 | database: "all" 228 | user: "guest" 229 | address: "0.0.0.0/0" 230 | method: "reject" 231 | 232 | # Allow local connections without password for specific user 233 | - type: "local" 234 | database: "all" 235 | user: "backup" 236 | method: "trust" 237 | ``` 238 | 239 | **Rule Format:** 240 | 241 | - `type`: Connection type - `local` (Unix socket), `host` (TCP/IP), `hostssl` (TCP/IP with SSL), `hostnossl` (TCP/IP without SSL) 242 | - `database`: Database name or `all` for all databases 243 | - `user`: Username or `all` for all users 244 | - `address`: CIDR address (required for non-local types, e.g., `192.168.1.0/24` or `0.0.0.0/0`) 245 | - `method`: Authentication method - `md5`, `scram-sha-256`, `trust`, `reject`, `peer`, `ident`, etc. 246 | - `options`: Optional authentication options (e.g., `clientcert=verify-full`) 247 | 248 | See the [PostgreSQL documentation](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html) for complete details on authentication methods. 249 | 250 | **Important Notes:** 251 | 252 | - Custom rules are **appended** to the default rules (not replaced) 253 | - Default rules remain in place to ensure basic connectivity 254 | - Rules are evaluated in order - the first matching rule is used 255 | - Invalid rules will be logged and skipped 256 | - Changes require a restart of the addon to take effect 257 | 258 | **⚠️ Warning:** Incorrect `pg_hba.conf` configuration can lock you out of the database. Always ensure you have at least one working authentication rule before adding restrictions. 259 | 260 | **Common Use Cases:** 261 | 262 | **Restrict access to specific network:** 263 | 264 | ```yaml 265 | pg_hba_config: 266 | - type: "host" 267 | database: "all" 268 | user: "all" 269 | address: "192.168.1.0/24" 270 | method: "md5" 271 | - type: "host" 272 | database: "all" 273 | user: "all" 274 | address: "0.0.0.0/0" 275 | method: "reject" 276 | ``` 277 | 278 | **Require SSL for all external connections:** 279 | 280 | ```yaml 281 | pg_hba_config: 282 | - type: "hostssl" 283 | database: "all" 284 | user: "all" 285 | address: "0.0.0.0/0" 286 | method: "scram-sha-256" 287 | - type: "hostnossl" 288 | database: "all" 289 | user: "all" 290 | address: "0.0.0.0/0" 291 | method: "reject" 292 | ``` 293 | 294 | **Allow passwordless local backup user:** 295 | 296 | ```yaml 297 | pg_hba_config: 298 | - type: "local" 299 | database: "all" 300 | user: "backup_user" 301 | method: "peer" 302 | ``` 303 | 304 | ### Migration from `init_commands` 305 | 306 | If you're currently using `init_commands` to modify PostgreSQL configuration, you can migrate to the new declarative approach: 307 | 308 | **Old way (still works):** 309 | 310 | ```yaml 311 | init_commands: 312 | - 'sed -i -e "/log_statement =/ s/= .*/= '\''all'\''/" /data/postgres/postgresql.conf' 313 | - 'sed -i -e "/work_mem =/ s/= .*/= '\''32MB'\''/" /data/postgres/postgresql.conf' 314 | ``` 315 | 316 | **New way (recommended):** 317 | 318 | ```yaml 319 | postgresql_config: 320 | log_statement: "all" 321 | work_mem: "32MB" 322 | ``` 323 | 324 | The new approach is simpler, safer, and easier to maintain. The `init_commands` option remains available for advanced use cases that aren't covered by the declarative configuration. 325 | 326 | ## Backup and Restore 327 | 328 | This addon implements a robust backup and restore mechanism designed to protect your database from data loss. 329 | 330 | ### How Backups Work 331 | 332 | When Home Assistant's backup system runs, it automatically: 333 | 334 | 1. **Pre-Backup**: Executes `pg_dumpall` to create a complete SQL dump of all databases (`backup_db.sql`) 335 | 2. **Backup**: Backs up the SQL dump file along with other addon data (but excludes the PostgreSQL data directory) 336 | 3. **Post-Backup**: Removes the temporary SQL dump file to save space 337 | 338 | This approach has several advantages: 339 | 340 | - **Consistency**: SQL dumps are transaction-consistent snapshots 341 | - **Safety**: No risk of backing up corrupted or in-transition database files 342 | - **Portability**: SQL dumps can be restored across different PostgreSQL versions 343 | - **Small Size**: Excludes the large PostgreSQL data directory from backup 344 | 345 | ### How Restore Works 346 | 347 | When you restore a Home Assistant backup: 348 | 349 | 1. The addon starts up with the restored `backup_db.sql` file 350 | 2. If the PostgreSQL data directory is missing or corrupted, the addon: 351 | - Initializes a fresh PostgreSQL database 352 | - Automatically restores all data from the SQL dump 353 | - Removes the SQL dump after successful restoration 354 | 355 | This automatic recovery process ensures your data is safely restored even if: 356 | 357 | - The database files were corrupted 358 | - You're restoring to a different system 359 | - A PostgreSQL upgrade failed 360 | 361 | ### Manual Backup 362 | 363 | You can also create manual backups using the PostgreSQL command-line tools: 364 | 365 | **Create a backup:** 366 | 367 | ```bash 368 | docker exec addon_timescaledb_timescaledb su - postgres -c "pg_dumpall -U postgres --clean --if-exists -f /data/manual_backup_$(date +%Y%m%d).sql" 369 | ``` 370 | 371 | **Restore from a manual backup:** 372 | 373 | ```bash 374 | docker exec addon_timescaledb_timescaledb su - postgres -c "psql -U postgres -f /data/manual_backup_YYYYMMDD.sql -d postgres" 375 | ``` 376 | 377 | ### Important Notes 378 | 379 | - The SQL dump is only present during the backup process and is automatically cleaned up 380 | - If you need to keep a copy of the backup SQL file, copy it before the backup completes 381 | - The PostgreSQL data directory (`/data/postgres/*`) is excluded from backups to reduce backup size and improve reliability 382 | - Restore is automatic - no manual intervention required when restoring from a Home Assistant backup 383 | 384 | ### Troubleshooting 385 | 386 | **If restore fails:** 387 | 388 | 1. Check the addon logs for detailed error messages 389 | 2. The backup SQL file will be preserved at `/data/backup_db.sql` for manual recovery 390 | 3. You can attempt manual restoration using: 391 | 392 | ```bash 393 | docker exec -it addon_timescaledb_timescaledb su - postgres -c "psql -U postgres -f /data/backup_db.sql -d postgres" 394 | ``` 395 | 396 | **If backup fails:** 397 | 398 | - Check that PostgreSQL is running during the backup 399 | - Ensure there's sufficient disk space for the SQL dump 400 | - Review the addon logs for specific error messages 401 | 402 | ### Now what.. 403 | 404 | Well.. Dive in! 405 | 406 | You can read additional documentation on how you van work with your data and Grafana here: 407 | 408 | https://github.com/expaso/hassos-addons/issues/1 409 | --------------------------------------------------------------------------------