Compare commits

...
Sign in to create a new pull request.

15 commits

Author SHA1 Message Date
Werner Kroneman
ae8a76cef5 Updated pnpm hash 2025-05-10 15:14:30 +03:00
Werner Kroneman
aecf896494 Added last-message-time-tracking 2024-12-08 17:52:36 +01:00
Werner Kroneman
2ead42c00b Added heartbeat handler. 2024-12-08 17:50:34 +01:00
Werner Kroneman
d0cb08c2df Fixed heatbeat message not being valid json; now includes unix time too. 2024-12-08 17:46:49 +01:00
Werner Kroneman
077fb751b5 Added heartbeat response in Connection.ts 2024-12-08 12:50:29 +01:00
Werner Kroneman
7ad73fedfb Added code to dismiss the reload dialogue on reconnect. 2024-12-07 15:33:43 +01:00
Werner Kroneman
88c8478f8c Inlined the confirm. 2024-12-07 14:28:00 +01:00
Werner Kroneman
ffb2ec60ed Inlined the confirm. 2024-12-07 14:25:36 +01:00
Werner Kroneman
3481f3fefc Removed the pointless dev shell. 2024-12-07 12:45:56 +01:00
Werner Kroneman
b854de4538 Cleaned up the startup script 2024-12-07 09:54:41 +01:00
Werner Kroneman
50f154af57 Got sharkey with isolated db running. 2024-12-07 08:57:00 +01:00
Werner Kroneman
75d6ae66aa Set sharkey package as default 2024-12-05 20:17:09 +02:00
Werner Kroneman
9c74210213 Added sharkey-service nixos module 2024-12-05 16:10:39 +02:00
Werner Kroneman
03df443aa8 Added sharkey-service nixos module 2024-12-05 16:02:48 +02:00
Werner Kroneman
73c128dbcb Rewrote service to call the package from the file. 2024-12-05 15:33:01 +02:00
7 changed files with 186 additions and 63 deletions

View file

@ -3,36 +3,10 @@
outputs = { self, nixpkgs }: { outputs = { self, nixpkgs }: {
nixosConfigurations.container = nixpkgs.lib.nixosSystem { nixosModules.sharkey-service = args : import ./sharkey-service.nix ( args // { sharkey = self.packages."x86_64-linux".sharkey; pkgs = nixpkgs.legacyPackages."x86_64-linux"; } );
system = "x86_64-linux";
modules =
[
( import ./sharkey-service.nix )
({ pkgs, ... }: {
boot.isContainer = true;
# Let 'nixos-version --json' know about the Git revision packages."x86_64-linux".sharkey = nixpkgs.legacyPackages."x86_64-linux".callPackage ./sharkey.nix { };
# of this flake.
system.configurationRevision = nixpkgs.lib.mkIf (self ? rev) self.rev;
# Network configuration.
networking.useDHCP = false;
networking.firewall.allowedTCPPorts = [ 3000 ];
system.stateVersion = "24.04";
services.sharkey = {
enable = true;
package = (pkgs.callPackage ./sharkey.nix {});
settings = {
url = "https://sharkey.localhost";
};
redis.createLocally = true;
database.createLocally = true;
};
})
];
};
packages."x86_64-linux".default = self.packages."x86_64-linux".sharkey;
}; };
} }

View file

@ -161,9 +161,19 @@ export default class Connection {
case 'disconnect': this.onChannelDisconnectRequested(body); break; case 'disconnect': this.onChannelDisconnectRequested(body); break;
case 'channel': this.onChannelMessageRequested(body); break; case 'channel': this.onChannelMessageRequested(body); break;
case 'ch': this.onChannelMessageRequested(body); break; // alias case 'ch': this.onChannelMessageRequested(body); break; // alias
case 'hb': this.onHeartbeat(body); break;
} }
} }
@bindThis
private onHeartbeat(data: JsonValue | undefined) {
if (!isJsonObject(data)) {
console.error('Received invalid heartbeat payload: ', data);
return;
}
this.sendMessageToWs('hb', data);
}
@bindThis @bindThis
private onBroadcastMessage(data: GlobalEvents['broadcast']['payload']) { private onBroadcastMessage(data: GlobalEvents['broadcast']['payload']) {
this.sendMessageToWs(data.type, data.body); this.sendMessageToWs(data.type, data.body);

View file

@ -8,7 +8,7 @@ import { common } from './common.js';
import type * as Misskey from 'misskey-js'; import type * as Misskey from 'misskey-js';
import {ui} from '@@/js/config.js'; import {ui} from '@@/js/config.js';
import {i18n} from '@/i18n.js'; import {i18n} from '@/i18n.js';
import { alert, confirm, popup, post, toast } from '@/os.js'; import {alert, popup, post, toast} from '@/os.js';
import {useStream} from '@/stream.js'; import {useStream} from '@/stream.js';
import * as sound from '@/scripts/sound.js'; import * as sound from '@/scripts/sound.js';
import {$i, signout, updateAccount} from '@/account.js'; import {$i, signout, updateAccount} from '@/account.js';
@ -24,8 +24,10 @@ import { mainRouter } from '@/router/main.js';
import {setFavIconDot} from '@/scripts/favicon-dot.js'; import {setFavIconDot} from '@/scripts/favicon-dot.js';
import {type Keymap, makeHotkey} from '@/scripts/hotkey.js'; import {type Keymap, makeHotkey} from '@/scripts/hotkey.js';
import {addCustomEmoji, removeCustomEmojis, updateCustomEmojis} from '@/custom-emojis.js'; import {addCustomEmoji, removeCustomEmojis, updateCustomEmojis} from '@/custom-emojis.js';
import MkDialog from "@/components/MkDialog.vue";
export async function mainBoot() { export async function mainBoot() {
const { isClientUpdated } = await common(() => createApp( const { isClientUpdated } = await common(() => createApp(
new URLSearchParams(window.location.search).has('zen') || (ui === 'deck' && deckStore.state.useSimpleUiForNonRootPages && location.pathname !== '/') ? defineAsyncComponent(() => import('@/ui/zen.vue')) : new URLSearchParams(window.location.search).has('zen') || (ui === 'deck' && deckStore.state.useSimpleUiForNonRootPages && location.pathname !== '/') ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
!$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) :
@ -45,20 +47,45 @@ export async function mainBoot() {
const stream = useStream(); const stream = useStream();
let reloadDialogShowing = false; // A reference to the function to close the "reconnecting" dialog; null if the dialog is not open.
let reloadDialogDisposeFn : Function | null = null;
// When the stream is disconnected, show a dialog to reload the page.
stream.on('_disconnected_', async () => { stream.on('_disconnected_', async () => {
// TODO: Where's that setting?
if (defaultStore.state.serverDisconnectedBehavior === 'dialog') { if (defaultStore.state.serverDisconnectedBehavior === 'dialog') {
if (reloadDialogShowing) return;
reloadDialogShowing = true; // If the dialog is already open, do nothing.
const { canceled } = await confirm({ if (reloadDialogDisposeFn) return;
// Show a popup dialog with a warning message and a "reload" button.
const {dispose} = popup(MkDialog, {
...({
type: 'warning', type: 'warning',
title: i18n.ts.disconnectedFromServer, title: i18n.ts.disconnectedFromServer,
text: i18n.ts.reloadConfirm, text: i18n.ts.reloadConfirm,
}),
showCancelButton: true, // Show a "cancel" button; this dismisses the dialog without reloading the page.
}, {
// If the user clicks one of the buttons, and it's the "positive" button (in this case, the "reload" button),
done: result => {
if (!result) location.reload();
},
// If the dialog is closed, dispose of the dialog.
closed: () => dispose(),
}); });
reloadDialogShowing = false;
if (!canceled) { // Save the function to close the dialog so that it can be called later.
location.reload(); reloadDialogDisposeFn = dispose;
} }
});
// When the stream is (re)connected, close the "reconnecting" dialog if it's open.
stream.on('_connected_', () => {
if (reloadDialogDisposeFn) {
reloadDialogDisposeFn();
reloadDialogDisposeFn = null;
toast("Reconnected to server.");
} }
}); });

View file

@ -50,6 +50,8 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
private nonSharedConnections: NonSharedConnection[] = []; private nonSharedConnections: NonSharedConnection[] = [];
private idCounter = 0; private idCounter = 0;
/// A timestamp of the last-received message.
private lastMessageTime = Date.now();
constructor(origin: string, user: { token: string; } | null, options?: { constructor(origin: string, user: { token: string; } | null, options?: {
WebSocket?: WebSocket; WebSocket?: WebSocket;
}) { }) {
@ -136,6 +138,9 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
* Callback of when open connection * Callback of when open connection
*/ */
private onOpen(): void { private onOpen(): void {
this.lastMessageTime = Date.now();
const isReconnect = this.state === 'reconnecting'; const isReconnect = this.state === 'reconnecting';
this.state = 'connected'; this.state = 'connected';
@ -162,6 +167,9 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
* Callback of when received a message from connection * Callback of when received a message from connection
*/ */
private onMessage(message: { data: string; }): void { private onMessage(message: { data: string; }): void {
this.lastMessageTime = Date.now();
const { type, body } = JSON.parse(message.data); const { type, body } = JSON.parse(message.data);
if (type === 'channel') { if (type === 'channel') {
@ -211,7 +219,8 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
} }
public heartbeat(): void { public heartbeat(): void {
this.stream.send('h'); // Send a heartbeat message, with the current time so the server can echo it.
this.send('hb', { time: Date.now() });
} }
/** /**

100
run_with_db.sh Executable file
View file

@ -0,0 +1,100 @@
# Set up a postgresql database and a redis server for the integration tests.
# Set up a PostgreSQL database for the integration tests.
#
# Preconditions:
# - PostgreSQL must be installed on the system.
# - The `initdb`, `pg_ctl`, and `psql` commands must be available in the PATH.
#
# Postconditions:
# - A temporary PostgreSQL data directory will be created and initialized.
# - PostgreSQL will be started using a Unix socket.
# - A PostgreSQL user and database named "sharkey" will be created.
create_tmp_psql() {
export PGDATA=$(mktemp -d)
# Initialize the PostgreSQL data directory
initdb -D $PGDATA -U postgres
# Start PostgreSQL using Unix sockets
pg_ctl -D $PGDATA -l $PGDATA/logfile start -o "-k $PGDATA --listen-addresses=''"
until pg_isready -h $PGDATA; do sleep 1; done
echo "PostgreSQL started with Unix socket at $PGDATA"
# Create a "sharkey" user and database
psql -h $PGDATA -c "CREATE USER sharkey" -U postgres
psql -h $PGDATA -c "CREATE DATABASE sharkey OWNER sharkey" -U postgres
}
# Function to find a random unused port
# It generates a random port number between 2000 and 65000
# and checks if it is in use. If it is in use, it recursively
# calls itself until an unused port is found.
function random_unused_port {
local port=$(shuf -i 2000-65000 -n 1)
netstat -lat | grep $port > /dev/null
if [[ $? == 1 ]] ; then
echo $port
else
random_unused_port
fi
}
# Function to create a Redis server on a random unused port
# It exports the port number to the REDIS_PORT environment variable
# and starts the Redis server with log level set to warning.
# It waits until the Redis server is ready to accept connections.
create_redis_on_random_port() {
export REDIS_PORT=$(random_unused_port)
redis-server --port $REDIS_PORT --loglevel warning &
until redis-cli -p $REDIS_PORT ping; do sleep 1; done
echo "Redis started on port $REDIS_PORT"
}
pick_sharkey_port() {
export SHARKEY_PORT=$(random_unused_port)
}
# Function to copy the example configuration file and update it with the PostgreSQL and Redis settings.
# Arguments:
# $1: The destination file path (optional). Defaults to `.config/default.yml`.
copy_and_update_config() {
local dest_file=.config/default.yml
cp .config/example.yml $dest_file
yq -i -Y ".db.host = \"$PGDATA\"" $dest_file
yq -i -Y ".redis.port = $REDIS_PORT" $dest_file
yq -i -Y ".port = $SHARKEY_PORT" $dest_file
}
# Function to stop the PostgreSQL server; $PGDATA must be set
stop_psql() {
pg_ctl -D "$PGDATA" stop
}
# Function to stop the Redis server
stop_redis() {
redis-cli -p "$REDIS_PORT" shutdown
}
# Function to stop both PostgreSQL and Redis servers
stop_databases() {
stop_psql
stop_redis
}
# Function to start a fresh instance of the Sharkey application
# It sets up a temporary PostgreSQL database and a Redis server on a random port,
# updates the configuration file, and runs the application.
start_fresh_sharkey() {
create_tmp_psql
create_redis_on_random_port
pick_sharkey_port
trap stop_databases EXIT
copy_and_update_config
pnpm run migrateandstart
}
start_fresh_sharkey
firefox http://localhost:$SHARKEY_PORT --marionette --new-instance

View file

@ -2,6 +2,7 @@
config, config,
pkgs, pkgs,
lib, lib,
sharkey,
... ...
}: }:
@ -186,9 +187,11 @@ in
{ {
options = { options = {
services.sharkey = { services.sharkey = {
enable = lib.mkEnableOption "sharkey"; enable = lib.mkEnableOption "sharkey";
package = lib.mkPackageOption pkgs "sharkey" { };
inherit settings; inherit settings;
database = { database = {
createLocally = lib.mkOption { createLocally = lib.mkOption {
@ -340,7 +343,7 @@ in
${pkgs.replace-secret}/bin/replace-secret '@MEILISEARCH_KEY@' "${cfg.meilisearch.keyFile}" /run/sharkey/default.yml ${pkgs.replace-secret}/bin/replace-secret '@MEILISEARCH_KEY@' "${cfg.meilisearch.keyFile}" /run/sharkey/default.yml
''); '');
serviceConfig = { serviceConfig = {
ExecStart = "${cfg.package}/bin/sharkey migrateandstart"; ExecStart = "${sharkey}/bin/sharkey migrateandstart";
RuntimeDirectory = "sharkey"; RuntimeDirectory = "sharkey";
RuntimeDirectoryMode = "700"; RuntimeDirectoryMode = "700";
StateDirectory = "sharkey"; StateDirectory = "sharkey";

View file

@ -36,7 +36,7 @@ stdenv.mkDerivation (finalAttrs: {
# https://nixos.org/manual/nixpkgs/unstable/#javascript-pnpm # https://nixos.org/manual/nixpkgs/unstable/#javascript-pnpm
pnpmDeps = pnpm.fetchDeps { pnpmDeps = pnpm.fetchDeps {
inherit (finalAttrs) pname version src; inherit (finalAttrs) pname version src;
hash = "sha256-PpXmNBO4pWj8AG0we4DpPhzfx/18rwDZHi86+esFghM="; hash = "sha256-ymHPzoU5/0RL1Z0v5MVDunFCsFU1c6uzKK5wusabZ+E=";
}; };
buildPhase = '' buildPhase = ''