chore(home): mirror from kalinamall (9883e6a) with papatramp URLs
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
from logging.config import fileConfig
|
||||
|
||||
from alembic import context
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
|
||||
from app.config import get_settings
|
||||
from app.database import Base
|
||||
from app.models import ApiKey, Event, Host # noqa: F401
|
||||
|
||||
config = context.config
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
target_metadata = Base.metadata
|
||||
settings = get_settings()
|
||||
config.set_main_option("sqlalchemy.url", settings.database_url)
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section, {}),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
with connectable.connect() as connection:
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
@@ -0,0 +1,25 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, None] = ${repr(down_revision)}
|
||||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
||||
@@ -0,0 +1,90 @@
|
||||
"""initial hosts events api_keys
|
||||
|
||||
Revision ID: 001
|
||||
Revises:
|
||||
Create Date: 2026-05-26
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "001"
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"hosts",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("agent_instance_id", sa.String(length=128), nullable=True),
|
||||
sa.Column("hostname", sa.String(length=255), nullable=False),
|
||||
sa.Column("display_name", sa.String(length=255), nullable=True),
|
||||
sa.Column("os_family", sa.String(length=32), nullable=False),
|
||||
sa.Column("os_version", sa.String(length=128), nullable=True),
|
||||
sa.Column("product", sa.String(length=64), nullable=False),
|
||||
sa.Column("product_version", sa.String(length=64), nullable=True),
|
||||
sa.Column("ipv4", sa.String(length=45), nullable=True),
|
||||
sa.Column("ipv6", sa.String(length=45), nullable=True),
|
||||
sa.Column("tags", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column("use_sac_mode", sa.String(length=32), nullable=True),
|
||||
sa.Column("last_seen_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("agent_instance_id"),
|
||||
)
|
||||
op.create_index("ix_hosts_hostname", "hosts", ["hostname"])
|
||||
op.create_index("ix_hosts_product", "hosts", ["product"])
|
||||
|
||||
op.create_table(
|
||||
"api_keys",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("name", sa.String(length=128), nullable=False),
|
||||
sa.Column("key_prefix", sa.String(length=16), nullable=False),
|
||||
sa.Column("key_hash", sa.String(length=128), nullable=False),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("key_hash"),
|
||||
)
|
||||
op.create_index("ix_api_keys_key_prefix", "api_keys", ["key_prefix"])
|
||||
|
||||
op.create_table(
|
||||
"events",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("event_id", sa.String(length=36), nullable=False),
|
||||
sa.Column("host_id", sa.Integer(), nullable=False),
|
||||
sa.Column("occurred_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("received_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("category", sa.String(length=64), nullable=False),
|
||||
sa.Column("type", sa.String(length=128), nullable=False),
|
||||
sa.Column("severity", sa.String(length=16), nullable=False),
|
||||
sa.Column("title", sa.String(length=256), nullable=False),
|
||||
sa.Column("summary", sa.Text(), nullable=False),
|
||||
sa.Column("details", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column("raw", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column("dedup_key", sa.String(length=512), nullable=True),
|
||||
sa.Column("correlation_id", sa.String(length=36), nullable=True),
|
||||
sa.Column("payload", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
|
||||
sa.ForeignKeyConstraint(["host_id"], ["hosts.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("event_id", name="uq_events_event_id"),
|
||||
)
|
||||
op.create_index("ix_events_event_id", "events", ["event_id"])
|
||||
op.create_index("ix_events_host_id", "events", ["host_id"])
|
||||
op.create_index("ix_events_occurred_at", "events", ["occurred_at"])
|
||||
op.create_index("ix_events_category", "events", ["category"])
|
||||
op.create_index("ix_events_type", "events", ["type"])
|
||||
op.create_index("ix_events_severity", "events", ["severity"])
|
||||
op.create_index("ix_events_dedup_key", "events", ["dedup_key"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("events")
|
||||
op.drop_table("api_keys")
|
||||
op.drop_table("hosts")
|
||||
@@ -0,0 +1,48 @@
|
||||
"""problems table
|
||||
|
||||
Revision ID: 002
|
||||
Revises: 001
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "002"
|
||||
down_revision: Union[str, None] = "001"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"problems",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("host_id", sa.Integer(), nullable=True),
|
||||
sa.Column("title", sa.String(length=256), nullable=False),
|
||||
sa.Column("summary", sa.Text(), nullable=False),
|
||||
sa.Column("severity", sa.String(length=16), nullable=False),
|
||||
sa.Column("status", sa.String(length=32), nullable=False, server_default="open"),
|
||||
sa.Column("rule_id", sa.String(length=64), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["host_id"], ["hosts.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_problems_status", "problems", ["status"])
|
||||
op.create_index("ix_problems_severity", "problems", ["severity"])
|
||||
|
||||
op.create_table(
|
||||
"problem_events",
|
||||
sa.Column("problem_id", sa.Integer(), nullable=False),
|
||||
sa.Column("event_id", sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["event_id"], ["events.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["problem_id"], ["problems.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("problem_id", "event_id"),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("problem_events")
|
||||
op.drop_table("problems")
|
||||
@@ -0,0 +1,52 @@
|
||||
"""problem correlation fields
|
||||
|
||||
Revision ID: 003
|
||||
Revises: 002
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "003"
|
||||
down_revision: Union[str, None] = "002"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("problems", sa.Column("fingerprint", sa.String(length=128), nullable=True))
|
||||
op.add_column(
|
||||
"problems",
|
||||
sa.Column("event_count", sa.Integer(), nullable=False, server_default="1"),
|
||||
)
|
||||
op.add_column("problems", sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=True))
|
||||
op.create_index("ix_problems_fingerprint", "problems", ["fingerprint"])
|
||||
op.create_index("ix_problems_last_seen_at", "problems", ["last_seen_at"])
|
||||
|
||||
op.execute(
|
||||
sa.text(
|
||||
"""
|
||||
UPDATE problems p SET
|
||||
fingerprint = 'h' || COALESCE(p.host_id::text, '0') || chr(58) || 'r'
|
||||
|| COALESCE(p.rule_id, 'unknown'),
|
||||
event_count = COALESCE(
|
||||
(SELECT COUNT(*)::int FROM problem_events pe WHERE pe.problem_id = p.id),
|
||||
1
|
||||
),
|
||||
last_seen_at = COALESCE(p.updated_at, p.created_at)
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
op.alter_column("problems", "fingerprint", nullable=False)
|
||||
op.alter_column("problems", "last_seen_at", nullable=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_problems_last_seen_at", table_name="problems")
|
||||
op.drop_index("ix_problems_fingerprint", table_name="problems")
|
||||
op.drop_column("problems", "last_seen_at")
|
||||
op.drop_column("problems", "event_count")
|
||||
op.drop_column("problems", "fingerprint")
|
||||
@@ -0,0 +1,37 @@
|
||||
"""notification_channels for UI-managed Telegram settings
|
||||
|
||||
Revision ID: 004
|
||||
Revises: 003
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "004"
|
||||
down_revision: Union[str, None] = "003"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"notification_channels",
|
||||
sa.Column("channel", sa.String(length=32), nullable=False),
|
||||
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
sa.Column("min_severity", sa.String(length=16), nullable=False, server_default="warning"),
|
||||
sa.Column("bot_token", sa.Text(), nullable=True),
|
||||
sa.Column("chat_id", sa.String(length=64), nullable=True),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("channel"),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("notification_channels")
|
||||
@@ -0,0 +1,30 @@
|
||||
"""notification_channels: webhook columns
|
||||
|
||||
Revision ID: 005
|
||||
Revises: 004
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "005"
|
||||
down_revision: Union[str, None] = "004"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("notification_channels", sa.Column("webhook_url", sa.Text(), nullable=True))
|
||||
op.add_column(
|
||||
"notification_channels",
|
||||
sa.Column("webhook_secret_header", sa.String(length=64), nullable=True),
|
||||
)
|
||||
op.add_column("notification_channels", sa.Column("webhook_secret", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("notification_channels", "webhook_secret")
|
||||
op.drop_column("notification_channels", "webhook_secret_header")
|
||||
op.drop_column("notification_channels", "webhook_url")
|
||||
@@ -0,0 +1,43 @@
|
||||
"""notification_channels: SMTP/email columns
|
||||
|
||||
Revision ID: 006
|
||||
Revises: 005
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "006"
|
||||
down_revision: Union[str, None] = "005"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("notification_channels", sa.Column("smtp_host", sa.Text(), nullable=True))
|
||||
op.add_column("notification_channels", sa.Column("smtp_port", sa.Integer(), nullable=True))
|
||||
op.add_column("notification_channels", sa.Column("smtp_user", sa.String(length=128), nullable=True))
|
||||
op.add_column("notification_channels", sa.Column("smtp_password", sa.Text(), nullable=True))
|
||||
op.add_column("notification_channels", sa.Column("mail_from", sa.String(length=256), nullable=True))
|
||||
op.add_column("notification_channels", sa.Column("mail_to", sa.Text(), nullable=True))
|
||||
op.add_column(
|
||||
"notification_channels",
|
||||
sa.Column("smtp_starttls", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
)
|
||||
op.add_column(
|
||||
"notification_channels",
|
||||
sa.Column("smtp_ssl", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("notification_channels", "smtp_ssl")
|
||||
op.drop_column("notification_channels", "smtp_starttls")
|
||||
op.drop_column("notification_channels", "mail_to")
|
||||
op.drop_column("notification_channels", "mail_from")
|
||||
op.drop_column("notification_channels", "smtp_password")
|
||||
op.drop_column("notification_channels", "smtp_user")
|
||||
op.drop_column("notification_channels", "smtp_port")
|
||||
op.drop_column("notification_channels", "smtp_host")
|
||||
@@ -0,0 +1,37 @@
|
||||
"""notification_policy singleton (global severity rule)
|
||||
|
||||
Revision ID: 007
|
||||
Revises: 006
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "007"
|
||||
down_revision: Union[str, None] = "006"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"notification_policy",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("min_severity", sa.String(length=16), nullable=False, server_default="warning"),
|
||||
sa.Column("use_telegram", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.Column("use_webhook", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
sa.Column("use_email", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.execute(
|
||||
sa.text(
|
||||
"INSERT INTO notification_policy (id, min_severity, use_telegram, use_webhook, use_email) "
|
||||
"VALUES (1, 'warning', true, false, false)"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("notification_policy")
|
||||
@@ -0,0 +1,31 @@
|
||||
"""notification_cooldown for SAC notify dedup (F-NOT-03)
|
||||
|
||||
Revision ID: 008
|
||||
Revises: 007
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "008"
|
||||
down_revision: Union[str, None] = "007"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"notification_cooldown",
|
||||
sa.Column("cooldown_key", sa.String(length=512), nullable=False),
|
||||
sa.Column("kind", sa.String(length=16), nullable=False),
|
||||
sa.Column("last_notified_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.PrimaryKeyConstraint("cooldown_key"),
|
||||
)
|
||||
op.create_index("ix_notification_cooldown_kind", "notification_cooldown", ["kind"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_notification_cooldown_kind", table_name="notification_cooldown")
|
||||
op.drop_table("notification_cooldown")
|
||||
@@ -0,0 +1,35 @@
|
||||
"""sac_users table for multi-user UI login
|
||||
|
||||
Revision ID: 009
|
||||
Revises: 008
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "009"
|
||||
down_revision: Union[str, None] = "008"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"sac_users",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("username", sa.String(length=64), nullable=False),
|
||||
sa.Column("password_hash", sa.String(length=128), nullable=False),
|
||||
sa.Column("role", sa.String(length=16), nullable=False, server_default="monitor"),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("username"),
|
||||
)
|
||||
op.create_index("ix_sac_users_username", "sac_users", ["username"], unique=True)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_sac_users_username", table_name="sac_users")
|
||||
op.drop_table("sac_users")
|
||||
@@ -0,0 +1,29 @@
|
||||
"""event_severity_overrides table
|
||||
|
||||
Revision ID: 010
|
||||
Revises: 009
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "010"
|
||||
down_revision: Union[str, None] = "009"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"event_severity_overrides",
|
||||
sa.Column("event_type", sa.String(length=128), nullable=False),
|
||||
sa.Column("severity", sa.String(length=16), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("event_type"),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("event_severity_overrides")
|
||||
@@ -0,0 +1,35 @@
|
||||
"""login_attempts for SAC UI login rate limiting
|
||||
|
||||
Revision ID: 011
|
||||
Revises: 010
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "011"
|
||||
down_revision: Union[str, None] = "010"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"login_attempts",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("ip_address", sa.String(length=64), nullable=False),
|
||||
sa.Column("username", sa.String(length=64), nullable=True),
|
||||
sa.Column("success", sa.Boolean(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_login_attempts_ip_address", "login_attempts", ["ip_address"], unique=False)
|
||||
op.create_index("ix_login_attempts_created_at", "login_attempts", ["created_at"], unique=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_login_attempts_created_at", table_name="login_attempts")
|
||||
op.drop_index("ix_login_attempts_ip_address", table_name="login_attempts")
|
||||
op.drop_table("login_attempts")
|
||||
@@ -0,0 +1,31 @@
|
||||
"""ui_settings singleton for SAC UI preferences
|
||||
|
||||
Revision ID: 012
|
||||
Revises: 011
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "012"
|
||||
down_revision: Union[str, None] = "011"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"ui_settings",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("show_sidebar_system_stats", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.execute(
|
||||
"INSERT INTO ui_settings (id, show_sidebar_system_stats) VALUES (1, true)"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("ui_settings")
|
||||
@@ -0,0 +1,26 @@
|
||||
"""host inventory JSONB
|
||||
|
||||
Revision ID: 013
|
||||
Revises: 012
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
revision: str = "013"
|
||||
down_revision: Union[str, None] = "012"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("hosts", sa.Column("inventory", JSONB, nullable=True))
|
||||
op.add_column("hosts", sa.Column("inventory_updated_at", sa.DateTime(timezone=True), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("hosts", "inventory_updated_at")
|
||||
op.drop_column("hosts", "inventory")
|
||||
@@ -0,0 +1,105 @@
|
||||
"""mobile settings, enrollment codes, devices, refresh tokens; policy use_mobile
|
||||
|
||||
Revision ID: 014
|
||||
Revises: 013
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "014"
|
||||
down_revision: Union[str, None] = "013"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
MOBILE_SETTINGS_ROW_ID = 1
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"notification_policy",
|
||||
sa.Column("use_mobile", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"mobile_settings",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("devices_allowed", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
sa.Column("max_devices_per_user", sa.Integer(), nullable=False, server_default="3"),
|
||||
sa.Column("min_app_version", sa.String(length=32), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.execute(
|
||||
sa.text(
|
||||
"INSERT INTO mobile_settings (id, devices_allowed, max_devices_per_user) "
|
||||
f"VALUES ({MOBILE_SETTINGS_ROW_ID}, false, 3)"
|
||||
)
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"mobile_enrollment_codes",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("label", sa.String(length=128), nullable=False, server_default=""),
|
||||
sa.Column("code_hash", sa.String(length=64), nullable=False),
|
||||
sa.Column("code_prefix", sa.String(length=16), nullable=False),
|
||||
sa.Column("created_by_user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("target_user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("login_mode", sa.String(length=16), nullable=False, server_default="password"),
|
||||
sa.Column("max_uses", sa.Integer(), nullable=False, server_default="1"),
|
||||
sa.Column("use_count", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["created_by_user_id"], ["sac_users.id"], ondelete="SET NULL"),
|
||||
sa.ForeignKeyConstraint(["target_user_id"], ["sac_users.id"], ondelete="SET NULL"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_mobile_enrollment_codes_code_hash", "mobile_enrollment_codes", ["code_hash"], unique=True)
|
||||
|
||||
op.create_table(
|
||||
"mobile_devices",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("user_id", sa.Integer(), nullable=False),
|
||||
sa.Column("device_uuid", sa.String(length=64), nullable=False),
|
||||
sa.Column("display_name", sa.String(length=128), nullable=False, server_default=""),
|
||||
sa.Column("platform", sa.String(length=32), nullable=False, server_default="android"),
|
||||
sa.Column("app_version", sa.String(length=32), nullable=True),
|
||||
sa.Column("fcm_token", sa.String(length=512), nullable=True),
|
||||
sa.Column("fcm_token_updated_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("enrollment_code_id", sa.Integer(), nullable=True),
|
||||
sa.Column("enrolled_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["sac_users.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["enrollment_code_id"], ["mobile_enrollment_codes.id"], ondelete="SET NULL"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_mobile_devices_device_uuid", "mobile_devices", ["device_uuid"], unique=True)
|
||||
op.create_index("ix_mobile_devices_user_id", "mobile_devices", ["user_id"])
|
||||
|
||||
op.create_table(
|
||||
"mobile_refresh_tokens",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("device_id", sa.Integer(), nullable=False),
|
||||
sa.Column("token_hash", sa.String(length=64), nullable=False),
|
||||
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(["device_id"], ["mobile_devices.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_mobile_refresh_tokens_token_hash", "mobile_refresh_tokens", ["token_hash"], unique=True)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_mobile_refresh_tokens_token_hash", table_name="mobile_refresh_tokens")
|
||||
op.drop_table("mobile_refresh_tokens")
|
||||
op.drop_index("ix_mobile_devices_user_id", table_name="mobile_devices")
|
||||
op.drop_index("ix_mobile_devices_device_uuid", table_name="mobile_devices")
|
||||
op.drop_table("mobile_devices")
|
||||
op.drop_index("ix_mobile_enrollment_codes_code_hash", table_name="mobile_enrollment_codes")
|
||||
op.drop_table("mobile_enrollment_codes")
|
||||
op.drop_table("mobile_settings")
|
||||
op.drop_column("notification_policy", "use_mobile")
|
||||
@@ -0,0 +1,26 @@
|
||||
"""problems.resolved_by — manual vs auto close for host_silence cooldown
|
||||
|
||||
Revision ID: 015
|
||||
Revises: 014
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "015"
|
||||
down_revision: Union[str, None] = "014"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"problems",
|
||||
sa.Column("resolved_by", sa.String(length=16), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("problems", "resolved_by")
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Revision ID: 016
|
||||
Revises: 015
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "016"
|
||||
down_revision: Union[str, None] = "015"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"agent_commands",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("command_uuid", sa.String(length=36), nullable=False),
|
||||
sa.Column("host_id", sa.Integer(), nullable=False),
|
||||
sa.Column("event_id", sa.Integer(), nullable=True),
|
||||
sa.Column("command_type", sa.String(length=32), nullable=False),
|
||||
sa.Column("params", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column("status", sa.String(length=16), nullable=False, server_default="pending"),
|
||||
sa.Column("result_stdout", sa.Text(), nullable=True),
|
||||
sa.Column("result_stderr", sa.Text(), nullable=True),
|
||||
sa.Column("requested_by", sa.String(length=128), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(["event_id"], ["events.id"], ondelete="SET NULL"),
|
||||
sa.ForeignKeyConstraint(["host_id"], ["hosts.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("command_uuid"),
|
||||
)
|
||||
op.create_index("ix_agent_commands_command_uuid", "agent_commands", ["command_uuid"])
|
||||
op.create_index("ix_agent_commands_host_id", "agent_commands", ["host_id"])
|
||||
op.create_index("ix_agent_commands_status", "agent_commands", ["status"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_agent_commands_status", table_name="agent_commands")
|
||||
op.drop_index("ix_agent_commands_host_id", table_name="agent_commands")
|
||||
op.drop_index("ix_agent_commands_command_uuid", table_name="agent_commands")
|
||||
op.drop_table("agent_commands")
|
||||
@@ -0,0 +1,25 @@
|
||||
"""ui_settings: Windows domain admin for agent commands
|
||||
|
||||
Revision ID: 017
|
||||
Revises: 016
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "017"
|
||||
down_revision: Union[str, None] = "016"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("ui_settings", sa.Column("win_admin_user", sa.String(256), nullable=True))
|
||||
op.add_column("ui_settings", sa.Column("win_admin_password", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("ui_settings", "win_admin_password")
|
||||
op.drop_column("ui_settings", "win_admin_user")
|
||||
@@ -0,0 +1,25 @@
|
||||
"""ui_settings: Linux SSH admin for remote agent updates
|
||||
|
||||
Revision ID: 018
|
||||
Revises: 017
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "018"
|
||||
down_revision: Union[str, None] = "017"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("ui_settings", sa.Column("linux_admin_user", sa.String(128), nullable=True))
|
||||
op.add_column("ui_settings", sa.Column("linux_admin_password", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("ui_settings", "linux_admin_password")
|
||||
op.drop_column("ui_settings", "linux_admin_user")
|
||||
@@ -0,0 +1,25 @@
|
||||
"""hosts: persist Linux SSH admin check status
|
||||
|
||||
Revision ID: 019
|
||||
Revises: 018
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "019"
|
||||
down_revision: Union[str, None] = "018"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("hosts", sa.Column("ssh_admin_ok", sa.Boolean(), nullable=True))
|
||||
op.add_column("hosts", sa.Column("ssh_admin_checked_at", sa.DateTime(timezone=True), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("hosts", "ssh_admin_checked_at")
|
||||
op.drop_column("hosts", "ssh_admin_ok")
|
||||
@@ -0,0 +1,98 @@
|
||||
"""agent control plane: host update/config + ui agent-update settings
|
||||
|
||||
Revision ID: 020
|
||||
Revises: 019
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
revision: str = "020"
|
||||
down_revision: Union[str, None] = "019"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"hosts",
|
||||
sa.Column("pending_agent_update", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
)
|
||||
op.add_column(
|
||||
"hosts",
|
||||
sa.Column("pending_update_requested_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"hosts",
|
||||
sa.Column("pending_update_target_version", sa.String(length=64), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"hosts",
|
||||
sa.Column("agent_config", JSONB, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"hosts",
|
||||
sa.Column("agent_config_revision", sa.Integer(), nullable=False, server_default="0"),
|
||||
)
|
||||
op.add_column(
|
||||
"hosts",
|
||||
sa.Column("agent_update_state", sa.String(length=32), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"hosts",
|
||||
sa.Column("agent_update_last_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.add_column("hosts", sa.Column("agent_update_last_error", sa.Text(), nullable=True))
|
||||
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_update_mode", sa.String(length=16), nullable=False, server_default="gpo"),
|
||||
)
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_update_fallback_enabled", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
)
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_update_fallback_minutes", sa.Integer(), nullable=False, server_default="15"),
|
||||
)
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_recommended_rdp_version", sa.String(length=64), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_recommended_ssh_version", sa.String(length=64), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_min_rdp_version", sa.String(length=64), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_min_ssh_version", sa.String(length=64), nullable=True),
|
||||
)
|
||||
op.add_column("ui_settings", sa.Column("win_agent_update_script", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("ui_settings", "win_agent_update_script")
|
||||
op.drop_column("ui_settings", "agent_min_ssh_version")
|
||||
op.drop_column("ui_settings", "agent_min_rdp_version")
|
||||
op.drop_column("ui_settings", "agent_recommended_ssh_version")
|
||||
op.drop_column("ui_settings", "agent_recommended_rdp_version")
|
||||
op.drop_column("ui_settings", "agent_update_fallback_minutes")
|
||||
op.drop_column("ui_settings", "agent_update_fallback_enabled")
|
||||
op.drop_column("ui_settings", "agent_update_mode")
|
||||
|
||||
op.drop_column("hosts", "agent_update_last_error")
|
||||
op.drop_column("hosts", "agent_update_last_at")
|
||||
op.drop_column("hosts", "agent_update_state")
|
||||
op.drop_column("hosts", "agent_config_revision")
|
||||
op.drop_column("hosts", "agent_config")
|
||||
op.drop_column("hosts", "pending_update_target_version")
|
||||
op.drop_column("hosts", "pending_update_requested_at")
|
||||
op.drop_column("hosts", "pending_agent_update")
|
||||
@@ -0,0 +1,51 @@
|
||||
"""agent git release repos and version cache
|
||||
|
||||
Revision ID: 021
|
||||
Revises: 020
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "021"
|
||||
down_revision: Union[str, None] = "020"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_rdp_git_repo_url", sa.String(length=512), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_ssh_git_repo_url", sa.String(length=512), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_git_branch", sa.String(length=64), nullable=False, server_default="main"),
|
||||
)
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_git_rdp_version", sa.String(length=64), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_git_ssh_version", sa.String(length=64), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("agent_git_fetched_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("ui_settings", "agent_git_fetched_at")
|
||||
op.drop_column("ui_settings", "agent_git_ssh_version")
|
||||
op.drop_column("ui_settings", "agent_git_rdp_version")
|
||||
op.drop_column("ui_settings", "agent_git_branch")
|
||||
op.drop_column("ui_settings", "agent_ssh_git_repo_url")
|
||||
op.drop_column("ui_settings", "agent_rdp_git_repo_url")
|
||||
@@ -0,0 +1,27 @@
|
||||
"""host remote_action JSON for background SSH/WinRM jobs
|
||||
|
||||
Revision ID: 022_host_remote_action
|
||||
Revises: 021
|
||||
Create Date: 2026-06-21
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision = "022_host_remote_action"
|
||||
down_revision = "021"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"hosts",
|
||||
sa.Column("remote_action", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("hosts", "remote_action")
|
||||
@@ -0,0 +1,34 @@
|
||||
"""event type visibility settings
|
||||
|
||||
Revision ID: 023_event_type_visibility
|
||||
Revises: 022_host_remote_action
|
||||
Create Date: 2026-06-21
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "023_event_type_visibility"
|
||||
down_revision = "022_host_remote_action"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"event_type_visibility",
|
||||
sa.Column("event_type", sa.String(length=128), nullable=False),
|
||||
sa.Column("show_in_events", sa.Boolean(), nullable=False),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("event_type"),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("event_type_visibility")
|
||||
@@ -0,0 +1,28 @@
|
||||
"""login security whitelist in ui_settings
|
||||
|
||||
Revision ID: 024_login_security
|
||||
Revises: 023_event_type_visibility
|
||||
Create Date: 2026-06-25
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "024_login_security"
|
||||
down_revision = "023_event_type_visibility"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("ui_settings", sa.Column("login_ip_whitelist", sa.Text(), nullable=True))
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("login_sync_fail2ban", sa.Boolean(), server_default="false", nullable=False),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("ui_settings", "login_sync_fail2ban")
|
||||
op.drop_column("ui_settings", "login_ip_whitelist")
|
||||
@@ -0,0 +1,26 @@
|
||||
"""auto RDP flap disconnect setting in ui_settings
|
||||
|
||||
Revision ID: 025_auto_rdp_flap_disconnect
|
||||
Revises: 024_login_security
|
||||
Create Date: 2026-07-02
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "025_auto_rdp_flap_disconnect"
|
||||
down_revision = "024_login_security"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"ui_settings",
|
||||
sa.Column("auto_rdp_flap_disconnect", sa.Boolean(), server_default="false", nullable=False),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("ui_settings", "auto_rdp_flap_disconnect")
|
||||
@@ -0,0 +1,74 @@
|
||||
"""dedupe host_silence problems + unique active index
|
||||
|
||||
Revision ID: 026_host_silence_dedupe
|
||||
Revises: 025_auto_rdp_flap_disconnect
|
||||
Create Date: 2026-07-03
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision = "026_host_silence_dedupe"
|
||||
down_revision = "025_auto_rdp_flap_disconnect"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _dedupe_host_silence_problems() -> None:
|
||||
op.execute(
|
||||
"""
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
p.id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY p.host_id
|
||||
ORDER BY p.last_seen_at DESC, p.id DESC
|
||||
) AS rn
|
||||
FROM problems p
|
||||
WHERE p.rule_id = 'rule:host_silence'
|
||||
AND p.status IN ('open', 'acknowledged')
|
||||
)
|
||||
UPDATE problems
|
||||
SET status = 'resolved',
|
||||
resolved_by = 'auto',
|
||||
updated_at = NOW()
|
||||
WHERE id IN (SELECT id FROM ranked WHERE rn > 1)
|
||||
"""
|
||||
)
|
||||
op.execute(
|
||||
"""
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
p.id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY lower(h.hostname)
|
||||
ORDER BY p.last_seen_at DESC, p.id DESC
|
||||
) AS rn
|
||||
FROM problems p
|
||||
JOIN hosts h ON h.id = p.host_id
|
||||
WHERE p.rule_id = 'rule:host_silence'
|
||||
AND p.status IN ('open', 'acknowledged')
|
||||
)
|
||||
UPDATE problems
|
||||
SET status = 'resolved',
|
||||
resolved_by = 'auto',
|
||||
updated_at = NOW()
|
||||
WHERE id IN (SELECT id FROM ranked WHERE rn > 1)
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
_dedupe_host_silence_problems()
|
||||
op.execute(
|
||||
"""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_problems_host_silence_active
|
||||
ON problems (host_id)
|
||||
WHERE rule_id = 'rule:host_silence'
|
||||
AND status IN ('open', 'acknowledged')
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP INDEX IF EXISTS uq_problems_host_silence_active")
|
||||
@@ -0,0 +1,25 @@
|
||||
"""per-host management credentials (SSH / WinRM override)
|
||||
|
||||
Revision ID: 027_host_mgmt_credentials
|
||||
Revises: 026_host_silence_dedupe
|
||||
Create Date: 2026-07-14
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "027_host_mgmt_credentials"
|
||||
down_revision = "026_host_silence_dedupe"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("hosts", sa.Column("mgmt_user", sa.String(length=256), nullable=True))
|
||||
op.add_column("hosts", sa.Column("mgmt_password", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("hosts", "mgmt_password")
|
||||
op.drop_column("hosts", "mgmt_user")
|
||||
Reference in New Issue
Block a user