Replace opencode with muxplex, add nvm, JuiceFS, and Nextcloud
- Remove opencode; install nvm, Claude Code, and pi-coding-agent for aicoder - Add muxplex as web terminal service behind Pangolin (auth: none, port 8088) - Add JuiceFS (Docker container with FUSE) backed by S3 + Redis for Nextcloud storage - Add Nextcloud + MariaDB with JuiceFS mount via depends_on chain - Add autoheal container to restart unhealthy services (covers stale FUSE mounts) - Add SSH key for aicoder user, uv for aicoder, cron cleanup script - Pin images: major for stable projects, minor for Pangolin/Gerbil - Query JuiceFS S3 credentials and cache size during init Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
72f691e25c
commit
25d6bbf284
11
init.sh
11
init.sh
|
|
@ -34,7 +34,18 @@ fi
|
|||
|
||||
PUBKEY=$(cat "$PUBKEY_PATH")
|
||||
|
||||
echo ""
|
||||
echo "--- JuiceFS / Nextcloud Storage ---"
|
||||
read -p "S3 endpoint (e.g. https://s3.amazonaws.com): " JUICEFS_S3_ENDPOINT
|
||||
read -p "S3 bucket name: " JUICEFS_S3_BUCKET
|
||||
read -p "S3 access key: " JUICEFS_S3_ACCESS_KEY
|
||||
read -s -p "S3 secret key: " JUICEFS_S3_SECRET_KEY
|
||||
echo
|
||||
read -p "JuiceFS local cache size [50G]: " JUICEFS_CACHE_SIZE
|
||||
JUICEFS_CACHE_SIZE="${JUICEFS_CACHE_SIZE:-50G}"
|
||||
|
||||
export SCRIPT_DIR HOST OLD_USER NEW_USER SSH_TARGET PUBKEY USER_PASSWORD
|
||||
export JUICEFS_S3_ENDPOINT JUICEFS_S3_BUCKET JUICEFS_S3_ACCESS_KEY JUICEFS_S3_SECRET_KEY JUICEFS_CACHE_SIZE
|
||||
|
||||
source "$SCRIPT_DIR/scripts/01_create_user.sh"
|
||||
source "$SCRIPT_DIR/scripts/02_remove_old_user.sh"
|
||||
|
|
|
|||
245
playbook.yml
245
playbook.yml
|
|
@ -7,12 +7,19 @@
|
|||
base_domain:
|
||||
pangolin_domain: "pangolin.{{ base_domain }}"
|
||||
forgejo_domain: "forgejo.{{ base_domain }}"
|
||||
opencode_domain: "opencode.{{ base_domain }}"
|
||||
muxplex_domain: "muxplex.{{ base_domain }}"
|
||||
nextcloud_domain: "nextcloud.{{ base_domain }}"
|
||||
acme_email: "admin@{{ base_domain }}"
|
||||
docker_hosting_dir: "/home/{{ login_user }}/docker_hosting"
|
||||
resources_dir: "{{ playbook_dir }}/resources"
|
||||
login_user: "{{ ansible_env.SUDO_USER | default(ansible_user_id) }}"
|
||||
deb_architecture: "{{ 'amd64' if ansible_architecture == 'x86_64' else 'arm64' if ansible_architecture == 'aarch64' else ansible_architecture }}"
|
||||
ssh_pubkey: ""
|
||||
juicefs_s3_endpoint: ""
|
||||
juicefs_s3_bucket: ""
|
||||
juicefs_s3_access_key: ""
|
||||
juicefs_s3_secret_key: ""
|
||||
juicefs_cache_size: "50G"
|
||||
|
||||
tasks:
|
||||
- name: Update apt cache
|
||||
|
|
@ -48,9 +55,8 @@
|
|||
- python3
|
||||
- python3-pip
|
||||
- python3-venv
|
||||
- nodejs
|
||||
- npm
|
||||
- jq
|
||||
- ttyd
|
||||
state: present
|
||||
|
||||
# --- Server hardening ---
|
||||
|
|
@ -142,12 +148,12 @@
|
|||
port: '21820'
|
||||
proto: udp
|
||||
|
||||
- name: UFW - Allow Docker to reach opencode on host
|
||||
- name: UFW - Allow Docker to reach muxplex on host
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
from_ip: 172.18.0.0/16
|
||||
to_ip: any
|
||||
port: '3000'
|
||||
port: '8088'
|
||||
proto: tcp
|
||||
|
||||
- name: UFW - Enable firewall
|
||||
|
|
@ -231,7 +237,7 @@
|
|||
creates: "/home/{{ login_user }}/.cargo/bin/rustup"
|
||||
become_user: "{{ login_user }}"
|
||||
|
||||
- name: Install uv (Python package manager)
|
||||
- name: Install uv (Python package manager) for login user
|
||||
ansible.builtin.shell:
|
||||
cmd: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
args:
|
||||
|
|
@ -294,6 +300,23 @@
|
|||
ansible.builtin.set_fact:
|
||||
pangolin_secret: "{{ pangolin_secret_result.stdout }}"
|
||||
|
||||
- name: Generate Nextcloud admin password
|
||||
ansible.builtin.shell:
|
||||
cmd: openssl rand -base64 24
|
||||
register: nextcloud_admin_pass_result
|
||||
changed_when: false
|
||||
|
||||
- name: Generate Nextcloud DB password
|
||||
ansible.builtin.shell:
|
||||
cmd: openssl rand -base64 24
|
||||
register: nextcloud_db_pass_result
|
||||
changed_when: false
|
||||
|
||||
- name: Set Nextcloud password facts
|
||||
ansible.builtin.set_fact:
|
||||
nextcloud_admin_pass: "{{ nextcloud_admin_pass_result.stdout }}"
|
||||
nextcloud_db_pass: "{{ nextcloud_db_pass_result.stdout }}"
|
||||
|
||||
# --- Directory structure ---
|
||||
|
||||
- name: Create docker_hosting directory structure
|
||||
|
|
@ -310,6 +333,9 @@
|
|||
- docker_data/pangolin/logs
|
||||
- docker_data/pangolin/traefik
|
||||
- docker_data/pangolin/traefik/logs
|
||||
- docker_data/nextcloud
|
||||
- docker_data/nextcloud/db
|
||||
- docker_data/nextcloud/redis
|
||||
|
||||
- name: Create Forgejo data directory
|
||||
ansible.builtin.file:
|
||||
|
|
@ -354,7 +380,7 @@
|
|||
group: "{{ login_user }}"
|
||||
mode: '0644'
|
||||
|
||||
# --- OpenCode / Claude Code ---
|
||||
# --- aicoder user setup ---
|
||||
|
||||
- name: Create aicoder user
|
||||
ansible.builtin.user:
|
||||
|
|
@ -363,64 +389,181 @@
|
|||
home: /home/aicoder
|
||||
create_home: yes
|
||||
|
||||
- name: Create codeprojects directory
|
||||
- name: Create aicoder directories
|
||||
ansible.builtin.file:
|
||||
path: /home/aicoder/codeprojects
|
||||
path: "/home/aicoder/{{ item }}"
|
||||
state: directory
|
||||
owner: aicoder
|
||||
group: aicoder
|
||||
mode: '0755'
|
||||
loop:
|
||||
- codeprojects
|
||||
- scripts
|
||||
- .ssh
|
||||
|
||||
- name: Install tmux
|
||||
ansible.builtin.apt:
|
||||
name: tmux
|
||||
state: present
|
||||
|
||||
- name: Install opencode for aicoder
|
||||
ansible.builtin.shell:
|
||||
cmd: curl -fsSL https://opencode.ai/install | bash
|
||||
args:
|
||||
creates: /home/aicoder/.opencode/bin/opencode
|
||||
become_user: aicoder
|
||||
|
||||
- name: Install Claude Code for aicoder
|
||||
ansible.builtin.shell:
|
||||
cmd: npm install -g @anthropic-ai/claude-code
|
||||
args:
|
||||
creates: /home/aicoder/.npm-global/lib/node_modules/@anthropic-ai/claude-code
|
||||
become_user: aicoder
|
||||
environment:
|
||||
NPM_CONFIG_PREFIX: /home/aicoder/.npm-global
|
||||
PATH: "/home/aicoder/.npm-global/bin:{{ ansible_env.PATH }}"
|
||||
|
||||
- name: Create opencode systemd service
|
||||
- name: Add SSH key for aicoder
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/opencode.service
|
||||
mode: '0644'
|
||||
content: |
|
||||
[Unit]
|
||||
Description=OpenCode Server
|
||||
After=network.target
|
||||
dest: /home/aicoder/.ssh/authorized_keys
|
||||
content: "{{ ssh_pubkey }}\n"
|
||||
owner: aicoder
|
||||
group: aicoder
|
||||
mode: '0600'
|
||||
when: ssh_pubkey | length > 0
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aicoder
|
||||
WorkingDirectory=/home/aicoder/codeprojects
|
||||
ExecStart=/home/aicoder/.opencode/bin/opencode serve --port 3000 --hostname 0.0.0.0
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
- name: Set aicoder .ssh directory permissions
|
||||
ansible.builtin.file:
|
||||
path: /home/aicoder/.ssh
|
||||
mode: '0700'
|
||||
owner: aicoder
|
||||
group: aicoder
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
- name: Install nvm for aicoder
|
||||
ansible.builtin.shell:
|
||||
cmd: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
|
||||
args:
|
||||
creates: /home/aicoder/.nvm/nvm.sh
|
||||
become_user: aicoder
|
||||
|
||||
- name: Start and enable opencode service
|
||||
- name: Install latest LTS Node.js via nvm
|
||||
ansible.builtin.shell:
|
||||
cmd: bash -c 'source ~/.nvm/nvm.sh && nvm install --lts'
|
||||
args:
|
||||
creates: /home/aicoder/.nvm/alias/lts
|
||||
become_user: aicoder
|
||||
|
||||
- name: Install Claude Code via nvm npm
|
||||
ansible.builtin.shell:
|
||||
cmd: bash -c 'source ~/.nvm/nvm.sh && npm install -g @anthropic-ai/claude-code'
|
||||
become_user: aicoder
|
||||
args:
|
||||
creates: /home/aicoder/.nvm/versions/node
|
||||
|
||||
- name: Install pi-coding-agent via nvm npm
|
||||
ansible.builtin.shell:
|
||||
cmd: bash -c 'source ~/.nvm/nvm.sh && npm install -g @mariozechner/pi-coding-agent'
|
||||
become_user: aicoder
|
||||
|
||||
- name: Install uv for aicoder
|
||||
ansible.builtin.shell:
|
||||
cmd: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
args:
|
||||
creates: /home/aicoder/.local/bin/uv
|
||||
become_user: aicoder
|
||||
|
||||
- name: Install muxplex for aicoder
|
||||
ansible.builtin.shell:
|
||||
cmd: /home/aicoder/.local/bin/uv tool install git+https://github.com/bkrabach/muxplex
|
||||
become_user: aicoder
|
||||
args:
|
||||
creates: /home/aicoder/.local/bin/muxplex
|
||||
|
||||
- name: Configure muxplex to bind to all interfaces
|
||||
ansible.builtin.shell:
|
||||
cmd: /home/aicoder/.local/bin/muxplex config set host 0.0.0.0
|
||||
become_user: aicoder
|
||||
|
||||
- name: Configure muxplex auth to none (Pangolin handles auth)
|
||||
ansible.builtin.shell:
|
||||
cmd: /home/aicoder/.local/bin/muxplex config set auth none
|
||||
become_user: aicoder
|
||||
|
||||
- name: Install muxplex as systemd service
|
||||
ansible.builtin.shell:
|
||||
cmd: /home/aicoder/.local/bin/muxplex service install
|
||||
become_user: aicoder
|
||||
|
||||
- name: Enable and start muxplex service
|
||||
ansible.builtin.systemd:
|
||||
name: opencode
|
||||
name: muxplex
|
||||
state: started
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
scope: user
|
||||
become_user: aicoder
|
||||
|
||||
# --- Start services ---
|
||||
- name: Enable lingering for aicoder (keep user services running)
|
||||
ansible.builtin.shell:
|
||||
cmd: loginctl enable-linger aicoder
|
||||
args:
|
||||
creates: /var/lib/systemd/linger/aicoder
|
||||
|
||||
# --- Cron cleanup script ---
|
||||
|
||||
- name: Create build artifact cleanup script
|
||||
ansible.builtin.copy:
|
||||
dest: /home/aicoder/scripts/cleanup-build-artifacts.sh
|
||||
owner: aicoder
|
||||
group: aicoder
|
||||
mode: '0755'
|
||||
content: |
|
||||
#!/bin/bash
|
||||
|
||||
PROJECTS_DIR="/home/aicoder/codeprojects"
|
||||
MAX_AGE_DAYS=4
|
||||
|
||||
# Rust - remove target directories
|
||||
find "$PROJECTS_DIR" -maxdepth 3 -type d -name "target" -mtime +$MAX_AGE_DAYS -exec rm -rf {} +
|
||||
|
||||
# Node - remove node_modules directories
|
||||
find "$PROJECTS_DIR" -maxdepth 3 -type d -name "node_modules" -mtime +$MAX_AGE_DAYS -exec rm -rf {} +
|
||||
|
||||
- name: Set up cron job for build artifact cleanup
|
||||
ansible.builtin.cron:
|
||||
name: "cleanup build artifacts"
|
||||
user: aicoder
|
||||
minute: "0"
|
||||
hour: "3"
|
||||
job: "/home/aicoder/scripts/cleanup-build-artifacts.sh"
|
||||
|
||||
# --- JuiceFS + Nextcloud directories ---
|
||||
|
||||
- name: Create JuiceFS and Nextcloud data directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ docker_hosting_dir }}/{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ login_user }}"
|
||||
group: "{{ login_user }}"
|
||||
mode: '0755'
|
||||
loop:
|
||||
- docker_data/nextcloud/data
|
||||
- docker_data/juicefs/cache
|
||||
|
||||
# --- Start infrastructure services (Redis needs to be up before JuiceFS format) ---
|
||||
|
||||
- name: Start Redis via docker compose
|
||||
ansible.builtin.shell:
|
||||
cmd: docker compose up -d redis
|
||||
chdir: "{{ docker_hosting_dir }}"
|
||||
|
||||
- name: Wait for Redis to be ready
|
||||
ansible.builtin.shell:
|
||||
cmd: docker exec redis redis-cli ping
|
||||
register: redis_ping
|
||||
until: redis_ping.stdout == "PONG"
|
||||
retries: 10
|
||||
delay: 3
|
||||
|
||||
- name: Format JuiceFS filesystem
|
||||
ansible.builtin.shell:
|
||||
cmd: >
|
||||
docker run --rm --network pangolin
|
||||
juicedata/mount:ce-v1
|
||||
juicefs format
|
||||
--storage s3
|
||||
--bucket {{ juicefs_s3_endpoint }}/{{ juicefs_s3_bucket }}
|
||||
--access-key {{ juicefs_s3_access_key }}
|
||||
--secret-key {{ juicefs_s3_secret_key }}
|
||||
"redis://redis:6379/1"
|
||||
nextcloud-data
|
||||
args:
|
||||
creates: /etc/juicefs-formatted
|
||||
|
||||
- name: Mark JuiceFS as formatted
|
||||
ansible.builtin.file:
|
||||
path: /etc/juicefs-formatted
|
||||
state: touch
|
||||
mode: '0644'
|
||||
|
||||
# --- Start all services ---
|
||||
|
||||
- name: Start all services via docker compose
|
||||
ansible.builtin.shell:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
# Image pinning strategy:
|
||||
# Pangolin/Gerbil: minor-pinned (newer projects, higher risk of breaking changes)
|
||||
# Everything else: major-pinned (get security patches automatically)
|
||||
# Run `docker compose pull && docker compose up -d` to update within pinned range
|
||||
|
||||
name: pangolin
|
||||
|
||||
services:
|
||||
pangolin:
|
||||
image: docker.io/fosrl/pangolin:latest
|
||||
image: docker.io/fosrl/pangolin:1.17
|
||||
container_name: pangolin
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
|
|
@ -14,7 +19,7 @@ services:
|
|||
retries: 15
|
||||
|
||||
gerbil:
|
||||
image: docker.io/fosrl/gerbil:latest
|
||||
image: docker.io/fosrl/gerbil:1.3
|
||||
container_name: gerbil
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
|
|
@ -53,7 +58,7 @@ services:
|
|||
- ./docker_data/pangolin/traefik/logs:/var/log/traefik
|
||||
|
||||
forgejo:
|
||||
image: codeberg.org/forgejo/forgejo:7
|
||||
image: codeberg.org/forgejo/forgejo:15
|
||||
container_name: forgejo
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
|
|
@ -68,6 +73,103 @@ services:
|
|||
- FORGEJO__server__START_SSH_SERVER=false
|
||||
- FORGEJO__server__SSH_PORT=22
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: redis
|
||||
restart: unless-stopped
|
||||
command: redis-server --appendonly yes
|
||||
volumes:
|
||||
- ./docker_data/nextcloud/redis:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: "5s"
|
||||
timeout: "3s"
|
||||
retries: 10
|
||||
|
||||
juicefs:
|
||||
image: juicedata/mount:ce-v1
|
||||
container_name: juicefs
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
command: >
|
||||
juicefs mount
|
||||
--foreground
|
||||
--cache-dir /var/cache/juicefs
|
||||
--cache-size {{ juicefs_cache_size | regex_replace('[^0-9]', '') | int * 1024 }}
|
||||
--buffer-size 300
|
||||
--prefetch 1
|
||||
"redis://redis:6379/1"
|
||||
/mnt/nextcloud
|
||||
devices:
|
||||
- /dev/fuse
|
||||
cap_add:
|
||||
- SYS_ADMIN
|
||||
security_opt:
|
||||
- apparmor:unconfined
|
||||
volumes:
|
||||
- ./docker_data/nextcloud/data:/mnt/nextcloud:rshared
|
||||
- ./docker_data/juicefs/cache:/var/cache/juicefs
|
||||
healthcheck:
|
||||
test: ["CMD", "mountpoint", "-q", "/mnt/nextcloud"]
|
||||
interval: "10s"
|
||||
timeout: "5s"
|
||||
retries: 10
|
||||
|
||||
nextcloud-db:
|
||||
image: mariadb:11
|
||||
container_name: nextcloud-db
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./docker_data/nextcloud/db:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD={{ nextcloud_db_pass }}
|
||||
- MYSQL_DATABASE=nextcloud
|
||||
- MYSQL_USER=nextcloud
|
||||
- MYSQL_PASSWORD={{ nextcloud_db_pass }}
|
||||
command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: "10s"
|
||||
timeout: "5s"
|
||||
retries: 10
|
||||
|
||||
nextcloud:
|
||||
image: nextcloud:33
|
||||
container_name: nextcloud
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
nextcloud-db:
|
||||
condition: service_healthy
|
||||
juicefs:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./docker_data/nextcloud/data:/var/www/html:rshared
|
||||
environment:
|
||||
- MYSQL_HOST=nextcloud-db
|
||||
- MYSQL_DATABASE=nextcloud
|
||||
- MYSQL_USER=nextcloud
|
||||
- MYSQL_PASSWORD={{ nextcloud_db_pass }}
|
||||
- NEXTCLOUD_ADMIN_USER=admin
|
||||
- NEXTCLOUD_ADMIN_PASSWORD={{ nextcloud_admin_pass }}
|
||||
- NEXTCLOUD_TRUSTED_DOMAINS={{ nextcloud_domain }}
|
||||
- OVERWRITEPROTOCOL=https
|
||||
- OVERWRITEHOST={{ nextcloud_domain }}
|
||||
- REDIS_HOST=redis
|
||||
|
||||
autoheal:
|
||||
image: willfarrell/autoheal:1
|
||||
container_name: autoheal
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- AUTOHEAL_CONTAINER_LABEL=all
|
||||
- AUTOHEAL_INTERVAL=30
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
|
|
|
|||
|
|
@ -5,5 +5,13 @@ echo "=== Phase 3: Copying files and running playbook ==="
|
|||
|
||||
rsync -az --exclude '.git' "$SCRIPT_DIR/" "$NEW_USER@pangolin.$HOST:~/server_init/"
|
||||
|
||||
EXTRA_VARS="base_domain=$HOST"
|
||||
EXTRA_VARS="$EXTRA_VARS ssh_pubkey=\"$PUBKEY\""
|
||||
EXTRA_VARS="$EXTRA_VARS juicefs_s3_endpoint=$JUICEFS_S3_ENDPOINT"
|
||||
EXTRA_VARS="$EXTRA_VARS juicefs_s3_bucket=$JUICEFS_S3_BUCKET"
|
||||
EXTRA_VARS="$EXTRA_VARS juicefs_s3_access_key=$JUICEFS_S3_ACCESS_KEY"
|
||||
EXTRA_VARS="$EXTRA_VARS juicefs_s3_secret_key=$JUICEFS_S3_SECRET_KEY"
|
||||
EXTRA_VARS="$EXTRA_VARS juicefs_cache_size=$JUICEFS_CACHE_SIZE"
|
||||
|
||||
ssh -t "$NEW_USER@pangolin.$HOST" \
|
||||
"bash -c 'sudo locale-gen en_US.UTF-8 && if ! command -v ansible &>/dev/null; then echo \"Installing Ansible...\"; sudo apt-get update && sudo apt-get install -y ansible-core python3-pip; fi && echo \"Running Ansible playbook...\" && cd ~/server_init && LC_ALL=en_US.UTF-8 ansible-playbook -i localhost, -c local playbook.yml -e base_domain=\"$HOST\" --ask-become-pass'"
|
||||
"bash -c 'sudo locale-gen en_US.UTF-8 && if ! command -v ansible &>/dev/null; then echo \"Installing Ansible...\"; sudo apt-get update && sudo apt-get install -y ansible-core python3-pip; fi && echo \"Running Ansible playbook...\" && cd ~/server_init && LC_ALL=en_US.UTF-8 ansible-playbook -i localhost, -c local playbook.yml -e \"$EXTRA_VARS\" --ask-become-pass'"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ echo " Server setup complete!"
|
|||
echo "==========================================="
|
||||
echo ""
|
||||
echo " SSH: ssh $NEW_USER@pangolin.$HOST"
|
||||
echo " SSH (aicoder): ssh aicoder@pangolin.$HOST"
|
||||
echo ""
|
||||
echo " Pangolin setup token: $SETUP_TOKEN"
|
||||
echo ""
|
||||
|
|
@ -20,8 +21,15 @@ echo " 1. Go to https://pangolin.$HOST/auth/initial-setup"
|
|||
echo " and enter the setup token above to create your admin account."
|
||||
echo ""
|
||||
echo " 2. In the Pangolin dashboard, add a local site, then create"
|
||||
echo " a public resource for opencode:"
|
||||
echo " resources for the following services:"
|
||||
echo ""
|
||||
echo " Muxplex (web terminal):"
|
||||
echo " - Target: host.docker.internal"
|
||||
echo " - Port: 3000"
|
||||
echo " - Domain: opencode.$HOST"
|
||||
echo " - Port: 8088"
|
||||
echo " - Domain: muxplex.$HOST"
|
||||
echo ""
|
||||
echo " Nextcloud:"
|
||||
echo " - Target: nextcloud"
|
||||
echo " - Port: 80"
|
||||
echo " - Domain: nextcloud.$HOST"
|
||||
echo "==========================================="
|
||||
|
|
|
|||
Loading…
Reference in a new issue