Fix bugs, many other changes

Signed-off-by: Izuru Yakumo <yakumo.izuru@chaotic.ninja>

git-svn-id: file:///srv/svn/repo/mai/trunk@67 e410bdd4-646f-c54f-a7ce-fffcc4f439ae
This commit is contained in:
yakumo.izuru 2024-02-07 00:26:14 +00:00
parent 2435673dff
commit b860f0610b
22 changed files with 172 additions and 501 deletions

13
HISTORY.md Normal file
View File

@ -0,0 +1,13 @@
## 2021
[SimplyTranslate](https://codeberg.org/SimpleWeb/SimplyTranslate-Web) was founded by metalune and fattalion.
It was written in Python and used the Quart framework.
## 2022
fattalion created a Go implementation.
## 2023
* Both metalune and fattalion retired, and they handed SimplyTranslate over to [ManeraKai](https://manerakai.com).
* Arya K. from [~vern](https://vern.cc) made a hard-fork named [Mozhi](https://codeberg.org/aryak/mozhi) and has vastly refactored it.
## 2024
Izuru Yakumo took interest in SimplyTranslate and decided to fork it in October 2023, and in January he turned it into a hard-fork, eventually renaming it to "Mai", after a character from the fifth [Touhou Project](https://en.touhouwiki.net/wiki/Touhou_Project) game, [Mai](https://en.touhouwiki.net/wiki/Mai).

11
INSTALL.md Normal file
View File

@ -0,0 +1,11 @@
# Installation
```shell
$ git clone https://git.chaotic.ninja/yakumo.izuru/mai
$ cd mai
$ make
# make PREFIX=/usr/local install
```
* Read the [mai.ini(5)](mai.ini.5) manual page
* Use any web server than is able to reverse proxy, like Apache, h2o, or NGINX.

6
INSTANCES.md Normal file
View File

@ -0,0 +1,6 @@
# List of known instances
* [tr.chaotic.ninja](https://tr.chaotic.ninja)
* Location: Germany
* Cloudflare: No

9
LEGAL.md Normal file
View File

@ -0,0 +1,9 @@
Mai does not host any content. All content shown on any Mai instances is from [Google Translate](https://translate.google.com), [Reverso](https://www.reverso.net/), and [LibreTranslate](https://libretranslate.com)
Mai is not affiliated with none of the above, which this program relays.
Trademarks belong to their respective owners.
Google Translate is a trademark of [Google LLC](https://www.google.com). Reverso is a trademark of Reverso, et cetera.
The creators and maintainers of this repository assume no liability for the accuracy and timeliness of any information provided above. Trademark owner information was researched to the best of the author's knowledge at the time of curation and may be outdated or incorrect.

View File

@ -1,45 +1,9 @@
## Mai
A privacy friendly frontend to multiple translation engines.
### History
1. SimplyTranslate was founded by [metalune and fattalion](https://codeberg.org/SimpleWeb/SimplyTranslate-Web). It was written in Python.
2. Fattalion created a Go implementation.
3. Both metalune and fattalion retired, and they handed SimplyTranslate over to ManeraKai.
4. [Izuru Yakumo The Violet Hermit](https://geidontei.chaotic.ninja/usr/yakumo_izuru) stole it, and renamed it after [Mai](https://en.touhouwiki.net/wiki/Mai) from [Mystic Square](https://en.touhouwiki.net/wiki/Mystic_Square)
### Instances
| URL | Location | Cloudflare? |
|-----|----------|-------------|
| [tr.chaotic.ninja](https://tr.chaotic.ninja) | DE | No |
### Installation
```shell
% git clone https://git.chaotic.ninja/yakumo.izuru/mai
% cd mai
% make
# make install
```
### Setup
For [nginx](https://www.nginx.com) you can use this snippet, this also serves the static resources.
```nginx
location / {
proxy_set_header Host $host;
proxy_pass http://localhost:5000;
}
```
### Legal notice
Mai does not host any content. All content shown on any Mai instances is from [Google Translate](https://translate.google.com), [Reverso](https://www.reverso.net/), [iCIBA](https://www.iciba.net) and [LibreTranslate](https://libretranslate.com)
Mai is not affiliated with none of the above, which this program relays.
Trademarks belong to their respective owners.
Google Translate is a trademark of [Google LLC](https://www.google.com). Reverso is a trademark of Reverso, et cetera.
The creators and maintainers of this repository assume no liability for the accuracy and timeliness of any information provided above. Trademark owner information was researched to the best of the author's knowledge at the time of curation and may be outdated or incorrect.
* [How to install](INSTALL.md)
* [List of instances](INSTANCES.md)
* [Legal notice](LEGAL.md)
### Other projects
* [Mozhi](https://codeberg.org/aryak/mozhi), also a fork of SimplyTranslate

View File

@ -21,13 +21,13 @@ import (
)
var (
configfile string
groupname string
username string
)
var conf struct {
group string
listen string
staticpath string
tmplpath string
user string
}
func main() {
parseFlags()
@ -41,8 +41,8 @@ func main() {
conf.staticpath = "./static"
conf.tmplpath = "./views"
if conf.user != "" {
uid, gid, err := usergroupids(conf.user, conf.group)
if username != "" {
uid, gid, err := usergroupids(username, groupname)
if err != nil {
fmt.Println(err)
os.Exit(1)
@ -258,6 +258,9 @@ func main() {
}
}
})
app.Get("/robots.txt", func(c *fiber.Ctx) error {
return c.SendString("User-Agent: *\nDisallow: /\n")
})
app.Get("/version", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"fiberversion": fiber.Version,

View File

@ -7,5 +7,7 @@ import (
func parseFlags() {
flag.StringVar(&configfile, "f", "", "Configuration file")
flag.StringVar(&username, "u", "", "Sets the user to which privilege dropping is done")
flag.StringVar(&groupname, "g", "", "Sets the group to which privilege dropping is done")
flag.Parse()
}

View File

@ -10,11 +10,9 @@ func readConf(file string) error {
if err != nil {
return err
}
conf.group = cfg.Section("mai").Key("group").String()
conf.listen = cfg.Section("mai").Key("listen").String()
conf.staticpath = cfg.Section("mai").Key("static").String()
conf.tmplpath = cfg.Section("mai").Key("templates").String()
conf.user = cfg.Section("mai").Key("user").String()
return nil
}

View File

@ -14,7 +14,7 @@ func usergroupids(username string, groupname string) (int, int, error) {
uid, _ := strconv.Atoi(u.Uid)
gid, _ := strconv.Atoi(u.Gid)
if conf.group != "" {
if groupname != "" {
g, err := user.LookupGroup(groupname)
if err != nil {
return uid, -1, err

View File

@ -7,49 +7,68 @@
<title>API Documentation | Mai</title>
</head>
<body id="body">
<h1 id="header">API Documentation</h1>
<br><hr>
<h2 class="get">[GET] /api/translate</h2>
<h2 class="post">[POST] /api/translate</h2>
<h3>Description</h3>
<p>Translation endpoint, input must be URL-encoded (e.g. multi-byte characters, words separated by space)</p>
<h3>Arguments</h3>
<ul>
<li>engine</li>
<li>from</li>
<li>text</li>
<li>to</li>
</ul>
<h3>Examples</h3>
<p><pre><code>GET /api/translate?engine=google&amp;from=auto&amp;to=en&amp;text="sonrisa"</code></pre></p>
<hr>
<h2 class="get">[GET] /api/source_languages</h2>
<h2 class="get">[GET] /api/target_languages</h2>
<h3>Description</h3>
<p>Get a JSON array of supported source and target languages for a particular engine</p>
<h3>Arguments</h3>
<ul>
<li>engine</li>
</ul>
<h3>Examples</h3>
<p><pre><code>GET /api/source_languages?engine=google</code></pre></p>
<p><pre><code>GET /api/target_languages?engine=google</code></pre></p>
<hr>
<h2 class="get">[GET] /api/tts</h2>
<h3>Description</h3>
<p>Obtain text-to-speech audio files from an engine, provided said engine supports them</p>
<h3>Arguments</h3>
<ul>
<li>engine</li>
<li>lang</li>
<li>text</li>
</ul>
<h3>Examples</h3>
<p><pre><code>GET /api/tts?engine=google&amp;lang=en&amp;text="hi"</code></pre></p>
<hr>
<h2 class="post">[POST] /switchlanguages</h2>
<h3>Description</h3>
<p>Switch between source and target languages, as long as the source language isn't "auto"</p>
<hr>
<h1>API documentation</h1>
<table border="1" align="center">
<tr>
<td>
<h2 class="get">[GET] /api/translate</h2>
<h2 class="post">[POST] /api/translate</h2>
<h3>Description</h3>
<p>Translation endpoint, input must be URL-encoded (e.g. multi-byte characters, words separated by space)</p>
<h3>Arguments</h3>
<ul>
<li>engine</li>
<li>from</li>
<li>text</li>
<li>to</li>
</ul>
</td>
</tr>
<tr>
<td>
<h2 class="get">[GET] /api/source_languages</h2>
<h2 class="get">[GET] /api/target_languages</h2>
<h3>Description</h3>
<p>Get a JSON array of supported source and target languages for a particular engine</p>
<h3>Arguments</h3>
<ul>
<li>engine</li>
</ul>
</td>
</tr>
<tr>
<td>
<h2 class="get">[GET] /api/tts</h2>
<h3>Description</h3>
<p>Obtain text-to-speech audio files from an engine, provided said engine supports them</p>
<h3>Arguments</h3>
<ul>
<li>engine</li>
<li>lang</li>
<li>text</li>
</ul>
</td>
</tr>
<tr>
<td>
<h2 class="get">[GET] /robots.txt</h2>
</td>
</tr>
<tr>
<td>
<h2 class="post">[POST] /switchlanguages</h2>
<h3>Description</h3>
<p>Switch between source and target languages, as long as the source language isn't "auto"</p>
<p>Must only be called inside the form interface</p>
</td>
</tr>
<tr>
<td>
<h2 class="get">[GET] /version</h2>
<h3>Description</h3>
<p>Return the software version as a JSON array</p>
</td>
</tr>
</table>
</body>
</html>

View File

@ -6,12 +6,27 @@
<meta name="author" content="Izuru Yakumo">
<title>Mai | Documentation</title>
</head>
<body id="body">
<img src="/static/favicon128x128.png" style="text-align: center;">
<h1 id="header">Documentation</h1>
<ul id="nav">
<li><a href="/docs/api">API</a></li>
</ul>
<body>
<table>
<tr><td>
<table border="1" align="left">
<tr>
<td><a href="/docs/api">API</a></td>
</tr>
</table>
</tr></td>
<tr><td>
<table border="1" align="center">
<tr>
<td>
<p>Welcome to Mai's documentation!<p>
<p>See the menu for the available entries</p>
<p>Anything else is covered by the man pages</p>
</td>
</tr>
</table>
</td></tr>
</table>
</body>
</html>

View File

@ -1,114 +1,22 @@
/*
* Taken from vnpower
*/
* {
box-sizing: border-box; }
body {
top: 0;
margin: 0;
background-color: #1d1f21;
color: white;
font-family: "Open Sans", "Roboto", "Noto Sans", "Noto Sans CJK JP", sans-serif;
line-height: 1.618;
background-color: #f8cfd2;
color: rgb(206, 147, 191);
}
h2.get {
color: orange;
a, a:visited {
color: rgb(206, 147, 191);
}
h2.post {
color: green;
@media screen and (prefers-color-scheme: dark) {
body {
background-color: #700000;
color: #ffffff;
}
a, a:visited {
color: #18c018;
}
}
@media (max-width: 684px) {
body {
font-size: 1rem; } }
@media (max-width: 382px) {
body {
font-size: 1rem; } }
a {
color: #81a2be;
text-decoration-color: #81a2be; }
#header {
width: 100%;
min-width: 320px;
background-color: #282a2e; }
#header img {
width: 280px; }
#header-content {
margin: 0 auto;
width: min-content; }
#nav {
display: flex;
justify-content: center;
background-color: #373b41; }
#footer {
margin-top: 0.5rem;
text-align: center; }
#body {
min-height: 75vh;
max-width: 800px;
margin: 1rem auto;
padding: 0 0.6rem; }
#body h1,
#body h2,
#body h3,
#body h4,
#body h5,
#body h6 {
line-height: 1.1;
font-family: sans-serif;
font-weight: 700;
margin-top: 1rem;
margin-bottom: 1rem;
overflow-wrap: break-word;
word-wrap: break-word;
-ms-word-break: break-all;
word-break: break-word; }
#body h1 {
border-bottom: 1px solid #b5bd68;
display: block; }
#body p {
margin-top: 0px;
margin-bottom: 0.5rem; }
#body li > p {
padding: 0;
margin: 0; }
#body small,
#body sub,
#body sup {
font-size: 75%; }
#body img,
#body video {
height: auto;
max-width: 100%;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.12), 0px 2px 2px 0px rgba(0, 0, 0, 0.12), 0px 4px 4px 0px rgba(0, 0, 0, 0.12), 0px 8px 8px 0px rgba(0, 0, 0, 0.12), 0px 16px 16px 0px rgba(0, 0, 0, 0.12); }
#body pre {
background-color: #373b41;
display: block;
padding: 1em;
overflow-x: auto;
margin-top: 0px;
margin-bottom: 0.5rem; }
#body code, #body kbd, #body samp {
padding: 0 0.5em;
background-color: #373b41;
white-space: pre-wrap; }
#body pre > code {
padding: 0;
background-color: transparent;
white-space: pre;
font-size: 1em; }
table {
border-color: white;
}

View File

@ -24,7 +24,6 @@ type Language map[string]string
var Engines = map[string]Engine{
"google": &GoogleTranslate{},
"reverso": &Reverso{},
"iciba": &ICIBA{},
"libretranslate": &LibreTranslate{
InstanceURL: os.Getenv("MAI_LIBRETRANSLATE_INSTANCE"),
APIKey: os.Getenv("MAI_LIBRETRANSLATE_API"),

View File

@ -1,283 +0,0 @@
package engines
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
// iCIBA is an engine that fetches data from https://www.iciba.com.
type ICIBA struct{}
func (_ *ICIBA) DisplayName() string { return "iCIBA" }
var icibaLanguages = Language{
// ICIBA does have an API, but they return Chinese names.
// For languages already present in Google translate, the English
// names in that engine file are used; Otherwise official names
// as researched on Wikipedia are used. They're validated against
// the Chinese names to the best of my ability.
// Missing "cni", "kbh", "tmh"
// due to conflict between ISO-639 table and Chinese label
// one "//" means on iciba but not on google
"ace": "Achinese", //
"acu": "Achuar-Shiwiar", //
"af": "Afrikaans",
"agr": "Aguaruna", //
"ake": "Akawaio", //
"sq": "Albanian",
"am": "Amharic",
"ar": "Arabic",
"hy": "Armenian",
"az": "Azerbaijani",
"bsn": "Barasana-Eduria", //
"ba": "Bashkir", //
"eu": "Basque",
"be": "Belarusian",
"bem": "Bemba", //
"bn": "Bengali",
"ber": "Berber", //
"bi": "Bislama", //
"bs": "Bosnian",
"br": "Breton", //
"bg": "Bulgarian",
"cjp": "Cabécar", //
"yue": "Cantonese",
"ca": "Catalan",
"ceb": "Cebuano",
"cha": "Chamorro", //
"chr": "Cherokee", //
"ny": "Chichewa",
"zh": "Chinese (Simplified)", // "zh-cn" on Google
"cht": "Chinese (Traditional)", // "zh-tw" on Google
"cv": "Chuvash",
"cop": "Coptic", //
"co": "Corsican",
"hr": "Croatian",
"cs": "Czech",
"da": "Danish",
"dv": "Dhivehi", //
"dik": "Dinka", //
"nl": "Dutch",
"dz": "Dzongkha", //
"en": "English",
"eo": "Esperanto",
"et": "Estonian",
"ee": "Ewe", //
"fo": "Faroese", //
"fj": "Fijian", //
"fil": "Filipino", // "tl" on Google
"fi": "Finnish",
"fr": "French",
"fy": "Frisian",
"gbi": "Galela", //
"gl": "Galician",
"lg": "Ganda", //
"jy": "Georgian", // "ka" on Google
"de": "German",
"el": "Greek",
"amu": "Guerrero Amuzgo", //
"gu": "Gujarati",
"ht": "Haitian Creole",
"ha": "Hausa",
"haw": "Hawaiian",
"he": "Hebrew", // "iw" on Google
"hi": "Hindi",
"mww": "Hmong Daw", //
"hmn": "Hmong", // not in iciba
"hu": "Hungarian",
"is": "Icelandic",
"ig": "Igbo",
"id": "Indonesian",
"ga": "Irish",
"it": "Italian",
"jac": "Jacalteco", //
"ja": "Japanese",
"jv": "Javanese", // "jw" on Google
"kab": "Kabyle", //
"kn": "Kannada",
"cak": "Kaqchikel", //
"ka": "Kazakh", // Google only has "kk"
"kk": "Kazakh (Cyrillic)", // Google has it as just "Kazakh"
"kek": "Kekchí", //
"km": "Khmer",
"rw": "Kinyarwanda",
"kg": "Kongo", //
"ko": "Korean",
"ku": "Kurdish (Kurmanji)",
"ky": "Kyrgyz",
"lo": "Lao",
"la": "Latin",
"lv": "Latvian",
"ln": "Lingala", //
"lt": "Lithuanian",
"dop": "Lukpa", //
"lb": "Luxembourgish",
"mk": "Macedonian",
"mg": "Malagasy",
"ms": "Malay",
"ml": "Malayalam",
"mt": "Maltese",
"mam": "Mam", //
"gv": "Manx", //
"mi": "Maori",
"mr": "Marathi",
"mhr": "Mari (Eastern)", //
"mrj": "Mari (Western)", //
"mn": "Mongolian",
"me": "Montenegrin", //
"my": "Myanmar (Burmese)",
"nhg": "Nahuatl", //
"djk": "Ndyuka", //
"ne": "Nepali",
"no": "Norwegian",
"or": "Odia (Oriya)",
"ojb": "Ojibwa",
"om": "Oromo", //
"os": "Ossetian", //
"pck": "Paite", //
"pap": "Papiamento", //
"ps": "Pashto",
"fa": "Persian",
"pl": "Polish",
"pt": "Portuguese",
"pot": "Potawatomi", //
"pa": "Punjabi",
"otq": "Querétaro Otomi", //
"quc": "Quiché", //
"quw": "Quichua", //
"chq": "Quiotepec Chinantec", //
"rmn": "Romani", //
"ro": "Romanian",
"rn": "Rundi", //
"ru": "Russian",
"sm": "Samoan",
"sg": "Sango", //
"gd": "Scots Gaelic",
"sr": "Serbian",
"crs": "Seselwa Creole French", //
"st": "Sesotho",
"sn": "Shona",
"jiv": "Shuar", //
"sd": "Sindhi",
"si": "Sinhala",
"sk": "Slovak",
"sl": "Slovenian",
"so": "Somali",
"es": "Spanish",
"su": "Sundanese",
"sw": "Swahili",
"sv": "Swedish",
"syc": "Syriac", // considered "extinct" but is somehow supported
"shi": "Tachelhit", //
"ty": "Tahitian", //
"tg": "Tajik",
"ta": "Tamil",
"tt": "Tatar",
"te": "Telugu",
"tet": "Tetum", //
"th": "Thai",
"ti": "Tigre", //
"tw": "Tiwi", //
"tpi": "Tok Pisin", //
"to": "Tonga", //
"ts": "Tsonga",
"tn": "Tswana", //
"tr": "Turkish",
"tk": "Turkmen",
"udm": "Udmurt", //
"uk": "Ukrainian",
"ppk": "Uma", //
"ur": "Urdu",
"usp": "Uspanteco", //
"uy": "Uyghur", // "ug" on Google
"uz": "Uzbek",
"ve": "Venda", //
"vi": "Vietnamese",
"war": "Waray", //
"cy": "Welsh",
"wal": "Wolaitta", //
"wol": "Wolof",
"xh": "Xhosa",
"yi": "Yiddish",
"yo": "Yoruba",
"yua": "Yucatán Maya", //
"dje": "Zarma", //
"zu": "Zulu",
}
func (_ *ICIBA) SourceLanguages() (Language, error) {
icibaLanguagesCopy := make(Language)
for k, v := range icibaLanguages {
icibaLanguagesCopy[k] = v
}
icibaLanguagesCopy["auto"] = "Detect Language"
return icibaLanguagesCopy, nil
}
func (_ *ICIBA) TargetLanguages() (Language, error) { return icibaLanguages, nil }
func (_ *ICIBA) Translate(text string, from, to string) (TranslationResult, error) {
requestURL, _ := url.Parse("https://ifanyi.iciba.com/index.php")
query := url.Values{}
query.Add("c", "trans")
query.Add("m", "fy")
query.Add("client", "6")
query.Add("auth_user", "key_web_fanyi")
sum := md5.Sum([]byte(("6key_web_fanyiifanyiweb8hc9s98e" + text)))
query.Add("sign", hex.EncodeToString(sum[:])[:16])
requestURL.RawQuery = query.Encode()
formData := url.Values{}
formData.Add("from", from)
formData.Add("to", to)
formData.Add("q", text)
response, err := http.PostForm(requestURL.String(), formData)
if err != nil {
return TranslationResult{}, err
}
defer response.Body.Close()
if response.StatusCode != 200 {
return TranslationResult{}, fmt.Errorf("got status code %d from iCIBA", response.StatusCode)
}
var responseJSON struct {
Content struct {
From string `json:"from"`
Out string `json:"out"`
} `json:"content"`
}
if err := json.NewDecoder(response.Body).Decode(&responseJSON); err != nil {
return TranslationResult{}, err
}
for code := range icibaLanguages {
if code == responseJSON.Content.From {
from = code
break
}
}
if from == "" {
return TranslationResult{TranslatedText: responseJSON.Content.Out},
fmt.Errorf("language code \"%s\" is not in iCIBA's language list", responseJSON.Content.From)
}
return TranslationResult{
SourceLanguage: from,
TranslatedText: responseJSON.Content.Out,
}, nil
}
func (_ *ICIBA) Tts(text, lang string) (string, error) { return "", nil }

View File

@ -2,4 +2,3 @@
listen = "127.0.0.1:5000"
rootdir = "./static"
tmplpath = "./views"
user = "www"

14
mai.1
View File

@ -7,6 +7,8 @@
.Sh SYNOPSIS
.Nm
.Op Fl f Ar config-file
.Op Fl u Ar user
.Op Fl g Ar group
.Sh DESCRIPTION
This project has been forked from the remains of the
rewrite of the SimplyTranslate project, which is currently
@ -14,11 +16,19 @@ maintained by ManeraKai, but as time passed, this fork
diverged wildly, and was renamed.
Much to anyone's dismay, this was named after
.Lk https://en.touhouwiki.net/wiki/Mai Mai from Mystic Square
.Sh FEATURES
.Bl -tag -width 11n -compact
.It Command-line tool
See
.Xr suwako 1
.It Privilege dropping
Stolen from
.Xr partage 1
just so you do not run this as root
.El
.Sh AUTHORS
.An fattalion
.An metalune
.An ManeraKai Lk https://manerakai.com
.Sh MAINTAINERS
.An Izuru Yakumo Aq Mt yakumo.izuru@chaotic.ninja
.Sh BUGS
iCIBA engine doesn't work, throws HTTP 500

View File

@ -7,9 +7,6 @@
.Sh OPTIONS
.Ss [mai] section
.Bl -tag -width 11n
.It group
Group ID for dropping privileges to.
If unset, assume the GID of user
.It listen
HTTP port for the server to listen.
Default is "localhost:5000"
@ -19,8 +16,6 @@ Default is "./static"
.It templates
Directory where the templates are located.
Default is "./views"
.It user
User ID for dropping privileges to.
.El
.Sh ENVIRONMENT
.Bl -tag -width 11n

View File

@ -20,7 +20,7 @@ load_rc_config $name
pidfile="/var/run/${name}.pid"
command="/usr/sbin/daemon"
procname="/usr/local/bin/${name}"
mai_args="-f ${mai_config}"
mai_args="-f ${mai_config} -u www -g www"
command_args="-S -m 3 -s info -l daemon -p ${pidfile} /usr/bin/env ${procname} ${mai_args}"
required_files="${mai_config}"

View File

@ -11,7 +11,7 @@ $_rc_subr_loaded . /etc/rc.subr
name="mai"
rcvar=$name
command="/usr/pkg/bin/mai"
command_args="-f /usr/pkg/etc/mai/mai.ini"
command_args="-f /usr/pkg/etc/mai/mai.ini -u www -g www"
pidfile="/var/run/${name}.pid"
start_cmd="mai_start"

View File

@ -2,7 +2,7 @@
# $TheSupernovaDuo$
daemon="/usr/local/bin/mai"
daemon_flags="-f /usr/local/etc/mai/mai.ini"
daemon_flags="-f /usr/local/etc/mai/mai.ini -u www -g www"
. /etc/rc.d/rc.subr

View File

@ -1 +1,2 @@
cmd: /usr/local/bin/mai -f /usr/local/etc/mai/mai.ini
cmd: /usr/local/bin/mai -f /usr/local/etc/mai/mai.ini -g www -u www
cwd: /usr/local/share/mai

View File

@ -127,8 +127,10 @@
<br><br><br><br><br>
<footer class="center">
<p>
<a href="https://git.chaotic.ninja/yakumo.izuru/mai">Source code</a><br>
a <em>Mirage AIB</em> project | Favicon source is <a href="https://pixiv.net/en/artworks/97787072">ユキマイ</a>
<a href="https://git.chaotic.ninja/yakumo.izuru/mai">Source code</a>
</p>
<p>
A <a href="https://mirage.h0stname.net">Mirage AIB</a> project
</p>
</footer>
<script src="/static/script.js"></script>