diff --git a/CLAUDE.md b/CLAUDE.md index 85c7d64f..1443cce3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -293,8 +293,8 @@ Towerops uses Forgejo CI/CD with two trigger paths (see `.forgejo/workflows/`): - Push to `main` triggers `production.yaml` - **Test gate:** ExUnit suite must pass before any image is built - After tests pass: - - Builds Docker image from `k8s/Dockerfile` — `FROM codeberg.org/gmcintire/towerops-base:latest` - - Pushes to `codeberg.org/gmcintire/towerops` (tagged `main--` and `production`) + - Builds Docker image from `k8s/Dockerfile` — `FROM git.mcintire.me/gmcintire/towerops-base:latest` + - Pushes to `git.mcintire.me/gmcintire/towerops` (tagged `main--` and `production`) - `argocd-image-updater` picks up the new tag (~2 min), writes it to the ArgoCD Application's `spec.source.kustomize.images`, and ArgoCD rolls the Deployment. No commit to `k8s/deployment.yaml`. @@ -306,7 +306,7 @@ ship to prod once tests pass. The slow runtime apt installs (`gdal-bin`, `snmp`, `libsnmp40`, `locales`, BEAM runtime libs) live in a prebuilt base image — -`codeberg.org/gmcintire/towerops-base:latest` — so day-to-day app builds +`git.mcintire.me/gmcintire/towerops-base:latest` — so day-to-day app builds skip ~500 MB of GIS dep installation. - **Source**: `k8s/Dockerfile.base` diff --git a/config/config.exs b/config/config.exs index 6e258ee7..dd4e0f73 100644 --- a/config/config.exs +++ b/config/config.exs @@ -136,7 +136,7 @@ config :towerops, :coverage_storage_dir, {:towerops, "priv/static/coverage"} # Agent Docker Image # Override this in runtime.exs or environment-specific config config :towerops, - agent_docker_image: "codeberg.org/towerops-agent/towerops-agent:latest" + agent_docker_image: "git.mcintire.me/towerops-agent/towerops-agent:latest" # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/docs/terraform-coverages.md b/docs/terraform-coverages.md index df372940..9db6bc29 100644 --- a/docs/terraform-coverages.md +++ b/docs/terraform-coverages.md @@ -7,7 +7,7 @@ use Bearer-token auth scoped to a single organization. > Looking for an Ansible-native interface? The official Towerops Ansible > collection wraps these endpoints with idempotent modules: -> . +> . ## Authentication diff --git a/k8s/Dockerfile b/k8s/Dockerfile index c03284a4..8caeac3a 100644 --- a/k8s/Dockerfile +++ b/k8s/Dockerfile @@ -7,7 +7,7 @@ # This file is based on these images: # # - https://hub.docker.com/r/hexpm/elixir/tags - for the build image -# - codeberg.org/gmcintire/towerops-base - prebuilt runtime base (see Dockerfile.base) +# - git.mcintire.me/gmcintire/towerops-base - prebuilt runtime base (see Dockerfile.base) # - Ex: docker.io/hexpm/elixir:1.20.2-erlang-29.0.3-debian-trixie-20260713-slim # ARG ELIXIR_VERSION=1.20.2 @@ -20,7 +20,7 @@ ARG BUILDER_IMAGE="docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION # Built by .forgejo/workflows/build-base.yaml only when Dockerfile.base # changes, so this Dockerfile pulls a ready-made layer instead of # reinstalling gdal (~500 MB) on every push. -ARG BASE_IMAGE="codeberg.org/gmcintire/towerops-base:latest" +ARG BASE_IMAGE="git.mcintire.me/gmcintire/towerops-base:latest" FROM ${BUILDER_IMAGE} AS builder ENV MIX_OS_DEPS_COMPILE_PARTITION_COUNT=6 diff --git a/k8s/Dockerfile.base b/k8s/Dockerfile.base index 8ecb4627..c60ef8ec 100644 --- a/k8s/Dockerfile.base +++ b/k8s/Dockerfile.base @@ -8,7 +8,7 @@ # # Built by `.forgejo/workflows/build-base.yaml` only when this file or # the workflow changes. The app `Dockerfile` does -# `FROM codeberg.org/gmcintire/towerops-base:latest` so everyday code +# `FROM git.mcintire.me/gmcintire/towerops-base:latest` so everyday code # pushes skip the gdal + snmp apt install entirely. # # Bumping the Debian release: change the ARG below and push — the base diff --git a/lib/towerops/agents/release_checker.ex b/lib/towerops/agents/release_checker.ex index b10de77a..026c4032 100644 --- a/lib/towerops/agents/release_checker.ex +++ b/lib/towerops/agents/release_checker.ex @@ -77,7 +77,7 @@ defmodule Towerops.Agents.ReleaseChecker do end defp fetch_and_cache do - url = "https://codeberg.org/api/v1/repos/#{@codeberg_repo}/releases/latest" + url = "https://git.mcintire.me/api/v1/repos/#{@codeberg_repo}/releases/latest" case req_get(url, headers: [{"accept", "application/json"}], retry: false) do {:ok, %Req.Response{status: 200, body: body}} -> diff --git a/lib/towerops/proto/decoder_macros.ex b/lib/towerops/proto/decoder_macros.ex index 1276cbe6..ce09b629 100644 --- a/lib/towerops/proto/decoder_macros.ex +++ b/lib/towerops/proto/decoder_macros.ex @@ -218,154 +218,227 @@ defmodule Towerops.Proto.DecoderMacros do end end - # Generate a field handler clause for a single field - defp build_field_handler_ast(fun, field_num, field_name, type_spec) do + # Generate a field handler clause for a single field (dispatch by type_spec shape) + + defp build_field_handler_ast(fun, field_num, field_name, :string) do fields_fn = :"#{fun}_fields" + build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :string) + end - cond do - type_spec == :string -> - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do - with {:ok, {value, rest2}} <- Wire.decode_bytes(rest) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value}) - end - end + defp build_field_handler_ast(fun, field_num, field_name, :uint) do + fields_fn = :"#{fun}_fields" + build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :uint) + end + + defp build_field_handler_ast(fun, field_num, field_name, :int64) do + fields_fn = :"#{fun}_fields" + build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :int64) + end + + defp build_field_handler_ast(fun, field_num, field_name, :double) do + fields_fn = :"#{fun}_fields" + build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :double) + end + + defp build_field_handler_ast(fun, field_num, field_name, :bool) do + fields_fn = :"#{fun}_fields" + build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :bool) + end + + defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:enum, conv_fn]}) do + fields_fn = :"#{fun}_fields" + build_enum_handler_ast(fun, field_num, field_name, fields_fn, conv_fn) + end + + defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:message, decode_fn]}) do + fields_fn = :"#{fun}_fields" + build_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn) + end + + defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:message, decode_fn, validate_fn]}) do + fields_fn = :"#{fun}_fields" + build_validated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, validate_fn) + end + + defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:repeated_message, decode_fn]}) do + fields_fn = :"#{fun}_fields" + build_repeated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn) + end + + defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:repeated_message, decode_fn, validate_fn]}) do + fields_fn = :"#{fun}_fields" + build_validated_repeated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, validate_fn) + end + + defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:repeated, :string]}) do + fields_fn = :"#{fun}_fields" + build_repeated_string_handler_ast(fun, field_num, field_name, fields_fn) + end + + defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:map, :string, :string]}) do + fields_fn = :"#{fun}_fields" + build_map_handler_ast(fun, field_num, field_name, fields_fn) + end + + defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:oneof, decode_fn, tag]}) do + fields_fn = :"#{fun}_fields" + build_oneof_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, tag) + end + + defp build_field_handler_ast(_fun, _field_num, _field_name, type_spec) do + raise "Unknown field type spec: #{inspect(type_spec)}" + end + + # -- Scalar helpers -- + + defp build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :string) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do + with {:ok, {value, rest2}} <- Wire.decode_bytes(rest) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value}) end + end + end + end - type_spec == :uint -> - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do - with {:ok, {value, rest2}} <- Wire.decode_varint(rest) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value}) - end - end + defp build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :uint) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do + with {:ok, {value, rest2}} <- Wire.decode_varint(rest) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value}) end + end + end + end - type_spec == :int64 -> - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do - with {:ok, {raw, rest2}} <- Wire.decode_varint(rest) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => Wire.decode_int64_value(raw)}) - end - end + defp build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :int64) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do + with {:ok, {raw, rest2}} <- Wire.decode_varint(rest) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => Wire.decode_int64_value(raw)}) end + end + end + end - type_spec == :double -> - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 1, rest}}) do - with {:ok, {value, rest2}} <- Wire.decode_double(rest) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value}) - end - end + defp build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :double) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 1, rest}}) do + with {:ok, {value, rest2}} <- Wire.decode_double(rest) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value}) end + end + end + end - type_spec == :bool -> - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do - with {:ok, {value, rest2}} <- Wire.decode_varint(rest) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value != 0}) - end - end + defp build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :bool) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do + with {:ok, {value, rest2}} <- Wire.decode_varint(rest) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value != 0}) end + end + end + end - # Tuple type specs arrive as {:{}, meta, [tag | args]} - match?({:{}, _, [:enum, _]}, type_spec) -> - {:{}, _, [_, conv_fn]} = type_spec + # -- Enum handler -- - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do - with {:ok, {value, rest2}} <- Wire.decode_varint(rest), - {:ok, converted} <- unquote(conv_fn).(value) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => converted}) - end - end + defp build_enum_handler_ast(fun, field_num, field_name, fields_fn, conv_fn) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do + with {:ok, {value, rest2}} <- Wire.decode_varint(rest), + {:ok, converted} <- unquote(conv_fn).(value) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => converted}) end + end + end + end - match?({:{}, _, [:message, _]}, type_spec) and - not match?({:{}, _, [:message, _, _]}, type_spec) -> - {:{}, _, [_, decode_fn]} = type_spec + # -- Message handlers -- - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do - with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest), - {:ok, msg} <- unquote(decode_fn)(msg_data) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => msg}) - end - end + defp build_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do + with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest), + {:ok, msg} <- unquote(decode_fn)(msg_data) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => msg}) end + end + end + end - match?({:{}, _, [:message, _, _]}, type_spec) -> - {:{}, _, [_, decode_fn, validate_fn]} = type_spec - - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do - with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest), - {:ok, msg} <- unquote(decode_fn)(msg_data), - {:ok, msg} <- unquote(validate_fn)(msg) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => msg}) - end - end + defp build_validated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, validate_fn) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do + with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest), + {:ok, msg} <- unquote(decode_fn)(msg_data), + {:ok, msg} <- unquote(validate_fn)(msg) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => msg}) end + end + end + end - match?({:{}, _, [:repeated_message, _]}, type_spec) and - not match?({:{}, _, [:repeated_message, _, _]}, type_spec) -> - {:{}, _, [_, decode_fn]} = type_spec + # -- Repeated message handlers -- - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do - with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest), - {:ok, msg} <- unquote(decode_fn)(msg_data) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [msg | acc.unquote(field_name)]}) - end - end + defp build_repeated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do + with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest), + {:ok, msg} <- unquote(decode_fn)(msg_data) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [msg | acc.unquote(field_name)]}) end + end + end + end - match?({:{}, _, [:repeated_message, _, _]}, type_spec) -> - {:{}, _, [_, decode_fn, validate_fn]} = type_spec - - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do - with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest), - {:ok, msg} <- unquote(decode_fn)(msg_data), - {:ok, msg} <- unquote(validate_fn)(msg) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [msg | acc.unquote(field_name)]}) - end - end + defp build_validated_repeated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, validate_fn) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do + with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest), + {:ok, msg} <- unquote(decode_fn)(msg_data), + {:ok, msg} <- unquote(validate_fn)(msg) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [msg | acc.unquote(field_name)]}) end + end + end + end - match?({:{}, _, [:repeated, :string]}, type_spec) -> - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do - with {:ok, {value, rest2}} <- Wire.decode_bytes(rest) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [value | acc.unquote(field_name)]}) - end - end + # -- Repeated string handler -- + + defp build_repeated_string_handler_ast(fun, field_num, field_name, fields_fn) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do + with {:ok, {value, rest2}} <- Wire.decode_bytes(rest) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [value | acc.unquote(field_name)]}) end + end + end + end - match?({:{}, _, [:map, :string, :string]}, type_spec) -> - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do - with {:ok, {entry_data, rest2}} <- Wire.decode_bytes(rest), - {:ok, {key, val}} <- decode_map_entry(entry_data) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => Map.put(acc.unquote(field_name), key, val)}) - end - end + # -- Map handler -- + + defp build_map_handler_ast(fun, field_num, field_name, fields_fn) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do + with {:ok, {entry_data, rest2}} <- Wire.decode_bytes(rest), + {:ok, {key, val}} <- decode_map_entry(entry_data) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => Map.put(acc.unquote(field_name), key, val)}) end + end + end + end - match?({:{}, _, [:oneof, _, _]}, type_spec) -> - {:{}, _, [_, decode_fn, tag]} = type_spec + # -- Oneof handler -- - quote do - defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do - with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest), - {:ok, msg} <- unquote(decode_fn)(msg_data) do - unquote(fields_fn)(rest2, %{acc | unquote(field_name) => {unquote(tag), msg}}) - end - end + defp build_oneof_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, tag) do + quote do + defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do + with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest), + {:ok, msg} <- unquote(decode_fn)(msg_data) do + unquote(fields_fn)(rest2, %{acc | unquote(field_name) => {unquote(tag), msg}}) end - - true -> - raise "Unknown field type spec: #{inspect(type_spec)}" + end end end end diff --git a/lib/towerops/snmp/discovery.ex b/lib/towerops/snmp/discovery.ex index 2786a1eb..a14516f5 100644 --- a/lib/towerops/snmp/discovery.ex +++ b/lib/towerops/snmp/discovery.ex @@ -229,6 +229,7 @@ defmodule Towerops.Snmp.Discovery do {:ok, transceivers} = with_timeout(client_opts, fn -> Base.discover_transceivers(client_opts) end, timeouts) Logger.info("Discovering printer supplies...", device_id: device.id) + {:ok, printer_supplies} = with_timeout(client_opts, fn -> Base.discover_printer_supplies(client_opts) end, timeouts) @@ -316,7 +317,8 @@ defmodule Towerops.Snmp.Discovery do timeout: timeouts[:slow], default: {:error, :interface_discovery_timeout} ) do - {:ok, _ifaces} = ok -> ok + {:ok, _ifaces} = ok -> + ok {:error, :interface_discovery_timeout} -> Logger.error("Interface discovery timed out - aborting to prevent data loss", device_id: device_id) @@ -334,7 +336,8 @@ defmodule Towerops.Snmp.Discovery do timeout: timeouts[:slow], default: {:error, :sensor_discovery_timeout} ) do - {:ok, _sensors} = ok -> ok + {:ok, _sensors} = ok -> + ok {:error, :sensor_discovery_timeout} -> Logger.error("Sensor discovery timed out - aborting to prevent data loss", device_id: device_id) @@ -1604,8 +1607,7 @@ defmodule Towerops.Snmp.Discovery do @spec update_device_name_from_snmp(DeviceSchema.t(), system_info()) :: {:ok, DeviceSchema.t()} | {:error, term()} - defp update_device_name_from_snmp(%{name: name} = device, _system_info) - when is_binary(name) and name != "" do + defp update_device_name_from_snmp(%{name: name} = device, _system_info) when is_binary(name) and name != "" do Logger.info("Device name already set or no sysName, skipping update", device_id: device.id) {:ok, device} end diff --git a/lib/towerops_web/controllers/api_docs_html/index.html.heex b/lib/towerops_web/controllers/api_docs_html/index.html.heex index 959e5196..87a618a5 100644 --- a/lib/towerops_web/controllers/api_docs_html/index.html.heex +++ b/lib/towerops_web/controllers/api_docs_html/index.html.heex @@ -243,9 +243,9 @@

Drive Towerops as code. The Ansible collection wraps every resource on this page with idempotent modules: codeberg.org/towerops/ansible. + >git.mcintire.me/towerops/ansible.

@@ -3922,7 +3922,7 @@ curl -G https://towerops.net/api/v1/activity \\ and progresses through computing to ready or failed. The status field is present on every response so polling-based clients (Terraform, the Ansible collection, scripts) can wait for a healthy resource. Outputs include a coloured PNG heatmap, a Float32 GeoTIFF raster, and a downloadable KMZ bundle for Google Earth.

diff --git a/lib/towerops_web/controllers/graphql_docs_html/index.html.heex b/lib/towerops_web/controllers/graphql_docs_html/index.html.heex index 640c40d1..141b9969 100644 --- a/lib/towerops_web/controllers/graphql_docs_html/index.html.heex +++ b/lib/towerops_web/controllers/graphql_docs_html/index.html.heex @@ -326,9 +326,9 @@

Prefer infrastructure-as-code? The Towerops Ansible collection covers every resource exposed here: codeberg.org/towerops/ansible. + >git.mcintire.me/towerops/ansible.

diff --git a/lib/towerops_web/controllers/page_html/home.html.heex b/lib/towerops_web/controllers/page_html/home.html.heex index 716f740c..66884422 100644 --- a/lib/towerops_web/controllers/page_html/home.html.heex +++ b/lib/towerops_web/controllers/page_html/home.html.heex @@ -199,7 +199,7 @@ and starts collecting metrics. No firewall changes, no VPN, no open ports.

- $ docker run -d codeberg.org/towerops-agent/towerops-agent + $ docker run -d git.mcintire.me/towerops-agent/towerops-agent
diff --git a/lib/towerops_web/live/agent_live/index.ex b/lib/towerops_web/live/agent_live/index.ex index 3cc06764..ba6e8d85 100644 --- a/lib/towerops_web/live/agent_live/index.ex +++ b/lib/towerops_web/live/agent_live/index.ex @@ -49,7 +49,7 @@ defmodule ToweropsWeb.AgentLive.Index do Application.get_env( :towerops, :agent_docker_image, - "codeberg.org/towerops-agent/towerops-agent:latest" + "git.mcintire.me/towerops-agent/towerops-agent:latest" ) {:ok, diff --git a/lib/towerops_web/live/graph_live/polling.ex b/lib/towerops_web/live/graph_live/polling.ex index e1ac47a6..33b7791f 100644 --- a/lib/towerops_web/live/graph_live/polling.ex +++ b/lib/towerops_web/live/graph_live/polling.ex @@ -31,9 +31,9 @@ defmodule ToweropsWeb.GraphLive.Polling do @doc """ Poll sensors for live mode, dispatching based on sensor type. """ - def poll_sensors(assigns, client_opts, timestamp_ms) do + def poll_sensors(assigns, client_opts, timestamp_ms, prev_stats) do if assigns.sensor_type == "traffic" do - poll_traffic(assigns, client_opts, timestamp_ms) + poll_traffic(assigns, client_opts, timestamp_ms, prev_stats) else poll_regular_sensors(assigns, client_opts, timestamp_ms) end @@ -62,7 +62,7 @@ defmodule ToweropsWeb.GraphLive.Polling do Poll traffic stats (used from non-state-updating path). Requires two readings to calculate BPS; uses `previous_interface_stats` from assigns. """ - def poll_traffic(assigns, client_opts, timestamp_ms) do + def poll_traffic(assigns, client_opts, timestamp_ms, prev_stats) do device = Snmp.get_device_with_associations(assigns.device_id) if is_nil(device) || Enum.empty?(device.interfaces) do @@ -70,9 +70,9 @@ defmodule ToweropsWeb.GraphLive.Polling do else current_stats = poll_interface_stats_parallel(device.interfaces, client_opts) - if assigns.previous_interface_stats do + if prev_stats do calculate_traffic_bps_from_stats( - assigns.previous_interface_stats, + prev_stats, current_stats, timestamp_ms ) @@ -86,7 +86,7 @@ defmodule ToweropsWeb.GraphLive.Polling do Poll traffic with socket state update. Returns `{data_points, updated_stats}` for the caller to assign back. """ - def poll_traffic_with_state(assigns, client_opts, timestamp_ms) do + def poll_traffic_with_state(assigns, client_opts, timestamp_ms, prev_stats) do device = Snmp.get_device_with_associations(assigns.device_id) if is_nil(device) || Enum.empty?(device.interfaces) do @@ -95,9 +95,9 @@ defmodule ToweropsWeb.GraphLive.Polling do current_stats = poll_interface_stats_parallel(device.interfaces, client_opts) data_points = - if assigns.previous_interface_stats do + if prev_stats do calculate_traffic_bps_from_stats( - assigns.previous_interface_stats, + prev_stats, current_stats, timestamp_ms ) diff --git a/lib/towerops_web/live/graph_live/show.ex b/lib/towerops_web/live/graph_live/show.ex index f1ffb33d..e513e7d0 100644 --- a/lib/towerops_web/live/graph_live/show.ex +++ b/lib/towerops_web/live/graph_live/show.ex @@ -353,7 +353,6 @@ defmodule ToweropsWeb.GraphLive.Show do |> assign(:show_zero_line, sensor_type == "traffic") |> assign(:max_value, nil) |> assign(:min_value, nil) - |> assign(:previous_interface_stats, nil) end defp get_title_suffix(assigns) do @@ -1070,10 +1069,12 @@ defmodule ToweropsWeb.GraphLive.Show do client_opts = Polling.build_snmp_client_opts(device) if socket.assigns.sensor_type == "traffic" do - {points, stats} = Polling.poll_traffic_with_state(socket.assigns, client_opts, timestamp_ms) + prev_stats = Map.get(socket.assigns, :previous_interface_stats) + {points, stats} = Polling.poll_traffic_with_state(socket.assigns, client_opts, timestamp_ms, prev_stats) {points, stats} else - points = Polling.poll_sensors(socket.assigns, client_opts, timestamp_ms) + prev_stats = Map.get(socket.assigns, :previous_interface_stats) + points = Polling.poll_sensors(socket.assigns, client_opts, timestamp_ms, prev_stats) {points, nil} end end diff --git a/lib/towerops_web/live/help_live/sections/about.ex b/lib/towerops_web/live/help_live/sections/about.ex index 3244ccb6..8ba6465c 100644 --- a/lib/towerops_web/live/help_live/sections/about.ex +++ b/lib/towerops_web/live/help_live/sections/about.ex @@ -23,7 +23,7 @@ defmodule ToweropsWeb.HelpLive.Sections.About do
  • No data collection or sharing.
  • Our remote agent is - + fully open source and ONLY collects monitoring jobs from the server and WILL NEVER allow us to access any part of your internal network. diff --git a/lib/towerops_web/live/help_live/sections/agents.ex b/lib/towerops_web/live/help_live/sections/agents.ex index 5273ee71..42736474 100644 --- a/lib/towerops_web/live/help_live/sections/agents.ex +++ b/lib/towerops_web/live/help_live/sections/agents.ex @@ -81,7 +81,7 @@ defmodule ToweropsWeb.HelpLive.Sections.Agents do
    <%= raw(~S[services:
         towerops-agent:
    -    image: codeberg.org/towerops-agent/towerops-agent:latest
    +    image: git.mcintire.me/towerops-agent/towerops-agent:latest
         container_name: towerops-agent
         restart: unless-stopped
         environment:
    diff --git a/lib/towerops_web/live/onboarding_live.html.heex b/lib/towerops_web/live/onboarding_live.html.heex
    index fd2c17ef..b88d2bf4 100644
    --- a/lib/towerops_web/live/onboarding_live.html.heex
    +++ b/lib/towerops_web/live/onboarding_live.html.heex
    @@ -347,7 +347,7 @@
       -e TOWEROPS_TOKEN=<%= if @agent_token_value, do: @agent_token_value, else: "" %> \
       -e TOWEROPS_SERVER=<%= ToweropsWeb.Endpoint.url() %> \
       --network host \
    -  codeberg.org/towerops-agent/towerops-agent:latest
    + git.mcintire.me/towerops-agent/towerops-agent:latest
    diff --git a/priv/proto/agent.proto b/priv/proto/agent.proto index c7cb68eb..47d65620 100644 --- a/priv/proto/agent.proto +++ b/priv/proto/agent.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package towerops.agent; -option go_package = "codeberg.org/towerops-agent/towerops-agent/pb"; +option go_package = "git.mcintire.me/towerops-agent/towerops-agent/pb"; // Configuration received from the API message AgentConfig { diff --git a/test/towerops/agents_test.exs b/test/towerops/agents_test.exs index 7a04b808..6ac8f689 100644 --- a/test/towerops/agents_test.exs +++ b/test/towerops/agents_test.exs @@ -1980,7 +1980,7 @@ defmodule Towerops.AgentsTest do Phoenix.PubSub.subscribe(Towerops.PubSub, "agent:#{agent_token.id}:lifecycle") url = - "https://codeberg.org/towerops-agent/towerops-agent/releases/download/v0.2.0/towerops-agent-linux-amd64" + "https://git.mcintire.me/towerops-agent/towerops-agent/releases/download/v0.2.0/towerops-agent-linux-amd64" checksum = "abc123def456"