Commit Graph
100 Commits
Author SHA1 Message Date
tiennm99 bd0fe78994 fix(gitea-mirror): keep gitea-mirror waiting as long as Gitea clones
Gitea's migrate API stays silent until the clone ends, and Bun's fetch
drops a connection idle for 5 minutes. gitea-mirror then marked large
repositories failed while Gitea kept cloning, and a later retry took the
half-made repository for a finished mirror. GITEA_CLONE_TIMEOUT now also
sets BUN_CONFIG_HTTP_IDLE_TIMEOUT.
2026-10-03 13:38:02 +07:00
tiennm99 efd0848143 feat(gitea-mirror): add cleanup of archived copies of deleted repos
Deletes the archived-* Gitea copies gitea-mirror keeps when a source in the
given owners disappears, and removes their tracking rows so they are not
re-mirrored. Dry run by default.
2026-10-03 13:09:05 +07:00
tiennm99 4576b4dfd5 fix(gitea-mirror): match failed mirrors by source name in detection
gitea-mirror clears mirroredLocation when a mirror fails, so failed repos
were dropped from the status map and reported as status unknown.
2026-10-03 13:09:05 +07:00
tiennm99 9a9e4cddf7 refactor(gitea-mirror): run mirror maintenance through tea and the gitea-mirror API
The skill reached Gitea on localhost and the mirror database inside the
container, which only worked on a local host. It now uses tea for Gitea
and the gitea-mirror API key for repository status and retries, in bash.
Private upstreams are no longer probed anonymously, which reported them
as deleted.
2026-10-03 11:23:32 +07:00
tiennm99 a84e158738 feat(gitea-mirror): raise the Gitea clone timeout to one hour
Gitea's default migrate and fetch timeouts cut multi-gigabyte mirrors off
mid-clone. GITEA_CLONE_TIMEOUT sets both, defaulting to 3600 seconds.
2026-10-03 11:23:32 +07:00
tiennm99 d4882c6f3e fix(gitea-mirror): read auth and encryption secrets from the environment
Data encrypted under one secret is unreadable under another, so the
secrets must move with the data instead of being regenerated by the image.
2026-10-03 10:07:34 +07:00
tiennm99 3c737b05cd chore: add sources dir and debug-service skill, make Coolify primary
sources/ holds gitignored upstream checkouts for debugging a service
against its real code; the debug-service skill walks through it. Coolify
is now the primary deployment target and Dokploy optional.
2026-10-03 09:58:52 +07:00
tiennm99 67da7cd870 feat(gitea-mirror): serve gitea and gitea-mirror through the proxy
Drop the localhost-bound ports, read the database password and both
public URLs from the environment, pin gitea to its major tag, add a gitea
health check and disable gitea's SSH server.
2026-10-03 09:58:52 +07:00
tiennm99 b9042fb191 refactor: rename remaining docker-compose.yml files to compose.yml 2026-09-30 15:46:52 +07:00
tiennm99 ea5079fd4e feat(opencode): pass OPENCODE_API_KEY for OpenCode Go and Zen 2026-09-30 15:46:52 +07:00
tiennm99 4c66ec76e6 docs: keep each service README inside its own directory
A service README now describes only its own service: no links to other
services or to the root, and no restating of the shared conventions that
the root README and CLAUDE.md already carry. Each service is a separate
Coolify app on a <service>/** watch path, so a cross-link made editing one
service redeploy another. The rule is recorded at the root; the alloy
compose comment now points at a heading that exists.
2026-09-29 20:00:15 +07:00
tiennm99 daf18abf70 refactor(opencode): rename service directory from opencode-web to opencode 2026-09-29 19:36:11 +07:00
tiennm99 d211e42a4a chore(code-server): install bubblewrap 2026-09-28 15:54:27 +07:00
tiennm99 1d98f2ea95 chore(diun): check for updates once a day at midnight UTC 2026-09-21 08:41:48 +07:00
tiennm99 9da8546d19 feat(goclaw): add multi-tenant AI agent gateway with pgvector postgres 2026-09-20 14:34:57 +07:00
tiennm99 c2b508c171 chore: set restart: unless-stopped on every service
Coolify injects the same value when a compose service omits one, but Dokploy
runs the file as written, so in its default compose mode an omitted policy
leaves the container down after a crash or a host reboot.
2026-09-20 14:34:57 +07:00
tiennm99 b626c5b70d chore(diun): track moving image tags instead of exact pins
Diun follows the upstream major tag 4; the socket proxy follows latest,
since that project publishes no moving major tag.
2026-09-20 12:50:04 +07:00
tiennm99 ad122ab9f2 chore: drop the openhands service
Remove the directory and its row from the root table, the related-services link
in opencode-web, and the workspace-volume convention's reference to it.
2026-09-19 17:44:55 +07:00
tiennm99 24cefe80a2 feat(diun): add diun template reading the Docker API through a proxy
Diun needs the Docker API to enumerate containers and inspect each one's
image. Mounting the socket into it directly is host-root-equivalent, and :ro on
a socket mount is cosmetic, so the socket goes into a docker-socket-proxy
sidecar and Diun reaches it at tcp://dockerproxy:2375. POST is revoked there,
so container create and exec return 403.

CONTAINERS and IMAGES are both required: with CONTAINERS alone the provider
loads and enumerates containers, then every ImageInspect returns 403 and
nothing is analysed. Verified against a live watch cycle — 25 images analysed,
no errors.

Pin crazymax/diun:4.33 rather than :latest, since this is the service whose job
is to talk to the daemon.
2026-09-19 17:18:31 +07:00
tiennm99 c2502d5f44 chore: drop ollama, netdata, per-service licenses and stale plans
Rename gitea-mirror-local to gitea-mirror.
2026-09-19 12:43:05 +07:00
tiennm99 e6dcdfef48 docs(plans): add openhands/opencode-web plan and paseo docs audit 2026-09-18 17:43:03 +07:00
tiennm99 14a5720f7a chore: drop code-server-base
code-server covers the remaining use.
2026-09-18 17:38:07 +07:00
tiennm99 a63c121075 feat(code-server): give the workspace its own volume
The workspace moves off the config volume onto code-server-workspace, so
wiping editor state and wiping code are separate acts.

The image only ever chowns the literal path /config/workspace, and reads
DEFAULT_WORKSPACE to pick the folder to open, so a named volume on /workspace
would come up root-owned and unwritable. Creating the directory in a local
Dockerfile seeds the volume with the right ownership instead.
2026-09-18 17:38:07 +07:00
tiennm99 ff9ec68b0b fix(alloy): drop read_only so the inline compose config can be created
Compose materialises a configs: entry with inline content by writing it into
the container and refuses to do so on a read-only service: "cannot create
config ... : `file` is the sole supported option". The container was created
without /etc/alloy/config.alloy and the deployment failed at start.

Keeping the config inline matters more than the read-only rootfs, so the flag
and its tmpfs go. Every other control stays: no privileged, cap_drop ALL with
only DAC_OVERRIDE added, no-new-privileges, the socket proxy, and the limits.
2026-09-18 17:36:12 +07:00
tiennm99 0ef059dc31 refactor(alloy): drop privileged and proxy the docker socket read-only
Replace privileged: true with cap_drop ALL plus DAC_OVERRIDE, no-new-privileges,
a read-only rootfs and memory/pid limits. DAC_OVERRIDE is what lets the uid-0
entrypoint create its storage directory and read the journal and /rootfs; every
other capability stays dropped.

Route prometheus.exporter.cadvisor, discovery.docker and loki.source.docker
through a docker-socket-proxy sidecar on 127.0.0.1:2375 instead of bind-mounting
the socket. POST is refused there, so container create and exec are no longer
reachable. NETWORKS is granted because Docker SD resolves network names per
container and returns no targets without it.

Add an alloy validate step to CI and boot the test container with the shipped
capability set, read-only rootfs and proxy rather than --privileged.
2026-09-18 17:28:08 +07:00
tiennm99 99f4f8b820 feat: add openhands and opencode-web services
openhands runs each agent session in a container it spawns through the host
docker socket, so the socket is mounted read-write and host.docker.internal is
resolved. No host workspace is exposed.

opencode-web serves the opencode agent as a browser UI. The vendor image ships
only the opencode binary on bare Alpine, so a local Dockerfile adds bash, git,
curl and an ssh client.
2026-09-18 17:04:04 +07:00
tiennm99 3574c55e0a chore(code-server): mount the docker socket read-only
The flag does not restrict Docker API access; the README says so.
2026-09-18 16:40:01 +07:00
tiennm99 a82cfcc677 refactor(paseo): leave the home chown to the base entrypoint
The base image ships /home/paseo owned by uid 1000 and declares it a
volume, so a fresh named volume is seeded with that ownership, and the
base entrypoint chowns it and the agent config directories when they are
not.
2026-09-18 14:36:53 +07:00
tiennm99 c20aed29cb feat(paseo)!: drop go and sdkman, rename AGENT_CLIS to AGENTS
The image installs python, gh, glab, build-essential, sudo, zsh and nano
only; go or a jvm goes into $HOME from a terminal instead.

SHELL and TZ are written into compose.yml rather than read from .env, so
the deploying shell's own values can no longer win over them.

The entrypoint calls chpasswd, chown and gosu by absolute path, tolerates
a chpasswd failure instead of taking the start down with it, turns
globbing off around the agent loop, and counts an agent as installed only
when its binary actually runs.
2026-09-18 11:39:09 +07:00
tiennm99 ac00eb9674 style: order environment variables by how much the service needs them
environment: blocks were in no particular order. They now run must-have ->
should-have -> optional, with related variables kept adjacent as a group that
takes the tier of its most important member: PUID/PGID, PASSWORD with
SUDO_PASSWORD, DOCKER_MODS ahead of the INSTALL_PACKAGES and
NODEJS_MOD_VERSION that configure it, the four GIT_* entries, the PASEO_*
daemon settings.

Each .env.example is reordered to match its compose file. The names do not map
one to one -- PASSWORD feeds both PASSWORD and SUDO_PASSWORD, SERVICE_HOSTNAME
feeds HOST -- so an entry sits where the first compose entry reading it sits.

The HOST comment in both compose files is dropped; the READMEs already carry
that explanation in full. CLAUDE.md records the ordering convention.

alloy and gitea-mirror-local are untouched: every variable there is required,
so the tiers collapse and the existing grouping is the better one.
2026-09-18 10:47:56 +07:00
tiennm99 ea46992e64 chore(code-server): drop the pnpm mod and trim installed packages
pnpm is not used anywhere -- npm is the package manager everywhere -- so the
mod that installs it is dead weight on every container start.

INSTALL_PACKAGES loses apache2-utils, bfs, ffmpeg, imagemagick, librsvg2-bin,
lsof, psmisc and ugrep, and gains glab. Each entry is re-resolved by apt on
every start, so the list is kept to what is actually reached for.
2026-09-18 10:47:46 +07:00
tiennm99 6924ba0424 docs: keep personal values out of the examples
Record the convention in CLAUDE.md: .env.example is a template, so its values
stay generic and the real ones are set per deployment in Coolify or Dokploy.
Note the naming trap alongside it -- Compose interpolation reads the deploying
shell's environment before the .env file, so a variable must not collide with
one the shell already exports.

The alloy README's example host is made generic to match.
2026-09-18 09:38:35 +07:00
tiennm99 2a4e635e09 fix: show the container hostname in the shell prompt
Coolify injects HOST=0.0.0.0 into every compose app, and zsh seeds $HOST and
the %m/%M prompt escapes from that variable rather than calling gethostname().
The prompt read "0", the first dot-separated field of 0.0.0.0, even though the
container hostname itself was set correctly.

Pass the hostname in as HOST alongside the hostname: key, both from a single
SERVICE_HOSTNAME variable. Neither service reads HOST itself -- code-server
binds [::]:8443, Paseo binds PASEO_LISTEN -- so this only affects the prompt.

Not named HOSTNAME: Compose interpolation lets the deploying shell's
environment win over the .env file, and HOSTNAME is set in every container,
including the one Coolify runs in.

PASEO_LABEL is renamed to SERVICE_HOSTNAME; it was never a Paseo variable.
The example git identity is blanked out along with it.
2026-09-18 09:35:04 +07:00
tiennm99 f02de334e5 feat(paseo): install SDKMAN on start, and rename the wrapper entrypoint
SDKMAN goes into ~/.sdkman whenever that directory is missing, the same way
the agent CLIs arrive: as paseo, through gosu, onto the volume, where `sdk
install` can write and the candidates persist. No variable gates it.

The chown of /home/paseo moves out of the AGENT_CLIS guard, since SDKMAN now
needs it even when no agent is named, and the agent loop reads AGENT_CLIS into
a local first so `set -u` does not trip on it being unset.

SDKMAN_DIR is set in the image, and the current/bin of java, scala, gradle,
maven and sbt joins PATH: `sdk` itself is a shell function from the rc hook and
so exists in terminals only, while the daemon and an agent's non-interactive
commands read no rc file and need the binaries on PATH.

paseo-sudo-entrypoint was named for the one thing it used to do. It is
/usr/local/bin/entrypoint now, matching the source file.
2026-09-17 14:21:25 +07:00
tiennm99 07991dabaf feat(code-server): mount the host docker socket
Bind-mount /var/run/docker.sock so the universal-docker mod's CLI has a
daemon to talk to, and document the sibling-container and permission
caveats in the service README.
2026-09-17 14:20:57 +07:00
tiennm99 f09325b2fc feat(code-server-base): add a stock code-server template
The LinuxServer image as it ships, with no mods and no extra packages,
as the starting point for a new instance.
2026-09-17 13:16:20 +07:00
tiennm99 feaf4b7e4c feat(paseo): install the agent CLIs named in AGENT_CLIS on start
The entrypoint runs each vendor's installer through gosu paseo for every name
in AGENT_CLIS whose command is not already on PATH, so a fresh paseo-home
volume comes up with agents ready. Defaults to claude and codex; the other
four are opt-in.

On start rather than in the Dockerfile: Docker seeds a named volume from the
image once, at creation, so a build-time install into /home/paseo would only
ever reach a volume that did not exist yet. The check also chowns /home/paseo
first, since a freshly created volume can arrive owned by root and the base
entrypoint has not run its own chown by then.

Unknown names and failed installs are logged and skipped rather than taking
the container down with them.

entrypoint.sh loses its explanations to the README along the way.
2026-09-17 10:36:51 +07:00
tiennm99 696c314726 docs: extend the comment rule to every file in a service directory
It was scoped to compose files and Dockerfiles, which left scripts out.
2026-09-17 10:36:51 +07:00
tiennm99 f39ca227ce feat(paseo): put the agent CLI install dirs on the image PATH
The daemon probes each provider's binary with `which` in its own
environment, which comes from the image and never sources a shell rc, so
agents installed into $HOME were reported unavailable.
2026-09-17 10:09:02 +07:00
tiennm99 55e8f7321d feat(paseo): install glab, and stop preinstalling the agent CLIs
The paseo user cannot write /usr/local, so a baked-in agent CLI can never
apply its own update — every one of them ships an updater that expects to
rewrite its own binary, and Claude Code nags about the failure at startup.
Installed under $HOME they update themselves and still persist, since that is
the paseo-home volume. The README now lists each vendor's installer.

Bun goes with them: it was only there because omp is compiled against it.

glab arrives as the .deb from GitLab's releases page, pinned by GLAB_VERSION
because the URL carries the version. GitLab runs no apt repository and calls
Homebrew its only officially supported Linux package manager.

The Dockerfile drops its explanations along the way; they are in the README.
2026-09-17 09:44:12 +07:00
tiennm99 044d9b5594 docs: keep rationale in READMEs, not in compose files and Dockerfiles
Comments say what a section installs or configures. Why not the distro
package, why that directory, why a version is pinned — that belongs in the
service README, where it can be read in full.

Replaces the line under "Installing software in an image" that asked for the
opposite.
2026-09-17 09:44:12 +07:00
tiennm99 92d24c8e64 feat(paseo): preinstall the build-essential C toolchain
The image had no compiler at all -- gcc, g++, make, cc and ld were all
missing -- so cgo, npm's node-gyp addons and Python C extensions could
not build. Goes in the existing apt layer to keep a single apt-get
update.
2026-09-16 22:55:56 +07:00
tiennm99 aa02fd2f5b chore: drop the openvpn-as and tastyigniter services
Remove both directories and their rows from the services table.
2026-09-16 20:57:04 +07:00
tiennm99 23b9bf8bf6 docs: describe current state only, and fill in the stub READMEs
Drop the history and roadmap asides: which services predate the collection's
conventions, the unwired Open Web UI plan, the generic clone-and-troubleshoot
boilerplate. Services that publish ports or set `restart:` now simply say so.

couchbase, openvpn-as and traffmonetizer had two-line READMEs; give them the
ports, variables and storage the root README promises. traffmonetizer reads
${TOKEN} and had no .env.example, so add one.
2026-09-16 20:45:18 +07:00
tiennm99 04ccc93969 feat(paseo): add Copilot, Pi and Oh My Pi agent CLIs
Six agents now ship in the image. All come from npm; omp additionally needs
Bun, since its bin is Bun-compiled and opens with `#!/usr/bin/env bun`.
BUN_INSTALL puts Bun in /usr/local, clear of the paseo-home volume.
2026-09-16 20:40:39 +07:00
tiennm99 65ec872e62 feat(paseo): add Codex and opencode-ai agent CLIs 2026-09-16 20:35:00 +07:00
tiennm99 d6f7eb46cd feat(paseo): preinstall nano and set the git identity from the environment
GIT_NAME and GIT_EMAIL expand into the four GIT_AUTHOR_*/GIT_COMMITTER_*
variables, the same shape code-server already uses. Passing the identity as
environment rather than running `git config` means agents and terminals commit
correctly with no setup step, and it does not depend on ~/.gitconfig surviving
in the /home/paseo volume.
2026-09-16 17:57:16 +07:00
tiennm99 cb1d38f147 feat(paseo): give the paseo user sudo, authenticated with PASEO_PASSWORD
The base image installs no sudo and leaves paseo out of the sudo group, so
add both. The password cannot be baked in at build time -- it is a secret and
would land in a layer -- and it cannot be set after the base entrypoint, which
ends in `exec gosu paseo` and never returns. Wrap that entrypoint instead and
set the password while still root, on every start: /etc/shadow lives in the
image, not on the /home/paseo volume, so it reverts on each recreate.
2026-09-16 17:21:11 +07:00
tiennm99 914474953f feat(paseo): select the terminal shell through SHELL
Paseo spawns terminals with process.env.SHELL and falls back to /bin/sh,
which is dash. It never consults the login shell, so chsh has no effect --
and would be reverted by the next rebuild regardless.
2026-09-16 16:35:49 +07:00
tiennm99 b796f098ca refactor(paseo): split the Dockerfile into one layer per concern
System packages, Python, Go, gh and the agent CLIs each get their own
block. Ordered least-changing first, so a Claude Code bump no longer
re-runs the toolchain installs.
2026-09-16 16:30:47 +07:00
tiennm99 5a4348cde7 feat(paseo): add Go, Python, shell tooling, hostname and timezone
Go 1.26.8 from the official tarball and Python 3.12 via uv, since Debian
12 carries 1.19 and 3.11. Both land outside $HOME, which the paseo-home
volume would otherwise mask.

Hostname and timezone come from the environment rather than being fixed in
the compose file. Paseo uses the container hostname as the host label in
its web UI, so without one the UI shows a random container ID.
2026-09-16 16:24:26 +07:00
tiennm99 6590c59f40 feat(paseo): install the gh CLI
Debian does not package gh, so use GitHub's signed apt repo. Config
lands in /home/paseo/.config/gh, inside the paseo-home volume, so the
login survives a redeploy.
2026-09-16 15:59:41 +07:00
tiennm99 341469ff43 docs(paseo): add setup steps and tighten the README
The pairing screen needs an explicit port, which is the least obvious
part of getting connected. Lead with the setup steps and cut the
explanation around them down to what a reader has to act on.
2026-09-16 15:49:30 +07:00
tiennm99 5edb865597 fix(paseo): trust the platform proxy so the web UI can connect
The daemon trusts X-Forwarded-Proto from loopback only by default, but
Coolify's Traefik reaches it from the Docker bridge network. It therefore
reported the request as plain HTTP and handed the UI useTls: false, so the
UI built a ws:// URL on an https:// page. The browser blocked it as mixed
content and the UI fell back to its built-in localhost:6767 default.

uniquelocal covers the private ranges Docker uses. An exact CIDR is
tighter but Coolify assigns a fresh subnet per project.
2026-09-16 15:26:52 +07:00
tiennm99 c1e10c3663 feat(paseo): add paseo service with Claude Code preinstalled
The upstream image ships no agent CLIs, so build from a local Dockerfile
that layers Claude Code on top. npm delivers the same native binary as
the standalone installer, which cannot be used here: it writes to
$HOME/.local, and $HOME is /home/paseo, a volume mount that masks
anything baked in at build time.

Runs as root by design -- the entrypoint chowns the mounted volumes and
then drops to the unprivileged paseo user with gosu.
2026-09-16 15:06:01 +07:00
tiennm99 2de7bb68ab docs: record compose.yml naming convention for new services
New services take the current Compose spec filename. Existing
docker-compose.yml files stay as they are so unrelated changes do not
carry a rename.
2026-09-16 15:06:01 +07:00
tiennm99 41fe522c0d docs: point netdata related-links at sibling services 2026-08-18 16:35:19 +07:00
tiennm99 1c07109f56 docs: list all absorbed services in the services table 2026-08-18 16:22:29 +07:00
tiennm99 85b35e449d docs: give each service its own README
Move the code-server details into code-server/README.md and reduce the
root README to shared conventions plus a table linking to each service.
2026-08-14 09:25:36 +07:00
tiennm99 34591b7e27 refactor(code-server): drop Codex access token
No longer used; removes the CODEX_ACCESS_TOKEN variable from the service
definition and its example env file.
2026-08-14 09:25:24 +07:00
tiennm99 550d1d1741 feat: add code-server service and repo scaffolding
Set up the per-service layout: each service directory holds compose.yml
with a committed .env.example and a gitignored .env.

Add code-server as the first service, plus a README and CLAUDE.md
documenting that these files target Coolify/Dokploy and deliberately
omit ports and restart policies.
2026-08-14 09:13:49 +07:00
tiennm99 0ab29485cb Initial commit 2026-08-14 08:57:46 +07:00
tiennm99 7a63d20424 feat: update 2025-03-25 22:04:48 +07:00
tiennm99 d1b1654cff feat: update 2025-03-15 07:48:27 +07:00
tiennm99 bf621b5315 Update docker-compose.yml 2025-03-15 07:38:20 +07:00
tiennm99 3e58b95b6c feat: update 2025-03-15 07:34:33 +07:00
tiennm99 12529023e1 feat: update 2025-03-15 07:19:10 +07:00
tiennm99 23501a2018 Update docker-compose.yml 2025-03-15 07:16:57 +07:00
tiennm99 247bb46f78 Update docker-compose.yml 2025-03-15 07:16:19 +07:00
tiennm99 fbcfaf47d8 fix: deploy fail 2025-03-15 06:54:24 +07:00
tiennm99 6481a83d2f feat: init 2025-03-15 06:46:20 +07:00
tiennm99 df94b9f400 Clear and rewrite
Retire the initial setup; history continues from tastyigniter-docker-compose.
2026-08-04 22:51:17 +07:00
tiennm99 83e15050e3 Clear and rewrite
Retire the docker-run setup; history continues from traffmonetizer-docker-compose.
2026-08-04 22:42:34 +07:00
tiennm99 44d3d75546 fix: run tea outside git work tree so --login is honored
Invoke-Tea started its job in the caller's directory. When that is a git
work tree, tea infers the target from the local remote, and a remote that
matches no configured login makes it discard --login, fall back to the
first login, and fail with "remote repository required". Pin the job to a
neutral directory so the requested login always resolves.
2026-08-01 20:33:56 +07:00
tiennm99 0134b64cde feat: add gitea mirror maintenance skill
Adds a skill to audit the local Gitea mirror stack for repos whose pull
failed, then clean up only what is safe to delete.

Detection combines four signals, since none is sufficient alone: the Gitea
API (empty repos with no completed initial pull), an upstream reachability
probe, the mirror app database, and the gitea container log. The container
log reflects a retention window rather than history, so it is never the
sole basis for deletion.

Deletion is gated on a contradiction between Gitea and the mirror app:
a repo that is empty while the app records the pull as finished. Repos the
app is still cloning or has queued look identical by API fields alone
(empty, zero size, no mirror timestamp), so they are excluded to avoid
destroying work in progress. Repos that still hold content are reported
for retry and never deleted, so a transient fetch error cannot cost a
mirror.

Deleting a broken repo also resets its mirror-app row to pending;
without that the app never re-pulls it and the mirror is lost instead of
restored. Cleanup is dry-run by default and warns on a stale plan.
2026-07-26 19:43:50 +07:00
tiennm99 2a3c5ad89f chore: always pull latest gitea-mirror image 2026-07-25 23:51:02 +07:00
tiennm99 21de313dec docs: add README and sample environment values
Document the compose services, published ports, volumes, and first-run
setup. Fill .env.example with sample values and note that compose.yml
does not yet consume them.
2026-07-25 18:45:18 +07:00
tiennm99 25aba88d0c chore: add gitea mirror docker compose setup
Compose stack for Gitea with Postgres and gitea-mirror, plus an
environment template listing the required configuration keys.
2026-07-25 18:08:56 +07:00
tiennm99 872d9ba5ea fix: bump alloy to v1.16.1 and align CI validator image 2026-05-31 10:41:57 +07:00
tiennm99 bad7082ac3 docs(readme): add customization section and related cluster links 2026-05-11 21:45:31 +07:00
tiennm99 2752b8fb5a docs: expand README — features table, GPU notes, smoke test 2026-05-11 20:44:53 +07:00
tiennm99 28cf2bd1f9 docs: flesh out README 2026-05-11 20:15:20 +07:00
tiennm99 6e79c6851d docs: add README 2026-05-11 17:04:17 +07:00
tiennm99 40d7b0e3fc fix: ship cadvisor working_set/rss metrics so memory panels reflect real usage
container_memory_usage_bytes counts page cache attributed to the cgroup,
which makes disk-heavy containers (e.g. gitea) appear to use ~all host RAM.
Add container_memory_working_set_bytes (the metric Grafana's Docker
integration dashboard expects), plus container_memory_rss,
container_memory_cache, and container_spec_memory_limit_bytes for
breakdown and limit-percentage panels.
2026-04-29 09:59:19 +07:00
tiennm99 2a24eaf10b fix: drop explicit journal path and bump alloy to v1.16.0
`loki.source.journal "default"` no longer pins `path = "/var/log/journal"`.
Upstream omits the field, which lets Alloy default to BOTH
`/var/log/journal` (persistent) and `/run/log/journal` (volatile). The
explicit path silently dropped journal logs on hosts with volatile-only
storage. Matches the canonical Linux Node integration template.

Also bumps the image six minor versions to current stable. Doc records
the 2026-04-26 re-audit.
2026-04-26 10:15:29 +07:00
tiennm99 9399a8b115 feat: keep-list verbatim from each integration's Metrics section
Copies the exact 157-metric list from the Linux Node integration's
Metrics anchor as the cadvisor keep-list already does for Docker
(16 metrics). Replaces the earlier `drop node_scrape_collector_.+`
rule, which was the integration page's alternate snippet but didn't
ship the explicit allowlist users see in the docs.

`instance:node_num_cpu:sum` from the Metrics section is intentionally
omitted — it's a recording-rule output computed server-side by
Grafana Cloud's ruler, not produced by the agent.

Doc + README updated to point at the Metrics anchors directly so the
source of truth is unambiguous.
2026-04-26 09:54:43 +07:00
tiennm99 c1db79a359 docs: codify upstream-sources-only rule for config decisions
Adds docs/upstream-sources-of-truth.md as the binding policy for what
this repo follows when deciding metrics, labels, log pipelines, and
dashboards to ship.

Hard rule: only tier 1-4 official sources (Grafana Cloud integration
docs, github.com/grafana/*, github.com/prometheus/*, the user's own
authenticated Grafana Cloud API). No third-party Terraform exports,
community gists, blog posts, or AI summaries — even when names match.

Records a tier-1+2 audit confirming the current cadvisor allowlist
matches both the Docker integration page and grafana/jsonnet-libs
docker-mixin/docker.json. Notes that tier-4 verification against the
live stack's full integration dashboard set was not performed and is
the only known gap.
2026-04-26 09:50:20 +07:00
tiennm99 54ab1fed27 feat: align metric/log collection with upstream Grafana Cloud integrations
Linux-Node integration:
- replace curated keep-list of ~140 node_* metrics with the upstream
  drop rule (drops only node_scrape_collector_*); ships the full
  ~130+ metric set the integration dashboards expect.
- add loki.source.file for /var/log/{syslog,messages,*.log} alongside
  the existing journal scrape, matching the upstream config.
- broaden the /var/log mount to cover both pipelines (was journal only).

Docker integration:
- drop container_memory_working_set_bytes from the cadvisor allowlist;
  not part of the documented metric set.

README: refresh "What it collects" + "Mounts" tables, document the
syslog-vs-journald duplication caveat for rsyslog hosts.
2026-04-26 09:24:44 +07:00
tiennm99 fd8cf058b2 docs: add Coolify SSH session spam runbook
Document a Coolify-specific noise pattern observed in the journal
pipeline: ~300 root sessions/hour from the Coolify host's connection
checks. Verified against coollabsio/coolify v4.x source (Kernel.php,
ServerManagerJob, ServerCheckJob, SshMultiplexingHelper).

Includes:
- exact call flow and skip conditions per Coolify source
- triage commands and key-fingerprint matcher
- two mitigations: drop at Alloy (loki.process stage.drop) or enable
  Sentinel server-side to bypass the SSH polling entirely
- framing: Coolify-only, base setup unchanged
2026-04-26 09:21:11 +07:00
tiennm99 513f688ea0 ci: bind alloy smoke test to real port (clustering needs non-zero) 2026-04-25 19:13:22 +07:00
tiennm99 b1911c0538 ci: make alloy smoke-test robust to runtime errors
Previous step failed in CI because the bare alloy container had no
/var/log/journal, no docker.sock, etc., so loki.source.journal +
discovery.docker exited the process — and --rm wiped the container
before logs could be inspected.

Now: drop --rm, mount the same host paths the prod compose uses, plus
an empty /var/log/journal stand-in. Capture logs unconditionally and
fail only on config-level patterns ('unknown component',
'undefined reference', 'syntax error', etc.). Runtime/component
failures against dummy endpoints are tolerated.
2026-04-25 19:12:05 +07:00
tiennm99 8a7f156487 fix: address review findings (network ns, journal path, CI semantics)
- network_mode: host so prometheus.exporter.unix reports real host
  interfaces (eth0...) rather than the alloy container's veth pair.
- loki.source.journal: set path = "/var/log/journal" explicitly so it
  doesn't silently fall through to /run/log/journal on volatile-journal
  hosts.
- cadvisor keep-list: add container_memory_working_set_bytes (drives
  several panels on the standard Docker dashboard).
- Drop /dev/kmsg device + extra_hosts:host.docker.internal — neither is
  needed by the current keep-lists, and host-network mode makes the
  extra_hosts entry meaningless.
- CI: extend Alloy validation beyond `fmt` (syntax-only) by booting
  alloy with the embedded config and asserting it stays running, which
  catches bad component refs / wrong arg names that fmt accepts.
- README: refresh Mounts table + Security note to match.
2026-04-25 19:09:49 +07:00
tiennm99 fed5d6f8c7 fix: point node_exporter at host bind mounts; drop pid:host
prometheus.exporter.unix now reads /rootproc and /rootfs (the existing
host bind mounts) instead of the container's own namespace, so the
filesystem + process metrics actually describe the host. This makes
pid:host unnecessary, so remove it — privileged is enough and pid:host
exposes every host process inside the container.
2026-04-25 11:20:03 +07:00
tiennm99 a63d142b53 feat: update cadvisor keep-list and add pid:host + machine-id mount
cadvisor regex: drop fs_inodes/fs_limit/network_tcp_usage; add fs_reads
+ fs_writes + network_(receive|transmit)_(errors|packets_dropped)_total
to match the standard Grafana Cloud docker integration dashboard.

Compose:
- pid: host so prometheus.exporter.unix sees host /proc (cpu, mem,
  load, processes) instead of the container's namespace.
- mount /etc/machine-id so loki.source.journal has a stable host id.
2026-04-25 11:16:13 +07:00
tiennm99 ac4c785053 ci: add REMOTECFG_* dummy vars so compose config passes
The compose file gained three new :?required guards
(REMOTECFG_URL/ID/USER) that the validate workflow was not exporting.
2026-04-25 10:51:16 +07:00
tiennm99 94c63452eb feat: merge linux+docker alloy configs and source creds from env
Embed unified config.alloy via compose configs, combining node_exporter
+ journal (linux) and cadvisor + docker logs (docker) collectors into one
container. Add remotecfg block for grafana fleet-management. Replace
hardcoded credentials with sys.env() reads of nine shell variables
(ALLOY_HOSTNAME, REMOTECFG_*, PROM_*, LOKI_*, GRAFANA_TOKEN).
2026-04-25 10:49:07 +07:00
tiennm99 f009b37ca0 fix: pass /dev/kmsg via devices: so cgroup allows the read
bind-mounting /dev/kmsg under volumes: creates the node but leaves the
device-cgroup controller blocking the read (EPERM). cap_drop=[ALL]
clears the default device allow-list, so even with CAP_SYSLOG the
kernel refuses. moving it under devices: adds the cgroup allow rule
alongside the bind-mount, which is what cadvisor actually needs.
2026-04-24 14:35:40 +07:00
tiennm99 9ca8c2a65b fix: mount /dev/kmsg and /run/udev/data for cadvisor + diskstats
clears two startup warnings:
 - cadvisor "Could not configure a source for OOM detection" — needs
   /dev/kmsg bind-mount and CAP_SYSLOG (kernel.dmesg_restrict=1 default)
 - node-exporter "Failed to open /run/udev/data" — diskstats collector
   enriches node_disk_* with model/serial/WWN labels from udev

both mounted read-only. CAP_SYSLOG added alongside DAC_OVERRIDE.
2026-04-24 14:24:00 +07:00
tiennm99 01261b18b0 fix: drop docker logs older than 6d before sending to loki
grafana cloud loki rejects entries older than 7 days with HTTP 400.
loki.source.docker has no "tail since" option, so on first start it
replays logs from each container's start time — long-running
containers (coolify-proxy, traffmonetizer) produced weeks of backlog
that loki refused to ingest.

insert a loki.process drop stage (older_than=144h) between the docker
source and loki.write.gc so stale entries are filtered before egress.
journal source already bounded by max_age=12h — no filter needed there.
2026-04-24 14:20:50 +07:00
tiennm99 59bf58a412 fix: grant DAC_OVERRIDE so alloy can write its data dir
container runs as root (0:0) but cap_drop=[ALL] stripped DAC_OVERRIDE,
so mkdir on /var/lib/alloy/data (alloy-owned in the image) failed with
permission denied. add it back (only DAC_OVERRIDE, nothing else) and
mount /etc/machine-id ro for a stable journal host id. annotate every
volume with the component that uses it.
2026-04-24 13:56:37 +07:00