mirror of
https://github.com/immich-app/yucca.git
synced 2026-09-30 13:33:00 +08:00
feat: use .well-known/yucca.json to discover backend (#126)
This commit is contained in:
@@ -7,7 +7,6 @@ import {
|
|||||||
RepositoryUpdateResponseDto,
|
RepositoryUpdateResponseDto,
|
||||||
} from '@futo-org/backups-api-client';
|
} from '@futo-org/backups-api-client';
|
||||||
import { BackendType } from '../enum';
|
import { BackendType } from '../enum';
|
||||||
import { ModuleConfig } from '../moduleConfig';
|
|
||||||
import { BackendConfiguration } from '../schema/tables/backend.table';
|
import { BackendConfiguration } from '../schema/tables/backend.table';
|
||||||
|
|
||||||
export abstract class Backend {
|
export abstract class Backend {
|
||||||
@@ -30,13 +29,10 @@ export abstract class Backend {
|
|||||||
abstract submitMetricRepositorySize(id: string, size: number): Promise<void>;
|
abstract submitMetricRepositorySize(id: string, size: number): Promise<void>;
|
||||||
abstract submitStructuredLog(summary: string, data: object): void;
|
abstract submitStructuredLog(summary: string, data: object): void;
|
||||||
|
|
||||||
static from(configuration: BackendConfiguration, moduleConfig: ModuleConfig) {
|
static from(configuration: BackendConfiguration) {
|
||||||
switch (configuration.type) {
|
switch (configuration.type) {
|
||||||
case BackendType.Yucca: {
|
case BackendType.Yucca: {
|
||||||
return new YuccaBackend({
|
return new YuccaBackend(configuration);
|
||||||
url: moduleConfig.yuccaProductionApi,
|
|
||||||
...configuration,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
case BackendType.Local: {
|
case BackendType.Local: {
|
||||||
return new LocalBackend(configuration);
|
return new LocalBackend(configuration);
|
||||||
|
|||||||
@@ -13,18 +13,49 @@ import {
|
|||||||
submitStructuredLog,
|
submitStructuredLog,
|
||||||
updateRepository,
|
updateRepository,
|
||||||
} from '@futo-org/backups-api-client';
|
} from '@futo-org/backups-api-client';
|
||||||
|
import { YUCCA_WELL_KNOWN } from '../const';
|
||||||
import { BackendType, CookieName } from '../enum';
|
import { BackendType, CookieName } from '../enum';
|
||||||
import { BackendConfiguration } from '../schema/tables/backend.table';
|
import { BackendConfiguration } from '../schema/tables/backend.table';
|
||||||
import { Backend } from './backend';
|
import { Backend } from './backend';
|
||||||
|
|
||||||
|
type WellKnown = {
|
||||||
|
backends: Record<string, { displayName: string; api: string }>;
|
||||||
|
defaultBackend: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
class YuccaWellKnown {
|
||||||
|
private data?: WellKnown;
|
||||||
|
|
||||||
|
async get() {
|
||||||
|
this.data ??= await fetch(YUCCA_WELL_KNOWN).then((response) => response.json());
|
||||||
|
return this.data! as WellKnown;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBaseUrlById(id: string) {
|
||||||
|
const { backends } = await this.get();
|
||||||
|
return backends[id].api;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBaseUrl() {
|
||||||
|
const { backends, defaultBackend } = await this.get();
|
||||||
|
return backends[defaultBackend].api;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const yuccaWellKnown = new YuccaWellKnown();
|
||||||
|
|
||||||
export class YuccaBackend extends Backend {
|
export class YuccaBackend extends Backend {
|
||||||
constructor(protected readonly configuration: BackendConfiguration & { type: BackendType.Yucca; url: string }) {
|
constructor(protected readonly configuration: BackendConfiguration & { type: BackendType.Yucca; url?: string }) {
|
||||||
super(configuration);
|
super(configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get requestOptions() {
|
private async getRequestOptions() {
|
||||||
return {
|
return {
|
||||||
baseUrl: this.configuration.url,
|
baseUrl:
|
||||||
|
this.configuration.url ??
|
||||||
|
(this.configuration.uuid
|
||||||
|
? await yuccaWellKnown.getBaseUrlById(this.configuration.uuid)
|
||||||
|
: await yuccaWellKnown.getBaseUrl()),
|
||||||
headers: {
|
headers: {
|
||||||
cookie: `${CookieName.YuccaAccessToken}=${this.configuration.accessToken}`,
|
cookie: `${CookieName.YuccaAccessToken}=${this.configuration.accessToken}`,
|
||||||
},
|
},
|
||||||
@@ -40,60 +71,56 @@ export class YuccaBackend extends Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkOnline() {
|
async checkOnline() {
|
||||||
await getAuth(this.requestOptions);
|
await getAuth(await this.getRequestOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
createRepository(dto: RepositoryCreateRequestDto) {
|
async createRepository(dto: RepositoryCreateRequestDto) {
|
||||||
return createRepository(dto, this.requestOptions);
|
return await createRepository(dto, await this.getRequestOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRepository(id: string, dto: RepositoryUpdateRequestDto) {
|
async updateRepository(id: string, dto: RepositoryUpdateRequestDto) {
|
||||||
return updateRepository(id, dto, this.requestOptions);
|
return await updateRepository(id, dto, await this.getRequestOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
getRepository(id: string) {
|
async getRepository(id: string) {
|
||||||
return getRepository(id, this.requestOptions);
|
return await getRepository(id, await this.getRequestOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
getRepositories() {
|
async getRepositories() {
|
||||||
return getRepositories(this.requestOptions);
|
return await getRepositories(await this.getRequestOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRepository(id: string) {
|
async deleteRepository(id: string) {
|
||||||
return deleteRepository(id, this.requestOptions);
|
return await deleteRepository(id, await this.getRequestOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
async getResticEndpoint(id: string) {
|
async getResticEndpoint(id: string) {
|
||||||
const { url } = await createResticUrl(id, this.requestOptions);
|
const { url } = await createResticUrl(id, await this.getRequestOptions());
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
submitMetricBackupStart(id: string): Promise<void> {
|
async submitMetricBackupStart(id: string): Promise<void> {
|
||||||
return submitMetricBackupStart(id, this.requestOptions);
|
return await submitMetricBackupStart(id, await this.getRequestOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
submitMetricBackupEnd(id: string, success: boolean, durationMs: number): Promise<void> {
|
async submitMetricBackupEnd(id: string, success: boolean, durationMs: number): Promise<void> {
|
||||||
return submitMetricBackupEnd(
|
return await submitMetricBackupEnd(
|
||||||
id,
|
id,
|
||||||
{
|
{
|
||||||
durationMs,
|
durationMs,
|
||||||
success,
|
success,
|
||||||
},
|
},
|
||||||
this.requestOptions,
|
await this.getRequestOptions(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
submitMetricRepositorySize(id: string, sizeBytes: number): Promise<void> {
|
async submitMetricRepositorySize(id: string, sizeBytes: number): Promise<void> {
|
||||||
return submitMetricRepositorySize(id, { sizeBytes }, this.requestOptions);
|
return await submitMetricRepositorySize(id, { sizeBytes }, await this.getRequestOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
submitStructuredLog(summary: string, data: object) {
|
submitStructuredLog(summary: string, data: object) {
|
||||||
void submitStructuredLog(
|
void this.getRequestOptions()
|
||||||
{
|
.then((requestOptions) => submitStructuredLog({ summary, data }, requestOptions))
|
||||||
summary,
|
.catch((error) => console.error('Failed to submit log', error));
|
||||||
data,
|
|
||||||
},
|
|
||||||
this.requestOptions,
|
|
||||||
).catch((error) => console.error('Failed to submit log', error));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export const ORCHESTRATION_PORT = 22_676;
|
export const ORCHESTRATION_PORT = 22_676;
|
||||||
export const YUCCA_PRODUCTION_UUID = 'd0368cdd-39ae-40e1-91d6-d81815c65c7e';
|
export const REPOSITORY_DEFAULT_CLOUD_UUID = 'd0368cdd-39ae-40e1-91d6-d81815c65c7e';
|
||||||
|
export const YUCCA_WELL_KNOWN = 'https://futo.cloud/.well-known/yucca.json';
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export type ImmichIntegration = {
|
|||||||
|
|
||||||
export type ModuleConfig = {
|
export type ModuleConfig = {
|
||||||
statePath: string;
|
statePath: string;
|
||||||
yuccaProductionApi: string;
|
yuccaProductionApi?: string;
|
||||||
externalBaseUrl?: string;
|
externalBaseUrl?: string;
|
||||||
requireWsAuth?: boolean;
|
requireWsAuth?: boolean;
|
||||||
requireLock?: boolean;
|
requireLock?: boolean;
|
||||||
|
|||||||
@@ -95,13 +95,9 @@ export const services = [
|
|||||||
|
|
||||||
@Module({})
|
@Module({})
|
||||||
export class OrchestrationApiModule {
|
export class OrchestrationApiModule {
|
||||||
static forRoot(config: Partial<ModuleConfig> & Pick<ModuleConfig, 'yuccaProductionApi'>): DynamicModule {
|
static forRoot(config: Partial<ModuleConfig>): DynamicModule {
|
||||||
config.statePath ??= resolve(homedir(), '.yucca');
|
config.statePath ??= resolve(homedir(), '.yucca');
|
||||||
|
|
||||||
if (!config.yuccaProductionApi) {
|
|
||||||
throw new Error('config.yuccaProductionApi is missing');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
module: OrchestrationApiModule,
|
module: OrchestrationApiModule,
|
||||||
imports: [
|
imports: [
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Kysely } from 'kysely';
|
import { Kysely } from 'kysely';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
|
import { Backend } from '../backends/backend';
|
||||||
import { DB } from '../schema';
|
import { DB } from '../schema';
|
||||||
import { BackendConfiguration } from '../schema/tables/backend.table';
|
import { BackendConfiguration } from '../schema/tables/backend.table';
|
||||||
|
|
||||||
@@ -22,10 +23,12 @@ export class BackendRepository {
|
|||||||
|
|
||||||
async getBackends() {
|
async getBackends() {
|
||||||
const backends = await this.db.selectFrom('backends').selectAll('backends').execute();
|
const backends = await this.db.selectFrom('backends').selectAll('backends').execute();
|
||||||
return backends.map(({ id, configuration }) => ({
|
return backends
|
||||||
id,
|
.map(({ id, configuration }) => ({
|
||||||
configuration: JSON.parse(configuration) as BackendConfiguration,
|
id,
|
||||||
}));
|
configuration: JSON.parse(configuration) as BackendConfiguration,
|
||||||
|
}))
|
||||||
|
.map(({ id, configuration }) => ({ id, configuration, backend: Backend.from(configuration) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBackend(id: string) {
|
async getBackend(id: string) {
|
||||||
@@ -35,9 +38,12 @@ export class BackendRepository {
|
|||||||
.where('id', '=', id)
|
.where('id', '=', id)
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
|
const configuration = JSON.parse(backend.configuration) as BackendConfiguration;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: backend.id,
|
id: backend.id,
|
||||||
configuration: JSON.parse(backend.configuration) as BackendConfiguration,
|
configuration,
|
||||||
|
backend: Backend.from(configuration),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export type BackendConfiguration =
|
|||||||
*/
|
*/
|
||||||
type: BackendType.Yucca;
|
type: BackendType.Yucca;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
uuid?: string;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable, InternalServerErrorException } from '@nestjs/common';
|
import { Injectable, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { createEventSource, EventSourceClient } from 'eventsource-client';
|
import { createEventSource, EventSourceClient } from 'eventsource-client';
|
||||||
import { YUCCA_PRODUCTION_UUID } from '../const';
|
import { yuccaWellKnown } from '../backends/yucca.backend';
|
||||||
|
import { REPOSITORY_DEFAULT_CLOUD_UUID } from '../const';
|
||||||
import { BackendType } from '../enum';
|
import { BackendType } from '../enum';
|
||||||
import { EventsGateway } from '../events/events.gateway';
|
import { EventsGateway } from '../events/events.gateway';
|
||||||
import { BackendRepository } from '../repositories/backend.repository';
|
import { BackendRepository } from '../repositories/backend.repository';
|
||||||
@@ -18,25 +19,26 @@ export class AuthService {
|
|||||||
readonly telemetry: TelemetryService,
|
readonly telemetry: TelemetryService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private async waitForDeviceFlow(events: EventSourceClient) {
|
private async waitForDeviceFlow(events: EventSourceClient, url?: string) {
|
||||||
for await (const { data } of events) {
|
for await (const { data } of events) {
|
||||||
const { type, accessToken } = JSON.parse(data);
|
const { type, accessToken } = JSON.parse(data);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'SUCCESS': {
|
case 'SUCCESS': {
|
||||||
await this.backend.updateBackend(YUCCA_PRODUCTION_UUID, {
|
await this.backend.updateBackend(REPOSITORY_DEFAULT_CLOUD_UUID, {
|
||||||
type: BackendType.Yucca,
|
type: BackendType.Yucca,
|
||||||
accessToken,
|
accessToken,
|
||||||
|
url,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.telemetry.submitStructuredLog('Connected FUTO Backups backend', {
|
this.telemetry.submitStructuredLog('Connected FUTO Backups backend', {
|
||||||
backendId: YUCCA_PRODUCTION_UUID,
|
backendId: REPOSITORY_DEFAULT_CLOUD_UUID,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.events.publish({
|
this.events.publish({
|
||||||
type: 'BackendCreate',
|
type: 'BackendCreate',
|
||||||
backend: {
|
backend: {
|
||||||
id: YUCCA_PRODUCTION_UUID,
|
id: REPOSITORY_DEFAULT_CLOUD_UUID,
|
||||||
type: BackendType.Yucca,
|
type: BackendType.Yucca,
|
||||||
description: 'FUTO Backups',
|
description: 'FUTO Backups',
|
||||||
isOnline: true,
|
isOnline: true,
|
||||||
@@ -61,15 +63,18 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async oidcDeviceFlow(): Promise<{ userCode: string; verificationUri: string }> {
|
async oidcDeviceFlow(): Promise<{ userCode: string; verificationUri: string }> {
|
||||||
|
const overrideEndpoint = this.moduleConfig.get().yuccaProductionApi;
|
||||||
|
const endpoint = overrideEndpoint ?? (await yuccaWellKnown.getBaseUrl());
|
||||||
|
|
||||||
const events: EventSourceClient = createEventSource({
|
const events: EventSourceClient = createEventSource({
|
||||||
url: new URL('/api/auth/oidc/device', this.moduleConfig.get().yuccaProductionApi),
|
url: new URL('/api/auth/oidc/device', endpoint),
|
||||||
onDisconnect: () => events.close(),
|
onDisconnect: () => events.close(),
|
||||||
});
|
});
|
||||||
|
|
||||||
for await (const { data } of events) {
|
for await (const { data } of events) {
|
||||||
const { userCode, verificationUri } = JSON.parse(data);
|
const { userCode, verificationUri } = JSON.parse(data);
|
||||||
|
|
||||||
void this.waitForDeviceFlow(events).catch(() => {});
|
void this.waitForDeviceFlow(events, overrideEndpoint).catch(() => {});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userCode,
|
userCode,
|
||||||
|
|||||||
@@ -5,13 +5,11 @@ import { BackendResponseDto, BackendsResponseDto, CreateLocalBackendRequestDto }
|
|||||||
import { BackendType } from '../enum';
|
import { BackendType } from '../enum';
|
||||||
import { EventsGateway } from '../events/events.gateway';
|
import { EventsGateway } from '../events/events.gateway';
|
||||||
import { BackendRepository } from '../repositories/backend.repository';
|
import { BackendRepository } from '../repositories/backend.repository';
|
||||||
import { ModuleConfigRepository } from '../repositories/moduleConfig.repository';
|
|
||||||
import { TelemetryService } from './telemetry.service';
|
import { TelemetryService } from './telemetry.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BackendService {
|
export class BackendService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly moduleConfig: ModuleConfigRepository,
|
|
||||||
private readonly repository: BackendRepository,
|
private readonly repository: BackendRepository,
|
||||||
private readonly events: EventsGateway,
|
private readonly events: EventsGateway,
|
||||||
private readonly telemetry: TelemetryService,
|
private readonly telemetry: TelemetryService,
|
||||||
@@ -22,7 +20,7 @@ export class BackendService {
|
|||||||
|
|
||||||
const error = await Promise.all(
|
const error = await Promise.all(
|
||||||
backends.map((backend) =>
|
backends.map((backend) =>
|
||||||
Backend.from(backend.configuration, this.moduleConfig.get())
|
Backend.from(backend.configuration)
|
||||||
.checkOnline()
|
.checkOnline()
|
||||||
.then(() => void 0)
|
.then(() => void 0)
|
||||||
.catch((error) => error),
|
.catch((error) => error),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { randomUUID } from 'node:crypto';
|
|||||||
import { type WriteStream } from 'node:fs';
|
import { type WriteStream } from 'node:fs';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { Backend } from '../backends/backend';
|
|
||||||
import { FilesystemListingRequestDto, FilesystemListingResponseDto } from '../dto/filesystem.dto';
|
import { FilesystemListingRequestDto, FilesystemListingResponseDto } from '../dto/filesystem.dto';
|
||||||
import {
|
import {
|
||||||
ListSnapshotsResponseDto,
|
ListSnapshotsResponseDto,
|
||||||
@@ -86,9 +85,7 @@ export class RepositoryService {
|
|||||||
backendId = backends[0].id;
|
backendId = backends[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { configuration } = await this.backend.getBackend(backendId);
|
const { backend, configuration } = await this.backend.getBackend(backendId);
|
||||||
const backend = Backend.from(configuration, this.moduleConfig.get());
|
|
||||||
|
|
||||||
const { repository: remote } = await backend.createRepository(dto);
|
const { repository: remote } = await backend.createRepository(dto);
|
||||||
const id = randomUUID();
|
const id = randomUUID();
|
||||||
|
|
||||||
@@ -144,8 +141,7 @@ export class RepositoryService {
|
|||||||
const backendsById = Object.fromEntries(backends.map((backend) => [backend.id, backend]));
|
const backendsById = Object.fromEntries(backends.map((backend) => [backend.id, backend]));
|
||||||
const remoteRepositories: Record<string, Record<string, RepositoryWithMetricsDto>> = {};
|
const remoteRepositories: Record<string, Record<string, RepositoryWithMetricsDto>> = {};
|
||||||
|
|
||||||
for (const { id: backendId, configuration } of backends) {
|
for (const { id: backendId, backend } of backends) {
|
||||||
const backend = Backend.from(configuration, this.moduleConfig.get());
|
|
||||||
remoteRepositories[backendId] = {};
|
remoteRepositories[backendId] = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -289,14 +285,13 @@ export class RepositoryService {
|
|||||||
remoteId = localRepository.remoteId;
|
remoteId = localRepository.remoteId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const backend = await this.backend.getBackend(backendId);
|
const { backend, configuration } = await this.backend.getBackend(backendId);
|
||||||
const backendInstance = Backend.from(backend.configuration, this.moduleConfig.get());
|
|
||||||
|
|
||||||
let remote;
|
let remote;
|
||||||
if (dto.name) {
|
if (dto.name) {
|
||||||
({ repository: remote } = await backendInstance.updateRepository(remoteId, dto));
|
({ repository: remote } = await backend.updateRepository(remoteId, dto));
|
||||||
} else {
|
} else {
|
||||||
({ repository: remote } = await backendInstance.getRepository(remoteId));
|
({ repository: remote } = await backend.getRepository(remoteId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto.paths) {
|
if (dto.paths) {
|
||||||
@@ -330,7 +325,7 @@ export class RepositoryService {
|
|||||||
backends: {
|
backends: {
|
||||||
primary: {
|
primary: {
|
||||||
id: backendId,
|
id: backendId,
|
||||||
type: backend.configuration.type,
|
type: configuration.type,
|
||||||
online: true,
|
online: true,
|
||||||
},
|
},
|
||||||
secondary: [],
|
secondary: [],
|
||||||
@@ -382,9 +377,8 @@ export class RepositoryService {
|
|||||||
({ backendId, remoteId } = repository);
|
({ backendId, remoteId } = repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
const backend = await this.backend.getBackend(backendId);
|
const { backend } = await this.backend.getBackend(backendId);
|
||||||
const backendInstance = Backend.from(backend.configuration, this.moduleConfig.get());
|
const endpoint = await backend.getResticEndpoint(remoteId);
|
||||||
const endpoint = await backendInstance.getResticEndpoint(remoteId);
|
|
||||||
|
|
||||||
const key = await this.config.deriveEncryptionKey(`repository-${remoteId}`);
|
const key = await this.config.deriveEncryptionKey(`repository-${remoteId}`);
|
||||||
|
|
||||||
@@ -424,8 +418,7 @@ export class RepositoryService {
|
|||||||
|
|
||||||
if (metrics.sizeBytes) {
|
if (metrics.sizeBytes) {
|
||||||
const { backendId, remoteId } = await this.repository.get(id);
|
const { backendId, remoteId } = await this.repository.get(id);
|
||||||
const { configuration } = await this.backend.getBackend(backendId);
|
const { backend } = await this.backend.getBackend(backendId);
|
||||||
const backend = Backend.from(configuration, this.moduleConfig.get());
|
|
||||||
|
|
||||||
if (backend.isMetricsCapable()) {
|
if (backend.isMetricsCapable()) {
|
||||||
await backend.submitMetricRepositorySize(remoteId, metrics.sizeBytes);
|
await backend.submitMetricRepositorySize(remoteId, metrics.sizeBytes);
|
||||||
@@ -458,9 +451,7 @@ export class RepositoryService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { backendId, remoteId } = await this.repository.get(id);
|
const { backendId, remoteId } = await this.repository.get(id);
|
||||||
const { configuration } = await this.backend.getBackend(backendId);
|
const { backend } = await this.backend.getBackend(backendId);
|
||||||
const backend = Backend.from(configuration, this.moduleConfig.get());
|
|
||||||
|
|
||||||
const { endpoint, key } = await this.getResticParameters(id);
|
const { endpoint, key } = await this.getResticParameters(id);
|
||||||
|
|
||||||
const paths = await this.repositoryPath.get(id);
|
const paths = await this.repositoryPath.get(id);
|
||||||
@@ -667,8 +658,7 @@ export class RepositoryService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { configuration } = await this.backend.getBackend(backendId);
|
const { configuration, backend } = await this.backend.getBackend(backendId);
|
||||||
const backend = Backend.from(configuration, this.moduleConfig.get());
|
|
||||||
const { repository: remote } = await backend.getRepository(id);
|
const { repository: remote } = await backend.getRepository(id);
|
||||||
const localId = randomUUID();
|
const localId = randomUUID();
|
||||||
|
|
||||||
@@ -734,8 +724,7 @@ export class RepositoryService {
|
|||||||
id: string,
|
id: string,
|
||||||
dto: RepositoryPrimaryBackendReconfigureRequestDto,
|
dto: RepositoryPrimaryBackendReconfigureRequestDto,
|
||||||
): Promise<RepositoryCreateResponseDto> {
|
): Promise<RepositoryCreateResponseDto> {
|
||||||
const { configuration } = await this.backend.getBackend(dto.backendId);
|
const { backend, configuration } = await this.backend.getBackend(dto.backendId);
|
||||||
const backend = Backend.from(configuration, this.moduleConfig.get());
|
|
||||||
|
|
||||||
const { repository: remote } = await backend.createRepository({
|
const { repository: remote } = await backend.createRepository({
|
||||||
name: 'Restored Repository',
|
name: 'Restored Repository',
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Backend } from '../backends/backend';
|
import { Backend } from '../backends/backend';
|
||||||
import { YUCCA_PRODUCTION_UUID } from '../const';
|
import { REPOSITORY_DEFAULT_CLOUD_UUID } from '../const';
|
||||||
import { BackendRepository } from '../repositories/backend.repository';
|
import { BackendRepository } from '../repositories/backend.repository';
|
||||||
import { ConfigRepository } from '../repositories/config.repository';
|
import { ConfigRepository } from '../repositories/config.repository';
|
||||||
import { ModuleConfigRepository } from '../repositories/moduleConfig.repository';
|
|
||||||
|
|
||||||
function serializeErrors(data: object): object {
|
function serializeErrors(data: object): object {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
@@ -22,7 +21,6 @@ export class TelemetryService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly config: ConfigRepository,
|
private readonly config: ConfigRepository,
|
||||||
private readonly backend: BackendRepository,
|
private readonly backend: BackendRepository,
|
||||||
private readonly moduleConfig: ModuleConfigRepository,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private async submitStructuredLogImpl(summary: string, data: object, force: boolean) {
|
private async submitStructuredLogImpl(summary: string, data: object, force: boolean) {
|
||||||
@@ -34,9 +32,9 @@ export class TelemetryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void this.backend
|
void this.backend
|
||||||
.getBackend(YUCCA_PRODUCTION_UUID)
|
.getBackend(REPOSITORY_DEFAULT_CLOUD_UUID)
|
||||||
.then(({ configuration }) => {
|
.then(({ configuration }) => {
|
||||||
const backend = Backend.from(configuration, this.moduleConfig.get());
|
const backend = Backend.from(configuration);
|
||||||
backend.submitStructuredLog(summary, {
|
backend.submitStructuredLog(summary, {
|
||||||
...serializeErrors(data),
|
...serializeErrors(data),
|
||||||
version,
|
version,
|
||||||
|
|||||||
@@ -21,4 +21,11 @@ records = {
|
|||||||
values = ["10.10.10.90", "10.10.10.91", "10.10.10.92"]
|
values = ["10.10.10.90", "10.10.10.91", "10.10.10.92"]
|
||||||
comment = "Sietch RGW S3 virtual-hosted buckets (tf/deployment/dev/dns)"
|
comment = "Sietch RGW S3 virtual-hosted buckets (tf/deployment/dev/dns)"
|
||||||
}
|
}
|
||||||
|
# move me to somewhere sensible!
|
||||||
|
"futo.cloud" = {
|
||||||
|
type = "A"
|
||||||
|
values = ["45.144.160.215"] # homelab
|
||||||
|
proxied = true
|
||||||
|
comment = "Website (just serving .well-known/yucca.json)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user