diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af78178..3c1fd4d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,6 +48,8 @@ jobs: run: tests/test-bootlog.sh - name: Power schedule (shareos-power) run: LD_LIBRARY_PATH="$HOME/hostroot/lib" tests/test-power.sh "$HOME/hostroot/bin" + - name: Dynamic DNS (shareos-ddns, all providers with a fake curl) + run: LD_LIBRARY_PATH="$HOME/hostroot/lib" tests/test-ddns.sh "$HOME/hostroot/bin" - name: Zabbix agent controller (settings, disk/RAID items) run: LD_LIBRARY_PATH="$HOME/hostroot/lib" tests/test-zabbix-items.sh "$HOME/hostroot/bin" - name: SMART evaluation (disk health) @@ -57,6 +59,7 @@ jobs: tests/check-ucode-order.py src/shareos-core/usr/share/rpcd/ucode/shareos.uc tests/check-ucode-order.py src/shareos-core/usr/sbin/shareos-apps tests/check-ucode-order.py src/shareos-core/usr/sbin/shareos-notify + tests/check-ucode-order.py src/shareos-core/usr/sbin/shareos-ddns tests/check-ucode-regex.py tests/check-acl.py tests/check-i18n.py @@ -81,7 +84,7 @@ jobs: sudo apt-get update sudo apt-get install -y --no-install-recommends \ build-essential bc bison flex cpio rsync unzip file wget \ - libssl-dev libncurses-dev python3 + libssl-dev libncurses-dev python3 qemu-user-static - name: Cache downloads uses: actions/cache@v4 @@ -96,6 +99,15 @@ jobs: - name: Test writing .bin files (rollback to Alt-F) run: tests/test-flash-bin.sh output/${{ matrix.board }}/host/bin + - name: Own certificate authority (shareos-ca with the firmware's mbedtls and uci in qemu) + run: | + T=$PWD/output/${{ matrix.board }}/target W=$(mktemp -d) + for p in gen_key cert_write cert_app uci; do + f=$T/usr/bin/$p; [ -e "$f" ] || f=$T/sbin/$p + printf '#!/bin/sh\nexec qemu-arm-static -L %s %s "$@"\n' "$T" "$f" > $W/$p; chmod 755 $W/$p + done + tests/test-ca.sh $W $W + - name: Initramfs load address of every model run: tests/check-initrd-address.sh output/${{ matrix.board }}/images diff --git a/CHANGELOG.md b/CHANGELOG.md index faf65fc..66a48d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ Changes per release (newest first). The section of a version is added to its GitHub release automatically when the release is built. Test versions are pre-releases for test devices only. +## 0.2.0-beta.18 + +- **DynDNS, own certificate authority and Let's Encrypt** (System → Netzwerk). + - *DynDNS*: DuckDNS, dynv6, Cloudflare, No-IP or any dyndns2 address (Strato, IONOS, …), IPv4 and/or IPv6 (for lines without their own IPv4 address: DS-Lite, fibre, mobile). The box reports its public address every 5 minutes when it changed, otherwise once a day; a failure becomes a message. Token and password go to curl through stdin, not the command line, and are never sent back to the browser. `shareos-ddns`, `tests/test-ddns.sh` (24 checks with a fake curl). + - *Own CA*: a certificate authority on the box (EC P-256, 10 years) issues certificates for the box and other devices in the home network (825 days, server authentication, wildcards like `*.home.lan`, IP addresses); its root certificate is installed once on PCs and phones, after that there are no certificate warnings. The box's own certificate follows name and IP address and is renewed 30 days before it runs out. The CA is part of the settings backup. `shareos-ca` (mbedtls of the firmware), `tests/test-ca.sh` (18 checks, in CI with the firmware's programs in qemu). + - *Let's Encrypt* for the DynDNS name with DuckDNS or Cloudflare: the check runs over DNS (TXT record at the provider), no port has to be open; renewed daily when less than 30 days are left. `shareos-acme` with uacme (105 KB, mbedtls), test mode with the staging CA. + - `shareos-cert` no longer replaces a certificate of the own CA with a self-signed one when the IP address changes (it only renews its own self-signed one). + ## 0.2.0-beta.17 - **SMB 3 with signing about three times as fast: the crypto engine computes the signatures** ([#94](https://github.com/UniqueDroid/ShareOS/issues/94)). Windows 11 signs every SMB connection, and on the 800 MHz Kirkwood the signature limited writing: measured on a DNS-320L, about 35 MB/s without signing (the disks), only 4–10 MB/s with SMB 3.1.1 – ksmbd computes AES-CMAC on the CPU, where AES manages about 12 MB/s. The Kirkwood has a crypto engine (Marvell CESA) that does AES-CBC at about 38 MB/s with DMA, but ksmbd could not use it. A kernel patch (`0001-ksmbd-cmac-via-cbc`) computes the AES-CMAC of messages from 8 KiB through `cbc(aes)` – the engine – (CMAC is a CBC-MAC; the last block and the subkeys on the CPU), checks the first result against the old way and falls back to it on any error. Measured: **19–23 MB/s** signed, the CPU load of ksmbd went from about 64 % to under 10 %. `shareos-shares` loads `marvell-cesa` before ksmbd; module parameter `ksmbd.cbc_cmac=0` switches it off. Tested before on a UML kernel with Samba's smbclient (signing required, checksums equal). Tried as well and dropped: AES-GMAC signing (GHASH in C on ARMv5 is not faster, 3–8 MB/s). diff --git a/CLAUDE.md b/CLAUDE.md index 1fa6455..7944281 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ - Tags/releases are created by Jan; prepare the release text and the `CHANGELOG.md` section. - **Every release updates the docs** – see `docs/RELEASE.md` (CHANGELOG, README status/features, TESTEN/TESTING, ENTWICKLUNG/DEVELOPMENT). - Photos for the docs: strip EXIF/GPS and blur serial-number stickers before committing. -- Tests: `tests/` (host: test-update, test-net, test-flash-bin, test-boards, test-raid-wait, test-smart, test-bootlog, test-power, test-notify, test-zabbix-items, check-acl.py, check-i18n.py; ARM emulator with sudo: run-stack-qemu --check, test-users, test-storage, test-cloud-paperless, test-apps, test-firstinstall, test-transmission, test-media, test-dlna, test-vpn, test-tailscale, test-autobrr, test-zabbix – these seven need `make sysapps-pack`, test-snapshots, test-sysperms). UI work: `tests/mock-ubus.py`; app SDK in Chromium: `node tests/sdk-storage.js` (Playwright). ucode plugin: `tests/check-ucode-order.py` (no hoisting in ucode). +- Tests: `tests/` (host: test-update, test-net, test-flash-bin, test-boards, test-raid-wait, test-smart, test-bootlog, test-power, test-notify, test-zabbix-items, test-ddns, test-ca (mbedtls/uci of the firmware through qemu-arm-static, see build.yml), check-acl.py, check-i18n.py; ARM emulator with sudo: run-stack-qemu --check, test-users, test-storage, test-cloud-paperless, test-apps, test-firstinstall, test-transmission, test-media, test-dlna, test-vpn, test-tailscale, test-autobrr, test-zabbix – these seven need `make sysapps-pack`, test-snapshots, test-sysperms). UI work: `tests/mock-ubus.py`; app SDK in Chromium: `node tests/sdk-storage.js` (Playwright). ucode plugin: `tests/check-ucode-order.py` (no hoisting in ucode). - Never `rm -rf` a chroot that still has mounts below it (it would delete the host's /dev). - Apps live in the separate repository UniqueDroid/ShareOS-Apps. - New UI texts: add them to `i18n/en.json` and to every `tools/i18n/.tsv` (new numbers at the end), then `tools/i18n/build-lang.py`; `tests/check-i18n.py` must pass. diff --git a/README.md b/README.md index 4638001..c8a33c0 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,8 @@ Version in brackets = first release with the feature. - First installation from the Alt-F or D-Link web interface: the `.bin` fetches the system from a USB stick or from GitHub (beta.7; tested on real hardware, step by step: [SWITCHING.md](docs/SWITCHING.md) · [UMSTIEG.md](docs/UMSTIEG.md)) - Back to Alt-F (or any D-Link `.bin`) from the firmware page, checked and read back (beta.7) - First-run assistant, network settings with automatic rollback, name via mDNS (`.local`), fallback address `192.168.0.32` without DHCP -- HTTPS with a self-signed certificate that follows name and IP, own certificate possible +- HTTPS with a self-signed certificate that follows name and IP, own certificate possible; own certificate authority for the home network and Let's Encrypt over DNS (0.2.0-beta.18) +- Dynamic DNS: DuckDNS, dynv6, Cloudflare, No-IP or any dyndns2 address, IPv4 and IPv6 (0.2.0-beta.18) - Power schedule: switch off and on per weekday (switching on through the wake-up alarm of the DNS-320L/320 rev B/327L; 0.2.0-beta.15; switching off works from 0.2.0-beta.17, the overview shows the date of the next switch-on) - Start log: the logs of the last 3 starts are kept on the flash (System → Protokolle → *Start-Protokoll*), so a box that was unreachable after a start can be looked into afterwards (0.2.0-beta.15) - Reset button: access reset (5–10 s: network, root password, certificate) or factory reset (≥ 15 s), USB button removes USB drives (beta.9) or copies them into a dated folder (0.2.0-beta.12); the buttons work again on the DNS-320L and DNS-325 (0.2.0-beta.10) diff --git a/configs/shareos_dns320l_defconfig b/configs/shareos_dns320l_defconfig index 3c9df35..59831bc 100644 --- a/configs/shareos_dns320l_defconfig +++ b/configs/shareos_dns320l_defconfig @@ -73,6 +73,7 @@ BR2_PACKAGE_LIBCURL=y BR2_PACKAGE_LIBCURL_CURL=y BR2_PACKAGE_LIBCURL_MBEDTLS=y BR2_PACKAGE_CA_CERTIFICATES=y +BR2_PACKAGE_UACME=y # --- Images --- # Full system as squashfs (goes into the UBI volume "rootfs"); the small diff --git a/package/shareos-core/shareos-core.mk b/package/shareos-core/shareos-core.mk index b02e40a..d7a9ca0 100644 --- a/package/shareos-core/shareos-core.mk +++ b/package/shareos-core/shareos-core.mk @@ -89,6 +89,10 @@ define SHAREOS_CORE_INSTALL_TARGET_CMDS $(INSTALL) -D -m 0755 $(@D)/etc/init.d/rcS $(TARGET_DIR)/etc/init.d/rcS $(INSTALL) -D -m 0755 $(@D)/usr/sbin/shareos-bootlog $(TARGET_DIR)/usr/sbin/shareos-bootlog $(INSTALL) -D -m 0755 $(@D)/usr/sbin/shareos-power $(TARGET_DIR)/usr/sbin/shareos-power + $(INSTALL) -D -m 0755 $(@D)/usr/sbin/shareos-ddns $(TARGET_DIR)/usr/sbin/shareos-ddns + $(INSTALL) -D -m 0755 $(@D)/usr/sbin/shareos-ca $(TARGET_DIR)/usr/sbin/shareos-ca + $(INSTALL) -D -m 0755 $(@D)/usr/sbin/shareos-acme $(TARGET_DIR)/usr/sbin/shareos-acme + $(INSTALL) -D -m 0755 $(@D)/usr/libexec/shareos-acme-hook $(TARGET_DIR)/usr/libexec/shareos-acme-hook $(INSTALL) -D -m 0755 $(@D)/usr/sbin/shareos-users $(TARGET_DIR)/usr/sbin/shareos-users $(INSTALL) -D -m 0755 $(@D)/usr/sbin/shareos-shares $(TARGET_DIR)/usr/sbin/shareos-shares $(INSTALL) -D -m 0755 $(@D)/usr/sbin/shareos-quota $(TARGET_DIR)/usr/sbin/shareos-quota diff --git a/src/shareos-core/usr/libexec/shareos-acme-hook b/src/shareos-core/usr/libexec/shareos-acme-hook new file mode 100755 index 0000000..2f86f58 --- /dev/null +++ b/src/shareos-core/usr/libexec/shareos-acme-hook @@ -0,0 +1,29 @@ +#!/bin/sh +# +# uacme hook of shareos-acme: answers only the DNS check (dns-01) through the +# TXT record at the DynDNS provider (shareos-ddns). +# +# shareos-acme-hook begin|done|failed +# +# uacme hands over the TXT value for dns-01 as key_auth. Exit 1 declines a +# check type (http-01, tls-alpn-01: no port of the box is open to the internet). +# +PATH=/usr/sbin:/usr/bin:/sbin:/bin +method=$1 type=$2 ident=$3 auth=$5 +[ "$type" = dns-01 ] || exit 1 +name="_acme-challenge.${ident#\*.}" +case "$method" in + begin) + /usr/sbin/shareos-ddns txt-set "$name" "$auth" >/dev/null || exit 1 + # wait until a public resolver sees the value (at most about 2 minutes) + for i in 1 2 3 4 5 6 7 8 9 10 11 12; do + sleep 10 + nslookup -type=txt "$name" 1.1.1.1 2>/dev/null | grep -qF -- "$auth" && break + done + sleep 5 + exit 0 ;; + done|failed) + /usr/sbin/shareos-ddns txt-clear "$name" "$auth" >/dev/null + exit 0 ;; +esac +exit 1 diff --git a/src/shareos-core/usr/sbin/shareos-acme b/src/shareos-core/usr/sbin/shareos-acme new file mode 100755 index 0000000..bcafdc5 --- /dev/null +++ b/src/shareos-core/usr/sbin/shareos-acme @@ -0,0 +1,132 @@ +#!/bin/sh +# +# shareos-acme – certificate from Let's Encrypt for the DynDNS name +# +# shareos-acme issue [staging] get a certificate for the DynDNS name +# (shareos.ddns.host) and install it for the +# web interface; staging: the test CA +# shareos-acme renew renew it when less than 30 days are left +# (cron, daily) and install the new one +# shareos-acme status JSON: name, valid until, last error +# shareos-acme off back to the self-signed certificate +# +# The check runs over DNS (DNS-01): the TXT record _acme-challenge. is +# set at the DynDNS provider (shareos-ddns txt-set) – no port has to be open +# to the internet. Works with DuckDNS and Cloudflare. uacme keeps the account +# and the certificates in /etc/shareos/acme. Settings: UCI shareos.acme +# (email: optional, for expiry mails of Let's Encrypt; staging '1'). +# +PATH=/usr/sbin:/usr/bin:/sbin:/bin +DIR=/etc/shareos/acme +LOG=/tmp/shareos-acme.log +HOOK=/usr/libexec/shareos-acme-hook + +die() { echo "shareos-acme: $*" >&2; echo "$*" > /tmp/shareos-acme.error; exit 1; } +host() { uci -q get shareos.ddns.host | tr A-Z a-z; } +name() { + h=$(host) + [ "$(uci -q get shareos.ddns.provider)" = duckdns ] && ! echo "$h" | grep -q '\.duckdns\.org$' && h="$h.duckdns.org" + echo "$h" +} +uacme_run() { + s=; [ "$(uci -q get shareos.acme.staging)" = 1 ] && s=-s + uacme -v -c "$DIR" -t EC -b 256 $s -h "$HOOK" "$@" >>$LOG 2>&1 +} +restart_web() { (sleep 1; killall uhttpd) >/dev/null 2>&1 & } + +check() { + command -v uacme >/dev/null || die "uacme missing in this firmware" + [ "$(uci -q get shareos.ddns.enabled)" = 1 ] || die "switch on DynDNS first" + case "$(uci -q get shareos.ddns.provider)" in + duckdns|cloudflare) ;; + *) die "Let's Encrypt over DNS needs DuckDNS or Cloudflare as DynDNS provider" ;; + esac + [ -n "$(host)" ] || die "no DynDNS name" +} + +account() { + [ -s "$DIR/private/key.pem" ] && return 0 + mkdir -p "$DIR"; chmod 700 "$DIR" + m=$(uci -q get shareos.acme.email) + uacme_run -y new $m || die "Let's Encrypt account could not be created (see $LOG)" +} + +install() { + n=$(name) + [ -s "$DIR/$n/cert.pem" ] && [ -s "$DIR/private/$n/key.pem" ] || die "no certificate from Let's Encrypt" + /usr/sbin/shareos-cert install "$DIR/$n/cert.pem" "$DIR/private/$n/key.pem" >/dev/null || die "the web server did not take the certificate" + uci -q get shareos.web >/dev/null || uci -q set shareos.web=web + uci -q get shareos.acme >/dev/null || uci -q set shareos.acme=acme + uci -q set shareos.web.source=acme + uci -q set shareos.acme.name="$n" + uci -q commit shareos + /usr/sbin/shareos-cron sync >/dev/null 2>&1 + restart_web +} + +issue() { + check + : > $LOG + rm -f /tmp/shareos-acme.error + uci -q get shareos.acme >/dev/null || uci -q set shareos.acme=acme + if [ "$1" = staging ]; then uci -q set shareos.acme.staging=1; else uci -q delete shareos.acme.staging; fi + uci -q commit shareos + # the account belongs to one CA: a new one when switching between test and real + ca=$( [ "$1" = staging ] && echo staging || echo prod ) + [ "$(cat "$DIR/ca" 2>/dev/null)" = "$ca" ] || rm -rf "$DIR" + account + echo "$ca" > "$DIR/ca" + uacme_run -f issue "$(name)" + rc=$? + [ $rc = 0 ] || die "Let's Encrypt did not issue the certificate (see $LOG)" + install + logger -t shareos-acme "certificate for $(name) issued" 2>/dev/null + echo "certificate for $(name) issued and installed" +} + +renew() { + [ "$(uci -q get shareos.web.source)" = acme ] || return 0 + check + : > $LOG + uacme_run -d 30 issue "$(name)" + case $? in + 0) install; rm -f /tmp/shareos-acme.error + /usr/sbin/shareos-notify clear acme-error >/dev/null 2>&1 + logger -t shareos-acme "certificate for $(name) renewed" 2>/dev/null; echo renewed ;; + 1) echo "still valid" ;; + *) echo "renewal failed" > /tmp/shareos-acme.error + /usr/sbin/shareos-notify send warn network acme-error "Let's-Encrypt-Zertifikat nicht erneuert" \ + "Let's Encrypt certificate not renewed" "Neuer Versuch morgen. Protokoll: $LOG" "Next try tomorrow. Log: $LOG" >/dev/null 2>&1 + exit 1 ;; + esac +} + +status() { + n=$(uci -q get shareos.acme.name); on=false + [ "$(uci -q get shareos.web.source)" = acme ] && on=true + u= + [ -n "$n" ] && [ -s "$DIR/$n/cert.pem" ] && u=$( (mbedtls_cert_app mode=file filename="$DIR/$n/cert.pem" 2>/dev/null \ + || cert_app mode=file filename="$DIR/$n/cert.pem" 2>/dev/null) | sed -n 's/^ *expires on *: *//p' | head -1) + e=$(cat /tmp/shareos-acme.error 2>/dev/null | head -1 | sed 's/\\/\\\\/g;s/"/\\"/g') + staging=false; [ "$(uci -q get shareos.acme.staging)" = 1 ] && staging=true + printf '{"active":%s,"name":"%s","until":"%s","staging":%s,"email":"%s","error":"%s"}\n' \ + $on "$n" "$u" $staging "$(uci -q get shareos.acme.email)" "$e" +} + +off() { + [ "$(uci -q get shareos.web.source)" = acme ] || { echo "not active"; return; } + /usr/sbin/shareos-cert selfsigned >/dev/null || die "self-signed certificate failed" + uci -q delete shareos.web.source + uci -q commit shareos + /usr/sbin/shareos-cron sync >/dev/null 2>&1 + restart_web + echo "back to the self-signed certificate" +} + +case "$1" in + issue) issue "$2" ;; + renew) renew ;; + status) status ;; + off) off ;; + *) sed -n '3,17s/^# \{0,1\}//p' "$0"; exit 2 ;; +esac diff --git a/src/shareos-core/usr/sbin/shareos-ca b/src/shareos-core/usr/sbin/shareos-ca new file mode 100755 index 0000000..e1ee89e --- /dev/null +++ b/src/shareos-core/usr/sbin/shareos-ca @@ -0,0 +1,208 @@ +#!/bin/sh +# +# shareos-ca – own certificate authority for the home network +# +# shareos-ca init create the CA (once; 10 years, EC P-256) +# shareos-ca info the CA: subject, valid until, SHA-256 fingerprint +# shareos-ca root the CA certificate (PEM) – install it once on +# PCs and phones as a trusted root +# shareos-ca issue ... +# key and certificate for another device or +# service (825 days, server authentication); +# names may be wildcards (*.home.lan) +# shareos-ca list issued certificates (name, names, valid until) +# shareos-ca show crt|key the certificate (with the CA after it) or key +# shareos-ca delete +# shareos-ca web [check] certificate for this box's web interface: +# hostname, hostname.local, IPv4 address and +# shareos.web.host; installed through +# shareos-cert, shareos.web.source = ca. +# check: only when the names changed or less +# than 30 days are left (prints "renewed") +# +# Files: /etc/shareos/ca (ca.key, ca.crt, certs/.key|.crt), root only. +# Uses the mbedtls programs gen_key, cert_write and cert_app of the firmware. +# SHAREOS_ROOT= fake root (tests) SHAREOS_CA_NOW= the time (tests) +# SHAREOS_CA_BIN= mbedtls programs (tests) +# +[ -n "$SHAREOS_ROOT" ] || PATH=/usr/sbin:/usr/bin:/sbin:/bin +DIR=${SHAREOS_ROOT:-}/etc/shareos/ca +DAYS=825 + +die() { echo "shareos-ca: $*" >&2; exit 1; } +prog() { + for p in "${SHAREOS_CA_BIN:+$SHAREOS_CA_BIN/}mbedtls_$1" "${SHAREOS_CA_BIN:+$SHAREOS_CA_BIN/}$1"; do + [ -x "$p" ] || command -v "$p" >/dev/null && { echo "$p"; return; } + done + die "mbedtls program $1 missing" +} +now() { echo "${SHAREOS_CA_NOW:-$(date +%s)}"; } +stamp() { date -u -d "@$1" +%Y%m%d%H%M%S; } +serial() { echo "01$(od -An -tx1 -N15 /dev/urandom | tr -d ' \n')"; } +host() { if [ -n "$SHAREOS_ROOT" ]; then echo testbox; else cat /proc/sys/kernel/hostname; fi; } +ip4() { [ -n "$SHAREOS_ROOT" ] && { echo "${SHAREOS_CA_IP-192.0.2.10}"; return; }; ip -4 -o addr show dev eth0 2>/dev/null | sed -n 's/.*inet \([0-9.]*\).*/\1/p' | head -1; } +uget() { if [ -n "$SHAREOS_ROOT" ]; then uci -q -c "$SHAREOS_ROOT/etc/config" get "$1"; else uci -q get "$1"; fi; } +# set an option of shareos.web (the section is created when missing) +uset() { + u="uci -q${SHAREOS_ROOT:+ -c $SHAREOS_ROOT/etc/config}" + $u get shareos.web >/dev/null || $u set shareos.web=web + $u set "shareos.web.$1" && $u commit shareos +} + +# lines of cert_app for one certificate file +details() { + $(prog cert_app) mode=file filename="$1" 2>/dev/null | sed -n \ + -e 's/^ *subject name *: */subject=/p' \ + -e 's/^ *issuer name *: */issuer=/p' \ + -e 's/^ *expires on *: */until=/p' \ + -e 's/^ *\(dNSName\|iPAddress\) *: */san=/p' +} +# expiry of the first certificate in a file as seconds since 1970 +expires() { + u=$(details "$1" | sed -n 's/^until=//p' | head -1) + [ -n "$u" ] && date -u -d "$u" +%s 2>/dev/null || echo 0 +} +fingerprint() { + sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/{/-----/d;p}' "$1" | head -c 100000 \ + | base64 -d 2>/dev/null | sha256sum | cut -d' ' -f1 | sed 's/../&:/g;s/:$//' | tr a-f A-F +} +json_str() { printf '"%s"' "$(printf '%s' "$1" | sed 's/\\/\\\\/g;s/"/\\"/g')"; } + +init() { + [ -s "$DIR/ca.crt" ] && [ -s "$DIR/ca.key" ] && { echo "CA exists"; return; } + umask 077 + mkdir -p "$DIR/certs" + n=$(now) + t=$(mktemp -d) + $(prog gen_key) type=ec ec_curve=secp256r1 format=pem filename=$t/key >/dev/null || { rm -rf $t; die "key generation failed"; } + $(prog cert_write) selfsign=1 issuer_key=$t/key issuer_name="CN=ShareOS CA $(host),O=ShareOS" \ + serial_hex=$(serial) not_before=$(stamp $((n - 3600))) not_after=$(stamp $((n + 3653 * 86400))) \ + is_ca=1 max_pathlen=0 key_usage=key_cert_sign,crl_sign output_file=$t/crt >/dev/null \ + || { rm -rf $t; die "CA certificate failed"; } + mv $t/key "$DIR/ca.key" && mv $t/crt "$DIR/ca.crt" + rm -rf $t + chmod 600 "$DIR/ca.key"; chmod 644 "$DIR/ca.crt" + echo "CA created" +} + +info() { + if ! [ -s "$DIR/ca.crt" ]; then echo '{"exists":false}'; return; fi + d=$(details "$DIR/ca.crt") + printf '{"exists":true,"subject":%s,"until":%s,"fingerprint":%s,"count":%d}\n' \ + "$(json_str "$(echo "$d" | sed -n 's/^subject=//p' | head -1)")" \ + "$(json_str "$(echo "$d" | sed -n 's/^until=//p' | head -1)")" \ + "$(json_str "$(fingerprint "$DIR/ca.crt")")" \ + "$(ls "$DIR/certs/"*.crt 2>/dev/null | wc -l)" +} + +valid_name() { echo "$1" | grep -qE '^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$'; } +san_of() { + s= + for h; do + if echo "$h" | grep -qE '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then s="$s;IP:$h" + elif echo "$h" | grep -qE '^(\*\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]{0,251}[a-zA-Z0-9])?$'; then s="$s;DNS:$(echo "$h" | tr A-Z a-z)" + else die "not a host name or IPv4 address: $h"; fi + done + echo "${s#;}" +} + +# issue +issue_to() { + base=$1; cn=$2; shift 2 + [ $# -ge 1 ] || die "no host names" + [ -s "$DIR/ca.key" ] || init >/dev/null + san=$(san_of "$@") || exit 1 + umask 077 + n=$(now) + t=$(mktemp -d) + $(prog gen_key) type=ec ec_curve=secp256r1 format=pem filename=$t/key >/dev/null || { rm -rf $t; die "key generation failed"; } + $(prog cert_write) subject_key=$t/key subject_name="CN=$cn,O=ShareOS" issuer_crt="$DIR/ca.crt" issuer_key="$DIR/ca.key" \ + serial_hex=$(serial) not_before=$(stamp $((n - 3600))) not_after=$(stamp $((n + DAYS * 86400))) \ + san="$san" key_usage=digital_signature ext_key_usage=serverAuth output_file=$t/crt >/dev/null \ + || { rm -rf $t; die "certificate failed"; } + mv $t/key "$base.key" && mv $t/crt "$base.crt" + rm -rf $t + chmod 600 "$base.key"; chmod 644 "$base.crt" +} + +issue() { + name=$1; shift + valid_name "$name" || die "invalid name (letters, digits, . _ -)" + [ "$name" = web ] && die "the name web is used for the web interface" + mkdir -p "$DIR/certs" + issue_to "$DIR/certs/$name" "$1" "$@" + echo "certificate $name issued" +} + +list() { + printf '[' + sep= + for c in "$DIR/certs/"*.crt; do + [ -s "$c" ] || continue + name=$(basename "$c" .crt) + d=$(details "$c") + hosts=$(echo "$d" | sed -n 's/^san=//p' | while read -r h; do printf '%s,' "$(json_str "$h")"; done) + printf '%s{"name":%s,"hosts":[%s],"until":%s,"expires":%s}' "$sep" "$(json_str "$name")" "${hosts%,}" \ + "$(json_str "$(echo "$d" | sed -n 's/^until=//p' | head -1)")" "$(expires "$c")" + sep=, + done + printf ']\n' +} + +show() { + valid_name "$1" || die "invalid name" + f="$DIR/certs/$1" + [ "$1" = web ] && f="$DIR/web" + case "$2" in + crt) [ -s "$f.crt" ] || die "no certificate $1"; cat "$f.crt" "$DIR/ca.crt" ;; + key) [ -s "$f.key" ] || die "no certificate $1"; cat "$f.key" ;; + *) die "show crt|key" ;; + esac +} + +delete() { + valid_name "$1" || die "invalid name" + [ -e "$DIR/certs/$1.crt" ] || die "no certificate $1" + rm -f "$DIR/certs/$1.crt" "$DIR/certs/$1.key" + echo "deleted" +} + +# names of the box's web certificate +web_names() { + h=$(host) + echo "$h"; echo "$h.local" + w=$(uget shareos.web.host); [ -n "$w" ] && [ "$w" != "$h" ] && echo "$w" + i=$(ip4); [ -n "$i" ] && echo "$i" +} + +web() { + names=$(web_names | tr '\n' ' ') + if [ "$1" = check ]; then + [ "$(uget shareos.web.source)" = ca ] || return 0 + if [ -s "$DIR/web.crt" ]; then + have=$(details "$DIR/web.crt" | sed -n 's/^san=//p' | sort | tr '\n' ' ') + want=$(echo $names | tr ' ' '\n' | tr A-Z a-z | sort | tr '\n' ' ') + left=$(( $(expires "$DIR/web.crt") - $(now) )) + [ "$have" = "$want" ] && [ $left -gt $((30 * 86400)) ] && return 0 + fi + fi + issue_to "$DIR/web" "$(host)" $names + cat "$DIR/web.crt" "$DIR/ca.crt" > "$DIR/web.chain" + if [ -n "$SHAREOS_ROOT" ]; then echo "web certificate issued"; uset source=ca; return; fi + /usr/sbin/shareos-cert install "$DIR/web.chain" "$DIR/web.key" >/dev/null || die "the web server did not take the certificate" + rm -f "$DIR/web.chain" + uset source=ca + [ "$1" = check ] && echo renewed || echo "web certificate issued and installed" +} + +case "$1" in + init) init ;; + info) info ;; + root) [ -s "$DIR/ca.crt" ] || die "no CA yet"; cat "$DIR/ca.crt" ;; + issue) shift; [ $# -ge 2 ] || die "issue ..."; issue "$@" ;; + list) list ;; + show) show "$2" "$3" ;; + delete) delete "$2" ;; + web) web "$2" ;; + *) sed -n '3,27s/^# \{0,1\}//p' "$0"; exit 2 ;; +esac diff --git a/src/shareos-core/usr/sbin/shareos-cert b/src/shareos-core/usr/sbin/shareos-cert index 36dd32e..5e7770d 100755 --- a/src/shareos-core/usr/sbin/shareos-cert +++ b/src/shareos-core/usr/sbin/shareos-cert @@ -10,6 +10,9 @@ # follow the certificate); tested before use # shareos-cert info details of the current certificate # +# shareos.web.source: where the installed certificate comes from – ca (own CA, +# shareos-ca), acme (Let's Encrypt, shareos-acme), empty (self-signed or own). +# # Files: shareos.web.cert / shareos.web.key (UCI), default /etc/ssl/shareos/. # The self-signed certificate is valid for 10 years and names the hostname, # .local and the current IPv4 address. @@ -52,7 +55,9 @@ selfsigned() { # our own self-signed certificate that no longer names this box outdated() { i=$(info) - echo "$i" | grep -q '^issuer=.*O=ShareOS' && echo "$i" | grep -q '^subject=.*O=ShareOS' || return 1 + iss=$(echo "$i" | sed -n 's/^issuer=//p'); sub=$(echo "$i" | sed -n 's/^subject=//p') + # only self-signed (issuer = subject), not one of the own CA (shareos-ca) + [ "$iss" = "$sub" ] && echo "$sub" | grep -q 'O=ShareOS' || return 1 host=$(cat /proc/sys/kernel/hostname); ip=$(ip4) echo "$i" | grep -qx "san=dNSName:$host" || return 0 [ -z "$ip" ] || echo "$i" | grep -qx "san=iPAddress:$ip" || return 0 @@ -60,6 +65,11 @@ outdated() { } ensure() { + # a certificate of the own CA follows the names itself (shareos-ca web check) + if [ "$(uci -q get shareos.web.source)" = ca ] && [ -s "$CRT" ] && [ -x /usr/sbin/shareos-ca ]; then + /usr/sbin/shareos-ca web check 2>/dev/null | grep -q renewed && echo renewed + return 0 + fi if ! [ -s "$CRT" ] || ! [ -s "$KEY" ]; then selfsigned elif outdated; then selfsigned >/dev/null && echo renewed fi diff --git a/src/shareos-core/usr/sbin/shareos-cron b/src/shareos-core/usr/sbin/shareos-cron index 60b004f..4e5069f 100755 --- a/src/shareos-core/usr/sbin/shareos-cron +++ b/src/shareos-core/usr/sbin/shareos-cron @@ -13,7 +13,9 @@ # every hour/6h/day/week at its time); every 10 minutes the checks of # shareos-notify; SMART self-tests (storage.global.smart_test); the power # schedule (shareos.power, shareos-power); SMART values for the Zabbix agent -# (zabbix.main.disk_interval, shareos-zabbix collect). Areas +# (zabbix.main.disk_interval, shareos-zabbix collect); DynDNS (shareos.ddns, +# every 5 minutes) and the renewal of the web certificate (own CA or Let's +# Encrypt, daily). Areas # switched off under System -> Appearance # (shareos.ui.area_apps/area_cloud/area_paperless = 0) get no entries. # @@ -57,6 +59,13 @@ entries() { done echo "1 * * * * /usr/sbin/shareos-power arm >/dev/null 2>&1" fi + # dynamic DNS (shareos.ddns): every 5 minutes, sent only when the address changed + [ "$(uci -q get shareos.ddns.enabled)" = 1 ] && echo "*/5 * * * * /usr/sbin/shareos-ddns update >/dev/null 2>&1" + # web certificate: of the own CA (renewed before it runs out) or Let's Encrypt + case "$(uci -q get shareos.web.source)" in + ca) echo "23 4 * * * /usr/sbin/shareos-ca web check | grep -q renewed && killall uhttpd >/dev/null 2>&1" ;; + acme) echo "41 4 * * * /usr/sbin/shareos-acme renew >/dev/null 2>&1" ;; + esac # SMART self-tests of the disks in the bays (storage.global.smart_test) case "$(uci -q get storage.global.smart_test)" in short_weekly) echo "30 3 * * 0 /usr/sbin/shareos-storage smart-test short >/dev/null 2>&1" ;; diff --git a/src/shareos-core/usr/sbin/shareos-ddns b/src/shareos-core/usr/sbin/shareos-ddns new file mode 100755 index 0000000..2262a4c --- /dev/null +++ b/src/shareos-core/usr/sbin/shareos-ddns @@ -0,0 +1,289 @@ +#!/usr/bin/ucode -S +// +// shareos-ddns – dynamic DNS: keep a name on the internet pointing at this box +// +// shareos-ddns update [force] send the public address to the provider when +// it changed (cron every 5 minutes); force: now +// shareos-ddns status settings (without secrets) and last result (JSON) +// shareos-ddns txt-set +// shareos-ddns txt-clear +// TXT record for the DNS-01 check of Let's Encrypt +// (shareos-acme; DuckDNS and Cloudflare) +// +// Settings: UCI shareos.ddns – enabled '1', provider duckdns|dynv6|cloudflare| +// noip|custom, host (full name; DuckDNS also just the sub-domain), token +// (DuckDNS/dynv6 token, Cloudflare API token, No-IP/custom password), user +// (No-IP/custom), url (custom: https URL with {host} {ip} {ip6} {user} {token}), +// ipv4 '1' (default) and ipv6 '0'. The public IPv4 address is asked from +// api.ipify.org (icanhazip.com as fallback); IPv6 is the global address of eth0. +// The provider is asked again when an address changed, after a failure at +// most every 30 minutes and otherwise once a day. A failure becomes a message +// (shareos-notify, key ddns-error), a success clears it. +// +// SHAREOS_ROOT= fake root (tests) +// SHAREOS_DDNS_CURL= instead of curl (tests) +// SHAREOS_DDNS_NOW= the current time (tests) +// SHAREOS_DDNS_IP6= instead of the address of eth0 (tests) +// +'use strict'; + +import { readfile, writefile, stat, popen, mkdir } from 'fs'; +import { cursor } from 'uci'; + +const ROOT = getenv('SHAREOS_ROOT') ?? ''; +const STATE = ROOT + '/tmp/shareos-ddns.json'; +const CA = '/etc/ssl/certs/ca-certificates.crt'; +const CURL = getenv('SHAREOS_DDNS_CURL') ?? 'curl'; +const PROVIDERS = { duckdns: 1, dynv6: 1, cloudflare: 1, noip: 1, custom: 1 }; +const CF = 'https://api.cloudflare.com/client/v4'; +const RETRY = 1800, DAILY = 86400; + +function out(o) { print(sprintf('%J\n', o)); } +function shq(s) { return "'" + replace('' + s, "'", "'\\''") + "'"; } +function now() { let n = getenv('SHAREOS_DDNS_NOW'); return n ? +n : time(); } +function log(msg) { system('logger -t shareos-ddns ' + shq(msg) + ' 2>/dev/null'); } +function enc(s) { + return join('', map(split('' + s, ''), (c) => match(c, /^[A-Za-z0-9._~-]$/) ? c : sprintf('%%%02X', ord(c)))); +} +function version() { + let m = match(readfile(ROOT + '/etc/os-release') ?? '', /VERSION_ID="?([^"\n]+)/); + return m ? m[1] : 'dev'; +} + +// curl: { code, body }; headers as [ 'Name: value' ], secrets travel on stdin +// (-H @- / -u through --config) so they do not show up in ps +function http(method, url, opt) { + opt ??= {}; + let cfg = []; + for (let h in opt.headers ?? []) push(cfg, 'header = ' + sprintf('%J', h)); + if (opt.auth) push(cfg, 'user = ' + sprintf('%J', opt.auth)); + let cmd = sprintf('%s -sS -m 30 -X %s %s -w %s -K - -A %s', CURL, method, + stat(CA) ? '--cacert ' + CA : '', shq('\n%{http_code}'), shq('ShareOS/' + version() + ' ddns')); + if (opt.ipv) cmd += ' -' + opt.ipv; + if (opt.json != null) cmd += ' -H ' + shq('Content-Type: application/json') + ' --data ' + shq(sprintf('%J', opt.json)); + cmd += ' ' + shq(url); + let f = popen('printf %s ' + shq(join('\n', cfg) + '\n') + ' | ' + cmd + ' 2>&1', 'r'); + if (!f) return { code: 0, body: 'curl missing' }; + let o = f.read('all') ?? ''; + f.close(); + // the last line is the HTTP status (-w) + let i = rindex(o, '\n'), c = substr(o, i + 1); + return i >= 0 && match(c, /^[0-9][0-9][0-9]$/) ? { code: +c, body: trim(substr(o, 0, i)) } : { code: 0, body: trim(o) }; +} + +function conf() { + let c = ROOT ? cursor(ROOT + '/etc/config') : cursor(); + let d = c.get_all('shareos', 'ddns') ?? {}; + return { + enabled: d.enabled == '1', provider: d.provider ?? '', host: lc(trim(d.host ?? '')), + token: d.token ?? '', user: d.user ?? '', url: d.url ?? '', + ipv4: d.ipv4 != '0', ipv6: d.ipv6 == '1' + }; +} + +function state() { let s = null; try { s = json(readfile(STATE) ?? 'null'); } catch (e) { } return type(s) == 'object' ? s : {}; } +function save(s) { mkdir(ROOT + '/tmp'); writefile(STATE, sprintf('%J', s)); } + +const IP4 = /^(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])){3}$/; +function private4(a) { + return !!match(a, /^(10\.|127\.|169\.254\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.)/); +} + +function public4() { + for (let u in [ 'https://api.ipify.org', 'https://ipv4.icanhazip.com' ]) { + let r = http('GET', u, { ipv: 4 }); + let a = trim(r.body); + if (r.code == 200 && match(a, IP4) && !private4(a)) return a; + } + return null; +} + +function public6() { + let t = getenv('SHAREOS_DDNS_IP6'); + if (t != null) return t || null; + let f = popen('ip -6 -o addr show dev eth0 scope global 2>/dev/null', 'r'); + let o = f ? (f.read('all') ?? '') : ''; + f?.close(); + let best = null; + for (let l in split(o, '\n')) { + if (match(l, /deprecated|tentative/)) continue; + let m = match(l, /inet6 ([0-9a-f:]+)\//); + if (!m || match(m[1], /^f[cd]/)) continue; // no ULA + // a stable address (no "temporary") is better for a name + if (!best || (!match(l, /temporary/) && best.tmp)) best = { a: m[1], tmp: !!match(l, /temporary/) }; + } + return best?.a; +} + +function duck_name(h) { return replace(h, /\.duckdns\.org$/, ''); } + +// first word of a dyndns2 answer +function dyn2(r) { + let w = split(trim(r.body), ' ')[0]; + if (r.code >= 200 && r.code < 300 && (w == 'good' || w == 'nochg')) return null; + let msgs = { badauth: 'user name or password wrong', nohost: 'host name unknown to the provider', + notfqdn: 'not a full host name', abuse: 'blocked by the provider (abuse)', '911': 'provider has a problem, try later', + dnserr: 'provider has a problem, try later', badagent: 'client refused by the provider' }; + if (msgs[w]) return msgs[w]; + if (r.code >= 200 && r.code < 300) return null; + return 'HTTP ' + r.code + (r.body ? ': ' + substr(r.body, 0, 120) : ''); +} + +// Cloudflare: zone of a name (the longest suffix that is a zone) +function cf_hdr(c) { return [ 'Authorization: Bearer ' + c.token ]; } +function cf_get(c, path) { + let r = http('GET', CF + path, { headers: cf_hdr(c) }), j = null; + try { j = json(r.body); } catch (e) { } + return j; +} +function cf_err(j) { return j?.errors?.[0]?.message ?? 'Cloudflare: no answer'; } +function cf_zone(c, name) { + let p = split(name, '.'); + for (let i = 0; i < length(p) - 1; i++) { + let z = join('.', slice(p, i)); + let j = cf_get(c, '/zones?name=' + enc(z)); + if (j && !j.success) return { error: cf_err(j) }; + if (j?.result?.[0]?.id) return { id: j.result[0].id }; + } + return { error: 'no Cloudflare zone for ' + name + ' (token needs Zone:Read and DNS:Edit)' }; +} +function cf_set(c, zone, type, name, content) { + let j = cf_get(c, sprintf('/zones/%s/dns_records?type=%s&name=%s', zone, type, enc(name))); + if (!j?.success) return cf_err(j); + let rec = j.result?.[0]; + if (rec && rec.content == content) return null; + let body = { type, name, content, ttl: 120, proxied: false }; + let r = rec ? http('PUT', sprintf('%s/zones/%s/dns_records/%s', CF, zone, rec.id), { headers: cf_hdr(c), json: body }) + : http('POST', sprintf('%s/zones/%s/dns_records', CF, zone), { headers: cf_hdr(c), json: body }); + let k = null; + try { k = json(r.body); } catch (e) { } + return k?.success ? null : cf_err(k); +} + +// send the addresses; null or an error text +function send(c, ip4, ip6) { + let p = c.provider, h = c.host; + if (p == 'duckdns') { + let r = http('GET', sprintf('https://www.duckdns.org/update?domains=%s&token=%s&ip=%s%s', + enc(duck_name(h)), enc(c.token), ip4 ?? '', ip6 ? '&ipv6=' + ip6 : '')); + return trim(r.body) == 'OK' ? null : r.code == 200 ? 'DuckDNS refused it (KO: name or token wrong)' : 'HTTP ' + r.code; + } + if (p == 'dynv6') { + let r = http('GET', sprintf('https://dynv6.com/api/update?hostname=%s&token=%s%s%s', + enc(h), enc(c.token), ip4 ? '&ipv4=' + ip4 : '', ip6 ? '&ipv6=' + ip6 : '')); + return r.code >= 200 && r.code < 300 ? null : r.code == 401 || r.code == 403 ? 'dynv6: token wrong' : + r.code == 404 ? 'dynv6: zone unknown' : 'HTTP ' + r.code + (r.body ? ': ' + substr(r.body, 0, 120) : ''); + } + if (p == 'noip') { + let r = http('GET', sprintf('https://dynupdate.no-ip.com/nic/update?hostname=%s%s%s', + enc(h), ip4 ? '&myip=' + ip4 : '', ip6 ? '&myipv6=' + ip6 : ''), { auth: c.user + ':' + c.token }); + return dyn2(r); + } + if (p == 'cloudflare') { + let z = cf_zone(c, h); + if (z.error) return z.error; + let e = ip4 ? cf_set(c, z.id, 'A', h, ip4) : null; + if (!e && ip6) e = cf_set(c, z.id, 'AAAA', h, ip6); + return e; + } + if (p == 'custom') { + if (!match(c.url, /^https?:\/\//)) return 'no update URL'; + let u = c.url; + for (let k, v in { host: enc(h), ip: ip4 ?? '', ip6: ip6 ?? '', user: enc(c.user), token: enc(c.token) }) + u = replace(u, '{' + k + '}', v); + let r = http('GET', u, c.user != '' && !match(c.url, /\{(user|token)\}/) ? { auth: c.user + ':' + c.token } : null); + return dyn2(r); + } + return 'unknown provider'; +} + +function valid(c) { + if (!PROVIDERS[c.provider]) return 'no provider chosen'; + if (!match(c.host, /^[a-z0-9]([a-z0-9.-]{0,251}[a-z0-9])?$/)) return 'no host name'; + if (c.provider != 'duckdns' && !match(c.host, /\./)) return 'host name without domain'; + if (c.provider != 'custom' && c.token == '') return 'no token or password'; + if (c.provider == 'noip' && c.user == '') return 'no user name'; + if (!c.ipv4 && !c.ipv6) return 'neither IPv4 nor IPv6 chosen'; + return null; +} + +function notify(err) { + if (getenv('SHAREOS_DDNS_CURL')) return; + if (err) + system(sprintf('/usr/sbin/shareos-notify send warn network ddns-error %s %s %s %s >/dev/null 2>&1', + shq('DynDNS-Aktualisierung fehlgeschlagen'), shq('Dynamic DNS update failed'), + shq('System → Netzwerk → DynDNS: ' + err), shq('System → Network → DynDNS: ' + err))); + else system('/usr/sbin/shareos-notify clear ddns-error >/dev/null 2>&1'); +} + +function update(force) { + let c = conf(), s = state(), t = now(); + if (!c.enabled) return { skipped: 'off' }; + let e = valid(c); + if (e) { save({ ...s, ok: false, error: e, checked: t }); return { error: e }; } + let ip4 = c.ipv4 ? public4() : null, ip6 = c.ipv6 ? public6() : null; + if (!ip4 && !ip6) { + e = c.ipv4 ? 'no public IPv4 address found (offline, or the line has no own IPv4 address – try IPv6)' : 'no global IPv6 address on eth0'; + save({ ...s, ok: false, error: e, checked: t }); + return { error: e }; + } + let same = s.ip4 == ip4 && s.ip6 == ip6 && s.host == c.host && s.provider == c.provider; + if (!force && same && ((s.ok && t - (s.sent ?? 0) < DAILY) || (!s.ok && t - (s.checked ?? 0) < RETRY))) + return { skipped: 'unchanged', ip4, ip6 }; + e = send(c, ip4, ip6); + let n = { provider: c.provider, host: c.host, ip4, ip6, checked: t, ok: !e, error: e, + sent: e ? s.sent : t, changed: !e && !same ? t : s.changed }; + save(n); + if (e) log('update of ' + c.host + ' failed: ' + e); + else if (!same) log(c.host + ' -> ' + join(' ', filter([ ip4, ip6 ], length))); + if (e || !s.ok) notify(e); + return e ? { error: e } : { ok: true, ip4, ip6 }; +} + +function status() { + let c = conf(), s = state(); + return { enabled: c.enabled, provider: c.provider, host: c.host, user: c.user, url: c.url, + token_set: c.token != '', ipv4: c.ipv4, ipv6: c.ipv6, last: length(s) ? s : null }; +} + +// Let's Encrypt DNS-01: _acme-challenge. TXT +function txt(set, name, value) { + let c = conf(); + if (!match(name ?? '', /^_acme-challenge\./) || !value) return { error: 'usage: txt-set|txt-clear _acme-challenge. ' }; + if (c.provider == 'duckdns') { + // DuckDNS keeps one TXT value for the whole domain + let r = http('GET', sprintf('https://www.duckdns.org/update?domains=%s&token=%s&txt=%s%s', + enc(duck_name(c.host)), enc(c.token), set ? enc(value) : '', set ? '' : '&clear=true')); + return trim(r.body) == 'OK' ? { ok: true } : { error: 'DuckDNS refused the TXT record' }; + } + if (c.provider == 'cloudflare') { + let z = cf_zone(c, substr(name, length('_acme-challenge.'))); + if (z.error) return { error: z.error }; + if (set) { + let r = http('POST', sprintf('%s/zones/%s/dns_records', CF, z.id), { headers: cf_hdr(c), + json: { type: 'TXT', name, content: value, ttl: 120 } }); + let j = null; try { j = json(r.body); } catch (e) { } + return j?.success ? { ok: true } : { error: cf_err(j) }; + } + let j = cf_get(c, sprintf('/zones/%s/dns_records?type=TXT&name=%s', z.id, enc(name))); + for (let rec in j?.result ?? []) + if (trim(rec.content, '"') == value) + http('DELETE', sprintf('%s/zones/%s/dns_records/%s', CF, z.id, rec.id), { headers: cf_hdr(c) }); + return { ok: true }; + } + return { error: 'Let\'s Encrypt over DNS needs DuckDNS or Cloudflare' }; +} + +const ARGS = ARGV; +switch (ARGS[0]) { +case 'update': out(update(ARGS[1] == 'force')); break; +case 'status': out(status()); break; +case 'txt-set': case 'txt-clear': { + let r = txt(ARGS[0] == 'txt-set', ARGS[1], ARGS[2]); + out(r); + exit(r.error ? 1 : 0); +} +default: + warn('usage: shareos-ddns update [force] | status | txt-set|txt-clear \n'); + exit(2); +} diff --git a/src/shareos-core/usr/share/rpcd/acl.d/shareos.json b/src/shareos-core/usr/share/rpcd/acl.d/shareos.json index 2f1ba43..6e96d74 100644 --- a/src/shareos-core/usr/share/rpcd/acl.d/shareos.json +++ b/src/shareos-core/usr/share/rpcd/acl.d/shareos.json @@ -167,7 +167,19 @@ "shareos.web": [ "set", "selfsigned", - "install" + "install", + "ddns_get", + "ddns_set", + "ddns_update", + "ca_get", + "ca_init", + "ca_issue", + "ca_pem", + "ca_delete", + "ca_web", + "acme_get", + "acme_issue", + "acme_off" ], "uci": [ "set", diff --git a/src/shareos-core/usr/share/rpcd/ucode/shareos.uc b/src/shareos-core/usr/share/rpcd/ucode/shareos.uc index 9a13d8c..52ab203 100644 --- a/src/shareos-core/usr/share/rpcd/ucode/shareos.uc +++ b/src/shareos-core/usr/share/rpcd/ucode/shareos.uc @@ -556,6 +556,14 @@ function cert_info() { return memo('cert', crt + '|' + fstamp(crt), 0, cert_info_read); } +// where the web certificate comes from: ca (own CA), acme (Let's Encrypt), '' (self-signed/own) +function web_source(v) { + let c = cursor(); + c.set('shareos', 'web', 'web'); + if (v) c.set('shareos', 'web', 'source', v); else c.delete('shareos', 'web', 'source'); + c.commit('shareos'); +} + // both web servers (admin interface and apps) run under shareos-uhttpd, // which starts them again and reads settings and certificate anew function restart_uhttpd() { @@ -842,7 +850,8 @@ function default_password() { const CRYPT = '/usr/sbin/shareos-crypt'; const BACKUP_CONFIGS = [ 'shareos', 'storage', 'network', 'accounts', 'shares', 'paperless', 'cloud', 'apps', 'snapshots', 'dlna', 'dockerhosts', 'zabbix' ]; -const BACKUP_FILES = [ '/etc/ksmbd/ksmbdpwd.db', '/etc/ssl/shareos/server.crt', '/etc/ssl/shareos/server.key', '/etc/hostname', '/etc/timezone', '/etc/shareos/zabbix.psk' ]; +const BACKUP_FILES = [ '/etc/ksmbd/ksmbdpwd.db', '/etc/ssl/shareos/server.crt', '/etc/ssl/shareos/server.key', '/etc/hostname', '/etc/timezone', '/etc/shareos/zabbix.psk', + '/etc/shareos/ca/ca.crt', '/etc/shareos/ca/ca.key' ]; function lines_of(path) { return filter(split(readfile(path) ?? '', '\n'), (l) => l != ''); } function field(l, i) { return split(l, ':')[i]; } @@ -2057,7 +2066,7 @@ return { let c = cursor(); let wh = web_host(); return { https: c.get('shareos', 'web', 'https') != '0', redirect: c.get('shareos', 'web', 'redirect') == '1', cert: cert_info(), - host: c.get('shareos', 'web', 'host') ?? '', cert_host: cert_host() }; + host: c.get('shareos', 'web', 'host') ?? '', cert_host: cert_host(), source: c.get('shareos', 'web', 'source') ?? '' }; } }, @@ -2084,11 +2093,144 @@ return { call: function() { let r = sh(CERT + ' selfsigned'); forget('cert'); if (r.rc != 0) return { error: trim(r.out) }; + web_source(''); restart_uhttpd(); return { ok: true, cert: cert_info() }; } }, + // dynamic DNS (shareos-ddns); the token is never sent back + ddns_get: { + call: function() { return json(trim(sh('/usr/sbin/shareos-ddns status').out) || 'null') ?? { error: 'shareos-ddns failed' }; } + }, + + ddns_set: { + args: { enabled: false, provider: '', host: '', token: '', user: '', url: '', ipv4: true, ipv6: false }, + call: function(req) { + let a = req.args, c = cursor(); + if (a.provider && !(a.provider in { duckdns: 1, dynv6: 1, cloudflare: 1, noip: 1, custom: 1 })) return { error: 'invalid provider' }; + if (a.host && !match(a.host, /^[A-Za-z0-9]([A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$/)) return { error: 'invalid host name' }; + if (a.url && !match(a.url, /^https?:\/\/[^ ]+$/)) return { error: 'invalid URL' }; + for (let k in [ 'token', 'user', 'url' ]) if (length(a[k] ?? '') > 512 || match(a[k] ?? '', /[\n\r]/)) return { error: 'invalid ' + k }; + c.set('shareos', 'ddns', 'ddns'); + c.set('shareos', 'ddns', 'enabled', a.enabled ? '1' : '0'); + for (let k in [ 'provider', 'host', 'user', 'url' ]) + if (a[k] != null) { if (a[k] == '') c.delete('shareos', 'ddns', k); else c.set('shareos', 'ddns', k, k == 'host' ? lc(a[k]) : a[k]); } + // empty: keep the stored token + if (a.token) c.set('shareos', 'ddns', 'token', a.token); + if (a.ipv4 != null) c.set('shareos', 'ddns', 'ipv4', a.ipv4 ? '1' : '0'); + if (a.ipv6 != null) c.set('shareos', 'ddns', 'ipv6', a.ipv6 ? '1' : '0'); + c.commit('shareos'); + system('/usr/sbin/shareos-cron sync >/dev/null 2>&1'); + system('rm -f /tmp/shareos-ddns.json'); + if (a.enabled) system('(/usr/sbin/shareos-ddns update force) >/dev/null 2>&1 &'); + return { ok: true, pending: !!a.enabled }; + } + }, + + ddns_update: { + call: function() { + system('(/usr/sbin/shareos-ddns update force) >/dev/null 2>&1 &'); + return { ok: true, pending: true }; + } + }, + + // own certificate authority (shareos-ca) + ca_get: { + call: function() { + let i = json(trim(sh('/usr/sbin/shareos-ca info').out) || 'null') ?? { exists: false }; + i.certs = i.exists ? (json(trim(sh('/usr/sbin/shareos-ca list').out) || '[]') ?? []) : []; + i.web = cursor().get('shareos', 'web', 'source') == 'ca'; + return i; + } + }, + + ca_init: { + call: function() { + let r = sh('/usr/sbin/shareos-ca init'); + return r.rc == 0 ? { ok: true } : { error: replace(trim(r.out), 'shareos-ca: ', '') }; + } + }, + + ca_issue: { + args: { name: '', hosts: [] }, + call: function(req) { + let n = req.args.name ?? '', h = req.args.hosts; + if (!match(n, /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/)) return { error: 'invalid name' }; + if (type(h) != 'array' || !length(h) || length(h) > 20) return { error: 'no host names' }; + for (let x in h) if (type(x) != 'string' || !match(x, /^(\*\.)?[A-Za-z0-9]([A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$/)) return { error: 'invalid host name: ' + x }; + let r = sh('/usr/sbin/shareos-ca issue ' + shq(n) + ' ' + join(' ', map(h, shq))); + return r.rc == 0 ? { ok: true } : { error: replace(trim(r.out), 'shareos-ca: ', '') }; + } + }, + + // PEM: root (the CA), or crt/key of an issued certificate + ca_pem: { + args: { name: '', kind: '' }, + call: function(req) { + let n = req.args.name ?? '', k = req.args.kind ?? ''; + let r = k == 'root' ? sh('/usr/sbin/shareos-ca root') + : match(n, /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/) && (k == 'crt' || k == 'key') ? sh('/usr/sbin/shareos-ca show ' + shq(n) + ' ' + k) + : null; + if (!r) return { error: 'invalid request' }; + return r.rc == 0 ? { pem: r.out } : { error: replace(trim(r.out), 'shareos-ca: ', '') }; + } + }, + + ca_delete: { + args: { name: '' }, + call: function(req) { + let n = req.args.name ?? ''; + if (!match(n, /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/)) return { error: 'invalid name' }; + let r = sh('/usr/sbin/shareos-ca delete ' + shq(n)); + return r.rc == 0 ? { ok: true } : { error: replace(trim(r.out), 'shareos-ca: ', '') }; + } + }, + + // the web interface gets a certificate of the own CA + ca_web: { + call: function() { + let r = sh('/usr/sbin/shareos-ca web'); forget('cert'); + if (r.rc != 0) return { error: replace(trim(r.out), 'shareos-ca: ', '') }; + system('/usr/sbin/shareos-cron sync >/dev/null 2>&1'); + restart_uhttpd(); + return { ok: true }; + } + }, + + // Let's Encrypt for the DynDNS name (shareos-acme, takes up to a few minutes) + acme_get: { + call: function() { + let j = json(trim(sh('/usr/sbin/shareos-acme status').out) || 'null') ?? {}; + j.running = !!stat('/tmp/shareos-acme.running'); + return j; + } + }, + + acme_issue: { + args: { email: '', staging: false }, + call: function(req) { + let m = req.args.email ?? ''; + if (m != '' && !match(m, /^[^@ ]+@[^@ ]+\.[^@ ]+$/)) return { error: 'invalid e-mail address' }; + if (stat('/tmp/shareos-acme.running')) return { error: 'already running' }; + let c = cursor(); + c.set('shareos', 'acme', 'acme'); + if (m == '') c.delete('shareos', 'acme', 'email'); else c.set('shareos', 'acme', 'email', m); + c.commit('shareos'); + system('(touch /tmp/shareos-acme.running; /usr/sbin/shareos-acme issue ' + (req.args.staging ? 'staging' : '') + + ' >/tmp/shareos-acme.out 2>&1; rm -f /tmp/shareos-acme.running) >/dev/null 2>&1 &'); + forget('cert'); + return { ok: true, running: true }; + } + }, + + acme_off: { + call: function() { + let r = sh('/usr/sbin/shareos-acme off'); forget('cert'); + return r.rc == 0 ? { ok: true } : { error: replace(trim(r.out), 'shareos-acme: ', '') }; + } + }, + install: { // PEM texts; the certificate may be followed by the chain args: { cert: '', key: '' }, @@ -2101,6 +2243,7 @@ return { let r = sh(CERT + ' install ' + shq(d + '/crt') + ' ' + shq(d + '/key')); forget('cert'); system('rm -rf ' + shq(d)); if (r.rc != 0) return { error: replace(trim(r.out), 'shareos-cert: ', '') }; + web_source(''); restart_uhttpd(); return { ok: true, cert: cert_info() }; } diff --git a/src/shareos-core/www/app.js b/src/shareos-core/www/app.js index eafdb45..f41200f 100644 --- a/src/shareos-core/www/app.js +++ b/src/shareos-core/www/app.js @@ -2607,7 +2607,7 @@ const SYS = {

Nach einer Änderung startet der Webserver neu; die Seite ist für einen Moment nicht erreichbar.

-
ZERTIFIKAT${c.none ? '' : c.selfsigned ? html`selbstsigniert` : html`eigenes Zertifikat`}
+
ZERTIFIKAT${c.none ? '' : c.selfsigned ? html`selbstsigniert` : w.source === 'ca' ? html`eigene CA` : w.source === 'acme' ? html`Let's Encrypt` : html`eigenes Zertifikat`}
${c.none ? html`

Noch kein Zertifikat – es wird beim Einschalten von HTTPS automatisch erstellt.

` : html`
Ausgestellt für
${c.subject || '–'}
@@ -2695,7 +2695,106 @@ const SYS = {

HTTPS & ZERTIFIKAT

- ${SYS.https()}`; + ${SYS.https()} +

DYNDNS

+ ${SYS.ddns()} +

EIGENE ZERTIFIKATE

+ ${SYS.ca()}`; + }, + + // dynamic DNS: a name on the internet for the box (shareos-ddns) + ddns() { + const d = S.ddns; + if (!d) return html`
Lade …
`; + const p = S.ddnsProv ?? d.provider ?? '', l = d.last; + const ok = d.enabled && l?.ok, err = d.enabled && l && !l.ok; + const opt = (v, t) => html``; + const hostPh = { duckdns: 'meinebox.duckdns.org', dynv6: 'meinebox.dynv6.net', cloudflare: 'nas.example.org', noip: 'meinebox.ddns.net', custom: 'nas.example.org' }[p] || 'meinebox.duckdns.org'; + return html`
+
+
ANBIETER${d.enabled ? 'aktiv' : 'aus'}
+ +
+ + + + + +
+
+ + +
+

Die Box meldet ihre öffentliche Adresse alle 5 Minuten, wenn sie sich geändert hat. Viele Anschlüsse (DS-Lite, Glasfaser, Mobilfunk) haben keine eigene IPv4-Adresse – dann IPv6 nehmen. Für Zugriff von außen muss der Router den Port weiterleiten (bzw. bei IPv6 freigeben).

+
+ ${d.enabled ? html`` : ''}
+
+
+
ZUSTAND${ok ? html`aktuell` : err ? html`Fehler` : ''}
+ ${d.enabled && l ? html`
+
Name
${l.host || d.host || '–'}
+
IPv4
${l.ip4 || '–'}
+
IPv6
${l.ip6 || '–'}
+
Zuletzt gemeldet
${fmtTime(l.sent)}
+
Zuletzt geprüft
${fmtTime(l.checked)}
+
+ ${err ? html`

${l.error}

` : ''}` + : html`

${d.enabled ? 'Noch keine Meldung – kommt gleich.' : 'DynDNS ist aus.'}

`} +
+
`; + }, + + // own certificate authority and Let's Encrypt + ca() { + const c = S.ca, a = S.acme || {}, d = S.ddns || {}; + if (!c) return html`
Lade …
`; + const leOk = d.enabled && (d.provider === 'duckdns' || d.provider === 'cloudflare') && d.host; + return html`
+
+
ZERTIFIZIERUNGSSTELLE (CA)${c.web ? html`diese Box nutzt sie` : ''}
+ ${c.exists ? html`
+
Name
${c.subject || '–'}
+
Gültig bis
${c.until || '–'}
+
Fingerabdruck
${c.fingerprint || '–'}
+
+
+ + +
+

Das Stammzertifikat einmal auf PC und Handy als vertrauenswürdig installieren (Windows: Doppelklick → „Vertrauenswürdige Stammzertifizierungsstellen“, Android/iOS: Einstellungen → Sicherheit). Danach sind alle Zertifikate dieser CA ohne Warnung – für die Box und andere Geräte im Heimnetz.

` + : html`

Eine eigene Zertifizierungsstelle stellt Zertifikate für die Box und andere Geräte im Heimnetz aus (z. B. *.home.lan). Ihr Stammzertifikat wird einmal auf den Geräten installiert – danach gibt es keine Zertifikatswarnungen mehr.

+
`} +
+
+
LET'S ENCRYPT${a.active ? html`aktiv${a.staging ? ' (Test)' : ''}` : ''}
+ ${a.running ? html`
Zertifikat wird geholt – das dauert bis zu 3 Minuten …
` + : a.active ? html`
Name
${a.name || '–'}
Gültig bis
${a.until || '–'}
+

Wird täglich geprüft und 30 Tage vor Ablauf erneuert.

+
` + : leOk ? html`
+

Ein Zertifikat für ${d.provider === 'duckdns' && !/\.duckdns\.org$/.test(d.host) ? d.host + '.duckdns.org' : d.host}, dem alle Browser vertrauen. Die Prüfung läuft über einen DNS-Eintrag beim DynDNS-Anbieter – kein Port muss offen sein.

+ + +
+
` + : html`

Braucht DynDNS mit DuckDNS oder Cloudflare (oben).

`} + ${a.error && !a.running ? html`

${a.error}

` : ''} +
+
+ ${c.exists ? html`
+
ZERTIFIKAT FÜR EIN ANDERES GERÄT
+
+ + +
+

Gültig 825 Tage. Auch Platzhalter wie *.home.lan sind möglich. Zertifikat und Schlüssel herunterladen und im Gerät eintragen.

+
+ ${c.certs?.length ? html`
+ ${c.certs.map((x) => html` + `)} +
NAMEHOSTNAMENGÜLTIG BIS
${x.name}${x.hosts.join(', ')}${x.until}
` : ''} +
` : ''}`; }, terminal() { @@ -2880,6 +2979,33 @@ const VIEWS_AFTER = { } if (sub === 'network') { if (!S.web) loadWeb(); + $('#ddns-prov')?.addEventListener('change', (e) => { S.ddnsProv = e.target.value; renderMain(); }); + $('#f-ddns')?.addEventListener('submit', async (e) => { + e.preventDefault(); + const f = e.target; + const v = { enabled: f.enabled.checked, provider: f.provider.value, host: f.host.value.trim(), token: f.token.value, user: f.user.value.trim(), url: f.url.value.trim(), ipv4: f.ipv4.checked, ipv6: f.ipv6.checked }; + if (v.enabled && (!v.provider || !v.host)) { toast('Bitte Anbieter und Namen angeben.', true); return; } + try { + const r = await rpc('shareos.web', 'ddns_set', v); + S.ddnsProv = null; + toast(r.pending ? 'Gespeichert – die Adresse wird gemeldet.' : 'Gespeichert.'); + await loadNetExtra(); renderMain(); + if (r.pending) setTimeout(async () => { await loadNetExtra(); if (S.route.sub === 'network') renderMain(); }, 6000); + } catch (err) { toast('DynDNS: ' + err.message, true); } + }); + $('#f-ca-issue')?.addEventListener('submit', async (e) => { + e.preventDefault(); + const f = e.target, hosts = f.hosts.value.split(/[\s,;]+/).filter(Boolean); + if (!f.name.value.trim() || !hosts.length) { toast('Bitte Name und Hostnamen angeben.', true); return; } + try { await rpc('shareos.web', 'ca_issue', { name: f.name.value.trim(), hosts }); toast('Zertifikat ausgestellt.'); await loadNetExtra(); renderMain(); } + catch (err) { toast('Zertifikat: ' + err.message, true); } + }); + $('#f-acme')?.addEventListener('submit', async (e) => { + e.preventDefault(); + const f = e.target; + try { await rpc('shareos.web', 'acme_issue', { email: f.email.value.trim(), staging: f.staging.checked }); await loadNetExtra(); renderMain(); } + catch (err) { toast('Let\'s Encrypt: ' + err.message, true); } + }); $('#opt-https')?.addEventListener('change', (e) => webSet({ https: e.target.checked })); $('#opt-redirect')?.addEventListener('change', (e) => webSet({ redirect: e.target.checked })); $('#f-webhost')?.addEventListener('submit', async (e) => { @@ -3485,9 +3611,29 @@ async function copyText(t) { async function loadWeb() { try { S.web = await rpc('shareos.web', 'get'); } catch (e) { toast('HTTPS: ' + e.message, true); } + await loadNetExtra(); if (S.route.sub === 'network') renderMain(); } +// DynDNS, own CA, Let's Encrypt (System -> Netzwerk) +async function loadNetExtra() { + const [d, c, a] = await Promise.all([rpc('shareos.web', 'ddns_get').catch(() => null), rpc('shareos.web', 'ca_get').catch(() => null), rpc('shareos.web', 'acme_get').catch(() => null)]); + if (d) S.ddns = d; + if (c) S.ca = c; + if (a) S.acme = a; + // Let's Encrypt runs in the background: look again until it is done + clearTimeout(S.acmeTimer); + if (S.acme?.running) S.acmeTimer = setTimeout(async () => { if (S.route.sub !== 'network') return; const was = S.acme.running; await loadNetExtra(); if (was && !S.acme.running) { toast(S.acme.active ? 'Let\'s-Encrypt-Zertifikat aktiv – die Seite verbindet sich neu.' : 'Let\'s Encrypt: ' + (S.acme.error || 'fehlgeschlagen'), !S.acme.active); if (S.acme.active) afterWebRestart(); } renderMain(); }, 5000); +} + +function pemDownload(pem, name) { + const a = document.createElement('a'); + a.href = URL.createObjectURL(new Blob([pem], { type: 'application/x-pem-file' })); + a.download = name; + document.body.append(a); a.click(); a.remove(); + setTimeout(() => URL.revokeObjectURL(a.href), 5000); +} + // the web server restarts after HTTPS changes: wait for it, keep the page function afterWebRestart() { setTimeout(async () => { @@ -4015,6 +4161,44 @@ const ACTIONS = { }, + async 'ddns-now'() { + try { await rpc('shareos.web', 'ddns_update'); toast('Wird gemeldet …'); setTimeout(async () => { await loadNetExtra(); if (S.route.sub === 'network') renderMain(); }, 5000); } + catch (e) { toast('DynDNS: ' + e.message, true); } + }, + + async 'ca-init'() { + try { await rpc('shareos.web', 'ca_init'); toast('Zertifizierungsstelle angelegt.'); await loadNetExtra(); renderMain(); } + catch (e) { toast('CA: ' + e.message, true); } + }, + + async 'ca-root'() { + try { const r = await rpc('shareos.web', 'ca_pem', { kind: 'root' }); pemDownload(r.pem, 'shareos-ca.crt'); } + catch (e) { toast('CA: ' + e.message, true); } + }, + + async 'ca-web'() { + if (!await confirmDlg('Zertifikat der eigenen CA verwenden?', 'Die Box bekommt ein Zertifikat ihrer eigenen Zertifizierungsstelle für Gerätenamen, .local-Namen und IP-Adresse; es ersetzt das bisherige. Ohne installiertes Stammzertifikat warnt der Browser weiter.', 'Verwenden')) return; + try { await rpc('shareos.web', 'ca_web'); toast('Zertifikat installiert – die Seite verbindet sich neu.'); afterWebRestart(); } + catch (e) { toast('CA: ' + e.message, true); } + }, + + async 'ca-dl'(el) { + try { const r = await rpc('shareos.web', 'ca_pem', { name: el.dataset.id, kind: el.dataset.kind }); pemDownload(r.pem, el.dataset.id + (el.dataset.kind === 'key' ? '.key' : '.crt')); } + catch (e) { toast('Zertifikat: ' + e.message, true); } + }, + + async 'ca-del'(el) { + if (!await confirmDlg('Zertifikat löschen?', `Das Zertifikat „${el.dataset.id}“ und sein Schlüssel werden von der Box gelöscht. Geräte, die es schon nutzen, behalten ihre Kopie.`, 'Löschen', true)) return; + try { await rpc('shareos.web', 'ca_delete', { name: el.dataset.id }); toast('Gelöscht.'); await loadNetExtra(); renderMain(); } + catch (e) { toast('Zertifikat: ' + e.message, true); } + }, + + async 'acme-off'() { + if (!await confirmDlg('Let\'s Encrypt abschalten?', 'Die Box nutzt danach wieder ein selbstsigniertes Zertifikat.', 'Abschalten')) return; + try { await rpc('shareos.web', 'acme_off'); toast('Abgeschaltet – die Seite verbindet sich neu.'); await loadNetExtra(); afterWebRestart(); } + catch (e) { toast('Let\'s Encrypt: ' + e.message, true); } + }, + async 'cert-self'() { if (!await confirmDlg('Neues selbstsigniertes Zertifikat?', 'Ein neues Zertifikat für den aktuellen Namen und die aktuelle IP wird erstellt und ersetzt das bisherige. Browser zeigen danach einmalig wieder eine Warnung.', 'Erstellen')) return; try { const r = await rpc('shareos.web', 'selfsigned'); S.web.cert = r.cert; toast('Zertifikat erstellt.'); afterWebRestart(); } diff --git a/src/shareos-core/www/i18n/ar.json b/src/shareos-core/www/i18n/ar.json index f79dcb3..d046922 100644 --- a/src/shareos-core/www/i18n/ar.json +++ b/src/shareos-core/www/i18n/ar.json @@ -1274,7 +1274,63 @@ "Keine Zeiten eingerichtet.": "لم تُضبط أوقات.", "GERÄT & ZUGANG": "الجهاز والوصول", "Mit dem Stern wird eine App zum Favoriten – Favoriten stehen oben auf „Mein ShareOS“.": "النجمة تجعل التطبيق مفضلاً – تظهر المفضلة أعلى «ShareOS الخاص بي».", - "Alle Hinweise anzeigen": "عرض كل التنبيهات" + "Alle Hinweise anzeigen": "عرض كل التنبيهات", + "EIGENE ZERTIFIKATE": "شهادات خاصة", + "DynDNS aktualisieren": "تحديث DNS الديناميكي", + "API-TOKEN (ZONE:LESEN, DNS:BEARBEITEN)": "رمز API (ZONE:READ، DNS:EDIT)", + "UPDATE-ADRESSE ({host} {ip} {ip6})": "عنوان التحديث ({host} {ip} {ip6})", + "Eigene Adresse (dyndns2)": "عنوان مخصص (dyndns2)", + "– wählen –": "– اختر –", + "gespeichert – leer lassen behält ihn": "محفوظ – اتركه فارغًا للإبقاء عليه", + "Die Box meldet ihre öffentliche Adresse alle 5 Minuten, wenn sie sich geändert hat. Viele Anschlüsse (DS-Lite, Glasfaser, Mobilfunk) haben keine eigene IPv4-Adresse – dann IPv6 nehmen. Für Zugriff von außen muss der Router den Port weiterleiten (bzw. bei IPv6 freigeben).": "يُبلغ الجهاز عن عنوانه العام كل 5 دقائق عند تغيّره. كثير من الاتصالات (DS-Lite، الألياف، الجوال) ليس لها عنوان IPv4 خاص – استخدم حينها IPv6. للوصول من الخارج يجب أن يعيد الموجّه توجيه المنفذ (أو يفتحه لـ IPv6).", + "Zuletzt gemeldet": "آخر إبلاغ", + "Zuletzt geprüft": "آخر فحص", + "Noch keine Meldung – kommt gleich.": "لم يُبلَّغ بعد – قريبًا.", + "DynDNS ist aus.": "DNS الديناميكي متوقف.", + "ZERTIFIZIERUNGSSTELLE (CA)": "جهة إصدار الشهادات (CA)", + "diese Box nutzt sie": "يستخدمها هذا الجهاز", + "Fingerabdruck": "البصمة", + "Stammzertifikat": "الشهادة الجذرية", + "Zertifikat der Box neu ausstellen": "إعادة إصدار شهادة الجهاز", + "Für diese Box verwenden": "استخدام لهذا الجهاز", + "Das Stammzertifikat einmal auf PC und Handy als vertrauenswürdig installieren (Windows: Doppelklick → „Vertrauenswürdige Stammzertifizierungsstellen“, Android/iOS: Einstellungen → Sicherheit). Danach sind alle Zertifikate dieser CA ohne Warnung – für die Box und andere Geräte im Heimnetz.": "ثبّت الشهادة الجذرية مرة واحدة كموثوقة على الحاسوب والهاتف (Windows: نقر مزدوج ← «جهات الترخيص الجذرية الموثوقة»، Android/iOS: الإعدادات ← الأمان). بعدها تعمل كل شهادات هذه الجهة دون تحذير – للجهاز ولأجهزة أخرى في الشبكة المنزلية.", + "Eine eigene Zertifizierungsstelle stellt Zertifikate für die Box und andere Geräte im Heimnetz aus (z. B.": "تصدر جهة شهادات خاصة شهادات للجهاز ولأجهزة أخرى في الشبكة المنزلية (مثل", + "). Ihr Stammzertifikat wird einmal auf den Geräten installiert – danach gibt es keine Zertifikatswarnungen mehr.": "). تُثبَّت شهادتها الجذرية مرة واحدة على الأجهزة – وبعدها لا تظهر تحذيرات الشهادات.", + "Zertifizierungsstelle anlegen": "إنشاء جهة الشهادات", + "Zertifikat wird geholt – das dauert bis zu 3 Minuten …": "جارٍ جلب الشهادة – يستغرق حتى 3 دقائق …", + "Wird täglich geprüft und 30 Tage vor Ablauf erneuert.": "يُفحص يوميًا ويُجدَّد قبل انتهائه بـ 30 يومًا.", + "Abschalten": "إيقاف", + "Ein Zertifikat für": "شهادة لـ", + ", dem alle Browser vertrauen. Die Prüfung läuft über einen DNS-Eintrag beim DynDNS-Anbieter – kein Port muss offen sein.": " تثق بها كل المتصفحات. يتم التحقق عبر سجل DNS لدى مزوّد DNS الديناميكي – لا حاجة لفتح أي منفذ.", + "E-MAIL (OPTIONAL, FÜR HINWEISE VON LET'S ENCRYPT)": "البريد الإلكتروني (اختياري، لإشعارات LET'S ENCRYPT)", + "Testmodus (Test-CA, Browser warnen)": "وضع الاختبار (جهة اختبار، تحذّر المتصفحات)", + "Zertifikat holen": "جلب الشهادة", + "Braucht DynDNS mit DuckDNS oder Cloudflare (oben).": "يتطلب DNS ديناميكيًا مع DuckDNS أو Cloudflare (أعلاه).", + "ZERTIFIKAT FÜR EIN ANDERES GERÄT": "شهادة لجهاز آخر", + "HOSTNAMEN UND IP-ADRESSEN (KOMMA-GETRENNT)": "أسماء المضيفين وعناوين IP (مفصولة بفواصل)", + "Gültig 825 Tage. Auch Platzhalter wie": "صالحة 825 يومًا. يمكن أيضًا استخدام أحرف بدل مثل", + "sind möglich. Zertifikat und Schlüssel herunterladen und im Gerät eintragen.": ". نزّل الشهادة والمفتاح وأدخلهما في الجهاز.", + "Ausstellen": "إصدار", + "HOSTNAMEN": "أسماء المضيفين", + "GÜLTIG BIS": "صالحة حتى", + "Schlüssel": "المفتاح", + "eigene CA": "جهة خاصة", + "aktiv (Test)": "نشط (اختبار)", + "Bitte Anbieter und Namen angeben.": "يرجى إدخال المزوّد والاسم.", + "Bitte Name und Hostnamen angeben.": "يرجى إدخال الاسم وأسماء المضيفين.", + "Gespeichert – die Adresse wird gemeldet.": "تم الحفظ – يجري الإبلاغ عن العنوان.", + "Wird gemeldet …": "جارٍ الإبلاغ …", + "Zertifizierungsstelle angelegt.": "تم إنشاء جهة الشهادات.", + "Zertifikat ausgestellt.": "تم إصدار الشهادة.", + "Zertifikat der eigenen CA verwenden?": "استخدام شهادة من الجهة الخاصة؟", + "Die Box bekommt ein Zertifikat ihrer eigenen Zertifizierungsstelle für Gerätenamen, .local-Namen und IP-Adresse; es ersetzt das bisherige. Ohne installiertes Stammzertifikat warnt der Browser weiter.": "يحصل الجهاز على شهادة من جهته الخاصة لاسم الجهاز واسم ‎.local وعنوان IP؛ وتحل محل الحالية. دون تثبيت الشهادة الجذرية يستمر المتصفح في التحذير.", + "Verwenden": "استخدام", + "Zertifikat installiert – die Seite verbindet sich neu.": "تم تثبيت الشهادة – تعيد الصفحة الاتصال.", + "Zertifikat löschen?": "حذف الشهادة؟", + "Let's Encrypt abschalten?": "إيقاف Let's Encrypt؟", + "Die Box nutzt danach wieder ein selbstsigniertes Zertifikat.": "يعود الجهاز بعدها إلى شهادة موقّعة ذاتيًا.", + "Abgeschaltet – die Seite verbindet sich neu.": "تم الإيقاف – تعيد الصفحة الاتصال.", + "Let's-Encrypt-Zertifikat aktiv – die Seite verbindet sich neu.": "شهادة Let's Encrypt نشطة – تعيد الصفحة الاتصال." }, "patterns": [ [ @@ -2572,6 +2628,10 @@ [ "^aus ([0-9:]+)$", "إيقاف $1" + ], + [ + "^Das Zertifikat „(.+)“ und sein Schlüssel werden von der Box gelöscht\\. Geräte, die es schon nutzen, behalten ihre Kopie\\.$", + "تُحذف الشهادة «$1» ومفتاحها من الجهاز. تحتفظ الأجهزة التي تستخدمها بنسختها." ] ], "words": [ diff --git a/src/shareos-core/www/i18n/bn.json b/src/shareos-core/www/i18n/bn.json index b0c402a..d9ff109 100644 --- a/src/shareos-core/www/i18n/bn.json +++ b/src/shareos-core/www/i18n/bn.json @@ -1274,7 +1274,63 @@ "Keine Zeiten eingerichtet.": "কোনো সময় সেট করা নেই।", "GERÄT & ZUGANG": "ডিভাইস ও অ্যাক্সেস", "Mit dem Stern wird eine App zum Favoriten – Favoriten stehen oben auf „Mein ShareOS“.": "তারকা দিয়ে অ্যাপ প্রিয় হয় – প্রিয়গুলি \"আমার ShareOS\"-এর উপরে দেখায়।", - "Alle Hinweise anzeigen": "সব বিজ্ঞপ্তি দেখান" + "Alle Hinweise anzeigen": "সব বিজ্ঞপ্তি দেখান", + "EIGENE ZERTIFIKATE": "নিজস্ব সার্টিফিকেট", + "DynDNS aktualisieren": "ডায়নামিক DNS হালনাগাদ করুন", + "API-TOKEN (ZONE:LESEN, DNS:BEARBEITEN)": "API টোকেন (ZONE:READ, DNS:EDIT)", + "UPDATE-ADRESSE ({host} {ip} {ip6})": "হালনাগাদ ঠিকানা ({host} {ip} {ip6})", + "Eigene Adresse (dyndns2)": "নিজস্ব ঠিকানা (dyndns2)", + "– wählen –": "– বেছে নিন –", + "gespeichert – leer lassen behält ihn": "সংরক্ষিত – ফাঁকা রাখলে আগেরটাই থাকবে", + "Die Box meldet ihre öffentliche Adresse alle 5 Minuten, wenn sie sich geändert hat. Viele Anschlüsse (DS-Lite, Glasfaser, Mobilfunk) haben keine eigene IPv4-Adresse – dann IPv6 nehmen. Für Zugriff von außen muss der Router den Port weiterleiten (bzw. bei IPv6 freigeben).": "ঠিকানা বদলালে বক্স প্রতি ৫ মিনিটে তার পাবলিক ঠিকানা জানায়। অনেক সংযোগের (DS-Lite, ফাইবার, মোবাইল) নিজস্ব IPv4 ঠিকানা থাকে না – তখন IPv6 নিন। বাইরে থেকে প্রবেশের জন্য রাউটারকে পোর্ট ফরওয়ার্ড করতে হবে (বা IPv6-এর জন্য খুলতে হবে)।", + "Zuletzt gemeldet": "শেষ জানানো", + "Zuletzt geprüft": "শেষ যাচাই", + "Noch keine Meldung – kommt gleich.": "এখনো জানানো হয়নি – এখনই হবে।", + "DynDNS ist aus.": "ডায়নামিক DNS বন্ধ।", + "ZERTIFIZIERUNGSSTELLE (CA)": "সার্টিফিকেট কর্তৃপক্ষ (CA)", + "diese Box nutzt sie": "এই বক্স এটি ব্যবহার করে", + "Fingerabdruck": "ফিঙ্গারপ্রিন্ট", + "Stammzertifikat": "রুট সার্টিফিকেট", + "Zertifikat der Box neu ausstellen": "বক্সের সার্টিফিকেট আবার ইস্যু করুন", + "Für diese Box verwenden": "এই বক্সের জন্য ব্যবহার করুন", + "Das Stammzertifikat einmal auf PC und Handy als vertrauenswürdig installieren (Windows: Doppelklick → „Vertrauenswürdige Stammzertifizierungsstellen“, Android/iOS: Einstellungen → Sicherheit). Danach sind alle Zertifikate dieser CA ohne Warnung – für die Box und andere Geräte im Heimnetz.": "রুট সার্টিফিকেট একবার পিসি ও ফোনে বিশ্বস্ত হিসেবে ইনস্টল করুন (Windows: ডাবল-ক্লিক → “Trusted Root Certification Authorities”, Android/iOS: সেটিংস → নিরাপত্তা)। এরপর এই CA-র সব সার্টিফিকেট সতর্কবার্তা ছাড়াই চলে – বক্স ও হোম নেটওয়ার্কের অন্য ডিভাইসের জন্য।", + "Eine eigene Zertifizierungsstelle stellt Zertifikate für die Box und andere Geräte im Heimnetz aus (z. B.": "নিজস্ব সার্টিফিকেট কর্তৃপক্ষ বক্স ও হোম নেটওয়ার্কের অন্য ডিভাইসের জন্য সার্টিফিকেট ইস্যু করে (যেমন", + "). Ihr Stammzertifikat wird einmal auf den Geräten installiert – danach gibt es keine Zertifikatswarnungen mehr.": "). এর রুট সার্টিফিকেট ডিভাইসে একবার ইনস্টল করা হয় – এরপর আর সার্টিফিকেট সতর্কবার্তা আসে না।", + "Zertifizierungsstelle anlegen": "সার্টিফিকেট কর্তৃপক্ষ তৈরি করুন", + "Zertifikat wird geholt – das dauert bis zu 3 Minuten …": "সার্টিফিকেট আনা হচ্ছে – এতে ৩ মিনিট পর্যন্ত লাগে …", + "Wird täglich geprüft und 30 Tage vor Ablauf erneuert.": "প্রতিদিন যাচাই হয় এবং মেয়াদ শেষের ৩০ দিন আগে নবায়ন হয়।", + "Abschalten": "বন্ধ করুন", + "Ein Zertifikat für": "এর জন্য সার্টিফিকেট:", + ", dem alle Browser vertrauen. Die Prüfung läuft über einen DNS-Eintrag beim DynDNS-Anbieter – kein Port muss offen sein.": ", যা সব ব্রাউজার বিশ্বাস করে। যাচাই হয় ডায়নামিক DNS প্রদানকারীর DNS রেকর্ডের মাধ্যমে – কোনো পোর্ট খুলতে হয় না।", + "E-MAIL (OPTIONAL, FÜR HINWEISE VON LET'S ENCRYPT)": "ই-মেইল (ঐচ্ছিক, LET'S ENCRYPT-এর বিজ্ঞপ্তির জন্য)", + "Testmodus (Test-CA, Browser warnen)": "টেস্ট মোড (টেস্ট CA, ব্রাউজার সতর্ক করবে)", + "Zertifikat holen": "সার্টিফিকেট আনুন", + "Braucht DynDNS mit DuckDNS oder Cloudflare (oben).": "DuckDNS বা Cloudflare সহ ডায়নামিক DNS লাগবে (উপরে)।", + "ZERTIFIKAT FÜR EIN ANDERES GERÄT": "অন্য ডিভাইসের জন্য সার্টিফিকেট", + "HOSTNAMEN UND IP-ADRESSEN (KOMMA-GETRENNT)": "হোস্টনাম ও IP ঠিকানা (কমা দিয়ে আলাদা)", + "Gültig 825 Tage. Auch Platzhalter wie": "৮২৫ দিন বৈধ। ওয়াইল্ডকার্ড যেমন", + "sind möglich. Zertifikat und Schlüssel herunterladen und im Gerät eintragen.": "-ও সম্ভব। সার্টিফিকেট ও কী ডাউনলোড করে ডিভাইসে বসান।", + "Ausstellen": "ইস্যু করুন", + "HOSTNAMEN": "হোস্টনাম", + "GÜLTIG BIS": "বৈধ পর্যন্ত", + "Schlüssel": "কী", + "eigene CA": "নিজস্ব CA", + "aktiv (Test)": "সক্রিয় (টেস্ট)", + "Bitte Anbieter und Namen angeben.": "অনুগ্রহ করে প্রদানকারী ও নাম দিন।", + "Bitte Name und Hostnamen angeben.": "অনুগ্রহ করে নাম ও হোস্টনাম দিন।", + "Gespeichert – die Adresse wird gemeldet.": "সংরক্ষিত – ঠিকানা জানানো হচ্ছে।", + "Wird gemeldet …": "জানানো হচ্ছে …", + "Zertifizierungsstelle angelegt.": "সার্টিফিকেট কর্তৃপক্ষ তৈরি হয়েছে।", + "Zertifikat ausgestellt.": "সার্টিফিকেট ইস্যু হয়েছে।", + "Zertifikat der eigenen CA verwenden?": "নিজস্ব CA-র সার্টিফিকেট ব্যবহার করবেন?", + "Die Box bekommt ein Zertifikat ihrer eigenen Zertifizierungsstelle für Gerätenamen, .local-Namen und IP-Adresse; es ersetzt das bisherige. Ohne installiertes Stammzertifikat warnt der Browser weiter.": "বক্স তার নিজস্ব CA থেকে ডিভাইসের নাম, .local নাম ও IP ঠিকানার জন্য সার্টিফিকেট পাবে; এটি বর্তমানটিকে বদলে দেবে। রুট সার্টিফিকেট ইনস্টল না থাকলে ব্রাউজার সতর্ক করতেই থাকবে।", + "Verwenden": "ব্যবহার করুন", + "Zertifikat installiert – die Seite verbindet sich neu.": "সার্টিফিকেট ইনস্টল হয়েছে – পেজ আবার সংযুক্ত হচ্ছে।", + "Zertifikat löschen?": "সার্টিফিকেট মুছবেন?", + "Let's Encrypt abschalten?": "Let's Encrypt বন্ধ করবেন?", + "Die Box nutzt danach wieder ein selbstsigniertes Zertifikat.": "এরপর বক্স আবার স্ব-স্বাক্ষরিত সার্টিফিকেট ব্যবহার করবে।", + "Abgeschaltet – die Seite verbindet sich neu.": "বন্ধ করা হয়েছে – পেজ আবার সংযুক্ত হচ্ছে।", + "Let's-Encrypt-Zertifikat aktiv – die Seite verbindet sich neu.": "Let's Encrypt সার্টিফিকেট সক্রিয় – পেজ আবার সংযুক্ত হচ্ছে।" }, "patterns": [ [ @@ -2572,6 +2628,10 @@ [ "^aus ([0-9:]+)$", "বন্ধ $1" + ], + [ + "^Das Zertifikat „(.+)“ und sein Schlüssel werden von der Box gelöscht\\. Geräte, die es schon nutzen, behalten ihre Kopie\\.$", + "সার্টিফিকেট “$1” ও তার কী বক্স থেকে মোছা হবে। যেসব ডিভাইস এটি ব্যবহার করছে, তারা নিজেদের কপি রাখবে।" ] ], "words": [ diff --git a/src/shareos-core/www/i18n/en.json b/src/shareos-core/www/i18n/en.json index 0e9102d..b020b3b 100644 --- a/src/shareos-core/www/i18n/en.json +++ b/src/shareos-core/www/i18n/en.json @@ -1274,7 +1274,63 @@ "Keine Zeiten eingerichtet.": "No times set up.", "GERÄT & ZUGANG": "DEVICE & ACCESS", "Mit dem Stern wird eine App zum Favoriten – Favoriten stehen oben auf „Mein ShareOS“.": "The star makes an app a favourite – favourites are shown at the top of “My ShareOS”.", - "Alle Hinweise anzeigen": "Show all notices" + "Alle Hinweise anzeigen": "Show all notices", + "EIGENE ZERTIFIKATE": "OWN CERTIFICATES", + "DynDNS aktualisieren": "Update dynamic DNS", + "API-TOKEN (ZONE:LESEN, DNS:BEARBEITEN)": "API TOKEN (ZONE:READ, DNS:EDIT)", + "UPDATE-ADRESSE ({host} {ip} {ip6})": "UPDATE URL ({host} {ip} {ip6})", + "Eigene Adresse (dyndns2)": "Own URL (dyndns2)", + "– wählen –": "– choose –", + "gespeichert – leer lassen behält ihn": "saved – leave empty to keep it", + "Die Box meldet ihre öffentliche Adresse alle 5 Minuten, wenn sie sich geändert hat. Viele Anschlüsse (DS-Lite, Glasfaser, Mobilfunk) haben keine eigene IPv4-Adresse – dann IPv6 nehmen. Für Zugriff von außen muss der Router den Port weiterleiten (bzw. bei IPv6 freigeben).": "The box reports its public address every 5 minutes when it has changed. Many connections (DS-Lite, fibre, mobile) have no IPv4 address of their own – then use IPv6. For access from outside, the router has to forward the port (or open it for IPv6).", + "Zuletzt gemeldet": "Last reported", + "Zuletzt geprüft": "Last checked", + "Noch keine Meldung – kommt gleich.": "Not reported yet – coming shortly.", + "DynDNS ist aus.": "Dynamic DNS is off.", + "ZERTIFIZIERUNGSSTELLE (CA)": "CERTIFICATE AUTHORITY (CA)", + "diese Box nutzt sie": "this box uses it", + "Fingerabdruck": "Fingerprint", + "Stammzertifikat": "Root certificate", + "Zertifikat der Box neu ausstellen": "Reissue the box certificate", + "Für diese Box verwenden": "Use for this box", + "Das Stammzertifikat einmal auf PC und Handy als vertrauenswürdig installieren (Windows: Doppelklick → „Vertrauenswürdige Stammzertifizierungsstellen“, Android/iOS: Einstellungen → Sicherheit). Danach sind alle Zertifikate dieser CA ohne Warnung – für die Box und andere Geräte im Heimnetz.": "Install the root certificate once on PC and phone as trusted (Windows: double-click → “Trusted Root Certification Authorities”, Android/iOS: Settings → Security). After that all certificates of this CA work without a warning – for the box and other devices in the home network.", + "Eine eigene Zertifizierungsstelle stellt Zertifikate für die Box und andere Geräte im Heimnetz aus (z. B.": "An own certificate authority issues certificates for the box and other devices in the home network (e.g.", + "). Ihr Stammzertifikat wird einmal auf den Geräten installiert – danach gibt es keine Zertifikatswarnungen mehr.": "). Its root certificate is installed once on the devices – after that there are no more certificate warnings.", + "Zertifizierungsstelle anlegen": "Create certificate authority", + "Zertifikat wird geholt – das dauert bis zu 3 Minuten …": "Getting the certificate – this takes up to 3 minutes …", + "Wird täglich geprüft und 30 Tage vor Ablauf erneuert.": "Checked daily and renewed 30 days before it expires.", + "Abschalten": "Switch off", + "Ein Zertifikat für": "A certificate for", + ", dem alle Browser vertrauen. Die Prüfung läuft über einen DNS-Eintrag beim DynDNS-Anbieter – kein Port muss offen sein.": ", trusted by all browsers. The check runs through a DNS record at the dynamic DNS provider – no port has to be open.", + "E-MAIL (OPTIONAL, FÜR HINWEISE VON LET'S ENCRYPT)": "E-MAIL (OPTIONAL, FOR NOTICES FROM LET'S ENCRYPT)", + "Testmodus (Test-CA, Browser warnen)": "Test mode (test CA, browsers warn)", + "Zertifikat holen": "Get certificate", + "Braucht DynDNS mit DuckDNS oder Cloudflare (oben).": "Needs dynamic DNS with DuckDNS or Cloudflare (above).", + "ZERTIFIKAT FÜR EIN ANDERES GERÄT": "CERTIFICATE FOR ANOTHER DEVICE", + "HOSTNAMEN UND IP-ADRESSEN (KOMMA-GETRENNT)": "HOST NAMES AND IP ADDRESSES (COMMA-SEPARATED)", + "Gültig 825 Tage. Auch Platzhalter wie": "Valid for 825 days. Wildcards such as", + "sind möglich. Zertifikat und Schlüssel herunterladen und im Gerät eintragen.": "are possible too. Download certificate and key and enter them in the device.", + "Ausstellen": "Issue", + "HOSTNAMEN": "HOST NAMES", + "GÜLTIG BIS": "VALID UNTIL", + "Schlüssel": "Key", + "eigene CA": "own CA", + "aktiv (Test)": "active (test)", + "Bitte Anbieter und Namen angeben.": "Please enter provider and name.", + "Bitte Name und Hostnamen angeben.": "Please enter name and host names.", + "Gespeichert – die Adresse wird gemeldet.": "Saved – the address is being reported.", + "Wird gemeldet …": "Reporting …", + "Zertifizierungsstelle angelegt.": "Certificate authority created.", + "Zertifikat ausgestellt.": "Certificate issued.", + "Zertifikat der eigenen CA verwenden?": "Use a certificate of the own CA?", + "Die Box bekommt ein Zertifikat ihrer eigenen Zertifizierungsstelle für Gerätenamen, .local-Namen und IP-Adresse; es ersetzt das bisherige. Ohne installiertes Stammzertifikat warnt der Browser weiter.": "The box gets a certificate of its own CA for the device name, the .local name and the IP address; it replaces the current one. Without the root certificate installed, the browser keeps warning.", + "Verwenden": "Use", + "Zertifikat installiert – die Seite verbindet sich neu.": "Certificate installed – the page reconnects.", + "Zertifikat löschen?": "Delete certificate?", + "Let's Encrypt abschalten?": "Switch off Let's Encrypt?", + "Die Box nutzt danach wieder ein selbstsigniertes Zertifikat.": "The box then uses a self-signed certificate again.", + "Abgeschaltet – die Seite verbindet sich neu.": "Switched off – the page reconnects.", + "Let's-Encrypt-Zertifikat aktiv – die Seite verbindet sich neu.": "Let's Encrypt certificate active – the page reconnects." }, "patterns": [ [ @@ -2572,6 +2628,10 @@ [ "^aus ([0-9:]+)$", "off $1" + ], + [ + "^Das Zertifikat „(.+)“ und sein Schlüssel werden von der Box gelöscht\\. Geräte, die es schon nutzen, behalten ihre Kopie\\.$", + "The certificate “$1” and its key are deleted from the box. Devices that already use it keep their copy." ] ], "words": [ diff --git a/src/shareos-core/www/i18n/es.json b/src/shareos-core/www/i18n/es.json index 03f6f19..9062306 100644 --- a/src/shareos-core/www/i18n/es.json +++ b/src/shareos-core/www/i18n/es.json @@ -1274,7 +1274,63 @@ "Keine Zeiten eingerichtet.": "No hay horas configuradas.", "GERÄT & ZUGANG": "EQUIPO Y ACCESO", "Mit dem Stern wird eine App zum Favoriten – Favoriten stehen oben auf „Mein ShareOS“.": "La estrella convierte una app en favorita: las favoritas aparecen arriba en «Mi ShareOS».", - "Alle Hinweise anzeigen": "Mostrar todos los avisos" + "Alle Hinweise anzeigen": "Mostrar todos los avisos", + "EIGENE ZERTIFIKATE": "CERTIFICADOS PROPIOS", + "DynDNS aktualisieren": "Actualizar DNS dinámico", + "API-TOKEN (ZONE:LESEN, DNS:BEARBEITEN)": "TOKEN DE API (ZONA:LEER, DNS:EDITAR)", + "UPDATE-ADRESSE ({host} {ip} {ip6})": "URL DE ACTUALIZACIÓN ({host} {ip} {ip6})", + "Eigene Adresse (dyndns2)": "URL propia (dyndns2)", + "– wählen –": "– elegir –", + "gespeichert – leer lassen behält ihn": "guardado – déjalo vacío para conservarlo", + "Die Box meldet ihre öffentliche Adresse alle 5 Minuten, wenn sie sich geändert hat. Viele Anschlüsse (DS-Lite, Glasfaser, Mobilfunk) haben keine eigene IPv4-Adresse – dann IPv6 nehmen. Für Zugriff von außen muss der Router den Port weiterleiten (bzw. bei IPv6 freigeben).": "La caja comunica su dirección pública cada 5 minutos cuando ha cambiado. Muchas conexiones (DS-Lite, fibra, móvil) no tienen dirección IPv4 propia – usa entonces IPv6. Para acceder desde fuera, el router debe reenviar el puerto (o abrirlo en IPv6).", + "Zuletzt gemeldet": "Último aviso", + "Zuletzt geprüft": "Última comprobación", + "Noch keine Meldung – kommt gleich.": "Aún sin aviso – llega en breve.", + "DynDNS ist aus.": "El DNS dinámico está desactivado.", + "ZERTIFIZIERUNGSSTELLE (CA)": "AUTORIDAD DE CERTIFICACIÓN (CA)", + "diese Box nutzt sie": "esta caja la usa", + "Fingerabdruck": "Huella", + "Stammzertifikat": "Certificado raíz", + "Zertifikat der Box neu ausstellen": "Reemitir el certificado de la caja", + "Für diese Box verwenden": "Usar para esta caja", + "Das Stammzertifikat einmal auf PC und Handy als vertrauenswürdig installieren (Windows: Doppelklick → „Vertrauenswürdige Stammzertifizierungsstellen“, Android/iOS: Einstellungen → Sicherheit). Danach sind alle Zertifikate dieser CA ohne Warnung – für die Box und andere Geräte im Heimnetz.": "Instala una vez el certificado raíz como de confianza en el PC y el móvil (Windows: doble clic → «Entidades de certificación raíz de confianza», Android/iOS: Ajustes → Seguridad). Después, todos los certificados de esta CA funcionan sin aviso – para la caja y otros dispositivos de la red doméstica.", + "Eine eigene Zertifizierungsstelle stellt Zertifikate für die Box und andere Geräte im Heimnetz aus (z. B.": "Una autoridad de certificación propia emite certificados para la caja y otros dispositivos de la red doméstica (p. ej.", + "). Ihr Stammzertifikat wird einmal auf den Geräten installiert – danach gibt es keine Zertifikatswarnungen mehr.": "). Su certificado raíz se instala una vez en los dispositivos – después ya no hay avisos de certificado.", + "Zertifizierungsstelle anlegen": "Crear autoridad de certificación", + "Zertifikat wird geholt – das dauert bis zu 3 Minuten …": "Obteniendo el certificado – tarda hasta 3 minutos …", + "Wird täglich geprüft und 30 Tage vor Ablauf erneuert.": "Se comprueba a diario y se renueva 30 días antes de caducar.", + "Abschalten": "Desactivar", + "Ein Zertifikat für": "Un certificado para", + ", dem alle Browser vertrauen. Die Prüfung läuft über einen DNS-Eintrag beim DynDNS-Anbieter – kein Port muss offen sein.": ", en el que confían todos los navegadores. La comprobación se hace con un registro DNS en el proveedor de DNS dinámico – no hace falta abrir ningún puerto.", + "E-MAIL (OPTIONAL, FÜR HINWEISE VON LET'S ENCRYPT)": "CORREO (OPCIONAL, PARA AVISOS DE LET'S ENCRYPT)", + "Testmodus (Test-CA, Browser warnen)": "Modo de prueba (CA de prueba, los navegadores avisan)", + "Zertifikat holen": "Obtener certificado", + "Braucht DynDNS mit DuckDNS oder Cloudflare (oben).": "Necesita DNS dinámico con DuckDNS o Cloudflare (arriba).", + "ZERTIFIKAT FÜR EIN ANDERES GERÄT": "CERTIFICADO PARA OTRO DISPOSITIVO", + "HOSTNAMEN UND IP-ADRESSEN (KOMMA-GETRENNT)": "NOMBRES DE HOST Y DIRECCIONES IP (SEPARADOS POR COMAS)", + "Gültig 825 Tage. Auch Platzhalter wie": "Válido 825 días. También comodines como", + "sind möglich. Zertifikat und Schlüssel herunterladen und im Gerät eintragen.": "también son posibles. Descarga el certificado y la clave e introdúcelos en el dispositivo.", + "Ausstellen": "Emitir", + "HOSTNAMEN": "NOMBRES DE HOST", + "GÜLTIG BIS": "VÁLIDO HASTA", + "Schlüssel": "Clave", + "eigene CA": "CA propia", + "aktiv (Test)": "activo (prueba)", + "Bitte Anbieter und Namen angeben.": "Indica el proveedor y el nombre.", + "Bitte Name und Hostnamen angeben.": "Indica el nombre y los nombres de host.", + "Gespeichert – die Adresse wird gemeldet.": "Guardado – se está comunicando la dirección.", + "Wird gemeldet …": "Comunicando …", + "Zertifizierungsstelle angelegt.": "Autoridad de certificación creada.", + "Zertifikat ausgestellt.": "Certificado emitido.", + "Zertifikat der eigenen CA verwenden?": "¿Usar un certificado de la CA propia?", + "Die Box bekommt ein Zertifikat ihrer eigenen Zertifizierungsstelle für Gerätenamen, .local-Namen und IP-Adresse; es ersetzt das bisherige. Ohne installiertes Stammzertifikat warnt der Browser weiter.": "La caja recibe un certificado de su propia CA para el nombre del dispositivo, el nombre .local y la dirección IP; sustituye al actual. Sin el certificado raíz instalado, el navegador sigue avisando.", + "Verwenden": "Usar", + "Zertifikat installiert – die Seite verbindet sich neu.": "Certificado instalado – la página se reconecta.", + "Zertifikat löschen?": "¿Eliminar el certificado?", + "Let's Encrypt abschalten?": "¿Desactivar Let's Encrypt?", + "Die Box nutzt danach wieder ein selbstsigniertes Zertifikat.": "La caja vuelve a usar un certificado autofirmado.", + "Abgeschaltet – die Seite verbindet sich neu.": "Desactivado – la página se reconecta.", + "Let's-Encrypt-Zertifikat aktiv – die Seite verbindet sich neu.": "Certificado de Let's Encrypt activo – la página se reconecta." }, "patterns": [ [ @@ -2572,6 +2628,10 @@ [ "^aus ([0-9:]+)$", "apagado $1" + ], + [ + "^Das Zertifikat „(.+)“ und sein Schlüssel werden von der Box gelöscht\\. Geräte, die es schon nutzen, behalten ihre Kopie\\.$", + "El certificado «$1» y su clave se eliminan de la caja. Los dispositivos que ya lo usan conservan su copia." ] ], "words": [ diff --git a/src/shareos-core/www/i18n/fr.json b/src/shareos-core/www/i18n/fr.json index 53480eb..049743f 100644 --- a/src/shareos-core/www/i18n/fr.json +++ b/src/shareos-core/www/i18n/fr.json @@ -1274,7 +1274,63 @@ "Keine Zeiten eingerichtet.": "Aucune heure définie.", "GERÄT & ZUGANG": "APPAREIL & ACCÈS", "Mit dem Stern wird eine App zum Favoriten – Favoriten stehen oben auf „Mein ShareOS“.": "L’étoile fait d’une app un favori – les favoris apparaissent en haut de « Mon ShareOS ».", - "Alle Hinweise anzeigen": "Afficher toutes les notifications" + "Alle Hinweise anzeigen": "Afficher toutes les notifications", + "EIGENE ZERTIFIKATE": "CERTIFICATS PROPRES", + "DynDNS aktualisieren": "Mettre à jour le DNS dynamique", + "API-TOKEN (ZONE:LESEN, DNS:BEARBEITEN)": "JETON API (ZONE:LECTURE, DNS:MODIFICATION)", + "UPDATE-ADRESSE ({host} {ip} {ip6})": "URL DE MISE À JOUR ({host} {ip} {ip6})", + "Eigene Adresse (dyndns2)": "URL personnalisée (dyndns2)", + "– wählen –": "– choisir –", + "gespeichert – leer lassen behält ihn": "enregistré – laisser vide pour le conserver", + "Die Box meldet ihre öffentliche Adresse alle 5 Minuten, wenn sie sich geändert hat. Viele Anschlüsse (DS-Lite, Glasfaser, Mobilfunk) haben keine eigene IPv4-Adresse – dann IPv6 nehmen. Für Zugriff von außen muss der Router den Port weiterleiten (bzw. bei IPv6 freigeben).": "La box signale son adresse publique toutes les 5 minutes lorsqu'elle a changé. Beaucoup de connexions (DS-Lite, fibre, mobile) n'ont pas d'adresse IPv4 propre – utilisez alors IPv6. Pour un accès depuis l'extérieur, le routeur doit rediriger le port (ou l'ouvrir en IPv6).", + "Zuletzt gemeldet": "Dernier signalement", + "Zuletzt geprüft": "Dernière vérification", + "Noch keine Meldung – kommt gleich.": "Pas encore signalé – cela arrive.", + "DynDNS ist aus.": "Le DNS dynamique est désactivé.", + "ZERTIFIZIERUNGSSTELLE (CA)": "AUTORITÉ DE CERTIFICATION (CA)", + "diese Box nutzt sie": "utilisée par cette box", + "Fingerabdruck": "Empreinte", + "Stammzertifikat": "Certificat racine", + "Zertifikat der Box neu ausstellen": "Réémettre le certificat de la box", + "Für diese Box verwenden": "Utiliser pour cette box", + "Das Stammzertifikat einmal auf PC und Handy als vertrauenswürdig installieren (Windows: Doppelklick → „Vertrauenswürdige Stammzertifizierungsstellen“, Android/iOS: Einstellungen → Sicherheit). Danach sind alle Zertifikate dieser CA ohne Warnung – für die Box und andere Geräte im Heimnetz.": "Installez une fois le certificat racine comme fiable sur le PC et le téléphone (Windows : double-clic → « Autorités de certification racines de confiance », Android/iOS : Réglages → Sécurité). Ensuite, tous les certificats de cette CA fonctionnent sans avertissement – pour la box et les autres appareils du réseau domestique.", + "Eine eigene Zertifizierungsstelle stellt Zertifikate für die Box und andere Geräte im Heimnetz aus (z. B.": "Une autorité de certification propre émet des certificats pour la box et les autres appareils du réseau domestique (p. ex.", + "). Ihr Stammzertifikat wird einmal auf den Geräten installiert – danach gibt es keine Zertifikatswarnungen mehr.": "). Son certificat racine est installé une fois sur les appareils – ensuite, plus d'avertissements de certificat.", + "Zertifizierungsstelle anlegen": "Créer l'autorité de certification", + "Zertifikat wird geholt – das dauert bis zu 3 Minuten …": "Obtention du certificat – cela prend jusqu'à 3 minutes …", + "Wird täglich geprüft und 30 Tage vor Ablauf erneuert.": "Vérifié chaque jour et renouvelé 30 jours avant expiration.", + "Abschalten": "Désactiver", + "Ein Zertifikat für": "Un certificat pour", + ", dem alle Browser vertrauen. Die Prüfung läuft über einen DNS-Eintrag beim DynDNS-Anbieter – kein Port muss offen sein.": ", reconnu par tous les navigateurs. La vérification passe par un enregistrement DNS chez le fournisseur DNS dynamique – aucun port ne doit être ouvert.", + "E-MAIL (OPTIONAL, FÜR HINWEISE VON LET'S ENCRYPT)": "E-MAIL (FACULTATIF, POUR LES AVIS DE LET'S ENCRYPT)", + "Testmodus (Test-CA, Browser warnen)": "Mode test (CA de test, les navigateurs avertissent)", + "Zertifikat holen": "Obtenir le certificat", + "Braucht DynDNS mit DuckDNS oder Cloudflare (oben).": "Nécessite le DNS dynamique avec DuckDNS ou Cloudflare (ci-dessus).", + "ZERTIFIKAT FÜR EIN ANDERES GERÄT": "CERTIFICAT POUR UN AUTRE APPAREIL", + "HOSTNAMEN UND IP-ADRESSEN (KOMMA-GETRENNT)": "NOMS D'HÔTE ET ADRESSES IP (SÉPARÉS PAR DES VIRGULES)", + "Gültig 825 Tage. Auch Platzhalter wie": "Valable 825 jours. Les jokers comme", + "sind möglich. Zertifikat und Schlüssel herunterladen und im Gerät eintragen.": "sont aussi possibles. Téléchargez le certificat et la clé et saisissez-les dans l'appareil.", + "Ausstellen": "Émettre", + "HOSTNAMEN": "NOMS D'HÔTE", + "GÜLTIG BIS": "VALABLE JUSQU'AU", + "Schlüssel": "Clé", + "eigene CA": "CA propre", + "aktiv (Test)": "actif (test)", + "Bitte Anbieter und Namen angeben.": "Veuillez indiquer le fournisseur et le nom.", + "Bitte Name und Hostnamen angeben.": "Veuillez indiquer le nom et les noms d'hôte.", + "Gespeichert – die Adresse wird gemeldet.": "Enregistré – l'adresse est signalée.", + "Wird gemeldet …": "Signalement …", + "Zertifizierungsstelle angelegt.": "Autorité de certification créée.", + "Zertifikat ausgestellt.": "Certificat émis.", + "Zertifikat der eigenen CA verwenden?": "Utiliser un certificat de la CA propre ?", + "Die Box bekommt ein Zertifikat ihrer eigenen Zertifizierungsstelle für Gerätenamen, .local-Namen und IP-Adresse; es ersetzt das bisherige. Ohne installiertes Stammzertifikat warnt der Browser weiter.": "La box reçoit un certificat de sa propre CA pour le nom de l'appareil, le nom .local et l'adresse IP ; il remplace l'actuel. Sans certificat racine installé, le navigateur continue d'avertir.", + "Verwenden": "Utiliser", + "Zertifikat installiert – die Seite verbindet sich neu.": "Certificat installé – la page se reconnecte.", + "Zertifikat löschen?": "Supprimer le certificat ?", + "Let's Encrypt abschalten?": "Désactiver Let's Encrypt ?", + "Die Box nutzt danach wieder ein selbstsigniertes Zertifikat.": "La box utilise ensuite de nouveau un certificat auto-signé.", + "Abgeschaltet – die Seite verbindet sich neu.": "Désactivé – la page se reconnecte.", + "Let's-Encrypt-Zertifikat aktiv – die Seite verbindet sich neu.": "Certificat Let's Encrypt actif – la page se reconnecte." }, "patterns": [ [ @@ -2572,6 +2628,10 @@ [ "^aus ([0-9:]+)$", "arrêt $1" + ], + [ + "^Das Zertifikat „(.+)“ und sein Schlüssel werden von der Box gelöscht\\. Geräte, die es schon nutzen, behalten ihre Kopie\\.$", + "Le certificat « $1 » et sa clé sont supprimés de la box. Les appareils qui l'utilisent déjà gardent leur copie." ] ], "words": [ diff --git a/src/shareos-core/www/i18n/hi.json b/src/shareos-core/www/i18n/hi.json index 36ed0fd..c286811 100644 --- a/src/shareos-core/www/i18n/hi.json +++ b/src/shareos-core/www/i18n/hi.json @@ -1274,7 +1274,63 @@ "Keine Zeiten eingerichtet.": "कोई समय सेट नहीं है।", "GERÄT & ZUGANG": "डिवाइस और पहुँच", "Mit dem Stern wird eine App zum Favoriten – Favoriten stehen oben auf „Mein ShareOS“.": "स्टार से ऐप पसंदीदा बनता है – पसंदीदा \"मेरा ShareOS\" के ऊपर दिखते हैं।", - "Alle Hinweise anzeigen": "सभी सूचनाएँ दिखाएँ" + "Alle Hinweise anzeigen": "सभी सूचनाएँ दिखाएँ", + "EIGENE ZERTIFIKATE": "अपने प्रमाणपत्र", + "DynDNS aktualisieren": "डायनामिक DNS अपडेट करें", + "API-TOKEN (ZONE:LESEN, DNS:BEARBEITEN)": "API टोकन (ZONE:READ, DNS:EDIT)", + "UPDATE-ADRESSE ({host} {ip} {ip6})": "अपडेट पता ({host} {ip} {ip6})", + "Eigene Adresse (dyndns2)": "अपना पता (dyndns2)", + "– wählen –": "– चुनें –", + "gespeichert – leer lassen behält ihn": "सहेजा गया – खाली छोड़ें तो वही रहेगा", + "Die Box meldet ihre öffentliche Adresse alle 5 Minuten, wenn sie sich geändert hat. Viele Anschlüsse (DS-Lite, Glasfaser, Mobilfunk) haben keine eigene IPv4-Adresse – dann IPv6 nehmen. Für Zugriff von außen muss der Router den Port weiterleiten (bzw. bei IPv6 freigeben).": "पता बदलने पर बॉक्स हर 5 मिनट में अपना सार्वजनिक पता भेजता है। कई कनेक्शनों (DS-Lite, फ़ाइबर, मोबाइल) का अपना IPv4 पता नहीं होता – तब IPv6 लें। बाहर से पहुँच के लिए राउटर को पोर्ट फ़ॉरवर्ड करना होगा (या IPv6 के लिए खोलना होगा)।", + "Zuletzt gemeldet": "पिछली बार भेजा", + "Zuletzt geprüft": "पिछली जाँच", + "Noch keine Meldung – kommt gleich.": "अभी तक नहीं भेजा – बस आने वाला है।", + "DynDNS ist aus.": "डायनामिक DNS बंद है।", + "ZERTIFIZIERUNGSSTELLE (CA)": "प्रमाणपत्र प्राधिकरण (CA)", + "diese Box nutzt sie": "यह बॉक्स इसका उपयोग करता है", + "Fingerabdruck": "फ़िंगरप्रिंट", + "Stammzertifikat": "रूट प्रमाणपत्र", + "Zertifikat der Box neu ausstellen": "बॉक्स का प्रमाणपत्र फिर से जारी करें", + "Für diese Box verwenden": "इस बॉक्स के लिए उपयोग करें", + "Das Stammzertifikat einmal auf PC und Handy als vertrauenswürdig installieren (Windows: Doppelklick → „Vertrauenswürdige Stammzertifizierungsstellen“, Android/iOS: Einstellungen → Sicherheit). Danach sind alle Zertifikate dieser CA ohne Warnung – für die Box und andere Geräte im Heimnetz.": "रूट प्रमाणपत्र को PC और फ़ोन पर एक बार विश्वसनीय के रूप में इंस्टॉल करें (Windows: डबल-क्लिक → “Trusted Root Certification Authorities”, Android/iOS: सेटिंग्स → सुरक्षा)। इसके बाद इस CA के सभी प्रमाणपत्र बिना चेतावनी चलते हैं – बॉक्स और घरेलू नेटवर्क के अन्य उपकरणों के लिए।", + "Eine eigene Zertifizierungsstelle stellt Zertifikate für die Box und andere Geräte im Heimnetz aus (z. B.": "अपना प्रमाणपत्र प्राधिकरण बॉक्स और घरेलू नेटवर्क के अन्य उपकरणों के लिए प्रमाणपत्र जारी करता है (जैसे", + "). Ihr Stammzertifikat wird einmal auf den Geräten installiert – danach gibt es keine Zertifikatswarnungen mehr.": "). इसका रूट प्रमाणपत्र उपकरणों पर एक बार इंस्टॉल होता है – इसके बाद प्रमाणपत्र चेतावनियाँ नहीं आतीं।", + "Zertifizierungsstelle anlegen": "प्रमाणपत्र प्राधिकरण बनाएँ", + "Zertifikat wird geholt – das dauert bis zu 3 Minuten …": "प्रमाणपत्र लाया जा रहा है – इसमें 3 मिनट तक लगते हैं …", + "Wird täglich geprüft und 30 Tage vor Ablauf erneuert.": "रोज़ जाँचा जाता है और समाप्ति से 30 दिन पहले नवीनीकृत होता है।", + "Abschalten": "बंद करें", + "Ein Zertifikat für": "इसके लिए प्रमाणपत्र:", + ", dem alle Browser vertrauen. Die Prüfung läuft über einen DNS-Eintrag beim DynDNS-Anbieter – kein Port muss offen sein.": ", जिस पर सभी ब्राउज़र भरोसा करते हैं। जाँच डायनामिक DNS प्रदाता के DNS रिकॉर्ड से होती है – कोई पोर्ट खोलना ज़रूरी नहीं।", + "E-MAIL (OPTIONAL, FÜR HINWEISE VON LET'S ENCRYPT)": "ई-मेल (वैकल्पिक, LET'S ENCRYPT की सूचनाओं के लिए)", + "Testmodus (Test-CA, Browser warnen)": "टेस्ट मोड (टेस्ट CA, ब्राउज़र चेतावनी देंगे)", + "Zertifikat holen": "प्रमाणपत्र प्राप्त करें", + "Braucht DynDNS mit DuckDNS oder Cloudflare (oben).": "DuckDNS या Cloudflare के साथ डायनामिक DNS चाहिए (ऊपर)।", + "ZERTIFIKAT FÜR EIN ANDERES GERÄT": "दूसरे उपकरण के लिए प्रमाणपत्र", + "HOSTNAMEN UND IP-ADRESSEN (KOMMA-GETRENNT)": "होस्ट नाम और IP पते (कॉमा से अलग)", + "Gültig 825 Tage. Auch Platzhalter wie": "825 दिन तक मान्य। वाइल्डकार्ड जैसे", + "sind möglich. Zertifikat und Schlüssel herunterladen und im Gerät eintragen.": "भी संभव हैं। प्रमाणपत्र और कुंजी डाउनलोड करें और उपकरण में डालें।", + "Ausstellen": "जारी करें", + "HOSTNAMEN": "होस्ट नाम", + "GÜLTIG BIS": "मान्य तक", + "Schlüssel": "कुंजी", + "eigene CA": "अपना CA", + "aktiv (Test)": "सक्रिय (टेस्ट)", + "Bitte Anbieter und Namen angeben.": "कृपया प्रदाता और नाम दें।", + "Bitte Name und Hostnamen angeben.": "कृपया नाम और होस्ट नाम दें।", + "Gespeichert – die Adresse wird gemeldet.": "सहेजा गया – पता भेजा जा रहा है।", + "Wird gemeldet …": "भेजा जा रहा है …", + "Zertifizierungsstelle angelegt.": "प्रमाणपत्र प्राधिकरण बनाया गया।", + "Zertifikat ausgestellt.": "प्रमाणपत्र जारी किया गया।", + "Zertifikat der eigenen CA verwenden?": "अपने CA का प्रमाणपत्र उपयोग करें?", + "Die Box bekommt ein Zertifikat ihrer eigenen Zertifizierungsstelle für Gerätenamen, .local-Namen und IP-Adresse; es ersetzt das bisherige. Ohne installiertes Stammzertifikat warnt der Browser weiter.": "बॉक्स को अपने CA से डिवाइस नाम, .local नाम और IP पते के लिए प्रमाणपत्र मिलेगा; यह मौजूदा को बदल देगा। रूट प्रमाणपत्र इंस्टॉल न होने पर ब्राउज़र चेतावनी देता रहेगा।", + "Verwenden": "उपयोग करें", + "Zertifikat installiert – die Seite verbindet sich neu.": "प्रमाणपत्र इंस्टॉल हुआ – पेज फिर से जुड़ रहा है।", + "Zertifikat löschen?": "प्रमाणपत्र हटाएँ?", + "Let's Encrypt abschalten?": "Let's Encrypt बंद करें?", + "Die Box nutzt danach wieder ein selbstsigniertes Zertifikat.": "इसके बाद बॉक्स फिर से स्व-हस्ताक्षरित प्रमाणपत्र उपयोग करेगा।", + "Abgeschaltet – die Seite verbindet sich neu.": "बंद किया गया – पेज फिर से जुड़ रहा है।", + "Let's-Encrypt-Zertifikat aktiv – die Seite verbindet sich neu.": "Let's Encrypt प्रमाणपत्र सक्रिय – पेज फिर से जुड़ रहा है।" }, "patterns": [ [ @@ -2572,6 +2628,10 @@ [ "^aus ([0-9:]+)$", "बंद $1" + ], + [ + "^Das Zertifikat „(.+)“ und sein Schlüssel werden von der Box gelöscht\\. Geräte, die es schon nutzen, behalten ihre Kopie\\.$", + "प्रमाणपत्र “$1” और उसकी कुंजी बॉक्स से हटाई जाएगी। जो उपकरण इसे पहले से उपयोग करते हैं, वे अपनी प्रति रखेंगे।" ] ], "words": [ diff --git a/src/shareos-core/www/i18n/pt.json b/src/shareos-core/www/i18n/pt.json index 149669b..2aaf08e 100644 --- a/src/shareos-core/www/i18n/pt.json +++ b/src/shareos-core/www/i18n/pt.json @@ -1274,7 +1274,63 @@ "Keine Zeiten eingerichtet.": "Nenhum horário configurado.", "GERÄT & ZUGANG": "EQUIPAMENTO E ACESSO", "Mit dem Stern wird eine App zum Favoriten – Favoriten stehen oben auf „Mein ShareOS“.": "A estrela torna um app favorito – os favoritos aparecem no topo de “Meu ShareOS”.", - "Alle Hinweise anzeigen": "Mostrar todos os avisos" + "Alle Hinweise anzeigen": "Mostrar todos os avisos", + "EIGENE ZERTIFIKATE": "CERTIFICADOS PRÓPRIOS", + "DynDNS aktualisieren": "Atualizar DNS dinâmico", + "API-TOKEN (ZONE:LESEN, DNS:BEARBEITEN)": "TOKEN DA API (ZONA:LER, DNS:EDITAR)", + "UPDATE-ADRESSE ({host} {ip} {ip6})": "URL DE ATUALIZAÇÃO ({host} {ip} {ip6})", + "Eigene Adresse (dyndns2)": "URL própria (dyndns2)", + "– wählen –": "– escolher –", + "gespeichert – leer lassen behält ihn": "guardado – deixe vazio para mantê-lo", + "Die Box meldet ihre öffentliche Adresse alle 5 Minuten, wenn sie sich geändert hat. Viele Anschlüsse (DS-Lite, Glasfaser, Mobilfunk) haben keine eigene IPv4-Adresse – dann IPv6 nehmen. Für Zugriff von außen muss der Router den Port weiterleiten (bzw. bei IPv6 freigeben).": "A caixa comunica o seu endereço público a cada 5 minutos quando este mudou. Muitas ligações (DS-Lite, fibra, móvel) não têm endereço IPv4 próprio – use então IPv6. Para acesso a partir de fora, o router tem de encaminhar a porta (ou abri-la em IPv6).", + "Zuletzt gemeldet": "Último envio", + "Zuletzt geprüft": "Última verificação", + "Noch keine Meldung – kommt gleich.": "Ainda sem envio – já a seguir.", + "DynDNS ist aus.": "O DNS dinâmico está desligado.", + "ZERTIFIZIERUNGSSTELLE (CA)": "AUTORIDADE DE CERTIFICAÇÃO (CA)", + "diese Box nutzt sie": "esta caixa usa-a", + "Fingerabdruck": "Impressão digital", + "Stammzertifikat": "Certificado raiz", + "Zertifikat der Box neu ausstellen": "Reemitir o certificado da caixa", + "Für diese Box verwenden": "Usar para esta caixa", + "Das Stammzertifikat einmal auf PC und Handy als vertrauenswürdig installieren (Windows: Doppelklick → „Vertrauenswürdige Stammzertifizierungsstellen“, Android/iOS: Einstellungen → Sicherheit). Danach sind alle Zertifikate dieser CA ohne Warnung – für die Box und andere Geräte im Heimnetz.": "Instale uma vez o certificado raiz como fidedigno no PC e no telemóvel (Windows: duplo clique → «Autoridades de Certificação de Raiz Fidedigna», Android/iOS: Definições → Segurança). Depois, todos os certificados desta CA funcionam sem aviso – para a caixa e outros dispositivos da rede doméstica.", + "Eine eigene Zertifizierungsstelle stellt Zertifikate für die Box und andere Geräte im Heimnetz aus (z. B.": "Uma autoridade de certificação própria emite certificados para a caixa e outros dispositivos da rede doméstica (p. ex.", + "). Ihr Stammzertifikat wird einmal auf den Geräten installiert – danach gibt es keine Zertifikatswarnungen mehr.": "). O seu certificado raiz é instalado uma vez nos dispositivos – depois deixa de haver avisos de certificado.", + "Zertifizierungsstelle anlegen": "Criar autoridade de certificação", + "Zertifikat wird geholt – das dauert bis zu 3 Minuten …": "A obter o certificado – demora até 3 minutos …", + "Wird täglich geprüft und 30 Tage vor Ablauf erneuert.": "Verificado diariamente e renovado 30 dias antes de expirar.", + "Abschalten": "Desligar", + "Ein Zertifikat für": "Um certificado para", + ", dem alle Browser vertrauen. Die Prüfung läuft über einen DNS-Eintrag beim DynDNS-Anbieter – kein Port muss offen sein.": ", em que todos os navegadores confiam. A verificação é feita através de um registo DNS no fornecedor de DNS dinâmico – nenhuma porta precisa de estar aberta.", + "E-MAIL (OPTIONAL, FÜR HINWEISE VON LET'S ENCRYPT)": "E-MAIL (OPCIONAL, PARA AVISOS DA LET'S ENCRYPT)", + "Testmodus (Test-CA, Browser warnen)": "Modo de teste (CA de teste, os navegadores avisam)", + "Zertifikat holen": "Obter certificado", + "Braucht DynDNS mit DuckDNS oder Cloudflare (oben).": "Precisa de DNS dinâmico com DuckDNS ou Cloudflare (acima).", + "ZERTIFIKAT FÜR EIN ANDERES GERÄT": "CERTIFICADO PARA OUTRO DISPOSITIVO", + "HOSTNAMEN UND IP-ADRESSEN (KOMMA-GETRENNT)": "NOMES DE ANFITRIÃO E ENDEREÇOS IP (SEPARADOS POR VÍRGULAS)", + "Gültig 825 Tage. Auch Platzhalter wie": "Válido 825 dias. Também curingas como", + "sind möglich. Zertifikat und Schlüssel herunterladen und im Gerät eintragen.": "também são possíveis. Transfira o certificado e a chave e introduza-os no dispositivo.", + "Ausstellen": "Emitir", + "HOSTNAMEN": "NOMES DE ANFITRIÃO", + "GÜLTIG BIS": "VÁLIDO ATÉ", + "Schlüssel": "Chave", + "eigene CA": "CA própria", + "aktiv (Test)": "ativo (teste)", + "Bitte Anbieter und Namen angeben.": "Indique o fornecedor e o nome.", + "Bitte Name und Hostnamen angeben.": "Indique o nome e os nomes de anfitrião.", + "Gespeichert – die Adresse wird gemeldet.": "Guardado – o endereço está a ser enviado.", + "Wird gemeldet …": "A enviar …", + "Zertifizierungsstelle angelegt.": "Autoridade de certificação criada.", + "Zertifikat ausgestellt.": "Certificado emitido.", + "Zertifikat der eigenen CA verwenden?": "Usar um certificado da CA própria?", + "Die Box bekommt ein Zertifikat ihrer eigenen Zertifizierungsstelle für Gerätenamen, .local-Namen und IP-Adresse; es ersetzt das bisherige. Ohne installiertes Stammzertifikat warnt der Browser weiter.": "A caixa recebe um certificado da sua própria CA para o nome do dispositivo, o nome .local e o endereço IP; substitui o atual. Sem o certificado raiz instalado, o navegador continua a avisar.", + "Verwenden": "Usar", + "Zertifikat installiert – die Seite verbindet sich neu.": "Certificado instalado – a página volta a ligar-se.", + "Zertifikat löschen?": "Eliminar o certificado?", + "Let's Encrypt abschalten?": "Desligar o Let's Encrypt?", + "Die Box nutzt danach wieder ein selbstsigniertes Zertifikat.": "A caixa volta a usar um certificado autoassinado.", + "Abgeschaltet – die Seite verbindet sich neu.": "Desligado – a página volta a ligar-se.", + "Let's-Encrypt-Zertifikat aktiv – die Seite verbindet sich neu.": "Certificado Let's Encrypt ativo – a página volta a ligar-se." }, "patterns": [ [ @@ -2572,6 +2628,10 @@ [ "^aus ([0-9:]+)$", "desliga $1" + ], + [ + "^Das Zertifikat „(.+)“ und sein Schlüssel werden von der Box gelöscht\\. Geräte, die es schon nutzen, behalten ihre Kopie\\.$", + "O certificado «$1» e a sua chave são eliminados da caixa. Os dispositivos que já o usam mantêm a sua cópia." ] ], "words": [ diff --git a/src/shareos-core/www/i18n/ru.json b/src/shareos-core/www/i18n/ru.json index 7e776ee..a263dfe 100644 --- a/src/shareos-core/www/i18n/ru.json +++ b/src/shareos-core/www/i18n/ru.json @@ -1274,7 +1274,63 @@ "Keine Zeiten eingerichtet.": "Время не задано.", "GERÄT & ZUGANG": "УСТРОЙСТВО И ДОСТУП", "Mit dem Stern wird eine App zum Favoriten – Favoriten stehen oben auf „Mein ShareOS“.": "Звёздочка делает приложение избранным – избранные показаны вверху «Мой ShareOS».", - "Alle Hinweise anzeigen": "Показать все уведомления" + "Alle Hinweise anzeigen": "Показать все уведомления", + "EIGENE ZERTIFIKATE": "СОБСТВЕННЫЕ СЕРТИФИКАТЫ", + "DynDNS aktualisieren": "Обновлять динамический DNS", + "API-TOKEN (ZONE:LESEN, DNS:BEARBEITEN)": "API-ТОКЕН (ZONE:READ, DNS:EDIT)", + "UPDATE-ADRESSE ({host} {ip} {ip6})": "АДРЕС ОБНОВЛЕНИЯ ({host} {ip} {ip6})", + "Eigene Adresse (dyndns2)": "Свой адрес (dyndns2)", + "– wählen –": "– выберите –", + "gespeichert – leer lassen behält ihn": "сохранён – оставьте пустым, чтобы не менять", + "Die Box meldet ihre öffentliche Adresse alle 5 Minuten, wenn sie sich geändert hat. Viele Anschlüsse (DS-Lite, Glasfaser, Mobilfunk) haben keine eigene IPv4-Adresse – dann IPv6 nehmen. Für Zugriff von außen muss der Router den Port weiterleiten (bzw. bei IPv6 freigeben).": "Устройство сообщает свой внешний адрес каждые 5 минут, если он изменился. У многих подключений (DS-Lite, оптика, мобильная связь) нет собственного IPv4-адреса – тогда используйте IPv6. Для доступа извне роутер должен перенаправить порт (или открыть его для IPv6).", + "Zuletzt gemeldet": "Последняя отправка", + "Zuletzt geprüft": "Последняя проверка", + "Noch keine Meldung – kommt gleich.": "Ещё не отправлено – скоро.", + "DynDNS ist aus.": "Динамический DNS выключен.", + "ZERTIFIZIERUNGSSTELLE (CA)": "УДОСТОВЕРЯЮЩИЙ ЦЕНТР (CA)", + "diese Box nutzt sie": "используется этим устройством", + "Fingerabdruck": "Отпечаток", + "Stammzertifikat": "Корневой сертификат", + "Zertifikat der Box neu ausstellen": "Перевыпустить сертификат устройства", + "Für diese Box verwenden": "Использовать для этого устройства", + "Das Stammzertifikat einmal auf PC und Handy als vertrauenswürdig installieren (Windows: Doppelklick → „Vertrauenswürdige Stammzertifizierungsstellen“, Android/iOS: Einstellungen → Sicherheit). Danach sind alle Zertifikate dieser CA ohne Warnung – für die Box und andere Geräte im Heimnetz.": "Один раз установите корневой сертификат как доверенный на ПК и телефоне (Windows: двойной щелчок → «Доверенные корневые центры сертификации», Android/iOS: Настройки → Безопасность). После этого все сертификаты этого CA работают без предупреждений – для устройства и других устройств домашней сети.", + "Eine eigene Zertifizierungsstelle stellt Zertifikate für die Box und andere Geräte im Heimnetz aus (z. B.": "Собственный удостоверяющий центр выпускает сертификаты для устройства и других устройств домашней сети (например,", + "). Ihr Stammzertifikat wird einmal auf den Geräten installiert – danach gibt es keine Zertifikatswarnungen mehr.": "). Его корневой сертификат один раз устанавливается на устройства – после этого предупреждений о сертификате больше нет.", + "Zertifizierungsstelle anlegen": "Создать удостоверяющий центр", + "Zertifikat wird geholt – das dauert bis zu 3 Minuten …": "Получение сертификата – это занимает до 3 минут …", + "Wird täglich geprüft und 30 Tage vor Ablauf erneuert.": "Проверяется ежедневно и обновляется за 30 дней до истечения.", + "Abschalten": "Отключить", + "Ein Zertifikat für": "Сертификат для", + ", dem alle Browser vertrauen. Die Prüfung läuft über einen DNS-Eintrag beim DynDNS-Anbieter – kein Port muss offen sein.": ", которому доверяют все браузеры. Проверка идёт через DNS-запись у провайдера динамического DNS – открывать порт не нужно.", + "E-MAIL (OPTIONAL, FÜR HINWEISE VON LET'S ENCRYPT)": "E-MAIL (НЕОБЯЗАТЕЛЬНО, ДЛЯ УВЕДОМЛЕНИЙ LET'S ENCRYPT)", + "Testmodus (Test-CA, Browser warnen)": "Тестовый режим (тестовый CA, браузеры предупреждают)", + "Zertifikat holen": "Получить сертификат", + "Braucht DynDNS mit DuckDNS oder Cloudflare (oben).": "Нужен динамический DNS с DuckDNS или Cloudflare (выше).", + "ZERTIFIKAT FÜR EIN ANDERES GERÄT": "СЕРТИФИКАТ ДЛЯ ДРУГОГО УСТРОЙСТВА", + "HOSTNAMEN UND IP-ADRESSEN (KOMMA-GETRENNT)": "ИМЕНА ХОСТОВ И IP-АДРЕСА (ЧЕРЕЗ ЗАПЯТУЮ)", + "Gültig 825 Tage. Auch Platzhalter wie": "Действителен 825 дней. Подстановочные имена вроде", + "sind möglich. Zertifikat und Schlüssel herunterladen und im Gerät eintragen.": "тоже возможны. Скачайте сертификат и ключ и укажите их в устройстве.", + "Ausstellen": "Выпустить", + "HOSTNAMEN": "ИМЕНА ХОСТОВ", + "GÜLTIG BIS": "ДЕЙСТВИТЕЛЕН ДО", + "Schlüssel": "Ключ", + "eigene CA": "собственный CA", + "aktiv (Test)": "активен (тест)", + "Bitte Anbieter und Namen angeben.": "Укажите провайдера и имя.", + "Bitte Name und Hostnamen angeben.": "Укажите имя и имена хостов.", + "Gespeichert – die Adresse wird gemeldet.": "Сохранено – адрес отправляется.", + "Wird gemeldet …": "Отправляется …", + "Zertifizierungsstelle angelegt.": "Удостоверяющий центр создан.", + "Zertifikat ausgestellt.": "Сертификат выпущен.", + "Zertifikat der eigenen CA verwenden?": "Использовать сертификат собственного CA?", + "Die Box bekommt ein Zertifikat ihrer eigenen Zertifizierungsstelle für Gerätenamen, .local-Namen und IP-Adresse; es ersetzt das bisherige. Ohne installiertes Stammzertifikat warnt der Browser weiter.": "Устройство получит сертификат собственного CA для имени, имени .local и IP-адреса; он заменит текущий. Без установленного корневого сертификата браузер будет предупреждать.", + "Verwenden": "Использовать", + "Zertifikat installiert – die Seite verbindet sich neu.": "Сертификат установлен – страница переподключается.", + "Zertifikat löschen?": "Удалить сертификат?", + "Let's Encrypt abschalten?": "Отключить Let's Encrypt?", + "Die Box nutzt danach wieder ein selbstsigniertes Zertifikat.": "После этого устройство снова использует самоподписанный сертификат.", + "Abgeschaltet – die Seite verbindet sich neu.": "Отключено – страница переподключается.", + "Let's-Encrypt-Zertifikat aktiv – die Seite verbindet sich neu.": "Сертификат Let's Encrypt активен – страница переподключается." }, "patterns": [ [ @@ -2572,6 +2628,10 @@ [ "^aus ([0-9:]+)$", "выкл. $1" + ], + [ + "^Das Zertifikat „(.+)“ und sein Schlüssel werden von der Box gelöscht\\. Geräte, die es schon nutzen, behalten ihre Kopie\\.$", + "Сертификат «$1» и его ключ удаляются с устройства. Устройства, которые уже его используют, сохраняют свою копию." ] ], "words": [ diff --git a/src/shareos-core/www/i18n/zh.json b/src/shareos-core/www/i18n/zh.json index 7d0e039..86b40f4 100644 --- a/src/shareos-core/www/i18n/zh.json +++ b/src/shareos-core/www/i18n/zh.json @@ -1274,7 +1274,63 @@ "Keine Zeiten eingerichtet.": "未设置时间。", "GERÄT & ZUGANG": "设备与访问", "Mit dem Stern wird eine App zum Favoriten – Favoriten stehen oben auf „Mein ShareOS“.": "星标可将应用设为收藏——收藏显示在“我的 ShareOS”顶部。", - "Alle Hinweise anzeigen": "显示所有提示" + "Alle Hinweise anzeigen": "显示所有提示", + "EIGENE ZERTIFIKATE": "自有证书", + "DynDNS aktualisieren": "更新动态 DNS", + "API-TOKEN (ZONE:LESEN, DNS:BEARBEITEN)": "API 令牌(ZONE:READ、DNS:EDIT)", + "UPDATE-ADRESSE ({host} {ip} {ip6})": "更新地址({host} {ip} {ip6})", + "Eigene Adresse (dyndns2)": "自定义地址 (dyndns2)", + "– wählen –": "– 请选择 –", + "gespeichert – leer lassen behält ihn": "已保存 – 留空则保留", + "Die Box meldet ihre öffentliche Adresse alle 5 Minuten, wenn sie sich geändert hat. Viele Anschlüsse (DS-Lite, Glasfaser, Mobilfunk) haben keine eigene IPv4-Adresse – dann IPv6 nehmen. Für Zugriff von außen muss der Router den Port weiterleiten (bzw. bei IPv6 freigeben).": "公网地址变化时,设备每 5 分钟上报一次。许多线路(DS-Lite、光纤、移动网络)没有独立的 IPv4 地址——此时请使用 IPv6。要从外部访问,路由器需要转发端口(IPv6 则需放行)。", + "Zuletzt gemeldet": "上次上报", + "Zuletzt geprüft": "上次检查", + "Noch keine Meldung – kommt gleich.": "尚未上报——马上就好。", + "DynDNS ist aus.": "动态 DNS 已关闭。", + "ZERTIFIZIERUNGSSTELLE (CA)": "证书颁发机构 (CA)", + "diese Box nutzt sie": "本设备在使用", + "Fingerabdruck": "指纹", + "Stammzertifikat": "根证书", + "Zertifikat der Box neu ausstellen": "重新签发本设备证书", + "Für diese Box verwenden": "用于本设备", + "Das Stammzertifikat einmal auf PC und Handy als vertrauenswürdig installieren (Windows: Doppelklick → „Vertrauenswürdige Stammzertifizierungsstellen“, Android/iOS: Einstellungen → Sicherheit). Danach sind alle Zertifikate dieser CA ohne Warnung – für die Box und andere Geräte im Heimnetz.": "在电脑和手机上将根证书安装为受信任一次(Windows:双击 →“受信任的根证书颁发机构”,Android/iOS:设置 → 安全)。之后该 CA 签发的所有证书都不再告警——适用于本设备和家庭网络中的其他设备。", + "Eine eigene Zertifizierungsstelle stellt Zertifikate für die Box und andere Geräte im Heimnetz aus (z. B.": "自有证书颁发机构可为本设备和家庭网络中的其他设备签发证书(例如", + "). Ihr Stammzertifikat wird einmal auf den Geräten installiert – danach gibt es keine Zertifikatswarnungen mehr.": ")。其根证书只需在设备上安装一次——之后不再出现证书告警。", + "Zertifizierungsstelle anlegen": "创建证书颁发机构", + "Zertifikat wird geholt – das dauert bis zu 3 Minuten …": "正在获取证书——最多需要 3 分钟……", + "Wird täglich geprüft und 30 Tage vor Ablauf erneuert.": "每天检查,在到期前 30 天续期。", + "Abschalten": "关闭", + "Ein Zertifikat für": "为", + ", dem alle Browser vertrauen. Die Prüfung läuft über einen DNS-Eintrag beim DynDNS-Anbieter – kein Port muss offen sein.": " 签发所有浏览器都信任的证书。验证通过动态 DNS 服务商的 DNS 记录完成——无需开放端口。", + "E-MAIL (OPTIONAL, FÜR HINWEISE VON LET'S ENCRYPT)": "电子邮件(可选,用于接收 LET'S ENCRYPT 通知)", + "Testmodus (Test-CA, Browser warnen)": "测试模式(测试 CA,浏览器会告警)", + "Zertifikat holen": "获取证书", + "Braucht DynDNS mit DuckDNS oder Cloudflare (oben).": "需要使用 DuckDNS 或 Cloudflare 的动态 DNS(见上方)。", + "ZERTIFIKAT FÜR EIN ANDERES GERÄT": "为其他设备签发证书", + "HOSTNAMEN UND IP-ADRESSEN (KOMMA-GETRENNT)": "主机名和 IP 地址(逗号分隔)", + "Gültig 825 Tage. Auch Platzhalter wie": "有效期 825 天。也可以使用通配符,如", + "sind möglich. Zertifikat und Schlüssel herunterladen und im Gerät eintragen.": "。下载证书和密钥并在设备中填入。", + "Ausstellen": "签发", + "HOSTNAMEN": "主机名", + "GÜLTIG BIS": "有效期至", + "Schlüssel": "密钥", + "eigene CA": "自有 CA", + "aktiv (Test)": "已启用(测试)", + "Bitte Anbieter und Namen angeben.": "请填写服务商和名称。", + "Bitte Name und Hostnamen angeben.": "请填写名称和主机名。", + "Gespeichert – die Adresse wird gemeldet.": "已保存——正在上报地址。", + "Wird gemeldet …": "正在上报……", + "Zertifizierungsstelle angelegt.": "证书颁发机构已创建。", + "Zertifikat ausgestellt.": "证书已签发。", + "Zertifikat der eigenen CA verwenden?": "使用自有 CA 的证书?", + "Die Box bekommt ein Zertifikat ihrer eigenen Zertifizierungsstelle für Gerätenamen, .local-Namen und IP-Adresse; es ersetzt das bisherige. Ohne installiertes Stammzertifikat warnt der Browser weiter.": "本设备将获得自有 CA 为设备名、.local 名称和 IP 地址签发的证书,替换当前证书。未安装根证书时,浏览器仍会告警。", + "Verwenden": "使用", + "Zertifikat installiert – die Seite verbindet sich neu.": "证书已安装——页面将重新连接。", + "Zertifikat löschen?": "删除证书?", + "Let's Encrypt abschalten?": "关闭 Let's Encrypt?", + "Die Box nutzt danach wieder ein selbstsigniertes Zertifikat.": "之后本设备将重新使用自签名证书。", + "Abgeschaltet – die Seite verbindet sich neu.": "已关闭——页面将重新连接。", + "Let's-Encrypt-Zertifikat aktiv – die Seite verbindet sich neu.": "Let's Encrypt 证书已启用——页面将重新连接。" }, "patterns": [ [ @@ -2572,6 +2628,10 @@ [ "^aus ([0-9:]+)$", "关 $1" + ], + [ + "^Das Zertifikat „(.+)“ und sein Schlüssel werden von der Box gelöscht\\. Geräte, die es schon nutzen, behalten ihre Kopie\\.$", + "证书“$1”及其密钥将从本设备删除。已在使用它的设备会保留各自的副本。" ] ], "words": [ diff --git a/tests/check-ucode-regex.py b/tests/check-ucode-regex.py index 2dfaedb..4b47b06 100755 --- a/tests/check-ucode-regex.py +++ b/tests/check-ucode-regex.py @@ -9,7 +9,7 @@ import sys FILES = sys.argv[1:] or ['src/shareos-core/usr/share/rpcd/ucode/shareos.uc', 'src/shareos-core/usr/sbin/shareos-apps', - 'src/shareos-core/usr/sbin/shareos-notify', 'src/shareos-core/usr/sbin/shareos-buttons', + 'src/shareos-core/usr/sbin/shareos-notify', 'src/shareos-core/usr/sbin/shareos-buttons', 'src/shareos-core/usr/sbin/shareos-ddns', 'src/shareos-core/www-apps/_shareos/cgi/fs'] bad = [] for f in FILES: diff --git a/tests/mock-ubus.py b/tests/mock-ubus.py index 12f46a4..798f265 100644 --- a/tests/mock-ubus.py +++ b/tests/mock-ubus.py @@ -699,6 +699,42 @@ def call(obj, method, args): STATE['led'].update({k: v for k, v in args.items() if v}) return {'fan': STATE['fan'], 'led': STATE['led'], 'temperature': 41000, 'fan_pwm': 100, 'presets': {'quiet': {'off_temp': 40, 'full_temp': 55, 'half_temp': 50}}} + if obj == 'shareos.web' and method.startswith(('ddns', 'ca_', 'acme')): + d = STATE.setdefault('ddns', {'enabled': False, 'provider': '', 'host': '', 'user': '', 'url': '', 'token_set': False, 'ipv4': True, 'ipv6': False, 'last': None}) + ca = STATE.setdefault('ca', {'exists': False, 'certs': [], 'web': False}) + if method == 'ddns_get': + return d + if method == 'ddns_set': + d.update({k: v for k, v in args.items() if k != 'token'}) + d['token_set'] = d['token_set'] or bool(args.get('token')) + d['last'] = {'provider': d['provider'], 'host': d['host'], 'ip4': '203.0.113.7', 'ip6': None, 'checked': 1793530000, 'sent': 1793530000, 'ok': True, 'error': None} if d['enabled'] else None + return {'ok': True, 'pending': d['enabled']} + if method == 'ddns_update': + return {'ok': True, 'pending': True} + if method == 'ca_get': + return ca + if method == 'ca_init': + ca.update({'exists': True, 'subject': 'CN=ShareOS CA skynet-nas, O=ShareOS', 'until': '2036-11-01 12:00:00', 'fingerprint': ':'.join(['A1'] * 32), 'count': 0}) + return {'ok': True} + if method == 'ca_issue': + ca['certs'].append({'name': args['name'], 'hosts': args['hosts'], 'until': '2029-02-04 12:00:00', 'expires': 1864816233}) + return {'ok': True} + if method == 'ca_delete': + ca['certs'] = [c for c in ca['certs'] if c['name'] != args['name']] + return {'ok': True} + if method == 'ca_pem': + return {'pem': '-----BEGIN CERTIFICATE-----\nMOCK\n-----END CERTIFICATE-----\n'} + if method == 'ca_web': + ca['web'] = True + return {'ok': True} + if method == 'acme_get': + return STATE.setdefault('acme', {'active': False, 'name': '', 'until': '', 'staging': False, 'email': '', 'error': '', 'running': False}) + if method == 'acme_issue': + STATE['acme'] = {'active': True, 'name': d['host'], 'until': '2027-01-30 10:00:00', 'staging': bool(args.get('staging')), 'email': args.get('email', ''), 'error': '', 'running': False} + return {'ok': True} + if method == 'acme_off': + STATE['acme']['active'] = False + return {'ok': True} if obj == 'shareos.web': if method == 'set': STATE.update({k: v for k, v in args.items() if k in ('https', 'redirect')}) diff --git a/tests/test-ca.sh b/tests/test-ca.sh new file mode 100755 index 0000000..b80fea9 --- /dev/null +++ b/tests/test-ca.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# +# Tests shareos-ca (own certificate authority): CA, issued certificates with +# their names and server authentication, the web certificate renewed only when +# its names change or it runs out. Needs the mbedtls programs gen_key, +# cert_write and cert_app – of the firmware through qemu-arm, e.g. +# +# tests/test-ca.sh +# (a dir of wrappers: exec qemu-arm-static -L /usr/bin/ "$@") +# +TOP=$(cd "$(dirname "$0")/.." && pwd) +UCIBIN=${1:-} +export SHAREOS_CA_BIN=${2:?mbedtls programs} +P="$TOP/src/shareos-core/usr/sbin/shareos-ca" +T=$(mktemp -d); trap 'rm -rf "$T"' EXIT +mkdir -p $T/etc/config +: > $T/etc/config/shareos +export SHAREOS_ROOT=$T PATH="${UCIBIN:+$UCIBIN:}$PATH" +run() { sh "$P" "$@"; } +pass=0; failed=0 +check() { if eval "$2"; then echo "PASS $1"; pass=$((pass+1)); else echo "FAIL $1"; failed=$((failed+1)); fi; } +app() { "$SHAREOS_CA_BIN/cert_app" mode=file filename="$1" 2>/dev/null; } + +check "info without CA" "run info | grep -q '\"exists\":false'" +check "init creates the CA" "run init | grep -q 'CA created' && [ -s $T/etc/shareos/ca/ca.key ]" +check "init again keeps it" "run init | grep -q 'CA exists'" +check "CA key root only" "[ \$(stat -c %a $T/etc/shareos/ca/ca.key) = 600 ]" +check "CA certificate is a CA" "app $T/etc/shareos/ca/ca.crt | grep -qi 'CA *= *true'" +check "info: subject and fingerprint" "run info | grep -q '\"subject\":\"CN=ShareOS CA testbox, O=ShareOS\"' && run info | grep -qE '\"fingerprint\":\"([0-9A-F]{2}:){31}[0-9A-F]{2}\"'" +check "issue for a host with wildcard and IP" "run issue nas nas.home.lan '*.home.lan' 192.168.1.5 | grep -q issued" +check "issued: names" "app $T/etc/shareos/ca/certs/nas.crt | grep -q 'dNSName *: \\*.home.lan' && app $T/etc/shareos/ca/certs/nas.crt | grep -q 'iPAddress *: 192.168.1.5'" +check "issued: server authentication, signed by the CA" "app $T/etc/shareos/ca/certs/nas.crt | grep -qi 'TLS Web Server' && app $T/etc/shareos/ca/certs/nas.crt | grep -q 'issuer name *: CN=ShareOS CA testbox'" +check "list" "run list | python3 -c \"import json,sys; d=json.load(sys.stdin); sys.exit(0 if d[0]['name']=='nas' and sorted(d[0]['hosts'])==['*.home.lan','192.168.1.5','nas.home.lan'] and d[0]['expires']>0 else 1)\"" +check "show crt: certificate and CA" "[ \$(run show nas crt | grep -c 'BEGIN CERTIFICATE') = 2 ]" +check "bad host name refused" "! run issue x 'a b' 2>/dev/null" +check "bad certificate name refused" "! run issue '../x' a.lan 2>/dev/null" +check "delete" "run delete nas >/dev/null && [ ! -e $T/etc/shareos/ca/certs/nas.crt ] && run list | grep -qx '\\[\\]'" +check "web certificate" "run web | grep -q 'web certificate issued' && [ \"\$(uci -c $T/etc/config get shareos.web.source)\" = ca ]" +cp $T/etc/shareos/ca/web.crt $T/w1 +check "web check: names unchanged – kept" "run web check && cmp -s $T/w1 $T/etc/shareos/ca/web.crt" +SHAREOS_CA_IP=192.0.2.99 check "web check: new IP – renewed" "run web check | grep -q issued && ! cmp -s $T/w1 $T/etc/shareos/ca/web.crt && app $T/etc/shareos/ca/web.crt | grep -q 192.0.2.99" +cp $T/etc/shareos/ca/web.crt $T/w2 +SHAREOS_CA_IP=192.0.2.99 SHAREOS_CA_NOW=$(( $(date +%s) + 800 * 86400 )) check "web check: under 30 days left – renewed" "run web check | grep -q issued && ! cmp -s $T/w2 $T/etc/shareos/ca/web.crt" + +echo "test-ca: $pass passed, $failed failed" +[ $failed = 0 ] diff --git a/tests/test-ddns.sh b/tests/test-ddns.sh new file mode 100755 index 0000000..da0586e --- /dev/null +++ b/tests/test-ddns.sh @@ -0,0 +1,103 @@ +#!/bin/sh +# +# Tests shareos-ddns (dynamic DNS) with a fake curl: the update URL of every +# provider, nothing sent while the address stays the same, a failure retried +# after 30 minutes, Cloudflare records created or changed, the TXT records +# for Let's Encrypt, secrets not on the command line. +# +# tests/test-ddns.sh [dir with ucode] (LD_LIBRARY_PATH for its libraries) +# +TOP=$(cd "$(dirname "$0")/.." && pwd) +BIN=${1:-} +UCODE=${BIN:+$BIN/}ucode +P="$TOP/src/shareos-core/usr/sbin/shareos-ddns" +T=$(mktemp -d); trap 'rm -rf "$T"' EXIT +mkdir -p $T/etc/config $T/tmp +# fake curl: logs the URL (last argument) and the config from stdin, answers by URL +cat > $T/curl <<'C' +#!/bin/sh +D=$(dirname "$0") +cfg=$(cat) +for a; do url=$a; done +args="$*" +echo "URL $url" >> $D/curl.log +echo "ARGS $args" >> $D/curl.log +[ -n "$cfg" ] && echo "CFG $cfg" >> $D/curl.log +ip=$(cat $D/ip 2>/dev/null || echo 203.0.113.7) +ans() { printf '%s\n%s' "$2" "$1"; exit 0; } +case "$url" in + https://api.ipify.org) ans 200 "$ip" ;; + https://www.duckdns.org/update*) [ -f $D/duck-ko ] && ans 200 KO; ans 200 OK ;; + https://dynv6.com/api/update*) ans 200 "addresses updated" ;; + https://dynupdate.no-ip.com/*) ans 200 "good $ip" ;; + https://dyn.example.net/*) ans 200 "badauth" ;; + */zones\?name=example.org) ans 200 '{"success":true,"result":[{"id":"Z1"}]}' ;; + */zones\?name=*) ans 200 '{"success":true,"result":[]}' ;; + */zones/Z1/dns_records\?type=A*) [ -f $D/cf-has ] && ans 200 '{"success":true,"result":[{"id":"R1","content":"198.51.100.1"}]}'; ans 200 '{"success":true,"result":[]}' ;; + */zones/Z1/dns_records\?type=TXT*) ans 200 '{"success":true,"result":[{"id":"T1","content":"\"abc\""}]}' ;; + */zones/Z1/dns_records*) ans 200 '{"success":true,"result":{}}' ;; +esac +ans 404 "not found" +C +chmod 755 $T/curl +export SHAREOS_ROOT=$T SHAREOS_DDNS_CURL=$T/curl SHAREOS_DDNS_IP6= +run() { "$UCODE" -L "${BIN:+$BIN/../lib/ucode}" "$P" "$@"; } +js() { python3 -c "import json,sys; d=json.load(sys.stdin); sys.exit(0 if ($1) else 1)"; } +conf() { printf "config ddns 'ddns'\n\toption enabled '1'\n" > $T/etc/config/shareos; for kv; do printf "\toption %s '%s'\n" "${kv%%=*}" "${kv#*=}" >> $T/etc/config/shareos; done; rm -f $T/tmp/shareos-ddns.json $T/curl.log; } +pass=0; failed=0 +check() { if eval "$2"; then echo "PASS $1"; pass=$((pass+1)); else echo "FAIL $1"; failed=$((failed+1)); fi; } +export SHAREOS_DDNS_NOW=1800000000 + +conf provider=duckdns host=mybox.duckdns.org token='t0k&en' +check "duckdns: update with the public address" "run update | js \"d.get('ok') and d['ip4']=='203.0.113.7'\"" +check "duckdns: sub-domain and token encoded in the URL" "grep -q 'URL https://www.duckdns.org/update?domains=mybox&token=t0k%26en&ip=203.0.113.7' $T/curl.log" +: > $T/curl.log +check "same address: nothing sent" "run update | js \"d.get('skipped')=='unchanged'\" && ! grep -q duckdns $T/curl.log" +echo 198.51.100.9 > $T/ip +check "new address: sent again" "run update | js \"d.get('ok') and d['ip4']=='198.51.100.9'\" && grep -q 'ip=198.51.100.9' $T/curl.log" +check "status: no token in the answer" "run status | js \"d['token_set'] and 'token' not in d and d['last']['ip4']=='198.51.100.9'\"" +SHAREOS_DDNS_NOW=$((1800000000 + 90000)) check "after a day: sent again" "run update | js \"d.get('ok')\"" +rm -f $T/ip + +conf provider=duckdns host=mybox token=x; touch $T/duck-ko +check "duckdns KO: error" "run update | js \"'wrong' in d.get('error','')\"" +rm $T/duck-ko; : > $T/curl.log +SHAREOS_DDNS_NOW=$((1800000000 + 600)) check "failure: not asked again within 30 minutes" "run update | js \"d.get('skipped')\" && ! grep -q duckdns $T/curl.log" +SHAREOS_DDNS_NOW=$((1800000000 + 1900)) check "failure: asked again after 30 minutes" "run update | js \"d.get('ok')\"" +check "force: always sent" "run update force | js \"d.get('ok')\"" + +conf provider=dynv6 host=box.dynv6.net token=abc ipv6=1; export SHAREOS_DDNS_IP6=2001:db8::5 +check "dynv6: IPv4 and IPv6" "run update | js \"d.get('ok') and d['ip6']=='2001:db8::5'\" && grep -q 'URL https://dynv6.com/api/update?hostname=box.dynv6.net&token=abc&ipv4=203.0.113.7&ipv6=2001:db8::5' $T/curl.log" +conf provider=dynv6 host=box.dynv6.net token=abc ipv4=0 ipv6=1 +check "dynv6: IPv6 only" "run update | js \"d.get('ok') and d['ip4'] is None\" && ! grep -q ipify $T/curl.log" +export SHAREOS_DDNS_IP6= + +conf provider=noip host=box.ddns.net user=jan token='geh eim' +check "no-ip: dyndns2 answer good" "run update | js \"d.get('ok')\"" +check "no-ip: password not on the command line, through the config" "! grep '^ARGS' $T/curl.log | grep -q 'geh eim' && grep -q 'CFG user = \"jan:geh eim\"' $T/curl.log" + +conf provider=custom host=box.example.net user=jan token=pw url='https://dyn.example.net/nic/update?hostname={host}&myip={ip}' +check "custom: placeholders and dyndns2 error badauth" "run update | js \"'password wrong' in d.get('error','')\" && grep -q 'URL https://dyn.example.net/nic/update?hostname=box.example.net&myip=203.0.113.7' $T/curl.log" + +conf provider=cloudflare host=nas.home.example.org token=cftoken +check "cloudflare: zone found, A record created" "run update | js \"d.get('ok')\" && grep -q 'URL https://api.cloudflare.com/client/v4/zones/Z1/dns_records\$' $T/curl.log && grep -q 'ARGS.*-X POST' $T/curl.log" +check "cloudflare: token through the config" "grep -q 'CFG header = \"Authorization: Bearer cftoken\"' $T/curl.log && ! grep '^ARGS' $T/curl.log | grep -q cftoken" +conf provider=cloudflare host=nas.home.example.org token=cftoken; touch $T/cf-has +check "cloudflare: existing record changed (PUT)" "run update | js \"d.get('ok')\" && grep -q 'URL https://api.cloudflare.com/client/v4/zones/Z1/dns_records/R1' $T/curl.log && grep -q 'ARGS.*-X PUT' $T/curl.log" +rm $T/cf-has +check "cloudflare: TXT for Let's Encrypt" "run txt-set _acme-challenge.nas.home.example.org abc | js \"d.get('ok')\"" +: > $T/curl.log +check "cloudflare: TXT removed again" "run txt-clear _acme-challenge.nas.home.example.org abc | js \"d.get('ok')\" && grep -q 'URL .*/dns_records/T1' $T/curl.log" + +conf provider=duckdns host=mybox token=x +check "duckdns: TXT set and cleared" "run txt-set _acme-challenge.mybox.duckdns.org v1 | js \"d.get('ok')\" && grep -q 'txt=v1' $T/curl.log && run txt-clear _acme-challenge.mybox.duckdns.org v1 | js \"d.get('ok')\" && grep -q 'clear=true' $T/curl.log" +conf provider=noip host=a.ddns.net user=u token=p +check "no-ip: no TXT records (error)" "! run txt-set _acme-challenge.a.ddns.net v >/dev/null" + +conf provider=noip host=box token=p user=u +check "host name without domain refused" "run update | js \"'domain' in d.get('error','')\"" +printf "config ddns 'ddns'\n\toption enabled '0'\n" > $T/etc/config/shareos +check "switched off: nothing" "run update | js \"d.get('skipped')=='off'\"" + +echo "test-ddns: $pass passed, $failed failed" +[ $failed = 0 ] diff --git a/tools/i18n/ar.tsv b/tools/i18n/ar.tsv index 50a39b7..e53ce91 100644 --- a/tools/i18n/ar.tsv +++ b/tools/i18n/ar.tsv @@ -1636,3 +1636,60 @@ P323 إيقاف $1 E1269 الجهاز والوصول E1270 النجمة تجعل التطبيق مفضلاً – تظهر المفضلة أعلى «ShareOS الخاص بي». E1271 عرض كل التنبيهات +E1272 شهادات خاصة +E1273 تحديث DNS الديناميكي +E1274 رمز API (ZONE:READ، DNS:EDIT) +E1275 عنوان التحديث ({host} {ip} {ip6}) +E1276 عنوان مخصص (dyndns2) +E1277 – اختر – +E1278 محفوظ – اتركه فارغًا للإبقاء عليه +E1279 يُبلغ الجهاز عن عنوانه العام كل 5 دقائق عند تغيّره. كثير من الاتصالات (DS-Lite، الألياف، الجوال) ليس لها عنوان IPv4 خاص – استخدم حينها IPv6. للوصول من الخارج يجب أن يعيد الموجّه توجيه المنفذ (أو يفتحه لـ IPv6). +E1280 آخر إبلاغ +E1281 آخر فحص +E1282 لم يُبلَّغ بعد – قريبًا. +E1283 DNS الديناميكي متوقف. +E1284 جهة إصدار الشهادات (CA) +E1285 يستخدمها هذا الجهاز +E1286 البصمة +E1287 الشهادة الجذرية +E1288 إعادة إصدار شهادة الجهاز +E1289 استخدام لهذا الجهاز +E1290 ثبّت الشهادة الجذرية مرة واحدة كموثوقة على الحاسوب والهاتف (Windows: نقر مزدوج ← «جهات الترخيص الجذرية الموثوقة»، Android/iOS: الإعدادات ← الأمان). بعدها تعمل كل شهادات هذه الجهة دون تحذير – للجهاز ولأجهزة أخرى في الشبكة المنزلية. +E1291 تصدر جهة شهادات خاصة شهادات للجهاز ولأجهزة أخرى في الشبكة المنزلية (مثل +E1292 ). تُثبَّت شهادتها الجذرية مرة واحدة على الأجهزة – وبعدها لا تظهر تحذيرات الشهادات. +E1293 إنشاء جهة الشهادات +E1294 جارٍ جلب الشهادة – يستغرق حتى 3 دقائق … +E1295 يُفحص يوميًا ويُجدَّد قبل انتهائه بـ 30 يومًا. +E1296 إيقاف +E1297 شهادة لـ +E1298 تثق بها كل المتصفحات. يتم التحقق عبر سجل DNS لدى مزوّد DNS الديناميكي – لا حاجة لفتح أي منفذ. +E1299 البريد الإلكتروني (اختياري، لإشعارات LET'S ENCRYPT) +E1300 وضع الاختبار (جهة اختبار، تحذّر المتصفحات) +E1301 جلب الشهادة +E1302 يتطلب DNS ديناميكيًا مع DuckDNS أو Cloudflare (أعلاه). +E1303 شهادة لجهاز آخر +E1304 أسماء المضيفين وعناوين IP (مفصولة بفواصل) +E1305 صالحة 825 يومًا. يمكن أيضًا استخدام أحرف بدل مثل +E1306 . نزّل الشهادة والمفتاح وأدخلهما في الجهاز. +E1307 إصدار +E1308 أسماء المضيفين +E1309 صالحة حتى +E1310 المفتاح +E1311 جهة خاصة +E1312 نشط (اختبار) +E1313 يرجى إدخال المزوّد والاسم. +E1314 يرجى إدخال الاسم وأسماء المضيفين. +E1315 تم الحفظ – يجري الإبلاغ عن العنوان. +E1316 جارٍ الإبلاغ … +E1317 تم إنشاء جهة الشهادات. +E1318 تم إصدار الشهادة. +E1319 استخدام شهادة من الجهة الخاصة؟ +E1320 يحصل الجهاز على شهادة من جهته الخاصة لاسم الجهاز واسم ‎.local وعنوان IP؛ وتحل محل الحالية. دون تثبيت الشهادة الجذرية يستمر المتصفح في التحذير. +E1321 استخدام +E1322 تم تثبيت الشهادة – تعيد الصفحة الاتصال. +E1323 حذف الشهادة؟ +P324 تُحذف الشهادة «$1» ومفتاحها من الجهاز. تحتفظ الأجهزة التي تستخدمها بنسختها. +E1324 إيقاف Let's Encrypt؟ +E1325 يعود الجهاز بعدها إلى شهادة موقّعة ذاتيًا. +E1326 تم الإيقاف – تعيد الصفحة الاتصال. +E1327 شهادة Let's Encrypt نشطة – تعيد الصفحة الاتصال. diff --git a/tools/i18n/bn.tsv b/tools/i18n/bn.tsv index a00225d..84337e2 100644 --- a/tools/i18n/bn.tsv +++ b/tools/i18n/bn.tsv @@ -1636,3 +1636,60 @@ P323 বন্ধ $1 E1269 ডিভাইস ও অ্যাক্সেস E1270 তারকা দিয়ে অ্যাপ প্রিয় হয় – প্রিয়গুলি "আমার ShareOS"-এর উপরে দেখায়। E1271 সব বিজ্ঞপ্তি দেখান +E1272 নিজস্ব সার্টিফিকেট +E1273 ডায়নামিক DNS হালনাগাদ করুন +E1274 API টোকেন (ZONE:READ, DNS:EDIT) +E1275 হালনাগাদ ঠিকানা ({host} {ip} {ip6}) +E1276 নিজস্ব ঠিকানা (dyndns2) +E1277 – বেছে নিন – +E1278 সংরক্ষিত – ফাঁকা রাখলে আগেরটাই থাকবে +E1279 ঠিকানা বদলালে বক্স প্রতি ৫ মিনিটে তার পাবলিক ঠিকানা জানায়। অনেক সংযোগের (DS-Lite, ফাইবার, মোবাইল) নিজস্ব IPv4 ঠিকানা থাকে না – তখন IPv6 নিন। বাইরে থেকে প্রবেশের জন্য রাউটারকে পোর্ট ফরওয়ার্ড করতে হবে (বা IPv6-এর জন্য খুলতে হবে)। +E1280 শেষ জানানো +E1281 শেষ যাচাই +E1282 এখনো জানানো হয়নি – এখনই হবে। +E1283 ডায়নামিক DNS বন্ধ। +E1284 সার্টিফিকেট কর্তৃপক্ষ (CA) +E1285 এই বক্স এটি ব্যবহার করে +E1286 ফিঙ্গারপ্রিন্ট +E1287 রুট সার্টিফিকেট +E1288 বক্সের সার্টিফিকেট আবার ইস্যু করুন +E1289 এই বক্সের জন্য ব্যবহার করুন +E1290 রুট সার্টিফিকেট একবার পিসি ও ফোনে বিশ্বস্ত হিসেবে ইনস্টল করুন (Windows: ডাবল-ক্লিক → “Trusted Root Certification Authorities”, Android/iOS: সেটিংস → নিরাপত্তা)। এরপর এই CA-র সব সার্টিফিকেট সতর্কবার্তা ছাড়াই চলে – বক্স ও হোম নেটওয়ার্কের অন্য ডিভাইসের জন্য। +E1291 নিজস্ব সার্টিফিকেট কর্তৃপক্ষ বক্স ও হোম নেটওয়ার্কের অন্য ডিভাইসের জন্য সার্টিফিকেট ইস্যু করে (যেমন +E1292 ). এর রুট সার্টিফিকেট ডিভাইসে একবার ইনস্টল করা হয় – এরপর আর সার্টিফিকেট সতর্কবার্তা আসে না। +E1293 সার্টিফিকেট কর্তৃপক্ষ তৈরি করুন +E1294 সার্টিফিকেট আনা হচ্ছে – এতে ৩ মিনিট পর্যন্ত লাগে … +E1295 প্রতিদিন যাচাই হয় এবং মেয়াদ শেষের ৩০ দিন আগে নবায়ন হয়। +E1296 বন্ধ করুন +E1297 এর জন্য সার্টিফিকেট: +E1298 , যা সব ব্রাউজার বিশ্বাস করে। যাচাই হয় ডায়নামিক DNS প্রদানকারীর DNS রেকর্ডের মাধ্যমে – কোনো পোর্ট খুলতে হয় না। +E1299 ই-মেইল (ঐচ্ছিক, LET'S ENCRYPT-এর বিজ্ঞপ্তির জন্য) +E1300 টেস্ট মোড (টেস্ট CA, ব্রাউজার সতর্ক করবে) +E1301 সার্টিফিকেট আনুন +E1302 DuckDNS বা Cloudflare সহ ডায়নামিক DNS লাগবে (উপরে)। +E1303 অন্য ডিভাইসের জন্য সার্টিফিকেট +E1304 হোস্টনাম ও IP ঠিকানা (কমা দিয়ে আলাদা) +E1305 ৮২৫ দিন বৈধ। ওয়াইল্ডকার্ড যেমন +E1306 -ও সম্ভব। সার্টিফিকেট ও কী ডাউনলোড করে ডিভাইসে বসান। +E1307 ইস্যু করুন +E1308 হোস্টনাম +E1309 বৈধ পর্যন্ত +E1310 কী +E1311 নিজস্ব CA +E1312 সক্রিয় (টেস্ট) +E1313 অনুগ্রহ করে প্রদানকারী ও নাম দিন। +E1314 অনুগ্রহ করে নাম ও হোস্টনাম দিন। +E1315 সংরক্ষিত – ঠিকানা জানানো হচ্ছে। +E1316 জানানো হচ্ছে … +E1317 সার্টিফিকেট কর্তৃপক্ষ তৈরি হয়েছে। +E1318 সার্টিফিকেট ইস্যু হয়েছে। +E1319 নিজস্ব CA-র সার্টিফিকেট ব্যবহার করবেন? +E1320 বক্স তার নিজস্ব CA থেকে ডিভাইসের নাম, .local নাম ও IP ঠিকানার জন্য সার্টিফিকেট পাবে; এটি বর্তমানটিকে বদলে দেবে। রুট সার্টিফিকেট ইনস্টল না থাকলে ব্রাউজার সতর্ক করতেই থাকবে। +E1321 ব্যবহার করুন +E1322 সার্টিফিকেট ইনস্টল হয়েছে – পেজ আবার সংযুক্ত হচ্ছে। +E1323 সার্টিফিকেট মুছবেন? +P324 সার্টিফিকেট “$1” ও তার কী বক্স থেকে মোছা হবে। যেসব ডিভাইস এটি ব্যবহার করছে, তারা নিজেদের কপি রাখবে। +E1324 Let's Encrypt বন্ধ করবেন? +E1325 এরপর বক্স আবার স্ব-স্বাক্ষরিত সার্টিফিকেট ব্যবহার করবে। +E1326 বন্ধ করা হয়েছে – পেজ আবার সংযুক্ত হচ্ছে। +E1327 Let's Encrypt সার্টিফিকেট সক্রিয় – পেজ আবার সংযুক্ত হচ্ছে। diff --git a/tools/i18n/es.tsv b/tools/i18n/es.tsv index ed0285d..0082c2d 100644 --- a/tools/i18n/es.tsv +++ b/tools/i18n/es.tsv @@ -1636,3 +1636,60 @@ P323 apagado $1 E1269 EQUIPO Y ACCESO E1270 La estrella convierte una app en favorita: las favoritas aparecen arriba en «Mi ShareOS». E1271 Mostrar todos los avisos +E1272 CERTIFICADOS PROPIOS +E1273 Actualizar DNS dinámico +E1274 TOKEN DE API (ZONA:LEER, DNS:EDITAR) +E1275 URL DE ACTUALIZACIÓN ({host} {ip} {ip6}) +E1276 URL propia (dyndns2) +E1277 – elegir – +E1278 guardado – déjalo vacío para conservarlo +E1279 La caja comunica su dirección pública cada 5 minutos cuando ha cambiado. Muchas conexiones (DS-Lite, fibra, móvil) no tienen dirección IPv4 propia – usa entonces IPv6. Para acceder desde fuera, el router debe reenviar el puerto (o abrirlo en IPv6). +E1280 Último aviso +E1281 Última comprobación +E1282 Aún sin aviso – llega en breve. +E1283 El DNS dinámico está desactivado. +E1284 AUTORIDAD DE CERTIFICACIÓN (CA) +E1285 esta caja la usa +E1286 Huella +E1287 Certificado raíz +E1288 Reemitir el certificado de la caja +E1289 Usar para esta caja +E1290 Instala una vez el certificado raíz como de confianza en el PC y el móvil (Windows: doble clic → «Entidades de certificación raíz de confianza», Android/iOS: Ajustes → Seguridad). Después, todos los certificados de esta CA funcionan sin aviso – para la caja y otros dispositivos de la red doméstica. +E1291 Una autoridad de certificación propia emite certificados para la caja y otros dispositivos de la red doméstica (p. ej. +E1292 ). Su certificado raíz se instala una vez en los dispositivos – después ya no hay avisos de certificado. +E1293 Crear autoridad de certificación +E1294 Obteniendo el certificado – tarda hasta 3 minutos … +E1295 Se comprueba a diario y se renueva 30 días antes de caducar. +E1296 Desactivar +E1297 Un certificado para +E1298 , en el que confían todos los navegadores. La comprobación se hace con un registro DNS en el proveedor de DNS dinámico – no hace falta abrir ningún puerto. +E1299 CORREO (OPCIONAL, PARA AVISOS DE LET'S ENCRYPT) +E1300 Modo de prueba (CA de prueba, los navegadores avisan) +E1301 Obtener certificado +E1302 Necesita DNS dinámico con DuckDNS o Cloudflare (arriba). +E1303 CERTIFICADO PARA OTRO DISPOSITIVO +E1304 NOMBRES DE HOST Y DIRECCIONES IP (SEPARADOS POR COMAS) +E1305 Válido 825 días. También comodines como +E1306 también son posibles. Descarga el certificado y la clave e introdúcelos en el dispositivo. +E1307 Emitir +E1308 NOMBRES DE HOST +E1309 VÁLIDO HASTA +E1310 Clave +E1311 CA propia +E1312 activo (prueba) +E1313 Indica el proveedor y el nombre. +E1314 Indica el nombre y los nombres de host. +E1315 Guardado – se está comunicando la dirección. +E1316 Comunicando … +E1317 Autoridad de certificación creada. +E1318 Certificado emitido. +E1319 ¿Usar un certificado de la CA propia? +E1320 La caja recibe un certificado de su propia CA para el nombre del dispositivo, el nombre .local y la dirección IP; sustituye al actual. Sin el certificado raíz instalado, el navegador sigue avisando. +E1321 Usar +E1322 Certificado instalado – la página se reconecta. +E1323 ¿Eliminar el certificado? +P324 El certificado «$1» y su clave se eliminan de la caja. Los dispositivos que ya lo usan conservan su copia. +E1324 ¿Desactivar Let's Encrypt? +E1325 La caja vuelve a usar un certificado autofirmado. +E1326 Desactivado – la página se reconecta. +E1327 Certificado de Let's Encrypt activo – la página se reconecta. diff --git a/tools/i18n/fr.tsv b/tools/i18n/fr.tsv index 6b1af10..d75f346 100644 --- a/tools/i18n/fr.tsv +++ b/tools/i18n/fr.tsv @@ -1636,3 +1636,60 @@ P323 arrêt $1 E1269 APPAREIL & ACCÈS E1270 L’étoile fait d’une app un favori – les favoris apparaissent en haut de « Mon ShareOS ». E1271 Afficher toutes les notifications +E1272 CERTIFICATS PROPRES +E1273 Mettre à jour le DNS dynamique +E1274 JETON API (ZONE:LECTURE, DNS:MODIFICATION) +E1275 URL DE MISE À JOUR ({host} {ip} {ip6}) +E1276 URL personnalisée (dyndns2) +E1277 – choisir – +E1278 enregistré – laisser vide pour le conserver +E1279 La box signale son adresse publique toutes les 5 minutes lorsqu'elle a changé. Beaucoup de connexions (DS-Lite, fibre, mobile) n'ont pas d'adresse IPv4 propre – utilisez alors IPv6. Pour un accès depuis l'extérieur, le routeur doit rediriger le port (ou l'ouvrir en IPv6). +E1280 Dernier signalement +E1281 Dernière vérification +E1282 Pas encore signalé – cela arrive. +E1283 Le DNS dynamique est désactivé. +E1284 AUTORITÉ DE CERTIFICATION (CA) +E1285 utilisée par cette box +E1286 Empreinte +E1287 Certificat racine +E1288 Réémettre le certificat de la box +E1289 Utiliser pour cette box +E1290 Installez une fois le certificat racine comme fiable sur le PC et le téléphone (Windows : double-clic → « Autorités de certification racines de confiance », Android/iOS : Réglages → Sécurité). Ensuite, tous les certificats de cette CA fonctionnent sans avertissement – pour la box et les autres appareils du réseau domestique. +E1291 Une autorité de certification propre émet des certificats pour la box et les autres appareils du réseau domestique (p. ex. +E1292 ). Son certificat racine est installé une fois sur les appareils – ensuite, plus d'avertissements de certificat. +E1293 Créer l'autorité de certification +E1294 Obtention du certificat – cela prend jusqu'à 3 minutes … +E1295 Vérifié chaque jour et renouvelé 30 jours avant expiration. +E1296 Désactiver +E1297 Un certificat pour +E1298 , reconnu par tous les navigateurs. La vérification passe par un enregistrement DNS chez le fournisseur DNS dynamique – aucun port ne doit être ouvert. +E1299 E-MAIL (FACULTATIF, POUR LES AVIS DE LET'S ENCRYPT) +E1300 Mode test (CA de test, les navigateurs avertissent) +E1301 Obtenir le certificat +E1302 Nécessite le DNS dynamique avec DuckDNS ou Cloudflare (ci-dessus). +E1303 CERTIFICAT POUR UN AUTRE APPAREIL +E1304 NOMS D'HÔTE ET ADRESSES IP (SÉPARÉS PAR DES VIRGULES) +E1305 Valable 825 jours. Les jokers comme +E1306 sont aussi possibles. Téléchargez le certificat et la clé et saisissez-les dans l'appareil. +E1307 Émettre +E1308 NOMS D'HÔTE +E1309 VALABLE JUSQU'AU +E1310 Clé +E1311 CA propre +E1312 actif (test) +E1313 Veuillez indiquer le fournisseur et le nom. +E1314 Veuillez indiquer le nom et les noms d'hôte. +E1315 Enregistré – l'adresse est signalée. +E1316 Signalement … +E1317 Autorité de certification créée. +E1318 Certificat émis. +E1319 Utiliser un certificat de la CA propre ? +E1320 La box reçoit un certificat de sa propre CA pour le nom de l'appareil, le nom .local et l'adresse IP ; il remplace l'actuel. Sans certificat racine installé, le navigateur continue d'avertir. +E1321 Utiliser +E1322 Certificat installé – la page se reconnecte. +E1323 Supprimer le certificat ? +P324 Le certificat « $1 » et sa clé sont supprimés de la box. Les appareils qui l'utilisent déjà gardent leur copie. +E1324 Désactiver Let's Encrypt ? +E1325 La box utilise ensuite de nouveau un certificat auto-signé. +E1326 Désactivé – la page se reconnecte. +E1327 Certificat Let's Encrypt actif – la page se reconnecte. diff --git a/tools/i18n/hi.tsv b/tools/i18n/hi.tsv index 1398ad8..5793f14 100644 --- a/tools/i18n/hi.tsv +++ b/tools/i18n/hi.tsv @@ -1636,3 +1636,60 @@ P323 बंद $1 E1269 डिवाइस और पहुँच E1270 स्टार से ऐप पसंदीदा बनता है – पसंदीदा "मेरा ShareOS" के ऊपर दिखते हैं। E1271 सभी सूचनाएँ दिखाएँ +E1272 अपने प्रमाणपत्र +E1273 डायनामिक DNS अपडेट करें +E1274 API टोकन (ZONE:READ, DNS:EDIT) +E1275 अपडेट पता ({host} {ip} {ip6}) +E1276 अपना पता (dyndns2) +E1277 – चुनें – +E1278 सहेजा गया – खाली छोड़ें तो वही रहेगा +E1279 पता बदलने पर बॉक्स हर 5 मिनट में अपना सार्वजनिक पता भेजता है। कई कनेक्शनों (DS-Lite, फ़ाइबर, मोबाइल) का अपना IPv4 पता नहीं होता – तब IPv6 लें। बाहर से पहुँच के लिए राउटर को पोर्ट फ़ॉरवर्ड करना होगा (या IPv6 के लिए खोलना होगा)। +E1280 पिछली बार भेजा +E1281 पिछली जाँच +E1282 अभी तक नहीं भेजा – बस आने वाला है। +E1283 डायनामिक DNS बंद है। +E1284 प्रमाणपत्र प्राधिकरण (CA) +E1285 यह बॉक्स इसका उपयोग करता है +E1286 फ़िंगरप्रिंट +E1287 रूट प्रमाणपत्र +E1288 बॉक्स का प्रमाणपत्र फिर से जारी करें +E1289 इस बॉक्स के लिए उपयोग करें +E1290 रूट प्रमाणपत्र को PC और फ़ोन पर एक बार विश्वसनीय के रूप में इंस्टॉल करें (Windows: डबल-क्लिक → “Trusted Root Certification Authorities”, Android/iOS: सेटिंग्स → सुरक्षा)। इसके बाद इस CA के सभी प्रमाणपत्र बिना चेतावनी चलते हैं – बॉक्स और घरेलू नेटवर्क के अन्य उपकरणों के लिए। +E1291 अपना प्रमाणपत्र प्राधिकरण बॉक्स और घरेलू नेटवर्क के अन्य उपकरणों के लिए प्रमाणपत्र जारी करता है (जैसे +E1292 ). इसका रूट प्रमाणपत्र उपकरणों पर एक बार इंस्टॉल होता है – इसके बाद प्रमाणपत्र चेतावनियाँ नहीं आतीं। +E1293 प्रमाणपत्र प्राधिकरण बनाएँ +E1294 प्रमाणपत्र लाया जा रहा है – इसमें 3 मिनट तक लगते हैं … +E1295 रोज़ जाँचा जाता है और समाप्ति से 30 दिन पहले नवीनीकृत होता है। +E1296 बंद करें +E1297 इसके लिए प्रमाणपत्र: +E1298 , जिस पर सभी ब्राउज़र भरोसा करते हैं। जाँच डायनामिक DNS प्रदाता के DNS रिकॉर्ड से होती है – कोई पोर्ट खोलना ज़रूरी नहीं। +E1299 ई-मेल (वैकल्पिक, LET'S ENCRYPT की सूचनाओं के लिए) +E1300 टेस्ट मोड (टेस्ट CA, ब्राउज़र चेतावनी देंगे) +E1301 प्रमाणपत्र प्राप्त करें +E1302 DuckDNS या Cloudflare के साथ डायनामिक DNS चाहिए (ऊपर)। +E1303 दूसरे उपकरण के लिए प्रमाणपत्र +E1304 होस्ट नाम और IP पते (कॉमा से अलग) +E1305 825 दिन तक मान्य। वाइल्डकार्ड जैसे +E1306 भी संभव हैं। प्रमाणपत्र और कुंजी डाउनलोड करें और उपकरण में डालें। +E1307 जारी करें +E1308 होस्ट नाम +E1309 मान्य तक +E1310 कुंजी +E1311 अपना CA +E1312 सक्रिय (टेस्ट) +E1313 कृपया प्रदाता और नाम दें। +E1314 कृपया नाम और होस्ट नाम दें। +E1315 सहेजा गया – पता भेजा जा रहा है। +E1316 भेजा जा रहा है … +E1317 प्रमाणपत्र प्राधिकरण बनाया गया। +E1318 प्रमाणपत्र जारी किया गया। +E1319 अपने CA का प्रमाणपत्र उपयोग करें? +E1320 बॉक्स को अपने CA से डिवाइस नाम, .local नाम और IP पते के लिए प्रमाणपत्र मिलेगा; यह मौजूदा को बदल देगा। रूट प्रमाणपत्र इंस्टॉल न होने पर ब्राउज़र चेतावनी देता रहेगा। +E1321 उपयोग करें +E1322 प्रमाणपत्र इंस्टॉल हुआ – पेज फिर से जुड़ रहा है। +E1323 प्रमाणपत्र हटाएँ? +P324 प्रमाणपत्र “$1” और उसकी कुंजी बॉक्स से हटाई जाएगी। जो उपकरण इसे पहले से उपयोग करते हैं, वे अपनी प्रति रखेंगे। +E1324 Let's Encrypt बंद करें? +E1325 इसके बाद बॉक्स फिर से स्व-हस्ताक्षरित प्रमाणपत्र उपयोग करेगा। +E1326 बंद किया गया – पेज फिर से जुड़ रहा है। +E1327 Let's Encrypt प्रमाणपत्र सक्रिय – पेज फिर से जुड़ रहा है। diff --git a/tools/i18n/pt.tsv b/tools/i18n/pt.tsv index 332a435..2f6e67a 100644 --- a/tools/i18n/pt.tsv +++ b/tools/i18n/pt.tsv @@ -1636,3 +1636,60 @@ P323 desliga $1 E1269 EQUIPAMENTO E ACESSO E1270 A estrela torna um app favorito – os favoritos aparecem no topo de “Meu ShareOS”. E1271 Mostrar todos os avisos +E1272 CERTIFICADOS PRÓPRIOS +E1273 Atualizar DNS dinâmico +E1274 TOKEN DA API (ZONA:LER, DNS:EDITAR) +E1275 URL DE ATUALIZAÇÃO ({host} {ip} {ip6}) +E1276 URL própria (dyndns2) +E1277 – escolher – +E1278 guardado – deixe vazio para mantê-lo +E1279 A caixa comunica o seu endereço público a cada 5 minutos quando este mudou. Muitas ligações (DS-Lite, fibra, móvel) não têm endereço IPv4 próprio – use então IPv6. Para acesso a partir de fora, o router tem de encaminhar a porta (ou abri-la em IPv6). +E1280 Último envio +E1281 Última verificação +E1282 Ainda sem envio – já a seguir. +E1283 O DNS dinâmico está desligado. +E1284 AUTORIDADE DE CERTIFICAÇÃO (CA) +E1285 esta caixa usa-a +E1286 Impressão digital +E1287 Certificado raiz +E1288 Reemitir o certificado da caixa +E1289 Usar para esta caixa +E1290 Instale uma vez o certificado raiz como fidedigno no PC e no telemóvel (Windows: duplo clique → «Autoridades de Certificação de Raiz Fidedigna», Android/iOS: Definições → Segurança). Depois, todos os certificados desta CA funcionam sem aviso – para a caixa e outros dispositivos da rede doméstica. +E1291 Uma autoridade de certificação própria emite certificados para a caixa e outros dispositivos da rede doméstica (p. ex. +E1292 ). O seu certificado raiz é instalado uma vez nos dispositivos – depois deixa de haver avisos de certificado. +E1293 Criar autoridade de certificação +E1294 A obter o certificado – demora até 3 minutos … +E1295 Verificado diariamente e renovado 30 dias antes de expirar. +E1296 Desligar +E1297 Um certificado para +E1298 , em que todos os navegadores confiam. A verificação é feita através de um registo DNS no fornecedor de DNS dinâmico – nenhuma porta precisa de estar aberta. +E1299 E-MAIL (OPCIONAL, PARA AVISOS DA LET'S ENCRYPT) +E1300 Modo de teste (CA de teste, os navegadores avisam) +E1301 Obter certificado +E1302 Precisa de DNS dinâmico com DuckDNS ou Cloudflare (acima). +E1303 CERTIFICADO PARA OUTRO DISPOSITIVO +E1304 NOMES DE ANFITRIÃO E ENDEREÇOS IP (SEPARADOS POR VÍRGULAS) +E1305 Válido 825 dias. Também curingas como +E1306 também são possíveis. Transfira o certificado e a chave e introduza-os no dispositivo. +E1307 Emitir +E1308 NOMES DE ANFITRIÃO +E1309 VÁLIDO ATÉ +E1310 Chave +E1311 CA própria +E1312 ativo (teste) +E1313 Indique o fornecedor e o nome. +E1314 Indique o nome e os nomes de anfitrião. +E1315 Guardado – o endereço está a ser enviado. +E1316 A enviar … +E1317 Autoridade de certificação criada. +E1318 Certificado emitido. +E1319 Usar um certificado da CA própria? +E1320 A caixa recebe um certificado da sua própria CA para o nome do dispositivo, o nome .local e o endereço IP; substitui o atual. Sem o certificado raiz instalado, o navegador continua a avisar. +E1321 Usar +E1322 Certificado instalado – a página volta a ligar-se. +E1323 Eliminar o certificado? +P324 O certificado «$1» e a sua chave são eliminados da caixa. Os dispositivos que já o usam mantêm a sua cópia. +E1324 Desligar o Let's Encrypt? +E1325 A caixa volta a usar um certificado autoassinado. +E1326 Desligado – a página volta a ligar-se. +E1327 Certificado Let's Encrypt ativo – a página volta a ligar-se. diff --git a/tools/i18n/ru.tsv b/tools/i18n/ru.tsv index 1db7ab0..3305b25 100644 --- a/tools/i18n/ru.tsv +++ b/tools/i18n/ru.tsv @@ -1636,3 +1636,60 @@ P323 выкл. $1 E1269 УСТРОЙСТВО И ДОСТУП E1270 Звёздочка делает приложение избранным – избранные показаны вверху «Мой ShareOS». E1271 Показать все уведомления +E1272 СОБСТВЕННЫЕ СЕРТИФИКАТЫ +E1273 Обновлять динамический DNS +E1274 API-ТОКЕН (ZONE:READ, DNS:EDIT) +E1275 АДРЕС ОБНОВЛЕНИЯ ({host} {ip} {ip6}) +E1276 Свой адрес (dyndns2) +E1277 – выберите – +E1278 сохранён – оставьте пустым, чтобы не менять +E1279 Устройство сообщает свой внешний адрес каждые 5 минут, если он изменился. У многих подключений (DS-Lite, оптика, мобильная связь) нет собственного IPv4-адреса – тогда используйте IPv6. Для доступа извне роутер должен перенаправить порт (или открыть его для IPv6). +E1280 Последняя отправка +E1281 Последняя проверка +E1282 Ещё не отправлено – скоро. +E1283 Динамический DNS выключен. +E1284 УДОСТОВЕРЯЮЩИЙ ЦЕНТР (CA) +E1285 используется этим устройством +E1286 Отпечаток +E1287 Корневой сертификат +E1288 Перевыпустить сертификат устройства +E1289 Использовать для этого устройства +E1290 Один раз установите корневой сертификат как доверенный на ПК и телефоне (Windows: двойной щелчок → «Доверенные корневые центры сертификации», Android/iOS: Настройки → Безопасность). После этого все сертификаты этого CA работают без предупреждений – для устройства и других устройств домашней сети. +E1291 Собственный удостоверяющий центр выпускает сертификаты для устройства и других устройств домашней сети (например, +E1292 ). Его корневой сертификат один раз устанавливается на устройства – после этого предупреждений о сертификате больше нет. +E1293 Создать удостоверяющий центр +E1294 Получение сертификата – это занимает до 3 минут … +E1295 Проверяется ежедневно и обновляется за 30 дней до истечения. +E1296 Отключить +E1297 Сертификат для +E1298 , которому доверяют все браузеры. Проверка идёт через DNS-запись у провайдера динамического DNS – открывать порт не нужно. +E1299 E-MAIL (НЕОБЯЗАТЕЛЬНО, ДЛЯ УВЕДОМЛЕНИЙ LET'S ENCRYPT) +E1300 Тестовый режим (тестовый CA, браузеры предупреждают) +E1301 Получить сертификат +E1302 Нужен динамический DNS с DuckDNS или Cloudflare (выше). +E1303 СЕРТИФИКАТ ДЛЯ ДРУГОГО УСТРОЙСТВА +E1304 ИМЕНА ХОСТОВ И IP-АДРЕСА (ЧЕРЕЗ ЗАПЯТУЮ) +E1305 Действителен 825 дней. Подстановочные имена вроде +E1306 тоже возможны. Скачайте сертификат и ключ и укажите их в устройстве. +E1307 Выпустить +E1308 ИМЕНА ХОСТОВ +E1309 ДЕЙСТВИТЕЛЕН ДО +E1310 Ключ +E1311 собственный CA +E1312 активен (тест) +E1313 Укажите провайдера и имя. +E1314 Укажите имя и имена хостов. +E1315 Сохранено – адрес отправляется. +E1316 Отправляется … +E1317 Удостоверяющий центр создан. +E1318 Сертификат выпущен. +E1319 Использовать сертификат собственного CA? +E1320 Устройство получит сертификат собственного CA для имени, имени .local и IP-адреса; он заменит текущий. Без установленного корневого сертификата браузер будет предупреждать. +E1321 Использовать +E1322 Сертификат установлен – страница переподключается. +E1323 Удалить сертификат? +P324 Сертификат «$1» и его ключ удаляются с устройства. Устройства, которые уже его используют, сохраняют свою копию. +E1324 Отключить Let's Encrypt? +E1325 После этого устройство снова использует самоподписанный сертификат. +E1326 Отключено – страница переподключается. +E1327 Сертификат Let's Encrypt активен – страница переподключается. diff --git a/tools/i18n/zh.tsv b/tools/i18n/zh.tsv index b0768e1..4e29f6e 100644 --- a/tools/i18n/zh.tsv +++ b/tools/i18n/zh.tsv @@ -1636,3 +1636,60 @@ P323 关 $1 E1269 设备与访问 E1270 星标可将应用设为收藏——收藏显示在“我的 ShareOS”顶部。 E1271 显示所有提示 +E1272 自有证书 +E1273 更新动态 DNS +E1274 API 令牌(ZONE:READ、DNS:EDIT) +E1275 更新地址({host} {ip} {ip6}) +E1276 自定义地址 (dyndns2) +E1277 – 请选择 – +E1278 已保存 – 留空则保留 +E1279 公网地址变化时,设备每 5 分钟上报一次。许多线路(DS-Lite、光纤、移动网络)没有独立的 IPv4 地址——此时请使用 IPv6。要从外部访问,路由器需要转发端口(IPv6 则需放行)。 +E1280 上次上报 +E1281 上次检查 +E1282 尚未上报——马上就好。 +E1283 动态 DNS 已关闭。 +E1284 证书颁发机构 (CA) +E1285 本设备在使用 +E1286 指纹 +E1287 根证书 +E1288 重新签发本设备证书 +E1289 用于本设备 +E1290 在电脑和手机上将根证书安装为受信任一次(Windows:双击 →“受信任的根证书颁发机构”,Android/iOS:设置 → 安全)。之后该 CA 签发的所有证书都不再告警——适用于本设备和家庭网络中的其他设备。 +E1291 自有证书颁发机构可为本设备和家庭网络中的其他设备签发证书(例如 +E1292 )。其根证书只需在设备上安装一次——之后不再出现证书告警。 +E1293 创建证书颁发机构 +E1294 正在获取证书——最多需要 3 分钟…… +E1295 每天检查,在到期前 30 天续期。 +E1296 关闭 +E1297 为 +E1298 签发所有浏览器都信任的证书。验证通过动态 DNS 服务商的 DNS 记录完成——无需开放端口。 +E1299 电子邮件(可选,用于接收 LET'S ENCRYPT 通知) +E1300 测试模式(测试 CA,浏览器会告警) +E1301 获取证书 +E1302 需要使用 DuckDNS 或 Cloudflare 的动态 DNS(见上方)。 +E1303 为其他设备签发证书 +E1304 主机名和 IP 地址(逗号分隔) +E1305 有效期 825 天。也可以使用通配符,如 +E1306 。下载证书和密钥并在设备中填入。 +E1307 签发 +E1308 主机名 +E1309 有效期至 +E1310 密钥 +E1311 自有 CA +E1312 已启用(测试) +E1313 请填写服务商和名称。 +E1314 请填写名称和主机名。 +E1315 已保存——正在上报地址。 +E1316 正在上报…… +E1317 证书颁发机构已创建。 +E1318 证书已签发。 +E1319 使用自有 CA 的证书? +E1320 本设备将获得自有 CA 为设备名、.local 名称和 IP 地址签发的证书,替换当前证书。未安装根证书时,浏览器仍会告警。 +E1321 使用 +E1322 证书已安装——页面将重新连接。 +E1323 删除证书? +P324 证书“$1”及其密钥将从本设备删除。已在使用它的设备会保留各自的副本。 +E1324 关闭 Let's Encrypt? +E1325 之后本设备将重新使用自签名证书。 +E1326 已关闭——页面将重新连接。 +E1327 Let's Encrypt 证书已启用——页面将重新连接。