Extend self-hosting variables

This commit is contained in:
Lim Chee Aun 2023-12-25 19:25:48 +08:00
parent 563a7bf03b
commit f520e30858
13 changed files with 179 additions and 50 deletions

7
.env
View file

@ -1,3 +1,4 @@
VITE_CLIENT_NAME=Phanpy
VITE_CLIENT_ID=social.phanpy
VITE_WEBSITE=https://phanpy.social
PHANPY_CLIENT_NAME=Phanpy
PHANPY_WEBSITE=https://phanpy.social
PHANPY_LINGVA_INSTANCES="lingva.phanpy.social lingva.lunar.icu lingva.garudalinux.org translate.plausibility.cloud"
PHANPY_PRIVACY_POLICY_URL="https://github.com/cheeaun/phanpy/blob/main/PRIVACY.MD"

View file

@ -126,10 +126,66 @@ This is a **pure static web app**. You can host it anywhere you want.
Two ways (choose one):
1. (Recommended) Go to [Releases](https://github.com/cheeaun/phanpy/releases) and download the latest `phanpy-dist.zip`. It's pre-built so don't need to run any install/build commands. Extract it. Serve the folder of extracted files.
2. Download or `git clone` this repository. Build it by running `npm run build` (after `npm install`). Serve the `dist` folder.
### Easy way
Try search for "how to self-host static sites" as there are many ways to do it.
Go to [Releases](https://github.com/cheeaun/phanpy/releases) and download the latest `phanpy-dist.zip` or `phanpy-dist.tar.gz`. It's pre-built so don't need to run any install/build commands. Extract it. Serve the folder of extracted files.
### Custom-build way
Download or `git clone` this repository. Build it by running `npm run build` (after `npm install`). Serve the `dist` folder.
Customization can be done by passing environment variables to the build command. Examples:
```bash
PHANPY_APP_TITLE="Phanpy Dev" \
PHANPY_WEBSITE="https://dev.phanpy.social" \
npm run build
```
```bash
PHANPY_DEFAULT_INSTANCE=hachyderm.io \
PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL=https://hachyderm.io/auth/sign_up \
PHANPY_PRIVACY_POLICY_URL=https://hachyderm.io/privacy-policy \
npm run build
```
It's also possible to set them in the `.env` file.
Available variables:
- `PHANPY_APP_TITLE` (optional, default: `Phanpy`) affects:
- Web page title, shown in the browser window or tab title
- App title, when installed as PWA, shown in the Home screen, macOS dock, Windows taskbar, etc
- OpenGraph card title, when shared on social networks
- Client name, when [registering the app for authentication](https://docs.joinmastodon.org/client/token/#app) and shown as client used on posts in some apps/clients
- `PHANPY_WEBSITE` (optional but recommended, default: `https://phanpy.social`) affects:
- Canonical URL of the website
- OpenGraph card URL, when shared on social networks
- Root path for the OpenGraph card image
- Client URL, when [registering the app for authentication](https://docs.joinmastodon.org/client/token/#app) and shown as client used on posts in some apps/clients
- `PHANPY_DEFAULT_INSTANCE` (optional, no defaults):
- e.g. 'mastodon.social', without `https://`
- Default instance for log-in
- When logging in, the user will be redirected instantly to the instance's authentication page instead of having to manually type the instance URL and submit
- `PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL` (optional, no defaults):
- URL of the instance registration page
- E.g. `https://mastodon.social/auth/sign_up`
- `PHANPY_PRIVACY_POLICY_URL` (optional, default to official instance's privacy policy):
- URL of the privacy policy page
- May specify the instance's own privacy policy
- `PHANPY_LINGVA_INSTANCES` (optional, space-separated list, default: `lingva.phanpy.social [...hard-coded list of fallback instances]`):
- Specify a space-separated list of instances. First will be used as default before falling back to the subsequent instances. If there's only 1 instance, means no fallback.
- May specify a self-hosted Lingva instance, powered by either [lingva-translate](https://github.com/thedaviddelta/lingva-translate) or [lingva-api](https://github.com/cheeaun/lingva-api)
- List of fallback instances hard-coded in `/.env`
- [↗️ List of lingva-translate instances](https://github.com/thedaviddelta/lingva-translate?tab=readme-ov-file#instances)
### Static site hosting
Try online search for "how to self-host static sites" as there are many ways to do it.
#### Lingva-translate or lingva-api hosting
See documentation for [lingva-translate](https://github.com/thedaviddelta/lingva-translate) or [lingva-api](https://github.com/cheeaun/lingva-api).
## Community deployments

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Compose / %VITE_CLIENT_NAME%</title>
<title>Compose / %PHANPY_CLIENT_NAME%</title>
<meta name="color-scheme" content="dark light" />
<meta name="google" content="notranslate" />
</head>

View file

@ -6,7 +6,7 @@
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>
<title>%VITE_CLIENT_NAME%</title>
<title>%PHANPY_CLIENT_NAME%</title>
<meta
name="description"
content="Minimalistic opinionated Mastodon web client"
@ -14,10 +14,10 @@
<meta name="color-scheme" content="dark light" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="%VITE_CLIENT_NAME%" />
<meta name="apple-mobile-web-app-title" content="%PHANPY_CLIENT_NAME%" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<link rel="canonical" href="%VITE_WEBSITE%" />
<link rel="canonical" href="%PHANPY_WEBSITE%" />
<meta
name=""
data-theme-setting="manual"
@ -46,13 +46,13 @@
<!-- Metacrap https://broken-links.com/2015/12/01/little-less-metacrap/ -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="og:url" content="%VITE_WEBSITE%" />
<meta property="og:title" content="%VITE_CLIENT_NAME%" />
<meta property="og:url" content="%PHANPY_WEBSITE%" />
<meta property="og:title" content="%PHANPY_CLIENT_NAME%" />
<meta
property="og:description"
content="Minimalistic opinionated Mastodon web client"
/>
<meta property="og:image" content="%VITE_WEBSITE%/og-image-2.jpg" />
<meta property="og:image" content="%PHANPY_WEBSITE%/og-image-2.jpg" />
</head>
<body>
<div id="app"></div>

View file

@ -1,7 +1,6 @@
// Fetch https://lingva.ml/api/v1/languages/{source|target}
import fs from 'fs';
fetch('https://lingva.ml/api/v1/languages/source')
fetch('https://lingva.phanpy.social/api/v1/languages/source')
.then((response) => response.json())
.then((json) => {
const file = './src/data/lingva-source-languages.json';
@ -9,7 +8,7 @@ fetch('https://lingva.ml/api/v1/languages/source')
fs.writeFileSync(file, JSON.stringify(json.languages, null, '\t'), 'utf8');
});
fetch('https://lingva.ml/api/v1/languages/target')
fetch('https://lingva.phanpy.social/api/v1/languages/target')
.then((response) => response.json())
.then((json) => {
const file = './src/data/lingva-target-languages.json';

View file

@ -12,19 +12,16 @@ import pmem from '../utils/pmem';
import Icon from './icon';
import Loader from './loader';
const { PHANPY_LINGVA_INSTANCES } = import.meta.env;
const LINGVA_INSTANCES = PHANPY_LINGVA_INSTANCES
? PHANPY_LINGVA_INSTANCES.split(/\s+/)
: [];
const throttle = pThrottle({
limit: 1,
interval: 2000,
});
// Using other API instances instead of lingva.ml because of this bug (slashes don't work):
// https://github.com/thedaviddelta/lingva-translate/issues/68
const LINGVA_INSTANCES = [
'lingva.phanpy.social',
'lingva.lunar.icu',
'lingva.garudalinux.org',
'translate.plausibility.cloud',
];
let currentLingvaInstance = 0;
function _lingvaTranslate(text, source, target) {
@ -243,4 +240,4 @@ function TranslationBlock({
);
}
export default TranslationBlock;
export default LINGVA_INSTANCES?.length ? TranslationBlock : () => null;

View file

@ -12,6 +12,8 @@ import { getAuthorizationURL, registerApplication } from '../utils/auth';
import store from '../utils/store';
import useTitle from '../utils/useTitle';
const { PHANPY_DEFAULT_INSTANCE: DEFAULT_INSTANCE } = import.meta.env;
function Login() {
useTitle('Log in');
const instanceURLRef = useRef();
@ -19,6 +21,7 @@ function Login() {
const [uiState, setUIState] = useState('default');
const [searchParams] = useSearchParams();
const instance = searchParams.get('instance');
const submit = searchParams.get('submit');
const [instanceText, setInstanceText] = useState(
instance || cachedInstanceURL?.toLowerCase() || '',
);
@ -129,6 +132,12 @@ function Login() {
submitInstance(selectedInstanceText);
};
if (submit) {
useEffect(() => {
submitInstance(instance || selectedInstanceText);
}, []);
}
return (
<main id="login" style={{ textAlign: 'center' }}>
<form onSubmit={onSubmit}>
@ -200,11 +209,13 @@ function Login() {
</div>
<Loader hidden={uiState !== 'loading'} />
<hr />
<p>
<a href="https://joinmastodon.org/servers" target="_blank">
Don't have an account? Create one!
</a>
</p>
{!DEFAULT_INSTANCE && (
<p>
<a href="https://joinmastodon.org/servers" target="_blank">
Don't have an account? Create one!
</a>
</p>
)}
<p>
<Link to="/">Go home</Link>
</p>

View file

@ -24,6 +24,10 @@ import store from '../utils/store';
const DEFAULT_TEXT_SIZE = 16;
const TEXT_SIZES = [15, 16, 17, 18, 19, 20];
const {
PHANPY_WEBSITE: WEBSITE,
PHANPY_PRIVACY_POLICY_URL: PRIVACY_POLICY_URL,
} = import.meta.env;
function Settings({ onClose }) {
const snapStates = useSnapshot(states);
@ -535,7 +539,7 @@ function Settings({ onClose }) {
</a>{' '}
&middot;{' '}
<a
href="https://github.com/cheeaun/phanpy/blob/main/PRIVACY.MD"
href={PRIVACY_POLICY_URL}
target="_blank"
rel="noopener noreferrer"
>
@ -544,7 +548,14 @@ function Settings({ onClose }) {
</p>
{__BUILD_TIME__ && (
<p>
Version:{' '}
{WEBSITE && (
<>
<span class="insignificant">Site:</span>{' '}
{WEBSITE.replace(/https?:\/\//g, '').replace(/\/$/, '')}
<br />
</>
)}
<span class="insignificant">Version:</span>{' '}
<input
type="text"
class="version-string"

View file

@ -75,6 +75,18 @@
margin-top: 0;
}
.app-site-version {
text-align: center;
opacity: 0.5;
color: var(--text-insignificant-color);
font-family: var(--monospace-font), monospace;
small {
font-size: 11px;
letter-spacing: -0.2px;
}
}
#why-container {
padding: 0 16px;
}

View file

@ -12,6 +12,21 @@ import Link from '../components/link';
import states from '../utils/states';
import useTitle from '../utils/useTitle';
const {
PHANPY_DEFAULT_INSTANCE: DEFAULT_INSTANCE,
PHANPY_WEBSITE: WEBSITE,
PHANPY_PRIVACY_POLICY_URL: PRIVACY_POLICY_URL,
PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL: DEFAULT_INSTANCE_REGISTRATION_URL,
} = import.meta.env;
const appSite = WEBSITE
? WEBSITE.replace(/https?:\/\//g, '').replace(/\/$/, '')
: null;
const appVersion = __BUILD_TIME__
? `${__BUILD_TIME__.slice(0, 10).replace(/-/g, '.')}${
__COMMIT_HASH__ ? `.${__COMMIT_HASH__}` : ''
}`
: null;
function Welcome() {
useTitle(null, ['/', '/welcome']);
return (
@ -33,18 +48,41 @@ function Welcome() {
</h1>
<p class="desc">A minimalistic opinionated Mastodon web client.</p>
<p>
<Link to="/login" class="button">
Log in with Mastodon
<Link
to={
DEFAULT_INSTANCE
? `/login?instance=${DEFAULT_INSTANCE}&submit=1`
: '/login'
}
class="button"
>
{DEFAULT_INSTANCE ? 'Log in' : 'Log in with Mastodon'}
</Link>
</p>
<p class="insignificant">
{DEFAULT_INSTANCE && DEFAULT_INSTANCE_REGISTRATION_URL && (
<p>
<a href={DEFAULT_INSTANCE_REGISTRATION_URL} class="button plain5">
Sign up
</a>
</p>
)}
{!DEFAULT_INSTANCE && (
<p class="insignificant">
<small>
Connect your existing Mastodon/Fediverse account.
<br />
Your credentials are not stored on this server.
</small>
</p>
)}
</div>
{(appSite || appVersion) && (
<p class="app-site-version">
<small>
Connect your existing Mastodon/Fediverse account.
<br />
Your credentials are not stored on this server.
{appSite} {appVersion}
</small>
</p>
</div>
)}
<p>
<a href="https://github.com/cheeaun/phanpy" target="_blank">
Built
@ -61,10 +99,7 @@ function Welcome() {
@cheeaun
</a>
.{' '}
<a
href="https://github.com/cheeaun/phanpy/blob/main/PRIVACY.MD"
target="_blank"
>
<a href={PRIVACY_POLICY_URL} target="_blank">
Privacy Policy
</a>
.

View file

@ -1,4 +1,4 @@
const { VITE_CLIENT_NAME: CLIENT_NAME, VITE_WEBSITE: WEBSITE } = import.meta
const { PHANPY_CLIENT_NAME: CLIENT_NAME, PHANPY_WEBSITE: WEBSITE } = import.meta
.env;
const SCOPES = 'read write follow push';

View file

@ -4,7 +4,7 @@ import { subscribeKey } from 'valtio/utils';
import states from './states';
const { VITE_CLIENT_NAME: CLIENT_NAME } = import.meta.env;
const { PHANPY_CLIENT_NAME: CLIENT_NAME } = import.meta.env;
export default function useTitle(title, path) {
function setTitle() {

View file

@ -2,6 +2,7 @@ import preact from '@preact/preset-vite';
import { execSync } from 'child_process';
import fs from 'fs';
import { resolve } from 'path';
import { uid } from 'uid/single';
import { defineConfig, loadEnv, splitVendorChunkPlugin } from 'vite';
import generateFile from 'vite-plugin-generate-file';
import htmlPlugin from 'vite-plugin-html-config';
@ -10,13 +11,19 @@ import removeConsole from 'vite-plugin-remove-console';
const { NODE_ENV } = process.env;
const {
VITE_CLIENT_NAME: CLIENT_NAME,
VITE_CLIENT_ID: CLIENT_ID,
VITE_APP_ERROR_LOGGING: ERROR_LOGGING,
PHANPY_CLIENT_NAME: CLIENT_NAME,
PHANPY_APP_ERROR_LOGGING: ERROR_LOGGING,
} = loadEnv('production', process.cwd());
const now = new Date();
const commitHash = execSync('git rev-parse --short HEAD').toString().trim();
let commitHash;
try {
commitHash = execSync('git rev-parse --short HEAD').toString().trim();
} catch (error) {
// If error, means git is not installed or not a git repo (could be downloaded instead of git cloned)
// Fallback to random hash which should be different on every build run 🤞
commitHash = uid();
}
const rollbarCode = fs.readFileSync(
resolve(__dirname, './rollbar.js'),
@ -26,6 +33,7 @@ const rollbarCode = fs.readFileSync(
// https://vitejs.dev/config/
export default defineConfig({
base: './',
envPrefix: ['VITE_', 'PHANPY_'],
mode: NODE_ENV,
define: {
__BUILD_TIME__: JSON.stringify(now),
@ -55,7 +63,6 @@ export default defineConfig({
]),
VitePWA({
manifest: {
id: CLIENT_ID,
name: CLIENT_NAME,
short_name: CLIENT_NAME,
description: 'Minimalistic opinionated Mastodon web client',