Skip to content
Snippets Groups Projects
firefish-20241205.md 29.4 KiB
Newer Older
dakkar's avatar
dakkar committed
---
title: "Migrating from Firefish v20241205"
weight: 1230
toc: false
---

If any of the steps fails, especially SQL queries, seek help from us
[Via Discord](https://discord.gg/8hF6pMVWja). Do *not* try fixing things
in the DB yourself unless you *really* know what you are doing.

Before you begin, please take a backup of your database. While you're
there, make sure you know how to restore from that backup! Using the
`plain` format for `pg_dump` is probably the simplest way.

It's a good idea to have a separate backup of your list of silenced
instances and of your `user` table.

### Using Docker (v20241205)

This guide was tested on Firefish version 20241205.  If you're using
version 1.0.5-RC, [please see the other guide](../firefish-1.0.5/).

{{< callout context="caution" icon="alert-triangle" >}}
Antennas from this version of Firefish are not compatible with Sharkey,
and you will need to delete them as part of this guide.

Please take note of any important antennas and recreate them when you're finished with the guide.
{{< /callout >}}

Stop / shut down the entire stack: PostgreSQL, Redis / KeyDB /
DragonflyDB, Sonic / ElasticSearch / MeiliSearch, Firefish
itself. Stop all of it.

Edit your docker compose and replace the Firefish image with
`registry.activitypub.software/transfem-org/sharkey:latest`

If you use Sonic or ElasticSearch replace that section of the Docker
Compose with the following, as Sharkey currently only supports
meilisearch:

```yaml
meilisearch:
    restart: always
    image: getmeili/meilisearch:v1.3.4
    environment:
      - MEILI_NO_ANALYTICS=true
      - MEILI_ENV=production
    env_file:
      - .config/meilisearch.env
    networks:
      - calcnet # <-- Use whatever network name is used in the docker compose here
    volumes:
      - ./meili_data:/meili_data # <--- make sure to replace the volume with one that fits your existing docker compose
```

If you use DragonflyDB replace it with Redis or KeyDB or Valkey, as
Sharkey currently does not support DragonflyDB. To do this replace the
section in the docker compose with the following:
dakkar's avatar
dakkar committed

```yaml
redis:
    restart: always
    image: redis:7-alpine
    networks:
      - calcnet # <-- Use whatever network name is used in the docker compose here
    volumes:
      - ./redis:/data # <-- Make sure to replace the volume with the one used in your firefish docker compose
    healthcheck:
      test: "redis-cli ping"
      interval: 5s
      retries: 20
```

You may need to migrate the data from DragonflyDB to the new software,
the [`librdb` command line tool](https://github.com/redis/librdb) may
help with that.

dakkar's avatar
dakkar committed
Backup your Firefish config and replace it with the [default
Sharkey
one](https://activitypub.software/TransFem-org/Sharkey/-/raw/stable/.config/docker_example.yml)

Edit the config inline with your instance settings. Make sure to use
the same `db` & `redis` settings as in your Firefish config.

Now is the time to *backup* your database and Redis volumes!

Firefish's docker-compose uses PostgreSQL version 12. We strongly
recommend upgrading to 15 or 16. You can do this by making a new db
volume, starting the newer PostgreSQL on it, and importing the data
from the backup you just made. Refer to [the PostgreSQL
documentation](https://www.postgresql.org/docs/current/backup-dump.html).

Make sure to update the *mount paths* for volumes of the Sharkey
container from `/firefish/` to `/sharkey/` (so your existing volumes
will show up at the new path, inside the container).

Now start *only* the database with `docker compose up -d db` (instead
of `db`, you may need to use whatever name is set for the service in
your docker compose config), and start a `psql` shell with `docker
exec -it db psql -U firefish -d firefish` (replace `db` as before, and
`-U firefish -d firefish` with the database user and database name,
respectively, if they're different from `firefish`).

You should now be connected to your Firefish database. We need to
massage it into shape so that Sharkey database migrations will
work. The following series of SQL queries / commands should do it, but
please read the comments and pay attention to the results after each
query!

```sql
-- start a transaction, so we won't leave the db in a halfway state if
-- things go wrong
BEGIN;

-- undo Firefish removing "_enum" from enum names
ALTER TYPE "antenna_src" RENAME TO "antenna_src_enum";
ALTER TYPE "drive_file_usage_hint" RENAME TO "drive_file_usage_hint_enum";
ALTER TYPE "muted_note_reason" RENAME TO "muted_note_reason_enum";
ALTER TYPE "note_visibility" RENAME TO "note_visibility_enum";
ALTER TYPE "notification_type" RENAME TO "notification_type_enum";
ALTER TYPE "page_visibility" RENAME TO "page_visibility_enum";
ALTER TYPE "poll_note_visibility" RENAME TO "poll_notevisibility_enum";
ALTER TYPE "relay_status" RENAME TO "relay_status_enum";
ALTER TYPE "user_emoji_mod_perm" RENAME TO "user_emojimodperm_enum";
ALTER TYPE "user_profile_ffvisibility" RENAME TO "user_profile_ffvisibility_enum";
ALTER TYPE "user_profile_muting_notification_types" RENAME TO "user_profile_mutingnotificationtypes_enum";

-- fix meta column names that Firefish changed
ALTER TABLE "meta" RENAME COLUMN "tosUrl" TO "ToSUrl";
ALTER TABLE "meta" RENAME COLUMN "objectStorageUseSsl" TO "objectStorageUseSSL";
ALTER TABLE "meta" RENAME COLUMN "customMotd" TO "customMOTD";

-- we need to add back some indexes that Firefish removed, but that Sharkey
-- expects
CREATE INDEX "IDX_01f4581f114e0ebd2bbb876f0b" ON "note_reaction" ("createdAt");
CREATE INDEX "IDX_0610ebcfcfb4a18441a9bcdab2" ON "poll" ("userId");
CREATE INDEX "IDX_25dfc71b0369b003a4cd434d0b" ON "note" ("attachedFileTypes");
CREATE INDEX "IDX_2710a55f826ee236ea1a62698f" ON "hashtag" ("mentionedUsersCount");
CREATE INDEX "IDX_4c02d38a976c3ae132228c6fce" ON "hashtag" ("mentionedRemoteUsersCount");
CREATE INDEX "IDX_51c063b6a133a9cb87145450f5" ON "note" ("fileIds");
CREATE INDEX "IDX_54ebcb6d27222913b908d56fd8" ON "note" ("mentions");
CREATE INDEX "IDX_7fa20a12319c7f6dc3aed98c0a" ON "poll" ("userHost");
CREATE INDEX "IDX_88937d94d7443d9a99a76fa5c0" ON "note" ("tags");
CREATE INDEX "IDX_b11a5e627c41d4dc3170f1d370" ON "notification" ("createdAt");
CREATE INDEX "IDX_c8dfad3b72196dd1d6b5db168a" ON "drive_file" ("createdAt");
CREATE INDEX "IDX_d57f9030cd3af7f63ffb1c267c" ON "hashtag" ("attachedUsersCount");
CREATE INDEX "IDX_e5848eac4940934e23dbc17581" ON "drive_file" ("uri");
CREATE INDEX "IDX_fa99d777623947a5b05f394cae" ON "user" ("tags");
dakkar's avatar
dakkar committed

-- we also need to add back some columns that Sharkey expects
ALTER TABLE "user_profile" ADD "integrations" JSONB NOT NULL DEFAULT '{}';
ALTER TABLE "meta" ADD "twitterConsumerSecret" VARCHAR(128);
ALTER TABLE "meta" ADD "twitterConsumerKey" VARCHAR(128);
ALTER TABLE "meta" ADD "enableTwitterIntegration" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "meta" ADD "enableGithubIntegration" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "meta" ADD "githubClientId" VARCHAR(128);
ALTER TABLE "meta" ADD "githubClientSecret" VARCHAR(128);
ALTER TABLE "meta" ADD "enableDiscordIntegration" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "meta" ADD "discordClientId" VARCHAR(128);
ALTER TABLE "meta" ADD "discordClientSecret" VARCHAR(128);

-- also an extra table, for the same reasons
CREATE TABLE antenna_note();

-- Misskey used to have a Reversi game, Firefish dropped the tables,
-- now Misskey uses them again
CREATE TABLE "reversi_game" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "startedAt" TIMESTAMP WITH TIME ZONE, "user1Id" character varying(32) NOT NULL, "user2Id" character varying(32) NOT NULL, "user1Accepted" boolean NOT NULL DEFAULT false, "user2Accepted" boolean NOT NULL DEFAULT false, "black" integer, "isStarted" boolean NOT NULL DEFAULT false, "isEnded" boolean NOT NULL DEFAULT false, "winnerId" character varying(32), "surrendered" character varying(32), "logs" jsonb NOT NULL DEFAULT '[]', "map" character varying(64) array NOT NULL, "bw" character varying(32) NOT NULL, "isLlotheo" boolean NOT NULL DEFAULT false, "canPutEverywhere" boolean NOT NULL DEFAULT false, "loopedBoard" boolean NOT NULL DEFAULT false, "form1" jsonb DEFAULT null, "form2" jsonb DEFAULT null, "crc32" character varying(32), CONSTRAINT "PK_76b30eeba71b1193ad7c5311c3f" PRIMARY KEY ("id"));
CREATE INDEX "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt");
CREATE TABLE "reversi_matching" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "parentId" character varying(32) NOT NULL, "childId" character varying(32) NOT NULL, CONSTRAINT "PK_880bd0afbab232f21c8b9d146cf" PRIMARY KEY ("id"));
CREATE INDEX "IDX_b604d92d6c7aec38627f6eaf16" ON "reversi_matching" ("createdAt");
CREATE INDEX "IDX_3b25402709dd9882048c2bbade" ON "reversi_matching" ("parentId");
CREATE INDEX "IDX_e247b23a3c9b45f89ec1299d06" ON "reversi_matching" ("childId");

-- move aside some FireFish columns; Sharkey migrations will
-- re-create them; we don't `DROP` them because we want to keep the data
ALTER TABLE "user" RENAME COLUMN "movedToUri" TO "ff_movedToUri";
ALTER TABLE "user" RENAME COLUMN "alsoKnownAs" TO "ff_alsoKnownAs";
ALTER TABLE "user" RENAME COLUMN "isIndexable" TO "ff_isIndexable";
ALTER TABLE "user" RENAME COLUMN "speakAsCat" TO "ff_speakAsCat";
ALTER TABLE "user_profile" RENAME COLUMN "preventAiLearning" TO "ff_preventAiLearning";
ALTER TABLE "meta" RENAME COLUMN "silencedHosts" TO "ff_silencedHosts";

-- this column was added by both Firefish and Misskey, but with
-- different names, let's fix it
ALTER TABLE "meta" RENAME COLUMN "ToSUrl" TO "termsOfServiceUrl";

-- update antenna types, this is only needed on some instances but
-- recommend to run anyway
--
-- this *removes* any antennas of types not supported by Sharkey!
CREATE TYPE public.new_antenna_src_enum AS ENUM ('home', 'all', 'list');
ALTER TABLE antenna ADD COLUMN new_src public.new_antenna_src_enum;
DELETE FROM antenna WHERE src NOT IN ('home', 'all', 'list');
ALTER TABLE antenna DROP COLUMN src;
ALTER TABLE antenna RENAME COLUMN new_src TO src;
DROP TYPE public.antenna_src_enum;
ALTER TYPE new_antenna_src_enum RENAME TO antenna_src_enum;

-- optional but recommended: delete all empty moderation log entries
DELETE FROM moderation_log WHERE info = '{}';

-- only needed on some instances, run this if
-- `\dT+ user_profile_mutingnotificationtypes_enum`
-- does not show `note` in the "elements" section
ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum" ADD VALUE 'note';
```

If everything worked and you saw no errors, you can run `COMMIT;` in
that same `psql` shell, to commit all the changes, then close that
shell. Again, if anything went wrong, come talk to us on
[Discord](https://discord.gg/8hF6pMVWja)!

Start Sharkey, and let it run all its migrations. Once that's done,
and Sharkey says it's listening, stop Sharkey but keep the database
running.

Open another `psql` shell like before (`docker exec -it db psql -U
firefish -d firefish`, replacing things as before). We need another
small pass of massaging.

```sql
BEGIN;

-- all existing users are approved, because Firefish doesn't have a
-- concept of approvals
UPDATE "user" SET approved = true;

-- now we put back the data we moved aside
UPDATE "user" SET "movedToUri" = "ff_movedToUri" WHERE "ff_movedToUri" IS NOT NULL;
UPDATE "user" SET "alsoKnownAs" = "ff_alsoKnownAs" WHERE "ff_alsoKnownAs" IS NOT NULL;
UPDATE "user" SET "noindex" = NOT (COALESCE("ff_isIndexable", true));
UPDATE "user" SET "speakAsCat" = COALESCE("ff_speakAsCat", false);
UPDATE "user_profile" SET "preventAiLearning" = COALESCE("ff_preventAiLearning", true);
UPDATE "meta" SET "silencedHosts" = COALESCE("ff_silencedHosts",'{}');

ALTER TABLE "user" DROP COLUMN "ff_movedToUri";
ALTER TABLE "user" DROP COLUMN "ff_alsoKnownAs";
ALTER TABLE "user" DROP COLUMN "ff_isIndexable";
ALTER TABLE "user" DROP COLUMN "ff_speakAsCat";
ALTER TABLE "user_profile" DROP COLUMN "ff_preventAiLearning";
ALTER TABLE "meta" DROP COLUMN "ff_silencedHosts";

-- next, add NSFW detection settings that Firefish removed
-- Sharkey doesn't use these right now, but it expects them to be here
CREATE TYPE "public"."meta_sensitivemediadetectionsensitivity_enum" AS ENUM('medium', 'low', 'high', 'veryLow', 'veryHigh');
CREATE TYPE "public"."meta_sensitivemediadetection_enum" AS ENUM('none', 'all', 'local', 'remote');

ALTER TABLE "user_profile" ADD "autoSensitive" BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE "meta" ADD "enableSensitiveMediaDetectionForVideos" BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE "meta" ADD "setSensitiveFlagAutomatically" BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE "meta" ADD "sensitiveMediaDetectionSensitivity" "public"."meta_sensitivemediadetectionsensitivity_enum" NOT NULL DEFAULT 'medium';
ALTER TABLE "meta" ADD "sensitiveMediaDetection" "public"."meta_sensitivemediadetection_enum" NOT NULL DEFAULT 'none';
ALTER TABLE "drive_file" ADD "maybePorn" BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE "drive_file" ADD "maybeSensitive" BOOLEAN NOT NULL DEFAULT FALSE;

COMMENT ON COLUMN "drive_file"."maybeSensitive" IS 'Whether the DriveFile is NSFW. (predict)';

-- recreate user profile columns that Firefish removed
ALTER TABLE "user_profile" ADD "room" JSONB NOT NULL DEFAULT '{}';
ALTER TABLE "user_profile" ADD "clientData" JSONB NOT NULL DEFAULT '{}';

COMMENT ON COLUMN "user_profile"."room" IS 'The room data of the User.';
COMMENT ON COLUMN "user_profile"."clientData" IS 'The client-specific data of the User.';

-- update incorrect user profile column types
ALTER TABLE user_profile ALTER "mutedWords" DROP DEFAULT;
ALTER TABLE user_profile ALTER "mutedWords" TYPE JSONB USING array_to_json("mutedWords")::JSONB;
ALTER TABLE user_profile ALTER "mutedWords" SET DEFAULT '[]'::JSONB;

ALTER TABLE user_profile ALTER "mutedInstances" DROP DEFAULT;
ALTER TABLE user_profile ALTER "mutedInstances" TYPE JSONB USING array_to_json("mutedInstances")::JSONB;
ALTER TABLE user_profile ALTER "mutedInstances" SET DEFAULT '[]'::JSONB;

-- clear the antenna table
-- antennas from this version of Firefish aren't compatible with Sharkey
TRUNCATE TABLE "antenna";

-- update incorrect antenna column types
ALTER TABLE antenna ALTER "keywords" DROP DEFAULT;
ALTER TABLE antenna ALTER "keywords" TYPE JSONB USING array_to_json("keywords")::jsonb;
ALTER TABLE antenna ALTER "keywords" SET DEFAULT '[]'::jsonb;

-- bring back chart columns removed by Firefish
CREATE TABLE public.__chart__ap_request (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "___deliverFailed" INTEGER DEFAULT 0 NOT NULL, "___deliverSucceeded" INTEGER DEFAULT 0 NOT NULL, "___inboxReceived" INTEGER DEFAULT 0 NOT NULL);
CREATE TABLE public.__chart__drive (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "___local_incCount" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___local_incSize" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___local_decCount" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___local_decSize" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___remote_incCount" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___remote_incSize" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___remote_decCount" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___remote_decSize" INTEGER DEFAULT '0'::BIGINT NOT NULL);
CREATE TABLE public.__chart__federation (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "unique_temp___deliveredInstances" CHARACTER VARYING[] DEFAULT '{}'::CHARACTER VARYING[] NOT NULL, "___deliveredInstances" SMALLINT DEFAULT '0'::SMALLINT NOT NULL, "unique_temp___inboxInstances" CHARACTER VARYING[] DEFAULT '{}'::CHARACTER VARYING[] NOT NULL, "___inboxInstances" SMALLINT DEFAULT '0'::SMALLINT NOT NULL, unique_temp___stalled CHARACTER VARYING[] DEFAULT '{}'::CHARACTER VARYING[] NOT NULL, ___stalled SMALLINT DEFAULT '0'::SMALLINT NOT NULL, ___sub SMALLINT DEFAULT '0'::SMALLINT NOT NULL, ___pub SMALLINT DEFAULT '0'::SMALLINT NOT NULL, ___pubsub SMALLINT DEFAULT '0'::SMALLINT NOT NULL, "___subActive" SMALLINT DEFAULT '0'::SMALLINT NOT NULL, "___pubActive" SMALLINT DEFAULT '0'::SMALLINT NOT NULL);
CREATE TABLE public.__chart__hashtag (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, ___local_users INTEGER DEFAULT 0 NOT NULL, ___remote_users INTEGER DEFAULT 0 NOT NULL, unique_temp___local_users CHARACTER VARYING[] DEFAULT '{}'::CHARACTER VARYING[] NOT NULL, unique_temp___remote_users CHARACTER VARYING[] DEFAULT '{}'::CHARACTER VARYING[] NOT NULL);
CREATE TABLE public.__chart__instance (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, ___requests_failed SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___requests_succeeded SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___requests_received SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___notes_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___notes_inc INTEGER DEFAULT '0'::BIGINT NOT NULL, ___notes_dec INTEGER DEFAULT '0'::BIGINT NOT NULL, ___notes_diffs_normal INTEGER DEFAULT '0'::BIGINT NOT NULL, ___notes_diffs_reply INTEGER DEFAULT '0'::BIGINT NOT NULL, ___notes_diffs_renote INTEGER DEFAULT '0'::BIGINT NOT NULL, ___users_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___users_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___users_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___following_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___following_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___following_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___followers_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___followers_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___followers_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, "___drive_totalFiles" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___drive_incFiles" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___drive_incUsage" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___drive_decFiles" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___drive_decUsage" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___notes_diffs_withFile" INTEGER DEFAULT 0 NOT NULL);
CREATE TABLE public.__chart__network (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "___incomingRequests" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___outgoingRequests" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___totalTime" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___incomingBytes" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___outgoingBytes" INTEGER DEFAULT '0'::BIGINT NOT NULL);
CREATE TABLE public.__chart__notes (id SERIAL NOT NULL, DATE INTEGER NOT NULL, ___local_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_inc INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_dec INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_diffs_normal INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_diffs_reply INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_diffs_renote INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_inc INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_dec INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_diffs_normal INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_diffs_reply INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_diffs_renote INTEGER DEFAULT '0'::BIGINT NOT NULL, "___local_diffs_withFile" INTEGER DEFAULT 0 NOT NULL, "___remote_diffs_withFile" INTEGER DEFAULT 0 NOT NULL);
CREATE TABLE public.__chart__per_user_drive (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, "___totalCount" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___totalSize" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___incCount" SMALLINT DEFAULT '0'::BIGINT NOT NULL, "___incSize" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___decCount" SMALLINT DEFAULT '0'::BIGINT NOT NULL, "___decSize" INTEGER DEFAULT '0'::BIGINT NOT NULL);
CREATE TABLE public.__chart__per_user_following (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, ___local_followings_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_followings_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___local_followings_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___local_followers_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_followers_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___local_followers_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_followings_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_followings_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_followings_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_followers_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_followers_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_followers_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL);
CREATE TABLE public.__chart__per_user_notes (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, ___total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___DEC SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___diffs_normal SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___diffs_reply SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___diffs_renote SMALLINT DEFAULT '0'::BIGINT NOT NULL, "___diffs_withFile" SMALLINT DEFAULT '0'::SMALLINT NOT NULL);
CREATE TABLE public.__chart__per_user_reaction (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, ___local_count SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_count SMALLINT DEFAULT '0'::BIGINT NOT NULL);
CREATE TABLE public.__chart__test (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128), ___foo_total BIGINT NOT NULL, ___foo_inc BIGINT NOT NULL, ___foo_dec BIGINT NOT NULL);
CREATE TABLE public.__chart__test_grouped (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128), ___foo_total BIGINT NOT NULL, ___foo_inc BIGINT NOT NULL, ___foo_dec BIGINT NOT NULL);
CREATE TABLE public.__chart__test_unique (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128), ___foo CHARACTER VARYING[] DEFAULT '{}'::CHARACTER VARYING[] NOT NULL);
CREATE TABLE public.__chart__users (id SERIAL NOT NULL, DATE INTEGER NOT NULL, ___local_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___local_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL);
CREATE TABLE public.__chart_day__ap_request (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "___deliverFailed" INTEGER DEFAULT 0 NOT NULL, "___deliverSucceeded" INTEGER DEFAULT 0 NOT NULL, "___inboxReceived" INTEGER DEFAULT 0 NOT NULL);
CREATE TABLE public.__chart_day__drive (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "___local_incCount" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___local_incSize" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___local_decCount" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___local_decSize" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___remote_incCount" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___remote_incSize" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___remote_decCount" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___remote_decSize" INTEGER DEFAULT '0'::BIGINT NOT NULL);
CREATE TABLE public.__chart_day__federation (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "unique_temp___deliveredInstances" CHARACTER VARYING[] DEFAULT '{}'::CHARACTER VARYING[] NOT NULL, "___deliveredInstances" SMALLINT DEFAULT '0'::SMALLINT NOT NULL, "unique_temp___inboxInstances" CHARACTER VARYING[] DEFAULT '{}'::CHARACTER VARYING[] NOT NULL, "___inboxInstances" SMALLINT DEFAULT '0'::SMALLINT NOT NULL, unique_temp___stalled CHARACTER VARYING[] DEFAULT '{}'::CHARACTER VARYING[] NOT NULL, ___stalled SMALLINT DEFAULT '0'::SMALLINT NOT NULL, ___sub SMALLINT DEFAULT '0'::SMALLINT NOT NULL, ___pub SMALLINT DEFAULT '0'::SMALLINT NOT NULL, ___pubsub SMALLINT DEFAULT '0'::SMALLINT NOT NULL, "___subActive" SMALLINT DEFAULT '0'::SMALLINT NOT NULL, "___pubActive" SMALLINT DEFAULT '0'::SMALLINT NOT NULL);
CREATE TABLE public.__chart_day__hashtag (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, ___local_users INTEGER DEFAULT 0 NOT NULL, ___remote_users INTEGER DEFAULT 0 NOT NULL, unique_temp___local_users CHARACTER VARYING[] DEFAULT '{}'::CHARACTER VARYING[] NOT NULL, unique_temp___remote_users CHARACTER VARYING[] DEFAULT '{}'::CHARACTER VARYING[] NOT NULL);
CREATE TABLE public.__chart_day__instance (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, ___requests_failed SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___requests_succeeded SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___requests_received SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___notes_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___notes_inc INTEGER DEFAULT '0'::BIGINT NOT NULL, ___notes_dec INTEGER DEFAULT '0'::BIGINT NOT NULL, ___notes_diffs_normal INTEGER DEFAULT '0'::BIGINT NOT NULL, ___notes_diffs_reply INTEGER DEFAULT '0'::BIGINT NOT NULL, ___notes_diffs_renote INTEGER DEFAULT '0'::BIGINT NOT NULL, ___users_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___users_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___users_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___following_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___following_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___following_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___followers_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___followers_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___followers_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, "___drive_totalFiles" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___drive_incFiles" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___drive_incUsage" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___drive_decFiles" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___drive_decUsage" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___notes_diffs_withFile" INTEGER DEFAULT 0 NOT NULL);
CREATE TABLE public.__chart_day__network (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "___incomingRequests" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___outgoingRequests" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___totalTime" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___incomingBytes" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___outgoingBytes" INTEGER DEFAULT '0'::BIGINT NOT NULL);
CREATE TABLE public.__chart_day__notes (id SERIAL NOT NULL, DATE INTEGER NOT NULL, ___local_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_inc INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_dec INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_diffs_normal INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_diffs_reply INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_diffs_renote INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_inc INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_dec INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_diffs_normal INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_diffs_reply INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_diffs_renote INTEGER DEFAULT '0'::BIGINT NOT NULL, "___local_diffs_withFile" INTEGER DEFAULT 0 NOT NULL, "___remote_diffs_withFile" INTEGER DEFAULT 0 NOT NULL);
CREATE TABLE public.__chart_day__per_user_drive (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, "___totalCount" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___totalSize" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___incCount" SMALLINT DEFAULT '0'::BIGINT NOT NULL, "___incSize" INTEGER DEFAULT '0'::BIGINT NOT NULL, "___decCount" SMALLINT DEFAULT '0'::BIGINT NOT NULL, "___decSize" INTEGER DEFAULT '0'::BIGINT NOT NULL);
CREATE TABLE public.__chart_day__per_user_following (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, ___local_followings_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_followings_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___local_followings_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___local_followers_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_followers_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___local_followers_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_followings_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_followings_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_followings_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_followers_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_followers_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_followers_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL);
CREATE TABLE public.__chart_day__per_user_notes (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, ___total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___DEC SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___diffs_normal SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___diffs_reply SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___diffs_renote SMALLINT DEFAULT '0'::BIGINT NOT NULL, "___diffs_withFile" SMALLINT DEFAULT '0'::SMALLINT NOT NULL);
CREATE TABLE public.__chart_day__per_user_reaction (id SERIAL NOT NULL, DATE INTEGER NOT NULL, "GROUP" CHARACTER VARYING(128) NOT NULL, ___local_count SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_count SMALLINT DEFAULT '0'::BIGINT NOT NULL);
CREATE TABLE public.__chart_day__users (id SERIAL NOT NULL, DATE INTEGER NOT NULL, ___local_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___local_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___local_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_total INTEGER DEFAULT '0'::BIGINT NOT NULL, ___remote_inc SMALLINT DEFAULT '0'::BIGINT NOT NULL, ___remote_dec SMALLINT DEFAULT '0'::BIGINT NOT NULL);
```

If everything worked and you saw no errors, you can run `COMMIT;` in
that same `psql` shell, to commit all the changes, then close that
shell. Again, if anything went wrong, come talk to us on
[Discord](https://discord.gg/8hF6pMVWja)!

Start everything up again, you should see no errors in the logs.

Log in as an administrator, and go to the control panel.
If you use an object store such as S3, double-check your settings
(it's possible, for example, that the URL now looks like
`https://https://yourdomain.com`, fix it). If you want your users to
be able to search notes, you must enable it via the "roles" system.

Congratulations, you're now running Sharkey!