underpost 3.1.3 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +0 -2
- package/.github/workflows/ghpkg.ci.yml +4 -4
- package/.github/workflows/npmpkg.ci.yml +28 -11
- package/.github/workflows/pwa-microservices-template-page.cd.yml +3 -4
- package/.github/workflows/pwa-microservices-template-test.ci.yml +3 -3
- package/.github/workflows/release.cd.yml +4 -4
- package/CHANGELOG.md +324 -1
- package/CLI-HELP.md +49 -3
- package/README.md +3 -2
- package/bin/build.js +18 -12
- package/bin/deploy.js +177 -124
- package/bin/file.js +3 -0
- package/conf.js +3 -2
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +72 -50
- package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
- package/manifests/deployment/playwright/deployment.yaml +1 -1
- package/nodemon.json +1 -1
- package/package.json +22 -15
- package/scripts/rhel-grpc-setup.sh +56 -0
- package/src/api/file/file.ref.json +18 -0
- package/src/api/user/user.service.js +8 -7
- package/src/cli/cluster.js +7 -7
- package/src/cli/db.js +76 -242
- package/src/cli/deploy.js +104 -65
- package/src/cli/env.js +1 -0
- package/src/cli/fs.js +2 -1
- package/src/cli/index.js +42 -1
- package/src/cli/kubectl.js +211 -0
- package/src/cli/release.js +284 -0
- package/src/cli/repository.js +291 -75
- package/src/cli/run.js +188 -33
- package/src/cli/test.js +3 -3
- package/src/client/Default.index.js +3 -4
- package/src/client/components/core/AppStore.js +69 -0
- package/src/client/components/core/CalendarCore.js +2 -2
- package/src/client/components/core/DropDown.js +129 -17
- package/src/client/components/core/Keyboard.js +2 -2
- package/src/client/components/core/LogIn.js +2 -2
- package/src/client/components/core/LogOut.js +2 -2
- package/src/client/components/core/Modal.js +0 -1
- package/src/client/components/core/Panel.js +0 -1
- package/src/client/components/core/PanelForm.js +19 -19
- package/src/client/components/core/SocketIo.js +82 -29
- package/src/client/components/core/SocketIoHandler.js +75 -0
- package/src/client/components/core/Stream.js +143 -95
- package/src/client/components/core/Webhook.js +40 -7
- package/src/client/components/default/AppStoreDefault.js +5 -0
- package/src/client/components/default/LogInDefault.js +3 -3
- package/src/client/components/default/LogOutDefault.js +2 -2
- package/src/client/components/default/MenuDefault.js +5 -5
- package/src/client/components/default/SocketIoDefault.js +3 -51
- package/src/client/services/core/core.service.js +20 -8
- package/src/client/services/user/user.management.js +2 -2
- package/src/index.js +24 -1
- package/src/runtime/express/Express.js +18 -1
- package/src/runtime/lampp/Dockerfile +9 -2
- package/src/runtime/lampp/Lampp.js +4 -3
- package/src/runtime/wp/Dockerfile +64 -0
- package/src/runtime/wp/Wp.js +497 -0
- package/src/server/auth.js +24 -1
- package/src/server/backup.js +19 -1
- package/src/server/client-build-docs.js +9 -2
- package/src/server/client-build.js +31 -31
- package/src/server/client-formatted.js +109 -57
- package/src/server/ipfs-client.js +24 -1
- package/src/server/peer.js +8 -0
- package/src/server/runtime.js +25 -1
- package/src/server/start.js +6 -0
- package/src/ws/IoInterface.js +1 -10
- package/src/ws/IoServer.js +14 -33
- package/src/ws/core/channels/core.ws.chat.js +65 -20
- package/src/ws/core/channels/core.ws.mailer.js +113 -32
- package/src/ws/core/channels/core.ws.stream.js +90 -31
- package/src/ws/core/core.ws.connection.js +12 -33
- package/src/ws/core/core.ws.emit.js +10 -26
- package/src/ws/core/core.ws.server.js +25 -58
- package/src/ws/default/channels/default.ws.main.js +53 -12
- package/src/ws/default/default.ws.connection.js +26 -13
- package/src/ws/default/default.ws.server.js +30 -12
- package/src/client/components/default/ElementsDefault.js +0 -38
- package/src/ws/core/management/core.ws.chat.js +0 -8
- package/src/ws/core/management/core.ws.mailer.js +0 -16
- package/src/ws/core/management/core.ws.stream.js +0 -8
- package/src/ws/default/management/default.ws.main.js +0 -8
|
@@ -169,9 +169,9 @@ class LamppService {
|
|
|
169
169
|
shellCd(`./engine-private/setup`);
|
|
170
170
|
|
|
171
171
|
if (!process.argv.includes(`server`)) {
|
|
172
|
-
// Download and run the XAMPP installer
|
|
172
|
+
// Download and run the XAMPP installer (PHP 8.2)
|
|
173
173
|
shellExec(
|
|
174
|
-
`curl -Lo xampp-linux-installer.run https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/
|
|
174
|
+
`curl -Lo xampp-linux-installer.run https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/8.2.12/xampp-linux-x64-8.2.12-0-installer.run`,
|
|
175
175
|
);
|
|
176
176
|
shellExec(`sudo chmod +x xampp-linux-installer.run`);
|
|
177
177
|
shellExec(
|
|
@@ -237,7 +237,8 @@ Listen ${port}
|
|
|
237
237
|
|
|
238
238
|
<VirtualHost *:${port}>
|
|
239
239
|
DocumentRoot "${documentRoot}"
|
|
240
|
-
ServerName ${host}
|
|
240
|
+
ServerName ${host}
|
|
241
|
+
UseCanonicalName Off
|
|
241
242
|
|
|
242
243
|
<Directory "${documentRoot}">
|
|
243
244
|
Options Indexes FollowSymLinks MultiViews
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
FROM rockylinux:9
|
|
2
|
+
|
|
3
|
+
# Update and install required packages
|
|
4
|
+
RUN dnf -y update && \
|
|
5
|
+
dnf -y install epel-release && \
|
|
6
|
+
dnf -y install --allowerasing \
|
|
7
|
+
bzip2 \
|
|
8
|
+
sudo \
|
|
9
|
+
curl \
|
|
10
|
+
unzip \
|
|
11
|
+
net-tools \
|
|
12
|
+
openssh-server \
|
|
13
|
+
nano \
|
|
14
|
+
vim-enhanced \
|
|
15
|
+
less \
|
|
16
|
+
openssl-devel \
|
|
17
|
+
wget \
|
|
18
|
+
git \
|
|
19
|
+
gnupg2 \
|
|
20
|
+
libnsl \
|
|
21
|
+
perl && \
|
|
22
|
+
dnf clean all
|
|
23
|
+
|
|
24
|
+
# --- Download and install XAMPP (PHP 8.2)
|
|
25
|
+
RUN curl -L -o /tmp/xampp-linux-installer.run "https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/8.2.12/xampp-linux-x64-8.2.12-0-installer.run" && \
|
|
26
|
+
chmod +x /tmp/xampp-linux-installer.run && \
|
|
27
|
+
bash -c "/tmp/xampp-linux-installer.run --mode unattended" && \
|
|
28
|
+
ln -sf /opt/lampp/lampp /usr/bin/lampp
|
|
29
|
+
|
|
30
|
+
# --- Create /xampp/htdocs (static root) and set permissions
|
|
31
|
+
RUN mkdir -p /opt/lampp/htdocs && \
|
|
32
|
+
chown -R root:root /opt/lampp/htdocs && \
|
|
33
|
+
chmod -R a+rX /opt/lampp/htdocs
|
|
34
|
+
|
|
35
|
+
# Add XAMPP binaries and /usr/local/bin to PATH for all shells
|
|
36
|
+
ENV PATH="/opt/lampp/bin:/usr/local/bin:${PATH}"
|
|
37
|
+
RUN echo 'export PATH="/opt/lampp/bin:/usr/local/bin:${PATH}"' > /etc/profile.d/lampp.sh
|
|
38
|
+
|
|
39
|
+
# Provide a no-op sendmail so WP plugins don't error on mail calls
|
|
40
|
+
RUN printf '#!/bin/sh\ncat > /dev/null\n' > /usr/sbin/sendmail && chmod +x /usr/sbin/sendmail
|
|
41
|
+
|
|
42
|
+
# Install WP-CLI
|
|
43
|
+
RUN curl -sL https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -o /usr/local/bin/wp && \
|
|
44
|
+
chmod +x /usr/local/bin/wp && \
|
|
45
|
+
wp --info --allow-root
|
|
46
|
+
|
|
47
|
+
# Install Node.js
|
|
48
|
+
RUN curl -fsSL https://rpm.nodesource.com/setup_24.x | bash -
|
|
49
|
+
RUN dnf install nodejs -y
|
|
50
|
+
RUN dnf clean all
|
|
51
|
+
|
|
52
|
+
# Verify Node.js and npm versions
|
|
53
|
+
RUN node --version
|
|
54
|
+
RUN npm --version
|
|
55
|
+
|
|
56
|
+
# Set working directory
|
|
57
|
+
WORKDIR /home/dd
|
|
58
|
+
|
|
59
|
+
EXPOSE 22
|
|
60
|
+
EXPOSE 80
|
|
61
|
+
EXPOSE 443
|
|
62
|
+
EXPOSE 3000-3100
|
|
63
|
+
EXPOSE 4000-4100
|
|
64
|
+
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress runtime for the Underpost engine.
|
|
3
|
+
* Manages WordPress installations backed by LAMPP (Apache + PHP 8+) and MariaDB.
|
|
4
|
+
*
|
|
5
|
+
* Two provisioning modes:
|
|
6
|
+
* - **clone** — `conf.repository` is set: clones that GitHub repo as the wp root.
|
|
7
|
+
* - **fresh** — no `conf.repository`: downloads wordpress.org/latest.zip, creates
|
|
8
|
+
* the database if it does not exist, and writes wp-config.php.
|
|
9
|
+
*
|
|
10
|
+
* @module src/runtime/wp/Wp.js
|
|
11
|
+
* @namespace WpService
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs-extra';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { shellExec } from '../../server/process.js';
|
|
17
|
+
import { loggerFactory } from '../../server/logger.js';
|
|
18
|
+
import { Lampp } from '../lampp/Lampp.js';
|
|
19
|
+
import Underpost from '../../index.js';
|
|
20
|
+
|
|
21
|
+
const logger = loggerFactory(import.meta);
|
|
22
|
+
|
|
23
|
+
const WP_DOWNLOAD_URL = 'https://wordpress.org/latest.zip';
|
|
24
|
+
const WP_ZIP_PATH = '/tmp/wordpress-latest.zip';
|
|
25
|
+
const WP_BASE_DIR = '/opt/lampp/htdocs/wp';
|
|
26
|
+
// XAMPP ships its own PHP binary under /opt/lampp/bin which is not on the default
|
|
27
|
+
// PATH for non-login shells spawned by shellExec. Prepend it to every wp-cli call.
|
|
28
|
+
const LAMPP_BIN = '/opt/lampp/bin';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @class WpService
|
|
32
|
+
* @description Manages WordPress provisioning and Apache virtual-host wiring
|
|
33
|
+
* on top of the LAMPP service. Each server conf entry with `runtime: 'wp'`
|
|
34
|
+
* is handled here.
|
|
35
|
+
* @memberof WpService
|
|
36
|
+
*/
|
|
37
|
+
class WpService {
|
|
38
|
+
/**
|
|
39
|
+
* Ensures the base WordPress hosting directory exists.
|
|
40
|
+
*/
|
|
41
|
+
static ensureBaseDir() {
|
|
42
|
+
if (!fs.existsSync(WP_BASE_DIR)) fs.mkdirSync(WP_BASE_DIR, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns the on-disk root path for a WordPress site.
|
|
47
|
+
* @param {string} host - Virtual-host name (used as folder name).
|
|
48
|
+
* @returns {string}
|
|
49
|
+
*/
|
|
50
|
+
static siteDir(host) {
|
|
51
|
+
return path.join(WP_BASE_DIR, host);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Ensures WP-CLI (`wp`) is available on PATH, downloading and installing it
|
|
56
|
+
* to `/usr/local/bin/wp` if it is not already present.
|
|
57
|
+
*/
|
|
58
|
+
static ensureWpCli() {
|
|
59
|
+
const existing = shellExec(`PATH="${LAMPP_BIN}:$PATH" which wp 2>/dev/null || true`, {
|
|
60
|
+
stdout: true,
|
|
61
|
+
silent: true,
|
|
62
|
+
disableLog: true,
|
|
63
|
+
});
|
|
64
|
+
if (existing && existing.trim()) return;
|
|
65
|
+
logger.info('WP-CLI not found — installing to /usr/local/bin/wp');
|
|
66
|
+
shellExec(`curl -sL -o /tmp/wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar`);
|
|
67
|
+
shellExec(`chmod +x /tmp/wp-cli.phar`);
|
|
68
|
+
shellExec(`sudo mv /tmp/wp-cli.phar /usr/local/bin/wp`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Ensures a no-op sendmail stub exists at /usr/sbin/sendmail so WP plugins
|
|
73
|
+
* that invoke sendmail directly do not crash when a real MTA is absent.
|
|
74
|
+
* Mirrors the Dockerfile `RUN printf '#!/bin/sh\ncat > /dev/null\n' > /usr/sbin/sendmail`
|
|
75
|
+
* line but guards against older container images that pre-date that layer.
|
|
76
|
+
*/
|
|
77
|
+
static ensureSendmail() {
|
|
78
|
+
const sendmailPath = '/usr/sbin/sendmail';
|
|
79
|
+
const existing = shellExec(`test -x "${sendmailPath}" && echo ok || true`, {
|
|
80
|
+
stdout: true,
|
|
81
|
+
silent: true,
|
|
82
|
+
disableLog: true,
|
|
83
|
+
});
|
|
84
|
+
if (existing && existing.trim() === 'ok') return;
|
|
85
|
+
logger.info('sendmail stub missing — creating no-op at /usr/sbin/sendmail');
|
|
86
|
+
shellExec(
|
|
87
|
+
`printf '#!/bin/sh\\ncat > /dev/null\\n' | sudo tee ${sendmailPath} > /dev/null && sudo chmod +x ${sendmailPath}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Provisions a WordPress site and registers it with the LAMPP virtual-host router.
|
|
93
|
+
*
|
|
94
|
+
* Directory layout (e.g. host='test.nexodev.org', pathRoute='/wp'):
|
|
95
|
+
* - DocumentRoot (vhostDir): /opt/lampp/htdocs/wp/test.nexodev.org
|
|
96
|
+
* - WordPress files (wpDir): /opt/lampp/htdocs/wp/test.nexodev.org/wp
|
|
97
|
+
* - Root .htaccess rewrites / → /wp/ so Apache serves WordPress transparently.
|
|
98
|
+
*
|
|
99
|
+
* When pathRoute='/' the DocumentRoot and wpDir are the same directory.
|
|
100
|
+
*
|
|
101
|
+
* @param {object} opts
|
|
102
|
+
* @param {number} opts.port - Apache VirtualHost listen port.
|
|
103
|
+
* @param {string} opts.host - Server name / virtual host.
|
|
104
|
+
* @param {string} opts.pathRoute - URL path (e.g. '/wp' or '/').
|
|
105
|
+
* @param {string|null} [opts.repository] - GitHub HTTPS clone URL. When set the
|
|
106
|
+
* site root is cloned instead of downloading WordPress.
|
|
107
|
+
* @param {object|null} [opts.db] - MariaDB connection config:
|
|
108
|
+
* `{ host, name, user, password }`.
|
|
109
|
+
* Required for fresh installs; ignored when
|
|
110
|
+
* `repository` is set (wp-config.php is assumed
|
|
111
|
+
* to be part of the repo).
|
|
112
|
+
* @param {object|null} [opts.wp] - WordPress install config:
|
|
113
|
+
* `{ title, adminUser, adminPassword, adminEmail }`.
|
|
114
|
+
* @param {boolean} [opts.redirect] - If true, enables Apache RewriteEngine redirection.
|
|
115
|
+
* @param {string} [opts.redirectTarget] - Target URL for the redirect rule.
|
|
116
|
+
* @param {boolean} [opts.resetRouter] - Clear LAMPP router before appending.
|
|
117
|
+
* @returns {{ disabled: boolean }}
|
|
118
|
+
*/
|
|
119
|
+
static createApp({ port, host, pathRoute, repository, db, wp, redirect, redirectTarget, resetRouter }) {
|
|
120
|
+
if (!Lampp.enabled()) {
|
|
121
|
+
logger.warn(`LAMPP not installed — skipping ${host}`);
|
|
122
|
+
return { disabled: true };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
WpService.ensureBaseDir();
|
|
126
|
+
|
|
127
|
+
// DocumentRoot for the Apache VirtualHost — always under WP_BASE_DIR/<host>
|
|
128
|
+
const vhostDir = WpService.siteDir(host);
|
|
129
|
+
|
|
130
|
+
// When pathRoute is a subdirectory (e.g. '/wp'), WordPress files live one level deeper.
|
|
131
|
+
// When pathRoute is '/' they share the same directory as the DocumentRoot.
|
|
132
|
+
const subDir = pathRoute && pathRoute !== '/' ? pathRoute.replace(/^\/+/, '').replace(/\/+$/, '') : '';
|
|
133
|
+
const wpDir = subDir ? path.join(vhostDir, subDir) : vhostDir;
|
|
134
|
+
|
|
135
|
+
if (repository) {
|
|
136
|
+
WpService.provisionClone({ host, siteRoot: wpDir, repository, db, wp, subDir });
|
|
137
|
+
} else {
|
|
138
|
+
WpService.provisionFresh({ host, siteRoot: wpDir, db, wp, subDir });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Ensure git is initialized and linked to the backup repository.
|
|
142
|
+
// Mark the directory as safe before git operations — the site root is owned
|
|
143
|
+
// by daemon:daemon (Apache) and git 2.35+ refuses to run in directories owned
|
|
144
|
+
// by a different user unless explicitly declared safe.
|
|
145
|
+
if (repository) {
|
|
146
|
+
shellExec(`git config --global --add safe.directory "${wpDir}"`);
|
|
147
|
+
Underpost.repo.initLocalRepo({ path: wpDir, origin: repository });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Write a root .htaccess that rewrites / → /subDir/ when running in subdirectory mode
|
|
151
|
+
if (subDir) {
|
|
152
|
+
WpService.ensureSubdirHtaccess({ vhostDir, subDir });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Make the site writable by the XAMPP Apache process (runs as daemon:daemon).
|
|
156
|
+
// This is required for plugins like Wordfence WAF and Sucuri that write config/upload files.
|
|
157
|
+
shellExec(`sudo chown -R daemon:daemon "${vhostDir}"`);
|
|
158
|
+
shellExec(`sudo find "${vhostDir}" -type d -exec chmod 755 {} \\;`);
|
|
159
|
+
shellExec(`sudo find "${vhostDir}" -type f -exec chmod 644 {} \\;`);
|
|
160
|
+
|
|
161
|
+
// Wire up Apache VirtualHost via Lampp — DocumentRoot is always vhostDir;
|
|
162
|
+
// Lampp.createApp uses `directory` directly as the DocumentRoot.
|
|
163
|
+
const { disabled } = Lampp.createApp({
|
|
164
|
+
port,
|
|
165
|
+
host,
|
|
166
|
+
path: pathRoute,
|
|
167
|
+
directory: vhostDir,
|
|
168
|
+
redirect,
|
|
169
|
+
redirectTarget,
|
|
170
|
+
resetRouter,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return { disabled };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Clone mode — clones `repository` into `siteRoot` if not present, then
|
|
178
|
+
* verifies `wp-config.php` exists. If it is missing the site root is wiped
|
|
179
|
+
* and a fresh WordPress install is performed (requires `db` config).
|
|
180
|
+
* @param {object} opts
|
|
181
|
+
* @param {string} opts.host - Virtual-host name (for logging).
|
|
182
|
+
* @param {string} opts.siteRoot - Absolute path where the site should live.
|
|
183
|
+
* @param {string} opts.repository - HTTPS clone URL.
|
|
184
|
+
* @param {object|null} [opts.db] - MariaDB config used as fallback for fresh install.
|
|
185
|
+
*/
|
|
186
|
+
static provisionClone({ host, siteRoot, repository, db, wp, subDir = '' }) {
|
|
187
|
+
if (!process.env.GITHUB_TOKEN && repository && repository.startsWith('https://github.com/')) {
|
|
188
|
+
logger.warn(`${host}: GITHUB_TOKEN not set — git operations will fail for private repositories`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Step 0 — verify the remote repository is reachable; fall back to fresh install if not.
|
|
192
|
+
// repository.js isRemoteRepo handles token injection internally.
|
|
193
|
+
const repoAccessible = Underpost.repo.isRemoteRepo(repository);
|
|
194
|
+
logger.info(`${host}: remote accessible = ${repoAccessible} (${repository})`);
|
|
195
|
+
if (!repoAccessible) {
|
|
196
|
+
logger.warn(`${host}: remote repository not accessible (${repository}) — running fresh install`);
|
|
197
|
+
WpService.provisionFresh({ host, siteRoot, db, wp, subDir });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Step 1 — clone if the directory does not exist yet
|
|
202
|
+
if (!fs.existsSync(siteRoot)) {
|
|
203
|
+
logger.info(`${host}: cloning ${repository} → ${siteRoot}`);
|
|
204
|
+
const cloneUrl = Underpost.repo.resolveAuthUrl(repository);
|
|
205
|
+
const tmp = `${siteRoot}.tmp`;
|
|
206
|
+
if (fs.existsSync(tmp)) shellExec(`sudo rm -rf "${tmp}"`);
|
|
207
|
+
shellExec(`git clone "${cloneUrl}" "${tmp}"`);
|
|
208
|
+
shellExec(`sudo mv "${tmp}" "${siteRoot}"`);
|
|
209
|
+
shellExec(`sudo chmod -R 755 "${siteRoot}"`);
|
|
210
|
+
shellExec(`sudo chown -R daemon:daemon "${siteRoot}"`);
|
|
211
|
+
} else {
|
|
212
|
+
logger.info(`${host}: repo already present at ${siteRoot}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Step 2 — verify wp-config.php; if missing, wipe and do a fresh install.
|
|
216
|
+
// initLocalRepo is NOT called here — createApp always calls it after provisionClone
|
|
217
|
+
// returns (whether clone or fallback path) so we avoid a double-init and ensure
|
|
218
|
+
// safe.directory is declared before git runs.
|
|
219
|
+
if (!fs.existsSync(path.join(siteRoot, 'wp-config.php'))) {
|
|
220
|
+
logger.warn(`${host}: wp-config.php not found — wiping site root and running fresh install`);
|
|
221
|
+
shellExec(`sudo rm -rf "${siteRoot}"`);
|
|
222
|
+
WpService.provisionFresh({ host, siteRoot, db, wp, subDir });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Fresh-install mode — downloads wordpress.org/latest.zip, extracts it,
|
|
228
|
+
* creates the MariaDB database, and writes wp-config.php.
|
|
229
|
+
* @param {object} opts
|
|
230
|
+
* @param {string} opts.host - Virtual-host name (for logging).
|
|
231
|
+
* @param {string} opts.siteRoot - Absolute path where WordPress should live.
|
|
232
|
+
* @param {object|null} opts.db - `{ host, name, user, password }`.
|
|
233
|
+
*/
|
|
234
|
+
static provisionFresh({ host, siteRoot, db, wp, subDir = '' }) {
|
|
235
|
+
// Validator: wp-config.php presence means installation is complete/valid
|
|
236
|
+
if (fs.existsSync(path.join(siteRoot, 'wp-config.php'))) {
|
|
237
|
+
logger.info(`${host}: wp-config.php found at ${siteRoot}, skipping fresh install`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
logger.info(`${host}: fresh install → ${siteRoot}`);
|
|
242
|
+
|
|
243
|
+
// Download WordPress zip if not already cached
|
|
244
|
+
if (!fs.existsSync(WP_ZIP_PATH)) {
|
|
245
|
+
shellExec(`curl -Lo "${WP_ZIP_PATH}" "${WP_DOWNLOAD_URL}"`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Extract to /tmp/wordpress then move to siteRoot
|
|
249
|
+
const extractDir = '/tmp/wp-extract';
|
|
250
|
+
if (fs.existsSync(extractDir)) shellExec(`sudo rm -rf "${extractDir}"`);
|
|
251
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
252
|
+
shellExec(`unzip -q "${WP_ZIP_PATH}" -d "${extractDir}"`);
|
|
253
|
+
// The zip always extracts to /tmp/wp-extract/wordpress/
|
|
254
|
+
const extracted = path.join(extractDir, 'wordpress');
|
|
255
|
+
if (fs.existsSync(siteRoot)) shellExec(`sudo rm -rf "${siteRoot}"`);
|
|
256
|
+
// Ensure parent directory exists (e.g. /opt/lampp/htdocs/wp/<host>/)
|
|
257
|
+
const parentDir = path.dirname(siteRoot);
|
|
258
|
+
if (!fs.existsSync(parentDir)) fs.mkdirSync(parentDir, { recursive: true });
|
|
259
|
+
shellExec(`sudo mv "${extracted}" "${siteRoot}"`);
|
|
260
|
+
shellExec(`sudo chmod -R 755 "${siteRoot}"`);
|
|
261
|
+
shellExec(`sudo chown -R daemon:daemon "${siteRoot}"`);
|
|
262
|
+
|
|
263
|
+
if (db) {
|
|
264
|
+
WpService.createDatabase(db);
|
|
265
|
+
WpService.writeWpConfig({ siteRoot, db, host, subDir, wp });
|
|
266
|
+
WpService.wpCliInstall({ siteRoot, db, host, wp, subDir });
|
|
267
|
+
} else {
|
|
268
|
+
logger.warn(`${host}: no db config provided — wp-config.php not written`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Runs WP-CLI to complete the WordPress installation non-interactively,
|
|
274
|
+
* then installs and activates the Wordfence security plugin.
|
|
275
|
+
* Safe to call on an already-installed site — wp-cli will detect it and skip.
|
|
276
|
+
* @param {object} opts
|
|
277
|
+
* @param {string} opts.siteRoot - Absolute path to the WordPress installation.
|
|
278
|
+
* @param {object} opts.db - MariaDB config (unused here but kept for signature consistency).
|
|
279
|
+
* @param {string} opts.host - Virtual-host name.
|
|
280
|
+
* @param {object|null} [opts.wp] - WordPress install config from server conf:
|
|
281
|
+
* `{ title, adminUser, adminPassword, adminEmail }`.
|
|
282
|
+
* @param {string} [opts.subDir] - Subdirectory name (e.g. 'wp').
|
|
283
|
+
*/
|
|
284
|
+
static wpCliInstall({ siteRoot, db, host, wp, subDir = '' }) {
|
|
285
|
+
WpService.ensureWpCli();
|
|
286
|
+
WpService.ensureSendmail();
|
|
287
|
+
const siteUrl = subDir ? `https://${host}/${subDir}` : `https://${host}`;
|
|
288
|
+
const adminUser = (wp && wp.adminUser) || process.env.WP_ADMIN_USER || 'admin';
|
|
289
|
+
const adminPassword =
|
|
290
|
+
(wp && wp.adminPassword) ||
|
|
291
|
+
process.env.WP_ADMIN_PASSWORD ||
|
|
292
|
+
'ChangeMe_' + Math.random().toString(36).slice(2, 10);
|
|
293
|
+
const adminEmail = (wp && wp.adminEmail) || process.env.WP_ADMIN_EMAIL || `admin@${host}`;
|
|
294
|
+
const siteTitle = (wp && wp.title) || process.env.WP_SITE_TITLE || host;
|
|
295
|
+
// Prepend XAMPP's bin dir so WP-CLI can find the bundled PHP binary.
|
|
296
|
+
const wpCli = (cmd) =>
|
|
297
|
+
shellExec(`PATH="${LAMPP_BIN}:$PATH" wp --allow-root --path="${siteRoot}" ${cmd}`, {
|
|
298
|
+
stdout: true,
|
|
299
|
+
silent: false,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Step 1 — install WordPress core (skipped automatically by WP-CLI if already installed)
|
|
303
|
+
logger.info(`${host}: running wp core install`);
|
|
304
|
+
wpCli(
|
|
305
|
+
`core install` +
|
|
306
|
+
` --url="${siteUrl}"` +
|
|
307
|
+
` --title="${siteTitle}"` +
|
|
308
|
+
` --admin_user="${adminUser}"` +
|
|
309
|
+
` --admin_password="${adminPassword}"` +
|
|
310
|
+
` --admin_email="${adminEmail}"` +
|
|
311
|
+
` --skip-email`,
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// Step 2 — install and activate Wordfence Security
|
|
315
|
+
logger.info(`${host}: installing Wordfence security plugin`);
|
|
316
|
+
wpCli(`plugin install wordfence --activate`);
|
|
317
|
+
wpCli(`plugin install all-in-one-wp-security-and-firewall --activate`);
|
|
318
|
+
wpCli(`plugin install sucuri-scanner --activate`);
|
|
319
|
+
wpCli(`plugin install cleantalk-spam-protect --activate`);
|
|
320
|
+
|
|
321
|
+
// Step 3 — enable auto-updates for the plugin
|
|
322
|
+
wpCli(`plugin auto-updates enable wordfence`);
|
|
323
|
+
wpCli(`plugin auto-updates enable all-in-one-wp-security-and-firewall`);
|
|
324
|
+
wpCli(`plugin auto-updates enable sucuri-scanner`);
|
|
325
|
+
wpCli(`plugin auto-updates enable cleantalk-spam-protect`);
|
|
326
|
+
|
|
327
|
+
// Step 4 — install and activate WP Mail SMTP when configured
|
|
328
|
+
if (wp && wp.wpMailSmtp) {
|
|
329
|
+
logger.info(`${host}: installing WP Mail SMTP plugin`);
|
|
330
|
+
wpCli(`plugin install wp-mail-smtp --activate`);
|
|
331
|
+
wpCli(`plugin auto-updates enable wp-mail-smtp`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
logger.info(`${host}: WP-CLI provisioning complete`, { siteUrl, adminUser, adminEmail });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Appends rewrite rules for a WordPress subdirectory to the root .htaccess.
|
|
339
|
+
* Each subdirectory gets its own scoped RewriteRule block so multiple
|
|
340
|
+
* WordPress installs under the same host do not overwrite each other.
|
|
341
|
+
* @param {{ vhostDir: string, subDir: string }} opts
|
|
342
|
+
*/
|
|
343
|
+
static ensureSubdirHtaccess({ vhostDir, subDir }) {
|
|
344
|
+
if (!fs.existsSync(vhostDir)) fs.mkdirSync(vhostDir, { recursive: true });
|
|
345
|
+
const htaccessPath = path.join(vhostDir, '.htaccess');
|
|
346
|
+
|
|
347
|
+
// Marker comments to identify each subDir block
|
|
348
|
+
const marker = `# -- wp-subdir: ${subDir} --`;
|
|
349
|
+
const block = `${marker}
|
|
350
|
+
RewriteCond %{REQUEST_URI} ^\\/${subDir}(\\/|$) [NC]
|
|
351
|
+
RewriteCond %{REQUEST_FILENAME} !-f
|
|
352
|
+
RewriteCond %{REQUEST_FILENAME} !-d
|
|
353
|
+
RewriteRule ^${subDir}\\/?(.*)?$ \\/${subDir}\\/index.php [L]
|
|
354
|
+
${marker} end`;
|
|
355
|
+
|
|
356
|
+
let existing = '';
|
|
357
|
+
if (fs.existsSync(htaccessPath)) {
|
|
358
|
+
existing = fs.readFileSync(htaccessPath, 'utf8');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// If this subDir block already exists, replace it; otherwise append
|
|
362
|
+
const markerRegex = new RegExp(
|
|
363
|
+
`${marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')} end`,
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
if (markerRegex.test(existing)) {
|
|
367
|
+
existing = existing.replace(markerRegex, block);
|
|
368
|
+
} else {
|
|
369
|
+
// Ensure the RewriteEngine directive and IfModule wrapper exist
|
|
370
|
+
if (!existing.includes('RewriteEngine on')) {
|
|
371
|
+
existing = `<IfModule mod_rewrite.c>\nRewriteEngine on\n\n</IfModule>\n`;
|
|
372
|
+
}
|
|
373
|
+
// Insert the new block before the closing </IfModule>
|
|
374
|
+
existing = existing.replace('</IfModule>', `${block}\n</IfModule>`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
fs.writeFileSync(htaccessPath, existing, 'utf8');
|
|
378
|
+
logger.info(`subdirectory .htaccess updated`, { vhostDir, subDir });
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Drops and recreates a MariaDB database to ensure a clean state for fresh installs.
|
|
383
|
+
* @param {{ host: string, name: string, user: string, password: string }} db
|
|
384
|
+
*/
|
|
385
|
+
static createDatabase({ host, name, user, password }) {
|
|
386
|
+
logger.info(`Dropping and recreating database "${name}" on ${host}`);
|
|
387
|
+
const mysql = `/opt/lampp/bin/mysql`;
|
|
388
|
+
const q = (s) => s.replace(/'/g, "\\'");
|
|
389
|
+
const exec = (sql) => shellExec(`${mysql} -h '${host}' -u '${q(user)}' -p'${q(password)}' -e "${sql}"`);
|
|
390
|
+
exec(`DROP DATABASE IF EXISTS \\\`${q(name)}\\\``);
|
|
391
|
+
exec(`CREATE DATABASE \\\`${q(name)}\\\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Writes a minimal `wp-config.php` from `wp-config-sample.php`.
|
|
396
|
+
* When `wp.wpMailSmtp` is provided, injects WP Mail SMTP plugin constants
|
|
397
|
+
* (WPMS_ON, WPMS_SMTP_HOST, etc.) so the plugin is pre-configured on first boot.
|
|
398
|
+
* @param {{ siteRoot: string, db: { host: string, name: string, user: string, password: string }, host?: string, subDir?: string, wp?: object }} opts
|
|
399
|
+
*/
|
|
400
|
+
static writeWpConfig({ siteRoot, db, host = '', subDir = '', wp }) {
|
|
401
|
+
const sample = path.join(siteRoot, 'wp-config-sample.php');
|
|
402
|
+
const target = path.join(siteRoot, 'wp-config.php');
|
|
403
|
+
if (!fs.existsSync(sample)) {
|
|
404
|
+
logger.warn(`wp-config-sample.php not found at ${siteRoot}`);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
let cfg = fs.readFileSync(sample, 'utf8');
|
|
408
|
+
cfg = cfg
|
|
409
|
+
.replace("define( 'DB_NAME', 'database_name_here' );", `define( 'DB_NAME', '${db.name}' );`)
|
|
410
|
+
.replace("define( 'DB_USER', 'username_here' );", `define( 'DB_USER', '${db.user}' );`)
|
|
411
|
+
.replace("define( 'DB_PASSWORD', 'password_here' );", `define( 'DB_PASSWORD', '${db.password}' );`)
|
|
412
|
+
.replace("define( 'DB_HOST', 'localhost' );", `define( 'DB_HOST', '${db.host}' );`)
|
|
413
|
+
.replace("define( 'DB_CHARSET', 'utf8' );", `define( 'DB_CHARSET', 'utf8mb4' );`);
|
|
414
|
+
// When WordPress is installed in a subdirectory, WP_HOME and WP_SITEURL must be set
|
|
415
|
+
if (host && subDir) {
|
|
416
|
+
const wpSiteUrl = `https://${host}/${subDir}`;
|
|
417
|
+
cfg = cfg.replace(
|
|
418
|
+
'/** Absolute path to the WordPress directory. */',
|
|
419
|
+
`define( 'WP_HOME', '${wpSiteUrl}' );\ndefine( 'WP_SITEURL', '${wpSiteUrl}' );\n\n/** Absolute path to the WordPress directory. */`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
// Inject reverse-proxy HTTPS detection (needed behind Contour/envoy)
|
|
423
|
+
const httpsSnippet = `
|
|
424
|
+
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
|
|
425
|
+
$_SERVER['HTTPS'] = 'on';
|
|
426
|
+
}
|
|
427
|
+
`;
|
|
428
|
+
cfg = cfg.replace('<?php', `<?php\n${httpsSnippet}`);
|
|
429
|
+
|
|
430
|
+
// Inject WP Mail SMTP constants when wpMailSmtp config is provided
|
|
431
|
+
const wpMailSmtp = wp && wp.wpMailSmtp;
|
|
432
|
+
if (wpMailSmtp) {
|
|
433
|
+
const smtp = wpMailSmtp.smtp || {};
|
|
434
|
+
const wpmsLines = [
|
|
435
|
+
`define( 'WPMS_ON', true );`,
|
|
436
|
+
wpMailSmtp.fromEmail ? `define( 'WPMS_MAIL_FROM', '${wpMailSmtp.fromEmail}' );` : null,
|
|
437
|
+
wpMailSmtp.fromName ? `define( 'WPMS_MAIL_FROM_NAME', '${wpMailSmtp.fromName}' );` : null,
|
|
438
|
+
`define( 'WPMS_MAIL_FROM_FORCE', true );`,
|
|
439
|
+
`define( 'WPMS_MAIL_FROM_NAME_FORCE', false );`,
|
|
440
|
+
wpMailSmtp.mailer ? `define( 'WPMS_MAILER', '${wpMailSmtp.mailer}' );` : null,
|
|
441
|
+
wpMailSmtp.returnPath !== undefined ? `define( 'WPMS_SET_RETURN_PATH', ${wpMailSmtp.returnPath} );` : null,
|
|
442
|
+
smtp.host ? `define( 'WPMS_SMTP_HOST', '${smtp.host}' );` : null,
|
|
443
|
+
smtp.port ? `define( 'WPMS_SMTP_PORT', ${smtp.port} );` : null,
|
|
444
|
+
smtp.encryption ? `define( 'WPMS_SSL', '${smtp.encryption}' );` : null,
|
|
445
|
+
smtp.auth !== undefined ? `define( 'WPMS_SMTP_AUTH', ${smtp.auth} );` : null,
|
|
446
|
+
`define( 'WPMS_SMTP_AUTOTLS', true );`,
|
|
447
|
+
smtp.user ? `define( 'WPMS_SMTP_USER', '${smtp.user}' );` : null,
|
|
448
|
+
smtp.pass ? `define( 'WPMS_SMTP_PASS', '${smtp.pass}' );` : null,
|
|
449
|
+
]
|
|
450
|
+
.filter(Boolean)
|
|
451
|
+
.join('\n');
|
|
452
|
+
cfg = cfg.replace(
|
|
453
|
+
'/** Absolute path to the WordPress directory. */',
|
|
454
|
+
`// WP Mail SMTP plugin constants\n${wpmsLines}\n\n/** Absolute path to the WordPress directory. */`,
|
|
455
|
+
);
|
|
456
|
+
logger.info(`${host}: WP Mail SMTP constants injected into wp-config.php`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
fs.writeFileSync(target, cfg, 'utf8');
|
|
460
|
+
logger.info(`wp-config.php written for ${db.name}`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Backs up a WordPress site: commits all changes and pushes the
|
|
465
|
+
* site directory to its git remote.
|
|
466
|
+
* If no `.git` directory exists but `repository` is provided, git is
|
|
467
|
+
* initialized first so subsequent cron-triggered backups can push.
|
|
468
|
+
* @param {object} opts
|
|
469
|
+
* @param {string} opts.host - Virtual-host name.
|
|
470
|
+
* @param {string|null} [opts.repository] - Git remote URL; used to initialize git if missing.
|
|
471
|
+
*/
|
|
472
|
+
static backup({ host, repository }) {
|
|
473
|
+
const siteRoot = WpService.siteDir(host);
|
|
474
|
+
const githubOrg = process.env.GITHUB_USERNAME || 'underpostnet';
|
|
475
|
+
if (!fs.existsSync(siteRoot)) {
|
|
476
|
+
logger.warn(`backup: site root does not exist — ${siteRoot}`);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
logger.info(`backup: ${host}`);
|
|
480
|
+
|
|
481
|
+
// Ensure git is initialized when a repository is configured
|
|
482
|
+
if (repository && !fs.existsSync(path.join(siteRoot, '.git'))) {
|
|
483
|
+
Underpost.repo.initLocalRepo({ path: siteRoot, origin: repository });
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// MariaDB export is handled by the shared db.js backup flow — no duplicate dump here.
|
|
487
|
+
if (fs.existsSync(path.join(siteRoot, '.git'))) {
|
|
488
|
+
shellExec(`cd "${siteRoot}" && git add -A && git commit -m "wp backup $(date -u +%Y-%m-%dT%H:%M:%SZ)" || true`);
|
|
489
|
+
shellExec(`cd "${siteRoot}" && underpost push . ${githubOrg}/${repository.split('/').pop().split('.')[0]}`);
|
|
490
|
+
logger.info(`backup: git push done for ${siteRoot}`);
|
|
491
|
+
} else {
|
|
492
|
+
logger.warn(`backup: no .git and no repository configured for ${host} — skipping git push`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export { WpService };
|
package/src/server/auth.js
CHANGED
|
@@ -9,7 +9,12 @@ import { loggerFactory } from './logger.js';
|
|
|
9
9
|
import crypto from 'crypto';
|
|
10
10
|
import { promisify } from 'util';
|
|
11
11
|
import { UserDto } from '../api/user/user.model.js';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
commonAdminGuard,
|
|
14
|
+
commonModeratorGuard,
|
|
15
|
+
commonUserGuard,
|
|
16
|
+
validatePassword,
|
|
17
|
+
} from '../client/components/core/CommonJs.js';
|
|
13
18
|
import helmet from 'helmet';
|
|
14
19
|
import rateLimit from 'express-rate-limit';
|
|
15
20
|
import slowDown from 'express-slow-down';
|
|
@@ -303,6 +308,23 @@ const moderatorGuard = (req, res, next) => {
|
|
|
303
308
|
return res.status(400).json({ status: 'error', message: 'bad request' });
|
|
304
309
|
}
|
|
305
310
|
};
|
|
311
|
+
/**
|
|
312
|
+
* Express middleware to guard routes for authenticated users (any non-guest role).
|
|
313
|
+
* @param {import('express').Request} req The Express request object.
|
|
314
|
+
* @param {import('express').Response} res The Express response object.
|
|
315
|
+
* @param {import('express').NextFunction} next The next middleware function.
|
|
316
|
+
* @memberof Auth
|
|
317
|
+
*/
|
|
318
|
+
const userGuard = (req, res, next) => {
|
|
319
|
+
try {
|
|
320
|
+
if (!req.auth || !commonUserGuard(req.auth.user.role))
|
|
321
|
+
return res.status(403).json({ status: 'error', message: 'Insufficient permission' });
|
|
322
|
+
return next();
|
|
323
|
+
} catch (err) {
|
|
324
|
+
logger.error(err);
|
|
325
|
+
return res.status(400).json({ status: 'error', message: 'bad request' });
|
|
326
|
+
}
|
|
327
|
+
};
|
|
306
328
|
|
|
307
329
|
// ---------- Password validation middleware (server-side) ----------
|
|
308
330
|
/**
|
|
@@ -678,6 +700,7 @@ export {
|
|
|
678
700
|
jwtVerify as verifyJWT,
|
|
679
701
|
adminGuard,
|
|
680
702
|
moderatorGuard,
|
|
703
|
+
userGuard,
|
|
681
704
|
validatePasswordMiddleware,
|
|
682
705
|
getBearerToken,
|
|
683
706
|
createSessionAndUserToken,
|
package/src/server/backup.js
CHANGED
|
@@ -8,7 +8,8 @@ import fs from 'fs-extra';
|
|
|
8
8
|
import { loggerFactory } from './logger.js';
|
|
9
9
|
import { shellExec } from './process.js';
|
|
10
10
|
import Underpost from '../index.js';
|
|
11
|
-
import { loadCronDeployEnv } from './conf.js';
|
|
11
|
+
import { loadCronDeployEnv, readConfJson } from './conf.js';
|
|
12
|
+
import { WpService } from '../runtime/wp/Wp.js';
|
|
12
13
|
|
|
13
14
|
const logger = loggerFactory(import.meta);
|
|
14
15
|
|
|
@@ -57,6 +58,23 @@ class BackUp {
|
|
|
57
58
|
logger.info('Executing database export for', deployId);
|
|
58
59
|
shellExec(command);
|
|
59
60
|
}
|
|
61
|
+
{
|
|
62
|
+
const confServer = readConfJson(deployId, 'server');
|
|
63
|
+
for (const host of Object.keys(confServer)) {
|
|
64
|
+
for (const path of Object.keys(confServer[host])) {
|
|
65
|
+
const entry = confServer[host][path];
|
|
66
|
+
try {
|
|
67
|
+
switch (entry.runtime) {
|
|
68
|
+
case 'wp':
|
|
69
|
+
WpService.backup({ host, repository: entry.repository });
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
logger.error(`Error during entry runtime backup for ${host}${path}:`, err);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
60
78
|
}
|
|
61
79
|
};
|
|
62
80
|
}
|