Merge 536b1fbc8b
into e4ff31c6c8
This commit is contained in:
commit
d199bd7df6
19 changed files with 321 additions and 3 deletions
14
conf/nginx-nodejs.conf
Normal file
14
conf/nginx-nodejs.conf
Normal file
|
@ -0,0 +1,14 @@
|
|||
#sub_path_only rewrite ^__PATH__$ __PATH__/ permanent;
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:__PORT__/;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_set_header X-Forwarded-Ssl on;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Forwarded-Scheme https;
|
||||
|
||||
proxy_buffering off;
|
||||
|
||||
# Include SSOWAT user panel.
|
||||
include conf.d/yunohost_panel.conf.inc;
|
||||
}
|
8
conf/nodejs-watcher.path
Normal file
8
conf/nodejs-watcher.path
Normal file
|
@ -0,0 +1,8 @@
|
|||
[Path]
|
||||
Unit=__APP__-nodejs-watcher.service
|
||||
|
||||
# Trigger on creation, deletion or change to a file
|
||||
PathChanged=__INSTALL_DIR__
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
13
conf/nodejs-watcher.service
Normal file
13
conf/nodejs-watcher.service
Normal file
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=__APP__ NodeJS restarter
|
||||
After=network.target
|
||||
StartLimitIntervalSec=10
|
||||
StartLimitBurst=5
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/systemctl restart __APP__-nodejs.service
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
54
conf/nodejs.service
Normal file
54
conf/nodejs.service
Normal file
|
@ -0,0 +1,54 @@
|
|||
[Unit]
|
||||
Description=__APP__ NodeJS Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=__APP__
|
||||
Group=__APP__
|
||||
WorkingDirectory=__INSTALL_DIR__/www
|
||||
StandardOutput=append:/var/log/__APP__-nodejs.log
|
||||
StandardError=inherit
|
||||
Environment=__YNH_NODE_LOAD_PATH__
|
||||
Environment=PORT=__PORT__
|
||||
Environment=NODE_ENV=production
|
||||
ExecStartPre=__YNH_NPM__ install
|
||||
ExecStartPre=__YNH_NPM__ run build
|
||||
ExecStart=__YNH_NPM__ run start
|
||||
|
||||
# Sandboxing options to harden security
|
||||
# Depending on specificities of your service/app, you may need to tweak these
|
||||
# .. but this should be a good baseline
|
||||
# Details for these options: https://www.freedesktop.org/software/systemd/man/systemd.exec.html
|
||||
NoNewPrivileges=yes
|
||||
PrivateTmp=yes
|
||||
PrivateDevices=yes
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
DevicePolicy=closed
|
||||
ProtectClock=yes
|
||||
ProtectHostname=yes
|
||||
ProtectProc=invisible
|
||||
ProtectSystem=full
|
||||
ProtectControlGroups=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
LockPersonality=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @setuid @swap @cpu-emulation @privileged
|
||||
|
||||
# Denying access to capabilities that should not be relevant for webapps
|
||||
# Doc: https://man7.org/linux/man-pages/man7/capabilities.7.html
|
||||
CapabilityBoundingSet=~CAP_RAWIO CAP_MKNOD
|
||||
CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE
|
||||
CapabilityBoundingSet=~CAP_SYS_BOOT CAP_SYS_TIME CAP_SYS_MODULE CAP_SYS_PACCT
|
||||
CapabilityBoundingSet=~CAP_LEASE CAP_LINUX_IMMUTABLE CAP_IPC_LOCK
|
||||
CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_WAKE_ALARM
|
||||
CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG
|
||||
CapabilityBoundingSet=~CAP_MAC_ADMIN CAP_MAC_OVERRIDE
|
||||
CapabilityBoundingSet=~CAP_NET_ADMIN CAP_NET_BROADCAST CAP_NET_RAW
|
||||
CapabilityBoundingSet=~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYSLOG
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -52,6 +52,15 @@ name = "My Webapp configuration"
|
|||
default = "low"
|
||||
help = "low: Personal usage, behind the sso. No RAM footprint when not used, but the impact on the processor can be high if many users are using the service.<br>medium: Low usage, few people or/and publicly accessible. Low RAM footprint, medium processor footprint when used.<br>high: High usage, frequently visited website. High RAM footprint, but lower on processor usage and quickly responding."
|
||||
|
||||
[main.nodejs]
|
||||
name = "NodeJS configuration"
|
||||
|
||||
[main.nodejs.nodeversion]
|
||||
ask = "NodeJS version"
|
||||
type = "select"
|
||||
choices = ["none", "18", "20", "21"]
|
||||
default = "none"
|
||||
|
||||
# TODO: Add protected_path as tags, which are created as permission "label (path)", so admin can protect a specific path
|
||||
# [main.permissions]
|
||||
# [main.permissions.proteced_path]
|
||||
|
|
13
doc/ADMIN.md
13
doc/ADMIN.md
|
@ -29,3 +29,16 @@ Once logged in, under the Web directory you will see a `www` folder which contai
|
|||
### Customizing the nginx configuration
|
||||
|
||||
If you want to add tweak the nginx configuration for this app, it is recommended to edit `/etc/nginx/conf.d/__DOMAIN__.d/__ID__.d/WHATEVER_NAME.conf` (ensure that the file has the `.conf` extension) and reload the nginx after making sure that the configuration is valid using `nginx -t`.
|
||||
|
||||
{% if nodeversion != 'none' %}
|
||||
|
||||
### Interfacing with NodeJS
|
||||
|
||||
A `package.json` should be available within the `/var/www/__APP__/www`. It is used to `npm install`, `npm run build` then `npm run start`. As such, it should at least define the dependencies and provide the `build` and `install` scripts.
|
||||
|
||||
You should then start a server in `/var/www/__APP__/www/index.js`.
|
||||
It should listen on the port provided through the `PORT` environment with `process.env.PORT` or statically with __PORT__.
|
||||
|
||||
The server should reload its files after they change, but due to systemd's limitations, it only works for top level folders/files.
|
||||
If your server does not display the right things, restart the `__APP__-nodejs` service.
|
||||
{% endif %}
|
||||
|
|
|
@ -29,3 +29,7 @@ Après vous être connecté, sous le répertoire Web vous verrez un dossier `www
|
|||
### Personnaliser la configuration nginx
|
||||
|
||||
Si vous souhaitez ajuster la configuration nginx pour cette app, il est recommandé d'éditer `/etc/nginx/conf.d/__DOMAIN__.d/__ID__.d/WHATEVER_NAME.conf` (assurez-vous que le fichier a l'extension `.conf`) puis rechargez nginx après vous être assuré que la configuration est valide à l'aide de `nginx -t`.
|
||||
|
||||
### Écouter le bon port dans NodeJS
|
||||
|
||||
Le port d'écoute est accessible par le processus node au travers de la variable d'environment `PORT`. Veillez à ce que votre fichier `.js` principal le récupère bien avec `process.env.PORT` car sa valeur n'est pas prédictible.
|
||||
|
|
|
@ -4,4 +4,6 @@ It can also create a MySQL database - which will be backed up and restored with
|
|||
|
||||
PHP-FPM version can also be selected among `none`, `7.4`, `8.0`, `8.1` and `8.2`.
|
||||
|
||||
Finally, NodeJS can alternatively be used instead of PHP, with versions `18`, `20` or `21`.
|
||||
|
||||
**Once installed, go to the chosen URL to know the user, domain and port you will have to use for the SFTP access.** The password is one you chosen during the installation. Under the Web directory, you will see a `www` folder which contains the public files served by this app. You can put all the files of your custom Web application inside.
|
||||
|
|
|
@ -4,4 +4,6 @@ Elle peut également créer une base de données MySQL - qui sera sauvegardée e
|
|||
|
||||
La version de PHP-FPM peut aussi être choisie, parmi `none`, `7.4`, `8.0`, `8.1` et `8.2`.
|
||||
|
||||
Un serveur NodeJS peut finalement être utilisé à la place de PHP, avec les versions `18`, `20` ou `21`.
|
||||
|
||||
**Une fois installé, rendez-vous sur l'URL choisie pour connaître l'utilisateur, le domaine et le port que vous devrez utiliser pour l'accès SFTP.** Le mot de passe est celui que vous avez choisi lors de l'installation. Sous le répertoire Web, vous verrez un dossier `www` qui contient les fichiers publics servis par cette application. Vous pouvez mettre tous les fichiers de votre application Web personnalisée à l'intérieur.
|
||||
|
|
|
@ -48,10 +48,21 @@ ram.runtime = "50M"
|
|||
[install.phpversion]
|
||||
ask.en = "Choose a PHP version you want to use for your app"
|
||||
ask.fr = "Choisissez une version PHP que vous souhaitez utiliser pour votre application"
|
||||
help.en = "You can only choose NodeJS or PHP, not both"
|
||||
help.fr = "Vous ne pouvez avoir que NodeJS ou PHP, pas les deux"
|
||||
type = "select"
|
||||
choices = ["none", "7.4", "8.0", "8.1", "8.2"]
|
||||
default = "8.0"
|
||||
|
||||
[install.nodeversion]
|
||||
ask.en = "Choose a NodeJS version you want to use for your app"
|
||||
ask.fr = "Choisissez une version NodeJS que vous souhaitez utiliser pour votre application"
|
||||
help.en = "You can only choose NodeJS or PHP, not both"
|
||||
help.fr = "Vous ne pouvez avoir que NodeJS ou PHP, pas les deux"
|
||||
type = "select"
|
||||
choices = ["none", "18", "20", "21"]
|
||||
default = "none"
|
||||
|
||||
[install.database]
|
||||
ask.en = "Do you need a database?"
|
||||
ask.fr = "Avez-vous besoin d'une base de données ?"
|
||||
|
@ -64,6 +75,9 @@ ram.runtime = "50M"
|
|||
|
||||
[resources.install_dir]
|
||||
|
||||
[resources.ports]
|
||||
main.default = 3000
|
||||
|
||||
[resources.permissions]
|
||||
main.url = "/"
|
||||
|
||||
|
|
|
@ -122,3 +122,53 @@ ynh_system_user_del_group() {
|
|||
gpasswd -d "$username" "$group"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
ynh_setup_my_nodeapp() {
|
||||
# Declare an array to define the options of this helper.
|
||||
local legacy_args=ai
|
||||
local -A args_array=([a]=app= [i]=install_dir=)
|
||||
local app
|
||||
local install_dir
|
||||
|
||||
ynh_handle_getopts_args "$@"
|
||||
|
||||
ynh_add_systemd_config --service="${app}-nodejs" --template="nodejs.service"
|
||||
ynh_add_systemd_config --service="${app}-nodejs-watcher" --template="nodejs-watcher.service"
|
||||
ynh_add_config --template="nodejs-watcher.path" --destination="/etc/systemd/system/${app}-nodejs-watcher.path"
|
||||
|
||||
systemctl enable "${app}-nodejs-watcher.path" --quiet
|
||||
systemctl daemon-reload
|
||||
|
||||
yunohost service add "${app}-nodejs" --description="$app NodeJS Server" --log="/var/log/$app-nodejs.log"
|
||||
ynh_systemd_action --service_name="${app}-nodejs"
|
||||
ynh_systemd_action --service_name="${app}-nodejs-watcher"
|
||||
ynh_systemd_action --service_name="${app}-nodejs-watcher.path"
|
||||
|
||||
# Add the config manually because yunohost does not support custom nginx confs
|
||||
ynh_add_config --template="nginx-nodejs.conf" --destination="/etc/nginx/conf.d/$domain.d/$app.conf"
|
||||
ynh_store_file_checksum --file="/etc/nginx/conf.d/$domain.d/$app.conf"
|
||||
ynh_systemd_action --service_name=nginx --action=reload
|
||||
|
||||
# Subsequent npm install will write to this folder (as it is within $app's home)
|
||||
# As such we prepare it with fitting rights
|
||||
mkdir -p "$install_dir/.npm"
|
||||
chown $app:$app "$install_dir/.npm"
|
||||
}
|
||||
|
||||
ynh_remove_my_nodeapp() {
|
||||
# Declare an array to define the options of this helper.
|
||||
local legacy_args=a
|
||||
local -A args_array=([a]=app=)
|
||||
local app
|
||||
|
||||
ynh_handle_getopts_args "$@"
|
||||
|
||||
yunohost service remove "${app}-nodejs"
|
||||
|
||||
ynh_remove_systemd_config --service="${app}-nodejs"
|
||||
ynh_remove_systemd_config --service="${app}-nodejs-watcher"
|
||||
ynh_secure_remove --file="/etc/systemd/system/${app}-nodejs-watcher.path"
|
||||
|
||||
ynh_remove_nodejs
|
||||
}
|
||||
|
|
|
@ -36,6 +36,17 @@ then
|
|||
ynh_backup --src_path="/etc/php/${phpversion}/fpm/pool.d/$app.conf"
|
||||
fi
|
||||
|
||||
#=================================================
|
||||
# BACKUP THE NodeJS CONFIGURATION
|
||||
#=================================================
|
||||
|
||||
if [ $nodeversion != "none" ]
|
||||
then
|
||||
ynh_backup --src_path="/etc/systemd/system/${app}-nodejs.service"
|
||||
ynh_backup --src_path="/etc/systemd/system/${app}-nodejs-watcher.service"
|
||||
ynh_backup --src_path="/etc/systemd/system/${app}-nodejs-watcher.path"
|
||||
fi
|
||||
|
||||
#=================================================
|
||||
# BACKUP THE MYSQL DATABASE
|
||||
#=================================================
|
||||
|
|
|
@ -105,6 +105,11 @@ ynh_app_config_validate() {
|
|||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${changed[nodeversion]}" == "true" ] && [ $nodeversion != "none" ] && [ $phpversion != "none" ]
|
||||
then
|
||||
ynh_die --message="You cannot have both PHP and NodeJS, choose only one."
|
||||
fi
|
||||
}
|
||||
|
||||
ynh_app_config_apply() {
|
||||
|
@ -152,6 +157,19 @@ ynh_app_config_apply() {
|
|||
then
|
||||
ynh_add_fpm_config --phpversion=$phpversion --usage=$fpm_usage --footprint=$fpm_footprint
|
||||
fi
|
||||
|
||||
if [ "${changed[nodeversion]}" == "true" ]
|
||||
then
|
||||
if [ "$nodeversion" != "none" ]
|
||||
then
|
||||
ynh_install_nodejs --nodejs_version=$nodeversion
|
||||
ynh_use_nodejs
|
||||
export port=$(ynh_app_setting_get $app port)
|
||||
ynh_setup_my_nodeapp --app=$app --install_dir=$install_dir
|
||||
else
|
||||
ynh_remove_my_nodeapp --app=$app
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
ynh_app_config_run $1
|
||||
|
|
|
@ -23,6 +23,7 @@ ssh_port=$(grep "^Port" /etc/ssh/sshd_config | awk '{print $2}')
|
|||
ynh_script_progression --message="Validating installation parameters..." --weight=2
|
||||
|
||||
[ $with_sftp -eq 0 ] || [ "$password" != "" ] || ynh_die --message="You need to set a password to enable SFTP"
|
||||
[ $phpversion != "none" ] || [ $nodeversion != "none" ] || ynh_die --message="Either PHP or NodeJS can be used, not both"
|
||||
|
||||
#=================================================
|
||||
# STORE SETTINGS FROM MANIFEST
|
||||
|
@ -134,6 +135,22 @@ then
|
|||
ynh_add_fpm_config --usage=$fpm_usage --footprint=$fpm_footprint --phpversion=$phpversion
|
||||
fi
|
||||
|
||||
#=================================================
|
||||
# NodeJS CONFIGURATION
|
||||
#=================================================
|
||||
|
||||
if [ $nodeversion != "none" ]
|
||||
then
|
||||
ynh_script_progression --message="Configuring NodeJS..." --weight=3
|
||||
|
||||
ynh_install_nodejs --nodejs_version=$nodeversion
|
||||
ynh_use_nodejs
|
||||
|
||||
ynh_add_config --template="../sources/www/package.json" --destination="$install_dir/www/package.json"
|
||||
ynh_add_config --template="../sources/www/index.js" --destination="$install_dir/www/index.js"
|
||||
ynh_setup_my_nodeapp --app=$app --install_dir=$install_dir
|
||||
fi
|
||||
|
||||
#=================================================
|
||||
# END OF SCRIPT
|
||||
#=================================================
|
||||
|
|
|
@ -46,6 +46,16 @@ ynh_script_progression --message="Removing PHP-FPM configuration..."
|
|||
# Remove the dedicated PHP-FPM config
|
||||
ynh_remove_fpm_config
|
||||
|
||||
#=================================================
|
||||
# REMOVE NodeJS CONFIGURATION
|
||||
#=================================================
|
||||
ynh_script_progression --message="Removing NodeJS configuration..."
|
||||
|
||||
if [ $nodeversion != "none" ]
|
||||
then
|
||||
ynh_remove_my_nodeapp --app=$app
|
||||
fi
|
||||
|
||||
#=================================================
|
||||
# END OF SCRIPT
|
||||
#=================================================
|
||||
|
|
|
@ -81,18 +81,36 @@ then
|
|||
ynh_restore_file --origin_path="/etc/php/${phpversion}/fpm/pool.d/$app.conf"
|
||||
fi
|
||||
|
||||
#=================================================
|
||||
# RESTORE THE NodeJS CONFIGURATION
|
||||
#=================================================
|
||||
|
||||
if [ $nodeversion != "none" ]
|
||||
then
|
||||
ynh_restore_file --origin_path="/etc/systemd/system/${app}-nodejs.service"
|
||||
ynh_restore_file --origin_path="/etc/systemd/system/${app}-nodejs-watcher.service"
|
||||
ynh_restore_file --origin_path="/etc/systemd/system/${app}-nodejs-watcher.path"
|
||||
mkdir -p "$install_dir"/.npm
|
||||
chown -R $app:$app "$install_dir"/.npm
|
||||
fi
|
||||
|
||||
#=================================================
|
||||
# GENERIC FINALIZATION
|
||||
#=================================================
|
||||
# RELOAD NGINX AND PHP-FPM
|
||||
#=================================================
|
||||
ynh_script_progression --message="Reloading NGINX web server and PHP-FPM..."
|
||||
ynh_script_progression --message="Reloading NGINX and the server..."
|
||||
|
||||
if [ $phpversion != "none" ]
|
||||
then
|
||||
ynh_systemd_action --service_name=php${phpversion}-fpm --action=reload
|
||||
fi
|
||||
|
||||
if [ $nodeversion != "none" ]
|
||||
then
|
||||
ynh_systemd_action --service_name=$app-nodejs --action=reload
|
||||
fi
|
||||
|
||||
ynh_systemd_action --service_name=nginx --action=reload
|
||||
|
||||
#=================================================
|
||||
|
|
|
@ -68,6 +68,12 @@ if [ -z "$phpversion" ]; then
|
|||
ynh_app_setting_set --app=$app --key=phpversion --value=$phpversion
|
||||
fi
|
||||
|
||||
# If phpversion doesn't exist, create it. We assume it is the default system one.
|
||||
if [ -z "$nodeversion" ]; then
|
||||
nodeversion="none"
|
||||
ynh_app_setting_set --app=$app --key=nodeversion --value=$nodeversion
|
||||
fi
|
||||
|
||||
# Delete old user
|
||||
if [ -n "$(ynh_app_setting_get --app=$app --key=user)" ]
|
||||
then
|
||||
|
@ -103,8 +109,17 @@ then
|
|||
fi
|
||||
|
||||
# Create a dedicated NGINX config
|
||||
ynh_add_nginx_config
|
||||
ynh_add_config --template="example-custom-nginx-config.conf" --destination="$nginx_extra_conf_dir/sample.conf"
|
||||
# Use a custom nginx config when using nodejs as it is incompatible with the html/php one
|
||||
if [ $nodeversion == "none" ]
|
||||
then
|
||||
ynh_add_nginx_config
|
||||
ynh_add_config --template="example-custom-nginx-config.conf" --destination="$nginx_extra_conf_dir/sample.conf"
|
||||
else
|
||||
# Add the config manually because yunohost does not support custom nginx confs
|
||||
ynh_add_config --template="nginx-nodejs.conf" --destination="/etc/nginx/conf.d/$domain.d/$app.conf"
|
||||
ynh_store_file_checksum --file="/etc/nginx/conf.d/$domain.d/$app.conf"
|
||||
ynh_systemd_action --service_name=nginx --action=reload
|
||||
fi
|
||||
|
||||
#=================================================
|
||||
# CREATE DEDICATED USER
|
||||
|
@ -156,6 +171,22 @@ setfacl -m g:$app:r-x "$install_dir"
|
|||
setfacl -m g:www-data:r-x "$install_dir"
|
||||
chmod 750 "$install_dir"
|
||||
|
||||
#=================================================
|
||||
# NodeJS CONFIGURATION
|
||||
#=================================================
|
||||
|
||||
if [ $nodeversion != "none" ]
|
||||
then
|
||||
ynh_script_progression --message="Updating NodeJS..." --weight=3
|
||||
|
||||
ynh_install_nodejs --nodejs_version=$nodeversion
|
||||
ynh_use_nodejs
|
||||
|
||||
ynh_add_config --template="../sources/www/package.json" --destination="$install_dir/www/package.json"
|
||||
ynh_add_config --template="../sources/www/index.js" --destination="$install_dir/www/index.js"
|
||||
ynh_setup_my_nodeapp --app=$app --install_dir=$install_dir
|
||||
fi
|
||||
|
||||
#=================================================
|
||||
# DEACTIVE MAINTENANCE MODE
|
||||
#=================================================
|
||||
|
|
19
sources/www/index.js
Normal file
19
sources/www/index.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
const http = require('node:http');
|
||||
const fs = require('fs');
|
||||
const index = fs.readFileSync('index.html').toString();
|
||||
|
||||
const host = '127.0.0.1';
|
||||
var port = process.env.PORT;
|
||||
port = (typeof port !== 'undefined') ? port : 3000;
|
||||
|
||||
const file = index.replace("<hr/>", `<hr/> <h2> Port configuration </h2> <p>Your node application have to listen on port ${port}. Alternatively, you can get port var from envirronment with the following: </p> <pre> process.env.PORT; </pre>`);
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end(file);
|
||||
});
|
||||
|
||||
server.listen(port, host, () => {
|
||||
console.log('Web server running at http://%s:%s', host, port);
|
||||
});
|
11
sources/www/package.json
Normal file
11
sources/www/package.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "www",
|
||||
"version": "1.0.0",
|
||||
"description": "dummy app",
|
||||
"author": "",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"build": "exit 0"
|
||||
},
|
||||
"license": "ISC"
|
||||
}
|
Loading…
Add table
Reference in a new issue