diff --git a/.github/actions/build/action.yaml b/.github/actions/build/action.yaml index 8f168dbfe4..6bf0d08299 100644 --- a/.github/actions/build/action.yaml +++ b/.github/actions/build/action.yaml @@ -4,7 +4,7 @@ inputs: is_dockerhub_pushed: description: "Need docker hub login?" required: true - default: '' + default: "" docker_password: description: "Docker password" required: true diff --git a/.github/actions/setup-elixir-and-cache-deps/action.yml b/.github/actions/setup-elixir-and-cache-deps/action.yml new file mode 100644 index 0000000000..8e4fea47f2 --- /dev/null +++ b/.github/actions/setup-elixir-and-cache-deps/action.yml @@ -0,0 +1,75 @@ +name: "Setup Elixir and Cache Dependencies" +description: "Setup Elixir, OTP and cache dependencies" +inputs: + elixir-version: + description: "Elixir version" + required: false + default: "1.16.2" + otp-version: + description: "OTP version" + required: false + default: "26" + cache-name-deps: + description: "Cache name for dependencies" + required: true + cache-name-compiled: + description: "Cache name for compiled build" + required: true + mix-env: + description: "Mix environment" + required: false + default: "dev" + ELIXIR_ASSERT_TIMEOUT: + description: "Elixir assert timeout" + required: false + default: "1000" +outputs: + elixir-version: + description: "The Elixir version used in the setup" + value: ${{ steps.beam.outputs.elixir-version }} + otp-version: + description: "The OTP version used in the setup" + value: ${{ steps.beam.outputs.otp-version }} +runs: + using: "composite" + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Setup Elixir and OTP + id: beam + uses: erlef/setup-beam@b9c58b0450cd832ccdb3c17cc156a47065d2114f # v1.18.1 + with: + elixir-version: ${{ inputs.elixir-version }} + otp-version: ${{ inputs.otp-version }} + + - name: Cache deps + id: cache-deps + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + path: deps + key: ${{ runner.os }}-mix-${{ inputs.cache-name-deps }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{ inputs.cache-name-deps }}- + + - name: Cache compiled build + id: cache-build + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + path: | + _build + priv/cldr/locales + key: ${{ runner.os }}-mix-${{ inputs.cache-name-compiled }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{ inputs.cache-name-compiled }}- + ${{ runner.os }}-mix- + + - name: Clean to rule out incremental build as a source of flakiness + if: github.run_attempt > 3 + run: | + mix deps.clean --all + mix clean + shell: sh + + - name: Install dependencies + run: mix deps.get + shell: sh diff --git a/.github/workflows/buildx.yml b/.github/workflows/buildx.yml index 50b8924ee8..de4c885263 100644 --- a/.github/workflows/buildx.yml +++ b/.github/workflows/buildx.yml @@ -2,6 +2,7 @@ name: Publish Docker images on: workflow_dispatch: + workflow_call: schedule: - cron: "0 3 * * *" push: @@ -10,7 +11,8 @@ on: paths: - "**/*" - "!.github/**" # Important: Exclude PRs related to .github from auto-run - - "!.github/workflows/**" # Important: Exclude PRs related to .github from auto-run + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run env: REGISTRY_IMAGE: teslamate/teslamate @@ -20,22 +22,11 @@ permissions: jobs: check_paths: - runs-on: ubuntu-latest - outputs: - githubfolder: ${{ steps.filter.outputs.githubfolder }} - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - id: filter - with: - filters: | - githubfolder: - - '.github/**' + uses: ./.github/workflows/check_paths.yml teslamate_build: needs: check_paths - if: needs.check_paths.outputs.githubfolder == 'false' || github.event_name == 'schedule' + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' strategy: fail-fast: false matrix: @@ -71,7 +62,7 @@ jobs: needs: - check_paths - teslamate_build - if: needs.check_paths.outputs.githubfolder == 'false' || github.event_name == 'schedule' + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' steps: - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 @@ -92,7 +83,7 @@ jobs: type=edge grafana: needs: check_paths - if: needs.check_paths.outputs.githubfolder == 'false' || github.event_name == 'schedule' + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' runs-on: ubuntu-latest timeout-minutes: 10 steps: diff --git a/.github/workflows/check_paths.yml b/.github/workflows/check_paths.yml new file mode 100644 index 0000000000..250bf7cf4c --- /dev/null +++ b/.github/workflows/check_paths.yml @@ -0,0 +1,41 @@ +name: Check paths + +on: + workflow_call: + # Map the workflow outputs to job outputs + outputs: + githubfolder: + description: "changes to .github folder" + value: ${{ jobs.check_paths.githubfolder }} + push: + paths: + - "**/*" + - "!.github/**" # Important: Exclude PRs related to .github from auto-run + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run + pull_request_target: + branches: ["master"] + paths: + - "**/*" + - "!.github/**" # Important: Exclude PRs related to .github from auto-run + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run + +permissions: + contents: read + +jobs: + check_paths: + runs-on: ubuntu-latest + outputs: + githubfolder: ${{ steps.filter.outputs.githubfolder }} + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + base: "master" # needed to set as a called workflow does not have direct access to repository.default_branch + filters: | + githubfolder: + - '.github/**' diff --git a/.github/workflows/devops.yml b/.github/workflows/devops.yml new file mode 100644 index 0000000000..3b4e575fb7 --- /dev/null +++ b/.github/workflows/devops.yml @@ -0,0 +1,58 @@ +name: DevOps + +on: + workflow_dispatch: + push: + paths: + - "**/*" + - "!.github/**" # Important: Exclude PRs related to .github from auto-run + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run + pull_request: + branches: ["master"] + paths: + - "**/*" + - "!.github/**" # Important: Exclude PRs related to .github from auto-run + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run + +permissions: + contents: read + packages: write + +jobs: + check_paths: + uses: ./.github/workflows/check_paths.yml + + spell_check: + needs: check_paths + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' + uses: ./.github/workflows/spell_check.yml + + ensure_linting: + needs: + - check_paths + - spell_check + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' + uses: ./.github/workflows/ensure_linting.yml + + elixir_dep_verification_and_static_analysis: + needs: + - check_paths + - ensure_linting + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' + uses: ./.github/workflows/elixir_dep_verification_and_static_analysis.yml + + elixir_test: + needs: + - check_paths + - ensure_linting + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' + uses: ./.github/workflows/elixir_test.yml + + ghcr_build: + needs: + - check_paths + - elixir_dep_verification_and_static_analysis + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' + uses: ./.github/workflows/ghcr_build.yml diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml deleted file mode 100644 index 6cbed2a3e7..0000000000 --- a/.github/workflows/elixir.yml +++ /dev/null @@ -1,198 +0,0 @@ -name: Elixir CI - -on: - push: - paths: - - "**/*" - - "!.github/**" # Important: Exclude PRs related to .github from auto-run - pull_request: - branches: ["master"] - paths: - - "**/*" - - "!.github/**" # Important: Exclude PRs related to .github from auto-run - -jobs: - lint: - name: Lint (Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }}) - runs-on: ubuntu-20.04 - - permissions: - contents: read - - strategy: - matrix: - include: - - elixir: "1.16.2" - otp: "26" - lint: true - - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - uses: erlef/setup-beam@b9c58b0450cd832ccdb3c17cc156a47065d2114f # v1.18.1 - id: beam - with: - otp-version: ${{ matrix.otp }} - elixir-version: ${{ matrix.elixir }} - - - name: Cache deps - id: cache-deps - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - env: - cache-name: cache-elixir-deps - with: - path: deps - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - - - name: Cache compiled build - id: cache-build - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - env: - cache-name: cache-compiled-dev-build - with: - path: | - _build - priv/cldr/locales - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - ${{ runner.os }}-mix- - - - name: Clean to rule out incremental build as a source of flakiness - if: github.run_attempt > 3 - run: | - mix deps.clean --all - mix clean - shell: sh - - - name: Restore PLT cache - id: plt_cache - uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - with: - key: | - ${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-plt - restore-keys: | - ${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-plt - path: | - priv/plts - - - name: Install dependencies - run: mix deps.get - - - name: Compile without warnings - run: mix compile --warnings-as-errors - - - name: Verify that POT files are up to date - run: mix gettext.extract --check-up-to-date - - - name: Spell check - uses: crate-ci/typos@c16dc8f5b4a7ad6211464ecf136c69c851e8e83c # v1.22.9 - - - name: Check formatting - run: mix format --check-formatted - - - name: Check unused dependencies - run: mix deps.unlock --check-unused - - - name: Create PLTs - if: steps.plt_cache.outputs.cache-hit != 'true' - run: mix dialyzer --plt - - - name: Save PLT cache - id: plt_cache_save - uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - if: steps.plt_cache.outputs.cache-hit != 'true' - with: - key: | - ${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-plt - path: | - priv/plts - - - name: Run dialyzer - run: mix dialyzer --format github - - test: - name: Test (Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }}) - runs-on: ubuntu-20.04 - - permissions: - contents: read - - strategy: - matrix: - include: - - elixir: "1.16.2" - otp: "26" - report_coverage: true - - services: - db: - image: postgres:17 - ports: ["5432:5432"] - env: - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - - env: - MIX_ENV: test - ELIXIR_ASSERT_TIMEOUT: 1000 - - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - uses: erlef/setup-beam@b9c58b0450cd832ccdb3c17cc156a47065d2114f # v1.18.1 - id: beam - with: - otp-version: ${{ matrix.otp }} - elixir-version: ${{ matrix.elixir }} - - - name: Cache deps - id: cache-deps - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - env: - cache-name: cache-elixir-deps - with: - path: deps - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - - - name: Cache compiled build - id: cache-build - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - env: - cache-name: cache-compiled-test-build - with: - path: | - _build - priv/cldr/locales - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - ${{ runner.os }}-mix- - - - name: Clean to rule out incremental build as a source of flakiness - if: github.run_attempt > 3 - run: | - mix deps.clean --all - mix clean - shell: sh - - - name: Install dependencies - run: mix deps.get - - - name: Compile - run: mix compile - - - name: Run tests - run: mix test --warnings-as-errors - - - name: Check Coverage - if: github.ref == 'refs/heads/master' && matrix.report_coverage - run: mix coveralls.github - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/elixir_dep_verification_and_static_analysis.yml b/.github/workflows/elixir_dep_verification_and_static_analysis.yml new file mode 100644 index 0000000000..47766f1e10 --- /dev/null +++ b/.github/workflows/elixir_dep_verification_and_static_analysis.yml @@ -0,0 +1,70 @@ +name: Elixir Dependency Verification and Static Analysis + +on: + workflow_call: + +env: + CACHE_NAME_DEPS: cache-elixir-deps + CACHE_NAME_COMPILED_DEV: cache-compiled-dev-build + CACHE_NAME_COMPILED_TEST: cache-compiled-test-build + ELIXIR_ASSERT_TIMEOUT: 1000 + +permissions: + contents: read + +jobs: + verify_dependencies_and_static_analysis: + name: Verify dependencies, POT files, unused dependencies, static analysis + runs-on: ubuntu-20.04 + + permissions: + contents: read + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Setup Elixir and Cache Dependencies + id: setup-elixir-and-cache-deps + uses: ./.github/actions/setup-elixir-and-cache-deps + with: + cache-name-deps: ${{ env.CACHE_NAME_DEPS }} + cache-name-compiled: ${{ env.CACHE_NAME_COMPILED_DEV }} + mix-env: dev + + - name: Compile without warnings + run: mix compile --warnings-as-errors + shell: sh + + - name: Verify that POT files are up to date + run: mix gettext.extract --check-up-to-date + + - name: Check unused dependencies + run: mix deps.unlock --check-unused + + - name: Restore PLT cache + id: plt_cache + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + key: | + ${{ runner.os }}-${{ steps.setup-elixir-and-cache-deps.outputs.elixir-version }}-${{ steps.setup-elixir-and-cache-deps.outputs.otp-version }}-plt + restore-keys: | + ${{ runner.os }}-${{ steps.setup-elixir-and-cache-deps.outputs.elixir-version }}-${{ steps.setup-elixir-and-cache-deps.outputs.otp-version }}-plt + path: | + priv/plts + + - name: Create Persistent Lookup Tables (PLTs) for Dialyzer + if: steps.plt_cache.outputs.cache-hit != 'true' + run: mix dialyzer --plt + + - name: Save PLT cache + id: plt_cache_save + uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + if: steps.plt_cache.outputs.cache-hit != 'true' + with: + key: | + ${{ runner.os }}-${{ steps.setup-elixir-and-cache-deps.outputs.elixir-version }}-${{ steps.setup-elixir-and-cache-deps.outputs.otp-version }}-plt + path: | + priv/plts + + - name: Run dialyzer for static analysis + run: mix dialyzer --format github diff --git a/.github/workflows/elixir_test.yml b/.github/workflows/elixir_test.yml new file mode 100644 index 0000000000..1c3b272573 --- /dev/null +++ b/.github/workflows/elixir_test.yml @@ -0,0 +1,55 @@ +name: Elixir Test and report coverage + +on: + workflow_call: + +env: + CACHE_NAME_DEPS: cache-elixir-deps + CACHE_NAME_COMPILED_TEST: cache-compiled-test-build + ELIXIR_ASSERT_TIMEOUT: 1000 + +permissions: + contents: read + +jobs: + test: + name: Test + runs-on: ubuntu-20.04 + + permissions: + contents: read + + services: + db: + image: postgres:17 + ports: ["5432:5432"] + env: + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Setup Elixir and Cache Dependencies + id: setup-elixir-and-cache-deps + uses: ./.github/actions/setup-elixir-and-cache-deps + with: + cache-name-deps: ${{ env.CACHE_NAME_DEPS }} + cache-name-compiled: ${{ env.CACHE_NAME_COMPILED_TEST }} + mix-env: test + ELIXIR_ASSERT_TIMEOUT: ${{ env.ELIXIR_ASSERT_TIMEOUT }} + + - name: Compile without warnings + run: mix compile --warnings-as-errors + shell: sh + + - name: Run tests + run: mix test --warnings-as-errors + + - name: Check Coverage + if: github.ref == 'refs/heads/master' + run: mix coveralls.github + continue-on-error: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ensure_linting.yml b/.github/workflows/ensure_linting.yml new file mode 100644 index 0000000000..d067b3c785 --- /dev/null +++ b/.github/workflows/ensure_linting.yml @@ -0,0 +1,25 @@ +name: Ensure Linting + +on: + workflow_call: + +permissions: + contents: read + +jobs: + check_linting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Install Nix + uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b #v27 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Nix binary cache + uses: DeterminateSystems/magic-nix-cache-action@87b14cf437d03d37989d87f0fa5ce4f5dc1a330b #v8 + + - name: Run treefmt in CI mode + run: nix develop --override-input devenv-root "file+file://"<(printf %s "$PWD") . --command treefmt --ci + # or use: https://github.com/cachix/git-hooks.nix?tab=readme-ov-file diff --git a/.github/workflows/ghcr_build.yml b/.github/workflows/ghcr_build.yml index 67e74ed70c..e80a2a0d3a 100644 --- a/.github/workflows/ghcr_build.yml +++ b/.github/workflows/ghcr_build.yml @@ -1,19 +1,14 @@ name: Build GHCR images on: - workflow_dispatch: - push: - paths: - - "**/*" - - "!.github/**" # Important: Exclude PRs related to .github from auto-run - - "!.github/workflows/**" # Important: Exclude PRs related to .github from auto-run - branches: ["ci"] + workflow_call: pull_request_target: - branches: ["master", "ci"] + branches: ["master"] paths: - "**/*" - "!.github/**" # Important: Exclude PRs related to .github from auto-run - - "!.github/workflows/**" # Important: Exclude PRs related to .github from auto-run + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run env: REGISTRY_IMAGE: ghcr.io/${{ github.repository }} @@ -24,23 +19,14 @@ permissions: jobs: check_paths: - runs-on: ubuntu-latest - outputs: - githubfolder: ${{ steps.filter.outputs.githubfolder }} - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - id: filter - with: - filters: | - githubfolder: - - '.github/**' + if: ${{ github.event_name != 'workflow_call' }} + uses: ./.github/workflows/check_paths.yml teslamate_build: name: Build images needs: check_paths - if: needs.check_paths.outputs.githubfolder == 'false' + # Only run if there are no changes to the github folder and on workflow call, or PRs from forks (to avoid duplicate runs for PRs from non-forks, and to avoid invalid reference format for cache name in PRs from our repo) + if: needs.check_paths.outputs.githubfolder != 'true' && (${{ github.event_name }} == 'workflow_call' || ${{ github.event.pull_request.head.repo.full_name }} != ${{ github.repository }}) strategy: fail-fast: false matrix: @@ -73,7 +59,7 @@ jobs: repository_owner: ${{ github.repository_owner }} repository: ${{ github.repository }} github_token: ${{ secrets.GITHUB_TOKEN }} - version: ${{ github.ref_name }} + version: ${{ github.head_ref || github.ref_name }} labels: | org.opencontainers.image.version=${{ github.ref || github.ref_name }} @@ -82,7 +68,7 @@ jobs: needs: - check_paths - teslamate_build - if: needs.check_paths.outputs.githubfolder == 'false' + if: needs.check_paths.outputs.githubfolder != 'true' runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -102,7 +88,8 @@ jobs: grafana: needs: check_paths - if: needs.check_paths.outputs.githubfolder == 'false' + # Only run if there are no changes to the github folder and on workflow call, or PRs from forks (to avoid duplicate runs for PRs from non-forks, and to avoid invalid reference format for cache name in PRs from our repo) + if: needs.check_paths.outputs.githubfolder != 'true' && (${{ github.event_name }} == 'workflow_call' || ${{ github.event.pull_request.head.repo.full_name }} != ${{ github.repository }}) runs-on: ubuntu-latest steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 diff --git a/.github/workflows/ghcr_purge.yml b/.github/workflows/ghcr_purge.yml index 6a9df09a59..73970a9bdd 100644 --- a/.github/workflows/ghcr_purge.yml +++ b/.github/workflows/ghcr_purge.yml @@ -1,14 +1,15 @@ name: Purge PR images on: + workflow_call: pull_request_target: types: - closed paths: - "**/*" - "!.github/**" # Important: Exclude PRs related to .github from auto-run - - "!.github/workflows/**" # Important: Exclude PRs related to .github from auto-run - + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run permissions: contents: read @@ -16,23 +17,12 @@ permissions: jobs: check_paths: - runs-on: ubuntu-latest - outputs: - githubfolder: ${{ steps.filter.outputs.githubfolder }} - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - id: filter - with: - filters: | - githubfolder: - - '.github/**' + uses: ./.github/workflows/check_paths.yml purge-pr-package: name: Delete images from ghcr.io when PR is closed needs: check_paths - if: needs.check_paths.outputs.githubfolder == 'false' + if: needs.check_paths.outputs.githubfolder != 'true' runs-on: ubuntu-latest steps: diff --git a/.github/workflows/spell_check.yml b/.github/workflows/spell_check.yml new file mode 100644 index 0000000000..cc836caca6 --- /dev/null +++ b/.github/workflows/spell_check.yml @@ -0,0 +1,18 @@ +name: Spell check + +on: + workflow_call: + +permissions: + contents: read + +jobs: + spell_check: + name: Spell check + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Spell check + uses: crate-ci/typos@c16dc8f5b4a7ad6211464ecf136c69c851e8e83c # v1.22.9 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 0da7ce65f4..c411c880ea 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,4 +1,4 @@ -name: 'Close Stale Issues and Pull Requests' +name: "Close Stale Issues and Pull Requests" on: schedule: @@ -26,5 +26,5 @@ jobs: days-before-close: 7 days-before-pr-close: -1 exempt-all-pr-milestones: true - exempt-issue-labels: 'area:grafana, area:tesla api, enhancement, kind:bug, kind:documentation, kind:idea, note:needs investigation, security, pinned' - exempt-pr-labels: 'awaiting-approval, area:grafana, area:tesla api, enhancement, kind:bug, kind:documentation, kind:idea, note:needs investigation, security, pinned' + exempt-issue-labels: "area:grafana, area:tesla api, enhancement, kind:bug, kind:documentation, kind:idea, note:needs investigation, security, pinned" + exempt-pr-labels: "awaiting-approval, area:grafana, area:tesla api, enhancement, kind:bug, kind:documentation, kind:idea, note:needs investigation, security, pinned" diff --git a/.markdownlint.yaml b/.markdownlint.yaml index d877b77a70..8d12b3a1c8 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -3,4 +3,4 @@ default: true MD013: line_length: 500 MD024: - siblings_only: true # heading duplication is allowed for non-sibling headings (common in changelogs) + siblings_only: true # heading duplication is allowed for non-sibling headings (common in changelogs) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ad092d964..faacb8c4ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - feat: PostgreSQL 17 (#4231 - @swiffer) - fix: add nix module option to specify postgres package (#4227 - @brianmay) - perf: limit positions to set elevation for to last 10 days (#4228 - @swiffer) +- feat: add treefmt-nix to nix flake (#4219 - @JakobLichterfeld) #### Build, CI, internal @@ -27,6 +28,18 @@ - build(deps): bump phoenix_ecto from 4.4.3 to 4.6.2 (#4213) - build(deps): bump jason from 1.4.1 to 1.4.4 (#4216) - build(deps): bump classnames from 2.3.2 to 2.5.1 in /website (#4211) +- ci: add treefmt as code formatting multiplexer (#4219 - @JakobLichterfeld) +- ci(refactor): use composite action to avoid duplication in elixir workflow (#4219 - @JakobLichterfeld) +- ci: prevent workflow runs for certain conditions and allow scheduled runs (#4219 - @JakobLichterfeld) +- ci(refactor): use reusable workflow to check paths (#4219 - @JakobLichterfeld) +- ci(refactor): use reusable workflows for streamlined DevOps pipeline (#4219 - @JakobLichterfeld) +- ci(refactor): allow ghcr_build parallel to elixir test (#4219 - @JakobLichterfeld) +- ci: ensure proper linting via treefmt (#4219 - @JakobLichterfeld) +- doc: update CI badge URL for devops workflow (#4219 - @JakobLichterfeld) +- ci(fix): handle empty path filter output (#4219 - @JakobLichterfeld) +- fix: avoid the need for impure for devenv (#4245 - @brianmay) +- ci(fix): run ghcr build workflow only for specific conditions (#4219 - @JakobLichterfeld) +- ci: remove branch restriction for check_paths workflow to increase sec (#4219 - @JakobLichterfeld) #### Dashboards diff --git a/README.md b/README.md index 2956b980b5..f0bc5acdaf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TeslaMate -[![CI](https://github.com/teslamate-org/teslamate/actions/workflows/elixir.yml/badge.svg)](https://github.com/teslamate-org/teslamate/actions/workflows/elixir.yml) +[![CI](https://github.com/teslamate-org/teslamate/actions/workflows/devops.yml/badge.svg)](https://github.com/teslamate-org/teslamate/actions/workflows/devops.yml) [![Publish Docker images](https://github.com/teslamate-org/teslamate/actions/workflows/buildx.yml/badge.svg)](https://github.com/teslamate-org/teslamate/actions/workflows/buildx.yml) [![](https://coveralls.io/repos/github/teslamate-org/teslamate/badge.svg?branch=master)](https://coveralls.io/github/teslamate-org/teslamate?branch=master) [![](https://img.shields.io/docker/v/teslamate/teslamate/latest)](https://hub.docker.com/r/teslamate/teslamate) diff --git a/assets/js/hooks.js b/assets/js/hooks.js index 0eeca83f79..5cdae1d0c5 100644 --- a/assets/js/hooks.js +++ b/assets/js/hooks.js @@ -53,7 +53,11 @@ export const LocalTimeRange = { const time = [this.el.dataset.startDate, this.el.dataset.endDate] .map((date) => - toLocalTime(date, { hour: "2-digit", minute: "2-digit", hour12: false }) + toLocalTime(date, { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }), ) .join(" – "); @@ -125,7 +129,7 @@ const DirectionArrow = CircleMarker.extend({ this.getElement().setAttributeNS( null, "transform", - `translate(${x},${y}) rotate(${this._heading})` + `translate(${x},${y}) rotate(${this._heading})`, ); const path = this._empty() ? "" : `M0,${3} L-4,${5} L0,${-5} L4,${5} z}`; @@ -137,15 +141,14 @@ const DirectionArrow = CircleMarker.extend({ function createMap(opts) { const map = new M(opts.elId != null ? `map_${opts.elId}` : "map", opts); - const osm = new TileLayer( - "https://tile.openstreetmap.org/{z}/{x}/{y}.png", - { maxZoom: 19 } - ); + const osm = new TileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { + maxZoom: 19, + }); if (opts.enableHybridLayer) { const hybrid = new TileLayer( "http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}", - { maxZoom: 20, subdomains: ["mt0", "mt1", "mt2", "mt3"] } + { maxZoom: 20, subdomains: ["mt0", "mt1", "mt2", "mt3"] }, ); new Control.Layers({ OSM: osm, Hybrid: hybrid }).addTo(map); @@ -184,8 +187,12 @@ export const SimpleMap = { map.removeControl(map.zoomControl); - map.on('mouseover', function(e) { map.addControl( map.zoomControl ); }); - map.on('mouseout', function(e) { map.removeControl( map.zoomControl ); }); + map.on("mouseover", function (e) { + map.addControl(map.zoomControl); + }); + map.on("mouseout", function (e) { + map.removeControl(map.zoomControl); + }); if (isArrow) { const setView = () => { diff --git a/assets/js/main.js b/assets/js/main.js index 744f966118..95af7d442a 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -5,7 +5,7 @@ document.querySelector(".navbar-burger").addEventListener("click", function () { }); for (const navDropdown of document.querySelectorAll( - ".navbar-item.has-dropdown" + ".navbar-item.has-dropdown", )) { navDropdown.addEventListener("click", function () { if (document.querySelector(".navbar-menu.is-active")) { diff --git a/assets/scripts/build.js b/assets/scripts/build.js index 86a42678b4..41a40d64f8 100644 --- a/assets/scripts/build.js +++ b/assets/scripts/build.js @@ -17,7 +17,8 @@ const buildLogger = { let count = 0; build.onEnd(({ errors, warnings }) => { if (errors.length > 0) console.error("[-] Esbuild failed:", errors); - else if (warnings.length > 0) console.warn("[-] Esbuild finished with warnings:", warnings); + else if (warnings.length > 0) + console.warn("[-] Esbuild finished with warnings:", warnings); else console.log(`[+] Esbuild succeeded`); }); }, diff --git a/coveralls.json b/coveralls.json index ce5213a486..ba07580c59 100644 --- a/coveralls.json +++ b/coveralls.json @@ -17,4 +17,3 @@ "lib/teslamate_web.ex" ] } - diff --git a/entrypoint.sh b/entrypoint.sh index cf8b4934be..8efe6dec63 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -5,9 +5,9 @@ set -e : "${DATABASE_PORT:=5432}" # wait until Postgres is ready -while ! nc -z ${DATABASE_HOST} ${DATABASE_PORT} 2>/dev/null; do - echo waiting for postgres at ${DATABASE_HOST}:${DATABASE_PORT} - sleep 1s +while ! nc -z "${DATABASE_HOST}" "${DATABASE_PORT}" 2>/dev/null; do + echo waiting for postgres at "${DATABASE_HOST}":"${DATABASE_PORT}" + sleep 1s done # apply migrations diff --git a/flake.lock b/flake.lock index d448d11a62..f5c78d168e 100644 --- a/flake.lock +++ b/flake.lock @@ -3,19 +3,61 @@ "cachix": { "inputs": { "devenv": "devenv_2", - "flake-compat": "flake-compat_2", + "flake-compat": [ + "devenv", + "flake-compat" + ], + "git-hooks": [ + "devenv", + "pre-commit-hooks" + ], "nixpkgs": [ "devenv", "nixpkgs" + ] + }, + "locked": { + "lastModified": 1726520618, + "narHash": "sha256-jOsaBmJ/EtX5t/vbylCdS7pWYcKGmWOKg4QKUzKr6dA=", + "owner": "cachix", + "repo": "cachix", + "rev": "695525f9086542dfb09fde0871dbf4174abbf634", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "cachix", + "type": "github" + } + }, + "cachix_2": { + "inputs": { + "devenv": "devenv_3", + "flake-compat": [ + "devenv", + "cachix", + "devenv", + "flake-compat" ], - "pre-commit-hooks": "pre-commit-hooks" + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "nixpkgs" + ], + "pre-commit-hooks": [ + "devenv", + "cachix", + "devenv", + "pre-commit-hooks" + ] }, "locked": { - "lastModified": 1710475558, - "narHash": "sha256-egKrPCKjy/cE+NqCj4hg2fNX/NwLCf0bRDInraYXDgs=", + "lastModified": 1712055811, + "narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=", "owner": "cachix", "repo": "cachix", - "rev": "661bbb7f8b55722a0406456b15267b5426a3bda6", + "rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30", "type": "github" }, "original": { @@ -27,22 +69,23 @@ "devenv": { "inputs": { "cachix": "cachix", - "flake-compat": "flake-compat_4", - "nix": "nix_2", - "nixpkgs": "nixpkgs_2", + "flake-compat": "flake-compat_2", + "nix": "nix_3", + "nixpkgs": "nixpkgs_3", "pre-commit-hooks": "pre-commit-hooks_2" }, "locked": { - "lastModified": 1711019207, - "narHash": "sha256-9LnGe0KWqXj18IV+A1panzXQuTamrH/QcasaqnuqiE0=", + "lastModified": 1728305966, + "narHash": "sha256-UIYz+bFftmXqLPOphFEkAzhDerErxN7wIDR1rM/LyB4=", "owner": "cachix", "repo": "devenv", - "rev": "a7ba6766c0cc351b8656c63560c6b19c3788455f", + "rev": "fed89fff44ccbc73f91d69ca326ac241baeb1726", "type": "github" }, "original": { "owner": "cachix", "repo": "devenv", + "rev": "fed89fff44ccbc73f91d69ca326ac241baeb1726", "type": "github" } }, @@ -60,15 +103,53 @@ }, "devenv_2": { "inputs": { + "cachix": "cachix_2", "flake-compat": [ "devenv", "cachix", "flake-compat" ], + "nix": "nix_2", + "nixpkgs": [ + "devenv", + "cachix", + "nixpkgs" + ], + "pre-commit-hooks": [ + "devenv", + "cachix", + "git-hooks" + ] + }, + "locked": { + "lastModified": 1723156315, + "narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=", + "owner": "cachix", + "repo": "devenv", + "rev": "ff5eb4f2accbcda963af67f1a1159e3f6c7f5f91", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "devenv_3": { + "inputs": { + "flake-compat": [ + "devenv", + "cachix", + "devenv", + "cachix", + "flake-compat" + ], "nix": "nix", "nixpkgs": "nixpkgs", "poetry2nix": "poetry2nix", "pre-commit-hooks": [ + "devenv", + "cachix", "devenv", "cachix", "pre-commit-hooks" @@ -121,51 +202,45 @@ "type": "github" } }, - "flake-compat_3": { - "flake": false, - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "type": "github" + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "devenv", + "nix", + "nixpkgs" + ] }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-compat_4": { - "flake": false, "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "lastModified": 1712014858, + "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", "type": "github" }, "original": { - "owner": "edolstra", - "repo": "flake-compat", + "owner": "hercules-ci", + "repo": "flake-parts", "type": "github" } }, - "flake-compat_5": { - "flake": false, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "lastModified": 1727826117, + "narHash": "sha256-K5ZLCyfO/Zj9mPFldf3iwS6oZStJcU4tSpiXTMYaaL0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "3d04084d54bedc3d6b8b736c70ef449225c361b1", "type": "github" }, "original": { - "owner": "edolstra", - "repo": "flake-compat", + "owner": "hercules-ci", + "repo": "flake-parts", "type": "github" } }, @@ -188,24 +263,6 @@ } }, "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_3": { "locked": { "lastModified": 1667395993, "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", @@ -220,39 +277,20 @@ "type": "github" } }, - "flake-utils_4": { - "inputs": { - "systems": "systems_3" - }, - "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "gitignore": { "inputs": { "nixpkgs": [ "devenv", - "cachix", "pre-commit-hooks", "nixpkgs" ] }, "locked": { - "lastModified": 1703887061, - "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=", + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", "type": "github" }, "original": { @@ -261,25 +299,19 @@ "type": "github" } }, - "gitignore_2": { - "inputs": { - "nixpkgs": [ - "devenv", - "pre-commit-hooks", - "nixpkgs" - ] - }, + "libgit2": { + "flake": false, "locked": { - "lastModified": 1660459072, - "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "lastModified": 1697646580, + "narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=", + "owner": "libgit2", + "repo": "libgit2", + "rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5", "type": "github" }, "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", + "owner": "libgit2", + "repo": "libgit2", "type": "github" } }, @@ -287,6 +319,8 @@ "inputs": { "flake-compat": "flake-compat", "nixpkgs": [ + "devenv", + "cachix", "devenv", "cachix", "devenv", @@ -295,11 +329,11 @@ "nixpkgs-regression": "nixpkgs-regression" }, "locked": { - "lastModified": 1708577783, - "narHash": "sha256-92xq7eXlxIT5zFNccLpjiP7sdQqQI30Gyui2p/PfKZM=", + "lastModified": 1712911606, + "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", "owner": "domenkozar", "repo": "nix", - "rev": "ecd0af0c1f56de32cbad14daa1d82a132bf298f8", + "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", "type": "github" }, "original": { @@ -312,6 +346,8 @@ "nix-github-actions": { "inputs": { "nixpkgs": [ + "devenv", + "cachix", "devenv", "cachix", "devenv", @@ -335,19 +371,26 @@ }, "nix_2": { "inputs": { - "flake-compat": "flake-compat_5", + "flake-compat": [ + "devenv", + "cachix", + "devenv", + "flake-compat" + ], "nixpkgs": [ + "devenv", + "cachix", "devenv", "nixpkgs" ], "nixpkgs-regression": "nixpkgs-regression_2" }, "locked": { - "lastModified": 1710500156, - "narHash": "sha256-zvCqeUO2GLOm7jnU23G4EzTZR7eylcJN+HJ5svjmubI=", + "lastModified": 1712911606, + "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", "owner": "domenkozar", "repo": "nix", - "rev": "c5bbf14ecbd692eeabf4184cc8d50f79c2446549", + "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", "type": "github" }, "original": { @@ -357,6 +400,34 @@ "type": "github" } }, + "nix_3": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-parts": "flake-parts", + "libgit2": "libgit2", + "nixpkgs": "nixpkgs_2", + "nixpkgs-23-11": "nixpkgs-23-11", + "nixpkgs-regression": "nixpkgs-regression_3", + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1727438425, + "narHash": "sha256-X8ES7I1cfNhR9oKp06F6ir4Np70WGZU5sfCOuNBEwMg=", + "owner": "domenkozar", + "repo": "nix", + "rev": "f6c5ae4c1b2e411e6b1e6a8181cc84363d6a7546", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "devenv-2.24", + "repo": "nix", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1692808169, @@ -373,6 +444,22 @@ "type": "github" } }, + "nixpkgs-23-11": { + "locked": { + "lastModified": 1717159533, + "narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", + "type": "github" + } + }, "nixpkgs-regression": { "locked": { "lastModified": 1643052045, @@ -405,45 +492,61 @@ "type": "github" } }, - "nixpkgs-stable": { + "nixpkgs-regression_3": { "locked": { - "lastModified": 1704874635, - "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.11", "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", "type": "github" } }, - "nixpkgs-stable_2": { + "nixpkgs-stable": { "locked": { - "lastModified": 1678872516, - "narHash": "sha256-/E1YwtMtFAu2KUQKV/1+KFuReYPANM2Rzehk84VxVoc=", + "lastModified": 1720386169, + "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9b8e5abb18324c7fe9f07cb100c3cd4a29cda8b8", + "rev": "194846768975b7ad2c4988bdb82572c00222c0d7", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-22.11", + "ref": "nixos-24.05", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1710796454, - "narHash": "sha256-lQlICw60RhH8sHTDD/tJiiJrlAfNn8FDI9c+7G2F0SE=", + "lastModified": 1717432640, + "narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "88269ab3044128b7c2f4c7d68448b2fb50456870", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "release-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1716977621, + "narHash": "sha256-Q1UQzYcMJH4RscmpTkjlgqQDX5yi1tZL0O345Ri6vXQ=", "owner": "cachix", "repo": "devenv-nixpkgs", - "rev": "06fb0f1c643aee3ae6838dda3b37ef0abc3c763b", + "rev": "4267e705586473d3e5c8d50299e71503f16a6fb6", "type": "github" }, "original": { @@ -453,13 +556,13 @@ "type": "github" } }, - "nixpkgs_3": { + "nixpkgs_4": { "locked": { - "lastModified": 1707092692, - "narHash": "sha256-ZbHsm+mGk/izkWtT4xwwqz38fdlwu7nUUKXTOmm4SyE=", + "lastModified": 1728018373, + "narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "faf912b086576fd1a15fca610166c98d47bc667e", + "rev": "bc947f541ae55e999ffdb4013441347d83b00feb", "type": "github" }, "original": { @@ -474,6 +577,8 @@ "flake-utils": "flake-utils", "nix-github-actions": "nix-github-actions", "nixpkgs": [ + "devenv", + "cachix", "devenv", "cachix", "devenv", @@ -496,22 +601,32 @@ }, "pre-commit-hooks": { "inputs": { - "flake-compat": "flake-compat_3", + "flake-compat": [ + "devenv", + "nix" + ], "flake-utils": "flake-utils_2", - "gitignore": "gitignore", + "gitignore": [ + "devenv", + "nix" + ], "nixpkgs": [ "devenv", - "cachix", + "nix", "nixpkgs" ], - "nixpkgs-stable": "nixpkgs-stable" + "nixpkgs-stable": [ + "devenv", + "nix", + "nixpkgs" + ] }, "locked": { - "lastModified": 1708018599, - "narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=", + "lastModified": 1712897695, + "narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431", + "rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8", "type": "github" }, "original": { @@ -526,20 +641,19 @@ "devenv", "flake-compat" ], - "flake-utils": "flake-utils_3", - "gitignore": "gitignore_2", + "gitignore": "gitignore", "nixpkgs": [ "devenv", "nixpkgs" ], - "nixpkgs-stable": "nixpkgs-stable_2" + "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1686050334, - "narHash": "sha256-R0mczWjDzBpIvM3XXhO908X5e2CQqjyh/gFbwZk/7/Q=", + "lastModified": 1726745158, + "narHash": "sha256-D5AegvGoEjt4rkKedmxlSEmC+nNLMBPWFxvmYnVLhjk=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "6881eb2ae5d8a3516e34714e7a90d9d95914c4dc", + "rev": "4e743a6920eab45e8ba0fbe49dc459f1423a4b74", "type": "github" }, "original": { @@ -552,8 +666,9 @@ "inputs": { "devenv": "devenv", "devenv-root": "devenv-root", - "flake-utils": "flake-utils_4", - "nixpkgs": "nixpkgs_3" + "flake-parts": "flake-parts_2", + "nixpkgs": "nixpkgs_4", + "treefmt-nix": "treefmt-nix" } }, "systems": { @@ -571,33 +686,23 @@ "type": "github" } }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_3": { "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "lastModified": 1727984844, + "narHash": "sha256-xpRqITAoD8rHlXQafYZOLvUXCF6cnZkPfoq67ThN0Hc=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "4446c7a6fc0775df028c5a3f6727945ba8400e64", "type": "github" }, "original": { - "owner": "nix-systems", - "repo": "default", + "owner": "numtide", + "repo": "treefmt-nix", "type": "github" } } diff --git a/flake.nix b/flake.nix index 99ed82929f..c2f7909fa3 100644 --- a/flake.nix +++ b/flake.nix @@ -2,204 +2,269 @@ description = "TeslaMate Logger"; inputs = { - nixpkgs = { url = "github:NixOS/nixpkgs/nixos-unstable"; }; - flake-utils = { url = "github:numtide/flake-utils"; }; - devenv.url = "github:cachix/devenv"; - devenv-root = { - url = "file+file:///dev/null"; - flake = false; - }; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; + devenv.url = "github:cachix/devenv/fed89fff44ccbc73f91d69ca326ac241baeb1726"; # https://github.com/cachix/devenv/issues/1497 + devenv-root.url = "file+file:///dev/null"; + devenv-root.flake = false; + treefmt-nix.url = "github:numtide/treefmt-nix"; + treefmt-nix.inputs.nixpkgs.follows = "nixpkgs"; + }; - outputs = inputs@{ self, nixpkgs, flake-utils, devenv, devenv-root }: - (flake-utils.lib.eachDefaultSystem (system: - let - inherit (pkgs.lib) optional optionals; - pkgs = nixpkgs.legacyPackages.${system}; - - elixir = pkgs.beam.packages.erlang.elixir_1_16; - beamPackages = pkgs.beam.packagesWith pkgs.beam.interpreters.erlang; - - src = ./.; - version = builtins.readFile ./VERSION; - pname = "teslamate"; - - mixFodDeps = beamPackages.fetchMixDeps { - TOP_SRC = src; - pname = "${pname}-mix-deps"; - inherit src version; - hash = "sha256-ukE7PKrsRaw6TWDSqTJljsPJQedi39mZgGFrMINEJlw="; - # hash = pkgs.lib.fakeHash; - }; + outputs = + inputs@{ self + , flake-parts + , devenv + , devenv-root + , ... + }: + flake-parts.lib.mkFlake { inherit inputs; } { - nodejs = pkgs.nodejs; - nodePackages = pkgs.buildNpmPackage { - name = "teslamate"; - src = ./assets; - npmDepsHash = "sha256-05AKPyms4WP8MHBqWMup8VXR3a1tv/f/7jT8c6EpWBw="; - # npmDepsHash = pkgs.lib.fakeHash; - dontNpmBuild = true; - inherit nodejs; + flake.nixosModules.default = import ./module.nix { inherit self; }; - installPhase = '' - mkdir $out - cp -r node_modules $out - ln -s $out/node_modules/.bin $out/bin + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + # See ./nix/modules/*.nix for the modules that are imported here. + imports = [ + inputs.devenv.flakeModule + ./nix/flake-modules/formatter.nix + ]; - rm $out/node_modules/phoenix - ln -s ${mixFodDeps}/phoenix $out/node_modules + perSystem = + { config + , self' + , inputs' + , pkgs + , system + , ... + }: + # legacy + let + inherit (pkgs.lib) optional optionals; + nixpkgs = inputs.nixpkgs; + pkgs = nixpkgs.legacyPackages.${system}; - rm $out/node_modules/phoenix_html - ln -s ${mixFodDeps}/phoenix_html $out/node_modules + elixir = pkgs.beam.packages.erlang_26.elixir_1_16; + beamPackages = pkgs.beam.packagesWith pkgs.beam.interpreters.erlang_26; - rm $out/node_modules/phoenix_live_view - ln -s ${mixFodDeps}/phoenix_live_view $out/node_modules - ''; - }; + src = ./.; + version = builtins.readFile ./VERSION; + pname = "teslamate"; - cldr = pkgs.fetchFromGitHub { - owner = "elixir-cldr"; - repo = "cldr"; - rev = "v2.37.5"; - sha256 = "sha256-T5Qvuo+xPwpgBsqHNZYnTCA4loToeBn1LKTMsDcCdYs="; - # sha256 = pkgs.lib.fakeHash; - }; + mixFodDeps = beamPackages.fetchMixDeps { + TOP_SRC = src; + pname = "${pname}-mix-deps"; + inherit src version; + hash = "sha256-Y+CGgvnSCiiuyhtsQ+j0vayq1IHO5IEPVl+V/wwTd6w="; + # hash = pkgs.lib.fakeHash; + }; - pkg = beamPackages.mixRelease { - TOP_SRC = src; - inherit pname version elixir src mixFodDeps; + nodejs = pkgs.nodejs; + nodePackages = pkgs.buildNpmPackage { + name = "teslamate"; + src = ./assets; + npmDepsHash = "sha256-05AKPyms4WP8MHBqWMup8VXR3a1tv/f/7jT8c6EpWBw="; + # npmDepsHash = pkgs.lib.fakeHash; + dontNpmBuild = true; + inherit nodejs; - LOCALES = "${cldr}/priv/cldr"; + installPhase = '' + mkdir $out + cp -r node_modules $out + ln -s $out/node_modules/.bin $out/bin - postBuild = '' - ln -sf ${mixFodDeps}/deps deps - ln -sf ${nodePackages}/node_modules assets/node_modules - export PATH="${pkgs.nodejs}/bin:${nodePackages}/bin:$PATH" - ${nodejs}/bin/npm run deploy --prefix ./assets + rm $out/node_modules/phoenix + ln -s ${mixFodDeps}/phoenix $out/node_modules - # for external task you need a workaround for the no deps check flag - # https://github.com/phoenixframework/phoenix/issues/2690 - mix do deps.loadpaths --no-deps-check, phx.digest - mix phx.digest --no-deps-check - ''; + rm $out/node_modules/phoenix_html + ln -s ${mixFodDeps}/phoenix_html $out/node_modules - meta = { - mainProgram = "teslamate"; + rm $out/node_modules/phoenix_live_view + ln -s ${mixFodDeps}/phoenix_live_view $out/node_modules + ''; }; - }; - postgres_port = 7000; - mosquitto_port = 7001; - process_compose_port = 7002; + cldr = pkgs.fetchFromGitHub { + owner = "elixir-cldr"; + repo = "cldr"; + rev = "v2.37.5"; + sha256 = "sha256-T5Qvuo+xPwpgBsqHNZYnTCA4loToeBn1LKTMsDcCdYs="; + # sha256 = pkgs.lib.fakeHash; + }; - psql = pkgs.writeShellScriptBin "teslamate_psql" '' - exec "${pkgs.postgresql}/bin/psql" --host "$DATABASE_HOST" --user "$DATABASE_USER" --port "$DATABASE_PORT" "$DATABASE_NAME" "$@" - ''; - mosquitto_sub = pkgs.writeShellScriptBin "teslamate_sub" '' - exec "${pkgs.mosquitto}/bin/mosquitto_sub" -h "$MQTT_HOST" -p "$MQTT_PORT" -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" "$@" - ''; + pkg = beamPackages.mixRelease { + TOP_SRC = src; + inherit + pname + version + elixir + src + mixFodDeps + ; - devShell = devenv.lib.mkShell { - inherit inputs pkgs; - modules = with pkgs; [{ + LOCALES = "${cldr}/priv/cldr"; - devenv.root = - let - devenvRootFileContent = builtins.readFile devenv-root.outPath; - in - pkgs.lib.mkIf (devenvRootFileContent != "") devenvRootFileContent; + postBuild = '' + ln -sf ${mixFodDeps}/deps deps + ln -sf ${nodePackages}/node_modules assets/node_modules + export PATH="${pkgs.nodejs}/bin:${nodePackages}/bin:$PATH" + ${nodejs}/bin/npm run deploy --prefix ./assets - packages = [ - elixir - elixir_ls - glibcLocales - node2nix - nodejs - prefetch-npm-deps - # for dashboard scripts - jq - psql - mosquitto - mosquitto_sub - ] ++ optional stdenv.isLinux inotify-tools - ++ optional stdenv.isDarwin terminal-notifier - ++ optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [ - CoreFoundation - CoreServices - ]); - enterShell = '' - export LOCALES="${cldr}/priv/cldr"; - export PORT="4000" - export ENCRYPTION_KEY="your_secure_encryption_key_here" - export DATABASE_USER="teslamate" - export DATABASE_PASS="your_secure_password_here" - export DATABASE_NAME="teslamate" - export DATABASE_HOST="127.0.0.1" - export DATABASE_PORT="${toString postgres_port}" - export MQTT_HOST="127.0.0.1" - export MQTT_PORT="${toString mosquitto_port}" - export RELEASE_COOKIE="1234567890123456789" - export TZDATA_DIR="$PWD/tzdata" + # for external task you need a workaround for the no deps check flag + # https://github.com/phoenixframework/phoenix/issues/2690 + mix do deps.loadpaths --no-deps-check, phx.digest + mix phx.digest --no-deps-check ''; - enterTest = '' - mix test - ''; - processes.mqtt = { - exec = - "${pkgs.mosquitto}/bin/mosquitto -p ${toString mosquitto_port}"; - }; - process.process-compose = { - port = process_compose_port; - tui = true; - }; - services.postgres = { - enable = true; - package = pkgs.postgresql_16; - listen_addresses = "127.0.0.1"; - port = postgres_port; - initialDatabases = [{ name = "teslamate"; }]; - initialScript = '' - CREATE USER teslamate with encrypted password 'your_secure_password_here'; - GRANT ALL PRIVILEGES ON DATABASE teslamate TO teslamate; - ALTER USER teslamate WITH SUPERUSER; - ''; + + meta = { + mainProgram = "teslamate"; }; - }]; + }; - }; + postgres_port = 7000; + mosquitto_port = 7001; + process_compose_port = 7002; - moduleTest = (nixpkgs.lib.nixos.runTest { - hostPkgs = pkgs; - defaults.documentation.enable = false; - imports = [{ - name = "teslamate"; - nodes.server = { - imports = [ self.nixosModules.default ]; - services.teslamate = { - enable = true; - secretsFile = builtins.toFile "teslamate.env" '' - ENCRYPTION_KEY=123456789 - DATABASE_PASS=123456789 - RELEASE_COOKIE=123456789 + psql = pkgs.writeShellScriptBin "teslamate_psql" '' + exec "${pkgs.postgresql}/bin/psql" --host "$DATABASE_HOST" --user "$DATABASE_USER" --port "$DATABASE_PORT" "$DATABASE_NAME" "$@" + ''; + mosquitto_sub = pkgs.writeShellScriptBin "teslamate_sub" '' + exec "${pkgs.mosquitto}/bin/mosquitto_sub" -h "$MQTT_HOST" -p "$MQTT_PORT" -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" "$@" + ''; + + devShell = inputs.devenv.lib.mkShell { + inherit inputs pkgs; + + modules = with pkgs; [ + { + devenv.root = + let + devenvRootFileContent = builtins.readFile devenv-root.outPath; + in + pkgs.lib.mkIf (devenvRootFileContent != "") devenvRootFileContent; + packages = + [ + elixir + elixir_ls + node2nix + nodejs + prefetch-npm-deps + # for dashboard scripts + jq + psql + mosquitto + mosquitto_sub + config.treefmt.build.wrapper + ] + ++ builtins.attrValues config.treefmt.build.programs + # ++ optional stdenv.isLinux [ + # inotify-tools # disabled to avoid error: A definition for option `packages."[definition 4-entry 16]"' is not of type `package'. + # glibcLocales # disabled to avoid error: A definition for option `packages."[definition 4-entry 16]"' is not of type `package'. + # ] + ++ optional stdenv.isDarwin terminal-notifier + ++ optionals stdenv.isDarwin ( + with darwin.apple_sdk.frameworks; + [ + CoreFoundation + CoreServices + ] + ); + enterShell = '' + export LOCALES="${cldr}/priv/cldr"; + export PORT="4000" + export ENCRYPTION_KEY="your_secure_encryption_key_here" + export DATABASE_USER="teslamate" + export DATABASE_PASS="your_secure_password_here" + export DATABASE_NAME="teslamate" + export DATABASE_HOST="127.0.0.1" + export DATABASE_PORT="${toString postgres_port}" + export MQTT_HOST="127.0.0.1" + export MQTT_PORT="${toString mosquitto_port}" + export RELEASE_COOKIE="1234567890123456789" + export TZDATA_DIR="$PWD/tzdata" + export MIX_REBAR3="${rebar3}/bin/rebar3"; + mix deps.get ''; - postgres.enable = true; - grafana.enable = true; - }; - }; + enterTest = '' + mix test + ''; + processes.mqtt = { + exec = "${pkgs.mosquitto}/bin/mosquitto -p ${toString mosquitto_port}"; + }; + process.managers.process-compose = { + port = process_compose_port; + tui.enable = true; + }; + services.postgres = { + enable = true; + package = pkgs.postgresql_16; # 17 is not yet available in nixpkgs + listen_addresses = "127.0.0.1"; + port = postgres_port; + initialDatabases = [{ name = "teslamate"; }]; + initialScript = '' + CREATE USER teslamate with encrypted password 'your_secure_password_here'; + GRANT ALL PRIVILEGES ON DATABASE teslamate TO teslamate; + ALTER USER teslamate WITH SUPERUSER; + ''; + }; + } + ]; - testScript = '' - server.wait_for_open_port(4000) + }; + + moduleTest = + (nixpkgs.lib.nixos.runTest { + hostPkgs = pkgs; + defaults.documentation.enable = false; + imports = [ + { + name = "teslamate"; + nodes.server = { + imports = [ self'.nixosModules.default ]; + services.teslamate = { + enable = true; + secretsFile = builtins.toFile "teslamate.env" '' + ENCRYPTION_KEY=123456789 + DATABASE_PASS=123456789 + RELEASE_COOKIE=123456789 + ''; + postgres.enable_server = true; + grafana.enable = true; + }; + }; + + testScript = '' + server.wait_for_open_port(4000) + ''; + } + ]; + }).config.result; + + emptyTest = pkgs.stdenv.mkDerivation { + name = "noTest"; + buildPhase = '' + echo "Tests are only supported on Linux." ''; - }]; - }).config.result; - in { - packages = { - devenv-up = devShell.config.procfileScript; - default = pkg; + }; + in + { + packages = { + devenv-up = devShell.config.procfileScript; + default = pkg; + }; + devShells.default = devShell; + + # for `nix flake check` + checks = { + default = if pkgs.stdenv.isLinux then moduleTest else emptyTest; + # formatter check is done in the formatter module + }; }; - devShells.default = devShell; - checks.default = moduleTest; - })) // { - nixosModules.default = import ./module.nix { inherit self; }; - }; + }; } diff --git a/grafana/dashboards.sh b/grafana/dashboards.sh index 73129dd523..0f07977fdc 100755 --- a/grafana/dashboards.sh +++ b/grafana/dashboards.sh @@ -18,108 +18,104 @@ readonly URL=${URL:-"http://localhost:3000"} readonly LOGIN=${LOGIN:-"admin:admin"} readonly DASHBOARDS_DIRECTORY=${DASHBOARDS_DIRECTORY:-"./grafana/dashboards"} - main() { - local task=$1 + local task=$1 - echo " + echo " URL: $URL LOGIN: $LOGIN DASHBOARDS_DIRECTORY: $DASHBOARDS_DIRECTORY " - case $task in - backup) backup;; - restore) restore;; - *) exit 1;; - esac + case $task in + backup) backup ;; + restore) restore ;; + *) exit 1 ;; + esac } - backup() { - local dashboard_json + local dashboard_json - for dashboard in $(list_dashboards); do - dashboard_json=$(get_dashboard "$dashboard") + for dashboard in $(list_dashboards); do + dashboard_json=$(get_dashboard "$dashboard") - if [[ -z "$dashboard_json" ]]; then - echo "ERROR: + if [[ -z $dashboard_json ]]; then + echo "ERROR: Couldn't retrieve dashboard $dashboard. " - exit 1 - fi + exit 1 + fi - echo "$dashboard_json" > "$DASHBOARDS_DIRECTORY/$dashboard".json + echo "$dashboard_json" >"$DASHBOARDS_DIRECTORY/$dashboard".json - echo "BACKED UP $(basename "$dashboard").json" - done + echo "BACKED UP $(basename "$dashboard").json" + done } - restore() { - find "$DASHBOARDS_DIRECTORY" -type f -name \*.json -print0 | - while IFS= read -r -d '' dashboard_path; do - folder_id=$(get_folder_id "$(basename "$dashboard_path" .json)") - curl \ - --silent --fail --show-error --output /dev/null \ - --user "$LOGIN" \ - -X POST -H "Content-Type: application/json" \ - -d "{\"dashboard\":$(cat "$dashboard_path"), \ + find "$DASHBOARDS_DIRECTORY" -type f -name \*.json -print0 | + while IFS= read -r -d '' dashboard_path; do + folder_id=$(get_folder_id "$(basename "$dashboard_path" .json)") + curl \ + --silent --fail --show-error --output /dev/null \ + --user "$LOGIN" \ + -X POST -H "Content-Type: application/json" \ + -d "{\"dashboard\":$(cat "$dashboard_path"), \ \"overwrite\":true, \ \"folderId\":$folder_id, \ \"inputs\":[{\"name\":\"DS_CLOUDWATCH\", \ \"type\":\"datasource\", \ \"pluginId\":\"cloudwatch\", \ \"value\":\"TeslaMate\"}]}" \ - "$URL/api/dashboards/import" + "$URL/api/dashboards/import" - echo "RESTORED $(basename "$dashboard_path")" - done + echo "RESTORED $(basename "$dashboard_path")" + done } get_dashboard() { - local dashboard=$1 + local dashboard=$1 - if [[ -z "$dashboard" ]]; then - echo "ERROR: + if [[ -z $dashboard ]]; then + echo "ERROR: A dashboard must be specified. " - exit 1 - fi - - curl \ - --silent \ - --user "$LOGIN" \ - "$URL/api/dashboards/db/$dashboard" | - jq '.dashboard | .id = null' + exit 1 + fi + + curl \ + --silent \ + --user "$LOGIN" \ + "$URL/api/dashboards/db/$dashboard" | + jq '.dashboard | .id = null' } get_folder_id() { - local dashboard=$1 + local dashboard=$1 - if [[ -z "$dashboard" ]]; then - echo "ERROR: + if [[ -z $dashboard ]]; then + echo "ERROR: A dashboard must be specified. " - exit 1 - fi - - curl \ - --silent \ - --user "$LOGIN" \ - "$URL/api/dashboards/db/$dashboard" | - jq '.meta | .folderId' + exit 1 + fi + + curl \ + --silent \ + --user "$LOGIN" \ + "$URL/api/dashboards/db/$dashboard" | + jq '.meta | .folderId' } list_dashboards() { - curl \ - --silent \ - --user "$LOGIN" \ - "$URL/api/search" | - jq -r '.[] | select(.type == "dash-db") | .uri' | - cut -d '/' -f2 + curl \ + --silent \ + --user "$LOGIN" \ + "$URL/api/search" | + jq -r '.[] | select(.type == "dash-db") | .uri' | + cut -d '/' -f2 } - main "$@" diff --git a/grafana/dashboards.yml b/grafana/dashboards.yml index 528fceb457..c093235f08 100644 --- a/grafana/dashboards.yml +++ b/grafana/dashboards.yml @@ -1,33 +1,33 @@ apiVersion: 1 providers: -- name: 'teslamate' - orgId: 1 - folder: TeslaMate - folderUid: Nr4ofiDZk - type: file - disableDeletion: false - editable: true - updateIntervalSeconds: 86400 - options: - path: /dashboards -- name: 'teslamate_internal' - orgId: 1 - folder: Internal - folderUid: Nr5ofiDZk - type: file - disableDeletion: false - editable: true - updateIntervalSeconds: 86400 - options: - path: /dashboards_internal -- name: 'teslamate_reports' - orgId: 1 - folder: Reports - folderUid: Nr6ofiDZk - type: file - disableDeletion: false - editable: true - updateIntervalSeconds: 86400 - options: - path: /dashboards_reports \ No newline at end of file + - name: "teslamate" + orgId: 1 + folder: TeslaMate + folderUid: Nr4ofiDZk + type: file + disableDeletion: false + editable: true + updateIntervalSeconds: 86400 + options: + path: /dashboards + - name: "teslamate_internal" + orgId: 1 + folder: Internal + folderUid: Nr5ofiDZk + type: file + disableDeletion: false + editable: true + updateIntervalSeconds: 86400 + options: + path: /dashboards_internal + - name: "teslamate_reports" + orgId: 1 + folder: Reports + folderUid: Nr6ofiDZk + type: file + disableDeletion: false + editable: true + updateIntervalSeconds: 86400 + options: + path: /dashboards_reports diff --git a/module.nix b/module.nix index 6837e64170..adf7c2d1ee 100644 --- a/module.nix +++ b/module.nix @@ -1,11 +1,25 @@ { self }: -{ config, lib, pkgs, ...}: +{ config +, lib +, pkgs +, ... +}: let teslamate = self.packages.${pkgs.system}.default; cfg = config.services.teslamate; - inherit (lib) mkPackageOption mkEnableOption mkOption types mkIf mkMerge getExe literalExpression; -in { + inherit (lib) + mkPackageOption + mkEnableOption + mkOption + types + mkIf + mkMerge + getExe + literalExpression + ; +in +{ options.services.teslamate = { enable = mkEnableOption "Teslamate"; @@ -69,6 +83,7 @@ in { }; package = mkPackageOption pkgs "postgresql_16" { + # 17 is not yet available in nixpkgs extraDescription = '' The postgresql package to use. ''; @@ -105,7 +120,7 @@ in { default = false; description = "Whether to create and provision grafana with the TeslaMate dashboards"; }; - + listenAddress = mkOption { type = types.str; default = "0.0.0.0"; @@ -143,158 +158,160 @@ in { }; }; - config = mkIf cfg.enable - (mkMerge [ - { - users.users.teslamate = { - isSystemUser = true; - group = "teslamate"; - home = "/var/lib/teslamate"; - createHome = true; - }; - users.groups.teslamate = {}; + config = mkIf cfg.enable (mkMerge [ + { + users.users.teslamate = { + isSystemUser = true; + group = "teslamate"; + home = "/var/lib/teslamate"; + createHome = true; + }; + users.groups.teslamate = { }; - systemd.services.teslamate = { - description = "TeslaMate"; - after = [ "network.target" "postgresql.service" ]; - wantedBy = mkIf cfg.autoStart [ "multi-user.target" ]; - serviceConfig = { - User = "teslamate"; - Restart = "on-failure"; - RestartSec = 5; + systemd.services.teslamate = { + description = "TeslaMate"; + after = [ + "network.target" + "postgresql.service" + ]; + wantedBy = mkIf cfg.autoStart [ "multi-user.target" ]; + serviceConfig = { + User = "teslamate"; + Restart = "on-failure"; + RestartSec = 5; - WorkingDirectory = "/var/lib/teslamate"; + WorkingDirectory = "/var/lib/teslamate"; - ExecStartPre = ''${getExe teslamate} eval "TeslaMate.Release.migrate"''; - ExecStart = "${getExe teslamate} start"; - ExecStop = "${getExe teslamate} stop"; + ExecStartPre = ''${getExe teslamate} eval "TeslaMate.Release.migrate"''; + ExecStart = "${getExe teslamate} start"; + ExecStop = "${getExe teslamate} stop"; - EnvironmentFile = cfg.secretsFile; - }; - environment = mkMerge [ - { - PORT = toString cfg.port; - DATABASE_USER = cfg.postgres.user; - DATABASE_NAME = cfg.postgres.database; - DATABASE_HOST = cfg.postgres.host; - DATABASE_PORT = toString cfg.postgres.port; - VIRTUAL_HOST = cfg.virtualHost; - URL_PATH = cfg.urlPath; - HTTP_BINDING_ADDRESS = mkIf (cfg.listenAddress != null) cfg.listenAddress; - DISABLE_MQTT = mkIf (!cfg.mqtt.enable) "true"; - } - (mkIf cfg.mqtt.enable { - MQTT_HOST = cfg.mqtt.host; - MQTT_PORT = mkIf (cfg.mqtt.port != null) (toString cfg.mqtt.port); - }) - ]; + EnvironmentFile = cfg.secretsFile; }; - } - (mkIf cfg.postgres.enable_server { - services.postgresql = { - enable = true; - inherit (cfg.postgres) port package; - - initialScript = pkgs.writeText "teslamate-psql-init" '' - \set password `echo $DATABASE_PASS` - CREATE DATABASE ${cfg.postgres.database}; - CREATE USER ${cfg.postgres.user} with encrypted password :'password'; - GRANT ALL PRIVILEGES ON DATABASE ${cfg.postgres.database} TO ${cfg.postgres.user}; - ALTER USER ${cfg.postgres.user} WITH SUPERUSER; - ''; + environment = mkMerge [ + { + PORT = toString cfg.port; + DATABASE_USER = cfg.postgres.user; + DATABASE_NAME = cfg.postgres.database; + DATABASE_HOST = cfg.postgres.host; + DATABASE_PORT = toString cfg.postgres.port; + VIRTUAL_HOST = cfg.virtualHost; + URL_PATH = cfg.urlPath; + HTTP_BINDING_ADDRESS = mkIf (cfg.listenAddress != null) cfg.listenAddress; + DISABLE_MQTT = mkIf (!cfg.mqtt.enable) "true"; + } + (mkIf cfg.mqtt.enable { + MQTT_HOST = cfg.mqtt.host; + MQTT_PORT = mkIf (cfg.mqtt.port != null) (toString cfg.mqtt.port); + }) + ]; + }; + } + (mkIf cfg.postgres.enable_server { + services.postgresql = { + enable = true; + inherit (cfg.postgres) port package; + + initialScript = pkgs.writeText "teslamate-psql-init" '' + \set password `echo $DATABASE_PASS` + CREATE DATABASE ${cfg.postgres.database}; + CREATE USER ${cfg.postgres.user} with encrypted password :'password'; + GRANT ALL PRIVILEGES ON DATABASE ${cfg.postgres.database} TO ${cfg.postgres.user}; + ALTER USER ${cfg.postgres.user} WITH SUPERUSER; + ''; + }; + + # Include secrets in postgres as well + systemd.services.postgresql = { + serviceConfig = { + EnvironmentFile = cfg.secretsFile; }; - - # Include secrets in postgres as well - systemd.services.postgresql = { - serviceConfig = { - EnvironmentFile = cfg.secretsFile; + }; + }) + (mkIf cfg.grafana.enable { + services.grafana = { + enable = true; + settings = { + server = { + domain = cfg.virtualHost; + http_port = cfg.grafana.port; + http_addr = cfg.grafana.listenAddress; + root_url = "http://%(domain)s${cfg.grafana.urlPath}"; + serve_from_sub_path = cfg.grafana.urlPath != "/"; + }; + security = { + allow_embedding = true; + disable_gravatr = true; }; + users = { + allow_sign_up = false; + }; + "auth.anonymous".enabled = false; + "auth.basic".enabled = false; + analytics.reporting_enabled = false; }; - }) - (mkIf cfg.grafana.enable { - services.grafana = { + provision = { enable = true; - settings = { - server = { - domain = cfg.virtualHost; - http_port = cfg.grafana.port; - http_addr = cfg.grafana.listenAddress; - root_url = "http://%(domain)s${cfg.grafana.urlPath}"; - serve_from_sub_path = cfg.grafana.urlPath != "/"; - }; - security = { - allow_embedding = true; - disable_gravatr = true; - }; - users = { - allow_sign_up = false; - }; - "auth.anonymous".enabled = false; - "auth.basic".enabled = false; - analytics.reporting_enabled = false; - }; - provision = { - enable = true; - datasources.path = ./grafana/datasource.yml; - # Need to duplicate dashboards.yml since it contains absolute paths - # which are incompatible with NixOS - dashboards.settings = { - apiVersion = 1; - providers = [ - { - name = "teslamate"; - orgId = 1; - folder = "TeslaMate"; - folderUid = "Nr4ofiDZk"; - type = "file"; - disableDeletion = false; - editable = true; - updateIntervalSeconds = 86400; - options.path = lib.sources.sourceFilesBySuffices - ./grafana/dashboards - [ ".json" ]; - } - { - name = "teslamate_internal"; - orgId = 1; - folder = "Internal"; - folderUid = "Nr5ofiDZk"; - type = "file"; - disableDeletion = false; - editable = true; - updateIntervalSeconds = 86400; - options.path = lib.sources.sourceFilesBySuffices - ./grafana/dashboards/internal - [ ".json" ]; - } - { - name = "teslamate_reports"; - orgId = 1; - folder = "Reports"; - folderUid = "Nr6ofiDZk"; - type = "file"; - disableDeletion = false; - editable = true; - updateIntervalSeconds = 86400; - options.path = lib.sources.sourceFilesBySuffices - ./grafana/dashboards/reports - [ ".json" ]; - } - ]; - }; + datasources.path = ./grafana/datasource.yml; + # Need to duplicate dashboards.yml since it contains absolute paths + # which are incompatible with NixOS + dashboards.settings = { + apiVersion = 1; + providers = [ + { + name = "teslamate"; + orgId = 1; + folder = "TeslaMate"; + folderUid = "Nr4ofiDZk"; + type = "file"; + disableDeletion = false; + editable = true; + updateIntervalSeconds = 86400; + options.path = lib.sources.sourceFilesBySuffices + ./grafana/dashboards + [ ".json" ]; + } + { + name = "teslamate_internal"; + orgId = 1; + folder = "Internal"; + folderUid = "Nr5ofiDZk"; + type = "file"; + disableDeletion = false; + editable = true; + updateIntervalSeconds = 86400; + options.path = lib.sources.sourceFilesBySuffices + ./grafana/dashboards/internal + [ ".json" ]; + } + { + name = "teslamate_reports"; + orgId = 1; + folder = "Reports"; + folderUid = "Nr6ofiDZk"; + type = "file"; + disableDeletion = false; + editable = true; + updateIntervalSeconds = 86400; + options.path = lib.sources.sourceFilesBySuffices + ./grafana/dashboards/reports + [ ".json" ]; + } + ]; }; }; + }; - systemd.services.grafana = { - serviceConfig.EnvironmentFile = cfg.secretsFile; - environment = { - DATABASE_USER = cfg.postgres.user; - DATABASE_NAME = cfg.postgres.database; - DATABASE_HOST = cfg.postgres.host; - DATABASE_PORT = toString cfg.postgres.port; - DATABASE_SSL_MODE = "disable"; - }; + systemd.services.grafana = { + serviceConfig.EnvironmentFile = cfg.secretsFile; + environment = { + DATABASE_USER = cfg.postgres.user; + DATABASE_NAME = cfg.postgres.database; + DATABASE_HOST = cfg.postgres.host; + DATABASE_PORT = toString cfg.postgres.port; + DATABASE_SSL_MODE = "disable"; }; - }) - ]); + }; + }) + ]); } diff --git a/nix/flake-modules/formatter.nix b/nix/flake-modules/formatter.nix new file mode 100644 index 0000000000..09cc81fb9d --- /dev/null +++ b/nix/flake-modules/formatter.nix @@ -0,0 +1,66 @@ +{ inputs, ... }: +{ + imports = [ + inputs.treefmt-nix.flakeModule + ]; + perSystem = + { config, pkgs, ... }: + { + # Auto formatters. This also adds a flake check to ensure that the + # source tree was auto formatted. + treefmt = { + flakeFormatter = true; # Enables treefmt the default formatter used by the nix fmt command + flakeCheck = false; # Add a flake check to run treefmt, disabled, as mix format does need the dependencies fetched beforehand + projectRootFile = "flake.nix"; + + # we really need to mirror the treefmt.toml as we can't use it directly + settings.global.excludes = [ + "*.gitignore" + "*.dockerignore" + ".envrc" + "*.node-version" + "Dockerfile" + "grafana/Dockerfile" + "Makefile" + "VERSION" + "LICENSE" + "*.metadata" + "*.manifest" + "*.webmanifest" + "*.dat" + "*.lock" + "*.txt" + "*.csv" + "*.ico" + "*.png" + "*.svg" + "*.properties" + "*.xml" + "*.po" + "*.pot" + "*.json.example" + "*.typos.toml" + "treefmt.toml" + "grafana/dashboards/*.json" # we use the grafana export style + ]; + programs.mix-format.enable = true; + settings.formatter.mix-format.includes = [ + "*.ex" + "*.exs" + "*.{heex,eex}" + ]; + # run shellcheck first + programs.shellcheck.enable = true; + settings.formatter.shellcheck.priority = 0; # default is 0, but we set it here for clarity + + # shfmt second + programs.shfmt.enable = true; + programs.shfmt.indent_size = 0; # 0 means tabs + settings.formatter.shfmt.priority = 1; + + programs.prettier.enable = true; + + programs.nixpkgs-fmt.enable = true; + }; + }; +} diff --git a/treefmt.toml b/treefmt.toml new file mode 100644 index 0000000000..5771b12021 --- /dev/null +++ b/treefmt.toml @@ -0,0 +1,63 @@ +# One CLI to format the code tree - https://git.numtide.com/numtide/treefmt + +[global] +excludes = [ + "*.gitignore", + "*.dockerignore", + ".envrc", + "*.node-version", + "Dockerfile", + "grafana/Dockerfile", + "Makefile", + "VERSION", + "LICENSE", + "*.metadata", + "*.manifest", + "*.webmanifest", + "*.dat", + "*.lock", + "*.txt", + "*.csv", + "*.ico", + "*.png", + "*.svg", + "*.properties", + "*.xml", + "*.po", + "*.pot", + "*.json.example", + "*.typos.toml", + "treefmt.toml", + "grafana/dashboards/*.json", #we use the grafana export style + ] + +[formatter.mix-format] +command = "mix" +excludes = [] +includes = ["*.ex", "*.exs" ,"*.{heex,eex}"] +options = ["format"] + +# run shellcheck first +[formatter.shellcheck] +command = "shellcheck" +includes = ["*.sh"] +priority = 0 # default is 0, but we set it here for clarity + +# shfmt second +[formatter.shfmt] +command = "shfmt" +options = ["-s", "-w"] +includes = ["*.sh"] +priority = 1 + +[formatter.prettier] +command = "prettier" +excludes = [] +includes = ["*.cjs", "*.css", "*.html", "*.js", "*.json", "*.json5", "*.jsx", "*.md", "*.mdx", "*.mjs", "*.scss", "*.ts", "*.tsx", "*.vue", "*.yaml", "*.yml"] +options = ["--write"] + +[formatter.nixfmt] +command = "nixfmt" +excludes = [] +includes = ["*.nix"] +options = [] \ No newline at end of file diff --git a/website/docs/configuration/environment_variables.md b/website/docs/configuration/environment_variables.md index a5411bbc25..75765353e7 100644 --- a/website/docs/configuration/environment_variables.md +++ b/website/docs/configuration/environment_variables.md @@ -7,7 +7,7 @@ sidebar_label: Environment Variables TeslaMate accepts the following environment variables for runtime configuration: | Variable Name | Description | Default Value | -|----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------| +| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | **ENCRYPTION_KEY** | A key used to encrypt the Tesla API tokens (**required**) | | | **DATABASE_USER** | Username (**required**) | | | **DATABASE_PASS** | User password (**required**) | | diff --git a/website/docs/development.md b/website/docs/development.md index f937fd77be..cd9457c5c6 100644 --- a/website/docs/development.md +++ b/website/docs/development.md @@ -11,6 +11,8 @@ sidebar_label: Development and Contributing - An **MQTT broker** e.g. mosquitto (_optional_) - **NodeJS** >= 20.15.0 +or [Nix](https://nixos.org/download/). You can then use the nix devenv (via direnv) setup. + ## Initial Setup To run the TeslaMate test suite you need a database named `teslamate_test`: @@ -49,6 +51,18 @@ mix compile ## Code formatting +### Format all files + +Install [Treefmt](https://github.com/numtide/treefmt/releases) or use the nix devenv (via direnv) setup. + +```bash +treefmt +``` + +You can even use a VS Code extension like [treefmt](https://marketplace.visualstudio.com/items?itemName=ibecker.treefmt-vscode) to format the files on save. + +### Only format elixir files + ```bash mix format ``` @@ -74,16 +88,16 @@ For TeslaMate: ```yml teslamate: - # image: teslamate/teslamate:latest - image: ghcr.io/teslamate-org/teslamate/teslamate:pr-3836 + # image: teslamate/teslamate:latest + image: ghcr.io/teslamate-org/teslamate/teslamate:pr-3836 ``` For Grafana: ```yml grafana: - # image: teslamate/grafana:latest - image: ghcr.io/teslamate-org/teslamate/grafana:pr-3836 + # image: teslamate/grafana:latest + image: ghcr.io/teslamate-org/teslamate/grafana:pr-3836 ``` ## Making Changes to Grafana Dashboards diff --git a/website/docs/faq.md b/website/docs/faq.md index ffa6b2e317..d6b719b2cb 100644 --- a/website/docs/faq.md +++ b/website/docs/faq.md @@ -79,9 +79,9 @@ HTTPS (TCP/443) auth.tesla.com owner-api.teslamotors.com streaming.vn.teslamotors.com -nominatim.openstreetmap.org +nominatim.openstreetmap.org HTTP (TCP/80) -step.esa.int +step.esa.int Note: This may change when Teslamate is updated! diff --git a/website/docs/guides/api.md b/website/docs/guides/api.md index 33dc01580e..242e2468bf 100644 --- a/website/docs/guides/api.md +++ b/website/docs/guides/api.md @@ -104,11 +104,12 @@ MyTeslaMate also provides streaming by [reproducing the old streaming from the d 1. Set up a third-party account at [developer.tesla.com](https://developer.tesla.com) as described on the [Tesla docs](https://developer.tesla.com/docs/fleet-api#setup) 1. Add the following environment variable with your own domain : - 1. Use [the correct region](https://developer.tesla.com/docs/fleet-api#endpoints-and-regional-requirements) in the `TESLA_API_HOST` field: - - North America, Asia-Pacific (excluding China): [https://fleet-api.prd.na.vn.cloud.tesla.com](https://fleet-api.prd.na.vn.cloud.tesla.com) - - Europe, Middle East, Africa: [https://fleet-api.prd.eu.vn.cloud.tesla.com](https://fleet-api.prd.eu.vn.cloud.tesla.com) - - China: [https://fleet-api.prd.cn.vn.cloud.tesla.cn](https://fleet-api.prd.cn.vn.cloud.tesla.cn) - 1. Update the `TESLA_AUTH_CLIENT_ID` with the client ID of your Tesla application. + + 1. Use [the correct region](https://developer.tesla.com/docs/fleet-api#endpoints-and-regional-requirements) in the `TESLA_API_HOST` field: + - North America, Asia-Pacific (excluding China): [https://fleet-api.prd.na.vn.cloud.tesla.com](https://fleet-api.prd.na.vn.cloud.tesla.com) + - Europe, Middle East, Africa: [https://fleet-api.prd.eu.vn.cloud.tesla.com](https://fleet-api.prd.eu.vn.cloud.tesla.com) + - China: [https://fleet-api.prd.cn.vn.cloud.tesla.cn](https://fleet-api.prd.cn.vn.cloud.tesla.cn) + 1. Update the `TESLA_AUTH_CLIENT_ID` with the client ID of your Tesla application. ```yml # API Fleet diff --git a/website/docs/guides/unix_domain_sockets.md b/website/docs/guides/unix_domain_sockets.md index fea8b31dc6..f682a209a7 100644 --- a/website/docs/guides/unix_domain_sockets.md +++ b/website/docs/guides/unix_domain_sockets.md @@ -2,8 +2,7 @@ title: Using Unix Domain Sockets with a reverse-proxy --- -It is possible to configure Teslamate to communicate over unix-domain sockets (UDS) instea of a typical network socket. This can be useful to improve security by restricting which applications can communicate to the application. A typical configuration would be to use a UDS between a reverse-proxy (like Nginx) and Teslamate. When paired with something like rootless-podman and [socket-activation](https://github.com/containers/podman/blob/main/docs/tutorials/socket_activation.md), Nginx can be configured with `--network=none` providing external access to Teslmate without the Nginx container having any networking at all. While setting up socket-activation and Podman is beyond the scope of this document, it will explain how to configure UDS between Teslamate and an Nginx reverse-proxy. - +It is possible to configure Teslamate to communicate over unix-domain sockets (UDS) instea of a typical network socket. This can be useful to improve security by restricting which applications can communicate to the application. A typical configuration would be to use a UDS between a reverse-proxy (like Nginx) and Teslamate. When paired with something like rootless-podman and [socket-activation](https://github.com/containers/podman/blob/main/docs/tutorials/socket_activation.md), Nginx can be configured with `--network=none` providing external access to Teslmate without the Nginx container having any networking at all. While setting up socket-activation and Podman is beyond the scope of this document, it will explain how to configure UDS between Teslamate and an Nginx reverse-proxy. ## Requirements @@ -11,20 +10,21 @@ It is possible to configure Teslamate to communicate over unix-domain sockets (U - These instructions will document the procedure for using a UDS with docker-compose, but it is not difficult to adapt them to a system running Teslamate natively via systemd. - Nginx configured as a reverse proxy - ## Instructions -Nginx requires that the UDS exist when it is started, but Teslamate will (re)create the UDS on startup. This means that Teslamate must be configured to start before Nginx, or Nginx must be configured to detect a socket change and reload (for example the [socket-gen](https://github.com/PhracturedBlue/socket-gen) utility designed for this purpose). Additionally, because docker-compose does not provide a method to run host-commands prior to starting a container, the directory containing the UDS must be manually created before Teslamate starts. It is easiest to manually create this directory on a persistent volume. - - Create a directory for the UDS: - `mkdir -p /opt/nginx_uds/teslamate` - - Allow Nginx to access the directory: - `chown /opt/nginx_uds/teslamate` - - Allow Teslamate to create the UDS: - `chgrp 10001 /opt/nginx_uds/teslamate` - `chmod 770 /opt/nginx_uds/teslamate` -An alternative to using owner/group access would be to use [ACLs](https://wiki.debian.org/Permissions#Access_Control_Lists_in_Linux) to control access to the UDS directory. - -Next configure Teslamate to use the UDS. Modify the teslamate service in docker-compose.yml to include: +Nginx requires that the UDS exist when it is started, but Teslamate will (re)create the UDS on startup. This means that Teslamate must be configured to start before Nginx, or Nginx must be configured to detect a socket change and reload (for example the [socket-gen](https://github.com/PhracturedBlue/socket-gen) utility designed for this purpose). Additionally, because docker-compose does not provide a method to run host-commands prior to starting a container, the directory containing the UDS must be manually created before Teslamate starts. It is easiest to manually create this directory on a persistent volume. + +- Create a directory for the UDS: + `mkdir -p /opt/nginx_uds/teslamate` +- Allow Nginx to access the directory: + `chown /opt/nginx_uds/teslamate` +- Allow Teslamate to create the UDS: + `chgrp 10001 /opt/nginx_uds/teslamate` + `chmod 770 /opt/nginx_uds/teslamate` + An alternative to using owner/group access would be to use [ACLs](https://wiki.debian.org/Permissions#Access_Control_Lists_in_Linux) to control access to the UDS directory. + +Next configure Teslamate to use the UDS. Modify the teslamate service in docker-compose.yml to include: + ``` volumes: ... @@ -37,7 +37,8 @@ Next configure Teslamate to use the UDS. Modify the teslamate service in docker # - 4000:4000 ``` -Lastly, configure the Nginx reverse-proxy to forward connections to the UDS. The relevant configuration would look something like: +Lastly, configure the Nginx reverse-proxy to forward connections to the UDS. The relevant configuration would look something like: + ``` upstream teslamate.uds { server unix:/opt/nginx_uds/teslamate/teslamate.sock; diff --git a/website/docs/import/tesla_apiscraper.md b/website/docs/import/tesla_apiscraper.md index 173b260cb3..18b9ff4b40 100644 --- a/website/docs/import/tesla_apiscraper.md +++ b/website/docs/import/tesla_apiscraper.md @@ -67,4 +67,3 @@ All of this is experimental and has not been extensively tested. If you encounte ### Part 3: Importing the processed CSV data into TeslaMate - Proceed with the [TeslaFi import](teslafi.md) steps using the CSV files you just created. - diff --git a/website/docs/integrations/Node-RED.md b/website/docs/integrations/Node-RED.md index 2d3fee9417..394dbbaffe 100644 --- a/website/docs/integrations/Node-RED.md +++ b/website/docs/integrations/Node-RED.md @@ -98,6 +98,7 @@ done docker compose stop node-red docker compose start node-red ``` + Note that if your function nodes need additional NPM packages, you can add those into 'MODULES'. You can import those in the function node 'Setup' page, like add module 'linq-js' and import it as variable 'Enumerable'. ## Import Flows diff --git a/website/docs/integrations/home_assistant.md b/website/docs/integrations/home_assistant.md index d6919ccacc..0e565d6ddf 100644 --- a/website/docs/integrations/home_assistant.md +++ b/website/docs/integrations/home_assistant.md @@ -62,8 +62,8 @@ Don't forget to replace `` and `` with correct unique_id: teslamate_1_display_name # internal id, used for device grouping availability: &teslamate_availability - topic: teslamate/cars/1/healthy - payload_available: 'true' - payload_not_available: 'false' + payload_available: "true" + payload_not_available: "false" device: &teslamate_device_info identifiers: [teslamate_car_1] configuration_url: https://teslamate.zxxz.io/ @@ -81,7 +81,7 @@ Don't forget to replace `` and `` with correct device: *teslamate_device_info json_attributes_topic: "teslamate/cars/1/location" icon: mdi:crosshairs-gps - + - device_tracker: name: Active route location object_id: tesla_active_route_location @@ -305,7 +305,7 @@ Don't forget to replace `` and `` with correct device_class: battery unit_of_measurement: "%" icon: mdi:battery-80 - + - sensor: name: Usable Battery Level object_id: tesla_usable_battery_level @@ -642,83 +642,82 @@ Don't forget to replace `` and `` with correct payload_on: "true" payload_off: "false" icon: mdi:ev-plug-tesla - ``` ### sensor.yaml (sensor: section of configuration.yaml) ```yml title="sensor.yaml" - - platform: template - sensors: +- platform: template + sensors: tesla_est_battery_range_mi: friendly_name: Estimated Range (mi) unit_of_measurement: mi icon_template: mdi:gauge value_template: > - {{ (states('sensor.tesla_est_battery_range_km') | float / 1.609344) | round(2) }} + {{ (states('sensor.tesla_est_battery_range_km') | float / 1.609344) | round(2) }} tesla_rated_battery_range_mi: friendly_name: Rated Range (mi) unit_of_measurement: mi icon_template: mdi:gauge value_template: > - {{ (states('sensor.tesla_rated_battery_range_km') | float / 1.609344) | round(2) }} + {{ (states('sensor.tesla_rated_battery_range_km') | float / 1.609344) | round(2) }} tesla_ideal_battery_range_mi: friendly_name: Ideal Range (mi) unit_of_measurement: mi icon_template: mdi:gauge value_template: > - {{ (states('sensor.tesla_ideal_battery_range_km') | float / 1.609344) | round(2) }} + {{ (states('sensor.tesla_ideal_battery_range_km') | float / 1.609344) | round(2) }} tesla_odometer_mi: friendly_name: Odometer (mi) unit_of_measurement: mi icon_template: mdi:counter value_template: > - {{ (states('sensor.tesla_odometer') | float / 1.609344) | round(2) }} + {{ (states('sensor.tesla_odometer') | float / 1.609344) | round(2) }} tesla_speed_mph: friendly_name: Speed (MPH) unit_of_measurement: mph icon_template: mdi:speedometer value_template: > - {{ (states('sensor.tesla_speed') | float / 1.609344) | round(2) }} + {{ (states('sensor.tesla_speed') | float / 1.609344) | round(2) }} tesla_elevation_ft: friendly_name: Elevation (ft) unit_of_measurement: ft icon_template: mdi:image-filter-hdr value_template: > - {{ (states('sensor.tesla_elevation') | float * 3.2808 ) | round(2) }} + {{ (states('sensor.tesla_elevation') | float * 3.2808 ) | round(2) }} tesla_tpms_pressure_fl_psi: friendly_name: Front Left Tire Pressure (psi) unit_of_measurement: psi icon_template: mdi:car-tire-alert value_template: > - {{ (states('sensor.tesla_tpms_pressure_fl') | float * 14.50377) | round(2) }} + {{ (states('sensor.tesla_tpms_pressure_fl') | float * 14.50377) | round(2) }} tesla_tpms_pressure_fr_psi: friendly_name: Front Right Tire Pressure (psi) unit_of_measurement: psi icon_template: mdi:car-tire-alert value_template: > - {{ (states('sensor.tesla_tpms_pressure_fr') | float * 14.50377) | round(2) }} + {{ (states('sensor.tesla_tpms_pressure_fr') | float * 14.50377) | round(2) }} tesla_tpms_pressure_rl_psi: friendly_name: Rear Left Tire Pressure (psi) unit_of_measurement: psi icon_template: mdi:car-tire-alert value_template: > - {{ (states('sensor.tesla_tpms_pressure_rl') | float * 14.50377) | round(2) }} + {{ (states('sensor.tesla_tpms_pressure_rl') | float * 14.50377) | round(2) }} tesla_tpms_pressure_rr_psi: friendly_name: Rear Right Tire Pressure (psi) unit_of_measurement: psi icon_template: mdi:car-tire-alert value_template: > - {{ (states('sensor.tesla_tpms_pressure_rr') | float * 14.50377) | round(2) }} + {{ (states('sensor.tesla_tpms_pressure_rr') | float * 14.50377) | round(2) }} tesla_active_route_distance_to_arrival_km: friendly_name: Active route distance to arrival (km) @@ -731,17 +730,17 @@ Don't forget to replace `` and `` with correct ### binary_sensor.yaml (binary_sensor: section of configuration.yaml) ```yml title="binary_sensor.yaml" - - platform: template - sensors: +- platform: template + sensors: tesla_park_brake: friendly_name: Parking Brake icon_template: mdi:car-brake-parking value_template: >- - {% if is_state('sensor.tesla_shift_state', 'P') %} - ON - {% else %} - OFF - {% endif %} + {% if is_state('sensor.tesla_shift_state', 'P') %} + ON + {% else %} + OFF + {% endif %} ``` ### ui-lovelace.yaml @@ -1100,7 +1099,7 @@ notify_tesla_windows_open: data: variables: whatsopen: "windows" - + - id: plugin-tesla-notify alias: Notify if Tesla not plugged in at night trigger: @@ -1120,4 +1119,3 @@ conditions: initial_state: true mode: single ``` - diff --git a/website/docs/integrations/mqtt.md b/website/docs/integrations/mqtt.md index a91d411585..30356fb268 100644 --- a/website/docs/integrations/mqtt.md +++ b/website/docs/integrations/mqtt.md @@ -9,82 +9,82 @@ The MQTT function within TeslaMate allows useful values to be published to an MQ Vehicle data will be published to the following topics: -| Topic | Example | Description | -| ------------------------------------------------------ | -------------------------------------------- | ------------------------------------------------------------------------------------- | -| `teslamate/cars/$car_id/display_name` | Blue Thunder | Vehicle Name | -| `teslamate/cars/$car_id/state` | asleep | Status of the vehicle (e.g. `online`, `asleep`, `charging`) | -| `teslamate/cars/$car_id/since` | 2019-02-29T23:00:07Z | Date of the last status change | -| `teslamate/cars/$car_id/healthy` | true | Health status of the logger for that vehicle | -| `teslamate/cars/$car_id/version` | 2019.32.12.2 | Software Version | -| `teslamate/cars/$car_id/update_available` | false | Indicates if a software update is available | -| `teslamate/cars/$car_id/update_version` | 2019.32.12.3 | Software version of the available update | -| | | | -| `teslamate/cars/$car_id/model` | 3 | Either "S", "3", "X" or "Y" | -| `teslamate/cars/$car_id/trim_badging` | P100D | Trim badging | -| `teslamate/cars/$car_id/exterior_color` | DeepBlue | The exterior color | -| `teslamate/cars/$car_id/wheel_type` | Pinwheel18 | The wheel type | -| `teslamate/cars/$car_id/spoiler_type` | None | The spoiler type | -| | | | -| `teslamate/cars/$car_id/geofence` | 🏡 Home | The name of the Geo-fence, if one exists at the current position | -| | | | -| `teslamate/cars/$car_id/latitude` | 35.278131 | DEPRECATED: Last reported car latitude | -| `teslamate/cars/$car_id/longitude` | 29.744801 | DEPRECATED: Last reported car longitude | -| `teslamate/cars/$car_id/location` |
\{
"latitude": 35.278131,
"longitude": 29.744801
\}
| Last reported car location (json blob) | -| `teslamate/cars/$car_id/shift_state` | D | Current/Last Shift State (D/N/R/P) | -| `teslamate/cars/$car_id/power` | -9 | Current battery power in watts. Positive value on discharge, negative value on charge | -| `teslamate/cars/$car_id/speed` | 12 | Current Speed in km/h | -| `teslamate/cars/$car_id/heading` | 340 | Last reported car direction | -| `teslamate/cars/$car_id/elevation` | 70 | Current elevation above sea level in meters | -| | | | -| `teslamate/cars/$car_id/locked` | true | Indicates if the car is locked | -| `teslamate/cars/$car_id/sentry_mode` | false | Indicates if Sentry Mode is active | -| `teslamate/cars/$car_id/windows_open` | false | Indicates if any of the windows are open | -| `teslamate/cars/$car_id/doors_open` | false | Indicates if any of the doors are open | -| `teslamate/cars/$car_id/driver_front_door_open` | false | Indicates if the driver-side front door is open | -| `teslamate/cars/$car_id/driver_rear_door_open` | false | Indicates if the driver-side rear door is open | -| `teslamate/cars/$car_id/passenger_front_door_open` | false | Indicates if the passenger-side front door is open | -| `teslamate/cars/$car_id/passenger_rear_door_open` | false | Indicates if the passenger-side rear door is open | -| `teslamate/cars/$car_id/trunk_open` | false | Indicates if the trunk is open | -| `teslamate/cars/$car_id/frunk_open` | false | Indicates if the frunk is open | -| `teslamate/cars/$car_id/is_user_present` | false | Indicates if a user is present in the vehicle | -| | | | -| `teslamate/cars/$car_id/is_climate_on` | true | Indicates if the climate control is on | -| `teslamate/cars/$car_id/inside_temp` | 20.8 | Inside Temperature in °C | -| `teslamate/cars/$car_id/outside_temp` | 18.4 | Temperature in °C | -| `teslamate/cars/$car_id/is_preconditioning` | false | Indicates if the vehicle is being preconditioned | -| | | | -| `teslamate/cars/$car_id/odometer` | 1653 | Car odometer in km | -| `teslamate/cars/$car_id/est_battery_range_km` | 372.5 | Estimated Range in km | -| `teslamate/cars/$car_id/rated_battery_range_km` | 401.63 | Rated Range in km | -| `teslamate/cars/$car_id/ideal_battery_range_km` | 335.79 | Ideal Range in km | -| | | | -| `teslamate/cars/$car_id/battery_level` | 88 | Battery Level Percentage | -| `teslamate/cars/$car_id/usable_battery_level` | 85 | Usable battery level percentage | -| `teslamate/cars/$car_id/plugged_in` | true | If car is currently plugged into a charger | -| `teslamate/cars/$car_id/charge_energy_added` | 5.06 | Last added energy in kWh | -| `teslamate/cars/$car_id/charge_limit_soc` | 90 | Charge Limit Configured in Percentage | -| `teslamate/cars/$car_id/charge_port_door_open` | true | Indicates if the charger door is open | -| `teslamate/cars/$car_id/charger_actual_current` | 2.05 | Current amperage supplied by charger | -| `teslamate/cars/$car_id/charger_phases` | 3 | Number of charger power phases (1-3) | -| `teslamate/cars/$car_id/charger_power` | 48.9 | Charger Power | -| `teslamate/cars/$car_id/charger_voltage` | 240 | Charger Voltage | -| `teslamate/cars/$car_id/charge_current_request` | 40 | How many amps the car wants | -| `teslamate/cars/$car_id/charge_current_request_max` | 40 | How many amps the car can have | -| `teslamate/cars/$car_id/scheduled_charging_start_time` | 2019-02-29T23:00:07Z | Start time of the scheduled charge | -| `teslamate/cars/$car_id/time_to_full_charge` | 1.83 | Hours remaining to full charge | -| `teslamate/cars/$car_id/tpms_pressure_fl` | 2.9 | Tire pressure measure in BAR, front left tire | -| `teslamate/cars/$car_id/tpms_pressure_fr` | 2.8 | Tire pressure measure in BAR, front right tire | -| `teslamate/cars/$car_id/tpms_pressure_rl` | 2.9 | Tire pressure measure in BAR, rear left tire | -| `teslamate/cars/$car_id/tpms_pressure_rr` | 2.8 | Tire pressure measure in BAR, rear right tire | -| `teslamate/cars/$car_id/tpms_soft_warning_fl` | true | Indicates if the Tire pressure measure is soft warning, front left tire | -| `teslamate/cars/$car_id/tpms_soft_warning_fr` | false | Indicates if the Tire pressure measure is soft warning, front right tire | -| `teslamate/cars/$car_id/tpms_soft_warning_rl` | false | Indicates if the Tire pressure measure is soft warning, rear left tire | -| `teslamate/cars/$car_id/tpms_soft_warning_rr` | false | Indicates if the Tire pressure measure is soft warning, rear right tire | -| `teslamate/cars/$car_id/active_route_destination` | Home | DEPRECATED: Navigation destination name (or "nil") | -| `teslamate/cars/$car_id/active_route_latitude` | 35.278131 | DEPRECATED: Navigation destination latitude (or "nil") | -| `teslamate/cars/$car_id/active_route_longitude` | 29.744801 | DEPRECATED: Navigation destination longitude (or "nil") | -| `teslamate/cars/$car_id/active_route` | _See below_ | Navigation details (json blob) | -| `teslamate/cars/$car_id/center_display_state` | 0 | Center Display State | +| Topic | Example | Description | +| ------------------------------------------------------ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| `teslamate/cars/$car_id/display_name` | Blue Thunder | Vehicle Name | +| `teslamate/cars/$car_id/state` | asleep | Status of the vehicle (e.g. `online`, `asleep`, `charging`) | +| `teslamate/cars/$car_id/since` | 2019-02-29T23:00:07Z | Date of the last status change | +| `teslamate/cars/$car_id/healthy` | true | Health status of the logger for that vehicle | +| `teslamate/cars/$car_id/version` | 2019.32.12.2 | Software Version | +| `teslamate/cars/$car_id/update_available` | false | Indicates if a software update is available | +| `teslamate/cars/$car_id/update_version` | 2019.32.12.3 | Software version of the available update | +| | | | +| `teslamate/cars/$car_id/model` | 3 | Either "S", "3", "X" or "Y" | +| `teslamate/cars/$car_id/trim_badging` | P100D | Trim badging | +| `teslamate/cars/$car_id/exterior_color` | DeepBlue | The exterior color | +| `teslamate/cars/$car_id/wheel_type` | Pinwheel18 | The wheel type | +| `teslamate/cars/$car_id/spoiler_type` | None | The spoiler type | +| | | | +| `teslamate/cars/$car_id/geofence` | 🏡 Home | The name of the Geo-fence, if one exists at the current position | +| | | | +| `teslamate/cars/$car_id/latitude` | 35.278131 | DEPRECATED: Last reported car latitude | +| `teslamate/cars/$car_id/longitude` | 29.744801 | DEPRECATED: Last reported car longitude | +| `teslamate/cars/$car_id/location` |
\{
"latitude": 35.278131,
"longitude": 29.744801
\}
| Last reported car location (json blob) | +| `teslamate/cars/$car_id/shift_state` | D | Current/Last Shift State (D/N/R/P) | +| `teslamate/cars/$car_id/power` | -9 | Current battery power in watts. Positive value on discharge, negative value on charge | +| `teslamate/cars/$car_id/speed` | 12 | Current Speed in km/h | +| `teslamate/cars/$car_id/heading` | 340 | Last reported car direction | +| `teslamate/cars/$car_id/elevation` | 70 | Current elevation above sea level in meters | +| | | | +| `teslamate/cars/$car_id/locked` | true | Indicates if the car is locked | +| `teslamate/cars/$car_id/sentry_mode` | false | Indicates if Sentry Mode is active | +| `teslamate/cars/$car_id/windows_open` | false | Indicates if any of the windows are open | +| `teslamate/cars/$car_id/doors_open` | false | Indicates if any of the doors are open | +| `teslamate/cars/$car_id/driver_front_door_open` | false | Indicates if the driver-side front door is open | +| `teslamate/cars/$car_id/driver_rear_door_open` | false | Indicates if the driver-side rear door is open | +| `teslamate/cars/$car_id/passenger_front_door_open` | false | Indicates if the passenger-side front door is open | +| `teslamate/cars/$car_id/passenger_rear_door_open` | false | Indicates if the passenger-side rear door is open | +| `teslamate/cars/$car_id/trunk_open` | false | Indicates if the trunk is open | +| `teslamate/cars/$car_id/frunk_open` | false | Indicates if the frunk is open | +| `teslamate/cars/$car_id/is_user_present` | false | Indicates if a user is present in the vehicle | +| | | | +| `teslamate/cars/$car_id/is_climate_on` | true | Indicates if the climate control is on | +| `teslamate/cars/$car_id/inside_temp` | 20.8 | Inside Temperature in °C | +| `teslamate/cars/$car_id/outside_temp` | 18.4 | Temperature in °C | +| `teslamate/cars/$car_id/is_preconditioning` | false | Indicates if the vehicle is being preconditioned | +| | | | +| `teslamate/cars/$car_id/odometer` | 1653 | Car odometer in km | +| `teslamate/cars/$car_id/est_battery_range_km` | 372.5 | Estimated Range in km | +| `teslamate/cars/$car_id/rated_battery_range_km` | 401.63 | Rated Range in km | +| `teslamate/cars/$car_id/ideal_battery_range_km` | 335.79 | Ideal Range in km | +| | | | +| `teslamate/cars/$car_id/battery_level` | 88 | Battery Level Percentage | +| `teslamate/cars/$car_id/usable_battery_level` | 85 | Usable battery level percentage | +| `teslamate/cars/$car_id/plugged_in` | true | If car is currently plugged into a charger | +| `teslamate/cars/$car_id/charge_energy_added` | 5.06 | Last added energy in kWh | +| `teslamate/cars/$car_id/charge_limit_soc` | 90 | Charge Limit Configured in Percentage | +| `teslamate/cars/$car_id/charge_port_door_open` | true | Indicates if the charger door is open | +| `teslamate/cars/$car_id/charger_actual_current` | 2.05 | Current amperage supplied by charger | +| `teslamate/cars/$car_id/charger_phases` | 3 | Number of charger power phases (1-3) | +| `teslamate/cars/$car_id/charger_power` | 48.9 | Charger Power | +| `teslamate/cars/$car_id/charger_voltage` | 240 | Charger Voltage | +| `teslamate/cars/$car_id/charge_current_request` | 40 | How many amps the car wants | +| `teslamate/cars/$car_id/charge_current_request_max` | 40 | How many amps the car can have | +| `teslamate/cars/$car_id/scheduled_charging_start_time` | 2019-02-29T23:00:07Z | Start time of the scheduled charge | +| `teslamate/cars/$car_id/time_to_full_charge` | 1.83 | Hours remaining to full charge | +| `teslamate/cars/$car_id/tpms_pressure_fl` | 2.9 | Tire pressure measure in BAR, front left tire | +| `teslamate/cars/$car_id/tpms_pressure_fr` | 2.8 | Tire pressure measure in BAR, front right tire | +| `teslamate/cars/$car_id/tpms_pressure_rl` | 2.9 | Tire pressure measure in BAR, rear left tire | +| `teslamate/cars/$car_id/tpms_pressure_rr` | 2.8 | Tire pressure measure in BAR, rear right tire | +| `teslamate/cars/$car_id/tpms_soft_warning_fl` | true | Indicates if the Tire pressure measure is soft warning, front left tire | +| `teslamate/cars/$car_id/tpms_soft_warning_fr` | false | Indicates if the Tire pressure measure is soft warning, front right tire | +| `teslamate/cars/$car_id/tpms_soft_warning_rl` | false | Indicates if the Tire pressure measure is soft warning, rear left tire | +| `teslamate/cars/$car_id/tpms_soft_warning_rr` | false | Indicates if the Tire pressure measure is soft warning, rear right tire | +| `teslamate/cars/$car_id/active_route_destination` | Home | DEPRECATED: Navigation destination name (or "nil") | +| `teslamate/cars/$car_id/active_route_latitude` | 35.278131 | DEPRECATED: Navigation destination latitude (or "nil") | +| `teslamate/cars/$car_id/active_route_longitude` | 29.744801 | DEPRECATED: Navigation destination longitude (or "nil") | +| `teslamate/cars/$car_id/active_route` | _See below_ | Navigation details (json blob) | +| `teslamate/cars/$car_id/center_display_state` | 0 | Center Display State | :::note `$car_id` usually starts at 1 @@ -96,16 +96,16 @@ Routing to a destination. ```json { - "destination": "Home", - "energy_at_arrival": 73, - "miles_to_arrival": 6.485299, - "minutes_to_arrival": 23.466667, - "traffic_minutes_delay": 0.0, - "location": { - "latitude": 35.278131, - "longitude": 29.744801 - }, - "error": null + "destination": "Home", + "energy_at_arrival": 73, + "miles_to_arrival": 6.485299, + "minutes_to_arrival": 23.466667, + "traffic_minutes_delay": 0.0, + "location": { + "latitude": 35.278131, + "longitude": 29.744801 + }, + "error": null } ``` @@ -113,6 +113,6 @@ Not routing to a destination. ```json { - "error": "No active route available" + "error": "No active route available" } ``` diff --git a/website/docs/maintenance/backup_restore.md b/website/docs/maintenance/backup_restore.md index e70bc86fdc..395ea9e862 100644 --- a/website/docs/maintenance/backup_restore.md +++ b/website/docs/maintenance/backup_restore.md @@ -1,6 +1,7 @@ --- title: Backup and Restore --- + :::note If you are using `docker-compose`, you are using Docker Compose v1, which has been deprecated. Docker Compose commands refer to Docker Compose v2. Consider upgrading your docker setup, see [Migrate to Compose V2](https://docs.docker.com/compose/migrate/) ::: diff --git a/website/docs/maintenance/manually_fixing_data.mdx b/website/docs/maintenance/manually_fixing_data.mdx index cb257c4a2a..a0298d1b86 100644 --- a/website/docs/maintenance/manually_fixing_data.mdx +++ b/website/docs/maintenance/manually_fixing_data.mdx @@ -1,6 +1,7 @@ --- title: Manually fixing data --- + :::note If you are using `docker-compose`, you are using Docker Compose v1, which has been deprecated. Docker Compose commands refer to Docker Compose v2. Consider upgrading your docker setup, see [Migrate to Compose V2](https://docs.docker.com/compose/migrate/) ::: @@ -9,8 +10,8 @@ If you are using `docker-compose`, you are using Docker Compose v1, which has be First you need to find out the ID of the drive or charge: -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; - ## Terminate a drive or charge If for some reason a drive or charge hasn't been fully recorded, for example due to a bug or an unexpected restart, you can terminate it manually. Among other things, this assigns an end date to the drive/charge. @@ -81,27 +81,31 @@ If for some reason a drive or charge was recorded incorrectly, you can delete it 2. Afterwards replace `9999` with the actual ID then run the query: - + - ```sql - DELETE FROM drives WHERE id = 9999; - ``` + ```sql + DELETE FROM drives WHERE id = 9999; + ``` + + - - ```sql - DELETE FROM charging_processes WHERE id = 9999; - ``` + ```sql + DELETE FROM charging_processes WHERE id = 9999; + ``` + + - ## Remove a vehicle from the database @@ -110,19 +114,22 @@ If for some reason a drive or charge was recorded incorrectly, you can delete it 1. Connect to your running TeslaMate database - ```bash - docker compose exec database psql teslamate teslamate - ``` + ```bash + docker compose exec database psql teslamate teslamate + ``` - :::note - If you get the error `No such service: database`, update your _docker-compose.yml_ or use `db` instead of `database` in the above command. - ::: + :::note + If you get the error `No such service: database`, update your _docker-compose.yml_ or use `db` instead of `database` in the above command. + ::: 2. Identify the right car ID in the database to remove + ``` SELECT id, vin FROM cars; ``` -3. Based upon the output, run the following command replacing `num` with the ID from the previous command. + +3. Based upon the output, run the following command replacing `num` with the ID from the previous command. + ``` DELETE FROM cars WHERE id = num; DELETE FROM car_settings WHERE id = num; diff --git a/website/docs/screenshots.mdx b/website/docs/screenshots.mdx index b69342a153..590dd66760 100644 --- a/website/docs/screenshots.mdx +++ b/website/docs/screenshots.mdx @@ -2,88 +2,97 @@ title: Screenshots --- -import useBaseUrl from '@docusaurus/useBaseUrl'; +import useBaseUrl from "@docusaurus/useBaseUrl"; ## Web Interface -Web Interface +Web Interface ## Battery Health -Visited addresses +Visited addresses ## Charge Level -Charge Level +Charge Level ## Charges -Charges +Charges ## Charge Details -Charge Details +Charge Details ## Charging Stats -Charge Stats +Charge Stats ## Drive Stats -Drive Stats +Drive Stats ## Drives -Drives +Drives ## Drive Details -Drive Details +Drive Details ## Efficiency -Efficiency +Efficiency ## Location (addresses) -Location (addresses) +Location (addresses) ## Mileage -Mileage +Mileage ## Overview -Overview +Overview ## Projected Range -Projected Range +Projected Range ## States -States +States ## Statistics -Statistics +Statistics ## Timeline -Timeline +Timeline ## Trip -Trip +Trip ## Updates -Updates +Updates ## Vampire Drain -Vampire Drain +Vampire Drain ## Visited (Lifetime driving map) -Lifetime driving map +Lifetime driving map diff --git a/website/sidebars.js b/website/sidebars.js index f5959572af..afd1f4973b 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -19,7 +19,12 @@ module.exports = { { type: "category", label: "Guides", - items: ["guides/traefik", "guides/apache", "guides/unix_domain_sockets", "guides/api"], + items: [ + "guides/traefik", + "guides/apache", + "guides/unix_domain_sockets", + "guides/api", + ], }, { type: "category",