Skip to main content

일정 시기(시각) 이전의 리모트 계정 기록을 삭제하는 방법

상황

제가 운영하던 서버가 회복 불가능한 손상이 발생하여 전체 프로그램을 재설치하고 데이터베이스까지 전부 새로 구축했습니다. 
문제는 서버가 다시 작동하면서 시작이 되었는데, 이 사건이 발생하기 전까지는 대부분 특정 서버가 운영을 중단하기로 결정하며 사라지거나 아니면 아예 다른 도메인의 서버가 새로 생기는 것 뿐이었습니다. 

하지만 이번 이벤트의 특징은 "특정 서버가 사용하던 고유의 키 값은 모두 사라진 상황에서 동일한 도메인의 동일한 사용자 계정이 다수 생성되며 정상적으로 동작중이던 다른 서버들이 기존에 가지고 있던 키 값과 새로 만들어진 서버의 키 값이 달라 제대로 통신을 할 수 없는 상황"이었습니다. 

해결방법

이 해결방법은 특정 시기 이전에 기록되어있던 서버에서 삭제되고 재구축된 서버의 기존 사용자 정보를 전부 초기화시키는 것에 있습니다. 

아이디 검색후 해당정보 삭제

이 방법은 서버의 id 체계로 aid/aidx를 사용중이라고 가정합니다. aid는 미스키 서버의 기본구성을 크게 바꾸지 않으면 사용하게 되는 가장 기본적인 키 값입니다. 

우선 해당하는 시각에 대응되는 aidx를 https://misskey-hub.net/en/tools/aid-converter/ 에서 구합니다. 
여기서 구한 aidx를 바탕으로, 데이터베이스를 뜯어서 다음 쿼리를 실행합니다.

SELECT id FROM "user" 
where
  id < 'xxxxxxxxxx'
  and host = 'example.com';

id의 목록을 구했으면, 다음 아이 스크립트에서 accounts 항목에 삽입하여 서버에 계정 삭제 요청을 보냅니다. 

let accounts = [
    'xxx',
    'xxx'
]

each(let account, accounts) {
    Mk:api('admin/accounts/delete', {
        userId: account
    })
}

<: `{accounts.len}개의 계정 삭제 요청을 보냈습니다`

AiScript를 이용한 리모트 계정의 일괄처리 코드

관리자 계정으로 스크래치 패드를 열고, 다음의 코드를 붙여넣은 후 실행합니다. 여기서 example.com을 해당하는 도메인으로 변경합니다.
실행 이전에 관리자 계정의 API 제한을 해제해야 합니다. 잠시 제한을 열어두고 다음 스크립트를 실행합니다.

let host = 'example.com'
let timeBefore_timestamp = '2024-01-13T12:48:23.954Z'
// ----
var offset = 0
var accounts = []
let timeBefore = Date:parse(timeBefore_timestamp)

<: `{host} 의 계정을 탐색합니다...`
loop {
  let res = Mk:api('admin/show-users', {
    hostname: host,
    limit: 30,
    offset: offset,
    sort: '+createdAt',
    state: 'all'
  })
  
  if (res.len == 0) break
  
  if (accounts.len == 0) {
    // 해당 시점의 계정을 탐색
    each(let account, res) {
      if (Date:parse(account.createdAt) < timeBefore) {
        accounts.push(account)
      }
    }
  } else {
    // 응답의 모든 계정이 주어진 시점의 이전 계정이라고 간주
    accounts = accounts.concat(res)
    offset = offset + res.len
  }
  
  
  if ((offset % 100) == 0) {
    <: `{offset} 번째 계정을 탐색했습니다. 계속해서 탐색합니다... ({accounts.len})`
  }
}

<: `조건에 일치하는 계정이 {accounts.len}개 발견되었습니다`
each(let account, accounts) {
  <: `{account.id} : @{account.username}@{account.host}`
}

let warning = [
  `{accounts.len} 개의 계정을 찾았습니다.`,
  `마지막으로 생성된 계정은 @{accounts[0].username}@{accounts[0].host}이며,`
  `최초로 생성된 계정은 @{accounts[accounts.len-1].username}@{accounts[accounts.len-1].host} 입니다.`
  `해당 범위의 계정을 모두 삭제하시겠습니까?`
].join(Str:lf)
let warning_additional = [
  `**제어판과 비교하여 삭제되는 계정의 범위가 기대와 일치하는 지 반드시 비교하십시오.**`
  '확인을 누르는 즉시 해당 범위에 대한 삭제 명령이 발동되며, 이는 **취소할 수 없습니다.**'
  '---'
  ''
].join(Str:lf)

if (Mk:confirm('탐색 완료', warning) == true) {
  if (Mk:confirm('마지막 경고입니다', `{warning_additional}{warning}`, 'warning') == true) {
    <: '요청에 따라 삭제를 실행합니다. 취소할 수 없습니다.'
    each(let account, accounts) {
      Mk:api('admin/accounts/delete', {
          userId: account.id
      })
      <: `{account.id} : @{account.username}@{account.host}`
    }
  
    <: `{accounts.len}개의 계정 삭제 요청을 보냈습니다`
  }
}

이벤트의 기준이 되는 2023.12.02 버전의 경우에는 API를 통한 리모트 유저 삭제를 했을때, 동일한 username으로 연합이 이루어지지 않는다고 합니다. 동일한 username으로 연합을 허용하려면, 데이터베이스에 접근하여 해당 유저에 대한 기록을 말소해야 합니다. 

모든 계정이 삭제된 것을 확인한 뒤에 실행합니다. 

DELETE FROM "user"
WHERE
  host = 'iqhina.org'
  AND "isDeleted" IS TRUE;