Merge remote-tracking branch 'origin/master' into 3214-feature-x-com-sendcoins-4-activate-email-and-event-actions-in-federation-modul

This commit is contained in:
clauspeterhuebner 2026-01-06 00:58:13 +01:00
commit ac56e04398
42 changed files with 580 additions and 389 deletions

View File

@ -1,7 +1,7 @@
name: gradido publish CI name: gradido publish CI
on: on:
push: pull_request:
branches: branches:
- master - master
@ -10,7 +10,7 @@ jobs:
# JOB: DOCKER BUILD PRODUCTION FRONTEND ###################################### # JOB: DOCKER BUILD PRODUCTION FRONTEND ######################################
############################################################################## ##############################################################################
build_production_frontend: build_production_frontend:
if: startsWith(github.event.head_commit.message, 'chore(release):') if: startsWith(github.event.pull_request.title, 'chore(release):')
name: Docker Build Production - Frontend name: Docker Build Production - Frontend
runs-on: ubuntu-latest runs-on: ubuntu-latest
#needs: [nothing] #needs: [nothing]

View File

@ -4,8 +4,26 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v2.7.4](https://github.com/gradido/gradido/compare/v2.7.3...v2.7.4)
- feat(workflow): update .env.dist in deploy and make install script more idempotent [`#3593`](https://github.com/gradido/gradido/pull/3593)
- feat(admin): load moderator names for contribution list [`#3599`](https://github.com/gradido/gradido/pull/3599)
- fix(other): email mime types [`#3601`](https://github.com/gradido/gradido/pull/3601)
- fix(database): use connection pool for drizzle orm [`#3600`](https://github.com/gradido/gradido/pull/3600)
- feat(admin): make deleted contributions better recognisable with Deuteranopie [`#3597`](https://github.com/gradido/gradido/pull/3597)
- fix(workflow): rewrite sort.sh as sortLocales.ts [`#3598`](https://github.com/gradido/gradido/pull/3598)
- refactor(database): add drizzleOrm, use it in openaiThreads [`#3595`](https://github.com/gradido/gradido/pull/3595)
- fix(other): fix biome config [`#3592`](https://github.com/gradido/gradido/pull/3592)
- feat(other): reduce github worker count [`#3591`](https://github.com/gradido/gradido/pull/3591)
- fix(other): removed phantom line, aligned env and compose [`#3570`](https://github.com/gradido/gradido/pull/3570)
- feat(dlt): migrate database transaction to dlt transactions [`#3571`](https://github.com/gradido/gradido/pull/3571)
- feat(dlt): add inspector as submodule, update nginx config to serve inspector and gradido node [`#3566`](https://github.com/gradido/gradido/pull/3566)
- chore(release): v2.7.3 [`#3590`](https://github.com/gradido/gradido/pull/3590)
#### [v2.7.3](https://github.com/gradido/gradido/compare/v2.7.2...v2.7.3) #### [v2.7.3](https://github.com/gradido/gradido/compare/v2.7.2...v2.7.3)
> 4 December 2025
- feat(admin): show user registered at in admin [`#3589`](https://github.com/gradido/gradido/pull/3589) - feat(admin): show user registered at in admin [`#3589`](https://github.com/gradido/gradido/pull/3589)
- feat(backend): 3573 feature introduce distributed semaphore base on redis [`#3580`](https://github.com/gradido/gradido/pull/3580) - feat(backend): 3573 feature introduce distributed semaphore base on redis [`#3580`](https://github.com/gradido/gradido/pull/3580)
- fix(workflow): make deployment install script more robust [`#3588`](https://github.com/gradido/gradido/pull/3588) - fix(workflow): make deployment install script more robust [`#3588`](https://github.com/gradido/gradido/pull/3588)

View File

@ -1,5 +1,3 @@
CONFIG_VERSION=$ADMIN_CONFIG_VERSION
COMMUNITY_HOST=$COMMUNITY_HOST COMMUNITY_HOST=$COMMUNITY_HOST
URL_PROTOCOL=$URL_PROTOCOL URL_PROTOCOL=$URL_PROTOCOL
WALLET_AUTH_PATH=$WALLET_AUTH_PATH WALLET_AUTH_PATH=$WALLET_AUTH_PATH

View File

@ -3,7 +3,7 @@
"description": "Administration Interface for Gradido", "description": "Administration Interface for Gradido",
"main": "index.js", "main": "index.js",
"author": "Gradido Academy - https://www.gradido.net", "author": "Gradido Academy - https://www.gradido.net",
"version": "2.7.3", "version": "2.7.4",
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -6,7 +6,7 @@
:icon="type === 'PageCreationConfirm' ? 'x' : 'eye-slash-fill'" :icon="type === 'PageCreationConfirm' ? 'x' : 'eye-slash-fill'"
aria-label="Help" aria-label="Help"
></b-icon> ></b-icon>
{{ $t('hide_details') }} {{ row.item.user.firstName }} {{ row.item.user.lastName }} {{ $t('hide_details') }}
</b-button> </b-button>
</b-card> </b-card>
</template> </template>

View File

@ -34,11 +34,20 @@
</BButton> </BButton>
</div> </div>
</template> </template>
<template #cell(name)="row">
<span v-if="row.item.user">
{{ row.item.user.firstName }} {{ row.item.user.lastName }}
<small v-if="row.item.user.alias">
<hr />
{{ row.item.user.alias }}
</small>
</span>
</template>
<template #cell(memo)="row"> <template #cell(memo)="row">
{{ row.value }} {{ row.value }}
<small v-if="row.item.updatedBy > 0"> <small v-if="isAddCommentToMemo(row.item)" class="no-select">
<hr /> <hr />
{{ $t('moderator.memo-modified') }} {{ getMemoComment(row.item) }}
</small> </small>
</template> </template>
<template #cell(editCreation)="row"> <template #cell(editCreation)="row">
@ -140,6 +149,7 @@
import RowDetails from '../RowDetails' import RowDetails from '../RowDetails'
import EditCreationFormular from '../EditCreationFormular' import EditCreationFormular from '../EditCreationFormular'
import ContributionMessagesList from '../ContributionMessages/ContributionMessagesList' import ContributionMessagesList from '../ContributionMessages/ContributionMessagesList'
import { useDateFormatter } from '@/composables/useDateFormatter'
const iconMap = { const iconMap = {
IN_PROGRESS: 'question-square', IN_PROGRESS: 'question-square',
@ -195,6 +205,7 @@ export default {
this.removeClipboardListener() this.removeClipboardListener()
}, },
methods: { methods: {
...useDateFormatter(),
myself(item) { myself(item) {
return item.userId === this.$store.state.moderator.id return item.userId === this.$store.state.moderator.id
}, },
@ -235,6 +246,36 @@ export default {
this.creationUserData = row.item this.creationUserData = row.item
} }
}, },
isAddCommentToMemo(item) {
return item.closedBy > 0 || item.moderatorId > 0 || item.updatedBy > 0
},
getMemoComment(item) {
let comment = ''
if (item.closedBy > 0) {
if (item.contributionStatus === 'CONFIRMED') {
comment = this.$t('contribution.confirmedBy', { name: item.closedByUserName })
} else if (item.contributionStatus === 'DENIED') {
comment = this.$t('contribution.deniedBy', { name: item.closedByUserName })
} else if (item.contributionStatus === 'DELETED') {
comment = this.$t('contribution.deletedBy', { name: item.closedByUserName })
}
}
if (item.updatedBy > 0) {
if (comment.length) {
comment += ' | '
}
comment += this.$t('moderator.memo-modified', { name: item.updatedByUserName })
}
if (item.moderatorId > 0) {
if (comment.length) {
comment += ' | '
}
comment += this.$t('contribution.createdBy', { name: item.moderatorUserName })
}
return comment
},
addClipboardListener() { addClipboardListener() {
document.addEventListener('copy', this.handleCopy) document.addEventListener('copy', this.handleCopy)
}, },

View File

@ -1,10 +1,14 @@
export const useDateFormatter = () => { export const useDateFormatter = () => {
const formatDateFromDateTime = (datetimeString) => { const formatDateFromDateTime = (datetimeString) => {
if (!datetimeString || !datetimeString?.includes('T')) return datetimeString if (!datetimeString || !datetimeString?.includes('T')) {
return datetimeString
}
return datetimeString.split('T')[0] return datetimeString.split('T')[0]
} }
const formatDateOrDash = (value) => (value ? new Date(value).toLocaleDateString() : '—')
return { return {
formatDateFromDateTime, formatDateFromDateTime,
formatDateOrDash,
} }
} }

View File

@ -19,19 +19,18 @@ query adminListContributions(
} }
amount amount
memo memo
createdAt closedAt
closedBy
closedByUserName
contributionDate contributionDate
confirmedAt createdAt
confirmedBy
updatedAt updatedAt
updatedBy updatedBy
updatedByUserName
contributionStatus contributionStatus
messagesCount messagesCount
deniedAt
deniedBy
deletedAt
deletedBy
moderatorId moderatorId
moderatorUserName
userId userId
resubmissionAt resubmissionAt
} }

View File

@ -16,6 +16,12 @@
"back": "zurück", "back": "zurück",
"change_user_role": "Nutzerrolle ändern", "change_user_role": "Nutzerrolle ändern",
"close": "Schließen", "close": "Schließen",
"contribution": {
"confirmedBy": "Bestätigt von {name}.",
"createdBy": "Erstellt von {name}.",
"deletedBy": "Gelöscht von {name}.",
"deniedBy": "Abgelehnt von {name}."
},
"contributionLink": { "contributionLink": {
"amount": "Betrag", "amount": "Betrag",
"changeSaved": "Änderungen gespeichert", "changeSaved": "Änderungen gespeichert",
@ -49,6 +55,7 @@
}, },
"contributions": { "contributions": {
"all": "Alle", "all": "Alle",
"closed": "Geschlossen",
"confirms": "Bestätigt", "confirms": "Bestätigt",
"deleted": "Gelöscht", "deleted": "Gelöscht",
"denied": "Abgelehnt", "denied": "Abgelehnt",
@ -166,7 +173,7 @@
"moderator": { "moderator": {
"history": "Die Daten wurden geändert. Dies sind die alten Daten.", "history": "Die Daten wurden geändert. Dies sind die alten Daten.",
"memo": "Text ändern", "memo": "Text ändern",
"memo-modified": "Text vom Moderator bearbeitet.", "memo-modified": "Text von {name} bearbeitet.",
"memo-tooltip": "Den Beitragstext bearbeiten", "memo-tooltip": "Den Beitragstext bearbeiten",
"message": "Nachricht", "message": "Nachricht",
"message-tooltip": "Nachricht an Benutzer schreiben", "message-tooltip": "Nachricht an Benutzer schreiben",

View File

@ -16,6 +16,12 @@
"back": "back", "back": "back",
"change_user_role": "Change user role", "change_user_role": "Change user role",
"close": "Close", "close": "Close",
"contribution": {
"confirmedBy": "Confirmed by {name}.",
"createdBy": "Created by {name}.",
"deletedBy": "Deleted by {name}.",
"deniedBy": "Rejected by {name}."
},
"contributionLink": { "contributionLink": {
"amount": "Amount", "amount": "Amount",
"changeSaved": "Changes saved", "changeSaved": "Changes saved",
@ -49,6 +55,7 @@
}, },
"contributions": { "contributions": {
"all": "All", "all": "All",
"closed": "Closed",
"confirms": "Confirmed", "confirms": "Confirmed",
"deleted": "Deleted", "deleted": "Deleted",
"denied": "Rejected", "denied": "Rejected",
@ -166,7 +173,7 @@
"moderator": { "moderator": {
"history": "The data has been changed. This is the old data.", "history": "The data has been changed. This is the old data.",
"memo": "Edit text", "memo": "Edit text",
"memo-modified": "Text edited by moderator", "memo-modified": "Text edited by {name}",
"memo-tooltip": "Edit the text of the contribution", "memo-tooltip": "Edit the text of the contribution",
"message": "Message", "message": "Message",
"message-tooltip": "Write message to user", "message-tooltip": "Write message to user",

View File

@ -108,6 +108,7 @@ import { confirmContribution } from '../graphql/confirmContribution'
import { denyContribution } from '../graphql/denyContribution' import { denyContribution } from '../graphql/denyContribution'
import { getContribution } from '../graphql/getContribution' import { getContribution } from '../graphql/getContribution'
import { useAppToast } from '@/composables/useToast' import { useAppToast } from '@/composables/useToast'
import { useDateFormatter } from '@/composables/useDateFormatter'
import CONFIG from '@/config' import CONFIG from '@/config'
const FILTER_TAB_MAP = [ const FILTER_TAB_MAP = [
@ -134,9 +135,10 @@ const query = ref('')
const noHashtag = ref(null) const noHashtag = ref(null)
const hideResubmissionModel = ref(true) const hideResubmissionModel = ref(true)
const formatDateOrDash = (value) => (value ? new Date(value).toLocaleDateString() : '—') const { formatDateOrDash } = useDateFormatter()
const baseFields = { const baseFields = {
name: { key: 'name', label: t('name'), class: 'no-select' },
firstName: { key: 'user.firstName', label: t('firstname'), class: 'no-select' }, firstName: { key: 'user.firstName', label: t('firstname'), class: 'no-select' },
lastName: { key: 'user.lastName', label: t('lastname'), class: 'no-select' }, lastName: { key: 'user.lastName', label: t('lastname'), class: 'no-select' },
amount: { key: 'amount', label: t('creation'), formatter: (value) => value + ' GDD' }, amount: { key: 'amount', label: t('creation'), formatter: (value) => value + ' GDD' },
@ -153,13 +155,12 @@ const baseFields = {
class: 'no-select', class: 'no-select',
formatter: formatDateOrDash, formatter: formatDateOrDash,
}, },
confirmedAt: { closedAt: {
key: 'confirmedAt', key: 'closedAt',
label: t('contributions.confirms'), label: t('contributions.closed'),
class: 'no-select', class: 'no-select',
formatter: formatDateOrDash, formatter: formatDateOrDash,
}, },
confirmedBy: { key: 'confirmedBy', label: t('moderator.moderator'), class: 'no-select' },
} }
const fields = computed( const fields = computed(
@ -169,70 +170,52 @@ const fields = computed(
[ [
{ key: 'bookmark', label: t('delete') }, { key: 'bookmark', label: t('delete') },
{ key: 'deny', label: t('deny') }, { key: 'deny', label: t('deny') },
baseFields.firstName, baseFields.name,
baseFields.lastName,
baseFields.amount, baseFields.amount,
baseFields.memo, baseFields.memo,
baseFields.contributionDate, baseFields.contributionDate,
{ key: 'moderatorId', label: t('moderator.moderator'), class: 'no-select' },
{ key: 'editCreation', label: t('details') }, { key: 'editCreation', label: t('details') },
{ key: 'confirm', label: t('save') }, { key: 'confirm', label: t('save') },
], ],
// confirmed contributions // confirmed contributions
[ [
baseFields.firstName, baseFields.name,
baseFields.lastName,
baseFields.amount, baseFields.amount,
baseFields.memo, baseFields.memo,
baseFields.contributionDate, baseFields.contributionDate,
baseFields.createdAt, baseFields.createdAt,
baseFields.confirmedAt, baseFields.closedAt,
baseFields.confirmedBy,
{ key: 'chatCreation', label: t('details') }, { key: 'chatCreation', label: t('details') },
], ],
// denied contributions // denied contributions
[ [
baseFields.firstName, baseFields.name,
baseFields.lastName,
baseFields.amount, baseFields.amount,
baseFields.memo, baseFields.memo,
baseFields.contributionDate, baseFields.contributionDate,
baseFields.createdAt, baseFields.createdAt,
{ baseFields.closedAt,
key: 'deniedAt',
label: t('contributions.denied'),
formatter: formatDateOrDash,
},
{ key: 'deniedBy', label: t('moderator.moderator') },
{ key: 'chatCreation', label: t('details') }, { key: 'chatCreation', label: t('details') },
], ],
// deleted contributions // deleted contributions
[ [
baseFields.firstName, baseFields.name,
baseFields.lastName,
baseFields.amount, baseFields.amount,
baseFields.memo, baseFields.memo,
baseFields.contributionDate, baseFields.contributionDate,
baseFields.createdAt, baseFields.createdAt,
{ baseFields.closedAt,
key: 'deletedAt',
label: t('contributions.deleted'),
formatter: formatDateOrDash,
},
{ key: 'deletedBy', label: t('moderator.moderator') },
{ key: 'chatCreation', label: t('details') }, { key: 'chatCreation', label: t('details') },
], ],
// all contributions // all contributions
[ [
{ key: 'contributionStatus', label: t('status') }, { key: 'contributionStatus', label: t('status') },
baseFields.firstName, baseFields.name,
baseFields.lastName,
baseFields.amount, baseFields.amount,
baseFields.memo, baseFields.memo,
baseFields.contributionDate, baseFields.contributionDate,
baseFields.createdAt, baseFields.createdAt,
baseFields.confirmedAt, baseFields.closedAt,
baseFields.confirmedBy,
{ key: 'chatCreation', label: t('details') }, { key: 'chatCreation', label: t('details') },
], ],
][tabIndex.value], ][tabIndex.value],

View File

@ -1,5 +1,3 @@
# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts
# Server # Server
JWT_SECRET=$JWT_SECRET JWT_SECRET=$JWT_SECRET
JWT_EXPIRES_IN=$JWT_EXPIRES_IN JWT_EXPIRES_IN=$JWT_EXPIRES_IN

View File

@ -1,6 +1,6 @@
{ {
"name": "backend", "name": "backend",
"version": "2.7.3", "version": "2.7.4",
"private": false, "private": false,
"description": "Gradido unified backend providing an API-Service for Gradido Transactions", "description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"repository": "https://github.com/gradido/gradido/backend", "repository": "https://github.com/gradido/gradido/backend",

View File

@ -1,6 +1,6 @@
import { ContributionStatus } from '@enum/ContributionStatus'
import { Contribution as DbContribution } from 'database' import { Contribution as DbContribution } from 'database'
import { Field, Int, ObjectType } from 'type-graphql' import { Field, Int, ObjectType } from 'type-graphql'
import { UnconfirmedContribution } from './UnconfirmedContribution' import { UnconfirmedContribution } from './UnconfirmedContribution'
@ObjectType() @ObjectType()
@ -8,6 +8,7 @@ export class Contribution extends UnconfirmedContribution {
constructor(dbContribution: DbContribution) { constructor(dbContribution: DbContribution) {
super(dbContribution) super(dbContribution)
this.createdAt = dbContribution.createdAt this.createdAt = dbContribution.createdAt
this.moderatorId = dbContribution.moderatorId
this.confirmedAt = dbContribution.confirmedAt this.confirmedAt = dbContribution.confirmedAt
this.confirmedBy = dbContribution.confirmedBy this.confirmedBy = dbContribution.confirmedBy
this.contributionDate = dbContribution.contributionDate this.contributionDate = dbContribution.contributionDate
@ -19,11 +20,36 @@ export class Contribution extends UnconfirmedContribution {
this.updatedAt = dbContribution.updatedAt this.updatedAt = dbContribution.updatedAt
this.updatedBy = dbContribution.updatedBy this.updatedBy = dbContribution.updatedBy
this.resubmissionAt = dbContribution.resubmissionAt this.resubmissionAt = dbContribution.resubmissionAt
if (ContributionStatus.CONFIRMED === dbContribution.contributionStatus) {
this.closedAt = dbContribution.confirmedAt
this.closedBy = dbContribution.confirmedBy
} else if (ContributionStatus.DELETED === dbContribution.contributionStatus) {
this.closedAt = dbContribution.deletedAt
this.closedBy = dbContribution.deletedBy
} else if (ContributionStatus.DENIED === dbContribution.contributionStatus) {
this.closedAt = dbContribution.deniedAt
this.closedBy = dbContribution.deniedBy
}
} }
@Field(() => Date, { nullable: true })
closedAt?: Date | null
@Field(() => Int, { nullable: true })
closedBy?: number | null
@Field(() => String, { nullable: true })
closedByUserName?: string | null
@Field(() => Date) @Field(() => Date)
createdAt: Date createdAt: Date
@Field(() => Int, { nullable: true })
moderatorId: number | null
@Field(() => String, { nullable: true })
moderatorUserName?: string | null
@Field(() => Date, { nullable: true }) @Field(() => Date, { nullable: true })
confirmedAt: Date | null confirmedAt: Date | null
@ -48,6 +74,9 @@ export class Contribution extends UnconfirmedContribution {
@Field(() => Int, { nullable: true }) @Field(() => Int, { nullable: true })
updatedBy: number | null updatedBy: number | null
@Field(() => String, { nullable: true })
updatedByUserName?: string | null
@Field(() => Date) @Field(() => Date)
contributionDate: Date contributionDate: Date

View File

@ -23,6 +23,7 @@ import {
Contribution as DbContribution, Contribution as DbContribution,
Transaction as DbTransaction, Transaction as DbTransaction,
User as DbUser, User as DbUser,
findUserNamesByIds,
getLastTransaction, getLastTransaction,
UserContact, UserContact,
} from 'database' } from 'database'
@ -348,6 +349,7 @@ export class ContributionResolver {
): Promise<ContributionListResult> { ): Promise<ContributionListResult> {
// Check if only count was requested (without contributionList) // Check if only count was requested (without contributionList)
const fields = Object.keys(extractGraphQLFields(info)) const fields = Object.keys(extractGraphQLFields(info))
// console.log(`fields: ${fields}`)
const countOnly: boolean = fields.includes('contributionCount') && fields.length === 1 const countOnly: boolean = fields.includes('contributionCount') && fields.length === 1
// check if related user was requested // check if related user was requested
const userRequested = const userRequested =
@ -370,8 +372,25 @@ export class ContributionResolver {
}, },
countOnly, countOnly,
) )
const result = new ContributionListResult(count, dbContributions)
return new ContributionListResult(count, dbContributions) const uniqueUserIds = new Set<number>()
const addIfExist = (userId?: number | null) => (userId ? uniqueUserIds.add(userId) : null)
for (const contribution of result.contributionList) {
addIfExist(contribution.updatedBy)
addIfExist(contribution.moderatorId)
addIfExist(contribution.closedBy)
}
const users = await findUserNamesByIds(Array.from(uniqueUserIds))
const getNameById = (userId?: number | null) => (userId ? (users.get(userId) ?? null) : null)
for (const contribution of result.contributionList) {
contribution.updatedByUserName = getNameById(contribution.updatedBy)
contribution.moderatorUserName = getNameById(contribution.moderatorId)
contribution.closedByUserName = getNameById(contribution.closedBy)
}
return result
} }
@Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION]) @Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION])

View File

@ -66,6 +66,10 @@ export const findContributions = async (
if (relations?.user) { if (relations?.user) {
qb.orWhere('user.first_name LIKE :firstName', { firstName: queryString }) qb.orWhere('user.first_name LIKE :firstName', { firstName: queryString })
.orWhere('user.last_name LIKE :lastName', { lastName: queryString }) .orWhere('user.last_name LIKE :lastName', { lastName: queryString })
.orWhere('user.alias LIKE :alias', { alias: queryString })
.orWhere("LOWER(CONCAT(user.first_name, ' ', user.last_name)) LIKE LOWER(:fullName)", {
fullName: queryString.toLowerCase(),
})
.orWhere('emailContact.email LIKE :emailContact', { emailContact: queryString }) .orWhere('emailContact.email LIKE :emailContact', { emailContact: queryString })
.orWhere({ memo: Like(queryString) }) .orWhere({ memo: Like(queryString) })
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "config-schema", "name": "config-schema",
"version": "2.7.3", "version": "2.7.4",
"description": "Gradido Config for validate config", "description": "Gradido Config for validate config",
"main": "./build/index.js", "main": "./build/index.js",
"types": "./src/index.ts", "types": "./src/index.ts",

View File

@ -1,6 +1,6 @@
{ {
"name": "core", "name": "core",
"version": "2.7.3", "version": "2.7.4",
"description": "Gradido Core Code, High-Level Shared Code, with dependencies on other modules", "description": "Gradido Core Code, High-Level Shared Code, with dependencies on other modules",
"main": "./build/index.js", "main": "./build/index.js",
"types": "./src/index.ts", "types": "./src/index.ts",

View File

@ -77,34 +77,46 @@ export const sendEmailTranslated = async ({
...receiver, ...receiver,
attachments: [ attachments: [
{ {
// filename: 'gradido-header.jpeg', filename: 'gradido-header.jpeg',
content: gradidoHeader, content: gradidoHeader,
cid: 'gradidoheader', cid: 'gradidoheader',
contentType: 'image/jpeg',
contentDisposition: 'inline',
}, },
{ {
// filename: 'facebook-icon.png', filename: 'facebook-icon.png',
content: facebookIcon, content: facebookIcon,
cid: 'facebookicon', cid: 'facebookicon',
contentType: 'image/png',
contentDisposition: 'inline',
}, },
{ {
// filename: 'telegram-icon.png', filename: 'telegram-icon.png',
content: telegramIcon, content: telegramIcon,
cid: 'telegramicon', cid: 'telegramicon',
contentType: 'image/png',
contentDisposition: 'inline',
}, },
{ {
// filename: 'twitter-icon.png', filename: 'twitter-icon.png',
content: twitterIcon, content: twitterIcon,
cid: 'twittericon', cid: 'twittericon',
contentType: 'image/png',
contentDisposition: 'inline',
}, },
{ {
// filename: 'youtube-icon.png', filename: 'youtube-icon.png',
content: youtubeIcon, content: youtubeIcon,
cid: 'youtubeicon', cid: 'youtubeicon',
contentType: 'image/png',
contentDisposition: 'inline',
}, },
{ {
// filename: 'chatbox-icon.png', filename: 'chatbox-icon.png',
content: chatboxIcon, content: chatboxIcon,
cid: 'chatboxicon', cid: 'chatboxicon',
contentType: 'image/png',
contentDisposition: 'inline',
}, },
], ],
}, },

View File

@ -1,5 +1,3 @@
CONFIG_VERSION=$DATABASE_CONFIG_VERSION
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3306 DB_PORT=3306
DB_USER=$DB_USER DB_USER=$DB_USER

View File

@ -25,7 +25,7 @@ const run = async (command: string) => {
host: ${CONFIG.DB_HOST} host: ${CONFIG.DB_HOST}
port: ${CONFIG.DB_PORT} port: ${CONFIG.DB_PORT}
user: ${CONFIG.DB_USER} user: ${CONFIG.DB_USER}
password: ${CONFIG.DB_PASSWORD.slice(-2)} password: last 2 character: ${CONFIG.DB_PASSWORD.slice(-2)}
database: ${CONFIG.DB_DATABASE}`, database: ${CONFIG.DB_DATABASE}`,
) )
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "database", "name": "database",
"version": "2.7.3", "version": "2.7.4",
"description": "Gradido Database Tool to execute database migrations", "description": "Gradido Database Tool to execute database migrations",
"main": "./build/index.js", "main": "./build/index.js",
"types": "./src/index.ts", "types": "./src/index.ts",

View File

@ -1,7 +1,7 @@
import { drizzle, MySql2Database } from 'drizzle-orm/mysql2' import { drizzle, MySql2Database } from 'drizzle-orm/mysql2'
import Redis from 'ioredis' import Redis from 'ioredis'
import { getLogger } from 'log4js' import { getLogger } from 'log4js'
import { Connection, createConnection } from 'mysql2/promise' import { Connection, createConnection, createPool, Pool } from 'mysql2/promise'
import { DataSource as DBDataSource, FileLogger } from 'typeorm' import { DataSource as DBDataSource, FileLogger } from 'typeorm'
import { latestDbVersion } from '.' import { latestDbVersion } from '.'
import { CONFIG } from './config' import { CONFIG } from './config'
@ -14,7 +14,7 @@ export class AppDatabase {
private static instance: AppDatabase private static instance: AppDatabase
private dataSource: DBDataSource | undefined private dataSource: DBDataSource | undefined
private drizzleDataSource: MySql2Database | undefined private drizzleDataSource: MySql2Database | undefined
private drizzleConnection: Connection | undefined private drizzlePool: Pool | undefined
private redisClient: Redis | undefined private redisClient: Redis | undefined
/** /**
@ -48,10 +48,10 @@ export class AppDatabase {
} }
public getDrizzleDataSource(): MySql2Database { public getDrizzleDataSource(): MySql2Database {
if (!this.drizzleDataSource) { if (!this.drizzlePool) {
throw new Error('Drizzle connection not initialized') throw new Error('Drizzle connection pool not initialized')
} }
return this.drizzleDataSource return drizzle({ client: this.drizzlePool })
} }
// create database connection, initialize with automatic retry and check for correct database version // create database connection, initialize with automatic retry and check for correct database version
@ -106,22 +106,25 @@ export class AppDatabase {
logger.info('Redis status=', this.redisClient.status) logger.info('Redis status=', this.redisClient.status)
if (!this.drizzleDataSource) { if (!this.drizzleDataSource) {
this.drizzleConnection = await createConnection({ this.drizzlePool = createPool({
host: CONFIG.DB_HOST, host: CONFIG.DB_HOST,
user: CONFIG.DB_USER, user: CONFIG.DB_USER,
password: CONFIG.DB_PASSWORD, password: CONFIG.DB_PASSWORD,
database: CONFIG.DB_DATABASE, database: CONFIG.DB_DATABASE,
port: CONFIG.DB_PORT, port: CONFIG.DB_PORT,
waitForConnections: true,
connectionLimit: 20,
queueLimit: 100,
enableKeepAlive: true,
keepAliveInitialDelay: 10000,
}) })
this.drizzleDataSource = drizzle({ client: this.drizzleConnection })
} }
} }
public async destroy(): Promise<void> { public async destroy(): Promise<void> {
await Promise.all([this.dataSource?.destroy(), this.drizzleConnection?.end()]) await Promise.all([this.dataSource?.destroy(), this.drizzlePool?.end()])
this.dataSource = undefined this.dataSource = undefined
this.drizzleConnection = undefined this.drizzlePool = undefined
this.drizzleDataSource = undefined
if (this.redisClient) { if (this.redisClient) {
await this.redisClient.quit() await this.redisClient.quit()
this.redisClient = undefined this.redisClient = undefined

View File

@ -23,7 +23,7 @@ afterAll(async () => {
describe('openaiThreads query test', () => { describe('openaiThreads query test', () => {
it('should insert a new openai thread', async () => { it('should insert a new openai thread', async () => {
await Promise.resolve([dbInsertOpenaiThread('7', 1), dbInsertOpenaiThread('72', 6)]) await Promise.all([dbInsertOpenaiThread('7', 1), dbInsertOpenaiThread('72', 6)])
const result = await db.select().from(openaiThreadsTable) const result = await db.select().from(openaiThreadsTable)
expect(result).toHaveLength(2) expect(result).toHaveLength(2)
expect(result).toMatchObject([ expect(result).toMatchObject([

View File

@ -1,6 +1,6 @@
import { getLogger } from 'log4js' import { getLogger } from 'log4js'
import { aliasSchema, emailSchema, uuidv4Schema } from 'shared' import { aliasSchema, emailSchema, uuidv4Schema } from 'shared'
import { Raw } from 'typeorm' import { In, Raw } from 'typeorm'
import { User as DbUser, UserContact as DbUserContact } from '../entity' import { User as DbUser, UserContact as DbUserContact } from '../entity'
import { findWithCommunityIdentifier, LOG4JS_QUERIES_CATEGORY_NAME } from './index' import { findWithCommunityIdentifier, LOG4JS_QUERIES_CATEGORY_NAME } from './index'
@ -81,3 +81,15 @@ export async function findForeignUserByUuids(
where: { foreign: true, communityUuid, gradidoID }, where: { foreign: true, communityUuid, gradidoID },
}) })
} }
export async function findUserNamesByIds(userIds: number[]): Promise<Map<number, string>> {
const users = await DbUser.find({
select: { id: true, firstName: true, lastName: true, alias: true },
where: { id: In(userIds) },
})
return new Map(
users.map((user) => {
return [user.id, `${user.firstName} ${user.lastName}`]
}),
)
}

View File

@ -1,7 +1,13 @@
# Need to adjust! # Need to adjust!
# Will be seen on login and from other communities if dht and federation modules are active
COMMUNITY_NAME="Your community name" COMMUNITY_NAME="Your community name"
# Description should have at least 10 characters
COMMUNITY_DESCRIPTION="Short Description from your Community." COMMUNITY_DESCRIPTION="Short Description from your Community."
# your domain name, without protocol (without https:// or http:// )
# domain name should be configured in your dns records to point to this server
# hetzner_cloud/install.sh will be acquire a SSL-certificate via letsencrypt for this domain
COMMUNITY_HOST=gddhost.tld COMMUNITY_HOST=gddhost.tld
# used in E-Mails and some error message, should be the email address of your own support (team)
COMMUNITY_SUPPORT_MAIL=support@supportmail.com COMMUNITY_SUPPORT_MAIL=support@supportmail.com
# setup email account for sending gradido system messages to users # setup email account for sending gradido system messages to users
@ -12,10 +18,44 @@ EMAIL_PASSWORD=1234
EMAIL_SMTP_HOST=smtp.lustig.de EMAIL_SMTP_HOST=smtp.lustig.de
EMAIL_SMTP_PORT=587 EMAIL_SMTP_PORT=587
BACKEND_PORT=4000 # Federation, Settings for transactions with other communities
# if set to true allow sending gradidos to another communities # if set to true allow sending gradidos to another communities
FEDERATION_XCOM_SENDCOINS_ENABLED=false FEDERATION_XCOM_SENDCOINS_ENABLED=false
# if set to true, allow redeeming gradido link from another community
CROSS_TX_REDEEM_LINK_ACTIVE=false
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
# on an hash created from this topic
# need for discover other communities
# get the correct topic from Gradido Academy, GRADIDO_HUB is our test topic
FEDERATION_DHT_TOPIC=GRADIDO_HUB
# Advanced Server Setup
# SCSS Parsing
# grass speed up frontend building, but needs some time for the first setup because it is a rust program which need to be compiled
USE_GRASS=false
# Logging
LOG_LEVEL=info
GRADIDO_LOG_PATH=/home/gradido/gradido/deployment/bare_metal/log
TYPEORM_LOGGING_RELATIVE_PATH=../deployment/bare_metal/log/typeorm.backend.log
# Need adjustments for test system
# protocol for community host, on production usually https, on local dev usually http
URL_PROTOCOL=https
# only for test server
DEPLOY_SEED_DATA=false
# test email
# if true all email will be send to EMAIL_TEST_RECEIVER instead of email address of user
EMAIL_TEST_MODUS=false
EMAIL_TEST_RECEIVER=test_team@gradido.net
# webhook for auto update on github repository changes
WEBHOOK_GITHUB_BRANCH=master
# Business Logic
# how many minutes email verification code is valid # how many minutes email verification code is valid
# also used for password reset code # also used for password reset code
@ -23,36 +63,8 @@ EMAIL_CODE_VALID_TIME=1440
# how many minutes user must wait before he can request the email verification code again # how many minutes user must wait before he can request the email verification code again
# also used for password reset code # also used for password reset code
EMAIL_CODE_REQUEST_TIME=10 EMAIL_CODE_REQUEST_TIME=10
# login expire time
# Need to adjust by updates JWT_EXPIRES_IN=10m
# config versions
DATABASE_CONFIG_VERSION=v1.2022-03-18
BACKEND_CONFIG_VERSION=v23.2024-04-04
FRONTEND_CONFIG_VERSION=v6.2024-02-27
ADMIN_CONFIG_VERSION=v2.2024-01-04
FEDERATION_CONFIG_VERSION=v2.2023-08-24
FEDERATION_DHT_CONFIG_VERSION=v4.2024-01-17
FEDERATION_DHT_TOPIC=GRADIDO_HUB
# Need adjustments for test system
URL_PROTOCOL=https
# start script
# only for test server
DEPLOY_SEED_DATA=false
# test email
# if true all email will be send to EMAIL_TEST_RECEIVER instead of email address of user
EMAIL_TEST_MODUS=false
EMAIL_TEST_RECEIVER=test_team@gradido.net
USE_CRYPTO_WORKER=true
# Logging
LOG_LEVEL=info
GRADIDO_LOG_PATH=/home/gradido/gradido/deployment/bare_metal/log
TYPEORM_LOGGING_RELATIVE_PATH=../deployment/bare_metal/log/typeorm.backend.log
# webhook
WEBHOOK_GITHUB_SECRET=secret
WEBHOOK_GITHUB_BRANCH=master
# frontend and admin paths, usually don't need changes # frontend and admin paths, usually don't need changes
# used in nginx config and for links in emails # used in nginx config and for links in emails
@ -66,44 +78,19 @@ EMAIL_LINK_SETPASSWORD_PATH=/reset-password/
EMAIL_LINK_FORGOTPASSWORD_PATH=/forgot-password EMAIL_LINK_FORGOTPASSWORD_PATH=/forgot-password
EMAIL_LINK_OVERVIEW_PATH=/overview EMAIL_LINK_OVERVIEW_PATH=/overview
ADMIN_AUTH_PATH=/admin/authenticate?token= ADMIN_AUTH_PATH=/admin/authenticate?token=
# need to change when frontend is hosted on a different domain as the backend
WALLET_URL=$URL_PROTOCOL://$COMMUNITY_HOST
GRAPHQL_PATH=/graphql GRAPHQL_PATH=/graphql
# login expire time
JWT_SECRET=secret123
JWT_EXPIRES_IN=10m
# Federation # Federation
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
# on an hash created from this topic
# FEDERATION_DHT_TOPIC=GRADIDO_HUB # FEDERATION_DHT_TOPIC=GRADIDO_HUB
# FEDERATION_DHT_SEED=64ebcb0e3ad547848fef4197c6e2332f #
# the api port is the baseport, which will be added with the api-version, e.g. 1_0 = 5010 # the api port is the baseport, which will be added with the api-version, e.g. 1_0 = 5010
FEDERATION_COMMUNITY_API_PORT=5000 FEDERATION_COMMUNITY_API_PORT=5000
FEDERATION_VALIDATE_COMMUNITY_TIMER=60000
# comma separated list of api-versions, which cause starting several federation modules # comma separated list of api-versions, which cause starting several federation modules
FEDERATION_COMMUNITY_APIS=1_0 FEDERATION_COMMUNITY_APIS=1_0
# externe gradido services (more added in future)
GDT_ACTIVE=false
AUTO_POLL_INTERVAL=30000
# DLT-Connector (still in develop)
DLT_ACTIVE=false
DLT_CONNECTOR_PORT=6010
DLT_NODE_SERVER_PORT=8340
DLT_NODE_SERVER_URL=$URL_PROTOCOL://$COMMUNITY_HOST/dlt
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER=/home/gradido/.gradido
# used for combining a newsletter on klicktipp with this gradido community
# if used, user will be subscribed on register and can unsubscribe in his account
KLICKTIPP=false
KLICKTIPP_USER=gradido_test
KLICKTIPP_PASSWORD=secret321
KLICKTIPP_APIKEY_DE=SomeFakeKeyDE
KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
# Meta data in frontend pages, important when shared via facebook or twitter or for search engines # Meta data in frontend pages, important when shared via facebook or twitter or for search engines
META_TITLE_DE="Gradido Dein Dankbarkeitskonto" META_TITLE_DE="Gradido Dein Dankbarkeitskonto"
META_TITLE_EN="Gradido - Your gratitude account" META_TITLE_EN="Gradido - Your gratitude account"
@ -113,6 +100,35 @@ META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natü
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System" META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie" META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
# externe gradido services
GDT_ACTIVE=false
GDT_API_URL=https://gdt.gradido.net
# DLT-Connector (still in develop)
# verify transaction additional via gradido blockchain
DLT_ACTIVE=false
DLT_CONNECTOR_PORT=6010
DLT_NODE_SERVER_PORT=8340
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER=/home/gradido/.gradido
# HIERO used from dlt-connector
HIERO_HEDERA_NETWORK=testnet
# https://docs.hedera.com/hedera/networks/testnet/testnet-access
HIERO_OPERATOR_ID=
HIERO_OPERATOR_KEY=
# for inspector from outside of server
DLT_NODE_SERVER_URL=$URL_PROTOCOL://$COMMUNITY_HOST/dlt
# used for combining a newsletter on klicktipp with this gradido community
# if used, user will be subscribed on register and can unsubscribe in his account
KLICKTIPP=false
KLICKTIPP_USER=gradido_test
KLICKTIPP_PASSWORD=secret321
KLICKTIPP_APIKEY_DE=SomeFakeKeyDE
KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
# update page shown while updating gradido # update page shown while updating gradido
# page will be fed with status changes # page will be fed with status changes
NGINX_UPDATE_PAGE_ROOT=/home/gradido/gradido/deployment/bare_metal/nginx/update-page NGINX_UPDATE_PAGE_ROOT=/home/gradido/gradido/deployment/bare_metal/nginx/update-page
@ -124,24 +140,82 @@ NGINX_SSL_DHPARAM=/etc/letsencrypt/ssl-dhparams.pem
NGINX_SSL_INCLUDE=/etc/letsencrypt/options-ssl-nginx.conf NGINX_SSL_INCLUDE=/etc/letsencrypt/options-ssl-nginx.conf
NGINX_REWRITE_LEGACY_URLS=false NGINX_REWRITE_LEGACY_URLS=false
# LEGACY # Services
WEBHOOK_ELOPAGE_SECRET=secret BACKEND_PORT=4000
# GMS # GMS
# Speak with Gradido Academy to get access to GMS
GMS_ACTIVE=false GMS_ACTIVE=false
# Coordinates of Illuminz test instance GMS_API_URL=https://gms-stage.gradido.net/gms
#GMS_API_URL=http://54.176.169.179:3071 GMS_DASHBOARD_URL=https://gms-stage.gradido.net/
GMS_API_URL=http://localhost:4044/ GMS_USER_SEARCH_FRONTEND_ROUTE=user-search
GMS_DASHBOARD_URL=http://localhost:8080/ # set your own or placeholder webhook_secret will be replaced by start.sh
GMS_WEBHOOK_SECRET=secret GMS_WEBHOOK_SECRET=webhook_secret
GMS_CREATE_USER_THROW_ERRORS=false GMS_CREATE_USER_THROW_ERRORS=false
# HUMHUB # HUMHUB
# Host your own humhub Server
HUMHUB_ACTIVE=false HUMHUB_ACTIVE=false
HUMHUB_API_URL=https://community.gradido.net HUMHUB_API_URL=https://your-humhub-domain.tld
HUMHUB_JWT_KEY= # Humhub jwt key, setup together with humhub
# set your own or placeholder jwt_secret will be replaced by start.sh
HUMHUB_JWT_KEY=jwt_secret
# OPENAI # OPENAI
# you need a paid openai account to use this feature
OPENAI_ACTIVE=false OPENAI_ACTIVE=false
OPENAI_API_KEY='' # assistant id for your openai assistant
OPENAI_ASSISTANT_ID=asst_MF5cchZr7wY7rNXayuWvZFsM OPENAI_ASSISTANT_ID=
# your api key form openai
OPENAI_API_KEY=
# Performance Settings
# in milliseconds, how often inspector and frontend ask on some views for new data
AUTO_POLL_INTERVAL=60000
# set to true if password encryption should take place in another thread, which don't block the main loop, true is recommended
USE_CRYPTO_WORKER=true
# in milliseconds, how often the other communities should be checked if still online and reachable
FEDERATION_VALIDATE_COMMUNITY_TIMER=60000
# Security Settings, placeholder (jwt_secret, webhook_secret, binary8_secret, binary16_secret, binary32_secret)
# will be replaced by start.sh
JWT_SECRET=jwt_secret
# secret for webhook, must be set here and in github.com webhook
WEBHOOK_GITHUB_SECRET=webhook_secret
# basic for key pair generation for federation, maximal 64 characters
FEDERATION_DHT_SEED=binary32_secret
WEBHOOK_ELOPAGE_SECRET=webhook_secret
# keys for dlt
GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET=binary8_secret
GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY=binary16_secret
HOME_COMMUNITY_SEED=binary32_secret
# Server Settings needed if modules are hosted on different servers (non-default setup)
# GUI-Module Configs (with nginx-routing always running on COMMUNITY_HOST)
ADMIN_MODULE_PROTOCOL=$URL_PROTOCOL
ADMIN_MODULE_HOST=$COMMUNITY_HOST
ADMIN_MODULE_PORT=8080
FRONTEND_MODULE_PROTOCOL=$URL_PROTOCOL
FRONTEND_MODULE_HOST=$COMMUNITY_HOST
FRONTEND_MODULE_PORT=3000
# Non-GUI-Module Configs (with nginx-routing always running on localhost in case of on bare metal)
BACKEND_MODULE_PROTOCOL=http
BACKEND_MODULE_HOST=localhost
BACKEND_PORT=4000
DHT_MODULE_PROTOCOL=http
DHT_MODULE_HOST=localhost
DHT_MODULE_PORT=5000
DLT_MODULE_PROTOCOL=http
DLT_MODULE_HOST=localhost
DLT_MODULE_PORT=6010
FEDERATION_MODULE_PROTOCOL=http
FEDERATION_MODULE_HOST=localhost
FEDERATION_MODULE_PORT=5010

View File

@ -40,6 +40,8 @@ ENV BRANCH_NAME=$BRANCH_NAME
RUN git clone https://github.com/gradido/gradido.git --branch $BRANCH_NAME RUN git clone https://github.com/gradido/gradido.git --branch $BRANCH_NAME
RUN cp /app/gradido/deployment/bare_metal/.env.dist /app/gradido/deployment/bare_metal/.env RUN cp /app/gradido/deployment/bare_metal/.env.dist /app/gradido/deployment/bare_metal/.env
RUN sed -i 's/^URL_PROTOCOL=https$/URL_PROTOCOL=http/' /app/gradido/deployment/bare_metal/.env RUN sed -i 's/^URL_PROTOCOL=https$/URL_PROTOCOL=http/' /app/gradido/deployment/bare_metal/.env
RUN sed -i 's/^COMMUNITY_HOST=gddhost.tld$/COMMUNITY_HOST=localhost/' /app/gradido/deployment/bare_metal/.env
# setup nginx # setup nginx
WORKDIR /app/gradido/deployment/bare_metal/nginx WORKDIR /app/gradido/deployment/bare_metal/nginx

View File

@ -1,10 +1,11 @@
#!/bin/bash #!/bin/bash
# source profile so PATH/NVM/BUN werden gesetzt (safe for non-login)
if [ -f /home/gradido/.profile ]; then
. /home/gradido/.profile
fi
# Ensure required tools are installed # Ensure required tools are installed
# make sure correct node version is installed
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
if ! command -v nvm > /dev/null if ! command -v nvm > /dev/null
then then
echo "'nvm' is missing, will be installed now!" echo "'nvm' is missing, will be installed now!"
@ -14,7 +15,7 @@ install_nvm() {
nvm install nvm install
nvm use nvm use
nvm alias default nvm alias default
npm i -g yarn pm2 npm i -g pm2 turbo
pm2 startup pm2 startup
} }
nvm use || install_nvm nvm use || install_nvm
@ -52,23 +53,24 @@ fi
if ! command -v turbo > /dev/null if ! command -v turbo > /dev/null
then then
echo "'turbo' is missing, will be installed now!" echo "'turbo' is missing, will be installed now!"
bun install --global turbo npm i -g turbo
fi fi
# rust and grass # rust and grass
if ! command -v cargo > /dev/null if [ "$USE_GRASS" = true ]; then
then if ! command -v cargo > /dev/null
echo "'cargo' is missing, will be installed now!" then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y echo "'cargo' is missing, will be installed now!"
export CARGO_HOME="$HOME/.cargo" curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
export PATH="$CARGO_HOME/bin:$PATH" export CARGO_HOME="$HOME/.cargo"
export PATH="$CARGO_HOME/bin:$PATH"
fi
if ! command -v grass > /dev/null
then
echo "'grass' is missing, will be installed now!"
cargo install grass
fi
fi fi
if ! command -v grass > /dev/null
then
echo "'grass' is missing, will be installed now!"
cargo install grass
fi
# redis # redis
if ! command -v redis-cli --version > /dev/null if ! command -v redis-cli --version > /dev/null
then then

View File

@ -1,149 +0,0 @@
#!/bin/bash
# This install script requires the minimum requirements already installed.
# How to do this is described in detail in [setup.md](./setup.md)
# Find current directory & configure paths
## For manualy use in terminal
## set -o allexport
## SCRIPT_DIR=$(pwd)
## PROJECT_ROOT=$SCRIPT_DIR/../..
## set +o allexport
# Use here in script
set -o allexport
SCRIPT_PATH=$(realpath $0)
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
PROJECT_ROOT=$SCRIPT_DIR/../..
set +o allexport
# Load .env or .env.dist if not present
# NOTE: all config values will be in process.env when starting
# the services and will therefore take precedence over the .env
if [ -f "$SCRIPT_DIR/.env" ]; then
set -o allexport
source $SCRIPT_DIR/.env
set +o allexport
else
set -o allexport
source $SCRIPT_DIR/.env.dist
set +o allexport
fi
# Configure git
git config pull.ff only
# Install mariadb
sudo apt-get install -y mariadb-server
sudo mysql_secure_installation
# Enter current password for root (enter for none): enter
# Switch to unix_socket authentication [Y/n] Y
# Change the root password? [Y/n] n
# Remove anonymous users? [Y/n] Y
# Disallow root login remotely? [Y/n] Y
# Remove test database and access to it? [Y/n] Y
# Reload privilege tables now? [Y/n] Y
# Install nginx
sudo apt-get install -y nginx
sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/sites-available/gradido.conf /etc/nginx/sites-available
# sudo ln -s /etc/nginx/sites-available/gradido.conf /etc/nginx/sites-enabled
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/sites-available/update-page.conf /etc/nginx/sites-available
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/common /etc/nginx/
sudo rmdir /etc/nginx/conf.d
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/conf.d /etc/nginx/
# Allow nginx configuration and restart for gradido
#TODO generate file
sudo nano /etc/sudoers.d/gradido
> gradido ALL=(ALL) NOPASSWD: /etc/init.d/nginx start,/etc/init.d/nginx stop,/etc/init.d/nginx restart
sudo chmod a+rw /etc/nginx/sites-enabled
# Install node 16.x
sudo apt-get install -y curl
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo apt-get install -y build-essential
# Install yarn
sudo apt-get install -y gnupg
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install -y yarn
# Install pm2
sudo yarn global add pm2
pm2 startup
> execute command output in shell
# Install certbot
sudo apt-get install -y certbot
sudo apt-get install -y python3-certbot-nginx
sudo certbot
> Enter email address (used for urgent renewal and security notices) > e.g. support@supportmail.com
> Please read the Terms of Service at > Y
> Would you be willing, once your first certificate is successfully issued, to > N
> No names were found in your configuration files. Please enter in your domain > stage1.gradido.net
# Note: this will throw an error regarding not beeing able to identify the nginx corresponding
# config but produce the required certificate - thats perfectly fine this way
# Troubleshoot: to manually renew a certificate with running nginx use the following command:
# (this might be required once to properly have things setup for the cron to autorenew)
# sudo certbot --nginx -d example.com -d www.example.com
# Troubleshoot: to check ut if things working you can use
# sudo certbot renew --dry-run
# Install logrotate
sudo apt-get install -y logrotate
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_DIR/logrotate/gradido.conf.template > $SCRIPT_DIR/logrotate/gradido.conf
sudo cp $SCRIPT_DIR/logrotate/gradido.conf.template /etc/logrotate.d/gradido.conf
sudo chown root:root /etc/logrotate.d/gradido.conf
# Install mysql autobackup
sudo apt-get install -y automysqlbackup
# Webhooks (optional) (for development)
sudo apt install -y webhook
# TODO generate
# put hook into github
# TODO adjust secret
# TODO adjust branch if needed
# https://stage1.gradido.net/hooks/github
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_DIR/webhook/hooks.json.template > ~/hooks.json
webhook -hooks ~/hooks.json &
# or for debugging
# webhook -hooks ~/hooks.json -verbose
# create db user
export DB_USER=gradido
export DB_PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
sudo mysql <<EOFMYSQL
CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASSWORD';
GRANT ALL PRIVILEGES ON *.* TO '$DB_USER'@'localhost';
FLUSH PRIVILEGES;
EOFMYSQL
# Configure database
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/database/.env.template > $PROJECT_ROOT/database/.env
# Configure backend
export JWT_SECRET=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/backend/.env.template > $PROJECT_ROOT/backend/.env
# Configure frontend
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/frontend/.env.template > $PROJECT_ROOT/frontend/.env
# Configure admin
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/admin/.env.template > $PROJECT_ROOT/admin/.env
# Configure dht-node
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/dht-node/.env.template > $PROJECT_ROOT/dht-node/.env
# create cronjob to delete yarn output in /tmp
# crontab -e
# hourly job: 0 * * * * find /tmp -name "yarn--*" -cmin +60 -exec rm -r {} \; > /dev/null
# daily job: 0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null
# Start gradido
# Note: on first startup some errors will occur - nothing serious
./start.sh

View File

@ -2,6 +2,11 @@
# stop if something fails # stop if something fails
set -euo pipefail set -euo pipefail
# source profile so PATH/NVM/BUN werden gesetzt (safe for non-login)
if [ -f /home/gradido/.profile ]; then
. /home/gradido/.profile
fi
# check for parameter # check for parameter
FAST_MODE=false FAST_MODE=false
POSITIONAL_ARGS=() POSITIONAL_ARGS=()
@ -184,7 +189,7 @@ cd $PROJECT_ROOT
# TODO: this overfetches alot, but ensures we can use start.sh with tags # TODO: this overfetches alot, but ensures we can use start.sh with tags
git fetch --all git fetch --all
git checkout $BRANCH_NAME git checkout $BRANCH_NAME
git pull git pull origin $BRANCH_NAME
git submodule update --init --recursive git submodule update --init --recursive
export BUILD_COMMIT="$(git rev-parse HEAD)" export BUILD_COMMIT="$(git rev-parse HEAD)"
@ -299,7 +304,7 @@ done
# Install all node_modules # Install all node_modules
log_step 'Installing node_modules' log_step 'Installing node_modules'
bun install bun install --frozen-lockfile
# build all modules # build all modules
log_step 'build all modules' log_step 'build all modules'
@ -309,12 +314,12 @@ turbo build --env-mode=loose --concurrency=$(nproc)
if [ "$DLT_ACTIVE" = true ]; then if [ "$DLT_ACTIVE" = true ]; then
log_step 'build inspector' log_step 'build inspector'
cd $PROJECT_ROOT/inspector cd $PROJECT_ROOT/inspector
bun install bun install --frozen-lockfile
bun run build bun run build
log_step 'build dlt-connector' log_step 'build dlt-connector'
cd $PROJECT_ROOT/dlt-connector cd $PROJECT_ROOT/dlt-connector
bun install bun install --frozen-lockfile
bun run build bun run build
cd $PROJECT_ROOT cd $PROJECT_ROOT

View File

@ -1,6 +1,15 @@
# Migration # Migration
[Migration from 2.2.0 to 2.2.1](migration/2_2_0-2_2_1/README.md) [Migration from 2.2.0 to 2.2.1](migration/2_2_0-2_2_1/README.md)
# Key Pair
It is recommended to create a new ssh key pair for your gradido server.
You can create it with this command:
```bash
ssh-keygen -t ed25519 -C "your_email@example.com"
```
**Reason**: We recommend `ed25519` because it provides strong security with smaller key sizes, faster performance, and resistance to known attacks, making it more secure and efficient than traditional RSA keys.
# Setup on Hetzner Cloud Server # Setup on Hetzner Cloud Server
Suggested OS: Suggested OS:
Debian 12 Debian 12
@ -15,7 +24,7 @@ ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAkLGbzbG7KIGfkssKJBkc/0EVAzQ/8vjvVHzNdxhK8J yourname - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAkLGbzbG7KIGfkssKJBkc/0EVAzQ/8vjvVHzNdxhK8J yourname
``` ```
I made a (german) video to show it to you: I made a (german) video to show it to you (video is older, cloudConfig.yaml differ):
[![Video](https://img.youtube.com/vi/fORK3Bt3lPw/hqdefault.jpg)](https://www.youtube.com/watch?v=fORK3Bt3lPw) [![Video](https://img.youtube.com/vi/fORK3Bt3lPw/hqdefault.jpg)](https://www.youtube.com/watch?v=fORK3Bt3lPw)
@ -23,14 +32,7 @@ I made a (german) video to show it to you:
### setup your domain pointing on server ip address ### setup your domain pointing on server ip address
### login to your new server as root ### login to your new server as root
```bash ```bash
ssh -i /path/to/privKey root@gddhost.tld ssh -i ~/.ssh/id_ed25519 root@gddhost.tld
```
### Change default shell
```bash
chsh -s /bin/bash
chsh -s /bin/bash gradido
``` ```
### Set password for user `gradido` ### Set password for user `gradido`
@ -51,16 +53,15 @@ su gradido
If you logout from the server you can test authentication: If you logout from the server you can test authentication:
```bash ```bash
$ ssh -i /path/to/privKey gradido@gddhost.tld $ ssh -i ~/.ssh/id_ed25519 gradido@gddhost.tld
# This should log you in and allow you to use sudo commands, which will require the user's password # This should log you in and allow you to use sudo commands, which will require the user's password
``` ```
### Disable password root login via ssh ### Disable password root login via ssh
```bash ```bash
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.org sudo sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config.d/ssh-hardening.conf
sudo sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config sudo sed -i '$a AllowUsers gradido' /etc/ssh/sshd_config.d/ssh-hardening.conf
sudo sed -i '$a AllowUsers gradido' /etc/ssh/sshd_config
sudo /etc/init.d/ssh restart sudo /etc/init.d/ssh restart
``` ```
@ -69,16 +70,17 @@ sudo /etc/init.d/ssh restart
```bash ```bash
$ ssh gradido@gddhost.tld $ ssh gradido@gddhost.tld
# Will result in in either a passphrase request for your key or the message 'Permission denied (publickey)' # Will result in in either a passphrase request for your key or the message 'Permission denied (publickey)'
$ ssh -i /path/to/privKey root@gddhost.tld $ ssh -i ~/.ssh/id_ed25519 root@gddhost.tld
# Will result in 'Permission denied (publickey)' # Will result in 'Permission denied (publickey)'
$ ssh -i /path/to/privKey gradido@gddhost.tld $ ssh -i ~/.ssh/id_ed25519 gradido@gddhost.tld
# Will succeed after entering the correct keys passphrase (if any) # Will succeed after entering the correct keys passphrase (if any)
``` ```
### Install `Gradido` code ### Install `Gradido` code
`latest` is a tag pointing on last stable release
```bash ```bash
cd ~ cd ~
git clone https://github.com/gradido/gradido.git git clone https://github.com/gradido/gradido.git --branch latest --depth 1
``` ```
### Adjust the values in `.env` ### Adjust the values in `.env`
@ -99,21 +101,35 @@ All your following installations in `install.sh` will fail!*
cd ~/gradido/deployment/bare_metal cd ~/gradido/deployment/bare_metal
cp .env.dist .env cp .env.dist .env
nano .env nano .env
# adjust values accordingly
``` ```
### Run `install.sh` with branch name For a minimal setup you need at least to change this values:
```env
COMMUNITY_NAME="Your community name"
COMMUNITY_DESCRIPTION="Short Description from your Community."
# your domain name, without protocol (without https:// or http:// )
# domain name should be configured in your dns records to point to this server
# hetzner_cloud/install.sh will be acquire a SSL-certificate via letsencrypt for this domain
COMMUNITY_HOST=gddhost.tld
# setup email account for sending gradido system messages to users
EMAIL_USERNAME=peter@lustig.de
EMAIL_SENDER=peter@lustig.de
EMAIL_PASSWORD=1234
EMAIL_SMTP_HOST=smtp.lustig.de
```
### Run `install.sh` with branch or tag name
***!!! Attention !!!*** ***!!! Attention !!!***
Don't use this script if you have custom config in /etc/nginx/conf.d, because this script Don't use this script if you have custom config in /etc/nginx/conf.d, because this script
will remove it and ln ../bare_metal/nginx/conf.d will remove it and ln ../bare_metal/nginx/conf.d
```bash ```bash
cd ~/gradido/deployment/hetzner_cloud cd ~/gradido/deployment/hetzner_cloud
sudo ./install.sh release-2_2_0 sudo ./install.sh latest
``` ```
I made a (german) video to show it to you: I made a (german) video to show it to you (video is older, output will differ):
[![Video](https://img.youtube.com/vi/9h-55Si6bMk/hqdefault.jpg)](https://www.youtube.com/watch?v=9h-55Si6bMk) [![Video](https://img.youtube.com/vi/9h-55Si6bMk/hqdefault.jpg)](https://www.youtube.com/watch?v=9h-55Si6bMk)
@ -133,3 +149,16 @@ sudo mysql -D gradido_community -e "insert into user_roles(user_id, role) values
I made a (german) video to show it to you: I made a (german) video to show it to you:
[![Video](https://img.youtube.com/vi/xVQ5t4MnLrE/hqdefault.jpg)](https://www.youtube.com/watch?v=xVQ5t4MnLrE) [![Video](https://img.youtube.com/vi/xVQ5t4MnLrE/hqdefault.jpg)](https://www.youtube.com/watch?v=xVQ5t4MnLrE)
### Troubleshooting
If after some tests this error occur, right after `Requesting a certificate for your-domain.tld`, try again another day. Letsencrypt is rate limited:
```bash
An unexpected error occurred:
AttributeError: can't set attribute
```
### But it isn't working
If it isn't working you can write us: [support@gradido.net](mailto:support@gradido.net)

View File

@ -14,6 +14,7 @@ packages:
- git - git
- mariadb-server - mariadb-server
- nginx - nginx
- redis
- curl - curl
- build-essential - build-essential
- gnupg - gnupg
@ -22,6 +23,7 @@ packages:
- logrotate - logrotate
- automysqlbackup - automysqlbackup
- expect - expect
- unzip
package_update: true package_update: true
package_upgrade: true package_upgrade: true
write_files: write_files:

View File

@ -1,7 +1,28 @@
#!/bin/bash #!/bin/bash
# stop if something fails
set -euo pipefail
log_error() {
local message="$1"
echo -e "\e[31m$message\e[0m" # red in console
}
# called always on error, log error really visible with ascii art in red on console and html
# stop script execution
onError() {
local exit_code=$?
log_error "Command failed!"
log_error " /\\_/\\ Line: $(caller 0)"
log_error "( x.x ) Exit Code: $exit_code"
log_error " > < Offending command: '$BASH_COMMAND'"
log_error ""
exit 1
}
trap onError ERR
# check for parameter # check for parameter
if [ -z "$1" ]; then if [ -z "$1" ]; then
echo "Usage: Please provide a branch name as the first argument." log_error "Usage: Please provide a branch name as the first argument."
exit 1 exit 1
fi fi
@ -19,25 +40,88 @@ LOCAL_SCRIPT_DIR=$(dirname $LOCAL_SCRIPT_PATH)
PROJECT_ROOT=$SCRIPT_DIR/.. PROJECT_ROOT=$SCRIPT_DIR/..
set +o allexport set +o allexport
# Replace placeholder secrets in .env
echo 'Replace placeholder secrets in .env'
# Load .env or .env.dist if not present
# NOTE: all config values will be in process.env when starting
# the services and will therefore take precedence over the .env
if [ -f "$SCRIPT_PATH/.env" ]; then
ENV_FILE="$SCRIPT_PATH/.env"
# --- Secret Generators -------------------------------------------------------
gen_jwt_secret() {
# 32 Character, URL-safe: A-Z a-z 0-9 _ -
tr -dc 'A-Za-z0-9_-' < /dev/urandom | head -c 32 2>/dev/null || true
}
gen_webhook_secret() {
# URL-safe, longer security (40 chars)
tr -dc 'A-Za-z0-9_-' < /dev/urandom | head -c 40 2>/dev/null || true
}
gen_binary_secret() {
local bytes="$1"
# Hex -> 2 chars pro byte
openssl rand -hex "$bytes" 2>/dev/null || true
}
# --- Mapping of Placeholder -> Function --------------------------------------
generate_secret_for() {
case "$1" in
jwt_secret) gen_jwt_secret ;;
webhook_secret) gen_webhook_secret ;;
binary8_secret) gen_binary_secret 8 ;;
binary16_secret) gen_binary_secret 16;;
binary32_secret) gen_binary_secret 32;;
*)
echo "Unknown Placeholder: $1" >&2
exit 1
;;
esac
}
# --- Placeholder List --------------------------------------------------------
placeholders=(
"jwt_secret"
"webhook_secret"
"binary8_secret"
"binary16_secret"
"binary32_secret"
)
# --- Processing in .env -------------------------------------------------
TMP_FILE="${ENV_FILE}.tmp"
cp "$ENV_FILE" "$TMP_FILE"
for ph in "${placeholders[@]}"; do
# Iterate over all lines containing the placeholder
while grep -q "$ph" "$TMP_FILE"; do
new_value=$(generate_secret_for "$ph")
# Replace only the first occurrence per line
sed -i "0,/$ph/s//$new_value/" "$TMP_FILE"
done
done
# Write back
mv "$TMP_FILE" "$ENV_FILE"
chown gradido:gradido "$ENV_FILE"
fi
# If install.sh will be called more than once # If install.sh will be called more than once
# We have to load the backend .env to get DB_USERNAME, DB_PASSWORD AND JWT_SECRET # We have to load the backend .env to get DB_USERNAME and DB_PASSWORD
# and the dht-node .env to get FEDERATION_DHT_SEED
export_var(){ export_var(){
export $1=$(grep -v '^#' $PROJECT_ROOT/backend/.env | grep -e "$1" | sed -e 's/.*=//') export $1=$(grep -v '^#' $PROJECT_ROOT/backend/.env | grep -e "$1" | sed -e 's/.*=//')
export $1=$(grep -v '^#' $PROJECT_ROOT/dht-node/.env | grep -e "$1" | sed -e 's/.*=//')
} }
if [ -f "$PROJECT_ROOT/backend/.env" ]; then if [ -f "$PROJECT_ROOT/backend/.env" ]; then
export_var 'DB_USER' export_var 'DB_USER'
export_var 'DB_PASSWORD' export_var 'DB_PASSWORD'
export_var 'JWT_SECRET'
fi fi
if [ -f "$PROJECT_ROOT/dht-node/.env" ]; then
export_var 'FEDERATION_DHT_SEED'
fi
# Load .env or .env.dist if not present # Load .env or .env.dist if not present
# NOTE: all config values will be in process.env when starting # NOTE: all config values will be in process.env when starting
# the services and will therefore take precedence over the .env # the services and will therefore take precedence over the .env
@ -97,15 +181,17 @@ systemctl restart fail2ban
rm /etc/nginx/sites-enabled/default rm /etc/nginx/sites-enabled/default
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/nginx/sites-available/gradido.conf.template > $SCRIPT_PATH/nginx/sites-available/gradido.conf envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/nginx/sites-available/gradido.conf.template > $SCRIPT_PATH/nginx/sites-available/gradido.conf
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/nginx/sites-available/update-page.conf.template > $SCRIPT_PATH/nginx/sites-available/update-page.conf envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/nginx/sites-available/update-page.conf.template > $SCRIPT_PATH/nginx/sites-available/update-page.conf
mkdir $SCRIPT_PATH/nginx/sites-enabled mkdir -p $SCRIPT_PATH/nginx/sites-enabled
ln -s $SCRIPT_PATH/nginx/sites-available/update-page.conf $SCRIPT_PATH/nginx/sites-enabled/default ln -sf $SCRIPT_PATH/nginx/sites-available/update-page.conf $SCRIPT_PATH/nginx/sites-enabled/default
ln -s $SCRIPT_PATH/nginx/sites-enabled/default /etc/nginx/sites-enabled ln -sf $SCRIPT_PATH/nginx/sites-enabled/default /etc/nginx/sites-enabled
ln -s $SCRIPT_PATH/nginx/common /etc/nginx/ ln -sf $SCRIPT_PATH/nginx/common /etc/nginx/
rmdir /etc/nginx/conf.d if [ -e /etc/nginx/conf.d ] && [ ! -L /etc/nginx/conf.d ]; then
ln -s $SCRIPT_PATH/nginx/conf.d /etc/nginx/ rm -rf /etc/nginx/conf.d
ln -s $SCRIPT_PATH/nginx/conf.d /etc/nginx/
fi
# Make nginx restart automatic # Make nginx restart automatic
mkdir /etc/systemd/system/nginx.service.d mkdir -p /etc/systemd/system/nginx.service.d
# Define the content to be put into the override.conf file # Define the content to be put into the override.conf file
CONFIG_CONTENT="[Unit] CONFIG_CONTENT="[Unit]
StartLimitIntervalSec=500 StartLimitIntervalSec=500
@ -124,11 +210,17 @@ sudo systemctl daemon-reload
# setup https with certbot # setup https with certbot
certbot certonly --nginx --non-interactive --agree-tos --domains $COMMUNITY_HOST --email $COMMUNITY_SUPPORT_MAIL certbot certonly --nginx --non-interactive --agree-tos --domains $COMMUNITY_HOST --email $COMMUNITY_SUPPORT_MAIL
export NVM_DIR="/home/gradido/.nvm"
BUN_VERSION_FILE="$PROJECT_ROOT/.bun-version"
if [ ! -f "$BUN_VERSION_FILE" ]; then
echo ".bun-version file not found at: $BUN_VERSION_FILE"
exit 1
fi
export BUN_VERSION="$(cat "$BUN_VERSION_FILE" | tr -d '[:space:]')"
export BUN_INSTALL="/home/gradido/.bun"
# run as gradido user (until EOF) # run as gradido user (until EOF)
sudo -u gradido bash <<'EOF' sudo -u gradido bash <<EOF
export NVM_DIR="/home/gradido/.nvm"
NODE_VERSION="v18.20.7"
export NVM_DIR
# Install nvm if it doesn't exist # Install nvm if it doesn't exist
if [ ! -d "$NVM_DIR" ]; then if [ ! -d "$NVM_DIR" ]; then
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
@ -137,15 +229,26 @@ sudo -u gradido bash <<'EOF'
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# Install Node if not already installed # Install Node if not already installed
if ! nvm ls $NODE_VERSION >/dev/null 2>&1; then if ! nvm ls >/dev/null 2>&1; then
nvm install $NODE_VERSION nvm install
fi fi
# Install yarn and pm2 # Install pm2 and turbo
npm i -g yarn pm2 npm i -g pm2 turbo
# start pm2
pm2 startup echo "'bun' v$BUN_VERSION will be installed now!"
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
EOF EOF
# Load bun
export BUN_INSTALL="/home/gradido/.bun"
export PATH="$BUN_INSTALL/bin:$PATH"
# Load nvm
export NVM_DIR="/home/gradido/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# start pm2
pm2 startup
# Install logrotate # Install logrotate
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/logrotate/gradido.conf.template > $SCRIPT_PATH/logrotate/gradido.conf envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/logrotate/gradido.conf.template > $SCRIPT_PATH/logrotate/gradido.conf
cp $SCRIPT_PATH/logrotate/gradido.conf /etc/logrotate.d/gradido.conf cp $SCRIPT_PATH/logrotate/gradido.conf /etc/logrotate.d/gradido.conf
@ -153,15 +256,15 @@ cp $SCRIPT_PATH/logrotate/gradido.conf /etc/logrotate.d/gradido.conf
# create db user # create db user
export DB_USER=gradido export DB_USER=gradido
# create a new password only if it not already exist # create a new password only if it not already exist
if [ -z "${DB_PASSWORD}" ]; then : "${DB_PASSWORD:=$(tr -dc '_A-Za-z0-9' < /dev/urandom | head -c 32)}"
export DB_PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c 32; echo);
fi
# Check if DB_PASSWORD is still empty, then exit with an error # Check if DB_PASSWORD is still empty, then exit with an error
if [ -z "${DB_PASSWORD}" ]; then if [ -z "${DB_PASSWORD}" ]; then
echo "Error: Failed to generate DB_PASSWORD." echo "Error: Failed to generate DB_PASSWORD."
exit 1 exit 1
fi fi
export DB_PASSWORD
mysql <<EOFMYSQL mysql <<EOFMYSQL
CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASSWORD'; CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASSWORD';
GRANT ALL PRIVILEGES ON *.* TO '$DB_USER'@'localhost'; GRANT ALL PRIVILEGES ON *.* TO '$DB_USER'@'localhost';
@ -195,5 +298,4 @@ chown -R gradido:gradido $PROJECT_ROOT
sudo -u gradido crontab < $LOCAL_SCRIPT_DIR/crontabs.txt sudo -u gradido crontab < $LOCAL_SCRIPT_DIR/crontabs.txt
# Start gradido # Start gradido
# Note: on first startup some errors will occur - nothing serious
sudo -u gradido $SCRIPT_PATH/start.sh $1 sudo -u gradido $SCRIPT_PATH/start.sh $1

View File

@ -1,6 +1,3 @@
# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts
CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
# Database # Database
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3306 DB_PORT=3306
@ -14,7 +11,6 @@ COMMUNITY_NAME=$COMMUNITY_NAME
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
# Federation # Federation
FEDERATION_DHT_CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen # if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
# on an hash created from this topic # on an hash created from this topic
FEDERATION_DHT_TOPIC=$FEDERATION_DHT_TOPIC FEDERATION_DHT_TOPIC=$FEDERATION_DHT_TOPIC

View File

@ -1,6 +1,6 @@
{ {
"name": "dht-node", "name": "dht-node",
"version": "2.7.3", "version": "2.7.4",
"description": "Gradido dht-node module", "description": "Gradido dht-node module",
"main": "src/index.ts", "main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/", "repository": "https://github.com/gradido/gradido/",

View File

@ -37,7 +37,7 @@ export const configSchema = v.object({
GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY: hex16Schema, GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY: hex16Schema,
HOME_COMMUNITY_SEED: v.pipe( HOME_COMMUNITY_SEED: v.pipe(
hexSchema, hexSchema,
v.length(64, 'expect seed length minimum 64 characters (32 Bytes)'), v.length(64, 'expect seed length 64 characters (32 Bytes)'),
v.transform<string, MemoryBlock>((input: string) => MemoryBlock.fromHex(input)), v.transform<string, MemoryBlock>((input: string) => MemoryBlock.fromHex(input)),
), ),
HIERO_HEDERA_NETWORK: v.optional( HIERO_HEDERA_NETWORK: v.optional(

View File

@ -1,6 +1,3 @@
# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts
CONFIG_VERSION=$FEDERATION_CONFIG_VERSION
LOG_LEVEL=$LOG_LEVEL LOG_LEVEL=$LOG_LEVEL
# this is set fix to false, because it is important for 'production' environments. only set to true if a graphql-playground should be in use # this is set fix to false, because it is important for 'production' environments. only set to true if a graphql-playground should be in use
GRAPHIQL=false GRAPHIQL=false

View File

@ -1,6 +1,6 @@
{ {
"name": "federation", "name": "federation",
"version": "2.7.3", "version": "2.7.4",
"description": "Gradido federation module providing Gradido-Hub-Federation and versioned API for inter community communication", "description": "Gradido federation module providing Gradido-Hub-Federation and versioned API for inter community communication",
"main": "src/index.ts", "main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/federation", "repository": "https://github.com/gradido/gradido/federation",

View File

@ -1,5 +1,3 @@
CONFIG_VERSION=$FRONTEND_CONFIG_VERSION
# Endpoints # Endpoints
GRAPHQL_PATH=$GRAPHQL_PATH GRAPHQL_PATH=$GRAPHQL_PATH
ADMIN_AUTH_PATH=$ADMIN_AUTH_PATH ADMIN_AUTH_PATH=$ADMIN_AUTH_PATH

View File

@ -1,6 +1,6 @@
{ {
"name": "frontend", "name": "frontend",
"version": "2.7.3", "version": "2.7.4",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "concurrently \"yarn watch-scss\" \"vite\"", "dev": "concurrently \"yarn watch-scss\" \"vite\"",

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido", "name": "gradido",
"version": "2.7.3", "version": "2.7.4",
"description": "Gradido", "description": "Gradido",
"main": "index.js", "main": "index.js",
"repository": "git@github.com:gradido/gradido.git", "repository": "git@github.com:gradido/gradido.git",
@ -21,7 +21,8 @@
], ],
"scripts": { "scripts": {
"release": "bumpp -r", "release": "bumpp -r",
"version": "auto-changelog -p --commit-limit 0 && git add CHANGELOG.md", "version": "auto-changelog -p --commit-limit 0 && git add CHANGELOG.md && git commit -m \"update changelog\" && git push",
"postversion": "git push origin :refs/tags/latest && git tag -f latest && git push origin latest",
"installAll": "bun run install", "installAll": "bun run install",
"docker": "cross-env BUILD_COMMIT=$(git rev-parse HEAD) docker compose -f docker-compose.yml up", "docker": "cross-env BUILD_COMMIT=$(git rev-parse HEAD) docker compose -f docker-compose.yml up",
"docker:rebuild": "cross-env BUILD_COMMIT=$(git rev-parse HEAD) docker compose -f docker-compose.yml build", "docker:rebuild": "cross-env BUILD_COMMIT=$(git rev-parse HEAD) docker compose -f docker-compose.yml build",

View File

@ -1,6 +1,6 @@
{ {
"name": "shared", "name": "shared",
"version": "2.7.3", "version": "2.7.4",
"description": "Gradido Shared Code, Low-Level Shared Code, without dependencies on other modules", "description": "Gradido Shared Code, Low-Level Shared Code, without dependencies on other modules",
"main": "./build/index.js", "main": "./build/index.js",
"types": "./src/index.ts", "types": "./src/index.ts",