diff --git a/.env.example b/.env.example index 20533aca8..1f475fa73 100644 --- a/.env.example +++ b/.env.example @@ -26,6 +26,9 @@ HTTPS_PORT=443 SMTP_PORT=25 SMTP_TLS_PORT=587 +# Whether to force redirect all HTTP traffic (80) to HTTPS (443). Defaults to true. +FORCE_HTTPS_REDIRECT=true + # =========================================== # EMAIL SERVER CONFIGURATION # =========================================== diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 29b53306d..88660582f 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -109,8 +109,8 @@ jobs: echo "πŸ”§ Testing admin password reset flow..." # Run the reset password script with auto-confirm - echo "Running reset-admin-password.sh script..." - password_output=$(docker exec aliasvault-test reset-admin-password.sh -y 2>&1) + echo "Running reset-admin-password command..." + password_output=$(docker exec aliasvault-test aliasvault reset-admin-password -y 2>&1) echo "Script output:" echo "$password_output" diff --git a/.gitignore b/.gitignore index 9468d6088..f9c85e684 100644 --- a/.gitignore +++ b/.gitignore @@ -404,6 +404,7 @@ certificates/**/*.crt certificates/**/*.key certificates/**/*.pfx certificates/**/*.pem +certificates/**/.hostname_marker certificates/letsencrypt/** # Secrets diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 66d0259e0..89e6b2fa4 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -199,7 +199,7 @@ { "label": "Build and watch Docs", "type": "shell", - "command": "docker compose up", + "command": "docker compose build && docker compose up", "problemMatcher": [], "group": { "kind": "build", diff --git a/README.md b/README.md index 5529a7d86..f81f2d1df 100644 --- a/README.md +++ b/README.md @@ -65,33 +65,31 @@ AliasVault is available on: [Screenshot of AliasVault](https://app.aliasvault.net) ## Self-hosting -For full control over your own data you can self-host and install AliasVault on your own servers. +> [!NOTE] +> **Requirements:** 1 vCPU, 1GB RAM, 16GB disk, Docker β‰₯ 20.10, 64-bit Linux -### Install using install script +AliasVault can be self-hosted on your own servers using two different installation methods. Both use Docker, but they differ in how much is automated versus how much you manage yourself. -This method uses pre-built Docker images and works on minimal hardware specifications: +- **Option 1: Install Script** - Managed solution with automatic SSL (recommended for VPS/cloud) -- 64-bit Linux VM (Ubuntu/AlmaLinux) or Raspberry Pi, with root access -- Minimum: 1 vCPU, 1GB RAM, 16GB disk -- Docker β‰₯ 20.10 and Docker Compose β‰₯ 2.0 +- **Option 2: Docker Compose** - Single container with manual setup for use with existing SSL infrastructure (NAS, homelab) +### Quick Start (Install Script) ```bash -# Download install script from latest stable release +# Download and run install script curl -L -o install.sh https://github.com/aliasvault/aliasvault/releases/latest/download/install.sh # Make install script executable and run it. This will create the .env file, pull the Docker images, and start the AliasVault containers. chmod +x install.sh + ./install.sh install ``` -The install script will output the URL where the app is available. By default this is: -- Client: https://localhost -- Admin portal: https://localhost/admin +For other installation methods and more detailed steps, please read the [full installation guide](https://docs.aliasvault.net/installation) in the official docs. -> Note: If you want to change the default AliasVault ports you can do so in the `.env` file. - -## Technical documentation +## Documentation For more information about the installation process, manual setup instructions and other topics, please see the official documentation website: + - [Documentation website (docs.aliasvault.net) πŸ“š](https://docs.aliasvault.net) ## Security Architecture @@ -135,7 +133,7 @@ Core features that are being worked on: πŸ‘‰ [View the full AliasVault roadmap here](https://github.com/aliasvault/aliasvault/issues/731) ### Got feedback or ideas? -Feel free to open an issue or join our [Discord](https://discord.gg/DsaXMTEtpF)! Contributions are warmly welcomedβ€”whether in feature development, testing, or spreading the word. Get in touch on Discord if you're interested in contributing. +Feel free to open an issue or discussion on GitHub. We warmly welcome all contributions: whether it’s translating, testing, helping to build features, sharing feedback - or helping spread the word about AliasVault. Every bit of support helps the project grow, so don’t hesitate to jump in and [say hi to us on Discord](https://discord.gg/DsaXMTEtpF)! ### Support the mission AliasVault is open-source and community-driven. If you like what we’re building, consider supporting us through [Open Collective](https://opencollective.com/aliasvault) or through: diff --git a/SonarLint.xml b/SonarLint.xml deleted file mode 100644 index f03773dad..000000000 --- a/SonarLint.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - S1135 - - - sonarlint.rule.enabled - false - - - - - diff --git a/apps/server/AliasVault.Admin/Auth/Pages/Login.razor b/apps/server/AliasVault.Admin/Auth/Pages/Login.razor index 7f3754cf8..73c3f6795 100644 --- a/apps/server/AliasVault.Admin/Auth/Pages/Login.razor +++ b/apps/server/AliasVault.Admin/Auth/Pages/Login.razor @@ -1,4 +1,5 @@ ο»Ώ@page "/user/login" +@using AliasVault.RazorComponents.Alerts @using AliasVault.Shared.Models.Enums Log in @@ -11,29 +12,29 @@ @if (!IsAdminConfigured) { -
-
-
- - - -
-
-

- Admin User Not Configured -

-
-

The admin user has not been configured yet. To set up admin access:

-
    -
  1. Connect to your Docker container: docker exec -it [container-name] /bin/bash
  2. -
  3. Run the password reset script: reset-admin-password.sh
  4. -
  5. Restart the container to apply changes: docker restart [container-name]
  6. -
-

Replace [container-name] with your actual container name, e.g. "aliasvault".

+ + The admin user has not been configured yet. To set up admin access: +
    +
  1. + Connect to your Docker container: +
    + $ docker compose exec -it aliasvault /bin/bash
    -
-
-
+ +
  • + Run the password reset script: +
    + $ aliasvault reset-admin-password +
    +
  • +
  • + Exit out of the container, then restart the container to apply changes: +
    + $ docker compose restart +
    +
  • + + } else { diff --git a/apps/server/AliasVault.Admin/StartupTasks.cs b/apps/server/AliasVault.Admin/StartupTasks.cs index ec77361d0..af727672d 100644 --- a/apps/server/AliasVault.Admin/StartupTasks.cs +++ b/apps/server/AliasVault.Admin/StartupTasks.cs @@ -48,7 +48,7 @@ public static class StartupTasks if (string.IsNullOrEmpty(config.AdminPasswordHash)) { Console.WriteLine("Admin password hash not configured - skipping admin user creation."); - Console.WriteLine("Run 'reset-admin-password.sh' to configure the admin password."); + Console.WriteLine("Run '$ aliasvault reset-admin-password' to configure the admin password."); return; } diff --git a/apps/server/AliasVault.Admin/wwwroot/css/tailwind.css b/apps/server/AliasVault.Admin/wwwroot/css/tailwind.css index 3c320f10e..f38ae160c 100644 --- a/apps/server/AliasVault.Admin/wwwroot/css/tailwind.css +++ b/apps/server/AliasVault.Admin/wwwroot/css/tailwind.css @@ -1270,11 +1270,6 @@ video { border-color: rgb(239 68 68 / var(--tw-border-opacity)); } -.border-yellow-200 { - --tw-border-opacity: 1; - border-color: rgb(254 240 138 / var(--tw-border-opacity)); -} - .border-yellow-500 { --tw-border-opacity: 1; border-color: rgb(234 179 8 / var(--tw-border-opacity)); @@ -1370,6 +1365,11 @@ video { background-color: rgb(21 128 61 / var(--tw-bg-opacity)); } +.bg-orange-100 { + --tw-bg-opacity: 1; + background-color: rgb(255 237 213 / var(--tw-bg-opacity)); +} + .bg-orange-400 { --tw-bg-opacity: 1; background-color: rgb(251 146 60 / var(--tw-bg-opacity)); @@ -1454,6 +1454,10 @@ video { --tw-bg-opacity: 0.5; } +.fill-current { + fill: currentColor; +} + .fill-primary-600 { fill: #d68338; } @@ -1774,11 +1778,21 @@ video { color: rgb(22 101 52 / var(--tw-text-opacity)); } +.text-orange-500 { + --tw-text-opacity: 1; + color: rgb(249 115 22 / var(--tw-text-opacity)); +} + .text-orange-600 { --tw-text-opacity: 1; color: rgb(234 88 12 / var(--tw-text-opacity)); } +.text-orange-700 { + --tw-text-opacity: 1; + color: rgb(194 65 12 / var(--tw-text-opacity)); +} + .text-primary-600 { --tw-text-opacity: 1; color: rgb(214 131 56 / var(--tw-text-opacity)); @@ -1814,11 +1828,6 @@ video { color: rgb(255 255 255 / var(--tw-text-opacity)); } -.text-yellow-600 { - --tw-text-opacity: 1; - color: rgb(202 138 4 / var(--tw-text-opacity)); -} - .text-yellow-700 { --tw-text-opacity: 1; color: rgb(161 98 7 / var(--tw-text-opacity)); @@ -2150,11 +2159,6 @@ video { border-color: rgb(234 179 8 / var(--tw-border-opacity)); } -.dark\:border-yellow-800:is(.dark *) { - --tw-border-opacity: 1; - border-color: rgb(133 77 14 / var(--tw-border-opacity)); -} - .dark\:bg-blue-800:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(30 64 175 / var(--tw-bg-opacity)); @@ -2251,10 +2255,6 @@ video { background-color: rgb(113 63 18 / var(--tw-bg-opacity)); } -.dark\:bg-yellow-900\/20:is(.dark *) { - background-color: rgb(113 63 18 / 0.2); -} - .dark\:bg-opacity-80:is(.dark *) { --tw-bg-opacity: 0.8; } @@ -2359,16 +2359,6 @@ video { color: rgb(254 240 138 / var(--tw-text-opacity)); } -.dark\:text-yellow-300:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(253 224 71 / var(--tw-text-opacity)); -} - -.dark\:text-yellow-400:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(250 204 21 / var(--tw-text-opacity)); -} - .dark\:placeholder-gray-400:is(.dark *)::-moz-placeholder { --tw-placeholder-opacity: 1; color: rgb(156 163 175 / var(--tw-placeholder-opacity)); diff --git a/apps/server/AliasVault.Client/Auth/Pages/Start.razor b/apps/server/AliasVault.Client/Auth/Pages/Start.razor index 316e98b6d..5b19a9cb4 100644 --- a/apps/server/AliasVault.Client/Auth/Pages/Start.razor +++ b/apps/server/AliasVault.Client/Auth/Pages/Start.razor @@ -30,6 +30,12 @@

    @Localizer["TaglineText"]

    + @if (_isHttpWarning) + { + + @Localizer["HttpsWarningMessage"] + + }
    @if (Config.PublicRegistrationEnabled) { @@ -50,6 +56,7 @@ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Auth.Start", "AliasVault.Client"); + private bool _isHttpWarning = false; /// protected override async Task OnInitializedAsync() @@ -62,5 +69,20 @@ // Already authenticated, redirect to home page. NavigationManager.NavigateTo("/"); } + + CheckHttpProtocol(); + } + + /// + /// Checks if the current URL is using HTTP and shows warning if needed. + /// Only shows warning for non-localhost hostnames since browsers allow crypto operations on localhost via HTTP. + /// + private void CheckHttpProtocol() + { + var uri = new Uri(NavigationManager.Uri); + var isLocalhost = uri.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase) || + uri.Host.Equals("127.0.0.1", StringComparison.OrdinalIgnoreCase) || + uri.Host.Equals("::1", StringComparison.OrdinalIgnoreCase); + _isHttpWarning = uri.Scheme == "http" && !isLocalhost; } } diff --git a/apps/server/AliasVault.Client/Main/Layout/Footer.razor b/apps/server/AliasVault.Client/Main/Layout/Footer.razor index a7078037d..fba77ae86 100644 --- a/apps/server/AliasVault.Client/Main/Layout/Footer.razor +++ b/apps/server/AliasVault.Client/Main/Layout/Footer.razor @@ -8,7 +8,7 @@

    - Β© 2024 @AppInfo.ApplicationName v@(AppInfo.GetFullVersion()). @Localizer["CopyrightText"] + Β© @(DateTime.Now.Year) @AppInfo.ApplicationName v@(AppInfo.GetFullVersion()). @Localizer["CopyrightText"]

    diff --git a/apps/server/AliasVault.Client/Resources/Pages/Auth/Start.en.resx b/apps/server/AliasVault.Client/Resources/Pages/Auth/Start.en.resx index c73f047fc..7e188fe8f 100644 --- a/apps/server/AliasVault.Client/Resources/Pages/Auth/Start.en.resx +++ b/apps/server/AliasVault.Client/Resources/Pages/Auth/Start.en.resx @@ -74,4 +74,12 @@ Log in with existing account Button text for logging in with existing account + + HTTPS Required + Title for HTTPS warning banner + + + Browsers only allow secure crypto operations via HTTPS, except for localhost. Login/registration won't work over HTTP with the current hostname. Please switch to HTTPS. + Message explaining why HTTPS is required + \ No newline at end of file diff --git a/apps/server/AliasVault.Client/wwwroot/css/tailwind.css b/apps/server/AliasVault.Client/wwwroot/css/tailwind.css index 625a64be6..bf4057d68 100644 --- a/apps/server/AliasVault.Client/wwwroot/css/tailwind.css +++ b/apps/server/AliasVault.Client/wwwroot/css/tailwind.css @@ -1525,6 +1525,11 @@ video { border-color: rgb(254 215 170 / var(--tw-border-opacity)); } +.border-orange-500 { + --tw-border-opacity: 1; + border-color: rgb(249 115 22 / var(--tw-border-opacity)); +} + .border-primary-100 { --tw-border-opacity: 1; border-color: rgb(253 222 133 / var(--tw-border-opacity)); @@ -1665,6 +1670,11 @@ video { background-color: rgb(238 242 255 / var(--tw-bg-opacity)); } +.bg-orange-100 { + --tw-bg-opacity: 1; + background-color: rgb(255 237 213 / var(--tw-bg-opacity)); +} + .bg-orange-50 { --tw-bg-opacity: 1; background-color: rgb(255 247 237 / var(--tw-bg-opacity)); @@ -1771,6 +1781,10 @@ video { --tw-gradient-to: #d68338 var(--tw-gradient-to-position); } +.fill-current { + fill: currentColor; +} + .fill-primary-600 { fill: #d68338; } @@ -2146,6 +2160,16 @@ video { color: rgb(67 56 202 / var(--tw-text-opacity)); } +.text-orange-500 { + --tw-text-opacity: 1; + color: rgb(249 115 22 / var(--tw-text-opacity)); +} + +.text-orange-700 { + --tw-text-opacity: 1; + color: rgb(194 65 12 / var(--tw-text-opacity)); +} + .text-orange-800 { --tw-text-opacity: 1; color: rgb(154 52 18 / var(--tw-text-opacity)); @@ -3443,6 +3467,10 @@ video { } @media (min-width: 1024px) { + .lg\:fixed { + position: fixed; + } + .lg\:relative { position: relative; } @@ -3503,6 +3531,10 @@ video { display: none; } + .lg\:min-h-screen { + min-height: 100vh; + } + .lg\:w-1\/2 { width: 50%; } diff --git a/apps/server/AliasVault.Client/wwwroot/js/crypto.js b/apps/server/AliasVault.Client/wwwroot/js/crypto.js index a834ce34a..7610ba8af 100644 --- a/apps/server/AliasVault.Client/wwwroot/js/crypto.js +++ b/apps/server/AliasVault.Client/wwwroot/js/crypto.js @@ -1,9 +1,34 @@ +/** + * Custom error class for crypto availability issues + */ +class CryptoNotAvailableError extends Error { + constructor(message) { + super(message); + this.name = 'CryptoNotAvailableError'; + // Prevent stack trace from being captured + this.stack = ''; + } +} + +/** + * Check if crypto API is available and throw user-friendly error if not. + */ +function checkCryptoAvailable() { + if (!window.crypto || !window.crypto.subtle) { + const error = new CryptoNotAvailableError("Cryptographic operations are not available. Please ensure you are accessing AliasVault over HTTPS, as this is required for security features to work properly."); + console.error(error.message); + throw error; + } +} + /** * AES (symmetric) encryption and decryption functions. * @type {{encrypt: (function(*, *): Promise), decrypt: (function(*, *): Promise), decryptBytes: (function(*, *): Promise)}} */ window.cryptoInterop = { encrypt: async function (plaintext, base64Key) { + checkCryptoAvailable(); + const key = await window.crypto.subtle.importKey( "raw", Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)), @@ -36,6 +61,8 @@ window.cryptoInterop = { ); }, decrypt: async function (base64Ciphertext, base64Key) { + checkCryptoAvailable(); + const key = await window.crypto.subtle.importKey( "raw", Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)), @@ -61,6 +88,8 @@ window.cryptoInterop = { return decoder.decode(decrypted); }, decryptBytes: async function (base64Ciphertext, base64Key) { + checkCryptoAvailable(); + const key = await window.crypto.subtle.importKey( "raw", Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)), @@ -96,6 +125,8 @@ window.rsaInterop = { * @returns {Promise<{publicKey: string, privateKey: string}>} A promise that resolves to an object containing the public and private keys as JWK strings. */ generateRsaKeyPair : async function() { + checkCryptoAvailable(); + const keyPair = await window.crypto.subtle.generateKey( { name: "RSA-OAEP", @@ -122,6 +153,8 @@ window.rsaInterop = { * @returns {Promise} A promise that resolves to the encrypted data as a base64-encoded string. */ encryptWithPublicKey : async function(plaintext, publicKey) { + checkCryptoAvailable(); + const publicKeyObj = await window.crypto.subtle.importKey( "jwk", JSON.parse(publicKey), @@ -151,6 +184,8 @@ window.rsaInterop = { * @returns {Promise} A promise that resolves to the decrypted data as a Uint8Array. */ decryptWithPrivateKey: async function(ciphertext, privateKey) { + checkCryptoAvailable(); + try { // Parse the private key let parsedPrivateKey = JSON.parse(privateKey); diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index d8bbe9826..877572723 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -10,8 +10,8 @@ LABEL org.opencontainers.image.description="Nginx reverse proxy for AliasVault. # Install OpenSSL and inotify-tools for certificate watching RUN apk add --no-cache openssl inotify-tools -# Copy configuration and entrypoint script -COPY apps/server/nginx.conf /etc/nginx/nginx.conf +# Copy all nginx configurations and entrypoint script +COPY apps/server/nginx*.conf /etc/nginx/ COPY apps/server/status.html /usr/share/nginx/html/status.html COPY apps/server/entrypoint.sh /docker-entrypoint.sh diff --git a/apps/server/Shared/AliasVault.RazorComponents/Alerts/MessageInfo.razor b/apps/server/Shared/AliasVault.RazorComponents/Alerts/MessageInfo.razor new file mode 100644 index 000000000..e1d1c1a11 --- /dev/null +++ b/apps/server/Shared/AliasVault.RazorComponents/Alerts/MessageInfo.razor @@ -0,0 +1,35 @@ +ο»Ώ@inherits ComponentBase + +@if (ChildContent != null) +{ + +} + +@code { + /// + /// Gets or sets the title to display. + /// + [Parameter] + public string? Title { get; set; } + + /// + /// Gets or sets the child content to display. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } +} diff --git a/apps/server/entrypoint.sh b/apps/server/entrypoint.sh index 919db936e..fa60f1333 100644 --- a/apps/server/entrypoint.sh +++ b/apps/server/entrypoint.sh @@ -3,17 +3,67 @@ # Create SSL directory if it doesn't exist mkdir -p /etc/nginx/ssl -# Generate self-signed SSL certificate if not exists -if [ ! -f /etc/nginx/ssl/cert.pem ] || [ ! -f /etc/nginx/ssl/key.pem ]; then +# Select the appropriate nginx configuration based on FORCE_HTTPS_REDIRECT +# Default to true (nginx-443.conf) for backward compatibility +# Only disable redirect if explicitly set to "false" +if [ "${FORCE_HTTPS_REDIRECT:-true}" = "false" ]; then + echo "Using nginx-80-443.conf (HTTP and HTTPS without redirect)" + cp /etc/nginx/nginx-80-443.conf /etc/nginx/nginx.conf +else + echo "Using nginx-443.conf (HTTP to HTTPS redirect enabled - default)" + cp /etc/nginx/nginx-443.conf /etc/nginx/nginx.conf +fi + +# Function to check if certificate needs regeneration +needs_cert_regeneration() { + # If cert doesn't exist, need to generate + if [ ! -f /etc/nginx/ssl/cert.pem ] || [ ! -f /etc/nginx/ssl/key.pem ]; then + return 0 + fi + + # Check if we have a hostname marker file + if [ -f /etc/nginx/ssl/.hostname_marker ]; then + STORED_HOSTNAME=$(cat /etc/nginx/ssl/.hostname_marker) + if [ "$STORED_HOSTNAME" != "${HOSTNAME:-localhost}" ]; then + echo "Hostname changed from '$STORED_HOSTNAME' to '${HOSTNAME:-localhost}', regenerating certificate..." + return 0 + fi + else + # No marker file, regenerate to be safe + return 0 + fi + + return 1 +} + +# Generate self-signed SSL certificate if not exists or hostname changed +if needs_cert_regeneration; then echo "Generating new SSL certificate (10 years validity)..." - openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ - -keyout /etc/nginx/ssl/key.pem \ - -out /etc/nginx/ssl/cert.pem \ - -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" + + HOSTNAME_VALUE="${HOSTNAME:-localhost}" + + if [ "$HOSTNAME_VALUE" = "localhost" ] || [ -z "$HOSTNAME_VALUE" ]; then + # Default localhost-only configuration + openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -keyout /etc/nginx/ssl/key.pem \ + -out /etc/nginx/ssl/cert.pem \ + -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" + else + # Generate certificate with the hostname and include localhost as SAN + openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -keyout /etc/nginx/ssl/key.pem \ + -out /etc/nginx/ssl/cert.pem \ + -subj "/C=US/ST=State/L=City/O=AliasVault/CN=${HOSTNAME_VALUE}" \ + -addext "subjectAltName=DNS:${HOSTNAME_VALUE},DNS:localhost,IP:127.0.0.1" + fi # Set proper permissions chmod 644 /etc/nginx/ssl/cert.pem chmod 600 /etc/nginx/ssl/key.pem + + # Store current hostname for change detection + echo "${HOSTNAME:-localhost}" > /etc/nginx/ssl/.hostname_marker + chmod 644 /etc/nginx/ssl/.hostname_marker fi # Create the appropriate SSL configuration based on LETSENCRYPT_ENABLED diff --git a/apps/server/nginx-443.conf b/apps/server/nginx-443.conf new file mode 100644 index 000000000..69af1f4aa --- /dev/null +++ b/apps/server/nginx-443.conf @@ -0,0 +1,143 @@ +# This is the Nginx configuration file for the AliasVault reverse-proxy container. +# which exposes all AliasVault services via 443 (HTTPS) with a self-signed or Let's Encrypt certificate. +# It also redirects all non-ACME challenge requests from HTTP to HTTPS. +events { + worker_connections 1024; +} + +http { + client_max_body_size 25M; + + upstream client { + server client:3000 max_fails=1 fail_timeout=5s; + } + + upstream api { + server api:3001 max_fails=1 fail_timeout=5s; + } + + upstream admin { + server admin:3002 max_fails=1 fail_timeout=5s; + } + + # Preserve any existing X-Forwarded-* headers, this is relevant if AliasVault + # is running behind another reverse proxy. + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Enable gzip compression, which reduces the amount of data that needs to be transferred + # to speed up WASM load times. + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + server { + listen 80; + server_name _; + + # Handle ACME challenge for Let's Encrypt certificate validation + location /.well-known/acme-challenge/ { + allow all; + root /var/www/certbot; + try_files $uri =404; + default_type "text/plain"; + add_header Cache-Control "no-cache"; + break; + } + + # Redirect all other HTTP traffic to HTTPS + location / { + return 301 https://$host$request_uri; + } + } + + server { + listen 443 ssl; + server_name _; + + # Include the appropriate SSL certificate configuration generated + # by the entrypoint script. + include /etc/nginx/ssl.conf; + + # Security headers + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Cross-Origin-Resource-Policy "same-origin" always; + add_header Content-Security-Policy "frame-ancestors 'self'" always; + + # Root for static files + root /usr/share/nginx/html; + + # Error page handler location (internal use only) + location = /status.html { + internal; + try_files /status.html =404; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + } + + # Admin interface + location /admin { + proxy_pass http://admin; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Prefix /admin/; + + # Rewrite HTTP redirects to HTTPS + proxy_redirect http:// https://; + + # Add WebSocket support for Blazor server + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 86400; + + # Show status page if admin is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + + # API endpoints + location /api { + proxy_pass http://api; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Show status page if api is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + + # Client app (root path) + location / { + proxy_pass http://client; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Rewrite HTTP redirects to HTTPS + proxy_redirect http:// https://; + + # Show status page if client is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + } +} diff --git a/apps/server/nginx-80-443.conf b/apps/server/nginx-80-443.conf new file mode 100644 index 000000000..23dad877d --- /dev/null +++ b/apps/server/nginx-80-443.conf @@ -0,0 +1,128 @@ +# This is the main Nginx configuration file for the AliasVault reverse-proxy container. +# which exposes all AliasVault services via both 80 (HTTP) +# and 443 (HTTPS) with either a self-signed or Let's Encrypt certificate. +events { + worker_connections 1024; +} + +http { + client_max_body_size 25M; + + upstream client { + server client:3000 max_fails=1 fail_timeout=5s; + } + + upstream api { + server api:3001 max_fails=1 fail_timeout=5s; + } + + upstream admin { + server admin:3002 max_fails=1 fail_timeout=5s; + } + + # Preserve any existing X-Forwarded-* headers, this is relevant if AliasVault + # is running behind another reverse proxy. + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Enable gzip compression, which reduces the amount of data that needs to be transferred + # to speed up WASM load times. + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + server { + listen 80; + listen 443 ssl; + server_name _; + + # Include the appropriate SSL certificate configuration generated + # by the entrypoint script (only applies to 443). + include /etc/nginx/ssl.conf; + + # Security headers + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Cross-Origin-Resource-Policy "same-origin" always; + add_header Content-Security-Policy "frame-ancestors 'self'" always; + + # Root for static files + root /usr/share/nginx/html; + + # Handle ACME challenge for Let's Encrypt certificate validation + location /.well-known/acme-challenge/ { + allow all; + root /var/www/certbot; + try_files $uri =404; + default_type "text/plain"; + add_header Cache-Control "no-cache"; + break; + } + + # Error page handler location (internal use only) + location = /status.html { + internal; + try_files /status.html =404; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + } + + # Admin interface + location /admin { + proxy_pass http://admin; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Prefix /admin/; + + # Add WebSocket support for Blazor server + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 86400; + + # Show status page if admin is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + + # API endpoints + location /api { + proxy_pass http://api; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Show status page if api is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + + # Client app (root path) + location / { + proxy_pass http://client; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Show status page if client is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + } +} diff --git a/dockerfiles/all-in-one/Dockerfile b/dockerfiles/all-in-one/Dockerfile index fd5b28cca..5785b3700 100644 --- a/dockerfiles/all-in-one/Dockerfile +++ b/dockerfiles/all-in-one/Dockerfile @@ -18,24 +18,39 @@ COPY shared/ ./shared/ # Install required .NET workloads and restore packages once for the entire solution WORKDIR /src/apps/server + +# Install workloads and restore packages once for the entire solution RUN dotnet workload install wasm-tools && \ dotnet restore aliasvault.sln -# Build all applications +# Build each application in separate layers to avoid lock contention + +# Build API RUN dotnet publish AliasVault.Api/AliasVault.Api.csproj \ - -c Release -o /app/api --no-restore && \ - dotnet publish AliasVault.Client/AliasVault.Client.csproj \ - -c Release -o /app/client --no-restore && \ - dotnet publish AliasVault.Admin/AliasVault.Admin.csproj \ - -c Release -o /app/admin --no-restore && \ - dotnet publish Services/AliasVault.SmtpService/AliasVault.SmtpService.csproj \ - -c Release -o /app/smtp --no-restore && \ - dotnet publish Services/AliasVault.TaskRunner/AliasVault.TaskRunner.csproj \ - -c Release -o /app/taskrunner --no-restore && \ - dotnet publish Utilities/AliasVault.InstallCli/AliasVault.InstallCli.csproj \ - -c Release -o /app/installcli --no-restore && \ - # Clean up .NET debug files and unnecessary files - find /app -name "*.pdb" -delete && \ + -c Release -o /app/api --no-restore + +# Build Client (contains WASM which can be slow) +RUN dotnet publish AliasVault.Client/AliasVault.Client.csproj \ + -c Release -o /app/client --no-restore + +# Build Admin +RUN dotnet publish AliasVault.Admin/AliasVault.Admin.csproj \ + -c Release -o /app/admin --no-restore + +# Build SMTP Service +RUN dotnet publish Services/AliasVault.SmtpService/AliasVault.SmtpService.csproj \ + -c Release -o /app/smtp --no-restore + +# Build Task Runner +RUN dotnet publish Services/AliasVault.TaskRunner/AliasVault.TaskRunner.csproj \ + -c Release -o /app/taskrunner --no-restore + +# Build Install CLI +RUN dotnet publish Utilities/AliasVault.InstallCli/AliasVault.InstallCli.csproj \ + -c Release -o /app/installcli --no-restore + +# Clean up .NET debug files and unnecessary files +RUN find /app -name "*.pdb" -delete && \ find /app -name "*.xml" -not -name "*.deps.json" -delete && \ find /app -name "*.Development.json" -delete && \ find /app -name "web.config" -delete @@ -109,31 +124,33 @@ RUN apt-get update && \ /app/taskrunner \ /app/installcli \ /database \ - /certificates/ssl \ /logs/postgres \ - /etc/nginx/ssl \ /var/run/postgresql \ - /var/www/certbot \ /etc/s6-overlay/s6-rc.d/user/contents.d # Copy built applications and all configuration files from builder stage COPY --from=dotnet-builder /app /app -# Copy configuration files and scripts directly to their destinations -COPY dockerfiles/all-in-one/reset-admin-password.sh /usr/local/bin/reset-admin-password.sh +# Copy all scripts from scripts directory to /usr/local/bin +COPY dockerfiles/all-in-one/scripts/ /usr/local/bin/ COPY apps/server/AliasVault.Client/nginx.conf /app/client/nginx.conf -COPY apps/server/nginx.conf /etc/nginx/nginx.conf +COPY dockerfiles/all-in-one/config/nginx-80-443.conf /etc/nginx/nginx-80-443.conf +COPY dockerfiles/all-in-one/config/nginx-443.conf /etc/nginx/nginx-443.conf COPY apps/server/status.html /usr/share/nginx/html/status.html COPY dockerfiles/all-in-one/s6-scripts /etc/s6-overlay/s6-rc.d/ # Copy InstallCLI to /usr/local/bin and configure everything RUN cp -r /app/installcli /usr/local/bin/aliasvault-cli && \ - chmod +x /usr/local/bin/aliasvault-cli/AliasVault.InstallCli \ - /usr/local/bin/reset-admin-password.sh && \ + chmod +x /usr/local/bin/aliasvault-cli/AliasVault.InstallCli && \ ln -s /usr/local/bin/aliasvault-cli/AliasVault.InstallCli /usr/local/bin/aliasvault-cli.sh && \ - sed -i 's/server client:3000/server localhost:3000/g; \ - s/server api:3001/server localhost:3001/g; \ - s/server admin:3002/server localhost:3002/g' /etc/nginx/nginx.conf && \ + # Make all scripts executable and create symlinks without .sh extension + for script in /usr/local/bin/*.sh; do \ + if [ -f "$script" ]; then \ + chmod +x "$script" && \ + basename="$(basename "$script" .sh)" && \ + ln -sf "$script" "/usr/local/bin/$basename"; \ + fi; \ + done && \ find /etc/s6-overlay/s6-rc.d -type f \( -name "run" -o -name "up" -o -name "script" \) -exec chmod +x {} \; && \ cd /etc/s6-overlay/s6-rc.d/user/contents.d && \ touch init postgres api client admin smtp taskrunner nginx notification && \ @@ -146,15 +163,17 @@ ENV ALIASVAULT_VERBOSITY=0 \ IP_LOGGING_ENABLED=true \ SUPPORT_EMAIL="" \ PRIVATE_EMAIL_DOMAINS="" \ + HOSTNAME=localhost \ POSTGRES_HOST=localhost \ POSTGRES_PORT=5432 \ POSTGRES_USER=aliasvault \ POSTGRES_DATABASE=aliasvault \ + FORCE_HTTPS_REDIRECT=false \ S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \ S6_VERBOSITY=0 EXPOSE 80 443 25 587 -VOLUME ["/database", "/certificates", "/logs", "/secrets"] +VOLUME ["/database", "/logs", "/secrets"] HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=3 \ - CMD curl -f -k https://localhost/api || exit 1 + CMD curl -f http://localhost/api || exit 1 ENTRYPOINT ["/init"] \ No newline at end of file diff --git a/dockerfiles/all-in-one/README.md b/dockerfiles/all-in-one/README.md index a22b570d0..778bf858e 100644 --- a/dockerfiles/all-in-one/README.md +++ b/dockerfiles/all-in-one/README.md @@ -18,9 +18,7 @@ docker build -f dockerfiles/all-in-one/Dockerfile -t aliasvault-allinone:local . docker run -d \ --name aliasvault \ -p 80:80 \ - -p 443:443 \ -v ./database:/database \ - -v ./certificates:/certificates \ -v ./logs:/logs \ -v ./secrets:/secrets \ aliasvault-allinone:local diff --git a/dockerfiles/all-in-one/config/nginx-443.conf b/dockerfiles/all-in-one/config/nginx-443.conf new file mode 100644 index 000000000..3a6da293d --- /dev/null +++ b/dockerfiles/all-in-one/config/nginx-443.conf @@ -0,0 +1,126 @@ +# Nginx configuration for AliasVault all-in-one container with forced HTTP to HTTPS redirect +# and uses a self-signed cert. +events { + worker_connections 1024; +} + +http { + client_max_body_size 25M; + + upstream client { + server localhost:3000 max_fails=1 fail_timeout=5s; + } + + upstream api { + server localhost:3001 max_fails=1 fail_timeout=5s; + } + + upstream admin { + server localhost:3002 max_fails=1 fail_timeout=5s; + } + + # Preserve any existing X-Forwarded-* headers, this is relevant if AliasVault + # is running behind another reverse proxy. + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Enable gzip compression, which reduces the amount of data that needs to be transferred + # to speed up WASM load times. + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + # HTTP server - redirects all traffic to HTTPS + server { + listen 80; + server_name _; + + # Redirect all HTTP traffic to HTTPS + return 301 https://$host$request_uri; + } + + # HTTPS server + server { + listen 443 ssl; + server_name _; + + # Include the appropriate SSL certificate configuration generated + # by the entrypoint script. + include /etc/nginx/ssl.conf; + + # Security headers + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Cross-Origin-Resource-Policy "same-origin" always; + add_header Content-Security-Policy "frame-ancestors 'self'" always; + + # Root for static files + root /usr/share/nginx/html; + + # Error page handler location (internal use only) + location = /status.html { + internal; + try_files /status.html =404; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + } + + # Admin interface + location /admin { + proxy_pass http://admin; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Prefix /admin/; + + # Add WebSocket support for Blazor server + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 86400; + + # Show status page if admin is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + + # API endpoints + location /api { + proxy_pass http://api; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Show status page if api is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + + # Client app (root path) + location / { + proxy_pass http://client; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Show status page if client is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + } +} \ No newline at end of file diff --git a/dockerfiles/all-in-one/config/nginx-80-443.conf b/dockerfiles/all-in-one/config/nginx-80-443.conf new file mode 100644 index 000000000..8e287a18c --- /dev/null +++ b/dockerfiles/all-in-one/config/nginx-80-443.conf @@ -0,0 +1,188 @@ +# This is the main Nginx configuration file for the AliasVault all-in-one container. +# which exposes all AliasVault services via both 80 (HTTP) +# and optionally 443 (HTTPS) with self-signed cert. +events { + worker_connections 1024; +} + +http { + client_max_body_size 25M; + + upstream client { + server localhost:3000 max_fails=1 fail_timeout=5s; + } + + upstream api { + server localhost:3001 max_fails=1 fail_timeout=5s; + } + + upstream admin { + server localhost:3002 max_fails=1 fail_timeout=5s; + } + + # Preserve any existing X-Forwarded-* headers, this is relevant if AliasVault + # is running behind another reverse proxy. + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Enable gzip compression, which reduces the amount of data that needs to be transferred + # to speed up WASM load times. + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + server { + listen 80; + server_name _; + + # Security headers + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Cross-Origin-Resource-Policy "same-origin" always; + add_header Content-Security-Policy "frame-ancestors 'self'" always; + + # Root for static files + root /usr/share/nginx/html; + + # Error page handler location (internal use only) + location = /status.html { + internal; + try_files /status.html =404; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + } + + # Admin interface + location /admin { + proxy_pass http://admin; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Prefix /admin/; + + # Add WebSocket support for Blazor server + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 86400; + + # Show status page if admin is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + + # API endpoints + location /api { + proxy_pass http://api; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Show status page if api is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + + # Client app (root path) + location / { + proxy_pass http://client; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Show status page if client is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + } + + # HTTPS server + server { + listen 443 ssl; + server_name _; + + # Include the appropriate SSL certificate configuration generated + # by the entrypoint script. + include /etc/nginx/ssl.conf; + + # Security headers + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Cross-Origin-Resource-Policy "same-origin" always; + add_header Content-Security-Policy "frame-ancestors 'self'" always; + + # Root for static files + root /usr/share/nginx/html; + + # Error page handler location (internal use only) + location = /status.html { + internal; + try_files /status.html =404; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + } + + # Admin interface + location /admin { + proxy_pass http://admin; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Prefix /admin/; + + # Add WebSocket support for Blazor server + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 86400; + + # Show status page if admin is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + + # API endpoints + location /api { + proxy_pass http://api; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Show status page if api is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + + # Client app (root path) + location / { + proxy_pass http://client; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Show status page if client is down + proxy_intercept_errors on; + error_page 502 503 504 =503 /status.html; + } + } +} \ No newline at end of file diff --git a/dockerfiles/all-in-one/s6-scripts/init/script b/dockerfiles/all-in-one/s6-scripts/init/script index f4204bfac..ecff5bea2 100644 --- a/dockerfiles/all-in-one/s6-scripts/init/script +++ b/dockerfiles/all-in-one/s6-scripts/init/script @@ -1,4 +1,4 @@ -#!/bin/sh -e +#!/command/with-contenv sh # AliasVault Container Initialization Script # This script runs once at container startup and handles all initialization tasks @@ -154,37 +154,81 @@ else chmod 700 /database/postgres fi -# Future: Database migrations could go here -# log 1 "[init] Checking for database migrations..." -# if [ -f /app/migrations/pending ]; then -# log 1 "[init] β†’ Running database migrations..." -# # Run migration logic here -# fi +# Function to check if certificate needs regeneration +needs_cert_regeneration() { + # If cert doesn't exist, need to generate + if [ ! -f /certificates/ssl/cert.pem ] || [ ! -f /certificates/ssl/key.pem ]; then + return 0 + fi -# Generate SSL certificates if needed -if [ ! -f /certificates/ssl/cert.pem ] || [ ! -f /certificates/ssl/key.pem ]; then + # Check if we have a hostname marker file + if [ -f /certificates/ssl/.hostname_marker ]; then + STORED_HOSTNAME=$(cat /certificates/ssl/.hostname_marker) + if [ "$STORED_HOSTNAME" != "${HOSTNAME:-localhost}" ]; then + log 0 "[init] Hostname changed from '$STORED_HOSTNAME' to '${HOSTNAME:-localhost}', regenerating certificate..." + return 0 + fi + else + # No marker file, regenerate to be safe + return 0 + fi + + return 1 +} + +# Generate SSL certificates if needed or hostname changed +if needs_cert_regeneration; then log 0 "" log 0 "[init] Generating SSL certificates..." mkdir -p /certificates/ssl - if [ "$VERBOSITY" -ge 2 ]; then - openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ - -keyout /certificates/ssl/key.pem \ - -out /certificates/ssl/cert.pem \ - -subj "/C=US/ST=State/L=City/O=AliasVault/CN=${HOSTNAME:-localhost}" + + HOSTNAME_VALUE="${HOSTNAME:-localhost}" + + if [ "$HOSTNAME_VALUE" = "localhost" ] || [ -z "$HOSTNAME_VALUE" ]; then + # Default localhost-only configuration + if [ "$VERBOSITY" -ge 2 ]; then + openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -keyout /certificates/ssl/key.pem \ + -out /certificates/ssl/cert.pem \ + -subj "/C=US/ST=State/L=City/O=AliasVault/CN=localhost" + else + openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -keyout /certificates/ssl/key.pem \ + -out /certificates/ssl/cert.pem \ + -subj "/C=US/ST=State/L=City/O=AliasVault/CN=localhost" \ + >/dev/null 2>&1 + fi else - openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ - -keyout /certificates/ssl/key.pem \ - -out /certificates/ssl/cert.pem \ - -subj "/C=US/ST=State/L=City/O=AliasVault/CN=${HOSTNAME:-localhost}" \ - >/dev/null 2>&1 + # Generate certificate with the hostname and include localhost as SAN + if [ "$VERBOSITY" -ge 2 ]; then + openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -keyout /certificates/ssl/key.pem \ + -out /certificates/ssl/cert.pem \ + -subj "/C=US/ST=State/L=City/O=AliasVault/CN=${HOSTNAME_VALUE}" \ + -addext "subjectAltName=DNS:${HOSTNAME_VALUE},DNS:localhost,IP:127.0.0.1" + else + openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -keyout /certificates/ssl/key.pem \ + -out /certificates/ssl/cert.pem \ + -subj "/C=US/ST=State/L=City/O=AliasVault/CN=${HOSTNAME_VALUE}" \ + -addext "subjectAltName=DNS:${HOSTNAME_VALUE},DNS:localhost,IP:127.0.0.1" \ + >/dev/null 2>&1 + fi fi + chmod 600 /certificates/ssl/key.pem chmod 644 /certificates/ssl/cert.pem + + # Store current hostname for change detection + echo "${HOSTNAME:-localhost}" > /certificates/ssl/.hostname_marker + chmod 644 /certificates/ssl/.hostname_marker + log 0 "[init] Self-signed SSL certificates generated" else log 0 "[init] βœ… Self-signed SSL certificates already exist" fi + echo "" echo "[init] Waiting for all services to be ready... this can take a short while..." echo "" diff --git a/dockerfiles/all-in-one/s6-scripts/nginx/run b/dockerfiles/all-in-one/s6-scripts/nginx/run index 761d645d2..2f06ccd78 100644 --- a/dockerfiles/all-in-one/s6-scripts/nginx/run +++ b/dockerfiles/all-in-one/s6-scripts/nginx/run @@ -19,6 +19,16 @@ ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDS ssl_prefer_server_ciphers off; SSLEOF +# Configure nginx based on FORCE_HTTPS_REDIRECT environment variable +FORCE_HTTPS_REDIRECT=${FORCE_HTTPS_REDIRECT:-false} +if [[ "${FORCE_HTTPS_REDIRECT}" == "true" ]]; then + echo "Configuring nginx with HTTPS-only (443) - redirects HTTP to HTTPS" + cp /etc/nginx/nginx-443.conf /etc/nginx/nginx.conf +else + echo "Configuring nginx with HTTP and HTTPS support (80+443)" + cp /etc/nginx/nginx-80-443.conf /etc/nginx/nginx.conf +fi + echo "Starting Nginx reverse proxy..." # Set nginx error log level based on verbosity diff --git a/dockerfiles/all-in-one/s6-scripts/notification/run b/dockerfiles/all-in-one/s6-scripts/notification/run index 90f820baa..372eb29ef 100755 --- a/dockerfiles/all-in-one/s6-scripts/notification/run +++ b/dockerfiles/all-in-one/s6-scripts/notification/run @@ -37,16 +37,16 @@ if [ "$VERBOSITY" -le 1 ]; then # Admin password hash exists - show the legacy warning echo "πŸ”‘ Admin Login:" echo " β€’ Username: admin" - echo " β€’ Password: (previously set - to reset the admin password, login to this container via \`docker exec -it [container-name] /bin/bash\` and run: reset-admin-password.sh)" + echo " β€’ Password: (previously set - to reset the admin password, login to this container via \`docker exec -it aliasvault /bin/bash\` and run: aliasvault reset-admin-password)" echo "" else # No admin password hash file - show setup instructions echo "πŸ”‘ Admin Setup:" echo " β€’ Admin user is not configured by default" echo " β€’ To configure admin access:" - echo " 1. docker exec -it [container-name] /bin/bash" - echo " 2. reset-admin-password.sh" - echo " 3. Restart the container" + echo " 1. $ docker exec -it aliasvault /bin/bash" + echo " 2. $ aliasvault reset-admin-password" + echo " 3. $ docker compose restart" echo "" fi diff --git a/dockerfiles/all-in-one/scripts/aliasvault.sh b/dockerfiles/all-in-one/scripts/aliasvault.sh new file mode 100644 index 000000000..615af1a1f --- /dev/null +++ b/dockerfiles/all-in-one/scripts/aliasvault.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Simple AliasVault command dispatcher wrapper which allows +# running AliasVault commands from the Docker container CLI +# like `aliasvault reset-admin-password`. + +case "$1" in + reset-admin-password) + shift + exec /usr/local/bin/reset-admin-password.sh "$@" + ;; + hash-password) + shift + exec /usr/local/bin/aliasvault-cli/AliasVault.InstallCli hash-password "$@" + ;; + help|--help|-h|"") + echo "AliasVault Commands" + echo "" + echo "Usage: aliasvault [options]" + echo "" + echo "Commands:" + echo " reset-admin-password Reset admin password" + echo " hash-password Hash a password" + echo " help Show this help" + echo "" + echo "Examples:" + echo " aliasvault reset-admin-password -y" + echo " aliasvault hash-password 'mypassword'" + ;; + *) + echo "Unknown command: $1" + echo "Run 'aliasvault help' for usage" + exit 1 + ;; +esac diff --git a/dockerfiles/all-in-one/reset-admin-password.sh b/dockerfiles/all-in-one/scripts/reset-admin-password.sh similarity index 100% rename from dockerfiles/all-in-one/reset-admin-password.sh rename to dockerfiles/all-in-one/scripts/reset-admin-password.sh diff --git a/dockerfiles/docker-compose.all-in-one.nas.yml b/dockerfiles/docker-compose.all-in-one.nas.yml new file mode 100644 index 000000000..35a340c20 --- /dev/null +++ b/dockerfiles/docker-compose.all-in-one.nas.yml @@ -0,0 +1,36 @@ +# Docker Compose file for the AliasVault all-in-one container +# +# Specific for NAS devices: +# - Uses custom HTTP ports by default to avoid conflicts +# - Uses named bind mounts for the database, logs and secrets to ensure they are persisted +# +version: "3" +services: + aliasvault: + image: ghcr.io/aliasvault/aliasvault:latest + container_name: aliasvault + restart: unless-stopped + + ports: + - "1080:80" + - "1443:443" + - "25:25" + - "587:587" + + volumes: + - avdata:/database + - avlogs:/logs + - avsecrets:/secrets + + environment: + HOSTNAME: "localhost" + PUBLIC_REGISTRATION_ENABLED: "true" + IP_LOGGING_ENABLED: "true" + FORCE_HTTPS_REDIRECT: "false" + SUPPORT_EMAIL: "" + PRIVATE_EMAIL_DOMAINS: "" + +volumes: + avdata: + avlogs: + avsecrets: diff --git a/dockerfiles/docker-compose.all-in-one.yml b/dockerfiles/docker-compose.all-in-one.yml index 5266de441..caf414981 100644 --- a/dockerfiles/docker-compose.all-in-one.yml +++ b/dockerfiles/docker-compose.all-in-one.yml @@ -1,5 +1,8 @@ -version: "3" - +# Docker Compose file for the AliasVault all-in-one container +# +# This is the general purpose template which uses local folder bind mounts for the database, logs and secrets. +# - For NAS devices, its safer to use the docker-compose.all-in-one.nas.yml template instead. +# services: aliasvault: image: ghcr.io/aliasvault/aliasvault:latest @@ -14,12 +17,13 @@ services: volumes: - ./database:/database - - ./certificates:/certificates - ./logs:/logs - ./secrets:/secrets environment: + HOSTNAME: "localhost" PUBLIC_REGISTRATION_ENABLED: "true" IP_LOGGING_ENABLED: "true" + FORCE_HTTPS_REDIRECT: "false" SUPPORT_EMAIL: "" PRIVATE_EMAIL_DOMAINS: "" diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 000000000..dfd401c61 --- /dev/null +++ b/docs/404.html @@ -0,0 +1,68 @@ +--- +layout: default +title: 404 - Page not found +permalink: /404.html +nav_exclude: true +search_exclude: true +--- + + + +
    +

    404

    +

    Page Not Found

    +

    The page you're looking for doesn't exist or may have been moved.

    +

    This could happen if the documentation structure has been reorganized or the page has been renamed.

    + + Go to Home + + +
    \ No newline at end of file diff --git a/docs/Gemfile b/docs/Gemfile index 50943fbdc..28b874301 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -6,3 +6,7 @@ gem "just-the-docs" # If you want to use GitHub Pages, remove the "gem "jekyll"" above and # uncomment the line below. To upgrade, run `bundle update github-pages`. gem "github-pages", group: :jekyll_plugins + +group :jekyll_plugins do + gem "jekyll-sitemap" +end diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index f68708513..a385a6e8f 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -284,6 +284,7 @@ PLATFORMS DEPENDENCIES github-pages + jekyll-sitemap just-the-docs BUNDLED WITH diff --git a/docs/_config.yml b/docs/_config.yml index 1d25a0ea5..2dd898807 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -38,3 +38,19 @@ callouts: note: title: Note color: purple + important: + title: Important + color: yellow + +# 404 page +defaults: + - scope: + path: "404.html" + values: + sitemap: false + +# Sitemap settings +url: "https://docs.aliasvault.net" +plugins: + - jekyll-sitemap + - jekyll-redirect-from diff --git a/docs/_plugins/404_server.rb b/docs/_plugins/404_server.rb new file mode 100644 index 000000000..032522873 --- /dev/null +++ b/docs/_plugins/404_server.rb @@ -0,0 +1,44 @@ +require 'webrick' + +module Jekyll + class FourOhFourPage < StaticFile + def write(dest) + true + end + end + + class FourOhFourGenerator < Generator + priority :low + + def generate(site) + site.static_files << FourOhFourPage.new(site, site.dest, '/', '404.html') + end + end +end + +# Override WEBrick to serve 404.html for missing files +if defined?(WEBrick) + module WEBrick + class HTTPServlet::FileHandler + alias_method :do_GET_original, :do_GET + + def do_GET(req, res) + do_GET_original(req, res) + rescue HTTPStatus::NotFound => ex + return_404_page(req, res) + rescue => ex + raise ex + end + + def return_404_page(req, res) + path = File.join(@config[:DocumentRoot], '404.html') + if File.exist?(path) + res.body = File.read(path) + res['content-type'] = 'text/html' + else + raise HTTPStatus::NotFound + end + end + end + end +end \ No newline at end of file diff --git a/docs/_sass/color_schemes/aliasvault.scss b/docs/_sass/color_schemes/aliasvault.scss index 2f8d0783f..2fe8b0cb0 100644 --- a/docs/_sass/color_schemes/aliasvault.scss +++ b/docs/_sass/color_schemes/aliasvault.scss @@ -38,5 +38,11 @@ $purple-100: #ffd5a8; $purple-200: #f49541; $purple-300: #d68338; +// Orange Colors +$orange-000: #f8b963; +$orange-100: #ffd5a8; +$orange-200: #f49541; +$orange-300: #d68338; + // Navigation additional $nav-button-color: #f49541; diff --git a/docs/index.md b/docs/index.md index df6170473..9fb7b6ccf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,11 +9,11 @@ permalink: / # AliasVault Documentation {: .fs-9 } -A self-hostable, end-to-end encrypted password manager with a built-in alias generator and email server. +A privacy-first password manager with built-in email aliasing. Fully encrypted and self-hostable. {: .fs-6 .fw-300 } -[Server Installation](./installation/install){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 } +[Self-host Install](./installation){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 } [View on GitHub](https://github.com/aliasvault/aliasvault){: .btn .fs-5 .mb-4 .mb-md-0 } --- @@ -37,7 +37,7 @@ All data is end-to-end encrypted on the client. Your master password never leave Generate virtual email addresses for each identity. Emails sent to these addresses are instantly visible in the AliasVault app. ### Virtual Identities -Create separate identities for different purposes, each with its own email aliases and credentials. +Create separate identities for different purposes, each with its own email aliases. --- diff --git a/docs/installation/advanced/database.md b/docs/installation/advanced/database.md deleted file mode 100644 index 8f4c2b7b6..000000000 --- a/docs/installation/advanced/database.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -layout: default -title: Database Backup -parent: Advanced -nav_order: 4 ---- - -# Database Backup - -In order to backup the database, you can use the `install.sh` script. This script will stop all services, export the database to a file, and then restart the services. - -```bash -./install.sh db-backup > backup.sql.gz -``` - -# Database Restore - -To restore the database, you can use the `install.sh` script. This script will stop all services, drop the database, import the database from a file, and then restart the services. - -```bash -./install.sh db-restore < backup.sql.gz -``` diff --git a/docs/installation/advanced/index.md b/docs/installation/advanced/index.md deleted file mode 100644 index 99bc98940..000000000 --- a/docs/installation/advanced/index.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: default -title: Advanced -parent: Server Installation -nav_order: 2 ---- - -# Advanced Installation -The following guides provide more advanced installation options for AliasVault. These options are not required for the basic installation, but may be useful for advanced users. - -- [Manual Setup](manual-setup.md) - Step-by-step manual installation without the install script -- [Reverse Proxy Configuration](reverse-proxy-configuration.md) - Configure AliasVault with your existing reverse proxy -- [Build from Source](build-from-source.md) - Build AliasVault from source code -- [Database Configuration](database.md) - Advanced database configuration options diff --git a/docs/installation/advanced/manual-setup.md b/docs/installation/advanced/manual-setup.md deleted file mode 100644 index d8445368f..000000000 --- a/docs/installation/advanced/manual-setup.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -layout: default -title: Manual Setup -parent: Advanced -nav_order: 1 ---- - -# Manual Setup - -If you prefer to manually set up AliasVault instead of using the `install.sh` script, this README provides step-by-step instructions. - -**Prerequisities:** -- Docker (20.10+) and Docker Compose (2.0+) installed on your system - - See instructions: [https://docs.docker.com/engine/install/](https://docs.docker.com/engine/install/) -- Knowledge of working with direct Docker commands -- Knowledge of .env files -- OpenSSL for generating random passwords - ---- - -{: .toc } -* TOC -{:toc} - ---- - - -## Steps -Follow these steps to manually install AliasVault on your own server. - -1. **Clone the git repository** - ```bash - # Clone repository - git clone https://github.com/aliasvault/aliasvault.git - - # Navigate to the AliasVault directory - cd aliasvault - ``` - -2. **Create required directories** - - Create the following directories in your project root: - ```bash - # Create required directories - mkdir -p certificates/ssl database/postgres - ``` - -3. **Create .env file** - - ```bash - # Copy the .env.example file to create a new .env file - cp .env.example .env - ``` - -4. **Set all required settings in .env** - - Open the .env file in your favorite text editor and fill in all required variables - by following the instructions inside the file. - - ```bash - # Open the .env file with your favorite editor, e.g. nano. - nano .env - ``` - -5. **Start the docker containers** - - After you are done configuring your .env file, you can start the Docker Compose stack: - ```bash - # Start the docker compose stack - docker compose up -d - ``` - -6. **Access AliasVault** - - AliasVault should now be running. You can access it at: - - - Admin Panel: https://localhost/admin - - Username: admin - - Password: [Use the password you set in the .env file] - - - Client Website: https://localhost/ - - Create your own account from here - - > Note: if you changed the default ports from 80/443 to something else in the .env file, use those ports to access AliasVault here. - -7. **Configuring private email domains** - - By default, the AliasVault private email domains feature is disabled. If you wish to enable this to use your own private domains to create email aliases with, please read the `Email Server Setup` section in the main installation guide [Basic Install](../install.md#3-email-server-setup). - - For more information, read the article explaining the differences between AliasVault's [private and public domains](../../misc/private-vs-public-email.md). - - -## Important Notes - -- Make sure to save both the admin password and PostgreSQL password in a secure location. -- Always keep your .env file secure and do not share it, as it contains sensitive information. -- The PostgreSQL data is persisted in the `database/postgres` directory. -- The docker-compose.yml file uses the `:latest` tag for containers by default. This means it always uses the latest available AliasVault version. In order to update AliasVault to a newer version at a later time, you can pull new containers when they are available with this command: -``` -docker compose pull && docker compose down && docker compose up -d -``` - -## Using a Custom Reverse Proxy (e.g. Cloudflare Tunnel) - -AliasVault includes its own internal reverse proxy (nginx) container that routes traffic to other containers. By default, the built-in nginx container (`reverse-proxy`) makes AliasVault's services available at: - -- **Client**: `http://localhost/` -- **API**: `http://localhost/api` -- **Admin**: `http://localhost/admin` - -If you want to use your own reverse proxy setup (e.g. with a Cloudflare Tunnel), you **must** ensure the following: - -- Your custom proxy/tunnel **points to the AliasVault `reverse-proxy` container**, **not** directly to the client, API, or admin containers. -- The forwarding protocol must be **HTTPS**, since the `reverse-proxy` container listens on port `443` for secure connections. - -> ⚠️ Failing to route through the reverse-proxy container correctly will break the app. Errors such as HTTP 502 often indicate a misconfigured reverse proxy. - -If you're using **Cloudflare Tunnel**, you will likely encounter TLS verification issues. In that case, go to the Cloudflare dashboard and enable the **"No TLS Verify"** option for your tunnel configuration. This tells Cloudflare to skip certificate validation when connecting to the internal reverse-proxy over HTTPS. - - -## Troubleshooting -If you encounter any miscellaneous issues during the setup: - -1. Check the Docker logs: - ```bash - docker compose logs - ``` -2. Ensure all required ports (80, 443, 25, 587 and 5432) are available and not being used by other services. -3. Verify that all variables in the .env file are set correctly. -4. Check PostgreSQL container logs specifically: - ```bash - docker compose logs postgres - ``` - -For more detailed troubleshooting information, please refer to the full [troubleshooting guide](../troubleshooting.md). - -## FAQ -### Why does AliasVault use its own reverse proxy? -AliasVault requires precise routing between its client, API, and admin interfaces. These are structured under `/`, `/api`, and `/admin`. A unified nginx reverse proxy ensures that all AliasVault's containers are accessible under the same hostname and path structure. If you use your own reverse proxy, you must replicate this logic exactly. See the [nginx.conf](https://raw.githubusercontent.com/aliasvault/aliasvault/refs/heads/main/apps/server/nginx.conf) configuration that's used by the official container for reference. diff --git a/docs/installation/advanced/reverse-proxy-configuration.md b/docs/installation/advanced/reverse-proxy-configuration.md deleted file mode 100644 index 570a5e380..000000000 --- a/docs/installation/advanced/reverse-proxy-configuration.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -layout: default -title: Reverse Proxy Configuration -parent: Advanced -nav_order: 2 ---- - -# Reverse Proxy Configuration -This guide is intended for users who already have a self-hosted reverse proxy setup (such as nginx, Apache, or Cloudflare Tunnel) and want to expose AliasVault through it. In this case, the recommended approach is to forward a single hostname (e.g., `aliasvault.example.com`) to the internal reverse proxy container that AliasVault provides. - -If you do **not** already have an existing reverse proxy, you can simply rely on the built-in reverse proxy that comes with AliasVault, as it's fully capable of handling all routing, SSL termination, and WebSocket support out of the box. - - -## Overview -AliasVault includes its own internal reverse proxy (nginx) that handles routing between its three core services: -- **Client Application** (`/`) – The main user interface -- **API Server** (`/api`) – REST API endpoints -- **Admin Panel** (`/admin`) – Administrative interface - -When using an external reverse proxy, you **must** forward requests to the `reverse-proxy` container. Do **not** route traffic directly to the individual services. - -## Why AliasVault Uses Its Own Reverse Proxy -AliasVault’s internal reverse proxy ensures proper routing and configuration of all services under one domain: - -1. **Unified Hostname** – All services operate under the same domain (e.g., `aliasvault.example.com`) -2. **Path-Based Routing** – Correct dispatch of `/`, `/api`, and `/admin` requests -3. **Security Headers** – Consistent headers across services -4. **SSL Termination** – Central SSL/TLS handling -5. **WebSocket Support** – Required for the Blazor-based admin interface - -## Internal Nginx Configuration Structure -AliasVault's reverse proxy follows a path-based routing configuration like this: - -```nginx -# Upstream services -upstream client { - server client:3000; -} -upstream api { - server api:3001; -} -upstream admin { - server admin:3002; -} - -server { - listen 443 ssl; - server_name _; - - location /admin { - proxy_pass http://admin; - # WebSocket support and headers - } - - location /api { - proxy_pass http://api; - } - - location / { - proxy_pass http://client; - } -} -``` - -## Simplest Setup (Recommended) -Forward all traffic from your external reverse proxy to the AliasVault internal reverse proxy container. This avoids manual path-based routing. - -### Example: External Nginx Reverse Proxy - -```nginx -server { - listen 80; - server_name aliasvault.example.com; - return 301 https://$host$request_uri; -} - -server { - listen 443 ssl; - server_name aliasvault.example.com; - - ssl_certificate /path/to/your/certificate.crt; - ssl_certificate_key /path/to/your/private.key; - - location / { - proxy_pass https://aliasvault-internal:443; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - -## Port Configuration -AliasVault’s internal reverse proxy defaults to port `443`. If this port is already in use, you can change it in the `.env` file inside your AliasVault folder: - -``` -HTTP_PORT=80 -HTTPS_PORT=443 -``` - -Make sure your external proxy points to the correct port. - -## Common Issues and Solutions - -### HTTP 502 Bad Gateway - -**Cause**: External proxy routing to wrong container. -**Fix**: Ensure it routes to `reverse-proxy` container, not `client`, `api`, or `admin`. - -### WebSocket Errors - -**Cause**: WebSocket headers not forwarded. -**Fix**: Make sure `Upgrade` and `Connection` headers are preserved. - -### SSL/TLS Verification (e.g., Cloudflare Tunnel) - -**Cause**: TLS verification fails. -**Fix**: Enable β€œNo TLS Verify” in your Cloudflare Tunnel configuration. - -### Partial Routing Failures - -**Cause**: External proxy modifies paths. -**Fix**: Do not strip or rewrite paths. Proxy should pass `/`, `/api`, `/admin` as-is. - -## Testing -After setup, verify: - -- `https://your-domain.com/` loads the client app -- `https://your-domain.com/api` returns OK -- `https://your-domain.com/admin` loads the admin interface - -All services must be available under the **same** hostname. - -## Security Considerations -- Always use HTTPS in production -- Preserve headers like `X-Real-IP`, `X-Forwarded-For` -- Consider adding rate limiting or IP restrictions on your external proxy -- Keep certificates and secrets secure - -For advanced scenarios or troubleshooting, refer to the main [troubleshooting guide](../troubleshooting.md). diff --git a/docs/installation/docker-compose/advanced/database.md b/docs/installation/docker-compose/advanced/database.md new file mode 100644 index 000000000..f8239b636 --- /dev/null +++ b/docs/installation/docker-compose/advanced/database.md @@ -0,0 +1,32 @@ +--- +layout: default +title: Database Operations +parent: Advanced +grand_parent: Docker Compose +nav_order: 4 +--- + +# Database Operations +This page explains how to import/export on the AliasVault server database via Docker commands. + +## Database Export +In order to backup the AliasVault server database (which includes all encrypted user vaults as well), you can use the following command. Run this command from the directory where your AliasVault `docker-compose.yml` is located. + +```bash +$ docker compose exec aliasvault pg_dump -U aliasvault aliasvault > backup.sql +``` + +## Database Import + +To restore a previously exported database, you can use the following snippet. Run these commands from the directory where your AliasVault `docker-compose.yml` is located. + +```bash +# Drop database first (warning: this can't be undone!) +docker compose exec -T aliasvault psql -U postgres -d postgres -c "DROP DATABASE IF EXISTS aliasvault WITH (FORCE);" + +# Create new empty database +docker compose exec -T aliasvault psql -U postgres -d postgres -c "CREATE DATABASE aliasvault OWNER aliasvault;" + +# Import backup +docker compose exec -T aliasvault psql -U aliasvault aliasvault < backup.sql +``` diff --git a/docs/installation/docker-compose/advanced/index.md b/docs/installation/docker-compose/advanced/index.md new file mode 100644 index 000000000..ed60a1a69 --- /dev/null +++ b/docs/installation/docker-compose/advanced/index.md @@ -0,0 +1,9 @@ +--- +layout: default +title: Advanced +parent: Docker Compose +nav_order: 2 +--- + +# Advanced Installation +The following guides provide more advanced installation options for AliasVault. These options are not required for the basic installation, but may be useful for advanced users. diff --git a/docs/installation/docker-compose/advanced/lifecycle.md b/docs/installation/docker-compose/advanced/lifecycle.md new file mode 100644 index 000000000..823758ec4 --- /dev/null +++ b/docs/installation/docker-compose/advanced/lifecycle.md @@ -0,0 +1,30 @@ +--- +layout: default +title: Stop/start +parent: Advanced +grand_parent: Docker Compose +nav_order: 2 +--- + +# Stopping and starting AliasVault +You can stop and start AliasVault via the default docker compose commands. Run these commands from the directory where your AliasVault `docker-compose.yml` is located. + +## Stop +To stop AliasVault: +```bash +$ docker compose down +``` + +## Start +To start AliasVault: + +```bash +$ docker compose up -d +``` + +## Restart +To restart AliasVault (note: when making changes to the `docker-compose.yml`, you'll need to manually stop and start to make the new changes be applied) + +```bash +$ docker compose restart +``` diff --git a/docs/installation/docker-compose/advanced/uninstall.md b/docs/installation/docker-compose/advanced/uninstall.md new file mode 100644 index 000000000..c6ad92574 --- /dev/null +++ b/docs/installation/docker-compose/advanced/uninstall.md @@ -0,0 +1,20 @@ +--- +layout: default +title: Uninstall +parent: Advanced +grand_parent: Docker Compose +nav_order: 4 +--- + +# Uninstall + +To uninstall AliasVault, run the following command. This will stop and remove the AliasVault containers and remove the Docker images. + +{: .note } +This will not delete any data stored in the database. If you wish to delete all data, you should manually delete the `database` directory and the other directories created by AliasVault. + +### Steps +1. Run docker compose down and remove any local Docker images related to AliasVault. +```bash +$ docker compose down --rmi all +``` diff --git a/docs/installation/docker-compose/index.md b/docs/installation/docker-compose/index.md new file mode 100644 index 000000000..0cd17e4de --- /dev/null +++ b/docs/installation/docker-compose/index.md @@ -0,0 +1,186 @@ +--- +layout: default +title: Docker Compose +parent: Self-host Install +redirect_from: + - /installation/advanced/manual-setup + - /installation/advanced/manual-setup.html +nav_order: 2 +--- + +# Self-host using Docker Compose (single container) +The following guide will walk you through the steps to install AliasVault via the All-In-One Docker container. This container uses `s6-overlay` to combine all AliasVault's services into one image for convenience. The only downside compared to the `install.sh` installer is that this version does NOT come with SSL/TLS support, so you'll have to make the container available through your own SSL/TLS proxy. + +{: .important-title } +> Requirements: +> - Docker (20.10+) and Docker Compose (2.0+) installed on your system +> - See instructions: [https://docs.docker.com/engine/install/](https://docs.docker.com/engine/install/) +> - You have existing SSL/TLS proxy infrastructure (Traefik, Nginx, HAProxy, Cloudflare Tunnel) +> - Knowledge of working with direct Docker commands +> - Knowledge of .yml and .env files + +## 1. Basic installation +1. Create a new folder where you want to store AliasVault's data and configuration folders. +```bash +mkdir aliasvault +cd aliasvault +``` +2. Create a new `docker-compose.yml` file with the following contents. Note: the directories specified in `volumes:` will be auto-created in the current folder on container first start. + +```yaml +services: + aliasvault: + image: ghcr.io/aliasvault/aliasvault:latest + container_name: aliasvault + restart: unless-stopped + + ports: + - "80:80" + - "443:443" + - "25:25" + - "587:587" + + volumes: + - ./database:/database + - ./logs:/logs + - ./secrets:/secrets + + environment: + HOSTNAME: "localhost" + PUBLIC_REGISTRATION_ENABLED: "true" + IP_LOGGING_ENABLED: "true" + FORCE_HTTPS_REDIRECT: "false" + SUPPORT_EMAIL: "" + PRIVATE_EMAIL_DOMAINS: "" +``` +3. Run `docker-compose up -d` to start the container. +4. After the container has started, AliasVault should now be running. You can access it at: + + - Admin Panel: http://localhost/admin + - **Username:** admin + - **Password:** [*Read instructions on page*] + + - Client Website: http://localhost/ + - Create your own account from here + + - API: http://localhost/api + - Used for configuring the browser extension and mobile app to connect to your server + +--- + +## 2. SSL/TLS configuration +To use AliasVault securely, HTTPS is required in the following situations: +- When accessing the web app from any address other than `localhost` (due to browser security restrictions) +- When using the mobile apps, which require the API URL to have a valid TLS certificate; otherwise, the app will not connect + +You must set up and configure your own TLS/SSL infrastructure (such as Traefik, Nginx, HAProxy, or Cloudflare Tunnel) to make the AliasVault container accessible over HTTPS with a valid SSL/TLS certificate. For example: `https://aliasvault.yourdomain.com`. + +--- + +## 3. Email Server Setup + +AliasVault includes a built-in email server that allows you to generate email aliases on-the-fly for every website you use, and receive the emails straight in AliasVault. + +{: .note } +If you skip this step, AliasVault will default to use public email domains offered by SpamOK. While this still works for creating aliases, it has privacy limitations. For complete privacy and control, we recommend setting up your own domain. +[Learn more about the differences between private and public email domains](../misc/private-vs-public-email.md). + +--- + +### Requirements +- A **public IPv4 address** with ports 25 and 587 forwarded to your AliasVault server +- Open ports **25** and **587** on your server firewall for email SMTP traffic (*NOTE: some residential IP's block this, check with your ISP*). + +#### Verifying Port Access + +While the AliasVault docker containers are running, use `telnet` to confirm your public IP allows access to the ports: + +```bash +# Test standard SMTP port +telnet 25 + +# Test secure SMTP port +telnet 587 +``` + +### Choose your configuration: primary domain vs subdomain + +AliasVault can be configured under: + +- **A primary (top-level) domain** + Example: `your-aliasvault.net`. This allows you to receive email on `%alias%@your-aliasvault.net`. + +- **A subdomain of your existing domain** + Example: `aliasvault.example.net`. This allows you to receive email on `%alias%@aliasvault.example.net`. Email sent to your main domain remains unaffected and will continue arriving in your usual inbox. + +--- + +#### a) Setup using a primary domain + +##### DNS Configuration + +Configure the following DNS records **on your primary domain** (e.g. `your-aliasvault.net`): + +| Name | Type | Priority | Content | TTL | +|------|------|----------|---------------------------|-----| +| mail | A | | `` | 3600 | +| @ | MX | 10 | `mail.your-aliasvault.net`| 3600 | + +> Replace `` with your actual server IP. + +##### Example + +- `mail.your-aliasvault.net` points to your server IP. +- Email to `@your-aliasvault.net` will be handled by your AliasVault server. + +--- + +#### b) Setup using a subdomain + +##### DNS Configuration + +Configure the following DNS records **on your subdomain setup** (for example, `aliasvault.example.com`): + +| Name | Type | Priority | Content | TTL | +|---------------------------|------|----------|-------------------------------|-----| +| mail.aliasvault | A | | `` | 3600 | +| aliasvault | MX | 10 | `mail.aliasvault.example.com` | 3600 | + +> πŸ”Ή Explanation: +> - `mail.aliasvault` creates a DNS A record for `mail.aliasvault.example.com` pointing to your server IP. +> - The MX record on `aliasvault.example` tells senders to send their mail addressed to `%@aliasvault.example.com` to `mail.aliasvault.example.com`. + +> Replace `` with your actual server’s IP address. + +##### Example + +- `mail.aliasvault.example.com` points to your server IP. +- Emails to `user@aliasvault.example.com` will be handled by your AliasVault server. + +This keeps the email configuration of your primary domain (`example.com`) completely separate, so you can keep receiving email on your normal email addresses and have unique AliasVault addresses too. + +--- + +### Configuring AliasVault +After setting up your DNS, you have to configure AliasVault to let it know which email domains it should support. Update the `docker-compose.yml` file: + +```bash +# ... + environment: + PRIVATE_EMAIL_DOMAINS: "yourdomain1.com,yourdomain2.com" +# ... +``` + +After updating the docker-compose.yml file, restart the Docker Compose stack: +```bash +# To apply new environment variables, containers must be recreated. +docker compose down +docker compose up -d +``` + +Afterwards, when you login to the AliasVault web app, you should now be able to create an alias with your configured private domain and be able to receive email on it. + +{: .note } +Important: DNS propagation can take up to 24-48 hours. During this time, email delivery might be inconsistent. + +If you encounter any issues, feel free to join the [Discord chat](https://discord.gg/DsaXMTEtpF) to get help from other users and maintainers. \ No newline at end of file diff --git a/docs/installation/docker-compose/troubleshooting.md b/docs/installation/docker-compose/troubleshooting.md new file mode 100644 index 000000000..9fe8de762 --- /dev/null +++ b/docs/installation/docker-compose/troubleshooting.md @@ -0,0 +1,82 @@ +--- +layout: default +title: Troubleshooting +parent: Docker Compose +nav_order: 3 +--- + +# Troubleshooting + +This guide covers common issues and troubleshooting steps for AliasVault encountered during installation, updates or general maintenance. + +{: .toc } +* TOC +{:toc} + +--- + +## Check Docker Container Status +For any issues you might encounter, the first step is to check the Docker containers health. This will give you a quick insight into the status of the individual containers which will help you identify the root cause of the issue. + +1. Check the Docker container running status: +```bash +$ docker compose ps +``` + +2. Check the Docker container logs +```bash +$ docker compose logs +``` + +3. Try restarting the Docker container +```bash +$ docker compose restart +``` + +--- + +## Check AliasVault Text Logs +All AliasVault services log information and errors to text files. These files are located in the `logs` directory. You can check the logs of a specific service by running the following command: + +```bash +$ cat logs/[service-name].txt +``` + +--- + +## Common Issues +Below are some common issues you might encounter and how to troubleshoot them. + +### 1. No emails being received +If you are not receiving emails on your aliases, check the following: +- Verify DNS records are correctly configured +- Ensure ports 25 and 587 are accessible +- Check your server's firewall settings +- Verify that your ISP/hosting provider allows SMTP traffic and does not block port 25 + +Refer to the [installation guide](./#3-email-server-setup) for more information on how to configure your DNS records and ports. + + +### 2. Forgot AliasVault Admin Password +If you have lost your admin password, you can reset it by running the `aliasvault reset-admin-password` option. This will generate a new random password and update the secret. After that it will restart the AliasVault containers to apply the changes. + +1. SSH into the aliasvault container: +```bash +$ docker compose exec -it aliasvault /bin/bash +``` +2. Run the reset-admin-password.sh script: +```bash +$ aliasvault reset-admin-password +``` +3. Remember the password outputted by the step above. Then quit out of the SSH session (ctrl+C) and then restart the container: +```bash +$ docker compose restart +``` +4. You can now login to the admin panel (/admin) with the new password. + +--- + +## Other Issues +If you encounter any other issues not mentioned here and need help, please join our Discord server or create an issue on the GitHub repository and we will be happy to help you out. + +Find all contact information on the contact page of our website: [https://www.aliasvault.net/contact](https://www.aliasvault.net/contact) \ No newline at end of file diff --git a/docs/installation/docker-compose/update/index.md b/docs/installation/docker-compose/update/index.md new file mode 100644 index 000000000..0b96a1e83 --- /dev/null +++ b/docs/installation/docker-compose/update/index.md @@ -0,0 +1,66 @@ +--- +layout: default +title: Update +parent: Docker Compose +nav_order: 1 +--- + +# Updating AliasVault +{: .no_toc } + +
    + + Table of contents + + {: .text-delta } +1. TOC +{:toc} +
    + +## Before You Begin +You can see the latest available version of AliasVault on [GitHub](https://github.com/aliasvault/aliasvault/releases). + +{: .warning } +Before updating, it's recommended to backup your database and other important data. You can do this by making +a copy of the `database` and `certificates` directories. + +## Standard Update Process +For most version updates, you can use the standard update process. The container will automatically handle database migrations on startup: + +1. Navigate to your AliasVault directory: +```bash +cd /path/to/your/aliasvault +``` + +2. Pull the latest Docker image: +```bash +docker compose pull +``` + +3. Restart the container with the new image: +```bash +docker compose down && docker compose up -d +``` + +## Version-Specific Upgrade Guides +While database migrations are automated, some releases may require manual file/config migration steps. Always check this page before updating to ensure you don't miss any required manual steps. + +> Currently there are no version-specific manual migration steps required for the single container setup. Check back here when updating to ensure you haven't missed any new requirements. + +## Additional Update Options + +### Installing a Specific Version +If you need to install a specific version instead of the latest, you can do the following. Note: downgrading to a previous version is not officially supported and may lead to unexpected issues, as database schema changes may prevent older versions from working correctly. + +1. Edit your `docker-compose.yml` file +2. Change the image tag from `:latest` to a specific version: +```yaml +# ... +image: ghcr.io/aliasvault/aliasvault:0.23.0 # Replace with desired version +# ... rest of configuration +``` +3. Pull and restart: +```bash +docker compose pull +docker compose down && docker compose up -d +``` diff --git a/docs/installation/index.md b/docs/installation/index.md index 26a5575f7..8fd9c1cd4 100644 --- a/docs/installation/index.md +++ b/docs/installation/index.md @@ -1,8 +1,121 @@ --- layout: default -title: Server Installation +title: Self-host Install nav_order: 2 --- -# Server Installation -The following guide will walk you through the steps to install AliasVault on your own server. Minimum experience with Docker and Linux is required. +# Self-host Install + +AliasVault can be self-hosted on your own servers using two different installation methods. Both use Docker, but they differ in how much is automated versus how much you manage yourself. + +## Which Installation Method to Choose? + +### πŸš€ **Option 1: Install Script** +The installer script is a **fully managed solution** that handles everything for you. Simply run it on a clean VM/LXC with Docker installed, and it will set up all required containers, configure SSL certificates and provide CLI helpers for easy updates and maintenance. + +### πŸ› οΈ **Option 2: Docker Compose** +If you prefer manual setup and **have existing SSL infrastructure**, use the all-in-one Docker image via Docker Compose. It works with your existing SSL proxy (Traefik, HAProxy, Caddy, etc.) and gives you full control over the configuration. Note: because this install method does not include a CLI, future updates may require some manual migrations. + +| | **Option 1: Install Script (multi-container)** | **Option 2: Docker Compose (single container)** | +|--------------------------|---------------------------------------------------|-----------------------------------------------| +| **Best for** | ☁️ VPS/VM/Proxmox, cloud hosts, DigitalOcean, AWS/Azure | 🏠 NAS/Synology/Unraid, Raspberry Pi, home servers | +| **Internet accessible** | Direct internet access with ports 80/443 | Behind existing infrastructure | +| **TLS/SSL** | Built-in reverse proxy + Let's Encrypt (automatic) | Bring your own (Traefik, Nginx, HAProxy, Caddy) | +| **Containers** | Multiple containers (client, api, postgres, task runner, smtp, admin, reverse proxy) | Single bundled container (all-in-one) | +| **Configuration** | Automatic docker-compose.yml setup | Standard Docker commands | +| **Updates** | `install.sh` assisted updates & migrations | `docker pull` (manual); occasional manual migrations | +| **Admin actions** | `install.sh` helpers for admin password reset | SSH into container for certain tasks (e.g. password reset) | +| **Setup style** | Managed, opinionated, production-ready defaults | Fits into existing homelab/stack tools (Portainer compatible) | +| **Build from source** | Supported | Pre-built container only | +| **Choose if…** | You want auto SSL and a managed stack | You already have TLS and prefer manual control | +| | [**Self-host via Install Script β†’**](./script){: .btn .btn-primary } | [**Self-host via Docker β†’**](./docker-compose){: .btn .btn-primary } | + +### Quick Decision Guide + +**Go with the Install Script if:** +- βœ… You have a fresh VM or VPS dedicated to AliasVault +- βœ… You want automatic SSL setup without hassle +- βœ… You prefer managed updates and maintenance +- βœ… You're new to Docker or want the simplest setup + +**Go with Docker Compose if:** +- βœ… You're already running other Docker containers on this host +- βœ… You have existing SSL infrastructure (reverse proxy) +- βœ… You want to integrate with your homelab tools (Portainer, etc.) +- βœ… You prefer manual control over the configuration + + +--- + +## Frequently Asked Questions + +
    +What's the difference between multi-container and single container? +
    + +| **Multi-container (Installer Script)** | **Single container (Manual Setup)** | +|----------------------------------------|-------------------------------------| +| Separates services into individual containers | All services bundled in one container | +| Easier to scale individual components | Simpler to manage with Docker commands | +| Uses docker-compose for orchestration | Lower resource overhead | +| Better for production deployments | Better for home labs and personal use | + +
    +
    + +
    +Do I need to handle SSL/TLS certificates myself? +
    + +- **Installer Script**: No, it includes automatic Let's Encrypt certificates +- **Manual Setup**: Yes, you need your own reverse proxy for HTTPS + +
    +
    + +
    +How do updates work? +
    + +| Method | Update Process | +|--------|---------------| +| **Installer Script** | Run `./install.sh update` for automated updates and migrations | +| **Manual Setup** | Use `docker pull` to get the latest image; manual migrations may be required | + +
    +
    + +
    +Can I migrate between installation methods? +
    + +Yes! Both methods use the same bind mount directories (`/database`, `/certificates`, `/logs`, `/secrets`), making migration straightforward. Simply stop/uninstall via one method and follow the installation steps for the other - your data will be preserved. + +
    +
    + +
    +What are the system requirements? +
    + +**Minimum requirements:** +- 64-bit Linux OS (Ubuntu or RHEL-based recommended) +- 1 vCPU, 1GB RAM, 16GB disk +- Docker CE (β‰₯ 20.10) and Docker Compose (β‰₯ 2.0) + +**Network requirements:** +- Ports 80 and 443 available +- Optional: Ports 25 and 587 for private email domains + +
    +
    + +
    +Can I build from source? +
    + +- **Installer Script**: Yes, optional build from source is supported +- **Manual Setup**: No, uses pre-built container images only + +
    +
    diff --git a/docs/installation/advanced/build-from-source.md b/docs/installation/script/advanced/build-from-source.md similarity index 87% rename from docs/installation/advanced/build-from-source.md rename to docs/installation/script/advanced/build-from-source.md index 4a832d7d3..10ec0111e 100644 --- a/docs/installation/advanced/build-from-source.md +++ b/docs/installation/script/advanced/build-from-source.md @@ -2,6 +2,7 @@ layout: default title: Build from Source parent: Advanced +grand_parent: Install Script nav_order: 3 --- @@ -18,13 +19,13 @@ Building from source requires more resources: ## Steps 1. Clone the repository ```bash -git clone https://github.com/aliasvault/aliasvault.git -cd aliasvault +$ git clone https://github.com/aliasvault/aliasvault.git +$ cd aliasvault ``` 2. Make the build script executable and run it. This will create the .env file, build the Docker images locally from source, and start the AliasVault containers. Follow the on-screen prompts to configure AliasVault. ```bash -chmod +x install.sh -./install.sh build +$ chmod +x install.sh +$ ./install.sh build ``` > **Note:** The complete build process can take a while depending on your hardware (5-15 minutes). diff --git a/docs/installation/script/advanced/database.md b/docs/installation/script/advanced/database.md new file mode 100644 index 000000000..a604b7951 --- /dev/null +++ b/docs/installation/script/advanced/database.md @@ -0,0 +1,25 @@ +--- +layout: default +title: Database Operations +parent: Advanced +grand_parent: Install Script +nav_order: 4 +--- + +# Database Operations +This page explains how to import/export on the AliasVault server database via the `./install.sh` script. + +## Database Export +In order to backup the AliasVault server database (which includes all encrypted user vaults as well), you can use the `install.sh` script. This script will stop all services, export the database to a file, and then restart the services. + +```bash +$ ./install.sh db-export > backup.sql.gz +``` + +## Database Import + +To restore a previously exported database, you can use the `install.sh` script. This script will stop all services, drop the database, import the database from a file, and then restart the services. + +```bash +$ ./install.sh db-import < backup.sql.gz +``` diff --git a/docs/installation/script/advanced/index.md b/docs/installation/script/advanced/index.md new file mode 100644 index 000000000..ba60d6d14 --- /dev/null +++ b/docs/installation/script/advanced/index.md @@ -0,0 +1,9 @@ +--- +layout: default +title: Advanced +parent: Install Script +nav_order: 4 +--- + +# Advanced Installation +The following guides provide more advanced installation options for AliasVault. These options are not required for the basic installation, but may be useful for advanced users. diff --git a/docs/installation/start-stop.md b/docs/installation/script/advanced/lifecycle.md similarity index 67% rename from docs/installation/start-stop.md rename to docs/installation/script/advanced/lifecycle.md index 2ffcbf8e2..d91fa07c4 100644 --- a/docs/installation/start-stop.md +++ b/docs/installation/script/advanced/lifecycle.md @@ -1,30 +1,31 @@ --- layout: default -title: Start/stop -parent: Server Installation +title: Stop/start +parent: Advanced +grand_parent: Install Script nav_order: 2 --- -# Starting and stopping AliasVault -You can start and stop AliasVault easily by using the install script. +# Stopping and starting AliasVault +You can stop and start AliasVault easily by using the install script. ## Stop To stop AliasVault, run the install script with the `stop` option. This will stop all running AliasVault containers. ```bash -./install.sh stop +$ ./install.sh stop ``` ## Start To start AliasVault, run the install script with the `start` option. This will start all AliasVault containers. ```bash -./install.sh start +$ ./install.sh start ``` ## Restart To restart AliasVault, run the install script with the `restart` option. This will restart all AliasVault containers. ```bash -./install.sh restart +$ ./install.sh restart ``` diff --git a/docs/installation/uninstall.md b/docs/installation/script/advanced/uninstall.md similarity index 79% rename from docs/installation/uninstall.md rename to docs/installation/script/advanced/uninstall.md index 4c3efbf4d..65dc8c6a6 100644 --- a/docs/installation/uninstall.md +++ b/docs/installation/script/advanced/uninstall.md @@ -1,13 +1,14 @@ --- layout: default title: Uninstall -parent: Server Installation +parent: Advanced +grand_parent: Install Script nav_order: 4 --- # Uninstall -To uninstall AliasVault, run the install script with the `uninstall` option. This will stop and remove the AliasVault containers, remove the Docker images, and delete the .env file. +To uninstall AliasVault, run the install script with the `uninstall` option. This will stop and remove the AliasVault containers and remove any local AliasVault Docker images. {: .note } This will not delete any data stored in the database. If you wish to delete all data, you should manually delete the `database` directory and the other directories created by AliasVault. @@ -15,5 +16,5 @@ This will not delete any data stored in the database. If you wish to delete all ### Steps 1. Run the install script with the `uninstall` option ```bash -./install.sh uninstall +$ ./install.sh uninstall ``` diff --git a/docs/installation/script/index-bak.md b/docs/installation/script/index-bak.md new file mode 100644 index 000000000..068e54638 --- /dev/null +++ b/docs/installation/script/index-bak.md @@ -0,0 +1,73 @@ +--- +layout: default +title: Installer Script (multi-container) +parent: Self-host Installs +nav_order: 1 +has_children: true +--- + +# Installer Script (multi-container) + +The installer script provides a managed, production-ready deployment of AliasVault using multiple Docker containers. This method includes automatic SSL certificates, built-in reverse proxy, and CLI-based management tools. + +{: .important } +> **Best for:** VPS, cloud hosting (AWS, Azure, DigitalOcean), dedicated servers with direct internet access + +1. **New Installation?** Start with the [Installation Guide](./installation) +2. **Upgrading?** Check the [Update Guide](./update/) +3. **Need Help?** Visit [Troubleshooting](./troubleshooting) or join our [Discord](https://discord.gg/DsaXMTEtpF) + +## πŸ“š Documentation + +
    + +
    +

    πŸš€ Getting Started

    +

    Initial installation and configuration

    + +
    + +
    +

    πŸ”„ Updates & Maintenance

    +

    Keep your instance up-to-date

    + +
    + +
    +

    ❓ Help & Support

    +

    Troubleshooting and assistance

    + +
    + +
    + +--- + +## Architecture Overview + +The installer script deploys AliasVault as a multi-container application: + +| Container | Purpose | +|-----------|---------| +| **reverse-proxy** | Nginx reverse proxy with SSL termination | +| **client** | Web interface (Blazor WebAssembly) | +| **api** | REST API backend | +| **admin** | Admin portal | +| **postgres** | PostgreSQL database | +| **smtp** | Email server for aliases | +| **task-runner** | Background jobs and maintenance | + +All containers are managed via `./install.sh` (which uses `docker compose` in the background) and configured through a centralized `.env` file. diff --git a/docs/installation/install.md b/docs/installation/script/index.md similarity index 75% rename from docs/installation/install.md rename to docs/installation/script/index.md index 91c92259f..daf6a6389 100644 --- a/docs/installation/install.md +++ b/docs/installation/script/index.md @@ -1,29 +1,27 @@ --- layout: default -title: Basic Install -parent: Server Installation +title: Install Script +parent: Self-host Install +redirect_from: + - /installation/install + - /installation/install.html nav_order: 1 --- -# Basic Install -The following guide will walk you through the steps to install AliasVault on your own server. Minimum experience with Docker and Linux is required. +# Self-host using Install Script (multi-container) +The following guide will walk you through the steps to install AliasVault on your own server using the AliasVault installer script: `install.sh`. This script will pull pre-built Docker Images and do all the configuration for you while using `docker compose` in the background. -{: .toc } -* TOC -{:toc} +{: .important-title } +> Requirements: +> - 64-bit Linux VM with root access (Ubuntu or RHEL-based recommended) +> - Minimum: 1 vCPU, 1GB RAM, 16GB disk +> - Docker (CE β‰₯ 20.10) and Docker Compose (β‰₯ 2.0) +> β†’ Installation guide: [Docker Docs](https://docs.docker.com/engine/install/) +> - Able to forward ports 80, 443 (with optional 25/587 for private email domains) --- ## 1. Basic Installation -To get AliasVault up and running quickly, run the install script to pull pre-built Docker images. The install script will also configure the .env file and start the AliasVault containers. You can get up and running in less than 5 minutes. - -### Hardware requirements -- 64-bit Linux VM with root access (Ubuntu or RHEL-based recommended) -- Minimum: 1 vCPU, 1GB RAM, 16GB disk -- Docker (CE β‰₯ 20.10) and Docker Compose (β‰₯ 2.0) - β†’ Installation guide: [Docker Docs](https://docs.docker.com/engine/install/) - -### Installation steps 1. Download the install script to a directory of your choice. All AliasVault files and directories will be created in this directory. ```bash # Download the install script @@ -45,14 +43,14 @@ chmod +x install.sh - Client: `https://localhost` - Admin: `https://localhost/admin` -> Note: if you do not wish to run the `install.sh` wizard but want to use Docker commands directly, follow the [manual setup guide](advanced/manual-setup.md). We do however encourage the use of `install.sh` as it will guide you through all configuration steps and allow you to easily update your AliasVault server later. +> Note: if you do not wish to run the `install.sh` wizard but prefer to use Docker commands directly, follow the [manual setup guide](../manual) instead. --- -## 2. SSL configuration -The default installation will create a self-signed SSL certificate and configure Nginx to use it. +## 2. TLS/SSL configuration +The default installation will create a self-signed TLS/SSL certificate and configure Nginx to use it. This is sufficient for local deployments using only the web-app, however the mobile apps (iOS and Android) require a valid (external) SSL certificate to be able to connect. -You can however also use Let's Encrypt to generate valid SSL certificates and configure Nginx to use it. In order to make this work you will need the following: +To generate a valid external TLS/SSL certificate for AliasVault, you can use Let's Encrypt via a built-in helper tool. In order to make this work you will need the following: - A public IPv4 address assigned to your server - Port 80 and 443 on your server must be open and accessible from the internet @@ -66,29 +64,28 @@ You can however also use Let's Encrypt to generate valid SSL certificates and co ``` 2. Follow the prompts to configure Let's Encrypt. -### Reverting to self-signed SSL -If at any point you would like to revert to the self-signed SSL certificate, run the install script again with the `configure-ssl` option +### Reverting to self-signed TLS/SSL +If at any point you would like to revert to the self-signed TLS/SSL certificate, run the install script again with the `configure-ssl` option and then in the prompt choose option 2. --- ## 3. Email Server Setup -AliasVault includes a built-in email server that allows you to generate email aliases on-the-fly for every website you use, and receive the and read the emails straight in AliasVault. +AliasVault includes a built-in email server that allows you to generate email aliases on-the-fly for every website you use, and receive + read the emails straight in AliasVault. -> **Note:** -> If you skip this step, AliasVault will default to use public email domains offered by SpamOK. While this still works for creating aliases, it has privacy limitations. For complete privacy and control, we recommend setting up your own domain. -> [Learn more about the differences between private and public email domains](../misc/private-vs-public-email.md). +{: .note } +If you skip this step, AliasVault will default to use public email domains offered by SpamOK. While this still works for creating aliases, it has privacy limitations. For complete privacy and control, we recommend setting up your own domain. [Learn more about the differences between private and public email domains](../misc/private-vs-public-email.md). --- ### Requirements -- A **public IPv4 address** with ports 25 and 587 pointing to your AliasVault server -- Open ports **25** and **587** on your server firewall for email SMTP traffic. +- A **public IPv4 address** with ports 25 and 587 forwarded to your AliasVault server +- Open ports **25** and **587** on your server firewall for email SMTP traffic (*NOTE: some residential IP's block this, check with your ISP*). #### Verifying Port Access -While the AliasVault docker containers are running, use `telnet` to confirm your public IP allows access to the ports: +While the AliasVault docker container is running, use `telnet` to confirm your public IP allows access to the ports: ```bash # Test standard SMTP port diff --git a/docs/installation/troubleshooting.md b/docs/installation/script/troubleshooting.md similarity index 80% rename from docs/installation/troubleshooting.md rename to docs/installation/script/troubleshooting.md index 569988658..b022b2d95 100644 --- a/docs/installation/troubleshooting.md +++ b/docs/installation/script/troubleshooting.md @@ -1,7 +1,10 @@ --- layout: default title: Troubleshooting -parent: Server Installation +parent: Install Script +redirect_from: + - /installation/troubleshooting + - /installation/troubleshooting.html nav_order: 5 --- @@ -42,10 +45,10 @@ docker compose restart [container-name-here] --- ## Check AliasVault Text Logs -All AliasVault services log information and errors to text files. These files are located in the `logs` directory. You can check the logs of a specific container by running the following command: +All AliasVault services log information and errors to text files. These files are located in the `logs` directory. You can check the logs of a specific service by running the following command: ```bash -cat logs/[container-name-here].log +cat logs/[service-name-here].txt ``` --- @@ -70,13 +73,13 @@ docker compose ps docker compose logs postgres ``` -### 2. SSL Certificate Issues +### 2. TLS/SSL Certificate Issues **Symptoms:** -- Browser shows SSL errors +- Browser shows TLS/SSL errors **Steps:** -1. Check the certbot container logs if SSL certificates are being correctly renewed: +1. Check the certbot container logs if TLS/SSL certificates are being correctly renewed: ```bash docker compose logs certbot ``` @@ -86,7 +89,7 @@ docker compose logs certbot docker compose logs reverse-proxy ``` -3. In case the SSL certificates are being correctly renewed, but the browser still shows SSL errors, try to restart AliasVault manually in order to force the NGINX container to reload the SSL certificates: +3. In case the SSL certificates are being correctly renewed, but the browser still shows TLS/SSL errors, try to restart AliasVault manually in order to force the NGINX container to reload the TLS/SSL certificates: ```bash ./install.sh restart ``` @@ -98,7 +101,7 @@ If you are not receiving emails on your aliases, check the following: - Check your server's firewall settings - Verify that your ISP/hosting provider allows SMTP traffic -Refer to the [installation guide](./install.md) for more information on how to configure your DNS records and ports. +Refer to the [installation guide](./#3-email-server-setup) for more information on how to configure your DNS records and ports. ### 4. Forgot AliasVault Admin Password @@ -113,4 +116,4 @@ If you have lost your admin password, you can reset it by running the install sc ## Other Issues If you encounter any other issues not mentioned here and need help, please join our Discord server or create an issue on the GitHub repository and we will be happy to help you out. -Find all contact information on the contact page of our website: [https://aliasvault.net/contact](https://aliasvault.net/contact) \ No newline at end of file +Find all contact information on the contact page of our website: [https://www.aliasvault.net/contact](https://www.aliasvault.net/contact) \ No newline at end of file diff --git a/docs/installation/update/index.md b/docs/installation/script/update/index.md similarity index 89% rename from docs/installation/update/index.md rename to docs/installation/script/update/index.md index 3d69a14f4..cdba4c42c 100644 --- a/docs/installation/update/index.md +++ b/docs/installation/script/update/index.md @@ -1,7 +1,10 @@ --- layout: default title: Update -parent: Server Installation +parent: Install Script +redirect_from: + - /installation/update + - /installation/update.html nav_order: 3 --- @@ -36,6 +39,7 @@ For most version updates, you can use the standard update process: ## Version-Specific Upgrade Guides Upgrading from certain earlier versions require additional steps during upgrade. If you are upgrading from an older version, please check the relevant articles below if it applies to your server: +- [Updating to 0.23.0](v0.23.0.html) - Update Docker Image locations due to new AliasVault GitHub organization - [Updating to 0.22.0](v0.22.0.html) - Move secrets from .env to file based secrets ## Additional Update Options @@ -56,5 +60,5 @@ To install a specific version and skip the automatic version checks, run the ins ./install.sh install # Example: -./install.sh install 0.7.0 +./install.sh install 0.22.0 ``` diff --git a/docs/installation/update/v0.22.0.md b/docs/installation/script/update/v0.22.0.md similarity index 96% rename from docs/installation/update/v0.22.0.md rename to docs/installation/script/update/v0.22.0.md index 1fbf405e8..1c97c17d7 100644 --- a/docs/installation/update/v0.22.0.md +++ b/docs/installation/script/update/v0.22.0.md @@ -2,8 +2,11 @@ layout: default title: Update to v0.22.0 parent: Update -grand_parent: Server Installation -nav_order: 1 +grand_parent: Install Script +redirect_from: + - /installation/update/v0.22.0 + - /installation/update/v0.22.0.html +nav_order: 2 --- # Updating to v0.22.0 diff --git a/docs/installation/script/update/v0.23.0.md b/docs/installation/script/update/v0.23.0.md new file mode 100644 index 000000000..5b606ca1e --- /dev/null +++ b/docs/installation/script/update/v0.23.0.md @@ -0,0 +1,107 @@ +--- +layout: default +title: Update to v0.23.0 +parent: Update +grand_parent: Install Script +redirect_from: + - /installation/update/v0.23.0 + - /installation/update/v0.23.0.html +nav_order: 1 +--- + +# Updating to v0.23.0 +{: .no_toc } + +Since v0.23.0, AliasVault has moved from `lanedirt/aliasvault` to the new `aliasvault/aliasvault` GitHub organization, and Docker image names have been simplified. + +## Update Methods + +### 1. Installed via install.sh + +If you have installed AliasVault using the official `install.sh` method, you don't need to do anything. Running the built-in `install.sh update` command will take care of all necessary changes for you. + +```bash +./install.sh update +``` + +### 2. Manually installed + +If you have manually installed AliasVault via a custom `docker-compose.yml` file or other Docker management interface such as Portainer, you need to update your Docker image references. + +#### Docker Image Name Changes + +Update all Docker image references in your `docker-compose.yml` file according to this mapping: + +| Old Image Name | New Image Name | +|----------------|----------------| +| ghcr.io/lanedirt/aliasvault-postgres | ghcr.io/aliasvault/postgres | +| ghcr.io/lanedirt/aliasvault-reverse-proxy | ghcr.io/aliasvault/reverse-proxy | +| ghcr.io/lanedirt/aliasvault-api | ghcr.io/aliasvault/api | +| ghcr.io/lanedirt/aliasvault-client | ghcr.io/aliasvault/client | +| ghcr.io/lanedirt/aliasvault-admin | ghcr.io/aliasvault/admin | +| ghcr.io/lanedirt/aliasvault-smtp | ghcr.io/aliasvault/smtp | +| ghcr.io/lanedirt/aliasvault-task-runner | ghcr.io/aliasvault/task-runner | + +#### Example docker-compose.yml Update + +**Before (v0.22.0 and earlier):** +```yaml +services: + postgres: + image: ghcr.io/lanedirt/aliasvault-postgres:0.22.0 + + reverse-proxy: + image: ghcr.io/lanedirt/aliasvault-reverse-proxy:0.22.0 + + api: + image: ghcr.io/lanedirt/aliasvault-api:0.22.0 + + client: + image: ghcr.io/lanedirt/aliasvault-client:0.22.0 + + admin: + image: ghcr.io/lanedirt/aliasvault-admin:0.22.0 + + smtp: + image: ghcr.io/lanedirt/aliasvault-smtp:0.22.0 + + task-runner: + image: ghcr.io/lanedirt/aliasvault-task-runner:0.22.0 +``` + +**After (v0.23.0 and later):** +```yaml +services: + postgres: + image: ghcr.io/aliasvault/postgres:0.23.0 + + reverse-proxy: + image: ghcr.io/aliasvault/reverse-proxy:0.23.0 + + api: + image: ghcr.io/aliasvault/api:0.23.0 + + client: + image: ghcr.io/aliasvault/client:0.23.0 + + admin: + image: ghcr.io/aliasvault/admin:0.23.0 + + smtp: + image: ghcr.io/aliasvault/smtp:0.23.0 + + task-runner: + image: ghcr.io/aliasvault/task-runner:0.23.0 +``` + +#### Update and restart + +After updating your `docker-compose.yml`, pull the latest images and restart your containers: + +```bash +docker-compose pull +docker-compose down +docker-compose up -d +``` + +If you encounter any issues during the upgrade, please join the AliasVault Discord, create an issue on GitHub or contact us by email. \ No newline at end of file diff --git a/docs/misc/dev/database-operations.md b/docs/misc/dev/database-operations.md index 2384e61ef..3cb2fb288 100644 --- a/docs/misc/dev/database-operations.md +++ b/docs/misc/dev/database-operations.md @@ -58,8 +58,7 @@ gunzip < aliasvault.sql.gz | docker compose exec -iT postgres psql -U aliasvault ``` ### Change master password -By default during initial installation the PostgreSQL master password is set to a random string that is -stored in the `.env` file with the `POSTGRES_PASSWORD` variable. +By default during initial installation the PostgreSQL master password is set to a random string that is stored in the `./secrets/postgres_password` secret file. If you wish to change the master password, you can do so by running the following command: @@ -74,7 +73,7 @@ If you wish to change the master password, you can do so by running the followin ``` 4. Press Enter to confirm the changes. 5. Exit the PostgreSQL shell by running `\q`. -6. Manually update the `.env` file variable `POSTGRES_PASSWORD` with the new password. +6. Manually update the `./secrets/postgres_password` secret file contents with the new password. 7. Restart the AliasVault containers by running the following command: ```bash docker compose restart diff --git a/docs/mobile-apps/android/ssl-setup.md b/docs/mobile-apps/android/ssl-setup.md new file mode 100644 index 000000000..d997776cf --- /dev/null +++ b/docs/mobile-apps/android/ssl-setup.md @@ -0,0 +1,65 @@ +--- +layout: page +title: Self-Signed SSL Setup +parent: Android App +grand_parent: Mobile Apps +nav_order: 3 +--- + +# Self-Signed SSL Certificate Setup for Android + +By default, the AliasVault Android app only supports connecting to servers with a valid SSL certificate from a trusted external authority. If you want to use your own self-signed certificate, you must manually install and trust the certificate on your Android device by following these steps. + +## Server Setup + +### Standard Installation +Configure your hostname and restart AliasVault: +```bash +./install.sh configure-hostname +./install.sh restart +``` + +### All-in-One Docker +Update your `docker-compose.yml`: +```yaml +environment: + HOSTNAME: "192.168.3.2" # Your server IP/hostname +``` +Then restart: `docker compose down && docker compose up -d` + +## Step 1: Get the Certificate + +### Option A: From Browser +1. Open Chrome, go to your AliasVault instance (e.g., `https://192.168.3.2`) +2. Click the padlock icon β†’ inspect certificate +3. Export the certificate and send it to your phone + +### Option B: From Server +Copy from your AliasVault installation directory: +```bash +cp [aliasvault-install-dir]/certificates/ssl/cert.pem ~/aliasvault.crt +``` +Transfer to your Android device. + +## Step 2: Install Certificate (Android 10+) + +1. **Open Settings** β†’ search for "Certificate" +2. **Tap "Install a certificate"** β†’ **"CA certificate"** +3. **Browse to Downloads** β†’ select your certificate file +4. **Enter your PIN/password** when prompted +5. **Name it "AliasVault"** and tap **OK** + +## Step 3: Configure AliasVault App + +1. **Open the AliasVault app** +2. **Go to Settings** β†’ **Server Configuration** +3. **Enter your server URL**: `https://192.168.3.2/api` (use your configured hostname) +4. **Test connection** - should work without SSL errors + +## Troubleshooting + +**Certificate not trusted**: Verify it's installed under Settings β†’ Security β†’ Trusted credentials β†’ User tab + +**App can't connect**: Ensure the hostname in the app matches your server configuration exactly + +**Installation fails**: Make sure you have a screen lock set up on your device \ No newline at end of file diff --git a/docs/mobile-apps/ios/ssl-setup.md b/docs/mobile-apps/ios/ssl-setup.md new file mode 100644 index 000000000..b0e667efa --- /dev/null +++ b/docs/mobile-apps/ios/ssl-setup.md @@ -0,0 +1,71 @@ +--- +layout: page +title: Self-Signed SSL Setup +parent: iOS App +grand_parent: Mobile Apps +nav_order: 3 +--- + +# Self-Signed SSL Certificate Setup for iOS + +By default, the AliasVault iOS app only supports connecting to servers with a valid SSL certificate from a trusted external authority. If you want to use your own self-signed certificate, you must manually install and trust the certificate on your iOS device by following these steps. + +## Server Setup + +### Standard Installation +Configure your hostname and restart AliasVault: +```bash +./install.sh configure-hostname +./install.sh restart +``` + +### All-in-One Docker +Update your `docker-compose.yml`: +```yaml +environment: + HOSTNAME: "192.168.3.2" # Your server IP/hostname +``` +Then restart: `docker compose down && docker compose up -d` + +## Step 1: Get the Certificate + +### Option A: From Browser +1. Open Chrome on your computer, go to your AliasVault instance (e.g., `https://192.168.3.2`) +2. Click the padlock icon β†’ inspect certificate +3. Export the certificate and send it to your device (e.g. via email) + +### Option B: From Server +Copy from your AliasVault installation directory: +```bash +cp [aliasvault-install-dir]/certificates/ssl/cert.pem ~/aliasvault.crt +``` + +## Step 2: Install Certificate Profile + +1. Open the certificate on your iOS device +1. **Tap "Install"** in the top right corner +2. **Enter your passcode** when prompted +3. **Tap "Install"** again to confirm the warning +4. **Tap "Done"** when complete + +## Step 3: Enable Certificate Trust (Critical!) + +1. **Settings** β†’ **General** β†’ **About** β†’ **Certificate Trust Settings** +2. **Find your certificate** (listed by hostname like "192.168.3.2") +3. **Toggle the switch to ON** +4. **Tap "Continue"** in the warning + +## Step 4: Configure AliasVault App + +1. **Open the AliasVault app** +2. **Go to Settings** β†’ **Server Configuration** +3. **Enter your server URL**: `https://192.168.3.2/api` (use your configured hostname) +4. **Test connection** - should work without SSL errors + +## Troubleshooting + +**SSL errors**: Ensure you completed Step 3 (Certificate Trust) - this is the most commonly missed step + +**Certificate Trust Settings not visible**: You must install a certificate profile first + +**App can't connect**: Verify the hostname in the app matches your server configuration exactly \ No newline at end of file diff --git a/install.sh b/install.sh index 6cde3c9fa..45ae63596 100755 --- a/install.sh +++ b/install.sh @@ -871,9 +871,9 @@ main() { if [ $? -eq 0 ]; then printf "${CYAN}> Restarting admin container...${NC}\n" if [ "$VERBOSE" = true ]; then - $(get_docker_compose_command) up -d --force-recreate admin + eval "$(get_docker_compose_command) up -d --force-recreate admin" else - $(get_docker_compose_command) up -d --force-recreate admin > /dev/null 2>&1 + eval "$(get_docker_compose_command) up -d --force-recreate admin" > /dev/null 2>&1 fi print_password_reset_message fi @@ -1142,7 +1142,7 @@ create_env_file() { populate_hostname() { if ! grep -q "^HOSTNAME=" "$ENV_FILE" || [ -z "$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)" ]; then while true; do - read -p "Enter the (public) hostname where this AliasVault server can be accessed from (e.g. aliasvault.net): " USER_HOSTNAME + read -p "Enter the hostname where this AliasVault server can be accessed (e.g. aliasvault.example.com): " USER_HOSTNAME if [ -n "$USER_HOSTNAME" ]; then HOSTNAME="$USER_HOSTNAME" break @@ -1583,13 +1583,13 @@ recreate_docker_containers() { if [ "$VERBOSE" = true ]; then printf "${CYAN}β„Ή (Re)creating Docker containers...${NC}\n" printf "\b${NC}\n" - if ! $(get_docker_compose_command) up -d --force-recreate; then + if ! eval "$(get_docker_compose_command) up -d --force-recreate"; then log_error "Failed to recreate Docker containers" exit 1 fi else ( - $(get_docker_compose_command) up -d --force-recreate > /tmp/docker_recreate.log 2>&1 & + eval "$(get_docker_compose_command) up -d --force-recreate" > /tmp/docker_recreate.log 2>&1 & RECREATE_PID=$! show_spinner $RECREATE_PID "Recreating Docker containers " wait $RECREATE_PID @@ -1793,13 +1793,13 @@ handle_build() { if [ "$VERBOSE" = true ]; then printf "${CYAN}β„Ή Building Docker Compose stack...${NC}\n" printf "\b${NC}\n" - if ! $(get_docker_compose_command) build; then + if ! eval "$(get_docker_compose_command) build"; then log_error "Failed to build Docker Compose stack" exit 1 fi else ( - $(get_docker_compose_command) build > install_compose_build_output.log 2>&1 & + eval "$(get_docker_compose_command) build" > install_compose_build_output.log 2>&1 & BUILD_PID=$! show_spinner $BUILD_PID "Building Docker Compose stack " wait $BUILD_PID @@ -1936,7 +1936,7 @@ handle_ssl_configuration() { printf "Hostname: ${CYAN}${CURRENT_HOSTNAME}${NC} (change via: ./install.sh configure-hostname)\n" printf "\n" printf "Choose an option:\n" - printf "1) Use Let's Encrypt certificate (recommended)\n" + printf "1) Use Let's Encrypt certificate (recommended for public domains)\n" printf "2) Use self-signed certificate\n" printf "3) Cancel\n" printf "\n" @@ -2192,11 +2192,11 @@ configure_letsencrypt() { # Restart only the reverse proxy with new configuration so it loads the new certificate printf "${CYAN}> Restarting reverse proxy with Let's Encrypt configuration...${NC}\n" - $(get_docker_compose_command) up -d reverse-proxy --force-recreate + eval "$(get_docker_compose_command) up -d reverse-proxy --force-recreate" # Starting certbot container to renew certificates automatically printf "${CYAN}> Starting new certbot container to renew certificates automatically...${NC}\n" - $(get_docker_compose_command) up -d certbot + eval "$(get_docker_compose_command) up -d certbot" # Print success message printf "\n" @@ -2210,17 +2210,27 @@ generate_self_signed_cert() { # Disable Let's Encrypt update_env_var "LETSENCRYPT_ENABLED" "false" + # Get current hostname from .env + HOSTNAME_VALUE=$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2) + + if [ -n "$HOSTNAME_VALUE" ] && [ "$HOSTNAME_VALUE" != "localhost" ]; then + printf "${CYAN}> Using configured hostname: ${HOSTNAME_VALUE}${NC}\n" + printf "${CYAN}> The certificate will include:${NC}\n" + printf " ${GREEN}Primary CN:${NC} ${HOSTNAME_VALUE}\n" + printf " ${GREEN}Alternative Names:${NC} localhost, 127.0.0.1\n\n" + fi + # Stop existing containers printf "${CYAN}> Stopping existing containers...${NC}\n" docker compose down - # Remove existing certificates - rm -f ./certificates/ssl/cert.pem ./certificates/ssl/key.pem + # Remove existing certificates and hostname marker + rm -f ./certificates/ssl/cert.pem ./certificates/ssl/key.pem ./certificates/ssl/.hostname_marker # Remove Let's Encrypt directories rm -rf ./certificates/letsencrypt - # Start containers (which will generate new self-signed certs) + # Start containers (which will generate new self-signed cert with hostname) printf "${CYAN}> Restarting services...${NC}\n" docker compose up -d @@ -2232,7 +2242,7 @@ generate_self_signed_cert() { # New functions to handle container lifecycle: handle_start() { printf "${CYAN}> Starting AliasVault containers...${NC}\n" - $(get_docker_compose_command) up -d + eval "$(get_docker_compose_command) up -d" printf "${GREEN}> AliasVault containers started successfully.${NC}\n" } @@ -2243,14 +2253,14 @@ handle_stop() { exit 0 fi - $(get_docker_compose_command) down + eval "$(get_docker_compose_command) down" printf "${GREEN}> AliasVault containers stopped successfully.${NC}\n" } handle_restart() { printf "\n${CYAN}> Restarting AliasVault containers...${NC}\n" - $(get_docker_compose_command) down - $(get_docker_compose_command) up -d + eval "$(get_docker_compose_command) down" + eval "$(get_docker_compose_command) up -d" printf "${GREEN}> AliasVault containers restarted successfully.${NC}\n" } @@ -2727,9 +2737,10 @@ handle_db_export() { printf "Options:\n" >&2 printf " --dev Export from development database\n" >&2 printf "\n" >&2 - printf "Example:\n" >&2 + printf "Examples:\n" >&2 printf " ./install.sh db-export > my_backup_$(date +%Y%m%d).sql.gz\n" >&2 printf " ./install.sh db-export --dev > my_dev_backup_$(date +%Y%m%d).sql.gz\n" >&2 + printf "\n" >&2 exit 1 fi @@ -2791,7 +2802,15 @@ handle_db_import() { # Check if we're getting input from a pipe if [ -t 0 ]; then - printf "Usage: ./install.sh db-import [--dev] < backup.sql.gz\n" + printf "Usage: ./install.sh db-import [--dev] < backup_file\n" + printf "\n" + printf "Options:\n" + printf " --dev Import to development database\n" + printf "\n" + printf "Examples:\n" + printf " ./install.sh db-import < backup.sql.gz # Import gzipped backup\n" + printf " ./install.sh db-import < backup.sql # Import plain SQL backup\n" + printf " ./install.sh db-import --dev < backup.sql # Import to dev database\n" exit 1 fi @@ -2840,15 +2859,25 @@ handle_db_import() { fi printf "database...${NC}\n" - # Create a temporary file to verify the gzip input + # Create a temporary file to store the input temp_file=$(mktemp) cat <&3 > "$temp_file" # Read from fd 3 instead of stdin exec 3<&- # Close fd 3 - if ! gzip -t "$temp_file" 2>/dev/null; then - printf "${RED}Error: Input is not a valid gzip file${NC}\n" - rm "$temp_file" - exit 1 + # Detect if the file is gzipped or plain SQL + is_gzipped=false + if gzip -t "$temp_file" 2>/dev/null; then + is_gzipped=true + printf "${CYAN}> Detected gzipped SQL backup${NC}\n" + else + # Check if it looks like SQL (basic validation) + if head -n 10 "$temp_file" | grep -qE '(^--|^CREATE |^INSERT |^ALTER |^DROP |^\\\\connect|^SET |^COMMENT |^GRANT |^REVOKE )'; then + printf "${CYAN}> Detected plain SQL backup${NC}\n" + else + printf "${RED}Error: Input is neither a valid gzip file nor a SQL file${NC}\n" + rm "$temp_file" + exit 1 + fi fi if [ "$DEV_DB" = true ]; then @@ -2856,24 +2885,40 @@ handle_db_import() { docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'aliasvault' AND pid <> pg_backend_pid();" && \ docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "DROP DATABASE IF EXISTS aliasvault;" && \ docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "CREATE DATABASE aliasvault OWNER aliasvault;" && \ - gunzip -c "$temp_file" | docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault aliasvault + if [ "$is_gzipped" = true ]; then + gunzip -c "$temp_file" | docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault aliasvault + else + cat "$temp_file" | docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault aliasvault + fi else docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'aliasvault' AND pid <> pg_backend_pid();" > /dev/null 2>&1 && \ docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "DROP DATABASE IF EXISTS aliasvault;" > /dev/null 2>&1 && \ docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "CREATE DATABASE aliasvault OWNER aliasvault;" > /dev/null 2>&1 && \ - gunzip -c "$temp_file" | docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault aliasvault > /dev/null 2>&1 + if [ "$is_gzipped" = true ]; then + gunzip -c "$temp_file" | docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault aliasvault > /dev/null 2>&1 + else + cat "$temp_file" | docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault aliasvault > /dev/null 2>&1 + fi fi else if [ "$VERBOSE" = true ]; then docker compose exec -T postgres psql -U aliasvault postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'aliasvault' AND pid <> pg_backend_pid();" && \ docker compose exec -T postgres psql -U aliasvault postgres -c "DROP DATABASE IF EXISTS aliasvault;" && \ docker compose exec -T postgres psql -U aliasvault postgres -c "CREATE DATABASE aliasvault OWNER aliasvault;" && \ - gunzip -c "$temp_file" | docker compose exec -T postgres psql -U aliasvault aliasvault + if [ "$is_gzipped" = true ]; then + gunzip -c "$temp_file" | docker compose exec -T postgres psql -U aliasvault aliasvault + else + cat "$temp_file" | docker compose exec -T postgres psql -U aliasvault aliasvault + fi else docker compose exec -T postgres psql -U aliasvault postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'aliasvault' AND pid <> pg_backend_pid();" > /dev/null 2>&1 && \ docker compose exec -T postgres psql -U aliasvault postgres -c "DROP DATABASE IF EXISTS aliasvault;" > /dev/null 2>&1 && \ docker compose exec -T postgres psql -U aliasvault postgres -c "CREATE DATABASE aliasvault OWNER aliasvault;" > /dev/null 2>&1 && \ - gunzip -c "$temp_file" | docker compose exec -T postgres psql -U aliasvault aliasvault > /dev/null 2>&1 + if [ "$is_gzipped" = true ]; then + gunzip -c "$temp_file" | docker compose exec -T postgres psql -U aliasvault aliasvault > /dev/null 2>&1 + else + cat "$temp_file" | docker compose exec -T postgres psql -U aliasvault aliasvault > /dev/null 2>&1 + fi fi fi @@ -2908,8 +2953,7 @@ handle_hostname_configuration() { fi printf "The hostname is the domain name where your AliasVault server will be accessible.\n" - printf "A valid hostname is required for Let's Encrypt SSL certificate generation.\n" - printf "The hostname must be a real domain that points to this server (not localhost).\n" + printf "This hostname will be used for both Let's Encrypt and self-signed certificates.\n" printf "\n" # Get current hostname @@ -2919,7 +2963,7 @@ handle_hostname_configuration() { # Ask for new hostname while true; do - read -p "Enter new hostname (e.g. aliasvault.net): " NEW_HOSTNAME + read -p "Enter new hostname (e.g. aliasvault.example.com): " NEW_HOSTNAME if [ -n "$NEW_HOSTNAME" ]; then break else @@ -2927,9 +2971,36 @@ handle_hostname_configuration() { fi done + # Check if hostname changed + HOSTNAME_CHANGED=false + if [ "$CURRENT_HOSTNAME" != "$NEW_HOSTNAME" ]; then + HOSTNAME_CHANGED=true + fi + # Update the hostname update_env_var "HOSTNAME" "$NEW_HOSTNAME" + # If using self-signed cert and hostname changed, offer to regenerate + if [ "$HOSTNAME_CHANGED" = true ]; then + LETSENCRYPT_ENABLED=$(grep "^LETSENCRYPT_ENABLED=" "$ENV_FILE" | cut -d '=' -f2) + if [ "$LETSENCRYPT_ENABLED" != "true" ]; then + printf "\n${YELLOW}Hostname changed. The self-signed certificate needs to be regenerated.${NC}\n" + read -p "Regenerate certificate now? (y/n): " REGEN_CERT + if [ "$REGEN_CERT" = "y" ] || [ "$REGEN_CERT" = "Y" ]; then + # Remove the hostname marker to force regeneration + rm -f ./certificates/ssl/.hostname_marker + + printf "\n${YELLOW}Restarting services to regenerate certificate...${NC}\n" + handle_restart + else + printf "${YELLOW}Please restart services manually to apply the new certificate.${NC}\n" + fi + else + printf "\n${YELLOW}Note: You're using Let's Encrypt. Make sure the new hostname has proper DNS records.${NC}\n" + printf "${YELLOW}You may need to reconfigure Let's Encrypt for the new hostname.${NC}\n" + fi + fi + printf "\n" print_success_box "Hostname updated successfully to ${NEW_HOSTNAME}!" }