underpost 2.8.65 → 2.8.67
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/.vscode/extensions.json +3 -2
- package/.vscode/settings.json +2 -0
- package/CHANGELOG.md +24 -4
- package/README.md +39 -2
- package/bin/deploy.js +1180 -144
- package/bin/file.js +8 -0
- package/bin/index.js +1 -240
- package/cli.md +451 -0
- package/docker-compose.yml +1 -1
- package/jsdoc.json +1 -1
- package/manifests/calico-custom-resources.yaml +25 -0
- package/manifests/deployment/adminer/deployment.yaml +32 -0
- package/manifests/deployment/adminer/kustomization.yaml +7 -0
- package/manifests/deployment/adminer/service.yaml +13 -0
- package/manifests/mongodb-4.4/service-deployment.yaml +1 -1
- package/manifests/postgresql/configmap.yaml +9 -0
- package/manifests/postgresql/kustomization.yaml +10 -0
- package/manifests/postgresql/pv.yaml +15 -0
- package/manifests/postgresql/pvc.yaml +13 -0
- package/manifests/postgresql/service.yaml +10 -0
- package/manifests/postgresql/statefulset.yaml +37 -0
- package/manifests/valkey/statefulset.yaml +6 -4
- package/package.json +2 -1
- package/src/cli/cluster.js +69 -10
- package/src/cli/deploy.js +55 -8
- package/src/cli/fs.js +14 -3
- package/src/cli/index.js +312 -0
- package/src/cli/monitor.js +93 -39
- package/src/client/components/core/JoyStick.js +2 -2
- package/src/client/components/core/Modal.js +1 -0
- package/src/index.js +1 -1
- package/src/server/client-build.js +13 -0
- package/src/server/conf.js +5 -1
- package/src/server/dns.js +47 -17
- package/src/server/runtime.js +2 -0
- package/src/server/start.js +0 -1
package/src/cli/monitor.js
CHANGED
|
@@ -5,6 +5,7 @@ import axios from 'axios';
|
|
|
5
5
|
import UnderpostRootEnv from './env.js';
|
|
6
6
|
import fs from 'fs-extra';
|
|
7
7
|
import { shellExec } from '../server/process.js';
|
|
8
|
+
import { isInternetConnection } from '../server/dns.js';
|
|
8
9
|
|
|
9
10
|
const logger = loggerFactory(import.meta);
|
|
10
11
|
|
|
@@ -13,7 +14,8 @@ class UnderpostMonitor {
|
|
|
13
14
|
async callback(
|
|
14
15
|
deployId,
|
|
15
16
|
env = 'development',
|
|
16
|
-
options = { now: false, single: false, msInterval: '', type: '' },
|
|
17
|
+
options = { now: false, single: false, msInterval: '', type: '', replicas: '', sync: false },
|
|
18
|
+
commanderOptions,
|
|
17
19
|
auxRouter,
|
|
18
20
|
) {
|
|
19
21
|
if (deployId === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`)) {
|
|
@@ -22,6 +24,7 @@ class UnderpostMonitor {
|
|
|
22
24
|
_deployId.trim(),
|
|
23
25
|
env,
|
|
24
26
|
options,
|
|
27
|
+
commanderOptions,
|
|
25
28
|
await UnderpostDeploy.API.routerFactory(_deployId, env),
|
|
26
29
|
);
|
|
27
30
|
return;
|
|
@@ -36,15 +39,43 @@ class UnderpostMonitor {
|
|
|
36
39
|
|
|
37
40
|
const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
|
|
38
41
|
|
|
39
|
-
logger.info(`${deployId} ${env}`, pathPortAssignmentData);
|
|
40
|
-
|
|
41
42
|
let errorPayloads = [];
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if (options.sync === true) {
|
|
44
|
+
const currentTraffic = UnderpostDeploy.API.getCurrentTraffic(deployId);
|
|
45
|
+
if (currentTraffic) UnderpostRootEnv.API.set(`${deployId}-${env}-traffic`, currentTraffic);
|
|
46
|
+
}
|
|
47
|
+
let traffic = UnderpostRootEnv.API.get(`${deployId}-${env}-traffic`) ?? 'blue';
|
|
48
|
+
const maxAttempts = parseInt(
|
|
49
|
+
Object.keys(pathPortAssignmentData)
|
|
50
|
+
.map((host) => pathPortAssignmentData[host].length)
|
|
51
|
+
.reduce((accumulator, value) => accumulator + value, 0) * 2.5,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
logger.info(`Init deploy monitor`, {
|
|
55
|
+
pathPortAssignmentData,
|
|
56
|
+
maxAttempts,
|
|
57
|
+
deployId,
|
|
58
|
+
env,
|
|
59
|
+
traffic,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const switchTraffic = () => {
|
|
63
|
+
if (traffic === 'blue') traffic = 'green';
|
|
64
|
+
else traffic = 'blue';
|
|
65
|
+
UnderpostRootEnv.API.set(`${deployId}-${env}-traffic`, traffic);
|
|
66
|
+
shellExec(
|
|
67
|
+
`node bin deploy --info-router --build-manifest --traffic ${traffic} --replicas ${
|
|
68
|
+
options.replicas ? options.replicas : 1
|
|
69
|
+
} ${deployId} ${env}`,
|
|
70
|
+
);
|
|
71
|
+
shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml`);
|
|
72
|
+
};
|
|
46
73
|
|
|
47
74
|
const monitor = async (reject) => {
|
|
75
|
+
if (UnderpostRootEnv.API.get(`monitor-init-callback-script`))
|
|
76
|
+
shellExec(UnderpostRootEnv.API.get(`monitor-init-callback-script`));
|
|
77
|
+
const currentTimestamp = new Date().getTime();
|
|
78
|
+
errorPayloads = errorPayloads.filter((e) => currentTimestamp - e.timestamp < 60 * 1000 * 5);
|
|
48
79
|
logger.info(`[${deployId}-${env}] Check server health`);
|
|
49
80
|
for (const host of Object.keys(pathPortAssignmentData)) {
|
|
50
81
|
for (const instance of pathPortAssignmentData[host]) {
|
|
@@ -52,6 +83,7 @@ class UnderpostMonitor {
|
|
|
52
83
|
if (path.match('peer') || path.match('socket')) continue;
|
|
53
84
|
let urlTest = `http://localhost:${port}${path}`;
|
|
54
85
|
switch (options.type) {
|
|
86
|
+
case 'remote':
|
|
55
87
|
case 'blue-green':
|
|
56
88
|
urlTest = `https://${host}${path}`;
|
|
57
89
|
break;
|
|
@@ -71,6 +103,7 @@ class UnderpostMonitor {
|
|
|
71
103
|
status: error.status,
|
|
72
104
|
code: error.code,
|
|
73
105
|
errors: error.errors,
|
|
106
|
+
timestamp: new Date().getTime(),
|
|
74
107
|
};
|
|
75
108
|
if (errorPayload.status !== 404) {
|
|
76
109
|
errorPayloads.push(errorPayload);
|
|
@@ -87,30 +120,31 @@ class UnderpostMonitor {
|
|
|
87
120
|
fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'),
|
|
88
121
|
);
|
|
89
122
|
|
|
123
|
+
shellExec(`kubectl delete configmap underpost-config`);
|
|
124
|
+
shellExec(
|
|
125
|
+
`kubectl create configmap underpost-config --from-file=/home/dd/engine/engine-private/conf/dd-cron/.env.${env}`,
|
|
126
|
+
);
|
|
127
|
+
|
|
90
128
|
for (const host of Object.keys(confServer)) {
|
|
91
129
|
shellExec(`sudo kubectl delete HTTPProxy ${host}`);
|
|
92
130
|
}
|
|
93
131
|
shellExec(`sudo kubectl rollout restart deployment/${deployId}-${env}-${traffic}`);
|
|
94
132
|
|
|
95
|
-
|
|
96
|
-
else traffic = 'blue';
|
|
97
|
-
|
|
98
|
-
shellExec(
|
|
99
|
-
`node bin deploy --info-router --build-manifest --traffic ${traffic} ${deployId} ${env}`,
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml`);
|
|
103
|
-
errorPayloads = [];
|
|
133
|
+
switchTraffic();
|
|
104
134
|
}
|
|
105
135
|
|
|
106
136
|
break;
|
|
107
137
|
|
|
138
|
+
case 'remote':
|
|
139
|
+
break;
|
|
140
|
+
|
|
108
141
|
default:
|
|
109
142
|
if (reject) reject(message);
|
|
110
143
|
else throw new Error(message);
|
|
111
144
|
}
|
|
145
|
+
errorPayloads = [];
|
|
112
146
|
}
|
|
113
|
-
logger.error(
|
|
147
|
+
logger.error(`Error accumulator ${deployId}-${env}-${traffic}`, errorPayloads.length);
|
|
114
148
|
}
|
|
115
149
|
});
|
|
116
150
|
}
|
|
@@ -119,13 +153,19 @@ class UnderpostMonitor {
|
|
|
119
153
|
if (options.now === true) await monitor();
|
|
120
154
|
if (options.single === true) return;
|
|
121
155
|
let optionsMsTimeout = parseInt(options.msInterval);
|
|
122
|
-
if (isNaN(optionsMsTimeout)) optionsMsTimeout =
|
|
156
|
+
if (isNaN(optionsMsTimeout)) optionsMsTimeout = 60250; // 60.25 seconds
|
|
123
157
|
let monitorTrafficName;
|
|
124
158
|
let monitorPodName;
|
|
125
159
|
const monitorCallBack = (resolve, reject) => {
|
|
126
160
|
const envMsTimeout = UnderpostRootEnv.API.get(`${deployId}-${env}-monitor-ms`);
|
|
127
161
|
setTimeout(
|
|
128
162
|
async () => {
|
|
163
|
+
const isOnline = await isInternetConnection();
|
|
164
|
+
if (!isOnline) {
|
|
165
|
+
logger.warn('No internet connection');
|
|
166
|
+
monitorCallBack(resolve, reject);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
129
169
|
switch (options.type) {
|
|
130
170
|
case 'blue-green':
|
|
131
171
|
{
|
|
@@ -135,14 +175,17 @@ class UnderpostMonitor {
|
|
|
135
175
|
}
|
|
136
176
|
const cmd = `underpost config get container-status`;
|
|
137
177
|
const checkDeploymentReadyStatus = () => {
|
|
138
|
-
const
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
178
|
+
const pods = UnderpostDeploy.API.get(`${deployId}-${env}-${traffic}`);
|
|
179
|
+
if (pods && pods[0]) {
|
|
180
|
+
const { NAME } = pods[0];
|
|
181
|
+
if (
|
|
182
|
+
shellExec(`sudo kubectl exec -i ${NAME} -- sh -c "${cmd}"`, { stdout: true }).match(
|
|
183
|
+
`${deployId}-${env}-running-deployment`,
|
|
184
|
+
)
|
|
185
|
+
) {
|
|
186
|
+
monitorPodName = NAME;
|
|
187
|
+
monitorTrafficName = `${traffic}`;
|
|
188
|
+
}
|
|
146
189
|
}
|
|
147
190
|
};
|
|
148
191
|
if (!monitorPodName) {
|
|
@@ -157,19 +200,30 @@ class UnderpostMonitor {
|
|
|
157
200
|
default:
|
|
158
201
|
break;
|
|
159
202
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
203
|
+
for (const monitorStatus of [
|
|
204
|
+
{ key: `monitor-input`, value: UnderpostRootEnv.API.get(`monitor-input`) },
|
|
205
|
+
{
|
|
206
|
+
key: `${deployId}-${env}-monitor-input`,
|
|
207
|
+
value: UnderpostRootEnv.API.get(`${deployId}-${env}-monitor-input`),
|
|
208
|
+
},
|
|
209
|
+
])
|
|
210
|
+
switch (monitorStatus.value) {
|
|
211
|
+
case 'pause':
|
|
212
|
+
monitorCallBack(resolve, reject);
|
|
213
|
+
return;
|
|
214
|
+
case 'restart':
|
|
215
|
+
UnderpostRootEnv.API.delete(monitorStatus.key);
|
|
216
|
+
return reject();
|
|
217
|
+
case 'stop':
|
|
218
|
+
UnderpostRootEnv.API.delete(monitorStatus.key);
|
|
219
|
+
return resolve();
|
|
220
|
+
case 'blue-green-switch':
|
|
221
|
+
UnderpostRootEnv.API.delete(monitorStatus.key);
|
|
222
|
+
switchTraffic();
|
|
223
|
+
}
|
|
224
|
+
await monitor(reject);
|
|
225
|
+
monitorCallBack(resolve, reject);
|
|
226
|
+
return;
|
|
173
227
|
},
|
|
174
228
|
!isNaN(envMsTimeout) ? envMsTimeout : optionsMsTimeout,
|
|
175
229
|
);
|
|
@@ -1629,6 +1629,7 @@ const Modal = {
|
|
|
1629
1629
|
currentTopModalId: '',
|
|
1630
1630
|
zIndexSync: function ({ idModal }) {
|
|
1631
1631
|
setTimeout(() => {
|
|
1632
|
+
if (!this.Data[idModal]) return;
|
|
1632
1633
|
const cleanTopModal = () => {
|
|
1633
1634
|
Object.keys(this.Data).map((_idModal) => {
|
|
1634
1635
|
if (this.Data[_idModal].options.zIndexSync && s(`.${_idModal}`)) s(`.${_idModal}`).style.zIndex = '3';
|
package/src/index.js
CHANGED
|
@@ -683,6 +683,19 @@ Sitemap: https://${host}${path === '/' ? '' : path}/sitemap.xml`,
|
|
|
683
683
|
root file where the route starts, such as index.js, app.js, routes.js, etc ... */
|
|
684
684
|
|
|
685
685
|
await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
|
|
686
|
+
|
|
687
|
+
const htmlFiles = await fs.readdir(`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}`);
|
|
688
|
+
for (const htmlFile of htmlFiles) {
|
|
689
|
+
if (htmlFile.match('.html')) {
|
|
690
|
+
fs.writeFileSync(
|
|
691
|
+
`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}/${htmlFile}`,
|
|
692
|
+
fs
|
|
693
|
+
.readFileSync(`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}/${htmlFile}`, 'utf8')
|
|
694
|
+
.replaceAll('Tutorials', 'References'),
|
|
695
|
+
'utf8',
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
686
699
|
}
|
|
687
700
|
|
|
688
701
|
if (client) {
|
package/src/server/conf.js
CHANGED
|
@@ -927,7 +927,9 @@ const rebuildConfFactory = ({ deployId, valkey, mongo }) => {
|
|
|
927
927
|
const confServer = loadReplicas(
|
|
928
928
|
JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
|
|
929
929
|
);
|
|
930
|
+
const hosts = {};
|
|
930
931
|
for (const host of Object.keys(confServer)) {
|
|
932
|
+
hosts[host] = {};
|
|
931
933
|
for (const path of Object.keys(confServer[host])) {
|
|
932
934
|
if (!confServer[host][path].db) continue;
|
|
933
935
|
const { singleReplica, replicas, db } = confServer[host][path];
|
|
@@ -940,6 +942,7 @@ const rebuildConfFactory = ({ deployId, valkey, mongo }) => {
|
|
|
940
942
|
);
|
|
941
943
|
for (const _host of Object.keys(confServerReplica)) {
|
|
942
944
|
for (const _path of Object.keys(confServerReplica[_host])) {
|
|
945
|
+
hosts[host][_path] = { replica: { host, path } };
|
|
943
946
|
confServerReplica[_host][_path].valkey = valkey;
|
|
944
947
|
switch (provider) {
|
|
945
948
|
case 'mongoose':
|
|
@@ -954,7 +957,7 @@ const rebuildConfFactory = ({ deployId, valkey, mongo }) => {
|
|
|
954
957
|
'utf8',
|
|
955
958
|
);
|
|
956
959
|
}
|
|
957
|
-
}
|
|
960
|
+
} else hosts[host][path] = {};
|
|
958
961
|
confServer[host][path].valkey = valkey;
|
|
959
962
|
switch (provider) {
|
|
960
963
|
case 'mongoose':
|
|
@@ -964,6 +967,7 @@ const rebuildConfFactory = ({ deployId, valkey, mongo }) => {
|
|
|
964
967
|
}
|
|
965
968
|
}
|
|
966
969
|
fs.writeFileSync(`./engine-private/conf/${deployId}/conf.server.json`, JSON.stringify(confServer, null, 4), 'utf8');
|
|
970
|
+
return { hosts };
|
|
967
971
|
};
|
|
968
972
|
|
|
969
973
|
const getRestoreCronCmd = async (options = { host: '', path: '', conf: {}, deployId: '' }) => {
|
package/src/server/dns.js
CHANGED
|
@@ -5,6 +5,9 @@ import validator from 'validator';
|
|
|
5
5
|
import { publicIp, publicIpv4, publicIpv6 } from 'public-ip';
|
|
6
6
|
import { loggerFactory } from './logger.js';
|
|
7
7
|
import UnderpostRootEnv from '../cli/env.js';
|
|
8
|
+
import dns from 'node:dns';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import { shellExec } from './process.js';
|
|
8
11
|
|
|
9
12
|
dotenv.config();
|
|
10
13
|
|
|
@@ -18,35 +21,59 @@ const ip = {
|
|
|
18
21
|
},
|
|
19
22
|
};
|
|
20
23
|
|
|
24
|
+
const isInternetConnection = (domain = 'google.com') =>
|
|
25
|
+
new Promise((resolve) => dns.lookup(domain, {}, (err) => resolve(err ? false : true)));
|
|
26
|
+
|
|
27
|
+
// export INTERFACE=$(ip route | grep default | cut -d ' ' -f 5)
|
|
28
|
+
// export IP_ADDRESS=$(ip -4 addr show dev $INTERFACE | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
|
|
29
|
+
const getLocalIPv4Address = () =>
|
|
30
|
+
os.networkInterfaces()[
|
|
31
|
+
shellExec(`ip route | grep default | cut -d ' ' -f 5`, {
|
|
32
|
+
stdout: true,
|
|
33
|
+
silent: true,
|
|
34
|
+
disableLog: true,
|
|
35
|
+
}).trim()
|
|
36
|
+
].find((i) => i.family === 'IPv4').address;
|
|
37
|
+
|
|
21
38
|
class Dns {
|
|
22
39
|
static callback = async function (deployList) {
|
|
23
40
|
// Network topology configuration:
|
|
24
41
|
// LAN -> [NAT-VPS](modem/router device) -> WAN
|
|
25
42
|
// enabled DMZ Host to proxy IP 80-443 (79-444) sometimes router block first port
|
|
26
|
-
|
|
43
|
+
|
|
44
|
+
// Enabling DHCP
|
|
45
|
+
// Navigate to Subnets > VLAN > Configure DHCP.
|
|
46
|
+
// Select the appropriate DHCP options (Managed or Relay).
|
|
47
|
+
// Save and apply changes.
|
|
48
|
+
|
|
27
49
|
// verify inet ip proxy server address
|
|
28
50
|
// DHCP (Dynamic Host Configuration Protocol) LAN reserver IP -> MAC ID
|
|
29
51
|
// LAN server or device's local servers port -> 3000-3100 (2999-3101)
|
|
30
52
|
// DNS Records: [ANAME](Address Dynamic) -> [A](ipv4) host | [AAAA](ipv6) host -> [public-ip]
|
|
31
53
|
// Forward the router's TCP/UDP ports to the LAN device's IP address
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const confCronPath = fs.existsSync(privateCronConfPath) ? privateCronConfPath : './conf/conf.cron.json';
|
|
36
|
-
const confCronData = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
|
|
37
|
-
|
|
38
|
-
let testIp;
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
testIp = await ip.public.ipv4();
|
|
42
|
-
} catch (error) {
|
|
43
|
-
logger.error(error, { testIp, stack: error.stack });
|
|
44
|
-
}
|
|
54
|
+
const isOnline = await isInternetConnection();
|
|
55
|
+
|
|
56
|
+
if (!isOnline) return;
|
|
45
57
|
|
|
46
|
-
|
|
58
|
+
let testIp;
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
try {
|
|
61
|
+
testIp = await ip.public.ipv4();
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logger.error(error, { testIp, stack: error.stack });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const currentIp = UnderpostRootEnv.API.get('ip');
|
|
67
|
+
|
|
68
|
+
if (validator.isIP(testIp) && currentIp !== testIp) {
|
|
69
|
+
logger.info(`new ip`, testIp);
|
|
70
|
+
UnderpostRootEnv.API.set('monitor-input', 'pause');
|
|
71
|
+
|
|
72
|
+
for (const _deployId of deployList.split(',')) {
|
|
73
|
+
const deployId = _deployId.trim();
|
|
74
|
+
const privateCronConfPath = `./engine-private/conf/${deployId}/conf.cron.json`;
|
|
75
|
+
const confCronPath = fs.existsSync(privateCronConfPath) ? privateCronConfPath : './conf/conf.cron.json';
|
|
76
|
+
const confCronData = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
|
|
50
77
|
for (const recordType of Object.keys(confCronData.records)) {
|
|
51
78
|
switch (recordType) {
|
|
52
79
|
case 'A':
|
|
@@ -68,6 +95,7 @@ class Dns {
|
|
|
68
95
|
if (verifyIp === testIp) {
|
|
69
96
|
logger.info('ip updated successfully', testIp);
|
|
70
97
|
UnderpostRootEnv.API.set('ip', testIp);
|
|
98
|
+
UnderpostRootEnv.API.delete('monitor-input');
|
|
71
99
|
} else logger.error('ip not updated', testIp);
|
|
72
100
|
} catch (error) {
|
|
73
101
|
logger.error(error, error.stack);
|
|
@@ -102,3 +130,5 @@ class Dns {
|
|
|
102
130
|
}
|
|
103
131
|
|
|
104
132
|
export default Dns;
|
|
133
|
+
|
|
134
|
+
export { Dns, ip, isInternetConnection, getLocalIPv4Address };
|
package/src/server/runtime.js
CHANGED
|
@@ -142,6 +142,8 @@ const buildRuntime = async () => {
|
|
|
142
142
|
// if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
|
|
143
143
|
// $_SERVER['HTTPS'] = 'on';
|
|
144
144
|
// }
|
|
145
|
+
// For plugins:
|
|
146
|
+
// define( 'FS_METHOD', 'direct' );
|
|
145
147
|
|
|
146
148
|
// ErrorDocument 404 /custom_404.html
|
|
147
149
|
// ErrorDocument 500 /custom_50x.html
|
package/src/server/start.js
CHANGED