mirror of
https://framagit.org/framasoft/framaspace/argos.git
synced 2025-05-18 19:20:36 +02:00
Compare commits
No commits in common. "8d82f7f9d6886e964cee51913f12a37e78d4a76b" and "73e7a8f4144d49e0ad5a3430789e607ae453d43f" have entirely different histories.
8d82f7f9d6
...
73e7a8f414
14 changed files with 222 additions and 222 deletions
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
- ✨ — Allow to customize agent User-Agent header (#78)
|
- ✨ — Allow to customize agent User-Agent header (#78)
|
||||||
- 📝 — Document how to add data to requests (#77)
|
- 📝 — Document how to add data to requests (#77)
|
||||||
- ✨ — No need cron tasks for DB cleaning anymore (#74 and #75)
|
|
||||||
- ✨ — No need cron tasks for agents watching (#76)
|
|
||||||
|
|
||||||
## 0.7.4
|
## 0.7.4
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,101 @@ def start(host, port, config, reload):
|
||||||
uvicorn.run("argos.server:app", host=host, port=port, reload=reload)
|
uvicorn.run("argos.server:app", host=host, port=port, reload=reload)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_max_lock_seconds(ctx, param, value):
|
||||||
|
if value <= 60:
|
||||||
|
raise click.BadParameter("Should be strictly higher than 60")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def validate_max_results(ctx, param, value):
|
||||||
|
if value <= 0:
|
||||||
|
raise click.BadParameter("Should be a positive integer")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@server.command()
|
||||||
|
@click.option(
|
||||||
|
"--max-results",
|
||||||
|
default=100,
|
||||||
|
help="Number of results per task to keep",
|
||||||
|
callback=validate_max_results,
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--max-lock-seconds",
|
||||||
|
default=100,
|
||||||
|
help=(
|
||||||
|
"The number of seconds after which a lock is "
|
||||||
|
"considered stale, must be higher than 60 "
|
||||||
|
"(the checks have a timeout value of 60 seconds)"
|
||||||
|
),
|
||||||
|
callback=validate_max_lock_seconds,
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--config",
|
||||||
|
default="argos-config.yaml",
|
||||||
|
help="Path of the configuration file. "
|
||||||
|
"If ARGOS_YAML_FILE environment variable is set, its value will be used instead. "
|
||||||
|
"Default value: argos-config.yaml and /etc/argos/config.yaml as fallback.",
|
||||||
|
envvar="ARGOS_YAML_FILE",
|
||||||
|
callback=validate_config_access,
|
||||||
|
)
|
||||||
|
@coroutine
|
||||||
|
async def cleandb(max_results, max_lock_seconds, config):
|
||||||
|
"""Clean the database (to run routinely)
|
||||||
|
|
||||||
|
\b
|
||||||
|
- Removes old results from the database.
|
||||||
|
- Removes locks from tasks that have been locked for too long.
|
||||||
|
"""
|
||||||
|
# It’s mandatory to do it before the imports
|
||||||
|
os.environ["ARGOS_YAML_FILE"] = config
|
||||||
|
|
||||||
|
# The imports are made here otherwise the agent will need server configuration files.
|
||||||
|
from argos.server import queries
|
||||||
|
|
||||||
|
db = await get_db()
|
||||||
|
removed = await queries.remove_old_results(db, max_results)
|
||||||
|
updated = await queries.release_old_locks(db, max_lock_seconds)
|
||||||
|
|
||||||
|
click.echo(f"{removed} results removed")
|
||||||
|
click.echo(f"{updated} locks released")
|
||||||
|
|
||||||
|
|
||||||
|
@server.command()
|
||||||
|
@click.option(
|
||||||
|
"--time-without-agent",
|
||||||
|
default=5,
|
||||||
|
help="Time without seeing an agent after which a warning will be issued, in minutes. "
|
||||||
|
"Default is 5 minutes.",
|
||||||
|
callback=validate_max_results,
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--config",
|
||||||
|
default="argos-config.yaml",
|
||||||
|
help="Path of the configuration file. "
|
||||||
|
"If ARGOS_YAML_FILE environment variable is set, its value will be used instead.",
|
||||||
|
envvar="ARGOS_YAML_FILE",
|
||||||
|
callback=validate_config_access,
|
||||||
|
)
|
||||||
|
@coroutine
|
||||||
|
async def watch_agents(time_without_agent, config):
|
||||||
|
"""Watch agents (to run routinely)
|
||||||
|
|
||||||
|
Issues a warning if no agent has been seen by the server for a given time.
|
||||||
|
"""
|
||||||
|
# It’s mandatory to do it before the imports
|
||||||
|
os.environ["ARGOS_YAML_FILE"] = config
|
||||||
|
|
||||||
|
# The imports are made here otherwise the agent will need server configuration files.
|
||||||
|
from argos.server import queries
|
||||||
|
|
||||||
|
db = await get_db()
|
||||||
|
agents = await queries.get_recent_agents_count(db, time_without_agent)
|
||||||
|
if agents == 0:
|
||||||
|
click.echo(f"No agent has been seen in the last {time_without_agent} minutes.")
|
||||||
|
sysexit(1)
|
||||||
|
|
||||||
|
|
||||||
@server.command(short_help="Load or reload tasks’ configuration")
|
@server.command(short_help="Load or reload tasks’ configuration")
|
||||||
@click.option(
|
@click.option(
|
||||||
"--config",
|
"--config",
|
||||||
|
@ -496,7 +591,6 @@ async def test_mail(config, domain, severity):
|
||||||
check="body-contains",
|
check="body-contains",
|
||||||
expected="foo",
|
expected="foo",
|
||||||
frequency=1,
|
frequency=1,
|
||||||
ip_version=4,
|
|
||||||
selected_by="test",
|
selected_by="test",
|
||||||
selected_at=now,
|
selected_at=now,
|
||||||
)
|
)
|
||||||
|
@ -561,7 +655,6 @@ async def test_gotify(config, domain, severity):
|
||||||
check="body-contains",
|
check="body-contains",
|
||||||
expected="foo",
|
expected="foo",
|
||||||
frequency=1,
|
frequency=1,
|
||||||
ip_version=4,
|
|
||||||
selected_by="test",
|
selected_by="test",
|
||||||
selected_at=now,
|
selected_at=now,
|
||||||
)
|
)
|
||||||
|
@ -629,7 +722,6 @@ async def test_apprise(config, domain, severity, apprise_group):
|
||||||
check="body-contains",
|
check="body-contains",
|
||||||
expected="foo",
|
expected="foo",
|
||||||
frequency=1,
|
frequency=1,
|
||||||
ip_version=4,
|
|
||||||
selected_by="test",
|
selected_by="test",
|
||||||
selected_at=now,
|
selected_at=now,
|
||||||
)
|
)
|
||||||
|
|
|
@ -81,12 +81,6 @@ general:
|
||||||
# To disable the IPv6 check of domains:
|
# To disable the IPv6 check of domains:
|
||||||
# ipv6: false
|
# ipv6: false
|
||||||
|
|
||||||
# Argos root path
|
|
||||||
# If not present, default value is ""
|
|
||||||
# Set it to /foo if you want to use argos at /foo/ instead of /
|
|
||||||
# on your web server
|
|
||||||
# root_path: "/foo"
|
|
||||||
|
|
||||||
# Which way do you want to be warned when a check goes to that severity?
|
# Which way do you want to be warned when a check goes to that severity?
|
||||||
# "local" emits a message in the server log
|
# "local" emits a message in the server log
|
||||||
# You’ll need to configure mail, gotify or apprise below to be able to use
|
# You’ll need to configure mail, gotify or apprise below to be able to use
|
||||||
|
@ -102,10 +96,11 @@ general:
|
||||||
- local
|
- local
|
||||||
unknown:
|
unknown:
|
||||||
- local
|
- local
|
||||||
# This alert is triggered when no Argos agent has been seen in a while
|
# Argos root path
|
||||||
# See recurring_tasks.time_without_agent below
|
# If not present, default value is ""
|
||||||
no_agent:
|
# Set it to /foo if you want to use argos at /foo/ instead of /
|
||||||
- local
|
# on your web server
|
||||||
|
# root_path: "/foo"
|
||||||
# Mail configuration is quite straight-forward
|
# Mail configuration is quite straight-forward
|
||||||
# mail:
|
# mail:
|
||||||
# mailfrom: no-reply@example.org
|
# mailfrom: no-reply@example.org
|
||||||
|
@ -149,20 +144,6 @@ ssl:
|
||||||
- "1d": critical
|
- "1d": critical
|
||||||
- "5d": warning
|
- "5d": warning
|
||||||
|
|
||||||
# Argos will execute some tasks in the background for you
|
|
||||||
# every 2 minutes and needs some configuration for that
|
|
||||||
recurring_tasks:
|
|
||||||
# Max number of results per tasks you want to keep
|
|
||||||
# Minimum value is 1, default is 100
|
|
||||||
max_results: 100
|
|
||||||
# Max number of seconds a task can be locked
|
|
||||||
# Minimum value is 61, default is 100
|
|
||||||
max_lock_seconds: 100
|
|
||||||
# Max number of minutes without seing an agent
|
|
||||||
# before sending an alert
|
|
||||||
# Minimum value is 1, default is 5
|
|
||||||
time_without_agent: 5
|
|
||||||
|
|
||||||
# It's also possible to define the checks in another file
|
# It's also possible to define the checks in another file
|
||||||
# with the include syntax:
|
# with the include syntax:
|
||||||
#
|
#
|
||||||
|
|
|
@ -14,10 +14,9 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# XXX Does not work ?
|
# XXX Does not work ?
|
||||||
def set_log_level(log_level: str, quiet: bool = False):
|
def set_log_level(log_level):
|
||||||
level = getattr(logging, log_level.upper(), None)
|
level = getattr(logging, log_level.upper(), None)
|
||||||
if not isinstance(level, int):
|
if not isinstance(level, int):
|
||||||
raise ValueError(f"Invalid log level: {log_level}")
|
raise ValueError(f"Invalid log level: {log_level}")
|
||||||
logger.setLevel(level=level)
|
logger.setLevel(level=level)
|
||||||
if not quiet:
|
logger.info("Log level set to %s", log_level)
|
||||||
logger.info("Log level set to %s", log_level)
|
|
||||||
|
|
|
@ -48,36 +48,6 @@ class SSL(BaseModel):
|
||||||
thresholds: List[Annotated[Tuple[int, Severity], BeforeValidator(parse_threshold)]]
|
thresholds: List[Annotated[Tuple[int, Severity], BeforeValidator(parse_threshold)]]
|
||||||
|
|
||||||
|
|
||||||
class RecurringTasks(BaseModel):
|
|
||||||
max_results: int
|
|
||||||
max_lock_seconds: int
|
|
||||||
time_without_agent: int
|
|
||||||
|
|
||||||
@field_validator("max_results", mode="before")
|
|
||||||
def parse_max_results(cls, value):
|
|
||||||
"""Ensure that max_results is higher than 0"""
|
|
||||||
if value >= 1:
|
|
||||||
return value
|
|
||||||
|
|
||||||
return 100
|
|
||||||
|
|
||||||
@field_validator("max_lock_seconds", mode="before")
|
|
||||||
def parse_max_lock_seconds(cls, value):
|
|
||||||
"""Ensure that max_lock_seconds is higher or equal to agent’s requests timeout (60)"""
|
|
||||||
if value > 60:
|
|
||||||
return value
|
|
||||||
|
|
||||||
return 100
|
|
||||||
|
|
||||||
@field_validator("time_without_agent", mode="before")
|
|
||||||
def parse_time_without_agent(cls, value):
|
|
||||||
"""Ensure that time_without_agent is at least one minute"""
|
|
||||||
if value >= 1:
|
|
||||||
return value
|
|
||||||
|
|
||||||
return 5
|
|
||||||
|
|
||||||
|
|
||||||
class WebsiteCheck(BaseModel):
|
class WebsiteCheck(BaseModel):
|
||||||
key: str
|
key: str
|
||||||
value: str | List[str] | Dict[str, str]
|
value: str | List[str] | Dict[str, str]
|
||||||
|
@ -220,7 +190,6 @@ class Alert(BaseModel):
|
||||||
warning: List[str]
|
warning: List[str]
|
||||||
critical: List[str]
|
critical: List[str]
|
||||||
unknown: List[str]
|
unknown: List[str]
|
||||||
no_agent: List[str]
|
|
||||||
|
|
||||||
|
|
||||||
class GotifyUrl(BaseModel):
|
class GotifyUrl(BaseModel):
|
||||||
|
@ -295,5 +264,4 @@ class Config(BaseModel):
|
||||||
general: General
|
general: General
|
||||||
service: Service
|
service: Service
|
||||||
ssl: SSL
|
ssl: SSL
|
||||||
recurring_tasks: RecurringTasks
|
|
||||||
websites: List[Website]
|
websites: List[Website]
|
||||||
|
|
|
@ -74,91 +74,6 @@ def get_icon_from_severity(severity: str) -> str:
|
||||||
return icon
|
return icon
|
||||||
|
|
||||||
|
|
||||||
def send_mail(mail: EmailMessage, config: Mail):
|
|
||||||
"""Send message by mail"""
|
|
||||||
|
|
||||||
if config.ssl:
|
|
||||||
logger.debug("Mail notification: SSL")
|
|
||||||
context = ssl.create_default_context()
|
|
||||||
smtp = smtplib.SMTP_SSL(host=config.host, port=config.port, context=context)
|
|
||||||
else:
|
|
||||||
smtp = smtplib.SMTP(
|
|
||||||
host=config.host, # type: ignore
|
|
||||||
port=config.port,
|
|
||||||
)
|
|
||||||
if config.starttls:
|
|
||||||
logger.debug("Mail notification: STARTTLS")
|
|
||||||
context = ssl.create_default_context()
|
|
||||||
smtp.starttls(context=context)
|
|
||||||
|
|
||||||
if config.auth is not None:
|
|
||||||
logger.debug("Mail notification: authentification")
|
|
||||||
smtp.login(config.auth.login, config.auth.password)
|
|
||||||
|
|
||||||
for address in config.addresses:
|
|
||||||
logger.debug("Sending mail to %s", address)
|
|
||||||
logger.debug(mail.get_body())
|
|
||||||
smtp.send_message(mail, to_addrs=address)
|
|
||||||
|
|
||||||
|
|
||||||
def send_gotify_msg(config, payload):
|
|
||||||
"""Send message with gotify"""
|
|
||||||
headers = {"accept": "application/json", "content-type": "application/json"}
|
|
||||||
|
|
||||||
for url in config:
|
|
||||||
logger.debug("Sending gotify message(s) to %s", url.url)
|
|
||||||
for token in url.tokens:
|
|
||||||
try:
|
|
||||||
res = httpx.post(
|
|
||||||
f"{url.url}message",
|
|
||||||
params={"token": token},
|
|
||||||
headers=headers,
|
|
||||||
json=payload,
|
|
||||||
)
|
|
||||||
res.raise_for_status()
|
|
||||||
except httpx.RequestError as err:
|
|
||||||
logger.error(
|
|
||||||
"An error occurred while sending a message to %s with token %s",
|
|
||||||
err.request.url,
|
|
||||||
token,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def no_agent_alert(config: Config):
|
|
||||||
"""Alert"""
|
|
||||||
msg = "You should check what’s going on with your Argos agents."
|
|
||||||
twa = config.recurring_tasks.time_without_agent
|
|
||||||
if twa > 1:
|
|
||||||
subject = f"No agent has been seen within the last {twa} minutes"
|
|
||||||
else:
|
|
||||||
subject = "No agent has been seen within the last minute"
|
|
||||||
|
|
||||||
if "local" in config.general.alerts.no_agent:
|
|
||||||
logger.error(subject)
|
|
||||||
|
|
||||||
if config.general.mail is not None and "mail" in config.general.alerts.no_agent:
|
|
||||||
mail = EmailMessage()
|
|
||||||
mail["Subject"] = f"[Argos] {subject}"
|
|
||||||
mail["From"] = config.general.mail.mailfrom
|
|
||||||
mail.set_content(msg)
|
|
||||||
send_mail(mail, config.general.mail)
|
|
||||||
|
|
||||||
if config.general.gotify is not None and "gotify" in config.general.alerts.no_agent:
|
|
||||||
priority = 9
|
|
||||||
payload = {"title": subject, "message": msg, "priority": priority}
|
|
||||||
send_gotify_msg(config.general.gotify, payload)
|
|
||||||
|
|
||||||
if config.general.apprise is not None:
|
|
||||||
for notif_way in config.general.alerts.no_agent:
|
|
||||||
if notif_way.startswith("apprise:"):
|
|
||||||
group = notif_way[8:]
|
|
||||||
apobj = apprise.Apprise()
|
|
||||||
for channel in config.general.apprise[group]:
|
|
||||||
apobj.add(channel)
|
|
||||||
|
|
||||||
apobj.notify(title=subject, body=msg)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_alert(config: Config, result, task, severity, old_severity, request): # pylint: disable-msg=too-many-positional-arguments
|
def handle_alert(config: Config, result, task, severity, old_severity, request): # pylint: disable-msg=too-many-positional-arguments
|
||||||
"""Dispatch alert through configured alert channels"""
|
"""Dispatch alert through configured alert channels"""
|
||||||
|
|
||||||
|
@ -248,13 +163,36 @@ See results of task on {request.url_for('get_task_results_view', task_id=task.id
|
||||||
] = f"[Argos] {icon} {urlparse(task.url).netloc} (IPv{task.ip_version}): status {severity}"
|
] = f"[Argos] {icon} {urlparse(task.url).netloc} (IPv{task.ip_version}): status {severity}"
|
||||||
mail["From"] = config.mailfrom
|
mail["From"] = config.mailfrom
|
||||||
mail.set_content(msg)
|
mail.set_content(msg)
|
||||||
send_mail(mail, config)
|
|
||||||
|
if config.ssl:
|
||||||
|
logger.debug("Mail notification: SSL")
|
||||||
|
context = ssl.create_default_context()
|
||||||
|
smtp = smtplib.SMTP_SSL(host=config.host, port=config.port, context=context)
|
||||||
|
else:
|
||||||
|
smtp = smtplib.SMTP(
|
||||||
|
host=config.host, # type: ignore
|
||||||
|
port=config.port,
|
||||||
|
)
|
||||||
|
if config.starttls:
|
||||||
|
logger.debug("Mail notification: STARTTLS")
|
||||||
|
context = ssl.create_default_context()
|
||||||
|
smtp.starttls(context=context)
|
||||||
|
|
||||||
|
if config.auth is not None:
|
||||||
|
logger.debug("Mail notification: authentification")
|
||||||
|
smtp.login(config.auth.login, config.auth.password)
|
||||||
|
|
||||||
|
for address in config.addresses:
|
||||||
|
logger.debug("Sending mail to %s", address)
|
||||||
|
logger.debug(msg)
|
||||||
|
smtp.send_message(mail, to_addrs=address)
|
||||||
|
|
||||||
|
|
||||||
def notify_with_gotify( # pylint: disable-msg=too-many-positional-arguments
|
def notify_with_gotify( # pylint: disable-msg=too-many-positional-arguments
|
||||||
result, task, severity: str, old_severity: str, config: List[GotifyUrl], request
|
result, task, severity: str, old_severity: str, config: List[GotifyUrl], request
|
||||||
) -> None:
|
) -> None:
|
||||||
logger.debug("Will send gotify notification")
|
logger.debug("Will send gotify notification")
|
||||||
|
headers = {"accept": "application/json", "content-type": "application/json"}
|
||||||
|
|
||||||
icon = get_icon_from_severity(severity)
|
icon = get_icon_from_severity(severity)
|
||||||
priority = 9
|
priority = 9
|
||||||
|
@ -290,4 +228,20 @@ See results of task on <{request.url_for('get_task_results_view', task_id=task.i
|
||||||
|
|
||||||
payload = {"title": subject, "message": msg, "priority": priority, "extras": extras}
|
payload = {"title": subject, "message": msg, "priority": priority, "extras": extras}
|
||||||
|
|
||||||
send_gotify_msg(config, payload)
|
for url in config:
|
||||||
|
logger.debug("Sending gotify message(s) to %s", url.url)
|
||||||
|
for token in url.tokens:
|
||||||
|
try:
|
||||||
|
res = httpx.post(
|
||||||
|
f"{url.url}message",
|
||||||
|
params={"token": token},
|
||||||
|
headers=headers,
|
||||||
|
json=payload,
|
||||||
|
)
|
||||||
|
res.raise_for_status()
|
||||||
|
except httpx.RequestError as err:
|
||||||
|
logger.error(
|
||||||
|
"An error occurred while sending a message to %s with token %s",
|
||||||
|
err.request.url,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
|
|
@ -6,14 +6,12 @@ from pathlib import Path
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi_login import LoginManager
|
from fastapi_login import LoginManager
|
||||||
from fastapi_utils.tasks import repeat_every
|
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
from sqlalchemy import create_engine, event
|
from sqlalchemy import create_engine, event
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
from argos.logging import logger, set_log_level
|
from argos.logging import logger
|
||||||
from argos.server import models, routes, queries
|
from argos.server import models, routes, queries
|
||||||
from argos.server.alerting import no_agent_alert
|
|
||||||
from argos.server.exceptions import NotAuthenticatedException, auth_exception_handler
|
from argos.server.exceptions import NotAuthenticatedException, auth_exception_handler
|
||||||
from argos.server.settings import read_yaml_config
|
from argos.server.settings import read_yaml_config
|
||||||
|
|
||||||
|
@ -128,28 +126,8 @@ def create_manager(cookie_secret: str) -> LoginManager:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@repeat_every(seconds=120, logger=logger)
|
|
||||||
async def recurring_tasks() -> None:
|
|
||||||
"""Recurring DB cleanup and watch-agents tasks"""
|
|
||||||
set_log_level("info", quiet=True)
|
|
||||||
logger.info("Start background recurring tasks")
|
|
||||||
with app.state.SessionLocal() as db:
|
|
||||||
config = app.state.config.recurring_tasks
|
|
||||||
removed = await queries.remove_old_results(db, config.max_results)
|
|
||||||
logger.info("%i results removed", removed)
|
|
||||||
|
|
||||||
updated = await queries.release_old_locks(db, config.max_lock_seconds)
|
|
||||||
logger.info("%i locks released", updated)
|
|
||||||
|
|
||||||
agents = await queries.get_recent_agents_count(db, config.time_without_agent)
|
|
||||||
if agents == 0:
|
|
||||||
no_agent_alert(app.state.config)
|
|
||||||
|
|
||||||
logger.info("Background recurring tasks ended")
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(appli: FastAPI):
|
async def lifespan(appli):
|
||||||
"""Server start and stop actions
|
"""Server start and stop actions
|
||||||
|
|
||||||
Setup database connection then close it at shutdown.
|
Setup database connection then close it at shutdown.
|
||||||
|
@ -164,7 +142,6 @@ async def lifespan(appli: FastAPI):
|
||||||
"There is no tasks in the database. "
|
"There is no tasks in the database. "
|
||||||
'Please launch the command "argos server reload-config"'
|
'Please launch the command "argos server reload-config"'
|
||||||
)
|
)
|
||||||
await recurring_tasks()
|
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
61
docs/cli.md
61
docs/cli.md
|
@ -84,6 +84,7 @@ Options:
|
||||||
--help Show this message and exit.
|
--help Show this message and exit.
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
|
cleandb Clean the database (to run routinely)
|
||||||
generate-config Output a self-documented example config file.
|
generate-config Output a self-documented example config file.
|
||||||
generate-token Generate a token for agents
|
generate-token Generate a token for agents
|
||||||
migrate Run database migrations
|
migrate Run database migrations
|
||||||
|
@ -94,6 +95,7 @@ Commands:
|
||||||
test-gotify Send a test gotify notification
|
test-gotify Send a test gotify notification
|
||||||
test-mail Send a test email
|
test-mail Send a test email
|
||||||
user User management
|
user User management
|
||||||
|
watch-agents Watch agents (to run routinely)
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--[[[end]]]
|
<!--[[[end]]]
|
||||||
|
@ -150,6 +152,65 @@ Options:
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
### Server cleandb
|
||||||
|
<!--
|
||||||
|
.. [[[cog
|
||||||
|
help(["server", "cleandb", "--help"])
|
||||||
|
.. ]]] -->
|
||||||
|
|
||||||
|
```man
|
||||||
|
Usage: argos server cleandb [OPTIONS]
|
||||||
|
|
||||||
|
Clean the database (to run routinely)
|
||||||
|
|
||||||
|
- Removes old results from the database.
|
||||||
|
- Removes locks from tasks that have been locked for too long.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--max-results INTEGER Number of results per task to keep
|
||||||
|
--max-lock-seconds INTEGER The number of seconds after which a lock is
|
||||||
|
considered stale, must be higher than 60 (the
|
||||||
|
checks have a timeout value of 60 seconds)
|
||||||
|
--config TEXT Path of the configuration file. If ARGOS_YAML_FILE
|
||||||
|
environment variable is set, its value will be
|
||||||
|
used instead. Default value: argos-config.yaml and
|
||||||
|
/etc/argos/config.yaml as fallback.
|
||||||
|
--help Show this message and exit.
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--[[[end]]]
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Server watch-agents
|
||||||
|
|
||||||
|
<!--
|
||||||
|
.. [[[cog
|
||||||
|
help(["server", "cleandb", "--help"])
|
||||||
|
.. ]]] -->
|
||||||
|
|
||||||
|
```man
|
||||||
|
Usage: argos server cleandb [OPTIONS]
|
||||||
|
|
||||||
|
Clean the database (to run routinely)
|
||||||
|
|
||||||
|
- Removes old results from the database.
|
||||||
|
- Removes locks from tasks that have been locked for too long.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--max-results INTEGER Number of results per task to keep
|
||||||
|
--max-lock-seconds INTEGER The number of seconds after which a lock is
|
||||||
|
considered stale, must be higher than 60 (the
|
||||||
|
checks have a timeout value of 60 seconds)
|
||||||
|
--config TEXT Path of the configuration file. If ARGOS_YAML_FILE
|
||||||
|
environment variable is set, its value will be
|
||||||
|
used instead. Default value: argos-config.yaml and
|
||||||
|
/etc/argos/config.yaml as fallback.
|
||||||
|
--help Show this message and exit.
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--[[[end]]]
|
||||||
|
-->
|
||||||
|
|
||||||
### Server reload-config
|
### Server reload-config
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
|
@ -14,9 +14,7 @@ description: Many thanks to their developers!
|
||||||
- [Alembic](https://alembic.sqlalchemy.org) is used for DB migrations;
|
- [Alembic](https://alembic.sqlalchemy.org) is used for DB migrations;
|
||||||
- [Tenacity](https://github.com/jd/tenacity) a small utility to retry a function in case an error occured;
|
- [Tenacity](https://github.com/jd/tenacity) a small utility to retry a function in case an error occured;
|
||||||
- [Uvicorn](https://www.uvicorn.org/) is the tool used to run our server;
|
- [Uvicorn](https://www.uvicorn.org/) is the tool used to run our server;
|
||||||
- [Gunicorn](https://gunicorn.org/) is the recommended WSGI HTTP server for production;
|
- [Gunicorn](https://gunicorn.org/) is the recommended WSGI HTTP server for production.
|
||||||
- [Apprise](https://github.com/caronc/apprise/wiki) allows Argos to send notifications through a lot of channels;
|
|
||||||
- [FastAPI Utilities](https://fastapiutils.github.io/fastapi-utils/) is in charge of recurring tasks.
|
|
||||||
|
|
||||||
## CSS framework
|
## CSS framework
|
||||||
|
|
||||||
|
|
|
@ -191,6 +191,18 @@ The only requirement is that the agent can reach the server through HTTP or HTTP
|
||||||
argos agent http://localhost:8000 "auth-token"
|
argos agent http://localhost:8000 "auth-token"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Cleaning the database
|
||||||
|
|
||||||
|
You have to run cleaning task periodically. `argos server cleandb --help` will give you more information on how to do that.
|
||||||
|
|
||||||
|
Here is a crontab example, which will clean the db each hour:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run the cleaning tasks every hour (at minute 7)
|
||||||
|
# Keeps 10 results per task, and remove tasks’ locks older than 1 hour
|
||||||
|
7 * * * * argos server cleandb --max-results 10 --max-lock-seconds 3600
|
||||||
|
```
|
||||||
|
|
||||||
## Watch the agents
|
## Watch the agents
|
||||||
|
|
||||||
In order to be sure that agents are up and communicate with the server, you can periodically run the `argos server watch-agents` command.
|
In order to be sure that agents are up and communicate with the server, you can periodically run the `argos server watch-agents` command.
|
||||||
|
|
|
@ -153,7 +153,8 @@ If all works well, you have to put some cron tasks in `argos` crontab:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat <<EOF | crontab -u argos -
|
cat <<EOF | crontab -u argos -
|
||||||
*/10 * * * * /opt/argos/venv/bin/argos server watch-agents --time-without-agent 10:
|
*/10 * * * * /opt/argos/venv/bin/argos server cleandb --max-lock-seconds 120 --max-results 1200
|
||||||
|
*/10 * * * * /opt/argos/venv/bin/argos server watch-agents --time-without-agent 10
|
||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ dependencies = [
|
||||||
"durations-nlp>=1.0.1,<2",
|
"durations-nlp>=1.0.1,<2",
|
||||||
"fastapi>=0.103,<0.104",
|
"fastapi>=0.103,<0.104",
|
||||||
"fastapi-login>=1.10.0,<2",
|
"fastapi-login>=1.10.0,<2",
|
||||||
"fastapi-utils>=0.8.0,<0.9",
|
|
||||||
"httpx>=0.27.2,<0.28.0",
|
"httpx>=0.27.2,<0.28.0",
|
||||||
"Jinja2>=3.0,<4",
|
"Jinja2>=3.0,<4",
|
||||||
"jsonpointer>=3.0,<4",
|
"jsonpointer>=3.0,<4",
|
||||||
|
@ -42,7 +41,6 @@ dependencies = [
|
||||||
"sqlalchemy[asyncio]>=2.0,<3",
|
"sqlalchemy[asyncio]>=2.0,<3",
|
||||||
"sqlalchemy-utils>=0.41,<1",
|
"sqlalchemy-utils>=0.41,<1",
|
||||||
"tenacity>=8.2,<9",
|
"tenacity>=8.2,<9",
|
||||||
"typing_inspect>=0.9.0,<1",
|
|
||||||
"uvicorn>=0.23,<1",
|
"uvicorn>=0.23,<1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,11 @@
|
||||||
---
|
---
|
||||||
general:
|
general:
|
||||||
# Except for frequency and recheck_delay settings, changes in general
|
|
||||||
# section of the configuration will need a restart of argos server.
|
|
||||||
db:
|
db:
|
||||||
# The database URL, as defined in SQLAlchemy docs:
|
# The database URL, as defined in SQLAlchemy docs:
|
||||||
# https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls
|
# https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls
|
||||||
url: "sqlite:////tmp/test-argos.db"
|
url: "sqlite:////tmp/test-argos.db"
|
||||||
# Can be "production", "dev", "test".
|
|
||||||
# If not present, default value is "production"
|
|
||||||
env: test
|
env: test
|
||||||
# To get a good string for cookie_secret, run:
|
|
||||||
# openssl rand -hex 32
|
|
||||||
cookie_secret: "foo-bar-baz"
|
cookie_secret: "foo-bar-baz"
|
||||||
|
|
||||||
# Default delay for checks.
|
|
||||||
# Can be superseeded in domain configuration.
|
|
||||||
# For ex., to run checks every 5 minutes:
|
|
||||||
frequency: "1m"
|
frequency: "1m"
|
||||||
alerts:
|
alerts:
|
||||||
ok:
|
ok:
|
||||||
|
@ -26,35 +16,12 @@ general:
|
||||||
- local
|
- local
|
||||||
unknown:
|
unknown:
|
||||||
- local
|
- local
|
||||||
no_agent:
|
|
||||||
- local
|
|
||||||
service:
|
service:
|
||||||
secrets:
|
secrets:
|
||||||
# Secrets can be generated using `argos server generate-token`.
|
|
||||||
# You need at least one. Write them as a list, like:
|
|
||||||
# - secret_token
|
|
||||||
- "O4kt8Max9/k0EmHaEJ0CGGYbBNFmK8kOZNIoUk3Kjwc"
|
- "O4kt8Max9/k0EmHaEJ0CGGYbBNFmK8kOZNIoUk3Kjwc"
|
||||||
- "x1T1VZR51pxrv5pQUyzooMG4pMUvHNMhA5y/3cUsYVs="
|
- "x1T1VZR51pxrv5pQUyzooMG4pMUvHNMhA5y/3cUsYVs="
|
||||||
ssl:
|
ssl:
|
||||||
thresholds:
|
thresholds:
|
||||||
- "1d": critical
|
- "1d": critical
|
||||||
- "5d": warning
|
"5d": warning
|
||||||
|
|
||||||
# Argos will execute some tasks in the background for you
|
|
||||||
# every 2 minutes and needs some configuration for that
|
|
||||||
recurring_tasks:
|
|
||||||
# Max number of results per tasks you want to keep
|
|
||||||
# Minimum value is 1, default is 100
|
|
||||||
max_results: 100
|
|
||||||
# Max number of seconds a task can be locked
|
|
||||||
# Minimum value is 61, default is 100
|
|
||||||
max_lock_seconds: 100
|
|
||||||
# Max number of seconds without seing an agent
|
|
||||||
# before sending an alert
|
|
||||||
# Minimum value is 61, default is 300
|
|
||||||
time_without_agent: 300
|
|
||||||
|
|
||||||
# It's also possible to define the checks in another file
|
|
||||||
# with the include syntax:
|
|
||||||
#
|
|
||||||
websites: !include websites.yaml
|
websites: !include websites.yaml
|
||||||
|
|
|
@ -235,7 +235,6 @@ def empty_config():
|
||||||
warning=["", ""],
|
warning=["", ""],
|
||||||
critical=["", ""],
|
critical=["", ""],
|
||||||
unknown=["", ""],
|
unknown=["", ""],
|
||||||
no_agent=["", ""],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
service=schemas.config.Service(
|
service=schemas.config.Service(
|
||||||
|
@ -244,11 +243,6 @@ def empty_config():
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
ssl=schemas.config.SSL(thresholds=[]),
|
ssl=schemas.config.SSL(thresholds=[]),
|
||||||
recurring_tasks=schemas.config.RecurringTasks(
|
|
||||||
max_results=100,
|
|
||||||
max_lock_seconds=120,
|
|
||||||
time_without_agent=300,
|
|
||||||
),
|
|
||||||
websites=[],
|
websites=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue