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 }: {
|
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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
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,
|
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";
|
||||||
|
|
|
||||||
|
|
@ -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 = ''
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue