서로 다른 Discourse 사이트 간 주제 동기화/크로스포스팅

현재 한 사이트에서 특정 주제를 운영하고 있으며, 이 주제를 다른 사이트에도 동일하게 사용하고 싶습니다. 하이퍼링크를 걸 수는 있지만, 실제로 원하시는 기능은 두 사이트 어디서든 주제를 편집할 수 있고, 그 변경 사항이 양쪽에 모두 반영되는 것입니다. 이렇게 하면 한쪽의 주제 버전이 크게 구식이 되는 것을 방지하고, 두 주제 모두 항상 최신 상태를 유지할 수 있습니다. 또한 이를 통해 제 사이트에서 정보를 분산시키는 방법도 제공하게 됩니다.

기능에 대한 몇 가지 생각

  • 시작점으로, 한 사이트는 주제의 호스트/소유자로 남아 있고, 다른 사이트(들)는 기본적으로 이를 미러링하는 역할을 수행합니다. 더 나아가, 원본 주제가 삭제될 경우 미러 주제가 해당 주제를 어떻게든 상속받을 수 있는지 궁금합니다.
  • 미러 사이트에서도 주제를 숨기거나, 닫기 등의 기능을 유지할 수 있어야 합니다.
  • 댓글은 동기화되지 않아야 합니다. 각 사이트마다 사용자 기반이 다르므로, 댓글 동기화가 어떻게 작동할 수 있을지 상상하기 어렵습니다.

이러한 기능을 구현하는 것이 결코 쉽지 않다는 점은 이해하지만, 이 기능이 이전에 검토된 적이 있는지, 그리고 이미 존재하는 결과물이나 실험이 있는지 궁금합니다.

2개의 좋아요

여러 사이트에 중복된 콘텐츠를 올리는 것은 SEO 측면에서 큰 금기 사항입니다. 따라서 이런 기능이 지원될 가능성은 낮습니다.

구체적인 사용 사례나 목적은 무엇인가요? 기본적으로 다른 사이트들을 주 사이트의 백업으로 사용하려는 건가요?

2개의 좋아요

이 부분에 대해 좀 더 자세한 정보를 주실 수 있을까요? 제 관점에서는 이것이 오히려 중복 콘텐츠를 줄이는 효과가 있을 것 같습니다.

백업이 목표는 아닙니다. 대신, 여러 사이트에서 의미가 있는 주제에 대해 단일 정보 출처(single point of truth)를 만드는 것이 목표입니다.

구체적인 예를 들어보겠습니다. 저는 비자를 변경하고 있습니다. 제 개인용 Discourse에는 기본적으로 수행해야 할 사항들의 체크리스트가 있는 주제가 있습니다. 그러나 제 친구들도 이 정보가 유용할 수 있을 것 같아, 우리가 공유하는 Discourse에도 동일한 주제를 만들었습니다. 문제는 두 주제의 정보를 각각 따로 동기화해야 한다는 점입니다. 하나의 주제만 업데이트하면 되는 것이 아니죠. 그 결과, 한쪽 주제에 중요한 정보가 누락되는 경우가 잦습니다.

아마 다른 사이트로의 API 키만 있으면 이것도 가능할 것 같습니다. 편집기에 버튼이나 섹션을 추가하여 대상 주제의 API 키와 URL 목록을 생성할 수 있도록 하는 방식이죠. 소스 주제에 변경 사항을 적용하면, "이 주제의 복제본으로 변경 사항 푸시"와 같은 버튼을 클릭할 수 있습니다. 이 기능은 다른 인스턴스의 주제들로 업데이트를 푸시하는 것만 수행하면 됩니다.

2개의 좋아요

모두가 볼 수 있는 정확히 한 곳에 정보를 두세요. 링크가 바로 그 방법입니다.

하지만 다른 곳에 공개하고 싶은 비밀 정보가 있는 거라면, 그건 또 다른 문제입니다. 플러그인을 사용하면 충분히 가능합니다. 이 문제는 인위적인 해결책이 실제 문제 해결에 필요한 작업량의 10배가 되는 유형의 문제입니다. (예를 들어, 단 한 번만 일어나는 작업을 자동화하는 데 몇 시간을 쓰는 경우가 많습니다.)

하지만 게시한 사용자만 접근할 수 있는 어딘가에 저장해야 합니다. 아니면 사이트 전체로 공개할까요?

다시 말하지만, 이는 사용자별로 구분되어야 하며 직렬화기(serializer)에서는 현재 사용자만 해당되도록 해야 합니다(아니면 사용자 프로필에 저장하는 것도 가능하겠죠?). 그리고 API 키를 다른 사이트들에 매핑하기 위한 어떤 데이터 구조도 필요합니다. 2~5시간 안에 해낼 수 있을 것 같은 작업처럼 보입니다.

즉, 이 토픽을 가져야 하는 다른 사이트들의 URL을 어딘가에 저장해야 합니다. 그 게시물을 만드는 방법도 복잡할 수 있습니다. 가장 쉬운 방법은 수동으로 생성한 후 소스 사이트에 해당 토픽의 URL을 포함하는 것입니다. 아마도 원본 게시물(raw post)에 어떤 종류의 BBcode 등으로 저장할 수 있을 겁니다. 그러면 각각에 대한 버튼과 링크를 생성하는 컴포넌트를 만들 수 있고, 다른 사이트로 게시물을 시도하는 작업을 큐에 추가하는 Rails 코드를 가질 수 있게 됩니다. 하지만 수신 사이트는 코드가 필요하지 않습니다–API를 사용하여 게시물에 편집을 푸시할 수 있으니까요.

5~10시간 안에 해낼 수 있을 것 같지만 실제로는 그 두 배가 걸릴 것 같은 유형의 작업입니다. 그게 당신에게 재미있다면, 멋진 프로젝트가 될 수 있겠네요.

4개의 좋아요

소스 사이트에서 편집이 발생할 때마다 웹훅을 수신하고, 이를 활용해 API를 통해 미러 사이트에 게시하는 외부 소형 앱을 통해 이 작업을 수행할 수도 있습니다.

4개의 좋아요

다시 한번 이 문제에 대해 생각해 보았습니다.

플러그인을 통해 원본 문서 소스의 URL을 위한 사용자 지정 토픽 필드를 추가할 수 있습니다. (메인 문서가 숨겨져야 한다고 가정할 때, 이는 아마도 사용자의 사용 사례일 것입니다. 이 경우 원격 사용자 이름과 API 키를 위한 필드도 필요할 것입니다. 하지만 이 부분은 나중에 처리해도 될 것 같습니다. 또는 사용자 지정 필드에 저장할 수도 있습니다. API 키에 읽기 전용 권한이 있는지 확인하는 것은 키를 생성한 사람의 몫이 될 것입니다.)

토픽을 생성할 때 remote: https://meta.discourse.org/t/synchronising-crossposting-topics-across-different-discourse-sites/263269와 같은 내용을 입력하면, 토픽이 생성될 때 Discourse가 원격 토픽의 원본 텍스트를 가져와 raw 필드에 편집으로 삽입하고, 원격 URL을 포함하는 topic_custom_field를 생성합니다. 또한 상단에 url에서 복사됨이라는 문구를 추가할 수도 있습니다.

이 시점에서는 원격 토픽을 로컬로 복사하여 그 기록을 갖추게 됩니다.

그 후, 원격 토픽을 가져와 원격 토픽의 updated_at을 저장하고, 필요에 따라 raw도 다른 사용자 지정 필드에 저장하는 “소스 확인” 버튼을 제공할 수 있습니다. (작업(Job)을 통해 주기적으로 이를 수행하여 UX를 조금 더 개선할 수도 있습니다.) 이후 업데이트 버튼을 통해 기존 raw를 원격 버전으로 편집하여 교체할 수 있습니다.

원본 사이트가 공개되어 있다면 이 부분은 정말 쉽습니다. 비공개 사이트에서 가져오기 위해 API 키를 추가하면 상황이 복잡해지고, 여러 사이트 간에 API 키 세트를 관리해야 하면 더 복잡해집니다. 원본 소스를 교체해야 하는 경우, remap rake 작업을 사용하거나 필요할 때 원격 URL이 포함된 사용자 지정 필드를 편집할 수 있는 기능을 추가하는 방식으로 처리할 수 있을 것입니다.

이 부분은 이 솔루션이 보조 사이트들이 메인 사이트에서 데이터를 가져오기 때문에 자연스럽게 제공됩니다.

맞습니다. 그리고 소스 사이트로 돌아가는 링크를 제공하여 사람들이 소스에서 해당 댓글을 볼 수 있게 하거나, https://meta.discourse.org/t/embed-comments-from-discourse-in-your-single-page-app/231798을 통해 이를 임베드할 수도 있습니다.

이 부분에 대해 예산이 조금이라도 있다면, 편하게 연락해 주세요.

1개의 좋아요

제이, 안녕하세요.

이 문제에 대해 꽤 오래 전부터 고민해 왔습니다. 다만 최근까지는 별다른 진전이 없었죠. 안타깝게도 Discourse에서 Discourse로의 동기화 기능은 제게 그 가치가 크게 떨어졌습니다(동기화 대상이 몇 개의 주제에 불과했기 때문이죠). 대신, 다른 플랫폼의 마크다운 파일을 일반적으로 동기화하고 싶은 욕구가 커졌습니다.

저희 회사에서 주된 사용 사례는 GitLab 프로젝트의 README와 위키를 Discourse에 가져와 피드백과 검색 용도로 활용하되, GitLab의 파일을 여전히 단일 진실 공급원(Single Source of Truth)으로 유지하는 것이었습니다. Ruby에 대한 지식이 부족했던 탓에 Python 스크립트를 작성하게 되었는데, 구현 방식은 확실히 과할 수 있지만 기능적으로는 만족스러운 수준입니다. 첫 번째 정식 버전은 제이님이 언급하신 내용 중 일부도 수행합니다. 주요 기능은 다음과 같습니다:

  • 원본 소스(GitLab 파일)에 대한 링크 포함
  • 특정 리비전(GitLab 커밋 번호)에 대한 링크 포함
  • 이미지 및 이미지 URL 처리
    • GitLab 저장소에서 이미지 다운로드
    • Discourse에 업로드
    • 업로드된 이미지의 Short URL로 원래 이미지 URL 대체
  • “synced_with_gitlab” 태그를 추가하여 관련 항목을 쉽게 찾을 수 있도록 함

GitHub에서도 거의 동일한 방식으로 작동했습니다. 두 플랫폼 모두 마크다운 문법이 거의 비슷해서 꽤 매끄럽게 동작했습니다.

이 프로젝트를 오픈소스로 공개하고 싶지만, 법무팀의 의견을 들어봐야 할 것 같습니다. 또한 아직은 다소 엉성한 Python 코드 덩어리 상태입니다. 언젠가 Ruby 플러그인으로 전환할 계획이지만, 필요한 시간을 확보할 수 있을지는 더 지켜봐야 할 것 같습니다.

3개의 좋아요

맞습니다. 저는 이 프로젝트를 다시 시도하고 있으며, 현재 다음과 같은 DB를 만드는 것을 검토 중입니다:

  1. 가능한 뉘앙스를 고려하기 위해 플랫폼별 테이블(Discourse 테이블, Gitlab 테이블 등)
  2. 각 플랫폼 테이블이 웹훅 및 API 키를 통한 폴링을 지원
  3. DB 또는 API 키 암호화 – 현재는 DB 전체를 암호화하고 스크립트와 패스프레이스를 통해 인터페이스하는 것이 최선이라고 생각하고 있습니다.

Discourse 테이블은 다음과 같이 보일 것입니다:

Type Interval(mins) Last run(mins) src_domain src_post_ID src_usr src_key tgt_domain tgt_post_id tgt_usr tgt_key
Webhook - 120 meta.discourse.com 1280952 - - discourse.mysite.com 120 Tris xyz12345
Polling 60 40 meta.discourse.com 1280953 Tris20 12345xyz discourse.mysite.com 121 Tris xyz12345
Polling 60 35 meta.discourse.com 1750968 Tris20 12345xyz discourse.mysite.com 221 Tris xyz12345
Polling 60 40 meta.discourse.com 1123292 Tris20 12345xyz discourse.mysite.com 131 Tris xyz12345
Webhook - 4800 meta.discourse.com 1283678 - - discourse.mysite.com 129 Tris xyz12345

이것이 미친 짓이거나 과잉 엔지니어링으로 보입니까? 저에게는 웹훅과 폴링이라는 두 가지 가능성을 모두 고려하여 이에 대한 적절한 솔루션을 구축하고 싶은 마음이 있습니다.

또한, DB 콘텐츠 보안을 유지하는 방법에 대한 조언을 매우 감사히 여깁니다. 현재 생각은 스크립트를 시작할 때 인수로 제공해야 하는 패스프레이스로 DB를 암호화하는 것입니다. 예:

discourse-sync run password123

영감을 드리기 위해, 웹훅은 매우 빠를 수 있습니다:


이것은 두 개의 다른 인스턴스에 있는 두 개의 주제였습니다. 왼쪽의 주제는 소스 주제이고, 오른쪽의 주제는 대상입니다

Rails에는 단일 필드를 암호화하는 방법이 있습니다. 저는 대시보드에서 이 방법을 사용했습니다.

폴링과 웹훅을 모두 사용하는 것은 중복된 것으로 보입니다. 저는 하나의 방식을 선택할 것입니다.

1개의 좋아요

ActivityPub 플러그인이 가까운 미래에 Discourse에서 Discourse로의 연동(federation) 기능도 추가될 가능성이 있는지 궁금합니다.

3개의 좋아요

참고로, 이 아이디어는 저에게도 여러 번 떠올랐습니다. 아직 여기에서 이 주제에 대한 전용 토론이 없는 것 같은데, 없다면 만들어야 할 것 같습니다!

2개의 좋아요

이론적으로는 동의합니다. 하지만 불행히도 기업 환경은 상황을 복잡하게 만듭니다:

  1. 회사의 IT 정책상 웹훅 사용이 불가능합니다(회사가 아닌 외부 CDCK에 호스팅되어 있으며, 외부로 포트 포워딩을 허용하려면 까다로운 프로세스가 필요합니다) - 따라서 API 버전은 필수입니다.
  2. 웹훅은 빠르고 훌륭하며, 다른 모든 사용자에게도 완벽하게 합리적입니다. 따라서 이들을 수용하는 것도 의미가 있습니다. :slight_smile:

얼마 전 이 기능의 초안을 구현해 보았습니다. 잘 작동하지만 확장성은 없습니다. 제 설계가 잘못되었습니다. 마크다운 테이블을 입력으로 사용하는 아이디어를 탐구 중이었거든요. 항목이 30개 미만일 때는 좋지만, 그 이상이면 엉망이 됩니다.

제가 보는 Discourse to Discourse 사용 사례의 일부는 다음과 같습니다: 문서(Meta)의 단일 진실 공급원(single point of truth)이 다른 인스턴스의 각 게시글과 동기화됩니다. 즉, 팀이 코어(Core)를 변경하고 사용자 문서 Meta를 업데이트하면, 내 인스턴스에는 모든 사용자가 쉽게 찾을 수 있는 해당 문서의 최신 버전이 네이티브로 존재하게 됩니다.

V2에서는 위와 같이 SQLlite 데이터베이스를 입력으로 사용할 계획이며, 이번에는 python 대신 Rust로 작성할 예정입니다.

아래는 대략적인 스케치입니다.

graph TB
    A[terminal] -- ~>discourse-sync run $PASSWORD --> B[Rust Script]
    B -- SQLCipher:Decrypt DB using Password --> C[( sqlite: sources-and-targets')]
    C -- 'Discourse' Table Data --> B
    B -.-> D{Decision on Type}
    D -- Webhook --> E[Listen for Webhook Info]
    D -- Polling --> F[Polling API]
    E --> G[Receive New Information]
    F --> G
    G --> H[Parse and Process Data]
    H --> I[POST\n tgt_domain, tgt_usr, tgt_key, post_id]
    I --'raw' and images--> J[ Target Post ]

    subgraph Rust Script Operations
    B
    D
E
F
G
H
I
    end

이에 대한 피드백과 제안을 기꺼이 듣고 싶습니다 :slightly_smiling_face:

2개의 좋아요

@angus 당신에게도 관심 있을 것 같아서 공유합니다. Activity Pub 플러그인으로 이 문제를 이미 많이 해결하고 있다고 생각합니다.

7개의 좋아요

네, 이제 ActivityPub 플러그인에서 이를 실제로 지원합니다. 메타와 내부 인스턴스 간에 문서를 동기화하기 위해 내부적으로 사용하는 단계까지 거의 왔습니다. 사실 다음 주 할 일 목록에 올려놓은 상태입니다.

8개의 좋아요

이것이 두 인스턴스 모두에 적용되나요? 예를 들어 소스(Source)는 공개이고 대상(Target)은 비공개인 경우에도 여전히 작동하나요?

ActivityPub 플러그인은 훌륭해 보이지만, 비공개 인스턴스를 지원하지 않을 수 있다는 점이 우려됩니다.

1개의 좋아요

사실 현재 버전의 AP 플러그인에서는 사설 인스턴스에 적용됩니다. 사설 인스턴스는 공개된 Discourse 인스턴스의 카테고리를 팔로우할 수 있으며, 따라서 해당 인스턴스에서 게시된 활동을 받을 수 있습니다. (다만, 사설 인스턴스의 콘텐츠는 게시되지 않으므로, 공개에서 사설로의 일방향 동기화입니다.)

4개의 좋아요

그러면 ActivityPub는 다음과 같이 작동하는 것 같습니다:

공개 → 공개 :white_check_mark:
공개 → 개인 :white_check_mark:

개인 → 공개 :x:
개인 → 개인 :x:

이것은 메타에서 문서를 방송하는 것과 같은 많은 경우에 유용합니다. 불행히도 이것은 개인 인스턴스에서 다른 개인 인스턴스로 게시하려는 제 사용 사례 중 하나를 아직 충족하지 못합니다. 여기저기 살펴본 결과, ActivityPub 플러그인이 향후 이러한 사용 사례를 지원하지 않을 가능성이 높다는 제 생각이 맞습니까? ActivityPub는 공개 대 공개를 염두에 두고 설계된 것처럼 보입니다.

3개의 좋아요

@Tris20 흥미롭네요! 이 주제에 대해 의견과 세부 사항을 공유해 주셔서 감사합니다.

설명해 주신 내용은 ActivityPub가 해결하기 위해 설계된 문제(들) 중 하나입니다. 기분을 너무 상하게 하고 싶지는 않지만, 솔직히 말씀드리면 설명하신 방식대로 이를 달성하려면 수많은 도전에 직면하게 될 것입니다. 극복해야 할 모든 도전을 빠짐없이 나열하지는 않겠지만, 감을 잡을 수 있도록 말씀드리자면 ActivityPub 플러그인에는 이미 거의 700개의 rspec 테스트가 있으며, 1년간의 개발 끝에 겨우 최근에서야 주제 간 전체 동기화를 지원하기 시작했습니다.

ActivityPub를 통해 비공개에서 공개(Public) 및 비공개에서 비공개(Private) 게시를 지원하는 데에는 제가 볼 때 본질적인 제한은 없습니다. 문제는 비공개 인스턴스를 다룰 때 접근 권한과 보안 관련 우려 사항이 충족되는지 보장하는 것입니다.

제안을 하나 드리자면, ActivityPub 플러그인(그리고 ActivityPub 표준)이 이 부분에 대해 이미 수행한 작업을 기반으로 어떻게 구축할 수 있을지 생각해 보시는 것은 어떨까요? 실제로 현재 Discourse에서 Discourse로 콘텐츠를 동기화하는 문제에 대한 솔루션이 존재하며 작동하고 있습니다. 아직 사용자의 사용 사례를 완전히 커버하지는 않지만, 요구 사항을 충족하기 위해 해결해야 할 대부분의 문제를 해결해 줍니다.

플러그인에서 비공개에서 비공개 동기화가 어떻게 작동할 수 있는지, 즉 접근 권한과 보안 문제가 어떻게 다뤄질 수 있을지 생각해 보시는 것은 어떨까요? 그런 다음 저와 함께 해당 기능을 추가하는 PR을 함께 작업해 볼 수도 있습니다. 플러그인(또는 ActivityPub 표준)의 맥락에서 원하는 것을 달성하는 것이 실제로 불가능하다고 느끼는 지점에 도달할 수도 있지만, 그 지점에 도달하기 위해 수행하게 될 작업은 독립적인 솔루션을 위해 수행해야 할 작업과 사실상 동일할 것이므로 헛된 노력이 되지는 않을 것입니다.

ActivityPub 분야에는 매우 똑똑한 분들이 많습니다. 이러한 유형의 문제(즉, 비공개 게시)가 이전에 깊이 고려되었을 것이라고 놀라지 않을 것입니다. 이에 대한 선행 연구(prior art)를 찾을 수 있는 곳 중 하나는 주요 ActivityPub 커뮤니티 포럼인 SocialHub입니다(물론 Discourse 기반이며, 현재 Discourse ActivityPub 플러그인을 사용하고 있습니다 :slight_smile:)

8개의 좋아요