Improve error handling
This commit is contained in:
parent
251b0ba9a0
commit
c90cd28488
8 changed files with 123 additions and 72 deletions
|
@ -1,10 +1,45 @@
|
|||
{
|
||||
"name": "Anonaddy",
|
||||
"port": 8084,
|
||||
"id": "anonaddy",
|
||||
"description": "",
|
||||
"short_desc": "Anonymous email forwarding",
|
||||
"author": "",
|
||||
"source": "https://github.com/anonaddy/anonaddy",
|
||||
"image": "https://avatars.githubusercontent.com/u/51450862?s=200&v=4",
|
||||
"form_fields": {}
|
||||
"requirements": {
|
||||
"ports": [25]
|
||||
},
|
||||
"form_fields": {
|
||||
"username": {
|
||||
"type": "text",
|
||||
"label": "Username",
|
||||
"required": true,
|
||||
"env_variable": "ANONADDY_USERNAME"
|
||||
},
|
||||
"key": {
|
||||
"type": "text",
|
||||
"label": "App key",
|
||||
"hint": "Application key for encrypter service. Generate one with : echo \"base64:$(openssl rand -base64 32)\"",
|
||||
"required": true,
|
||||
"env_variable": "ANONADDY_KEY"
|
||||
},
|
||||
"domain": {
|
||||
"type": "fqdn",
|
||||
"label": "Your email domain (eg. example.com)",
|
||||
"max": 50,
|
||||
"min": 3,
|
||||
"required": true,
|
||||
"env_variable": "ANONADDY_DOMAIN"
|
||||
},
|
||||
"secret": {
|
||||
"type": "text",
|
||||
"label": "App secret",
|
||||
"hint": "Long random string used when hashing data for the anonymous replies",
|
||||
"max": 50,
|
||||
"min": 3,
|
||||
"required": true,
|
||||
"env_variable": "ANONADDY_SECRET"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,29 +32,32 @@ services:
|
|||
container_name: anonaddy
|
||||
ports:
|
||||
- 25:25
|
||||
- ${APP_ANONADDY_PORT}:8000
|
||||
- ${APP_PORT}:8000
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
volumes:
|
||||
- "${APP_DATA_DIR}/data:/data"
|
||||
environment:
|
||||
TZ: ${TZ}
|
||||
DB_HOST: db-anonaddy
|
||||
DB_PASSWORD: anonaddy
|
||||
REDIS_HOST: redis-anonaddy
|
||||
APP_KEY: ${APP_ANONADDY_KEY}
|
||||
ANONADDY_DOMAIN: ${APP_ANONADDY_DOMAIN}
|
||||
ANONADDY_SECRET: ${APP_ANONADDY_SECRET}
|
||||
APP_KEY: ${ANONADDY_KEY}
|
||||
ANONADDY_DOMAIN: ${ANONADDY_DOMAIN}
|
||||
ANONADDY_SECRET: ${ANONADDY_SECRET}
|
||||
ANONADDY_ADMIN_USERNAME: ${ANONADDY_USERNAME}
|
||||
POSTFIX_DEBUG: true
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- tipi_main_network
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.anonaddy.rule: Host(`anonaddy.tipi.home`)
|
||||
traefik.http.routers.anonaddy.tls: true
|
||||
traefik.http.routers.anonaddy.entrypoints: websecure
|
||||
traefik.http.routers.anonaddy.service: anonaddy
|
||||
traefik.http.services.anonaddy.loadbalancer.server.port: 8000
|
||||
# labels:
|
||||
# traefik.enable: true
|
||||
# traefik.http.routers.anonaddy.rule: Host(`anonaddy.tipi.home`)
|
||||
# traefik.http.routers.anonaddy.tls: true
|
||||
# traefik.http.routers.anonaddy.entrypoints: websecure
|
||||
# traefik.http.routers.anonaddy.service: anonaddy
|
||||
# traefik.http.services.anonaddy.loadbalancer.server.port: 8000
|
||||
# labels:
|
||||
# traefik.enable: true
|
||||
# traefik.http.routers.anonaddy.rule: PathPrefix(`/anonaddy`)
|
||||
|
|
|
@ -2,5 +2,4 @@ version: "3.7"
|
|||
|
||||
networks:
|
||||
tipi_main_network:
|
||||
external:
|
||||
name: runtipi_tipi_main_network
|
|
@ -7,6 +7,5 @@
|
|||
"author": "",
|
||||
"source": "",
|
||||
"image": "https://avatars.githubusercontent.com/u/45698031?s=200&v=4",
|
||||
"dependencies": ["transmission"],
|
||||
"form_fields": {}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ interface IProps {
|
|||
}
|
||||
|
||||
const AppActions: React.FC<IProps> = ({ app, onInstall, onUninstall, onStart, onStop, onOpen, onUpdate }) => {
|
||||
const hasSettings = Object.keys(app.form_fields).length > 0;
|
||||
|
||||
if (app?.installed && app.status === AppStatus.STOPPED) {
|
||||
return (
|
||||
<div className="flex flex-wrap justify-center">
|
||||
|
@ -25,10 +27,12 @@ const AppActions: React.FC<IProps> = ({ app, onInstall, onUninstall, onStart, on
|
|||
Remove
|
||||
<FiTrash2 className="ml-1" />
|
||||
</Button>
|
||||
<Button onClick={onUpdate} width={160} className="mt-3 mr-2">
|
||||
Settings
|
||||
<FiSettings className="ml-1" />
|
||||
</Button>
|
||||
{hasSettings && (
|
||||
<Button onClick={onUpdate} width={160} className="mt-3 mr-2">
|
||||
Settings
|
||||
<FiSettings className="ml-1" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else if (app?.installed && app.status === AppStatus.RUNNING) {
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"installed":" transmission filerun","environment":{"anonaddy":{}}}
|
||||
{"installed":" transmission filerun anonaddy","environment":{"anonaddy":{}}}
|
|
@ -3,7 +3,7 @@ import si from 'systeminformation';
|
|||
import { appNames } from '../../config/apps';
|
||||
import { AppConfig } from '../../config/types';
|
||||
import { createFolder, fileExists, readJsonFile, writeFile, readFile } from '../fs/fs.helpers';
|
||||
import { checkAppExists, checkAppRequirements, checkEnvFile, getInitalFormValues, runAppScript } from './apps.helpers';
|
||||
import { checkAppExists, checkAppRequirements, checkEnvFile, ensureAppState, getInitalFormValues, runAppScript } from './apps.helpers';
|
||||
|
||||
type AppsState = { installed: string };
|
||||
|
||||
|
@ -30,51 +30,6 @@ const generateEnvFile = (appName: string, form: Record<string, string>) => {
|
|||
writeFile(`/app-data/${appName}/app.env`, envFile);
|
||||
};
|
||||
|
||||
const installApp = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { form } = req.body;
|
||||
|
||||
if (!id) {
|
||||
throw new Error('App name is required');
|
||||
}
|
||||
|
||||
const appExists = fileExists(`/app-data/${id}`);
|
||||
|
||||
if (appExists) {
|
||||
throw new Error(`App ${id} already installed`);
|
||||
}
|
||||
|
||||
const appIsAvailable = appNames.includes(id);
|
||||
|
||||
if (!appIsAvailable) {
|
||||
throw new Error(`App ${id} not available`);
|
||||
}
|
||||
|
||||
const appIsValid = await checkAppRequirements(id);
|
||||
|
||||
if (!appIsValid) {
|
||||
throw new Error(`App ${id} requirements not met`);
|
||||
}
|
||||
|
||||
// Create app folder
|
||||
createFolder(`/app-data/${id}`);
|
||||
|
||||
// Create env file
|
||||
generateEnvFile(id, form);
|
||||
const state = getStateFile();
|
||||
state.installed += ` ${id}`;
|
||||
writeFile('/state/apps.json', JSON.stringify(state));
|
||||
|
||||
// Run script
|
||||
await runAppScript(['install', id]);
|
||||
|
||||
res.status(200).json({ message: 'App installed successfully' });
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
};
|
||||
|
||||
const uninstallApp = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { id: appName } = req.params;
|
||||
|
@ -84,12 +39,7 @@ const uninstallApp = async (req: Request, res: Response, next: NextFunction) =>
|
|||
}
|
||||
|
||||
checkAppExists(appName);
|
||||
|
||||
// Remove app from apps.json
|
||||
const state = getStateFile();
|
||||
state.installed = state.installed.replace(` ${appName}`, '');
|
||||
|
||||
writeFile('/state/apps.json', JSON.stringify(state));
|
||||
ensureAppState(appName, false);
|
||||
|
||||
// Run script
|
||||
await runAppScript(['uninstall', appName]);
|
||||
|
@ -200,12 +150,57 @@ const startApp = async (req: Request, res: Response, next: NextFunction) => {
|
|||
// Run script
|
||||
await runAppScript(['start', appName]);
|
||||
|
||||
ensureAppState(appName, true);
|
||||
|
||||
res.status(200).json({ message: 'App started successfully' });
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
};
|
||||
|
||||
const installApp = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { form } = req.body;
|
||||
|
||||
if (!id) {
|
||||
throw new Error('App name is required');
|
||||
}
|
||||
|
||||
const appIsAvailable = appNames.includes(id);
|
||||
|
||||
if (!appIsAvailable) {
|
||||
throw new Error(`App ${id} not available`);
|
||||
}
|
||||
|
||||
const appExists = fileExists(`/app-data/${id}`);
|
||||
|
||||
if (appExists) {
|
||||
await startApp(req, res, next);
|
||||
} else {
|
||||
const appIsValid = await checkAppRequirements(id);
|
||||
|
||||
if (!appIsValid) {
|
||||
throw new Error(`App ${id} requirements not met`);
|
||||
}
|
||||
|
||||
// Create app folder
|
||||
createFolder(`/app-data/${id}`);
|
||||
|
||||
// Create env file
|
||||
generateEnvFile(id, form);
|
||||
ensureAppState(id, true);
|
||||
|
||||
// Run script
|
||||
await runAppScript(['install', id]);
|
||||
|
||||
res.status(200).json({ message: 'App installed successfully' });
|
||||
}
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
};
|
||||
|
||||
const initalFormValues = (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import portUsed from 'tcp-port-used';
|
||||
import p from 'p-iteration';
|
||||
import { AppConfig } from '../../config/types';
|
||||
import { fileExists, readFile, readJsonFile, runScript } from '../fs/fs.helpers';
|
||||
import { fileExists, readFile, readJsonFile, runScript, writeFile } from '../fs/fs.helpers';
|
||||
import { internalIpV4 } from 'internal-ip';
|
||||
|
||||
export const checkAppRequirements = async (appName: string) => {
|
||||
|
@ -83,3 +83,19 @@ export const runAppScript = (params: string[]): Promise<void> => {
|
|||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const ensureAppState = (appName: string, installed: boolean) => {
|
||||
const state = readJsonFile('/state/apps.json');
|
||||
|
||||
if (installed) {
|
||||
if (state.installed.indexOf(appName) === -1) {
|
||||
state.installed += ` ${appName}`;
|
||||
writeFile('/state/apps.json', JSON.stringify(state));
|
||||
}
|
||||
} else {
|
||||
if (state.installed.indexOf(appName) !== -1) {
|
||||
state.installed = state.installed.replace(` ${appName}`, '');
|
||||
writeFile('/state/apps.json', JSON.stringify(state));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue