Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2b74c926a | ||
|
|
211f1efd5c | ||
|
|
a59ccd293d | ||
|
|
5514047eda | ||
|
|
810f9a66f6 | ||
|
|
3aa66c48af | ||
|
|
97d63eecda | ||
|
|
1d0ced8fd4 | ||
|
|
3ebead7663 | ||
|
|
2882960a8e | ||
|
|
bdf67e96f0 | ||
|
|
d95b8b99ce | ||
|
|
7e6908ef3d | ||
|
|
dd51e89abf |
@@ -18,6 +18,7 @@ jobs:
|
|||||||
- "brain"
|
- "brain"
|
||||||
- "jeeves"
|
- "jeeves"
|
||||||
- "rhapsody-in-green"
|
- "rhapsody-in-green"
|
||||||
|
- "portal-1"
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -19,5 +19,5 @@ jobs:
|
|||||||
python -m python.gitea_flake_lock merge
|
python -m python.gitea_flake_lock merge
|
||||||
--repo "${{ github.repository }}"
|
--repo "${{ github.repository }}"
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
JEEVES_BOT_TOKEN: ${{ secrets.JEEVES_BOT_TOKEN }}
|
||||||
GITEA_URL: https://gitea.tmmworkshop.com
|
GITEA_URL: https://gitea.tmmworkshop.com
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
run: nix flake update
|
run: nix flake update
|
||||||
- name: Create or update flake.lock PR
|
- name: Create or update flake.lock PR
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
JEEVES_BOT_TOKEN: ${{ secrets.JEEVES_BOT_TOKEN }}
|
||||||
GITEA_URL: https://gitea.tmmworkshop.com
|
GITEA_URL: https://gitea.tmmworkshop.com
|
||||||
run: >-
|
run: >-
|
||||||
nix develop .#devShells.x86_64-linux.default -c
|
nix develop .#devShells.x86_64-linux.default -c
|
||||||
|
|||||||
Vendored
-3
@@ -10,7 +10,6 @@
|
|||||||
"aiounifi",
|
"aiounifi",
|
||||||
"alsa",
|
"alsa",
|
||||||
"apiclient",
|
"apiclient",
|
||||||
"apscheduler",
|
|
||||||
"archlinux",
|
"archlinux",
|
||||||
"ashift",
|
"ashift",
|
||||||
"asrouter",
|
"asrouter",
|
||||||
@@ -337,8 +336,6 @@
|
|||||||
"yubioath",
|
"yubioath",
|
||||||
"yzhang",
|
"yzhang",
|
||||||
"zeroconf",
|
"zeroconf",
|
||||||
"zerotier",
|
|
||||||
"zerotierone",
|
|
||||||
"zoxide",
|
"zoxide",
|
||||||
"zram",
|
"zram",
|
||||||
"zstd"
|
"zstd"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
monitoringInterface = "ztwfunumly";
|
monitoringInterface = "tailscale0";
|
||||||
nodeTextfileDir = "/var/lib/prometheus-node-exporter-textfile";
|
nodeTextfileDir = "/var/lib/prometheus-node-exporter-textfile";
|
||||||
|
|
||||||
mkProcessNameTemplate =
|
mkProcessNameTemplate =
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
nixpkgs.hostPlatform = {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
gcc = {
|
||||||
|
arch = "x86-64-v3";
|
||||||
|
tune = "generic";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Generated
+18
-18
@@ -28,11 +28,11 @@
|
|||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "pkgs/firefox-addons",
|
"dir": "pkgs/firefox-addons",
|
||||||
"lastModified": 1787025780,
|
"lastModified": 1788840136,
|
||||||
"narHash": "sha256-NhyLP9G4DFOn/7aYr7K/D7hWrzEGr5EgUBV+lpdmJ24=",
|
"narHash": "sha256-ej5jnQIfjbw4wwPzy8Y4ntG9F3asdF6YBkLqpYeGUxc=",
|
||||||
"owner": "rycee",
|
"owner": "rycee",
|
||||||
"repo": "nur-expressions",
|
"repo": "nur-expressions",
|
||||||
"rev": "5ad360b6d3cb0aa1b61f9cb27fef113ca9117c37",
|
"rev": "555b23e68256c5abfb8a85a385231830d1d7ad1f",
|
||||||
"type": "gitlab"
|
"type": "gitlab"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -49,11 +49,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1786999651,
|
"lastModified": 1788651960,
|
||||||
"narHash": "sha256-MTGMFlLDTklsXhCp4r5GXB4VAVadPdalXLvUjd/K7h0=",
|
"narHash": "sha256-v9wJd32eZ2bvhBzVOd7TIjLQd011P7nwOhjKtWlci5I=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "353742587cbaf079b3caee743115d037bc51fea6",
|
"rev": "2c0350c759688177331b8f5242311fae8877bdb3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -67,11 +67,11 @@
|
|||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1786867632,
|
"lastModified": 1788860136,
|
||||||
"narHash": "sha256-ez+ubZlA1RtdjCB18a6zJ9M4u8qoPDy08EcnsW5M3Xw=",
|
"narHash": "sha256-MhPMOFV4pVkygWEbQ8t1De/uQ9cWF1u++tRe2L5tG48=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixos-hardware",
|
"repo": "nixos-hardware",
|
||||||
"rev": "ff17823245ab9ff7bcae6acf950bd89cba82c38c",
|
"rev": "62173785b9a18c78b4a15aca2623d02bceb9d077",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -96,11 +96,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-master": {
|
"nixpkgs-master": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787081018,
|
"lastModified": 1788892992,
|
||||||
"narHash": "sha256-K0uwZBtZsbBigHAMQW7YWti3gPe6a5ct5bcOw5F+Q9Y=",
|
"narHash": "sha256-cIMFh9gyU4/aLeB3JCcsWM3tTAvD9pAq9Smr1Wa8aIU=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "cacac5ac351a010599d9f9d106acfed25a8e4c77",
|
"rev": "dff6994123e257ec9901c271bc2b52e64d7c8f05",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -128,11 +128,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787001381,
|
"lastModified": 1788752844,
|
||||||
"narHash": "sha256-Ue1Yo8gfHdD4TMtNewhA4tkSYeFqXThju0nCyJc3ALo=",
|
"narHash": "sha256-VaWGJ6+cIYN2erfSecbRV+4ljI185Ty2wUrXyvQbgOw=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ec2d622de0773551768cf98f3fc50cbcc003b9c5",
|
"rev": "dc5d91f840324650bac8c379428c7037a416959a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -162,11 +162,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1786629091,
|
"lastModified": 1788337237,
|
||||||
"narHash": "sha256-gkig4nPi1CWc4Z50GBsjE4ygSE7hMpl/TwID2an2Cck=",
|
"narHash": "sha256-gkSH8VUtCo6hnysNmb9DbTuDepH2t5pv+QWjP75xKAk=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "a8627b21b9107c5711c96b84f32a9a4b3d45295f",
|
"rev": "fbf759290e0cb0a98dfc813a4eb7d53ad1dacb57",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
ps: with ps; [
|
ps: with ps; [
|
||||||
alembic
|
alembic
|
||||||
apprise
|
apprise
|
||||||
apscheduler
|
|
||||||
fastapi
|
fastapi
|
||||||
fastapi-cli
|
fastapi-cli
|
||||||
httpx
|
httpx
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ license = "MIT"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"alembic",
|
"alembic",
|
||||||
"apprise",
|
"apprise",
|
||||||
"apscheduler",
|
|
||||||
"beautifulsoup4",
|
"beautifulsoup4",
|
||||||
"bm25s",
|
"bm25s",
|
||||||
"ebooklib",
|
"ebooklib",
|
||||||
@@ -20,7 +19,6 @@ dependencies = [
|
|||||||
"httpx",
|
"httpx",
|
||||||
"jinja2",
|
"jinja2",
|
||||||
"pgvector",
|
"pgvector",
|
||||||
"polars",
|
|
||||||
"psycopg[binary]",
|
"psycopg[binary]",
|
||||||
"pydantic",
|
"pydantic",
|
||||||
"pydantic-settings",
|
"pydantic-settings",
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class RerankConfig(BaseSettings):
|
|||||||
model_config = SettingsConfigDict(env_prefix="EBOOK_SEARCH_RERANK_", frozen=True, protected_namespaces=())
|
model_config = SettingsConfigDict(env_prefix="EBOOK_SEARCH_RERANK_", frozen=True, protected_namespaces=())
|
||||||
|
|
||||||
enabled: bool = True
|
enabled: bool = True
|
||||||
base_url: str = "http://192.168.90.25:8001"
|
base_url: str = "http://bob:8001"
|
||||||
model: str = "qwen3-reranker-06b"
|
model: str = "qwen3-reranker-06b"
|
||||||
candidates: int = 24
|
candidates: int = 24
|
||||||
timeout_seconds: float = 30.0
|
timeout_seconds: float = 30.0
|
||||||
@@ -67,7 +67,7 @@ class EbookSearchConfig(BaseSettings):
|
|||||||
)
|
)
|
||||||
chat_model: str = "deepseek-v4-flash"
|
chat_model: str = "deepseek-v4-flash"
|
||||||
answer_enabled: bool = True
|
answer_enabled: bool = True
|
||||||
embedding_base_url: str = "http://192.168.90.25:8000/v1"
|
embedding_base_url: str = "http://bob:8000/v1"
|
||||||
embedding_api_key: str = "not-needed"
|
embedding_api_key: str = "not-needed"
|
||||||
embedding_model: str = "qwen3-embedding-0.6b"
|
embedding_model: str = "qwen3-embedding-0.6b"
|
||||||
embedding_batch_size: int = 32
|
embedding_batch_size: int = 32
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "${EBOOK_SEARCH_PORT:-8070}:8070"
|
- "${EBOOK_SEARCH_PORT:-8070}:8070"
|
||||||
extra_hosts:
|
|
||||||
- "jeeves:192.168.90.40"
|
|
||||||
env_file:
|
env_file:
|
||||||
- ../../../.env
|
- ../../../.env
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -84,13 +84,13 @@ def push_branch(*, branch: str) -> None:
|
|||||||
run_cmd(["git", "push", "origin", f"HEAD:{branch}", "--force"])
|
run_cmd(["git", "push", "origin", f"HEAD:{branch}", "--force"])
|
||||||
|
|
||||||
|
|
||||||
def _required_gitea_token() -> str:
|
def _required_jeeves_bot_token() -> str:
|
||||||
"""Read the required Gitea token from the environment."""
|
"""Read the required Gitea token from the environment."""
|
||||||
token = getenv("GITEA_TOKEN")
|
token = getenv("JEEVES_BOT_TOKEN")
|
||||||
if token:
|
if token:
|
||||||
return token
|
return token
|
||||||
|
|
||||||
msg = "GITEA_TOKEN environment variable is required"
|
msg = "JEEVES_BOT_TOKEN environment variable is required"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ def update(
|
|||||||
owner, repo_name = split_repo_name(repo)
|
owner, repo_name = split_repo_name(repo)
|
||||||
with GiteaClient(
|
with GiteaClient(
|
||||||
base_url=getenv("GITEA_URL", DEFAULT_GITEA_URL),
|
base_url=getenv("GITEA_URL", DEFAULT_GITEA_URL),
|
||||||
token=_required_gitea_token(),
|
token=_required_jeeves_bot_token(),
|
||||||
) as client:
|
) as client:
|
||||||
pull_request = ensure_flake_lock_pull_request(
|
pull_request = ensure_flake_lock_pull_request(
|
||||||
client,
|
client,
|
||||||
@@ -134,7 +134,7 @@ def merge(
|
|||||||
owner, repo_name = split_repo_name(repo)
|
owner, repo_name = split_repo_name(repo)
|
||||||
with GiteaClient(
|
with GiteaClient(
|
||||||
base_url=getenv("GITEA_URL", DEFAULT_GITEA_URL),
|
base_url=getenv("GITEA_URL", DEFAULT_GITEA_URL),
|
||||||
token=_required_gitea_token(),
|
token=_required_jeeves_bot_token(),
|
||||||
) as client:
|
) as client:
|
||||||
pull_request = find_flake_lock_pull_request(client, owner=owner, repo=repo_name)
|
pull_request = find_flake_lock_pull_request(client, owner=owner, repo=repo_name)
|
||||||
if not pull_request:
|
if not pull_request:
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
"""Van weather service - fetches weather with masked GPS location."""
|
|
||||||
@@ -1,293 +0,0 @@
|
|||||||
"""Van weather service - fetches weather with masked GPS for privacy."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from datetime import UTC, datetime
|
|
||||||
from typing import Annotated, Any
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
import typer
|
|
||||||
from apscheduler.schedulers.blocking import BlockingScheduler
|
|
||||||
from tenacity import before_sleep_log, retry, stop_after_attempt, wait_fixed
|
|
||||||
|
|
||||||
from python.common import configure_logger
|
|
||||||
from python.van_weather.models import Config, DailyForecast, HourlyForecast, Weather
|
|
||||||
|
|
||||||
# Map Pirate Weather icons to Home Assistant conditions
|
|
||||||
CONDITION_MAP = {
|
|
||||||
"clear-day": "sunny",
|
|
||||||
"clear-night": "clear-night",
|
|
||||||
"rain": "rainy",
|
|
||||||
"snow": "snowy",
|
|
||||||
"sleet": "snowy-rainy",
|
|
||||||
"wind": "windy",
|
|
||||||
"fog": "fog",
|
|
||||||
"cloudy": "cloudy",
|
|
||||||
"partly-cloudy-day": "partlycloudy",
|
|
||||||
"partly-cloudy-night": "partlycloudy",
|
|
||||||
}
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@retry(
|
|
||||||
stop=stop_after_attempt(3),
|
|
||||||
wait=wait_fixed(5),
|
|
||||||
before_sleep=before_sleep_log(logger, logging.WARNING),
|
|
||||||
reraise=True,
|
|
||||||
)
|
|
||||||
def get_ha_state(url: str, token: str, entity_id: str) -> float:
|
|
||||||
"""Get numeric state from Home Asasistant entity."""
|
|
||||||
response = httpx.get(
|
|
||||||
f"{url}/api/states/{entity_id}",
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
timeout=30,
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
state = response.json()["state"]
|
|
||||||
if state in ("unavailable", "unknown"):
|
|
||||||
error = f"{entity_id} is {state}"
|
|
||||||
raise ValueError(error)
|
|
||||||
return float(state)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_daily_forecast(data: dict[str, dict[str, Any]]) -> list[DailyForecast]:
|
|
||||||
"""Parse daily forecast from Pirate Weather API."""
|
|
||||||
daily = data.get("daily", {}).get("data", [])
|
|
||||||
daily_forecasts = []
|
|
||||||
for day in daily[:8]: # Up to 8 days
|
|
||||||
time_stamp = day.get("time")
|
|
||||||
if time_stamp:
|
|
||||||
date_time = datetime.fromtimestamp(time_stamp, tz=UTC).isoformat()
|
|
||||||
daily_forecasts.append(
|
|
||||||
DailyForecast(
|
|
||||||
date_time=date_time,
|
|
||||||
condition=CONDITION_MAP.get(day.get("icon", ""), "cloudy"),
|
|
||||||
temperature=day.get("temperatureHigh"),
|
|
||||||
templow=day.get("temperatureLow"),
|
|
||||||
precipitation_probability=day.get("precipProbability"),
|
|
||||||
moon_phase=day.get("moonPhase"),
|
|
||||||
wind_gust=day.get("windGust"),
|
|
||||||
cloud_cover=day.get("cloudCover"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return daily_forecasts
|
|
||||||
|
|
||||||
|
|
||||||
def parse_hourly_forecast(data: dict[str, dict[str, Any]]) -> list[HourlyForecast]:
|
|
||||||
"""Parse hourly forecast from Pirate Weather API."""
|
|
||||||
hourly = data.get("hourly", {}).get("data", [])
|
|
||||||
hourly_forecasts = []
|
|
||||||
for hour in hourly[:48]: # Up to 48 hours
|
|
||||||
time_stamp = hour.get("time")
|
|
||||||
if time_stamp:
|
|
||||||
date_time = datetime.fromtimestamp(time_stamp, tz=UTC).isoformat()
|
|
||||||
hourly_forecasts.append(
|
|
||||||
HourlyForecast(
|
|
||||||
date_time=date_time,
|
|
||||||
condition=CONDITION_MAP.get(hour.get("icon", ""), "cloudy"),
|
|
||||||
temperature=hour.get("temperature"),
|
|
||||||
precipitation_probability=hour.get("precipProbability"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return hourly_forecasts
|
|
||||||
|
|
||||||
|
|
||||||
@retry(
|
|
||||||
stop=stop_after_attempt(3),
|
|
||||||
wait=wait_fixed(5),
|
|
||||||
before_sleep=before_sleep_log(logger, logging.WARNING),
|
|
||||||
reraise=True,
|
|
||||||
)
|
|
||||||
def fetch_weather(api_key: str, lat: float, lon: float) -> Weather:
|
|
||||||
"""Fetch weather from Pirate Weather API."""
|
|
||||||
url = f"https://api.pirateweather.net/forecast/{api_key}/{lat},{lon}"
|
|
||||||
response = httpx.get(url, params={"units": "us"}, timeout=30)
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
|
|
||||||
daily_forecasts = parse_daily_forecast(data)
|
|
||||||
hourly_forecasts = parse_hourly_forecast(data)
|
|
||||||
|
|
||||||
current = data.get("currently", {})
|
|
||||||
icon = current.get("icon", "")
|
|
||||||
return Weather(
|
|
||||||
temperature=current.get("temperature"),
|
|
||||||
feels_like=current.get("apparentTemperature"),
|
|
||||||
humidity=current.get("humidity"),
|
|
||||||
wind_speed=current.get("windSpeed"),
|
|
||||||
wind_bearing=current.get("windBearing"),
|
|
||||||
condition=CONDITION_MAP.get(icon, "cloudy"),
|
|
||||||
summary=current.get("summary"),
|
|
||||||
pressure=current.get("pressure"),
|
|
||||||
visibility=current.get("visibility"),
|
|
||||||
uv_index=current.get("uvIndex"),
|
|
||||||
ozone=current.get("ozone"),
|
|
||||||
nearest_storm_distance=current.get("nearestStormDistance"),
|
|
||||||
nearest_storm_bearing=current.get("nearestStormBearing"),
|
|
||||||
precip_probability=current.get("precipProbability"),
|
|
||||||
cloud_cover=current.get("cloudCover"),
|
|
||||||
daily_forecasts=daily_forecasts,
|
|
||||||
hourly_forecasts=hourly_forecasts,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@retry(
|
|
||||||
stop=stop_after_attempt(3),
|
|
||||||
wait=wait_fixed(5),
|
|
||||||
before_sleep=before_sleep_log(logger, logging.WARNING),
|
|
||||||
reraise=True,
|
|
||||||
)
|
|
||||||
def post_to_ha(url: str, token: str, weather: Weather) -> None:
|
|
||||||
"""Post weather data to Home Assistant as sensor entities."""
|
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
|
||||||
|
|
||||||
# Post current weather as individual sensors
|
|
||||||
sensors = {
|
|
||||||
"sensor.van_weather_condition": {
|
|
||||||
"state": weather.condition or "unknown",
|
|
||||||
"attributes": {"friendly_name": "Van Weather Condition"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_temperature": {
|
|
||||||
"state": weather.temperature,
|
|
||||||
"attributes": {"unit_of_measurement": "°F", "device_class": "temperature"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_apparent_temperature": {
|
|
||||||
"state": weather.feels_like,
|
|
||||||
"attributes": {"unit_of_measurement": "°F", "device_class": "temperature"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_humidity": {
|
|
||||||
"state": int((weather.humidity or 0) * 100),
|
|
||||||
"attributes": {"unit_of_measurement": "%", "device_class": "humidity"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_pressure": {
|
|
||||||
"state": weather.pressure,
|
|
||||||
"attributes": {"unit_of_measurement": "mbar", "device_class": "pressure"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_wind_speed": {
|
|
||||||
"state": weather.wind_speed,
|
|
||||||
"attributes": {"unit_of_measurement": "mph", "device_class": "wind_speed"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_wind_bearing": {
|
|
||||||
"state": weather.wind_bearing,
|
|
||||||
"attributes": {"unit_of_measurement": "°"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_visibility": {
|
|
||||||
"state": weather.visibility,
|
|
||||||
"attributes": {"unit_of_measurement": "mi"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_uv_index": {
|
|
||||||
"state": weather.uv_index,
|
|
||||||
"attributes": {"friendly_name": "Van Weather UV Index", "icon": "mdi:sun-wireless"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_ozone": {
|
|
||||||
"state": weather.ozone,
|
|
||||||
"attributes": {"unit_of_measurement": "DU", "icon": "mdi:earth"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_nearest_storm_distance": {
|
|
||||||
"state": weather.nearest_storm_distance,
|
|
||||||
"attributes": {"unit_of_measurement": "mi", "icon": "mdi:weather-lightning"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_nearest_storm_bearing": {
|
|
||||||
"state": weather.nearest_storm_bearing,
|
|
||||||
"attributes": {"unit_of_measurement": "°", "icon": "mdi:weather-lightning"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_precip_probability": {
|
|
||||||
"state": int((weather.precip_probability or 0) * 100),
|
|
||||||
"attributes": {"unit_of_measurement": "%", "icon": "mdi:weather-rainy"},
|
|
||||||
},
|
|
||||||
"sensor.van_weather_cloud_cover": {
|
|
||||||
"state": int((weather.cloud_cover or 0) * 100),
|
|
||||||
"attributes": {"unit_of_measurement": "%", "icon": "mdi:weather-cloudy"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for entity_id, data in sensors.items():
|
|
||||||
if data["state"] is not None:
|
|
||||||
response = httpx.post(f"{url}/api/states/{entity_id}", headers=headers, json=data, timeout=30)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
# Post daily forecast as JSON attribute sensor
|
|
||||||
daily_forecast = [
|
|
||||||
{
|
|
||||||
"datetime": daily_forecast.date_time.isoformat(),
|
|
||||||
"condition": daily_forecast.condition,
|
|
||||||
"temperature": daily_forecast.temperature,
|
|
||||||
"templow": daily_forecast.templow,
|
|
||||||
"precipitation_probability": int((daily_forecast.precipitation_probability or 0) * 100),
|
|
||||||
}
|
|
||||||
for daily_forecast in weather.daily_forecasts
|
|
||||||
]
|
|
||||||
|
|
||||||
response = httpx.post(
|
|
||||||
f"{url}/api/states/sensor.van_weather_forecast_daily",
|
|
||||||
headers=headers,
|
|
||||||
json={"state": len(daily_forecast), "attributes": {"forecast": daily_forecast}},
|
|
||||||
timeout=30,
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
# Post hourly forecast as JSON attribute sensor
|
|
||||||
hourly_forecast = [
|
|
||||||
{
|
|
||||||
"datetime": hourly_forecast.date_time.isoformat(),
|
|
||||||
"condition": hourly_forecast.condition,
|
|
||||||
"temperature": hourly_forecast.temperature,
|
|
||||||
"precipitation_probability": int((hourly_forecast.precipitation_probability or 0) * 100),
|
|
||||||
}
|
|
||||||
for hourly_forecast in weather.hourly_forecasts
|
|
||||||
]
|
|
||||||
|
|
||||||
response = httpx.post(
|
|
||||||
f"{url}/api/states/sensor.van_weather_forecast_hourly",
|
|
||||||
headers=headers,
|
|
||||||
json={"state": len(hourly_forecast), "attributes": {"forecast": hourly_forecast}},
|
|
||||||
timeout=30,
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
|
|
||||||
def update_weather(config: Config) -> None:
|
|
||||||
"""Fetch weather using last-known location, post to HA."""
|
|
||||||
lat = get_ha_state(config.ha_url, config.ha_token, config.lat_entity)
|
|
||||||
lon = get_ha_state(config.ha_url, config.ha_token, config.lon_entity)
|
|
||||||
|
|
||||||
masked_lat = round(lat, config.mask_decimals)
|
|
||||||
masked_lon = round(lon, config.mask_decimals)
|
|
||||||
|
|
||||||
logger.info(f"Masked location: {masked_lat}, {masked_lon}")
|
|
||||||
|
|
||||||
weather = fetch_weather(config.pirate_weather_api_key, masked_lat, masked_lon)
|
|
||||||
logger.info(f"Weather: {weather.temperature}°F, {weather.condition}")
|
|
||||||
|
|
||||||
post_to_ha(config.ha_url, config.ha_token, weather)
|
|
||||||
logger.info("Posted weather to HA")
|
|
||||||
|
|
||||||
|
|
||||||
def main(
|
|
||||||
ha_url: Annotated[str, typer.Option(envvar="HA_URL")],
|
|
||||||
ha_token: Annotated[str, typer.Option(envvar="HA_TOKEN")],
|
|
||||||
api_key: Annotated[str, typer.Option(envvar="PIRATE_WEATHER_API_KEY")],
|
|
||||||
interval: Annotated[int, typer.Option(help="Poll interval in seconds")] = 900,
|
|
||||||
log_level: Annotated[str, typer.Option()] = "INFO",
|
|
||||||
) -> None:
|
|
||||||
"""Fetch weather for van using masked GPS location."""
|
|
||||||
configure_logger(log_level)
|
|
||||||
|
|
||||||
config = Config(ha_url=ha_url, ha_token=ha_token, pirate_weather_api_key=api_key)
|
|
||||||
|
|
||||||
logger.info(f"Starting van weather service, polling every {interval}s")
|
|
||||||
|
|
||||||
scheduler = BlockingScheduler()
|
|
||||||
scheduler.add_job(
|
|
||||||
update_weather,
|
|
||||||
"interval",
|
|
||||||
seconds=interval,
|
|
||||||
args=[config],
|
|
||||||
next_run_time=datetime.now(UTC),
|
|
||||||
)
|
|
||||||
scheduler.start()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
typer.run(main)
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
"""Models for van weather service."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from datetime import datetime # noqa: TC003 This is required for pydantic
|
|
||||||
|
|
||||||
from pydantic import BaseModel, field_serializer
|
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseModel):
|
|
||||||
"""Service configuration."""
|
|
||||||
|
|
||||||
ha_url: str
|
|
||||||
ha_token: str
|
|
||||||
pirate_weather_api_key: str
|
|
||||||
lat_entity: str = "sensor.van_last_known_latitude"
|
|
||||||
lon_entity: str = "sensor.van_last_known_longitude"
|
|
||||||
mask_decimals: int = 1 # ~11km accuracy
|
|
||||||
|
|
||||||
|
|
||||||
class DailyForecast(BaseModel):
|
|
||||||
"""Daily forecast entry."""
|
|
||||||
|
|
||||||
date_time: datetime
|
|
||||||
condition: str | None = None
|
|
||||||
temperature: float | None = None # High
|
|
||||||
templow: float | None = None # Low
|
|
||||||
precipitation_probability: float | None = None
|
|
||||||
moon_phase: float | None = None
|
|
||||||
wind_gust: float | None = None
|
|
||||||
cloud_cover: float | None = None
|
|
||||||
|
|
||||||
@field_serializer("date_time")
|
|
||||||
def serialize_date_time(self, date_time: datetime) -> str:
|
|
||||||
"""Serialize datetime to ISO format."""
|
|
||||||
return date_time.isoformat()
|
|
||||||
|
|
||||||
|
|
||||||
class HourlyForecast(BaseModel):
|
|
||||||
"""Hourly forecast entry."""
|
|
||||||
|
|
||||||
date_time: datetime
|
|
||||||
condition: str | None = None
|
|
||||||
temperature: float | None = None
|
|
||||||
precipitation_probability: float | None = None
|
|
||||||
|
|
||||||
@field_serializer("date_time")
|
|
||||||
def serialize_date_time(self, date_time: datetime) -> str:
|
|
||||||
"""Serialize datetime to ISO format."""
|
|
||||||
return date_time.isoformat()
|
|
||||||
|
|
||||||
|
|
||||||
class Weather(BaseModel):
|
|
||||||
"""Weather data from Pirate Weather."""
|
|
||||||
|
|
||||||
temperature: float | None = None
|
|
||||||
feels_like: float | None = None
|
|
||||||
humidity: float | None = None
|
|
||||||
wind_speed: float | None = None
|
|
||||||
wind_bearing: float | None = None
|
|
||||||
condition: str | None = None
|
|
||||||
summary: str | None = None
|
|
||||||
pressure: float | None = None
|
|
||||||
visibility: float | None = None
|
|
||||||
uv_index: float | None = None
|
|
||||||
ozone: float | None = None
|
|
||||||
nearest_storm_distance: float | None = None
|
|
||||||
nearest_storm_bearing: float | None = None
|
|
||||||
precip_probability: float | None = None
|
|
||||||
cloud_cover: float | None = None
|
|
||||||
daily_forecasts: list[DailyForecast] = []
|
|
||||||
hourly_forecasts: list[HourlyForecast] = []
|
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"${inputs.self}/common/optional/systemd-boot.nix"
|
"${inputs.self}/common/optional/systemd-boot.nix"
|
||||||
"${inputs.self}/common/optional/tailscale.nix"
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
"${inputs.self}/common/optional/update.nix"
|
"${inputs.self}/common/optional/update.nix"
|
||||||
"${inputs.self}/common/optional/yubikey.nix"
|
"${inputs.self}/common/optional/x86-64-v3.nix"
|
||||||
"${inputs.self}/common/optional/zfs"
|
"${inputs.self}/common/optional/zfs"
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
./syncthing.nix
|
./syncthing.nix
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"${inputs.self}/common/optional/systemd-boot.nix"
|
"${inputs.self}/common/optional/systemd-boot.nix"
|
||||||
"${inputs.self}/common/optional/tailscale.nix"
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
"${inputs.self}/common/optional/update.nix"
|
"${inputs.self}/common/optional/update.nix"
|
||||||
|
"${inputs.self}/common/optional/x86-64-v3.nix"
|
||||||
"${inputs.self}/common/optional/zfs"
|
"${inputs.self}/common/optional/zfs"
|
||||||
./docker
|
./docker
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
|
|||||||
@@ -79,14 +79,13 @@
|
|||||||
extraComponents = [ "isal" ];
|
extraComponents = [ "isal" ];
|
||||||
customComponents = with pkgs.home-assistant-custom-components; [
|
customComponents = with pkgs.home-assistant-custom-components; [
|
||||||
garmin_connect
|
garmin_connect
|
||||||
pirate-weather
|
|
||||||
];
|
];
|
||||||
|
|
||||||
};
|
};
|
||||||
esphome = {
|
esphome = {
|
||||||
enable = true;
|
enable = true;
|
||||||
openFirewall = true;
|
openFirewall = true;
|
||||||
address = "192.168.90.35";
|
address = "0.0.0.0";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
local all richie trust
|
local all richie trust
|
||||||
host all richie 127.0.0.1/32 trust
|
host all richie 127.0.0.1/32 trust
|
||||||
host all richie ::1/128 trust
|
host all richie ::1/128 trust
|
||||||
host all richie 192.168.90.1/24 trust
|
host all richie 100.64.0.0/10 trust
|
||||||
host all richie 192.168.99.1/24 trust
|
host all richie 192.168.99.1/24 trust
|
||||||
|
|
||||||
local vaninventory vaninventory trust
|
local vaninventory vaninventory trust
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
local hass hass trust
|
local hass hass trust
|
||||||
|
|
||||||
# ipv4
|
# ipv4
|
||||||
host hass hass 192.168.90.1/24 trust
|
host hass hass 100.64.0.0/10 trust
|
||||||
host hass hass 127.0.0.1/32 trust
|
host hass hass 127.0.0.1/32 trust
|
||||||
|
|
||||||
# ipv6
|
# ipv6
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
services.syncthing = {
|
services.syncthing = {
|
||||||
overrideFolders = false;
|
overrideFolders = false;
|
||||||
guiAddress = "192.168.90.35:8384";
|
guiAddress = "0.0.0.0:8384";
|
||||||
settings = {
|
settings = {
|
||||||
"dotfiles" = {
|
"dotfiles" = {
|
||||||
path = "/home/richie/dotfiles";
|
path = "/home/richie/dotfiles";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{ inputs, ... }:
|
{ inputs, lib, ... }:
|
||||||
let
|
let
|
||||||
vars = import ./vars.nix;
|
vars = import ./vars.nix;
|
||||||
in
|
in
|
||||||
@@ -15,16 +15,21 @@ in
|
|||||||
"${inputs.self}/common/optional/syncthing_base.nix"
|
"${inputs.self}/common/optional/syncthing_base.nix"
|
||||||
"${inputs.self}/common/optional/tailscale.nix"
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
"${inputs.self}/common/optional/update.nix"
|
"${inputs.self}/common/optional/update.nix"
|
||||||
|
"${inputs.self}/common/optional/x86-64-v3.nix"
|
||||||
"${inputs.self}/common/optional/zfs"
|
"${inputs.self}/common/optional/zfs"
|
||||||
./monitoring
|
./monitoring
|
||||||
./docker
|
./docker
|
||||||
./services
|
./services
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
./networking.nix
|
./networking
|
||||||
./programs.nix
|
./programs.nix
|
||||||
./runners
|
./runners
|
||||||
./syncthing.nix
|
./syncthing.nix
|
||||||
];
|
];
|
||||||
|
nix.settings.system-features = lib.mkAfter [
|
||||||
|
"gccarch-x86-64-v2"
|
||||||
|
"gccarch-x86-64-v3"
|
||||||
|
];
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
openssh.ports = [ 629 ];
|
openssh.ports = [ 629 ];
|
||||||
@@ -35,14 +40,6 @@ in
|
|||||||
path = ./snapshot_config.toml;
|
path = ./snapshot_config.toml;
|
||||||
EnvironmentFile = "${vars.secrets}/services/snapshot_manager";
|
EnvironmentFile = "${vars.secrets}/services/snapshot_manager";
|
||||||
};
|
};
|
||||||
|
|
||||||
zerotierone = {
|
|
||||||
enable = true;
|
|
||||||
joinNetworks = [
|
|
||||||
"e4da7455b2ae64ca"
|
|
||||||
"a09acf02330d37b9"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
users.groups = {
|
users.groups = {
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ let
|
|||||||
vars = import ../vars.nix;
|
vars = import ../vars.nix;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
8989
|
|
||||||
];
|
|
||||||
virtualisation.oci-containers.containers.signal_cli_rest_api = {
|
virtualisation.oci-containers.containers.signal_cli_rest_api = {
|
||||||
image = "bbernhard/signal-cli-rest-api:0.199-dev";
|
image = "bbernhard/signal-cli-rest-api:0.199-dev";
|
||||||
ports = [
|
ports = [
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Monitoring
|
||||||
|
|
||||||
|
## Vultr API metrics
|
||||||
|
|
||||||
|
The `vultr-exporter` service reads its API token from:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/zfs/storage/secrets/services/vultr-exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the file on Jeeves as root with the following contents:
|
||||||
|
|
||||||
|
```text
|
||||||
|
API_KEY=<Vultr API token>
|
||||||
|
```
|
||||||
|
|
||||||
|
The token needs read access to the Vultr Account and Billing APIs. Unrelated
|
||||||
|
resource collectors are disabled in the packaged exporter.
|
||||||
|
|
||||||
|
Restrict the file to root and ensure the public egress IP used by Jeeves is
|
||||||
|
allowed for the token in the Vultr API settings:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo chown root:root /zfs/storage/secrets/services/vultr-exporter
|
||||||
|
sudo chmod 600 /zfs/storage/secrets/services/vultr-exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
The exporter listens on `127.0.0.1:9188`; it is scraped by the local
|
||||||
|
`prometheus-main` service every five minutes and is not exposed through the
|
||||||
|
host firewall.
|
||||||
|
|
||||||
|
Portal-1 exposes its node exporter only through `tailscale0` on port `9100`.
|
||||||
|
Jeeves reaches it using the Portal-1 Tailscale hostname.
|
||||||
@@ -0,0 +1,798 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana",
|
||||||
|
"uid": "-- Grafana --"
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": false,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 1,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"unit": "short",
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mappings": [
|
||||||
|
{
|
||||||
|
"options": {
|
||||||
|
"0": {
|
||||||
|
"color": "red",
|
||||||
|
"index": 1,
|
||||||
|
"text": "Offline"
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"color": "green",
|
||||||
|
"index": 0,
|
||||||
|
"text": "Online"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 5,
|
||||||
|
"w": 4,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "up{job=\"node\",instance=\"portal-1\"}",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"range": false,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Node Exporter",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"unit": "percent",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 90
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 5,
|
||||||
|
"w": 5,
|
||||||
|
"x": 4,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "100 * (1 - avg(rate(node_cpu_seconds_total{job=\"node\",instance=\"portal-1\",mode=\"idle\"}[5m])))",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"range": false,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "CPU Used",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"unit": "percent",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 90
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 5,
|
||||||
|
"w": 5,
|
||||||
|
"x": 9,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "100 * (1 - (node_memory_MemAvailable_bytes{job=\"node\",instance=\"portal-1\"} / node_memory_MemTotal_bytes{job=\"node\",instance=\"portal-1\"}))",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"range": false,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "RAM Used",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"unit": "percent",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 90
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 5,
|
||||||
|
"w": 5,
|
||||||
|
"x": 14,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "100 * (1 - (node_filesystem_avail_bytes{job=\"node\",instance=\"portal-1\",mountpoint=\"/\",fstype!=\"\"} / node_filesystem_size_bytes{job=\"node\",instance=\"portal-1\",mountpoint=\"/\",fstype!=\"\"}))",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"range": false,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Root Filesystem Used",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 5,
|
||||||
|
"w": 5,
|
||||||
|
"x": 19,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "node_load1{job=\"node\",instance=\"portal-1\"}",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"range": false,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Load (1m)",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"unit": "percent",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 90
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "100 * (1 - avg(rate(node_cpu_seconds_total{job=\"node\",instance=\"portal-1\",mode=\"idle\"}[5m])))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "CPU used",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "CPU Usage",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"unit": "percent",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 90
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "100 * (1 - (node_memory_MemAvailable_bytes{job=\"node\",instance=\"portal-1\"} / node_memory_MemTotal_bytes{job=\"node\",instance=\"portal-1\"}))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "RAM used",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "RAM Usage",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"unit": "percent",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 90
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 13
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"showHeader": true,
|
||||||
|
"sortBy": [
|
||||||
|
{
|
||||||
|
"desc": true,
|
||||||
|
"displayName": "Value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "100 * (1 - (node_filesystem_avail_bytes{job=\"node\",instance=\"portal-1\",fstype!~\"tmpfs|devtmpfs|overlay|squashfs\",mountpoint!~\"/run.*\"} / node_filesystem_size_bytes{job=\"node\",instance=\"portal-1\",fstype!~\"tmpfs|devtmpfs|overlay|squashfs\",mountpoint!~\"/run.*\"}))",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "{{mountpoint}}",
|
||||||
|
"range": false,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Filesystem Usage",
|
||||||
|
"type": "table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"unit": "Bps"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 13
|
||||||
|
},
|
||||||
|
"id": 9,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(rate(node_disk_read_bytes_total{job=\"node\",instance=\"portal-1\",device!~\"loop.*|ram.*|fd.*\"}[5m]))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "read",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(rate(node_disk_written_bytes_total{job=\"node\",instance=\"portal-1\",device!~\"loop.*|ram.*|fd.*\"}[5m]))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "write",
|
||||||
|
"range": true,
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Disk Throughput",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"unit": "iops"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 21
|
||||||
|
},
|
||||||
|
"id": 10,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(rate(node_disk_reads_completed_total{job=\"node\",instance=\"portal-1\",device!~\"loop.*|ram.*|fd.*\"}[5m]))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "reads",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(rate(node_disk_writes_completed_total{job=\"node\",instance=\"portal-1\",device!~\"loop.*|ram.*|fd.*\"}[5m]))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "writes",
|
||||||
|
"range": true,
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Disk Operations",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"unit": "Bps"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 21
|
||||||
|
},
|
||||||
|
"id": 11,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(rate(node_network_receive_bytes_total{job=\"node\",instance=\"portal-1\",device!=\"lo\"}[5m]))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "received",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-main"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(rate(node_network_transmit_bytes_total{job=\"node\",instance=\"portal-1\",device!=\"lo\"}[5m]))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "sent",
|
||||||
|
"range": true,
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Network Traffic",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "30s",
|
||||||
|
"schemaVersion": 39,
|
||||||
|
"tags": [
|
||||||
|
"monitoring",
|
||||||
|
"portal-1",
|
||||||
|
"vultr"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-24h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "Portal-1 Host",
|
||||||
|
"uid": "portal-1-host",
|
||||||
|
"version": 1,
|
||||||
|
"weekStart": ""
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -41,29 +41,36 @@ let
|
|||||||
{
|
{
|
||||||
job_name = "node";
|
job_name = "node";
|
||||||
static_configs = [
|
static_configs = [
|
||||||
(mkTarget "jeeves" "192.168.90.40:9100")
|
(mkTarget "jeeves" "jeeves.taile39c31.ts.net:9100")
|
||||||
(mkTarget "bob" "192.168.90.25:9100")
|
(mkTarget "bob" "bob.taile39c31.ts.net:9100")
|
||||||
|
(mkTarget "portal-1" "portal-1.taile39c31.ts.net:9100")
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
job_name = "vultr";
|
||||||
|
scrape_interval = "5m";
|
||||||
|
scrape_timeout = "45s";
|
||||||
|
static_configs = [ (mkTarget "vultr" "127.0.0.1:9188") ];
|
||||||
|
}
|
||||||
{
|
{
|
||||||
job_name = "process_grouped";
|
job_name = "process_grouped";
|
||||||
static_configs = [
|
static_configs = [
|
||||||
(mkTarget "jeeves" "192.168.90.40:9256")
|
(mkTarget "jeeves" "jeeves.taile39c31.ts.net:9256")
|
||||||
(mkTarget "bob" "192.168.90.25:9256")
|
(mkTarget "bob" "bob.taile39c31.ts.net:9256")
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
job_name = "smartctl";
|
job_name = "smartctl";
|
||||||
static_configs = [
|
static_configs = [
|
||||||
(mkTarget "jeeves" "192.168.90.40:9633")
|
(mkTarget "jeeves" "jeeves.taile39c31.ts.net:9633")
|
||||||
(mkTarget "bob" "192.168.90.25:9633")
|
(mkTarget "bob" "bob.taile39c31.ts.net:9633")
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
job_name = "zfs";
|
job_name = "zfs";
|
||||||
static_configs = [
|
static_configs = [
|
||||||
(mkTarget "jeeves" "192.168.90.40:9134")
|
(mkTarget "jeeves" "jeeves.taile39c31.ts.net:9134")
|
||||||
(mkTarget "bob" "192.168.90.25:9134")
|
(mkTarget "bob" "bob.taile39c31.ts.net:9134")
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -79,8 +86,8 @@ let
|
|||||||
{
|
{
|
||||||
job_name = "process_pid";
|
job_name = "process_pid";
|
||||||
static_configs = [
|
static_configs = [
|
||||||
(mkTarget "jeeves" "192.168.90.40:9257")
|
(mkTarget "jeeves" "jeeves.taile39c31.ts.net:9257")
|
||||||
(mkTarget "bob" "192.168.90.25:9257")
|
(mkTarget "bob" "bob.taile39c31.ts.net:9257")
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -97,7 +104,9 @@ let
|
|||||||
after = [
|
after = [
|
||||||
"zfs-media-database-prometheus.mount"
|
"zfs-media-database-prometheus.mount"
|
||||||
"network.target"
|
"network.target"
|
||||||
|
"tailscaled-autoconnect.service"
|
||||||
];
|
];
|
||||||
|
wants = [ "tailscaled-autoconnect.service" ];
|
||||||
requires = [ "zfs-media-database-prometheus.mount" ];
|
requires = [ "zfs-media-database-prometheus.mount" ];
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
unitConfig.RequiresMountsFor = [ dataDir ];
|
unitConfig.RequiresMountsFor = [ dataDir ];
|
||||||
@@ -151,6 +160,8 @@ let
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
imports = [ ./vultr-exporter.nix ];
|
||||||
|
|
||||||
users = {
|
users = {
|
||||||
groups.prometheus = { };
|
groups.prometheus = { };
|
||||||
users.prometheus = {
|
users.prometheus = {
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
vars = import ../vars.nix;
|
||||||
|
|
||||||
|
vultrExporter = pkgs.buildGoModule rec {
|
||||||
|
pname = "vultr-exporter";
|
||||||
|
version = "0-unstable-2025-10-14";
|
||||||
|
|
||||||
|
src = pkgs.fetchFromGitHub {
|
||||||
|
owner = "DazWilkin";
|
||||||
|
repo = "vultr-exporter";
|
||||||
|
rev = "d8b5d7107c2eda31566e6e895c8a67db32fdaf68";
|
||||||
|
hash = "sha256-3LpXh+mFMBRJfiY2ftELpM3AIE0LiZ9c6NFok0OeH5I=";
|
||||||
|
};
|
||||||
|
|
||||||
|
vendorHash = "sha256-qCHWmg6GxLIjqlf5UqRcuDRd1xqRFI3sl4ZduLajx+Y=";
|
||||||
|
subPackages = [ "cmd/server" ];
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
substituteInPlace cmd/server/main.go \
|
||||||
|
--replace-fail 'registry.MustRegister(collector.NewBlockStorageCollector(s, client, log))' "" \
|
||||||
|
--replace-fail 'registry.MustRegister(collector.NewKubernetesCollector(s, client, log))' "" \
|
||||||
|
--replace-fail 'registry.MustRegister(collector.NewLoadBalancerCollector(s, client, log))' "" \
|
||||||
|
--replace-fail 'registry.MustRegister(collector.NewReservedIPsCollector(s, client, log))' ""
|
||||||
|
'';
|
||||||
|
|
||||||
|
ldflags = [
|
||||||
|
"-s"
|
||||||
|
"-w"
|
||||||
|
"-X main.GitCommit=${src.rev}"
|
||||||
|
"-X main.OSVersion=NixOS"
|
||||||
|
];
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
mv $out/bin/server $out/bin/vultr-exporter
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Prometheus exporter for Vultr account, billing, and bandwidth metrics";
|
||||||
|
homepage = "https://github.com/DazWilkin/vultr-exporter";
|
||||||
|
license = lib.licenses.asl20;
|
||||||
|
mainProgram = "vultr-exporter";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
systemd.services.vultr-exporter = {
|
||||||
|
description = "Vultr Prometheus exporter";
|
||||||
|
documentation = [ "https://github.com/DazWilkin/vultr-exporter" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${lib.getExe vultrExporter} --endpoint=127.0.0.1:9188 --path=/metrics";
|
||||||
|
EnvironmentFile = "${vars.secrets}/services/vultr-exporter";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = "30s";
|
||||||
|
|
||||||
|
CapabilityBoundingSet = [ "" ];
|
||||||
|
DeviceAllow = [ "" ];
|
||||||
|
DynamicUser = true;
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateDevices = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
RemoveIPC = true;
|
||||||
|
RestrictAddressFamilies = [
|
||||||
|
"AF_INET"
|
||||||
|
"AF_INET6"
|
||||||
|
];
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
SystemCallFilter = [
|
||||||
|
"@system-service"
|
||||||
|
"~@privileged"
|
||||||
|
];
|
||||||
|
UMask = "0077";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./firewall.nix
|
||||||
|
./interfaces.nix
|
||||||
|
];
|
||||||
|
networking = {
|
||||||
|
hostName = "jeeves";
|
||||||
|
hostId = "0e15ce35";
|
||||||
|
useNetworkd = true;
|
||||||
|
# Without this, unconfigured NICs get a catch-all DHCP network and
|
||||||
|
# join the LAN uninvited (how the dead atlantic card ended up
|
||||||
|
# ARP-poisoning 192.168.99.14). Every interface must be configured
|
||||||
|
# explicitly below.
|
||||||
|
useDHCP = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
let
|
||||||
|
ports = {
|
||||||
|
audiobookshelf = 8000;
|
||||||
|
ebook_searcher = 8070;
|
||||||
|
gems = 8002;
|
||||||
|
gitea_http = 6443;
|
||||||
|
gitea_ssh = 2223;
|
||||||
|
grafana = 3000;
|
||||||
|
jellyfin_http = 8096;
|
||||||
|
nix_binary_cache = 5000;
|
||||||
|
nornsight = 8001;
|
||||||
|
ollama = 11434;
|
||||||
|
open_webui = 8080;
|
||||||
|
postgresql = 5432;
|
||||||
|
share = 8091;
|
||||||
|
signal_cli = 8989;
|
||||||
|
syncthing = 8384;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
networking.firewall = {
|
||||||
|
enable = true;
|
||||||
|
interfaces.br-nix-builder = {
|
||||||
|
allowedTCPPorts = [ ];
|
||||||
|
allowedUDPPorts = [ ];
|
||||||
|
};
|
||||||
|
interfaces.tailscale0.allowedTCPPorts = [
|
||||||
|
ports.audiobookshelf
|
||||||
|
ports.gems
|
||||||
|
ports.jellyfin_http
|
||||||
|
ports.nix_binary_cache
|
||||||
|
ports.nornsight
|
||||||
|
ports.share
|
||||||
|
];
|
||||||
|
allowedTCPPorts = [
|
||||||
|
ports.ebook_searcher
|
||||||
|
ports.gitea_http
|
||||||
|
ports.gitea_ssh
|
||||||
|
ports.grafana
|
||||||
|
ports.ollama
|
||||||
|
ports.open_webui
|
||||||
|
ports.postgresql
|
||||||
|
ports.signal_cli
|
||||||
|
ports.syncthing
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -8,36 +8,6 @@
|
|||||||
"net.bridge.bridge-nf-call-iptables" = 0;
|
"net.bridge.bridge-nf-call-iptables" = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
networking = {
|
|
||||||
hostName = "jeeves";
|
|
||||||
hostId = "0e15ce35";
|
|
||||||
firewall = {
|
|
||||||
enable = true;
|
|
||||||
interfaces.br-nix-builder = {
|
|
||||||
allowedTCPPorts = [ ];
|
|
||||||
allowedUDPPorts = [ ];
|
|
||||||
};
|
|
||||||
interfaces.tailscale0.allowedTCPPorts = [
|
|
||||||
5000 # Nix binary cache
|
|
||||||
8000 # Audiobookshelf
|
|
||||||
8001 # Norn Sight
|
|
||||||
8002 # Gems
|
|
||||||
8091 # Share
|
|
||||||
8096 # Jellyfin HTTP
|
|
||||||
];
|
|
||||||
allowedTCPPorts = [
|
|
||||||
6443 # Gitea HTTP
|
|
||||||
8070 # ebook searcher
|
|
||||||
];
|
|
||||||
};
|
|
||||||
useNetworkd = true;
|
|
||||||
# Without this, unconfigured NICs get a catch-all DHCP network and
|
|
||||||
# join the LAN uninvited (how the dead atlantic card ended up
|
|
||||||
# ARP-poisoning 192.168.99.14). Every interface must be configured
|
|
||||||
# explicitly below.
|
|
||||||
useDHCP = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.network = {
|
systemd.network = {
|
||||||
enable = true;
|
enable = true;
|
||||||
wait-online = {
|
wait-online = {
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
outputs,
|
pkgs,
|
||||||
utils,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
@@ -12,6 +11,121 @@ let
|
|||||||
cfg = config.services.nix_builder;
|
cfg = config.services.nix_builder;
|
||||||
runnerUsername = "gitea-runner";
|
runnerUsername = "gitea-runner";
|
||||||
runnerUserid = 601;
|
runnerUserid = 601;
|
||||||
|
runnerLabels = [
|
||||||
|
"self-hosted:host"
|
||||||
|
"nixos:host"
|
||||||
|
];
|
||||||
|
containerConfig =
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
runnerConfigFile = (pkgs.formats.yaml { }).generate "gitea-runner.yaml" { };
|
||||||
|
registerRunner = pkgs.writeShellApplication {
|
||||||
|
name = "register-gitea-runner";
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.hostname
|
||||||
|
config.services.gitea-actions-runner.package
|
||||||
|
];
|
||||||
|
text = builtins.readFile ./register_gitea_runner.sh;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
networking = {
|
||||||
|
hostName = "nix-builder";
|
||||||
|
useDHCP = lib.mkDefault true;
|
||||||
|
interfaces.eth0.useDHCP = true;
|
||||||
|
# Ensure containers don't inherit the host's stub resolver (127.0.0.53) which was causing issues
|
||||||
|
useHostResolvConf = false;
|
||||||
|
};
|
||||||
|
nix.settings = {
|
||||||
|
system-features = lib.mkAfter [
|
||||||
|
"gccarch-x86-64-v2"
|
||||||
|
"gccarch-x86-64-v3"
|
||||||
|
];
|
||||||
|
trusted-substituters = [
|
||||||
|
"https://cache.nixos.org"
|
||||||
|
"https://cache.tmmworkshop.com"
|
||||||
|
"https://nix-community.cachix.org"
|
||||||
|
];
|
||||||
|
substituters = [
|
||||||
|
"https://cache.nixos.org/?priority=2&want-mass-query=true"
|
||||||
|
"https://cache.tmmworkshop.com/?priority=2&want-mass-query=true"
|
||||||
|
"https://nix-community.cachix.org/?priority=10&want-mass-query=true"
|
||||||
|
];
|
||||||
|
trusted-public-keys = [
|
||||||
|
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
||||||
|
"cache.tmmworkshop.com:jHffkpgbmEdstQPoihJPYW9TQe6jnQbWR2LqkNGV3iA="
|
||||||
|
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
|
||||||
|
];
|
||||||
|
experimental-features = [
|
||||||
|
"flakes"
|
||||||
|
"nix-command"
|
||||||
|
];
|
||||||
|
sandbox = true;
|
||||||
|
allowed-users = [ runnerUsername ];
|
||||||
|
trusted-users = [
|
||||||
|
"root"
|
||||||
|
runnerUsername
|
||||||
|
];
|
||||||
|
};
|
||||||
|
users = {
|
||||||
|
users.${runnerUsername} = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = runnerUsername;
|
||||||
|
uid = runnerUserid;
|
||||||
|
};
|
||||||
|
groups.${runnerUsername}.gid = runnerUserid;
|
||||||
|
};
|
||||||
|
services.gitea-actions-runner.instances.builder = {
|
||||||
|
enable = true;
|
||||||
|
name = "jeeves-nix-builder";
|
||||||
|
url = "http://192.168.99.14:6443/";
|
||||||
|
labels = runnerLabels;
|
||||||
|
tokenFile = "/run/secrets/gitea-runners/registration-token";
|
||||||
|
hostPackages = with pkgs; [
|
||||||
|
bash
|
||||||
|
coreutils
|
||||||
|
curl
|
||||||
|
gawk
|
||||||
|
gitMinimal
|
||||||
|
gnused
|
||||||
|
my_python
|
||||||
|
nix
|
||||||
|
nixfmt
|
||||||
|
nixos-rebuild
|
||||||
|
nodejs
|
||||||
|
treefmt
|
||||||
|
wget
|
||||||
|
];
|
||||||
|
};
|
||||||
|
systemd.services."gitea-runner-builder" = {
|
||||||
|
serviceConfig = {
|
||||||
|
DynamicUser = mkForce false;
|
||||||
|
User = mkForce runnerUsername;
|
||||||
|
Group = mkForce runnerUsername;
|
||||||
|
ExecStartPre = mkForce [
|
||||||
|
"${getExe registerRunner} builder http://192.168.99.14:6443/ ${runnerConfigFile} ${escapeShellArgs runnerLabels}"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
system.stateVersion = "24.05";
|
||||||
|
};
|
||||||
|
sharedContainerPath =
|
||||||
|
(import "${pkgs.path}/nixos/lib/eval-config.nix" {
|
||||||
|
modules = [
|
||||||
|
{
|
||||||
|
boot.isNspawnContainer = true;
|
||||||
|
nixpkgs.pkgs = pkgs;
|
||||||
|
}
|
||||||
|
containerConfig
|
||||||
|
];
|
||||||
|
system = null;
|
||||||
|
}).config.system.build.toplevel;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.services.nix_builder = {
|
options.services.nix_builder = {
|
||||||
@@ -48,9 +162,11 @@ in
|
|||||||
containers = mapAttrs (
|
containers = mapAttrs (
|
||||||
name: containerCfg:
|
name: containerCfg:
|
||||||
mkIf containerCfg.enable {
|
mkIf containerCfg.enable {
|
||||||
|
path = sharedContainerPath;
|
||||||
autoStart = true;
|
autoStart = true;
|
||||||
privateNetwork = true;
|
privateNetwork = true;
|
||||||
hostBridge = cfg.bridgeName;
|
hostBridge = cfg.bridgeName;
|
||||||
|
extraFlags = [ "--hostname=${name}" ];
|
||||||
bindMounts = {
|
bindMounts = {
|
||||||
host-nix = {
|
host-nix = {
|
||||||
mountPoint = "/host-nix/var/nix/daemon-socket";
|
mountPoint = "/host-nix/var/nix/daemon-socket";
|
||||||
@@ -68,93 +184,6 @@ in
|
|||||||
isReadOnly = false;
|
isReadOnly = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config =
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
networking = {
|
|
||||||
useDHCP = lib.mkDefault true;
|
|
||||||
interfaces.eth0.useDHCP = true;
|
|
||||||
# Ensure containers don't inherit the host's stub resolver (127.0.0.53) which was causing issues
|
|
||||||
useHostResolvConf = false;
|
|
||||||
};
|
|
||||||
nix.settings = {
|
|
||||||
trusted-substituters = [
|
|
||||||
"https://cache.nixos.org"
|
|
||||||
"https://cache.tmmworkshop.com"
|
|
||||||
"https://nix-community.cachix.org"
|
|
||||||
];
|
|
||||||
substituters = [
|
|
||||||
"https://cache.nixos.org/?priority=2&want-mass-query=true"
|
|
||||||
"https://cache.tmmworkshop.com/?priority=2&want-mass-query=true"
|
|
||||||
"https://nix-community.cachix.org/?priority=10&want-mass-query=true"
|
|
||||||
];
|
|
||||||
trusted-public-keys = [
|
|
||||||
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
|
||||||
"cache.tmmworkshop.com:jHffkpgbmEdstQPoihJPYW9TQe6jnQbWR2LqkNGV3iA="
|
|
||||||
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
|
|
||||||
];
|
|
||||||
experimental-features = [
|
|
||||||
"flakes"
|
|
||||||
"nix-command"
|
|
||||||
];
|
|
||||||
sandbox = true;
|
|
||||||
allowed-users = [ "gitea-runner" ];
|
|
||||||
trusted-users = [
|
|
||||||
"root"
|
|
||||||
"gitea-runner"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
nixpkgs = {
|
|
||||||
overlays = builtins.attrValues outputs.overlays;
|
|
||||||
config.allowUnfree = true;
|
|
||||||
};
|
|
||||||
users = {
|
|
||||||
users.${runnerUsername} = {
|
|
||||||
isSystemUser = true;
|
|
||||||
group = runnerUsername;
|
|
||||||
uid = runnerUserid;
|
|
||||||
};
|
|
||||||
groups.${runnerUsername}.gid = runnerUserid;
|
|
||||||
};
|
|
||||||
services.gitea-actions-runner.instances.${name} = {
|
|
||||||
enable = true;
|
|
||||||
name = "jeeves-${name}";
|
|
||||||
url = "http://192.168.99.14:6443/";
|
|
||||||
labels = [
|
|
||||||
"self-hosted:host"
|
|
||||||
"nixos:host"
|
|
||||||
];
|
|
||||||
tokenFile = "/run/secrets/gitea-runners/registration-token";
|
|
||||||
hostPackages = with pkgs; [
|
|
||||||
bash
|
|
||||||
coreutils
|
|
||||||
curl
|
|
||||||
gawk
|
|
||||||
gitMinimal
|
|
||||||
gnused
|
|
||||||
my_python
|
|
||||||
nix
|
|
||||||
nixfmt
|
|
||||||
nixos-rebuild
|
|
||||||
nodejs
|
|
||||||
treefmt
|
|
||||||
wget
|
|
||||||
];
|
|
||||||
};
|
|
||||||
systemd.services."gitea-runner-${utils.escapeSystemdPath name}" = {
|
|
||||||
serviceConfig = {
|
|
||||||
DynamicUser = mkForce false;
|
|
||||||
User = mkForce runnerUsername;
|
|
||||||
Group = mkForce runnerUsername;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
system.stateVersion = "24.05";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
) cfg.containers;
|
) cfg.containers;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
legacy_instance_dir="$STATE_DIRECTORY/$(hostname)"
|
||||||
|
instance_dir="$STATE_DIRECTORY/$1"
|
||||||
|
runner_url="$2"
|
||||||
|
runner_config_file="$3"
|
||||||
|
shift 3
|
||||||
|
runner_labels="$(IFS=,; printf '%s' "$*")"
|
||||||
|
|
||||||
|
if [ ! -e "$instance_dir" ] && [ -d "$legacy_instance_dir" ]; then
|
||||||
|
mv "$legacy_instance_dir" "$instance_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -vp "$instance_dir"
|
||||||
|
cd "$instance_dir" || exit 1
|
||||||
|
|
||||||
|
token_hash_file="$instance_dir/.token-hash"
|
||||||
|
token_hash_current="$(printf '%s' "$TOKEN" | sha256sum | cut -d' ' -f1)"
|
||||||
|
token_hash_stored="$(cat "$token_hash_file" 2>/dev/null || echo "")"
|
||||||
|
labels_file="$instance_dir/.labels"
|
||||||
|
labels_wanted="$(printf '%s\n' "$@" | sort)"
|
||||||
|
labels_current="$(cat "$labels_file" 2>/dev/null || echo 0)"
|
||||||
|
|
||||||
|
if [ ! -e "$instance_dir/.runner" ] || [ "$labels_wanted" != "$labels_current" ] || [ "$token_hash_current" != "$token_hash_stored" ]; then
|
||||||
|
rm -v "$instance_dir/.runner" || true
|
||||||
|
|
||||||
|
gitea-runner register --no-interactive \
|
||||||
|
--instance "$runner_url" \
|
||||||
|
--token "$TOKEN" \
|
||||||
|
--name "jeeves-$(hostname)" \
|
||||||
|
--labels "$runner_labels" \
|
||||||
|
--config "$runner_config_file"
|
||||||
|
|
||||||
|
printf '%s' "$token_hash_current" > "$token_hash_file"
|
||||||
|
printf '%s\n' "$labels_wanted" > "$labels_file"
|
||||||
|
fi
|
||||||
@@ -2,10 +2,6 @@ let
|
|||||||
vars = import ../vars.nix;
|
vars = import ../vars.nix;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
2223
|
|
||||||
];
|
|
||||||
|
|
||||||
services.gitea = {
|
services.gitea = {
|
||||||
enable = true;
|
enable = true;
|
||||||
appName = "TMM Workshop";
|
appName = "TMM Workshop";
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ let
|
|||||||
grafanaDataDir = "${vars.services}/grafana";
|
grafanaDataDir = "${vars.services}/grafana";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [ 3000 ];
|
|
||||||
|
|
||||||
services.grafana = {
|
services.grafana = {
|
||||||
enable = true;
|
enable = true;
|
||||||
dataDir = grafanaDataDir;
|
dataDir = grafanaDataDir;
|
||||||
@@ -19,9 +17,9 @@ in
|
|||||||
secret_key = "$__file{${vars.secrets}/services/grafana/secret_key}";
|
secret_key = "$__file{${vars.secrets}/services/grafana/secret_key}";
|
||||||
};
|
};
|
||||||
server = {
|
server = {
|
||||||
http_addr = "192.168.90.40";
|
http_addr = "0.0.0.0";
|
||||||
http_port = 3000;
|
http_port = 3000;
|
||||||
root_url = "http://192.168.90.40:3000/";
|
root_url = "http://jeeves:3000/";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
provision = {
|
provision = {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ in
|
|||||||
{
|
{
|
||||||
services.jellyfin = {
|
services.jellyfin = {
|
||||||
enable = true;
|
enable = true;
|
||||||
openFirewall = false;
|
|
||||||
dataDir = "${vars.services}/jellyfin";
|
dataDir = "${vars.services}/jellyfin";
|
||||||
cacheDir = "${vars.services}/jellyfin/cache";
|
cacheDir = "${vars.services}/jellyfin/cache";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ in
|
|||||||
"qwen3.5:35b"
|
"qwen3.5:35b"
|
||||||
];
|
];
|
||||||
modelsDir = vars.ollama;
|
modelsDir = vars.ollama;
|
||||||
openFirewall = true;
|
|
||||||
};
|
};
|
||||||
systemd.services = {
|
systemd.services = {
|
||||||
ollama.serviceConfig = {
|
ollama.serviceConfig = {
|
||||||
|
|||||||
@@ -7,6 +7,5 @@ in
|
|||||||
package = pkgs.nix-serve-ng;
|
package = pkgs.nix-serve-ng;
|
||||||
enable = true;
|
enable = true;
|
||||||
secretKeyFile = "${vars.secrets}/services/nix-cache/cache-priv-key.pem";
|
secretKeyFile = "${vars.secrets}/services/nix-cache/cache-priv-key.pem";
|
||||||
openFirewall = false;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
services.open-webui = {
|
services.open-webui = {
|
||||||
enable = true;
|
enable = true;
|
||||||
host = "0.0.0.0";
|
host = "0.0.0.0";
|
||||||
openFirewall = true;
|
|
||||||
environment = {
|
environment = {
|
||||||
ANONYMIZED_TELEMETRY = "False";
|
ANONYMIZED_TELEMETRY = "False";
|
||||||
DO_NOT_TRACK = "True";
|
DO_NOT_TRACK = "True";
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ let
|
|||||||
vars = import ../vars.nix;
|
vars = import ../vars.nix;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [ 5432 ];
|
|
||||||
|
|
||||||
# Symlink pg_wal to a ZFS dataset on the special (metadata) vdev for fast WAL writes
|
# Symlink pg_wal to a ZFS dataset on the special (metadata) vdev for fast WAL writes
|
||||||
# this is required for systemd sandboxing
|
# this is required for systemd sandboxing
|
||||||
systemd.services.postgresql.serviceConfig.ReadWritePaths = [ "/zfs/media/database/postgres-wal" ];
|
systemd.services.postgresql.serviceConfig.ReadWritePaths = [ "/zfs/media/database/postgres-wal" ];
|
||||||
@@ -27,7 +25,7 @@ in
|
|||||||
local all richie trust
|
local all richie trust
|
||||||
host all richie 127.0.0.1/32 trust
|
host all richie 127.0.0.1/32 trust
|
||||||
host all richie ::1/128 trust
|
host all richie ::1/128 trust
|
||||||
host all richie 192.168.90.1/24 trust
|
host all richie 100.64.0.0/10 trust
|
||||||
host all richie 192.168.99.1/24 trust
|
host all richie 192.168.99.1/24 trust
|
||||||
host all richie 172.16.0.0/12 trust
|
host all richie 172.16.0.0/12 trust
|
||||||
|
|
||||||
@@ -42,12 +40,12 @@ in
|
|||||||
local postgres math trust
|
local postgres math trust
|
||||||
host postgres math 127.0.0.1/32 trust
|
host postgres math 127.0.0.1/32 trust
|
||||||
host postgres math ::1/128 trust
|
host postgres math ::1/128 trust
|
||||||
host postgres math 192.168.90.1/24 trust
|
host postgres math 100.64.0.0/10 trust
|
||||||
|
|
||||||
local data_science_dev math trust
|
local data_science_dev math trust
|
||||||
host data_science_dev math 127.0.0.1/32 trust
|
host data_science_dev math 127.0.0.1/32 trust
|
||||||
host data_science_dev math ::1/128 trust
|
host data_science_dev math ::1/128 trust
|
||||||
host data_science_dev math 192.168.90.1/24 trust
|
host data_science_dev math 100.64.0.0/10 trust
|
||||||
'';
|
'';
|
||||||
|
|
||||||
identMap = ''
|
identMap = ''
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ in
|
|||||||
message-level = 3;
|
message-level = 3;
|
||||||
peer-port = 51413;
|
peer-port = 51413;
|
||||||
rpc-bind-address = "0.0.0.0";
|
rpc-bind-address = "0.0.0.0";
|
||||||
rpc-host-whitelist = "127.0.0.1,192.168.90.40";
|
rpc-host-whitelist = "127.0.0.1,jeeves,jeeves.taile39c31.ts.net";
|
||||||
rpc-host-whitelist-enabled = true;
|
rpc-host-whitelist-enabled = true;
|
||||||
rpc-port = 9091;
|
rpc-port = 9091;
|
||||||
rpc-whitelist-enabled = true;
|
rpc-whitelist-enabled = true;
|
||||||
rpc-whitelist = "127.0.0.1,192.168.90.49";
|
rpc-whitelist = "127.0.0.1,100.*.*.*";
|
||||||
seed-queue-enabled = false;
|
seed-queue-enabled = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ let
|
|||||||
vars = import ./vars.nix;
|
vars = import ./vars.nix;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [ 8384 ];
|
|
||||||
|
|
||||||
services.syncthing = {
|
services.syncthing = {
|
||||||
overrideFolders = false;
|
overrideFolders = false;
|
||||||
guiAddress = "192.168.90.40:8384";
|
guiAddress = "0.0.0.0:8384";
|
||||||
settings = {
|
settings = {
|
||||||
devices.davids-server.id = "7GXTDGR-AOXFW2O-K6J7NM3-XYZNRRW-AKHAFWM-GBOWUPQ-OA6JIWD-ER7RDQL"; # cspell:disable-line
|
devices.davids-server.id = "7GXTDGR-AOXFW2O-K6J7NM3-XYZNRRW-AKHAFWM-GBOWUPQ-OA6JIWD-ER7RDQL"; # cspell:disable-line
|
||||||
folders = {
|
folders = {
|
||||||
|
|||||||
@@ -10,12 +10,12 @@
|
|||||||
"${inputs.self}/users/richie"
|
"${inputs.self}/users/richie"
|
||||||
"${inputs.self}/common/global"
|
"${inputs.self}/common/global"
|
||||||
"${inputs.self}/common/optional/tailscale.nix"
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
|
"${inputs.self}/common/optional/x86-64-v3.nix"
|
||||||
./disk-config.nix
|
./disk-config.nix
|
||||||
./haproxy
|
./haproxy
|
||||||
|
./monitoring.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
nixpkgs.hostPlatform = "x86_64-linux";
|
|
||||||
|
|
||||||
boot = {
|
boot = {
|
||||||
# Avoid consuming the VM's limited memory for /tmp.
|
# Avoid consuming the VM's limited memory for /tmp.
|
||||||
tmp.useTmpfs = false;
|
tmp.useTmpfs = false;
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ defaults
|
|||||||
|
|
||||||
#Application Setup
|
#Application Setup
|
||||||
frontend ContentSwitching
|
frontend ContentSwitching
|
||||||
bind *:80 v4v6
|
bind [::]:80 v4v6
|
||||||
bind *:443 v4v6 ssl crt /var/lib/acme/audiobookshelf.tmmworkshop.com/full.pem crt /var/lib/acme/cache.tmmworkshop.com/full.pem crt /var/lib/acme/gems.tmmworkshop.com/full.pem crt /var/lib/acme/jellyfin.tmmworkshop.com/full.pem crt /var/lib/acme/share.tmmworkshop.com/full.pem crt /var/lib/acme/gitea.tmmworkshop.com/full.pem crt /var/lib/acme/www.norn-sight.com/full.pem
|
bind [::]:443 v4v6 ssl crt /var/lib/acme/audiobookshelf.tmmworkshop.com/full.pem crt /var/lib/acme/cache.tmmworkshop.com/full.pem crt /var/lib/acme/gems.tmmworkshop.com/full.pem crt /var/lib/acme/jellyfin.tmmworkshop.com/full.pem crt /var/lib/acme/share.tmmworkshop.com/full.pem crt /var/lib/acme/gitea.tmmworkshop.com/full.pem crt /var/lib/acme/www.norn-sight.com/full.pem
|
||||||
mode http
|
mode http
|
||||||
|
|
||||||
# ACME challenge routing (must be first)
|
# ACME challenge routing (must be first)
|
||||||
@@ -113,7 +113,7 @@ frontend ContentSwitching
|
|||||||
# Proxy Gitea's built-in SSH server over Tailscale to Jeeves. Port 22 remains
|
# Proxy Gitea's built-in SSH server over Tailscale to Jeeves. Port 22 remains
|
||||||
# available to Endlessh on Portal.
|
# available to Endlessh on Portal.
|
||||||
frontend GiteaSSH
|
frontend GiteaSSH
|
||||||
bind *:2223 v4v6
|
bind [::]:2223 v4v6
|
||||||
mode tcp
|
mode tcp
|
||||||
option tcplog
|
option tcplog
|
||||||
default_backend gitea_ssh
|
default_backend gitea_ssh
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
networking.firewall.interfaces.tailscale0.allowedTCPPorts = [
|
||||||
|
config.services.prometheus.exporters.node.port
|
||||||
|
];
|
||||||
|
|
||||||
|
services.prometheus.exporters.node = {
|
||||||
|
enable = true;
|
||||||
|
enabledCollectors = [
|
||||||
|
"pressure"
|
||||||
|
"processes"
|
||||||
|
"systemd"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
"${inputs.self}/common/optional/syncthing_base.nix"
|
"${inputs.self}/common/optional/syncthing_base.nix"
|
||||||
"${inputs.self}/common/optional/systemd-boot.nix"
|
"${inputs.self}/common/optional/systemd-boot.nix"
|
||||||
"${inputs.self}/common/optional/tailscale.nix"
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
|
"${inputs.self}/common/optional/x86-64-v3.nix"
|
||||||
"${inputs.self}/common/optional/yubikey.nix"
|
"${inputs.self}/common/optional/yubikey.nix"
|
||||||
"${inputs.self}/common/optional/zfs"
|
"${inputs.self}/common/optional/zfs"
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ def test_config_defaults_enable_reranking(mocker: MockerFixture) -> None:
|
|||||||
config = load_rerank_config()
|
config = load_rerank_config()
|
||||||
|
|
||||||
assert config.enabled is True
|
assert config.enabled is True
|
||||||
assert config.base_url == "http://192.168.90.25:8001"
|
assert config.base_url == "http://bob:8001"
|
||||||
assert config.model == "qwen3-reranker-06b"
|
assert config.model == "qwen3-reranker-06b"
|
||||||
assert config.candidates == 24
|
assert config.candidates == 24
|
||||||
assert config.timeout_seconds == 30
|
assert config.timeout_seconds == 30
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"sqltools.connections": [
|
"sqltools.connections": [
|
||||||
{
|
{
|
||||||
"previewLimit": 50,
|
"previewLimit": 50,
|
||||||
"server": "192.168.90.40",
|
"server": "jeeves.taile39c31.ts.net",
|
||||||
"port": 5432,
|
"port": 5432,
|
||||||
"askForPassword": true,
|
"askForPassword": true,
|
||||||
"driver": "PostgreSQL",
|
"driver": "PostgreSQL",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
enableDefaultConfig = false;
|
enableDefaultConfig = false;
|
||||||
settings = {
|
settings = {
|
||||||
jeeves = {
|
jeeves = {
|
||||||
HostName = "192.168.90.40";
|
HostName = "jeeves";
|
||||||
User = "richie";
|
User = "richie";
|
||||||
IdentityFile = "~/.ssh/id_ed25519";
|
IdentityFile = "~/.ssh/id_ed25519";
|
||||||
Port = 629;
|
Port = 629;
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
Port = 2222;
|
Port = 2222;
|
||||||
};
|
};
|
||||||
brain = {
|
brain = {
|
||||||
HostName = "192.168.90.35";
|
HostName = "brain";
|
||||||
User = "richie";
|
User = "richie";
|
||||||
IdentityFile = "~/.ssh/id_ed25519";
|
IdentityFile = "~/.ssh/id_ed25519";
|
||||||
Port = 129;
|
Port = 129;
|
||||||
@@ -31,14 +31,14 @@
|
|||||||
Port = 2222;
|
Port = 2222;
|
||||||
};
|
};
|
||||||
bob = {
|
bob = {
|
||||||
HostName = "192.168.90.25";
|
HostName = "bob";
|
||||||
User = "richie";
|
User = "richie";
|
||||||
IdentityFile = "~/.ssh/id_ed25519";
|
IdentityFile = "~/.ssh/id_ed25519";
|
||||||
Port = 262;
|
Port = 262;
|
||||||
DynamicForward = [ { port = 9050; } ];
|
DynamicForward = [ { port = 9050; } ];
|
||||||
};
|
};
|
||||||
rhapsody-in-green = {
|
rhapsody-in-green = {
|
||||||
HostName = "192.168.90.221";
|
HostName = "rhapsody-in-green";
|
||||||
User = "richie";
|
User = "richie";
|
||||||
IdentityFile = "~/.ssh/id_ed25519";
|
IdentityFile = "~/.ssh/id_ed25519";
|
||||||
Port = 922;
|
Port = 922;
|
||||||
|
|||||||
Reference in New Issue
Block a user