Compare commits
No commits in common. "34a05015e2cf0711c54eb16dc19c6d3242293e12" and "3833f6e5e2697db4a10fd87f35e947cde3538b04" have entirely different histories.
34a05015e2
...
3833f6e5e2
94
external-sync.sh
Executable file
94
external-sync.sh
Executable file
@ -0,0 +1,94 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Initialize variables
|
||||||
|
commit_message=""
|
||||||
|
action=""
|
||||||
|
folder_to_sync=""
|
||||||
|
external_base_dir="../external-nise.moe"
|
||||||
|
|
||||||
|
# Manual argument parsing
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
sync|create|configure|push)
|
||||||
|
action="$1"
|
||||||
|
folder_to_sync="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-m)
|
||||||
|
commit_message="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid argument: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validation
|
||||||
|
if [ -z "$action" ]; then
|
||||||
|
echo "Error: Please provide an action (sync or create)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$folder_to_sync" ]; then
|
||||||
|
echo "Error: Please provide a folder name."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
target_folder="$external_base_dir/$folder_to_sync"
|
||||||
|
|
||||||
|
# Further validation and action handling
|
||||||
|
if [ "$action" == "sync" ]; then
|
||||||
|
if [ ! -d "$folder_to_sync" ]; then
|
||||||
|
echo "Error: Folder '$folder_to_sync' does not exist in the monorepo."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$target_folder"
|
||||||
|
echo "Synchronizing files from '$folder_to_sync' to '$target_folder'..."
|
||||||
|
rsync -av --delete --exclude-from='.gitignore' --exclude='.git' "$folder_to_sync/" "$target_folder/"
|
||||||
|
|
||||||
|
cd "$target_folder" || { echo "Error: Failed to change directory to $target_folder"; exit 1; }
|
||||||
|
|
||||||
|
git add .
|
||||||
|
if [ -z "$commit_message" ]; then
|
||||||
|
commit_message=$(date +%Y%m%d)
|
||||||
|
fi
|
||||||
|
git commit -S -am "$commit_message"
|
||||||
|
|
||||||
|
echo "Synchronization complete and changes committed with message: '$commit_message'."
|
||||||
|
elif [ "$action" == "configure" ]; then
|
||||||
|
echo "Configuring external folder '$target_folder'..."
|
||||||
|
cd "$target_folder" || { echo "Error: Failed to change directory to $target_folder"; exit 1; }
|
||||||
|
|
||||||
|
git config user.email "162507023+nise-moe@users.noreply.github.com"
|
||||||
|
git config user.name "nise.moe"
|
||||||
|
git config gpg.format ssh
|
||||||
|
git config commit.gpgsign true
|
||||||
|
git config user.signingkey /home/anon/.ssh/nise-moe.pub
|
||||||
|
elif [ "$action" == "push" ]; then
|
||||||
|
echo "Pushing changes to remote repository..."
|
||||||
|
cd "$target_folder" || { echo "Error: Failed to change directory to $target_folder"; exit 1; }
|
||||||
|
|
||||||
|
git push origin main
|
||||||
|
echo "Changes pushed to remote repository."
|
||||||
|
elif [ "$action" == "create" ]; then
|
||||||
|
mkdir -p "$target_folder"
|
||||||
|
|
||||||
|
cd "$target_folder" || { echo "Error: Failed to change directory to $target_folder"; exit 1; }
|
||||||
|
|
||||||
|
git init
|
||||||
|
git config user.email "162507023+nise-moe@users.noreply.github.com"
|
||||||
|
git config user.name "nise.moe"
|
||||||
|
git config gpg.format ssh
|
||||||
|
git config commit.gpgsign true
|
||||||
|
git config user.signingkey /home/anon/.ssh/nise-moe.pub
|
||||||
|
git branch -M main
|
||||||
|
git remote add origin git@github.com:nise-moe/"$folder_to_sync".git
|
||||||
|
|
||||||
|
echo "External folder '$target_folder' created and initialized as a Git repository."
|
||||||
|
else
|
||||||
|
echo "Error: Invalid action '$action'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@ -43,7 +43,7 @@ class UserService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getUserDetails(identifier: Any): UserDetailsExtended? {
|
fun getUserDetails(identifier: Any): UserDetailsExtended? {
|
||||||
var user = when (identifier) {
|
val user = when (identifier) {
|
||||||
is Long -> dslContext.selectFrom(USERS)
|
is Long -> dslContext.selectFrom(USERS)
|
||||||
.where(USERS.USER_ID.eq(identifier))
|
.where(USERS.USER_ID.eq(identifier))
|
||||||
.fetchOneInto(UsersRecord::class.java)
|
.fetchOneInto(UsersRecord::class.java)
|
||||||
@ -53,16 +53,6 @@ class UserService(
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup user by ID if we have not found a user via a username lookup and the identifier is a valid number
|
|
||||||
if (user == null && identifier is String) {
|
|
||||||
val longIdentifier = identifier.toLongOrNull()
|
|
||||||
if (longIdentifier != null) {
|
|
||||||
user = dslContext.selectFrom(USERS)
|
|
||||||
.where(USERS.USER_ID.eq(longIdentifier))
|
|
||||||
.fetchOneInto(UsersRecord::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
val userDetails = UserDetails(
|
val userDetails = UserDetails(
|
||||||
user.userId!!,
|
user.userId!!,
|
||||||
@ -85,7 +75,7 @@ class UserService(
|
|||||||
// The database does NOT have the user; we will now use the osu!api
|
// The database does NOT have the user; we will now use the osu!api
|
||||||
val apiUser = when (identifier) {
|
val apiUser = when (identifier) {
|
||||||
is Long -> this.osuApi.getUserProfile(userId = identifier.toString(), mode = "osu", key = "id")
|
is Long -> this.osuApi.getUserProfile(userId = identifier.toString(), mode = "osu", key = "id")
|
||||||
is String -> this.osuApi.getUserProfile(userId = identifier, mode = "osu")
|
is String -> this.osuApi.getUserProfile(userId = identifier, mode = "osu", key = "username")
|
||||||
else -> null
|
else -> null
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
CREATE INDEX idx_scores_is_banned ON public.scores(is_banned);
|
|
||||||
CREATE INDEX idx_scores_pp ON public.scores(pp);
|
|
||||||
CREATE INDEX idx_scores_sliderend_release_standard_deviation_adjusted ON public.scores(sliderend_release_standard_deviation_adjusted);
|
|
||||||
CREATE INDEX idx_scores_keypresses_standard_deviation_adjusted ON public.scores(keypresses_standard_deviation_adjusted);
|
|
||||||
@ -68,7 +68,7 @@ export function getMockReplayData(): ReplayData {
|
|||||||
|
|
||||||
export interface ReplayData {
|
export interface ReplayData {
|
||||||
replay_id: number | null;
|
replay_id: number | null;
|
||||||
user_id: number | null;
|
user_id: number;
|
||||||
username: string;
|
username: string;
|
||||||
date: string;
|
date: string;
|
||||||
beatmap_id: number;
|
beatmap_id: number;
|
||||||
|
|||||||
@ -157,7 +157,7 @@
|
|||||||
<ng-container *ngIf="column.type == 'string'">
|
<ng-container *ngIf="column.type == 'string'">
|
||||||
|
|
||||||
<ng-container *ngIf="column.name == 'user_username'; else stringField">
|
<ng-container *ngIf="column.name == 'user_username'; else stringField">
|
||||||
<a [href]="'/u/' + getValue(entry, 'user_id')" target="_blank">{{ getValue(entry, column.name) }}</a>
|
<a [href]="'/u/' + getValue(entry, column.name)" target="_blank">{{ getValue(entry, column.name) }}</a>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #stringField>
|
<ng-template #stringField>
|
||||||
{{ getValue(entry, column.name) }}
|
{{ getValue(entry, column.name) }}
|
||||||
|
|||||||
@ -346,7 +346,7 @@ export class SearchComponent implements OnInit {
|
|||||||
|
|
||||||
getLink(entry: any): any {
|
getLink(entry: any): any {
|
||||||
if(this.searchType === 'user') {
|
if(this.searchType === 'user') {
|
||||||
return "/u/" + this.getValue(entry, 'user_id');
|
return "/u/" + this.getValue(entry, 'username');
|
||||||
} else {
|
} else {
|
||||||
return "/s/" + this.getValue(entry, 'replay_id');
|
return "/s/" + this.getValue(entry, 'replay_id');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
import {UserDetails} from './userDetails';
|
|
||||||
import {SimilarReplay, SuspiciousScore} from './replays';
|
|
||||||
|
|
||||||
export class TextReportService {
|
|
||||||
static generateTextReportForUserScores(
|
|
||||||
userDetails: UserDetails,
|
|
||||||
suspiciousScores: SuspiciousScore[],
|
|
||||||
similarReplays: SimilarReplay[],
|
|
||||||
) {
|
|
||||||
const detections: string[] = [];
|
|
||||||
|
|
||||||
if (suspiciousScores.length > 0) {
|
|
||||||
detections.push('Relax');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (similarReplays.length > 0) {
|
|
||||||
detections.push('Replay Stealing');
|
|
||||||
}
|
|
||||||
|
|
||||||
let report = `[osu!std] ${userDetails.username} | ${detections.join(', ')}\n\n`;
|
|
||||||
report += `Profile: https://osu.ppy.sh/users/${userDetails.user_id}\n`;
|
|
||||||
|
|
||||||
for (const suspiciousScore of suspiciousScores) {
|
|
||||||
report += `\n${this.getRelaxReport(suspiciousScore)}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const similarReplay of similarReplays) {
|
|
||||||
report += `\n${this.getStealingReport(similarReplay)}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
report += `\nGenerated on nise.moe - [${userDetails.username} on nise.moe](https://nise.moe/u/${userDetails.user_id})`;
|
|
||||||
|
|
||||||
return report;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getRelaxReport(suspiciousScore: SuspiciousScore): string {
|
|
||||||
return `[Replay on ${suspiciousScore.beatmap_title}](https://osu.ppy.sh/scores/osu/${suspiciousScore.replay_id})
|
|
||||||
cvUR: ${suspiciousScore.ur.toFixed(2)} according to Circleguard`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getStealingReport(similarReplay: SimilarReplay): string {
|
|
||||||
return `[${similarReplay.username_2}'s replay (cheated)](https://osu.ppy.sh/scores/osu/${similarReplay.replay_id_2})
|
|
||||||
[${similarReplay.username_1}'s replay (original)](https://osu.ppy.sh/scores/osu/${similarReplay.replay_id_1})
|
|
||||||
${similarReplay.similarity.toFixed(2)} similarity according to Circleguard`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -62,11 +62,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Player</td>
|
<td>Player</td>
|
||||||
<td>
|
<td>
|
||||||
<a [routerLink]="['/u/' + this.pair.replays[0].user_id]">{{ this.pair.replays[0].username }}</a>
|
<a [routerLink]="['/u/' + this.pair.replays[0].username]">{{ this.pair.replays[0].username }}</a>
|
||||||
<a class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.pair.replays[0].user_id }}" target="_blank">osu!web</a>
|
<a class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.pair.replays[0].user_id }}" target="_blank">osu!web</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a [routerLink]="['/u/' + this.pair.replays[1].user_id]">{{ this.pair.replays[1].username }}</a>
|
<a [routerLink]="['/u/' + this.pair.replays[1].username]">{{ this.pair.replays[1].username }}</a>
|
||||||
<a class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.pair.replays[1].user_id }}" target="_blank">osu!web</a>
|
<a class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.pair.replays[1].user_id }}" target="_blank">osu!web</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="score-player__row score-player__row--player mt-2">
|
<div class="score-player__row score-player__row--player mt-2">
|
||||||
Played by <a [routerLink]="['/u/' + (this.replayData.user_id ?? this.replayData.username)]">{{ this.replayData.username }}</a> <a *ngIf="this.replayData.user_id" class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.replayData.user_id }}" target="_blank">osu!web</a>
|
Played by <a [routerLink]="['/u/' + this.replayData.username]">{{ this.replayData.username }}</a> <a *ngIf="this.replayData.user_id" class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.replayData.user_id }}" target="_blank">osu!web</a>
|
||||||
<ng-container *ngIf="!this.isUserScore">
|
<ng-container *ngIf="!this.isUserScore">
|
||||||
<br>
|
<br>
|
||||||
Submitted on <strong>{{ this.replayData.date }}</strong>
|
Submitted on <strong>{{ this.replayData.date }}</strong>
|
||||||
@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
<div style="display: flex; justify-content: space-between; margin-bottom: 10px" class="link-list">
|
<div style="display: flex; justify-content: space-between; margin-bottom: 10px" class="link-list">
|
||||||
|
|
||||||
<a style="flex: 1" class="text-center" href="https://osu.ppy.sh/scores/osu/{{ this.replayData.replay_id }}" target="_blank" [class.disabled]="!isWebScore()">
|
<a style="flex: 1" class="text-center" href="https://osu.ppy.sh/scores/osu/{{ this.replayData.replay_id }}" target="_blank">
|
||||||
Open in osu!web
|
Open in osu!web
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@ -196,7 +196,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let score of this.replayData.similar_scores">
|
<tr *ngFor="let score of this.replayData.similar_scores">
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a [routerLink]="['/u/' + score.user_id]">{{ score.username }}</a>
|
<a [routerLink]="['/u/' + score.username]">{{ score.username }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{{ score.pp | number: '1.2-2' }}
|
{{ score.pp | number: '1.2-2' }}
|
||||||
|
|||||||
@ -63,10 +63,6 @@ export class ViewScoreComponent implements OnInit {
|
|||||||
return this.hasErrorDistribution();
|
return this.hasErrorDistribution();
|
||||||
}
|
}
|
||||||
|
|
||||||
isWebScore(): boolean {
|
|
||||||
return !!this.replayData && this.replayData.replay_id !== 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.activatedRoute.params.subscribe(params => {
|
this.activatedRoute.params.subscribe(params => {
|
||||||
this.replayId = params['replayId'];
|
this.replayId = params['replayId'];
|
||||||
|
|||||||
@ -98,7 +98,7 @@
|
|||||||
<tbody style="font-size: 14px;">
|
<tbody style="font-size: 14px;">
|
||||||
<tr *ngFor="let score of this.getCurrentPage()">
|
<tr *ngFor="let score of this.getCurrentPage()">
|
||||||
<td>
|
<td>
|
||||||
<a [routerLink]="['/u/' + score.user_id]">
|
<a [routerLink]="['/u/' + score.username]">
|
||||||
{{ score.username }}
|
{{ score.username }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -82,12 +82,6 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="this.userInfo.suspicious_scores.length > 0 || this.userInfo.similar_replays.length > 0">
|
|
||||||
<button (click)="copyReportToClipboard()" class="btn btn-outline-secondary btn-sm">
|
|
||||||
Generate and copy report
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *ngIf="this.userInfo.suspicious_scores.length > 0">
|
<ng-container *ngIf="this.userInfo.suspicious_scores.length > 0">
|
||||||
<h4>Suspicious Scores ({{ this.userInfo.suspicious_scores.length }})</h4>
|
<h4>Suspicious Scores ({{ this.userInfo.suspicious_scores.length }})</h4>
|
||||||
<div class="table">
|
<div class="table">
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import {SimilarReplay, SuspiciousScore} from "../replays";
|
|||||||
import { HttpClient } from "@angular/common/http";
|
import { HttpClient } from "@angular/common/http";
|
||||||
import {catchError, EMPTY, finalize, Observable, Subscription} from "rxjs";
|
import {catchError, EMPTY, finalize, Observable, Subscription} from "rxjs";
|
||||||
import {environment} from "../../environments/environment";
|
import {environment} from "../../environments/environment";
|
||||||
import {DatePipe, DecimalPipe, JsonPipe, Location, NgForOf, NgIf, NgOptimizedImage} from "@angular/common";
|
import {DatePipe, DecimalPipe, JsonPipe, NgForOf, NgIf, NgOptimizedImage} from "@angular/common";
|
||||||
import {ActivatedRoute, RouterLink} from "@angular/router";
|
import {ActivatedRoute, RouterLink} from "@angular/router";
|
||||||
import {UserDetails, UserQueueDetails} from "../userDetails";
|
import {UserDetails, UserQueueDetails} from "../userDetails";
|
||||||
import {calculateTimeAgo, countryCodeToFlag, formatDuration} from "../format";
|
import {calculateTimeAgo, countryCodeToFlag, formatDuration} from "../format";
|
||||||
@ -14,7 +14,6 @@ import {CuteLoadingComponent} from "../../corelib/components/cute-loading/cute-l
|
|||||||
import {FilterManagerService} from "../filter-manager.service";
|
import {FilterManagerService} from "../filter-manager.service";
|
||||||
import {UserService} from "../../corelib/service/user.service";
|
import {UserService} from "../../corelib/service/user.service";
|
||||||
import {FollowService} from "../../corelib/service/follow.service";
|
import {FollowService} from "../../corelib/service/follow.service";
|
||||||
import {TextReportService} from '../text-report.service';
|
|
||||||
|
|
||||||
interface UserInfo {
|
interface UserInfo {
|
||||||
user_details: UserDetails;
|
user_details: UserDetails;
|
||||||
@ -69,7 +68,6 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private title: Title,
|
private title: Title,
|
||||||
private rxStompService: RxStompService,
|
private rxStompService: RxStompService,
|
||||||
private location: Location,
|
|
||||||
public userService: UserService,
|
public userService: UserService,
|
||||||
public followService: FollowService
|
public followService: FollowService
|
||||||
) { }
|
) { }
|
||||||
@ -137,8 +135,6 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.subscribeToUser();
|
this.subscribeToUser();
|
||||||
}
|
}
|
||||||
this.checkIfUserIsFollowed();
|
this.checkIfUserIsFollowed();
|
||||||
|
|
||||||
this.location.replaceState(`u/${this.userInfo.user_details.user_id}`);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -197,20 +193,6 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async copyReportToClipboard(): Promise<void> {
|
|
||||||
if (!this.userInfo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const report = TextReportService.generateTextReportForUserScores(
|
|
||||||
this.userInfo.user_details,
|
|
||||||
this.userInfo.suspicious_scores,
|
|
||||||
this.userInfo.similar_replays,
|
|
||||||
);
|
|
||||||
|
|
||||||
await navigator.clipboard.writeText(report);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly formatDuration = formatDuration;
|
protected readonly formatDuration = formatDuration;
|
||||||
protected readonly countryCodeToFlag = countryCodeToFlag;
|
protected readonly countryCodeToFlag = countryCodeToFlag;
|
||||||
protected readonly calculateTimeAgo = calculateTimeAgo;
|
protected readonly calculateTimeAgo = calculateTimeAgo;
|
||||||
|
|||||||
2
nise-infra/.gitignore
vendored
2
nise-infra/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
*.env
|
|
||||||
*.sql
|
|
||||||
@ -1,165 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
|
|
||||||
nginx-main:
|
|
||||||
image: nginx:latest
|
|
||||||
container_name: nginx-main
|
|
||||||
restart: always
|
|
||||||
volumes:
|
|
||||||
- ./nginx-main.conf:/etc/nginx/nginx.conf:ro
|
|
||||||
# nise.moe certificates (by Cloudflare)
|
|
||||||
- ./nise-data/certificate.pem:/etc/ssl/certs/nisemoe/certificate.pem:ro
|
|
||||||
- ./nise-data/private.key:/etc/ssl/certs/nisemoe/private.key:ro
|
|
||||||
ports:
|
|
||||||
- "443:443"
|
|
||||||
- "80:80"
|
|
||||||
|
|
||||||
# Shared services which are used by others
|
|
||||||
postgres:
|
|
||||||
image: groonga/pgroonga:3.1.6-alpine-15
|
|
||||||
container_name: postgres
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: ${DB_USER}
|
|
||||||
POSTGRES_PASSWORD: ${DB_PASS}
|
|
||||||
volumes:
|
|
||||||
- postgres-data:/var/lib/postgresql/data
|
|
||||||
command: >
|
|
||||||
-c shared_buffers=6GB
|
|
||||||
-c effective_cache_size=12GB
|
|
||||||
-c work_mem=64MB
|
|
||||||
-c maintenance_work_mem=2GB
|
|
||||||
-c checkpoint_completion_target=0.9
|
|
||||||
-c checkpoint_timeout=15min
|
|
||||||
-c max_wal_size=2GB
|
|
||||||
-c wal_buffers=16MB
|
|
||||||
-c max_connections=100
|
|
||||||
-c max_worker_processes=8
|
|
||||||
-c max_parallel_workers_per_gather=4
|
|
||||||
-c max_parallel_workers=8
|
|
||||||
-c effective_io_concurrency=40
|
|
||||||
shm_size: '128mb'
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:alpine
|
|
||||||
container_name: redis
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
|
|
||||||
gitea:
|
|
||||||
image: gitea/gitea
|
|
||||||
container_name: gitea
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
USER_UID: 1336
|
|
||||||
USER_GID: 1336
|
|
||||||
GITEA__database__DB_TYPE: postgres
|
|
||||||
GITEA__database__HOST: ${DB_HOST}:5432
|
|
||||||
GITEA__database__NAME: gitea
|
|
||||||
GITEA__database__USER: ${DB_USER}
|
|
||||||
GITEA__database__PASSWD: ${DB_PASS}
|
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
- redis
|
|
||||||
volumes:
|
|
||||||
- ./gitea-data/app.ini:/data/gitea/conf/app.ini
|
|
||||||
- gitea-data:/data
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
|
|
||||||
nise-nginx:
|
|
||||||
image: nginx:latest
|
|
||||||
container_name: nise-nginx
|
|
||||||
restart: always
|
|
||||||
volumes:
|
|
||||||
- ./nise-data/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
||||||
|
|
||||||
nise-backend:
|
|
||||||
image: git.nise.moe/nuff/nise-backend:latest
|
|
||||||
container_name: nise-backend
|
|
||||||
environment:
|
|
||||||
SPRING_PROFILES_ACTIVE: postgres,discord,import:scores,import:users,fix:scores
|
|
||||||
# App configuration
|
|
||||||
OLD_SCORES_PAGE_SIZE: 1000
|
|
||||||
# Postgres
|
|
||||||
POSTGRES_HOST: ${DB_HOST}
|
|
||||||
POSTGRES_USER: ${DB_USER}
|
|
||||||
POSTGRES_PASS: ${DB_PASS}
|
|
||||||
POSTGRES_DB: nise
|
|
||||||
# redis
|
|
||||||
REDIS_DB: 4
|
|
||||||
# Discord
|
|
||||||
WEBHOOK_URL: ${WEBHOOK_URL}
|
|
||||||
SCORES_WEBHOOK_URL: ${SCORES_WEBHOOK_URL}
|
|
||||||
# osu!api
|
|
||||||
OSU_API_KEY: ${OSU_API_KEY}
|
|
||||||
OSU_CLIENT_ID: ${OSU_CLIENT_ID}
|
|
||||||
OSU_CLIENT_SECRET: ${OSU_CLIENT_SECRET}
|
|
||||||
OSU_CALLBACK: "https://nise.moe/api/login/oauth2/code/osu"
|
|
||||||
# Metabase
|
|
||||||
METABASE_API_KEY: ${METABASE_API_KEY}
|
|
||||||
# Internal API
|
|
||||||
CIRCLEGUARD_API_URL: http://nise-circleguard:5000
|
|
||||||
# Auth
|
|
||||||
ORIGIN: "https://nise.moe"
|
|
||||||
REPLAY_ORIGIN: "https://replay.nise.moe"
|
|
||||||
COOKIE_SECURE: false
|
|
||||||
BEATMAPS_PATH: "/app/dbs"
|
|
||||||
restart: always
|
|
||||||
volumes:
|
|
||||||
- ./nise-data/beatmaps:/app/dbs
|
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
- redis
|
|
||||||
|
|
||||||
nise-circleguard:
|
|
||||||
image: git.nise.moe/nuff/nise-circleguard:latest
|
|
||||||
container_name: nise-circleguard
|
|
||||||
environment:
|
|
||||||
OSU_API_KEY: ${OSU_API_KEY}
|
|
||||||
restart: always
|
|
||||||
volumes:
|
|
||||||
- ./nise-data/beatmaps:/app/dbs
|
|
||||||
|
|
||||||
nise-frontend2:
|
|
||||||
image: git.nise.moe/nuff/nise-frontend:latest
|
|
||||||
container_name: nise-frontend2
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
nise-replay-viewer:
|
|
||||||
image: git.nise.moe/nuff/nise-replay-viewer:latest
|
|
||||||
container_name: nise-replay-viewer
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
nise-discord:
|
|
||||||
image: git.nise.moe/nuff/nise-discord:latest
|
|
||||||
container_name: nise-discord
|
|
||||||
environment:
|
|
||||||
DISCORD_TOKEN: ${DISCORD_TOKEN}
|
|
||||||
REACTION_CHANNEL_ID: ${REACTION_CHANNEL_ID}
|
|
||||||
REACTION_EMOJI_ID: ${REACTION_EMOJI_ID}
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
nise-metabase:
|
|
||||||
image: metabase/metabase:latest
|
|
||||||
container_name: nise-metabase
|
|
||||||
volumes:
|
|
||||||
- /dev/urandom:/dev/random:ro
|
|
||||||
environment:
|
|
||||||
MB_DB_TYPE: postgres
|
|
||||||
MB_DB_DBNAME: metabase
|
|
||||||
MB_DB_PORT: 5432
|
|
||||||
MB_DB_USER: ${DB_METABASE_USER}
|
|
||||||
MB_DB_PASS: ${DB_METABASE_PASS}
|
|
||||||
MB_DB_HOST: postgres
|
|
||||||
healthcheck:
|
|
||||||
test: curl --fail -I http://localhost:3000/api/health || exit 1
|
|
||||||
interval: 15s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres-data:
|
|
||||||
gitea-data:
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
events {}
|
|
||||||
|
|
||||||
http {
|
|
||||||
|
|
||||||
upstream nise-nginx {
|
|
||||||
server nise-nginx:80;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Redirect HTTP to HTTPS
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name nise.moe replay.nise.moe neko.nise.moe;
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name nise.moe replay.nise.moe git.nise.moe neko.nise.moe;
|
|
||||||
|
|
||||||
ssl_certificate /etc/ssl/certs/nisemoe/certificate.pem;
|
|
||||||
ssl_certificate_key /etc/ssl/certs/nisemoe/private.key;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
# For git.nise.moe
|
|
||||||
client_max_body_size 512M;
|
|
||||||
|
|
||||||
proxy_pass http://nise-nginx;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
|
|
||||||
# Websockets
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
events {}
|
|
||||||
|
|
||||||
http {
|
|
||||||
|
|
||||||
upstream gitea {
|
|
||||||
server gitea:3000;
|
|
||||||
}
|
|
||||||
|
|
||||||
upstream nise-frontend {
|
|
||||||
server nise-frontend2:80;
|
|
||||||
}
|
|
||||||
|
|
||||||
upstream nise-replay-viewer {
|
|
||||||
server nise-replay-viewer:80;
|
|
||||||
}
|
|
||||||
|
|
||||||
upstream nise-backend {
|
|
||||||
server nise-backend:8080;
|
|
||||||
}
|
|
||||||
|
|
||||||
upstream nise-metabase {
|
|
||||||
server nise-metabase:3000;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name nise.moe;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://nise-frontend;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /api/ {
|
|
||||||
proxy_pass http://nise-backend/;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
|
|
||||||
# Websockets
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
|
|
||||||
server_name git.nise.moe;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
client_max_body_size 10G;
|
|
||||||
proxy_pass http://gitea/;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name replay.nise.moe;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://nise-replay-viewer/;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name neko.nise.moe;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://nise-metabase/;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -15,8 +15,9 @@ the website is split into the following modules, each with their own folder and
|
|||||||
| nise-backend | The main module, serves an API and processes new scores. | Kotlin, Spring Boot |
|
| nise-backend | The main module, serves an API and processes new scores. | Kotlin, Spring Boot |
|
||||||
| nise-frontend | The frontend module (Angular), uses the API to display data. | Angular |
|
| nise-frontend | The frontend module (Angular), uses the API to display data. | Angular |
|
||||||
| nise-circleguard | Written in Python, serves as an HTTP interface for circleguard. | Python |
|
| nise-circleguard | Written in Python, serves as an HTTP interface for circleguard. | Python |
|
||||||
|
| konata | Sub-module to detect stolen replays with multithreading support. | Kotlin |
|
||||||
|
| mari | Sub-module to handle replay files and judgement data | Kotlin |
|
||||||
| nise-discord | Module that runs a Discord bot for role management | Python |
|
| nise-discord | Module that runs a Discord bot for role management | Python |
|
||||||
| nise-infra | Docker (compose) configuration for production deployment | Docker |
|
|
||||||
| nise-replay-viewer | Standalone react-based website that plays osu!std replays in your browser | React/p5.js |
|
| nise-replay-viewer | Standalone react-based website that plays osu!std replays in your browser | React/p5.js |
|
||||||
|
|
||||||
# how to run
|
# how to run
|
||||||
@ -27,7 +28,7 @@ you can read the individual readme files for each module to see how to run them
|
|||||||
|
|
||||||
### production
|
### production
|
||||||
|
|
||||||
we manage the production stack in the `nise-infra` folder. it uses docker-compose to handle the containers, and nginx as a reverse proxy.
|
we manage the production stack in a separate `infra` repository. it uses docker-compose to handle the containers, and traefik as a reverse proxy.
|
||||||
|
|
||||||
# contributing
|
# contributing
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
rsync -avz --exclude '.git' --exclude '.env' --exclude 'nise-data/beatmaps/*' --exclude 'nise-data/private.key' --exclude 'nise-data/certificate.pem' --exclude 'gitea-data' nise-moe:/root/Lab/ ./nise-infra
|
|
||||||
Loading…
Reference in New Issue
Block a user