Compare commits
15 commits
feature/hi
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
667b324572 | ||
|
|
ab9969283b | ||
|
|
7dc90e078e | ||
|
|
69ffa0e520 | ||
|
|
b48ae3e9fa | ||
|
|
b4b72ab2df | ||
|
|
3d3cf5bd7a | ||
|
|
e6e48fb6bc | ||
|
|
51afbbaf72 | ||
|
|
f0139ae1e0 | ||
|
|
97d17c537b | ||
|
|
82376f312d | ||
|
|
fd2af6dfe6 | ||
|
|
387dc4bb4b | ||
|
|
0efd5eff2b |
24 changed files with 796 additions and 52 deletions
|
|
@ -229,3 +229,8 @@ checkActivityPubGetSignature: false
|
|||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
# CHMod-style permission bits to apply to uploaded files.
|
||||
# Permission bits are specified as a base-8 string representing User/Group/Other permissions.
|
||||
# This setting is only useful for custom deployments, such as using a reverse proxy to serve media.
|
||||
#filePermissionBits: '644'
|
||||
|
|
|
|||
|
|
@ -222,3 +222,8 @@ allowedPrivateNetworks: [
|
|||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
# CHMod-style permission bits to apply to uploaded files.
|
||||
# Permission bits are specified as a base-8 string representing User/Group/Other permissions.
|
||||
# This setting is only useful for custom deployments, such as using a reverse proxy to serve media.
|
||||
#filePermissionBits: '644'
|
||||
|
|
|
|||
|
|
@ -312,3 +312,8 @@ checkActivityPubGetSignature: false
|
|||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
# CHMod-style permission bits to apply to uploaded files.
|
||||
# Permission bits are specified as a base-8 string representing User/Group/Other permissions.
|
||||
# This setting is only useful for custom deployments, such as using a reverse proxy to serve media.
|
||||
#filePermissionBits: '644'
|
||||
|
|
|
|||
|
|
@ -334,3 +334,8 @@ checkActivityPubGetSignature: false
|
|||
|
||||
# PID File of master process
|
||||
#pidFile: /tmp/misskey.pid
|
||||
|
||||
# CHMod-style permission bits to apply to uploaded files.
|
||||
# Permission bits are specified as a base-8 string representing User/Group/Other permissions.
|
||||
# This setting is only useful for custom deployments, such as using a reverse proxy to serve media.
|
||||
#filePermissionBits: '644'
|
||||
|
|
|
|||
|
|
@ -3,27 +3,33 @@
|
|||
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/SECURITY.md)
|
||||
🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md) -->
|
||||
|
||||
**What happened?** _(Please give us a brief description of what happened.)_
|
||||
# **What happened?**
|
||||
<!-- Please give us a brief description of what happened. -->
|
||||
|
||||
**What did you expect to happen?** _(Please give us a brief description of what you expected to happen.)_
|
||||
# **What did you expect to happen?**
|
||||
<!-- Please give us a brief description of what you expected to happen. -->
|
||||
|
||||
**Version** _(What version of Sharkey is your instance running? You can find this by clicking your instance's logo at the top left and then clicking instance information.)_
|
||||
# **Version**
|
||||
<!-- What version of Sharkey is your instance running? You can find this by clicking your instance's logo at the top left and then clicking instance information. -->
|
||||
|
||||
**Instance** _(What instance of Sharkey are you using?)_
|
||||
# **Instance**
|
||||
<!-- What instance of Sharkey are you using? -->
|
||||
|
||||
**What type of issue is this?** _(If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side.)_
|
||||
# **What type of issue is this?**
|
||||
<!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
|
||||
|
||||
**What browser are you using? (Client-side issues only)**
|
||||
# **What browser are you using? (Client-side issues only)**
|
||||
|
||||
**What operating system are you using? (Client-side issues only)**
|
||||
# **What operating system are you using? (Client-side issues only)**
|
||||
|
||||
**How do you deploy Sharkey on your server? (Server-side issues only)**
|
||||
# **How do you deploy Sharkey on your server? (Server-side issues only)**
|
||||
|
||||
**What operating system are you using? (Server-side issues only)**
|
||||
# **What operating system are you using? (Server-side issues only)**
|
||||
|
||||
**Relevant log output** _(Please copy and paste any relevant log output. You can find your log by inspecting the page, and going to the "console" tab. This will be automatically formatted into code, so no need for backticks.)_
|
||||
# **Relevant log output**
|
||||
<!-- Please copy and paste any relevant log output. You can find your log by inspecting the page, and going to the "console" tab. This will be automatically formatted into code, so no need for backticks. -->
|
||||
|
||||
**Contribution Guidelines**
|
||||
# **Contribution Guidelines**
|
||||
By submitting this issue, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md)
|
||||
- [ ] I agree to follow this project's Contribution Guidelines
|
||||
- [ ] I have searched the issue tracker for similar issues, and this is not a duplicate.
|
||||
|
|
|
|||
|
|
@ -3,15 +3,19 @@
|
|||
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/SECURITY.md)
|
||||
🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md) -->
|
||||
|
||||
**What feature would you like implemented?** _(Please give us a brief description of what you'd like.)_
|
||||
# **What feature would you like implemented?**
|
||||
<!-- Please give us a brief description of what you'd like. -->
|
||||
|
||||
**Why should we add this feature?** _(Please give us a brief description of why your feature is important.)_
|
||||
# **Why should we add this feature?**
|
||||
<!-- Please give us a brief description of why your feature is important. -->
|
||||
|
||||
**Version** _(What version of Sharkey is your instance running? You can find this by clicking your instance's logo at the top left and then clicking instance information.)_
|
||||
# **Version**
|
||||
<!-- What version of Sharkey is your instance running? You can find this by clicking your instance's logo at the top left and then clicking instance information. -->
|
||||
|
||||
**Instance** _(What instance of Sharkey are you using?)_
|
||||
# **Instance**
|
||||
<!-- What instance of Sharkey are you using? -->
|
||||
|
||||
**Contribution Guidelines**
|
||||
# **Contribution Guidelines**
|
||||
By submitting this issue, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md)
|
||||
- [ ] I agree to follow this project's Contribution Guidelines
|
||||
- [ ] I have searched the issue tracker for similar requests, and this is not a duplicate.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
<!-- Thanks for taking the time to make Sharkey better! -->
|
||||
|
||||
**What does this PR do?** _(Please give us a brief description of what this PR does.)_
|
||||
# **What does this MR do?**
|
||||
<!-- Please give us a brief description of what this PR does. -->
|
||||
|
||||
**Contribution Guidelines**
|
||||
# **Contribution Guidelines**
|
||||
By submitting this merge request, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md)
|
||||
- [ ] I agree to follow this project's Contribution Guidelines
|
||||
- [ ] I have made sure to test this pull request
|
||||
- [ ] I have made sure to test this merge request
|
||||
|
||||
<!-- Uncomment if your merge request has multiple authors -->
|
||||
<!-- Co-authored-by: Name <email@email.com> -->
|
||||
|
|
|
|||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1733212471,
|
||||
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
38
flake.nix
Normal file
38
flake.nix
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs }: {
|
||||
|
||||
nixosConfigurations.container = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules =
|
||||
[
|
||||
( import ./sharkey-service.nix )
|
||||
({ pkgs, ... }: {
|
||||
boot.isContainer = true;
|
||||
|
||||
# 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;
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
|
|
@ -93,6 +93,7 @@
|
|||
"@swc/core": "1.6.6",
|
||||
"@transfem-org/sfm-js": "0.24.5",
|
||||
"@twemoji/parser": "15.1.1",
|
||||
"@types/psl": "^1.1.3",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.17.1",
|
||||
"archiver": "7.0.1",
|
||||
|
|
@ -135,9 +136,9 @@
|
|||
"json5": "2.2.3",
|
||||
"jsonld": "8.3.2",
|
||||
"jsrsasign": "11.1.0",
|
||||
"juice": "11.0.0",
|
||||
"megalodon": "workspace:*",
|
||||
"meilisearch": "0.42.0",
|
||||
"juice": "11.0.0",
|
||||
"microformats-parser": "2.0.2",
|
||||
"mime-types": "2.1.35",
|
||||
"misskey-js": "workspace:*",
|
||||
|
|
@ -158,6 +159,7 @@
|
|||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
"proxy-addr": "^2.0.7",
|
||||
"psl": "^1.13.0",
|
||||
"pug": "3.0.3",
|
||||
"punycode": "2.3.1",
|
||||
"qrcode": "1.5.4",
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ type Source = {
|
|||
};
|
||||
|
||||
pidFile: string;
|
||||
filePermissionBits?: string;
|
||||
};
|
||||
|
||||
export type Config = {
|
||||
|
|
@ -212,6 +213,7 @@ export type Config = {
|
|||
} | undefined;
|
||||
|
||||
pidFile: string;
|
||||
filePermissionBits?: string;
|
||||
};
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
|
|
@ -347,6 +349,7 @@ export function loadConfig(): Config {
|
|||
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
|
||||
import: config.import,
|
||||
pidFile: config.pidFile,
|
||||
filePermissionBits: config.filePermissionBits,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -452,7 +455,10 @@ function applyEnvOverrides(config: Source) {
|
|||
}
|
||||
}
|
||||
|
||||
const alwaysStrings = { 'chmodSocket': true } as { [key: string]: boolean };
|
||||
const alwaysStrings: { [key in string]?: boolean } = {
|
||||
'chmodSocket': true,
|
||||
'filePermissionBits': true,
|
||||
};
|
||||
|
||||
function _assign(path: (string | number)[], lastStep: string | number, value: string) {
|
||||
let thisConfig = config as any;
|
||||
|
|
@ -490,7 +496,7 @@ function applyEnvOverrides(config: Source) {
|
|||
_apply_top(['sentryForBackend', 'enableNodeProfiling']);
|
||||
_apply_top([['clusterLimit', 'deliverJobConcurrency', 'inboxJobConcurrency', 'relashionshipJobConcurrency', 'deliverJobPerSec', 'inboxJobPerSec', 'relashionshipJobPerSec', 'deliverJobMaxAttempts', 'inboxJobMaxAttempts']]);
|
||||
_apply_top([['outgoingAddress', 'outgoingAddressFamily', 'proxy', 'proxySmtp', 'mediaProxy', 'proxyRemoteFiles', 'videoThumbnailGenerator']]);
|
||||
_apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'pidFile']]);
|
||||
_apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'pidFile', 'filePermissionBits']]);
|
||||
_apply_top(['import', ['downloadTimeout', 'maxFileSize']]);
|
||||
_apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature']]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import { copyFile, mkdir, unlink, writeFile } from 'node:fs/promises';
|
||||
import { copyFile, unlink, writeFile, chmod } from 'node:fs/promises';
|
||||
import * as Path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
|
|
@ -41,12 +41,20 @@ export class InternalStorageService {
|
|||
@bindThis
|
||||
public async saveFromPath(key: string, srcPath: string): Promise<string> {
|
||||
await copyFile(srcPath, this.resolvePath(key));
|
||||
return `${this.config.url}/files/${key}`;
|
||||
return await this.finalizeSavedFile(key);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async saveFromBuffer(key: string, data: Buffer): Promise<string> {
|
||||
await writeFile(this.resolvePath(key), data);
|
||||
return await this.finalizeSavedFile(key);
|
||||
}
|
||||
|
||||
private async finalizeSavedFile(key: string): Promise<string> {
|
||||
if (this.config.filePermissionBits) {
|
||||
const path = this.resolvePath(key);
|
||||
await chmod(path, this.config.filePermissionBits);
|
||||
}
|
||||
return `${this.config.url}/files/${key}`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
*/
|
||||
|
||||
import { URL } from 'node:url';
|
||||
import { toASCII } from 'punycode';
|
||||
import punycode from 'punycode/punycode.js';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import RE2 from 're2';
|
||||
import psl from 'psl';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
|
@ -106,13 +107,13 @@ export class UtilityService {
|
|||
|
||||
@bindThis
|
||||
public toPuny(host: string): string {
|
||||
return toASCII(host.toLowerCase());
|
||||
return punycode.toASCII(host.toLowerCase());
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public toPunyNullable(host: string | null | undefined): string | null {
|
||||
if (host == null) return null;
|
||||
return toASCII(host.toLowerCase());
|
||||
return punycode.toASCII(host.toLowerCase());
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
@ -122,6 +123,26 @@ export class UtilityService {
|
|||
return host;
|
||||
}
|
||||
|
||||
private specialSuffix(hostname: string): string | null {
|
||||
// masto.host provides domain names for its clients, we have to
|
||||
// treat it as if it were a public suffix
|
||||
const mastoHost = hostname.match(/\.?([a-zA-Z0-9-]+\.masto\.host)$/i);
|
||||
if (mastoHost) {
|
||||
return mastoHost[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public punyHostPSLDomain(url: string): string {
|
||||
const urlObj = new URL(url);
|
||||
const hostname = urlObj.hostname;
|
||||
const domain = this.specialSuffix(hostname) ?? psl.get(hostname) ?? hostname;
|
||||
const host = `${this.toPuny(domain)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`;
|
||||
return host;
|
||||
}
|
||||
|
||||
public isFederationAllowedHost(host: string): boolean {
|
||||
if (this.meta.federation === 'none') return false;
|
||||
if (this.meta.federation === 'specified' && !this.meta.federationHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`))) return false;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import { AbuseReportService } from '@/core/AbuseReportService.js';
|
|||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
import { fromTuple } from '@/misc/from-tuple.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { getApHrefNullable, getApId, getApIds, getApType, getNullableApId, isAccept, isActor, isAdd, isAnnounce, isApObject, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
||||
import { getApHrefNullable, getApId, getApIds, getApType, getNullableApId, isAccept, isActor, isAdd, isAnnounce, isApObject, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isDislike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
||||
import { ApNoteService } from './models/ApNoteService.js';
|
||||
import { ApLoggerService } from './ApLoggerService.js';
|
||||
import { ApDbResolverService } from './ApDbResolverService.js';
|
||||
|
|
@ -41,7 +41,7 @@ import { ApAudienceService } from './ApAudienceService.js';
|
|||
import { ApPersonService } from './models/ApPersonService.js';
|
||||
import { ApQuestionService } from './models/ApQuestionService.js';
|
||||
import type { Resolver } from './ApResolverService.js';
|
||||
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
|
||||
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IDislike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApInboxService {
|
||||
|
|
@ -166,6 +166,8 @@ export class ApInboxService {
|
|||
return await this.announce(actor, activity, resolver);
|
||||
} else if (isLike(activity)) {
|
||||
return await this.like(actor, activity, resolver);
|
||||
} else if (isDislike(activity)) {
|
||||
return await this.dislike(actor, activity);
|
||||
} else if (isUndo(activity)) {
|
||||
return await this.undo(actor, activity, resolver);
|
||||
} else if (isBlock(activity)) {
|
||||
|
|
@ -220,6 +222,11 @@ export class ApInboxService {
|
|||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async dislike(actor: MiRemoteUser, dislike: IDislike): Promise<string> {
|
||||
return await this.undoLike(actor, dislike);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async accept(actor: MiRemoteUser, activity: IAccept, resolver?: Resolver): Promise<string> {
|
||||
const uri = activity.id ?? activity;
|
||||
|
|
@ -782,7 +789,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async undoLike(actor: MiRemoteUser, activity: ILike): Promise<string> {
|
||||
private async undoLike(actor: MiRemoteUser, activity: ILike | IDislike): Promise<string> {
|
||||
const targetUri = getApId(activity.object);
|
||||
|
||||
const note = await this.apNoteService.fetchNote(targetUri);
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ export class ApRequestService {
|
|||
if (alternate) {
|
||||
const href = alternate.getAttribute('href');
|
||||
if (href) {
|
||||
if (this.utilityService.punyHost(url) === this.utilityService.punyHost(href)) {
|
||||
if (this.utilityService.punyHostPSLDomain(url) === this.utilityService.punyHostPSLDomain(href)) {
|
||||
return await this.signedGet(href, user, false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ export class Resolver {
|
|||
throw new UnrecoverableError(`invalid AP object ${value}: missing id`);
|
||||
}
|
||||
|
||||
if (this.utilityService.punyHost(object.id) !== this.utilityService.punyHost(value)) {
|
||||
if (this.utilityService.punyHostPSLDomain(object.id) !== this.utilityService.punyHostPSLDomain(value)) {
|
||||
throw new UnrecoverableError(`invalid AP object ${value}: id ${object.id} has different host`);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -192,8 +192,8 @@ export class ApNoteService {
|
|||
throw new UnrecoverableError(`unexpected schema of note.url ${url} in ${entryUri}`);
|
||||
}
|
||||
|
||||
if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(note.id)) {
|
||||
throw new Error(`note url <> uri host mismatch: ${url} <> ${note.id} in ${entryUri}`);
|
||||
if (this.utilityService.punyHostPSLDomain(url) !== this.utilityService.punyHostPSLDomain(note.id)) {
|
||||
throw new UnrecoverableError(`note url <> uri host mismatch: ${url} <> ${note.id} in ${entryUri}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -444,7 +444,7 @@ export class ApNoteService {
|
|||
throw new UnrecoverableError(`unexpected schema of note.url ${url} in ${noteUri}`);
|
||||
}
|
||||
|
||||
if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(note.id)) {
|
||||
if (this.utilityService.punyHostPSLDomain(url) !== this.utilityService.punyHostPSLDomain(note.id)) {
|
||||
throw new UnrecoverableError(`note url <> id host mismatch: ${url} <> ${note.id} in ${noteUri}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
*/
|
||||
@bindThis
|
||||
private validateActor(x: IObject, uri: string): IActor {
|
||||
const expectHost = this.utilityService.punyHost(uri);
|
||||
const expectHost = this.utilityService.punyHostPSLDomain(uri);
|
||||
|
||||
if (!isActor(x)) {
|
||||
throw new UnrecoverableError(`invalid Actor type '${x.type}' in ${uri}`);
|
||||
|
|
@ -152,7 +152,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
throw new UnrecoverableError(`invalid Actor ${uri} - wrong inbox type`);
|
||||
}
|
||||
|
||||
const inboxHost = this.utilityService.punyHost(x.inbox);
|
||||
const inboxHost = this.utilityService.punyHostPSLDomain(x.inbox);
|
||||
if (inboxHost !== expectHost) {
|
||||
throw new UnrecoverableError(`invalid Actor ${uri} - wrong inbox ${inboxHost}`);
|
||||
}
|
||||
|
|
@ -160,7 +160,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
const sharedInboxObject = x.sharedInbox ?? (x.endpoints ? x.endpoints.sharedInbox : undefined);
|
||||
if (sharedInboxObject != null) {
|
||||
const sharedInbox = getApId(sharedInboxObject);
|
||||
if (!(typeof sharedInbox === 'string' && sharedInbox.length > 0 && this.utilityService.punyHost(sharedInbox) === expectHost)) {
|
||||
if (!(typeof sharedInbox === 'string' && sharedInbox.length > 0 && this.utilityService.punyHostPSLDomain(sharedInbox) === expectHost)) {
|
||||
throw new UnrecoverableError(`invalid Actor ${uri} - wrong shared inbox ${sharedInbox}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -170,7 +170,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
if (xCollection != null) {
|
||||
const collectionUri = getApId(xCollection);
|
||||
if (typeof collectionUri === 'string' && collectionUri.length > 0) {
|
||||
if (this.utilityService.punyHost(collectionUri) !== expectHost) {
|
||||
if (this.utilityService.punyHostPSLDomain(collectionUri) !== expectHost) {
|
||||
throw new UnrecoverableError(`invalid Actor ${uri} - wrong ${collection} ${collectionUri}`);
|
||||
}
|
||||
} else if (collectionUri != null) {
|
||||
|
|
@ -202,7 +202,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
x.summary = truncate(x.summary, summaryLength);
|
||||
}
|
||||
|
||||
const idHost = this.utilityService.punyHost(x.id);
|
||||
const idHost = this.utilityService.punyHostPSLDomain(x.id);
|
||||
if (idHost !== expectHost) {
|
||||
throw new UnrecoverableError(`invalid Actor ${uri} - wrong id ${x.id}`);
|
||||
}
|
||||
|
|
@ -212,7 +212,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
throw new UnrecoverableError(`invalid Actor ${uri} - wrong publicKey.id type`);
|
||||
}
|
||||
|
||||
const publicKeyIdHost = this.utilityService.punyHost(x.publicKey.id);
|
||||
const publicKeyIdHost = this.utilityService.punyHostPSLDomain(x.publicKey.id);
|
||||
if (publicKeyIdHost !== expectHost) {
|
||||
throw new UnrecoverableError(`invalid Actor ${uri} - wrong publicKey.id ${x.publicKey.id}`);
|
||||
}
|
||||
|
|
@ -351,7 +351,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
throw new UnrecoverableError(`unexpected schema of person url ${url} in ${uri}`);
|
||||
}
|
||||
|
||||
if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(person.id)) {
|
||||
if (this.utilityService.punyHostPSLDomain(url) !== this.utilityService.punyHostPSLDomain(person.id)) {
|
||||
throw new UnrecoverableError(`person url <> uri host mismatch: ${url} <> ${person.id} in ${uri}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -563,7 +563,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
throw new UnrecoverableError(`unexpected schema of person url ${url} in ${uri}`);
|
||||
}
|
||||
|
||||
if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(person.id)) {
|
||||
if (this.utilityService.punyHostPSLDomain(url) !== this.utilityService.punyHostPSLDomain(person.id)) {
|
||||
throw new UnrecoverableError(`person url <> uri host mismatch: ${url} <> ${person.id} in ${uri}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -336,6 +336,10 @@ export interface ILike extends IActivity {
|
|||
_misskey_reaction?: string;
|
||||
}
|
||||
|
||||
export interface IDislike extends IActivity {
|
||||
type: 'Dislike';
|
||||
}
|
||||
|
||||
export interface IAnnounce extends IActivity {
|
||||
type: 'Announce';
|
||||
}
|
||||
|
|
@ -368,6 +372,7 @@ export const isLike = (object: IObject): object is ILike => {
|
|||
const type = getApType(object);
|
||||
return type != null && ['Like', 'EmojiReaction', 'EmojiReact'].includes(type);
|
||||
};
|
||||
export const isDislike = (object: IObject): object is IDislike => getApType(object) === 'Dislike';
|
||||
export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce';
|
||||
export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block';
|
||||
export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag';
|
||||
|
|
|
|||
43
packages/backend/test/unit/UtilityService.ts
Normal file
43
packages/backend/test/unit/UtilityService.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import * as assert from 'assert';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { CoreModule } from '@/core/CoreModule.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
|
||||
describe('UtilityService', () => {
|
||||
let utilityService: UtilityService;
|
||||
|
||||
beforeAll(async () => {
|
||||
const app = await Test.createTestingModule({
|
||||
imports: [GlobalModule, CoreModule],
|
||||
}).compile();
|
||||
utilityService = app.get<UtilityService>(UtilityService);
|
||||
});
|
||||
|
||||
describe('punyHost', () => {
|
||||
test('simple', () => {
|
||||
assert.equal(utilityService.punyHost('http://www.foo.com'), 'www.foo.com');
|
||||
});
|
||||
test('japanese', () => {
|
||||
assert.equal(utilityService.punyHost('http://www.新聞.com'), 'www.xn--efvv70d.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('punyHostPSLDomain', () => {
|
||||
test('simple', () => {
|
||||
assert.equal(utilityService.punyHostPSLDomain('http://www.foo.com'), 'foo.com');
|
||||
});
|
||||
test('japanese', () => {
|
||||
assert.equal(utilityService.punyHostPSLDomain('http://www.新聞.com'), 'xn--efvv70d.com');
|
||||
});
|
||||
test('lower', () => {
|
||||
assert.equal(utilityService.punyHostPSLDomain('http://foo.github.io'), 'foo.github.io');
|
||||
assert.equal(utilityService.punyHostPSLDomain('http://foo.bar.github.io'), 'bar.github.io');
|
||||
});
|
||||
test('special', () => {
|
||||
assert.equal(utilityService.punyHostPSLDomain('http://foo.masto.host'), 'foo.masto.host');
|
||||
assert.equal(utilityService.punyHostPSLDomain('http://foo.bar.masto.host'), 'bar.masto.host');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -612,8 +612,8 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) {
|
|||
username: config.db.user,
|
||||
password: config.db.pass,
|
||||
database: config.db.db,
|
||||
synchronize: true && !justBorrow,
|
||||
dropSchema: true && !justBorrow,
|
||||
synchronize: !justBorrow,
|
||||
dropSchema: !justBorrow,
|
||||
entities: initEntities ?? entities,
|
||||
});
|
||||
|
||||
|
|
|
|||
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
|
|
@ -170,6 +170,9 @@ importers:
|
|||
'@twemoji/parser':
|
||||
specifier: 15.1.1
|
||||
version: 15.1.1
|
||||
'@types/psl':
|
||||
specifier: ^1.1.3
|
||||
version: 1.1.3
|
||||
accepts:
|
||||
specifier: 1.3.8
|
||||
version: 1.3.8
|
||||
|
|
@ -365,6 +368,9 @@ importers:
|
|||
proxy-addr:
|
||||
specifier: ^2.0.7
|
||||
version: 2.0.7
|
||||
psl:
|
||||
specifier: ^1.13.0
|
||||
version: 1.13.0
|
||||
pug:
|
||||
specifier: 3.0.3
|
||||
version: 3.0.3
|
||||
|
|
@ -4835,6 +4841,9 @@ packages:
|
|||
'@types/proxy-addr@2.0.3':
|
||||
resolution: {integrity: sha512-TgAHHO4tNG3HgLTUhB+hM4iwW6JUNeQHCLnF1DjaDA9c69PN+IasoFu2MYDhubFc+ZIw5c5t9DMtjvrD6R3Egg==}
|
||||
|
||||
'@types/psl@1.1.3':
|
||||
resolution: {integrity: sha512-Iu174JHfLd7i/XkXY6VDrqSlPvTDQOtQI7wNAXKKOAADJ9TduRLkNdMgjGiMxSttUIZnomv81JAbAbC0DhggxA==}
|
||||
|
||||
'@types/pug@2.0.10':
|
||||
resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
|
||||
|
||||
|
|
@ -5307,6 +5316,7 @@ packages:
|
|||
|
||||
acorn-import-assertions@1.9.0:
|
||||
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
|
||||
deprecated: package has been renamed to acorn-import-attributes
|
||||
peerDependencies:
|
||||
acorn: ^8
|
||||
|
||||
|
|
@ -9736,8 +9746,8 @@ packages:
|
|||
pseudomap@1.0.2:
|
||||
resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
|
||||
|
||||
psl@1.9.0:
|
||||
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
|
||||
psl@1.13.0:
|
||||
resolution: {integrity: sha512-BFwmFXiJoFqlUpZ5Qssolv15DMyc84gTBds1BjsV1BfXEo1UyyD7GsmN67n7J77uRhoSNW1AXtXKPLcBFQn9Aw==}
|
||||
|
||||
pstree.remy@1.1.8:
|
||||
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
|
||||
|
|
@ -13103,7 +13113,7 @@ snapshots:
|
|||
'@eslint/config-array@0.17.1':
|
||||
dependencies:
|
||||
'@eslint/object-schema': 2.1.4
|
||||
debug: 4.3.5(supports-color@8.1.1)
|
||||
debug: 4.3.7
|
||||
minimatch: 3.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -13125,7 +13135,7 @@ snapshots:
|
|||
'@eslint/eslintrc@3.1.0':
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.3.5(supports-color@8.1.1)
|
||||
debug: 4.3.7
|
||||
espree: 10.1.0
|
||||
globals: 14.0.0
|
||||
ignore: 5.3.1
|
||||
|
|
@ -15648,6 +15658,8 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/node': 20.14.12
|
||||
|
||||
'@types/psl@1.1.3': {}
|
||||
|
||||
'@types/pug@2.0.10': {}
|
||||
|
||||
'@types/punycode@2.1.4': {}
|
||||
|
|
@ -18435,7 +18447,7 @@ snapshots:
|
|||
ajv: 6.12.6
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.3
|
||||
debug: 4.3.5(supports-color@8.1.1)
|
||||
debug: 4.3.7
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 8.0.2
|
||||
eslint-visitor-keys: 4.0.0
|
||||
|
|
@ -22014,7 +22026,9 @@ snapshots:
|
|||
|
||||
pseudomap@1.0.2: {}
|
||||
|
||||
psl@1.9.0: {}
|
||||
psl@1.13.0:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
pstree.remy@1.1.8: {}
|
||||
|
||||
|
|
@ -23256,7 +23270,7 @@ snapshots:
|
|||
|
||||
tough-cookie@4.1.4:
|
||||
dependencies:
|
||||
psl: 1.9.0
|
||||
psl: 1.13.0
|
||||
punycode: 2.3.1
|
||||
universalify: 0.2.0
|
||||
url-parse: 1.5.10
|
||||
|
|
|
|||
418
sharkey-service.nix
Normal file
418
sharkey-service.nix
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.sharkey;
|
||||
settingsFormat = pkgs.formats.yaml { };
|
||||
redisType = lib.types.submodule {
|
||||
freeformType = lib.types.attrsOf settingsFormat.type;
|
||||
options = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "localhost";
|
||||
description = "The Redis host.";
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 6379;
|
||||
description = "The Redis port.";
|
||||
};
|
||||
};
|
||||
};
|
||||
settings = lib.mkOption {
|
||||
description = ''
|
||||
Configuration for Misskey, see
|
||||
[`example.yml`](https://github.com/sharkey-dev/sharkey/blob/develop/.config/example.yml)
|
||||
for all supported options.
|
||||
'';
|
||||
type = lib.types.submodule {
|
||||
freeformType = lib.types.attrsOf settingsFormat.type;
|
||||
options = {
|
||||
url = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "https://example.tld/";
|
||||
description = ''
|
||||
The final user-facing URL. Do not change after running Misskey for the first time.
|
||||
|
||||
This needs to match up with the configured reverse proxy and is automatically configured when using `services.sharkey.reverseProxy`.
|
||||
'';
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 3000;
|
||||
description = "The port your Misskey server should listen on.";
|
||||
};
|
||||
socket = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = "/path/to/sharkey.sock";
|
||||
description = "The UNIX socket your Misskey server should listen on.";
|
||||
};
|
||||
chmodSocket = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "777";
|
||||
description = "The file access mode of the UNIX socket.";
|
||||
};
|
||||
db = lib.mkOption {
|
||||
description = "Database settings.";
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/run/postgresql";
|
||||
example = "localhost";
|
||||
description = "The PostgreSQL host.";
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 5432;
|
||||
description = "The PostgreSQL port.";
|
||||
};
|
||||
db = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "sharkey";
|
||||
description = "The database name.";
|
||||
};
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "sharkey";
|
||||
description = "The user used for database authentication.";
|
||||
};
|
||||
pass = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "The password used for database authentication.";
|
||||
};
|
||||
disableCache = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to disable caching queries.";
|
||||
};
|
||||
extra = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.attrsOf settingsFormat.type);
|
||||
default = null;
|
||||
example = {
|
||||
ssl = true;
|
||||
};
|
||||
description = "Extra connection options.";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
redis = lib.mkOption {
|
||||
type = redisType;
|
||||
default = { };
|
||||
description = "`ioredis` options. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
|
||||
};
|
||||
redisForPubsub = lib.mkOption {
|
||||
type = lib.types.nullOr redisType;
|
||||
default = null;
|
||||
description = "`ioredis` options for pubsub. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
|
||||
};
|
||||
redisForJobQueue = lib.mkOption {
|
||||
type = lib.types.nullOr redisType;
|
||||
default = null;
|
||||
description = "`ioredis` options for the job queue. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
|
||||
};
|
||||
redisForTimelines = lib.mkOption {
|
||||
type = lib.types.nullOr redisType;
|
||||
default = null;
|
||||
description = "`ioredis` options for timelines. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
|
||||
};
|
||||
meilisearch = lib.mkOption {
|
||||
description = "Meilisearch connection options.";
|
||||
type = lib.types.nullOr (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "localhost";
|
||||
description = "The Meilisearch host.";
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 7700;
|
||||
description = "The Meilisearch port.";
|
||||
};
|
||||
apiKey = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "The Meilisearch API key.";
|
||||
};
|
||||
ssl = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to connect via SSL.";
|
||||
};
|
||||
index = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Meilisearch index to use.";
|
||||
};
|
||||
scope = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"local"
|
||||
"global"
|
||||
];
|
||||
default = "local";
|
||||
description = "The search scope.";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = null;
|
||||
};
|
||||
id = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"aid"
|
||||
"aidx"
|
||||
"meid"
|
||||
"ulid"
|
||||
"objectid"
|
||||
];
|
||||
default = "aidx";
|
||||
description = "The ID generation method to use. Do not change after starting Misskey for the first time.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.sharkey = {
|
||||
enable = lib.mkEnableOption "sharkey";
|
||||
package = lib.mkPackageOption pkgs "sharkey" { };
|
||||
inherit settings;
|
||||
database = {
|
||||
createLocally = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Create the PostgreSQL database locally. Sets `services.sharkey.settings.db.{db,host,port,user,pass}`.";
|
||||
};
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = "The path to a file containing the database password. Sets `services.sharkey.settings.db.pass`.";
|
||||
};
|
||||
};
|
||||
redis = {
|
||||
createLocally = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Create and use a local Redis instance. Sets `services.sharkey.settings.redis.host`.";
|
||||
};
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = "The path to a file containing the Redis password. Sets `services.sharkey.settings.redis.pass`.";
|
||||
};
|
||||
};
|
||||
meilisearch = {
|
||||
createLocally = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Create and use a local Meilisearch instance. Sets `services.sharkey.settings.meilisearch.{host,port,ssl}`.";
|
||||
};
|
||||
keyFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = "The path to a file containing the Meilisearch API key. Sets `services.sharkey.settings.meilisearch.apiKey`.";
|
||||
};
|
||||
};
|
||||
reverseProxy = {
|
||||
enable = lib.mkEnableOption "a HTTP reverse proxy for Misskey";
|
||||
webserver = lib.mkOption {
|
||||
type = lib.types.attrTag {
|
||||
nginx = lib.mkOption {
|
||||
type = lib.types.submodule (import ../web-servers/nginx/vhost-options.nix);
|
||||
default = { };
|
||||
description = ''
|
||||
Extra configuration for the nginx virtual host of Misskey.
|
||||
Set to `{ }` to use the default configuration.
|
||||
'';
|
||||
};
|
||||
caddy = lib.mkOption {
|
||||
type = lib.types.submodule (
|
||||
import ../web-servers/caddy/vhost-options.nix { cfg = config.services.caddy; }
|
||||
);
|
||||
default = { };
|
||||
description = ''
|
||||
Extra configuration for the caddy virtual host of Misskey.
|
||||
Set to `{ }` to use the default configuration.
|
||||
'';
|
||||
};
|
||||
};
|
||||
description = "The webserver to use as the reverse proxy.";
|
||||
};
|
||||
host = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
The fully qualified domain name to bind to. Sets `services.sharkey.settings.url`.
|
||||
|
||||
This is required when using `services.sharkey.reverseProxy.enable = true`.
|
||||
'';
|
||||
example = "sharkey.example.com";
|
||||
default = null;
|
||||
};
|
||||
ssl = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.bool;
|
||||
description = ''
|
||||
Whether to enable SSL for the reverse proxy. Sets `services.sharkey.settings.url`.
|
||||
|
||||
This is required when using `services.sharkey.reverseProxy.enable = true`.
|
||||
'';
|
||||
example = true;
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
cfg.reverseProxy.enable -> ((cfg.reverseProxy.host != null) && (cfg.reverseProxy.ssl != null));
|
||||
message = "`services.sharkey.reverseProxy.enable` requires `services.sharkey.reverseProxy.host` and `services.sharkey.reverseProxy.ssl` to be set.";
|
||||
}
|
||||
];
|
||||
|
||||
services.sharkey.settings = lib.mkMerge [
|
||||
(lib.mkIf cfg.database.createLocally {
|
||||
db = {
|
||||
db = lib.mkDefault "sharkey";
|
||||
# Use unix socket instead of localhost to allow PostgreSQL peer authentication,
|
||||
# required for `services.postgresql.ensureUsers`
|
||||
host = lib.mkDefault "/var/run/postgresql";
|
||||
port = lib.mkDefault config.services.postgresql.settings.port;
|
||||
user = lib.mkDefault "sharkey";
|
||||
pass = lib.mkDefault null;
|
||||
};
|
||||
})
|
||||
(lib.mkIf (cfg.database.passwordFile != null) { db.pass = lib.mkDefault "@DATABASE_PASSWORD@"; })
|
||||
(lib.mkIf cfg.redis.createLocally { redis.host = lib.mkDefault "localhost"; })
|
||||
(lib.mkIf (cfg.redis.passwordFile != null) { redis.pass = lib.mkDefault "@REDIS_PASSWORD@"; })
|
||||
(lib.mkIf cfg.meilisearch.createLocally {
|
||||
meilisearch = {
|
||||
host = lib.mkDefault "localhost";
|
||||
port = lib.mkDefault config.services.meilisearch.listenPort;
|
||||
ssl = lib.mkDefault false;
|
||||
};
|
||||
})
|
||||
(lib.mkIf (cfg.meilisearch.keyFile != null) {
|
||||
meilisearch.apiKey = lib.mkDefault "@MEILISEARCH_KEY@";
|
||||
})
|
||||
(lib.mkIf cfg.reverseProxy.enable {
|
||||
url = lib.mkDefault "${
|
||||
if cfg.reverseProxy.ssl then "https" else "http"
|
||||
}://${cfg.reverseProxy.host}";
|
||||
})
|
||||
];
|
||||
|
||||
systemd.services.sharkey = {
|
||||
after = [
|
||||
"network-online.target"
|
||||
"postgresql.service"
|
||||
];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
MISSKEY_CONFIG_YML = "/run/sharkey/default.yml";
|
||||
};
|
||||
preStart =
|
||||
''
|
||||
install -m 700 ${settingsFormat.generate "sharkey-config.yml" cfg.settings} /run/sharkey/default.yml
|
||||
''
|
||||
+ (lib.optionalString (cfg.database.passwordFile != null) ''
|
||||
${pkgs.replace-secret}/bin/replace-secret '@DATABASE_PASSWORD@' "${cfg.database.passwordFile}" /run/sharkey/default.yml
|
||||
'')
|
||||
+ (lib.optionalString (cfg.redis.passwordFile != null) ''
|
||||
${pkgs.replace-secret}/bin/replace-secret '@REDIS_PASSWORD@' "${cfg.redis.passwordFile}" /run/sharkey/default.yml
|
||||
'')
|
||||
+ (lib.optionalString (cfg.meilisearch.keyFile != null) ''
|
||||
${pkgs.replace-secret}/bin/replace-secret '@MEILISEARCH_KEY@' "${cfg.meilisearch.keyFile}" /run/sharkey/default.yml
|
||||
'');
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/sharkey migrateandstart";
|
||||
RuntimeDirectory = "sharkey";
|
||||
RuntimeDirectoryMode = "700";
|
||||
StateDirectory = "sharkey";
|
||||
StateDirectoryMode = "700";
|
||||
TimeoutSec = 60;
|
||||
DynamicUser = true;
|
||||
User = "sharkey";
|
||||
LockPersonality = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectProc = "invisible";
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql = lib.mkIf cfg.database.createLocally {
|
||||
enable = true;
|
||||
ensureDatabases = [ "sharkey" ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "sharkey";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.redis.servers = lib.mkIf cfg.redis.createLocally {
|
||||
sharkey = {
|
||||
enable = true;
|
||||
port = cfg.settings.redis.port;
|
||||
};
|
||||
};
|
||||
|
||||
services.meilisearch = lib.mkIf cfg.meilisearch.createLocally { enable = true; };
|
||||
|
||||
services.caddy = lib.mkIf (cfg.reverseProxy.enable && cfg.reverseProxy.webserver ? caddy) {
|
||||
enable = true;
|
||||
virtualHosts.${cfg.settings.url} = lib.mkMerge [
|
||||
cfg.reverseProxy.webserver.caddy
|
||||
{
|
||||
hostName = lib.mkDefault cfg.settings.url;
|
||||
extraConfig = ''
|
||||
reverse_proxy localhost:${toString cfg.settings.port}
|
||||
'';
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx = lib.mkIf (cfg.reverseProxy.enable && cfg.reverseProxy.webserver ? nginx) {
|
||||
enable = true;
|
||||
virtualHosts.${cfg.reverseProxy.host} = lib.mkMerge [
|
||||
cfg.reverseProxy.webserver.nginx
|
||||
{
|
||||
locations."/" = {
|
||||
proxyPass = lib.mkDefault "http://localhost:${toString cfg.settings.port}";
|
||||
proxyWebsockets = lib.mkDefault true;
|
||||
recommendedProxySettings = lib.mkDefault true;
|
||||
};
|
||||
}
|
||||
(lib.mkIf (cfg.reverseProxy.ssl != null) { forceSSL = lib.mkDefault cfg.reverseProxy.ssl; })
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
maintainers = [ lib.maintainers.feathecutie ];
|
||||
};
|
||||
}
|
||||
124
sharkey.nix
Normal file
124
sharkey.nix
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
stdenv,
|
||||
lib,
|
||||
nixosTests,
|
||||
fetchFromGitHub,
|
||||
nodejs,
|
||||
pnpm,
|
||||
makeWrapper,
|
||||
python3,
|
||||
bash,
|
||||
jemalloc,
|
||||
ffmpeg-headless,
|
||||
writeShellScript,
|
||||
xcbuild,
|
||||
...
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "sharkey";
|
||||
|
||||
version = "2024.10.0";
|
||||
|
||||
src = fetchGit {
|
||||
url = "https://activitypub.software/TransFem-org/Sharkey.git";
|
||||
rev = "150d949a3ec2b5162e2dfda10c2cc5dddea8c59a";
|
||||
submodules = true;
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
nodejs
|
||||
pnpm.configHook
|
||||
makeWrapper
|
||||
python3
|
||||
] ++ lib.optionals stdenv.hostPlatform.isDarwin [ xcbuild.xcrun ];
|
||||
|
||||
# https://nixos.org/manual/nixpkgs/unstable/#javascript-pnpm
|
||||
pnpmDeps = pnpm.fetchDeps {
|
||||
inherit (finalAttrs) pname version src;
|
||||
hash = "sha256-PpXmNBO4pWj8AG0we4DpPhzfx/18rwDZHi86+esFghM=";
|
||||
};
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
# https://github.com/NixOS/nixpkgs/pull/296697/files#r1617546739
|
||||
(
|
||||
cd node_modules/.pnpm/node_modules/v-code-diff
|
||||
pnpm run postinstall
|
||||
)
|
||||
|
||||
# https://github.com/NixOS/nixpkgs/pull/296697/files#r1617595593
|
||||
export npm_config_nodedir=${nodejs}
|
||||
(
|
||||
cd node_modules/.pnpm/node_modules/re2
|
||||
pnpm run rebuild
|
||||
)
|
||||
(
|
||||
cd node_modules/.pnpm/node_modules/sharp
|
||||
pnpm run install
|
||||
)
|
||||
|
||||
pnpm build
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase =
|
||||
let
|
||||
checkEnvVarScript = writeShellScript "sharkey-check-env-var" ''
|
||||
if [[ -z $MISSKEY_CONFIG_YML ]]; then
|
||||
echo "MISSKEY_CONFIG_YML must be set to the location of the Misskey config file."
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
in
|
||||
''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/data
|
||||
cp -r . $out/data
|
||||
|
||||
# Set up symlink for use at runtime
|
||||
# TODO: Find a better solution for this (potentially patch Misskey to make this configurable?)
|
||||
# Line that would need to be patched: https://github.com/sharkey-dev/sharkey/blob/9849aab40283cbde2184e74d4795aec8ef8ccba3/packages/backend/src/core/InternalStorageService.ts#L18
|
||||
# Otherwise, maybe somehow bindmount a writable directory into <package>/data/files.
|
||||
ln -s /var/lib/sharkey $out/data/files
|
||||
|
||||
makeWrapper ${pnpm}/bin/pnpm $out/bin/sharkey \
|
||||
--run "${checkEnvVarScript} || exit" \
|
||||
--chdir $out/data \
|
||||
--add-flags run \
|
||||
--set-default NODE_ENV production \
|
||||
--prefix PATH : ${
|
||||
lib.makeBinPath [
|
||||
nodejs
|
||||
pnpm
|
||||
bash
|
||||
]
|
||||
} \
|
||||
--prefix LD_LIBRARY_PATH : ${
|
||||
lib.makeLibraryPath [
|
||||
jemalloc
|
||||
ffmpeg-headless
|
||||
stdenv.cc.cc
|
||||
]
|
||||
}
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
inherit (finalAttrs) pnpmDeps;
|
||||
tests.sharkey = nixosTests.sharkey;
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "🌎 An interplanetary microblogging platform 🚀";
|
||||
homepage = "https://sharkey-hub.net/";
|
||||
license = lib.licenses.agpl3Only;
|
||||
maintainers = [ lib.maintainers.feathecutie ];
|
||||
platforms = lib.platforms.unix;
|
||||
mainProgram = "sharkey";
|
||||
};
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue