feat: initial OpenCode configuration with custom commands and skills

This commit is contained in:
2026-01-08 10:07:17 +08:00
commit 2d646383b2
18 changed files with 3240 additions and 0 deletions

View File

@@ -0,0 +1,350 @@
# Setup MQTTS with Auto-Renewal Certificate
## Description
Complete workflow to configure MQTTS (MQTT over TLS) for EMQX using acme.sh with automatic certificate renewal. Supports DNS-based certificate validation (Alibaba Cloud DNS API).
## Parameters
- `domain`: MQTT domain name (e.g., mq.example.com)
- `ca`: Certificate Authority (default: zerossl, options: letsencrypt, zerossl)
- `dns_provider`: DNS provider (default: dns_ali, supports acme.sh DNS APIs)
- `emqx_container`: EMQX Docker container name (default: emqx)
- `cert_dir`: Certificate storage directory (default: /root/certs)
## Prerequisites Check
1. Verify DNS resolution for the domain
2. Check if acme.sh is installed
3. Verify DNS API credentials are configured
4. Check if EMQX container is running
5. Verify current container configuration
## Execution Steps
### Phase 1: DNS and Environment Verification
```bash
# Check DNS resolution
dig ${domain} +short
# Get server public IP
curl -s ifconfig.me
# Verify acme.sh installation
~/.acme.sh/acme.sh --version
# Check DNS API configuration
cat ~/.acme.sh/account.conf | grep -E "Ali_Key|Ali_Secret"
# Check EMQX container status
docker ps | grep ${emqx_container}
```
### Phase 2: Certificate Directory Setup
```bash
# Create certificate directory structure
mkdir -p ${cert_dir}/${domain}
chmod 755 ${cert_dir}
chmod 700 ${cert_dir}/${domain}
```
### Phase 3: Certificate Issuance
```bash
# Issue certificate using DNS validation
~/.acme.sh/acme.sh --issue \
--dns ${dns_provider} \
-d ${domain} \
--keylength 2048 \
--server ${ca}
```
### Phase 4: Certificate Installation
```bash
# Install certificate with auto-reload
~/.acme.sh/acme.sh --install-cert \
-d ${domain} \
--cert-file ${cert_dir}/${domain}/cert.pem \
--key-file ${cert_dir}/${domain}/key.pem \
--fullchain-file ${cert_dir}/${domain}/fullchain.pem \
--reloadcmd "docker restart ${emqx_container}"
# Set proper permissions
chmod 755 ${cert_dir}/${domain}
chmod 644 ${cert_dir}/${domain}/cert.pem
chmod 644 ${cert_dir}/${domain}/key.pem
chmod 644 ${cert_dir}/${domain}/fullchain.pem
# Create CA cert symlink
ln -sf ${cert_dir}/${domain}/fullchain.pem ${cert_dir}/${domain}/cacert.pem
```
### Phase 5: Extract Public Key
```bash
# Extract public key from private key
openssl rsa -in ${cert_dir}/${domain}/key.pem -pubout -out ${cert_dir}/${domain}/public.pem 2>/dev/null
chmod 644 ${cert_dir}/${domain}/public.pem
```
### Phase 6: EMQX Container Reconfiguration
```bash
# Backup current container configuration
docker inspect ${emqx_container} > /root/emqx-backup-$(date +%Y%m%d-%H%M%S).json
# Get current container ports and volumes
PORTS=$(docker inspect ${emqx_container} --format '{{range $p, $conf := .NetworkSettings.Ports}}{{if $conf}}-p {{(index $conf 0).HostPort}}:{{$p}} {{end}}{{end}}')
# Stop and remove current container
docker stop ${emqx_container}
docker rm ${emqx_container}
# Recreate container with certificate mount
docker run -d \
--name ${emqx_container} \
--restart unless-stopped \
-p 1883:1883 \
-p 8083:8083 \
-p 8084:8084 \
-p 8883:8883 \
-p 18083:18083 \
-v /root/emqx/data:/opt/emqx/data \
-v /root/emqx/log:/opt/emqx/log \
-v ${cert_dir}/${domain}:/opt/emqx/etc/certs:ro \
emqx/emqx:5.8.8
```
### Phase 7: Verification
```bash
# Wait for container to start
sleep 8
# Check container status
docker ps | grep ${emqx_container}
# Verify certificate files in container
docker exec ${emqx_container} ls -l /opt/emqx/etc/certs/
# Check EMQX listeners
docker exec ${emqx_container} emqx ctl listeners
# Test SSL connection
timeout 10 openssl s_client -connect ${domain}:8883 -servername ${domain} -showcerts 2>/dev/null | openssl x509 -noout -text | grep -E "Subject:|Issuer:|Not Before|Not After|DNS:"
# Verify SSL handshake
timeout 10 openssl s_client -connect ${domain}:8883 -servername ${domain} 2>&1 <<< "Q" | grep -E "Verify return code:|SSL handshake|Protocol|Cipher"
```
### Phase 8: Generate Documentation
```bash
# Extract public key fingerprint
PUB_FP_MD5=$(openssl rsa -in ${cert_dir}/${domain}/key.pem -pubout 2>/dev/null | openssl md5 | awk '{print $2}')
PUB_FP_SHA256=$(openssl rsa -in ${cert_dir}/${domain}/key.pem -pubout 2>/dev/null | openssl sha256 | awk '{print $2}')
# Extract certificate fingerprints
CERT_FP_MD5=$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -fingerprint -md5 | cut -d= -f2)
CERT_FP_SHA1=$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -fingerprint -sha1 | cut -d= -f2)
CERT_FP_SHA256=$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -fingerprint -sha256 | cut -d= -f2)
# Generate fingerprints file
cat > ${cert_dir}/${domain}/fingerprints.txt << EOF
Certificate and Key Fingerprints
Generated: $(date)
=== Private Key Fingerprint ===
MD5: ${PUB_FP_MD5}
SHA256: ${PUB_FP_SHA256}
=== Certificate Fingerprint ===
MD5: ${CERT_FP_MD5}
SHA1: ${CERT_FP_SHA1}
SHA256: ${CERT_FP_SHA256}
=== Certificate Details ===
$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -subject -issuer -dates)
EOF
# Generate README
cat > ${cert_dir}/${domain}/README.txt << 'EOF'
EMQX MQTTS Certificate Files
Domain: ${domain}
Created: $(date +%Y-%m-%d)
CA: ${ca}
Key Length: RSA 2048
Auto-Renewal: Enabled
Files:
- cert.pem: Server certificate
- key.pem: Private key (CONFIDENTIAL)
- public.pem: Public key
- fullchain.pem: Full certificate chain
- cacert.pem: CA certificate (symlink)
- fingerprints.txt: Certificate fingerprints
- README.txt: This file
Client Configuration:
- PC/Mobile: Use system CA (no files needed)
- Embedded: Use fullchain.pem
- Connection: mqtts://${domain}:8883
Security:
⚠️ Keep key.pem secure and confidential
✓ cert.pem and fullchain.pem are safe to distribute
EOF
```
### Phase 9: Create Backup Package
```bash
# Create compressed backup
cd ${cert_dir}
tar czf ${domain}-complete-$(date +%Y%m%d-%H%M%S).tar.gz ${domain}/
# Generate checksums
md5sum ${domain}-complete-*.tar.gz | tail -1 > ${domain}-checksums.txt
sha256sum ${domain}-complete-*.tar.gz | tail -1 >> ${domain}-checksums.txt
```
### Phase 10: Verify Auto-Renewal
```bash
# Check certificate renewal configuration
~/.acme.sh/acme.sh --info -d ${domain}
# List all managed certificates
~/.acme.sh/acme.sh --list
# Verify cron job
crontab -l | grep acme
```
## Post-Installation Summary
Display the following information:
1. Certificate details (domain, validity, CA)
2. EMQX container status and ports
3. MQTTS listener status (port 8883)
4. Public key and certificate fingerprints
5. Client configuration guide (system CA vs fullchain.pem)
6. Backup file location and checksums
7. Auto-renewal schedule
## Client Configuration Guide
### Option 1: System CA (Recommended for PC/Mobile)
No files needed. The operating system's built-in CA trust store will verify the certificate automatically.
**Python Example:**
```python
import paho.mqtt.client as mqtt
import ssl
client = mqtt.Client()
client.username_pw_set("username", "password")
client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2)
client.connect("${domain}", 8883, 60)
```
**Node.js Example:**
```javascript
const mqtt = require('mqtt');
const client = mqtt.connect('mqtts://${domain}:8883', {
username: 'username',
password: 'password',
rejectUnauthorized: true
});
```
### Option 2: fullchain.pem (Recommended for Embedded Devices)
Distribute `fullchain.pem` to clients that cannot access system CA.
**Python Example:**
```python
client.tls_set(ca_certs="fullchain.pem", cert_reqs=ssl.CERT_REQUIRED)
client.connect("${domain}", 8883, 60)
```
**ESP32/Arduino Example:**
```cpp
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
const char* root_ca = "..."; // Content from fullchain.pem
WiFiClientSecure espClient;
PubSubClient client(espClient);
void setup() {
espClient.setCACert(root_ca);
client.setServer("${domain}", 8883);
client.connect("ESP32", "username", "password");
}
```
## Troubleshooting
### DNS Resolution Issues
- Verify A record points to server IP
- Wait 5-10 minutes for DNS propagation
- Test with: `dig ${domain} +short`
### Certificate Validation Failed
- Check system time is synchronized
- Update system CA certificates
- Use fullchain.pem instead of system CA
### Container Start Failed
- Check certificate file permissions
- Verify volume mount paths
- Review container logs: `docker logs ${emqx_container}`
### SSL Connection Refused
- Verify port 8883 is open in firewall
- Check EMQX listener status
- Test with: `openssl s_client -connect ${domain}:8883`
## Maintenance
### Certificate Renewal
Auto-renewal is configured via cron (daily at 00:34). Manual renewal:
```bash
~/.acme.sh/acme.sh --renew -d ${domain} --force
```
### Check Certificate Expiry
```bash
openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -dates
```
### Update Client Certificates
When certificate is renewed, container automatically restarts. No client updates needed unless using public key pinning.
## Security Notes
1. **Private Key (key.pem)**: Keep confidential, never share
2. **Certificate (cert.pem)**: Safe to distribute to clients
3. **Full Chain (fullchain.pem)**: Safe to distribute to clients
4. **Public Key (public.pem)**: Safe to distribute for pinning
5. **Backup Files**: Store in encrypted storage
6. **Auto-Renewal**: Enabled, checks daily, renews 60 days before expiry
## Output Files
- `${cert_dir}/${domain}/cert.pem`: Server certificate
- `${cert_dir}/${domain}/key.pem`: Private key
- `${cert_dir}/${domain}/public.pem`: Public key
- `${cert_dir}/${domain}/fullchain.pem`: Full certificate chain
- `${cert_dir}/${domain}/cacert.pem`: CA certificate (symlink)
- `${cert_dir}/${domain}/fingerprints.txt`: Fingerprints
- `${cert_dir}/${domain}/README.txt`: Documentation
- `${cert_dir}/${domain}-complete-*.tar.gz`: Backup package
- `${cert_dir}/${domain}-checksums.txt`: Package checksums
- `/root/emqx-backup-*.json`: Container configuration backup
## Success Criteria
- ✅ DNS resolves correctly
- ✅ Certificate issued successfully
- ✅ EMQX container running with certificate mount
- ✅ SSL listener on port 8883 active
- ✅ SSL handshake succeeds
- ✅ Auto-renewal configured
- ✅ Documentation generated
- ✅ Backup package created
## Related Commands
- Check certificate info: `openssl x509 -in cert.pem -text -noout`
- Test MQTTS connection: `mosquitto_pub -h ${domain} -p 8883 --cafile fullchain.pem -t test -m hello`
- View EMQX logs: `docker logs -f ${emqx_container}`
- List certificates: `~/.acme.sh/acme.sh --list`
- Force renewal: `~/.acme.sh/acme.sh --renew -d ${domain} --force`