Compare commits

..

No commits in common. "main" and "v0.10.0-rc1" have entirely different histories.

9558 changed files with 2395179 additions and 5935453 deletions

187
.drone.yml Normal file
View file

@ -0,0 +1,187 @@
---
### Drone configuration file for GoToSocial.
### Connects to https://drone.superseriousbusiness.org to perform testing, linting, and automatic builds/pushes to docker.
###
### For documentation on drone, see: https://docs.drone.io/
### For documentation on drone docker pipelines in particular: https://docs.drone.io/pipeline/docker/overview/
kind: pipeline
type: docker
name: default
steps:
# We use golangci-lint for linting.
# See: https://golangci-lint.run/
- name: lint
image: golangci/golangci-lint:v1.53.1
volumes:
- name: go-build-cache
path: /root/.cache/go-build
- name: golangci-lint-cache
path: /root/.cache/golangci-lint
- name: go-src
path: /go
commands:
- golangci-lint run
when:
event:
include:
- pull_request
- name: test
image: golang:1.20.4-alpine
volumes:
- name: go-build-cache
path: /root/.cache/go-build
- name: go-src
path: /go
commands:
- apk update --no-cache && apk add git
- CGO_ENABLED=0 GTS_DB_TYPE="sqlite" GTS_DB_ADDRESS=":memory:" go test ./...
- CGO_ENABLED=0 ./test/envparsing.sh
when:
event:
include:
- pull_request
- name: web-setup
image: node:18-alpine
when:
event:
include:
- pull_request
volumes:
- name: yarn_cache
path: /tmp/cache
commands:
- cd web/source
- yarn --frozen-lockfile --cache-folder /tmp/cache
- name: web-lint
image: node:18-alpine
when:
event:
include:
- pull_request
depends_on:
- web-setup
commands:
- cd web/source
- yarn run lint
- name: web-build
image: node:18-alpine
when:
event:
include:
- pull_request
depends_on:
- web-setup
commands:
- cd web/source
- yarn run build
- name: snapshot
image: superseriousbusiness/gotosocial-drone-build:0.2.0 # https://github.com/superseriousbusiness/gotosocial-drone-build
volumes:
- name: go-build-cache
path: /root/.cache/go-build
- name: docker
path: /var/run/docker.sock
environment:
DOCKER_USERNAME: gotosocial
DOCKER_PASSWORD:
from_secret: gts_docker_password
commands:
- git fetch --tags
- /go/dockerlogin.sh
- goreleaser release --rm-dist --snapshot
- docker push superseriousbusiness/gotosocial:snapshot-armv6 &&
- docker push superseriousbusiness/gotosocial:snapshot-armv7
- docker push superseriousbusiness/gotosocial:snapshot-arm64v8
- docker push superseriousbusiness/gotosocial:snapshot-amd64
- docker manifest create superseriousbusiness/gotosocial:snapshot superseriousbusiness/gotosocial:snapshot-armv6 superseriousbusiness/gotosocial:snapshot-armv7 superseriousbusiness/gotosocial:snapshot-amd64 superseriousbusiness/gotosocial:snapshot-arm64v8
- docker manifest push superseriousbusiness/gotosocial:snapshot
when:
event:
include:
- push
branch:
include:
- main
- name: release
image: superseriousbusiness/gotosocial-drone-build:0.2.0 # https://github.com/superseriousbusiness/gotosocial-drone-build
volumes:
- name: go-build-cache
path: /root/.cache/go-build
- name: docker
path: /var/run/docker.sock
environment:
DOCKER_USERNAME: gotosocial
DOCKER_PASSWORD:
from_secret: gts_docker_password
GITHUB_TOKEN:
from_secret: github_token
commands:
- git fetch --tags
- /go/dockerlogin.sh
- goreleaser release --rm-dist
when:
event:
include:
- tag
# We can speed up builds significantly by caching build artifacts between runs.
# See: https://docs.drone.io/pipeline/docker/syntax/volumes/host/
volumes:
- name: go-build-cache
host:
path: /drone/gotosocial/go-build
- name: golangci-lint-cache
host:
path: /drone/gotosocial/golangci-lint
- name: go-src
host:
path: /drone/gotosocial/go
- name: docker
host:
path: /var/run/docker.sock
trigger:
repo:
- superseriousbusiness/gotosocial
- NyaaaWhatsUpDoc/gotosocial
- f0x52/gotosocial
---
kind: pipeline
type: docker
name: cron
trigger:
event:
- cron
cron:
- nightly
clone:
disable: true
steps:
- name: mirror
image: superseriousbusiness/gotosocial-drone-build:0.2.0
environment:
ORIGIN_REPO: https://github.com/superseriousbusiness/gotosocial
TARGET_REPO: https://codeberg.org/superseriousbusiness/gotosocial
CODEBERG_USER: gotosocialbot
CODEBERG_EMAIL: admin@gotosocial.org
CODEBERG_TOKEN:
from_secret: gts_codeberg_token
commands:
- /go/codeberg_clone.sh
---
kind: signature
hmac: 946c2ffd4e79de07a767ec06ebac0a8ca70a03ce5666aae093c9b0af455041d1
...

2
.gitattributes vendored
View file

@ -1,3 +1 @@
*.go diff=golang
go.sum linguist-generated merge=ours
/vendor/ linguist-generated

View file

@ -1,76 +0,0 @@
name: Bug Report
description: Create a report to help us improve
title: "[bug] Issue Title"
labels: [bug]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please be cautious with the sensitive information/logs while filing the issue.
- type: markdown
attributes:
value: |
⚠️ If you're reporting an interoperability issue with a **closed source**
ActivityPub/Mastodon client or server, please report the issue to the
developers of that client or server instead. Without access to their
source debugging the issue is difficult for us. Since GoToSocial is
open source, developers of closed source implementations can run a copy
and autonomously debug the issue. We'll gladly address any bugs they
raise with us.
- type: textarea
id: desc
attributes:
label: Describe the bug with a clear and concise description of what the bug is.
validations:
required: true
- type: input
id: GoToSocial-Version
attributes:
label: What's your GoToSocial Version?
description: Enter the Version of your GoToSocial Installation
placeholder: e.g. v0.3.4
validations:
required: true
- type: input
id: GoToSocial-Arch
attributes:
label: GoToSocial Arch
description: What architecture do you use and did you do a Binary or Docker installation?
placeholder: e.g. arm64 Docker or arm64 Binary
validations:
required: false
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Enter exactly what happened.
validations:
required: false
- type: textarea
id: what-expected
attributes:
label: What you expected to happen?
description: Enter what you expected to happen.
validations:
required: false
- type: textarea
id: how-to-reproduce
attributes:
label: How to reproduce it?
description: As minimally and precisely as possible.
validations:
required: false
- type: textarea
id: anything-else
attributes:
label: Anything else we need to know?
validations:
required: false

View file

@ -1 +0,0 @@
blank_issues_enabled: false

View file

@ -1,44 +0,0 @@
name: Feature request
description: Suggest an idea for this project
title: "[feature] Issue Title"
labels: [enhancement]
assignees:
-
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request!
- type: textarea
id: desc
attributes:
label: Is your feature request related to a problem ?
description: Give a clear and concise description of what the problem is.
placeholder: ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
id: prop-solution
attributes:
label: Describe the solution you'd like.
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered.
description: A clear and concise description of any alternative solutions or features you've considered. If nothing, please enter `NONE`
validations:
required: true
- type: textarea
id: additional-ctxt
attributes:
label: Additional context.
description: Add any other context or screenshots about the feature request here.
validations:
required: false

View file

@ -1,67 +0,0 @@
name: Frontend Bug Report
description: Report an issue related to the web frontend
title: "[bug/frontend] Issue Title"
labels: ["bug", "frontend"]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please be cautious with the sensitive information/logs while filing the issue.
- type: textarea
id: desc
attributes:
label: Describe the bug with a clear and concise description of what the bug is. Please include screenshots of any visual issues.
validations:
required: true
- type: input
id: GoToSocial-Version
attributes:
label: What's your GoToSocial Version?
description: Enter the Version of your GoToSocial Installation
placeholder: e.g. v0.3.4
validations:
required: true
- type: input
id: Browser-Version
attributes:
label: Browser version
description: What browser(s) and versions are you using that show this bug?
placeholder: Firefox 103.0b9 (64-bit)
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Enter exactly what happened.
validations:
required: false
- type: textarea
id: what-expected
attributes:
label: What you expected to happen?
description: Enter what you expected to happen.
validations:
required: false
- type: textarea
id: how-to-reproduce
attributes:
label: How to reproduce it?
description: As minimally and precisely as possible.
validations:
required: false
- type: textarea
id: anything-else
attributes:
label: Anything else we need to know?
validations:
required: false

View file

@ -1,6 +0,0 @@
---
name: Other
about: A different type of issue or question.
labels:
- question
---

2
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,2 @@
open_collective: gotosocial
liberapay: GoToSocial

View file

@ -0,0 +1,67 @@
name: Frontend Bug Report
description: Report an issue related to the web frontend
title: "[bug] Issue Title"
labels: ["bug", "frontend"]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please be cautious with the sensitive information/logs while filing the issue.
- type: textarea
id: desc
attributes:
label: Describe the bug with a clear and concise description of what the bug is. Please include screenshots of any visual issues.
validations:
required: true
- type: input
id: GoToSocial-Version
attributes:
label: What's your GoToSocial Version?
description: Enter the Version of your GoToSocial Installation
placeholder: e.g. v0.3.4
validations:
required: true
- type: input
id: Browser-Version
attributes:
label: Browser version
description: What browser(s) and versions are you using that show this bug?
placeholder: Firefox 103.0b9 (64-bit)
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Enter exactly what happened.
validations:
required: false
- type: textarea
id: what-expected
attributes:
label: What you expected to happen?
description: Enter what you expected to happen.
validations:
required: false
- type: textarea
id: how-to-reproduce
attributes:
label: How to reproduce it?
description: As minimally and precisely as possible.
validations:
required: false
- type: textarea
id: anything-else
attributes:
label: Anything else we need to know?
validations:
required: false

76
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View file

@ -0,0 +1,76 @@
name: Bug Report
description: Create a report to help us improve
title: "[bug] Issue Title"
labels: [bug]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please be cautious with the sensitive information/logs while filing the issue.
- type: markdown
attributes:
value: |
⚠️ If you're reporting an interoperability issue with a **closed source**
ActivityPub/Mastodon client or server, please report the issue to the
developers of that client or server instead. Without access to their
source debugging the issue is difficult for us. Since GoToSocial is
open source, developers of closed source implementations can run a copy
and autonomously debug the issue. We'll gladly address any bugs they
raise with us.
- type: textarea
id: desc
attributes:
label: Describe the bug with a clear and concise description of what the bug is.
validations:
required: true
- type: input
id: GoToSocial-Version
attributes:
label: What's your GoToSocial Version?
description: Enter the Version of your GoToSocial Installation
placeholder: e.g. v0.3.4
validations:
required: true
- type: input
id: GoToSocial-Arch
attributes:
label: GoToSocial Arch
description: What architecture do you use and did you do a Binary or Docker installation?
placeholder: e.g. arm64 Docker or arm64 Binary
validations:
required: false
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Enter exactly what happened.
validations:
required: false
- type: textarea
id: what-expected
attributes:
label: What you expected to happen?
description: Enter what you expected to happen.
validations:
required: false
- type: textarea
id: how-to-reproduce
attributes:
label: How to reproduce it?
description: As minimally and precisely as possible.
validations:
required: false
- type: textarea
id: anything-else
attributes:
label: Anything else we need to know?
validations:
required: false

8
.github/ISSUE_TEMPLATE/custom.md vendored Normal file
View file

@ -0,0 +1,8 @@
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---

View file

@ -0,0 +1,44 @@
name: Feature request
description: Suggest an idea for this project
title: "[feature] Issue Title"
labels: [enhancement]
assignees:
-
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request!
- type: textarea
id: desc
attributes:
label: Is your feature request related to a problem ?
description: Give a clear and concise description of what the problem is.
placeholder: ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
id: prop-solution
attributes:
label: Describe the solution you'd like.
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered.
description: A clear and concise description of any alternative solutions or features you've considered. If nothing, please enter `NONE`
validations:
required: true
- type: textarea
id: additional-ctxt
attributes:
label: Additional context.
description: Add any other context or screenshots about the feature request here.
validations:
required: false

5
.github/README.md vendored
View file

@ -1,5 +0,0 @@
# GoToSocial
This is a mirror. You can find us on https://codeberg.org/superseriousbusiness/gotosocial.
We will **stop mirroring** to Github after 0.20 is released. This will likely be sometime **in August**.

15
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,15 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
commit-message:
prefix: "[chore]"
labels:
- "chore"

View file

@ -15,9 +15,8 @@ Please put an x inside each checkbox to indicate that you've read and followed i
If this is a documentation change, only the first checkbox must be filled (you can delete the others if you want).
- [ ] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [ ] I/we have read the [GoToSocial contribution guidelines](https://github.com/superseriousbusiness/gotosocial/blob/main/CONTRIBUTING.md).
- [ ] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [ ] I/we have not leveraged AI to create the proposed changes.
- [ ] I/we have performed a self-review of added code.
- [ ] I/we have written code that is legible and maintainable by others.
- [ ] I/we have commented the added code, particularly in hard-to-understand areas.

View file

@ -1,30 +0,0 @@
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
permissions:
issues: write
pull-requests: write
jobs:
autoclose:
runs-on: ubuntu-latest
steps:
- name: issue
if: ${{ github.event.issue.id != '' }}
run: |
gh issue close $ISSUE --comment "This repository is a mirror. Please open issues on https://codeberg.org/superseriousbusiness/gotosocial." --reason "not planned"
gh issue lock $ISSUE
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE: ${{ github.event.issue.html_url }}
- name: pr
if: ${{ github.event.pull_request.id != '' }}
run: |
gh pr close $PULL_REQUEST --comment "This repository is a mirror. Please open PRs on https://codeberg.org/superseriousbusiness/gotosocial."
gh pr lock $PULL_REQUEST
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PULL_REQUEST: ${{ github.event.pull_request.html_url }}

12
.gitignore vendored
View file

@ -4,10 +4,6 @@
# exclude built documentation, since readthedocs will build it for us anyway
/docs/_build
# exclude kim's commonly used
# test stdout file location
test.out
# exclude coverage report
cp.out
@ -23,9 +19,6 @@ dist/
# exclude the copy of swagger.yaml moved into assets during packaging
web/assets/swagger.yaml
# exclude the copy of all_licenses.txt moved into assets during packaging
web/assets/all_licenses.txt
# exludes docker-volume from exemple/docker-compose
example/docker-compose/docker-volume
@ -44,6 +37,5 @@ shell.nix
/.idea/
/.fleet/
# ignore cached pngs from mkdocs serve,
# while preserving cached fonts.
/docs/.cache/plugin/social/*.png
# ignore cache dir from mkdocs serve
/.cache

View file

@ -4,21 +4,27 @@
#
# For GoToSocial we mostly take the default linters, but we add a few to catch style issues as well.
version: "2"
# options for analysis running
run:
# include test files or not, default is true
tests: false
# timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 5m
linters:
# enable some extra linters, see here for the list: https://golangci-lint.run/usage/linters/
enable:
- forcetypeassert
- goconst
- gocritic
- gofmt
- goheader
- gosec
- nilerr
- revive
# https://golangci-lint.run/usage/linters/#linters-configuration
settings:
# https://golangci-lint.run/usage/linters/#linters-configuration
linters-settings:
# https://golangci-lint.run/usage/linters/#goheader
goheader:
template: |-
@ -71,31 +77,11 @@ linters:
- name: unreachable-code
# Disable below rules.
- name: redefines-builtin-id
disabled: true
disabled: true # This one is just annoying.
- name: unused-parameter
disabled: true
- name: var-naming
arguments:
- []
- []
- - skip-package-name-checks: true
disabled: true # We often pass parameters to fulfil interfaces.
# https://golangci-lint.run/usage/linters/#staticcheck
staticcheck:
# Enable all checks, but disable SA1012: nil context passing.
# See: https://staticcheck.io/docs/configuration/options/#checks
checks:
- SA*
- -SA1012
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
formatters:
enable:
- gofmt
exclusions:
generated: lax
checks: ["all", "-SA1012"]

View file

@ -1,30 +1,17 @@
# Version 2 of GoReleaser: https://goreleaser.com/errors/version/
version: 2
# https://goreleaser.com
project_name: gotosocial
# https://goreleaser.com/scm/gitea/#urls
gitea_urls:
api: https://codeberg.org/api/v1
download: https://codeberg.org
# https://goreleaser.com/customization/hooks/
before:
# https://goreleaser.com/customization/hooks/
hooks:
# generate the swagger.yaml file using go-swagger and bundle it into the assets directory
- go run ./vendor/github.com/go-swagger/go-swagger/cmd/swagger generate spec --scan-models --exclude-deps -o web/assets/swagger.yaml
- swagger generate spec --scan-models --exclude-deps -o web/assets/swagger.yaml
- sed -i "s/REPLACE_ME/{{ incpatch .Version }}/" web/assets/swagger.yaml
# Install web deps + bundle web assets
- yarn --cwd ./web/source install
- yarn --cwd ./web/source ts-patch install # https://typia.io/docs/setup/#manual-setup
- yarn --cwd ./web/source build
# Bundle all licenses into web/assets/all_licenses.txt
- ./scripts/bundle_licenses.sh
# https://goreleaser.com/customization/build/
# bundle web assets
- yarn install --cwd web/source
- scripts/bundle.sh
builds:
# DEFAULT WASM BINARY BUILDS
# https://goreleaser.com/customization/build/
-
id: gotosocial
main: ./cmd/gotosocial
binary: gotosocial
ldflags:
@ -37,48 +24,11 @@ builds:
- netgo
- osusergo
- static_build
- kvformat
- timetzdata
- >-
{{ if and (index .Env "DEBUG") (.Env.DEBUG) }}debugenv{{ end }}
env:
- CGO_ENABLED=0
goos:
- linux
- freebsd
- netbsd
goarch:
- amd64
- arm64
mod_timestamp: "{{ .CommitTimestamp }}"
# NOWASM BINARY BUILDS
-
id: gotosocial_nowasm
main: ./cmd/gotosocial
binary: gotosocial
ldflags:
- -s
- -w
- -extldflags
- -static
- -X main.Version={{.Version}}
tags:
- netgo
- osusergo
- static_build
- kvformat
- timetzdata
- nowasm
- >-
{{ if and (index .Env "DEBUG") (.Env.DEBUG) }}debugenv{{ end }}
env:
- CGO_ENABLED=0
goos:
# moderncsqlite doesn't
# build for netbsd right
# now so leave it out.
- linux
- freebsd
goarch:
- 386
- amd64
@ -88,8 +38,7 @@ builds:
- 6
- 7
ignore:
# Don't build BSDs
# for arm/32-bit.
# build freebsd only for amd64
- goos: freebsd
goarch: arm64
- goos: freebsd
@ -97,33 +46,22 @@ builds:
- goos: freebsd
goarch: 386
mod_timestamp: "{{ .CommitTimestamp }}"
# https://goreleaser.com/customization/docker/
dockers:
# DEFAULT WASM DOCKER BUILDS
# https://goreleaser.com/customization/docker/
-
use: buildx
goos: linux
goarch: amd64
id: amd64
ids:
- gotosocial
image_templates:
- "{{ if not .IsSnapshot }}superseriousbusiness/{{ .ProjectName }}:{{ .Version }}-amd64{{ end }}" # Use version tag (eg., `0.19.0`, `0.19.0-rc1`) for proper releases and prereleases.
- "{{ if and (not .Prerelease) (not .IsSnapshot) }}superseriousbusiness/{{ .ProjectName }}:latest-amd64{{ end }}" # Only use `latest` for proper releases, not prereleases or snapshots.
- "{{ if .IsSnapshot }}superseriousbusiness/{{ .ProjectName }}:snapshot-amd64{{ end }}" # Only use `snapshot` for snapshot builds triggered by merge to main.
- "superseriousbusiness/{{ .ProjectName }}:{{ .Version }}-amd64"
- "superseriousbusiness/{{ .ProjectName }}:latest-amd64"
- "superseriousbusiness/{{ .ProjectName }}:snapshot-amd64"
build_flag_templates:
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.title=GoToSocial"
- "--label=org.opencontainers.image.authors=GoToSocial Authors"
- "--label=org.opencontainers.image.description=Fast, fun, small ActivityPub server."
- "--label=org.opencontainers.image.url=https://docs.gotosocial.org"
- "--label=org.opencontainers.image.documentation=https://docs.gotosocial.org/en/latest/getting_started/installation/container/"
- "--label=org.opencontainers.image.source=https://codeberg.org/superseriousbusiness/gotosocial"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.licenses=AGPL-3.0-or-later"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
extra_files:
- web
- go.mod
@ -134,57 +72,87 @@ dockers:
use: buildx
goos: linux
goarch: arm64
id: arm64v8
ids:
- gotosocial
image_templates:
- "{{ if not .IsSnapshot }}superseriousbusiness/{{ .ProjectName }}:{{ .Version }}-arm64v8{{ end }}" # Use version tag (eg., `0.19.0`, `0.19.0-rc1`) for proper releases and prereleases.
- "{{ if and (not .Prerelease) (not .IsSnapshot) }}superseriousbusiness/{{ .ProjectName }}:latest-arm64v8{{ end }}" # Only use `latest` for proper releases, not prereleases or snapshots.
- "{{ if .IsSnapshot }}superseriousbusiness/{{ .ProjectName }}:snapshot-arm64v8{{ end }}" # Only use `snapshot` for snapshot builds triggered by merge to main.
- "superseriousbusiness/{{ .ProjectName }}:{{ .Version }}-arm64v8"
- "superseriousbusiness/{{ .ProjectName }}:latest-arm64v8"
- "superseriousbusiness/{{ .ProjectName }}:snapshot-arm64v8"
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--label=org.opencontainers.image.title=GoToSocial"
- "--label=org.opencontainers.image.authors=GoToSocial Authors"
- "--label=org.opencontainers.image.description=Fast, fun, small ActivityPub server."
- "--label=org.opencontainers.image.url=https://docs.gotosocial.org"
- "--label=org.opencontainers.image.documentation=https://docs.gotosocial.org/en/latest/getting_started/installation/container/"
- "--label=org.opencontainers.image.source=https://codeberg.org/superseriousbusiness/gotosocial"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.licenses=AGPL-3.0-or-later"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
extra_files:
- web
- go.mod
- go.sum
- cmd
- internal
-
use: buildx
goos: linux
goarch: arm
goarm: 6
image_templates:
- "superseriousbusiness/{{ .ProjectName }}:{{ .Version }}-armv6"
- "superseriousbusiness/{{ .ProjectName }}:latest-armv6"
- "superseriousbusiness/{{ .ProjectName }}:snapshot-armv6"
build_flag_templates:
- "--platform=linux/arm/v6"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
extra_files:
- web
- go.mod
- go.sum
- cmd
- internal
-
use: buildx
goos: linux
goarch: arm
goarm: 7
image_templates:
- "superseriousbusiness/{{ .ProjectName }}:{{ .Version }}-armv7"
- "superseriousbusiness/{{ .ProjectName }}:latest-armv7"
- "superseriousbusiness/{{ .ProjectName }}:snapshot-armv7"
build_flag_templates:
- "--platform=linux/arm/v7"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
extra_files:
- web
- go.mod
- go.sum
- cmd
- internal
# https://goreleaser.com/customization/docker_manifest/
docker_manifests:
# Use version tag (eg., `0.19.0`, `0.19.0-rc1`) for proper releases and prereleases.
- name_template: "{{ if not .IsSnapshot }}superseriousbusiness/{{ .ProjectName }}:{{ .Version }}{{ end }}"
- name_template: superseriousbusiness/{{ .ProjectName }}:{{ .Version }}
image_templates:
- superseriousbusiness/{{ .ProjectName }}:{{ .Version }}-amd64
- superseriousbusiness/{{ .ProjectName }}:{{ .Version }}-arm64v8
# Only use `latest` for proper releases, not prereleases or snapshots.
- name_template: "{{ if and (not .Prerelease) (not .IsSnapshot) }}superseriousbusiness/{{ .ProjectName }}:latest{{ end }}"
- superseriousbusiness/{{ .ProjectName }}:{{ .Version }}-armv6
- superseriousbusiness/{{ .ProjectName }}:{{ .Version }}-armv7
- name_template: superseriousbusiness/{{ .ProjectName }}:latest
image_templates:
- superseriousbusiness/{{ .ProjectName }}:latest-amd64
- superseriousbusiness/{{ .ProjectName }}:latest-arm64v8
# Only use `snapshot` for snapshot builds triggered by merge to main.
- name_template: "{{ if .IsSnapshot }}superseriousbusiness/{{ .ProjectName }}:snapshot{{ end }}"
- superseriousbusiness/{{ .ProjectName }}:latest-armv6
- superseriousbusiness/{{ .ProjectName }}:latest-armv7
- name_template: superseriousbusiness/{{ .ProjectName }}:snapshot
image_templates:
- superseriousbusiness/{{ .ProjectName }}:snapshot-amd64
- superseriousbusiness/{{ .ProjectName }}:snapshot-arm64v8
# https://goreleaser.com/customization/archive/
- superseriousbusiness/{{ .ProjectName }}:snapshot-armv6
- superseriousbusiness/{{ .ProjectName }}:snapshot-armv7
archives:
# DEFAULT WASM BUILD
# https://goreleaser.com/customization/archive/
-
id: gotosocial
builds:
- gotosocial
id: binary-release
files:
# standard release files
- LICENSE
@ -196,24 +164,6 @@ archives:
# example config files
- example/config.yaml
- example/gotosocial.service
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 \"v1\") }}{{ .Amd64 }}{{ end }}"
# NOWASM BUILD
-
id: gotosocial_nowasm
builds:
- gotosocial_nowasm
files:
# standard release files
- LICENSE
- README.md
- CHANGELOG*
# web stuff minus source
- web/assets
- web/template
# example config files
- example/config.yaml
- example/gotosocial.service
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 \"v1\") }}{{ .Amd64 }}{{ end }}_nowasm"
-
id: web-assets
files:
@ -223,126 +173,13 @@ archives:
- web/template
meta: true
name_template: "{{ .ProjectName }}_{{ .Version }}_web-assets"
# https://goreleaser.com/customization/checksum/
checksum:
# https://goreleaser.com/customization/checksum/
name_template: 'checksums.txt'
# https://goreleaser.com/customization/snapshots/
snapshot:
version_template: "{{ incpatch .Version }}-SNAPSHOT"
# https://goreleaser.com/customization/source/
# https://goreleaser.com/customization/snapshots/
name_template: "{{ incpatch .Version }}-SNAPSHOT"
source:
# https://goreleaser.com/customization/source/
enabled: true
name_template: "{{ .ProjectName }}-{{ .Version }}-source-code"
# https://goreleaser.com/customization/release/
release:
# https://goreleaser.com/customization/release/#gitea
gitea:
owner: superseriousbusiness
name: gotosocial
draft: true
prerelease: auto
header: |
Here's version {{ .Version }} of GoToSocial.
Please read the migration notes carefully for instructions on how to upgrade to this version.
## Release highlights
- Pee pee
- Poo poo
- Wee wee
## Migration notes
### Upgrading
To upgrade to {{ .Tag }} from a previous release:
#### Binary/tar
1. Stop GoToSocial.
2. **Back up your database!** If you're running on SQLite, this is as simple as copying your `sqlite.db` file, eg., `cp sqlite.db sqlite.db.backup`. On Postgres you can do this with [pg_dump](https://www.postgresql.org/docs/current/backup-dump.html).
3. Download and untar the new release, **including the web assets and html templates**, not just the binary.
4. Edit your config.yaml file if necessary (see below).
5. Start GoToSocial.
6. Wait patiently for any migrations to run, **do not interrupt migrations or you could leave your db in a broken state and will have to restore from backup**!
7. Enjoy your updated instance.
#### Docker
1. Stop GoToSocial.
2. **Back up your database!** If you're running on SQLite, this is as simple as copying your `sqlite.db` file, eg., `cp sqlite.db sqlite.db.backup`. On Postgres you can do this with [pg_dump](https://www.postgresql.org/docs/current/backup-dump.html).
3. Pull the new docker container with `docker pull docker.io/superseriousbusiness/gotosocial:{{ .Version }}` or `docker pull docker.io/superseriousbusiness/gotosocial:latest` if this is a stable release and not a release candidate.
4. Edit your config.yaml file or environment variables if necessary (see below).
5. Start GoToSocial.
6. Wait patiently for any migrations to run, **do not interrupt migrations or you could leave your db in a broken state and will have to restore from backup**!
7. Enjoy your updated instance.
### config.yaml
The configuration file has changed since the previous release.
- Changed `pee pee` to `poo poo`.
- Changed `wee wee` to `more wee wee`.
You can see a diff of the config file here: https://codeberg.org/superseriousbusiness/gotosocial/compare/{{ .PreviousTag }}...{{ .Tag }}#diff-c071e03510b2c57e193a44503fd9528a785f0f411497cc75841a9f8d0b1ac622
### Database Migrations
⚠️⚠️⚠️
This release may contain database migrations which will run the first time you start up this new version.
Be sure not to interrupt this migration process.
This will take anywhere between a couple seconds and ten minutes or more (on slower hardware).
**Please be patient!**
⚠️⚠️⚠️
### Which release archive/container should I use?
GoToSocial releases binary builds for 64-bit Linux, FreeBSD, and NetBSD operating systems. We also release Docker builds for 64-bit Linux.
| OS | Architecture | Support level | Binary archive | Docker |
| ------- | ----------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| Linux | x86-64/AMD64 (64-bit) | 🟢 Full | [linux_amd64.tar.gz](https://codeberg.org/superseriousbusiness/gotosocial/releases/download/{{ .Tag }}/gotosocial_{{ .Version }}_linux_amd64.tar.gz) | `docker.io/superseriousbusiness/gotosocial:{{ .Version }}` |
| Linux | Armv8/ARM64 (64-bit) | 🟢 Full | [linux_arm64.tar.gz](https://codeberg.org/superseriousbusiness/gotosocial/releases/download/{{ .Tag }}/gotosocial_{{ .Version }}_linux_arm64.tar.gz) | `docker.io/superseriousbusiness/gotosocial:{{ .Version }}` |
| FreeBSD | x86-64/AMD64 (64-bit) | 🟢 Full | [freebsd_amd64.tar.gz](https://codeberg.org/superseriousbusiness/gotosocial/releases/download/{{ .Tag }}/gotosocial_{{ .Version }}_freebsd_amd64.tar.gz) | Not provided |
| FreeBSD | Armv8/ARM64 (64-bit) | 🟢 Full | [freebsd_arm64.tar.gz](https://codeberg.org/superseriousbusiness/gotosocial/releases/download/{{ .Tag }}/gotosocial_{{ .Version }}_freebsd_arm64.tar.gz) | Not provided |
| NetBSD | x86-64/AMD64 (64-bit) | 🟢 Full | [netbsd_amd64.tar.gz](https://codeberg.org/superseriousbusiness/gotosocial/releases/download/{{ .Tag }}/gotosocial_{{ .Version }}_netbsd_amd64.tar.gz) | Not provided |
| NetBSD | Armv8/ARM64 (64-bit) | 🟢 Full | [netbsd_arm64.tar.gz](https://codeberg.org/superseriousbusiness/gotosocial/releases/download/{{ .Tag }}/gotosocial_{{ .Version }}_netbsd_arm64.tar.gz) | Not provided |
#### `nowasm`
For your convenience, we also provide **UNSUPPORTED, EXPERIMENTAL BUILDS**, created using the `nowasm` tag, in the downloads list below. There is no Docker build for `nowasm`.
GoToSocial releases built with `nowasm` use the Go-native, modernc version of SQLite instead of the WASM one, and will use *on-system ffmpeg and ffprobe binaries* for media processing.
Using a `nowasm` build is currently the only way to run GoToSocial on a 32-bit system.
For more information on running a `nowasm` build, see the [nowasm](https://docs.gotosocial.org/en/latest/advanced/builds/nowasm/) documentation page.
# https://goreleaser.com/customization/changelog/
changelog:
use: gitea
groups:
- title: Features and performance
regexp: '^.*\[(?:feature|performance).*\].*$'
order: 0
- title: Bug fixes
regexp: '^.*\[bug.*\].*$'
order: 1
- title: Chores & version bumps
regexp: '^.*\[chore.*\].*$'
order: 2
- title: Documentation
regexp: '^.*\[doc.*\].*$'
order: 3
- title: Other
order: 999

View file

@ -6,9 +6,9 @@
version: 2
build:
os: ubuntu-22.04
os: "ubuntu-20.04"
tools:
python: "mambaforge-22.9" # https://docs.readthedocs.io/en/stable/guides/conda.html#making-builds-faster-with-mamba
python: "mambaforge-4.10" # https://docs.readthedocs.io/en/stable/guides/conda.html#making-builds-faster-with-mamba
mkdocs:
configuration: "mkdocs.yml"

19
.vscode/settings.json vendored
View file

@ -3,21 +3,10 @@
"go.lintFlags": [
"--fast"
],
"gopls": {
"analyses": {
"composites": false
},
},
"go.vetFlags": [
"-composites=false ."
],
"eslint.workingDirectories": ["web/source"],
"eslint.lintTask.enable": true,
"eslint.lintTask.options": "${workspaceFolder}/web/source",
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
"eslint.lintTask.options": "${workspaceFolder}/web/source"
}

View file

@ -1,137 +0,0 @@
# https://woodpecker-ci.org/docs/usage/workflow-syntax#when---global-workflow-conditions
when:
- event: pull_request
steps:
# Lint the Go code only if
# some Go files have changed.
#
# CI_PIPELINE_FILES is undefined if
# files changed > 500, and empty on
# force pushes, so account for this
# and run step to be safe.
lint:
when:
# https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate
# https://woodpecker-ci.org/docs/usage/environment#built-in-environment-variables
- evaluate: >-
(not ("CI_PIPELINE_FILES" in $env)) ||
CI_PIPELINE_FILES == "[]" ||
any(fromJSON(CI_PIPELINE_FILES), { # startsWith "internal/" || # startsWith "cmd/" || # startsWith "testrig/" }) ||
len(fromJSON(CI_PIPELINE_FILES)) == 0
# We use golangci-lint for linting.
# See: https://golangci-lint.run/
image: golangci/golangci-lint:v2.3.1
pull: true
# https://woodpecker-ci.org/docs/administration/configuration/backends/docker#run-user
backend_options:
docker:
user: 1000:1000
# https://woodpecker-ci.org/docs/usage/volumes
volumes:
- /woodpecker/gotosocial/go-build-cache:/.cache/go-build
- /woodpecker/gotosocial/go-pkg-cache:/go/pkg
- /woodpecker/gotosocial/golangci-lint-cache:/.cache/golangci-lint
# https://woodpecker-ci.org/docs/usage/environment
environment:
GOFLAGS: "-buildvcs=false"
# https://woodpecker-ci.org/docs/usage/workflow-syntax#commands
commands:
- golangci-lint run
# Test the Go code only if
# some Go files have changed.
#
# CI_PIPELINE_FILES is undefined if
# files changed > 500, and empty on
# force pushes, so account for this
# and run step to be safe.
test:
when:
# https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate
# https://woodpecker-ci.org/docs/usage/environment#built-in-environment-variables
- evaluate: >-
(not ("CI_PIPELINE_FILES" in $env)) ||
CI_PIPELINE_FILES == "[]" ||
any(fromJSON(CI_PIPELINE_FILES), { # startsWith "internal/" || # startsWith "cmd/" || # startsWith "testrig/" || # startsWith "vendor/" }) ||
len(fromJSON(CI_PIPELINE_FILES)) == 0
image: golang:1.24-alpine
pull: true
# https://woodpecker-ci.org/docs/administration/configuration/backends/docker#run-user
backend_options:
docker:
user: 1000:1000
# https://woodpecker-ci.org/docs/usage/volumes
volumes:
- /woodpecker/gotosocial/go-build-cache:/.cache/go-build
- /woodpecker/gotosocial/go-pkg-cache:/go/pkg
- /woodpecker/gotosocial/wazero-compilation-cache:/.cache/wazero
- /woodpecker/gotosocial/test-tmp:/tmp
# https://woodpecker-ci.org/docs/usage/environment
environment:
CGO_ENABLED: "0"
GTS_WAZERO_COMPILATION_CACHE: "/.cache/wazero"
# https://woodpecker-ci.org/docs/usage/workflow-syntax#commands
commands:
- >-
go test
-ldflags="-s -w -extldflags '-static'"
-tags="netgo osusergo static_build kvformat timetzdata"
-failfast
-timeout=30m
./...
- ./test/envparsing.sh
- ./test/swagger.sh
# Validate the web code only
# if web source has changed.
#
# CI_PIPELINE_FILES is undefined if
# files changed > 500, and empty on
# force pushes, so account for this
# and run step to be safe.
web:
when:
# https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate
# https://woodpecker-ci.org/docs/usage/environment#built-in-environment-variables
- evaluate: >-
(not ("CI_PIPELINE_FILES" in $env)) ||
CI_PIPELINE_FILES == "[]" ||
any(fromJSON(CI_PIPELINE_FILES), { # startsWith "web/source/" }) ||
len(fromJSON(CI_PIPELINE_FILES)) == 0
image: node:lts-alpine
pull: true
# https://woodpecker-ci.org/docs/administration/configuration/backends/docker#run-user
backend_options:
docker:
user: 1000:1000
# https://woodpecker-ci.org/docs/usage/volumes
volumes:
- /woodpecker/gotosocial/node_modules:/woodpecker/src/codeberg.org/superseriousbusiness/gotosocial/web/source/node_modules
- /woodpecker/gotosocial/yarn-cache:/.cache/yarn
- /woodpecker/gotosocial/web-dist-test:/woodpecker/src/codeberg.org/superseriousbusiness/gotosocial/web/assets/dist
# https://woodpecker-ci.org/docs/usage/workflow-syntax#commands
commands:
# Install web dependencies.
- yarn --cwd ./web/source install --frozen-lockfile --cache-folder /.cache/yarn
- yarn --cwd ./web/source ts-patch install # https://typia.io/docs/setup/#manual-setup
# Lint web source.
- yarn --cwd ./web/source lint
# Ensure build works.
- yarn --cwd ./web/source build

View file

@ -1,54 +0,0 @@
# https://woodpecker-ci.org/docs/usage/workflow-syntax#when---global-workflow-conditions
when:
- event: tag
# https://goreleaser.com/ci/woodpecker/
# https://woodpecker-ci.org/docs/usage/workflow-syntax#clone
clone:
git:
image: woodpeckerci/plugin-git
settings:
tags: true
steps:
release:
# https://codeberg.org/superseriousbusiness/gotosocial-woodpecker-build
image: superseriousbusiness/gotosocial-woodpecker-build:0.12.1
pull: true
# https://woodpecker-ci.org/docs/usage/volumes
volumes:
- /woodpecker/gotosocial/go-build-cache-root:/root/.cache/go-build
- /woodpecker/gotosocial/go-pkg-cache-root:/go/pkg
- /var/run/docker.sock:/var/run/docker.sock
# https://woodpecker-ci.org/docs/usage/environment
# https://woodpecker-ci.org/docs/usage/secrets#usage
environment:
# Needed for goreleaser to
# push manifests + containers.
DOCKER_USERNAME: gotosocial
DOCKER_PASSWORD:
from_secret: gts_docker_password
# Needed for goreleaser
# to publish the release.
# https://goreleaser.com/scm/gitea/
GITEA_TOKEN:
from_secret: codeberg_token
# https://woodpecker-ci.org/docs/usage/workflow-syntax#commands
commands:
- git fetch --tags
- /go/dockerlogin.sh
# When releasing, compare commits to the most recent tag that is not the
# current one AND is not a release candidate tag (ie., no "rc" in the name).
#
# The CI_COMMIT_TAG env var should point to the tag that triggered this build.
# See: https://woodpecker-ci.org/docs/usage/environment
#
# Note, this may cause annoyances when doing backport releases, for example,
# releasing v0.10.1 when we've already released v0.15.0 or whatever, but
# they should only be superficial annoyances related to the release notes.
- GORELEASER_PREVIOUS_TAG=$(git tag -l | grep -v "rc\|${CI_COMMIT_TAG}" | sort -V -r | head -n 1) goreleaser release --clean

View file

@ -1,76 +0,0 @@
# https://woodpecker-ci.org/docs/usage/workflow-syntax#when---global-workflow-conditions
when:
- event: push
branch: main
# https://goreleaser.com/ci/woodpecker/
# https://woodpecker-ci.org/docs/usage/workflow-syntax#clone
clone:
git:
image: woodpeckerci/plugin-git
settings:
tags: true
steps:
snapshot:
# Snapshot only if some interesting
# source code files have changed.
#
# CI_PIPELINE_FILES is undefined if
# files changed > 500, so account for
# this and snapshot anyway if so.
when:
# https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate
# https://woodpecker-ci.org/docs/usage/environment#built-in-environment-variables
- evaluate: >-
(not ("CI_PIPELINE_FILES" in $env)) ||
CI_PIPELINE_FILES == "[]" ||
any(fromJSON(CI_PIPELINE_FILES), { # startsWith "internal/" || # startsWith "cmd/" || # startsWith "testrig/" || # startsWith "vendor/" || # startsWith "web/" || # == "Dockerfile" }) ||
len(fromJSON(CI_PIPELINE_FILES)) == 0
# https://codeberg.org/superseriousbusiness/gotosocial-woodpecker-build
image: superseriousbusiness/gotosocial-woodpecker-build:0.12.1
pull: true
# https://woodpecker-ci.org/docs/usage/volumes
volumes:
- /woodpecker/gotosocial/go-build-cache-root:/root/.cache/go-build
- /woodpecker/gotosocial/go-pkg-cache-root:/go/pkg
- /var/run/docker.sock:/var/run/docker.sock
# https://woodpecker-ci.org/docs/usage/environment
# https://woodpecker-ci.org/docs/usage/secrets#usage
environment:
# Needed to push snapshot
# manifests + containers.
DOCKER_USERNAME: gotosocial
DOCKER_PASSWORD:
from_secret: gts_docker_password
# Needed for snapshot script
# to publish artifacts to S3.
S3_ACCESS_KEY_ID:
from_secret: gts_s3_access_key_id
S3_SECRET_ACCESS_KEY:
from_secret: gts_s3_secret_access_key
S3_HOSTNAME: "https://s3.superseriousbusiness.org"
S3_BUCKET_NAME: "gotosocial-snapshots"
# https://woodpecker-ci.org/docs/usage/workflow-syntax#commands
commands:
# Create a snapshot build with GoReleaser.
- git fetch --tags
- goreleaser release --clean --snapshot
# Login to Docker, push Docker image snapshots + manifests.
- /go/dockerlogin.sh
- docker push superseriousbusiness/gotosocial:snapshot-arm64v8
- docker push superseriousbusiness/gotosocial:snapshot-amd64
- |
docker manifest create superseriousbusiness/gotosocial:snapshot \
superseriousbusiness/gotosocial:snapshot-amd64 \
superseriousbusiness/gotosocial:snapshot-arm64v8
- docker manifest push superseriousbusiness/gotosocial:snapshot
# Publish binary .tar.gz snapshots to S3.
- /go/snapshot_publish.sh

View file

@ -4,8 +4,6 @@ This document is currently a stub.
In lieu of a fuller code of conduct, here are a few ground rules.
1. We are not interested in input from right-wingers, nazis, fascists, transphobes, homophobes, racists, harassers, abusers, white-supremacists, misogynists or capitalists. This list is NOT exhaustive. Input includes PRs, issues, and any other communication. Be gone!
2. We will not accept changes (code or otherwise) created with the aid of "AI" tooling. "AI" models are trained at the expense of underpaid workers filtering inputs of abhorrent content, and does not respect the owners of input content. Ethically, it sucks.
3. We will not accept changes (code or otherwise) that move GoToSocial toward enterprise / surveillance / other toxic capitalist pursuits.
4. We will not accept changes (code or otherwise) that encourage toxic behaviour on social media. This is obviously subject to debate, as humanity as a whole is still figuring out how to safely engage in social media.
5. No spam!
1. We *DO NOT ACCEPT* PRs from right-wingers, Nazis, transphobes, homophobes, racists, harassers, abusers, white-supremacists, misogynists, tech-bros of questionable ethics. If that's you, politely fuck off somewhere else.
2. Any PR that moves GoToSocial in the direction of surveillance capitalism or other bad fediverse behavior will be rejected.
3. Don't spam the general chat too hard.

View file

@ -18,23 +18,22 @@ These contribution guidelines were adapted from / inspired by those of Gitea (ht
- [Docker](#docker)
- [With GoReleaser](#with-goreleaser)
- [Manually](#manually)
- [Stylesheet / Web dev](#stylesheet-web-dev)
- [Stylesheet / Web dev](#stylesheet--web-dev)
- [Live Reloading](#live-reloading)
- [Project Structure](#project-structure)
- [Finding your way around the code](#finding-your-way-around-the-code)
- [Style / Linting / Formatting](#style-linting-formatting)
- [Style / Linting / Formatting](#style--linting--formatting)
- [Testing](#testing)
- [Standalone Testrig with Pinafore](#standalone-testrig-with-pinafore)
- [Configuring the Standalone Testrig](#configuring-the-standalone-testrig)
- [Standalone Testrig with Semaphore](#standalone-testrig-with-semaphore)
- [Running automated tests](#running-automated-tests)
- [SQLite](#sqlite)
- [Postgres](#postgres)
- [CLI Tests](#cli-tests)
- [Federation](#federation)
- [Updating Swagger docs](#updating-swagger-docs)
- [CI/CD configuration](#ci-cd-configuration)
- [Other Useful Stuff](#other-useful-stuff)
- [Running migrations on a Postgres DB backup locally](#running-migrations-on-a-postgres-db-backup-locally)
- [CI/CD configuration](#cicd-configuration)
- [Release Checklist](#release-checklist)
- [What if something goes wrong?](#what-if-something-goes-wrong)
## Introduction
@ -42,11 +41,11 @@ This document contains important information that will help you to write a succe
## Bug reports and feature requests
Currently, we use Codeberg's issue system for tracking bug reports and feature requests.
Currently, we use Github's issue system for tracking bug reports and feature requests.
You can view all open issues [here](https://codeberg.org/superseriousbusiness/gotosocial/issues "The Codeberg Issues page for GoToSocial").
You can view all open issues [here](https://github.com/superseriousbusiness/gotosocial/issues "The Github Issues page for GoToSocial").
Before opening a new issue, whether bug or feature request, **please search carefully through both open and closed issues to make sure it hasn't been addressed already**. You can use Codeberg's keyword issue search for this. If your issue is a duplicate of an existing issue, it will be closed.
Before opening a new issue, whether bug or feature request, **please search carefully through both open and closed issues to make sure it hasn't been addressed already**. You can use Github's keyword issue search for this. If your issue is a duplicate of an existing issue, it will be closed.
Before you open a feature request issue, please consider the following:
@ -62,7 +61,7 @@ We tend to prioritize feature requests related to accessibility, fedi interopera
We welcome pull requests from new and existing contributors, with the following provisos:
- You have read and agree to our [Code of Conduct](./CODE_OF_CONDUCT.md).
- You have read and agree to our Code of Conduct.
- The pull request addresses an existing issue or bug (please link to the relevant issue in your pull request), or is related to documentation.
- If your pull request introduces significant new code paths, you are willing to do some maintenance work on those code paths, and address bugs. We do not appreciate drive-by pull requests that introduce a significant maintenance burden!
- The pull request is of decent quality. We are a small team and unfortunately we don't have a lot of time to help shepherd pull requests, or help with basic coding questions. If you're unsure, don't bite off more than you can chew: start with a small feature or bugfix for your first PR, and work your way up.
@ -100,45 +99,39 @@ If you see something in the documentation that's missing, wrong, or unclear, fee
We support a [Conda](https://docs.conda.io/en/latest/)-based workflow for hacking, building & publishing the documentation. Here's how you can get started locally:
* Install [`miniconda`](https://www.anaconda.com/docs/getting-started/miniconda/main)
* Install [`miniconda`](https://docs.conda.io/en/latest/miniconda.html)
* Create your conda environment: `conda env create -f ./docs/environment.yml`
* Activate the environment: `conda activate gotosocial-docs`
* Serve locally: `mkdocs serve`
Then you can visit [localhost:8000](http://127.0.0.1:8000/) in your browser to view.
When adding a new page, you need to include it in the [`mkdocs.yml`](mkdocs.yml) so it shows in the sidebar in the right section.
If you don't use Conda, you can read the `docs/environment.yml` to see which dependencies are required and `pip install` them manually. It's advisable to do this in a virtual environment, which you can create with something like `python3 -m venv /path-to/store-the-venv`. You can then call `/path-to/store-the-venv/bin/pip`, `/path-to/store-the-venv/bin/mkdocs` etc.
In order to upgrade dependencies, use `conda update --update-all` in the activated environment. You can then update the `environment.yml` with:
```sh
conda env export -n gotosocial-docs --from-history --override-channels -c conda-forge -c nodefaults -f ./docs/environment.yml
```
Beware that `conda env export` will add a `prefix` entry to the environment.yml file, and drop the `pip` dependencies, so make sure to remove the prefix and add the `pip` dependencies back in.
If you don't use Conda, you can read the `docs/environment.yml` to see which dependencies are required and `pip install` them manually.
## Development
### Golang forking quirks
One of the quirks of Golang is that it relies on the source management path being the same as the one used within `go.mod` and in package imports within individual Go files. This makes working with forks a bit awkward. The solution to this is to fork, then clone the upstream repository, then set `origin` of the upstream repository to that of your fork.
One of the quirks of Golang is that it relies on the source management path being the same as the one used within `go.mod` and in package imports within individual Go files. This makes working with forks a bit awkward.
Let's say you fork GoToSocial to `github.com/yourgithubname/gotosocial`, and then clone that repository to `~/go/src/github.com/yourgithubname/gotosocial`. You will probably run into errors trying to run tests or build, so you might change your `go.mod` file so that the module is called `github.com/yourgithubname/gotosocial` instead of `github.com/superseriousbusiness/gotosocial`. But then this breaks all the imports within the project. Nightmare! So now you have to go through the source files and painstakingly replace `github.com/superseriousbusiness/gotosocial` with `github.com/yourgithubname/gotosocial`. This works OK, but when you decide to make a pull request against the original repo, all the changed paths are included! Argh!
The correct solution to this is to fork, then clone the upstream repository, then set `origin` of the upstream repository to that of your fork.
See [this blog post](https://blog.sgmansfield.com/2016/06/working-with-forks-in-go/) for more details.
In case this post disappears, here are the steps (slightly modified):
>
> Fork the repository on Codeberg or set up whatever other remote git repo you will be using. In this case, I would go to Codeberg and fork the repository.
> Fork the repository on GitHub or set up whatever other remote git repo you will be using. In this case, I would go to GitHub and fork the repository.
>
> Now clone the upstream repo (not the fork):
>
> `mkdir -p ~/go/src/code.superseriousbusiness.org && git clone git@codeberg.org:superseriousbusiness/gotosocial ~/go/src/code.superseriousbusiness.org/gotosocial`
> `mkdir -p ~/go/src/github.com/superseriousbusiness && git clone git@github.com:superseriousbusiness/gotosocial ~/go/src/github.com/superseriousbusiness/gotosocial`
>
> Navigate to the top level of the upstream repository on your computer:
>
> `cd ~/go/src/code.superseriousbusiness.org/gotosocial`
> `cd ~/go/src/github.com/superseriousbusiness/gotosocial`
>
> Rename the current origin remote to upstream:
>
@ -146,25 +139,21 @@ In case this post disappears, here are the steps (slightly modified):
>
> Add your fork as origin:
>
> `git remote add origin git@codeberg.org:username/gotosocial`
> `git remote add origin git@github.com/yourgithubname/gotosocial`
>
Be sure to run `git fetch` before building the project for the first time.
### Building GoToSocial
#### Binary
To get started, you first need to have Go installed. Check the top of the `go.mod` file to see which version of Go you need to install, and see [here](https://golang.org/doc/install) for installation instructions.
To get started, you first need to have Go installed. GtS is currently using Go 1.20, so you should take that too. See [here](https://golang.org/doc/install) for installation instructions.
Once you've got Go installed, clone this repository into your Go path. Normally, this should be `~/go/src/code.superseriousbusiness.org/gotosocial`.
Once you've got go installed, clone this repository into your Go path. Normally, this should be `~/go/src/github.com/superseriousbusiness/gotosocial`.
Once you've installed the prerequisites, you can try building the project: `./scripts/build.sh`. This will build the `gotosocial` binary.
If there are no errors, great, you're good to go!
If you see the error `fatal: No names found, cannot describe anything.`, you need to run `git fetch`.
For automatic re-compiling during development, you can use [nodemon](https://www.npmjs.com/package/nodemon):
```bash
@ -173,7 +162,7 @@ nodemon -e go --signal SIGTERM --exec "go run ./cmd/gotosocial --host localhost
#### Docker
For both of the below methods, you need to have [Docker buildx](https://docs.docker.com/build/concepts/overview/#buildx) installed.
For both of the below methods, you need to have [Docker buildx](https://docs.docker.com/buildx/working-with-buildx/) installed.
##### With GoReleaser
@ -185,19 +174,21 @@ Normally, these processes are handled by Drone (see CI/CD above). However, you c
To do this, first [install GoReleaser](https://goreleaser.com/install/).
Then install Node and Yarn as described in [Stylesheet / Web dev](#stylesheet-web-dev).
Then install GoSwagger as described in [the Swagger section](#updating-swagger-docs).
Then install Node and Yarn as described in [Stylesheet / Web dev](#stylesheet--web-dev).
Finally, to create a snapshot build, do:
```bash
goreleaser release --clean --snapshot
goreleaser --rm-dist --snapshot
```
If all goes according to plan, you should now have a number of multiple-architecture binaries and tars inside the `./dist` folder, and snapshot Docker images should be built (check your terminal output for version).
##### Manually
If you prefer a simple approach to building a Docker container, with fewer dependencies (Node, Yarn), you can also just build in the following way:
If you prefer a simple approach to building a Docker container, with fewer dependencies (go-swagger, Node, Yarn), you can also just build in the following way:
```bash
./scripts/build.sh && docker buildx build -t superseriousbusiness/gotosocial:latest .
@ -205,8 +196,6 @@ If you prefer a simple approach to building a Docker container, with fewer depen
The above command first builds the `gotosocial` binary, then invokes Docker buildx to build the container image.
If you get an error while doing the build that looks like `"/web/assets/swagger.yaml": not found`, then you need to (re)generate the Swagger docs once, see [Updating Swagger docs](#updating-swagger-docs) for the command.
If you want to build a docker image for a different CPU architecture without setting up buildx (for example for ARMv7 aka 32-bit ARM), first modify the Dockerfile by adding the following lines to the top (but don't commit this!):
```dockerfile
@ -235,46 +224,31 @@ To bundle changes, you need [Node.js](https://nodejs.org/en/download/) and [Yarn
Using [NVM](https://github.com/nvm-sh/nvm) is one convenient way to install them which also supports managing different Node versions.
To install frontend dependencies:
To install Yarn dependencies:
```bash
yarn --cwd ./web/source install && yarn --cwd ./web/source ts-patch install
yarn install --cwd web/source
```
The `ts-patch` step is necessary because of Typia, which we use for some type validation: see [Typia install docs](https://typia.io/docs/setup/#manual-setup).
To recompile frontend bundles into `web/assets/dist`:
To recompile bundles:
```bash
yarn --cwd ./web/source build
node web/source
```
#### Live Reloading
For a more convenient development environment, you can run a livereloading version of the bundler alongside the [testrig](#testing).
First build the GtS binary with DEBUG=1 to enable the testrig:
Open two terminals, first start the testrig on port 8081:
``` bash
DEBUG=1 ./scripts/build.sh
GTS_PORT=8081 go run ./cmd/gotosocial testrig start
```
Now open two terminals.
In the first terminal, run the testrig on port 8081, using the binary you just built:
```bash
DEBUG=1 GTS_PORT=8081 ./gotosocial testrig start
```
Then start the bundler, which will run on port 8080, and proxy requests to the testrig instance where needed.
``` bash
NODE_ENV=development yarn --cwd ./web/source dev
NODE_ENV=development node web/source
```
You can then log in to the GoToSocial settings panel at `http://localhost:8080/settings` and see your changes reflected in real time as the dev bundler reloads.
The livereloading bundler *will not* change the bundled assets in `dist/`, so once you are finished with changes and want to deploy it somewhere, you have to run `node web/source` to generate production-ready bundles.
### Project Structure
@ -382,7 +356,7 @@ We use [golangci-lint](https://golangci-lint.run/) for linting, which allows us
If you make a PR that doesn't pass the linter, it will be rejected. As such, it's good practice to run the linter locally before pushing or opening a PR.
To do this, first install the linter following the instructions [here](https://golangci-lint.run/welcome/install/).
To do this, first install the linter following the instructions [here](https://golangci-lint.run/usage/install/#local-installation).
Then, you can run the linter with:
@ -394,13 +368,13 @@ If there's no output, great! It passed :)
### Testing
GoToSocial provides a [testrig](https://codeberg.org/superseriousbusiness/gotosocial/tree/main/testrig) with a number of mock packages you can use in integration tests.
GoToSocial provides a [testrig](https://github.com/superseriousbusiness/gotosocial/tree/main/testrig) with a number of mock packages you can use in integration tests.
One thing that *isn't* mocked is the Database interface because it's just easier to use an in-memory SQLite database than to mock everything out.
#### Standalone Testrig with Pinafore
#### Standalone Testrig with Semaphore
You can launch a testrig as a standalone server running at localhost, which you can connect to using something like [Pinafore](https://github.com/nolanlawson/pinafore/).
You can launch a testrig as a standalone server running at localhost, which you can connect to using something like [Semaphore](https://github.com/NickColley/semaphore/).
To do this, first build the gotosocial binary with `DEBUG=1 ./scripts/build.sh`.
@ -410,14 +384,14 @@ Then, launch the testrig with the `DEBUG` environment variable set by invoking t
DEBUG=1 ./gotosocial testrig start
```
To run Pinafore locally in dev mode, first clone the [Pinafore](https://github.com/nolanlawson/pinafore/) repository, and then run the following commands in the cloned directory:
To run Semaphore locally in dev mode, first clone the [Semaphore](https://github.com/NickColley/semaphore/) repository, and then run the following commands in the cloned directory:
```bash
yarn # install dependencies
yarn run dev
```
The Pinafore instance will start running on `localhost:4002`.
The Semaphore instance will start running on `localhost:4002`.
To connect to the testrig, navigate to `http://localhost:4002` and enter your instance name as `localhost:8080`.
@ -425,35 +399,10 @@ At the login screen, enter the email address `zork@example.org` and password `pa
Note the following constraints:
- Since the testrig uses an in-memory database by default, the database will be destroyed when the testrig is stopped.
- If you stop the testrig and start it again, by default any tokens or applications you created during your tests will also be removed. As such, you need to log out and in again every time you stop/start the rig.
- Since the testrig uses an in-memory database, the database will be destroyed when the testrig is stopped.
- If you stop the testrig and start it again, any tokens or applications you created during your tests will also be removed. As such, you need to log out and in again every time you stop/start the rig.
- The testrig does not make any actual external HTTP calls, so federation will not work from a testrig.
##### Configuring the Standalone Testrig
By default the standalone testrig uses an in-memory SQLite database, which is filled with test data when starting up, and is cleared when shutting down, but you can tweak this (and a few other settings) with environment variables:
- `GTS_LOG_LEVEL` - you can set this to `trace` if you want to see all DB queries.
- `GTS_TESTRIG_SKIP_DB_SETUP` - set this to any value to skip the creation of tables and population of test data when the testrig starts.
- `GTS_TESTRIG_SKIP_DB_TEARDOWN` - set this to any value to skip the deletion of tables and test data when the testrig stops.
- `GTS_STORAGE_BACKEND` - this uses in-memory storage by default, but you can set this to `s3` to use a locally-running Minio etc for testing.
- `GTS_DB_TYPE` - you can change this to `postgres` to test against a locally-running Postgres intance.
- `GTS_DB_ADDRESS` - this is set to `:memory:` by default. You can change this to use an sqlite.db file somewhere, or set it to a Postgres address.
- `GTS_DB_PORT`, `GTS_DB_USER`, `GTS_DB_PASSWORD`, `GTS_DB_DATABASE`, `GTS_DB_TLS_MODE`, `GTS_DB_TLS_CA_CERT` - you can set these if you change `GTS_DB_ADDRESS` to `postgres` and don't use `GTS_DB_POSTGRES_CONNECTION_STRING`.
- `GTS_DB_POSTGRES_CONNECTION_STRING` - use this to provide a Postgres connection string if you don't want to set all the db env variables mentioned in the previous point.
Using these variables you can also (albeit awkwardly) test migrations from one schema to another.
For example, to test SQLite migrations:
1. Switch to main branch.
2. Build the debug binary, and then start the testrig with `DEBUG=1 GTS_LOG_LEVEL=trace GTS_DB_ADDRESS=./sqlite.test.db GTS_TESTRIG_SKIP_DB_TEARDOWN=1 ./gotosocial testrig start`. This instructs the testrig to use trace logging, use an actual file for the SQLite db, and to skip tearing it down when finished.
3. Stop the testrig.
4. The file `sqlite.test.db` now contains the schema and test models from the main branch.
5. Switch to the branch with the migration you want to test.
6. Build the debug binary, and then start the testrig with `DEBUG=1 GTS_LOG_LEVEL=trace GTS_DB_ADDRESS=./sqlite.test.db GTS_TESTRIG_SKIP_DB_SETUP=1 ./gotosocial testrig start`. This instructs the testrig to use trace logging, and to use the already-populated sqlite.test.db file.
7. You should see logging for migrations.
#### Running automated tests
Tests can be run against both SQLite and Postgres.
@ -486,79 +435,81 @@ Although this test *is* part of the CI/CD testing process, you probably won't ne
#### Federation
By using the support for loading TLS files from disk it is possible to have two or more local instances with TLS to allow for (manually) testing federation.
By using the support for loading TLS files from disk it is possible to have two local instances with TLS to allow for (manually) testing federation.
You'll need to set the following configuration options:
- `GTS_TLS_CERTIFICATE_CHAIN`: poiting to a PEM-encoded certificate chain including the public certificate.
- `GTS_TLS_CERTIFICATE_KEY`: pointing to a PEM-encoded private key.
* `GTS_TLS_CERTIFICATE_CHAIN`: poiting to a PEM-encoded certificate chain including the public certificate
* `GTS_TLS_CERTIFICATE_KEY`: pointing to a PEM-encoded private key
Additionally, for the Go HTTP client to recognise certificates issued by a custom CA as valid, you'll need to set one of:
* `SSL_CERT_FILE`: pointing to the public key of your custom CA
* `SSL_CERT_DIR`: a `:`-separated list of directories to load CA certificates from
- `SSL_CERT_FILE`: pointing to the public key of your custom CA.
- `SSL_CERT_DIR`: a `:`-separated list of directories to load CA certificates from.
The above `SSL_CERT` variables work on Unix-like systems only, excluding Mac. See https://pkg.go.dev/crypto/x509#SystemCertPool. If you are running your tests on an architecture that doesn't support setting the above variables, you can instead disable TLS certificate verification for the HTTP client entirely by setting `http-client.tls-insecure-skip-verify` to `true` in the config.yaml file.
You'll additionally need functioning DNS for your two instance names, which you can achieve through entries in `/etc/hosts` or by running a local DNS server like [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html).
You'll additionally need functioning DNS for your two instance names which you can achieve through entries in `/etc/hosts` or by running a local DNS server like [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html).
### Updating Swagger docs
GoToSocial uses [go-swagger](https://goswagger.io) to generate Swagger API documentation from code annotations.
If you change Swagger annotations on any of the API paths, you can generate a new Swagger file at `./docs/api/swagger.yaml`, and copy that file to web assets, by running:
You can install go-swagger following the instructions [here](https://goswagger.io/install.html).
If you change Swagger annotations on any of the API paths, you can generate a new Swagger file at `./docs/api/swagger.yaml` by running:
```bash
go run ./vendor/github.com/go-swagger/go-swagger/cmd/swagger \
generate spec --scan-models --exclude-deps -o docs/api/swagger.yaml \
&& cp docs/api/swagger.yaml web/assets/swagger.yaml
swagger generate spec --scan-models --exclude-deps -o docs/api/swagger.yaml
```
You shouldn't need to install go-swagger to run this command, as it's already included in the `vendor` folder.
### CI/CD configuration
GoToSocial uses [Woodpecker CI](https://woodpecker-ci.org/) for CI/CD tasks like running tests, linting, and building Docker containers.
GoToSocial uses [Drone](https://www.drone.io/) for CI/CD tasks like running tests, linting, and building Docker containers.
These runs are integrated with Codeberg, and will be run on opening a pull request or merging into main.
These runs are integrated with GitHub, and will be run on opening a pull request or merging into main.
The `woodpecker` pipeline files are in the `.woodpecker` directory of this repository — these define how and when Woodpecker should run.
The Drone instance for GoToSocial is [here](https://drone.superseriousbusiness.org/superseriousbusiness/gotosocial).
The Woodpecker instance for GoToSocial is [here](https://woodpecker.superseriousbusiness.org/repos/2).
The `drone.yml` file is [here](./.drone.yml) — this defines how and when Drone should run. Documentation for Drone is [here](https://docs.drone.io/).
Documentation for Woodpecker is [here](https://woodpecker-ci.org/docs/intro).
It is worth noting that the `drone.yml` file must be signed by the Drone admin account to be considered valid. This must be done every time the file is changed. This is to prevent tampering and hijacking of the Drone instance. See [here](https://docs.drone.io/signature/).
## Other Useful Stuff
Various bits and bobs.
### Running migrations on a Postgres DB backup locally
It may be useful when testing or debugging migrations to be able to run them against a copy of a real instance's Postgres database locally.
Basic steps for this:
First dump the Postgres database on the remote machine, and copy the dump over to your development machine.
Now create a local Postgres container and mount the dump into it with, for example:
To sign the file, first install and setup the [drone cli tool](https://docs.drone.io/cli/install/). Then, run:
```bash
docker run -it --name postgres --network host -e POSTGRES_PASSWORD=postgres -v /path/to/db_dump:/db_dump postgres
drone -t PUT_YOUR_DRONE_ADMIN_TOKEN_HERE -s https://drone.superseriousbusiness.org sign superseriousbusiness/gotosocial --save
```
In a separate terminal window, execute a command inside the running container to load the dump into the "postgres" database:
### Release Checklist
```bash
docker exec -it --user postgres postgres psql -X -f /db_dump postgres
```
First things first: If this is a security hot-fix, we'll probably rush through this list, and make a prettier release a few days later.
With the Postgres container still running, run GoToSocial and point it towards the container. Use the appropriate `GTS_HOST` (and `GTS_ACCOUNT_DOMAIN`) values for the instance you dumped:
Now, with that out of the way, here's our Checklist.
```bash
GTS_HOST=example.org \
GTS_DB_TYPE=postgres \
GTS_DB_POSTGRES_CONNECTION_STRING=postgres://postgres:postgres@localhost:5432/postgres \
./gotosocial migrations run
```
GoToSocial follows [Semantic Versioning](https://semver.org/).
So our first concern on the Checklist is:
When you're done messing around, don't forget to remove any containers that you started up, and remove any lingering volumes with `docker volume prune`, else you might end up filling your disk with unused temporary volumes.
- What version are we releasing?
Next we need to check:
- Do the assets have to be rebuilt and committed to the repository.
- Do the swagger docs have to be rebuilt?
On the project management side:
- Are there any issues that have to be moved to a different milestone?
- Are there any things on the [Roadmap](./ROADMAP.md) that can be ticked off?
Once we're happy with our Checklist, we can create the tag, and push it.
And the rest [is automation](./.drone.yml).
We can now head to GitHub, and add some personality to the release notes.
Finally, we make announcements on the all our channels that the release is out!
#### What if something goes wrong?
Sometimes things go awry.
We release a buggy release, we forgot something ­ something important.
If the release is so bad that it's unusable ­ or dangerous! ­ to a great part of our user-base, we can pull.
That is: Delete the tag.
Either way, once we've resolved the issue, we just start from the top of this list again. Version numbers are cheap. It's cheap to burn them.

View file

@ -1,17 +1,26 @@
# syntax=docker/dockerfile:1.3
# Dockerfile reference: https://docs.docker.com/engine/reference/builder/
# stage 1: generate the web/assets/dist bundles
FROM --platform=${BUILDPLATFORM} node:lts-alpine AS bundler
# stage 1: generate up-to-date swagger.yaml to put in the final container
FROM --platform=${BUILDPLATFORM} quay.io/goswagger/swagger:v0.30.4 AS swagger
COPY go.mod /go/src/github.com/superseriousbusiness/gotosocial/go.mod
COPY go.sum /go/src/github.com/superseriousbusiness/gotosocial/go.sum
COPY cmd /go/src/github.com/superseriousbusiness/gotosocial/cmd
COPY internal /go/src/github.com/superseriousbusiness/gotosocial/internal
WORKDIR /go/src/github.com/superseriousbusiness/gotosocial
RUN swagger generate spec -o /go/src/github.com/superseriousbusiness/gotosocial/swagger.yaml --scan-models
# stage 2: generate the web/assets/dist bundles
FROM --platform=${BUILDPLATFORM} node:16.19.1-alpine3.17 AS bundler
COPY web web
RUN yarn --cwd ./web/source install && \
yarn --cwd ./web/source ts-patch install && \
yarn --cwd ./web/source build && \
rm -rf ./web/source
RUN yarn install --cwd web/source && \
BUDO_BUILD=1 node web/source && \
rm -r web/source
# stage 2: build the executor container
FROM --platform=${TARGETPLATFORM} alpine:3.21 AS executor
# stage 3: build the executor container
FROM --platform=${TARGETPLATFORM} alpine:3.17.2 as executor
# switch to non-root user:group for GtS
USER 1000:1000
@ -22,10 +31,9 @@ USER 1000:1000
#
# See https://docs.docker.com/engine/reference/builder/#workdir
#
# First make sure storage + cache exist and are owned by 1000:1000,
# then go back to just /gotosocial, where we'll actually run from.
# First make sure storage exists + is owned by 1000:1000, then go back
# to just /gotosocial, where we'll run from
WORKDIR "/gotosocial/storage"
WORKDIR "/gotosocial/.cache"
WORKDIR "/gotosocial"
# copy the dist binary created by goreleaser or build.sh
@ -33,7 +41,7 @@ COPY --chown=1000:1000 gotosocial /gotosocial/gotosocial
# copy over the web directories with templates, assets etc
COPY --chown=1000:1000 --from=bundler web /gotosocial/web
COPY --chown=1000:1000 ./web/assets/swagger.yaml /gotosocial/web/assets/swagger.yaml
COPY --chown=1000:1000 --from=swagger /go/src/github.com/superseriousbusiness/gotosocial/swagger.yaml web/assets/swagger.yaml
VOLUME [ "/gotosocial/storage", "/gotosocial/.cache" ]
VOLUME [ "/gotosocial/storage" ]
ENTRYPOINT [ "/gotosocial/gotosocial", "server", "start" ]

429
README.md
View file

@ -1,26 +1,20 @@
<!--overview-start-->
# GoToSocial <!-- omit in toc -->
**Update regarding corporate sponsors: we are open to sponsorship arrangements with organizations that align with our values; see the conditions below**
🏳️‍🌈 GoToSocial is an [ActivityPub](https://activitypub.rocks/) social network server, written in Golang. 🏳️‍⚧️
GoToSocial is an [ActivityPub](https://activitypub.rocks/) social network server, written in Golang.
With GoToSocial, you can keep in touch with your friends, post, read, and share images and articles. All without being tracked or advertised to!
<p align="middle">
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/sloth.webp" width="300"/>
<img src="./docs/assets/sloth.png" width="300"/>
</p>
**GoToSocial is still [BETA SOFTWARE](https://en.wikipedia.org/wiki/Software_release_life_cycle#Beta)**. It is already deployable and useable, and it federates cleanly with many other Fediverse servers (not yet all). However, many things are not yet implemented, and there are plenty of bugs! We left alpha stage around September/October 2024, and we intend to exit beta some time around 2026.
**GoToSocial is still [ALPHA SOFTWARE](https://en.wikipedia.org/wiki/Software_release_life_cycle#Alpha)**. It is already deployable and useable, and it federates cleanly with many other Fediverse servers (not yet all). However, many things are not yet implemented, and there are plenty of bugs! We foresee entering beta around the beginning of 2024.
Documentation is at [docs.gotosocial.org](https://docs.gotosocial.org). You can skip straight to the API documentation [here](https://docs.gotosocial.org/en/latest/api/swagger/).
Documentation is at [docs.gotosocial.org](https://docs.gotosocial.org). You can skip straight to the API documentation [here](https://docs.gotosocial.org/en/latest/api/swagger/). To build from source, check the [CONTRIBUTING.md](./CONTRIBUTING.md) file.
To build from source, check the [CONTRIBUTING.md](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md) file.
Here's a screenshot of the instance landing page!
Here's a screenshot of the instance landing page! Check out the project's [official account](https://gts.superseriousbusiness.org/@gotosocial) running on GoToSocial.
![Screenshot of the landing page for the GoToSocial instance goblin.technology. It shows basic information about the instance; number of users and posts etc.](https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/instancesplash.png)
<!--overview-end-->
![Screenshot of the landing page for the GoToSocial instance goblin.technology. It shows basic information about the instance; number of users and posts etc.](./docs/assets/instancesplash.png)
## Table of Contents <!-- omit in toc -->
@ -29,52 +23,41 @@ Here's a screenshot of the instance landing page! Check out the project's [offic
- [History and Status](#history-and-status)
- [Features](#features)
- [Mastodon API compatibility](#mastodon-api-compatibility)
- [Granular post visibility settings](#granular-post-visibility-settings)
- [Reply controls](#reply-controls)
- [Local-only posting](#local-only-posting)
- [RSS feed](#rss-feed)
- [Rich text formatting](#rich-text-formatting)
- [Themes and custom CSS](#themes-and-custom-css)
- [Granular post settings](#granular-post-settings)
- [Customizability for admins](#customizability-for-admins)
- [Easy to run](#easy-to-run)
- [Safety + security features](#safety-security-features)
- [Safety + security features](#safety--security-features)
- [Various federation modes](#various-federation-modes)
- [OIDC integration](#oidc-integration)
- [Backend-first design](#backend-first-design)
- [Alternatives to GoToSocial](#alternatives-to-gotosocial)
- [Wishlist](#wishlist)
- [Getting Started](#getting-started)
- [Third-Party Packaging](#third-party-packaging)
- [Distribution packaging](#distribution-packaging)
- [Self-hosting](#self-hosting)
- [Known Issues](#known-issues)
- [Installing GoToSocial](#installing-gotosocial)
- [Supported Platforms](#supported-platforms)
- [64-bit](#64-bit)
- [BSDs](#bsds)
- [32-bit](#32-bit)
- [OpenBSD](#openbsd)
- [Stable Releases](#stable-releases)
- [Snapshot Releases](#snapshot-releases)
- [Docker](#docker)
- [Binary release .tar.gz](#binary-release-tar-gz)
- [From Source](#from-source)
- [Third-party Packaging](#third-party-packaging)
- [Client App Issues](#client-app-issues)
- [Federation Issues](#federation-issues)
- [Contributing](#contributing)
- [Building](#building)
- [Contact](#contact)
- [Credits](#credits)
- [Libraries](#libraries)
- [Image Attribution and Licensing](#image-attribution-and-licensing)
- [Team](#team)
- [Special Thanks](#special-thanks)
- [Sponsorship + Funding](#sponsorship-funding)
- [Sponsorship + Funding](#sponsorship--funding)
- [Crowdfunding](#crowdfunding)
- [Corporate Sponsorship](#corporate-sponsorship)
- [NLnet](#nlnet)
- [License](#license)
<!--body-1-start-->
## What is GoToSocial?
GoToSocial provides a lightweight, customizable, and safety-focused entryway into the [Fediverse](https://en.wikipedia.org/wiki/Fediverse), and is comparable to (but distinct from) existing projects such as [Mastodon](https://joinmastodon.org/), [Pleroma](https://pleroma.social/), [Friendica](https://friendi.ca), and [PixelFed](https://pixelfed.org/).
If you've ever used something like Twitter or Tumblr (or even Myspace!) GoToSocial will probably feel familiar to you: You can follow people and have followers, you make posts which people can favourite and reply to and share, and you scroll through posts from people you follow using a timeline. You can write long posts or short posts, or just post images, it's up to you. You can also, of course, block people or otherwise limit interactions that you don't want by posting just to your friends.
![Screenshot of the web view of a profile in GoToSocial, showing header and avatar, bio, and numbers of followers/following.](https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/profile1.png)
![Screenshot of the web view of a profile in GoToSocial, showing header and avatar, bio, and numbers of followers/following.](./docs/assets/profile1.png)
**GoToSocial does NOT use recommendation algorithms or collect data about you to suggest content or 'improve your experience'**. The timeline is chronological: whatever you see at the top of your timeline is there because it's *just been posted*, not because it's been selected as interesting (or controversial) based on your personal profile.
@ -86,7 +69,7 @@ GoToSocial doesn't claim to be *better* than any other application, but it offer
Because GoToSocial uses [ActivityPub](https://activitypub.rocks/), you can hang out not just with people on your home server, but with people all over the [Fediverse](https://en.wikipedia.org/wiki/Fediverse), seamlessly.
![the activitypub logo](https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/ap_logo.svg)
![the activitypub logo](docs/assets/ap_logo.svg)
Federation means that your home server is part of a network of servers all over the world that all communicate using the same protocol. Your data is no longer centralized on one company's servers, but resides on your own server and is shared — as you see fit — across a resilient web of servers run by other people.
@ -100,11 +83,7 @@ This project sprang up in February/March 2021 out of a dissatisfaction with the
It began as a solo project, and then picked up steam as more developers became interested and jumped on.
We made our first Alpha release in November 2021. We left Alpha and entered Beta in September/October 2024.
For a detailed view on what's implemented and what's not, and progress made towards [stable release](https://en.wikipedia.org/wiki/Software_release_life_cycle#Stable_release), please see [the roadmap document](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/ROADMAP.md).
---
For a detailed view on what's implemented and what's not, and progress made towards [beta release](https://en.wikipedia.org/wiki/Software_release_life_cycle#Beta), please see [the roadmap document](./ROADMAP.md). The [FAQ](docs/faq.md) contains a higher-level overview.
## Features
@ -112,157 +91,54 @@ For a detailed view on what's implemented and what's not, and progress made towa
The Mastodon API has become the de facto standard for client communication with federated servers, so GoToSocial has implemented and extended the API with custom functionality.
Though most apps that implement the Mastodon API should work, GoToSocial is tested and works reliably with beautiful apps like:
In short, this means full support for modern, beautiful apps like [Tusky](https://tusky.app/) and [Semaphore](https://semaphore.social/).
* [Tusky](https://tusky.app/) for Android
* [Pinafore](https://pinafore.social/) in the browser
* [Feditext](https://github.com/feditext/feditext) (beta) on iOS, iPadOS and macOS
Tusky | Semaphore
:-----------------------------------------------------------:|:------------------------------------------------------------------:
![An image of GoToSocial in Tusky](./docs/assets/tusky.png) | ![An image of GoToSocial in Semaphore](./docs/assets/semaphore.png)
If you've used Mastodon with a third-party app before, you'll find using GoToSocial a breeze.
If you're used to using Mastodon with Tusky or Semaphore, you'll find using GoToSocial a breeze.
### Granular post visibility settings
### Granular post settings
It's important that when you post something, you can choose who sees it.
GoToSocial offers public, unlisted/unlocked, followers-only, and direct posts (slide in DMs! -- with consent).
GoToSocial offers public/unlisted/friends-only/mutuals-only/and direct posts (slide in DMs! -- with consent).
### Reply controls
It also allows you to customize how people interact with your posts:
GoToSocial lets you choose who can reply to your posts, via [interaction policies](https://docs.gotosocial.org/en/latest/user_guide/settings/#default-interaction-policies). You can choose to let anyone reply to your posts, let only your friends reply, and more.
- Local-only posts.
- Rebloggable/boostable toggle.
- 'Likeable' toggle.
- 'Replyable' toggle.
![interaction policies settings](https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/user-settings-interaction-policy-1.png)
### Customizability for admins
### Local-only posting
Plenty of [config options](./example/config.yaml) for admins to play around with, including:
Sometimes you only want to talk to people you share an instance with. GoToSocial supports this via local-only posting, which ensures that your post stays on your instance only, even if it gets boosted. They also do not show up in the web interface, and are not accessible publicly via URL. (Local-only posting is currently dependent on client support.)
### RSS feed
GoToSocial lets you opt-in to exposing your profile as an RSS feed, so that people can subscribe to your public feed without missing a post.
### Rich text formatting
With GoToSocial, you can write posts using the popular, easy-to-use Markdown markup language, which lets you produce rich HTML posts with support for blockquotes, syntax-highlighted code blocks, lists, inline links, and more.
![markdown-formatted post](https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/markdown-post.png)
### Themes and custom CSS
Users can [choose from a variety of fun themes](https://docs.gotosocial.org/en/latest/user_guide/settings/#select-theme) for their profile, or even write their own [custom CSS](https://docs.gotosocial.org/en/latest/user_guide/settings/#custom-css).
It's also easy for admins to [add their own custom themes](https://docs.gotosocial.org/en/latest/admin/themes/) for users to choose from.
<details>
<summary>Show theme examples</summary>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-blurple-dark.png"/>
<figcaption>Blurple dark</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-blurple-light.png"/>
<figcaption>Blurple light</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-brutalist-light.png"/>
<figcaption>Brutalist light</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-brutalist-dark.png"/>
<figcaption>Brutalist dark</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-ecks-pee.png"/>
<figcaption>Ecks pee</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-midnight-trip.png"/>
<figcaption>Midnight trip</figcaption>
</figure>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-moonlight-hunt.png"/>
<figcaption>Moonlight hunt</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-rainforest.png"/>
<figcaption>Rainforest</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-soft.png"/>
<figcaption>Soft</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-solarized-dark.png"/>
<figcaption>Solarized dark</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-solarized-light.png"/>
<figcaption>Solarized light</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-sunset.png"/>
<figcaption>Sunset</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-hacker-dark.png"/>
<figcaption>Hacker dark</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-hacker-light.png"/>
<figcaption>Hacker light</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-programmer-socks-dark.png"/>
<figcaption>Programmer socks dark</figcaption>
</figure>
<hr/>
<figure>
<img src="https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/theme-programmer-socks-light.png"/>
<figcaption>Programmer socks light</figcaption>
</figure>
<hr/>
</details>
- Easily adjustable post length.
- Media upload size settings.
### Easy to run
GoToSocial uses only about 250-350MiB of RAM, and requires very little CPU power, so it plays nice with single-board computers, old laptops and tiny $5/month VPSes.
No external dependencies apart from a database (or just use SQLite!). Simply download the binary + assets (or Docker container), and run.
![Grafana graph showing GoToSocial heap in use hovering around 250MB and spiking occasionally to 400MB-500MB.](https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/docs/overrides/public/getting-started-memory-graph.png)
No external dependencies apart from a database (or just use SQLite!).
Simply download the binary + assets (or Docker container), tweak your configuration, and run.
GoToSocial plays nice with lower-powered machines like Raspberry Pi, old laptops and tiny $5/month VPSes.
### Safety + security features
- Strict privacy enforcement for posts, and strict blocking logic.
- [Choose the visibility of posts on the web view of your profile](https://docs.gotosocial.org/en/latest/user_guide/settings/#visibility-level-of-posts-to-show-on-your-profile).
- [Import, export](https://docs.gotosocial.org/en/latest/admin/settings/#importexport), and [subscribe](https://docs.gotosocial.org/en/latest/admin/domain_permission_subscriptions) to community-created domain allow and domain block lists.
- HTTP signature authentication: GoToSocial requires [HTTP Signatures](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12) when sending and receiving messages, to ensure that your messages can't be tampered with and your identity can't be forged.
- Built-in, automatic support for secure HTTPS with [Let's Encrypt](https://letsencrypt.org/).
- Support for two-factor authentication via time-based one-time passwords (Google authenticator, LastPass authenticator, etc).
- Strict privacy enforcement for posts and strict blocking logic.
- Import and export allow lists and deny lists. Subscribe to community-created block lists (think Ad blocker, but for federation!).
- HTTP signature authentication: GoToSocial requires [HTTP Signatures](https://tools.ietf.org/id/draft-cavage-http-signatures-01.html) when sending and receiving messages, to ensure that your messages can't be tampered with and your identity can't be forged.
### Various federation modes
GoToSocial doesn't apply a one-size-fits-all approach to federation. Who your server federates with should be up to you.
- 'blocklist' mode (default): discover new servers; block servers you don't like.
- 'allowlist' mode (experimental); opt-in to federation with trusted servers.
- 'zero' federation mode; keep your server private (not yet implemented).
[See the docs for more info](https://docs.gotosocial.org/en/latest/admin/federation_modes).
- 'Normal' federation; discover new servers.
- *Allow list*-only federation; choose which servers you talk to (not yet implemented).
- Zero federation; keep your server private (not yet implemented).
### OIDC integration
@ -276,155 +152,86 @@ Instead, like Matrix.org's [Synapse](https://github.com/matrix-org/synapse) proj
On top of this API, web developers are encouraged to build any front-end implementation or mobile application that they wish, whether Tumblr-like, Facebook-like, Twitter-like, or something else entirely.
---
## Wishlist
## Alternatives to GoToSocial
These cool things will be implemented if time allows (because we really want them):
Don't like GtS but still want to use the fediverse? Like GtS but prefer not to use beta software? There are lots of alternatives that might suit you better! Here are some that we know work well (alphabetical order):
- **Groups** and group posting!
- Reputation-based 'slow' federation.
- Community decision-making for federation and moderation actions.
- User-selectable custom templates for rendering public posts:
- Twitter-style
- Blogpost
- Gallery
- Etc.
- [Akkoma](https://akkoma.social/): Full-featured ActivityPub microblogging with emoji reacts and quote posts (Elixir).
- [Honk](https://humungus.tedunangst.com/r/honk/m/activitypub.7): Minimalist, opinionated microblogging with "No likes, no faves, no polls, no stars, no claps, no counts." (Go).
- [Iceshrimp.net](https://iceshrimp.dev/iceshrimp/Iceshrimp.NET): rewrite of Iceshrimp from the ground up (.Net).
- [Mastodon](https://joinmastodon.org/): Actively developed, widely recognized, scaleable ActivityPub microblogging instance (Ruby).
- [Snac2](https://codeberg.org/grunfink/snac2): Simple, minimalistic instance with very low system requirements (portable C).
## Getting Started
---
All docs for installation and configuration are hosted at [docs.gotosocial.org](https://docs.gotosocial.org).
## Known Issues
Since GoToSocial is still in beta, there are plenty of bugs. We use [Codeberg issues](https://codeberg.org/superseriousbusiness/gotosocial/issues?labels=378161) to track these.
Since every ActivityPub server implementation has a slightly different interpretation of the protocol, some servers don't quite federate properly with GoToSocial yet. We're tracking these issues [with the federation label](https://codeberg.org/superseriousbusiness/gotosocial/issues?labels=378188). Eventually, we want to make sure that any implementation that can federate nicely with Mastodon should also be able to federate with GoToSocial.
---
## Installing GoToSocial
Check our [getting started](https://docs.gotosocial.org/en/latest/getting_started/) documentation! And have a peruse of our [releases page](https://codeberg.org/superseriousbusiness/gotosocial/releases).
<!--releases-start-->
### Supported Platforms
While we try to support a reasonable number of architectures and operating systems, it's not always possible to support a given platform due to library constraints or performance issues.
Platforms that we don't officially support *may* still work, but we can't test or guarantee performance or stability.
This is the current status of support offered by GoToSocial for different platforms (if something is unlisted it means we haven't checked yet so we don't know):
| OS | Architecture | Support level | Binary archive | Docker container |
| ------- | ----------------------- | ----------------------------------------- | -------------- | ---------------- |
| Linux | x86-64/AMD64 (64-bit) | 🟢 Full<sup>[1](#64-bit)</sup> | Yes | Yes |
| Linux | Armv8/ARM64 (64-bit) | 🟢 Full<sup>[1](#64-bit)</sup> | Yes | Yes |
| FreeBSD | x86-64/AMD64 (64-bit) | 🟢 Full<sup>[1](#64-bit),[2](#bsds)</sup> | Yes | No |
| FreeBSD | Armv8/ARM64 (64-bit) | 🟢 Full<sup>[1](#64-bit),[2](#bsds)</sup> | Yes | No |
| NetBSD | x86-64/AMD64 (64-bit) | 🟢 Full<sup>[1](#64-bit),[2](#bsds)</sup> | Yes | No |
| NetBSD | Armv8/ARM64 (64-bit) | 🟢 Full<sup>[1](#64-bit),[2](#bsds)</sup> | Yes | No |
| Linux | x86-32/i386 (32-bit) | 🟡 Partial<sup>[3](#32-bit)</sup> | Yes | Yes |
| Linux | Armv7/ARM32 (32-bit) | 🟡 Partial<sup>[3](#32-bit)</sup> | Yes | Yes |
| Linux | Armv6/ARM32 (32-bit) | 🟡 Partial<sup>[3](#32-bit)</sup> | Yes | Yes |
| OpenBSD | Any | 🔴 None<sup>[4](#openbsd)</sup> | No | No |
#### 64-bit
Notes on 64-bit CPU feature requirements:
- x86_64 requires the [x86-64-v2](https://en.wikipedia.org/wiki/X86-64-v2) level instruction sets. (CPUs manufactured after ~2010)
- ARM64 requires no specific features, ARMv8 CPUs (and later) have all required features.
If any of the above features are missing, performance of media processing (and possibly, SQLite) will suffer. In these situations, you may have some success building a binary yourself with the totally **unsupported, experimental** [nowasm](https://docs.gotosocial.org/en/latest/advanced/builds/nowasm/) tag.
#### BSDs
Mostly works, just [a few things to keep in mind](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) with WASM SQLite; check release notes carefully when installing on NetBSD or FreeBSD. If running with Postgres you should have no issues.
#### 32-bit
GtS doesn't work well on 32-bit systems like i386, or Armv6/v7, mainly due to performance of media decoding.
We don't recommend running GtS on 32-bit, but you may have some success either turning off remote media processing altogether, or building a binary yourself with the totally **unsupported, experimental** [nowasm](https://docs.gotosocial.org/en/latest/advanced/builds/nowasm/) tag.
For more guidance, check release notes when trying to install on 32-bit.
#### OpenBSD
Marked as unsupported due to performance issues (no WASM compiler, high memory usage when idle, crashes while processing media).
While we don't support running GtS on OpenBSD, you may have some success building a binary yourself with the totally **unsupported, experimental** [nowasm](https://docs.gotosocial.org/en/latest/advanced/builds/nowasm/) tag.
### Stable Releases
We package our stable releases for both binary builds and Docker containers, so that you don't have to build from source yourself.
The Docker image `docker.io/superseriousbusiness/gotosocial:latest` will always correspond to the latest stable release. Since this tag is overwritten frequently, you may want to use Docker CLI flag `--pull always` to ensure that you always have the most up-to-date image every time you run using this tag. Alternatively, run `docker pull docker.io/superseriousbusiness/gotosocial:latest` manually just before use.
### Snapshot Releases
We also make snapshot builds every time something is merged into the main branch, so you can run from whatever code is on main if you wish.
Please be warned that you do so at your own risk! We try to keep main working properly, but we make absolutely no guarantees. Take a stable release instead if you're unsure.
#### Docker
To run from main using Docker, use the `snapshot` Docker tag. The Docker image `docker.io/superseriousbusiness/gotosocial:snapshot` will always correspond to the latest commit on main that involves changes to Go code or web assets/source. Since this tag is overwritten frequently, you may want to use Docker CLI flag `--pull always` to ensure that you always have the most up-to-date image every time you run using this tag. Alternatively, run `docker pull docker.io/superseriousbusiness/gotosocial:snapshot` manually just before use.
#### Binary release .tar.gz
To run from main using a binary release, download the appropriate .tar.gz file for your architecture from our [self-hosted Minio S3 repository](https://minio.s3.superseriousbusiness.org/browser/gotosocial-snapshots).
Snapshot binary releases in the S3 bucket are keyed by commit hash. To get the latest one, sort by Last Modified, or check out the list of commits [here](https://codeberg.org/superseriousbusiness/gotosocial/commits/main), copy the SHA of the latest one, and paste it in the Minio console filter. Snapshot binary releases are expired after 28 days, to keep our hosting costs down.
### From Source
Instructions for building GoToSocial from source are in the [CONTRIBUTING.md](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md) file.
### Third-party Packaging
## Third-Party Packaging
Thank you so much to the cool people who have put time and energy into packaging GoToSocial!
### Distribution packaging
These packages are not maintained by GoToSocial, so please direct questions and issues to the repository maintainers (and donate to them!).
[![Packaging status](https://repology.org/badge/vertical-allrepos/gotosocial.svg)](https://repology.org/project/gotosocial/versions)
You can also deploy your own instance of GoToSocial with the help of:
### Self-hosting
You can deploy your own instance of GoToSocial with the help of:
- [YunoHost GoToSocial Packaging](https://github.com/YunoHost-Apps/gotosocial_ynh) by [OniriCorpe](https://github.com/OniriCorpe).
- [Ansible Playbook (MASH)](https://github.com/mother-of-all-self-hosting/mash-playbook): The playbook supports a many services, including GoToSocial. [Documentation](https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/services/gotosocial.md)
- [GoToSocial Helm Chart](https://github.com/fSocietySocial/charts/tree/main/charts/gotosocial) by [0hlov3](https://github.com/0hlov3).
- GoToSocial Helm Charts:
- [GoToSocial Helm Chart](https://github.com/fSocietySocial/charts/tree/main/charts/gotosocial) by [0hlov3](https://github.com/0hlov3).
<!--releases-end-->
---
## Known Issues
Since GoToSocial is still in alpha, there are plenty of bugs. We use [GitHub issues](https://github.com/superseriousbusiness/gotosocial/issues?q=is%3Aissue+is%3Aopen+label%3Abug) to track these. The [FAQ](docs/faq.md) also describes some of the features that haven't been implemented yet.
### Client App Issues
GoToSocial works great with Tusky and Semaphore, but some other client applications still need work or have issues connecting to GoToSocial. We're tracking them [right here](https://github.com/superseriousbusiness/gotosocial/projects/5). It's our goal to make any app that's compatible with the Mastodon API work seamlessly with GoToSocial.
### Federation Issues
Since every ActivityPub server implementation has a slightly different interpretation of the protocol, some servers don't quite federate properly with GoToSocial yet. We're tracking these issues [in this project](https://github.com/superseriousbusiness/gotosocial/projects/4). Eventually, we want to make sure that any implementation that can federate nicely with Mastodon should also be able to federate with GoToSocial.
## Contributing
You would like to contribute to GtS? Great! ❤️❤️❤️ Check out the issues page to see if there's anything you intend to jump in on, and read the [CONTRIBUTING.md](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md) file for guidelines and setting up your dev environment.
You would like to contribute to GtS? Great! ❤️❤️❤️ Check out the issues page to see if there's anything you intend to jump in on, and read the [CONTRIBUTING.md](./CONTRIBUTING.md) file for guidelines and setting up your dev environment.
---
## Building
Instructions for building GoToSocial from source are in the [CONTRIBUTING.md](./CONTRIBUTING.md) file.
## Contact
For questions and comments, you can [join our Matrix space](https://matrix.to/#/#gotosocial-space:superseriousbusiness.org) at `#gotosocial-space:superseriousbusiness.org`. This is the quickest way to reach the devs. You can also mail [admin@gotosocial.org](mailto:admin@gotosocial.org).
For bugs and feature requests, please check to see if there's [already an issue](https://codeberg.org/superseriousbusiness/gotosocial/issues), and if not, open one or use one of the above channels to make a request (if you don't have a Codeberg account).
---
For bugs and feature requests, please check to see if there's [already an issue](https://github.com/superseriousbusiness/gotosocial/issues), and if not, open one or use one of the above channels to make a request (if you don't have a Github account).
## Credits
<!--body-1-end-->
### Libraries
The following open source libraries, frameworks, and tools are used by GoToSocial, with gratitude 💕
- [abema/go-mp4](https://github.com/abema/go-mp4); mp4 parsing. [MIT License](https://spdx.org/licenses/MIT.html).
- [buckket/go-blurhash](https://github.com/buckket/go-blurhash); used for generating image blurhashes. [GPL-3.0 License](https://spdx.org/licenses/GPL-3.0-only.html).
- [coreos/go-oidc](https://github.com/coreos/go-oidc); OIDC client library. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html).
- [DmitriyVTitov/size](https://github.com/DmitriyVTitov/size); runtime model memory size calculations. [MIT License](https://spdx.org/licenses/MIT.html).
- [disintegration/imaging](https://github.com/disintegration/imaging); image resizing. [MIT License](https://spdx.org/licenses/MIT.html).
- Gin:
- [gin-contrib/cors](https://github.com/gin-contrib/cors); Gin CORS middleware. [MIT License](https://spdx.org/licenses/MIT.html).
- [gin-contrib/gzip](https://github.com/gin-contrib/gzip); Gin gzip middleware. [MIT License](https://spdx.org/licenses/MIT.html).
- [gin-contrib/sessions](https://github.com/gin-contrib/sessions); Gin sessions middleware. [MIT License](https://spdx.org/licenses/MIT.html).
- [gin-gonic/gin](https://github.com/gin-gonic/gin); speedy router engine. [MIT License](https://spdx.org/licenses/MIT.html).
- [go-fed/httpsig](https://github.com/go-fed/httpsig); secure HTTP signature library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
- [google/uuid](https://github.com/google/uuid); UUID generation. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
- [google/wuffs](https://github.com/google/wuffs); png-stripping code. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html).
- Go-Playground:
- [go-playground/form](https://github.com/go-playground/form); funky form mapping support. [MIT License](https://spdx.org/licenses/MIT.html).
- [go-playground/validator](https://github.com/go-playground/validator); struct validation. [MIT License](https://spdx.org/licenses/MIT.html).
@ -434,24 +241,20 @@ The following open source libraries, frameworks, and tools are used by GoToSocia
- [go-swagger/go-swagger](https://github.com/go-swagger/go-swagger); Swagger OpenAPI spec generation. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html).
- gruf:
- [gruf/go-bytesize](https://codeberg.org/gruf/go-bytesize); byte size parsing / formatting. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-cache](https://codeberg.org/gruf/go-cache); LRU and TTL caches. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-cache](https://codeberg.org/gruf/go-cache); object & result caching. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-debug](https://codeberg.org/gruf/go-debug); debug build tag. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-errors](https://codeberg.org/gruf/go-errors); context-like error w/ value wrapping [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-fastcopy](https://codeberg.org/gruf/go-fastcopy); performant (buffer pooled) I/O copying [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-ffmpreg](https://codeberg.org/gruf/go-ffmpreg); embedded ffmpeg / ffprobe WASM binaries [GPL-3.0 License](https://spdx.org/licenses/GPL-3.0-only.html).
- [gruf/go-errors](https://codeberg.org/gruf/go-errors); performant multi-error checking [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-fastcopy](https://codeberg.org/gruf/go-fastcopy); performant pooled I/O copying [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-kv](https://codeberg.org/gruf/go-kv); log field formatting. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-list](https://codeberg.org/gruf/go-list); generic doubly linked list. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-mutexes](https://codeberg.org/gruf/go-mutexes); safemutex & mutex map. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-runners](https://codeberg.org/gruf/go-runners); synchronization utilities. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-runners](https://codeberg.org/gruf/go-runners); workerpools and synchronization. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-sched](https://codeberg.org/gruf/go-sched); task scheduler. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-split](https://codeberg.org/gruf/go-split); configuration string handling. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-storage](https://codeberg.org/gruf/go-storage); file storage backend (local & s3). [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-structr](https://codeberg.org/gruf/go-structr); struct caching + queueing with automated indexing by field. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-store](https://codeberg.org/gruf/go-store); file storage backend (local & s3). [MIT License](https://spdx.org/licenses/MIT.html).
- [h2non/filetype](https://github.com/h2non/filetype); filetype checking. [MIT License](https://spdx.org/licenses/MIT.html).
- jackc:
- [jackc/pgconn](https://github.com/jackc/pgconn); Postgres driver. [MIT License](https://spdx.org/licenses/MIT.html).
- [jackc/pgx](https://github.com/jackc/pgconn); Postgres driver. [MIT License](https://spdx.org/licenses/MIT.html).
- [jackc/pgx](https://github.com/jackc/pgx); Postgres driver and toolkit. [MIT License](https://spdx.org/licenses/MIT.html).
- [KimMachineGun/automemlimit](https://github.com/KimMachineGun/automemlimit); cgroups memory limit checking. [MIT License](https://spdx.org/licenses/MIT.html).
- [k3a/html2text](https://github.com/k3a/html2text); HTML-to-text conversion. [MIT License](https://spdx.org/licenses/MIT.html).
- [mcuadros/go-syslog](https://github.com/mcuadros/go-syslog); Syslog server library. [MIT License](https://spdx.org/licenses/MIT.html).
- [microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday); HTML user-input sanitization. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
- [miekg/dns](https://github.com/miekg/dns); DNS utilities. [Go License](https://go.dev/LICENSE).
@ -461,17 +264,14 @@ The following open source libraries, frameworks, and tools are used by GoToSocia
- [mvdan.cc/xurls](https://github.com/mvdan/xurls); URL parsing regular expressions. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
- [oklog/ulid](https://github.com/oklog/ulid); sequential, database-friendly ID generation. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html).
- [open-telemetry/opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go); OpenTelemetry API + SDK. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html).
- [pquerna/otp](https://github.com/pquerna/otp); One Time Password utilities. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html).
- spf13:
- [spf13/cobra](https://github.com/spf13/cobra); command-line tooling. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html).
- [spf13/viper](https://github.com/spf13/viper); configuration management. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html).
- [stretchr/testify](https://github.com/stretchr/testify); test framework. [MIT License](https://spdx.org/licenses/MIT.html).
- superseriousbusiness:
- [superseriousbusiness/activity](https://code.superseriousbusiness.org/activity) forked from [go-fed/activity](https://github.com/go-fed/activity); Golang ActivityPub/ActivityStreams library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
- [superseriousbusiness/exif-terminator](https://code.superseriousbusiness.org/exif-terminator); EXIF data removal. [GNU AGPL v3 LICENSE](https://spdx.org/licenses/AGPL-3.0-or-later.html).
- [superseriousbusiness/httpsig](https://code.superseriousbusiness.org/httpsig) forked from [go-fed/httpsig](https://github.com/go-fed/httpsig); secure HTTP signature library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
- [superseriousbusiness/oauth2](https://code.superseriousbusiness.org/oauth2) forked from [go-oauth2/oauth2](https://github.com/go-oauth2/oauth2); OAuth server framework and token handling. [MIT License](https://spdx.org/licenses/MIT.html).
- [temoto/robotstxt](https://github.com/temoto/robotstxt); robots.txt parsing. [MIT License](https://spdx.org/licenses/MIT.html).
- [superseriousbusiness/activity](https://github.com/superseriousbusiness/activity) forked from [go-fed/activity](https://github.com/go-fed/activity); Golang ActivityPub/ActivityStreams library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
- [superseriousbusiness/exif-terminator](https://github.com/superseriousbusiness/exif-terminator); EXIF data removal. [GNU AGPL v3 LICENSE](https://spdx.org/licenses/AGPL-3.0-or-later.html).
- [superseriousbusiness/oauth2](https://github.com/superseriousbusiness/oauth2) forked from [go-oauth2/oauth2](https://github.com/go-oauth2/oauth2); OAuth server framework and token handling. [MIT License](https://spdx.org/licenses/MIT.html).
- [tdewolff/minify](https://github.com/tdewolff/minify); HTML minification for Markdown-submitted posts. [MIT License](https://spdx.org/licenses/MIT.html).
- [uber-go/automaxprocs](https://github.com/uber-go/automaxprocs); GOMAXPROCS automation. [MIT License](https://spdx.org/licenses/MIT.html).
- [ulule/limiter](https://github.com/ulule/limiter); http rate limit middleware. [MIT License](https://spdx.org/licenses/MIT.html).
@ -479,7 +279,6 @@ The following open source libraries, frameworks, and tools are used by GoToSocia
- [wagslane/go-password-validator](https://github.com/wagslane/go-password-validator); password strength validation. [MIT License](https://spdx.org/licenses/MIT.html).
- [yuin/goldmark](https://github.com/yuin/goldmark); markdown parser. [MIT License](https://spdx.org/licenses/MIT.html).
<!--body-2-start-->
### Image Attribution and Licensing
Sloth logo by [Anna Abramek](https://abramek.art/).
@ -488,10 +287,9 @@ Sloth logo by [Anna Abramek](https://abramek.art/).
The Creative Commons Attribution-ShareAlike 4.0 International License license applies specifically to the following files and subdirectories of this repository:
- [sloth logo png](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/web/assets/logo.png)
- [sloth logo webp](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/web/assets/logo.webp)
- [sloth logo svg](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/web/assets/logo.svg)
- [all default avatars](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/web/assets/default_avatars)
- [sloth logo png](./web/assets/logo.png)
- [sloth logo svg](./web/assets/logo.svg)
- [all default avatars](./web/assets/default_avatars)
Under the terms of the license, you are free to:
@ -502,11 +300,10 @@ Under the terms of the license, you are free to:
In alphabetical order (... and order of smell):
- daenney
- f0x \[[donate with liberapay](https://liberapay.com/f0x)\]
- kim \[check out my code @ [codeberg](https://codeberg.org/gruf), or find me @ [@kim](https://k.iim.gay/@kim)\]
- tobi \[[donate with liberapay](https://liberapay.com/GoToSocial/)\]
- vyr
- maloki \[[@maloki@goblin.technology](https://goblin.technology/@maloki)\]
### Special Thanks
@ -514,11 +311,9 @@ A huge thank you to CJ from [go-fed](https://github.com/go-fed/activity): withou
Thanks to everyone who has used GtS, opened an issue, suggested something, given funding, and otherwise encouraged or supported the project!
---
## Sponsorship + Funding
**Update regarding corporate sponsors: we are open to sponsorship arrangements with organizations that align with our values; see the conditions below**
**Please note: GoToSocial has NO CORPORATE SPONSORS and does not desire corporate sponsorship. In addition, we do not take donations from any of the following: adult websites, affiliate and review websites, casinos and gambling, insurance and financial products (credit), pharmacy products, SEO services and social media buying, VPN and proxy services, and essay writing services. Donations from such sources will be automatically rejected.**
### Crowdfunding
@ -534,32 +329,17 @@ Crowdfunded donations to our OpenCollective and Liberapay accounts go towards pa
💕 🦥 💕 Thank you!
### Corporate Sponsorship
GoToSocial is open to sponsorship arrangements with organizations that align with our values. To show our thanks for your support, we will display your logo, website, and a short tagline on the repository and documentation. The following caveats apply to sponsorships:
1. GoToSocial project direction will always remain 100% in the hands of the core team, and will never be dictated or influenced by corporate sponsorship. This is non-negotiable. Corporations are of course free of course to suggest / request features in the same manner as any other user, but they are not given special treatment.
2. Corporate sponsorship is dependent on your organization meeting our team's ethical guidelines. These are not a concrete set of rules but instead boil down to "is your company causing harm?". For example, those in the defense industry need not apply as the simple answer to that question is, "yes!".
If after reading this you are still interested in supporting us, that is wonderful! Please reach out to us at admin@gotosocial.org to further discuss :)
### NLnet
<img src="https://nlnet.nl/logo/NGI/NGIZero-green.hex.svg" width="75" alt="NGIZero logo"/>
Combined with the above crowdfunding sources, 2023 Alpha development of GoToSocial was funded by a 50,000 EUR grant from the [NGI0 Entrust Fund](https://nlnet.nl/entrust/), via [NLnet](https://nlnet.nl/). See [here](https://nlnet.nl/project/GoToSocial/#ack) for more details. The successful grant application is archived [here](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/archive/nlnet/2022-next-generation-internet-zero.md).
2024 Beta development of GoToSocial is being funded by an additional 50,000 EUR grant from the [NGI0 Entrust Fund](https://nlnet.nl/entrust/), via [NLnet](https://nlnet.nl/).
---
Combined with the above crowdfunding sources, 2023 Alpha development of GoToSocial is also funded by a 50,000 EUR grant from the [NGI0 Entrust Fund](https://nlnet.nl/entrust/), via [NLnet](https://nlnet.nl/). See [here](https://nlnet.nl/project/GoToSocial/#ack) for more details. The successful grant application is archived [here](https://github.com/superseriousbusiness/gotosocial/blob/main/archive/nlnet/2022-next-generation-internet-zero.md).
## License
![the gnu AGPL logo](https://www.gnu.org/graphics/agplv3-155x51.png)
GoToSocial is free software, licensed under the [GNU AGPL v3 LICENSE](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/LICENSE). We encourage forking and changing the code, hacking around with it, and experimenting.
GoToSocial is free software, licensed under the [GNU AGPL v3 LICENSE](LICENSE). We encourage forking and changing the code, hacking around with it, and experimenting.
See [here](https://www.gnu.org/licenses/why-affero-gpl.html) for the differences between AGPL versus GPL licensing, and [here](https://www.gnu.org/licenses/gpl-faq.html) for FAQ's about GPL licenses, including the AGPL.
@ -567,7 +347,6 @@ If you modify the GoToSocial source code, and run that modified code in a way th
> \[I\]f you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software.
Copyright (C) GoToSocial Authors
Copyright (C) 2021-2023 GoToSocial Authors
<!--I'm adding this here to take the crown of having the 1000th commit ~ kim-->
<!--body-2-end-->
(I'm adding this here to take the crown of having the 1000th commit ~ kim)

View file

@ -1,10 +1,10 @@
# Roadmap to Beta <!-- omit in toc -->
This document contains the roadmap for GoToSocial to be considered eligible for its first proper [stable release](https://en.wikipedia.org/wiki/Software_release_life_cycle#Stable_release).
This document contains the roadmap for GoToSocial to be considered eligible for its first [beta release](https://en.wikipedia.org/wiki/Software_release_life_cycle#Beta).
All the info contained in this document is best-guess only. It's useful to have a rough timeline we can direct people to, but things will undoubtedly change along the way; don't hold us to anything in this doc!
Thank you to [NLnet](https://nlnet.nl) for helping to fund the alpha and beta phases of GoToSocial development!
Thank you to [NLnet](https://nlnet.nl) for helping to fund the alpha phase of GoToSocial development and get us moving towards beta!
Big thank you to all of our [Open Collective](https://opencollective.com/gotosocial) and [Liberapay](https://liberapay.com/gotosocial) contributors, who've helped us keep the lights on! 💕
@ -13,12 +13,10 @@ Big thank you to all of our [Open Collective](https://opencollective.com/gotosoc
- [Beta Aims](#beta-aims)
- [Timeline](#timeline)
- [Mid 2023](#mid-2023)
- [Mid/late 2023](#mid-late-2023)
- [Mid/late 2023](#midlate-2023)
- [Late 2023](#late-2023)
- [Early 2024](#early-2024)
- [BETA milestone](#beta-milestone)
- [Remainder 2024 - early 2025](#remainder-2024-early-2025)
- [On the way out of BETA to STABLE RELEASE](#on-the-way-out-of-beta-to-stable-release)
- [Wishlist](#wishlist)
- [And then...](#and-then)
## Beta Aims
@ -47,53 +45,25 @@ What follows is a rough timeline of features that will be implemented on the roa
### Mid 2023
- [x] **Hashtags** -- implement federating hashtags and viewing hashtags to allow users to discover posts that they might be interested in. (Done! https://codeberg.org/superseriousbusiness/gotosocial/pulls/2032).
- **Hashtags** -- implement federating hashtags and viewing hashtags to allow users to discover posts that they might be interested in.
- **Block list subscriptions** -- allow instance admins to subscribe their instance to plaintext domain block lists (much of the work for this is already in place).
- **Direct conversation view** -- allow users to easily page through all direct-message conversations they're a part of.
### Mid/late 2023
- [x] **Polls** -- implementing parsing, creating, and voting in polls. (Done! https://codeberg.org/superseriousbusiness/gotosocial/pulls/2330)
- [x] **Mute posts/threads** -- opt-out of notifications for replies to a thread; no longer show a given post in your timeline. (Done! https://codeberg.org/superseriousbusiness/gotosocial/pulls/2278)
- [x] **Limited peering/allowlists** -- allow instance admins to limit federation with other instances by default. (Done! https://codeberg.org/superseriousbusiness/gotosocial/pulls/2200)
- **Polls** -- implementing parsing, creating, and voting in polls.
- **Mute posts/threads** -- opt-out of notifications for replies to a thread; no longer show a given post in your timeline.
- **Limited peering/allowlists** -- allow instance admins to limit federation with other instances by default.
### Late 2023
- **Move activity** -- use the ActivityPub `Move` activity to support migration of a user's profile across servers.
- **Sign-up flow** -- allow users to submit a sign-up request to an instance; allow admins to moderate sign-up requests.
### Early 2024
- [x] **Move activity** -- use the ActivityPub `Move` activity to support migration of a user's profile across servers.
- [x] **Sign-up flow** -- allow users to submit a sign-up request to an instance; allow admins to moderate sign-up requests.
- **Non-replyable posts** -- design a non-replyable post path for GoToSocial based on https://github.com/mastodon/mastodon/issues/14762#issuecomment-1196889788; allow users to create non-replyable posts.
### BETA milestone
### And then...
Completion of all above features indicates that we are now in the BETA phase of GoToSocial. We foresee this happening around Feb/March 2024. EDIT: It ended up happening in September/October 2024, whoops!
### Remainder 2024 - early 2025
These are provided in no specific order.
- [x] **Filters v2** -- implement v2 of the filters API.
- [x] **Mute accounts** -- mute accounts to prevent their posts showing up in your home timeline (optional: for limited period of time).
- [x] **Non-replyable posts** -- design a non-replyable post path for GoToSocial based on https://github.com/mastodon/mastodon/issues/14762#issuecomment-1196889788; allow users to create non-replyable posts.
- [x] **Block + allow list subscriptions** -- allow instance admins to subscribe their instance to domain block/allow lists.
- [x] **Direct conversation view** -- allow users to easily page through all direct-message conversations they're a part of.
- [x] **Oauth token management** -- create / view / invalidate OAuth tokens via the settings panel.
- [x] **Status EDIT support** -- edit statuses that you've created, without having to delete + redraft. Federate edits out properly.
- [ ] **Fediverse relay support** -- publish posts to relays, pull posts from relays.
- [x] **Two factor authentication (2fa)** -- allow users to enable 2FA for their account via the settings panel, enforce 2FA on login.
- [ ] **Moderation: Append content warning / mark-as-sensitive all content from an instance/account**.
More tbd!
### On the way out of BETA to STABLE RELEASE
Tbd.
## Wishlist
These cool things will be implemented if time allows (because we really want them):
- **Groups** and group posting!
- Reputation-based 'slow' federation.
- Community decision-making for federation and moderation actions.
- User-selectable custom templates for rendering public posts:
- Twitter-style
- Blogpost
- Gallery
- Etc.
BETA TIME baby!

View file

@ -1 +0,0 @@
Please email security issues to: admin@gotosocial.org

View file

@ -95,7 +95,7 @@ todo
### Friendica
Unsure: Friendica and GoToSocial still don't federate properly with one another (https://codeberg.org/superseriousbusiness/gotosocial/issues/169) so it's hard to test this.
Unsure: Friendica and GoToSocial still don't federate properly with one another (https://github.com/superseriousbusiness/gotosocial/issues/169) so it's hard to test this.
## What should GoToSocial do?

View file

@ -10,7 +10,7 @@ GoToSocial
> Website / wiki
https://codeberg.org/superseriousbusiness/gotosocial / https://docs.gotosocial.org
https://github.com/superseriousbusiness/gotosocial / https://docs.gotosocial.org
> Abstract: Can you explain the whole project and its expected outcome(s). (you have 1200 characters)
@ -87,8 +87,8 @@ Thirdly, we want to make GtS as customizable as possible by allowing admins to e
The main technical challenges we foresee on the project are:
1. Ensuring compatibility with other AP servers (see federation issues: https://codeberg.org/superseriousbusiness/gotosocial/issues?labels=378188).
2. Ensuring compatibility with clients that use the Mastodon API (see client compatibility issues: https://codeberg.org/superseriousbusiness/gotosocial/issues?labels=378194).
1. Ensuring compatibility with other AP servers (see here: https://github.com/superseriousbusiness/gotosocial/projects/4).
2. Ensuring compatibility with clients that use the Mastodon API (see here: https://github.com/superseriousbusiness/gotosocial/projects/5).
3. Designing nuanced federation safety features that allow instance admins to screen federation without totally breaking it. This will require careful design discussions and lots of testing.
4. Implementing our own open-source http signature library with a reference implementation of the latest draft of the http signature proposal: https://httpwg.org/http-extensions/draft-ietf-httpbis-message-signatures.html.
5. Writing + maintaining our own extensions to the AP protocol (see below).

View file

@ -10,7 +10,7 @@ GoToSocial
> Website / wiki
https://codeberg.org/superseriousbusiness/gotosocial / https://docs.gotosocial.org
https://github.com/superseriousbusiness/gotosocial / https://docs.gotosocial.org
> Abstract: Can you explain the whole project and its expected outcome(s). (you have 1200 characters)
@ -20,7 +20,7 @@ GtS emphasizes user safety and privacy. Unlike other AP servers, it always requi
GtS values ease of deployment and maintenance; this means low system requirements, simple configuration, minimal external dependencies, and clear documentation. GtS makes it easy + affordable for self-hosting newcomers to set up a Fediverse server on low- (or even solar-) powered equipment they might have lying around at home.
GtS began development in Feb 2021. It is still in Alpha, and we hope to use NLNet funding to bring it up to the Beta phase. The project roadmap (https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/ROADMAP.md) gives more information on what we have planned.
GtS began development in Feb 2021. It is still in Alpha, and we hope to use NLNet funding to bring it up to the Beta phase. The project roadmap (https://github.com/superseriousbusiness/gotosocial/blob/main/ROADMAP.md) gives more information on what we have planned.
> Have you been involved with projects or organisations relevant to this project before? And if so, can you tell us a bit about your contributions? (Optional) This can help us determine if you are the right person to undertake this effort.
@ -45,7 +45,7 @@ Aside from GoToSocial, I've also made small PRs upstream to the ActivityPub libr
Currently, GoToSocial receives about €22/week from LiberaPay donations - https://liberapay.com/gotosocial. I have been paying my own costs for working on the project from my savings, which is unfortunately not sustainable for a lot longer.
The requested NLNet budget will be used to fund the remaining Alpha portion of development, and bring GoToSocial into the Beta phase (see the roadmap - https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/ROADMAP.md). In practical terms, this means paying myself to work full time on the project for one year, and paying for contributions from other developers as well.
The requested NLNet budget will be used to fund the remaining Alpha portion of development, and bring GoToSocial into the Beta phase (see the roadmap - https://github.com/superseriousbusiness/gotosocial/blob/main/ROADMAP.md). In practical terms, this means paying myself to work full time on the project for one year, and paying for contributions from other developers as well.
To pay my living costs + rent I need to make about €2,000/month after tax, working full time. In Belgium, that equates to about €3,000/month, which is €36,000 for one year of work. Naively calculated at 40 hours / week, that's €18.75 per hour.
@ -83,8 +83,8 @@ Thirdly, we want to make GtS as customizable as possible by allowing admins to e
The main technical challenges we foresee on the project are:
1. Ensuring compatibility with other AP servers (see federation issues: https://codeberg.org/superseriousbusiness/gotosocial/issues?labels=378188).
2. Ensuring compatibility with clients that use the Mastodon API (see client compatibility issues: https://codeberg.org/superseriousbusiness/gotosocial/issues?labels=378194).
1. Ensuring compatibility with other AP servers (see here: https://github.com/superseriousbusiness/gotosocial/projects/4).
2. Ensuring compatibility with clients that use the Mastodon API (see here: https://github.com/superseriousbusiness/gotosocial/projects/5).
3. Designing nuanced federation safety features that allow instance admins to screen federation without totally breaking it. This will require careful design discussions and lots of testing.
4. Implementing our own open-source http signature library with a reference implementation of the latest draft of the http signature proposal: https://httpwg.org/http-extensions/draft-ietf-httpbis-message-signatures.html.
5. Writing + maintaining our own extensions to the AP protocol (see below).

View file

@ -1,123 +0,0 @@
# NLnet Grant Application - NGI Zero Commons 2025
This document is the application on behalf of GoToSocial for funding from the [NLnet](https://nlnet.nl) [NGI Zero Commons fund](https://nlnet.nl/commonsfund/), April 2025. See [here](https://nlnet.nl/propose/).
## General Project Information
> Project Name
GoToSocial
> Website / wiki
https://codeberg.org/superseriousbusiness/gotosocial / https://docs.gotosocial.org
> Abstract: Can you explain the whole project and its expected outcome(s). (you have 1200 characters)
GoToSocial (GtS) is an ActivityPub social network server, which provides a lightweight, simple entryway into Fediverse hosting. It is comparable to (but distinct from) projects such as Mastodon, Friendica, and PixelFed.
GtS emphasizes user safety and privacy. Unlike other AP servers, it always requires http signatures, and makes a strong differentiation between 'public' and other kinds of posts. It also makes it very easy for admins to block instances they don't want to interact with, by allowing them to subscribe to block lists or allow lists, and to import blocks, ensuring that users stay safe.
GtS values ease of deployment and maintenance; this means low system requirements, simple configuration, minimal external dependencies, and clear documentation. GtS makes it easy + affordable for self-hosting newcomers to set up a Fediverse server on low- (or even solar-) powered equipment they might have lying around at home.
GtS began development in Feb 2021, and entered Beta in 2024. We hope to use NLnet funding to continue tuning performance and adding features as we work towards a 1.0 release.
> Have you been involved with projects or organisations relevant to this project before? And if so, can you tell us a bit about your contributions? (Optional) This can help us determine if you are the right person to undertake this effort. (max 2500 characters)
I have been working on GoToSocial since the beginning of the project, first independently, paying myself from my savings, and then thanks to two previous grants from NLnet. My colleague Kim has a similar trajectory, as they now work full time on the project, again thanks to NLnet.
Over the last years we have put many thousands of hours of work into the project: writing code and documentation, fixing bugs, communicating with contributors and doing code review, deploying infrastructure for project builds and discussion, doing project planning, and answering user questions.
Aside from our work on GoToSocial, we also maintain a fork of the Activity library (https://codeberg.org/superseriousbusiness/activity), a fork of the standalone Mastodon frontend customized for GoToSocial (https://codeberg.org/superseriousbusiness/masto-fe-standalone, deployed at https://masto-fe.superseriousbusiness.org), and Kim in particular maintains a large amount of libraries used by the project (https://codeberg.org/gruf), particularly go-ffmpreg (https://codeberg.org/gruf/go-ffmpreg).
## Requested Support
> Explain what the requested budget will be used for?
> Does the project have other funding sources, both past and present?
> (If you want, you can in addition attach a budget at the bottom of the form)
> Explain costs for hardware, human labor (including rates used), travel cost to technical meetings, etc. (max 2500 characters, be concise)
GoToSocial has received two NLnet grants previously, one in 2022-2023, for 50k euros, and another one in 2024-2025, for 75k euros, which we shared between the two of us. These grants went towards paying living costs for myself and Kim (rent, groceries, utilities, taxes, etc), while we both worked full time on the project.
For our first grant we underbudgeted our own costs and ended up underpaying ourselves. We better estimated the second grant, but still had some issues doing more work than we budgeted for, taking account of bug fixes, extra features, and release coordination.
With the benefit of experience, this time we intend to budget more sensibly so that we don't end up going into our overdrafts before the end of each milestone, hence we are asking for a larger amount of funding: 100k euros in total, or 50k euros each per year. This amount of money is comparable to the rate for a mid/senior-level developer in the countries we live in, and should allow us to pay our costs without panic.
Happily, we receive a decent amount of money per month via OpenCollective (https://opencollective.com/gotosocial), which allows us to pay for costs (Greenhost.net) for our CI/CD + snapshot distribution server. It also allows us to pay our freelance colleague f0x for the contributions they are able to make when school is not too busy. As such, NLnet money does not need to be used for anything besides mine and Kim's living costs.
This year, we would like to use the NGI Zero Commons grant to fund development of the following efforts (more tbd):
- Add functionality to allow users and admins to configure cleanup of old statuses and accounts from the database, to keep database sizes smaller.
- Implement better threading support when statuses are deleted (ie., store + show status tombstones).
- Improve search performance and add full-text-search (SQLite, Postgres).
- Add additional in-memory caches for frontend object types (statuses, notifications, etc) to reduce database calls and improve response times.
- Add support for subscribing to relays, and allowing GoToSocial itself to act as a relay, improving connectivity with other instances.
- Add additional federation controls (silence/mute/limit instances).
- Add an opt-in profile directory to make it easier to discover accounts on an instance.
- Implement admin panel section to track unreachable instances, so that admins can tell whether another instance has shut down, and take appropriate action.
> Compare your own project with existing or historical efforts
> E.g. what is new, more thorough or otherwise different. (max 4000 characters, be concise)
ActivityPub is now a popular protocol, with a proliferation of AP implementations like Mastodon, Akkoma, Lemmy, Misskey (and its many forks), Pixelfed, WriteFreely, Wordpress, and more, each with its own focus and purpose. As strong as many of these implementations are, however, they are not without their downsides.
For instance, Mastodon and Pixelfed both lean more towards replicating existing corporate social media efforts (Twitter and Instagram respectively), which is offputting for many users. These two implementations are also complex to install and maintain, which puts many would-be admins off hosting their own servers, and leads to a concentration of users on developer-run servers like mastodon.social and pixelfed.social, which breaks the promise of decentralization.
Other ActivityPub-enabled microblogging softwares like Honk and Snac2 have fewer moving parts and lower system requirements, which has led to a surge of deployments of Snac2 in particular. However, their implementation of the Mastodon client API (the de-facto client API of the fediverse, for better or worse), misses some features, and the barebones admin functionality is not user-friendly for people unaccustomed to using the command line.
In addition, some other fediverse projects have a heavy front-end UI which results in a poor experience on low-bandwidth connections or low-powered devices.
GoToSocial's continued aim is to strike a balance between featureful but fairly resource-intensive AP implementations like Mastodon, and lighter and simpler AP implementations like Snac2 and Honk.
Firstly, GtS is small, easy to run, and very well documented. It uses about 200-300MiB of memory on average, so it can be deployed on tiny VPSs for cheap, and on single-board computers. It has no dependencies on things like ffmpeg or on-system SQLite, as everything is virtualized inside the single binary using WASM/Wazero. This means that people who want to run GoToSocial don't need to install or maintain anything else on the host, and it is very easy for third parties like Yunohost to package and distribute.
Secondly, the admin/settings panel offers admins and users the ability to easily customize their profiles and adjust the way that their instance looks, feels, and federates, and to handle day-to-day admin tasks like reviewing reports, blocking or allowing other instances, etc. The web view of profiles is rendered in simple HTML + CSS; Javascript is not required, but is used for progressive enhancement if available. This makes it relatively easy to view a GoToSocial profile on a mobile device with poor connection, on a lower-end laptop etc.
Thirdly, it provides strong safety features thanks to strict block implementation, always-on HTTP signature authentication, interaction controls, and allowing users to choose what visibility of level of posts can be viewed on their profile. The allowlist subscription functionality we added in 2024 is also critical here, as it allows groups of instances to easily federate only with each other, forming "islands" that can be more easily moderated than fully open federation.
Fourthly, we have been -- and continue to be -- fastidious with our Mastodon client API implementation, which means that GoToSocial can be used with a wide variety of clients that provide different experiences to the user. It even works with apps for Pixelfed, so user's can use a GoToSocial account for 'gram-style media-only posting.
> What are significant technical challenges you expect to solve during the project, if any? (optional but recommended, max 5000 characters)
Many of the technical challenges we expect to overcome during this development period are specific to the development efforts we will undertake as part of this grant:
- Status cleanup: Ensure that cleanup processes are capable of being regularly scheduled asynchronously, while not consuming all available server resources (with what will likely involve scanning the entirety of our largest database table!). This may require some clever indexing and/or marking of statuses as expirable in advance.
- Status tombstones: figure out whether we can write a migration to retroactively rethread old statuses that have become unlinked due to deletes.
- Relay support: figure out whether adding support for relay mode will require a separate relay binary, and if so, refactor sections of the codebase into libraries that can be shared by that binary.
- Relay support: make sure GoToSocial can subscribe to both existing relay services, as well as GoToSocial relays (this will probably involve lots of dipping into the codebases of existing relay services like fedi buzz, to figure out what they're doing). And vice versa: ensure that existing fedi implementations with relay support can subscribe to GoToSocial relays.
- Relay support: make sure that DB sizes and memory usage don't become too burdensome given the amount of statuses that relays are likely to process compared to a vanilla instance.
- Profile directory: ensure "discoverable" flag is respected; optimize required new database queries to ensure they use existing indexes (or figure out which new ones need to be created).
- Search support: ensure that the same functionality and performance is offered by both Postgres and SQLite; possibly refactor our database wrappers and migration code for this.
- Unreachable instances: develop a reasonable heuristic to determine whether an instance is unreachable; work out the best way of storing this information in the database and presenting it to admins via the settings panel.
Other technical challenges we will (continue to) address in the near future are not related to any specific milestone task:
- Ensure continued compatibility of GoToSocial with other fedi software projects.
- Ensure continued compatibility of GoToSocial with Mastodon API client apps.
- Refactor old parts of the codebase to increase readability and remove bugs.
- Make performance tweaks to the codebase wherever necessary (reduce CPU usage, improve memory usage).
- Increase coverage of our test suites.
- Continue to support seamless database migration from old versions of GoToSocial to newer ones.
- Refactor some of the frontend settings panel code to maximize code reuse and minimize compiled javascript size, before adding lots of new functionality.
- Move our CI/CD infrastructure and code repository from Github to Codeberg with minimal disruption to our work.
> Describe the ecosystem of the project, and how you will engage with relevant actors and promote the outcomes. E.g. which actors will you involve? Who should run or deploy your solution to make it a success? (max 2500 characters, be concise)
Much of the work we do involves debugging and solving interoperability issues with other federated softwares, which requires keeping communication channels open with the maintainers of those, and figuring out who needs to change what in order for the issue to be resolved. We've done that a lot over the last year or so:
- Fixed interop with Bandwagon: https://github.com/EmissarySocial/bandwagon/issues/152
- Fixed interop with Iceshrimp: https://codeberg.org/superseriousbusiness/gotosocial/issues/1947
- Coordinated interop with Mastodon: https://codeberg.org/superseriousbusiness/gotosocial/pulls/3703
- Fixed federation with Gancio: https://codeberg.org/superseriousbusiness/gotosocial/issues/3875
- Alerted Pixelfed of AP serialization issues: https://github.com/pixelfed/pixelfed/issues/5642
- Cajoled Bluesky into adding user-agent headers: https://github.com/bluesky-social/atproto/issues/3504
- Help out Writefreely with http signature request issues: https://github.com/writefreely/writefreely/issues/661#issuecomment-1951367449
- Debug federation with Lemmy along with one of the Lemmy devs: https://codeberg.org/superseriousbusiness/gotosocial/issues/2697
For GoToSocial-specific extensions to ActivityPub, we've also diligently documented what we've done so far, and exposed a GoToSocial namespace so that remote softwares can easily incorporate GtS extensions if they want to: https://docs.gotosocial.org/en/latest/federation/interaction_policy/, https://gotosocial.org/ns.
This is all part and parcel of our goal for GoToSocial to be a "good citizen" in terms of how it federates, and how we fit into the larger ActivityPub microblogging ecosystem. We intend to keep doing this!
## Attachments
> Attachments: add any additional information about the project that may help us to gain more insight into the proposed effort, for instance a more detailed task description, a justification of costs or relevant endorsements. Attachments should only contain background information, please make sure that the proposal without attachments is self-contained and concise. Don't waste too much time on this. Really.

View file

@ -1,22 +0,0 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import "code.superseriousbusiness.org/gotosocial/internal/id"
func main() { println(id.NewULID()) }

View file

@ -19,125 +19,97 @@ package account
import (
"context"
"errors"
"fmt"
"os"
"text/tabwriter"
"time"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/db/bundb"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/log"
userprocessor "code.superseriousbusiness.org/gotosocial/internal/processing/user"
"code.superseriousbusiness.org/gotosocial/internal/state"
"code.superseriousbusiness.org/gotosocial/internal/util"
"code.superseriousbusiness.org/gotosocial/internal/validate"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/validate"
"golang.org/x/crypto/bcrypt"
)
var (
// check function conformance
_ action.GTSAction = Create
_ action.GTSAction = List
_ action.GTSAction = Confirm
_ action.GTSAction = Promote
_ action.GTSAction = Demote
_ action.GTSAction = Enable
_ action.GTSAction = Disable
_ action.GTSAction = Password
)
func initState(ctx context.Context) (*state.State, error) {
// Create creates a new account in the database using the provided flags.
var Create action.GTSAction = func(ctx context.Context) error {
var state state.State
state.Caches.Init()
if err := state.Caches.Start(); err != nil {
return nil, fmt.Errorf("error starting caches: %w", err)
}
state.Workers.Start()
// Only set state DB connection.
// Don't need Actions or Workers for this (yet).
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return nil, fmt.Errorf("error creating dbConn: %w", err)
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
return &state, nil
}
func stopState(state *state.State) error {
err := state.DB.Close()
state.Caches.Stop()
return err
}
// Create creates a new account and user
// in the database using the provided flags.
func Create(ctx context.Context) error {
state, err := initState(ctx)
if err != nil {
return err
}
defer func() {
// Ensure state gets stopped on return.
if err := stopState(state); err != nil {
log.Error(ctx, err)
}
}()
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
}
if err := validate.Username(username); err != nil {
return err
}
usernameAvailable, err := state.DB.IsUsernameAvailable(ctx, username)
usernameAvailable, err := dbConn.IsUsernameAvailable(ctx, username)
if err != nil {
return err
}
if !usernameAvailable {
return fmt.Errorf("username %s is already in use", username)
}
email := config.GetAdminAccountEmail()
if email == "" {
return errors.New("no email set")
}
if err := validate.Email(email); err != nil {
return err
}
emailAvailable, err := state.DB.IsEmailAvailable(ctx, email)
emailAvailable, err := dbConn.IsEmailAvailable(ctx, email)
if err != nil {
return err
}
if !emailAvailable {
return fmt.Errorf("email address %s is already in use", email)
}
password := config.GetAdminAccountPassword()
if err := validate.Password(password); err != nil {
if password == "" {
return errors.New("no password set")
}
if err := validate.NewPassword(password); err != nil {
return err
}
_, err = state.DB.NewSignup(ctx, gtsmodel.NewSignup{
Username: username,
Email: email,
Password: password,
EmailVerified: true, // Assume cli user wants email marked as verified already.
PreApproved: true, // Assume cli user wants account marked as approved already.
})
return err
}
// List returns all existing local accounts.
func List(ctx context.Context) error {
state, err := initState(ctx)
_, err = dbConn.NewSignup(ctx, username, "", false, email, password, nil, "", "", true, "", false)
if err != nil {
return err
}
users, err := state.DB.GetAllUsers(ctx)
return dbConn.Stop(ctx)
}
// List returns all existing local accounts.
var List action.GTSAction = func(ctx context.Context) error {
var state state.State
state.Caches.Init()
state.Workers.Start()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
users, err := dbConn.GetAllUsers(ctx)
if err != nil {
return err
}
@ -164,279 +136,222 @@ func List(ctx context.Context) error {
for _, u := range users {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", u.Account.Username, u.AccountID, fmtBool(u.Approved), fmtBool(u.Admin), fmtBool(u.Moderator), fmtDate(u.Account.SuspendedAt), fmtDate(u.ConfirmedAt))
}
return w.Flush()
w.Flush()
return nil
}
// Confirm sets a user to Approved, sets Email to the current
// UnconfirmedEmail value, and sets ConfirmedAt to now.
func Confirm(ctx context.Context) error {
state, err := initState(ctx)
// Confirm sets a user to Approved, sets Email to the current UnconfirmedEmail value, and sets ConfirmedAt to now.
var Confirm action.GTSAction = func(ctx context.Context) error {
var state state.State
state.Caches.Init()
state.Workers.Start()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return err
return fmt.Errorf("error creating dbservice: %s", err)
}
defer func() {
// Ensure state gets stopped on return.
if err := stopState(state); err != nil {
log.Error(ctx, err)
}
}()
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
}
if err := validate.Username(username); err != nil {
return err
}
account, err := state.DB.GetAccountByUsernameDomain(ctx, username, "")
a, err := dbConn.GetAccountByUsernameDomain(ctx, username, "")
if err != nil {
return err
}
user, err := state.DB.GetUserByAccountID(ctx, account.ID)
u, err := dbConn.GetUserByAccountID(ctx, a.ID)
if err != nil {
return err
}
user.Approved = func() *bool { a := true; return &a }()
user.Email = user.UnconfirmedEmail
user.ConfirmedAt = time.Now()
user.SignUpIP = nil
return state.DB.UpdateUser(
ctx, user,
"approved",
"email",
"confirmed_at",
"sign_up_ip",
)
updatingColumns := []string{"approved", "email", "confirmed_at"}
approved := true
u.Approved = &approved
u.Email = u.UnconfirmedEmail
u.ConfirmedAt = time.Now()
if err := dbConn.UpdateUser(ctx, u, updatingColumns...); err != nil {
return err
}
return dbConn.Stop(ctx)
}
// Promote sets admin + moderator flags on a user to true.
func Promote(ctx context.Context) error {
state, err := initState(ctx)
// Promote sets a user to admin.
var Promote action.GTSAction = func(ctx context.Context) error {
var state state.State
state.Caches.Init()
state.Workers.Start()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return err
return fmt.Errorf("error creating dbservice: %s", err)
}
defer func() {
// Ensure state gets stopped on return.
if err := stopState(state); err != nil {
log.Error(ctx, err)
}
}()
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
}
if err := validate.Username(username); err != nil {
return err
}
account, err := state.DB.GetAccountByUsernameDomain(ctx, username, "")
a, err := dbConn.GetAccountByUsernameDomain(ctx, username, "")
if err != nil {
return err
}
user, err := state.DB.GetUserByAccountID(ctx, account.ID)
u, err := dbConn.GetUserByAccountID(ctx, a.ID)
if err != nil {
return err
}
user.Admin = func() *bool { a := true; return &a }()
user.Moderator = func() *bool { a := true; return &a }()
return state.DB.UpdateUser(
ctx, user,
"admin", "moderator",
)
admin := true
u.Admin = &admin
if err := dbConn.UpdateUser(ctx, u, "admin"); err != nil {
return err
}
return dbConn.Stop(ctx)
}
// Demote sets admin + moderator flags on a user to false.
func Demote(ctx context.Context) error {
state, err := initState(ctx)
// Demote sets admin on a user to false.
var Demote action.GTSAction = func(ctx context.Context) error {
var state state.State
state.Caches.Init()
state.Workers.Start()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return err
return fmt.Errorf("error creating dbservice: %s", err)
}
defer func() {
// Ensure state gets stopped on return.
if err := stopState(state); err != nil {
log.Error(ctx, err)
}
}()
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
}
if err := validate.Username(username); err != nil {
return err
}
a, err := state.DB.GetAccountByUsernameDomain(ctx, username, "")
a, err := dbConn.GetAccountByUsernameDomain(ctx, username, "")
if err != nil {
return err
}
user, err := state.DB.GetUserByAccountID(ctx, a.ID)
u, err := dbConn.GetUserByAccountID(ctx, a.ID)
if err != nil {
return err
}
user.Admin = func() *bool { a := false; return &a }()
user.Moderator = func() *bool { a := false; return &a }()
return state.DB.UpdateUser(
ctx, user,
"admin", "moderator",
)
admin := false
u.Admin = &admin
if err := dbConn.UpdateUser(ctx, u, "admin"); err != nil {
return err
}
return dbConn.Stop(ctx)
}
// Disable sets Disabled to true on a user.
func Disable(ctx context.Context) error {
state, err := initState(ctx)
var Disable action.GTSAction = func(ctx context.Context) error {
var state state.State
state.Caches.Init()
state.Workers.Start()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return err
return fmt.Errorf("error creating dbservice: %s", err)
}
defer func() {
// Ensure state gets stopped on return.
if err := stopState(state); err != nil {
log.Error(ctx, err)
}
}()
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
}
if err := validate.Username(username); err != nil {
return err
}
account, err := state.DB.GetAccountByUsernameDomain(ctx, username, "")
a, err := dbConn.GetAccountByUsernameDomain(ctx, username, "")
if err != nil {
return err
}
user, err := state.DB.GetUserByAccountID(ctx, account.ID)
u, err := dbConn.GetUserByAccountID(ctx, a.ID)
if err != nil {
return err
}
user.Disabled = util.Ptr(true)
return state.DB.UpdateUser(
ctx, user,
"disabled",
)
}
// Enable sets Disabled to false on a user.
func Enable(ctx context.Context) error {
state, err := initState(ctx)
if err != nil {
disabled := true
u.Disabled = &disabled
if err := dbConn.UpdateUser(ctx, u, "disabled"); err != nil {
return err
}
defer func() {
// Ensure state gets stopped on return.
if err := stopState(state); err != nil {
log.Error(ctx, err)
}
}()
username := config.GetAdminAccountUsername()
if err := validate.Username(username); err != nil {
return err
}
account, err := state.DB.GetAccountByUsernameDomain(ctx, username, "")
if err != nil {
return err
}
user, err := state.DB.GetUserByAccountID(ctx, account.ID)
if err != nil {
return err
}
user.Disabled = util.Ptr(false)
return state.DB.UpdateUser(
ctx, user,
"disabled",
)
return dbConn.Stop(ctx)
}
// Password sets the password of target account.
func Password(ctx context.Context) error {
state, err := initState(ctx)
var Password action.GTSAction = func(ctx context.Context) error {
var state state.State
state.Caches.Init()
state.Workers.Start()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return err
return fmt.Errorf("error creating dbservice: %s", err)
}
defer func() {
// Ensure state gets stopped on return.
if err := stopState(state); err != nil {
log.Error(ctx, err)
}
}()
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
}
if err := validate.Username(username); err != nil {
return err
}
password := config.GetAdminAccountPassword()
if err := validate.Password(password); err != nil {
if password == "" {
return errors.New("no password set")
}
if err := validate.NewPassword(password); err != nil {
return err
}
account, err := state.DB.GetAccountByUsernameDomain(ctx, username, "")
a, err := dbConn.GetAccountByUsernameDomain(ctx, username, "")
if err != nil {
return err
}
user, err := state.DB.GetUserByAccountID(ctx, account.ID)
u, err := dbConn.GetUserByAccountID(ctx, a.ID)
if err != nil {
return err
}
encryptedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("error hashing password: %s", err)
}
user.EncryptedPassword = string(encryptedPassword)
log.Info(ctx, "Updating password; you must restart the server to use the new password.")
return state.DB.UpdateUser(
ctx, user,
"encrypted_password",
)
}
// Disable2FA disables 2FA for target account.
func Disable2FA(ctx context.Context) error {
state, err := initState(ctx)
if err != nil {
return err
}
defer func() {
// Ensure state gets stopped on return.
if err := stopState(state); err != nil {
log.Error(ctx, err)
}
}()
username := config.GetAdminAccountUsername()
if err := validate.Username(username); err != nil {
return err
}
account, err := state.DB.GetAccountByUsernameDomain(ctx, username, "")
if err != nil {
return err
}
user, err := state.DB.GetUserByAccountID(ctx, account.ID)
if err != nil {
return err
}
err = userprocessor.TwoFactorDisable(ctx, state, user)
if err != nil {
return err
}
fmt.Printf("2fa disabled\n")
return nil
u.EncryptedPassword = string(pw)
return dbConn.UpdateUser(ctx, u, "encrypted_password")
}

View file

@ -18,276 +18,148 @@
package media
import (
"bufio"
"context"
"errors"
"fmt"
"os"
"path"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/db"
"code.superseriousbusiness.org/gotosocial/internal/db/bundb"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/paging"
"code.superseriousbusiness.org/gotosocial/internal/state"
"codeberg.org/gruf/go-byteutil"
"codeberg.org/gruf/go-fastpath/v2"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
)
// check function conformance.
var _ action.GTSAction = ListAttachments
var _ action.GTSAction = ListEmojis
// ListAttachments lists local, remote, or all attachment paths.
func ListAttachments(ctx context.Context) error {
list, err := setupList(ctx)
if err != nil {
return err
}
defer func() {
// Ensure lister gets shutdown on exit.
if err := list.shutdown(); err != nil {
log.Errorf(ctx, "error shutting down: %v", err)
}
}()
// List attachment media paths from db.
return list.ListAttachmentPaths(ctx)
}
// ListEmojis lists local, remote, or all emoji filepaths.
func ListEmojis(ctx context.Context) error {
list, err := setupList(ctx)
if err != nil {
return err
}
defer func() {
// Ensure lister gets shutdown on exit.
if err := list.shutdown(); err != nil {
log.Errorf(ctx, "error shutting down: %v", err)
}
}()
// List emoji media paths from db.
return list.ListEmojiPaths(ctx)
}
type list struct {
dbService db.DB
state *state.State
localOnly bool
remoteOnly bool
maxID string
limit int
out *bufio.Writer
}
func (l *list) ListAttachmentPaths(ctx context.Context) error {
// Page reused for iterative
// attachment queries, with
// predefined limit.
var page paging.Page
page.Limit = 500
// Storage base path, used for path building.
basePath := config.GetStorageLocalBasePath()
func (l *list) GetAllMediaPaths(ctx context.Context, filter func(*gtsmodel.MediaAttachment) string) ([]string, error) {
res := make([]string, 0, 100)
for {
// Get next page of media attachments up to max ID.
medias, err := l.state.DB.GetAttachments(ctx, &page)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return fmt.Errorf("failed to fetch media from database: %w", err)
attachments, err := l.dbService.GetAttachments(ctx, l.maxID, l.limit)
if err != nil {
return nil, fmt.Errorf("failed to retrieve media metadata from database: %w", err)
}
// Get current max ID.
maxID := page.Max.Value
for _, a := range attachments {
v := filter(a)
if v != "" {
res = append(res, v)
}
}
// If no media or the same group is returned, we reached end.
if len(medias) == 0 || maxID == medias[len(medias)-1].ID {
// If we got less results than our limit, we've reached the
// last page to retrieve and we can break the loop. If the
// last batch happens to contain exactly the same amount of
// items as the limit we'll end up doing one extra query.
if len(attachments) < l.limit {
break
}
// Use last ID as the next 'maxID'.
maxID = medias[len(medias)-1].ID
page.Max.Value = maxID
switch {
case l.localOnly:
// Only print local media paths.
for _, media := range medias {
if media.RemoteURL == "" {
printMediaPaths(basePath, media)
// Grab the last ID from the batch and set it as the maxID
// that'll be used in the next iteration so we don't get items
// we've already seen.
l.maxID = attachments[len(attachments)-1].ID
}
}
case l.remoteOnly:
// Only print remote media paths.
for _, media := range medias {
if media.RemoteURL != "" {
printMediaPaths(basePath, media)
}
}
default:
// Print all known media paths.
for _, media := range medias {
printMediaPaths(basePath, media)
}
}
}
return nil
}
func (l *list) ListEmojiPaths(ctx context.Context) error {
// Page reused for iterative
// attachment queries, with
// predefined limit.
var page paging.Page
page.Limit = 500
// Storage base path, used for path building.
basePath := config.GetStorageLocalBasePath()
for {
// Get the next page of emoji media up to max ID.
emojis, err := l.state.DB.GetEmojis(ctx, &page)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return fmt.Errorf("failed to fetch emojis from database: %w", err)
}
// Get current max ID.
maxID := page.Max.Value
// If no emojis or the same group is returned, we reached end.
if len(emojis) == 0 || maxID == emojis[len(emojis)-1].ID {
break
}
// Use last ID as the next 'maxID'.
maxID = emojis[len(emojis)-1].ID
page.Max.Value = maxID
switch {
case l.localOnly:
// Only print local emoji paths.
for _, emoji := range emojis {
if emoji.ImageRemoteURL == "" {
printEmojiPaths(basePath, emoji)
}
}
case l.remoteOnly:
// Only print remote emoji paths.
for _, emoji := range emojis {
if emoji.ImageRemoteURL != "" {
printEmojiPaths(basePath, emoji)
}
}
default:
// Print all known emoji paths.
for _, emoji := range emojis {
printEmojiPaths(basePath, emoji)
}
}
}
return nil
return res, nil
}
func setupList(ctx context.Context) (*list, error) {
var (
localOnly = config.GetAdminMediaListLocalOnly()
remoteOnly = config.GetAdminMediaListRemoteOnly()
state state.State
)
var state state.State
// Validate flags.
if localOnly && remoteOnly {
return nil, errors.New(
"local-only and remote-only flags cannot be true at the same time; " +
"choose one or the other, or set neither to list all media",
)
}
// Initialize caches.
state.Caches.Init()
state.Caches.Start()
// Ensure background cache tasks are running.
if err := state.Caches.Start(); err != nil {
return nil, fmt.Errorf("error starting caches: %w", err)
}
state.Workers.Start()
var err error
// Only set state DB connection.
// Don't need Actions or Workers for this.
state.DB, err = bundb.NewBunDBService(ctx, &state)
dbService, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return nil, fmt.Errorf("error creating dbservice: %w", err)
}
state.DB = dbService
return &list{
dbService: dbService,
state: &state,
localOnly: localOnly,
remoteOnly: remoteOnly,
limit: 200,
maxID: "",
out: bufio.NewWriter(os.Stdout),
}, nil
}
func (l *list) shutdown() error {
err := l.state.DB.Close()
func (l *list) shutdown(ctx context.Context) error {
l.out.Flush()
err := l.dbService.Stop(ctx)
l.state.Workers.Stop()
l.state.Caches.Stop()
return err
}
// reusable path building buffer,
// only usable here as we're not
// performing concurrent writes.
var pb fastpath.Builder
// reusable string output buffer,
// only usable here as we're not
// performing concurrent writes.
var outbuf byteutil.Buffer
func printMediaPaths(basePath string, media *gtsmodel.MediaAttachment) {
// Append file path if present.
if media.File.Path != "" {
path := pb.Join(basePath, media.File.Path)
_, _ = outbuf.WriteString(path + "\n")
var ListLocal action.GTSAction = func(ctx context.Context) error {
list, err := setupList(ctx)
if err != nil {
return err
}
// Append thumb path if present.
if media.Thumbnail.Path != "" {
path := pb.Join(basePath, media.Thumbnail.Path)
_, _ = outbuf.WriteString(path + "\n")
defer func() {
// Ensure lister gets shutdown on exit.
if err := list.shutdown(ctx); err != nil {
log.Error(ctx, err)
}
}()
mediaPath := config.GetStorageLocalBasePath()
media, err := list.GetAllMediaPaths(
ctx,
func(m *gtsmodel.MediaAttachment) string {
if m.RemoteURL == "" {
return path.Join(mediaPath, m.File.Path)
}
return ""
})
if err != nil {
return err
}
// Only write if any
// string was prepared.
if outbuf.Len() > 0 {
_, _ = os.Stdout.Write(outbuf.B)
outbuf.Reset()
for _, m := range media {
_, _ = list.out.WriteString(m + "\n")
}
return nil
}
func printEmojiPaths(basePath string, emoji *gtsmodel.Emoji) {
// Append image path if present.
if emoji.ImagePath != "" {
path := pb.Join(basePath, emoji.ImagePath)
_, _ = outbuf.WriteString(path + "\n")
var ListRemote action.GTSAction = func(ctx context.Context) error {
list, err := setupList(ctx)
if err != nil {
return err
}
// Append static path if present.
if emoji.ImageStaticPath != "" {
path := pb.Join(basePath, emoji.ImageStaticPath)
_, _ = outbuf.WriteString(path + "\n")
defer func() {
// Ensure lister gets shutdown on exit.
if err := list.shutdown(ctx); err != nil {
log.Error(ctx, err)
}
}()
media, err := list.GetAllMediaPaths(
ctx,
func(m *gtsmodel.MediaAttachment) string {
return m.RemoteURL
})
if err != nil {
return err
}
// Only write if any
// string was prepared.
if outbuf.Len() > 0 {
_, _ = os.Stdout.Write(outbuf.B)
outbuf.Reset()
for _, m := range media {
_, _ = list.out.WriteString(m + "\n")
}
return nil
}

View file

@ -20,17 +20,14 @@ package prune
import (
"context"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/log"
)
// check function conformance.
var _ action.GTSAction = All
// All performs all media clean actions
func All(ctx context.Context) error {
var All action.GTSAction = func(ctx context.Context) error {
// Setup pruning utilities.
prune, err := setupPrune(ctx)
if err != nil {
@ -39,7 +36,7 @@ func All(ctx context.Context) error {
defer func() {
// Ensure pruner gets shutdown on exit.
if err := prune.shutdown(); err != nil {
if err := prune.shutdown(ctx); err != nil {
log.Error(ctx, err)
}
}()
@ -53,7 +50,7 @@ func All(ctx context.Context) error {
// Perform the actual pruning with logging.
prune.cleaner.Media().All(ctx, days)
prune.cleaner.Emoji().All(ctx, days)
prune.cleaner.Emoji().All(ctx)
// Perform a cleanup of storage (for removed local dirs).
if err := prune.storage.Storage.Clean(ctx); err != nil {

View file

@ -21,13 +21,13 @@ import (
"context"
"fmt"
"code.superseriousbusiness.org/gotosocial/internal/cleaner"
"code.superseriousbusiness.org/gotosocial/internal/db"
"code.superseriousbusiness.org/gotosocial/internal/db/bundb"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/media"
"code.superseriousbusiness.org/gotosocial/internal/state"
gtsstorage "code.superseriousbusiness.org/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/internal/cleaner"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/state"
gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage"
)
type prune struct {
@ -42,17 +42,10 @@ func setupPrune(ctx context.Context) (*prune, error) {
var state state.State
state.Caches.Init()
if err := state.Caches.Start(); err != nil {
return nil, fmt.Errorf("error starting caches: %w", err)
}
state.Caches.Start()
// Scheduler is required for the
// cleaner, but no other workers
// are needed for this CLI action.
state.Workers.StartScheduler()
state.Workers.Start()
// Set state DB connection.
// Don't need Actions for this.
dbService, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return nil, fmt.Errorf("error creating dbservice: %w", err)
@ -81,14 +74,18 @@ func setupPrune(ctx context.Context) (*prune, error) {
}, nil
}
func (p *prune) shutdown() error {
errs := gtserror.NewMultiError(2)
func (p *prune) shutdown(ctx context.Context) error {
var errs gtserror.MultiError
if err := p.dbService.Close(); err != nil {
errs.Appendf("error stopping database: %w", err)
if err := p.storage.Close(); err != nil {
errs.Appendf("error closing storage backend: %v", err)
}
p.state.Workers.Scheduler.Stop()
if err := p.dbService.Stop(ctx); err != nil {
errs.Appendf("error stopping database: %v", err)
}
p.state.Workers.Stop()
p.state.Caches.Stop()
return errs.Combine()

View file

@ -20,17 +20,14 @@ package prune
import (
"context"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/log"
)
// check function conformance.
var _ action.GTSAction = Orphaned
// Orphaned prunes orphaned media from storage.
func Orphaned(ctx context.Context) error {
var Orphaned action.GTSAction = func(ctx context.Context) error {
// Setup pruning utilities.
prune, err := setupPrune(ctx)
if err != nil {
@ -39,7 +36,7 @@ func Orphaned(ctx context.Context) error {
defer func() {
// Ensure pruner gets shutdown on exit.
if err := prune.shutdown(); err != nil {
if err := prune.shutdown(ctx); err != nil {
log.Error(ctx, err)
}
}()

View file

@ -21,17 +21,14 @@ import (
"context"
"time"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/log"
)
// check function conformance.
var _ action.GTSAction = Remote
// Remote prunes old and/or unused remote media.
func Remote(ctx context.Context) error {
var Remote action.GTSAction = func(ctx context.Context) error {
// Setup pruning utilities.
prune, err := setupPrune(ctx)
if err != nil {
@ -40,7 +37,7 @@ func Remote(ctx context.Context) error {
defer func() {
// Ensure pruner gets shutdown on exit.
if err := prune.shutdown(); err != nil {
if err := prune.shutdown(ctx); err != nil {
log.Error(ctx, err)
}
}()

View file

@ -22,26 +22,23 @@ import (
"errors"
"fmt"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/db/bundb"
"code.superseriousbusiness.org/gotosocial/internal/state"
"code.superseriousbusiness.org/gotosocial/internal/trans"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/trans"
)
// check function conformance.
var _ action.GTSAction = Export
// Export exports info from the database into a file
func Export(ctx context.Context) error {
var Export action.GTSAction = func(ctx context.Context) error {
var state state.State
// Only set state DB connection.
// Don't need Actions or Workers for this.
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
exporter := trans.NewExporter(dbConn)
@ -55,5 +52,5 @@ func Export(ctx context.Context) error {
return err
}
return dbConn.Close()
return dbConn.Stop(ctx)
}

View file

@ -22,26 +22,23 @@ import (
"errors"
"fmt"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/db/bundb"
"code.superseriousbusiness.org/gotosocial/internal/state"
"code.superseriousbusiness.org/gotosocial/internal/trans"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/trans"
)
// check function conformance.
var _ action.GTSAction = Import
// Import imports info from a file into the database
func Import(ctx context.Context) error {
var Import action.GTSAction = func(ctx context.Context) error {
var state state.State
// Only set state DB connection.
// Don't need Actions or Workers for this.
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
importer := trans.NewImporter(dbConn)
@ -55,5 +52,5 @@ func Import(ctx context.Context) error {
return err
}
return dbConn.Close()
return dbConn.Stop(ctx)
}

View file

@ -22,21 +22,21 @@ import (
"encoding/json"
"os"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
)
// check function conformance.
var _ action.GTSAction = Config
// Config just prints the collated config out to stdout as json.
func Config(ctx context.Context) (err error) {
var Config action.GTSAction = func(ctx context.Context) (err error) {
var raw map[string]interface{}
// Marshal configuration to a raw JSON map
config.Config(func(cfg *config.Configuration) {
raw = cfg.MarshalMap()
raw, err = cfg.MarshalMap()
})
if err != nil {
return err
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")

View file

@ -1,68 +0,0 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package migration
import (
"context"
"fmt"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/db/bundb"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/state"
)
// check function conformance.
var _ action.GTSAction = Run
// Run will initialize the database, running any available migrations.
func Run(ctx context.Context) error {
var state state.State
defer func() {
if state.DB != nil {
// Lastly, if database service was started,
// ensure it gets closed now all else stopped.
if err := state.DB.Close(); err != nil {
log.Errorf(ctx, "error stopping database: %v", err)
}
}
// Finally reached end of shutdown.
log.Info(ctx, "done! exiting...")
}()
// Initialize caches
state.Caches.Init()
if err := state.Caches.Start(); err != nil {
return fmt.Errorf("error starting caches: %w", err)
}
log.Info(ctx, "starting db service...")
// Open connection to the database now caches started.
dbService, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set DB on state.
state.DB = dbService
return nil
}

View file

@ -22,273 +22,108 @@ import (
"errors"
"fmt"
"net/http"
"net/netip"
"os"
"os/signal"
"runtime"
"strings"
"syscall"
"time"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/admin"
"code.superseriousbusiness.org/gotosocial/internal/api"
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
"code.superseriousbusiness.org/gotosocial/internal/cleaner"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/db/bundb"
"code.superseriousbusiness.org/gotosocial/internal/email"
"code.superseriousbusiness.org/gotosocial/internal/federation"
"code.superseriousbusiness.org/gotosocial/internal/federation/federatingdb"
"code.superseriousbusiness.org/gotosocial/internal/filter/interaction"
"code.superseriousbusiness.org/gotosocial/internal/filter/mutes"
"code.superseriousbusiness.org/gotosocial/internal/filter/spam"
"code.superseriousbusiness.org/gotosocial/internal/filter/status"
"code.superseriousbusiness.org/gotosocial/internal/filter/visibility"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/httpclient"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/media"
"code.superseriousbusiness.org/gotosocial/internal/media/ffmpeg"
"code.superseriousbusiness.org/gotosocial/internal/messages"
"code.superseriousbusiness.org/gotosocial/internal/middleware"
"code.superseriousbusiness.org/gotosocial/internal/oauth"
"code.superseriousbusiness.org/gotosocial/internal/oauth/handlers"
"code.superseriousbusiness.org/gotosocial/internal/observability"
"code.superseriousbusiness.org/gotosocial/internal/oidc"
"code.superseriousbusiness.org/gotosocial/internal/processing"
"code.superseriousbusiness.org/gotosocial/internal/router"
"code.superseriousbusiness.org/gotosocial/internal/state"
gtsstorage "code.superseriousbusiness.org/gotosocial/internal/storage"
"code.superseriousbusiness.org/gotosocial/internal/subscriptions"
"code.superseriousbusiness.org/gotosocial/internal/transport"
"code.superseriousbusiness.org/gotosocial/internal/typeutils"
"code.superseriousbusiness.org/gotosocial/internal/web"
"code.superseriousbusiness.org/gotosocial/internal/webpush"
"github.com/KimMachineGun/automemlimit/memlimit"
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/api"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/middleware"
tlprocessor "github.com/superseriousbusiness/gotosocial/internal/processing/timeline"
"github.com/superseriousbusiness/gotosocial/internal/timeline"
"github.com/superseriousbusiness/gotosocial/internal/tracing"
"github.com/superseriousbusiness/gotosocial/internal/visibility"
"go.uber.org/automaxprocs/maxprocs"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/email"
"github.com/superseriousbusiness/gotosocial/internal/federation"
"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb"
"github.com/superseriousbusiness/gotosocial/internal/gotosocial"
"github.com/superseriousbusiness/gotosocial/internal/httpclient"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/oidc"
"github.com/superseriousbusiness/gotosocial/internal/processing"
"github.com/superseriousbusiness/gotosocial/internal/router"
"github.com/superseriousbusiness/gotosocial/internal/state"
gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/internal/transport"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
"github.com/superseriousbusiness/gotosocial/internal/web"
// Inherit memory limit if set from cgroup
_ "github.com/KimMachineGun/automemlimit"
)
// check function conformance.
var _ action.GTSAction = Maintenance
var _ action.GTSAction = Start
// Maintenance starts and creates a GoToSocial server
// in maintenance mode (returns 503 for most requests).
func Maintenance(ctx context.Context) error {
route, err := router.New(ctx)
if err != nil {
return fmt.Errorf("error creating maintenance router: %w", err)
}
// Route maintenance handlers.
maintenance := web.NewMaintenance()
maintenance.Route(route)
// Start the maintenance router.
if err := route.Start(); err != nil {
return fmt.Errorf("error starting maintenance router: %w", err)
}
// Catch shutdown signals from the OS.
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigs // block until signal received
log.Infof(ctx, "received signal %s, shutting down", sig)
if err := route.Stop(); err != nil {
log.Errorf(ctx, "error stopping router: %v", err)
}
return nil
}
// Start creates and starts a gotosocial server
func Start(ctx context.Context) error {
// Set GOMAXPROCS / GOMEMLIMIT
// to match container limits.
setLimits(ctx)
var (
// Define necessary core variables
// before anything so we can prepare
// defer function for safe shutdown
// depending on what services were
// managed to be started.
state = new(state.State)
route *router.Router
process *processing.Processor
)
defer func() {
// Stop any started caches.
//
// Noop if never started.
state.Caches.Stop()
if route != nil {
// We reached a point where the API router
// was created + setup. Ensure it gets stopped
// first to stop processing new information.
if err := route.Stop(); err != nil {
log.Errorf(ctx, "error stopping router: %v", err)
}
var Start action.GTSAction = func(ctx context.Context) error {
if _, err := maxprocs.Set(maxprocs.Logger(nil)); err != nil {
log.Infof(ctx, "could not set CPU limits from cgroup: %s", err)
}
// Stop any currently running
// worker processes / scheduled
// tasks from being executed.
//
// Noop on unstarted workers.
state.Workers.Stop()
if process != nil {
const timeout = time.Minute
// Use a new timeout context to ensure
// persisting queued tasks does not fail!
// The main ctx is very likely canceled.
ctx := context.WithoutCancel(ctx)
ctx, cncl := context.WithTimeout(ctx, timeout)
defer cncl()
// Now that all the "moving" components have been stopped,
// persist any remaining queued worker tasks to the database.
if err := process.Admin().PersistWorkerQueues(ctx); err != nil {
log.Errorf(ctx, "error persisting worker queues: %v", err)
}
}
if state.DB != nil {
// Lastly, if database service was started,
// ensure it gets closed now all else stopped.
if err := state.DB.Close(); err != nil {
log.Errorf(ctx, "error stopping database: %v", err)
}
}
// Finally reached end of shutdown.
log.Info(ctx, "done! exiting...")
}()
// Create maintenance router.
var err error
route, err = router.New(ctx)
if err != nil {
return fmt.Errorf("error creating maintenance router: %w", err)
}
// Route maintenance handlers.
maintenance := web.NewMaintenance()
maintenance.Route(route)
// Start the maintenance router to handle reqs
// while the instance is starting up / migrating.
if err := route.Start(); err != nil {
return fmt.Errorf("error starting maintenance router: %w", err)
}
// Initialize tracing (noop if not enabled).
if err := observability.InitializeTracing(ctx); err != nil {
return fmt.Errorf("error initializing tracing: %w", err)
}
var state state.State
// Initialize caches
state.Caches.Init()
if err := state.Caches.Start(); err != nil {
return fmt.Errorf("error starting caches: %w", err)
state.Caches.Start()
defer state.Caches.Stop()
// Initialize Tracing
if err := tracing.Initialize(); err != nil {
return fmt.Errorf("error initializing tracing: %w", err)
}
// Open connection to the database now caches started.
dbService, err := bundb.NewBunDBService(ctx, state)
// Open connection to the database
dbService, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set DB on state.
// Set the state DB connection
state.DB = dbService
// Set Actions on state, providing workers to
// Actions as well for triggering side effects.
state.AdminActions = admin.New(dbService, &state.Workers)
// Ensure necessary database instance prerequisites exist.
if err := dbService.CreateInstanceAccount(ctx); err != nil {
return fmt.Errorf("error creating instance account: %s", err)
}
if err := dbService.CreateInstanceInstance(ctx); err != nil {
return fmt.Errorf("error creating instance instance: %s", err)
}
if err := dbService.CreateInstanceApplication(ctx); err != nil {
return fmt.Errorf("error creating instance application: %s", err)
}
// Get the instance account (we'll need this later).
instanceAccount, err := dbService.GetInstanceAccount(ctx, "")
// Open the storage backend
storage, err := gtsstorage.AutoConfig()
if err != nil {
return fmt.Errorf("error retrieving instance account: %w", err)
return fmt.Errorf("error creating storage backend: %w", err)
}
// Open the storage backend according to config.
state.Storage, err = gtsstorage.AutoConfig()
if err != nil {
return fmt.Errorf("error opening storage backend: %w", err)
}
// Set the state storage driver
state.Storage = storage
// Parse http client allow
// and block range exceptions.
ranges, err := parseClientRanges()
if err != nil {
return err
}
// Prepare wrapped httpclient with config.
// Build HTTP client
client := httpclient.New(httpclient.Config{
AllowRanges: ranges.allow,
BlockRanges: ranges.block,
AllowRanges: config.MustParseIPPrefixes(config.GetHTTPClientAllowIPs()),
BlockRanges: config.MustParseIPPrefixes(config.GetHTTPClientBlockIPs()),
Timeout: config.GetHTTPClientTimeout(),
TLSInsecureSkipVerify: config.GetHTTPClientTLSInsecureSkipVerify(),
})
// Compile WASM modules ahead of first use
// to prevent unexpected initial slowdowns.
//
// Note that this can take a bit of memory
// and processing so we perform this much
// later after any database migrations.
log.Info(ctx, "compiling WebAssembly")
if err := compileWASM(ctx); err != nil {
return err
}
// Initialize workers.
state.Workers.Start()
defer state.Workers.Stop()
// Build handlers used in later initializations.
mediaManager := media.NewManager(state)
oauthServer := oauth.New(ctx, state,
handlers.GetValidateURIHandler(ctx),
handlers.GetClientScopeHandler(ctx, state),
handlers.GetAuthorizeScopeHandler(),
handlers.GetInternalErrorHandler(ctx),
handlers.GetResponseErrorHandler(ctx),
handlers.GetUserAuthorizationHandler(),
)
typeConverter := typeutils.NewConverter(state)
visFilter := visibility.NewFilter(state)
muteFilter := mutes.NewFilter(state)
intFilter := interaction.NewFilter(state)
statusFilter := status.NewFilter(state)
spamFilter := spam.NewFilter(state)
federatingDB := federatingdb.New(state, typeConverter, visFilter, intFilter, spamFilter)
transportController := transport.NewController(state, federatingDB, client)
federator := federation.NewFederator(
state,
federatingDB,
transportController,
typeConverter,
visFilter,
intFilter,
mediaManager,
)
mediaManager := media.NewManager(&state)
oauthServer := oauth.New(ctx, dbService)
typeConverter := typeutils.NewConverter(dbService)
filter := visibility.NewFilter(&state)
federatingDB := federatingdb.New(&state, typeConverter)
transportController := transport.NewController(&state, federatingDB, &federation.Clock{}, client)
federator := federation.NewFederator(&state, federatingDB, transportController, typeConverter, mediaManager)
// Decide whether to create a noop email
// sender (won't send emails) or a real one.
@ -307,171 +142,64 @@ func Start(ctx context.Context) error {
}
}
// Get or create a VAPID key pair.
if _, err := dbService.GetVAPIDKeyPair(ctx); err != nil {
return gtserror.Newf("error getting or creating VAPID key pair: %w", err)
}
// Create a Web Push notification sender.
webPushSender := webpush.NewSender(client, state, typeConverter)
// Start the job scheduler
// (this is required for cleaner).
state.Workers.StartScheduler()
// Add a task to the scheduler to sweep caches.
// Frequency = 1 * minute
// Threshold = 60% capacity
if !state.Workers.Scheduler.AddRecurring(
"@cachesweep", // id
time.Time{}, // start
time.Minute, // freq
func(context.Context, time.Time) {
state.Caches.Sweep(60)
},
) {
return fmt.Errorf("error scheduling cache sweep: %w", err)
}
// Create background cleaner.
cleaner := cleaner.New(state)
// Create subscriptions fetcher.
subscriptions := subscriptions.New(
state,
transportController,
typeConverter,
// Initialize timelines.
state.Timelines.Home = timeline.NewManager(
tlprocessor.HomeTimelineGrab(&state),
tlprocessor.HomeTimelineFilter(&state, filter),
tlprocessor.HomeTimelineStatusPrepare(&state, typeConverter),
tlprocessor.SkipInsert(),
)
if err := state.Timelines.Home.Start(); err != nil {
return fmt.Errorf("error starting home timeline: %s", err)
}
// Create the processor using all the
// other services we've created so far.
process = processing.NewProcessor(
cleaner,
subscriptions,
typeConverter,
federator,
oauthServer,
mediaManager,
state,
emailSender,
webPushSender,
visFilter,
muteFilter,
intFilter,
statusFilter,
state.Timelines.List = timeline.NewManager(
tlprocessor.ListTimelineGrab(&state),
tlprocessor.ListTimelineFilter(&state, filter),
tlprocessor.ListTimelineStatusPrepare(&state, typeConverter),
tlprocessor.SkipInsert(),
)
// Schedule background cleaning tasks.
if err := cleaner.ScheduleJobs(); err != nil {
return fmt.Errorf("error scheduling cleaner jobs: %w", err)
if err := state.Timelines.List.Start(); err != nil {
return fmt.Errorf("error starting list timeline: %s", err)
}
// Schedule background subscriptions updating.
if err := subscriptions.ScheduleJobs(); err != nil {
return fmt.Errorf("error scheduling subscriptions jobs: %w", err)
}
// Create the processor using all the other services we've created so far.
processor := processing.NewProcessor(typeConverter, federator, oauthServer, mediaManager, &state, emailSender)
// Initialize the specialized workers pools.
state.Workers.Client.Init(messages.ClientMsgIndices())
state.Workers.Federator.Init(messages.FederatorMsgIndices())
state.Workers.Delivery.Init(client)
state.Workers.Client.Process = process.Workers().ProcessFromClientAPI
state.Workers.Federator.Process = process.Workers().ProcessFromFediAPI
// Now start workers!
state.Workers.Start()
// Schedule notif tasks for all existing poll expiries.
if err := process.Polls().ScheduleAll(ctx); err != nil {
return fmt.Errorf("error scheduling poll expiries: %w", err)
}
// schedule publication tasks for all scheduled statuses.
if err := process.Status().ScheduledStatusesScheduleAll(ctx); err != nil {
return fmt.Errorf("error scheduling status publications: %w", err)
}
// Initialize metrics.
if err := observability.InitializeMetrics(ctx, state); err != nil {
return fmt.Errorf("error initializing metrics: %w", err)
}
// Run advanced migrations.
if err := process.AdvancedMigrations().Migrate(ctx); err != nil {
return err
}
// Set state client / federator worker enqueue functions
state.Workers.EnqueueClientAPI = processor.EnqueueClientAPI
state.Workers.EnqueueFederator = processor.EnqueueFederator
/*
HTTP router initialization
*/
// Close down the maintenance router.
if err := route.Stop(); err != nil {
return fmt.Errorf("error stopping maintenance router: %w", err)
}
// Instantiate the main router.
route, err = router.New(ctx)
router, err := router.New(ctx)
if err != nil {
return fmt.Errorf("error creating main router: %s", err)
return fmt.Errorf("error creating router: %s", err)
}
// Start preparing global middleware
// stack (used for every request).
middlewares := make([]gin.HandlerFunc, 1)
// RequestID middleware must run before tracing!
middlewares[0] = middleware.AddRequestID(config.GetRequestIDHeader())
// Add tracing middleware if enabled.
middlewares := []gin.HandlerFunc{
middleware.AddRequestID(config.GetRequestIDHeader()), // requestID middleware must run before tracing
}
if config.GetTracingEnabled() {
middlewares = append(middlewares, observability.TracingMiddleware())
middlewares = append(middlewares, tracing.InstrumentGin())
}
// Add metrics middleware if enabled.
if config.GetMetricsEnabled() {
middlewares = append(middlewares, observability.MetricsMiddleware())
}
middlewares = append(middlewares, []gin.HandlerFunc{
// note: hooks adding ctx fields must be ABOVE
// the logger, otherwise won't be accessible.
middleware.Logger(config.GetLogClientIP()),
middleware.HeaderFilter(state),
middleware.UserAgent(),
middleware.CORS(),
middleware.ExtraHeaders(),
}...)
// Instantiate Content-Security-Policy
// middleware, with extra URIs.
cspExtraURIs := make([]string, 0)
// Probe storage to check if extra URI is needed in CSP.
// Error here means something is wrong with storage.
storageCSPUri, err := state.Storage.ProbeCSPUri(ctx)
if err != nil {
return fmt.Errorf("error deriving Content-Security-Policy uri from storage: %w", err)
}
// storageCSPUri may be empty string if
// not S3-backed storage; check for this.
if storageCSPUri != "" {
cspExtraURIs = append(cspExtraURIs, storageCSPUri)
}
// Add any extra CSP URIs from config.
cspExtraURIs = append(cspExtraURIs, config.GetAdvancedCSPExtraURIs()...)
// Add CSP to middlewares.
middlewares = append(middlewares, middleware.ContentSecurityPolicy(cspExtraURIs...))
// attach global middlewares which are used for every request
route.AttachGlobalMiddleware(middlewares...)
router.AttachGlobalMiddleware(middlewares...)
// attach global no route / 404 handler to the router
route.AttachNoRouteHandler(func(c *gin.Context) {
apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(errors.New(http.StatusText(http.StatusNotFound))), process.InstanceGetV1)
router.AttachNoRouteHandler(func(c *gin.Context) {
apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(errors.New(http.StatusText(http.StatusNotFound))), processor.InstanceGetV1)
})
// build router modules
@ -493,83 +221,51 @@ func Start(ctx context.Context) error {
return fmt.Errorf("error generating session name for session middleware: %w", err)
}
// Configure our instance cookie policy.
cookiePolicy := apiutil.NewCookiePolicy()
var (
authModule = api.NewAuth(state, process, idp, routerSession, sessionName, cookiePolicy) // auth/oauth paths
clientModule = api.NewClient(state, process) // api client endpoints
healthModule = api.NewHealth(dbService.Ready) // Health check endpoints
fileserverModule = api.NewFileserver(process) // fileserver endpoints
robotsModule = api.NewRobots() // robots.txt endpoint
wellKnownModule = api.NewWellKnown(process) // .well-known endpoints
nodeInfoModule = api.NewNodeInfo(process) // nodeinfo endpoint
activityPubModule = api.NewActivityPub(dbService, process) // ActivityPub endpoints
webModule = web.New(dbService, process, cookiePolicy) // web pages + user profiles + settings panels etc
authModule = api.NewAuth(dbService, processor, idp, routerSession, sessionName) // auth/oauth paths
clientModule = api.NewClient(dbService, processor) // api client endpoints
fileserverModule = api.NewFileserver(processor) // fileserver endpoints
wellKnownModule = api.NewWellKnown(processor) // .well-known endpoints
nodeInfoModule = api.NewNodeInfo(processor) // nodeinfo endpoint
activityPubModule = api.NewActivityPub(dbService, processor) // ActivityPub endpoints
webModule = web.New(dbService, processor) // web pages + user profiles + settings panels etc
)
// Create per-route / per-grouping middlewares.
// create required middleware
// rate limiting
rlLimit := config.GetAdvancedRateLimitRequests()
exceptions := config.GetAdvancedRateLimitExceptions()
clLimit := middleware.RateLimit(rlLimit, exceptions) // client api
s2sLimit := middleware.RateLimit(rlLimit, exceptions) // server-to-server (AP)
fsMainLimit := middleware.RateLimit(rlLimit, exceptions) // fileserver / web templates
fsEmojiLimit := middleware.RateLimit(rlLimit*2, exceptions) // fileserver (emojis only, use high limit)
limit := config.GetAdvancedRateLimitRequests()
clLimit := middleware.RateLimit(limit) // client api
s2sLimit := middleware.RateLimit(limit) // server-to-server (AP)
fsLimit := middleware.RateLimit(limit) // fileserver / web templates
// throttling
cpuMultiplier := config.GetAdvancedThrottlingMultiplier()
retryAfter := config.GetAdvancedThrottlingRetryAfter()
clThrottle := middleware.Throttle(cpuMultiplier, retryAfter) // client api
s2sThrottle := middleware.Throttle(cpuMultiplier, retryAfter)
// server-to-server (AP)
fsThrottle := middleware.Throttle(cpuMultiplier, retryAfter) // fileserver / web templates / emojis
s2sThrottle := middleware.Throttle(cpuMultiplier, retryAfter) // server-to-server (AP)
fsThrottle := middleware.Throttle(cpuMultiplier, retryAfter) // fileserver / web templates
pkThrottle := middleware.Throttle(cpuMultiplier, retryAfter) // throttle public key endpoint separately
// Robots http headers (x-robots-tag).
//
// robotsDisallowAll is used for client API + S2S endpoints
// that definitely should never be indexed by crawlers.
//
// robotsDisallowAIOnly is used for utility endpoints,
// fileserver, and for web endpoints that set their own
// additional robots directives in HTML meta tags.
//
// Other endpoints like .well-known and nodeinfo handle
// robots headers themselves based on configuration.
robotsDisallowAll := middleware.RobotsHeaders("")
robotsDisallowAIOnly := middleware.RobotsHeaders("aiOnly")
// Gzip middleware is applied to all endpoints except
// fileserver (compression too expensive for those),
// health (which really doesn't need compression), and
// metrics (which does its own compression handling that
// is rather annoying to neatly override).
gzip := middleware.Gzip()
gzip := middleware.Gzip() // applied to all except fileserver
// these should be routed in order;
// apply throttling *after* rate limiting
authModule.Route(route, clLimit, clThrottle, robotsDisallowAll, gzip)
clientModule.Route(route, clLimit, clThrottle, robotsDisallowAll, gzip)
healthModule.Route(route, clLimit, clThrottle, robotsDisallowAIOnly)
fileserverModule.Route(route, fsMainLimit, fsThrottle, robotsDisallowAIOnly)
fileserverModule.RouteEmojis(route, instanceAccount.ID, fsEmojiLimit, fsThrottle, robotsDisallowAIOnly)
robotsModule.Route(route, fsMainLimit, fsThrottle, robotsDisallowAIOnly, gzip)
wellKnownModule.Route(route, gzip, s2sLimit, s2sThrottle)
nodeInfoModule.Route(route, s2sLimit, s2sThrottle, gzip)
activityPubModule.Route(route, s2sLimit, s2sThrottle, robotsDisallowAll, gzip)
activityPubModule.RoutePublicKey(route, s2sLimit, pkThrottle, robotsDisallowAll, gzip)
webModule.Route(route, fsMainLimit, fsThrottle, robotsDisallowAIOnly, gzip)
authModule.Route(router, clLimit, clThrottle, gzip)
clientModule.Route(router, clLimit, clThrottle, gzip)
fileserverModule.Route(router, fsLimit, fsThrottle)
wellKnownModule.Route(router, gzip, s2sLimit, s2sThrottle)
nodeInfoModule.Route(router, s2sLimit, s2sThrottle, gzip)
activityPubModule.Route(router, s2sLimit, s2sThrottle, gzip)
activityPubModule.RoutePublicKey(router, s2sLimit, pkThrottle, gzip)
webModule.Route(router, fsLimit, fsThrottle, gzip)
// Finally start the main http server!
if err := route.Start(); err != nil {
return fmt.Errorf("error starting router: %w", err)
gts, err := gotosocial.NewServer(dbService, router, federator, mediaManager)
if err != nil {
return fmt.Errorf("error creating gotosocial service: %s", err)
}
// Fill worker queues from persisted task data in database.
if err := process.Admin().FillWorkerQueues(ctx); err != nil {
return fmt.Errorf("error filling worker queues: %w", err)
if err := gts.Start(ctx); err != nil {
return fmt.Errorf("error starting gotosocial service: %s", err)
}
// catch shutdown signals from the operating system
@ -578,77 +274,11 @@ func Start(ctx context.Context) error {
sig := <-sigs // block until signal received
log.Infof(ctx, "received signal %s, shutting down", sig)
// close down all running services in order
if err := gts.Stop(ctx); err != nil {
return fmt.Errorf("error closing gotosocial service: %s", err)
}
log.Info(ctx, "done! exiting...")
return nil
}
func setLimits(ctx context.Context) {
if _, err := maxprocs.Set(maxprocs.Logger(nil)); err != nil {
log.Warnf(ctx, "could not set CPU limits from cgroup: %s", err)
}
if _, err := memlimit.SetGoMemLimitWithOpts(); err != nil {
if !strings.Contains(err.Error(), "cgroup mountpoint does not exist") {
log.Warnf(ctx, "could not set Memory limits from cgroup: %s", err)
}
}
}
func compileWASM(ctx context.Context) error {
// Use admin-set ffmpeg pool size, and fall
// back to GOMAXPROCS if number 0 or less.
ffPoolSize := config.GetMediaFfmpegPoolSize()
if ffPoolSize <= 0 {
ffPoolSize = runtime.GOMAXPROCS(0)
}
if err := ffmpeg.InitFfmpeg(ctx, ffPoolSize); err != nil {
return gtserror.Newf("error compiling ffmpeg: %w", err)
}
if err := ffmpeg.InitFfprobe(ctx, ffPoolSize); err != nil {
return gtserror.Newf("error compiling ffprobe: %w", err)
}
return nil
}
func parseClientRanges() (
*struct {
allow []netip.Prefix
block []netip.Prefix
},
error,
) {
parseF := func(ips []string, ranges []netip.Prefix, flag string) error {
for i, ip := range ips {
p, err := netip.ParsePrefix(ip)
if err != nil {
return fmt.Errorf("error parsing %s value %s: %w", flag, ip, err)
}
ranges[i] = p
}
return nil
}
allowIPs := config.GetHTTPClientAllowIPs()
allowRanges := make([]netip.Prefix, len(allowIPs))
allowFlag := config.HTTPClientAllowIPsFlag
if err := parseF(allowIPs, allowRanges, allowFlag); err != nil {
return nil, err
}
blockIPs := config.GetHTTPClientBlockIPs()
blockRanges := make([]netip.Prefix, len(blockIPs))
blockFlag := config.HTTPClientBlockIPsFlag
if err := parseF(blockIPs, blockRanges, blockFlag); err != nil {
return nil, err
}
return &struct {
allow []netip.Prefix
block []netip.Prefix
}{
allow: allowRanges,
block: blockRanges,
}, nil
}

View file

@ -1,33 +0,0 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build !debug && !debugenv
package testrig
import (
"context"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
)
// check function conformance.
var _ action.GTSAction = Start
// Start creates and starts a gotosocial testrig server.
// This is only enabled in debug builds, else is nil.
func Start(context.Context) error { return nil }

View file

@ -15,229 +15,138 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build debug || debugenv
package testrig
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"os/signal"
"syscall"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/admin"
"code.superseriousbusiness.org/gotosocial/internal/api"
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
"code.superseriousbusiness.org/gotosocial/internal/cleaner"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/language"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/middleware"
"code.superseriousbusiness.org/gotosocial/internal/observability"
"code.superseriousbusiness.org/gotosocial/internal/oidc"
"code.superseriousbusiness.org/gotosocial/internal/router"
"code.superseriousbusiness.org/gotosocial/internal/state"
"code.superseriousbusiness.org/gotosocial/internal/storage"
"code.superseriousbusiness.org/gotosocial/internal/subscriptions"
"code.superseriousbusiness.org/gotosocial/internal/typeutils"
"code.superseriousbusiness.org/gotosocial/internal/web"
"code.superseriousbusiness.org/gotosocial/testrig"
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/api"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gotosocial"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/middleware"
"github.com/superseriousbusiness/gotosocial/internal/oidc"
tlprocessor "github.com/superseriousbusiness/gotosocial/internal/processing/timeline"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/internal/timeline"
"github.com/superseriousbusiness/gotosocial/internal/tracing"
"github.com/superseriousbusiness/gotosocial/internal/visibility"
"github.com/superseriousbusiness/gotosocial/internal/web"
"github.com/superseriousbusiness/gotosocial/testrig"
)
// check function conformance.
var _ action.GTSAction = Start
// Start creates and starts a gotosocial testrig server
var Start action.GTSAction = func(ctx context.Context) error {
var state state.State
// Start creates and starts a gotosocial testrig server.
// This is only enabled in debug builds, else is nil.
func Start(ctx context.Context) error {
testrig.InitTestConfig()
testrig.InitTestLog()
var (
// Define necessary core variables
// before anything so we can prepare
// defer function for safe shutdown
// depending on what services were
// managed to be started.
state = new(state.State)
route *router.Router
)
defer func() {
// Stop caches with
// background tasks.
state.Caches.Stop()
if route != nil {
// We reached a point where the API router
// was created + setup. Ensure it gets stopped
// first to stop processing new information.
if err := route.Stop(); err != nil {
log.Errorf(ctx, "error stopping router: %v", err)
}
}
// Stop any currently running
// worker processes / scheduled
// tasks from being executed.
testrig.StopWorkers(state)
if state.Storage != nil {
// If storage was created, ensure torn down.
testrig.StandardStorageTeardown(state.Storage)
}
if state.DB != nil {
// Clean up database by
// dropping tables if required.
if !config.GetTestrigSkipDBTeardown() {
testrig.StandardDBTeardown(state.DB)
}
// Lastly, if database service was started,
// ensure it gets closed now all else stopped.
if err := state.DB.Close(); err != nil {
log.Errorf(ctx, "error stopping database: %v", err)
}
}
// Finally reached end of shutdown.
log.Info(ctx, "done! exiting...")
}()
parsedLangs, err := language.InitLangs(config.GetInstanceLanguages().TagStrs())
if err != nil {
return fmt.Errorf("error initializing languages: %w", err)
}
config.SetInstanceLanguages(parsedLangs)
if err := observability.InitializeTracing(ctx); err != nil {
if err := tracing.Initialize(); err != nil {
return fmt.Errorf("error initializing tracing: %w", err)
}
// Initialize caches and database
state.DB = testrig.NewTestDB(state)
// Set Actions on state, providing workers to
// Actions as well for triggering side effects.
state.AdminActions = admin.New(state.DB, &state.Workers)
state.DB = testrig.NewTestDB(&state)
// New test db inits caches so we don't need to do
// that twice, we can just start the initialized caches.
state.Caches.Start()
defer state.Caches.Stop()
// Populate database tables + data if required.
if !config.GetTestrigSkipDBSetup() {
testrig.StandardDBSetup(state.DB, nil)
}
// Get the instance account (we'll need this later).
instanceAccount, err := state.DB.GetInstanceAccount(ctx, "")
if err != nil {
return fmt.Errorf("error retrieving instance account: %w", err)
}
if os.Getenv("GTS_STORAGE_BACKEND") == "s3" {
var err error
state.Storage, err = storage.NewS3Storage()
if err != nil {
return fmt.Errorf("error initializing storage: %w", err)
}
state.Storage, _ = storage.NewS3Storage()
} else {
state.Storage = testrig.NewInMemoryStorage()
}
testrig.StandardStorageSetup(state.Storage, "./testrig/media")
// Initialize workers.
state.Workers.Start()
defer state.Workers.Stop()
// build backend handlers
httpClient := testrig.NewMockHTTPClient(nil, "./testrig/media")
transportController := testrig.NewTestTransportController(state, httpClient)
mediaManager := testrig.NewTestMediaManager(state)
federator := testrig.NewTestFederator(state, transportController, mediaManager)
transportController := testrig.NewTestTransportController(&state, testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) {
r := io.NopCloser(bytes.NewReader([]byte{}))
return &http.Response{
StatusCode: 200,
Body: r,
}, nil
}, ""))
mediaManager := testrig.NewTestMediaManager(&state)
federator := testrig.NewTestFederator(&state, transportController, mediaManager)
emailSender := testrig.NewEmailSender("./web/template/", nil)
webPushSender := testrig.NewWebPushMockSender()
typeConverter := typeutils.NewConverter(state)
typeConverter := testrig.NewTestTypeConverter(state.DB)
filter := visibility.NewFilter(&state)
processor := testrig.NewTestProcessor(state, federator, emailSender, webPushSender, mediaManager)
// Initialize workers.
testrig.StartWorkers(state, processor.Workers())
defer testrig.StopWorkers(state)
// Initialize metrics.
if err := observability.InitializeMetrics(ctx, state); err != nil {
return fmt.Errorf("error initializing metrics: %w", err)
// Initialize timelines.
state.Timelines.Home = timeline.NewManager(
tlprocessor.HomeTimelineGrab(&state),
tlprocessor.HomeTimelineFilter(&state, filter),
tlprocessor.HomeTimelineStatusPrepare(&state, typeConverter),
tlprocessor.SkipInsert(),
)
if err := state.Timelines.Home.Start(); err != nil {
return fmt.Errorf("error starting home timeline: %s", err)
}
// Run advanced migrations.
if err := processor.AdvancedMigrations().Migrate(ctx); err != nil {
return err
state.Timelines.List = timeline.NewManager(
tlprocessor.ListTimelineGrab(&state),
tlprocessor.ListTimelineFilter(&state, filter),
tlprocessor.ListTimelineStatusPrepare(&state, typeConverter),
tlprocessor.SkipInsert(),
)
if err := state.Timelines.List.Start(); err != nil {
return fmt.Errorf("error starting list timeline: %s", err)
}
processor := testrig.NewTestProcessor(&state, federator, emailSender, mediaManager)
/*
HTTP router initialization
*/
route = testrig.NewTestRouter(state.DB)
router := testrig.NewTestRouter(state.DB)
middlewares := []gin.HandlerFunc{
middleware.AddRequestID(config.GetRequestIDHeader()), // requestID middleware must run before tracing
}
if config.GetTracingEnabled() {
middlewares = append(middlewares, observability.TracingMiddleware())
middlewares = append(middlewares, tracing.InstrumentGin())
}
if config.GetMetricsEnabled() {
middlewares = append(middlewares, observability.MetricsMiddleware())
}
middlewares = append(middlewares, []gin.HandlerFunc{
middleware.Logger(config.GetLogClientIP()),
middleware.HeaderFilter(state),
middleware.UserAgent(),
middleware.CORS(),
middleware.ExtraHeaders(),
}...)
// Instantiate Content-Security-Policy
// middleware, with extra URIs.
cspExtraURIs := make([]string, 0)
// Probe storage to check if extra URI is needed in CSP.
// Error here means something is wrong with storage.
storageCSPUri, err := state.Storage.ProbeCSPUri(ctx)
if err != nil {
return fmt.Errorf("error deriving Content-Security-Policy uri from storage: %w", err)
}
// storageCSPUri may be empty string if
// not S3-backed storage; check for this.
if storageCSPUri != "" {
cspExtraURIs = append(cspExtraURIs, storageCSPUri)
}
// Add any extra CSP URIs from config.
cspExtraURIs = append(cspExtraURIs, config.GetAdvancedCSPExtraURIs()...)
// Add CSP to middlewares.
middlewares = append(middlewares, middleware.ContentSecurityPolicy(cspExtraURIs...))
// attach global middlewares which are used for every request
route.AttachGlobalMiddleware(middlewares...)
router.AttachGlobalMiddleware(middlewares...)
// attach global no route / 404 handler to the router
route.AttachNoRouteHandler(func(c *gin.Context) {
router.AttachNoRouteHandler(func(c *gin.Context) {
apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(errors.New(http.StatusText(http.StatusNotFound))), processor.InstanceGetV1)
})
// build router modules
var idp oidc.IDP
var err error
if config.GetOIDCEnabled() {
idp, err = oidc.NewIDP(ctx)
if err != nil {
@ -255,57 +164,33 @@ func Start(ctx context.Context) error {
return fmt.Errorf("error generating session name for session middleware: %w", err)
}
// Configure our instance cookie policy.
cookiePolicy := apiutil.NewCookiePolicy()
var (
authModule = api.NewAuth(state, processor, idp, routerSession, sessionName, cookiePolicy) // auth/oauth paths
clientModule = api.NewClient(state, processor) // api client endpoints
healthModule = api.NewHealth(state.DB.Ready) // Health check endpoints
authModule = api.NewAuth(state.DB, processor, idp, routerSession, sessionName) // auth/oauth paths
clientModule = api.NewClient(state.DB, processor) // api client endpoints
fileserverModule = api.NewFileserver(processor) // fileserver endpoints
robotsModule = api.NewRobots() // robots.txt endpoint
wellKnownModule = api.NewWellKnown(processor) // .well-known endpoints
nodeInfoModule = api.NewNodeInfo(processor) // nodeinfo endpoint
activityPubModule = api.NewActivityPub(state.DB, processor) // ActivityPub endpoints
webModule = web.New(state.DB, processor, cookiePolicy) // web pages + user profiles + settings panels etc
webModule = web.New(state.DB, processor) // web pages + user profiles + settings panels etc
)
// these should be routed in order
authModule.Route(route)
clientModule.Route(route)
healthModule.Route(route)
fileserverModule.Route(route)
fileserverModule.RouteEmojis(route, instanceAccount.ID)
robotsModule.Route(route)
wellKnownModule.Route(route)
nodeInfoModule.Route(route)
activityPubModule.Route(route)
activityPubModule.RoutePublicKey(route)
webModule.Route(route)
authModule.Route(router)
clientModule.Route(router)
fileserverModule.Route(router)
wellKnownModule.Route(router)
nodeInfoModule.Route(router)
activityPubModule.Route(router)
activityPubModule.RoutePublicKey(router)
webModule.Route(router)
// Create background cleaner.
cleaner := cleaner.New(state)
// Schedule background cleaning tasks.
if err := cleaner.ScheduleJobs(); err != nil {
return fmt.Errorf("error scheduling cleaner jobs: %w", err)
gts, err := gotosocial.NewServer(state.DB, router, federator, mediaManager)
if err != nil {
return fmt.Errorf("error creating gotosocial service: %s", err)
}
// Create subscriptions fetcher.
subscriptions := subscriptions.New(
state,
transportController,
typeConverter,
)
// Schedule background subscriptions updating.
if err := subscriptions.ScheduleJobs(); err != nil {
return fmt.Errorf("error scheduling subscriptions jobs: %w", err)
}
// Finally start the main http server!
if err := route.Start(); err != nil {
return fmt.Errorf("error starting router: %w", err)
if err := gts.Start(ctx); err != nil {
return fmt.Errorf("error starting gotosocial service: %s", err)
}
// catch shutdown signals from the operating system
@ -314,5 +199,14 @@ func Start(ctx context.Context) error {
sig := <-sigs
log.Infof(ctx, "received signal %s, shutting down", sig)
testrig.StandardDBTeardown(state.DB)
testrig.StandardStorageTeardown(state.Storage)
// close down all running services in order
if err := gts.Stop(ctx); err != nil {
return fmt.Errorf("error closing gotosocial service: %s", err)
}
log.Info(ctx, "done! exiting...")
return nil
}

View file

@ -18,12 +18,12 @@
package main
import (
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action/admin/account"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action/admin/media"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action/admin/media/prune"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action/admin/trans"
"code.superseriousbusiness.org/gotosocial/internal/config"
"github.com/spf13/cobra"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/admin/account"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/admin/media"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/admin/media/prune"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/admin/trans"
"github.com/superseriousbusiness/gotosocial/internal/config"
)
func adminCommands() *cobra.Command {
@ -108,7 +108,7 @@ func adminCommands() *cobra.Command {
adminAccountDisableCmd := &cobra.Command{
Use: "disable",
Short: "set 'disabled' to true on a local account to prevent it from signing in or posting etc, but don't delete anything",
Short: "prevent a local account from signing in or posting etc, but don't delete anything",
PreRunE: func(cmd *cobra.Command, args []string) error {
return preRun(preRunArgs{cmd: cmd})
},
@ -119,19 +119,6 @@ func adminCommands() *cobra.Command {
config.AddAdminAccount(adminAccountDisableCmd)
adminAccountCmd.AddCommand(adminAccountDisableCmd)
adminAccountEnableCmd := &cobra.Command{
Use: "enable",
Short: "undo a previous disable command by setting 'disabled' to false on a local account",
PreRunE: func(cmd *cobra.Command, args []string) error {
return preRun(preRunArgs{cmd: cmd})
},
RunE: func(cmd *cobra.Command, args []string) error {
return run(cmd.Context(), account.Enable)
},
}
config.AddAdminAccount(adminAccountEnableCmd)
adminAccountCmd.AddCommand(adminAccountEnableCmd)
adminAccountPasswordCmd := &cobra.Command{
Use: "password",
Short: "set a new password for the given local account",
@ -146,19 +133,6 @@ func adminCommands() *cobra.Command {
config.AddAdminAccountPassword(adminAccountPasswordCmd)
adminAccountCmd.AddCommand(adminAccountPasswordCmd)
adminAccountDisable2FACmd := &cobra.Command{
Use: "disable-2fa",
Short: "disable 2fa for the given local account",
PreRunE: func(cmd *cobra.Command, args []string) error {
return preRun(preRunArgs{cmd: cmd})
},
RunE: func(cmd *cobra.Command, args []string) error {
return run(cmd.Context(), account.Disable2FA)
},
}
config.AddAdminAccount(adminAccountDisable2FACmd)
adminAccountCmd.AddCommand(adminAccountDisable2FACmd)
adminCmd.AddCommand(adminAccountCmd)
/*
@ -204,31 +178,29 @@ func adminCommands() *cobra.Command {
ADMIN MEDIA LIST COMMANDS
*/
adminMediaListAttachmentsCmd := &cobra.Command{
Use: "list-attachments",
Short: "list local, remote, or all attachments",
adminMediaListLocalCmd := &cobra.Command{
Use: "list-local",
Short: "admin command to list media on local storage",
PreRunE: func(cmd *cobra.Command, args []string) error {
return preRun(preRunArgs{cmd: cmd})
},
RunE: func(cmd *cobra.Command, args []string) error {
return run(cmd.Context(), media.ListAttachments)
return run(cmd.Context(), media.ListLocal)
},
}
config.AddAdminMediaList(adminMediaListAttachmentsCmd)
adminMediaCmd.AddCommand(adminMediaListAttachmentsCmd)
adminMediaListEmojisLocalCmd := &cobra.Command{
Use: "list-emojis",
Short: "list local, remote, or all emojis",
adminMediaListRemoteCmd := &cobra.Command{
Use: "list-remote",
Short: "admin command to list remote media cached on this instance",
PreRunE: func(cmd *cobra.Command, args []string) error {
return preRun(preRunArgs{cmd: cmd})
},
RunE: func(cmd *cobra.Command, args []string) error {
return run(cmd.Context(), media.ListEmojis)
return run(cmd.Context(), media.ListRemote)
},
}
config.AddAdminMediaList(adminMediaListEmojisLocalCmd)
adminMediaCmd.AddCommand(adminMediaListEmojisLocalCmd)
adminMediaCmd.AddCommand(adminMediaListLocalCmd, adminMediaListRemoteCmd)
/*
ADMIN MEDIA PRUNE COMMANDS

View file

@ -21,10 +21,10 @@ import (
"context"
"fmt"
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/log"
"github.com/spf13/cobra"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/log"
)
type preRunArgs struct {
@ -43,16 +43,16 @@ type preRunArgs struct {
// env vars or cli flag.
func preRun(a preRunArgs) error {
if err := config.BindFlags(a.cmd); err != nil {
return fmt.Errorf("error binding flags: %w", err)
return fmt.Errorf("error binding flags: %s", err)
}
if err := config.LoadConfigFile(); err != nil {
return fmt.Errorf("error loading config file: %w", err)
if err := config.Reload(); err != nil {
return fmt.Errorf("error reloading config: %s", err)
}
if !a.skipValidation {
if err := config.Validate(); err != nil {
return fmt.Errorf("invalid config: %w", err)
return fmt.Errorf("invalid config: %s", err)
}
}
@ -63,18 +63,11 @@ func preRun(a preRunArgs) error {
// The idea here is to take a GTSAction and run it with the given
// context, after initializing any last-minute things like loggers etc.
func run(ctx context.Context, action action.GTSAction) error {
log.SetTimeFormat(config.GetLogTimestampFormat())
// Set the global log level from configuration.
// Set the global log level from configuration
if err := log.ParseLevel(config.GetLogLevel()); err != nil {
return fmt.Errorf("error parsing log level: %w", err)
}
// Set global log output format from configuration.
if err := log.ParseFormat(config.GetLogFormat()); err != nil {
return fmt.Errorf("error parsing log format: %w", err)
}
if config.GetSyslogEnabled() {
// Enable logging to syslog
if err := log.EnableSyslog(

View file

@ -18,8 +18,9 @@
package main
import (
configaction "code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action/debug/config"
"github.com/spf13/cobra"
configaction "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/debug/config"
"github.com/superseriousbusiness/gotosocial/internal/config"
)
func debugCommands() *cobra.Command {
@ -38,6 +39,7 @@ func debugCommands() *cobra.Command {
return run(cmd.Context(), configaction.Config)
},
}
config.AddServerFlags(debugConfigCmd)
debugCmd.AddCommand(debugConfigCmd)
return debugCmd
}

View file

@ -23,9 +23,11 @@ import (
godebug "runtime/debug"
"strings"
_ "code.superseriousbusiness.org/gotosocial/docs"
"code.superseriousbusiness.org/gotosocial/internal/config"
"codeberg.org/gruf/go-debug"
"github.com/spf13/cobra"
_ "github.com/superseriousbusiness/gotosocial/docs"
"github.com/superseriousbusiness/gotosocial/internal/config"
)
// Version is the version of GoToSocial being used.
@ -40,38 +42,41 @@ func main() {
// override version in config store
config.SetSoftwareVersion(version)
rootCmd := new(cobra.Command)
rootCmd.Use = "gotosocial"
rootCmd.Short = "GoToSocial - a fediverse social media server"
rootCmd.Long = "GoToSocial - a fediverse social media server\n\nFor help, see: https://docs.gotosocial.org.\n\nCode: https://codeberg.org/superseriousbusiness/gotosocial"
rootCmd.Version = version
rootCmd.SilenceErrors = true
rootCmd.SilenceUsage = true
// instantiate the root command
rootCmd := &cobra.Command{
Use: "gotosocial",
Short: "GoToSocial - a fediverse social media server",
Long: "GoToSocial - a fediverse social media server\n\nFor help, see: https://docs.gotosocial.org.\n\nCode: https://github.com/superseriousbusiness/gotosocial",
Version: version,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// before running any other cmd funcs, we must load config-path
return config.LoadEarlyFlags(cmd)
},
SilenceErrors: true,
SilenceUsage: true,
}
// Register global flags with root.
config.RegisterGlobalFlags(rootCmd)
// attach global flags to the root command so that they can be accessed from any subcommand
config.AddGlobalFlags(rootCmd)
// Add subcommands with their flags.
// add subcommands
rootCmd.AddCommand(serverCommands())
rootCmd.AddCommand(debugCommands())
rootCmd.AddCommand(adminCommands())
rootCmd.AddCommand(migrationCommands())
// Testrigcmd will only be set when debug is enabled.
if testrigCmd := testrigCommands(); testrigCmd != nil {
rootCmd.AddCommand(testrigCmd)
if debug.DEBUG {
// only add testrig if debug enabled.
rootCmd.AddCommand(testrigCommands())
} else if len(os.Args) > 1 && os.Args[1] == "testrig" {
log.Fatal("gotosocial must be built and run with the DEBUG enviroment variable set to enable and access testrig")
log.Fatalln("gotosocial must be built and run with the DEBUG enviroment variable set to enable and access testrig")
}
// Run the prepared root command.
// run
if err := rootCmd.Execute(); err != nil {
log.Fatalf("error executing command: %s", err)
}
}
// version will build a version string from binary's stored build information.
// It is SemVer-compatible so long as Version is SemVer-compatible.
func version() string {
// Read build information from binary
build, ok := godebug.ReadBuildInfo()
@ -110,5 +115,5 @@ func version() string {
}
}
return strings.Join(info, "+")
return strings.Join(info, " ")
}

View file

@ -1,43 +0,0 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action/migration"
"github.com/spf13/cobra"
)
// migrationCommands returns the 'migrations' subcommand
func migrationCommands() *cobra.Command {
migrationCmd := &cobra.Command{
Use: "migrations",
Short: "gotosocial migrations-related tasks",
}
migrationRunCmd := &cobra.Command{
Use: "run",
Short: "starts and stops the database, running any outstanding migrations",
PreRunE: func(cmd *cobra.Command, args []string) error {
return preRun(preRunArgs{cmd: cmd})
},
RunE: func(cmd *cobra.Command, args []string) error {
return run(cmd.Context(), migration.Run)
},
}
migrationCmd.AddCommand(migrationRunCmd)
return migrationCmd
}

View file

@ -18,8 +18,9 @@
package main
import (
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action/server"
"github.com/spf13/cobra"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/server"
"github.com/superseriousbusiness/gotosocial/internal/config"
)
// serverCommands returns the 'server' subcommand
@ -38,19 +39,7 @@ func serverCommands() *cobra.Command {
return run(cmd.Context(), server.Start)
},
}
config.AddServerFlags(serverStartCmd)
serverCmd.AddCommand(serverStartCmd)
serverMaintenanceCmd := &cobra.Command{
Use: "maintenance",
Short: "start the gotosocial server in maintenance mode (returns 503 for almost all requests)",
PreRunE: func(cmd *cobra.Command, args []string) error {
return preRun(preRunArgs{cmd: cmd})
},
RunE: func(cmd *cobra.Command, args []string) error {
return run(cmd.Context(), server.Maintenance)
},
}
serverCmd.AddCommand(serverMaintenanceCmd)
return serverCmd
}

View file

@ -18,14 +18,11 @@
package main
import (
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action/testrig"
"code.superseriousbusiness.org/gotosocial/internal/config"
"codeberg.org/gruf/go-debug"
"github.com/spf13/cobra"
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/testrig"
)
func testrigCommands() *cobra.Command {
if debug.DEBUG {
testrigCmd := &cobra.Command{
Use: "testrig",
Short: "gotosocial testrig-related tasks",
@ -40,8 +37,5 @@ func testrigCommands() *cobra.Command {
}
testrigCmd.AddCommand(testrigStartCmd)
config.AddTestrig(testrigCmd)
return testrigCmd
}
return nil
}

View file

@ -1,133 +0,0 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"context"
"io"
"os"
"os/signal"
"syscall"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/db/bundb"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/media"
"code.superseriousbusiness.org/gotosocial/internal/media/ffmpeg"
"code.superseriousbusiness.org/gotosocial/internal/state"
"code.superseriousbusiness.org/gotosocial/internal/storage"
"code.superseriousbusiness.org/gotosocial/internal/util"
"codeberg.org/gruf/go-storage/memory"
)
func main() {
ctx := context.Background()
ctx, cncl := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT)
defer cncl()
log.SetLevel(log.INFO)
if len(os.Args) != 3 {
log.Panic(ctx, "Usage: go run ./cmd/process-emoji <input-file> <output-static>")
}
if err := ffmpeg.InitFfprobe(ctx, 1); err != nil {
log.Panic(ctx, err)
}
if err := ffmpeg.InitFfmpeg(ctx, 1); err != nil {
log.Panic(ctx, err)
}
var st storage.Driver
st.Storage = memory.Open(10, true)
var state state.State
state.Storage = &st
state.Caches.Init()
var err error
config.SetHost("example.com")
config.SetStorageBackend("disk")
config.SetStorageLocalBasePath("/tmp/gotosocial")
config.SetDbType("sqlite")
config.SetDbAddress(":memory:")
state.DB, err = bundb.NewBunDBService(ctx, &state)
if err != nil {
log.Panic(ctx, err)
}
if err := state.DB.CreateInstanceAccount(ctx); err != nil {
log.Panicf(ctx, "error creating instance account: %s", err)
}
if err := state.DB.CreateInstanceInstance(ctx); err != nil {
log.Panicf(ctx, "error creating instance instance: %s", err)
}
if err := state.DB.CreateInstanceApplication(ctx); err != nil {
log.Panicf(ctx, "error creating instance application: %s", err)
}
mgr := media.NewManager(&state)
processing, err := mgr.CreateEmoji(ctx,
"emoji",
"example.com",
func(ctx context.Context) (reader io.ReadCloser, err error) {
return os.Open(os.Args[1])
},
media.AdditionalEmojiInfo{
URI: util.Ptr("example.com/emoji"),
},
)
if err != nil {
log.Panic(ctx, err)
}
emoji, err := processing.Load(ctx)
if err != nil {
log.Panic(ctx, err)
}
copyFile(ctx, &st, emoji.ImageStaticPath, os.Args[2])
}
func copyFile(ctx context.Context, st *storage.Driver, key string, path string) {
rc, err := st.GetStream(ctx, key)
if err != nil {
log.Panic(ctx, err)
}
defer rc.Close()
_ = os.Remove(path)
output, err := os.Create(path)
if err != nil {
log.Panic(ctx, err)
}
defer output.Close()
_, err = io.Copy(output, rc)
if err != nil {
log.Panic(ctx, err)
}
}

View file

@ -1,245 +0,0 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"context"
"fmt"
"io"
"os"
"os/signal"
"strings"
"syscall"
"time"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/db/bundb"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/media"
"code.superseriousbusiness.org/gotosocial/internal/media/ffmpeg"
"code.superseriousbusiness.org/gotosocial/internal/state"
"code.superseriousbusiness.org/gotosocial/internal/storage"
"codeberg.org/gruf/go-storage/memory"
)
func main() {
ctx := context.Background()
ctx, cncl := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT)
defer cncl()
log.SetLevel(log.ERROR)
if len(os.Args) != 4 {
log.Panic(ctx, "Usage: go run ./cmd/process-media <input-file> <output-processed> <output-thumbnail>")
}
if err := ffmpeg.InitFfprobe(ctx, 1); err != nil {
log.Panic(ctx, err)
}
if err := ffmpeg.InitFfmpeg(ctx, 1); err != nil {
log.Panic(ctx, err)
}
var st storage.Driver
st.Storage = memory.Open(10, true)
var state state.State
state.Storage = &st
state.Caches.Init()
var err error
config.SetProtocol("http")
config.SetHost("localhost:8080")
config.SetStorageBackend("disk")
config.SetStorageLocalBasePath("/tmp/gotosocial")
config.SetDbType("sqlite")
config.SetDbAddress(":memory:")
state.DB, err = bundb.NewBunDBService(ctx, &state)
if err != nil {
log.Panic(ctx, err)
}
if err := state.DB.CreateInstanceAccount(ctx); err != nil {
log.Panicf(ctx, "error creating instance account: %s", err)
}
if err := state.DB.CreateInstanceInstance(ctx); err != nil {
log.Panicf(ctx, "error creating instance instance: %s", err)
}
if err := state.DB.CreateInstanceApplication(ctx); err != nil {
log.Panicf(ctx, "error creating instance application: %s", err)
}
account, err := state.DB.GetInstanceAccount(ctx, "")
if err != nil {
log.Panic(ctx, err)
}
mgr := media.NewManager(&state)
processing, err := mgr.CreateMedia(ctx,
account.ID,
func(ctx context.Context) (reader io.ReadCloser, err error) {
return os.Open(os.Args[1])
},
media.AdditionalMediaInfo{},
)
if err != nil {
log.Panic(ctx, err)
}
media, err := processing.Load(ctx)
if err != nil {
log.Panic(ctx, err)
}
outputCopyable(media)
copyFile(ctx, &st, media.File.Path, os.Args[2])
copyFile(ctx, &st, media.Thumbnail.Path, os.Args[3])
}
func copyFile(ctx context.Context, st *storage.Driver, key string, path string) {
rc, err := st.GetStream(ctx, key)
if err != nil {
if storage.IsNotFound(err) {
return
}
log.Panic(ctx, err)
}
defer rc.Close()
_ = os.Remove(path)
output, err := os.Create(path)
if err != nil {
log.Panic(ctx, err)
}
defer output.Close()
_, err = io.Copy(output, rc)
if err != nil {
log.Panic(ctx, err)
}
}
func outputCopyable(media *gtsmodel.MediaAttachment) {
var (
now = time.Now()
nowStr = now.Format(time.RFC3339)
mediaType string
fileMetaExtra string
)
switch media.Type {
case gtsmodel.FileTypeImage:
mediaType = "gtsmodel.FileTypeImage"
case gtsmodel.FileTypeVideo:
mediaType = "gtsmodel.FileTypeVideo"
case gtsmodel.FileTypeGifv:
mediaType = "gtsmodel.FileTypeGifv"
case gtsmodel.FileTypeAudio:
mediaType = "gtsmodel.FileTypeAudio"
case gtsmodel.FileTypeUnknown:
mediaType = "gtsmodel.FileTypeUnknown"
}
if media.FileMeta.Original.Duration != nil {
fileMetaExtra += fmt.Sprintf("\n\t\t\tDuration: util.Ptr[float32](%f),", *media.FileMeta.Original.Duration)
}
if media.FileMeta.Original.Framerate != nil {
fileMetaExtra += fmt.Sprintf("\n\t\t\tFramerate: util.Ptr[float32](%f),", *media.FileMeta.Original.Framerate)
}
if media.FileMeta.Original.Bitrate != nil {
fileMetaExtra += fmt.Sprintf("\n\t\t\tBitrate: util.Ptr[uint64](%d),", *media.FileMeta.Original.Bitrate)
}
fmt.Printf(`{
ID: "%s",
StatusID: "STATUS_ID_GOES_HERE",
URL: "%s",
RemoteURL: "",
CreatedAt: TimeMustParse("%s"),
Type: %s,
FileMeta: gtsmodel.FileMeta{
Original: gtsmodel.Original{
Width: %d,
Height: %d,
Size: %d,
Aspect: %f,%s
},
Small: gtsmodel.Small{
Width: %d,
Height: %d,
Size: %d,
Aspect: %f,
},
Focus: gtsmodel.Focus{
X: 0,
Y: 0,
},
},
AccountID: "ACCOUNT_ID_GOES_HERE",
Description: "DESCRIPTION_GOES_HERE",
ScheduledStatusID: "",
Blurhash: "%s",
Processing: 2,
File: gtsmodel.File{
Path: "%s",
ContentType: "%s",
FileSize: %d,
},
Thumbnail: gtsmodel.Thumbnail{
Path: "%s",
ContentType: "%s",
FileSize: %d,
URL: "%s",
RemoteURL: "",
},
Avatar: util.Ptr(false),
Header: util.Ptr(false),
Cached: util.Ptr(true),
}`+"\n",
media.ID,
strings.ReplaceAll(media.URL, media.AccountID, "ACCOUNT_ID_GOES_HERE"),
nowStr,
mediaType,
media.FileMeta.Original.Width,
media.FileMeta.Original.Height,
media.FileMeta.Original.Size,
media.FileMeta.Original.Aspect,
fileMetaExtra,
media.FileMeta.Small.Width,
media.FileMeta.Small.Height,
media.FileMeta.Small.Size,
media.FileMeta.Small.Aspect,
media.Blurhash,
strings.ReplaceAll(media.File.Path, media.AccountID, "ACCOUNT_ID_GOES_HERE"),
media.File.ContentType,
media.File.FileSize,
strings.ReplaceAll(media.Thumbnail.Path, media.AccountID, "ACCOUNT_ID_GOES_HERE"),
media.Thumbnail.ContentType,
media.Thumbnail.FileSize,
strings.ReplaceAll(media.Thumbnail.URL, media.AccountID, "ACCOUNT_ID_GOES_HERE"),
)
}

View file

@ -108,7 +108,7 @@ Disadvantages:
Regardless of whether you're using PostgreSQL or SQLite as your GoToSocial database, it's possible to simply back up the database files directly by using something like [rclone](https://rclone.org/), or following best practices for [backing up Postgres data](https://www.postgresql.org/docs/15/backup.html) or [SQLite data](https://sqlite.org/backup.html).
Use the GoToSocial CLI's media [`list-attachments`](cli.md#gotosocial-admin-media-list-attachments) and [`list-emojis`](cli.md#gotosocial-admin-media-list-emojis) commands to get a list of media files you need to safeguard.
Use the [GoToSocial CLI](cli.md#gotosocial-admin-media-list-local) to get a list of media files you need to safeguard.
Advantages:
@ -178,7 +178,7 @@ hooks:
For PostgreSQL, you'll want to use `postgresql_databases` instead.
The file mentioned in `patterns_from` can be created by transforming the output from the GoToSocial CLI media [`list-attachments`](cli.md#gotosocial-admin-media-list-attachments) and [`list-emojis`](cli.md#gotosocial-admin-media-list-emojis) commands. In order to generate the right patterns you can use the [`media-to-borg-patterns.py`](https://codeberg.org/superseriousbusiness/gotosocial/tree/main/example/borgmatic/media-to-borg-patterns.py) script. How Borg patterns work is explained in [their documentation](https://man.archlinux.org/man/borg-patterns.1).
The file mentioned in `patterns_from` can be created by transforming the output from the [GoToSocial CLI](cli.md#gotosocial-admin-media-list-local). In order to generate the right patterns you can use the [`media-to-borg-patterns.py`](https://github.com/superseriousbusiness/gotosocial/tree/main/example/borgmatic/media-to-borg-patterns.py) script. How Borg patterns work is explained in [their documentation](https://man.archlinux.org/man/borg-patterns.1).
You'll need to put that file on your GoToSocial instance and make sure the file is executable. It requires Python 3 which you will already have if you have Borg and Borgmatic installed. It only depends on the Python standard library.
@ -186,7 +186,7 @@ You'll need to put that file on your GoToSocial instance and make sure the file
For this to work reliably, you should ensure that the [storage-local-base-path](../configuration/storage.md) in your GoToSocial configuration uses an absolute path. Otherwise you'll have to tweak the paths yourself.
```sh
$ gotosocial --config-path /path/to/config.yaml admin media list-attachments --local-only | \
$ gotosocial admin media list-local | \
/path/to/media-to-borg-patterns.py \
<storage-local-base-path>
```
@ -210,7 +210,7 @@ If you're running Borgmatic as a systemd service, you can [create a drop-in](htt
```ini
[Service]
ExecStartPre=/path/to/gotosocial --config-path /path/to/config.yaml admin media list-attachments --local-only | /path/to/media-to-borg-patterns.py <storage-local-base-path> /etc/borgmatic/gotosocial_patterns
ExecStartPre=/path/to/gotosocial admin media list-local | /path/to/media-to-borg-patterns.py <storage-local-base-path> /etc/borgmatic/gotosocial_patterns
```
Documentation that's good to review:

View file

@ -13,41 +13,25 @@ GoToSocial - a fediverse social media server
For help, see: https://docs.gotosocial.org.
Code: https://codeberg.org/superseriousbusiness/gotosocial
Code: https://github.com/superseriousbusiness/gotosocial
Usage:
gotosocial [command]
Available Commands:
admin gotosocial admin-related tasks
completion generate the autocompletion script for the specified shell
debug gotosocial debug-related tasks
help Help about any command
server gotosocial server-related tasks
testrig gotosocial testrig-related tasks
```
Under `Available Commands`, you can see the standard `server` command. But there are also commands doing admin and debugging etc, which will be explained in this document.
Under `Available Commands`, you can see the standard `server` command. But there are also commands doing admin and testing etc, which will be explained in this document.
!!! Info "Passing global config to the CLI"
**Please note -- for all of these commands, you will still need to set the global options correctly so that the CLI tool knows how eg., how to connect to your database, which database to use, which host and account domain to use etc.**
For all of these commands, you will still need to set the global options correctly so that the CLI tool knows how to connect to your database, which database to use, which host and account domain to use, etc.
You can set these options using environment variables, passing them as CLI flags (eg., `gotosocial [commands] --host example.org`), or by just pointing the CLI tool towards your config file (eg., `gotosocial --config-path ./config.yaml [commands]`).
!!! Info
When running CLI commands, you'll get a bit of output like the following:
```text
time=XXXX level=info msg=connected to SQLITE database
time=XXXX level=info msg=there are no new migrations to run func=doMigration
time=XXXX level=info msg=closing db connection
```
This is normal and indicates that the commands ran as expected.
!!! Warning "Restarting GtS after running admin commands"
Because of the way internal caching works in GoToSocial, you may need to restart GoToSocial after running some of these commands in order for the effect of the command to "take". We are still looking for a way to make this unnecessary. In the meantime, commands that require a restart after running the command are highlighted below.
You can set these options using environment variables, passing them as CLI flags (eg., `gotosocial [commands] --host example.org`), or by just pointing the CLI tool towards your config file (eg., `gotosocial --config-path ./config.yaml [commands]`).
## gotosocial admin
@ -57,9 +41,6 @@ Contains `account`, `export`, `import`, and `media` subcommands.
This command can be used to create a new account on your instance.
!!! Warning
You must have launched the server at least once before running this command, to initialize essential entries in the database.
`gotosocial admin account create --help`:
```text
@ -87,11 +68,7 @@ gotosocial admin account create \
### gotosocial admin account confirm
This command can be used to confirm a user+account on your instance, allowing them to log in and use the account.
!!! Info
If the account was created using `admin account create` it is not necessary to run `confirm` on the account, it will be confirmed already.
This command can be used to confirm a user+account on your instance, allowing them to log in and use the account. Note that if the account was created using `admin account create` this is not necessary.
`gotosocial admin account confirm --help`:
@ -116,10 +93,6 @@ gotosocial admin account confirm --username some_username --config-path config.y
This command can be used to promote a user to admin.
!!! Warning "Server restart required"
In order for the change to "take", this command requires a restart of GoToSocial after running the command.
`gotosocial admin account promote --help`:
```text
@ -143,10 +116,6 @@ gotosocial admin account promote --username some_username --config-path config.y
This command can be used to demote a user from admin to normal user.
!!! Warning "Server restart required"
In order for the change to "take", this command requires a restart of GoToSocial after running the command.
`gotosocial admin account demote --help`:
```text
@ -170,14 +139,10 @@ gotosocial admin account demote --username some_username --config-path config.ya
This command can be used to disable an account on your instance: prevent it from signing in or doing anything, without deleting data.
!!! Warning "Server restart required"
In order for the change to "take", this command requires a restart of GoToSocial after running the command.
`gotosocial admin account disable --help`:
```text
set 'disabled' to true on a local account to prevent it from signing in or posting etc, but don't delete anything
prevent a local account from signing in or posting etc, but don't delete anything
Usage:
gotosocial admin account disable [flags]
@ -193,41 +158,10 @@ Example:
gotosocial admin account disable --username some_username --config-path config.yaml
```
### gotosocial admin account enable
This command can be used to reenable an account on your instance, undoing a previous `disable` command.
!!! Warning "Server restart required"
In order for the change to "take", this command requires a restart of GoToSocial after running the command.
`gotosocial admin account enable --help`:
```text
undo a previous disable command by setting 'disabled' to false on a local account
Usage:
gotosocial admin account enable [flags]
Flags:
-h, --help help for enable
--username string the username to create/delete/etc
```
Example:
```bash
gotosocial admin account enable --username some_username --config-path config.yaml
```
### gotosocial admin account password
This command can be used to set a new password on the given local account.
!!! Warning "Server restart required"
In order for the change to "take", this command requires a restart of GoToSocial after running the command.
`gotosocial admin account password --help`:
```text
@ -245,7 +179,7 @@ Flags:
Example:
```bash
gotosocial admin account password --username some_username --password some_really_good_password --config-path config.yaml
gotosocial admin account password --username some_username --pasword some_really_good_password --config-path config.yaml
```
### gotosocial admin export
@ -321,73 +255,17 @@ Example:
gotosocial admin import --path example.json --config-path config.yaml
```
### gotosocial admin media list-attachments
### gotosocial admin media list-local
Can be used to list the storage paths of local, remote, or all media attachments on your instance (including headers and avatars).
This command can be used to list local media. Local media is media that belongs to posts by users with an account on the instance.
`local-only` and `remote-only` can be used as filters; they cannot both be set at once.
The output will be a list of files. The list can be used to drive your backups.
If neither `local-only` or `remote-only` are set, all media attachments on your instance will be listed.
### gotosocial admin media list-remote
You may want to run this with `GTS_LOG_LEVEL` set to `warn` or `error`, otherwise it will log a lot of info messages you probably don't need.
This is the corollary to list-local, but instead lists media from remote instances. Remote media belongs to other instances, but was attached to a post we received over federation and have potentially cached locally.
`gotosocial admin media list-attachments --help`:
```text
list local, remote, or all attachments
Usage:
gotosocial admin media list-attachments [flags]
Flags:
-h, --help help for list-attachments
--local-only list only local attachments/emojis; if specified then remote-only cannot also be true
--remote-only list only remote attachments/emojis; if specified then local-only cannot also be true
```
Example output:
```text
/gotosocial/062G5WYKY35KKD12EMSM3F8PJ8/attachment/original/01PFPMWK2FF0D9WMHEJHR07C3R.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg
/gotosocial/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH8RMYQ6MSNY3JM2XT1CQ5.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH7TDVANYKWVE8VVKFPJTJ.gif
/gotosocial/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH58A357CV5K7R7TJMSH6S.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01CDR64G398ADCHXK08WWTHEZ5.gif
```
### gotosocial admin media list-emojis
Can be used to list the storage paths of local, remote, or all emojis on your instance.
`local-only` and `remote-only` can be used as filters; they cannot both be set at once.
If neither `local-only` or `remote-only` are set, all emojis on your instance will be listed.
You may want to run this with `GTS_LOG_LEVEL` set to `warn` or `error`, otherwise it will log a lot of info messages you probably don't need.
`gotosocial admin media list-emojis --help`:
```text
list local, remote, or all emojis
Usage:
gotosocial admin media list-emojis [flags]
Flags:
-h, --help help for list-emojis
--local-only list only local attachments/emojis; if specified then remote-only cannot also be true
--remote-only list only remote attachments/emojis; if specified then local-only cannot also be true
```
Example output:
```text
/gotosocial/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01GD5KP5CQEE1R3X43Y1EHS2CW.png
/gotosocial/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png
```
The output will be a list of URLs to retrieve the original content from. GoToSocial automatically retrieves remote media when it needs it, so you should never need to do so yourself.
### gotosocial admin media prune orphaned
@ -395,11 +273,7 @@ This command can be used to prune orphaned media from your GoToSocial.
Orphaned media is defined as media that is in storage under a key that matches the format used by GoToSocial, but which does not have a corresponding database entry. This is useful for excising files that may be remaining from a previous installation, or files that were placed in storage mistakenly.
!!! Warning "Requires a stopped server"
This command only works when GoToSocial is not running, since it acquires an exclusive lock on storage.
Stop GoToSocial first before running this command!
**This command only works when GoToSocial is not running, since it acquires an exclusive lock on storage. Stop GoToSocial first before running this command!**
```text
prune orphaned media from storage
@ -436,11 +310,7 @@ These items will be refetched later on demand, if necessary.
Unused media means avatars/headers/status attachments which are not currently in use by an account or status.
!!! Warning "Requires a stopped server"
This command only works when GoToSocial is not running, since it acquires an exclusive lock on storage.
Stop GoToSocial first before running this command!
**This command only works when GoToSocial is not running, since it acquires an exclusive lock on storage. Stop GoToSocial first before running this command!**
```text
prune unused/stale remote media from storage, older than given number of days

View file

@ -1,55 +0,0 @@
# Database Maintenance
Regardless of whether you choose to run GoToSocial with SQLite or Postgres, you may need to occasionally take maintenance steps to keep your database running well.
!!! tip
Though the maintenance tips provided here are intended to be non-destructive, you should backup your database before manually performing maintenance. That way if you mistype something or accidentally run a bad command, you can restore your backup and try again.
!!! danger
Manually creating, deleting, or updating entries in your GoToSocial database is **heavily discouraged**, and such commands are not provided here. Even if you think you know what you are doing, running `DELETE` statements etc. may introduce issues that are very difficult to debug. The maintenance tips below are designed to help with the smooth running of your instance; they will not save your ass if you have manually gone into your database and hacked at entries, tables, and indexes.
## SQLite
To do manual SQLite maintenance, you should first install the SQLite command line tool `sqlite3` on the same machine that your GoToSocial sqlite.db file is stored on. See [here](https://sqlite.org/cli.html) for details about `sqlite3`.
### Analyze / Optimize
Following [SQLite best practice](https://sqlite.org/lang_analyze.html#recommended_usage_pattern), GoToSocial runs the `optimize` SQLite pragma with `analysis_limit=1000` on closing database connections to keep index information up to date.
After each set of database migrations (eg., when starting a newer version of GoToSocial), GoToSocial will run `ANALYZE` to ensure that any indexes added or removed by migrations are taken into account correctly by the query planner.
The `ANALYZE` command may take ~10 minutes depending on your hardware and the size of your database file.
Because of the above automated steps, in normal circumstances you should not need to run manual `ANALYZE` commands against your SQLite database file.
However, if you interrupted a previous `ANALYZE` command, and you notice that queries are running remarkably slowly, it could be the case that the index metadata stored in SQLite's internal tables has been removed or undesirably altered.
If this is the case, you can try manually running a full `ANALYZE` command, by doing the following:
1. Stop GoToSocial.
2. While connected to your GoToSocial database file in the `sqlite3` shell, run `PRAGMA analysis_limit=0; ANALYZE;` (this may take quite a few minutes).
3. Start GoToSocial.
[See here](https://sqlite.org/lang_analyze.html#approximate_analyze_for_large_databases) for more info.
### Vacuum
GoToSocial does not currently enable auto-vacuum for SQLite. To repack the database file to an optimal size you may want to run a `VACUUM` command on your SQLite database periodically (eg., every few months).
You can see lots of information about the `VACUUM` command [here](https://sqlite.org/lang_vacuum.html).
The basic steps are:
1. Stop GoToSocial.
2. While connected to your GoToSocial database file in the `sqlite3` shell, run `VACUUM;` (this may take quite a few minutes).
3. Start GoToSocial.
### Replication
It's a common practice to set up safeguards for your database like replication. SQLite can be replicated using external software. The basic steps are described on the [Replicating SQLite](../advanced/replicating-sqlite.md) page.
## Postgres
TODO: Maintenance recommendations for Postgres.

View file

@ -1,73 +0,0 @@
# Domain Blocks
GoToSocial supports 'blocking'/'suspending' domains that you don't want your instance to federate with. In our documentation, the two terms 'block' and 'suspend' are used interchangeably with regard to domains, because they mean the same thing: preventing your instance and the instance running on the target domain from communicating with one another, effectively cutting off federation between the two instances.
You can view, create, and remove domain blocks and domain allows using the [instance admin panel](./settings.md#domain-permissions).
This document focuses on what domain blocks actually *do* and what side effects are processed when you create a new domain block.
## How does a domain block work
A domain block works by doing two things:
Firstly, it instructs your instance to refuse any requests made to it from the target domain:
- All incoming requests from the blocked domain to your instance will be responded to with HTTP status code `403 Forbidden`.
- This makes it impossible for an account on the target domain to interact with an account on your instance, or any statuses created by that account, since your instance will simply refuse to process the request.
- This also extends to GET requests: your instance will no longer serve an ActivityPub response to a request by a blocked instance to fetch, say, an account's bio, or pinned statuses, etc.
- Boosts of statuses from accounts on your instance should also not be visible to accounts on blocked instances, since those instances will not be able to fetch the content of the status that has been boosted.
Secondly, a domain block instructs your instance to no longer make any requests to the target instance. This means:
- Your instance will not deliver any messages to an instance on a blocked domain.
- Nor will it fetch statuses, accounts, media, or emojis from that instance.
## Safety concerns
### Block evasion
Domain blocking is not airtight. GoToSocial *can* ensure that it will neither serve requests from nor make requests to instances on blocked domains. Unfortunately it *cannot* guarantee that accounts on your instance will never be visible in any way to users with accounts on blocked instances. Consider the following circumstances, all of which represent a form of [block evasion](https://en.wikipedia.org/wiki/Block_(Internet)#Evasion):
- You've domain blocked `blocked.instance.org`. A user on `blocked.instance.org` makes an account on `not-blocked.domain`, so that they can use their new account to interact with your posts or send messages to you. They may be upfront about who they are, or they may use a false identity.
- You've domain blocked `blocked.instance.org`. A user on `not-blocked.domain` screenshots a post of yours and sends it to someone on `blocked.instance.org`.
- You've domain blocked `blocked.instance.org`. A user on `blocked.instance.org` visits the web view of your profile to read your public posts.
- You've domain blocked `blocked.instance.org`. You have RSS enabled for your profile. A user from `blocked.instance.org` subscribes to your RSS feed to read your public posts.
In the above cases, `blocked.instance.org` remains blocked, but users from that instance may still have other ways of seeing your posts and possibly reaching you.
With this in mind, you should only ever treat domain blocking as *one layer* of your privacy onion. That is, domain blocking should be deployed alongside other layers in order to achieve a level of privacy that you are comfortable with. This ought to include things like not posting sensitive information publicly, not accidentally doxxing yourself in photos, etc.
### Block announce bots
Unfortunately, the Fediverse has its share of trolls, many of whom see domain blocking as an adversary to be defeated. To achieve this, they often target instances which use domain blocks to protect users.
As such, there are bots on the Fediverse which scrape instance domain blocks and announce any discovered blocks to the followers of the bot, opening the admin of the blocking instance up to harassment. These bots use the `api/v1/instance/peers?filter=suspended`, `api/v1/instance/peers?filter=blocked`, and/or `api/v1/instance/domain_blocks` endpoints of GoToSocial instances to gather domain block information.
By default, GoToSocial does not expose these endpoints publicly, so your instance will be safe from such scraping. However, if you set `instance-expose-blocklist` to `true` in your config.yaml file, you may find that these endpoints gets scraped occasionally, and you may see your blocks being announced by troll bots.
## What are the side effects of creating a domain block
When you create a new domain block (or resubmit an existing domain block), your instance will process side effects for the block. These side effects are:
1. Mark all accounts stored in your database from the target domain as suspended, and remove most information (bio, display name, fields, etc) from each account marked this way.
2. Clear all mutual and one-way relationships between local accounts and suspended accounts (followed, following, follow requests, bookmarks, etc).
3. Delete all statuses from suspended accounts.
4. Delete all media from suspended accounts and their statuses, including media attachments, avatars, headers, and emojis.
!!! danger
Currently, most of the above side effects are **irreversible**. If you unblock a domain after blocking it, all accounts on that domain will be marked as no longer suspended, and you will be able to interact with them again, but all relationships will still be wiped out, and all statuses and media will be gone.
Think carefully before blocking a domain.
## Blocking a domain and all subdomains
When you add a new domain block, GoToSocial will also block all subdomains of the blocked domain. This allows you to block specific subdomains, if you wish, or to block a domain more generally if you don't trust the domain owner.
Some examples:
1. You block `example.org`. This blocks the following domains (not exhaustive): `example.org`, `subdomain.example.org`, `another-subdomain.example.org`, `sub.sub.sub.domain.example.org`.
2. You block `baddies.example.org`. This blocks the following domains (not exhaustive): `baddies.example.org`, `really-bad.baddies.example.org`. However the following domains are not blocked (not exhaustive): `example.org`, `subdomain.example.org`, `not-baddies.example.org`.
A more practical example:
Some absolute jabroni owns the domain `fossbros-anonymous.io`. Not only do they run a Mastodon instance at `mastodon.fossbros-anonymous.io`, they also have a GoToSocial instance at `gts.fossbros-anonymous.io`, and an Akkoma instance at `akko.fossbros-anonymous.io`. You want to block all of these instances at once (and any future instances they might create at, say, `pl.fossbros-anonymous.io`, etc). You can do this by simply creating a domain block for `fossbros-anonymous.io`. None of the instances at subdomains will be able to communicate with your instance. Yeet!

View file

@ -1,185 +0,0 @@
# Domain Permission Subscriptions
Via the [admin settings panel](./settings.md#subscriptions), you can create and manage domain permission subscriptions.
Domain permission subscriptions allow you to specify a URL at which a permission list is hosted. Every 24hrs at 11pm (by default), your instance will fetch and parse each list you're subscribed to, in order of priority (highest to lowest), and create domain permissions (or domain permission drafts) based on entries discovered in the lists.
Each domain permission subscription can be used to create domain allow or domain block entries.
!!! warning
Currently, via blocklist subscriptions it is only possible to create "suspend" level domain blocks; other severities are not yet supported. Entries of severity "silence" or "limit" etc. on subscribed blocklists will be skipped.
## Priority
When you specify multiple domain permission subscriptions, they will be fetched and parsed in order of priority, from highest priority (255) to lowest priority (0).
Permissions discovered on lists higher up in the priority ranking will override permissions on lists lower down in the priority ranking.
For example, an instance admin subscribes to two allow lists, "Important List" at priority 255, and "Less Important List" at priority 128. Each of these subscribed lists contain an entry for `good-eggs.example.org`.
The subscription with the higher priority is the one that now creates and manages the domain allow entry for `good-eggs.example.org`.
If the subscription with the higher priority is removed, then the next time all the subscriptions are fetched, "Less Important List" will create (or take ownership of) the domain allow instead.
## Retractions
Sometimes, an entry that was present on a subscribed block or allow list will be removed later by the curator(s) of that list. When this happens, the removed domain permission entry can be said to have been "retracted".
For example, say your instance subscribes to one block list, and that block list contains an entry for `baddies.example.org`. A corresponding domain block for `baddies.example.org` has therefore been created in your database, with the subscription ID of your block list. In other words, the domain block is in force, and is managed by your block list subscription.
At some point, your instance fetches the list again, and this time it sees that the entry for `baddies.example.org` is no longer present in the list, because it has been removed by the list curator(s) (perhaps the admins turned their policies around, or the instance was shut down, etc). Thus, according to your instance, the block for `baddies.example.org` is now a "retracted" domain permission entry.
If the domain permission subscription is set to "Remove retracted permissions," then the now-retracted domain block will be removed from the database, and will no longer be enforced. In this example, that means your instance will start federating (again) with `baddies.example.org`.
If the domain permission subscription is *not* set to "Remove retracted permissions," then instead of the retracted block being removed from the database, it will be kept in the database but "orphaned" -- ie., it will still be in force, but it will be marked as no longer being managed by the subscription. In this example, that means your instance will keep blocking `baddies.example.org`.
!!!! Note "Retracted permissions and other subscriptions"
When a permission is retracted and removed from the database, but an entry for it exists on the list of *another* subscription of a lower priority than the one it was retracted from, then the permission will be recreated as an entry managed by the lower priority list.
For example, say you subscribe to List1 at priority 255, and List2 at priority 128, and `baddies.example.org` is present on both lists. That means the domain block entry will be managed by List1. If List1 later *retracts* the entry, it will be removed from your database (assuming you have "Remove retracted permissions" set). However, as soon as List2 is checked (usually seconds after List1), then an entry for `baddies.example.org` will be created again, but managed by List2 this time.
In other words, it is only when an entry is retracted from *every list you subscribe to* that it will truly be removed.
## Orphan Permissions
Domain permissions (blocks or allows) that are not currently managed by a domain permission subscription are considered "orphan" permissions. This includes permissions that an admin created in the settings panel by hand, entries which were imported manually via the import/export page, or entries that belonged to a subscription but have since been retracted but not removed.
If you wish, when creating a domain permission subscription, you can set ["adopt orphans"](./settings.md#adopt-orphan-permissions) to true for that subscription. If a domain permission subscription that is set to adopt orphans encounters an orphan permission which is *also present on the list at the subscription's URI*, then it will "adopt" the orphan by setting the orphan's subscription ID to its own ID.
For example, an instance admin manually creates a domain block for the domain `horrid-trolls.example.org`. Later, they create a domain permission subscription for a block list that contains an entry for `horrid-trolls.example.org`, and they set "adopt orphans" to true. When their instance fetches and parses the list, and creates domain permission entries from it, then the orphan domain block for `horrid-trolls.example.org` gets adopted by the domain permission subscription. Now, if the domain permission subscription is removed, and the option to remove all permissions owned by the subscription is checked, then the domain block for `horrid-trolls.example.org` will also be removed.
## Fun Stuff To Do With Domain Permission Subscriptions
### 1. Create an allowlist-federation cluster.
Domain permission subscriptions make it possible to easily create allowlist-federation clusters, ie., a group of instances can essentially form their own mini-fediverse, wherein each instance runs in [allowlist federation mode](./federation_modes.md#allowlist-federation-mode), and subscribes to a cooperatively-managed allowlist hosted somewhere.
For example, instances `instance-a.example.org`, `instance-b.example.org`, and `instance-c.example.org` decide that they only want to federate with each other.
Using some version management platform like Codeberg, they host a plaintext-formatted allowlist at something like `https://codeberg.org/our-cluster/allowlist/raw/branch/main/allows.txt`.
The contents of the plaintext-formatted allowlist are as follows:
```text
instance-a.example.org
instance-b.example.org
instance-c.example.org
```
Each instance admin sets their federation mode to `allowlist`, and creates a subscription to create allows from `https://codeberg.org/our-cluster/allowlist/raw/branch/main/allows.txt`, which results in domain allow entries being created for their own domain, and for each other domain in the cluster.
At some point, someone from `instance-d.example.org` asks (out of band) whether they can be added to the cluster. The existing admins agree, and update their plaintext-formatted allowlist to read:
```text
instance-a.example.org
instance-b.example.org
instance-c.example.org
instance-d.example.org
```
The next time each instance fetches the list, a new domain allow entry will be created for `instance-d.example.org`, and it will be able to federate with the other domains on the list.
### 2. Cooperatively manage a blocklist.
Domain permission subscriptions make it easy to collaborate on and subscribe to shared blocklists of domains that host illegal / fashy / otherwise undesired accounts and content.
For example, the admins of instances `instance-e.example.org`, `instance-f.example.org`, and `instance-g.example.org` decide that they are tired of duplicating work by playing whack-a-mole with bad actors. To make their lives easier, they decide to collaborate on a shared blocklist.
Using some version management platform like Codeberg, they host a blocklist at something like `https://codeberg.org/our-cluster/allowlist/raw/branch/main/blocks.csv`.
When someone discovers a new domain hosting an instance they don't like, they can open a pull request or similar against the list, to add the questionable instance to the domain.
For example, someone gets an unpleasant reply from a new instance `fashy-arseholes.example.org`. Using their collaboration tools, they propose adding `fashy-arseholes.example.org` to the blocklist. After some deliberation and discussion, the domain is added to the list.
The next time each of `instance-e.example.org`, `instance-f.example.org`, and `instance-g.example.org` fetch the block list, a block entry will be created for ``fashy-arseholes.example.org``.
### 3. Subscribe to a blocklist, but ignore some of it.
Say that `instance-g.example.org` in the previous section decides that they agree with most of the collaboratively-curated blocklist, but they actually would like to keep federating with ``fashy-arseholes.example.org`` for some godforsaken reason.
This can be done in one of three ways:
1. The admin of `instance-g.example.org` subscribes to the shared blocklist, but they do so with the ["create as drafts"](./settings.md#create-permissions-as-drafts) option set to true. When their instance fetches the blocklist, a draft block is created for `fashy-arseholes.example.org`. The admin of `instance-g` just leaves the permission as a draft, or rejects it, so it never comes into force.
2. Before the blocklist is re-fetched, the admin of `instance-g.example.org` creates a [domain permission exclude](./settings.md#excludes) entry for ``instance-g.example.org``. The domain ``instance-g.example.org`` then becomes exempt/excluded from automatic permission creation, and so the block for ``instance-g.example.org`` on the shared blocklist does not get created in the database of ``instance-g.example.org`` the next time the list is fetched.
3. The admin of `instance-g.example.org` creates an explicit domain allow entry for `fashy-arseholes.example.org` on their own instance. Because their instance is running in `blocklist` federation mode, [the explicit allow overrides the domain block entry](./federation_modes.md#in-blocklist-mode), and so the domain remains unblocked.
### 4. Subscribe directly to another instance's blocklist.
Because GoToSocial is able to fetch and parse JSON-formatted lists of domain permissions, it is possible to subscribe directly to another instance's list of blocked domains via their `/api/v1/instance/domain_blocks` (Mastodon) or `/api/v1/instance/peers?filter=suspended` (GoToSocial) endpoint (if exposed).
For example, the Mastodon instance `peepee.poopoo.example.org` exposes their block list publicly, and the owner of the GoToSocial instance `instance-h.example.org` decides they quite like the cut of the Mastodon moderator's jib. They create a domain permission subscription of type JSON, and set the URI to `https://peepee.poopoo.example.org/api/v1/instance/domain_blocks`. Every 24 hours, their instance will go fetch the blocklist JSON from the Mastodon instance, and create permissions based on entries discovered therein.
## Example lists per content type
Shown below are examples of the different permission list formats that GoToSocial is able to understand and parse.
Each list contains three domains, `bumfaces.net`, `peepee.poopoo`, and `nothanks.com`.
### CSV
CSV lists use content type `text/csv`.
Mastodon domain permission exports generally use this format.
```csv
#domain,#severity,#reject_media,#reject_reports,#public_comment,#obfuscate
bumfaces.net,suspend,false,false,big jerks,false
peepee.poopoo,suspend,false,false,harassment,false
nothanks.com,suspend,false,false,,false
```
### JSON (application/json)
JSON lists use content type `application/json`.
```json
[
{
"domain": "bumfaces.net",
"suspended_at": "2020-05-13T13:29:12.000Z",
"comment": "big jerks"
},
{
"domain": "peepee.poopoo",
"suspended_at": "2020-05-13T13:29:12.000Z",
"comment": "harassment"
},
{
"domain": "nothanks.com",
"suspended_at": "2020-05-13T13:29:12.000Z"
}
]
```
As an alternative to `"comment"`, `"public_comment"` will also work:
```json
[
{
"domain": "bumfaces.net",
"suspended_at": "2020-05-13T13:29:12.000Z",
"public_comment": "big jerks"
},
{
"domain": "peepee.poopoo",
"suspended_at": "2020-05-13T13:29:12.000Z",
"public_comment": "harassment"
},
{
"domain": "nothanks.com",
"suspended_at": "2020-05-13T13:29:12.000Z"
}
]
```
### Plaintext (text/plain)
Plaintext lists use content type `text/plain`.
Note that it is not possible to include any fields like "obfuscate" or "public comment" in plaintext lists, as they are simply a newline-separated list of domains.
```text
bumfaces.net
peepee.poopoo
nothanks.com
```

View file

@ -1,62 +0,0 @@
# Federation Modes
GoToSocial currently offers both 'blocklist' and 'allowlist' federation modes, which can be set using the `instance-federation-mode` setting in the config.yaml, or using the `GTS_INSTANCE_FEDERATION_MODE` environment variable. These are described below.
## Blocklist federation mode (default)
When `instance-federation-mode` is set to `blocklist`, your instance will federate openly with other instances, without restriction, with the exception of instances you have explicitly created domain blocks for via the settings panel.
When your instance receives a new request from an instance that is not blocked via a domain block entry, it will serve the request if the request is valid, and if the requester is permitted to view the resource that's being requested (taking account of status visibility, and any user-level blocks).
When your instance encounters a mention or an announce of a status or account it hasn't seen before, it will go fetch the resource if the domain of the resource is not blocked via a domain block entry.
!!! info
Blocklist federation mode is the default federation mode for GoToSocial. It's also the default for most other ActivityPub server implementations.
## Allowlist federation mode
!!! warning
Allowlist federation mode is still considered "experimental" while we work out how well it actually works in practice. It should do what it says on the box, but it may cause bugs or edge cases to appear elsewhere, we're not sure yet!
When `instance-federation-mode` is set to `allowlist`, your instance will federate only with instances for which an explicit allow has been created via the settings panel, and will restrict access by any instances for which an allow has not been created.
When your instance receives a new request from an instance that is not explicitly allowed via a domain allow entry, it will refuse to serve the request. If the request comes from a domain that is on the allowlist, your instance will serve the request (taking account of status visibility, and any user-level blocks).
When your instance encounters a mention or an announce of a status or account it hasn't seen before, it will only go fetch the resource if the domain of the resource is explicitly allowed via a domain allow entry.
!!! tip
Allowlist federation mode is useful in cases where you want to federate only with select 'trusted' instances. However, this comes at the cost of hampering discovery. Under blocklist federation mode, you will organically encounter posts and accounts from instances you were not yet aware of, via boosts and replies, but in allowlist federation mode no such serendipity will occur.
As such, it is recommended that you *either* start with blocklist federation mode and switch over to allowlist federation later on once you've established which other instances you 'like', *or* you start with allowlist federation mode, and have an allowlist populated and ready to import after first booting up your instance, in order to 'bootstrap' it.
## Combining blocks and allows
!!! danger
Combining blocks and allows is a tricky business!
When importing lists of allows and blocks, you should always review the list manually to make sure that you do not inadvertently block a domain that you would prefer not to block, since this can have **very annoying side effects** like removing follows/following, statuses, etc.
When in doubt, always add an explicit allow first as an insurance policy!
It is possible to both block and allow the same domain, and the effect of combining these two things depends on which federation mode your instance is currently using, as explained in the following subsections, which are summarised in a flowchart that you can find below.
### In blocklist mode
As the chart below shows, in blocklist mode (the left-hand part of the diagram), an explicit domain allow can be used to override a domain block.
This is useful in cases where you are importing a blocklist from someone else, but the imported blocklist contains some instances you would actually prefer not to block. To avoid blocking those instances, you can create explicit domain allows for those instances first. Then, when you import the block list, the explicitly allowed domains will not be blocked, and the side effects of creating a block (deleting statuses, media, relationships etc) will not be processed.
If you later remove an explicit allow for a domain that also has a block, the instance will become blocked, and side effects of block creation will be processed.
Conversely, if you add an explicit allow for a domain that was blocked, the side effects of block *deletion* will be processed.
### In allowlist mode
As the chart below shows, in allowlist mode (the right-hand part of the diagram) an explicit domain block trumps an explicit domain allow. The following two things must be true in order for an instance to be allowed through, when running in allowlist mode:
1. An explicit domain block **does not exist** for the instance.
2. An explicit domain allow **does exist** for the instance.
If either of the above conditions are not met, the request will be denied.
![A flow chart diagram showing how the two different federation modes treat incoming requests.](../public/diagrams/federation_modes.png)

View file

@ -1,57 +0,0 @@
# Media Caching
GoToSocial uses the configured [storage backend](https://docs.gotosocial.org/en/latest/configuration/storage/) in order to store media (images, videos, etc) uploaded to the instance by local users, as well as to cache media attached to posts and profiles federated in from remote instances.
Media uploaded by local instance users will be kept in storage forever (unless the post or profile it's attached to is deleted), so that it's always available to be served in response to requests coming from remote instances.
Remote media, on the other hand, is cached only temporarily. After a certain amount of time (see below), it will be removed from storage to help alleviate storage space usage. Remote media uncached this way will be re-fetched automatically from the remote instance if it's needed again.
!!! info "Why cache?"
There is an argument to be made for not caching remote media at all, since it's always available on the origin server. Why not just forego caching entirely, and rely on the remote instance to serve everything on demand?
While this is a straightforward approach to saving storage space, it can cause other problems and is generally considered to be rather impolite.
For example, say someone from a small instance makes a funny post with an image attached. The post gets boosted by an account that's followed by 1,000 people across 5 different instances (200 on each instance). Each of those 1,000 people then have the image put in their timeline at once.
With no remote media caching in place, this may cause up to 1,000 requests to hit the small instance simultaneously, as the browser of each recipient of the post must go and make a unique request to fetch the image from the small instance. This causes a large traffic spike for the small instance. In extreme scenarios, this can cause the instance to become unresponsive or crash, essentially DDOS'ing it.
With remote media caching in place, however, boosting a post to 1,000 people across 5 different instances will cause only 5 requests to the small instance: 1 request for each instance. Each instance will then serve 200 requests to its local users from the cached version of the remote image, effectively spreading the load and sparing the smaller instance.
## Cleanup
Cleanup of the remote media cache occurs as a scheduled background process, and no manual intervention is required by admins. Cleanup takes somewhere between 5-30 minutes depending on the speed of the server, the speed of the configured storage, and the amount of media to work through.
GoToSocial exposes three variables that let you, the admin, tune when and how this work is performed: `media-remote-cache-days`, `media-cleanup-from` and `media-cleanup-every`.
By default, these variables are set to the following values:
| Variable name | Default | Meaning |
|---------------------------|--------------|----------|
| `media-remote-cache-days` | `7` | 7 days |
| `media-cleanup-from` | `"00:00"` | midnight |
| `media-cleanup-every` | `"24h"` | daily |
In other words, the default settings mean that every night at midnight, remote media older than a week will be uncached and removed from storage.
You can achieve different results by tuning these variables. For example, say you wanted to prune at 4.30am instead of midnight, you could change `media-cleanup-from` to `"04:30"`.
If you only want to prune every couple of days instead of every night, you could set `media-cleanup-every` to a higher value, like `"48h"` or `"72h"`.
If you wanted to adopt a more aggressive cleanup strategy to minimize storage usage, you could set the following values:
| Variable name | Setting | Meaning |
|---------------------------|--------------|-------------|
| `media-remote-cache-days` | `1` | 1 day |
| `media-cleanup-from` | `"00:00"` | midnight |
| `media-cleanup-every` | `"8h"` | every 8 hrs |
The above settings would mean that every 8 hours starting from midnight, GoToSocial would prune any media older than 1 day (24hrs). The prune jobs would run at 00:00, 08:00, and 16:00, ie., midnight, 8am, and 4pm. With this configuration, the longest amount of time you could possibly keep remote media in your storage would be about 32 hours.
!!! tip
Setting `media-remote-cache-days` to 0 or less means that remote media will never be uncached. However, cleanup jobs for orphaned local media and other consistency checks will still be run using the schedule defined by the other variables.
!!! tip
You can also run cleanup manually as a one-off action through the admin panel, if you so wish ([see docs](./settings.md#media)).
!!! warning
Setting `media-cleanup-every` to a very small value like `"30m"` or less will probably cause your instance to just constantly iterate through attachments, causing high database use for very little benefit. We don't recommend setting this value to less than about `"8h"` and even that is probably overkill.

View file

@ -1,31 +0,0 @@
# HTTP Request Header Filtering Modes
GoToSocial currently offers 'block', 'allow' and disabled HTTP request header filtering modes, which can be set using the `advanced-header-filter-mode` setting in the config.yaml, or using the `GTS_ADVANCED_HEADER_FILTER_MODE` environment variable. These are described below.
!!! warning
HTTP request header filtering is an advanced setting. If you are not well versed in the uses and intricacies of HTTP request headers, you may break federation or even access to your own instance by changing these.
HTTP request header filtering is also still considered "experimental". It should do what it says on the box, but it may cause bugs or edge cases to appear elsewhere, we're not sure yet!
## Disabled header filtering mode (default)
When `advanced-header-filter-mode` is set to `""`, i.e. an empty string, all request header filtering will be disabled.
## Block filtering mode
When `advanced-header-filter-mode` is set to `"block"`, your instance will accept HTTP requests as normal (pending API token checks, HTTP signature checks etc), with the exception of matching block header filters you have explicitly created via the settings panel.
In block mode, an allow header filter can be used to override an existing block filter, providing an extra level of granularity.
A request in block mode will be accepted if it is EXPLICITLY ALLOWED OR NOT EXPLICITLY BLOCKED.
## Allow filtering mode
When `advanced-header-filter-mode` is set to `"allow"`, your instance will only accept HTTP requests for which a matching allow header filter has been explicitly created via the settings panel. All other requests will be refused.
In allow mode, a block header filter can be used to override an existing allow filter, providing an extra level of granularity.
A request in allow mode will only be accepted if it is EXPLICITLY ALLOWED AND NOT EXPLICITLY BLOCKED.
!!! danger
Allow filtering mode is an extremely restrictive mode that will almost certainly prevent many (legitimate) clients from being able to access your instance, including yourself. You should only enable this mode if you know exactly what you're trying to achieve.

View file

@ -1,15 +0,0 @@
# Robots.txt
GoToSocial serves a `robots.txt` file on the host domain. This file contains rules that attempt to block known AI scrapers, as well as some other indexers. It also includes some rules to ensure things like API endpoints aren't indexed by search engines since there really isn't any point to them.
## Allow/disallow stats collection
You can allow or disallow crawlers from collecting stats about your instance from the `/nodeinfo/2.0` and `/nodeinfo/2.1` endpoints by changing the setting `instance-stats-mode`, which modifies the `robots.txt` file. See [instance configuration](../configuration/instance.md) for more details.
## AI scrapers
The AI scrapers come from a [community maintained repository][airobots]. It's manually kept in sync for the time being. If you know of any missing robots, please send them a PR!
A number of AI scrapers are known to ignore entries in `robots.txt` even if it explicitly matches their User-Agent. This means the `robots.txt` file is not a foolproof way of ensuring AI scrapers don't grab your content. In addition to this you might want to look into blocking User-Agents via [requester header filtering](request_filtering_modes.md).
[airobots]: https://github.com/ai-robots-txt/ai.robots.txt/

View file

@ -1,262 +1,57 @@
# Admin Settings Panel
# Admin Settings
The GoToSocial admin settings panel uses the [admin API](https://docs.gotosocial.org/en/latest/api/swagger/#operations-tag-admin) to manage your instance. It's combined with the [user settings panel](../user_guide/settings.md) and uses the same OAuth mechanism as normal clients (with scope: admin).
The GoToSocial Settings interface uses the [admin api routes](https://docs.gotosocial.org/en/latest/api/swagger/#operations-tag-admin) to manage your instance. It's combined with the [User settings](../user_guide/settings.md) and uses the same OAUTH mechanism as normal clients (with scope: admin).
## Setting admin account permissions and logging in
To use the admin settings panel, your account has to be promoted to admin:
```bash
## Account permissions
To use the Admin API your account has to be promoted as such:
```
./gotosocial --config-path ./config.yaml admin account promote --username YOUR_USERNAME
```
In order for the promotion to 'take', you may need to restart your instance after running the command.
After this, you can enter your instance domain in the login field (auto-filled if you run GoToSocial on the same domain), and login like you would with any other client.
After this, you can navigate to `https://[your-instance-name.org]/settings`, enter your domain in the login field, and login like you would with any other client. You should now see the admin settings.
## Moderation
## Instance Settings
![Screenshot of the GoToSocial admin panel, showing the fields to change an instance's settings](../assets/admin-settings.png)
Instance moderation settings.
Here you can set various metadata for your instance, like the displayed name, thumbnail image, description texts (HTML), and contact username and email.
### Reports
## Actions
You can use media cleanup to remove remote media older than the specified number of days. This also removes unused headers and avatars.
![List of reports for testing, showing one open report.](../public/admin-settings-reports.png)
## Federation
![List of suspended instances, with a field to filter/add new blocks. Below is a link to the bulk import/export interface](../assets/admin-settings-federation.png)
The reports section shows a list of reports, originating from your local users, or remote instances (shown anonymously as just the name of the instance, without specific username).
In the federation section you can influence which instances you federate with, through adding domain blocks. You can enter a domain to suspend in the search field, which will filter the list to show you if you already have a block for it. Clicking 'suspend' gives you a form to add a public and/or private comment, and submit to add the block. Adding a suspension will suspend all the currently known accounts on the instance, and prevent any new interactions with any user on the blocked instance.
Clicking a report shows if it was resolved (with the reasoning if available), more information, and a list of reported toots if selected by the reporting user. You can also use this view to mark a report as resolved, and fill in a comment. Whatever comment you enter here will be visible to the user that created the report, if that user is from your instance.
### Bulk import/export
Through the link at the bottom of the Federation section (or going to `/settings/admin/federation/import-export`) you can do bulk import/export of your domain blocklist.
![The detailed view of an open report, showing the reported status and the reason for the report.](../public/admin-settings-report-detail.png)
Clicking on the username of the reported account opens that account in the 'Accounts' view, allowing you to perform moderation actions on it.
### Accounts
You can use this section to search for an account and perform moderation actions on it.
### Domain Permissions
![List of suspended instances, with a field to filter/add new blocks. Below is a link to the bulk import/export interface](../public/admin-settings-federation.png)
In the domain permissions section you can create, delete, and review domain blocks, domain allows, drafts, excludes, and subscriptions.
For more detail on federation settings, and specifically how domain allows and domain blocks work in combination, please see [the federation modes section](./federation_modes.md), and [the domain blocks section](./domain_blocks.md).
#### Domain Blocks
You can enter a domain to suspend in the search field, which will filter the list to show you if you already have a block for it.
Clicking 'suspend' gives you a form to add a public and/or private comment, and submit to add the block.
Adding a domain block will suspend all currently known accounts from that domain, and prevent any new interactions with the blocked domain.
#### Domain Allows
The domain allows section works much like the domain blocks section, described above, only for explicit domain allows rather than domain blocks.
#### Import/export
In this section you can do bulk import/export of domain permissions in JSON, CSV, or plaintext formats.
![List of domains included in an import, providing ways to select some or all of them, change their domains, and update the use of subdomains.](../public/admin-settings-federation-import-export.png)
![List of domains included in an import, providing ways to select some or all of them, change their domains, and update the use of subdomains.](../assets/admin-settings-federation-import-export.png)
Upon importing a list, either through the input field or from a file, you can review the entries in the list before importing a subset. You'll also be warned for entries that use subdomains, providing an easy way to change them to the main domain.
#### Drafts
## Reports
![List of reports for testing, one resolved and one open.](../assets/admin-settings-reports.png)
In this section you can create, search through, accept, and reject domain permission drafts.
The reports section shows a list of reports, originating from your local users, or remote instances (shown anonymously as just the name of the instance, without specific username).
Domain permission drafts are domain permissions that have been proposed (either via manual creation or as an entry from a subscribed block / allow list), but have not yet come into force.
Until it is accepted, a domain permission draft will not have any effect on federation with the domain it targets. Upon acceptance, it will be converted into either a domain block or a domain allow, and start being enforced.
#### Excludes
In this section, you can create, search through, and remove domain permission excludes.
Domain permission excludes prevent permissions for a domain (and all subdomains) from being automatically managed by domain permission subscriptions.
For example, if you create an exclude entry for the domain `example.org`, then a blocklist or allowlist subscription will exclude entries for `example.org` and any of its subdomains (`sub.example.org`, `another.sub.example.org` etc.) when creating domain permission drafts and domain blocks/allows.
This functionality allows you to manually manage permissions for excluded domains, in cases where you know you definitely do or don't want to federate with a given domain, no matter what entries are contained in a domain permission subscription.
Note that by itself, creation of an exclude entry for a given domain does not affect federation with that domain at all, it is only useful in combination with permission subscriptions.
#### Subscriptions
In this section, you can create, search through, edit, test, and remove domain permission subscriptions.
Domain permission subscriptions allow you to specify a URL at which a permission list is hosted. Every 24hrs at 11pm (by default), your instance will fetch and parse each subscribed list, and create domain permissions (or domain permission drafts) based on entries in the lists.
##### Title
You can optionally use the title field to set a title for the subscription, as a reminder for yourself and other admins.
For example, you might subscribe to a list at `https://lists.example.org/baddies.csv` and set the title of the subscription to something that reflects the contents of that list, such as "Basic block list (worst of the worst)", or similar.
##### Subscription Priority
When you specify multiple domain permission subscriptions, they will be fetched and parsed in order of priority, from highest priority (255) to lowest priority (0).
Permissions discovered on lists higher up in the priority ranking will override permissions on lists lower down in the priority ranking.
For more information on priority, please see the separate [domain permission subscriptions](./domain_permission_subscriptions.md) document.
##### Permission Type
You can use this dropdown to select whether permissions discovered at the list URL should be created as domain blocks, or domain allows.
##### Content Type
You can use this dropdown to select the content type of the list at the subscribed URL.
Use CSV for Mastodon-style permission lists, plain for plaintext lists of domain names, or JSON for json-exported lists.
##### Basic Auth
Check this box to provide a basic auth username and/or password credential for the subscribed list, which will be sent along with each request to fetch the list.
##### Adopt Orphan Permissions
If you check this box, then any existing domain permissions will become managed by this subscription in the following circumstances:
1. They don't already have a subscription ID (ie., they're not managed by any domain permission subscription).
2. They match a domain permission included in the list at the URL of this subscription.
For more information on orphan permissions, please see the separate [domain permission subscriptions](./domain_permission_subscriptions.md#orphan-permissions) document.
##### Remove Retracted Permissions
This setting controls how retractions are handled by this domain permission subscription: if "Remove retracted permissions" is checked, retracted entries will be removed from the database; if "Remove retracted permissions" is not checked, retracted entries will just be orphaned instead.
For more detail on how retractions work, with examples, please see the separate [domain permission subscriptions](./domain_permission_subscriptions.md#retractions) document.
##### Create Permissions as Drafts
With this box checked (default), any permissions created by this subscription will be created as **drafts** which require manual approval to come into force.
It is recommended to leave this box checked unless you absolutely trust the subscribed list, to avoid inadvertent blocking or allowing of domains you'd rather not block or allow.
##### Test a Subscription
To test whether a subscription can be successfully parsed, first create the subscription, then in the detailed view for that subscription, click on the "Test" button.
If your instance is able to fetch and parse permissions at the subscription URI, then you will see a list of these after clicking "Test". Otherwise, you will see an error message.
![Screenshot of the detailed view of a subscription, with arrows pointing to the test section near the bottom.](../public/admin-settings-federation-subscription-test.png)
## Administration
Instance administration settings.
### Actions
Run one-off administrative actions.
#### Email
You can use this section to send a test email to the given email address, with an optional test message.
#### Media
You can use this section run a media action to clean up the remote media cache using the specified number of days. Media older than the given number of days will be removed from storage (s3 or local). Media removed in this way will be refetched again later if the media is required again. This action is functionally identical to the media cleanup that runs automatically.
#### Keys
You can use this section to expire/invalidate public keys from the selected remote instance. The next time your instance receives a signed request using an expired key, it will attempt to fetch and store the public key again.
### Custom Emoji
Clicking a report shows if it was resolved (with the reasoning if available), more information, and a list of reported toots if selected by the reporting user.
## Custom Emoji
Custom Emoji will be automatically fetched when included in remote toots, but to use them in your own posts they have to be enabled on your instance.
#### Local
![Local custom emoji section, showing an overview of custom emoji sorted by category. There are a lot of garfields.](../public/admin-settings-emoji-local.png)
### Local
![Local custom emoji section, showing an overview of custom emoji sorted by category. There are a lot of garfields.](../assets/admin-settings-emoji-local.png)
This section shows an overview of all the custom emoji enabled on your instance, sorted by their category. Clicking an emoji shows it's details, and provides options to change the category or image, or delete it completely. The shortcode cannot be updated here, you would have to upload it with the new shortcode yourself (and optionally delete the old one).
Below the overview you can upload your own custom emoji, after previewing how they look in a toot. PNG and (animated) GIF's are supported.
#### Remote
![Remote custom emoji section, showing a list of 3 emoji parsed from the entered toot, garfield, blobfoxbox and blobhajmlem. They can be selected, their shortcode can be tweaked, and they can be assigned to a category, before submitting as a copy or delete operation](../public/admin-settings-emoji-remote.png)
### Remote
![Remote custom emoji section, showing a list of 3 emoji parsed from the entered toot, garfield, blobfoxbox and blobhajmlem. They can be selected, their shortcode can be tweaked, and they can be assigned to a category, before submitting as a copy or delete operation](../assets/admin-settings-emoji-remote.png)
Through the 'remote' section, you can look up a link to any remote toots (provided the instance isn't suspended). If they use any custom emoji they will be listed, providing an easy way to copy them to the local emoji (for use in your own toots), or disable them ( hiding them from toots).
**Note:** as the testrig server does not federate, this feature can't be used in development (500: Internal Server Error).
### Instance Settings
![Screenshot of the GoToSocial admin panel, showing the fields to change an instance's settings](../public/admin-settings-instance.png)
Here you can set various metadata for your instance, like the displayed name/title, thumbnail image, (short) description, and contact info.
#### Instance Appearance
These settings primary affect how your instance appears to others and on the web.
Your **instance title** will appear at the top of every web page on your instance, and in OpenGraph meta tags, so pick something that represents the vibe of your instance.
The **instance avatar** is sort of like the mascot of your instance. It will appear next to the instance title at the top of every page, and as the preview image in browser tabs, OpenGraph links, and that sort of thing.
If you set an instance avatar, we highly recommend setting the **avatar image description** as well. This will provide alt text for the image you set as avatar, helping screenreader users to understand what's depicted in the image. Keep it short and sweet.
#### Instance Descriptors
You can use these fields to set short and full descriptions of your instance, as well as to provide terms and conditions for current and prospective users of your instance.
The **short description** will be shown on the instance home page, right near the top, and in response to `/api/v1/instance` queries.
It's a good idea to provide something pithy in here, to give visitors to your instance an immediate impression of what you're all about. For example:
> This is an instance for enthusiasts of classic synthesizers.
>
> Sick beats are for life, and not just for Christmas!
or:
> This is a single-user instance just for me!
>
> Here's my profile: @your_username
The **full description** will appear on your instance's /about page, and in response to `/api/v1/instance` queries.
You can use this to provide info like:
- your instance's history, ethos, attitude, and vibe
- the kinds of things your instance denizens tend to post about
- how to get an account on your instance (if it's possible at all)
- a list of users with accounts on the instance, who want to be found more easily
The **terms and conditions** box also appears on your instance's /about page, and in response to `/api/v1/instance` queries.
Use it for filling in stuff like:
- legal jargon (imprint, GDPR, or links thereto)
- federation policy
- data policy
- account deletion/suspension policy
All of the above fields accept **markdown** input, so you can write proper lists, codeblocks, horizontal rules, block quotes, or whatever you like.
You can also mention accounts using the standard `@user[@domain]` format.
Have a look at the [markdown cheat sheet](https://markdownguide.offshoot.io/cheat-sheet/) to see what else you can do.
### Instance Contact Info
In this section, you can provide visitors to your instance with a convenient way of reaching your instance admin.
Links to the set contact account and/or email address will appear on the footer of every web page of your instance, on the /about page in the "contact" section, and in response to `/api/v1/instance` queries.
The selected **contact user** must be an active (not suspended) admin and/or moderator on the instance.
If you're on a single-user instance and you give admin privileges to your main account, you can just fill in your own username here; you don't need to make a separate admin account just for this.
### Instance Custom CSS
custom CSS allows you to further customize the way your instance looks when visited through a browser.
This custom CSS will be applied to all pages of your instance. Users themes and CSS still take precedence over this customization.
See the [Custom CSS](../user_guide/custom_css.md) page for some tips on writing custom CSS for your instance.

View file

@ -1,61 +0,0 @@
# New Account Sign-Ups
If you want to allow more people than just you to have an account on your instance, you can open your instance to new account sign-ups / registrations.
Be wary that as instance admin, like it or not, you are responsible for what people post on your instance. If users on your instance harass or annoy other people on the fediverse, you may find your instance gets a bad reputation and becomes blocked by others. Moderating a space properly takes work. As such, you should carefully consider whether or not you are willing and able to do moderation, and consider accepting sign-ups on your instance only from friends and people that you really trust.
!!! warning
For the sign-up flow to work as intended, your instance [should be configured to send emails](../configuration/smtp.md).
As mentioned below, several emails are sent during the sign-up flow, both to you (as admin/moderator) and to the applicant, including an email asking them to confirm their email address.
If they cannot receive this email (because your instance is not configured to send emails), you will have to manually confirm the account by [using the CLI tool](../admin/cli.md#gotosocial-admin-account-confirm).
## Opening Sign-Ups
You can open new account sign-ups for your instance by changing the variable `accounts-registration-open` to `true` in your [configuration](../configuration/accounts.md), and restarting your GoToSocial instance.
A sign-up form for your instance will be available at the `/signup` endpoint. For example, `https://your-instance.example.org/signup`.
![Sign-up form, showing email, password, username, and reason fields.](../public/signup-form.png)
Also, your instance homepage and "about" pages will be updated to reflect that registrations are open.
When someone submits a new sign-up, they'll receive an email at the provided email address, giving them a link to confirm that the address really belongs to them.
In the meantime, admins and moderators on your instance will receive an email and a notification that a new sign-up has been submitted.
## Handling Sign-Ups
Instance admins and moderators can handle a new sign-up by either approving or rejecting it via the "accounts" -> "pending" section in the admin panel.
![Admin settings panel open to "accounts" -> "pending", showing one account in a list.](../public/signup-pending.png)
If you have no sign-ups, the list pictured above will be empty. If you have a pending account sign-up, however, you can click on it to open that account in the account details screen:
![Details of a new pending account, giving options to approve or reject the sign-up.](../public/signup-account.png)
At the bottom, you will find actions that let you approve or reject the sign-up.
If you **approve** the sign-up, the account will be marked as "approved", and an email will be sent to the applicant informing them their sign-up has been approved, and reminding them to confirm their email address if they haven't already done so. If they have already confirmed their email address, they will be able to log in and start using their account.
If you **reject** the sign-up, you may wish to inform the applicant that their sign-up has been rejected, which you can do by ticking the "send email" checkbox. This will send a short email to the applicant informing them of the rejection. If you wish, you can add a custom message, which will be added at the bottom of the email. You can also add a private note that will be visible to other admins only.
!!! warning
You may want to hold off on approving a sign-up until they have confirmed their email address, in case the applicant made a typo when submitting, or the email address they provided does not actually belong to them. If they cannot confirm their email address, they will not be able to log in and use their account.
## Sign-Up Limits
By default, to avoid sign-up backlogs overwhelming admins and moderators, GoToSocial limits the sign-up pending backlog to 20 accounts. Once there are 20 accounts pending in the backlog waiting to be handled by an admin or moderator, new sign-ups will not be accepted via the form.
By default, new sign-ups will also not be accepted via the form if 10 or more new account sign-ups have been approved in the last 24 hours, to avoid instances rapidly expanding beyond the capabilities of moderators.
In both cases, applicants will be shown an error message explaining why they could not submit the form, and inviting them to try again later.
The limit of sign-ups per day, and the backlog size, can be configured or disabled altogether with the variables `accounts-registration-daily-limit` and `accounts-registration-backlog-limit`. See the [accounts config section](../configuration/accounts.md) for more info.
To combat spam accounts, GoToSocial account sign-ups **always** require manual approval by an administrator, and applicants must **always** confirm their email address before they are able to log in and post.
## Sign-Up Via Invite
NOT IMPLEMENTED YET: in a future update, admins and moderators will be able to create and send invites that allow accounts to be created even when public sign-up is closed, and to pre-approve accounts created via invitation, and/or allow them to override the sign-up limits described above.

View file

@ -1,43 +0,0 @@
# Managing GtS on slow hardware
While GoToSocial runs great on lower-end hardware, some operations are not practical on it, especially
instances with the database on slow storage (think anything that is not an SSD). This document
offers some suggestions on how to work around common issues when running GtS on slow hardware.
## Running database migrations on a different machine
Sometimes a database migration will need to do operations that are taxing on the database's storage.
These operations can take days if the database resides on a hard disk or SD card. If your
database is on slow storage, it can save a lot of time to follow the following procedure:
!!! danger
It might seem tempting to keep GtS running while you run the migrations on another machine, but
doing this will lead to all the posts that are received during the migration post disappearing
once the migrated database is re-imported.
1. Shut down GtS
2. Take a [backup](backup_and_restore.md#what-to-backup-database) of the database
3. Import the database on faster hardware
4. Run the GtS migration on the faster hardware
5. Take a backup of the resultant database
6. Import the resultant backup and overwrite the old database
7. Start GtS with the new version
### Running GtS migrations separately
After you import the database on the faster hardware, you can run the migration without starting
GtS by downloading the *target* GtS version from the [releases](https://codeberg.org/superseriousbusiness/gotosocial/releases) page.
For instance, if you are running `v0.19.2` and you want to upgrade to `v0.20.0-rc1`, download the
latter version. Once you have the binary, set it to executable by running `chmod u+x /path/to/gotosocial`. Afterwards, copy the configuration of the original server, and alter
it with the location of the new database. We copy the configuration in case variables like
the hostname is used in the migration, we want to keep that consistent.
Once everything is in place, you can run the migration like this:
```sh
$ /path/to/gotosocial --config-path /path/to/config migrations run
```
This will run all the migrations, just like GtS would if it was started normally. Once this is done
you can copy the result to the original instance and start the new GtS version there as well, which
will see that everything is migrated and that there's nothing to do except run as expected.

View file

@ -1,23 +0,0 @@
# Spam Filtering
To make life a bit easier for admins trying to combat spam messages from open signup instances, GoToSocial has an experimental spam filter option.
If you or your users are being barraged by spam, try setting the option `instance-federation-spam-filter` to true in your config.yaml. You can read more about the heuristics used in the [instance config page](../configuration/instance.md).
Messages that are considered to be spam will not be stored on your instance, and will not generate notifications.
!!! warning
Spam filters are necessarily imperfect tools, since they will likely catch at least a few legitimate messages in the filter, or indeed fail to catch some messages that *are* spam.
Enabling `instance-federation-spam-filter` should be viewed as a "battening down the hatches" option for when the fediverse is facing a spam wave. Under normal circumstances, you will likely want to leave it turned off to avoid filtering out legitimate messages by accident.
!!! tip
If you want to check what's being caught by the spam filter (if anything), grep your logs for the phrase "looked like spam".
If you're [running GoToSocial as a systemd service](../getting_started/installation/metal.md#optional-enable-the-systemd-service), you can do this with the command:
```bash
journalctl -u gotosocial --no-pager | grep 'looked like spam'
```
If you see no output, that means no spam has been caught in the filter. Otherwise, you will see one or more log lines with links to statuses that have been filtered and dropped.

View file

@ -1,30 +0,0 @@
# Themes
Users on your instance can select a theme for their profile from any css files present in the `web/assets/themes` directory.
GoToSocial comes with some theme files already, but you can add more yourself by doing the following:
1. Create a file in `web/assets/themes` called (for example) `new-theme.css`.
2. (Optional) Include the following comment at the top of your theme file to title and describe your theme:
```css
/*
theme-title: My New Theme
theme-description: This is an example theme
*/
```
You can use any text you like for these fields, but bear in mind whatever you write here will appear in the settings panel to help users when selecting a theme, so keep it short and sweet.
3. Fill out your custom CSS in the rest of the file. You can use one of the existing CSS files to guide you. Also see [this page](../user_guide/custom_css.md) for some rough guidelines about how to write accessible CSS.
4. Restart your instance so that the new CSS file is picked up.
!!! info
If you're using Docker for your deployment, you can mount theme files from the host machine into your GoToSocial `web/assets/themes` directory instead, by including entries for them in the `volumes` section of your Docker configuration.
For example, say you've created a theme on your host machine at `~/gotosocial/my-themes/new-theme.css`, you could mount that theme into the GoToSocial Docker container in the following way:
```yaml
volumes:
[.... some other volume entries ...]
- "~/gotosocial/my-themes/new-theme.css:/gotosocial/web/assets/themes/new-theme.css"
```
Bear in mind if you mount an entire directory to `/gotosocial/web/assets/themes` instead of mounting individual theme files, you'll override the default themes.

View file

@ -1,31 +0,0 @@
# Build without Wazero / WASM
!!! Danger "This is unsupported"
We do not offer any kind of support for deployments of GoToSocial built with the `nowasm` tag described in this section. Such builds should be considered strictly experimental, and any issues that come when running them are none of our business! Please don't open issues on the repo looking for help debugging deployments of `nowasm` builds.
On [supported platforms](../../getting_started/releases.md#supported-platforms), GoToSocial uses the WebAssembly runtime [Wazero](https://wazero.io/) to sandbox `ffmpeg`, `ffprobe`, and `sqlite3` WebAssembly binaries, allowing these applications to be packaged and run inside the GoToSocial binary, without requiring admins to install + manage any external dependencies.
This has the advantage of making it easier for admins to maintain their GoToSocial instance, as their GtS binary is completely isolated from any changes to their system-installed `ffmpeg`, `ffprobe`, and `sqlite`. It's also a bit safer to run `ffmpeg` in this way, as GoToSocial wraps the `ffmpeg` binary in a very constrained file system that doesn't permit the `ffmpeg` binary to access any files other than the ones it's decoding + reencoding. In other words, GoToSocial on supported platforms offers most of the functionality of `ffmpeg` and so on, without some of the headaches.
However, not all platforms are capable of running Wazero in the much-faster "compiler" mode, and have to fall back to the very slow (and resource-heavy) "interpreter" mode. See [this table](https://github.com/tetratelabs/wazero?tab=readme-ov-file#conformance) from Wazero for conformance.
"Interpreter" mode runs so poorly for GoToSocial's use case that it's simply not feasible to run a GoToSocial instance in a stable manner on platforms that aren't 64-bit Linux or 64-bit FreeBSD, as all the memory and CPU get gobbled up by media processing.
However! To enable folks to run **experimental, unsupported deployments of GoToSocial**, we expose the `nowasm` build tag, which can be used to compile a build of GoToSocial that does not use Wazero or WASM at all.
A GoToSocial binary built with `nowasm` will use the [modernc version of SQLite](https://pkg.go.dev/modernc.org/sqlite) instead of the WASM one, and will use on-system `ffmpeg` and `ffprobe` binaries for media processing.
!!! tip
To test if your system is compatible with the standard builds, you can use this command:
`if grep -qE '^flags.* (sse4|LSE)' /proc/cpuinfo; then echo "Your system is supporting GTS!"; else echo "Your system is not supporting GTS, you'll have to use the 'nowasm' builds :("; fi`
To build GoToSocial with the `nowasm` tag, you can pass the tag into our convenience `build.sh` script like so:
```bash
GO_BUILDTAGS=nowasm ./scripts/build.sh
```
In order to run a version of GoToSocial built in this way, you must ensure that `ffmpeg` and `ffprobe` are installed on the host. This is usually as simple as running a command like `doas -u root pkg_add ffmpeg` (OpenBSD), or `sudo apt install ffmpeg` (Debian etc.).
!!! Danger "No really though, it's unsupported"
Again, if running builds of GoToSocial with `nowasm` works for your OS/Arch combination, that's great, but we do not support such builds and we won't be able to help debugging why something doesn't work.

View file

@ -19,25 +19,25 @@ Many implementations will regularly request the public key for a user in order t
## Configuration snippets
=== "nginx"
### nginx
For nginx, you'll need to start by configuring a cache zone. The cache zone must be created in the `http` section, not within `server` or `location`.
For nginx, you'll need to start by configuring a cache zone. The cache zone must be created in the `http` section, not within `server` or `location`.
```nginx
http {
```nginx
http {
...
proxy_cache_path /var/cache/nginx keys_zone=gotosocial_ap_public_responses:10m inactive=1w;
}
```
}
```
This configures a cache of 10MB whose entries will be kept up to one week if they're not accessed.
This configures a cache of 10MB whose entries will be kept up to one week if they're not accessed.
The zone is named `gotosocial_ap_public_responses` but you can name it whatever you want. 10MB is a lot of cache keys; you can probably use a smaller value on small instances.
The zone is named `gotosocial_ap_public_responses` but you can name it whatever you want. 10MB is a lot of cache keys; you can probably use a smaller value on small instances.
Second, we need to update our GoToSocial nginx configuration to actually use the cache for the endpoints we want to cache.
Second, we need to update our GoToSocial nginx configuration to actually use the cache for the endpoints we want to cache.
```nginx
server {
```nginx
server {
server_name social.example.org;
location ~ /.well-known/(webfinger|host-meta)$ {
@ -71,16 +71,16 @@ Many implementations will regularly request the public key for a user in order t
proxy_pass http://localhost:8080;
}
```
```
The `proxy_pass` and `proxy_set_header` are mostly the same, but the `proxy_cache*` entries warrant some explanation:
The `proxy_pass` and `proxy_set_header` are mostly the same, but the `proxy_cache*` entries warrant some explanation:
- `proxy_cache gotosocial_ap_public_responses` tells nginx to use the `gotosocial_ap_public_responses` cache zone we previously created. If you named it something else, you should change this value
- `proxy_cache_background_update on` means nginx will try and refresh a cached resource that's about to expire in the background, to ensure it has a current copy on disk
- `proxy_cache_key` is configured in such a way that it takes the query string into account for caching. So a request for `.well-known/webfinger?acct=user1@example.org` and `.well-known/webfinger?acct=user2@example.org` are not seen as the same.
- `proxy_cache_valid 200 10m;` means we only cache 200 responses from GTS and for 10 minutes. You can add additional lines of these, like `proxy_cache_valid 404 1m;` to cache 404 responses for 1 minute
- `proxy_cache_use_stale` tells nginx it's allowed to use a stale cache entry (so older than 10 minutes) in certain cases
- `proxy_cache_lock on` means that if a resource is not cached and there's multiple concurrent requests for them, the queries will be queued up so that only one request goes through and the rest is then answered from cache
- `add_header X-Cache-Status $upstream_cache_status` will add an `X-Cache-Status` header to the response so you can check if things are getting cached. You can remove this.
- `proxy_cache gotosocial_ap_public_responses` tells nginx to use the `gotosocial_ap_public_responses` cache zone we previously created. If you named it something else, you should change this value
- `proxy_cache_background_update on` means nginx will try and refresh a cached resource that's about to expire in the background, to ensure it has a current copy on disk
- `proxy_cache_key` is configured in such a way that it takes the query string into account for caching. So a request for `.well-known/webfinger?acct=user1@example.org` and `.well-known/webfinger?acct=user2@example.org` are not seen as the same.
- `proxy_cache_valid 200 10m;` means we only cache 200 responses from GTS and for 10 minutes. You can add additional lines of these, like `proxy_cache_valid 404 1m;` to cache 404 responses for 1 minute
- `proxy_cache_use_stale` tells nginx it's allowed to use a stale cache entry (so older than 10 minutes) in certain cases
- `proxy_cache_lock on` means that if a resource is not cached and there's multiple concurrent requests for them, the queries will be queued up so that only one request goes through and the rest is then answered from cache
- `add_header X-Cache-Status $upstream_cache_status` will add an `X-Cache-Status` header to the response so you can check if things are getting cached. You can remove this.
The provided configuration will serve a stale response in case there's an error proxying to GoToSocial, if our connection to GoToSocial times out, if GoToSocial returns a `5xx` status code or if GoToSocial returns 429 (Too Many Requests). The `updating` value says that we're allowed to serve a stale entry if nginx is currently in the process of refreshing its cache. Because we configured `inactive=1w` in the `proxy_cache_path` directive, nginx may serve a response up to one week old if the conditions in `proxy_cache_use_stale` are met.
The provided configuration will serve a stale response in case there's an error proxying to GoToSocial, if our connection to GoToSocial times out, if GoToSocial returns a `5xx` status code or if GoToSocial returns 429 (Too Many Requests). The `updating` value says that we're allowed to serve a stale entry if nginx is currently in the process of refreshing its cache. Because we configured `inactive=1w` in the `proxy_cache_path` directive, nginx may serve a response up to one week old if the conditions in `proxy_cache_use_stale` are met.

View file

@ -20,81 +20,12 @@ The filesystem location of `/assets` is defined by the [`web-asset-base-dir`](..
## Configuration
=== "apache2"
### nginx
The `Cache-Control` header is manually set to merge the values
from the configuration and the `expires` directive to avoid
breakage from having two header lines. `Header set` defaults
to ` onsuccess`, so it is also not added to error responses.
Here's an example of the three location blocks you'll need to add to your existing configuration in nginx:
Assuming your GtS installation is rooted in `/opt/GtS` with a
`storage` subdirectory, and the webserver has been given access,
add the following section to the vhost:
```apacheconf
<Directory /opt/GtS/web/assets>
Options None
AllowOverride None
Require all granted
ExpiresActive on
ExpiresDefault A300
Header set Cache-Control "public, max-age=300"
</Directory>
RewriteRule "^/assets/(.*)$" "/opt/GtS/web/assets/$1" [L]
<Directory /opt/GtS/storage>
Options None
AllowOverride None
Require all granted
ExpiresActive on
ExpiresDefault A604800
Header set Cache-Control "private, immutable, max-age=604800"
</Directory>
RewriteCond "/opt/GtS/storage/$1" -f
RewriteRule "^/fileserver/(.*)$" "/opt/GtS/storage/$1" [L]
```
The trick here is that, in an Apache 2-based reverse proxy setup…
```apacheconf
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://localhost:8980/$1" [P,L]
ProxyIOBufferSize 65536
ProxyTimeout 120
ProxyPreserveHost On
<Location "/">
ProxyPass http://127.0.0.1:8980/
ProxyPassReverse http://127.0.0.1:8980/
</Location>
```
… everything is proxied by default, the `RewriteRule` bypasses
the proxy (by specifying a filesystem path to redirect to) for
specific URL præficēs and the `RewriteCond` ensures to only
disable the `/fileserver/` proxy if the file is, indeed, present.
Also run the following commands (assuming a Debian-like setup)
to enable the modules used:
```
$ sudo a2enmod expires
$ sudo a2enmod headers
$ sudo a2enmod rewrite
```
Then (after a configtest), restart Apache.
=== "nginx"
Here's an example of the three location blocks you'll need to add to your existing configuration in nginx:
```nginx
server {
```nginx
server {
server_name social.example.org;
location /assets/ {
@ -116,26 +47,26 @@ The filesystem location of `/assets` is defined by the [`web-asset-base-dir`](..
location /fileserver/ {
alias storage-local-base-path/;
autoindex off;
expires 1w;
expires max;
add_header Cache-Control "private, immutable";
try_files $uri @fileserver;
}
}
```
}
```
The `/fileserver` location is a bit special. When we fail to fetch the media from disk, we want to proxy the request on to GoToSocial so it can try and fetch it. The `try_files` directive can't take a `proxy_pass` itself so instead we created the named `@fileserver` location that we pass in last to `try_files`.
The `/fileserver` location is a bit special. When we fail to fetch the media from disk, we want to proxy the request on to GoToSocial so it can try and fetch it. The `try_files` directive can't take a `proxy_pass` itself so instead we created the named `@fileserver` location that we pass in last to `try_files`.
!!! bug "Trailing slashes"
!!! bug "Trailing slashes"
The trailing slashes in the `location` directives and the `alias` are significant, do not remove those.
The `expires` directive adds the necessary headers to inform the client how long it may cache the resource:
The `expires` directive adds the necessary headers to inform the client how long it may cache the resource:
* For assets, which may change on each release, 5 minutes is used in this example
* For attachments, which should never change once they're created, we currently use one week
* For assets, which may change on each release, 5 minutes is used in this example
* For attachments, which should never change once they're created, `max` is used instead setting the cache expiry to the 31st of December 2037.
For other options, see the nginx documentation on the [`expires` directive](https://nginx.org/en/docs/http/ngx_http_headers_module.html#expires).
For other options, see the nginx documentation on the [`expires` directive](https://nginx.org/en/docs/http/ngx_http_headers_module.html#expires).
Nginx does not add cache headers to 4xx or 5xx response codes so a failure to fetch an asset won't get cached by clients. The `autoindex off` directive tells nginx to not serve a directory listing. This should be the default but it doesn't hurt to be explicit. The added `add_header` lines set additional options for the `Cache-Control` header:
Nginx does not add cache headers to 4xx or 5xx response codes so a failure to fetch an asset won't get cached by clients. The `autoindex off` directive tells nginx to not serve a directory listing. This should be the default but it doesn't hurt to be explicit. The added `add_header` lines set additional options for the `Cache-Control` header:
* `public` is used to indicate that anyone may cache this resource
* `immutable` is used to indicate this resource will never change while it is fresh (it's before the end of the expires) allowing clients to forgo conditional requests to revalidate the resource during that timespan.
* `public` is used to indicate that anyone may cache this resource
* `immutable` is used to indicate this resource will never change while it is fresh (it's before the end of the expires) allowing clients to forgo conditional requests to revalidate the resource during that timespan.

View file

@ -1,48 +0,0 @@
# Health Checks
GoToSocial exposes two health check HTTP endpoints: `/readyz` and `/livez`.
These can be used to check whether GoToSocial is reachable and able to make simple database queries.
`/livez` will always return a 200 OK response with no body, in response to both GET and HEAD requests. This is useful to check if the GoToSocial service is alive.
`/readyz` will return a 200 OK response with no body, in response to both GET and HEAD requests, if GoToSocial is able to run a very simple SELECT query against the configured database backend. If an error occurs while running the SELECT, the error will be logged, and 500 Internal Server Error will be returned, with no body.
You can use the above endpoints to implement health checks in container runtimes / orchestration systems.
For example, in a Docker setup, you could add the following to your docker-compose.yaml:
```yaml
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:8080/readyz || exit 1
interval: 120s
retries: 5
start_period: 30s
timeout: 10s
```
The above health check will start after 30 seconds, and check every two minutes whether the service is available by doing a HEAD request to `/readyz`. If the check fails five times in a row, the service will be reported as unhealthy. You can use this in whatever orchestration system you are using to force the container to restart.
!!! warning
When doing database migrations on slow hardware, migration might take longer than the 10 minutes afforded by the above health check.
On such a system, you may want to increase the interval or number of retries of the health check to ensure that you don't stop GoToSocial in the middle of a migration (which is a very bad thing to do!).
!!! tip
Though the health check endpoints don't reveal any sensitive info, and run only very simple queries, you may want to avoid exposing them to the outside world. You could do this in nginx, for example, by adding the following snippet to your `server` stanza:
```nginx
location /livez {
return 404;
}
location /readyz {
return 404;
}
```
This will cause nginx to intercept these requests *before* they are passed to GoToSocial, and just return 404 Not Found.
References:
- [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#healthcheck)
- [Compose file reference](https://docs.docker.com/compose/compose-file/compose-file-v3/#healthcheck)

View file

@ -71,18 +71,18 @@ In order to configure the redirect, you'll need to configure it on the account d
```nginx
server {
server_name example.org; # account-domain
server_name example.org;
location /.well-known/webfinger {
rewrite ^.*$ https://social.example.org/.well-known/webfinger permanent; # host
rewrite ^.*$ https://social.example.org/.well-known/webfinger permanent;
}
location /.well-known/host-meta {
rewrite ^.*$ https://social.example.org/.well-known/host-meta permanent; # host
rewrite ^.*$ https://social.example.org/.well-known/host-meta permanent;
}
location /.well-known/nodeinfo {
rewrite ^.*$ https://social.example.org/.well-known/nodeinfo permanent; # host
rewrite ^.*$ https://social.example.org/.well-known/nodeinfo permanent;
}
}
```
@ -96,23 +96,9 @@ myservice:
image: foo
# Other stuff
labels:
- 'traefik.http.routers.myservice.rule=Host(`example.org`)' # account-domain
- 'traefik.http.routers.myservice.rule=Host(`example.org`)'
- 'traefik.http.middlewares.myservice-gts.redirectregex.permanent=true'
- 'traefik.http.middlewares.myservice-gts.redirectregex.regex=^https://(.*)/.well-known/(webfinger|nodeinfo|host-meta)(\?.*)?$' # host
- 'traefik.http.middlewares.myservice-gts.redirectregex.replacement=https://social.$${1}/.well-known/$${2}$${3}' # host
- 'traefik.http.middlewares.myservice-gts.redirectregex.regex=^https://(.*)/.well-known/(webfinger|nodeinfo|host-meta)$$'
- 'traefik.http.middlewares.myservice-gts.redirectregex.replacement=https://social.$${1}/.well-known/$${2}'
- 'traefik.http.routers.myservice.middlewares=myservice-gts@docker'
```
### Caddy 2
Ensure that the redirect is configured on the account domain in your `Caddyfile`. The following example assumes the account domain as `example.com`, and host domain as `social.example.com`.
```
example.com { # account-domain
redir /.well-known/host-meta* https://social.example.com{uri} permanent # host
redir /.well-known/webfinger* https://social.example.com{uri} permanent # host
redir /.well-known/nodeinfo* https://social.example.com{uri} permanent # host
}
```

View file

@ -1,6 +1,6 @@
# Advanced
In this section we touch on a number of more advanced topics, primarily related around building, deploying, operating and tuning GoToSocial.
In this section we touch on a number of more advanced topics, primarily related around deploying, operating and tuning GoToSocial.
We consider these topics advanced because applying them incorrectly does have the possibility of causing client and federation issues. Applying any of these configuration changes may also make it harder for you to debug an issue with your GoToSocial instance if you don't understand the changes that you're making.
@ -14,6 +14,3 @@ We consider these topics advanced because applying them incorrectly does have th
* [Process sandboxing](security/sandboxing.md)
* [Firewall configuration](security/firewall.md)
* [Tracing](tracing.md)
* [Metrics](metrics.md)
* [Replicating SQLite](replicating-sqlite.md)
* [SQLite on networked storage](sqlite-networked-storage.md)

View file

@ -1,108 +0,0 @@
# Metrics
GoToSocial uses the [OpenTelemetry][otel] Go SDK to enable instance admins to expose runtime metrics in the Prometheus metrics format.
Currently, the following metrics are collected:
* Go performance and runtime metrics
* Gin (HTTP server) metrics
* Bun (database) metrics
## Enabling metrics
To enable metrics, first set the `metrics-enabled` configuration value to `true` in your config.yaml file:
```yaml
metrics-enabled: true
```
Then, you will need to set some additional environment variables on the GoToSocial process in order to configure OpenTelemetry to expose metrics in the Prometheus format:
```env
OTEL_METRICS_PRODUCERS=prometheus
OTEL_METRICS_EXPORTER=prometheus
```
By default, this configuration will instantiate an additional HTTP server running alongside the standard GoToSocial HTTP server, which exposes a Prometheus metrics endpoint at `localhost:9464/metrics`.
!!! tip
If you are running GoToSocial using the [example systemd service definition](../../example/gotosocial.service), you can easily set these environment variables by uncommenting the relevant two lines in that file, and reloading + restarting the service.
If you wish, you can further customize this metrics HTTP server by using the following environment variables to change the host and port:
```env
OTEL_EXPORTER_PROMETHEUS_HOST=example.org
OTEL_EXPORTER_PROMETHEUS_PORT=9999
```
## Serving metrics to the outside world
If you have deployed GoToSocial in a "bare-metal" fashion without a reverse proxy, you can expose the metrics endpoint to the outside world by setting `OTEL_EXPORTER_PROMETHEUS_HOST` to your host value. For example, if your GtS instance `host` configuration value is set to `example.org`, you should set `OTEL_EXPORTER_PROMETHEUS_HOST=example.org`. You should then be able to access your metrics at `http://example.org:9464/metrics`. GoToSocial running in this fashion will not serve LetsEncrypt certificates at the metrics endpoint, so you will be limited to using HTTP rather than HTTPS.
If you are using a reverse proxy like Nginx, you can expose the metrics endpoint to the outside world with HTTPS certificates, by putting an additional location stanza in your Nginx configuration above the catch-all `location /` stanza:
```nginx
location /metrics {
proxy_pass http://127.0.0.1:9464;
}
```
This will instruct Nginx to forward requests to `example.org/metrics` to the separate Prometheus server running on port 9464.
## Enabling basic authentication
Although there is no sensitive data contained in the OTEL runtime statistics exported by Prometheus, you may nevertheless wish to gate access to the `/metrics` endpoint behind some kind of authentication, to prevent every Tom, Dick, and Harry from looking at your runtime stats.
You can do this by configuring your reverse proxy to require basic authentication for access to `/metrics`.
In Nginx, for example, you could do this by creating an `htpasswd` file alongside your site in the `sites-available` directory of Nginx, and instructing Nginx to use that file to gate access.
Assuming you followed the [guide for setting up Nginx as your reverse proxy](../getting_started/reverse_proxy/nginx.md), you will already have a file for your Nginx service definition at `/etc/nginx/sites-available/example.org`, where `example.org` is the hostname of your instance.
You can create an `htpasswd` file alongside this file using the following command:
```bash
htpasswd -c /etc/nginx/sites-available/example.org.htpasswd username
```
In the command, be sure to replace `example.org` with your hostname, and `username` with whatever username you want to use.
Now, edit `/etc/nginx/sites-available/example.org` and update your `/metrics` stanza to use the `httpasswd` file. After editing it should look something like this:
```nginx
location /metrics {
proxy_pass http://127.0.0.1:9464;
auth_basic "Metrics";
auth_basic_user_file /etc/nginx/sites-available/example.org.htpasswd;
}
```
Again, replace `example.org` in that snippet with your instance hostname.
When you're finished editing, reload + restart Nginx, and you should see a basic authentication prompt when visiting the `/metrics` endpoint of your instance in your browser.
## Prometheus scrape configuration
You can scrape your `/metrics` endpoint with a Prometheus instance using the following configuration in your `scrape_configs`:
```yaml
- job_name: gotosocial
metrics_path: /metrics
scheme: https
basic_auth:
username: some_username
password: some_password
static_configs:
- targets:
- example.org
```
Change `example.org` to your hostname in the above snippet. If you are not using HTTPS, change the `scheme` value to `http`. If you are not using basic authentication, you can remove the `basic_auth` section. If you are not using a reverse proxy, and metrics are exposed on port 9464, add the port to the host (eg., `example.org` -> `example.org:9464`).
## Viewing metrics on Grafana
Instructions on how to set up Grafana are beyond the scope of this document. However, once you have set up a Grafana to pull from your Prometheus instance, you can import the [example Grafana dashboard](https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/example/metrics/gotosocial_grafana_dashboard.json) into your Grafana frontend to easily view GoToSocial Go runtime and HTTP metrics.
[otel]: https://opentelemetry.io/
[prom]: https://prometheus.io/docs/instrumenting/exposition_formats/
[obs]: ../configuration/observability_and_metrics.md

View file

@ -1,104 +0,0 @@
# Replicating SQLite
Next to your regular [backup methods](../admin/backup_and_restore.md), you might want to set up replication for disaster recovery to another path or external host.
For this to work properly, SQLite needs the journal mode to be configured in `WAL` mode, with synchronous mode set to `NORMAL`. This is the default configuration for GoToSocial.
You can check your settings in the configuration file. The journal mode is set in `db-sqlite-journal-mode` and the synchronous mode in `db-sqlite-synchronous`.
## Litestream on Linux
A relatively light, and fast way to set up replication with SQLite is by using [Litestream](https://litestream.io). It can be configured very easily and supports different backends like file based replication, S3 compatible storage and many other setups.
You can then install the prebuilt package by either the deb file on Linux, or building it from source on other distributions.
Using a .deb package on Linux:
Navigate to the [releases page](https://github.com/benbjohnson/litestream/releases/latest), and download the latest release (make sure to select the appropiate platform for the wget command below).
```bash
wget https://github.com/benbjohnson/litestream/releases/download/v0.3.13/litestream-v0.3.13-linux-amd64.deb
sudo dpkg -i litestream-*.deb
```
## Configuring Litestream
Configuration is done by editing the configuration file. It's located in /etc/litestream.yml.
### Configuring file based replication
```yaml
dbs:
- path: /gotosocial/sqlite.db
- path: /backup/sqlite.db
```
### Configuring S3 based replication
Set up a bucket for replication, and make sure to set it to be private.
Make sure to replace the example `access-key-id` and `secret-access-key` with the proper values from your dashboard.
```yaml
access-key-id: AKIAJSIE27KKMHXI3BJQ
secret-access-key: 5bEYu26084qjSFyclM/f2pz4gviSfoOg+mFwBH39
dbs:
- path: /gotosocial/sqlite.db
- url: s3://my.bucket.com/db
```
When using a S3 compatible storage provider you will need to set an endpoint.
For example for minio this can be done with the following configuration.
```yaml
access-key-id: miniouser
secret-access-key: miniopassword
dbs:
- path: /gotosocial/sqlite.db
- type: s3
bucket: mybucket
path: sqlite.db
endpoint: minio:9000
```
## Enabling replication
You can enable replication on Linux by enabling the Litestream service.
```bash
sudo systemctl enable litestream
sudo systemctl start litestream
```
Check if it's running properly using `sudo journalctl -u litestream -f`.
If you need to change the configuration file, restart Litestream:
```bash
sudo systemctl restart litestream
```
### Recovering from the configured backend
You can pull down a recovery file from the stored backend with the following simple command.
```bash
sudo litestream restore
```
If you have configured multiple files to be backupped, or have multiple replicas, specify what you want to do.
For filebased replication:
```bash
sudo litestream restore -o /gotosocial/sqlite.db /backup/sqlite.db
```
For s3 based replication:
```bash
sudo litestream restore -o /gotosocial/sqlite.db s3://bucketname/db
```

View file

@ -82,92 +82,3 @@ Both SSHGuard and fail2ban ship with "backends" that can target iptables and nft
* [ArchWiki](https://wiki.archlinux.org/title/sshguard) on sshguard
* [FreeBSD manual](https://man.freebsd.org/cgi/man.cgi?query=sshguard&sektion=8&manpath=FreeBSD+13.2-RELEASE+and+Ports) for sshguard
* [SSHGuard setup](https://manpages.ubuntu.com/manpages/lunar/en/man7/sshguard-setup.7.html) manual for Ubuntu
For fail2ban, you can use the following regex, which triggers fail2ban on failed logins and not another 'Unauthorized' errors (API for example):
```regex
statusCode=401 path=/auth/sign_in clientIP=<HOST> .* msg=\"Unauthorized:
```
## IP blocking
GoToSocial implements rate-limiting in order to try and protect your instance from one party taking up all your processing capacity. However, if you know this traffic isn't legitimate or coming from an instance you don't wish to federate with anyway, you can block the IP(s) the traffic is originating from instead and spare GoToSocial from having to do any work.
### Linux
Blocking IPs is done with iptables or nftables. If you're using a firewall frontend like UFW or firewalld, use their facilities to block an IP.
In iptables, people tend to add a `DROP` rule for an IP in the `filter` table on the `INPUT` chain. On nftables, it's often done on a table with a chain with the `ip` or `ip6` address family. In both those cases the kernel has already done a lot of unnecessary processing of the incoming traffic, just for it to then be blocked by an IP match.
When using iptables, this can be done more effectively using the `mangle` table and the `PREROUTING` chain. You can check this blog post on [how that works in iptables][iptblock]. For nftables, you want to block using [the `netdev` family][nftnetdev] instead.
[iptblock]: https://javapipe.com/blog/iptables-ddos-protection/
[nftnetdev]: https://wiki.nftables.org/wiki-nftables/index.php/Nftables_families#netdev
#### iptables
An example of blocking an IP using `iptables`:
```
iptables -t mangle -A PREROUTING -s 1.0.0.0/8 -j DROP
ip6tables -t mangle -A PREROUTING -s fc00::/7 -j DROP
```
When using iptables, adding many rules slows things down significantly, including reloading the firewall when adding/removing rules. Since you may wish to block many IP addresses, use [the `ipset` module][ipset] and add a single block rule for the set instead.
[ipset]: https://ipset.netfilter.org/ipset.man.html
Start by creating your sets and adding some IPs to them:
```
ipset create baddiesv4 hash:ip family inet
ipset create baddiesv6 hash:ip family inet6
ipset add baddiesv4 1.0.0.0/8
ipset add baddiesv6 fc00::/7
```
Then, update your iptables rules to target the set instead:
```
iptables -t mangle -A PREROUTING -m set --match-set baddiesv4 src -j DROP
ip6tables -t mangle -A PREROUTING -m set --match-set baddiesv6 src -j DROP
```
#### nftables
For nftables, you can use something like:
```
table netdev filter {
chain ingress {
set baddiesv4 {
type ipv4_addr
flags interval
elements = { \
1.0.0.0/8, \
2.2.2.2/32 \
}
}
set baddiesv6 {
type ipv6_addr
flags interval
elements = { \
2620:4f:8000::/48, \
fc00::/7 \
}
}
type filter hook ingress device <interface name> priority -500;
ip saddr @baddiesv4 drop
ip6 saddr @baddiesv6 drop
}
}
```
### BSDs
When using pf, you can create a persistent table, typically named `<badhosts>`, to which you add the IP addresses you want to block. Tables can also read from other files, so it's possible to keep the list of IPs outside of your main `pf.conf`.
An example of how to do this can be found [in the pf manual][manpf].
[manpf]: https://man.openbsd.org/pf.conf#TABLES

View file

@ -18,13 +18,13 @@ Different distributions have different sandboxing mechanisms they prefer and sup
We ship an example AppArmor policy for GoToSocial, which you can retrieve and install as follows:
```sh
$ curl -LO 'https://codeberg.org/superseriousbusiness/gotosocial/raw/main/example/apparmor/gotosocial'
$ curl -LO 'https://github.com/superseriousbusiness/gotosocial/raw/main/example/apparmor/gotosocial'
$ sudo install -o root -g root gotosocial /etc/apparmor.d/gotosocial
$ sudo apparmor_parser -Kr /etc/apparmor.d/gotosocial
```
!!! tip
The provided AppArmor example is just intended to get you started. It will still need to be edited depending on your exact setup; consult the comments in the example profile file for more information.
If you're using SQLite, the AppArmor profile expects the database in `/gotosocial/db/` so you'll need to adjust your configuration paths or the policy accordingly.
With the policy installed, you'll need to configure your system to use it to constrain the permissions GoToSocial has.

View file

@ -1,35 +0,0 @@
# SQLite on networked storage
SQLite's operating model assumes the database and the processes or applications using it are colocated on the same host. When running the database in WAL-mode, which is GoToSocial's default, it relies on shared memory between processes to ensure the integrity of your database.
!!! quote
All processes using a database must be on the same host computer; WAL does not work over a network filesystem. This is because WAL requires all processes to share a small amount of memory and processes on separate host machines obviously cannot share memory with each other.
— SQLite.org [Write-Ahead Logging](https://www.sqlite.org/wal.html)
This also means that any other processes accessing the database need to run in the same namespace or container context.
It is in theory possible to run SQLite over Samba, NFS, iSCSI or other forms of filesystems accessed over the network. But it is neither recommended nor supported by the SQLite maintainers, irrespective of whether you're running with write-ahead logging or not. Doing so puts you at risk of database corruption. There is a long history of networked storage having synchronisation issues in their locking primitives, or implementing them with weaker guarantees than what a local filesystem can provide.
Your cloud provider's external volumes, like Hetzner Cloud Volumes, AWS EBS, GCP Persistent Disk etc. may also cause problems, and add variable latency. This has a tendency to severely degrade SQLite's performance.
If you're going to access your database over the network, it's better to use a database with a client-server architecture. GoToSocial supports Postgres for such use-cases.
For the purpose of having a copy of the SQLite database on durable long-term storage, refer to [SQLite streaming replication](replicating-sqlite.md) instead. Remember that neither replication nor using a networked filesystem are a substitute [for having backups](../admin/backup_and_restore.md).
## Settings
!!! danger "Corrupted database"
We do not support running GoToSocial with SQLite on a networked filesystem and we will not be able to help you if you damage your database this way.
Should you really want to take this risk, you'll need to adjust the SQLite [synchronous][sqlite-sync] mode and [journal][sqlite-journal] mode to match the limitations of the filesystem.
[sqlite-sync]: https://www.sqlite.org/pragma.html#pragma_synchronous
[sqlite-journal]: https://www.sqlite.org/pragma.html#pragma_journal_mode
You'll need to update the following settings:
* `db-sqlite-journal-mode`
* `db-sqlite-synchronous`
We don't provide any recommendations as this will vary based on the solution you're using. See [this issue](https://codeberg.org/superseriousbusiness/gotosocial/issues/3360#issuecomment-2380332027) for what you could potentially set those values to.

View file

@ -1,20 +1,25 @@
# Tracing
GoToSocial comes with [OpenTelemetry][otel] based tracing built-in. It's not wired through every function, but our HTTP handlers and database library will create spans that may help you debug issues.
## Enabling tracing
To enable tracing on your instance, you must set `tracing-enabled` to `true` in your config.yaml file. Then, you must set the environment variable `OTEL_TRACES_EXPORTER` to your desired tracing format. A list of available options is available [here](https://opentelemetry.io/docs/languages/sdk-configuration/general/#otel_traces_exporter). Once you have changed your config and set the environment variable, restart your instance.
If necessary, you can do further configuration of tracing using the other environment variables listed [here](https://opentelemetry.io/docs/languages/sdk-configuration/general/).
## Ingesting traces
GoToSocial comes with [OpenTelemetry][otel] based tracing built-in. It's not wired through every function, but our HTTP handlers and database library will create spans. How to configure tracing is explained in the [Observability configuration reference][obs].
In order to receive the traces, you need something to ingest them and then visualise them. There are many options available including self-hosted and commercial options.
In [`example/tracing`][ext] we provide an example of how to do this using [Grafana Tempo][tempo] to ingest the spans and [Grafana][grafana] to explore them. You can use the files with `docker-compose up -d` to get Tempo and Grafana running.
We provide an example of how to do this using [Grafana Tempo][tempo] to ingest the spans and [Grafana][grafana] to explore them. Please beware that the configuration we provide is not suitable for a production setup. It can be used safely for local development and can provide a good starting point for setting up your own tracing infrastructure.
Please be aware that while the example configuration we provide can be used safely for local development and can provide a good starting point for setting up your own tracing infrastructure, it is not suitable for a so-called "production" setup.
You'll need the files in [`example/tracing`][ext]. Once you have those you can run `docker-compose up -d` to get Tempo and Grafana running. With both services running, you can add the following to your GoToSocial configuration and restart your instance:
```yaml
tracing-enabled: true
tracing-transport: "grpc"
tracing-endpoint: "localhost:4317"
tracing-insecure: true
```
[otel]: https://opentelemetry.io/
[obs]: ../configuration/observability.md
[tempo]: https://grafana.com/oss/tempo/
[grafana]: https://grafana.com/oss/grafana/
[ext]: https://github.com/superseriousbusiness/gotosocial/tree/main/example/tracing
## Querying and visualising traces
@ -22,23 +27,18 @@ Once you execute a few queries against your instance, you'll be able to find the
Using TraceQL, a simple query to find all traces related to requests to `/api/v1/instance` would look like this:
```traceql
```
{.http.route = "/api/v1/instance"}
```
If you wanted to see all GoToSocial traces, you could instead run:
```traceql
```
{.service.name = "GoToSocial"}
```
Once you select a trace, a second panel will open up visualising the span. You can drill down from there, by clicking into every sub-span to see what it was doing.
![Grafana showing a trace for the /api/v1/instance endpoint](../overrides/public/tracing.png)
![Grafana showing a trace for the /api/v1/instance endpoint](../assets/tracing.png)
[traceql]: https://grafana.com/docs/tempo/latest/traceql/
[otel]: https://opentelemetry.io/
[obs]: ../configuration/observability_and_metrics.md
[tempo]: https://grafana.com/oss/tempo/
[grafana]: https://grafana.com/oss/grafana/
[ext]: https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/example/tracing

View file

@ -1,144 +0,0 @@
# Authentication with the API
Using the client API requires authentication. This page documents the general flow for retrieving an authentication token with examples for doing this on the CLI using `curl`.
!!! tip
If you want to get an API access token via the settings panel instead, without having to use the command line, see the [Application documentation](https://docs.gotosocial.org/en/latest/user_guide/settings/#applications).
## Create a new application
We need to register a new application, which we can then use to request an OAuth token. This is done by making a `POST` request to the `/api/v1/apps` endpoint. Replace `your_app_name` in the command below with the name you want to use for your application:
```bash
curl \
-H 'Content-Type:application/json' \
-d '{
"client_name": "your_app_name",
"redirect_uris": "urn:ietf:wg:oauth:2.0:oob",
"scopes": "read"
}' \
'https://example.org/api/v1/apps'
```
The string `urn:ietf:wg:oauth:2.0:oob` is an indication of what is known as out-of-band authentication - a technique used in multi-factor authentication to reduce the number of ways that a bad actor can intrude on the authentication process. In this instance, it allows us to view and manually copy the tokens created to use further in this process.
!!! tip "Scopes"
It is always good practice to grant your application the lowest tier permissions it needs to do its job. e.g. If your application won't be making posts, use `scope=read` or even a subscope of that.
In this spirit, "read" is used in the example above, which means that the application will be restricted to only being able to do "read" actions.
For a list of available scopes, see [the swagger docs](https://docs.gotosocial.org/en/latest/api/swagger/).
!!! warning
GoToSocial did not support scoped authorization tokens before version 0.19.0, so if you are using a version of GoToSocial below that, then any token you obtain in this process will be able to perform all actions on your behalf, including admin actions if your account has admin permissions.
A successful call returns a response with a `client_id` and `client_secret`, which we are going need to use in the rest of the process. It looks something like this:
```json
{
"id": "01J1CYJ4QRNFZD6WHQMZV7248G",
"name": "your_app_name",
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}
```
!!! tip
Ensure you save the `client_id` and `client_secret` values somewhere so you can refer to them as we go.
## Authorize your application to act on your behalf
We've registered a new application with GoToSocial, but it isn't connected to your account just yet. Now we need to tell GoToSocial that that new application is actually going to act on your behalf. To do this, we need to authenticate with your instance via a browser to initiate the login and permission-granting process.
Create a URL with a query string like so, replacing `YOUR_CLIENT_ID` with the `client_id` you received in the previous step, and paste the URL into your browser:
```text
https://example.org/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=read
```
!!! tip
If you used different scopes to register your application, then replace `scope=read` in the URL above with a plus-separated list of the scopes you registered with. For example, if you registered your application with a `scopes` value of `read write` then you should change `scope=read` in the above URL to `scope=read+write`.
After pasting the URL into your browser, you'll be directed to a login form for your instance which prompts you to enter your email address and password in order to connect the application to your account.
Once you've submitted your credentials, you will arrive on a page that says something like this:
```
Hi `your_username`!
Application `your_app_name` would like to perform actions on your behalf, with scope *`read`*.
The application will redirect to urn:ietf:wg:oauth:2.0:oob to continue.
```
Click `Allow`, and you will get a page that looks something like this:
```text
Here's your out-of-band token with scope "read", use it wisely:
YOUR_AUTHORIZATION_TOKEN
```
Copy the out-of-band authorization token somewhere, as you'll need it in the next step.
## Get an access token
The next step is to exchange the out-of-band authorization token you just received with a reusable access token that can be sent along with all further API requests.
You can do this with another `POST` request that looks like the following:
```bash
curl \
-H 'Content-Type: application/json' \
-d '{
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"grant_type": "authorization_code",
"code": "YOUR_AUTHORIZATION_TOKEN"
}' \
'https://example.org/oauth/token'
```
Make sure to replace:
- `YOUR_CLIENT_ID` with the client ID received in the first step.
- `YOUR_CLIENT_SECRET` with the client secret received in the first step.
- `YOUR_AUTHORIZATION_TOKEN` with the out-of-band authorization token received in the second step.
You'll get a response that includes your access token and looks something like this:
```json
{
"access_token": "YOUR_ACCESS_TOKEN",
"created_at": 1719577950,
"scope": "read",
"token_type": "Bearer"
}
```
Copy and save your access token somewhere safe.
## Verifying
To make sure everything worked, try querying the `/api/v1/verify_credentials` endpoint, adding your access token to the request header as `Authorization: Bearer YOUR_ACCESS_TOKEN`.
See this example:
```bash
curl \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
'https://example.org/api/v1/accounts/verify_credentials'
```
If all goes well, you should get your user profile as a JSON response.
## Final notes
Now that you have an access token, you can reuse that token in every API request for authorization. You do not need to do the entire token exchange dance every time!
For example, you can issue another `GET` request to the API using the same access token to get your notifications, as follows:
```bash
curl \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
'https://example.org/api/v1/notifications'
```

View file

@ -16,7 +16,7 @@ Every response will include the current status of the rate limit with the follow
- `X-Ratelimit-Limit`: maximum number of requests allowed per time period.
- `X-Ratelimit-Remaining`: number of remaining requests that can still be performed within.
- `X-Ratelimit-Reset`: ISO8601 timestamp indicating when the rate limit will reset.
- `X-Ratelimit-Reset`: unix timestamp indicating when the rate limit will reset.
In case the rate limit is exceeded, an [HTTP 429 Too Many Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) error is returned to the caller.
@ -24,18 +24,14 @@ In case the rate limit is exceeded, an [HTTP 429 Too Many Requests](https://deve
### My rate limit keeps being exceeded! Why?
If you find that your rate limit is regularly being exceeded (both for yourself and other callers) during normal use of your instance, it may be that GoToSocial can't tell the clients apart by IP address. You can investigate this by viewing the logs of your instance. If (almost) all logged client IP addresses appear to be the same IP address (something like `172.x.x.x`), then the rate limiting will cause problems.
If you find that your rate limit is regularly being exceeded (both for yourself and other callers) during normal use of your instance, it may be that GoToSocial can't tell the clients apart by IP address. You can investigate this by viewing the logs of your instance. If (almost) all logged IP addresses appear to be the same IP address (something like `172.x.x.x`), then the rate limiting will cause problems.
This happens when your server is running inside NAT (port forwarding), or behind an HTTP proxy without the correct configuration, causing your instance to see all incoming IP addresses as the same address: namely, the IP address of your reverse proxy or gateway. This means that all incoming requests are *sharing the same rate limit*, rather than being split correctly per IP.
If you are using an HTTP proxy then it's likely that your `trusted-proxies` is not correctly configured. See the [trusted-proxies](../configuration/trusted_proxies.md) documentation for more info on how to resolve this.
If you are using an HTTP proxy then it's likely that your `trusted-proxies` is not correctly configured. If this is the case, try adding the IP address of your reverse proxy to the list of `trusted-proxies`, and restarting your instance.
If you don't have an HTTP proxy, then it's likely caused by NAT. In this case you should disable rate limiting altogether.
### Can I configure the rate limit? Can I just turn it off?
Yes! Set `advanced-rate-limit-requests: 0` in the config.
### Can I exclude one or more IP addresses from rate limiting, but leave the rest in place?
Yes! Set `advanced-rate-limit-exceptions` in the config.

View file

@ -1,21 +1,9 @@
# Routes and Methods
# API Documentation
GoToSocial uses [go-swagger](https://github.com/go-swagger/go-swagger) to generate a V2 [OpenAPI specification](https://swagger.io/specification/v2/) document from code annotations.
The resulting API documentation is rendered below. Please note that the doc is intended for reference only. You will not be able to use the built-in Authorize functionality in the below widget to actually connect to an instance or make API calls. Instead, you should use something like curl, Postman, or similar.
The resulting API documentation is rendered below, for quick reference.
Most of the GoToSocial API endpoints require a user-level OAuth token. For a guide on how to authenticate with the API using an OAuth token, see the [authentication doc](./authentication.md).
!!! tip
If you'd like to do more with the spec, you can also view the [swagger.yaml](./swagger.yaml) directly, and then paste it into something like the [Swagger Editor](https://editor.swagger.io/). That way you can try autogenerating GoToSocial API clients in different languages (not supported, but fun), or convert the doc to JSON or OpenAPI v3 specification, etc. See [here](https://swagger.io/tools/open-source/getting-started/) for more.
!!! info "Gotcha: uploading files"
When using an API endpoint that involves submitting a form to upload files (eg., the media attachments endpoints, or the emoji upload endpoint, etc), please note that `filename` is a required on the form field, due to the dependency that GoToSocial uses to parse forms, and some quirks in Go.
See the following issues for more context:
- [#1958](https://codeberg.org/superseriousbusiness/gotosocial/issues/1958)
- [#1944](https://codeberg.org/superseriousbusiness/gotosocial/issues/1944)
- [#2641](https://codeberg.org/superseriousbusiness/gotosocial/issues/2641)
If you'd like to do more with the spec, you can also view the [swagger.yaml](/api/swagger/swagger.yaml) directly, and then paste it into something like the [Swagger Editor](https://editor.swagger.io/) in order to autogenerate GoToSocial API clients in different languages, convert the doc to JSON or OpenAPI v3 specification, etc. See [here](https://swagger.io/tools/open-source/getting-started/) for more.
<swagger-ui src="swagger.yaml"/>

Some files were not shown because too many files have changed in this diff Show more