Compare commits
15 commits
develop
...
flake-stuf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae8a76cef5 | ||
|
|
aecf896494 | ||
|
|
2ead42c00b | ||
|
|
d0cb08c2df | ||
|
|
077fb751b5 | ||
|
|
7ad73fedfb | ||
|
|
88c8478f8c | ||
|
|
ffb2ec60ed | ||
|
|
3481f3fefc | ||
|
|
b854de4538 | ||
|
|
50f154af57 | ||
|
|
75d6ae66aa | ||
|
|
9c74210213 | ||
|
|
03df443aa8 | ||
|
|
73c128dbcb |
7 changed files with 186 additions and 63 deletions
32
flake.nix
32
flake.nix
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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
100
run_with_db.sh
Executable 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
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 = ''
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue