monsterops 1.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. monsterops/__init__.py +7 -0
  2. monsterops/alembic/README +1 -0
  3. monsterops/alembic/env.py +85 -0
  4. monsterops/alembic/script.py.mako +28 -0
  5. monsterops/alembic/versions/a2f9c4b1e837_phase13_notifications.py +122 -0
  6. monsterops/alembic/versions/a4d1c7e6b520_firewall_autoblock.py +37 -0
  7. monsterops/alembic/versions/a9d4e7f2c1b5_realms_proxy.py +115 -0
  8. monsterops/alembic/versions/a9f3e2b1c0d8_retrograde_stop_trigger.py +39 -0
  9. monsterops/alembic/versions/b3e9a1f72c04_radpostauth_extra_columns.py +22 -0
  10. monsterops/alembic/versions/b6f2d8a3e91c_vpn_tunnels.py +54 -0
  11. monsterops/alembic/versions/b7d3f1e92a05_radpostauth_station_class_columns.py +21 -0
  12. monsterops/alembic/versions/b7f2c9a4e1d3_nas_reachability.py +35 -0
  13. monsterops/alembic/versions/b8e3f1a2d9c4_firewall_set_managed_source.py +22 -0
  14. monsterops/alembic/versions/c1f7b4a9d2e6_keyset_pagination_indexes.py +40 -0
  15. monsterops/alembic/versions/c2a8f4e71b93_radacct_unique_index_and_nasreload.py +27 -0
  16. monsterops/alembic/versions/c3f1a8e20d74_nas_manager.py +59 -0
  17. monsterops/alembic/versions/c7a1e3f92b84_nas_groups.py +48 -0
  18. monsterops/alembic/versions/c9e4b1f2a8d5_phase14_integrations.py +50 -0
  19. monsterops/alembic/versions/d1e5f8a2c3b7_phase15_apikeys_scheduler.py +75 -0
  20. monsterops/alembic/versions/d2e8b3f1a9c7_mr_bulk_jobs.py +32 -0
  21. monsterops/alembic/versions/d3b7f0a15c92_firewall_block_event.py +38 -0
  22. monsterops/alembic/versions/d4b2f6c81e93_nas_config_history.py +57 -0
  23. monsterops/alembic/versions/e1f4a9c2b6d8_mr_webhook_subs.py +36 -0
  24. monsterops/alembic/versions/e3a7f1c90d52_group_access_types.py +28 -0
  25. monsterops/alembic/versions/e5c9a1f3d827_auth_framework.py +134 -0
  26. monsterops/alembic/versions/e7c3a9d15f84_firewall.py +93 -0
  27. monsterops/alembic/versions/e85388a04f39_initial_schema.py +162 -0
  28. monsterops/alembic/versions/f2a8c4d97b16_log_table_indexes.py +19 -0
  29. monsterops/alembic/versions/f3a7c1e2d9b4_mr_automation_rules.py +39 -0
  30. monsterops/alembic/versions/f4c8d2a91b37_phase9_auth_enrichment.py +23 -0
  31. monsterops/alembic/versions/fd99a6deb43d_add_audit_log.py +35 -0
  32. monsterops/alembic.ini +150 -0
  33. monsterops/app.py +246 -0
  34. monsterops/cli.py +568 -0
  35. monsterops/config.py +106 -0
  36. monsterops/database.py +29 -0
  37. monsterops/events.py +51 -0
  38. monsterops/geo.py +108 -0
  39. monsterops/keyrotate.py +71 -0
  40. monsterops/limiter.py +6 -0
  41. monsterops/modules/__init__.py +0 -0
  42. monsterops/modules/accounting/__init__.py +0 -0
  43. monsterops/modules/accounting/coa.py +172 -0
  44. monsterops/modules/accounting/models.py +63 -0
  45. monsterops/modules/accounting/radius.dict +17 -0
  46. monsterops/modules/accounting/router.py +299 -0
  47. monsterops/modules/accounting/schemas.py +60 -0
  48. monsterops/modules/accounting/static/accounting.js +685 -0
  49. monsterops/modules/accounting/static/manifest.json +12 -0
  50. monsterops/modules/apikeys/__init__.py +0 -0
  51. monsterops/modules/apikeys/models.py +27 -0
  52. monsterops/modules/apikeys/router.py +210 -0
  53. monsterops/modules/apikeys/schemas.py +30 -0
  54. monsterops/modules/apikeys/static/apikeys.js +338 -0
  55. monsterops/modules/apikeys/static/manifest.json +5 -0
  56. monsterops/modules/apikeys/v1.py +754 -0
  57. monsterops/modules/auth/__init__.py +0 -0
  58. monsterops/modules/auth/models.py +40 -0
  59. monsterops/modules/auth/router.py +333 -0
  60. monsterops/modules/auth/schemas.py +71 -0
  61. monsterops/modules/auth/utils.py +201 -0
  62. monsterops/modules/auth_logs/__init__.py +0 -0
  63. monsterops/modules/auth_logs/models.py +29 -0
  64. monsterops/modules/auth_logs/router.py +383 -0
  65. monsterops/modules/auth_logs/schemas.py +50 -0
  66. monsterops/modules/auth_logs/static/auth_logs.js +821 -0
  67. monsterops/modules/auth_logs/static/manifest.json +12 -0
  68. monsterops/modules/automation/__init__.py +0 -0
  69. monsterops/modules/automation/engine.py +335 -0
  70. monsterops/modules/automation/models.py +23 -0
  71. monsterops/modules/automation/router.py +169 -0
  72. monsterops/modules/automation/static/automation.js +639 -0
  73. monsterops/modules/automation/static/manifest.json +12 -0
  74. monsterops/modules/dashboard/__init__.py +0 -0
  75. monsterops/modules/dashboard/router.py +240 -0
  76. monsterops/modules/dashboard/schemas.py +62 -0
  77. monsterops/modules/dashboard/static/dashboard.js +1074 -0
  78. monsterops/modules/dashboard/static/manifest.json +7 -0
  79. monsterops/modules/firewall/__init__.py +0 -0
  80. monsterops/modules/firewall/generator.py +203 -0
  81. monsterops/modules/firewall/geoblock.py +149 -0
  82. monsterops/modules/firewall/models.py +114 -0
  83. monsterops/modules/firewall/nft.py +96 -0
  84. monsterops/modules/firewall/presets.py +81 -0
  85. monsterops/modules/firewall/router.py +665 -0
  86. monsterops/modules/firewall/schemas.py +193 -0
  87. monsterops/modules/firewall/service.py +543 -0
  88. monsterops/modules/firewall/static/firewall.js +1048 -0
  89. monsterops/modules/firewall/static/manifest.json +12 -0
  90. monsterops/modules/firewall/validators.py +79 -0
  91. monsterops/modules/firewall/worker.py +168 -0
  92. monsterops/modules/groups/__init__.py +0 -0
  93. monsterops/modules/groups/models.py +40 -0
  94. monsterops/modules/groups/router.py +601 -0
  95. monsterops/modules/groups/schemas.py +85 -0
  96. monsterops/modules/groups/static/groups.js +1100 -0
  97. monsterops/modules/groups/static/manifest.json +12 -0
  98. monsterops/modules/health/__init__.py +0 -0
  99. monsterops/modules/health/router.py +574 -0
  100. monsterops/modules/health/schemas.py +36 -0
  101. monsterops/modules/health/static/health.js +519 -0
  102. monsterops/modules/health/static/manifest.json +12 -0
  103. monsterops/modules/integrations/__init__.py +0 -0
  104. monsterops/modules/integrations/graylog_client.py +108 -0
  105. monsterops/modules/integrations/models.py +21 -0
  106. monsterops/modules/integrations/router.py +257 -0
  107. monsterops/modules/integrations/schemas.py +38 -0
  108. monsterops/modules/integrations/static/img/graylog.svg +1 -0
  109. monsterops/modules/integrations/static/img/zabbix.svg +1 -0
  110. monsterops/modules/integrations/static/integrations.js +1166 -0
  111. monsterops/modules/integrations/static/manifest.json +5 -0
  112. monsterops/modules/integrations/zabbix_client.py +178 -0
  113. monsterops/modules/ip_pools/__init__.py +0 -0
  114. monsterops/modules/ip_pools/models.py +20 -0
  115. monsterops/modules/ip_pools/router.py +253 -0
  116. monsterops/modules/ip_pools/schemas.py +54 -0
  117. monsterops/modules/ip_pools/static/ip_pools.js +942 -0
  118. monsterops/modules/ip_pools/static/manifest.json +12 -0
  119. monsterops/modules/nas/__init__.py +0 -0
  120. monsterops/modules/nas/models.py +64 -0
  121. monsterops/modules/nas/probe.py +142 -0
  122. monsterops/modules/nas/radius_attr_hints.py +167 -0
  123. monsterops/modules/nas/router.py +630 -0
  124. monsterops/modules/nas/schemas.py +152 -0
  125. monsterops/modules/nas/static/manifest.json +12 -0
  126. monsterops/modules/nas/static/nas.js +1782 -0
  127. monsterops/modules/nas_manager/__init__.py +0 -0
  128. monsterops/modules/nas_manager/crypto.py +30 -0
  129. monsterops/modules/nas_manager/history.py +126 -0
  130. monsterops/modules/nas_manager/models.py +80 -0
  131. monsterops/modules/nas_manager/radius_deploy.py +264 -0
  132. monsterops/modules/nas_manager/router.py +873 -0
  133. monsterops/modules/nas_manager/schemas.py +258 -0
  134. monsterops/modules/nas_manager/service.py +125 -0
  135. monsterops/modules/nas_manager/static/manifest.json +5 -0
  136. monsterops/modules/nas_manager/static/nas_manager.js +1353 -0
  137. monsterops/modules/nas_manager/vendor_map.py +71 -0
  138. monsterops/modules/nas_manager/worker.py +89 -0
  139. monsterops/modules/notifications/__init__.py +0 -0
  140. monsterops/modules/notifications/channels.py +97 -0
  141. monsterops/modules/notifications/models.py +61 -0
  142. monsterops/modules/notifications/router.py +178 -0
  143. monsterops/modules/notifications/schemas.py +81 -0
  144. monsterops/modules/notifications/static/manifest.json +12 -0
  145. monsterops/modules/notifications/static/notifications.js +881 -0
  146. monsterops/modules/notifications/worker.py +208 -0
  147. monsterops/modules/radius_logs/__init__.py +0 -0
  148. monsterops/modules/radius_logs/static/manifest.json +5 -0
  149. monsterops/modules/radius_logs/static/radius_logs.js +407 -0
  150. monsterops/modules/realms/__init__.py +0 -0
  151. monsterops/modules/realms/enforcement/__init__.py +16 -0
  152. monsterops/modules/realms/enforcement/base.py +47 -0
  153. monsterops/modules/realms/enforcement/freeradius.py +207 -0
  154. monsterops/modules/realms/ldap_probe.py +132 -0
  155. monsterops/modules/realms/ldap_sync.py +544 -0
  156. monsterops/modules/realms/models.py +199 -0
  157. monsterops/modules/realms/probe.py +111 -0
  158. monsterops/modules/realms/proxyconf.py +135 -0
  159. monsterops/modules/realms/radius.dict +4 -0
  160. monsterops/modules/realms/router.py +1181 -0
  161. monsterops/modules/realms/schemas.py +402 -0
  162. monsterops/modules/realms/static/manifest.json +12 -0
  163. monsterops/modules/realms/static/realms.js +1604 -0
  164. monsterops/modules/reports/__init__.py +0 -0
  165. monsterops/modules/reports/router.py +302 -0
  166. monsterops/modules/reports/schemas.py +39 -0
  167. monsterops/modules/reports/static/manifest.json +12 -0
  168. monsterops/modules/reports/static/reports.js +552 -0
  169. monsterops/modules/scheduler/__init__.py +0 -0
  170. monsterops/modules/scheduler/jobs.py +455 -0
  171. monsterops/modules/scheduler/models.py +45 -0
  172. monsterops/modules/scheduler/router.py +160 -0
  173. monsterops/modules/scheduler/schemas.py +56 -0
  174. monsterops/modules/scheduler/service.py +172 -0
  175. monsterops/modules/scheduler/static/manifest.json +5 -0
  176. monsterops/modules/scheduler/static/scheduler.js +465 -0
  177. monsterops/modules/system/__init__.py +0 -0
  178. monsterops/modules/system/router.py +304 -0
  179. monsterops/modules/system/static/manifest.json +12 -0
  180. monsterops/modules/system/static/system.js +1006 -0
  181. monsterops/modules/users/__init__.py +0 -0
  182. monsterops/modules/users/models.py +58 -0
  183. monsterops/modules/users/router.py +1205 -0
  184. monsterops/modules/users/schemas.py +235 -0
  185. monsterops/modules/users/static/manifest.json +12 -0
  186. monsterops/modules/users/static/users.js +1817 -0
  187. monsterops/modules/vpn/__init__.py +0 -0
  188. monsterops/modules/vpn/backends/__init__.py +0 -0
  189. monsterops/modules/vpn/backends/base.py +145 -0
  190. monsterops/modules/vpn/backends/l2tp_ipsec.py +242 -0
  191. monsterops/modules/vpn/backends/wireguard.py +128 -0
  192. monsterops/modules/vpn/models.py +44 -0
  193. monsterops/modules/vpn/router.py +268 -0
  194. monsterops/modules/vpn/schemas.py +271 -0
  195. monsterops/modules/vpn/service.py +32 -0
  196. monsterops/modules/vpn/static/manifest.json +12 -0
  197. monsterops/modules/vpn/static/vpn.js +618 -0
  198. monsterops/modules/vpn/wgkeys.py +69 -0
  199. monsterops/modules/vpn/worker.py +41 -0
  200. monsterops/modules/webhooks/__init__.py +0 -0
  201. monsterops/modules/webhooks/handler.py +122 -0
  202. monsterops/modules/webhooks/models.py +21 -0
  203. monsterops/modules/webhooks/router.py +225 -0
  204. monsterops/modules/webhooks/static/manifest.json +5 -0
  205. monsterops/modules/webhooks/static/webhooks.js +506 -0
  206. monsterops/pagination.py +20 -0
  207. monsterops/plugins/__init__.py +0 -0
  208. monsterops/plugins/loader.py +49 -0
  209. monsterops/radius_reload.py +115 -0
  210. monsterops/static/css/theme.css +401 -0
  211. monsterops/static/img/monsterops-mascot.png +0 -0
  212. monsterops/static/index.html +25 -0
  213. monsterops/static/js/api.js +131 -0
  214. monsterops/static/js/app.js +199 -0
  215. monsterops/static/js/components/app-confirm.js +145 -0
  216. monsterops/static/js/components/app-sidebar.js +653 -0
  217. monsterops/static/js/components/app-statusbar.js +187 -0
  218. monsterops/static/js/components/app-toast.js +105 -0
  219. monsterops/static/js/components/base-component.js +51 -0
  220. monsterops/static/js/components/mr-card.js +93 -0
  221. monsterops/static/js/components/mr-modal.js +179 -0
  222. monsterops/static/js/components/server-console.js +519 -0
  223. monsterops/static/js/components/signal-pulse.js +103 -0
  224. monsterops/static/js/loader.js +22 -0
  225. monsterops/static/js/router.js +49 -0
  226. monsterops/static/js/utils/countries.js +263 -0
  227. monsterops/static/js/utils/empty.js +151 -0
  228. monsterops/static/js/utils/form.js +88 -0
  229. monsterops/static/js/utils/geo.js +45 -0
  230. monsterops/static/js/utils/poll.js +41 -0
  231. monsterops/static/js/utils/sparkline.js +96 -0
  232. monsterops/static/js/utils/table.js +186 -0
  233. monsterops/static/js/views/login.js +167 -0
  234. monsterops/static/js/views/setup.js +146 -0
  235. monsterops/static/js/views/style_guide.js +289 -0
  236. monsterops-1.12.0.dist-info/METADATA +138 -0
  237. monsterops-1.12.0.dist-info/RECORD +240 -0
  238. monsterops-1.12.0.dist-info/WHEEL +4 -0
  239. monsterops-1.12.0.dist-info/entry_points.txt +2 -0
  240. monsterops-1.12.0.dist-info/licenses/LICENSE +21 -0
monsterops/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+
2
+ from importlib.metadata import PackageNotFoundError, version
3
+
4
+ try:
5
+ __version__ = version("monsterops")
6
+ except PackageNotFoundError:
7
+ __version__ = "0.0.0+unknown"
@@ -0,0 +1 @@
1
+ Generic single-database configuration.
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from logging.config import fileConfig
5
+
6
+ from sqlalchemy import pool
7
+ from sqlalchemy.ext.asyncio import async_engine_from_config
8
+
9
+ import monsterops.modules.accounting.models # noqa: F401
10
+ import monsterops.modules.apikeys.models # noqa: F401
11
+
12
+ import monsterops.modules.auth.models # noqa: F401
13
+ import monsterops.modules.auth_logs.models # noqa: F401
14
+ import monsterops.modules.automation.models # noqa: F401
15
+ import monsterops.modules.groups.models # noqa: F401
16
+ import monsterops.modules.integrations.models # noqa: F401
17
+ import monsterops.modules.ip_pools.models # noqa: F401
18
+ import monsterops.modules.nas.models # noqa: F401
19
+ import monsterops.modules.notifications.models # noqa: F401
20
+ import monsterops.modules.scheduler.models # noqa: F401
21
+ import monsterops.modules.users.models # noqa: F401
22
+ import monsterops.modules.webhooks.models # noqa: F401
23
+ from alembic import context
24
+ from monsterops.config import settings
25
+ from monsterops.database import Base
26
+
27
+ config = context.config
28
+ config.set_main_option("sqlalchemy.url", settings.database_url)
29
+
30
+ if config.config_file_name is not None:
31
+ fileConfig(config.config_file_name)
32
+
33
+ target_metadata = Base.metadata
34
+
35
+
36
+ def run_migrations_offline() -> None:
37
+ url = config.get_main_option("sqlalchemy.url")
38
+ context.configure(
39
+ url=url,
40
+ target_metadata=target_metadata,
41
+ literal_binds=True,
42
+ dialect_opts={"paramstyle": "named"},
43
+ )
44
+ with context.begin_transaction():
45
+ context.run_migrations()
46
+
47
+
48
+ _SKIP_INDEXES = {"radacct_calss_idx", "radpostauth_class_idx"}
49
+
50
+
51
+ def _include_object(obj, name, type_, reflected, compare_to):
52
+ if type_ == "index" and name in _SKIP_INDEXES:
53
+ return False
54
+ return True
55
+
56
+
57
+ def do_run_migrations(connection):
58
+ context.configure(
59
+ connection=connection,
60
+ target_metadata=target_metadata,
61
+ include_object=_include_object,
62
+ )
63
+ with context.begin_transaction():
64
+ context.run_migrations()
65
+
66
+
67
+ async def run_async_migrations() -> None:
68
+ connectable = async_engine_from_config(
69
+ config.get_section(config.config_ini_section, {}),
70
+ prefix="sqlalchemy.",
71
+ poolclass=pool.NullPool,
72
+ )
73
+ async with connectable.connect() as connection:
74
+ await connection.run_sync(do_run_migrations)
75
+ await connectable.dispose()
76
+
77
+
78
+ def run_migrations_online() -> None:
79
+ asyncio.run(run_async_migrations())
80
+
81
+
82
+ if context.is_offline_mode():
83
+ run_migrations_offline()
84
+ else:
85
+ run_migrations_online()
@@ -0,0 +1,28 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ ${upgrades if upgrades else "pass"}
24
+
25
+
26
+ def downgrade() -> None:
27
+ """Downgrade schema."""
28
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,122 @@
1
+
2
+ from typing import Sequence, Union
3
+
4
+ import sqlalchemy as sa
5
+ from sqlalchemy.dialects import postgresql
6
+
7
+ from alembic import op
8
+
9
+ revision: str = "a2f9c4b1e837"
10
+ down_revision: Union[str, Sequence[str], None] = "e3a7f1c90d52"
11
+ branch_labels: Union[str, Sequence[str], None] = None
12
+ depends_on: Union[str, Sequence[str], None] = None
13
+
14
+
15
+ def upgrade() -> None:
16
+ op.create_table(
17
+ "mr_notification_channels",
18
+ sa.Column("id", sa.Integer(), nullable=False),
19
+ sa.Column("name", sa.Text(), nullable=False),
20
+ sa.Column("type", sa.Text(), nullable=False),
21
+ sa.Column(
22
+ "config",
23
+ postgresql.JSONB(astext_type=sa.Text()),
24
+ server_default="{}",
25
+ nullable=False,
26
+ ),
27
+ sa.Column("enabled", sa.Boolean(), nullable=False, default=True),
28
+ sa.Column(
29
+ "created_at",
30
+ sa.TIMESTAMP(timezone=True),
31
+ server_default=sa.text("now()"),
32
+ nullable=False,
33
+ ),
34
+ sa.Column(
35
+ "updated_at",
36
+ sa.TIMESTAMP(timezone=True),
37
+ server_default=sa.text("now()"),
38
+ nullable=False,
39
+ ),
40
+ sa.PrimaryKeyConstraint("id"),
41
+ sa.UniqueConstraint("name"),
42
+ )
43
+
44
+ op.create_table(
45
+ "mr_notification_rules",
46
+ sa.Column("id", sa.Integer(), nullable=False),
47
+ sa.Column("name", sa.Text(), nullable=False),
48
+ sa.Column("event_type", sa.Text(), nullable=False),
49
+ sa.Column(
50
+ "config",
51
+ postgresql.JSONB(astext_type=sa.Text()),
52
+ server_default="{}",
53
+ nullable=False,
54
+ ),
55
+ sa.Column("channel_id", sa.Integer(), nullable=True),
56
+ sa.Column("cooldown_minutes", sa.Integer(), nullable=False, server_default="60"),
57
+ sa.Column("enabled", sa.Boolean(), nullable=False, server_default="true"),
58
+ sa.Column("last_triggered", sa.TIMESTAMP(timezone=True), nullable=True),
59
+ sa.Column(
60
+ "created_at",
61
+ sa.TIMESTAMP(timezone=True),
62
+ server_default=sa.text("now()"),
63
+ nullable=False,
64
+ ),
65
+ sa.Column(
66
+ "updated_at",
67
+ sa.TIMESTAMP(timezone=True),
68
+ server_default=sa.text("now()"),
69
+ nullable=False,
70
+ ),
71
+ sa.ForeignKeyConstraint(
72
+ ["channel_id"],
73
+ ["mr_notification_channels.id"],
74
+ ondelete="SET NULL",
75
+ ),
76
+ sa.PrimaryKeyConstraint("id"),
77
+ )
78
+ op.create_index("ix_notification_rules_event_type", "mr_notification_rules", ["event_type"])
79
+
80
+ op.create_table(
81
+ "mr_notification_history",
82
+ sa.Column("id", sa.BigInteger(), nullable=False),
83
+ sa.Column("rule_id", sa.Integer(), nullable=True),
84
+ sa.Column("rule_name", sa.Text(), nullable=True),
85
+ sa.Column("channel_id", sa.Integer(), nullable=True),
86
+ sa.Column("channel_name", sa.Text(), nullable=True),
87
+ sa.Column("event_type", sa.Text(), nullable=False),
88
+ sa.Column("subject", sa.Text(), nullable=False),
89
+ sa.Column("message", sa.Text(), nullable=False),
90
+ sa.Column("status", sa.Text(), nullable=False),
91
+ sa.Column("error", sa.Text(), nullable=True),
92
+ sa.Column(
93
+ "created_at",
94
+ sa.TIMESTAMP(timezone=True),
95
+ server_default=sa.text("now()"),
96
+ nullable=False,
97
+ ),
98
+ sa.ForeignKeyConstraint(
99
+ ["channel_id"],
100
+ ["mr_notification_channels.id"],
101
+ ondelete="SET NULL",
102
+ ),
103
+ sa.ForeignKeyConstraint(
104
+ ["rule_id"],
105
+ ["mr_notification_rules.id"],
106
+ ondelete="SET NULL",
107
+ ),
108
+ sa.PrimaryKeyConstraint("id"),
109
+ )
110
+ op.create_index(
111
+ "ix_notification_history_created_at",
112
+ "mr_notification_history",
113
+ ["created_at"],
114
+ )
115
+
116
+
117
+ def downgrade() -> None:
118
+ op.drop_index("ix_notification_history_created_at", table_name="mr_notification_history")
119
+ op.drop_table("mr_notification_history")
120
+ op.drop_index("ix_notification_rules_event_type", table_name="mr_notification_rules")
121
+ op.drop_table("mr_notification_rules")
122
+ op.drop_table("mr_notification_channels")
@@ -0,0 +1,37 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ import sqlalchemy as sa
5
+
6
+ from alembic import op
7
+
8
+ revision = "a4d1c7e6b520"
9
+ down_revision = "f2a8c4d97b16"
10
+ branch_labels = None
11
+ depends_on = None
12
+
13
+
14
+ def upgrade() -> None:
15
+ op.add_column(
16
+ "mr_firewall_config",
17
+ sa.Column("autoblock_enabled", sa.Boolean(), nullable=False, server_default=sa.false()),
18
+ )
19
+ op.add_column(
20
+ "mr_firewall_config",
21
+ sa.Column("autoblock_threshold", sa.Integer(), nullable=False, server_default="10"),
22
+ )
23
+ op.add_column(
24
+ "mr_firewall_config",
25
+ sa.Column("autoblock_window", sa.Integer(), nullable=False, server_default="10"),
26
+ )
27
+ op.add_column(
28
+ "mr_firewall_config",
29
+ sa.Column("autoblock_ban_seconds", sa.Integer(), nullable=False, server_default="3600"),
30
+ )
31
+
32
+
33
+ def downgrade() -> None:
34
+ op.drop_column("mr_firewall_config", "autoblock_ban_seconds")
35
+ op.drop_column("mr_firewall_config", "autoblock_window")
36
+ op.drop_column("mr_firewall_config", "autoblock_threshold")
37
+ op.drop_column("mr_firewall_config", "autoblock_enabled")
@@ -0,0 +1,115 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ import sqlalchemy as sa
5
+
6
+ from alembic import op
7
+
8
+ revision = "a9d4e7f2c1b5"
9
+ down_revision = "f3a7c1e2d9b4"
10
+ branch_labels = None
11
+ depends_on = None
12
+
13
+
14
+ def upgrade() -> None:
15
+ op.create_table(
16
+ "mr_home_server",
17
+ sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
18
+ sa.Column("name", sa.String(64), nullable=False, unique=True),
19
+ sa.Column("host", sa.Text(), nullable=False),
20
+ sa.Column("auth_port", sa.Integer(), nullable=False, server_default="1812"),
21
+ sa.Column("acct_port", sa.Integer(), nullable=False, server_default="1813"),
22
+ sa.Column("secret", sa.Text(), nullable=False),
23
+ sa.Column("type", sa.String(8), nullable=False, server_default="auth"),
24
+ sa.Column("response_window", sa.Integer(), nullable=False, server_default="20"),
25
+ sa.Column("zombie_period", sa.Integer(), nullable=False, server_default="40"),
26
+ sa.Column("revive_interval", sa.Integer(), nullable=False, server_default="120"),
27
+ sa.Column("vpn_interface", sa.String(32), nullable=True),
28
+ sa.Column("status", sa.String(16), nullable=False, server_default="unknown"),
29
+ sa.Column("last_rtt_ms", sa.Float(), nullable=True),
30
+ sa.Column("last_seen_at", sa.TIMESTAMP(timezone=True), nullable=True),
31
+ sa.Column("last_probe_at", sa.TIMESTAMP(timezone=True), nullable=True),
32
+ sa.Column(
33
+ "created_at",
34
+ sa.TIMESTAMP(timezone=True),
35
+ nullable=False,
36
+ server_default=sa.text("now()"),
37
+ ),
38
+ )
39
+
40
+ op.create_table(
41
+ "mr_home_server_pool",
42
+ sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
43
+ sa.Column("name", sa.String(64), nullable=False, unique=True),
44
+ sa.Column("pool_type", sa.String(24), nullable=False, server_default="fail-over"),
45
+ sa.Column(
46
+ "created_at",
47
+ sa.TIMESTAMP(timezone=True),
48
+ nullable=False,
49
+ server_default=sa.text("now()"),
50
+ ),
51
+ )
52
+
53
+ op.create_table(
54
+ "mr_home_server_pool_member",
55
+ sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
56
+ sa.Column(
57
+ "pool_id",
58
+ sa.Integer(),
59
+ sa.ForeignKey("mr_home_server_pool.id", ondelete="CASCADE"),
60
+ nullable=False,
61
+ ),
62
+ sa.Column(
63
+ "server_id",
64
+ sa.Integer(),
65
+ sa.ForeignKey("mr_home_server.id", ondelete="CASCADE"),
66
+ nullable=False,
67
+ ),
68
+ sa.Column("position", sa.Integer(), nullable=False, server_default="0"),
69
+ sa.UniqueConstraint("pool_id", "server_id", name="uq_pool_server"),
70
+ )
71
+
72
+ op.create_table(
73
+ "mr_realm",
74
+ sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
75
+ sa.Column("name", sa.String(128), nullable=False, unique=True),
76
+ sa.Column(
77
+ "pool_id",
78
+ sa.Integer(),
79
+ sa.ForeignKey("mr_home_server_pool.id", ondelete="SET NULL"),
80
+ nullable=True,
81
+ ),
82
+ sa.Column("strip_username", sa.Boolean(), nullable=False, server_default="true"),
83
+ sa.Column(
84
+ "created_at",
85
+ sa.TIMESTAMP(timezone=True),
86
+ nullable=False,
87
+ server_default=sa.text("now()"),
88
+ ),
89
+ )
90
+
91
+ op.create_table(
92
+ "mr_nas_group_realm",
93
+ sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
94
+ sa.Column(
95
+ "nas_group_id",
96
+ sa.Integer(),
97
+ sa.ForeignKey("mr_nas_group.id", ondelete="CASCADE"),
98
+ nullable=False,
99
+ ),
100
+ sa.Column(
101
+ "realm_id",
102
+ sa.Integer(),
103
+ sa.ForeignKey("mr_realm.id", ondelete="CASCADE"),
104
+ nullable=False,
105
+ ),
106
+ sa.UniqueConstraint("nas_group_id", "realm_id", name="uq_nasgroup_realm"),
107
+ )
108
+
109
+
110
+ def downgrade() -> None:
111
+ op.drop_table("mr_nas_group_realm")
112
+ op.drop_table("mr_realm")
113
+ op.drop_table("mr_home_server_pool_member")
114
+ op.drop_table("mr_home_server_pool")
115
+ op.drop_table("mr_home_server")
@@ -0,0 +1,39 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ from typing import Sequence, Union
5
+
6
+ from alembic import op
7
+
8
+ revision: str = "a9f3e2b1c0d8"
9
+ down_revision: Union[str, Sequence[str], None] = "d1e5f8a2c3b7"
10
+ branch_labels: Union[str, Sequence[str], None] = None
11
+ depends_on: Union[str, Sequence[str], None] = None
12
+
13
+
14
+
15
+ def upgrade() -> None:
16
+ op.execute("""
17
+ CREATE OR REPLACE FUNCTION fn_prevent_retrograde_stop()
18
+ RETURNS TRIGGER AS $$
19
+ BEGIN
20
+ IF NEW.acctstoptime IS NOT NULL AND NEW.acctstoptime < NEW.acctstarttime THEN
21
+ NEW.acctstoptime := NULL;
22
+ NEW.acctsessiontime := NULL;
23
+ NEW.acctterminatecause := '';
24
+ END IF;
25
+ RETURN NEW;
26
+ END;
27
+ $$ LANGUAGE plpgsql
28
+ """)
29
+ op.execute("DROP TRIGGER IF EXISTS trg_prevent_retrograde_stop ON radacct")
30
+ op.execute("""
31
+ CREATE TRIGGER trg_prevent_retrograde_stop
32
+ BEFORE INSERT OR UPDATE ON radacct
33
+ FOR EACH ROW EXECUTE FUNCTION fn_prevent_retrograde_stop()
34
+ """)
35
+
36
+
37
+ def downgrade() -> None:
38
+ op.execute("DROP TRIGGER IF EXISTS trg_prevent_retrograde_stop ON radacct")
39
+ op.execute("DROP FUNCTION IF EXISTS fn_prevent_retrograde_stop()")
@@ -0,0 +1,22 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ import sqlalchemy as sa
5
+ from sqlalchemy.dialects.postgresql import INET
6
+
7
+ from alembic import op
8
+
9
+ revision = "b3e9a1f72c04"
10
+ down_revision = "c7a1e3f92b84"
11
+ branch_labels = None
12
+ depends_on = None
13
+
14
+
15
+ def upgrade() -> None:
16
+ op.add_column("radpostauth", sa.Column("nasipaddress", INET(), nullable=True))
17
+ op.add_column("radpostauth", sa.Column("nasidentifier", sa.Text(), nullable=True))
18
+
19
+
20
+ def downgrade() -> None:
21
+ op.drop_column("radpostauth", "nasidentifier")
22
+ op.drop_column("radpostauth", "nasipaddress")
@@ -0,0 +1,54 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ import sqlalchemy as sa
5
+
6
+ from alembic import op
7
+
8
+ revision = "b6f2d8a3e91c"
9
+ down_revision = "a9d4e7f2c1b5"
10
+ branch_labels = None
11
+ depends_on = None
12
+
13
+
14
+ def upgrade() -> None:
15
+ op.create_table(
16
+ "mr_vpn_tunnel",
17
+ sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
18
+ sa.Column("name", sa.String(15), nullable=False, unique=True),
19
+ sa.Column("type", sa.String(16), nullable=False, server_default="wireguard"),
20
+ sa.Column("enabled", sa.Boolean(), nullable=False, server_default="false"),
21
+ sa.Column("description", sa.String(120), nullable=True),
22
+ sa.Column("routes", sa.Text(), nullable=False, server_default=""),
23
+ sa.Column("wg_private_key", sa.Text(), nullable=True),
24
+ sa.Column("wg_public_key", sa.Text(), nullable=True),
25
+ sa.Column("wg_address", sa.Text(), nullable=True),
26
+ sa.Column("wg_listen_port", sa.Integer(), nullable=True),
27
+ sa.Column("wg_peer_public_key", sa.Text(), nullable=True),
28
+ sa.Column("wg_peer_host", sa.Text(), nullable=True),
29
+ sa.Column("wg_peer_port", sa.Integer(), nullable=True, server_default="51820"),
30
+ sa.Column("wg_persistent_keepalive", sa.Integer(), nullable=True),
31
+ sa.Column("wg_mtu", sa.Integer(), nullable=True),
32
+ sa.Column("wg_dns", sa.Text(), nullable=True),
33
+ sa.Column("l2tp_gateway", sa.Text(), nullable=True),
34
+ sa.Column("l2tp_psk", sa.Text(), nullable=True),
35
+ sa.Column("l2tp_username", sa.Text(), nullable=True),
36
+ sa.Column("l2tp_password", sa.Text(), nullable=True),
37
+ sa.Column("oper_state", sa.String(16), nullable=False, server_default="unknown"),
38
+ sa.Column("iface", sa.String(32), nullable=True),
39
+ sa.Column("rx_bytes", sa.BigInteger(), nullable=True),
40
+ sa.Column("tx_bytes", sa.BigInteger(), nullable=True),
41
+ sa.Column("last_handshake_at", sa.TIMESTAMP(timezone=True), nullable=True),
42
+ sa.Column("last_error", sa.Text(), nullable=True),
43
+ sa.Column("last_status_at", sa.TIMESTAMP(timezone=True), nullable=True),
44
+ sa.Column(
45
+ "created_at",
46
+ sa.TIMESTAMP(timezone=True),
47
+ nullable=False,
48
+ server_default=sa.text("now()"),
49
+ ),
50
+ )
51
+
52
+
53
+ def downgrade() -> None:
54
+ op.drop_table("mr_vpn_tunnel")
@@ -0,0 +1,21 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ from alembic import op
5
+
6
+ revision = "b7d3f1e92a05"
7
+ down_revision = "a9f3e2b1c0d8"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ op.execute("ALTER TABLE radpostauth ADD COLUMN IF NOT EXISTS calledstationid text")
14
+ op.execute("ALTER TABLE radpostauth ADD COLUMN IF NOT EXISTS callingstationid text")
15
+ op.execute('ALTER TABLE radpostauth ADD COLUMN IF NOT EXISTS "class" text')
16
+
17
+
18
+ def downgrade() -> None:
19
+ op.execute('ALTER TABLE radpostauth DROP COLUMN IF EXISTS "class"')
20
+ op.execute("ALTER TABLE radpostauth DROP COLUMN IF EXISTS callingstationid")
21
+ op.execute("ALTER TABLE radpostauth DROP COLUMN IF EXISTS calledstationid")
@@ -0,0 +1,35 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ import sqlalchemy as sa
5
+
6
+ from alembic import op
7
+
8
+ revision = "b7f2c9a4e1d3"
9
+ down_revision = "e5c9a1f3d827"
10
+ branch_labels = None
11
+ depends_on = None
12
+
13
+
14
+ def upgrade() -> None:
15
+ op.create_table(
16
+ "mr_nas_reachability",
17
+ sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
18
+ sa.Column(
19
+ "nas_id",
20
+ sa.Integer(),
21
+ sa.ForeignKey("nas.id", ondelete="CASCADE"),
22
+ nullable=False,
23
+ ),
24
+ sa.Column("status", sa.String(16), nullable=False, server_default="unknown"),
25
+ sa.Column("method", sa.String(8), nullable=False, server_default="icmp"),
26
+ sa.Column("last_rtt_ms", sa.Float(), nullable=True),
27
+ sa.Column("last_seen_at", sa.TIMESTAMP(timezone=True), nullable=True),
28
+ sa.Column("last_probe_at", sa.TIMESTAMP(timezone=True), nullable=True),
29
+ sa.Column("detail", sa.Text(), nullable=True),
30
+ )
31
+ op.create_unique_constraint("uq_mr_nas_reachability_nas_id", "mr_nas_reachability", ["nas_id"])
32
+
33
+
34
+ def downgrade() -> None:
35
+ op.drop_table("mr_nas_reachability")
@@ -0,0 +1,22 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ import sqlalchemy as sa
5
+
6
+ from alembic import op
7
+
8
+ revision = "b8e3f1a2d9c4"
9
+ down_revision = "a4d1c7e6b520"
10
+ branch_labels = None
11
+ depends_on = None
12
+
13
+
14
+ def upgrade() -> None:
15
+ op.add_column(
16
+ "mr_firewall_set",
17
+ sa.Column("managed_source", sa.String(length=48), nullable=True),
18
+ )
19
+
20
+
21
+ def downgrade() -> None:
22
+ op.drop_column("mr_firewall_set", "managed_source")
@@ -0,0 +1,40 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ from alembic import op
5
+
6
+ revision = "c1f7b4a9d2e6"
7
+ down_revision = "b8e3f1a2d9c4"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ with op.get_context().autocommit_block():
14
+ op.create_index(
15
+ "ix_radacct_keyset",
16
+ "radacct",
17
+ ["acctstarttime", "radacctid"],
18
+ postgresql_concurrently=True,
19
+ if_not_exists=True,
20
+ )
21
+ op.create_index(
22
+ "ix_radpostauth_keyset",
23
+ "radpostauth",
24
+ ["authdate", "id"],
25
+ postgresql_concurrently=True,
26
+ if_not_exists=True,
27
+ )
28
+
29
+
30
+ def downgrade() -> None:
31
+ with op.get_context().autocommit_block():
32
+ op.drop_index(
33
+ "ix_radpostauth_keyset",
34
+ table_name="radpostauth",
35
+ postgresql_concurrently=True,
36
+ if_exists=True,
37
+ )
38
+ op.drop_index(
39
+ "ix_radacct_keyset", table_name="radacct", postgresql_concurrently=True, if_exists=True
40
+ )
@@ -0,0 +1,27 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ from alembic import op
5
+
6
+ revision = "c2a8f4e71b93"
7
+ down_revision = "b7d3f1e92a05"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ op.execute("""
14
+ CREATE UNIQUE INDEX IF NOT EXISTS radacct_acctuniqueid
15
+ ON radacct (acctuniqueid)
16
+ """)
17
+ op.execute("""
18
+ CREATE TABLE IF NOT EXISTS nasreload (
19
+ nasipaddress inet PRIMARY KEY,
20
+ reloadtime timestamp with time zone NOT NULL
21
+ )
22
+ """)
23
+
24
+
25
+ def downgrade() -> None:
26
+ op.execute("DROP TABLE IF EXISTS nasreload")
27
+ op.execute("DROP INDEX IF EXISTS radacct_acctuniqueid")