.gitea/workflows/wipe-mnesia-ift.yml View File
| 1 | name: Wipe Mnesia IFT
|
| 2 |
|
| 3 | # Manual only: full Mnesia wipe on IFT (NOT prod).
|
| 4 | on:
|
| 7 | concurrency:
|
| 8 | group: wipe-mnesia-ift
|
| 9 | cancel-in-progress: false
|
| 11 | jobs:
|
| 12 | wipe-mnesia-ift:
|
| 13 | runs-on: ubuntu-latest
|
| 16 | - uses: actions/checkout@v4
|
| 17 | - name: Full Mnesia wipe IFT
|
| 18 | env:
|
| 21 | SSH_KEY: ${{ secrets.IFT_SSH_PRIVATE_KEY }}
|
| 22 | run: bash scripts/ci-full-mnesia-wipe.sh ift |
.gitea/workflows/wipe-mnesia-stage.yml View File
| 1 | name: Wipe Mnesia stage
|
| 2 |
|
| 3 | # Manual only: full Mnesia wipe on stage (NOT prod).
|
| 4 | on:
|
| 7 | concurrency:
|
| 8 | group: wipe-mnesia-stage
|
| 9 | cancel-in-progress: false
|
| 11 | jobs:
|
| 12 | wipe-mnesia-stage:
|
| 13 | runs-on: ubuntu-latest
|
| 16 | - uses: actions/checkout@v4
|
| 17 | - name: Full Mnesia wipe stage
|
| 18 | env:
|
| 21 | SSH_KEY: ${{ secrets.STAGE_SSH_PRIVATE_KEY }}
|
| 22 | run: bash scripts/ci-full-mnesia-wipe.sh stage |
Makefile View File
| 62 | @echo "Cleaning old data..."
|
| 63 | @rm -rf Mnesia.*
|
| 64 | @echo "Starting server..."
|
| 103 | @rm -rf logs/test/ct/ct_run.*
|
| 104 | @rm -rf data/Mnesia.ct data/Mnesia.ct.* data/mnesia_ct Mnesia.eventhub_api_test@* 2>/dev/null || true
|
| 105 | @bash scripts/fix-rebar-unit-symlink.sh
|
| 323 | @docker exec $$(docker ps -qf "name=eventhub_eventhub" | head -n 1) /app/bin/eventhub eval 'nodes().'
|
| 324 | @echo "Список узлов Mnesia:"
|
| 325 | @docker exec $$(docker ps -qf "name=eventhub_eventhub" | head -n 1) /app/bin/eventhub eval 'mnesia:table_info(user, disc_copies).'
|
docker/ObserverWeb.Dockerfile View File
docker/docker-compose.swarm.yml View File
docker/grafana/dashboards/eventhub-erlang.json View File
| 604 | "panels": [],
|
| 605 | "title": "Mnesia",
|
| 606 | "type": "row"
|
| 708 | {
|
| 709 | "expr": "rate(erlang_mnesia_committed_transactions{instance=~\"$instance\"}[1m])",
|
| 710 | "legendFormat": "Committed Tx Rate - {{instance}}",
|
| 713 | {
|
| 714 | "expr": "rate(erlang_mnesia_failed_transactions{instance=~\"$instance\"}[1m])",
|
| 715 | "legendFormat": "Failed Tx Rate - {{instance}}",
|
| 791 | {
|
| 792 | "expr": "erlang_mnesia_held_locks{instance=~\"$instance\"}",
|
| 793 | "legendFormat": "Held Locks - {{instance}}",
|
| 796 | {
|
| 797 | "expr": "erlang_mnesia_lock_queue{instance=~\"$instance\"}",
|
| 798 | "legendFormat": "Lock Queue - {{instance}}",
|
| 801 | {
|
| 802 | "expr": "erlang_mnesia_transaction_participants{instance=~\"$instance\"}",
|
| 803 | "legendFormat": "Participants - {{instance}}",
|
| 806 | {
|
| 807 | "expr": "erlang_mnesia_transaction_coordinators{instance=~\"$instance\"}",
|
| 808 | "legendFormat": "Coordinators - {{instance}}", |
include/records.hrl View File
scripts/ci-full-mnesia-wipe.sh View File
| 1 | #!/usr/bin/env bash
|
| 2 | # Full Mnesia wipe for IFT/stage from CI runner (NOT prod).
|
| 3 | # Order: scale 0 -> wipe eventhub-data-1..N -> scale 1 -> mnesia-safe-redeploy up -> ensure-smoke-user
|
| 4 | # Env: SSH_HOST SSH_USER SSH_KEY; arg: ift|stage
|
| 6 |
|
| 7 | STAND="${1:?usage: ci-full-mnesia-wipe.sh ift|stage}"
|
| 8 | case "${STAND}" in
|
| 39 |
|
| 40 | echo "### FULL MNESIA WIPE ${STAND} slots=1..${MAX_REPLICAS}"
|
| 41 | ssh_cmd "docker volume ls --format '{{.Name}}' | grep eventhub-data || true"
|
| 121 |
|
| 122 | ssh_cmd "bash ${SCRIPTS}/mnesia-safe-redeploy.sh up ${STAND} ${MAX_REPLICAS}"
|
| 123 | ssh_cmd "bash ${SCRIPTS}/ensure-smoke-user.sh ${STAND}"
|
| 128 | ssh_cmd "docker volume ls --format '{{.Name}}' | grep eventhub-data || true"
|
| 129 | echo "OK full mnesia wipe ${STAND}" |
scripts/run-stand-api-tests.sh View File
src/config/sys.config View File
src/core/core_admin.erl View File
| 49 | },
|
| 50 | mnesia:dirty_write(Admin),
|
| 51 | {ok, Admin}
|
| 63 | F = fun() ->
|
| 64 | case mnesia:read(admin, AdminId) of
|
| 65 | [] ->
|
| 68 | UpdatedAdmin = apply_updates(Admin, Updates),
|
| 69 | mnesia:write(UpdatedAdmin),
|
| 70 | {ok, UpdatedAdmin}
|
| 72 | end,
|
| 73 | case mnesia:transaction(F) of
|
| 74 | {atomic, Result} -> Result;
|
| 84 | Match = #admin{email = Email, _ = '_'},
|
| 85 | case mnesia:dirty_match_object(Match) of
|
| 86 | [Admin] -> {ok, Admin};
|
| 95 | get_by_id(Id) ->
|
| 96 | case mnesia:dirty_read({admin, Id}) of
|
| 97 | [Admin] -> {ok, Admin};
|
| 106 | list_all() ->
|
| 107 | mnesia:dirty_match_object(#admin{_ = '_'}).
|
| 108 |
|
| 118 | Updated = Admin#admin{role = NewRole, updated_at = calendar:universal_time()},
|
| 119 | mnesia:dirty_write(Updated),
|
| 120 | {ok, Updated};
|
| 133 | Updated = Admin#admin{last_login = calendar:universal_time()},
|
| 134 | mnesia:dirty_write(Updated),
|
| 135 | {ok, Updated};
|
| 164 | Updated = Admin#admin{status = Status, updated_at = calendar:universal_time()},
|
| 165 | mnesia:dirty_write(Updated),
|
| 166 | {ok, Updated};
|
| 177 | {ok, _Admin} ->
|
| 178 | mnesia:dirty_delete(admin, AdminId),
|
| 179 | {ok, deleted}; |
src/core/core_admin_audit.erl View File
src/core/core_admin_session.erl View File
| 27 | },
|
| 28 | mnesia:dirty_write(Session),
|
| 29 | core_counters:inc(admin_active_sessions),
|
| 41 | validate(Token) ->
|
| 42 | case mnesia:dirty_read({admin_session, Token}) of
|
| 43 | [Session] ->
|
| 46 | false ->
|
| 47 | mnesia:dirty_delete({admin_session, Token}),
|
| 48 | core_counters:dec(admin_active_sessions),
|
| 59 | delete(Token) ->
|
| 60 | mnesia:dirty_delete({admin_session, Token}),
|
| 61 | core_counters:dec(admin_active_sessions), |
src/core/core_auth_session.erl View File
| 1 | %%%-------------------------------------------------------------------
|
| 2 | %%% @doc Единая модель сессий (refresh JWT) в Mnesia.
|
| 3 | %%% Таблица `auth_session` реплицируется через disc_copies — безопасна в кластере.
|
| 51 | %% Transaction ensures atomic write of the new session record.
|
| 52 | case mnesia:transaction(fun() -> mnesia:write(Session) end) of
|
| 53 | {atomic, ok} ->
|
| 65 | get(SessionId) ->
|
| 66 | case mnesia:dirty_read({auth_session, SessionId}) of
|
| 67 | [Session] -> {ok, Session};
|
| 82 | F = fun() ->
|
| 83 | case mnesia:read(auth_session, SessionId, write) of
|
| 84 | [Session] ->
|
| 89 | end,
|
| 90 | case mnesia:transaction(F) of
|
| 91 | {atomic, Result} -> Result;
|
| 102 | F = fun() ->
|
| 103 | case mnesia:read(auth_session, SessionId, write) of
|
| 104 | [Session] when Session#auth_session.revoked =:= true ->
|
| 107 | Now = calendar:universal_time(),
|
| 108 | mnesia:write(Session#auth_session{revoked = true, updated_at = Now}),
|
| 109 | {revoked, Session#auth_session.subject_type};
|
| 113 | end,
|
| 114 | case mnesia:transaction(F) of
|
| 115 | {atomic, ok} -> ok;
|
| 131 | F = fun() ->
|
| 132 | Sessions = mnesia:index_read(auth_session, FamilyId, #auth_session.family_id),
|
| 133 | Now = calendar:universal_time(),
|
| 137 | false ->
|
| 138 | mnesia:write(Session#auth_session{revoked = true, updated_at = Now}),
|
| 139 | {true, Session#auth_session.subject_type}
|
| 143 | end,
|
| 144 | case mnesia:transaction(F) of
|
| 145 | {atomic, Types} ->
|
| 163 | when Sid =:= SubjectId -> S end),
|
| 164 | Sessions = mnesia:select(auth_session, MS, write),
|
| 165 | Now = calendar:universal_time(),
|
| 169 | true ->
|
| 170 | mnesia:write(Session#auth_session{revoked = true, updated_at = Now}),
|
| 171 | Acc + 1;
|
| 177 | end,
|
| 178 | case mnesia:transaction(F) of
|
| 179 | {atomic, Count} ->
|
| 196 | when Sid =:= SubjectId -> S end),
|
| 197 | Sessions = mnesia:dirty_select(auth_session, MS),
|
| 198 | lists:filter(fun(S) ->
|
| 214 | when Sid =:= SubjectId -> S end),
|
| 215 | Sessions = mnesia:select(auth_session, MS, write),
|
| 216 | Now = calendar:universal_time(),
|
| 221 | true ->
|
| 222 | mnesia:write(Session#auth_session{revoked = true, updated_at = Now}),
|
| 223 | Acc + 1;
|
| 228 | end,
|
| 229 | case mnesia:transaction(F) of
|
| 230 | {atomic, Count} ->
|
| 240 |
|
| 241 | %% NOTE: Called inside a mnesia:transaction/1 from rotate/2.
|
| 242 | %% Uses mnesia:read/3 and mnesia:write/1 (not dirty_ variants).
|
| 243 | rotate_session(Session, PresentedJti) ->
|
| 259 | },
|
| 260 | mnesia:write(Updated),
|
| 261 | {ok, NewJti, Updated}; |
src/core/core_automod_hit.erl View File
| 11 | create(EntityType, EntityId, Trigger, MatchedWords, ActionTaken) ->
|
| 12 | case lists:member(automod_hit, mnesia:system_info(tables)) of
|
| 13 | false ->
|
| 39 | },
|
| 40 | mnesia:dirty_write(Hit),
|
| 41 | {ok, Hit}
|
| 45 | get_by_id(Id) ->
|
| 46 | case mnesia:dirty_read(automod_hit, Id) of
|
| 47 | [Hit] -> {ok, Hit};
|
| 52 | list() ->
|
| 53 | mnesia:dirty_match_object(#automod_hit{_ = '_'}).
|
| 54 |
|
| 68 | },
|
| 69 | mnesia:dirty_write(Updated),
|
| 70 | {ok, Updated}; |
src/core/core_automod_settings.erl View File
| 12 | get() ->
|
| 13 | case catch mnesia:dirty_read(automod_settings, ?ID) of
|
| 14 | [S] -> S;
|
| 17 | ok ->
|
| 18 | case catch mnesia:dirty_read(automod_settings, ?ID) of
|
| 19 | [S2] -> S2;
|
| 32 | ensure_defaults() ->
|
| 33 | case lists:member(automod_settings, mnesia:system_info(tables)) of
|
| 34 | false ->
|
| 36 | true ->
|
| 37 | case mnesia:dirty_read(automod_settings, ?ID) of
|
| 38 | [_] -> ok;
|
| 39 | [] ->
|
| 40 | mnesia:dirty_write(default_record(<<>>)),
|
| 41 | ok
|
| 57 | },
|
| 58 | mnesia:dirty_write(Next),
|
| 59 | {ok, Next} |
src/core/core_banned_words.erl View File
| 11 | list_banned_words() ->
|
| 12 | mnesia:dirty_match_object(#banned_word{_ = '_'}).
|
| 13 |
|
| 34 | },
|
| 35 | mnesia:dirty_write(BannedWord),
|
| 36 | {ok, BannedWord}
|
| 47 | Match = #banned_word{word = Word, _ = '_'},
|
| 48 | [Record] = mnesia:dirty_match_object(Match),
|
| 49 | mnesia:dirty_delete({banned_word, Record#banned_word.id}),
|
| 50 | {ok, deleted};
|
| 61 | Match = #banned_word{word = Word, _ = '_'},
|
| 62 | case mnesia:dirty_match_object(Match) of
|
| 63 | [] -> false;
|
| 81 | Match = #banned_word{word = OldWord, _ = '_'},
|
| 82 | [Record] = mnesia:dirty_match_object(Match),
|
| 83 | Updated = Record#banned_word{word = NewWord},
|
| 84 | mnesia:dirty_write(Updated),
|
| 85 | {ok, Updated} |