Compare commits
5 Commits
v0.2.1-old
...
directory-
| Author | SHA1 | Date | |
|---|---|---|---|
|
d8344da524
|
|||
|
a9d1217db7
|
|||
|
6558c0eab6
|
|||
|
e1d09b7572
|
|||
|
d206429bab
|
@@ -1 +0,0 @@
|
|||||||
test/automated/api/node_modules
|
|
||||||
@@ -1,17 +1,12 @@
|
|||||||
# Read first
|
|
||||||
|
|
||||||
If this is an unsolicited change, or there is no existing issue filed for it, please open a GitHub issue before creating a pull request. This will allow us to discuss the motivations and the big picture behind the change first. It's possible there may be other solutions that should be discussed for what you think should be built. It is possible your change will be rejected unless some discussion around your proposal happens first. While creating this PR means you probably already did the work, it still makes sense to file an issue now, and into the future when you have proposed changes.
|
Please include a summary of the change and which issue number is fixed, including relevant motivation and context. Feel free to mark this as a Draft or WIP and write up some details later.
|
||||||
|
|
||||||
## Description
|
If there is no issue filed for this particular change it's highly recommended you file one. While creating this PR means you probably already did the work, in the future make sure an issue is filed beforehand so changes, fixes and features can be discussed ahead of time.
|
||||||
|
|
||||||
Please include a summary of the change and which issue number is fixed, including relevant motivation and context. Feel free to mark this as a Draft or WIP and write up some details later and start a conversation, even if your PR is not ready for review.
|
# Description
|
||||||
|
|
||||||
Fixes # (issue)
|
Fixes # (issue)
|
||||||
|
|
||||||
## Screenshot Examples or Logs
|
|
||||||
|
|
||||||
If this is a frontend change, please include a screenshot of the change. If this is a backend change, please include relevant logs or examples of the change in action if applicable.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Some things you might want to mention:
|
Some things you might want to mention:
|
||||||
@@ -21,4 +16,4 @@ Some things you might want to mention:
|
|||||||
3. If you're fixing something, what was wrong? How should we stop from having this issue happen again?
|
3. If you're fixing something, what was wrong? How should we stop from having this issue happen again?
|
||||||
4. If this is a new feature or addition to functionality, why should it be added? What are the use cases? Who was asking for this functionality?
|
4. If this is a new feature or addition to functionality, why should it be added? What are the use cases? Who was asking for this functionality?
|
||||||
|
|
||||||
Thank you so much for contributing to Owncast! 🎉
|
If this is an unsolicited change or have no issue associated please do your best to detail the motivations behind this PR, and think about filing an issue to discuss changes ahead of time in the future.
|
||||||
|
|||||||
31
.github/stale.yml
vendored
Normal file
31
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 60
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 7
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- backlog
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: stale
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. If this
|
||||||
|
was a feature request that others have shown no interest in then it's
|
||||||
|
likely to not get implemented due to lack of interest. If others also
|
||||||
|
want to see this feature then now is the time to say something!
|
||||||
|
Thank you for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
||||||
|
exemptMilestones: true
|
||||||
|
|
||||||
|
# Since old PRs are less useful than old issues ping them sooner.
|
||||||
|
pulls:
|
||||||
|
daysUntilStale: 30
|
||||||
|
markComment: >
|
||||||
|
This pull request has not had any activity in 30 days. Since things move fast it's best
|
||||||
|
to get PRs merged in. If this PR addresses a previously filed issue that needs to be
|
||||||
|
resolved please work to get it merged in, or allow somebody else to work on a fix.
|
||||||
|
This PR will be closed if no further activity occurs. Thank you for your contributions!
|
||||||
|
exemptLabels:
|
||||||
|
- bot
|
||||||
10
.github/workflows/actions-lint.yml
vendored
10
.github/workflows/actions-lint.yml
vendored
@@ -13,15 +13,7 @@ jobs:
|
|||||||
name: GitHub actions
|
name: GitHub actions
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out pull request code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- uses: docker://rhysd/actionlint:latest
|
- uses: docker://rhysd/actionlint:latest
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/auto-comment-on-label.yaml
vendored
2
.github/workflows/auto-comment-on-label.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- name: Add comment
|
- name: Add comment
|
||||||
uses: peter-evans/create-or-update-comment@fdb73c443d3a4f66832374f01fb9a713fad84937
|
uses: peter-evans/create-or-update-comment@71ac479718f8aed504782bc920d802da994c05fa
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
body: |
|
body: |
|
||||||
|
|||||||
24
.github/workflows/automated-end-to-end-api.yaml
vendored
24
.github/workflows/automated-end-to-end-api.yaml
vendored
@@ -18,43 +18,23 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
concurrent_skipping: 'same_content_newer'
|
concurrent_skipping: 'same_content_newer'
|
||||||
|
|
||||||
- name: Check out pull request code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- name: Get changed files
|
|
||||||
id: changed-files-yaml
|
|
||||||
uses: tj-actions/changed-files@v45
|
|
||||||
with:
|
|
||||||
files_yaml: |
|
|
||||||
src:
|
|
||||||
- '**/*.{go,mod,sum}'
|
|
||||||
|
|
||||||
- uses: earthly/actions-setup@v1
|
- uses: earthly/actions-setup@v1
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
with:
|
with:
|
||||||
version: 'latest' # or pin to an specific version, e.g. "v0.6.10"
|
version: 'latest' # or pin to an specific version, e.g. "v0.6.10"
|
||||||
|
|
||||||
- name: Earthly version
|
- name: Earthly version
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
run: earthly --version
|
run: earthly --version
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
id: qemu
|
id: qemu
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
image: tonistiigi/binfmt:latest
|
image: tonistiigi/binfmt:latest
|
||||||
platforms: all
|
platforms: all
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run API tests
|
- name: Run API tests
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
uses: nick-fields/retry@v3
|
uses: nick-fields/retry@v3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
|
|||||||
11
.github/workflows/browser-testing.yml
vendored
11
.github/workflows/browser-testing.yml
vendored
@@ -19,19 +19,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
concurrent_skipping: 'same_content_newer'
|
concurrent_skipping: 'same_content_newer'
|
||||||
|
|
||||||
- name: Check out pull request code
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '22.9.0'
|
node-version: latest
|
||||||
|
|
||||||
- name: Cache node modules
|
- name: Cache node modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|||||||
9
.github/workflows/build-storybook.yml
vendored
9
.github/workflows/build-storybook.yml
vendored
@@ -11,15 +11,8 @@ jobs:
|
|||||||
if: github.repository == 'owncast/owncast'
|
if: github.repository == 'owncast/owncast'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out pull request code
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- name: Cache node modules
|
- name: Cache node modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|||||||
30
.github/workflows/chromatic.yml
vendored
30
.github/workflows/chromatic.yml
vendored
@@ -27,37 +27,23 @@ jobs:
|
|||||||
uses: fkirc/skip-duplicate-actions@v5
|
uses: fkirc/skip-duplicate-actions@v5
|
||||||
with:
|
with:
|
||||||
concurrent_skipping: 'same_content_newer'
|
concurrent_skipping: 'same_content_newer'
|
||||||
|
- name: Check out code
|
||||||
- name: Check out pull request code
|
if: ${{ github.actor != 'renovate[bot]' && github.actor != 'renovate' }}
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target'
|
|
||||||
with:
|
with:
|
||||||
|
# Make sure the actual branch is checked out when running on pull requests
|
||||||
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
|
fetch-depth: 0
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- name: Get changed files
|
|
||||||
id: changed-files-yaml
|
|
||||||
uses: tj-actions/changed-files@v45
|
|
||||||
with:
|
|
||||||
path: 'web'
|
|
||||||
files_ignore: |
|
|
||||||
static/**
|
|
||||||
web/next.config.js
|
|
||||||
files_yaml: |
|
|
||||||
src:
|
|
||||||
- '**/*.{js,ts,tsx,jsx,md}'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: ${{ github.actor != 'renovate[bot]' && github.actor != 'renovate' && steps.changed-files-yaml.outputs.src_any_changed == 'true'}}
|
if: ${{ github.actor != 'renovate[bot]' && github.actor != 'renovate' }}
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
- name: Publish to Chromatic
|
- name: Publish to Chromatic
|
||||||
if: ${{ github.actor != 'renovate[bot]' && github.actor != 'renovate' && steps.changed-files-yaml.outputs.src_any_changed == 'true' }}
|
if: ${{ github.actor != 'renovate[bot]' && github.actor != 'renovate' }}
|
||||||
uses: chromaui/action@v11
|
|
||||||
|
|
||||||
|
uses: chromaui/action@v11
|
||||||
# Chromatic GitHub Action options
|
# Chromatic GitHub Action options
|
||||||
with:
|
with:
|
||||||
workingDir: web
|
workingDir: web
|
||||||
|
|||||||
9
.github/workflows/codeql-analysis.yml
vendored
9
.github/workflows/codeql-analysis.yml
vendored
@@ -36,15 +36,8 @@ jobs:
|
|||||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out pull request code
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
|
|||||||
10
.github/workflows/container-lint.yml
vendored
10
.github/workflows/container-lint.yml
vendored
@@ -19,15 +19,7 @@ jobs:
|
|||||||
container:
|
container:
|
||||||
image: aquasec/trivy
|
image: aquasec/trivy
|
||||||
steps:
|
steps:
|
||||||
- name: Check out pull request code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- name: Check critical issues
|
- name: Check critical issues
|
||||||
run: trivy config --exit-code 1 --severity "HIGH,CRITICAL" ./Dockerfile
|
run: trivy config --exit-code 1 --severity "HIGH,CRITICAL" ./Dockerfile
|
||||||
|
|||||||
9
.github/workflows/container.yaml
vendored
9
.github/workflows/container.yaml
vendored
@@ -37,15 +37,10 @@ jobs:
|
|||||||
image: tonistiigi/binfmt:latest
|
image: tonistiigi/binfmt:latest
|
||||||
platforms: all
|
platforms: all
|
||||||
|
|
||||||
- name: Check out pull request code
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
with:
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push' || github.event_name == 'schedule'
|
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
if: ${{ github.event_name == 'schedule' && env.GH_CR_PAT != null }}
|
if: ${{ github.event_name == 'schedule' && env.GH_CR_PAT != null }}
|
||||||
|
|||||||
53
.github/workflows/css-lint.yaml
vendored
53
.github/workflows/css-lint.yaml
vendored
@@ -1,53 +0,0 @@
|
|||||||
name: CSS Lint and Formatting
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- 'web/**'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'web/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
css-lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./web
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out pull request code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- name: Get changed files
|
|
||||||
id: changed-files-yaml
|
|
||||||
uses: tj-actions/changed-files@v45
|
|
||||||
with:
|
|
||||||
path: 'web'
|
|
||||||
files_yaml: |
|
|
||||||
src:
|
|
||||||
- '**/*.{css,scss}'
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '22.9.0'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
run: npm install
|
|
||||||
|
|
||||||
- name: Run Prettier
|
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
run: npx prettier --check ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}
|
|
||||||
|
|
||||||
- name: Run Stylelint
|
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
run: npx stylelint ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}
|
|
||||||
@@ -9,15 +9,8 @@ jobs:
|
|||||||
name: Generate API Documentation
|
name: Generate API Documentation
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out pull request code
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- name: Run redoc on openapi.yaml
|
- name: Run redoc on openapi.yaml
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
21
.github/workflows/go-tests.yaml
vendored
21
.github/workflows/go-tests.yaml
vendored
@@ -18,16 +18,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get changed files
|
|
||||||
id: changed-files-yaml
|
|
||||||
uses: tj-actions/changed-files@v45
|
|
||||||
with:
|
|
||||||
files_yaml: |
|
|
||||||
src:
|
|
||||||
- '**/*.{go,mod,sum}'
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/go-build
|
~/.cache/go-build
|
||||||
@@ -37,14 +28,12 @@ jobs:
|
|||||||
go-test-
|
go-test-
|
||||||
|
|
||||||
- name: Install go
|
- name: Install go
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '^1'
|
go-version: '^1'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
run: go test ./...
|
run: go test ./...
|
||||||
|
|
||||||
test-bsds:
|
test-bsds:
|
||||||
@@ -60,14 +49,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get changed files
|
|
||||||
id: changed-files-yaml
|
|
||||||
uses: tj-actions/changed-files@v45
|
|
||||||
with:
|
|
||||||
files_yaml: |
|
|
||||||
src:
|
|
||||||
- '**/*.{go,mod,sum}'
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -78,12 +59,10 @@ jobs:
|
|||||||
go-test-
|
go-test-
|
||||||
|
|
||||||
- name: Install go
|
- name: Install go
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '^1'
|
go-version: '^1'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
run: go test ./...
|
run: go test ./...
|
||||||
|
|||||||
23
.github/workflows/hls-tests.yml
vendored
23
.github/workflows/hls-tests.yml
vendored
@@ -24,27 +24,8 @@ jobs:
|
|||||||
uses: fkirc/skip-duplicate-actions@v5
|
uses: fkirc/skip-duplicate-actions@v5
|
||||||
with:
|
with:
|
||||||
concurrent_skipping: 'same_content_newer'
|
concurrent_skipping: 'same_content_newer'
|
||||||
|
- uses: actions/checkout@v4
|
||||||
- name: Check out pull request code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- name: Get changed files
|
|
||||||
id: changed-files-yaml
|
|
||||||
uses: tj-actions/changed-files@v45
|
|
||||||
with:
|
|
||||||
files_yaml: |
|
|
||||||
src:
|
|
||||||
- '**/*.{go,mod,sum}'
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.22'
|
||||||
cache: true
|
cache: true
|
||||||
@@ -62,7 +43,6 @@ jobs:
|
|||||||
${{ runner.os }}-
|
${{ runner.os }}-
|
||||||
|
|
||||||
- name: Local stroage
|
- name: Local stroage
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
uses: nick-fields/retry@v3
|
uses: nick-fields/retry@v3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
@@ -70,7 +50,6 @@ jobs:
|
|||||||
command: cd test/automated/hls && ./run.sh
|
command: cd test/automated/hls && ./run.sh
|
||||||
|
|
||||||
- name: S3 storage
|
- name: S3 storage
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
|
||||||
uses: nick-fields/retry@v3
|
uses: nick-fields/retry@v3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
|
|||||||
@@ -28,25 +28,18 @@ jobs:
|
|||||||
cancel_others: 'true'
|
cancel_others: 'true'
|
||||||
skip_after_successful_duplicate: 'true'
|
skip_after_successful_duplicate: 'true'
|
||||||
|
|
||||||
- name: Check out pull request code
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
with:
|
||||||
|
# Make sure the actual branch is checked out when running on pull requests
|
||||||
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
fetch-depth: 0
|
||||||
|
persist-credentials: true
|
||||||
- name: Setup Nodejs
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '22.9.0'
|
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files-yaml
|
id: changed-files-yaml
|
||||||
uses: tj-actions/changed-files@v45
|
uses: tj-actions/changed-files@v44
|
||||||
with:
|
with:
|
||||||
path: 'web'
|
path: 'web'
|
||||||
files_ignore: |
|
files_ignore: |
|
||||||
@@ -54,7 +47,7 @@ jobs:
|
|||||||
web/next.config.js
|
web/next.config.js
|
||||||
files_yaml: |
|
files_yaml: |
|
||||||
src:
|
src:
|
||||||
- '**/*.{js,ts,tsx,jsx,md}'
|
- '**/*.{js,ts,tsx,jsx,css,md}'
|
||||||
|
|
||||||
- name: Cache node modules
|
- name: Cache node modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -71,27 +64,19 @@ jobs:
|
|||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
- name: Lint and fix
|
- name: Lint
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true' && github.event_name != 'pull_request'
|
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
||||||
run: npx eslint --fix ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}
|
run: npx eslint --fix ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}
|
||||||
|
|
||||||
- name: Lint
|
- name: Prettier
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true' && github.event_name == 'pull_request'
|
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
||||||
run: npx eslint ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}
|
|
||||||
|
|
||||||
- name: Prettier formatting
|
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true' && github.event_name == 'pull_request'
|
|
||||||
run: npx prettier --write ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}
|
run: npx prettier --write ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}
|
||||||
|
|
||||||
- name: Prettier check
|
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true' && github.event_name != 'pull_request'
|
|
||||||
run: npx prettier ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}
|
|
||||||
|
|
||||||
- name: Debug changed files output
|
- name: Debug changed files output
|
||||||
run: 'pwd && echo "Changed files: ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}"'
|
run: 'pwd && echo "Changed files: ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}"'
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
if: steps.changed-files-yaml.outputs.src_any_changed == 'true' && github.event_name != 'pull_request'
|
if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
|
||||||
uses: EndBug/add-and-commit@v9
|
uses: EndBug/add-and-commit@v9
|
||||||
with:
|
with:
|
||||||
author_name: Owncast
|
author_name: Owncast
|
||||||
@@ -116,20 +101,13 @@ jobs:
|
|||||||
cancel_others: 'true'
|
cancel_others: 'true'
|
||||||
skip_after_successful_duplicate: 'true'
|
skip_after_successful_duplicate: 'true'
|
||||||
|
|
||||||
- name: Check out pull request code
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
with:
|
||||||
|
# Make sure the actual branch is checked out when running on pull requests
|
||||||
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
|
fetch-depth: 0
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- name: Setup Nodejs
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '22.9.0'
|
|
||||||
|
|
||||||
- name: Cache node modules
|
- name: Cache node modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -149,10 +127,6 @@ jobs:
|
|||||||
- name: Check for unused JS code and dependencies
|
- name: Check for unused JS code and dependencies
|
||||||
run: npx knip --include dependencies,files,exports
|
run: npx knip --include dependencies,files,exports
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
working-directory: ./web
|
|
||||||
run: npm test
|
|
||||||
|
|
||||||
# After any formatting and linting is complete we can run the build
|
# After any formatting and linting is complete we can run the build
|
||||||
# and bundle step. This both will verify that the build is successful as
|
# and bundle step. This both will verify that the build is successful as
|
||||||
# well as commiting the updated static files into the repository for use.
|
# well as commiting the updated static files into the repository for use.
|
||||||
@@ -169,11 +143,6 @@ jobs:
|
|||||||
cancel_others: 'true'
|
cancel_others: 'true'
|
||||||
skip_after_successful_duplicate: 'true'
|
skip_after_successful_duplicate: 'true'
|
||||||
|
|
||||||
- name: Setup Nodejs
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '22.9.0'
|
|
||||||
|
|
||||||
- name: Cache node modules
|
- name: Cache node modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
env:
|
env:
|
||||||
@@ -186,15 +155,13 @@ jobs:
|
|||||||
${{ runner.os }}-build-
|
${{ runner.os }}-build-
|
||||||
${{ runner.os }}-
|
${{ runner.os }}-
|
||||||
|
|
||||||
- name: Check out pull request code
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
with:
|
||||||
|
# Make sure the actual branch is checked out when running on pull requests
|
||||||
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
|
fetch-depth: 0
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- name: Bundle web app (next.js build)
|
- name: Bundle web app (next.js build)
|
||||||
run: build/web/bundleWeb.sh
|
run: build/web/bundleWeb.sh
|
||||||
45
.github/workflows/javascript-tests.yml
vendored
Normal file
45
.github/workflows/javascript-tests.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Javascript Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'web/**'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'web/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
jest-run:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- id: skip_check
|
||||||
|
uses: fkirc/skip-duplicate-actions@v5
|
||||||
|
with:
|
||||||
|
concurrent_skipping: 'same_content_newer'
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18.9.0
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v4
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules-javascript-tests
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('web/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||||
|
${{ runner.os }}-build-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
working-directory: ./web
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
working-directory: ./web
|
||||||
|
run: npm test
|
||||||
58
.github/workflows/screenshots.yml
vendored
Normal file
58
.github/workflows/screenshots.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
name: Take nightly screenshots
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 4 * * *'
|
||||||
|
|
||||||
|
env:
|
||||||
|
BROWSERSTACK_KEY: ${{ secrets.BROWSERSTACK_KEY }}
|
||||||
|
BROWSERSTACK_PASSWORD: ${{ secrets.BROWSERSTACK_PASSWORD }}
|
||||||
|
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
|
||||||
|
TEST_URL: http://localhost:8080
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Screenshots:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.22'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v4
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules-screenshots
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('test/automated/screenshots/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||||
|
${{ runner.os }}-build-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Automate screenshots
|
||||||
|
uses: nick-fields/retry@v3
|
||||||
|
with:
|
||||||
|
timeout_minutes: 10
|
||||||
|
max_attempts: 4
|
||||||
|
command: cd test/automated/screenshots && ./run.sh
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
uses: EndBug/add-and-commit@v9
|
||||||
|
with:
|
||||||
|
author_name: Owncast
|
||||||
|
author_email: owncast@owncast.online
|
||||||
|
message: 'Commit screenshots'
|
||||||
|
add: '*.png'
|
||||||
|
pull: '--rebase --autostash'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Dispatch event to web site
|
||||||
|
uses: peter-evans/repository-dispatch@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.BUNDLE_STORYBOOK_OWNCAST_ONLINE }}
|
||||||
|
repository: owncast/owncast.github.io
|
||||||
|
event-type: bundle-components-library
|
||||||
10
.github/workflows/shellcheck.yml
vendored
10
.github/workflows/shellcheck.yml
vendored
@@ -20,15 +20,7 @@ jobs:
|
|||||||
container:
|
container:
|
||||||
image: docker.io/ubuntu:24.04
|
image: docker.io/ubuntu:24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Check out pull request code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
|
|
||||||
- name: Install shellcheck
|
- name: Install shellcheck
|
||||||
run: apt update && apt install -y shellcheck bash && shellcheck --version
|
run: apt update && apt install -y shellcheck bash && shellcheck --version
|
||||||
|
|||||||
49
.github/workflows/stale.yml
vendored
49
.github/workflows/stale.yml
vendored
@@ -1,49 +0,0 @@
|
|||||||
name: 'Close stale issues and PRs'
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '30 */2 * * *'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v9
|
|
||||||
with:
|
|
||||||
exempt-all-milestones: true
|
|
||||||
|
|
||||||
days-before-issue-stale: 60
|
|
||||||
days-before-issue-close: 67
|
|
||||||
exempt-issue-labels: backlog,long-lived,bot
|
|
||||||
exempt-all-issue-milestones: true
|
|
||||||
stale-issue-message: >
|
|
||||||
This issue has been automatically marked as stale because it has not had
|
|
||||||
recent activity. It will be closed if no further activity occurs. If this
|
|
||||||
was a feature request that others have shown no interest in, then it's
|
|
||||||
unlikely to get implemented due to lack of interest. If others also
|
|
||||||
want to see this feature then now is the time to say something! If this
|
|
||||||
is a bug report or you have questions that still need answering, please say
|
|
||||||
something. Feel free to drop by [our chat](https://owncast.rocket.chat) if
|
|
||||||
you'd like to discuss in real-time with people.
|
|
||||||
close-issue-message: >
|
|
||||||
This issue has been automatically closed due to inactivity. This isn't done
|
|
||||||
to be a jerk, or because the project doesn't care. But simply to keep the focus
|
|
||||||
on things that are actively discussed, and has continued interest from the community and
|
|
||||||
Owncast developers. Feel free to to comment if there is still discussion to be
|
|
||||||
had, or if you plan to work on it. Feel free to drop by [our chat](https://owncast.rocket.chat)
|
|
||||||
if you'd like to discuss in real-time with people. Thank you for being involved!
|
|
||||||
|
|
||||||
days-before-pr-stale: 30
|
|
||||||
days-before-pr-close: 37
|
|
||||||
exempt-pr-labels: backlog,long-lived,bot
|
|
||||||
exempt-all-pr-milestones: true
|
|
||||||
stale-pr-message: >
|
|
||||||
This pull request has not had any activity in 30 days. If it has been abandoned
|
|
||||||
no future actions are necessary, it will be automatically closed. If this is a PR
|
|
||||||
with no clear plan on how to move forward on it getting into the project, then
|
|
||||||
further discussion is needed. Now is a good time to discuss if this is still
|
|
||||||
something that should be worked on. If this PR is idle simply because nobody
|
|
||||||
has reviewed it, then feel free to ping somebody. However, if this PR is not linked to an
|
|
||||||
existing issue regarding something that was previously determined to be important, then even
|
|
||||||
more discussion needs to take place before it can get anywhere.
|
|
||||||
This PR will be closed if no further activity occurs. Thank you for your contributions!
|
|
||||||
close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
|
|
||||||
62
.github/workflows/translations.yml
vendored
62
.github/workflows/translations.yml
vendored
@@ -1,62 +0,0 @@
|
|||||||
name: Translation job
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
# Run the workflow every hour
|
|
||||||
- cron: "0 * * * *"
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- 'web/i18n/en/translation.json'
|
|
||||||
- 'web/**/*.tsx'
|
|
||||||
- 'web/**/*.js'
|
|
||||||
- 'crowdin.yml'
|
|
||||||
- '.github/workflows/translations.yml'
|
|
||||||
- 'web/i18next-parser.config.mjs'
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
generate-translations:
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./web
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
if: ${{ github.actor != 'renovate[bot]' && github.actor != 'renovate' }}
|
|
||||||
run: npm install
|
|
||||||
|
|
||||||
- name: Generate translation files
|
|
||||||
run: npm run translate
|
|
||||||
|
|
||||||
- name: Crowdin upload sources/download translations
|
|
||||||
uses: crowdin/github-action@v1
|
|
||||||
with:
|
|
||||||
upload_sources: true
|
|
||||||
download_translations: true
|
|
||||||
create_pull_request: true
|
|
||||||
pull_request_title: 'New Translations'
|
|
||||||
localization_branch_name: translations
|
|
||||||
pull_request_base_branch_name: 'develop'
|
|
||||||
commit_message: 'Updated translations'
|
|
||||||
config: crowdin.yml
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
|
||||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
|
||||||
|
|
||||||
- name: Commit changes
|
|
||||||
uses: EndBug/add-and-commit@v9
|
|
||||||
with:
|
|
||||||
author_name: Owncast
|
|
||||||
author_email: owncast@owncast.online
|
|
||||||
message: 'Commit updated translations'
|
|
||||||
add: 'web/i18n/**'
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,7 +4,6 @@
|
|||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ linters:
|
|||||||
- bodyclose
|
- bodyclose
|
||||||
- dupl
|
- dupl
|
||||||
- errcheck
|
- errcheck
|
||||||
|
- exportloopref
|
||||||
- goconst
|
- goconst
|
||||||
- godot
|
- godot
|
||||||
- godox
|
- godox
|
||||||
@@ -48,7 +49,7 @@ linters:
|
|||||||
- cyclop
|
- cyclop
|
||||||
- gosimple
|
- gosimple
|
||||||
- unused
|
- unused
|
||||||
- copyloopvar
|
- exportloopref
|
||||||
- gocritic
|
- gocritic
|
||||||
- forbidigo
|
- forbidigo
|
||||||
- unparam
|
- unparam
|
||||||
@@ -66,6 +67,12 @@ linters-settings:
|
|||||||
# should ignore tests
|
# should ignore tests
|
||||||
skip-tests: true
|
skip-tests: true
|
||||||
|
|
||||||
|
gosimple:
|
||||||
|
# Select the Go version to target. The default is '1.13'.
|
||||||
|
go: '1.22'
|
||||||
|
# https://staticcheck.io/docs/options#checks
|
||||||
|
checks: ['all']
|
||||||
|
|
||||||
gocritic:
|
gocritic:
|
||||||
disabled-checks:
|
disabled-checks:
|
||||||
- ifElseChain
|
- ifElseChain
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ ENV NAME=${NAME}
|
|||||||
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -ldflags "-extldflags \"-static\" -s -w -X github.com/owncast/owncast/config.GitCommit=$GIT_COMMIT -X github.com/owncast/owncast/config.VersionNumber=$VERSION -X github.com/owncast/owncast/config.BuildPlatform=$NAME" -o owncast .
|
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -ldflags "-extldflags \"-static\" -s -w -X github.com/owncast/owncast/config.GitCommit=$GIT_COMMIT -X github.com/owncast/owncast/config.VersionNumber=$VERSION -X github.com/owncast/owncast/config.BuildPlatform=$NAME" -o owncast .
|
||||||
|
|
||||||
# Create the image by copying the result of the build into a new alpine image
|
# Create the image by copying the result of the build into a new alpine image
|
||||||
FROM alpine:3.21.2
|
FROM alpine:3.20.1
|
||||||
RUN apk update && apk add --no-cache ffmpeg ffmpeg-libs ca-certificates && update-ca-certificates
|
RUN apk update && apk add --no-cache ffmpeg ffmpeg-libs ca-certificates && update-ca-certificates
|
||||||
|
|
||||||
RUN addgroup -g 101 -S owncast && adduser -u 101 -S owncast -G owncast
|
RUN addgroup -g 101 -S owncast && adduser -u 101 -S owncast -G owncast
|
||||||
|
|||||||
10
Earthfile
10
Earthfile
@@ -1,6 +1,6 @@
|
|||||||
VERSION --new-platform 0.6
|
VERSION --new-platform 0.6
|
||||||
|
|
||||||
FROM --platform=linux/amd64 alpine:3.21.2
|
FROM --platform=linux/amd64 alpine:3.15.5
|
||||||
ARG version=develop
|
ARG version=develop
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
@@ -87,7 +87,7 @@ build:
|
|||||||
RUN upx -t owncast
|
RUN upx -t owncast
|
||||||
END
|
END
|
||||||
|
|
||||||
SAVE ARTIFACT --keep-ts owncast owncast
|
SAVE ARTIFACT owncast owncast
|
||||||
|
|
||||||
package:
|
package:
|
||||||
RUN apk add --update --no-cache zip >> /dev/null
|
RUN apk add --update --no-cache zip >> /dev/null
|
||||||
@@ -109,7 +109,7 @@ package:
|
|||||||
ARG NAME=custom
|
ARG NAME=custom
|
||||||
END
|
END
|
||||||
|
|
||||||
COPY --keep-ts (+build/owncast --platform $TARGETPLATFORM) /build/dist/owncast
|
COPY (+build/owncast --platform $TARGETPLATFORM) /build/dist/owncast
|
||||||
ENV ZIPNAME owncast-$version-$NAME.zip
|
ENV ZIPNAME owncast-$version-$NAME.zip
|
||||||
RUN cd /build/dist && zip -r -q -8 /build/dist/owncast.zip .
|
RUN cd /build/dist && zip -r -q -8 /build/dist/owncast.zip .
|
||||||
SAVE ARTIFACT --keep-ts /build/dist/owncast.zip owncast.zip AS LOCAL dist/$ZIPNAME
|
SAVE ARTIFACT --keep-ts /build/dist/owncast.zip owncast.zip AS LOCAL dist/$ZIPNAME
|
||||||
@@ -119,11 +119,11 @@ docker:
|
|||||||
# in as space separated strings using the full account/repo:tag format.
|
# in as space separated strings using the full account/repo:tag format.
|
||||||
# https://github.com/earthly/earthly/blob/aea38448fa9c0064b1b70d61be717ae740689fb9/docs/earthfile/earthfile.md#assigning-multiple-image-names
|
# https://github.com/earthly/earthly/blob/aea38448fa9c0064b1b70d61be717ae740689fb9/docs/earthfile/earthfile.md#assigning-multiple-image-names
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
FROM --platform=$TARGETPLATFORM alpine:3.21.2
|
FROM --platform=$TARGETPLATFORM alpine:3.15.5
|
||||||
RUN apk update && apk add --no-cache ffmpeg ffmpeg-libs ca-certificates unzip && update-ca-certificates
|
RUN apk update && apk add --no-cache ffmpeg ffmpeg-libs ca-certificates unzip && update-ca-certificates
|
||||||
RUN addgroup -g 101 -S owncast && adduser -u 101 -S owncast -G owncast
|
RUN addgroup -g 101 -S owncast && adduser -u 101 -S owncast -G owncast
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --keep-ts --platform=$TARGETPLATFORM +package/owncast.zip /app
|
COPY --platform=$TARGETPLATFORM +package/owncast.zip /app
|
||||||
RUN unzip -x owncast.zip && mkdir data
|
RUN unzip -x owncast.zip && mkdir data
|
||||||
|
|
||||||
# temporarily disable until we figure out how to move forward
|
# temporarily disable until we figure out how to move forward
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
package activitypub
|
package activitypub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/owncast/owncast/activitypub/crypto"
|
"github.com/owncast/owncast/activitypub/crypto"
|
||||||
"github.com/owncast/owncast/activitypub/inbox"
|
"github.com/owncast/owncast/activitypub/inbox"
|
||||||
"github.com/owncast/owncast/activitypub/outbox"
|
"github.com/owncast/owncast/activitypub/outbox"
|
||||||
"github.com/owncast/owncast/activitypub/persistence"
|
"github.com/owncast/owncast/activitypub/persistence"
|
||||||
"github.com/owncast/owncast/activitypub/workerpool"
|
"github.com/owncast/owncast/activitypub/workerpool"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
|
|
||||||
"github.com/owncast/owncast/core/data"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
@@ -17,35 +14,21 @@ import (
|
|||||||
|
|
||||||
// Start will initialize and start the federation support.
|
// Start will initialize and start the federation support.
|
||||||
func Start(datastore *data.Datastore) {
|
func Start(datastore *data.Datastore) {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
persistence.Setup(datastore)
|
persistence.Setup(datastore)
|
||||||
|
workerpool.InitOutboundWorkerPool()
|
||||||
outboundWorkerPoolSize := getOutboundWorkerPoolSize()
|
|
||||||
workerpool.InitOutboundWorkerPool(outboundWorkerPoolSize)
|
|
||||||
inbox.InitInboxWorkerPool()
|
inbox.InitInboxWorkerPool()
|
||||||
|
|
||||||
// Generate the keys for signing federated activity if needed.
|
// Generate the keys for signing federated activity if needed.
|
||||||
if configRepository.GetPrivateKey() == "" {
|
if data.GetPrivateKey() == "" {
|
||||||
privateKey, publicKey, err := crypto.GenerateKeys()
|
privateKey, publicKey, err := crypto.GenerateKeys()
|
||||||
_ = configRepository.SetPrivateKey(string(privateKey))
|
_ = data.SetPrivateKey(string(privateKey))
|
||||||
_ = configRepository.SetPublicKey(string(publicKey))
|
_ = data.SetPublicKey(string(publicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("Unable to get private key", err)
|
log.Errorln("Unable to get private key", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOutboundWorkerPoolSize() int {
|
|
||||||
var followerCount int64
|
|
||||||
fc, err := persistence.GetFollowerCount()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("Unable to get follower count", err)
|
|
||||||
fc = 50 // Arbitrary fallback value.
|
|
||||||
}
|
|
||||||
followerCount = int64(math.Max(float64(fc), 50))
|
|
||||||
return int(followerCount * 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendLive will send a "Go Live" message to followers.
|
// SendLive will send a "Go Live" message to followers.
|
||||||
func SendLive() error {
|
func SendLive() error {
|
||||||
return outbox.SendLive()
|
return outbox.SendLive()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-fed/activity/streams"
|
"github.com/go-fed/activity/streams"
|
||||||
"github.com/go-fed/activity/streams/vocab"
|
"github.com/go-fed/activity/streams/vocab"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrivacyAudience represents the audience for an activity.
|
// PrivacyAudience represents the audience for an activity.
|
||||||
@@ -87,10 +87,8 @@ func MakeActivityDirect(activity vocab.ActivityStreamsCreate, toIRI *url.URL) vo
|
|||||||
// MakeActivityPublic sets the required properties to make this activity
|
// MakeActivityPublic sets the required properties to make this activity
|
||||||
// seen as public.
|
// seen as public.
|
||||||
func MakeActivityPublic(activity vocab.ActivityStreamsCreate) vocab.ActivityStreamsCreate {
|
func MakeActivityPublic(activity vocab.ActivityStreamsCreate) vocab.ActivityStreamsCreate {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
// TO the public if we're not treating ActivityPub as "private".
|
// TO the public if we're not treating ActivityPub as "private".
|
||||||
if !configRepository.GetFederationIsPrivate() {
|
if !data.GetFederationIsPrivate() {
|
||||||
public, _ := url.Parse(PUBLIC)
|
public, _ := url.Parse(PUBLIC)
|
||||||
|
|
||||||
to := streams.NewActivityStreamsToProperty()
|
to := streams.NewActivityStreamsToProperty()
|
||||||
@@ -123,9 +121,7 @@ func MakeUpdateActivity(activityID *url.URL) vocab.ActivityStreamsUpdate {
|
|||||||
activity.SetJSONLDId(id)
|
activity.SetJSONLDId(id)
|
||||||
|
|
||||||
// CC the public if we're not treating ActivityPub as "private".
|
// CC the public if we're not treating ActivityPub as "private".
|
||||||
configRepository := configrepository.Get()
|
if !data.GetFederationIsPrivate() {
|
||||||
|
|
||||||
if !configRepository.GetFederationIsPrivate() {
|
|
||||||
public, _ := url.Parse(PUBLIC)
|
public, _ := url.Parse(PUBLIC)
|
||||||
cc := streams.NewActivityStreamsCcProperty()
|
cc := streams.NewActivityStreamsCcProperty()
|
||||||
cc.AppendIRI(public)
|
cc.AppendIRI(public)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"github.com/go-fed/activity/streams"
|
"github.com/go-fed/activity/streams"
|
||||||
"github.com/go-fed/activity/streams/vocab"
|
"github.com/go-fed/activity/streams/vocab"
|
||||||
"github.com/owncast/owncast/activitypub/crypto"
|
"github.com/owncast/owncast/activitypub/crypto"
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -101,13 +101,11 @@ func MakeActorPropertyWithID(idIRI *url.URL) vocab.ActivityStreamsActorProperty
|
|||||||
|
|
||||||
// MakeServiceForAccount will create a new local actor service with the the provided username.
|
// MakeServiceForAccount will create a new local actor service with the the provided username.
|
||||||
func MakeServiceForAccount(accountName string) vocab.ActivityStreamsService {
|
func MakeServiceForAccount(accountName string) vocab.ActivityStreamsService {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
actorIRI := MakeLocalIRIForAccount(accountName)
|
actorIRI := MakeLocalIRIForAccount(accountName)
|
||||||
|
|
||||||
person := streams.NewActivityStreamsService()
|
person := streams.NewActivityStreamsService()
|
||||||
nameProperty := streams.NewActivityStreamsNameProperty()
|
nameProperty := streams.NewActivityStreamsNameProperty()
|
||||||
nameProperty.AppendXMLSchemaString(configRepository.GetServerName())
|
nameProperty.AppendXMLSchemaString(data.GetServerName())
|
||||||
person.SetActivityStreamsName(nameProperty)
|
person.SetActivityStreamsName(nameProperty)
|
||||||
|
|
||||||
preferredUsernameProperty := streams.NewActivityStreamsPreferredUsernameProperty()
|
preferredUsernameProperty := streams.NewActivityStreamsPreferredUsernameProperty()
|
||||||
@@ -121,7 +119,7 @@ func MakeServiceForAccount(accountName string) vocab.ActivityStreamsService {
|
|||||||
person.SetActivityStreamsInbox(inboxProp)
|
person.SetActivityStreamsInbox(inboxProp)
|
||||||
|
|
||||||
needsFollowApprovalProperty := streams.NewActivityStreamsManuallyApprovesFollowersProperty()
|
needsFollowApprovalProperty := streams.NewActivityStreamsManuallyApprovesFollowersProperty()
|
||||||
needsFollowApprovalProperty.Set(configRepository.GetFederationIsPrivate())
|
needsFollowApprovalProperty.Set(data.GetFederationIsPrivate())
|
||||||
person.SetActivityStreamsManuallyApprovesFollowers(needsFollowApprovalProperty)
|
person.SetActivityStreamsManuallyApprovesFollowers(needsFollowApprovalProperty)
|
||||||
|
|
||||||
outboxIRI := MakeLocalIRIForResource("/user/" + accountName + "/outbox")
|
outboxIRI := MakeLocalIRIForResource("/user/" + accountName + "/outbox")
|
||||||
@@ -154,7 +152,7 @@ func MakeServiceForAccount(accountName string) vocab.ActivityStreamsService {
|
|||||||
publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKeyType)
|
publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKeyType)
|
||||||
person.SetW3IDSecurityV1PublicKey(publicKeyProp)
|
person.SetW3IDSecurityV1PublicKey(publicKeyProp)
|
||||||
|
|
||||||
if t, err := configRepository.GetServerInitTime(); t != nil {
|
if t, err := data.GetServerInitTime(); t != nil {
|
||||||
publishedDateProp := streams.NewActivityStreamsPublishedProperty()
|
publishedDateProp := streams.NewActivityStreamsPublishedProperty()
|
||||||
publishedDateProp.Set(t.Time)
|
publishedDateProp.Set(t.Time)
|
||||||
person.SetActivityStreamsPublished(publishedDateProp)
|
person.SetActivityStreamsPublished(publishedDateProp)
|
||||||
@@ -165,8 +163,8 @@ func MakeServiceForAccount(accountName string) vocab.ActivityStreamsService {
|
|||||||
// Profile properties
|
// Profile properties
|
||||||
|
|
||||||
// Avatar
|
// Avatar
|
||||||
uniquenessString := configRepository.GetLogoUniquenessString()
|
uniquenessString := data.GetLogoUniquenessString()
|
||||||
userAvatarURLString := configRepository.GetServerURL() + "/logo/external"
|
userAvatarURLString := data.GetServerURL() + "/logo/external"
|
||||||
userAvatarURL, err := url.Parse(userAvatarURLString)
|
userAvatarURL, err := url.Parse(userAvatarURLString)
|
||||||
userAvatarURL.RawQuery = "uc=" + uniquenessString
|
userAvatarURL.RawQuery = "uc=" + uniquenessString
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -197,14 +195,14 @@ func MakeServiceForAccount(accountName string) vocab.ActivityStreamsService {
|
|||||||
|
|
||||||
// Profile bio
|
// Profile bio
|
||||||
summaryProperty := streams.NewActivityStreamsSummaryProperty()
|
summaryProperty := streams.NewActivityStreamsSummaryProperty()
|
||||||
summaryProperty.AppendXMLSchemaString(configRepository.GetServerSummary())
|
summaryProperty.AppendXMLSchemaString(data.GetServerSummary())
|
||||||
person.SetActivityStreamsSummary(summaryProperty)
|
person.SetActivityStreamsSummary(summaryProperty)
|
||||||
|
|
||||||
// Links
|
// Links
|
||||||
if serverURL := configRepository.GetServerURL(); serverURL != "" {
|
if serverURL := data.GetServerURL(); serverURL != "" {
|
||||||
addMetadataLinkToProfile(person, "Stream", serverURL)
|
addMetadataLinkToProfile(person, "Stream", serverURL)
|
||||||
}
|
}
|
||||||
for _, link := range configRepository.GetSocialHandles() {
|
for _, link := range data.GetSocialHandles() {
|
||||||
addMetadataLinkToProfile(person, link.Platform, link.URL)
|
addMetadataLinkToProfile(person, link.Platform, link.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +220,7 @@ func MakeServiceForAccount(accountName string) vocab.ActivityStreamsService {
|
|||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
tagProp := streams.NewActivityStreamsTagProperty()
|
tagProp := streams.NewActivityStreamsTagProperty()
|
||||||
for _, tagString := range configRepository.GetServerMetadataTags() {
|
for _, tagString := range data.GetServerMetadataTags() {
|
||||||
hashtag := MakeHashtag(tagString)
|
hashtag := MakeHashtag(tagString)
|
||||||
tagProp.AppendTootHashtag(hashtag)
|
tagProp.AppendTootHashtag(hashtag)
|
||||||
}
|
}
|
||||||
@@ -231,7 +229,7 @@ func MakeServiceForAccount(accountName string) vocab.ActivityStreamsService {
|
|||||||
|
|
||||||
// Work around an issue where a single attachment will not serialize
|
// Work around an issue where a single attachment will not serialize
|
||||||
// as an array, so add another item to the mix.
|
// as an array, so add another item to the mix.
|
||||||
if len(configRepository.GetSocialHandles()) == 1 {
|
if len(data.GetSocialHandles()) == 1 {
|
||||||
addMetadataLinkToProfile(person, "Owncast", "https://owncast.online")
|
addMetadataLinkToProfile(person, "Owncast", "https://owncast.online")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/go-fed/activity/streams"
|
"github.com/go-fed/activity/streams"
|
||||||
"github.com/go-fed/activity/streams/vocab"
|
"github.com/go-fed/activity/streams/vocab"
|
||||||
"github.com/owncast/owncast/core/data"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeFakeService() vocab.ActivityStreamsService {
|
func makeFakeService() vocab.ActivityStreamsService {
|
||||||
@@ -56,11 +55,9 @@ func TestMain(m *testing.M) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
data.SetupPersistence(dbFile.Name())
|
data.SetupPersistence(dbFile.Name())
|
||||||
|
data.SetServerURL("https://my.cool.site.biz")
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
configRepository.SetServerURL("https://my.cool.site.biz")
|
|
||||||
|
|
||||||
m.Run()
|
m.Run()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-fed/activity/streams"
|
"github.com/go-fed/activity/streams"
|
||||||
"github.com/go-fed/activity/streams/vocab"
|
"github.com/go-fed/activity/streams/vocab"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,9 +27,7 @@ func MakeRemoteIRIForResource(resourcePath string, host string) (*url.URL, error
|
|||||||
|
|
||||||
// MakeLocalIRIForResource will create an IRI for the local server.
|
// MakeLocalIRIForResource will create an IRI for the local server.
|
||||||
func MakeLocalIRIForResource(resourcePath string) *url.URL {
|
func MakeLocalIRIForResource(resourcePath string) *url.URL {
|
||||||
configRepository := configrepository.Get()
|
host := data.GetServerURL()
|
||||||
|
|
||||||
host := configRepository.GetServerURL()
|
|
||||||
u, err := url.Parse(host)
|
u, err := url.Parse(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("unable to parse local IRI url", host, err)
|
log.Errorln("unable to parse local IRI url", host, err)
|
||||||
@@ -43,9 +41,7 @@ func MakeLocalIRIForResource(resourcePath string) *url.URL {
|
|||||||
|
|
||||||
// MakeLocalIRIForAccount will return a full IRI for the local server account username.
|
// MakeLocalIRIForAccount will return a full IRI for the local server account username.
|
||||||
func MakeLocalIRIForAccount(account string) *url.URL {
|
func MakeLocalIRIForAccount(account string) *url.URL {
|
||||||
configRepository := configrepository.Get()
|
host := data.GetServerURL()
|
||||||
|
|
||||||
host := configRepository.GetServerURL()
|
|
||||||
u, err := url.Parse(host)
|
u, err := url.Parse(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("unable to parse local IRI account server url", err)
|
log.Errorln("unable to parse local IRI account server url", err)
|
||||||
@@ -68,9 +64,7 @@ func Serialize(obj vocab.Type) ([]byte, error) {
|
|||||||
|
|
||||||
// MakeLocalIRIForStreamURL will return a full IRI for the local server stream url.
|
// MakeLocalIRIForStreamURL will return a full IRI for the local server stream url.
|
||||||
func MakeLocalIRIForStreamURL() *url.URL {
|
func MakeLocalIRIForStreamURL() *url.URL {
|
||||||
configRepository := configrepository.Get()
|
host := data.GetServerURL()
|
||||||
|
|
||||||
host := configRepository.GetServerURL()
|
|
||||||
u, err := url.Parse(host)
|
u, err := url.Parse(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("unable to parse local IRI stream url", err)
|
log.Errorln("unable to parse local IRI stream url", err)
|
||||||
@@ -84,9 +78,7 @@ func MakeLocalIRIForStreamURL() *url.URL {
|
|||||||
|
|
||||||
// MakeLocalIRIforLogo will return a full IRI for the local server logo.
|
// MakeLocalIRIforLogo will return a full IRI for the local server logo.
|
||||||
func MakeLocalIRIforLogo() *url.URL {
|
func MakeLocalIRIforLogo() *url.URL {
|
||||||
configRepository := configrepository.Get()
|
host := data.GetServerURL()
|
||||||
|
|
||||||
host := configRepository.GetServerURL()
|
|
||||||
u, err := url.Parse(host)
|
u, err := url.Parse(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("unable to parse local IRI stream url", err)
|
log.Errorln("unable to parse local IRI stream url", err)
|
||||||
@@ -101,9 +93,7 @@ func MakeLocalIRIforLogo() *url.URL {
|
|||||||
// GetLogoType will return the rel value for the webfinger response and
|
// GetLogoType will return the rel value for the webfinger response and
|
||||||
// the default static image is of type png.
|
// the default static image is of type png.
|
||||||
func GetLogoType() string {
|
func GetLogoType() string {
|
||||||
configRepository := configrepository.Get()
|
imageFilename := data.GetLogoPath()
|
||||||
|
|
||||||
imageFilename := configRepository.GetLogoPath()
|
|
||||||
if imageFilename == "" {
|
if imageFilename == "" {
|
||||||
return "image/png"
|
return "image/png"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,12 @@ import (
|
|||||||
"github.com/owncast/owncast/activitypub/apmodels"
|
"github.com/owncast/owncast/activitypub/apmodels"
|
||||||
"github.com/owncast/owncast/activitypub/crypto"
|
"github.com/owncast/owncast/activitypub/crypto"
|
||||||
"github.com/owncast/owncast/activitypub/requests"
|
"github.com/owncast/owncast/activitypub/requests"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ActorHandler handles requests for a single actor.
|
// ActorHandler handles requests for a single actor.
|
||||||
func ActorHandler(w http.ResponseWriter, r *http.Request) {
|
func ActorHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
configRepository := configrepository.Get()
|
if !data.GetFederationEnabled() {
|
||||||
|
|
||||||
if !configRepository.GetFederationEnabled() {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -24,7 +22,7 @@ func ActorHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
pathComponents := strings.Split(r.URL.Path, "/")
|
pathComponents := strings.Split(r.URL.Path, "/")
|
||||||
accountName := pathComponents[3]
|
accountName := pathComponents[3]
|
||||||
|
|
||||||
if _, valid := configRepository.GetFederatedInboxMap()[accountName]; !valid {
|
if _, valid := data.GetFederatedInboxMap()[accountName]; !valid {
|
||||||
// User is not valid
|
// User is not valid
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/owncast/owncast/activitypub/crypto"
|
"github.com/owncast/owncast/activitypub/crypto"
|
||||||
"github.com/owncast/owncast/activitypub/persistence"
|
"github.com/owncast/owncast/activitypub/persistence"
|
||||||
"github.com/owncast/owncast/activitypub/requests"
|
"github.com/owncast/owncast/activitypub/requests"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -145,9 +145,7 @@ func getFollowersPage(page string, r *http.Request) (vocab.ActivityStreamsOrdere
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createPageURL(r *http.Request, page *string) (*url.URL, error) {
|
func createPageURL(r *http.Request, page *string) (*url.URL, error) {
|
||||||
configRepository := configrepository.Get()
|
domain := data.GetServerURL()
|
||||||
|
|
||||||
domain := configRepository.GetServerURL()
|
|
||||||
if domain == "" {
|
if domain == "" {
|
||||||
return nil, errors.New("unable to get server URL")
|
return nil, errors.New("unable to get server URL")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/owncast/owncast/activitypub/apmodels"
|
"github.com/owncast/owncast/activitypub/apmodels"
|
||||||
"github.com/owncast/owncast/activitypub/inbox"
|
"github.com/owncast/owncast/activitypub/inbox"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -22,9 +22,7 @@ func InboxHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func acceptInboxRequest(w http.ResponseWriter, r *http.Request) {
|
func acceptInboxRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
configRepository := configrepository.Get()
|
if !data.GetFederationEnabled() {
|
||||||
|
|
||||||
if !configRepository.GetFederationEnabled() {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -41,7 +39,7 @@ func acceptInboxRequest(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// The account this request is for must match the account name we have set
|
// The account this request is for must match the account name we have set
|
||||||
// for federation.
|
// for federation.
|
||||||
if forLocalAccount != configRepository.GetFederationUsername() {
|
if forLocalAccount != data.GetFederationUsername() {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/owncast/owncast/activitypub/persistence"
|
"github.com/owncast/owncast/activitypub/persistence"
|
||||||
"github.com/owncast/owncast/activitypub/requests"
|
"github.com/owncast/owncast/activitypub/requests"
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,14 +25,12 @@ func NodeInfoController(w http.ResponseWriter, r *http.Request) {
|
|||||||
Links []links `json:"links"`
|
Links []links `json:"links"`
|
||||||
}
|
}
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
if !data.GetFederationEnabled() {
|
||||||
|
|
||||||
if !configRepository.GetFederationEnabled() {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
serverURL := configRepository.GetServerURL()
|
serverURL := data.GetServerURL()
|
||||||
if serverURL == "" {
|
if serverURL == "" {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
@@ -91,9 +89,7 @@ func NodeInfoV2Controller(w http.ResponseWriter, r *http.Request) {
|
|||||||
Metadata metadata `json:"metadata"`
|
Metadata metadata `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
if !data.GetFederationEnabled() {
|
||||||
|
|
||||||
if !configRepository.GetFederationEnabled() {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -121,7 +117,7 @@ func NodeInfoV2Controller(w http.ResponseWriter, r *http.Request) {
|
|||||||
OpenRegistrations: false,
|
OpenRegistrations: false,
|
||||||
Protocols: []string{"activitypub"},
|
Protocols: []string{"activitypub"},
|
||||||
Metadata: metadata{
|
Metadata: metadata{
|
||||||
ChatEnabled: !configRepository.GetChatDisabled(),
|
ChatEnabled: !data.GetChatDisabled(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,14 +163,12 @@ func XNodeInfo2Controller(w http.ResponseWriter, r *http.Request) {
|
|||||||
OpenRegistrations bool `json:"openRegistrations"`
|
OpenRegistrations bool `json:"openRegistrations"`
|
||||||
}
|
}
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
if !data.GetFederationEnabled() {
|
||||||
|
|
||||||
if !configRepository.GetFederationEnabled() {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
serverURL := configRepository.GetServerURL()
|
serverURL := data.GetServerURL()
|
||||||
if serverURL == "" {
|
if serverURL == "" {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
@@ -184,7 +178,7 @@ func XNodeInfo2Controller(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
res := &response{
|
res := &response{
|
||||||
Organization: Organization{
|
Organization: Organization{
|
||||||
Name: configRepository.GetServerName(),
|
Name: data.GetServerName(),
|
||||||
Contact: serverURL,
|
Contact: serverURL,
|
||||||
},
|
},
|
||||||
Server: Server{
|
Server: Server{
|
||||||
@@ -238,14 +232,12 @@ func InstanceV1Controller(w http.ResponseWriter, r *http.Request) {
|
|||||||
InvitesEnabled bool `json:"invites_enabled"`
|
InvitesEnabled bool `json:"invites_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
if !data.GetFederationEnabled() {
|
||||||
|
|
||||||
if !configRepository.GetFederationEnabled() {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
serverURL := configRepository.GetServerURL()
|
serverURL := data.GetServerURL()
|
||||||
if serverURL == "" {
|
if serverURL == "" {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
@@ -262,9 +254,9 @@ func InstanceV1Controller(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
res := response{
|
res := response{
|
||||||
URI: serverURL,
|
URI: serverURL,
|
||||||
Title: configRepository.GetServerName(),
|
Title: data.GetServerName(),
|
||||||
ShortDescription: configRepository.GetServerSummary(),
|
ShortDescription: data.GetServerSummary(),
|
||||||
Description: configRepository.GetServerSummary(),
|
Description: data.GetServerSummary(),
|
||||||
Version: config.GetReleaseString(),
|
Version: config.GetReleaseString(),
|
||||||
Stats: Stats{
|
Stats: Stats{
|
||||||
UserCount: 1,
|
UserCount: 1,
|
||||||
@@ -283,9 +275,7 @@ func InstanceV1Controller(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeResponse(payload interface{}, w http.ResponseWriter) error {
|
func writeResponse(payload interface{}, w http.ResponseWriter) error {
|
||||||
configRepository := configrepository.Get()
|
accountName := data.GetDefaultFederationUsername()
|
||||||
|
|
||||||
accountName := configRepository.GetDefaultFederationUsername()
|
|
||||||
actorIRI := apmodels.MakeLocalIRIForAccount(accountName)
|
actorIRI := apmodels.MakeLocalIRIForAccount(accountName)
|
||||||
publicKey := crypto.GetPublicKey(actorIRI)
|
publicKey := crypto.GetPublicKey(actorIRI)
|
||||||
|
|
||||||
@@ -294,15 +284,7 @@ func writeResponse(payload interface{}, w http.ResponseWriter) error {
|
|||||||
|
|
||||||
// HostMetaController points to webfinger.
|
// HostMetaController points to webfinger.
|
||||||
func HostMetaController(w http.ResponseWriter, r *http.Request) {
|
func HostMetaController(w http.ResponseWriter, r *http.Request) {
|
||||||
configRepository := configrepository.Get()
|
serverURL := data.GetServerURL()
|
||||||
|
|
||||||
if !configRepository.GetFederationEnabled() {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
log.Debugln("host meta request rejected! Federation is not enabled")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
serverURL := configRepository.GetServerURL()
|
|
||||||
if serverURL == "" {
|
if serverURL == "" {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -8,33 +8,31 @@ import (
|
|||||||
"github.com/owncast/owncast/activitypub/crypto"
|
"github.com/owncast/owncast/activitypub/crypto"
|
||||||
"github.com/owncast/owncast/activitypub/persistence"
|
"github.com/owncast/owncast/activitypub/persistence"
|
||||||
"github.com/owncast/owncast/activitypub/requests"
|
"github.com/owncast/owncast/activitypub/requests"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ObjectHandler handles requests for a single federated ActivityPub object.
|
// ObjectHandler handles requests for a single federated ActivityPub object.
|
||||||
func ObjectHandler(w http.ResponseWriter, r *http.Request) {
|
func ObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
configRepository := configrepository.Get()
|
if !data.GetFederationEnabled() {
|
||||||
|
|
||||||
if !configRepository.GetFederationEnabled() {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If private federation mode is enabled do not allow access to objects.
|
// If private federation mode is enabled do not allow access to objects.
|
||||||
if configRepository.GetFederationIsPrivate() {
|
if data.GetFederationIsPrivate() {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iri := strings.Join([]string{strings.TrimSuffix(configRepository.GetServerURL(), "/"), r.URL.Path}, "")
|
iri := strings.Join([]string{strings.TrimSuffix(data.GetServerURL(), "/"), r.URL.Path}, "")
|
||||||
object, _, _, err := persistence.GetObjectByIRI(iri)
|
object, _, _, err := persistence.GetObjectByIRI(iri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
accountName := configRepository.GetDefaultFederationUsername()
|
accountName := data.GetDefaultFederationUsername()
|
||||||
actorIRI := apmodels.MakeLocalIRIForAccount(accountName)
|
actorIRI := apmodels.MakeLocalIRIForAccount(accountName)
|
||||||
publicKey := crypto.GetPublicKey(actorIRI)
|
publicKey := crypto.GetPublicKey(actorIRI)
|
||||||
|
|
||||||
|
|||||||
@@ -6,22 +6,20 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/owncast/owncast/activitypub/apmodels"
|
"github.com/owncast/owncast/activitypub/apmodels"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebfingerHandler will handle webfinger lookup requests.
|
// WebfingerHandler will handle webfinger lookup requests.
|
||||||
func WebfingerHandler(w http.ResponseWriter, r *http.Request) {
|
func WebfingerHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
configRepository := configrepository.Get()
|
if !data.GetFederationEnabled() {
|
||||||
|
|
||||||
if !configRepository.GetFederationEnabled() {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
log.Debugln("webfinger request rejected! Federation is not enabled")
|
log.Debugln("webfinger request rejected! Federation is not enabled")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceHostURL := configRepository.GetServerURL()
|
instanceHostURL := data.GetServerURL()
|
||||||
if instanceHostURL == "" {
|
if instanceHostURL == "" {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
log.Warnln("webfinger request rejected! Federation is enabled but server URL is empty.")
|
log.Warnln("webfinger request rejected! Federation is enabled but server URL is empty.")
|
||||||
@@ -31,7 +29,7 @@ func WebfingerHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
instanceHostString := utils.GetHostnameFromURLString(instanceHostURL)
|
instanceHostString := utils.GetHostnameFromURLString(instanceHostURL)
|
||||||
if instanceHostString == "" {
|
if instanceHostString == "" {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
log.Warnln("webfinger request rejected! Federation is enabled but server URL is not set properly. data.GetServerURL(): " + configRepository.GetServerURL())
|
log.Warnln("webfinger request rejected! Federation is enabled but server URL is not set properly. data.GetServerURL(): " + data.GetServerURL())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +51,7 @@ func WebfingerHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
host := userComponents[1]
|
host := userComponents[1]
|
||||||
user := userComponents[0]
|
user := userComponents[0]
|
||||||
|
|
||||||
if _, valid := configRepository.GetFederatedInboxMap()[user]; !valid {
|
if _, valid := data.GetFederatedInboxMap()[user]; !valid {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
log.Debugln("webfinger request rejected! Invalid user: " + user)
|
log.Debugln("webfinger request rejected! Invalid user: " + user)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -8,15 +8,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetPublicKey will return the public key for the provided actor.
|
// GetPublicKey will return the public key for the provided actor.
|
||||||
func GetPublicKey(actorIRI *url.URL) PublicKey {
|
func GetPublicKey(actorIRI *url.URL) PublicKey {
|
||||||
configRepository := configrepository.Get()
|
key := data.GetPublicKey()
|
||||||
|
|
||||||
key := configRepository.GetPublicKey()
|
|
||||||
idURL, err := url.Parse(actorIRI.String() + "#main-key")
|
idURL, err := url.Parse(actorIRI.String() + "#main-key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("unable to parse actor iri string", idURL, err)
|
log.Errorln("unable to parse actor iri string", idURL, err)
|
||||||
@@ -31,9 +29,7 @@ func GetPublicKey(actorIRI *url.URL) PublicKey {
|
|||||||
|
|
||||||
// GetPrivateKey will return the internal server private key.
|
// GetPrivateKey will return the internal server private key.
|
||||||
func GetPrivateKey() *rsa.PrivateKey {
|
func GetPrivateKey() *rsa.PrivateKey {
|
||||||
configRepository := configrepository.Get()
|
key := data.GetPrivateKey()
|
||||||
|
|
||||||
key := configRepository.GetPrivateKey()
|
|
||||||
|
|
||||||
block, _ := pem.Decode([]byte(key))
|
block, _ := pem.Decode([]byte(key))
|
||||||
if block == nil {
|
if block == nil {
|
||||||
|
|||||||
@@ -7,19 +7,17 @@ import (
|
|||||||
"github.com/owncast/owncast/activitypub/resolvers"
|
"github.com/owncast/owncast/activitypub/resolvers"
|
||||||
"github.com/owncast/owncast/core/chat"
|
"github.com/owncast/owncast/core/chat"
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleEngagementActivity(eventType events.EventType, isLiveNotification bool, actorReference vocab.ActivityStreamsActorProperty, action string) error {
|
func handleEngagementActivity(eventType events.EventType, isLiveNotification bool, actorReference vocab.ActivityStreamsActorProperty, action string) error {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
// Do nothing if displaying engagement actions has been turned off.
|
// Do nothing if displaying engagement actions has been turned off.
|
||||||
if !configRepository.GetFederationShowEngagement() {
|
if !data.GetFederationShowEngagement() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do nothing if chat is disabled
|
// Do nothing if chat is disabled
|
||||||
if configRepository.GetChatDisabled() {
|
if data.GetChatDisabled() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,11 +36,11 @@ func handleEngagementActivity(eventType events.EventType, isLiveNotification boo
|
|||||||
if isLiveNotification && action == events.FediverseEngagementLike {
|
if isLiveNotification && action == events.FediverseEngagementLike {
|
||||||
suffix = "liked that this stream went live."
|
suffix = "liked that this stream went live."
|
||||||
} else if action == events.FediverseEngagementLike {
|
} else if action == events.FediverseEngagementLike {
|
||||||
suffix = fmt.Sprintf("liked a post from %s.", configRepository.GetServerName())
|
suffix = fmt.Sprintf("liked a post from %s.", data.GetServerName())
|
||||||
} else if isLiveNotification && action == events.FediverseEngagementRepost {
|
} else if isLiveNotification && action == events.FediverseEngagementRepost {
|
||||||
suffix = "shared this stream with their followers."
|
suffix = "shared this stream with their followers."
|
||||||
} else if action == events.FediverseEngagementRepost {
|
} else if action == events.FediverseEngagementRepost {
|
||||||
suffix = fmt.Sprintf("shared a post from %s.", configRepository.GetServerName())
|
suffix = fmt.Sprintf("shared a post from %s.", data.GetServerName())
|
||||||
} else if action == events.FediverseEngagementFollow {
|
} else if action == events.FediverseEngagementFollow {
|
||||||
suffix = "followed this stream."
|
suffix = "followed this stream."
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,15 +10,13 @@ import (
|
|||||||
"github.com/owncast/owncast/activitypub/requests"
|
"github.com/owncast/owncast/activitypub/requests"
|
||||||
"github.com/owncast/owncast/activitypub/resolvers"
|
"github.com/owncast/owncast/activitypub/resolvers"
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleFollowInboxRequest(c context.Context, activity vocab.ActivityStreamsFollow) error {
|
func handleFollowInboxRequest(c context.Context, activity vocab.ActivityStreamsFollow) error {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
follow, err := resolvers.MakeFollowRequest(c, activity)
|
follow, err := resolvers.MakeFollowRequest(c, activity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("unable to create follow inbox request", err)
|
log.Errorln("unable to create follow inbox request", err)
|
||||||
@@ -29,7 +27,7 @@ func handleFollowInboxRequest(c context.Context, activity vocab.ActivityStreamsF
|
|||||||
return fmt.Errorf("unable to handle request")
|
return fmt.Errorf("unable to handle request")
|
||||||
}
|
}
|
||||||
|
|
||||||
approved := !configRepository.GetFederationIsPrivate()
|
approved := !data.GetFederationIsPrivate()
|
||||||
|
|
||||||
followRequest := *follow
|
followRequest := *follow
|
||||||
|
|
||||||
@@ -38,7 +36,7 @@ func handleFollowInboxRequest(c context.Context, activity vocab.ActivityStreamsF
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
localAccountName := configRepository.GetDefaultFederationUsername()
|
localAccountName := data.GetDefaultFederationUsername()
|
||||||
|
|
||||||
if approved {
|
if approved {
|
||||||
if err := requests.SendFollowAccept(follow.Inbox, activity, localAccountName); err != nil {
|
if err := requests.SendFollowAccept(follow.Inbox, activity, localAccountName); err != nil {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/owncast/owncast/activitypub/apmodels"
|
"github.com/owncast/owncast/activitypub/apmodels"
|
||||||
"github.com/owncast/owncast/activitypub/persistence"
|
"github.com/owncast/owncast/activitypub/persistence"
|
||||||
"github.com/owncast/owncast/activitypub/resolvers"
|
"github.com/owncast/owncast/activitypub/resolvers"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -131,9 +131,7 @@ func Verify(request *http.Request) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isBlockedDomain(domain string) bool {
|
func isBlockedDomain(domain string) bool {
|
||||||
configRepository := configrepository.Get()
|
blockedDomains := data.GetBlockedFederatedDomains()
|
||||||
|
|
||||||
blockedDomains := configRepository.GetBlockedFederatedDomains()
|
|
||||||
|
|
||||||
for _, blockedDomain := range blockedDomains {
|
for _, blockedDomain := range blockedDomains {
|
||||||
if strings.Contains(domain, blockedDomain) {
|
if strings.Contains(domain, blockedDomain) {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/owncast/owncast/activitypub/apmodels"
|
"github.com/owncast/owncast/activitypub/apmodels"
|
||||||
"github.com/owncast/owncast/activitypub/persistence"
|
"github.com/owncast/owncast/activitypub/persistence"
|
||||||
"github.com/owncast/owncast/core/data"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeFakePerson() vocab.ActivityStreamsPerson {
|
func makeFakePerson() vocab.ActivityStreamsPerson {
|
||||||
@@ -50,24 +49,21 @@ func makeFakePerson() vocab.ActivityStreamsPerson {
|
|||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
data.SetupPersistence(":memory:")
|
data.SetupPersistence(":memory:")
|
||||||
configRepository := configrepository.Get()
|
data.SetServerURL("https://my.cool.site.biz")
|
||||||
configRepository.SetServerURL("https://my.cool.site.biz")
|
|
||||||
persistence.Setup(data.GetDatastore())
|
persistence.Setup(data.GetDatastore())
|
||||||
m.Run()
|
m.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBlockedDomains(t *testing.T) {
|
func TestBlockedDomains(t *testing.T) {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
person := makeFakePerson()
|
person := makeFakePerson()
|
||||||
|
|
||||||
configRepository.SetBlockedFederatedDomains([]string{"freedom.eagle", "guns.life"})
|
data.SetBlockedFederatedDomains([]string{"freedom.eagle", "guns.life"})
|
||||||
|
|
||||||
if len(configRepository.GetBlockedFederatedDomains()) != 2 {
|
if len(data.GetBlockedFederatedDomains()) != 2 {
|
||||||
t.Error("Blocked federated domains is not set correctly")
|
t.Error("Blocked federated domains is not set correctly")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, domain := range configRepository.GetBlockedFederatedDomains() {
|
for _, domain := range data.GetBlockedFederatedDomains() {
|
||||||
if domain == person.GetJSONLDId().GetIRI().Host {
|
if domain == person.GetJSONLDId().GetIRI().Host {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import (
|
|||||||
"github.com/owncast/owncast/activitypub/resolvers"
|
"github.com/owncast/owncast/activitypub/resolvers"
|
||||||
"github.com/owncast/owncast/activitypub/webfinger"
|
"github.com/owncast/owncast/activitypub/webfinger"
|
||||||
"github.com/owncast/owncast/activitypub/workerpool"
|
"github.com/owncast/owncast/activitypub/workerpool"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/teris-io/shortid"
|
"github.com/teris-io/shortid"
|
||||||
@@ -27,9 +27,7 @@ import (
|
|||||||
|
|
||||||
// SendLive will send all followers the message saying you started a live stream.
|
// SendLive will send all followers the message saying you started a live stream.
|
||||||
func SendLive() error {
|
func SendLive() error {
|
||||||
configRepository := configrepository.Get()
|
textContent := data.GetFederationGoLiveMessage()
|
||||||
|
|
||||||
textContent := configRepository.GetFederationGoLiveMessage()
|
|
||||||
|
|
||||||
// If the message is empty then do not send it.
|
// If the message is empty then do not send it.
|
||||||
if textContent == "" {
|
if textContent == "" {
|
||||||
@@ -40,7 +38,7 @@ func SendLive() error {
|
|||||||
reg := regexp.MustCompile("[^a-zA-Z0-9]+")
|
reg := regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||||
|
|
||||||
tagProp := streams.NewActivityStreamsTagProperty()
|
tagProp := streams.NewActivityStreamsTagProperty()
|
||||||
for _, tagString := range configRepository.GetServerMetadataTags() {
|
for _, tagString := range data.GetServerMetadataTags() {
|
||||||
tagWithoutSpecialCharacters := reg.ReplaceAllString(tagString, "")
|
tagWithoutSpecialCharacters := reg.ReplaceAllString(tagString, "")
|
||||||
hashtag := apmodels.MakeHashtag(tagWithoutSpecialCharacters)
|
hashtag := apmodels.MakeHashtag(tagWithoutSpecialCharacters)
|
||||||
tagProp.AppendTootHashtag(hashtag)
|
tagProp.AppendTootHashtag(hashtag)
|
||||||
@@ -59,15 +57,15 @@ func SendLive() error {
|
|||||||
tagsString := strings.Join(tagStrings, " ")
|
tagsString := strings.Join(tagStrings, " ")
|
||||||
|
|
||||||
var streamTitle string
|
var streamTitle string
|
||||||
if title := configRepository.GetStreamTitle(); title != "" {
|
if title := data.GetStreamTitle(); title != "" {
|
||||||
streamTitle = fmt.Sprintf("<p>%s</p>", title)
|
streamTitle = fmt.Sprintf("<p>%s</p>", title)
|
||||||
}
|
}
|
||||||
textContent = fmt.Sprintf("<p>%s</p>%s<p>%s</p><p><a href=\"%s\">%s</a></p>", textContent, streamTitle, tagsString, configRepository.GetServerURL(), configRepository.GetServerURL())
|
textContent = fmt.Sprintf("<p>%s</p>%s<p>%s</p><p><a href=\"%s\">%s</a></p>", textContent, streamTitle, tagsString, data.GetServerURL(), data.GetServerURL())
|
||||||
|
|
||||||
activity, _, note, noteID := createBaseOutboundMessage(textContent)
|
activity, _, note, noteID := createBaseOutboundMessage(textContent)
|
||||||
|
|
||||||
// To the public if we're not treating ActivityPub as "private".
|
// To the public if we're not treating ActivityPub as "private".
|
||||||
if !configRepository.GetFederationIsPrivate() {
|
if !data.GetFederationIsPrivate() {
|
||||||
note = apmodels.MakeNotePublic(note)
|
note = apmodels.MakeNotePublic(note)
|
||||||
activity = apmodels.MakeActivityPublic(activity)
|
activity = apmodels.MakeActivityPublic(activity)
|
||||||
}
|
}
|
||||||
@@ -75,7 +73,7 @@ func SendLive() error {
|
|||||||
note.SetActivityStreamsTag(tagProp)
|
note.SetActivityStreamsTag(tagProp)
|
||||||
|
|
||||||
// Attach an image along with the Federated message.
|
// Attach an image along with the Federated message.
|
||||||
previewURL, err := url.Parse(configRepository.GetServerURL())
|
previewURL, err := url.Parse(data.GetServerURL())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var imageToAttach string
|
var imageToAttach string
|
||||||
var mediaType string
|
var mediaType string
|
||||||
@@ -96,7 +94,7 @@ func SendLive() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if configRepository.GetNSFW() {
|
if data.GetNSFW() {
|
||||||
// Mark content as sensitive.
|
// Mark content as sensitive.
|
||||||
sensitive := streams.NewActivityStreamsSensitiveProperty()
|
sensitive := streams.NewActivityStreamsSensitiveProperty()
|
||||||
sensitive.AppendXMLSchemaBoolean(true)
|
sensitive.AppendXMLSchemaBoolean(true)
|
||||||
@@ -153,8 +151,6 @@ func SendDirectMessageToAccount(textContent, account string) error {
|
|||||||
|
|
||||||
// SendPublicMessage will send a public message to all followers.
|
// SendPublicMessage will send a public message to all followers.
|
||||||
func SendPublicMessage(textContent string) error {
|
func SendPublicMessage(textContent string) error {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
originalContent := textContent
|
originalContent := textContent
|
||||||
textContent = utils.RenderSimpleMarkdown(textContent)
|
textContent = utils.RenderSimpleMarkdown(textContent)
|
||||||
|
|
||||||
@@ -177,7 +173,7 @@ func SendPublicMessage(textContent string) error {
|
|||||||
activity, _, note, noteID := createBaseOutboundMessage(textContent)
|
activity, _, note, noteID := createBaseOutboundMessage(textContent)
|
||||||
note.SetActivityStreamsTag(tagProp)
|
note.SetActivityStreamsTag(tagProp)
|
||||||
|
|
||||||
if !configRepository.GetFederationIsPrivate() {
|
if !data.GetFederationIsPrivate() {
|
||||||
note = apmodels.MakeNotePublic(note)
|
note = apmodels.MakeNotePublic(note)
|
||||||
activity = apmodels.MakeActivityPublic(activity)
|
activity = apmodels.MakeActivityPublic(activity)
|
||||||
}
|
}
|
||||||
@@ -201,8 +197,7 @@ func SendPublicMessage(textContent string) error {
|
|||||||
|
|
||||||
// nolint: unparam
|
// nolint: unparam
|
||||||
func createBaseOutboundMessage(textContent string) (vocab.ActivityStreamsCreate, string, vocab.ActivityStreamsNote, string) {
|
func createBaseOutboundMessage(textContent string) (vocab.ActivityStreamsCreate, string, vocab.ActivityStreamsNote, string) {
|
||||||
configRepository := configrepository.Get()
|
localActor := apmodels.MakeLocalIRIForAccount(data.GetDefaultFederationUsername())
|
||||||
localActor := apmodels.MakeLocalIRIForAccount(configRepository.GetDefaultFederationUsername())
|
|
||||||
noteID := shortid.MustGenerate()
|
noteID := shortid.MustGenerate()
|
||||||
noteIRI := apmodels.MakeLocalIRIForResource(noteID)
|
noteIRI := apmodels.MakeLocalIRIForResource(noteID)
|
||||||
id := shortid.MustGenerate()
|
id := shortid.MustGenerate()
|
||||||
@@ -223,8 +218,7 @@ func getHashtagLinkHTMLFromTagString(baseHashtag string) string {
|
|||||||
|
|
||||||
// SendToFollowers will send an arbitrary payload to all follower inboxes.
|
// SendToFollowers will send an arbitrary payload to all follower inboxes.
|
||||||
func SendToFollowers(payload []byte) error {
|
func SendToFollowers(payload []byte) error {
|
||||||
configRepository := configrepository.Get()
|
localActor := apmodels.MakeLocalIRIForAccount(data.GetDefaultFederationUsername())
|
||||||
localActor := apmodels.MakeLocalIRIForAccount(configRepository.GetDefaultFederationUsername())
|
|
||||||
|
|
||||||
followers, _, err := persistence.GetFederationFollowers(-1, 0)
|
followers, _, err := persistence.GetFederationFollowers(-1, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -247,8 +241,7 @@ func SendToFollowers(payload []byte) error {
|
|||||||
|
|
||||||
// SendToUser will send a payload to a single specific inbox.
|
// SendToUser will send a payload to a single specific inbox.
|
||||||
func SendToUser(inbox *url.URL, payload []byte) error {
|
func SendToUser(inbox *url.URL, payload []byte) error {
|
||||||
configRepository := configrepository.Get()
|
localActor := apmodels.MakeLocalIRIForAccount(data.GetDefaultFederationUsername())
|
||||||
localActor := apmodels.MakeLocalIRIForAccount(configRepository.GetDefaultFederationUsername())
|
|
||||||
|
|
||||||
req, err := requests.CreateSignedRequest(payload, inbox, localActor)
|
req, err := requests.CreateSignedRequest(payload, inbox, localActor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -262,10 +255,8 @@ func SendToUser(inbox *url.URL, payload []byte) error {
|
|||||||
|
|
||||||
// UpdateFollowersWithAccountUpdates will send an update to all followers alerting of a profile update.
|
// UpdateFollowersWithAccountUpdates will send an update to all followers alerting of a profile update.
|
||||||
func UpdateFollowersWithAccountUpdates() error {
|
func UpdateFollowersWithAccountUpdates() error {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
// Don't do anything if federation is disabled.
|
// Don't do anything if federation is disabled.
|
||||||
if !configRepository.GetFederationEnabled() {
|
if !data.GetFederationEnabled() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +265,7 @@ func UpdateFollowersWithAccountUpdates() error {
|
|||||||
activity := apmodels.MakeUpdateActivity(objectID)
|
activity := apmodels.MakeUpdateActivity(objectID)
|
||||||
|
|
||||||
actor := streams.NewActivityStreamsPerson()
|
actor := streams.NewActivityStreamsPerson()
|
||||||
actorID := apmodels.MakeLocalIRIForAccount(configRepository.GetDefaultFederationUsername())
|
actorID := apmodels.MakeLocalIRIForAccount(data.GetDefaultFederationUsername())
|
||||||
actorIDProperty := streams.NewJSONLDIdProperty()
|
actorIDProperty := streams.NewJSONLDIdProperty()
|
||||||
actorIDProperty.Set(actorID)
|
actorIDProperty.Set(actorID)
|
||||||
actor.SetJSONLDId(actorIDProperty)
|
actor.SetJSONLDId(actorIDProperty)
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ func GetFederationFollowers(limit int, offset int) ([]models.Follower, int, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
followersResult, err := _datastore.GetQueries().GetFederationFollowersWithOffset(ctx, db.GetFederationFollowersWithOffsetParams{
|
followersResult, err := _datastore.GetQueries().GetFederationFollowersWithOffset(ctx, db.GetFederationFollowersWithOffsetParams{
|
||||||
Limit: limit,
|
Limit: int32(limit),
|
||||||
Offset: offset,
|
Offset: int32(offset),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ func GetOutbox(limit int, offset int) (vocab.ActivityStreamsOrderedCollection, e
|
|||||||
orderedItems := streams.NewActivityStreamsOrderedItemsProperty()
|
orderedItems := streams.NewActivityStreamsOrderedItemsProperty()
|
||||||
rows, err := _datastore.GetQueries().GetOutboxWithOffset(
|
rows, err := _datastore.GetQueries().GetOutboxWithOffset(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
db.GetOutboxWithOffsetParams{Limit: limit, Offset: offset},
|
db.GetOutboxWithOffsetParams{Limit: int32(limit), Offset: int32(offset)},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return collection, err
|
return collection, err
|
||||||
@@ -309,8 +309,8 @@ func SaveInboundFediverseActivity(objectIRI string, actorIRI string, eventType s
|
|||||||
func GetInboundActivities(limit int, offset int) ([]models.FederatedActivity, int, error) {
|
func GetInboundActivities(limit int, offset int) ([]models.FederatedActivity, int, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
rows, err := _datastore.GetQueries().GetInboundActivitiesWithOffset(ctx, db.GetInboundActivitiesWithOffsetParams{
|
rows, err := _datastore.GetQueries().GetInboundActivitiesWithOffset(ctx, db.GetInboundActivitiesWithOffsetParams{
|
||||||
Limit: limit,
|
Limit: int32(limit),
|
||||||
Offset: offset,
|
Offset: int32(offset),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/go-fed/activity/streams/vocab"
|
"github.com/go-fed/activity/streams/vocab"
|
||||||
"github.com/owncast/owncast/activitypub/apmodels"
|
"github.com/owncast/owncast/activitypub/apmodels"
|
||||||
"github.com/owncast/owncast/activitypub/crypto"
|
"github.com/owncast/owncast/activitypub/crypto"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -47,12 +47,11 @@ func Resolve(c context.Context, data []byte, callbacks ...interface{}) error {
|
|||||||
|
|
||||||
// ResolveIRI will resolve an IRI ahd call the correct callback for the resolved type.
|
// ResolveIRI will resolve an IRI ahd call the correct callback for the resolved type.
|
||||||
func ResolveIRI(c context.Context, iri string, callbacks ...interface{}) error {
|
func ResolveIRI(c context.Context, iri string, callbacks ...interface{}) error {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
log.Debugln("Resolving", iri)
|
log.Debugln("Resolving", iri)
|
||||||
|
|
||||||
req, _ := http.NewRequest(http.MethodGet, iri, nil)
|
req, _ := http.NewRequest(http.MethodGet, iri, nil)
|
||||||
|
|
||||||
actor := apmodels.MakeLocalIRIForAccount(configRepository.GetDefaultFederationUsername())
|
actor := apmodels.MakeLocalIRIForAccount(data.GetDefaultFederationUsername())
|
||||||
if err := crypto.SignRequest(req, nil, actor); err != nil {
|
if err := crypto.SignRequest(req, nil, actor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ package workerpool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// workerPoolSize defines the number of concurrent HTTP ActivityPub requests.
|
||||||
|
var workerPoolSize = runtime.GOMAXPROCS(0)
|
||||||
|
|
||||||
// Job struct bundling the ActivityPub and the payload in one struct.
|
// Job struct bundling the ActivityPub and the payload in one struct.
|
||||||
type Job struct {
|
type Job struct {
|
||||||
request *http.Request
|
request *http.Request
|
||||||
@@ -14,8 +18,8 @@ type Job struct {
|
|||||||
var queue chan Job
|
var queue chan Job
|
||||||
|
|
||||||
// InitOutboundWorkerPool starts n go routines that await ActivityPub jobs.
|
// InitOutboundWorkerPool starts n go routines that await ActivityPub jobs.
|
||||||
func InitOutboundWorkerPool(workerPoolSize int) {
|
func InitOutboundWorkerPool() {
|
||||||
queue = make(chan Job, workerPoolSize)
|
queue = make(chan Job)
|
||||||
|
|
||||||
// start workers
|
// start workers
|
||||||
for i := 1; i <= workerPoolSize; i++ {
|
for i := 1; i <= workerPoolSize; i++ {
|
||||||
@@ -25,13 +29,8 @@ func InitOutboundWorkerPool(workerPoolSize int) {
|
|||||||
|
|
||||||
// AddToOutboundQueue will queue up an outbound http request.
|
// AddToOutboundQueue will queue up an outbound http request.
|
||||||
func AddToOutboundQueue(req *http.Request) {
|
func AddToOutboundQueue(req *http.Request) {
|
||||||
select {
|
|
||||||
case queue <- Job{req}:
|
|
||||||
default:
|
|
||||||
log.Debugln("Outbound ActivityPub job queue is full")
|
|
||||||
queue <- Job{req} // will block until received by a worker at this point
|
|
||||||
}
|
|
||||||
log.Tracef("Queued request for ActivityPub destination %s", req.RequestURI)
|
log.Tracef("Queued request for ActivityPub destination %s", req.RequestURI)
|
||||||
|
queue <- Job{req}
|
||||||
}
|
}
|
||||||
|
|
||||||
func worker(workerID int, queue <-chan Job) {
|
func worker(workerID int, queue <-chan Job) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -47,8 +47,6 @@ func setupExpiredRequestPruner() {
|
|||||||
|
|
||||||
// StartAuthFlow will begin the IndieAuth flow by generating an auth request.
|
// StartAuthFlow will begin the IndieAuth flow by generating an auth request.
|
||||||
func StartAuthFlow(authHost, userID, accessToken, displayName string) (*url.URL, error) {
|
func StartAuthFlow(authHost, userID, accessToken, displayName string) (*url.URL, error) {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
// Limit the number of pending requests
|
// Limit the number of pending requests
|
||||||
if len(pendingAuthRequests) >= maxPendingRequests {
|
if len(pendingAuthRequests) >= maxPendingRequests {
|
||||||
return nil, errors.New("Please try again later. Too many pending requests.")
|
return nil, errors.New("Please try again later. Too many pending requests.")
|
||||||
@@ -70,7 +68,7 @@ func StartAuthFlow(authHost, userID, accessToken, displayName string) (*url.URL,
|
|||||||
return nil, errors.New("only servers secured with https are supported")
|
return nil, errors.New("only servers secured with https are supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
serverURL := configRepository.GetServerURL()
|
serverURL := data.GetServerURL()
|
||||||
if serverURL == "" {
|
if serverURL == "" {
|
||||||
return nil, errors.New("Owncast server URL must be set when using auth")
|
return nil, errors.New("Owncast server URL must be set when using auth")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/teris-io/shortid"
|
"github.com/teris-io/shortid"
|
||||||
)
|
)
|
||||||
@@ -70,8 +70,6 @@ func StartServerAuth(clientID, redirectURI, codeChallenge, state, me string) (*S
|
|||||||
// CompleteServerAuth will verify that the values provided in the final step
|
// CompleteServerAuth will verify that the values provided in the final step
|
||||||
// of the IndieAuth flow are correct, and return some basic profile info.
|
// of the IndieAuth flow are correct, and return some basic profile info.
|
||||||
func CompleteServerAuth(code, redirectURI, clientID string, codeVerifier string) (*ServerProfileResponse, error) {
|
func CompleteServerAuth(code, redirectURI, clientID string, codeVerifier string) (*ServerProfileResponse, error) {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
request, pending := pendingServerAuthRequests[code]
|
request, pending := pendingServerAuthRequests[code]
|
||||||
if !pending {
|
if !pending {
|
||||||
return nil, errors.New("no pending authentication request")
|
return nil, errors.New("no pending authentication request")
|
||||||
@@ -91,11 +89,11 @@ func CompleteServerAuth(code, redirectURI, clientID string, codeVerifier string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
response := ServerProfileResponse{
|
response := ServerProfileResponse{
|
||||||
Me: configRepository.GetServerURL(),
|
Me: data.GetServerURL(),
|
||||||
Profile: ServerProfile{
|
Profile: ServerProfile{
|
||||||
Name: configRepository.GetServerName(),
|
Name: data.GetServerName(),
|
||||||
URL: configRepository.GetServerURL(),
|
URL: data.GetServerURL(),
|
||||||
Photo: fmt.Sprintf("%s/%s", configRepository.GetServerURL(), configRepository.GetLogoPath()),
|
Photo: fmt.Sprintf("%s/%s", data.GetServerURL(), data.GetLogoPath()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ folderPath="webserver/handlers/generated"
|
|||||||
specPath="openapi.yaml"
|
specPath="openapi.yaml"
|
||||||
|
|
||||||
# validate scripts are installed
|
# validate scripts are installed
|
||||||
if ! command -v redocly &>/dev/null; then
|
if ! command -v swagger-cli &>/dev/null; then
|
||||||
echo "Please install \`redocly cli\` before running this script: npm install -g @redocly/cli"
|
echo "Please install \`swagger-cli\` before running this script"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ if ! command -v oapi-codegen &>/dev/null; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# validate schema
|
# validate schema
|
||||||
npx redocly lint $specPath
|
swagger-cli validate $specPath
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Open API specification is not valid"
|
echo "Open API specification is not valid"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import "path/filepath"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// StaticVersionNumber is the version of Owncast that is used when it's not overwritten via build-time settings.
|
// StaticVersionNumber is the version of Owncast that is used when it's not overwritten via build-time settings.
|
||||||
StaticVersionNumber = "0.2.2" // Shown when you build from develop
|
StaticVersionNumber = "0.2.0" // Shown when you build from develop
|
||||||
// FfmpegSuggestedVersion is the version of ffmpeg we suggest.
|
// FfmpegSuggestedVersion is the version of ffmpeg we suggest.
|
||||||
FfmpegSuggestedVersion = "v4.1.5" // Requires the v
|
FfmpegSuggestedVersion = "v4.1.5" // Requires the v
|
||||||
// DataDirectory is the directory we save data to.
|
// DataDirectory is the directory we save data to.
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/webserver/handlers/generated"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defaults will hold default configuration values.
|
// Defaults will hold default configuration values.
|
||||||
@@ -26,7 +25,7 @@ type Defaults struct {
|
|||||||
WebServerIP string
|
WebServerIP string
|
||||||
Name string
|
Name string
|
||||||
AdminPassword string
|
AdminPassword string
|
||||||
StreamKeys []generated.StreamKey
|
StreamKeys []models.StreamKey
|
||||||
|
|
||||||
StreamVariants []models.StreamOutputVariant
|
StreamVariants []models.StreamOutputVariant
|
||||||
|
|
||||||
@@ -44,16 +43,14 @@ type Defaults struct {
|
|||||||
|
|
||||||
// GetDefaults will return default configuration values.
|
// GetDefaults will return default configuration values.
|
||||||
func GetDefaults() Defaults {
|
func GetDefaults() Defaults {
|
||||||
defaultStreamKey := "abc123"
|
|
||||||
defaultStreamKeyComment := "Default stream key"
|
|
||||||
return Defaults{
|
return Defaults{
|
||||||
Name: "New Owncast Server",
|
Name: "New Owncast Server",
|
||||||
Summary: "This is a new live video streaming server powered by Owncast.",
|
Summary: "This is a new live video streaming server powered by Owncast.",
|
||||||
ServerWelcomeMessage: "",
|
ServerWelcomeMessage: "",
|
||||||
Logo: "logo.svg",
|
Logo: "logo.svg",
|
||||||
AdminPassword: "abc123",
|
AdminPassword: "abc123",
|
||||||
StreamKeys: []generated.StreamKey{
|
StreamKeys: []models.StreamKey{
|
||||||
{Key: &defaultStreamKey, Comment: &defaultStreamKeyComment},
|
{Key: "abc123", Comment: "Default stream key"},
|
||||||
},
|
},
|
||||||
Tags: []string{
|
Tags: []string{
|
||||||
"owncast",
|
"owncast",
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -23,8 +23,6 @@ var (
|
|||||||
func Start(getStatusFunc func() models.Status) error {
|
func Start(getStatusFunc func() models.Status) error {
|
||||||
setupPersistence()
|
setupPersistence()
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
getStatus = getStatusFunc
|
getStatus = getStatusFunc
|
||||||
_server = NewChat()
|
_server = NewChat()
|
||||||
|
|
||||||
@@ -37,7 +35,7 @@ func Start(getStatusFunc func() models.Status) error {
|
|||||||
Help: "The number of chat messages incremented over time.",
|
Help: "The number of chat messages incremented over time.",
|
||||||
ConstLabels: map[string]string{
|
ConstLabels: map[string]string{
|
||||||
"version": config.VersionNumber,
|
"version": config.VersionNumber,
|
||||||
"host": configRepository.GetServerURL(),
|
"host": data.GetServerURL(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
"github.com/owncast/owncast/services/geoip"
|
"github.com/owncast/owncast/services/geoip"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -133,9 +133,7 @@ func (c *Client) readPump() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if this message passes the optional language filter
|
// Check if this message passes the optional language filter
|
||||||
configRepository := configrepository.Get()
|
if data.GetChatSlurFilterEnabled() && !c.messageFilter.Allow(string(message)) {
|
||||||
|
|
||||||
if configRepository.GetChatSlurFilterEnabled() && !c.messageFilter.Allow(string(message)) {
|
|
||||||
c.sendAction("Sorry, that message contained language that is not allowed in this chat.")
|
c.sendAction("Sorry, that message contained language that is not allowed in this chat.")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -211,11 +209,9 @@ func (c *Client) close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) passesRateLimit() bool {
|
func (c *Client) passesRateLimit() bool {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
// If spam rate limiting is disabled, or the user is a moderator, always
|
// If spam rate limiting is disabled, or the user is a moderator, always
|
||||||
// allow the message.
|
// allow the message.
|
||||||
if !configRepository.GetChatSpamProtectionEnabled() || c.User.IsModerator() {
|
if !data.GetChatSpamProtectionEnabled() || c.User.IsModerator() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setSystemConcurrentConnectionLimit(limit uint64) {
|
func setSystemConcurrentConnectionLimit(limit int64) {
|
||||||
var rLimit syscall.Rlimit
|
var rLimit syscall.Rlimit
|
||||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
originalLimit := rLimit.Cur
|
originalLimit := rLimit.Cur
|
||||||
rLimit.Cur = limit
|
rLimit.Cur = uint64(limit)
|
||||||
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setSystemConcurrentConnectionLimit(limit uint64) {
|
func setSystemConcurrentConnectionLimit(limit int64) {
|
||||||
var rLimit syscall.Rlimit
|
var rLimit syscall.Rlimit
|
||||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
|
|
||||||
package chat
|
package chat
|
||||||
|
|
||||||
func setSystemConcurrentConnectionLimit(limit uint64) {}
|
func setSystemConcurrentConnectionLimit(limit int64) {}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/core/webhooks"
|
"github.com/owncast/owncast/core/webhooks"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
"github.com/owncast/owncast/persistence/userrepository"
|
"github.com/owncast/owncast/persistence/userrepository"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -22,12 +22,10 @@ func (s *Server) userNameChanged(eventData chatClientEvent) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
proposedUsername := receivedEvent.NewName
|
proposedUsername := receivedEvent.NewName
|
||||||
|
|
||||||
// Check if name is on the blocklist
|
// Check if name is on the blocklist
|
||||||
blocklist := configRepository.GetForbiddenUsernameList()
|
blocklist := data.GetForbiddenUsernameList()
|
||||||
|
|
||||||
// Names have a max length
|
// Names have a max length
|
||||||
proposedUsername = utils.MakeSafeStringOfLength(proposedUsername, config.MaxChatDisplayNameLength)
|
proposedUsername = utils.MakeSafeStringOfLength(proposedUsername, config.MaxChatDisplayNameLength)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/yuin/goldmark/extension"
|
"github.com/yuin/goldmark/extension"
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
"mvdan.cc/xurls/v2"
|
"mvdan.cc/xurls"
|
||||||
|
|
||||||
"github.com/owncast/owncast/core/data"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
@@ -220,7 +220,7 @@ func RenderMarkdown(raw string) string {
|
|||||||
[]byte("https:"),
|
[]byte("https:"),
|
||||||
}),
|
}),
|
||||||
extension.WithLinkifyURLRegexp(
|
extension.WithLinkifyURLRegexp(
|
||||||
xurls.Strict(),
|
xurls.Strict,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
emoji.New(
|
emoji.New(
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package events
|
package events
|
||||||
|
|
||||||
import (
|
import "github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FediverseEngagementEvent is a message displayed in chat on representing an action on the Fediverse.
|
// FediverseEngagementEvent is a message displayed in chat on representing an action on the Fediverse.
|
||||||
type FediverseEngagementEvent struct {
|
type FediverseEngagementEvent struct {
|
||||||
@@ -15,8 +13,6 @@ type FediverseEngagementEvent struct {
|
|||||||
|
|
||||||
// GetBroadcastPayload will return the object to send to all chat users.
|
// GetBroadcastPayload will return the object to send to all chat users.
|
||||||
func (e *FediverseEngagementEvent) GetBroadcastPayload() EventPayload {
|
func (e *FediverseEngagementEvent) GetBroadcastPayload() EventPayload {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
return EventPayload{
|
return EventPayload{
|
||||||
"id": e.ID,
|
"id": e.ID,
|
||||||
"timestamp": e.Timestamp,
|
"timestamp": e.Timestamp,
|
||||||
@@ -26,7 +22,7 @@ func (e *FediverseEngagementEvent) GetBroadcastPayload() EventPayload {
|
|||||||
"title": e.UserAccountName,
|
"title": e.UserAccountName,
|
||||||
"link": e.Link,
|
"link": e.Link,
|
||||||
"user": EventPayload{
|
"user": EventPayload{
|
||||||
"displayName": configRepository.GetServerName(),
|
"displayName": data.GetServerName(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package events
|
package events
|
||||||
|
|
||||||
import (
|
import "github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SystemMessageEvent is a message displayed in chat on behalf of the server.
|
// SystemMessageEvent is a message displayed in chat on behalf of the server.
|
||||||
type SystemMessageEvent struct {
|
type SystemMessageEvent struct {
|
||||||
@@ -12,15 +10,13 @@ type SystemMessageEvent struct {
|
|||||||
|
|
||||||
// GetBroadcastPayload will return the object to send to all chat users.
|
// GetBroadcastPayload will return the object to send to all chat users.
|
||||||
func (e *SystemMessageEvent) GetBroadcastPayload() EventPayload {
|
func (e *SystemMessageEvent) GetBroadcastPayload() EventPayload {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
return EventPayload{
|
return EventPayload{
|
||||||
"id": e.ID,
|
"id": e.ID,
|
||||||
"timestamp": e.Timestamp,
|
"timestamp": e.Timestamp,
|
||||||
"body": e.Body,
|
"body": e.Body,
|
||||||
"type": SystemMessageSent,
|
"type": SystemMessageSent,
|
||||||
"user": EventPayload{
|
"user": EventPayload{
|
||||||
"displayName": configRepository.GetServerName(),
|
"displayName": data.GetServerName(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
"github.com/owncast/owncast/core/data"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/authrepository"
|
|
||||||
"github.com/owncast/owncast/persistence/tables"
|
"github.com/owncast/owncast/persistence/tables"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -25,9 +24,7 @@ const (
|
|||||||
func setupPersistence() {
|
func setupPersistence() {
|
||||||
_datastore = data.GetDatastore()
|
_datastore = data.GetDatastore()
|
||||||
tables.CreateMessagesTable(_datastore.DB)
|
tables.CreateMessagesTable(_datastore.DB)
|
||||||
|
data.CreateBanIPTable(_datastore.DB)
|
||||||
authRepository := authrepository.Get()
|
|
||||||
authRepository.CreateBanIPTable(_datastore.DB)
|
|
||||||
|
|
||||||
chatDataPruner := time.NewTicker(5 * time.Minute)
|
chatDataPruner := time.NewTicker(5 * time.Minute)
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ import (
|
|||||||
|
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/core/webhooks"
|
"github.com/owncast/owncast/core/webhooks"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/authrepository"
|
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
"github.com/owncast/owncast/persistence/userrepository"
|
"github.com/owncast/owncast/persistence/userrepository"
|
||||||
"github.com/owncast/owncast/services/geoip"
|
"github.com/owncast/owncast/services/geoip"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
@@ -42,7 +41,7 @@ type Server struct {
|
|||||||
// a map of user IDs and timers that fire for chat part messages.
|
// a map of user IDs and timers that fire for chat part messages.
|
||||||
userPartedTimers map[string]*time.Ticker
|
userPartedTimers map[string]*time.Ticker
|
||||||
seq uint
|
seq uint
|
||||||
maxSocketConnectionLimit uint64
|
maxSocketConnectionLimit int64
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
@@ -96,9 +95,7 @@ func (s *Server) Addclient(conn *websocket.Conn, user *models.User, accessToken
|
|||||||
ConnectedAt: time.Now(),
|
ConnectedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
shouldSendJoinedMessages := data.GetChatJoinPartMessagesEnabled()
|
||||||
|
|
||||||
shouldSendJoinedMessages := configRepository.GetChatJoinPartMessagesEnabled()
|
|
||||||
|
|
||||||
// If there are existing clients connected for this user do not send
|
// If there are existing clients connected for this user do not send
|
||||||
// a user joined message. Do not put this under a mutex, as
|
// a user joined message. Do not put this under a mutex, as
|
||||||
@@ -189,10 +186,8 @@ func (s *Server) sendUserPartedMessage(c *Client) {
|
|||||||
userPartEvent.User = c.User
|
userPartEvent.User = c.User
|
||||||
userPartEvent.ClientID = c.Id
|
userPartEvent.ClientID = c.Id
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
// If part messages are disabled.
|
// If part messages are disabled.
|
||||||
if configRepository.GetChatJoinPartMessagesEnabled() {
|
if data.GetChatJoinPartMessagesEnabled() {
|
||||||
if err := s.Broadcast(userPartEvent.GetBroadcastPayload()); err != nil {
|
if err := s.Broadcast(userPartEvent.GetBroadcastPayload()); err != nil {
|
||||||
log.Errorln("error sending chat part message", err)
|
log.Errorln("error sending chat part message", err)
|
||||||
}
|
}
|
||||||
@@ -203,17 +198,14 @@ func (s *Server) sendUserPartedMessage(c *Client) {
|
|||||||
|
|
||||||
// HandleClientConnection is fired when a single client connects to the websocket.
|
// HandleClientConnection is fired when a single client connects to the websocket.
|
||||||
func (s *Server) HandleClientConnection(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) HandleClientConnection(w http.ResponseWriter, r *http.Request) {
|
||||||
configRepository := configrepository.Get()
|
if data.GetChatDisabled() {
|
||||||
authRepository := authrepository.Get()
|
|
||||||
|
|
||||||
if configRepository.GetChatDisabled() {
|
|
||||||
_, _ = w.Write([]byte(events.ChatDisabled))
|
_, _ = w.Write([]byte(events.ChatDisabled))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ipAddress := utils.GetIPAddressFromRequest(r)
|
ipAddress := utils.GetIPAddressFromRequest(r)
|
||||||
// Check if this client's IP address is banned. If so send a rejection.
|
// Check if this client's IP address is banned. If so send a rejection.
|
||||||
if blocked, err := authRepository.IsIPAddressBanned(ipAddress); blocked {
|
if blocked, err := data.IsIPAddressBanned(ipAddress); blocked {
|
||||||
log.Debugln("Client ip address has been blocked. Rejecting.")
|
log.Debugln("Client ip address has been blocked. Rejecting.")
|
||||||
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
@@ -223,7 +215,7 @@ func (s *Server) HandleClientConnection(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Limit concurrent chat connections
|
// Limit concurrent chat connections
|
||||||
if uint64(len(s.clients)) >= s.maxSocketConnectionLimit {
|
if int64(len(s.clients)) >= s.maxSocketConnectionLimit {
|
||||||
log.Warnln("rejecting incoming client connection as it exceeds the max client count of", s.maxSocketConnectionLimit)
|
log.Warnln("rejecting incoming client connection as it exceeds the max client count of", s.maxSocketConnectionLimit)
|
||||||
_, _ = w.Write([]byte(events.ErrorMaxConnectionsExceeded))
|
_, _ = w.Write([]byte(events.ErrorMaxConnectionsExceeded))
|
||||||
return
|
return
|
||||||
@@ -385,14 +377,12 @@ func SendActionToUser(userID string, text string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) eventReceived(event chatClientEvent) {
|
func (s *Server) eventReceived(event chatClientEvent) {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
c := event.client
|
c := event.client
|
||||||
u := c.User
|
u := c.User
|
||||||
|
|
||||||
// If established chat user only mode is enabled and the user is not old
|
// If established chat user only mode is enabled and the user is not old
|
||||||
// enough then reject this event and send them an informative message.
|
// enough then reject this event and send them an informative message.
|
||||||
if u != nil && configRepository.GetChatEstbalishedUsersOnlyMode() && time.Since(event.client.User.CreatedAt) < config.GetDefaults().ChatEstablishedUserModeTimeDuration && !u.IsModerator() {
|
if u != nil && data.GetChatEstbalishedUsersOnlyMode() && time.Since(event.client.User.CreatedAt) < config.GetDefaults().ChatEstablishedUserModeTimeDuration && !u.IsModerator() {
|
||||||
s.sendActionToClient(c, "You have not been an established chat participant long enough to take part in chat. Please enjoy the stream and try again later.")
|
s.sendActionToClient(c, "You have not been an established chat participant long enough to take part in chat. Please enjoy the stream and try again later.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -419,12 +409,10 @@ func (s *Server) eventReceived(event chatClientEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) sendWelcomeMessageToClient(c *Client) {
|
func (s *Server) sendWelcomeMessageToClient(c *Client) {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
// Add an artificial delay so people notice this message come in.
|
// Add an artificial delay so people notice this message come in.
|
||||||
time.Sleep(7 * time.Second)
|
time.Sleep(7 * time.Second)
|
||||||
|
|
||||||
welcomeMessage := utils.RenderSimpleMarkdown(configRepository.GetServerWelcomeMessage())
|
welcomeMessage := utils.RenderSimpleMarkdown(data.GetServerWelcomeMessage())
|
||||||
|
|
||||||
if welcomeMessage != "" {
|
if welcomeMessage != "" {
|
||||||
s.sendSystemMessageToClient(c, welcomeMessage)
|
s.sendSystemMessageToClient(c, welcomeMessage)
|
||||||
@@ -432,9 +420,7 @@ func (s *Server) sendWelcomeMessageToClient(c *Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) sendAllWelcomeMessage() {
|
func (s *Server) sendAllWelcomeMessage() {
|
||||||
configRepository := configrepository.Get()
|
welcomeMessage := utils.RenderSimpleMarkdown(data.GetServerWelcomeMessage())
|
||||||
|
|
||||||
welcomeMessage := utils.RenderSimpleMarkdown(configRepository.GetServerWelcomeMessage())
|
|
||||||
|
|
||||||
if welcomeMessage != "" {
|
if welcomeMessage != "" {
|
||||||
clientMessage := events.SystemMessageEvent{
|
clientMessage := events.SystemMessageEvent{
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getMaximumConcurrentConnectionLimit() uint64 {
|
func getMaximumConcurrentConnectionLimit() int64 {
|
||||||
var rLimit syscall.Rlimit
|
var rLimit syscall.Rlimit
|
||||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the limit to 70% of max so the machine doesn't die even if it's maxed out for some reason.
|
// Return the limit to 70% of max so the machine doesn't die even if it's maxed out for some reason.
|
||||||
proposedLimit := uint64(float32(rLimit.Max) * 0.7)
|
proposedLimit := int64(float32(rLimit.Max) * 0.7)
|
||||||
|
|
||||||
return proposedLimit
|
return proposedLimit
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
package chat
|
package chat
|
||||||
|
|
||||||
func getMaximumConcurrentConnectionLimit() uint64 {
|
func getMaximumConcurrentConnectionLimit() int64 {
|
||||||
// The maximum limit I can find for windows is 16,777,216
|
// The maximum limit I can find for windows is 16,777,216
|
||||||
// (essentially unlimited, but add the 0.7 multiplier as well to be
|
// (essentially unlimited, but add the 0.7 multiplier as well to be
|
||||||
// consistent with other systems)
|
// consistent with other systems)
|
||||||
|
|||||||
15
core/core.go
15
core/core.go
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/owncast/owncast/core/webhooks"
|
"github.com/owncast/owncast/core/webhooks"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/notifications"
|
"github.com/owncast/owncast/notifications"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
"github.com/owncast/owncast/persistence/tables"
|
"github.com/owncast/owncast/persistence/tables"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
"github.com/owncast/owncast/yp"
|
"github.com/owncast/owncast/yp"
|
||||||
@@ -35,10 +34,10 @@ var (
|
|||||||
// Start starts up the core processing.
|
// Start starts up the core processing.
|
||||||
func Start() error {
|
func Start() error {
|
||||||
resetDirectories()
|
resetDirectories()
|
||||||
configRepository := configrepository.Get()
|
|
||||||
// configRepository.PopulateDefaults()
|
|
||||||
|
|
||||||
if err := configRepository.VerifySettings(); err != nil {
|
data.PopulateDefaults()
|
||||||
|
|
||||||
|
if err := data.VerifySettings(); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -76,7 +75,7 @@ func Start() error {
|
|||||||
// start the rtmp server
|
// start the rtmp server
|
||||||
go rtmp.Start(setStreamAsConnected, setBroadcaster)
|
go rtmp.Start(setStreamAsConnected, setBroadcaster)
|
||||||
|
|
||||||
rtmpPort := configRepository.GetRTMPPortNumber()
|
rtmpPort := data.GetRTMPPortNumber()
|
||||||
if rtmpPort != 1935 {
|
if rtmpPort != 1935 {
|
||||||
log.Infof("RTMP is accepting inbound streams on port %d.", rtmpPort)
|
log.Infof("RTMP is accepting inbound streams on port %d.", rtmpPort)
|
||||||
}
|
}
|
||||||
@@ -114,8 +113,7 @@ func transitionToOfflineVideoStreamContent() {
|
|||||||
go _transcoder.Start(false)
|
go _transcoder.Start(false)
|
||||||
|
|
||||||
// Copy the logo to be the thumbnail
|
// Copy the logo to be the thumbnail
|
||||||
configRepository := configrepository.Get()
|
logo := data.GetLogoPath()
|
||||||
logo := configRepository.GetLogoPath()
|
|
||||||
dst := filepath.Join(config.TempDir, "thumbnail.jpg")
|
dst := filepath.Join(config.TempDir, "thumbnail.jpg")
|
||||||
if err = utils.Copy(filepath.Join("data", logo), dst); err != nil {
|
if err = utils.Copy(filepath.Join("data", logo), dst); err != nil {
|
||||||
log.Warnln(err)
|
log.Warnln(err)
|
||||||
@@ -132,8 +130,7 @@ func resetDirectories() {
|
|||||||
utils.CleanupDirectory(config.HLSStoragePath)
|
utils.CleanupDirectory(config.HLSStoragePath)
|
||||||
|
|
||||||
// Remove the previous thumbnail
|
// Remove the previous thumbnail
|
||||||
configRepository := configrepository.Get()
|
logo := data.GetLogoPath()
|
||||||
logo := configRepository.GetLogoPath()
|
|
||||||
if utils.DoesFileExists(logo) {
|
if utils.DoesFileExists(logo) {
|
||||||
err := utils.Copy(path.Join("data", logo), filepath.Join(config.DataDirectory, "thumbnail.jpg"))
|
err := utils.Copy(path.Join("data", logo), filepath.Join(config.DataDirectory, "thumbnail.jpg"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
13
core/data/activitypub.go
Normal file
13
core/data/activitypub.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
// GetFederatedInboxMap is a mapping between account names and their outbox.
|
||||||
|
func GetFederatedInboxMap() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
GetDefaultFederationUsername(): GetDefaultFederationUsername(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaultFederationUsername will return the username used for sending federation activities.
|
||||||
|
func GetDefaultFederationUsername() string {
|
||||||
|
return GetFederationUsername()
|
||||||
|
}
|
||||||
1027
core/data/config.go
Normal file
1027
core/data/config.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
package models
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -12,48 +12,48 @@ type ConfigEntry struct {
|
|||||||
Key string
|
Key string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigEntry) GetStringSlice() ([]string, error) {
|
func (c *ConfigEntry) getStringSlice() ([]string, error) {
|
||||||
decoder := c.GetDecoder()
|
decoder := c.getDecoder()
|
||||||
var result []string
|
var result []string
|
||||||
err := decoder.Decode(&result)
|
err := decoder.Decode(&result)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigEntry) GetStringMap() (map[string]string, error) {
|
func (c *ConfigEntry) getStringMap() (map[string]string, error) {
|
||||||
decoder := c.GetDecoder()
|
decoder := c.getDecoder()
|
||||||
var result map[string]string
|
var result map[string]string
|
||||||
err := decoder.Decode(&result)
|
err := decoder.Decode(&result)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigEntry) GetString() (string, error) {
|
func (c *ConfigEntry) getString() (string, error) {
|
||||||
decoder := c.GetDecoder()
|
decoder := c.getDecoder()
|
||||||
var result string
|
var result string
|
||||||
err := decoder.Decode(&result)
|
err := decoder.Decode(&result)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigEntry) GetNumber() (float64, error) {
|
func (c *ConfigEntry) getNumber() (float64, error) {
|
||||||
decoder := c.GetDecoder()
|
decoder := c.getDecoder()
|
||||||
var result float64
|
var result float64
|
||||||
err := decoder.Decode(&result)
|
err := decoder.Decode(&result)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigEntry) GetBool() (bool, error) {
|
func (c *ConfigEntry) getBool() (bool, error) {
|
||||||
decoder := c.GetDecoder()
|
decoder := c.getDecoder()
|
||||||
var result bool
|
var result bool
|
||||||
err := decoder.Decode(&result)
|
err := decoder.Decode(&result)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigEntry) GetObject(result interface{}) error {
|
func (c *ConfigEntry) getObject(result interface{}) error {
|
||||||
decoder := c.GetDecoder()
|
decoder := c.getDecoder()
|
||||||
err := decoder.Decode(result)
|
err := decoder.Decode(result)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigEntry) GetDecoder() *gob.Decoder {
|
func (c *ConfigEntry) getDecoder() *gob.Decoder {
|
||||||
valueBytes := c.Value.([]byte)
|
valueBytes := c.Value.([]byte)
|
||||||
decoder := gob.NewDecoder(bytes.NewBuffer(valueBytes))
|
decoder := gob.NewDecoder(bytes.NewBuffer(valueBytes))
|
||||||
return decoder
|
return decoder
|
||||||
23
core/data/crypto.go
Normal file
23
core/data/crypto.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
// GetPublicKey will return the public key.
|
||||||
|
func GetPublicKey() string {
|
||||||
|
value, _ := _datastore.GetString(publicKeyKey)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPublicKey will save the public key.
|
||||||
|
func SetPublicKey(key string) error {
|
||||||
|
return _datastore.SetString(publicKeyKey, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrivateKey will return the private key.
|
||||||
|
func GetPrivateKey() string {
|
||||||
|
value, _ := _datastore.GetString(privateKeyKey)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrivateKey will save the private key.
|
||||||
|
func SetPrivateKey(key string) error {
|
||||||
|
return _datastore.SetString(privateKeyKey, key)
|
||||||
|
}
|
||||||
@@ -75,7 +75,7 @@ func SetupPersistence(file string) error {
|
|||||||
_, _ = db.Exec("pragma temp_store = memory")
|
_, _ = db.Exec("pragma temp_store = memory")
|
||||||
_, _ = db.Exec("pragma wal_checkpoint(full)")
|
_, _ = db.Exec("pragma wal_checkpoint(full)")
|
||||||
|
|
||||||
tables.CreateWebhooksTable(db)
|
createWebhooksTable()
|
||||||
tables.CreateUsersTable(db)
|
tables.CreateUsersTable(db)
|
||||||
tables.CreateAccessTokenTable(db)
|
tables.CreateAccessTokenTable(db)
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/owncast/owncast/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
@@ -91,7 +89,7 @@ func TestCustomType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save config entry to the database
|
// Save config entry to the database
|
||||||
if err := _datastore.Save(models.ConfigEntry{&testStruct, testKey}); err != nil {
|
if err := _datastore.Save(ConfigEntry{&testStruct, testKey}); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +101,7 @@ func TestCustomType(t *testing.T) {
|
|||||||
|
|
||||||
// Get a typed struct out of it
|
// Get a typed struct out of it
|
||||||
var testResult TestStruct
|
var testResult TestStruct
|
||||||
if err := entryResult.GetObject(&testResult); err != nil {
|
if err := entryResult.getObject(&testResult); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +121,7 @@ func TestStringMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save config entry to the database
|
// Save config entry to the database
|
||||||
if err := _datastore.Save(models.ConfigEntry{Value: &testMap, Key: testKey}); err != nil {
|
if err := _datastore.Save(ConfigEntry{&testMap, testKey}); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +131,7 @@ func TestStringMap(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
testResult, err := entryResult.GetStringMap()
|
testResult, err := entryResult.getStringMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package configrepository
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/owncast/owncast/core/data"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/webserver/handlers/generated"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,8 +12,8 @@ const (
|
|||||||
datastoreValueVersionKey = "DATA_STORE_VERSION"
|
datastoreValueVersionKey = "DATA_STORE_VERSION"
|
||||||
)
|
)
|
||||||
|
|
||||||
func migrateDatastoreValues(datastore *data.Datastore, configRepository ConfigRepository) {
|
func migrateDatastoreValues(datastore *Datastore) {
|
||||||
currentVersion, _ := datastore.GetNumber(datastoreValueVersionKey)
|
currentVersion, _ := _datastore.GetNumber(datastoreValueVersionKey)
|
||||||
if currentVersion == 0 {
|
if currentVersion == 0 {
|
||||||
currentVersion = datastoreValuesVersion
|
currentVersion = datastoreValuesVersion
|
||||||
}
|
}
|
||||||
@@ -25,21 +24,21 @@ func migrateDatastoreValues(datastore *data.Datastore, configRepository ConfigRe
|
|||||||
case 0:
|
case 0:
|
||||||
migrateToDatastoreValues1(datastore)
|
migrateToDatastoreValues1(datastore)
|
||||||
case 1:
|
case 1:
|
||||||
migrateToDatastoreValues2(datastore, configRepository)
|
migrateToDatastoreValues2(datastore)
|
||||||
case 2:
|
case 2:
|
||||||
migrateToDatastoreValues3ServingEndpoint3(configRepository)
|
migrateToDatastoreValues3ServingEndpoint3(datastore)
|
||||||
case 3:
|
case 3:
|
||||||
migrateToDatastoreValues4(datastore, configRepository)
|
migrateToDatastoreValues4(datastore)
|
||||||
default:
|
default:
|
||||||
log.Fatalln("missing datastore values migration step")
|
log.Fatalln("missing datastore values migration step")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := datastore.SetNumber(datastoreValueVersionKey, datastoreValuesVersion); err != nil {
|
if err := _datastore.SetNumber(datastoreValueVersionKey, datastoreValuesVersion); err != nil {
|
||||||
log.Errorln("error setting datastore value version:", err)
|
log.Errorln("error setting datastore value version:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateToDatastoreValues1(datastore *data.Datastore) {
|
func migrateToDatastoreValues1(datastore *Datastore) {
|
||||||
// Migrate the forbidden usernames to be a slice instead of a string.
|
// Migrate the forbidden usernames to be a slice instead of a string.
|
||||||
forbiddenUsernamesString, _ := datastore.GetString(blockedUsernamesKey)
|
forbiddenUsernamesString, _ := datastore.GetString(blockedUsernamesKey)
|
||||||
if forbiddenUsernamesString != "" {
|
if forbiddenUsernamesString != "" {
|
||||||
@@ -59,29 +58,28 @@ func migrateToDatastoreValues1(datastore *data.Datastore) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateToDatastoreValues2(datastore *data.Datastore, configRepository ConfigRepository) {
|
func migrateToDatastoreValues2(datastore *Datastore) {
|
||||||
oldAdminPassword, _ := datastore.GetString("stream_key")
|
oldAdminPassword, _ := datastore.GetString("stream_key")
|
||||||
// Avoids double hashing the password
|
// Avoids double hashing the password
|
||||||
_ = datastore.SetString("admin_password_key", oldAdminPassword)
|
_ = datastore.SetString("admin_password_key", oldAdminPassword)
|
||||||
comment := "Default stream key"
|
_ = SetStreamKeys([]models.StreamKey{
|
||||||
_ = configRepository.SetStreamKeys([]generated.StreamKey{
|
{Key: oldAdminPassword, Comment: "Default stream key"},
|
||||||
{Key: &oldAdminPassword, Comment: &comment},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateToDatastoreValues3ServingEndpoint3(configRepository ConfigRepository) {
|
func migrateToDatastoreValues3ServingEndpoint3(_ *Datastore) {
|
||||||
s3Config := configRepository.GetS3Config()
|
s3Config := GetS3Config()
|
||||||
|
|
||||||
if !s3Config.Enabled {
|
if !s3Config.Enabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = configRepository.SetVideoServingEndpoint(s3Config.ServingEndpoint)
|
_ = SetVideoServingEndpoint(s3Config.ServingEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateToDatastoreValues4(datastore *data.Datastore, configRepository ConfigRepository) {
|
func migrateToDatastoreValues4(datastore *Datastore) {
|
||||||
unhashed_pass, _ := datastore.GetString("admin_password_key")
|
unhashed_pass, _ := datastore.GetString("admin_password_key")
|
||||||
err := configRepository.SetAdminPassword(unhashed_pass)
|
err := SetAdminPassword(unhashed_pass)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("error migrating admin password:", err)
|
log.Fatalln("error migrating admin password:", err)
|
||||||
}
|
}
|
||||||
54
core/data/defaults.go
Normal file
54
core/data/defaults.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/owncast/owncast/config"
|
||||||
|
"github.com/owncast/owncast/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HasPopulatedDefaults will determine if the defaults have been inserted into the database.
|
||||||
|
func HasPopulatedDefaults() bool {
|
||||||
|
hasPopulated, err := _datastore.GetBool("HAS_POPULATED_DEFAULTS")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return hasPopulated
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasPopulatedFederationDefaults() bool {
|
||||||
|
hasPopulated, err := _datastore.GetBool("HAS_POPULATED_FEDERATION_DEFAULTS")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return hasPopulated
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopulateDefaults will set default values in the database.
|
||||||
|
func PopulateDefaults() {
|
||||||
|
_datastore.warmCache()
|
||||||
|
|
||||||
|
defaults := config.GetDefaults()
|
||||||
|
|
||||||
|
if HasPopulatedDefaults() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = SetAdminPassword(defaults.AdminPassword)
|
||||||
|
_ = SetStreamKeys(defaults.StreamKeys)
|
||||||
|
_ = SetHTTPPortNumber(float64(defaults.WebServerPort))
|
||||||
|
_ = SetRTMPPortNumber(float64(defaults.RTMPServerPort))
|
||||||
|
_ = SetLogoPath(defaults.Logo)
|
||||||
|
_ = SetServerMetadataTags([]string{"owncast", "streaming"})
|
||||||
|
_ = SetServerSummary(defaults.Summary)
|
||||||
|
_ = SetServerWelcomeMessage("")
|
||||||
|
_ = SetServerName(defaults.Name)
|
||||||
|
_ = SetExtraPageBodyContent(defaults.PageBodyContent)
|
||||||
|
_ = SetFederationGoLiveMessage(defaults.FederationGoLiveMessage)
|
||||||
|
_ = SetSocialHandles([]models.SocialHandle{
|
||||||
|
{
|
||||||
|
Platform: "github",
|
||||||
|
URL: "https://github.com/owncast/owncast",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
_ = _datastore.SetBool("HAS_POPULATED_DEFAULTS", true)
|
||||||
|
}
|
||||||
@@ -1,5 +1,14 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/owncast/owncast/db"
|
||||||
|
"github.com/owncast/owncast/models"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
// GetMessagesCount will return the number of messages in the database.
|
// GetMessagesCount will return the number of messages in the database.
|
||||||
func GetMessagesCount() int64 {
|
func GetMessagesCount() int64 {
|
||||||
query := `SELECT COUNT(*) FROM messages`
|
query := `SELECT COUNT(*) FROM messages`
|
||||||
@@ -16,3 +25,58 @@ func GetMessagesCount() int64 {
|
|||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateBanIPTable will create the IP ban table if needed.
|
||||||
|
func CreateBanIPTable(db *sql.DB) {
|
||||||
|
createTableSQL := ` CREATE TABLE IF NOT EXISTS ip_bans (
|
||||||
|
"ip_address" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"notes" TEXT,
|
||||||
|
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);`
|
||||||
|
|
||||||
|
stmt, err := db.Prepare(createTableSQL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("error creating ip ban table", err)
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
if _, err := stmt.Exec(); err != nil {
|
||||||
|
log.Fatal("error creating ip ban table", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BanIPAddress will persist a new IP address ban to the datastore.
|
||||||
|
func BanIPAddress(address, note string) error {
|
||||||
|
return _datastore.GetQueries().BanIPAddress(context.Background(), db.BanIPAddressParams{
|
||||||
|
IpAddress: address,
|
||||||
|
Notes: sql.NullString{String: note, Valid: true},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIPAddressBanned will return if an IP address has been previously blocked.
|
||||||
|
func IsIPAddressBanned(address string) (bool, error) {
|
||||||
|
blocked, error := _datastore.GetQueries().IsIPAddressBlocked(context.Background(), address)
|
||||||
|
return blocked > 0, error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIPAddressBans will return all the banned IP addresses.
|
||||||
|
func GetIPAddressBans() ([]models.IPAddress, error) {
|
||||||
|
result, err := _datastore.GetQueries().GetIPAddressBans(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := []models.IPAddress{}
|
||||||
|
for _, ip := range result {
|
||||||
|
response = append(response, models.IPAddress{
|
||||||
|
IPAddress: ip.IpAddress,
|
||||||
|
Notes: ip.Notes.String,
|
||||||
|
CreatedAt: ip.CreatedAt.Time,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveIPAddressBan will remove a previously banned IP address.
|
||||||
|
func RemoveIPAddressBan(address string) error {
|
||||||
|
return _datastore.GetQueries().RemoveIPAddressBan(context.Background(), address)
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
// sqlite requires a blank import.
|
// sqlite requires a blank import.
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/owncast/owncast/config"
|
||||||
"github.com/owncast/owncast/db"
|
"github.com/owncast/owncast/db"
|
||||||
"github.com/owncast/owncast/models"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,8 +21,7 @@ type Datastore struct {
|
|||||||
DbLock *sync.Mutex
|
DbLock *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarmCache pre-caches all configuration values in memory.
|
func (ds *Datastore) warmCache() {
|
||||||
func (ds *Datastore) WarmCache() {
|
|
||||||
log.Traceln("Warming config value cache")
|
log.Traceln("Warming config value cache")
|
||||||
|
|
||||||
res, err := ds.DB.Query("SELECT key, value FROM datastore")
|
res, err := ds.DB.Query("SELECT key, value FROM datastore")
|
||||||
@@ -46,10 +46,10 @@ func (ds *Datastore) GetQueries() *db.Queries {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get will query the database for the key and return the entry.
|
// Get will query the database for the key and return the entry.
|
||||||
func (ds *Datastore) Get(key string) (models.ConfigEntry, error) {
|
func (ds *Datastore) Get(key string) (ConfigEntry, error) {
|
||||||
cachedValue, err := ds.GetCachedValue(key)
|
cachedValue, err := ds.GetCachedValue(key)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return models.ConfigEntry{
|
return ConfigEntry{
|
||||||
Key: key,
|
Key: key,
|
||||||
Value: cachedValue,
|
Value: cachedValue,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -60,10 +60,10 @@ func (ds *Datastore) Get(key string) (models.ConfigEntry, error) {
|
|||||||
|
|
||||||
row := ds.DB.QueryRow("SELECT key, value FROM datastore WHERE key = ? LIMIT 1", key)
|
row := ds.DB.QueryRow("SELECT key, value FROM datastore WHERE key = ? LIMIT 1", key)
|
||||||
if err := row.Scan(&resultKey, &resultValue); err != nil {
|
if err := row.Scan(&resultKey, &resultValue); err != nil {
|
||||||
return models.ConfigEntry{}, err
|
return ConfigEntry{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := models.ConfigEntry{
|
result := ConfigEntry{
|
||||||
Key: resultKey,
|
Key: resultKey,
|
||||||
Value: resultValue,
|
Value: resultValue,
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ func (ds *Datastore) Get(key string) (models.ConfigEntry, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save will save the ConfigEntry to the database.
|
// Save will save the ConfigEntry to the database.
|
||||||
func (ds *Datastore) Save(e models.ConfigEntry) error {
|
func (ds *Datastore) Save(e ConfigEntry) error {
|
||||||
ds.DbLock.Lock()
|
ds.DbLock.Lock()
|
||||||
defer ds.DbLock.Unlock()
|
defer ds.DbLock.Unlock()
|
||||||
|
|
||||||
@@ -93,6 +93,7 @@ func (ds *Datastore) Save(e models.ConfigEntry) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = stmt.Exec(e.Key, dataGob.Bytes())
|
_, err = stmt.Exec(e.Key, dataGob.Bytes())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -120,6 +121,26 @@ func (ds *Datastore) Setup() {
|
|||||||
);`
|
);`
|
||||||
|
|
||||||
ds.MustExec(createTableSQL)
|
ds.MustExec(createTableSQL)
|
||||||
|
|
||||||
|
if !HasPopulatedDefaults() {
|
||||||
|
PopulateDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasPopulatedFederationDefaults() {
|
||||||
|
if err := SetFederationGoLiveMessage(config.GetDefaults().FederationGoLiveMessage); err != nil {
|
||||||
|
log.Errorln(err)
|
||||||
|
}
|
||||||
|
if err := _datastore.SetBool("HAS_POPULATED_FEDERATION_DEFAULTS", true); err != nil {
|
||||||
|
log.Errorln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the server initialization date if needed.
|
||||||
|
if hasSetInitDate, _ := GetServerInitTime(); hasSetInitDate == nil || !hasSetInitDate.Valid {
|
||||||
|
_ = SetServerInitTime(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
migrateDatastoreValues(_datastore)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset will delete all config entries in the datastore and start over.
|
// Reset will delete all config entries in the datastore and start over.
|
||||||
@@ -135,6 +156,8 @@ func (ds *Datastore) Reset() {
|
|||||||
if _, err = stmt.Exec(); err != nil {
|
if _, err = stmt.Exec(); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PopulateDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDatastore returns the shared instance of the owncast datastore.
|
// GetDatastore returns the shared instance of the owncast datastore.
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import "github.com/owncast/owncast/models"
|
|
||||||
|
|
||||||
// GetStringSlice will return the string slice value for a key.
|
// GetStringSlice will return the string slice value for a key.
|
||||||
func (ds *Datastore) GetStringSlice(key string) ([]string, error) {
|
func (ds *Datastore) GetStringSlice(key string) ([]string, error) {
|
||||||
configEntry, err := ds.Get(key)
|
configEntry, err := ds.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{}, err
|
return []string{}, err
|
||||||
}
|
}
|
||||||
return configEntry.GetStringSlice()
|
return configEntry.getStringSlice()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetStringSlice will set the string slice value for a key.
|
// SetStringSlice will set the string slice value for a key.
|
||||||
func (ds *Datastore) SetStringSlice(key string, value []string) error {
|
func (ds *Datastore) SetStringSlice(key string, value []string) error {
|
||||||
configEntry := models.ConfigEntry{Value: value, Key: key}
|
configEntry := ConfigEntry{value, key}
|
||||||
return ds.Save(configEntry)
|
return ds.Save(configEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,12 +21,12 @@ func (ds *Datastore) GetString(key string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return configEntry.GetString()
|
return configEntry.getString()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetString will set the string value for a key.
|
// SetString will set the string value for a key.
|
||||||
func (ds *Datastore) SetString(key string, value string) error {
|
func (ds *Datastore) SetString(key string, value string) error {
|
||||||
configEntry := models.ConfigEntry{Value: value, Key: key}
|
configEntry := ConfigEntry{value, key}
|
||||||
return ds.Save(configEntry)
|
return ds.Save(configEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,12 +36,12 @@ func (ds *Datastore) GetNumber(key string) (float64, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return configEntry.GetNumber()
|
return configEntry.getNumber()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNumber will set the numeric value for a key.
|
// SetNumber will set the numeric value for a key.
|
||||||
func (ds *Datastore) SetNumber(key string, value float64) error {
|
func (ds *Datastore) SetNumber(key string, value float64) error {
|
||||||
configEntry := models.ConfigEntry{Value: value, Key: key}
|
configEntry := ConfigEntry{value, key}
|
||||||
return ds.Save(configEntry)
|
return ds.Save(configEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,12 +51,12 @@ func (ds *Datastore) GetBool(key string) (bool, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return configEntry.GetBool()
|
return configEntry.getBool()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBool will set the boolean value for a key.
|
// SetBool will set the boolean value for a key.
|
||||||
func (ds *Datastore) SetBool(key string, value bool) error {
|
func (ds *Datastore) SetBool(key string, value bool) error {
|
||||||
configEntry := models.ConfigEntry{Value: value, Key: key}
|
configEntry := ConfigEntry{value, key}
|
||||||
return ds.Save(configEntry)
|
return ds.Save(configEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,11 +66,11 @@ func (ds *Datastore) GetStringMap(key string) (map[string]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return map[string]string{}, err
|
return map[string]string{}, err
|
||||||
}
|
}
|
||||||
return configEntry.GetStringMap()
|
return configEntry.getStringMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetStringMap will set the string map value for a key.
|
// SetStringMap will set the string map value for a key.
|
||||||
func (ds *Datastore) SetStringMap(key string, value map[string]string) error {
|
func (ds *Datastore) SetStringMap(key string, value map[string]string) error {
|
||||||
configEntry := models.ConfigEntry{Value: value, Key: key}
|
configEntry := ConfigEntry{value, key}
|
||||||
return ds.Save(configEntry)
|
return ds.Save(configEntry)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package webhookrepository
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -6,51 +6,38 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/owncast/owncast/core/data"
|
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebhookRepository interface {
|
func createWebhooksTable() {
|
||||||
InsertWebhook(url string, events []models.EventType) (int, error)
|
log.Traceln("Creating webhooks table...")
|
||||||
DeleteWebhook(id int) error
|
|
||||||
GetWebhooksForEvent(event models.EventType) []models.Webhook
|
|
||||||
GetWebhooks() ([]models.Webhook, error)
|
|
||||||
SetWebhookAsUsed(webhook models.Webhook) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type SqlWebhookRepository struct {
|
createTableSQL := `CREATE TABLE IF NOT EXISTS webhooks (
|
||||||
datastore *data.Datastore
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
}
|
"url" string NOT NULL,
|
||||||
|
"events" TEXT NOT NULL,
|
||||||
|
"timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"last_used" DATETIME
|
||||||
|
);`
|
||||||
|
|
||||||
// NOTE: This is temporary during the transition period.
|
stmt, err := _db.Prepare(createTableSQL)
|
||||||
var temporaryGlobalInstance WebhookRepository
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
// Get will return the user repository.
|
|
||||||
func Get() WebhookRepository {
|
|
||||||
if temporaryGlobalInstance == nil {
|
|
||||||
i := New(data.GetDatastore())
|
|
||||||
temporaryGlobalInstance = i
|
|
||||||
}
|
}
|
||||||
return temporaryGlobalInstance
|
defer stmt.Close()
|
||||||
}
|
if _, err = stmt.Exec(); err != nil {
|
||||||
|
log.Warnln(err)
|
||||||
// New will create a new instance of the UserRepository.
|
|
||||||
func New(datastore *data.Datastore) WebhookRepository {
|
|
||||||
r := SqlWebhookRepository{
|
|
||||||
datastore: datastore,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertWebhook will add a new webhook to the database.
|
// InsertWebhook will add a new webhook to the database.
|
||||||
func (r *SqlWebhookRepository) InsertWebhook(url string, events []models.EventType) (int, error) {
|
func InsertWebhook(url string, events []models.EventType) (int, error) {
|
||||||
log.Traceln("Adding new webhook")
|
log.Traceln("Adding new webhook")
|
||||||
|
|
||||||
eventsString := strings.Join(events, ",")
|
eventsString := strings.Join(events, ",")
|
||||||
|
|
||||||
tx, err := r.datastore.DB.Begin()
|
tx, err := _db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -78,10 +65,10 @@ func (r *SqlWebhookRepository) InsertWebhook(url string, events []models.EventTy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteWebhook will delete a webhook from the database.
|
// DeleteWebhook will delete a webhook from the database.
|
||||||
func (r *SqlWebhookRepository) DeleteWebhook(id int) error {
|
func DeleteWebhook(id int) error {
|
||||||
log.Traceln("Deleting webhook")
|
log.Traceln("Deleting webhook")
|
||||||
|
|
||||||
tx, err := r.datastore.DB.Begin()
|
tx, err := _db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -109,7 +96,7 @@ func (r *SqlWebhookRepository) DeleteWebhook(id int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetWebhooksForEvent will return all of the webhooks that want to be notified about an event type.
|
// GetWebhooksForEvent will return all of the webhooks that want to be notified about an event type.
|
||||||
func (r *SqlWebhookRepository) GetWebhooksForEvent(event models.EventType) []models.Webhook {
|
func GetWebhooksForEvent(event models.EventType) []models.Webhook {
|
||||||
webhooks := make([]models.Webhook, 0)
|
webhooks := make([]models.Webhook, 0)
|
||||||
|
|
||||||
query := `SELECT * FROM (
|
query := `SELECT * FROM (
|
||||||
@@ -124,9 +111,9 @@ func (r *SqlWebhookRepository) GetWebhooksForEvent(event models.EventType) []mod
|
|||||||
SELECT id, url, event
|
SELECT id, url, event
|
||||||
FROM split
|
FROM split
|
||||||
WHERE event <> ''
|
WHERE event <> ''
|
||||||
) AS webhook WHERE event IS ?`
|
) AS webhook WHERE event IS "` + event + `"`
|
||||||
|
|
||||||
rows, err := r.datastore.DB.Query(query, event)
|
rows, err := _db.Query(query)
|
||||||
if err != nil || rows.Err() != nil {
|
if err != nil || rows.Err() != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -153,12 +140,12 @@ func (r *SqlWebhookRepository) GetWebhooksForEvent(event models.EventType) []mod
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetWebhooks will return all the webhooks.
|
// GetWebhooks will return all the webhooks.
|
||||||
func (r *SqlWebhookRepository) GetWebhooks() ([]models.Webhook, error) { //nolint
|
func GetWebhooks() ([]models.Webhook, error) { //nolint
|
||||||
webhooks := make([]models.Webhook, 0)
|
webhooks := make([]models.Webhook, 0)
|
||||||
|
|
||||||
query := "SELECT * FROM webhooks"
|
query := "SELECT * FROM webhooks"
|
||||||
|
|
||||||
rows, err := r.datastore.DB.Query(query)
|
rows, err := _db.Query(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return webhooks, err
|
return webhooks, err
|
||||||
}
|
}
|
||||||
@@ -206,8 +193,8 @@ func (r *SqlWebhookRepository) GetWebhooks() ([]models.Webhook, error) { //nolin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetWebhookAsUsed will update the last used time for a webhook.
|
// SetWebhookAsUsed will update the last used time for a webhook.
|
||||||
func (r *SqlWebhookRepository) SetWebhookAsUsed(webhook models.Webhook) error {
|
func SetWebhookAsUsed(webhook models.Webhook) error {
|
||||||
tx, err := r.datastore.DB.Begin()
|
tx, err := _db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -12,9 +12,8 @@ import (
|
|||||||
|
|
||||||
"github.com/nareix/joy5/format/rtmp"
|
"github.com/nareix/joy5/format/rtmp"
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
"github.com/owncast/owncast/webserver/handlers/generated"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _hasInboundRTMPConnection = false
|
var _hasInboundRTMPConnection = false
|
||||||
@@ -34,9 +33,7 @@ func Start(setStreamAsConnected func(*io.PipeReader), setBroadcaster func(models
|
|||||||
_setStreamAsConnected = setStreamAsConnected
|
_setStreamAsConnected = setStreamAsConnected
|
||||||
_setBroadcaster = setBroadcaster
|
_setBroadcaster = setBroadcaster
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
port := data.GetRTMPPortNumber()
|
||||||
|
|
||||||
port := configRepository.GetRTMPPortNumber()
|
|
||||||
s := rtmp.NewServer()
|
s := rtmp.NewServer()
|
||||||
var lis net.Listener
|
var lis net.Listener
|
||||||
var err error
|
var err error
|
||||||
@@ -81,18 +78,16 @@ func HandleConn(c *rtmp.Conn, nc net.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
accessGranted := false
|
accessGranted := false
|
||||||
validStreamingKeys := configRepository.GetStreamKeys()
|
validStreamingKeys := data.GetStreamKeys()
|
||||||
|
|
||||||
// If a stream key override was specified then use that instead.
|
// If a stream key override was specified then use that instead.
|
||||||
if config.TemporaryStreamKey != "" {
|
if config.TemporaryStreamKey != "" {
|
||||||
validStreamingKeys = []generated.StreamKey{{Key: &config.TemporaryStreamKey}}
|
validStreamingKeys = []models.StreamKey{{Key: config.TemporaryStreamKey}}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, key := range validStreamingKeys {
|
for _, key := range validStreamingKeys {
|
||||||
if key.Key != nil && secretMatch(*key.Key, c.URL.Path) {
|
if secretMatch(key.Key, c.URL.Path) {
|
||||||
accessGranted = true
|
accessGranted = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
"github.com/owncast/owncast/services/geoip"
|
"github.com/owncast/owncast/services/geoip"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,8 +48,7 @@ func IsStreamConnected() bool {
|
|||||||
// Kind of a hack. It takes a handful of seconds between a RTMP connection and when HLS data is available.
|
// Kind of a hack. It takes a handful of seconds between a RTMP connection and when HLS data is available.
|
||||||
// So account for that with an artificial buffer of four segments.
|
// So account for that with an artificial buffer of four segments.
|
||||||
timeSinceLastConnected := time.Since(_stats.LastConnectTime.Time).Seconds()
|
timeSinceLastConnected := time.Since(_stats.LastConnectTime.Time).Seconds()
|
||||||
configRepository := configrepository.Get()
|
waitTime := math.Max(float64(data.GetStreamLatencyLevel().SecondsPerSegment)*3.0, 7)
|
||||||
waitTime := math.Max(float64(configRepository.GetStreamLatencyLevel().SecondsPerSegment)*3.0, 7)
|
|
||||||
if timeSinceLastConnected < waitTime {
|
if timeSinceLastConnected < waitTime {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -76,7 +75,7 @@ func SetViewerActive(viewer *models.Viewer) {
|
|||||||
l.Lock()
|
l.Lock()
|
||||||
defer l.Unlock()
|
defer l.Unlock()
|
||||||
|
|
||||||
// Asynchronously, optionally, fetch GeoIP configRepository.
|
// Asynchronously, optionally, fetch GeoIP data.
|
||||||
go func(viewer *models.Viewer) {
|
go func(viewer *models.Viewer) {
|
||||||
viewer.Geo = _geoIPClient.GetGeoFromIP(viewer.IPAddress)
|
viewer.Geo = _geoIPClient.GetGeoFromIP(viewer.IPAddress)
|
||||||
}(viewer)
|
}(viewer)
|
||||||
@@ -112,29 +111,27 @@ func pruneViewerCount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func saveStats() {
|
func saveStats() {
|
||||||
configRepository := configrepository.Get()
|
if err := data.SetPeakOverallViewerCount(_stats.OverallMaxViewerCount); err != nil {
|
||||||
if err := configRepository.SetPeakOverallViewerCount(_stats.OverallMaxViewerCount); err != nil {
|
|
||||||
log.Errorln("error saving viewer count", err)
|
log.Errorln("error saving viewer count", err)
|
||||||
}
|
}
|
||||||
if err := configRepository.SetPeakSessionViewerCount(_stats.SessionMaxViewerCount); err != nil {
|
if err := data.SetPeakSessionViewerCount(_stats.SessionMaxViewerCount); err != nil {
|
||||||
log.Errorln("error saving viewer count", err)
|
log.Errorln("error saving viewer count", err)
|
||||||
}
|
}
|
||||||
if _stats.LastDisconnectTime != nil && _stats.LastDisconnectTime.Valid {
|
if _stats.LastDisconnectTime != nil && _stats.LastDisconnectTime.Valid {
|
||||||
if err := configRepository.SetLastDisconnectTime(_stats.LastDisconnectTime.Time); err != nil {
|
if err := data.SetLastDisconnectTime(_stats.LastDisconnectTime.Time); err != nil {
|
||||||
log.Errorln("error saving disconnect time", err)
|
log.Errorln("error saving disconnect time", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSavedStats() models.Stats {
|
func getSavedStats() models.Stats {
|
||||||
configRepository := configrepository.Get()
|
savedLastDisconnectTime, _ := data.GetLastDisconnectTime()
|
||||||
savedLastDisconnectTime, _ := configRepository.GetLastDisconnectTime()
|
|
||||||
|
|
||||||
result := models.Stats{
|
result := models.Stats{
|
||||||
ChatClients: make(map[string]models.Client),
|
ChatClients: make(map[string]models.Client),
|
||||||
Viewers: make(map[string]*models.Viewer),
|
Viewers: make(map[string]*models.Viewer),
|
||||||
SessionMaxViewerCount: configRepository.GetPeakSessionViewerCount(),
|
SessionMaxViewerCount: data.GetPeakSessionViewerCount(),
|
||||||
OverallMaxViewerCount: configRepository.GetPeakOverallViewerCount(),
|
OverallMaxViewerCount: data.GetPeakOverallViewerCount(),
|
||||||
LastDisconnectTime: savedLastDisconnectTime,
|
LastDisconnectTime: savedLastDisconnectTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetStatus gets the status of the system.
|
// GetStatus gets the status of the system.
|
||||||
@@ -17,7 +17,6 @@ func GetStatus() models.Status {
|
|||||||
viewerCount = len(_stats.Viewers)
|
viewerCount = len(_stats.Viewers)
|
||||||
}
|
}
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
|
||||||
return models.Status{
|
return models.Status{
|
||||||
Online: IsStreamConnected(),
|
Online: IsStreamConnected(),
|
||||||
ViewerCount: viewerCount,
|
ViewerCount: viewerCount,
|
||||||
@@ -26,7 +25,7 @@ func GetStatus() models.Status {
|
|||||||
LastDisconnectTime: _stats.LastDisconnectTime,
|
LastDisconnectTime: _stats.LastDisconnectTime,
|
||||||
LastConnectTime: _stats.LastConnectTime,
|
LastConnectTime: _stats.LastConnectTime,
|
||||||
VersionNumber: config.VersionNumber,
|
VersionNumber: config.VersionNumber,
|
||||||
StreamTitle: configRepository.GetStreamTitle(),
|
StreamTitle: data.GetStreamTitle(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/core/storageproviders"
|
"github.com/owncast/owncast/core/storageproviders"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupStorage() error {
|
func setupStorage() error {
|
||||||
configRepository := configrepository.Get()
|
s3Config := data.GetS3Config()
|
||||||
s3Config := configRepository.GetS3Config()
|
|
||||||
|
|
||||||
if s3Config.Enabled {
|
if s3Config.Enabled {
|
||||||
_storage = storageproviders.NewS3Storage()
|
_storage = storageproviders.NewS3Storage()
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LocalStorage represents an instance of the local storage provider for HLS video.
|
// LocalStorage represents an instance of the local storage provider for HLS video.
|
||||||
@@ -21,8 +22,7 @@ func NewLocalStorage() *LocalStorage {
|
|||||||
|
|
||||||
// Setup configures this storage provider.
|
// Setup configures this storage provider.
|
||||||
func (s *LocalStorage) Setup() error {
|
func (s *LocalStorage) Setup() error {
|
||||||
configRepository := configrepository.Get()
|
s.host = data.GetVideoServingEndpoint()
|
||||||
s.host = configRepository.GetVideoServingEndpoint()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,8 +63,7 @@ func (s *LocalStorage) Save(filePath string, retryCount int) (string, error) {
|
|||||||
// Cleanup will remove old files from the storage provider.
|
// Cleanup will remove old files from the storage provider.
|
||||||
func (s *LocalStorage) Cleanup() error {
|
func (s *LocalStorage) Cleanup() error {
|
||||||
// Determine how many files we should keep on disk
|
// Determine how many files we should keep on disk
|
||||||
configRepository := configrepository.Get()
|
maxNumber := data.GetStreamLatencyLevel().SegmentCount
|
||||||
maxNumber := configRepository.GetStreamLatencyLevel().SegmentCount
|
|
||||||
buffer := 10
|
buffer := 10
|
||||||
return localCleanup(maxNumber + buffer)
|
return localCleanup(maxNumber + buffer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -64,9 +64,9 @@ func NewS3Storage() *S3Storage {
|
|||||||
// Setup sets up the s3 storage for saving the video to s3.
|
// Setup sets up the s3 storage for saving the video to s3.
|
||||||
func (s *S3Storage) Setup() error {
|
func (s *S3Storage) Setup() error {
|
||||||
log.Trace("Setting up S3 for external storage of video...")
|
log.Trace("Setting up S3 for external storage of video...")
|
||||||
configRepository := configrepository.Get()
|
|
||||||
s3Config := configRepository.GetS3Config()
|
s3Config := data.GetS3Config()
|
||||||
customVideoServingEndpoint := configRepository.GetVideoServingEndpoint()
|
customVideoServingEndpoint := data.GetVideoServingEndpoint()
|
||||||
|
|
||||||
if customVideoServingEndpoint != "" {
|
if customVideoServingEndpoint != "" {
|
||||||
s.host = customVideoServingEndpoint
|
s.host = customVideoServingEndpoint
|
||||||
@@ -106,9 +106,8 @@ func (s *S3Storage) SegmentWritten(localFilePath string) {
|
|||||||
averagePerformance := utils.GetAveragePerformance(performanceMonitorKey)
|
averagePerformance := utils.GetAveragePerformance(performanceMonitorKey)
|
||||||
|
|
||||||
// Warn the user about long-running save operations
|
// Warn the user about long-running save operations
|
||||||
configRepository := configrepository.Get()
|
|
||||||
if averagePerformance != 0 {
|
if averagePerformance != 0 {
|
||||||
if averagePerformance > float64(configRepository.GetStreamLatencyLevel().SecondsPerSegment)*0.9 {
|
if averagePerformance > float64(data.GetStreamLatencyLevel().SecondsPerSegment)*0.9 {
|
||||||
log.Warnln("Possible slow uploads: average upload S3 save duration", averagePerformance, "s. troubleshoot this issue by visiting https://owncast.online/docs/troubleshooting/")
|
log.Warnln("Possible slow uploads: average upload S3 save duration", averagePerformance, "s. troubleshoot this issue by visiting https://owncast.online/docs/troubleshooting/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,8 +220,7 @@ func (s *S3Storage) Cleanup() error {
|
|||||||
// RemoteCleanup will remove old files from the remote storage provider.
|
// RemoteCleanup will remove old files from the remote storage provider.
|
||||||
func (s *S3Storage) RemoteCleanup() error {
|
func (s *S3Storage) RemoteCleanup() error {
|
||||||
// Determine how many files we should keep on S3 storage
|
// Determine how many files we should keep on S3 storage
|
||||||
configRepository := configrepository.Get()
|
maxNumber := data.GetStreamLatencyLevel().SegmentCount
|
||||||
maxNumber := configRepository.GetStreamLatencyLevel().SegmentCount
|
|
||||||
buffer := 20
|
buffer := 20
|
||||||
|
|
||||||
keys, err := s.getDeletableVideoSegmentsWithOffset(maxNumber + buffer)
|
keys, err := s.getDeletableVideoSegmentsWithOffset(maxNumber + buffer)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/owncast/owncast/core/webhooks"
|
"github.com/owncast/owncast/core/webhooks"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/notifications"
|
"github.com/owncast/owncast/notifications"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,11 +39,9 @@ func setStreamAsConnected(rtmpOut *io.PipeReader) {
|
|||||||
_stats.LastConnectTime = &now
|
_stats.LastConnectTime = &now
|
||||||
_stats.SessionMaxViewerCount = 0
|
_stats.SessionMaxViewerCount = 0
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
_currentBroadcast = &models.CurrentBroadcast{
|
_currentBroadcast = &models.CurrentBroadcast{
|
||||||
LatencyLevel: configRepository.GetStreamLatencyLevel(),
|
LatencyLevel: data.GetStreamLatencyLevel(),
|
||||||
OutputSettings: configRepository.GetStreamOutputVariants(),
|
OutputSettings: data.GetStreamOutputVariants(),
|
||||||
}
|
}
|
||||||
|
|
||||||
StopOfflineCleanupTimer()
|
StopOfflineCleanupTimer()
|
||||||
@@ -72,7 +69,7 @@ func setStreamAsConnected(rtmpOut *io.PipeReader) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
go webhooks.SendStreamStatusEvent(models.StreamStarted)
|
go webhooks.SendStreamStatusEvent(models.StreamStarted)
|
||||||
selectedThumbnailVideoQualityIndex, isVideoPassthrough := configRepository.FindHighestVideoQualityIndex(_currentBroadcast.OutputSettings)
|
selectedThumbnailVideoQualityIndex, isVideoPassthrough := data.FindHighestVideoQualityIndex(_currentBroadcast.OutputSettings)
|
||||||
transcoder.StartThumbnailGenerator(segmentPath, selectedThumbnailVideoQualityIndex, isVideoPassthrough)
|
transcoder.StartThumbnailGenerator(segmentPath, selectedThumbnailVideoQualityIndex, isVideoPassthrough)
|
||||||
|
|
||||||
_ = chat.SendSystemAction("Stay tuned, the stream is **starting**!", true)
|
_ = chat.SendSystemAction("Stay tuned, the stream is **starting**!", true)
|
||||||
@@ -179,9 +176,8 @@ func startLiveStreamNotificationsTimer() context.CancelFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
configRepository := configrepository.Get()
|
|
||||||
// Send Fediverse message.
|
// Send Fediverse message.
|
||||||
if configRepository.GetFederationEnabled() {
|
if data.GetFederationEnabled() {
|
||||||
log.Traceln("Sending Federated Go Live message.")
|
log.Traceln("Sending Federated Go Live message.")
|
||||||
if err := activitypub.SendLive(); err != nil {
|
if err := activitypub.SendLive(); err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ var supportedCodecs = map[string]string{
|
|||||||
(&Libx264Codec{}).Name(): "libx264",
|
(&Libx264Codec{}).Name(): "libx264",
|
||||||
(&OmxCodec{}).Name(): "omx",
|
(&OmxCodec{}).Name(): "omx",
|
||||||
(&VaapiCodec{}).Name(): "vaapi",
|
(&VaapiCodec{}).Name(): "vaapi",
|
||||||
(&QuicksyncCodec{}).Name(): "qsv",
|
|
||||||
(&NvencCodec{}).Name(): "NVIDIA nvenc",
|
(&NvencCodec{}).Name(): "NVIDIA nvenc",
|
||||||
(&VideoToolboxCodec{}).Name(): "videotoolbox",
|
(&VideoToolboxCodec{}).Name(): "videotoolbox",
|
||||||
}
|
}
|
||||||
@@ -192,7 +191,7 @@ func (c *VaapiCodec) GlobalFlags() string {
|
|||||||
|
|
||||||
// PixelFormat is the pixel format required for this codec.
|
// PixelFormat is the pixel format required for this codec.
|
||||||
func (c *VaapiCodec) PixelFormat() string {
|
func (c *VaapiCodec) PixelFormat() string {
|
||||||
return "vaapi"
|
return "vaapi_vld"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scaler is the scaler used for resizing the video in the transcoder.
|
// Scaler is the scaler used for resizing the video in the transcoder.
|
||||||
@@ -202,7 +201,7 @@ func (c *VaapiCodec) Scaler() string {
|
|||||||
|
|
||||||
// ExtraFilters are the extra filters required for this codec in the transcoder.
|
// ExtraFilters are the extra filters required for this codec in the transcoder.
|
||||||
func (c *VaapiCodec) ExtraFilters() string {
|
func (c *VaapiCodec) ExtraFilters() string {
|
||||||
return "hwupload=extra_hw_frames=64,format=vaapi"
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtraArguments are the extra arguments used with this codec in the transcoder.
|
// ExtraArguments are the extra arguments used with this codec in the transcoder.
|
||||||
@@ -318,22 +317,17 @@ func (c *QuicksyncCodec) DisplayName() string {
|
|||||||
|
|
||||||
// GlobalFlags are the global flags used with this codec in the transcoder.
|
// GlobalFlags are the global flags used with this codec in the transcoder.
|
||||||
func (c *QuicksyncCodec) GlobalFlags() string {
|
func (c *QuicksyncCodec) GlobalFlags() string {
|
||||||
flags := []string{
|
return ""
|
||||||
"-init_hw_device", "qsv=hw",
|
|
||||||
"-filter_hw_device", "hw",
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(flags, " ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PixelFormat is the pixel format required for this codec.
|
// PixelFormat is the pixel format required for this codec.
|
||||||
func (c *QuicksyncCodec) PixelFormat() string {
|
func (c *QuicksyncCodec) PixelFormat() string {
|
||||||
return "qsv"
|
return "nv12"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scaler is the scaler used for resizing the video in the transcoder.
|
// Scaler is the scaler used for resizing the video in the transcoder.
|
||||||
func (c *QuicksyncCodec) Scaler() string {
|
func (c *QuicksyncCodec) Scaler() string {
|
||||||
return "scale_qsv"
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtraArguments are the extra arguments used with this codec in the transcoder.
|
// ExtraArguments are the extra arguments used with this codec in the transcoder.
|
||||||
@@ -343,7 +337,7 @@ func (c *QuicksyncCodec) ExtraArguments() string {
|
|||||||
|
|
||||||
// ExtraFilters are the extra filters required for this codec in the transcoder.
|
// ExtraFilters are the extra filters required for this codec in the transcoder.
|
||||||
func (c *QuicksyncCodec) ExtraFilters() string {
|
func (c *QuicksyncCodec) ExtraFilters() string {
|
||||||
return "hwupload=extra_hw_frames=64,format=qsv"
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// VariantFlags returns a string representing a single variant processed by this codec.
|
// VariantFlags returns a string representing a single variant processed by this codec.
|
||||||
@@ -354,16 +348,16 @@ func (c *QuicksyncCodec) VariantFlags(v *HLSVariant) string {
|
|||||||
// GetPresetForLevel returns the string preset for this codec given an integer level.
|
// GetPresetForLevel returns the string preset for this codec given an integer level.
|
||||||
func (c *QuicksyncCodec) GetPresetForLevel(l int) string {
|
func (c *QuicksyncCodec) GetPresetForLevel(l int) string {
|
||||||
presetMapping := map[int]string{
|
presetMapping := map[int]string{
|
||||||
0: "veryfast",
|
0: "ultrafast",
|
||||||
1: "fast",
|
1: "superfast",
|
||||||
2: "medium",
|
2: "veryfast",
|
||||||
3: "slow",
|
3: "faster",
|
||||||
4: "veryslow",
|
4: "fast",
|
||||||
}
|
}
|
||||||
|
|
||||||
preset, ok := presetMapping[l]
|
preset, ok := presetMapping[l]
|
||||||
if !ok {
|
if !ok {
|
||||||
defaultPreset := presetMapping[2]
|
defaultPreset := presetMapping[1]
|
||||||
log.Errorf("Invalid level for quicksync preset %d, defaulting to %s", l, defaultPreset)
|
log.Errorf("Invalid level for quicksync preset %d, defaulting to %s", l, defaultPreset)
|
||||||
return defaultPreset
|
return defaultPreset
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -88,9 +88,9 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
|
|||||||
if len(names) == 0 {
|
if len(names) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
configRepository := configrepository.Get()
|
|
||||||
mostRecentFile := path.Join(framePath, names[0])
|
mostRecentFile := path.Join(framePath, names[0])
|
||||||
ffmpegPath := utils.ValidatedFfmpegPath(configRepository.GetFfMpegPath())
|
ffmpegPath := utils.ValidatedFfmpegPath(data.GetFfMpegPath())
|
||||||
outputFileTemp := path.Join(config.TempDir, "tempthumbnail.jpg")
|
outputFileTemp := path.Join(config.TempDir, "tempthumbnail.jpg")
|
||||||
|
|
||||||
thumbnailCmdFlags := []string{
|
thumbnailCmdFlags := []string{
|
||||||
@@ -120,8 +120,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeAnimatedGifPreview(sourceFile string, outputFile string) {
|
func makeAnimatedGifPreview(sourceFile string, outputFile string) {
|
||||||
configRepository := configrepository.Get()
|
ffmpegPath := utils.ValidatedFfmpegPath(data.GetFfMpegPath())
|
||||||
ffmpegPath := utils.ValidatedFfmpegPath(configRepository.GetFfMpegPath())
|
|
||||||
outputFileTemp := path.Join(config.TempDir, "temppreview.gif")
|
outputFileTemp := path.Join(config.TempDir, "temppreview.gif")
|
||||||
|
|
||||||
// Filter is pulled from https://engineering.giphy.com/how-to-make-gifs-with-ffmpeg/
|
// Filter is pulled from https://engineering.giphy.com/how-to-make-gifs-with-ffmpeg/
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import (
|
|||||||
"github.com/teris-io/shortid"
|
"github.com/teris-io/shortid"
|
||||||
|
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/logging"
|
"github.com/owncast/owncast/logging"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -229,8 +229,7 @@ func (t *Transcoder) getString() string {
|
|||||||
"-hls_segment_filename", localListenerAddress + "/%v/stream-" + t.segmentIdentifier + "-%d.ts", // Send HLS segments back to us over HTTP
|
"-hls_segment_filename", localListenerAddress + "/%v/stream-" + t.segmentIdentifier + "-%d.ts", // Send HLS segments back to us over HTTP
|
||||||
"-max_muxing_queue_size", "400", // Workaround for Too many packets error: https://trac.ffmpeg.org/ticket/6375?cversion=0
|
"-max_muxing_queue_size", "400", // Workaround for Too many packets error: https://trac.ffmpeg.org/ticket/6375?cversion=0
|
||||||
|
|
||||||
"-method PUT", // HLS results sent back to us will be over PUTs
|
"-method PUT", // HLS results sent back to us will be over PUTs
|
||||||
|
|
||||||
localListenerAddress + "/%v/stream.m3u8", // Send HLS playlists back to us over HTTP
|
localListenerAddress + "/%v/stream.m3u8", // Send HLS playlists back to us over HTTP
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,16 +273,15 @@ func getVariantFromConfigQuality(quality models.StreamOutputVariant, index int)
|
|||||||
|
|
||||||
// NewTranscoder will return a new Transcoder, populated by the config.
|
// NewTranscoder will return a new Transcoder, populated by the config.
|
||||||
func NewTranscoder() *Transcoder {
|
func NewTranscoder() *Transcoder {
|
||||||
configRepository := configrepository.Get()
|
ffmpegPath := utils.ValidatedFfmpegPath(data.GetFfMpegPath())
|
||||||
ffmpegPath := utils.ValidatedFfmpegPath(configRepository.GetFfMpegPath())
|
|
||||||
|
|
||||||
transcoder := new(Transcoder)
|
transcoder := new(Transcoder)
|
||||||
transcoder.ffmpegPath = ffmpegPath
|
transcoder.ffmpegPath = ffmpegPath
|
||||||
transcoder.internalListenerPort = config.InternalHLSListenerPort
|
transcoder.internalListenerPort = config.InternalHLSListenerPort
|
||||||
|
|
||||||
transcoder.currentStreamOutputSettings = configRepository.GetStreamOutputVariants()
|
transcoder.currentStreamOutputSettings = data.GetStreamOutputVariants()
|
||||||
transcoder.currentLatencyLevel = configRepository.GetStreamLatencyLevel()
|
transcoder.currentLatencyLevel = data.GetStreamLatencyLevel()
|
||||||
transcoder.codec = getCodec(configRepository.GetVideoCodec())
|
transcoder.codec = getCodec(data.GetVideoCodec())
|
||||||
transcoder.segmentOutputPath = config.HLSStoragePath
|
transcoder.segmentOutputPath = config.HLSStoragePath
|
||||||
transcoder.playlistOutputPath = config.HLSStoragePath
|
transcoder.playlistOutputPath = config.HLSStoragePath
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
package transcoder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/owncast/owncast/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFFmpegQuicksyncCommand(t *testing.T) {
|
|
||||||
latencyLevel := models.GetLatencyLevel(2)
|
|
||||||
codec := QuicksyncCodec{}
|
|
||||||
|
|
||||||
transcoder := new(Transcoder)
|
|
||||||
transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg")
|
|
||||||
transcoder.SetInput("fakecontent.flv")
|
|
||||||
transcoder.SetOutputPath("fakeOutput")
|
|
||||||
transcoder.SetIdentifier("jdofFGg")
|
|
||||||
transcoder.SetInternalHTTPPort("8123")
|
|
||||||
transcoder.SetCodec(codec.Name())
|
|
||||||
transcoder.currentLatencyLevel = latencyLevel
|
|
||||||
|
|
||||||
variant := HLSVariant{}
|
|
||||||
variant.videoBitrate = 1200
|
|
||||||
variant.isAudioPassthrough = true
|
|
||||||
variant.SetVideoFramerate(30)
|
|
||||||
variant.SetCPUUsageLevel(2)
|
|
||||||
transcoder.AddVariant(variant)
|
|
||||||
|
|
||||||
variant2 := HLSVariant{}
|
|
||||||
variant2.videoBitrate = 3500
|
|
||||||
variant2.isAudioPassthrough = true
|
|
||||||
variant2.SetVideoFramerate(24)
|
|
||||||
variant2.SetCPUUsageLevel(4)
|
|
||||||
transcoder.AddVariant(variant2)
|
|
||||||
|
|
||||||
variant3 := HLSVariant{}
|
|
||||||
variant3.isAudioPassthrough = true
|
|
||||||
variant3.isVideoPassthrough = true
|
|
||||||
transcoder.AddVariant(variant3)
|
|
||||||
|
|
||||||
cmd := transcoder.getString()
|
|
||||||
|
|
||||||
expectedLogPath := filepath.Join("data", "logs", "transcoder.log")
|
|
||||||
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -init_hw_device qsv=hw -filter_hw_device hw -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_qsv -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -filter:v:0 "hwupload=extra_hw_frames=64,format=qsv" -preset medium -map v:0 -c:v:1 h264_qsv -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -filter:v:1 "hwupload=extra_hw_frames=64,format=qsv" -preset veryslow -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset veryfast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -pix_fmt qsv -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
|
|
||||||
|
|
||||||
if cmd != expected {
|
|
||||||
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -42,7 +42,7 @@ func TestFFmpegVaapiCommand(t *testing.T) {
|
|||||||
cmd := transcoder.getString()
|
cmd := transcoder.getString()
|
||||||
|
|
||||||
expectedLogPath := filepath.Join("data", "logs", "transcoder.log")
|
expectedLogPath := filepath.Join("data", "logs", "transcoder.log")
|
||||||
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_vaapi -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -filter:v:0 "hwupload=extra_hw_frames=64,format=vaapi" -preset veryfast -map v:0 -c:v:1 h264_vaapi -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -filter:v:1 "hwupload=extra_hw_frames=64,format=vaapi" -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -pix_fmt vaapi -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
|
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_vaapi -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_vaapi -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -pix_fmt vaapi_vld -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
|
||||||
|
|
||||||
if cmd != expected {
|
if cmd != expected {
|
||||||
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
|
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -59,8 +59,6 @@ var ignoredErrors = []string{
|
|||||||
"Non-monotonous DTS in output",
|
"Non-monotonous DTS in output",
|
||||||
"frames duplicated",
|
"frames duplicated",
|
||||||
"To ignore this",
|
"To ignore this",
|
||||||
"Driver does not support some wanted packed headers (wanted 0xd, found 0x1)",
|
|
||||||
"Failed to allocate a vaapi/nv12 frame from a fixed pool of hardware frames.",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleTranscoderMessage(message string) {
|
func handleTranscoderMessage(message string) {
|
||||||
@@ -101,9 +99,9 @@ func handleTranscoderMessage(message string) {
|
|||||||
func createVariantDirectories() {
|
func createVariantDirectories() {
|
||||||
// Create private hls data dirs
|
// Create private hls data dirs
|
||||||
utils.CleanupDirectory(config.HLSStoragePath)
|
utils.CleanupDirectory(config.HLSStoragePath)
|
||||||
configRepository := configrepository.Get()
|
|
||||||
if len(configRepository.GetStreamOutputVariants()) != 0 {
|
if len(data.GetStreamOutputVariants()) != 0 {
|
||||||
for index := range configRepository.GetStreamOutputVariants() {
|
for index := range data.GetStreamOutputVariants() {
|
||||||
if err := os.MkdirAll(path.Join(config.HLSStoragePath, strconv.Itoa(index)), 0o750); err != nil {
|
if err := os.MkdirAll(path.Join(config.HLSStoragePath, strconv.Itoa(index)), 0o750); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package webhooks
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
"github.com/teris-io/shortid"
|
"github.com/teris-io/shortid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,15 +14,13 @@ func SendStreamStatusEvent(eventType models.EventType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendStreamStatusEvent(eventType models.EventType, id string, timestamp time.Time) {
|
func sendStreamStatusEvent(eventType models.EventType, id string, timestamp time.Time) {
|
||||||
configRepository := configrepository.Get()
|
|
||||||
|
|
||||||
SendEventToWebhooks(WebhookEvent{
|
SendEventToWebhooks(WebhookEvent{
|
||||||
Type: eventType,
|
Type: eventType,
|
||||||
EventData: map[string]interface{}{
|
EventData: map[string]interface{}{
|
||||||
"id": id,
|
"id": id,
|
||||||
"name": configRepository.GetServerName(),
|
"name": data.GetServerName(),
|
||||||
"summary": configRepository.GetServerSummary(),
|
"summary": data.GetServerSummary(),
|
||||||
"streamTitle": configRepository.GetStreamTitle(),
|
"streamTitle": data.GetStreamTitle(),
|
||||||
"status": getStatus(),
|
"status": getStatus(),
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,16 +5,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSendStreamStatusEvent(t *testing.T) {
|
func TestSendStreamStatusEvent(t *testing.T) {
|
||||||
configRepository := configrepository.Get()
|
data.SetServerName("my server")
|
||||||
|
data.SetServerSummary("my server where I stream")
|
||||||
configRepository.SetServerName("my server")
|
data.SetStreamTitle("my stream")
|
||||||
configRepository.SetServerSummary("my server where I stream")
|
|
||||||
configRepository.SetStreamTitle("my stream")
|
|
||||||
|
|
||||||
checkPayload(t, models.StreamStarted, func() {
|
checkPayload(t, models.StreamStarted, func() {
|
||||||
sendStreamStatusEvent(events.StreamStarted, "id", time.Unix(72, 6).UTC())
|
sendStreamStatusEvent(events.StreamStarted, "id", time.Unix(72, 6).UTC())
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/webhookrepository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebhookEvent represents an event sent as a webhook.
|
// WebhookEvent represents an event sent as a webhook.
|
||||||
@@ -31,8 +31,7 @@ func SendEventToWebhooks(payload WebhookEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendEventToWebhooks(payload WebhookEvent, wg *sync.WaitGroup) {
|
func sendEventToWebhooks(payload WebhookEvent, wg *sync.WaitGroup) {
|
||||||
webhooksRepo := webhookrepository.Get()
|
webhooks := data.GetWebhooksForEvent(payload.Type)
|
||||||
webhooks := webhooksRepo.GetWebhooksForEvent(payload.Type)
|
|
||||||
|
|
||||||
for _, webhook := range webhooks {
|
for _, webhook := range webhooks {
|
||||||
// Use wg to track the number of notifications to be sent.
|
// Use wg to track the number of notifications to be sent.
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
"github.com/owncast/owncast/core/data"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/webhookrepository"
|
|
||||||
jsonpatch "gopkg.in/evanphx/json-patch.v5"
|
jsonpatch "gopkg.in/evanphx/json-patch.v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,14 +62,12 @@ func TestPublicSend(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer svr.Close()
|
defer svr.Close()
|
||||||
|
|
||||||
webhooksRepo := webhookrepository.Get()
|
hook, err := data.InsertWebhook(svr.URL, []models.EventType{models.MessageSent})
|
||||||
|
|
||||||
hook, err := webhooksRepo.InsertWebhook(svr.URL, []models.EventType{models.MessageSent})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
|
if err := data.DeleteWebhook(hook); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -110,15 +107,13 @@ func TestRouting(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer svr.Close()
|
defer svr.Close()
|
||||||
|
|
||||||
webhooksRepo := webhookrepository.Get()
|
|
||||||
|
|
||||||
for _, eventType := range eventTypes {
|
for _, eventType := range eventTypes {
|
||||||
hook, err := webhooksRepo.InsertWebhook(svr.URL+"/"+eventType, []models.EventType{eventType})
|
hook, err := data.InsertWebhook(svr.URL+"/"+eventType, []models.EventType{eventType})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
|
if err := data.DeleteWebhook(hook); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -153,15 +148,13 @@ func TestMultiple(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer svr.Close()
|
defer svr.Close()
|
||||||
|
|
||||||
webhooksRepo := webhookrepository.Get()
|
|
||||||
|
|
||||||
for i := 0; i < times; i++ {
|
for i := 0; i < times; i++ {
|
||||||
hook, err := webhooksRepo.InsertWebhook(fmt.Sprintf("%v/%v", svr.URL, i), []models.EventType{models.MessageSent})
|
hook, err := data.InsertWebhook(fmt.Sprintf("%v/%v", svr.URL, i), []models.EventType{models.MessageSent})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
|
if err := data.DeleteWebhook(hook); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -193,16 +186,14 @@ func TestTimestamps(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer svr.Close()
|
defer svr.Close()
|
||||||
|
|
||||||
webhooksRepo := webhookrepository.Get()
|
|
||||||
|
|
||||||
for i, eventType := range eventTypes {
|
for i, eventType := range eventTypes {
|
||||||
hook, err := webhooksRepo.InsertWebhook(svr.URL+"/"+eventType, []models.EventType{eventType})
|
hook, err := data.InsertWebhook(svr.URL+"/"+eventType, []models.EventType{eventType})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
handlerIds[i] = hook
|
handlerIds[i] = hook
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
|
if err := data.DeleteWebhook(hook); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -218,7 +209,7 @@ func TestTimestamps(t *testing.T) {
|
|||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
hooks, err := webhooksRepo.GetWebhooks()
|
hooks, err := data.GetWebhooks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -294,14 +285,12 @@ func TestParallel(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer svr.Close()
|
defer svr.Close()
|
||||||
|
|
||||||
webhooksRepo := webhookrepository.Get()
|
hook, err := data.InsertWebhook(svr.URL, []models.EventType{models.MessageSent})
|
||||||
|
|
||||||
hook, err := webhooksRepo.InsertWebhook(svr.URL, []models.EventType{models.MessageSent})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
|
if err := data.DeleteWebhook(hook); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -331,15 +320,13 @@ func checkPayload(t *testing.T, eventType models.EventType, send func(), expecte
|
|||||||
}))
|
}))
|
||||||
defer svr.Close()
|
defer svr.Close()
|
||||||
|
|
||||||
webhooksRepo := webhookrepository.Get()
|
|
||||||
|
|
||||||
// Subscribe to the webhook.
|
// Subscribe to the webhook.
|
||||||
hook, err := webhooksRepo.InsertWebhook(svr.URL, []models.EventType{eventType})
|
hook, err := data.InsertWebhook(svr.URL, []models.EventType{eventType})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
|
if err := data.DeleteWebhook(hook); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/webhookrepository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// webhookWorkerPoolSize defines the number of concurrent HTTP webhook requests.
|
// webhookWorkerPoolSize defines the number of concurrent HTTP webhook requests.
|
||||||
@@ -87,8 +87,7 @@ func sendWebhook(job Job) error {
|
|||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
webhooksRepo := webhookrepository.Get()
|
if err := data.SetWebhookAsUsed(job.webhook); err != nil {
|
||||||
if err := webhooksRepo.SetWebhookAsUsed(job.webhook); err != nil {
|
|
||||||
log.Warnln(err)
|
log.Warnln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
crowdin.yml
11
crowdin.yml
@@ -1,11 +0,0 @@
|
|||||||
project_id_env: CROWDIN_PROJECT_ID
|
|
||||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
|
||||||
pull_request_title: Translations update
|
|
||||||
pull_request_labels:
|
|
||||||
- crowdin
|
|
||||||
- i18n
|
|
||||||
- translation
|
|
||||||
commit_message: Updated translations
|
|
||||||
files:
|
|
||||||
- source: /web/i18n/en/translation.json
|
|
||||||
translation: /web/i18n/%two_letters_code%/translation.json
|
|
||||||
@@ -158,7 +158,7 @@ UPDATE users SET display_color = $1 WHERE id = $2
|
|||||||
`
|
`
|
||||||
|
|
||||||
type ChangeDisplayColorParams struct {
|
type ChangeDisplayColorParams struct {
|
||||||
DisplayColor int
|
DisplayColor int32
|
||||||
ID string
|
ID string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,8 +253,8 @@ SELECT iri, inbox, name, username, image, created_at FROM ap_followers WHERE app
|
|||||||
`
|
`
|
||||||
|
|
||||||
type GetFederationFollowersWithOffsetParams struct {
|
type GetFederationFollowersWithOffsetParams struct {
|
||||||
Limit int
|
Limit int32
|
||||||
Offset int
|
Offset int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetFederationFollowersWithOffsetRow struct {
|
type GetFederationFollowersWithOffsetRow struct {
|
||||||
@@ -365,8 +365,8 @@ SELECT iri, actor, type, timestamp FROM ap_accepted_activities ORDER BY timestam
|
|||||||
`
|
`
|
||||||
|
|
||||||
type GetInboundActivitiesWithOffsetParams struct {
|
type GetInboundActivitiesWithOffsetParams struct {
|
||||||
Limit int
|
Limit int32
|
||||||
Offset int
|
Offset int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetInboundActivitiesWithOffsetRow struct {
|
type GetInboundActivitiesWithOffsetRow struct {
|
||||||
@@ -514,8 +514,8 @@ SELECT value FROM ap_outbox LIMIT $1 OFFSET $2
|
|||||||
`
|
`
|
||||||
|
|
||||||
type GetOutboxWithOffsetParams struct {
|
type GetOutboxWithOffsetParams struct {
|
||||||
Limit int
|
Limit int32
|
||||||
Offset int
|
Offset int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetOutboxWithOffset(ctx context.Context, arg GetOutboxWithOffsetParams) ([][]byte, error) {
|
func (q *Queries) GetOutboxWithOffset(ctx context.Context, arg GetOutboxWithOffsetParams) ([][]byte, error) {
|
||||||
|
|||||||
9054
docs/api/index.html
9054
docs/api/index.html
File diff suppressed because one or more lines are too long
111
go.mod
111
go.mod
@@ -1,81 +1,94 @@
|
|||||||
module github.com/owncast/owncast
|
module github.com/owncast/owncast
|
||||||
|
|
||||||
go 1.22.0
|
go 1.22
|
||||||
|
|
||||||
toolchain go1.23.1
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/CAFxX/httpcompression v0.0.9
|
github.com/aws/aws-sdk-go v1.54.13
|
||||||
github.com/SherClockHolmes/webpush-go v1.4.0
|
github.com/go-fed/activity v1.0.1-0.20210803212804-d866ba75dd0f
|
||||||
github.com/TwiN/go-away v1.6.14
|
|
||||||
github.com/andybalholm/cascadia v1.3.3
|
|
||||||
github.com/aws/aws-sdk-go v1.55.6
|
|
||||||
github.com/go-chi/chi/v5 v5.2.0
|
|
||||||
github.com/go-fed/activity v1.0.1-0.20220119073622-b14b50eecad0
|
|
||||||
github.com/go-fed/httpsig v1.1.0
|
github.com/go-fed/httpsig v1.1.0
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/grafov/m3u8 v0.12.1
|
github.com/grafov/m3u8 v0.12.0
|
||||||
github.com/jellydator/ttlcache/v3 v3.3.0
|
|
||||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27
|
github.com/microcosm-cc/bluemonday v1.0.27
|
||||||
github.com/mssola/user_agent v0.6.0
|
|
||||||
github.com/nakabonne/tstorage v0.3.6
|
|
||||||
github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590
|
github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590
|
||||||
github.com/oapi-codegen/runtime v1.1.1
|
|
||||||
github.com/oschwald/geoip2-golang v1.11.0
|
github.com/oschwald/geoip2-golang v1.11.0
|
||||||
github.com/pkg/errors v0.9.1
|
|
||||||
github.com/prometheus/client_golang v1.20.5
|
|
||||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
|
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
|
||||||
github.com/schollz/sqlite3dump v1.3.1
|
github.com/schollz/sqlite3dump v1.3.1
|
||||||
github.com/shirou/gopsutil/v4 v4.24.12
|
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/stretchr/testify v1.10.0
|
|
||||||
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569
|
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569
|
||||||
github.com/yuin/goldmark v1.7.8
|
github.com/yuin/goldmark v1.7.4
|
||||||
github.com/yuin/goldmark-emoji v1.0.4
|
golang.org/x/mod v0.18.0
|
||||||
golang.org/x/crypto v0.32.0
|
golang.org/x/time v0.5.0
|
||||||
golang.org/x/mod v0.22.0
|
|
||||||
golang.org/x/net v0.34.0
|
|
||||||
golang.org/x/time v0.9.0
|
|
||||||
gopkg.in/evanphx/json-patch.v5 v5.9.0
|
|
||||||
mvdan.cc/xurls/v2 v2.6.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||||
|
github.com/lestrrat-go/strftime v1.0.4 // indirect
|
||||||
|
github.com/mvdan/xurls v1.1.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
|
golang.org/x/crypto v0.24.0
|
||||||
|
golang.org/x/net v0.26.0
|
||||||
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require github.com/prometheus/client_golang v1.19.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.16.7 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/nakabonne/tstorage v0.3.6
|
||||||
|
github.com/shirou/gopsutil/v4 v4.24.6
|
||||||
|
)
|
||||||
|
|
||||||
|
require github.com/SherClockHolmes/webpush-go v1.3.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/ebitengine/purego v0.8.1 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
|
||||||
github.com/go-test/deep v1.0.4 // indirect
|
github.com/go-test/deep v1.0.4 // indirect
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
|
||||||
github.com/google/uuid v1.5.0 // indirect
|
github.com/google/uuid v1.5.0 // indirect
|
||||||
github.com/gorilla/css v1.0.1 // indirect
|
github.com/gorilla/css v1.0.1 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
|
||||||
github.com/klauspost/compress v1.17.9 // indirect
|
|
||||||
github.com/lestrrat-go/strftime v1.0.4 // indirect
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
|
||||||
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
github.com/prometheus/common v0.55.0 // indirect
|
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
|
||||||
golang.org/x/text v0.21.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/CAFxX/httpcompression v0.0.9
|
||||||
|
github.com/TwiN/go-away v1.6.13
|
||||||
|
github.com/andybalholm/cascadia v1.3.2
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0
|
||||||
|
github.com/jellydator/ttlcache/v3 v3.2.0
|
||||||
|
github.com/mssola/user_agent v0.6.0
|
||||||
|
github.com/oapi-codegen/runtime v1.1.1
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
github.com/yuin/goldmark-emoji v1.0.3
|
||||||
|
gopkg.in/evanphx/json-patch.v5 v5.9.0
|
||||||
|
mvdan.cc/xurls v1.1.0
|
||||||
|
mvdan.cc/xurls/v2 v2.5.0
|
||||||
|
)
|
||||||
|
|
||||||
replace github.com/go-fed/activity => github.com/owncast/activity v1.0.1-0.20211229051252-7821289d4026
|
replace github.com/go-fed/activity => github.com/owncast/activity v1.0.1-0.20211229051252-7821289d4026
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user