Add OpenCode community-style LXC with web startup
This commit is contained in:
commit
c5aea8a7ad
4 changed files with 599 additions and 0 deletions
28
pve_community/AGENTS.md
Normal file
28
pve_community/AGENTS.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Repo Purpose
|
||||||
|
|
||||||
|
This repository contains small Proxmox LXC helper scripts.
|
||||||
|
|
||||||
|
- `ct/` contains host-side Proxmox creation entrypoints
|
||||||
|
- `install/` contains in-container install logic pushed into the created CT
|
||||||
|
|
||||||
|
## Working Rules
|
||||||
|
|
||||||
|
- Keep changes minimal and shell-first
|
||||||
|
- Prefer aligning host scripts with the `community-scripts` lifecycle when practical
|
||||||
|
- Preserve the documented legacy environment variables in `README.md` even when adding `var_*` support
|
||||||
|
- Use `apply_patch` for manual file edits
|
||||||
|
- Do not add extra tooling or framework files unless they directly support the scripts
|
||||||
|
|
||||||
|
## Script Expectations
|
||||||
|
|
||||||
|
- Host scripts should stay runnable with `bash ct/<name>.sh` on a Proxmox host
|
||||||
|
- In-container installers should stay runnable as root inside the CT
|
||||||
|
- Favor documented Proxmox long options like `--content` and `--perms`
|
||||||
|
- Any new runtime service should be enabled explicitly and remain configurable through environment variables
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Run `bash -n` on changed shell scripts after edits
|
||||||
|
- Update `README.md` when user-facing behavior or environment variables change
|
||||||
84
pve_community/README.md
Normal file
84
pve_community/README.md
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# Personal Proxmox LXC Scripts
|
||||||
|
|
||||||
|
This repo is a small personal version of the `community-scripts` pattern:
|
||||||
|
|
||||||
|
- `ct/` contains host-side Proxmox creation scripts
|
||||||
|
- `install/` contains the in-container install logic
|
||||||
|
|
||||||
|
## OpenCode LXC
|
||||||
|
|
||||||
|
`ct/opencode.sh` creates a Debian LXC and installs the `opencode` CLI for a normal user.
|
||||||
|
|
||||||
|
It now follows the upstream `community-scripts` host-side flow more closely, including the standard `Default Install`, `Advanced Install`, and `User Defaults` entry points.
|
||||||
|
|
||||||
|
### What it does
|
||||||
|
|
||||||
|
- picks a Debian 12 template from your configured Proxmox storages
|
||||||
|
- creates an unprivileged LXC with sensible defaults for a coding box
|
||||||
|
- installs common CLI tooling (`git`, `ripgrep`, `fd`, `curl`, `build-essential`)
|
||||||
|
- installs OpenCode via the official installer
|
||||||
|
- creates a writable `/workspace` directory owned by the `opencode` user
|
||||||
|
- enables a systemd-managed OpenCode web interface on boot
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Run on the Proxmox host as `root`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash ct/opencode.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Example with overrides:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CTID=220 \
|
||||||
|
HOSTNAME=opencode-dev \
|
||||||
|
BRIDGE=vmbr1 \
|
||||||
|
CONTAINER_STORAGE=local-lvm \
|
||||||
|
TEMPLATE_STORAGE=local \
|
||||||
|
MEMORY=8192 \
|
||||||
|
CORES=4 \
|
||||||
|
DISK_GB=24 \
|
||||||
|
OPENCODE_USER=dev \
|
||||||
|
OPENCODE_WEB_PORT=4096 \
|
||||||
|
bash ct/opencode.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use the upstream-style `var_*` overrides, for example `var_cpu`, `var_ram`, `var_disk`, `var_brg`, `var_net`, `var_ctid`, and `var_hostname`.
|
||||||
|
|
||||||
|
### Common Variables
|
||||||
|
|
||||||
|
- `CTID` default `120`
|
||||||
|
- `HOSTNAME` default `opencode`
|
||||||
|
- `CORES` default `2`
|
||||||
|
- `MEMORY` default `4096`
|
||||||
|
- `DISK_GB` default `12`
|
||||||
|
- `BRIDGE` default `vmbr0`
|
||||||
|
- `IP_CONFIG` default `dhcp`
|
||||||
|
- `TEMPLATE_STORAGE` auto-selects first storage with `vztmpl`
|
||||||
|
- `CONTAINER_STORAGE` auto-selects first storage with `rootdir`
|
||||||
|
- `OPENCODE_USER` default `opencode`
|
||||||
|
- `OPENCODE_VERSION` empty means latest release
|
||||||
|
- `OPENCODE_WEB_HOSTNAME` default `0.0.0.0`
|
||||||
|
- `OPENCODE_WEB_PORT` default `4096`
|
||||||
|
- `OPENCODE_SERVER_USERNAME` default `opencode`
|
||||||
|
- `OPENCODE_SERVER_PASSWORD` optional basic-auth password for the web UI
|
||||||
|
- `SSH_PUBLIC_KEY_FILE` optional path to a host public key file to inject into the CT
|
||||||
|
|
||||||
|
### Community-Scripts Behavior
|
||||||
|
|
||||||
|
- supports the standard `community-scripts` settings flow on the Proxmox host
|
||||||
|
- supports `default.vars` and app-default handling from the shared `build.func` base
|
||||||
|
- still accepts the legacy environment variable names documented above for quick one-shot runs
|
||||||
|
|
||||||
|
### After Creation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pct enter 120
|
||||||
|
su - opencode
|
||||||
|
opencode
|
||||||
|
```
|
||||||
|
|
||||||
|
The web interface is started automatically at boot and listens on `http://<ct-ip>:4096` by default.
|
||||||
|
|
||||||
|
Then configure a provider inside OpenCode with `/connect`, or set your provider credentials manually.
|
||||||
364
pve_community/ct/opencode.sh
Normal file
364
pve_community/ct/opencode.sh
Normal file
|
|
@ -0,0 +1,364 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
BUILD_FUNC_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func"
|
||||||
|
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
source <(curl -fsSL "$BUILD_FUNC_URL")
|
||||||
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
|
source <(wget -qO- "$BUILD_FUNC_URL")
|
||||||
|
else
|
||||||
|
printf '[OpenCode] Error: missing required command: curl or wget\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copyright (c) 2026
|
||||||
|
# License: MIT
|
||||||
|
|
||||||
|
APP="OpenCode"
|
||||||
|
DEFAULT_HOSTNAME="opencode"
|
||||||
|
if [[ -n "${var_hostname:-}" ]]; then
|
||||||
|
:
|
||||||
|
elif [[ -n "${HOSTNAME:-}" && "${HOSTNAME}" != "$(hostname)" ]]; then
|
||||||
|
var_hostname="$HOSTNAME"
|
||||||
|
else
|
||||||
|
var_hostname="$DEFAULT_HOSTNAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ONBOOT="${ONBOOT:-1}"
|
||||||
|
TEMPLATE_ARCH="${TEMPLATE_ARCH:-${ARCH:-amd64}}"
|
||||||
|
|
||||||
|
var_tags="${var_tags:-opencode}"
|
||||||
|
var_ctid="${var_ctid:-${CTID:-}}"
|
||||||
|
var_cpu="${var_cpu:-${CORES:-2}}"
|
||||||
|
var_ram="${var_ram:-${MEMORY:-4096}}"
|
||||||
|
var_disk="${var_disk:-${DISK_GB:-12}}"
|
||||||
|
var_os="${var_os:-debian}"
|
||||||
|
var_version="${var_version:-${OSVERSION:-12}}"
|
||||||
|
var_unprivileged="${var_unprivileged:-${UNPRIVILEGED:-1}}"
|
||||||
|
var_brg="${var_brg:-${BRIDGE:-vmbr0}}"
|
||||||
|
var_net="${var_net:-${IP_CONFIG:-dhcp}}"
|
||||||
|
var_gateway="${var_gateway:-${GATEWAY:-}}"
|
||||||
|
var_ns="${var_ns:-${NAMESERVER:-}}"
|
||||||
|
var_searchdomain="${var_searchdomain:-${SEARCHDOMAIN:-}}"
|
||||||
|
var_template_storage="${var_template_storage:-${TEMPLATE_STORAGE:-}}"
|
||||||
|
var_container_storage="${var_container_storage:-${CONTAINER_STORAGE:-}}"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
INSTALL_SCRIPT="${SCRIPT_DIR%/ct}/install/opencode-install.sh"
|
||||||
|
OPENCODE_USER="${OPENCODE_USER:-opencode}"
|
||||||
|
OPENCODE_VERSION="${OPENCODE_VERSION:-}"
|
||||||
|
OPENCODE_WEB_HOSTNAME="${OPENCODE_WEB_HOSTNAME:-0.0.0.0}"
|
||||||
|
OPENCODE_WEB_PORT="${OPENCODE_WEB_PORT:-4096}"
|
||||||
|
OPENCODE_SERVER_USERNAME="${OPENCODE_SERVER_USERNAME:-opencode}"
|
||||||
|
OPENCODE_SERVER_PASSWORD="${OPENCODE_SERVER_PASSWORD:-}"
|
||||||
|
SSH_PUBLIC_KEY_FILE="${SSH_PUBLIC_KEY_FILE:-}"
|
||||||
|
SWAP="${SWAP:-512}"
|
||||||
|
|
||||||
|
header_info "$APP"
|
||||||
|
variables
|
||||||
|
color
|
||||||
|
catch_errors
|
||||||
|
|
||||||
|
clean_password() {
|
||||||
|
local raw="${var_pw:-}"
|
||||||
|
|
||||||
|
[[ -n "$raw" ]] || return 0
|
||||||
|
|
||||||
|
case "$raw" in
|
||||||
|
--password\ *) raw="${raw#--password }" ;;
|
||||||
|
-password\ *) raw="${raw#-password }" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
while [[ "$raw" == -* ]]; do
|
||||||
|
raw="${raw#-}"
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$raw" ]] && printf '%s\n' "$raw"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_template_name() {
|
||||||
|
pveam available --section system | awk -v os="$var_os" -v ver="$var_version" -v arch="$TEMPLATE_ARCH" '
|
||||||
|
$2 ~ ("^" os "-" ver "-standard_.*_" arch "\\.tar\\.(gz|xz|zst)$") {print $2; exit}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
build_net_arg() {
|
||||||
|
local net="name=eth0,bridge=${BRG:-vmbr0},ip=${NET:-dhcp}"
|
||||||
|
|
||||||
|
[[ -n "${GATE:-}" ]] && net+=",gw=${GATE}"
|
||||||
|
[[ -n "${VLAN:-}" ]] && net+=",tag=${VLAN}"
|
||||||
|
[[ -n "${MTU:-}" ]] && net+=",mtu=${MTU}"
|
||||||
|
[[ -n "${MAC:-}" ]] && net+=",hwaddr=${MAC}"
|
||||||
|
|
||||||
|
case "${IPV6_METHOD:-none}" in
|
||||||
|
auto) net+=",ip6=auto" ;;
|
||||||
|
dhcp) net+=",ip6=dhcp" ;;
|
||||||
|
static)
|
||||||
|
[[ -n "${IPV6_STATIC:-}" ]] && net+=",ip6=${IPV6_STATIC}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf '%s\n' "$net"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_features_arg() {
|
||||||
|
if [[ -n "${FEATURES:-}" ]]; then
|
||||||
|
printf '%s\n' "$FEATURES"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local features=()
|
||||||
|
|
||||||
|
if [[ "${ENABLE_NESTING:-1}" == "1" ]]; then
|
||||||
|
features+=("nesting=1")
|
||||||
|
fi
|
||||||
|
if [[ "${CT_TYPE:-1}" == "1" ]]; then
|
||||||
|
features+=("keyctl=1")
|
||||||
|
fi
|
||||||
|
if [[ "${ENABLE_FUSE:-no}" == "yes" ]]; then
|
||||||
|
features+=("fuse=1")
|
||||||
|
fi
|
||||||
|
if [[ "${ENABLE_MKNOD:-0}" == "1" ]]; then
|
||||||
|
features+=("mknod=1")
|
||||||
|
fi
|
||||||
|
if [[ -n "${var_mount_fs:-}" ]]; then
|
||||||
|
features+=("mount=${var_mount_fs}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#features[@]} -gt 0 ]]; then
|
||||||
|
local joined="${features[*]}"
|
||||||
|
printf '%s\n' "${joined// /,}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_custom_ssh_keys() {
|
||||||
|
[[ -n "$SSH_PUBLIC_KEY_FILE" ]] || return 0
|
||||||
|
[[ -f "$SSH_PUBLIC_KEY_FILE" ]] || {
|
||||||
|
msg_error "SSH key file not found: $SSH_PUBLIC_KEY_FILE"
|
||||||
|
exit 116
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_info "Installing custom SSH public keys"
|
||||||
|
pct exec "$CTID" -- sh -c 'mkdir -p /root/.ssh && chmod 700 /root/.ssh'
|
||||||
|
pct push "$CTID" "$SSH_PUBLIC_KEY_FILE" /tmp/opencode-authorized_keys --perms 600 >/dev/null
|
||||||
|
pct exec "$CTID" -- sh -c 'cat /tmp/opencode-authorized_keys >> /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys && rm -f /tmp/opencode-authorized_keys'
|
||||||
|
msg_ok "Installed custom SSH public keys"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_opencode_user() {
|
||||||
|
if id -u "$OPENCODE_USER" >/dev/null 2>&1; then
|
||||||
|
printf '%s\n' "$OPENCODE_USER"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if id -u opencode >/dev/null 2>&1; then
|
||||||
|
printf '%s\n' "opencode"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local uid_1000_user
|
||||||
|
uid_1000_user="$(getent passwd 1000 | cut -d: -f1 || true)"
|
||||||
|
[[ -n "$uid_1000_user" ]] && printf '%s\n' "$uid_1000_user"
|
||||||
|
}
|
||||||
|
|
||||||
|
update_script() {
|
||||||
|
header_info
|
||||||
|
check_container_storage
|
||||||
|
check_container_resources
|
||||||
|
|
||||||
|
msg_info "Updating base system"
|
||||||
|
$STD apt-get update
|
||||||
|
$STD apt-get upgrade -y
|
||||||
|
msg_ok "Base system updated"
|
||||||
|
|
||||||
|
local install_user
|
||||||
|
install_user="$(resolve_opencode_user)"
|
||||||
|
|
||||||
|
if [[ -z "$install_user" ]]; then
|
||||||
|
msg_warn "No OpenCode user found, skipping OpenCode update"
|
||||||
|
msg_ok "Updated successfully!"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_info "Updating OpenCode"
|
||||||
|
if [[ -n "$OPENCODE_VERSION" ]]; then
|
||||||
|
$STD su - "$install_user" -c "curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path --version ${OPENCODE_VERSION}"
|
||||||
|
else
|
||||||
|
$STD su - "$install_user" -c "curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -x "/home/${install_user}/.opencode/bin/opencode" ]]; then
|
||||||
|
ln -sf "/home/${install_user}/.opencode/bin/opencode" /usr/local/bin/opencode
|
||||||
|
fi
|
||||||
|
install -d -m 0755 -o "$install_user" -g "$install_user" /workspace
|
||||||
|
if systemctl list-unit-files opencode-web.service >/dev/null 2>&1; then
|
||||||
|
$STD systemctl daemon-reload
|
||||||
|
$STD systemctl restart opencode-web.service
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_ok "OpenCode updated"
|
||||||
|
msg_ok "Updated successfully!"
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
build_container() {
|
||||||
|
[[ -f "$INSTALL_SCRIPT" ]] || {
|
||||||
|
msg_error "Missing installer script at $INSTALL_SCRIPT"
|
||||||
|
exit 117
|
||||||
|
}
|
||||||
|
|
||||||
|
export CTID="$CT_ID"
|
||||||
|
|
||||||
|
local template_storage="${TEMPLATE_STORAGE:-${var_template_storage:-}}"
|
||||||
|
local container_storage="${CONTAINER_STORAGE:-${var_container_storage:-}}"
|
||||||
|
local template_name
|
||||||
|
local template_ref
|
||||||
|
local net_arg
|
||||||
|
local features_arg
|
||||||
|
local clean_pw
|
||||||
|
local tags
|
||||||
|
local version
|
||||||
|
local install_path="/root/opencode-install.sh"
|
||||||
|
local -a pct_args
|
||||||
|
|
||||||
|
[[ -n "$template_storage" ]] || template_storage="$(pvesm status --content vztmpl | awk 'NR>1 {print $1; exit}')"
|
||||||
|
[[ -n "$container_storage" ]] || container_storage="$(pvesm status --content rootdir | awk 'NR>1 {print $1; exit}')"
|
||||||
|
|
||||||
|
[[ -n "$template_storage" ]] || {
|
||||||
|
msg_error "No storage found for content type 'vztmpl'"
|
||||||
|
exit 118
|
||||||
|
}
|
||||||
|
[[ -n "$container_storage" ]] || {
|
||||||
|
msg_error "No storage found for content type 'rootdir'"
|
||||||
|
exit 119
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_info "Updating template list"
|
||||||
|
pveam update >/dev/null
|
||||||
|
|
||||||
|
template_name="$(resolve_template_name)"
|
||||||
|
[[ -n "$template_name" ]] || {
|
||||||
|
msg_error "No template found for ${var_os}-${var_version} (amd64)"
|
||||||
|
exit 120
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_info "Ensuring template ${template_name} exists on ${template_storage}"
|
||||||
|
if ! pveam list "$template_storage" | awk 'NR>1 {print $2}' | grep -Fxq "$template_name"; then
|
||||||
|
pveam download "$template_storage" "$template_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
template_ref="${template_storage}:vztmpl/${template_name}"
|
||||||
|
net_arg="$(build_net_arg)"
|
||||||
|
features_arg="$(build_features_arg)"
|
||||||
|
clean_pw="$(clean_password || true)"
|
||||||
|
tags="community-script;${var_tags}"
|
||||||
|
|
||||||
|
pct_args=(
|
||||||
|
create "$CTID" "$template_ref"
|
||||||
|
--hostname "$HN"
|
||||||
|
--ostype "$var_os"
|
||||||
|
--unprivileged "$CT_TYPE"
|
||||||
|
--cores "$CORE_COUNT"
|
||||||
|
--memory "$RAM_SIZE"
|
||||||
|
--swap "$SWAP"
|
||||||
|
--rootfs "${container_storage}:${DISK_SIZE}"
|
||||||
|
--net0 "$net_arg"
|
||||||
|
--onboot "$ONBOOT"
|
||||||
|
--tags "$tags"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -n "$features_arg" ]]; then
|
||||||
|
pct_args+=(--features "$features_arg")
|
||||||
|
fi
|
||||||
|
if [[ -n "${var_ns:-}" ]]; then
|
||||||
|
pct_args+=(--nameserver "${var_ns}")
|
||||||
|
fi
|
||||||
|
if [[ -n "${var_searchdomain:-}" ]]; then
|
||||||
|
pct_args+=(--searchdomain "${var_searchdomain}")
|
||||||
|
fi
|
||||||
|
if [[ -n "${CT_TIMEZONE:-}" ]]; then
|
||||||
|
pct_args+=(--timezone "${CT_TIMEZONE}")
|
||||||
|
fi
|
||||||
|
if [[ -n "$clean_pw" ]]; then
|
||||||
|
pct_args+=(--password "$clean_pw")
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_info "Creating LXC Container"
|
||||||
|
pct "${pct_args[@]}"
|
||||||
|
msg_ok "Created LXC Container"
|
||||||
|
|
||||||
|
if [[ "${PROTECT_CT:-no}" == "yes" || "${PROTECT_CT:-0}" == "1" ]]; then
|
||||||
|
pct set "$CTID" --protection 1 >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_info "Starting LXC Container"
|
||||||
|
pct start "$CTID"
|
||||||
|
sleep 5
|
||||||
|
msg_ok "Started LXC Container"
|
||||||
|
|
||||||
|
install_ssh_keys_into_ct || true
|
||||||
|
install_custom_ssh_keys
|
||||||
|
|
||||||
|
msg_info "Pushing OpenCode installer"
|
||||||
|
pct push "$CTID" "$INSTALL_SCRIPT" "$install_path" --perms 755
|
||||||
|
msg_ok "Pushed OpenCode installer"
|
||||||
|
|
||||||
|
msg_info "Installing OpenCode"
|
||||||
|
pct exec "$CTID" -- env \
|
||||||
|
OPENCODE_USER="$OPENCODE_USER" \
|
||||||
|
OPENCODE_VERSION="$OPENCODE_VERSION" \
|
||||||
|
OPENCODE_WEB_HOSTNAME="$OPENCODE_WEB_HOSTNAME" \
|
||||||
|
OPENCODE_WEB_PORT="$OPENCODE_WEB_PORT" \
|
||||||
|
OPENCODE_SERVER_USERNAME="$OPENCODE_SERVER_USERNAME" \
|
||||||
|
OPENCODE_SERVER_PASSWORD="$OPENCODE_SERVER_PASSWORD" \
|
||||||
|
bash "$install_path"
|
||||||
|
msg_ok "Installed OpenCode"
|
||||||
|
|
||||||
|
version="$(pct exec "$CTID" -- su - "$OPENCODE_USER" -c 'opencode --version' 2>/dev/null | tail -n 1 || true)"
|
||||||
|
[[ -n "$version" ]] && export OPENCODE_INSTALLED_VERSION="$version"
|
||||||
|
}
|
||||||
|
|
||||||
|
description() {
|
||||||
|
local version="${OPENCODE_INSTALLED_VERSION:-}"
|
||||||
|
local ct_ip
|
||||||
|
local description
|
||||||
|
|
||||||
|
ct_ip="$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}' || true)"
|
||||||
|
|
||||||
|
description=$(
|
||||||
|
cat <<EOF
|
||||||
|
<div align='center'>
|
||||||
|
<a href='https://github.com/anomalyco/opencode' target='_blank' rel='noopener noreferrer'>
|
||||||
|
<h2 style='font-size: 24px; margin: 20px 0;'>${APP} LXC</h2>
|
||||||
|
</a>
|
||||||
|
<p style='margin: 12px 0;'>OpenCode web-enabled development container.</p>
|
||||||
|
<p style='margin: 12px 0;'>
|
||||||
|
<a href='https://github.com/anomalyco/opencode' target='_blank' rel='noopener noreferrer'>GitHub</a>
|
||||||
|
|
|
||||||
|
<a href='https://opencode.ai/docs/web/' target='_blank' rel='noopener noreferrer'>Web Docs</a>
|
||||||
|
</p>
|
||||||
|
<p style='margin: 12px 0;'>
|
||||||
|
Web UI: ${ct_ip:-Unavailable}:${OPENCODE_WEB_PORT}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
pct set "$CTID" --description "$description" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
msg_ok "Completed successfully!\n"
|
||||||
|
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||||
|
if [[ -n "$version" ]]; then
|
||||||
|
echo -e "${INFO}${YW} Installed version: ${BGN}${version}${CL}"
|
||||||
|
fi
|
||||||
|
echo -e "${INFO}${YW} Next steps:${CL}"
|
||||||
|
echo -e "${TAB}Web UI: http://${ct_ip:-<ct-ip>}:${OPENCODE_WEB_PORT}"
|
||||||
|
echo -e "${TAB}pct enter ${CTID}"
|
||||||
|
echo -e "${TAB}su - ${OPENCODE_USER}"
|
||||||
|
echo -e "${TAB}opencode"
|
||||||
|
echo -e "${INFO}${YW} Configure a provider with /connect or provider environment variables.${CL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
start
|
||||||
|
build_container
|
||||||
|
description
|
||||||
123
pve_community/install/opencode-install.sh
Normal file
123
pve_community/install/opencode-install.sh
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
OPENCODE_USER="${OPENCODE_USER:-opencode}"
|
||||||
|
OPENCODE_VERSION="${OPENCODE_VERSION:-}"
|
||||||
|
OPENCODE_HOME="/home/${OPENCODE_USER}"
|
||||||
|
OPENCODE_BIN="${OPENCODE_HOME}/.opencode/bin/opencode"
|
||||||
|
OPENCODE_WEB_HOSTNAME="${OPENCODE_WEB_HOSTNAME:-0.0.0.0}"
|
||||||
|
OPENCODE_WEB_PORT="${OPENCODE_WEB_PORT:-4096}"
|
||||||
|
OPENCODE_SERVER_USERNAME="${OPENCODE_SERVER_USERNAME:-opencode}"
|
||||||
|
OPENCODE_SERVER_PASSWORD="${OPENCODE_SERVER_PASSWORD:-}"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[opencode-install] %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf '[opencode-install] Error: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run_as_user() {
|
||||||
|
su - "$OPENCODE_USER" -c "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_web_service() {
|
||||||
|
local password_line="# OPENCODE_SERVER_PASSWORD="
|
||||||
|
if [[ -n "$OPENCODE_SERVER_PASSWORD" ]]; then
|
||||||
|
password_line="OPENCODE_SERVER_PASSWORD=${OPENCODE_SERVER_PASSWORD}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >/etc/default/opencode-web <<EOF
|
||||||
|
OPENCODE_WEB_HOSTNAME=${OPENCODE_WEB_HOSTNAME}
|
||||||
|
OPENCODE_WEB_PORT=${OPENCODE_WEB_PORT}
|
||||||
|
OPENCODE_SERVER_USERNAME=${OPENCODE_SERVER_USERNAME}
|
||||||
|
# Set OPENCODE_SERVER_PASSWORD to require HTTP basic auth for web access.
|
||||||
|
${password_line}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >/etc/systemd/system/opencode-web.service <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=OpenCode Web Interface
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=${OPENCODE_USER}
|
||||||
|
Group=${OPENCODE_USER}
|
||||||
|
WorkingDirectory=/workspace
|
||||||
|
Environment=HOME=${OPENCODE_HOME}
|
||||||
|
Environment=BROWSER=/bin/true
|
||||||
|
EnvironmentFile=-/etc/default/opencode-web
|
||||||
|
ExecStart=/bin/sh -lc 'exec /usr/local/bin/opencode web --hostname "${OPENCODE_WEB_HOSTNAME:-0.0.0.0}" --port "${OPENCODE_WEB_PORT:-4096}"'
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now opencode-web.service
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
[[ $EUID -eq 0 ]] || fail "Run as root inside the container"
|
||||||
|
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
log "Updating apt package index"
|
||||||
|
apt-get update
|
||||||
|
|
||||||
|
log "Installing base packages"
|
||||||
|
apt-get install -y \
|
||||||
|
bash-completion \
|
||||||
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
fd-find \
|
||||||
|
git \
|
||||||
|
jq \
|
||||||
|
less \
|
||||||
|
ripgrep \
|
||||||
|
sudo \
|
||||||
|
tar \
|
||||||
|
unzip
|
||||||
|
|
||||||
|
if ! id -u "$OPENCODE_USER" >/dev/null 2>&1; then
|
||||||
|
log "Creating user ${OPENCODE_USER}"
|
||||||
|
useradd --create-home --shell /bin/bash "$OPENCODE_USER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
usermod -aG sudo "$OPENCODE_USER"
|
||||||
|
install -d -m 0755 -o "$OPENCODE_USER" -g "$OPENCODE_USER" /workspace
|
||||||
|
|
||||||
|
if [[ -x /usr/bin/fdfind && ! -e /usr/local/bin/fd ]]; then
|
||||||
|
ln -s /usr/bin/fdfind /usr/local/bin/fd
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Installing OpenCode"
|
||||||
|
if [[ -n "$OPENCODE_VERSION" ]]; then
|
||||||
|
run_as_user "curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path --version ${OPENCODE_VERSION}"
|
||||||
|
else
|
||||||
|
run_as_user "curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -x "$OPENCODE_BIN" ]] || fail "OpenCode binary was not installed"
|
||||||
|
ln -sf "$OPENCODE_BIN" /usr/local/bin/opencode
|
||||||
|
|
||||||
|
log "Configuring OpenCode web service"
|
||||||
|
install_web_service
|
||||||
|
|
||||||
|
cat >/etc/profile.d/opencode-workspace.sh <<EOF
|
||||||
|
export PATH=/usr/local/bin:\$PATH
|
||||||
|
cd /workspace 2>/dev/null || true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log "Installed version: $(run_as_user "opencode --version" | tail -n 1)"
|
||||||
|
log "Web interface: http://$(hostname -I | awk '{print $1}'):${OPENCODE_WEB_PORT}"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue