fix: CSRF race condition, UI containers, Tor ordering, seed layout
- session.rs: use OnceCell for remember_secret to prevent concurrent requests on first boot from generating different HMAC secrets, which caused CSRF token mismatch on every state-changing RPC call (app install, start, stop all failed with "CSRF token missing or invalid") - install.rs: write lnd.conf with Bitcoin RPC credentials before LND container starts (prevents "bitcoin.mainnet must be specified" crash); inject Bitcoin RPC auth into bitcoin-ui nginx.conf; add proper error logging to UI container build/run steps; fix UI containers to use --network=host (they proxy to localhost backend/bitcoin RPC) - Tor: remove After=tor.service from archipelago-tor-helper.path to break systemd ordering cycle that prevented Tor from starting on boot - Seed screen: compact grid layout (2 cols mobile, 4 cols sm+) with tighter padding to fit kiosk displays without scrolling - Dockerfiles: remove nonexistent assets/ COPY from bitcoin-ui, fix electrs-ui to COPY qrcode.js and EXPOSE 50002 (matches nginx.conf) - image-versions.sh: add UI container image variables for registry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
030015fce6
commit
4b0e1cfbe3
@ -13,6 +13,24 @@ use anyhow::{Context, Result};
|
|||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
|
const INSTALL_LOG: &str = "/var/log/archipelago-container-installs.log";
|
||||||
|
|
||||||
|
/// Append a timestamped line to the persistent install log.
|
||||||
|
async fn install_log(msg: &str) {
|
||||||
|
let ts = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC");
|
||||||
|
let line = format!("[{}] {}\n", ts, msg);
|
||||||
|
let _ = tokio::fs::OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(INSTALL_LOG)
|
||||||
|
.await
|
||||||
|
.and_then(|mut f| {
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
Box::pin(async move { f.write_all(line.as_bytes()).await })
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
impl RpcHandler {
|
impl RpcHandler {
|
||||||
/// Install a package from a Docker image.
|
/// Install a package from a Docker image.
|
||||||
/// Security: Image verification, resource limits, network isolation.
|
/// Security: Image verification, resource limits, network isolation.
|
||||||
@ -33,12 +51,14 @@ impl RpcHandler {
|
|||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.ok_or_else(|| anyhow::anyhow!("Missing dockerImage"))?;
|
.ok_or_else(|| anyhow::anyhow!("Missing dockerImage"))?;
|
||||||
|
|
||||||
|
install_log(&format!("INSTALL START: {} (image: {})", package_id, docker_image)).await;
|
||||||
debug!(
|
debug!(
|
||||||
"Installing package {} from image {}",
|
"Installing package {} from image {}",
|
||||||
package_id, docker_image
|
package_id, docker_image
|
||||||
);
|
);
|
||||||
|
|
||||||
if !is_valid_docker_image(docker_image) {
|
if !is_valid_docker_image(docker_image) {
|
||||||
|
install_log(&format!("INSTALL FAIL: {} — invalid image format", package_id)).await;
|
||||||
return Err(anyhow::anyhow!("Invalid Docker image format"));
|
return Err(anyhow::anyhow!("Invalid Docker image format"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,9 +100,11 @@ impl RpcHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pull or verify image
|
// Pull or verify image
|
||||||
|
install_log(&format!("INSTALL PULL: {} — pulling image {}", package_id, docker_image)).await;
|
||||||
let has_local_fallback = self
|
let has_local_fallback = self
|
||||||
.pull_or_verify_image(package_id, docker_image)
|
.pull_or_verify_image(package_id, docker_image)
|
||||||
.await?;
|
.await?;
|
||||||
|
install_log(&format!("INSTALL PULL OK: {} — image ready (local_fallback={})", package_id, has_local_fallback)).await;
|
||||||
|
|
||||||
// Normalize container name for legacy aliases
|
// Normalize container name for legacy aliases
|
||||||
let container_name = match package_id {
|
let container_name = match package_id {
|
||||||
@ -173,6 +195,11 @@ impl RpcHandler {
|
|||||||
self.write_bitcoin_conf(&rpc_user, &rpc_pass).await;
|
self.write_bitcoin_conf(&rpc_user, &rpc_pass).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-install: lnd.conf with Bitcoin RPC credentials
|
||||||
|
if package_id == "lnd" {
|
||||||
|
self.write_lnd_conf(&rpc_user, &rpc_pass).await;
|
||||||
|
}
|
||||||
|
|
||||||
// Pre-install: SearXNG settings.yml (required or container exits immediately)
|
// Pre-install: SearXNG settings.yml (required or container exits immediately)
|
||||||
if package_id == "searxng" {
|
if package_id == "searxng" {
|
||||||
let searx_dir = "/var/lib/archipelago/searxng";
|
let searx_dir = "/var/lib/archipelago/searxng";
|
||||||
@ -244,6 +271,7 @@ impl RpcHandler {
|
|||||||
|
|
||||||
if !run_output.status.success() {
|
if !run_output.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&run_output.stderr);
|
let stderr = String::from_utf8_lossy(&run_output.stderr);
|
||||||
|
install_log(&format!("INSTALL FAIL: {} — podman run failed: {}", package_id, stderr)).await;
|
||||||
// Rollback: remove partially created container
|
// Rollback: remove partially created container
|
||||||
let _ = tokio::process::Command::new("podman")
|
let _ = tokio::process::Command::new("podman")
|
||||||
.args(["rm", "-f", container_name])
|
.args(["rm", "-f", container_name])
|
||||||
@ -305,6 +333,8 @@ impl RpcHandler {
|
|||||||
// Post-install hooks — await completion before returning success
|
// Post-install hooks — await completion before returning success
|
||||||
self.run_post_install_hooks(package_id).await;
|
self.run_post_install_hooks(package_id).await;
|
||||||
|
|
||||||
|
install_log(&format!("INSTALL OK: {} (container: {})", package_id, &container_id[..12.min(container_id.len())])).await;
|
||||||
|
|
||||||
Ok(serde_json::json!({
|
Ok(serde_json::json!({
|
||||||
"success": true,
|
"success": true,
|
||||||
"package_id": package_id,
|
"package_id": package_id,
|
||||||
@ -544,6 +574,47 @@ printtoconsole=1\n",
|
|||||||
info!("Created bitcoin.conf with rpcauth (no plaintext credentials)");
|
info!("Created bitcoin.conf with rpcauth (no plaintext credentials)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write LND config file with Bitcoin RPC credentials.
|
||||||
|
async fn write_lnd_conf(&self, rpc_user: &str, rpc_pass: &str) {
|
||||||
|
let lnd_dir = "/var/lib/archipelago/lnd";
|
||||||
|
let conf_path = format!("{}/lnd.conf", lnd_dir);
|
||||||
|
|
||||||
|
// Don't overwrite existing config (user may have customized it)
|
||||||
|
if tokio::fs::try_exists(&conf_path).await.unwrap_or(false) {
|
||||||
|
info!("lnd.conf already exists, skipping write");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lnd_conf = format!(
|
||||||
|
"\
|
||||||
|
[Application Options]\n\
|
||||||
|
listen=0.0.0.0:9735\n\
|
||||||
|
rpclisten=0.0.0.0:10009\n\
|
||||||
|
restlisten=0.0.0.0:8080\n\
|
||||||
|
debuglevel=info\n\
|
||||||
|
noseedbackup=true\n\
|
||||||
|
\n\
|
||||||
|
[Bitcoin]\n\
|
||||||
|
bitcoin.mainnet=true\n\
|
||||||
|
bitcoin.node=bitcoind\n\
|
||||||
|
\n\
|
||||||
|
[Bitcoind]\n\
|
||||||
|
bitcoind.rpchost=bitcoin-knots:8332\n\
|
||||||
|
bitcoind.rpcuser={user}\n\
|
||||||
|
bitcoind.rpcpass={pass}\n\
|
||||||
|
bitcoind.rpcpolling=true\n\
|
||||||
|
bitcoind.estimatemode=ECONOMICAL\n\
|
||||||
|
\n\
|
||||||
|
[autopilot]\n\
|
||||||
|
autopilot.active=false\n",
|
||||||
|
user = rpc_user,
|
||||||
|
pass = rpc_pass,
|
||||||
|
);
|
||||||
|
let _ = tokio::fs::create_dir_all(lnd_dir).await;
|
||||||
|
let _ = tokio::fs::write(&conf_path, lnd_conf).await;
|
||||||
|
info!("Created lnd.conf with Bitcoin RPC credentials");
|
||||||
|
}
|
||||||
|
|
||||||
/// Run post-install hooks (Nextcloud trusted domains, Bitcoin UI container).
|
/// Run post-install hooks (Nextcloud trusted domains, Bitcoin UI container).
|
||||||
/// Critical hooks (credential setup, config) are awaited; UI container builds are background.
|
/// Critical hooks (credential setup, config) are awaited; UI container builds are background.
|
||||||
async fn run_post_install_hooks(&self, package_id: &str) {
|
async fn run_post_install_hooks(&self, package_id: &str) {
|
||||||
@ -647,54 +718,105 @@ printtoconsole=1\n",
|
|||||||
info!("Nextcloud trusted domains configured for {}", host_ip);
|
info!("Nextcloud trusted domains configured for {}", host_ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build and start companion UI containers for headless services
|
// Pre-build: inject Bitcoin RPC auth into bitcoin-ui nginx.conf
|
||||||
let ui_builds: Vec<(&str, &str, &str, &str)> = match package_id {
|
if matches!(package_id, "bitcoin" | "bitcoin-core" | "bitcoin-knots") {
|
||||||
|
let (rpc_user, rpc_pass) = crate::bitcoin_rpc::bitcoin_rpc_credentials().await;
|
||||||
|
use base64::Engine;
|
||||||
|
let auth_b64 = base64::engine::general_purpose::STANDARD
|
||||||
|
.encode(format!("{}:{}", rpc_user, rpc_pass));
|
||||||
|
for dir in ["/opt/archipelago/docker/bitcoin-ui", "/home/archipelago/archy/docker/bitcoin-ui"] {
|
||||||
|
let conf_path = format!("{}/nginx.conf", dir);
|
||||||
|
if let Ok(content) = tokio::fs::read_to_string(&conf_path).await {
|
||||||
|
if content.contains("__BITCOIN_RPC_AUTH__") {
|
||||||
|
let updated = content.replace("__BITCOIN_RPC_AUTH__", &auth_b64);
|
||||||
|
let _ = tokio::fs::write(&conf_path, updated).await;
|
||||||
|
info!("Injected Bitcoin RPC auth into {}", conf_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build and start companion UI containers for headless services.
|
||||||
|
// All UIs proxy to localhost (backend :5678 or bitcoin :8332) so they need --network=host.
|
||||||
|
let ui_builds: Vec<(&str, &str, &str)> = match package_id {
|
||||||
"bitcoin" | "bitcoin-core" | "bitcoin-knots" => {
|
"bitcoin" | "bitcoin-core" | "bitcoin-knots" => {
|
||||||
vec![("bitcoin-ui", "/opt/archipelago/docker/bitcoin-ui", "localhost/bitcoin-ui", "8334:80")]
|
vec![("archy-bitcoin-ui", "/opt/archipelago/docker/bitcoin-ui", "bitcoin-ui")]
|
||||||
}
|
}
|
||||||
"lnd" => {
|
"lnd" => {
|
||||||
vec![("archy-lnd-ui", "/opt/archipelago/docker/lnd-ui", "localhost/lnd-ui", "8081:80")]
|
vec![("archy-lnd-ui", "/opt/archipelago/docker/lnd-ui", "lnd-ui")]
|
||||||
}
|
}
|
||||||
"electrumx" | "electrs" | "mempool-electrs" => {
|
"electrumx" | "electrs" | "mempool-electrs" => {
|
||||||
vec![("archy-electrs-ui", "/opt/archipelago/docker/electrs-ui", "localhost/electrs-ui", "50002:80")]
|
vec![("archy-electrs-ui", "/opt/archipelago/docker/electrs-ui", "electrs-ui")]
|
||||||
}
|
}
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
for (name, ui_dir, image, port) in ui_builds {
|
for (name, ui_dir, image_base) in ui_builds {
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
let ui_dir = ui_dir.to_string();
|
let ui_dir = ui_dir.to_string();
|
||||||
let image = image.to_string();
|
let image_base = image_base.to_string();
|
||||||
let port = port.to_string();
|
let registry = "80.71.235.15:3000/archipelago";
|
||||||
|
let registry_image = format!("{}/{}:latest", registry, image_base);
|
||||||
|
let local_image = format!("localhost/{}:latest", image_base);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if !std::path::Path::new(&ui_dir).exists() {
|
// Remove existing container
|
||||||
info!("UI source not found at {}, skipping", ui_dir);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
info!("Building UI container {} from {}", name, ui_dir);
|
|
||||||
let _ = tokio::process::Command::new("podman")
|
|
||||||
.args(["build", "-t", &image, &ui_dir])
|
|
||||||
.output()
|
|
||||||
.await;
|
|
||||||
let _ = tokio::process::Command::new("podman")
|
let _ = tokio::process::Command::new("podman")
|
||||||
.args(["rm", "-f", &name])
|
.args(["rm", "-f", &name])
|
||||||
.output()
|
.output()
|
||||||
.await;
|
.await;
|
||||||
let _ = tokio::process::Command::new("podman")
|
|
||||||
|
// Try registry image first, fall back to local build
|
||||||
|
let image = {
|
||||||
|
let pull = tokio::process::Command::new("podman")
|
||||||
|
.args(["pull", ®istry_image])
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
if pull.map_or(false, |o| o.status.success()) {
|
||||||
|
info!("Pulled {} UI from registry", name);
|
||||||
|
registry_image.clone()
|
||||||
|
} else if std::path::Path::new(&ui_dir).exists() {
|
||||||
|
info!("Registry pull failed, building {} from {}", name, ui_dir);
|
||||||
|
let build = tokio::process::Command::new("podman")
|
||||||
|
.args(["build", "-t", &local_image, &ui_dir])
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
match build {
|
||||||
|
Ok(o) if o.status.success() => local_image,
|
||||||
|
Ok(o) => {
|
||||||
|
warn!("Failed to build {}: {}", name,
|
||||||
|
String::from_utf8_lossy(&o.stderr));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to build {}: {}", name, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("No registry image or source for {} — skipping", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run with --network=host (UIs proxy to localhost backend/bitcoin)
|
||||||
|
let run = tokio::process::Command::new("podman")
|
||||||
.args([
|
.args([
|
||||||
"run", "-d",
|
"run", "-d",
|
||||||
"--name", &name,
|
"--name", &name,
|
||||||
"--restart=unless-stopped",
|
"--restart=unless-stopped",
|
||||||
"--network=archy-net",
|
"--network=host",
|
||||||
"--cap-drop=ALL",
|
"--cap-drop=ALL",
|
||||||
"--cap-add=NET_BIND_SERVICE",
|
"--cap-add=NET_BIND_SERVICE",
|
||||||
"--memory=64m",
|
"--memory=64m",
|
||||||
"-p", &port,
|
&image,
|
||||||
&format!("{}:latest", image),
|
|
||||||
])
|
])
|
||||||
.output()
|
.output()
|
||||||
.await;
|
.await;
|
||||||
info!("{} UI container started on port {}", name, port);
|
match run {
|
||||||
|
Ok(o) if o.status.success() => info!("{} UI container started (host network)", name),
|
||||||
|
Ok(o) => warn!("Failed to start {}: {}", name, String::from_utf8_lossy(&o.stderr)),
|
||||||
|
Err(e) => warn!("Failed to start {}: {}", name, e),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,6 +50,12 @@ impl DockerPackageScanner {
|
|||||||
"immich_redis",
|
"immich_redis",
|
||||||
"endurain-db",
|
"endurain-db",
|
||||||
"nextcloud-db",
|
"nextcloud-db",
|
||||||
|
"indeedhub-api",
|
||||||
|
"indeedhub-ffmpeg",
|
||||||
|
"indeedhub-postgres",
|
||||||
|
"indeedhub-redis",
|
||||||
|
"indeedhub-minio",
|
||||||
|
"indeedhub-relay",
|
||||||
"indeedhub-build_api_1",
|
"indeedhub-build_api_1",
|
||||||
"indeedhub-build_postgres_1",
|
"indeedhub-build_postgres_1",
|
||||||
"indeedhub-build_redis_1",
|
"indeedhub-build_redis_1",
|
||||||
|
|||||||
@ -5,9 +5,12 @@ use std::collections::HashMap;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::{OnceCell, RwLock};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
/// Cached remember secret — loaded once, never regenerated within a process.
|
||||||
|
static REMEMBER_SECRET: OnceCell<Vec<u8>> = OnceCell::const_new();
|
||||||
|
|
||||||
type HmacSha256 = Hmac<Sha256>;
|
type HmacSha256 = Hmac<Sha256>;
|
||||||
|
|
||||||
const FULL_SESSION_TTL: u64 = 86400; // 24 hours of inactivity
|
const FULL_SESSION_TTL: u64 = 86400; // 24 hours of inactivity
|
||||||
@ -391,21 +394,23 @@ impl SessionStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_or_create_remember_secret() -> Vec<u8> {
|
pub async fn load_or_create_remember_secret() -> Vec<u8> {
|
||||||
// Try existing secret file first
|
REMEMBER_SECRET.get_or_init(|| async {
|
||||||
if let Ok(secret) = tokio::fs::read(REMEMBER_SECRET_FILE).await {
|
// Try existing secret file first
|
||||||
if secret.len() == 32 {
|
if let Ok(secret) = tokio::fs::read(REMEMBER_SECRET_FILE).await {
|
||||||
return secret;
|
if secret.len() == 32 {
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// Generate a cryptographically random 32-byte secret on first boot
|
||||||
// Generate a cryptographically random 32-byte secret on first boot
|
let mut secret = [0u8; 32];
|
||||||
let mut secret = [0u8; 32];
|
rand::rngs::OsRng.fill_bytes(&mut secret);
|
||||||
rand::rngs::OsRng.fill_bytes(&mut secret);
|
// Ensure parent directory exists
|
||||||
// Ensure parent directory exists
|
if let Some(parent) = std::path::Path::new(REMEMBER_SECRET_FILE).parent() {
|
||||||
if let Some(parent) = std::path::Path::new(REMEMBER_SECRET_FILE).parent() {
|
let _ = tokio::fs::create_dir_all(parent).await;
|
||||||
let _ = tokio::fs::create_dir_all(parent).await;
|
}
|
||||||
}
|
let _ = tokio::fs::write(REMEMBER_SECRET_FILE, &secret).await;
|
||||||
let _ = tokio::fs::write(REMEMBER_SECRET_FILE, &secret).await;
|
secret.to_vec()
|
||||||
secret.to_vec()
|
}).await.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
FROM 80.71.235.15:3000/archipelago/nginx:1.27.4-alpine
|
FROM 80.71.235.15:3000/archipelago/nginx:1.27.4-alpine
|
||||||
COPY index.html /usr/share/nginx/html/
|
COPY index.html /usr/share/nginx/html/
|
||||||
COPY 50x.html /usr/share/nginx/html/
|
COPY 50x.html /usr/share/nginx/html/
|
||||||
COPY assets/ /usr/share/nginx/html/assets/
|
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
EXPOSE 8334
|
EXPOSE 8334
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
FROM 80.71.235.15:3000/archipelago/nginx:1.27.4-alpine
|
FROM 80.71.235.15:3000/archipelago/nginx:1.27.4-alpine
|
||||||
COPY index.html /usr/share/nginx/html/
|
COPY index.html /usr/share/nginx/html/
|
||||||
COPY 50x.html /usr/share/nginx/html/
|
COPY 50x.html /usr/share/nginx/html/
|
||||||
COPY assets/ /usr/share/nginx/html/assets/
|
COPY qrcode.js /usr/share/nginx/html/
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
EXPOSE 80
|
EXPOSE 50002
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Watch for Archipelago Tor management actions
|
Description=Watch for Archipelago Tor management actions
|
||||||
After=tor.service
|
|
||||||
|
|
||||||
[Path]
|
[Path]
|
||||||
PathExists=/var/lib/archipelago/tor-config/tor-action
|
PathExists=/var/lib/archipelago/tor-config/tor-action
|
||||||
|
|||||||
@ -36,14 +36,14 @@
|
|||||||
|
|
||||||
<!-- Word Grid -->
|
<!-- Word Grid -->
|
||||||
<div v-if="words.length > 0" class="w-full max-w-[600px]">
|
<div v-if="words.length > 0" class="w-full max-w-[600px]">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-3 md:grid-cols-4 gap-1.5 sm:gap-2">
|
<div class="grid grid-cols-2 sm:grid-cols-4 gap-1 sm:gap-1.5">
|
||||||
<div
|
<div
|
||||||
v-for="(word, i) in words"
|
v-for="(word, i) in words"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="bg-black/60 rounded-lg px-3 py-1.5 sm:py-2 border border-white/10"
|
class="bg-black/60 rounded-lg px-2.5 py-1 sm:py-1.5 border border-white/10"
|
||||||
>
|
>
|
||||||
<span class="text-white/40 text-[1rem] font-mono mr-1.5">{{ i + 1 }}.</span>
|
<span class="text-white/40 text-sm font-mono mr-1">{{ i + 1 }}.</span>
|
||||||
<span class="text-white/95 text-[1.2rem] font-mono">{{ word }}</span>
|
<span class="text-white/95 text-[1.05rem] font-mono">{{ word }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export const SERVICE_NAMES = new Set([
|
|||||||
'mysql-mempool', 'mempool-api', 'archy-mempool-web',
|
'mysql-mempool', 'mempool-api', 'archy-mempool-web',
|
||||||
'archy-bitcoin-ui', 'archy-lnd-ui', 'archy-electrs-ui',
|
'archy-bitcoin-ui', 'archy-lnd-ui', 'archy-electrs-ui',
|
||||||
'indeedhub-postgres', 'indeedhub-redis', 'indeedhub-minio',
|
'indeedhub-postgres', 'indeedhub-redis', 'indeedhub-minio',
|
||||||
|
'indeedhub-api', 'indeedhub-ffmpeg',
|
||||||
'indeedhub-relay', 'indeedhub-build_api_1', 'indeedhub-build_ffmpeg-worker_1',
|
'indeedhub-relay', 'indeedhub-build_api_1', 'indeedhub-build_ffmpeg-worker_1',
|
||||||
'indeedhub-build_postgres_1', 'indeedhub-build_redis_1', 'indeedhub-build_minio_1',
|
'indeedhub-build_postgres_1', 'indeedhub-build_redis_1', 'indeedhub-build_minio_1',
|
||||||
'indeedhub-build_minio-init_1', 'indeedhub-build_relay_1',
|
'indeedhub-build_minio-init_1', 'indeedhub-build_relay_1',
|
||||||
|
|||||||
@ -81,5 +81,10 @@ PENPOT_BACKEND_IMAGE="$ARCHY_REGISTRY/penpot-backend:2.4"
|
|||||||
PENPOT_EXPORTER_IMAGE="$ARCHY_REGISTRY/penpot-exporter:2.4"
|
PENPOT_EXPORTER_IMAGE="$ARCHY_REGISTRY/penpot-exporter:2.4"
|
||||||
PENPOT_FRONTEND_IMAGE="$ARCHY_REGISTRY/penpot-frontend:2.4"
|
PENPOT_FRONTEND_IMAGE="$ARCHY_REGISTRY/penpot-frontend:2.4"
|
||||||
|
|
||||||
|
# Custom UI containers (built from docker/ dirs, pushed to registry)
|
||||||
|
BITCOIN_UI_IMAGE="$ARCHY_REGISTRY/bitcoin-ui:latest"
|
||||||
|
LND_UI_IMAGE="$ARCHY_REGISTRY/lnd-ui:latest"
|
||||||
|
ELECTRS_UI_IMAGE="$ARCHY_REGISTRY/electrs-ui:latest"
|
||||||
|
|
||||||
# Base images
|
# Base images
|
||||||
NGINX_ALPINE_IMAGE="$ARCHY_REGISTRY/nginx:1.27.4-alpine"
|
NGINX_ALPINE_IMAGE="$ARCHY_REGISTRY/nginx:1.27.4-alpine"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user