diff --git a/ct/piler.sh b/ct/deferred/piler.sh similarity index 100% rename from ct/piler.sh rename to ct/deferred/piler.sh diff --git a/ct/headers/wishlist b/ct/headers/wishlist deleted file mode 100644 index a55ad71bc..000000000 --- a/ct/headers/wishlist +++ /dev/null @@ -1,6 +0,0 @@ - _ ___ __ ___ __ -| | / (_)____/ /_ / (_)____/ /_ -| | /| / / / ___/ __ \/ / / ___/ __/ -| |/ |/ / (__ ) / / / / (__ ) /_ -|__/|__/_/____/_/ /_/_/_/____/\__/ - diff --git a/ct/wger.sh b/ct/wger.sh index 8629b4de3..845745ff5 100644 --- a/ct/wger.sh +++ b/ct/wger.sh @@ -31,7 +31,7 @@ function update_script() { if check_for_gh_release "wger" "wger-project/wger"; then msg_info "Stopping Service" - systemctl stop apache2 + systemctl stop redis-server nginx celery celery-beat wger msg_ok "Stopped Service" msg_info "Backing up Data" @@ -45,6 +45,7 @@ function update_script() { cp -r /opt/wger_media_backup/. /opt/wger/media cp /opt/wger_env_backup /opt/wger/.env rm -rf /opt/wger_media_backup /opt/wger_env_backup + msg_ok "Restored Data" msg_info "Updating wger" @@ -56,9 +57,9 @@ function update_script() { $STD uv run python manage.py collectstatic --no-input msg_ok "Updated wger" - msg_info "Starting Service" - systemctl start apache2 - msg_ok "Started Service" + msg_info "Starting Services" + systemctl start redis-server nginx celery celery-beat wger + msg_ok "Started Services" msg_ok "Updated Successfully" fi exit @@ -71,4 +72,4 @@ description msg_ok "Completed Successfully!\n" echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" echo -e "${INFO}${YW} Access it using the following URL:${CL}" -echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}" diff --git a/ct/wishlist.sh b/ct/wishlist.sh deleted file mode 100644 index e533463e3..000000000 --- a/ct/wishlist.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash -source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) -# Copyright (c) 2021-2026 community-scripts ORG -# Author: Dunky13 -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/cmintey/wishlist - -APP="Wishlist" -var_tags="${var_tags:-sharing}" -var_cpu="${var_cpu:-2}" -var_ram="${var_ram:-2048}" -var_disk="${var_disk:-5}" -var_os="${var_os:-debian}" -var_version="${var_version:-13}" -var_unprivileged="${var_unprivileged:-1}" - -header_info "$APP" -variables -color -catch_errors -function update_script() { - header_info - check_container_storage - check_container_resources - if [[ ! -d /opt/wishlist ]]; then - msg_error "No ${APP} Installation Found!" - exit - fi - - if check_for_gh_release "wishlist" "cmintey/wishlist"; then - NODE_VERSION="24" NODE_MODULE="pnpm" setup_nodejs - - msg_info "Stopping Service" - systemctl stop wishlist - msg_ok "Stopped Service" - - mkdir -p /opt/wishlist-backup - cp /opt/wishlist/.env /opt/wishlist-backup/.env - cp -a /opt/wishlist/uploads /opt/wishlist-backup - cp -a /opt/wishlist/data /opt/wishlist-backup - - CLEAN_INSTALL=1 fetch_and_deploy_gh_release "wishlist" "cmintey/wishlist" "tarball" - LATEST_APP_VERSION=$(get_latest_github_release "cmintey/wishlist" false) - - msg_info "Updating Wishlist" - cd /opt/wishlist - $STD pnpm install - $STD pnpm svelte-kit sync - $STD pnpm prisma generate - sed -i 's|/usr/src/app/|/opt/wishlist/|g' $(grep -rl '/usr/src/app/' /opt/wishlist) - export VERSION="${LATEST_APP_VERSION}" - export SHA="${LATEST_APP_VERSION}" - $STD pnpm run build - $STD pnpm prune --prod - chmod +x /opt/wishlist/entrypoint.sh - cp /opt/wishlist-backup/.env /opt/wishlist/.env - cp -a /opt/wishlist-backup/uploads /opt/wishlist - cp -a /opt/wishlist-backup/data /opt/wishlist - rm -rf /opt/wishlist-backup - msg_ok "Updated Wishlist" - msg_info "Starting Service" - systemctl start wishlist - msg_ok "Started Service" - msg_ok "Updated successfully!" - fi - exit -} - -start -build_container -description - -msg_ok "Completed Successfully!\n" -echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" -echo -e "${INFO}${YW} Access it using the following URL:${CL}" -echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3280${CL}" diff --git a/ct/writefreely.sh b/ct/writefreely.sh deleted file mode 100644 index bb0c64c58..000000000 --- a/ct/writefreely.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env bash -source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) -# Copyright (c) 2021-2026 community-scripts ORG -# Author: StellaeAlis -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/writefreely/writefreely - -# App Default Values -APP="WriteFreely" -var_tags="${var_tags:-writing}" -var_cpu="${var_cpu:-2}" -var_ram="${var_ram:-1024}" -var_disk="${var_disk:-4}" -var_os="${var_os:-debian}" -var_version="${var_version:-13}" -var_unprivileged="${var_unprivileged:-1}" - -header_info "$APP" -variables -color -catch_errors - -function update_script() { - header_info - check_container_storage - check_container_resources - - if [[ ! -d /opt/writefreely ]]; then - msg_error "No ${APP} Installation Found!" - exit - fi - - if check_for_gh_release "writefreely" "writefreely/writefreely"; then - msg_info "Stopping Services" - systemctl stop writefreely - msg_ok "Stopped Services" - - msg_info "Creating Backup" - mkdir -p /tmp/writefreely_backup - cp /opt/writefreely/keys /tmp/writefreely_backup/ 2>/dev/null - cp /opt/writefreely/config.ini /tmp/writefreely_backup/ 2>/dev/null - msg_ok "Created Backup" - - CLEAN_INSTALL=1 fetch_and_deploy_gh_release "writefreely" "writefreely/writefreely" "prebuild" "latest" "/opt/writefreely" "writefreely_*_linux_amd64.tar.gz" - - msg_info "Restoring Data" - cp /tmp/writefreely_backup/config.ini /opt/writefreely/ 2>/dev/null - cp /tmp/writefreely_backup/keys/* /opt/writefreely/keys/ 2>/dev/null - rm -rf /tmp/writefreely_backup - msg_ok "Restored Data" - - msg_info "Running Post-Update Tasks" - cd /opt/writefreely - $STD ./writefreely db migrate - msg_ok "Ran Post-Update Tasks" - - msg_info "Starting Services" - systemctl start writefreely - msg_ok "Started Services" - msg_ok "Updated successfully!" - fi - exit -} - -start -build_container -description - -msg_ok "Completed successfully!\n" -echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" -echo -e "${INFO}${YW} Access it using the following URL:${CL}" -echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}" diff --git a/frontend/public/json/github-versions.json b/frontend/public/json/github-versions.json index 85c22ee41..5a425b011 100644 --- a/frontend/public/json/github-versions.json +++ b/frontend/public/json/github-versions.json @@ -1,5 +1,5 @@ { - "generated": "2026-02-04T07:03:37Z", + "generated": "2026-02-04T13:17:54Z", "versions": [ { "slug": "affine", @@ -99,13 +99,6 @@ "pinned": true, "date": "2026-01-28T15:08:23Z" }, - { - "slug": "piler", - "repo": "jsuto/piler", - "version": "piler-1.4.8", - "pinned": false, - "date": "2025-09-24T06:51:38Z" - }, { "slug": "pixelfed", "repo": "pixelfed/pixelfed", @@ -135,18 +128,11 @@ "date": "2026-01-21T19:07:21Z" }, { - "slug": "wishlist", - "repo": "cmintey/wishlist", - "version": "v0.59.0", + "slug": "wger", + "repo": "wger-project/wger", + "version": "2.4", "pinned": false, - "date": "2026-01-19T16:42:14Z" - }, - { - "slug": "writefreely", - "repo": "writefreely/writefreely", - "version": "v0.16.0", - "pinned": false, - "date": "2025-08-29T19:30:02Z" + "date": "2026-01-18T12:12:02Z" } ] } diff --git a/frontend/public/json/nginx-ui.json b/frontend/public/json/nginx-ui.json index 100c2420a..176f4cc12 100644 --- a/frontend/public/json/nginx-ui.json +++ b/frontend/public/json/nginx-ui.json @@ -11,7 +11,7 @@ "interface_port": 9000, "documentation": "https://nginxui.com/guide/", "website": "https://nginxui.com", - "logo": "https://raw.githubusercontent.com/0xJacky/nginx-ui/master/app/public/logo.png", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/nginx-ui.webp", "config_path": "/usr/local/etc/nginx-ui/app.ini", "description": "Nginx UI is a comprehensive web-based interface designed to simplify the management and configuration of Nginx servers. It provides features like online statistics, ChatGPT-powered config assistant, automatic Let's Encrypt certificates, and config file editing with syntax highlighting.", "install_methods": [ @@ -32,10 +32,6 @@ "password": null }, "notes": [ - { - "text": "Create your admin account on first login via the web interface.", - "type": "info" - }, { "text": "Nginx runs on ports 80/443, Nginx UI management interface on port 9000.", "type": "info" diff --git a/frontend/public/json/piler.json b/frontend/public/json/piler.json deleted file mode 100644 index fb3b31f77..000000000 --- a/frontend/public/json/piler.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Piler", - "slug": "piler", - "categories": [ - 7 - ], - "date_created": "2025-12-15", - "type": "ct", - "updateable": true, - "privileged": false, - "interface_port": 80, - "documentation": "https://www.mailpiler.org/", - "config_path": "", - "website": "https://www.mailpiler.org/", - "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/piler.webp", - "description": "Piler is a feature rich open source email archiving solution with support for legal hold, deduplication, full text search, and many more features.", - "install_methods": [ - { - "type": "default", - "script": "ct/piler.sh", - "resources": { - "cpu": 4, - "ram": 4096, - "hdd": 20, - "os": "Debian", - "version": "12" - } - } - ], - "default_credentials": { - "username": null, - "password": null - }, - "notes": [] -} \ No newline at end of file diff --git a/frontend/public/json/wger.json b/frontend/public/json/wger.json new file mode 100644 index 000000000..804ef56ef --- /dev/null +++ b/frontend/public/json/wger.json @@ -0,0 +1,40 @@ +{ + "name": "wger", + "slug": "wger", + "categories": [ + 24 + ], + "date_created": "2025-02-24", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": 3000, + "documentation": "https://wger.readthedocs.io/en/latest/index.html", + "website": "https://wger.de", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/wger.webp", + "config_path": "/opt/wger/wger.env", + "description": "wger (ˈvɛɡɐ) Workout Manager is a free, open source web application that helps you manage your personal workouts, weight and diet plans and can also be used as a simple gym management utility. It offers a REST API as well, for easy integration with other projects and tools.", + "install_methods": [ + { + "type": "default", + "script": "ct/wger.sh", + "resources": { + "cpu": 2, + "ram": 2048, + "hdd": 8, + "os": "debian", + "version": "13" + } + } + ], + "default_credentials": { + "username": "admin", + "password": "adminadmin" + }, + "notes": [ + { + "text": "This LXC also runs Celery and Redis to synchronize workouts and ingredients", + "type": "info" + } + ] +} diff --git a/frontend/public/json/wishlist.json b/frontend/public/json/wishlist.json deleted file mode 100644 index 4d7f7f45b..000000000 --- a/frontend/public/json/wishlist.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Wishlist", - "slug": "wishlist", - "categories": [ - 0 - ], - "date_created": "2025-12-29", - "type": "ct", - "updateable": true, - "privileged": false, - "interface_port": 3280, - "documentation": "https://github.com/cmintey/wishlist/blob/main/README.md#getting-started", - "config_path": "/opt/wishlist/.env", - "website": "https://github.com/cmintey/wishlist", - "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/cmintey-wishlist.webp", - "description": "Wishlist is a self-hosted wishlist application that you can share with your friends and family. You no longer have to wonder what to get your family for the holidays, simply check their wishlist and claim any available item!", - "install_methods": [ - { - "type": "default", - "script": "ct/wishlist.sh", - "resources": { - "cpu": 2, - "ram": 2048, - "hdd": 5, - "os": "Debian", - "version": "13" - } - } - ], - "default_credentials": { - "username": null, - "password": null - }, - "notes": [] -} diff --git a/frontend/public/json/writefreely.json b/frontend/public/json/writefreely.json deleted file mode 100644 index 984243807..000000000 --- a/frontend/public/json/writefreely.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "WriteFreely", - "slug": "writefreely", - "categories": [ - 12 - ], - "date_created": "2026-01-25", - "type": "ct", - "updateable": true, - "privileged": false, - "interface_port": 80, - "documentation": "https://writefreely.org/docs", - "config_path": "/opt/writefreely/config.ini", - "website": "https://writefreely.org/", - "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/writefreely-light.webp", - "description": "WriteFreely is free and open source software for easily publishing writing on the web with support for the ActivityPub protocol. Use it to start a personal blog — or an entire community.", - "install_methods": [ - { - "type": "default", - "script": "ct/writefreely.sh", - "resources": { - "cpu": 2, - "ram": 1024, - "hdd": 4, - "os": "Debian", - "version": "13" - } - } - ], - "default_credentials": { - "username": null, - "password": null - }, - "notes": [ - { - "text": "After installation execute `./writefreely user create --admin username:password` in the `/opt/writefreely` directory to create your user.", - "type": "info" - } - ] -} diff --git a/install/piler-install.sh b/install/deferred/piler-install.sh similarity index 100% rename from install/piler-install.sh rename to install/deferred/piler-install.sh diff --git a/install/discourse-install.sh b/install/discourse-install.sh index ee40d2eb9..253200580 100644 --- a/install/discourse-install.sh +++ b/install/discourse-install.sh @@ -29,13 +29,21 @@ $STD apt install -y \ msg_ok "Installed Dependencies" PG_VERSION="16" setup_postgresql -PG_DB_NAME="discourse" PG_DB_USER="discourse" setup_postgresql_db NODE_VERSION="22" setup_nodejs -RUBY_VERSION="3.3" setup_ruby +RUBY_VERSION="3.3.6" setup_ruby + +msg_info "Configuring PostgreSQL for Discourse" +DISCOURSE_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13) +# Configure pg_hba.conf for md5 authentication +PG_HBA="/etc/postgresql/16/main/pg_hba.conf" +sed -i 's/^local\s\+all\s\+all\s\+peer$/local all all md5/' "$PG_HBA" +$STD systemctl restart postgresql +# Create user with CREATEDB permission - Rails will create the database +$STD sudo -u postgres psql -c "CREATE ROLE discourse WITH LOGIN PASSWORD '$DISCOURSE_DB_PASS' CREATEDB;" +msg_ok "Configured PostgreSQL for Discourse" msg_info "Configuring Discourse" -DISCOURSE_SECRET_KEY=$(openssl rand -hex 32) -DISCOURSE_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13) +DISCOURSE_SECRET_KEY=$(openssl rand -hex 64) git clone --depth 1 https://github.com/discourse/discourse.git /opt/discourse cd /opt/discourse @@ -45,7 +53,7 @@ RAILS_ENV=production RAILS_LOG_TO_STDOUT=true RAILS_SERVE_STATIC_FILES=true SECRET_KEY_BASE=${DISCOURSE_SECRET_KEY} -DISCOURSE_DB_HOST=localhost +DISCOURSE_DB_HOST=/var/run/postgresql DISCOURSE_DB_PORT=5432 DISCOURSE_DB_NAME=discourse DISCOURSE_DB_USERNAME=discourse @@ -64,26 +72,42 @@ chmod 755 /opt/discourse msg_ok "Configured Discourse" msg_info "Installing Discourse Dependencies" +$STD systemctl enable --now redis-server cd /opt/discourse -$STD bundle install --deployment --without test development -$STD yarn install +export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH" +eval "$(rbenv init - bash)" 2>/dev/null || true +export RAILS_ENV=production +export COREPACK_ENABLE_DOWNLOAD_PROMPT=0 +$STD corepack enable +$STD bundle config set --local deployment true +$STD bundle config set --local without 'test development' +$STD bundle install +$STD pnpm install msg_ok "Installed Discourse Dependencies" -msg_info "Building Discourse Assets" -cd /opt/discourse -$STD bundle exec rails assets:precompile -msg_ok "Built Discourse Assets" - msg_info "Setting Up Database" cd /opt/discourse +export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH" +eval "$(rbenv init - bash)" 2>/dev/null || true +export RAILS_ENV=production +$STD bundle exec rails db:create $STD bundle exec rails db:migrate -$STD systemctl enable --now redis-server -$STD systemctl enable --now postgresql msg_ok "Set Up Database" +msg_info "Building Discourse Assets" +cd /opt/discourse +export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH" +eval "$(rbenv init - bash)" 2>/dev/null || true +export RAILS_ENV=production +$STD bundle exec rails assets:precompile +msg_ok "Built Discourse Assets" + msg_info "Creating Discourse Admin User" cd /opt/discourse -$STD bundle exec rails runner -e production "User.create!(email: 'admin@local', username: 'admin', password: '${DISCOURSE_DB_PASS}', admin: true)" || true +export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH" +eval "$(rbenv init - bash)" 2>/dev/null || true +export RAILS_ENV=production +$STD bundle exec rails runner "User.create!(email: 'admin@local', username: 'admin', password: '${DISCOURSE_DB_PASS}', admin: true)" || true msg_ok "Created Discourse Admin User" msg_info "Creating Service" diff --git a/install/wger-install.sh b/install/wger-install.sh index 0e8e9ace7..df8a089d0 100644 --- a/install/wger-install.sh +++ b/install/wger-install.sh @@ -14,18 +14,18 @@ network_check update_os msg_info "Installing Dependencies" -$STD apt-get install -y \ - apache2 \ - libapache2-mod-wsgi-py3 \ +$STD apt install -y \ + build-essential \ + nginx \ + redis-server \ libpq-dev msg_ok "Installed Dependencies" +import_local_ip NODE_VERSION="22" NODE_MODULE="sass" setup_nodejs setup_uv - PG_VERSION="16" setup_postgresql PG_DB_NAME="wger" PG_DB_USER="wger" setup_postgresql_db - fetch_and_deploy_gh_release "wger" "wger-project/wger" "tarball" "latest" "/opt/wger" msg_info "Setting up wger" @@ -36,85 +36,147 @@ $STD corepack enable $STD npm install $STD npm run build:css:sass $STD uv venv -$STD uv pip install . +$STD uv pip install . --group docker SECRET_KEY=$(openssl rand -base64 40) cat </opt/wger/.env +DJANGO_SETTINGS_MODULE=settings.main +PYTHONPATH=/opt/wger + DJANGO_DB_ENGINE=django.db.backends.postgresql DJANGO_DB_DATABASE=${PG_DB_NAME} DJANGO_DB_USER=${PG_DB_USER} DJANGO_DB_PASSWORD=${PG_DB_PASS} DJANGO_DB_HOST=localhost DJANGO_DB_PORT=5432 +DATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME} + DJANGO_MEDIA_ROOT=/opt/wger/media DJANGO_STATIC_ROOT=/opt/wger/static +DJANGO_STATIC_URL=/static/ + +ALLOWED_HOSTS=${LOCAL_IP},localhost,127.0.0.1 +CSRF_TRUSTED_ORIGINS=http://${LOCAL_IP}:3000 + +USE_X_FORWARDED_HOST=True +SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,http + +DJANGO_CACHE_BACKEND=django_redis.cache.RedisCache +DJANGO_CACHE_LOCATION=redis://127.0.0.1:6379/1 +DJANGO_CACHE_TIMEOUT=300 +DJANGO_CACHE_CLIENT_CLASS=django_redis.client.DefaultClient +AXES_CACHE_ALIAS=default + +USE_CELERY=True +CELERY_BROKER=redis://127.0.0.1:6379/2 +CELERY_BACKEND=redis://127.0.0.1:6379/2 + +SITE_URL=http://${LOCAL_IP}:3000 SECRET_KEY=${SECRET_KEY} EOF -cat <<'WSGI' >/opt/wger/wsgi_wrapper.py -import os -from pathlib import Path - -env_file = Path('/opt/wger/.env') -if env_file.exists(): - for line in env_file.read_text().splitlines(): - if line.strip() and not line.startswith('#') and '=' in line: - key, value = line.split('=', 1) - os.environ.setdefault(key.strip(), value.strip()) - -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.main') - -from wger.wsgi import application -WSGI set -a && source /opt/wger/.env && set +a -export DJANGO_SETTINGS_MODULE=settings.main -$STD uv run python manage.py migrate -$STD uv run python manage.py loaddata languages -$STD uv run python manage.py loaddata gym_config -$STD uv run python manage.py loaddata groups -$STD uv run python manage.py loaddata site +$STD uv run wger bootstrap $STD uv run python manage.py collectstatic --no-input cat </etc/systemd/system/wger.service +[Unit] +Description=wger Gunicorn +After=network.target -msg_info "Creating Service" -cat </etc/apache2/sites-available/wger.conf - - - Require all granted - - +[Service] +User=root +WorkingDirectory=/opt/wger +EnvironmentFile=/opt/wger/.env +ExecStart=/opt/wger/.venv/bin/gunicorn \ + --bind 127.0.0.1:8000 \ + --workers 3 \ + --threads 2 \ + --timeout 120 \ + wger.wsgi:application +Restart=always - - WSGIApplicationGroup %{GLOBAL} - WSGIDaemonProcess wger python-path=/opt/wger python-home=/opt/wger/.venv - WSGIProcessGroup wger - WSGIScriptAlias / /opt/wger/wsgi_wrapper.py - WSGIPassAuthorization On - - Alias /static/ /opt/wger/static/ - - Require all granted - - - Alias /media/ /opt/wger/media/ - - Require all granted - - - ErrorLog /var/log/apache2/wger-error.log - CustomLog /var/log/apache2/wger-access.log combined - +[Install] +WantedBy=multi-user.target EOF -$STD a2dissite 000-default.conf -$STD a2ensite wger -systemctl restart apache2 -msg_ok "Created Service" +cat </etc/systemd/system/celery.service +[Unit] +Description=wger Celery Worker +After=network.target redis-server.service +Requires=redis-server.service + +[Service] +WorkingDirectory=/opt/wger +EnvironmentFile=/opt/wger/.env +ExecStart=/opt/wger/.venv/bin/celery -A wger worker -l info +Restart=always + +[Install] +WantedBy=multi-user.target +EOF + +mkdir -p /var/lib/wger/celery +chmod 700 /var/lib/wger/celery +cat </etc/systemd/system/celery-beat.service +[Unit] +Description=wger Celery Beat +After=network.target redis-server.service +Requires=redis-server.service + +[Service] +WorkingDirectory=/opt/wger +EnvironmentFile=/opt/wger/.env +ExecStart=/opt/wger/.venv/bin/celery -A wger beat -l info \ + --schedule /var/lib/wger/celery/celerybeat-schedule +Restart=always + +[Install] +WantedBy=multi-user.target +EOF +cat <<'EOF' >/etc/nginx/sites-available/wger +server { + listen 3000; + server_name _; + + client_max_body_size 20M; + + location /static/ { + alias /opt/wger/static/; + expires 30d; + } + + location /media/ { + alias /opt/wger/media/; + } + + location / { + proxy_pass http://127.0.0.1:8000; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_redirect off; + } +} +EOF +$STD rm -f /etc/nginx/sites-enabled/default +$STD ln -sf /etc/nginx/sites-available/wger /etc/nginx/sites-enabled/wger +systemctl enable -q --now redis-server nginx wger celery celery-beat +systemctl restart nginx +msg_ok "Created Config and Services" motd_ssh customize diff --git a/install/wishlist-install.sh b/install/wishlist-install.sh deleted file mode 100644 index 659439f74..000000000 --- a/install/wishlist-install.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2021-2026 community-scripts ORG -# Author: Dunky13 -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/cmintey/wishlist - -source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" -color -verb_ip6 -catch_errors -setting_up_container -network_check -update_os - -msg_info "Installing dependencies" -$STD apt install -y \ - build-essential \ - python3 \ - openssl \ - caddy -msg_ok "Installed dependencies" - -NODE_VERSION="24" NODE_MODULE="pnpm" setup_nodejs -fetch_and_deploy_gh_release "wishlist" "cmintey/wishlist" "tarball" -LATEST_APP_VERSION=$(get_latest_github_release "cmintey/wishlist" false) - - -msg_info "Installing Wishlist" -cd /opt/wishlist -cp .env.example .env -sed -i "s|^ORIGIN=.*|ORIGIN=http://${LOCAL_IP}:3000|" /opt/wishlist/.env -echo "NODE_ENV=production" >>/opt/wishlist/.env -$STD pnpm install -$STD pnpm svelte-kit sync -$STD pnpm prisma generate -sed -i 's|/usr/src/app/|/opt/wishlist/|g' $(grep -rl '/usr/src/app/' /opt/wishlist) -export VERSION="v${LATEST_APP_VERSION}" -export SHA="v${LATEST_APP_VERSION}" -$STD pnpm run build -$STD pnpm prune --prod -chmod +x /opt/wishlist/entrypoint.sh -mkdir -p /opt/wishlist/uploads -mkdir -p /opt/wishlist/data -msg_ok "Installed Wishlist" - -msg_info "Creating Service" -cat </etc/systemd/system/wishlist.service -[Unit] -Description=Wishlist Service -After=network.target - -[Service] -WorkingDirectory=/opt/wishlist -EnvironmentFile=/opt/wishlist/.env -ExecStart=/usr/bin/env sh -c './entrypoint.sh' -Restart=on-failure - -[Install] -WantedBy=multi-user.target -EOF -systemctl enable -q --now wishlist -msg_ok "Created Service" - -motd_ssh -customize -cleanup_lxc diff --git a/install/writefreely-install.sh b/install/writefreely-install.sh deleted file mode 100644 index c3a1e21a7..000000000 --- a/install/writefreely-install.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2021-2026 community-scripts ORG -# Author: StellaeAlis -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/writefreely/writefreely - -# Import Functions and Setup -source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" -color -verb_ip6 -catch_errors -setting_up_container -network_check -update_os - -msg_info "Installing Dependencies" -$STD apt install -y crudini -msg_ok "Installed Dependencies" - -setup_mariadb -MARIADB_DB_NAME="writefreely" MARIADB_DB_USER="writefreely" setup_mariadb_db - -get_lxc_ip - -fetch_and_deploy_gh_release "writefreely" "writefreely/writefreely" "prebuild" "latest" "/opt/writefreely" "writefreely_*_linux_amd64.tar.gz" - -msg_info "Setting up WriteFreely" -cd /opt/writefreely -$STD ./writefreely config generate -$STD ./writefreely keys generate -msg_ok "Setup WriteFreely" - -msg_info "Configuring WriteFreely" -$STD crudini --set config.ini server port 80 -$STD crudini --set config.ini server bind $LOCAL_IP -$STD crudini --set config.ini database username $MARIADB_DB_USER -$STD crudini --set config.ini database password $MARIADB_DB_PASS -$STD crudini --set config.ini database database $MARIADB_DB_NAME -$STD crudini --set config.ini app host http://$LOCAL_IP:80 -$STD ./writefreely db init -msg_ok "Configured WriteFreely" - -msg_info "Creating Service" -cat </etc/systemd/system/writefreely.service -[Unit] -Description=WriteFreely Service -After=syslog.target network.target - -[Service] -Type=simple -User=root -WorkingDirectory=/opt/writefreely -ExecStart=/opt/writefreely/writefreely -Restart=on-failure -RestartSec=5 - -[Install] -WantedBy=multi-user.target -EOF -systemctl enable -q --now writefreely -msg_ok "Created Service" - -msg_info "Cleaning up" -$STD rm ~/writefreely.creds -msg_ok "Cleaned up" - -motd_ssh -customize - -cleanup_lxc diff --git a/misc/cloud-init.func b/misc/cloud-init.func index f4d570ee7..0c8597f9b 100644 --- a/misc/cloud-init.func +++ b/misc/cloud-init.func @@ -35,10 +35,203 @@ set +u CLOUDINIT_DEFAULT_USER="${CLOUDINIT_DEFAULT_USER:-root}" CLOUDINIT_DNS_SERVERS="${CLOUDINIT_DNS_SERVERS:-1.1.1.1 8.8.8.8}" CLOUDINIT_SEARCH_DOMAIN="${CLOUDINIT_SEARCH_DOMAIN:-local}" -CLOUDINIT_SSH_KEYS="${CLOUDINIT_SSH_KEYS:-/root/.ssh/authorized_keys}" +CLOUDINIT_SSH_KEYS="${CLOUDINIT_SSH_KEYS:-}" # Empty by default - user must explicitly provide keys # ============================================================================== -# SECTION 2: HELPER FUNCTIONS +# SECTION 2: SSH KEY DISCOVERY AND SELECTION +# ============================================================================== + +# ------------------------------------------------------------------------------ +# _ci_ssh_extract_keys_from_file - Extracts valid SSH public keys from a file +# ------------------------------------------------------------------------------ +function _ci_ssh_extract_keys_from_file() { + local file="$1" + [[ -f "$file" && -r "$file" ]] || return 0 + grep -E '^(ssh-(rsa|ed25519|dss|ecdsa)|ecdsa-sha2-)' "$file" 2>/dev/null || true +} + +# ------------------------------------------------------------------------------ +# _ci_ssh_discover_files - Scans standard paths for SSH keys +# ------------------------------------------------------------------------------ +function _ci_ssh_discover_files() { + local -a cand=() + shopt -s nullglob + cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2) + cand+=(/root/.ssh/*.pub) + cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*) + shopt -u nullglob + printf '%s\0' "${cand[@]}" +} + +# ------------------------------------------------------------------------------ +# _ci_ssh_build_choices - Builds whiptail checklist from SSH key files +# +# Sets: CI_SSH_CHOICES (array), CI_SSH_COUNT (int), CI_SSH_MAPFILE (path) +# ------------------------------------------------------------------------------ +function _ci_ssh_build_choices() { + local -a files=("$@") + CI_SSH_CHOICES=() + CI_SSH_COUNT=0 + CI_SSH_MAPFILE="$(mktemp)" + local id key typ fp cmt base + + for f in "${files[@]}"; do + [[ -f "$f" && -r "$f" ]] || continue + base="$(basename -- "$f")" + # Skip known_hosts and private keys + case "$base" in + known_hosts | known_hosts.* | config) continue ;; + id_*) [[ "$f" != *.pub ]] && continue ;; + esac + + while IFS= read -r key; do + [[ -n "$key" ]] || continue + + typ="" + fp="" + cmt="" + read -r _typ _b64 _cmt <<<"$key" + typ="${_typ:-key}" + cmt="${_cmt:-}" + + # Get fingerprint via ssh-keygen if available + if command -v ssh-keygen >/dev/null 2>&1; then + fp="$(printf '%s\n' "$key" | ssh-keygen -lf - 2>/dev/null | awk '{print $2}')" + fi + + # Shorten long comments + [[ ${#cmt} -gt 40 ]] && cmt="${cmt:0:37}..." + + CI_SSH_COUNT=$((CI_SSH_COUNT + 1)) + id="K${CI_SSH_COUNT}" + echo "${id}|${key}" >>"$CI_SSH_MAPFILE" + CI_SSH_CHOICES+=("$id" "[$typ] ${fp:+$fp }${cmt:+$cmt }— ${base}" "OFF") + done < <(_ci_ssh_extract_keys_from_file "$f") + done +} + +# ------------------------------------------------------------------------------ +# configure_cloudinit_ssh_keys - Interactive SSH key selection for Cloud-Init +# +# Usage: configure_cloudinit_ssh_keys +# Sets: CLOUDINIT_SSH_KEYS (path to temporary file with selected keys) +# ------------------------------------------------------------------------------ +function configure_cloudinit_ssh_keys() { + local backtitle="Proxmox VE Helper Scripts" + local ssh_key_mode + + # Create temp file for selected keys + CLOUDINIT_SSH_KEYS_TEMP="$(mktemp)" + : >"$CLOUDINIT_SSH_KEYS_TEMP" + + # Discover keys and build choices + IFS=$'\0' read -r -d '' -a _def_files < <(_ci_ssh_discover_files && printf '\0') + _ci_ssh_build_choices "${_def_files[@]}" + local default_key_count="$CI_SSH_COUNT" + + if [[ "$default_key_count" -gt 0 ]]; then + ssh_key_mode=$(whiptail --backtitle "$backtitle" --title "SSH KEY SOURCE" --menu \ + "Provision SSH keys for Cloud-Init VM:" 14 72 4 \ + "found" "Select from detected keys (${default_key_count})" \ + "manual" "Paste a single public key" \ + "folder" "Scan another folder (path or glob)" \ + "none" "No SSH keys (password auth only)" 3>&1 1>&2 2>&3) || return 1 + else + ssh_key_mode=$(whiptail --backtitle "$backtitle" --title "SSH KEY SOURCE" --menu \ + "No host keys detected. Choose:" 12 72 3 \ + "manual" "Paste a single public key" \ + "folder" "Scan another folder (path or glob)" \ + "none" "No SSH keys (password auth only)" 3>&1 1>&2 2>&3) || return 1 + fi + + case "$ssh_key_mode" in + found) + # Show checklist with individual keys + local selection + selection=$(whiptail --backtitle "$backtitle" --title "SELECT SSH KEYS" \ + --checklist "Select one or more keys to import:" 20 140 10 "${CI_SSH_CHOICES[@]}" 3>&1 1>&2 2>&3) || return 1 + + for tag in $selection; do + tag="${tag%\"}" + tag="${tag#\"}" + local line + line=$(grep -E "^${tag}\|" "$CI_SSH_MAPFILE" | head -n1 | cut -d'|' -f2-) + [[ -n "$line" ]] && printf '%s\n' "$line" >>"$CLOUDINIT_SSH_KEYS_TEMP" + done + local imported + imported=$(wc -l <"$CLOUDINIT_SSH_KEYS_TEMP") + echo -e "${ROOTSSH:- 🔑 }${BOLD}${DGN}SSH Keys: ${BGN}${imported} key(s) selected${CL}" + ;; + manual) + local pubkey + pubkey=$(whiptail --backtitle "$backtitle" --title "PASTE SSH PUBLIC KEY" \ + --inputbox "Paste your SSH public key (ssh-rsa, ssh-ed25519, etc.):" 10 76 3>&1 1>&2 2>&3) || return 1 + if [[ -n "$pubkey" ]]; then + echo "$pubkey" >"$CLOUDINIT_SSH_KEYS_TEMP" + echo -e "${ROOTSSH:- 🔑 }${BOLD}${DGN}SSH Keys: ${BGN}1 key added manually${CL}" + else + echo -e "${ROOTSSH:- 🔑 }${BOLD}${DGN}SSH Keys: ${BGN}none (empty input)${CL}" + CLOUDINIT_SSH_KEYS="" + rm -f "$CLOUDINIT_SSH_KEYS_TEMP" "$CI_SSH_MAPFILE" 2>/dev/null + return 0 + fi + ;; + folder) + local glob_path + glob_path=$(whiptail --backtitle "$backtitle" --title "SCAN FOLDER/GLOB" \ + --inputbox "Enter a folder or glob to scan (e.g. /root/.ssh/*.pub):" 10 72 3>&1 1>&2 2>&3) || return 1 + if [[ -n "$glob_path" ]]; then + shopt -s nullglob + local -a _scan_files=($glob_path) + shopt -u nullglob + if [[ "${#_scan_files[@]}" -gt 0 ]]; then + _ci_ssh_build_choices "${_scan_files[@]}" + if [[ "$CI_SSH_COUNT" -gt 0 ]]; then + local folder_selection + folder_selection=$(whiptail --backtitle "$backtitle" --title "SELECT FOLDER KEYS" \ + --checklist "Select key(s) to import:" 20 140 10 "${CI_SSH_CHOICES[@]}" 3>&1 1>&2 2>&3) || return 1 + for tag in $folder_selection; do + tag="${tag%\"}" + tag="${tag#\"}" + local line + line=$(grep -E "^${tag}\|" "$CI_SSH_MAPFILE" | head -n1 | cut -d'|' -f2-) + [[ -n "$line" ]] && printf '%s\n' "$line" >>"$CLOUDINIT_SSH_KEYS_TEMP" + done + local imported + imported=$(wc -l <"$CLOUDINIT_SSH_KEYS_TEMP") + echo -e "${ROOTSSH:- 🔑 }${BOLD}${DGN}SSH Keys: ${BGN}${imported} key(s) from folder${CL}" + else + whiptail --backtitle "$backtitle" --msgbox "No keys found in: $glob_path" 8 60 + fi + else + whiptail --backtitle "$backtitle" --msgbox "Path/glob returned no files." 8 60 + fi + fi + ;; + none | *) + echo -e "${ROOTSSH:- 🔑 }${BOLD}${DGN}SSH Keys: ${BGN}none (password auth only)${CL}" + CLOUDINIT_SSH_KEYS="" + rm -f "$CLOUDINIT_SSH_KEYS_TEMP" "$CI_SSH_MAPFILE" 2>/dev/null + return 0 + ;; + esac + + # Cleanup mapfile + rm -f "$CI_SSH_MAPFILE" 2>/dev/null + + # Set the variable for setup_cloud_init to use + if [[ -s "$CLOUDINIT_SSH_KEYS_TEMP" ]]; then + CLOUDINIT_SSH_KEYS="$CLOUDINIT_SSH_KEYS_TEMP" + else + CLOUDINIT_SSH_KEYS="" + rm -f "$CLOUDINIT_SSH_KEYS_TEMP" + fi + + return 0 +} + +# ============================================================================== +# SECTION 3: HELPER FUNCTIONS # ============================================================================== # ------------------------------------------------------------------------------ @@ -148,9 +341,10 @@ function setup_cloud_init() { local cipassword=$(openssl rand -base64 16) qm set "$vmid" --cipassword "$cipassword" >/dev/null - # Add SSH keys if available - if [ -f "$CLOUDINIT_SSH_KEYS" ]; then + # Add SSH keys only if explicitly provided (not auto-imported from host) + if [ -n "${CLOUDINIT_SSH_KEYS:-}" ] && [ -f "$CLOUDINIT_SSH_KEYS" ]; then qm set "$vmid" --sshkeys "$CLOUDINIT_SSH_KEYS" >/dev/null 2>&1 || true + _ci_msg_info "SSH keys imported from: $CLOUDINIT_SSH_KEYS" fi # Configure network diff --git a/vm/docker-vm.sh b/vm/docker-vm.sh index 6c862c3f4..82b56897a 100644 --- a/vm/docker-vm.sh +++ b/vm/docker-vm.sh @@ -169,6 +169,11 @@ function advanced_settings() { select_os select_cloud_init + # SSH Key selection for Cloud-Init VMs + if [ "$USE_CLOUD_INIT" = "yes" ]; then + configure_cloudinit_ssh_keys || true + fi + METHOD="advanced" [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid)