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 }: {
nixosConfigurations.container = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules =
[
( import ./sharkey-service.nix )
({ pkgs, ... }: {
boot.isContainer = true;
nixosModules.sharkey-service = args : import ./sharkey-service.nix ( args // { sharkey = self.packages."x86_64-linux".sharkey; pkgs = nixpkgs.legacyPackages."x86_64-linux"; } );
# Let 'nixos-version --json' know about the Git revision
# 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".sharkey = nixpkgs.legacyPackages."x86_64-linux".callPackage ./sharkey.nix { };
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 'channel': this.onChannelMessageRequested(body); break;
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
private onBroadcastMessage(data: GlobalEvents['broadcast']['payload']) {
this.sendMessageToWs(data.type, data.body);

View file

@ -3,29 +3,31 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { createApp, defineAsyncComponent, markRaw } from 'vue';
import { common } from './common.js';
import {createApp, defineAsyncComponent, markRaw} from 'vue';
import {common} from './common.js';
import type * as Misskey from 'misskey-js';
import { ui } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
import { alert, confirm, popup, post, toast } from '@/os.js';
import { useStream } from '@/stream.js';
import {ui} from '@@/js/config.js';
import {i18n} from '@/i18n.js';
import {alert, popup, post, toast} from '@/os.js';
import {useStream} from '@/stream.js';
import * as sound from '@/scripts/sound.js';
import { $i, signout, updateAccount } from '@/account.js';
import { instance } from '@/instance.js';
import { ColdDeviceStorage, defaultStore } from '@/store.js';
import { reactionPicker } from '@/scripts/reaction-picker.js';
import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
import { initializeSw } from '@/scripts/initialize-sw.js';
import { deckStore } from '@/ui/deck/deck-store.js';
import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mainRouter } from '@/router/main.js';
import { setFavIconDot } from '@/scripts/favicon-dot.js';
import { type Keymap, makeHotkey } from '@/scripts/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
import {$i, signout, updateAccount} from '@/account.js';
import {instance} from '@/instance.js';
import {ColdDeviceStorage, defaultStore} from '@/store.js';
import {reactionPicker} from '@/scripts/reaction-picker.js';
import {miLocalStorage} from '@/local-storage.js';
import {claimAchievement, claimedAchievements} from '@/scripts/achievements.js';
import {initializeSw} from '@/scripts/initialize-sw.js';
import {deckStore} from '@/ui/deck/deck-store.js';
import {emojiPicker} from '@/scripts/emoji-picker.js';
import {mainRouter} from '@/router/main.js';
import {setFavIconDot} from '@/scripts/favicon-dot.js';
import {type Keymap, makeHotkey} from '@/scripts/hotkey.js';
import {addCustomEmoji, removeCustomEmojis, updateCustomEmojis} from '@/custom-emojis.js';
import MkDialog from "@/components/MkDialog.vue";
export async function mainBoot() {
const { isClientUpdated } = await common(() => createApp(
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')) :
@ -45,20 +47,45 @@ export async function mainBoot() {
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 () => {
// TODO: Where's that setting?
if (defaultStore.state.serverDisconnectedBehavior === 'dialog') {
if (reloadDialogShowing) return;
reloadDialogShowing = true;
const { canceled } = await confirm({
type: 'warning',
title: i18n.ts.disconnectedFromServer,
text: i18n.ts.reloadConfirm,
// If the dialog is already open, do nothing.
if (reloadDialogDisposeFn) return;
// Show a popup dialog with a warning message and a "reload" button.
const {dispose} = popup(MkDialog, {
...({
type: 'warning',
title: i18n.ts.disconnectedFromServer,
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) {
location.reload();
}
// Save the function to close the dialog so that it can be called later.
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 idCounter = 0;
/// A timestamp of the last-received message.
private lastMessageTime = Date.now();
constructor(origin: string, user: { token: string; } | null, options?: {
WebSocket?: WebSocket;
}) {
@ -136,6 +138,9 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
* Callback of when open connection
*/
private onOpen(): void {
this.lastMessageTime = Date.now();
const isReconnect = this.state === 'reconnecting';
this.state = 'connected';
@ -162,6 +167,9 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
* Callback of when received a message from connection
*/
private onMessage(message: { data: string; }): void {
this.lastMessageTime = Date.now();
const { type, body } = JSON.parse(message.data);
if (type === 'channel') {
@ -211,7 +219,8 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
}
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,
pkgs,
lib,
sharkey,
...
}:
@ -186,9 +187,11 @@ in
{
options = {
services.sharkey = {
enable = lib.mkEnableOption "sharkey";
package = lib.mkPackageOption pkgs "sharkey" { };
inherit settings;
database = {
createLocally = lib.mkOption {
@ -340,7 +343,7 @@ in
${pkgs.replace-secret}/bin/replace-secret '@MEILISEARCH_KEY@' "${cfg.meilisearch.keyFile}" /run/sharkey/default.yml
'');
serviceConfig = {
ExecStart = "${cfg.package}/bin/sharkey migrateandstart";
ExecStart = "${sharkey}/bin/sharkey migrateandstart";
RuntimeDirectory = "sharkey";
RuntimeDirectoryMode = "700";
StateDirectory = "sharkey";

View file

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