Improve error handling

This commit is contained in:
Nicolas Meienberger 2022-04-14 11:42:40 +02:00
parent 251b0ba9a0
commit c90cd28488
8 changed files with 123 additions and 72 deletions

View file

@ -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"
}
}
}

View file

@ -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`)

View file

@ -2,5 +2,4 @@ version: "3.7"
networks:
tipi_main_network:
external:
name: runtipi_tipi_main_network

View file

@ -7,6 +7,5 @@
"author": "",
"source": "",
"image": "https://avatars.githubusercontent.com/u/45698031?s=200&v=4",
"dependencies": ["transmission"],
"form_fields": {}
}

View file

@ -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) {

View file

@ -1 +1 @@
{"installed":" transmission filerun","environment":{"anonaddy":{}}}
{"installed":" transmission filerun anonaddy","environment":{"anonaddy":{}}}

View file

@ -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;

View file

@ -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));
}
}
};